intel的UHCI
一种usb主机控制器的接口规范,遵守它的硬件称为UHCI主机控制器,Linux中,把这种硬件叫做HC,host controller,与之对应的软件,叫做HCD,hc driver,
depends on usb & pci:
它的内核软件模块代码是uhci-hcd.c
uhci_hcd_init初始化开始:
usb_disable函数:在启动参数cmdline,加入nousb参数可以禁止usb模块
kmalloc和kmem_cache_creat:slab分配器的接口函数,用于创建一个cache,之后可以使用kmem_cache_zalloc申请内存了
debugfs_creat_dir:虚拟的一个文件系统,挂载在/sys/kernel/debug下面
pci_register_driver:注册一个pci驱动
usb_hcd_pci_probe:pci驱动的开始
hcd的代码,一部分在core下,hcd-pci.c、hcd.c,公共的代码,,另一部分在host下面,具体的控制器代码,如uhci,xhci等

pci_enable_device:pci设备使用之前必须调用的,激活pci设备上的IO资源和内存资源
图中寄存器有必须也有非必须的,class就是必选的,usb的class code就是0x0C03
usb_creat_hcd:正式进入hcd,主要就是为了给struct usb_hcd申请内存空间并且初始化
struct hc_driver结构体描述一个主机控制器设备
struct usb_hcd结构体描述一个主机控制器驱动
一个主机控制器意味着一条总线,所以还有一个结构体:struct usb_bus
IO内存和IO端口
有处理器,内存和IO是独立编址独立寻址的,所以有了内存空间和IO空间,访问内存和IO空间要使用不同的指令,IO端口的物理地址被映射到IO地址空间,IO映射
嵌入式处理器中,只有一个地址空间,内存空间,那IO端口的物理地址就要被映射到内存地址空间中,内存映射
Linux中,IO Memory和IO ports都被视作一种资源,分别被记录在/proc/iomem和/proc/ioports中
要使用IO内存首先要申请(request_mem_region),然后再映射(ioremap或者ioremap_nocache)
usb_add_hcd:
1、hcd_buffer_creat:初始化一个buffer池,
DMA:直接内存访问,cpu直接和设备数据传输
usb主机控制器控制着一条usb总线,总线一项工作就是在内存和usb总线之间传输数据,这个过程不适用DMA就是PIO方式,使用DMA就似乎HCD这边先创建一个内存池,然后设备驱动可以直接使用
在hcd_buffer_creat中调用dma_pool_creat函数,去创建DMA池,结构体是struct dma_pool
2、使用dma_pool_alloc和dma_pool_free去从dma池中使用和释放,usb中对应usb_buffer_alloc和usb_buffer_free
控制器是否支持DMA操作,看dma_mask的值不为NULL
3、usb_register_bus: bus_map,一共有64位,可以有64条usb总线
class_device_create,/sys/class下创建了usb_host类
usb_notify_add_bus:牵涉内核的notify机制,常用的一种事件回调处理机制
主机控制器初始化
usb_alloc_dev:为root_hub申请一个struct usb_device结构体并初始化,root_hub的parent指针指向了controller本身
device_init_wakeup:第二个参数是1意味着把root_hub的wakeup能力打开,0可以关闭
uhci_init:struct uhci_hcd
hcd_to_uhci:uhci_hcd和usb_hcd之间的转换,
然后决定root_hub有几个端口了,端口号从0开始,UHCI的root_hub最多不能超过8个端口,最少有2个端口
继续回到usb_add_hcd中:
hcd->irq记录中断号,使用request_irq请求中断,中断句柄:第一个参数就是中断号,irqnum一路传下来的,就是最初的dev->irq,第二个参数就是中断句柄,usb_hcd_irq;第三个参数是irqflags,这里是IRQF_SHARED,表示这中断可以被多个device共享;第四个参数字符串;第五个参数用来表示使用中断的设备,一个指针,区分不同的设备,可以设为NULL;
usb_hcd_irq很重要,主机控制器需要中断的时候被调用
driver->start:uhci_start
usb_hcd_pci_probe ——》usb_add_hcd ——》uhci_start
TD:usb总线上传输的数据包,Transaction Descriptor
QH:队列头,Queue Head
SKEL:框架
toggle:反转位
对主机控制器,四种传输方式有优先级区别:等时传输 > 中断传输 > 控制传输 > Bulk传输; 等时和中断都是周期传输
start_rh:rh表示root_hub
最后设置uhci->rh_state为UHCI_RH_RUNNING,并设置uhci_tohcd(uhci)->poll_rk为1,到这里,uhci_start可以返回了,又回到usb_add_hcd中
root_hub的注册
回到usb_add_hcd之后,得到rhdev的bus_mA,默认值是0,表示没有限制,hub中给它每个端口设置最多500mA
Frame:帧
register_root_hub:
root_hub的devnum设置为1,因为hub设备树的起源就是root_hub,
之后记录bus的devnum_next,从2开始
初始化bus的devmap,并且把root hub的那把交椅先给占了
usb_set_device_steta()函数,设置hub状态,比如USB_STATE_ADDRESS
设置root hub的wMaxPacketSize为64,然后获取root hub的设备描述符
usb_new_device,将root hub添加到usb设备链表中,这里真正注册了root hub
如果正在注册中把主机控制器驱动卸载了,就去执行usb_hc_died,它里面会调用usb_kick_khubd,然后出发hub_events,因为这时hub处于NOTARRACHED状态,会调用hub_pre_reset处理后事,
usb_hcd_poll_rk_status:
天生为了中断传输
uhci_hub_status_data——》uhci_scan_schedule——》uhci_clear_next_interrupt——》uhci_get_current_frame_number
uhci_check_port:遍历root hub的端口,读取对应的端口寄存器
get_hub_status_data:
usb_hcd_poll_rk_status遍历端口,读取每个端口的寄存器,如果有变化,就把信息存在buf中,函数中有个buffer数组,它被一次次的传递下来。buf一共32个bits,这里凡是一个端口的寄存器里面有东西(处理状态改变位以外),就在buf的设置相应的位为1
然后判断rk_status了,前面在start_rh中设置了为RUNNING,所以这里执行any_ports_active
再去读取端口寄存器,其中CCS表示端口连接有变化
在start_rh中有个mod_timer,在usb_creat_hcd那里就初始化了,绑定了函数rh_timer_func,它还是调用了usb_hcd_poll_rh_status,所以它被多次调用,延时250ms,轮询
ROOT_HUB控制传输,usb_submit_urb
register_root_hub()——》usb_get_device_descriptor()——》usb_get_descriptor()——》usb_control_msg()——》usb_internal_control_msg()——》usb_start_wait_urb()——》usb_submit_urb()——》usb_hcd_submit_urb()
每个endpoint都维护着一个urb_list,所有与它相关的urb都放到这个队列中;
rh_urb_enqueue会被执行,对于非root_hub的,driver->urb_enqueue会被执行
rh_urb_enqueue:控制传输的话,调用rh_call_control
对于控制传输,首先获得它的setup_packet,来自urb结构体,然后把它给cmd指针,然后把其中的各个成员都取出来,分别给临时变量:typeReq、wValue、wIndex、wLength
然后判断typeReq,根据设备请求方向IN、OUT或者端点请求方向IN、OUT,做各种操作,给len赋值,都是usb spec中规定的
如果是hub特定的类请求,而且是GetHubStatus或者GetPortStatus,则设置len为4
如果是hub特定的类请求,而且是GetHubDescriptor,则设置len为usb_hub_descroptor结构体大小
最后对于hub特定类请求,要调用主机控制器驱动程序的hub_control函数,对于uhci就是uhci_hub_control
uhci_hcd结构体有个成员,dead,它如果是1就表明控制器挂了
如果请求是GetPortStatus,调用uhci_check_ports,然后读端口寄存器,USBPORTSC_SCS表示端口连接有变化,USBPORTSC_PEC表示port enable有变化,USBPORTSC_OCC表示over current有变化,
以上做的一切都是为了获得,wPortStatus和wPortChange,以此来响应GetPortStatus这个请求
回到rh_call_control,switch结束了,然后调用usb_hcd_givback_urb函数:
里面调用了urb的complete函数,然后rh_call_control函数也返回了,complete函数也是让回到了usb_start_wait_urb去,而控制传输需要的数据也已经copy到了urb->transfer_buffer中去了;控制传输完成了,usb_get_device_descriptor函数完成
非root hub的控制传输,
usb_submit_urb()——》usb_hcd_submit_urb()——》uhci_urb_enqueue()——》uhci_submit_control()——》uhci_add_td_to_urbp()
新增了一个结构体 struct urb_priv,
在uhci_hcd_init中使用kmem_cache_creat创建了一个cache,并给了uhci_ip_cachep,现在要使用内存了,用kmem_cache_zalloc函数去取
控制传输有三个阶段,分别是Setup阶段,数据阶段(Data),状态阶段(Status); data阶段非必须的,
setup阶段对应一个TD,数据节点有一个或者N个TD,状态阶段对应一个TD,所以控制传输就是建立好几个TD连接起来
uhci_add_td_to_urbp,每个urb都有一个队列,所有它的TD都被链到这td_list中,咱们调用usb_submit_urb提交了一个urb,但这个urb可以包含很多个TD,并且要链入一个队列,然后用uhci_fill_td来填充整个TD,
最终,uhci_submit_control函数返回到uhci_urb_enqueue中,把整个urb链接到qh队列中,qh的队列是专门组建urb队列的,一个Normal qh可以带多个urb,一个urb又可以带多个td
fsbr:带宽回收的一个特性,如果各个qh都被执行了一遍之后带宽还有剩,就回收利用
uhci_urbp_wants_fsbr结束之后,uhci_urb_enqueue也就结束了,usb_submit_urb结束,控制传输要传送的数据,也通过urb的transfer_buffer传送了,urb的complete函数仍然会被调用
非root hub的Bulk传输
root_hub没有bulk传输,所以看非root_hub的bulk传输
usb_submit_urb()——》usb_hcd_submit_urb()——》start_rh()——》uhci_submit_bulk()——》uhci_submit_common()
在start_rh中设置了hcd->state状态为RUNNING,所以这里list_add_tail会被执行,这个urb会被加入到ep的urb_list队列中去
然后driver->urb_enqueue会被执行,再次进入了uhci_urb_enqueue,然后uhci_alloc_urb_priv和uhci_alloc_qh会再次被执行以申请urbp和qh,但这次不会调用uhci_submit_control,而调用的是uhci_submit_bulk()函数,
uhci_submit_bulk:除了设置qh->skel为SKEL_BULK以外,就是调用uhci_submit_common,这个函数在后面中断传输中也调用
因为bulk传输和中断传输一样,就是一个节点,直接传递数据就可以了,然后成功返回再调用uhci_add_fsbr把urbp->fsbr设置为1
uhci_submit_common:
maxsze等于端点描述符里记录的wMaxPacketSize,即包的最大size,传输的长度小于maxsze,说明这个包是最后一个包了,
两个队列,td->liist,td->link;一个虚拟地址,一个物理地址的;因为usb主机控制器不认识虚拟地址,但从软件角度,要保证CPU能够访问各个TD,又必须要虚拟地址,从而使得cpu可以对uhci_td数据结构进行常规的队列操作,uhci_submit_common函数结束后,各个TD就组成了一个QH
控制器基本职责就是取TD和执行TD,这里TD建立好了,也连接好了,剩下的具体执行就是硬件的事情
TD队列过程:uhci_alloc_td、uhci_add_td_to_urbp、uhci_fill_td,申请td,加入大部队,填充
结束uhci_submit_common之后回到uhci_submit_bulk,uhci_urb_enqueue,最后urb的complete函数被执行,这个complete函数在root_hub时在hcd_giveback_urb中被调用,非root_hub的,为控制传输和Bulk传输的最后一个TD设置了IOC,于是该TD完成之后的那个Frame,主机控制器会向CPU发送中断,于是中断函数会被调用
中断服务程序(ISR)
在usb_add_hcd中使用了request_irq注册了中断函数,现在开始调用了usb_hcd_irq
USBSTS就是UHCI的状态寄存器,USBSTS_USBINT标志状态寄存器的bit0,bit0对应于IOC,USBSTS_ERROR对应于bit1,USBSTS_RD对应于bit2,RD就是resume detect,主机控制器在接收到resume信号会把它置为1,
usb_hcd_irq——》uhci_scan_schedule——》uhci_advance_check——》uhci_result_common——》uhci_fixup_short_transfer——》usb_hcd_giveback_urb——》uhci_make_qh_idle
root hub的中断传输
中断传输和等时传输都是周期性的
usb_submit_urb开始,在hub驱动的probe中,在hub探测过程中,会提交一个urb,是一个中断urb,通过usb_submit_urb提交
PIPE_ISOCHRONOUS:等时管道
PIPE_INTERRUPT:中断管道
usb_submit_urb——》usb_hcd_submit_urb——》rh_urb_enqueue——》rh_queue_status——》rh_urb_enqueue——》usb_hcd_submit_urb——》usb_submit_urb
如果root hub端口没什么改变的话,usb_submit_urb为root_hub而提交的中断urb不干什么事,
非root hub的中断传输
usb_submit_urb——》usb_hcd_submit_urb——》driver->urb_enqueue——》uhci_urb_enqueue——》uhci_submit_interrupt
带宽:单位时间内传输的数据量,就是单位时间内最大可能提供多少个二进制传输,等时传输和中断传输,需要分配带宽
根据interval确定最终的period,最终设定的周期period都是2的整数次方,只要period小于等于interval就行
bmAttributes:这属性的bit1和bit0表征了端点的传输类型,00为控制,01为等时,10为Bulk,11为中断
在提交中断或者等时的urb时,需要检查带宽,uhci_check_bandwidth
等时传输
usb_submit_urb——》usb_hcd_submit_urb——》uhci_urb_enqueue——》uhci_submit_isochronous
usb_submit_urb开始,判断PIPE_ISOCHERONOUS就是判断是不是等时传输,urb的成员number_of_packets用来指定处理的等时传输缓冲区的数量,即这个等时传输要传多少个packet,每一个packet都用usb_iso_packet_descriptor结构体描述,对于每一个packet都要建立一个TD
urb还有一个成员iso_frame_desc[0],零长数组,用来urb定义多个等时传输,这个数组实际长度就是前面的number_of_packet大小,是在usb_alloc_urb()申请urb时就确定了
uhci_submit_isochronous,URB_ISO_ASAP这个flag时专门给等时传输用的,告诉驱动,只要带宽允许,就从此点开始设置这个urb的start_frame变量,通常为了尽快得到图像数据,要在urb种指定这个flag,因为它会尽可能快的发出本urb
回到uhci_urb_enqueue,下一步执行uhci_activate_qh,在这里调用link_iso,加入到skel_iso_qh队列中
EHCI
EHCI仅仅支持高速传输,所以它还必须要一个协助控制器HC,入UHCI来支持低俗和全速设备
FS和LS设备插入到root hub port,会由companion HC(uhci、ohci)发现并管理设备
FS和LS设备插入到usb2.0 hub(非root hub),由ehci通过split transaction(分离事务)和transaction transaction(tt)支持设备
EHCI接口体系
EHCI是一个PCI设备

