diff --git a/components/aic8800/build.rs b/components/aic8800/build.rs index 99cb4a0a37..414c6874b4 100644 --- a/components/aic8800/build.rs +++ b/components/aic8800/build.rs @@ -59,6 +59,34 @@ const FIRMWARE_FILES: &[FirmwareFile] = &[ remote_path: "aic8800DC/fw_patch_table_8800dc_u02.bin", sha256: "e7eea12cc85fca5d8667182b4520b6a0929044c70c6d9e9a3d7ece8b16169688", }, + FirmwareFile { + name: "fmacfw_patch_tbl_8800dc_u02.bin", + remote_path: "aic8800DC/fmacfw_patch_tbl_8800dc_u02.bin", + sha256: "62d53a223eda1ea064ba82a6fe67829d0720e9f4e87d26763fd13316ccd2a90b", + }, + // AIC8800DC-H (sub_id==2, chip_id_h) WiFi-only patch + patch table. + // Fetched from the pinned upstream mirror (same repo/commit as the other blobs). + FirmwareFile { + name: "fmacfw_patch_8800dc_h_u02.bin", + remote_path: "aic8800DC/fmacfw_patch_8800dc_h_u02.bin", + sha256: "f388dcb419a0f677c777a1eaad798156eabdfbb72c512a4d993df0dbc4f351d1", + }, + FirmwareFile { + name: "fmacfw_patch_tbl_8800dc_h_u02.bin", + remote_path: "aic8800DC/fmacfw_patch_tbl_8800dc_h_u02.bin", + sha256: "0469686691b72fa8296ff7abd1669ba978bdc0f115137fd392aa00a2717ff887", + }, + // AIC8800DC-H DPD calibration firmware: uploaded to 0x130000 and run via + // start_app(0x130009, FNCALL) to power on the RF/misc-RAM (0x110000) region + // before patch_config. Fetched from the pinned upstream mirror. + FirmwareFile { + name: "fmacfw_calib_8800dc_h_u02.bin", + remote_path: "aic8800DC/fmacfw_calib_8800dc_h_u02.bin", + sha256: "12bdcdd48e41b33bfd74834bffa326b4469bea82e7134de079392fbc2508acc7", + }, + // NB: the AIC8800DC RF config tables (ldpc/agc/txgain) are NOT firmware + // images and have no upstream mirror — they are vendor BSP source arrays, + // inlined as Rust byte arrays in `src/fw/firmware/dc_rf_cfg.rs` (no blob). FirmwareFile { name: "fmacfw_8800d80_u02.bin", remote_path: "aic8800_and_aic8800D80/fmacfw_8800d80_u02.bin", @@ -95,6 +123,14 @@ fn read_if_matches(path: &Path, expected_sha256: &str) -> Option> { /// Download a blob from the pinned upstream commit and verify its digest. fn download(file: &FirmwareFile) -> Vec { + assert!( + !file.remote_path.is_empty(), + "firmware {} has no upstream mirror (remote_path is empty) and was not found in the \ + in-tree firmware dir or $AIC8800_FIRMWARE_DIR. This blob is vendored in \ + components/aic8800/firmware/ — ensure it is checked out (it is allow-listed in that \ + dir's .gitignore).", + file.name + ); let url = format!( "https://raw.githubusercontent.com/{}/{}/{}", FIRMWARE_REPO, FIRMWARE_COMMIT, file.remote_path diff --git a/components/aic8800/firmware/.gitignore b/components/aic8800/firmware/.gitignore index 409ca21f2e..50d8e8f6c8 100644 --- a/components/aic8800/firmware/.gitignore +++ b/components/aic8800/firmware/.gitignore @@ -1,5 +1,10 @@ -# AIC8800 firmware blobs are fetched on demand by the xtask build tooling -# (see scripts/axbuild/src/firmware.rs) and integrity-checked against pinned -# SHA-256 digests. They are intentionally not vendored into version control. +# AIC8800 firmware blobs are fetched on demand at build time (see build.rs and +# scripts/axbuild/src/firmware.rs), integrity-checked against pinned SHA-256 +# digests, and provisioned into OUT_DIR. They are never vendored into version +# control. +# +# (The AIC8800DC RF config tables — ldpc/agc/txgain — are not firmware images +# and have no upstream mirror; they live as Rust byte arrays in +# src/fw/firmware/dc_rf_cfg.rs, not as .bin files here.) *.bin *.ini diff --git a/components/aic8800/firmware/README.md b/components/aic8800/firmware/README.md index 059922655d..0bc43377e9 100644 --- a/components/aic8800/firmware/README.md +++ b/components/aic8800/firmware/README.md @@ -31,6 +31,17 @@ The `aic8800` crate embeds the following blobs via `include_bytes!` | `fmacfw_patch_8800dc_u02.bin` | `aic8800DC/fmacfw_patch_8800dc_u02.bin` | | `fw_patch_8800dc_u02.bin` | `aic8800DC/fw_patch_8800dc_u02.bin` | | `fw_patch_table_8800dc_u02.bin` | `aic8800DC/fw_patch_table_8800dc_u02.bin` | +| `fmacfw_patch_8800dc_h_u02.bin` | `aic8800DC/fmacfw_patch_8800dc_h_u02.bin` | +| `fmacfw_patch_tbl_8800dc_h_u02.bin` | `aic8800DC/fmacfw_patch_tbl_8800dc_h_u02.bin` | +| `fmacfw_calib_8800dc_h_u02.bin` | `aic8800DC/fmacfw_calib_8800dc_h_u02.bin` | | `fmacfw_8800d80_u02.bin` | `aic8800_and_aic8800D80/fmacfw_8800d80_u02.bin` | | `fw_patch_8800d80_u02.bin` | `aic8800_and_aic8800D80/fw_patch_8800d80_u02.bin` | | `fw_patch_table_8800d80_u02.bin` | `aic8800_and_aic8800D80/fw_patch_table_8800d80_u02.bin` | + +## AIC8800DC RF config tables + +The AIC8800DC LDPC / AGC / TX-gain tables (`FW_DC_LDPC_CFG`, `FW_DC_AGC_CFG`, +`FW_DC_TXGAIN_MAP`, `FW_DC_TXGAIN_MAP_H`) are **not** firmware images and have no +upstream firmware mirror — they are little-endian `u32` arrays from the vendor +BSP source `aic8800dc_compat.c`, inlined as Rust byte arrays in +`src/fw/firmware/dc_rf_cfg.rs`, so no `.bin` blob is kept here for them. diff --git a/components/aic8800/src/fdrv/consts.rs b/components/aic8800/src/fdrv/consts.rs index 7174d05aba..d392367563 100644 --- a/components/aic8800/src/fdrv/consts.rs +++ b/components/aic8800/src/fdrv/consts.rs @@ -50,6 +50,19 @@ pub const INTR_CONFIG_VALUE: u8 = 0x07; /// 堆栈起始参数 pub const STACK_START_PARAM: [u8; 4] = [0x01, 0x00, 0x00, 0x00]; +/// AP 模式下注册表(registered_stas)的容量上限。防止异常情况下(如持续收到 +/// 不同 MAC 的 AssocReq 而无对应 deauth)注册表无界增长。当前固件 SoftAP 实际 +/// 关联数远低于此值。 +pub const MAX_REGISTERED_STAS: usize = 16; + +/// AP 控制端口(authorize)打开命令的最大重试次数。开放网络关联后必须显式授权, +/// 否则固件丢弃该 STA 的所有数据帧(DHCP/ARP/IP)。单核协作调度下该命令可能 +/// 概率性超时,AP worker 周期对账据此重试,到上限仍失败则放弃(STA 多半已离线)。 +pub const CONTROL_PORT_MAX_RETRY: u8 = 20; + +/// AP worker 控制端口对账的周期(毫秒)。仅在存在未授权 STA 时才周期性自唤醒。 +pub const CONTROL_PORT_RECONCILE_MS: u64 = 50; + // ============================================================ // 协议头部常量 // ============================================================ diff --git a/components/aic8800/src/fdrv/core/bus.rs b/components/aic8800/src/fdrv/core/bus.rs index 459e8946d0..1dac2d5a3e 100644 --- a/components/aic8800/src/fdrv/core/bus.rs +++ b/components/aic8800/src/fdrv/core/bus.rs @@ -163,6 +163,9 @@ impl TxState { } } +/// 已注册 STA 的表项:(MAC, sta_idx, 控制端口是否已成功打开, 控制端口重试次数)。 +pub type RegisteredSta = ([u8; 6], u8, bool, u8); + /// AP 模式状态:待处理的关联请求队列。 /// /// RX 线程收到 AssocReq 时把整帧 mpdu 入队(非阻塞),由独立的 AP worker @@ -172,6 +175,18 @@ impl TxState { pub struct ApState { pub assoc_queue: Mutex>>, pub assoc_pollset: PollSet, + /// 待从固件 STA 表删除的 sta_idx 队列。deauth/disassoc 在 RX 线程触发,但 + /// MM_STA_DEL_REQ 走 send_cmd 阻塞等 CFM(CFM 由 RX 线程处理)——在 RX 线程里 + /// 直接发会死锁,故入队交给 AP worker 线程执行(与 assoc_queue 同理)。 + pub sta_del_queue: Mutex>, + /// 已注册 STA 的 (MAC, sta_idx, 控制端口是否已成功打开, 控制端口重试次数)。 + /// - 手机重传 AssocReq 时据此去重:同一 MAC 已注册就不再发 ME_STA_ADD + /// (固件对重复注册不回 CFM,会让 AP worker 阻塞 5 秒超时、连接抖动)。 + /// - 控制端口标志为 false 时(首次开失败/超时),AP worker 周期对账会主动重试 + /// 打开(不依赖手机重传 AssocReq),实现自愈,避免数据帧被固件丢弃导致 + /// ping 不通;重试次数到上限后放弃,防止已离线 STA 空转。 + /// - 收到 deauth/disassoc 时移除该 MAC,使重连能完整重新注册。 + pub registered_stas: Mutex>, } impl Default for ApState { @@ -185,6 +200,8 @@ impl ApState { Self { assoc_queue: Mutex::new(VecDeque::new()), assoc_pollset: PollSet::new(), + sta_del_queue: Mutex::new(VecDeque::new()), + registered_stas: Mutex::new(Vec::new()), } } } diff --git a/components/aic8800/src/fdrv/core/init.rs b/components/aic8800/src/fdrv/core/init.rs index 8259a24457..3d4e2ffef7 100644 --- a/components/aic8800/src/fdrv/core/init.rs +++ b/components/aic8800/src/fdrv/core/init.rs @@ -10,12 +10,13 @@ use sdio_host::SdioHost; use crate::{ common::{ - ChipVariant, SDIO_TYPE_CFG_CMD_RSP, SDIOWIFI_BLOCK_CNT_REG, + ChipVariant, SDIO_TYPE_CFG_CMD_RSP, SDIOWIFI_BLOCK_CNT_REG, SDIOWIFI_BYTEMODE_ENABLE_REG, SDIOWIFI_BYTEMODE_ENABLE_REG_V3, SDIOWIFI_FLOW_CTRL_Q1_REG_V3, SDIOWIFI_FLOW_CTRL_REG, SDIOWIFI_FLOWCTRL_MASK, SDIOWIFI_INTR_CONFIG_REG, SDIOWIFI_INTR_ENABLE_REG_V3, SDIOWIFI_MISC_INT_STATUS_REG_V3, SDIOWIFI_RD_FIFO_ADDR, SDIOWIFI_RD_FIFO_ADDR_V3, - SDIOWIFI_SLEEP_REG_V3, SDIOWIFI_V3_SLEEP_READY_BIT, SDIOWIFI_V3_WAKEUP_VALUE, - SDIOWIFI_WAKEUP_REG_V3, SDIOWIFI_WR_FIFO_ADDR, SDIOWIFI_WR_FIFO_ADDR_V3, + SDIOWIFI_REGISTER_BLOCK, SDIOWIFI_SLEEP_REG_V3, SDIOWIFI_V3_SLEEP_READY_BIT, + SDIOWIFI_V3_WAKEUP_VALUE, SDIOWIFI_WAKEUP_REG_V3, SDIOWIFI_WR_FIFO_ADDR, + SDIOWIFI_WR_FIFO_ADDR_V3, }, fdrv::{ consts::*, @@ -70,6 +71,16 @@ fn intr_config_reg(is_v3: bool) -> u32 { } } +/// DC/DW 的命令邮箱在 SDIO function 2(与 bootrom 阶段一致, 真机实测 CFM 的 +/// block_cnt 在 func2 递增, func1 恒 0)。其余芯片走 func1。 +fn cmd_func(chip: ChipVariant) -> u8 { + if matches!(chip, ChipVariant::Aic8800DC | ChipVariant::Aic8800DW) { + 2 + } else { + 1 + } +} + // ===== polling_send_cmd 辅助函数 ===== /// 计算轮询命令帧的长度和对齐 @@ -120,13 +131,23 @@ fn build_polling_cmd_frame(msg_id: u16, dest_id: u16, param: &[u8], is_v3: bool) } /// 轮询模式流控检查(初始化阶段直接操作 sdio) -fn check_flow_control_polling(sdio: &H, is_v3: bool) -> Result<(), &'static str> { +/// +/// DC/DW(func==2): bootrom 已验证 DC 命令路径不轮询流控寄存器, 直接跳过。 +fn check_flow_control_polling( + sdio: &H, + is_v3: bool, + func: u8, +) -> Result<(), &'static str> { + if func == 2 { + log::info!("[fdrv] DC: skip flow_ctrl check (cmd on func2)"); + return Ok(()); + } for retry in 0..FLOW_CONTROL_MAX_RETRY { match sdio.read_byte(1, flow_ctrl_reg(is_v3)) { Ok(fc) => { let fc_val = fc & SDIOWIFI_FLOWCTRL_MASK; if fc_val != 0 { - log::debug!( + log::info!( "[fdrv] flow_ctrl OK, reg=0x{:02x}, val={} (raw=0x{:02x})", flow_ctrl_reg(is_v3), fc_val, @@ -149,20 +170,32 @@ fn check_flow_control_polling(sdio: &H, is_v3: bool) -> Result<(), fn poll_for_response( sdio: &H, is_v3: bool, + func: u8, expected_cfm: u16, cfm_buf: &mut [u8], ) -> Result { for retry in 0..RESPONSE_MAX_RETRY { let raw = sdio - .read_byte(1, block_cnt_reg(is_v3)) + .read_byte(func, block_cnt_reg(is_v3)) .map_err(|_| "read block_cnt error")?; - if retry > 0 && retry % 1000 == 0 { - log::debug!( - "[fdrv] poll retry {}: reg=0x{:02x}, raw=0x{:02x}", + // DIAG: 每 500 次扫一遍关键寄存器, 定位 CFM 到底回没回/回在哪个 func。 + // 仅在 trace 开启时执行,避免生产路径上多发 4 笔 CMD52。 + if retry % 500 == 0 && log::log_enabled!(log::Level::Trace) { + let f1_bc = sdio.read_byte(1, SDIOWIFI_BLOCK_CNT_REG).unwrap_or(0xEE); + let f2_bc = sdio.read_byte(2, SDIOWIFI_BLOCK_CNT_REG).unwrap_or(0xEE); + let f1_fc = sdio.read_byte(1, SDIOWIFI_FLOW_CTRL_REG).unwrap_or(0xEE); + let f0_ip = sdio.read_byte(0, 0x05).unwrap_or(0xEE); + let irqc = IRQ_COUNT.load(Ordering::Relaxed); + log::trace!( + "[DIAG] retry={} f1_bc=0x{:02x} f2_bc=0x{:02x} f1_fc(0x0a)=0x{:02x} \ + f0_intpend(0x05)=0x{:02x} irq#38={}", retry, - block_cnt_reg(is_v3), - raw + f1_bc, + f2_bc, + f1_fc, + f0_ip, + irqc ); } @@ -180,7 +213,7 @@ fn poll_for_response( continue; } - match read_and_parse_response(sdio, is_v3, block_cnt, expected_cfm, cfm_buf) { + match read_and_parse_response(sdio, is_v3, func, block_cnt, expected_cfm, cfm_buf) { Ok(len) => return Ok(len), Err(e) => { log::warn!("[polling] {}", e); @@ -196,6 +229,7 @@ fn poll_for_response( fn read_and_parse_response( sdio: &H, is_v3: bool, + func: u8, block_cnt: u8, expected_cfm: u16, cfm_buf: &mut [u8], @@ -203,7 +237,10 @@ fn read_and_parse_response( let read_len = (block_cnt as usize) * SDIOWIFI_FUNC_BLOCKSIZE; let mut rx_buf: Vec = vec![0u8; read_len]; - if sdio.read_fifo(1, rd_fifo_reg(is_v3), &mut rx_buf).is_err() { + if sdio + .read_fifo(func, rd_fifo_reg(is_v3), &mut rx_buf) + .is_err() + { crate::runtime::runtime().sleep_ms(2); return Err("CRC error, retrying"); } @@ -237,6 +274,7 @@ fn read_and_parse_response( pub fn polling_send_cmd( sdio: &H, is_v3: bool, + func: u8, msg_id: u16, dest_id: u16, param: &[u8], @@ -244,18 +282,26 @@ pub fn polling_send_cmd( cfm_buf: &mut [u8], ) -> Result { let buf = build_polling_cmd_frame(msg_id, dest_id, param, is_v3); - check_flow_control_polling(sdio, is_v3)?; + check_flow_control_polling(sdio, is_v3, func)?; log::debug!( - "[fdrv] sending cmd 0x{:04x} via fifo reg 0x{:02x}, len={}", + "[fdrv] sending cmd 0x{:04x} via func{} fifo reg 0x{:02x}, len={}", msg_id, + func, wr_fifo_reg(is_v3), buf.len() ); - let pre_reg = sdio.read_byte(1, block_cnt_reg(is_v3)).unwrap_or(0xFF); - log::debug!("[fdrv] pre-send block_cnt_reg=0x{:02x}", pre_reg); + if log::log_enabled!(log::Level::Trace) { + let pre_f1 = sdio.read_byte(1, SDIOWIFI_BLOCK_CNT_REG).unwrap_or(0xEE); + let pre_f2 = sdio.read_byte(2, SDIOWIFI_BLOCK_CNT_REG).unwrap_or(0xEE); + log::trace!( + "[fdrv] pre-send block_cnt f1=0x{:02x} f2=0x{:02x}", + pre_f1, + pre_f2 + ); + } - sdio.write_fifo(1, wr_fifo_reg(is_v3), &buf) + sdio.write_fifo(func, wr_fifo_reg(is_v3), &buf) .map_err(|_| "write_fifo error")?; if !wait_cfm { @@ -263,7 +309,7 @@ pub fn polling_send_cmd( } let expected_cfm = msg_id + 1; - poll_for_response(sdio, is_v3, expected_cfm, cfm_buf) + poll_for_response(sdio, is_v3, func, expected_cfm, cfm_buf) } /// 排空残留数据 @@ -313,9 +359,9 @@ fn wait_for_firmware_stabilization() { } /// 排空初始化前的残留数据 -fn drain_initial_stale_data(sdio: &H, is_v3: bool) { +fn drain_initial_stale_data(sdio: &H, is_v3: bool, func: u8) { for i in 0..5u32 { - match sdio.read_byte(1, block_cnt_reg(is_v3)) { + match sdio.read_byte(func, block_cnt_reg(is_v3)) { Ok(raw) => { let cnt = raw & BLOCK_COUNT_MASK; if cnt == 0 { @@ -324,7 +370,7 @@ fn drain_initial_stale_data(sdio: &H, is_v3: bool) { } let len = (cnt as usize) * SDIOWIFI_FUNC_BLOCKSIZE; let mut discard: Vec = vec![0u8; len]; - let _ = sdio.read_fifo(1, rd_fifo_reg(is_v3), &mut discard); + let _ = sdio.read_fifo(func, rd_fifo_reg(is_v3), &mut discard); log::debug!("[fdrv] drain: discarded {} bytes (block_cnt={})", len, cnt); } Err(e) => { @@ -354,10 +400,12 @@ fn send_stack_start_command( }; let param: [u8; 4] = [0x01, 0x00, set_vendor_info, 0x00]; + let func = cmd_func(chip); let mut cfm = [0u8; 2]; match polling_send_cmd( sdio, is_v3, + func, MM_SET_STACK_START_REQ, TASK_MM, ¶m, @@ -393,11 +441,11 @@ fn send_lmac_init_commands( } /// 排空 LMAC 初始化后的残留数据 -fn drain_post_init_data(sdio: &H, is_v3: bool) { +fn drain_post_init_data(sdio: &H, is_v3: bool, func: u8) { crate::runtime::runtime().sleep_ms(50); for _ in 0..10u32 { - match sdio.read_byte(1, block_cnt_reg(is_v3)) { + match sdio.read_byte(func, block_cnt_reg(is_v3)) { Ok(raw) => { let cnt = raw & BLOCK_COUNT_MASK; if cnt == 0 { @@ -405,7 +453,7 @@ fn drain_post_init_data(sdio: &H, is_v3: bool) { } let len = (cnt as usize) * SDIOWIFI_FUNC_BLOCKSIZE; let mut discard: Vec = vec![0u8; len]; - let _ = sdio.read_fifo(1, rd_fifo_reg(is_v3), &mut discard); + let _ = sdio.read_fifo(func, rd_fifo_reg(is_v3), &mut discard); log::debug!("[fdrv] post-init drain: discarded {} bytes", len); } Err(_) => break, @@ -460,7 +508,9 @@ fn start_driver_threads(bus: &alloc::sync::Arc) { /// /// 参考 radxa FDRV probe 后的 aicwf_sdiov3_func_init: /// V3: func0 0xF2=0x7F, 禁用 byte mode, wakeup=0x11, 检查 sleep_reg -fn reinit_sdio_func(sdio: &mut H, is_v3: bool) -> Result<(), &'static str> { +fn reinit_sdio_func(sdio: &mut H, chip: ChipVariant) -> Result<(), &'static str> { + let is_v3 = chip.is_v3(); + let is_dc = matches!(chip, ChipVariant::Aic8800DC | ChipVariant::Aic8800DW); if is_v3 { sdio.write_byte(0, 0xF2, 0x7F) .map_err(|_| "func0 0xF2 write failed")?; @@ -486,6 +536,32 @@ fn reinit_sdio_func(sdio: &mut H, is_v3: bool) -> Result<(), &'stat sdio.write_byte(0, 0x04, 0x07) .map_err(|_| "func0 0x04 write failed")?; log::debug!("[fdrv] V3 FN0 reg 0x04 = 0x07 (interrupt enable)"); + } else if is_dc { + // AIC8800DC/DW: 固件启动后, fdrv 阶段必须重做 SDIO func 初始化 + // (对齐 vendor aicwf_sdio_func_init)。固件 start 会重置 SDIO 状态机, + // bootrom 阶段写的 block-mode 失效, 不重写则固件不按 block 模式回包, + // block_cnt(0x12) 不递增 → 命令轮询永远超时。 + // 命令走 func1, 但 func2 也必须 enable + 设块大小 + 写 block/byte 模式。 + sdio.enable_func(2).map_err(|_| "func2 enable failed")?; + sdio.set_block_size(2, SDIOWIFI_FUNC_BLOCKSIZE as u16) + .map_err(|_| "func2 block size failed")?; + sdio.write_byte(2, SDIOWIFI_REGISTER_BLOCK, 0x01) + .map_err(|_| "func2 0x0B write failed")?; + sdio.write_byte(2, SDIOWIFI_BYTEMODE_ENABLE_REG, 0x01) + .map_err(|_| "func2 0x11 write failed")?; + // func1: 块模式使能 + 禁字节模式 + sdio.write_byte(1, SDIOWIFI_REGISTER_BLOCK, 0x01) + .map_err(|_| "func1 0x0B write failed")?; + sdio.write_byte(1, SDIOWIFI_BYTEMODE_ENABLE_REG, 0x01) + .map_err(|_| "func1 0x11 write failed")?; + crate::runtime::runtime().sleep_ms(10); + // 关键: 武装 func1 RX 中断路径 (对齐 vendor aicwf_sdio_bus_start)。 + // DC 固件的命令 CFM 经 SDIO 中断路径回包: 不写 INTR_CONFIG(0x04)=0x07, + // 芯片永不驱动 RX, block_cnt(0x12) 恒 0 → 轮询永远超时。 + // 必须在发第一笔 MM 命令之前写, 否则第一笔命令拿不到响应。 + sdio.write_byte(1, intr_config_reg(is_v3), 0x07) + .map_err(|_| "func1 0x04 intr_config write failed")?; + log::info!("[fdrv] DC SDIO func re-init done (func1+func2 block mode, func1 intr armed)"); } Ok(()) } @@ -495,21 +571,22 @@ pub fn init( chip: ChipVariant, ) -> Result, &'static str> { let is_v3 = chip.is_v3(); + let func = cmd_func(chip); // ---- Step 0: 等待固件 SDIO 接口稳定 ---- wait_for_firmware_stabilization(); // ---- Step 0.5: 固件启动后重新初始化 SDIO 功能寄存器 ---- - reinit_sdio_func(&mut sdio, is_v3)?; + reinit_sdio_func(&mut sdio, chip)?; // ---- Step 1: 排空残留数据 ---- - drain_initial_stale_data(&sdio, is_v3); + drain_initial_stale_data(&sdio, is_v3, func); // ---- Step 2: 轮询模式发送 MM_SET_STACK_START_REQ ---- send_lmac_init_commands(&sdio, is_v3, chip)?; // ---- Step 3: 排空 LMAC 初始化产生的残留数据 ---- - drain_post_init_data(&sdio, is_v3); + drain_post_init_data(&sdio, is_v3, func); // ---- Step 4: 创建 SdioTransport + WifiBus ---- let transport = SdioTransport::new(sdio, chip); diff --git a/components/aic8800/src/fdrv/core/sdio_transport.rs b/components/aic8800/src/fdrv/core/sdio_transport.rs index 2e84bdf601..d6596ba471 100644 --- a/components/aic8800/src/fdrv/core/sdio_transport.rs +++ b/components/aic8800/src/fdrv/core/sdio_transport.rs @@ -28,16 +28,29 @@ pub struct SdioTransport { sdio: Arc>, card_irq: Option>, is_v3: bool, + /// 命令/数据邮箱所在的 SDIO function。DC/DW = 2(真机实测 CFM 在 func2), + /// 其余 = 1。RX 线程读 block_cnt/RD_FIFO、TX 线程写 WR_FIFO 都用它。 + cmd_func: u8, + /// 芯片型号。保留完整变体,供上层线程(ap/rx/tx)按变体精确门控 + /// DC/DW 专属行为,使 D80/8801 走与 upstream/dev 一致的原有路径。 + chip: ChipVariant, } impl SdioTransport { /// 从任意 SdioHost 实现创建 SdioTransport pub fn new(sdio: H, chip: ChipVariant) -> Arc { let card_irq = sdio.card_irq_ctrl(); + let cmd_func = if matches!(chip, ChipVariant::Aic8800DC | ChipVariant::Aic8800DW) { + 2 + } else { + 1 + }; Arc::new(Self { sdio: Arc::new(Mutex::new(sdio)), card_irq, is_v3: chip.is_v3(), + cmd_func, + chip, }) } @@ -45,6 +58,34 @@ impl SdioTransport { self.is_v3 } + /// 芯片型号(完整变体) + pub fn chip(&self) -> ChipVariant { + self.chip + } + + /// 是否为双管道芯片(AIC8800DC/DW):命令走 func2、数据走 func1。 + /// + /// 等价于 `matches!(chip, Aic8800DC | Aic8800DW)`,也等价于 `cmd_func()==2`, + /// 但语义清晰。上层 AP/RX/TX 线程用它门控 DC/DW 专属行为,确保 D80/8801 + /// 走 upstream/dev 已验证的原有路径,不受 DC 适配影响。 + pub fn is_dual_pipe(&self) -> bool { + matches!(self.chip, ChipVariant::Aic8800DC | ChipVariant::Aic8800DW) + } + + /// 命令/数据邮箱所在的 SDIO function(DC/DW=2, 其余=1) + pub fn cmd_func(&self) -> u8 { + self.cmd_func + } + + /// 数据/管理帧平面所在的 SDIO function。 + /// + /// DC/DW 的 SDIO 是双管道:func2 是命令邮箱(命令 TX→CFM RX), + /// func1 是数据平面(数据/管理帧 TX/RX + 流控,真机实测数据帧 RX 在 func1)。 + /// 命令走 cmd_func()=2,数据/管理帧走本方法=1。非 DC 芯片两者都是 func1。 + pub fn data_func(&self) -> u8 { + 1 + } + pub fn block_cnt_reg(&self) -> u32 { if self.is_v3 { SDIOWIFI_MISC_INT_STATUS_REG_V3 diff --git a/components/aic8800/src/fdrv/protocol/apm.rs b/components/aic8800/src/fdrv/protocol/apm.rs index aab1ef7a68..398c275316 100644 --- a/components/aic8800/src/fdrv/protocol/apm.rs +++ b/components/aic8800/src/fdrv/protocol/apm.rs @@ -287,3 +287,28 @@ pub fn send_me_sta_add_req( } Ok(sta_idx) } + +/// 发送 MM_STA_DEL_REQ:从固件 STA 表中删除一个已注册 STA(对应 vendor +/// rwnx_send_mm_sta_del_req)。STA deauth/disassoc 后必须调用,否则固件继续占用 +/// 该 sta_idx 槽位,下一个 STA 会被分到更大的 idx——而本驱动下行数据帧用全局 +/// 单一 sta_idx 路由(hostdesc staid),非 0 槽位的下行无法送达,导致重连后 +/// ping/SSH 不通。vendor struct mm_sta_del_req { u8 sta_idx }。 +pub fn send_mm_sta_del_req( + bus: &Arc, + sta_idx: u8, + timeout_ms: u64, +) -> Result<(), CmdError> { + let param = [sta_idx]; + let cfm = send_cmd(bus, MM_STA_DEL_REQ, TASK_MM, ¶m, timeout_ms)?; + // mm_sta_del_cfm: status@0 + let status = cfm.first().copied().unwrap_or(0xFF); + log::info!( + "[apm] MM_STA_DEL_CFM: sta_idx={} status={}", + sta_idx, + status + ); + if status != 0 { + return Err(CmdError::InvalidResponse); + } + Ok(()) +} diff --git a/components/aic8800/src/fdrv/protocol/cmd.rs b/components/aic8800/src/fdrv/protocol/cmd.rs index 24701c27e3..14c3d36aaa 100644 --- a/components/aic8800/src/fdrv/protocol/cmd.rs +++ b/components/aic8800/src/fdrv/protocol/cmd.rs @@ -107,7 +107,7 @@ fn validate_and_extract_cfm_param(rsp: &[u8], _expected_cfm_id: u16) -> Result) { return Poll::Ready(()); } + // 先处理 STA 删除(deauth/disassoc 后释放固件 STA 槽位),再处理新关联, + // 这样新 STA 能复用刚释放的低位 sta_idx(下行单一 sta_idx 路由只对低位 + // 槽位可靠)。仅 DC/DW:D80/8801 不做槽位回收(固件自管理),队列恒空。 + if bus.transport.is_dual_pipe() { + loop { + let del_idx = bus.ap.sta_del_queue.lock().pop_front(); + match del_idx { + Some(idx) => { + if let Err(e) = send_mm_sta_del_req(&bus, idx, 0) { + log::warn!("[wifi-ap] MM_STA_DEL sta_idx={} failed: {:?}", idx, e); + } + } + None => break, + } + } + } + // 取出所有待处理的关联请求 loop { let assoc_req = bus.ap.assoc_queue.lock().pop_front(); @@ -37,10 +55,24 @@ pub fn start(bus: Arc) { } } + // 控制端口对账:对所有尚未授权的 STA 主动重试打开控制端口。不依赖手机 + // 重传 AssocReq——手机关联成功后通常不再发 AssocReq,若首次 authorize + // 命令超时,数据面会永久不通(ping/SSH 不通)。这里兜底直到成功或到上限。 + // 仅 DC/DW:D80/8801 走原有路径(关联时一次性 authorize,无周期对账)。 + let has_pending = if bus.transport.is_dual_pipe() { + reconcile_control_ports(&bus) + } else { + false + }; + bus.ap.assoc_pollset.register(cx.waker()); // 注册后再检查一次,避免错过唤醒 if !bus.ap.assoc_queue.lock().is_empty() { cx.waker().wake_by_ref(); + } else if has_pending { + // 仍有未授权 STA:周期性自唤醒重试,直到全部授权或到重试上限。 + crate::runtime::runtime().sleep_ms(CONTROL_PORT_RECONCILE_MS); + cx.waker().wake_by_ref(); } Poll::Pending }), @@ -62,21 +94,72 @@ fn handle_assoc_req(bus: &Arc, mpdu: &[u8]) { // AssocReq body: mgmt hdr(24) + cap(2) + listen_int(2) + IEs let rates = parse_supported_rates(&mpdu[28..]); - // 1. 注册 STA - let sta_idx = match send_me_sta_add_req(bus, &sta_mac, &rates, aid, vif_idx, 0) { - Ok(idx) => idx, - Err(e) => { - log::warn!("[wifi-ap] ME_STA_ADD failed: {:?}", e); + // DC/DW 专属:STA 注册去重 + 注册表维护(配合控制端口对账/槽位回收)。 + // D80/8801 走 upstream/dev 原有路径——不查重、不维护注册表,每个 AssocReq 都 + // 走一次 ME_STA_ADD(固件自管理重复注册)。 + let dual = bus.transport.is_dual_pipe(); + + // 已注册的 STA(手机重传 AssocReq):跳过 ME_STA_ADD,只补发 Assoc Response。 + // 固件对同一 MAC 的重复 ME_STA_ADD 不回 CFM,会让本 worker 阻塞 5 秒超时, + // 期间连接抖动(实测 DHCP 退回 Discover)。重传多因手机没收到上一个 Assoc + // Response,故补发即可。 + // 查注册表:返回 (sta_idx, 控制端口是否已开)。仅 DC/DW 查重。 + let existing = if dual { + bus.ap + .registered_stas + .lock() + .iter() + .find(|(mac, ..)| *mac == sta_mac) + .map(|(_, idx, ctrl, _)| (*idx, *ctrl)) + } else { + None + }; + + let (sta_idx, ctrl_open) = if let Some((idx, ctrl)) = existing { + log::info!( + "[wifi-ap] STA {:02x?} already registered (sta_idx={}, ctrl_open={}), resend Assoc \ + Response{}", + sta_mac, + idx, + ctrl, + if ctrl { + " only" + } else { + " + retry control port" + } + ); + (idx, ctrl) + } else { + // 新 MAC:先检查注册表容量,避免异常情况下无界增长(仅 DC/DW 维护注册表)。 + if dual && bus.ap.registered_stas.lock().len() >= MAX_REGISTERED_STAS { + log::warn!( + "[wifi-ap] registered_stas full ({}), reject new STA {:02x?}", + MAX_REGISTERED_STAS, + sta_mac + ); return; } + // 注册 STA。固件对重复注册不回 CFM,故仅新 MAC 才发 ME_STA_ADD。 + let idx = match send_me_sta_add_req(bus, &sta_mac, &rates, aid, vif_idx, 0) { + Ok(idx) => idx, + Err(e) => { + log::warn!("[wifi-ap] ME_STA_ADD failed: {:?}", e); + return; + } + }; + bus.conn.sta_idx.store(idx, Ordering::Release); + // 注册表仅 DC/DW 维护(用于去重/槽位回收);D80/8801 不入表。 + if dual { + bus.ap.registered_stas.lock().push((sta_mac, idx, false, 0)); + } + log::info!( + "[wifi-ap] STA {:02x?} registered: sta_idx={}, aid={}", + sta_mac, + idx, + aid + ); + (idx, false) }; - bus.conn.sta_idx.store(sta_idx, Ordering::Release); - log::info!( - "[wifi-ap] STA {:02x?} registered: sta_idx={}, aid={}", - sta_mac, - sta_idx, - aid - ); // 2. 回 Assoc Response let ap_mac = match *bus.conn.sta_mac.lock() { @@ -95,10 +178,69 @@ fn handle_assoc_req(bus: &Arc, mpdu: &[u8]) { // 3. 打开控制端口(authorize)。开放网络无 EAPOL,关联后必须显式授权, // 否则固件只放行 EAPOL、丢弃该 STA 的所有普通数据帧(DHCP/ARP/IP)。 // 对应 vendor change_station(AUTHORIZED) → rwnx_send_me_set_control_port_req。 + // 这里做首次尝试(低延迟);若超时,AP worker 的周期对账会继续重试直到成功。 + if !ctrl_open { + try_open_control_port(bus, &sta_mac, sta_idx); + } +} + +/// 尝试为指定 STA 打开控制端口(authorize),成功则在注册表置标志。 +/// 返回是否成功。失败(超时)不在此处重试——由调用方/周期对账驱动重试。 +fn try_open_control_port(bus: &Arc, sta_mac: &[u8; 6], sta_idx: u8) -> bool { match send_set_control_port_req(bus, sta_idx, true, 0) { - Ok(_) => log::info!("[wifi-ap] control port OPENED for sta_idx={}", sta_idx), - Err(e) => log::warn!("[wifi-ap] open control port failed: {:?}", e), + Ok(_) => { + log::info!("[wifi-ap] control port OPENED for sta_idx={}", sta_idx); + if let Some(e) = bus + .ap + .registered_stas + .lock() + .iter_mut() + .find(|(mac, ..)| mac == sta_mac) + { + e.2 = true; + } + true + } + Err(e) => { + log::warn!( + "[wifi-ap] open control port failed (sta_idx={}): {:?}", + sta_idx, + e + ); + false + } + } +} + +/// 周期对账:对注册表中所有"未授权且未到重试上限"的 STA 重试打开控制端口。 +/// 返回是否仍有待授权的 STA(用于决定 AP worker 是否需要继续周期自唤醒)。 +fn reconcile_control_ports(bus: &Arc) -> bool { + // 先快照出待重试的 (mac, idx),避免持锁期间发命令(send_cmd 会让出/阻塞)。 + let pending: alloc::vec::Vec<([u8; 6], u8)> = { + let tbl = bus.ap.registered_stas.lock(); + tbl.iter() + .filter(|(.., open, retries)| !*open && *retries < CONTROL_PORT_MAX_RETRY) + .map(|(mac, idx, ..)| (*mac, *idx)) + .collect() + }; + if pending.is_empty() { + return false; + } + + let mut still_pending = false; + for (mac, idx) in pending { + // 先自增重试计数(即便本次又超时,也能朝上限收敛,防止已离线 STA 永久空转)。 + { + let mut tbl = bus.ap.registered_stas.lock(); + if let Some(e) = tbl.iter_mut().find(|(m, ..)| *m == mac) { + e.3 = e.3.saturating_add(1); + } + } + if !try_open_control_port(bus, &mac, idx) { + still_pending = true; + } } + still_pending } /// 从关联请求的 IE 区解析 SupportedRates (EID=1),返回原始速率字节。 diff --git a/components/aic8800/src/fdrv/thread/rx.rs b/components/aic8800/src/fdrv/thread/rx.rs index e03d696915..7ff542aafb 100644 --- a/components/aic8800/src/fdrv/thread/rx.rs +++ b/components/aic8800/src/fdrv/thread/rx.rs @@ -19,6 +19,10 @@ use crate::{ pub static RX_WAKE_COUNT: AtomicU64 = AtomicU64::new(0); +/// DIAG: kicker 唤醒次数,用于确诊 kicker 是否真在跑。 +#[cfg(debug_assertions)] +static RX_KICK_COUNT: AtomicU64 = AtomicU64::new(0); + /// 上层(StarryOS)注册的"收到数据帧"回调,存为 `fn()` 裸指针。 /// /// AIC8800 是 SDIO WiFi,RX 走自己的线程并独占 SDIO CARD_INT (IRQ#38), @@ -56,6 +60,11 @@ fn align_up(val: usize, align: usize) -> usize { /// 启动 wifi-rx 线程 pub fn start(bus: Arc) { log::debug!("[wifi-rx] thread starting"); + // RX poll kicker 仅 DC/DW 启用(与 TX kicker 对称)。D80/8801 走 upstream/dev + // 的纯事件(ISR)驱动路径,不启动周期 kicker。 + if bus.transport.is_dual_pipe() { + start_rx_poll_kicker(bus.clone()); + } crate::runtime::runtime().spawn_poll_task( "wifi-rx", alloc::boxed::Box::new(move |cx| { @@ -97,18 +106,50 @@ pub fn start(bus: Arc) { ); } +/// 周期性唤醒 RX 线程去轮询 func2 的兜底任务。 +/// +/// 背景:RX 线程纯靠 waker 驱动,唤醒源只有 ISR(IRQ#38)和每次 TX。命令 CFM +/// 因发命令时顺带 wake 而能收到;但异步到来的帧(如 STA WPA2 握手的 EAPOL M1) +/// 没有 TX 触发,若 IRQ#38 不可靠,RX 线程会一直睡、帧烂在 func2 FIFO 没人读。 +/// 这里每 10ms 唤醒一次 RX,确保异步入站帧能被及时捞出。 +fn start_rx_poll_kicker(bus: Arc) { + crate::runtime::runtime().spawn_poll_task( + "wifi-rx-kick", + alloc::boxed::Box::new(move |cx| { + if *bus.state.lock() == BusState::Down { + return Poll::Ready(()); + } + // 唤醒 RX 线程去 poll func2(不自己做 SDIO,避免与 RX 线程争锁) + bus.rx.irq_waker.wake(); + // 每 ~1s 打一次心跳(仅 trace):证明 kicker 活着 + #[cfg(debug_assertions)] + { + let kicks = RX_KICK_COUNT.fetch_add(1, Ordering::Relaxed) + 1; + if kicks.is_multiple_of(100) { + log::trace!("[RXKICK] alive kicks={}", kicks); + } + } + // 阻塞 sleep:本任务独占一个 ax_task 线程,只拖慢自己 + crate::runtime::runtime().sleep_ms(10); + // 自唤醒,让 block_on 立即重新 poll,形成 10ms 周期循环 + cx.waker().wake_by_ref(); + Poll::Pending + }), + ); +} + // ===== RX 帧读取处理 ===== /// 读取 block_cnt 并处理 SDIO_OTHER_INTERRUPT 重试 /// /// # 返回 /// (block_cnt, should_continue) - 读取的块计数和是否应该继续处理 -fn read_block_count_with_retry(bus: &WifiBus, other_int_retries: &mut u32) -> (u8, bool) { +fn read_block_count_with_retry(bus: &WifiBus, func: u8, other_int_retries: &mut u32) -> (u8, bool) { let intstatus = { - match bus.transport.read_byte(1, bus.transport.block_cnt_reg()) { + match bus.transport.read_byte(func, bus.transport.block_cnt_reg()) { Ok(v) => v, Err(e) => { - log::error!("[wifi-rx] read block_cnt failed: {:?}", e); + log::error!("[wifi-rx] read block_cnt(func{}) failed: {:?}", func, e); return (0, false); } } @@ -196,7 +237,10 @@ fn resolve_rx_data_len(bus: &WifiBus, intstatus: u8) -> usize { /// byte 模式可为任意 ≤512 的字节数)。按 512 字节分段读,避免 1-bit SDIO 模式下单次 /// CMD53 传输 block 数量过多导致 SDHCI 控制器超时;最后不足 512 的尾段以 byte 模式读 /// (底层 read_fifo 对非 512 对齐长度自动走 byte-mode CMD53)。 -fn read_fifo_data(bus: &WifiBus, data_len: usize) -> Option> { +/// +/// `func`:DC 的 CFM/indication 邮箱在 func2,数据帧在 func1,需按队列读对应 func 的 +/// FIFO;8801/D80 命令与数据同在 func1。 +fn read_fifo_data(bus: &WifiBus, func: u8, data_len: usize) -> Option> { let mut buf = vec![0u8; data_len]; let mut offset = 0; @@ -204,7 +248,7 @@ fn read_fifo_data(bus: &WifiBus, data_len: usize) -> Option> { let chunk_len = core::cmp::min(data_len - offset, SDIOWIFI_FUNC_BLOCKSIZE); if let Err(e) = bus.transport.read_fifo( - 1, + func, bus.transport.rd_fifo_addr(), &mut buf[offset..offset + chunk_len], ) { @@ -231,6 +275,18 @@ fn process_rx_frames(bus: &WifiBus) { // ISR 只设 flag 不 mask,这里先 mask CARD_INT 防止重入 bus.transport.mask_card_irq(); + // 排空 RX FIFO。DC/DW 是双管道:func2 是命令 CFM/indication 邮箱,func1 是数据 + // 平面;两个 func 的 block_cnt 独立,需各自排空(EAPOL M1 等入站数据帧在 func1)。 + // 8801/D80 命令与数据同在 func1,func2 不是独立邮箱——只排空 func1,避免对 func2 + // 发无谓的 CMD52(cmd_func() 对 DC/DW=2,其余=1)。 + if bus.transport.cmd_func() == 2 { + drain_func(bus, 2); + } + drain_func(bus, 1); +} + +/// 排空指定 func 的 RX FIFO,读出所有待处理帧并分发。 +fn drain_func(bus: &WifiBus, func: u8) { let mut other_int_retries = 0u32; loop { @@ -239,7 +295,8 @@ fn process_rx_frames(bus: &WifiBus) { // ISR 触发了,继续读取(不 break) } - let (intstatus, should_continue) = read_block_count_with_retry(bus, &mut other_int_retries); + let (intstatus, should_continue) = + read_block_count_with_retry(bus, func, &mut other_int_retries); if !should_continue { break; } @@ -263,7 +320,7 @@ fn process_rx_frames(bus: &WifiBus) { ); // 读取 FIFO 数据 - let Some(buf) = read_fifo_data(bus, data_len) else { + let Some(buf) = read_fifo_data(bus, func, data_len) else { break; }; @@ -357,7 +414,7 @@ fn handle_mgmt_frame(bus: &WifiBus, mpdu: &[u8], pkt_len: usize) { 0xB if pkt_len >= 30 => { let alg = u16::from_le_bytes([mpdu[24], mpdu[25]]); let seq = u16::from_le_bytes([mpdu[26], mpdu[27]]); - log::info!("[ap-rx] Auth from {:02x?}: alg={} seq={}", sa, alg, seq); + log::debug!("[ap-rx] Auth from {:02x?}: alg={} seq={}", sa, alg, seq); // 开放认证(alg=0)、Auth Request(seq=1) → 回 Auth Response if alg == 0 && seq == 1 { @@ -367,7 +424,7 @@ fn handle_mgmt_frame(bus: &WifiBus, mpdu: &[u8], pkt_len: usize) { // Assoc/Reassoc Req → 交给 AP worker 线程处理(ME_STA_ADD + Assoc Resp) 0x0 | 0x2 if pkt_len >= 28 => { let cap = u16::from_le_bytes([mpdu[24], mpdu[25]]); - log::info!("[ap-rx] {} from {:02x?}: cap=0x{:04x}", subtype, sa, cap); + log::debug!("[ap-rx] {} from {:02x?}: cap=0x{:04x}", subtype, sa, cap); // 不能在 RX 线程做 ME_STA_ADD(send_cmd 会死锁),整帧入队转给 AP worker bus.ap .assoc_queue @@ -375,6 +432,37 @@ fn handle_mgmt_frame(bus: &WifiBus, mpdu: &[u8], pkt_len: usize) { .push_back(mpdu[..pkt_len].to_vec()); bus.ap.assoc_pollset.wake(); } + // Deauth(0xC)/Disassoc(0xA):STA 断开。从驱动注册表移除(使重连能完整重新 + // 注册),并把该 STA 的 sta_idx 入队,交 AP worker 发 MM_STA_DEL_REQ 释放固件 + // 槽位。否则固件继续占用旧 idx,下一个 STA 被分到更大 idx,而下行数据帧用全局 + // 单一 sta_idx 路由,非 0 槽位送不达 → 重连后 ping/SSH 不通。 + 0xC | 0xA => { + // DC/DW 专属:断开时清注册表 + 入队 MM_STA_DEL 释放固件槽位,使重连能 + // 复用低位 sta_idx(DC 下行单一 sta_idx 路由只对低位槽位可靠)。 + // D80/8801 走 upstream/dev 原有路径:不做注册表/槽位维护(固件自管理)。 + if !bus.transport.is_dual_pipe() { + log::info!("[ap-rx] {} from {:02x?}", subtype, sa); + return; + } + let mut mac = [0u8; 6]; + mac.copy_from_slice(sa); + let removed_idx = { + let mut tbl = bus.ap.registered_stas.lock(); + let idx = tbl.iter().find(|(m, ..)| *m == mac).map(|(_, i, ..)| *i); + tbl.retain(|(m, ..)| *m != mac); + idx + }; + if let Some(idx) = removed_idx { + bus.ap.sta_del_queue.lock().push_back(idx); + bus.ap.assoc_pollset.wake(); + } + log::info!( + "[ap-rx] {} from {:02x?} (removed_from_table={})", + subtype, + sa, + removed_idx.is_some() + ); + } _ => { // Beacon / ProbeReq 等:周围 AP 和扫描设备的帧,与连接无关。 // 降为 trace,避免淹没握手/数据帧日志。 @@ -406,7 +494,7 @@ fn send_auth_response(bus: &WifiBus, dst: &[u8]) { frame.extend_from_slice(&0u16.to_le_bytes()); // status = success(0) match crate::fdrv::thread::tx::enqueue_mgmt_frame(bus, frame) { - Ok(()) => log::info!("[ap-tx] Auth Response queued -> {:02x?}", dst), + Ok(()) => log::debug!("[ap-tx] Auth Response queued -> {:02x?}", dst), Err(e) => log::warn!("[ap-tx] Auth Response enqueue failed: {:?}", e), } } @@ -718,6 +806,12 @@ fn dispatch_frames(bus: &WifiBus, buf: &[u8]) { if !is_cfg { // ========== DATA 帧 ========== + log::trace!( + "[RXDIAG] DATA frame: pkt_type=0x{:02x} pkt_len={} offset={}", + pkt_type, + pkt_len, + offset + ); let aggr_len = pkt_len + RX_HWHRD_LEN; let advance = align_up(aggr_len, RX_ALIGNMENT); diff --git a/components/aic8800/src/fdrv/thread/tx.rs b/components/aic8800/src/fdrv/thread/tx.rs index 27f1c933d5..aee8f3ed11 100644 --- a/components/aic8800/src/fdrv/thread/tx.rs +++ b/components/aic8800/src/fdrv/thread/tx.rs @@ -44,6 +44,12 @@ fn pad_cmd_frame(cmd: &mut Vec) -> usize { /// 启动 wifi-tx 线程 pub fn start(bus: Arc) { log::debug!("[wifi-tx] thread starting"); + // TX poll kicker 仅 DC/DW 启用:它兜底 PollSet 无 sticky 导致的唤醒丢失,是 + // DC 双管道 + 控制端口对账场景下的需要。D80/8801 走 upstream/dev 的纯事件 + // 驱动路径,不启动 kicker,避免额外周期任务干扰已验证的调度。 + if bus.transport.is_dual_pipe() { + start_tx_poll_kicker(bus.clone()); + } crate::runtime::runtime().spawn_poll_task( "wifi-tx", alloc::boxed::Box::new(move |cx| { @@ -75,6 +81,37 @@ pub fn start(bus: Arc) { ); } +/// 周期性唤醒 wifi-tx 线程的兜底任务。 +/// +/// 背景:wifi-tx 是边沿触发的 poll 任务,且先 tx_process 再 register waker +/// (tx.rs:57/60)。PollSet 无 sticky 位(pollset.rs),若 `wake_pollset.wake()` +/// 触发时 TX 的 waker 恰不在 set 里(例如 TX 正在 tx_process 内的 yield 点), +/// 这次唤醒会永久丢失;叠加 `pending_flag` 队头阻塞(process_data_tx 见 pending +/// CMD 即 break),一次丢失的 CMD 唤醒会冻结整条 TX 数秒,直到下一次偶然命中 +/// 注册窗口的 wake——这正是控制端口命令时好时坏超时的根因。 +/// +/// RX 线程早有对称的 10ms kicker(start_rx_poll_kicker)解决同类问题;此处为 +/// TX 补上,确保任何丢失的唤醒最多被延迟 ~10ms 即被救醒。 +fn start_tx_poll_kicker(bus: Arc) { + crate::runtime::runtime().spawn_poll_task( + "wifi-tx-kick", + alloc::boxed::Box::new(move |cx| { + if *bus.state.lock() == BusState::Down { + return Poll::Ready(()); + } + // 仅在确有待发工作时才唤醒 TX,避免空转。 + if has_pending_work(&bus) { + bus.tx.wake_pollset.wake(); + } + // 阻塞 sleep:本任务独占一个 ax_task 线程,只拖慢自己。 + crate::runtime::runtime().sleep_ms(10); + // 自唤醒,形成 10ms 周期循环。 + cx.waker().wake_by_ref(); + Poll::Pending + }), + ); +} + // ===== CMD 发送处理 ===== /// 处理 CMD 帧发送 @@ -150,7 +187,7 @@ fn perform_cmd_flow_control_and_send( let fc_ok = transport.wait_flow_ctrl_for_size(send_len, 10); let did_work = if fc_ok { - match transport.write_fifo(1, transport.wr_fifo_addr(), cmd) { + match transport.write_fifo(transport.cmd_func(), transport.wr_fifo_addr(), cmd) { Ok(()) => true, Err(e) => { log::error!("[wifi-tx] CMD write_fifo failed: {:?}", e); @@ -247,17 +284,24 @@ fn send_single_data_frame( // 管理帧(raw 802.11)走独立构造路径 if frame.is_mgmt { - let buf = match build_mgmt_frame(&frame.data, vif_idx, transport.is_v3()) { + let buf = match build_mgmt_frame( + &frame.data, + vif_idx, + transport.is_v3(), + transport.cmd_func() == 2, + ) { Ok(b) => b, Err(_) => return false, }; - if let Err(e) = transport.write_fifo(1, transport.wr_fifo_addr(), &buf) { + if let Err(e) = transport.write_fifo(transport.data_func(), transport.wr_fifo_addr(), &buf) + { log::error!("[wifi-tx] MGMT write_fifo failed: {:?}", e); return false; } log::debug!( - "[wifi-tx] MGMT frame sent ({} bytes 802.11)", - frame.data.len() + "[wifi-tx] MGMT frame sent OK ({} bytes 802.11, vif={})", + frame.data.len(), + vif_idx ); return true; } @@ -289,7 +333,7 @@ fn send_single_data_frame( eth_frame.len() ); - if let Err(e) = transport.write_fifo(1, transport.wr_fifo_addr(), &buf) { + if let Err(e) = transport.write_fifo(transport.data_func(), transport.wr_fifo_addr(), &buf) { log::error!("[wifi-tx] DATA write_fifo failed: {:?}", e); return false; } @@ -339,6 +383,8 @@ fn build_data_frame( // 填充 SDIO header buf[0] = (sdio_hdr_len & 0xFF) as u8; buf[1] = ((sdio_hdr_len >> 8) & 0x0F) as u8; + // TX 数据路径 byte[2] = SDIO_TYPE_DATA(0x01):vendor aicwf_sdio.c TX 永远写 0x01, + // 与 upstream/dev 一致。写 0x00 会被固件 ingress 误判丢弃。 buf[2] = SDIO_TYPE_DATA; buf[3] = if is_v3 { crc8_ponl_107(&buf[0..3]) @@ -406,6 +452,7 @@ fn build_mgmt_frame( mgmt_frame: &[u8], vif_idx: u8, is_v3: bool, + dual_pipe: bool, ) -> Result, DataFrameBuildError> { const SDIO_HEADER_LEN: usize = 4; const HOSTDESC_SIZE: usize = 28; @@ -432,6 +479,7 @@ fn build_mgmt_frame( // SDIO header buf[0] = (sdio_hdr_len & 0xFF) as u8; buf[1] = ((sdio_hdr_len >> 8) & 0x0F) as u8; + // 同数据路径:TX byte[2] = SDIO_TYPE_DATA(0x01),与 upstream/dev 一致。 buf[2] = SDIO_TYPE_DATA; buf[3] = if is_v3 { crc8_ponl_107(&buf[0..3]) @@ -444,11 +492,14 @@ fn build_mgmt_frame( let hd = &mut buf[SDIO_HEADER_LEN..SDIO_HEADER_LEN + HOSTDESC_SIZE]; hd[0..2].copy_from_slice(&(payload_len as u16).to_le_bytes()); // flags_ext [2..4] = 0 - // hostid [4..8]: bit31 请求 TX CFM - hd[4..8].copy_from_slice(&0x8000_0001u32.to_le_bytes()); + // hostid [4..8]: Auth(FC=0xb0) 不在需要 CFM 的列表,vendor 填 0 + hd[4..8].copy_from_slice(&0u32.to_le_bytes()); // eth_dest/eth_src/ethertype 对管理帧无意义,置 0 hd[20..22].copy_from_slice(&0u16.to_le_bytes()); // ethertype = 0 - hd[22] = 0; // ac + // ac:DC/DW 实测需把 host 注入的管理帧路由到 VO 硬件队列(=3),ac=0 会落到 + // BK 队列导致 AP 注入的管理帧不被发出。8801/D80 沿用 vendor 的 ac=0(BK), + // 与 upstream/dev 一致,避免回归。 + hd[22] = if dual_pipe { 3 } else { 0 }; // ac: VO(DC) / BK(其余) hd[23] = 0xFF; // tid = 0xFF (非 QoS) hd[24] = vif_idx; hd[25] = 0xFF; // staid = 0xFF (未关联 STA) diff --git a/components/aic8800/src/fdrv/wifi/api.rs b/components/aic8800/src/fdrv/wifi/api.rs index bd17af4376..5d3aabe71b 100644 --- a/components/aic8800/src/fdrv/wifi/api.rs +++ b/components/aic8800/src/fdrv/wifi/api.rs @@ -573,6 +573,12 @@ impl WifiClient { .sta_idx .store(connect_result.ap_idx, core::sync::atomic::Ordering::Release); + // 禁用 Power Save 模式:必须在握手【之前】,否则 STA 关联后进省电, + // AP 会缓冲 EAPOL M1 不立即下发,host 永远等不到 → 握手超时。 + if let Err(e) = send_me_set_ps_mode_req(&self.bus, MM_PS_MODE_OFF, 3000) { + log::warn!("[WifiClient] Failed to disable PS mode: {:?}", e); + } + // WPA2 四次握手 if config.auth_type == WifiAuthType::Wpa2Psk && let Some(ref password) = config.password @@ -587,11 +593,6 @@ impl WifiClient { )?; } - // 禁用 Power Save 模式,防止固件空闲后自动进入省电导致 TX/RX 异常 - if let Err(e) = send_me_set_ps_mode_req(&self.bus, MM_PS_MODE_OFF, 3000) { - log::warn!("[WifiClient] Failed to disable PS mode: {:?}", e); - } - Ok(()) } diff --git a/components/aic8800/src/fw/chip/mod.rs b/components/aic8800/src/fw/chip/mod.rs index 059dcb3ad1..47781eb3b7 100644 --- a/components/aic8800/src/fw/chip/mod.rs +++ b/components/aic8800/src/fw/chip/mod.rs @@ -37,6 +37,7 @@ pub use variant::{ RAM_FMAC_FW_ADDR, RAM_FMAC_FW_PATCH_ADDR, RF_TBL_MASKED, + ROM_FMAC_CALIB_ADDR, ROM_FMAC_FW_ADDR, ROM_FMAC_PATCH_ADDR, SDIO_TYPE_CFG, diff --git a/components/aic8800/src/fw/chip/variant.rs b/components/aic8800/src/fw/chip/variant.rs index 1ce095b21d..ffc8b5e673 100644 --- a/components/aic8800/src/fw/chip/variant.rs +++ b/components/aic8800/src/fw/chip/variant.rs @@ -16,6 +16,8 @@ pub const ROM_FMAC_FW_ADDR: u32 = 0x0001_0000; pub const RAM_FMAC_FW_PATCH_ADDR: u32 = 0x0019_0000; /// ROM FMAC 固件补丁地址 (AIC8800DC) pub const ROM_FMAC_PATCH_ADDR: u32 = 0x0018_0000; +/// ROM FMAC 校准固件加载地址 (AIC8800DC DPD calib) +pub const ROM_FMAC_CALIB_ADDR: u32 = 0x0013_0000; // ============================================================ // 固件上传常量 diff --git a/components/aic8800/src/fw/firmware/data.rs b/components/aic8800/src/fw/firmware/data.rs index 35445f8a8d..ce3b39e69e 100644 --- a/components/aic8800/src/fw/firmware/data.rs +++ b/components/aic8800/src/fw/firmware/data.rs @@ -33,6 +33,20 @@ pub static FW_8801_PATCH_TBL: &[u8] = &[]; pub static FW_DC: &[u8] = include_bytes!(fw_path!("fmacfw_patch_8800dc_u02.bin")); pub static FW_DC_PATCH: &[u8] = include_bytes!(fw_path!("fw_patch_8800dc_u02.bin")); pub static FW_DC_PATCH_TBL: &[u8] = include_bytes!(fw_path!("fw_patch_table_8800dc_u02.bin")); +/// AIC8800DC WiFi 补丁表 (fmacfw_patch_tbl, 不同于上面的 BT patch table) +/// patch_config 阶段用它做代码跳转表 (aicwf_patch_table_load) +pub static FW_DC_FMAC_PATCH_TBL: &[u8] = + include_bytes!(fw_path!("fmacfw_patch_tbl_8800dc_u02.bin")); +/// AIC8800DC-H (sub_id==2) 专属 patch 固件 + 跳转表 (纯 WiFi, CONFIG_SDIO_BT=n) +pub static FW_DC_H_PATCH: &[u8] = include_bytes!(fw_path!("fmacfw_patch_8800dc_h_u02.bin")); +pub static FW_DC_H_FMAC_PATCH_TBL: &[u8] = + include_bytes!(fw_path!("fmacfw_patch_tbl_8800dc_h_u02.bin")); +/// AIC8800DC-H DPD 校准固件 (上传 0x130000, FNCALL 跑起来初始化 RF/misc RAM) +pub static FW_DC_H_CALIB: &[u8] = include_bytes!(fw_path!("fmacfw_calib_8800dc_h_u02.bin")); +/// AIC8800DC RF 配置 blob (patch_config 阶段上传到固件指定的 RAM 地址)。 +/// 这些不是固件镜像,而是 vendor BSP 源码 `aic8800dc_compat.c` 的 u32 数组, +/// 无上游下载源,故内联为 Rust 字节数组(见 `dc_rf_cfg`),不再 vendor .bin。 +pub use super::dc_rf_cfg::{FW_DC_AGC_CFG, FW_DC_LDPC_CFG, FW_DC_TXGAIN_MAP, FW_DC_TXGAIN_MAP_H}; // ============================================================ // AIC8800D80 固件 diff --git a/components/aic8800/src/fw/firmware/dc.rs b/components/aic8800/src/fw/firmware/dc.rs new file mode 100644 index 0000000000..26f28985e7 --- /dev/null +++ b/components/aic8800/src/fw/firmware/dc.rs @@ -0,0 +1,416 @@ +//! AIC8800DC/DW 固件 bring-up 序列 +//! +//! 移植自 vendor BSP `aic8800dc_compat.c` 的 `aicwifi_init()` DC 分支: +//! 1. `system_config_8800dc` — 读 chip_id/sub_id、set_bbpll_config、syscfg 表 +//! 2. `rwnx_plat_patch_load` — patch 固件上传到 0x180000 + misc_ram_init +//! 3. `aicwf_patch_config_8800dc` — wifisetting + ldpc/agc/txgain + patch_tbl +//! 4. `start_from_bootrom_8800DC` — 从 0x120000 启动 (HOST_START_APP_DUMMY) +//! +//! DC 的 FMAC 固件常驻 ROM,host 只上传 patch 覆盖;与 D80/8801 把整份 +//! 固件灌进 RAM 的模型不同。 + +use sdio_host::{SdioHost, error::SdioError}; + +use super::{ + super::{ + chip::*, + protocol::{ + IpcTransport, ipc_mem_block_write, ipc_mem_mask_write, ipc_mem_read, ipc_mem_write, + ipc_start_app, + }, + }, + data, +}; + +/// system_config 读 chip_id 的内存地址 +const CHIP_ID_ADDR: u32 = 0x4050_0000; +/// system_config 读 chip_sub_id 的内存地址 +const CHIP_SUB_ID_ADDR: u32 = 0x0000_0020; +/// patch_config 的配置指针基址 (cfg_base) +const CFG_BASE: u32 = 0x0001_0164; +/// CHIP_ID_H 掩码 (sub_id 区分普通/H 变体) +const CHIP_ID_H_MASK: u8 = 0xC0; +/// patch_tbl 描述块字节数 (describe_size 124 + 4 字节 base 头) +const PATCH_TBL_DESC_BYTES: usize = 124 + 4; +/// patch_config block 上传分片 (vendor 每次 512 字节) +const CFG_CHUNK: usize = 512; + +/// syscfg_tbl_8800dc: 160MHz clk +const SYSCFG_TBL_DC: &[(u32, u32)] = &[(0x4050_0010, 0x0000_0004), (0x4050_0010, 0x0000_0006)]; + +/// syscfg_tbl_8800dc_sdio_u02 (chip_mcu_id==0 时追加) +const SYSCFG_TBL_DC_SDIO_U02: &[(u32, u32)] = &[ + (0x4003_0000, 0x0003_6DA4), // loop forever after assert_err + (0x0011_E800, 0xE7FE_4070), + (0x4003_0084, 0x0011_E800), + (0x4003_0080, 0x0000_0001), + (0x4010_001C, 0x0000_0000), +]; + +/// syscfg_tbl_masked_8800dc: {addr, mask, data} — CONFIG_VRF_DCDC_MODE=y 分支 +/// pmic_pmu_init。0x70001000 项在 mcu_id==0 时运行期追加 (1<<8)|(1<<15)。 +const SYSCFG_TBL_MASKED_DC: &[(u32, u32, u32)] = &[ + (0x7000_216C, 0x3 << 2, 0x1 << 2), + (0x7000_21BC, 0x3 << 2, 0x1 << 2), + ( + 0x7000_2118, + (0x7 << 4) | (0x1 << 7), + (0x2 << 4) | (0x1 << 7), + ), + (0x7000_2104, 0x3F | (0x1 << 6), 0x2 | (0x1 << 6)), + (0x7000_210C, 0x3F | (0x1 << 6), 0x2 | (0x1 << 6)), + (0x7000_2170, 0xF, 0x1), + (0x7000_2190, 0x3F, 24), + (0x7000_21CC, (0x7 << 4) | (0x1 << 7), 0x0), + (0x7000_10A0, 0x1 << 11, 0x1 << 11), + (0x7000_1034, (0x1 << 20) | (0x7 << 26), 0x2 << 26), + (0x7000_1038, 0x1 << 8, 0x1 << 8), + (0x7000_1094, 0x3 << 2, 0x0), + ( + 0x7000_21D0, + (0x1 << 5) | (0x1 << 6), + (0x1 << 5) | (0x1 << 6), + ), + ( + 0x7000_1000, + (0x1 << 0) | (0x1 << 20) | (0x1 << 22), + (0x1 << 0) | (0x1 << 20), + ), + (0x7000_1028, 0xF << 2, 0x1 << 2), +]; + +/// syscfg_tbl_masked_8800dc_u01 (chip_sub_id==0 追加, low-temp 修正) +const SYSCFG_TBL_MASKED_DC_U01: &[(u32, u32, u32)] = &[ + (0x7000_1000, 0x1 << 16, 0x1 << 16), + (0x7000_1028, 0x1 << 6, 0x1 << 6), + (0x7000_1000, 0x1 << 16, 0x0), +]; + +/// syscfg_tbl_masked_8800dc_h: {addr, mask, data} — IS_CHIP_ID_H() 专属 +/// 与非 H 表差异: 0x7000216C 值不同、多 0x70002138/213C/2144、无 0x70001034。 +/// 0x70001000 为 CONFIG_VRF_DCDC_MODE=y 分支。 +const SYSCFG_TBL_MASKED_DC_H: &[(u32, u32, u32)] = &[ + ( + 0x7000_216C, + (0x3 << 2) | (0x3 << 4), + (0x2 << 2) | (0x2 << 4), + ), + (0x7000_2138, 0xFF, 0xFF), + (0x7000_213C, 0xFF, 0xFF), + (0x7000_2144, 0xFF, 0xFF), + (0x7000_21BC, 0x3 << 2, 0x1 << 2), + ( + 0x7000_2118, + (0x7 << 4) | (0x1 << 7), + (0x2 << 4) | (0x1 << 7), + ), + (0x7000_2104, 0x3F | (0x1 << 6), 0x2 | (0x1 << 6)), + (0x7000_210C, 0x3F | (0x1 << 6), 0x2 | (0x1 << 6)), + (0x7000_2170, 0xF, 0x1), + (0x7000_2190, 0x3F, 24), + (0x7000_21CC, (0x7 << 4) | (0x1 << 7), 0x0), + (0x7000_10A0, 0x1 << 11, 0x1 << 11), + // 注意: 0x70001034 在 H 表里是注释掉的, 不写 + (0x7000_1038, 0x1 << 8, 0x1 << 8), + (0x7000_1094, 0x3 << 2, 0x0), + ( + 0x7000_21D0, + (0x1 << 5) | (0x1 << 6), + (0x1 << 5) | (0x1 << 6), + ), + ( + 0x7000_1000, + (0x1 << 0) | (0x1 << 20) | (0x1 << 22), + (0x1 << 0) | (0x1 << 20), + ), + (0x7000_1028, 0xF << 2, 0x1 << 2), +]; + +/// 运行期探测到的 DC 芯片标识 +struct DcChipId { + chip_id: u8, + sub_id: u8, + mcu_id: u8, +} + +impl DcChipId { + fn is_h(&self) -> bool { + (self.chip_id & CHIP_ID_H_MASK) == CHIP_ID_H_MASK + } +} + +/// set_bbpll_config: 晶振由 CPU 提供时设置 bbpll +fn set_bbpll_config(transport: &mut IpcTransport) -> Result<(), SdioError> { + let v = ipc_mem_read(transport, 0x4050_0148)?; + if v & 0x01 == 0 { + log::debug!("[aic8800] DC crystal not provided by CPU"); + return Ok(()); + } + let bb = ipc_mem_read(transport, 0x4050_5010)?; + if (bb >> 29) == 3 { + return Ok(()); // already set + } + let new = (bb | (0x1 << 29) | (0x1 << 30)) & !(0x1 << 31); + ipc_mem_write(transport, 0x4050_5010, new)?; + Ok(()) +} + +/// system_config_8800dc: 读芯片标识 + bbpll + syscfg 表 +fn system_config(transport: &mut IpcTransport) -> Result { + let md = ipc_mem_read(transport, CHIP_ID_ADDR)?; + let chip_id = (md >> 16) as u8; + let mcu_id: u8 = if (md >> 25) & 0x1 == 0 { 1 } else { 0 }; + let sub_id = (ipc_mem_read(transport, CHIP_SUB_ID_ADDR)? & 0xFF) as u8; + let id = DcChipId { + chip_id, + sub_id, + mcu_id, + }; + log::info!( + "[aic8800] DC chip_id=0x{:02x} sub_id=0x{:02x} mcu_id={} is_h={}", + chip_id, + sub_id, + mcu_id, + id.is_h() + ); + + set_bbpll_config(transport)?; + let _ = ipc_mem_read(transport, 0x4050_0010)?; + + for &(a, d) in SYSCFG_TBL_DC { + ipc_mem_write(transport, a, d)?; + } + if mcu_id == 0 && (sub_id == 1 || sub_id == 2) { + for &(a, d) in SYSCFG_TBL_DC_SDIO_U02 { + ipc_mem_write(transport, a, d)?; + } + } + + // masked syscfg: H 变体用专属表 (PMIC/时钟配置不同, 且不写 0x70001034) + let masked_tbl = if id.is_h() { + SYSCFG_TBL_MASKED_DC_H + } else { + SYSCFG_TBL_MASKED_DC + }; + for &(a, mask, data) in masked_tbl { + let (m, dd) = if a == 0x7000_1000 && mcu_id == 0 { + let extra = (0x1 << 8) | (0x1 << 15); + (mask | extra, data | extra) + } else { + (mask, data) + }; + ipc_mem_mask_write(transport, a, m, dd)?; + } + if sub_id == 0 { + for &(a, mask, data) in SYSCFG_TBL_MASKED_DC_U01 { + ipc_mem_mask_write(transport, a, mask, data)?; + } + } + Ok(id) +} + +/// misc_ram_init_8800dc: 清零固件指向的 misc ram (12 字节) +fn misc_ram_init(transport: &mut IpcTransport) -> Result<(), SdioError> { + let misc_ram_addr = ipc_mem_read(transport, CFG_BASE + 0x14)?; + log::info!("[aic8800] DC misc_ram_addr=0x{:08x}", misc_ram_addr); + for i in 0..3 { + ipc_mem_write(transport, misc_ram_addr + i * 4, 0)?; + } + Ok(()) +} + +/// rf_misc_ram_t 中 bit_mask 距结构体起始的偏移 (= 0, bit_mask 是首成员) +const RF_MISC_RAM_BITMASK_OFF: u32 = 0; + +/// misc_ram_valid_check_8800dc: 读 misc_ram 的 bit_mask[0..4], 判断校准是否已生效 +/// valid 条件: bit_mask[0]==0 && (bit_mask[1]&0xFFF00000)==0x80000000 && +/// bit_mask[2]==0 && (bit_mask[3]&0xFFFFFF00)==0 +fn misc_ram_valid(transport: &mut IpcTransport) -> Result { + let misc_ram_addr = ipc_mem_read(transport, CFG_BASE + 0x14)?; + let base = misc_ram_addr + RF_MISC_RAM_BITMASK_OFF; + let mut bm = [0u32; 4]; + for (i, slot) in bm.iter_mut().enumerate() { + *slot = ipc_mem_read(transport, base + (i as u32) * 4)?; + } + let valid = bm[0] == 0 + && (bm[1] & 0xFFF0_0000) == 0x8000_0000 + && bm[2] == 0 + && (bm[3] & 0xFFFF_FF00) == 0; + log::info!( + "[aic8800] DC misc_ram bit_mask={:08x},{:08x},{:08x},{:08x} valid={}", + bm[0], + bm[1], + bm[2], + bm[3], + valid + ); + Ok(valid) +} + +/// aicwf_dpd_calib_8800dc (CONFIG_DPD + FORCE_DPD_CALIB 路径) +/// 若 misc_ram 未生效: 上传校准固件 → start_app(0x130009, FNCALL) 跑起来, +/// 由校准固件初始化 RF/misc RAM (含 0x110000 区) 并就地 apply DPD。 +/// 校准固件以 FNCALL 方式执行完即返回, 无需读回结果 (FORCE 路径不调用 apply)。 +fn dpd_calib( + transport: &mut IpcTransport, + calib_fw: &[u8], +) -> Result<(), SdioError> { + if misc_ram_valid(transport)? { + log::info!("[aic8800] DC misc ram valid, skip dpd calib"); + return Ok(()); + } + super::upload::upload_firmware(transport, calib_fw, ROM_FMAC_CALIB_ADDR)?; + log::info!("[aic8800] DC calib fw uploaded ({} bytes)", calib_fw.len()); + // 入口 0x130009 (Thumb, ROM_FMAC_CALIB_ADDR | 9), FNCALL=4 同步执行 + let status = ipc_start_app(transport, ROM_FMAC_CALIB_ADDR + 9, HOST_START_APP_FNCALL)?; + log::info!("[aic8800] DC dpd calib done status=0x{:08x}", status); + Ok(()) +} + +/// 按 vendor 512 字节分片上传一段 cfg blob 到指定 RAM 地址 +fn cfg_block_upload( + transport: &mut IpcTransport, + addr: u32, + blob: &[u8], +) -> Result<(), SdioError> { + let mut off = 0; + while off < blob.len() { + let end = core::cmp::min(off + CFG_CHUNK, blob.len()); + ipc_mem_block_write(transport, addr + off as u32, &blob[off..end])?; + off = end; + } + Ok(()) +} + +/// aicwf_patch_table_load: 解析 patch_tbl blob 并写入芯片 +/// 格式: dst[0]=describe_base; [0..128B] 描述块整体块写; 之后按 (addr,data) +/// 对从 offset 128 开始逐对 mem_write。 +fn patch_table_load( + transport: &mut IpcTransport, + blob: &[u8], +) -> Result<(), SdioError> { + if blob.len() < 128 { + log::error!("[aic8800] DC patch_tbl too small: {}", blob.len()); + return Err(SdioError::Unsupported); + } + let describe_base = u32::from_le_bytes([blob[0], blob[1], blob[2], blob[3]]); + log::debug!( + "[aic8800] DC patch_tbl describe_base=0x{:08x}", + describe_base + ); + // 描述块: describe_base 处写 124+4 字节 (含头部 base 字) + ipc_mem_block_write(transport, describe_base, &blob[..PATCH_TBL_DESC_BYTES])?; + + // 从字节偏移 128 起, 每 8 字节一对 (addr, data) + let mut off = 128; + while off + 8 <= blob.len() { + let a = u32::from_le_bytes([blob[off], blob[off + 1], blob[off + 2], blob[off + 3]]); + let d = u32::from_le_bytes([blob[off + 4], blob[off + 5], blob[off + 6], blob[off + 7]]); + ipc_mem_write(transport, a, d)?; + off += 8; + } + Ok(()) +} + +/// aicwf_patch_config_8800dc (testmode==0, sub_id>=1 路径) +/// 读 cfg 指针 → 写 wifisetting → 上传 ldpc/agc/txgain → patch_tbl_load +fn patch_config( + transport: &mut IpcTransport, + id: &DcChipId, +) -> Result<(), SdioError> { + if id.sub_id == 0 { + // u01 走 jump_tbl/patch_tbl_func 路径, 本移植暂只支持 u02/h_u02 + log::error!("[aic8800] DC sub_id==0 (u01) not supported by this port"); + return Err(SdioError::Unsupported); + } + + let wifisetting_cfg_addr = ipc_mem_read(transport, CFG_BASE)?; + let ldpc_cfg_addr = ipc_mem_read(transport, CFG_BASE + 0x8)?; + let agc_cfg_addr = ipc_mem_read(transport, CFG_BASE + 0xC)?; + let txgain_cfg_addr = ipc_mem_read(transport, CFG_BASE + 0x10)?; + log::info!( + "[aic8800] DC cfg: wifi=0x{:08x} ldpc=0x{:08x} agc=0x{:08x} txgain=0x{:08x}", + wifisetting_cfg_addr, + ldpc_cfg_addr, + agc_cfg_addr, + txgain_cfg_addr + ); + + // wifisetting (patch_tbl_wifisetting_8800dc_u02, 偏移 0x124): + // 实测此处已含 0x03001e01, 低3字节(使能/睡眠间隔/pwrctrl)已与 vendor 目标 + // 0x01001E01 一致, 唯一差异是 byte3(03 vs 01)。而写 byte3 会 wedge 我们的 + // SDIO host(改低字节正常、改 byte3 即死)。故保留 byte3, 只设低3字节, + // 既不卡死、有意义的配置位也正确。 + let ws_addr = wifisetting_cfg_addr + 0x0124; + let ws_cur = ipc_mem_read(transport, ws_addr)?; + let ws_val = (0x0100_1E01 & 0x00FF_FFFF) | (ws_cur & 0xFF00_0000); + log::info!( + "[aic8800] DC wifisetting [0x{:08x}] cur=0x{:08x} -> 0x{:08x}", + ws_addr, + ws_cur, + ws_val + ); + ipc_mem_write(transport, ws_addr, ws_val)?; + + cfg_block_upload(transport, ldpc_cfg_addr, data::FW_DC_LDPC_CFG)?; + cfg_block_upload(transport, agc_cfg_addr, data::FW_DC_AGC_CFG)?; + + // txgain: H 变体用 txgain_map_h, 否则 txgain_map (CONFIG_EXT_FEM_8800DCDW=n) + let txgain = if id.is_h() { + data::FW_DC_TXGAIN_MAP_H + } else { + data::FW_DC_TXGAIN_MAP + }; + cfg_block_upload(transport, txgain_cfg_addr, txgain)?; + + // sub_id 1/2 → 从 patch_tbl 文件加载跳转表 (H 变体用专属 tbl) + let patch_tbl = if id.is_h() { + data::FW_DC_H_FMAC_PATCH_TBL + } else { + data::FW_DC_FMAC_PATCH_TBL + }; + patch_table_load(transport, patch_tbl)?; + Ok(()) +} + +/// AIC8800DC/DW 完整固件 bring-up +pub fn init( + transport: &mut IpcTransport, + fw_set: &super::data::FirmwareSet, +) -> Result<(), SdioError> { + // 1. system_config: 芯片标识 + bbpll + syscfg + let id = system_config(transport)?; + log::info!("[aic8800] DC system_config done"); + + // 2. patch 固件上传到 ROM_FMAC_PATCH_ADDR (0x180000) + // H 变体 (sub_id==2) 用专属 patch, 否则用 fw_set 里的 u02 patch + let patch_fw = if id.is_h() { + data::FW_DC_H_PATCH + } else { + fw_set.wl_fw + }; + super::upload::upload_firmware(transport, patch_fw, ROM_FMAC_PATCH_ADDR)?; + // 校准/初始化: H 变体 (CONFIG_DPD+FORCE_DPD_CALIB) 跑 DPD 校准固件, + // 由它上电并初始化 RF/misc RAM (0x110000 区); 否则走 misc_ram_init 简化路径。 + if id.is_h() { + dpd_calib(transport, data::FW_DC_H_CALIB)?; + } else { + misc_ram_init(transport)?; + } + log::info!("[aic8800] DC patch_load done"); + + // 3. patch_config: wifisetting + rf cfg + patch_tbl + patch_config(transport, &id)?; + log::info!("[aic8800] DC patch_config done"); + + // 4. 从 bootrom 启动: 读 0x120000 后 start_app(0x120000, DUMMY) + let rd = ipc_mem_read(transport, RAM_FMAC_FW_ADDR)?; + log::debug!( + "[aic8800] DC fw mem [0x{:08x}]=0x{:08x}", + RAM_FMAC_FW_ADDR, + rd + ); + let status = ipc_start_app(transport, RAM_FMAC_FW_ADDR, HOST_START_APP_DUMMY)?; + log::info!("[aic8800] DC start_app status=0x{:08x}", status); + Ok(()) +} diff --git a/components/aic8800/src/fw/firmware/dc_rf_cfg.rs b/components/aic8800/src/fw/firmware/dc_rf_cfg.rs new file mode 100644 index 0000000000..015a6e05e8 --- /dev/null +++ b/components/aic8800/src/fw/firmware/dc_rf_cfg.rs @@ -0,0 +1,281 @@ +//! AIC8800DC-H RF 配置数据(LDPC / AGC / TX gain map)。 +//! +//! 这些**不是固件二进制**,而是 vendor BSP 驱动源码 `aic8800dc_compat.c` 里的 +//! `const u32[]` 配置表(小端)。原先以 `.bin` 形式 vendored 进仓库,但它们本质是 +//! 源码常量、无上游固件镜像可下载,故直接内联为 Rust 字节数组,使 `firmware/` 目录 +//! 不再保留任何 in-tree 二进制 blob(其余固件均由 build.rs 按 sha256 下载)。 +//! +//! 字节序与 vendor 源码一致(小端 u32 序列),按字节流经 `cfg_block_upload` 下发。 +//! 由 `gen_dc_rf_cfg` 从 vendor 提取的 .bin 生成,请勿手改。 + +pub static FW_DC_LDPC_CFG: &[u8] = &[ + 0x79, 0x76, 0x76, 0x00, 0x70, 0xf8, 0xf8, 0x1d, 0x70, 0xf8, 0xf8, 0x1d, 0x70, 0xf8, 0xf8, 0x1d, + 0x70, 0xf8, 0xf8, 0x1d, 0x72, 0x6e, 0x6e, 0x00, 0x69, 0xf8, 0xf8, 0x1d, 0x69, 0xf8, 0xf8, 0x1d, + 0x69, 0xf8, 0xf8, 0x1d, 0x69, 0xf8, 0xf8, 0x1d, 0x7b, 0x76, 0x76, 0x00, 0x70, 0xf8, 0xf8, 0x1d, + 0x70, 0xf8, 0xf8, 0x1d, 0x70, 0xf8, 0xf8, 0x1d, 0x70, 0xf8, 0xf8, 0x1d, 0x85, 0x7e, 0x7e, 0x00, + 0x76, 0xf8, 0xf4, 0x1d, 0x76, 0xf8, 0xf4, 0x1d, 0x76, 0xf8, 0xf4, 0x1d, 0x76, 0xf8, 0xf8, 0x1d, + 0x8a, 0x81, 0x81, 0x00, 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, + 0x7b, 0xf8, 0xf8, 0x1d, 0x8d, 0x81, 0x81, 0x00, 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, + 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, 0x8a, 0x81, 0x81, 0x00, 0x7b, 0xf8, 0xf8, 0x1d, + 0x7c, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, 0x40, 0x7e, 0x7e, 0x00, + 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, 0x7b, 0xf8, 0xf8, 0x1d, + 0x92, 0x8b, 0x8b, 0x00, 0x87, 0xf8, 0xf8, 0x1d, 0x89, 0xf8, 0xf8, 0x1d, 0x87, 0xf8, 0xf8, 0x1d, + 0x87, 0xf8, 0xf8, 0x1d, 0x55, 0x51, 0x51, 0x00, 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, + 0x89, 0xf8, 0xf8, 0x1d, 0x89, 0xf8, 0xf8, 0x1d, 0x54, 0x51, 0x51, 0x00, 0x4c, 0xf8, 0xf8, 0x1d, + 0x4c, 0xf8, 0xf8, 0x1d, 0x88, 0xf8, 0xf8, 0x1d, 0x88, 0xf8, 0xf8, 0x1d, 0x53, 0x4f, 0x4f, 0x00, + 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, + 0x53, 0x4f, 0x4f, 0x00, 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, + 0x4a, 0xf8, 0xf8, 0x1d, 0x53, 0x4f, 0x4f, 0x00, 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, + 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, 0x53, 0x4e, 0x4e, 0x00, 0x49, 0xf8, 0xf8, 0x1d, + 0x48, 0xf8, 0xf8, 0x1d, 0x48, 0xf8, 0xf8, 0x1d, 0x48, 0xf8, 0xf8, 0x1d, 0x52, 0x4d, 0x4d, 0x00, + 0x47, 0xf8, 0xf8, 0x1d, 0x47, 0xf8, 0xf8, 0x1d, 0x47, 0xf8, 0xf8, 0x1d, 0x47, 0xf8, 0xf8, 0x1d, + 0x55, 0x4f, 0x4f, 0x00, 0x4b, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, + 0x4a, 0xf8, 0xf8, 0x1d, 0x53, 0x4e, 0x4e, 0x00, 0x49, 0xf8, 0xf8, 0x1d, 0x48, 0xf8, 0xf8, 0x1d, + 0x48, 0xf8, 0xf8, 0x1d, 0x48, 0xf8, 0xf8, 0x1d, 0x4d, 0x49, 0x49, 0x00, 0x44, 0xf8, 0xf8, 0x1d, + 0x44, 0xf8, 0xf8, 0x1d, 0x44, 0xf8, 0xf8, 0x1d, 0x44, 0xf8, 0xf8, 0x1d, 0x8f, 0x51, 0x51, 0x00, + 0x49, 0xf8, 0xf8, 0x1d, 0x48, 0xf8, 0xf8, 0x1d, 0x48, 0xf8, 0xf8, 0x1d, 0x48, 0xf8, 0xf8, 0x1d, + 0x77, 0x42, 0x42, 0x00, 0x3f, 0xf8, 0xf8, 0x1d, 0x3c, 0xf8, 0xf8, 0x1d, 0x3c, 0xf8, 0xf8, 0x1d, + 0x3c, 0xf8, 0xf8, 0x1d, 0x75, 0x42, 0x42, 0x00, 0x9e, 0xf8, 0xf8, 0x1d, 0x3c, 0xf8, 0xf8, 0x1d, + 0x3c, 0xf8, 0xf8, 0x1d, 0x3c, 0xf8, 0xf8, 0x1d, 0x5c, 0x55, 0x55, 0x00, 0x4c, 0xf8, 0xf8, 0x1d, + 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x5c, 0x53, 0x53, 0x00, + 0x4c, 0xf8, 0xf8, 0x1d, 0x4b, 0xf8, 0xf8, 0x1d, 0x4b, 0xf8, 0xf8, 0x1d, 0x4b, 0xf8, 0xf8, 0x1d, + 0x9e, 0xf8, 0xf8, 0x00, 0x8c, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, + 0x4a, 0xf8, 0xf8, 0x1d, 0x40, 0x89, 0x89, 0x00, 0x46, 0xf8, 0xf8, 0x18, 0x45, 0xf8, 0xcf, 0x18, + 0x44, 0xf8, 0xcf, 0x18, 0x44, 0xf8, 0xcf, 0x18, 0x5f, 0x56, 0x56, 0x00, 0x4f, 0xf8, 0xf8, 0x1d, + 0x4f, 0xf8, 0xf8, 0x1d, 0x4f, 0xf8, 0xf8, 0x1d, 0x4f, 0xf8, 0xf8, 0x1d, 0x5e, 0x55, 0x55, 0x00, + 0x4e, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, + 0x5f, 0x56, 0x56, 0x00, 0x4f, 0xf8, 0xf8, 0x1d, 0x4f, 0xf8, 0xf8, 0x1d, 0x4f, 0xf8, 0xf8, 0x1d, + 0x4f, 0xf8, 0xf8, 0x1d, 0x61, 0x55, 0x55, 0x00, 0x50, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, + 0x4e, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, 0x5f, 0x53, 0x53, 0x00, 0x4d, 0xf8, 0xf8, 0x1d, + 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x5f, 0x55, 0x55, 0x00, + 0x4f, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, + 0xaa, 0x55, 0x55, 0x00, 0x54, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, 0x4e, 0xf8, 0xf8, 0x1d, + 0x4e, 0xf8, 0xf8, 0x1d, 0xa6, 0x59, 0x59, 0x00, 0x4d, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, + 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x9b, 0x4f, 0x4f, 0x00, 0x4e, 0xf8, 0xf8, 0x1d, + 0x46, 0xf8, 0xf8, 0x1d, 0x46, 0xf8, 0xf8, 0x1d, 0x46, 0xf8, 0xf8, 0x1d, 0xa5, 0xf8, 0xf8, 0x00, + 0x94, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, + 0xa4, 0x98, 0x98, 0x00, 0x4d, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, + 0x4c, 0xf8, 0xf8, 0x1d, 0x86, 0x46, 0x46, 0x00, 0xb3, 0xf8, 0xf8, 0x1d, 0x3d, 0xf8, 0xf8, 0x1d, + 0x3d, 0xf8, 0xf8, 0x1d, 0x3d, 0xf8, 0xf8, 0x1d, 0x40, 0x8e, 0x8e, 0x00, 0x48, 0xf8, 0xf8, 0x1a, + 0x48, 0xf8, 0xdf, 0x1a, 0x46, 0xf8, 0xdf, 0x1a, 0x46, 0xf8, 0xdf, 0x1a, 0x40, 0x7f, 0x7f, 0x00, + 0x75, 0xd2, 0xd2, 0x18, 0x3a, 0xd2, 0xd2, 0x18, 0x3a, 0xd2, 0xd2, 0x18, 0x39, 0xd2, 0xd2, 0x18, + 0x40, 0x45, 0x45, 0x00, 0x64, 0x86, 0x86, 0x0f, 0x3e, 0x86, 0x86, 0x0f, 0x3d, 0x86, 0x86, 0x0f, + 0x3d, 0x86, 0x86, 0x0f, 0x64, 0x5c, 0x5c, 0x00, 0x56, 0xf8, 0xf8, 0x1d, 0x55, 0xf8, 0xf8, 0x1d, + 0x55, 0xf8, 0xf8, 0x1d, 0x55, 0xf8, 0xf8, 0x1d, 0x68, 0x5b, 0x5b, 0x00, 0x58, 0xf8, 0xf8, 0x1d, + 0x55, 0xf8, 0xf8, 0x1d, 0x55, 0xf8, 0xf8, 0x1d, 0x55, 0xf8, 0xf8, 0x1d, 0x64, 0x5a, 0x5a, 0x00, + 0x55, 0xf8, 0xf8, 0x1d, 0x54, 0xf8, 0xf8, 0x1d, 0x54, 0xf8, 0xf8, 0x1d, 0x54, 0xf8, 0xf8, 0x1d, + 0xb5, 0x5a, 0x5a, 0x00, 0x5b, 0xf8, 0xf8, 0x1d, 0x55, 0xf8, 0xf8, 0x1d, 0x54, 0xf8, 0xf8, 0x1d, + 0x54, 0xf8, 0xf8, 0x1d, 0xb0, 0xf8, 0xf8, 0x00, 0xa3, 0xf8, 0xf8, 0x1d, 0x52, 0xf8, 0xf8, 0x1d, + 0x52, 0xf8, 0xf8, 0x1d, 0x52, 0xf8, 0xf8, 0x1d, 0xae, 0xa4, 0xa4, 0x00, 0x54, 0xf8, 0xf8, 0x1d, + 0x52, 0xf8, 0xf8, 0x1d, 0x52, 0xf8, 0xf8, 0x1d, 0x52, 0xf8, 0xf8, 0x1d, 0x40, 0x9a, 0x9a, 0x00, + 0x4e, 0xf8, 0xf8, 0x1d, 0x4d, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, 0x4c, 0xf8, 0xf8, 0x1d, + 0x40, 0x9c, 0x9c, 0x00, 0x95, 0xf8, 0xf8, 0x1d, 0x49, 0xf8, 0xf8, 0x1d, 0x4a, 0xf8, 0xf8, 0x1d, + 0x4a, 0xf8, 0xf8, 0x1d, 0x40, 0x49, 0x49, 0x00, 0x6f, 0x97, 0x97, 0x11, 0x42, 0x97, 0x97, 0x11, + 0x41, 0x97, 0x97, 0x11, 0x41, 0x97, 0x97, 0x11, 0x74, 0x6e, 0x6e, 0x00, 0x69, 0xf8, 0xf8, 0x1d, + 0x69, 0xf8, 0xf8, 0x1d, 0x69, 0xf8, 0xf8, 0x1d, 0x69, 0xf8, 0xf8, 0x1d, 0x40, 0x6e, 0x6e, 0x00, + 0x69, 0xf8, 0xde, 0x1a, 0x69, 0xf8, 0xde, 0x1a, 0x69, 0xf8, 0xde, 0x1a, 0x69, 0xf8, 0xde, 0x1a, + 0x40, 0x75, 0x75, 0x00, 0x6e, 0xf8, 0x78, 0x0d, 0x6e, 0xf8, 0x78, 0x0d, 0x6e, 0xf8, 0x78, 0x0d, + 0x6e, 0xf8, 0x79, 0x0d, 0x85, 0x78, 0x78, 0x00, 0x73, 0xf8, 0xf8, 0x1d, 0x73, 0xf8, 0xf8, 0x1d, + 0x73, 0xf8, 0xf8, 0x1d, 0x73, 0xf8, 0xf8, 0x1d, 0x40, 0x78, 0x78, 0x00, 0x73, 0xf8, 0xf8, 0x1d, + 0x73, 0xf8, 0xf8, 0x1d, 0x73, 0xf8, 0xf8, 0x1d, 0x73, 0xf8, 0xf8, 0x1d, 0x40, 0x78, 0x78, 0x00, + 0x73, 0xf8, 0x81, 0x0e, 0x73, 0xf8, 0x81, 0x0e, 0x73, 0xf8, 0x81, 0x0e, 0x73, 0xf8, 0x82, 0x0e, + 0x40, 0x40, 0x40, 0x00, 0x73, 0xf8, 0x82, 0x0e, 0x73, 0xf8, 0x82, 0x0e, 0x73, 0xf8, 0x82, 0x0e, + 0x73, 0xf8, 0x82, 0x0e, 0x40, 0x81, 0x81, 0x00, 0x7e, 0xf8, 0x92, 0x10, 0x7e, 0xf8, 0x92, 0x10, + 0x7e, 0xf8, 0x92, 0x10, 0x7e, 0xf8, 0x92, 0x10, 0x40, 0x40, 0x40, 0x00, 0x7e, 0xf8, 0x92, 0x10, + 0x7e, 0xf8, 0x92, 0x10, 0x7e, 0xf8, 0x92, 0x10, 0x7e, 0xf8, 0x92, 0x10, 0x40, 0x73, 0x73, 0x00, + 0x6b, 0xb2, 0xb2, 0x14, 0x35, 0xb2, 0xb2, 0x14, 0x35, 0xb2, 0xb2, 0x14, 0x35, 0xb2, 0xb2, 0x14, + 0x40, 0x40, 0x40, 0x00, 0x60, 0x82, 0x82, 0x0e, 0x3d, 0x82, 0x82, 0x0e, 0x3c, 0x82, 0x82, 0x0e, + 0x3c, 0x82, 0x82, 0x0e, 0x40, 0x40, 0x40, 0x00, 0x66, 0x8b, 0x8b, 0x0f, 0x3f, 0x8b, 0x8b, 0x0f, + 0x3d, 0x8b, 0x8b, 0x0f, 0x3d, 0x8b, 0x8b, 0x0f, 0x40, 0x40, 0x40, 0x00, 0x3d, 0x68, 0x68, 0x0b, + 0x1e, 0x68, 0x68, 0x0b, 0x1e, 0x68, 0x68, 0x0b, 0x1e, 0x68, 0x68, 0x0b, 0x40, 0x22, 0x22, 0x00, + 0x18, 0x43, 0x43, 0x06, 0x29, 0x43, 0x43, 0x06, 0x18, 0x43, 0x43, 0x06, 0x18, 0x43, 0x43, 0x06, + 0x40, 0x40, 0x40, 0x00, 0x72, 0x9d, 0x9d, 0x12, 0x43, 0x9d, 0x9d, 0x12, 0x41, 0x9d, 0x9d, 0x12, + 0x41, 0x9d, 0x9d, 0x12, 0x40, 0x40, 0x40, 0x00, 0x42, 0x75, 0x75, 0x0d, 0x20, 0x75, 0x75, 0x0d, + 0x20, 0x75, 0x75, 0x0d, 0x20, 0x75, 0x75, 0x0d, 0x40, 0x23, 0x23, 0x00, 0x19, 0x4c, 0x4c, 0x08, + 0x2c, 0x4c, 0x4c, 0x08, 0x19, 0x4c, 0x4c, 0x08, 0x19, 0x4c, 0x4c, 0x08, +]; + +pub static FW_DC_AGC_CFG: &[u8] = &[ + 0x00, 0x00, 0x00, 0x20, 0x0e, 0x00, 0x00, 0x04, 0x0e, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00, 0x5b, + 0x4b, 0x00, 0x00, 0x04, 0x8e, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x32, 0x7b, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x40, 0x26, 0x00, 0x00, 0xf8, 0x11, 0x00, 0x00, 0x04, 0x8e, 0x00, 0x19, 0x48, + 0x20, 0x00, 0x00, 0x9c, 0x91, 0x01, 0x00, 0x08, 0x00, 0x80, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0a, + 0x11, 0x44, 0x10, 0x08, 0x00, 0x80, 0x01, 0x38, 0x41, 0x46, 0x00, 0x0c, 0x14, 0x00, 0xd0, 0x08, + 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x17, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, + 0x00, 0x00, 0x00, 0x3c, 0x1a, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x38, 0x01, 0x00, 0x00, 0x40, + 0x1d, 0x00, 0x00, 0x08, 0x8e, 0x00, 0x08, 0x38, 0x50, 0x00, 0x00, 0x14, 0x20, 0x00, 0x00, 0x08, + 0x8e, 0x00, 0x00, 0x40, 0x7b, 0x00, 0x00, 0xa4, 0x01, 0x01, 0x00, 0x00, 0x9f, 0x33, 0x00, 0x30, + 0x00, 0x07, 0x00, 0x41, 0x20, 0x44, 0x10, 0x04, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x49, + 0x2f, 0x84, 0x0e, 0xf0, 0x2c, 0x84, 0x0e, 0xec, 0x2c, 0x84, 0x0e, 0xec, 0x32, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x30, 0x01, 0x01, 0x00, 0x48, 0x32, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, + 0x02, 0x02, 0x00, 0x48, 0x32, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x46, + 0x11, 0x00, 0x00, 0x04, 0x06, 0x00, 0x01, 0x58, 0x72, 0x04, 0x04, 0x3d, 0x39, 0x44, 0x20, 0xdc, + 0xd2, 0xd4, 0x1d, 0x08, 0x06, 0x00, 0x0a, 0x48, 0xdc, 0x44, 0x20, 0xdc, 0x3c, 0xd4, 0x1d, 0x08, + 0x04, 0x00, 0x05, 0x38, 0xc3, 0xf1, 0xf1, 0x0e, 0xdc, 0x44, 0x20, 0x34, 0x00, 0x00, 0x00, 0x30, + 0x00, 0x00, 0x00, 0x01, 0x42, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x33, + 0x45, 0x44, 0x10, 0x04, 0x00, 0x80, 0x00, 0x38, 0x9c, 0x10, 0x00, 0x22, 0x48, 0x44, 0x10, 0x08, + 0x00, 0x80, 0x00, 0x38, 0x9c, 0x50, 0xd4, 0x23, 0x17, 0x44, 0x10, 0x08, 0x00, 0xa0, 0x00, 0x90, + 0x00, 0x00, 0x00, 0x32, 0x63, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x14, 0x51, 0x00, 0x00, 0x1c, + 0x57, 0x00, 0x00, 0x10, 0x00, 0x80, 0x02, 0x38, 0x01, 0x00, 0x00, 0x0c, 0x66, 0x44, 0xd0, 0x08, + 0x0f, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x38, + 0x01, 0x26, 0x00, 0x0c, 0x5a, 0x44, 0xd0, 0x08, 0x00, 0x00, 0x00, 0x30, 0x30, 0x02, 0x02, 0x3d, + 0x5d, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x00, 0x01, 0x00, 0x3e, 0x66, 0x00, 0x00, 0x04, + 0x00, 0x80, 0x02, 0x38, 0x01, 0x16, 0x00, 0x0c, 0x66, 0x44, 0x20, 0x34, 0x00, 0x80, 0x02, 0x38, + 0x01, 0x0a, 0x00, 0x0c, 0x66, 0x44, 0x20, 0x34, 0x04, 0x80, 0x00, 0x38, 0x00, 0x00, 0x00, 0xff, + 0x7b, 0x00, 0x00, 0x08, 0x2f, 0x80, 0x00, 0x38, 0x00, 0x00, 0x00, 0x26, 0x6c, 0x00, 0x00, 0x08, + 0xaf, 0x04, 0x04, 0x38, 0x10, 0x10, 0x19, 0x1f, 0x6f, 0x00, 0x00, 0x08, 0xaf, 0x0c, 0x00, 0x20, + 0x71, 0x00, 0x00, 0x04, 0xaf, 0x0c, 0x00, 0x60, 0x79, 0x00, 0x70, 0x18, 0x77, 0x00, 0x00, 0x14, + 0x75, 0x00, 0x00, 0x10, 0xaf, 0x0c, 0x14, 0x28, 0x84, 0x00, 0xb0, 0x09, 0xaf, 0x0c, 0x0a, 0x28, + 0x84, 0x00, 0xb0, 0x09, 0xaf, 0x0c, 0x06, 0x28, 0x84, 0x00, 0xb0, 0x09, 0x86, 0x80, 0x04, 0x28, + 0x7d, 0x00, 0x00, 0x08, 0x86, 0x00, 0x00, 0x38, 0x00, 0x00, 0x80, 0x22, 0x80, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x30, 0x01, 0xf1, 0xf1, 0x0e, 0x83, 0x48, 0x00, 0x36, 0x00, 0x00, 0x02, 0x28, + 0x85, 0x00, 0x00, 0x08, 0x8e, 0x00, 0x02, 0x38, 0x31, 0x04, 0x04, 0x3d, 0x88, 0x00, 0x00, 0x08, + 0x8e, 0x00, 0x05, 0x38, 0x21, 0x18, 0x24, 0x1f, 0x8b, 0x00, 0x00, 0x08, 0x8e, 0x00, 0x00, 0x30, + 0x21, 0x30, 0x16, 0xa0, 0x8e, 0x00, 0x00, 0x04, 0x8e, 0x00, 0x00, 0x30, 0x12, 0x00, 0xf1, 0x0e, + 0x91, 0x00, 0x00, 0x34, 0xcc, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x50, 0x94, 0x00, 0x00, 0x04, + 0xfe, 0x95, 0x00, 0x38, 0x00, 0x00, 0x01, 0x32, 0x97, 0x00, 0x00, 0x04, 0xfe, 0x1f, 0x00, 0x50, + 0x00, 0x00, 0x01, 0x5a, 0x9b, 0x98, 0xc9, 0x6d, 0xb9, 0xd4, 0x19, 0xfc, 0x86, 0x01, 0x00, 0x30, + 0x73, 0x03, 0x84, 0x3d, 0x9e, 0x00, 0x00, 0x04, 0x8e, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0a, + 0xa1, 0x00, 0x00, 0x04, 0x8e, 0x00, 0x00, 0x30, 0x00, 0x00, 0xc0, 0x22, 0xa4, 0x00, 0x00, 0x04, + 0x8e, 0x02, 0x00, 0x90, 0x01, 0x00, 0x01, 0x32, 0xaa, 0x00, 0x40, 0x8e, 0xb0, 0x00, 0x00, 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x30, 0x01, 0x00, 0x01, 0x32, + 0xcb, 0x00, 0x00, 0x04, 0x8e, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x29, 0x11, 0x50, 0x04, 0x94, + 0xb6, 0x19, 0x00, 0x30, 0x00, 0x00, 0x01, 0x32, 0xb3, 0x00, 0x00, 0x04, 0xb6, 0x19, 0x00, 0x30, + 0x31, 0x04, 0x04, 0x3d, 0xb6, 0x00, 0x00, 0x04, 0xb6, 0x19, 0x00, 0x30, 0x00, 0x00, 0x80, 0x22, + 0x97, 0x00, 0x00, 0x04, 0x86, 0x01, 0x00, 0x30, 0x73, 0x04, 0x84, 0x3d, 0xbc, 0x00, 0x00, 0x04, + 0x8e, 0x00, 0x00, 0x30, 0x00, 0x00, 0x03, 0x29, 0xbf, 0x00, 0x00, 0x04, 0x8e, 0x02, 0xee, 0x9a, + 0x00, 0x01, 0x01, 0x32, 0xc5, 0x00, 0x00, 0x7c, 0xb0, 0x00, 0x00, 0xcc, 0xb0, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x30, 0x00, 0x01, 0x01, 0x32, 0xc8, 0x00, 0x00, 0x04, + 0x8e, 0x02, 0x00, 0x30, 0x00, 0x00, 0x00, 0x29, 0x11, 0x50, 0x04, 0x94, 0x8e, 0x03, 0x00, 0x50, + 0x00, 0x00, 0x00, 0x29, 0x11, 0x50, 0x04, 0x94, 0x35, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x01, 0x38, + 0x72, 0x04, 0x04, 0x3d, 0xd2, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x30, 0x41, 0xf1, 0xf1, 0x0e, + 0xd5, 0x00, 0x00, 0x34, 0x04, 0x00, 0x04, 0x28, 0xd7, 0x00, 0x00, 0x08, 0x0e, 0x00, 0x08, 0x28, + 0xd9, 0x00, 0x00, 0x08, 0x8e, 0x01, 0x00, 0x30, 0x52, 0x00, 0xf1, 0x0e, 0xdc, 0x00, 0x00, 0x34, + 0x8e, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00, 0x29, 0x11, 0x50, 0x04, 0x94, 0x00, 0x00, 0x02, 0x38, + 0x00, 0x00, 0x00, 0x32, 0xe2, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x60, 0xe6, 0x00, 0x00, 0xd8, + 0xe9, 0x00, 0x00, 0xd4, 0xec, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, 0x21, 0xf1, 0xf1, 0x0e, + 0xef, 0x48, 0x00, 0x36, 0x00, 0x00, 0x00, 0x30, 0x21, 0x24, 0x00, 0x0c, 0xef, 0x48, 0x00, 0x36, + 0x00, 0x00, 0x00, 0x30, 0x21, 0x00, 0x00, 0x0c, 0xef, 0x48, 0x00, 0x36, 0x00, 0x00, 0x02, 0x28, + 0x7b, 0x00, 0x00, 0x08, 0xfe, 0x1e, 0x00, 0x50, 0x00, 0x00, 0x01, 0x5a, 0xf5, 0x98, 0xc9, 0x6d, + 0xf8, 0xd4, 0x19, 0xfc, 0x8e, 0x02, 0x00, 0x30, 0x40, 0x00, 0x00, 0x32, 0xfb, 0x00, 0x00, 0x04, + 0x8e, 0x02, 0xee, 0x3a, 0x80, 0x00, 0x00, 0x32, 0xfb, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x30, + 0x01, 0xf1, 0xf1, 0x0e, 0xfe, 0x48, 0x00, 0x36, 0x00, 0x00, 0x02, 0x28, 0x00, 0x01, 0x00, 0x08, + 0x8e, 0x00, 0x02, 0x38, 0x31, 0x04, 0x04, 0x3d, 0x03, 0x01, 0x00, 0x08, 0x8e, 0x00, 0x05, 0x38, + 0x21, 0x18, 0x24, 0x1f, 0x06, 0x01, 0x00, 0x08, 0x8e, 0x00, 0x00, 0x30, 0x21, 0x30, 0x16, 0xa0, + 0x09, 0x01, 0x00, 0x04, 0x8e, 0x00, 0x00, 0x30, 0x12, 0x00, 0xf1, 0x0e, 0x0c, 0x01, 0x00, 0x34, + 0xf6, 0x14, 0x00, 0x30, 0x00, 0x00, 0x01, 0x32, 0x14, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, + 0x11, 0x01, 0x00, 0x04, 0xec, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x50, 0xf1, 0x00, 0x00, 0x04, + 0xf6, 0x14, 0x00, 0x30, 0x00, 0x00, 0x03, 0x32, 0x17, 0x01, 0x00, 0x04, 0x86, 0x10, 0x00, 0x30, + 0x73, 0x04, 0x84, 0x3d, 0x1a, 0x01, 0x00, 0x04, 0x8e, 0x10, 0x00, 0x50, 0x00, 0x00, 0xc0, 0x22, + 0xcb, 0xc0, 0x47, 0x8e, 0x1e, 0x01, 0x30, 0xcb, 0xb6, 0x19, 0x00, 0x30, 0x00, 0x00, 0x04, 0x32, + 0x21, 0x01, 0x00, 0x04, 0xb6, 0x19, 0x00, 0x30, 0x31, 0x04, 0x04, 0x3d, 0x24, 0x01, 0x00, 0x04, + 0xb6, 0x19, 0x00, 0x30, 0x00, 0x00, 0x80, 0x22, 0x11, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x01, 0x00, 0x30, 0x73, 0x04, 0x84, 0x3d, + 0x2d, 0x01, 0x00, 0x04, 0x8e, 0x03, 0x00, 0x50, 0x00, 0x00, 0x00, 0x29, 0x11, 0x50, 0x04, 0x94, + 0x31, 0x01, 0x00, 0xc0, 0x0e, 0x80, 0x0c, 0x38, 0x00, 0x00, 0x00, 0xff, 0x34, 0x01, 0x00, 0x08, + 0x04, 0x00, 0x00, 0x30, 0x03, 0xf1, 0xf1, 0x0f, 0x37, 0x01, 0x00, 0x34, 0x00, 0x00, 0x02, 0x28, + 0x39, 0x01, 0x00, 0x08, 0x8e, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00, 0x29, 0x11, 0x50, 0x04, 0x94, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x58, + 0x72, 0x04, 0x04, 0x3d, 0x43, 0x45, 0x20, 0xdc, 0xd2, 0xd4, 0x1d, 0x08, 0x06, 0x00, 0x0a, 0x48, + 0xdc, 0x44, 0x20, 0xdc, 0x46, 0xd5, 0x1d, 0x08, 0x04, 0x00, 0x05, 0x38, 0x41, 0xf1, 0xf1, 0x0e, + 0xdc, 0x44, 0x20, 0x34, 0x0e, 0x80, 0x02, 0x28, 0xdc, 0x00, 0x00, 0x08, 0x35, 0x00, 0x00, 0x48, + 0x4a, 0x01, 0x00, 0x04, 0x8f, 0x63, 0x96, 0x78, 0x0f, 0x00, 0x00, 0x41, 0x4f, 0x01, 0x00, 0x8c, + 0xc4, 0x50, 0x04, 0x08, 0x74, 0x45, 0x10, 0x90, 0x0f, 0x62, 0xc8, 0x88, 0x5a, 0x01, 0x00, 0xc0, + 0x74, 0x45, 0x10, 0x90, 0x54, 0x45, 0x10, 0x08, 0x57, 0x45, 0x10, 0x94, 0x8f, 0x62, 0x00, 0x30, + 0x00, 0x00, 0x00, 0x29, 0x7a, 0x51, 0x04, 0x94, 0x8f, 0x63, 0x00, 0x30, 0x00, 0x00, 0x00, 0x29, + 0x7a, 0x45, 0x10, 0x04, 0x05, 0xe0, 0x00, 0x38, 0x31, 0x01, 0x01, 0x3d, 0x5d, 0x45, 0x10, 0x08, + 0x0f, 0x60, 0x32, 0xa8, 0x74, 0x45, 0x10, 0x90, 0x54, 0x01, 0x00, 0x08, 0x57, 0x45, 0x10, 0x94, + 0x67, 0x45, 0x10, 0xc6, 0x63, 0x55, 0x18, 0xc4, 0x0f, 0xe0, 0x02, 0x58, 0x07, 0xea, 0xee, 0x0f, + 0x74, 0x01, 0x00, 0x80, 0x6b, 0x45, 0x20, 0x34, 0x0f, 0xe0, 0x02, 0x58, 0x07, 0xea, 0xee, 0x0e, + 0x74, 0x01, 0x00, 0x80, 0x6b, 0x45, 0x20, 0x34, 0x00, 0x40, 0x00, 0x30, 0x01, 0x00, 0x00, 0x33, + 0x6e, 0x01, 0x00, 0x04, 0x05, 0x40, 0x03, 0x38, 0x73, 0x03, 0x03, 0x3d, 0x71, 0x01, 0x00, 0x08, + 0x07, 0x60, 0x00, 0x30, 0x00, 0x00, 0x00, 0x33, 0x74, 0x01, 0x00, 0x04, 0x8f, 0x60, 0x00, 0x30, + 0x00, 0x00, 0x00, 0x29, 0x77, 0x51, 0x04, 0x94, 0x8f, 0x60, 0x00, 0x40, 0x7d, 0x45, 0x10, 0xa0, + 0x7a, 0x45, 0x10, 0x04, 0x8f, 0x60, 0x00, 0x30, 0x01, 0x01, 0x00, 0x64, 0x11, 0x44, 0x10, 0x04, + 0x8f, 0x60, 0x00, 0x30, 0x01, 0x01, 0x00, 0x64, 0x80, 0x45, 0x10, 0x04, 0x8f, 0x61, 0x00, 0x30, + 0x01, 0x00, 0x00, 0x42, 0x83, 0x01, 0x00, 0x04, 0x00, 0x80, 0x02, 0x38, 0x00, 0x00, 0x00, 0x32, + 0x86, 0x45, 0x10, 0x08, 0x8f, 0x61, 0x0a, 0x28, 0x88, 0x01, 0x00, 0x08, 0x8f, 0x61, 0x0a, 0x48, + 0x8b, 0x01, 0x00, 0xbc, 0x8e, 0x01, 0x00, 0x08, 0x8f, 0x61, 0x00, 0x30, 0x01, 0x00, 0x00, 0x34, + 0x05, 0x00, 0x00, 0x04, 0x8f, 0x61, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x08, 0x00, 0x00, 0x04, + 0x8f, 0x00, 0x00, 0x30, 0x0f, 0xed, 0xea, 0x0e, 0x94, 0x01, 0x00, 0x36, 0x00, 0x80, 0x03, 0x38, + 0x00, 0x00, 0x00, 0x34, 0x97, 0x01, 0x00, 0x08, 0x05, 0x80, 0x02, 0x38, 0x02, 0x00, 0x01, 0x29, + 0x9a, 0x01, 0x00, 0x08, 0x8f, 0x02, 0x00, 0x30, 0x9c, 0x20, 0x00, 0x22, 0x9d, 0x01, 0x00, 0x04, + 0x8f, 0x02, 0x00, 0x30, 0x9c, 0x50, 0xd4, 0x23, 0xa0, 0x01, 0x00, 0x04, 0x8f, 0x02, 0x14, 0x28, + 0xa2, 0x01, 0x00, 0x08, 0x8f, 0x02, 0x00, 0x30, 0x01, 0x02, 0x01, 0x43, 0xa5, 0x01, 0x00, 0x04, + 0x8f, 0x12, 0x00, 0x30, 0x00, 0x01, 0x00, 0x32, 0xa8, 0x01, 0x00, 0x04, 0x8f, 0x13, 0xee, 0x5a, + 0x0f, 0x00, 0x00, 0x41, 0xac, 0x01, 0x00, 0x7c, 0xf9, 0x00, 0x00, 0x08, 0x8f, 0x13, 0x2c, 0x59, + 0x00, 0x00, 0x00, 0x29, 0xb0, 0x01, 0x00, 0x8c, 0xf9, 0x00, 0x00, 0x08, 0x8f, 0x13, 0x00, 0x20, + 0x11, 0x50, 0x04, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub static FW_DC_TXGAIN_MAP: &[u8] = &[ + 0x80, 0xd7, 0xff, 0x00, 0x72, 0xd8, 0xff, 0x00, 0x80, 0xd8, 0xff, 0x00, 0x72, 0xd9, 0xff, 0x00, + 0x80, 0xd9, 0xff, 0x00, 0x75, 0xda, 0xff, 0x00, 0x86, 0xda, 0xff, 0x00, 0x77, 0xdb, 0xff, 0x00, + 0x86, 0xdb, 0xff, 0x00, 0x78, 0xdc, 0xff, 0x00, 0x89, 0xdc, 0xff, 0x00, 0x79, 0xdd, 0xff, 0x00, + 0x89, 0xdd, 0xff, 0x00, 0x83, 0xde, 0xff, 0x00, 0x79, 0xdf, 0xff, 0x00, 0x8b, 0xdf, 0xff, 0x00, + 0x72, 0xd0, 0xff, 0x00, 0x72, 0xd0, 0xff, 0x00, 0x80, 0xd0, 0xff, 0x00, 0x72, 0xd1, 0xff, 0x00, + 0x80, 0xd1, 0xff, 0x00, 0x72, 0xd2, 0xff, 0x00, 0x80, 0xd2, 0xff, 0x00, 0x6d, 0xd3, 0xff, 0x00, + 0x79, 0xd3, 0xff, 0x00, 0x6d, 0xd4, 0xff, 0x00, 0x79, 0xd4, 0xff, 0x00, 0x72, 0xd5, 0xff, 0x00, + 0x80, 0xd5, 0xff, 0x00, 0x72, 0xd6, 0xff, 0x00, 0x80, 0xd6, 0xff, 0x00, 0x72, 0xd7, 0xff, 0x00, + 0x7d, 0xc8, 0xff, 0x00, 0x8b, 0xc8, 0xff, 0x00, 0x79, 0xc9, 0xff, 0x00, 0x89, 0xc9, 0xff, 0x00, + 0x4b, 0xcc, 0xff, 0x00, 0x54, 0xcc, 0xff, 0x00, 0x5e, 0xcc, 0xff, 0x00, 0x69, 0xcc, 0xff, 0x00, + 0x78, 0xcc, 0xff, 0x00, 0x85, 0xcc, 0xff, 0x00, 0x70, 0xcd, 0xff, 0x00, 0x80, 0xcd, 0xff, 0x00, + 0x70, 0xce, 0xff, 0x00, 0x80, 0xce, 0xff, 0x00, 0x7d, 0xcf, 0xff, 0x00, 0x90, 0xcf, 0xff, 0x00, + 0x80, 0xc0, 0xff, 0x00, 0x90, 0xc0, 0xff, 0x00, 0x80, 0xc1, 0xff, 0x00, 0x90, 0xc1, 0xff, 0x00, + 0x7b, 0xc2, 0xff, 0x00, 0x8b, 0xc2, 0xff, 0x00, 0x7b, 0xc3, 0xff, 0x00, 0x90, 0xc3, 0xff, 0x00, + 0x85, 0xc4, 0xff, 0x00, 0x95, 0xc4, 0xff, 0x00, 0x79, 0xc5, 0xff, 0x00, 0x89, 0xc5, 0xff, 0x00, + 0x79, 0xc6, 0xff, 0x00, 0x89, 0xc6, 0xff, 0x00, 0x80, 0xc7, 0xff, 0x00, 0x90, 0xc7, 0xff, 0x00, + 0x7d, 0xc8, 0xff, 0x00, 0x8b, 0xc8, 0xff, 0x00, 0x79, 0xc9, 0xff, 0x00, 0x89, 0xc9, 0xff, 0x00, + 0x4b, 0xcc, 0xff, 0x00, 0x54, 0xcc, 0xff, 0x00, 0x5e, 0xcc, 0xff, 0x00, 0x69, 0xcc, 0xff, 0x00, + 0x78, 0xcc, 0xff, 0x00, 0x85, 0xcc, 0xff, 0x00, 0x70, 0xcd, 0xff, 0x00, 0x80, 0xcd, 0xff, 0x00, + 0x90, 0xcd, 0xff, 0x00, 0x68, 0xcf, 0xff, 0x00, 0x75, 0xcf, 0xff, 0x00, 0x83, 0xcf, 0xff, 0x00, + 0x80, 0xc0, 0xff, 0x00, 0x90, 0xc0, 0xff, 0x00, 0x80, 0xc1, 0xff, 0x00, 0x90, 0xc1, 0xff, 0x00, + 0x7b, 0xc2, 0xff, 0x00, 0x8b, 0xc2, 0xff, 0x00, 0x7b, 0xc3, 0xff, 0x00, 0x90, 0xc3, 0xff, 0x00, + 0x85, 0xc4, 0xff, 0x00, 0x95, 0xc4, 0xff, 0x00, 0x79, 0xc5, 0xff, 0x00, 0x89, 0xc5, 0xff, 0x00, + 0x79, 0xc6, 0xff, 0x00, 0x89, 0xc6, 0xff, 0x00, 0x80, 0xc7, 0xff, 0x00, 0x90, 0xc7, 0xff, 0x00, +]; + +pub static FW_DC_TXGAIN_MAP_H: &[u8] = &[ + 0x88, 0xd8, 0xff, 0x00, 0x79, 0xd9, 0xff, 0x00, 0x88, 0xd9, 0xff, 0x00, 0x79, 0xda, 0xff, 0x00, + 0x88, 0xda, 0xff, 0x00, 0x79, 0xdb, 0xff, 0x00, 0x88, 0xdb, 0xff, 0x00, 0x72, 0xdc, 0xff, 0x00, + 0x80, 0xdc, 0xff, 0x00, 0x80, 0xdd, 0xff, 0x00, 0x66, 0xde, 0xff, 0x00, 0x72, 0xde, 0xff, 0x00, + 0x80, 0xde, 0xff, 0x00, 0x79, 0xdf, 0xff, 0x00, 0x88, 0xdf, 0xff, 0x00, 0x98, 0xdf, 0xff, 0x00, + 0x79, 0xd0, 0xff, 0x00, 0x88, 0xd0, 0xff, 0x00, 0x79, 0xd1, 0xff, 0x00, 0x88, 0xd1, 0xff, 0x00, + 0x88, 0xd2, 0xff, 0x00, 0x6c, 0xd3, 0xff, 0x00, 0x79, 0xd3, 0xff, 0x00, 0x88, 0xd3, 0xff, 0x00, + 0x79, 0xd4, 0xff, 0x00, 0x88, 0xd4, 0xff, 0x00, 0x79, 0xd5, 0xff, 0x00, 0x88, 0xd5, 0xff, 0x00, + 0x79, 0xd6, 0xff, 0x00, 0x88, 0xd6, 0xff, 0x00, 0x79, 0xd7, 0xff, 0x00, 0x79, 0xd8, 0xff, 0x00, + 0x79, 0xc8, 0xff, 0x00, 0x6b, 0xc9, 0xff, 0x00, 0x79, 0xc9, 0xff, 0x00, 0x45, 0xcc, 0xff, 0x00, + 0x4d, 0xcc, 0xff, 0x00, 0x56, 0xcc, 0xff, 0x00, 0x60, 0xcc, 0xff, 0x00, 0x6b, 0xcc, 0xff, 0x00, + 0x79, 0xcc, 0xff, 0x00, 0x72, 0xcd, 0xff, 0x00, 0x60, 0xce, 0xff, 0x00, 0x72, 0xce, 0xff, 0x00, + 0x72, 0xcf, 0xff, 0x00, 0x80, 0xcf, 0xff, 0x00, 0x90, 0xcf, 0xff, 0x00, 0x90, 0xcf, 0xff, 0x00, + 0x79, 0xc0, 0xff, 0x00, 0x6b, 0xc1, 0xff, 0x00, 0x79, 0xc1, 0xff, 0x00, 0x6b, 0xc2, 0xff, 0x00, + 0x79, 0xc2, 0xff, 0x00, 0x6b, 0xc3, 0xff, 0x00, 0x79, 0xc3, 0xff, 0x00, 0x6b, 0xc4, 0xff, 0x00, + 0x79, 0xc4, 0xff, 0x00, 0x6b, 0xc5, 0xff, 0x00, 0x79, 0xc5, 0xff, 0x00, 0x6b, 0xc6, 0xff, 0x00, + 0x79, 0xc6, 0xff, 0x00, 0x6b, 0xc7, 0xff, 0x00, 0x79, 0xc7, 0xff, 0x00, 0x6b, 0xc8, 0xff, 0x00, + 0x79, 0xc8, 0xff, 0x00, 0x6b, 0xc9, 0xff, 0x00, 0x79, 0xc9, 0xff, 0x00, 0x45, 0xcc, 0xff, 0x00, + 0x4d, 0xcc, 0xff, 0x00, 0x56, 0xcc, 0xff, 0x00, 0x60, 0xcc, 0xff, 0x00, 0x6b, 0xcc, 0xff, 0x00, + 0x79, 0xcc, 0xff, 0x00, 0x72, 0xcd, 0xff, 0x00, 0x5b, 0xcf, 0xff, 0x00, 0x66, 0xcf, 0xff, 0x00, + 0x72, 0xcf, 0xff, 0x00, 0x80, 0xcf, 0xff, 0x00, 0x90, 0xcf, 0xff, 0x00, 0x90, 0xcf, 0xff, 0x00, + 0x79, 0xc0, 0xff, 0x00, 0x6b, 0xc1, 0xff, 0x00, 0x79, 0xc1, 0xff, 0x00, 0x6b, 0xc2, 0xff, 0x00, + 0x79, 0xc2, 0xff, 0x00, 0x6b, 0xc3, 0xff, 0x00, 0x79, 0xc3, 0xff, 0x00, 0x6b, 0xc4, 0xff, 0x00, + 0x79, 0xc4, 0xff, 0x00, 0x6b, 0xc5, 0xff, 0x00, 0x79, 0xc5, 0xff, 0x00, 0x6b, 0xc6, 0xff, 0x00, + 0x79, 0xc6, 0xff, 0x00, 0x6b, 0xc7, 0xff, 0x00, 0x79, 0xc7, 0xff, 0x00, 0x6b, 0xc8, 0xff, 0x00, +]; diff --git a/components/aic8800/src/fw/firmware/mod.rs b/components/aic8800/src/fw/firmware/mod.rs index 96b7920b5e..ea5c318724 100644 --- a/components/aic8800/src/fw/firmware/mod.rs +++ b/components/aic8800/src/fw/firmware/mod.rs @@ -1,7 +1,10 @@ //! 固件数据管理和上传模块 mod data; +mod dc; +mod dc_rf_cfg; mod upload; pub use data::{FirmwareSet, get_firmware_set}; -pub use upload::{init_aic8800d80_firmware, init_aic8800dc_firmware, init_aic8801_firmware}; +pub use dc::init as init_aic8800dc_firmware; +pub use upload::{init_aic8800d80_firmware, init_aic8801_firmware}; diff --git a/components/aic8800/src/fw/firmware/upload.rs b/components/aic8800/src/fw/firmware/upload.rs index 73c4ac8326..d05101e3eb 100644 --- a/components/aic8800/src/fw/firmware/upload.rs +++ b/components/aic8800/src/fw/firmware/upload.rs @@ -122,35 +122,6 @@ pub fn init_aic8801_firmware( Ok(()) } -/// AIC8800DC 固件初始化流程 -pub fn init_aic8800dc_firmware( - transport: &mut IpcTransport, - fw_set: &FirmwareSet, - chip_rev: &ChipRevision, -) -> Result<(), SdioError> { - let upload_addr = if chip_rev.rev == CHIP_REV_U01 { - RAM_FMAC_FW_ADDR - } else { - ROM_FMAC_FW_ADDR - }; - upload_firmware(transport, fw_set.wl_fw, upload_addr)?; - - if chip_rev.rev == CHIP_REV_U01 && !fw_set.wl_patch.is_empty() { - upload_firmware(transport, fw_set.wl_patch, ROM_FMAC_PATCH_ADDR)?; - } - - let boot_addr = if chip_rev.rev == CHIP_REV_U01 { - RAM_FMAC_FW_ADDR - } else { - ROM_FMAC_FW_ADDR - }; - let status = ipc_start_app(transport, boot_addr, HOST_START_APP_DUMMY)?; - log::debug!("[aic8800] AIC8800DC start_app status = 0x{:08x}", status); - - log::info!("[aic8800] AIC8800DC firmware init done"); - Ok(()) -} - /// AIC8800D80 Patch 配置 pub fn aicwifi_patch_config_8800d80( transport: &mut IpcTransport, diff --git a/components/aic8800/src/fw/mod.rs b/components/aic8800/src/fw/mod.rs index 87a92e628a..d6a1b028de 100644 --- a/components/aic8800/src/fw/mod.rs +++ b/components/aic8800/src/fw/mod.rs @@ -23,17 +23,41 @@ pub use crate::common; use crate::common::{SDIOWIFI_V3_WAKEUP_VALUE, SDIOWIFI_WAKEUP_REG_V3}; /// SDIO 功能寄存器初始化 /// -/// `is_v3`: true = AIC8800D80/D80X2, false = AIC8801/DC/DW -pub fn sdio_func_setup(host: &mut H, is_v3: bool) -> Result<(), SdioError> { +/// - AIC8801/DC/DW: 非 v3 寄存器组 +/// - AIC8800D80/D80X2: v3 寄存器组 +/// +/// AIC8800DC/DW 额外需要带起 SDIO function 2 (func_msg) 作为命令邮箱: +/// 使能 func2、设块大小 512、写 func2 的 register_block/bytemode/中断使能。 +/// 否则 bootrom 命令邮箱不响应,第一笔 DBG_MEM_READ 会一直超时。 +pub fn sdio_func_setup(host: &mut H, chip: ChipVariant) -> Result<(), SdioError> { + let is_v3 = matches!(chip, ChipVariant::Aic8800D80 | ChipVariant::Aic8800D80X2); + let is_dc = matches!(chip, ChipVariant::Aic8800DC | ChipVariant::Aic8800DW); + if !is_v3 { // ---- AIC8801 / AIC8800DC / AIC8800DW ---- + // AIC8800DC/DW: 命令邮箱在 SDIO function 2, 必须先带起 func2 + if is_dc { + host.enable_func(2)?; + host.set_block_size(2, SDIOWIFI_FUNC_BLOCKSIZE)?; + // func2: 使能块模式 + 禁用字节模式 + host.write_byte(2, SDIOWIFI_REGISTER_BLOCK, 0x01)?; + host.write_byte(2, SDIOWIFI_BYTEMODE_ENABLE_REG, 0x01)?; + // func2 中断使能 (bootrom cfm 经由 func2 返回) + host.write_byte(2, SDIOWIFI_INTR_CONFIG_REG, 0x07)?; + } + // 使能块模式 (block_bit0 = 0x1) host.write_byte(1, SDIOWIFI_REGISTER_BLOCK, 0x01)?; // 禁用字节模式 (byte_mode_disable = 0x1, 即 "no byte mode") host.write_byte(1, SDIOWIFI_BYTEMODE_ENABLE_REG, 0x01)?; + // func1 中断使能 + if is_dc { + host.write_byte(1, SDIOWIFI_INTR_CONFIG_REG, 0x07)?; + } + // 延时等待芯片内部状态稳定 (Linux: mdelay(10)) crate::runtime::runtime().sleep_ms(10); } else { @@ -60,7 +84,7 @@ pub fn sdio_func_setup(host: &mut H, is_v3: bool) -> Result<(), Sdi log::info!("[aic8800] V3 SDIO ready (sleep_reg=0x{:02x})", sleep_val); } - log::debug!("[aic8800] SDIO func setup done (v3={})", is_v3); + log::debug!("[aic8800] SDIO func setup done (chip={:?})", chip); Ok(()) } @@ -87,9 +111,9 @@ fn aicbsp_system_config(ipc: &mut IpcTransport) -> Result<(), Sd pub fn firmware_init(host: &mut H, chip: ChipVariant) -> Result<(), SdioError> { log::info!("[aic8800] firmware_init: chip={:?}", chip); - // 1. SDIO 功能寄存器初始化 (区分 v3 芯片) + // 1. SDIO 功能寄存器初始化 (区分芯片型号; DC/DW 会带起 func2) let is_v3 = matches!(chip, ChipVariant::Aic8800D80 | ChipVariant::Aic8800D80X2); - sdio_func_setup(host, is_v3)?; + sdio_func_setup(host, chip)?; // 2. 时钟配置 if matches!(chip, ChipVariant::Aic8801) { @@ -130,7 +154,7 @@ pub fn firmware_init(host: &mut H, chip: ChipVariant) -> Result<(), match chip { ChipVariant::Aic8801 => init_aic8801_firmware(&mut ipc, &fw_set)?, ChipVariant::Aic8800DC | ChipVariant::Aic8800DW => { - init_aic8800dc_firmware(&mut ipc, &fw_set, &chip_rev)? + init_aic8800dc_firmware(&mut ipc, &fw_set)? } ChipVariant::Aic8800D80 | ChipVariant::Aic8800D80X2 => { init_aic8800d80_firmware(&mut ipc, &fw_set)? diff --git a/components/aic8800/src/fw/protocol/ipc_msg.rs b/components/aic8800/src/fw/protocol/ipc_msg.rs index d6858cde6a..6690472151 100644 --- a/components/aic8800/src/fw/protocol/ipc_msg.rs +++ b/components/aic8800/src/fw/protocol/ipc_msg.rs @@ -143,6 +143,19 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { ) } + /// AIC8800DC/DW 使用独立的 SDIO function 2 (func_msg) 作为命令邮箱 + fn is_dc(&self) -> bool { + matches!(self.chip, ChipVariant::Aic8800DC | ChipVariant::Aic8800DW) + } + + /// 命令通道使用的 SDIO function 号 + /// + /// - AIC8800DC/DW: function 2 (func_msg) — bootrom 命令邮箱独立于数据口 + /// - AIC8801/D80/D80X2: function 1 + fn cmd_func(&self) -> u8 { + if self.is_dc() { 2 } else { 1 } + } + fn flow_ctrl_reg(&self) -> u32 { if self.is_v3() { SDIOWIFI_FLOW_CTRL_Q1_REG_V3 @@ -281,8 +294,10 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { /// 写入消息到 FIFO fn write_to_fifo(&mut self, send_len: usize) -> Result<(), SdioError> { + let func = self.cmd_func(); + let addr = self.wr_fifo_addr(); self.sdio_host - .write_fifo(1, self.wr_fifo_addr(), &self.tx_buf[..send_len])?; + .write_fifo(func, addr, &self.tx_buf[..send_len])?; Ok(()) } @@ -310,7 +325,8 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { /// 轮询 BLOCK_CNT_REG 等待响应数据 fn poll_block_count(&mut self) -> Result, SdioError> { - let raw_cnt = self.sdio_host.read_byte(1, self.block_cnt_reg())?; + let func = self.cmd_func(); + let raw_cnt = self.sdio_host.read_byte(func, self.block_cnt_reg())?; // mask 掉 SDIO_OTHER_INTERRUPT (bit7) if raw_cnt & SDIO_OTHER_INTERRUPT_FLAG != 0 { log::warn!("IPC: SDIO_OTHER_INTERRUPT set, raw_cnt=0x{:02x}", raw_cnt); @@ -325,8 +341,10 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { /// 从 FIFO 读取响应 fn read_response_fifo(&mut self, read_len: usize) -> Result<(), SdioError> { + let func = self.cmd_func(); + let addr = self.rd_fifo_addr(); self.sdio_host - .read_fifo(1, self.rd_fifo_addr(), &mut self.rx_buf[..read_len])?; + .read_fifo(func, addr, &mut self.rx_buf[..read_len])?; Ok(()) } @@ -358,6 +376,16 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { /// 等待响应消息 fn wait_for_response(&mut self, msg_id: u16, cfm_buf: &mut [u8]) -> Result { + self.wait_for_response_to(msg_id, cfm_buf, RESPONSE_MAX_RETRY) + } + + /// 等待响应消息 (可指定最大重试次数, 用于短超时探测) + fn wait_for_response_to( + &mut self, + msg_id: u16, + cfm_buf: &mut [u8], + max_retry: u32, + ) -> Result { let mut retry = 0u32; let mut read_err_cnt = 0u32; let expected_id = msg_id + 1; @@ -379,8 +407,7 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { }, Ok(None) => { retry += 1; - if retry > RESPONSE_MAX_RETRY { - log::error!("IPC: response timeout for msg_id=0x{:04x}", msg_id); + if retry > max_retry { return Err(SdioError::Timeout); } crate::runtime::runtime().sleep_ms(1); @@ -404,7 +431,11 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { let id = msg_id.msg_id(); let send_len = self.build_msg(id, payload); - self.wait_flow_control()?; + // AIC8800DC/DW 的命令路径不经过流控寄存器 (0x0A); + // 流控仅用于后续批量 data TX。直接写 func2 FIFO。 + if !self.is_dc() { + self.wait_flow_control()?; + } self.write_to_fifo(send_len)?; if !wait_cfm { @@ -413,6 +444,23 @@ impl<'a, H: SdioHost> IpcTransport<'a, H> { self.wait_for_response(id, cfm_buf) } + + /// 发送消息并以短超时等待响应 (探测用, max_retry ms 后返回 Timeout 而非死等) + pub fn send_msg_short( + &mut self, + msg_id: DbgMsgId, + payload: &[u8], + cfm_buf: &mut [u8], + max_retry: u32, + ) -> Result { + let id = msg_id.msg_id(); + let send_len = self.build_msg(id, payload); + if !self.is_dc() { + self.wait_flow_control()?; + } + self.write_to_fifo(send_len)?; + self.wait_for_response_to(id, cfm_buf, max_retry) + } } // ============================================================ @@ -446,6 +494,21 @@ pub fn ipc_mem_write( Ok(()) } +/// 探测写: 短超时 (2s) 写 4 字节, 超时返回 Err 而非死等 100s。 +/// 用于诊断哪些地址可写而不被首个 hang 卡死。 +pub fn ipc_mem_write_probe( + transport: &mut IpcTransport, + addr: u32, + data: u32, +) -> Result<(), SdioError> { + let mut payload = [0u8; 8]; + payload[..4].copy_from_slice(&addr.to_le_bytes()); + payload[4..].copy_from_slice(&data.to_le_bytes()); + let mut cfm = [0u8; 8]; + transport.send_msg_short(DbgMsgId::MemWriteReq, &payload, &mut cfm, 2000)?; + Ok(()) +} + /// 块写入芯片内存 (最大 1032 字节) pub fn ipc_mem_block_write( transport: &mut IpcTransport, diff --git a/components/aic8800/src/fw/protocol/mod.rs b/components/aic8800/src/fw/protocol/mod.rs index 69cecb9ab7..3782e1585f 100644 --- a/components/aic8800/src/fw/protocol/mod.rs +++ b/components/aic8800/src/fw/protocol/mod.rs @@ -4,5 +4,5 @@ mod ipc_msg; pub use ipc_msg::{ IpcTransport, ipc_mem_block_write, ipc_mem_mask_write, ipc_mem_read, ipc_mem_write, - ipc_start_app, + ipc_mem_write_probe, ipc_start_app, };