USB Probe流程



CP注:原文地址为http://blog.youkuaiyun.com/jiang_dlut/article/details/5832237 ,原文参考linux版本为2.6,博主比对了linux3.12源码,对文章内容进行了部分调整。

——————————————————————————————————————————————————————————

本文将详细讲述一个USB 设备插上Linux 系统的PC 后是如何一步一步调到我们的usb 设备驱动的probe 函数的我们知道我们的USB 驱动的probe 函数中的一个参数是interface 结构因此一般来说,  一个USB 设备中的任何一个接口都应该有对应的一个驱动程序, 当然也有例外(cdc-acm).

我们知道USB 设备都是通过插入上层HUB 的一个Port 来连入系统并进而被系统发现的,。USB设备插入一个HUB 时,  系统会调用中断服务程序hub_irq()来唤醒USB守护线程,此时会调用hub_port_connect_change()  /*driver/usb/core/hub.c*/

static void hub_connect_change(struct usb_hub *hub, int portl, u16 portstatus, u16 portchange)

{

….

usb_new_device(udev);

}

该函数创建一个usb_device 的对象udev, 并初始化它, 接着调用usb_new_device() 来获取这个usb 设备的各种描述符并为每个interface 找到对用的driver.

int usb_new_device(struct usb_device *udev)

{

 ….

err = usb_enumerate_device(udev); //枚举设备 CP注

 ….

device_add(&udev->dev);

}

该函数首先调用usb_enumerate_device() 来进行设备枚举,它内部会进行一系列的信息获取和解析过程, 接着调用device_add() 来把这个USB 设备添加到USB 系统中去也就是在这个过程中系统回去为这个设备找到相应的驱动2.6 的早期的一些版本中在分析配置描述符后得到interface 的同时把interface 作为设备来调用device_add() 的

int device_add(struct device *dev)

{

….

if((error = bus_add_device(dev)))

bus_probe_device(dev);

}

这个函数是个通用的设备管理函数, 它会为每个设备调用bus_add_device 来把这个设备添加到相应bus 的设备列表中去接着调用bus_probe_device() 来匹配对应的驱动程序对于USB 设备来说第一次调用bus_probe_device() 时的参数dev 代表的是整个usb 设备( 以后usb 设备中的interface 也会作为设备调用这个函数).

int bus_probe_device(struct device *dev)

{

ret = device_attach(dev);

}

这个函数就是用来为设备找到相应的设备驱动程序的( 通过调用device_attach() 实现).

int device_attach(struct device *dev)

{

ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);

}

该函数调用bus_for_each_drv() 来从总线上已注册的所有驱动中找出匹配的驱动程序.

int bus_for_each_drv(struct bus_type *bus,

struct device_driver *start,

void *data,

int (*fn)(struct device_driver *, void *))

{

       ….

while((drv = next_driver(&i)) && !error)

error = fn(drv, data);  // 返回0 将继续搜索, 返回错误值将停止搜索.

}

该函数遍历bus 上的所有驱动程序, 并为每个驱动调用fn() 来查看是否匹配这里的fn 就是__device_attach.

static int __device_attach(struct device_driver *drv, void *data)

{

      struct device *dev = data;


if (!driver_match_device(drv, dev))//判断drv和dev或者都是设备级的或者都是接口级的
return 0;

       return driver_probe_device(drv, dev);

}

int driver_probe_device(struct device *drv, struct device *dev)

{

 ……

  ret = really_probe(dev, drv);

}

    对于usb 驱动来说, 我们通过usb_registe()r 来注册我们的驱动程序, 这个函数会为我们的驱动程序对象(usb_driver) 中的bus 指定为usb_bus_type:

   Struct bus_type usb_bus_type = {

   …

   .match = usb_device_match,

   ….

}

因此对于usb 驱动会首先调用usb_device_match().

static int usb_device_match(struct device *dev, struct device_driver *drv)

{

     if(is_usb_device(dev)) {  /*dev 代表整个usb 设备*/

          ….

}

else  if (is_usb_interface(dev)) /*dev 代表一个usb 设备interface*/

{

   …

   usb_match_id();

   …

   usb_match_dynamic_id();

   …

}

}

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

static int really_probe(struct device *dev, struct device_driver *drv)