1、pci configuration space:由于EHCI是一个PCI设备,这里用于系统组件枚举和PCI的电源管理
读取PCI的套路:假如读取PCI总线上地址为add,长度为4字节的内容:
outl(add, 0xcf8); \先把add的out到地址为0xcf8的地方
value = inl(0xcfc); \然后再读取0xcfc的内容
每个PCI外设有一个总线号,一个设备号,一个功能号标识号:00:1a:7;00总线号,1a设备号,7功能号
2、register space:这是基于内的IO寄存器,就是IO内存
可以读/proc/iomem看IO内存的分配情况,这内存不可以直接读写,先要调用ioremap或者ioremap_nocache映射成虚拟地址后再使用;因为软件读写要用虚拟地址(地址不够)
3、schedule interface space:普通的内存,可以直接访问
套路
加载ehci-hcd驱动之后,也符合sysfs那里说的设备模型,两个重要链表挂在bus上,一个是设备device链表,一个是驱动driver链表
每当向一个bus注册驱动时的套路:
driver_register()——》bus_add_driver()——》driver_attach()——》bus_for_each_dev()——》device_attach()——》driver_probe_device()
bus_for_each_dev遍历该总线上所有的device,执行一次device_attach(),看能不能将设备关联(attach)到某个已登记的驱动上去
总结:注册一个某bus的驱动,就是先把驱动自己链入到bus驱动链表中去,再从bus的设备链表中寻找,有没有自己可以关联上的设备;找到probe,再把二者bing起来;反之添加设备也是一样的
module_init()——》pci_register_driver()——》driver_register()
判断一个设备和驱动是否匹配,是看设备的描述符是否和驱动所支持的一样:
pci_match_device()——》driver->dynid,driver->id_table(由一系列的pci_device_id构成)——》找到返回pci_device_id——》pce_device_id->driver_data指向了每个pci设备驱动所特有的数据结构

2325

被折叠的 条评论
为什么被折叠?



