您的位置:首页 > 运维架构 > Linux

Linux kernel U盘识别流程

2017-08-06 15:11 155 查看

一、正常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()
来获得
portchange
portstatus
的状态并设置
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)


其中
portstatus
portchange
有如下几种状态,这些状态都在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上某个端口的变化,通过判断
portstatus
portchange
来判断当前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_CONNECTION
USB_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_type
block_class
赋值给
struct device
中的成员
type
class


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 gendisk
minors的
值固定为
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->major
disk->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()
添加分区;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: