USB NCM usbnet 枚举流程代码分析
一、cdc_ncm.c
在linux 驱动中,CDC NCM的驱动位于: kernel\drivers\net\usb\cdc_ncm.c
static struct usb_driver cdc_ncm_driver = {
.name = "cdc_ncm",
.id_table = cdc_devs,
.probe = usbnet_probe,
.disconnect = usbnet_disconnect,
.suspend = usbnet_suspend,
.resume = usbnet_resume,
.reset_resume = usbnet_resume,
.supports_autosuspend = 1,
.disable_hub_initiated_lpm = 1,
};
module_usb_driver(cdc_ncm_driver);
在结构体cdc_ncm_driver
中,比较重要的是id_table
,probe
这两个,
在id_table
中定义了所有符合cdc_ncm
设备的 idVendor
号,当linux
枚举时检测到USB设备的idVendor
在这个id_table
中时,就会调用usbnet_probe
进行起网操作。
1.1 【id_table】struct usb_device_id结构体
在分析cdc_ncm
的id_table
之前,我们先来看下usb_device_id
结构体是如何定义的:
# kernel\include\linux\mod_devicetable.h
struct usb_device_id {
/* which fields to match against? */
__u16 match_flags; //说明使用哪种匹配方式
/* Used for product specific matches; range is inclusive */
__u16 idVendor; //供应商ID
__u16 idProduct; //产品ID
__u16 bcdDevice_lo;
__u16 bcdDevice_hi;
/* Used for device class matches */
__u8 bDeviceClass; //设备类型
__u8 bDeviceSubClass; //设备子类型
__u8 bDeviceProtocol; //协议
/* Used for interface class matches */
__u8 bInterfaceClass; //接口类
__u8 bInterfaceSubClass; //接口子类
__u8 bInterfaceProtocol; //接口协议
/* Used for vendor-specific interface matches */
__u8 bInterfaceNumber;
/* not matched against */
kernel_ulong_t driver_info
__attribute__((aligned(sizeof(kernel_ulong_t)))); // NCM 驱动结构体,其中包含了收发包的函数等
};
结合结构体定义,我们来看下cdc_ncm
的id_table
:
static const struct usb_device_id cdc_devs[] = {
// 爱立信 F5521gw USB网卡
/* Ericsson MBM devices like F5521gw */
{ .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | USB_DEVICE_ID_MATCH_VENDOR,
//匹配方式:CLASS(设备类)、SUBCLASS(子类)、PROTOCOL(设备协议)
.idVendor = 0x0bdb, //供应商ID: 0x0bdb
.bInterfaceClass = USB_CLASS_COMM, //设备类型: 网卡类
.bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, //设备子类型:NCM
.bInterfaceProtocol = USB_CDC_PROTO_NONE, //设备协议: 无
.driver_info = (unsigned long) &wwan_info, // NCM 驱动结构体,其中包含了收发包的函数等
},
/* DW5812 LTE Verizon Mobile Broadband Card Unlike DW5550 this device requires FLAG_NOARP */
{ USB_DEVICE_AND_INTERFACE_INFO(0x413c, 0x81bb, //匹配方式:USB_DEVICE_ID_MATCH_INT_INFO、USB_DEVICE_ID_MATCH_DEVICE
USB_CLASS_COMM,
USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
.driver_info = (unsigned long)&wwan_noarp_info,
},
/* DW5813 LTE AT&T Mobile Broadband Card Unlike DW5550 this device requires FLAG_NOARP */
{ USB_DEVICE_AND_INTERFACE_INFO(0x413c, 0x81bc,
USB_CLASS_COMM,
USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
.driver_info = (unsigned long)&wwan_noarp_info,
},
/* Dell branded MBM devices like DW5550 */
{ .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | USB_DEVICE_ID_MATCH_VENDOR,
.idVendor = 0x413c,
.bInterfaceClass = USB_CLASS_COMM,
.bInterfaceSubClass = USB_CDC_SUBCLASS_NCM,
.bInterfaceProtocol = USB_CDC_PROTO_NONE,
.driver_info = (unsigned long) &wwan_info,
},
/* Toshiba branded MBM devices */
{ .match_flags = USB_DEVICE_ID_MATCH_INT_INFO | USB_DEVICE_ID_MATCH_VENDOR,
.idVendor = 0x0930,
.bInterfaceClass = USB_CLASS_COMM,
.bInterfaceSubClass = USB_CDC_SUBCLASS_NCM,
.bInterfaceProtocol = USB_CDC_PROTO_NONE,
.driver_info = (unsigned long) &wwan_info,
},
/* tag Huawei devices as wwan */
{ USB_VENDOR_AND_INTERFACE_INFO(0x12d1,
USB_CLASS_COMM,
USB_CDC_SUBCLASS_NCM,
USB_CDC_PROTO_NONE),
.driver_info = (unsigned long)&wwan_info,
},
/* Infineon(now Intel) HSPA Modem platform */
{ USB_DEVICE_AND_INTERFACE_INFO(0x1519, 0x0443,
USB_CLASS_COMM,
USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
.driver_info = (unsigned long)&wwan_noarp_info,
},
/* Generic CDC-NCM devices */
{ USB_INTERFACE_INFO(USB_CLASS_COMM,
USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
.driver_info = (unsigned long)&cdc_ncm_info,
}
};
MODULE_DEVICE_TABLE(usb, cdc_devs);
初始化 usb_device_id 结构体时,除了可以使用上面每个成员赋值的方式,还可以使用如下宏控更加快捷的初始化:
USB_DEVICE(vendor, product)
创建一个 struct usb_device_id, 可用来只匹配特定供应商和产品 ID 值,
对于需要特定驱动的 USB 设备,这是非常普遍用的。
USB_DEVICE_VER(vendor, product, lo, hi)
创建一个 struct usb_device_id, 用来在一个版本范围中只匹配特定供应商和产品 ID 值。
USB_DEVICE_INFO(class, subclass, protocol)
创建一个 struct usb_device_id, 可用来只匹配一个特定类的 USB 设备。
USB_INTERFACE_INFO(class, subclass, protocol)
创建一个 struct usb_device_id, 可用来只匹配一个特定类的 USB 接口。
1.1.1 match_flags 设备类型
所有的match_flags
定义在mod_devicetable.h
中:
# kernel\include\linux\mod_devicetable.h
/* Some useful macros to use to create struct usb_device_id */
#define USB_DEVICE_ID_MATCH_VENDOR 0x0001
#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002
#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004
#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008
#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010
#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020
#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040
#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080
#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100
#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200
#define USB_DEVICE_ID_MATCH_INT_NUMBER 0x0400
1.1.2 bInterfaceClass 接口类
# kernel\include\uapi\linux\usb\ch9.h
/* Device and/or Interface Class codes
* as found in bDeviceClass or bInterfaceClass
* and defined by www.usb.org documents */
#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */
#define USB_CLASS_AUDIO 1 //声音设备
#define USB_CLASS_COMM 2 //调制解调器,网卡,ISDN连接
#define USB_CLASS_HID 3 //HID设备,如鼠标,键盘
#define USB_CLASS_PHYSICAL 5 //物理设备
#define USB_CLASS_STILL_IMAGE 6 //静止图像捕捉设备
#define USB_CLASS_PRINTER 7 //打印机
#define USB_CLASS_MASS_STORAGE 8 //批量存储设备
#define USB_CLASS_HUB 9 //USB HUBS
#define USB_CLASS_CDC_DATA 0x0a
#define USB_CLASS_CSCID 0x0b /* chip+ smart card */ //智能卡
#define USB_CLASS_CONTENT_SEC 0x0d /* content security */
#define USB_CLASS_VIDEO 0x0e //视频设备,如网络摄像头
#define USB_CLASS_WIRELESS_CONTROLLER 0xe0 //网络控制设备
#define USB_CLASS_MISC 0xef //杂项设备
#define USB_CLASS_APP_SPEC 0xfe
#define USB_CLASS_VENDOR_SPEC 0xff //厂商自定义的设备
#define USB_SUBCLASS_VENDOR_SPEC 0xff
1.2 【usbnet_probe】NCM 驱动探测函数
注册ncm 驱动使用的是module_usb_driver(cdc_ncm_driver);
,
但因为是通用usb驱动代码,所以这个不是我们要关心的,接下来,我们要关心的是当注册好ncm usb
驱动后,
系统通过bInterfaceSubClass
检测到ncm
设备接入时,同时根据match_flags
匹配成功时,就会调用用预先配置好的probe
函数进行ncm
设备初始化操作。
主要工作如下:
- 初始化
struct net_device
结构体,结构体中包含了网络操作相关的参数及操作函数
# kernel\drivers\net\usb\usbnet.c
int usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
{
struct usbnet *dev;
struct net_device *net;
struct usb_host_interface *interface;
struct driver_info *info;
struct usb_device *xdev;
int status;
const char *name;
struct usb_driver *driver = to_usb_driver(udev->dev.driver);
name = udev->dev.driver->name;
info = (struct driver_info *) prod->driver_info;
xdev = interface_to_usbdev (udev);
interface = udev->cur_altsetting;
status = -ENOMEM;
// 1. 初始化net_device结构体,结构体中包含了网络操作相关的参数及操作函数
// set up our own records
net = alloc_etherdev(sizeof(*dev));
// 将udev 设置为net的sys文件系统的 parent
/* netdev_printk() needs this so do it as early as possible */
SET_NETDEV_DEV(net, &udev->dev);
// 2. 初始化各种queue队列,包括,等待队列,接收队列,发送队列。
dev = netdev_priv(net);
dev->udev = xdev;
dev->intf = udev;
dev->driver_info = info;
dev->driver_name = name;
dev->msg_enable = netif_msg_init (msg_level, NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK);
init_waitqueue_head(&dev->wait);
skb_queue_head_init (&dev->rxq);
skb_queue_head_init (&dev->txq);
skb_queue_head_init (&dev->done);
skb_queue_head_init(&dev->rxq_pause);
dev->bh.func = usbnet_bh;
dev->bh.data = (unsigned long) dev;
INIT_WORK (&dev->kevent, usbnet_deferred_kevent);
init_usb_anchor(&dev->deferred);
dev->delay.function = usbnet_bh;
dev->delay.data = (unsigned long) dev;
init_timer (&dev->delay);
mutex_init (&dev->phy_mutex);
mutex_init(&dev->interrupt_mutex);
dev->interrupt_count = 0;
dev->net = net;
strcpy (net->name, "usb%d");
memcpy (net->dev_addr, node_id, sizeof node_id);
/* rx and tx sides can use different message sizes;
* bind() should set rx_urb_size in that case.
*/
dev->hard_mtu = net->mtu + net->hard_header_len;
net->netdev_ops = &usbnet_netdev_ops;
net->watchdog_timeo = TX_TIMEOUT_JIFFIES;
net->ethtool_ops = &usbnet_ethtool_ops;
// allow device-specific bind/init procedures
// NOTE net->name still not usable ...
if (info->bind) {
status = info->bind (dev, udev);
if (status < 0)
goto out1;
// heuristic: "usb%d" for links we know are two-host,
// else "eth%d" when there's reasonable doubt. userspace
// can rename the link if it knows better.
if ((dev->driver_info->flags & FLAG_ETHER) != 0 &&
((dev->driver_info->flags & FLAG_POINTTOPOINT) == 0 ||
(net->dev_addr [0] & 0x02) == 0))
strcpy (net->name, "eth%d");
/* WLAN devices should always be named "wlan%d" */
if ((dev->driver_info->flags & FLAG_WLAN) != 0)
strcpy(net->name, "wlan%d");
/* WWAN devices should always be named "wwan%d" */
if ((dev->driver_info->flags & FLAG_WWAN) != 0)
strcpy(net->name, "wwan%d");
/* LTE devices should always be named "lte%d" */
if ((dev->driver_info->flags & FLAG_LTE) != 0)
strcpy(net->name, "lte%d");
/* devices that cannot do ARP */
if ((dev->driver_info->flags & FLAG_NOARP) != 0)
net->flags |= IFF_NOARP;
/* maybe the remote can't receive an Ethernet MTU */
if (net->mtu > (dev->hard_mtu - net->hard_header_len))
net->mtu = dev->hard_mtu - net->hard_header_len;
} else if (!info->in || !info->out)
status = usbnet_get_endpoints (dev, udev);
else {
dev->in = usb_rcvbulkpipe (xdev, info->in);
dev->out = usb_sndbulkpipe (xdev, info->out);
if (!(info->flags & FLAG_NO_SETINT))
status = usb_set_interface (xdev,
interface->desc.bInterfaceNumber,
interface->desc.bAlternateSetting);
else
status = 0;
}
if (status >= 0 && dev->status)
status = init_status (dev, udev);
if (status < 0)
goto out3;
if (!dev->rx_urb_size)
dev->rx_urb_size = dev->hard_mtu;
dev->maxpacket = usb_maxpacket (dev->udev, dev->out, 1);
/* let userspace know we have a random address */
if (ether_addr_equal(net->dev_addr, node_id))
net->addr_assign_type = NET_ADDR_RANDOM;
if ((dev->driver_info->flags & FLAG_WLAN) != 0)
SET_NETDEV_DEVTYPE(net, &wlan_type);
if ((dev->driver_info->flags & FLAG_WWAN) != 0)
SET_NETDEV_DEVTYPE(net, &wwan_type);
/* initialize max rx_qlen and tx_qlen */
usbnet_update_max_qlen(dev);
if (dev->can_dma_sg && !(info->flags & FLAG_SEND_ZLP) &&
!(info->flags & FLAG_MULTI_PACKET)) {
dev->padding_pkt = kzalloc(1, GFP_KERNEL);
if (!dev->padding_pkt) {
status = -ENOMEM;
goto out4;
}
}
status = register_netdev (net);
if (status)
goto out5;
netif_info(dev, probe, dev->net,
"register '%s' at usb-%s-%s, %s, %pM\n",
udev->dev.driver->name,
xdev->bus->bus_name, xdev->devpath,
dev->driver_info->description,
net->dev_addr);
// ok, it's ready to go.
usb_set_intfdata (udev, dev);
netif_device_attach (net);
if (dev->driver_info->flags & FLAG_LINK_INTR)
usbnet_link_change(dev, 0, 0);
return 0;
out5:
kfree(dev->padding_pkt);
out4:
usb_free_urb(dev->interrupt);
out3:
if (info->unbind)
info->unbind (dev, udev);
out1:
/* subdrivers must undo all they did in bind() if they
* fail it, but we may fail later and a deferred kevent
* may trigger an error resubmitting itself and, worse,
* schedule a timer. So we kill it all just in case.
*/
cancel_work_sync(&dev->kevent);
del_timer_sync(&dev->delay);
free_netdev(net);
out:
return status;
}
EXPORT_SYMBOL_GPL(usbnet_probe);