您的位置:首页 > 运维架构 > Linux

LINUX下USB1.1设备学习小记(4)_uhci(3)

2014-03-28 16:23 477 查看
在pci中访问uhci寄存器需要使用io端口,0x0为uhci的控制命令寄存器,所有的详细寄存器在uhci的手册中均有描述,我这里就不详细介绍了,想要的在下面的附录中有提供
check_and_reset_hc检测uhci是否需要复位,并完成复位后的初始化工作

check_and_reset_hc在/drivers/usb/host/uhci-hcd.c中

static void check_and_reset_hc(struct uhci_hcd *uhci)

{

//检测是否需要复位

if (uhci_check_and_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr))

finish_reset(uhci);

}

uhci_check_and_reset_hc负责真正的检测工作,并执行需要的复位工作

uhci_check_and_reset_hc在/drivers/usb/host/pci-quirks.c中

int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)

{

u16 legsup;

unsigned int cmd, intr;

/*

* When restarting a suspended controller, we expect all the

* settings to be the same as we left them:

*

* PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;

* Controller is stopped and configured with EGSM set;

* No interrupts enabled except possibly Resume Detect.

*

* If any of these conditions are violated we do a complete reset.

*/

//读取UHCI的UHCI_USBLEGSUP寄存器到legsup

pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup);

//检测UHCI_USBLEGSUP寄存器的0 1 2 3 4 5 7 13位是否为1

//其中一位为1则跳到reset_needed

//这些控制器和开机使用usb键盘鼠标有关,个人猜测

if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC))

{

dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n",__func__, legsup);

goto reset_needed;

}

//读取UHCI_USBCMD寄存器

cmd = inw(base + UHCI_USBCMD);

//检测UHCI_USBCMD寄存器的Run/Stop位是否为1

//进入全局悬挂标志位 ,配置位是否为0

if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) || !(cmd & UHCI_USBCMD_EGSM))

{

dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n",__func__, cmd);

goto reset_needed;

}

//读取UHCI_USBINTR寄存器

intr = inw(base + UHCI_USBINTR);

//检测UHCI_USBINTR寄存器的恢复中断位外的其它位是否为1

//也就是短包中断,TD完成中断和超时/CRC中断

if (intr & (~UHCI_USBINTR_RESUME))

{

dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n",__func__, intr);

goto reset_needed;

}

return 0;

reset_needed:

dev_dbg(&pdev->dev, "Performing full reset\n");

//执行复位

uhci_reset_hc(pdev, base);

return 1;

}

uhci_reset_hc执行复位操作

uhci_reset_hc在/drivers/usb/host/pci-quirks.c中

void uhci_reset_hc(struct pci_dev *pdev, unsigned long base)

{

/* Turn off PIRQ enable and SMI enable. (This also turns off the

* BIOS's USB Legacy Support.) Turn off all the R/WC bits too.

*/

// 将UHCI_USBLEGSUP寄存器的8 9 10 11 15位置1

pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);

/* Reset the HC - this will force us to get a

* new notification of any already connected

* ports due to the virtual disconnect that it

* implies.

*/

//将UHCI_USBCMD寄存器的主机控制器复位位置1

outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD);

mb();

//等待5ms

udelay(5);

//读取UHCI_USBCMD,检测主机控制器复位位

//为1则说明复位还没完成

if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET)

dev_warn(&pdev->dev, "HCRESET not completed yet!\n");

/* Just to be safe, disable interrupt requests and

* make sure the controller is stopped.

*/

//将UHCI_USBINTR和UHCI_USBCMD寄存器清0

outw(0, base + UHCI_USBINTR);

outw(0, base + UHCI_USBCMD);

}

finish_reset执行复位完成后的初始化工作

finish_reset在/drivers/usb/host/uhci-hcd.c中

static void finish_reset(struct uhci_hcd *uhci)

{

int port;

/* HCRESET doesn't affect the Suspend, Reset, and Resume Detect

* bits in the port status and control registers.

* We have to clear them by hand.

*/

//历遍所有端口

for (port = 0; port uhci->rh_numports; ++port)

//清0端口寄存器

outw(0, uhci->io_addr + USBPORTSC1 + (port * 2));

//初始化端口悬挂和恢复标记组

uhci->port_c_suspend = uhci->resuming_ports = 0;

//设置状态为复位

uhci->rh_state = UHCI_RH_RESET;

//设置停止标志为真

uhci->is_stopped = UHCI_IS_STOPPED;

//设置主机控制器状态为停止

uhci_to_hcd(uhci)->state = HC_STATE_HALT;

uhci_to_hcd(uhci)->poll_rh = 0;

uhci->dead = 0; /* Full reset resurrects the controller */

}

