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()添加分区;
相关文章推荐
- U盘退出后再插入win7无法识别
- 解决centos6.5不能识别NTFS格式的移动硬盘或U盘问题
- Android Linux Kernel 移植流程
- windows操作系统对U盘不识别的解决方案及在linux操作系统下挂载U盘
- linux kernel Makefile编译流程分析
- linux 配置如何识别U盘
- 让ubuntu系统中的virtualbox虚拟机识别U盘的方法
- DeepLearning tutorial(5)CNN卷积神经网络应用于人脸识别(详细流程+代码实现)
- C#识别U盘和盘符
- linux kernel 启动流程简单分析
- 【linux kernel 3.18】Platform总线驱动注册和注销流程
- DeepLearning tutorial(5)CNN卷积神经网络应用于人脸识别(详细流程+代码实现)
- windows7 下U盘不能识别的解决方法
- ubuntu9 如何识别u盘
- DeepLearning tutorial(5)CNN卷积神经网络应用于人脸识别(详细流程+代码实现)
- 如何让系统识别自己的U盘?
- 多启动U盘制作流程
- Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(4) Linux 多核心啟動流程-kthreadd 與相關的
- wince设备识别为U盘
- Vmware下Ubuntu自动识别U盘