USB Gadget 入门一条龙

文章说明:

1.基于atmel 的sama5d3处理器, linux kernel 4.1.20.

2.本文章的原doc格式文档, 文章中流程图的原drawio格式文件存放路径:

          https://gitee.com/suiren/sama5d3_usb_gadget_read/tree/master/sama5d3_read

3.文章中分析使用的精简的gadget zero驱动, 和测试该zero驱动所使用的host端驱动例程路径:

          https://gitee.com/suiren/sama5d3_usb_gadget_read/tree/master/usb_bbb

4.Gadget代码中结构体与代码的关系说明:

         https://gitee.com/suiren/sama5d3_usb_gadget_read/blob/master/sama5d3_read/gadget_read.c

5.本文章使用SED情景分析法阅读而得.

  1. usb gadget框架, usb device控制器和gadget_zero功能的代码流程总结:

1.1 Gadget初始化

usba_udc_probe()

    ->读取dts, 获得控制器寄存器的地址,和ep数据内存的地址,获取控制器irq号. 为struct usba_udc *udc_a 分配内存.

    ->atmel_udc_of_init()

        ->读取dts, 获得usb_vbus和usb_id引脚的gpio号, 获取所有端点的信息,包含每个端点支持的能力. 当前usb device控制器拥有16个端点. 为udc_a->usba_ep数组分配内存, 根据dts的信息填充usba_ep数组. usba_ep[0]->ep作为控制端点供枚举流程使用, usba_ep[1~15]->ep可供用户编程使用. 将usba_ep[1~15]->ep添加进ep_list列表.

    ->注册控制器的irq中断服务函数, 用于处理控制器的状态变化,ep收发等中断. 注册usb_vbus引脚中断,处理设备的拔插. 注册usb_id引脚中断, 处理id引脚的电平, 开关usb_host的vbus的电压输出.

    ->usb_add_gadget_udc()

        ->usb_add_gadget_udc_release()

           ->为 struct usb_udc *udc分配内存, 将udc添加进udc_list列表.

usb_composite_probe()

    ->usb_gadget_probe_driver()

        -> 遍历 udc_list列表, 找到udc->driver为NULL的udc结构体.

        ->udc_bind_to_driver()

            ->driver->bind() ==> composite_bind()

            ->为struct usb_composite_dev *cdev分配内存.

            ->composite_dev_prepare()

                ->设置cdev->req, 该req用于设备枚举流程的数据传输.为cdev->req->buf分配内存.

            ->composite->bind() ==> zero_bind()

                #注释: loopback_driver 是包含纯粹的配置描述符的信息,func_lb则是包含每个接口描述符的信息. 每个接口描述符紧跟着还有还有端点描述符的信息.

                ->usb_add_config_only(cdev, &loopback_driver)

                    -> 将loopback_driver 加入cdev->configs列表. loopback_driver包含配置描述符的部分头部信息.

                ->usb_add_function(&loopback_driver, func_lb)

                    ->将func_lb加入loopback_driver->functions列表.

                    ->function->bind() ==> loopback_bind()

                        ->usb_interface_id()

                            -> 将func_lb 指针复制到 loopback_driver->interface[x]. x从0开始计数. 多次调用usb_add_function(), 就可以向interface[]数组添加更多的functions,即接口描述符+端点描述符.

                        ->usb_ep_autoconfig()

                            ->根据端点描述符从ep_list列表内,找到符合的,未被使用的ep, 并根据端点描述符设置改ep的地址.

            ->usb_gadget_udc_start()

                ->udc->gadget->ops->udc_start ==> atmel_usba_start()

                    ->获取usb_vbus引脚的电压,高电平为已经接入设备.那么将执行 usba_start()函数去初始化usb device控制器. 当前电压为低电平.

1.2 设备接入主机后,引发中断

usba_vbus_irq_thread()

    -> 使能时钟. 使能控制器, 使能写控制器寄存器功能. 使能控制器reset完成中断, 即控制器reset完成后,将发出中断.

1.3 控制器reset完成后

usba_udc_irq()

    ->if (status & USBA_END_OF_RESET)

        ->清楚中断标志位. 写寄存器,设置端点0属性:最大包大小64,类型为控制端点. 使能端点0 RX_setup中断, 即端点0收到控制传输的SETUP包时将发出中断. 使能 suspend中断, 使能resume 完成中断, 使能ep0 中断.

