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 using cfg80211_ops
  • iwlwifi registers with mac80211 using ieee80211_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,