本文是 《qca-wifi-host-cmn objmgr 解析》 的续篇。objmgr 的“骨架”梳理完之后,读代码时最容易卡住的反而是几个更基础的问题:

  • HDD Adapter 到底是什么?
  • PSOC / PDEV / VDEV / PEER 分别是什么?
  • Link Info 是什么?
  • Link Info 和 VDEV 是什么关系?
  • Adapter 和 VDEV 是什么关系?

这一篇就把它们一次讲透:从 Linux 网络设备讲起,一层层往下剥,目标是初学者也能看懂。

This article is a sequel to qca-wifi-host-cmn objmgr Analysis. With the objmgr “skeleton” laid out, what actually trips people up while reading the code turns out to be a few more basic questions:

  • What exactly is the HDD Adapter?
  • What are PSOC / PDEV / VDEV / PEER, respectively?
  • What is Link Info?
  • How do Link Info and VDEV relate to each other?
  • How do Adapter and VDEV relate to each other?

This post answers them all in one go: starting from Linux network devices, we peel off the layers one by one, with the goal of making it understandable even for beginners.

术语表(先扫一眼,读不懂再回来查)

下面这些缩写会在全文反复出现。第一次读不用背,遇到卡壳时回来查即可。

分层 / 模块

缩写 全称 一句话解释
HDD Host Device Driver qcacld-3.0 的 OS 适配层,对接 Linux 的 net_device / cfg80211(代码里文件头写作 “WLAN Host Device Driver”)
UMAC Upper MAC 跨平台的“上层 MAC”协议层,即 qca-wifi-host-cmn,负责 MLME、扫描、连接管理等
LMAC Lower MAC 固件里的“下层 MAC”,负责实时性强的时序、速率控制等
SME Station Management Entity HDD 与 UMAC 之间的“会话管理”入口,HDD 通过 sme_xxx() 调用下层
MLME MAC Sublayer Management Entity 802.11 的 MAC 管理状态机(扫描/认证/关联/断开)
objmgr Object Manager qca-wifi-host-cmn 里的对象管理器,管理 PSOC/PDEV/VDEV/PEER 四层对象
DP Data Path 真正的数据收发路径(core/dp/、qca-wifi-host-cmn/dp/),注意不是 HDD

对象 / 接口

缩写 全称 一句话解释
PSOC Pseudo SoC 一颗 Wi-Fi 芯片(objmgr 顶层对象)
PDEV Physical Device 一个射频子系统(radio / PHY),同一时刻只在一个信道上工作(objmgr 第二层)
VDEV Virtual Device 一个虚拟接口(objmgr 第三层)
PEER Peer 一个对端设备(objmgr 第四层)
VAP Virtual AP 虚拟接入点,即一个虚拟 Wi-Fi 接口(≈ VDEV)
MLD Multi-Link Device Wi-Fi 7 中由多条链路组成的“逻辑设备”
MLO Multi-Link Operation Wi-Fi 7 多链路操作,一个连接同时用多条链路

Linux / 无线子系统

缩写 全称 一句话解释
net_device Network Device Linux 内核里的一张网卡(如 wlan0)
cfg80211 Configuration 802.11 Linux 内核的无线配置框架,iw/wpa_supplicant 通过它下发命令
nl80211 Netlink 802.11 cfg80211 对用户空间暴露的 netlink 接口
wiphy Wireless PHY cfg80211 里描述一个无线硬件(对应 PSOC/PDEV)
wdev Wireless Device cfg80211 里描述一个无线接口(对应一个 VAP)
wext Wireless Extensions 老的无线 ioctl 接口(已被 cfg80211 取代,代码里仍保留兼容)

特性 / 状态

缩写 全称 一句话解释
DBS Dual Band Simultaneous 双频并发,2.4G 和 5G 可同时工作(对应多个 PDEV)
SSR SubSystem Restart 子系统重启,固件崩溃后驱动整体重启恢复
OCB Outside Context of BSS 车联网(V2X)用的通信模式
NDI NAN Data Interface NAN(邻居感知网络)的数据接口
NAN Neighbor Awareness Networking Wi-Fi 邻居感知网络(Wi-Fi Aware)
FTM Factory Test Mode 工厂测试模式

先建立一个直觉:Wi-Fi 驱动在“模拟”什么?

在 Linux 里,一张网卡就是一个 网络设备(net_device),比如 eth0、wlan0。
用户空间(wpa_supplicant、hostapd、iw、ip)看到的是:

1
2
3
wlan0   -> 一个 STA 接口(连路由器)
wlan1 -> 一个 AP 接口(开热点)
p2p0 -> 一个 P2P 接口

但 Wi-Fi 芯片内部并不是“一个接口对应一个硬件”。一颗 Wi-Fi 芯片(SoC)可以同时:

  • 在 2.4G 上做 STA,在 5G 上做 AP(并发 / concurrency)
  • 支持多个虚拟接口(VAP,Virtual AP)
  • 支持 Wi-Fi 7 的 MLO(Multi-Link Operation,一个连接同时用多条链路)

所以驱动内部需要一套分层对象模型来描述“硬件 → radio(射频子系统)→ 虚拟接口 → 对端设备”。
这套模型就是 objmgr,它的四个核心对象是:

1
PSOC  →  PDEV  →  VDEV  →  PEER

而 HDD Adapter 和 Link Info 是 qcacld-3.0(上层 OS 适配层) 里的概念,
它们负责把 Linux 的 net_device 和 objmgr 里的 VDEV 连接起来。

一句话先记住:

Adapter 是 Linux 侧的“网卡接口”,VDEV 是固件/UMAC 侧的“虚拟设备”,Link Info 是两者之间的“桥”。


全景图:一张图看懂所有对象

固件 / 硬件

Linux 内核

用户空间

qca-wifi-host-cmn (UMAC 层)

qcacld-3.0 (HDD 层)

netlink / ioctl

link_info->vdev 指针

vdev_nif.osdev->legacy_osif_priv

wpa_supplicant / hostapd / iw

hdd_context
(全局上下文)

hdd_adapter
(= net_device 私有数据)

wlan_hdd_link_info
(link_info[] 数组)

wlan_objmgr_psoc
(一颗芯片)

wlan_objmgr_pdev
(一个 radio/PHY)

wlan_objmgr_vdev
(一个虚拟接口)

wlan_objmgr_peer
(一个对端设备)

Wi-Fi 芯片

  • 蓝色:objmgr 对象(在 qca-wifi-host-cmn 里,跨平台通用)
  • 橙色:HDD Adapter(在 qcacld-3.0 里,Linux 专用)
  • 绿色:Link Info(在 qcacld-3.0 里,连接 Adapter 和 VDEV)

objmgr 四层对象:PSOC / PDEV / VDEV / PEER

先讲清楚“固件侧”的四个对象。它们定义在:

对象 头文件
PSOC qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_psoc_obj.h
PDEV qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_pdev_obj.h
VDEV qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_vdev_obj.h
PEER qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_peer_obj.h

PSOC —— “一颗 Wi-Fi 芯片”

PSOC = Pseudo SoC(伪 SoC)。它代表一颗物理 Wi-Fi 芯片。

你可以把它理解成“整张网卡”。它保存的是全局的、芯片级别的信息:

1
2
3
4
5
6
7
8
9
/* wlan_objmgr_psoc_obj.h */
struct wlan_objmgr_psoc {
struct wlan_objmgr_psoc_regulatory soc_reg; /* 国家码、regdomain */
struct wlan_objmgr_psoc_nif soc_nif; /* phy_version、FW 能力、MAC 地址 */
struct wlan_objmgr_psoc_objmgr soc_objmgr; /* 对象管理:pdev/vdev/peer 列表 */
struct wlan_soc_southbound_cb soc_cb; /* 南向回调(tx_ops/rx_ops) */
void *soc_comp_priv_obj[WLAN_UMAC_MAX_COMPONENTS]; /* 各组件私有对象 */
...
};

关键点:

  • soc_objmgr.wlan_pdev_list[]:这颗芯片下所有的 PDEV
  • soc_objmgr.wlan_vdev_list[]:这颗芯片下所有的 VDEV(最多 WLAN_UMAC_PSOC_MAX_VDEVS = 51)
  • soc_objmgr.peer_list:这颗芯片下所有的 PEER
  • soc_nif.soc_fw_caps:固件能力位图(比如是否支持 11ax、11be)

类比:PSOC 就像一台“电脑主机”,里面可以插多张“显卡”(PDEV)。

创建入口:wlan_objmgr_psoc_obj_create()(wlan_objmgr_psoc_obj.c)。

PDEV —— “一个射频子系统(radio / PHY)”

PDEV = Physical Device。它代表一个独立的射频子系统(radio / PHY):
一套能调谐到某个信道、同一时刻只在一个信道上收发的完整收发系统(基带 + 若干射频链 + 对应天线)。

一颗芯片可能有多个 radio。比如支持 DBS(Dual Band Simultaneous,双频并发) 的芯片,
可以同时有 2.4G 和 5G 两个独立的 radio,那就是 2 个 PDEV;
单射频芯片(或 DBS 芯片工作在单 MAC 硬件模式下)则只有 1 个 PDEV。

注意:radio 不是天线,也不是一条射频链。2x2 MIMO 指的是一个 radio 内部有 2 条射频链、2 根天线,
它们锁定在同一个信道上并行收发——所以“只有 1 个 PDEV 却支持 2x2 MIMO”是完全正常的。
PDEV 的数量只决定“能同时在几个信道上收发”(并发能力),与 MIMO 阶数(NSS,空间流数)是两个正交的维度。
固件在 service ready 时按**当前硬件模式(hw mode)**上报 radio 数,DBS 激活后才会从 1 个 PDEV 变成 2 个。

1
2
3
4
5
6
7
8
/* wlan_objmgr_pdev_obj.h */
struct wlan_objmgr_pdev {
struct wlan_chan_list *current_chan_list; /* 当前信道列表 */
struct wlan_objmgr_pdev_objmgr pdev_objmgr; /* 对象管理:vdev 列表 */
struct wlan_objmgr_pdev_mlme pdev_mlme; /* PDEV 级 MLME 状态 */
void *pdev_comp_priv_obj[WLAN_UMAC_MAX_COMPONENTS];
...
};

