修复IWDG超时未配置与dhara resume回滚:看门狗误复位与复位后写入全部丢失,台架全量自检PASS

This commit is contained in:
2026-10-02 20:48:30 +08:00
parent d1a2eaab07
commit 930215f81a
33 changed files with 1875 additions and 94 deletions
+13 -6
View File
@@ -159,6 +159,7 @@ static int send_file(int socket, const char *path)
int read_len;
while ((read_len = lftpd_io_read(fh, buffer, sizeof(buffer))) > 0) {
lftpd_task_wd_beat(); /* 看门狗:每分片喂心跳(文件传输分片处,RETR) */
unsigned char *p = buffer;
while (read_len > 0) {
int write_len = lftpd_inet_write(socket, p, read_len);
@@ -189,6 +190,7 @@ static int receive_file(int socket, const char *path)
int total = 0;
while ((err = lftpd_inet_read(socket, buffer, sizeof(buffer))) > 0) {
lftpd_task_wd_beat(); /* 看门狗:每分片喂心跳(文件传输分片处,STOR) */
total += err;
if (lftpd_io_write(fh, buffer, err) != err) {
err = -1;
@@ -204,11 +206,8 @@ static int receive_file(int socket, const char *path)
lftpd_io_unlink(path);
return err;
}
if (total == 0) {
lftpd_log_error("received 0 bytes");
lftpd_io_unlink(path);
return -1;
}
/* EOF 正常结束:total==0 是合法空文件上传(如以空内容 STOR 清台账),
* 回报 226 成功(2026-10-02 台架:0 字节 STOR 曾被误判故障并删文件) */
return 0;
}
@@ -642,12 +641,20 @@ int lftpd_start(const char *directory, int port, lftpd_t *lftpd)
lftpd->server_socket = -1; /* memset 清零后显式置"无监听器"(0 是合法 fd) */
while (true) {
lftpd_task_wd_beat(); /* 看门狗:事件循环每轮喂心跳(ftpTask 主循环节拍点) */
if (lftpd->server_socket < 0) {
/* 首次启动 / 网络重建后:全量创建监听器(socket+bind+listen) */
lftpd->server_socket = lftpd_inet_listen(port);
if (lftpd->server_socket < 0) {
lftpd_log_error("error creating listener");
vTaskDelay(pdMS_TO_TICKS(3000));
/* 看门狗:3s 重试等待拆短片喂心跳(单次 3s 挂起超 IWDG 最坏窗
* ~1.7s → 误复位;与 time_sync.c 监听重试同模式) */
uint32_t wd_left = 3000U;
while (wd_left >= LFTPD_WAIT_SLICE_MS) {
lftpd_task_wd_beat();
vTaskDelay(pdMS_TO_TICKS(LFTPD_WAIT_SLICE_MS));
wd_left -= LFTPD_WAIT_SLICE_MS;
}
continue;
}
}
+10
View File
@@ -17,3 +17,13 @@ typedef struct {
int lftpd_start(const char *directory, int port, lftpd_t *lftpd);
int lftpd_stop(lftpd_t *lftpd);
/*
* 看门狗心跳钩子(弱符号,默认空实现见 lftpd_inet.c):
* lftpd 事件循环与长等待点周期调用,为 ftpTask 喂任务心跳;
* 应用层(App/util/task_watchdog.c)提供强符号路由到
* task_wd_beat(TASK_WD_FTP),驱动层因此不向上依赖应用层
* (分层铁律);未接线的构建(如模拟器)空实现生效、行为不变。
* 2026-10-02 台架:FTP 心跳缺失致 IWDG 复位循环,本钩子为修复的一部分。
*/
void lftpd_task_wd_beat(void);
+24
View File
@@ -19,11 +19,25 @@
*/
#define LFTPD_TOTAL_BUDGET_MS 30000U
/*
* 看门狗心跳钩子弱默认实现:空(未接应用层的构建——如模拟器——不喂心跳,
* 看门狗为 MCU 构建专属概念)。MCU 构建下 App/util/task_watchdog.c 提供
* 同名强符号(路由到 task_wd_beat(TASK_WD_FTP)),链接器取强符号
* (与 wd_platform_* 钩子同机制)。
*/
#if defined(__CC_ARM)
__weak
#endif
void lftpd_task_wd_beat(void)
{
}
int lftpd_inet_listen(int port)
{
int retry;
for (retry = 0; retry < 5; retry++) {
lftpd_task_wd_beat(); /* 看门狗:每轮喂心跳(重试间隔 500ms 在 800ms 容差内) */
int s = net_socket(NET_AF_INET, NET_SOCK_STREAM);
if (s < 0) {
if (retry == 0) DBG_ERROR("create socket");
@@ -84,6 +98,7 @@ int lftpd_inet_accept(int socket)
DBG_ERROR("network re-init: abort accept, relisten");
return -1;
}
lftpd_task_wd_beat(); /* 看门狗:自旋轮次喂心跳(无限等待,可能长时间无客户端) */
vTaskDelay(pdMS_TO_TICKS(100));
}
}
@@ -103,6 +118,7 @@ int lftpd_inet_accept_timeout(int socket, int timeout_ms)
DBG_ERROR("data socket closed, accept aborted");
return -1;
}
lftpd_task_wd_beat(); /* 看门狗:每轮喂心跳(数据连接等待最长 5s) */
vTaskDelay(pdMS_TO_TICKS(100));
elapsed += 100;
}
@@ -127,6 +143,10 @@ int lftpd_inet_read_line(int socket, char *buffer, size_t buffer_len)
if (rem == 0U) {
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
}
lftpd_task_wd_beat(); /* 看门狗:每片前喂心跳(长等待无数据期保持节奏) */
if (rem > LFTPD_WAIT_SLICE_MS) {
rem = LFTPD_WAIT_SLICE_MS; /* 分片:单次 30s 挂起拆为 ≤200ms 片 */
}
int read_len = net_recv_to(socket,
buffer + total_read_len,
(int)(buffer_len - total_read_len - 1),
@@ -208,6 +228,10 @@ int lftpd_inet_read(int socket, void *buf, int len)
if (rem == 0U) {
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
}
lftpd_task_wd_beat(); /* 看门狗:每片前喂心跳(长等待无数据期保持节奏) */
if (rem > LFTPD_WAIT_SLICE_MS) {
rem = LFTPD_WAIT_SLICE_MS; /* 分片:单次 30s 挂起拆为 ≤200ms 片 */
}
int n = net_recv_to(socket, buf, len, rem);
if (n >= 0) {
return n; /* >0 数据 / 0 EOF */
+35 -1
View File
@@ -7,6 +7,8 @@
#include "ff.h"
#include "private/lftpd_io.h"
#include "lftpd.h"
#include "vol_lock.h"
#define DBG_TAG "[LFT_IO]"
#include "dbg_log.h"
@@ -32,6 +34,24 @@ static const char *to_fatfs_path(const char *path)
static int s_file_open = 0;
static FIL s_ftp_file;
/*
* 卷锁获取(分片阻塞 + 心跳):FatFs/FTL 卷状态非线程安全,文件操作序列
* 必须与 defaultTask 的 syslog/配置盘写互斥(2026-10-02 台架实证:并发
* FTL 写 → dhara journal 竞争 → 长耗时恢复旋转饥饿 defaultTask 主循环
* (看门狗喂狗点)→ IWDG 误复位)。等待按 100ms 分片、每片置 FTP 心跳位
* (与就绪屏障等待同模式),防长等待造成 FTP 心跳位缺席误复位。
* 持有约定:open...close / opendir...closedir 全程持锁;lftpd_io_read/
* write 假定锁已由 open 持有;stat/unlink 自行短持。
*/
static void lftpd_io_vol_lock_take(void) {
for (;;) {
if (vol_lock_wait(100u) != 0) {
return;
}
lftpd_task_wd_beat(); /* 看门狗:分片心跳(卷锁等待中) */
}
}
char *lftpd_io_canonicalize_path(const char *base, const char *name)
{
if (base == NULL) {
@@ -100,10 +120,12 @@ int lftpd_io_open_read(const char *path)
DBG_ERROR("open_read '%s': already open", path);
return -1;
}
lftpd_io_vol_lock_take();
const char *fpath = to_fatfs_path(path);
FRESULT fr = f_open(&s_ftp_file, fpath, FA_READ);
if (fr != FR_OK) {
DBG_ERROR("open_read '%s': f_open err=%d", fpath, (int)fr);
vol_lock_release();
return -1;
}
s_file_open = 1;
@@ -116,10 +138,12 @@ int lftpd_io_open_write(const char *path)
DBG_ERROR("open_write '%s': already open", path);
return -1;
}
lftpd_io_vol_lock_take();
const char *fpath = to_fatfs_path(path);
FRESULT fr = f_open(&s_ftp_file, fpath, FA_WRITE | FA_CREATE_ALWAYS);
if (fr != FR_OK) {
DBG_ERROR("open_write '%s': f_open err=%d", fpath, (int)fr);
vol_lock_release();
return -1;
}
s_file_open = 1;
@@ -155,6 +179,7 @@ void lftpd_io_close(int handle)
if (s_file_open) {
f_close(&s_ftp_file);
s_file_open = 0;
vol_lock_release(); /* 卷锁:open...close 序列结束 */
}
}
@@ -163,7 +188,9 @@ int lftpd_io_stat(const char *path, uint32_t *p_size, int *p_is_dir)
FILINFO fno;
const char *fpath = to_fatfs_path(path);
lftpd_io_vol_lock_take();
if (f_stat(fpath, &fno) != FR_OK) {
vol_lock_release();
return -1;
}
@@ -174,15 +201,19 @@ int lftpd_io_stat(const char *path, uint32_t *p_size, int *p_is_dir)
*p_is_dir = (fno.fattrib & AM_DIR) ? 1 : 0;
}
vol_lock_release();
return 0;
}
int lftpd_io_unlink(const char *path)
{
const char *fpath = to_fatfs_path(path);
lftpd_io_vol_lock_take();
if (f_unlink(fpath) == FR_OK) {
vol_lock_release();
return 0;
}
vol_lock_release();
return -1;
}
@@ -193,11 +224,13 @@ void *lftpd_io_opendir(const char *path)
return NULL;
}
const char *fpath = to_fatfs_path(path);
lftpd_io_vol_lock_take();
if (f_opendir(dp, fpath) != FR_OK) {
vPortFree(dp);
vol_lock_release();
return NULL;
}
return (void *)dp;
return (void *)dp; /* 卷锁持有至 lftpd_io_closedir */
}
int lftpd_io_readdir(void *dp, char *name, int name_max, uint32_t *p_size, int *p_is_dir)
@@ -233,5 +266,6 @@ void lftpd_io_closedir(void *dp)
if (dp != NULL) {
f_closedir((DIR *)dp);
vPortFree(dp);
vol_lock_release(); /* 卷锁:opendir...closedir 序列结束 */
}
}
@@ -3,6 +3,15 @@
#include <stdlib.h>
#include <stdint.h>
/*
* 看门狗分片等待:单次 net_recv_to 等待片长上限。须显著小于看门狗抽样窗
* (100ms)的容错窗口(8 窗 = 800ms),200ms 留足余量;语义上仅把一次长
* 等待拆成多次短等待,总预算不变(与 time_sync.c 同款,见看门狗设计说明
* §4.4 长耗时白名单)。2026-10-02 台架:FTP 心跳缺失 + 30s 不分片等待 →
* IWDG 复位循环,本分片为修复的一部分。
*/
#define LFTPD_WAIT_SLICE_MS 200U
int lftpd_inet_listen(int port);
int lftpd_inet_get_socket_port(int socket);
int lftpd_inet_accept(int socket);