Linux无线网络内核框架

Linux WiFi Kernel Stack研究。

Linux无线网络内核框架

几个主要数据结构之间的关系1

2016012801.png

A high level description of the Linux WiFi kernel stack

It's important to understand there are 2 paths in which userspace communicates with the kernel when we're talking about WiFi

Data path

the data being received is passed from the wireless driver to the netdev core (usually using netifrx()). From there the net core will pass it through the TCP/IP stack code and will queue it on the relevant sockets from which the userspace process will read it. On the Tx path packets will be sent from the netdev core to the wireless driver using the ndostartxmit() callback. The driver registers (like other netdevices such as an ethernet driver) a set of operations callbacks by using the struct netdeviceops.

Control path

This path is how userspace controls the WiFi interface/device and performs operations like scan / authentication / association. The userspace interface is based on netlink and called nl80211 (see include/uapi/linux/nl80211.h). You can send commands and get events in response.

When you send an nl80211 command it gets initially handled by cfg80211 kernel module(it's code is under net/wireless and the handlers are in net/wireless/nl80211.c). cfg80211 will usually call a lower level driver. In case of Full MAC hardware the specific HW driver is right below cfg80211. The driver below cfg80211 registers a set of ops with cfg80211 by using cfg80211ops struct. For example see brcmfmac driver (drivers/net/wireless/brcm80211/brcmfmac/wlcfg80211.c)

For Soft MAC hardware there's mac80211 which is a kernel module implementing the 802.11 MAC layer. In this case cfg80211 will talk to mac80211 which will in turn use the hardware specific lower level driver. An example of this is iwlwifi (For Intel chips).

mac80211 registers itself with cfg80211 by using the cfg80211ops (see net/mac80211/cfg.c).The specific HW driver registers itself with mac80211 by using the ieee80211ops struct (for example drivers/net/wireless/iwlwifi/mvm/mac80211.c).

Initialization of a new NIC you've connected occurs from the bottom up the stack. The HW specific driver will call mac80211's ieee80211allowhw() usually after probing the HW. ieee80211allochw() gets the size of private data struct used by the HW driver. It in turns calls cfg80211 wiphynew() which does the actual allocation of space sufficient for the wiphy struct, the ieee80211local struct (which is used by mac80211) and the HW driver private data (the layering is seen in ieee80211allochw code). ieee80211hw is an embedded struct within ieee80211local which is "visible" to the the HW driver. All of these (wiphy, ieee80211local,ieee80211hw) represent a single physical device connected.

On top of a single physical device (also referred to as phy) you can set up multiple virtual interfaces. These are essentially what you know as wlan0 or wlan1 which you control with ifconfig. Each such virtual interface is represented by an ieee80211vif. This struct also contains at the end private structs accessed by the HW driver. Multiple interfaces can be used to run something like a station on wlan0 and an AP on wlan1 (this is possible depending on the HW capabilities).

cfg80211

主要API速览

  • cfg80211_register 这个必须在 regsiter_netdevice() 之前调用
  • cfg80211_connect_result 通知上层当前的连接状态。
  • cfg80211_disconnected 通知上层底层连接已经断开
  • cfg80211_get_chandef_type 主要的类型有:
    • NL80211_CHAN_NO_HT
    • NL80211_CHAN_HT20
    • NL80211_CHAN_HT40MINUS
    • NL80211_CHAN_HT40PLUS
  • cfg80211_chandef_create 创建Channel的信息
  • cfg80211_get_bss
  • cfg80211_put_bss
  • cfg80211_inform_bss 2.6.31后,优先使用此接口 通知80211,获得了一个scan bss。
  • cfg80211_inform_bss_frame
  • cfg80211_find_ie 寻找指定的IE。 如SSID
  • cfg80211_find_vendor_ie
  • cfg80211_tdls_oper_request
  • cfg80211_scan_done 通知上层,扫描动作停止。
  • cfg80211_rx_mgmt 通知上层,接收到未处理的管理帧, 从Kernel上报告管理帧,都是通过些接 口。
  • cfg80211_rx_action 通知上层,接收到未处理的Action帧
  • cfg80211_mgmt_tx_status
  • cfg80211_action_tx_status 通知上层,Action帧的发送状态
  • cfg80211_new_sta 当关联成功后,会调用此接口通知上层添加了新的关联工作站。 驱动在处理对方的Association Request后,如果成功回应了Association Response,会通过该接口通知上层,有新的STA创建。
  • cfg80211_del_sta 当Deauth或Diassoc后,会调用些接口。
  • cfg80211_michael_mic_failure
  • cfg80211_roamed
  • cfg80211_report_obss_beacon
  • cfg80211_ibss_joined
  • cfg80211_ready_on_channel repoort remain on channel ready.
  • cfg80211_remain_on_channel_expired
  • cfg80211_classify8021d 返回802.1d中的tag优先级
  • wiphy_new 会创建 struct cfg80211_registered_device 结构实例,并初始化其 wdev_list 链表。这个链表是由 struct wireless_dev 类型元素组成。

    两个相关函数:

    1. wiphy_priv()
    2. priv_to_wiphy()

主要数据结构速览

  • struct wireless_dev 无线设备的状态, 在 struct net_deviceieee80211_ptr 域中,会 指向这个数据结构。
    struct wireless_dev {
            struct wiphy *wiphy;
            enum nl80211_iftype iftype;
    
            /* the remainder of this struct should be private to cfg80211 */
            struct list_head list;
            struct net_device *netdev;
    
            u32 identifier;
    
            struct list_head mgmt_registrations;
            spinlock_t mgmt_registrations_lock;
    
            struct mutex mtx;
    
            bool use_4addr, p2p_started;
    
            u8 address[ETH_ALEN] __aligned(sizeof(u16));
    
            /* currently used for IBSS and SME - might be rearranged later */
            u8 ssid[IEEE80211_MAX_SSID_LEN];
            u8 ssid_len, mesh_id_len, mesh_id_up_len;
            struct cfg80211_conn *conn;
            struct cfg80211_cached_keys *connect_keys;
    
            struct list_head event_list;
            spinlock_t event_lock;
    
            struct cfg80211_internal_bss *current_bss; /* associated / joined */
            struct cfg80211_chan_def preset_chandef;
            struct cfg80211_chan_def chandef;
    
            bool ibss_fixed;
            bool ibss_dfs_possible;
    
            bool ps;
            int ps_timeout;
    
            int beacon_interval;
    
            u32 ap_unexpected_nlportid;
    
            bool cac_started;
            unsigned long cac_start_time;
            unsigned int cac_time_ms;
    
            u32 owner_nlportid;
    
    #ifdef CPTCFG_CFG80211_WEXT
            /* wext data */
            struct {
                    struct cfg80211_ibss_params ibss;
                    struct cfg80211_connect_params connect;
                    struct cfg80211_cached_keys *keys;
                    const u8 *ie;
                    size_t ie_len;
                    u8 bssid[ETH_ALEN], prev_bssid[ETH_ALEN];
                    u8 ssid[IEEE80211_MAX_SSID_LEN];
                    s8 default_key, default_mgmt_key;
                    bool prev_bssid_valid;
            } wext;
    #endif
    };
    
  • struct cfg80211_registered_device
    struct cfg80211_registered_device {
            const struct cfg80211_ops *ops;
            struct list_head list;
            /* we hold this mutex during any call so that
             * we cannot do multiple calls at once, and also
             * to avoid the deregister call to proceed while
             * any call is in progress */
            struct mutex mtx;
    
            /* rfkill support */
            struct rfkill_ops rfkill_ops;
            struct rfkill *rfkill;
            struct work_struct rfkill_sync;
    
            /* ISO / IEC 3166 alpha2 for which this device is receiving
             * country IEs on, this can help disregard country IEs from APs
             * on the same alpha2 quickly. The alpha2 may differ from
             * cfg80211_regdomain's alpha2 when an intersection has occurred.
             * If the AP is reconfigured this can also be used to tell us if
             * the country on the country IE changed. */
            char country_ie_alpha2[2];
    
            /* If a Country IE has been received this tells us the environment
             * which its telling us its in. This defaults to ENVIRON_ANY */
            enum environment_cap env;
    
            /* wiphy index, internal only */
            int wiphy_idx;
    
            /* associated wireless interfaces */
            struct mutex devlist_mtx;
            /* protected by devlist_mtx or RCU */
            struct list_head wdev_list;
            int devlist_generation, wdev_id;
            int opencount; /* also protected by devlist_mtx */
            wait_queue_head_t dev_wait;
    
            struct list_head beacon_registrations;
            spinlock_t beacon_registrations_lock;
    
            /* protected by RTNL only */
            int num_running_ifaces;
            int num_running_monitor_ifaces;
    
            /* BSSes/scanning */
            spinlock_t bss_lock;
            struct list_head bss_list;
            struct rb_root bss_tree;
            u32 bss_generation;
            struct cfg80211_scan_request *scan_req; /* protected by RTNL */
            struct cfg80211_sched_scan_request *sched_scan_req;
            unsigned long suspend_at;
            struct work_struct scan_done_wk;
            struct work_struct sched_scan_results_wk;
    
            struct mutex sched_scan_mtx;
    
    #ifdef CONFIG_NL80211_TESTMODE
            struct genl_info *testmode_info;
    #endif
    
            struct work_struct conn_work;
            struct work_struct event_work;
    
            struct cfg80211_wowlan *wowlan;
    
            struct delayed_work dfs_update_channels_wk;
    
            /* netlink port which started critical protocol (0 means not started) */
            u32 crit_proto_nlportid;
    
            /* must be last because of the way we do wiphy_priv(),
             * and it should at least be aligned to NETDEV_ALIGN */
            struct wiphy wiphy __aligned(NETDEV_ALIGN);
    };
    
  • struct cfg80211_chan_def 信道定义
  • struct cfg80211_scan_request
  • struct cfg80211_ibss_params
  • struct cfg80211_connect_params Connection parameters This structure provides information needed to complete IEEE 802.11 authentication and association.
    struct cfg80211_ops CFG80211_Ops = {
      ...
      .connect = mt76xx_cfg80211_connect,
      ...
    };
    
    static int mt76xx_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
                                       struct cfg80211_connect_params *sme)
    {
    #ifdef CONFIG_STA_SUPPORT
            RTMP_ADAPTER *pAd;
            INT32 Pairwise;
            INT32 Groupwise;
            INT32 Keymgmt = 0;
            int i;
            CMD_RTPRIV_IOCTL_80211_CONNECT ConnInfo;
    
            MAC80211_PAD_GET(pAd, wiphy);
            if (!pAd)
                    return -EFAULT;
    
            CFG80211DBG(RT_DEBUG_TRACE, ("80211> %s ==>\n", __func__));
    
            //Group Wise
            CFG80211DBG(RT_DEBUG_TRACE, ("Groupwise: %x\n", sme->crypto.cipher_group));
            Groupwise = sme->crypto.cipher_group;
    
            //Pair Wise
            if (sme->crypto.n_ciphers_pairwise)
                    Pairwise = sme->crypto.ciphers_pairwise[0];
            else
                    Pairwise = 0;
            CFG80211DBG(RT_DEBUG_TRACE, ("Pairwise %x\n", sme->crypto.ciphers_pairwise[0]));
    
            //Key management
            for (i = 0; i < sme->crypto.n_akm_suites; ++i)
                    Keymgmt |= sme->crypto.akm_suites[i];
    
            memset(&ConnInfo, 0, sizeof(ConnInfo));
    
            //WPA Version
    
            if (!sme->crypto.wpa_versions)
                    ConnInfo.WpaVer = 0;
            else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2)
                    ConnInfo.WpaVer = 2;
            else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1)
                    ConnInfo.WpaVer = 1;
            else {
                    CFG80211DBG(RT_DEBUG_ERROR, ("version %x not supported\n",
                                                 sme->crypto.wpa_versions));
                    return -ENOTSUPP;
            }
            CFG80211DBG(RT_DEBUG_TRACE, ("wpa_versions %x\n", sme->crypto.wpa_versions));
    
            /* GeK: [todo] WLAN_AKM_SUITE_PSK and others? */
            CFG80211DBG(RT_DEBUG_TRACE, ("Keymgmt %x\n", Keymgmt));
            if (Keymgmt == WLAN_AKM_SUITE_8021X)
                    ConnInfo.FlgIs8021x = TRUE;
            else
                    ConnInfo.FlgIs8021x = FALSE;
    
            //Auth type
            CFG80211DBG(RT_DEBUG_TRACE, ("Auth_type %x\n", sme->auth_type));
            if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY)
                    ConnInfo.AuthType = Ndis802_11AuthModeShared;
            else if (sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM)
                    ConnInfo.AuthType = Ndis802_11AuthModeOpen;
            else
                    ConnInfo.AuthType = Ndis802_11AuthModeAutoSwitch;
    
            switch (Pairwise) {
            case 0:
                    CFG80211DBG(RT_DEBUG_TRACE, ("NONE...\n"));
                    ConnInfo.PairwiseEncrypType |= RT_CMD_80211_CONN_ENCRYPT_NONE;
                    break;
            case WLAN_CIPHER_SUITE_CCMP:
                    CFG80211DBG(RT_DEBUG_TRACE, ("WLAN_CIPHER_SUITE_CCMP...\n"));
                    ConnInfo.PairwiseEncrypType |= RT_CMD_80211_CONN_ENCRYPT_CCMP;
                    break;
            case WLAN_CIPHER_SUITE_TKIP:
                    CFG80211DBG(RT_DEBUG_TRACE, ("WLAN_CIPHER_SUITE_TKIP...\n"));
                    ConnInfo.PairwiseEncrypType |= RT_CMD_80211_CONN_ENCRYPT_TKIP;
                    break;
            case WLAN_CIPHER_SUITE_WEP40:
                    /* fall through */
            case WLAN_CIPHER_SUITE_WEP104:
                    CFG80211DBG(RT_DEBUG_TRACE, ("WLAN_CIPHER_SUITE_WEP...\n"));
                    ConnInfo.PairwiseEncrypType |= RT_CMD_80211_CONN_ENCRYPT_WEP;
                    break;
            default:
                    CFG80211DBG(RT_DEBUG_ERROR, ("pairwise %x not supported\n", Pairwise));
                    return -ENOTSUPP;
            }
    
            if (Groupwise == WLAN_CIPHER_SUITE_CCMP)
                    ConnInfo.GroupwiseEncrypType |= RT_CMD_80211_CONN_ENCRYPT_CCMP;
            else if (Groupwise == WLAN_CIPHER_SUITE_TKIP)
                    ConnInfo.GroupwiseEncrypType |= RT_CMD_80211_CONN_ENCRYPT_TKIP;
            else
                    ConnInfo.GroupwiseEncrypType |= RT_CMD_80211_CONN_ENCRYPT_NONE;
    
            CFG80211DBG(RT_DEBUG_TRACE, ("ConnInfo.KeyLen ===> %d\n", sme->key_len));
            CFG80211DBG(RT_DEBUG_TRACE, ("ConnInfo.KeyIdx ===> %d\n", sme->key_idx));
    
            ConnInfo.pKey = (UINT8 *) (sme->key);
            ConnInfo.KeyLen = sme->key_len;
            ConnInfo.pSsid = sme->ssid;
            ConnInfo.SsidLen = sme->ssid_len;
            ConnInfo.KeyIdx = sme->key_idx;
            ConnInfo.bWpsConnection = FALSE;
            /* Check if WPS is triggerred */
            pAd->StaCfg.wpa_supplicant_info.WpaSupplicantUP = WPA_SUPPLICANT_ENABLE;
            if (sme->ie && sme->ie_len &&
                sme->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM &&
                ConnInfo.PairwiseEncrypType == RT_CMD_80211_CONN_ENCRYPT_NONE) {
                    if (RTMPFindWPSIE(sme->ie, (UINT32) sme->ie_len) != NULL) {
                            ConnInfo.bWpsConnection = TRUE;
                            pAd->StaCfg.wpa_supplicant_info.WpaSupplicantUP
                                    |= WPA_SUPPLICANT_ENABLE_WPS;
                    }
            }
    
            /* Use SIOCSIWGENIE to make out the WPA/WPS IEs in AssocReq. */
    #ifdef RT_CFG80211_P2P_CONCURRENT_DEVICE
            if (dev->ieee80211_ptr->iftype == NL80211_IFTYPE_P2P_CLIENT) {
                    if (sme->ie_len > 0)
                            CFG80211DRV_SetP2pCliAssocIe(pAd, sme->ie, sme->ie_len);
                    else
                            CFG80211DRV_SetP2pCliAssocIe(pAd, NULL, 0);
            } else
    #endif /* RT_CFG80211_P2P_CONCURRENT_DEVICE */
            {
                    if (sme->ie_len > 0)
                            RtmpIoctl_rt_ioctl_siwgenie(pAd, sme->ie, sme->ie_len);
                    else
                            RtmpIoctl_rt_ioctl_siwgenie(pAd, NULL, 0);
            }
    
    #ifdef DOT11W_PMF_SUPPORT
    #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0))
            CFG80211DBG(RT_DEBUG_TRACE, ("80211> PMF Connect %d\n", sme->mfp));
            if (sme->mfp)
                    ConnInfo.mfp = TRUE;
            else
                    ConnInfo.mfp = FALSE;
    #endif /* LINUX_VERSION_CODE */
    #endif /* DOT11W_PMF_SUPPORT */
    
            /* %NULL if not specified (auto-select based on scan) */
            if (sme->bssid != NULL && !MAC_ADDR_EQUAL(sme->bssid, ZERO_MAC_ADDR)) {
                    CFG80211DBG(RT_DEBUG_OFF, ("80211> Connect bssid %02x:%02x:%02x:%02x:%02x:%02x\n",
                                               PRINT_MAC(sme->bssid)));
                    ConnInfo.pBssid = sme->bssid;
            } else
                    ConnInfo.pBssid = NULL;
    
            RTMP_DRIVER_80211_CONNECT(pAd, &ConnInfo, dev->ieee80211_ptr->iftype);
    #endif /*CONFIG_STA_SUPPORT */
            return 0;
    }                               /* mt76xx_cfg80211_connect */
    
  • struct cfg80211_pmksa
  • struct cfg80211_gtk_rekey_data
  • struct cfg80211_mgmt_tx_params
  • struct cfg80211_ap_settings 起softap时,会通过该数据结构传递下层配置的一些参数下来。
  • struct cfg80211_beacon_data
  • struct cfg80211_bitrate_mask
  • struct cfg80211_ops TODO: changestation 何时会调用
  • struct cfg80211_crypto_settings
  • struct ieee80211_iface_combination 接口约束定义
    static const struct ieee80211_iface_limit ra_p2p_sta_go_limits[] = 
    {
            {
                    .max = 3,
                    .types = BIT(NL80211_IFTYPE_STATION)| BIT(NL80211_IFTYPE_AP),
            },
            {
                    .max = 1,
                    .types = BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_P2P_CLIENT),
            },
    };
    
  • struct ieee80211_iface_limit
    static const struct ieee80211_iface_combination 
    ra_iface_combinations_p2p[] = {
            {
                    .num_different_channels = 1,
                    .max_interfaces = 3,
                    //.beacon_int_infra_match = true,
                    .limits = ra_p2p_sta_go_limits,
                    .n_limits = 1,//ARRAY_SIZE(ra_p2p_sta_go_limits),
            },
    };
    
    static const struct ieee80211_iface_combination 
    ra_iface_combinations_p2p_GO[] = {
            {
                    .num_different_channels = 1,
                    .max_interfaces = 3,
                    //.beacon_int_infra_match = true,
                    .limits = ra_p2p_sta_go_limits,
                    .n_limits = ARRAY_SIZE(ra_p2p_sta_go_limits),
            },
    };
    

    在调用 wiphy_register 之前,可以将上述信息设置到 struct wiphyiface_combinationsn_iface_combinations

  • struct ieee80211_mgmt 管理帧,相关API有:
    • ieee80211_is_mgmt
    • ieee80211_is_probe_resp
    • ieee80211_is_disassoc
    • ieee80211_is_deauth
    • ieee80211_is_action
    • cfg80211_rx_mgmt 向上层报告有未处理的管理帧收到。
  • struct cfg80211_bss BSS信息
  • struct station_info 描述STA相关的信息, 其中 filled 可以告知 nl80211 层,上报上来的 消息包含哪些IE。 如 STATION_INFO_ASSOC_REQ_IES 表明当前上报上来的 Association Request会包含相关的IE信息。
    VOID CFG80211OS_NewSta(IN PNET_DEV pNetDev, IN const PUCHAR mac_addr, IN const PUCHAR assoc_frame, IN UINT32 assoc_len)
    {
            struct station_info sinfo;
            struct ieee80211_mgmt *mgmt;
    
            NdisZeroMemory(&sinfo, sizeof(sinfo));
    
            sinfo.filled = STATION_INFO_ASSOC_REQ_IES;
    
            mgmt = (struct ieee80211_mgmt *) assoc_frame;
            sinfo.assoc_req_ies_len = assoc_len - 24 - 4;
            sinfo.assoc_req_ies = mgmt->u.assoc_req.variable;
    
            return cfg80211_new_sta(pNetDev, mac_addr, &sinfo, GFP_KERNEL);
    }
    

    还有tx rate的相关信息

  • struct survey_info site survey information

