* [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs
@ 2026-11-04 15:15 Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 1/7] wifi: brcmfmac: match firmware capabilities as whole words Andre Eikmeyer
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer
This series adds AWDL support to brcmfmac, so Linux on the Apple T2 Macs
can send and receive files over AirDrop with Macs, iPhones and iPads.
AirDrop runs over Apple Wireless Direct Link (AWDL). On these Macs the
Apple firmware runs AWDL itself. The host creates an AWDL interface and
sets its parameters. From then on the firmware keeps the AWDL schedule,
sends the AWDL frames it gets from the host and carries data frames to
and from the peers. Election, peer discovery and AirDrop run in user
space.
The driver enables AWDL on any firmware that announces "awdl" in its
capabilities and supports interface_create version 3. The linux-firmware
image for the BCM4350 (7.35.180.133) on a pre-T2 MacBookPro14,1
announces "awdl" and answers interface_create with BCME_UNSUPPORTED, so
it stays without the feature. Other Apple chips, such as the BCM4355,
are untested.
The KaiT2en community, about 280 members, runs this series with its
kernels. Users reported it working with the BCM4364 and BCM4377 on the
MacBookPro15,1, 15,2, 15,3, 15,4, 16,1, 16,4, MacBookAir9,1 and
iMac20,1, and nobody reported a regression. AirDrop transfers on the
MacBookPro16,1 and 16,4 are slower than on the other models, the cause
is still open.
1 match firmware capabilities as whole words
2 move the interface_create definitions to fwil_types.h
3 detect AWDL support of the firmware
4 create and remove the AWDL interface
5 add the AWDL data path
6 add vendor commands for AWDL
7 pass AWDL frames and role changes to user space
Patch 1 is a fix on its own and carries a Fixes tag. It is here because
patch 3 adds an entry next to the lines it changes.
The AWDL interface is a network device "awdl%d" outside cfg80211. User
space talks to the driver through typed vendor commands and events of
the Broadcom OUI: start, stop, frames, sync, peer add and remove, and
events for received frames and role changes. Creating the interface
needs RTNL and vendor commands run with the wiphy lock only, so
AWDL_START and AWDL_STOP run in a work item and report their result as
vendor events. The driver sets the firmware parameters itself.
The firmware interface was worked out by tracing. A dtrace script
recorded the iovar calls macOS 15.4.1 made to the firmware on a
MacBookAir9,1 (BCM4377) while AirDrop was opened and closed in Finder.
The other way round, the MacBookAir9,1 was traced as AWDL peer while a
MacBookPro15,1 (BCM4364) brought AWDL up under Linux, together with the
AWDL frames macOS received and the console and events of the BCM4364
firmware. The BCM4364 handling in patch 6 comes from these runs. The
iovars, their order, their values and the AWDL frames come from these
traces and from the frames the firmware passes up.
The user space side is t2-drop, part of KaiT2en-Fedora and published
under the GPL:
https://github.com/kaiT2en/KaiT2en-Fedora/tree/main/apps/t2-drop
With it, AirDrop works in both directions.
Comments on the vendor command interface are welcome in particular.
nl80211 has no AWDL interface type, and the AWDL schedule and frame
exchange run in the firmware here.
Andre Eikmeyer (7):
wifi: brcmfmac: match firmware capabilities as whole words
wifi: brcmfmac: move the interface_create definitions to fwil_types.h
wifi: brcmfmac: detect AWDL support of the firmware
wifi: brcmfmac: create and remove the AWDL interface
wifi: brcmfmac: add the AWDL data path
wifi: brcmfmac: add vendor commands for AWDL
wifi: brcmfmac: pass AWDL frames and role changes to user space
.../broadcom/brcm80211/brcmfmac/Makefile | 3 +-
.../broadcom/brcm80211/brcmfmac/awdl.c | 1043 +++++++++++++++++
.../broadcom/brcm80211/brcmfmac/awdl.h | 113 ++
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 50 +-
.../broadcom/brcm80211/brcmfmac/core.c | 80 +-
.../broadcom/brcm80211/brcmfmac/core.h | 7 +
.../broadcom/brcm80211/brcmfmac/feature.c | 40 +-
.../broadcom/brcm80211/brcmfmac/feature.h | 3 +-
.../broadcom/brcm80211/brcmfmac/fweh.c | 19 +
.../broadcom/brcm80211/brcmfmac/fweh.h | 5 +
.../broadcom/brcm80211/brcmfmac/fwil_types.h | 46 +
.../broadcom/brcm80211/brcmfmac/vendor.c | 86 ++
.../broadcom/brcm80211/brcmfmac/vendor.h | 98 ++
13 files changed, 1537 insertions(+), 56 deletions(-)
create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
base-commit: 991a73f4909f1348ffb4b38765cdb522a775e4c2
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/7] wifi: brcmfmac: match firmware capabilities as whole words
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
@ 2026-11-04 15:15 ` Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 2/7] wifi: brcmfmac: move the interface_create definitions to fwil_types.h Andre Eikmeyer
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer, Aditya Garg, Yossef Osman
brcmf_feat_firmware_capabilities() looks for each capability with
strnstr() in the space separated reply of the "cap" iovar, so a
capability matches inside a longer one. The Apple firmware for the
BCM4364 lists "hp2p" but not "p2p":
... evt_ext lpr_scan bkoff_evt hp2p rng_reseed arpoe
The driver still sets BRCMF_FEAT_P2P and advertises the P2P interface
types. wpa_supplicant then creates a P2P device, which fails because
the firmware rejects p2p_disc and p2p_da_override with
BCME_UNSUPPORTED:
brcmf_p2p_set_firmware: failed to update device address ret -52
brcmf_p2p_create_p2pdev: set p2p_disc error
brcmf_cfg80211_add_iface: add iface p2p-dev-wlp3s0 type 10 failed: err=-52
Split the reply at spaces and compare whole words. The trailing spaces
in "sae", which kept it from matching "sae-ap" and "sae_ext", and in
"fbt" can go.
Fixes: ec64241c9fd2 ("brcmfmac: obtain feature info using 'cap' firmware command")
Reported-by: Aditya Garg <aditya.garg@linux.dev>
Closes: https://lore.kernel.org/all/MA0P287MB021718EE92FC809CB2BB0F82B8BD2@MA0P287MB0217.INDP287.PROD.OUTLOOK.COM/
Tested-by: Yossef Osman <Yossef.Osman@proton.me>
Tested-by: Aditya Garg <aditya.garg@linux.dev>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
.../wireless/broadcom/brcm80211/brcmfmac/feature.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index cc19e5e7ebf..b324eb53a69 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -42,10 +42,10 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = {
{ BRCMF_FEAT_MONITOR_FLAG, "rtap" },
{ BRCMF_FEAT_MONITOR_FMT_RADIOTAP, "rtap" },
{ BRCMF_FEAT_DOT11H, "802.11h" },
- { BRCMF_FEAT_SAE, "sae " },
+ { BRCMF_FEAT_SAE, "sae" },
{ BRCMF_FEAT_FWAUTH, "idauth" },
{ BRCMF_FEAT_SAE_EXT, "sae_ext" },
- { BRCMF_FEAT_FBT, "fbt " },
+ { BRCMF_FEAT_FBT, "fbt" },
{ BRCMF_FEAT_OKC, "okc" },
};
@@ -231,6 +231,7 @@ static void brcmf_feat_firmware_capabilities(struct brcmf_if *ifp)
{
struct brcmf_pub *drvr = ifp->drvr;
char caps[MAX_CAPS_BUFFER_SIZE];
+ char *cap, *next;
enum brcmf_feat_id id;
int i, err;
@@ -240,10 +241,15 @@ static void brcmf_feat_firmware_capabilities(struct brcmf_if *ifp)
return;
}
+ caps[sizeof(caps) - 1] = '\0';
brcmf_dbg(INFO, "[ %s]\n", caps);
- for (i = 0; i < ARRAY_SIZE(brcmf_fwcap_map); i++) {
- if (strnstr(caps, brcmf_fwcap_map[i].fwcap_id, sizeof(caps))) {
+ /* Match whole words, "hp2p" must not enable P2P */
+ next = caps;
+ while ((cap = strsep(&next, "\n "))) {
+ for (i = 0; i < ARRAY_SIZE(brcmf_fwcap_map); i++) {
+ if (strcmp(cap, brcmf_fwcap_map[i].fwcap_id))
+ continue;
id = brcmf_fwcap_map[i].feature;
brcmf_dbg(INFO, "enabling feature: %s\n",
brcmf_feat_names[id]);
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/7] wifi: brcmfmac: move the interface_create definitions to fwil_types.h
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 1/7] wifi: brcmfmac: match firmware capabilities as whole words Andre Eikmeyer
@ 2026-11-04 15:15 ` Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 3/7] wifi: brcmfmac: detect AWDL support of the firmware Andre Eikmeyer
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer, Yossef Osman, Aditya Garg
The interface_create iovar structures and flags are firmware interface
types like the others in fwil_types.h. Move them there so code outside
cfg80211.c can create firmware interfaces as well.
Tested-by: Yossef Osman <Yossef.Osman@proton.me>
Tested-by: Aditya Garg <aditya.garg@linux.dev>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 42 ------------------
.../broadcom/brcm80211/brcmfmac/fwil_types.h | 43 +++++++++++++++++++
2 files changed, 43 insertions(+), 42 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 46714d87126..1037abe6d58 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -268,48 +268,6 @@ struct parsed_vndr_ies {
struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT];
};
-#define WL_INTERFACE_CREATE_VER_1 1
-#define WL_INTERFACE_CREATE_VER_2 2
-#define WL_INTERFACE_CREATE_VER_3 3
-#define WL_INTERFACE_CREATE_VER_MAX WL_INTERFACE_CREATE_VER_3
-
-#define WL_INTERFACE_MAC_DONT_USE 0x0
-#define WL_INTERFACE_MAC_USE 0x2
-
-#define WL_INTERFACE_CREATE_STA 0x0
-#define WL_INTERFACE_CREATE_AP 0x1
-
-struct wl_interface_create_v1 {
- u16 ver; /* structure version */
- u32 flags; /* flags for operation */
- u8 mac_addr[ETH_ALEN]; /* MAC address */
- u32 wlc_index; /* optional for wlc index */
-};
-
-struct wl_interface_create_v2 {
- u16 ver; /* structure version */
- u8 pad1[2];
- u32 flags; /* flags for operation */
- u8 mac_addr[ETH_ALEN]; /* MAC address */
- u8 iftype; /* type of interface created */
- u8 pad2;
- u32 wlc_index; /* optional for wlc index */
-};
-
-struct wl_interface_create_v3 {
- u16 ver; /* structure version */
- u16 len; /* length of structure + data */
- u16 fixed_len; /* length of structure */
- u8 iftype; /* type of interface created */
- u8 wlc_index; /* optional for wlc index */
- u32 flags; /* flags for operation */
- u8 mac_addr[ETH_ALEN]; /* MAC address */
- u8 bssid[ETH_ALEN]; /* optional for BSSID */
- u8 if_index; /* interface index request */
- u8 pad[3];
- u8 data[]; /* Optional for specific data */
-};
-
static u8 nl80211_band_to_fwil(enum nl80211_band band)
{
switch (band) {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index e74a23e1183..6f13e10b16d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -1236,4 +1236,47 @@ struct brcmf_mkeep_alive_pkt_le {
u8 data[];
} __packed;
+/* interface_create iovar */
+#define WL_INTERFACE_CREATE_VER_1 1
+#define WL_INTERFACE_CREATE_VER_2 2
+#define WL_INTERFACE_CREATE_VER_3 3
+#define WL_INTERFACE_CREATE_VER_MAX WL_INTERFACE_CREATE_VER_3
+
+#define WL_INTERFACE_MAC_DONT_USE 0x0
+#define WL_INTERFACE_MAC_USE 0x2
+
+#define WL_INTERFACE_CREATE_STA 0x0
+#define WL_INTERFACE_CREATE_AP 0x1
+
+struct wl_interface_create_v1 {
+ u16 ver; /* structure version */
+ u32 flags; /* flags for operation */
+ u8 mac_addr[ETH_ALEN]; /* MAC address */
+ u32 wlc_index; /* optional for wlc index */
+};
+
+struct wl_interface_create_v2 {
+ u16 ver; /* structure version */
+ u8 pad1[2];
+ u32 flags; /* flags for operation */
+ u8 mac_addr[ETH_ALEN]; /* MAC address */
+ u8 iftype; /* type of interface created */
+ u8 pad2;
+ u32 wlc_index; /* optional for wlc index */
+};
+
+struct wl_interface_create_v3 {
+ u16 ver; /* structure version */
+ u16 len; /* length of structure + data */
+ u16 fixed_len; /* length of structure */
+ u8 iftype; /* type of interface created */
+ u8 wlc_index; /* optional for wlc index */
+ u32 flags; /* flags for operation */
+ u8 mac_addr[ETH_ALEN]; /* MAC address */
+ u8 bssid[ETH_ALEN]; /* optional for BSSID */
+ u8 if_index; /* interface index request */
+ u8 pad[3];
+ u8 data[]; /* Optional for specific data */
+};
+
#endif /* FWIL_TYPES_H_ */
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/7] wifi: brcmfmac: detect AWDL support of the firmware
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 1/7] wifi: brcmfmac: match firmware capabilities as whole words Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 2/7] wifi: brcmfmac: move the interface_create definitions to fwil_types.h Andre Eikmeyer
@ 2026-11-04 15:15 ` Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 4/7] wifi: brcmfmac: create and remove the AWDL interface Andre Eikmeyer
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer, Yossef Osman, Aditya Garg
The Apple firmware of the BCM4364 and BCM4377 runs Apple Wireless
Direct Link (AWDL) itself and announces it with "awdl" in its
capabilities. Add the feature flag for it.
An AWDL interface is created with interface_create version 3 and
interface type 2. The host gives the MAC address, the interface index
and the BSSID, which the flags 0x2, 0x8 and 0x10 select. Add the
definitions for the interface type and the two new flags.
The BCM4350 firmware 7.35.180.133 lists "awdl" as well and answers
every interface_create, including the version query, with
BCME_UNSUPPORTED. Keep the flag only when the firmware reports
interface_create version 3.
Tested-by: Yossef Osman <Yossef.Osman@proton.me>
Tested-by: Aditya Garg <aditya.garg@linux.dev>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
.../broadcom/brcm80211/brcmfmac/feature.c | 26 +++++++++++++++++++
.../broadcom/brcm80211/brcmfmac/feature.h | 3 ++-
.../broadcom/brcm80211/brcmfmac/fwil_types.h | 3 +++
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index b324eb53a69..e7d5bc79c08 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -47,6 +47,7 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = {
{ BRCMF_FEAT_SAE_EXT, "sae_ext" },
{ BRCMF_FEAT_FBT, "fbt" },
{ BRCMF_FEAT_OKC, "okc" },
+ { BRCMF_FEAT_AWDL, "awdl" },
};
#ifdef DEBUG
@@ -294,6 +295,29 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data)
return 0;
}
+/**
+ * brcmf_feat_awdl_check() - keep AWDL only with interface_create version 3.
+ *
+ * @ifp: interface to query.
+ *
+ * The AWDL interface is created with interface_create version 3. The
+ * BCM4350 firmware 7.35.180.133 lists "awdl" but answers every
+ * interface_create with BCME_UNSUPPORTED.
+ */
+static void brcmf_feat_awdl_check(struct brcmf_if *ifp)
+{
+ u32 ver = 0;
+ s32 err;
+
+ err = brcmf_fil_bsscfg_int_query(ifp, "interface_create", &ver);
+ if (!err && ver == WL_INTERFACE_CREATE_VER_3)
+ return;
+
+ brcmf_dbg(INFO, "disabling AWDL, interface_create version %u (%d)\n",
+ ver, err);
+ ifp->drvr->feat_flags &= ~BIT(BRCMF_FEAT_AWDL);
+}
+
void brcmf_feat_attach(struct brcmf_pub *drvr)
{
struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0);
@@ -303,6 +327,8 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
s32 err;
brcmf_feat_firmware_capabilities(ifp);
+ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_AWDL))
+ brcmf_feat_awdl_check(ifp);
memset(&gscan_cfg, 0, sizeof(gscan_cfg));
if (drvr->bus_if->chip != BRCM_CC_43430_CHIP_ID &&
drvr->bus_if->chip != BRCM_CC_4345_CHIP_ID &&
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 8165a286b63..0d98d4d7122 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -61,7 +61,8 @@
BRCMF_FEAT_DEF(PMKID_V3) \
BRCMF_FEAT_DEF(SAE_EXT) \
BRCMF_FEAT_DEF(FBT) \
- BRCMF_FEAT_DEF(OKC)
+ BRCMF_FEAT_DEF(OKC) \
+ BRCMF_FEAT_DEF(AWDL)
/*
* Quirks:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 6f13e10b16d..8d1532edbad 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -1244,9 +1244,12 @@ struct brcmf_mkeep_alive_pkt_le {
#define WL_INTERFACE_MAC_DONT_USE 0x0
#define WL_INTERFACE_MAC_USE 0x2
+#define WL_INTERFACE_IF_INDEX_USE 0x8
+#define WL_INTERFACE_BSSID_USE 0x10
#define WL_INTERFACE_CREATE_STA 0x0
#define WL_INTERFACE_CREATE_AP 0x1
+#define WL_INTERFACE_CREATE_AWDL 0x2
struct wl_interface_create_v1 {
u16 ver; /* structure version */
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 4/7] wifi: brcmfmac: create and remove the AWDL interface
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
` (2 preceding siblings ...)
2026-11-04 15:15 ` [PATCH 3/7] wifi: brcmfmac: detect AWDL support of the firmware Andre Eikmeyer
@ 2026-11-04 15:15 ` Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 5/7] wifi: brcmfmac: add the AWDL data path Andre Eikmeyer
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer, Yossef Osman, Aditya Garg
Firmware with AWDL support takes an interface of type AWDL from
interface_create. The host gives the MAC address, the firmware
interface index 2 and the AWDL BSSID 00:25:00:ff:94:73. The firmware
answers with an interface event like for any other interface.
The AWDL interface lives outside cfg80211. Add brcmf_awdl_add() and
brcmf_awdl_del(), called with RTNL and the wiphy mutex held, which
create and remove it together with a network device "awdl%d":
- The interface events of the AWDL interface complete a wait in
brcmf_awdl_add() and brcmf_awdl_del() instead of going to the
cfg80211 handler. A vif_event armed for a P2P or AP interface at the
same time stays with that interface. When the firmware removes the
AWDL interface on its own, the event worker removes it as before.
- The network device gets its own net_device_ops with open, stop and
start_xmit before it is registered. The firmware filters AWDL traffic
itself.
- brcmf_net_attach() and brcmf_net_detach() also register and
unregister a network device outside cfg80211 with RTNL held.
- brcmf_awdl_add() needs the AWDL feature and a free firmware
interface index 2.
- The AWDL interface uses direct addressing like an AP, every peer
gets its own flowrings. brcmf_awdl_del() deletes them before it
stops AWDL and removes the interface.
- The AWDL interface is removed in the cfg80211 suspend callback while
the firmware still takes commands.
Tested-by: Yossef Osman <Yossef.Osman@proton.me>
Tested-by: Aditya Garg <aditya.garg@linux.dev>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
.../broadcom/brcm80211/brcmfmac/Makefile | 3 +-
.../broadcom/brcm80211/brcmfmac/awdl.c | 268 ++++++++++++++++++
.../broadcom/brcm80211/brcmfmac/awdl.h | 47 +++
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 6 +
.../broadcom/brcm80211/brcmfmac/core.c | 59 +++-
.../broadcom/brcm80211/brcmfmac/core.h | 7 +
.../broadcom/brcm80211/brcmfmac/fweh.c | 17 ++
7 files changed, 398 insertions(+), 9 deletions(-)
create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
index e5ca0f51182..36c634199a2 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
@@ -25,7 +25,8 @@ brcmfmac-objs += \
btcoex.o \
vendor.o \
pno.o \
- xtlv.o
+ xtlv.o \
+ awdl.o
brcmfmac-$(CONFIG_BRCMFMAC_PROTO_BCDC) += \
bcdc.o \
fwsignal.o
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
new file mode 100644
index 00000000000..485ed74f3d6
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 André Eikmeyer <dev@deq.rocks>
+ *
+ * AWDL interface of the Apple firmware of the BCM4364 and BCM4377.
+ *
+ * The firmware runs Apple Wireless Direct Link itself once the host creates
+ * an interface of type AWDL with interface_create and configures it through
+ * the awdl_* iovars. The AWDL interface has a network device but no cfg80211
+ * vif. Its interface events are handled here and not by cfg80211.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+
+#include "core.h"
+#include "debug.h"
+#include "feature.h"
+#include "fwil.h"
+#include "fwil_types.h"
+#include "proto.h"
+#include "awdl.h"
+
+/* The firmware interface index of the AWDL interface */
+#define BRCMF_AWDL_IFIDX 2
+#define BRCMF_AWDL_IF_TIMEOUT msecs_to_jiffies(2000)
+
+/* The BSSID of all AWDL frames */
+static const u8 brcmf_awdl_bssid[ETH_ALEN] = {
+ 0x00, 0x25, 0x00, 0xff, 0x94, 0x73,
+};
+
+void brcmf_awdl_attach(struct brcmf_pub *drvr)
+{
+ spin_lock_init(&drvr->awdl.lock);
+ init_completion(&drvr->awdl.if_event);
+}
+
+/*
+ * Called from the firmware event worker after an interface was added. Takes
+ * the interface if it is the AWDL interface being created.
+ */
+bool brcmf_awdl_if_added(struct brcmf_pub *drvr, struct brcmf_if *ifp)
+{
+ struct brcmf_awdl *awdl = &drvr->awdl;
+ bool own;
+
+ spin_lock(&awdl->lock);
+ own = awdl->adding && ifp->ndev &&
+ ether_addr_equal(ifp->mac_addr, awdl->mac);
+ if (own) {
+ ifp->awdl = true;
+ strscpy(ifp->ndev->name, "awdl%d", IFNAMSIZ);
+ awdl->ifp = ifp;
+ awdl->adding = false;
+ complete(&awdl->if_event);
+ }
+ spin_unlock(&awdl->lock);
+ return own;
+}
+
+/*
+ * Called from the firmware event worker before an interface is removed.
+ * Returns true if the removal of the AWDL interface was requested by
+ * brcmf_awdl_del(), which then removes it. When the firmware removes the
+ * AWDL interface on its own, it is forgotten here and removed by the caller.
+ */
+bool brcmf_awdl_if_removed(struct brcmf_pub *drvr, struct brcmf_if *ifp)
+{
+ struct brcmf_awdl *awdl = &drvr->awdl;
+ bool requested = false;
+
+ if (!ifp->awdl)
+ return false;
+
+ spin_lock(&awdl->lock);
+ if (awdl->ifp == ifp) {
+ requested = awdl->removing;
+ if (requested)
+ complete(&awdl->if_event);
+ else
+ awdl->ifp = NULL;
+ }
+ spin_unlock(&awdl->lock);
+ if (!requested)
+ brcmf_proto_configure_addr_mode(drvr, ifp->ifidx,
+ ADDR_INDIRECT);
+ return requested;
+}
+
+/* The firmware interface index of the AWDL interface must be free */
+static bool brcmf_awdl_ifidx_used(struct brcmf_pub *drvr)
+{
+ int i;
+
+ for (i = 1; i < BRCMF_MAX_IFS; i++)
+ if (drvr->iflist[i] && drvr->iflist[i]->ifidx == BRCMF_AWDL_IFIDX)
+ return true;
+ return false;
+}
+
+/* Undoes interface_create for an interface that was not attached */
+static void brcmf_awdl_drop_if(struct brcmf_pub *drvr, struct brcmf_if *ifp)
+{
+ struct net_device *ndev = ifp->ndev;
+
+ brcmf_fil_iovar_data_set(ifp, "interface_remove", NULL, 0);
+ brcmf_proto_configure_addr_mode(drvr, ifp->ifidx, ADDR_INDIRECT);
+ brcmf_proto_del_if(drvr, ifp);
+ if (drvr->iflist[ifp->bsscfgidx] == ifp)
+ drvr->iflist[ifp->bsscfgidx] = NULL;
+ if (drvr->if2bss[ifp->ifidx] == ifp->bsscfgidx)
+ drvr->if2bss[ifp->ifidx] = BRCMF_BSSIDX_INVALID;
+ free_netdev(ndev);
+}
+
+/**
+ * brcmf_awdl_add() - create the AWDL interface.
+ *
+ * @drvr: device.
+ * @mac: MAC address of the AWDL interface.
+ * @ndev: set to the network device of the AWDL interface.
+ *
+ * Called with RTNL and the wiphy mutex held. The interface uses the firmware interface index 2
+ * and the AWDL BSSID 00:25:00:ff:94:73, which the host passes to the
+ * firmware. Return: 0 or a negative error code.
+ */
+int brcmf_awdl_add(struct brcmf_pub *drvr, const u8 *mac,
+ struct net_device **ndev)
+{
+ struct brcmf_awdl *awdl = &drvr->awdl;
+ struct brcmf_if *pri = brcmf_get_ifp(drvr, 0);
+ struct wl_interface_create_v3 req = {};
+ struct brcmf_if *ifp;
+ long left = 0;
+ int err;
+
+ ASSERT_RTNL();
+ lockdep_assert_wiphy(drvr->wiphy);
+
+ if (!pri || !brcmf_feat_is_enabled(pri, BRCMF_FEAT_AWDL))
+ return -EOPNOTSUPP;
+ if (!is_valid_ether_addr(mac))
+ return -EINVAL;
+ if (awdl->detached)
+ return -ENODEV;
+ if (awdl->ifp)
+ return -EBUSY;
+ if (brcmf_awdl_ifidx_used(drvr)) {
+ bphy_err(drvr, "interface index %d is in use, no AWDL\n",
+ BRCMF_AWDL_IFIDX);
+ return -EBUSY;
+ }
+
+ spin_lock(&awdl->lock);
+ ether_addr_copy(awdl->mac, mac);
+ awdl->adding = true;
+ reinit_completion(&awdl->if_event);
+ spin_unlock(&awdl->lock);
+
+ req.ver = WL_INTERFACE_CREATE_VER_3;
+ req.len = sizeof(req);
+ req.fixed_len = sizeof(req);
+ req.iftype = WL_INTERFACE_CREATE_AWDL;
+ req.flags = WL_INTERFACE_MAC_USE | WL_INTERFACE_IF_INDEX_USE |
+ WL_INTERFACE_BSSID_USE;
+ ether_addr_copy(req.mac_addr, mac);
+ ether_addr_copy(req.bssid, brcmf_awdl_bssid);
+ req.if_index = BRCMF_AWDL_IFIDX;
+ err = brcmf_fil_iovar_data_get(pri, "interface_create", &req,
+ sizeof(req));
+ if (!err)
+ left = wait_for_completion_timeout(&awdl->if_event,
+ BRCMF_AWDL_IF_TIMEOUT);
+
+ spin_lock(&awdl->lock);
+ awdl->adding = false;
+ ifp = awdl->ifp;
+ spin_unlock(&awdl->lock);
+
+ if (err) {
+ bphy_err(drvr, "AWDL interface_create failed: %d\n", err);
+ return err;
+ }
+ if (!left || !ifp) {
+ bphy_err(drvr, "no interface event for the AWDL interface\n");
+ return -ETIMEDOUT;
+ }
+
+ /* Like an AP, AWDL sends to each peer through its own flowrings */
+ brcmf_proto_configure_addr_mode(drvr, ifp->ifidx, ADDR_DIRECT);
+ err = brcmf_net_attach(ifp, true);
+ if (err) {
+ spin_lock(&awdl->lock);
+ awdl->ifp = NULL;
+ spin_unlock(&awdl->lock);
+ brcmf_awdl_drop_if(drvr, ifp);
+ return err;
+ }
+ brcmf_dbg(INFO, "%s: AWDL interface, bsscfgidx %d ifidx %d\n",
+ ifp->ndev->name, ifp->bsscfgidx, ifp->ifidx);
+ *ndev = ifp->ndev;
+ return 0;
+}
+
+/**
+ * brcmf_awdl_del() - remove the AWDL interface.
+ *
+ * @drvr: device.
+ *
+ * Called with RTNL and the wiphy mutex held. Deletes the flowrings of the AWDL interface, stops
+ * AWDL and removes the interface in the firmware and on the host.
+ * Return: 0 or a negative error code.
+ */
+int brcmf_awdl_del(struct brcmf_pub *drvr)
+{
+ struct brcmf_awdl *awdl = &drvr->awdl;
+ struct brcmf_if *ifp;
+ int err;
+
+ ASSERT_RTNL();
+ lockdep_assert_wiphy(drvr->wiphy);
+
+ ifp = awdl->ifp;
+ if (!ifp)
+ return -ENOENT;
+
+ netif_tx_disable(ifp->ndev);
+ /* Deletes the flowrings of all peers */
+ brcmf_proto_configure_addr_mode(drvr, ifp->ifidx, ADDR_INDIRECT);
+ brcmf_fil_iovar_int_set(ifp, "awdl", 0);
+
+ spin_lock(&awdl->lock);
+ awdl->removing = true;
+ reinit_completion(&awdl->if_event);
+ spin_unlock(&awdl->lock);
+
+ err = brcmf_fil_iovar_data_set(ifp, "interface_remove", NULL, 0);
+ if (!err && !wait_for_completion_timeout(&awdl->if_event,
+ BRCMF_AWDL_IF_TIMEOUT)) {
+ bphy_err(drvr, "no interface event for the AWDL removal\n");
+ err = -ETIMEDOUT;
+ }
+
+ spin_lock(&awdl->lock);
+ awdl->removing = false;
+ awdl->ifp = NULL;
+ spin_unlock(&awdl->lock);
+
+ brcmf_remove_interface(ifp, true);
+ return err;
+}
+
+/* Called before the interfaces of the device are removed */
+void brcmf_awdl_detach(struct brcmf_pub *drvr)
+{
+ struct brcmf_awdl *awdl = &drvr->awdl;
+
+ rtnl_lock();
+ wiphy_lock(drvr->wiphy);
+ spin_lock(&awdl->lock);
+ awdl->detached = true;
+ awdl->ifp = NULL;
+ spin_unlock(&awdl->lock);
+ wiphy_unlock(drvr->wiphy);
+ rtnl_unlock();
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
new file mode 100644
index 00000000000..b12cbd32187
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 André Eikmeyer <dev@deq.rocks>
+ *
+ * AWDL interface of the Apple firmware, see awdl.c
+ */
+#ifndef BRCMFMAC_AWDL_H
+#define BRCMFMAC_AWDL_H
+
+#include <linux/completion.h>
+#include <linux/if_ether.h>
+#include <linux/spinlock.h>
+
+struct brcmf_pub;
+struct brcmf_if;
+
+/**
+ * struct brcmf_awdl - AWDL interface of a device.
+ *
+ * @lock: protects @mac, @adding, @removing and @ifp against the firmware
+ * event worker. Changes of @ifp also hold RTNL and the wiphy mutex.
+ * @if_event: completed by the interface event of the AWDL interface.
+ * @mac: MAC address requested for the AWDL interface.
+ * @adding: interface_create was sent, its interface event is awaited.
+ * @removing: interface_remove was sent, its interface event is awaited.
+ * @detached: the device goes away, no new AWDL interface is created.
+ * @ifp: the AWDL interface, NULL when there is none.
+ */
+struct brcmf_awdl {
+ spinlock_t lock; /* AWDL state against the event worker */
+ struct completion if_event;
+ u8 mac[ETH_ALEN];
+ bool adding;
+ bool removing;
+ bool detached;
+ struct brcmf_if *ifp;
+};
+
+void brcmf_awdl_attach(struct brcmf_pub *drvr);
+void brcmf_awdl_detach(struct brcmf_pub *drvr);
+int brcmf_awdl_add(struct brcmf_pub *drvr, const u8 *mac,
+ struct net_device **ndev);
+int brcmf_awdl_del(struct brcmf_pub *drvr);
+bool brcmf_awdl_if_added(struct brcmf_pub *drvr, struct brcmf_if *ifp);
+bool brcmf_awdl_if_removed(struct brcmf_pub *drvr, struct brcmf_if *ifp);
+
+#endif /* BRCMFMAC_AWDL_H */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 1037abe6d58..4f35dbef082 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -4279,6 +4279,12 @@ static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy,
brcmf_dbg(TRACE, "Enter\n");
+ /* The AWDL interface does not survive sleep, the firmware still
+ * takes commands here. User space creates it again after resume.
+ */
+ if (cfg->pub->awdl.ifp)
+ brcmf_awdl_del(cfg->pub);
+
/* if the primary net_device is not READY there is nothing
* we can do but pray resume goes smoothly.
*/
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 92ec269f43b..37c558b6f0d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -32,8 +32,6 @@
#define MAX_WAIT_FOR_8021X_TX msecs_to_jiffies(950)
-#define BRCMF_BSSIDX_INVALID -1
-
#define RXS_PBPRES BIT(2)
#define D11_PHY_HDR_LEN 6
@@ -642,6 +640,42 @@ static const struct net_device_ops brcmf_netdev_ops_pri = {
.ndo_set_rx_mode = brcmf_netdev_set_multicast_list
};
+static int brcmf_awdl_netdev_open(struct net_device *ndev)
+{
+ struct brcmf_if *ifp = netdev_priv(ndev);
+
+ if (ifp->drvr->bus_if->state != BRCMF_BUS_UP)
+ return -EAGAIN;
+
+ /* The firmware runs AWDL, the link is there while it exists */
+ netif_carrier_on(ndev);
+ netif_start_queue(ndev);
+ return 0;
+}
+
+static int brcmf_awdl_netdev_stop(struct net_device *ndev)
+{
+ netif_stop_queue(ndev);
+ netif_carrier_off(ndev);
+ return 0;
+}
+
+/*
+ * The AWDL interface gets no multicast list and no MAC address change, the
+ * firmware filters AWDL traffic itself.
+ */
+static const struct net_device_ops brcmf_netdev_ops_awdl = {
+ .ndo_open = brcmf_awdl_netdev_open,
+ .ndo_stop = brcmf_awdl_netdev_stop,
+ .ndo_start_xmit = brcmf_netdev_start_xmit,
+};
+
+static bool brcmf_netdev_is_brcmf(struct net_device *ndev)
+{
+ return ndev->netdev_ops == &brcmf_netdev_ops_pri ||
+ ndev->netdev_ops == &brcmf_netdev_ops_awdl;
+}
+
int brcmf_net_attach(struct brcmf_if *ifp, bool locked)
{
struct brcmf_pub *drvr = ifp->drvr;
@@ -653,7 +687,10 @@ int brcmf_net_attach(struct brcmf_if *ifp, bool locked)
ndev = ifp->ndev;
/* set appropriate operations */
- ndev->netdev_ops = &brcmf_netdev_ops_pri;
+ if (ifp->awdl)
+ ndev->netdev_ops = &brcmf_netdev_ops_awdl;
+ else
+ ndev->netdev_ops = &brcmf_netdev_ops_pri;
ndev->needed_headroom += drvr->hdrlen;
ndev->ethtool_ops = &brcmf_ethtool_ops;
@@ -665,10 +702,12 @@ int brcmf_net_attach(struct brcmf_if *ifp, bool locked)
INIT_WORK(&ifp->multicast_work, _brcmf_set_multicast_list);
INIT_WORK(&ifp->ndoffload_work, _brcmf_update_ndtable);
- if (locked)
+ if (!locked)
+ err = register_netdev(ndev);
+ else if (ndev->ieee80211_ptr)
err = cfg80211_register_netdevice(ndev);
else
- err = register_netdev(ndev);
+ err = register_netdevice(ndev);
if (err != 0) {
bphy_err(drvr, "couldn't register the net device\n");
goto fail;
@@ -689,10 +728,12 @@ int brcmf_net_attach(struct brcmf_if *ifp, bool locked)
void brcmf_net_detach(struct net_device *ndev, bool locked)
{
if (ndev->reg_state == NETREG_REGISTERED) {
- if (locked)
+ if (!locked)
+ unregister_netdev(ndev);
+ else if (ndev->ieee80211_ptr)
cfg80211_unregister_netdevice(ndev);
else
- unregister_netdev(ndev);
+ unregister_netdevice(ndev);
} else {
brcmf_cfg80211_free_vif(ndev);
free_netdev(ndev);
@@ -943,7 +984,7 @@ static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx,
netif_stop_queue(ifp->ndev);
}
- if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) {
+ if (brcmf_netdev_is_brcmf(ifp->ndev)) {
cancel_work_sync(&ifp->multicast_work);
cancel_work_sync(&ifp->ndoffload_work);
}
@@ -1371,6 +1412,7 @@ int brcmf_attach(struct device *dev)
drvr->if2bss[i] = BRCMF_BSSIDX_INVALID;
mutex_init(&drvr->proto_block);
+ brcmf_awdl_attach(drvr);
/* Link to bus module */
drvr->hdrlen = 0;
@@ -1486,6 +1528,7 @@ void brcmf_detach(struct device *dev)
unregister_inet6addr_notifier(&drvr->inet6addr_notifier);
#endif
+ brcmf_awdl_detach(drvr);
brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN);
/* make sure primary interface removed last */
for (i = BRCMF_MAX_IFS - 1; i > -1; i--) {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
index 399b6810e39..fc8a713e80c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
@@ -12,6 +12,7 @@
#include <net/cfg80211.h>
#include "fweh.h"
+#include "awdl.h"
#if IS_MODULE(CONFIG_BRCMFMAC)
#define BRCMF_EXPORT_SYMBOL_GPL(__sym) EXPORT_SYMBOL_NS_GPL(__sym, "BRCMFMAC")
@@ -25,6 +26,8 @@
/* For supporting multiple interfaces */
#define BRCMF_MAX_IFS 16
+#define BRCMF_BSSIDX_INVALID -1
+
/* Small, medium and maximum buffer size for dcmd
*/
#define BRCMF_DCMD_SMLEN 256
@@ -145,6 +148,8 @@ struct brcmf_pub {
u8 sta_mac_idx;
const struct brcmf_fwvid_ops *vops;
void *vdata;
+
+ struct brcmf_awdl awdl;
};
/* forward declarations */
@@ -184,6 +189,7 @@ enum brcmf_netif_stop_reason {
* @pend_8021x_cnt: tracks outstanding number of 802.1x frames.
* @pend_8021x_wait: used for signalling change in count.
* @fwil_fwerr: flag indicating fwil layer should return firmware error codes.
+ * @awdl: this is the AWDL interface, it has no cfg80211 vif.
*/
struct brcmf_if {
struct brcmf_pub *drvr;
@@ -202,6 +208,7 @@ struct brcmf_if {
struct in6_addr ipv6_addr_tbl[NDOL_MAX_ENTRIES];
u8 ipv6addr_idx;
bool fwil_fwerr;
+ bool awdl;
};
int brcmf_netdev_wait_pend8021x(struct brcmf_if *ifp);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
index 1cff4ba7694..8c098029104 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
@@ -170,6 +170,9 @@ static void brcmf_fweh_handle_if_event(struct brcmf_pub *drvr,
return;
if (!is_p2pdev)
brcmf_proto_add_if(drvr, ifp);
+ /* The AWDL interface is attached by brcmf_awdl_add() */
+ if (brcmf_awdl_if_added(drvr, ifp))
+ return;
if (!drvr->fweh->evt_handler[BRCMF_E_IF])
if (brcmf_net_attach(ifp, false) < 0)
return;
@@ -178,6 +181,14 @@ static void brcmf_fweh_handle_if_event(struct brcmf_pub *drvr,
if (ifp && ifevent->action == BRCMF_E_IF_CHANGE)
brcmf_proto_reset_if(drvr, ifp);
+ /* The AWDL interface has no vif, cfg80211 does not know it */
+ if (ifp && ifp->awdl) {
+ if (ifevent->action == BRCMF_E_IF_DEL &&
+ !brcmf_awdl_if_removed(drvr, ifp))
+ brcmf_remove_interface(ifp, false);
+ return;
+ }
+
brcmf_fweh_call_event_handler(drvr, ifp, emsg->event_code, emsg,
data);
@@ -313,6 +324,12 @@ static void brcmf_fweh_event_worker(struct work_struct *work)
ifp = drvr->iflist[0];
else
ifp = drvr->iflist[emsg.bsscfgidx];
+ /* The AWDL interface has no vif, its events are not for
+ * the cfg80211 handlers
+ */
+ if (ifp && ifp->awdl)
+ goto event_free;
+
err = brcmf_fweh_call_event_handler(drvr, ifp, event->code,
&emsg, event->data);
if (err) {
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 5/7] wifi: brcmfmac: add the AWDL data path
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
` (3 preceding siblings ...)
2026-11-04 15:15 ` [PATCH 4/7] wifi: brcmfmac: create and remove the AWDL interface Andre Eikmeyer
@ 2026-11-04 15:15 ` Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 6/7] wifi: brcmfmac: add vendor commands for AWDL Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 7/7] wifi: brcmfmac: pass AWDL frames and role changes to user space Andre Eikmeyer
6 siblings, 0 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer, Yossef Osman, Aditya Garg
The firmware passes AWDL data frames as 802.3 frames. An LLC/SNAP
header with the Apple OUI (aa aa 03 00 17 f2 08 00) is followed by the
AWDL data header 03 04, a 16 bit sequence number, two reserved bytes
and the ethertype.
Let the AWDL network device send and receive plain Ethernet frames. Its
transmit function adds the 16 bytes of headers and counts the sequence
number, brcmf_netif_rx() removes them from received frames. The headroom
of the network device covers the added header. The addresses of
received frames are moved so that they form an Ethernet header with the
ethertype again, which keeps packet taps consistent. Other frames on
the AWDL interface are dropped.
Tested-by: Yossef Osman <Yossef.Osman@proton.me>
Tested-by: Aditya Garg <aditya.garg@linux.dev>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
.../broadcom/brcm80211/brcmfmac/awdl.c | 83 +++++++++++++++++++
.../broadcom/brcm80211/brcmfmac/awdl.h | 9 ++
.../broadcom/brcm80211/brcmfmac/core.c | 23 ++++-
3 files changed, 114 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
index 485ed74f3d6..95619283471 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
@@ -8,11 +8,25 @@
* an interface of type AWDL with interface_create and configures it through
* the awdl_* iovars. The AWDL interface has a network device but no cfg80211
* vif. Its interface events are handled here and not by cfg80211.
+ *
+ * The firmware passes AWDL data frames as 802.3 frames with an LLC/SNAP
+ * header with the Apple OUI and an AWDL data header:
+ *
+ * aa aa 03 00 17 f2 08 00 LLC/SNAP, OUI 00:17:f2, protocol 0x0800
+ * 03 04 AWDL data header
+ * seq (le16) 00 00 sequence number, reserved
+ * ethertype (be16)
+ *
+ * The network device of the AWDL interface sends and receives plain
+ * Ethernet frames. The header is added before frames are sent and removed
+ * from received frames.
*/
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
+#include <linux/skbuff.h>
+#include <linux/unaligned.h>
#include "core.h"
#include "debug.h"
@@ -26,6 +40,14 @@
#define BRCMF_AWDL_IFIDX 2
#define BRCMF_AWDL_IF_TIMEOUT msecs_to_jiffies(2000)
+/* LLC/SNAP with the Apple OUI and the start of the AWDL data header */
+static const u8 brcmf_awdl_llc[10] = {
+ 0xaa, 0xaa, 0x03, 0x00, 0x17, 0xf2, 0x08, 0x00, 0x03, 0x04,
+};
+
+#define BRCMF_AWDL_SEQ_OFF 10
+#define BRCMF_AWDL_ETHERTYPE_OFF 14
+
/* The BSSID of all AWDL frames */
static const u8 brcmf_awdl_bssid[ETH_ALEN] = {
0x00, 0x25, 0x00, 0xff, 0x94, 0x73,
@@ -252,6 +274,67 @@ int brcmf_awdl_del(struct brcmf_pub *drvr)
return err;
}
+/**
+ * brcmf_awdl_encap() - add the AWDL data header to a frame to be sent.
+ *
+ * @ifp: the AWDL interface.
+ * @skb: Ethernet frame, an 802.3 frame on return.
+ *
+ * Called in the transmit path of the AWDL network device, which serializes
+ * it. Return: 0 or a negative error code, the caller frees @skb on error.
+ */
+int brcmf_awdl_encap(struct brcmf_if *ifp, struct sk_buff *skb)
+{
+ struct brcmf_awdl *awdl = &ifp->drvr->awdl;
+ __be16 proto;
+ u8 *hdr;
+
+ if (skb->len < ETH_HLEN || skb_cow_head(skb, BRCMF_AWDL_HDR_LEN))
+ return -EINVAL;
+
+ proto = eth_hdr(skb)->h_proto;
+ hdr = skb_push(skb, BRCMF_AWDL_HDR_LEN);
+ memmove(hdr, hdr + BRCMF_AWDL_HDR_LEN, 2 * ETH_ALEN);
+ put_unaligned_be16(skb->len - ETH_HLEN, hdr + 2 * ETH_ALEN);
+ hdr += ETH_HLEN;
+ memcpy(hdr, brcmf_awdl_llc, sizeof(brcmf_awdl_llc));
+ put_unaligned_le16(awdl->tx_seq++, hdr + BRCMF_AWDL_SEQ_OFF);
+ put_unaligned_le16(0, hdr + BRCMF_AWDL_SEQ_OFF + 2);
+ put_unaligned(proto, (__be16 *)(hdr + BRCMF_AWDL_ETHERTYPE_OFF));
+ skb_reset_mac_header(skb);
+ return 0;
+}
+
+/**
+ * brcmf_awdl_decap() - remove the AWDL data header from a received frame.
+ *
+ * @skb: frame after eth_type_trans(), an Ethernet frame on return.
+ *
+ * The addresses are moved behind the AWDL data header, where they form an
+ * Ethernet header with the ethertype of the AWDL data header. Return: 0 or
+ * a negative error code for frames that are not AWDL data frames.
+ */
+int brcmf_awdl_decap(struct sk_buff *skb)
+{
+ __be16 proto;
+ u8 *eth;
+
+ if (skb->protocol != htons(ETH_P_802_2) ||
+ !pskb_may_pull(skb, BRCMF_AWDL_HDR_LEN) ||
+ memcmp(skb->data, brcmf_awdl_llc, sizeof(brcmf_awdl_llc)) ||
+ skb_cow_head(skb, 0))
+ return -EINVAL;
+
+ proto = get_unaligned((__be16 *)(skb->data + BRCMF_AWDL_ETHERTYPE_OFF));
+ eth = skb->data + BRCMF_AWDL_HDR_LEN - ETH_HLEN;
+ memmove(eth, skb_mac_header(skb), 2 * ETH_ALEN);
+ skb_pull_rcsum(skb, BRCMF_AWDL_HDR_LEN);
+ skb_set_mac_header(skb, -ETH_HLEN);
+ skb_reset_network_header(skb);
+ skb->protocol = proto;
+ return 0;
+}
+
/* Called before the interfaces of the device are removed */
void brcmf_awdl_detach(struct brcmf_pub *drvr)
{
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
index b12cbd32187..3edc0e96bb9 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
@@ -13,6 +13,8 @@
struct brcmf_pub;
struct brcmf_if;
+struct net_device;
+struct sk_buff;
/**
* struct brcmf_awdl - AWDL interface of a device.
@@ -25,6 +27,7 @@ struct brcmf_if;
* @removing: interface_remove was sent, its interface event is awaited.
* @detached: the device goes away, no new AWDL interface is created.
* @ifp: the AWDL interface, NULL when there is none.
+ * @tx_seq: sequence number of the next data frame sent.
*/
struct brcmf_awdl {
spinlock_t lock; /* AWDL state against the event worker */
@@ -34,8 +37,12 @@ struct brcmf_awdl {
bool removing;
bool detached;
struct brcmf_if *ifp;
+ u16 tx_seq;
};
+/* Length of the LLC/SNAP and AWDL data header of AWDL data frames */
+#define BRCMF_AWDL_HDR_LEN 16
+
void brcmf_awdl_attach(struct brcmf_pub *drvr);
void brcmf_awdl_detach(struct brcmf_pub *drvr);
int brcmf_awdl_add(struct brcmf_pub *drvr, const u8 *mac,
@@ -43,5 +50,7 @@ int brcmf_awdl_add(struct brcmf_pub *drvr, const u8 *mac,
int brcmf_awdl_del(struct brcmf_pub *drvr);
bool brcmf_awdl_if_added(struct brcmf_pub *drvr, struct brcmf_if *ifp);
bool brcmf_awdl_if_removed(struct brcmf_pub *drvr, struct brcmf_if *ifp);
+int brcmf_awdl_encap(struct brcmf_if *ifp, struct sk_buff *skb);
+int brcmf_awdl_decap(struct sk_buff *skb);
#endif /* BRCMFMAC_AWDL_H */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 37c558b6f0d..d64d24ce65f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -422,6 +422,12 @@ void brcmf_netif_rx(struct brcmf_if *ifp, struct sk_buff *skb)
return;
}
+ if (ifp->awdl && brcmf_awdl_decap(skb)) {
+ ifp->ndev->stats.rx_dropped++;
+ brcmu_pkt_buf_free_skb(skb);
+ return;
+ }
+
ifp->ndev->stats.rx_bytes += skb->len;
ifp->ndev->stats.rx_packets++;
@@ -653,6 +659,19 @@ static int brcmf_awdl_netdev_open(struct net_device *ndev)
return 0;
}
+static netdev_tx_t brcmf_awdl_netdev_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct brcmf_if *ifp = netdev_priv(ndev);
+
+ if (brcmf_awdl_encap(ifp, skb)) {
+ dev_kfree_skb_any(skb);
+ ndev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+ }
+ return brcmf_netdev_start_xmit(skb, ndev);
+}
+
static int brcmf_awdl_netdev_stop(struct net_device *ndev)
{
netif_stop_queue(ndev);
@@ -667,7 +686,7 @@ static int brcmf_awdl_netdev_stop(struct net_device *ndev)
static const struct net_device_ops brcmf_netdev_ops_awdl = {
.ndo_open = brcmf_awdl_netdev_open,
.ndo_stop = brcmf_awdl_netdev_stop,
- .ndo_start_xmit = brcmf_netdev_start_xmit,
+ .ndo_start_xmit = brcmf_awdl_netdev_start_xmit,
};
static bool brcmf_netdev_is_brcmf(struct net_device *ndev)
@@ -693,6 +712,8 @@ int brcmf_net_attach(struct brcmf_if *ifp, bool locked)
ndev->netdev_ops = &brcmf_netdev_ops_pri;
ndev->needed_headroom += drvr->hdrlen;
+ if (ifp->awdl)
+ ndev->needed_headroom += BRCMF_AWDL_HDR_LEN;
ndev->ethtool_ops = &brcmf_ethtool_ops;
/* set the mac address & netns */
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 6/7] wifi: brcmfmac: add vendor commands for AWDL
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
` (4 preceding siblings ...)
2026-11-04 15:15 ` [PATCH 5/7] wifi: brcmfmac: add the AWDL data path Andre Eikmeyer
@ 2026-11-04 15:15 ` Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 7/7] wifi: brcmfmac: pass AWDL frames and role changes to user space Andre Eikmeyer
6 siblings, 0 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer, Yossef Osman, Aditya Garg
AWDL election, peer discovery and services run in user space. The
firmware sends and receives the AWDL frames and the data once the host
has set its AWDL parameters. Add vendor commands of the Broadcom OUI on
the primary interface for that:
- AWDL_START creates the AWDL interface with the given MAC address and
starts AWDL with the given TLVs. AWDL_STOP stops it and removes the
interface. Registering and unregistering the network device needs
RTNL, vendor commands run with the wiphy lock only. Both commands
therefore run in a work item and report their result with the vendor
events AWDL_STARTED and AWDL_STOPPED.
- AWDL_SET_FRAMES replaces the TLVs the firmware sends, the TLVs of
awdl_payload and awdl_oob_af_auto. The driver adds the headers.
- AWDL_SET_SYNC sets the master to synchronize to, with its metric and
the distance to it. Leaving out the master makes the device its own
master.
- AWDL_PEER_ADD and AWDL_PEER_DEL add and remove a peer data frames go
to. AWDL_PEER_ADD takes the HT and VHT capabilities elements of the
peer, which macOS builds from the AWDL TLVs of the peer and passes in
awdl_peer_op.
The other firmware parameters of AWDL are set by the driver in the
order and with the values macOS 15.4.1 sets on the BCM4377 of a
MacBookAir9,1. The BCM4364 gets slot_bss without SDB,
its firmware traps with the SDB form while the station is on 5 GHz.
Some parameters change 500 ms and 5 s after the start.
The AWDL interface is removed before system sleep with an AWDL_STOPPED
event.
Tested-by: Yossef Osman <Yossef.Osman@proton.me>
Tested-by: Aditya Garg <aditya.garg@linux.dev>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
.../broadcom/brcm80211/brcmfmac/awdl.c | 586 +++++++++++++++++-
.../broadcom/brcm80211/brcmfmac/awdl.h | 53 ++
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 5 +-
.../broadcom/brcm80211/brcmfmac/vendor.c | 78 +++
.../broadcom/brcm80211/brcmfmac/vendor.h | 86 +++
5 files changed, 804 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
index 95619283471..7df77a7fdf4 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
@@ -27,13 +27,18 @@
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <linux/unaligned.h>
+#include <net/netlink.h>
+#include <brcm_hw_ids.h>
#include "core.h"
#include "debug.h"
#include "feature.h"
#include "fwil.h"
#include "fwil_types.h"
+#include "bus.h"
#include "proto.h"
+#include "cfg80211.h"
+#include "vendor.h"
#include "awdl.h"
/* The firmware interface index of the AWDL interface */
@@ -53,10 +58,123 @@ static const u8 brcmf_awdl_bssid[ETH_ALEN] = {
0x00, 0x25, 0x00, 0xff, 0x94, 0x73,
};
+/*
+ * Firmware parameters the host sets for AWDL. The values are the ones the
+ * firmware takes from the host it was made for. Their fields are not known
+ * beyond what the comments say.
+ */
+#define BRCMF_AWDL_CONFIG_START 0x83
+#define BRCMF_AWDL_CONFIG_SETTLED 0x73
+#define BRCMF_AWDL_PRESENCEMODE 4
+#define BRCMF_AWDL_OSOC_CHAN 44
+#define BRCMF_AWDL_AFTX_DELAY msecs_to_jiffies(500)
+#define BRCMF_AWDL_SETTLE_DELAY msecs_to_jiffies(5000)
+#define BRCMF_AWDL_TLV_MAX 2048
+
+static const u8 brcmf_awdl_sync_params[] = {
+ 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x10, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+static const u8 brcmf_awdl_psf_dwell[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6e, 0x00,
+};
+
+/* Header of awdl_oob_af_auto, the AWDL BSSID at 8, followed by the TLV length */
+static const u8 brcmf_awdl_oob_hdr[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xd8, 0x40, 0x00, 0x00,
+ 0x00, 0x25, 0x00, 0xff, 0x94, 0x73, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/* awdl_opmode: byte 1 the role, 2 master and 0 slave, 4 to 9 the master */
+#define BRCMF_AWDL_OPMODE_ROLE 1
+#define BRCMF_AWDL_OPMODE_ROLE_MASTER 2
+#define BRCMF_AWDL_OPMODE_ROLE_SLAVE 0
+#define BRCMF_AWDL_OPMODE_ADDR 4
+static const u8 brcmf_awdl_opmode[] = {
+ 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/* awdl_election_tree: the master at 0x0f, its metric at 0x15, distance 0x19 */
+#define BRCMF_AWDL_ELECTION_ADDR 0x0f
+#define BRCMF_AWDL_ELECTION_METRIC 0x15
+#define BRCMF_AWDL_ELECTION_DISTANCE 0x19
+static const u8 brcmf_awdl_election_tree[] = {
+ 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0xba, 0x00, 0x00, 0x01, 0x03, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+};
+
+/*
+ * slot_bss with the chanspecs of slice 0 at 0x4c and of slice 1 at 0x78, 16
+ * each. Chips without SDB get 9 at 0x0c and all chanspecs in slice 0.
+ */
+#define BRCMF_AWDL_SLOT_SDB 0x0c
+#define BRCMF_AWDL_SLOT_SDB_OFF 9
+#define BRCMF_AWDL_SLOT_SLICE0 0x4c
+#define BRCMF_AWDL_SLOT_SLICE1 0x78
+#define BRCMF_AWDL_SLOTS 16
+static const u8 brcmf_awdl_slot_bss[] = {
+ 0x00, 0x80, 0x01, 0x01, 0x01, 0x00, 0x90, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00,
+ 0x01, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0f, 0x02, 0x00, 0x03, 0xff, 0xff, 0xe2, 0x2a,
+ 0xe2, 0x2a, 0xe2, 0x2a, 0xe2, 0x2a, 0xe2, 0x2a,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x06,
+ 0xe2, 0x2a, 0xe2, 0x2a, 0xe2, 0x2a, 0xe2, 0x2a,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x02, 0x00, 0xe2, 0x2a, 0xe2, 0x2a,
+ 0xe2, 0x2a, 0xe2, 0x2a, 0xe2, 0x2a, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x2a,
+ 0xe2, 0x2a, 0xe2, 0x2a, 0xe2, 0x2a, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x28, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * awdl_peer_op: byte 1 the operation, 2 to 7 the peer, 8 the flags, from 9
+ * the HT and VHT capabilities elements of the peer. An add without them is
+ * 0x25 bytes long.
+ */
+#define BRCMF_AWDL_PEER_OP_ADD 0
+#define BRCMF_AWDL_PEER_OP_DEL 1
+#define BRCMF_AWDL_PEER_OP_HDR_LEN 9
+#define BRCMF_AWDL_PEER_OP_ADD_LEN 0x25
+#define BRCMF_AWDL_PEER_IES_MAX 128
+
+static void brcmf_awdl_ctrl_work(struct work_struct *work);
+static void brcmf_awdl_aftx_work(struct wiphy *wiphy, struct wiphy_work *work);
+static void brcmf_awdl_settle_work(struct wiphy *wiphy,
+ struct wiphy_work *work);
+
void brcmf_awdl_attach(struct brcmf_pub *drvr)
{
- spin_lock_init(&drvr->awdl.lock);
- init_completion(&drvr->awdl.if_event);
+ struct brcmf_awdl *awdl = &drvr->awdl;
+
+ spin_lock_init(&awdl->lock);
+ init_completion(&awdl->if_event);
+ INIT_WORK(&awdl->ctrl_work, brcmf_awdl_ctrl_work);
+ wiphy_delayed_work_init(&awdl->aftx_work, brcmf_awdl_aftx_work);
+ wiphy_delayed_work_init(&awdl->settle_work, brcmf_awdl_settle_work);
}
/*
@@ -248,6 +366,8 @@ int brcmf_awdl_del(struct brcmf_pub *drvr)
if (!ifp)
return -ENOENT;
+ wiphy_delayed_work_cancel(drvr->wiphy, &awdl->aftx_work);
+ wiphy_delayed_work_cancel(drvr->wiphy, &awdl->settle_work);
netif_tx_disable(ifp->ndev);
/* Deletes the flowrings of all peers */
brcmf_proto_configure_addr_mode(drvr, ifp->ifidx, ADDR_INDIRECT);
@@ -335,6 +455,461 @@ int brcmf_awdl_decap(struct sk_buff *skb)
return 0;
}
+/* Sets a firmware parameter of the AWDL interface, errors are only logged */
+static void brcmf_awdl_set(struct brcmf_if *ifp, const char *name,
+ const void *data, u32 len)
+{
+ int err;
+
+ err = brcmf_fil_iovar_data_set(ifp, name, data, len);
+ if (err)
+ brcmf_dbg(INFO, "AWDL %s: %d\n", name, err);
+}
+
+static void brcmf_awdl_set_u32(struct brcmf_if *ifp, const char *name,
+ u32 val)
+{
+ __le32 data = cpu_to_le32(val);
+
+ brcmf_awdl_set(ifp, name, &data, sizeof(data));
+}
+
+static void brcmf_awdl_free_frames(struct brcmf_awdl *awdl)
+{
+ kfree(awdl->payload);
+ kfree(awdl->oob);
+ awdl->payload = NULL;
+ awdl->oob = NULL;
+ awdl->payload_len = 0;
+ awdl->oob_len = 0;
+}
+
+/* Builds awdl_payload and awdl_oob_af_auto around the TLVs of user space */
+static int brcmf_awdl_store_frames(struct brcmf_awdl *awdl,
+ const struct nlattr *payload,
+ const struct nlattr *oob)
+{
+ u32 plen = nla_len(payload), olen = nla_len(oob);
+ u8 *p, *o;
+
+ p = kmalloc(2 + plen, GFP_KERNEL);
+ o = kmalloc(sizeof(brcmf_awdl_oob_hdr) + 2 + olen, GFP_KERNEL);
+ if (!p || !o) {
+ kfree(p);
+ kfree(o);
+ return -ENOMEM;
+ }
+ put_unaligned_le16(plen, p);
+ memcpy(p + 2, nla_data(payload), plen);
+ memcpy(o, brcmf_awdl_oob_hdr, sizeof(brcmf_awdl_oob_hdr));
+ put_unaligned_le16(olen, o + sizeof(brcmf_awdl_oob_hdr));
+ memcpy(o + sizeof(brcmf_awdl_oob_hdr) + 2, nla_data(oob), olen);
+
+ brcmf_awdl_free_frames(awdl);
+ awdl->payload = p;
+ awdl->payload_len = 2 + plen;
+ awdl->oob = o;
+ awdl->oob_len = sizeof(brcmf_awdl_oob_hdr) + 2 + olen;
+ return 0;
+}
+
+static void brcmf_awdl_set_payload(struct brcmf_awdl *awdl)
+{
+ brcmf_awdl_set(awdl->ifp, "awdl_payload", awdl->payload,
+ awdl->payload_len);
+}
+
+static void brcmf_awdl_set_opmode(struct brcmf_awdl *awdl)
+{
+ u8 buf[sizeof(brcmf_awdl_opmode)];
+
+ memcpy(buf, brcmf_awdl_opmode, sizeof(buf));
+ if (awdl->has_master) {
+ buf[BRCMF_AWDL_OPMODE_ROLE] = BRCMF_AWDL_OPMODE_ROLE_SLAVE;
+ ether_addr_copy(buf + BRCMF_AWDL_OPMODE_ADDR, awdl->master);
+ } else {
+ buf[BRCMF_AWDL_OPMODE_ROLE] = BRCMF_AWDL_OPMODE_ROLE_MASTER;
+ ether_addr_copy(buf + BRCMF_AWDL_OPMODE_ADDR, awdl->mac);
+ }
+ brcmf_awdl_set(awdl->ifp, "awdl_opmode", buf, sizeof(buf));
+}
+
+static void brcmf_awdl_set_election(struct brcmf_awdl *awdl)
+{
+ u8 buf[sizeof(brcmf_awdl_election_tree)];
+
+ memcpy(buf, brcmf_awdl_election_tree, sizeof(buf));
+ if (awdl->has_master) {
+ ether_addr_copy(buf + BRCMF_AWDL_ELECTION_ADDR, awdl->master);
+ put_unaligned_le32(awdl->master_metric,
+ buf + BRCMF_AWDL_ELECTION_METRIC);
+ buf[BRCMF_AWDL_ELECTION_DISTANCE] = awdl->master_distance;
+ } else {
+ ether_addr_copy(buf + BRCMF_AWDL_ELECTION_ADDR, awdl->mac);
+ }
+ brcmf_awdl_set(awdl->ifp, "awdl_election_tree", buf, sizeof(buf));
+}
+
+static void brcmf_awdl_set_slots(struct brcmf_pub *drvr)
+{
+ u8 buf[sizeof(brcmf_awdl_slot_bss)];
+ u8 *s0, *s1;
+ int i;
+
+ memcpy(buf, brcmf_awdl_slot_bss, sizeof(buf));
+ /* The BCM4364 firmware traps with the SDB form while the station is
+ * on 5 GHz
+ */
+ if (drvr->bus_if->chip == BRCM_CC_4364_CHIP_ID) {
+ buf[BRCMF_AWDL_SLOT_SDB] = BRCMF_AWDL_SLOT_SDB_OFF;
+ for (i = 0; i < BRCMF_AWDL_SLOTS; i++) {
+ s0 = buf + BRCMF_AWDL_SLOT_SLICE0 + 2 * i;
+ s1 = buf + BRCMF_AWDL_SLOT_SLICE1 + 2 * i;
+ if (!get_unaligned_le16(s0))
+ memcpy(s0, s1, 2);
+ memset(s1, 0, 2);
+ }
+ }
+ brcmf_awdl_set(drvr->awdl.ifp, "slot_bss", buf, sizeof(buf));
+}
+
+/* Sends the frames and the synchronization state again */
+static void brcmf_awdl_refresh(struct brcmf_awdl *awdl)
+{
+ brcmf_awdl_set(awdl->ifp, "awdl_oob_af_auto", awdl->oob,
+ awdl->oob_len);
+ brcmf_awdl_set_payload(awdl);
+ brcmf_awdl_set(awdl->ifp, "awdl_sync_params", brcmf_awdl_sync_params,
+ sizeof(brcmf_awdl_sync_params));
+ brcmf_awdl_set_opmode(awdl);
+ brcmf_awdl_set_election(awdl);
+ brcmf_awdl_set(awdl->ifp, "awdl_sync_params", brcmf_awdl_sync_params,
+ sizeof(brcmf_awdl_sync_params));
+}
+
+/* Starts AWDL on the AWDL interface, the firmware expects this order */
+static void brcmf_awdl_enable(struct brcmf_pub *drvr)
+{
+ struct brcmf_awdl *awdl = &drvr->awdl;
+ struct brcmf_if *ifp = awdl->ifp;
+
+ brcmf_awdl_set_payload(awdl);
+ brcmf_awdl_set_u32(ifp, "awdl_aftxmode", 0);
+ brcmf_awdl_set(ifp, "awdl_sync_params", brcmf_awdl_sync_params,
+ sizeof(brcmf_awdl_sync_params));
+ brcmf_awdl_set_u32(ifp, "awdl_osoc_chan", 0);
+ brcmf_awdl_set_u32(ifp, "awdl_config", BRCMF_AWDL_CONFIG_START);
+ brcmf_awdl_set(ifp, "awdl_psf_dwell", brcmf_awdl_psf_dwell,
+ sizeof(brcmf_awdl_psf_dwell));
+ brcmf_awdl_set_slots(drvr);
+ brcmf_awdl_set_payload(awdl);
+ brcmf_awdl_set_election(awdl);
+ brcmf_awdl_set_u32(ifp, "awdl", 1);
+ brcmf_awdl_set_u32(ifp, "awdl_presencemode", BRCMF_AWDL_PRESENCEMODE);
+ brcmf_awdl_set_slots(drvr);
+ brcmf_awdl_refresh(awdl);
+ wiphy_delayed_work_queue(drvr->wiphy, &awdl->aftx_work,
+ BRCMF_AWDL_AFTX_DELAY);
+ wiphy_delayed_work_queue(drvr->wiphy, &awdl->settle_work,
+ BRCMF_AWDL_SETTLE_DELAY);
+}
+
+static void brcmf_awdl_aftx_work(struct wiphy *wiphy, struct wiphy_work *work)
+{
+ struct brcmf_awdl *awdl = container_of(work, struct brcmf_awdl,
+ aftx_work.work);
+
+ if (!awdl->ifp)
+ return;
+ brcmf_awdl_set_u32(awdl->ifp, "awdl_aftxmode", 0);
+ brcmf_awdl_set_u32(awdl->ifp, "awdl_osoc_chan", BRCMF_AWDL_OSOC_CHAN);
+}
+
+static void brcmf_awdl_settle_work(struct wiphy *wiphy,
+ struct wiphy_work *work)
+{
+ struct brcmf_awdl *awdl = container_of(work, struct brcmf_awdl,
+ settle_work.work);
+ static const u8 dwell_off[sizeof(brcmf_awdl_psf_dwell)];
+
+ if (!awdl->ifp)
+ return;
+ brcmf_awdl_set_u32(awdl->ifp, "awdl_config", BRCMF_AWDL_CONFIG_SETTLED);
+ brcmf_awdl_set(awdl->ifp, "awdl_psf_dwell", dwell_off,
+ sizeof(dwell_off));
+}
+
+static void brcmf_awdl_send_result(struct brcmf_pub *drvr, int event,
+ int status, struct net_device *ndev)
+{
+ struct brcmf_if *pri = brcmf_get_ifp(drvr, 0);
+ struct sk_buff *skb;
+
+ skb = cfg80211_vendor_event_alloc(drvr->wiphy,
+ pri && pri->vif ? &pri->vif->wdev :
+ NULL,
+ NLMSG_DEFAULT_SIZE, event,
+ GFP_KERNEL);
+ if (!skb)
+ return;
+ if (nla_put_s32(skb, BRCMF_AWDL_ATTR_STATUS, status) ||
+ (ndev && nla_put_u32(skb, BRCMF_AWDL_ATTR_IFINDEX,
+ ndev->ifindex))) {
+ kfree_skb(skb);
+ return;
+ }
+ cfg80211_vendor_event(skb, GFP_KERNEL);
+}
+
+/* Runs the AWDL_START and AWDL_STOP vendor commands with RTNL */
+static void brcmf_awdl_ctrl_work(struct work_struct *work)
+{
+ struct brcmf_awdl *awdl = container_of(work, struct brcmf_awdl,
+ ctrl_work);
+ struct brcmf_pub *drvr = container_of(awdl, struct brcmf_pub, awdl);
+ struct net_device *ndev = NULL;
+ enum brcmf_awdl_op op;
+ int err;
+
+ rtnl_lock();
+ wiphy_lock(drvr->wiphy);
+ spin_lock(&awdl->lock);
+ op = awdl->op;
+ awdl->op = BRCMF_AWDL_OP_NONE;
+ spin_unlock(&awdl->lock);
+
+ switch (op) {
+ case BRCMF_AWDL_OP_START:
+ err = brcmf_awdl_add(drvr, awdl->start_mac, &ndev);
+ if (!err) {
+ awdl->has_master = false;
+ brcmf_awdl_enable(drvr);
+ }
+ brcmf_awdl_send_result(drvr, BRCMF_VNDR_EVTS_AWDL_STARTED, err,
+ ndev);
+ break;
+ case BRCMF_AWDL_OP_STOP:
+ err = brcmf_awdl_del(drvr);
+ brcmf_awdl_send_result(drvr, BRCMF_VNDR_EVTS_AWDL_STOPPED, err,
+ NULL);
+ break;
+ default:
+ break;
+ }
+ wiphy_unlock(drvr->wiphy);
+ rtnl_unlock();
+}
+
+/*
+ * Removes the AWDL interface before system sleep, with RTNL and the wiphy
+ * mutex held, while the firmware still takes commands. A queued operation is
+ * dropped.
+ */
+void brcmf_awdl_suspend(struct brcmf_pub *drvr)
+{
+ struct brcmf_awdl *awdl = &drvr->awdl;
+ int err;
+
+ spin_lock(&awdl->lock);
+ awdl->op = BRCMF_AWDL_OP_NONE;
+ spin_unlock(&awdl->lock);
+ if (!awdl->ifp)
+ return;
+ err = brcmf_awdl_del(drvr);
+ brcmf_awdl_send_result(drvr, BRCMF_VNDR_EVTS_AWDL_STOPPED, err, NULL);
+}
+
+const struct nla_policy brcmf_awdl_vndr_policy[BRCMF_AWDL_ATTR_MAX + 1] = {
+ [BRCMF_AWDL_ATTR_ADDR] = NLA_POLICY_ETH_ADDR,
+ [BRCMF_AWDL_ATTR_PAYLOAD] = { .type = NLA_BINARY,
+ .len = BRCMF_AWDL_TLV_MAX },
+ [BRCMF_AWDL_ATTR_OOB] = { .type = NLA_BINARY,
+ .len = BRCMF_AWDL_TLV_MAX },
+ [BRCMF_AWDL_ATTR_MASTER] = NLA_POLICY_ETH_ADDR,
+ [BRCMF_AWDL_ATTR_MASTER_METRIC] = { .type = NLA_U32 },
+ [BRCMF_AWDL_ATTR_MASTER_DISTANCE] = { .type = NLA_U8 },
+ [BRCMF_AWDL_ATTR_STATUS] = { .type = NLA_REJECT },
+ [BRCMF_AWDL_ATTR_IFINDEX] = { .type = NLA_REJECT },
+ [BRCMF_AWDL_ATTR_PEER_IES] = { .type = NLA_BINARY,
+ .len = BRCMF_AWDL_PEER_IES_MAX },
+};
+
+/* The device of a vendor command and its parsed attributes */
+static struct brcmf_pub *brcmf_awdl_vndr_parse(struct wiphy *wiphy,
+ const void *data, int len,
+ struct nlattr **tb)
+{
+ struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct brcmf_pub *drvr = cfg->pub;
+ struct brcmf_if *pri = brcmf_get_ifp(drvr, 0);
+ int err;
+
+ if (!pri || !brcmf_feat_is_enabled(pri, BRCMF_FEAT_AWDL))
+ return ERR_PTR(-EOPNOTSUPP);
+ err = nla_parse(tb, BRCMF_AWDL_ATTR_MAX, data, len,
+ brcmf_awdl_vndr_policy, NULL);
+ if (err)
+ return ERR_PTR(err);
+ return drvr;
+}
+
+int brcmf_awdl_vndr_start(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len)
+{
+ struct nlattr *tb[BRCMF_AWDL_ATTR_MAX + 1];
+ struct brcmf_pub *drvr;
+ struct brcmf_awdl *awdl;
+ int err;
+
+ drvr = brcmf_awdl_vndr_parse(wiphy, data, len, tb);
+ if (IS_ERR(drvr))
+ return PTR_ERR(drvr);
+ awdl = &drvr->awdl;
+ if (!tb[BRCMF_AWDL_ATTR_ADDR] || !tb[BRCMF_AWDL_ATTR_PAYLOAD] ||
+ !tb[BRCMF_AWDL_ATTR_OOB])
+ return -EINVAL;
+ if (!is_valid_ether_addr(nla_data(tb[BRCMF_AWDL_ATTR_ADDR])))
+ return -EINVAL;
+ if (awdl->detached)
+ return -ENODEV;
+ if (awdl->ifp || awdl->op != BRCMF_AWDL_OP_NONE)
+ return -EBUSY;
+
+ err = brcmf_awdl_store_frames(awdl, tb[BRCMF_AWDL_ATTR_PAYLOAD],
+ tb[BRCMF_AWDL_ATTR_OOB]);
+ if (err)
+ return err;
+ ether_addr_copy(awdl->start_mac, nla_data(tb[BRCMF_AWDL_ATTR_ADDR]));
+ spin_lock(&awdl->lock);
+ awdl->op = BRCMF_AWDL_OP_START;
+ spin_unlock(&awdl->lock);
+ schedule_work(&awdl->ctrl_work);
+ return 0;
+}
+
+int brcmf_awdl_vndr_stop(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len)
+{
+ struct nlattr *tb[BRCMF_AWDL_ATTR_MAX + 1];
+ struct brcmf_pub *drvr;
+ struct brcmf_awdl *awdl;
+
+ drvr = brcmf_awdl_vndr_parse(wiphy, data, len, tb);
+ if (IS_ERR(drvr))
+ return PTR_ERR(drvr);
+ awdl = &drvr->awdl;
+ if (awdl->detached)
+ return -ENODEV;
+ if (!awdl->ifp && awdl->op != BRCMF_AWDL_OP_START)
+ return -ENOENT;
+
+ spin_lock(&awdl->lock);
+ awdl->op = BRCMF_AWDL_OP_STOP;
+ spin_unlock(&awdl->lock);
+ schedule_work(&awdl->ctrl_work);
+ return 0;
+}
+
+int brcmf_awdl_vndr_set_frames(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len)
+{
+ struct nlattr *tb[BRCMF_AWDL_ATTR_MAX + 1];
+ struct brcmf_pub *drvr;
+ int err;
+
+ drvr = brcmf_awdl_vndr_parse(wiphy, data, len, tb);
+ if (IS_ERR(drvr))
+ return PTR_ERR(drvr);
+ if (!tb[BRCMF_AWDL_ATTR_PAYLOAD] || !tb[BRCMF_AWDL_ATTR_OOB])
+ return -EINVAL;
+ if (!drvr->awdl.ifp)
+ return -ENOENT;
+
+ err = brcmf_awdl_store_frames(&drvr->awdl, tb[BRCMF_AWDL_ATTR_PAYLOAD],
+ tb[BRCMF_AWDL_ATTR_OOB]);
+ if (err)
+ return err;
+ brcmf_awdl_refresh(&drvr->awdl);
+ return 0;
+}
+
+int brcmf_awdl_vndr_set_sync(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len)
+{
+ struct nlattr *tb[BRCMF_AWDL_ATTR_MAX + 1];
+ struct brcmf_pub *drvr;
+ struct brcmf_awdl *awdl;
+
+ drvr = brcmf_awdl_vndr_parse(wiphy, data, len, tb);
+ if (IS_ERR(drvr))
+ return PTR_ERR(drvr);
+ awdl = &drvr->awdl;
+ if (!awdl->ifp)
+ return -ENOENT;
+
+ if (tb[BRCMF_AWDL_ATTR_MASTER]) {
+ if (!tb[BRCMF_AWDL_ATTR_MASTER_METRIC] ||
+ !tb[BRCMF_AWDL_ATTR_MASTER_DISTANCE])
+ return -EINVAL;
+ awdl->has_master = true;
+ ether_addr_copy(awdl->master,
+ nla_data(tb[BRCMF_AWDL_ATTR_MASTER]));
+ awdl->master_metric =
+ nla_get_u32(tb[BRCMF_AWDL_ATTR_MASTER_METRIC]);
+ awdl->master_distance =
+ nla_get_u8(tb[BRCMF_AWDL_ATTR_MASTER_DISTANCE]);
+ } else {
+ awdl->has_master = false;
+ }
+ brcmf_awdl_refresh(awdl);
+ return 0;
+}
+
+static int brcmf_awdl_peer_op(struct wiphy *wiphy, const void *data, int len,
+ bool add)
+{
+ u8 op[BRCMF_AWDL_PEER_OP_HDR_LEN + BRCMF_AWDL_PEER_IES_MAX] = {};
+ struct nlattr *tb[BRCMF_AWDL_ATTR_MAX + 1];
+ u32 op_len = BRCMF_AWDL_PEER_OP_HDR_LEN;
+ struct brcmf_pub *drvr;
+ struct nlattr *ies;
+
+ drvr = brcmf_awdl_vndr_parse(wiphy, data, len, tb);
+ if (IS_ERR(drvr))
+ return PTR_ERR(drvr);
+ if (!tb[BRCMF_AWDL_ATTR_ADDR])
+ return -EINVAL;
+ if (!drvr->awdl.ifp)
+ return -ENOENT;
+
+ op[1] = add ? BRCMF_AWDL_PEER_OP_ADD : BRCMF_AWDL_PEER_OP_DEL;
+ ether_addr_copy(op + 2, nla_data(tb[BRCMF_AWDL_ATTR_ADDR]));
+ op[8] = add;
+ if (add) {
+ ies = tb[BRCMF_AWDL_ATTR_PEER_IES];
+ if (ies) {
+ memcpy(op + BRCMF_AWDL_PEER_OP_HDR_LEN, nla_data(ies),
+ nla_len(ies));
+ op_len += nla_len(ies);
+ }
+ op_len = max_t(u32, op_len, BRCMF_AWDL_PEER_OP_ADD_LEN);
+ }
+ return brcmf_fil_iovar_data_set(drvr->awdl.ifp, "awdl_peer_op", op,
+ op_len);
+}
+
+int brcmf_awdl_vndr_peer_add(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len)
+{
+ return brcmf_awdl_peer_op(wiphy, data, len, true);
+}
+
+int brcmf_awdl_vndr_peer_del(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len)
+{
+ return brcmf_awdl_peer_op(wiphy, data, len, false);
+}
+
/* Called before the interfaces of the device are removed */
void brcmf_awdl_detach(struct brcmf_pub *drvr)
{
@@ -342,10 +917,17 @@ void brcmf_awdl_detach(struct brcmf_pub *drvr)
rtnl_lock();
wiphy_lock(drvr->wiphy);
+ wiphy_delayed_work_cancel(drvr->wiphy, &awdl->aftx_work);
+ wiphy_delayed_work_cancel(drvr->wiphy, &awdl->settle_work);
spin_lock(&awdl->lock);
awdl->detached = true;
+ awdl->op = BRCMF_AWDL_OP_NONE;
awdl->ifp = NULL;
spin_unlock(&awdl->lock);
+ brcmf_awdl_free_frames(awdl);
wiphy_unlock(drvr->wiphy);
rtnl_unlock();
+
+ /* A queued operation finds the device detached */
+ cancel_work_sync(&awdl->ctrl_work);
}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
index 3edc0e96bb9..63030221dea 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
@@ -10,12 +10,21 @@
#include <linux/completion.h>
#include <linux/if_ether.h>
#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <net/cfg80211.h>
+#include <net/netlink.h>
struct brcmf_pub;
struct brcmf_if;
struct net_device;
struct sk_buff;
+enum brcmf_awdl_op {
+ BRCMF_AWDL_OP_NONE,
+ BRCMF_AWDL_OP_START,
+ BRCMF_AWDL_OP_STOP,
+};
+
/**
* struct brcmf_awdl - AWDL interface of a device.
*
@@ -28,6 +37,22 @@ struct sk_buff;
* @detached: the device goes away, no new AWDL interface is created.
* @ifp: the AWDL interface, NULL when there is none.
* @tx_seq: sequence number of the next data frame sent.
+ * @ctrl_work: creates or removes the AWDL interface for the vendor commands,
+ * which run without RTNL.
+ * @op: operation of @ctrl_work, under @lock.
+ * @start_mac: MAC address for %BRCMF_AWDL_OP_START.
+ * @payload: awdl_payload with the TLVs of user space.
+ * @payload_len: length of @payload.
+ * @oob: awdl_oob_af_auto with the TLVs of user space.
+ * @oob_len: length of @oob.
+ * @has_master: another device is the master.
+ * @master: MAC address of the master.
+ * @master_metric: metric of the master.
+ * @master_distance: hops to the master.
+ * @aftx_work: changes the firmware parameters 500 ms after the start.
+ * @settle_work: changes the firmware parameters 5 s after the start.
+ *
+ * The members from @start_mac on are protected by the wiphy mutex.
*/
struct brcmf_awdl {
spinlock_t lock; /* AWDL state against the event worker */
@@ -38,6 +63,19 @@ struct brcmf_awdl {
bool detached;
struct brcmf_if *ifp;
u16 tx_seq;
+ struct work_struct ctrl_work;
+ enum brcmf_awdl_op op;
+ u8 start_mac[ETH_ALEN];
+ u8 *payload;
+ u32 payload_len;
+ u8 *oob;
+ u32 oob_len;
+ bool has_master;
+ u8 master[ETH_ALEN];
+ u32 master_metric;
+ u8 master_distance;
+ struct wiphy_delayed_work aftx_work;
+ struct wiphy_delayed_work settle_work;
};
/* Length of the LLC/SNAP and AWDL data header of AWDL data frames */
@@ -48,9 +86,24 @@ void brcmf_awdl_detach(struct brcmf_pub *drvr);
int brcmf_awdl_add(struct brcmf_pub *drvr, const u8 *mac,
struct net_device **ndev);
int brcmf_awdl_del(struct brcmf_pub *drvr);
+void brcmf_awdl_suspend(struct brcmf_pub *drvr);
bool brcmf_awdl_if_added(struct brcmf_pub *drvr, struct brcmf_if *ifp);
bool brcmf_awdl_if_removed(struct brcmf_pub *drvr, struct brcmf_if *ifp);
int brcmf_awdl_encap(struct brcmf_if *ifp, struct sk_buff *skb);
int brcmf_awdl_decap(struct sk_buff *skb);
+extern const struct nla_policy brcmf_awdl_vndr_policy[];
+int brcmf_awdl_vndr_start(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len);
+int brcmf_awdl_vndr_stop(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len);
+int brcmf_awdl_vndr_set_frames(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len);
+int brcmf_awdl_vndr_set_sync(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len);
+int brcmf_awdl_vndr_peer_add(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len);
+int brcmf_awdl_vndr_peer_del(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const void *data, int len);
+
#endif /* BRCMFMAC_AWDL_H */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 4f35dbef082..41ea0481cf1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -4282,8 +4282,7 @@ static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy,
/* The AWDL interface does not survive sleep, the firmware still
* takes commands here. User space creates it again after resume.
*/
- if (cfg->pub->awdl.ifp)
- brcmf_awdl_del(cfg->pub);
+ brcmf_awdl_suspend(cfg->pub);
/* if the primary net_device is not READY there is nothing
* we can do but pray resume goes smoothly.
@@ -7901,6 +7900,8 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
/* vendor commands/events support */
wiphy->vendor_commands = brcmf_vendor_cmds;
wiphy->n_vendor_commands = BRCMF_VNDR_CMDS_LAST - 1;
+ wiphy->vendor_events = brcmf_vendor_events;
+ wiphy->n_vendor_events = BRCMF_VNDR_EVTS_LAST;
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_WOWL))
brcmf_wiphy_wowl_params(wiphy, ifp);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
index d07e7c7355d..5e7de07c995 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
@@ -15,6 +15,7 @@
#include "cfg80211.h"
#include "vendor.h"
#include "fwil.h"
+#include "awdl.h"
static int brcmf_cfg80211_vndr_cmds_dcmd_handler(struct wiphy *wiphy,
struct wireless_dev *wdev,
@@ -115,4 +116,81 @@ const struct wiphy_vendor_command brcmf_vendor_cmds[] = {
.policy = VENDOR_CMD_RAW_DATA,
.doit = brcmf_cfg80211_vndr_cmds_dcmd_handler
},
+ {
+ {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_CMDS_AWDL_START
+ },
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV,
+ .policy = brcmf_awdl_vndr_policy,
+ .maxattr = BRCMF_AWDL_ATTR_MAX,
+ .doit = brcmf_awdl_vndr_start
+ },
+ {
+ {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_CMDS_AWDL_STOP
+ },
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV,
+ .policy = brcmf_awdl_vndr_policy,
+ .maxattr = BRCMF_AWDL_ATTR_MAX,
+ .doit = brcmf_awdl_vndr_stop
+ },
+ {
+ {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_CMDS_AWDL_SET_FRAMES
+ },
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV,
+ .policy = brcmf_awdl_vndr_policy,
+ .maxattr = BRCMF_AWDL_ATTR_MAX,
+ .doit = brcmf_awdl_vndr_set_frames
+ },
+ {
+ {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_CMDS_AWDL_SET_SYNC
+ },
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV,
+ .policy = brcmf_awdl_vndr_policy,
+ .maxattr = BRCMF_AWDL_ATTR_MAX,
+ .doit = brcmf_awdl_vndr_set_sync
+ },
+ {
+ {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_CMDS_AWDL_PEER_ADD
+ },
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV,
+ .policy = brcmf_awdl_vndr_policy,
+ .maxattr = BRCMF_AWDL_ATTR_MAX,
+ .doit = brcmf_awdl_vndr_peer_add
+ },
+ {
+ {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_CMDS_AWDL_PEER_DEL
+ },
+ .flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+ WIPHY_VENDOR_CMD_NEED_NETDEV,
+ .policy = brcmf_awdl_vndr_policy,
+ .maxattr = BRCMF_AWDL_ATTR_MAX,
+ .doit = brcmf_awdl_vndr_peer_del
+ },
+};
+
+const struct nl80211_vendor_cmd_info brcmf_vendor_events[] = {
+ [BRCMF_VNDR_EVTS_AWDL_STARTED] = {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_EVTS_AWDL_STARTED
+ },
+ [BRCMF_VNDR_EVTS_AWDL_STOPPED] = {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_EVTS_AWDL_STOPPED
+ },
};
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h
index 418f33ea6fd..ce7f7c46710 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h
@@ -8,12 +8,97 @@
#define BROADCOM_OUI 0x001018
+/**
+ * enum brcmf_vndr_cmds - vendor commands of the Broadcom OUI
+ *
+ * @BRCMF_VNDR_CMDS_DCMD: firmware command, see struct brcmf_vndr_dcmd_hdr.
+ * @BRCMF_VNDR_CMDS_AWDL_START: create the AWDL interface with
+ * %BRCMF_AWDL_ATTR_ADDR and start AWDL with the TLVs of
+ * %BRCMF_AWDL_ATTR_PAYLOAD and %BRCMF_AWDL_ATTR_OOB. Runs asynchronously,
+ * the result comes with %BRCMF_VNDR_EVTS_AWDL_STARTED.
+ * @BRCMF_VNDR_CMDS_AWDL_STOP: stop AWDL and remove the AWDL interface. Runs
+ * asynchronously, the result comes with %BRCMF_VNDR_EVTS_AWDL_STOPPED.
+ * @BRCMF_VNDR_CMDS_AWDL_SET_FRAMES: replace the TLVs of
+ * %BRCMF_AWDL_ATTR_PAYLOAD and %BRCMF_AWDL_ATTR_OOB.
+ * @BRCMF_VNDR_CMDS_AWDL_SET_SYNC: synchronize to the master of
+ * %BRCMF_AWDL_ATTR_MASTER, %BRCMF_AWDL_ATTR_MASTER_METRIC and
+ * %BRCMF_AWDL_ATTR_MASTER_DISTANCE. Without %BRCMF_AWDL_ATTR_MASTER the
+ * device is its own master.
+ * @BRCMF_VNDR_CMDS_AWDL_PEER_ADD: add the peer %BRCMF_AWDL_ATTR_ADDR, data
+ * frames to it are sent from then on. %BRCMF_AWDL_ATTR_PEER_IES gives its
+ * HT and VHT capabilities, sent again it updates them.
+ * @BRCMF_VNDR_CMDS_AWDL_PEER_DEL: remove the peer %BRCMF_AWDL_ATTR_ADDR.
+ * @BRCMF_VNDR_CMDS_LAST: number of vendor commands plus one.
+ *
+ * The AWDL commands are sent to the primary interface. Election, peer
+ * discovery and services are left to user space, the firmware sends and
+ * receives the AWDL frames and data.
+ */
enum brcmf_vndr_cmds {
BRCMF_VNDR_CMDS_UNSPEC,
BRCMF_VNDR_CMDS_DCMD,
+ BRCMF_VNDR_CMDS_AWDL_START,
+ BRCMF_VNDR_CMDS_AWDL_STOP,
+ BRCMF_VNDR_CMDS_AWDL_SET_FRAMES,
+ BRCMF_VNDR_CMDS_AWDL_SET_SYNC,
+ BRCMF_VNDR_CMDS_AWDL_PEER_ADD,
+ BRCMF_VNDR_CMDS_AWDL_PEER_DEL,
BRCMF_VNDR_CMDS_LAST
};
+/**
+ * enum brcmf_vndr_evts - vendor events of the Broadcom OUI
+ *
+ * @BRCMF_VNDR_EVTS_AWDL_STARTED: result of %BRCMF_VNDR_CMDS_AWDL_START with
+ * %BRCMF_AWDL_ATTR_STATUS and, on success, %BRCMF_AWDL_ATTR_IFINDEX.
+ * @BRCMF_VNDR_EVTS_AWDL_STOPPED: the AWDL interface was removed, after
+ * %BRCMF_VNDR_CMDS_AWDL_STOP or before system sleep, with
+ * %BRCMF_AWDL_ATTR_STATUS.
+ * @BRCMF_VNDR_EVTS_LAST: number of vendor events.
+ *
+ * The value of an event is its index in the vendor events and its vendor
+ * subcommand.
+ */
+enum brcmf_vndr_evts {
+ BRCMF_VNDR_EVTS_AWDL_STARTED,
+ BRCMF_VNDR_EVTS_AWDL_STOPPED,
+ BRCMF_VNDR_EVTS_LAST
+};
+
+/**
+ * enum brcmf_awdl_attrs - attributes of the AWDL vendor commands and events
+ *
+ * @BRCMF_AWDL_ATTR_ADDR: MAC address, of the AWDL interface or of a peer.
+ * @BRCMF_AWDL_ATTR_PAYLOAD: AWDL TLVs for the firmware parameter
+ * awdl_payload.
+ * @BRCMF_AWDL_ATTR_OOB: AWDL TLVs for the firmware parameter
+ * awdl_oob_af_auto.
+ * @BRCMF_AWDL_ATTR_MASTER: MAC address of the master (u8[6]).
+ * @BRCMF_AWDL_ATTR_MASTER_METRIC: metric of the master (u32).
+ * @BRCMF_AWDL_ATTR_MASTER_DISTANCE: hops from this device to the master (u8).
+ * @BRCMF_AWDL_ATTR_STATUS: 0 or a negative error code (s32).
+ * @BRCMF_AWDL_ATTR_IFINDEX: interface index of the AWDL interface (u32).
+ * @BRCMF_AWDL_ATTR_PEER_IES: HT and VHT capabilities elements of a peer,
+ * built from the AWDL TLVs it sends, as macOS passes them.
+ * @BRCMF_AWDL_ATTR_MAX: highest attribute.
+ */
+enum brcmf_awdl_attrs {
+ BRCMF_AWDL_ATTR_UNSPEC,
+
+ BRCMF_AWDL_ATTR_ADDR,
+ BRCMF_AWDL_ATTR_PAYLOAD,
+ BRCMF_AWDL_ATTR_OOB,
+ BRCMF_AWDL_ATTR_MASTER,
+ BRCMF_AWDL_ATTR_MASTER_METRIC,
+ BRCMF_AWDL_ATTR_MASTER_DISTANCE,
+ BRCMF_AWDL_ATTR_STATUS,
+ BRCMF_AWDL_ATTR_IFINDEX,
+ BRCMF_AWDL_ATTR_PEER_IES,
+
+ __BRCMF_AWDL_ATTR_AFTER_LAST,
+ BRCMF_AWDL_ATTR_MAX = __BRCMF_AWDL_ATTR_AFTER_LAST - 1
+};
+
/**
* enum brcmf_nlattrs - nl80211 message attributes
*
@@ -49,5 +134,6 @@ struct brcmf_vndr_dcmd_hdr {
};
extern const struct wiphy_vendor_command brcmf_vendor_cmds[];
+extern const struct nl80211_vendor_cmd_info brcmf_vendor_events[];
#endif /* _vendor_h_ */
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 7/7] wifi: brcmfmac: pass AWDL frames and role changes to user space
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
` (5 preceding siblings ...)
2026-11-04 15:15 ` [PATCH 6/7] wifi: brcmfmac: add vendor commands for AWDL Andre Eikmeyer
@ 2026-11-04 15:15 ` Andre Eikmeyer
6 siblings, 0 replies; 8+ messages in thread
From: Andre Eikmeyer @ 2026-11-04 15:15 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Andre Eikmeyer, Yossef Osman, Aditya Garg
User space runs the AWDL election and peer discovery from the AWDL
action frames other devices send. The firmware reports them with two
events on the AWDL interface, ACTION_FRAME_RX and AWDL_EVENT (98). A
header is in front of the frame. Its first 16 bits, high byte first,
are its version: 1 is struct brcmf_rx_mgmt_data with 16 bytes, 2 has 24
bytes. Both events come with either version. AWDL_EVENT carries a frame
only with some of its status values. AWDL_ROLE (97) reports the AWDL
role of the firmware in its status, 0 slave, 1 master not taking part
in the election and 2 master.
Register the two AWDL events when the firmware supports AWDL, so the
event mask includes them. Pass every AWDL action frame (vendor specific
category, OUI 00:17:f2, type 8) of the two events and the role changes
to user space as the vendor events AWDL_FRAME and AWDL_ROLE. Other
events of the AWDL interface are dropped as before.
Tested-by: Yossef Osman <Yossef.Osman@proton.me>
Tested-by: Aditya Garg <aditya.garg@linux.dev>
Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
.../broadcom/brcm80211/brcmfmac/awdl.c | 122 +++++++++++++++++-
.../broadcom/brcm80211/brcmfmac/awdl.h | 4 +
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 1 +
.../broadcom/brcm80211/brcmfmac/fweh.c | 8 +-
.../broadcom/brcm80211/brcmfmac/fweh.h | 5 +
.../broadcom/brcm80211/brcmfmac/vendor.c | 8 ++
.../broadcom/brcm80211/brcmfmac/vendor.h | 12 ++
7 files changed, 151 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
index 7df77a7fdf4..c8d92e84c5a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.c
@@ -639,17 +639,125 @@ static void brcmf_awdl_settle_work(struct wiphy *wiphy,
sizeof(dwell_off));
}
+/*
+ * ACTION_FRAME_RX and AWDL_EVENT put a header in front of a received frame.
+ * Its first 16 bits, high byte first, are the version: 1 is struct
+ * brcmf_rx_mgmt_data with 16 bytes, 2 has 24 bytes.
+ */
+#define BRCMF_AWDL_RX_HDR_V1_LEN 16
+#define BRCMF_AWDL_RX_HDR_V2_LEN 24
+
+/* AWDL action frame: vendor specific category, Apple OUI, type AWDL */
+static const u8 brcmf_awdl_af[] = { 0x7f, 0x00, 0x17, 0xf2, 0x08 };
+
+static struct sk_buff *brcmf_awdl_event_alloc(struct brcmf_pub *drvr,
+ int event, int len)
+{
+ struct brcmf_if *pri = brcmf_get_ifp(drvr, 0);
+
+ return cfg80211_vendor_event_alloc(drvr->wiphy,
+ pri && pri->vif ? &pri->vif->wdev :
+ NULL,
+ len, event, GFP_KERNEL);
+}
+
+/**
+ * brcmf_awdl_fweh_event() - pass a firmware event of the AWDL interface on.
+ *
+ * @ifp: interface of the event.
+ * @e: event message.
+ * @data: event data.
+ *
+ * Called from the firmware event worker for the events of the AWDL
+ * interface, which do not go to the cfg80211 handlers. Received AWDL action
+ * frames and role changes become vendor events, other events are dropped.
+ * AWDL_EVENT carries an AWDL action frame with some of its status values.
+ * Return: 0 or a negative error code.
+ */
+int brcmf_awdl_fweh_event(struct brcmf_if *ifp,
+ const struct brcmf_event_msg *e, void *data)
+{
+ struct brcmf_pub *drvr = ifp->drvr;
+ struct sk_buff *skb;
+ u32 hdr;
+
+ if (!ifp->awdl)
+ return 0;
+
+ switch (e->event_code) {
+ case BRCMF_E_ACTION_FRAME_RX:
+ case BRCMF_E_AWDL_EVENT:
+ break;
+ case BRCMF_E_AWDL_ROLE:
+ skb = brcmf_awdl_event_alloc(drvr, BRCMF_VNDR_EVTS_AWDL_ROLE,
+ NLMSG_DEFAULT_SIZE);
+ if (!skb)
+ return -ENOMEM;
+ if (nla_put_u32(skb, BRCMF_AWDL_ATTR_ROLE, e->status)) {
+ kfree_skb(skb);
+ return -EMSGSIZE;
+ }
+ cfg80211_vendor_event(skb, GFP_KERNEL);
+ return 0;
+ default:
+ return 0;
+ }
+
+ if (e->datalen < 2)
+ return 0;
+ switch (get_unaligned_be16(data)) {
+ case 1:
+ hdr = BRCMF_AWDL_RX_HDR_V1_LEN;
+ break;
+ case 2:
+ hdr = BRCMF_AWDL_RX_HDR_V2_LEN;
+ break;
+ default:
+ return 0;
+ }
+ if (e->datalen < hdr + sizeof(brcmf_awdl_af) ||
+ memcmp((u8 *)data + hdr, brcmf_awdl_af, sizeof(brcmf_awdl_af)))
+ return 0;
+
+ skb = brcmf_awdl_event_alloc(drvr, BRCMF_VNDR_EVTS_AWDL_FRAME,
+ e->datalen);
+ if (!skb)
+ return -ENOMEM;
+ if (nla_put(skb, BRCMF_AWDL_ATTR_ADDR, ETH_ALEN, e->addr) ||
+ nla_put(skb, BRCMF_AWDL_ATTR_FRAME, e->datalen - hdr,
+ (u8 *)data + hdr)) {
+ kfree_skb(skb);
+ return -EMSGSIZE;
+ }
+ cfg80211_vendor_event(skb, GFP_KERNEL);
+ return 0;
+}
+
+/*
+ * Registers the firmware events that only the AWDL interface reports, so the
+ * event mask includes them. The firmware runs AWDL with all of them enabled.
+ */
+void brcmf_awdl_register_events(struct brcmf_pub *drvr)
+{
+ static const enum brcmf_fweh_event_code events[] = {
+ BRCMF_E_AWDL_AW, BRCMF_E_AWDL_ROLE, BRCMF_E_AWDL_EVENT,
+ BRCMF_E_AWDL_STATS, BRCMF_E_SDB_STATE,
+ };
+ struct brcmf_if *pri = brcmf_get_ifp(drvr, 0);
+ int i;
+
+ if (!pri || !brcmf_feat_is_enabled(pri, BRCMF_FEAT_AWDL))
+ return;
+ for (i = 0; i < ARRAY_SIZE(events); i++)
+ brcmf_fweh_register(drvr, events[i], brcmf_awdl_fweh_event);
+}
+
static void brcmf_awdl_send_result(struct brcmf_pub *drvr, int event,
int status, struct net_device *ndev)
{
- struct brcmf_if *pri = brcmf_get_ifp(drvr, 0);
struct sk_buff *skb;
- skb = cfg80211_vendor_event_alloc(drvr->wiphy,
- pri && pri->vif ? &pri->vif->wdev :
- NULL,
- NLMSG_DEFAULT_SIZE, event,
- GFP_KERNEL);
+ skb = brcmf_awdl_event_alloc(drvr, event, NLMSG_DEFAULT_SIZE);
if (!skb)
return;
if (nla_put_s32(skb, BRCMF_AWDL_ATTR_STATUS, status) ||
@@ -732,6 +840,8 @@ const struct nla_policy brcmf_awdl_vndr_policy[BRCMF_AWDL_ATTR_MAX + 1] = {
[BRCMF_AWDL_ATTR_IFINDEX] = { .type = NLA_REJECT },
[BRCMF_AWDL_ATTR_PEER_IES] = { .type = NLA_BINARY,
.len = BRCMF_AWDL_PEER_IES_MAX },
+ [BRCMF_AWDL_ATTR_FRAME] = { .type = NLA_REJECT },
+ [BRCMF_AWDL_ATTR_ROLE] = { .type = NLA_REJECT },
};
/* The device of a vendor command and its parsed attributes */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
index 63030221dea..06c932a3c95 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/awdl.h
@@ -18,6 +18,7 @@ struct brcmf_pub;
struct brcmf_if;
struct net_device;
struct sk_buff;
+struct brcmf_event_msg;
enum brcmf_awdl_op {
BRCMF_AWDL_OP_NONE,
@@ -87,6 +88,9 @@ int brcmf_awdl_add(struct brcmf_pub *drvr, const u8 *mac,
struct net_device **ndev);
int brcmf_awdl_del(struct brcmf_pub *drvr);
void brcmf_awdl_suspend(struct brcmf_pub *drvr);
+void brcmf_awdl_register_events(struct brcmf_pub *drvr);
+int brcmf_awdl_fweh_event(struct brcmf_if *ifp,
+ const struct brcmf_event_msg *e, void *data);
bool brcmf_awdl_if_added(struct brcmf_pub *drvr, struct brcmf_if *ifp);
bool brcmf_awdl_if_removed(struct brcmf_pub *drvr, struct brcmf_if *ifp);
int brcmf_awdl_encap(struct brcmf_if *ifp, struct sk_buff *skb);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 41ea0481cf1..cad12496173 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -7045,6 +7045,7 @@ static s32 wl_init_priv(struct brcmf_cfg80211_info *cfg)
if (err)
return err;
brcmf_register_event_handlers(cfg);
+ brcmf_awdl_register_events(cfg->pub);
mutex_init(&cfg->usr_sync);
brcmf_init_escan(cfg);
brcmf_init_conf(cfg->conf);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
index 8c098029104..bd4bf205cc1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
@@ -324,11 +324,13 @@ static void brcmf_fweh_event_worker(struct work_struct *work)
ifp = drvr->iflist[0];
else
ifp = drvr->iflist[emsg.bsscfgidx];
- /* The AWDL interface has no vif, its events are not for
- * the cfg80211 handlers
+ /* The AWDL interface has no vif, its events go to user
+ * space and not to the cfg80211 handlers
*/
- if (ifp && ifp->awdl)
+ if (ifp && ifp->awdl) {
+ brcmf_awdl_fweh_event(ifp, &emsg, event->data);
goto event_free;
+ }
err = brcmf_fweh_call_event_handler(drvr, ifp, event->code,
&emsg, event->data);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
index e327dd58d29..f8dee73cab1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
@@ -94,7 +94,12 @@ struct brcmf_cfg80211_info;
BRCMF_ENUM_DEF(FIFO_CREDIT_MAP, 74) \
BRCMF_ENUM_DEF(ACTION_FRAME_RX, 75) \
BRCMF_ENUM_DEF(TDLS_PEER_EVENT, 92) \
+ BRCMF_ENUM_DEF(AWDL_AW, 96) \
+ BRCMF_ENUM_DEF(AWDL_ROLE, 97) \
+ BRCMF_ENUM_DEF(AWDL_EVENT, 98) \
BRCMF_ENUM_DEF(BCMC_CREDIT_SUPPORT, 127) \
+ BRCMF_ENUM_DEF(AWDL_STATS, 150) \
+ BRCMF_ENUM_DEF(SDB_STATE, 152) \
BRCMF_ABSTRACT_ENUM_DEF(EXT_AUTH_REQ, 0) \
BRCMF_ABSTRACT_ENUM_DEF(EXT_AUTH_FRAME_RX, 1) \
BRCMF_ABSTRACT_ENUM_DEF(MGMT_FRAME_TXSTATUS, 2) \
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
index 5e7de07c995..aae9cd75947 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.c
@@ -193,4 +193,12 @@ const struct nl80211_vendor_cmd_info brcmf_vendor_events[] = {
.vendor_id = BROADCOM_OUI,
.subcmd = BRCMF_VNDR_EVTS_AWDL_STOPPED
},
+ [BRCMF_VNDR_EVTS_AWDL_FRAME] = {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_EVTS_AWDL_FRAME
+ },
+ [BRCMF_VNDR_EVTS_AWDL_ROLE] = {
+ .vendor_id = BROADCOM_OUI,
+ .subcmd = BRCMF_VNDR_EVTS_AWDL_ROLE
+ },
};
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h
index ce7f7c46710..96009375eb7 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/vendor.h
@@ -54,6 +54,11 @@ enum brcmf_vndr_cmds {
* @BRCMF_VNDR_EVTS_AWDL_STOPPED: the AWDL interface was removed, after
* %BRCMF_VNDR_CMDS_AWDL_STOP or before system sleep, with
* %BRCMF_AWDL_ATTR_STATUS.
+ * @BRCMF_VNDR_EVTS_AWDL_FRAME: AWDL action frame received from
+ * %BRCMF_AWDL_ATTR_ADDR, the frame body from the category on in
+ * %BRCMF_AWDL_ATTR_FRAME.
+ * @BRCMF_VNDR_EVTS_AWDL_ROLE: the firmware changed its AWDL role to
+ * %BRCMF_AWDL_ATTR_ROLE.
* @BRCMF_VNDR_EVTS_LAST: number of vendor events.
*
* The value of an event is its index in the vendor events and its vendor
@@ -62,6 +67,8 @@ enum brcmf_vndr_cmds {
enum brcmf_vndr_evts {
BRCMF_VNDR_EVTS_AWDL_STARTED,
BRCMF_VNDR_EVTS_AWDL_STOPPED,
+ BRCMF_VNDR_EVTS_AWDL_FRAME,
+ BRCMF_VNDR_EVTS_AWDL_ROLE,
BRCMF_VNDR_EVTS_LAST
};
@@ -80,6 +87,9 @@ enum brcmf_vndr_evts {
* @BRCMF_AWDL_ATTR_IFINDEX: interface index of the AWDL interface (u32).
* @BRCMF_AWDL_ATTR_PEER_IES: HT and VHT capabilities elements of a peer,
* built from the AWDL TLVs it sends, as macOS passes them.
+ * @BRCMF_AWDL_ATTR_FRAME: received action frame body.
+ * @BRCMF_AWDL_ATTR_ROLE: AWDL role of the firmware (u32), 0 slave, 1 master
+ * not taking part in the election, 2 master.
* @BRCMF_AWDL_ATTR_MAX: highest attribute.
*/
enum brcmf_awdl_attrs {
@@ -94,6 +104,8 @@ enum brcmf_awdl_attrs {
BRCMF_AWDL_ATTR_STATUS,
BRCMF_AWDL_ATTR_IFINDEX,
BRCMF_AWDL_ATTR_PEER_IES,
+ BRCMF_AWDL_ATTR_FRAME,
+ BRCMF_AWDL_ATTR_ROLE,
__BRCMF_AWDL_ATTR_AFTER_LAST,
BRCMF_AWDL_ATTR_MAX = __BRCMF_AWDL_ATTR_AFTER_LAST - 1
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-04 15:16 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04 15:15 [PATCH 0/7] wifi: brcmfmac: AWDL for AirDrop on Apple T2 Macs Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 1/7] wifi: brcmfmac: match firmware capabilities as whole words Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 2/7] wifi: brcmfmac: move the interface_create definitions to fwil_types.h Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 3/7] wifi: brcmfmac: detect AWDL support of the firmware Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 4/7] wifi: brcmfmac: create and remove the AWDL interface Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 5/7] wifi: brcmfmac: add the AWDL data path Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 6/7] wifi: brcmfmac: add vendor commands for AWDL Andre Eikmeyer
2026-11-04 15:15 ` [PATCH 7/7] wifi: brcmfmac: pass AWDL frames and role changes to user space Andre Eikmeyer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®