hcd->driver->start为uhci_start,这个函数负责初始化uhci的帧列表

uhci_start在/drivers/usb/host/uhci-hcd.c中

static int uhci_start(struct usb_hcd *hcd)

{

//取得uhci_hcd结构

struct uhci_hcd *uhci = hcd_to_uhci(hcd);

int retval = -EBUSY;

int i;

struct dentry *dentry;

hcd->uses_new_polling = 1;

spin_lock_init(&uhci->lock);

//初始化另一个定时结构

setup_timer(&uhci->fsbr_timer, uhci_fsbr_timeout, (unsigned long) uhci);

INIT_LIST_HEAD(&uhci->idle_qh_list);

init_waitqueue_head(&uhci->waitqh);

//分配DMA内存池空间

uhci->frame = dma_alloc_coherent(uhci_dev(uhci),

UHCI_NUMFRAMES * sizeof(*uhci->frame),

&uhci->frame_dma_handle,

0);

if (!uhci->frame)

{

dev_err(uhci_dev(uhci), "unable to allocate " "consistent memory for frame list\n");

goto err_alloc_frame;

}

//用0初始化uhci->frame所指向的dma内存空间

memset(uhci->frame, 0, UHCI_NUMFRAMES * sizeof(*uhci->frame));

uhci->frame_cpu = kcalloc(UHCI_NUMFRAMES, sizeof(*uhci->frame_cpu),GFP_KERNEL);

if (!uhci->frame_cpu)

{

dev_err(uhci_dev(uhci), "unable to allocate " "memory for frame pointers\n");

goto err_alloc_frame_cpu;

}

//创建一个td描述符用的dma内存池

uhci->td_pool = dma_pool_create("uhci_td", uhci_dev(uhci), sizeof(struct uhci_td), 16, 0);

if (!uhci->td_pool)

{

dev_err(uhci_dev(uhci), "unable to create td dma_pool\n");

goto err_create_td_pool;

}

//创建一个qh描述符用的dma内存池,qh是什么,等一下做说明

uhci->qh_pool = dma_pool_create("uhci_qh", uhci_dev(uhci),sizeof(struct uhci_qh), 16, 0);

if (!uhci->qh_pool)

{

dev_err(uhci_dev(uhci), "unable to create qh dma_pool\n");

goto err_create_qh_pool;

}

//分配一个td用于结尾td

uhci->term_td = uhci_alloc_td(uhci);

//检测结尾td分配是否成功

if (!uhci->term_td)

{

dev_err(uhci_dev(uhci), "unable to allocate terminating TD\n");

goto err_alloc_term_td;

}

//分配11个龙骨qh

for (i = 0; i UHCI_NUM_SKELQH; i++)

{

//为龙骨qh数组分配qh

uhci->skelqh = uhci_alloc_qh(uhci, NULL, NULL);

if (!uhci->skelqh)

{

dev_err(uhci_dev(uhci), "unable to allocate QH\n");

goto err_alloc_skelqh;

}

}

/*

* 8 Interrupt queues; link all higher int queues to int1 = async

*/

for (i = SKEL_ISO + 1; i SKEL_ASYNC; ++i)

//让2号到8号龙骨的link全部连接到9号龙骨上

uhci->skelqh->link = LINK_TO_QH(uhci->skel_async_qh);

//#define skel_async_qh skelqh[SKEL_ASYNC]

//设置9号龙骨的link为无效

uhci->skel_async_qh->link = UHCI_PTR_TERM;

//#define skel_term_qh skelqh[SKEL_TERM]

//设置10号龙骨的link为自己,也就是自己连接自己

uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_term_qh);

/* This dummy TD is to work around a bug in Intel PIIX controllers */

