linux usb 系统 (6)
Linux中EHCI控制器驱动的架构首 先,让我们来看看linux中是如何来定义这些寄存器的。PCI系列的EHCI寄存器我们不关心,我们只关心Capability系列的和Controller系列的寄存器。Host Controller Capability Registers struct ehci_caps {
Linux中EHCI控制器驱动的架构
首 先,让我们来看看linux中是如何来定义这些寄存器的。
PCI系列的EHCI寄存器我们不关心,我们只关心Capability系列的和Controller系列的寄存器。
Host Controller Capability Registers | |
struct ehci_caps { /* these fields are specified as 8 and 16 bit registers, * but some hosts can't perform 8 or 16 bit PCI accesses. */ u32 hc_capbase; #define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ #define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ u32 hcs_params; /* HCSPARAMS - offset 0x4 */ #define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ #define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ #define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ #define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ #define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ #define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ #define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ u32 hcc_params; /* HCCPARAMS - offset 0x8 */ #define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ #define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ #define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ #define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ #define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ #define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ u8 portroute [8]; /* nibbles for routing - offset 0xC */ } __attribute__ ((packed)); |
Host Controller Operational Registers | |
struct ehci_regs { /* USBCMD: offset 0x00 */ u32 command; /* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ #define CMD_PARK (1<<11) /* enable "park" on async qh */ #define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ #define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ #define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ #define CMD_ASE (1<<5) /* async schedule enable */ #define CMD_PSE (1<<4) /* periodic schedule enable */ /* 3:2 is periodic frame list size */ #define CMD_RESET (1<<1) /* reset HC not bus */ #define CMD_RUN (1<<0) /* start/stop HC */ /* USBSTS: offset 0x04 */ u32 status; #define STS_ASS (1<<15) /* Async Schedule Status */ #define STS_PSS (1<<14) /* Periodic Schedule Status */ #define STS_RECL (1<<13) /* Reclamation */ #define STS_HALT (1<<12) /* Not running (any reason) */ /* some bits reserved */ /* these STS_* flags are also intr_enable bits (USBINTR) */ #define STS_IAA (1<<5) /* Interrupted on async advance */ #define STS_FATAL (1<<4) /* such as some PCI access errors */ #define STS_FLR (1<<3) /* frame list rolled over */ #define STS_PCD (1<<2) /* port change detect */ #define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ #define STS_INT (1<<0) /* "normal" completion (short, ...) */ /* USBINTR: offset 0x08 */ u32 intr_enable; /* FRINDEX: offset 0x0C */ u32 frame_index; /* current microframe number */ /* CTRLDSSEGMENT: offset 0x10 */ u32 segment; /* address bits 63:32 if needed */ /* PERIODICLISTBASE: offset 0x14 */ u32 frame_list; /* points to periodic list */ /* ASYNCLISTADDR: offset 0x18 */ u32 async_next; /* address of next async queue head */ u32 reserved [9]; /* CONFIGFLAG: offset 0x40 */ u32 configured_flag; #define FLAG_CF (1<<0) /* true: we'll support "high speed" */ /* PORTSC: offset 0x44 */ u32 port_status [0]; /* up to N_PORTS */ /* 31:23 reserved */ #define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ #define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ #define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ /* 19:16 for port testing */ #define PORT_LED_OFF (0<<14) #define PORT_LED_AMBER (1<<14) #define PORT_LED_GREEN (2<<14) #define PORT_LED_MASK (3<<14) #define PORT_OWNER (1<<13) /* true: companion hc owns this port */ #define PORT_POWER (1<<12) /* true: has power (see PPC) */ #define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ /* 11:10 for detecting lowspeed devices (reset vs release ownership) */ /* 9 reserved */ #define PORT_RESET (1<<8) /* reset port */ #define PORT_SUSPEND (1<<7) /* suspend port */ #define PORT_RESUME (1<<6) /* resume it */ #define PORT_OCC (1<<5) /* over current change */ #define PORT_OC (1<<4) /* over current active */ #define PORT_PEC (1<<3) /* port enable change */ #define PORT_PE (1<<2) /* port enable */ #define PORT_CSC (1<<1) /* connect status change */ #define PORT_CONNECT (1<<0) /* device connected */ #define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) } __attribute__ ((packed)); |
上面两个结构体就是linux对EHCI寄存器的定义,我们可以对照<>2.2和2.3节来详细了解各个字段的定义。即使没有spec,代码中的注释和宏也己经能让我们理解个八九成。
Linux要管理ehci,仅有寄存器的定义还不够,下面我们再来看看ehci的定义:
struct ehci_hcd { /* one per controller */ /* glue to PCI and HCD framework */ struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; struct ehci_dbg_port __iomem *debug; __u32 hcs_params; /* cached register copy */ spinlock_t lock; /* async schedule support */ struct ehci_qh *async; struct ehci_qh *reclaim; unsigned scanning : 1; /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ unsigned periodic_size; __hc32 *periodic; /* hw periodic table */ dma_addr_t periodic_dma; unsigned i_thresh; /* uframes HC might cache */ union ehci_shadow *pshadow; /* mirror hw periodic table */ int next_uframe; /* scan periodic, start here */ unsigned periodic_sched; /* periodic activity count */ /* list of itds completed while clock_frame was still active */ struct list_head cached_itd_list; unsigned clock_frame; /* per root hub port */ unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; /* bit vectors (one bit per port) */ unsigned long bus_suspended; /* which ports were already suspended at the start of a bus suspend */ unsigned long companion_ports; /* which ports are dedicated to the companion controller */ unsigned long owned_ports; /* which ports are owned by the companion during a bus suspend */ unsigned long port_c_suspend; /* which ports have the change-suspend feature turned on */ unsigned long suspended_ports; /* which ports are suspended */ /* per-HC memory pools (could be per-bus, but ...) */ struct dma_pool *qh_pool; /* qh per active urb */ struct dma_pool *qtd_pool; /* one or more per qh */ struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */ struct timer_list iaa_watchdog; struct timer_list watchdog; unsigned long actions; unsigned stamp; unsigned long next_statechange; u32 command; /* SILICON QUIRKS */ unsigned no_selective_suspend:1; unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned big_endian_mmio:1; unsigned big_endian_desc:1; unsigned has_amcc_usb23:1; /* required for usb32 quirk */ #define OHCI_CTRL_HCFS (3 << 6) #define OHCI_USB_OPER (2 << 6) #define OHCI_USB_SUSPEND (3 << 6) #define OHCI_HCCTRL_OFFSET 0x4 #define OHCI_HCCTRL_LEN 0x4 __hc32 *ohci_hcctrl_reg; u8 sbrn; /* packed release number */ /* irq statistics */ #ifdef EHCI_STATS struct ehci_stats stats; # define COUNT(x) do { (x)++; } while (0) #else # define COUNT(x) do {} while (0) #endif /* debug files */ #ifdef DEBUG struct dentry *debug_dir; struct dentry *debug_async; struct dentry *debug_periodic; struct dentry *debug_registers; #endif #ifdef CONFIG_USB_OTG /* * OTG controllers and transceivers need software interaction; * other external transceivers should be software-transparent */ struct otg_transceiver *transceiver; #endif }; |
struct ehci_hcd就像是一个大杂汇,把所有与EHCI的东西都放了进来,在内核中,一个ehci_hcd 的定义 (分配了实际的内存)就描述一个硬件上的EHCI控制器。
我们看看ehci_hcd的主要成员:
struct ehci_caps __iomem *caps; struct ehci_regs __iomem *regs; struct ehci_dbg_port __iomem *debug; __u32 hcs_params; /* cached register copy */ __hc32 *periodic; | 硬件相关,这些结构体的成员都要指向寄存器的地址。 | |
struct ehci_qh *async; struct ehci_qh *reclaim; unsigned scanning : 1; unsigned periodic_size; __hc32 *periodic; dma_addr_t periodic_dma; unsigned i_thresh; union ehci_shadow *pshadow; int next_uframe; unsigned periodic_sched; | 管理EHCI的等时调度和异性调度。 | |
struct otg_transceiver *transceiver; | 如果系统中有OTG,此成员负责OTG的操作和管理。 | |
struct dma_pool *qh_pool; / struct dma_pool *qtd_pool; struct dma_pool *itd_pool; struct dma_pool *sitd_pool; struct timer_list iaa_watchdog; struct timer_list watchdog; unsigned long actions; unsigned stamp; unsigned long next_statechange; struct list_head cached_itd_list; unsigned clock_frame; | EHCI控制过程中所需要用到的操作系统的资源的管理。 | |
我们用usb_create_hcd (const struct hc_driver *driver, struct device *dev, const char *bus_name)这个函数向内核中增加一个控制器,也就是创建一个usb_hcd结构,并把usb_hcd与 hc_driver 关联接起来。
Struct usb_hcd 定义的是一个抽象的HCD结构,但我们实际的HCD可能是EHCI,也可能是OHCI/UHCI,这两者之间如何转换呢。HCD中有一个成员是hcd_priv,解决问题的关键就是它。
我们先看两个关键的代码:
struct usb_hcd { ...... /* The HC driver's private data is stored at the end of * this structure. */ unsigned long hcd_priv[0] __attribute__ ((aligned(sizeof(unsigned long)))); }; | |
struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, struct device *dev, const char *bus_name) { ...... hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); ...... } | |
unsigned long hcd_priv[0] __attribute__ ((aligned(sizeof(unsigned long)))); 这样的语句是GNUC语言特有的用法,定义一个指向本结构体末尾的指针。 usb_create_hcd在给hcd分配内存是会多分配 driver->hcd_priv_size 大小的内存,按HCD的定义,这段多分配的内存正是unsigned long hcd_priv[0]指向的地址。这段多分配的地址正是用来存放ehci_hcd 对象的,在hc_driver中,我们一定要把ehci_hcd的长度赋给hcd_priv_size,如下: hcd_priv_size = sizeof(struct ehci_hcd), 这样usb_create_hcd 才能正确的创建usb_hcd对象,ehci_hcd才有容身之地。 至于两者这、之间的转化,可以用下面这些函数为工具: static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd) { return (struct ehci_hcd *) (hcd->hcd_priv); } static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) { return container_of ((void *) ehci, struct usb_hcd, hcd_priv); } |
linux中struct hc_driver 这个结构体来描述USB主控制器功能的接口,这算是linux对控制器驱动要求提供的基本接口,无论是EHCI还是OHCI/UHCI的驱动,要想和上层协调工作,就得实现这些接口,我们驱动EHCI控制器的工作就是要实现这些接口。
我们先研究一下这些接口:
struct hc_driver { const char *description; /* "ehci-hcd" etc */ const char *product_desc; /* product/vendor string */ size_t hcd_priv_size; /* size of private data */ /* irq handler */ irqreturn_t (*irq) (struct usb_hcd *hcd); int flags; #define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ #define HCD_LOCAL_MEM 0x0002 /* HC needs local memory */ #define HCD_USB11 0x0010 /* USB 1.1 */ #define HCD_USB2 0x0020 /* USB 2.0 */ /* called to init HCD and root hub */ int (*reset) (struct usb_hcd *hcd); int (*start) (struct usb_hcd *hcd); /* NOTE: these suspend/resume calls relate to the HC as * a whole, not just the root hub; they're for PCI bus glue. */ /* called after suspending the hub, before entering D3 etc */ int (*pci_suspend) (struct usb_hcd *hcd, pm_message_t message); /* called after entering D0 (etc), before resuming the hub */ int (*pci_resume) (struct usb_hcd *hcd); /* cleanly make HCD stop writing memory and doing I/O */ void (*stop) (struct usb_hcd *hcd); /* shutdown HCD */ void (*shutdown) (struct usb_hcd *hcd); /* return current frame number */ int (*get_frame_number) (struct usb_hcd *hcd); //控制器的关键是下面两个接口的实现,这两个接口负责处理数据的处理。 /* manage i/o requests, device state */ int (*urb_enqueue)(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags); int (*urb_dequeue)(struct usb_hcd *hcd, struct urb *urb, int status); /* hw synch, freeing endpoint resources that urb_dequeue can't */ void (*endpoint_disable)(struct usb_hcd *hcd, struct usb_host_endpoint *ep); /* root hub support */ int (*hub_status_data) (struct usb_hcd *hcd, char *buf); int (*hub_control) (struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int (*bus_suspend)(struct usb_hcd *); int (*bus_resume)(struct usb_hcd *); int (*start_port_reset)(struct usb_hcd *, unsigned port_num); int (*disconnect)(struct usb_hcd *); int (*connect)(struct usb_hcd *, struct usb_device*); /* force handover of high-speed port to full-speed companion */ void (*relinquish_port)(struct usb_hcd *, int); /* has a port been handed over to a companion? */ int (*port_handed_over)(struct usb_hcd *, int); }; |
看看omap 中对这些接口的实现
static const struct hc_driver ehci_omap_hc_driver = { .description = hcd_name, .product_desc = "OMAP-EHCI Host Controller", .hcd_priv_size = sizeof(struct ehci_hcd), /* * generic hardware linkage */ .irq = ehci_irq, .flags = HCD_MEMORY | HCD_USB2, /* * basic lifecycle operations */ .reset = ehci_init, .start = ehci_run, .stop = ehci_stop, .shutdown = ehci_shutdown, /* * managing i/o requests and associated device resources */ .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, .endpoint_reset = ehci_endpoint_reset, /* * scheduling support */ .get_frame_number = ehci_get_frame, /* * root hub support */ .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control, .bus_suspend = ehci_bus_suspend, .bus_resume = ehci_bus_resume, .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, }; |
上表中红色标识的部份都是十分复杂的接口,而且实现起来很困难,但幸运的是,EHCI中对寄存器的接口有了标准,所以这我们有许多可以重用的代码,这些代码就在 drivers/usb/host/ehci-hcd.c中,这里己经实现了与ECHI相关的许许多多重要的接口,一般而言,我们可以使用这些实现而不会有任何问题,就像上表中做的一样。
当然,我们首先要传一些参数给 hc_driver,像EHCI寄存器的基址和irq,这些都无法标准化的东西。
如下:
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); if (!hcd->regs) { dev_err(&pdev->dev, "EHCI ioremap failed/n"); ret = -ENOMEM; goto err_ioremap; } /* we know this is the memory we want, no need to ioremap again */ omap->ehci->caps = hcd->regs; omap->ehci_base = hcd->regs; res = platform_get_resource(pdev, IORESOURCE_MEM, 1); omap->uhh_base = ioremap(res->start, resource_size(res)); if (!omap->uhh_base) { dev_err(&pdev->dev, "UHH ioremap failed/n"); ret = -ENOMEM; goto err_uhh_ioremap; } |
上表中的代码很清晰,从此中我们可以看出标准化的威力,只要给内核传少许几个参数,再实现少许的接口,复杂的EHCI就可以工作起来,不用太花心思去理解我上面讲的调度了,因为大部份复杂的工作早就被先驱者完成了。
下面探讨一下先驱者是如何来尊守EHCI规范的。
Isochronous (High-Speed) Transfer Descriptor (iTD)
上图就是EHCI规范中的Isochronous (High-Speed) Transfer Descriptor (iTD),也就是等时传输数据结构,下面看看在linux kernel中如何定义的.
struct ehci_itd { /* first part defined by EHCI spec */ __hc32 hw_next; /* see EHCI 3.3.1 */ __hc32 hw_transaction [8]; /* see EHCI 3.3.2 */ #define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ #define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ #define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ #define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ #define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) #define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ #define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE) __hc32 hw_bufp [7]; /* see EHCI 3.3.3 */ __hc32 hw_bufp_hi [7]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t itd_dma; /* for this itd */ union ehci_shadow itd_next; /* ptr to periodic q entry */ struct urb *urb; struct ehci_iso_stream *stream; /* endpoint's queue */ struct list_head itd_list; /* list of stream's itds */ /* any/all hw_transactions here may be used by that urb */ unsigned frame; /* where scheduled */ unsigned pg; unsigned index[8]; /* in urb->iso_frame_desc */ } __attribute__ ((aligned (32))); |
struct ehci_itd的定义和EHCI spec是能一一对应的,而且注释中也标明了各成员在EHCI sepc中对应的章节。
__hc32 hw_next; | 指向下一个调度数据结构的指针。 | |
__hc32 hw_transaction [8]; #define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ #define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ #define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ #define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ #define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) #define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ #define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE) | 传输状态和控制字段 设置成1,EHCI会自动传输本itd数据(对此数据进行调度),完成后EHCI置此位为0,此数据以后不参与调度。 有传输错误是置1(overrun/underrun). 检测到 babble时EHCI会将此字段置1。 不能收到有效数据是EHCI置此位为1(超时,CRC错,PID错等) 传输数据长度,OUT传输时表示软件想要传送的数据长度,由软件写入,IN传输表示软件希望接收的数据长度,传输完成EHCI会写入实际的传输长度。 传输完成 使能ITD的宏。 Page Select (PG).Transaction X Offset这两个字段linux中没有使用。 | |
__hc32 hw_bufp [7]; | hw_bufp [0]的0-6位是设备地址,8-11位是端点号,12-31是buf指针。 | |
__hc32 hw_bufp_hi [7]; | 64位时会用到此位,扩展指针位数 | |
Split Transaction Isochronous Transfer Descriptor (siTD)
struct ehci_sitd { /* first part defined by EHCI spec */ __hc32 hw_next; /* uses bit field macros above - see EHCI 0.95 Table 3-8 */ __hc32 hw_fullspeed_ep; /* EHCI table 3-9 */ __hc32 hw_uframe; /* EHCI table 3-10 */ __hc32 hw_results; /* EHCI table 3-11 */ #define SITD_IOC (1 << 31) /* interrupt on completion */ #define SITD_PAGE (1 << 30) /* buffer 0/1 */ #define SITD_LENGTH(x) (0x3ff & ((x)>>16)) #define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define SITD_STS_ERR (1 << 6) /* error from TT */ #define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define SITD_STS_BABBLE (1 << 4) /* device was babbling */ #define SITD_STS_XACT (1 << 3) /* illegal IN response */ #define SITD_STS_MMF (1 << 2) /* incomplete split transaction */ #define SITD_STS_STS (1 << 1) /* split transaction state */ #define SITD_ACTIVE(ehci) cpu_to_hc32(ehci, SITD_STS_ACTIVE) __hc32 hw_buf [2]; /* EHCI table 3-12 */ __hc32 hw_backpointer; /* EHCI table 3-13 */ __hc32 hw_buf_hi [2]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t sitd_dma; union ehci_shadow sitd_next; /* ptr to periodic q entry */ struct urb *urb; struct ehci_iso_stream *stream; /* endpoint's queue */ struct list_head sitd_list; /* list of stream's sitds */ unsigned frame; unsigned index; } __attribute__ ((aligned (32))); |
__hc32 hw_next; | 指向下一sitd结构的指针,第1-2位表示传输类型(itd/sitd/qt),5-31位表示指针。 | |
__hc32 hw_fullspeed_ep; | 31位位表示方向,24-30表示端口数,16-22表示hub地址,8-11位表端点号,0-6位表设备地址。 | |
__hc32 hw_uframe; | ||
__hc32 hw_results; #define SITD_IOC (1 << 31) /* interrupt on completion */ #define SITD_PAGE (1 << 30) /* buffer 0/1 */ #define SITD_LENGTH(x) (0x3ff & ((x)>>16)) #define SITD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define SITD_STS_ERR (1 << 6) /* error from TT */ #define SITD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define SITD_STS_BABBLE (1 << 4) /* device was babbling */ #define SITD_STS_XACT (1 << 3) /* illegal IN response */ #define SITD_STS_MMF (1 << 2) /* incomplete split transaction */ #define SITD_STS_STS (1 << 1) /* split transaction state */ | ||
__hc32 hw_buf [2]; | 12-31位表示buf地址, | |
__hc32 hw_backpointer; | ||
__hc32 hw_buf_hi [2]; | 一般置0或指向一个sitd数据。 |
struct ehci_sitd中有hub的信息,当ehci配合hub支持低全速设备时,sitd就派上用场了,hub会把低速数据翻译成高速的sitd数据与ehci通讯。这样ehci在hub的帮助下也可以支持低全速设 备,而不需要兼容控制器(ohci/uhci)的参与了。
Queue Element Transfer Descriptor
struct ehci_qtd { /* first part defined by EHCI spec */ __hc32 hw_next; /* see EHCI 3.5.1 */ __hc32 hw_alt_next; /* see EHCI 3.5.2 */ __hc32 hw_token; /* see EHCI 3.5.3 */ #define QTD_TOGGLE (1 << 31) /* data toggle */ #define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) #define QTD_IOC (1 << 15) /* interrupt on complete */ #define QTD_CERR(tok) (((tok)>>10) & 0x3) #define QTD_PID(tok) (((tok)>>8) & 0x3) #define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define QTD_STS_HALT (1 << 6) /* halted on error */ #define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ #define QTD_STS_XACT (1 << 3) /* device gave illegal response */ #define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ #define QTD_STS_STS (1 << 1) /* split transaction state */ #define QTD_STS_PING (1 << 0) /* issue PING? */ #define ACTIVE_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_ACTIVE) #define HALT_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_HALT) #define STATUS_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_STS) __hc32 hw_buf [5]; /* see EHCI 3.5.4 */ __hc32 hw_buf_hi [5]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t qtd_dma; /* qtd address */ struct list_head qtd_list; /* sw qtd list */ struct urb *urb; /* qtd's urb */ size_t length; /* length of buffer */ } __attribute__ ((aligned (32))); |
hw_next | 指向下一传输单元 | |
hw_alt_next | 也是指向一个传输单元,当处理当前传输时遇到一个短包此字段会有用。 | |
hw_token #define QTD_TOGGLE (1 << 31) /* data toggle */ #define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) #define QTD_IOC (1 << 15) /* interrupt on complete */ #define QTD_CERR(tok) (((tok)>>10) & 0x3) #define QTD_PID(tok) (((tok)>>8) & 0x3) #define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ #define QTD_STS_HALT (1 << 6) /* halted on error */ #define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ #define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ #define QTD_STS_XACT (1 << 3) /* device gave illegal response */ #define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ #define QTD_STS_STS (1 << 1) /* split transaction state */ #define QTD_STS_PING (1 << 0) /* issue PING? */ | ||
hw_buf [5] | buf指针 | |
hw_buf_hi [5]; | 兼容64位的指针 |
Queue Head
队列头传输结构在linux中的定义
struct ehci_qh {
/* first part defined by EHCI spec */
__hc32 hw_next; /* see EHCI 3.6.1 */
__hc32 hw_info1; /* see EHCI 3.6.2 */
#define QH_HEAD 0x00008000
__hc32 hw_info2; /* see EHCI 3.6.2 */
#define QH_SMASK 0x000000ff
#define QH_CMASK 0x0000ff00
#define QH_HUBADDR 0x007f0000
#define QH_HUBPORT 0x3f800000
#define QH_MULT 0xc0000000
__hc32 hw_current; /* qtd list - see EHCI 3.6.4 */
/* qtd overlay (hardware parts of a struct ehci_qtd) */
__hc32 hw_qtd_next;
__hc32 hw_alt_next;
__hc32 hw_token;
__hc32 hw_buf [5];
__hc32 hw_buf_hi [5];
/* the rest is HCD-private */
dma_addr_t qh_dma; /* address of qh */
union ehci_shadow qh_next; /* ptr to qh; or periodic */
struct list_head qtd_list; /* sw qtd list */
struct ehci_qtd *dummy;
struct ehci_qh *reclaim; /* next to reclaim */
struct ehci_hcd *ehci;
/*
* Do NOT use atomic operations for QH refcounting. On some CPUs
* (PPC7448 for example), atomic operations cannot be performed on
* memory that is cache-inhibited (i.e. being used for DMA).
* Spinlocks are used to protect all QH fields.
*/
u32 refcount;
unsigned stamp;
u8 qh_state;
#define QH_STATE_LINKED 1 /* HC sees this */
#define QH_STATE_UNLINK 2 /* HC may still see this */
#define QH_STATE_IDLE 3 /* HC doesn't see this */
#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */
#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */
/* periodic schedule info */
u8 usecs; /* intr bandwidth */
u8 gap_uf; /* uframes split/csplit gap */
u8 c_usecs; /* ... split completion bw */
u16 tt_usecs; /* tt downstream bandwidth */
unsigned short period; /* polling interval */
unsigned short start; /* where polling starts */
#define NO_FRAME ((unsigned short)~0) /* pick new start */
struct usb_device *dev; /* access to TT */
} __attribute__ ((aligned (32)));
hw_next | 队列头水平指向的指针 | |
hw_info1 | 端点的特性 | |
hw_current | 当前指针 | |
hw_qtd_next | 下一传输块指针 | |
hw_alt_next | ||
hw_buf | buf指针 | |
hw_buf_hi | 兼容64位的指针 |
Periodic Frame Span Traversal Node
FSTN结构用来管理跨越几个主机时间帧( Host-frame boundary)的全速和低速传输,当 HCIVERSION寄存器指示的ehci版本低于0.96时,此结构不能使用,FSTN有点像CPU中的管理中断的栈,保存两个地址,一个指要处理的数据地址,一个指向返回地址。
struct ehci_fstn { __hc32 hw_next; /* any periodic q entry */ __hc32 hw_prev; /* qh or EHCI_LIST_END */ /* the rest is HCD-private */ dma_addr_t fstn_dma; union ehci_shadow fstn_next; /* ptr to periodic q entry */ } __attribute__ ((aligned (32))); |
我们发现,linux中itd/sitd/qtd/qh 的描述除了有ehci规范中要求的字段外,还定义了其它的字段,这些字段与linux中的usb核心系统有密切的关系。
dma_addr_t | 一个符合DMA访问要求的地址 | |
urb * | 指向URB的指针 | |
ehci_iso_stream * | 管理所有的sitd和itd传输单元中的信息 | |
list_head* | 链表指针 | |
上面列举的几个数据结构就是EHCI的核心,软件要做的事就是把上层设备驱动传下来的数转换成一个个的符合itd/sitd/qh/qtd/fstn要求的数据并在内存中放好,利用itd/sitd/qh/qtd/fstn的中定义的指针把这些数据链成链表,这些数据就相当于CPU中指令的集合,CPU的运行要求把指令在内存中放好,然后在PC寄存器的帮助下按的指令执行,这就是程序。而在EHCI中,FRINDEX寄存器和AsyncListAddr寄存器就扮演 了PC寄存器的角色。
注:转载请注明出处 datangsoc@hotmail.com
更多推荐
所有评论(0)