From c79f1b9fecb9ffa9b606818e80f933b2e3d06580 Mon Sep 17 00:00:00 2001 From: Shen Mintao Date: Wed, 28 Oct 2026 20:51:06 +0800 Subject: [PATCH] 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")