当前位置: 首页 > news >正文

Linux内核USB3.0驱动框架分析--USB主机控制器hcd驱动分析

一,概述

usb主机控制器驱动一般以platform的形式将驱动注册进内核,,因此我们需要从前面一篇文章的框图说起。主要分析下图中橙色部分的内容。
在这里插入图片描述
二,usb主机控制器相关函数

2.1 usb_create_hcd

我们来看一下usb_create_hcd函数,该函数定义在drivers/usb/core/hcd.c:

struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,    // 参数1为ohci_s3c2410_hc_driver,参数2为s3c_device_ohci ->dev,参数3位s3c24xxstruct device *dev, const char *bus_name)
{return __usb_create_hcd(driver, dev, dev, bus_name, NULL);
}

这里调用了__usb_create_hcd函数:

struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver,   struct device *sysdev, struct device *dev, const char *bus_name,struct usb_hcd *primary_hcd)
{struct usb_hcd *hcd;hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); // 动态申请内存空间,额外申请hcd私有数据大小的内存timer_setup(&hcd->rh_timer, rh_timer_func, 0);  // 初始化定时器,设置定时器超时函数hcd->driver = driver;   // 绑定hc_driverreturn hcd;
}

总结一下函数执行流程:

首先分配一个hcd的内存空间,包含私有数据空间;
设置hcd->rh_timer设置定时器超时函数rh_timer_func,主机控制器以轮询的方式查找端口变化状态;
设置 hcd->driver = &_hc_driver等 ;

hc_driver是usb主机控制器的驱动函数,实现了通过主机控制器硬件向外通信的方法。

2.2 rh_timer_func

定时器回调函数定义在drivers/usb/core/hcd.c文件中:

/* timer callback */
static void rh_timer_func (struct timer_list *t)
{struct usb_hcd *_hcd = from_timer(_hcd, t, rh_timer);usb_hcd_poll_rh_status(_hcd);
}

2.3 usc_add_hcd

用usb_hcd结构体定义好usb_hdc设备后,用usb_add_hcd函数向linux内核注册usb主机控制器驱动,函数定义在drivers/usb/core/hcd.c:

int usb_add_hcd(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags)
{int retval;struct usb_device *rhdev;retval = usb_phy_roothub_set_mode(hcd->phy_roothub,PHY_MODE_USB_HOST);retval = usb_register_bus(&hcd->self);if (retval < 0)goto err_register_bus;rhdev = usb_alloc_dev(NULL, &hcd->self, 0);if (rhdev == NULL) {dev_err(hcd->self.sysdev, "unable to allocate root hub\n");retval = -ENOMEM;goto err_allocate_root_hub;}mutex_lock(&usb_port_peer_mutex);hcd->self.root_hub = rhdev;mutex_unlock(&usb_port_peer_mutex);/* initialize tasklets */init_giveback_urb_bh(&hcd->high_prio_bh);init_giveback_urb_bh(&hcd->low_prio_bh);/* starting here, usbcore will pay attention to this root hub */retval = register_root_hub(hcd);if (hcd->uses_new_polling && HCD_POLL_RH(hcd))usb_hcd_poll_rh_status(hcd);     // 定时器轮询root hub端口状态return retval;
}

该函数代码比较长,这里挑重点说:

usb_register_bus(&hcd->self) ,将 hcd->usb_bus 注册到全局链表usb_bus_list;
usb_alloc_dev为根hub分配一个usb_device 结构(内核中,所有的真实的usb设备;比如hub,鼠标等都用usb_device结构来描述);
register_root_hub注册根hub设备到usb_bus_type;
usb_hcd_poll_rh_status定时器函数轮询hub端口状态;

usb主机控制器也只不过是分配了一个usb_hcd结构体,为它的根hub分配了一个usb_device 结构体,注册到usb_bus_type罢了,后边是根hub的注册和设备枚举过程了。

2.4 usb_alloc_dev

usb_alloc_dev函数定义在drivers/usb/core/hcd.c:

