From db928a6ce3afdaaa279d210b83c175f9347d46aa Mon Sep 17 00:00:00 2001 From: lhenry-realtek <176550561+lhenry-realtek@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:26:45 +0800 Subject: [PATCH] os/arch/arm/src/amebasmart: Fix UART data loss when wakeup from PG Issue is encountered where bytes are lost on UART wakeup source during resume from sleep Test Requirements: Please ensure PR #6482 is applied before testing Analysis: - UART FIFO is started but not drained during resume. When the peripheral is initialized again, it causes the FIFO to also be cleared and cause data loss - Similarly if more than 64 bytes is passed in together, since there is no mechanism to drain the FIFO into RAM it will cause LSR overrun error This fix drains the FIFO first at KM4, then again at CA32 until it fully wake, then normal Tizen ISR handler resume operation - The drained buffer is also passed to application layer - Watchdog is used to monitor UART1 until the FIFO is completely drained --- .../arm/src/amebasmart/amebasmart_serial.c | 181 +++++++++++++++++- .../soc/amebad2/fwlib/include/ameba_ipc.h | 4 +- 2 files changed, 178 insertions(+), 7 deletions(-) diff --git a/os/arch/arm/src/amebasmart/amebasmart_serial.c b/os/arch/arm/src/amebasmart/amebasmart_serial.c index f79ca4d87b..1cbc5d9a2b 100644 --- a/os/arch/arm/src/amebasmart/amebasmart_serial.c +++ b/os/arch/arm/src/amebasmart/amebasmart_serial.c @@ -69,6 +69,7 @@ #include #ifdef CONFIG_PM #include +#include #endif #ifdef CONFIG_SERIAL_TERMIOS @@ -512,6 +513,16 @@ static uart_dev_t g_uart4port = { }; #endif +#ifdef CONFIG_PM +/* FIFO Drain buffer for UART PG wakeup */ +static ALIGNMTO(CACHE_LINE_SIZE) u8 g_uart1_buf[256] = { 0 }; +static u32 g_uart1_buf_head = &g_uart1_buf; +static u32 g_uart1_dataleft = 0; + +/* keep uart active when TX/RX interrupt is still firing */ +#define UART_MONITOR_WD_MS 1 +static WDOG_ID uart_timer_wd; +#endif /**************************************************************************** * Private Functions ****************************************************************************/ @@ -574,6 +585,9 @@ static int rtl8730e_log_uart_irq(void *Data) u32 txempty_en = LOGUART_GET_ETPFEI(IrqEn); if ((txempty_en == 0x4 && (reg_lsr & LOGUART_BIT_TP4F_EMPTY)) || (reg_lsr & LOGUART_BIT_TP4F_NOT_FULL)) { uart_xmitchars(&CONSOLE_DEV); + } else { + /* Workaround: Further investigation required to identify where Tx IRQ was disabled */ + LOGUART_INTConfig(LOGUART_DEV, LOGUART_TX_EMPTY_PATH_4_INTR, ENABLE); } return 0; } @@ -1053,8 +1067,32 @@ static int rtl8730e_up_receive(struct uart_dev_s *dev, unsigned int*status) uint32_t rxd; DEBUGASSERT(priv); - rxd = serial_getc(sdrv[uart_index_get(priv->tx)]); - *status = rxd; + + /* if there is still data in the FIFO drain buffer, read from there, otherwise read from peripheral */ + if (g_uart1_dataleft > 0) { + rxd = *((u8 *)g_uart1_buf_head); + g_uart1_buf_head++; + g_uart1_dataleft--; + + /* prevent g_uart1_buf_head from overflow */ + if (((u32)g_uart1_buf_head - (u32)g_uart1_buf) > sizeof(g_uart1_buf)) { + DiagPrintf("Head pointer exceed buffer size!\n"); + g_uart1_buf_head--; + } + + /* prevent g_uart1_dataleft from underflow */ + if (g_uart1_dataleft == 0xFFFFFFFF) { + g_uart1_dataleft = 0; + } + } else { + /* force reset buf head if out of sync */ + if (g_uart1_buf_head != &g_uart1_buf) { + g_uart1_buf_head = &g_uart1_buf; + } + /* read from FIFO */ + rxd = serial_getc(sdrv[uart_index_get(priv->tx)]); + *status = rxd; + } return rxd & 0xff; } @@ -1074,6 +1112,21 @@ static void rtl8730e_up_rxint(struct uart_dev_s *dev, bool enable) serial_irq_set(sdrv[uart_index_get(priv->tx)], RxIrq, enable); // 1= ENABLE } +#ifdef CONFIG_PM +static void uart_timer_timeout(int argc, int uart_id) +{ + /* for now, only handle UART1 */ + if (uart_id != 1) { + return; + } + DEBUGASSERT(uart_timer_wd != NULL); + /* PM transition will be resume here */ + bsp_pm_domain_control(BSP_UART_DRV, 0); + (void)wd_delete(uart_timer_wd); + uart_timer_wd = NULL; +} +#endif + /**************************************************************************** * Name: up_rxavailable * @@ -1086,7 +1139,22 @@ static bool rtl8730e_up_rxavailable(struct uart_dev_s *dev) { struct rtl8730e_up_dev_s *priv = (struct rtl8730e_up_dev_s *)dev->priv; DEBUGASSERT(priv); - return (serial_readable(sdrv[uart_index_get(priv->tx)])); + + /* there is data available if either FIFO DRDY==1 or there is stuff in drain buffer */ + u8 fifo_hasdata = serial_readable(sdrv[uart_index_get(priv->tx)]); + u8 buf_hasdata = g_uart1_dataleft > 0; + u8 available = (fifo_hasdata || buf_hasdata); + +#ifdef CONFIG_PM + irqstate_t flags = enter_critical_section(); + /* if there is a wd, it means that we are still clearing fifo in wakeup. if there is data, restart the wd */ + if (uart_timer_wd && available) { + (void)wd_cancel(uart_timer_wd); + wd_start(uart_timer_wd, MSEC2TICK(UART_MONITOR_WD_MS), (wdentry_t)uart_timer_timeout, 1, uart_index_get(priv->tx)); + } + leave_critical_section(flags); +#endif + return available; } /**************************************************************************** @@ -1204,6 +1272,39 @@ static uint32_t rtk_uart_suspend(uint32_t expected_idle_time, void *param) { (void)expected_idle_time; (void)param; + + ALIGNMTO(CACHE_LINE_SIZE) u8 flag[CACHE_LINE_ALIGMENT(64)] = { 0 }; + ALIGNMTO(CACHE_LINE_SIZE) u32 uart_data[16] = { 0 }; + + /* clear rx fifo before going to sleep */ + serial_clear_rx(sdrv[uart_index_get(g_uart1priv.tx)]); + + IPC_MSG_STRUCT ipc_req_msg __attribute__((aligned(64))); + ipc_req_msg.msg_type = IPC_USER_POINT; + ipc_req_msg.msg = (u32)uart_data; + ipc_req_msg.msg_len = sizeof(uart_data); + ipc_req_msg.rsvd = (u32)flag; + + /* indicate CA32 is ready to rx, switch back to CA32 */ + uart_data[0] = g_uart1priv.tx; //tx + uart_data[1] = g_uart1priv.rx; //rx + uart_data[2] = uart_index_get(g_uart1priv.tx); //uart_idx + uart_data[3] = g_uart1priv.baud; //uart baudrate + uart_data[4] = g_uart1priv.parity; //parity + uart_data[5] = g_uart1priv.bits; //bits + uart_data[6] = g_uart1priv.stopbit; //stop bit + uart_data[7] = 1; //1 switch to KM4, 0 switch to CA32 + + DCache_Clean((u32)uart_data, sizeof(uart_data)); + ipc_send_message(IPC_AP_TO_NP, IPC_A2N_UART, &ipc_req_msg); + + while (1) { + DCache_Invalidate((u32)flag, sizeof(flag)); + if (flag[0]) { + break; + } + } + #ifdef CONFIG_RTL8730E_UART1 if (sdrv[uart_index_get(g_uart1priv.tx)] != NULL) { serial_change_clcksrc(sdrv[uart_index_get(g_uart1priv.tx)], g_uart1priv.baud, 0); @@ -1216,14 +1317,83 @@ static uint32_t rtk_uart_resume(uint32_t expected_idle_time, void *param) { (void)expected_idle_time; (void)param; + + ALIGNMTO(CACHE_LINE_SIZE) u8 flag[CACHE_LINE_ALIGMENT(64)] = { 0 }; + ALIGNMTO(CACHE_LINE_SIZE) u32 uart_data[16] = { 0 }; + + /* reset buffer and head pointer for FIFO drain buffer */ + g_uart1_dataleft = 0; + memset(g_uart1_buf, 0, sizeof(g_uart1_buf)); + g_uart1_buf_head = &g_uart1_buf; + + IPC_MSG_STRUCT ipc_req_msg __attribute__((aligned(64))); + ipc_req_msg.msg_type = IPC_USER_POINT; + ipc_req_msg.msg = (u32)uart_data; + ipc_req_msg.msg_len = sizeof(uart_data); + ipc_req_msg.rsvd = (u32)flag; + + /* indicate CA32 is ready to rx, switch back to CA32 */ + uart_data[2] = uart_index_get(g_uart1priv.tx); + uart_data[7] = 0; // 1 switch to KM4, 0 switch to CA32 + uart_data[10] = 0; // hold the length of km4 data + uart_data[11] = (u32)g_uart1_buf; // buffer to hold drained FIFO data + + /* prepare buffers and notify KM4 to begin resume process */ + DCache_Clean((u32)uart_data, sizeof(uart_data)); + DCache_Clean((u32)g_uart1_buf, sizeof(g_uart1_buf)); + ipc_send_message(IPC_AP_TO_NP, IPC_A2N_UART, &ipc_req_msg); + + /* wait for KM4 to finish the drain on its side */ + while (1) { + DCache_Invalidate((u32)flag, sizeof(flag)); + if (flag[0]) { + /* invalidate the cache to receive the drained FIFO data */ + DCache_Invalidate((u32)g_uart1_buf, sizeof(g_uart1_buf)); + DCache_Invalidate((u32)uart_data, sizeof(uart_data)); + + /* null terminate for safety */ + g_uart1_buf[uart_data[10]] = 0; + break; + } + } + + /* + * control has switched back from KM4 to CA32. + * KM4 has stopped reading the FIFO, so now we can drain it in CA32 + * no extra config on the peripheral should be done except detach attach irq as required + */ + u8 ch = 0; + g_uart1_dataleft = uart_data[10]; + UART_TypeDef* uartx = UART_DEV_TABLE[uart_index_get(g_uart1priv.tx)].UARTx; + + /* drain the remainder FIFO from CA32 side */ + while (UART_Readable(uartx) == 1) { + UART_CharGet(uartx, &ch); + g_uart1_buf[g_uart1_dataleft++] = ch; + } + + /* force clear Rx status (this is normally done with API, but poll mode require manual clearing */ + UART_INT_Clear(uartx, RUART_BIT_RLSICF); + #ifdef CONFIG_RTL8730E_UART1 if (sdrv[uart_index_get(g_uart1priv.tx)] != NULL) { serial_change_clcksrc(sdrv[uart_index_get(g_uart1priv.tx)], g_uart1priv.baud, 1); } #endif + + /* create a wd to monitor activity on UART immediately after wakeup */ + if (!uart_timer_wd) { + uart_timer_wd = wd_create(); + DEBUGASSERT(uart_timer_wd != NULL); + DEBUGASSERT(wd_start(uart_timer_wd, MSEC2TICK(UART_MONITOR_WD_MS), (wdentry_t)uart_timer_timeout, 1, uart_index_get(g_uart1priv.tx)) == OK); + + /* hold the PM lock to prevent transition, as FIFO is still draining in wakeup */ + bsp_pm_domain_control(BSP_UART_DRV, 1); + } + return 1; } -#endif +#endif /* CONFIG_PM */ /**************************************************************************** * Public Functions @@ -1315,10 +1485,9 @@ int up_lowgetc(void) { uint8_t rxd; #ifdef CONFIG_UART4_SERIAL_CONSOLE - u32 IrqEn = LOGUART_GetIMR(); LOGUART_SetIMR(0); rxd = LOGUART_GetChar(_FALSE); - LOGUART_SetIMR(IrqEn); + LOGUART_SetIMR(1); #else if (CONSOLE_DEV.isconsole == false) return; diff --git a/os/board/rtl8730e/src/component/soc/amebad2/fwlib/include/ameba_ipc.h b/os/board/rtl8730e/src/component/soc/amebad2/fwlib/include/ameba_ipc.h index 891d20bff4..805718bf9c 100644 --- a/os/board/rtl8730e/src/component/soc/amebad2/fwlib/include/ameba_ipc.h +++ b/os/board/rtl8730e/src/component/soc/amebad2/fwlib/include/ameba_ipc.h @@ -326,6 +326,7 @@ typedef struct _IPC_INIT_TABLE_ { #define IPC_A2L_UARTBRIDGE 2 #define IPC_A2L_DISLOGUART 3 #define IPC_A2L_WIFI_FW_INFO 4 /*!< AP --> LP Get stats info from WIFI FW */ +#define IPC_A2L_UART 5 /*! < AP --> NP UART data receive during PG*/ //#define IPC_A2L_Channel5 5 //#define IPC_A2L_Channel6 6 #define IPC_A2L_IMQ_TRX_TRAN 7 /*!< AP --> LP IMQ Message Exchange */ @@ -335,7 +336,8 @@ typedef struct _IPC_INIT_TABLE_ { #define IPC_A2N_FLASHPG_REQ 2 /*!< AP --> NP Flash Program Request*/ #define IPC_A2N_BT_API_TRAN 3 /*!< AP --> NP BT API Exchange */ #define IPC_A2N_BT_DRC_TRAN 4 /*!< AP --> NP BT DATA Message Exchange */ -#define IPC_A2N_802154_TRAN 5 +//#define IPC_A2N_802154_TRAN 5 +#define IPC_A2N_UART 5 /*! < AP --> NP UART data receive during PG*/ #define IPC_A2N_OTP_RX_TRAN 6 #define IPC_A2N_LOGUART_RX_SWITCH 7 /*!< AP --> NP Loguart Message Exchange for Linux*/ #define IPC_A2N_IMQ_TRX_TRAN 7 /*!< AP --> NP IMQ Message Exchange for RTOS*/