Linux kernel U盘识别流程

一、正常USB枚举及断开的log

正常U盘插入和拔掉Kernel log的打印消息如下:
U盘插入

[  220.836836] usb 2-1: new high-speed USB device number 4 using xxx-ehci
[  220.984187] usb-storage 2-1:1.0: USB Mass Storage device detected
[  220.990977] scsi1 : usb-storage 2-1:1.0
[  220.998948] scsi 1:0:0:0: Direct-Access     Kingston DataTraveler 3.0 PMAP PQ: 0 ANSI: 6
[  221.010354] sd 1:0:0:0: [sda] 30277632 512-byte logical blocks: (15.5 GB/14.4 GiB)
[  221.018712] sd 1:0:0:0: [sda] Write Protect is off
[  221.023549] sd 1:0:0:0: [sda] Mode Sense: 45 00 00 00
[  221.029224] sd 1:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
[  221.055488]  sda: sda1
[  221.068303] sd 1:0:0:0: [sda] Attached SCSI removable disk

U盘拔掉

[  226.396127] usb 2-1: USB disconnect, device number 4

下面的USB设备的枚举过程将根据 http://blog.csdn.net/myarrow/article/details/8270029 这篇文章进行梳理。


二、内核线程检测Hub端口的变化

在初始化USB subsystem(usb.c 中的usb_init())的时候,会去做usb hub的初始化usb_hub_init(),在那里面会去开启一个内核线程hub_thread() ,在该函数中会去循环判断链表是否非空以及是否唤醒等待队列。如果条件都满足,就会去执行 hub_events() 的内容。

static int hub_thread(void *__unused)
{
    /* khubd needs to be freezable to avoid interfering with USB-PERSIST
     * port handover.  Otherwise it might see that a full-speed device
     * was gone before the EHCI controller had handed its port over to
     * the companion full-speed controller.
     */
    set_freezable();

    do {
        hub_events();
        wait_event_freezable(khubd_wait,
                !list_empty(&hub_event_list) ||
                kthread_should_stop());
    } while (!kthread_should_stop() || !list_empty(&hub_event_list));

    pr_debug("%s: khubd exiting\n", usbcore_name);
    return 0;
}

hub_irq() 检测到设备插入唤醒队列:
对于4.15版本的Kernel来说,在hub_irq()函数中会调用 kick_hub_wq() 中让 hub_wq 的工作队列开始 hub_event() 工作。

if (queue_work(hub_wq, &hub->events))
    return;

INIT_WORK(&hub->events, hub_event);

这里的hub_event()函数执行的内容就跟我们经常见的hub_events()的内容是一致的。只是名字变了。

对于3.14版本的Kernel来说,在 kick_khubd() 中往 hub->event_list的链表中添加一个 hub_event_list 的事件,并且唤醒 khubd_wait 等待队列。

// kick_khubd()

if (!hub->disconnected && list_empty(&hub->event_list)) {
        list_add_tail(&hub->event_list, &hub_event_list);

        /* Suppress autosuspend until khubd runs */
        usb_autopm_get_interface_no_resume(
                to_usb_interface(hub->intfdev));
        wake_up(&khubd_wait);
    }

后面的内容都是基于3.14版本的Kernel 中的hub_events()来说明。


三、hub_events()函数

所有有关hub端口的变化最终都会在hub_events()中执行,以此来做相应的动作。比如枚举连接上来的USB设备,在 hub_events() 中通过调用 hub_port_status() 来获得 portchangeportstatus 的状态并设置 connect_change变量。connect_change 为1表示Hub上的端口状态有变化(设备插入或拔出),进而执行 hub_port_connect_change() 的内容。

hub_port_connect_change()中调用usb_alloc_dev()为新的USB设备申请资源,并进行一些初始化。设置设备的状态为 USB_STATE_POWERED,接下来会枚举USB设备(复位、握手、获取描述符),当USB设备枚举完成后就会触发加载与之对应的USB接口驱动。

for (i = 1; i <= hdev->maxchild; i++) {
    if (test_bit(i, hub->busy_bits))
        continue;
    connect_change = test_bit(i, hub->change_bits);
    wakeup_change = test_and_clear_bit(i, hub->wakeup_bits);
    …….

    if (connect_change)
        hub_port_connect_change(hub, i,
            portstatus, portchange);
}

