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;
}
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;
}
相关文章推荐
- LINUX下USB1.1设备学习小记(4)_uhci(2)
- LINUX下USB1.1设备学习小记(4)_uhci(7)
- LINUX下USB1.1设备学习小记(3)_host与device
- LINUX下USB1.1设备学习小记(2)_协议
- LINUX下USB1.1设备学习小记(3)_host与device
- LINUX下USB1.1设备学习小记(6)_hid与input子系统(3)
- LINUX下USB1.1设备学习小记(6)_hid与input子系统(1)
- LINUX下USB1.1设备学习小记(2)_协议
- LINUX下USB1.1设备学习小记(6)_hid与input子系统(2)
- LINUX下USB1.1设备学习小记
- LINUX下USB1.1设备学习小记(6)_hid与input子系统(3)
- LINUX下USB1.1设备学习小记(1)
- LINUX下USB1.1设备学习小记(2)_协…
- LINUX下USB1.1设备学习小记(2)_协…
- LINUX下USB1.1设备学习小记(1)
- LINUX下USB1.1设备学习小记(2)_协议
- LINUX下的USB1。1设备学习小记
- LINUX下USB1.1设备学习小记
- 《Linux 设备驱动 Edition 3》学习笔记
- Linux设备驱动程序学习(5)-高级字符驱动程序操作[(2)阻塞型I/O和休眠]