mt7921-usb驱动注册分析

作者:taikulawo创建时间:2023-11-03字数统计:4646预计阅读需要12分钟

#Kernel

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

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

pci驱动为 mt7921e USB接口 MT7921 外观 usb

PCI接口的MT7921网卡外观

pci-e

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

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

驱动使用kernel module_usb_driver进行注册

#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 开始驱动初始化

#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中注册为无线网卡。

call stack

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

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

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网卡,分析驱动调用栈

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

mt7921 probe调用栈/也是USB module调用栈

0 comments
Anonymous
Markdown is supported

Be the first guy leaving a comment!