在这里依次判断hub上的端口状态变化,如果发生状态变化,那么就设置connect_change=1,并调用hub_port_connect_change()函数去处理该端口。这个函数里面更确切的说主要来处理枚举过程。


四、hub_port_connect_change()函数

/* Handle physical or logical connection change events.
 * This routine is called when:
 *  a port connection-change occurs;
 *  a port enable-change occurs (often caused by EMI);
 *  usb_reset_and_verify_device() encounters changed descriptors (as from
 *      a firmware download)
 * caller already locked the hub
 */
static void hub_port_connect_change(struct usb_hub *hub, int port1,
                    u16 portstatus, u16 portchange)

其中portstatusportchange有如下几种状态,这些状态都在USB协议中第11章做了明确的规定。

portstatus各个bit的意义如下:

/*
 * wPortStatus bit field
 * See USB 2.0 spec Table 11-21
 */
#define USB_PORT_STAT_CONNECTION    0x0001
#define USB_PORT_STAT_ENABLE        0x0002
#define USB_PORT_STAT_SUSPEND       0x0004
#define USB_PORT_STAT_OVERCURRENT   0x0008
#define USB_PORT_STAT_RESET     0x0010
#define USB_PORT_STAT_L1        0x0020
/* bits 6 to 7 are reserved */
#define USB_PORT_STAT_POWER     0x0100
#define USB_PORT_STAT_LOW_SPEED     0x0200
#define USB_PORT_STAT_HIGH_SPEED        0x0400
#define USB_PORT_STAT_TEST              0x0800
#define USB_PORT_STAT_INDICATOR         0x1000
/* bits 13 to 15 are reserved */

这里写图片描述
这里写图片描述


portchange各个bit的意义如下:

/*
 * wPortChange bit field
 * See USB 2.0 spec Table 11-22 and USB 2.0 LPM ECN Table-4.10
 * Bits 0 to 5 shown, bits 6 to 15 are reserved
 */
#define USB_PORT_STAT_C_CONNECTION  0x0001
#define USB_PORT_STAT_C_ENABLE      0x0002
#define USB_PORT_STAT_C_SUSPEND     0x0004
#define USB_PORT_STAT_C_OVERCURRENT 0x0008
#define USB_PORT_STAT_C_RESET       0x0010
#define USB_PORT_STAT_C_L1      0x0020
/*
 * USB 3.0 wPortChange bit fields
 * See USB 3.0 spec Table 10-11
 */
#define USB_PORT_STAT_C_BH_RESET    0x0020
#define USB_PORT_STAT_C_LINK_STATE  0x0040
#define USB_PORT_STAT_C_CONFIG_ERROR    0x0080

这里写图片描述

这个函数主要处理hub上某个端口的变化,通过判断portstatusportchange来判断当前hub上端口的状态,如果有设备连接上,要为其分配struct usb_devices并将其添加到USB总线上,之后会匹配对应的USB接口驱动。接下来说说这个函数内主要的做的事情。


1、USB设备连接去抖动

if (portchange & (USB_PORT_STAT_C_CONNECTION |
                USB_PORT_STAT_C_ENABLE)) {
        status = hub_port_debounce_be_stable(hub, port1);
        if (status < 0) {
            if (status != -ENODEV && printk_ratelimit())
                dev_err(hub_dev, "connect-debounce failed, "
                        "port %d disabled\n", port1);
            portstatus &= ~USB_PORT_STAT_CONNECTION;
        } else {
            portstatus = status;
        }
    }

如果判断portchanged的状态是USB_PORT_STAT_C_CONNECTIONUSB_PORT_STAT_C_ENABLE表示有新的USB设备连接。之后会调用hub_port_debounce_be_stable()不间断检测端口的状态,实际上就是一个去抖动的过程,保证USB设备完全连接上。在USB协议上规定至少100ms的去抖动时间。

static inline int hub_port_debounce_be_stable(struct usb_hub *hub,
        int port1)
{
    return hub_port_debounce(hub, port1, false);
}

