linux usb ehci 驱动解读(一)

一直都是使用Usb 驱动程序,从来没有好好研读过。之前项目中碰到usb相关的也是usb register配置一下就好了。

至于Usb驱动如何工作,让我们慢慢来揭开它神秘的面纱。

对usb ehci 驱动的基本框架理解得益与understanding linux usb ehci device driver。这里还是借用这张经典的usb驱动框图,明确下EHCI在整个USB驱动中所处的地位。



EHCI驱动: Linux usb host controller driver(HCD )

usb host controller驱动,负责将usb_core发来的URB传输请求转化成HC可识别的格式并启动HC传输,直至完成传输. 整个usb subsystem只有该驱动直接操作硬件寄存器.该层也支持多种不同的host controller driver,比如UHCI,OHCI

在整个HCD的解读之前,需要了解ehci-specification-for-usb.pdf.这里跳过规范相关说明,用到时会加以引用。或请认真阅读understanding linux usb ehci device driver(1),是对EHCI规范的部分解读。

kernel/linux-3.10.y/drivers/usb$ ls -al

drwxr-xr-x  20 karry karry   4096  9月  7 14:46 .

drwxr-xr-x 117 karry karry   4096  9月  7 14:46 ..

drwxr-xr-x   2 karry karry   4096  9月  7 14:45 core

drwxr-xr-x   3 karry karry   4096  9月  7 14:57 gadget

drwxr-xr-x   3 karry karry   4096  9月  7 14:45 host

drwxr-xr-x   2 karry karry   4096  9月  7 14:46 storage

-rw-r–r–   1 karry karry   2462 1019  2015 usb-common.c

-rw-rw-r–   1 karry karry   3372 1019  2015 usb-common.o

-rw-rw-r–   1 karry karry  33959 1019  2015 .usb-common.o.cmd

usb host controller driver 只需关注host目录,结合core目录即可。

从数据结构来看,需要实现usbcorestruct hc_driver定义的接口。

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_MEMORY0x0001/* HC regs use memory (else I/O) */
#define HCD_LOCAL_MEM0x0002/* HC needs local memory */
#define HCD_SHARED0x0004/* Two (or more) usb_hcds share HW */
#define HCD_USB110x0010/* USB 1.1 */
#define HCD_USB20x0020/* USB 2.0 */
#define HCD_USB30x0040/* USB 3.0 */
#define HCD_MASK0x0070

/* 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, bool do_wakeup);

/* called after entering D0 (etc), before resuming the hub */
int (*pci_resume)(struct usb_hcd *hcd, bool hibernated);

/* 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);

/*
* (optional) these hooks allow an HCD to override the default DMA
* mapping and unmapping routines.  In general, they shouldn't be
* necessary unless the host controller has special DMA requirements,
* such as alignment contraints.  If these are not specified, the
* general usb_hcd_(un)?map_urb_for_dma functions will be used instead
* (and it may be a good idea to call these functions in your HCD
* implementation)
*/
int (*map_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb,
  gfp_t mem_flags);
void    (*unmap_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb);

/* hw synch, freeing endpoint resources that urb_dequeue can't */
void (*endpoint_disable)(struct usb_hcd *hcd,
struct usb_host_endpoint *ep);

/* (optional) reset any endpoint state such as sequence number
  and current window */
void (*endpoint_reset)(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);

/* 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);

/* CLEAR_TT_BUFFER completion callback */
void (*clear_tt_buffer_complete)(struct usb_hcd *,
struct usb_host_endpoint *);

/* xHCI specific functions */
/* Called by usb_alloc_dev to alloc HC device structures */
int (*alloc_dev)(struct usb_hcd *, struct usb_device *);
/* Called by usb_disconnect to free HC device structures */
void (*free_dev)(struct usb_hcd *, struct usb_device *);
/* Change a group of bulk endpoints to support multiple stream IDs */
int (*alloc_streams)(struct usb_hcd *hcd, struct usb_device *udev,
struct usb_host_endpoint **eps, unsigned int num_eps,
unsigned int num_streams, gfp_t mem_flags);
/* Reverts a group of bulk endpoints back to not using stream IDs.
* Can fail if we run out of memory.
*/
int (*free_streams)(struct usb_hcd *hcd, struct usb_device *udev,
struct usb_host_endpoint **eps, unsigned int num_eps,
gfp_t mem_flags);

