From 73e0a571288ecd9b7cafcf60d186103a913d20d2 Mon Sep 17 00:00:00 2001 From: kaijia323 Date: Wed, 28 Oct 2026 01:14:37 +0800 Subject: [PATCH 1/3] fix(legacy-mcu1): support AIC8800D80 product 0x8820 and unstick cfg80211 connect Two independent fixes found on a UGREEN a69c:5723 adapter (chip_id=7, chip_mcu_id=1, product_id=0x8820, issue #118): 1) The firmware boots and answers every configuration request, but never confirms MM_START_REQ, so the MAC never starts and wlan0 stays unusable (the symptom reported in #79). The Windows loader (aicloadfw.sys) handles product 0x8820 differently: it writes 0x40500010 = 0x2, masked-writes 0x40506024 (mask 0xff) = 0xdf, and uploads only the FMAC firmware without the adid/patch/patch-table/patch-config layer. Mirror that behaviour, gated on product_id == 0x8820 so that other D80 revisions (for example the a69c:572f from #58) keep uploading the patch layer and stay unaffected. The existing MCU1 cache fix (0x40100020 |= 0x1) is kept for both. 2) rwnx_cfg80211_connect() returned 0 when the state machine was already CONNECTING/DISCONNECTING, without ever reporting a result. cfg80211 then keeps wdev->conn allocated and rejects every later NL80211_CMD_CONNECT with -EALREADY, so the driver's connect op is never called again and NetworkManager loops on the password prompt ("associating -> disconnected" every 5 s) with no driver activity at all. Report the failure instead: reset the state and return -EBUSY. Tested on Ubuntu 26.04 / 7.0.0-14-generic: 5 GHz association, DHCP, IPv6 SLAAC and internet traffic all work, and it survives reboot and USB re-plug. Full logs and analysis: #118 --- drivers/aic8800/aic8800_fdrv/rwnx_main.c | 10 +++++- .../aic8800/aic_load_fw/aic_compat_8800d80.c | 33 +++++++++++++++++-- drivers/aic8800/aic_load_fw/aicwf_usb.c | 1 + 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/drivers/aic8800/aic8800_fdrv/rwnx_main.c b/drivers/aic8800/aic8800_fdrv/rwnx_main.c index 4421b9e..1548fe4 100644 --- a/drivers/aic8800/aic8800_fdrv/rwnx_main.c +++ b/drivers/aic8800/aic8800_fdrv/rwnx_main.c @@ -3206,7 +3206,15 @@ static int rwnx_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, if((int)atomic_read(&rwnx_vif->drv_conn_state) == (int)RWNX_DRV_STATUS_DISCONNECTING|| (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__); - return 0; + /* + * local fix: returning 0 here leaves cfg80211 waiting for a connect + * result that never comes, so every later connect fails with -EALREADY + * (NetworkManager then loops asking for the password forever). + * Report the failure instead so cfg80211 drops its pending request. + */ + rwnx_set_conn_state(rwnx_vif, &rwnx_vif->drv_conn_state, + (int)RWNX_DRV_STATUS_DISCONNECTED); + return -EBUSY; } #endif diff --git a/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c b/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c index f80596c..fb94f59 100644 --- a/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c +++ b/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c @@ -18,6 +18,7 @@ void rwnx_release_firmware_common(u32** buffer); extern int testmode; extern u8 chip_id; extern u8 chip_mcu_id; +extern u8 aic8800d80_m80_variant; typedef u32 (*array2_tbl_t)[2]; @@ -233,6 +234,7 @@ int rwnx_plat_userconfig_load_8800d80(struct aic_usb_dev *usb_dev){ int system_config_8800d80(struct aic_usb_dev *usb_dev){ int syscfg_num; int ret, cnt; + u32 product_id; const u32 mem_addr = 0x40500000; const u32 cache_mem_addr = 0x40100020; struct dbg_mem_read_cfm rd_mem_addr_cfm; @@ -245,7 +247,9 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){ chip_mcu_id = 1; } chip_id = (u8)(rd_mem_addr_cfm.memdata >> 16); - printk("chip_id=%x, chip_mcu_id = %d\n", chip_id, chip_mcu_id); + product_id = rd_mem_addr_cfm.memdata & 0xFFFF; + printk("chip_id=%x, chip_mcu_id = %d, product_id=0x%04x\n", + chip_id, chip_mcu_id, product_id); printk("AIC8800D80 legacy: using Radxa SDK V3 loader profile\n"); if (chip_mcu_id == 1) { ret = rwnx_send_dbg_mem_read_req(usb_dev, cache_mem_addr, &rd_mem_addr_cfm); @@ -261,6 +265,27 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){ } printk("AIC8800D80 MCU1: enabled Bluetooth cache fix\n"); } + /* + * product 0x8820: the Windows loader (aicloadfw.sys) performs two extra + * system-configuration writes and uploads only the FMAC firmware, without + * the adid/patch/patch-table/patch-config layer. Upstream issue #79 and + * #118: without this the firmware boots and answers configuration + * requests, but never confirms MM_START_REQ, so the MAC never starts. + */ + if (product_id == 0x8820) { + ret = rwnx_send_dbg_mem_write_req(usb_dev, 0x40500010, 0x2); + if (ret) { + printk("0x8820: 40500010 write fail: %d\n", ret); + return ret; + } + ret = rwnx_send_dbg_mem_mask_write_req(usb_dev, 0x40506024, 0xff, 0xdf); + if (ret) { + printk("0x8820: 40506024 mask write fail: %d\n", ret); + return ret; + } + aic8800d80_m80_variant = 1; + printk("AIC8800D80 0x8820: applied vendor syscfg writes, patch layer will be skipped\n"); + } #if 1 syscfg_num = sizeof(syscfg_tbl_8800d80) / sizeof(u32) / 2; for (cnt = 0; cnt < syscfg_num; cnt++) { @@ -364,6 +389,7 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev) if(testmode == FW_NORMAL_MODE){ if (chip_id != CHIP_REV_U01){ + if (!aic8800d80_m80_variant) { if(rwnx_plat_bin_fw_upload_android(usb_dev, patch_info.addr_adid, FW_ADID_BASE_NAME_8800D80_U02)) { return -1; } @@ -378,6 +404,9 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev) if (aicbt_patch_table_load(usb_dev, head)) { return -1; } + } else { + printk("AIC8800D80 0x8820: skipping adid/patch/patch-table upload (vendor loader behaviour)\n"); + } if (IS_CHIP_ID_H()){ if(rwnx_plat_bin_fw_upload_android(usb_dev, RAM_FMAC_FW_ADDR_8800D80_U02, FW_BASE_NAME_8800D80_H_U02)) @@ -393,7 +422,7 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev) if (rwnx_send_dbg_mem_write_req(usb_dev, 0x40500048, 0x1e0000)) return -1; #endif - if (aicwf_patch_config_8800d80(usb_dev)) { + if (!aic8800d80_m80_variant && aicwf_patch_config_8800d80(usb_dev)) { return -1; } if (rwnx_send_dbg_start_app_req(usb_dev, RAM_FMAC_FW_ADDR_8800D80_U02, HOST_START_APP_AUTO)) { diff --git a/drivers/aic8800/aic_load_fw/aicwf_usb.c b/drivers/aic8800/aic_load_fw/aicwf_usb.c index 3f550f6..6655261 100644 --- a/drivers/aic8800/aic_load_fw/aicwf_usb.c +++ b/drivers/aic8800/aic_load_fw/aicwf_usb.c @@ -39,6 +39,7 @@ extern uint32_t gpio_dft_lvl;//0:defalut pull down, 1:default pull up u8 chip_id = 0; u8 chip_sub_id = 0; u8 chip_mcu_id = 0; +u8 aic8800d80_m80_variant = 0; /* product_id == 0x8820 (upstream issue #79) */ int fw_loaded = 0; void aicwf_usb_tx_flowctrl(struct aic_usb_dev *usb_dev, bool state) From 35d815c0ae95bb9135b82250ce0d21d948b5aa54 Mon Sep 17 00:00:00 2001 From: Shen Mintao Date: Wed, 28 Oct 2026 10:17:39 +0800 Subject: [PATCH 2/3] 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. --- drivers/aic8800/aic8800_fdrv/rwnx_main.c | 6 +- .../aic8800/aic_load_fw/aic_compat_8800d80.c | 9 +- drivers/aic8800/aic_load_fw/aicwf_usb.c | 1 - drivers/aic8800/aic_load_fw/aicwf_usb.h | 1 + tests/issue119-8820/README.md | 16 +++ tests/issue119-8820/test_loader.py | 136 ++++++++++++++++++ 6 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 tests/issue119-8820/README.md create mode 100644 tests/issue119-8820/test_loader.py diff --git a/drivers/aic8800/aic8800_fdrv/rwnx_main.c b/drivers/aic8800/aic8800_fdrv/rwnx_main.c index 1548fe4..8b7a984 100644 --- a/drivers/aic8800/aic8800_fdrv/rwnx_main.c +++ b/drivers/aic8800/aic8800_fdrv/rwnx_main.c @@ -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) { 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 * (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; } #endif diff --git a/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c b/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c index fb94f59..e080032 100644 --- a/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c +++ b/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c @@ -18,7 +18,6 @@ void rwnx_release_firmware_common(u32** buffer); extern int testmode; extern u8 chip_id; extern u8 chip_mcu_id; -extern u8 aic8800d80_m80_variant; 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); 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", chip_id, chip_mcu_id, product_id); 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 * 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); if (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); return ret; } - aic8800d80_m80_variant = 1; printk("AIC8800D80 0x8820: applied vendor syscfg writes, patch layer will be skipped\n"); } #if 1 @@ -389,7 +388,7 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev) if(testmode == FW_NORMAL_MODE){ 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)) { 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)) return -1; #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; } if (rwnx_send_dbg_start_app_req(usb_dev, RAM_FMAC_FW_ADDR_8800D80_U02, HOST_START_APP_AUTO)) { diff --git a/drivers/aic8800/aic_load_fw/aicwf_usb.c b/drivers/aic8800/aic_load_fw/aicwf_usb.c index 6655261..3f550f6 100644 --- a/drivers/aic8800/aic_load_fw/aicwf_usb.c +++ b/drivers/aic8800/aic_load_fw/aicwf_usb.c @@ -39,7 +39,6 @@ extern uint32_t gpio_dft_lvl;//0:defalut pull down, 1:default pull up u8 chip_id = 0; u8 chip_sub_id = 0; u8 chip_mcu_id = 0; -u8 aic8800d80_m80_variant = 0; /* product_id == 0x8820 (upstream issue #79) */ int fw_loaded = 0; void aicwf_usb_tx_flowctrl(struct aic_usb_dev *usb_dev, bool state) diff --git a/drivers/aic8800/aic_load_fw/aicwf_usb.h b/drivers/aic8800/aic_load_fw/aicwf_usb.h index 736fdcf..c29813d 100644 --- a/drivers/aic8800/aic_load_fw/aicwf_usb.h +++ b/drivers/aic8800/aic_load_fw/aicwf_usb.h @@ -223,6 +223,7 @@ struct aic_usb_dev { #endif u16 chipid; + bool d80_m80_variant; /* D80 product 0x8820: FMAC-only loader sequence */ bool tbusy; bool app_cmp; u32 fw_version_uint; diff --git a/tests/issue119-8820/README.md b/tests/issue119-8820/README.md new file mode 100644 index 0000000..0195cb6 --- /dev/null +++ b/tests/issue119-8820/README.md @@ -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. + diff --git a/tests/issue119-8820/test_loader.py b/tests/issue119-8820/test_loader.py new file mode 100644 index 0000000..bd3ba90 --- /dev/null +++ b/tests/issue119-8820/test_loader.py @@ -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 +#include +#include +#include +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") + From c79f1b9fecb9ffa9b606818e80f933b2e3d06580 Mon Sep 17 00:00:00 2001 From: Shen Mintao Date: Wed, 28 Oct 2026 20:51:06 +0800 Subject: [PATCH 3/3] fix(legacy-mcu1): preserve Bluetooth patches for product 0x8820 Keep the product-specific syscfg writes and restore all patch uploads, including patch configuration. Derive the syscfg condition from the current product readback without retaining a loader flag. Update regression checks for full patch uploads on both products and syscfg write failures, and document the reported hardware results and remaining validation. --- drivers/aic8800/aic8800_fdrv/rwnx_main.c | 5 ++-- .../aic8800/aic_load_fw/aic_compat_8800d80.c | 20 +++++-------- drivers/aic8800/aic_load_fw/aicwf_usb.h | 1 - tests/issue119-8820/README.md | 30 +++++++++++++------ tests/issue119-8820/test_loader.py | 25 ++++++++++------ 5 files changed, 46 insertions(+), 35 deletions(-) diff --git a/drivers/aic8800/aic8800_fdrv/rwnx_main.c b/drivers/aic8800/aic8800_fdrv/rwnx_main.c index 8b7a984..d604a3f 100644 --- a/drivers/aic8800/aic8800_fdrv/rwnx_main.c +++ b/drivers/aic8800/aic8800_fdrv/rwnx_main.c @@ -3207,9 +3207,8 @@ 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) { AICWFDBG(LOGERROR, "%s driver is disconnecting or connecting ,return it \r\n", __func__); /* - * Returning 0 here leaves cfg80211 waiting for a connect - * result that never comes, so every later connect fails with -EALREADY - * (NetworkManager then loops asking for the password forever). + * Returning 0 would accept this request without submitting it to + * the firmware or providing its own connect result. * Reject the new request without changing an in-flight operation's state. */ return -EBUSY; diff --git a/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c b/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c index e080032..96757e3 100644 --- a/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c +++ b/drivers/aic8800/aic_load_fw/aic_compat_8800d80.c @@ -247,7 +247,6 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){ } chip_id = (u8)(rd_mem_addr_cfm.memdata >> 16); 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", chip_id, chip_mcu_id, product_id); printk("AIC8800D80 legacy: using Radxa SDK V3 loader profile\n"); @@ -266,13 +265,12 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){ printk("AIC8800D80 MCU1: enabled Bluetooth cache fix\n"); } /* - * product 0x8820: the Windows loader (aicloadfw.sys) performs two extra - * system-configuration writes and uploads only the FMAC firmware, without - * the adid/patch/patch-table/patch-config layer. Upstream issue #79 and - * #118: without this the firmware boots and answers configuration - * requests, but never confirms MM_START_REQ, so the MAC never starts. + * Product 0x8820 needs these extra system-configuration writes for + * MM_START to complete (#118/#119). Keep the normal patch uploads: + * skipping them causes Bluetooth HCI Reset to time out on the + * reported MCU1 unit. */ - if (usb_dev->d80_m80_variant) { + if (product_id == 0x8820) { ret = rwnx_send_dbg_mem_write_req(usb_dev, 0x40500010, 0x2); if (ret) { printk("0x8820: 40500010 write fail: %d\n", ret); @@ -283,7 +281,7 @@ int system_config_8800d80(struct aic_usb_dev *usb_dev){ printk("0x8820: 40506024 mask write fail: %d\n", ret); return ret; } - printk("AIC8800D80 0x8820: applied vendor syscfg writes, patch layer will be skipped\n"); + printk("AIC8800D80 0x8820: applied vendor syscfg writes\n"); } #if 1 syscfg_num = sizeof(syscfg_tbl_8800d80) / sizeof(u32) / 2; @@ -388,7 +386,6 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev) if(testmode == FW_NORMAL_MODE){ if (chip_id != CHIP_REV_U01){ - 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)) { return -1; } @@ -403,9 +400,6 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev) if (aicbt_patch_table_load(usb_dev, head)) { return -1; } - } else { - printk("AIC8800D80 0x8820: skipping adid/patch/patch-table upload (vendor loader behaviour)\n"); - } if (IS_CHIP_ID_H()){ if(rwnx_plat_bin_fw_upload_android(usb_dev, RAM_FMAC_FW_ADDR_8800D80_U02, FW_BASE_NAME_8800D80_H_U02)) @@ -421,7 +415,7 @@ int aicfw_download_fw_8800d80(struct aic_usb_dev *usb_dev) if (rwnx_send_dbg_mem_write_req(usb_dev, 0x40500048, 0x1e0000)) return -1; #endif - if (!usb_dev->d80_m80_variant && aicwf_patch_config_8800d80(usb_dev)) { + if (aicwf_patch_config_8800d80(usb_dev)) { return -1; } if (rwnx_send_dbg_start_app_req(usb_dev, RAM_FMAC_FW_ADDR_8800D80_U02, HOST_START_APP_AUTO)) { diff --git a/drivers/aic8800/aic_load_fw/aicwf_usb.h b/drivers/aic8800/aic_load_fw/aicwf_usb.h index c29813d..736fdcf 100644 --- a/drivers/aic8800/aic_load_fw/aicwf_usb.h +++ b/drivers/aic8800/aic_load_fw/aicwf_usb.h @@ -223,7 +223,6 @@ struct aic_usb_dev { #endif u16 chipid; - bool d80_m80_variant; /* D80 product 0x8820: FMAC-only loader sequence */ bool tbusy; bool app_cmp; u32 fw_version_uint; diff --git a/tests/issue119-8820/README.md b/tests/issue119-8820/README.md index 0195cb6..1945e6a 100644 --- a/tests/issue119-8820/README.md +++ b/tests/issue119-8820/README.md @@ -3,14 +3,26 @@ 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. +that both product `0x8820` and regular D80 devices upload the complete +adid/patch/extended-patch/patch-table/patch-config layer before starting FMAC. +Alternating and reprobed devices receive the extra syscfg writes only when +their current product readback is `0x8820`. The checks also retain the MCU1 +cache fix, propagate read/cache/syscfg write errors, and reject 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. +The [hardware follow-up in PR #119](https://github.com/shenmintao/aic8800d80/pull/119#issuecomment-6468608942) +corrects the initial FMAC-only hypothesis from #118. On one UGREEN +`a69c:5723`, chip revision 7, MCU1, product `0x8820` unit running Ubuntu 26.04 +and kernel `7.0.0-14-generic`, the two syscfg writes alone resolve the +`MM_START` timeout. Skipping the patch layer causes Bluetooth HCI Reset +(`0x0c03`) to time out; restoring all patch uploads, including patch config, +preserves Wi-Fi and restores controller initialization and LE discovery. +The firmware remains the branch's matched V3 set, including its 327,037-byte +FMAC. + +The contributor tested the restored-patch variant, rather than this exact +follow-up commit. The cold-start sequence was replayed through USB +re-enumeration; physical unplug/replug, reboot of that variant, Bluetooth +pairing/audio, and other units remain unverified. These mocked checks do not +establish hardware compatibility or support for every device in issue #79. diff --git a/tests/issue119-8820/test_loader.py b/tests/issue119-8820/test_loader.py index bd3ba90..e6112ee 100644 --- a/tests/issue119-8820/test_loader.py +++ b/tests/issue119-8820/test_loader.py @@ -41,14 +41,14 @@ typedef uint8_t u8; #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 int testmode = FW_NORMAL_MODE, fail_read, fail_write, fail_mask; +static u32 fail_write_addr; 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) { @@ -56,13 +56,14 @@ static int rwnx_send_dbg_mem_read_req(struct aic_usb_dev *dev, u32 addr, 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 (fail_write && (!fail_write_addr || addr == fail_write_addr)) 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); + if (fail_mask) return -EIO; dev->syscfg_masks++; return 0; } static int rwnx_plat_bin_fw_upload_android(struct aic_usb_dev *dev, u32 addr, const char *name) { @@ -102,22 +103,28 @@ 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. */ + /* Both products need the full patch layer; only 0x8820 gets extra syscfg. */ 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(&m80) == 0 && m80.patches == 5 && 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. */ + /* Re-read the product on reprobe in both directions. */ m80.id = 0x00078800; m80.patches = m80.fmacs = m80.starts = 0; assert(system_config_8800d80(&m80) == 0); - assert(load_normal(&m80) == 0 && m80.patches == 5); + assert(load_normal(&m80) == 0 && m80.patches == 5 && m80.fmacs == 1 && m80.starts == 1); + assert(m80.syscfg_writes == 1 && m80.syscfg_masks == 1); 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); + assert(load_normal(&d80) == 0 && d80.patches == 5 && d80.fmacs == 1 && d80.starts == 1); + assert(d80.syscfg_writes == 1 && d80.syscfg_masks == 1); fail_read = 1; assert(system_config_8800d80(&d80) == -EIO); fail_read = 0; fail_write = 1; assert(system_config_8800d80(&d80) == -EIO); + fail_write_addr = 0x40500010; + assert(system_config_8800d80(&d80) == -EIO); + fail_write = 0; fail_mask = 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); @@ -132,5 +139,5 @@ with tempfile.TemporaryDirectory(prefix="aic-8820-test-") as temp: 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") +print("PASS: full patch uploads for both products, per-probe syscfg, MCU1 cache fix, write errors, busy connect state")