主要流程

数据结构

  • struct wiphy wireless hardware description
  • wireless_dev wireless device state
  • struct net_device_ops 在Linux 2.6.31内核版本后,在注册设备时,如下几个回调接口一般需要 被定义:
    1. ndo_open
    2. ndo_stop
    3. ndo_start_xmit
    4. ndo_do_ioctl
    5. ndo_get_stats 可选,获取状态信息
    6. ethtool_ops 获取驱动信息的回调函数
    7. ndo_validate_addr
  • struct net_device_stats
  • struct ieee80211_rate 支持的速率定义
  • struct ieee80211_channel 信道定义, IEEE80211_CHAN_RADAR 用于判断当前信道是否需要回避雷 达。 相关接口:
    1. ieee80211_get_channel
    2. ieee80211_channel_to_frequency
  • struct ieee80211_supported_band 频段定义:2.4G(IEEE80211_BAND_2GHZ), 5G(IEEE80211_BAND_5GHZ)等

频段和速率的定义

  1. 信道定义的定义 center_freq, hw_value, max_power, max_antenna_gain 这个值的定 义。需要用到信道与频率之间的映射函数: ieee80211_channel_to_frequency
  2. 初始化所支持的速率 定义 struct ieee80211_rate 数组
  3. 频段定义 包含的信息有:支持的信道数,支持的速率,以及HT Capability相关信 息。

