修复 P2 全部 10 项:错误传播/竞态/门控/文档失真

待办池 docs/待处理内容.md §3.2 的 10 个 P2 全部闭环;逐项落代码 + 注释说明。

P2-1  cfg_file: 受管键行超 CFG_LINE_TEST_MAX 拒绝保存(防重复键行)
P2-2  ch395f: DMA 失败传播状态 + 锁存通道故障 + 读不拷不可信数据
P2-7  net_select: LISTENING 不再恒判可读(消除 select 忙轮询地雷)
P2-8  time_sync: 核实 L3 pending 改写已闭环(net_recv_to 分片 + 总预算)
P2-9  sys_log: 挂载改幂等 + 卷锁,defaultTask 2s 重试建 /log
P2-10 sys_log: s_read_raw 纳入卷锁 try(读者/写者 unlink 互斥)
P2-11 net_task: RECONFIG 改 osEventFlagsWait 原子取走 + 清除
P2-12 app_cfg: APP_*_ENABLE 如实标注为未接线占位,删“开关”说法
P2-13 freertos: 产品构建 vTaskDelete(testTask) 回收 8KB CCM 栈
P2-15 download.bat: 先删旧日志 + 增“未产出日志”判定

P2-13 用 USER CODE 区回收而非生成区 #if 门控:生成区改动会被 CubeMX
重生成覆盖,本方案重生成不丢。P2-12 因产品定义 B0 未定、APP_NET_ENABLE
无单一接线点,按修复建议备选(删“开关”说法)处理。