/* Bandwidth computation functions */
/* Note that add_endpoint() can only be called once per endpoint before
* check_bandwidth() or reset_bandwidth() must be called.
* drop_endpoint() can only be called once per endpoint also.
* A call to xhci_drop_endpoint() followed by a call to
* xhci_add_endpoint() will add the endpoint to the schedule with
* possibly new parameters denoted by a different endpoint descriptor
* in usb_host_endpoint.  A call to xhci_add_endpoint() followed by a
* call to xhci_drop_endpoint() is not allowed.
*/
/* Allocate endpoint resources and add them to a new schedule */
int (*add_endpoint)(struct usb_hcd *, struct usb_device *,
struct usb_host_endpoint *);
/* Drop an endpoint from a new schedule */
int (*drop_endpoint)(struct usb_hcd *, struct usb_device *,
struct usb_host_endpoint *);
/* Check that a new hardware configuration, set using
* endpoint_enable and endpoint_disable, does not exceed bus
* bandwidth.  This must be called before any set configuration
* or set interface requests are sent to the device.
*/
int (*check_bandwidth)(struct usb_hcd *, struct usb_device *);
/* Reset the device schedule to the last known good schedule,
* which was set from a previous successful call to
* check_bandwidth().  This reverts any add_endpoint() and
* drop_endpoint() calls since that last successful call.
* Used for when a check_bandwidth() call fails due to resource
* or bandwidth constraints.
*/
void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *);
/* Returns the hardware-chosen device address */
int (*address_device)(struct usb_hcd *, struct usb_device *udev);
/* Notifies the HCD after a hub descriptor is fetched.
* Will block.
*/
int (*update_hub_device)(struct usb_hcd *, struct usb_device *hdev,
struct usb_tt *tt, gfp_t mem_flags);
int (*reset_device)(struct usb_hcd *, struct usb_device *);
/* Notifies the HCD after a device is connected and its
* address is set
*/
int (*update_device)(struct usb_hcd *, struct usb_device *);
int (*set_usb2_hw_lpm)(struct usb_hcd *, struct usb_device *, int);
/* USB 3.0 Link Power Management */
/* Returns the USB3 hub-encoded value for the U1/U2 timeout. */
int (*enable_usb3_lpm_timeout)(struct usb_hcd *,
struct usb_device *, enum usb3_link_state state);
/* The xHCI host controller can still fail the command to
* disable the LPM timeouts, so this can return an error code.
*/
int (*disable_usb3_lpm_timeout)(struct usb_hcd *,
struct usb_device *, enum usb3_link_state state);
int (*find_raw_port_number)(struct usb_hcd *, int);
}

struct ehci_hcd 定义了ehci host controller driver的数据结构部分,,struct hc_driver则定义了基于struct usb_hcd的接口,该接口中的所有函数类型第一个参数都是struct usb_hcd*;可以通过hcd_to_ehci()struct usb_hcd *获得对应的struct ehci_hcd *, 还可以通过ehci_to_hcd()struct ehci_hcd *获得struct usb_hcd*.

/* convert between an HCD pointer and the corresponding 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);

}

Ehci device driver的主要工作就是实现struct hc_driver中定义的主要接口,一般来说以下接口是必须要实现的:
irqreturn_t(*irq)(struct usb_hcd *hcd);//ehci hcdirq handler

/* called to init HCD and root hub */

int(*reset)(struct usb_hcd *hcd);

int(*start)(struct usb_hcd *hcd); //启动HC

/* cleanly make HCD stop writing memory and doing I/O */
void (*stop)(struct usb_hcd *hcd); //停止HC
int  (*get_frame_number)(struct usb_hcd *hcd);//获得当前的frame

/*根据hcdep的信息,安排urbscheduleEHCI,URB的传输完成后,会调用urb->complete ()通知usbcore*/
int(*urb_enqueue)(struct usb_hcd *hcd,struct urb *urb, gfp_t mem_flags);

/*该接口用于用户取消已经enqueueurb,主要为usbcoreunlink_urb()所调用*/
int(*urb_dequeue)(struct usb_hcd *hcd,struct urb *urb, int status);

 /*disable the ep , 并且释放该ep上资源(unlinkep上的qh)*/
void   (*endpoint_disable)(struct usb_hcd *hcd,struct usb_host_endpoint *ep);
/*获取root hub port的状态信息*/
int      (*hub_status_data) (struct usb_hcd *hcd, char *buf);

