About

Work - Learn - Share

Lê Quang Thành : quangthanh010290@gmail.com | thanhlev@amazon.com.vn

Linux Wi-Fi Driver có gì đặc biệt?

Table of contents

  1. Revision history
  2. Giới thiệu
  3. Data path
  4. Control path
    1. Control path thời kì đồ đá
    2. Control path thời kì đồ sắt
      1. Giao tiếp giữa user và extension
        1. /proc/net/wireless
        2. iwconfig
        3. iwpriv
        4. ioctl
    3. Control path ngày nay
    4. nl80211
    5. cfg8021
    6. cfg80211_ops
    7. mac80211
  5. Flow hoạt động của driver đời mới

Revision history

Revision Date Remark
0.1 Jan-12-2023 Cập nhật Control Path
0.2 Jan-16-2023 Cập nhật Control path ngày nay và Flow hoạt động của driver đời mới

Giới thiệu

Wi-Fi driver là một network driver, nhiệm vụ đầu tiên nó cần làm là đăng ký với Kernel để tạo ra net device

Net device này tương tự như ethernet interface mà Kernel đã hỗ trợ từ lâu. Sau khi khởi tạo thành công thì interface được tạo ra hoạt động giống hệt như một ethernet interface, kernel đối xử với nó như một ethernet interface. Bằng chứng là lệnh ifconfig show ra chúng không có gì khác nhau ngoài cái tên

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.100  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::3e7c:3fff:fe28:a7d1  prefixlen 64  scopeid 0x20<link>
        ether 3c:7c:3f:28:a7:d1  txqueuelen 1000  (Ethernet)
        RX packets 5095768  bytes 5407146907 (5.4 GB)
        RX errors 0  dropped 1  overruns 0  frame 0
        TX packets 2931637  bytes 1322759143 (1.3 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 16  memory 0xa1100000-a1120000

wlan0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether ac:12:03:d9:ca:e7  txqueuelen 1000  (Ethernet)
        RX packets 199840  bytes 19071504 (19.0 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 33979  bytes 3645765 (3.6 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Và tên này thì chúng ta cũng có thể đổi được, không những đổi được tên mà con tên tiếng Việt nữa cơ

sudo ifconfig wlan0 down
sudo ip set name "cu-tèo" dev wlan0
sudo ifconfig "cu-tèo" up

Kết quả nó như thế này

└──▶ ifconfig
cu-tèo: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether ac:12:03:d9:ca:e7  txqueuelen 1000  (Ethernet)
        RX packets 199840  bytes 19071504 (19.0 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 33979  bytes 3645765 (3.6 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Để tăng tính thuyết phục nó là một net device thông thường, mình set thêm địa chỉ IP cho nó.

sudo ifconfig set "cu-tèo" 20.10.20.10 netmask 255.255.255.0

Xem kết quả nào

thanh@pc ~
└──▶ ifconfig
cu-tèo: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 20.10.20.10  netmask 255.255.255.0  broadcast 20.10.20.255
        ether ac:12:03:d9:ca:e7  txqueuelen 1000  (Ethernet)
        RX packets 199840  bytes 19071504 (19.0 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 33979  bytes 3645765 (3.6 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Tiếp theo ta sẽ tìm hiểu xem cách thức truyền nhận dữ liệu của nó như thế nào.

Data path

Kernel sẽ cho driver đăng ký callback để khi có data đến interface hoặc kernel muốn gửi data đến interface thì thông qua callback này. Đường giao tiếp này được gọi là Data path.

Khi Wi-Fi module nhận được dữ liệu từ không gian, nó sẽ thông qua hàm netif_rx() để chuyển dữ liệu này đến netdev core. Và từ đây net core sẽ chuyển nó đến TCP/IP stack để phân phối đến các application ở user space thông qua các socket.

Ở đường dữ liệu ngược lại, application từ user space thông qua socket chuyển dữ liệu đến net core, sau khi đi qua một loại các routing rule, filter rules, chèn header ở mỗi layer của TCP/IP stack, gói tin được Kernel (net-core) chuyển đến interface thông qua callback ndo_start_xmit() Đây là các API của driver, được đăng ký với Kernel ở giai đoạn đăng ký interface thông qua struct

struct net_device_ops

Việc còn lại của driver là chuyển nó đến Wi-Fi module, data này sau đó sẽ được Wi-Fi firmware truyền đi vào không gian.

Nghe có vẽ đơn giản nhỉ, thế nhưng phần còn lại phía sau đây mới làm chúng ta choáng ngợp.

Coi lại cái wireless interface xem nào:

thanh@pc ~
└──▶ ifconfig
cu-tèo: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 20.10.20.10  netmask 255.255.255.0  broadcast 20.10.20.255
        ether ac:12:03:d9:ca:e7  txqueuelen 1000  (Ethernet)
        RX packets 199840  bytes 19071504 (19.0 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 33979  bytes 3645765 (3.6 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Cài đặt Layer 3 sẵn sàng rồi, kiểm tra Layer 2 xem đã có Link connection chưa nào.

ip link l

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP ....
3: cu-tèo: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN ...

state DOWN ... Thôi toang, quên mất nếu là ethernet thì cắm sợi dây vô switch hoặc router đang hoạt động thì có kết nối ngay (link connection), còn cái “cu-tèo” interface kia thì làm sao?

Bể khổ bắt đầu từ đây, thế là Wi-Fi driver phải nghĩ ra cách để tạo được Link connection đến thiết bị khác. Thời Wi-Fi “tiền sử“, không có tiêu chuẩn gì cả (chưa có khái niệm STA, AP, WPA …), driver làm gì cũng được miễn là tạo được link connection với một thiết bị khác, vậy nên mỗi hãng tự viết các tiêu chuẩn riêng

Để tạo được kết nối, Wi-Fi driver tạo ra một đường giao tiếp giữa module Wi-Fi và người dùng, và đường giao tiếp này gọi là Control Path dùng để cấu hình cho thiết bị và lấy thông tin từ thiết bị.

Control path

Để 2 module kết nối với nhau cả hai cần những thiết lập như Channel, SSID/Password (này mình lấy cho dễ hiễu, SSID mới xuất hiện sau này thôi.)

Control path thời kì đồ đá

Như tiêu đề, giai đoạn này mỗi hãng tự viết tiêu chuẩn riêng để cấu hình cho module Wi-Fi của mình. Một ví dụ điển hình là các USB hoặc module 3/4/5G hiện nay.

Các hãng sẽ viết các phần mềm để giao tiếp với thiết bị qua control path, các module đời cũ thì dùng qua tập lệnh AT, cao cấp hơn thì qua web socket …

Control path thời kì đồ sắt

Nhận thấy sự phân mãnh của các driver cũng như phần mềm cấu hình cho module Wi-Fi, năm 1996 ông Jean Tourrilhes giới thiệu bộ công cụ Wireless Extensions for Linux, bộ công cụ này gồm ba phần:

Khó khăn nhất là ở phần 3 vì các hãng luôn muốn độc quyền sản phẩm của mình hơn là đi theo tiêu chuẩn. Đầu tiên thì cần phải có vài hãng tiên phong, sau đó nếu doanh thu bị ảnh hưởng thì các hãng lớn buộc phải hỗ trợ.

Giao tiếp giữa user và extension

/proc/net/wireless
 cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo: 5390764   56790    0    0    0     0          0         0  5390764   56790    0    0    0     0       0          0
  eth0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
 wlan0:  169874  716173    0   59    0     0          0         0    14152    1103    0    0    0     0       0          0
 wlan1:    1610      10    0    0    0     0          0         0    13537      95    0    1    0     0       0          0
 wlan2:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
 wlan3:   16106     207    0    0    0     0          0         0     1722      11    0    0    0     0       0          0
 wlan4:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
docker0:      0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
iwconfig
thanh@dell ~
└──▶ iwconfig  --help
Usage: iwconfig [interface]
                interface essid {NNN|any|on|off}
                interface mode {managed|ad-hoc|master|...}
                interface freq N.NNN[k|M|G]
                interface channel N
                interface bit {N[k|M|G]|auto|fixed}
                interface rate {N[k|M|G]|auto|fixed}
                interface enc {NNNN-NNNN|off}
                interface key {NNNN-NNNN|off}
                interface power {period N|timeout N|saving N|off}
                interface nickname NNN
                interface nwid {NN|on|off}
                interface ap {N|off|auto}
                interface txpower {NmW|NdBm|off|auto}
                interface sens N
                interface retry {limit N|lifetime N}
                interface rts {N|auto|fixed|off}
                interface frag {N|auto|fixed|off}
                interface modulation {11g|11a|CCK|OFDMg|...}
                interface commit
       Check man pages for more details.
└──▶ iwconfig wlan0
wlan0     IEEE 802.11  ESSID:"home_ssid"
          Mode:Managed  Frequency:5.18 GHz  Access Point: xx:xx:xx:xx:xx:xx
          Bit Rate=866.7 Mb/s   Tx-Power=22 dBm
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:on
          Link Quality=54/70  Signal level=-56 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:0  Invalid misc:597   Missed beacon:0

Linux quản lý network stack bằng cách dùng một cấu trúc (device structure) để kiểm soát mỗi net device trong hệ thống. Phần đầu tiên của structure này là tiêu chuẩn và chứa các thông số như địa chỉ base I/O của thiết bị, địa chỉ IP…, các callback (start device, gửi-nhận packets…)

Jean Tourrilhes đã thêm một callback khác get_wireless_stats để lấy các thông số wireless cho file /proc/net/wireless. Khi Linux tạo thư muc /proc, nó sẽ gọi callback này trên tất cả các net device đang hiện diện trong hệ thống. Nếu một device không đăng ký callback này thì sẽ được bỏ qua (không ảnh hưởng đến hoạt động của các ethernet device)

Khi được gọi, driver có nhiệm vụ giao tiếp với firmware trên Wi-Fi module để lấy thông tin và trả về cho Kernel thông tin thông qua struct iw_statistics

/* ------------------------ WIRELESS STATS ------------------------ */
/*
 * Wireless statistics (used for /proc/net/wireless)
 */
struct iw_statistics {
	__u16		status;		/* Status
					 * - device dependent for now */

	struct iw_quality	qual;		/* Quality of the link
						 * (instant/mean/max) */
	struct iw_discarded	discard;	/* Packet discarded counts */
	struct iw_missed	miss;		/* Packet missed counts */
};

Ví dụ trên Ubuntu Desktop 22.04

cat /proc/net/wireless
Inter- | sta-|   Quality        |   Discarded packets               | Missed | WE
 face  | tus | link level noise |  nwid  crypt   frag  retry   misc | beacon | 22
 wlan0: 0000   76.  -61.   -256.       0      0      0      0      0        0
 wlan1: 0000    0.  -256.  -256.       0      0      0      0      0        0
 wlan2: 0000    0.  -256.  -256.       0      0      0      0      0        0
 wlan3: 0000    1.  -99.   -256.       0      0      0      0      0        0
iwpriv

Trong khi iwconfig yêu cầu các driver cần phải hỗ trợ để làm việc được với nhau thì iwprive giúp mỗi hãng có cách để cấu hình riêng cho driver của mình mà có thể không xuất hiện trên các driver của hảng khác hoặc dòng chip khác.

ioctl

ioctl thông thường được dùng để user-space giao tiếp với Kernel thông qua file descriptor, tuy nhiên vẫn có thể áp dụng với network socket. Công cụ ifconfig đang sử dụng phương pháp này để giao tiếp với Kernel

Ví dụ để thay đổi địa chỉ IP trên interface eth2, ifconfig có thể tạo ra 1 ioctl với các tham số sau:

"eth2", SIOCSIFADDR, new address

Cấu trúc dữ liệu cho lời gọi trên nằm ở /usr/include/linux/if.h. Các option cho các chức năng khác: vlan, routing, arp, bonding, bridge nằm trong file /usr/include/linux/sockios.h

/* Routing table calls. */
/* Socket configuration         controls. */
/* ARP cache control calls. */
/* RARP cache control calls. */
/* Driver configuration calls */
/* DLCI configuration calls */
/* bonding calls */
/* bridge calls */

Jean Tourrilhes đã thêm các IOCTL sau (/usr/include/linux/wireless.h):

/* -------------------------- IOCTL LIST -------------------------- */

/* Wireless Identification */
/* Basic operations */
/* Informative stuff */
/* Spy support (statistics per MAC address - used for Mobile IP support) */
/* Access Point manipulation */
/* 802.11 specific support */
/* Other parameters useful in 802.11 and some other devices */
/* Encoding stuff (scrambling, hardware security, WEP...) */
/* Power saving stuff (power management, unicast and multicast) */
/* WPA : IEEE 802.11 MLME requests */
/* WPA : Authentication mode parameters */
/* WPA : Extended version of encoding configuration */
/* WPA2 : PMKSA cache management */

Control path ngày nay

Nhu cầu sử dụng Wi-Fi là cực kì lớn, hầu như xuất hiện ở mỗi gia đình, mỗi thiết bị. Kéo theo nó là nhiều công nghệ, nhiều tiêu chuẩn được sinh ra. Điều này cũng tạo ra áp lực cho các nhà phát triển phần mềm, với số lượng tính năng ngày càng nhiều, nhiều doanh nghiệp sử dụng. Họ cần đãm bảo ít lỗi nhất trong các bản release.

Nhìn vào prototype chung của giao tiếp ioctl

    int ioctl(int fd, unsigned long cmd, ...);

Đặc điểm của hàm này làm người review sẽ không biết hàm đó làm gì, không biết rõ có bao nhiêu tham số, kiểu dữ liệu của mỗi tham số. Điều này gây khó khăn cho việc kiểm lỗi quá trình biên dịch và tạo document.

Ngoài ra prototype trên cũng khó hiểu. Các kỹ sư đã cho ra đời 1 thiết kế mới nl80211, đọc là netlink 80211

nl80211

Đặt ra các tiêu chuẩn để giao tiếp giữa user-space và kernel(thay thế cách dùng ioctl của wireless-extension). Các “khách hàng” nổi tiếng đang sử dụng tiêu chuẩn nl80211 có thể kể tên như: iw, hostapd, wpa_supplicant, Network Manager, iwd…

cfg8021

Thiết kế mới của driver theo chuẩn của nl80211, chứa các API phục vụ cho việc config Linux 802.11, cfg802.11 thay thế các Wireless extension. cfg80211

cfg80211_ops

Là các operation struct dùng cho việc giao tiếp giữa protocol cfg80211 và application code. cfg80211_ops

mac80211

Các module Wi-Fi hiện nay có 2 loại hardware:

mac80211 là một Kernel module hỗ trợ cho các Wi-Fi module không có khả thực hiện L2 Routing, bất kì dữ liệu gì nó nhận được đều chuyển đến cho driver thực hiện bóc tách, filter …

mac80211 thực hiện đăng ký nó với cfg80211 thông qua struct cfg80211_ops

Ví dụ về HW driver đăng ký với mac80211: drivers/net/wireless/iwlwifi/mvm/mac80211.c

Driver đăng ký sử dụng mac80211 thông qua struct ieee80211_ops

Flow hoạt động của driver đời mới

Bước Giải thích
1. Probing the HW Kiểm tra xem Thiết bị Wi-Fi sử dụng hardware nào để chọn driver phù hợp, phổ biến USB và PCI
2. Khởi tạo mac80211 Sau khi chọn được driver, driver gọi hàm ieee80211_allow_hw() để đăng ký mac80211 với cfg80211
3. Khởi tạo wiphy Driver gọi hàm cfg80211_wiphy_new() để đăng ký một physical device cho Wi-Fi

Với tiêu chuẩn nl80211 mới này, các thông tin, mode hỗ trợ, băng tần … driver cần phải hỗ trợ để cung cấp thông tin đầy đủ để user-space application thực hiện cấu hình.

Ví dụ:

thanhle@thanhle ~
└──▶ iw phy0 info
Wiphy phy0
        ...
        Supported interface modes:
                 * IBSS
                 * managed
                 * AP
                 * AP/VLAN
                 * monitor
                 * P2P-client
                 * P2P-GO
                 * P2P-device
        ...
        Supported commands:
                 * new_interface
                 * set_interface
                 * new_key
                 * start_ap
                 * new_station
                 * new_mpath
                 * set_mesh_config
                 * set_bss
                 * authenticate
                 * associate
                 * deauthenticate
                 * disassociate
                 * join_ibss
                 * join_mesh
                 * remain_on_channel
                 * set_tx_bitrate_mask
                 * frame
                 * frame_wait_cancel
                 * set_wiphy_netns
                 * set_channel
                 * start_sched_scan
                 * probe_client
                 * set_noack_map
                 * register_beacons
                 * start_p2p_device
                 * set_mcast_rate
                 * connect
                 * disconnect
                 * channel_switch
                 * set_qos_map
                 * add_tx_ts
                 * set_multicast_to_unicast
       ...
        valid interface combinations:
                 * #{ managed } <= 1, #{ AP, P2P-client, P2P-GO } <= 1, #{ P2P-device } <= 1,
                   total <= 3, #channels <= 2
        HT Capability overrides:
                 * MCS: ff ff ff ff ff ff ff ff ff ff
                 * maximum A-MSDU length
                 * supported channel width
                 * short GI for 40 MHz
                 * max A-MPDU length exponent
                 * min MPDU start spacing