关键点:

  • pdev_objmgr.wlan_vdev_list:这个 radio 上创建的所有 VDEV
  • pdev_objmgr.wlan_psoc:反向指针,指回所属的 PSOC
  • pdev_objmgr.max_vdev_count = WLAN_UMAC_PDEV_MAX_VDEVS = 17

类比:PDEV 就像一张“显卡”,它有自己的“显存”(信道、硬件能力),
上面可以跑多个“程序”(VDEV)。

创建入口:wlan_objmgr_pdev_obj_create()(wlan_objmgr_pdev_obj.c)。

VDEV —— “一个虚拟接口”

VDEV = Virtual Device。它代表一个虚拟 Wi-Fi 接口,也就是我们常说的 VAP(Virtual AP)。

一个 VDEV 对应一种“角色(opmode)”:

opmode 含义
QDF_STA_MODE 客户端(连路由器)
QDF_SAP_MODE SoftAP(开热点)
QDF_P2P_CLIENT_MODE P2P 客户端
QDF_P2P_GO_MODE P2P GO
QDF_P2P_DEVICE_MODE P2P Device
QDF_MONITOR_MODE 监听模式
QDF_OCB_MODE 车联网 OCB
QDF_NDI_MODE NAN Data Interface
1
2
3
4
5
6
7
8
9
/* wlan_objmgr_vdev_obj.h */
struct wlan_objmgr_vdev {
qdf_list_node_t vdev_node;
struct wlan_objmgr_vdev_mlme vdev_mlme; /* opmode、状态机、信道、MAC */
struct wlan_objmgr_vdev_objmgr vdev_objmgr; /* vdev_id、peer 列表、pdev 指针 */
struct wlan_objmgr_vdev_nif vdev_nif; /* OS 私有指针 osdev */
void *vdev_comp_priv_obj[WLAN_UMAC_MAX_COMPONENTS];
...
};

其中 vdev_mlme 里存着这个接口最核心的信息:

1
2
3
4
5
6
7
8
9
10
11
struct wlan_objmgr_vdev_mlme {
enum QDF_OPMODE vdev_opmode; /* 角色:STA / SAP / ... */
enum wlan_vdev_state mlme_state; /* 状态机状态 */
struct wlan_channel *bss_chan; /* 当前 BSS 信道 */
struct wlan_channel *des_chan; /* 期望信道 */
uint8_t macaddr[QDF_MAC_ADDR_SIZE]; /* 接口 MAC */
uint8_t mldaddr[QDF_MAC_ADDR_SIZE]; /* MLD MAC(MLO 用) */
uint8_t linkaddr[QDF_MAC_ADDR_SIZE]; /* Link MAC(MLO 用) */
uint8_t mlo_link_id; /* MLO link id */
...
};

类比:VDEV 就像显卡上跑的一个“程序窗口”,每个窗口有自己的角色和状态。

创建入口:wlan_objmgr_vdev_obj_create()(wlan_objmgr_vdev_obj.c)。
注意它的第一个参数是 pdev,说明 VDEV 必须挂在某个 PDEV 下。

PEER —— “一个对端设备”

PEER = 对端。它代表一个和你有数据交互的远端设备。

  • STA 模式下:PEER 就是你连的那个 AP
  • SAP 模式下:每个连上你热点的手机/电脑都是一个 PEER
  • P2P 模式下:对端设备是 PEER
1
2
3
4
5
6
7
/* wlan_objmgr_peer_obj.h */
struct wlan_objmgr_peer {
uint8_t macaddr[QDF_MAC_ADDR_SIZE]; /* 对端 MAC */
struct wlan_objmgr_peer_mlme peer_mlme;
struct wlan_objmgr_peer_objmgr peer_objmgr; /* 反向指针 vdev */
...
};

类比:PEER 就像“和你通信的那个人”。

创建入口:wlan_objmgr_peer_obj_create(),第一个参数是 vdev,说明 PEER 挂在 VDEV 下。

四层对象的关系图

PSOC
一颗芯片
psoc_id

PDEV 0
2.4G radio

PDEV 1
5G radio

VDEV 0
STA

VDEV 1
SAP

VDEV 2
P2P GO

PEER
连接的 AP

PEER
手机 A

PEER
手机 B

包含关系(1 对多):

1
1 PSOC  ──< N PDEV  ──< N VDEV  ──< N PEER

每个对象里都有反向指针,可以往上找:

  • pdev->pdev_objmgr.wlan_psoc → PSOC
  • vdev->vdev_objmgr.wlan_pdev → PDEV
  • peer->peer_objmgr.vdev → VDEV

HDD Adapter —— Linux 侧的“网卡接口”