设备注册

  • wiphy_new Allocate wiphy and hook cfg80211 ops
  • wiphy_register Register the wiphy to cfg80211. Do sanity checking , set up regulatory info according to the wiphy info and so on.
  • wiphy_free Free the allocated wiphy
  • wiphy_unregister Unregister the wiphy.

在注册 netdev 之前进行,主要包含硬件的一些能力信息:

  1. bands and channels
  2. bitrates per band
  3. HT capabilites
  4. supported interface modes

netdev结构的 ieee80211 ptr 指针指向注册的 struct wireless_dev 对象 。

注册cfg80211相关函数接口。

static void wlanCreateWirelessDevice(void)
{
        struct wiphy *prWiphy = NULL;
        struct wireless_dev *prWdev = NULL;

        /* 4 <1.1> Create wireless_dev */
        prWdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);
        if (!prWdev) {
                DBGLOG(INIT, ERROR, "Allocating memory to wireless_dev context failed\n");
                return;
        }
        /* 4 <1.2> Create wiphy */
        prWiphy = wiphy_new(&mtk_wlan_ops, sizeof(GLUE_INFO_T));
        if (!prWiphy) {
                DBGLOG(INIT, ERROR, "Allocating memory to wiphy device failed\n");
                goto free_wdev;
        }
        /* 4 <1.3> configure wireless_dev & wiphy */
        prWdev->iftype = NL80211_IFTYPE_STATION;
        prWiphy->iface_combinations = p_mtk_iface_combinations_sta;
        prWiphy->n_iface_combinations = mtk_iface_combinations_sta_num;
        prWiphy->max_scan_ssids = 1;    /* FIXME: for combo scan */
        prWiphy->max_scan_ie_len = 512;
        prWiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC);
        prWiphy->bands[IEEE80211_BAND_2GHZ] = &mtk_band_2ghz;
        /* always assign 5Ghz bands here, if the chip is not support 5Ghz,
         *  bands[IEEE80211_BAND_5GHZ] will be assign to NULL
         */
        prWiphy->bands[IEEE80211_BAND_5GHZ] = &mtk_band_5ghz;
        prWiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
        prWiphy->cipher_suites = (const u32 *)mtk_cipher_suites;
        prWiphy->n_cipher_suites = ARRAY_SIZE(mtk_cipher_suites);
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)
        prWiphy->flags = WIPHY_FLAG_CUSTOM_REGULATORY | WIPHY_FLAG_SUPPORTS_FW_ROAM | WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