/* USB 2.0 spec, 7.1.7.3 / fig 7-29:
 *
 * Between connect detection and reset signaling there must be a delay
 * of 100ms at least for debounce and power-settling.  The corresponding
 * timer shall restart whenever the downstream port detects a disconnect.
 *
 * Apparently there are some bluetooth and irda-dongles and a number of
 * low-speed devices for which this debounce period may last over a second.
 * Not covered by the spec - but easy to deal with.
 *
 * This implementation uses a 1500ms total debounce timeout; if the
 * connection isn't stable by then it returns -ETIMEDOUT.  It checks
 * every 25ms for transient disconnects.  When the port status has been
 * unchanged for 100ms it returns the port status.
 */

int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected)

2、枚举USB设备

在这个for循环中会循环SET_CONFIG_TRIES次去枚举USB设备,一旦枚举成功就跳出循环,如果SET_CONFIG_TRIES次都失败,那么就会报告枚举失败,USB设备无法识别。关于这部分代码的内容,详见下面的分析:

for (i = 0; i < SET_CONFIG_TRIES; i++) {

        /* reallocate for each attempt, since references
         * to the previous one can escape in various ways
         */
        udev = usb_alloc_dev(hdev, hdev->bus, port1);
        if (!udev) {
            dev_err (hub_dev,
                "couldn't allocate port %d usb_device\n",
                port1);
            goto done;
        }

        usb_set_device_state(udev, USB_STATE_POWERED);
        udev->bus_mA = hub->mA_per_port;
        udev->level = hdev->level + 1;
        udev->wusb = hub_is_wusb(hub);

        /* Only USB 3.0 devices are connected to SuperSpeed hubs. */
        if (hub_is_superspeed(hub->hdev))
            udev->speed = USB_SPEED_SUPER;
        else
            udev->speed = USB_SPEED_UNKNOWN;

        choose_devnum(udev);
        if (udev->devnum <= 0) {
            status = -ENOTCONN; /* Don't retry */
            goto loop;
        }

        /* reset (non-USB 3.0 devices) and get descriptor */
        status = hub_port_init(hub, udev, port1, i);
        if (status < 0)
            goto loop;

        usb_detect_quirks(udev);
        if (udev->quirks & USB_QUIRK_DELAY_INIT)
            msleep(1000);

        /* consecutive bus-powered hubs aren't reliable; they can
         * violate the voltage drop budget.  if the new child has
         * a "powered" LED, users should notice we didn't enable it
         * (without reading syslog), even without per-port LEDs
         * on the parent.
         */
        if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
                && udev->bus_mA <= unit_load) {
            u16 devstat;

            status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
                    &devstat);
            if (status) {
                dev_dbg(&udev->dev, "get status %d ?\n", status);
                goto loop_disable;
            }
            if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
                dev_err(&udev->dev,
                    "can't connect bus-powered hub "
                    "to this port\n");
                if (hub->has_indicators) {
                    hub->indicator[port1-1] =
                        INDICATOR_AMBER_BLINK;
                    schedule_delayed_work (&hub->leds, 0);
                }
                status = -ENOTCONN; /* Don't retry */
                goto loop_disable;
            }
        }

        /* check for devices running slower than they could */
        if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
                && udev->speed == USB_SPEED_FULL
                && highspeed_hubs != 0)
            check_highspeed (hub, udev, port1);

        /* Store the parent's children[] pointer.  At this point
         * udev becomes globally accessible, although presumably
         * no one will look at it until hdev is unlocked.
         */
        status = 0;

        /* We mustn't add new devices if the parent hub has
         * been disconnected; we would race with the
         * recursively_mark_NOTATTACHED() routine.
         */
        spin_lock_irq(&device_state_lock);
        if (hdev->state == USB_STATE_NOTATTACHED)
            status = -ENOTCONN;
        else
            hub->ports[port1 - 1]->child = udev;
        spin_unlock_irq(&device_state_lock);

        /* Run it through the hoops (find a driver, etc) */
        if (!status) {
            status = usb_new_device(udev);
            if (status) {
                spin_lock_irq(&device_state_lock);
                hub->ports[port1 - 1]->child = NULL;
                spin_unlock_irq(&device_state_lock);
            }
        }

        if (status)
            goto loop_disable;

        status = hub_power_remaining(hub);
        if (status)
            dev_dbg(hub_dev, "%dmA power budget left\n", status);

        return;