1.4 控制器suspend完成后:

usba_udc_irq()

    -> 清除中断标志位

1.5 ep0 接收到控制传输的SETUP包,host请求设备描述符

usba_udc_irq()

    ->if (ep_status)

        -> 遍历udc_a->usba_ep数组, 找到类型为控制端点的ep, 都执行usba_control_irq()函数. 当前找到的ep为 struct usba_ep eps[0]

        ->usba_control_irq()

            -> 读取端点状态寄存器, 判断RX_SETUP位, 值为1继续执行.

                ->从eps[0]->fifo获取host发来的数据,数据内容为:

                bRequestType 0x80. bRequest 0x06, wValue 0x100length 64

                即host请求设备描述符.

                清除RX_SETUP中断标志位.

                    -> 判断当前ep的索引值, 为0代表时默认用于控制传输的ep0, 则继续执行下面代码.

                        ->handle_ep0_setup()

                            ->switch (crq->bRequest) {

                              default:

                                ->udc->driver->setup() ==> composite_setup()

                                    -> 根据ep获得的数据ep->fifo, 判断此次host获取描述符的请求是要获取设备描述符.

                                        -> 将根据配置描述符数量,ep0最大包大小等数据构建的cdev->desc设备描述符 复制到 cdev->req->buf. buf的内存在composite_dev_prepare()内被分配.

                            -> 判断ep要发送的数据长度大于等于0, 继续执行

                                ->若长度为0,就要设置为要发送0长数据包,以告诉host数据传输完毕.

                                ->composite_ep0_queue()

                                    ->usb_ep_queue()

                                        ->ep->ops->queue() ==> usba_ep_queue()

                                            ->将cdev->req 添加进struct usba_ep *eps[0]->queue列表.

                                            -> 写控制器寄存器, 使能tx包准备完成中断.   

             

1.6 ep0 IN数据包准备完成中断发生, 即要发送给host的数据包准备好了

usba_udc_irq()

    ->if (ep_status)

        -> 遍历udc_a->usba_ep数组, 找到类型为控制端点的ep, 都执行usba_control_irq()函数.

        ->usba_control_irq()

            -> 读取端点状态寄存器的值. 从struct usba_ep *eps[0]->queue列表中取出一个req.

            -> 判断端点状态寄存器的tx包准备完成中断标志位(USBA_TX_PK_RDY)值为1, 则继续执行.

                ->submit_request()

                    ->next_fifo_transaction()

                        ->将req->buf的数据复制到struct usba_ep eps[0]->fifo 内.

                        -> 写控制器寄存器, 使能tx包准备完成中断.

                    ->判断req->last_transaction, 值为1, 代表所有数据都复制到eps[0]->fifo.  值为1, 则继续执行.

                        -> 关闭tx包准备完成中断(USBA_TX_PK_RDY), 使能IN数据传输完成中断, 即数据向host发送完成时,将发出中断.

            -> 读端点控制寄存器, 判断 USBA_TX_COMPLETE 标志位. 即IN数据传输完成后的中断标志位. 值为1, 继续执行.

                ->根据端点状态寄存器, 判断当前控制传输处于数据IN阶段

                  switch (ep->state) {

                      case DATA_STAGE_IN:

                   -> 使能OUT数据接收中断, 即当conghost接收到数据时将发出中断. 关闭IN数据发生中断.

            -> 读端点控制寄存器, 判断 USBA_RX_BK_RDY 标志位. 即OUT数据接收完成后的中断标志位. 值为1, 继续执行.

                ->根据端点状态寄存器, 判断当前控制传输处于数据OUT阶段

                    switch (ep->state) {

                        case STATUS_STAGE_OUT:

                    -> 关闭OUT数据接收完成中断. 清除OUT数据接收完成中断标志位. 将req从eps[0]->queue队列中移除.

1.7 再次发生控制器reset完成中断

        

1.8 ep0 接收到控制传输的SETUP包, host请求设置设备地址

usba_udc_irq()

    ->if (ep_status)

    # 与上一次接收到setup包的流程相似,但这次为host请求设置设备地址.将省略类似的代码.

        ->switch (crq->bRequest) {

             case USB_REQ_SET_ADDRESS:

            ->写控制器寄存器, 设置设备地址.使能IN数据输出完成中断.

1.9 发生IN数据传输完成中断:

usba_udc_irq()

    ->if (ep_status)

        -> 判断当前端点寄存器的 IN数据传输完成中断标志位位1.

          if ((epstatus & epctrl) & USBA_TX_COMPLETE)

           ->switch (ep->state) {

                 case STATUS_STAGE_ADDR:

                -> 写控制器寄存器, 使能设备地址.

1.10 ep0 接收到控制传输的SETUP包,host请求完整设备描述符:

usba_udc_irq()

    ->if (ep_status)

    # 此次host再次请求设备描述符, 不再重复描述.

1.11 ep0 接收到控制传输的SETUP包, 此次时获取配置描述符:

usba_udc_irq()

    ->if (ep_status)

        ->usba_control_irq()

            -> 从eps[0]->fifo获取host发来的数据.

            ->handle_ep0_setup()

                ->udc->driver->setup() ==> composite_setup()

                     ->case USB_REQ_GET_DESCRIPTOR:

                          switch (w_value >> 8) {

                              case USB_DT_CONFIG:

                         -> 从eps[0]->fifo中,得知host希望获取的配置描述符的编号(w_index的低8位). 遍历cdev->configs队列,找到第x个()的配置(config).

                            ->config_buf()

                                -> 将配置描述符(config)的内容复制到cdev->req->buf内. 遍历config->functions列表, 找到匹配gadget->speed的function(包含接口描述符和端点描述符), 将function的内容复制到cdev->req->buf.

                    ->usb_ep_queue()

                        ->将cdev->req 加入eps[0]->queue列表

     # 接下来就是将cdev->req->buf的内容复制到eps[0]->fifo,发送给host

1.12 ep0 接收到控制传输的SETUP包, 依旧是获取配置描述符

上一次只是获取描述符而已. 这一次获取描述符+接口,端点描述符. 不进行重复描述.

1.13 ep0 收到控制传输的SETUP包, 此次host请求设置配置描述符

usba_udc_irq()

    ->if (ep_status)

        ->usba_control_irq()

            ->handle_ep0_setup()

                ->composite_setup()

                     ->switch (ctrl->bRequest) {

                        case USB_REQ_SET_CONFIGURATION:

                            -> 遍历cdev->configs列表所有的配置, 找到对应host请求编号的config. 遍历config->interface[]数组内所有的描述符,对与接口描述符则执行其set_alt()函数,进行接口的初始化.该函数由gadget设备驱动定义,即由用户定义.

                               ->f->set_alt() ==> loopback_set_alt()

                                    ->enable_endpoint()

                                        ->usb_ep_enable()

                                             ->ep->ops->enable ==> usba_ep_enable()

                                                -> 根据loopback->in_ep 和 loopback->out_ep 找到对应的物理端点寄存器, 使能对应端点. 根据端点描述符, 设置端点的最大包大小,端点传输方向, 端点类型.

                                    ->alloc_requests()

                                        -> 为loopback->in_req 和out_ereq成员分配内存, 为xx_req->buf分配内存. 设置xx_req->comlete数据传输完成函数(当buf接收到数据,或数据传输为host之后,会执行该函数) 为loopback_complete().

                                        ->usb_ep_queue()

                                            -> 将xx_req加入loopback->xx_ep->queue队列内. 等待数据接收或被传输至host..

1.14 epx 端点发生中断, 是host有批量传输的请求.

usba_udc_irq()

-> if (dma_status) {

        -> 遍历所有端点,读取对应端点的状态寄存器, 判断是否该端点发生数据传输的中断.

            从xx_ep->queue队列取出位于队首的req, 将它移除队列. 执行该req->complete()函数.

  1. usb device 控制器寄存器说明:

2.1 在usba_start()函数中使用

CTRL     USBA_ENABLE_MASK  使能控制器, 使能写控制器寄存器功能

INT_ENB USBA_END_OF_RESET  控制器reset 完成时, 引发中断

2.2 在usba_udc_irq()函数中使用

INT_STA USBA_DET_SUSPEND 控制器suspend 完成标志位

      DMA_INT  DMA中断发生标志位

          EPT_INT  端点中断标志位

          USBA_END_OF_RESET 控制器reset完成标志位

       USBA_HIGH_SPEED 控制器的工作速度,满速或高速

if (status & USBA_DET_SUSPEND)

INT_CLR USBA_DET_SUSPEND 清除INT_STA寄存器的中断标志位

if (dma_status)

DMA CONTROL  USBA_DMA_END_TR_IE 数据传输完成时产生中断

                USBA_DMA_CH_EN  使能该DMA通道

                USBA_DMA_END_BUF_IE  data 全部传输完成会产生中断

                USBA_DMA_END_BUF_EN

if (ep_status)

EP STA    USBA_RX_SETUP 端点收到控制传输的SETUP包

         USBA_TX_PK_RDY 向host发送的数据包准备完成.

EP CTL  USBA_TX_PK_RDY   向host发送的数据包准备完成时, 将发出中断

EP CLR_STA  USBA_RX_SETUP  清除 UDPHS_EPTSTAx 寄存器中的 RX_SETUP 标志位

EP CTL_ENB  USBA_TX_PK_RDY 使能tx包准备ok,和tx传输错误中断

            USBA_EPT_ENABLE 根据设备描述符 使能该端点

            USBA_RX_SETUP 使能 RX_SETUP 中断

         

EP SET_STA   USBA_TX_PK_RDY 表示ep->fifo已经写入数据, 可以进行数据传输

            USBA_TX_COMPLETE 使能 IN data 传输完成中断

EP CTL_DIS USBA_TX_PK_RDY  关闭tx包准备OK, 和tx传输错误中断

            USBA_TX_COMPLETE 关闭 IN data 传输完成 中断

EP CFG   USBA_EPT_TYPE_BULK 端点类型:   批量端点

         USBA_BK_NUMBER_DOUBLE

         USBA_EPT_DIR_IN  方向为向host发送数据

       USBA_EPT_TYPE_CONTROL 控制端点

USBA_BK_NUMBER_ONE

if (status & USBA_END_OF_RESET)

INT_ENB  USBA_DET_SUSPEND:  使能 suspend 中断

         USBA_END_OF_RESUME: 使能 END of resume 中断. resume完成后引发中断

         EPT_0:  使能 ep0 中断

  1. 3 usb gadget 驱动编写注意事项

    1. 3.1 Complete函数必须有效 

struct usb_request *req.

    req->complete 该函数指针必须被设置, gadget框架在调用它时并没有检查其有效性.

3.2 usb_ep_queue() 不能对一个端点连续多次使用. 

由于该函数有将 struct usb_request *req->buf 的数据写入当前端点的fifo寄存器或dma buf地址寄存器的操作,

    因此多次调用该函数后, 最终仅仅是最后一次的调用有效. 每个端点的fifo寄存器或dma buf地址寄存器是独立的,各自调用usb_ep_queue()并不影响.

3.3 usb_eq_queue()正确调用方法

   req = alloc_ep_req(xxx) 分配req.

   req->complete = out_complete; 必须设置complete()函数

   usb_ep_queue(ep, req);    将req加入该ep的队列内.

   当req->buf的数据发送给host,或从host发来数据,产生中断, 调用req->complete().

   req->complete()

   那么在comlete()函数内, 可以再次分配一个req,或继续使用当前req, 执行usb_ep_queue()将req加入队列.

   设置req->complete()指向不同的函数, 以匹配不同的传输流程.

4 usb_ep_queue()函数相关代码的执行流程图

5 usb gadget驱动与 gadget框架间的关系

5.1 设备描述符的设置

gadget驱动提供的设备描述符,仅仅是包含pid和vid等少量信息.

bMaxPacketSize0 为gadget框架根据该usb device控制器的ep0的最大包大小进行设置;

bNumConfigurations 为gadget框架根据配置描述符数组中已加入的描述符数量进行设置;

5.2 gadget驱动提供的struct usb_function.set_alt()函数, 是在gadget框架收到usb host发来请求设置配置描述符后,

根据所请求的描述符index,从配置描述符数组中找到下标为index的配置描述符, 对配置描述符之下的接口描述符一一进行初始化.一般而言, 一个接口描述符对应着一个function, 对应着struct usb_function 结构体.

5.3 ep0 端点由gadget框架接管, 未加入ep_list中, 而分配给gadget驱动的ep都从ep_list中获取. 既然ep0 由gadget框架使用, 因此枚举过程全程由gadget框架负责, 将gadet驱动提供的描述符发送给usb host.

  1. usb gadget驱动和usb host驱动例程

    1. 使用方法

解压压缩包, 修改文件夹内的Makefile的内核路径以及编译器路径.

在gadget目录和host目录执行make生成ko文件. 复制到开发板

开发板的usb host与usb device接口相连, 加载ko文件.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值