uhci_fill_td(uhci->term_td, 0, uhci_explen(0) | (0x7f TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0);

//设置终结td的link为无效

uhci->term_td->link = UHCI_PTR_TERM;

//设置9号龙骨和10号龙骨的element为终结td

uhci->skel_async_qh->element = uhci->skel_term_qh->element = LINK_TO_TD(uhci->term_td);

/*

* Fill the frame list: make all entries point to the proper

* interrupt queue.

*/

//历遍帧队列

for (i = 0; i UHCI_NUMFRAMES; i++)

{

/* Only place we don't use the frame list routines */

//设置帧连接的龙骨qh

uhci->frame = uhci_frame_skel_link(uhci, i);

}

/*

* Some architectures require a full mb() to enforce completion of

* the memory writes above before the I/O transfers in configure_hc().

*/

mb();

//配置主机控制器

configure_hc(uhci);

//设置初始化标志为1

uhci->is_initialized = 1;

//启动根集线器

start_rh(uhci);

return 0;

}

现在不得不谈一下qh了~ qh是queue head的缩写,我们已经知道帧和td的用途了,呢qh是干嘛的呢?

简单来说qh是td的一个集合,先看看qh的数据结构,如下图



很简单,只有2个字段,从上到下为Link字段和Element字段,这两个字段都为连接字段,用于连接qh或者td,这两个字段中的T字段用于标记结束,没了,这是最后一个连接,无论连接存不存在都是无效的,Q为qh/td标识,为1则标识连接对象为qh,0则为td

下图描述了使用qh结构的队列



左边qh的link指向右边的link,element指向下面的td

右边的qh的link为无效,element指向下面的td

执行顺序为从上到下先执行左边qh的td,再执行右边qh的td

呢什么是龙骨qh呢?~ 在中断传输中,有一个时间片的概念,有些设备要求主机每间隔5ms提取一次数据,有些设备要求10ms,为了满足这样的时间间隔,就诞生了龙骨qh,uhci有1024个帧,每个帧的执行时间为1ms,呢么历遍一次所有的帧就是1024ms,在中断传输中分为,1ms,2ms,4ms,8ms,16ms,32ms,64ms,128ms,我们按照fudan_abc的命名方法,称为int1,int2,.....

怎么满足这样的间隔要求呢?聪明的朋友应该猜到了吧,就是把所有的1024帧连接到同一个qh,不就是1ms访问一次了么,或者把其中一半的帧按间隔分开,512帧连接到同一个qh,不就是2ms了么

对的,龙骨qh干的就是这事情,请看下图



可以看到2号龙骨有8帧,呢么就是1024/8=int512,3号龙骨有16帧,呢么就是1024/16=int256,以此类推,

最后一个有点特别,看以看见,2号到8号龙骨qh的link都连接到了9号龙骨,呢么9号龙骨间接使用了这些帧,所以9号龙骨有所有的1024帧,呢么就是int1

负责分配连接这些帧的函数为uhci_frame_skel_link

uhci_frame_skel_link在drivers/usb/host/uhci-hcd.c中

static __le32 uhci_frame_skel_link(struct uhci_hcd *uhci, int frame)

{

int skelnum;

/*

* The interrupt queues will be interleaved as evenly as possible.

* There's not much to be done about period-1 interrupts; they have

* to occur in every frame. But we can schedule period-2 interrupts

* in odd-numbered frames, period-4 interrupts in frames congruent

* to 2 (mod 4), and so on. This way each frame only has two

* interrupt QHs, which will help spread out bandwidth utilization.

*

* ffs (Find First bit Set) does exactly what we need:

* 1,3,5,... => ffs = 0 => use period-2 QH = skelqh[8],

* 2,6,10,... => ffs = 1 => use period-4 QH = skelqh[7], etc.

* ffs >= 7 => not on any high-period queue, so use

* period-1 QH = skelqh[9].

* Add in UHCI_NUMFRAMES to insure at least one bit is set.

*/

skelnum = 8 - (int) __ffs(frame | UHCI_NUMFRAMES);

if (skelnum = 1)

skelnum = 9;

return LINK_TO_QH(uhci->skelqh[skelnum]);

}

这个函数很简单,是一个模运算,具体怎么模,参考fudan_abc的文章吧,哈哈~

UHCI_PTR_TERM为设置link或者element字段为无效

0号龙骨为无连接qh,1号龙骨为等时qh,2到9号龙骨为中断龙骨,10号龙骨为终结龙骨,一共11个龙骨

如何真正使用这些龙骨在后面介绍

uhci_alloc_td在td_pool中分配一个td结构所需要的内存大小

uhci_alloc_td在drivers/usb/host/uhci-q.c中

static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci)