loop_disable:
        hub_port_disable(hub, port1, 1);
loop:
        usb_ep0_reinit(udev);
        release_devnum(udev);
        hub_free_dev(udev);
        usb_put_dev(udev);
        if ((status == -ENOTCONN) || (status == -ENOTSUPP))
            break;
    }

3、为端口上的USB设备分配struct usb_device

/* reallocate for each attempt, since references
* to the previous one can escape in various ways
*/
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) {
    dev_err (hub_dev,
        "couldn't allocate port %d usb_device\n",
        port1);
    goto done;
}

执行usb_alloc_dev()会将这个USB设备挂到USB总线上,并将USB设备的连接状态置为USB_STATE_ATTACHED。USB的总线是为了USB设备和USB接口驱动匹配用的。usb_alloc_dev()大概内容如下:

device_initialize(&dev->dev);
dev->dev.bus = &usb_bus_type;
dev->dev.type = &usb_device_type;
dev->dev.groups = usb_device_groups;
dev->dev.dma_mask = bus->controller->dma_mask;
set_dev_node(&dev->dev, dev_to_node(bus->controller));
dev->state = USB_STATE_ATTACHED;

4、设置USB设备的状态为USB_STATE_POWERED

usb_set_device_state(udev, USB_STATE_POWERED);
udev->bus_mA = hub->mA_per_port;
udev->level = hdev->level + 1;
udev->wusb = hub_is_wusb(hub);

前面在usb_alloc_dev()函数中设置USB设备的状态为USB_STATE_ATTACHED,之后设置USB设备从总线上汲取的电流大小以及设置USB设备的状态为USB_STATE_POWERED。这个时序满足USB协议第9章规定的USB设备状态迁移。也可以参照我的上一篇文章USB设备状态设置– usb_gadget_set_state()


5、为USB设备分配编号

调用 choose_devnum() 为设备分配一个小于128的编号,下面打印的编号4就是这个函数产生的。

[  220.836836] usb 2-1: new high-speed USB device number 4 using xxx-ehci
choose_devnum(udev);
if (udev->devnum <= 0) {
    status = -ENOTCONN; /* Don't retry */
    goto loop;
}

6、调用 hub_port_init() 对插入的USB设备进行复位、分配地址、获取默认管道的最大包长度、获取设备描述符。

在后面将着重讲hub_port_init()函数的内容。


7、调用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 detected but not fully
 * enumerated.  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.
 *
 * 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.
 *
 * Return: Whether the device is configured properly or not. Zero if the
 * interface was registered with the driver core; else a negative errno
 * value.
 *
 */
int usb_new_device(struct usb_device *udev)

该函数主要做了以下几件事:

  • 调用usb_enumerate_device()获取USB设备的描述符(配置描述符、接口描述符);

  • 调用announce_device()告诉世界新的USB设备添加了;

static void announce_device(struct usb_device *udev)
{
    dev_info(&udev->dev, "New USB device found, idVendor=%04x, idProduct=%04x\n",
        le16_to_cpu(udev->descriptor.idVendor),
        le16_to_cpu(udev->descriptor.idProduct));
    dev_info(&udev->dev,
        "New USB 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);
}
  • 将已枚举完成且USB状态为USB_STATE_ADDRESS的USB设备通过设备模型提供的接口device_add()进行设备的注册;

  • 为该USB设备创建对应的sysfs接口;

至此,当调用usb_new_device()成功之后,那么表明连接到Hub上的某个端口的USB设备已经完全的被枚举了,USB Core已经完全知道该USB设备的所有特性(通过设备描述符知道)。那么之后就是根据该USB设备的配置描述符会匹配一个或多个与之对应的USB接口驱动。在讲USB接口驱动之前,我们再详细的说明一下hub_port_init()这个函数。


五、hub_port_init()函数

这个函数也是异常的庞大,先看看它的介绍:

/* Reset device, (re)assign address, get device descriptor.
 * Device connection must be stable, no more debouncing needed.
 * Returns device in USB_STATE_ADDRESS, except on error.
 *
 * If this is called for an already-existing device (as part of
 * usb_reset_and_verify_device), the caller must own the device lock.  For a
 * newly detected device that is not accessible through any global
 * pointers, it's not necessary to lock the device.
 */
static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        int retry_counter)

该函数的主要功能是复位设备、分配地址、获取设备描述符。如果成功,那么返回USB设备的状态USB_STATE_ADDRESS。接下来讲讲这个函数中的具体细节吧。


1、调用 hub_port_reset() 对设备复位

/* Reset the device; full speed may morph to high speed */
    /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
    retval = hub_port_reset(hub, port1, udev, delay, false);
    if (retval < 0)     /* error or disconnect */
        goto fail;
    /* success, speed is known */

复位之后,USB Host就可以确定该USB设备的速度是Full Speed或者是High Speed了。然后设置USB设备状态为USB_STATE_DEFAULT(这个是在hub_port_finish_reset()函数中设置的)。

具体速度的判断是Hub通过与设备之间的握手信号(JK序列)来判断此USB设备是全速设备(Full Speed)还是高速设备(High Speed)。当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址0,端点号0进行。此时,设备能从总线上得到的最大电流是100mA。(所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信)。

关于USB设备的识别可以参照之前的文章:USB 全速/高速设备识别信号分析


2、获取默认管道的最大包长度

/* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
 * it's fixed size except for full speed devices.
 * For Wireless USB devices, ep0 max packet is always 512 (tho
 * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
 */
switch (udev->speed) {
case USB_SPEED_SUPER:
case USB_SPEED_WIRELESS:    /* fixed at 512 */
    udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
    break;
case USB_SPEED_HIGH:        /* fixed at 64 */
    udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
    break;
case USB_SPEED_FULL:        /* 8, 16, 32, or 64 */
    /* to determine the ep0 maxpacket size, try to read
     * the device descriptor to get bMaxPacketSize0 and
     * then correct our initial guess.
     */
    udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
    break;
case USB_SPEED_LOW:     /* fixed at 8 */
    udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
    break;
default:
    goto fail;
}

3、打印该USB设备的提示语句

接下来就是打印如下的log,标记哪个总线上的哪个端口上连接了哪种速度的USB设备,分配的设备编号是多少,与其对应的USB主控制器名称是什么。

[  220.836836] usb 2-1: new high-speed USB device number 4 using xxx-ehci
// hub.c   hub_port_init()
if (udev->speed != USB_SPEED_SUPER)
    dev_info(&udev->dev,
        "%s %s USB device number %d using %s\n",
         (udev->config) ? "reset" : "new", speed,
        devnum, udev->bus->controller->driver->name);

hub_port_init() 中会打印这句话,走到这一步,表示已经识别上一个高速的USB设备。


4、调用 hub_set_address() 给USB设备分配一个地址

为USB设置分配一个地址并配置设备的状态为 USB_STATE_ADDRESS。之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。


5、调用 usb_get_device_descriptor() 获取设备描述符

获取设备描述符之后USB Host就知道连接到Hub上端口的USB设备是属于哪一类了。主机发送 Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。此时设备的状态还是USB_STATE_ADDRESS。

对于设备描述符的相关信息,可以参照USB 2.0协议第9章的内容。Host之所以要获取USB设备的描述符,是想要对该USB设备有足够的了解,知道它是属于哪个类,然后为其加载对应的驱动程序。有可能读出来的bDeviceClass、bDeviceSubClass、bDeviceProtocol字段都为0,表示USB类的信息被保存在接口描述符中。
USB Host发送的获取描述符命令的格式如下:

这里写图片描述


USB 接收到USB设备描述符的格式如下:

这里写图片描述
这里写图片描述


六、USB接口驱动与USB设备匹配

一般在嵌入式系统中,都会根据需要,预先加载USB接口驱动,等与之对应的USB设备插上去枚举成功并与USB接口驱动匹配。接下来的工作就是USB接口驱动的事了。在上面我们接入了一个U盘,属于usb-storage 类。通过获取设备描述符或者接口描述符解析来判断属于哪种类型的USB设备。

在 driver.c 中的 usb_device_match() 函数,每次有新的USB设备添加到USB总线上都会执行该函数去与检查与系统中预先加载的USB接口驱动是否匹配。在该函数中会取得USB设备的接口,通过接口描述符来判定与 USB 接口驱动是否匹配。最终通过 usb_match_one_id_intf() 函数来判断是否匹配。