struct usb_device *usb_alloc_dev(struct usb_device *parent,struct usb_bus *bus, unsigned port1)
{struct usb_device *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL);device_initialize(&dev->dev);dev->dev.bus = &usb_bus_type;dev->dev.type = &usb_device_type;dev->dev.groups = usb_device_groups;dev->state = USB_STATE_ATTACHED;dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE;dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT;/* ep0 maxpacket comes later, from device descriptor */usb_enable_endpoint(dev, &dev->ep0, false);dev->can_submit = 1;/* Save readable and stable topology id, distinguishing devices* by location for diagnostics, tools, driver model, etc.  The* string is a path along hub ports, from the root.  Each device's* dev->devpath will be stable until USB is re-cabled, and hubs* are often labeled with these port numbers.  The name isn't* as stable:  bus->busnum changes easily from modprobe order,* cardbus or pci hotplugging, and so on.*/if (unlikely(!parent)) {dev->devpath[0] = '0';dev->route = 0;dev->dev.parent = bus->controller;device_set_of_node_from_dev(&dev->dev, bus->sysdev);dev_set_name(&dev->dev, "usb%d", bus->busnum);root_hub = 1;}return dev;
}

该函数主要做了以下事情:

这里实际上就是为根hub,动态分配一个usb_device;
初始化设备基类dev->dev:dev->dev.bus = &usb_bus_type,设置总线类型dev->dev.type = &usb_device_type,设置设备类型,用于和usb接口区分;等等;
初始化dev其它成员;dev->state = USB_STATE_ATTACHED,设备状态设置位已连接;usb_enable_endpoint函数使能usb端点0,用于usb通信,usb主机控制器会通过该端点和root hub通信获取设备/配置描述信息;一个 usb 设备有多个配置,每个配置又有多个接口,每个接口有多个端点。但是端点0比较特殊,它是整个usb设备共享的,端点0是直接存储在usb_device->ep0字段中。等等;

2.5 register_root_hub

register_root_hub函数定义在drivers/usb/core/hcd.c,顾名思义,就是注册root_hub设备,先是对自身初始化,然后获取设备描述符,最后把自己当普通usb设备给注册了。

static int register_root_hub(struct usb_hcd *hcd)
{struct device *parent_dev = hcd->self.controller;struct usb_device *usb_dev = hcd->self.root_hub;  // 根hub设备const int devnum = 1;int retval;usb_dev->devnum = devnum;        //根hub设备地址为1usb_dev->bus->devnum_next = devnum + 1;set_bit (devnum, usb_dev->bus->devmap.devicemap);usb_set_device_state(usb_dev, USB_STATE_ADDRESS);   // 变更状态mutex_lock(&usb_bus_idr_lock);usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);  // 获取设备描述符retval = usb_new_device (usb_dev);if (retval) {dev_err (parent_dev, "can't register root hub for %s, %d\n",dev_name(&usb_dev->dev), retval);} else {spin_lock_irq (&hcd_root_hub_lock);hcd->rh_registered = 1;spin_unlock_irq (&hcd_root_hub_lock);/* Did the HC die before the root hub was registered? */if (HCD_DEAD(hcd))usb_hc_died (hcd);      /* This time clean up */}mutex_unlock(&usb_bus_idr_lock);return retval;
}

这里我们先介绍usb_get_device_descriptor函数,该函数用于获取根hub设备的描述符;

int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
{struct usb_device_descriptor *desc;desc = kmalloc(sizeof(*desc), GFP_NOIO);ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size); // 获取usb设备描述符,request = 0x06,requestType = 0x80、value = 0x100return ret;
}

2.6 usb_new_device
接下来我们再来看看usb_new_device。

int usb_new_device(struct usb_device *udev)
{err = usb_enumerate_device(udev);       /* Read descriptors */dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n",udev->devnum, udev->bus->busnum,(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));/* export the usbdev device-node for libusb */udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,(((udev->bus->busnum-1) * 128) + (udev->devnum-1)));/* Tell the world! */announce_device(udev);if (udev->serial)add_device_randomness(udev->serial, strlen(udev->serial));if (udev->product)add_device_randomness(udev->product, strlen(udev->product));if (udev->manufacturer)add_device_randomness(udev->manufacturer,strlen(udev->manufacturer));err = device_add(&udev->dev);return err;
}

usb_new_device函数用于注册usb根hub设备,先是枚举调用usb_enumerate_device获取设备配置描述符信息(这里枚举获取的pid、vid等用于设备驱动匹配的id使用):

static int usb_enumerate_device(struct usb_device *udev)
{int err;struct usb_hcd *hcd = bus_to_hcd(udev->bus);if (udev->config == NULL) {err = usb_get_configuration(udev);    // 获取配置描述符,这里根据设备描述符dev->descriptor.bNumConfiguratios中配置数量,来一一发送USC_DT_CONFIG(value=0x200)命令获取配置描述符,初始化dev->config[num]成员}/* 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);  // 从设备描述符获取产品串号信息err = usb_enumerate_device_otg(udev);return 0;
}

usb_get_configuration,大致步骤也是调用usb_control_msg函数通过usb主机控制器向root hub发送控制报文,获取配置描述符信息。

然后调用device_add将设备加入到设备链表中,在执行device_add后,将执行总线的匹配函数usb_device_match:

device_addbus_probe_devicdevice_attachbus_for_each_drv__device_attachdriver_match_devicedrv->bus->match(dev, drv)usb_device_match

注册根hub设备后,由于在usb子系统初始化函数中注册了usb通用设备驱动usb_generic_driver,这样就会执行usb_generic_driver的probe函数,即generic_probe。这里就和前面一幅图的流程一样了。

2.7 usb_hcd_poll_rh_status

/** Root Hub interrupt transfers are polled using a timer if the* driver requests it; otherwise the driver is responsible for* calling usb_hcd_poll_rh_status() when an event occurs.** Completions are called in_interrupt(), but they may or may not* be in_irq().*/
void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{struct urb      *urb;length = hcd->driver->hub_status_data(hcd, buffer);if (length > 0) {/* try to complete the status urb */spin_lock_irqsave(&hcd_root_hub_lock, flags);urb = hcd->status_urb;if (urb) {clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);hcd->status_urb = NULL;urb->actual_length = length;memcpy(urb->transfer_buffer, buffer, length);usb_hcd_unlink_urb_from_ep(hcd, urb);usb_hcd_giveback_urb(hcd, urb, 0);} else {length = 0;set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);}spin_unlock_irqrestore(&hcd_root_hub_lock, flags);}/* The USB 2.0 spec says 256 ms.  This is close enough and won't* exceed that limit if HZ is 100. The math is more clunky than* maybe expected, this is to make sure that all timers for USB devices* fire at the same time to give the CPU a break in between */if (hcd->uses_new_polling ? HCD_POLL_RH(hcd) :(length == 0 && hcd->status_urb != NULL))mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));   // 修改定时器超时时间,并重新激活定时器
}

usb_hcd_poll_rh_status 调用主机控制器的hub_status_data函数获取端口状态。如果端口的状态有变化,那么length > 0,把获取到的端口状态的数组拷贝到urb->transfer_buffer中,就是前面的hub->buffer中,同时调用usb_hcd_giveback_urb函数。

2.8 usb_hcd_giveback_urb

void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
{   if (!hcd_giveback_urb_in_bh(hcd) && !is_root_hub(urb->dev)) {__usb_hcd_giveback_urb(urb);return;}}
static void __usb_hcd_giveback_urb(struct urb *urb)
{urb->complete(urb);}

usb_hcd_giveback_urb函数中调用urb->complete (urb),而urb->complete = hub_irq,这样就返回到了hub中 ;

三,usb驱动的probe匹配过程
前面我们分析到调用usb_new_device来进行配置使usb设备可以正常工作,我们现在分析一下具体过程。主要是找到对应的客户驱动程序和该USB设备挂钩。

usb_new_device中调用device_add,将设备添加到设备层次结构中。

大概调用流程:device_add -> bus_probe_device -> device_initial_probe -> __device_attach -> bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);

3.1 1. usb_device_match

我们分析一下就是usb_device_match,这个函数只是做一些粗略的匹配, 如果匹配成功则返回1。这个函数只是做一些粗略的匹配, 如果匹配成功则返回1, 然后由driver_probe_device来做进一步的匹配, 如果匹配失败则返回0, 并且driver_probe_device也不会在执行. 这个函数的调用保证了dev, drv 要么都是设备级别的( 即dev 代表usb 设备,drv 代表usb 设备驱动), 要么都是接口级别的( 即dev 代表usb 设备的一个interface,drv 代表usb 接口驱动).