{

dma_addr_t dma_handle;

struct uhci_td *td;

//dma_handle保存的 是分配的dma缓冲区起始地址

td = dma_pool_alloc(uhci->td_pool, GFP_ATOMIC, &dma_handle);

if (!td)

return NULL;

//保存dma缓冲区起始地址

td->dma_handle = dma_handle;

td->frame = -1;

INIT_LIST_HEAD(&td->list);

INIT_LIST_HEAD(&td->fl_list);

return td;

}

uhci_alloc_qh在qh_pool中分配一个qh结构所需要的内存大小

uhci_alloc_qh在drivers/usb/host/uhci-q.c中

static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,

struct usb_device *udev, struct usb_host_endpoint *hep)

{

dma_addr_t dma_handle;

struct uhci_qh *qh;

qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle);

if (!qh)

return NULL;

memset(qh, 0, sizeof(*qh));

//保存dma缓冲区起始地址

qh->dma_handle = dma_handle;

//设置该qh的连接和部件都为终结

qh->element = UHCI_PTR_TERM;

qh->link = UHCI_PTR_TERM;

INIT_LIST_HEAD(&qh->queue);

INIT_LIST_HEAD(&qh->node);

//没有usb设备则为普通qh

if (udev)

{

//取得端点的传输类型

qh->type = hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;

//检测是否为等时传输

if (qh->type != USB_ENDPOINT_XFER_ISOC)

{

//分配一个虚拟td

qh->dummy_td = uhci_alloc_td(uhci);

//检测分配是否成功

if (!qh->dummy_td)

{

dma_pool_free(uhci->qh_pool, qh, dma_handle);

return NULL;

}

}

//设置qh的状态为空闲

qh->state = QH_STATE_IDLE;

//连接端点到qh

qh->hep = hep;

//连接usb设备到qh

qh->udev = udev;

//连接该qh到端点

hep->hcpriv = qh;

//检测qh的类型是否为中断或等时

if (qh->type == USB_ENDPOINT_XFER_INT || qh->type == USB_ENDPOINT_XFER_ISOC)

qh->load = usb_calc_bus_time(udev->speed,

usb_endpoint_dir_in(&hep->desc),

qh->type == USB_ENDPOINT_XFER_ISOC,

le16_to_cpu(hep->desc.wMaxPacketSize))

/ 1000 + 1;

}

//龙骨qh,现在所使用的为龙骨qh,直接到这里

else

{ /* Skeleton QH */

//设置qh的状态为有效

qh->state = QH_STATE_ACTIVE;

qh->type = -1;

}

return qh;

}

configure_hc负责存储主机控制器需要的基本寄存器设定

configure_hc在drivers/usb/host/uhci-hcd.c中

static void configure_hc(struct uhci_hcd *uhci)

{

/* Set the frame length to the default: 1 ms exactly */

//修改USBSOF寄存器,设置周期为默认值1ms

outb(USBSOF_DEFAULT, uhci->io_addr + USBSOF);

/* Store the frame list base address */

//修改USBFLBASEADD寄存器,设置frame_list的地址

outl(uhci->frame_dma_handle, uhci->io_addr + USBFLBASEADD);

/* Set the current frame number */

//修改USBFRNUM寄存器,设置当前第一帧帧号

outw(uhci->frame_number & UHCI_MAX_SOF_NUMBER,uhci->io_addr + USBFRNUM);

/* Mark controller as not halted before we enable interrupts */

//设置主机控制器驱动为悬挂状态

uhci_to_hcd(uhci)->state = HC_STATE_SUSPENDED;

mb();

/* Enable PIRQ */

//修改USBLEGSUP寄存器,使能PIRQ

pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,USBLEGSUP_DEFAULT);

}

start_rh负责启动根集线器

start_rh在/drivers/usb/host/uhci-hcd.c中

static void start_rh(struct uhci_hcd *uhci)

{

//设置主机控制器驱动状态为运行

uhci_to_hcd(uhci)->state = HC_STATE_RUNNING;

//设置主机控制器的停止标志为0

uhci->is_stopped = 0;

/* Mark it configured and running with a 64-byte max packet.

* All interrupts are enabled, even though RESUME won't do anything.

*/

//写主机命令寄存器

//设置开始运行位,配置标志,最大包为64比特

outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, uhci->io_addr + USBCMD);

//写主机中断寄存器

//设置开启全部中断

outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,uhci->io_addr + USBINTR);

mb();

//设置uhci的状态为运行

uhci->rh_state = UHCI_RH_RUNNING;

uhci_to_hcd(uhci)->poll_rh = 1;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: