USB 3G網(wǎng)卡驅(qū)動(dòng)流程 簡介 首先介紹一下linux下的整體驅(qū)動(dòng)模式: 本文基于的linux kernel版本為2.6.36 (并且華為EM770W驅(qū)動(dòng),是由FriendlyARM公司定制的。 所以該部分驅(qū)動(dòng)可以在友善的官方網(wǎng)站上下載。其宏定義為CONFIG_MACH_MINI6410) 總線,設(shè)備,驅(qū)動(dòng):三者是互相關(guān)聯(lián)的,在總線上有設(shè)備列表,和驅(qū)動(dòng)列表。當(dāng)一個(gè)設(shè)備接入時(shí),會(huì)在 總線上遍歷所有的驅(qū)動(dòng),知道找到支持他的驅(qū)動(dòng)。然后就會(huì)將設(shè)備結(jié)構(gòu)體下的driver指針指向該驅(qū)動(dòng)。 同樣,驅(qū)動(dòng)也會(huì)將該設(shè)備加入自己的設(shè)備指針列表。 3G網(wǎng)卡是一個(gè)USB設(shè)備,那么就介紹一下USB驅(qū)動(dòng)。 總線 Usb總線結(jié)構(gòu)體 struct bus_type usb_bus_type ={}; //那我們就來看看總線的結(jié)構(gòu)體 [include/linux/device.h]文件中 struct bus_type { struct kset *devices_kset; struct klist klist_devices; struct klist klist_drivers; struct blocking_notifier_head bus_notifier; unsigned int drivers_autoprobe:1; struct bus_type *bus; }; 其中的klist_devices, klist_drivers分別為總線上的設(shè)備和驅(qū)動(dòng)列表。當(dāng)有新的設(shè)備接入,或加載新的 驅(qū)動(dòng)時(shí)。就會(huì)在這兩個(gè)列表中遍歷。 設(shè)備 這里就以u(píng)sb_device為例 [include/linux/usb.h]文件中 struct usb_device { …... struct usb_bus *bus; //指向該設(shè)備的總線 …... struce device dev; //linux下通用設(shè)備的屬性 }; [include/linux/device.h]文件中 struct device { …... struct device_driver *driver; /* which driver has allocated this device */ …... }; 這里就有設(shè)備指向的總線和驅(qū)動(dòng)指針了。一個(gè)設(shè)備之用一個(gè)驅(qū)動(dòng)。下面的驅(qū)動(dòng),所支持的設(shè)備就是個(gè)列 表了。驅(qū)動(dòng)可以支持一系列的設(shè)備。 驅(qū)動(dòng) 以opton_driver為例[drivers/usb/serial/option.c] static struct usb_driver option_driver = { .name = "option", .probe = usb_serial_probe, .disconnect = usb_serial_disconnect, #ifdef CONFIG_PM .suspend = usb_serial_suspend, .resume = usb_serial_resume, .supports_autosuspend = 1, #endif .id_table = option_ids, .no_dynamic_id = 1, }; [include/linux/usb.h]文件中 struct usb_driver { const char *name; …... struct usbdrv_wrap drvwrap; …... }; struct usbdrv_wrap { struct device_driver driver; int for_devices; }; [include/linux/device.h]文件中 struct device_driver { …... struct bus_type *bus; //驅(qū)動(dòng)所屬的總線 …... struct driver_private *p; …... }; [drivers/base/base.h]文件中 struct driver_private { struct kobject kobj; struct klist klist_devices; //正在使用此驅(qū)動(dòng)的設(shè)備列表 struct klist_node knode_bus; struct module_kobject *mkobj; struct device_driver *driver; }; option驅(qū)動(dòng)總線賦值 [drivers/usb/serial/option.c]中option初始化時(shí),注冊(cè)過程會(huì)將驅(qū)動(dòng)所屬總線賦值 static int __init option_init(void) { retval = usb_register(&option_driver); } [include/linux/usb.h]中 static inline int usb_register(struct usb_driver *driver) { return usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME); } [drivers/usb/core/driver.c]文件中 int usb_register_driver(struct usb_driver *new_driver, struct module *owner, const char *mod_name) { new_driver->drvwrap.driver.bus = &usb_bus_type; new_driver->drvwrap.driver.probe = usb_probe_interface; } 圖例 以華為EM770W為例 以HUAWEI EM770W WCDMA 3G網(wǎng)卡為例。 在內(nèi)核文件linux/drivers/usb/serial/option.c中。 static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600, 0xff, 0xff, 0xff) }, } 有此行定義,在設(shè)備EM770W中讀取出的.idProduct與 HUAWEI_PRODUCT_E600定義的值相同。 當(dāng)一個(gè)USB設(shè)備插入時(shí),會(huì)觸發(fā)hub_event時(shí)間調(diào)用函數(shù) 一: 添加usb設(shè)備 hub_events(); -->hub_port_connect_change(); --> udev = usb_alloc_dev(hdev, hdev->bus, port1); //將&usb_bus_type分配給設(shè)備 usb_new_device(udev); -->device_add(&udev->dev);(drivers/base/core.c) -->bus_add_device(dev); //將設(shè)備加入總線設(shè)備列表 //klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); bus_probe_device(dev); -->device_attach(dev);[drivers/base/dd.c] -->bus_for_each_drv(dev->bus, NULL, dev, __device_attach); // 遍歷驅(qū)動(dòng),找到支持此設(shè)備的 驅(qū)動(dòng) -->__device_attach(drv, dev); // 找的驅(qū)動(dòng)usb 支持此設(shè)備 -->driver_probe_device(drv, dev); -->really_probe(dev, drv); [drivers/base/dd.c] -->drv->probe(dev); // 實(shí)際調(diào)用 usb_probe_device(dev);[drivers/usb/core/driver.c] -->udriver->probe(udev); //實(shí)際上調(diào)用的 generic_probe(udev); [drivers/usb/core/generic.c] -->usb_set_configuration(udev, c); [drivers/usb/core/message.c] -->nintf = cp->desc.bNumInterfaces; // 這里首先得到設(shè)備的Interface數(shù)量,下面循環(huán)添加每個(gè) Interface for (i = 0; i < nintf; ++i) { ret = device_add(&intf->dev); } 二: 添加interface 下面進(jìn)入每個(gè)interface的添加設(shè)備過程,在這里單獨(dú)列出 device_add(&intf->dev); -->bus_probe_device(dev); -->device_attach(dev); [drivers/base/dd.c] -->bus_for_each_drv(dev->bus, NULL, dev, __device_attach); // 遍歷驅(qū)動(dòng),找到支持此設(shè)備的 驅(qū)動(dòng) -->__device_attach(drv, dev); // 遍歷到option驅(qū)動(dòng)時(shí),在option_ids[]結(jié)構(gòu)體中,找到該驅(qū)動(dòng)支 持此設(shè)備 -->driver_probe_device(drv, dev); -->really_probe(dev, drv); [drivers/base/dd.c] -->drv->probe(dev); //這里實(shí)際調(diào)用的函數(shù)為 usb_probe_interface(dev); -->driver->probe(inif, id); //這里實(shí)際調(diào)用的函數(shù)為 usb_serial_probe(interface, id); [drivers/usb/serial/usb-serial.c] //這里檢測到使用pl2303 convertor, 并執(zhí)行pl2303的attach函數(shù) /***************** *下面這部分做了許多事情。 * 創(chuàng)建一個(gè)usb_serial數(shù)據(jù)結(jié)構(gòu),增加一個(gè)interface接口設(shè)備,找到該接口的驅(qū)動(dòng)(如pl2303),設(shè)置 interface的endpoint等 ******************/ -->type = search_serial_device(interface); //這里找到匹配的pl2303驅(qū)動(dòng) 參看下一節(jié)【USB 檢 測pl2303驅(qū)動(dòng)】 serial = create_serial(dev, interface, type); //創(chuàng)建serial, 將type驅(qū)動(dòng)賦值給新創(chuàng)建的serial- >type,即驅(qū)動(dòng)指向?yàn)閜l2303 port = serial->port[i]; dev_set_name(&port->dev, "ttyUSB%d", port->number); device_add(&port->dev); //調(diào)用增加設(shè)備 ttyUSB0 -->bus_probe_device(dev); -->...如上... -->really_probe(dev, drv); [drivers/base/dd.c] -->dev->bus->probe(dev); // ***** 這里實(shí)際上調(diào)用的就是usb_serial_device_probe(dev); -->tty_register_device(usb_serial_tty_driver, minor, dev); //這里會(huì)再次調(diào)用 device_add(dev); 函數(shù) USB 檢測pl2303驅(qū)動(dòng) USB的串行設(shè)備也分為很多種類。如pl2303,ViVOpay usb_serial_probe(interface, id);[drivers/usb/serial/usb-serial.c] -->type = search_serial_device(interface); //根據(jù)提供的interface接口, //獲得了 static struct usb_serial_driver pl2303_device ={} [drivers/usb/serial/pl2303.c] -->list_for_each_entry(drv, &usb_serial_driver_list, driver_list) { id = get_iface_id(drv, iface); if (id) return drv; } pl2303驅(qū)動(dòng)的注冊(cè)過程 static int __init pl2303_init(void) { retval = usb_serial_register(&pl2303_device); } [drivers/usb/serial/usb-serial.c] 在注冊(cè)函數(shù)中,將pl2303驅(qū)動(dòng)加入usb serial驅(qū)動(dòng)列表 int usb_serial_register(struct usb_serial_driver *driver) { list_add(&driver->driver_list, &usb_serial_driver_list); } 三: 添加endpoint // 至此,整個(gè)調(diào)用逐步返回! 最后在usb_set_configuration(udev, c); [drivers/usb/core/message.c] 函數(shù)返回時(shí)調(diào)用 -->create_intf_ep_devs(intf); //增加endpoint設(shè)備 -->for (i = 0; i < alt->desc.bNumEndpoints; ++i) (void) usb_create_ep_devs(&intf->dev, &alt->endpoint[i], udev); -->retval = device_register(&ep_dev->dev); 3G網(wǎng)卡如何讀寫呢 在pl2303驅(qū)動(dòng)加載完成后,生成的設(shè)備ttyUSB0, ttyUSB1, ttyUSB2后,即可以對(duì)這些設(shè)備進(jìn)行操 作。如open,close,read,write,ioctl等命令 pl2303.c中有如下函數(shù),即當(dāng)打開網(wǎng)卡設(shè)備時(shí),就會(huì)調(diào)用此函數(shù)。 static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) { } 在板子的可使用3G WCDMA網(wǎng)卡上,因?yàn)殚_發(fā)板為3G網(wǎng)卡建立3個(gè)USB設(shè)備 ttyUSB0,ttyUSB1,ttyUSB2 所以在打開時(shí),會(huì)調(diào)用3次該函數(shù)。 大概流程為 static int serial_open(struct tty_struct *tty, struct file *filp) -->tty_port_open(&port->port, tty, filp); -->port->ops->activate(port, tty); //此處調(diào)用的為static int serial_activate(struct tty_port *tport, struct tty_struct *tty) -->port->serial->type->open(tty, port); //這里即是調(diào)用static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) 從而對(duì)將設(shè)備打開,可以對(duì)設(shè)備進(jìn)行讀寫,控制等操作。 設(shè)備識(shí)別 在設(shè)備插入后,以HUAWEI ET127 3G網(wǎng)卡為例。插入設(shè)備后會(huì)有如下的log scsi 0:0:0:0: CD-ROM HUAWEI Mobile CMCC CD 1.25 PQ: 0 ANSI: 0 大唐的AirCard 901插入后,同樣會(huì)有l(wèi)og如下: csi 1:0:0:0: CD-ROM AirCard SetupDisk 1.00 PQ: 0 ANSI: 2 這時(shí),友善開發(fā)板將此設(shè)備識(shí)別成為一個(gè)光盤,即儲(chǔ)存設(shè)備。這時(shí),如果在ubuntu下,可以使用 usb_modeswitch將usb設(shè)備進(jìn)行模式轉(zhuǎn)換。然后驅(qū)動(dòng)3G網(wǎng)卡。我在網(wǎng)上搜索到一篇文章。說可以驅(qū) 動(dòng)大部分3G網(wǎng)卡,就此我問過胡菲菲。此前驅(qū)動(dòng)大唐的AirCard 901用的就是此種方法。 http://:81/Linux/2011-03/33430.htm 我昨天嘗試按照這些步驟進(jìn)行實(shí)現(xiàn)。但是遇到兩個(gè)問題 1:arm 卡發(fā)板上使用的是busybox, 和一些應(yīng)用程序(如:dhcpcd)在Android系統(tǒng)下編譯。我編譯的 usb_modeswitch無法運(yùn)行 2:插入usb 3G設(shè)備后,arm開發(fā)板識(shí)別為sr0, sr1設(shè)備。不知道該如何將此設(shè)備退出?;蛟S usb_modeswitch轉(zhuǎn)換完成后,即可以進(jìn)行AT命令通信。還需要進(jìn)行試驗(yàn)才能知道。 usb_modeswitch作用 USB_ModeSwitch 是控制"flip flop"(多重設(shè)備)USB裝置的模式轉(zhuǎn)換工具。作用有如下的幾種: 1. Usb 閃存模式:提取和安裝驅(qū)動(dòng) 2. 轉(zhuǎn)換模式:儲(chǔ)存設(shè)備模式轉(zhuǎn)換為所需(如3G)設(shè)備模式 3. 所需設(shè)備模式:使用設(shè)備新的功能 詳細(xì)的可以參看usb_modeswitch的README文檔。 一些術(shù)語 USB 端點(diǎn)(endpoint) 端點(diǎn)是由廠商在USB設(shè)備內(nèi)部建立的,因此是永久存在的。端點(diǎn)(endpoint)和主控制器(host controller)基于管道(pipe)連接。 一個(gè)端點(diǎn)只能單向的(in/out)傳輸數(shù)據(jù)。 端點(diǎn)是以interface來分組的。每一個(gè)interface對(duì)應(yīng)著一個(gè)設(shè)備功能。 USB Interface endpoint(端點(diǎn))是以interface來分組的。比如華為的WCDMA是以每三個(gè)ep為一組,隸屬于一個(gè) interface。而且每一個(gè)interface對(duì)應(yīng)著一個(gè)單獨(dú)的設(shè)備功能。如華為ET127 TD-CDMA 3G網(wǎng)卡其 中一個(gè)interface設(shè)備命名為ttyUSB_utps_mms,對(duì)應(yīng)的功能應(yīng)該即為短信發(fā)送/接收。 USB設(shè)備號(hào) //WCDMA 華為EM770W在ubuntu 10.04下查看 crw-rw---- 1 root dialout 188, 1 2011-05-04 16:19 ttyUSB_utps_diag crw-rw---- 1 root dialout 188, 0 2011-05-04 16:19 ttyUSB_utps_modem crw-rw---- 1 root dialout 188, 2 2011-05-04 16:21 ttyUSB_utps_pcui //TD-SCDMA 華為ET127在ubuntu 10.04下查看 crw-rw---- 1 root dialout 166, 1 2011-05-04 16:23 ttyUSB_utps_mms crw-rw---- 1 root dialout 166, 0 2011-05-04 16:23 ttyUSB_utps_modem crw-rw---- 1 root dialout 166, 2 2011-05-04 16:23 ttyUSB_utps_pcui idVendor=12d1, idProduct=1da1 大唐TD-SCDMA設(shè)備參數(shù) idVendor=1ab7, idProduct=0301 主設(shè)備號(hào)含義 ACM調(diào)制解調(diào)器:主設(shè)備號(hào) 166 USB打印機(jī): 主設(shè)備號(hào) 180(次設(shè)備號(hào)0~15) USB串口: 主設(shè)備號(hào) 188 |
|