验证:@build 0 错 0 警;主机单测 watchdog 39/39 + sys_clock 100/100 +
cfg_file 149/149(新增 TC-CFG-044);pre-commit --all-files 17/17。
This commit is contained in:
2026-10-04 08:30:30 +08:00
parent 13eeba944b
commit e0b02d397c
16 changed files with 332 additions and 93 deletions
+42 -22
View File
@@ -10,6 +10,9 @@
* 2026-07-18 王建锋 改进write_send_buf/read_recv_buf使用SPI2 DMA批量传输
* 2026-10-02 王建锋 E-34:3 处字节级 SPI 调用由 HAL_MAX_DELAY 改有限超时(100ms)
* + 返回值检查 + 故障锁存(沿程只记一条日志);原 SPI 异常时调用方永久挂死
* 2026-10-04 王建锋 P2-2:ch395f_spi_dma_transfer 由 void 改为返回状态(0 成功/-1 失败),
* DMA 初始化失败/超时中止均锁存通道故障;read_recv_buf 在 DMA 失败时不再把
* rx_buf 内容拷入调用方缓冲(改零填充),消除"失败当成功"的静默数据损坏
*/
/* 头文件包含区 - 先包含系统头文件,再包含自定义头文件 */
@@ -40,7 +43,7 @@
* 静态变量定义区 - DMA传输相关
*/
static volatile uint8_t s_spi2_dma_done = 0U; /* DMA传输完成标志 */
static uint8_t s_spi_byte_fault = 0U; /* E-34:字节级SPI故障锁存(超时/出错置位,MCU复位清除) */
static uint8_t s_spi_fault = 0U; /* E-34/P2-2:SPI 通道故障锁存(字节级或 DMA 级超时/出错置位,MCU复位清除) */
__attribute__((aligned(4)))
static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA发送缓冲区 */
@@ -52,8 +55,8 @@ static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA接收缓冲区 */
*/
static void ch395f_spi_begin(void);
static void ch395f_spi_end(void);
static void ch395f_spi_dma_transfer(uint16_t len);
static void ch395f_spi_byte_fault_set(void);
static int ch395f_spi_dma_transfer(uint16_t len);
static void ch395f_spi_fault_set(const char *kind);
/*
* 私有函数定义区
@@ -82,31 +85,34 @@ static void ch395f_spi_end(void) {
}
/*
* 函数功能:锁存一次字节级SPI故障(E-34:仅首条故障记日志)
* 入口参数:无
* 函数功能:锁存一次 SPI 通道故障(E-34/P2-2:仅首条故障记日志)
* 入口参数:kind - 故障类别标签("byte"/"dma-hal"/"dma-timeout") const char*
* 返回值:无
* 限定条件:无
* 函数说明:任一字节级SPI调用超时/失败时调用;置位 s_spi_byte_fault 并记一条日志,
* 函数说明:任一字节级或 DMA 级 SPI 调用超时/失败时调用;置位 s_spi_fault 并记一条日志,
* 后续故障静默(防串口刷屏);上层经 ch395f_spi_byte_fault() 查询
*/
static void ch395f_spi_byte_fault_set(void) {
if (s_spi_byte_fault == 0U) {
s_spi_byte_fault = 1U;
DBG_ERROR("SPI byte op fail, channel latched");
static void ch395f_spi_fault_set(const char *kind) {
if (s_spi_fault == 0U) {
s_spi_fault = 1U;
DBG_ERROR("SPI channel fault latched: %s", kind);
}
}
/*
* 函数功能:DMA全双工传输并等待完成
* 入口参数:len - 传输长度(字节) uint16_t 1 - CH395F_DMA_BUF_SIZE
* 返回值:0 - 传输完成且无错误 -1 - 传输失败(HAL 启动失败 / 超时中止)
* 限定条件:1. s_spi2_dma_tx_buf中已填充待发送数据
* 2. 需确保DMA传输缓冲区已对齐到4字节边界
* 函数说明:1. 发送s_spi2_dma_tx_buf内容,同时接收数据存入s_spi2_dma_rx_buf
* 2. 阻塞等待DMA完成回调置标志位
* 3. 超时(1秒)时强制中止SPI DMA并打印错误信息
* 4. 传输完成后检查SPI错误码并清除溢出标志
* 5. P2-2(2026-10-04):失败路径锁存通道故障并返回 -1,调用方据此
* 判定 rx_buf 内容不可信,不再"失败当成功"继续消费
*/
static void ch395f_spi_dma_transfer(uint16_t len) {
static int ch395f_spi_dma_transfer(uint16_t len) {
HAL_StatusTypeDef status;
uint32_t tickstart;
volatile uint32_t loop_cnt = 0U;
@@ -119,7 +125,8 @@ static void ch395f_spi_dma_transfer(uint16_t len) {
(int)hspi2.State,
hspi2.ErrorCode,
(int)hspi2.hdmarx->State);
return;
ch395f_spi_fault_set("dma-hal");
return -1;
}
tickstart = HAL_GetTick();
@@ -142,7 +149,8 @@ static void ch395f_spi_dma_transfer(uint16_t len) {
SPI2->CR1, SPI2->CR2, SPI2->SR);
/* 强制中止SPI2 DMA状态,避免死锁 */
HAL_SPI_Abort(&hspi2);
return;
ch395f_spi_fault_set("dma-timeout");
return -1;
}
}
@@ -152,6 +160,8 @@ static void ch395f_spi_dma_transfer(uint16_t len) {
hspi2.ErrorCode, (int)hspi2.State);
__HAL_SPI_CLEAR_OVRFLAG(&hspi2);
}
return 0;
}
/*
@@ -200,7 +210,7 @@ uint8_t ch395f_write_cmd(uint8_t cmd) {
/* 通过SPI发送命令并接收回显(E-34:有限超时+返回值检查;失败返回0xFF占位并锁存故障) */
if (HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1,
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
ch395f_spi_byte_fault_set();
ch395f_spi_fault_set("byte");
}
return rx_data;
}
@@ -217,7 +227,7 @@ uint8_t ch395f_write_data(uint8_t data) {
/* 通过SPI发送数据并接收回显(E-34:有限超时+返回值检查) */
if (HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1,
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
ch395f_spi_byte_fault_set();
ch395f_spi_fault_set("byte");
}
return rx_data;
}
@@ -235,20 +245,20 @@ uint8_t ch395f_read_data(void) {
/* 发送0xFF作为时钟源,读取芯片返回数据(E-34:有限超时+返回值检查) */
if (HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1,
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
ch395f_spi_byte_fault_set();
ch395f_spi_fault_set("byte");
}
return rx_data;
}
/*
* 函数功能:查询字节级SPI故障锁存(E-34)
* 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误
* 函数功能:查询 SPI 通道故障锁存(E-34 / P2-2)
* 返回值:0 - 正常 1 - 曾发生 SPI 超时/错误(字节级或 DMA 级)
* 限定条件:无
* 函数说明:锁存式(不自动清除,MCU 复位前一直有效);上层据此判定
* "SPI 通道异常"(区别于"无芯片"/"PHY 未链接");仅首条故障记日志
*/
uint8_t ch395f_spi_byte_fault(void) {
return s_spi_byte_fault;
return s_spi_fault;
}
/*
@@ -1173,9 +1183,10 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
/* 数据同步屏障:确保memcpy写入对DMA可见 */
__DSB();
/* CS低 → DMA批量传输 → CS高 */
/* CS低 → DMA批量传输 → CS高(P2-2:失败已由 transfer 锁存通道故障并记日志,
* 调用方经 ch395f_spi_byte_fault() 查询;此处无后续处理,失败即放弃本次发送) */
ch395f_spi_begin();
ch395f_spi_dma_transfer(total);
(void)ch395f_spi_dma_transfer(total);
ch395f_spi_end();
}
@@ -1193,6 +1204,7 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
*/
void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
uint16_t total;
int rc;
/* 检查输入参数合法性 */
if (p_data == NULL) {
@@ -1221,9 +1233,17 @@ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) {
/* CS低 → DMA全双工传输(发送命令头 + 接收数据) → CS高 */
ch395f_spi_begin();
ch395f_spi_dma_transfer(total);
rc = ch395f_spi_dma_transfer(total);
ch395f_spi_end();
/* P2-2(2026-10-04):DMA 失败时 rx_buf 内容不可信,禁止拷入调用方缓冲
* (否则把总线残留/半截数据当有效载荷 → 静默数据损坏);改零填充并返回,
* 调用方据 ch395f_spi_byte_fault() 判定通道异常。 */
if (rc != 0) {
memset(p_data, 0, len);
return;
}
/* 从DMA接收缓冲区偏移4处拷贝有效数据到用户缓冲区 */
memcpy(p_data, &s_spi2_dma_rx_buf[4], len);
}
+2 -2
View File
@@ -302,8 +302,8 @@ uint8_t ch395f_write_data(uint8_t data);
uint8_t ch395f_read_data(void);
/*
* 函数功能:查询字节级SPI故障锁存(E-34)
* 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误
* 函数功能:查询 SPI 通道故障锁存(E-34 / P2-2)
* 返回值:0 - 正常 1 - 曾发生 SPI 超时/错误(字节级或 DMA 级)
* 限定条件:无
* 函数说明:锁存式(MCU 复位前一直有效);上层据此判定"SPI 通道异常"
* (区别于"无芯片"/"PHY 未链接");仅首条故障记日志
+12 -7
View File
@@ -241,10 +241,16 @@ int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) {
* 函数功能:检查指定 Socket 当前是否可读
* 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1
* 出口参数:无
* 返回值:1 - 可读(监听态有新连接,或已连接态接收缓冲非空);0 - 不可读或描述符无效 int
* 返回值:1 - 可读(接收缓冲非空);0 - 不可读或描述符无效 int
* 限定条件:net_init() 已调用
* 函数说明:1. 监听态 Socket 视为可读(存在可 accept 的新连接)
* 2. 已建立连接且接收缓冲区长度大于 0 时视为可读
* 函数说明:1. P2-7(2026-10-04):LISTENING 态**不再**恒判可读。本网络层无
* "待 accept 的挂起连接队列"——新连接到达时 CONNECT 事件把该 socket
* 自身直接转为 ESTABLISHED(handle_connect_event:"监听自身转数据通道"),
* 故监听态恒无数据可读。原实现对 LISTENING 恒返回 1,会使
* select()/poll() 在监听 socket 上永久立即就绪(忙轮询地雷;当前无
* 调用方,属潜在地雷)。连接到达经 NET_EVENT_CONNECTED 回调通知。
* 2. 其余态:readable 由 net_poll 在 RECV_OK 时置位、net_recv_locked
* 读空后清零。避免应用任务直接访问 CH395F SPI(见 Trap 16)
*/
static int check_socket_readable(int sockfd) {
const net_sock_t *p_sock;
@@ -254,13 +260,12 @@ static int check_socket_readable(int sockfd) {
return 0;
}
/* 监听 Socket 有新连接时可读 */
/* 监听态恒不可读(见上方 P2-7 说明) */
if (NET_SOCK_STATE_LISTENING == p_sock->state) {
return 1;
return 0;
}
/* 已建立连接或 UDP 等有数据可读:readable 由 net_poll 在 RECV_OK 时置位、
* net_recv_locked 读空后清零。避免应用任务直接访问 CH395F SPI(见 Trap 16) */
/* 有数据可读 */
if (p_sock->readable) {
return 1;
}