#else
        prWiphy->flags = WIPHY_FLAG_SUPPORTS_FW_ROAM | WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
        prWiphy->regulatory_flags = REGULATORY_CUSTOM_REG;
#endif

#if (CFG_SUPPORT_TDLS == 1)
        TDLSEX_WIPHY_FLAGS_INIT(prWiphy->flags);
#endif /* CFG_SUPPORT_TDLS */
        prWiphy->max_remain_on_channel_duration = 5000;
        prWiphy->mgmt_stypes = mtk_cfg80211_ais_default_mgmt_stypes;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)
        prWiphy->vendor_commands = mtk_wlan_vendor_ops;
        prWiphy->n_vendor_commands = sizeof(mtk_wlan_vendor_ops) / sizeof(struct wiphy_vendor_command);
        prWiphy->vendor_events = mtk_wlan_vendor_events;
        prWiphy->n_vendor_events = ARRAY_SIZE(mtk_wlan_vendor_events);
#endif
        /* 4 <1.4> wowlan support */
#ifdef CONFIG_PM
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)
        prWiphy->wowlan = &mtk_wlan_wowlan_support;
#else
        kalMemCopy(&prWiphy->wowlan, &mtk_wlan_wowlan_support, sizeof(struct wiphy_wowlan_support));
#endif
#endif

