From: Andreas Wendleder <gonsolo@gmail.com>
To: linux-wireless@vger.kernel.org
Cc: Johannes Berg <johannes@sipsolutions.net>,
b43-dev@lists.infradead.org, Michael Buesch <m@bues.ch>,
Alessio Ferri <alessio.ferri@mythread.it>,
kvalo@kernel.org, julian.calaby@gmail.com, jpeisach@ubuntu.com,
rosenp@gmail.com, linux-kernel@vger.kernel.org
Subject: [PATCH RFC wireless-next 2/4] wifi: b43: TX descriptor and TX status for the AC microcode
Date: Thu, 12 Nov 2026 18:56:38 +0100 [thread overview]
Message-ID: <20261112175640.40603-3-gonsolo@gmail.com> (raw)
In-Reply-To: <20261112175640.40603-1-gonsolo@gmail.com>
The AC microcode takes a 4 byte passthrough prefix and a 124 byte
descriptor with four 20 byte rate entries. Add the descriptor builder:
legacy CCK and OFDM rates, HT rates with the PLCP laid out as brcmsmac
does, VHT rate blocks (frame type 3, with the width and the subband of the
primary channel in the first two words and the VHT-SIG-A fields), and the
cache info that session frames carry (the microcode builds the A-MPDU
from them). The cache info must not carry a cipher: mac80211 has encrypted
the frame already.
The TX status comes as two 4 word records. The attempt count is the low
byte of word 2, not the sum of its bytes, and a record with more than one
MPDU carries a block ack bitmap for the consecutive cookies; the cookies
are advanced with the new b43_dma_cookie_advance().
Only microcode revision 832 has been tested.
Co-developed-by: Alessio Ferri <alessio.ferri@mythread.it>
Signed-off-by: Alessio Ferri <alessio.ferri@mythread.it>
Signed-off-by: Andreas Wendleder <gonsolo@gmail.com>
---
drivers/net/wireless/broadcom/b43/dma.c | 13 ++
drivers/net/wireless/broadcom/b43/dma.h | 1 +
drivers/net/wireless/broadcom/b43/main.c | 76 ++++++++
drivers/net/wireless/broadcom/b43/xmit.c | 214 +++++++++++++++++++++++
drivers/net/wireless/broadcom/b43/xmit.h | 11 ++
5 files changed, 315 insertions(+)
diff --git a/drivers/net/wireless/broadcom/b43/dma.c b/drivers/net/wireless/broadcom/b43/dma.c
index 5f968b0..404f75f 100644
--- a/drivers/net/wireless/broadcom/b43/dma.c
+++ b/drivers/net/wireless/broadcom/b43/dma.c
@@ -1444,6 +1444,19 @@ out:
return err;
}
+/* The cookie of the frame @n frames after @cookie on the same ring. */
+u16 b43_dma_cookie_advance(struct b43_wldev *dev, u16 cookie, unsigned int n)
+{
+ struct b43_dmaring *ring;
+ int slot;
+
+ ring = parse_cookie(dev, cookie, &slot);
+ if (!ring)
+ return cookie;
+ return generate_cookie(ring, (slot + n * TX_SLOTS_PER_FRAME) %
+ ring->nr_slots);
+}
+
void b43_dma_handle_txstatus(struct b43_wldev *dev,
const struct b43_txstatus *status)
{
diff --git a/drivers/net/wireless/broadcom/b43/dma.h b/drivers/net/wireless/broadcom/b43/dma.h
index 14de4cb..5c84b1f 100644
--- a/drivers/net/wireless/broadcom/b43/dma.h
+++ b/drivers/net/wireless/broadcom/b43/dma.h
@@ -298,6 +298,7 @@ void b43_dma_tx_resume(struct b43_wldev *dev);
int b43_dma_tx(struct b43_wldev *dev,
struct sk_buff *skb);
+u16 b43_dma_cookie_advance(struct b43_wldev *dev, u16 cookie, unsigned int n);
void b43_dma_handle_txstatus(struct b43_wldev *dev,
const struct b43_txstatus *status);
diff --git a/drivers/net/wireless/broadcom/b43/main.c b/drivers/net/wireless/broadcom/b43/main.c
index da7e420..5830acb 100644
--- a/drivers/net/wireless/broadcom/b43/main.c
+++ b/drivers/net/wireless/broadcom/b43/main.c
@@ -1414,6 +1414,74 @@ static void handle_irq_transmit_status(struct b43_wldev *dev)
break;
v1 = b43_read32(dev, B43_MMIO_XMITSTAT_1);
+ if (b43_ucode_is_ac(dev)) {
+ /* rev 40+ ucode: two 4-word records per frame */
+ u32 v2 = b43_read32(dev, B43_MMIO_XMITSTAT_0 + 8);
+ u32 v3 = b43_read32(dev, B43_MMIO_XMITSTAT_0 + 12);
+ unsigned int n;
+
+ /*
+ * second package: words 1 and 2 are the block ack
+ * bitmap
+ */
+ u32 w0 = b43_read32(dev, B43_MMIO_XMITSTAT_0);
+ u32 w1 = b43_read32(dev, B43_MMIO_XMITSTAT_1);
+ u32 w2 = b43_read32(dev, B43_MMIO_XMITSTAT_0 + 8);
+ u32 w3 = b43_read32(dev, B43_MMIO_XMITSTAT_0 + 12);
+ unsigned int mpdus = (v0 >> 8) & 0x7f, i;
+ u64 bitmap = ((u64)w2 << 32) | w1;
+
+ memset(&stat, 0, sizeof(stat));
+ stat.cookie = v0 >> 16;
+ stat.seq = v1 & 0xffff;
+ stat.phy_stat = (v1 >> 16) & 0xff;
+ stat.intermediate = !!(v0 & 0x0004);
+ stat.pm_indicated = !!(v0 & 0x0008);
+ stat.supp_reason = (v0 & 0x00f0) >> 4;
+ stat.acked = !!(v0 & 0x8000);
+ /*
+ * : the attempt count is the LOW BYTE of word 2 only.
+ * Bits 16-23 hold a constant 1 on every acknowledged
+ * frame (0 on suppressed ones), not a second attempt
+ * counter: summing it made every frame look like it
+ * needed a retry, so minstrel saw ~50 % failures at
+ * every rate and TX stayed at 1-6 Mbit/s (1.2 "retries"
+ * per frame regardless of rate or TX power).
+ */
+ n = v2 & 0xff;
+ stat.frame_count = min(n, 15u);
+ stat.fb_count = min((v2 >> 16) & 0xff, 15u);
+ b43dbg(dev->wl,
+ "AC txstatus %08x %08x %08x %08x / %08x %08x %08x %08x\n",
+ v0, v1, v2, v3, w0, w1, w2, w3);
+ if (mpdus > 1) {
+ /*
+ * A-MPDU: one status for @mpdus frames with
+ * consecutive cookies
+ */
+ u16 cookie = stat.cookie;
+
+ /*
+ * the attempt count covers the whole aggregate
+ */
+ stat.frame_count =
+ min(DIV_ROUND_UP(n, mpdus), 15u);
+ stat.fb_count =
+ min(DIV_ROUND_UP((v2 >> 16) & 0xff,
+ mpdus), 15u);
+ for (i = 0; i < mpdus; i++) {
+ stat.cookie = cookie;
+ stat.acked = !!(bitmap & (1ULL << i));
+ b43_handle_txstatus(dev, &stat);
+ cookie = b43_dma_cookie_advance(
+ dev, cookie, 1);
+ }
+ continue;
+ }
+ b43_handle_txstatus(dev, &stat);
+ continue;
+ }
+
stat.cookie = (v0 >> 16);
stat.seq = (v1 & 0x0000FFFF);
stat.phy_stat = ((v1 & 0x00FF0000) >> 16);
@@ -1444,6 +1512,14 @@ static void drain_txstatus_queue(struct b43_wldev *dev)
if (!(dummy & 0x00000001))
break;
dummy = b43_read32(dev, B43_MMIO_XMITSTAT_1);
+ if (b43_ucode_is_ac(dev)) {
+ int i;
+
+ b43_read32(dev, B43_MMIO_XMITSTAT_0 + 8);
+ b43_read32(dev, B43_MMIO_XMITSTAT_0 + 12);
+ for (i = 0; i < 4; i++)
+ b43_read32(dev, B43_MMIO_XMITSTAT_0 + 4 * i);
+ }
}
}
diff --git a/drivers/net/wireless/broadcom/b43/xmit.c b/drivers/net/wireless/broadcom/b43/xmit.c
index f0b0825..cf9e7a5 100644
--- a/drivers/net/wireless/broadcom/b43/xmit.c
+++ b/drivers/net/wireless/broadcom/b43/xmit.c
@@ -231,6 +231,216 @@ static u8 b43_calc_fallback_rate(u8 bitrate, int gmode)
return 0;
}
+/*
+ * One VHT rate block (frame type 3), as the stock driver lays it out and
+ * Alessio Ferri's driver builds it: the width in word 0 bits 15:14, the
+ * subband of the primary within that width in word 1, MCS and NSS - 1 in
+ * word 2, VHT-SIG-A1/A2 in the PLCP bytes (group ID 63, both reserved bits
+ * set), FBW 0x0300 and the rate in 500 kbit/s.
+ */
+static void b43_txhdr_ac_vht_block(u8 *ri, const struct ieee80211_tx_rate *t,
+ u16 chanspec)
+{
+ static const u8 rate_bw[] = {
+ RATE_INFO_BW_20, RATE_INFO_BW_40, RATE_INFO_BW_80,
+ };
+ u8 mcs = ieee80211_rate_get_vht_mcs(t);
+ u8 nss = ieee80211_rate_get_vht_nss(t);
+ unsigned int bw = 0;
+ struct rate_info rinfo = { .flags = RATE_INFO_FLAGS_VHT_MCS };
+ u16 sb = 0;
+ u32 a1, a2;
+
+ /* wider than 20 MHz only inside the 80 MHz channel */
+ if ((chanspec & 0x3800) == 0x2000) {
+ if (t->flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
+ bw = 2;
+ else if (t->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
+ bw = 1;
+ sb = ((chanspec >> 8) & 0x7) >> bw;
+ }
+ rinfo.mcs = mcs;
+ rinfo.nss = nss;
+ rinfo.bw = rate_bw[bw];
+
+ put_unaligned_le16(3 | 0x0004 |
+ ((nss > 1 ? 3 : 1) << 6) |
+ (bw << 14), ri + 0x00);
+ put_unaligned_le16(sb, ri + 0x02);
+ put_unaligned_le16((mcs & 0xf) | ((nss - 1) << 4), ri + 0x04);
+ a1 = bw | BIT(2) | (63 << 4) | ((nss - 1) << 10) | BIT(23);
+ a2 = (mcs << 4) | BIT(9);
+ ri[0x06] = a1;
+ ri[0x07] = a1 >> 8;
+ ri[0x08] = a1 >> 16;
+ ri[0x09] = a2;
+ ri[0x0a] = a2 >> 8;
+ ri[0x0b] = a2 >> 16;
+ put_unaligned_le16(0x0300, ri + 0x0c);
+ put_unaligned_le16(cfg80211_calculate_bitrate(&rinfo) / 5, ri + 0x0e);
+}
+
+static int b43_generate_txhdr_ac(struct b43_wldev *dev, u8 *h,
+ struct sk_buff *skb,
+ struct ieee80211_tx_info *info, u16 cookie)
+{
+ static const u8 cck[] = { B43_CCK_RATE_1MB, B43_CCK_RATE_2MB,
+ B43_CCK_RATE_5MB, B43_CCK_RATE_11MB };
+ static const u8 ofdm[] = { B43_OFDM_RATE_6MB, B43_OFDM_RATE_9MB,
+ B43_OFDM_RATE_12MB, B43_OFDM_RATE_18MB,
+ B43_OFDM_RATE_24MB, B43_OFDM_RATE_36MB,
+ B43_OFDM_RATE_48MB, B43_OFDM_RATE_54MB };
+ const struct ieee80211_hdr *wlhdr = (const void *)skb->data;
+ struct ieee80211_rate *txrate =
+ ieee80211_get_tx_rate(dev->wl->hw, info);
+ u8 rate = txrate ? txrate->hw_value : B43_CCK_RATE_1MB;
+ bool is_ofdm = b43_is_ofdm_rate(rate);
+ unsigned int len = skb->len + FCS_LEN;
+ u8 *ri = h + B43_TXH_AC_RATE(0);
+ u16 mac_lo = 0, phy0, idx = 0, chanspec, subband;
+ int ht_mcs = -1;
+ /* Only HT rates go out as session frames: a legacy probe frame with the
+ * aggregate header fails at every attempt.
+ */
+ bool vht = info->control.rates[0].flags & IEEE80211_TX_RC_VHT_MCS;
+ bool agg = (info->flags & IEEE80211_TX_CTL_AMPDU) &&
+ (info->control.rates[0].flags &
+ (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS));
+ const u8 *tbl = is_ofdm ? ofdm : cck;
+ unsigned int n = is_ofdm ? ARRAY_SIZE(ofdm) : ARRAY_SIZE(cck);
+
+ /* Hardware crypto needs the AC key fields; load with nohwcrypt=1. */
+ if (info->control.hw_key)
+ return -EOPNOTSUPP;
+
+ memset(h, 0, B43_TXH_AC_LEN);
+ h[0] = 0x02; /* passthrough prefix, as wl sends it */
+ h[2] = 0x02;
+ h += B43_TXH_AC_PREFIX;
+ ri = h + B43_TXH_AC_RATE(0);
+ while (idx < n && tbl[idx] != rate)
+ idx++;
+ if (idx == n)
+ idx = 0;
+
+ if (!is_multicast_ether_addr(wlhdr->addr1) &&
+ !(info->flags & IEEE80211_TX_CTL_NO_ACK))
+ mac_lo |= 0x0080; /* expect immediate ACK */
+ if (!(le16_to_cpu(wlhdr->seq_ctrl) & IEEE80211_SCTL_FRAG))
+ mac_lo |= 0x4000; /* start of MSDU */
+ if (ieee80211_is_beacon(wlhdr->frame_control))
+ mac_lo |= 0x0200; /* ignore PMQ */
+
+ chanspec = b43_ac_chanspec(dev, dev->phy.channel);
+ /* A 20 MHz frame inside an 80 MHz channel names its subband (word 1) */
+ subband = (chanspec & 0x3800) == 0x2000 ? (chanspec >> 8) & 0x7 : 0;
+
+ if (agg) {
+ /* MPDU of a block ack session, as the stock driver posts it
+ * (rxtx-1s-ht20-40-80.zip): MAC control 0x45c0, no fixed rate,
+ * the sequence number, and the cache info at +0x64: no cipher
+ * (we encrypt in software; the stock 50 04 makes the microcode
+ * encrypt again), MPDUs per aggregate at the primary and the
+ * fallback rate, duration 5414 us, window 63, max length 0x14.
+ * Without the cache info the AP deauthenticates us.
+ */
+ static const u8 cache[8] = { 0x00, 0x00, 0x04, 0x04,
+ 0x26, 0x15, 0x3f, 0x14 };
+
+ mac_lo = 0x45c0;
+ memcpy(h + 0x64, cache, sizeof(cache));
+ }
+ put_unaligned_le16(mac_lo, h + 0x02);
+ put_unaligned_le16(agg ? 0 : 0x0002, h + 0x04); /* fixed rate */
+ put_unaligned_le16(chanspec, h + 0x06);
+ h[0x08] = ieee80211_hdrlen(wlhdr->frame_control);
+ put_unaligned_le16(len, h + 0x0a);
+ put_unaligned_le16(cookie, h + 0x0c);
+ if (agg)
+ put_unaligned_le16(le16_to_cpu(wlhdr->seq_ctrl), h + 0x0e);
+
+ /* Rate entry 0: frame type, always-set bit 2, TX core 0 mask. */
+ phy0 = (is_ofdm ? 1 : 0) | 0x0004 | (1 << 6);
+ if (!is_ofdm && rate != B43_CCK_RATE_1MB &&
+ (info->control.rates[0].flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE))
+ phy0 |= 0x0010;
+ if (info->control.rates[0].flags & IEEE80211_TX_RC_MCS) {
+ ht_mcs = info->control.rates[0].idx;
+ } else if (vht && !agg) {
+ /*
+ * A VHT frame always goes out as an A-MPDU; outside a session
+ * send the HT20 rate of the same stream count instead.
+ */
+ const struct ieee80211_tx_rate *r = &info->control.rates[0];
+
+ ht_mcs = min_t(int, ieee80211_rate_get_vht_mcs(r), 7) +
+ 8 * (ieee80211_rate_get_vht_nss(r) - 1);
+ }
+
+ if (vht && agg) {
+ b43_txhdr_ac_vht_block(ri, &info->control.rates[0], chanspec);
+ } else if (ht_mcs >= 0 && ht_mcs <= 15) {
+ /*
+ * HT20: frame type 2, PLCP as brcmsmac lays it out, rate
+ * index = MCS
+ */
+ static const u8 ht20_500k[8] = { 13, 26, 39, 52,
+ 78, 104, 117, 130 };
+ u8 mcs = ht_mcs;
+ u8 *plcp = ri + 0x06;
+
+ phy0 = 2 | 0x0004 | ((mcs > 7 ? 3 : 1) << 6);
+ put_unaligned_le16(phy0, ri + 0x00);
+ put_unaligned_le16(subband, ri + 0x02);
+ put_unaligned_le16(mcs, ri + 0x04);
+ plcp[0] = mcs;
+ plcp[1] = len & 0xff;
+ plcp[2] = len >> 8;
+ /* smoothing, not sounding, reserved (brcmsmac) */
+ plcp[3] = 0x07;
+ plcp[4] = 0;
+ plcp[5] = 0;
+ put_unaligned_le16(ht20_500k[mcs & 7] * (mcs > 7 ? 2 : 1),
+ ri + 0x0e);
+ } else {
+ put_unaligned_le16(phy0, ri + 0x00);
+ put_unaligned_le16(subband, ri + 0x02);
+ put_unaligned_le16(idx, ri + 0x04);
+ b43_generate_plcp_hdr((struct b43_plcp_hdr4 *)(ri + 0x06),
+ len, rate);
+ put_unaligned_le16(rate, ri + 0x0e); /* 500 kbit/s units */
+ }
+ if (vht && agg && info->control.rates[1].idx >= 0 &&
+ (info->control.rates[1].flags & IEEE80211_TX_RC_VHT_MCS)) {
+ put_unaligned_le16(0, ri + 0x10);
+ ri += 0x14;
+ b43_txhdr_ac_vht_block(ri, &info->control.rates[1], chanspec);
+ } else if (agg && !vht && info->control.rates[1].idx >= 0 &&
+ info->control.rates[1].idx <= 15 &&
+ (info->control.rates[1].flags & IEEE80211_TX_RC_MCS)) {
+ /* Second rate block: the fallback rate of the session frame */
+ static const u8 ht20_500k[8] = { 13, 26, 39, 52,
+ 78, 104, 117, 130 };
+ u8 mcs = info->control.rates[1].idx;
+
+ put_unaligned_le16(0, ri + 0x10);
+ ri += 0x14;
+ put_unaligned_le16(2 | 0x0004 | ((mcs > 7 ? 3 : 1) << 6),
+ ri + 0x00);
+ put_unaligned_le16(subband, ri + 0x02);
+ put_unaligned_le16(mcs, ri + 0x04);
+ ri[0x06] = mcs;
+ ri[0x07] = len & 0xff;
+ ri[0x08] = len >> 8;
+ ri[0x09] = 0x07;
+ put_unaligned_le16(ht20_500k[mcs & 7] * (mcs > 7 ? 2 : 1),
+ ri + 0x0e);
+ }
+ put_unaligned_le16(0x0020, ri + 0x10); /* last rate entry */
+
+ return 0;
+}
+
/* Generate a TX data header. */
int b43_generate_txhdr(struct b43_wldev *dev,
u8 *_txhdr,
@@ -259,6 +469,10 @@ int b43_generate_txhdr(struct b43_wldev *dev,
struct ieee80211_rate *txrate;
struct ieee80211_tx_rate *rates;
+ if (b43_ucode_is_ac(dev))
+ return b43_generate_txhdr_ac(dev, _txhdr, skb_frag, info,
+ cookie);
+
memset(txhdr, 0, sizeof(*txhdr));
txrate = ieee80211_get_tx_rate(dev->wl->hw, info);
diff --git a/drivers/net/wireless/broadcom/b43/xmit.h b/drivers/net/wireless/broadcom/b43/xmit.h
index 6524a75..d403a28 100644
--- a/drivers/net/wireless/broadcom/b43/xmit.h
+++ b/drivers/net/wireless/broadcom/b43/xmit.h
@@ -188,9 +188,19 @@ struct b43_tx_legacy_rate_phy_ctl_entry {
#define B43_TXH_PHY1_MODUL_QAM256 0x2000 /* QAM256 */
+/*
+ * rev 40+ (AC) TX buffer: 4-byte TX offload passthrough prefix, then the
+ * 124-byte descriptor (packet info + 4 rate entries + cache).
+ */
+#define B43_TXH_AC_PREFIX 4
+#define B43_TXH_AC_LEN (B43_TXH_AC_PREFIX + 124)
+#define B43_TXH_AC_RATE(i) (0x14 + (i) * 0x14)
+
static inline
size_t b43_txhdr_size(struct b43_wldev *dev)
{
+ if (b43_ucode_is_ac(dev))
+ return B43_TXH_AC_LEN;
switch (dev->fw.hdr_format) {
case B43_FW_HDR_598:
return 112 + sizeof(struct b43_plcp_hdr6);
@@ -214,6 +224,7 @@ struct b43_txstatus {
u16 seq; /* Sequence number */
u8 phy_stat; /* PHY TX status */
u8 frame_count; /* Frame transmit count */
+ u8 fb_count; /* AC: attempts at the fallback rate */
u8 rts_count; /* RTS transmit count */
u8 supp_reason; /* Suppression reason */
/* flags */
next prev parent reply other threads:[~2026-11-12 17:56 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 17:56 [PATCH RFC wireless-next 0/4] wifi: b43: AC microcode TX and RX formats (series 3/4) Andreas Wendleder
2026-11-12 17:56 ` [PATCH RFC wireless-next 1/4] wifi: b43: 11ac chanspec for the AC microcode Andreas Wendleder
2026-11-12 17:56 ` Andreas Wendleder [this message]
2026-11-12 17:56 ` [PATCH RFC wireless-next 3/4] wifi: b43: RX header handling " Andreas Wendleder
2026-11-12 17:56 ` [PATCH RFC wireless-next 4/4] wifi: b43: A-MPDU " Andreas Wendleder
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=20261112175640.40603-3-gonsolo@gmail.com \
--to=gonsolo@gmail.com \
--cc=alessio.ferri@mythread.it \
--cc=b43-dev@lists.infradead.org \
--cc=johannes@sipsolutions.net \
--cc=jpeisach@ubuntu.com \
--cc=julian.calaby@gmail.com \
--cc=kvalo@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-wireless@vger.kernel.org \
--cc=m@bues.ch \
--cc=rosenp@gmail.com \
/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®