七、USB 接口驱动

USB 接口驱动通过 struct usb_driver 来定义,然后通过 module_usb_driver() 函数进行注册。比如我们常见的 usb-storage 的USB 接口驱动定义和声明如下:

// <kernel_dir>/drivers/usb/storage/usb.c

static struct usb_driver usb_storage_driver = {
    .name =     "usb-storage",
    .probe =    storage_probe,
    .disconnect =   usb_stor_disconnect,
    .suspend =  usb_stor_suspend,
    .resume =   usb_stor_resume,
    .reset_resume = usb_stor_reset_resume,
    .pre_reset =    usb_stor_pre_reset,
    .post_reset =   usb_stor_post_reset,
    .id_table = usb_storage_usb_ids,
    .supports_autosuspend = 1,
    .soft_unbind =  1,
};

module_usb_driver(usb_storage_driver);

其中对 usb_storage_usb_ids 的定义如下:

// <kernel_dir>/drivers/usb/storage/usual-tables.c

struct usb_device_id usb_storage_usb_ids[] = {
#   include "unusual_devs.h"
    { }     /* Terminating entry */
};

unusual_devs.h 文件包含了所有支持usb-storage类的USB设备的定义。分成常见(USUAL_DEV)和不常见(UNUSUAL_DEV)。比如说,常见的设备定义如下:

// <kernel_dir>/drivers/usb/storage/usual-tables.c

#define USUAL_DEV(useProto, useTrans) \
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans) }

这里将所有的USB设备的 bInterfaceClass 值设置为 USB_CLASS_MASS_STORAGE(8)。这里与USB-IF规定的usb-storage类的值是一致的。USB_INTERFACE_INFO 的定义如下:

// <kernel_dir>/include/linux/usb.h

/**
 * USB_INTERFACE_INFO - macro used to describe a class of usb interfaces
 * @cl: bInterfaceClass value
 * @sc: bInterfaceSubClass value
 * @pr: bInterfaceProtocol value
 *
 * This macro is used to create a struct usb_device_id that matches a
 * specific class of interfaces.
 */
#define USB_INTERFACE_INFO(cl, sc, pr) \
    .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
    .bInterfaceClass = (cl), \
    .bInterfaceSubClass = (sc), \
    .bInterfaceProtocol = (pr)

加入代码调试发现,当插入U盘,在 usb_match_id() 中匹配上了USB接口驱动usb_storage_usb_ids[] 中的 USUAL_DEV(USB_SC_SCSI, USB_PR_BULK)


注:下面的内容还不是完全理解usb-storage驱动以及scsci驱动的相关内容,只是列出了简单的调用关系。

八、usb-storage 接口驱动中USB 识别并添加设备

usb_stor_probe1() 会打印如下提示信息:

[  220.984187] usb-storage 2-1:1.0: USB Mass Storage device detected

usb_stor_probe2() -> scsi_add_host () -> scsi_add_host_with_dma()。在这个函数会打印如下的提示信息:

[  220.990977] scsi1 : usb-storage 2-1:1.0

九、scsi驱动中获取U盘信息的内容

以下内容主要是在<Kernel_Dir>/driver/scsi/sd.c 文件中实现:

1、注册一个块设备驱动

static int __init init_sd(void) 中注册一个modules,调用关系如下:

  • 调用register_blkdev()注册 SD_MAJORS 个以“sd”命名的块设备。可通过 cat /proc/devices 查看到;

  • 注册名为“scsi_disk”的类;

  • 调用 scsi_register_driver(&sd_template.gendrv)注册驱动程序;


2、调用sd_probe()函数

当 scsi 设备接入到系统之后会调用 sd_probe()进行设备与驱动匹配。

struct scsi_device;
struct scsi_disk;
struct gendisk;
struct hd_struct;//用于标记disk的头部信息
  • 调用 alloc_disk()-> alloc_disk_node() 分配一个 struct gendisk 的指针,该指针标记通用的disk;

  • struct gendisk 中有个 struct hd_struct 类型的成员part0,将 disk_typeblock_class 赋值给struct device中的成员typeclass

disk_to_dev(disk)->class = &block_class;
disk_to_dev(disk)->type = &disk_type;