/*操作root hub以及port*/

int      (*hub_control)(struct usb_hcd *hcd,
                            u16 typeReq, u16 wValue, u16 wIndex,
                            char *buf, u16 wLength);

///结合源码分析hc_driver接口实现///

1. HCD加载( 分析海思3536平台 hiusb-ehci.c )

static struct platform_driver hiusb_ehci_hcd_driver = {

.probe         = hiusb_ehci_hcd_drv_probe,

.remove        = hiusb_ehci_hcd_drv_remove,

.shutdown      = usb_hcd_platform_shutdown,

.driver = {

.name  = "hiusb-ehci",

.owner = THIS_MODULE,

.pm    = HIUSB_EHCI_PMOPS,

}

};

static struct resource hiusb_ehci_res[] = {

[0] = {

.start = CONFIG_HIUSB_EHCI_IOBASE,

.end   = CONFIG_HIUSB_EHCI_IOBASE

+ CONFIG_HIUSB_EHCI_IOSIZE – 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = CONFIG_HIUSB_EHCI_IRQNUM,

.end   = CONFIG_HIUSB_EHCI_IRQNUM,

.flags = IORESOURCE_IRQ,

},

};

static struct platform_device hiusb_ehci_platdev = {

.name = "hiusb-ehci",

.id = 0,

.dev = {

.platform_data     = NULL,

.dma_mask          = &usb_dmamask,

.coherent_dma_mask = DMA_BIT_MASK(32),

.release           = usb_ehci_platdev_release,

},

.num_resources = ARRAY_SIZE(hiusb_ehci_res),

.resource      = hiusb_ehci_res,

};

再请看:Ehci-hcd.c

module_init(ehci_hcd_init);==>

static int __init ehci_hcd_init(void)==>

1. retval = platform_device_register(&hiusb_ehci_platdev);

pr_info("%s: " DRIVER_DESC "\n", hcd_name);  //参见内核打印输出;

内核打印:

ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver   

 before uhci_hcd and ohci_hcd, not after

hiusb-ehci hiusb-ehci.0: HIUSB EHCI

hiusb-ehci hiusb-ehci.0: new USB bus registered, assigned bus number 1

hiusb-ehci hiusb-ehci.0: irq 53, io mem 0x10040000

hiusb-ehci hiusb-ehci.0: USB 0.0 started, EHCI 1.00

hub 1-0:1.0: USB hub found

hub 1-0:1.0: 2 ports detected

……

2. retval = platform_driver_register(&PLATFORM_DRIVER);

    #definePLATFORM_DRIVER    hiusb_ehci_hcd_driver

这样系统在检测到usb host controller时会调用

static int hiusb_ehci_hcd_drv_probe(struct platform_device *pdev)==

1. hcd = usb_create_hcd(&hiusb_ehci_hc_driver, &pdev->dev, "hiusb-ehci");//创建usb_hcd

2.  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

hcd->rsrc_start = res->start;

hcd->rsrc_len = resource_size(res);

request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name);

hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); //获取IO基地址;

3. hiusb_start_hcd();   //配置ustctrlusb clk等。

4. ehci = hcd_to_ehci(hcd);

//初始化ehci寄存器基地址caps,regs

5. ehci->caps = hcd->regs;//capability Registers

6. ehci->regs = hcd->regs +HC_LENGTH(ehci, readl(&ehci->caps->hc_capbase));//Operational Registers,之后hc_driver 很多操作的实现也是读写该寄存器。 

4. /* cache this readonly data; minimize chip reads */ //ehciCapability Parameters读入到ehci->hcs_params缓冲起来

ehci->hcs_params = readl(&ehci->caps->hcs_params);

5. ret =usb_add_hcd(hcd, pdev->resource[1].start,IRQF_DISABLED | IRQF_SHARED);

6. platform_set_drvdata(pdev, hcd);

Capability Registers


Operational Registers

2.ehci接口实现

2.1 .reset= hiusb_ehci_setup 接口实现

 usbcore API —usb_add_hcd()会通过hcd->driver->reset(hcd)来调用.

 hiusb_ehci_hcd_drv_probe()函数会调用 usb_add_hcd(). //见上面分析

static int hiusb_ehci_hcd_drv_probe(struct platform_device *pdev)调用:

ret = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_DISABLED | IRQF_SHARED);调用:

