From ed59aaebf6a7709e5966a4fb8da72f99d7bfb4f3 Mon Sep 17 00:00:00 2001 From: OnlyoutzZ <17393117531@163.com> Date: Mon, 14 Sep 2026 16:39:53 +0800 Subject: [PATCH] [bsp][n32] n32hxxx: bound the XSPI waits and fix I2C/SPI/NAND edge cases - qspi: both XSPI variants poll the status flags with no bound, so an unresponsive bus hangs the caller and silently defeats the timeout it passed in. Route every wait through the timeout-carrying helpers, return -RT_ETIMEOUT and disable the controller on failure, and replace the "poll then send" loops with one bounded wait per element. - hard_i2c: the interrupt receive path programs the BYTENUM window once and never reloads it, so a longer message was truncated and still reported as success; refuse it with -RT_EINVAL (enable RX DMA for longer reads) instead of capping XferSize. Stop capping the transmit byte count at the window size as well: BYTENUM counts received bytes only, and the cap stopped feeding DAT after byte 255 while TXDATE stayed asserted. Reject RT_I2C_NO_START/RT_I2C_NO_STOP on the series without frame chaining, where they would silently become a fresh START and a STOP, and tear the DMA channel down when the address phase times out instead of leaving it armed against a buffer about to unwind. - spi: the H7xx LLI chain advanced the memory side by SPI_DMA_BLOCK_MAX bytes although BlkTfrSize counts elements, so a 16-bit transfer walked half a word early per node; advance by elements and scale both the chunk offset and the staging buffer by the data width. Propagate the DMA arm failure that was ignored. - nand: an odd data length has no halfword-only encoding on the 16-bit bus and padding would program a byte the caller never asked for, so refuse it before the command phase; issue the empty-page dummy store as a halfword there too, where 8-bit AHB writes are unsupported. - Kconfig: gate the Ethernet/SDRAM/LCD/QSPI/NAND options behind BSP_PERIPH_PIN_CFG_READY on all three boards. The peripheral clock and IO are not configured for these boards yet, so selecting one of them builds a driver whose pins stay in the reset function. --- .../N32_Drivers/drivers/drv_hard_i2c.c | 123 +++++++++-- .../libraries/N32_Drivers/drivers/drv_nand.c | 29 ++- .../libraries/N32_Drivers/drivers/drv_qspi.c | 209 ++++++++++++++---- .../libraries/N32_Drivers/drivers/drv_spi.c | 50 ++++- .../libraries/N32_Drivers/drivers/drv_spi.h | 2 +- bsp/n32/n32hxxx/n32h487zgl7-evb/board/Kconfig | 10 + bsp/n32/n32hxxx/n32h497zgl7-evb/board/Kconfig | 11 + bsp/n32/n32hxxx/n32h760zil7-stb/board/Kconfig | 12 + 8 files changed, 358 insertions(+), 88 deletions(-) diff --git a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c index e5f88e87a5..ec106e4d7e 100644 --- a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c +++ b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_hard_i2c.c @@ -626,6 +626,24 @@ static rt_err_t n32_i2c_master_seq_receive_it(struct n32_i2c *i2c, uint16_t DevA if (i2c->transfer.state == I2C_READY) { + /* A master receive fits in a single BYTENUM window: the counter is + * loaded once below and this path never reloads it, so the BSF + * completion ends the transfer after that many bytes. A longer message + * would be silently truncated yet still reported as success; refuse it + * instead. BYTENUM is 8 bits (the SDK's own setter takes a uint8_t), so + * the count cannot be widened either. Only the DMA path reloads the + * window as it drains. + * + * n32_i2c_master_xfer screens the same condition before dispatching, so + * the caller gets an errno; this check keeps the invariant stated where + * the window is actually programmed. + */ + if (Size > MAX_NBYTE_SIZE) + { + LOG_E("I2C IT receive of %u bytes exceeds the %u-byte BYTENUM window, enable RX DMA for longer reads", (unsigned int)Size, (unsigned int)MAX_NBYTE_SIZE); + return -RT_EINVAL; + } + /* Set transfer parameters */ i2c->transfer.state = I2C_BUSY_RX; i2c->transfer.pBuffPtr = pData; @@ -633,16 +651,8 @@ static rt_err_t n32_i2c_master_seq_receive_it(struct n32_i2c *i2c, uint16_t DevA i2c->transfer.XferOptions = XferOptions; i2c->i2c_isr_callback = i2c_master_ev_isr_handler_it; - /* If Size > MAX_NBYTE_SIZE, use reload mode */ - if (Size > MAX_NBYTE_SIZE) - { - i2c->transfer.XferSize = MAX_NBYTE_SIZE; - } - else - { - i2c->transfer.XferSize = i2c->transfer.XferCount; - } - + /* One window covers the whole transfer - see the size check above */ + i2c->transfer.XferSize = i2c->transfer.XferCount; #if defined(SOC_SERIES_N32H49x) I2C_EnableByteNum(i2c->config->Instance, ENABLE); @@ -769,15 +779,14 @@ static rt_err_t n32_i2c_master_seq_send_it(struct n32_i2c *i2c, uint16_t DevAddr i2c->transfer.XferOptions = XferOptions; i2c->i2c_isr_callback = i2c_master_ev_isr_handler_it; - /* If Size > MAX_NBYTE_SIZE, use reload mode */ - if (Size > MAX_NBYTE_SIZE) - { - i2c->transfer.XferSize = MAX_NBYTE_SIZE; - } - else - { - i2c->transfer.XferSize = i2c->transfer.XferCount; - } + /* No 255-byte window here: BYTENUM counts received bytes only, and this + * ISR feeds DAT one byte per TXDATE interrupt, so XferSize is nothing + * more than the remaining byte count. Capping it at MAX_NBYTE_SIZE made + * the TXDATE branch stop feeding after the 255th byte of a longer + * message - the tail was never sent while TXDATE stayed asserted and + * stormed this handler until the caller timed out. + */ + i2c->transfer.XferSize = i2c->transfer.XferCount; /* Wait for the previous STOP to complete. Toggling PE here would not * reset the state machine while BUSY is set, and could hold the lines. @@ -904,7 +913,14 @@ static rt_err_t n32_i2c_master_seq_receive_dma(struct n32_i2c *i2c, uint16_t Dev rt_err_t start_ret = n32_i2c_master_start_addr(i2c, (uint8_t)DevAddress, I2C_DIRECTION_RECV); if (start_ret != RT_EOK) { - i2c->transfer.state = I2C_READY; + /* The DMA request and the BUF/ERR interrupts are armed by + * now and the DMA channel still targets the caller's + * buffer, so resetting only the state would let a late + * byte write through a stack frame that is about to + * unwind. The timeout paths of n32_i2c_master_start_addr + * raise no error interrupt that could tear this down, so + * do it here. */ + I2C_ABORT_ON_TIMEOUT(i2c); return start_ret; } } @@ -1010,7 +1026,14 @@ static rt_err_t n32_i2c_master_seq_send_dma(struct n32_i2c *i2c, uint16_t DevAdd rt_err_t start_ret = n32_i2c_master_start_addr(i2c, (uint8_t)DevAddress, I2C_DIRECTION_SEND); if (start_ret != RT_EOK) { - i2c->transfer.state = I2C_READY; + /* The DMA request and the BUF/ERR interrupts are armed by + * now and the DMA channel still targets the caller's + * buffer, so resetting only the state would let a late + * byte write through a stack frame that is about to + * unwind. The timeout paths of n32_i2c_master_start_addr + * raise no error interrupt that could tear this down, so + * do it here. */ + I2C_ABORT_ON_TIMEOUT(i2c); return start_ret; } } @@ -1075,6 +1098,51 @@ static rt_ssize_t n32_i2c_master_xfer(struct rt_i2c_bus_device *bus, i2c_obj = rt_container_of(bus, struct n32_i2c, i2c_bus); completion = &i2c_obj->completion; +#if defined(SOC_SERIES_N32H49x) || defined(SOC_SERIES_N32H47x_48x) + /* This series cannot chain frames, so neither flag can be honoured. The + * mode constants the translate step below derives from them are all + * 0x00000000U on this series (see the definitions at the top of this file), + * so XferOptions is always zero here and changes nothing in the CTRL2 + * write. Every message instead re-runs the START + 7-bit address sequence + * in its own per-message setup, and completion always ends in a STOP + * (STOPGEN in i2c_it_completion_done, or the BYTENUM auto-stop on the DMA + * receive). A caller asking for RT_I2C_NO_START or RT_I2C_NO_STOP would + * therefore silently get a fresh START and a STOP instead of a repeated + * start -- a different bus transaction than the one requested, which some + * slaves reject. Refuse it rather than change it behind the caller's back; + * a repeated-start sequence needs the frame chaining this controller + * lacks. + */ + for (i = 0; i < num; i++) + { + if (msgs[i].flags & (RT_I2C_NO_START | RT_I2C_NO_STOP)) + { + LOG_E("I2C: RT_I2C_NO_START/RT_I2C_NO_STOP are not supported on this series (no frame chaining), msg[%d] flags=0x%x", i, msgs[i].flags); + return -RT_ENOSYS; + } + + /* A receive that the interrupt path has to serve fits in one BYTENUM + * window, and that path never reloads the counter. Screen it here as + * well as in the receive setup below, because a rejection raised from + * inside the transfer is reported as a message count rather than an + * errno (see "out:"), so the caller would only see a silent short + * read. The test mirrors this function's own DMA dispatch: the + * interrupt path is taken when RX DMA is off or the message is too + * short for it. + */ + if ((msgs[i].flags & RT_I2C_RD) && (msgs[i].len > MAX_NBYTE_SIZE)) + { + rt_bool_t rx_dma_ready = (i2c_obj->i2c_dma_flag & I2C_USING_RX_DMA_FLAG) ? RT_TRUE : RT_FALSE; + + if ((rx_dma_ready != RT_TRUE) || (msgs[i].len < DMA_TRANS_MIN_LEN)) + { + LOG_E("I2C IT receive of %u bytes exceeds the %u-byte BYTENUM window, enable RX DMA for longer reads", (unsigned int)msgs[i].len, (unsigned int)MAX_NBYTE_SIZE); + return -RT_EINVAL; + } + } + } +#endif + LOG_D("xfer start %d mags", num); for (i = 0; i < (num - 1); i++) { @@ -1890,12 +1958,23 @@ static void i2c_master_ev_isr_handler_it(struct n32_i2c *drv_i2c) } } - /* Byte Sequence Finished - independent check, handles completion when XferCount == 0 */ + /* Byte Sequence Finished - only reachable with XferCount == 0, so completing + * unconditionally is correct: the RX setup refuses sizes beyond the single + * BYTENUM window it programs, and a master transmit is not windowed at all - + * its last byte is fed to DAT exactly when XferCount reaches zero. A nonzero + * XferCount here means the two setups above drifted out of sync, which is + * worth a trace rather than silently truncating the message. + */ if ((itflags & I2C_STS1_BSF) && (itsources & I2C_CTRL2_BUFINTEN)) { /* Clear BSF flag */ I2C_ClrIntPendingBit(drv_i2c->config->Instance, I2C_INT_BSF); + if (drv_i2c->transfer.XferCount != 0U) + { + LOG_W("I2C BSF with %u bytes still queued", (unsigned int)drv_i2c->transfer.XferCount); + } + /* Transfer complete - BYTENUM expired (RX) or all bytes sent (TX) */ i2c_it_completion_done(drv_i2c); } diff --git a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_nand.c b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_nand.c index 9b2c781a11..2f7d0cfbb5 100644 --- a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_nand.c +++ b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_nand.c @@ -662,6 +662,20 @@ static rt_err_t _write_page(struct rt_mtd_nand_device *device, uint32_t i; rt_err_t ret; +#ifdef BSP_USING_NAND_BUS_WIDTH_16B + /* 16-bit NAND has no 8-bit store (see the data phase below), so an odd + * data_len has no halfword-only encoding: its trailing byte could only + * leave as an 8-bit write, and padding would program a byte the caller + * never asked for. Refuse it before the command phase, so the chip is + * never left in the middle of a program sequence. + */ + if (data_len & 1U) + { + LOG_E("page %d: 16-bit NAND needs an even data length, got %u", (int)page, (unsigned int)data_len); + return -RT_EINVAL; + } +#endif + /* Command phase: WRITE_1ST + column(2x 0) + row(NAND_ROW_ADDR_CYCLES) */ *(__IO uint8_t *)(bank | NAND_CMD_AREA) = NAND_CMD_WRITE_1ST; *(__IO uint8_t *)(bank | NAND_ADDR_AREA) = 0x00; @@ -686,7 +700,8 @@ static rt_err_t _write_page(struct rt_mtd_nand_device *device, * 16-bit NAND: 8-bit AHB writes are NOT supported (see FEMC manual * "supported memories and operations"), so data must be written as * 16-bit accesses. If the source buffer is not 16-bit aligned, copy - * it into an aligned temporary buffer first. + * it into an aligned temporary buffer first. An odd data_len was + * refused at the top of this function. */ if (((uint32_t)data & 0x1U) == 0U) { @@ -716,11 +731,6 @@ static rt_err_t _write_page(struct rt_mtd_nand_device *device, } rt_free(tmp); } - /* odd trailing byte */ - if (data_len & 1) - { - *(__IO uint8_t *)(bank | NAND_DATA_AREA) = data[data_len - 1]; - } #else for (i = 0; i < data_len; i++) { @@ -730,7 +740,14 @@ static rt_err_t _write_page(struct rt_mtd_nand_device *device, } else { + /* No payload: one store still has to be issued so the data phase clocks + * out. 16-bit NAND rejects 8-bit AHB writes (see the note above), so the + * dummy element must be halfword-sized on that bus. */ +#ifdef BSP_USING_NAND_BUS_WIDTH_16B + *(__IO uint16_t *)(bank | NAND_DATA_AREA) = 0x0000; +#else *(__IO uint8_t *)(bank | NAND_DATA_AREA) = 0x00; +#endif } /* spare area write not implemented yet */ diff --git a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_qspi.c b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_qspi.c index f8b5d12019..7b2b6e03db 100644 --- a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_qspi.c +++ b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_qspi.c @@ -45,18 +45,6 @@ static rt_err_t xspi_wait_flag(XSPI_Module *xspi, uint32_t flag, uint32_t timeou return RT_EOK; } -static void xspi_wait_tx_complete(XSPI_Module *xspi) -{ - while (XSPI_GetFlagStatus(xspi, XSPI_TXFE_FLAG) != SET) - { - } - - while (XSPI_GetFlagStatus(xspi, XSPI_BUSY_FLAG) != RESET) - { - } -} - - static rt_err_t xspi_wait_busy(XSPI_Module *xspi, uint32_t timeout) { uint32_t tickstart = rt_tick_get(); @@ -73,6 +61,24 @@ static rt_err_t xspi_wait_busy(XSPI_Module *xspi, uint32_t timeout) return RT_EOK; } +/* TX FIFO drained + transfer finished. Both waits carry the caller's timeout: + * an unresponsive bus (TXFE that never sets after a failed arm, or a slave + * that stretches the clock and never releases BUSY) must not spin here + * forever, because that would silently defeat the timeout its caller was + * given. */ +static rt_err_t xspi_wait_tx_complete(XSPI_Module *xspi, uint32_t timeout) +{ + rt_err_t result; + + result = xspi_wait_flag(xspi, XSPI_TXFE_FLAG, timeout); + if (result != RT_EOK) + { + return result; + } + + return xspi_wait_busy(xspi, timeout); +} + /* ---- multi-line helpers ---- */ static uint32_t xspi_get_transfer_type(uint8_t instr_lines, uint8_t addr_lines) @@ -215,12 +221,15 @@ static rt_err_t xspi_qspi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mes XSPI_SendData(xspi, qspi_msg->instruction.content); XSPI_SendData(xspi, qspi_msg->address.content); - while (i < len) + for (i = 0; i < len; i++) { - if (XSPI_GetFlagStatus(xspi, XSPI_TXFNF_FLAG) == SET) + if (xspi_wait_flag(xspi, XSPI_TXFNF_FLAG, timeout) != RT_EOK) { - XSPI_SendData(xspi, buf[i++]); + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; } + + XSPI_SendData(xspi, buf[i]); } ret = xspi_wait_busy(xspi, timeout); @@ -270,8 +279,13 @@ static rt_err_t xspi_qspi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_mess } - while (XSPI_GetFlagStatus(xspi, XSPI_BUSY_FLAG) != RESET) + /* Bound the post-transfer wait like the receive loop above: a slave that + * stretches the clock and never releases BUSY must not spin here forever, + * defeating the timeout the caller was given. */ + if (xspi_wait_busy(xspi, timeout) != RT_EOK) { + ret = -RT_ETIMEOUT; + goto exit; } XSPI_ClearRxFIFO(xspi); @@ -305,7 +319,10 @@ static rt_err_t xspi_spi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mess XSPI_SetNumberOfDataFrame(xspi, qspi_msg->parent.length); - xspi_wait_tx_complete(xspi); + if (xspi_wait_tx_complete(xspi, timeout) != RT_EOK) + { + return -RT_ETIMEOUT; + } XSPI_Enable(xspi, ENABLE); @@ -324,17 +341,24 @@ static rt_err_t xspi_spi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mess if (qspi_msg->parent.length <= 16) { - while (i < qspi_msg->parent.length) + for (i = 0; i < qspi_msg->parent.length; i++) { - if (XSPI_GetFlagStatus(xspi, XSPI_TXFNF_FLAG) != RESET) - { - uint8_t tx = send_buf ? send_buf[i++] : 0xFF; + uint8_t tx = send_buf ? send_buf[i] : 0xFF; - XSPI_SendData(xspi, tx); + if (xspi_wait_flag(xspi, XSPI_TXFNF_FLAG, timeout) != RT_EOK) + { + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; } + + XSPI_SendData(xspi, tx); } - xspi_wait_tx_complete(xspi); + if (xspi_wait_tx_complete(xspi, timeout) != RT_EOK) + { + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; + } if (recv_buf) { @@ -359,6 +383,7 @@ static rt_err_t xspi_spi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mess if (xspi_wait_flag(xspi, XSPI_TXFNF_FLAG, timeout) != RT_EOK) { + XSPI_Enable(xspi, DISABLE); return -RT_ETIMEOUT; } @@ -368,6 +393,7 @@ static rt_err_t xspi_spi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mess { if (xspi_wait_flag(xspi, XSPI_RXFNE_FLAG, timeout) != RT_EOK) { + XSPI_Enable(xspi, DISABLE); return -RT_ETIMEOUT; } @@ -408,7 +434,11 @@ static rt_err_t xspi_spi_send(struct n32_xspi *xspi_drv, struct rt_qspi_message XSPI_Enable(xspi, ENABLE); - xspi_wait_tx_complete(xspi); + if (xspi_wait_tx_complete(xspi, timeout) != RT_EOK) + { + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; + } XSPI_ClearRxFIFO(xspi); @@ -432,15 +462,22 @@ static rt_err_t xspi_spi_send(struct n32_xspi *xspi_drv, struct rt_qspi_message XSPI_SendData(xspi, qspi_msg->address.content & 0xff); - while (number < qspi_msg->parent.length) + for (number = 0; number < qspi_msg->parent.length; number++) { - if (XSPI_GetFlagStatus(xspi, XSPI_TXFNF_FLAG) != RESET) + if (xspi_wait_flag(xspi, XSPI_TXFNF_FLAG, timeout) != RT_EOK) { - XSPI_SendData(xspi, buf[number++]); + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; } + + XSPI_SendData(xspi, buf[number]); } - xspi_wait_tx_complete(xspi); + if (xspi_wait_tx_complete(xspi, timeout) != RT_EOK) + { + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; + } XSPI_Enable(xspi, DISABLE); @@ -494,7 +531,12 @@ static rt_err_t xspi_spi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_messa while (i < qspi_msg->parent.length + 4) /*Tx Fifo not full*/ { - while ((xspi->STS & XSPI_TXFNF_FLAG) != XSPI_TXFNF_FLAG); /*wait tx FIFO not full flag set*/ + /* Wait for TX FIFO not full, bounded: the write below assumes room. */ + if (xspi_wait_flag(xspi, XSPI_TXFNF_FLAG, timeout) != RT_EOK) + { + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; + } if (i < 4) { @@ -522,6 +564,12 @@ static rt_err_t xspi_spi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_messa i++; } + /* Drain the RX FIFO until the transfer finishes. The reads must stay on + * every iteration -- the waits in this file stop as soon as a flag + * clears, which here would strand the last bytes in the FIFO -- so only + * the exit condition is bounded. */ + uint32_t tickstart = rt_tick_get(); + do { if ((xspi->STS & XSPI_RXFNE_FLAG)) /*Rx Fifo not empty set*/ @@ -529,6 +577,11 @@ static rt_err_t xspi_spi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_messa *(buf++) = xspi->DAT0; /*read data register*/ } + if (((rt_tick_get() - tickstart) >= timeout) && (timeout != 0xFFFFFFFFU)) + { + XSPI_Enable(xspi, DISABLE); + return -RT_ETIMEOUT; + } } while ((xspi->STS & XSPI_BUSY_FLAG) == SET); XSPI_Enable(xspi, DISABLE); @@ -636,17 +689,6 @@ static rt_err_t xspi_wait_flag(uint32_t flag, uint32_t timeout) return RT_EOK; } -static void xspi_wait_tx_complete(void) -{ - while (XSPI_GetFlagStatus(XSPI_STS_TXFE) != SET) /* TX FIFO empty */ - { - } - - while (XSPI_GetFlagStatus(XSPI_STS_BUSY) != RESET) /* transfer done */ - { - } -} - static rt_err_t xspi_wait_busy(uint32_t timeout) { uint32_t tickstart = rt_tick_get(); @@ -662,6 +704,24 @@ static rt_err_t xspi_wait_busy(uint32_t timeout) return RT_EOK; } +/* TX FIFO drained + transfer finished. Both waits carry the caller's timeout: + * an unresponsive bus (TXFE that never sets after a failed arm, or a slave + * that stretches the clock and never releases BUSY) must not spin here + * forever, because that would silently defeat the timeout its caller was + * given. */ +static rt_err_t xspi_wait_tx_complete(uint32_t timeout) +{ + rt_err_t result; + + result = xspi_wait_flag(XSPI_STS_TXFE, timeout); + if (result != RT_EOK) + { + return result; + } + + return xspi_wait_busy(timeout); +} + /* data line width -> SPIFRF frame format */ static uint32_t xspi_get_spifrf(uint8_t lines) { @@ -733,6 +793,11 @@ static rt_err_t xspi_spi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mess } XSPI_Cmd(ENABLE); + /* The sends below stay non-blocking on purpose: a blocking wait would stop + * draining the RX FIFO, which the comment inside warns would overflow on + * large full-duplex transfers. Only the loop's exit is bounded. */ + uint32_t tickstart = rt_tick_get(); + while (i < len) { if (XSPI_GetFlagStatus(XSPI_STS_TXFNF) == SET) @@ -753,9 +818,19 @@ static rt_err_t xspi_spi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mess (void)XSPI_ReceiveData(); } } + + if (((rt_tick_get() - tickstart) >= timeout) && (timeout != 0xFFFFFFFFU)) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } } - xspi_wait_tx_complete(); + if (xspi_wait_tx_complete(timeout) != RT_EOK) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } if (recv_buf) { @@ -798,6 +873,10 @@ static rt_err_t xspi_spi_send(struct n32_xspi *xspi_drv, struct rt_qspi_message s_data[2] = (qspi_msg->address.content & 0xff00) >> 8; s_data[3] = qspi_msg->address.content & 0xff; + /* Non-blocking sends + lockstep RX discard; only the exit is bounded (a + * blocking wait would stall the discard and let the RX FIFO overflow). */ + uint32_t tickstart = rt_tick_get(); + while (i < len + 4) { if (XSPI_GetFlagStatus(XSPI_STS_TXFNF) == SET) @@ -810,6 +889,12 @@ static rt_err_t xspi_spi_send(struct n32_xspi *xspi_drv, struct rt_qspi_message { (void)XSPI_ReceiveData(); /* discard received bytes */ } + + if (((rt_tick_get() - tickstart) >= timeout) && (timeout != 0xFFFFFFFFU)) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } } /* drain the remaining received bytes */ @@ -818,7 +903,11 @@ static rt_err_t xspi_spi_send(struct n32_xspi *xspi_drv, struct rt_qspi_message (void)XSPI_ReceiveData(); } - xspi_wait_tx_complete(); + if (xspi_wait_tx_complete(timeout) != RT_EOK) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } XSPI_Cmd(DISABLE); return RT_EOK; } @@ -852,6 +941,9 @@ static rt_err_t xspi_spi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_messa s_data[2] = (qspi_msg->address.content & 0xff00) >> 8; s_data[3] = qspi_msg->address.content & 0xff; + /* Non-blocking sends + lockstep RX collect; only the exits are bounded. */ + uint32_t tickstart = rt_tick_get(); + while (i < len + 4) { if (XSPI_GetFlagStatus(XSPI_STS_TXFNF) == SET) @@ -869,6 +961,12 @@ static rt_err_t xspi_spi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_messa } j++; } + + if (((rt_tick_get() - tickstart) >= timeout) && (timeout != 0xFFFFFFFFU)) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } } /* drain the remaining received bytes (len + 4 in total) */ @@ -883,6 +981,12 @@ static rt_err_t xspi_spi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_messa } j++; } + + if (((rt_tick_get() - tickstart) >= timeout) && (timeout != 0xFFFFFFFFU)) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } } XSPI_Cmd(DISABLE); @@ -931,15 +1035,22 @@ static rt_err_t xspi_qspi_transmit(struct n32_xspi *xspi_drv, struct rt_qspi_mes XSPI_SendData(qspi_msg->instruction.content); XSPI_SendData(qspi_msg->address.content); - while (i < len) + for (i = 0; i < len; i++) { - if (XSPI_GetFlagStatus(XSPI_STS_TXFNF) == SET) + if (xspi_wait_flag(XSPI_STS_TXFNF, timeout) != RT_EOK) { - XSPI_SendData(buf[i++]); + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; } + + XSPI_SendData(buf[i]); } - xspi_wait_tx_complete(); + if (xspi_wait_tx_complete(timeout) != RT_EOK) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } XSPI_Cmd(DISABLE); return RT_EOK; } @@ -969,7 +1080,11 @@ static rt_err_t xspi_qspi_receive(struct n32_xspi *xspi_drv, struct rt_qspi_mess buf[i] = (uint8_t)XSPI_ReceiveData(); } - xspi_wait_tx_complete(); + if (xspi_wait_tx_complete(timeout) != RT_EOK) + { + XSPI_Cmd(DISABLE); + return -RT_ETIMEOUT; + } XSPI_Cmd(DISABLE); return RT_EOK; } diff --git a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.c b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.c index 734d45fbc3..893436a328 100644 --- a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.c +++ b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.c @@ -104,9 +104,10 @@ N32_SPI_DUMMY_ALIGN32 static rt_uint8_t spi_fd_rxonly_dummy[8192]; #if defined(SOC_SERIES_N32H7xx) -/* Build an LLI chain for a 'size'-byte transfer: the memory side advances - * 4095B per node while the peripheral (DAT) address stays fixed. Only the - * last node has IntEn=1, so the whole chain raises a single TC interrupt. */ +/* Build an LLI chain for a 'size'-element transfer: the memory side advances + * SPI_DMA_BLOCK_MAX elements per node while the peripheral (DAT) address + * stays fixed. Only the last node has IntEn=1, so the whole chain raises a + * single TC interrupt. */ static rt_uint16_t n32_spi_lli_build(DMA_LinkListItemType *lli, const DMA_ChInitType *ch, rt_uint32_t periph_addr, rt_uint32_t mem_addr, rt_bool_t mem_is_src, rt_uint16_t size) @@ -121,8 +122,17 @@ static rt_uint16_t n32_spi_lli_build(DMA_LinkListItemType *lli, const DMA_ChInit rt_uint16_t blk = (rest > SPI_DMA_BLOCK_MAX) ? SPI_DMA_BLOCK_MAX : rest; rt_bool_t has_next = (i + 1U < n) ? RT_TRUE : RT_FALSE; - node->SrcAddr = mem_is_src ? (mem_addr + (rt_uint32_t)i * SPI_DMA_BLOCK_MAX) : periph_addr; - node->DstAddr = mem_is_src ? periph_addr : (mem_addr + (rt_uint32_t)i * SPI_DMA_BLOCK_MAX); + /* BlkTfrSize counts data items, so the memory side advances one block + * worth of ELEMENTS per node: the byte stride is SPI_DMA_BLOCK_MAX + * items times the memory-side transfer width. That width field is the + * DMA transfer width code (0 = 8-bit, 1 = 16-bit, 2 = 32-bit), i.e. + * 1 << width bytes -- the very value programmed into the node below, + * so stride and width cannot disagree. Advancing by SPI_DMA_BLOCK_MAX + * BYTES instead overlaps the nodes by half a word at 16-bit width. */ + rt_uint32_t mem_stride = (rt_uint32_t)SPI_DMA_BLOCK_MAX * + (1UL << (mem_is_src ? ch->SrcTfrWidth : ch->DstTfrWidth)); + node->SrcAddr = mem_is_src ? (mem_addr + (rt_uint32_t)i * mem_stride) : periph_addr; + node->DstAddr = mem_is_src ? periph_addr : (mem_addr + (rt_uint32_t)i * mem_stride); node->pNext = has_next ? &lli[i + 1U] : RT_NULL; node->IntEn = has_next ? 0U : 1U; node->DstTfrWidth = ch->DstTfrWidth; @@ -596,7 +606,10 @@ static rt_err_t SPI_DMA_Transmit(struct n32_spi *spi_drv, uint8_t *pData, uint16 { #if defined(SOC_SERIES_N32H7xx) /* SPI TX DMA send (single block or seamless LLI chain) */ - n32_spi_dma_arm(spi_drv, RT_FALSE, RT_TRUE, pData, Size); + if (n32_spi_dma_arm(spi_drv, RT_FALSE, RT_TRUE, pData, Size) != RT_EOK) + { + return -RT_ERROR; + } #elif defined(SOC_SERIES_N32H49x) || defined(SOC_SERIES_N32H47x_48x) /* SPI TX DMA Send Data for H49X */ DMA_EnableChannel(spi_drv->config->dma_tx->DMAChx, DISABLE); @@ -697,7 +710,10 @@ static rt_err_t SPI_DMA_Receive(struct n32_spi *spi_drv, uint8_t *pData, uint16_ { #if defined(SOC_SERIES_N32H7xx) /* SPI RX DMA receive (single block or seamless LLI chain) */ - n32_spi_dma_arm(spi_drv, RT_TRUE, RT_TRUE, pData, Size); + if (n32_spi_dma_arm(spi_drv, RT_TRUE, RT_TRUE, pData, Size) != RT_EOK) + { + return -RT_ERROR; + } #elif defined(SOC_SERIES_N32H49x) || defined(SOC_SERIES_N32H47x_48x) /* SPI RX DMA Receive Data for H49X */ DMA_EnableChannel(spi_drv->config->dma_rx->DMAChx, DISABLE); @@ -1840,14 +1856,19 @@ static rt_ssize_t spixfer(struct rt_spi_device *device, struct rt_spi_message *m * grid (see the warm re-arm note). Slaves never chunk below * SPI_DMA_CHAIN_MAX and ignore the flag anyway. */ spi_drv->fd_chunk_cont = (fd_msg && (already_send_length != 0U)) ? RT_TRUE : RT_FALSE; - /* avoid null pointer problems */ + /* avoid null pointer problems. + * already_send_length counts elements while the buffers are byte + * addressed, so this chunk's offset must be scaled by the data width + * (16-bit data = 2 bytes per element). Without it every chunk after + * the first starts one byte early per element -- half the real offset + * -- and the DMA walks the wrong half of the buffer. */ if (message->send_buf) { - send_buf = (rt_uint8_t *)message->send_buf + already_send_length; + send_buf = (rt_uint8_t *)message->send_buf + (already_send_length * (spi_drv->cfg->data_width / 8u)); } if (message->recv_buf) { - recv_buf = (rt_uint8_t *)message->recv_buf + already_send_length; + recv_buf = (rt_uint8_t *)message->recv_buf + (already_send_length * (spi_drv->cfg->data_width / 8u)); } rt_uint32_t *dma_aligned_buffer = RT_NULL; /* TX staging buffer (copy path only) */ @@ -1917,6 +1938,11 @@ static rt_ssize_t spixfer(struct rt_spi_device *device, struct rt_spi_message *m rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, p_tx_buffer, send_bytes); } #else + /* send_length counts elements: the staging buffer must be sized and + * filled in bytes (x2 for 16-bit data). Sized in elements the + * halfword DMA reads twice as far as the allocation and the copy + * only carries half of the payload. */ + rt_uint32_t send_bytes = send_length * (spi_drv->cfg->data_width / 8u); if (RT_IS_ALIGN((rt_uint32_t)send_buf, 4) && send_buf != RT_NULL) /* aligned with 4 bytes? */ { p_tx_buffer = (rt_uint32_t *)send_buf; /* send_buf aligns with 4 bytes, no more operations */ @@ -1924,14 +1950,14 @@ static rt_ssize_t spixfer(struct rt_spi_device *device, struct rt_spi_message *m else { /* send_buf doesn't align with 4 bytes, so creat a cache buffer with 4 bytes aligned */ - dma_aligned_buffer = (rt_uint32_t *)rt_malloc(send_length); /* aligned with RT_ALIGN_SIZE (8 bytes by default) */ + dma_aligned_buffer = (rt_uint32_t *)rt_malloc(send_bytes); /* aligned with RT_ALIGN_SIZE (8 bytes by default) */ if (dma_aligned_buffer == RT_NULL) { LOG_E("SPI DMA TX buffer malloc failed!"); state = -RT_ENOMEM; goto spi_staging_free; } - rt_memcpy(dma_aligned_buffer, send_buf, send_length); + rt_memcpy(dma_aligned_buffer, send_buf, send_bytes); p_tx_buffer = dma_aligned_buffer; } #endif diff --git a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.h b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.h index c018bc76c0..5e726fe4e4 100644 --- a/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.h +++ b/bsp/n32/n32hxxx/libraries/N32_Drivers/drivers/drv_spi.h @@ -48,7 +48,7 @@ struct n32_spi_device #define SPI_USING_RX_DMA_FLAG (1 << 0) #define SPI_USING_TX_DMA_FLAG (1 << 1) -/* Max bytes of a single DMA block (12-bit BlkTfrSize) */ +/* Max elements (data items) of a single DMA block (12-bit BlkTfrSize) */ #define SPI_DMA_BLOCK_MAX (4095U) /* Number of LLI chain nodes per DMA leg (sizes lli_tx/lli_rx) */ #define SPI_DMA_CHAIN_NODES (16U) diff --git a/bsp/n32/n32hxxx/n32h487zgl7-evb/board/Kconfig b/bsp/n32/n32hxxx/n32h487zgl7-evb/board/Kconfig index 5a884858f3..b291cb34f3 100644 --- a/bsp/n32/n32hxxx/n32h487zgl7-evb/board/Kconfig +++ b/bsp/n32/n32hxxx/n32h487zgl7-evb/board/Kconfig @@ -1,10 +1,18 @@ menu "Hardware Drivers Config" + config BSP_PERIPH_PIN_CFG_READY + bool "Optional peripheral AF pins are configured on this board" + default n + help + The peripheral clock and IO must be configured in + board/Cube_Config/USER/src/n32h47x_48x_cfg.c. + menu "Onboard Peripheral Drivers" menuconfig BSP_USING_ETH bool "Enable Ethernet" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_LWIP if BSP_USING_ETH choice BSP_ETH_INTERFACE_SEL @@ -231,6 +239,7 @@ menu "On-chip Peripheral Drivers" menuconfig BSP_USING_QSPI bool "Enable QSPI (XSPI) BUS" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_QSPI select RT_USING_SPI @@ -683,6 +692,7 @@ menu "On-chip Peripheral Drivers" menuconfig BSP_USING_NAND bool "Enable Nand" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_MTD_NAND if BSP_USING_NAND choice BSP_NAND_BANK_SEL diff --git a/bsp/n32/n32hxxx/n32h497zgl7-evb/board/Kconfig b/bsp/n32/n32hxxx/n32h497zgl7-evb/board/Kconfig index aa5e67a533..7be0abd4ec 100644 --- a/bsp/n32/n32hxxx/n32h497zgl7-evb/board/Kconfig +++ b/bsp/n32/n32hxxx/n32h497zgl7-evb/board/Kconfig @@ -1,10 +1,18 @@ menu "Hardware Drivers Config" + config BSP_PERIPH_PIN_CFG_READY + bool "Optional peripheral AF pins are configured on this board" + default n + help + The peripheral clock and IO must be configured in + board/Cube_Config/USER/src/n32h49x_cfg.c. + menu "Onboard Peripheral Drivers" menuconfig BSP_USING_ETH bool "Enable Ethernet" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_LWIP if BSP_USING_ETH choice BSP_ETH_INTERFACE_SEL @@ -48,6 +56,7 @@ menu "Onboard Peripheral Drivers" menuconfig BSP_USING_SDRAM bool "Enable SDRAM" default n + depends on BSP_PERIPH_PIN_CFG_READY if BSP_USING_SDRAM choice BSP_SDRAM_BANK_SEL prompt "Select SDRAM Bank" @@ -340,6 +349,7 @@ menu "On-chip Peripheral Drivers" menuconfig BSP_USING_QSPI bool "Enable QSPI (XSPI) BUS" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_QSPI select RT_USING_SPI @@ -764,6 +774,7 @@ menu "On-chip Peripheral Drivers" menuconfig BSP_USING_NAND bool "Enable Nand" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_MTD_NAND if BSP_USING_NAND choice BSP_NAND_BANK_SEL diff --git a/bsp/n32/n32hxxx/n32h760zil7-stb/board/Kconfig b/bsp/n32/n32hxxx/n32h760zil7-stb/board/Kconfig index 2a76fec34b..a67a40229c 100644 --- a/bsp/n32/n32hxxx/n32h760zil7-stb/board/Kconfig +++ b/bsp/n32/n32hxxx/n32h760zil7-stb/board/Kconfig @@ -1,10 +1,18 @@ menu "Hardware Drivers Config" + config BSP_PERIPH_PIN_CFG_READY + bool "Optional peripheral AF pins are configured on this board" + default n + help + The peripheral clock and IO must be configured in + board/Cube_Config/USER/src/n32h7xx_cfg.c. + menu "Onboard Peripheral Drivers" menuconfig BSP_USING_SDRAM bool "Enable SDRAM" default n + depends on BSP_PERIPH_PIN_CFG_READY if BSP_USING_SDRAM choice BSP_SDRAM_BANK_SEL prompt "Select SDRAM Bank" @@ -85,6 +93,7 @@ menu "Onboard Peripheral Drivers" menuconfig BSP_USING_LCD bool "Enable LCD" default n + depends on BSP_PERIPH_PIN_CFG_READY select BSP_USING_SDRAM if BSP_USING_LCD config BSP_BITS_PER_PIXEL @@ -175,6 +184,7 @@ menu "Onboard Peripheral Drivers" menuconfig BSP_USING_ETH bool "Enable Ethernet" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_LWIP if BSP_USING_ETH choice BSP_ETH_SEL @@ -650,6 +660,7 @@ menu "On-chip Peripheral Drivers" menuconfig BSP_USING_QSPI bool "Enable QSPI Quad SPI BUS" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_QSPI select RT_USING_SPI if BSP_USING_QSPI @@ -1933,6 +1944,7 @@ menu "On-chip Peripheral Drivers" menuconfig BSP_USING_NAND bool "Enable Nand" default n + depends on BSP_PERIPH_PIN_CFG_READY select RT_USING_MTD_NAND if BSP_USING_NAND choice BSP_NAND_BANK_SEL