#ifdef CONFIG_CFG80211_WEXT
        /* 4 <1.5> Use wireless extension to replace IOCTL */
        prWiphy->wext = &wext_handler_def;
#endif
        if (wiphy_register(prWiphy) < 0) {
                DBGLOG(INIT, ERROR, "wiphy_register error\n");
                goto free_wiphy;
        }
        prWdev->wiphy = prWiphy;
        gprWdev = prWdev;
        DBGLOG(INIT, INFO, "create wireless device success\n");
        return;

free_wiphy:
        wiphy_free(prWiphy);
free_wdev:
        kfree(prWdev);
}

定义基本的网络设备的基本操作函数

#+TITLE:注册网络设备相关函数

struct net_device *device = alloc_etherdev(privDataSize);

struct net_device_ops *pNetDevOps = NULL;

device->netdev_ops = pNetDevOps;

//分配一个接口名
dev_get_by_name(...)

//cfg80211 register

//注册
if (rtnl_locked)
  ret = register_netdevice(pNetDev);
else
  ret = register_netdev(pNetDev);

定义管制规则regulatory enforcement2

Channel使用信息, 管制 regulatory_init

  1. 声明 struct ieee80211_regdomain
    const struct ieee80211_regdomain regdom_cn = {
            .n_reg_rules = 5,
            .reg_rules = {
            /* channels 1..11 */
            REG_RULE_LIGHT(2412-10, 2462+10, 40, 0),
            /* channels 12,13 */
            REG_RULE_LIGHT(2467-10, 2472+10, 40, 0),
            /* channels 36..48 */
            REG_RULE_LIGHT(5180-10, 5240+10, 80, 0),
            /* channels 52..64 */
            REG_RULE_LIGHT(5260-10, 5320+10, 80, NL80211_RRF_DFS),
            /* channels 149..165 */
            REG_RULE_LIGHT(5745-10, 5825+10, 80, 0) }
    };
    
  2. 声明 struct mtk_regdomain
    #if (CFG_SUPPORT_SINGLE_SKU_LOCAL_DB == 1)
    struct mtk_regdomain {
            char country_code[4];
            const struct ieee80211_regdomain *prRegdRules;
    };
    #endif
    
    const struct mtk_regdomain my_regdom_cn = {
            .country_code = "CN",
            .prRegdRules = &regdom_cn
    };
    
  3. 注册到表格中
    const struct mtk_regdomain *g_prRegRuleTable[] = {
      ...
      &my_regdom_cn,
      ...
      NULL /* this NULL SHOULD be at the end of the array */
    };
    

    通过这个表格可以查询各个国家或地区的信道管制要求。\

  4. 更新到内核
    /* update to kernel */
    wiphy_apply_custom_regulatory(pWiphy, pRegdom);
    

