fix: isolate the 0x8820 loader profile per USB device

Derive the FMAC-only profile from each device readback instead of a module-global latch. A regular D80 must still load its patches after another adapter reports product 0x8820. Reject a busy connect with EBUSY without resetting the state of an operation already in flight.

Extracted-function tests cover alternating devices, reprobe, cache setup, configuration errors and unchanged busy states. All three modules build against Linux 6.17 headers. Hardware retest of this revision and Bluetooth remain pending.
This commit is contained in:
Shen Mintao committed 2026-10-28 10:17:39 +08:00
1 parent 73e0a57128
commit 35d815c0ae
6 files changed
+159 -10

No files matched your search

+2 -4
View File
@@ -3207,13 +3207,11 @@ static int rwnx_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
(int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTING) { (int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_CONNECTING) {
AICWFDBG(LOGERROR, "%s driver is disconnecting or connecting ,return it \r\n", __func__); AICWFDBG(LOGERROR, "%s driver is disconnecting or connecting ,return it \r\n", __func__);
/* /*
* local fix: returning 0 here leaves cfg80211 waiting for a connect * Returning 0 here leaves cfg80211 waiting for a connect
* result that never comes, so every later connect fails with -EALREADY * result that never comes, so every later connect fails with -EALREADY
* (NetworkManager then loops asking for the password forever). * (NetworkManager then loops asking for the password forever).
* Report the failure instead so cfg80211 drops its pending request. * Reject the new request without changing an in-flight operation's state.
*/ */
rwnx_set_conn_state(rwnx_vif, &rwnx_vif->drv_conn_state,
(int)RWNX_DRV_STATUS_DISCONNECTED);
return -EBUSY; return -EBUSY;
} }
#endif #endif
@@ -18,7 +18,6 @@ void rwnx_release_firmware_common(u32** buffer);
extern int testmode; extern int testmode;
extern u8 chip_id; extern u8 chip_id;
extern u8 chip_mcu_id; extern u8 chip_mcu_id;
extern u8 aic8800d80_m80_variant;
typedef u32 (*array2_tbl_t)[2]; typedef u32 (*array2_tbl_t)[2];
@@ -248,6 +247,7 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){
} }
chip_id = (u8)(rd_mem_addr_cfm.memdata >> 16); chip_id = (u8)(rd_mem_addr_cfm.memdata >> 16);
product_id = rd_mem_addr_cfm.memdata & 0xFFFF; product_id = rd_mem_addr_cfm.memdata & 0xFFFF;
usb_dev->d80_m80_variant = product_id == 0x8820;
printk("chip_id=%x, chip_mcu_id = %d, product_id=0x%04x\n", printk("chip_id=%x, chip_mcu_id = %d, product_id=0x%04x\n",
chip_id, chip_mcu_id, product_id); chip_id, chip_mcu_id, product_id);
printk("AIC8800D80 legacy: using Radxa SDK V3 loader profile\n"); printk("AIC8800D80 legacy: using Radxa SDK V3 loader profile\n");
@@ -272,7 +272,7 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){
* #118: without this the firmware boots and answers configuration * #118: without this the firmware boots and answers configuration
* requests, but never confirms MM_START_REQ, so the MAC never starts. * requests, but never confirms MM_START_REQ, so the MAC never starts.
*/ */
if (product_id == 0x8820) { if (usb_dev->d80_m80_variant) {
ret = rwnx_send_dbg_mem_write_req(usb_dev, 0x40500010, 0x2); ret = rwnx_send_dbg_mem_write_req(usb_dev, 0x40500010, 0x2);
if (ret) { if (ret) {
printk("0x8820: 40500010 write fail: %d\n", ret); printk("0x8820: 40500010 write fail: %d\n", ret);
@@ -283,7 +283,6 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){
printk("0x8820: 40506024 mask write fail: %d\n", ret); printk("0x8820: 40506024 mask write fail: %d\n", ret);
return ret; return ret;
} }
aic8800d80_m80_variant = 1;
printk("AIC8800D80 0x8820: applied vendor syscfg writes, patch layer will be skipped\n"); printk("AIC8800D80 0x8820: applied vendor syscfg writes, patch layer will be skipped\n");
} }
#if 1 #if 1
@@ -389,7 +388,7 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev)
if(testmode == FW_NORMAL_MODE){ if(testmode == FW_NORMAL_MODE){
if (chip_id != CHIP_REV_U01){ if (chip_id != CHIP_REV_U01){
if (!aic8800d80_m80_variant) { if (!usb_dev->d80_m80_variant) {
if(rwnx_plat_bin_fw_upload_android(usb_dev, patch_info.addr_adid, FW_ADID_BASE_NAME_8800D80_U02)) { if(rwnx_plat_bin_fw_upload_android(usb_dev, patch_info.addr_adid, FW_ADID_BASE_NAME_8800D80_U02)) {
return -1; return -1;
} }
@@ -422,7 +421,7 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev)
if (rwnx_send_dbg_mem_write_req(usb_dev, 0x40500048, 0x1e0000)) if (rwnx_send_dbg_mem_write_req(usb_dev, 0x40500048, 0x1e0000))
return -1; return -1;
#endif #endif
if (!aic8800d80_m80_variant && aicwf_patch_config_8800d80(usb_dev)) { if (!usb_dev->d80_m80_variant && aicwf_patch_config_8800d80(usb_dev)) {
return -1; return -1;
} }
if (rwnx_send_dbg_start_app_req(usb_dev, RAM_FMAC_FW_ADDR_8800D80_U02, HOST_START_APP_AUTO)) { if (rwnx_send_dbg_start_app_req(usb_dev, RAM_FMAC_FW_ADDR_8800D80_U02, HOST_START_APP_AUTO)) {
-1
View File
@@ -39,7 +39,6 @@ extern uint32_t gpio_dft_lvl;//0:defalut pull down, 1:default pull up
u8 chip_id = 0; u8 chip_id = 0;
u8 chip_sub_id = 0; u8 chip_sub_id = 0;
u8 chip_mcu_id = 0; u8 chip_mcu_id = 0;
u8 aic8800d80_m80_variant = 0; /* product_id == 0x8820 (upstream issue #79) */
int fw_loaded = 0; int fw_loaded = 0;
void aicwf_usb_tx_flowctrl(struct aic_usb_dev *usb_dev, bool state) void aicwf_usb_tx_flowctrl(struct aic_usb_dev *usb_dev, bool state)
+1
View File
@@ -223,6 +223,7 @@ struct aic_usb_dev {
#endif #endif
u16 chipid; u16 chipid;
bool d80_m80_variant; /* D80 product 0x8820: FMAC-only loader sequence */
bool tbusy; bool tbusy;
bool app_cmp; bool app_cmp;
u32 fw_version_uint; u32 fw_version_uint;
+16
View File
@@ -0,0 +1,16 @@
# Product 0x8820 loader checks
Run `python3 tests/issue119-8820/test_loader.py` with a C compiler available.
The test extracts the actual system configuration, normal-mode upload branch,
and busy-connect guard and runs them against mocked USB requests. It checks
that alternating/reprobing M80 and regular D80 devices preserves each loader
sequence, retains the MCU1 cache fix, propagates configuration errors, and
rejects a new connect without overwriting an operation already in progress.
These checks do not establish hardware or Bluetooth compatibility. Issue #118
reports working Wi-Fi on one UGREEN `a69c:5723`, chip revision 7, MCU1 unit
with product `0x8820` and the branch's 327,037-byte V3 FMAC. Bluetooth pairing
and audio remain unverified. Product `0x8820` is not a rule to skip patches
on every MCU1 device, and the report does not establish that every device in
issue #79 works with this firmware.
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""Exercise extracted loader paths with mocked USB requests, without hardware."""
from pathlib import Path
import os
import subprocess
import tempfile
ROOT = Path(__file__).resolve().parents[2]
def block(source, signature):
start = source.index(signature)
brace = source.index("{", start)
end, depth = brace + 1, 1
while depth:
depth += (source[end] == "{") - (source[end] == "}")
end += 1
return source[start:end]
loader = (ROOT / "drivers/aic8800/aic_load_fw/aic_compat_8800d80.c").read_text()
header = (ROOT / "drivers/aic8800/aic_load_fw/aic_compat_8800d80.h").read_text()
header = "\n".join(line for line in header.splitlines() if not line.startswith("#include"))
config = block(loader, "int system_config_8800d80(")
normal = block(loader, "if(testmode == FW_NORMAL_MODE)")
connect = (ROOT / "drivers/aic8800/aic8800_fdrv/rwnx_main.c").read_text()
guard = block(connect[connect.index("static int rwnx_cfg80211_connect("):],
"if((int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING")
prelude = r'''
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
typedef uint32_t u32;
typedef uint8_t u8;
#define printk(...) ((void)0)
#define AICWFDBG(...) ((void)0)
#define CHIP_REV_U01 1
#define CHIP_REV_U02 3
#define CHIP_REV_U03 7
#define FW_NORMAL_MODE 0
#define HOST_START_APP_AUTO 0
struct aic_usb_dev {
bool d80_m80_variant;
u32 id, cache;
int syscfg_writes, syscfg_masks, patches, fmacs, starts;
};
struct dbg_mem_read_cfm { u32 memdata; };
struct aicbt_patch_info_t { u32 addr_adid, addr_patch; };
static u8 chip_id, chip_mcu_id;
static int testmode = FW_NORMAL_MODE, fail_read, fail_write;
static u32 syscfg_tbl_8800d80[0][2], syscfg_tbl_masked_8800d80[0][3];
static int rwnx_send_dbg_mem_read_req(struct aic_usb_dev *dev, u32 addr,
struct dbg_mem_read_cfm *cfm) {
if (fail_read) return -EIO;
cfm->memdata = addr == 0x40500000 ? dev->id : dev->cache; return 0;
}
static int rwnx_send_dbg_mem_write_req(struct aic_usb_dev *dev, u32 addr, u32 val) {
if (fail_write) return -EIO;
if (addr == 0x40100020) dev->cache = val;
if (addr == 0x40500010) { assert(val == 2); dev->syscfg_writes++; }
return 0;
}
static int rwnx_send_dbg_mem_mask_write_req(struct aic_usb_dev *dev, u32 addr, u32 mask, u32 val) {
assert(addr == 0x40506024 && mask == 0xff && val == 0xdf);
dev->syscfg_masks++; return 0;
}
static int rwnx_plat_bin_fw_upload_android(struct aic_usb_dev *dev, u32 addr, const char *name) {
if (name[0] == 'f' && name[1] == 'm') {
assert(addr == 0x120000); dev->fmacs++;
} else { dev->patches++; }
return 0;
}
static int aicbt_ext_patch_data_load(struct aic_usb_dev *dev, struct aicbt_patch_info_t *info, const char *name) {
dev->patches++; return 0;
}
static int aicbt_patch_table_load(struct aic_usb_dev *dev, void *head) {
dev->patches++; return 0;
}
static int rwnx_send_dbg_start_app_req(struct aic_usb_dev *dev, u32 addr, int mode) {
assert(addr == 0x120000); dev->starts++; return 0;
}
'''
normal_wrapper = r'''
int aicwf_patch_config_8800d80(struct aic_usb_dev *dev) { dev->patches++; return 0; }
static int load_normal(struct aic_usb_dev *usb_dev) {
struct aicbt_patch_info_t patch_info = {0};
void *head = 0;
'''
busy_wrapper = r'''
return 0;
}
enum { RWNX_DRV_STATUS_DISCONNECTED, RWNX_DRV_STATUS_CONNECTING,
RWNX_DRV_STATUS_DISCONNECTING };
struct vif { int drv_conn_state; };
#define atomic_read(p) (*(p))
static void rwnx_set_conn_state(struct vif *vif, int *state, int val) { *state = val; }
static int busy(struct vif *rwnx_vif) {
'''
checks = r'''
return 0;
}
int main(void) {
struct aic_usb_dev m80 = {.id = 0x00078820}, d80 = {.id = 0x00078800};
/* Configuring a second adapter must not change the first's load sequence. */
assert(system_config_8800d80(&m80) == 0);
assert(system_config_8800d80(&d80) == 0);
assert(load_normal(&m80) == 0 && m80.patches == 0 && m80.fmacs == 1 && m80.starts == 1);
assert(load_normal(&d80) == 0 && d80.patches == 5 && d80.fmacs == 1 && d80.starts == 1);
assert(m80.syscfg_writes == 1 && m80.syscfg_masks == 1 && (m80.cache & 1));
assert(d80.syscfg_writes == 0 && d80.syscfg_masks == 0 && (d80.cache & 1));
/* Reusing a zeroed/reprobed device, and a fresh device after an M80. */
m80.id = 0x00078800; m80.patches = m80.fmacs = m80.starts = 0;
assert(system_config_8800d80(&m80) == 0);
assert(load_normal(&m80) == 0 && m80.patches == 5);
d80.id = 0x00078820; d80.patches = d80.fmacs = d80.starts = 0;
assert(system_config_8800d80(&d80) == 0);
assert(load_normal(&d80) == 0 && d80.patches == 0 && d80.fmacs == 1);
fail_read = 1; assert(system_config_8800d80(&d80) == -EIO);
fail_read = 0; fail_write = 1; assert(system_config_8800d80(&d80) == -EIO);
for (int state = RWNX_DRV_STATUS_CONNECTING; state <= RWNX_DRV_STATUS_DISCONNECTING; state++) {
struct vif vif = {.drv_conn_state = state};
assert(busy(&vif) == -EBUSY && vif.drv_conn_state == state);
}
return 0;
}
'''
with tempfile.TemporaryDirectory(prefix="aic-8820-test-") as temp:
c_file, binary = Path(temp) / "loader.c", Path(temp) / "loader"
c_file.write_text(prelude + header + config + normal_wrapper + normal +
busy_wrapper + guard + checks)
subprocess.run([os.environ.get("CC", "cc"), "-std=gnu11", "-o", str(binary),
str(c_file)], check=True)
subprocess.run([str(binary)], check=True)
print("PASS: per-device/reprobe loader selection, MCU1 cache fix, write errors, busy connect state")