分类: LINUX
2011-08-13 16:56:39
搞了个很潮的标题~当RT73遇上S1R72V17。Ralink公司的RT73芯片平台是造就了不少优秀的54M无线网卡,它的Linux驱动做的也很完善,我在S3C2410和NXP ISP1160上都能够正常使用。但是,当RT73碰上了EPSON的S1R72V17(以后简称V17)这颗USB-HOST芯片,却无法正常工作。表面现象为可以正确加载驱动,但一旦使用ifconfig激活无线网卡后,RT73进程很快就会占据100%的CPU资源,造成系统假死。我自己调试了一段时间,至少可以肯定不是RT73驱动程序的问题,但是没有完全解决问题。正好这天EPSON的工程师来南京,带来了USB协议分析仪等高级货,和他一起调了一整天终于解决了这个问题。记录一下整个调试过程:
首先打开RT73的调试信息,将V17和1160进行对比,因为我以前调试过,里面有错误信息。
**RT2573**<7>Submit Rx URB failed -19
V17的输出信息里面多了这么一条语句,无线网卡传输的灯不会闪,自然也连接不上Dlink无线路由器。发送URB失败,在ruusb_bulk.c中发现其源码:
if((ret = rtusb_submit_urb(pUrb))!=0)
{
DBGPRINT(RT_DEBUG_ERROR,"Submit Rx URB failed %d\n", ret);
return;
}
说明rtusb_submit_urb函数返回了-19这个错误,而且是USB在进行bulk传输时出现了问题。查阅相关资料发现上面这个函数会调用到Linux内核中的drivers/usb/core/urb.c中的usb_submit_urb(struct urb *urb, gfp_t mem_flags)函数。-19的含义是-ENODEV,在usb_submit_urb函数中只能是这个发送前的判断了:
if (!usb_pipecontrol (pipe) && dev->state < USB_STATE_CONFIGURED)
return -ENODEV;
将判断中的值打印出来,发现错误出现在dev->state这个状态上。此时RT73要进行bulk传输,但是只有dev->state= USB_STATE_CONFIGURED时才可以使用USB的Endpoint0之外的端口(EP0只能进行控制传输),查看state发现是USB_STATE_ADDRESS,所以函数返回错误。
和工程师讨论了一下,感觉USB控制器在配置后就应该处于USB_STATE_CONFIGURED状态了,这儿还是处于USB_STATE_ADDRESS很奇怪。增加了更多的调试信息后发现在插上无线网卡后USB的状态的确已经处于Configured态,但是启动网卡后又改变回Address。于是我们开始跟踪V17的代码,看是什么时候将状态改回去的。发现在s1r72xxx-q.c文件中的check_device_request函数能够改变USB的状态。激活无线网卡时运行了如下代码导致无法USB状态变化:
case USB_REQ_SET_CONFIGURATION:
switch (urb->dev->state) {
/** * - 1.2.3. USB_STATE_CONFIGURED: */
case USB_STATE_CONFIGURED:
if (urb->setup_packet[2] == 0
&& urb->setup_packet[3] == 0) {
urb->dev->state = USB_STATE_ADDRESS;
}
break;
default:
break;
}
这儿用了发送USB_REQ_SET_CONFIGURATION的附加信息(setup_packet[2、3])进行判断,估计和RT73附带信息不兼容,导致USB状态机又跳回Address了。我们查看了以他USB控制器的代码,基本没有发现有在驱动里面改变USB状态的,V17的驱动这儿写的蛮奇怪,也可能是日本人写程序更严谨吧,还在意状态机的变化。
以上算是第一个问题,解决方法就是将urb->dev->state = USB_STATE_ADDRESS;这句话给注释掉,以后再也跳不回Address啦。我之前调试差不多就做到这一步,当然我没有修改V17的代码,我在urb.c里面那个出错判断用了以下语句将USB状态机强行置为configured态,当然效果是一样的。
usb_set_device_state(dev, USB_STATE_CONFIGURED);
OK,第一阶段完成,运行后不会有URB错误了,但是还是不能正常工作。于是我们继续跟踪代码,又回到了RT73网卡的驱动调试信息,这次竟然能够发现Dlink路由器了,但是又出现了新的错误信息。
**RT2573**<7>SYNC - Switch to channel 6, SSID dlink
**RT2573**<7>SYNC - Wait BEACON from 00:21:91:6f:f1:f2 ...
**RT2573**<7>SYNC - receive desired BEACON at JoinWaitBeacon... Channel = 6
**RT2573**<7>SYNC - after JOIN, SupRateLen=4, ExtRateLen=8
**RT2573**<7>AUTH - Send AUTH request seq#1 (Alg=0) 6...
**RT2573**<7><---MlmeRate 1 Channel 6
**RT2573**<7>AUTH - AuthTimeout
**RT2573**<7>AUTH – AuthTimeoutAction
发现路由器,但是连接超时。更关键的错误是这儿:
**RT2573**<7>Bulk Out MLME Failed!
bulk发送MLME数据错误?就是说bulk传输还是存在问题。EPSON工程师使用USB分析器观察数据,的确是只有bulk in的数据包,没有bulk out的包,所以能够搜索到无线网卡,但是不能进行下一步通讯。这段调试信息的出处还是rtusb_bulk.c这个文件。在RTUSBBulkOutMLMEPacketComplete函数中有如下代码:
if ((!RTMP_TEST_FLAG(pAd, fRTMP_ADAPTER_RESET_IN_PROGRESS)) &&
(!RTMP_TEST_FLAG(pAd, fRTMP_ADAPTER_HALT_IN_PROGRESS)) &&
(!RTMP_TEST_FLAG(pAd, fRTMP_ADAPTER_NIC_NOT_EXIST)) &&
(!RTMP_TEST_FLAG(pAd, fRTMP_ADAPTER_BULKOUT_RESET)))
{
DBGPRINT_RAW(RT_DEBUG_ERROR, "Bulk Out MLME Failed\n");
RTMP_SET_FLAG(pAd, fRTMP_ADAPTER_BULKOUT_RESET);
RTUSBEnqueueInternalCmd(pAd, RT_OID_USB_RESET_BULK_OUT);
}
虽然是RTUSBBulkOutMLMEPacketComplete函数报错,但这个函数是bulk调用完成才执行的,真正调用发送urb的函数应该是RTUSBBulkOutMLMEPacket()。
// Init Tx context descriptor
RTUSBInitTxDesc(pAd, pMLMEContext, 0, RTUSBBulkOutMLMEPacketComplete);
这段代码是填充USB的bulk数据结构体,并把发送完成的调用函数勾上去,既然能够发送完成,说明下面的rtusb_submit_urb(pUrb)函数至少运行完了,但是返回的数据不正确,还是没有能够正确发送。
rtusb_submit_urb(pUrb)调用了urb.c中的usb_submit_urb(),在usb_submit_urb()最后的return op->submit_urb (urb, mem_flags);才是真正的发送函数。这个函数的真身是什么呢?在hcd.c中有如下内容:
* usb_hcd_operations - adapts usb_bus framework to HCD framework (bus glue)
*/
static struct usb_operations usb_hcd_operations = {
.get_frame_number = hcd_get_frame_number,
.submit_urb = hcd_submit_urb,
.unlink_urb = hcd_unlink_urb,
.buffer_alloc = hcd_buffer_alloc,
.buffer_free = hcd_buffer_free,
.disable = hcd_endpoint_disable,
};
.submit_urb = hcd_submit_urb,看到这行吧,下面来找hcd_submit_urb()函数,其中的最后 status = hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags);
说明调用了urb_enqueue (hcd, ep, urb, mem_flags);函数,这又是何方神圣?看V17的驱动s1r72xxx-hcd.c函数,注册结构体如下:
static struct hc_driver s1r72xxx_hc_driver = {
.description = DRV_NAME,
.hcd_priv_size = sizeof(struct hcd_priv),
.irq = s1r72xxx_hcd_irq,
.flags = HCD_USB2 | HCD_MEMORY,
.reset = s1r72xxx_hcd_reset,
.start = s1r72xxx_hcd_start,
.suspend = s1r72xxx_hcd_suspend,
.resume = s1r72xxx_hcd_resume,
.stop = s1r72xxx_hcd_stop,
.get_frame_number = s1r72xxx_hcd_get_frame,
.urb_enqueue = s1r72xxx_hcd_enqueue,
.urb_dequeue = s1r72xxx_hcd_dequeue,
.endpoint_disable = s1r72xxx_hcd_endpoint_disable,
.hub_status_data = s1r72xxx_hcd_hub_status,
.hub_control = s1r72xxx_hcd_hub_control,
.bus_suspend = s1r72xxx_hcd_hub_suspend, //kyon
.bus_resume = s1r72xxx_hcd_hub_resume,
.start_port_reset = s1r72xxx_start_port_reset,
};
其中就有.urb_enqueue = s1r72xxx_hcd_enqueue,肯定会调用到这个函数。在这个函数中加入调试信息,发现发送错误的时候,函数没有运行完毕就直接返回了,主要问题在如下函数处:
/**
* - 3. Allocate CH:
* - Check allocated CH to a same EP. If allocated CH is not exist,
* allocate new CH.:
*/
dev_addr = get_devaddr(urb);
allocated = get_ch_connected_to(priv, dev_addr, ep);
//allocated = -19;
ch = allocated;
DPRINT(DBG_API,"%s: get ch connected %d\n",__FUNCTION__, allocated);
s1r72xxx_queue_log(S_R_DEBUG_QUEUE, allocated, hcd_queue->count);
s1r72xxx_queue_log(S_R_DEBUG_QUEUE, urb->transfer_buffer_length
,urb->actual_length);
if (allocated < 0){
/* if this ep is not asigned early enqueue, allocate new CH. */
ch = alloc_ch_resource(hcd, priv, dev_addr, ep, urb);
DPRINT(DBG_API,"%s: alloc ch resource %d\n",__FUNCTION__,ch);
}
这一段主要是分配用来传输USB数据的endpoint。调试时我们发现了一段很有趣的事情,系统首先会调用get_ch_connected_to(priv, dev_addr, ep);试图寻找一个已有的endping,如果找不到就调用alloc_ch_resource(hcd, priv, dev_addr, ep, urb);分配一个。结果正确传输的数据包在get_ch_connected_to(priv, dev_addr, ep);函数的返回都是找不到,必要要重新分配一个,偏偏我们传输错误的bulk包,在get_ch_connected_to()时获得了ep1,没有执行alloc_ch_resource()函数。
问题的关键就是这儿!因为此时要bulk out ,此时获得了ed1,但是ep1已经被bulk in给占用了!v17对bulk in和bulk out的通常做法是用两个ednpoint来完成,此时bulk in已经占用了ep1,而bulk out时却分配了ep1,当然发送不出去。据epson工程师分析,应该是get_ch_connected_to()获取ep信息时没有判断bulk的状态是in还是out,于是我们在get_ch_connected_to()函数中添加的相关通讯方向的判断:
if ((((ep->desc.bEndpointAddress) & USB_ENDPOINT_DIR_MASK)
== USB_DIR_IN && priv->ch_info[ch_ct].dir == S_R_CH_DIR_IN)
&& (((ep->desc.bEndpointAddress) & USB_ENDPOINT_DIR_MASK)
== USB_DIR_OUT && priv->ch_info[ch_ct].dir == S_R_CH_DIR_OUT)
{
DPRINT(DBG_API,"%s:match CH%d addr=%d, ep=%d\n", __FUNCTION__,
ch_ct, dev_addr, ep_no);
return ch_ct;
}
好了,当再次bulk out调用get_ch_connected_to()时,由于bulk方向不同,不会被分配到ep1去了,而是从新申请了ep2通道。
至此,华硕无线网卡上的小蓝灯终于开始闪烁,S1R72V17不怕RT73!
最后要感谢EPSON公司的钟先生陪我调到晚上10点,辛苦了。