static int usb_device_match(struct device *dev, struct device_driver *drv)
{/* devices and interfaces are handled separately */if (is_usb_device(dev)) {/* interface drivers never match devices */ //是匹配USB设备的驱动,USB接口的驱动不能匹配if (!is_usb_device_driver(drv))return 0;/* TODO: Add real matching code */return 1;} else if (is_usb_interface(dev)) { //如果是USB接口struct usb_interface *intf;struct usb_driver *usb_drv;const struct usb_device_id *id;/* device drivers never match interfaces *///usb接口在注册driver时将for_devices设置为0,for_devices =1,表示设备驱动,for_devices = 0,表示接口驱动if (is_usb_device_driver(drv))return 0;intf = to_usb_interface(dev);usb_drv = to_usb_driver(drv);id = usb_match_id(intf, usb_drv->id_table); //匹配id tableif (id)return 1;id = usb_match_dynamic_id(intf, usb_drv); //匹配动态id tableif (id)return 1;}return 0;
}

3.2 driver_probe_device

driver_probe_device主要是调用really_probe -> (drv->probe)

对于usb 来说这个函数的调用有2 种分支, 1: dev,drv 代表的是设备级别的, 2 dev,drv 代表的是接口级别的. 其他情况组合在usb_device_match 中被过滤掉了.

分支1: dev,drv 代表的是设备级别:

此时的drv 肯定是usb_generic_driver. 因为在当前的usb 系统中只有这个driver 是代表整个设备的驱动, 它是在usb_init 中被注册的, 而我们通常写的usb 驱动都是代表一个interface 的.

因此, 此时的drv->probe 将调用generic_probe().再到usb_set_configuration

int usb_set_configuration(struct usb_device *dev, int configuration)
{for(I = 0; I < nintf; i++) {struct usb_interface *intf = cp->interface[i];device_add(&intf->dev); //又会进入匹配
}

该函数比较重要, 但我们只关心probe 过程因此省掉了很多东西. 它为当前配置下的每个interface 调用device_add() 函数, 根据前面的分析可知, 这个过程将会走到接下来我们要分析的分支2.

分支2: dev,drv 代表的是interface 级别:

此时的dev 代表着一个interface, 而drv 就代表了我们自己的usb 驱动. 但是我们应当看到drv是device_driver 类型, 而我们写的usb 驱动的类型一般是usb_driver, 因此这里的probe 和我们自己写的probe 显然不是同一个. 实际上这里的drv 是我们的驱动对象里内嵌的一个子对象( 因为linux 下所以的驱动都必须用device_driver 来代表,). 那这个子对象的probe 函数是在哪里赋值的呢?

这就要看usb_register宏了,实际里面是调用usb_register_driver

int usb_register_driver(struct usb_driver *new_driver, struct module *owner,const char *mod_name)
{new_driver->drvwrap.for_devices = 0;new_driver->drvwrap.driver.name = new_driver->name;new_driver->drvwrap.driver.bus = &usb_bus_type;new_driver->drvwrap.driver.probe = usb_probe_interface; //这里是probe函数new_driver->drvwrap.driver.remove = usb_unbind_interface; new_driver->drvwrap.driver.owner = owner;new_driver->drvwrap.driver.mod_name = mod_name;new_driver->drvwrap.driver.dev_groups = new_driver->dev_groups;spin_lock_init(&new_driver->dynids.lock);INIT_LIST_HEAD(&new_driver->dynids.list);retval = driver_register(&new_driver->drvwrap.driver);if (retval)goto out;retval = usb_create_newid_files(new_driver);if (retval)goto out_newid;pr_info("%s: registered new interface driver %s\n",usbcore_name, new_driver->name); //一般log会打印这个
}

跟踪这个函数我们可以看到这里的probe 函数实际上是usb_probe_interface( 所有的usb interface 驱动都是一样的).

 static int usb_probe_interface(struct device *dev)
{struct driver = to_usb_driver(dev->driver);  //dev->driver   在really_probe 中设置.error = driver->probe(intf, id);   // 这个就是我们自己写的probe 函数了.
}

driver->probe(intf, id); 这就调用到我们自己写的代码里面了,

3.3 流程图

大概流程图是一样的:
在这里插入图片描述


http://www.mrgr.cn/news/48746.html

相关文章:

  • 【经管】上市公司供应链金融数据(1990-2023年)
  • React
  • 题解:牛客小白月赛102(A - C)
  • ASR-01和ESP32语音控制LED灯——基于VSCODE编辑器和ESP-IDF环境
  • 《Spring Cloud 微服务:构建高效、灵活的分布式系统》
  • 优秀的面试官!通过一个问题考察了所有网络编程知识点
  • Floyd
  • 51单片机的土壤湿度检测控制系统【proteus仿真+程序+报告+原理图+演示视频】
  • CBA认证培训,业务架构师的筑梦之旅!
  • Windows,MySQL主从复制搭建
  • 状态管理(2)——@State组件内状态
  • 【pyspark学习从入门到精通2】理解pyspark_2
  • 85 外网用户通过域名访问内网服务器
  • 复盘20241012
  • 计算机网络:数据链路层 —— 可靠传输服务
  • 【工具类】hutool http请求获取S3图片流
  • 3D技术的应用场景有哪些?
  • [Gtk] 前言
  • Centos7快速安装配置RabbitMQ
  • LangChain——Embedding 智谱AI