station management

添加,移除和修改STA。 Dump STA列表 主要的回调接口:

  • .add_ station
  • .del_station
  • .change_station
  • .get_station
  • .dump_station

mesh management

mesh路径处理 读取或设置mesh参数

virtual interface management

  • 创建虚拟接口和移除虚拟接口
  • 改变虚拟接口的类型
  • 改变 monitor 标记
  • 跟踪与无线设备关联的接口

mac80211

主要API速览

ieee80211_alloc_hw_nw(..)

ieee80211_register_hw(...)

ieee80211_check_tim()

checks a specific information element (TIM) The TIM is an array of 2008 entries. Because the TIM size is 251 bytes (2008 bits)

ieee80211_get_buffered_bc()

retrieve packets from the multicast/broadcast buffer

sta_info_insert

Adds a station

sta_info_destroy_addr

Removes a station

sta_info_get

Fetches a station; the address of the station (it’s bssid) is passed as a parameter.

ieee80211_rate_control_register

注册速率控制算法

MLME

扫描

ieee80211_send_probe_req() => =the ieee80211_request_scan()

Change Channel: ieee80211_hw_config()

Channel to Frequence Transition: ieee80211_channel_to_frequency()

认证

the ieee80211_send_auth()

有两种类型的认证方式:

  1. WLAN_AUTH_OPEN
  2. WLAN_AUTH_SHARED_KEY

关联

ieee80211_send_assoc()

重新关联

ieee80211_send_assoc()

mac80211实现

数据结构

  1. ieee80211_hw 代表硬件信息
  2. ieee80211_ops 实例传递给 ieee80211_alloc_hw() 方法,这个实际定义了一些回调 函数 :
    • tx() The transmit handler called for each transmitted packet. It usually returns NETDEV_TX_OK (except for under certain limited conditions).
    • start() 开户数据帧的接收
    • stop() 停止数据帧的接收,关闭硬件
    • add_interface() Called when a network device attached to the hardware is enabled.
    • remove_interface() Informs a driver that the interface is going down.
    • config() Handles configuration requests, such as hardware channel configuration.
    • configure_filter() Configures the device’s Rx filter.
  3. struct ieee80211_tx_info
    struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
    
  4. struct ieee80211_sta station table entry: represents a station we are possibly communicating with.

