初始化:
在Linux下,驱动大多都是module,可以被自由的装载卸载。Rt73 usb wireless驱动也不例外,是一个可独立编译的module。Module一般都是由module_init入口,module_exit出口。
Init所做的工作就是将rtusb驱动挂入到内核usb驱动链中:returnusb_register(&rtusb_driver);
rtusb_driver的定义如下:
struct usb_driver rtusb_driver = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15)
.owner = THIS_MODULE,
#endif
.name="rt73",
.probe=usb_rtusb_probe,
.disconnect=usb_rtusb_disconnect,
.id_table=rtusb_usb_id,
#ifdef CONFIG_PM
.suspend = rt73_suspend,
.resume = rt73_resume,
#endif
};
usb_rtusb_probe函数是最重要的,主要用于设备的探测以及所需内核资源的初始化。rt73_suspend和rt73_resume主要是电源管理相关,如收到suspend时以怎样的低功耗工作,以及resume时如何恢复等。
usb_rtusb_disconnect是当usb设备从系统中移除之后会调用接口。rtusb_usb_id是驱动支持的usb设备ID列表:
struct usb_device_id rtusb_usb_id[] = RT73_USB_DEVICES;
#define RT73_USB_DEVICES { \
{USB_DEVICE(0x148f,0x2573)}, /* Ralink*/ \
{USB_DEVICE(0x148f,0x2671)}, /* Ralink*/ \
…
{ }/* Terminating entry */ \
} /* end marker */
Module初始化时向系统注册完之后,驱动就会扫描系统总线,查找是否有此驱动相应的设备在总线上,如果有设备就会调用probe函数;
或者驱动初始化后,再把设备插到系统上,此时也会从总线的中断触发到驱动的probe函数。两种调用的触发方式不同。
Probe函数:
内核程序中Usb设备驱动有一个骨架程序,描述了一个最基本的usb驱动应该具备的方法以及调用的最简单接口。因此本部分涉及基本调用不再赘述,参照骨架程序理解。
因为rt73 usb wireless为usb设备的同时,还是网络设备,需要用到网络设备驱动程序的基本结构和方法。
struct net_device *netdev;
netdev = alloc_etherdev(sizeof(PRTMP_ADAPTER));
PRTMP_ADAPTER是最核心的网络设备驱动结构,包含了所有网络设备驱动层需要的成员(是否恰当?),再对netdev的成员进行赋值,
netif_stop_queue(netdev); //设置链路状态是off
ether_setup(pAd->net_dev); //以太网接口的网络接口设置函数
netdev->open = usb_rtusb_open;
netdev->stop = usb_rtusb_close;
netdev->hard_start_xmit =usb_rtusb_sendpkt;
netdev->get_stats = rt73_get_ether_stats;
netdev->do_ioctl = rt73_ioctl;
netdev->wireless_handlers = (struct iw_handler_def *)&rt73_iw_handler_def;
rt73_iw_handler_def定义了用于wireless的一部分接口。
网络设备初始化完成之后,向内核中注册:
res = register_netdev(pAd->net_dev);
usb_set_intfdata(intf, pAd);
然后会初始化usb device:
if(usb_rtusb_init_device(pAd->net_dev)!=NDIS_STATUS_SUCCESS)//很重要
在初始化函数usb_rtusb_init_device中主要完成数据接收的初始化处理,使用tasklet,处理函数是RTUSBBulkRxHandle,
tasklet_init(&pAd->rx_bh, RTUSBBulkRxHandle, (unsignedlong)pAd);
函数RTUSBBulkRxHandle会调用RTUSBRxPacket。
初始化完发送和接收需要的相关结构后,初始化mlme相关资源。主要是mlme队列,以及不同的处理函数,调用CreateThreads进行MlmeThread(网络管理队列线程)和RTUSBCmdThread(usb命令队列线程)两个内核线程的创建。
Mlme线程是用于设置连接路由器的工作,相当于是一个状态机,如状态ASSOC_STATE_MACHINE等,设置wep,wpa密码等连接方式主要由这个线程完成,主要是基于PRTMP_ADAPTER的mlme结构体的mlme_Queue完成业务处理;
RTUSBCmdThread主要负责是一个转换线程,将对网络接口的相关设置进行转换到mlme中,包括AP搜索,数据传输,设置网络接口参数ioctl等,它主要是基于PRTMP_ADAPTER的cmdQ进行业务逻辑处理。
MLME线程:
此线程的核心结构是MLME_STRUCT,
typedef struct _MLME_STRUCT {
STATE_MACHINE CntlMachine; //不同状态机状态—控制
STATE_MACHINE AssocMachine;//关联
STATE_MACHINE AuthMachine;//认证
STATE_MACHINE AuthRspMachine;//认证回应
STATE_MACHINE SyncMachine;//同步
STATE_MACHINE WpaPskMachine;//wpapsk
STATE_MACHINE_FUNC AssocFunc[ASSOC_FUNC_SIZE];
STATE_MACHINE_FUNC AuthFunc[AUTH_FUNC_SIZE];
STATE_MACHINE_FUNC AuthRspFunc[AUTH_RSP_FUNC_SIZE];
STATE_MACHINE_FUNC SyncFunc[SYNC_FUNC_SIZE];
STATE_MACHINE_FUNC WpaPskFunc[WPA_PSK_FUNC_SIZE];
ULONG ChannelQuality; // 0..100,Channel Quality Indication for Roaming
ULONG Now32; // latch the value of
NdisGetSystemUpTime()
BOOLEAN bRunning;
spinlock_t TaskLock;
MLME_QUEUE Queue;
UINT ShiftReg;
RALINK_TIMER_STRUCT PeriodicTimer;
RALINK_TIMER_STRUCT LinkDownTimer;
ULONG PeriodicRound;
MLME_MEMORY_HANDLER MemHandler; //The handler of the nonpaged memory inside MLME
} MLME_STRUCT, *PMLME_STRUCT;
每种状态对应相应的状态处理函数,即STATE_MACHINE_FUNC。相关成员的初始化赋值是在MlmeInit中完成的。每种状态中又细分了多个阶段,每个阶段都有对应的不同处理。如AssocStateMachineInit中体现的。每个阶段是用结构体MLME_QUEUE_ELEM记录的。
typedef struct _MLME_QUEUE_ELEM {
UCHAR Msg[MAX_LEN_OF_MLME_BUFFER]; // add by johnli, fix the bug of alignment
ULONG Machine;
ULONG MsgType;
ULONG MsgLen;
LARGE_INTEGER TimeStamp;
UCHAR Rssi;
UCHAR Signal;
UCHAR Channel;
BOOLEAN Occupied;
BOOLEAN bReqIsFromNdis;
//UCHAR Msg[MAX_LEN_OF_MLME_BUFFER]; //remove by johnli, move above, fix the bug of alignment
} MLME_QUEUE_ELEM, *PMLME_QUEUE_ELEM;
多个MLEM_QUEUE_ELEM组成了MLME_QUEUE,然后由MLME线程统一处理。MLME_QUEUE是PRTMP_ADAPTER的一个成员。
typedef struct _MLME_QUEUE {
ULONG Num;
ULONG Head;
ULONG Tail;
spinlock_t Lock;
MLME_QUEUE_ELEM Entry[MAX_LEN_OF_MLME_QUEUE];
} MLME_QUEUE,*PMLME_QUEUE;
MLME处理的几种状态有:
ASSOC_STATE_MACHINE 关联状态
AUTH_STATE_MACHINE 认证状态
AUTH_RSP_STATE_MACHINE 认证回应状态
SYNC_STATE_MACHINE 同步状态
MLME_CNTL_STATE_MACHINE 控制状态
WPA_PSK_STATE_MACHINE WPAPSK状态
MLME状态机,代码比较复杂,暂不分析。Mlme部分相关的几个源文件:assoc.cauth.c auth_rsp.c connect.c mlme.c mlme_ex.c sync.c
CMDHandler线程提供---服务。将从网络设备来的上层服务,转化为MLME并加入其对列处理中,最终传递到usb层,或者直接调用usb的相关服务函数。从本质上讲是一个服务转化层。
数据是如何发送的:
初始化发送和接收相关的数据结构:
Status = NICInitTransmit(pAd); //if fail clean itself
Status = NICInitRecv(pAd);
Usb层发送数据处理函数:当网络层有数据需要发送时,就会调用usb_rtusb_sendpkt函数
netdev->hard_start_xmit =usb_rtusb_sendpkt;,此函数又会调用RTMPSendPackets函数。
dev->hard_start_xmit 是一个回调函数,值设置为dsr_dev_start_xmit。这个函数不是驱动程来调用的,而是用由上层协议来调用的。如IP或ARP层需要发送报文时,它会根据目标IP地址来决定通过哪个网络设备net_dev来传输数据,然后调用它的hard_start_xmit回调函数。
IP/TCP协议栈的调用和控制在内核已实现好了,只有数据来到设备时,才调用驱动的回调函数。这也是驱动程序实现的原理,网络程动与其它程驱动就一样的。
对rt73 usb驱动而言,真正的数据发送是通过usb完成的,在RTMPSendPackets函数中又会调用RTUSBKickBulkOut,完成数据的发送。发送函数中会发送的数据类型包括以下几类:
//1. Data Fragment has highest priority
RTUSBBulkOutDataPacket
//2. PS-Poll frame is next
RTUSBBulkOutPsPoll
//5. Mlme frame is next
RTUSBBulkOutMLMEPacket
//6. Data frame normal is next
RTUSBBulkOutDataPacket
//7. Null frame is the last
RTUSBBulkOutDataPacket
//8. No data available
RTUSBBulkOutNullFrame
数据发送是放在不同的环形buffer中的。发送的最后环节是rtusb_submit_urb,将数据放到申请的urb中,提交给usb core处理。处理完毕之后,调用rt73 网络驱动注册的回调函数RTUSBBulkOutDataPacketComplete。(此处一直怀疑是导致内存OOM的root cause)。
数据的接收处理过程:
VOID RTUSBBulkRxHandle( IN unsigned longdata)
{
purbb_t pUrb = (purbb_t)data;
PRTMP_ADAPTER pAd;
PRX_CONTEXT pRxContext;
pRxContext = (PRX_CONTEXT)pUrb->context;
pAd= pRxContext->pAd;
/*device had been closed */
if(RTMP_TEST_FLAG(pAd, fRTMP_ADAPTER_REMOVE_IN_PROGRESS))
return;
if(pUrb->status != 0)
{
RTUSBBulkReceive(pAd);
return;
}
RTUSBRxPacket(data);
return;
}
/*
========================================================================
Routine Description:
USB_RxPacket initializes a URB and uses the RxIRP to submit it
toUSB. It checks if an Rx Descriptor is available and passes the
thecoresponding buffer to be filled. If no descriptor is available
fails the request. When setting the completionroutine we pass our
Adapter Object as Context.
Return Value:
TRUE found matched tuple cache
FALSE no matched found
Note:
========================================================================
*/
VOID RTUSBBulkReceive(
/*
========================================================================
Description:
This is the completion routine for the USB_RxPacket which submits
aURB to USBD for a transmission.
========================================================================
*/
VOID RTUSBRxPacket(
RTUSBRxPacket函数中有802.11协议栈处理。Usb部分收到数据之后,调用网卡驱动的接收函数,进行简单的协议栈层处理,并将数据拷贝的之上的网络层(sk_buf)。
Ioctl处理:
netif_rx
rtusb_io.c----usb驱动的IOTCTL处理
ioctl列表:
RT_OID_CHECK_GPIO:读gpio寄存器信息
OID_802_11_BSSID_LIST_SCAN:搜索AP
RT_OID_PERIODIC_EXECUT:
RT_OID_802_11_BSSID:
OID_802_11_SSID:
OID_802_11_DISASSOCIATE:分离关联
netif_block.c---接口调用net_devices.c中提供的接口
/*
*INET An implementation of the TCP/IPprotocol suite for the LINUX
* operating system. INET isimplemented using the BSD Socket
* interface as the means of communication with the user level.
* Definitions for the Interfaces handler.
/**
*netif_wake_queue - restart transmit
*@dev: network device
*Allow upper layers to call the device hard_start_xmit routine.
*Used for flow control when transmit resources are available.
*/
/**
*netif_stop_queue - stop transmitted packets
*@dev: network device
*Stop upper layers calling the device hard_start_xmit routine.
*Used for flow control when transmit resources are unavailable.
*/
驱动已经准备完毕,应用程序需要什么服务?应用程序要使用wifi网卡进行数据传输,首先需要进行设置。完毕之后,会创建socket进行数据传输。
wifi进行 AP搜索时,会短时间内中断数据传输。
问题原因:AP搜索时会导致“短暂”的数据传输中断,驱动里有相关注释
case CNTL_WAIT_OID_LIST_SCAN:
if(Elem->MsgType == MT2_SCAN_CONF)
{
//resume txring after scanning complete,we hope the out-of-service time will not be too long to let upperlayer
//time-out the waiting frames
RTUSBResumeMsduTransmission(pAd);
Rt73 ioctl
break;
case RT_PRIV_IOCTL:
subcmd=wrq->u.data.flags;
if(subcmd&OID_GET_SET_TOGGLE) Status =RTMPSetInformation(pAd, rq, subcmd);
else Status = RTMPQueryInformation(pAd, rq,subcmd);
break;
struct iwreq *wrq=(struct iwreq *)rq;
//使用时填充好iwreq的各个变量,wrq->u.data.flags=RT_OID_802_11_EXTRA_INFO,wrq->u.data.pointer会是状态信息。
case OID_GEN_MEDIA_CONNECT_STATUS:
case RT_OID_802_11_EXTRA_INFO:
wrq->u.data.length = sizeof(ULONG);
Status = copy_to_user(wrq->u.data.pointer,&pAdapter->ExtraInfo, wrq->u.data.length);
DBGPRINT(RT_DEBUG_TRACE, "Query::RT_OID_802_11_EXTRA_INFO(=%d)\n", pAdapter->ExtraInfo);
break;
}