This is the second post in a series about iwlwifi
the linux intel wireless controller driver. Unlucky(or luck) me, this driver crashes randomly on my machine. I decided to debug the crash before giving up and bump up kernel and modules version. iwlwifi
is basically PCIe driver but it also interacts with network stack and network configuration stack.
PCIe driver Link to heading
Starting with PCIe side, Drivers have to register with the PCIe subsystem and the PCIe core will iterate devices and match drivers to the devices.
iwlwifi registers the driver with pci_register_driver
int __must_check iwl_pci_register_driver(void)
{
int ret;
ret = pci_register_driver(&iwl_pci_driver);
if (ret)
pr_err("Unable to initialize PCI module\n");
return ret;
}
probe
is the most important one as it’s called on every device enumerated.
static struct pci_driver iwl_pci_driver = {
.name = DRV_NAME,
.id_table = iwl_hw_card_ids,
.probe = iwl_pci_probe,
.remove = iwl_pci_remove,
.driver.pm = IWL_PM_OPS,
};
There is also pci ops that iwl wraps with iwl_trans_ops
.
static const struct iwl_trans_ops trans_ops_pcie = {
IWL_TRANS_COMMON_OPS,
.start_hw = iwl_trans_pcie_start_hw,
.fw_alive = iwl_trans_pcie_fw_alive,
.start_fw = iwl_trans_pcie_start_fw,
.stop_device = iwl_trans_pcie_stop_device,
.send_cmd = iwl_pcie_enqueue_hcmd,
.tx = iwl_trans_pcie_tx,
.reclaim = iwl_txq_reclaim,
.txq_disable = iwl_trans_pcie_txq_disable,
.txq_enable = iwl_trans_pcie_txq_enable,
.txq_set_shared_mode = iwl_trans_pcie_txq_set_shared_mode,
.wait_tx_queues_empty = iwl_trans_pcie_wait_txqs_empty,
.freeze_txq_timer = iwl_trans_txq_freeze_timer,
.block_txq_ptrs = iwl_trans_pcie_block_txq_ptrs,
#ifdef CONFIG_IWLWIFI_DEBUGFS
.debugfs_cleanup = iwl_trans_pcie_debugfs_cleanup,
#endif
};
More about PCIe side in separate posts.
Configuration path Link to heading
The configuration path starts with user space and ends with iwlwifi
.
User land -> nl80211 -> cfg80211 -> mac80211 -> iwlwifi
- User land talks to the kernel through Netlink sockets (implmeneted in
nl80211
). - nl80211 interfaces with
cfg80211
layer - cfg80211 then passes it down to
mac80211
.mac80211
registers with cfg80211 usingcfg80211_ops
iwlwifi
registers with mac80211 usingieee80211_ops
From nl80211 to iwlwifi, the files are as follow
./net/wireless/nl80211.c
./net/wireless/core.c
cfg80211_ops ./net/mac80211/cfg.c
./net/mac80211
./drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c ieee80211_ops
./net/wireless/intel/iwlwifi
Let’s trace one configuration from netlink down to iwlwifi, nl80211_new_interface
is one of the configuration APIs defined in NL80211 netlink. It is defined in net/wireless/nl80211.c
.
It calls _nl80211_new_interface
static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
{
...
ret = _nl80211_new_interface(skb, info);
}
which calls rdev_add_virtual_intf
ending up at rdev->ops->add_virtual_intf
static inline struct wireless_dev
*rdev_add_virtual_intf(struct cfg80211_registered_device *rdev, char *name,
unsigned char name_assign_type,
enum nl80211_iftype type,
struct vif_params *params)
{
struct wireless_dev *ret;
trace_rdev_add_virtual_intf(&rdev->wiphy, name, type);
ret = rdev->ops->add_virtual_intf(&rdev->wiphy, name, name_assign_type,
type, params);
trace_rdev_return_wdev(&rdev->wiphy, ret);
return ret;
}
ere we jump to mac80211
layer asadd_virtual_intf
is part of cfg80211_ops which is defined ./net/mac80211/cfg.c
const struct cfg80211_ops mac80211_config_ops = {
.add_virtual_intf = ieee80211_add_iface,
.del_virtual_intf = ieee80211_del_iface,
In ieee80211_if_add
, ieee80211_dataif_ops is registered
int ieee80211_if_add(struct ieee80211_local *local, const char *name,
unsigned char name_assign_type,
struct wireless_dev **new_wdev, enum nl80211_iftype type,
struct vif_params *params)
{
eventually ieee80211_open
is called which starts the chain that calls the registered methods from iwlwifi
.
In net/mac80211/iface.c
, ieee80211_open
is registered.
static const struct net_device_ops ieee80211_dataif_ops = {
.ndo_open = ieee80211_open,
.ndo_stop = ieee80211_stop,
static int ieee80211_open(struct net_device *dev)
{
int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
{
In net/mac80211/driver-ops.c
, drv_add_interface
is eventually called
int drv_add_interface(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata)
{
...
ret = local->ops->add_interface(&local->hw, &sdata->vif);
And add_interface
is defined drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
const struct ieee80211_ops iwl_mvm_hw_ops = {
...
.add_interface = iwl_mvm_mac_add_interface,
Data path Link to heading
For data path (tx, rx), It again starts with user land and ends all the way down at iwlwifi
User land -> Socket layer -> Network Stack (TCP/IP) -> Network layer -> mac80211 -> iwlwifi
I will start at the interface between network layer and mac80211 interacts. in net/mac80211/iface.c
, net_devince_ops
is defined and registered with Network layer. on transmit path, ndo_start_xmit
is called.
static const struct net_device_ops ieee80211_dataif_ops = {
.ndo_open = ieee80211_open,
.ndo_stop = ieee80211_stop,
.ndo_uninit = ieee80211_uninit,
.ndo_start_xmit = ieee80211_subif_start_xmit,
.ndo_set_rx_mode = ieee80211_set_multicast_list,
.ndo_set_mac_address = ieee80211_change_mac,
.ndo_select_queue = ieee80211_netdev_select_queue,
.ndo_get_stats64 = ieee80211_get_stats64,
};
ieee80211_subif_start_xmit
calls __ieee80211_subif_start_xmit
netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
...
...
} else {
__ieee80211_subif_start_xmit(skb, dev, 0, 0, NULL);
}
return NETDEV_TX_OK;
}
After few calls down the rabbit hole, drv_tx
is called and it calls tx
static inline void drv_tx(struct ieee80211_local *local,
struct ieee80211_tx_control *control,
struct sk_buff *skb)
{
local->ops->tx(&local->hw, control, skb);
}
ops
here is ieee80211_ops
registered by iwlwifi
in drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
const struct ieee80211_ops iwl_mvm_hw_ops = {
.tx = iwl_mvm_mac_tx,