0%

mt7921-usb驱动注册分析

以联发科 mt7921 无线网卡驱动为例

kernel mt7921/ 下有pci,usb接口的驱动,usb接口驱动入口是usb.c。手里有mt7921芯片的USB无线网卡,对应的驱动为mt7921u

pci驱动为 mt7921e
USB接口 MT7921 外观

PCI接口的MT7921网卡外观

驱动采用USB总线注册到kernel,使用kernel提供的 module_usb_driver(mt7921u_driver);,调用 usb_register_driver进行注册

kernel驱动采用callback式,,linux为各种接口设备提供helper

驱动使用kernel module_usb_driver进行注册

1
2
3
#define module_usb_driver(__usb_driver) \
module_driver(__usb_driver, usb_register, \
usb_deregister)

经过usb的一系列宏替换,module_init调用usb的通用init函数 usb_register 初始化,最终调用 struct usb_driver#probe 开始驱动初始化

1
2
3
4
5
6
7
8
9
10
11
12
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

usb 是接口类型,usb接口探测完成后,驱动调用kernel 80211一系列helper,在kernel中注册为无线网卡。

驱动的80211回调。有数据需要发送kernel则调用.tx

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
const struct ieee80211_ops mt7921_ops = {
.tx = mt792x_tx,
.start = mt7921_start,
.stop = mt7921_stop,
.add_interface = mt7921_add_interface,
.remove_interface = mt792x_remove_interface,
.config = mt7921_config,
.conf_tx = mt792x_conf_tx,
.configure_filter = mt7921_configure_filter,
.bss_info_changed = mt7921_bss_info_changed,
.start_ap = mt7921_start_ap,
.stop_ap = mt7921_stop_ap,
.sta_state = mt7921_sta_state,
.sta_pre_rcu_remove = mt76_sta_pre_rcu_remove,
.set_key = mt7921_set_key,
.sta_set_decap_offload = mt7921_sta_set_decap_offload,
#if IS_ENABLED(CONFIG_IPV6)
.ipv6_addr_change = mt7921_ipv6_addr_change,
#endif /* CONFIG_IPV6 */
.ampdu_action = mt7921_ampdu_action,
.set_rts_threshold = mt7921_set_rts_threshold,
.wake_tx_queue = mt76_wake_tx_queue,
.release_buffered_frames = mt76_release_buffered_frames,
.channel_switch_beacon = mt7921_channel_switch_beacon,
.get_txpower = mt76_get_txpower,
.get_stats = mt792x_get_stats,
.get_et_sset_count = mt792x_get_et_sset_count,
.get_et_strings = mt792x_get_et_strings,
.get_et_stats = mt792x_get_et_stats,
.get_tsf = mt792x_get_tsf,
.set_tsf = mt792x_set_tsf,
.get_survey = mt76_get_survey,
.get_antenna = mt76_get_antenna,
.set_antenna = mt7921_set_antenna,
.set_coverage_class = mt792x_set_coverage_class,
.hw_scan = mt7921_hw_scan,
.cancel_hw_scan = mt7921_cancel_hw_scan,
.sta_statistics = mt792x_sta_statistics,
.sched_scan_start = mt7921_start_sched_scan,
.sched_scan_stop = mt7921_stop_sched_scan,
CFG80211_TESTMODE_CMD(mt7921_testmode_cmd)
CFG80211_TESTMODE_DUMP(mt7921_testmode_dump)
#ifdef CONFIG_PM
.suspend = mt7921_suspend,
.resume = mt7921_resume,
.set_wakeup = mt792x_set_wakeup,
.set_rekey_data = mt7921_set_rekey_data,
#endif /* CONFIG_PM */
.flush = mt792x_flush,
.set_sar_specs = mt7921_set_sar_specs,
.remain_on_channel = mt7921_remain_on_channel,
.cancel_remain_on_channel = mt7921_cancel_remain_on_channel,
.add_chanctx = mt7921_add_chanctx,
.remove_chanctx = mt7921_remove_chanctx,
.change_chanctx = mt7921_change_chanctx,
.assign_vif_chanctx = mt792x_assign_vif_chanctx,
.unassign_vif_chanctx = mt792x_unassign_vif_chanctx,
.mgd_prepare_tx = mt7921_mgd_prepare_tx,
.mgd_complete_tx = mt7921_mgd_complete_tx,
};

追踪 .tx调用路径

收包送到kernel协议栈路径

调用路径有两个,pci 接口走的DMA,而USB接口则是kthread polling

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
int __mt76_worker_fn(void *ptr)
{
struct mt76_worker *w = ptr;

while (!kthread_should_stop()) {
set_current_state(TASK_INTERRUPTIBLE);

if (kthread_should_park()) {
kthread_parkme();
continue;
}

if (!test_and_clear_bit(MT76_WORKER_SCHEDULED, &w->state)) {
schedule();
continue;
}

set_bit(MT76_WORKER_RUNNING, &w->state);
set_current_state(TASK_RUNNING);
// kthread 持续调用 mt76u_rx_worker
w->fn(w);
cond_resched();
clear_bit(MT76_WORKER_RUNNING, &w->state);
}

return 0;
}

// https://github.com/torvalds/linux/blob/v6.6/drivers/net/wireless/mediatek/mt76/usb.c#L628
static void mt76u_rx_worker(struct mt76_worker *w)
{
struct mt76_usb *usb = container_of(w, struct mt76_usb, rx_worker);
struct mt76_dev *dev = container_of(usb, struct mt76_dev, usb);
int i;

rcu_read_lock();
// 遍历每个队列处理rx数据
mt76_for_each_q_rx(dev, i)
mt76u_process_rx_queue(dev, &dev->q_rx[i]);
rcu_read_unlock();
}

qemu绑定mt7921u网卡,分析驱动调用栈

1
qemu-system-x86_64 --kernel ./arch/x86/boot/bzImage -initrd ./rootfs.cpio -device e1000,netdev=eth0 -netdev user,id=eth0,hostfwd=tcp::5555-:22,net=192.168.76.0/24,dhcpstart=192.168.76.9  -append "nokaslr console=ttyS0" -S -nographic -gdb tcp::1234 -virtfs local,path=/,security_model=none,mount_tag=guestroot -usb -device usb-host,vendorid=0x0e8d,productid=0x7961