use sdio_host::{SdioHost, error::SdioError};
use super::super::chip::{
ChipVariant, DRV_TASK_ID, LMAC_FIRST_DBG, SDIO_TYPE_CFG_CMD_RSP, SDIOWIFI_BLOCK_CNT_REG,
SDIOWIFI_FLOW_CTRL_Q1_REG_V3, SDIOWIFI_FLOW_CTRL_REG, SDIOWIFI_FLOWCTRL_MASK,
SDIOWIFI_MISC_INT_STATUS_REG_V3, SDIOWIFI_RD_FIFO_ADDR, SDIOWIFI_RD_FIFO_ADDR_V3,
SDIOWIFI_WR_FIFO_ADDR, SDIOWIFI_WR_FIFO_ADDR_V3, TASK_DBG,
};
#[repr(u16)]
#[derive(Debug, Clone, Copy)]
pub enum DbgMsgId {
MemReadReq, MemReadCfm, MemWriteReq, MemWriteCfm, SetModFilterReq, SetModFilterCfm, SetSevFilterReq, SetSevFilterCfm, ErrorInd, GetSysStatReq, GetSysStatCfm, MemBlockWriteReq, MemBlockWriteCfm, StartAppReq, StartAppCfm, StartNpcReq, StartNpcCfm, MemMaskWriteReq, MemMaskWriteCfm, }
impl DbgMsgId {
pub fn msg_id(self) -> u16 {
LMAC_FIRST_DBG
+ match self {
Self::MemReadReq => 0,
Self::MemReadCfm => 1,
Self::MemWriteReq => 2,
Self::MemWriteCfm => 3,
Self::SetModFilterReq => 4,
Self::SetModFilterCfm => 5,
Self::SetSevFilterReq => 6,
Self::SetSevFilterCfm => 7,
Self::ErrorInd => 8,
Self::GetSysStatReq => 9,
Self::GetSysStatCfm => 10,
Self::MemBlockWriteReq => 11,
Self::MemBlockWriteCfm => 12,
Self::StartAppReq => 13,
Self::StartAppCfm => 14,
Self::StartNpcReq => 15,
Self::StartNpcCfm => 16,
Self::MemMaskWriteReq => 17,
Self::MemMaskWriteCfm => 18,
}
}
}
const MSG_BUF_MAX: usize = 1536;
const TRANSPORT_HEADER_SIZE: usize = 4;
const DUMMY_WORD_SIZE: usize = 4;
const LMAC_MSG_HEADER_SIZE: usize = 8;
const RESPONSE_PAYLOAD_OFFSET: usize =
TRANSPORT_HEADER_SIZE + DUMMY_WORD_SIZE + LMAC_MSG_HEADER_SIZE;
const SDIO_BLOCK_SIZE: usize = 512;
const TX_ALIGNMENT: usize = 4;
const TAIL_LEN: usize = 4;
const FLOW_CONTROL_MAX_RETRY: u32 = 50;
const SDIO_OTHER_INTERRUPT_FLAG: u8 = 0x80;
const RESPONSE_MAX_RETRY: u32 = 100_000;
const FIFO_READ_MAX_RETRY: u32 = 5;
use crate::common::crc8_ponl_107;
pub struct IpcTransport<'a, H: SdioHost> {
sdio_host: &'a mut H,
chip: ChipVariant,
tx_buf: [u8; MSG_BUF_MAX],
rx_buf: [u8; MSG_BUF_MAX],
}
impl<'a, H: SdioHost> IpcTransport<'a, H> {
pub fn new(sdio_host: &'a mut H, chip: ChipVariant) -> Self {
Self {
sdio_host,
chip,
tx_buf: [0; MSG_BUF_MAX],
rx_buf: [0; MSG_BUF_MAX],
}
}
pub fn host(&mut self) -> &mut H {
self.sdio_host
}
fn is_v3(&self) -> bool {
matches!(
self.chip,
ChipVariant::Aic8800D80 | ChipVariant::Aic8800D80X2
)
}
fn flow_ctrl_reg(&self) -> u32 {
if self.is_v3() {
SDIOWIFI_FLOW_CTRL_Q1_REG_V3
} else {
SDIOWIFI_FLOW_CTRL_REG
}
}
fn wr_fifo_addr(&self) -> u32 {
if self.is_v3() {
SDIOWIFI_WR_FIFO_ADDR_V3
} else {
SDIOWIFI_WR_FIFO_ADDR
}
}
fn rd_fifo_addr(&self) -> u32 {
if self.is_v3() {
SDIOWIFI_RD_FIFO_ADDR_V3
} else {
SDIOWIFI_RD_FIFO_ADDR
}
}
fn block_cnt_reg(&self) -> u32 {
if self.is_v3() {
SDIOWIFI_MISC_INT_STATUS_REG_V3
} else {
SDIOWIFI_BLOCK_CNT_REG
}
}
fn build_transport_header(&mut self, total_payload_len: usize) {
self.tx_buf[0] = (total_payload_len & 0xFF) as u8; self.tx_buf[1] = ((total_payload_len >> 8) & 0x0F) as u8; self.tx_buf[2] = SDIO_TYPE_CFG_CMD_RSP; match self.chip {
ChipVariant::Aic8800D80 | ChipVariant::Aic8800D80X2 => {
self.tx_buf[3] = crc8_ponl_107(&self.tx_buf[0..3]);
}
_ => {
self.tx_buf[3] = 0x00;
}
}
}
fn fill_dummy_word(&mut self) {
self.tx_buf[4..8].fill(0);
}
fn build_lmac_msg_header(&mut self, msg_id: u16, payload_len: u16) {
let idx = TRANSPORT_HEADER_SIZE + DUMMY_WORD_SIZE;
self.tx_buf[idx..idx + 2].copy_from_slice(&msg_id.to_le_bytes()); self.tx_buf[idx + 2..idx + 4].copy_from_slice(&TASK_DBG.to_le_bytes()); self.tx_buf[idx + 4..idx + 6].copy_from_slice(&DRV_TASK_ID.to_le_bytes()); self.tx_buf[idx + 6..idx + 8].copy_from_slice(&payload_len.to_le_bytes()); }
fn copy_payload(&mut self, payload: &[u8]) {
let payload_start = TRANSPORT_HEADER_SIZE + DUMMY_WORD_SIZE + LMAC_MSG_HEADER_SIZE;
self.tx_buf[payload_start..payload_start + payload.len()].copy_from_slice(payload);
}
fn align_to_4_bytes(&mut self, raw_len: usize) -> usize {
let aligned4 = (raw_len + TX_ALIGNMENT - 1) & !(TX_ALIGNMENT - 1); for i in raw_len..aligned4 {
self.tx_buf[i] = 0; }
aligned4
}
fn align_to_block(&mut self, aligned4: usize) -> usize {
if !aligned4.is_multiple_of(SDIO_BLOCK_SIZE) {
let with_tail = aligned4 + TAIL_LEN;
let block_aligned = ((with_tail / SDIO_BLOCK_SIZE) + 1) * SDIO_BLOCK_SIZE; for i in aligned4..block_aligned.min(MSG_BUF_MAX) {
self.tx_buf[i] = 0; }
block_aligned
} else {
aligned4 }
}
fn build_msg(&mut self, msg_id: u16, payload: &[u8]) -> usize {
let lmac_msg_len = LMAC_MSG_HEADER_SIZE + payload.len(); let total_payload_len = TRANSPORT_HEADER_SIZE + lmac_msg_len;
self.build_transport_header(total_payload_len);
self.fill_dummy_word();
self.build_lmac_msg_header(msg_id, payload.len() as u16);
self.copy_payload(payload);
let raw_len = TRANSPORT_HEADER_SIZE + DUMMY_WORD_SIZE + lmac_msg_len;
let aligned4 = self.align_to_4_bytes(raw_len);
self.align_to_block(aligned4)
}
fn wait_flow_control(&mut self) -> Result<(), SdioError> {
let mut fc_retry = 0u32;
loop {
let fc_reg = self.sdio_host.read_byte(1, self.flow_ctrl_reg())?;
if fc_reg & SDIOWIFI_FLOWCTRL_MASK != 0 {
return Ok(());
}
fc_retry += 1;
if fc_retry > FLOW_CONTROL_MAX_RETRY {
log::error!("IPC: flow control timeout, last fc_reg=0x{:02x}", fc_reg);
return Err(SdioError::Timeout);
}
crate::runtime::runtime().sleep_ms(1);
}
}
fn write_to_fifo(&mut self, send_len: usize) -> Result<(), SdioError> {
self.sdio_host
.write_fifo(1, self.wr_fifo_addr(), &self.tx_buf[..send_len])?;
Ok(())
}
fn wait_sdio_stable(&self) {
crate::runtime::runtime().sleep_ms(2);
}
fn handle_fifo_read_error(&mut self, read_err_cnt: u32, msg_id: u16) -> Result<(), SdioError> {
log::warn!(
"IPC: read_fifo error ({}/{}) for msg_id=0x{:04x}",
read_err_cnt,
FIFO_READ_MAX_RETRY,
msg_id
);
if read_err_cnt > FIFO_READ_MAX_RETRY {
log::error!("IPC: too many read errors for msg_id=0x{:04x}", msg_id);
return Err(SdioError::CrcError);
}
self.wait_sdio_stable();
Ok(())
}
fn poll_block_count(&mut self) -> Result<Option<usize>, SdioError> {
let raw_cnt = self.sdio_host.read_byte(1, self.block_cnt_reg())?;
if raw_cnt & SDIO_OTHER_INTERRUPT_FLAG != 0 {
log::warn!("IPC: SDIO_OTHER_INTERRUPT set, raw_cnt=0x{:02x}", raw_cnt);
return Ok(None);
} else if raw_cnt > 0 {
let block_cnt = raw_cnt as usize;
let read_len = (block_cnt * SDIO_BLOCK_SIZE).min(MSG_BUF_MAX);
return Ok(Some(read_len));
}
Ok(None)
}
fn read_response_fifo(&mut self, read_len: usize) -> Result<(), SdioError> {
self.sdio_host
.read_fifo(1, self.rd_fifo_addr(), &mut self.rx_buf[..read_len])?;
Ok(())
}
fn validate_response_id(&self, expected_id: u16) -> Result<(), SdioError> {
let resp_id = u16::from_le_bytes([self.rx_buf[4], self.rx_buf[5]]);
if resp_id != expected_id {
log::error!(
"IPC: unexpected response id=0x{:04x}, expected=0x{:04x}",
resp_id,
expected_id
);
return Err(SdioError::CrcError);
}
Ok(())
}
fn extract_response_payload(&self, cfm_buf: &mut [u8], read_len: usize) -> usize {
let payload_offset = RESPONSE_PAYLOAD_OFFSET;
let cfm_len = cfm_buf.len().min(read_len.saturating_sub(payload_offset));
if cfm_len > 0 {
cfm_buf[..cfm_len]
.copy_from_slice(&self.rx_buf[payload_offset..payload_offset + cfm_len]);
}
cfm_len
}
fn wait_for_response(&mut self, msg_id: u16, cfm_buf: &mut [u8]) -> Result<usize, SdioError> {
let mut retry = 0u32;
let mut read_err_cnt = 0u32;
let expected_id = msg_id + 1;
loop {
match self.poll_block_count() {
Ok(Some(read_len)) => match self.read_response_fifo(read_len) {
Ok(()) => {
self.validate_response_id(expected_id)?;
let cfm_len = self.extract_response_payload(cfm_buf, read_len);
return Ok(cfm_len);
}
Err(e) => {
log::warn!("IPC: read_response_fifo error: {:?}", e);
read_err_cnt += 1;
self.handle_fifo_read_error(read_err_cnt, msg_id)?;
continue;
}
},
Ok(None) => {
retry += 1;
if retry > RESPONSE_MAX_RETRY {
log::error!("IPC: response timeout for msg_id=0x{:04x}", msg_id);
return Err(SdioError::Timeout);
}
crate::runtime::runtime().sleep_ms(1);
}
Err(e) => {
log::warn!("IPC: poll_block_count error: {:?}", e);
return Err(e);
}
}
}
}
pub fn send_msg(
&mut self,
msg_id: DbgMsgId,
payload: &[u8],
wait_cfm: bool,
cfm_buf: &mut [u8],
) -> Result<usize, SdioError> {
let id = msg_id.msg_id();
let send_len = self.build_msg(id, payload);
self.wait_flow_control()?;
self.write_to_fifo(send_len)?;
if !wait_cfm {
return Ok(0);
}
self.wait_for_response(id, cfm_buf)
}
}
pub fn ipc_mem_read<H: SdioHost>(
transport: &mut IpcTransport<H>,
addr: u32,
) -> Result<u32, SdioError> {
let payload = addr.to_le_bytes(); let mut cfm = [0u8; 8];
transport.send_msg(DbgMsgId::MemReadReq, &payload, true, &mut cfm)?;
let data = u32::from_le_bytes([cfm[4], cfm[5], cfm[6], cfm[7]]); Ok(data)
}
pub fn ipc_mem_write<H: SdioHost>(
transport: &mut IpcTransport<H>,
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(DbgMsgId::MemWriteReq, &payload, true, &mut cfm)?;
Ok(())
}
pub fn ipc_mem_block_write<H: SdioHost>(
transport: &mut IpcTransport<H>,
addr: u32,
data: &[u8],
) -> Result<(), SdioError> {
assert!(data.len() <= 1024, "block write max 1024 bytes");
let payload_len = 4 + 4 + data.len(); let mut payload = [0u8; 1032]; payload[..4].copy_from_slice(&addr.to_le_bytes()); payload[4..8].copy_from_slice(&(data.len() as u32).to_le_bytes()); payload[8..8 + data.len()].copy_from_slice(data); let mut cfm = [0u8; 4];
transport.send_msg(
DbgMsgId::MemBlockWriteReq,
&payload[..payload_len],
true,
&mut cfm,
)?;
Ok(())
}
pub fn ipc_mem_mask_write<H: SdioHost>(
transport: &mut IpcTransport<H>,
addr: u32,
mask: u32,
data: u32,
) -> Result<(), SdioError> {
let mut payload = [0u8; 12];
payload[0..4].copy_from_slice(&addr.to_le_bytes());
payload[4..8].copy_from_slice(&mask.to_le_bytes());
payload[8..12].copy_from_slice(&data.to_le_bytes());
let mut cfm = [0u8; 8];
transport.send_msg(DbgMsgId::MemMaskWriteReq, &payload, true, &mut cfm)?;
Ok(())
}
pub fn ipc_start_app<H: SdioHost>(
transport: &mut IpcTransport<H>,
boot_addr: u32,
boot_type: u32,
) -> Result<u32, SdioError> {
let mut payload = [0u8; 8];
payload[..4].copy_from_slice(&boot_addr.to_le_bytes()); payload[4..].copy_from_slice(&boot_type.to_le_bytes()); let mut cfm = [0u8; 4];
transport.send_msg(DbgMsgId::StartAppReq, &payload, true, &mut cfm)?;
let boot_status = u32::from_le_bytes([cfm[0], cfm[1], cfm[2], cfm[3]]);
Ok(boot_status)
}