Linux USB 驱动开发实例(七)—— 基于USB 总线的无线网卡浅析
回顾一下USB的相关知识
USB(Universal Serial Bus)总线又叫通用串行外部总线,
它是20世纪90年代发展起来的。USB接口现在得到了广泛的应用和普及,现在的PC机中都带有大量的USB接口。它最大的特点就是方便通用、支持热插拔并且可以在一个接口上插上多个设备。当设备用电量小的时候,它还可以充当电源。它的众多优点使得它得到了广泛的应用。
在PC机器内部有个USB中央控制器,这个中央控制器负责管理插到USB接口上的设备。
当主机要向设备发送或接受数据时,都是向USB中央控制器发出命令,USB设备不具备主动与主机通信的能力。编写USB设备驱动不用考虑申请设备地址空间,因为USB中央控制器会给设备分配一个设备号,这个设备号就代表这个设备。
USB设备和USB中央控制器之间的通信是通过端点来完成的。
端点的职能有点类似一栋大楼的传达室。
例如每个楼层都有一个传达室,当要访问5楼的10号房间时,那就是向5号端点发起对话,并提供偏移量,也就10号房间。
USB接口的端点按传输信息的类型分为以下4种:
控制端点
主要用来传输控制信息的,例如配置设备时发出的控制信息。
控制端点一般都是双向,既可以输入又可以输出。
其他端点的输出方向一般是单向的,要么是输入,要么是输出的。
这里是站在主机的角度来谈论输入输出的。中断端点
主要用来传输中断信息的,由于USB设备是受USB中央控制器管理的,
因此USB设备没有向主机发出中断的能力,并且USB设备不能主动向主机发出请求,只有主机可以向USB设备发出命令请求,
因此所谓的中断是指主机周期性的查询USB设备。批量端点
主要用来传输批量信息的,批量信息就意味着大量的信息。
U盘一般主要使用的就是批量端点。
本文研究的USB无线网卡也是使用批量断点来传输数据的。
发送和接收函数都是使用批量端点和USB设备传输数据的。等时端点
主要用来传输等时信息的,主要用于传输实时性要求较高的信息,
例如实时的音频、视频等信息。有代表性的USB设备是USB摄像头等。
在一个具体的USB设备中不要求一定都存在这4种类型的端点,例如U盘一般就只有批量端点和控制端点。在Linux内核中用来描述USB设备端点信息的数据结构如下:
struct usb_endpoint_descriptor { __u8 bLength; //描述本数据结构共有多少字节,因为后两个成员是针对音频设备的,如果不是音频设备则可以没有后两个成员。__u8 bDescriptorType; //描述本数据结构要描述的类型,这里是描述端点的,在内核中0x05就代表端点 __u8 bEndpointAddress; //包含端点号和输出方向,bit0-bit3表示的是端点号 (一个USB设备最多只能有不超过16个端点),bits8是代表传输方向的(1:输入,读设备;0输出,写设备)__u8 bmAttributes; //该端点的类型 ,控制,中断,批量,等时__le16 wMaxPacketSize; //该端点一次可以传输的最多字节数 (如果要传输的数据大于这个数字,那就要分多次传输)__u8 bInterval; //代表的是该端点希望主机轮询自己的时间间隔__u8 bRefresh; //针对音频设备的成员_u8 bSynchAddress; //针对音频设备的成员
} __attribute__ ((packed));
该数据结构最后的 attribute((packed)) 代表在分配该数据结构时数据成员之间不要为了内存对齐而留下空隙,
例如,有这样一个数据结构的相邻两个成员类型是u8 和u16,在一般情况下u8后面要空一个字节,然后才是u16成员,
如果有上面attribute的要求后,在u8后面就不要留空间,紧接着就是u16成员。
在内核中有很多需要访问设备的数据结构都有这样的要求,因为在一个设备中一般没有内存对齐的要求。
一、USB设备驱动程序的构成
设备的探测
用于检查传递给探测函数的设备信息,确认驱动程序是否适合该设备。数据的发送和接收
负责主机到设备的发送和设备到主机的数据接收。设备断开
当设备断开时候,模块负责清除和该设备关联的所有资源。模块的加载和卸载
用于加载和卸载usb接口的无线网卡驱动程序。
二、USB无线网卡的构成
USB无线网卡主要由USB接口、MAC控制器、基带处理、调制解调器、功率放大器和收发器及天线等组成。
MAC控制器是核心部件,它负责从主机读取数据并发送出去,或者接收数据并发送给主机等。它负责通道选择、速率选择、加密解密等等的控制。
固件存储区是用来存储MAC控制器要运行的微码。固件是一种经过编译的可执行代码,一般是由设备的芯片来执行的。
帧缓存就是用来存储数据的暂时场所。
EEPROM是否有没有 要看具体的设备,有的设备是没有的,EEPROM一般都存放一些本设备的一些参数,例如本设备的MAC地址,本设备在家族产品中的型号等等。
基带处理和ADC、DAC是数模拟转换的功能部分。要发送的数据或者接收的模拟信号在这个地方进行转换。
收发器的功能类似调制解调器,收发器内部有个功率放大器,把弱信号增强到一定的强信号,收发器还负责滤波等工作。
天线系统就是负责把数据通过天线发送或接收。天线的作用是使传输距离更远。
USB接口无线网卡的硬件逻辑:
USB无线网卡的通道和速率是多个的,在发送和接收时通道和速率是可以变换的。
在Linux中通道用如下数据结构表示:
struct ieee80211_channel {enum ieee80211_band band; u16 center_freq; u16 hw_value; u32 flags; int max_antenna_gain; int max_power; bool beacon_found; u32 orig_flags; int orig_mag, orig_mpwr;
};
三、模块的加载
在编写USB无线网卡驱动函数之前,首先先了解一下设备在插入到USB接口到设备成功找到它自己的驱动这一过程。
获取设备一些信息,发生在USB核心
当把USB设备插到USB接口上后, USB主机控制器会检测到有设备插入USB接口了,Linux内核会给设备分配一个数据结构来代表这个设备。
本文中涉及的硬件是USB设备,因此Linux会分配一个struct usb_device数据结构来代表该设备,该数据结构记录设备的一些属性及数据。
并把该数据结构挂载到一个全局的USB设备链上。在这一期间主机通过0号端点(控制端点)得知了设备的一些信息,并知道了设备的厂家号和产品号。找到匹配的驱动,发生在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]; //上文提及的参数链链头指针数组, 是Linux中的常用的双向链表结构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;
}; 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{int (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);int (*start)(struct ieee80211_hw *hw);void (*stop)(struct ieee80211_hw *hw);int (*add_interface)(struct ieee80211_hw *hw,struct ieee80211_if_init_conf *conf);void (*remove_interface)(struct ieee80211_hw *hw,struct ieee80211_if_init_conf *conf);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,u32 changed);
};
这里只列举了部分主要的函数,一个驱动程序不一定要把这个数据结构中的所有函数指针所指向的函数都实现了,这要根据具体设备的情况而定。
其中tx函数指针是指向发送函数,start函数指针指向的是开始函数,config函数指针指向的是配置函数,stop函数是停止函数等等。
当把这里必须要实现的函数指针实现后,驱动程序就算写完了。
六、USB接口无线网卡数据的接收
与pci、pcmia等无线网卡不同,usb总线没有中断资源。
因此usb无线网卡的数据接收不通过中断实现,而是在open函数通过主机主动查询是否有数据需要读取。
因此,在open函数中向usb core发送一个读请求的urb,使得网络数据到来时候,主机能够接收到。
open回调函数主要代码:
......
usb_fill_bulk_urb(dev->rx_urb,//构造读请求的urbdev->udev,usb_rcvbulkpipe(dev->udev,6),//指定读得端点dev->rx_skb->data,512,//countrx_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,//构造写请求的urbdev->udev,usb_sndbulkpipe(dev->udev,2),//指定写端点skb->data,512,//countwrite_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 驱动开发实例 (三)—— 基于USB总线的无线网卡浅析》
Linux USB 驱动开发实例(七)—— 基于USB 总线的无线网卡浅析相关推荐
- Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结
http://blog.csdn.net/zqixiao_09/article/details/51057086 设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程 ...
- linux 下usb驱动开发,LINUX_ARM下的USB驱动开发.pdf
LINUX_ARM下的USB驱动开发 CN 431258 / TP 计算机工程与科学 2006年第 28卷第 3期 ISSN 1007130X COM PU TER EN GIN EER IN G & ...
- Linux设备驱动开发详解 第3版 (即 Linux设备驱动开发详解 基于最新的Linux 4 0内核 )前言
Linux从未停歇脚步.Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核.做技术,从来没有终南捷径,拼的就是坐冷板凳的 ...
- Linux USB 驱动开发实例 (三)—— 基于USB总线的无线网卡浅析
回顾一下USB的相关知识 USB(Universal Serial Bus)总线又叫通用串行外部总线,它是20世纪90年代发展起来的.USB接口现在得到了广泛的应用和普及,现在的PC机中都带有大量 ...
- LINUX USB驱动开发(2)-USB驱动体系分析
一.日常USB现象: 1.将USB设备接入电脑上,右下角弹出"发现android phone" 2.跳出对话框.提示你安装驱动程序 问题1:既然没有安装驱动,为何能知道设备是&qu ...
- Linux USB 驱动开发(一)—— USB设备基础概念
http://blog.csdn.net/zqixiao_09/article/details/50984074 在终端用户看来,USB设备为主机提供了多种多样的附加功能,如文件传输,声音播放等,但对 ...
- Linux USB 驱动开发(一)—— USB设备基础概念【转】
本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50984074 在终端用户看来,USB设备为主机提供了多种多样的附加功能,如文件传输,声音 ...
- Linux USB 驱动开发(二)—— USB 驱动几个重要数据结构
前面我们学习了USB 驱动的一个描述符,下面来学习 USB 驱动的几个重要数据结构 一.struct usb_interface 接口函数 [cpp] view plaincopy struct u ...
- Linux USB 驱动开发实例(二)—— USB 鼠标驱动注解及测试
参考2.6.14版本中的driver/usb/input/usbmouse.c.鼠标驱动可分为几个部分:驱动加载部分.probe部分.open部分.urb回调函数处理部分. 一.驱动加载部分 [cpp ...
最新文章
- 一键生成CSDN文章的思维导图目录
- java画满天星_java_java实现的满天星效果实例,本文实例讲述了java实现满天星 - phpStudy...
- python 调用linux命令-Python 调用系统命令
- 原生态纯JavaScript 100大技巧大收集---你值得拥有
- Android基站定位
- 视觉硬件 - 相机 镜头 选型
- iOS手势操作简介(三)
- ltsc系统激活_WIN10_X64企业版LTSC 电脑公司装机版 202008
- UPS不间断电源的种类有哪些 常见的3类UPS电源
- Flex与.NET互操作(十三):FluorineFx.Net实现视频录制与视频回放
- 性能测试之LoardRunner 手动关联二
- c#图像处理、图片拼接、图片裁剪、图片缩放、图上添加形状、屏幕截图、图片反色、改变图片色彩度全解
- java 认证考试题_2017年Java认证考试真题及答案
- 光线微弯传感器matlab仿真,光纤压力与位移传感特性
- 将Spring Boot Web应用部署到Tomcat服务器
- html怎么设置空链接,HTML a标签之空链接
- 零基础:21天搞定Python分布爬虫
- 2022东北四省赛 F. Tree Path (树剖 + 线段树维护堆)
- R语言在逻辑回归中求R square R方
- 新年新玩法,数组“招婿”:老许,你要老婆不要?