Linux 驱动开发之USB设备分析8(基于Linux6.6)---USB无线网卡驱动介绍
一、概述
在 Linux 中,USB 无线网卡驱动的工作原理类似于其他 USB 设备的驱动,但由于无线网卡需要处理无线协议栈和网络接口的管理,驱动实现较为复杂。通常,USB 无线网卡驱动分为硬件接口驱动、无线协议栈驱动和网络接口驱动三部分。
1.1、USB 无线网卡驱动概述
USB 无线网卡驱动通常包含以下几个核心部分:
- USB 子系统驱动:负责检测、初始化 USB 无线网卡设备,并将其注册到系统中。
- 无线协议栈驱动:负责处理无线网卡的无线信号传输、接收、加密等操作。无线网卡的工作通常基于标准的无线协议栈(如 802.11)。
- 网络接口驱动:通过 Linux 的网络栈与系统通信,向上层应用提供网络接口(通常表现为一个
wlanX
接口)。
1.2、驱动模块
USB 无线网卡的驱动通常由以下几个模块组成:
1.USB 子系统模块
usbcore
:这是 Linux 系统中用于管理所有 USB 设备的核心模块。当你插入一个 USB 无线网卡时,usbcore
会识别该设备并根据设备的 ID 加载相应的驱动模块。
2.硬件驱动模块
-
设备专用驱动:每种 USB 无线网卡的硬件都可能采用不同的芯片,这些芯片的硬件操作需要专用的驱动程序。常见的芯片厂商包括 Atheros、Broadcom、Realtek、Ralink 等。例如,
rtl8187
和rtl8192cu
等驱动就是 Realtek 无线芯片的驱动。 -
无线芯片的驱动模块:这些驱动负责与硬件进行通信,通常与无线协议栈相结合,处理数据的发送和接收。此部分的驱动通常依赖于具体芯片的设计和功能。
3.无线协议栈
无线协议栈通常基于 IEEE 802.11 标准,它在硬件驱动和网络栈之间提供数据的封装与解封装:
mac80211
:Linux 中的无线通信框架,它负责 802.11 无线网络的协议实现,管理接入点扫描、连接、信道选择等工作。设备驱动通常通过mac80211
来实现无线通信。cfg80211
:是mac80211
的用户空间接口,允许用户空间工具(如iw
、ifconfig
)与内核进行交互,控制无线网络接口。
4.网络接口模块
网络接口模块用于将数据传输与接收与 Linux 网络栈连接起来,并创建网络接口(如 wlan0
或 wlan1
),这通常依赖于 netdevice
接口:
net_device
:在 Linux 中,网络接口通过net_device
结构来表示。无线网卡驱动需要通过它来实现数据包的接收和发送。
5.用户空间接口
iwconfig
、iw
、ifconfig
等工具:这些命令行工具通常用来配置和管理无线网络接口。它们通过与内核中的cfg80211
和mac80211
进行交互,来设置 SSID、密码、信道等信息。
1.3、USB 无线网卡工作流程
USB 无线网卡的工作流程包括从设备插入到数据发送和接收的多个步骤:
1.设备插入与驱动加载
- 当 USB 无线网卡插入时,
usbcore
会识别设备。 - 根据设备的 USB 设备 ID,Linux 系统会自动加载相应的硬件驱动模块。例如,如果是 Realtek 设备,可能会加载
rtl8192cu
驱动。 - 驱动程序加载后,设备会被注册到
mac80211
无线协议栈,并创建一个虚拟的网络接口(如wlan0
)。
2.无线信号的管理
- 驱动通过
mac80211
处理无线信号的收发。它负责将数据包从用户空间传递到硬件(通过无线芯片),同时接收来自硬件的信号。 - 驱动负责管理信号的加密、解密、数据帧的封装与解封装等。
- 无线网卡可以扫描可用的无线网络、连接到指定的 AP(接入点),并获取 IP 地址等网络信息。
3.网络接口的配置
- 用户可以通过
iwconfig
或nmcli
等命令工具配置无线接口,如连接到 Wi-Fi 网络。 - 系统会根据
mac80211
协议栈提供的接口,配置无线网络的参数,如 SSID、加密方式、密码等。 - 配置完成后,
wlan0
等网络接口就可以开始通过无线信道进行数据交换。
4.数据传输
- 网络数据通过
net_device
接口传递给无线网卡驱动。 - 驱动将数据封装为无线数据包,通过无线信号传输到目标接入点。
- 接收的数据通过
mac80211
进行解封装,传递到网络层(如 TCP/IP)。
1.4、无线网卡驱动的模块化与开源
- 开源无线驱动:许多无线网卡的 Linux 驱动是开源的,例如 Atheros、Realtek 和 Broadcom 的一些无线网卡都提供了开源驱动。Linux 内核中包含了一些无线驱动模块,如
ath9k
(Atheros)、b43
(Broadcom)等。 - 非开源驱动:某些无线网卡(特别是一些老旧的 Broadcom 网卡)可能需要专有驱动程序,这些驱动程序通常在某些发行版中作为额外的软件包提供(如
b43-fwcutter
)。
1.5、USB 无线网卡的常见驱动模块
rtl8187
:Realtek 8187 USB 无线网卡驱动,支持 802.11b/g 无线标准。rtl8192cu
:Realtek 8192cu USB 无线网卡驱动,支持 802.11n 标准。ath9k
:Atheros 设备的无线驱动,通常用于支持 802.11n 的设备。b43
:Broadcom 无线网卡的开源驱动,支持旧型号的 Broadcom 802.11g 设备。iwlwifi
:Intel 无线网卡驱动,支持 Intel 的 802.11a/b/g/n/ac 网卡。
1.6、说明
USB(Universal Serial Bus)总线又叫通用串行外部总线,它是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 -- 批量端点
主要用来传输批量信息的,批量信息就意味着大量的信息。U盘一般主要使用的就是批量端点。本文研究的USB无线网卡也是使用批量断点来传输数据的。发送和接收函数都是使用批量端点和USB设备传输数据的。
b -- 等时端点
主要用来传输等时信息的,主要用于传输实时性要求较高的信息,例如实时的音频、视频等信息。有代表性的USB设备是USB摄像头等。
在一个具体的USB设备中不要求一定都存在这4种类型的端点,例如U盘一般就只有批量端点和控制端点。在Linux内核中用来描述USB设备端点信息的数据结构如下:
include/uapi/linux/usb/ch9.h
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __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控制器、基带处理、调制解调器、功率放大器和收发器及天线等组成。
1. USB 接口
- 作用:作为无线网卡与计算机之间的物理连接点,USB 接口负责数据的传输和供电。USB 无线网卡通过 USB 接口与操作系统进行通信,完成网络数据的发送和接收。
- 特性:通常使用 USB 2.0 或 3.0 标准,提供足够的带宽来支持无线数据传输。通过 USB 接口,无线网卡可以方便地插拔,并且能够供电,免去外部电源的需求。
2. MAC 控制器(媒体接入控制器)
- 作用:MAC 控制器负责处理数据链路层(Layer 2)的任务,包括信道接入、数据封装和解封装、地址管理、错误检测和纠正等。它位于无线协议栈中,负责无线通信的协调。
- 特性:MAC 控制器通常基于 IEEE 802.11 无线局域网标准进行设计,确保无线数据能够可靠地传输。它在数据传输过程中起着关键作用,确保数据在无线信道上的有效传输。
3. 基带处理器
- 作用:基带处理器负责处理无线信号的基带部分,即解调接收到的信号和调制待发送的信号。它将来自 MAC 控制器的数据转换成可以通过无线信道传输的信号,或者将接收到的无线信号转换为网络数据。
- 特性:基带处理器根据无线协议的要求进行数据的调制和解调。它负责将数字信号转化为模拟信号,或者将接收到的模拟信号转换为数字信号,确保无线信号可以有效地传输。
4. 调制解调器(Modem)
- 作用:调制解调器负责将基带处理器处理后的数据转换成适合无线传输的高频信号,或将接收到的高频信号转换为数字数据。它承担了调制(发送数据)和解调(接收数据)过程。
- 特性:调制解调器通常与基带处理器紧密配合,使用不同的调制技术(例如 QAM、PSK 等)来实现数据的无线传输。它直接影响无线信号的传输质量和稳定性。
5. 功率放大器(PA)
- 作用:功率放大器负责增强发送信号的功率,使其能够覆盖更广的距离并确保数据能够穿透墙壁或障碍物到达接入点(AP)。同时,它也用于增强接收信号的灵敏度。
- 特性:功率放大器是无线网卡中的重要组件,它影响到无线信号的覆盖范围和传输速率。无线网卡的有效工作范围取决于功率放大器的性能。
6. 收发器(Transceiver)
- 作用:收发器是无线网卡中负责信号接收和发送的组件。它将无线信号发送至天线或从天线接收信号,并将接收到的信号传递给基带处理器进行解调处理。
- 特性:收发器通常集成了接收和发送模块,通过无线电频率信号(如 2.4 GHz 或 5 GHz)与无线网络进行通信。它是无线通信的核心组件之一。
7. 天线
- 作用:天线负责将电磁信号辐射到空中或接收来自空中的电磁波。在无线网卡中,天线是与外界通信的桥梁,负责无线信号的发送和接收。
- 特性:天线通常设计为外置或内置,外置天线可以提供更强的信号接收和传输能力,内置天线则具有更小的体积,适用于便携设备。天线的设计和位置对无线信号的质量和稳定性有很大影响。
USB接口无线网卡的硬件逻辑:
USB无线网卡的通道和速率是多个的,在发送和接收时通道和速率是可以变换的。在Linux中通道用如下数据结构表示:
include/net/cfg80211.h
struct ieee80211_channel {
enum nl80211_band band;
u32 center_freq;
u16 freq_offset;
u16 hw_value;
u32 flags;
int max_antenna_gain;
int max_power;
int max_reg_power;
bool beacon_found;
u32 orig_flags;
int orig_mag, orig_mpwr;
enum nl80211_dfs_state dfs_state;
unsigned long dfs_state_entered;
unsigned int dfs_cac_ms;
};
四、模块的加载
在编写USB无线网卡驱动函数之前,首先先了解一下设备在插入到USB接口到设备成功找到它自己的驱动这一过程。
1、获取设备一些信息,发生在USB核心
当把USB设备插到USB接口上后,USB主机控制器会检测到有设备插入USB接口了,Linux内核会给设备分配一个数据结构来代表这个设备。本文中涉及的硬件是USB设备,因此Linux会分配一个struct usb_device数据结构来代表该设备,该数据结构记录设备的一些属性及数据。并把该数据结构挂载到一个全局的USB设备链上。在这一期间主机通过0号端点(控制端点)得知了设备的一些信息,并知道了设备的厂家号和产品号。
2、找到匹配的驱动,发生在USB核心
然后到一个全局的USB驱动链上查找,看看哪个驱动程序支持的设备列表中有该设备的厂家号和产品号。当找到后设备就和驱动匹配上了。
了解了上面的过程后,首先需要注册一个代表USB驱动的数据结构,并要明确表示本驱动要支持的设备。在模块初始化函数module_init中,通过usb_register_driver注册一个usb驱动程序。USB核心将调用通过usb_register_driver注册的探测回调函数,在Linux中代表USB驱动的数据结构部分成员如下:
struct usb_driver{
.name="alld";
.probe=ad_probe;
.disconnect=ad_disconnect;
.id_table=ad_usb_ids;
};
该数据结构中name成员是代表该驱动的名称,该名称在USB驱动中必须要独一无二的,不能和别的驱动的名字重复,在起名字的时候最好和模块名字相同。
成员 probe()函数指针就是本章要实现的探索函数,该函数在本驱动和设备的厂家号和产品号相匹配后调用,作用是探索该驱动是否支持该设备,如果支持该设备的接口,那么在probe函数中调用usb_set_intfdata(struct usb_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_t priv_data_len,const struct ieee80211_ops *ops),该函数第一个参数是自己驱动程序中的私有数据结构的长度,第二个参数是上文提及的指向驱动程序各个函数的数据结构的指针,正是在这里把驱动程序的所有函数提供给ieee802.11协议层的。ieee80211_alloc_hw()函数是即分配了802.11协议层需要的内存结构,又顺便分配了驱动的私有数据结构,该函数分配的内存结构如下图所示。图中除了驱动程序自己的私有数据结构,其他几个数据结构都是802.11协议层使用的数据结构。需要设计自己的私有数据结构,把这个私有数据结构抽象成为设备,把和设备有关的参数都设计成为数据结构放到这个私有数据结构中,在编写驱动程序的各个函数时,只要传递了私有数据结构的指针,就能找到所有关于设备的参数,并且它是全局的。
函数ieee80211_alloc_hw()成功后返回的是struct ieee80211_hw结构的指针,而该结构的priv指向了的私有数据结构。私有数据结构如下:
struct priv_dev{
unsigned long flags;
struct usb_device *udev;
struct usb_interface *intf;
struct ieee80211_hw *hw;
loff_t savep;
char fw_name[64];
char path[64];
u8 *eeprom; struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS];
enum ieee80211_band curr_band;
spinlock_t list_lock;
struct mutex list_op,rw_lock;
int timeout;
struct list_head cfmg_list[30];
u8 bulk[BULKSIZE];
struct config_msg *msg_fun[10];//record config_msg() position
unsigned char *skb_data,*skb_tail,*rx_skb_data,*rx_skb_tail;
struct data_queue *rx,*tx,*beacon;
struct prob_desc probdesc;
struct priv_rate *privrate;
struct priv_channel *privchannel;
struct privdev_rx_status rxstatus;
struct priv_intf privintf;
u32 parameter[PRIV_PARAMETER_SIZE];
int sparameter[PRIV_PARAMETER_SIZE];
struct pstack ps;
};
其中成员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就是上文提及的参数链链头指针数组,struct list_head数据结构是Linux中的常用的双向链表结构,它的结构非常简单:
struct list_head {
struct list_head *next, *prev;
};
成员next指向下一个list_head数据结构,prev指向上一个list_head数据结构。那么如何使用list_head呢?在使用时把list_head嵌入到宿主数据结构中,只要知道list_head的地址,就可以算出宿主数据结构的地址。内核中给提供了list_entry(ptr,type,member)这个宏来计算宿主数据结构的地址,ptr就是宿主数据结构中list_head成员的地址,type是宿主数据结构的类型,member是list_head数据结构在宿主数据结构中的成员名字,在本文中如果知道list_head的指针例如head,那么config_msg的地址就是list_entry(head,struct config_msg,list)。
六、操作函数集
当探索完成后,就要编写驱动程序的打开、发送等函数。这些函数都要填充到下面 struct ieee80211_ops数据结构中去:
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw,
struct ieee80211_tx_control *control,
struct sk_buff *skb);
int (*start)(struct ieee80211_hw *hw);
void (*stop)(struct ieee80211_hw *hw);
#ifdef CONFIG_PM
int (*suspend)(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan);
int (*resume)(struct ieee80211_hw *hw);
void (*set_wakeup)(struct ieee80211_hw *hw, bool enabled);
#endif
int (*add_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
int (*change_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
enum nl80211_iftype new_type, bool p2p);
void (*remove_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
int (*config)(struct ieee80211_hw *hw, u32 changed);
void (*bss_info_changed)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info,
u64 changed);
void (*vif_cfg_changed)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
u64 changed);
void (*link_info_changed)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info,
u64 changed);
int (*start_ap)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
void (*stop_ap)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
u64 (*prepare_multicast)(struct ieee80211_hw *hw,
struct netdev_hw_addr_list *mc_list);
void (*configure_filter)(struct ieee80211_hw *hw,
unsigned int changed_flags,
unsigned int *total_flags,
u64 multicast);
void (*config_iface_filter)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
unsigned int filter_flags,
unsigned int changed_flags);
int (*set_tim)(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
bool set);
int (*set_key)(struct ieee80211_hw *hw, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct ieee80211_key_conf *key);
void (*update_tkip_key)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_key_conf *conf,
struct ieee80211_sta *sta,
u32 iv32, u16 *phase1key);
void (*set_rekey_data)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct cfg80211_gtk_rekey_data *data);
void (*set_default_unicast_key)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif, int idx);
int (*hw_scan)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_scan_request *req);
void (*cancel_hw_scan)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
int (*sched_scan_start)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct cfg80211_sched_scan_request *req,
struct ieee80211_scan_ies *ies);
int (*sched_scan_stop)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
void (*sw_scan_start)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
const u8 *mac_addr);
void (*sw_scan_complete)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
int (*get_stats)(struct ieee80211_hw *hw,
struct ieee80211_low_level_stats *stats);
void (*get_key_seq)(struct ieee80211_hw *hw,
struct ieee80211_key_conf *key,
struct ieee80211_key_seq *seq);
int (*set_frag_threshold)(struct ieee80211_hw *hw, u32 value);
int (*set_rts_threshold)(struct ieee80211_hw *hw, u32 value);
int (*sta_add)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
int (*sta_remove)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
...
};
一个驱动程序不一定要把这个数据结构中的所有函数指针所指向的函数都实现了,这要根据具体设备的情况而定。其中tx函数指针是指向发送函数,start函数指针指向的是开始函数,config函数指针指向的是配置函数,stop函数是停止函数等等。
七、USB接口无线网卡数据的接收
与pci、pcmia等无线网卡不同,usb总线没有中断资源。因此usb无线网卡的数据接收不通过中断实现,而是在open函数通过主机主动查询是否有数据需要读取。
因此,在open函数中向usb core发送一个读请求的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的usb core
}
读请求完成时候,read_bulk_callback函数将被内核调用,它构造一个skb_bufff数据结构来描述数据包,并调用netif_rx把数据包传给网络子系统,从而完成一次数据的接收过程。
八、USB接口无线网卡数据的发送
当网络子系统要发送一个数据时候,上层协议会构造一个sk_buff来描述一个数据包,并调用驱动程序注册和实现的hard_start_xmit来发送数据包,由于该函数被调用时候,网络子系统持有xmit_lock自旋锁,因此驱动程序不必考虑设备写操作的同步问题。hard_start_xmit根据数据包的长度,拆分成usb设备可以传输的长度,然后构造相应地写请求urb,发送至usb core即可。
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))
{
将发送给usb core
}
写请求完成时候,write_bulk_callback回调函数将被调用,根据发送情况更新统计数据。
九、设备的断开
已经分析了usb_driver结构的探测函数,与设备探测对应的是设备的断开。设备断开可以看做是设备探测的逆过程,主要工作是释放驱动程序已经分配的系统资源。
设备断开调用了usb_driver结构的disconnect(struct usb_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(struct net_device * net_dev,struct ifreq * ,int cmd)
十二、总结
Linux 系统中 USB 无线网卡的驱动分为多个模块,每个模块负责不同的功能。从硬件设备的初始化,到数据的传输,每个环节都经过严格的模块划分。下面是对这些模块的总结,以及数据传输的流程图。
12.1、驱动模块的总结
1.设备初始化模块(初始化阶段)
这个模块负责在 USB 无线网卡插入系统时进行设备的初始化,并注册到 Linux 内核中。它由以下部分组成:
usbcore
:USB 子系统,负责管理 USB 设备的检测和加载相应的驱动程序。- 设备识别:当插入设备时,
usbcore
会根据设备的 USB ID,加载对应的硬件驱动模块,如rtl8192cu
(Realtek 无线芯片)或ath9k
(Atheros 无线芯片)。 - 驱动初始化:设备驱动程序会初始化硬件,设置无线芯片的工作模式,并将设备注册到 Linux 的网络栈中。
主要功能:
- 识别和注册 USB 设备。
- 加载适当的硬件驱动模块。
- 设置无线网卡的基本工作状态。
2.上层软件接口模块
该模块是用户空间与内核之间的接口,允许用户配置和管理无线网卡。它包括:
mac80211
:Linux 内核的无线协议栈框架,负责管理无线信号的收发、加密解密等。cfg80211
:提供给用户空间的无线网卡管理接口,允许通过命令行工具配置网络。- 网络管理工具:如
iwconfig
、iw
、nmcli
等,它们通过cfg80211
与内核交互,配置 SSID、密码、信道等信息。
主要功能:
- 允许用户管理和配置无线网卡。
- 提供与内核通信的接口,控制网卡的工作状态。
3.数据传输模块
数据传输模块负责处理无线网卡的数据包的收发,确保数据从用户空间到硬件,以及从硬件到网络栈的传输。其组成包括:
- 数据传输路径:当用户向网络接口发送数据时,数据会通过
net_device
结构传递给无线网卡驱动。 - 协议栈处理:
mac80211
负责无线信号的协议处理,将数据转换为无线信号。 - 硬件驱动:硬件驱动模块(如
rtl8192cu
)负责将数据通过无线信道传输到接入点,或者接收从接入点发送来的数据。
主要功能:
- 负责无线数据的封装、加密与发送。
- 接收无线数据,解密并传输到上层网络栈。
4.卸载模块(卸载阶段)
当 USB 无线网卡驱动不再需要时,系统会卸载驱动程序,释放硬件资源。卸载流程包括:
- 卸载驱动:当用户断开 USB 无线网卡或禁用网络接口时,系统会卸载相应的驱动模块。
- 资源清理:释放硬件相关资源,关闭与无线网卡的通信。
主要功能:
- 卸载硬件驱动模块。
- 清理硬件资源,关闭网络接口。
12.2、Linux USB 无线网卡驱动流程图
+----------------------------------------------------------+
| 设备插入USB端口 |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| USB 子系统(usbcore) |
| - 检测到设备,并加载相应的硬件驱动模块(如 rtl8192cu) |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| 硬件驱动初始化模块 |
| - 初始化无线网卡硬件(如配置芯片、信道等) |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| 无线协议栈(mac80211) |
| - 管理无线信号的收发、加密解密、信道切换等 |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| 用户空间接口(cfg80211) |
| - 提供配置接口,如 `iwconfig`, `iw`, `nmcli` |
| - 配置 SSID、密码、连接管理等 |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| 网络接口模块(net_device) |
| - 接收来自网络层的数据,发送到无线网卡 |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| 数据传输模块(硬件信号收发) |
| - 数据从上层网络栈传输到无线网卡,并通过无线信道发送 |
| - 或者接收无线信号,解封装后传输到上层网络栈 |
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| 设备断开或卸载驱动 |
| - 卸载无线网卡驱动并释放硬件资源 |
+----------------------------------------------------------+
12.3、数据传输流程
-
设备插入与初始化:
- USB 无线网卡插入时,
usbcore
会识别并加载对应的驱动模块(如rtl8192cu
)。 - 驱动初始化并注册到系统中。
- USB 无线网卡插入时,
-
上层软件配置:
- 用户通过
iwconfig
、iw
等工具配置无线网卡,设置 SSID、密码、信道等参数。 - 配置通过
cfg80211
和mac80211
传递给内核,网络接口如wlan0
被创建。
- 用户通过
-
数据传输:
- 当上层应用通过网络接口发送数据时,数据通过
net_device
传递给驱动模块。 - 驱动模块通过
mac80211
处理数据的加密、封装等,并将其转换为无线信号。 - 无线信号通过 USB 无线网卡传输到接入点或目标设备。
- 当上层应用通过网络接口发送数据时,数据通过
-
接收数据:
- 从无线网络接收到的数据通过硬件驱动解密、解封装后传输到上层网络栈。
- 数据到达用户空间应用。
-
卸载与清理:
- 当无线网卡设备被拔出或禁用时,系统会卸载驱动,释放所有资源,并关闭网络接口。