static struct device_type disk_type = {
    .name       = "disk",
    .groups     = disk_attr_groups,
    .release    = disk_release,
    .devnode    = block_devnode,
    .uevent         = disk_uevent,
};
  • 调用 sd_format_disk_name() 重新设置 gendisk 中的 disk_name 为”sdx”,表示disk的个数;

  • 调用执行 sd_probe_async()


3、调用sd_probe_async()函数

sd_probe_async() 承接执行上一步 sd_probe() 的内容

  • 获取 (major, first_minor, minors)struct gendiskminors的值固定为SD_MINORS

  • 设置 struct gendisk的成员 fops 为 sd_fops,为操作scsi相关的file operations;

  • 调用 sd_revalidate_disk() 读取磁盘的相关信息;

  • 调用 add_disk() 注册该磁盘的分区信息到内核中去;

  • 再次调用 sd_revalidate_disk();


4、static int sd_revalidate_disk(struct gendisk *disk)

  • 调用 sd_spinup_disk()让磁盘转起来,以此可以获取磁盘相关信息;

  • 调用 sd_read_capacity() 读取磁盘容量大小;

  • 调用 sd_read_write_protect_flag() 判断磁盘是否有些保护;

  • 读取磁盘的其他信息;

下面的内核打印信息都是在这个函数中打印的,获取到磁盘的相关信息并打印出来。

[  221.010354] sd 1:0:0:0: [sda] 30277632 512-byte logical blocks: (15.5 GB/14.4 GiB)
[  221.018712] sd 1:0:0:0: [sda] Write Protect is off
[  221.023549] sd 1:0:0:0: [sda] Mode Sense: 45 00 00 00
[  221.029224] sd 1:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA

5、void add_disk(struct gendisk *disk)

  • 调用 blk_alloc_devt()struct gendisk中的成员 part0 分配设备号赋值给 disk->majordisk->first_minor;

  • 调用 disk_alloc_events() 为 disk 分配 struct disk_events的空间;

  • 调用 blk_register_region() 来管理设备号;

  • 调用 register_disk() 注册磁盘分区信息;


6、static void register_disk(struct gendisk *disk)

  • 调用 disk_part_scan_enabled() 判断分区数不能大于 DISK_MAX_PARTS

  • 调用 get_capacity() 获取磁盘容量大小,即struct gendisk disk->part0.nr_sects;

  • 调用 bdget_disk() -> disk_get_part() 查找分区号指定的分区;

  • 调用 blkdev_get() 打开一个block 设备;

  • kobject_uevent(&ddev->kobj, KOBJ_ADD);将会执行发送uevent的内容给用户空间,就是在这里会调用
    static int disk_uevent(struct device *dev, struct kobj_uevent_env *env) 的内容将 NPARTS 的参数传递上去。

详细的内容可以参照:http://blog.csdn.net/zjujoe/article/details/2986634


7、int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)

  • 检测block设备的有效性,并claiming;

  • 调用 __blkdev_get();

  • 做一些claiming的操作;


8、static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)

  • 调用 get_gendisk() 通过已知的设备号返回分区号,并返回 struct gendisk;

  • 调用 disk_block_events() block and flush disk event checking;

  • 调用 disk_get_part() 通过 struct gendisk 和分区号来返回分区信息存放在 struct hd_struct;

  • disk->fops->open,执行 sd_open() 的动作;

  • blk_get_backing_dev_info()bdev_inode_switch_bdi()

  • 调用rescan_partitions() 检查分区信息;

  • 如果是无效的分区,调用 invalidate_partitions() 设置其无效;


9、int rescan_partitions(struct gendisk *disk, struct block_device *bdev)

  • 调用 drop_partitions() 判断分区信息是否有效,如果无效需调用 delete_partition();

  • check_disk_size_change() 检查disk的大小是否改变;

  • get_capacity() 获取disk大小;

  • 判断分区code是否超出 EOD;

  • kobject_uevent(&disk_to_dev(disk)->kobj, KOBJ_CHANGE);告诉用户空间分区表改变;

  • disk_expand_part_tbl() 展开 disk_expand_part_tbl 的内容;

  • 判断是否添加分区信息add partitions,并保存在 struct partition_meta_info

  • 如果获取到的分区size不为0,那么调用add_partition()添加分区;

  • 15
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值