Rx Path

主要的接收函数 ieee80211_rx() 对接收到的数据,会进行一些检查,可能会丢弃一些数据包:

ieee80211_rx_h_mgmt_check(struct ieee80211_rx_data *rx)
{
  struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *) rx->skb->data;
  struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
  . . .
    if (rx->skb->len < 24)
      return RX_DROP_MONITOR;
    if (!ieee80211_is_mgmt(mgmt->frame_control))
      return RX_DROP_MONITOR;
    . . .
}

Tx Path

主要的发送函数 ieee80211_tx() 基于mac80211框架的驱动需要提供一个 struct ieee80211_ops 结构实 例,这些回调函数是mac80211框架与驱动及底层硬件进行交互的通道。

  • data path

    ieee80211_if_add -> alloc_netdev_mqs -> ieee80211_if_setup

    static void ieee80211_if_setup(struct net_device *dev)
    {
            ether_setup(dev);
            dev->priv_flags &= ~IFF_TX_SKB_SHARING;
            dev->netdev_ops = &ieee80211_dataif_ops;
            dev->destructor = ieee80211_if_free;
    }
    
    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_change_mtu         = ieee80211_change_mtu,
            .ndo_set_mac_address    = ieee80211_change_mac,
            .ndo_select_queue       = ieee80211_netdev_select_queue,
            .ndo_get_stats64        = ieee80211_get_stats64,
    };
    

    最终通过 ieee80211_xmit 一路调用下去。

  • configuration path
    wiphy_new_nm(&mac80211_config_ops, priv_size, requested_name);
    
    const struct cfg80211_ops mac80211_config_ops = {
            .add_virtual_intf = ieee80211_add_iface,
            .del_virtual_intf = ieee80211_del_iface,
            .change_virtual_intf = ieee80211_change_iface,
            .start_p2p_device = ieee80211_start_p2p_device,
            .stop_p2p_device = ieee80211_stop_p2p_device,
            .add_key = ieee80211_add_key,
            .del_key = ieee80211_del_key,
            .get_key = ieee80211_get_key,
            .set_default_key = ieee80211_config_default_key,
            .set_default_mgmt_key = ieee80211_config_default_mgmt_key,
            .start_ap = ieee80211_start_ap,
            .change_beacon = ieee80211_change_beacon,
            .stop_ap = ieee80211_stop_ap,
            .add_station = ieee80211_add_station,
            .del_station = ieee80211_del_station,
            .change_station = ieee80211_change_station,
            .get_station = ieee80211_get_station,
            .dump_station = ieee80211_dump_station,
            .dump_survey = ieee80211_dump_survey,
    #ifdef CPTCFG_MAC80211_MESH
            .add_mpath = ieee80211_add_mpath,
            .del_mpath = ieee80211_del_mpath,
            .change_mpath = ieee80211_change_mpath,
            .get_mpath = ieee80211_get_mpath,
            .dump_mpath = ieee80211_dump_mpath,
            .get_mpp = ieee80211_get_mpp,
            .dump_mpp = ieee80211_dump_mpp,
            .update_mesh_config = ieee80211_update_mesh_config,
            .get_mesh_config = ieee80211_get_mesh_config,
            .join_mesh = ieee80211_join_mesh,
            .leave_mesh = ieee80211_leave_mesh,
    #endif
            .join_ocb = ieee80211_join_ocb,
            .leave_ocb = ieee80211_leave_ocb,
            .change_bss = ieee80211_change_bss,
            .set_txq_params = ieee80211_set_txq_params,
            .set_monitor_channel = ieee80211_set_monitor_channel,
            .suspend = ieee80211_suspend,
            .resume = ieee80211_resume,
            .scan = ieee80211_scan,
            .sched_scan_start = ieee80211_sched_scan_start,
            .sched_scan_stop = ieee80211_sched_scan_stop,
            .auth = ieee80211_auth,
            .assoc = ieee80211_assoc,
            .deauth = ieee80211_deauth,
            .disassoc = ieee80211_disassoc,
            .join_ibss = ieee80211_join_ibss,
            .leave_ibss = ieee80211_leave_ibss,
            .set_mcast_rate = ieee80211_set_mcast_rate,
            .set_wiphy_params = ieee80211_set_wiphy_params,
            .set_tx_power = ieee80211_set_tx_power,
            .get_tx_power = ieee80211_get_tx_power,
            .set_wds_peer = ieee80211_set_wds_peer,
            .rfkill_poll = ieee80211_rfkill_poll,
            CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
            CFG80211_TESTMODE_DUMP(ieee80211_testmode_dump)
            .set_power_mgmt = ieee80211_set_power_mgmt,
            .set_bitrate_mask = ieee80211_set_bitrate_mask,
            .remain_on_channel = ieee80211_remain_on_channel,
            .cancel_remain_on_channel = ieee80211_cancel_remain_on_channel,
            .mgmt_tx = ieee80211_mgmt_tx,
            .mgmt_tx_cancel_wait = ieee80211_mgmt_tx_cancel_wait,
            .set_cqm_rssi_config = ieee80211_set_cqm_rssi_config,
            .mgmt_frame_register = ieee80211_mgmt_frame_register,
            .set_antenna = ieee80211_set_antenna,
            .get_antenna = ieee80211_get_antenna,
            .set_rekey_data = ieee80211_set_rekey_data,
            .tdls_oper = ieee80211_tdls_oper,
            .tdls_mgmt = ieee80211_tdls_mgmt,
            .tdls_channel_switch = ieee80211_tdls_channel_switch,
            .tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch,
            .probe_client = ieee80211_probe_client,
            .set_noack_map = ieee80211_set_noack_map,
    #ifdef CONFIG_PM
            .set_wakeup = ieee80211_set_wakeup,
    #endif
            .get_channel = ieee80211_cfg_get_channel,
            .start_radar_detection = ieee80211_start_radar_detection,
            .channel_switch = ieee80211_channel_switch,
            .set_qos_map = ieee80211_set_qos_map,
            .set_ap_chanwidth = ieee80211_set_ap_chanwidth,
            .add_tx_ts = ieee80211_add_tx_ts,
            .del_tx_ts = ieee80211_del_tx_ts,
    };
    