{

   …

   dev->driver = drv;  // 先赋值, 以后的probe 过程中会用到

   if(dev->bus->probe)

  {

    //经过跟踪,未发现进入该分支

 }

   else if(drv->probe)//接口级和设备级都会进入这个分支

          ret = drv->probe(dev);

probe_failed:

   dev->drvier = NULL;  //probe 失败重设它

   …

}

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

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

此时的drv 是device_driver类型,那它只能是我们之前注册在USB设备驱动(usb_device_driver)中的内嵌的设备驱动(device_driver)的probe---usb_probe_device.

int usb_register_device_driver(struct usb_device_driver *new_udriver,struct module *owner)

{

      ……

new_udriver->drvwrap.driver.probe = usb_probe_device;  //用来查看特定设备是否存在,所有设备的probe都是这个

    ……

}

usb_probe_device的实现是

static int usb_probe_device(struct device *dev)
{
struct usb_device_driver *udriver = to_usb_device_driver(dev->driver);

……

if (!error)
error = udriver->probe(udev);//执行在generic.c中的 usb_generic_driver 的generic_probe()函数  

        …… 

}

usb_probe_device最终调用的是usb_probe_device的probe函数,在我们的系统中那就是usb_generic_driver. 因为在当前的usb 系统中只有这个driver 是代表整个USB设备的驱动, 它是在usb_init 中被注册的而我们通常写的usb 驱动都是代表一个interface .

struct usb_device_driver usb_generic_driver = {

   …

   .probe = generic_probe,

   …

}

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

static int generic_probe(struct usb_device *udev)

{

   …

   c = choose_configuration(dev);

   if(c >= 0) {

   err = usb_set_configuration(udev, c);  // 设置配置, 并注册interface.

   …

}

}

该函数为这个usb 设备选择一个合适的配置, 并注册这个配置下面的interface.

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() 函数, 它的参数是接口&intf->dev!!根据前面的分析可知这个过程将会走到接下来我们要分析的情况2.

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

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

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

  static int usb_probe_interface(struct device *dev)

{

  struct usb_driver= to_usb_driver(dev->driver);  //dev->driver   在really_probe 中设置.

  …

  error = driver->probe(intf, id);   // 这个就是我们自己写的probe 函数了.

  …

}

 driver->probe(intf, id); 这就调用到我们自己写的代码里面了。也就是使用usb_register()注册的驱动,如hub的接口驱动hub_driver。

 

整个流程大概就是这样:

