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_tableprobe这两个,
id_table 中定义了所有符合cdc_ncm设备的 idVendor号,当linux 枚举时检测到USB设备的idVendor在这个id_table中时,就会调用usbnet_probe进行起网操作。


1.1 【id_table】struct usb_device_id结构体

在分析cdc_ncmid_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_ncmid_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设备初始化操作。

主要工作如下:

  1. 初始化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);
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

"小夜猫&小懒虫&小财迷"的男人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值