1.if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) {}

2. retval = hcd->driver->start(hcd);

下面分析hiusb_ehci_setup

hiusb_ehci_setup(struct usb_hcd *hcd)=

ehci_init(hcd);=》//调用 ehci_init() 初始化ehci的数据结构:

1.spin_lock_init(&ehci->lock);

2. hrtimer_init(&ehci->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);//初始化watchdog timer主要用于发现和处理irq lost的情况

3.hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params);

4.ehci->periodic_size = DEFAULT_I_TDPS;  //1024

5 .ehci_mem_init(ehci, GFP_KERNEL)调用ehci_mem_init()分配并初始化HC schedule所需的数据结构,主要有

    .预先分配一定数量的qtd,qh,itd以及sitdehci->qtd_pool, ehci->qh_pool, ehci->itd_poolehci->sitd_pool中作为cache;

/* QTDs for control/bulk/intr transfers */

ehci->qtd_pool = dma_pool_create ("ehci_qtd",…)

/* QHs for control/bulk/intr transfers */

ehci->qh_pool = dma_pool_create ("ehci_qh",…)

ehci->async = ehci_qh_alloc (ehci, flags);==> 

// .ehci->qh_pool分配一个qh, 并使得ehci->async指向该qh,这个qh用作asynchronous schedulereclamation list head (H bit1),实现Empty Asynchronous //Schedule Detection;

a.qh = kzalloc(sizeof *qh, GFP_ATOMIC);

b.qh->hw = (struct ehci_qh_hw *)dma_pool_alloc(ehci->qh_pool, flags, &dma);

c.qh->qh_dma = dma;

d.INIT_LIST_HEAD (&qh->qtd_list);

/* dummy td enables safe urb queuing */

e.qh->dummy = ehci_qtd_alloc(ehci, flags);==>

  e.1 qtd = dma_pool_alloc (ehci->qtd_pool, flags, &dma);

  e.2 ehci_qtd_init(ehci, qtd, dma);

/* ITD for high speed ISO transfers */

ehci->itd_pool = dma_pool_create ("ehci_itd",…)

/* SITD for full/low speed split ISO transfers */

ehci->sitd_pool = dma_pool_create ("ehci_sitd",…)

/* Hardware periodic table */// .调用dma_alloc_coherent()分配Hardware periodic table,并令ehci->periodic指向其,ehci->periodic_size设为 1024,ehci->periodic_dma返回表格对应的物理地址;初始化表格中每项初值为EHCI_LIST_END,即不包含 periodic schedule data structure;

ehci->periodic = (__le32 *)

dma_alloc_coherent (ehci_to_hcd(ehci)->self.controller,

ehci->periodic_size * sizeof(__le32),

&ehci->periodic_dma, 0);

ehci->periodic[i] = EHCI_LIST_END(ehci);

/* software shadow of hardware table */// .分配software shadow of hardware table, ehci->ehci->pshadow指向其,并初始化表格内容为全0;

ehci->pshadow = kcalloc(ehci->periodic_size, sizeof(void *), flags);

6. /* controllers may cache some of the periodic schedule … *///
根据
ehci->caps->hcc_params
指向的参数初始化
ehci->i_thresh,
该参数代表了
HC
会预取多 少个
micro-frame

periodic schedule data structure;

if (HCC_ISOC_CACHE(hcc_params))// full frame cache
ehci->i_thresh = 0;
else // N microframes cached

ehci->i_thresh = 2 + HCC_ISOC_THRES(hcc_params);

7.  初始化asynchronous schedule data structure:
ehci->async->qh_next.qh = NULL;

hw = ehci->async->hw;

hw->hw_next = QH_NEXT(ehci, ehci->async->qh_dma);

hw->hw_info1 = cpu_to_hc32(ehci, QH_HEAD);

#if defined(CONFIG_PPC_PS3)

hw->hw_info1 |= cpu_to_hc32(ehci, QH_INACTIVATE);

#endif

hw->hw_token = cpu_to_hc32(ehci, QTD_STS_HALT);

hw->hw_qtd_next = EHCI_LIST_END(ehci);

ehci->async->qh_state = QH_STATE_LINKED;

hw->hw_alt_next = QTD_NEXT(ehci, ehci->async->dummy->qtd_dma);
          

8. 依据irq_thresh, park mode, periodic size等信息构造ehci->command缺省值;

