Aic8800
从0开始的Linux驱动学习:以AIC8800驱动为例
rwnx_main.c:
1
2
3
4
5
6
7
8
9
10
module_init(rwnx_mod_init);
module_exit(rwnx_mod_exit);
MODULE_FIRMWARE(RWNX_CONFIG_FW_NAME);
MODULE_DESCRIPTION(RW_DRV_DESCRIPTION);
MODULE_VERSION(RWNX_VERS_MOD);
MODULE_AUTHOR(RW_DRV_COPYRIGHT " " RW_DRV_AUTHOR);
MODULE_LICENSE("GPL");
C 语言程序的入口是 main() 函数,但在 Linux 内核驱动里,没有 main 函数。内核怎么知道哪段代码是入口? 就是靠 module_init 和 module_exit。一般来说,一个驱动只有一个 module_init 和 module_exit。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static int __init rwnx_mod_init(void)
{
RWNX_DBG(RWNX_FN_ENTRY_STR);
rwnx_print_version();
AICWFDBG(LOGINFO, "RELEASE DATE:%s \r\n", RELEASE_DATE);
rwnx_init_cmd_array();
sema_init(&aicwf_deinit_sem, 1);
atomic_set(&aicwf_deinit_atomic, 1);
init_completion(&hostif_register_done);
aicsmac_driver_register();
#ifdef AICWF_SDIO_SUPPORT
if ((wait_for_completion_timeout(&hostif_register_done, msecs_to_jiffies(REGISTRATION_TIMEOUT)) == 0)) {
AICWFDBG(LOGERROR, "register_driver timeout or error\n");
aicwf_sdio_exit();
return -ENODEV;
}
#endif /* AICWF_SDIO_SUPPORT */
#ifdef AICWF_USB_SUPPORT
//aicwf_usb_exit();
#endif /*AICWF_USB_SUPPORT */
#ifdef AICWF_PCIE_SUPPORT
return rwnx_platform_register_drv();
#else
return 0;
#endif
}
__init是一个宏,用来标识这个函数是linux驱动启动函数。爱科微在写这套驱动时采用了高度抽象的设计。aic8800 这颗芯片在出厂时,可能有好几种硬件封装形态:
- 焊接在低成本开发板上(走 SDIO 总线)
- 做成外插的无线网卡、接收器(走 USB 总线)
- 做成高端的 M.2 网卡插在电脑上(走 PCIe 总线)
原厂工程师不想为这三种接口写三套驱动,于是把核心的 Wi-Fi 协议处理(rwnx层)抽离出来,而把总线接口做成可配置的。在编译时,通过 Makefile 传入不同的宏,就能让驱动自动变为 SDIO、USB 或 PCIe 驱动。
当驱动刚加载时,多核 CPU 可能会产生混乱,为了确保安全退出初始化一个互斥信号量。在Linux内核中,信号量(semaphore)是一种用于进程或线程间同步的机制。信号量可以是二值的,即只有两个值,通常用于实现互斥锁(mutex),也可以有多个值,用于控制对共享资源的并发访问。信号量的一个关键特性是,如果一个任务尝试获取一个已经被占用的信号量,它会进入睡眠状态,直到该信号量被释放。例如:
1
2
3
#include <linux/semaphore.h>
struct semaphore sem;
sema_init(&sem, 1); // 初始化信号量,计数值设置为1
这里的sema_init(&aicwf_deinit_sem, 1);,是为了如果将来用户疯狂输入 rmmod 卸载驱动,或者系统关机触发卸载,能确保驱动卸载、释放内存在同一时刻只能有一个线程在干,不允许多个线程同时去释放内存。
在多核 CPU 里,如果两个核心同时去改一个普通的全局变量 int a,会导致数据错乱。而内核提供了 atomic_t(原子类型),它利用了 CPU 硬件层面的锁总线指令,确保对这个变量的读写是“不可分割”的绝对安全。atomic_set函数用于将原子类型变量的值设置为指定的整数值。原子操作需要硬件支持,因此与体系结构相关,通常用于实现资源计数。
在 Linux 内核中,completion 是一种非常轻量级的线程同步机制。简单来说,它的核心作用就是:让一个线程停下来死等,直到另一个线程通知某件事已经做完了,才继续往下走。init_completion(&hostif_register_done);就是在做等待初始化。
1
2
3
4
5
6
7
8
9
10
11
12
static void aicsmac_driver_register(void)
{
#ifdef AICWF_SDIO_SUPPORT
aicwf_sdio_register();
#endif
#ifdef AICWF_USB_SUPPORT
aicwf_usb_register();
#endif
#ifdef AICWF_PCIE_SUPPORT
aicwf_pcie_register();
#endif
}
由于我前面一直在看基于AIC8800的USB无线网卡驱动部分,所以这里直接看aicwf_usb_register():
1
2
//aicwf_usb.h
extern void aicwf_usb_register(void);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static struct usb_driver aicwf_usbdrvr = {
.name = KBUILD_MODNAME,
.probe = aicwf_usb_probe,
.disconnect = aicwf_usb_disconnect,
.id_table = aicwf_usb_id_table,
.suspend = aicwf_usb_suspend,
.resume = aicwf_usb_resume,
.reset_resume = aicwf_usb_reset_resume,
#ifdef ANDROID_PLATFORM
.supports_autosuspend = 1,
#else
.supports_autosuspend = 0,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
.disable_hub_initiated_lpm = 1,
#endif
};
void aicwf_usb_register(void)
{
if (usb_register(&aicwf_usbdrvr) < 0) {
usb_err("usb_register failed\n");
}
}
在include/linux/usb.h中,Linux定义了usb注册机制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* struct usb_driver - 向 usbcore 标识 USB 接口驱动
* @name: 驱动名称,在 USB 驱动中应该是唯一的,并且通常与模块名相同。
* @probe: 被调用来判断该驱动是否愿意管理设备上的特定接口。
* 如果愿意,probe 返回零,并使用 usb_set_intfdata() 将驱动相关的数据与接口关联。
* 它也可以使用 usb_set_interface() 来指定合适的备用设置(altsetting)。
* 如果不愿意管理该接口,返回 -ENODEV;如果发生了真正的 I/O 错误,返回相应的负 errno 值。
* @disconnect: 当接口不再可访问时被调用,通常因为其设备已经(或正在)被断开连接,
* 或者驱动模块正在被卸载。
* @unlocked_ioctl: 供希望通过 "usbfs" 文件系统与用户空间通信的驱动使用。
* 这让设备能够提供向用户空间暴露信息的方法,无论它们在文件系统中其他地方
* 是否(或是否不)出现。
* @suspend: 当设备将要被系统挂起时调用,无论是系统休眠还是运行时挂起上下文。
* 在系统休眠上下文中返回值将被忽略,因此如果挂起失败,不要尝试继续使用设备。
* 相反,让 resume 或 reset_resume 例程从失败中恢复。
* @resume: 当设备被系统恢复时调用。
* @reset_resume: 当被挂起的设备被复位而不是恢复时调用。
* @pre_reset: 当设备即将被复位时由 usb_reset_device() 调用。
* 该例程必须等待直到驱动对该设备没有活跃的 URB 才能返回,
* 并且在 post_reset 方法被调用之前不能再提交任何 URB。
* @post_reset: 设备复位之后由 usb_reset_device() 调用。
* @shutdown: 在系统关机时调用,使设备静止。
* @id_table: USB 驱动使用 ID 表来支持热插拔。
* 使用 MODULE_DEVICE_TABLE(usb,...) 导出此表。这个字段必须设置,
* 否则你的驱动的 probe 函数将永远不会被调用。
* @dev_groups: 绑定到驱动后,将添加到设备的属性组。
* @dynids: 内部使用,用于保存为此驱动动态添加的设备 ID 列表。
* @driver: 驱动模型的核心驱动结构。
* @no_dynamic_id: 如果设为 1,USB 核心将阻止为此驱动添加动态 ID,
* 即不创建对应的 sysfs 文件。
* @supports_autosuspend: 如果设为 0,USB 核心将不允许绑定到此驱动的接口进行自动挂起。
* @soft_unbind: 如果设为 1,USB 核心在调用驱动的 disconnect 方法之前,
* 不会杀掉 URB 也不会禁用端点。
* @disable_hub_initiated_lpm: 如果设为 1,USB 核心将不允许集线器在空闲超时发生时
* 发起低功耗链路状态转换。设备发起的 USB 3.0 链路 PM 仍然允许。
*
* USB 接口驱动必须提供 name、probe() 和 disconnect() 方法,以及一个 id_table。
* 其他驱动字段是可选的。
*
* id_table 用于热插拔。它包含一组描述符,并且可以为每个条目关联特定的数据。
* 该表同时被用户态和内核态的热插拔支持使用。
*
* probe() 和 disconnect() 方法在允许睡眠的上下文中被调用,但不应滥用这一特权。
* 连接设备的大部分工作应在设备打开时完成,并在最后一次关闭时撤销。
* disconnect 代码需要处理与 open() 和 close() 方法的并发问题,
* 以及强制所有未决 I/O 请求完成(通过必要时断开它们,并阻塞直到断开完成)。
*/
struct usb_driver {
const char *name;
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);
int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,
void *buf);
int (*suspend) (struct usb_interface *intf, pm_message_t message);
int (*resume) (struct usb_interface *intf);
int (*reset_resume)(struct usb_interface *intf);
int (*pre_reset)(struct usb_interface *intf);
int (*post_reset)(struct usb_interface *intf);
void (*shutdown)(struct usb_interface *intf);
const struct usb_device_id *id_table;
const struct attribute_group **dev_groups;
struct usb_dynids dynids;
struct device_driver driver;
unsigned int no_dynamic_id:1;
unsigned int supports_autosuspend:1;
unsigned int disable_hub_initiated_lpm:1;
unsigned int soft_unbind:1;
};
/*
* 在 module_init()/module_exit() 中使用这些函数,
* 并且不要忘记 MODULE_DEVICE_TABLE(usb, ...)
*/
extern int usb_register_driver(struct usb_driver *, struct module *,
const char *);
/* 使用宏定义以避免包含链来获取 THIS_MODULE 及相关宏 */
#define usb_register(driver) \
usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
probe是整个驱动最核心的入口之一。 当用户把 AIC8800 USB 网卡插到电脑上时,USB 核心层发现该设备的 ID 匹配成功,就会调用这个 aicwf_usb_probe 函数。在这个函数里,驱动会进行内存分配、初始化网络设备、准备好接收和发送数据。
1.空间申请
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int aicwf_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
int ret = 0;
struct usb_device *usb = interface_to_usbdev(intf);
struct aicwf_bus *bus_if = NULL;
struct device *dev = NULL;
struct aicwf_rx_priv* rx_priv = NULL;
struct aic_usb_dev *usb_dev = NULL;
#ifdef CONFIG_USB_TX_AGGR
struct aicwf_tx_priv *tx_priv = NULL;
#endif
usb_dev = kzalloc(sizeof(struct aic_usb_dev), GFP_ATOMIC);
if (!usb_dev) {
return -ENOMEM;
}
usb_dev->udev = usb;
usb_dev->dev = &usb->dev;
usb_set_intfdata(x, usb_dev);
usb_set_intfdata(intf, usb_dev):这把新申请的自定义数据 usb_dev 挂靠到了 Linux 标准的 usb_interface 结构体中。这样以后在拔出网卡(disconnect 阶段)时,驱动能随时把它取出来并释放。
2.芯片型号匹配
1
2
3
4
5
6
7
ret = aicwf_usb_chipmatch(usb_dev, id->idVendor, id->idProduct);
if (ret < 0) {
AICWFDBG(LOGERROR, "%s pid:0x%04X vid:0x%04X unsupport\n",
__func__, id->idVendor, id->idProduct);
goto out_free_bus;
}
3.解析USB传输端点
1
2
3
4
5
6
7
8
9
10
11
ret = aicwf_parse_usb(usb_dev, intf);
if (ret) {
AICWFDBG(LOGERROR, "aicwf_parse_usb err %d\n", ret);
goto out_free;
}
ret = aicwf_usb_init(usb_dev);
if (ret) {
AICWFDBG(LOGERROR, "aicwf_usb_init err %d\n", ret);
goto out_free;
}
aicwf_parse_usb 负责去扫描 USB 设备的描述符,找出哪些是用于接收网络数据的批量输入端点,哪些是用于发送网络数据的批量输出端点。随后 aicwf_usb_init 内部会去初始化 Linux 的 URB(USB Request Block),准备好数据的物理传输载体。
4.总线与数据收发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
bus_if = kzalloc(sizeof(struct aicwf_bus), GFP_ATOMIC);
if (!bus_if) {
ret = -ENOMEM;
goto out_free_usb;
}
dev = usb_dev->dev;
bus_if->dev = dev;
usb_dev->bus_if = bus_if;
bus_if->bus_priv.usb = usb_dev;
dev_set_drvdata(dev, bus_if);
bus_if->ops = &aicwf_usb_bus_ops;
rx_priv = aicwf_rx_init(usb_dev);
if(!rx_priv) {
AICWFDBG(LOGERROR, "rx init failed\n");
ret = -1;
goto out_free_bus;
}
usb_dev->rx_priv = rx_priv;
#ifdef CONFIG_USB_TX_AGGR
tx_priv = aicwf_tx_init(usb_dev);
if(!tx_priv) {
usb_err("tx init fail\n");
goto out_free_bus;
}
usb_dev->tx_priv = tx_priv;
aicwf_frame_queue_init(&tx_priv->txq, 8, TXQLEN);
spin_lock_init(&tx_priv->txqlock);
spin_lock_init(&tx_priv->txdlock);
#endif
bus_if:AIC 的驱动架构支持多种接口。这里抽象出一个 bus_if 并绑定 aicwf_usb_bus_ops,使得上层的无线网络层不需要关心底层到底是 USB 还是 SDIO。
rx_init 与 tx_init:初始化网卡的接收和发送队列。
CONFIG_USB_TX_AGGR:这是一个条件编译宏。如果开启了 TX Aggregation(发送聚合),驱动会把多个小的网络数据包打包成一个大包一次性发给 USB,这能大幅提升 Wi-Fi 的吞吐量。里面还初始化了自旋锁(spin_lock_init)防止多核多线程冲突。
5.固件下载、总线启动、开机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ret = aicwf_bus_init(0, dev);
if (ret < 0) {
AICWFDBG(LOGERROR, "aicwf_bus_init err %d\n", ret);
goto out_free_bus;
}
ret = aicwf_bus_start(bus_if);
if (ret < 0) {
AICWFDBG(LOGERROR, "aicwf_bus_start err %d\n", ret);
goto out_free_bus;
}
ret = aicwf_rwnx_usb_platform_init(usb_dev);
if (ret < 0) {
AICWFDBG(LOGERROR, "aicwf_rwnx_usb_platform_init err %d\n", ret);
goto out_free_bus;
}
aicwf_hostif_ready();
#ifdef CONFIG_GPIO_WAKEUP
rwnx_register_hostwake_irq(usb_dev->dev);
#endif
return 0;
aicwf_bus_start 会让网卡开始监听 USB 总线,准备接收数据。
aicwf_rwnx_usb_platform_init:负责向网卡芯片下载固件,并初始化底层的 mac80211/cfg80211。
aicwf_hostif_ready():正式通知系统网卡的主机接口已准备好,可以开始创建 wlan0 网卡接口了。
6.异常回滚
1
2
3
4
5
6
7
8
9
10
11
out_free_bus:
aicwf_bus_deinit(dev);
kfree(bus_if);
out_free_usb:
aicwf_usb_deinit(usb_dev);
out_free:
usb_err("failed with errno %d\n", ret);
kfree(usb_dev);
usb_set_intfdata(intf, NULL);
return ret;
}
如果在前边的步骤比如初始化 TX 失败了,代码就会通过 goto 跳到对应的标签。前边申请了什么,这里就倒着释放什么,保证失败退出时不会出现内存泄漏、系统死机。