现在进入 qcacld-3.0。HDD = Host Device Driver,是 Linux 平台的适配层。
(代码里 qcacld-3.0/core/hdd/inc/*.h 的文件头注释统一写作 “WLAN Host Device Driver”。)

Adapter 的本质:net_device 的私有数据

在 Linux 里创建一个网络接口时,会分配一个 struct net_device。
驱动可以在这块内存后面附加自己的“私有数据”,通过 netdev_priv() 拿到。

qcacld 就是这么做的:

1
2
3
/* wlan_hdd_main.h */
#define WLAN_HDD_GET_PRIV_PTR(__dev__) \
(struct hdd_adapter *)(netdev_priv((__dev__)))

也就是说:

hdd_adapter 就是 net_device 的私有数据。
拿到 net_device 指针,就能拿到 hdd_adapter;反之亦然。

netdev_priv

adapter->dev

struct net_device
(wlan0)

struct hdd_adapter
(netdev_priv)

Adapter 里有什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* qcacld-3.0/core/hdd/inc/wlan_hdd_main.h */
struct hdd_adapter {
uint32_t magic; /* 魔数,用于校验指针有效性 */
qdf_list_node_t node; /* 挂到 hdd_ctx->hdd_adapters 链表 */
struct hdd_context *hdd_ctx; /* 全局上下文(含 psoc/pdev) */
struct net_device *dev; /* 对应的 Linux 网络设备 */
enum QDF_OPMODE device_mode; /* 接口角色:STA/SAP/P2P... */
struct wireless_dev wdev; /* cfg80211 的无线设备 */
struct qdf_mac_addr mac_addr; /* 接口 MAC 地址 */
struct qdf_mac_addr mld_addr; /* MLD MAC(MLO) */

struct wlan_hdd_link_info *deflink; /* 默认 link(指向 link_info[0]) */
struct wlan_hdd_link_info link_info[WLAN_MAX_ML_BSS_LINKS]; /* link 数组 */
...
};

几个关键成员:

成员 作用
dev 对应的 net_device,用户看到的 wlan0
device_mode 这个接口是 STA 还是 AP 还是 P2P
hdd_ctx 全局上下文,里面有 psoc 和 pdev
deflink 默认 link,指向 link_info[0]
link_info[] link 数组,每个元素对应一个 VDEV

hdd_context —— 全局上下文

hdd_adapter 里有个 hdd_ctx,它是整个驱动的“总管家”:

1
2
3
4
5
6
7
8
9
/* wlan_hdd_main.h */
struct hdd_context {
struct wlan_objmgr_psoc *psoc; /* 指向 objmgr 的 PSOC */
struct wlan_objmgr_pdev *pdev; /* 指向 objmgr 的 PDEV */
struct wiphy *wiphy; /* cfg80211 的 wiphy */
qdf_list_t hdd_adapters; /* 所有 adapter 的链表 */
struct hdd_config *config; /* INI 配置 */
...
};

注意:hdd_context 是 HDD 层和 objmgr 层的“接口”。
它同时持有 psoc 和 pdev,所以从任何一个 adapter 都能一路找到 objmgr 对象:

1
2
adapter->hdd_ctx->psoc  →  PSOC
adapter->hdd_ctx->pdev → PDEV

Adapter 的创建

入口是 hdd_open_adapter()(wlan_hdd_main.c)。它会:

  1. 根据 session_type(STA/SAP/P2P…)分配 net_device 和 hdd_adapter
  2. 设置 adapter->device_mode
  3. 注册到内核(hdd_register_interface)
  4. 初始化 link_info[](hdd_adapter_init_link_info)
  5. 把 adapter 挂到 hdd_ctx->hdd_adapters 链表
objmgrLinux net_devicehdd_open_adapter用户空间objmgrLinux net_devicehdd_open_adapter用户空间此时还没有创建 VDEV创建 wlan0 (STA)alloc_netdev + register初始化 link_info[]返回 hdd_adapterifconfig wlan0 uphdd_vdev_create() → sme_vdev_create()返回 wlan_objmgr_vdevhdd_store_vdev_info(link_info, vdev)

重要:Adapter 创建 ≠ VDEV 创建。
Adapter 在接口被创建时就有了(hdd_open_adapter),
而 VDEV 是在接口 up 的时候才创建的(hdd_vdev_create)。
这就是为什么 link_info->vdev 一开始是 NULL。


在 Wi-Fi 6 及以前,一个接口 = 一个 VDEV,关系很简单,直接放个 vdev_id 就够了。

但到了 Wi-Fi 7 MLO,情况变了:

一个接口(一个 net_device)可以同时使用多条链路(link)。
每条链路在固件里都是一个独立的 VDEV。

比如一个 STA 接口 wlan0 同时连到 AP 的 2.4G 和 5G 两个频段:

1
2
3
wlan0 (一个 net_device / 一个 adapter)
├── link 0 → VDEV 5 (2.4G)
└── link 1 → VDEV 6 (5G)

所以 adapter 里不能再只放一个 vdev_id,而要放一个数组,每个元素描述一条链路。
这个数组元素就是 wlan_hdd_link_info。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* qcacld-3.0/core/hdd/inc/wlan_hdd_main.h */
struct wlan_hdd_link_info {
struct hdd_adapter *adapter; /* 反向指针:属于哪个 adapter */
uint8_t vdev_id; /* VDEV 的 id */
qdf_spinlock_t vdev_lock; /* 保护 vdev 指针 */
struct wlan_objmgr_vdev *vdev; /* ★ 指向 objmgr 的 VDEV */
struct completion vdev_destroy_event; /* vdev 销毁同步事件 */
struct qdf_mac_addr link_addr; /* 这条链路的 MAC 地址 */

union {
struct hdd_station_ctx station; /* STA 模式专属上下文 */
struct hdd_ap_ctx ap; /* AP 模式专属上下文 */
struct hdd_monitor_ctx monitor; /* Monitor 模式专属上下文 */
} session;

int8_t rssi; /* 信号强度 */
uint8_t snr; /* 信噪比 */
uint32_t estimated_linkspeed; /* 估计速率 */
struct hdd_stats hdd_stats; /* 统计信息 */
qdf_bitmap(link_flags, WLAN_LINK_FLAG_BITS_MAX); /* 链路标志位 */
...
};

Link Info 是“每条链路”的上下文,它同时持有:

  • 往上的指针:adapter(属于哪个接口)
  • 往下的指针:vdev(对应哪个固件虚拟设备)
  • 链路专属数据:session(STA/AP/Monitor 各自的上下文)、rssi、stats 等

session 联合体:不同角色的专属数据

session 是一个 union,根据接口角色使用不同的成员:

wlan_hdd_link_info

session.station
hdd_station_ctx
连接信息、漫游 profile

session.ap
hdd_ap_ctx
beacon、加密、ACS

session.monitor
hdd_monitor_ctx
监听频率、带宽

访问宏:

1
2
3
#define WLAN_HDD_GET_STATION_CTX_PTR(link_info) (&(link_info)->session.station)
#define WLAN_HDD_GET_AP_CTX_PTR(link_info) (&(link_info)->session.ap)
#define WLAN_HDD_GET_MONITOR_CTX_PTR(link_info) (&(link_info)->session.monitor)

deflink:默认链路

Adapter 里有一个 deflink 指针,永远指向 link_info[0]:

1
2
3
#define WLAN_HDD_DEFLINK_IDX  0
/* hdd_adapter_init_link_info() 里 */
adapter->deflink = &adapter->link_info[WLAN_HDD_DEFLINK_IDX];

对于非 MLO 的普通接口,只有一条链路,所有代码都用 adapter->deflink 就够了。
对于 MLO 接口,才会遍历整个 link_info[] 数组。

遍历宏:

1
2
3
4
5
#define hdd_adapter_for_each_link_info(adapter, link_info) \
for (__hdd_adapter_get_firstlink(adapter, link_info); \
__hdd_is_link_info_valid(link_info) && \
__hdd_is_link_info_idx_valid(adapter, link_info); \
__hdd_adapter_next_link_info(link_info))

Adapter 和 VDEV 是什么关系?

这是最核心的问题。答案是:

Adapter 是 Linux 侧的接口,VDEV 是固件侧的虚拟设备。
一个 Adapter 通过它的 link_info[] 数组,关联到一个或多个 VDEV。

关系图

objmgr 层 (qca-wifi-host-cmn)

HDD 层 (qcacld-3.0)

link_info->vdev

link_info->vdev

未使用 (NULL)

hdd_adapter
wlan0 / device_mode=STA

link_info[0]
deflink

link_info[1]

link_info[2]

wlan_objmgr_vdev
vdev_id=5

wlan_objmgr_vdev
vdev_id=6

三种典型场景

场景 A:普通 STA(非 MLO)

1
2
adapter (wlan0)
└── link_info[0] (deflink) ──> vdev (vdev_id=0)

一个 adapter 一个 vdev,link_info[1]、link_info[2] 都是空的。

场景 B:MLO STA(Wi-Fi 7,多链路)

1
2
3
adapter (wlan0)
├── link_info[0] (deflink) ──> vdev (vdev_id=5, 2.4G)
└── link_info[1] ──> vdev (vdev_id=6, 5G)

一个 adapter 两个 vdev,用户只看到一个 wlan0,但固件里有两个 VDEV。

场景 C:SAP + STA 并发

1
2
adapter (wlan0, STA)  ──> link_info[0] ──> vdev 0
adapter (wlan1, SAP) ──> link_info[0] ──> vdev 1

两个 adapter,各自一个 vdev。它们可能在不同的 PDEV 上(DBS)。

双向查找

Adapter → VDEV(通过 link_info):

1
2
3
4
5
6
7
8
9
/* 拿到默认链路的 vdev */
struct wlan_objmgr_vdev *vdev = adapter->deflink->vdev;

/* 遍历所有链路 */
struct wlan_hdd_link_info *link_info;
hdd_adapter_for_each_link_info(adapter, link_info) {
struct wlan_objmgr_vdev *vdev = link_info->vdev;
...
}

VDEV → Adapter(通过 osdev 私有指针):

1
2
3
4
5
6
7
8
/* wlan_hdd_main.c */
struct wlan_hdd_link_info *
wlan_hdd_get_link_info_from_objmgr(struct wlan_objmgr_vdev *vdev)
{
if (vdev->vdev_nif.osdev)
return vdev->vdev_nif.osdev->legacy_osif_priv;
return NULL;
}

这里的 legacy_osif_priv 就是在 hdd_store_vdev_info() 里设置的:

1
2
3
4
5
6
7
8
9
10
11
12
/* wlan_hdd_main.c */
static void hdd_store_vdev_info(struct wlan_hdd_link_info *link_info,
struct wlan_objmgr_vdev *vdev)
{
struct vdev_osif_priv *osif_priv = wlan_vdev_get_ospriv(vdev);
if (osif_priv) {
osif_priv->wdev = link_info->adapter->dev->ieee80211_ptr;
osif_priv->legacy_osif_priv = link_info; /* ★ 反向指针 */
}
link_info->vdev_id = wlan_vdev_get_id(vdev);
link_info->vdev = vdev; /* ★ 正向指针 */
}

所以:

1
2
3
link_info->vdev                              → VDEV
vdev->vdev_nif.osdev->legacy_osif_priv → link_info
link_info->adapter → Adapter

创建流程(Adapter 和 VDEV 如何绑定)

objmgrSMEHDD用户空间objmgrSMEHDD用户空间1. 创建接口2. 接口 up3. 接口 downhdd_open_adapter()分配 net_device + hdd_adapterhdd_adapter_init_link_info()link_info->vdev = NULLifconfig wlan0 uphdd_vdev_create(link_info)sme_vdev_create(mac_handle, params)wlan_objmgr_vdev_obj_create(pdev, params)返回 vdev返回 vdevhdd_store_vdev_info(link_info, vdev)link_info->vdev = vdevosdev->legacy_osif_priv = link_infoifconfig wlan0 downhdd_vdev_destroy(link_info)link_info->vdev = NULL

一句话:

Link Info 是 VDEV 在 HDD 层的“代理/包装”。
一个 Link Info 对应一个 VDEV(1:1),
但一个 Adapter 可以拥有多个 Link Info(1:N)。

1:1 对应

每个 link_info 里恰好有一个 vdev 指针,每个 VDEV 也恰好被一个 link_info 引用:

objmgr 层

HDD 层

1 : 1

wlan_hdd_link_info
vdev_id / link_addr / rssi / stats

wlan_objmgr_vdev
opmode / mlme_state / bss_chan / peers

职责分工

维度 Link Info(HDD 层) VDEV(objmgr 层)
定位 Linux 适配层 跨平台通用层
关注点 OS 接口、用户可见状态 固件交互、协议状态机
典型数据 rssi、snr、stats、session(STA/AP 上下文) opmode、mlme_state、bss_chan、peer_list
生命周期 随 adapter 创建/销毁 随接口 up/down 创建/销毁
谁创建 hdd_adapter_init_link_info() wlan_objmgr_vdev_obj_create()

因为 objmgr 是跨平台的(Linux / Android / 其他 OS 都能用),
它不能包含 Linux 特有的东西(比如 net_device、wireless_dev、cfg80211)。

所以 qcacld 用 link_info 作为适配层:

  • 把 Linux 的东西(adapter、session)放在 link_info 里
  • 把通用的东西(opmode、mlme_state)放在 vdev 里
  • 两者通过指针互相引用

跨平台通用

Linux 特有

hdd_adapter
net_device / wdev

wlan_hdd_link_info
session / rssi / stats

wlan_objmgr_vdev
opmode / mlme_state

wlan_objmgr_pdev

wlan_objmgr_psoc


完整关系总图

把前面所有内容合起来:

qca-wifi-host-cmn (objmgr)

qcacld-3.0 (HDD)

用户空间

netlink

netlink

vdev

vdev

vdev

psoc

pdev

wpa_supplicant / hostapd

hdd_context
psoc / pdev / wiphy / adapters 链表

hdd_adapter
wlan0 (STA)

hdd_adapter
wlan1 (SAP)

link_info[0]
deflink

link_info[1]

link_info[0]
deflink

wlan_objmgr_psoc
一颗芯片

wlan_objmgr_pdev
2.4G

wlan_objmgr_pdev
5G

vdev 0
STA link0

vdev 1
STA link1

vdev 2
SAP

peer
AP

peer
手机


生命周期对比

不同对象的创建/销毁时机不同,这是初学者最容易踩坑的地方:

对象 创建时机 销毁时机 触发函数
PSOC 驱动 probe / 固件 ready 驱动 remove / SSR wlan_objmgr_psoc_obj_create
PDEV PSOC 创建后 PSOC 销毁时 wlan_objmgr_pdev_obj_create
Adapter 用户创建接口(iw phy ... interface add) 用户删除接口 hdd_open_adapter
Link Info Adapter 创建时(数组一次性分配) Adapter 销毁时 hdd_adapter_init_link_info
VDEV 接口 up 时 接口 down 时 hdd_vdev_create / hdd_vdev_destroy
PEER 连接建立时(关联/认证) 断开连接时 wlan_objmgr_peer_obj_create

驱动 probe

PSOC 创建

PDEV 创建

用户创建接口

Adapter + Link Info 创建

接口 up

VDEV 创建

连接建立

PEER 创建

关键记忆点:

  • Adapter 和 Link Info 是“接口级”的,接口存在它们就存在。
  • VDEV 是“连接级”的,接口 up 才有,down 就没了。
  • 所以代码里访问 link_info->vdev 前,一定要判空!

端到端故事线:跟着一次连接走一遍

前面各节是“分块讲解”。这一节我们把它们串起来,跟着用户的一次真实操作,
看对象是怎么一层层被创建、被引用的。这是理解整套模型最快的方式。

我们假设用户执行:

1
2
3
4
5
6
7
8
# 1. 创建接口(如果还没有)
iw phy phy0 interface add wlan0 type managed

# 2. 拉起接口
ip link set wlan0 up

# 3. 连接一个 AP
iw dev wlan0 connect MyAP

全景时序图

固件objmgrSME / UMACHDD (qcacld-3.0)cfg80211用户空间 (iw)固件objmgrSME / UMACHDD (qcacld-3.0)cfg80211用户空间 (iw)阶段 1:创建接口此时只有 Adapter + Link Info,没有 VDEV阶段 2:接口 up现在 link_info <-> vdev 双向绑定完成阶段 3:连接 AP阶段 4:断开 / downiw phy phy0 interface add wlan0hdd_open_adapter()alloc_netdev + hdd_adapterhdd_adapter_init_link_info()link_info->vdev = NULLip link set wlan0 uphdd_vdev_create(link_info)sme_vdev_create(mac_handle, params)wlan_objmgr_vdev_obj_create(pdev, params)返回 vdev返回 vdevhdd_store_vdev_info(link_info, vdev)link_info->vdev = vdevosdev->legacy_osif_priv = link_infoiw dev wlan0 connect MyAPwlan_hdd_cfg80211_connect()wlan_hdd_cm_connect()adapter = netdev_priv(ndev)vdev = adapter->deflink->vdevosif_cm_connect(ndev, vdev, req, params)扫描 / 认证 / 关联关联成功wlan_objmgr_peer_obj_create(vdev, ...)返回 peer(对端 AP)hdd_cm_connect_complete(vdev, rsp)连接成功iw dev wlan0 disconnectwlan_hdd_cm_disconnect()销毁 peerip link set wlan0 downhdd_vdev_destroy(link_info)销毁 vdevlink_info->vdev = NULL

分阶段拆解

阶段 1:创建接口 —— 只有 Adapter 和 Link Info

步骤 函数 产生的对象
用户敲 iw phy ... interface add cfg80211 → hdd_open_adapter() net_device + hdd_adapter
初始化链路数组 hdd_adapter_init_link_info() link_info[](vdev 全为 NULL)

关键:此时还没有 VDEV。adapter->deflink->vdev == NULL。

阶段 2:接口 up —— 创建 VDEV 并双向绑定

步骤 函数 说明
用户敲 ip link set wlan0 up cfg80211 → hdd_vdev_create(link_info) 传入的是 link_info,不是 adapter
创建固件侧对象 sme_vdev_create() → wlan_objmgr_vdev_obj_create() 在 PDEV 下创建 VDEV
建立双向指针 hdd_store_vdev_info(link_info, vdev) link_info->vdev = vdev;osdev->legacy_osif_priv = link_info

关键:VDEV 是“接口 up”时才有的。这就是为什么访问 link_info->vdev 前必须判空。

阶段 3:连接 AP —— 创建 PEER

步骤 函数 说明
用户敲 iw dev wlan0 connect MyAP cfg80211 → wlan_hdd_cfg80211_connect() cfg80211 的 connect 回调
进入 HDD 连接逻辑 wlan_hdd_cm_connect() adapter = netdev_priv(ndev),vdev = adapter->deflink->vdev
下发到 UMAC osif_cm_connect(ndev, vdev, req, params) 连接管理器开始扫描/认证/关联
关联成功,创建对端 wlan_objmgr_peer_obj_create(vdev, ...) PEER 挂在 VDEV 下
回调通知 HDD hdd_cm_connect_complete(vdev, rsp) 更新 link_info->session.station 等状态

关键:PEER 是“连接建立”时才有的。STA 模式下 PEER 就是那个 AP。

阶段 4:断开 / down —— 逐层销毁

步骤 函数 说明
iw dev wlan0 disconnect wlan_hdd_cm_disconnect() 销毁 PEER
ip link set wlan0 down hdd_vdev_destroy(link_info) 销毁 VDEV,link_info->vdev = NULL
删除接口 hdd_close_adapter() 销毁 Adapter 和 Link Info

一句话记住这条链

1
2
3
4
5
6
7
8
9
用户操作            iw / ip
↓ cfg80211
HDD 入口 hdd_open_adapter / hdd_vdev_create / wlan_hdd_cm_connect
↓
HDD 对象 adapter ──> link_info ──> (vdev 指针)
↓ SME
objmgr 对象 psoc ──> pdev ──> vdev ──> peer
↓
固件 真正收发数据

记忆口诀:

建接口 → 有 Adapter/Link Info;up → 有 VDEV;连上 → 有 PEER;down/断开 → 逆序销毁。


MLO 场景深入(Wi-Fi 7)

MLO 是理解 Link Info 存在意义的关键。我们单独讲一下。

什么是 MLO

MLO(Multi-Link Operation) 是 Wi-Fi 7 的核心特性:
一个 STA 可以同时和 AP 的多条链路(不同频段)通信,聚合带宽、降低延迟。

MLO 下的对象关系

以 MLMR(多链路多射频) 为例:link 0 走 2.4G radio,link 1 走 5G radio,
两条链路各自占一个 PDEV,可以同时收发:

objmgr 层

HDD 层

hdd_adapter
wlan0 (MLO STA)

link_info[0]
link_addr = AA:..:01

link_info[1]
link_addr = AA:..:02

wlan_mlo_dev_context
mld_addr = AA:..:00

PDEV 0
2.4G radio

PDEV 1
5G radio

vdev 5
mlo_link_id=0

vdev 6
mlo_link_id=1

MLO 形态与 PDEV 的关系:link 是 VDEV 层面的概念,但每个 VDEV 仍然必须挂在某个 PDEV(radio)下:

  • MLMR(多链路多射频):每个 link 各占一个 PDEV,链路同时收发——即上图;
  • MLSR / EMLSR(单射频):所有 link 共用同一个 PDEV,同一时刻只有一条链路在收发(eMLSR 靠在链路间快速切换来降低延迟);
  • EMLMR(增强多射频):radio 数少于 link 数,PDEV 在链路间动态指派。

代码里 wlan_mlo_get_pdev_hw_link_id() / wlan_mlo_get_pdev_by_hw_link_id() 就是 link 与 PDEV 之间的映射接口。

三种 MAC 地址

MLO 下每个 VDEV 有三个 MAC 地址,别搞混:

地址 字段 含义
MLD MAC vdev_mlme.mldaddr 整个 MLO 连接的“逻辑地址”,对上层可见
Link MAC vdev_mlme.linkaddr 每条链路的物理地址,对空口可见
Interface MAC vdev_mlme.macaddr 接口地址(非 MLO 时等于 linkaddr)

MLO 相关常量

1
2
3
/* qcacld-3.0/configs/config_to_feature.h */
#define WLAN_MAX_MLD (CONFIG_WLAN_MAX_MLD) /* 默认 2 */
#define WLAN_MAX_ML_BSS_LINKS CONFIG_WLAN_MAX_ML_BSS_LINKS /* 默认 3 */

所以 link_info[] 数组大小默认是 3,即一个 adapter 最多 3 条链路。


速查表 & 常见问题

一句话总结

概念 一句话解释 所在层
PSOC 一颗 Wi-Fi 芯片 objmgr
PDEV 一个射频子系统(radio / PHY) objmgr
VDEV 一个虚拟接口(VAP) objmgr
PEER 一个对端设备 objmgr
Adapter Linux 的一个网络接口(net_device) HDD
Link Info 一条链路的上下文,连接 Adapter 和 VDEV HDD

关系速查

1
2
3
4
5
6
7
PSOC  1 ──< N  PDEV
PDEV 1 ──< N VDEV
VDEV 1 ──< N PEER

Adapter 1 ──< N Link Info
Link Info 1 ── 1 VDEV
Adapter 1 ──< N VDEV (通过 Link Info 间接关联)

常见问题

Q1:Adapter 和 VDEV 是一一对应的吗?

不是。普通接口是 1:1,但 MLO 接口是 1:N(一个 adapter 多个 vdev)。
准确说法是:一个 Link Info 对应一个 VDEV,一个 Adapter 对应一个或多个 Link Info。

Q2:为什么 link_info->vdev 有时候是 NULL?

因为 VDEV 是在接口 up 时才创建的,而 Link Info 在接口创建时就存在了。
接口 down 后 VDEV 被销毁,link_info->vdev 也会被清空。

Q3:adapter->deflink 是什么?

是 link_info[0] 的快捷方式。非 MLO 场景下所有代码都用它。

Q4:怎么从 VDEV 找到 Adapter?

1
2
vdev->vdev_nif.osdev->legacy_osif_priv  →  link_info
link_info->adapter → adapter

Q5:怎么从 Adapter 找到 PSOC/PDEV?

1
2
adapter->hdd_ctx->psoc  →  PSOC
adapter->hdd_ctx->pdev → PDEV

Q6:hdd_context 和 wlan_objmgr_psoc 有什么区别?

hdd_context 是 HDD 层的全局上下文(含 Linux 相关的东西,如 wiphy、config);
wlan_objmgr_psoc 是 objmgr 层的芯片对象(跨平台通用)。
hdd_context 里持有 psoc 和 pdev 指针,是两层的桥梁。


关键代码位置索引

本文涉及的关键代码都位于 WANG-Guangxin/wlan-driver,下表链接固定在提交 da0ed2c,为永久链接:

内容 文件
struct hdd_adapter、struct wlan_hdd_link_info、struct hdd_context、struct hdd_station_ctx、struct hdd_ap_ctx qcacld-3.0/core/hdd/inc/wlan_hdd_main.h
hdd_open_adapter()、hdd_vdev_create()、hdd_vdev_destroy()、hdd_store_vdev_info()、hdd_adapter_init_link_info()、wlan_hdd_get_link_info_from_objmgr() qcacld-3.0/core/hdd/src/wlan_hdd_main.c
wlan_hdd_cfg80211_connect() qcacld-3.0/core/hdd/src/wlan_hdd_cfg80211.c
wlan_hdd_cm_connect()、hdd_cm_connect_complete() qcacld-3.0/core/hdd/src/wlan_hdd_cm_connect.c
wlan_hdd_cm_disconnect() qcacld-3.0/core/hdd/src/wlan_hdd_cm_disconnect.c
osif_cm_connect() qca-wifi-host-cmn/os_if/linux/mlme/src/osif_cm_req.c
struct wlan_objmgr_psoc qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_psoc_obj.h
struct wlan_objmgr_pdev qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_pdev_obj.h
struct wlan_objmgr_vdev qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_vdev_obj.h
struct wlan_objmgr_peer qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_peer_obj.h
wlan_objmgr_vdev_obj_create() qca-wifi-host-cmn/umac/cmn_services/obj_mgr/src/wlan_objmgr_vdev_obj.c
wlan_objmgr_peer_obj_create() qca-wifi-host-cmn/umac/cmn_services/obj_mgr/src/wlan_objmgr_peer_obj.c
struct vdev_osif_priv qca-wifi-host-cmn/os_if/linux/wlan_osif_priv.h
struct wlan_mlo_dev_context qca-wifi-host-cmn/umac/mlo_mgr/inc/wlan_mlo_mgr_public_structs.h

结语

用一句话串起来:

用户空间看到的是 Adapter(net_device);
驱动内部用 Link Info 把 Adapter 和固件的 VDEV 连起来;
VDEV 挂在 PDEV 上,PDEV 挂在 PSOC 上;
每个 VDEV 下挂着若干 PEER(对端设备)。

用户空间
wlan0

Adapter

Link Info

VDEV

PDEV

PSOC

PEER

理解了这套对象模型,再看 qcacld 的代码就会清晰很多:
看到 adapter 就知道是 Linux 接口层,看到 vdev 就知道是固件交互层,
看到 link_info 就知道是在处理某一条具体链路。

Glossary (skim it first, come back when something is unclear)

The abbreviations below recur throughout the article. Don’t memorize them on the first read — come back and check whenever you get stuck.

Layers / Modules

Abbreviation Full name One-sentence explanation
HDD Host Device Driver The OS adaptation layer of qcacld-3.0, interfacing with Linux net_device / cfg80211 (file headers in the code say “WLAN Host Device Driver”)
UMAC Upper MAC The cross-platform “upper MAC” protocol layer, i.e. qca-wifi-host-cmn, responsible for MLME, scanning, connection management, etc.
LMAC Lower MAC The “lower MAC” inside the firmware, handling timing-critical sequencing, rate control, etc.
SME Station Management Entity The “session management” entry between HDD and UMAC; HDD invokes the layers below through sme_xxx()
MLME MAC Sublayer Management Entity The 802.11 MAC management state machine (scan/authentication/association/disconnect)
objmgr Object Manager The object manager inside qca-wifi-host-cmn, managing the four object layers PSOC/PDEV/VDEV/PEER
DP Data Path The actual data TX/RX path (core/dp/, qca-wifi-host-cmn/dp/), note: not HDD

Objects / Interfaces

Abbreviation Full name One-sentence explanation
PSOC Pseudo SoC One Wi-Fi chip (the top-level objmgr object)
PDEV Physical Device One radio subsystem (radio/PHY) that works on only one channel at a time (the second objmgr layer)
VDEV Virtual Device One virtual interface (the third objmgr layer)
PEER Peer One remote device (the fourth objmgr layer)
VAP Virtual AP A virtual access point, i.e. one virtual Wi-Fi interface (≈ VDEV)
MLD Multi-Link Device The “logical device” composed of multiple links in Wi-Fi 7
MLO Multi-Link Operation Wi-Fi 7 multi-link operation: one connection uses multiple links simultaneously

Linux / Wireless Subsystem

Abbreviation Full name One-sentence explanation
net_device Network Device One network card in the Linux kernel (e.g. wlan0)
cfg80211 Configuration 802.11 The kernel’s wireless configuration framework; iw/wpa_supplicant issue commands through it
nl80211 Netlink 802.11 The netlink interface cfg80211 exposes to userspace
wiphy Wireless PHY Describes one wireless hardware device in cfg80211 (corresponds to PSOC/PDEV)
wdev Wireless Device Describes one wireless interface in cfg80211 (corresponds to one VAP)
wext Wireless Extensions The legacy wireless ioctl interface (superseded by cfg80211, still kept for compatibility in the code)

Features / States

Abbreviation Full name One-sentence explanation
DBS Dual Band Simultaneous Dual-band concurrency: 2.4G and 5G can work at the same time (corresponds to multiple PDEVs)
SSR SubSystem Restart Subsystem restart: the driver restarts as a whole to recover after a firmware crash
OCB Outside Context of BSS The communication mode used by V2X (vehicular networking)
NDI NAN Data Interface The data interface of NAN (Neighbor Awareness Networking)
NAN Neighbor Awareness Networking Wi-Fi Neighbor Awareness Networking (Wi-Fi Aware)
FTM Factory Test Mode Factory test mode

First, build an intuition: what is the Wi-Fi driver “simulating”?

In Linux, a network card is a network device (net_device), such as eth0, wlan0.
What userspace (wpa_supplicant, hostapd, iw, ip) sees is:

1
2
3
wlan0   -> a STA interface (connects to a router)
wlan1 -> an AP interface (runs a hotspot)
p2p0 -> a P2P interface

But inside the Wi-Fi chip, it is not “one interface per piece of hardware”. A single Wi-Fi chip (SoC) can simultaneously:

  • act as a STA on 2.4G and an AP on 5G (concurrency)
  • support multiple virtual interfaces (VAP, Virtual AP)
  • support Wi-Fi 7 MLO (Multi-Link Operation: one connection uses multiple links at the same time)

So the driver internally needs a layered object model to describe “hardware → radio → virtual interface → remote device”.
That model is objmgr, whose four core objects are:

1
PSOC  →  PDEV  →  VDEV  →  PEER

HDD Adapter and Link Info are concepts in qcacld-3.0 (the upper OS adaptation layer);
they are responsible for connecting the Linux net_device with the objmgr VDEV.

Remember this one line first:

Adapter is the “network interface” on the Linux side, VDEV is the “virtual device” on the firmware/UMAC side, and Link Info is the “bridge” between the two.


The big picture: one diagram to understand all objects

Firmware / Hardware

Linux kernel

User space

qca-wifi-host-cmn (UMAC layer)

qcacld-3.0 (HDD layer)

netlink / ioctl

link_info->vdev pointer

vdev_nif.osdev->legacy_osif_priv

wpa_supplicant / hostapd / iw

hdd_context
(global context)

hdd_adapter
(= net_device private data)

wlan_hdd_link_info
(link_info[] array)

wlan_objmgr_psoc
(one chip)

wlan_objmgr_pdev
(one radio/PHY)

wlan_objmgr_vdev
(one virtual interface)

wlan_objmgr_peer
(one remote device)

Wi-Fi chip

  • Blue: objmgr objects (in qca-wifi-host-cmn, cross-platform)
  • Orange: HDD Adapter (in qcacld-3.0, Linux-specific)
  • Green: Link Info (in qcacld-3.0, connecting Adapter and VDEV)

The four objmgr layers: PSOC / PDEV / VDEV / PEER

Let’s first clarify the four “firmware-side” objects. They are defined in:

Object Header file
PSOC qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_psoc_obj.h
PDEV qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_pdev_obj.h
VDEV qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_vdev_obj.h
PEER qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_peer_obj.h

PSOC — “one Wi-Fi chip”

PSOC = Pseudo SoC. It represents one physical Wi-Fi chip.

You can think of it as “the whole network card”. It holds global, chip-level information:

1
2
3
4
5
6
7
8
9
/* wlan_objmgr_psoc_obj.h */
struct wlan_objmgr_psoc {
struct wlan_objmgr_psoc_regulatory soc_reg; /* country code, regdomain */
struct wlan_objmgr_psoc_nif soc_nif; /* phy_version, FW capabilities, MAC address */
struct wlan_objmgr_psoc_objmgr soc_objmgr; /* object management: pdev/vdev/peer lists */
struct wlan_soc_southbound_cb soc_cb; /* southbound callbacks (tx_ops/rx_ops) */
void *soc_comp_priv_obj[WLAN_UMAC_MAX_COMPONENTS]; /* per-component private objects */
...
};

Key points:

  • soc_objmgr.wlan_pdev_list[]: all PDEVs under this chip
  • soc_objmgr.wlan_vdev_list[]: all VDEVs under this chip (at most WLAN_UMAC_PSOC_MAX_VDEVS = 51)
  • soc_objmgr.peer_list: all PEERs under this chip
  • soc_nif.soc_fw_caps: the firmware capability bitmap (e.g. whether 11ax/11be is supported)

Analogy: PSOC is like a “computer host” — you can plug multiple “graphics cards” (PDEVs) into it.

Creation entry: wlan_objmgr_psoc_obj_create() (wlan_objmgr_psoc_obj.c).

PDEV — “one radio subsystem (radio / PHY)”

PDEV = Physical Device. It represents one independent radio subsystem (radio / PHY):
a complete TX/RX system (baseband + a number of RF chains + their antennas) that tunes to one channel and transmits and receives on only one channel at a time.

A chip can have multiple radios. For example, a chip supporting DBS (Dual Band Simultaneous)
can have two independent radios, 2.4G and 5G, working at the same time — that means 2 PDEVs;
a single-radio chip (or a DBS chip operating in single-MAC hardware mode) has only 1 PDEV.

Note: a radio is not an antenna, and not a single RF chain either. 2x2 MIMO means one radio has 2 RF chains and 2 antennas inside,
locked onto the same channel for parallel TX/RX — so “only 1 PDEV yet 2x2 MIMO” is perfectly normal.
The number of PDEVs only determines “how many channels you can be on at the same time” (concurrency), which is orthogonal to the MIMO order (NSS, spatial streams).
The firmware reports the radio count at service ready according to the currently active hardware mode (hw mode); only after DBS is activated does the driver go from 1 PDEV to 2.

1
2
3
4
5
6
7
8
/* wlan_objmgr_pdev_obj.h */
struct wlan_objmgr_pdev {
struct wlan_chan_list *current_chan_list; /* current channel list */
struct wlan_objmgr_pdev_objmgr pdev_objmgr; /* object management: vdev list */
struct wlan_objmgr_pdev_mlme pdev_mlme; /* PDEV-level MLME state */
void *pdev_comp_priv_obj[WLAN_UMAC_MAX_COMPONENTS];
...
};

Key points:

  • pdev_objmgr.wlan_vdev_list: all VDEVs created on this radio
  • pdev_objmgr.wlan_psoc: back pointer to the PSOC it belongs to
  • pdev_objmgr.max_vdev_count = WLAN_UMAC_PDEV_MAX_VDEVS = 17

Analogy: PDEV is like a “graphics card” — it has its own “video memory” (channels, radio capabilities),
and multiple “programs” (VDEVs) can run on it.

Creation entry: wlan_objmgr_pdev_obj_create() (wlan_objmgr_pdev_obj.c).

VDEV — “one virtual interface”

VDEV = Virtual Device. It represents one virtual Wi-Fi interface, i.e. what we usually call a VAP (Virtual AP).

Each VDEV corresponds to one “role (opmode)”:

opmode Meaning
QDF_STA_MODE Client (connects to a router)
QDF_SAP_MODE SoftAP (runs a hotspot)
QDF_P2P_CLIENT_MODE P2P client
QDF_P2P_GO_MODE P2P GO
QDF_P2P_DEVICE_MODE P2P Device
QDF_MONITOR_MODE Monitor mode
QDF_OCB_MODE V2X OCB
QDF_NDI_MODE NAN Data Interface
1
2
3
4
5
6
7
8
9
/* wlan_objmgr_vdev_obj.h */
struct wlan_objmgr_vdev {
qdf_list_node_t vdev_node;
struct wlan_objmgr_vdev_mlme vdev_mlme; /* opmode, state machine, channel, MAC */
struct wlan_objmgr_vdev_objmgr vdev_objmgr; /* vdev_id, peer list, pdev pointer */
struct wlan_objmgr_vdev_nif vdev_nif; /* OS private pointer osdev */
void *vdev_comp_priv_obj[WLAN_UMAC_MAX_COMPONENTS];
...
};

Among them, vdev_mlme holds the most core information of this interface:

1
2
3
4
5
6
7
8
9
10
11
struct wlan_objmgr_vdev_mlme {
enum QDF_OPMODE vdev_opmode; /* role: STA / SAP / ... */
enum wlan_vdev_state mlme_state; /* state machine state */
struct wlan_channel *bss_chan; /* current BSS channel */
struct wlan_channel *des_chan; /* desired channel */
uint8_t macaddr[QDF_MAC_ADDR_SIZE]; /* interface MAC */
uint8_t mldaddr[QDF_MAC_ADDR_SIZE]; /* MLD MAC (for MLO) */
uint8_t linkaddr[QDF_MAC_ADDR_SIZE]; /* Link MAC (for MLO) */
uint8_t mlo_link_id; /* MLO link id */
...
};

Analogy: VDEV is like a “program window” running on the graphics card; each window has its own role and state.

Creation entry: wlan_objmgr_vdev_obj_create() (wlan_objmgr_vdev_obj.c).
Note that its first argument is pdev, which means a VDEV must hang under some PDEV.

PEER — “one remote device”

PEER = remote end. It represents a remote device that exchanges data with you.

  • In STA mode: the PEER is the AP you are connected to
  • In SAP mode: every phone/computer connected to your hotspot is a PEER
  • In P2P mode: the remote device is a PEER
1
2
3
4
5
6
7
/* wlan_objmgr_peer_obj.h */
struct wlan_objmgr_peer {
uint8_t macaddr[QDF_MAC_ADDR_SIZE]; /* peer MAC */
struct wlan_objmgr_peer_mlme peer_mlme;
struct wlan_objmgr_peer_objmgr peer_objmgr; /* back pointer vdev */
...
};

Analogy: PEER is like “the person you are talking to”.

Creation entry: wlan_objmgr_peer_obj_create(); its first argument is vdev, which means a PEER hangs under a VDEV.

Relationship diagram of the four layers

PSOC
one chip
psoc_id

PDEV 0
2.4G radio

PDEV 1
5G radio

VDEV 0
STA

VDEV 1
SAP

VDEV 2
P2P GO

PEER
connected AP

PEER
Phone A

PEER
Phone B

Containment (1-to-many):

1
1 PSOC  ──< N PDEV  ──< N VDEV  ──< N PEER

Every object also has a back pointer so you can walk upward:

  • pdev->pdev_objmgr.wlan_psoc → PSOC
  • vdev->vdev_objmgr.wlan_pdev → PDEV
  • peer->peer_objmgr.vdev → VDEV

HDD Adapter — the “network interface” on the Linux side

Now we enter qcacld-3.0. HDD = Host Device Driver is the adaptation layer for the Linux platform.
(The file header comments under qcacld-3.0/core/hdd/inc/*.h uniformly say “WLAN Host Device Driver”.)

The essence of Adapter: the private data of net_device

When creating a network interface in Linux, a struct net_device is allocated.
The driver can append its own “private data” after that memory and retrieve it via netdev_priv().

That is exactly what qcacld does:

1
2
3
/* wlan_hdd_main.h */
#define WLAN_HDD_GET_PRIV_PTR(__dev__) \
(struct hdd_adapter *)(netdev_priv((__dev__)))

In other words:

hdd_adapter is the private data of net_device.
Given a net_device pointer, you can get the hdd_adapter, and vice versa.

netdev_priv

adapter->dev

struct net_device
(wlan0)

struct hdd_adapter
(netdev_priv)

What’s inside an Adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* qcacld-3.0/core/hdd/inc/wlan_hdd_main.h */
struct hdd_adapter {
uint32_t magic; /* magic number, used to validate the pointer */
qdf_list_node_t node; /* linked into hdd_ctx->hdd_adapters */
struct hdd_context *hdd_ctx; /* global context (contains psoc/pdev) */
struct net_device *dev; /* the corresponding Linux network device */
enum QDF_OPMODE device_mode; /* interface role: STA/SAP/P2P... */
struct wireless_dev wdev; /* cfg80211 wireless device */
struct qdf_mac_addr mac_addr; /* interface MAC address */
struct qdf_mac_addr mld_addr; /* MLD MAC (MLO) */

struct wlan_hdd_link_info *deflink; /* default link (points to link_info[0]) */
struct wlan_hdd_link_info link_info[WLAN_MAX_ML_BSS_LINKS]; /* link array */
...
};

A few key members:

Member Purpose
dev the corresponding net_device, what the user sees as wlan0
device_mode whether this interface is STA, AP, or P2P
hdd_ctx the global context, which contains psoc and pdev
deflink the default link, pointing to link_info[0]
link_info[] the link array; each element corresponds to one VDEV

hdd_context — the global context

Inside hdd_adapter there is an hdd_ctx, the “head butler” of the whole driver:

1
2
3
4
5
6
7
8
9
/* wlan_hdd_main.h */
struct hdd_context {
struct wlan_objmgr_psoc *psoc; /* points to the objmgr PSOC */
struct wlan_objmgr_pdev *pdev; /* points to the objmgr PDEV */
struct wiphy *wiphy; /* cfg80211 wiphy */
qdf_list_t hdd_adapters; /* list of all adapters */
struct hdd_config *config; /* INI configuration */
...
};

Note: hdd_context is the “interface” between the HDD layer and the objmgr layer.
It holds both psoc and pdev, so from any adapter you can find the objmgr objects all the way up:

1
2
adapter->hdd_ctx->psoc  →  PSOC
adapter->hdd_ctx->pdev → PDEV

Adapter creation

The entry point is hdd_open_adapter() (wlan_hdd_main.c). It will:

  1. allocate net_device and hdd_adapter according to session_type (STA/SAP/P2P…)
  2. set adapter->device_mode
  3. register with the kernel (hdd_register_interface)
  4. initialize link_info[] (hdd_adapter_init_link_info)
  5. link the adapter into hdd_ctx->hdd_adapters
objmgrLinux net_devicehdd_open_adapterUser spaceobjmgrLinux net_devicehdd_open_adapterUser spaceNo VDEV has been created yetcreate wlan0 (STA)alloc_netdev + registerinit link_info[]return hdd_adapterifconfig wlan0 uphdd_vdev_create() → sme_vdev_create()return wlan_objmgr_vdevhdd_store_vdev_info(link_info, vdev)

Important: Adapter creation ≠ VDEV creation.
The Adapter exists as soon as the interface is created (hdd_open_adapter),
while the VDEV is only created when the interface is brought up (hdd_vdev_create).
That is why link_info->vdev is NULL at the beginning.


Before Wi-Fi 6, one interface = one VDEV; the relationship was simple, and a single vdev_id was enough.

But with Wi-Fi 7 MLO, things changed:

One interface (one net_device) can use multiple links simultaneously.
Each link is an independent VDEV in the firmware.

For example, a STA interface wlan0 connects to the AP on both 2.4G and 5G at the same time:

1
2
3
wlan0 (one net_device / one adapter)
├── link 0 → VDEV 5 (2.4G)
└── link 1 → VDEV 6 (5G)

So the adapter can no longer hold a single vdev_id; it needs an array, with each element describing one link.
That array element is wlan_hdd_link_info.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* qcacld-3.0/core/hdd/inc/wlan_hdd_main.h */
struct wlan_hdd_link_info {
struct hdd_adapter *adapter; /* back pointer: which adapter it belongs to */
uint8_t vdev_id; /* id of the VDEV */
qdf_spinlock_t vdev_lock; /* protects the vdev pointer */
struct wlan_objmgr_vdev *vdev; /* ★ points to the objmgr VDEV */
struct completion vdev_destroy_event; /* synchronization event for vdev destruction */
struct qdf_mac_addr link_addr; /* MAC address of this link */

union {
struct hdd_station_ctx station; /* STA-mode private context */
struct hdd_ap_ctx ap; /* AP-mode private context */
struct hdd_monitor_ctx monitor; /* Monitor-mode private context */
} session;

int8_t rssi; /* signal strength */
uint8_t snr; /* signal-to-noise ratio */
uint32_t estimated_linkspeed; /* estimated speed */
struct hdd_stats hdd_stats; /* statistics */
qdf_bitmap(link_flags, WLAN_LINK_FLAG_BITS_MAX); /* link flags */
...
};

Link Info is the per-link context; it simultaneously holds:

  • the upward pointer: adapter (which interface it belongs to)
  • the downward pointer: vdev (which firmware virtual device it corresponds to)
  • link-specific data: session (the respective STA/AP/Monitor contexts), rssi, stats, etc.

The session union: role-specific data

session is a union; different members are used depending on the interface role:

wlan_hdd_link_info

session.station
hdd_station_ctx
connection info, roaming profile

session.ap
hdd_ap_ctx
beacon, encryption, ACS

session.monitor
hdd_monitor_ctx
monitor frequency, bandwidth

Access macros:

1
2
3
#define WLAN_HDD_GET_STATION_CTX_PTR(link_info) (&(link_info)->session.station)
#define WLAN_HDD_GET_AP_CTX_PTR(link_info) (&(link_info)->session.ap)
#define WLAN_HDD_GET_MONITOR_CTX_PTR(link_info) (&(link_info)->session.monitor)

The Adapter has a deflink pointer that always points to link_info[0]:

1
2
3
#define WLAN_HDD_DEFLINK_IDX  0
/* inside hdd_adapter_init_link_info() */
adapter->deflink = &adapter->link_info[WLAN_HDD_DEFLINK_IDX];

For an ordinary non-MLO interface there is only one link, so all code just uses adapter->deflink.
Only for MLO interfaces does the code walk the whole link_info[] array.

The iteration macro:

1
2
3
4
5
#define hdd_adapter_for_each_link_info(adapter, link_info) \
for (__hdd_adapter_get_firstlink(adapter, link_info); \
__hdd_is_link_info_valid(link_info) && \
__hdd_is_link_info_idx_valid(adapter, link_info); \
__hdd_adapter_next_link_info(link_info))

What is the relationship between Adapter and VDEV?

This is the most central question. The answer:

Adapter is the interface on the Linux side, VDEV is the virtual device on the firmware side.
One Adapter is associated with one or more VDEVs through its link_info[] array.

Relationship diagram

objmgr layer (qca-wifi-host-cmn)

HDD layer (qcacld-3.0)

link_info->vdev

link_info->vdev

unused (NULL)

hdd_adapter
wlan0 / device_mode=STA

link_info[0]
deflink

link_info[1]

link_info[2]

wlan_objmgr_vdev
vdev_id=5

wlan_objmgr_vdev
vdev_id=6

Three typical scenarios

Scenario A: ordinary STA (non-MLO)

1
2
adapter (wlan0)
└── link_info[0] (deflink) ──> vdev (vdev_id=0)

One adapter, one vdev; link_info[1] and link_info[2] are all empty.

Scenario B: MLO STA (Wi-Fi 7, multi-link)

1
2
3
adapter (wlan0)
├── link_info[0] (deflink) ──> vdev (vdev_id=5, 2.4G)
└── link_info[1] ──> vdev (vdev_id=6, 5G)

One adapter, two vdevs; the user only sees one wlan0, but there are two VDEVs in the firmware.

Scenario C: SAP + STA concurrency

1
2
adapter (wlan0, STA)  ──> link_info[0] ──> vdev 0
adapter (wlan1, SAP) ──> link_info[0] ──> vdev 1

Two adapters, each with one vdev. They may sit on different PDEVs (DBS).

Bidirectional lookup

Adapter → VDEV (via link_info):

1
2
3
4
5
6
7
8
9
/* get the vdev of the default link */
struct wlan_objmgr_vdev *vdev = adapter->deflink->vdev;

/* walk all links */
struct wlan_hdd_link_info *link_info;
hdd_adapter_for_each_link_info(adapter, link_info) {
struct wlan_objmgr_vdev *vdev = link_info->vdev;
...
}

VDEV → Adapter (via the osdev private pointer):

1
2
3
4
5
6
7
8
/* wlan_hdd_main.c */
struct wlan_hdd_link_info *
wlan_hdd_get_link_info_from_objmgr(struct wlan_objmgr_vdev *vdev)
{
if (vdev->vdev_nif.osdev)
return vdev->vdev_nif.osdev->legacy_osif_priv;
return NULL;
}

The legacy_osif_priv here is set in hdd_store_vdev_info():

1
2
3
4
5
6
7
8
9
10
11
12
/* wlan_hdd_main.c */
static void hdd_store_vdev_info(struct wlan_hdd_link_info *link_info,
struct wlan_objmgr_vdev *vdev)
{
struct vdev_osif_priv *osif_priv = wlan_vdev_get_ospriv(vdev);
if (osif_priv) {
osif_priv->wdev = link_info->adapter->dev->ieee80211_ptr;
osif_priv->legacy_osif_priv = link_info; /* ★ back pointer */
}
link_info->vdev_id = wlan_vdev_get_id(vdev);
link_info->vdev = vdev; /* ★ forward pointer */
}

So:

1
2
3
link_info->vdev                              → VDEV
vdev->vdev_nif.osdev->legacy_osif_priv → link_info
link_info->adapter → Adapter

Creation flow (how Adapter and VDEV get bound)

objmgrSMEHDDUser spaceobjmgrSMEHDDUser space1. create the interface2. interface up3. interface downhdd_open_adapter()allocate net_device + hdd_adapterhdd_adapter_init_link_info()link_info->vdev = NULLifconfig wlan0 uphdd_vdev_create(link_info)sme_vdev_create(mac_handle, params)wlan_objmgr_vdev_obj_create(pdev, params)return vdevreturn vdevhdd_store_vdev_info(link_info, vdev)link_info->vdev = vdevosdev->legacy_osif_priv = link_infoifconfig wlan0 downhdd_vdev_destroy(link_info)link_info->vdev = NULL

In one sentence:

Link Info is the “proxy/wrapper” of the VDEV at the HDD layer.
One Link Info corresponds to one VDEV (1:1),
but one Adapter can own multiple Link Infos (1:N).

The 1:1 correspondence

Each link_info holds exactly one vdev pointer, and each VDEV is referenced by exactly one link_info:

objmgr layer

HDD layer

1 : 1

wlan_hdd_link_info
vdev_id / link_addr / rssi / stats

wlan_objmgr_vdev
opmode / mlme_state / bss_chan / peers

Division of responsibility

Dimension Link Info (HDD layer) VDEV (objmgr layer)
Positioning Linux adaptation layer Cross-platform common layer
Focus OS interface, user-visible state Firmware interaction, protocol state machine
Typical data rssi, snr, stats, session (STA/AP context) opmode, mlme_state, bss_chan, peer_list
Lifetime created/destroyed with the adapter created/destroyed as the interface goes up/down
Who creates it hdd_adapter_init_link_info() wlan_objmgr_vdev_obj_create()

Because objmgr is cross-platform (Linux / Android / other OSes can all use it),
it cannot contain Linux-specific things (such as net_device, wireless_dev, cfg80211).

So qcacld uses link_info as the adaptation layer:

  • Linux-specific things (adapter, session) live in link_info
  • Generic things (opmode, mlme_state) live in vdev
  • The two reference each other through pointers

Cross-platform common

Linux-specific

hdd_adapter
net_device / wdev

wlan_hdd_link_info
session / rssi / stats

wlan_objmgr_vdev
opmode / mlme_state

wlan_objmgr_pdev

wlan_objmgr_psoc


The complete relationship diagram

Putting everything above together:

qca-wifi-host-cmn (objmgr)

qcacld-3.0 (HDD)

User space

netlink

netlink

vdev

vdev

vdev

psoc

pdev

wpa_supplicant / hostapd

hdd_context
psoc / pdev / wiphy / adapters list

hdd_adapter
wlan0 (STA)

hdd_adapter
wlan1 (SAP)

link_info[0]
deflink

link_info[1]

link_info[0]
deflink

wlan_objmgr_psoc
one chip

wlan_objmgr_pdev
2.4G

wlan_objmgr_pdev
5G

vdev 0
STA link0

vdev 1
STA link1

vdev 2
SAP

peer
AP

peer
phone


Lifetime comparison

Different objects are created/destroyed at different times — this is where beginners trip up most easily:

Object Created Destroyed Trigger function
PSOC driver probe / firmware ready driver remove / SSR wlan_objmgr_psoc_obj_create
PDEV after PSOC creation when the PSOC is destroyed wlan_objmgr_pdev_obj_create
Adapter user creates the interface (iw phy ... interface add) user deletes the interface hdd_open_adapter
Link Info when the Adapter is created (array allocated at once) when the Adapter is destroyed hdd_adapter_init_link_info
VDEV when the interface goes up when the interface goes down hdd_vdev_create / hdd_vdev_destroy
PEER when the connection is established (association/authentication) when the connection is torn down wlan_objmgr_peer_obj_create

driver probe

PSOC created

PDEV created

user creates the interface

Adapter + Link Info created

interface up

VDEV created

connection established

PEER created

Key takeaways:

  • Adapter and Link Info are “interface-level”: they exist as long as the interface exists.
  • VDEV is “connection-level”: it exists only while the interface is up, and is gone once it goes down.
  • Therefore, before accessing link_info->vdev in code, always check it for NULL!

End-to-end storyline: follow one connection through

The previous sections explained things piece by piece. This section ties them together by following one real user operation and watching how objects get created and referenced layer by layer. This is the fastest way to understand the whole model.

Assume the user runs:

1
2
3
4
5
6
7
8
# 1. create the interface (if not yet present)
iw phy phy0 interface add wlan0 type managed

# 2. bring the interface up
ip link set wlan0 up

# 3. connect to an AP
iw dev wlan0 connect MyAP

The full sequence diagram

FirmwareobjmgrSME / UMACHDD (qcacld-3.0)cfg80211User space (iw)FirmwareobjmgrSME / UMACHDD (qcacld-3.0)cfg80211User space (iw)Phase 1: create the interfaceOnly Adapter + Link Info exist now, no VDEV yetPhase 2: interface uplink_info <-> vdev are now bound bidirectionallyPhase 3: connect to the APPhase 4: disconnect / downiw phy phy0 interface add wlan0hdd_open_adapter()alloc_netdev + hdd_adapterhdd_adapter_init_link_info()link_info->vdev = NULLip link set wlan0 uphdd_vdev_create(link_info)sme_vdev_create(mac_handle, params)wlan_objmgr_vdev_obj_create(pdev, params)return vdevreturn vdevhdd_store_vdev_info(link_info, vdev)link_info->vdev = vdevosdev->legacy_osif_priv = link_infoiw dev wlan0 connect MyAPwlan_hdd_cfg80211_connect()wlan_hdd_cm_connect()adapter = netdev_priv(ndev)vdev = adapter->deflink->vdevosif_cm_connect(ndev, vdev, req, params)scan / authenticate / associateassociation succeedswlan_objmgr_peer_obj_create(vdev, ...)return peer (the remote AP)hdd_cm_connect_complete(vdev, rsp)connectediw dev wlan0 disconnectwlan_hdd_cm_disconnect()destroy peerip link set wlan0 downhdd_vdev_destroy(link_info)destroy vdevlink_info->vdev = NULL

Phase-by-phase breakdown

Phase 1: create the interface — only Adapter and Link Info

Step Function Objects produced
user runs iw phy ... interface add cfg80211 → hdd_open_adapter() net_device + hdd_adapter
initialize the link array hdd_adapter_init_link_info() link_info[] (all vdev are NULL)

Key point: there is no VDEV yet. adapter->deflink->vdev == NULL.

Phase 2: interface up — create the VDEV and bind both directions

Step Function Notes
user runs ip link set wlan0 up cfg80211 → hdd_vdev_create(link_info) the argument is link_info, not the adapter
create the firmware-side object sme_vdev_create() → wlan_objmgr_vdev_obj_create() creates the VDEV under the PDEV
establish the bidirectional pointers hdd_store_vdev_info(link_info, vdev) link_info->vdev = vdev; osdev->legacy_osif_priv = link_info

Key point: the VDEV only exists after the interface is up. That is why link_info->vdev must be null-checked before access.

Phase 3: connect to the AP — create the PEER

Step Function Notes
user runs iw dev wlan0 connect MyAP cfg80211 → wlan_hdd_cfg80211_connect() cfg80211’s connect callback
enter the HDD connect logic wlan_hdd_cm_connect() adapter = netdev_priv(ndev), vdev = adapter->deflink->vdev
hand down to UMAC osif_cm_connect(ndev, vdev, req, params) the connection manager starts scanning/authentication/association
association succeeds, create the remote wlan_objmgr_peer_obj_create(vdev, ...) the PEER hangs under the VDEV
callback notifies HDD hdd_cm_connect_complete(vdev, rsp) updates link_info->session.station and other state

Key point: the PEER only exists once the connection is established. In STA mode, the PEER is that AP.

Phase 4: disconnect / down — destroy layer by layer

Step Function Notes
iw dev wlan0 disconnect wlan_hdd_cm_disconnect() destroys the PEER
ip link set wlan0 down hdd_vdev_destroy(link_info) destroys the VDEV, link_info->vdev = NULL
delete the interface hdd_close_adapter() destroys the Adapter and Link Info

Remember the chain in one line

1
2
3
4
5
6
7
8
9
User operation            iw / ip
↓ cfg80211
HDD entry hdd_open_adapter / hdd_vdev_create / wlan_hdd_cm_connect
↓
HDD objects adapter ──> link_info ──> (vdev pointer)
↓ SME
objmgr objects psoc ──> pdev ──> vdev ──> peer
↓
Firmware actually sends and receives data

Mnemonic:

Create the interface → Adapter/Link Info exist; up → VDEV exists; connected → PEER exists; down/disconnect → destroyed in reverse order.


A deeper look at MLO (Wi-Fi 7)

MLO is the key to understanding why Link Info exists, so let’s cover it separately.

What is MLO

MLO (Multi-Link Operation) is the core feature of Wi-Fi 7:
a STA can communicate with the AP over multiple links (different bands) simultaneously, aggregating bandwidth and reducing latency.

Object relationships under MLO

Taking MLMR (Multi-Link Multi-Radio) as an example: link 0 rides the 2.4G radio and link 1 rides the 5G radio; each link occupies its own PDEV, so both can transmit and receive at the same time:

objmgr layer

HDD layer

hdd_adapter
wlan0 (MLO STA)

link_info[0]
link_addr = AA:..:01

link_info[1]
link_addr = AA:..:02

wlan_mlo_dev_context
mld_addr = AA:..:00

PDEV 0
2.4G radio

PDEV 1
5G radio

vdev 5
mlo_link_id=0

vdev 6
mlo_link_id=1

MLO modes vs. PDEV: a link is a VDEV-level concept, but every VDEV still has to hang under some PDEV (radio):

  • MLMR (Multi-Link Multi-Radio): each link occupies its own PDEV, links TX/RX simultaneously — the figure above;
  • MLSR / EMLSR (single radio): all links share the same PDEV; only one link is active at a time (eMLSR lowers latency by fast-switching between links);
  • EMLMR (enhanced multi-radio): fewer radios than links; PDEVs are assigned dynamically to links.

wlan_mlo_get_pdev_hw_link_id() / wlan_mlo_get_pdev_by_hw_link_id() in the code are exactly the link ↔ PDEV mapping interfaces.

The three MAC addresses

Under MLO each VDEV has three MAC addresses — don’t mix them up:

Address Field Meaning
MLD MAC vdev_mlme.mldaddr the “logical address” of the whole MLO connection, visible to upper layers
Link MAC vdev_mlme.linkaddr the physical address of each link, visible on the air interface
Interface MAC vdev_mlme.macaddr the interface address (equal to linkaddr when not MLO)
1
2
3
/* qcacld-3.0/configs/config_to_feature.h */
#define WLAN_MAX_MLD (CONFIG_WLAN_MAX_MLD) /* default 2 */
#define WLAN_MAX_ML_BSS_LINKS CONFIG_WLAN_MAX_ML_BSS_LINKS /* default 3 */

So the default size of the link_info[] array is 3, i.e. one adapter has at most 3 links.


Cheat sheet & FAQ

One-sentence summary

Concept One-sentence explanation Layer
PSOC one Wi-Fi chip objmgr
PDEV one radio subsystem (radio/PHY) objmgr
VDEV one virtual interface (VAP) objmgr
PEER one remote device objmgr
Adapter one Linux network interface (net_device) HDD
Link Info the context of one link, connecting Adapter and VDEV HDD

Relationship quick reference

1
2
3
4
5
6
7
PSOC  1 ──< N  PDEV
PDEV 1 ──< N VDEV
VDEV 1 ──< N PEER

Adapter 1 ──< N Link Info
Link Info 1 ── 1 VDEV
Adapter 1 ──< N VDEV (indirectly associated via Link Info)

FAQ

Q1: Are Adapter and VDEV one-to-one?

No. An ordinary interface is 1:1, but an MLO interface is 1:N (one adapter, multiple vdevs).
The precise statement: one Link Info corresponds to one VDEV, and one Adapter corresponds to one or more Link Infos.

Q2: Why is link_info->vdev sometimes NULL?

Because the VDEV is only created when the interface goes up, while Link Info exists as soon as the interface is created.
When the interface goes down, the VDEV is destroyed and link_info->vdev is cleared.

Q3: What is adapter->deflink?

A shortcut for link_info[0]. All code uses it in non-MLO scenarios.

Q4: How do I get from a VDEV to the Adapter?

1
2
vdev->vdev_nif.osdev->legacy_osif_priv  →  link_info
link_info->adapter → adapter

Q5: How do I get from an Adapter to the PSOC/PDEV?

1
2
adapter->hdd_ctx->psoc  →  PSOC
adapter->hdd_ctx->pdev → PDEV

Q6: What’s the difference between hdd_context and wlan_objmgr_psoc?

hdd_context is the HDD layer’s global context (containing Linux-specific things such as wiphy, config);
wlan_objmgr_psoc is the objmgr layer’s chip object (cross-platform).
hdd_context holds the psoc and pdev pointers, acting as the bridge between the two layers.


Key code location index

All key code in this article lives in WANG-Guangxin/wlan-driver; the links below are pinned to commit da0ed2c, so they are permanent:

Item File
struct hdd_adapter / struct wlan_hdd_link_info / struct hdd_context / struct hdd_station_ctx / struct hdd_ap_ctx qcacld-3.0/core/hdd/inc/wlan_hdd_main.h
hdd_open_adapter() / hdd_vdev_create() / hdd_vdev_destroy() / hdd_store_vdev_info() / hdd_adapter_init_link_info() / wlan_hdd_get_link_info_from_objmgr() qcacld-3.0/core/hdd/src/wlan_hdd_main.c
wlan_hdd_cfg80211_connect() qcacld-3.0/core/hdd/src/wlan_hdd_cfg80211.c
wlan_hdd_cm_connect() / hdd_cm_connect_complete() qcacld-3.0/core/hdd/src/wlan_hdd_cm_connect.c
wlan_hdd_cm_disconnect() qcacld-3.0/core/hdd/src/wlan_hdd_cm_disconnect.c
osif_cm_connect() qca-wifi-host-cmn/os_if/linux/mlme/src/osif_cm_req.c
struct wlan_objmgr_psoc qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_psoc_obj.h
struct wlan_objmgr_pdev qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_pdev_obj.h
struct wlan_objmgr_vdev qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_vdev_obj.h
struct wlan_objmgr_peer qca-wifi-host-cmn/umac/cmn_services/obj_mgr/inc/wlan_objmgr_peer_obj.h
wlan_objmgr_vdev_obj_create() qca-wifi-host-cmn/umac/cmn_services/obj_mgr/src/wlan_objmgr_vdev_obj.c
wlan_objmgr_peer_obj_create() qca-wifi-host-cmn/umac/cmn_services/obj_mgr/src/wlan_objmgr_peer_obj.c
struct vdev_osif_priv qca-wifi-host-cmn/os_if/linux/wlan_osif_priv.h
struct wlan_mlo_dev_context qca-wifi-host-cmn/umac/mlo_mgr/inc/wlan_mlo_mgr_public_structs.h

Conclusion

Tying it all together in one sentence:

Userspace sees the Adapter (net_device);
inside the driver, Link Info connects the Adapter to the firmware’s VDEV;
the VDEV hangs under a PDEV, and the PDEV hangs under a PSOC;
each VDEV has several PEERs (remote devices) under it.

User space
wlan0

Adapter

Link Info

VDEV

PDEV

PSOC

PEER

Once you understand this object model, the qcacld code becomes much clearer:
when you see adapter, you know it is the Linux interface layer; when you see vdev, you know it is the firmware interaction layer;
when you see link_info, you know it is handling one specific link.