从获取描述符的角度理解Gadget框架

描述

安装好 gadget 驱动程序后(比如 modprobe g_zero), 它只是构造好了各类描述符。在设备的枚举过程会读取描述符。

使用 OTG 线连接电脑和开发板时,电脑软件会执行如下操作:

  • 使用控制传输,读取设备信息(设备描述符):第一次读取时,它只需要得到 8 字节数据,因为第 8 个数据表示端点 0 能传输的最大数据长度。
  • Host 分配地址给设备,然后把新地址发给设备。
  • 使用新地址,重新读取设备描述符,设备描述符长度是 18
  • 读取配置描述符:它传入的长度是 255,想一次性把当前配置描述符、它下面的接口描述符、端点描述符全部读出来。
  • 读取字符描述符。

上述过程里,设备方都是接收到 Host 发给 endpoint 0 的数据,然后做出回应。不同的 Gadget 设备,在返回描述符给主机时,这些操作都是一样的,只是回应的数据不同而已。源码分析的起点都是某个中断函数:

  • IMX6ULL:ci_irq(drivers/usb/chipidea/core.c)
  • STM32MP157: dwc2_hsotg_irq(drivers/usb/dwc2/gadget.c)

4.1 IMX6ULL 的核心函数

IMX6ULL 芯片中 USB 控制器型号是 chipidea,在Linux-4.9.88driversusbchipideacore.c中注册了中断函数:

ci_hdrc_probe
 ret = devm_request_irq(dev, ci- >irq, ci_irq, IRQF_SHARED,
   ci- >platdata- >name, ci);

发生中断后,对于 endpoint 0 的数据处理流程如下:

// Linux-4.9.88driversusbchipideacore.c
ci_irq
 /* Handle device/host interrupt */
 if (ci- >role != CI_ROLE_END)
  ret = ci_role(ci)- >irq(ci);  // udc_irq
   
   // Linux-4.9.88driversusbchipideaudc.c
   udc_irq
                if (USBi_UI  & intr)
                 // Linux-4.9.88driversusbchipideaudc.c
                    isr_tr_complete_handler(ci);
                        /* Only handle setup packet below */
                        if (i == 0 &&
                            hw_test_and_clear(ci, OP_ENDPTSETUPSTAT, BIT(0)))
                            // Linux-4.9.88driversusbchipideaudc.c
                            isr_setup_packet_handler(ci);

函数isr_setup_packet_handler就是处理 endpoint 0 接收到的控制传输的关键。

4.2 STM32MP157的核心函数

STM32MP157 芯片中 USB 控制器型号是 dwc2,在Linux-5.4driversusbdwc2gadget.c中注册了中断函数:

dwc2_gadget_init
 ret = devm_request_irq(hsotg- >dev, hsotg- >irq, dwc2_hsotg_irq,
          IRQF_SHARED, dev_name(hsotg- >dev), hsotg);

发生中断后,函数dwc2_hsotg_irq被调用,它处理 endpoint 中断有两种方法:

  • 使用 DMA 时:调用dwc2_hsotg_epint来处理
  • 不使用 DMA 时:调用dwc2_hsotg_handle_rx来处理

dwc2_hsotg_epint为例进行分析,对于 endpoint 0 的数据处理流程如下:

// Linux-5.4driversusbdwc2gadget.c
dwc2_hsotg_irq
  // 处理endpoint中断
  for (ep = 0; ep < hsotg- >num_of_eps && daint_out; ep++, daint_out > >= 1) {
   if (daint_out & 1)
    dwc2_hsotg_epint(hsotg, ep, 0);
  }

  for (ep = 0; ep < hsotg- >num_of_eps  && daint_in; ep++, daint_in > >= 1) {
   if (daint_in & 1)
    dwc2_hsotg_epint(hsotg, ep, 1);
  }

函数dwc2_hsotg_epint中,对于 endpoint 0 的处理如下:

// Linux-5.4driversusbdwc2gadget.c
dwc2_hsotg_epint
    if (idx == 0 && !hs_ep- >req)
     dwc2_hsotg_enqueue_setup(hsotg);

函数dwc2_hsotg_enqueue_setup被调用时,Gadget 设备已经收到了 SETUP 令牌包,但是还没收到 DATA0 令牌包。dwc2_hsotg_enqueue_setup的作用是,设置、启动一个 request,核心在于设置了 request 的 complete 函数(当 SETTUP 事务完成后这个函数被调用):

框架

当控制传输的"setup事务"完成时,函数dwc2_hsotg_complete_setup被调用。

4.3 如何处理控制传输

无论是 MX6ULL 的函数isr_setup_packet_handler,还是 STM32M157 的函数dwc2_hsotg_complete_setup,它们都是在 Gadget 设备收到"SETUP事务"后才被调用。接收完"SETUP事务"后,就可以从里面知道这个控制传输想做什么(req.bRequest 是什么),然后就可以处理它了。

怎么处理呢?可以分为 3 层:

框架

  • UDC 驱动程序:类似"设置地址"的控制传输,在底层的 UDC 驱动程序里就可以处理,
    • 这类请求有:
      USB_REQ_SET_ADDRESS
      USB_REQ_SET_FEATURE     // 有一些请求可能需要上报改 gadget driver
      USB_REQ_CLEAR_FEATURE   // 有一些请求可能需要上报改 gadget driver
      USB_REQ_GET_STATUS      // 有一些请求可能需要上报改 gadget driver
      
    • 驱动程序位置
      IMX6ULL: Linux-4.9.88driversusbchipideaudc.c, 函数 isr_setup_packet_handler
      STM32MP157: Linux-5.4driversusbdwc2gadget.c, 函数 dwc2_hsotg_complete_setup
      
  • gadget driver:涉及描述符的操作
    • 这类请求有:
      USB_REQ_GET_DESCRIPTOR
      USB_REQ_SET_CONFIGURATION
      USB_REQ_GET_CONFIGURATION
      USB_REQ_SET_INTERFACE
      USB_REQ_GET_INTERFACE
      USB_REQ_GET_STATUS     // 底层 UDC 驱动无法处理的话, gadget driver 来处理
      USB_REQ_CLEAR_FEATURE  // 底层 UDC 驱动无法处理的话, gadget driver 来处理
      USB_REQ_SET_FEATURE    // 底层 UDC 驱动无法处理的话, gadget driver 来处理
      
    • 驱动程序位置
      文件:driversusbgadgetcomposite.c
      函数:composite_setup
      
  • usb_configuration 或 usb_function 的处理:这是二选一的。大部分设备使用控制传输实现标准的 USB 请求,但是也可以用控制传输来进行实现相关的请求,对于这些非标准的请求,就需要上层驱动来处理。
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分