……

ehci->command = temp;
 

2.2 .start= ehci_run, 接口实现           

usbcore API —usb_add_hcd()在分配完root hub usbdevice后会通过hcd->driver->start(hcd)来调用.

/* start HC running; it's halted, ehci_init() has been run (once) *///写入periodic schedule list地址以及asynchronous schedule list地址到HC的相应寄存器:

Host Controller Operational Registers的定义参见:Table2.8

static int ehci_run (struct usb_hcd *hcd)==

1. ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list);

/* PERIODICLISTBASE: offset 0x14 */

ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next);

/* ASYNCLISTADDR: offset 0x18 */

2. hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params);

if (HCC_64BIT_ADDR(hcc_params)) {

ehci_writel(ehci, 0, &ehci->regs->segment);

} //64bit模式(HC作为bus master生成64bit地址)的处理:

3. 
启动HC

// Philips, Intel, and maybe others need CMD_RUN before the

// root hub will detect new devices (why?); NEC doesn't

ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET);

ehci->command |= CMD_RUN;

ehci_writel(ehci, ehci->command, &ehci->regs->command);

4.  ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); /* Turn On Interrupts */

2.3 

.stop= ehci_stop,
接口实现
         

1) hiusb_ehci_hcd_drv_remove()调用usb_remove_hcd()

2) usb_remove_hcd() 会通过hcd->driver->stop(hcd) 调用;

static int hiusb_ehci_hcd_drv_remove(struct platform_device *pdev)

{

struct usb_hcd *hcd = platform_get_drvdata(pdev);

usb_remove_hcd(hcd);

iounmap(hcd->regs);

release_mem_region(hcd->rsrc_start, hcd->rsrc_len);

usb_put_hcd(hcd);

hiusb_stop_hcd();

platform_set_drvdata(pdev, NULL);

return 0;

}

 // Called when the ehci_hcd module is removed.

static void ehci_stop (struct usb_hcd *hcd)==》

1.    强制HCrunning state进入idle状态,并复位HC chip, disable所有中断;

ehci_quiesce(ehci);
ehci_silence_controller(ehci);
ehci_reset (ehci);

/*
 * Idle the controller (turn off the schedules).
 * Must be called with interrupts enabled and the lock not held.
 */
static void ehci_quiesce (struct ehci_hcd *ehci)
{
u32 temp;

if (ehci->rh_state != EHCI_RH_RUNNING)
return;

/* wait for any schedule enables/disables to take effect */
temp = (ehci->command << 10) & (STS_ASS | STS_PSS);
handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, temp, 16 * 125);

/* then disable anything that's still active */
spin_lock_irq(&ehci->lock);
ehci->command &= ~(CMD_ASE | CMD_PSE);
ehci_writel(ehci, ehci->command, &ehci->regs->command);
spin_unlock_irq(&ehci->lock);

/* hardware can take 16 microframes to turn off … */
handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, 0, 16 * 125);
}

/*
 * Halt HC, turn off all ports, and let the BIOS use the companion controllers.
 * Must be called with interrupts enabled and the lock not held.
 */
static void ehci_silence_controller(struct ehci_hcd *ehci)//root hubport 控制权交给companion HC;
{
ehci_halt(ehci);

spin_lock_irq(&ehci->lock);
ehci->rh_state = EHCI_RH_HALTED;
ehci_turn_off_all_ports(ehci);

/* make BIOS/etc use companion controller during reboot */
ehci_writel(ehci, 0, &ehci->regs->configured_flag);

/* unblock posted writes */
ehci_readl(ehci, &ehci->regs->configured_flag);
spin_unlock_irq(&ehci->lock);
}

2.ehci->enabled_hrtimer_events = 0;,删除watchdog timer;

hrtimer_cancel(&ehci->hrtimer);

3.   


/* root hub is shut down separately (first, when possible) */


spin_lock_irq (&ehci->lock);


end_free_itds(ehci);


spin_unlock_irq (&ehci->lock);


ehci_mem_cleanup (ehci);

2.4 .get_frame_number= ehci_get_frame,接口实现     

 hcd_get_frame_number():hcd.c, 该函数为struct usb_operations定义的接口;返回当前usb busframe number;


return (ehci_read_frame_index(ehci) >> 3) % ehci->periodic_size;

       //未完待续

Published by

风君子

独自遨游何稽首 揭天掀地慰生平