Rate Control Algorithm

  • minstrel

    minstrel is a mac80211 rate control algorithm ported over from MadWifi which supports multiple rate retries and claimed to be one of the best, if not the best, rate control algorithm.

    The implementation of minstrel provides a rate table for each of the remote nodes being communicated with. This rate table is found in the debugfs directory, and shows some interesting things. Sometimes, the 11mbit rate is more likely to succeed, or has a higher throughput, than the the 2mbit rate.

    操作理论

Packet Aggregation

  1. ieee80211_start_tx_ba_session() The originator starts the block acknowledgement session ADDBA Request
  2. ieee80211_send_addba_resp() ADDBA response
  3. ieee80211_send_bar() sends a Block Ack Request (BAR) packet
  4. ieee80211_send_delba() DELBA request

设备注册

  1. struct ieee80211_hw 新建 struct ieee80211 数据结构,注册 struct ieee80211_ops 回调函数 函数 ieee80211_alloc_hw 调用了 wiphy_new ,向cfg80211注册 了回调函数。
    /* This function both allocates and initializes hw and priv. */
    struct ieee80211_hw *iwl_alloc_all(void)
    {
            struct iwl_priv *priv;
            struct iwl_op_mode *op_mode;
            /* mac80211 allocates memory for this device instance, including
             *   space for this driver's private structure */
            struct ieee80211_hw *hw;
    
            hw = ieee80211_alloc_hw(sizeof(struct iwl_priv) +
                                    sizeof(struct iwl_op_mode), &iwlagn_hw_ops);
            if (!hw)
                    goto out;
    
            op_mode = hw->priv;
            priv = IWL_OP_MODE_GET_DVM(op_mode);
            priv->hw = hw;
    
    out:
            return hw;
    }
    
    
    const struct ieee80211_ops iwlagn_hw_ops = {
            .tx = iwlagn_mac_tx,
            .start = iwlagn_mac_start,
            .stop = iwlagn_mac_stop,
    #ifdef CONFIG_PM_SLEEP
            .suspend = iwlagn_mac_suspend,
            .resume = iwlagn_mac_resume,
            .set_wakeup = iwlagn_mac_set_wakeup,
    #endif
            .add_interface = iwlagn_mac_add_interface,
            .remove_interface = iwlagn_mac_remove_interface,
            .change_interface = iwlagn_mac_change_interface,
            .config = iwlagn_mac_config,
            .configure_filter = iwlagn_configure_filter,
            .set_key = iwlagn_mac_set_key,
            .update_tkip_key = iwlagn_mac_update_tkip_key,
            .set_rekey_data = iwlagn_mac_set_rekey_data,
            .conf_tx = iwlagn_mac_conf_tx,
            .bss_info_changed = iwlagn_bss_info_changed,
            .ampdu_action = iwlagn_mac_ampdu_action,
            .hw_scan = iwlagn_mac_hw_scan,
            .sta_notify = iwlagn_mac_sta_notify,
            .sta_state = iwlagn_mac_sta_state,
            .channel_switch = iwlagn_mac_channel_switch,
            .flush = iwlagn_mac_flush,
            .tx_last_beacon = iwlagn_mac_tx_last_beacon,
            .rssi_callback = iwlagn_mac_rssi_callback,
            .set_tim = iwlagn_mac_set_tim,
    };
    

    设置 hw->wiphy 相关信息: 设置hw相关的属性,如flags

  2. set_wiphy_dev
    SET_IEEE80211_DEV(priv->hw, priv->trans->dev);
    
  3. 注册 struct ieee80211_hw 调用了 wiphy_register

会添加一个默认的接口。

nl80211

NL80211_CMD_NEW_INTERFACE

添加一个新的网络接口, 由 nl80211_create_iface ( wpa_supplicant )触发。

NL80211_CMD_REGISTER_BEACONS

向内核注册beacon帧, 以便能在用户空间接收到beacon帧。

NL80211_CMD_REGISTER_ACTION

注册Action帧,这样driver就会将一些Action帧上报到用户空间。

NL80211_CMD_START_SCHED_SCAN

Request the driver to initiate scheduled scan. This operation should be used for scheduled scan offload to the hardware. Every time scan results are available, the driver report the scan results event to upper layer.

这是一个可选的功能,有些驱动可能并不支持。

NL80211_CMD_GET_REG

获取驱动的 regulatory information

NL80211_CMD_VENDOR

NL80211扩展命令,由驱动注册第三方专有命令。