ath9k usb wifi 網絡卡驅動淺析
ieee80211
802.11協議簇是國際電工電子工程學會(IEEE)為無線區域網絡制定的標準。
概述
- nl80211: 用於對無線裝置進行配置管理,它是一個基本Netlink的使用者態協議(User態)
- cfg80211: 用於對無線裝置進行配置管理。與FullMAC, mac80211和nl80211一起工作。(Kernel態)
- mac80211: 是一個driver開發者可用於為SoftMAC無線裝置寫驅動的框架 (Kernel態)。
- WNIC : Wireless Network Interface Controller, 它總是指望硬體執行協議(如IEEE802.11)描述的功能。
- MLME: 即MAC(Media Access Control ) Layer Management Entity,它管理物理層MAC狀態機。
- SoftMAC: 其MLME由軟體實現,mac80211為SoftMAC實現提供了一個driver API。 即:SoftMAC裝置允許對硬體執行更好地控制,允許用軟體實現對802.11的幀管理,包括解析和產生802.11無線幀。目前大多數802.11裝置為SoftMAC,而FullMAC裝置較少。
- FullMAC: 其MLME由硬體管理,當寫FullMAC無線驅動時,不需要使用mac80211。
- wpa_supplicant: 是使用者空間一個應用程式,主要發起MLME命令,然後處理相關結果。
關聯
cfg80211是Linux 802.11配置API。cfg80211用於程式碼wext(Wireless-Extensions),nl80211用於配置一個cfg80211裝置,且用於kernel與userspace間的通訊。wext現處理維護狀態,沒有新的功能被增加,只是修改bug。如果需要通過wext操作,則需要定義CONFIG_CFG80211_WEXT。
- cfg80211 and nl80211: 基於訊息機制,使用netlink介面
- wext: 基於ioctl機制
- struct ieee80211_hw: 表示硬體資訊和狀態
- ieee80211_alloc_hw:每個driver呼叫ieee80211_alloc_hw分配ieee80211_hw,且以ieee80211_ops為引數
- ieee80211_register_hw:每個driver呼叫ieee80211_register_hw建立wlan0和 wmaster0,並進行各種初始化。
- struct ieee80211_ops:每個driver實現它的成員函式,且它的成員函式都以struct ieee80211_hw做為第一個引數。在struct ieee80211_ops中定義了24個方法,以下7個方法必須實現:
tx,start,stop,add_interface,remove_interface,config和configure_filter。
mac80211它是一個driver開發者可用於為SoftMAC無線裝置寫驅動的框架,mac80211為SoftMAC裝置實現了cfg80211回撥函式,且mac80211通過cfg80211實現了向網路子系統註冊和配置。
資料傳遞過程
mac80211和wifi裝置驅動程式息息相關,但是它是核心抽象出來的裝置無關層,每一個註冊進來的wifi裝置,它都幫助我們註冊一個 net_device,並且使用統一的 ops 函式。那麼網路協議上層傳遞過來的資料包首先經過mac80211層,然後才會傳遞到裝置驅動層,再由硬體傳送出去。
ath9k向mac80211的註冊過程
當 usb host 識別出 usb 裝置為 ath9k 時,會呼叫到驅動程式中的 probe 函式。
static int ath9k_hif_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct hif_device_usb *hif_dev;
int ret = 0;
hif_dev = kzalloc(sizeof(struct hif_device_usb), GFP_KERNEL);
usb_get_dev(udev);
hif_dev->udev = udev;
hif_dev->interface = interface;
hif_dev->usb_device_id = id;
//dev_set_drvdata(&intf->dev, data);
usb_set_intfdata(interface, hif_dev);
init_completion(&hif_dev->fw_done);
/* Find out which firmware to load */
if (IS_AR7010_DEVICE(id->driver_info))
hif_dev->fw_name = FIRMWARE_AR7010_1_1;
else
hif_dev->fw_name = FIRMWARE_AR9271;
//載入韌體,成功後呼叫 ath9k_hif_usb_firmware_cb
ret = request_firmware_nowait(THIS_MODULE, true, hif_dev->fw_name,
&hif_dev->udev->dev, GFP_KERNEL,
hif_dev, ath9k_hif_usb_firmware_cb);
return 0;
}
static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context)
{
struct hif_device_usb *hif_dev = context;
int ret;
//hif_dev->htc_handle->target->hif = hif_usb
hif_dev->htc_handle = ath9k_htc_hw_alloc(hif_dev, &hif_usb, &hif_dev->udev->dev);
hif_dev->firmware = fw;
ret = ath9k_hif_usb_dev_init(hif_dev);
ret = ath9k_htc_hw_init(hif_dev->htc_handle,
&hif_dev->interface->dev,
hif_dev->usb_device_id->idProduct,
hif_dev->udev->product,
hif_dev->usb_device_id->driver_info);
complete(&hif_dev->fw_done);
return;
}
static int ath9k_hif_usb_dev_init(struct hif_device_usb *hif_dev)
{
struct usb_host_interface *alt = &hif_dev->interface->altsetting[0];
struct usb_endpoint_descriptor *endp;
int ret, idx;
ret = ath9k_hif_usb_download_fw(hif_dev);
ret = ath9k_hif_usb_alloc_urbs(hif_dev);
return 0;
}
static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev)
{
int transfer, err;
const void *data = hif_dev->firmware->data;
size_t len = hif_dev->firmware->size;
u32 addr = AR9271_FIRMWARE;
u8 *buf = kzalloc(4096, GFP_KERNEL);
u32 firm_offset;
if (!buf)
return -ENOMEM;
while (len) {
transfer = min_t(size_t, len, 4096);
memcpy(buf, data, transfer);
//通過控制傳輸將韌體傳遞給usb裝置
//FIRMWARE_DOWNLOAD並非標準的request,可能是廠家自己定義的
//我們構造urb時無需關心端點的最大包大小,雖然確實是由多次傳輸構成的
//比如主機請求一個長度16位元組的描述符,裝置收到請求之後會分兩次傳送給主機
err = usb_control_msg(hif_dev->udev,
usb_sndctrlpipe(hif_dev->udev, 0),
FIRMWARE_DOWNLOAD, 0x40 | USB_DIR_OUT,
addr >> 8, 0, buf, transfer, HZ);
len -= transfer;
data += transfer;
addr += transfer;
}
kfree(buf);
if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info))
firm_offset = AR7010_FIRMWARE_TEXT;
else
firm_offset = AR9271_FIRMWARE_TEXT;
//通知裝置韌體下載完成
err = usb_control_msg(hif_dev->udev, usb_sndctrlpipe(hif_dev->udev, 0),
FIRMWARE_DOWNLOAD_COMP,
0x40 | USB_DIR_OUT,
firm_offset >> 8, 0, NULL, 0, HZ);
return 0;
}
//資料傳輸4個同的端點
#define USB_WLAN_TX_PIPE 1
#define USB_WLAN_RX_PIPE 2
#define USB_REG_IN_PIPE 3
#define USB_REG_OUT_PIPE 4
static int ath9k_hif_usb_alloc_urbs(struct hif_device_usb *hif_dev)
{
init_usb_anchor(&hif_dev->regout_submitted);
//分配一個tx_buf繫結一個urb,把tx_buf掛入hif_dev->tx.tx_buf連結串列
ath9k_hif_usb_alloc_tx_urbs(hif_dev)
ath9k_hif_usb_alloc_rx_urbs(hif_dev)
ath9k_hif_usb_alloc_reg_in_urbs(hif_dev)
return 0;
}
資料urb,有資料到來呼叫 ath9k_hif_usb_rx_cb
static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev)
{
struct urb *urb = NULL;
struct sk_buff *skb = NULL;
int i, ret;
init_usb_anchor(&hif_dev->rx_submitted);
spin_lock_init(&hif_dev->rx_lock);
for (i = 0; i < MAX_RX_URB_NUM; i++) {
urb = usb_alloc_urb(0, GFP_KERNEL);
skb = alloc_skb(MAX_RX_BUF_SIZE, GFP_KERNEL);
//提交urb 有資料到來時呼叫 ath9k_hif_usb_rx_cb
usb_fill_bulk_urb(urb, hif_dev->udev,
usb_rcvbulkpipe(hif_dev->udev,
USB_WLAN_RX_PIPE),
skb->data, MAX_RX_BUF_SIZE,
ath9k_hif_usb_rx_cb, skb);
usb_anchor_urb(urb, &hif_dev->rx_submitted);
ret = usb_submit_urb(urb, GFP_KERNEL);
//當urb的引用計數為0時釋放記憶體
usb_free_urb(urb);
}
return 0;
}
控制資料的urb ?,有控制資料到來呼叫 ath9k_hif_usb_reg_in_cb
static int ath9k_hif_usb_alloc_reg_in_urbs(struct hif_device_usb *hif_dev)
{
struct urb *urb = NULL;
struct sk_buff *skb = NULL;
int i, ret;
init_usb_anchor(&hif_dev->reg_in_submitted);
for (i = 0; i < MAX_REG_IN_URB_NUM; i++) {
urb = usb_alloc_urb(0, GFP_KERNEL);
skb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_KERNEL);
usb_fill_bulk_urb(urb, hif_dev->udev,
usb_rcvbulkpipe(hif_dev->udev,
USB_REG_IN_PIPE),
skb->data, MAX_REG_IN_BUF_SIZE,
ath9k_hif_usb_reg_in_cb, skb);
usb_anchor_urb(urb, &hif_dev->reg_in_submitted);
ret = usb_submit_urb(urb, GFP_KERNEL);
usb_free_urb(urb);
}
return 0;
}
int ath9k_htc_hw_init(struct htc_target *target,
struct device *dev, u16 devid,
char *product, u32 drv_info)
{
if (ath9k_htc_probe_device(target, dev, devid, product, drv_info)) {
pr_err("Failed to initialize the device\n");
return -ENODEV;
}
return 0;
}
int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev,
u16 devid, char *product, u32 drv_info)
{
struct ieee80211_hw *hw;
struct ath9k_htc_priv *priv;
int ret;
//分配一個無線裝置結構,繫結一個操作函式集
hw = ieee80211_alloc_hw(sizeof(struct ath9k_htc_priv), &ath9k_htc_ops);
priv = hw->priv;
priv->hw = hw;
priv->htc = htc_handle;
priv->dev = dev;
htc_handle->drv_priv = priv;
SET_IEEE80211_DEV(hw, priv->dev);
ret = ath9k_htc_wait_for_target(priv);
priv->wmi = ath9k_init_wmi(priv);
ret = ath9k_init_htc_services(priv, devid, drv_info);
ret = ath9k_init_device(priv, devid, product, drv_info);
return 0;
}
ath9k_htc_probe_device(target, dev, devid, product, drv_info))
//在核心中wiphy(包括local)來描述一個線裝置,在這裡構造一個,並指定操作函式
ieee80211_alloc_hw(sizeof(struct ath9k_htc_priv), &ath9k_htc_ops);
struct wiphy * wiphy = wiphy_new(&mac80211_config_ops, priv_size);
struct ieee80211_local *local = wiphy_priv(wiphy);
local->ops = ops;
tasklet_init(&local->tx_pending_tasklet, ieee80211_tx_pending,(unsigned long)local);
tasklet_init(&local->tasklet,ieee80211_tasklet_handler,(unsigned long) local);
ath9k_init_device(priv, devid, product, drv_info);
ath9k_init_priv(priv, devid, product, drv_info);
tasklet_init(&priv->rx_tasklet, ath9k_rx_tasklet, (unsigned long)priv);
ieee80211_register_hw(hw); //硬體無關,無線子系統幫我們構造了一個netdevice提供統一的ops:ieee80211_dataif_ops
wiphy_register(local->hw.wiphy);
ieee80211_if_add(local, "wlan%d", NULL,NL80211_IFTYPE_STATION, NULL);
alloc_netdev_mqs(sizeof(*sdata) + local->hw.vif_data_size,name, ieee80211_if_setup, txqs, 1);
ieee80211_setup_sdata(sdata, type);
dev->netdev_ops = &ieee80211_dataif_ops;
register_netdevice(ndev);
struct ieee80211_ops ath9k_htc_ops = {
.tx = ath9k_htc_tx,
.start = ath9k_htc_start,
.stop = ath9k_htc_stop,
.add_interface = ath9k_htc_add_interface,
.remove_interface = ath9k_htc_remove_interface,
.config = ath9k_htc_config,
.configure_filter = ath9k_htc_configure_filter,
.sta_add = ath9k_htc_sta_add,
.sta_remove = ath9k_htc_sta_remove,
.conf_tx = ath9k_htc_conf_tx,
.bss_info_changed = ath9k_htc_bss_info_changed,
.set_key = ath9k_htc_set_key,
.get_tsf = ath9k_htc_get_tsf,
.set_tsf = ath9k_htc_set_tsf,
.reset_tsf = ath9k_htc_reset_tsf,
.ampdu_action = ath9k_htc_ampdu_action,
.sw_scan_start = ath9k_htc_sw_scan_start,
.sw_scan_complete = ath9k_htc_sw_scan_complete,
.set_rts_threshold = ath9k_htc_set_rts_threshold,
.rfkill_poll = ath9k_htc_rfkill_poll_state,
.set_coverage_class = ath9k_htc_set_coverage_class,
.set_bitrate_mask = ath9k_htc_set_bitrate_mask,
.get_stats = ath9k_htc_get_stats,
};
資料包傳送過程
協議上層傳送資料包過來,首先是到無線子系統幫我們註冊的netdevice這裡,呼叫
static const struct net_device_ops ieee80211_dataif_ops = {
.ndo_open = ieee80211_open,
.ndo_stop = ieee80211_stop,
.ndo_uninit = ieee80211_teardown_sdata,
.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,
};
中的 ieee80211_subif_start_xmit
//硬體無關
ieee80211_xmit
=> ieee80211_tx
=> __ieee80211_tx
=> ieee80211_tx_frags
=> drv_tx
local->ops->tx(&local->hw, skb); => ath9k_htc_tx
//硬體相關
static void ath9k_htc_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
ret = ath9k_htc_tx_start(priv, skb, slot, false);
//判斷是資料還是管理包,至於如何分辨的需要分析sk_buff,暫時保留
if (ieee80211_is_data(hdr->frame_control))
ath9k_htc_tx_data(priv, vif, skb, sta_idx, vif_idx, slot, is_cab);
else
ath9k_htc_tx_mgmt(priv, avp, skb, sta_idx, vif_idx, slot);
htc_send(priv->htc, skb);
htc_issue_send(target, skb, skb->len, 0, tx_ctl->epid);
hif_usb_send
//根據資料包的型別,傳送給對應的端點
ret = hif_usb_send_tx(hif_dev, skb);
ret = hif_usb_send_regout(hif_dev, skb);
usb_fill_bulk_urb(urb, hif_dev->udev,
usb_sndbulkpipe(hif_dev->udev,
USB_REG_OUT_PIPE),
skb->data,
skb->len,
hif_usb_regout_cb,
cmd);
資料包接收過程
當一個數據包在空中被無線裝置捕捉到後,硬體將會向核心發出一箇中斷(大部分 PCI 介面的裝置這樣做),或則通過輪詢機制判斷是否有資料到來(如,使用了 USB 介面)。
前者,中斷將會引發中斷處理程式的執行,後者促使特定的接收函式將被呼叫。
一般裝置驅動層的回撥函式不會做太多關於接收資料包的操作,僅僅做資料校驗,為 mac80211 填充接收描述符,然後把資料包推給 mac80211,由 mac80211 來做之後的工作(直接或間接將資料包放入接收佇列)。
資料進入 mac80211 後,將會呼叫 ieee80211_rx 或者其他變種接收函式。在這裡資料路徑和管理路徑也將分開進行。
如果收到的幀是資料,它將被轉換成 802.3 資料幀(通過 __ieee80211_data_to8023 實現),然後該資料幀將通過 netif_receive_skb 交付到網路協議棧。在協議棧中,各層網路協議將會對資料進行解析,識別協議首部。
如果接收到的是控制幀,資料將會由 ieee80211_sta_rx_queued_mgmt 處理。部分控制幀在 mac80211 層就終止,另外一些將會通過 cfg80211 傳送到使用者空間下的管理程式。
例如,身份認證控制幀被 cfg80211_rx_mlme_mgmt 處理,然後通過 nl80211_send_rx_auth 傳送到使用者空間下的 wpa_supplicant ; 相應的關聯響應控制幀被 cfg80211_rx_assoc_resp 處理,並由 nl80211_send_rx_assoc 傳送到使用者空間。
//硬體相關
ath9k_hif_usb_alloc_urbs(hif_dev);
static int ath9k_hif_usb_alloc_reg_in_urbs(struct hif_device_usb *hif_dev)
usb_fill_bulk_urb(urb, hif_dev->udev, usb_rcvbulkpipe(hif_dev->udev, USB_REG_IN_PIPE), skb->data, MAX_REG_IN_BUF_SIZE,
ath9k_hif_usb_reg_in_cb, skb);
static void ath9k_hif_usb_reg_in_cb(struct urb *urb)
ath9k_htc_rx_msg(hif_dev->htc_handle, skb, skb->len, USB_REG_IN_PIPE);
endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv, skb, epid);
static inline int ath9k_htc_connect_svc(struct ath9k_htc_priv *priv,
req.ep_callbacks.rx = ath9k_htc_rxep;
void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb, enum htc_endpoint_id ep_id)
tasklet_schedule(&priv->rx_tasklet);
void ath9k_rx_tasklet(unsigned long data)
ieee80211_rx(priv->hw, skb);
//硬體無關
/*
* This is the receive path handler. It is called by a low level driver when an
* 802.11 MPDU is received from the hardware.
*/
void ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb)
static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,struct sk_buff *skb)
static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,struct sk_buff *skb, bool consume)
static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)
static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx)
static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx)
{
CALL_RXH(ieee80211_rx_h_decrypt)
CALL_RXH(ieee80211_rx_h_check_more_data)
CALL_RXH(ieee80211_rx_h_uapsd_and_pspoll)
CALL_RXH(ieee80211_rx_h_sta_process)
CALL_RXH(ieee80211_rx_h_defragment)
CALL_RXH(ieee80211_rx_h_michael_mic_verify)
/* must be after MMIC verify so header is counted in MPDU mic */
#ifdef CONFIG_MAC80211_MESH
if (ieee80211_vif_is_mesh(&rx->sdata->vif))
CALL_RXH(ieee80211_rx_h_mesh_fwding);
#endif
CALL_RXH(ieee80211_rx_h_amsdu)
CALL_RXH(ieee80211_rx_h_data) //資料
CALL_RXH(ieee80211_rx_h_ctrl); //控制
CALL_RXH(ieee80211_rx_h_mgmt_check)
CALL_RXH(ieee80211_rx_h_action)
CALL_RXH(ieee80211_rx_h_userspace_mgmt)
CALL_RXH(ieee80211_rx_h_action_return)
CALL_RXH(ieee80211_rx_h_mgmt) //管理
}
以資料為例:
ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
err = __ieee80211_data_to_8023(rx, &port_control);
ieee80211_deliver_skb(rx);
dev_queue_xmit(xmit_skb);
netif_receive_skb(skb);
管理過程
理論上,我們可以像資料路徑一樣在使用者空間下通過套接字傳送控制幀。但是目前有很多開發得十分完善的使用者層管理工具能完成這樣的工作。
特別是 wpa_supplicant 和 host_apd 。wpa_supplicant 控制客戶端 STA 模式下無線網路的連線,如掃描發現網路、身份認證、關聯等。
而 host_apd 可以做 AP 。說白了前者就是用來連線熱點,後者用來發射熱點。這些使用者層工具通過 netlink 套接字與核心通訊。
核心中相關的回撥介面是 cfg80211 中的 nl80211 。使用者層的工具通過 netlink 提供的庫(如, NL80211_CMD_TRIGGER_SCAN )將命令傳送到核心。
在核心中,由 nl80211 接收應用層發出的命令。如下程式碼展示了對應繫結情況。
static int __init cfg80211_init(void)
{
err = register_pernet_device(&cfg80211_pernet_ops);
err = wiphy_sysfs_init();
err = register_netdevice_notifier(&cfg80211_netdev_notifier);
err = nl80211_init();
}
int nl80211_init(void)
{
err = genl_register_family_with_ops(&nl80211_fam, nl80211_ops, ARRAY_SIZE(nl80211_ops));
...
}
static struct genl_ops nl80211_ops[] = {
{
.cmd = NL80211_CMD_GET_WIPHY,
.doit = nl80211_get_wiphy,
.dumpit = nl80211_dump_wiphy,
.policy = nl80211_policy,
/* can be retrieved by unprivileged users */
.internal_flags = NL80211_FLAG_NEED_WIPHY,
},
{
.cmd = NL80211_CMD_TRIGGER_SCAN,
.doit = nl80211_trigger_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
...
}
static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
err = rdev->ops->scan(&rdev->wiphy, dev, request);
}
在 mac80211 中, ieee80211_scan 將會具體去實現掃描發現網路的具體細節,最終還是會調到驅動層的操作函式
local->ops->sw_scan_start(&local->hw);