<think>好的,用户之前问过关于Zynq芯片LCD设备树和驱动的问题,现在又询问USB驱动的流程。我需要先回顾之前的回答结构,确保这次回答保持类似的格式,符合用户的要求。用户可能是在开发Zynq平台的系统,涉及外设驱动的配置,需要了解USB部分的具体实现步骤。 首先,用户的问题是关于USB驱动的流程,需要明确是否需要编写驱动,设备树如何配置,以及整个驱动的工作流程。之前的回答中,分成了设备树编写、驱动需求分析、调试关键点和问题解决方案,这次可以沿用类似的结构。 接下来,我需要确认Xilinx Zynq芯片的USB控制器类型,通常是DWC2或者DWC3,不同版本可能需要不同的配置。设备树部分需要包括USB控制器的节点、PHY配置、时钟和电源管理,可能还需要引脚控制。例如,USB0和USB1的不同模式(主机或设备)在设备树中的配置差异。 驱动方面,Xilinx一般会提供标准驱动,但某些情况下可能需要调整,比如使用OTG模式或自定义PHY设置。需要检查内核配置中的相关选项,如CONFIG_USB_DWC2,并确认驱动路径。流程部分需要描述从硬件初始化、设备树解析、PHY和时钟配置、控制器驱动加载到USB设备枚举的整个过程。 调试关键点可能包括时钟频率、PHY状态、电源管理和DMA设置,用户可能需要使用dmesg、lsusbusbmon等工具进行调试。常见问题如设备无法识别、供电不足或速度不匹配,需要给出相应的解决方案,比如检查VBUS供电,调整设备树中的参数。 用户可能的隐藏需求是希望有一个全面的指南,帮助他们在实际项目中配置和调试USB接口,避免常见错误。因此,回答需要详细但不过于技术化,步骤清晰,并提供验证命令和示例代码片段,方便用户直接应用。同时,需要提醒用户根据硬件设计和内核版本调整配置,必要时参考官方文档或社区资源。</think>关于Zynq芯片USB驱动实现流程,以下是分步技术解析: 一、设备树配置核心要素 1. USB控制器节点配置(以USB0为例) ```dts usb0: usb@e0002000 { compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2"; reg = <0xe0002000 0x1000>; interrupts = <0 21 4>; clocks = <&clkc 28>; dr_mode = "host"; // 可配置为host/otg/peripheral phy-names = "usb-phy"; phy-type = "ulpi"; usb-phy = <&usb_phy0>; }; ``` 2. USB PHY配置(需根据实际硬件设计) ```dts usb_phy0: phy@e0002000 { compatible = "usb-nop-xceiv"; reg = <0xe0002000 0x1000>; #phy-cells = <0>; reset-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; }; ``` 3. 时钟与电源配置 ```dts clocks { usb0_clk: clock { #clock-cells = <0>; clock-frequency = <480000000>; }; }; reg_usb0_vbus: regulator@0 { compatible = "regulator-fixed"; regulator-name = "USB0_VBUS"; regulator-min-microvolt = <5000000>; regulator-max-microvolt = <5000000>; gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>; }; ``` 二、驱动加载流程 1. 硬件初始化流程: - USB控制器时钟使能 - PHY硬件复位(通过GPIO控制) - VBUS电源使能 - ULPI总线初始化 2. Linux驱动加载顺序: ```text USB PHY驱动(drivers/phy/phy-*) → USB控制器驱动(drivers/usb/dwc2/platform.c) → USB核心层(drivers/usb/core) → USB设备类驱动(HID/Storage等) ``` 三、内核配置关键选项 ```bash CONFIG_USB=y CONFIG_USB_DWC2=y # DesignWare USB2控制器驱动 CONFIG_USB_ULPI=y # ULPI总线支持 CONFIG_USB_STORAGE=y # USB存储设备支持 CONFIG_USB_EHCI_HCD=y # 高速主机控制器 CONFIG_USB_OHCI_HCD=y # 全速/低速主机控制器 CONFIG_USB_GADGET=y # 设备模式支持 ``` 四、调试关键方法 1. 基础状态检测: ```bash dmesg | grep -i usb # 查看内核初始化日志 lsusb -v # 枚举连接的USB设备 cat /sys/kernel/debug/usb/devices ``` 2. 信号质量检测: ```bash usbmon工具抓取USB协议数据包 # 启用usbmon模块 modprobe usbmon # 查看捕获接口 ls /sys/kernel/debug/usb/usbmon ``` 3. 实时参数监测: ```bash cat /sys/bus/usb/devices/usb1/power/level # 查看电源状态 cat /sys/kernel/debug/phy/phy0/status # PHY状态检测 ``` 五、典型问题解决方案 1. 设备无法识别: - 检查VBUS电压(需5V±5%) - 验证D+/D-线路阻抗匹配(通常接22Ω串联电阻) - 检测PHY复位时序(复位脉冲宽度需>1ms) 2. 高速模式降速: ```dts // 在设备树中增加PHY参数 usb_phy0: phy@e0002000 { compatible = "ulpi-phy"; view-port = <0x0170>; drv-vbus; }; ``` 3. OTG模式切换失败: ```bash # 手动切换模式(需内核支持) echo host > /sys/devices/platform/soc/e0002000.usb/driver_override echo "e0002000.usb" > /sys/bus/platform/drivers/dwc2/bind ``` 六、驱动开发特殊场景 1. 自定义HID设备开发: - 实现`struct usb_driver`结构体 - 注册端点中断处理程序 ```c static struct usb_driver myhid_driver = { .name = "myhid", .probe = myhid_probe, .disconnect = myhid_disconnect, .id_table = myhid_table, }; ``` 2. 性能优化技巧: - 启用DMA传输模式 ```dts usb0: usb@e0002000 { snps,usb-dma = <0>; snps,usb-dma-burst = <8>; }; ``` - 调整URB缓冲区大小 ```c usb_alloc_coherent(dev, 4096, GFP_KERNEL, &dma_handle); ``` 建议使用Xilinx提供的USB分析工具包(需搭配ChipScope)进行信号完整性验证,同时注意不同Zynq型号的USB IP核版本差异(如Zynq-7000与UltraScale+的USB3.0控制器架构不同)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值