
1.简介
整个通用串行总线系统的通讯模型如上图所示,本文详细解析其中设备各模块的架构和原理(图中彩色部分)。
2.平台层
最底层是通用串行总线设备控制器。
2.1平台设备
通常情况下,在DTS中定义一个城市发展管理机构平台设备:
usbd:USB @ 10200000 { compatible=' SNPs,dw C2 'reg=;中断=;时钟=,clock-names=' otg '重置=,reset-names='dwc2 '' dw C2-ECC 'g-rx-FIFO-size=;g-NP-tx-FIFO-size=;g-tx-FIFO-size=;dr_mode='外设;状态='好的;};
2.2平台驱动程序
对应的城市发展管理机构平台驱动程序:
driversusbdwc2platform.c:静态结构C2平台驱动={ .驱动程序={。name=dwc2_driver_name,匹配表的C2,匹配表的。pm=dwc2_dev_pm_ops,},probe=dwc2_driver_probe,remove=dwc2_driver_remove,shutdown=dwc2_driver_shutdown,};设备标识的构造结构dwc2_of_match_table[]={.{ .compatible='snps,dwc2 'data=dwc2_set_usb_params },{},};
该驱动的主要功能是创建和注册小工具设备,一个城市发展管理机构对应一个小工具设备:
dw C2 _驱动_探测()USB _添加_小工具_ UDC()USB _添加_小工具_ UDC _释放()USB _添加_小工具():int USB _添加_小工具(struct USB _小工具*小工具){ struct USB _ UDC * UDCint ret=-eno mem;udc=kzalloc(sizeof(*udc),GFP _ KERNEL);如果(!udc)转到错误;device _ initialize(UDC-dev);UDC-dev。发布=USB _ UDC _ releaseUDC-dev。class=UDC _ classUDC-dev。groups=USB _ UDC _ attr _ groupsUDC-dev。parent=gadget-dev父母;ret=dev_set_name(udc-dev,' %s 'ko object _ name(gadget-dev。parent-kobj));if(ret)goto err _ put _ UDC;ret=device _ add(gadget-dev);if(ret)goto err _ put _ UDC;UDC-gadget=gadget;gadget-UDC=UDC;互斥锁(UDC _ lock);list_add_tail(udc-list,UDC _ list);ret=device _ add(UDC-dev);if(ret)goto err _ un list _ UDC;usb_gadget_set_state(小工具,USB _ STATE _未附加);UDC-vbus=true;ret=check _ pending _ gadget _ drivers(UDC);if(ret)goto err _ del _ UDC;互斥_解锁(UDC _ lock);}
3.UDC/小工具层
小工具层层把各式各样的城市发展管理机构封装成标准的小工具设备,提供统一的向上接口小工具驱动程序又把各式各样的功能和小工具设备链接起来。
3.1小工具总线
小工具层层没有定义一个标准的公共汽车总线,而是自定义了两条链表来分别存储设备和驱动程序:
它们的使用场景如下:
1、在小工具设备创建时,首先把设备加入到udc_list链表,然后尝试和小工具_驱动程序_待定_列表链表中的驾驶员进行match():
USB _ add _ gadget _ UDC()USB _ add _ gadget _ UDC _ release()USB _ add _ gadget():int USB _ add _ gadget(struct USB _ gadget * gadget){list_add_tail(udc-list,UDC _ list);ret=check _ pending _ gadget _ drivers(UDC);if(ret)goto err _ del _ UDC;互斥_解锁(UDC _ lock);}static int check _ pending _ gadget _ drivers(struct USB _ UDC * UDC){ struct USB _ gadget _ driver * driver;int ret=0;list_for_each_entry(driver,gadget_driver_pending_list,pending) if(!driver-UDC _ name | | strcmp(driver-UDC _ name,dev _ name(UDC-dev))==0){ret=UDC _ bind _ to _ driver(UDC,driver);如果(ret!=-EPROBE_DEFER) list_del_init(驱动程序-待定);打破;浸水使柔软返回;}
2、在小工具驱动程序创建时,首先尝试和udc_list链表中的设备进行match(),match()不成功则把驾驶员加入到小工具_驱动程序_待定_列表链表中:
gadget _ dev _ desc _ UDC _ store()USB _ gadget _ probe _ driver():int USB _ gadget _ probe _ driver(struct USB _ gadget _ driver * driver){ struct USB _ UDC * UDC=NULL;int ret=-eno dev;如果(!驱动||!驱动程序绑定||!driver-setup)return-EINVAL;互斥锁(UDC _ lock);if(driver-UDC _ name){ list _ for _ each _ entry(UDC,udc_list,list){ ret=strcmp(driver-UDC _ name,dev _ name(UDC-dev));如果(!ret)break;} if(ret)ret=-ENODEV;else if(UDC-driver)ret=-EBUSY;其他找到goto} else { list _ For _ each _ entry(UDC,udc_list,list) { if(!找到了UDC-driver)goto;} }如果(!driver-match _ existing _ only){list_add_tail(驱动程序待定,gadget _ driver _ pending _ list);pr_info('udc-core:找不到可用的UDC -已将[%s]添加到待定驱动程序列表中,驱动程序功能);ret=0;}互斥锁_解锁(UDC _ lock);if (ret) pr_warn('udc-core:找不到可用的通用十进制分类法或它正忙');返回浸水使柔软找到:ret=UDC _ bind _ to _ driver(UDC,driver);互斥_解锁(UDC _ lock);返回ret}
3、在设备和驱动程序匹配成功时的绑定()动作:
static int UDC _ bind _ to _ driver(struct USB _ UDC * UDC,struct USB _ gadget _ driver * driver){ int ret;dev_dbg(udc-dev,'注册通用十进制分类法驱动程序[%s]'驱动程序函数);UDC-driver=driver;UDC-dev。司机=司机-司机;UDC-小工具-开发。司机=司机-司机;usb_gadget_udc_set_speed(udc,driver-max _ speed);ret=driver-bind(udc-gadget,driver);if(ret)goto err 1;ret=USB _ gadget _ UDC _ start(UDC);if(ret){ driver-unbind(UDC-gadget);goto err1} USB _ UDC _ connect _ control(UDC);ko object _ u event(UDC-dev。KOBJ,KOBJ _ CHANGE);返回0;}
注意:这里和一般的设备和驾驶员的适配规则有些不一样。一般的规则是一个迪弗可以适配多个设备,而一个设备只能适配一个司机。而这里的规则是一个小工具设备只能适配一个小工具驱动程序,而一个小工具驱动程序只能适配一个小工具设备。小工具设备代表的是一个UDC,而小工具驱动程序代表的是一个复合设备。
3.2小工具设备
上一节说过小工具设备由城市发展管理机构驱动程序创建。
dw C2 _驱动_探测()USB _添加_小工具_ UDC()USB _添加_小工具_ UDC _释放()USB _添加_小工具()
小工具设备的主要作用是提供了端点资源,供功能层使用标准的小工具应用程序接口来进行访问。
端点分配
城市发展管理机构驱动程序在调用usb_add_gadget_udc()注册小工具设备之前,初始化了小玩意的端点资源链表:
dw C2 _驱动程序_探测()dw C2 _小工具_初始化():int dw C2 _小工具_初始化(结构dw C2 _ HSO TG * HSO TG){INIT _ LIST _ HEAD(HSO TG-小工具。EP _ LIST);HSO TG小工具。ep0=HSO TG-EPS _ out[0]-EP;for(EP编号=0;epnum每股收益的数量;epnum){ if(hsotg-eps_in[epnum])dw C2 _ HSO TG _ initep(HSO TG,HSO TG-EPS _ in[EP num],EP num,1);if(hsotg-EPS _ out[epnum])dw C2 _ HSO TG _ initep(HSO TG,hsotg-eps_out[epnum],EP num,0);} }静态void dw C2 _ HSO TG _ initep(struct dw C2 _ HSO TG * HSO TG,struct dwc2_hsotg_ep *hs_ep,int epnum,bool dir _ in){ INIT _ LIST _ HEAD(hs _ EP-queue);INIT _ LIST _ HEAD。EP _ LIST);if(EP num)list _ add _ tail(hs _ EP-EP。HSO TG-gadget。EP _ list);hs _ EP-parent=HSO TG;hs _ EP-EP。name=hs _ EP-name;如果(HSO TG-params。SPEED==dw C2 _ SPEED _ PARAM _ LOW)USB _ EP _ set _ max packet _ limit(hs _ EP-EP,8);else USB _ EP _ set _ max packet _ limit(hs _ EP-EP,epnum?1024:EP0 _ MPS _ LIMIT);hs _ EP-EP。ops=dw C2 _ HSO TG _ EP _ ops;if(EP num==0){ hs _ EP-EP。帽子。type _ control=true} else { if (hsotg-params.speed!=dw C2速度参数低){ hs _ EP-EP。帽子。type _ iso=truehs _ EP-EP。帽子。type _ bulk=true} hs _ EP-EP。帽子。type _ int=true} if(dir _ in)hs _ EP-EP。帽子。dir _ in=trueelse hs _ EP-EP。帽子。dir _ out=true}
小工具设备准备好了端点资源链表以后,通过usb_add_gadget_udc()注册。这样就可以功能层就可以通过调用小工具美国石油学会(American Petroleum Institute)来动态分配端点了。例如:
static int ACM _ bind(struct USB _ configuration * c,struct usb_function *f){ EP=USB _ EP _ auto config(cdev-gadget,ACM _ fs _ in _ desc);如果(!ep)转到失败;ACM-端口。in=EPEP=USB _ EP _ auto config(cdev-gadget,ACM _ fs _ out _ desc);如果(!ep)转到失败;ACM-端口。out=EPEP=USB _ EP _ auto config(cdev-gadget,ACM _ fs _ notify _ desc);如果(!ep)转到失败;ACM-notify=EP;}
其中通过usb_ep_autoconfig()函数从小工具设备的端点资源链表中分配空闲的端点:
driversusbgadgetfunctionf _ ACM。c:USB _ EP _ auto config()USB _ EP _ auto config _ ss():struct USB _ EP * USB _ EP _ auto config _ ss(struct USB _ gadget * gadget,struct USB _ endpoint _ descriptor * desc,struct USB _ ss _ EP _ comp _ descriptor * EP _ comp){ struct USB _ EP * EP;if(gadget-ops-match _ EP){ EP=gadget-ops-match _ EP(gadget,desc,EP _ comp);if(EP)goto found _ EP;} list _ for _ each _ entry(ep,gadget-ep_list,EP _ list){ if(USB _ gadget _ EP _ match _ desc(gadget,EP,desc,EP _ comp))goto found _ EP;} 返回NULLfound_ep:欧洲地址=desc-弯曲点地址;欧洲desc=空;EP-comp _ desc=NULL;EP-claimed=true;返回EP;}
终端访问
小工具设备不仅仅为小工具美国石油学会(American Petroleum Institute)提供了分配端点的支持,还支持对端点收发数据的底层支持。在上一节的端点初始化时,就已经设置端点的操作函数集dwc2_hsotg_ep_ops:
dw C2 _驱动_探测()dw C2 _小工具_初始化()dw C2 _ HSO TG _初始化():静态void dw C2 _ HSO TG _ initep(struct dw C2 _ HSO TG * HSO TG,struct dwc2_hsotg_ep *hs_ep,int epnum,bool dir_in){ hs _ EP-EP。ops=dw C2 _ HSO TG _ EP _ ops;}静态const struct USB _ EP _ ops dw C2 _ HSO TG _ EP _ ops={ .enable=dwc2_hsotg_ep_enable,disable=dw C2 _ HSO TG _ EP _ disable _ lock,C2 HSO TG _ EP _ alloc _ request .C2 HSO TG _ EP _ free _ request .queue=dwc2_hsotg_ep_queue_lock,dequeue=dwc2_hsotg_ep_dequeue,set _ halt=dw C2 _ HSO TG _ EP _ set halt _ lock,};
小工具美国石油学会(American Petroleum Institute)提供了以下接口来操作端点读写数据。在主持侧对端点进行一次操作请求的数据结构是结构城市,而在设备侧也有类似的数据结构称为结构usb _请求,对端点的数据读写就是围绕结构usb _请求展开的:
driversusbgadgetfunctionf _ ACM。c:static int ACM _ CDC _ notify(struct f _ ACM * ACM,u8 type,u16 value,void *data,unsigned length){ struct USB _ EP * EP=ACM-notify;struct USB _ req * reqstruct USB _ CDC _ notification * notify;const无符号len=sizeof(* notify)长度;void * bufint状态;req=ACM-notify _ req;ACM-notify _ req=NULL;ACM-pending=false;请求-长度=lennotify=req-buf;buf=notify 1;notify-bmRequestType=USB _ DIR _ IN | USB _ TYPE _ CLASS | USB _ RECIP _ INTERFACE;notify-bNotificationType=type;notify-wValue=CPU _ to _ le16(value);notify-wIndex=CPU _ to _ le16(ACM-ctrl _ id);notify-wLength=cpu_to_le16(长度);memcpy(buf,data,length);spin _ unlock(ACM-lock);status=usb_ep_queue(ep,req,GFP _ ATOMIC);自旋锁(美国计算机学会锁);}
其中usb_ep_queue()函数就会调用端点的操作函数集dwc2_hsotg_ep_ops中的。长队函数:
int USB _ EP _ queue(struct USB _ EP * EP,struct USB _ req * req,GFP _ t GFP _ flags){ int ret=0;if (WARN_ON_ONCE(!EP-enabled EP-address)){ ret=-ESHUTDOWN;外出;} ret=EP-ops-queue(EP,req,GFP _ flags);out: trace_usb_ep_queue(ep,req,ret);返回ret}
城市发展管理机构控制
小工具设备还提供了城市发展管理机构层级的一些操作函数,UDC驱动程序在调用usb_add_gadget_udc()注册小工具设备之前,初始化了小玩意的操作函数集:
dw C2 _驱动_探测()dw C2 _小工具_初始化():int dw C2 _小工具_初始化(结构dw C2 _ HSO TG * HSO TG){ HSO TG-小工具。max _ SPEED=USB _ SPEED _ HIGHHSO TG-gadget。ops=dw C2 _ HSO TG _ gadget _ ops;HSO TG小工具。name=dev _ name(dev);HSO TG-远程_唤醒_允许=0;}静态const struct USB _ gadget _ ops dw C2 _ HSO TG _ gadget _ ops={ .get _ frame=dw C2 _ HSO TG _ gadget _ get frame,set _ self powered=dw C2 _ HSO TG _ set _ self powered .udc_start=dwc2_hsotg_udc_start,udc_stop=dwc2_hsotg_udc_stop,pullup=dwc2_hsotg_pullup,0 .vbus _ session=dw C2 _ HSO TG _ vbus _ session .vbus_draw=dwc2_hsotg_vbus_draw,};
小工具美国石油学会(American Petroleum Institute)提供了一些内部函数来调用:
static inline int USB _ gadget _ UDC _ start(struct USB _ UDC * UDC){ return UDC-gadget-ops-UDC _ start(UDC-gadget,UDC-driver);}静态内联void USB _ gadget _ UDC _ stop(struct USB _ UDC * UDC){ UDC-gadget-ops-UDC _ stop(UDC-gadget);}静态内联void USB _ gadget _ UDC _ set _ speed(struct USB _ UDC * UDC,enum USB _ device _ speed speed){ if(UDC-gadget-ops-UDC _ set _ speed){ enum USB _ device _ speed s;s=最小值(速度,UDC-gadget-max _ speed);UDC-gadget-ops-UDC _ set _ speed(UDC-gadget,s);} } int USB _ gadget _ connect(struct USB _ gadget * gadget){ int ret=0;如果(!gadget-ops-pullup){ ret=-EOPNOTSUPP;外出;} if(小工具已停用){ /* *如果小玩意被停用,我们只保存新状态。*小工具将在激活后自动连接。*/小工具-连接=真;外出;} ret=gadget-ops-pullup(gadget,1);如果(!ret)小工具连接=1;out:trace _ USB _ gadget _ connect(gadget,ret);返回ret } int USB _ gadget _ disconnect(struct USB _ gadget * gadget){ int ret=0;如果(!gadget-ops-pullup){ ret=-EOPNOTSUPP;外出;}如果(!小工具连接)外出;if(小工具已停用){ /* *如果小玩意被停用,我们只保存新状态。*小工具在激活后将保持断开连接。*/小工具-已连接=假外出;} ret=gadget-ops-pullup(gadget,0);如果(!ret){ gadget-connected=0;小工具-UDC-驱动程序-断开连接(小工具);} out:trace _ USB _ gadget _ disconnect(gadget,ret);返回ret}
3.3小工具驱动程序(配置文件系统)
小工具设备支撑了核心小工具美国石油学会(American Petroleum Institute)的实现,而功能层又需要使用这些Api。怎么样将两者适配起来?小工具驱动程序就是用来完成这项工作的。
目前存在两种风格的小工具驱动程序,其中包括:
遗产。这是早期风格的小工具驱动程序,只能通过静态编译的方式指定使用哪些功能。
配置文件系统。这是目前流行的小工具驱动程序,可以通过配置文件系统文件系统,不用重新编译内核,动态的配置需要使用的功能。
我们首先介绍配置文件系统风格的小工具驱动程序。
配置文件系统使用
首先从使用上体验一下配置文件系统的便捷。例如创建一个美国计算机协会(Association for Computing Machinery)功能:
//1、挂载配置文件系统文件系统mount-t configfs none/sys/kernel/config CD/sys/kernel/config/USB _ gadget//2、创建g1目录,实例化一个新的小玩意模板(复合设备).mkdir g1cd g1//3 .1、定义通用串行总线产品的文章和PID。echo '0x1d6b ' idVendorecho '0x 0104 ' id产品//3 .2、实例化英语语言身份证。(0x409是通用串行总线语言身份证明美国英语,不是任意的,可以在USBIF网站上下载文档查询。mkdir strings/0x 409 ls strings/0x 409///3 .3、将开发商、产品和序列号字符串写入内核echo ' 0123456789 '字符串/0x 409/序列号echo ' AAAA公司strings/0x 409/manufacturer echo ' Bar Gadget ' strings/0x 409/product//4、创建`功能'功能实例,需要注意的是,一个功能如果有多个实例的话,扩展名必须用数字编号mkdir函数/acm .GS0//5 .1、创建一个USB `接口配置'配置实例:mkdir配置/c.1ls配置/c.1//5 .2、定义配置描述符使用的字符串mkdir configs/c . 1/strings/0x 409 ls configs/c . 1/strings/0x 409/echo ' ACM ' configs/c . 1/strings/0x 409/configuration//6、捆绑功能`功能'实例到`配置'配置c1ln-s函数/acm .GS0配置/c.1//7 .1、查找本机可获得的城市发展管理机构实例(即小工具设备)# ls/sys/class/UDC/10200000。USB//7 .2、将小玩意驱动注册到城市发展管理机构上,插上通用串行总线线到电脑上,电脑就会枚举通用串行总线设备回声10200000。USB UDC
配置文件系统层次结构
配置文件系统并不是小玩意专用的,它是一个通用文件系统,方便用户通过文件系统创建文件夹、文件的方式来创建内核对象。
配置文件系统是很好理解的,结构配置_组相当于一个文件夹,结构配置项类型是这个文件夹的属性集。其中config _ item _ type-CT _ group _ ops-make _ group()/drop _ item()定义了创建/销毁下一层子文件夹的方法,配置项目类型联系类型属性定义了子文件和相关操作函数。
我们通过解析driversusbgadgetconfigfs.c文件来深入理解配置文件系统的使用方法:
1、首先创建首层文件夹/sys/kernel/config/usb_gadget:
静态结构configfs _ group _ operations gadgets _ ops={ .make_group=gadgets_make,drop_item=gadgets_drop,};静态构造结构config_item_type小工具类型={ .ct_group_ops=gadgets_ops,ct_owner=THIS_MODULE,};静态结构configfs _ subsystem gadget _ subsys={ .su_group={ .cg_item={ .ci_namebuf='usb_gadget 'ci_type=gadgets_type,},},su _ MUTEX=_ _ MUTEX _ INITIALIZER(gadget _ subsys。su _ MUTEX),};static int _ _ init gadget _ CFS _ init(void){ int ret;配置组初始化(小工具子系统。su _ group);ret=configfs _ register _ subsystem(gadget _ subsys);返回ret }模块初始化(小工具_ CFS _ init);
2、创建/sys/kernel/config/USB _ gadget/G1相当于创建一个全新的复合设备。会调用顶层结构配置_组的config _ item _ type-CT _ group _ ops-make _ group()函数,即gadgets_make():
static struct config _ group * gadgets _ make(struct config _ group * group,const char * name){ struct gadget _ info * gi;gi=kzalloc(sizeof(*gi),GFP _ KERNEL);如果(!gi)返回ERR _ PTR(-eno mem);/* (1) 创建顶层文件夹`/sys/kernel/config/USB _ gadget/G1 '对应的`结构配置_组'结构`/sys/kernel/config/USB _ gadget/G1 '下对应不少子文件,在小工具_根_类型。CT _属性中定义,即` gadget _ root _ attrs `:static struct configfs _ attribute * gadget _ root _ attrs[]={ gadget_dev _ desc _ attr _ bDeviceClass,gadget _ dev _ desc _ attr _ bDeviceSubClass,gadget _ dev _ desc _ attr _ bDeviceProtocol,gadget _ dev _ desc _ attr _ bmax packet size 0,gadget_dev_desc_attr_idVendor,gadget_dev_desc_attr_idProduct,gadget_dev_desc_attr_bcdDevice,gadget _ dev/* (2) 创建子文件夹`/sys/kernel/config/USB _ gadget/G1/f
3、创建 /sys/kernel/config/usb_gadget/g1/functions/acm.GS0。会调用 functions_type 中定义的 function_make() 函数:
在 ln -s functions/acm.GS0 configs/c.1 时给 function 实例安装实际的函数:
3.3.3 gadget driver
Configfs 风格的 gadget driver 的定义:
在调用 echo"/sys/class/udc/10200000.usb"> /sys/kernel/config/usb_gadget/g1/UDC 时,将上述 gadget driver 进行注册,和 UDC 已经注册好的 gadget device 进行动态适配。
本质上是 使用 configfs 创建好的 composite device 和 gadget device 进行绑定:
但是 bind() 以后 function 实例只是分配了 endpoint 资源还没有被启动,因为 Device 是被动状态,只有连上 Host,被 Host Set Configuration 操作以后。某一组 Configuration 被配置,相应的 Function 实例 才会被启用:
3.4 Gadget Driver (Legacy)
对于 Legacy Gadget Driver 驱动来说,相当于 Configfs Gadget Driver 的一个简化版。
3.4.1 gadget driver
Legacy 风格的 gadget driver 的定义:
驱动提供了一个注册函数 usb_composite_probe(),以供 composite device 来进行调用:
3.4.2 composite device
没有了 configfs 由用户来创建 composite device,只能使用一个文件来创建 composite device 定义其使用哪些 function 和一系列配置。例如:
在 gadget driver 驱动适配后,调用 bind() 函数:
在 acm_ms_bind() 函数中创建 composite device 的 Configuration 和 Function/Interface,并且和 Gadget Device / UDC 进行绑定。
其他操作和 Configfs Gadget Driver 类似。
在 drivers/usb/gadget/function/ 路径下有一批 Gadget Function 的定义:
大家使用 DECLARE_USB_FUNCTION_INIT() 宏定义来调用 usb_function_register() 函数,把 usb_function_driver 注册到全局链表 func_list 中。等待 composite device 来进行实例化。
在 Function Layer 主要使用以下 Gadget Layer 层提供的 API:
5. UDC Hardware
UDC 全称 Usb Device Controller,是设备作为 Usb Device 时最底层的控制器。在硬件层面实现了以下功能:
UDC 实现的一项主要工作是数据搬移:
UDC 发送时,数据先从内存 Memory 搬移到 UDC 的内部 FIFO 当中,然后由 UDC 发送到 USB 物理线路上。
UDC 接收时,数据先从 USB 物理线路接收到 UDC 的内部 FIFO 当中,然后再从 FIFO 拷贝到 内存 Memory 当中。
对于 FIFO 和 Memory 之间的数据搬移工作,支持两种方式:
1、DMA Mode。
由 UDC 内部的 DMA 模块来承担数据搬移工作,只要使用寄存器配置好 FIFO 的分配,以及在寄存器中配置好 DMA 的其实地址,DMA 会完成数据的搬移。
2、Slave Mode。
也可以不使用 DMA 而直接使用 CPU 来搬移,这种方式非常消耗 CPU 的带宽,CPU 被简单重复的数据拷贝拖住不能做其他的事情。这种方式一般用于 Debug 模式。
5.2 Endpoint FIFO Mode
不同的 UDC 中 Endpoint 对 FIFO 的使用有多种模式,UDC 选用的是 Shared Transmit FIFO 模式。
在 Shared Transmit FIFO 模式中,Endpoint 对 FIFO 使用模式如下:
所有的 non-periodic IN endpoints 共享一个 transmit FIFO。non-periodic endpoints 包括 isochronous transfers 和 interrupt transfers。
每一个 periodic IN endpoint 独立拥有一个 transmit FIFO。periodic endpoints 包括 bulk transfers 和 control transfers。
所有的 OUT endpoints 共享一个 receive FIFO。
5.3 Endpoint Resource
USB 协议定义一个 Device 最多可以实现 16 个 IN endpoint + 16 个 OUT endpoint。除了 endpoint 0 IN/OUT 被系统默认使用,剩下的可以被驱动动态分配使用。
DIEPCTL(0-15)
DIEPINT(0-15)
DIEPTSIZ(0-15)
DIEPDMA(0-15)
DOEPCTL(0-15)
DOEPINT(0-15)
DOEPTSIZ(0-15)
DOEPDMA(0-15)
如上一节所描述,UDC 是Shared Transmit FIFO 模式,periodic IN endpoint 需要拥有一个独立的 transmit FIFO。最多有两个这样的 transmit FIFO 资源,供驱动动态分配。
DPTXFSIZ1
DPTXFSIZ2
如果驱动创建一个 periodic IN endpoint 它分配到了第一个 endpoint 资源,但是没有分配到 transmit FIFO 资源,也会创建失败。
由上几节的描述可以看到,UDC 有多个模块需要使用内部 FIFO。包括:
(1) OUT endpoints RxFIFO。
(2) IN non-periodic endpoints TxFIFO。
(3) IN periodic endpoints TxFIFO。
(4) DMA 。
UDC 内部 FIFO 总大小是固定的,那么怎么样来分配 FIFO 空间给这些模块呢?UDC 提供了以下计算公式:
Receive FIFO RAM allocation
Transmit FIFO RAM allocation
Internal Register Storage Space Allocation
当在内部DMA模式下运行时,核心将端点DMA地址寄存器(DI/OEPDMA)存储在SPRAM中。必须为每个端点分配一个位置。
例如,如果一个端点是双向的,那么必须分配两个位置。如果端点是IN或OUT,则必须只分配一个位置。
Example
The MPS is 1,024 bytes for a periodic USB packet and 512 bytes for a non-periodic USB packet.
There are three OUT endpoints, three IN endpoints, one control endpoint.
Device RxFIFO = (5 * 1 + 8) + ((1,024 / 4) +1) + (2 * 4) + 1 = 279
Non-Periodic TxFIFO = (512 / 4) = 128
Device Periodic TxFIFO:
EP 1 = (1,024 / 4) = 256
EP 2 = (1,024 / 4) = 256
EP 3 = (1,024 / 4) = 256
5.5 FIFO Mapping
由上几节可知对一个端点 Endpoint 来说,它对应的 FIFO 是动态分配的。在 DMA 模式下,一旦初始化时配置完成就不用再去管 Endpoint FIFO 的地址。但是对 Slave 模式来说,在数据收发过程中需要 CPU 访问对应 FIFO 空间。
为了方便 CPU 对 Endpoint FIFO 的访问,UDC 把 Endpoint FIFO 映射到了固定地址。其中读操作会映射到 OUT Endpoint FIFO,写操作会映射到 IN Endpoint FIFO。
5.6 Interrupt Cascade
由于 UDC 的中断状态较多,所以分成 3 级级联:
layer
register
descript
1
GINTSTS & GINTMSK
DOEPINTn & DOEPMSK
DIEPINTn & DIEPMSK
Endpoint 中断细节,每一个 Endpoint 拥有一组这样的寄存器。
寄存器中的每一 bit 代表某个 Endpoint 的某种中断状态
5.7 Data Transfer
UDC 内部的数据收发流程如上图所示。主要的工作就是根据 USB 接收到的读写指令,把数据在 FIFO 和 Memory 之间进行搬移。具体分为几种情况:
OUT Endpoint。所有 OUT Endpoint 的线路数据会接收到一个统一的 Rx FIFO 当中,然后根据接收数据的具体 Endpoint配置的 Memory 地址和长度,DMA 把数据从 FIFO 搬移到对应 Memory 当中,最后产生中断。
IN Non-period Endpoint。所有 IN Non-period Endpoint 共享一个统一的 Tx Non-period FIFO ,根据Endpoint配置的 Memory 地址和长度,DMA 把数据从 Memory 搬移到统一的 FIFO 当中,发送到线路上后产生中断。IN Non-period Endpoint 需要配置 Next Endpoint 指针,这样 DMA处理完一个 Endpoint 的数据后才知道下一个需要处理的 Endpoint。
IN Period Endpoint。每一个 IN Period Endpoint 拥有自己独立的 FIFO,根据Endpoint配置的 Memory 地址和长度,DMA 把数据从 Memory 搬移到对应的 FIFO 当中,发送到线路上后产生中断。
1.USB 2.0 Specification
审核编辑 :李倩









