转载了一个比较详细的关于USB 3G上网卡的讲解,原文地址:
http://blog.chinaunix.net/uid-20786208-id-3157021.html
这里我们先讲讲USB热插拔事件的处理工作。—–Khubd守护进程。
-Khubd守护进程它是一个守护进程,来检查usb port的事件通知HCD和usb core,然后做相应的处理。
驱动目录drivers/usb/*
usb/serial usb 串行设备驱动 (例如usb 3G卡、蓝牙等)
usb/storage usb 大储量磁盘驱动(u盘)
usb/host usb host usb主机控制器驱动(嵌入式otg:dwc_otg)
usb/core usb 核心一些处理代码,所有的驱动相关处理都在这里,也都注册到它里面。
usb/usb-skeleton.c 经典的usb客户驱动框架,可以参考。
当然还有其他这里不再说明。
下面贴出USB的整体驱动框架:
这里我们主要分析khub的工作原理: 硬件层次是hub的工作,如何和host及其设备间通信及相应事件
看usb/core/hub.c
int usb_hub_init(void)
{
if (usb_register(&hub_driver) < 0) {
printk(KERN_ERR "%s: can't register hub driver\n",
usbcore_name);
return -1;
}
khubd_task = kthread_run(hub_thread, NULL, "khubd");
if (!IS_ERR(khubd_task))
return 0;
/* Fall through if kernel_thread failed */
usb_deregister(&hub_driver);
printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);
return -1;
}
这里我们只关心kthread_run(hub_thread, NULL, “khubd”); 然后我们看hub_thread函数
static int hub_thread(void *__unused)
{
do {
hub_events();
wait_event_interruptible(khubd_wait,
!list_empty(&hub_event_list) ||
kthread_should_stop());
try_to_freeze();
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}
这里我们看到了hub_events()
函数。然后设置运行状态,如果有事件就加入hub_event_list
。
自此khubd运行起来了。
这里我们同样贴出它的函数调用流程图。
通过流程图我们可以清晰的明白,当usb设备插入usb接口后,khubd运行,它检测到port状态的变化
调用hub_port_connect_change()
,如果是新设备那么usb_allco_dev
,然后调用usb_new_device
来进行配置使usb设备可以正常工作。详细流程请看源码。
我们已经讲到usb_new_device
来初始化设备配置,然后让设备工作,我们知道这之前,首先hub检测到端口电流变化,然后分配usb设备地址,申请设备,获取设备一些描述性信息(这些信息有助于以后总线match和probe函数来check是否支持这个设备和找到相应的驱动)。这里我们首先展开usb_new_device
这个函数看看它到底做了什么工作。
/**
* usb_new_device - perform initial device setup (usbcore-internal)
* @udev: newly addressed device (in ADDRESS state)
*
* This is called with devices which have been enumerated, but not yet
* configured. The device descriptor is available, but not descriptors
* for any device configuration. The caller must have locked either
* the parent hub (if udev is a normal device) or else the
* usb_bus_list_lock (if udev is a root hub). The parent's pointer to
* udev has already been installed, but udev is not yet visible through
* sysfs or other filesystem code.
*
* Returns 0 for success (device is configured and listed, with its
* interfaces, in sysfs); else a negative errno value.
*
* This call is synchronous, and may not be used in an interrupt context.
*
* Only the hub driver or root-hub registrar should ever call this.
*/
int usb_new_device(struct usb_device *udev)
{
int err;
int c;
err = usb_get_configuration(udev);
if (err < 0) {
dev_err(&udev->dev, "can't read configurations, error %d\n",
err);
goto fail;
}
/* read the standard strings and cache them if present */
udev->product = usb_cache_string(udev, udev->descriptor.iProduct);
udev->manufacturer = usb_cache_string(udev,
udev->descriptor.iManufacturer);
udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);
/* Tell the world! */
dev_dbg(&udev->dev, "new device strings: Mfr=%d, Product=%d, "
"SerialNumber=%d\n",
udev->descriptor.iManufacturer,
udev->descriptor.iProduct,
udev->descriptor.iSerialNumber);
show_string(udev, "Product", udev->product);
show_string(udev, "Manufacturer", udev->manufacturer);
show_string(udev, "SerialNumber", udev->serial);
#ifdef CONFIG_USB_OTG
/*
* OTG-aware devices on OTG-capable root hubs may be able to use SRP,
* to wake us after we've powered off VBUS; and HNP, switching roles
* "host" to "peripheral". The OTG descriptor helps figure this out.
*/
if (!udev->bus->is_b_host
&& udev->config
&& udev->parent == udev->bus->root_hub) {
struct usb_otg_descriptor *desc = 0;
struct usb_bus *bus = udev->bus;
/* descriptor may appear anywhere in config */
if (__usb_get_extra_descriptor (udev->rawdescriptors[0],
le16_to_cpu(udev->config[0].desc.wTotalLength),
USB_DT_OTG, (void **) &desc) == 0) {
if (desc->bmAttributes & USB_OTG_HNP) {
unsigned port1 = udev->portnum;
struct usb_device *root = udev->parent;
dev_info(&udev->dev,
"Dual-Role OTG device on %sHNP port\n",
(port1 == bus->otg_port)
? "" : "non-");
/* enable HNP before suspend, it's simpler */
if (port1 == bus->otg_port)
bus->b_hnp_enable = 1;
err = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
USB_REQ_SET_FEATURE, 0,
bus->b_hnp_enable
? USB_DEVICE_B_HNP_ENABLE
: USB_DEVICE_A_ALT_HNP_SUPPORT,
0, NULL, 0, USB_CTRL_SET_TIMEOUT);
if (err < 0) {
/* OTG MESSAGE: report errors here,
* customize to match your product.
*/
dev_info(&udev->dev,
"can't set HNP mode; %d\n",
err);
bus->b_hnp_enable = 0;
}
}
}
}
if (!is_targeted(udev)) {
/* Maybe it can talk to us, though we can't talk to it.
* (Includes HNP test device.)
*/
if (udev->bus->b_hnp_enable || udev->bus->is_b_host) {
static int __usb_suspend_device(struct usb_device *,
int port1);
err = __usb_suspend_device(udev, udev->bus->otg_port);
if (err < 0)
dev_dbg(&udev->dev, "HNP fail, %d\n", err);
}
err = -ENODEV;
goto fail;
}
#endif
/* put device-specific files into sysfs */
err = device_add (&udev->dev);
if (err) {
dev_err(&udev->dev, "can't device_add, error %d\n", err);
goto fail;
}
usb_create_sysfs_dev_files (udev);
usb_lock_device(udev);
/* choose and set the configuration. that registers the interfaces
* with the driver core, and lets usb device drivers bind to them.
*/
c = choose_configuration(udev);
if (c >= 0) {
err = usb_set_configuration(udev, c);
if (err) {
dev_err(&udev->dev, "can't set config #%d, error %d\n",
c, err);
/* This need not be fatal. The user can try to
* set other configurations. */
}
}
/* USB device state == configured ... usable */
usb_notify_add_device(udev);
usb_unlock_device(udev);
return 0;
fail:
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
return err;
}
这里我们主要看usb_get_configuration(udev)
、 device_add (&udev->dev)
、 usb_create_sysfs_dev_files(udev)
、choose_configuration(udev)
、 usb_set_configuration(udev, c)
为关键函数,新设备默认为host控制器总线下从设备。首先从外设获取配置信息
usb_get_configuration()
,然后是device_add()
这里完成了设备的注册,当然包括设备总线的注册,以及在设备模型中的注册,而真正热插拔事件处理函数是uevent(在lld3中是hotplug函数,不过看2.6以后的内核全部为uevent),这个函数指针初始化是在总线bus_type初始化的时候初始化的,在usb.c文件中代码如下:
struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match,
.uevent = usb_uevent,
.suspend = usb_generic_suspend,
.resume = usb_generic_resume,
};
我们知道在一个usb设备check的过程中,当设备被注册进usb core后,就是设备的总线的一系列的函数的工作(包括match、probe、uevent)。uevent它的作用就是把内核的一些环境变量传递到用户空间,以便通知用户空间做出相应的事件。
这里我们简单说一下match函数:
int usb_device_match(struct device *dev, struct device_driver *drv)
{
struct usb_interface *intf;
struct usb_driver *usb_drv;
const struct usb_device_id *id;
/* check for generic driver, which we don't match any device with */
if (drv == &usb_generic_driver)
return 0;
intf = to_usb_interface(dev);
usb_drv = to_usb_driver(drv);
id = usb_match_id(intf, usb_drv->id_table);
if (id)
return 1;
id = usb_match_dynamic_id(intf, usb_drv);
if (id)
return 1;
return 0;
}
这里我们只需要关注usb_match_id()
这个函数即可,让我们可以真正明白一个热插拔设备识别和检测的机制是如何运作的。这里还要说明一下首先总线检验总线是否支持这个设备,如果支持那么才去找相应的驱动是否支持它,如果支持就和他bind在一起,那么这个设备就可以工作了。我们可以通过/proc来查看usb设备的信息:
#cat /proc/bus/usb/devices
当然我们知道3G卡插入usb口,首先识别出来的是U盘,即加载的是storage驱动,那么我们需要通过usb_modemswitch
工具来把它转换成modem模式 (就是usb 串行工作模式 / *usb 串行设备 */
),它会把总线初始化为usb_serial_bus
,并加载usb serial
驱动(usb/serial目录下option.c) 而这个通知事件就是通过上边提过的uevent这个函数来完成。如果直接是usb serial设备那么它会直接识别为usb serial总线,并加载相应的驱动。例如有些U盘还附带bluetooth的功能,bluetooth就是串行设备。那么下一节我们将分析usb serial相关驱动。