回顾一下USB的相关知识
USB(UniversalSerialBus)总线又叫通用串行外部总线,它是20世纪90年代发展上去的。USB插口如今得到了广泛的应用和普及,现今的PC机中都带有大量的USB插口。它最大的特征就是便捷通用、支持热拔插而且可以在一个插口上插上多个设备。当设备用电量小的时侯,它还可以充当电源。它的诸多优点促使它得到了广泛的应用。
在PC机器内部有个USB中央控制器,这个中央控制器负责管理插到USB插口上的设备。当主机要向设备发送或接受数据时,都是向USB中央控制器发出命令,USB设备不具备主动与主机通讯的能力。编撰USB设备驱动不用考虑申请设备地址空间,由于USB中央控制器会给设备分配一个设备号,这个设备号就代表这个设备。
USB设备和USB中央控制器之间的通讯是通过端点来完成的。端点的职能有点类似一栋大厦的门卫。诸如每位楼层都有一个门卫,当要访问5楼的10号卧室时,那就是向5号端点发起对话,并提供偏斜量,也就10号卧室。USB插口的端点按传输信息的类型分为以下4种:
a--控制端点
主要拿来传输控制信息的,比如配置设备时发出的控制信息。控制端点通常都是单向,既可以输入又可以输出。其他端点的输出方向通常是双向的,要么是输入,要么是输出的。这儿是站在主机的角度来谈论输入输出的。
b--中断端点
主要拿来传输中断信息的,因为USB设备是受USB中央控制器管理的,因而USB设备没有向主机发出中断的能力,但是USB设备不能主动向主机发出恳求,只有主机可以向USB设备发出命令恳求,因而所谓的中断是指主机周期性的查询USB设备。
c--批量端点
主要拿来传输批量信息的linux系统编程,批量信息就意味着大量的信息。U盘通常主要使用的就是批量端点。本文研究的USB无线网卡也是使用批量断点来传输数据的。发送和接收函数都是使用批量端点和USB设备传输数据的。
b--等时端点
主要拿来传输等时信息的,主要用于传输实时性要求较高的信息,比如实时的音频、视频等信息。有代表性的USB设备是USB摄像头等。
在一个具体的USB设备中不要求一定都存在这4种类型的端点,比如U盘通常就只有批量端点和控制端点。在Linux内核中拿来描述USB设备端点信息的数据结构如下:
structusb_endpoint_descriptor{
__u8bLength;
__u8bDescriptorType;
__u8bEndpointAddress;
__u8bmAttributes;
__le16wMaxPacketSize;
__u8bInterval;
__u8bRefresh;
_u8bSynchAddress;
}__attribute__((packed));
复制代码
成员bLength描述本数据结构共有多少字节,由于后两个成员是针对音频设备的,假如不是音频设备则可以没有后两个成员。成员bDescriptorType是描述本数据结构要描述的类型,这儿是描述端点的,在内核中0x05就代表端点。
成员bEndpointAddress包含端点号和输出方向,bits0-bits3表示的是端点号,从这儿可以看出一个USB设备最多只能有不超过16个端点,bits8是代表传输方向的,假如该位是1就代表输入,也就是读设备;假如该位为0就代表输出,也就是写设备。
成员bmAttributes表示该端点的类型,如上述的4种类型。
成员wMaxPacketSize表示该端点一次可以传输的最多字节数。假如要传输的数据小于这个数字,那就要分多次传输。成员bInterval代表的是该端点希望主机协程自己的时间间隔,这只是一种希望,具体还要看主机如何做。
该数据结构最后的__attribute__((packed))代表在分配该数据结构时数据成员之间不要为了显存对齐而留下缝隙,比如有这样一个数据结构的相邻两个成员类型是u8和u16,在通常情况下u8前面要空一个字节,之后才是u16成员,假如有前面attribute的要求后,在u8前面就不要留空间,紧接着就是u16成员。在内核中有好多须要访问设备的数据结构都有这样的要求,由于在一个设备中通常没有显存对齐的要求。
一、USB设备驱动程序的构成
1、设备的侦测
用于检测传递给侦测函数的设备信息,确认驱动程序是否适宜该设备。
2、数据的发送和接收
负责主机到设备的发送和设备到主机的数据接收。
3、设备断掉
当设备断掉时侯,模块负责清理和该设备关联的所有资源。
4、模块的加载和卸载
用于加载和卸载usb插口的无线网卡驱动程序。
二、USB无线网卡的构成
USB无线网卡主要由USB插口、MAC控制器、基带处理、调制混频器、功率放大器和收发器及天线等组成。
MAC控制器是核心部件,它负责从主机读取数据并发送出去,或则接收数据并发献给主机等。它负责通道选择、速率选择、加密揭秘等等的控制。
固件储存区是拿来储存MAC控制器要运行的微码。固件是一种经过编译的可执行代码,通常是由设备的芯片来执行的。
帧缓存就是拿来储存数据的暂时场所。
EEPROM是否有没有要看具体的设备,有的设备是没有的,EEPROM通常都储存一些本设备的一些参数,比如本设备的MAC地址,本设备在家族产品中的机型等等。
基带处理和ADC、DAC是数模拟转换的功能部份。要发送的数据或则接收的模拟讯号在这个地方进行转换。
收发器的功能类似调制混频器,收发器内部有个功率放大器,把弱讯号提高到一定的强讯号,收发器还负责混频等工作。
天线系统就是负责把数据通过天线发送或接收。天线的作用是使传输距离更远。
USB插口无线网卡的硬件逻辑:
USB无线网卡的通道和速度是多个的,在发送和接收时通道和速度是可以变换的。在linux中通道用如下数据结构表示:
structieee80211_channel{
enumieee80211_bandband;
u16center_freq;
u16hw_value;
u32flags;
intmax_antenna_gain;
intmax_power;
boolbeacon_found;
u32orig_flags;
intorig_mag,orig_mpwr;
};
复制代码
三、模块的加载
在编撰USB无线网卡驱动函数之前,首先先了解一下设备在插入到USB插口到设备成功找到它自己的驱动这一过程。
1、获取设备一些信息,发生在USB核心
当把USB设备插到USB插口上后,USB主机控制器会检查到有设备插入USB插口了,Linux内核会给设备分配一个数据结构来代表这个设备。本文中涉及的硬件是USB设备,因而Linux会分配一个structusb_device数据结构来代表该设备,该数据结构记录设备的一些属性及数据。并把该数据结构挂载到一个全局的USB设备链上。在这一期间主机通过0号端点(控制端点)得知了设备的一些信息,并晓得了设备的厂家号和产品号。
2、找到匹配的驱动,发生在USB核心
之后到一个全局的USB驱动链上查找,瞧瞧那个驱动程序支持的设备列表中有该设备的厂家号和产品号。当找到后设备就和驱动匹配上了。
了解了前面的过程后,首先须要注册一个代表USB驱动的数据结构,并要明晰表示本驱动要支持的设备。在模块初始化函数module_init中,通过usb_register_driver注册一个usb驱动程序。USB核心将调用通过usb_register_driver注册的侦测反弹函数,在Linux中代表USB驱动的数据结构部分成员如下:
structusb_driver{
.name="alld";
.probe=ad_probe;
.disconnect=ad_disconnect;
.id_table=ad_usb_ids;
};
复制代码
该数据结构中name成员是代表该驱动的名称,该名称在USB驱动中必需要独一无二的,不能和别的驱动的名子重复,在取名字的时侯最好和模块名子相同。
成员probe()函数表针就是本章要实现的探求函数,该函数在本驱动和设备的厂家号和产品号相匹配后调用,作用是探求该驱动是否支持该设备,假如支持该设备的插口,这么在probe函数中调用usb_set_intfdata(structusb_interface*intf,void*data)函数,该函数中的第一个参数就是的驱动要支持的那种设备插口数据结构的表针,第二个参数是该驱动为了实现插口正常运行而分配的自己的数据结构。
usb_set_intfdata()的作用就是把插口和它的驱动要用到的数据结构关联上去。成功后返回0;若果不支持该设备这么返回-ENODEV。
函数probe()的参数usb_interface验证了前文所说的一个插口对应一个驱动,本文所涉及的设备都是单一插口的,因而没有太分辨插口和设备的差异,probe()的第二个参数usb_device_id数据结构就包含了上文提到的厂家号和产品号。它是设备的厂家号和产品号,而usb_driver的id_table是本驱动支持的所有设备的厂家号和产品号的列表。
成员disconnect函数表针指向的函数的作用是当设备已然移走或则模块被卸载时调用,主要就是处善后工作,比如早已注册的取消注册,早已分配的显存释放掉。
四、私有数据结构的设计
上文中提及probe()函数中要调用usb_set_intfdata()函数,该函数的第二个参数就是本文驱动程序要用到的私有数据结构。因为驱动程序是工作在ieee802.11合同层,ieee802.11为驱动程序提供了一个分配显存函数ieee80211_hw*ieee80211_alloc_hw(size_tpriv_data_len,conststructieee80211_ops*ops),该函数第一个参数是自己驱动程序中的私有数据结构的厚度,第二个参数是上文提到的指向驱动程序各个函数的数据结构的表针,正是在这儿把驱动程序的所有函数提供给ieee802.11合同层的。ieee80211_alloc_hw()函数是即分配了802.11合同层须要的显存结构,又顺便分配了驱动的私有数据结构,该函数分配的显存结构如右图所示。图中不仅驱动程序自己的私有数据结构,其他几个数据结构都是802.11合同层使用的数据结构。须要设计自己的私有数据结构,把这个私有数据结构具象成为设备,把和设备有关的参数都设计成为数据结构放在这个私有数据结构中,在编撰驱动程序的各个函数时,只要传递了私有数据结构的表针,才能找到所有关于设备的参数,但是它是全局的。
函数ieee80211_alloc_hw()成功后返回的是structieee80211_hw结构的表针,而该结构的priv指向了的私有数据结构。本文设计的私有数据结构如下:
structpriv_dev{
unsignedlongflags;
structusb_device*udev;
structusb_interface*intf;
structieee80211_hw*hw;
loff_tsavep;
charfw_name[64];
charpath[64];
u8*eeprom;structieee80211_supported_bandbands[IEEE80211_NUM_BANDS];
enumieee80211_bandcurr_band;
spinlock_tlist_lock;
structmutexlist_op,rw_lock;
inttimeout;
structlist_headcfmg_list[30];
u8bulk[BULKSIZE];
structconfig_msg*msg_fun[10];//recordconfig_msg()position
unsignedchar*skb_data,*skb_tail,*rx_skb_data,*rx_skb_tail;
structdata_queue*rx,*tx,*beacon;
structprob_descprobdesc;
structpriv_rate*privrate;
structpriv_channel*privchannel;
structprivdev_rx_statusrxstatus;
structpriv_intfprivintf;
u32parameter[PRIV_PARAMETER_SIZE];
intsparameter[PRIV_PARAMETER_SIZE];
structpstackps;
};
复制代码
其中成员udev、intf和hw成员都是指向下层的数据结构,有了这种成员后可以很便捷的找寻下层数据结构。成员savep是用于在读参数文件时记录参数文件的偏斜量,path成员是参数文件所在路径及参数文件的名子。成员fw_name是拿来储存设备固件程序的名子。成员eeprom只有在设备存在EEPROM的时侯才有意义,假如设备有EEPROM,这么本文的做法是分配一个和设备EEPROM一样大小的显存来储存EEPROM中所有的数据,这样的用处是当要从EEPROM中读数据时,就从显存读取,这样提高了读取的速率。这样也避免错误代码把EEPROM中的数据冲掉了。成员bands和curr_band记录本设备所在的频带及通道和速度列表,bands数据结构中存在指向通道和速度的表针成员。成员list_lock、list_op和rw_lock都是锁[29],list_lock是载流子锁,它用于短时间的锁,它的特征是在获取锁失败后不睡眠,而是始终循环查询锁的状态。List_op和rw_lock是互斥锁,它可以用于长时间锁,它的特征是获取锁不成功就阻塞在锁的数组上。成员timeout是和设备通讯的定时器时间,因为本驱动框架想要支持多个设备,这么它的值就从参数文件中读取。成员cfmg_list就是上文提到的参数链链头表针字段,structlist_head数据结构是Linux中的常用的单向数组结构,它的结构十分简单:
structlist_head{
structlist_head*next,*prev;
};
成员next指向下一个list_head数据结构,prev指向上一个list_head数据结构。这么怎样使用list_head呢?在使用时把list_head嵌入到寄主数据结构中,只要晓得list_head的地址,就可以算出寄主数据结构的地址。内核中给提供了list_entry(ptr,type,member)这个宏来估算寄主数据结构的地址linux windows,ptr就是寄主数据结构中list_head成员的地址,type是寄主数据结构的类型,member是list_head数据结构在寄主数据结构中的成员名子,在本文中若果晓得list_head的表针诸如head,这么config_msg的地址就是list_entry(head,structconfig_msg,list)。
五、操作函数集
当探求完成后,就要编撰驱动程序的打开、发送等函数。这种函数都要填充到下边structieee80211_ops数据结构中去:
structieee80211_ops{
int(*tx)(structieee80211_hw*hw,structsk_buff*skb);
int(*start)(structieee80211_hw*hw);
void(*stop)(structieee80211_hw*hw);
int(*add_interface)(structieee80211_hw*hw,
structieee80211_if_init_conf*conf);
void(*remove_interface)(structieee80211_hw*hw,
structieee80211_if_init_conf*conf);
int(*config)(structieee80211_hw*hw,u32changed);
void(*bss_info_changed)(structieee80211_hw*hw,
structieee80211_vif*vif,
structieee80211_bss_conf*info,
u32changed);
};
复制代码
这儿只列出了部份主要的函数,一个驱动程序不一定要把这个数据结构中的所有函数表针所指向的函数都实现了,这要依据具体设备的情况而定。其中tx函数表针是指向发送函数,start函数表针指向的是开始函数,config函数表针指向的是配置函数,stop函数是停止函数等等。当把这儿必需要实现的函数表针实现后,驱动程序即使写完了。
六、USB插口无线网卡数据的接收
与pci、pcmia等无线网卡不同,usb总线没有中断资源。因而usb无线网卡的数据接收不通过中断实现,而是在open函数通过主机主动查询是否有数据须要读取。
为此,在open函数中向usbcore发送一个读恳求的urb,致使网路数据到来时侯,主机才能接收到。
open反弹函数主要代码:
......
usb_fill_bulk_urb(dev->rx_urb,//构造读恳求的urb
dev->udev,
usb_rcvbulkpipe(dev->udev,6),//指定读得端点
dev->rx_skb->data,
512,//count
rx_complete,//读恳求的反弹函数
dev
);
if(result=usb_submit_urb(dev->urb,GFP_KERNEL))
将发送给kernel的usbcore
复制代码
读恳求完成时侯,read_bulk_callback函数将被内核调用,它构造一个skb_bufff数据结构来描述数据包,并调用netif_rx把数据包传给网路子系统,进而完成一次数据的接收过程。
七、USB插口无线网卡数据的发送
当网路子系统要发送一个数据时侯,下层合同会构造一个sk_buff来描述一个数据包,并调用驱动程序注册和实现的hard_start_xmit来发送数据包,因为该函数被调用时侯,网路子系统持有xmit_lock载流子锁,因而驱动程序毋须考虑设备写操作的同步问题。hard_start_xmit按照数据包的宽度linux安装usb网卡驱动,拆分成usb设备可以传输的宽度linux安装usb网卡驱动,之后构造相应地写恳求urb,发送至usbcore即可。
hard_start_xmit反弹函数的主要代码:
......
usb_fill_bulk_urb(dev->tx_urb,//构造写恳求的urb
dev->udev,
usb_sndbulkpipe(dev->udev,2),//指定写端点
skb->data,
512,//count
write_bulk_callback,//写恳求的反弹函数
dev
);
if(result=usb_submit_urb(dev->tx_urb,GFP_ATOMIC))
将发送给usbcore
复制代码
写恳求完成时侯,write_bulk_callback反弹函数将被调用,按照发送情况更新统计数据
八、设备的断掉
我们早已剖析了usb_driver结构的侦测函数,与设备侦测对应的是设备的断掉。设备断掉可以看做是设备侦测的逆过程,主要工作是释放驱动程序早已分配的系统资源。
设备断掉调用了usb_driver结构的disconnect(structusb_interface*)函数,函数首先通过调用usb_get_intfdata()获取相关资源,之后通过usb_set_intfdata(intf,NULL)将资源清零,并释放资源。
九、模块的卸载
与模块加载对应的是模块的卸载,module_exit函数首先调用usb_rtusb_exit()卸载网卡驱动程序,接着调用usb_deregister(&rtusb_driver)实现设备的注销。
十、IOCTL函数
Linux中要让网卡正常工作须要配置IP地址、SSID、工作频段、工作模式等,这种控制操作都是通过ifconfig和iwconfig调用驱动实现的IOCTL函数实现的。驱动程序通过IOCTL为应用程序提供了一些例如IO显存地址读写访问、配置空间寄存器读写访问、数据成员读写访问等函数,通过这种函数,应用程序就可以对设备进行相应地操作,其各类函数都是通过IOCTL命令实现的。应用程序将IOCTL命令将有关信息传递到驱动程序的内核空间,驱动程序再处理相应地操作。
比如该函数的原型:
rtxxx_ioctl(structnet_device*net_dev,structifreq*,intcmd)。
转自博客,博主zqixiao_09
无线网卡