From: Andre Eikmeyer <dev@deq.rocks>
To: arend.vanspriel@broadcom.com
Cc: linux-wireless@vger.kernel.org, brcm80211@lists.linux.dev,
brcm80211-dev-list.pdl@broadcom.com,
linux-kernel@vger.kernel.org, Andre Eikmeyer <dev@deq.rocks>,
Yossef Osman <Yossef.Osman@proton.me>,
Aditya Garg <aditya.garg@linux.dev>
Subject: [PATCH 7/7] wifi: brcmfmac: pass AWDL frames and role changes to user space
Date: Wed, 4 Nov 2026 16:15:47 +0100 [thread overview]
Message-ID: <20261104151547.70333-8-dev@deq.rocks> (raw)
In-Reply-To: <20261104151547.70333-1-dev@deq.rocks>
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
prev parent reply other threads:[~2026-11-04 15:16 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Andre Eikmeyer [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261104151547.70333-8-dev@deq.rocks \
--to=dev@deq.rocks \
--cc=Yossef.Osman@proton.me \
--cc=aditya.garg@linux.dev \
--cc=arend.vanspriel@broadcom.com \
--cc=brcm80211-dev-list.pdl@broadcom.com \
--cc=brcm80211@lists.linux.dev \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-wireless@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®