USB 3G网卡驱动流程 简介 首先介绍一下linux下的整体驱动模式: 本文基于的linux kernel版本为2.6.36 (并且华为EM770W驱动,是由FriendlyARM公司定制的。 所以该部分驱动可以在友善的官方网站上下载。其宏定义为CONFIG_MACH_MINI6410) 总线,设备,驱动:三者是互相关联的,在总线上有设备列表,和驱动列表。当一个设备接入时,会在 总线上遍历所有的驱动,知道找到支持他的驱动。然后就会将设备结构体下的driver指针指向该驱动。 同样,驱动也会将该设备加入自己的设备指针列表。 3G网卡是一个USB设备,那么就介绍一下USB驱动。 总线 Usb总线结构体 struct bus_type usb_bus_type ={}; //那我们就来看看总线的结构体 [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分别为总线上的设备和驱动列表。当有新的设备接入,或加载新的 驱动时。就会在这两个列表中遍历。 设备 这里就以usb_device为例 [include/linux/usb.h]文件中 struct usb_device { …... struct usb_bus *bus; //指向该设备的总线 …... struce device dev; //linux下通用设备的属性 }; [include/linux/device.h]文件中 struct device { …... struct device_driver *driver; /* which driver has allocated this device */ …... }; 这里就有设备指向的总线和驱动指针了。一个设备之用一个驱动。下面的驱动,所支持的设备就是个列 表了。驱动可以支持一系列的设备。 驱动 以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; //驱动所属的总线 …... struct driver_private *p; …... }; [drivers/base/base.h]文件中 struct driver_private { struct kobject kobj; struct klist klist_devices; //正在使用此驱动的设备列表 struct klist_node knode_bus; struct module_kobject *mkobj; struct device_driver *driver; }; option驱动总线赋值 [drivers/usb/serial/option.c]中option初始化时,注册过程会将驱动所属总线赋值 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网卡为例。 在内核文件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) }, } 有此行定义,在设备EM770W中读取出的.idProduct与 HUAWEI_PRODUCT_E600定义的值相同。 当一个USB设备插入时,会触发hub_event时间调用函数 一: 添加usb设备 hub_events(); -->hub_port_connect_change(); --> udev = usb_alloc_dev(hdev, hdev->bus, port1); //将&usb_bus_type分配给设备 usb_new_device(udev); -->device_add(&udev->dev);(drivers/base/core.c) -->bus_add_device(dev); //将设备加入总线设备列表 //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); // 遍历驱动,找到支持此设备的 驱动 -->__device_attach(drv, dev); // 找的驱动usb 支持此设备 -->driver_probe_device(drv, dev); -->really_probe(dev, drv); [drivers/base/dd.c] -->drv->probe(dev); // 实际调用 usb_probe_device(dev);[drivers/usb/core/driver.c] -->udriver->probe(udev); //实际上调用的 generic_probe(udev); [drivers/usb/core/generic.c] -->usb_set_configuration(udev, c); [drivers/usb/core/message.c] -->nintf = cp->desc.bNumInterfaces; // 这里首先得到设备的Interface数量,下面循环添加每个 Interface for (i = 0; i < nintf; ++i) { ret = device_add(&intf->dev); } 二: 添加interface 下面进入每个interface的添加设备过程,在这里单独列出 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); // 遍历驱动,找到支持此设备的 驱动 -->__device_attach(drv, dev); // 遍历到option驱动时,在option_ids[]结构体中,找到该驱动支 持此设备 -->driver_probe_device(drv, dev); -->really_probe(dev, drv); [drivers/base/dd.c] -->drv->probe(dev); //这里实际调用的函数为 usb_probe_interface(dev); -->driver->probe(inif, id); //这里实际调用的函数为 usb_serial_probe(interface, id); [drivers/usb/serial/usb-serial.c] //这里检测到使用pl2303 convertor, 并执行pl2303的attach函数 /***************** *下面这部分做了许多事情。 * 创建一个usb_serial数据结构,增加一个interface接口设备,找到该接口的驱动(如pl2303),设置 interface的endpoint等 ******************/ -->type = search_serial_device(interface); //这里找到匹配的pl2303驱动 参看下一节【USB 检 测pl2303驱动】 serial = create_serial(dev, interface, type); //创建serial, 将type驱动赋值给新创建的serial- >type,即驱动指向为pl2303 port = serial->port[i]; dev_set_name(&port->dev, "ttyUSB%d", port->number); device_add(&port->dev); //调用增加设备 ttyUSB0 -->bus_probe_device(dev); -->...如上... -->really_probe(dev, drv); [drivers/base/dd.c] -->dev->bus->probe(dev); // ***** 这里实际上调用的就是usb_serial_device_probe(dev); -->tty_register_device(usb_serial_tty_driver, minor, dev); //这里会再次调用 device_add(dev); 函数 USB 检测pl2303驱动 USB的串行设备也分为很多种类。如pl2303,ViVOpay usb_serial_probe(interface, id);[drivers/usb/serial/usb-serial.c] -->type = search_serial_device(interface); //根据提供的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驱动的注册过程 static int __init pl2303_init(void) { retval = usb_serial_register(&pl2303_device); } [drivers/usb/serial/usb-serial.c] 在注册函数中,将pl2303驱动加入usb serial驱动列表 int usb_serial_register(struct usb_serial_driver *driver) { list_add(&driver->driver_list, &usb_serial_driver_list); } 三: 添加endpoint // 至此,整个调用逐步返回! 最后在usb_set_configuration(udev, c); [drivers/usb/core/message.c] 函数返回时调用 -->create_intf_ep_devs(intf); //增加endpoint设备 -->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网卡如何读写呢 在pl2303驱动加载完成后,生成的设备ttyUSB0, ttyUSB1, ttyUSB2后,即可以对这些设备进行操 作。如open,close,read,write,ioctl等命令 pl2303.c中有如下函数,即当打开网卡设备时,就会调用此函数。 static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) { } 在板子的可使用3G WCDMA网卡上,因为开发板为3G网卡建立3个USB设备 ttyUSB0,ttyUSB1,ttyUSB2 所以在打开时,会调用3次该函数。 大概流程为 static int serial_open(struct tty_struct *tty, struct file *filp) -->tty_port_open(&port->port, tty, filp); -->port->ops->activate(port, tty); //此处调用的为static int serial_activate(struct tty_port *tport, struct tty_struct *tty) -->port->serial->type->open(tty, port); //这里即是调用static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) 从而对将设备打开,可以对设备进行读写,控制等操作。 设备识别 在设备插入后,以HUAWEI ET127 3G网卡为例。插入设备后会有如下的log scsi 0:0:0:0: CD-ROM HUAWEI Mobile CMCC CD 1.25 PQ: 0 ANSI: 0 大唐的AirCard 901插入后,同样会有log如下: csi 1:0:0:0: CD-ROM AirCard SetupDisk 1.00 PQ: 0 ANSI: 2 这时,友善开发板将此设备识别成为一个光盘,即储存设备。这时,如果在ubuntu下,可以使用 usb_modeswitch将usb设备进行模式转换。然后驱动3G网卡。我在网上搜索到一篇文章。说可以驱 动大部分3G网卡,就此我问过胡菲菲。此前驱动大唐的AirCard 901用的就是此种方法。 http://:81/Linux/2011-03/33430.htm 我昨天尝试按照这些步骤进行实现。但是遇到两个问题 1:arm 卡发板上使用的是busybox, 和一些应用程序(如:dhcpcd)在Android系统下编译。我编译的 usb_modeswitch无法运行 2:插入usb 3G设备后,arm开发板识别为sr0, sr1设备。不知道该如何将此设备退出。或许 usb_modeswitch转换完成后,即可以进行AT命令通信。还需要进行试验才能知道。 usb_modeswitch作用 USB_ModeSwitch 是控制"flip flop"(多重设备)USB装置的模式转换工具。作用有如下的几种: 1. Usb 闪存模式:提取和安装驱动 2. 转换模式:储存设备模式转换为所需(如3G)设备模式 3. 所需设备模式:使用设备新的功能 详细的可以参看usb_modeswitch的README文档。 一些术语 USB 端点(endpoint) 端点是由厂商在USB设备内部建立的,因此是永久存在的。端点(endpoint)和主控制器(host controller)基于管道(pipe)连接。 一个端点只能单向的(in/out)传输数据。 端点是以interface来分组的。每一个interface对应着一个设备功能。 USB Interface endpoint(端点)是以interface来分组的。比如华为的WCDMA是以每三个ep为一组,隶属于一个 interface。而且每一个interface对应着一个单独的设备功能。如华为ET127 TD-CDMA 3G网卡其 中一个interface设备命名为ttyUSB_utps_mms,对应的功能应该即为短信发送/接收。 USB设备号 //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设备参数 idVendor=1ab7, idProduct=0301 主设备号含义 ACM调制解调器:主设备号 166 USB打印机: 主设备号 180(次设备号0~15) USB串口: 主设备号 188 |
|