以 linux-4.19-rc2 為例,平台為 TP-Link WDR4300。程式碼以 linux-4.19-rc2 為準,有特別標示者以 OpenWrt/LEDE 為準。
1. ath9k 驅動程式註冊點:ath9k/init.c#ath9k_init(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 25 26 27 |
static int __init ath9k_init(void) { int error; error = ath_pci_init(); if (error < 0) { pr_err(“No PCI devices found, driver not installed\n”); error = –ENODEV; goto err_out; } error = ath_ahb_init(); if (error < 0) { error = –ENODEV; goto err_pci_exit; } dmi_check_system(ath9k_quirks); return 0; err_pci_exit: ath_pci_exit(); err_out: return error; } module_init(ath9k_init); |
主要有三件事情,(1) 初始化 PCI 匯流排、(2) 初始化 AHB 匯流排、(3) 初始化 quirks:
1 2 3 4 5 6 7 8 9 10 11 12 |
static int __init ath9k_init(void) { // 初始化 PCI 匯流排 error = ath_pci_init(); // 初始化 AHB 匯流排 error = ath_ahb_init(); // 初始化 quirks 設定 dmi_check_system(ath9k_quirks); return 0; } |
2. 初始化 PCI 匯流排:ath9k/pci.c#ath_pci_init(void)
1 2 3 4 |
int ath_pci_init(void) { return pci_register_driver(&ath_pci_driver); } |
這邊傳入 struct pci_driver ath_pci_driver
當參數,ath_pci_driver
定義為:
1 2 3 4 5 6 7 |
static struct pci_driver ath_pci_driver = { .name = “ath9k”, .id_table = ath_pci_id_table, .probe = ath_pci_probe, .remove = ath_pci_remove, .driver.pm = ATH9K_PM_OPS, }; |
struct pci_driver
本身定義了一些 PCI driver 的 method, 同時有 struct device_driver
給 device driver-model 使用 (The struct device_driver
structure, which represents one driver capable of handling certain devices on a certain bus)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
struct pci_driver { struct list_head node; const char *name; const struct pci_device_id *id_table; /* Must be non-NULL for probe to be called */ int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */ void (*remove)(struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */ int (*suspend)(struct pci_dev *dev, pm_message_t state); /* Device suspended */ int (*suspend_late)(struct pci_dev *dev, pm_message_t state); int (*resume_early)(struct pci_dev *dev); int (*resume) (struct pci_dev *dev); /* Device woken up */ void (*shutdown) (struct pci_dev *dev); int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* On PF */ const struct pci_error_handlers *err_handler; const struct attribute_group **groups; struct device_driver driver; struct pci_dynids dynids; }; |
將 ath_pci_driver
傳入 pci_register_driver 後,就會進行 PCI 註冊的部份:
1 2 3 4 5 6 7 |
/* Proper probing supporting hot-pluggable devices */ int __must_check __pci_register_driver(struct pci_driver *, struct module *, const char *mod_name); /* pci_register_driver() must be a macro so KBUILD_MODNAME can be expanded */ #define pci_register_driver(driver) \ __pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) |
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 |
/** * __pci_register_driver – register a new pci driver * @drv: the driver structure to register * @owner: owner module of drv * @mod_name: module name string * * Adds the driver structure to the list of registered drivers. * Returns a negative value on error, otherwise 0. * If no error occurred, the driver remains registered even if * no device was claimed during registration. */ int __pci_register_driver(struct pci_driver *drv, struct module *owner, const char *mod_name) { /* initialize common driver fields */ drv->driver.name = drv->name; drv->driver.bus = &pci_bus_type; drv->driver.owner = owner; drv->driver.mod_name = mod_name; drv->driver.groups = drv->groups; spin_lock_init(&drv->dynids.lock); INIT_LIST_HEAD(&drv->dynids.list); /* register with core */ return driver_register(&drv->driver); } EXPORT_SYMBOL(__pci_register_driver); |
主要是初始化 device driver-model 中的 struct device_driver
的部份,可以看到初始化最重要的部份是 drv->driver.bus
,將 struct bus_type
設定為 pci_bus_type (struct bus_type
在 device driver-model 中代表著使用的 bus 型態,這邊是 PCI bus)。
將 struct device_driver
初始化完成後,傳入 device_register
進行驅動程式註冊:
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 |
/** * driver_register – register driver with bus * @drv: driver to register * * We pass off most of the work to the bus_add_driver() call, * since most of the things we have to do deal with the bus * structures. */ int driver_register(struct device_driver *drv) { int ret; struct device_driver *other; if (!drv->bus->p) { pr_err(“Driver ‘%s’ was unable to register with bus_type ‘%s’ because the bus was not initialized.\n”, drv->name, drv->bus->name); return –EINVAL; } if ((drv->bus->probe && drv->probe) || (drv->bus->remove && drv->remove) || (drv->bus->shutdown && drv->shutdown)) printk(KERN_WARNING “Driver ‘%s’ needs updating – please use “ “bus_type methods\n”, drv->name); other = driver_find(drv->name, drv->bus); if (other) { printk(KERN_ERR “Error: Driver ‘%s’ is already registered, “ “aborting…\n”, drv->name); return –EBUSY; } ret = bus_add_driver(drv); if (ret) return ret; ret = driver_add_groups(drv, drv->groups); if (ret) { bus_remove_driver(drv); return ret; } kobject_uevent(&drv->p->kobj, KOBJ_ADD); return ret; } EXPORT_SYMBOL_GPL(driver_register); |
接著進入各種 calling 最後調用 ath_pci_probe:bus_add_driver -> driver_attach -> __driver_attach -> driver_probe_device -> really_probe -> dev->bus->probe(dev)
(pci_bus_type.probe -> pci_device_probe) -> __pci_device_probe -> pci_call_probe -> local_pci_probe -> pci_drv->probe
(ath_pci_driver.probe -> ath_pci_probe) -> ath_pci_probe
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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
tatic int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct ath_softc *sc; struct ieee80211_hw *hw; u8 csz; u32 val; int ret = 0; char hw_name[64]; int msi_enabled = 0; if (pcim_enable_device(pdev)) return –EIO; ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); if (ret) { pr_err(“32-bit DMA not available\n”); return ret; } ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); if (ret) { pr_err(“32-bit DMA consistent DMA enable failed\n”); return ret; } /* * Cache line size is used to size and align various * structures used to communicate with the hardware. */ pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &csz); if (csz == 0) { /* * Linux 2.4.18 (at least) writes the cache line size * register as a 16-bit wide register which is wrong. * We must have this setup properly for rx buffer * DMA to work so force a reasonable value here if it * comes up zero. */ csz = L1_CACHE_BYTES / sizeof(u32); pci_write_config_byte(pdev, PCI_CACHE_LINE_SIZE, csz); } /* * The default setting of latency timer yields poor results, * set it to the value used by other systems. It may be worth * tweaking this setting more. */ pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xa8); pci_set_master(pdev); /* * Disable the RETRY_TIMEOUT register (0x41) to keep * PCI Tx retries from interfering with C3 CPU state. */ pci_read_config_dword(pdev, 0x40, &val); if ((val & 0x0000ff00) != 0) pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); ret = pcim_iomap_regions(pdev, BIT(0), “ath9k”); if (ret) { dev_err(&pdev->dev, “PCI memory region reserve error\n”); return –ENODEV; } ath9k_fill_chanctx_ops(); hw = ieee80211_alloc_hw(sizeof(struct ath_softc), &ath9k_ops); if (!hw) { dev_err(&pdev->dev, “No memory for ieee80211_hw\n”); return –ENOMEM; } SET_IEEE80211_DEV(hw, &pdev->dev); pci_set_drvdata(pdev, hw); sc = hw->priv; sc->hw = hw; sc->dev = &pdev->dev; sc->mem = pcim_iomap_table(pdev)[0]; sc->driver_data = id->driver_data; if (ath9k_use_msi) { if (pci_enable_msi(pdev) == 0) { msi_enabled = 1; dev_err(&pdev->dev, “Using MSI\n”); } else { dev_err(&pdev->dev, “Using INTx\n”); } } if (!msi_enabled) ret = request_irq(pdev->irq, ath_isr, IRQF_SHARED, “ath9k”, sc); else ret = request_irq(pdev->irq, ath_isr, 0, “ath9k”, sc); if (ret) { dev_err(&pdev->dev, “request_irq failed\n”); goto err_irq; } sc->irq = pdev->irq; ret = ath9k_init_device(id->device, sc, &ath_pci_bus_ops); if (ret) { dev_err(&pdev->dev, “Failed to initialize device\n”); goto err_init; } sc->sc_ah->msi_enabled = msi_enabled; sc->sc_ah->msi_reg = 0; ath9k_hw_name(sc->sc_ah, hw_name, sizeof(hw_name)); wiphy_info(hw->wiphy, “%s mem=0x%lx, irq=%d\n”, hw_name, (unsigned long)sc->mem, pdev->irq); return 0; err_init: free_irq(sc->irq, sc); err_irq: ieee80211_free_hw(hw); return ret; } |
其中大略分為以下幾項:(1) 設定 PCI 相關事物 (2) 初始化 ieee80211_hw、ath_softc (3) 初始化 ath9k 實體。
(1) 設定 PCI 相關事物:
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 |
// 有關 dma_mask 以及 consistent_dma_mask 的差異 // 請見:http://lwn.net/2001/0712/a/dma-interface.php3 ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); // 設定 cache line size pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &csz) if (csz == 0) { csz = L1_CACHE_BYTES / sizeof(u32); pci_write_config_byte(pdev, PCI_CACHE_LINE_SIZE, csz); } // 設定 latency timer,default 的太爛 pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xa8); // 設定 bus-mastering pci_set_master(pdev); // Disable RETRY_TIMEOUT 暫存器 來讓 PCI Tx 與 C3 CPU state 重嘗試 pci_read_config_dword(pdev, 0x40, &val); if ((val & 0x0000ff00) != 0) pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); // 基於 mask 請求一個 PCI Base Address Registers regions ret = pcim_iomap_regions(pdev, BIT(0), “ath9k”); |
(2) 初始化 ieee80211_hw、ath9k_softc
1 2 3 4 5 6 7 8 9 10 |
// 初始化 ath9k_ops 中的 chanctx 相關 methods // About chanctx, ref: https://www.kernel.org/doc/html/v4.9/80211/mac80211.html ath9k_fill_chanctx_ops(); // 初始化 ieee80211_hw // ———————> 這是 hw.priv 的長度 hw = ieee80211_allow_hw(sizeof(struct ath_softc), &ath9k_ops); ... SET_IEEE80211_DEV(hw, &pdev->dev); pci_set_drvdata(pdev, hw); |
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 |
struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len, const struct ieee80211_ops *ops, const char *requested_name) { struct ieee80211_local *local; int priv_size, i; struct wiphy *wiphy; bool use_chanctx; // 確認 ops methods 以及 channel context operations 是否存在 ... /* Ensure 32-byte alignment of our private data and hw private data. * We use the wiphy priv data for both our ieee80211_local and for * the driver’s private data * * In memory it’ll be like this: * * +————————-+ * | struct wiphy | * +————————-+ * | struct ieee80211_local | * +————————-+ * | driver’s private data | * +————————-+ * */ // 建立新的 wiphy priv_size = ALIGN(sizeof(*local), NETDEV_ALIGN) + priv_data_len; wiphy = wiphy_new_nm(&mac80211_config_ops, priv_size, requested_name); ... local = wiphy_priv(wiphy); ... // 嵌套實例回 local->hw local->hw.wiphy = wiphy local->hw.priv = (char *)local + ALIGN(sizeof(*local), NETDEV_ALIGN); // 放置 operations local->ops = ops; local->use_chanctx = use_chanctx; // 設定 local->hw 以及 wiphy 預設參數 ... // 初始化 workqueue INIT_DELAYED_WORK(&local->scan_work, ieee80211_scan_work); INIT_WORK(&locak->restart_work, ieee80211_restart_work); ... // 初始化 tasklet tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending, (unsigned long)local); tasklet_init(&local->tasklet, ieee80211_tasklet_handler, (unsigned long) local); ... return &local->hw; err_free: wiphy_free(wiphy); return NULL; } |
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 |
... // 設定 sc sc = hw->priv; sc->hw = hw; sc->dev = &pdev->dev; sc->mem = pcim_iomap_table(pdev)[0]; sc->driver_data = id->driver_data; // 設定中斷方式 if (ath9k_use_msi) { if (pci_enable_msi(pdev) == 0) { msi_enabled = 1; dev_err(&pdev->dev, “Using MSI\n”); } else { dev_err(&pdev->dev, “Using INTx\n”); } } if (!msi_enabled) ret = request_irq(pdev->irq, ath_isr, IRQF_SHARED, “ath9k”, sc); else ret = request_irq(pdev->irq, ath_isr, 0, “ath9k”, sc); if (ret) { dev_err(&pdev->dev, “request_irq failed\n”); goto err_irq; } sc->irq = pdev->irq; // 初始化 ath9k device ret = ath9k_init_device(id->device, sc, &ath_pci_bus_ops); ... sc->sc_ah->msi_enabled = msi_enabled; sc->sc_ah->msi_reg = 0; ath9k_hw_name(sc->sc_ah, hw_name, sizeof(hw_name)); wiphy_info(hw->wiphy, “%s mem=0x%lx, irq=%d\n”, hw_name, (unsigned long)sc->mem, pdev->irq); return 0; ... |
(3) 初始化 ath9k 實體
1 2 3 4 5 6 7 8 9 10 11 12 13 |
int ath9k_init_device(u16 devid, struct ath_softc *sc, const struct ath_bus_ops *bus_ops) { struct ieee80211_hw *hw = sc->hw; struct ath_common *common; struct ath_hw *ah; int error = 0; struct ath_regulatory *reg; /* Bring up device */ // 初始化 sc error = ath9k_init_softc(devid, sc, bus_ops); ... |
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 |
static int ath9k_init_softc(u16 devid, struct ath_softc *sc, const struct ath_bus_ops *bus_ops) { struct ath_hw *ah = NULL; struct ath9k_hw_capabilities *pCap; struct ath_common *common; // 初始化 ath_hw ah = devm_kzalloc(sc->dev, sizeof(struct ath_hw), GFP_KERNEL); ... // Platform quirks 設定 ath9k_init_pcoem_platform(sc); ... // 初始化 lock 以及 interrupt / becon tasklet spin_lock_init(&common->cc_lock); spin_lock_init(&sc->intr_lock); spin_lock_init(&sc->sc_serial_rw); spin_lock_init(&sc->sc_pm_lock); spin_lock_init(&sc->chan_lock); mutex_init(&sc->mutex); tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc); tasklet_init(&sc->bcon_tasklet, ath9k_beacon_tasklet, (unsigned long)sc); // 初始化 work 以及 channel context ... // 初始化所支援的硬體功能 ret = ath9k_hw_init(ah); ret = ath9k_init_queues(sc); ret = ath9k_init_btcoex(sc); ret = ath9k_cmn_init_channels_rates(common); ret = ath9k_init_p2p(sc); // 初始化剩餘的東西 ath9k_cmn_init_crypto(sc->sc_ah); ath9k_init_misc(sc); ath_chanctx_init(sc); ath9k_offchannel_init(sc); if (common->bus_ops->aspm_init) common->bus_ops->aspm_init(common); return 0; ... } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* Setup TX DMA */ error = ath_tx_init(sc, ATH_TXBUF); if (error != 0) goto deinit; /* Setup RX DMA */ error = ath_rx_init(sc, ATH_RXBUF); if (error != 0) goto deinit; // 將硬體註冊到 mac80211 error = ieee80211_register_hw(hw); // 初始化 debug 功能 error = ath9k_init_debug(ah); ... } |
3. dmi_check_system
ref: https://www.fsl.cs.sunysb.edu/kernel-api/re748.html
driver 會需要因為不同的硬體而有不同的設定,在 Linux 之中,可以透過 quirks 來做設定。dmi_check_system
則是用來確認 system DMI (Desktop Management Information) 的函式。用途就是,會 iterate 過整個傳入的 blacklist,如果其中有對應到的 DMI 的話,就會觸發 blacklist 中的 callback 函數。
以 ath9k 為例,這個地方是用來設定不同的中斷方式,預設的中斷方式是 INTx,某些硬體 (這邊都是 dell 的硬體) 則會選擇使用 MSI:
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 |
// .callback 的 methods static int __init set_use_msi(const struct dmi_system_id *dmi) { ath9k_use_msi = 1; return 1; } static const struct dmi_system_id ath9k_quirks[] __initconst = { { .callback = set_use_msi, .ident = “Dell Inspiron 24-3460”, .matches = { DMI_MATCH(DMI_SYS_VENDOR, “Dell Inc.”), DMI_MATCH(DMI_PRODUCT_NAME, “Inspiron 24-3460”), }, }, { .callback = set_use_msi, .ident = “Dell Vostro 3262”, .matches = { DMI_MATCH(DMI_SYS_VENDOR, “Dell Inc.”), DMI_MATCH(DMI_PRODUCT_NAME, “Vostro 3262”), }, }, { .callback = set_use_msi, .ident = “Dell Inspiron 3472”, .matches = { DMI_MATCH(DMI_SYS_VENDOR, “Dell Inc.”), DMI_MATCH(DMI_PRODUCT_NAME, “Inspiron 3472”), }, }, { .callback = set_use_msi, .ident = “Dell Vostro 15-3572”, .matches = { DMI_MATCH(DMI_SYS_VENDOR, “Dell Inc.”), DMI_MATCH(DMI_PRODUCT_NAME, “Vostro 15-3572”), }, }, { .callback = set_use_msi, .ident = “Dell Inspiron 14-3473”, .matches = { DMI_MATCH(DMI_SYS_VENDOR, “Dell Inc.”), DMI_MATCH(DMI_PRODUCT_NAME, “Inspiron 14-3473”), }, }, {} }; static int __init ath9k_init(void) { ... dmi_check_system(ath9k_quirks); return 0; } |
至此 ath9k 完成初始化,將硬體註冊到了 mac80211 之中。
Leave a Reply