diff --git a/.opencode/command/build.md b/.opencode/command/build.md new file mode 100644 index 0000000..757aa18 --- /dev/null +++ b/.opencode/command/build.md @@ -0,0 +1,14 @@ +--- +description: Build the STM32F4-Base Keil MDK project. Use ONLY when the user asks to build, compile, or verify the project. +--- + +Run the Keil MDK-ARM build from the project root: + +``` +cmd /c "cd /d "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM" && build.bat" +``` + +Check the last 10 lines of `MDK-ARM/build_log.txt` for errors/warnings. +Return code 0 = success (0 errors, 0 warnings). +Return code 1 = warnings-only (also failure per project rules). +Return code 2+ = compile errors. diff --git a/AGENTS.md b/AGENTS.md index e576e07..fe4a0d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,7 @@ # STM32F4-Base +> **⚠️ 编译验证:所有代码修改后必须执行 `@build` 验证编译通过(0 错误 0 警告),否则不要提交。** + ## 项目概述 STM32F407ZGTx (Cortex-M4 FPU) 基础固件项目,集成 CH395F 以太网控制器 SPI 驱动、GD5F2GQ5UE SPI NAND Flash 存储(含 FlashDB KVDB/TSDB 数据库)和 TPAFE5160 16位8通道同步采样 ADC 并行接口驱动。 @@ -58,33 +60,19 @@ STM32F4-Base/ 仅支持 Keil MDK-ARM v5 (ARMCC)。打开 `MDK-ARM/STM32F407-Demo.uvprojx` 编译。 -- 编译器:ARMCC V5.06 update 7 -- 优化等级:`-O4` (项目级),`spi.c`/`usart.c` / HAL 源文件使用 `-O0` -- C 标准:C99 -- 全局宏定义:`USE_HAL_DRIVER, STM32F407xx` +- 编译器:ARMCC V5.06 update 7,优化 `-O4`(`spi.c`/`usart.c`/HAL 源文件 `-O0`),C99,`USE_HAL_DRIVER, STM32F407xx` ### 命令行编译(Agent 使用) -修改代码后必须执行命令行编译验证,编译脚本位于 `MDK-ARM/build.bat`: +修改代码后必须执行命令行编译验证,**仅使用** `@build`,不得直接调用 UV4.exe: ``` -cmd /c "cd /d "工作目录" && build.bat" +@build ``` -实际执行示例(从项目根目录): +该命令定义在 `.opencode/command/build.md`,自动执行 `MDK-ARM/build.bat` 进行全量编译(`-r`)。 -``` -cmd /c "cd /d "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM" && build.bat" -``` - -**编译结果判读:** -- 退出码 0:无错误无警告,编译成功 -- 退出码 1:有警告无错误,编译失败(警告不可接受,需修复) -- 退出码 2+:编译失败,需查看 `MDK-ARM/build_log.txt` 定位错误 - -**UV4.exe 路径:** `C:\Keil_v5\UV4\UV4.exe` -**工程文件:** `MDK-ARM/STM32F407-Demo.uvprojx` -**目标名称:** `STM32F407-Demo` +**退出码:** 0 = 成功(0 Error, 0 Warning)| 1 = 有警告(同样不通过)| 2+ = 错误,需查看 `MDK-ARM/build_log.txt` 定位 ## 硬件配置 @@ -158,12 +146,17 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init() - 块擦除(D8h)参数是字节地址(块编号 × 128KB),不是块编号 - 读取 ID(9Fh)返回 3 字节,第 0 字节无意义,第 1 字节 MID,第 2 字节 DID -## FlashDB 分区规划 +## Flash 分区规划(256MB GD5F2GQ5UE) -| 分区名 | 偏移 | 大小 | 用途 | -|--------|------|------|------| -| fdb_kvdb1 | 0 | 64MB | KVDB 键值数据库 | -| fdb_tsdb1 | 64MB | 64MB | TSDB 时序数据库 | +| 分区名 | 偏移 | 大小 | 块范围 | 用途 | +|--------|------|------|--------|------| +| fdb_kvdb1 | 0 | 64MB | Block 0~511 | FlashDB KVDB 键值数据库 | +| fdb_tsdb1 | 64MB | 64MB | Block 512~1023 | FlashDB TSDB 时序数据库 | +| ftl_fatfs | 128MB | 128MB | Block 1024~2047 | dhara FTL + FatFS 文件系统 | + +- FlashDB 通过 FAL 层访问 `fdb_kvdb1` / `fdb_tsdb1`(`Drivers/BSP/GD5F2GQ5UE/fal_cfg.h`) +- FatFS 通过 dhara FTL 访问 `ftl_fatfs` 分区,`nand_ftl.c` 中 `FTL_START_BLOCK = FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE`(与 `fal_cfg.h` 联动) +- 三个分区物理隔离,互不重叠,分区配置统一在 `fal_cfg.h:25-30` ## TPAFE5160 驱动关键点 diff --git a/App/app_cfg.h b/App/app_cfg.h index 49060c4..1e2054e 100644 --- a/App/app_cfg.h +++ b/App/app_cfg.h @@ -22,12 +22,6 @@ extern "C" { */ #define APP_SYS_TIME_SRC 0 -/* - * 时间戳输出使能 - * 定义后在日志行首自动追加 [T ssssss.uuuuuu] 微秒时间戳 - */ -#define APP_TIMESTAMP_ENABLE - /* * 功能模块使能开关 */ diff --git a/App/app_main.c b/App/app_main.c index 3906956..70a414e 100644 --- a/App/app_main.c +++ b/App/app_main.c @@ -10,7 +10,6 @@ #include "app_main.h" #include "app_cfg.h" -#include "sys_time.h" #define DBG_TAG "[APP]" #include "dbg_log.h" @@ -24,10 +23,6 @@ */ int app_main_init(void) { - if (sys_time_init() != 0) { - DBG_ERROR("sys_time_init failed"); - return -1; - } DBG_INFO("App init OK"); return 0; } diff --git a/App/sys_clock.c b/App/sys_clock.c new file mode 100644 index 0000000..bfd55f0 --- /dev/null +++ b/App/sys_clock.c @@ -0,0 +1,199 @@ +/* + * 模块名称:System Clock — Wall Clock Time Maintenance + * 模块功能:实现系统墙钟时间维护,包括 RTC 初始读取、HAL Tick 漂移维持 + * 适用平台:STM32F407ZGTx (Cortex-M4 FPU, 168MHz) + * 作者:王建锋 + * 创建日期:2026-07-19 + * 修改记录: + * v1.0 2026-07-19 王建锋 创建初始版本 + */ + +#include "sys_clock.h" +#include "main.h" +#include +#include + +#define DBG_TAG "[CLK]" +#include "dbg_log.h" + +/* ============================================================ + * 内部状态:系统墙钟 + * ============================================================ */ + +static volatile uint32_t s_base_tick = 0; /* HAL_GetTick() 基准 */ +static volatile uint32_t s_base_unix_secs = UINT32_MAX; + +/* ============================================================ + * 内部辅助函数:Unix time ↔ sd2506_time_t 转换 + * ============================================================ */ + +static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time) +{ + uint32_t y, m, d; + uint8_t h, mi, s; + int is_leap; + + /* 时分秒 */ + { + uint64_t total = (uint64_t)unix_secs; + s = (uint8_t)(total % 60uLL); + total /= 60uLL; + mi = (uint8_t)(total % 60uLL); + total /= 60uLL; + h = (uint8_t)(total % 24uL); + } + + /* 年月日 */ + { + uint64_t day_count = ((uint64_t)unix_secs / 86400uL); + + y = 1970u; + while (day_count >= 365uLL) { + uint64_t diy = 365uLL; + if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) { + diy = 366uLL; + } + if (day_count < diy) break; + day_count -= diy; + y++; + } + + is_leap = ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)); + uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (is_leap) mdays[1] = 29; + + m = 0; + while (m < 12u) { + if (day_count < (uint64_t)mdays[m]) break; + day_count -= mdays[m]; + m++; + } + d = (uint32_t)(day_count + 1uL); + + /* 星期(Zeller's congruence,0=Sunday) */ + uint64_t wy = y; + uint64_t wm = m; + if (wm <= 2) { + wy--; + wm += 12uL; + } + uint8_t week = ((uint32_t)(wy % 100uLL + wy / 100uLL / 4uLL - wy / 100uLL / 15uLL) + + (uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) + + (uint32_t)(d % 100uLL)) % 7uLL; + + /* SD2506 year 字段为 0~99(+2000) */ + uint16_t sd_year = y >= 2000 ? (y - 2000) : (y + 2000); + + p_sd_time->year = sd_year; + p_sd_time->month = m + 1uL; /* 月份从 0 → 1 */ + p_sd_time->day = d; + p_sd_time->hour = h; + p_sd_time->minute = mi; + p_sd_time->second = s; + p_sd_time->week = week; + } +} + +/* ============================================================ + * 系统墙钟时间维护 + * ============================================================ */ + +int sys_clock_init(void) +{ + sd2506_time_t sd_time; + uint32_t year, month, day, hour, minute, second; + int64_t unix_secs = 0; + int i; + + /* Step 1: 从 SD2506 RTC 读取初始时间 */ + sd2506_get_time(&sd_time); + + year = (uint32_t)sd_time.year + 2000uL; + month = (uint32_t)sd_time.month; + day = (uint32_t)sd_time.day; + hour = (uint32_t)sd_time.hour; + minute = (uint32_t)sd_time.minute; + second = (uint32_t)sd_time.second; + + DBG_INFO("sys_clock_init: RTC initial time %04d-%02d-%02d %02d:%02d:%02d", + year, month, day, hour, minute, second); + + /* Step 2: Unix epoch → seconds(简化算法,适用于 1970~2038) */ + { + uint64_t total_days = 0; + + for (i = 1970; i < year; i++) { + if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) { + total_days += 366uLL; + } else { + total_days += 365uLL; + } + } + + int is_leap = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); + uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (is_leap) mdays[1] = 29; + + for (i = 0; i < month - 1; i++) { + total_days += mdays[i]; + } + total_days += day - 1uL; + + unix_secs = (int64_t)(total_days * 86400LL) + hour * 3600LL + minute * 60LL + second; + } + + /* Step 3: 设置系统墙钟基准 */ + s_base_tick = HAL_GetTick(); + s_base_unix_secs = (uint32_t)unix_secs; + + DBG_INFO("sys_clock_init: wall clock base = unix %lu, base_tick=%lu", + (unsigned long)s_base_unix_secs, (unsigned long)s_base_tick); + + return 0; +} + +uint32_t sys_clock_get(void) +{ + uint32_t now_tick = HAL_GetTick(); + int64_t unix_secs = (int64_t)s_base_unix_secs + (int64_t)((uint32_t)(now_tick - s_base_tick) / 1000uL); + + if (unix_secs < 0) { + return UINT32_MAX; /* 未初始化 */ + } + + return (uint32_t)unix_secs; +} + +char *sys_clock_get_str(char *buf, size_t buf_size) +{ + uint32_t unix_secs = sys_clock_get(); + if (unix_secs == UINT32_MAX) { + snprintf(buf, buf_size, "----/--/-- --:--:--"); + return buf; + } + + sd2506_time_t sd; + unix_to_sd2506(unix_secs, &sd); + + snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d", + sd.year + 2000uL, sd.month, sd.day, + sd.hour, sd.minute, sd.second); + return buf; +} + +int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time) +{ + /* Step 1: 更新墙钟基准 */ + s_base_tick = HAL_GetTick(); + s_base_unix_secs = unix_secs; + + DBG_INFO("sys_clock_set: wall clock updated to %lu", (unsigned long)unix_secs); + + /* Step 2: 写回 SD2506 RTC(若提供了 sd_time) */ + if (sd_time != NULL) { + sd2506_set_time(sd_time); + DBG_INFO("sys_clock_set: SD2506 RTC written"); + } + + return 0; +} diff --git a/App/sys_clock.h b/App/sys_clock.h new file mode 100644 index 0000000..22d7b35 --- /dev/null +++ b/App/sys_clock.h @@ -0,0 +1,65 @@ +/* + * 模块名称:System Clock — Wall Clock Time Maintenance + * 模块功能:基于 HAL Tick + SD2506 RTC 的系统墙钟时间维护 + * 启动时从 SD2506 RTC 读取初始时间,运行期间以 HAL Tick 计数维持 + * 适用平台:STM32F407ZGTx (Cortex-M4 FPU, 168MHz) + * 作者:王建锋 + * 创建日期:2026-07-19 + * 修改记录: + * v1.0 2026-07-19 王建锋 创建初始版本 + */ + +#ifndef __SYS_CLOCK_H +#define __SYS_CLOCK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "sd2506.h" + +/* + * 函数功能:系统时钟初始化(从 SD2506 RTC 读取初始时间) + * 入口参数:无 + * 返回值:0 - 成功, -1 - 失败 + * 限定条件:sd2506_init() 已调用 + * 函数说明:读取 SD2506 RTC 当前值转换为 Unix timestamp,设为系统墙钟初始时间 + */ +int sys_clock_init(void); + +/* + * 函数功能:获取当前系统墙钟时间(Unix epoch seconds) + * 入口参数:无 + * 返回值:Unix time (秒),失败返回 UINT32_MAX + * 限定条件:sys_clock_init() 已调用 + * 函数说明:基于 HAL Tick 计数 + 上次校准时刻的 Unix 秒数计算当前时间, + * 精度为毫秒级 + */ +uint32_t sys_clock_get(void); + +/* + * 函数功能:格式化系统墙钟时间为字符串 + * 入口参数:buf - 输出缓冲区(至少 20 字节) + * buf_size - 缓冲区大小 + * 返回值:buf 指针,失败返回 NULL + * 限定条件:sys_clock_init() 已调用 + * 函数说明:格式 "YYYY-MM-DD HH:MM:SS" + */ +char *sys_clock_get_str(char *buf, size_t buf_size); + +/* + * 函数功能:设置系统墙钟时间 + * 入口参数:unix_secs - Unix epoch time (秒) + * sd_time - SD2506 RTC 时间结构体(可选,非 NULL 时同时写入 RTC) + * 返回值:0 - 成功, -1 - 失败 + * 限定条件:sys_clock_init() 已调用 + * 函数说明:更新系统墙钟基准 + 写回 SD2506 RTC 保持长期准确性 + */ +int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time); + +#ifdef __cplusplus +} +#endif + +#endif /* __SYS_CLOCK_H */ diff --git a/App/sys_time.c b/App/sys_time.c deleted file mode 100644 index 510fdfb..0000000 --- a/App/sys_time.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 模块名称:System Timer - * 模块功能:DWT CYCCNT 实现 64-bit 微秒级时间戳 - * 在 SysTick 中断中检测 CYCCNT 溢出以扩展至 64-bit - * 适用平台:STM32F407ZGTx (168MHz) - * 作者:王建锋 - * 创建日期:2026-07-19 - * - * 限定条件: - * - CPU 主频 168MHz(若更改须同步 SYS_TIME_CYCLE_PER_US 宏) - * - 须在 FreeRTOS 启动前调用 sys_time_init() - * - sys_time_systick_hook() 须在 SysTick 中断中调用 - * - * 修改记录: - * v1.0 2026-07-19 王建锋 创建初始版本 - */ - -#include "sys_time.h" -#include "app_cfg.h" -#include "stm32f4xx.h" - -/* - * 每微秒 CPU 周期数(168MHz → 168 cycles/µs) - */ -#define SYS_TIME_CYCLE_PER_US 168UL - -/* - * CYCCNT 每溢出一次代表的微秒数(2^32 / 168 ≈ 25,564,904 µs ≈ 25.56s) - */ -#define SYS_TIME_OVERFLOW_US (uint64_t)(((uint64_t)1 << 32) / SYS_TIME_CYCLE_PER_US) - -static volatile uint32_t s_overflow_cnt; - -/* - * 函数功能:初始化 DWT CYCCNT - * 入口参数:无 - * 返 回 值:0 - 成功 - * 限定条件:须在 FreeRTOS 启动前调用 - * 函数说明:启用 DWT 和 CYCCNT 计数器,计数器从 0 开始 - */ -int sys_time_init(void) -{ - CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; - DWT->CYCCNT = 0; - DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; - - s_overflow_cnt = 0; - - return 0; -} - -/* - * 函数功能:获取微秒级时间戳 - * 入口参数:无 - * 返 回 值:64 位微秒时间戳 - * 函数说明:由当前 CYCCNT 值与溢出计数合成 64-bit 周期数再转换 - */ -uint64_t sys_time_us(void) -{ - uint32_t cyccnt = 0; - uint64_t ticks = 0; - uint32_t ov = 0; - uint32_t ov2 = 0; - - /* - * 原子读取溢出计数与 CYCCNT,处理潜在的读取竞争 - * 若两次读取溢出计数不一致则重读 CYCCNT - */ - do { - ov = s_overflow_cnt; - cyccnt = DWT->CYCCNT; - ov2 = s_overflow_cnt; - } while (ov != ov2); - - ticks = ((uint64_t)ov << 32) | cyccnt; - return ticks / SYS_TIME_CYCLE_PER_US; -} - -/* - * 函数功能:获取毫秒级时间戳 - * 入口参数:无 - * 返 回 值:64 位毫秒时间戳 - * 限定条件:sys_time_init() 已成功调用 - * 函数说明:通过对 sys_time_us() 取整实现 - */ -uint64_t sys_time_ms(void) -{ - return sys_time_us() / 1000; -} - -/* - * 函数功能:SysTick 中断钩子,检测 CYCCNT 溢出 - * 入口参数:无 - * 返 回 值:无 - * 函数说明:必须在 SysTick_Handler() 中调用 - * 每 1ms 检查一次 CYCCNT 是否已回绕 - */ -void sys_time_systick_hook(void) -{ - static uint32_t s_last_cyccnt = 0; - uint32_t cyccnt = DWT->CYCCNT; - - if (cyccnt < s_last_cyccnt) { - s_overflow_cnt++; - } - s_last_cyccnt = cyccnt; -} diff --git a/App/sys_time.h b/App/sys_time.h deleted file mode 100644 index af1d4cd..0000000 --- a/App/sys_time.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 模块名称:System Timer - * 模块功能:基于 DWT CYCCNT + SysTick 溢出的 64 位微秒级时间戳 - * 适用平台:STM32F407ZGTx (Cortex-M4 FPU) - * 作者:王建锋 - * 创建日期:2026-07-19 - * 修改记录: - * v1.0 2026-07-19 王建锋 创建初始版本 - */ - -#ifndef __SYS_TIME_H -#define __SYS_TIME_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -int sys_time_init(void); -uint64_t sys_time_us(void); -uint64_t sys_time_ms(void); -void sys_time_systick_hook(void); - -#ifdef __cplusplus -} -#endif - -#endif /* __SYS_TIME_H */ diff --git a/App/task/net_task.c b/App/task/net_task.c index 3aeaa63..5c4cdab 100644 --- a/App/task/net_task.c +++ b/App/task/net_task.c @@ -46,10 +46,11 @@ void net_task_func(void const *arg) (void)arg; osDelay(2000); - DBG_INFO("netTask: started"); + DBG_INFO("netTask: started, processing messages"); for (;;) { net_poll(); + net_process_messages(); #ifdef ENABLE_PHASE4_TESTS for (int i = 1; i < 8; i++) { @@ -58,9 +59,9 @@ void net_task_func(void const *arg) continue; } - int n = net_recv(i, s_net_buf, sizeof(s_net_buf), NET_MSG_DONTWAIT); + int n = net_recv_sock(p_sk, s_net_buf, sizeof(s_net_buf), NET_MSG_DONTWAIT); if (n > 0) { - net_send(i, s_net_buf, n, 0); + net_send_sock(p_sk, s_net_buf, n); } } #endif diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c index dd45b2f..4205a01 100644 --- a/Drivers/BSP/CH395F/ch395f.c +++ b/Drivers/BSP/CH395F/ch395f.c @@ -1,8 +1,8 @@ /* * 模块名称:CH395F SPI Ethernet Protocol Stack Driver - * 模块功能:CH395F 以太网协议栈芯片 SPI 驱动实现,提供芯片检测、初始化? * 网络参数配置、PHY 管理、DHCP、Socket 管理及数据收发功? * 适用平台:STM32F4 系列(SPI2 接口? * 作者:王建? * 创建日期?026-03-14 - * 修改记录? * 2026-03-14 王建? 创建初始版本,按代码规范重构 - * 2026-07-18 王建? 改�?write_send_buf / read_recv_buf 使用 SPI2 DMA 批量传输 + * 模块功能:CH395F 以太网协议栈芯片 SPI 驱动实现,提供芯片检测、初始化�? * 网络参数配置、PHY 管理、DHCP、Socket 管理及数据收发功�? * 适用平台:STM32F4 系列(SPI2 接口�? * 作者:王建�? * 创建日期�?026-03-14 + * 修改记录�? * 2026-03-14 王建�? 创建初始版本,按代码规范重构 + * 2026-07-18 王建�? 改�?write_send_buf / read_recv_buf 使用 SPI2 DMA 批量传输 */ /* 头文件包含区 - 先包含系统头文件,再包含本模块头文件 */ @@ -21,51 +21,51 @@ /* * 私有宏定义区 */ -#define CH395F_CHECK_EXIST_TEST_DATA 0x57U /* 芯片检测测试字?*/ -#define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms?*/ -#define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms?*/ -#define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI 事务开始延时(ms?*/ +#define CH395F_CHECK_EXIST_TEST_DATA 0x57U /* 芯片检测测试字�?*/ +#define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms�?*/ +#define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms�?*/ +#define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI 事务开始延时(ms�?*/ #define CH395F_DMA_BUF_SIZE 1500U /* DMA 缓冲区最大字节数 */ /* - * 私有变量?- DMA 传输相关 + * 私有变量�?- DMA 传输相关 */ static volatile uint8_t s_spi2_dma_done = 0U; /* DMA 传输完成标志 */ __attribute__((aligned(4))) static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA 发送缓冲区 */ __attribute__((aligned(4))) -static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA 接收缓冲?*/ +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); /* - * 私有函数实现? */ + * 私有函数实现�? */ /* - * 函数功能:开?SPI 事务(CS 拉高 -> 拉低? * 函数说明:手册要?SPI 模式下字节间无需间隔? * CS 拉高再拉低满足最小片选间隔要? */ + * 函数功能:开�?SPI 事务(CS 拉高 -> 拉低�? * 函数说明:手册要�?SPI 模式下字节间无需间隔�? * CS 拉高再拉低满足最小片选间隔要�? */ static void ch395f_spi_begin(void) { /* CS 先拉高,确保空闲状�?*/ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); - /* CS 拉低,启动事务(无需额外延时,SPI 时序?SCK 频率决定?*/ + /* CS 拉低,启动事务(无需额外延时,SPI 时序�?SCK 频率决定�?*/ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_RESET); } /* - * 函数功能:结?SPI 事务(CS 拉高? */ + * 函数功能:结�?SPI 事务(CS 拉高�? */ static void ch395f_spi_end(void) { - /* CS 拉高,结束事?*/ + /* CS 拉高,结束事�?*/ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); } /* * 函数功能:DMA 全双工传输并等待完成 - * 入口参数:len - 传输字节? uint16_t - * 函数说明:发?s_spi2_dma_tx_buf,接收到 s_spi2_dma_rx_buf? * 阻塞等待 DMA 完成回调置位标志 + * 入口参数:len - 传输字节�? uint16_t + * 函数说明:发�?s_spi2_dma_tx_buf,接收到 s_spi2_dma_rx_buf�? * 阻塞等待 DMA 完成回调置位标志 */ static void ch395f_spi_dma_transfer(uint16_t len) { HAL_StatusTypeDef status; @@ -117,32 +117,39 @@ static void ch395f_spi_dma_transfer(uint16_t len) { /* * 函数功能:SPI 收发完成回调 - * 函数说明:由 HAL SPI 驱动?DMA TX+RX 完成后调用(HAL_SPI_TransmitReceive_DMA 触发此回调) + * 函数说明:由 HAL SPI 驱动�?DMA TX+RX 完成后调用(HAL_SPI_TransmitReceive_DMA 触发此回调) */ +/* SPI1 DMA 完成标志(定义在 gd5f2gq5ue.c 中) */ +extern volatile uint8_t g_spi1_dma_done; + void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { s_spi2_dma_done = 1U; + } else if (hspi->Instance == SPI1) { + g_spi1_dma_done = 1U; } } /* * 函数功能:SPI 错误回调 - * 函数说明:由 HAL SPI 驱动?DMA 传输出错时调用,释放等待 + * 函数说明:由 HAL SPI 驱动�?DMA 传输出错时调用,释放等待 */ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { s_spi2_dma_done = 1U; + } else if (hspi->Instance == SPI1) { + g_spi1_dma_done = 1U; } } /* - * 公共函数实现? */ + * 公共函数实现�? */ /* * 函数功能:向 CH395 写入命令字节 - * 入口参数:cmd - 命令? uint8_t 0x00 - 0xFF + * 入口参数:cmd - 命令�? uint8_t 0x00 - 0xFF * 返回值:命令码回显�? uint8_t - * 限定条件:需?ch395f_spi_begin() ?ch395f_spi_end() 之间调用 + * 限定条件:需�?ch395f_spi_begin() �?ch395f_spi_end() 之间调用 * 函数说明:SPI 模式下命令与数据之间无需间隔 */ uint8_t ch395f_write_cmd(uint8_t cmd) { @@ -156,8 +163,8 @@ uint8_t ch395f_write_cmd(uint8_t cmd) { /* * 函数功能:向 CH395 写入数据字节 * 入口参数:data - 数据字节 uint8_t 0x00 - 0xFF - * 返回值:数据回显? uint8_t - * 限定条件:需?ch395f_spi_begin() ?ch395f_spi_end() 之间调用 + * 返回值:数据回显�? uint8_t + * 限定条件:需�?ch395f_spi_begin() �?ch395f_spi_end() 之间调用 */ uint8_t ch395f_write_data(uint8_t data) { uint8_t rx_data; @@ -170,34 +177,34 @@ uint8_t ch395f_write_data(uint8_t data) { /* * 函数功能:从 CH395 读取数据字节 * 返回值:接收到的数据字节 uint8_t - * 限定条件:需?ch395f_spi_begin() ?ch395f_spi_end() 之间调用 + * 限定条件:需�?ch395f_spi_begin() �?ch395f_spi_end() 之间调用 */ uint8_t ch395f_read_data(void) { uint8_t tx_dummy = 0xFFU; uint8_t rx_data; - /* 发�?0xFF 作为时钟源,读取芯片返回的数?*/ + /* 发�?0xFF 作为时钟源,读取芯片返回的数�?*/ HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, HAL_MAX_DELAY); return rx_data; } /* - * 函数功能:检?CH395 芯片是否存在且通信正常 + * 函数功能:检�?CH395 芯片是否存在且通信正常 * 返回值:CH395F_STATUS_OK - 检测到芯片,CH395F_STATUS_NOT_DETECTED - 未检测到 * 限定条件:SPI2 已正确初始化 - * 函数说明:发?CMD_CHECK_EXIST (0x06) 及测试字?0x57? * 芯片应返回按位取反�?0xA8 + * 函数说明:发�?CMD_CHECK_EXIST (0x06) 及测试字�?0x57�? * 芯片应返回按位取反�?0xA8 */ ch395f_status_t ch395f_check_exist(void) { uint8_t test_data = CH395F_CHECK_EXIST_TEST_DATA; uint8_t reply_data; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送检测命?*/ + /* 发送检测命�?*/ ch395f_write_cmd(CH395F_CMD_CHECK_EXIST); - /* 发送测试数?*/ + /* 发送测试数�?*/ ch395f_write_data(test_data); /* 读取芯片回复 */ @@ -215,18 +222,18 @@ ch395f_status_t ch395f_check_exist(void) { } /* - * 函数功能:读?CH395 芯片版本? * 返回值:版本字节(bit5:0 为版本号)uint8_t - * 限定条件:芯片已通过 ch395f_check_exist() 检? */ + * 函数功能:读�?CH395 芯片版本�? * 返回值:版本字节(bit5:0 为版本号)uint8_t + * 限定条件:芯片已通过 ch395f_check_exist() 检�? */ uint8_t ch395f_get_version(void) { uint8_t version; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送版本读取命?*/ + /* 发送版本读取命�?*/ ch395f_write_cmd(CH395F_CMD_GET_IC_VER); - /* 读取版本?*/ + /* 读取版本�?*/ version = ch395f_read_data(); /* 结束 SPI 事务 */ @@ -236,9 +243,9 @@ uint8_t ch395f_get_version(void) { } /* - * 函数功能:获取远?IP 和端? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * p_buf - 6 字节输出缓冲区(4字节IP + 2字节端口? * 限定条件:TCP Server 模式下连接建立后调用 - * 函数说明:IP 低字节在前,端口低字节在? */ + * 函数功能:获取远�?IP 和端�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * p_buf - 6 字节输出缓冲区(4字节IP + 2字节端口�? * 限定条件:TCP Server 模式下连接建立后调用 + * 函数说明:IP 低字节在前,端口低字节在�? */ void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) { uint8_t i; @@ -247,10 +254,10 @@ void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送获取远?IP 和端口命?*/ + /* 发送获取远�?IP 和端口命�?*/ ch395f_write_cmd(CH395F_CMD_GET_REMOT_IPP_SN); /* 发�?Socket 索引 */ @@ -261,7 +268,7 @@ void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) { p_buf[i] = ch395f_read_data(); } - /* 读取 2 字节端口?*/ + /* 读取 2 字节端口�?*/ for (i = 4; i < 6; i++) { p_buf[i] = ch395f_read_data(); } @@ -271,18 +278,18 @@ void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) { } /* - * 函数功能:设?CH395 功能参数 + * 函数功能:设�?CH395 功能参数 * 入口参数:para - 4 字节功能参数 uint32_t * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_fun_para(uint32_t para) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置功能参数命?*/ + /* 发送设置功能参数命�?*/ ch395f_write_cmd(CH395F_CMD_SET_FUN_PARA); - /* 发�?4 字节参数(低字节在前?*/ + /* 发�?4 字节参数(低字节在前�?*/ ch395f_write_data((uint8_t)(para)); ch395f_write_data((uint8_t)(para >> 8)); ch395f_write_data((uint8_t)(para >> 16)); @@ -293,15 +300,15 @@ void ch395f_set_fun_para(uint32_t para) { } /* - * 函数功能:设?TCP MSS - * 入口参数:mss - MSS ? uint16_t 60~1460 + * 函数功能:设�?TCP MSS + * 入口参数:mss - MSS �? uint16_t 60~1460 * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_tcp_mss(uint16_t mss) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?TCP MSS 命令 */ + /* 发送设�?TCP MSS 命令 */ ch395f_write_cmd(CH395F_CMD_SET_TCP_MSS); /* 发�?2 字节 MSS 值(低字节在前) */ @@ -313,13 +320,13 @@ void ch395f_set_tcp_mss(uint16_t mss) { } /* - * 函数功能:设?Socket 接收缓冲? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 函数功能:设�?Socket 接收缓冲�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * start_block - 起始块地址 uint8_t - * block_count - 块个? uint8_t + * block_count - 块个�? uint8_t * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 */ void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); /* 发送设置接收缓冲区命令 */ @@ -339,14 +346,14 @@ void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) } /* - * 函数功能:设?Socket 发送缓冲区 + * 函数功能:设�?Socket 发送缓冲区 * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * start_block - 起始块地址 uint8_t - * block_count - 块个? uint8_t + * block_count - 块个�? uint8_t * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 */ void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); /* 发送设置发送缓冲区命令 */ @@ -366,11 +373,11 @@ void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) } /* - * 函数功能:清?Socket 接收缓冲? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 函数功能:清�?Socket 接收缓冲�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * 限定条件:Socket 已打开 */ void ch395f_clear_recv_buf(uint8_t sock) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); /* 发送清空接收缓冲区命令 */ @@ -384,15 +391,15 @@ void ch395f_clear_recv_buf(uint8_t sock) { } /* - * 函数功能:设?TCP KeepAlive 空闲时间 - * 入口参数:time - 空闲时间(秒? uint32_t + * 函数功能:设�?TCP KeepAlive 空闲时间 + * 入口参数:time - 空闲时间(秒�? uint32_t * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_keepalive_idle(uint32_t time) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?KeepAlive 空闲时间命令 */ + /* 发送设�?KeepAlive 空闲时间命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_IDLE); /* 发�?4 字节时间值(低字节在前) */ @@ -406,15 +413,15 @@ void ch395f_set_keepalive_idle(uint32_t time) { } /* - * 函数功能:设?TCP KeepAlive 探测间隔 - * 入口参数:time - 间隔时间(秒? uint32_t + * 函数功能:设�?TCP KeepAlive 探测间隔 + * 入口参数:time - 间隔时间(秒�? uint32_t * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_keepalive_intvl(uint32_t time) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?KeepAlive 探测间隔命令 */ + /* 发送设�?KeepAlive 探测间隔命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_INTVL); /* 发�?4 字节时间值(低字节在前) */ @@ -428,18 +435,18 @@ void ch395f_set_keepalive_intvl(uint32_t time) { } /* - * 函数功能:设?TCP KeepAlive 探测次数 + * 函数功能:设�?TCP KeepAlive 探测次数 * 入口参数:cnt - 探测次数 uint8_t * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_keepalive_cnt(uint8_t cnt) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?KeepAlive 探测次数命令 */ + /* 发送设�?KeepAlive 探测次数命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_CNT); - /* 发送探测次?*/ + /* 发送探测次�?*/ ch395f_write_data(cnt); /* 结束 SPI 事务 */ @@ -447,22 +454,22 @@ void ch395f_set_keepalive_cnt(uint8_t cnt) { } /* - * 函数功能:启?禁用指定 Socket ?KeepAlive + * 函数功能:启�?禁用指定 Socket �?KeepAlive * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * enable - 1 启用? 禁用 uint8_t + * enable - 1 启用�? 禁用 uint8_t * 限定条件:Socket 已打开 */ void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?KeepAlive 使能命令 */ + /* 发送设�?KeepAlive 使能命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_SN); /* 发�?Socket 索引 */ ch395f_write_data(sock); - /* 发送使能标?*/ + /* 发送使能标�?*/ ch395f_write_data(enable); /* 结束 SPI 事务 */ @@ -470,17 +477,17 @@ void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable) { } /* - * 函数功能:设?TCP 重传次数 - * 入口参数:cnt - 重传次数 uint8_t?=使用默认?? * 限定条件:必须在 ch395f_init() 之前调用 + * 函数功能:设�?TCP 重传次数 + * 入口参数:cnt - 重传次数 uint8_t�?=使用默认�?�? * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_retrans_count(uint8_t cnt) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置重传次数命?*/ + /* 发送设置重传次数命�?*/ ch395f_write_cmd(CH395F_CMD_SET_RETRAN_COUNT); - /* 发送重传次?*/ + /* 发送重传次�?*/ ch395f_write_data(cnt); /* 结束 SPI 事务 */ @@ -488,15 +495,15 @@ void ch395f_set_retrans_count(uint8_t cnt) { } /* - * 函数功能:设?TCP 重传周期 + * 函数功能:设�?TCP 重传周期 * 入口参数:period - 重传周期(毫秒) uint16_t * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_retrans_period(uint16_t period) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置重传周期命?*/ + /* 发送设置重传周期命�?*/ ch395f_write_cmd(CH395F_CMD_SET_RETRAN_PERIOD); /* 发�?2 字节周期值(低字节在前) */ @@ -508,39 +515,41 @@ void ch395f_set_retrans_period(uint16_t period) { } /* - * 函数功能:软件复?CH395 + * 函数功能:软件复�?CH395 * 返回值:CH395F_STATUS_OK * 限定条件:SPI2 已正确初始化 - * 函数说明:复位耗时?15ms(TE0),函数内部已包含延? */ + * 函数说明:复位耗时�?15ms(TE0),函数内部已包含延�? */ ch395f_status_t ch395f_reset(void) { - /* 开?SPI 事务 */ + DBG_INFO("ch395f_reset: spi_begin"); ch395f_spi_begin(); - /* 发送复位命?*/ + DBG_INFO("ch395f_reset: write_cmd RESET_ALL"); ch395f_write_cmd(CH395F_CMD_RESET_ALL); - /* 结束 SPI 事务 */ + DBG_INFO("ch395f_reset: spi_end"); ch395f_spi_end(); - /* 等待复位完成(TE0 = 15ms 典型值,等待 20ms?*/ + DBG_INFO("ch395f_reset: HAL_Delay(%d)", CH395F_RESET_DELAY_MS); + DBG_INFO("ch395f_reset: tick=%lu basepri=0x%02X", HAL_GetTick(), __get_BASEPRI()); HAL_Delay(CH395F_RESET_DELAY_MS); + DBG_INFO("ch395f_reset: done"); return CH395F_STATUS_OK; } /* - * 函数功能:查询命令执行状? * 返回值:状态字节(CH395F_ERR_SUCCESS / CH395F_ERR_BUSY / ...)uint8_t - * 限定条件:仅在长执行命令(如初始化)后调? */ + * 函数功能:查询命令执行状�? * 返回值:状态字节(CH395F_ERR_SUCCESS / CH395F_ERR_BUSY / ...)uint8_t + * 限定条件:仅在长执行命令(如初始化)后调�? */ uint8_t ch395f_get_cmd_status(void) { uint8_t status; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送状态查询命?*/ + /* 发送状态查询命�?*/ ch395f_write_cmd(CH395F_CMD_GET_CMD_STATUS); - /* 读取状态字?*/ + /* 读取状态字�?*/ status = ch395f_read_data(); /* 结束 SPI 事务 */ @@ -550,8 +559,8 @@ uint8_t ch395f_get_cmd_status(void) { } /* - * 函数功能:轮询等待命令执行完? * 入口参数:timeout_ms - 超时时间(毫秒) uint32_t - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS ?CH395F_CMD_RET_ABORT? * 限定条件:在发送长执行命令后调? * 函数说明:每 20ms 查询一次,直到命令完成或超? */ + * 函数功能:轮询等待命令执行完�? * 入口参数:timeout_ms - 超时时间(毫秒) uint32_t + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS �?CH395F_CMD_RET_ABORT�? * 限定条件:在发送长执行命令后调�? * 函数说明:每 20ms 查询一次,直到命令完成或超�? */ uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms) { uint8_t status; uint32_t retry_count = 0U; @@ -572,10 +581,10 @@ uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms) { } /* - * 函数功能:初始化 CH395(包?MAC、PHY、TCP/IP 协议栈) + * 函数功能:初始化 CH395(包�?MAC、PHY、TCP/IP 协议栈) * 返回值:CH395F_STATUS_OK - 成功,CH395F_STATUS_TIMEOUT - 超时失败 * 限定条件:芯片已通过 ch395f_reset() 复位 - * 函数说明:阻塞等待初始化完成或超时(芯片需要约 200ms,这里等最?4s? */ + * 函数说明:阻塞等待初始化完成或超时(芯片需要约 200ms,这里等最�?4s�? */ ch395f_status_t ch395f_init(void) { uint8_t status; @@ -593,7 +602,7 @@ ch395f_status_t ch395f_init(void) { } /* - * 函数功能:设?CH395 IP 地址(低字节在前? * 入口参数:p_ip - 4 字节 IP 地址指针 uint8_t* + * 函数功能:设�?CH395 IP 地址(低字节在前�? * 入口参数:p_ip - 4 字节 IP 地址指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ void ch395f_set_ip_addr(uint8_t *p_ip) { @@ -604,13 +613,13 @@ void ch395f_set_ip_addr(uint8_t *p_ip) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?IP 命令 */ + /* 发送设�?IP 命令 */ ch395f_write_cmd(CH395F_CMD_SET_IP_ADDR); - /* 逐字节发?IP 地址 */ + /* 逐字节发�?IP 地址 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_ip[i]); } @@ -620,7 +629,7 @@ void ch395f_set_ip_addr(uint8_t *p_ip) { } /* - * 函数功能:设置网?IP 地址(低字节在前? * 入口参数:p_gwip - 4 字节网关 IP 指针 uint8_t* + * 函数功能:设置网�?IP 地址(低字节在前�? * 入口参数:p_gwip - 4 字节网关 IP 指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ void ch395f_set_gwip_addr(uint8_t *p_gwip) { @@ -631,13 +640,13 @@ void ch395f_set_gwip_addr(uint8_t *p_gwip) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置网关命?*/ + /* 发送设置网关命�?*/ ch395f_write_cmd(CH395F_CMD_SET_GWIP_ADDR); - /* 逐字节发送网?IP */ + /* 逐字节发送网�?IP */ for (i = 0; i < 4; i++) { ch395f_write_data(p_gwip[i]); } @@ -659,13 +668,13 @@ void ch395f_set_mask_addr(uint8_t *p_mask) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置掩码命?*/ + /* 发送设置掩码命�?*/ ch395f_write_cmd(CH395F_CMD_SET_MASK_ADDR); - /* 逐字节发送子网掩?*/ + /* 逐字节发送子网掩�?*/ for (i = 0; i < 4; i++) { ch395f_write_data(p_mask[i]); } @@ -675,9 +684,9 @@ void ch395f_set_mask_addr(uint8_t *p_mask) { } /* - * 函数功能:设?MAC 地址(低字节在前,存储于 EEPROM? * 入口参数:p_mac - 6 字节 MAC 地址指针 uint8_t* + * 函数功能:设�?MAC 地址(低字节在前,存储于 EEPROM�? * 入口参数:p_mac - 6 字节 MAC 地址指针 uint8_t* * 限定条件:指针非空,芯片已初始化 - * 函数说明:执行耗时?30us(TE5? */ + * 函数说明:执行耗时�?30us(TE5�? */ void ch395f_set_mac_addr(uint8_t *p_mac) { uint8_t i; @@ -686,13 +695,13 @@ void ch395f_set_mac_addr(uint8_t *p_mac) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?MAC 命令 */ + /* 发送设�?MAC 命令 */ ch395f_write_cmd(CH395F_CMD_SET_MAC_ADDR); - /* 逐字节发?MAC 地址 */ + /* 逐字节发�?MAC 地址 */ for (i = 0; i < 6; i++) { ch395f_write_data(p_mac[i]); } @@ -703,8 +712,8 @@ void ch395f_set_mac_addr(uint8_t *p_mac) { /* * 函数功能:从 CH395 读取 MAC 地址 - * 出口参数:p_mac - 6 字节 MAC 地址缓冲区指? uint8_t* - * 限定条件:指针非? */ + * 出口参数:p_mac - 6 字节 MAC 地址缓冲区指�? uint8_t* + * 限定条件:指针非�? */ void ch395f_get_mac_addr(uint8_t *p_mac) { uint8_t i; @@ -713,13 +722,13 @@ void ch395f_get_mac_addr(uint8_t *p_mac) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送读?MAC 命令 */ + /* 发送读�?MAC 命令 */ ch395f_write_cmd(CH395F_CMD_GET_MAC_ADDR); - /* 逐字节读?MAC 地址 */ + /* 逐字节读�?MAC 地址 */ for (i = 0; i < 6; i++) { p_mac[i] = ch395f_read_data(); } @@ -729,8 +738,8 @@ void ch395f_get_mac_addr(uint8_t *p_mac) { } /* - * 函数功能:读?IP 信息(IP + 掩码 + DNS,共 20 字节? * 出口参数:p_buf - 20 字节缓冲区指? uint8_t* - * 限定条件:指针非? */ + * 函数功能:读�?IP 信息(IP + 掩码 + DNS,共 20 字节�? * 出口参数:p_buf - 20 字节缓冲区指�? uint8_t* + * 限定条件:指针非�? */ void ch395f_get_ip_inf(uint8_t *p_buf) { uint8_t i; @@ -739,13 +748,13 @@ void ch395f_get_ip_inf(uint8_t *p_buf) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送读?IP 信息命令 */ + /* 发送读�?IP 信息命令 */ ch395f_write_cmd(CH395F_CMD_GET_IP_INF); - /* 逐字节读?IP 信息(共 20 字节?*/ + /* 逐字节读�?IP 信息(共 20 字节�?*/ for (i = 0; i < 20; i++) { p_buf[i] = ch395f_read_data(); } @@ -755,15 +764,15 @@ void ch395f_get_ip_inf(uint8_t *p_buf) { } /* - * 函数功能:获?PHY 连接状�? * 返回值:PHY 状态码(CH395F_PHY_DISCONN、CH395F_PHY_10M_FULL 等)uint8_t - * 限定条件:芯片已初始? */ + * 函数功能:获�?PHY 连接状�? * 返回值:PHY 状态码(CH395F_PHY_DISCONN、CH395F_PHY_10M_FULL 等)uint8_t + * 限定条件:芯片已初始�? */ uint8_t ch395f_get_phy_status(void) { uint8_t status; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送读?PHY 状态命?*/ + /* 发送读�?PHY 状态命�?*/ ch395f_write_cmd(CH395F_CMD_GET_PHY_STATUS); /* 读取 PHY 状�?*/ @@ -776,17 +785,17 @@ uint8_t ch395f_get_phy_status(void) { } /* - * 函数功能:设?PHY 连接模式 - * 入口参数:phystat - 连接模式? uint8_t 0x20 = 自动协商 - * 限定条件:芯片已初始? */ + * 函数功能:设�?PHY 连接模式 + * 入口参数:phystat - 连接模式�? uint8_t 0x20 = 自动协商 + * 限定条件:芯片已初始�? */ void ch395f_set_phy(uint8_t phystat) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设?PHY 命令 */ + /* 发送设�?PHY 命令 */ ch395f_write_cmd(CH395F_CMD_SET_PHY); - /* 发送连接模式参?*/ + /* 发送连接模式参�?*/ ch395f_write_data(phystat); /* 结束 SPI 事务 */ @@ -794,17 +803,17 @@ void ch395f_set_phy(uint8_t phystat) { } /* - * 函数功能:使?关闭 CH395 响应 PING - * 入口参数:enable - 1 使能 PING 响应? 关闭 - * 限定条件:芯片已初始? * 函数说明:手?5.37 CMD_PING_ENABLE,默认关? */ + * 函数功能:使�?关闭 CH395 响应 PING + * 入口参数:enable - 1 使能 PING 响应�? 关闭 + * 限定条件:芯片已初始�? * 函数说明:手�?5.37 CMD_PING_ENABLE,默认关�? */ void ch395f_ping_enable(uint8_t enable) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); /* 发�?PING 使能命令 */ ch395f_write_cmd(CH395F_CMD_PING_ENABLE); - /* 发送使能标?*/ + /* 发送使能标�?*/ ch395f_write_data(enable); /* 结束 SPI 事务 */ @@ -812,17 +821,17 @@ void ch395f_ping_enable(uint8_t enable) { } /* - * 函数功能:启?禁用 DHCP - * 入口参数:enable - 1 启用? 禁用 uint8_t - * 限定条件:芯片已初始? */ + * 函数功能:启�?禁用 DHCP + * 入口参数:enable - 1 启用�? 禁用 uint8_t + * 限定条件:芯片已初始�? */ void ch395f_set_dhcp(uint8_t enable) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); /* 发�?DHCP 使能命令 */ ch395f_write_cmd(CH395F_CMD_DHCP_ENABLE); - /* 发送启?禁用参数 */ + /* 发送启�?禁用参数 */ ch395f_write_data(enable); /* 结束 SPI 事务 */ @@ -830,15 +839,15 @@ void ch395f_set_dhcp(uint8_t enable) { } /* - * 函数功能:获?DHCP 状�? * 返回值:0 = 成功,非?= 错误/超时 uint8_t - * 限定条件:芯片已初始化且 DHCP 已启? */ + * 函数功能:获�?DHCP 状�? * 返回值:0 = 成功,非�?= 错误/超时 uint8_t + * 限定条件:芯片已初始化且 DHCP 已启�? */ uint8_t ch395f_get_dhcp_status(void) { uint8_t status; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送读?DHCP 状态命?*/ + /* 发送读�?DHCP 状态命�?*/ ch395f_write_cmd(CH395F_CMD_GET_DHCP_STATUS); /* 读取 DHCP 状�?*/ @@ -851,22 +860,22 @@ uint8_t ch395f_get_dhcp_status(void) { } /* - * 函数功能:设?Socket 协议类型 + * 函数功能:设�?Socket 协议类型 * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * proto - 协议类型 uint8_t CH395F_PROTO_TYPE_xxx * 限定条件:Socket 未打开 */ void ch395f_set_proto_type(uint8_t sock, uint8_t proto) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置协议类型命?*/ + /* 发送设置协议类型命�?*/ ch395f_write_cmd(CH395F_CMD_SET_PROTO_TYPE_SN); /* 发�?Socket 索引 */ ch395f_write_data(sock); - /* 发送协议类?*/ + /* 发送协议类�?*/ ch395f_write_data(proto); /* 结束 SPI 事务 */ @@ -874,10 +883,10 @@ void ch395f_set_proto_type(uint8_t sock, uint8_t proto) { } /* - * 函数功能:设?Socket 目标 IP 地址 + * 函数功能:设�?Socket 目标 IP 地址 * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * p_ip - 4 字节目标 IP 指针 uint8_t* - * 限定条件:指针非? */ + * 限定条件:指针非�? */ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip) { uint8_t i; @@ -886,16 +895,16 @@ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip) { return; } - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置目?IP 命令 */ + /* 发送设置目�?IP 命令 */ ch395f_write_cmd(CH395F_CMD_SET_IP_ADDR_SN); /* 发�?Socket 索引 */ ch395f_write_data(sock); - /* 逐字节发送目?IP */ + /* 逐字节发送目�?IP */ for (i = 0; i < 4; i++) { ch395f_write_data(p_ip[i]); } @@ -905,15 +914,15 @@ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip) { } /* - * 函数功能:设?Socket 目标端口(小端序? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 函数功能:设�?Socket 目标端口(小端序�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * port - 目标端口 uint16_t * 限定条件:无 */ void ch395f_set_des_port(uint8_t sock, uint16_t port) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送设置目标端口命?*/ + /* 发送设置目标端口命�?*/ ch395f_write_cmd(CH395F_CMD_SET_DES_PORT_SN); /* 发�?Socket 索引 */ @@ -930,13 +939,13 @@ void ch395f_set_des_port(uint8_t sock, uint16_t port) { } /* - * 函数功能:设?Socket 源端口(小端序) + * 函数功能:设�?Socket 源端口(小端序) * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * port - 源端? uint16_t + * port - 源端�? uint16_t * 限定条件:无 */ void ch395f_set_sour_port(uint8_t sock, uint16_t port) { - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); /* 发送设置源端口命令 */ @@ -958,8 +967,8 @@ void ch395f_set_sour_port(uint8_t sock, uint16_t port) { /* * 函数功能:打开 Socket * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功? uint8_t - * 限定条件:协议类型、目?IP、端口已设置 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t + * 限定条件:协议类型、目�?IP、端口已设置 */ uint8_t ch395f_open_socket(uint8_t sock) { ch395f_spi_begin(); @@ -971,9 +980,9 @@ uint8_t ch395f_open_socket(uint8_t sock) { } /* - * 函数功能:关?Socket + * 函数功能:关�?Socket * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功? uint8_t + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t * 限定条件:Socket 已打开 */ uint8_t ch395f_close_socket(uint8_t sock) { @@ -986,9 +995,9 @@ uint8_t ch395f_close_socket(uint8_t sock) { } /* - * 函数功能:启?TCP 监听模式 + * 函数功能:启�?TCP 监听模式 * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功? uint8_t + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t * 限定条件:Socket 已打开且协议类型为 TCP */ uint8_t ch395f_tcp_listen(uint8_t sock) { @@ -1001,9 +1010,9 @@ uint8_t ch395f_tcp_listen(uint8_t sock) { } /* - * 函数功能:启?TCP 连接 + * 函数功能:启�?TCP 连接 * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功? uint8_t + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t * 限定条件:Socket 已打开且协议类型为 TCP */ uint8_t ch395f_tcp_connect(uint8_t sock) { @@ -1018,8 +1027,8 @@ uint8_t ch395f_tcp_connect(uint8_t sock) { /* * 函数功能:断开 TCP 连接 * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功? uint8_t - * 限定条件:TCP 已建立连? */ + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功�? uint8_t + * 限定条件:TCP 已建立连�? */ uint8_t ch395f_tcp_disconnect(uint8_t sock) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_TCP_DISCONNECT_SN); @@ -1030,11 +1039,11 @@ uint8_t ch395f_tcp_disconnect(uint8_t sock) { } /* - * 函数功能:向 Socket 发送缓冲区写入数据(DMA 批量传输? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 函数功能:向 Socket 发送缓冲区写入数据(DMA 批量传输�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * p_data - 数据指针 uint8_t* * len - 数据长度 uint16_t * 限定条件:指针非空,长度大于 0,TCP 已连接或 UDP 已打开 - * 函数说明:将命令头和数据打包?DMA 缓冲区,一?SPI DMA 传输完成 + * 函数说明:将命令头和数据打包�?DMA 缓冲区,一�?SPI DMA 传输完成 */ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { uint16_t total; @@ -1056,10 +1065,10 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { s_spi2_dma_tx_buf[2] = len & 0xFFU; s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; - /* 拷贝用户数据?DMA 发送缓冲区 */ + /* 拷贝用户数据�?DMA 发送缓冲区 */ memcpy(&s_spi2_dma_tx_buf[4], p_data, len); - /* 数据同步屏障:确?memcpy 写入?DMA 读取前对总线可见 */ + /* 数据同步屏障:确�?memcpy 写入�?DMA 读取前对总线可见 */ __DSB(); /* CS 拉低 -> DMA 批量传输 -> CS 拉高 */ @@ -1069,10 +1078,10 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { } /* - * 函数功能:从 Socket 接收缓冲区读取数据(DMA 批量传输? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 - * p_data - 输出缓冲区指? uint8_t* - * len - 待读取数据长? uint16_t - * 限定条件:指针非空,长度大于 0,接收缓冲区有数? * 函数说明:命令头打包到发送缓冲区(数据部分填 0xFF 作时钟)? * 一?SPI DMA 全双工传输,有效数据从接收缓冲区偏移 4 字节处拷? */ + * 函数功能:从 Socket 接收缓冲区读取数据(DMA 批量传输�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * p_data - 输出缓冲区指�? uint8_t* + * len - 待读取数据长�? uint16_t + * 限定条件:指针非空,长度大于 0,接收缓冲区有数�? * 函数说明:命令头打包到发送缓冲区(数据部分填 0xFF 作时钟)�? * 一�?SPI DMA 全双工传输,有效数据从接收缓冲区偏移 4 字节处拷�? */ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { uint16_t total; @@ -1101,28 +1110,28 @@ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { ch395f_spi_dma_transfer(total); ch395f_spi_end(); - /* ?DMA 接收缓冲区偏?4 字节处拷贝有效数据到用户缓冲?*/ + /* �?DMA 接收缓冲区偏�?4 字节处拷贝有效数据到用户缓冲�?*/ memcpy(p_data, &s_spi2_dma_rx_buf[4], len); } /* - * 函数功能:获?Socket 接收缓冲区数据长? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 函数功能:获�?Socket 接收缓冲区数据长�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * 返回值:接收数据长度 uint16_t * 限定条件:Socket 已打开 */ uint16_t ch395f_get_recv_len(uint8_t sock) { uint16_t recv_len; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送获取接收长度命?*/ + /* 发送获取接收长度命�?*/ ch395f_write_cmd(CH395F_CMD_GET_RECV_LEN_SN); /* 发�?Socket 索引 */ ch395f_write_data(sock); - /* 读取长度低字?*/ + /* 读取长度低字�?*/ recv_len = ch395f_read_data(); /* 读取长度高字节并组合 */ @@ -1135,15 +1144,15 @@ uint16_t ch395f_get_recv_len(uint8_t sock) { } /* - * 函数功能:获取全局中断状�? * 返回值:中断状态字? uint8_t - * 限定条件:芯片已初始? */ + * 函数功能:获取全局中断状�? * 返回值:中断状态字�? uint8_t + * 限定条件:芯片已初始�? */ uint8_t ch395f_get_glob_int_status(void) { uint8_t status; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送读取全局中断状态命?*/ + /* 发送读取全局中断状态命�?*/ ch395f_write_cmd(CH395F_CMD_GET_GLOB_INT_STATUS); /* 读取中断状�?*/ @@ -1156,7 +1165,7 @@ uint8_t ch395f_get_glob_int_status(void) { } /* - * 函数功能:获取全局中断状态(2 字节版,支持 Socket 0~7? * 返回值:中断状�?uint16_t(低字节=Socket 0~3 + PHY/DHCP,高字节=Socket 4~7 + DHCPv6? * 限定条件:芯片已初始? */ + * 函数功能:获取全局中断状态(2 字节版,支持 Socket 0~7�? * 返回值:中断状�?uint16_t(低字节=Socket 0~3 + PHY/DHCP,高字节=Socket 4~7 + DHCPv6�? * 限定条件:芯片已初始�? */ uint16_t ch395f_get_glob_int_status_all(void) { uint16_t status; @@ -1170,16 +1179,16 @@ uint16_t ch395f_get_glob_int_status_all(void) { } /* - * 函数功能:获?Socket 中断状�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * 返回值:Socket 中断状态字? uint8_t - * 限定条件:芯片已初始? */ + * 函数功能:获�?Socket 中断状�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:Socket 中断状态字�? uint8_t + * 限定条件:芯片已初始�? */ uint8_t ch395f_get_sock_int_status(uint8_t sock) { uint8_t status; - /* 开?SPI 事务 */ + /* 开�?SPI 事务 */ ch395f_spi_begin(); - /* 发送读?Socket 中断状态命?*/ + /* 发送读�?Socket 中断状态命�?*/ ch395f_write_cmd(CH395F_CMD_GET_INT_STATUS_SN); /* 发�?Socket 索引 */ @@ -1195,8 +1204,8 @@ uint8_t ch395f_get_sock_int_status(uint8_t sock) { } /* - * 函数功能:获?Socket 状态(2 字节输出? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * status - 2 字节输出缓冲?[0]=Socket状�? [1]=TCP状�? * 限定条件:芯片已初始? */ + * 函数功能:获�?Socket 状态(2 字节输出�? * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * status - 2 字节输出缓冲�?[0]=Socket状�? [1]=TCP状�? * 限定条件:芯片已初始�? */ void ch395f_get_socket_status(uint8_t sock, uint8_t *p_status) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_GET_SOCKET_STATUS_SN); @@ -1207,10 +1216,10 @@ void ch395f_get_socket_status(uint8_t sock, uint8_t *p_status) { } /* - * 函数功能:设?ARP 重传参数 - * 入口参数:period_100ms - ARP 重传周期(单?100ms?=默认?10?秒) uint8_t - * cnt - ARP 重传次数?=默认?3? uint8_t - * 限定条件:应?ch395f_init() 之后调用 + * 函数功能:设�?ARP 重传参数 + * 入口参数:period_100ms - ARP 重传周期(单�?100ms�?=默认�?10�?秒) uint8_t + * cnt - ARP 重传次数�?=默认�?3�? uint8_t + * 限定条件:应�?ch395f_init() 之后调用 * 函数说明:周期最大值约 25.5 秒,255 表示无限重传 */ void ch395f_set_arp(uint8_t period_100ms, uint8_t cnt) { @@ -1222,9 +1231,9 @@ void ch395f_set_arp(uint8_t period_100ms, uint8_t cnt) { } /* - * 函数功能:设?Socket TTL + * 函数功能:设�?Socket TTL * 入口参数:sock - Socket 索引 uint8_t 0 - 7 - * ttl - TTL ? uint8_t 最?128 + * ttl - TTL �? uint8_t 最�?128 * 限定条件:Socket 已打开,TCP Server 模式下不生效 */ void ch395f_set_ttl(uint8_t sock, uint8_t ttl) { @@ -1237,7 +1246,7 @@ void ch395f_set_ttl(uint8_t sock, uint8_t ttl) { /* * 函数功能:获取不可达信息(用于调试连接失败原因) - * 出口参数:p_buf - 8 字节输出缓冲区(类型+协议?端口+IP? * 限定条件:收?GINT_STAT_UNREACH 中断后调? */ + * 出口参数:p_buf - 8 字节输出缓冲区(类型+协议�?端口+IP�? * 限定条件:收�?GINT_STAT_UNREACH 中断后调�? */ void ch395f_get_unreach_info(uint8_t *p_buf) { uint8_t i = 0; diff --git a/Drivers/BSP/CH395F/ch395f_test.c b/Drivers/BSP/CH395F/ch395f_test.c index 32358d8..7e089a9 100644 --- a/Drivers/BSP/CH395F/ch395f_test.c +++ b/Drivers/BSP/CH395F/ch395f_test.c @@ -2,8 +2,8 @@ * 模块名称:CH395F Driver Test Suite * 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例 * 适用平台:STM32F4 系列(CH395F 以太网芯片) - * 作者:王建? * 创建日期?026-07-19 - * 修改记录? * 2026-07-19 Phase 1/2/3 implemented + * 作者:王建�? * 创建日期�?026-07-19 + * 修改记录�? * 2026-07-19 Phase 1/2/3 implemented */ /* 头文件包含区 */ @@ -18,7 +18,7 @@ test_stats_t g_test_stats; /* - * Phase 2 配置默认? */ + * Phase 2 配置默认�? */ #ifndef PHASE2_REMOTE_IP #define PHASE2_REMOTE_IP "192.168.1.2" #endif @@ -36,7 +36,7 @@ test_stats_t g_test_stats; #endif /* - * Phase 3 配置默认? */ + * Phase 3 配置默认�? */ #ifndef PHASE3_REMOTE_IP #define PHASE3_REMOTE_IP "192.168.1.2" #endif @@ -54,9 +54,9 @@ test_stats_t g_test_stats; #endif /* - * 函数功能:恢?TCP Server 多连接模式的数据 Socket 配置 + * 函数功能:恢�?TCP Server 多连接模式的数据 Socket 配置 * 入口参数:port - TCP 源端口号 - * ??值:? * 限定条件:CH395F 协议栈已初始? * 函数说明:重新设置数?Socket 的协议类型和源端? */ + * �?�?值:�? * 限定条件:CH395F 协议栈已初始�? * 函数说明:重新设置数�?Socket 的协议类型和源端�? */ static void restore_data_sockets(uint16_t port) { int i = 0; for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++) { @@ -70,8 +70,8 @@ static void restore_data_sockets(uint16_t port) { * ============================ Phase 1 ============================ * 函数功能:寄存器读写 + 中断一致性基础测试 * 入口参数:无 - * ??值:? * 限定条件:CH395F 芯片正常应答 - * 函数说明:包?GET_VERSION / GET_CMD_STATUS / GET_IP_INF ?9 ? */ + * �?�?值:�? * 限定条件:CH395F 芯片正常应答 + * 函数说明:包�?GET_VERSION / GET_CMD_STATUS / GET_IP_INF �?9 �? */ void ch395f_phase1_tests(void) { uint8_t i = 0; @@ -112,7 +112,7 @@ void ch395f_phase1_tests(void) { "GINT_STATUS_ALL: 0x%04X (lo=0x%02X hi=0x%02X)", gint_all, gint_all & 0xFF, (gint_all >> 8) & 0xFF); - /* Test 5: GINT_STATUS (1-byte version) ?read-clear check */ + /* Test 5: GINT_STATUS (1-byte version) �?read-clear check */ uint8_t gint_1b = ch395f_get_glob_int_status(); TEST_CHECK(gint_1b == 0x00, "GINT_STATUS_1byte: 0x%02X (expect 0x00 after read-clear)", gint_1b); @@ -146,8 +146,8 @@ void ch395f_phase1_tests(void) { /* * ============================ Phase 2 ============================ - * 函数功能:TCP Client 收发 + 关闭重连 ×3 ? * 入口参数:无 - * ??值:? * 限定条件:PC 端已启动 TCP Server(默?192.168.1.2:8081? * 函数说明:每轮连接→发送→接收→关闭,连续 3 ? */ + * 函数功能:TCP Client 收发 + 关闭重连 ×3 �? * 入口参数:无 + * �?�?值:�? * 限定条件:PC 端已启动 TCP Server(默�?192.168.1.2:8081�? * 函数说明:每轮连接→发送→接收→关闭,连续 3 �? */ void ch395f_phase2_tests(void) { uint8_t sock = 0; @@ -174,7 +174,7 @@ void ch395f_phase2_tests(void) { sock = (uint8_t)(NET_MAX_SOCKETS - 1); - /* 一次性配?*/ + /* 一次性配�?*/ ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP); ch395f_set_send_buf(sock, 28, 2); ch395f_set_recv_buf(sock, 30, 2); @@ -204,7 +204,7 @@ void ch395f_phase2_tests(void) { HAL_Delay(100); - /* 发送测试数?*/ + /* 发送测试数�?*/ { uint8_t test_buf[64] = {0}; const char *msg = "HelloFromCH395F_TCP_Client! This is a 64-byte test message."; @@ -214,7 +214,7 @@ void ch395f_phase2_tests(void) { ch395f_write_send_buf(sock, test_buf, 64); } - /* Echo 循环:收数据 ?回发 */ + /* Echo 循环:收数据 �?回发 */ { int got_echo = 0; tick = HAL_GetTick(); @@ -266,7 +266,7 @@ phase2_iter_done: * ============================ Phase 3 ============================ * 函数功能:UDP Server Echo + PING + 大包测试 * 入口参数:无 - * ??值:? * 限定条件:PC 端已启动 UDP Client(默?192.168.1.2:8082? * 函数说明:包?20 次回显�? ?PING?0 次大包收? */ + * �?�?值:�? * 限定条件:PC 端已启动 UDP Client(默�?192.168.1.2:8082�? * 函数说明:包�?20 次回显�? �?PING�?0 次大包收�? */ void ch395f_phase3_tests(void) { uint8_t sock = 0; @@ -279,7 +279,7 @@ void ch395f_phase3_tests(void) { sock = (uint8_t)(NET_MAX_SOCKETS - 1); - /* UDP Server 模式:DesIP=0xFFFFFFFF ?接受任意来源 */ + /* UDP Server 模式:DesIP=0xFFFFFFFF �?接受任意来源 */ ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP); ch395f_set_send_buf(sock, 28, 2); ch395f_set_recv_buf(sock, 30, 2); @@ -295,7 +295,7 @@ void ch395f_phase3_tests(void) { HAL_Delay(200); - /* Echo 循环:收??解析来源 ?回显 */ + /* Echo 循环:收�?�?解析来源 �?回显 */ for (i = 0; i < PHASE3_ECHO_COUNT; i++) { tick = HAL_GetTick(); echo_ok = 0; @@ -348,7 +348,7 @@ void ch395f_phase3_tests(void) { /* * ============================ Phase 4 ============================ - * NET ?TCP Echo:在主循环中调用,用 net_recv + net_send 回显 + * NET �?TCP Echo:在主循环中调用,用 net_recv + net_send 回显 */ #ifdef ENABLE_PHASE4_TESTS @@ -356,9 +356,9 @@ static char s_phase4_buf[2048]; #endif /* - * 函数功能:NET ?TCP Echo 测试 + * 函数功能:NET �?TCP Echo 测试 * 入口参数:无 - * ??值:? * 限定条件:net_init 已完? * 函数说明:在 netTask 中运行,轮询 Socket 1~7 回显接收数据 + * �?�?值:�? * 限定条件:net_init 已完�? * 函数说明:在 netTask 中运行,轮询 Socket 1~7 回显接收数据 */ void ch395f_phase4_tests(void) { #ifdef ENABLE_PHASE4_TESTS @@ -368,9 +368,9 @@ void ch395f_phase4_tests(void) { continue; } - int n = net_recv(i, s_phase4_buf, sizeof(s_phase4_buf), NET_MSG_DONTWAIT); + int n = net_recv_sock(sk, s_phase4_buf, sizeof(s_phase4_buf), NET_MSG_DONTWAIT); if (n > 0) { - net_send(i, s_phase4_buf, n, 0); + net_send_sock(sk, s_phase4_buf, n); } } #endif @@ -378,7 +378,7 @@ void ch395f_phase4_tests(void) { /* * ============================ Phase 5 ============================ - * NET ?UDP Echo:netTask 中运行,?net_recvfrom + net_sendto 回显 + * NET �?UDP Echo:netTask 中运行,�?net_recvfrom + net_sendto 回显 */ #ifndef PHASE5_LOCAL_PORT @@ -392,8 +392,8 @@ static uint32_t s_phase5_count = 0; #endif /* - * 函数功能:NET ?UDP Echo 初始? * 入口参数:无 - * ??值:? * 限定条件:net_init 已完? * 函数说明:创?UDP Socket 并绑定本地端? */ + * 函数功能:NET �?UDP Echo 初始�? * 入口参数:无 + * �?�?值:�? * 限定条件:net_init 已完�? * 函数说明:创�?UDP Socket 并绑定本地端�? */ void ch395f_phase5_init(void) { #ifdef ENABLE_PHASE5_TESTS struct net_sockaddr_in addr; @@ -410,7 +410,7 @@ void ch395f_phase5_init(void) { if (net_bind(s_phase5_sock, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) { DBG_ERROR("Phase5: net_bind failed"); - net_close(s_phase5_sock); + net_close_sock(net_get_sock(s_phase5_sock)); s_phase5_sock = -1; return; } @@ -421,9 +421,9 @@ void ch395f_phase5_init(void) { } /* - * 函数功能:NET ?UDP Echo 测试 + * 函数功能:NET �?UDP Echo 测试 * 入口参数:无 - * ??值:? * 限定条件:ch395f_phase5_init 已成功调? * 函数说明:在 netTask 中运行,回显 UDP 接收数据 + * �?�?值:�? * 限定条件:ch395f_phase5_init 已成功调�? * 函数说明:在 netTask 中运行,回显 UDP 接收数据 */ void ch395f_phase5_tests(void) { #ifdef ENABLE_PHASE5_TESTS @@ -445,7 +445,7 @@ void ch395f_phase5_tests(void) { } /* - * Phase 6 配置默认? */ + * Phase 6 配置默认�? */ #ifndef PHASE6_ANNOUNCE_PORT #define PHASE6_ANNOUNCE_PORT 60001 #endif @@ -457,8 +457,8 @@ void ch395f_phase5_tests(void) { * ============================ Phase 6 ============================ * 函数功能:DHCP 自动获取 IP 测试 * 入口参数:无 - * ??值:? * 限定条件:局域网内存?DHCP Server - * 函数说明:启?DHCP ?轮询等待 IP ?打印结果 ?广播宣告 ?HELLO 回显 + * �?�?值:�? * 限定条件:局域网内存�?DHCP Server + * 函数说明:启�?DHCP �?轮询等待 IP �?打印结果 �?广播宣告 �?HELLO 回显 */ void ch395f_phase6_tests(void) { @@ -498,7 +498,7 @@ void ch395f_phase6_tests(void) { goto phase6_end; } - /* 3. 读取获取?IP */ + /* 3. 读取获取�?IP */ ch395f_get_ip_inf(ip_info); DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); DBG_INFO(" GW: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); @@ -515,7 +515,7 @@ void ch395f_phase6_tests(void) { uint8_t bcast_ip[4] = {0xFF, 0xFF, 0xFF, 0xFF}; uint8_t rx_buf[64] = {0}; - /* UDP Server 模式:本地端?60000(接?HELLO),DesIP=0xFFFFFFFF */ + /* UDP Server 模式:本地端�?60000(接�?HELLO),DesIP=0xFFFFFFFF */ ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP); ch395f_set_send_buf(anno_sock, 24, 2); ch395f_set_recv_buf(anno_sock, 26, 2); @@ -524,7 +524,7 @@ void ch395f_phase6_tests(void) { ch395f_set_des_port(anno_sock, 0); if (ch395f_open_socket(anno_sock) == CH395F_ERR_SUCCESS) { - /* 发送广播宣告(宣告端口 = 60001,来源端?= 60000?*/ + /* 发送广播宣告(宣告端口 = 60001,来源端�?= 60000�?*/ ch395f_set_des_ip(anno_sock, bcast_ip); ch395f_set_des_port(anno_sock, PHASE6_ANNOUNCE_PORT); ch395f_write_send_buf(anno_sock, ip_info, 4); @@ -546,14 +546,14 @@ void ch395f_phase6_tests(void) { PHASE6_HELLO_PORT, PHASE6_ANNOUNCE_PORT); TEST_CHECK(1, "DHCP announcement sent"); - /* 重置 Server 模式,等?PC 发�?HELLO */ + /* 重置 Server 模式,等�?PC 发�?HELLO */ ch395f_set_des_ip(anno_sock, bcast_ip); ch395f_set_des_port(anno_sock, 0); uint32_t wait = HAL_GetTick(); uint8_t hello_ok = 0; while (HAL_GetTick() - wait < 5000) { - /* 读取全局中断(刷?socket 中断状态) */ + /* 读取全局中断(刷�?socket 中断状态) */ ch395f_get_glob_int_status_all(); uint16_t rlen = ch395f_get_recv_len(anno_sock); if (rlen > 8) { @@ -593,7 +593,7 @@ phase6_end: /* * ============================ Phase 7 ============================ - * Select/Poll I/O 多路复用测试 ?net_select() + net_recv/send echo + * Select/Poll I/O 多路复用测试 �?net_select() + net_recv/send echo */ #include "net_select.h" @@ -605,7 +605,7 @@ static char s_phase7_buf[2048]; /* * 函数功能:Select/Poll I/O 多路复用测试 * 入口参数:无 - * ??值:? * 限定条件:net_init 已完成,多连?TCP Server 已启? * 函数说明:在 netTask 中运行,验证 select/poll 正确返回读写事件 + * �?�?值:�? * 限定条件:net_init 已完成,多连�?TCP Server 已启�? * 函数说明:在 netTask 中运行,验证 select/poll 正确返回读写事件 */ void ch395f_phase7_tests(void) { #ifdef ENABLE_PHASE7_TESTS @@ -630,11 +630,12 @@ void ch395f_phase7_tests(void) { if (n <= 0) return; for (int i = 0; i < NET_MAX_SOCKETS; i++) { - if (!NET_FD_ISSET(i, &readfds)) continue; + net_sock_t *sk = net_get_sock(i); + if (!sk || !NET_FD_ISSET(i, &readfds)) continue; - int len = net_recv(i, s_phase7_buf, sizeof(s_phase7_buf), NET_MSG_DONTWAIT); + int len = net_recv_sock(sk, s_phase7_buf, sizeof(s_phase7_buf), NET_MSG_DONTWAIT); if (len > 0) { - net_send(i, s_phase7_buf, len, 0); + net_send_sock(sk, s_phase7_buf, len); s_phase7_count++; TEST_CHECK(1, "Phase7: select echo #%lu (sock=%d, %d bytes)", s_phase7_count, i, len); } diff --git a/Drivers/BSP/GD5F2GQ5UE/fal_cfg.h b/Drivers/BSP/GD5F2GQ5UE/fal_cfg.h index 38821f0..c94faad 100644 --- a/Drivers/BSP/GD5F2GQ5UE/fal_cfg.h +++ b/Drivers/BSP/GD5F2GQ5UE/fal_cfg.h @@ -3,43 +3,57 @@ /* * 模块名称:FAL 配置 - * 模块功能:定?FAL Flash 设备表和分区? * 适用平台:STM32F407ZGT6 - * 作者:王建? * 创建日期?026-07-16 - * 修改记录? * 2026-07-16 王建? 创建初始版本 + * 模块功能:定义 Flash 物理分区布局和 FAL 设备/分区表 + * 适用平台:STM32F407ZGT6 + * 作者:王建锋 + * 创建日期:2026-07-16 + * 修改记录: + * 2026-07-20 王建锋 统一分区配置,添加 FTL 分区宏供 nand_ftl.c 使用 */ #ifdef __cplusplus extern "C" { #endif -/* FAL 调试开关:0-关闭?-开?*/ +/* ================== 物理分区布局(256MB GD5F2GQ5UE) ================== */ +/* + * 分区名 偏移 大小 块范围 用途 + * fdb_kvdb1 0 64MB Block 0~511 FlashDB KVDB + * fdb_tsdb1 64MB 64MB Block 512~1023 FlashDB TSDB + * ftl_fatfs 128MB 128MB Block 1024~2047 dhara FTL + FatFS + */ +#define FDB_KVDB1_OFFSET 0 +#define FDB_KVDB1_SIZE (64 * 1024 * 1024) +#define FDB_TSDB1_OFFSET (FDB_KVDB1_OFFSET + FDB_KVDB1_SIZE) +#define FDB_TSDB1_SIZE (64 * 1024 * 1024) +#define FTL_FATFS_OFFSET (FDB_TSDB1_OFFSET + FDB_TSDB1_SIZE) +#define FTL_FATFS_SIZE (128 * 1024 * 1024) + +/* ====================== FAL 配置(需 FAL 头文件) ====================== */ + #define FAL_DEBUG 0 -/* 启用分区表配?*/ +/* 启用分区表配置(FAL 库要求此宏) */ #define FAL_PART_HAS_TABLE_CFG -/* ======================== Flash 设备?======================== */ +/* ======================== Flash 设备表 ======================== */ extern const struct fal_flash_dev g_gd5f2gq5ue_flash; #define FAL_FLASH_DEV_TABLE \ { \ - &g_gd5f2gq5ue_flash, \ + &g_gd5f2gq5ue_flash, \ } -/* ======================== 分区?======================== */ -/* - * 分区名称 设备名称 偏移 大小 - * KVDB: 64MB @ 0 - * TSDB: 64MB @ 64MB - */ +/* ======================== 分区表 ======================== */ + #ifdef FAL_PART_HAS_TABLE_CFG -#define FAL_PART_TABLE \ -{ \ - {FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", 0, 64*1024*1024, 0}, \ - {FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", 64*1024*1024, 64*1024*1024, 0}, \ +#define FAL_PART_TABLE \ +{ \ + {FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", FDB_KVDB1_OFFSET, FDB_KVDB1_SIZE, 0}, \ + {FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", FDB_TSDB1_OFFSET, FDB_TSDB1_SIZE, 0}, \ } -#endif +#endif /* FAL_PART_HAS_TABLE_CFG */ #ifdef __cplusplus } diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c index 9cec98b..f0f5ca0 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c @@ -1,9 +1,9 @@ /* * 模块名称:GD5F2GQ5UE SPI NAND Flash 驱动 - * 模块功能:提?GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接? * 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI - * 作者:王建? * 创建日期?026-07-16 - * 修改记录? * 2026-07-16 王建? 创建初始版本 - * 2026-07-17 王建? 切换硬件 SPI,修正擦除地址,参?NuttX 驱动 + * 模块功能:提�?GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接�? * 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI + * 作者:王建�? * 创建日期�?026-07-16 + * 修改记录�? * 2026-07-16 王建�? 创建初始版本 + * 2026-07-17 王建�? 切换硬件 SPI,修正擦除地址,参�?NuttX 驱动 */ /* 头文件包含区 */ @@ -17,34 +17,58 @@ #include "dbg_log.h" /* 私有宏定义区 */ -#define GD5F_SPI_TIMEOUT 100 +#define GD5F_SPI_TIMEOUT 100 +#define GD5F_BBT_SIZE (GD5F_TOTAL_BLOCKS / 8) + +/* BBT 静态数组(1 bit 表示一个块,0=好块 1=坏块) */ +static uint8_t s_bbt[GD5F_BBT_SIZE]; /* 外部 SPI 句柄声明 */ extern SPI_HandleTypeDef hspi1; +/* DMA 完成标志(ch395f.c 中的 HAL_SPI_TxRxCpltCallback 会设置此标志) */ +volatile uint8_t g_spi1_dma_done; + +/* DMA 阈值(字节数超过此值使用 DMA 传输) */ +#define GD5F_DMA_THRESHOLD 32 + +/* + * 函数功能:SPI1 TX DMA 完成回调(PROGRAM_LOAD 数据发送用) + */ +void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { + if (hspi->Instance == SPI1) { + g_spi1_dma_done = 1U; + } +} + +/* + * 函数功能:SPI1 RX DMA 完成回调(READ_FROM_CACHE 数据接收用) + * 函数说明:HAL_SPI_Receive_DMA() 在 2 线 master 模式下 + * 内部走 HAL_SPI_TransmitReceive_DMA 但 state=BUSY_RX, + * 完成回调是 HAL_SPI_RxCpltCallback 而非 TxRxCpltCallback + */ +void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { + if (hspi->Instance == SPI1) { + g_spi1_dma_done = 1U; + } +} + /* ======================== 私有函数声明 ======================== */ -static int gd5f_wait_busy(uint32_t timeout_ms); -static int gd5f_write_enable(void); -static int gd5f_read_status(uint8_t *p_status); -static int gd5f_page_read(uint32_t page_addr); -static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, - size_t size); static int gd5f_page_program(uint32_t page_addr, uint16_t column, const uint8_t *p_buf, size_t size); static int gd5f_set_feature(uint8_t addr, uint8_t data); -static int gd5f_block_erase(uint32_t block_addr); -/* ======================== 私有函数定义 ======================== */ +/* ======================== SPI 原语定义 ======================== */ /* * 函数功能:等待芯片操作完成(轮询 OIP 位) * 入口参数:timeout_ms - 超时时间 uint32_t > 0 - * 返回值:0 - 操作完成?2 - 超时 + * 返回值:0 - 操作完成,-2 - 超时 * 限定条件:SPI 已初始化 * 函数说明:循环读取状态寄存器直到 OIP 位清零或超时 */ -static int gd5f_wait_busy(uint32_t timeout_ms) { +int gd5f_wait_busy(uint32_t timeout_ms) { uint8_t cmd = GD5F_CMD_GET_FEATURE; uint8_t addr = GD5F_REG_STATUS; uint8_t status = 0; @@ -71,8 +95,8 @@ static int gd5f_wait_busy(uint32_t timeout_ms) { * 入口参数:无 * 返回值:0 - 成功 * 限定条件:SPI 已初始化 - * 函数说明:CS# 拉低后发?06h 命令再拉? */ -static int gd5f_write_enable(void) { + * 函数说明:CS# 拉低后发�?06h 命令再拉�? */ +int gd5f_write_enable(void) { uint8_t cmd = GD5F_CMD_WRITE_ENABLE; GD5F_CS_LOW(); @@ -84,11 +108,11 @@ static int gd5f_write_enable(void) { /* * 函数功能:读取状态寄存器 - * 入口参数:p_status - 状态值输出指? uint8_t* 不为 NULL + * 入口参数:p_status - 状态值输出指�? uint8_t* 不为 NULL * 返回值:0 - 成功 * 限定条件:SPI 已初始化 - * 函数说明:发?0Fh + C0h 地址后读?字节状态�? */ -static int gd5f_read_status(uint8_t *p_status) { + * 函数说明:发�?0Fh + C0h 地址后读�?字节状态�? */ +int gd5f_read_status(uint8_t *p_status) { uint8_t cmd = GD5F_CMD_GET_FEATURE; uint8_t addr = GD5F_REG_STATUS; @@ -102,11 +126,11 @@ static int gd5f_read_status(uint8_t *p_status) { } /* - * 函数功能:页读取(将数据从存储阵列加载到内部缓存? * 入口参数:page_addr - 页地址 uint32_t - * 返回值:0 - 成功,其?- 错误? * 限定条件:SPI 已初始化 - * 函数说明:发?13h + 3字节行地址,等?OIP 清零 + * 函数功能:页读取(将数据从存储阵列加载到内部缓存�? * 入口参数:page_addr - 页地址 uint32_t + * 返回值:0 - 成功,其�?- 错误�? * 限定条件:SPI 已初始化 + * 函数说明:发�?13h + 3字节行地址,等�?OIP 清零 */ -static int gd5f_page_read(uint32_t page_addr) { +int gd5f_page_read(uint32_t page_addr) { uint8_t cmd[4] = {0}; cmd[0] = GD5F_CMD_PAGE_READ; @@ -124,12 +148,12 @@ static int gd5f_page_read(uint32_t page_addr) { /* * 函数功能:从内部缓存读取数据 * 入口参数:column - 列地址(页内偏移) uint16_t 0 - 2047 - * p_buf - 数据输出缓冲? uint8_t* 不为 NULL - * size - 读取字节? size_t > 0 + * p_buf - 数据输出缓冲�? uint8_t* 不为 NULL + * size - 读取字节�? size_t > 0 * 返回值:0 - 成功 * 限定条件:必须先调用 gd5f_page_read 完成数据加载 - * 函数说明:发?0Bh + 2字节列地址 +1字节 dummy 后读取数? */ -static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, + * 函数说明:发�?0Bh + 2字节列地址 +1字节 dummy 后读取数�? */ +int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, size_t size) { uint8_t cmd[4] = {0}; @@ -140,7 +164,24 @@ static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, GD5F_CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT); - HAL_SPI_Receive(&hspi1, p_buf, size, GD5F_SPI_TIMEOUT); + + if (size > GD5F_DMA_THRESHOLD) { + uint32_t tick_start = HAL_GetTick(); + g_spi1_dma_done = 0U; + if (HAL_SPI_Receive_DMA(&hspi1, p_buf, (uint16_t)size) != HAL_OK) { + GD5F_CS_HIGH(); + return GD5F_ERROR; + } + while (g_spi1_dma_done == 0U) { + if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { + HAL_SPI_DMAStop(&hspi1); + GD5F_CS_HIGH(); + return GD5F_BUSY_TIMEOUT; + } + } + } else { + HAL_SPI_Receive(&hspi1, p_buf, size, GD5F_SPI_TIMEOUT); + } GD5F_CS_HIGH(); return GD5F_OK; @@ -150,29 +191,57 @@ static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, * 函数功能:页编程(将数据写入指定页) * 入口参数:page_addr - 页地址 uint32_t * column - 列地址 uint16_t 0 - 2047 - * p_buf - 数据缓冲? const uint8_t* - * size - 写入字节? size_t > 0 - * 返回值:0 - 成功,其?- 错误? * 限定条件:目标区域已擦除 - * 函数说明?. 写使?- 02h 加载数据 - 10h 执行编程 - 等待完成 - * 2. 编程完成后检?P_FAIL ? */ + * p_buf - 数据缓冲�? const uint8_t* + * size - 写入字节�? size_t > 0 + * 返回值:0 - 成功,其�?- 错误�? * 限定条件:目标区域已擦除 + * 函数说明�?. 写使�?- 02h 加载数据 - 10h 执行编程 - 等待完成 + * 2. 编程完成后检�?P_FAIL �? */ static int gd5f_page_program(uint32_t page_addr, uint16_t column, const uint8_t *p_buf, size_t size) { - int ret = GD5F_OK; - uint8_t cmd[4]; - uint8_t status = 0; - + int ret; gd5f_write_enable(); + ret = gd5f_program_load(column, p_buf, size); + if (ret != GD5F_OK) return ret; + return gd5f_program_exec(page_addr); +} +int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) { + uint8_t cmd[3]; cmd[0] = GD5F_CMD_PROGRAM_LOAD; - cmd[1] = (column >> 8) & 0xFF; - cmd[2] = column & 0xFF; + cmd[1] = (uint8_t)(column >> 8); + cmd[2] = (uint8_t)(column); GD5F_CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT); - HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size, - GD5F_SPI_TIMEOUT); + + if (size > GD5F_DMA_THRESHOLD) { + uint32_t tick_start = HAL_GetTick(); + g_spi1_dma_done = 0U; + if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)p_buf, (uint16_t)size) + != HAL_OK) { + GD5F_CS_HIGH(); + return GD5F_ERROR; + } + while (g_spi1_dma_done == 0U) { + if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { + HAL_SPI_DMAStop(&hspi1); + GD5F_CS_HIGH(); + return GD5F_BUSY_TIMEOUT; + } + } + } else { + HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size, GD5F_SPI_TIMEOUT); + } GD5F_CS_HIGH(); + return GD5F_OK; +} + +int gd5f_program_exec(uint32_t page_addr) { + int ret; + uint8_t cmd[4]; + uint8_t status; + cmd[0] = GD5F_CMD_PROGRAM_EXEC; cmd[1] = (page_addr >> 16) & 0xFF; cmd[2] = (page_addr >> 8) & 0xFF; @@ -183,24 +252,28 @@ static int gd5f_page_program(uint32_t page_addr, uint16_t column, GD5F_CS_HIGH(); ret = gd5f_wait_busy(1000); - if (ret != GD5F_OK) { - return ret; - } + if (ret != GD5F_OK) return ret; gd5f_read_status(&status); - if (status & GD5F_STATUS_P_FAIL) { - return GD5F_PROGRAM_FAIL; - } + if (status & GD5F_STATUS_P_FAIL) return GD5F_PROGRAM_FAIL; return GD5F_OK; } +int gd5f_check_ecc(void) { + uint8_t status; + gd5f_read_status(&status); + uint8_t ecc = (status >> 4) & 0x03; + if (ecc == 0x02) return GD5F_ECC_ERROR; + return GD5F_OK; +} + /* - * 函数功能:设?Feature 寄存? * 入口参数:addr - 寄存器地址 uint8_t + * 函数功能:设�?Feature 寄存�? * 入口参数:addr - 寄存器地址 uint8_t * data - 写入数据 uint8_t * 返回值:0 - 成功 * 限定条件:SPI 已初始化 - * 函数说明:先写使能,发�?1Fh + 地址 + 数据,等待操作完? */ + * 函数说明:先写使能,发�?1Fh + 地址 + 数据,等待操作完�? */ static int gd5f_set_feature(uint8_t addr, uint8_t data) { uint8_t cmd[3]; @@ -220,11 +293,11 @@ static int gd5f_set_feature(uint8_t addr, uint8_t data) { } /* - * 函数功能:块擦除(擦?28KB块) - * 入口参数:block_addr - 块编? uint32_t 0 - 2047 - * 返回值:0 - 成功,其?- 错误? * 限定条件:gd5f2gq5ue_init() 已调? * 函数说明?. 写使?- D8h + 3字节字节地址 - 等待完成 - * 2. 擦除完成后检?E_FAIL ? */ -static int gd5f_block_erase(uint32_t block_addr) { + * 函数功能:块擦除(擦�?28KB块) + * 入口参数:block_addr - 块编�? uint32_t 0 - 2047 + * 返回值:0 - 成功,其�?- 错误�? * 限定条件:gd5f2gq5ue_init() 已调�? * 函数说明�?. 写使�?- D8h + 3字节字节地址 - 等待完成 + * 2. 擦除完成后检�?E_FAIL �? */ +int gd5f_block_erase(uint32_t block_addr) { int ret = GD5F_OK; uint8_t cmd[4]; uint8_t status = 0; @@ -254,14 +327,49 @@ static int gd5f_block_erase(uint32_t block_addr) { return GD5F_OK; } +/* + * 函数功能:扫描所有块构建 BBT(在 ECC 使能前调用) + * 入口参数:无 + * 返回值:0 - 成功 + * 限定条件:SPI 已初始化,ECC 尚未使能 + * 函数说明:读取每个块最后一页(page 63)的 spare byte 0, + * 若不为 0xFF 则为出厂坏块 + */ +static int gd5f_bbt_scan(void) { + uint32_t block; + uint8_t spare[8]; + uint32_t bad_count = 0; + + memset(s_bbt, 0, GD5F_BBT_SIZE); + + for (block = 0; block < GD5F_TOTAL_BLOCKS; block++) { + uint32_t page = (block << 6) | 63; + + gd5f_page_read(page); + gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, 1); + + if (spare[0] != 0xFF) { + s_bbt[block >> 3] |= (1 << (block & 7)); + bad_count++; + } + } + + DBG_INFO("BBT scan: %lu bad blocks found", bad_count); + return GD5F_OK; +} + /* ======================== 公共函数定义 ======================== */ /* * 函数功能:初始化 GD5F2GQ5UE * 入口参数:无 - * 返回值:0 - 成功,其?- 错误? * 限定条件:SPI1 和相?GPIO 已由 CubeMX 初始化完? * 函数说明?. 发送复位命令并等待完成 - * 2. 读取芯片 ID 并校? * 3. 使能内部 ECC (B0h bit4) - * 4. 解除所有块保护 (A0h = 0x00) + * 返回值:0 - 成功,其他 - 错误码 + * 限定条件:SPI1 和相关 GPIO 已由 CubeMX 初始化完成 + * 函数说明:1. 发送复位命令并等待完成 + * 2. 读取芯片 ID 并校验 + * 3. 扫描出厂坏块构建 BBT(ECC 使能前) + * 4. 使能内部 ECC (B0h bit4) + * 5. 解除所有块保护 (A0h = 0x00) */ int gd5f2gq5ue_init(void) { int ret = GD5F_OK; @@ -298,6 +406,9 @@ int gd5f2gq5ue_init(void) { return GD5F_ID_MISMATCH; } + DBG_INFO("Scanning bad blocks..."); + gd5f_bbt_scan(); + DBG_INFO("Enabling ECC..."); gd5f_set_feature(0xB0, 0x10); gd5f_wait_busy(100); @@ -311,11 +422,11 @@ int gd5f2gq5ue_init(void) { } /* - * 函数功能:读取芯?ID(MID + DID? * 入口参数:mid - 制造商 ID 输出指针 uint8_t* 不为 NULL + * 函数功能:读取芯�?ID(MID + DID�? * 入口参数:mid - 制造商 ID 输出指针 uint8_t* 不为 NULL * did - 设备 ID 输出指针 uint8_t* 不为 NULL * 返回值:0 - 成功 * 限定条件:SPI 已初始化 - * 函数说明:发?9Fh 命令后接??dummy + MID + DID + * 函数说明:发�?9Fh 命令后接�?�?dummy + MID + DID */ int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) { uint8_t cmd = GD5F_CMD_READ_ID; @@ -334,10 +445,10 @@ int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) { /* * 函数功能:从 NAND 读取数据(支持跨页) - * 入口参数:offset - 起始字节偏移 long 0 ~ 总容?1 - * p_buf - 数据缓冲? uint8_t* 不为 NULL - * size - 读取字节? size_t > 0 - * 返回值:0 - 成功,其?- 错误? * 限定条件:gd5f2gq5ue_init() 已成功调? * 函数说明:自动处理跨页读? */ + * 入口参数:offset - 起始字节偏移 long 0 ~ 总容�?1 + * p_buf - 数据缓冲�? uint8_t* 不为 NULL + * size - 读取字节�? size_t > 0 + * 返回值:0 - 成功,其�?- 错误�? * 限定条件:gd5f2gq5ue_init() 已成功调�? * 函数说明:自动处理跨页读�? */ int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) { int ret = GD5F_OK; @@ -370,10 +481,10 @@ int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) { /* * 函数功能:向 NAND 写入数据(支持跨页) - * 入口参数:offset - 起始字节偏移 long 0 ~ 总容?1 - * p_buf - 数据缓冲? const uint8_t* 不为 NULL - * size - 写入字节? size_t > 0 - * 返回值:0 - 成功,其?- 错误? * 限定条件:gd5f2gq5ue_init() 已成功调用,目标区域已擦? * 函数说明:自动处理跨页写? */ + * 入口参数:offset - 起始字节偏移 long 0 ~ 总容�?1 + * p_buf - 数据缓冲�? const uint8_t* 不为 NULL + * size - 写入字节�? size_t > 0 + * 返回值:0 - 成功,其�?- 错误�? * 限定条件:gd5f2gq5ue_init() 已成功调用,目标区域已擦�? * 函数说明:自动处理跨页写�? */ int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) { int ret = GD5F_OK; @@ -400,7 +511,7 @@ int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) { } /* - * 函数功能:擦除块(按块擦除,最小单?128KB? * 入口参数:offset - 起始字节偏移 long 必须块对? * size - 擦除字节? size_t 必须块大小整数�? * 返回值:0 - 成功,其?- 错误? * 限定条件:gd5f2gq5ue_init() 已成功调? * 函数说明:擦除操作以块为单位 + * 函数功能:擦除块(按块擦除,最小单�?128KB�? * 入口参数:offset - 起始字节偏移 long 必须块对�? * size - 擦除字节�? size_t 必须块大小整数�? * 返回值:0 - 成功,其�?- 错误�? * 限定条件:gd5f2gq5ue_init() 已成功调�? * 函数说明:擦除操作以块为单位 */ int gd5f2gq5ue_erase(long offset, size_t size) { int ret = GD5F_OK; @@ -428,10 +539,10 @@ int gd5f2gq5ue_erase(long offset, size_t size) { } /* - * 函数功能:复位芯? * 入口参数:无 + * 函数功能:复位芯�? * 入口参数:无 * 返回值:0 - 成功 * 限定条件:SPI 已初始化 - * 函数说明:发?FFh 复位命令后等?5ms + * 函数说明:发�?FFh 复位命令后等�?5ms */ int gd5f2gq5ue_reset(void) { uint8_t cmd = GD5F_CMD_RESET; @@ -445,3 +556,31 @@ int gd5f2gq5ue_reset(void) { return GD5F_OK; } +/* + * 函数功能:查询块是否坏块 + * 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1 + * 返回值:0 - 好块,1 - 坏块 + * 限定条件:gd5f2gq5ue_init() 已成功调用 + * 函数说明:通过 BBT 查询 + */ +int gd5f2gq5ue_is_block_bad(uint32_t block) { + if (block >= GD5F_TOTAL_BLOCKS) { + return 1; + } + return (s_bbt[block >> 3] >> (block & 7)) & 1; +} + +/* + * 函数功能:标记块为坏块 + * 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1 + * 返回值:无 + * 限定条件:gd5f2gq5ue_init() 已成功调用 + * 函数说明:更新 BBT + */ +void gd5f2gq5ue_mark_block_bad(uint32_t block) { + if (block >= GD5F_TOTAL_BLOCKS) { + return; + } + s_bbt[block >> 3] |= (1 << (block & 7)); +} + diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h index ff9401d..4e65fdf 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h @@ -144,6 +144,36 @@ int gd5f2gq5ue_erase(long offset, size_t size); */ int gd5f2gq5ue_reset(void); +/* + * 函数功能:查询块是否坏块 + * 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1 + * 返回值:0 - 好块,1 - 坏块 + * 限定条件:gd5f2gq5ue_init() 已成功调用 + * 函数说明:通过 BBT(Bad Block Table)查询,BBT 在 init 阶段 ECC 使能前扫描构建 + */ +int gd5f2gq5ue_is_block_bad(uint32_t block); + +/* + * 函数功能:标记块为坏块 + * 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1 + * 返回值:无 + * 限定条件:gd5f2gq5ue_init() 已成功调用 + * 函数说明:更新 BBT,并尝试物理标记该块最后一页 spare byte 0 为 0x00 + */ +void gd5f2gq5ue_mark_block_bad(uint32_t block); + +/* ==================== SPI 原语(FTL 共享) ==================== */ + +int gd5f_wait_busy(uint32_t timeout_ms); +int gd5f_write_enable(void); +int gd5f_read_status(uint8_t *p_status); +int gd5f_page_read(uint32_t page_addr); +int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, size_t size); +int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size); +int gd5f_program_exec(uint32_t page_addr); +int gd5f_block_erase(uint32_t block_addr); +int gd5f_check_ecc(void); + #ifdef __cplusplus } #endif diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c new file mode 100644 index 0000000..aad373c --- /dev/null +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c @@ -0,0 +1,386 @@ +/* + * 模块名称:NAND FTL 适配层(dhara + FatFS diskio 胶水) + * 模块功能:实现 dhara NAND HAL 和 FatFS 磁盘 I/O,连接 GD5F2GQ5UE 底层驱动 + * 适用平台:STM32F407ZGT6 + * 作者:王建锋 + * 创建日期:2026-07-20 + * 修改记录: + * 2026-07-20 王建锋 创建初始版本 + */ + +/* 头文件包含区 */ +#include +#include "gd5f2gq5ue.h" +#include "ff.h" +#include "diskio.h" +#include "nand.h" +#include "map.h" + +/* 统一分区配置 */ +#include "fal_cfg.h" + +/* 调试输出配置 */ +#define DBG_TAG "[NAND_FTL]" +#include "dbg_log.h" + +/* 私有宏定义区 */ +#define FTL_PAGE_SIZE GD5F_PAGE_SIZE +#define FTL_SECTORS_PER_PAGE (FTL_PAGE_SIZE / 512) +#define FTL_START_BLOCK (FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE) +#define FTL_NUM_BLOCKS (GD5F_TOTAL_BLOCKS - FTL_START_BLOCK) +#define FTL_START_PAGE (FTL_START_BLOCK * GD5F_PAGES_PER_BLOCK) + +/* dhara 逻辑地址到物理地址转换 */ +static inline uint32_t ftl_phy_block(dhara_block_t b) { return b + FTL_START_BLOCK; } +static inline uint32_t ftl_phy_page(dhara_page_t p) { return p + FTL_START_PAGE; } + +/* ======================== 私有变量 ======================== */ + +/* dhara 核心数据结构 */ +static struct dhara_nand s_nand; +static struct dhara_map s_map; +static uint8_t s_page_buf[FTL_PAGE_SIZE]; /* dhara 内部页缓冲区 */ + +/* 页面缓存(FatFS 读写缓存) */ +static uint8_t s_cache_buf[FTL_PAGE_SIZE]; +static dhara_sector_t s_cached_lpn; +static uint8_t s_cache_dirty; + +/* 拷贝临时缓冲区 */ +static uint8_t s_copy_buf[FTL_PAGE_SIZE]; + +/* 初始化状态 */ +static uint8_t s_initialized; + +/* ======================== 私有函数声明 ======================== */ + +static int ftl_flush_cache(void); +static int ftl_read_page(dhara_sector_t lpn); + +/* SPI 原语从 gd5f2gq5ue.h 获取 */ + +/* ======================== 页面缓存管理 ======================== */ + +static int ftl_flush_cache(void) { + if (!s_cache_dirty) { + return GD5F_OK; + } + + dhara_error_t err; + if (dhara_map_write(&s_map, s_cached_lpn, s_cache_buf, &err) < 0) { + DBG_ERROR("Cache flush failed: LPN=%lu err=%d", s_cached_lpn, err); + return GD5F_ERROR; + } + + s_cache_dirty = 0; + return GD5F_OK; +} + +static int ftl_read_page(dhara_sector_t lpn) { + dhara_error_t err; + + if (dhara_map_read(&s_map, lpn, s_cache_buf, &err) < 0) { + DBG_ERROR("Page read failed: LPN=%lu err=%d", lpn, err); + return GD5F_ERROR; + } + + s_cached_lpn = lpn; + s_cache_dirty = 0; + return GD5F_OK; +} + +/* ======================== dhara NAND HAL ======================== */ + +int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b) { + (void)n; + return gd5f2gq5ue_is_block_bad(ftl_phy_block(b)); +} + +void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) { + (void)n; + gd5f2gq5ue_mark_block_bad(ftl_phy_block(b)); +} + +int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t *err) { + (void)n; + int ret = gd5f_block_erase(ftl_phy_block(b)); + if (ret == GD5F_ERASE_FAIL || ret != GD5F_OK) { + dhara_nand_mark_bad(n, b); + dhara_set_error(err, DHARA_E_BAD_BLOCK); + return -1; + } + return 0; +} + +int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *data, dhara_error_t *err) { + int ret; + uint32_t phy_pg = ftl_phy_page(p); + + gd5f_write_enable(); + ret = gd5f_program_load(0, data, FTL_PAGE_SIZE); + if (ret != GD5F_OK) { + dhara_nand_mark_bad(n, p >> n->log2_ppb); + dhara_set_error(err, DHARA_E_BAD_BLOCK); + return -1; + } + + ret = gd5f_program_exec(phy_pg); + if (ret == GD5F_PROGRAM_FAIL || ret != GD5F_OK) { + dhara_nand_mark_bad(n, p >> n->log2_ppb); + dhara_set_error(err, DHARA_E_BAD_BLOCK); + return -1; + } + + return 0; +} + +int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) { + (void)n; + uint32_t i; + uint8_t buf[64]; + uint32_t phy_pg = ftl_phy_page(p); + + if (gd5f_page_read(phy_pg) != GD5F_OK) { + return 0; + } + gd5f_read_from_cache(0, buf, sizeof(buf)); + + for (i = 0; i < sizeof(buf); i++) { + if (buf[i] != 0xFF) { + return 0; + } + } + return 1; +} + +int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, size_t length, uint8_t *data, dhara_error_t *err) { + (void)n; + uint32_t phy_pg = ftl_phy_page(p); + + if (gd5f_page_read(phy_pg) != GD5F_OK) { + dhara_set_error(err, DHARA_E_ECC); + return -1; + } + + if (gd5f_check_ecc() != GD5F_OK) { + dhara_set_error(err, DHARA_E_ECC); + return -1; + } + + gd5f_read_from_cache((uint16_t)offset, data, length); + return 0; +} + +int dhara_nand_copy(const struct dhara_nand *n, dhara_page_t src, dhara_page_t dst, dhara_error_t *err) { + (void)n; + + if (dhara_nand_read(n, src, 0, FTL_PAGE_SIZE, s_copy_buf, err) < 0) { + return -1; + } + + if (dhara_nand_prog(n, dst, s_copy_buf, err) < 0) { + return -1; + } + + return 0; +} + +/* ======================== FatFS 磁盘 I/O ======================== */ + +DSTATUS disk_initialize(BYTE pdrv) { + if (pdrv != 0) { + return STA_NOINIT; + } + + if (s_initialized) { + return 0; + } + + DBG_INFO("Initializing NAND FTL..."); + + s_nand.log2_page_size = 11; + s_nand.log2_ppb = 6; + s_nand.num_blocks = FTL_NUM_BLOCKS; + + dhara_map_init(&s_map, &s_nand, s_page_buf, 4); + + memset(s_cache_buf, 0, FTL_PAGE_SIZE); + s_cached_lpn = 0; + s_cache_dirty = 0; + + { + dhara_error_t err; + if (dhara_map_resume(&s_map, &err) < 0) { + DBG_INFO("No valid map found, creating fresh (err=%d)", err); + dhara_map_clear(&s_map); + } else { + DBG_INFO("Map resumed: %lu/%lu sectors used", + dhara_map_size(&s_map), dhara_map_capacity(&s_map)); + } + } + + s_initialized = 1; + DBG_INFO("NAND FTL ready (capacity: %lu pages = %lu MB)", + dhara_map_capacity(&s_map), + (dhara_map_capacity(&s_map) * FTL_PAGE_SIZE) / (1024 * 1024)); + + return 0; +} + +DSTATUS disk_status(BYTE pdrv) { + if (pdrv != 0) { + return STA_NOINIT; + } + if (!s_initialized) { + return STA_NOINIT; + } + return 0; +} + +DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { + if (pdrv != 0 || !buff) { + return RES_PARERR; + } + if (!s_initialized && disk_initialize(pdrv) != 0) { + return RES_NOTRDY; + } + + while (count > 0) { + dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE); + uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512; + uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE); + + if (batch > count) { + batch = count; + } + + if (lpn != s_cached_lpn) { + if (ftl_flush_cache() != GD5F_OK) { + return RES_ERROR; + } + if (ftl_read_page(lpn) != GD5F_OK) { + return RES_ERROR; + } + } + + memcpy(buff, s_cache_buf + offset, batch * 512); + buff += batch * 512; + sector += batch; + count -= (UINT)batch; + } + + return RES_OK; +} + +DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) { + if (pdrv != 0 || !buff) { + return RES_PARERR; + } + if (!s_initialized && disk_initialize(pdrv) != 0) { + return RES_NOTRDY; + } + + while (count > 0) { + dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE); + uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512; + uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE); + + if (batch > count) { + batch = count; + } + + if (lpn != s_cached_lpn) { + if (ftl_flush_cache() != GD5F_OK) { + return RES_ERROR; + } + if (ftl_read_page(lpn) != GD5F_OK) { + return RES_ERROR; + } + } + + memcpy(s_cache_buf + offset, buff, batch * 512); + s_cache_dirty = 1; + + if (offset == 0 && batch == FTL_SECTORS_PER_PAGE) { + if (ftl_flush_cache() != GD5F_OK) { + return RES_ERROR; + } + } + + buff += batch * 512; + sector += batch; + count -= (UINT)batch; + } + + return RES_OK; +} + +DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) { + if (pdrv != 0) { + return RES_PARERR; + } + if (!s_initialized) { + return RES_NOTRDY; + } + + switch (cmd) { + case CTRL_SYNC: + if (ftl_flush_cache() != GD5F_OK) { + return RES_ERROR; + } + { + dhara_error_t err; + if (dhara_map_sync(&s_map, &err) < 0) { + DBG_ERROR("Map sync failed: %d", err); + return RES_ERROR; + } + } + return RES_OK; + + case GET_SECTOR_COUNT: { + LBA_t *p_sectors = (LBA_t *)buff; + dhara_sector_t pages = dhara_map_capacity(&s_map); + *p_sectors = pages * FTL_SECTORS_PER_PAGE; + return RES_OK; + } + + case GET_SECTOR_SIZE: + *(WORD *)buff = 512; + return RES_OK; + + case GET_BLOCK_SIZE: + *(DWORD *)buff = 1; + return RES_OK; + + default: + return RES_PARERR; + } +} + +/* ======================== 公共函数定义 ======================== */ + +int nand_ftl_format(void) { + dhara_error_t err; + + if (!s_initialized) { + if (disk_initialize(0) != 0) { + return GD5F_ERROR; + } + } + + if (ftl_flush_cache() != GD5F_OK) { + return GD5F_ERROR; + } + + dhara_map_clear(&s_map); + + if (dhara_map_sync(&s_map, &err) < 0) { + DBG_ERROR("Map sync after clear failed: %d", err); + return GD5F_ERROR; + } + + s_cache_dirty = 0; + + DBG_INFO("NAND FTL formatted, capacity: %lu pages", dhara_map_capacity(&s_map)); + return GD5F_OK; +} diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h new file mode 100644 index 0000000..84b9363 --- /dev/null +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.h @@ -0,0 +1,17 @@ +#ifndef __NAND_FTL_H +#define __NAND_FTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +int nand_ftl_init(void); +int nand_ftl_format(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Drivers/BSP/NET/net_config.h b/Drivers/BSP/NET/net_config.h index b306ed3..3612ec2 100644 --- a/Drivers/BSP/NET/net_config.h +++ b/Drivers/BSP/NET/net_config.h @@ -98,6 +98,13 @@ extern "C" { #define NET_POLL_DELAY_MS 1 /* 非阻塞轮询延时 */ #endif +/* + * 消息队列配置 + */ +#ifndef NET_MSG_QUEUE_LENGTH +#define NET_MSG_QUEUE_LENGTH 8 /* 消息队列深度 */ +#endif + #ifdef __cplusplus } #endif diff --git a/Drivers/BSP/NET/net_select.c b/Drivers/BSP/NET/net_select.c index f0c1643..c10ce26 100644 --- a/Drivers/BSP/NET/net_select.c +++ b/Drivers/BSP/NET/net_select.c @@ -1,24 +1,50 @@ /* * 模块名称:Network Select Implementation - * 模块功能:I/O 多路复用实现,包�?select �?poll 模式�? * 用于同时监控多个 Socket 的读写事�? * 适用平台:STM32F4 系列(CH395F 以太网芯片) - * 作者:王建�? * 创建日期�?026-07-18 - * 修改记录�? */ + * 模块功能:I/O 多路复用实现,包括 select 和 poll 两种模式,用于同时监控多个 Socket 的读写事件 + * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + * 2026-07-18 王建锋 创建初始版本 + */ + +#include +#include + +#include "cmsis_os.h" /* CMSIS-RTOS2 API: osDelay() */ +#include "FreeRTOS.h" +#include "task.h" #include "net_select.h" #include "net_socket.h" #include "ch395f.h" +#include "main.h" /* - * 私有函数声明�? */ + * 私有函数声明区 + */ + static int check_socket_readable(int sockfd); static int check_socket_writable(int sockfd); static int check_socket_error(int sockfd); /* - * 公共函数实现�? */ + * 公共函数实现区 + */ /* - * 函数功能:I/O 多路复用(select 模式�? */ + * 函数功能:I/O 多路复用(select 模式),等待多个 Socket 的读写事件就绪 + * 入口参数:nfds - 最大文件描述符数 +1 int > 0 + * readfds - 读事件集合指针 net_fd_set *(可为 NULL) + * writefds - 写事件集合指针 net_fd_set *(可为 NULL) + * exceptfds- 异常事件集合指针 net_fd_set *(可为 NULL) + * timeout - 超时时间结构体指针 net_timeval *(NULL=无限等待) + * 返回值:就绪的 Socket 数量 int >= 0 || -1 + * -1 - 参数无效 int + * 限定条件:net_init() + MX_FREERTOS_Init() 已执行;必须在 netTask 上下文中调用 + * 函数说明:轮询所有 Socket,检查读/写/异常事件是否就绪。阻塞等待直到有事件或超时 + */ + int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, net_fd_set *exceptfds, net_timeval *timeout) { uint32_t start_tick; @@ -89,8 +115,8 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, } } - /* 短暂延时避免忙等 */ - HAL_Delay(NET_POLL_DELAY_MS); + /* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */ + osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */ } /* 复制结果 */ @@ -108,7 +134,15 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, } /* - * 函数功能:I/O 多路复用(poll 模式�? */ + * 函数功能:I/O 多路复用(poll 模式),等待多个 Socket 的读写事件就绪 + * 入口参数:fds - pollfd 数组指针 net_pollfd * + * nfds - 数组元素数量 int > 0 + * timeout - 超时时间(毫秒) int >= 0 + * 返回值:就绪的 fd 数量 int >= 0 || -1 + * -1 - 参数无效 int + * 限定条件:net_init() + MX_FREERTOS_Init() 已执行;必须在 netTask 上下文中调用 + * 函数说明:轮询所有 Socket,检查读/写/错误/挂起事件是否就绪。阻塞等待直到有事件或超时 + */ int net_poll_events(net_pollfd *fds, int nfds, int timeout) { uint32_t start_tick; int ready = 0; @@ -185,7 +219,8 @@ int net_poll_events(net_pollfd *fds, int nfds, int timeout) { } } - HAL_Delay(NET_POLL_DELAY_MS); + /* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */ + osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */ } return ready; diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index 53c57f3..d48c8d7 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -1,12 +1,21 @@ /* * 模块名称:Network Socket Core - * 模块功能:BSD Socket API 核心实现,包�?Socket 管理、状态机�? * 事件处理、数据收发等功能,底层调�?CH395F 驱动 - * 适用平台:STM32F4 系列(CH395F 以太网芯片) - * 作者:王建�? * 创建日期�?026-07-18 - * 修改记录�? */ + * 模块功能:BSD Socket API 核心实现,包含 Socket 管理、状态机、消息队列(线程安全)、事件处理、数据收发等功能,底层调用 CH395F 驱动 + * 适用平台:STM32F407ZGTx + CH395F 以太网芯片 + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + * 2026-07-18 王建锋 创建初始版本 + * 2026-07-19 王建锋 添加消息队列线程安全机制,替换 xQueueCreate 为 CubeMX osMessageQueue + */ #include #include + +#include "FreeRTOS.h" +#include "task.h" +#include "cmsis_os.h" + #include "net_socket.h" #include "ch395f.h" #include "main.h" @@ -20,21 +29,32 @@ /* * 私有宏定义区 */ +#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔(ms) */ /* - * 私有变量�? */ -static net_sock_t s_net_socks[NET_MAX_SOCKETS]; /* Socket 控制块数�?*/ -static int s_net_errno = 0; /* 最后错误码 */ -static uint8_t s_net_initialized = 0; /* 初始化标�?*/ - - -/* - * 私有宏定义区 - 补充 + * 全局变量定义区 - 尽量避免使用全局变量 */ -#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔(ms�?*/ +static net_sock_t s_net_socks[NET_MAX_SOCKETS]; /* Socket 控制块数组 */ +static int s_net_errno = 0; /* 最后错误码 */ +static uint8_t s_net_initialized = 0; /* 初始化标志 */ /* - * 私有函数声明�? */ + * 消息队列 - 线程安全核心 + * 外部任务通过 net_send/net_recv/net_close 等 API 将请求放入队列, + * netTask 调用 net_process_messages() 串行处理所有 CH395F 操作 + * 由 CubeMX 在 MX_FREERTOS_Init()(osKernelInitialize 之后)中创建 + */ +extern osMessageQueueId_t netMsgQueueHandle; /* CubeMX 生成的消息队列句柄 */ + + + + + + +/* + * 私有函数声明区 + */ + static int alloc_socket(void); static void handle_connect_event(net_sock_t *p_sock); static void handle_disconnect_event(net_sock_t *p_sock); @@ -43,31 +63,50 @@ static void handle_send_event(net_sock_t *p_sock); static void handle_timeout_event(net_sock_t *p_sock); static void fire_event(net_sock_t *p_sock, net_event_t event); static void parse_ip_string(const char *str, uint8_t *ip_arr); +static int net_send_locked(net_sock_t *p_sock, const void *buf, int len); +static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags); +static int net_close_locked(net_sock_t *p_sock); /* - * 公共函数实现�? */ - -/* - * 函数功能:网络子系统初始�? */ + * 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式) + * 入口参数:ip - IP 地址字符串 "192.168.1.100",NULL 使用 DHCP 自动获取 const char * + * mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认值 const char * + * gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认值 const char * + * 返回值:0 - 初始化成功 int + * -1 - 初始化失败(芯片检测/复位/协议栈初始化失败) int + * 限定条件:SPI2 已正确初始化;netTask 尚未启动 + * 函数说明:1. 检查 CH395F 芯片是否存在并复位 + * 2. 启用 TCP Server 多连接模式(bit1=1),设置 TCP MSS、重传参数、KeepAlive 参数 + * 3. 配置 IP/网关/子网掩码,或启用 DHCP + * 4. 初始化 CH395F 协议栈并使能 PING 响应 + */ int net_init(const char *ip, const char *mask, const char *gateway) { ch395f_status_t ret; - /* 清零 Socket 控制�?*/ + DBG_INFO("net_init: start"); + + /* 清零 Socket 控制块 */ memset(s_net_socks, 0, sizeof(s_net_socks)); s_net_errno = 0; s_net_initialized = 0; - /* 检�?CH395F 芯片 */ + DBG_INFO("net_init: queue=%p", (void *)netMsgQueueHandle); + + DBG_INFO("net_init: check_exist..."); + /* 检查 CH395F 芯片 */ if (ch395f_check_exist() != CH395F_STATUS_OK) { + DBG_ERROR("net_init: ch395f_check_exist FAILED"); s_net_errno = NET_ERR; return -1; } - + DBG_INFO("net_init: reset..."); /* 复位芯片 */ ch395f_reset(); + DBG_INFO("net_init: set_fun_para..."); - /* 启用 TCP Server 多连接模式(bit1=1�? * Socket 0 专职监听,Socket 1~7 自动分配给新连接 */ + /* 启用 TCP Server 多连接模式(bit1=1) */ ch395f_set_fun_para(0x02); + DBG_INFO("net_init: set_tcp_mss..."); /* 设置 TCP MSS */ ch395f_set_tcp_mss(1460); @@ -115,7 +154,14 @@ int net_init(const char *ip, const char *mask, const char *gateway) { } /* - * 函数功能:轮询所�?Socket 状�? */ + * 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件 + * 返回值:本次轮询中发生事件的 Socket 数量 int >= 0 + * 限定条件:s_net_initialized == 1;必须在 netTask 上下文中调用 + * 函数说明:1. GPIO 轮询 CH395F INT# 引脚电平(低电平有效),无需 EXTI 中断 + * 2. 读取 GET_GLOB_INT_STATUS_ALL(2 字节版)获取所有 Socket 的中断状态 + * 3. 处理连接/断开/数据接收/发送完成/超时等事件,更新 Socket 状态机 + * 4. RECV 中断为电平触发,每次 net_poll() 只处理一批中断,避免主循环饿死 + */ int net_poll(void) { int event_count = 0; int i; @@ -126,7 +172,9 @@ int net_poll(void) { } /* - * GPIO 轮询模式:仅�?INT# 有效(低电平)时读取中断状�? * 读取 GET_GLOB_INT_STATUS_ALL �?CH395F 拉高 INT#,若仍有中断则再次拉�? */ + * GPIO 轮询模式:仅当 INT# 有效(低电平)时读取中断状态。 + * 读取 GET_GLOB_INT_STATUS_ALL,CH395F 拉高 INT#;若仍有中断则再次拉低。 + */ do { if (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_SET) { @@ -139,7 +187,8 @@ int net_poll(void) { break; } - /* 诊断:每 3 秒打印一次全局中断状态和 Socket 状�? * 注意:使用已读取的 int_status,不要再读 GINT(读即清除�?*/ + /* 诊断:每 3 秒打印一次全局中断状态和 Socket 状态。 + * 注意:使用已读取的 int_status,不要再读 GINT(读即清除)。 */ static uint32_t s_last_diag = 0; uint32_t now_diag = HAL_GetTick(); if ((now_diag - s_last_diag >= 3000) || s_last_diag == 0) { @@ -208,11 +257,13 @@ int net_poll(void) { } /* - * 处理完毕后检�?INT# 是否仍为低电�? * 如果是,说明在处理期间有新中断产生,需要再次读�? * - * 注意:CH395F �?RECV 中断是电平触发的(只要接收缓冲区有数据就保持低电平)�? * 如果不跳出循环,net_poll() 会无限循环处�?RECV 中断�? * 导致主循环中的应用代码(�?echo)永远无法执行�? * 因此每次 net_poll() 调用只处理一批中断,由主循环负责读取数据�? */ + * 处理完毕后检查 INT# 是否仍为低电平。如果是,说明在处理期间有新中断产生,需要再次读取。 + * 注意:CH395F 的 RECV 中断是电平触发的(只要接收缓冲区有数据就保持低电平)。 + * 如果不跳出循环,net_poll() 会无限循环处理 RECV 中断,导致主循环中的应用代码(如 echo)永远无法执行。 + * 因此每次 net_poll() 调用只处理一批中断,由主循环负责读取数据。 */ } while (0); - /* �?2 秒打印一次诊断信息(调试用) */ + /* 2 秒打印一次诊断信息(调试用) */ { static uint32_t s_last_dbg = 0; uint32_t now = HAL_GetTick(); @@ -235,13 +286,76 @@ int net_poll(void) { } /* - * 函数功能:创�?socket + * 函数功能:处理消息队列中的请求 + * 限定条件:必须在 netTask 上下文中调用(独占 CH395F 和 s_net_socks 访问权) + * 函数说明:每次调用处理一条消息。处理 send/recv 时内部会调用 net_poll() + * 等待操作完成。处理完毕后通知调用方任务。 + */ +void net_process_messages(void) { + net_msg_t msg; + + if (netMsgQueueHandle == NULL) { + return; + } + + while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) { + switch (msg.type) { + case NET_MSG_SEND: { + net_sock_t *p_sock = net_get_sock(msg.sockfd); + if (p_sock == NULL || p_sock->state != NET_SOCK_STATE_ESTABLISHED) { + msg.result = -1; + } else { + msg.result = net_send_locked(p_sock, msg.buf, msg.len); + } + break; + } + case NET_MSG_RECV: { + net_sock_t *p_sock = net_get_sock(msg.sockfd); + if (p_sock == NULL) { + msg.result = -1; + } else { + msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags); + } + break; + } + case NET_MSG_CLOSE: { + net_sock_t *p_sock = net_get_sock(msg.sockfd); + if (p_sock == NULL) { + msg.result = -1; + } else { + msg.result = net_close_locked(p_sock); + } + break; + } + case NET_MSG_CONNECT: { + msg.result = net_connect(msg.sockfd, + (const struct net_sockaddr *)&msg.addr, + msg.addrlen); + break; + } + default: + msg.result = -1; + break; + } + + xTaskNotifyGive(msg.caller); + } +} + +/* + * 函数功能:创建 Socket,分配空闲 Socket 控制块并初始化状态 + * 入口参数:domain - 地址族,仅支持 NET_AF_INET int + * type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP) int + * protocol - 通常为 0 int + * 返回值:Socket 描述符 (0~7),失败返回 -1 int + * 限定条件:net_init() 已调用成功;s_net_initialized == 1 + * 函数说明:自动分配一个空闲的 Socket 控制块(in_use=0),初始化为 CREATED 状态 */ int net_socket(int domain, int type, int protocol) { int sockfd; net_sock_t *p_sock; - /* 参数检�?*/ + /* 参数检查*/ if (domain != NET_AF_INET) { s_net_errno = NET_ERR_INVAL; return -1; @@ -265,7 +379,17 @@ int net_socket(int domain, int type, int protocol) { } /* - * 函数功能:绑定本地地址和端�? */ + * 函数功能:绑定 Socket 到本地 IP 地址和端口号 + * 入口参数:sockfd - Socket 描述符 (0~7) int + * addr - 本地地址结构体指针 const struct net_sockaddr * + * addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in) + * 返回值:0 - 绑定成功 int + * -1 - 绑定失败(参数无效、Socket 未使用等) int + * 限定条件:Socket 已通过 net_socket() 创建且未被其他操作占用;net_init() 已调用 + * 函数说明:1. TCP Socket 仅记录本地端口,进入 BOUND 状态等待 listen + * 2. UDP Socket(SOCK_DGRAM)立即打开 CH395F Socket 并配置为 Server 模式 + * - Socket 4~7 需显式分配收发缓冲区 + */ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) { net_sock_t *p_sock; const struct net_sockaddr_in *p_addr_in; @@ -327,7 +451,15 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) { } /* - * 函数功能:TCP Server 监听 + * 函数功能:启动 TCP Server 监听(多连接模式) + * 入口参数:sockfd - Socket 描述符 (0~7),必须为已绑定的 TCP Socket int + * backlog - 最大并发数据 Socket 数量 int >= 1 + * 返回值:0 - 监听启动成功 int + * -1 - 监听失败(参数无效、状态错误等) int + * 限定条件:Socket 已通过 net_socket() + net_bind() 创建并绑定;type == NET_SOCK_STREAM + * 函数说明:1. 多连接模式下,先为数据 Socket(NET_TCP_SERVER_DATA_SOCK_START ~ 7)配置协议类型、源端口和独立缓冲区 + * 2. CH395F 根据源端口匹配、协议类型为 TCP、状态为 CLOSED 查找可用 Socket 进行自动分配 + * 3. 最后配置监听 Socket 并启动 TCP_LISTEN */ int net_listen(int sockfd, int backlog) { net_sock_t *p_sock; @@ -351,8 +483,10 @@ int net_listen(int sockfd, int backlog) { return -1; } - /* 多连接模式:先为数据 Socket 设置协议类型、源端口和独立缓冲区�? * 必须在监听话�?Socket OPEN+LISTEN 之前配置,确�?CH395F �? * 自动分配机制能扫描到所有可�?Socket - * CH395F 根据源端口匹配、协议类�?TCP、状�?CLOSED 查找可用 Socket */ + /* 多连接模式:先为数据 Socket 设置协议类型、源端口和独立缓冲区。 + * 必须在监听 Socket OPEN+LISTEN 之前配置,确保 CH395F + * 自动分配机制能扫描到所有可用 Socket。 + * CH395F 根据源端口匹配、协议类型为 TCP、状态为 CLOSED 查找可用 Socket。 */ for (int ds = NET_TCP_SERVER_DATA_SOCK_START; ds < NET_MAX_SOCKETS; ds++) { uint8_t base = (uint8_t)(ds * 4); ch395f_set_send_buf((uint8_t)ds, base, 2); @@ -393,7 +527,13 @@ int net_listen(int sockfd, int backlog) { } /* - * 函数功能:TCP Server 接受连接 + * 函数功能:TCP Server 接受新连接,返回已建立连接的 Socket 描述符 + * 入口参数:sockfd - 监听 Socket 描述符 int + * addr - 输出:客户端地址结构体指针 struct net_sockaddr *(可为 NULL) + * addrlen - 输入输出:地址结构体长度 int *(可为 NULL) + * 返回值:新连接的 Socket 描述符 (1~7),失败返回 -1 int >= 0 || -1 + * 限定条件:Socket 处于 LISTENING 状态;net_poll() 已检测到 CONNECT 事件 + * 函数说明:遍历所有数据 Socket,查找与客户端地址匹配且已建立连接(ESTABLISHED)的 Socket */ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) { net_sock_t *p_listen_sock; @@ -438,7 +578,15 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) { } /* - * 函数功能:TCP Client 发起连接 + * 函数功能:TCP Client 发起与服务器的连接 + * 入口参数:sockfd - Socket 描述符 (0~7),必须为已创建的 TCP Socket int + * addr - 服务器地址结构体指针 const struct net_sockaddr * + * addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in) + * 返回值:0 - 连接请求发送成功(异步,需等待 ESTABLISHED) int + * -1 - 连接失败(参数无效、状态错误等) int + * 限定条件:Socket 已通过 net_socket() 创建且 type == NET_SOCK_STREAM;尚未连接 + * 函数说明:设置目标 IP 和端口后打开 CH395F Socket,进入 CONNECTING 状态。连接建立需等待 + * net_poll() 检测到 CONNECT 事件并将状态更新为 ESTABLISHED */ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) { net_sock_t *p_sock; @@ -494,47 +642,37 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) { } /* - * 函数功能:发送数据(TCP�? */ -int net_send(int sockfd, const void *buf, int len, int flags) { - net_sock_t *p_sock; + * 函数功能:发送数据(内部锁定版本,必须在 netTask 上下文调用) + */ +static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) { int sent = 0; uint32_t tick_start; - - /* 参数检�?*/ - if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) { - s_net_errno = NET_ERR_BADF; - return -1; - } - - p_sock = &s_net_socks[sockfd]; - if (!p_sock->in_use || p_sock->state != NET_SOCK_STATE_ESTABLISHED) { - s_net_errno = NET_ERR_NOTCONN; - return -1; - } + int sockfd = (int)(p_sock - s_net_socks); if (buf == NULL || len <= 0) { s_net_errno = NET_ERR_INVAL; return -1; } + if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) { + s_net_errno = NET_ERR_NOTCONN; + return -1; + } + tick_start = HAL_GetTick(); - /* 分块发�?*/ while (sent < len) { int chunk = len - sent; if (chunk > NET_SEND_BUF_SIZE) { chunk = NET_SEND_BUF_SIZE; } - /* 检查发送缓冲区是否空闲 */ if (!p_sock->send_ready) { - /* 超时检�?*/ if (NET_SEND_TIMEOUT_MS > 0 && (HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) { s_net_errno = NET_ERR_TIMEDOUT; break; } - /* 继续轮询 */ net_poll(); continue; } @@ -544,7 +682,6 @@ int net_send(int sockfd, const void *buf, int len, int flags) { sent += chunk; p_sock->send_bytes += (uint32_t)chunk; - /* 短暂延时等待发送完�?*/ tick_start = HAL_GetTick(); while (!p_sock->send_ready) { net_poll(); @@ -559,30 +696,12 @@ int net_send(int sockfd, const void *buf, int len, int flags) { } /* - * 函数功能:接收数据(TCP�? */ -int net_recv(int sockfd, void *buf, int len, int flags) { - net_sock_t *p_sock; + * 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用) + */ +static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) { uint16_t recv_len; uint32_t tick_start; - - /* 参数检�?*/ - if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) { - s_net_errno = NET_ERR_BADF; - return -1; - } - - p_sock = &s_net_socks[sockfd]; - if (!p_sock->in_use) { - s_net_errno = NET_ERR_BADF; - return -1; - } - - /* 检查连接状�?*/ - if (p_sock->type == NET_SOCK_STREAM && - p_sock->state != NET_SOCK_STATE_ESTABLISHED) { - s_net_errno = NET_ERR_NOTCONN; - return -1; - } + int sockfd = (int)(p_sock - s_net_socks); if (buf == NULL || len <= 0) { s_net_errno = NET_ERR_INVAL; @@ -596,35 +715,183 @@ int net_recv(int sockfd, void *buf, int len, int flags) { recv_len = ch395f_get_recv_len(sockfd); if (recv_len > 0) { - /* 有数据可�?*/ int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len; ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len); p_sock->recv_bytes += (uint32_t)read_len; return read_len; } - /* 非阻塞模式检�?*/ if (flags & NET_MSG_DONTWAIT) { s_net_errno = NET_ERR_WOULDBLOCK; return -1; } - /* 超时检�?*/ if (NET_RECV_TIMEOUT_MS > 0 && (HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) { s_net_errno = NET_ERR_TIMEDOUT; return -1; } - /* 连接已断开检�?*/ if (p_sock->state == NET_SOCK_STATE_TCP_ACCEPT) { - return 0; /* 对端关闭 */ + return 0; } } } /* - * 函数功能:发�?UDP 数据 + * 函数功能:关闭 socket(内部锁定版本,必须在 netTask 上下文调用) + */ +static int net_close_locked(net_sock_t *p_sock) { + int sockfd = (int)(p_sock - s_net_socks); + + ch395f_close_socket(sockfd); + memset(p_sock, 0, sizeof(net_sock_t)); + + return 0; +} + +/* + * 函数功能:发送数据(线程安全版本,通过消息队列委托给 netTask) + * 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS + * buf - 待发送数据缓冲区指针 const void * + * len - 数据长度 int > 0 + * flags - 标志位,通常为 0 int + * 返回值:实际发送字节数 int >= 0 || -1 + * -1 - 发送失败(参数无效、队列满等) int + * 限定条件:Socket 处于 ESTABLISHED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文 + * 函数说明:将发送请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_send_sock() + */ +int net_send(int sockfd, const void *buf, int len, int flags) { + (void)flags; + net_msg_t msg; + + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) { + s_net_errno = NET_ERR_BADF; + return -1; + } + + if (netMsgQueueHandle == NULL) { + s_net_errno = NET_ERR; + return -1; + } + + msg.type = NET_MSG_SEND; + msg.sockfd = sockfd; + msg.buf = (void *)buf; + msg.len = len; + msg.flags = 0; + msg.caller = xTaskGetCurrentTaskHandle(); + msg.result = -1; + + if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) { + s_net_errno = NET_ERR_BUSY; + return -1; + } + + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (msg.result < 0) { + s_net_errno = NET_ERR; + } + return msg.result; +} + +/* + * 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask) + * 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS + * buf - 接收缓冲区指针 void * + * len - 缓冲区大小 int > 0 + * flags - 标志位,0=阻塞,NET_MSG_DONTWAIT=非阻塞 int + * 返回值:实际接收字节数 int >= 0 || 0 || -1 + * 0 - 对端关闭连接 int + * -1 - 接收失败(参数无效、队列满等) int + * 限定条件:Socket 处于 ESTABLISHED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文 + * 函数说明:将接收请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_recv_sock() + */ +int net_recv(int sockfd, void *buf, int len, int flags) { + net_msg_t msg; + + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) { + s_net_errno = NET_ERR_BADF; + return -1; + } + + if (netMsgQueueHandle == NULL) { + s_net_errno = NET_ERR; + return -1; + } + + msg.type = NET_MSG_RECV; + msg.sockfd = sockfd; + msg.buf = buf; + msg.len = len; + msg.flags = flags; + msg.caller = xTaskGetCurrentTaskHandle(); + msg.result = -1; + + if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) { + s_net_errno = NET_ERR_BUSY; + return -1; + } + + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (msg.result < 0) { + s_net_errno = NET_ERR; + } + return msg.result; +} + +/* + * 函数功能:关闭 Socket(线程安全版本,通过消息队列委托给 netTask) + * 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS + * 返回值:0 - 关闭成功 int + * -1 - 关闭失败 int + * 限定条件:Socket 已创建且处于非 CLOSED 状态;net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文 + * 函数说明:将关闭请求放入消息队列,阻塞等待 netTask 处理完毕。TCP Socket 先断开连接再释放控制块。 + * netTask 内请使用 net_close_sock() + */ +int net_close(int sockfd) { + net_msg_t msg; + + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) { + s_net_errno = NET_ERR_BADF; + return -1; + } + + if (netMsgQueueHandle == NULL) { + s_net_errno = NET_ERR; + return -1; + } + + msg.type = NET_MSG_CLOSE; + msg.sockfd = sockfd; + msg.caller = xTaskGetCurrentTaskHandle(); + msg.result = -1; + + if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) { + s_net_errno = NET_ERR_BUSY; + return -1; + } + + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (msg.result < 0) { + s_net_errno = NET_ERR; + } + return msg.result; +} +/* + * 函数功能:发送 UDP 数据报(含目标地址) + * 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS + * buf - 待发送数据缓冲区指针 const void * + * len - 数据长度 int > 0 + * flags - 标志位,通常为 0 int + * dest_addr - 目标地址结构体指针 const struct net_sockaddr * + * addrlen - 目标地址结构体长度 int >= sizeof(struct net_sockaddr_in) + * 返回值:实际发送字节数 int > 0 || -1 + * -1 - 发送失败 int + * 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用 */ int net_sendto(int sockfd, const void *buf, int len, int flags, const struct net_sockaddr *dest_addr, int addrlen) { @@ -704,9 +971,17 @@ int net_sendto(int sockfd, const void *buf, int len, int flags, return sent; } - /* - * 函数功能:接�?UDP 数据 + * 函数功能:接收 UDP 数据报(含源地址) + * 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS + * buf - 接收缓冲区指针 void * + * len - 缓冲区大小 int > 0 + * flags - 标志位,通常为 0 int + * src_addr - 输出:发送方地址结构体指针 struct net_sockaddr *(可为 NULL) + * addrlen - 输入输出:源地址长度 int *(可为 NULL) + * 返回值:实际接收字节数 int >= 0 || -1 + * -1 - 接收失败 int + * 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用;有数据可用 */ int net_recvfrom(int sockfd, void *buf, int len, int flags, struct net_sockaddr *src_addr, int *addrlen) { @@ -784,34 +1059,59 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags, } /* - * 函数功能:关�?socket + * 函数功能:发送数据(内部版本,绕过消息队列,供 netTask 直接使用) */ -int net_close(int sockfd) { - net_sock_t *p_sock; - - /* 参数检�?*/ - if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) { +int net_send_sock(net_sock_t *p_sock, const void *buf, int len) { + if (p_sock == NULL) { s_net_errno = NET_ERR_BADF; return -1; } - - p_sock = &s_net_socks[sockfd]; - if (!p_sock->in_use) { - s_net_errno = NET_ERR_BADF; - return -1; - } - - /* 关闭 CH395F Socket */ - ch395f_close_socket(sockfd); - - /* 清零控制�?*/ - memset(p_sock, 0, sizeof(net_sock_t)); - - return 0; + return net_send_locked(p_sock, buf, len); } - /* - * 函数功能:注册事件回�? */ + * 函数功能:接收数据(内部版本,绕过消息队列,供 netTask 直接使用) + * 入口参数:p_sock - Socket 控制块指针 net_sock_t * + * buf - 接收缓冲区指针 void * + * len - 缓冲区大小 int > 0 + * flags - 标志位,NET_MSG_DONTWAIT=非阻塞 int + * 返回值:实际接收字节数 int >= 0 || 0 || -1 + * 0 - 对端关闭连接 int + * -1 - 接收失败 int + * 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1 + * 函数说明:直接操作 Socket 控制块,不经过消息队列。避免 netTask 向自身发送消息造成死锁 + */ +int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) { + if (p_sock == NULL) { + s_net_errno = NET_ERR_BADF; + return -1; + } + return net_recv_locked(p_sock, buf, len, flags); +} +/* + * 函数功能:关闭 Socket(内部版本,绕过消息队列,供 netTask 直接使用) + * 入口参数:p_sock - Socket 控制块指针 net_sock_t * + * 返回值:0 - 关闭成功 int + * -1 - 关闭失败 int + * 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1 + * 函数说明:直接操作 Socket 控制块,不经过消息队列。TCP Socket 先断开连接再释放控制块 + */ +int net_close_sock(net_sock_t *p_sock) { + if (p_sock == NULL) { + s_net_errno = NET_ERR_BADF; + return -1; + } + return net_close_locked(p_sock); +} +/* + * 函数功能:注册 Socket 事件回调函数 + * 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS + * cb - 回调函数指针 net_event_cb_t(可为 NULL 取消注册) + * arg - 用户自定义参数指针 void * + * 返回值:0 - 注册成功 int + * -1 - 注册失败 int + * 限定条件:Socket 已创建且 in_use == 1;net_init() 已调用 + */ + int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) { net_sock_t *p_sock; @@ -832,10 +1132,13 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) { return 0; } - /* - * 函数功能:获�?socket 最后错误码 + * 函数功能:获取 Socket 最后一次操作返回的错误码 + * 返回值:当前错误码值(NET_OK=0, NET_ERR_xxx=-N) int + * 限定条件:无 + * 函数说明:每次 net_*() API 调用失败后,错误码被设置。调用成功时不自动清零 */ + int net_get_errno(void) { return s_net_errno; } diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h index 0cf0b37..219d177 100644 --- a/Drivers/BSP/NET/net_socket.h +++ b/Drivers/BSP/NET/net_socket.h @@ -16,6 +16,31 @@ extern "C" { #endif #include "net_types.h" +#include "FreeRTOS.h" +#include "task.h" + +/* + * 消息队列类型定义 + */ +typedef enum { + NET_MSG_SEND, + NET_MSG_RECV, + NET_MSG_CLOSE, + NET_MSG_CONNECT, +} net_msg_type_t; + +typedef struct { + net_msg_type_t type; /* 操作类型 */ + int sockfd; /* Socket 描述符 */ + void *buf; /* 数据缓冲区(send/recv) */ + int len; /* 数据长度/缓冲区大小 */ + int flags; /* 接收标志 */ + TaskHandle_t caller; /* 调用方任务句柄 */ + int result; /* 操作结果 */ + /* 连接参数 */ + struct net_sockaddr_in addr; /* 连接地址 */ + int addrlen; /* 地址长度 */ +} net_msg_t; /* * 网络初始化接口 @@ -245,6 +270,14 @@ uint32_t net_htonl(uint32_t hostlong); */ uint32_t net_ntohl(uint32_t netlong); +/* + * 函数功能:处理消息队列中的请求(必须在 netTask 主循环中调用) + * 限定条件:net_init() 已调用 + * 函数说明:处理其他任务通过 net_send/net_recv 等接口发送的请求, + * 在 netTask 上下文中串行化执行所有 CH395F 操作 + */ +void net_process_messages(void); + /* * 函数功能:获取 Socket 控制块指针(内部使用) * 入口参数:sockfd - socket 描述符 @@ -252,6 +285,39 @@ uint32_t net_ntohl(uint32_t netlong); */ net_sock_t *net_get_sock(int sockfd); +/* + * 内部 API(需在 netTask 上下文中调用,绕过消息队列) + */ + +/* + * 函数功能:发送数据(内部版本,直接操作,不经过消息队列) + * 入口参数:p_sock - Socket 控制块指针 + * buf - 数据缓冲区 + * len - 数据长度 + * 返回值:实际发送字节数,失败返回 -1 + * 限定条件:必须在 netTask 上下文中调用 + */ +int net_send_sock(net_sock_t *p_sock, const void *buf, int len); + +/* + * 函数功能:接收数据(内部版本,直接操作,不经过消息队列) + * 入口参数:p_sock - Socket 控制块指针 + * buf - 接收缓冲区 + * len - 缓冲区大小 + * flags - 通常为 0,可设置 NET_MSG_DONTWAIT + * 返回值:实际接收字节数,0=对端关闭,-1=错误 + * 限定条件:必须在 netTask 上下文中调用 + */ +int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags); + +/* + * 函数功能:关闭 socket(内部版本,直接操作,不经过消息队列) + * 入口参数:p_sock - Socket 控制块指针 + * 返回值:0 成功,-1 失败 + * 限定条件:必须在 netTask 上下文中调用 + */ +int net_close_sock(net_sock_t *p_sock); + #ifdef __cplusplus } #endif diff --git a/Drivers/BSP/RS485/rs485.c b/Drivers/BSP/RS485/rs485.c index 9db3dff..faee6b2 100644 --- a/Drivers/BSP/RS485/rs485.c +++ b/Drivers/BSP/RS485/rs485.c @@ -1,75 +1,105 @@ /* * 模块名称:RS-485 半双工通信驱动 - * 模块功能:RS-485 半双工通信驱动实现,核心要点: - * 1. 发送前拉高 DE(切换到发送态) - * 2. HAL_UART_Transmit 内部已等?TC(发送完成)标志 - * 3. 发送完毕后拉低 DE(切回接收态) - * 4. 使用 HAL_UARTEx_ReceiveToIdle_IT 实现变长帧接? * 参考:ST AN3070 应用笔记、controllerstech.com RS485 教程 + * 模块功能:RS-485 半双工通信驱动实现,封装任意 UART 外设实现 485 方向控制, + * 提供阻塞发送、中断接收(IDLE 空闲帧检测)。使用 DWT CYCCNT 提供 + * 微秒级延时,确保 RS485 收发器 DE 使能稳定。 * 适用平台:STM32F4 系列 - * 作者:王建? * 创建日期?026-07-18 - * 修改记录? * 2026-07-18 王建? 创建初始版本 + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + * 2026-07-18 王建锋 创建初始版本 + * 2026-07-20 王建锋 修复 DE 时序:添加 DWT 微秒延时替代 for 循环 + * 2026-07-20 王建锋 清理中文注释乱码,对齐代码规范 */ #include "rs485.h" +/* ======================== 内部辅助函数 ======================== */ + /* - * 内部辅助?- 方向控制 + * 函数功能:DWT 微秒级延时 + * 入口参数:us - 延时微秒数 uint32_t 1 - N + * 返回值:无 + * 限定条件:无(首次调用自动初始化 DWT CYCCNT) + * 函数说明:利用 Cortex-M4 DWT CYCCNT 实现精确微秒延时,168MHz 下 1us = 168 计数。 + * 首次调用时自动使能 DWT 和 CYCCNT,后续只读 CYCCNT 寄存器,不影响其他模块。 */ +static void delay_us(uint32_t us) +{ + if (!(CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk)) { + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CYCCNT = 0; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + } + uint32_t start = DWT->CYCCNT; + uint32_t ticks = us * (SystemCoreClock / 1000000uL); + while ((DWT->CYCCNT - start) < ticks) { ; } +} + +/* ======================== 方向控制宏 ======================== */ + #define RS485_DIR_TX(h) HAL_GPIO_WritePin((h)->dir_port, (h)->dir_pin, GPIO_PIN_SET) #define RS485_DIR_RX(h) HAL_GPIO_WritePin((h)->dir_port, (h)->dir_pin, GPIO_PIN_RESET) -/* - * 公共函数实现 - */ +/* ======================== 公共函数实现 ======================== */ /* * 函数功能:初始化 RS-485 句柄 - * 入口参数:handle - RS-485 句柄指针 - * huart - UART 外设句柄 - * dir_port - DE 引脚 GPIO 端口 - * dir_pin - DE 引脚 GPIO 编号 - * 返回值:? * 限定条件:UART ?GPIO 须先?CubeMX 完成初始? * 函数说明:初始化后立即将 DE 拉低,使 RS-485 收发器处于接收状? */ + * 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空) + * p_huart - UART 外设句柄指针(已由 CubeMX 初始化) UART_HandleTypeDef*(非空) + * p_dir_port - 方向控制 GPIO 端口(如 GPIOD) GPIO_TypeDef*(非空) + * dir_pin - 方向控制 GPIO 引脚号(如 GPIO_PIN_0) uint16_t + * 返回值:无 + * 限定条件:UART 和 GPIO 须先由 CubeMX 完成初始化 + * 函数说明:初始化后默认处于接收状态(DE 引脚拉低) + */ void rs485_init(rs485_handle_t *p_handle, UART_HandleTypeDef *p_huart, GPIO_TypeDef *p_dir_port, - uint16_t dir_pin) { + uint16_t dir_pin) +{ p_handle->huart = p_huart; p_handle->dir_port = p_dir_port; p_handle->dir_pin = dir_pin; p_handle->rx_size = 0; - /* 默认进入接收状�?*/ RS485_DIR_RX(p_handle); } /* - * 函数功能:阻塞方式发送数? * 入口参数:handle - RS-485 句柄指针 - * data - 待发送数据缓冲区 - * len - 待发送字节数 - * timeout - 发送超时(ms),?0 使用默认? * 返回值:HAL_OK / HAL_TIMEOUT / HAL_ERROR - * 限定条件:须在主循环?RTOS 任务中调用,不可?UART 中断中调? * 函数说明? * 时序:DE=HIGH ?UART 发�?N 字节 ??TC ?DE=LOW - * HAL_UART_Transmit 内部会等?TXE(发送寄存器空)逐字节搬数据? * 最后还会等?TC(发送完成)标志,确保最后一字节的停止位已移出�? * 因此返回后再拉低 DE 是安全的 + * 函数功能:阻塞方式发送数据 + * 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空) + * p_data - 待发送数据缓冲区 uint8_t*(非空) + * len - 待发送字节数 uint16_t 1 - N + * timeout - 发送超时(ms),传 0 使用 RS485_TIMEOUT_DEFAULT uint32_t 0 / 1 - N + * 返回值:HAL_OK - 发送成功 HAL_StatusTypeDef + * HAL_TIMEOUT - 发送超时 + * HAL_ERROR - 发送错误 + * 限定条件:须在主循环或 FreeRTOS 任务中调用,不可在 UART 中断中调用 + * 函数说明:发送前拉高 DE,等待 50us 确保 RS485 收发器驱动使能稳定后再启动 UART 发送。 + * HAL_UART_Transmit 内部等待 TC(发送完成)标志,确保最后一字节完全移出后 + * 再拉低 DE 切回接收状态。 */ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, const uint8_t *p_data, uint16_t len, - uint32_t timeout) { + uint32_t timeout) +{ HAL_StatusTypeDef status = HAL_OK; if (timeout == 0U) { timeout = RS485_TIMEOUT_DEFAULT; } - /* 1. 切换到发送状?*/ RS485_DIR_TX(p_handle); - /* 2. 阻塞发送(内部等待 TC?*/ + delay_us(50); + status = HAL_UART_Transmit(p_handle->huart, (uint8_t *)p_data, len, timeout); - /* 3. 发送完毕或超时,切回接收状?*/ RS485_DIR_RX(p_handle); return status; @@ -77,28 +107,46 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, /* * 函数功能:启动中断方式接收(IDLE 空闲帧检测) - * 入口参数:handle - RS-485 句柄指针 - * buf - 接收缓冲? * buf_size - 缓冲区最大容? * 返回值:HAL_OK / HAL_ERROR - * 限定条件:UART 须已开启全局中断(NVIC 使能? * 函数说明? * 使用 HAL_UARTEx_ReceiveToIdle_IT 实现变长帧接收�? * ?UART 总线空闲超过 1 个字符时间后,硬件触?IDLE 中断? * HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收字节数�? * 用户须在该回调中重新调用本函数重新开启接? */ + * 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空) + * p_buf - 接收缓冲区 uint8_t*(非空) + * buf_size - 缓冲区大小 uint16_t + * 返回值:HAL_OK - 启动成功 HAL_StatusTypeDef + * HAL_ERROR - 启动失败 + * 限定条件:UART 须已开启全局中断(NVIC 使能) + * 函数说明:使用 HAL_UARTEx_ReceiveToIdle_IT 实现变长帧接收。 + * 当 UART 总线空闲超过 1 个字符时间后硬件触发 IDLE 中断, + * HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收字节数。 + * 用户须在回调中重新调用本函数重新开启接收。 + */ HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle, uint8_t *p_buf, - uint16_t buf_size) { + uint16_t buf_size) +{ p_handle->rx_size = 0; return HAL_UARTEx_ReceiveToIdle_IT(p_handle->huart, p_buf, buf_size); } /* - * 函数功能:缓存最近一次接收的字节? * 入口参数:handle - RS-485 句柄指针 - * size - 本次接收到的字节? * 返回值:? * 限定条件:须?HAL_UARTEx_RxEventCallback 中调? */ -void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size) { + * 函数功能:缓存最近一次接收的字节数 + * 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空) + * size - 本次接收到的字节数 uint16_t + * 返回值:无 + * 限定条件:须在 HAL_UARTEx_RxEventCallback 中调用 + * 函数说明:将 HAL 回调中接收到的数据大小回写到句柄的 rx_size 字段 + */ +void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size) +{ p_handle->rx_size = size; } /* - * 函数功能:获取最近一次接收的字节? * 入口参数:handle - RS-485 句柄指针 - * 返回值:字节? */ -uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle) { + * 函数功能:获取最近一次接收的字节数 + * 入口参数:p_handle - RS-485 句柄指针 const rs485_handle_t*(非空) + * 返回值:最近一次接收的字节数 uint16_t + * 限定条件:须在 HAL_UARTEx_RxEventCallback 触发后调用 + * 函数说明:获取 rx_size 字段中缓存的接收字节数 + */ +uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle) +{ return p_handle->rx_size; } - - diff --git a/Drivers/BSP/SD2506/sd2506.c b/Drivers/BSP/SD2506/sd2506.c index 04dad06..bfba8f2 100644 --- a/Drivers/BSP/SD2506/sd2506.c +++ b/Drivers/BSP/SD2506/sd2506.c @@ -1,12 +1,16 @@ /* - * 模块名称:SD2506API-G RTC 实时时钟驱动 - * 模块功能:SD2506API-G 高精度温补实时时钟模�?I2C 驱动 - * 适用平台:STM32F407ZGT6,I2C1 接口 (PB6-SCL, PB7-SDA) - * 作者:王建�? * 创建日期�?026-07-17 - * 修改记录�? * 2026-07-17 王建�? 创建初始版本,参�?SD2506API-G Ver2.0 手册 + * 模块名称:SD2506API-G RTC Driver + * 模块功能:SD2506API-G 高精度温补实时时钟驱动,I2C 接口 + * 适用平台:STM32F407ZGTx + SD2506API-G 实时时钟芯片(I2C1: PB6-SCL, PB7-SDA) + * 作者:王建锋 + * 创建日期:2026-07-17 + * 修改记录: + * 2026-07-17 王建锋 创建初始版本,参考 SD2506API-G Ver2.0 手册 */ #include +#include + #include "sd2506.h" #include "i2c.h" @@ -15,8 +19,11 @@ extern I2C_HandleTypeDef hi2c1; /* ======================== 内部辅助函数 ======================== */ /* - * 函数功能:BCD 码转十进�? * 入口参数:bcd - BCD 编码�? uint8_t 0x00 - 0x99 - * �?�?值:十进制数�? * 限定条件:输入为合法 BCD �? * 函数说明:高 4 位为十位,低 4 位为个位 + * 函数功能:BCD 码转十进制数 + * 入口参数:bcd - BCD 编码值 uint8_t 0x00 - 0x99 + * 返回值:十进制数值 uint8_t + * 限定条件:输入为合法 BCD 编码(高 4 位和低 4 位均 <= 9) + * 函数说明:高 4 位为十位,低 4 位为个位,分别乘 10 和加后返回 */ uint8_t sd2506_bcd_to_dec(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0FU); @@ -31,9 +38,11 @@ uint8_t sd2506_dec_to_bcd(uint8_t dec) { /* * 函数功能:写单字节寄存器 - * 入口参数:reg - 寄存器地址 uint8_t 00H - 79H - * val - 写入�? uint8_t 0x00 - 0xFF - * �?�?值:0 - 成功�?2 - I2C 错误 + * 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79 + * val - 写入数据 uint8_t 0x00 - 0xFF + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:I2C 外设已初始化 + * 函数说明:通过 I2C 向指定地址写入 1 字节数据 * 限定条件:I2C 外设已初始化 * 函数说明:通过 I2C 向指定地址写入 1 字节 */ @@ -48,10 +57,12 @@ static int sd2506_write_reg(uint8_t reg, uint8_t val) { /* * 函数功能:读单字节寄存器 - * 入口参数:reg - 寄存器地址 uint8_t 00H - 79H - * val - 读取值指�? uint8_t* - * �?�?值:0 - 成功�?2 - I2C 错误 - * 限定条件:I2C 外设已初始化,val 为非空指�? * 函数说明:通过 I2C 从指定地址读取 1 字节 + * 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79 + * p_val - 读取值指针 uint8_t*(非空) + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:I2C 外设已初始化,p_val 为非空指针 + * 函数说明:通过 I2C 从指定地址读取 1 字节数据 + * 限定条件:I2C 外设已初始化,val 为非空指针函数说明:通过 I2C 从指定地址读取 1 字节 */ static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) { if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, @@ -63,12 +74,14 @@ static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) { } /* - * 函数功能:写多字节寄存器 - * 入口参数:reg - 起始寄存器地址 uint8_t 00H - 79H - * data - 数据缓冲区指�? uint8_t* - * len - 数据长度 uint8_t 1 - N - * �?�?值:0 - 成功�?2 - I2C 错误 - * 限定条件:I2C 外设已初始化,data 为非空指�? * 函数说明:从 reg 开始连续写�?len 字节 + * 函数功能:写多字节寄存器(连续写) + * 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79 + * p_data - 数据缓冲区指针 uint8_t*(非空) + * len - 数据长度 uint8_t 1 - N + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:I2C 外设已初始化,p_data 为非空指针 + * 函数说明:从 reg 开始连续写入 len 字节数据 + * 限定条件:I2C 外设已初始化,data 为非空指针 */ static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) { if (HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, @@ -80,12 +93,14 @@ static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) { } /* - * 函数功能:读多字节寄存器 - * 入口参数:reg - 起始寄存器地址 uint8_t 00H - 79H - * data - 数据缓冲区指�? uint8_t* - * len - 数据长度 uint8_t 1 - N - * �?�?值:0 - 成功�?2 - I2C 错误 - * 限定条件:I2C 外设已初始化,data 为非空指�? * 函数说明:从 reg 开始连续读�?len 字节 + * 函数功能:读多字节寄存器(连续读) + * 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79 + * p_data - 数据缓冲区指针 uint8_t*(非空) + * len - 数据长度 uint8_t 1 - N + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:I2C 外设已初始化,p_data 为非空指针 + * 函数说明:从 reg 开始连续读取 len 字节数据 + * 限定条件:I2C 外设已初始化,data 为非空指针 */ static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) { if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, @@ -97,16 +112,16 @@ static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) { } /* - * 函数功能:开启写保护(允许写�?00H~71H 寄存器) + * 函数功能:开启写保护(允许写入 00H~71H 寄存器) * 入口参数:无 - * 返回值:0 - 成功�?2 - I2C 错误 + * 返回值:0 - 成功 2 - I2C 错误 * 限定条件:I2C 外设已初始化 * 函数说明:顺序:先写 WRTC1=1, 再写 WRTC2=1 + WRTC3=1 */ static int sd2506_write_enable(void) { int ret = 0; - /* 先置 WRTC1=1 (bit6=1), 其它位参考手�? 0x84 */ + /* 先置 WRTC1=1 (bit6=1), 其它位参考手册0x84 */ ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U); if (ret != SD2506_OK) return ret; @@ -118,9 +133,9 @@ static int sd2506_write_enable(void) { } /* - * 函数功能:关闭写保护(禁止写�?00H~71H 寄存器) + * 函数功能:关闭写保护(禁止写入 00H~71H 寄存器) * 入口参数:无 - * 返回值:0 - 成功�?2 - I2C 错误 + * 返回值:0 - 成功 2 - I2C 错误 * 限定条件:I2C 外设已初始化 * 函数说明:顺序:先写 WRTC2=0 + WRTC3=0, 再写 WRTC1=0 */ @@ -143,10 +158,14 @@ static int sd2506_write_disable(void) { /* * 函数功能:初始化 SD2506API-G RTC * 入口参数:无 - * 返回值:0 - 成功�?2 - I2C 错误 - * 限定条件:CubeMX 已完�?I2C1 初始�? - * 函数说明�?. 读取芯片 ID 验证通信 - * 2. 上电重置充电寄存�?18H=82H + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:CubeMX 已完成 I2C1 初始化(PB6-SCL, PB7-SDA) + * 函数说明:1. 读取芯片 ID 验证通信 + * 2. 上电重置充电寄存器 (0x18=0x82) + * 3. 配置 24 小时制、开自动复位 + * 限定条件:CubeMX 已完成I2C1 初始化 + * 函数说明:读取芯片 ID 验证通信 + * 2. 上电重置充电寄存器18H=82H * 3. 配置 24 小时制、开自动复位 */ int sd2506_init(void) { @@ -221,9 +240,11 @@ int sd2506_set_time(const sd2506_time_t *p_time) { } /* - * function: read RTC time - * param: p_time - output time struct - * return: 0=OK, -2=I2C error + * 函数功能:读取 RTC 时间日期 + * 入口参数:p_time - 输出时间结构体指针 sd2506_time_t*(非空) + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:sd2506_init() 已调用成功 + * 函数说明:一次读取 7 字节时间数据 (00H~06H),所有实时数据被锁存避免错误 * note: sd2506_init() must be called first */ int sd2506_get_time(sd2506_time_t *p_time) { @@ -253,8 +274,11 @@ int sd2506_get_time(sd2506_time_t *p_time) { } /* - * function: read internal temperature - * param: p_temp - output temperature + * 函数功能:读取内部温度 + * 入口参数:p_temp - 输出温度值指针 int8_t*(非空) + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:sd2506_init() 已调用成功 + * 函数说明:读取寄存器 0x16,bit7 为符号位 * return: 0=OK, -2=I2C error * note: reads register 0x16 */ @@ -284,8 +308,11 @@ int sd2506_get_temperature(int8_t *p_temp) { } /* - * function: read battery voltage (mV) - * param: p_voltage - output voltage + * 函数功能:读取电池电压(毫伏) + * 入口参数:p_voltage - 输出电压指针 uint16_t*(非空) + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:sd2506_init() 已调用成功 + * 函数说明:组合 9 位值 (bit8=BAT8_VAL, bit7~0=BAT_VL),转换为毫伏 * return: 0=OK, -2=I2C error * note: combines 9-bit value */ @@ -314,8 +341,11 @@ int sd2506_get_battery_voltage(uint16_t *p_voltage) { } /* - * function: read 8-byte chip ID - * param: p_id - 8-byte output buffer + * 函数功能:读取 8 字节芯片 ID + * 入口参数:p_id - 输出缓冲区指针 uint8_t[8](非空) + * 返回值:0-成功,2-I2C 错误 int + * 限定条件:sd2506_init() 已调用成功 + * 函数说明:读取寄存器 0x72~0x79 * return: 0=OK, -2=I2C error * note: reads registers 0x72-0x79 */ @@ -328,10 +358,13 @@ int sd2506_get_id(uint8_t p_id[8]) { } /* - * function: read SRAM data - * param: addr - start address (0-69) - * p_buf - output buffer - * len - read length + * 函数功能:读取 SRAM 数据 + * 入口参数:addr - 起始地址 uint8_t 0-69 + * p_buf - 输出缓冲区指针 uint8_t*(非空) + * len - 读取长度 uint8_t 1-N + * 返回值:0-成功,-1=参数错误,-2=I2C 错误 int + * 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70) + * 函数说明:SRAM 范围 0x2C~0x71 * return: 0=OK, -1=param err, -2=I2C err * note: SRAM range 0x2C-0x71 */ @@ -348,10 +381,13 @@ int sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len) { } /* - * function: write SRAM data - * param: addr - start address (0-69) - * p_buf - input data - * len - write length + * 函数功能:写入 SRAM 数据 + * 入口参数:addr - 起始地址 uint8_t 0-69 + * p_buf - 输入数据指针 uint8_t*(非空) + * len - 写入长度 uint8_t 1-N + * 返回值:0-成功,-1=参数错误,-2=I2C 错误 int + * 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70) + * 函数说明:SRAM 写入需走写保护流程(虽 SRAM 本身无需) * return: 0=OK, -1=param err, -2=I2C err */ int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) { @@ -377,9 +413,12 @@ int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) { } /* - * function: set alarm interrupt - * param: p_time - alarm time struct - * mask - alarm match mask + * 函数功能:设置闹钟中断 + * 入口参数:p_time - 报警时间结构体指针 sd2506_time_t*(非空) + * mask - 报警匹配掩码 uint8_t + * 返回值:0-成功,-2=I2C 错误 int + * 限定条件:sd2506_init() 已调用成功 + * 函数说明:写入 8 字节报警数据 (07H~0EH),使能 CTR2.INTAE+INTS0+IM * return: 0=OK, -2=I2C error */ int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { @@ -421,8 +460,11 @@ int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { } /* - * function: clear alarm interrupt flag - * param: none + * 函数功能:清除闹钟中断标志 + * 入口参数:无 + * 返回值:0-成功,-2=I2C 错误 int + * 限定条件:sd2506_init() 已调用成功;ARST=1 时自动清除 INTAF + * 函数说明:读取 CTR1 即可清除 INTAF 标志 * return: 0=OK, -2=I2C error * note: reading CTR1 clears INTAF */ @@ -440,8 +482,10 @@ int sd2506_clear_alarm(void) { } /* - * function: read control register 1 - * param: p_val - output value + * 函数功能:读取控制寄存器 1 (CTR1) + * 入口参数:p_val - 输出值指针 uint8_t*(非空) + * 返回值:0-成功,-2=I2C 错误 int + * 限定条件:sd2506_init() 已调用成功 * return: 0=OK, -2=I2C error */ int sd2506_read_ctr1(uint8_t *p_val) { diff --git a/Drivers/BSP/dbg_cfg.h b/Drivers/BSP/dbg_cfg.h index b3647b1..eb76e64 100644 --- a/Drivers/BSP/dbg_cfg.h +++ b/Drivers/BSP/dbg_cfg.h @@ -33,6 +33,12 @@ extern "C" { */ /* #define DBG_DEBUG_ENABLE */ +/* + * 系统时间戳开关:取消注释在所有日志行首追加 [YYYY-MM-DD HH:MM:SS] 时间戳 + * 依赖 sys_clock_init() 已调用(SD2506 RTC 初始化完成) + */ +#define APP_TIMESTAMP_ENABLE + #ifdef __cplusplus } #endif diff --git a/Drivers/BSP/dbg_log.h b/Drivers/BSP/dbg_log.h index ff3e71d..9cd7abf 100644 --- a/Drivers/BSP/dbg_log.h +++ b/Drivers/BSP/dbg_log.h @@ -22,8 +22,13 @@ extern "C" { #endif #include +#include #include "dbg_cfg.h" +#ifdef APP_TIMESTAMP_ENABLE +#include "sys_clock.h" +#endif + /* * 总开关:DBG_ENABLE * 定义 → 输出 DBG_ERROR / DBG_INFO;若 DBG_DEBUG_ENABLE 也定义则输出 DBG_DEBUG @@ -48,22 +53,65 @@ extern "C" { #define DBG_TAG "" #endif +#ifdef APP_TIMESTAMP_ENABLE /* - * DBG_ERROR - 错误输出,受 DBG_ENABLE 控制 + * 时间戳输出宏 — 在日志行首追加 [YYYY-MM-DD HH:MM:SS] 格式的系统墙钟时间 + * sys_clock_get_str() 线程安全(内部使用 DWT US 计数,无锁) + */ +#define DBG_TS() sys_clock_get_str((char[32]){0}, 32) + +/* + * DBG_ERROR - 错误输出,受 DBG_ENABLE + APP_TIMESTAMP_ENABLE 控制 + */ +#define DBG_ERROR(fmt, ...) do { \ + char __ts_buf[32]; \ + (void)__ts_buf; \ + DBG_PRINT("[%s]" "[ERR]" DBG_TAG " " fmt "\r\n", \ + sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), ##__VA_ARGS__); \ +} while (0) + +/* + * DBG_INFO - 信息输出,受 DBG_ENABLE + APP_TIMESTAMP_ENABLE 控制 + */ +#define DBG_INFO(fmt, ...) do { \ + char __ts_buf[32]; \ + (void)__ts_buf; \ + DBG_PRINT("[%s]" DBG_TAG " " fmt "\r\n", \ + sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), ##__VA_ARGS__); \ +} while (0) + +/* + * DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE + APP_TIMESTAMP_ENABLE 同时定义 + */ +#ifdef DBG_DEBUG_ENABLE +#define DBG_DEBUG(fmt, ...) do { \ + char __ts_buf[32]; \ + (void)__ts_buf; \ + DBG_PRINT("[%s]" DBG_TAG "(%s:%d) " fmt "\r\n", \ + sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), __func__, __LINE__, ##__VA_ARGS__); \ +} while (0) +#else +#define DBG_DEBUG(fmt, ...) +#endif + +#else /* APP_TIMESTAMP_ENABLE not defined */ + +/* + * DBG_ERROR - 错误输出,受 DBG_ENABLE 控制(无时间戳) */ #define DBG_ERROR(fmt, ...) do { \ DBG_PRINT("[ERR]" DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \ } while (0) /* - * DBG_INFO - 信息输出,受 DBG_ENABLE 控制 + * DBG_INFO - 信息输出,受 DBG_ENABLE 控制(无时间戳) */ #define DBG_INFO(fmt, ...) do { \ DBG_PRINT(DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \ } while (0) /* - * DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE 同时定义 + * DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE 同时定义(无时间戳) */ #ifdef DBG_DEBUG_ENABLE #define DBG_DEBUG(fmt, ...) do { \ @@ -73,6 +121,8 @@ extern "C" { #define DBG_DEBUG(fmt, ...) #endif +#endif /* APP_TIMESTAMP_ENABLE */ + #else /* DBG_ENABLE not defined — all debug output disabled */ #define DBG_ERROR(fmt, ...) diff --git a/Inc/stm32f4xx_it.h b/Inc/stm32f4xx_it.h index bd61a6b..d86a5de 100644 --- a/Inc/stm32f4xx_it.h +++ b/Inc/stm32f4xx_it.h @@ -55,6 +55,7 @@ void DebugMon_Handler(void); void DMA1_Stream3_IRQHandler(void); void DMA1_Stream4_IRQHandler(void); void EXTI9_5_IRQHandler(void); +void SPI1_IRQHandler(void); void SPI2_IRQHandler(void); void USART1_IRQHandler(void); void USART2_IRQHandler(void); @@ -62,7 +63,9 @@ void USART3_IRQHandler(void); void UART4_IRQHandler(void); void UART5_IRQHandler(void); void TIM7_IRQHandler(void); +void DMA2_Stream0_IRQHandler(void); void DMA2_Stream2_IRQHandler(void); +void DMA2_Stream3_IRQHandler(void); void DMA2_Stream7_IRQHandler(void); /* USER CODE BEGIN EFP */ diff --git a/Lib/FatFs/diskio.h b/Lib/FatFs/diskio.h new file mode 100644 index 0000000..1de99c2 --- /dev/null +++ b/Lib/FatFs/diskio.h @@ -0,0 +1,43 @@ +#ifndef _DISKIO_DEFINED +#define _DISKIO_DEFINED + +#ifdef __cplusplus +extern "C" { +#endif + +typedef BYTE DSTATUS; + +typedef enum { + RES_OK = 0, + RES_ERROR, + RES_WRPRT, + RES_NOTRDY, + RES_PARERR +} DRESULT; + +DSTATUS disk_initialize(BYTE pdrv); +DSTATUS disk_status(BYTE pdrv); +DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); +DRESULT disk_write(BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); +DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff); + +#define STA_NOINIT 0x01 +#define STA_NODISK 0x02 +#define STA_PROTECT 0x04 + +#define CTRL_SYNC 0 +#define GET_SECTOR_COUNT 1 +#define GET_SECTOR_SIZE 2 +#define GET_BLOCK_SIZE 3 +#define CTRL_TRIM 4 + +#define CTRL_POWER 5 +#define CTRL_LOCK 6 +#define CTRL_EJECT 7 +#define CTRL_FORMAT 8 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Lib/FatFs/ff.c b/Lib/FatFs/ff.c new file mode 100644 index 0000000..6d412fa --- /dev/null +++ b/Lib/FatFs/ff.c @@ -0,0 +1,7084 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem Module R0.15 w/patch1 / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2022, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: +/ +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/ +/----------------------------------------------------------------------------*/ + + +#include +#include "ff.h" /* Declarations of FatFs API */ +#include "diskio.h" /* Declarations of device I/O functions */ + + +/*-------------------------------------------------------------------------- + + Module Private Definitions + +---------------------------------------------------------------------------*/ + +#if FF_DEFINED != 80286 /* Revision ID */ +#error Wrong include file (ff.h). +#endif + + +/* Limits and boundaries */ +#define MAX_DIR 0x200000 /* Max size of FAT directory */ +#define MAX_DIR_EX 0x10000000 /* Max size of exFAT directory */ +#define MAX_FAT12 0xFF5 /* Max FAT12 clusters (differs from specs, but right for real DOS/Windows behavior) */ +#define MAX_FAT16 0xFFF5 /* Max FAT16 clusters (differs from specs, but right for real DOS/Windows behavior) */ +#define MAX_FAT32 0x0FFFFFF5 /* Max FAT32 clusters (not specified, practical limit) */ +#define MAX_EXFAT 0x7FFFFFFD /* Max exFAT clusters (differs from specs, implementation limit) */ + + +/* Character code support macros */ +#define IsUpper(c) ((c) >= 'A' && (c) <= 'Z') +#define IsLower(c) ((c) >= 'a' && (c) <= 'z') +#define IsDigit(c) ((c) >= '0' && (c) <= '9') +#define IsSeparator(c) ((c) == '/' || (c) == '\\') +#define IsTerminator(c) ((UINT)(c) < (FF_USE_LFN ? ' ' : '!')) +#define IsSurrogate(c) ((c) >= 0xD800 && (c) <= 0xDFFF) +#define IsSurrogateH(c) ((c) >= 0xD800 && (c) <= 0xDBFF) +#define IsSurrogateL(c) ((c) >= 0xDC00 && (c) <= 0xDFFF) + + +/* Additional file access control and file status flags for internal use */ +#define FA_SEEKEND 0x20 /* Seek to end of the file on file open */ +#define FA_MODIFIED 0x40 /* File has been modified */ +#define FA_DIRTY 0x80 /* FIL.buf[] needs to be written-back */ + + +/* Additional file attribute bits for internal use */ +#define AM_VOL 0x08 /* Volume label */ +#define AM_LFN 0x0F /* LFN entry */ +#define AM_MASK 0x3F /* Mask of defined bits in FAT */ +#define AM_MASKX 0x37 /* Mask of defined bits in exFAT */ + + +/* Name status flags in fn[11] */ +#define NSFLAG 11 /* Index of the name status byte */ +#define NS_LOSS 0x01 /* Out of 8.3 format */ +#define NS_LFN 0x02 /* Force to create LFN entry */ +#define NS_LAST 0x04 /* Last segment */ +#define NS_BODY 0x08 /* Lower case flag (body) */ +#define NS_EXT 0x10 /* Lower case flag (ext) */ +#define NS_DOT 0x20 /* Dot entry */ +#define NS_NOLFN 0x40 /* Do not find LFN */ +#define NS_NONAME 0x80 /* Not followed */ + + +/* exFAT directory entry types */ +#define ET_BITMAP 0x81 /* Allocation bitmap */ +#define ET_UPCASE 0x82 /* Up-case table */ +#define ET_VLABEL 0x83 /* Volume label */ +#define ET_FILEDIR 0x85 /* File and directory */ +#define ET_STREAM 0xC0 /* Stream extension */ +#define ET_FILENAME 0xC1 /* Name extension */ + + +/* FatFs refers the FAT structure as simple byte array instead of structure member +/ because the C structure is not binary compatible between different platforms */ + +#define BS_JmpBoot 0 /* x86 jump instruction (3-byte) */ +#define BS_OEMName 3 /* OEM name (8-byte) */ +#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD) */ +#define BPB_SecPerClus 13 /* Cluster size [sector] (BYTE) */ +#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD) */ +#define BPB_NumFATs 16 /* Number of FATs (BYTE) */ +#define BPB_RootEntCnt 17 /* Size of root directory area for FAT [entry] (WORD) */ +#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD) */ +#define BPB_Media 21 /* Media descriptor byte (BYTE) */ +#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD) */ +#define BPB_SecPerTrk 24 /* Number of sectors per track for int13h [sector] (WORD) */ +#define BPB_NumHeads 26 /* Number of heads for int13h (WORD) */ +#define BPB_HiddSec 28 /* Volume offset from top of the drive (DWORD) */ +#define BPB_TotSec32 32 /* Volume size (32-bit) [sector] (DWORD) */ +#define BS_DrvNum 36 /* Physical drive number for int13h (BYTE) */ +#define BS_NTres 37 /* WindowsNT error flag (BYTE) */ +#define BS_BootSig 38 /* Extended boot signature (BYTE) */ +#define BS_VolID 39 /* Volume serial number (DWORD) */ +#define BS_VolLab 43 /* Volume label string (8-byte) */ +#define BS_FilSysType 54 /* Filesystem type string (8-byte) */ +#define BS_BootCode 62 /* Boot code (448-byte) */ +#define BS_55AA 510 /* Signature word (WORD) */ + +#define BPB_FATSz32 36 /* FAT32: FAT size [sector] (DWORD) */ +#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD) */ +#define BPB_FSVer32 42 /* FAT32: Filesystem version (WORD) */ +#define BPB_RootClus32 44 /* FAT32: Root directory cluster (DWORD) */ +#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD) */ +#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD) */ +#define BS_DrvNum32 64 /* FAT32: Physical drive number for int13h (BYTE) */ +#define BS_NTres32 65 /* FAT32: Error flag (BYTE) */ +#define BS_BootSig32 66 /* FAT32: Extended boot signature (BYTE) */ +#define BS_VolID32 67 /* FAT32: Volume serial number (DWORD) */ +#define BS_VolLab32 71 /* FAT32: Volume label string (8-byte) */ +#define BS_FilSysType32 82 /* FAT32: Filesystem type string (8-byte) */ +#define BS_BootCode32 90 /* FAT32: Boot code (420-byte) */ + +#define BPB_ZeroedEx 11 /* exFAT: MBZ field (53-byte) */ +#define BPB_VolOfsEx 64 /* exFAT: Volume offset from top of the drive [sector] (QWORD) */ +#define BPB_TotSecEx 72 /* exFAT: Volume size [sector] (QWORD) */ +#define BPB_FatOfsEx 80 /* exFAT: FAT offset from top of the volume [sector] (DWORD) */ +#define BPB_FatSzEx 84 /* exFAT: FAT size [sector] (DWORD) */ +#define BPB_DataOfsEx 88 /* exFAT: Data offset from top of the volume [sector] (DWORD) */ +#define BPB_NumClusEx 92 /* exFAT: Number of clusters (DWORD) */ +#define BPB_RootClusEx 96 /* exFAT: Root directory start cluster (DWORD) */ +#define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */ +#define BPB_FSVerEx 104 /* exFAT: Filesystem version (WORD) */ +#define BPB_VolFlagEx 106 /* exFAT: Volume flags (WORD) */ +#define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in unit of byte (BYTE) */ +#define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in unit of sector (BYTE) */ +#define BPB_NumFATsEx 110 /* exFAT: Number of FATs (BYTE) */ +#define BPB_DrvNumEx 111 /* exFAT: Physical drive number for int13h (BYTE) */ +#define BPB_PercInUseEx 112 /* exFAT: Percent in use (BYTE) */ +#define BPB_RsvdEx 113 /* exFAT: Reserved (7-byte) */ +#define BS_BootCodeEx 120 /* exFAT: Boot code (390-byte) */ + +#define DIR_Name 0 /* Short file name (11-byte) */ +#define DIR_Attr 11 /* Attribute (BYTE) */ +#define DIR_NTres 12 /* Lower case flag (BYTE) */ +#define DIR_CrtTime10 13 /* Created time sub-second (BYTE) */ +#define DIR_CrtTime 14 /* Created time (DWORD) */ +#define DIR_LstAccDate 18 /* Last accessed date (WORD) */ +#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD) */ +#define DIR_ModTime 22 /* Modified time (DWORD) */ +#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD) */ +#define DIR_FileSize 28 /* File size (DWORD) */ +#define LDIR_Ord 0 /* LFN: LFN order and LLE flag (BYTE) */ +#define LDIR_Attr 11 /* LFN: LFN attribute (BYTE) */ +#define LDIR_Type 12 /* LFN: Entry type (BYTE) */ +#define LDIR_Chksum 13 /* LFN: Checksum of the SFN (BYTE) */ +#define LDIR_FstClusLO 26 /* LFN: MBZ field (WORD) */ +#define XDIR_Type 0 /* exFAT: Type of exFAT directory entry (BYTE) */ +#define XDIR_NumLabel 1 /* exFAT: Number of volume label characters (BYTE) */ +#define XDIR_Label 2 /* exFAT: Volume label (11-WORD) */ +#define XDIR_CaseSum 4 /* exFAT: Sum of case conversion table (DWORD) */ +#define XDIR_NumSec 1 /* exFAT: Number of secondary entries (BYTE) */ +#define XDIR_SetSum 2 /* exFAT: Sum of the set of directory entries (WORD) */ +#define XDIR_Attr 4 /* exFAT: File attribute (WORD) */ +#define XDIR_CrtTime 8 /* exFAT: Created time (DWORD) */ +#define XDIR_ModTime 12 /* exFAT: Modified time (DWORD) */ +#define XDIR_AccTime 16 /* exFAT: Last accessed time (DWORD) */ +#define XDIR_CrtTime10 20 /* exFAT: Created time subsecond (BYTE) */ +#define XDIR_ModTime10 21 /* exFAT: Modified time subsecond (BYTE) */ +#define XDIR_CrtTZ 22 /* exFAT: Created timezone (BYTE) */ +#define XDIR_ModTZ 23 /* exFAT: Modified timezone (BYTE) */ +#define XDIR_AccTZ 24 /* exFAT: Last accessed timezone (BYTE) */ +#define XDIR_GenFlags 33 /* exFAT: General secondary flags (BYTE) */ +#define XDIR_NumName 35 /* exFAT: Number of file name characters (BYTE) */ +#define XDIR_NameHash 36 /* exFAT: Hash of file name (WORD) */ +#define XDIR_ValidFileSize 40 /* exFAT: Valid file size (QWORD) */ +#define XDIR_FstClus 52 /* exFAT: First cluster of the file data (DWORD) */ +#define XDIR_FileSize 56 /* exFAT: File/Directory size (QWORD) */ + +#define SZDIRE 32 /* Size of a directory entry */ +#define DDEM 0xE5 /* Deleted directory entry mark set to DIR_Name[0] */ +#define RDDEM 0x05 /* Replacement of the character collides with DDEM */ +#define LLEF 0x40 /* Last long entry flag in LDIR_Ord */ + +#define FSI_LeadSig 0 /* FAT32 FSI: Leading signature (DWORD) */ +#define FSI_StrucSig 484 /* FAT32 FSI: Structure signature (DWORD) */ +#define FSI_Free_Count 488 /* FAT32 FSI: Number of free clusters (DWORD) */ +#define FSI_Nxt_Free 492 /* FAT32 FSI: Last allocated cluster (DWORD) */ + +#define MBR_Table 446 /* MBR: Offset of partition table in the MBR */ +#define SZ_PTE 16 /* MBR: Size of a partition table entry */ +#define PTE_Boot 0 /* MBR PTE: Boot indicator */ +#define PTE_StHead 1 /* MBR PTE: Start head */ +#define PTE_StSec 2 /* MBR PTE: Start sector */ +#define PTE_StCyl 3 /* MBR PTE: Start cylinder */ +#define PTE_System 4 /* MBR PTE: System ID */ +#define PTE_EdHead 5 /* MBR PTE: End head */ +#define PTE_EdSec 6 /* MBR PTE: End sector */ +#define PTE_EdCyl 7 /* MBR PTE: End cylinder */ +#define PTE_StLba 8 /* MBR PTE: Start in LBA */ +#define PTE_SizLba 12 /* MBR PTE: Size in LBA */ + +#define GPTH_Sign 0 /* GPT HDR: Signature (8-byte) */ +#define GPTH_Rev 8 /* GPT HDR: Revision (DWORD) */ +#define GPTH_Size 12 /* GPT HDR: Header size (DWORD) */ +#define GPTH_Bcc 16 /* GPT HDR: Header BCC (DWORD) */ +#define GPTH_CurLba 24 /* GPT HDR: This header LBA (QWORD) */ +#define GPTH_BakLba 32 /* GPT HDR: Another header LBA (QWORD) */ +#define GPTH_FstLba 40 /* GPT HDR: First LBA for partition data (QWORD) */ +#define GPTH_LstLba 48 /* GPT HDR: Last LBA for partition data (QWORD) */ +#define GPTH_DskGuid 56 /* GPT HDR: Disk GUID (16-byte) */ +#define GPTH_PtOfs 72 /* GPT HDR: Partition table LBA (QWORD) */ +#define GPTH_PtNum 80 /* GPT HDR: Number of table entries (DWORD) */ +#define GPTH_PteSize 84 /* GPT HDR: Size of table entry (DWORD) */ +#define GPTH_PtBcc 88 /* GPT HDR: Partition table BCC (DWORD) */ +#define SZ_GPTE 128 /* GPT PTE: Size of partition table entry */ +#define GPTE_PtGuid 0 /* GPT PTE: Partition type GUID (16-byte) */ +#define GPTE_UpGuid 16 /* GPT PTE: Partition unique GUID (16-byte) */ +#define GPTE_FstLba 32 /* GPT PTE: First LBA of partition (QWORD) */ +#define GPTE_LstLba 40 /* GPT PTE: Last LBA of partition (QWORD) */ +#define GPTE_Flags 48 /* GPT PTE: Partition flags (QWORD) */ +#define GPTE_Name 56 /* GPT PTE: Partition name */ + + +/* Post process on fatal error in the file operations */ +#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); } + + +/* Re-entrancy related */ +#if FF_FS_REENTRANT +#if FF_USE_LFN == 1 +#error Static LFN work area cannot be used in thread-safe configuration +#endif +#define LEAVE_FF(fs, res) { unlock_volume(fs, res); return res; } +#else +#define LEAVE_FF(fs, res) return res +#endif + + +/* Definitions of logical drive - physical location conversion */ +#if FF_MULTI_PARTITION +#define LD2PD(vol) VolToPart[vol].pd /* Get physical drive number */ +#define LD2PT(vol) VolToPart[vol].pt /* Get partition number (0:auto search, 1..:forced partition number) */ +#else +#define LD2PD(vol) (BYTE)(vol) /* Each logical drive is associated with the same physical drive number */ +#define LD2PT(vol) 0 /* Auto partition search */ +#endif + + +/* Definitions of sector size */ +#if (FF_MAX_SS < FF_MIN_SS) || (FF_MAX_SS != 512 && FF_MAX_SS != 1024 && FF_MAX_SS != 2048 && FF_MAX_SS != 4096) || (FF_MIN_SS != 512 && FF_MIN_SS != 1024 && FF_MIN_SS != 2048 && FF_MIN_SS != 4096) +#error Wrong sector size configuration +#endif +#if FF_MAX_SS == FF_MIN_SS +#define SS(fs) ((UINT)FF_MAX_SS) /* Fixed sector size */ +#else +#define SS(fs) ((fs)->ssize) /* Variable sector size */ +#endif + + +/* Timestamp */ +#if FF_FS_NORTC == 1 +#if FF_NORTC_YEAR < 1980 || FF_NORTC_YEAR > 2107 || FF_NORTC_MON < 1 || FF_NORTC_MON > 12 || FF_NORTC_MDAY < 1 || FF_NORTC_MDAY > 31 +#error Invalid FF_FS_NORTC settings +#endif +#define GET_FATTIME() ((DWORD)(FF_NORTC_YEAR - 1980) << 25 | (DWORD)FF_NORTC_MON << 21 | (DWORD)FF_NORTC_MDAY << 16) +#else +#define GET_FATTIME() get_fattime() +#endif + + +/* File lock controls */ +#if FF_FS_LOCK +#if FF_FS_READONLY +#error FF_FS_LOCK must be 0 at read-only configuration +#endif +typedef struct { + FATFS* fs; /* Object ID 1, volume (NULL:blank entry) */ + DWORD clu; /* Object ID 2, containing directory (0:root) */ + DWORD ofs; /* Object ID 3, offset in the directory */ + UINT ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */ +} FILESEM; +#endif + + +/* SBCS up-case tables (\x80-\xFF) */ +#define TBL_CT437 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT720 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT737 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \ + 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xEF,0xF5,0xF0,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT771 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDC,0xDE,0xDE, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFE,0xFF} +#define TBL_CT775 {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT850 {0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41, \ + 0x45,0x92,0x92,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x9C,0x4F,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0x45,0x45,0x45,0x49,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF, \ + 0x4F,0xE1,0x4F,0x4F,0x4F,0x4F,0xE6,0xE8,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT852 {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0xAC, \ + 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF} +#define TBL_CT855 {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F, \ + 0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \ + 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \ + 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF, \ + 0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT857 {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0x49,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT860 {0x80,0x9A,0x90,0x8F,0x8E,0x91,0x86,0x80,0x89,0x89,0x92,0x8B,0x8C,0x98,0x8E,0x8F, \ + 0x90,0x91,0x92,0x8C,0x99,0xA9,0x96,0x9D,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x86,0x8B,0x9F,0x96,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT861 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x8B,0x8B,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x8D,0x55,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA4,0xA5,0xA6,0xA7,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT862 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT863 {0x43,0x55,0x45,0x41,0x41,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x41,0x8F, \ + 0x45,0x45,0x45,0x4F,0x45,0x49,0x55,0x55,0x98,0x4F,0x55,0x9B,0x9C,0x55,0x55,0x9F, \ + 0xA0,0xA1,0x4F,0x55,0xA4,0xA5,0xA6,0xA7,0x49,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT864 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT865 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT866 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT869 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x86,0x9C,0x8D,0x8F,0x90, \ + 0x91,0x90,0x92,0x95,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xA4,0xA5,0xA6,0xD9,0xDA,0xDB,0xDC,0xA7,0xA8,0xDF, \ + 0xA9,0xAA,0xAC,0xAD,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xCF,0xCF,0xD0,0xEF, \ + 0xF0,0xF1,0xD1,0xD2,0xD3,0xF5,0xD4,0xF7,0xF8,0xF9,0xD5,0x96,0x95,0x98,0xFE,0xFF} + + +/* DBCS code range |----- 1st byte -----| |----------- 2nd byte -----------| */ +/* <------> <------> <------> <------> <------> */ +#define TBL_DC932 {0x81, 0x9F, 0xE0, 0xFC, 0x40, 0x7E, 0x80, 0xFC, 0x00, 0x00} +#define TBL_DC936 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0x80, 0xFE, 0x00, 0x00} +#define TBL_DC949 {0x81, 0xFE, 0x00, 0x00, 0x41, 0x5A, 0x61, 0x7A, 0x81, 0xFE} +#define TBL_DC950 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0xA1, 0xFE, 0x00, 0x00} + + +/* Macros for table definitions */ +#define MERGE_2STR(a, b) a ## b +#define MKCVTBL(hd, cp) MERGE_2STR(hd, cp) + + + + +/*-------------------------------------------------------------------------- + + Module Private Work Area + +---------------------------------------------------------------------------*/ +/* Remark: Variables defined here without initial value shall be guaranteed +/ zero/null at start-up. If not, the linker option or start-up routine is +/ not compliance with C standard. */ + +/*--------------------------------*/ +/* File/Volume controls */ +/*--------------------------------*/ + +#if FF_VOLUMES < 1 || FF_VOLUMES > 10 +#error Wrong FF_VOLUMES setting +#endif +static FATFS *FatFs[FF_VOLUMES]; /* Pointer to the filesystem objects (logical drives) */ +static WORD Fsid; /* Filesystem mount ID */ + +#if FF_FS_RPATH != 0 +static BYTE CurrVol; /* Current drive set by f_chdrive() */ +#endif + +#if FF_FS_LOCK != 0 +static FILESEM Files[FF_FS_LOCK]; /* Open object lock semaphores */ +#if FF_FS_REENTRANT +static BYTE SysLock; /* System lock flag (0:no mutex, 1:unlocked, 2:locked) */ +#endif +#endif + +#if FF_STR_VOLUME_ID +#ifdef FF_VOLUME_STRS +static const char *const VolumeStr[FF_VOLUMES] = {FF_VOLUME_STRS}; /* Pre-defined volume ID */ +#endif +#endif + +#if FF_LBA64 +#if FF_MIN_GPT > 0x100000000 +#error Wrong FF_MIN_GPT setting +#endif +static const BYTE GUID_MS_Basic[16] = {0xA2,0xA0,0xD0,0xEB,0xE5,0xB9,0x33,0x44,0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7}; +#endif + + + +/*--------------------------------*/ +/* LFN/Directory working buffer */ +/*--------------------------------*/ + +#if FF_USE_LFN == 0 /* Non-LFN configuration */ +#if FF_FS_EXFAT +#error LFN must be enabled when enable exFAT +#endif +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() +#define LEAVE_MKFS(res) return res + +#else /* LFN configurations */ +#if FF_MAX_LFN < 12 || FF_MAX_LFN > 255 +#error Wrong setting of FF_MAX_LFN +#endif +#if FF_LFN_BUF < FF_SFN_BUF || FF_SFN_BUF < 12 +#error Wrong setting of FF_LFN_BUF or FF_SFN_BUF +#endif +#if FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3 +#error Wrong setting of FF_LFN_UNICODE +#endif +static const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* FAT: Offset of LFN characters in the directory entry */ +#define MAXDIRB(nc) ((nc + 44U) / 15 * SZDIRE) /* exFAT: Size of directory entry block scratchpad buffer needed for the name length */ + +#if FF_USE_LFN == 1 /* LFN enabled with static working buffer */ +#if FF_FS_EXFAT +static BYTE DirBuf[MAXDIRB(FF_MAX_LFN)]; /* Directory entry block scratchpad buffer */ +#endif +static WCHAR LfnBuf[FF_MAX_LFN + 1]; /* LFN working buffer */ +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() +#define LEAVE_MKFS(res) return res + +#elif FF_USE_LFN == 2 /* LFN enabled with dynamic working buffer on the stack */ +#if FF_FS_EXFAT +#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; BYTE dbuf[MAXDIRB(FF_MAX_LFN)]; /* LFN working buffer and directory entry block scratchpad buffer */ +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; (fs)->dirbuf = dbuf; } +#define FREE_NAMBUF() +#else +#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; /* LFN working buffer */ +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; } +#define FREE_NAMBUF() +#endif +#define LEAVE_MKFS(res) return res + +#elif FF_USE_LFN == 3 /* LFN enabled with dynamic working buffer on the heap */ +#if FF_FS_EXFAT +#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer and directory entry block scratchpad buffer */ +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2 + MAXDIRB(FF_MAX_LFN)); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+FF_MAX_LFN+1); } +#define FREE_NAMBUF() ff_memfree(lfn) +#else +#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer */ +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; } +#define FREE_NAMBUF() ff_memfree(lfn) +#endif +#define LEAVE_MKFS(res) { if (!work) ff_memfree(buf); return res; } +#define MAX_MALLOC 0x8000 /* Must be >=FF_MAX_SS */ + +#else +#error Wrong setting of FF_USE_LFN + +#endif /* FF_USE_LFN == 1 */ +#endif /* FF_USE_LFN == 0 */ + + + +/*--------------------------------*/ +/* Code conversion tables */ +/*--------------------------------*/ + +#if FF_CODE_PAGE == 0 /* Run-time code page configuration */ +#define CODEPAGE CodePage +static WORD CodePage; /* Current code page */ +static const BYTE* ExCvt; /* Ptr to SBCS up-case table Ct???[] (null:not used) */ +static const BYTE* DbcTbl; /* Ptr to DBCS code range table Dc???[] (null:not used) */ + +static const BYTE Ct437[] = TBL_CT437; +static const BYTE Ct720[] = TBL_CT720; +static const BYTE Ct737[] = TBL_CT737; +static const BYTE Ct771[] = TBL_CT771; +static const BYTE Ct775[] = TBL_CT775; +static const BYTE Ct850[] = TBL_CT850; +static const BYTE Ct852[] = TBL_CT852; +static const BYTE Ct855[] = TBL_CT855; +static const BYTE Ct857[] = TBL_CT857; +static const BYTE Ct860[] = TBL_CT860; +static const BYTE Ct861[] = TBL_CT861; +static const BYTE Ct862[] = TBL_CT862; +static const BYTE Ct863[] = TBL_CT863; +static const BYTE Ct864[] = TBL_CT864; +static const BYTE Ct865[] = TBL_CT865; +static const BYTE Ct866[] = TBL_CT866; +static const BYTE Ct869[] = TBL_CT869; +static const BYTE Dc932[] = TBL_DC932; +static const BYTE Dc936[] = TBL_DC936; +static const BYTE Dc949[] = TBL_DC949; +static const BYTE Dc950[] = TBL_DC950; + +#elif FF_CODE_PAGE < 900 /* Static code page configuration (SBCS) */ +#define CODEPAGE FF_CODE_PAGE +static const BYTE ExCvt[] = MKCVTBL(TBL_CT, FF_CODE_PAGE); + +#else /* Static code page configuration (DBCS) */ +#define CODEPAGE FF_CODE_PAGE +static const BYTE DbcTbl[] = MKCVTBL(TBL_DC, FF_CODE_PAGE); + +#endif + + + + +/*-------------------------------------------------------------------------- + + Module Private Functions + +---------------------------------------------------------------------------*/ + + +/*-----------------------------------------------------------------------*/ +/* Load/Store multi-byte word in the FAT structure */ +/*-----------------------------------------------------------------------*/ + +static WORD ld_word (const BYTE* ptr) /* Load a 2-byte little-endian word */ +{ + WORD rv; + + rv = ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} + +static DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */ +{ + DWORD rv; + + rv = ptr[3]; + rv = rv << 8 | ptr[2]; + rv = rv << 8 | ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} + +#if FF_FS_EXFAT +static QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */ +{ + QWORD rv; + + rv = ptr[7]; + rv = rv << 8 | ptr[6]; + rv = rv << 8 | ptr[5]; + rv = rv << 8 | ptr[4]; + rv = rv << 8 | ptr[3]; + rv = rv << 8 | ptr[2]; + rv = rv << 8 | ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} +#endif + +#if !FF_FS_READONLY +static void st_word (BYTE* ptr, WORD val) /* Store a 2-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} + +static void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} + +#if FF_FS_EXFAT +static void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} +#endif +#endif /* !FF_FS_READONLY */ + + + +/*-----------------------------------------------------------------------*/ +/* String functions */ +/*-----------------------------------------------------------------------*/ + +/* Test if the byte is DBC 1st byte */ +static int dbc_1st (BYTE c) +{ +#if FF_CODE_PAGE == 0 /* Variable code page */ + if (DbcTbl && c >= DbcTbl[0]) { + if (c <= DbcTbl[1]) return 1; /* 1st byte range 1 */ + if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; /* 1st byte range 2 */ + } +#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */ + if (c >= DbcTbl[0]) { + if (c <= DbcTbl[1]) return 1; + if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; + } +#else /* SBCS fixed code page */ + if (c != 0) return 0; /* Always false */ +#endif + return 0; +} + + +/* Test if the byte is DBC 2nd byte */ +static int dbc_2nd (BYTE c) +{ +#if FF_CODE_PAGE == 0 /* Variable code page */ + if (DbcTbl && c >= DbcTbl[4]) { + if (c <= DbcTbl[5]) return 1; /* 2nd byte range 1 */ + if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; /* 2nd byte range 2 */ + if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; /* 2nd byte range 3 */ + } +#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */ + if (c >= DbcTbl[4]) { + if (c <= DbcTbl[5]) return 1; + if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; + if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; + } +#else /* SBCS fixed code page */ + if (c != 0) return 0; /* Always false */ +#endif + return 0; +} + + +#if FF_USE_LFN + +/* Get a Unicode code point from the TCHAR string in defined API encodeing */ +static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on surrogate pair, 0xFFFFFFFF on decode error) */ + const TCHAR** str /* Pointer to pointer to TCHAR string in configured encoding */ +) +{ + DWORD uc; + const TCHAR *p = *str; + +#if FF_LFN_UNICODE == 1 /* UTF-16 input */ + WCHAR wc; + + uc = *p++; /* Get a unit */ + if (IsSurrogate(uc)) { /* Surrogate? */ + wc = *p++; /* Get low surrogate */ + if (!IsSurrogateH(uc) || !IsSurrogateL(wc)) return 0xFFFFFFFF; /* Wrong surrogate? */ + uc = uc << 16 | wc; + } + +#elif FF_LFN_UNICODE == 2 /* UTF-8 input */ + BYTE b; + int nf; + + uc = (BYTE)*p++; /* Get an encoding unit */ + if (uc & 0x80) { /* Multiple byte code? */ + if ((uc & 0xE0) == 0xC0) { /* 2-byte sequence? */ + uc &= 0x1F; nf = 1; + } else if ((uc & 0xF0) == 0xE0) { /* 3-byte sequence? */ + uc &= 0x0F; nf = 2; + } else if ((uc & 0xF8) == 0xF0) { /* 4-byte sequence? */ + uc &= 0x07; nf = 3; + } else { /* Wrong sequence */ + return 0xFFFFFFFF; + } + do { /* Get trailing bytes */ + b = (BYTE)*p++; + if ((b & 0xC0) != 0x80) return 0xFFFFFFFF; /* Wrong sequence? */ + uc = uc << 6 | (b & 0x3F); + } while (--nf != 0); + if (uc < 0x80 || IsSurrogate(uc) || uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */ + if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ + } + +#elif FF_LFN_UNICODE == 3 /* UTF-32 input */ + uc = (TCHAR)*p++; /* Get a unit */ + if (uc >= 0x110000 || IsSurrogate(uc)) return 0xFFFFFFFF; /* Wrong code? */ + if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ + +#else /* ANSI/OEM input */ + BYTE b; + WCHAR wc; + + wc = (BYTE)*p++; /* Get a byte */ + if (dbc_1st((BYTE)wc)) { /* Is it a DBC 1st byte? */ + b = (BYTE)*p++; /* Get 2nd byte */ + if (!dbc_2nd(b)) return 0xFFFFFFFF; /* Invalid code? */ + wc = (wc << 8) + b; /* Make a DBC */ + } + if (wc != 0) { + wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM ==> Unicode */ + if (wc == 0) return 0xFFFFFFFF; /* Invalid code? */ + } + uc = wc; + +#endif + *str = p; /* Next read pointer */ + return uc; +} + + +/* Store a Unicode char in defined API encoding */ +static UINT put_utf ( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */ + DWORD chr, /* UTF-16 encoded character (Surrogate pair if >=0x10000) */ + TCHAR* buf, /* Output buffer */ + UINT szb /* Size of the buffer */ +) +{ +#if FF_LFN_UNICODE == 1 /* UTF-16 output */ + WCHAR hs, wc; + + hs = (WCHAR)(chr >> 16); + wc = (WCHAR)chr; + if (hs == 0) { /* Single encoding unit? */ + if (szb < 1 || IsSurrogate(wc)) return 0; /* Buffer overflow or wrong code? */ + *buf = wc; + return 1; + } + if (szb < 2 || !IsSurrogateH(hs) || !IsSurrogateL(wc)) return 0; /* Buffer overflow or wrong surrogate? */ + *buf++ = hs; + *buf++ = wc; + return 2; + +#elif FF_LFN_UNICODE == 2 /* UTF-8 output */ + DWORD hc; + + if (chr < 0x80) { /* Single byte code? */ + if (szb < 1) return 0; /* Buffer overflow? */ + *buf = (TCHAR)chr; + return 1; + } + if (chr < 0x800) { /* 2-byte sequence? */ + if (szb < 2) return 0; /* Buffer overflow? */ + *buf++ = (TCHAR)(0xC0 | (chr >> 6 & 0x1F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 2; + } + if (chr < 0x10000) { /* 3-byte sequence? */ + if (szb < 3 || IsSurrogate(chr)) return 0; /* Buffer overflow or wrong code? */ + *buf++ = (TCHAR)(0xE0 | (chr >> 12 & 0x0F)); + *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 3; + } + /* 4-byte sequence */ + if (szb < 4) return 0; /* Buffer overflow? */ + hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */ + chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ + if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ + chr = (hc | chr) + 0x10000; + *buf++ = (TCHAR)(0xF0 | (chr >> 18 & 0x07)); + *buf++ = (TCHAR)(0x80 | (chr >> 12 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 4; + +#elif FF_LFN_UNICODE == 3 /* UTF-32 output */ + DWORD hc; + + if (szb < 1) return 0; /* Buffer overflow? */ + if (chr >= 0x10000) { /* Out of BMP? */ + hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */ + chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ + if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ + chr = (hc | chr) + 0x10000; + } + *buf++ = (TCHAR)chr; + return 1; + +#else /* ANSI/OEM output */ + WCHAR wc; + + wc = ff_uni2oem(chr, CODEPAGE); + if (wc >= 0x100) { /* Is this a DBC? */ + if (szb < 2) return 0; + *buf++ = (char)(wc >> 8); /* Store DBC 1st byte */ + *buf++ = (TCHAR)wc; /* Store DBC 2nd byte */ + return 2; + } + if (wc == 0 || szb < 1) return 0; /* Invalid char or buffer overflow? */ + *buf++ = (TCHAR)wc; /* Store the character */ + return 1; +#endif +} +#endif /* FF_USE_LFN */ + + +#if FF_FS_REENTRANT +/*-----------------------------------------------------------------------*/ +/* Request/Release grant to access the volume */ +/*-----------------------------------------------------------------------*/ + +static int lock_volume ( /* 1:Ok, 0:timeout */ + FATFS* fs, /* Filesystem object to lock */ + int syslock /* System lock required */ +) +{ + int rv; + + +#if FF_FS_LOCK + rv = ff_mutex_take(fs->ldrv); /* Lock the volume */ + if (rv && syslock) { /* System lock reqiered? */ + rv = ff_mutex_take(FF_VOLUMES); /* Lock the system */ + if (rv) { + SysLock = 2; /* System lock succeeded */ + } else { + ff_mutex_give(fs->ldrv); /* Failed system lock */ + } + } +#else + rv = syslock ? ff_mutex_take(fs->ldrv) : ff_mutex_take(fs->ldrv); /* Lock the volume (this is to prevent compiler warning) */ +#endif + return rv; +} + + +static void unlock_volume ( + FATFS* fs, /* Filesystem object */ + FRESULT res /* Result code to be returned */ +) +{ + if (fs && res != FR_NOT_ENABLED && res != FR_INVALID_DRIVE && res != FR_TIMEOUT) { +#if FF_FS_LOCK + if (SysLock == 2) { /* Is the system locked? */ + SysLock = 1; + ff_mutex_give(FF_VOLUMES); + } +#endif + ff_mutex_give(fs->ldrv); /* Unlock the volume */ + } +} + +#endif + + + +#if FF_FS_LOCK +/*-----------------------------------------------------------------------*/ +/* File shareing control functions */ +/*-----------------------------------------------------------------------*/ + +static FRESULT chk_share ( /* Check if the file can be accessed */ + DIR* dp, /* Directory object pointing the file to be checked */ + int acc /* Desired access type (0:Read mode open, 1:Write mode open, 2:Delete or rename) */ +) +{ + UINT i, be; + + /* Search open object table for the object */ + be = 0; + for (i = 0; i < FF_FS_LOCK; i++) { + if (Files[i].fs) { /* Existing entry */ + if (Files[i].fs == dp->obj.fs && /* Check if the object matches with an open object */ + Files[i].clu == dp->obj.sclust && + Files[i].ofs == dp->dptr) break; + } else { /* Blank entry */ + be = 1; + } + } + if (i == FF_FS_LOCK) { /* The object has not been opened */ + return (!be && acc != 2) ? FR_TOO_MANY_OPEN_FILES : FR_OK; /* Is there a blank entry for new object? */ + } + + /* The object was opened. Reject any open against writing file and all write mode open */ + return (acc != 0 || Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK; +} + + +static int enq_share (void) /* Check if an entry is available for a new object */ +{ + UINT i; + + for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ; /* Find a free entry */ + return (i == FF_FS_LOCK) ? 0 : 1; +} + + +static UINT inc_share ( /* Increment object open counter and returns its index (0:Internal error) */ + DIR* dp, /* Directory object pointing the file to register or increment */ + int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */ +) +{ + UINT i; + + + for (i = 0; i < FF_FS_LOCK; i++) { /* Find the object */ + if (Files[i].fs == dp->obj.fs + && Files[i].clu == dp->obj.sclust + && Files[i].ofs == dp->dptr) break; + } + + if (i == FF_FS_LOCK) { /* Not opened. Register it as new. */ + for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ; /* Find a free entry */ + if (i == FF_FS_LOCK) return 0; /* No free entry to register (int err) */ + Files[i].fs = dp->obj.fs; + Files[i].clu = dp->obj.sclust; + Files[i].ofs = dp->dptr; + Files[i].ctr = 0; + } + + if (acc >= 1 && Files[i].ctr) return 0; /* Access violation (int err) */ + + Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */ + + return i + 1; /* Index number origin from 1 */ +} + + +static FRESULT dec_share ( /* Decrement object open counter */ + UINT i /* Semaphore index (1..) */ +) +{ + UINT n; + FRESULT res; + + + if (--i < FF_FS_LOCK) { /* Index number origin from 0 */ + n = Files[i].ctr; + if (n == 0x100) n = 0; /* If write mode open, delete the object semaphore */ + if (n > 0) n--; /* Decrement read mode open count */ + Files[i].ctr = n; + if (n == 0) { /* Delete the object semaphore if open count becomes zero */ + Files[i].fs = 0; /* Free the entry << 1, there is a potential error in this process >>> */ + } + res = FR_OK; + } else { + res = FR_INT_ERR; /* Invalid index number */ + } + return res; +} + + +static void clear_share ( /* Clear all lock entries of the volume */ + FATFS* fs +) +{ + UINT i; + + for (i = 0; i < FF_FS_LOCK; i++) { + if (Files[i].fs == fs) Files[i].fs = 0; + } +} + +#endif /* FF_FS_LOCK */ + + + +/*-----------------------------------------------------------------------*/ +/* Move/Flush disk access window in the filesystem object */ +/*-----------------------------------------------------------------------*/ +#if !FF_FS_READONLY +static FRESULT sync_window ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs /* Filesystem object */ +) +{ + FRESULT res = FR_OK; + + + if (fs->wflag) { /* Is the disk access window dirty? */ + if (disk_write(fs->pdrv, fs->win, fs->winsect, 1) == RES_OK) { /* Write it back into the volume */ + fs->wflag = 0; /* Clear window dirty flag */ + if (fs->winsect - fs->fatbase < fs->fsize) { /* Is it in the 1st FAT? */ + if (fs->n_fats == 2) disk_write(fs->pdrv, fs->win, fs->winsect + fs->fsize, 1); /* Reflect it to 2nd FAT if needed */ + } + } else { + res = FR_DISK_ERR; + } + } + return res; +} +#endif + + +static FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs, /* Filesystem object */ + LBA_t sect /* Sector LBA to make appearance in the fs->win[] */ +) +{ + FRESULT res = FR_OK; + + + if (sect != fs->winsect) { /* Window offset changed? */ +#if !FF_FS_READONLY + res = sync_window(fs); /* Flush the window */ +#endif + if (res == FR_OK) { /* Fill sector window with new data */ + if (disk_read(fs->pdrv, fs->win, sect, 1) != RES_OK) { + sect = (LBA_t)0 - 1; /* Invalidate window if read data is not valid */ + res = FR_DISK_ERR; + } + fs->winsect = sect; + } + } + return res; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Synchronize filesystem and data on the storage */ +/*-----------------------------------------------------------------------*/ + +static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs /* Filesystem object */ +) +{ + FRESULT res; + + + res = sync_window(fs); + if (res == FR_OK) { + if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */ + /* Create FSInfo structure */ + memset(fs->win, 0, sizeof fs->win); + st_word(fs->win + BS_55AA, 0xAA55); /* Boot signature */ + st_dword(fs->win + FSI_LeadSig, 0x41615252); /* Leading signature */ + st_dword(fs->win + FSI_StrucSig, 0x61417272); /* Structure signature */ + st_dword(fs->win + FSI_Free_Count, fs->free_clst); /* Number of free clusters */ + st_dword(fs->win + FSI_Nxt_Free, fs->last_clst); /* Last allocated culuster */ + fs->winsect = fs->volbase + 1; /* Write it into the FSInfo sector (Next to VBR) */ + disk_write(fs->pdrv, fs->win, fs->winsect, 1); + fs->fsi_flag = 0; + } + /* Make sure that no pending write process in the lower layer */ + if (disk_ioctl(fs->pdrv, CTRL_SYNC, 0) != RES_OK) res = FR_DISK_ERR; + } + + return res; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Get physical sector number from cluster number */ +/*-----------------------------------------------------------------------*/ + +static LBA_t clst2sect ( /* !=0:Sector number, 0:Failed (invalid cluster#) */ + FATFS* fs, /* Filesystem object */ + DWORD clst /* Cluster# to be converted */ +) +{ + clst -= 2; /* Cluster number is origin from 2 */ + if (clst >= fs->n_fatent - 2) return 0; /* Is it invalid cluster number? */ + return fs->database + (LBA_t)fs->csize * clst; /* Start sector number of the cluster */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Read value of an FAT entry */ +/*-----------------------------------------------------------------------*/ + +static DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */ + FFOBJID* obj, /* Corresponding object */ + DWORD clst /* Cluster number to get the value */ +) +{ + UINT wc, bc; + DWORD val; + FATFS *fs = obj->fs; + + + if (clst < 2 || clst >= fs->n_fatent) { /* Check if in valid range */ + val = 1; /* Internal error */ + + } else { + val = 0xFFFFFFFF; /* Default value falls on disk error */ + + switch (fs->fs_type) { + case FS_FAT12 : + bc = (UINT)clst; bc += bc / 2; + if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break; + wc = fs->win[bc++ % SS(fs)]; /* Get 1st byte of the entry */ + if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break; + wc |= fs->win[bc % SS(fs)] << 8; /* Merge 2nd byte of the entry */ + val = (clst & 1) ? (wc >> 4) : (wc & 0xFFF); /* Adjust bit position */ + break; + + case FS_FAT16 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))) != FR_OK) break; + val = ld_word(fs->win + clst * 2 % SS(fs)); /* Simple WORD array */ + break; + + case FS_FAT32 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break; + val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x0FFFFFFF; /* Simple DWORD array but mask out upper 4 bits */ + break; +#if FF_FS_EXFAT + case FS_EXFAT : + if ((obj->objsize != 0 && obj->sclust != 0) || obj->stat == 0) { /* Object except root dir must have valid data length */ + DWORD cofs = clst - obj->sclust; /* Offset from start cluster */ + DWORD clen = (DWORD)((LBA_t)((obj->objsize - 1) / SS(fs)) / fs->csize); /* Number of clusters - 1 */ + + if (obj->stat == 2 && cofs <= clen) { /* Is it a contiguous chain? */ + val = (cofs == clen) ? 0x7FFFFFFF : clst + 1; /* No data on the FAT, generate the value */ + break; + } + if (obj->stat == 3 && cofs < obj->n_cont) { /* Is it in the 1st fragment? */ + val = clst + 1; /* Generate the value */ + break; + } + if (obj->stat != 2) { /* Get value from FAT if FAT chain is valid */ + if (obj->n_frag != 0) { /* Is it on the growing edge? */ + val = 0x7FFFFFFF; /* Generate EOC */ + } else { + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break; + val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x7FFFFFFF; + } + break; + } + } + val = 1; /* Internal error */ + break; +#endif + default: + val = 1; /* Internal error */ + } + } + + return val; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT access - Change value of an FAT entry */ +/*-----------------------------------------------------------------------*/ + +static FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */ + FATFS* fs, /* Corresponding filesystem object */ + DWORD clst, /* FAT index number (cluster number) to be changed */ + DWORD val /* New value to be set to the entry */ +) +{ + UINT bc; + BYTE *p; + FRESULT res = FR_INT_ERR; + + + if (clst >= 2 && clst < fs->n_fatent) { /* Check if in valid range */ + switch (fs->fs_type) { + case FS_FAT12: + bc = (UINT)clst; bc += bc / 2; /* bc: byte offset of the entry */ + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = fs->win + bc++ % SS(fs); + *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; /* Update 1st byte */ + fs->wflag = 1; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = fs->win + bc % SS(fs); + *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); /* Update 2nd byte */ + fs->wflag = 1; + break; + + case FS_FAT16: + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))); + if (res != FR_OK) break; + st_word(fs->win + clst * 2 % SS(fs), (WORD)val); /* Simple WORD array */ + fs->wflag = 1; + break; + + case FS_FAT32: +#if FF_FS_EXFAT + case FS_EXFAT: +#endif + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))); + if (res != FR_OK) break; + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { + val = (val & 0x0FFFFFFF) | (ld_dword(fs->win + clst * 4 % SS(fs)) & 0xF0000000); + } + st_dword(fs->win + clst * 4 % SS(fs), val); + fs->wflag = 1; + break; + } + } + return res; +} + +#endif /* !FF_FS_READONLY */ + + + + +#if FF_FS_EXFAT && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* exFAT: Accessing FAT and Allocation Bitmap */ +/*-----------------------------------------------------------------------*/ + +/*--------------------------------------*/ +/* Find a contiguous free cluster block */ +/*--------------------------------------*/ + +static DWORD find_bitmap ( /* 0:Not found, 2..:Cluster block found, 0xFFFFFFFF:Disk error */ + FATFS* fs, /* Filesystem object */ + DWORD clst, /* Cluster number to scan from */ + DWORD ncl /* Number of contiguous clusters to find (1..) */ +) +{ + BYTE bm, bv; + UINT i; + DWORD val, scl, ctr; + + + clst -= 2; /* The first bit in the bitmap corresponds to cluster #2 */ + if (clst >= fs->n_fatent - 2) clst = 0; + scl = val = clst; ctr = 0; + for (;;) { + if (move_window(fs, fs->bitbase + val / 8 / SS(fs)) != FR_OK) return 0xFFFFFFFF; + i = val / 8 % SS(fs); bm = 1 << (val % 8); + do { + do { + bv = fs->win[i] & bm; bm <<= 1; /* Get bit value */ + if (++val >= fs->n_fatent - 2) { /* Next cluster (with wrap-around) */ + val = 0; bm = 0; i = SS(fs); + } + if (bv == 0) { /* Is it a free cluster? */ + if (++ctr == ncl) return scl + 2; /* Check if run length is sufficient for required */ + } else { + scl = val; ctr = 0; /* Encountered a cluster in-use, restart to scan */ + } + if (val == clst) return 0; /* All cluster scanned? */ + } while (bm != 0); + bm = 1; + } while (++i < SS(fs)); + } +} + + +/*----------------------------------------*/ +/* Set/Clear a block of allocation bitmap */ +/*----------------------------------------*/ + +static FRESULT change_bitmap ( + FATFS* fs, /* Filesystem object */ + DWORD clst, /* Cluster number to change from */ + DWORD ncl, /* Number of clusters to be changed */ + int bv /* bit value to be set (0 or 1) */ +) +{ + BYTE bm; + UINT i; + LBA_t sect; + + + clst -= 2; /* The first bit corresponds to cluster #2 */ + sect = fs->bitbase + clst / 8 / SS(fs); /* Sector address */ + i = clst / 8 % SS(fs); /* Byte offset in the sector */ + bm = 1 << (clst % 8); /* Bit mask in the byte */ + for (;;) { + if (move_window(fs, sect++) != FR_OK) return FR_DISK_ERR; + do { + do { + if (bv == (int)((fs->win[i] & bm) != 0)) return FR_INT_ERR; /* Is the bit expected value? */ + fs->win[i] ^= bm; /* Flip the bit */ + fs->wflag = 1; + if (--ncl == 0) return FR_OK; /* All bits processed? */ + } while (bm <<= 1); /* Next bit */ + bm = 1; + } while (++i < SS(fs)); /* Next byte */ + i = 0; + } +} + + +/*---------------------------------------------*/ +/* Fill the first fragment of the FAT chain */ +/*---------------------------------------------*/ + +static FRESULT fill_first_frag ( + FFOBJID* obj /* Pointer to the corresponding object */ +) +{ + FRESULT res; + DWORD cl, n; + + + if (obj->stat == 3) { /* Has the object been changed 'fragmented' in this session? */ + for (cl = obj->sclust, n = obj->n_cont; n; cl++, n--) { /* Create cluster chain on the FAT */ + res = put_fat(obj->fs, cl, cl + 1); + if (res != FR_OK) return res; + } + obj->stat = 0; /* Change status 'FAT chain is valid' */ + } + return FR_OK; +} + + +/*---------------------------------------------*/ +/* Fill the last fragment of the FAT chain */ +/*---------------------------------------------*/ + +static FRESULT fill_last_frag ( + FFOBJID* obj, /* Pointer to the corresponding object */ + DWORD lcl, /* Last cluster of the fragment */ + DWORD term /* Value to set the last FAT entry */ +) +{ + FRESULT res; + + + while (obj->n_frag > 0) { /* Create the chain of last fragment */ + res = put_fat(obj->fs, lcl - obj->n_frag + 1, (obj->n_frag > 1) ? lcl - obj->n_frag + 2 : term); + if (res != FR_OK) return res; + obj->n_frag--; + } + return FR_OK; +} + +#endif /* FF_FS_EXFAT && !FF_FS_READONLY */ + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT handling - Remove a cluster chain */ +/*-----------------------------------------------------------------------*/ + +static FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */ + FFOBJID* obj, /* Corresponding object */ + DWORD clst, /* Cluster to remove a chain from */ + DWORD pclst /* Previous cluster of clst (0 if entire chain) */ +) +{ + FRESULT res = FR_OK; + DWORD nxt; + FATFS *fs = obj->fs; +#if FF_FS_EXFAT || FF_USE_TRIM + DWORD scl = clst, ecl = clst; +#endif +#if FF_USE_TRIM + LBA_t rt[2]; +#endif + + if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Check if in valid range */ + + /* Mark the previous cluster 'EOC' on the FAT if it exists */ + if (pclst != 0 && (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT || obj->stat != 2)) { + res = put_fat(fs, pclst, 0xFFFFFFFF); + if (res != FR_OK) return res; + } + + /* Remove the chain */ + do { + nxt = get_fat(obj, clst); /* Get cluster status */ + if (nxt == 0) break; /* Empty cluster? */ + if (nxt == 1) return FR_INT_ERR; /* Internal error? */ + if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error? */ + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { + res = put_fat(fs, clst, 0); /* Mark the cluster 'free' on the FAT */ + if (res != FR_OK) return res; + } + if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */ + fs->free_clst++; + fs->fsi_flag |= 1; + } +#if FF_FS_EXFAT || FF_USE_TRIM + if (ecl + 1 == nxt) { /* Is next cluster contiguous? */ + ecl = nxt; + } else { /* End of contiguous cluster block */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */ + if (res != FR_OK) return res; + } +#endif +#if FF_USE_TRIM + rt[0] = clst2sect(fs, scl); /* Start of data area to be freed */ + rt[1] = clst2sect(fs, ecl) + fs->csize - 1; /* End of data area to be freed */ + disk_ioctl(fs->pdrv, CTRL_TRIM, rt); /* Inform storage device that the data in the block may be erased */ +#endif + scl = ecl = nxt; + } +#endif + clst = nxt; /* Next cluster */ + } while (clst < fs->n_fatent); /* Repeat while not the last link */ + +#if FF_FS_EXFAT + /* Some post processes for chain status */ + if (fs->fs_type == FS_EXFAT) { + if (pclst == 0) { /* Has the entire chain been removed? */ + obj->stat = 0; /* Change the chain status 'initial' */ + } else { + if (obj->stat == 0) { /* Is it a fragmented chain from the beginning of this session? */ + clst = obj->sclust; /* Follow the chain to check if it gets contiguous */ + while (clst != pclst) { + nxt = get_fat(obj, clst); + if (nxt < 2) return FR_INT_ERR; + if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; + if (nxt != clst + 1) break; /* Not contiguous? */ + clst++; + } + if (clst == pclst) { /* Has the chain got contiguous again? */ + obj->stat = 2; /* Change the chain status 'contiguous' */ + } + } else { + if (obj->stat == 3 && pclst >= obj->sclust && pclst <= obj->sclust + obj->n_cont) { /* Was the chain fragmented in this session and got contiguous again? */ + obj->stat = 2; /* Change the chain status 'contiguous' */ + } + } + } + } +#endif + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Stretch a chain or Create a new chain */ +/*-----------------------------------------------------------------------*/ + +static DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */ + FFOBJID* obj, /* Corresponding object */ + DWORD clst /* Cluster# to stretch, 0:Create a new chain */ +) +{ + DWORD cs, ncl, scl; + FRESULT res; + FATFS *fs = obj->fs; + + + if (clst == 0) { /* Create a new chain */ + scl = fs->last_clst; /* Suggested cluster to start to find */ + if (scl == 0 || scl >= fs->n_fatent) scl = 1; + } + else { /* Stretch a chain */ + cs = get_fat(obj, clst); /* Check the cluster status */ + if (cs < 2) return 1; /* Test for insanity */ + if (cs == 0xFFFFFFFF) return cs; /* Test for disk error */ + if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */ + scl = clst; /* Cluster to start to find */ + } + if (fs->free_clst == 0) return 0; /* No free cluster */ + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + ncl = find_bitmap(fs, scl, 1); /* Find a free cluster */ + if (ncl == 0 || ncl == 0xFFFFFFFF) return ncl; /* No free cluster or hard error? */ + res = change_bitmap(fs, ncl, 1, 1); /* Mark the cluster 'in use' */ + if (res == FR_INT_ERR) return 1; + if (res == FR_DISK_ERR) return 0xFFFFFFFF; + if (clst == 0) { /* Is it a new chain? */ + obj->stat = 2; /* Set status 'contiguous' */ + } else { /* It is a stretched chain */ + if (obj->stat == 2 && ncl != scl + 1) { /* Is the chain got fragmented? */ + obj->n_cont = scl - obj->sclust; /* Set size of the contiguous part */ + obj->stat = 3; /* Change status 'just fragmented' */ + } + } + if (obj->stat != 2) { /* Is the file non-contiguous? */ + if (ncl == clst + 1) { /* Is the cluster next to previous one? */ + obj->n_frag = obj->n_frag ? obj->n_frag + 1 : 2; /* Increment size of last framgent */ + } else { /* New fragment */ + if (obj->n_frag == 0) obj->n_frag = 1; + res = fill_last_frag(obj, clst, ncl); /* Fill last fragment on the FAT and link it to new one */ + if (res == FR_OK) obj->n_frag = 1; + } + } + } else +#endif + { /* On the FAT/FAT32 volume */ + ncl = 0; + if (scl == clst) { /* Stretching an existing chain? */ + ncl = scl + 1; /* Test if next cluster is free */ + if (ncl >= fs->n_fatent) ncl = 2; + cs = get_fat(obj, ncl); /* Get next cluster status */ + if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */ + if (cs != 0) { /* Not free? */ + cs = fs->last_clst; /* Start at suggested cluster if it is valid */ + if (cs >= 2 && cs < fs->n_fatent) scl = cs; + ncl = 0; + } + } + if (ncl == 0) { /* The new cluster cannot be contiguous and find another fragment */ + ncl = scl; /* Start cluster */ + for (;;) { + ncl++; /* Next cluster */ + if (ncl >= fs->n_fatent) { /* Check wrap-around */ + ncl = 2; + if (ncl > scl) return 0; /* No free cluster found? */ + } + cs = get_fat(obj, ncl); /* Get the cluster status */ + if (cs == 0) break; /* Found a free cluster? */ + if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */ + if (ncl == scl) return 0; /* No free cluster found? */ + } + } + res = put_fat(fs, ncl, 0xFFFFFFFF); /* Mark the new cluster 'EOC' */ + if (res == FR_OK && clst != 0) { + res = put_fat(fs, clst, ncl); /* Link it from the previous one if needed */ + } + } + + if (res == FR_OK) { /* Update FSINFO if function succeeded. */ + fs->last_clst = ncl; + if (fs->free_clst <= fs->n_fatent - 2) fs->free_clst--; + fs->fsi_flag |= 1; + } else { + ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; /* Failed. Generate error status */ + } + + return ncl; /* Return new cluster number or error status */ +} + +#endif /* !FF_FS_READONLY */ + + + + +#if FF_USE_FASTSEEK +/*-----------------------------------------------------------------------*/ +/* FAT handling - Convert offset into cluster with link map table */ +/*-----------------------------------------------------------------------*/ + +static DWORD clmt_clust ( /* <2:Error, >=2:Cluster number */ + FIL* fp, /* Pointer to the file object */ + FSIZE_t ofs /* File offset to be converted to cluster# */ +) +{ + DWORD cl, ncl; + DWORD *tbl; + FATFS *fs = fp->obj.fs; + + + tbl = fp->cltbl + 1; /* Top of CLMT */ + cl = (DWORD)(ofs / SS(fs) / fs->csize); /* Cluster order from top of the file */ + for (;;) { + ncl = *tbl++; /* Number of cluters in the fragment */ + if (ncl == 0) return 0; /* End of table? (error) */ + if (cl < ncl) break; /* In this fragment? */ + cl -= ncl; tbl++; /* Next fragment */ + } + return cl + *tbl; /* Return the cluster number */ +} + +#endif /* FF_USE_FASTSEEK */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Fill a cluster with zeros */ +/*-----------------------------------------------------------------------*/ + +#if !FF_FS_READONLY +static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS *fs, /* Filesystem object */ + DWORD clst /* Directory table to clear */ +) +{ + LBA_t sect; + UINT n, szb; + BYTE *ibuf; + + + if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */ + sect = clst2sect(fs, clst); /* Top of the cluster */ + fs->winsect = sect; /* Set window to top of the cluster */ + memset(fs->win, 0, sizeof fs->win); /* Clear window buffer */ +#if FF_USE_LFN == 3 /* Quick table clear by using multi-secter write */ + /* Allocate a temporary buffer */ + for (szb = ((DWORD)fs->csize * SS(fs) >= MAX_MALLOC) ? MAX_MALLOC : fs->csize * SS(fs), ibuf = 0; szb > SS(fs) && (ibuf = ff_memalloc(szb)) == 0; szb /= 2) ; + if (szb > SS(fs)) { /* Buffer allocated? */ + memset(ibuf, 0, szb); + szb /= SS(fs); /* Bytes -> Sectors */ + for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */ + ff_memfree(ibuf); + } else +#endif + { + ibuf = fs->win; szb = 1; /* Use window buffer (many single-sector writes may take a time) */ + for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */ + } + return (n == fs->csize) ? FR_OK : FR_DISK_ERR; +} +#endif /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Set directory index */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp, /* Pointer to directory object */ + DWORD ofs /* Offset of directory table */ +) +{ + DWORD csz, clst; + FATFS *fs = dp->obj.fs; + + + if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR) || ofs % SZDIRE) { /* Check range of offset and alignment */ + return FR_INT_ERR; + } + dp->dptr = ofs; /* Set current offset */ + clst = dp->obj.sclust; /* Table start cluster (0:root) */ + if (clst == 0 && fs->fs_type >= FS_FAT32) { /* Replace cluster# 0 with root cluster# */ + clst = (DWORD)fs->dirbase; + if (FF_FS_EXFAT) dp->obj.stat = 0; /* exFAT: Root dir has an FAT chain */ + } + + if (clst == 0) { /* Static table (root-directory on the FAT volume) */ + if (ofs / SZDIRE >= fs->n_rootdir) return FR_INT_ERR; /* Is index out of range? */ + dp->sect = fs->dirbase; + + } else { /* Dynamic table (sub-directory or root-directory on the FAT32/exFAT volume) */ + csz = (DWORD)fs->csize * SS(fs); /* Bytes per cluster */ + while (ofs >= csz) { /* Follow cluster chain */ + clst = get_fat(&dp->obj, clst); /* Get next cluster */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Reached to end of table or internal error */ + ofs -= csz; + } + dp->sect = clst2sect(fs, clst); + } + dp->clust = clst; /* Current cluster# */ + if (dp->sect == 0) return FR_INT_ERR; + dp->sect += ofs / SS(fs); /* Sector# of the directory entry */ + dp->dir = fs->win + (ofs % SS(fs)); /* Pointer to the entry in the win[] */ + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Move directory table index next */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */ + DIR* dp, /* Pointer to the directory object */ + int stretch /* 0: Do not stretch table, 1: Stretch table if needed */ +) +{ + DWORD ofs, clst; + FATFS *fs = dp->obj.fs; + + + ofs = dp->dptr + SZDIRE; /* Next entry */ + if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR)) dp->sect = 0; /* Disable it if the offset reached the max value */ + if (dp->sect == 0) return FR_NO_FILE; /* Report EOT if it has been disabled */ + + if (ofs % SS(fs) == 0) { /* Sector changed? */ + dp->sect++; /* Next sector */ + + if (dp->clust == 0) { /* Static table */ + if (ofs / SZDIRE >= fs->n_rootdir) { /* Report EOT if it reached end of static table */ + dp->sect = 0; return FR_NO_FILE; + } + } + else { /* Dynamic table */ + if ((ofs / SS(fs) & (fs->csize - 1)) == 0) { /* Cluster changed? */ + clst = get_fat(&dp->obj, dp->clust); /* Get next cluster */ + if (clst <= 1) return FR_INT_ERR; /* Internal error */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst >= fs->n_fatent) { /* It reached end of dynamic table */ +#if !FF_FS_READONLY + if (!stretch) { /* If no stretch, report EOT */ + dp->sect = 0; return FR_NO_FILE; + } + clst = create_chain(&dp->obj, dp->clust); /* Allocate a cluster */ + if (clst == 0) return FR_DENIED; /* No free cluster */ + if (clst == 1) return FR_INT_ERR; /* Internal error */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (dir_clear(fs, clst) != FR_OK) return FR_DISK_ERR; /* Clean up the stretched table */ + if (FF_FS_EXFAT) dp->obj.stat |= 4; /* exFAT: The directory has been stretched */ +#else + if (!stretch) dp->sect = 0; /* (this line is to suppress compiler warning) */ + dp->sect = 0; return FR_NO_FILE; /* Report EOT */ +#endif + } + dp->clust = clst; /* Initialize data for new cluster */ + dp->sect = clst2sect(fs, clst); + } + } + } + dp->dptr = ofs; /* Current entry */ + dp->dir = fs->win + ofs % SS(fs); /* Pointer to the entry in the win[] */ + + return FR_OK; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Directory handling - Reserve a block of directory entries */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp, /* Pointer to the directory object */ + UINT n_ent /* Number of contiguous entries to allocate */ +) +{ + FRESULT res; + UINT n; + FATFS *fs = dp->obj.fs; + + + res = dir_sdi(dp, 0); + if (res == FR_OK) { + n = 0; + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; +#if FF_FS_EXFAT + if ((fs->fs_type == FS_EXFAT) ? (int)((dp->dir[XDIR_Type] & 0x80) == 0) : (int)(dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0)) { /* Is the entry free? */ +#else + if (dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0) { /* Is the entry free? */ +#endif + if (++n == n_ent) break; /* Is a block of contiguous free entries found? */ + } else { + n = 0; /* Not a free entry, restart to search */ + } + res = dir_next(dp, 1); /* Next entry with table stretch enabled */ + } while (res == FR_OK); + } + + if (res == FR_NO_FILE) res = FR_DENIED; /* No directory entry to allocate */ + return res; +} + +#endif /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* FAT: Directory handling - Load/Store start cluster number */ +/*-----------------------------------------------------------------------*/ + +static DWORD ld_clust ( /* Returns the top cluster value of the SFN entry */ + FATFS* fs, /* Pointer to the fs object */ + const BYTE* dir /* Pointer to the key entry */ +) +{ + DWORD cl; + + cl = ld_word(dir + DIR_FstClusLO); + if (fs->fs_type == FS_FAT32) { + cl |= (DWORD)ld_word(dir + DIR_FstClusHI) << 16; + } + + return cl; +} + + +#if !FF_FS_READONLY +static void st_clust ( + FATFS* fs, /* Pointer to the fs object */ + BYTE* dir, /* Pointer to the key entry */ + DWORD cl /* Value to be set */ +) +{ + st_word(dir + DIR_FstClusLO, (WORD)cl); + if (fs->fs_type == FS_FAT32) { + st_word(dir + DIR_FstClusHI, (WORD)(cl >> 16)); + } +} +#endif + + + +#if FF_USE_LFN +/*--------------------------------------------------------*/ +/* FAT-LFN: Compare a part of file name with an LFN entry */ +/*--------------------------------------------------------*/ + +static int cmp_lfn ( /* 1:matched, 0:not matched */ + const WCHAR* lfnbuf, /* Pointer to the LFN working buffer to be compared */ + BYTE* dir /* Pointer to the directory entry containing the part of LFN */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */ + + i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */ + + for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ + uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + if (wc != 0) { + if (i >= FF_MAX_LFN + 1 || ff_wtoupper(uc) != ff_wtoupper(lfnbuf[i++])) { /* Compare it */ + return 0; /* Not matched */ + } + wc = uc; + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } + + if ((dir[LDIR_Ord] & LLEF) && wc && lfnbuf[i]) return 0; /* Last segment matched but different length */ + + return 1; /* The part of LFN matched */ +} + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT +/*-----------------------------------------------------*/ +/* FAT-LFN: Pick a part of file name from an LFN entry */ +/*-----------------------------------------------------*/ + +static int pick_lfn ( /* 1:succeeded, 0:buffer overflow or invalid LFN entry */ + WCHAR* lfnbuf, /* Pointer to the LFN working buffer */ + BYTE* dir /* Pointer to the LFN entry */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO is 0 */ + + i = ((dir[LDIR_Ord] & ~LLEF) - 1) * 13; /* Offset in the LFN buffer */ + + for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ + uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + if (wc != 0) { + if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */ + lfnbuf[i++] = wc = uc; /* Store it */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } + + if (dir[LDIR_Ord] & LLEF && wc != 0) { /* Put terminator if it is the last LFN part and not terminated */ + if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */ + lfnbuf[i] = 0; + } + + return 1; /* The part of LFN is valid */ +} +#endif + + +#if !FF_FS_READONLY +/*-----------------------------------------*/ +/* FAT-LFN: Create an entry of LFN entries */ +/*-----------------------------------------*/ + +static void put_lfn ( + const WCHAR* lfn, /* Pointer to the LFN */ + BYTE* dir, /* Pointer to the LFN entry to be created */ + BYTE ord, /* LFN order (1-20) */ + BYTE sum /* Checksum of the corresponding SFN */ +) +{ + UINT i, s; + WCHAR wc; + + + dir[LDIR_Chksum] = sum; /* Set checksum */ + dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */ + dir[LDIR_Type] = 0; + st_word(dir + LDIR_FstClusLO, 0); + + i = (ord - 1) * 13; /* Get offset in the LFN working buffer */ + s = wc = 0; + do { + if (wc != 0xFFFF) wc = lfn[i++]; /* Get an effective character */ + st_word(dir + LfnOfs[s], wc); /* Put it */ + if (wc == 0) wc = 0xFFFF; /* Padding characters for following items */ + } while (++s < 13); + if (wc == 0xFFFF || !lfn[i]) ord |= LLEF; /* Last LFN part is the start of LFN sequence */ + dir[LDIR_Ord] = ord; /* Set the LFN order */ +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_USE_LFN */ + + + +#if FF_USE_LFN && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT-LFN: Create a Numbered SFN */ +/*-----------------------------------------------------------------------*/ + +static void gen_numname ( + BYTE* dst, /* Pointer to the buffer to store numbered SFN */ + const BYTE* src, /* Pointer to SFN in directory form */ + const WCHAR* lfn, /* Pointer to LFN */ + UINT seq /* Sequence number */ +) +{ + BYTE ns[8], c; + UINT i, j; + WCHAR wc; + DWORD sreg; + + + memcpy(dst, src, 11); /* Prepare the SFN to be modified */ + + if (seq > 5) { /* In case of many collisions, generate a hash number instead of sequential number */ + sreg = seq; + while (*lfn) { /* Create a CRC as hash value */ + wc = *lfn++; + for (i = 0; i < 16; i++) { + sreg = (sreg << 1) + (wc & 1); + wc >>= 1; + if (sreg & 0x10000) sreg ^= 0x11021; + } + } + seq = (UINT)sreg; + } + + /* Make suffix (~ + hexadecimal) */ + i = 7; + do { + c = (BYTE)((seq % 16) + '0'); seq /= 16; + if (c > '9') c += 7; + ns[i--] = c; + } while (i && seq); + ns[i] = '~'; + + /* Append the suffix to the SFN body */ + for (j = 0; j < i && dst[j] != ' '; j++) { /* Find the offset to append */ + if (dbc_1st(dst[j])) { /* To avoid DBC break up */ + if (j == i - 1) break; + j++; + } + } + do { /* Append the suffix */ + dst[j++] = (i < 8) ? ns[i++] : ' '; + } while (j < 8); +} +#endif /* FF_USE_LFN && !FF_FS_READONLY */ + + + +#if FF_USE_LFN +/*-----------------------------------------------------------------------*/ +/* FAT-LFN: Calculate checksum of an SFN entry */ +/*-----------------------------------------------------------------------*/ + +static BYTE sum_sfn ( + const BYTE* dir /* Pointer to the SFN entry */ +) +{ + BYTE sum = 0; + UINT n = 11; + + do { + sum = (sum >> 1) + (sum << 7) + *dir++; + } while (--n); + return sum; +} + +#endif /* FF_USE_LFN */ + + + +#if FF_FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* exFAT: Checksum */ +/*-----------------------------------------------------------------------*/ + +static WORD xdir_sum ( /* Get checksum of the directoly entry block */ + const BYTE* dir /* Directory entry block to be calculated */ +) +{ + UINT i, szblk; + WORD sum; + + + szblk = (dir[XDIR_NumSec] + 1) * SZDIRE; /* Number of bytes of the entry block */ + for (i = sum = 0; i < szblk; i++) { + if (i == XDIR_SetSum) { /* Skip 2-byte sum field */ + i++; + } else { + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + dir[i]; + } + } + return sum; +} + + + +static WORD xname_sum ( /* Get check sum (to be used as hash) of the file name */ + const WCHAR* name /* File name to be calculated */ +) +{ + WCHAR chr; + WORD sum = 0; + + + while ((chr = *name++) != 0) { + chr = (WCHAR)ff_wtoupper(chr); /* File name needs to be up-case converted */ + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr & 0xFF); + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr >> 8); + } + return sum; +} + + +#if !FF_FS_READONLY && FF_USE_MKFS +static DWORD xsum32 ( /* Returns 32-bit checksum */ + BYTE dat, /* Byte to be calculated (byte-by-byte processing) */ + DWORD sum /* Previous sum value */ +) +{ + sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + dat; + return sum; +} +#endif + + + +/*------------------------------------*/ +/* exFAT: Get a directory entry block */ +/*------------------------------------*/ + +static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ + DIR* dp /* Reading directory object pointing top of the entry block to load */ +) +{ + FRESULT res; + UINT i, sz_ent; + BYTE *dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory directory entry block 85+C0+C1s */ + + + /* Load file directory entry */ + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != ET_FILEDIR) return FR_INT_ERR; /* Invalid order */ + memcpy(dirb + 0 * SZDIRE, dp->dir, SZDIRE); + sz_ent = (dirb[XDIR_NumSec] + 1) * SZDIRE; + if (sz_ent < 3 * SZDIRE || sz_ent > 19 * SZDIRE) return FR_INT_ERR; + + /* Load stream extension entry */ + res = dir_next(dp, 0); + if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */ + if (res != FR_OK) return res; + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != ET_STREAM) return FR_INT_ERR; /* Invalid order */ + memcpy(dirb + 1 * SZDIRE, dp->dir, SZDIRE); + if (MAXDIRB(dirb[XDIR_NumName]) > sz_ent) return FR_INT_ERR; + + /* Load file name entries */ + i = 2 * SZDIRE; /* Name offset to load */ + do { + res = dir_next(dp, 0); + if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */ + if (res != FR_OK) return res; + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != ET_FILENAME) return FR_INT_ERR; /* Invalid order */ + if (i < MAXDIRB(FF_MAX_LFN)) memcpy(dirb + i, dp->dir, SZDIRE); + } while ((i += SZDIRE) < sz_ent); + + /* Sanity check (do it for only accessible object) */ + if (i <= MAXDIRB(FF_MAX_LFN)) { + if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR; + } + return FR_OK; +} + + +/*------------------------------------------------------------------*/ +/* exFAT: Initialize object allocation info with loaded entry block */ +/*------------------------------------------------------------------*/ + +static void init_alloc_info ( + FATFS* fs, /* Filesystem object */ + FFOBJID* obj /* Object allocation information to be initialized */ +) +{ + obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Start cluster */ + obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize); /* Size */ + obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; /* Allocation status */ + obj->n_frag = 0; /* No last fragment info */ +} + + + +#if !FF_FS_READONLY || FF_FS_RPATH != 0 +/*------------------------------------------------*/ +/* exFAT: Load the object's directory entry block */ +/*------------------------------------------------*/ + +static FRESULT load_obj_xdir ( + DIR* dp, /* Blank directory object to be used to access containing directory */ + const FFOBJID* obj /* Object with its containing directory information */ +) +{ + FRESULT res; + + /* Open object containing directory */ + dp->obj.fs = obj->fs; + dp->obj.sclust = obj->c_scl; + dp->obj.stat = (BYTE)obj->c_size; + dp->obj.objsize = obj->c_size & 0xFFFFFF00; + dp->obj.n_frag = 0; + dp->blk_ofs = obj->c_ofs; + + res = dir_sdi(dp, dp->blk_ofs); /* Goto object's entry block */ + if (res == FR_OK) { + res = load_xdir(dp); /* Load the object's entry block */ + } + return res; +} +#endif + + +#if !FF_FS_READONLY +/*----------------------------------------*/ +/* exFAT: Store the directory entry block */ +/*----------------------------------------*/ + +static FRESULT store_xdir ( + DIR* dp /* Pointer to the directory object */ +) +{ + FRESULT res; + UINT nent; + BYTE *dirb = dp->obj.fs->dirbuf; /* Pointer to the directory entry block 85+C0+C1s */ + + /* Create set sum */ + st_word(dirb + XDIR_SetSum, xdir_sum(dirb)); + nent = dirb[XDIR_NumSec] + 1; + + /* Store the directory entry block to the directory */ + res = dir_sdi(dp, dp->blk_ofs); + while (res == FR_OK) { + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) break; + memcpy(dp->dir, dirb, SZDIRE); + dp->obj.fs->wflag = 1; + if (--nent == 0) break; + dirb += SZDIRE; + res = dir_next(dp, 0); + } + return (res == FR_OK || res == FR_DISK_ERR) ? res : FR_INT_ERR; +} + + + +/*-------------------------------------------*/ +/* exFAT: Create a new directory entry block */ +/*-------------------------------------------*/ + +static void create_xdir ( + BYTE* dirb, /* Pointer to the directory entry block buffer */ + const WCHAR* lfn /* Pointer to the object name */ +) +{ + UINT i; + BYTE nc1, nlen; + WCHAR wc; + + + /* Create file-directory and stream-extension entry */ + memset(dirb, 0, 2 * SZDIRE); + dirb[0 * SZDIRE + XDIR_Type] = ET_FILEDIR; + dirb[1 * SZDIRE + XDIR_Type] = ET_STREAM; + + /* Create file-name entries */ + i = SZDIRE * 2; /* Top of file_name entries */ + nlen = nc1 = 0; wc = 1; + do { + dirb[i++] = ET_FILENAME; dirb[i++] = 0; + do { /* Fill name field */ + if (wc != 0 && (wc = lfn[nlen]) != 0) nlen++; /* Get a character if exist */ + st_word(dirb + i, wc); /* Store it */ + i += 2; + } while (i % SZDIRE != 0); + nc1++; + } while (lfn[nlen]); /* Fill next entry if any char follows */ + + dirb[XDIR_NumName] = nlen; /* Set name length */ + dirb[XDIR_NumSec] = 1 + nc1; /* Set secondary count (C0 + C1s) */ + st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */ +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_FS_EXFAT */ + + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* Read an object from the directory */ +/*-----------------------------------------------------------------------*/ + +#define DIR_READ_FILE(dp) dir_read(dp, 0) +#define DIR_READ_LABEL(dp) dir_read(dp, 1) + +static FRESULT dir_read ( + DIR* dp, /* Pointer to the directory object */ + int vol /* Filtered by 0:file/directory or 1:volume label */ +) +{ + FRESULT res = FR_NO_FILE; + FATFS *fs = dp->obj.fs; + BYTE attr, b; +#if FF_USE_LFN + BYTE ord = 0xFF, sum = 0xFF; +#endif + + while (dp->sect) { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + b = dp->dir[DIR_Name]; /* Test for the entry type */ + if (b == 0) { + res = FR_NO_FILE; break; /* Reached to end of the directory */ + } +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + if (FF_USE_LABEL && vol) { + if (b == ET_VLABEL) break; /* Volume label entry? */ + } else { + if (b == ET_FILEDIR) { /* Start of the file entry block? */ + dp->blk_ofs = dp->dptr; /* Get location of the block */ + res = load_xdir(dp); /* Load the entry block */ + if (res == FR_OK) { + dp->obj.attr = fs->dirbuf[XDIR_Attr] & AM_MASK; /* Get attribute */ + } + break; + } + } + } else +#endif + { /* On the FAT/FAT32 volume */ + dp->obj.attr = attr = dp->dir[DIR_Attr] & AM_MASK; /* Get attribute */ +#if FF_USE_LFN /* LFN configuration */ + if (b == DDEM || b == '.' || (int)((attr & ~AM_ARC) == AM_VOL) != vol) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (attr == AM_LFN) { /* An LFN entry is found */ + if (b & LLEF) { /* Is it start of an LFN sequence? */ + sum = dp->dir[LDIR_Chksum]; + b &= (BYTE)~LLEF; ord = b; + dp->blk_ofs = dp->dptr; + } + /* Check LFN validity and capture it */ + ord = (b == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; + } else { /* An SFN entry is found */ + if (ord != 0 || sum != sum_sfn(dp->dir)) { /* Is there a valid LFN? */ + dp->blk_ofs = 0xFFFFFFFF; /* It has no LFN. */ + } + break; + } + } +#else /* Non LFN configuration */ + if (b != DDEM && b != '.' && attr != AM_LFN && (int)((attr & ~AM_ARC) == AM_VOL) == vol) { /* Is it a valid entry? */ + break; + } +#endif + } + res = dir_next(dp, 0); /* Next entry */ + if (res != FR_OK) break; + } + + if (res != FR_OK) dp->sect = 0; /* Terminate the read operation on error or EOT */ + return res; +} + +#endif /* FF_FS_MINIMIZE <= 1 || FF_USE_LABEL || FF_FS_RPATH >= 2 */ + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Find an object in the directory */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp /* Pointer to the directory object with the file name */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; + BYTE c; +#if FF_USE_LFN + BYTE a, ord, sum; +#endif + + res = dir_sdi(dp, 0); /* Rewind directory object */ + if (res != FR_OK) return res; +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + BYTE nc; + UINT di, ni; + WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */ + + while ((res = DIR_READ_FILE(dp)) == FR_OK) { /* Read an item */ +#if FF_MAX_LFN < 255 + if (fs->dirbuf[XDIR_NumName] > FF_MAX_LFN) continue; /* Skip comparison if inaccessible object name */ +#endif + if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash mismatched */ + for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */ + if ((di % SZDIRE) == 0) di += 2; + if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break; + } + if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */ + } + return res; + } +#endif + /* On the FAT/FAT32 volume */ +#if FF_USE_LFN + ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ +#endif + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + c = dp->dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ +#if FF_USE_LFN /* LFN configuration */ + dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK; + if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (!(dp->fn[NSFLAG] & NS_NOLFN)) { + if (c & LLEF) { /* Is it start of LFN sequence? */ + sum = dp->dir[LDIR_Chksum]; + c &= (BYTE)~LLEF; ord = c; /* LFN start order */ + dp->blk_ofs = dp->dptr; /* Start offset of LFN */ + } + /* Check validity of the LFN entry and compare it with given name */ + ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; + } + } else { /* An SFN entry is found */ + if (ord == 0 && sum == sum_sfn(dp->dir)) break; /* LFN matched? */ + if (!(dp->fn[NSFLAG] & NS_LOSS) && !memcmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */ + ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ + } + } +#else /* Non LFN configuration */ + dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK; + if (!(dp->dir[DIR_Attr] & AM_VOL) && !memcmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */ +#endif + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + + return res; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Register an object to the directory */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */ + DIR* dp /* Target directory with object name to be created */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; +#if FF_USE_LFN /* LFN configuration */ + UINT n, len, n_ent; + BYTE sn[12], sum; + + + if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME; /* Check name validity */ + for (len = 0; fs->lfnbuf[len]; len++) ; /* Get lfn length */ + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + n_ent = (len + 14) / 15 + 2; /* Number of entries to allocate (85+C0+C1s) */ + res = dir_alloc(dp, n_ent); /* Allocate directory entries */ + if (res != FR_OK) return res; + dp->blk_ofs = dp->dptr - SZDIRE * (n_ent - 1); /* Set the allocated entry block offset */ + + if (dp->obj.stat & 4) { /* Has the directory been stretched by new allocation? */ + dp->obj.stat &= ~4; + res = fill_first_frag(&dp->obj); /* Fill the first fragment on the FAT if needed */ + if (res != FR_OK) return res; + res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF); /* Fill the last fragment on the FAT if needed */ + if (res != FR_OK) return res; + if (dp->obj.sclust != 0) { /* Is it a sub-directory? */ + DIR dj; + + res = load_obj_xdir(&dj, &dp->obj); /* Load the object status */ + if (res != FR_OK) return res; + dp->obj.objsize += (DWORD)fs->csize * SS(fs); /* Increase the directory size by cluster size */ + st_qword(fs->dirbuf + XDIR_FileSize, dp->obj.objsize); + st_qword(fs->dirbuf + XDIR_ValidFileSize, dp->obj.objsize); + fs->dirbuf[XDIR_GenFlags] = dp->obj.stat | 1; /* Update the allocation status */ + res = store_xdir(&dj); /* Store the object status */ + if (res != FR_OK) return res; + } + } + + create_xdir(fs->dirbuf, fs->lfnbuf); /* Create on-memory directory block to be written later */ + return FR_OK; + } +#endif + /* On the FAT/FAT32 volume */ + memcpy(sn, dp->fn, 12); + if (sn[NSFLAG] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */ + dp->fn[NSFLAG] = NS_NOLFN; /* Find only SFN */ + for (n = 1; n < 100; n++) { + gen_numname(dp->fn, sn, fs->lfnbuf, n); /* Generate a numbered name */ + res = dir_find(dp); /* Check if the name collides with existing SFN */ + if (res != FR_OK) break; + } + if (n == 100) return FR_DENIED; /* Abort if too many collisions */ + if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */ + dp->fn[NSFLAG] = sn[NSFLAG]; + } + + /* Create an SFN with/without LFNs. */ + n_ent = (sn[NSFLAG] & NS_LFN) ? (len + 12) / 13 + 1 : 1; /* Number of entries to allocate */ + res = dir_alloc(dp, n_ent); /* Allocate entries */ + if (res == FR_OK && --n_ent) { /* Set LFN entry if needed */ + res = dir_sdi(dp, dp->dptr - n_ent * SZDIRE); + if (res == FR_OK) { + sum = sum_sfn(dp->fn); /* Checksum value of the SFN tied to the LFN */ + do { /* Store LFN entries in bottom first */ + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + put_lfn(fs->lfnbuf, dp->dir, (BYTE)n_ent, sum); + fs->wflag = 1; + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK && --n_ent); + } + } + +#else /* Non LFN configuration */ + res = dir_alloc(dp, 1); /* Allocate an entry for SFN */ + +#endif + + /* Set SFN entry */ + if (res == FR_OK) { + res = move_window(fs, dp->sect); + if (res == FR_OK) { + memset(dp->dir, 0, SZDIRE); /* Clean the entry */ + memcpy(dp->dir + DIR_Name, dp->fn, 11); /* Put SFN */ +#if FF_USE_LFN + dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT); /* Put NT flag */ +#endif + fs->wflag = 1; + } + } + + return res; +} + +#endif /* !FF_FS_READONLY */ + + + +#if !FF_FS_READONLY && FF_FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Remove an object from the directory */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */ + DIR* dp /* Directory object pointing the entry to be removed */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; +#if FF_USE_LFN /* LFN configuration */ + DWORD last = dp->dptr; + + res = (dp->blk_ofs == 0xFFFFFFFF) ? FR_OK : dir_sdi(dp, dp->blk_ofs); /* Goto top of the entry block if LFN is exist */ + if (res == FR_OK) { + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + dp->dir[XDIR_Type] &= 0x7F; /* Clear the entry InUse flag. */ + } else { /* On the FAT/FAT32 volume */ + dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'. */ + } + fs->wflag = 1; + if (dp->dptr >= last) break; /* If reached last entry then all entries of the object has been deleted. */ + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR; + } +#else /* Non LFN configuration */ + + res = move_window(fs, dp->sect); + if (res == FR_OK) { + dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'.*/ + fs->wflag = 1; + } +#endif + + return res; +} + +#endif /* !FF_FS_READONLY && FF_FS_MINIMIZE == 0 */ + + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 +/*-----------------------------------------------------------------------*/ +/* Get file information from directory entry */ +/*-----------------------------------------------------------------------*/ + +static void get_fileinfo ( + DIR* dp, /* Pointer to the directory object */ + FILINFO* fno /* Pointer to the file information to be filled */ +) +{ + UINT si, di; +#if FF_USE_LFN + BYTE lcf; + WCHAR wc, hs; + FATFS *fs = dp->obj.fs; + UINT nw; +#else + TCHAR c; +#endif + + + fno->fname[0] = 0; /* Invaidate file info */ + if (dp->sect == 0) return; /* Exit if read pointer has reached end of directory */ + +#if FF_USE_LFN /* LFN configuration */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* exFAT volume */ + UINT nc = 0; + + si = SZDIRE * 2; di = 0; /* 1st C1 entry in the entry block */ + hs = 0; + while (nc < fs->dirbuf[XDIR_NumName]) { + if (si >= MAXDIRB(FF_MAX_LFN)) { /* Truncated directory block? */ + di = 0; break; + } + if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */ + wc = ld_word(fs->dirbuf + si); si += 2; nc++; /* Get a character */ + if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */ + hs = wc; continue; /* Get low surrogate */ + } + nw = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in API encoding */ + if (nw == 0) { /* Buffer overflow or wrong char? */ + di = 0; break; + } + di += nw; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + if (di == 0) fno->fname[di++] = '\?'; /* Inaccessible object name? */ + fno->fname[di] = 0; /* Terminate the name */ + fno->altname[0] = 0; /* exFAT does not support SFN */ + + fno->fattrib = fs->dirbuf[XDIR_Attr] & AM_MASKX; /* Attribute */ + fno->fsize = (fno->fattrib & AM_DIR) ? 0 : ld_qword(fs->dirbuf + XDIR_FileSize); /* Size */ + fno->ftime = ld_word(fs->dirbuf + XDIR_ModTime + 0); /* Time */ + fno->fdate = ld_word(fs->dirbuf + XDIR_ModTime + 2); /* Date */ + return; + } else +#endif + { /* FAT/FAT32 volume */ + if (dp->blk_ofs != 0xFFFFFFFF) { /* Get LFN if available */ + si = di = 0; + hs = 0; + while (fs->lfnbuf[si] != 0) { + wc = fs->lfnbuf[si++]; /* Get an LFN character (UTF-16) */ + if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */ + hs = wc; continue; /* Get low surrogate */ + } + nw = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in API encoding */ + if (nw == 0) { /* Buffer overflow or wrong char? */ + di = 0; break; + } + di += nw; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + fno->fname[di] = 0; /* Terminate the LFN (null string means LFN is invalid) */ + } + } + + si = di = 0; + while (si < 11) { /* Get SFN from SFN entry */ + wc = dp->dir[si++]; /* Get a char */ + if (wc == ' ') continue; /* Skip padding spaces */ + if (wc == RDDEM) wc = DDEM; /* Restore replaced DDEM character */ + if (si == 9 && di < FF_SFN_BUF) fno->altname[di++] = '.'; /* Insert a . if extension is exist */ +#if FF_LFN_UNICODE >= 1 /* Unicode output */ + if (dbc_1st((BYTE)wc) && si != 8 && si != 11 && dbc_2nd(dp->dir[si])) { /* Make a DBC if needed */ + wc = wc << 8 | dp->dir[si++]; + } + wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM -> Unicode */ + if (wc == 0) { /* Wrong char in the current code page? */ + di = 0; break; + } + nw = put_utf(wc, &fno->altname[di], FF_SFN_BUF - di); /* Store it in API encoding */ + if (nw == 0) { /* Buffer overflow? */ + di = 0; break; + } + di += nw; +#else /* ANSI/OEM output */ + fno->altname[di++] = (TCHAR)wc; /* Store it without any conversion */ +#endif + } + fno->altname[di] = 0; /* Terminate the SFN (null string means SFN is invalid) */ + + if (fno->fname[0] == 0) { /* If LFN is invalid, altname[] needs to be copied to fname[] */ + if (di == 0) { /* If LFN and SFN both are invalid, this object is inaccessible */ + fno->fname[di++] = '\?'; + } else { + for (si = di = 0, lcf = NS_BODY; fno->altname[si]; si++, di++) { /* Copy altname[] to fname[] with case information */ + wc = (WCHAR)fno->altname[si]; + if (wc == '.') lcf = NS_EXT; + if (IsUpper(wc) && (dp->dir[DIR_NTres] & lcf)) wc += 0x20; + fno->fname[di] = (TCHAR)wc; + } + } + fno->fname[di] = 0; /* Terminate the LFN */ + if (!dp->dir[DIR_NTres]) fno->altname[0] = 0; /* Altname is not needed if neither LFN nor case info is exist. */ + } + +#else /* Non-LFN configuration */ + si = di = 0; + while (si < 11) { /* Copy name body and extension */ + c = (TCHAR)dp->dir[si++]; + if (c == ' ') continue; /* Skip padding spaces */ + if (c == RDDEM) c = DDEM; /* Restore replaced DDEM character */ + if (si == 9) fno->fname[di++] = '.';/* Insert a . if extension is exist */ + fno->fname[di++] = c; + } + fno->fname[di] = 0; /* Terminate the SFN */ +#endif + + fno->fattrib = dp->dir[DIR_Attr] & AM_MASK; /* Attribute */ + fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */ + fno->ftime = ld_word(dp->dir + DIR_ModTime + 0); /* Time */ + fno->fdate = ld_word(dp->dir + DIR_ModTime + 2); /* Date */ +} + +#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */ + + + +#if FF_USE_FIND && FF_FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Pattern matching */ +/*-----------------------------------------------------------------------*/ + +#define FIND_RECURS 4 /* Maximum number of wildcard terms in the pattern to limit recursion */ + + +static DWORD get_achar ( /* Get a character and advance ptr */ + const TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */ +) +{ + DWORD chr; + + +#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode input */ + chr = tchar2uni(ptr); + if (chr == 0xFFFFFFFF) chr = 0; /* Wrong UTF encoding is recognized as end of the string */ + chr = ff_wtoupper(chr); + +#else /* ANSI/OEM input */ + chr = (BYTE)*(*ptr)++; /* Get a byte */ + if (IsLower(chr)) chr -= 0x20; /* To upper ASCII char */ +#if FF_CODE_PAGE == 0 + if (ExCvt && chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */ +#elif FF_CODE_PAGE < 900 + if (chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */ +#endif +#if FF_CODE_PAGE == 0 || FF_CODE_PAGE >= 900 + if (dbc_1st((BYTE)chr)) { /* Get DBC 2nd byte if needed */ + chr = dbc_2nd((BYTE)**ptr) ? chr << 8 | (BYTE)*(*ptr)++ : 0; + } +#endif + +#endif + return chr; +} + + +static int pattern_match ( /* 0:mismatched, 1:matched */ + const TCHAR* pat, /* Matching pattern */ + const TCHAR* nam, /* String to be tested */ + UINT skip, /* Number of pre-skip chars (number of ?s, b8:infinite (* specified)) */ + UINT recur /* Recursion count */ +) +{ + const TCHAR *pptr; + const TCHAR *nptr; + DWORD pchr, nchr; + UINT sk; + + + while ((skip & 0xFF) != 0) { /* Pre-skip name chars */ + if (!get_achar(&nam)) return 0; /* Branch mismatched if less name chars */ + skip--; + } + if (*pat == 0 && skip) return 1; /* Matched? (short circuit) */ + + do { + pptr = pat; nptr = nam; /* Top of pattern and name to match */ + for (;;) { + if (*pptr == '\?' || *pptr == '*') { /* Wildcard term? */ + if (recur == 0) return 0; /* Too many wildcard terms? */ + sk = 0; + do { /* Analyze the wildcard term */ + if (*pptr++ == '\?') { + sk++; + } else { + sk |= 0x100; + } + } while (*pptr == '\?' || *pptr == '*'); + if (pattern_match(pptr, nptr, sk, recur - 1)) return 1; /* Test new branch (recursive call) */ + nchr = *nptr; break; /* Branch mismatched */ + } + pchr = get_achar(&pptr); /* Get a pattern char */ + nchr = get_achar(&nptr); /* Get a name char */ + if (pchr != nchr) break; /* Branch mismatched? */ + if (pchr == 0) return 1; /* Branch matched? (matched at end of both strings) */ + } + get_achar(&nam); /* nam++ */ + } while (skip && nchr); /* Retry until end of name if infinite search is specified */ + + return 0; +} + +#endif /* FF_USE_FIND && FF_FS_MINIMIZE <= 1 */ + + + +/*-----------------------------------------------------------------------*/ +/* Pick a top segment and create the object name in directory form */ +/*-----------------------------------------------------------------------*/ + +static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */ + DIR* dp, /* Pointer to the directory object */ + const TCHAR** path /* Pointer to pointer to the segment in the path string */ +) +{ +#if FF_USE_LFN /* LFN configuration */ + BYTE b, cf; + WCHAR wc; + WCHAR *lfn; + const TCHAR* p; + DWORD uc; + UINT i, ni, si, di; + + + /* Create LFN into LFN working buffer */ + p = *path; lfn = dp->obj.fs->lfnbuf; di = 0; + for (;;) { + uc = tchar2uni(&p); /* Get a character */ + if (uc == 0xFFFFFFFF) return FR_INVALID_NAME; /* Invalid code or UTF decode error */ + if (uc >= 0x10000) lfn[di++] = (WCHAR)(uc >> 16); /* Store high surrogate if needed */ + wc = (WCHAR)uc; + if (wc < ' ' || IsSeparator(wc)) break; /* Break if end of the path or a separator is found */ + if (wc < 0x80 && strchr("*:<>|\"\?\x7F", (int)wc)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */ + if (di >= FF_MAX_LFN) return FR_INVALID_NAME; /* Reject too long name */ + lfn[di++] = wc; /* Store the Unicode character */ + } + if (wc < ' ') { /* Stopped at end of the path? */ + cf = NS_LAST; /* Last segment */ + } else { /* Stopped at a separator */ + while (IsSeparator(*p)) p++; /* Skip duplicated separators if exist */ + cf = 0; /* Next segment may follow */ + if (IsTerminator(*p)) cf = NS_LAST; /* Ignore terminating separator */ + } + *path = p; /* Return pointer to the next segment */ + +#if FF_FS_RPATH != 0 + if ((di == 1 && lfn[di - 1] == '.') || + (di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.')) { /* Is this segment a dot name? */ + lfn[di] = 0; + for (i = 0; i < 11; i++) { /* Create dot name for SFN entry */ + dp->fn[i] = (i < di) ? '.' : ' '; + } + dp->fn[i] = cf | NS_DOT; /* This is a dot entry */ + return FR_OK; + } +#endif + while (di) { /* Snip off trailing spaces and dots if exist */ + wc = lfn[di - 1]; + if (wc != ' ' && wc != '.') break; + di--; + } + lfn[di] = 0; /* LFN is created into the working buffer */ + if (di == 0) return FR_INVALID_NAME; /* Reject null name */ + + /* Create SFN in directory form */ + for (si = 0; lfn[si] == ' '; si++) ; /* Remove leading spaces */ + if (si > 0 || lfn[si] == '.') cf |= NS_LOSS | NS_LFN; /* Is there any leading space or dot? */ + while (di > 0 && lfn[di - 1] != '.') di--; /* Find last dot (di<=si: no extension) */ + + memset(dp->fn, ' ', 11); + i = b = 0; ni = 8; + for (;;) { + wc = lfn[si++]; /* Get an LFN character */ + if (wc == 0) break; /* Break on end of the LFN */ + if (wc == ' ' || (wc == '.' && si != di)) { /* Remove embedded spaces and dots */ + cf |= NS_LOSS | NS_LFN; + continue; + } + + if (i >= ni || si == di) { /* End of field? */ + if (ni == 11) { /* Name extension overflow? */ + cf |= NS_LOSS | NS_LFN; + break; + } + if (si != di) cf |= NS_LOSS | NS_LFN; /* Name body overflow? */ + if (si > di) break; /* No name extension? */ + si = di; i = 8; ni = 11; b <<= 2; /* Enter name extension */ + continue; + } + + if (wc >= 0x80) { /* Is this an extended character? */ + cf |= NS_LFN; /* LFN entry needs to be created */ +#if FF_CODE_PAGE == 0 + if (ExCvt) { /* In SBCS cfg */ + wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */ + if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */ + } else { /* In DBCS cfg */ + wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Up-convert ==> ANSI/OEM code */ + } +#elif FF_CODE_PAGE < 900 /* In SBCS cfg */ + wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */ + if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */ +#else /* In DBCS cfg */ + wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Up-convert ==> ANSI/OEM code */ +#endif + } + + if (wc >= 0x100) { /* Is this a DBC? */ + if (i >= ni - 1) { /* Field overflow? */ + cf |= NS_LOSS | NS_LFN; + i = ni; continue; /* Next field */ + } + dp->fn[i++] = (BYTE)(wc >> 8); /* Put 1st byte */ + } else { /* SBC */ + if (wc == 0 || strchr("+,;=[]", (int)wc)) { /* Replace illegal characters for SFN */ + wc = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */ + } else { + if (IsUpper(wc)) { /* ASCII upper case? */ + b |= 2; + } + if (IsLower(wc)) { /* ASCII lower case? */ + b |= 1; wc -= 0x20; + } + } + } + dp->fn[i++] = (BYTE)wc; + } + + if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */ + + if (ni == 8) b <<= 2; /* Shift capital flags if no extension */ + if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* LFN entry needs to be created if composite capitals */ + if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */ + if (b & 0x01) cf |= NS_EXT; /* NT flag (Extension has small capital letters only) */ + if (b & 0x04) cf |= NS_BODY; /* NT flag (Body has small capital letters only) */ + } + + dp->fn[NSFLAG] = cf; /* SFN is created into dp->fn[] */ + + return FR_OK; + + +#else /* FF_USE_LFN : Non-LFN configuration */ + BYTE c, d; + BYTE *sfn; + UINT ni, si, i; + const char *p; + + /* Create file name in directory form */ + p = *path; sfn = dp->fn; + memset(sfn, ' ', 11); + si = i = 0; ni = 8; +#if FF_FS_RPATH != 0 + if (p[si] == '.') { /* Is this a dot entry? */ + for (;;) { + c = (BYTE)p[si++]; + if (c != '.' || si >= 3) break; + sfn[i++] = c; + } + if (!IsSeparator(c) && c > ' ') return FR_INVALID_NAME; + *path = p + si; /* Return pointer to the next segment */ + sfn[NSFLAG] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of the path */ + return FR_OK; + } +#endif + for (;;) { + c = (BYTE)p[si++]; /* Get a byte */ + if (c <= ' ') break; /* Break if end of the path name */ + if (IsSeparator(c)) { /* Break if a separator is found */ + while (IsSeparator(p[si])) si++; /* Skip duplicated separator if exist */ + break; + } + if (c == '.' || i >= ni) { /* End of body or field overflow? */ + if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Field overflow or invalid dot? */ + i = 8; ni = 11; /* Enter file extension field */ + continue; + } +#if FF_CODE_PAGE == 0 + if (ExCvt && c >= 0x80) { /* Is SBC extended character? */ + c = ExCvt[c & 0x7F]; /* To upper SBC extended character */ + } +#elif FF_CODE_PAGE < 900 + if (c >= 0x80) { /* Is SBC extended character? */ + c = ExCvt[c & 0x7F]; /* To upper SBC extended character */ + } +#endif + if (dbc_1st(c)) { /* Check if it is a DBC 1st byte */ + d = (BYTE)p[si++]; /* Get 2nd byte */ + if (!dbc_2nd(d) || i >= ni - 1) return FR_INVALID_NAME; /* Reject invalid DBC */ + sfn[i++] = c; + sfn[i++] = d; + } else { /* SBC */ + if (strchr("*+,:;<=>[]|\"\?\x7F", (int)c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */ + if (IsLower(c)) c -= 0x20; /* To upper */ + sfn[i++] = c; + } + } + *path = &p[si]; /* Return pointer to the next segment */ + if (i == 0) return FR_INVALID_NAME; /* Reject nul string */ + + if (sfn[0] == DDEM) sfn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */ + sfn[NSFLAG] = (c <= ' ' || p[si] <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */ + + return FR_OK; +#endif /* FF_USE_LFN */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Follow a file path */ +/*-----------------------------------------------------------------------*/ + +static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ + DIR* dp, /* Directory object to return last directory and found object */ + const TCHAR* path /* Full-path string to find a file or directory */ +) +{ + FRESULT res; + BYTE ns; + FATFS *fs = dp->obj.fs; + + +#if FF_FS_RPATH != 0 + if (!IsSeparator(*path) && (FF_STR_VOLUME_ID != 2 || !IsTerminator(*path))) { /* Without heading separator */ + dp->obj.sclust = fs->cdir; /* Start at the current directory */ + } else +#endif + { /* With heading separator */ + while (IsSeparator(*path)) path++; /* Strip separators */ + dp->obj.sclust = 0; /* Start from the root directory */ + } +#if FF_FS_EXFAT + dp->obj.n_frag = 0; /* Invalidate last fragment counter of the object */ +#if FF_FS_RPATH != 0 + if (fs->fs_type == FS_EXFAT && dp->obj.sclust) { /* exFAT: Retrieve the sub-directory's status */ + DIR dj; + + dp->obj.c_scl = fs->cdc_scl; + dp->obj.c_size = fs->cdc_size; + dp->obj.c_ofs = fs->cdc_ofs; + res = load_obj_xdir(&dj, &dp->obj); + if (res != FR_OK) return res; + dp->obj.objsize = ld_dword(fs->dirbuf + XDIR_FileSize); + dp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2; + } +#endif +#endif + + if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */ + dp->fn[NSFLAG] = NS_NONAME; + res = dir_sdi(dp, 0); + + } else { /* Follow path */ + for (;;) { + res = create_name(dp, &path); /* Get a segment name of the path */ + if (res != FR_OK) break; + res = dir_find(dp); /* Find an object with the segment name */ + ns = dp->fn[NSFLAG]; + if (res != FR_OK) { /* Failed to find the object */ + if (res == FR_NO_FILE) { /* Object is not found */ + if (FF_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, stay there */ + if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */ + dp->fn[NSFLAG] = NS_NONAME; + res = FR_OK; + } else { /* Could not find the object */ + if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */ + } + } + break; + } + if (ns & NS_LAST) break; /* Last segment matched. Function completed. */ + /* Get into the sub-directory */ + if (!(dp->obj.attr & AM_DIR)) { /* It is not a sub-directory and cannot follow */ + res = FR_NO_PATH; break; + } +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* Save containing directory information for next dir */ + dp->obj.c_scl = dp->obj.sclust; + dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat; + dp->obj.c_ofs = dp->blk_ofs; + init_alloc_info(fs, &dp->obj); /* Open next directory */ + } else +#endif + { + dp->obj.sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs)); /* Open next directory */ + } + } + } + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Get logical drive number from path name */ +/*-----------------------------------------------------------------------*/ + +static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive number or null pointer) */ + const TCHAR** path /* Pointer to pointer to the path name */ +) +{ + const TCHAR *tp; + const TCHAR *tt; + TCHAR tc; + int i; + int vol = -1; +#if FF_STR_VOLUME_ID /* Find string volume ID */ + const char *sp; + char c; +#endif + + tt = tp = *path; + if (!tp) return vol; /* Invalid path name? */ + do { /* Find a colon in the path */ + tc = *tt++; + } while (!IsTerminator(tc) && tc != ':'); + + if (tc == ':') { /* DOS/Windows style volume ID? */ + i = FF_VOLUMES; + if (IsDigit(*tp) && tp + 2 == tt) { /* Is there a numeric volume ID + colon? */ + i = (int)*tp - '0'; /* Get the LD number */ + } +#if FF_STR_VOLUME_ID == 1 /* Arbitrary string is enabled */ + else { + i = 0; + do { + sp = VolumeStr[i]; tp = *path; /* This string volume ID and path name */ + do { /* Compare the volume ID with path name */ + c = *sp++; tc = *tp++; + if (IsLower(c)) c -= 0x20; + if (IsLower(tc)) tc -= 0x20; + } while (c && (TCHAR)c == tc); + } while ((c || tp != tt) && ++i < FF_VOLUMES); /* Repeat for each id until pattern match */ + } +#endif + if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */ + vol = i; /* Drive number */ + *path = tt; /* Snip the drive prefix off */ + } + return vol; + } +#if FF_STR_VOLUME_ID == 2 /* Unix style volume ID is enabled */ + if (*tp == '/') { /* Is there a volume ID? */ + while (*(tp + 1) == '/') tp++; /* Skip duplicated separator */ + i = 0; + do { + tt = tp; sp = VolumeStr[i]; /* Path name and this string volume ID */ + do { /* Compare the volume ID with path name */ + c = *sp++; tc = *(++tt); + if (IsLower(c)) c -= 0x20; + if (IsLower(tc)) tc -= 0x20; + } while (c && (TCHAR)c == tc); + } while ((c || (tc != '/' && !IsTerminator(tc))) && ++i < FF_VOLUMES); /* Repeat for each ID until pattern match */ + if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */ + vol = i; /* Drive number */ + *path = tt; /* Snip the drive prefix off */ + } + return vol; + } +#endif + /* No drive prefix is found */ +#if FF_FS_RPATH != 0 + vol = CurrVol; /* Default drive is current drive */ +#else + vol = 0; /* Default drive is 0 */ +#endif + return vol; /* Return the default drive */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* GPT support functions */ +/*-----------------------------------------------------------------------*/ + +#if FF_LBA64 + +/* Calculate CRC32 in byte-by-byte */ + +static DWORD crc32 ( /* Returns next CRC value */ + DWORD crc, /* Current CRC value */ + BYTE d /* A byte to be processed */ +) +{ + BYTE b; + + + for (b = 1; b; b <<= 1) { + crc ^= (d & b) ? 1 : 0; + crc = (crc & 1) ? crc >> 1 ^ 0xEDB88320 : crc >> 1; + } + return crc; +} + + +/* Check validity of GPT header */ + +static int test_gpt_header ( /* 0:Invalid, 1:Valid */ + const BYTE* gpth /* Pointer to the GPT header */ +) +{ + UINT i; + DWORD bcc, hlen; + + + if (memcmp(gpth + GPTH_Sign, "EFI PART" "\0\0\1", 12)) return 0; /* Check signature and version (1.0) */ + hlen = ld_dword(gpth + GPTH_Size); /* Check header size */ + if (hlen < 92 || hlen > FF_MIN_SS) return 0; + for (i = 0, bcc = 0xFFFFFFFF; i < hlen; i++) { /* Check header BCC */ + bcc = crc32(bcc, i - GPTH_Bcc < 4 ? 0 : gpth[i]); + } + if (~bcc != ld_dword(gpth + GPTH_Bcc)) return 0; + if (ld_dword(gpth + GPTH_PteSize) != SZ_GPTE) return 0; /* Table entry size (must be SZ_GPTE bytes) */ + if (ld_dword(gpth + GPTH_PtNum) > 128) return 0; /* Table size (must be 128 entries or less) */ + + return 1; +} + +#if !FF_FS_READONLY && FF_USE_MKFS + +/* Generate random value */ +static DWORD make_rand ( + DWORD seed, /* Seed value */ + BYTE *buff, /* Output buffer */ + UINT n /* Data length */ +) +{ + UINT r; + + + if (seed == 0) seed = 1; + do { + for (r = 0; r < 8; r++) seed = seed & 1 ? seed >> 1 ^ 0xA3000000 : seed >> 1; /* Shift 8 bits the 32-bit LFSR */ + *buff++ = (BYTE)seed; + } while (--n); + return seed; +} + +#endif +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Load a sector and check if it is an FAT VBR */ +/*-----------------------------------------------------------------------*/ + +/* Check what the sector is */ + +static UINT check_fs ( /* 0:FAT/FAT32 VBR, 1:exFAT VBR, 2:Not FAT and valid BS, 3:Not FAT and invalid BS, 4:Disk error */ + FATFS* fs, /* Filesystem object */ + LBA_t sect /* Sector to load and check if it is an FAT-VBR or not */ +) +{ + WORD w, sign; + BYTE b; + + + fs->wflag = 0; fs->winsect = (LBA_t)0 - 1; /* Invaidate window */ + if (move_window(fs, sect) != FR_OK) return 4; /* Load the boot sector */ + sign = ld_word(fs->win + BS_55AA); +#if FF_FS_EXFAT + if (sign == 0xAA55 && !memcmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1; /* It is an exFAT VBR */ +#endif + b = fs->win[BS_JmpBoot]; + if (b == 0xEB || b == 0xE9 || b == 0xE8) { /* Valid JumpBoot code? (short jump, near jump or near call) */ + if (sign == 0xAA55 && !memcmp(fs->win + BS_FilSysType32, "FAT32 ", 8)) { + return 0; /* It is an FAT32 VBR */ + } + /* FAT volumes formatted with early MS-DOS lack BS_55AA and BS_FilSysType, so FAT VBR needs to be identified without them. */ + w = ld_word(fs->win + BPB_BytsPerSec); + b = fs->win[BPB_SecPerClus]; + if ((w & (w - 1)) == 0 && w >= FF_MIN_SS && w <= FF_MAX_SS /* Properness of sector size (512-4096 and 2^n) */ + && b != 0 && (b & (b - 1)) == 0 /* Properness of cluster size (2^n) */ + && ld_word(fs->win + BPB_RsvdSecCnt) != 0 /* Properness of reserved sectors (MNBZ) */ + && (UINT)fs->win[BPB_NumFATs] - 1 <= 1 /* Properness of FATs (1 or 2) */ + && ld_word(fs->win + BPB_RootEntCnt) != 0 /* Properness of root dir entries (MNBZ) */ + && (ld_word(fs->win + BPB_TotSec16) >= 128 || ld_dword(fs->win + BPB_TotSec32) >= 0x10000) /* Properness of volume sectors (>=128) */ + && ld_word(fs->win + BPB_FATSz16) != 0) { /* Properness of FAT size (MNBZ) */ + return 0; /* It can be presumed an FAT VBR */ + } + } + return sign == 0xAA55 ? 2 : 3; /* Not an FAT VBR (valid or invalid BS) */ +} + + +/* Find an FAT volume */ +/* (It supports only generic partitioning rules, MBR, GPT and SFD) */ + +static UINT find_volume ( /* Returns BS status found in the hosting drive */ + FATFS* fs, /* Filesystem object */ + UINT part /* Partition to fined = 0:find as SFD and partitions, >0:forced partition number */ +) +{ + UINT fmt, i; + DWORD mbr_pt[4]; + + + fmt = check_fs(fs, 0); /* Load sector 0 and check if it is an FAT VBR as SFD format */ + if (fmt != 2 && (fmt >= 3 || part == 0)) return fmt; /* Returns if it is an FAT VBR as auto scan, not a BS or disk error */ + + /* Sector 0 is not an FAT VBR or forced partition number wants a partition */ + +#if FF_LBA64 + if (fs->win[MBR_Table + PTE_System] == 0xEE) { /* GPT protective MBR? */ + DWORD n_ent, v_ent, ofs; + QWORD pt_lba; + + if (move_window(fs, 1) != FR_OK) return 4; /* Load GPT header sector (next to MBR) */ + if (!test_gpt_header(fs->win)) return 3; /* Check if GPT header is valid */ + n_ent = ld_dword(fs->win + GPTH_PtNum); /* Number of entries */ + pt_lba = ld_qword(fs->win + GPTH_PtOfs); /* Table location */ + for (v_ent = i = 0; i < n_ent; i++) { /* Find FAT partition */ + if (move_window(fs, pt_lba + i * SZ_GPTE / SS(fs)) != FR_OK) return 4; /* PT sector */ + ofs = i * SZ_GPTE % SS(fs); /* Offset in the sector */ + if (!memcmp(fs->win + ofs + GPTE_PtGuid, GUID_MS_Basic, 16)) { /* MS basic data partition? */ + v_ent++; + fmt = check_fs(fs, ld_qword(fs->win + ofs + GPTE_FstLba)); /* Load VBR and check status */ + if (part == 0 && fmt <= 1) return fmt; /* Auto search (valid FAT volume found first) */ + if (part != 0 && v_ent == part) return fmt; /* Forced partition order (regardless of it is valid or not) */ + } + } + return 3; /* Not found */ + } +#endif + if (FF_MULTI_PARTITION && part > 4) return 3; /* MBR has 4 partitions max */ + for (i = 0; i < 4; i++) { /* Load partition offset in the MBR */ + mbr_pt[i] = ld_dword(fs->win + MBR_Table + i * SZ_PTE + PTE_StLba); + } + i = part ? part - 1 : 0; /* Table index to find first */ + do { /* Find an FAT volume */ + fmt = mbr_pt[i] ? check_fs(fs, mbr_pt[i]) : 3; /* Check if the partition is FAT */ + } while (part == 0 && fmt >= 2 && ++i < 4); + return fmt; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Determine logical drive number and mount the volume if needed */ +/*-----------------------------------------------------------------------*/ + +static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */ + const TCHAR** path, /* Pointer to pointer to the path name (drive number) */ + FATFS** rfs, /* Pointer to pointer to the found filesystem object */ + BYTE mode /* Desiered access mode to check write protection */ +) +{ + int vol; + FATFS *fs; + DSTATUS stat; + LBA_t bsect; + DWORD tsect, sysect, fasize, nclst, szbfat; + WORD nrsv; + UINT fmt; + + + /* Get logical drive number */ + *rfs = 0; + vol = get_ldnumber(path); + if (vol < 0) return FR_INVALID_DRIVE; + + /* Check if the filesystem object is valid or not */ + fs = FatFs[vol]; /* Get pointer to the filesystem object */ + if (!fs) return FR_NOT_ENABLED; /* Is the filesystem object available? */ +#if FF_FS_REENTRANT + if (!lock_volume(fs, 1)) return FR_TIMEOUT; /* Lock the volume, and system if needed */ +#endif + *rfs = fs; /* Return pointer to the filesystem object */ + + mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */ + if (fs->fs_type != 0) { /* If the volume has been mounted */ + stat = disk_status(fs->pdrv); + if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */ + if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */ + return FR_WRITE_PROTECTED; + } + return FR_OK; /* The filesystem object is already valid */ + } + } + + /* The filesystem object is not valid. */ + /* Following code attempts to mount the volume. (find an FAT volume, analyze the BPB and initialize the filesystem object) */ + + fs->fs_type = 0; /* Invalidate the filesystem object */ + stat = disk_initialize(fs->pdrv); /* Initialize the volume hosting physical drive */ + if (stat & STA_NOINIT) { /* Check if the initialization succeeded */ + return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */ + } + if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */ + return FR_WRITE_PROTECTED; + } +#if FF_MAX_SS != FF_MIN_SS /* Get sector size (multiple sector size cfg only) */ + if (disk_ioctl(fs->pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR; + if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR; +#endif + + /* Find an FAT volume on the hosting drive */ + fmt = find_volume(fs, LD2PT(vol)); + if (fmt == 4) return FR_DISK_ERR; /* An error occurred in the disk I/O layer */ + if (fmt >= 2) return FR_NO_FILESYSTEM; /* No FAT volume is found */ + bsect = fs->winsect; /* Volume offset in the hosting physical drive */ + + /* An FAT volume is found (bsect). Following code initializes the filesystem object */ + +#if FF_FS_EXFAT + if (fmt == 1) { + QWORD maxlba; + DWORD so, cv, bcl, i; + + for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ; /* Check zero filler */ + if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM; + + if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT version (must be version 1.0) */ + + if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) { /* (BPB_BytsPerSecEx must be equal to the physical sector size) */ + return FR_NO_FILESYSTEM; + } + + maxlba = ld_qword(fs->win + BPB_TotSecEx) + bsect; /* Last LBA of the volume + 1 */ + if (!FF_LBA64 && maxlba >= 0x100000000) return FR_NO_FILESYSTEM; /* (It cannot be accessed in 32-bit LBA) */ + + fs->fsize = ld_dword(fs->win + BPB_FatSzEx); /* Number of sectors per FAT */ + + fs->n_fats = fs->win[BPB_NumFATsEx]; /* Number of FATs */ + if (fs->n_fats != 1) return FR_NO_FILESYSTEM; /* (Supports only 1 FAT) */ + + fs->csize = 1 << fs->win[BPB_SecPerClusEx]; /* Cluster size */ + if (fs->csize == 0) return FR_NO_FILESYSTEM; /* (Must be 1..32768 sectors) */ + + nclst = ld_dword(fs->win + BPB_NumClusEx); /* Number of clusters */ + if (nclst > MAX_EXFAT) return FR_NO_FILESYSTEM; /* (Too many clusters) */ + fs->n_fatent = nclst + 2; + + /* Boundaries and Limits */ + fs->volbase = bsect; + fs->database = bsect + ld_dword(fs->win + BPB_DataOfsEx); + fs->fatbase = bsect + ld_dword(fs->win + BPB_FatOfsEx); + if (maxlba < (QWORD)fs->database + nclst * fs->csize) return FR_NO_FILESYSTEM; /* (Volume size must not be smaller than the size required) */ + fs->dirbase = ld_dword(fs->win + BPB_RootClusEx); + + /* Get bitmap location and check if it is contiguous (implementation assumption) */ + so = i = 0; + for (;;) { /* Find the bitmap entry in the root directory (in only first cluster) */ + if (i == 0) { + if (so >= fs->csize) return FR_NO_FILESYSTEM; /* Not found? */ + if (move_window(fs, clst2sect(fs, (DWORD)fs->dirbase) + so) != FR_OK) return FR_DISK_ERR; + so++; + } + if (fs->win[i] == ET_BITMAP) break; /* Is it a bitmap entry? */ + i = (i + SZDIRE) % SS(fs); /* Next entry */ + } + bcl = ld_dword(fs->win + i + 20); /* Bitmap cluster */ + if (bcl < 2 || bcl >= fs->n_fatent) return FR_NO_FILESYSTEM; /* (Wrong cluster#) */ + fs->bitbase = fs->database + fs->csize * (bcl - 2); /* Bitmap sector */ + for (;;) { /* Check if bitmap is contiguous */ + if (move_window(fs, fs->fatbase + bcl / (SS(fs) / 4)) != FR_OK) return FR_DISK_ERR; + cv = ld_dword(fs->win + bcl % (SS(fs) / 4) * 4); + if (cv == 0xFFFFFFFF) break; /* Last link? */ + if (cv != ++bcl) return FR_NO_FILESYSTEM; /* Fragmented bitmap? */ + } + +#if !FF_FS_READONLY + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ +#endif + fmt = FS_EXFAT; /* FAT sub-type */ + } else +#endif /* FF_FS_EXFAT */ + { + if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */ + + fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */ + if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32); + fs->fsize = fasize; + + fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FATs */ + if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */ + fasize *= fs->n_fats; /* Number of sectors for FAT area */ + + fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */ + if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */ + + fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */ + if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */ + + tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */ + if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32); + + nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */ + if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */ + + /* Determine the FAT sub type */ + sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */ + if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ + if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + fmt = 0; + if (nclst <= MAX_FAT32) fmt = FS_FAT32; + if (nclst <= MAX_FAT16) fmt = FS_FAT16; + if (nclst <= MAX_FAT12) fmt = FS_FAT12; + if (fmt == 0) return FR_NO_FILESYSTEM; + + /* Boundaries and Limits */ + fs->n_fatent = nclst + 2; /* Number of FAT entries */ + fs->volbase = bsect; /* Volume start sector */ + fs->fatbase = bsect + nrsv; /* FAT start sector */ + fs->database = bsect + sysect; /* Data start sector */ + if (fmt == FS_FAT32) { + if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */ + if (fs->n_rootdir != 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ + fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */ + szbfat = fs->n_fatent * 4; /* (Needed FAT size) */ + } else { + if (fs->n_rootdir == 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must not be 0) */ + fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ + szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */ + fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1); + } + if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_FATSz must not be less than the size needed) */ + +#if !FF_FS_READONLY + /* Get FSInfo if available */ + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ + fs->fsi_flag = 0x80; +#if (FF_FS_NOFSINFO & 3) != 3 + if (fmt == FS_FAT32 /* Allow to update FSInfo only if BPB_FSInfo32 == 1 */ + && ld_word(fs->win + BPB_FSInfo32) == 1 + && move_window(fs, bsect + 1) == FR_OK) + { + fs->fsi_flag = 0; + if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSInfo data if available */ + && ld_dword(fs->win + FSI_LeadSig) == 0x41615252 + && ld_dword(fs->win + FSI_StrucSig) == 0x61417272) + { +#if (FF_FS_NOFSINFO & 1) == 0 + fs->free_clst = ld_dword(fs->win + FSI_Free_Count); +#endif +#if (FF_FS_NOFSINFO & 2) == 0 + fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free); +#endif + } + } +#endif /* (FF_FS_NOFSINFO & 3) != 3 */ +#endif /* !FF_FS_READONLY */ + } + + fs->fs_type = (BYTE)fmt;/* FAT sub-type (the filesystem object gets valid) */ + fs->id = ++Fsid; /* Volume mount ID */ +#if FF_USE_LFN == 1 + fs->lfnbuf = LfnBuf; /* Static LFN working buffer */ +#if FF_FS_EXFAT + fs->dirbuf = DirBuf; /* Static directory block scratchpad buuffer */ +#endif +#endif +#if FF_FS_RPATH != 0 + fs->cdir = 0; /* Initialize current directory */ +#endif +#if FF_FS_LOCK /* Clear file lock semaphores */ + clear_share(fs); +#endif + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Check if the file/directory object is valid or not */ +/*-----------------------------------------------------------------------*/ + +static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */ + FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/DIR structure, to check validity */ + FATFS** rfs /* Pointer to pointer to the owner filesystem object to return */ +) +{ + FRESULT res = FR_INVALID_OBJECT; + + + if (obj && obj->fs && obj->fs->fs_type && obj->id == obj->fs->id) { /* Test if the object is valid */ +#if FF_FS_REENTRANT + if (lock_volume(obj->fs, 0)) { /* Take a grant to access the volume */ + if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the hosting phsical drive is kept initialized */ + res = FR_OK; + } else { + unlock_volume(obj->fs, FR_OK); /* Invalidated volume, abort to access */ + } + } else { /* Could not take */ + res = FR_TIMEOUT; + } +#else + if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the hosting phsical drive is kept initialized */ + res = FR_OK; + } +#endif + } + *rfs = (res == FR_OK) ? obj->fs : 0; /* Return corresponding filesystem object if it is valid */ + return res; +} + + + + +/*--------------------------------------------------------------------------- + + Public Functions (FatFs API) + +----------------------------------------------------------------------------*/ + + + +/*-----------------------------------------------------------------------*/ +/* Mount/Unmount a Logical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mount ( + FATFS* fs, /* Pointer to the filesystem object to be registered (NULL:unmount)*/ + const TCHAR* path, /* Logical drive number to be mounted/unmounted */ + BYTE opt /* Mount option: 0=Do not mount (delayed mount), 1=Mount immediately */ +) +{ + FATFS *cfs; + int vol; + FRESULT res; + const TCHAR *rp = path; + + + /* Get volume ID (logical drive number) */ + vol = get_ldnumber(&rp); + if (vol < 0) return FR_INVALID_DRIVE; + cfs = FatFs[vol]; /* Pointer to the filesystem object of the volume */ + + if (cfs) { /* Unregister current filesystem object if regsitered */ + FatFs[vol] = 0; +#if FF_FS_LOCK + clear_share(cfs); +#endif +#if FF_FS_REENTRANT /* Discard mutex of the current volume */ + ff_mutex_delete(vol); +#endif + cfs->fs_type = 0; /* Invalidate the filesystem object to be unregistered */ + } + + if (fs) { /* Register new filesystem object */ + fs->pdrv = LD2PD(vol); /* Volume hosting physical drive */ +#if FF_FS_REENTRANT /* Create a volume mutex */ + fs->ldrv = (BYTE)vol; /* Owner volume ID */ + if (!ff_mutex_create(vol)) return FR_INT_ERR; +#if FF_FS_LOCK + if (SysLock == 0) { /* Create a system mutex if needed */ + if (!ff_mutex_create(FF_VOLUMES)) { + ff_mutex_delete(vol); + return FR_INT_ERR; + } + SysLock = 1; /* System mutex is ready */ + } +#endif +#endif + fs->fs_type = 0; /* Invalidate the new filesystem object */ + FatFs[vol] = fs; /* Register new fs object */ + } + + if (opt == 0) return FR_OK; /* Do not mount now, it will be mounted in subsequent file functions */ + + res = mount_volume(&path, &fs, 0); /* Force mounted the volume */ + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Open or Create a File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_open ( + FIL* fp, /* Pointer to the blank file object */ + const TCHAR* path, /* Pointer to the file name */ + BYTE mode /* Access mode and open mode flags */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; +#if !FF_FS_READONLY + DWORD cl, bcs, clst, tm; + LBA_t sc; + FSIZE_t ofs; +#endif + DEF_NAMBUF + + + if (!fp) return FR_INVALID_OBJECT; + + /* Get logical drive number */ + mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND; + res = mount_volume(&path, &fs, mode); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ +#if !FF_FS_READONLY /* Read/Write configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */ + res = FR_INVALID_NAME; + } +#if FF_FS_LOCK + else { + res = chk_share(&dj, (mode & ~FA_READ) ? 1 : 0); /* Check if the file can be used */ + } +#endif + } + /* Create or Open a file */ + if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { + if (res != FR_OK) { /* No file, create new */ + if (res == FR_NO_FILE) { /* There is no file to open, create a new entry */ +#if FF_FS_LOCK + res = enq_share() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES; +#else + res = dir_register(&dj); +#endif + } + mode |= FA_CREATE_ALWAYS; /* File is created */ + } + else { /* Any object with the same name is already existing */ + if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */ + res = FR_DENIED; + } else { + if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */ + } + } + if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate the file if overwrite mode */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + /* Get current allocation info */ + fp->obj.fs = fs; + init_alloc_info(fs, &fp->obj); + /* Set directory entry block initial state */ + memset(fs->dirbuf + 2, 0, 30); /* Clear 85 entry except for NumSec */ + memset(fs->dirbuf + 38, 0, 26); /* Clear C0 entry except for NumName and NameHash */ + fs->dirbuf[XDIR_Attr] = AM_ARC; + st_dword(fs->dirbuf + XDIR_CrtTime, GET_FATTIME()); + fs->dirbuf[XDIR_GenFlags] = 1; + res = store_xdir(&dj); + if (res == FR_OK && fp->obj.sclust != 0) { /* Remove the cluster chain if exist */ + res = remove_chain(&fp->obj, fp->obj.sclust, 0); + fs->last_clst = fp->obj.sclust - 1; /* Reuse the cluster hole */ + } + } else +#endif + { + /* Set directory entry initial state */ + tm = GET_FATTIME(); /* Set created time */ + st_dword(dj.dir + DIR_CrtTime, tm); + st_dword(dj.dir + DIR_ModTime, tm); + cl = ld_clust(fs, dj.dir); /* Get current cluster chain */ + dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */ + st_clust(fs, dj.dir, 0); /* Reset file allocation info */ + st_dword(dj.dir + DIR_FileSize, 0); + fs->wflag = 1; + if (cl != 0) { /* Remove the cluster chain if exist */ + sc = fs->winsect; + res = remove_chain(&dj.obj, cl, 0); + if (res == FR_OK) { + res = move_window(fs, sc); + fs->last_clst = cl - 1; /* Reuse the cluster hole */ + } + } + } + } + } + else { /* Open an existing file */ + if (res == FR_OK) { /* Is the object exsiting? */ + if (dj.obj.attr & AM_DIR) { /* File open against a directory */ + res = FR_NO_FILE; + } else { + if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* Write mode open against R/O file */ + res = FR_DENIED; + } + } + } + } + if (res == FR_OK) { + if (mode & FA_CREATE_ALWAYS) mode |= FA_MODIFIED; /* Set file change flag if created or overwritten */ + fp->dir_sect = fs->winsect; /* Pointer to the directory entry */ + fp->dir_ptr = dj.dir; +#if FF_FS_LOCK + fp->obj.lockid = inc_share(&dj, (mode & ~FA_READ) ? 1 : 0); /* Lock the file for this session */ + if (fp->obj.lockid == 0) res = FR_INT_ERR; +#endif + } +#else /* R/O configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it origin directory itself? */ + res = FR_INVALID_NAME; + } else { + if (dj.obj.attr & AM_DIR) { /* Is it a directory? */ + res = FR_NO_FILE; + } + } + } +#endif + + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fp->obj.c_scl = dj.obj.sclust; /* Get containing directory info */ + fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat; + fp->obj.c_ofs = dj.blk_ofs; + init_alloc_info(fs, &fp->obj); + } else +#endif + { + fp->obj.sclust = ld_clust(fs, dj.dir); /* Get object allocation info */ + fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize); + } +#if FF_USE_FASTSEEK + fp->cltbl = 0; /* Disable fast seek mode */ +#endif + fp->obj.fs = fs; /* Validate the file object */ + fp->obj.id = fs->id; + fp->flag = mode; /* Set file access mode */ + fp->err = 0; /* Clear error flag */ + fp->sect = 0; /* Invalidate current data sector */ + fp->fptr = 0; /* Set file pointer top of the file */ +#if !FF_FS_READONLY +#if !FF_FS_TINY + memset(fp->buf, 0, sizeof fp->buf); /* Clear sector buffer */ +#endif + if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */ + fp->fptr = fp->obj.objsize; /* Offset to seek */ + bcs = (DWORD)fs->csize * SS(fs); /* Cluster size in byte */ + clst = fp->obj.sclust; /* Follow the cluster chain */ + for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) { + clst = get_fat(&fp->obj, clst); + if (clst <= 1) res = FR_INT_ERR; + if (clst == 0xFFFFFFFF) res = FR_DISK_ERR; + } + fp->clust = clst; + if (res == FR_OK && ofs % SS(fs)) { /* Fill sector buffer if not on the sector boundary */ + sc = clst2sect(fs, clst); + if (sc == 0) { + res = FR_INT_ERR; + } else { + fp->sect = sc + (DWORD)(ofs / SS(fs)); +#if !FF_FS_TINY + if (disk_read(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR; +#endif + } + } +#if FF_FS_LOCK + if (res != FR_OK) dec_share(fp->obj.lockid); /* Decrement file open counter if seek failed */ +#endif + } +#endif + } + + FREE_NAMBUF(); + } + + if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */ + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_read ( + FIL* fp, /* Open file to be read */ + void* buff, /* Data buffer to store the read data */ + UINT btr, /* Number of bytes to read */ + UINT* br /* Number of bytes read */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst; + LBA_t sect; + FSIZE_t remain; + UINT rcnt, cc, csect; + BYTE *rbuff = (BYTE*)buff; + + + *br = 0; /* Clear read byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */ + if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + remain = fp->obj.objsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr > 0; btr -= rcnt, *br += rcnt, rbuff += rcnt, fp->fptr += rcnt) { /* Repeat until btr bytes read */ + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */ + if (csect == 0) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow cluster chain from the origin */ + } else { /* Middle or end of the file */ +#if FF_USE_FASTSEEK + if (fp->cltbl) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + } else +#endif + { + clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */ + } + } + if (clst < 2) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + sect = clst2sect(fs, fp->clust); /* Get current sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btr / SS(fs); /* When remaining bytes >= sector size, */ + if (cc > 0) { /* Read maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_read(fs->pdrv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); +#if !FF_FS_READONLY && FF_FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */ +#if FF_FS_TINY + if (fs->wflag && fs->winsect - sect < cc) { + memcpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs)); + } +#else + if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) { + memcpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs)); + } +#endif +#endif + rcnt = SS(fs) * cc; /* Number of bytes transferred */ + continue; + } +#if !FF_FS_TINY + if (fp->sect != sect) { /* Load data sector if not in cache */ +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ + } +#endif + fp->sect = sect; + } + rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */ + if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */ +#if FF_FS_TINY + if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */ + memcpy(rbuff, fs->win + fp->fptr % SS(fs), rcnt); /* Extract partial sector */ +#else + memcpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */ +#endif + } + + LEAVE_FF(fs, FR_OK); +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Write File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_write ( + FIL* fp, /* Open file to be written */ + const void* buff, /* Data to be written */ + UINT btw, /* Number of bytes to write */ + UINT* bw /* Number of bytes written */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst; + LBA_t sect; + UINT wcnt, cc, csect; + const BYTE *wbuff = (const BYTE*)buff; + + + *bw = 0; /* Clear write byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */ + if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + /* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */ + if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) { + btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr); + } + + for ( ; btw > 0; btw -= wcnt, *bw += wcnt, wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize) { /* Repeat until all data written */ + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1); /* Sector offset in the cluster */ + if (csect == 0) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow from the origin */ + if (clst == 0) { /* If no cluster is allocated, */ + clst = create_chain(&fp->obj, 0); /* create a new cluster chain */ + } + } else { /* On the middle or end of the file */ +#if FF_USE_FASTSEEK + if (fp->cltbl) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + } else +#endif + { + clst = create_chain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */ + } + } + if (clst == 0) break; /* Could not allocate a new cluster (disk full) */ + if (clst == 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + if (fp->obj.sclust == 0) fp->obj.sclust = clst; /* Set start cluster if the first write */ + } +#if FF_FS_TINY + if (fs->winsect == fp->sect && sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Write-back sector cache */ +#else + if (fp->flag & FA_DIRTY) { /* Write-back sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + sect = clst2sect(fs, fp->clust); /* Get current sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btw / SS(fs); /* When remaining bytes >= sector size, */ + if (cc > 0) { /* Write maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_write(fs->pdrv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); +#if FF_FS_MINIMIZE <= 2 +#if FF_FS_TINY + if (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + memcpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs)); + fs->wflag = 0; + } +#else + if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + memcpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs)); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif +#endif + wcnt = SS(fs) * cc; /* Number of bytes transferred */ + continue; + } +#if FF_FS_TINY + if (fp->fptr >= fp->obj.objsize) { /* Avoid silly cache filling on the growing edge */ + if (sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); + fs->winsect = sect; + } +#else + if (fp->sect != sect && /* Fill sector cache with file data */ + fp->fptr < fp->obj.objsize && + disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) { + ABORT(fs, FR_DISK_ERR); + } +#endif + fp->sect = sect; + } + wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */ + if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */ +#if FF_FS_TINY + if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */ + memcpy(fs->win + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */ + fs->wflag = 1; +#else + memcpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */ + fp->flag |= FA_DIRTY; +#endif + } + + fp->flag |= FA_MODIFIED; /* Set file change flag */ + + LEAVE_FF(fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_sync ( + FIL* fp /* Open file to be synced */ +) +{ + FRESULT res; + FATFS *fs; + DWORD tm; + BYTE *dir; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) { + if (fp->flag & FA_MODIFIED) { /* Is there any change to the file? */ +#if !FF_FS_TINY + if (fp->flag & FA_DIRTY) { /* Write-back cached data if needed */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) LEAVE_FF(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + /* Update the directory entry */ + tm = GET_FATTIME(); /* Modified time */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + res = fill_first_frag(&fp->obj); /* Fill first fragment on the FAT if needed */ + if (res == FR_OK) { + res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */ + } + if (res == FR_OK) { + DIR dj; + DEF_NAMBUF + + INIT_NAMBUF(fs); + res = load_obj_xdir(&dj, &fp->obj); /* Load directory entry block */ + if (res == FR_OK) { + fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */ + fs->dirbuf[XDIR_GenFlags] = fp->obj.stat | 1; /* Update file allocation information */ + st_dword(fs->dirbuf + XDIR_FstClus, fp->obj.sclust); /* Update start cluster */ + st_qword(fs->dirbuf + XDIR_FileSize, fp->obj.objsize); /* Update file size */ + st_qword(fs->dirbuf + XDIR_ValidFileSize, fp->obj.objsize); /* (FatFs does not support Valid File Size feature) */ + st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Update modified time */ + fs->dirbuf[XDIR_ModTime10] = 0; + st_dword(fs->dirbuf + XDIR_AccTime, 0); + res = store_xdir(&dj); /* Restore it to the directory */ + if (res == FR_OK) { + res = sync_fs(fs); + fp->flag &= (BYTE)~FA_MODIFIED; + } + } + FREE_NAMBUF(); + } + } else +#endif + { + res = move_window(fs, fp->dir_sect); + if (res == FR_OK) { + dir = fp->dir_ptr; + dir[DIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */ + st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation information */ + st_dword(dir + DIR_FileSize, (DWORD)fp->obj.objsize); /* Update file size */ + st_dword(dir + DIR_ModTime, tm); /* Update modified time */ + st_word(dir + DIR_LstAccDate, 0); + fs->wflag = 1; + res = sync_fs(fs); /* Restore it to the directory */ + fp->flag &= (BYTE)~FA_MODIFIED; + } + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Close File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_close ( + FIL* fp /* Open file to be closed */ +) +{ + FRESULT res; + FATFS *fs; + +#if !FF_FS_READONLY + res = f_sync(fp); /* Flush cached data */ + if (res == FR_OK) +#endif + { + res = validate(&fp->obj, &fs); /* Lock volume */ + if (res == FR_OK) { +#if FF_FS_LOCK + res = dec_share(fp->obj.lockid); /* Decrement file open counter */ + if (res == FR_OK) fp->obj.fs = 0; /* Invalidate file object */ +#else + fp->obj.fs = 0; /* Invalidate file object */ +#endif +#if FF_FS_REENTRANT + unlock_volume(fs, FR_OK); /* Unlock volume */ +#endif + } + } + return res; +} + + + + +#if FF_FS_RPATH >= 1 +/*-----------------------------------------------------------------------*/ +/* Change Current Directory or Current Drive, Get Current Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chdrive ( + const TCHAR* path /* Drive number to set */ +) +{ + int vol; + + + /* Get logical drive number */ + vol = get_ldnumber(&path); + if (vol < 0) return FR_INVALID_DRIVE; + CurrVol = (BYTE)vol; /* Set it as current volume */ + + return FR_OK; +} + + + +FRESULT f_chdir ( + const TCHAR* path /* Pointer to the directory path */ +) +{ +#if FF_STR_VOLUME_ID == 2 + UINT i; +#endif + FRESULT res; + DIR dj; + FATFS *fs; + DEF_NAMBUF + + + /* Get logical drive */ + res = mount_volume(&path, &fs, 0); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it the start directory itself? */ + fs->cdir = dj.obj.sclust; +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->cdc_scl = dj.obj.c_scl; + fs->cdc_size = dj.obj.c_size; + fs->cdc_ofs = dj.obj.c_ofs; + } +#endif + } else { + if (dj.obj.attr & AM_DIR) { /* It is a sub-directory */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->cdir = ld_dword(fs->dirbuf + XDIR_FstClus); /* Sub-directory cluster */ + fs->cdc_scl = dj.obj.sclust; /* Save containing directory information */ + fs->cdc_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat; + fs->cdc_ofs = dj.blk_ofs; + } else +#endif + { + fs->cdir = ld_clust(fs, dj.dir); /* Sub-directory cluster */ + } + } else { + res = FR_NO_PATH; /* Reached but a file */ + } + } + } + FREE_NAMBUF(); + if (res == FR_NO_FILE) res = FR_NO_PATH; +#if FF_STR_VOLUME_ID == 2 /* Also current drive is changed if in Unix style volume ID */ + if (res == FR_OK) { + for (i = FF_VOLUMES - 1; i && fs != FatFs[i]; i--) ; /* Set current drive */ + CurrVol = (BYTE)i; + } +#endif + } + + LEAVE_FF(fs, res); +} + + +#if FF_FS_RPATH >= 2 +FRESULT f_getcwd ( + TCHAR* buff, /* Pointer to the directory path */ + UINT len /* Size of buff in unit of TCHAR */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + UINT i, n; + DWORD ccl; + TCHAR *tp = buff; +#if FF_VOLUMES >= 2 + UINT vl; +#if FF_STR_VOLUME_ID + const char *vp; +#endif +#endif + FILINFO fno; + DEF_NAMBUF + + + /* Get logical drive */ + buff[0] = 0; /* Set null string to get current volume */ + res = mount_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + + /* Follow parent directories and create the path */ + i = len; /* Bottom of buffer (directory stack base) */ + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* (Cannot do getcwd on exFAT and returns root path) */ + dj.obj.sclust = fs->cdir; /* Start to follow upper directory from current directory */ + while ((ccl = dj.obj.sclust) != 0) { /* Repeat while current directory is a sub-directory */ + res = dir_sdi(&dj, 1 * SZDIRE); /* Get parent directory */ + if (res != FR_OK) break; + res = move_window(fs, dj.sect); + if (res != FR_OK) break; + dj.obj.sclust = ld_clust(fs, dj.dir); /* Goto parent directory */ + res = dir_sdi(&dj, 0); + if (res != FR_OK) break; + do { /* Find the entry links to the child directory */ + res = DIR_READ_FILE(&dj); + if (res != FR_OK) break; + if (ccl == ld_clust(fs, dj.dir)) break; /* Found the entry */ + res = dir_next(&dj, 0); + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */ + if (res != FR_OK) break; + get_fileinfo(&dj, &fno); /* Get the directory name and push it to the buffer */ + for (n = 0; fno.fname[n]; n++) ; /* Name length */ + if (i < n + 1) { /* Insufficient space to store the path name? */ + res = FR_NOT_ENOUGH_CORE; break; + } + while (n) buff[--i] = fno.fname[--n]; /* Stack the name */ + buff[--i] = '/'; + } + } + if (res == FR_OK) { + if (i == len) buff[--i] = '/'; /* Is it the root-directory? */ +#if FF_VOLUMES >= 2 /* Put drive prefix */ + vl = 0; +#if FF_STR_VOLUME_ID >= 1 /* String volume ID */ + for (n = 0, vp = (const char*)VolumeStr[CurrVol]; vp[n]; n++) ; + if (i >= n + 2) { + if (FF_STR_VOLUME_ID == 2) *tp++ = (TCHAR)'/'; + for (vl = 0; vl < n; *tp++ = (TCHAR)vp[vl], vl++) ; + if (FF_STR_VOLUME_ID == 1) *tp++ = (TCHAR)':'; + vl++; + } +#else /* Numeric volume ID */ + if (i >= 3) { + *tp++ = (TCHAR)'0' + CurrVol; + *tp++ = (TCHAR)':'; + vl = 2; + } +#endif + if (vl == 0) res = FR_NOT_ENOUGH_CORE; +#endif + /* Add current directory path */ + if (res == FR_OK) { + do { /* Copy stacked path string */ + *tp++ = buff[i++]; + } while (i < len); + } + } + FREE_NAMBUF(); + } + + *tp = 0; + LEAVE_FF(fs, res); +} + +#endif /* FF_FS_RPATH >= 2 */ +#endif /* FF_FS_RPATH >= 1 */ + + + +#if FF_FS_MINIMIZE <= 2 +/*-----------------------------------------------------------------------*/ +/* Seek File Read/Write Pointer */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_lseek ( + FIL* fp, /* Pointer to the file object */ + FSIZE_t ofs /* File pointer from top of file */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, bcs; + LBA_t nsect; + FSIZE_t ifptr; +#if FF_USE_FASTSEEK + DWORD cl, pcl, ncl, tcl, tlen, ulen; + DWORD *tbl; + LBA_t dsc; +#endif + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) res = (FRESULT)fp->err; +#if FF_FS_EXFAT && !FF_FS_READONLY + if (res == FR_OK && fs->fs_type == FS_EXFAT) { + res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */ + } +#endif + if (res != FR_OK) LEAVE_FF(fs, res); + +#if FF_USE_FASTSEEK + if (fp->cltbl) { /* Fast seek */ + if (ofs == CREATE_LINKMAP) { /* Create CLMT */ + tbl = fp->cltbl; + tlen = *tbl++; ulen = 2; /* Given table size and required table size */ + cl = fp->obj.sclust; /* Origin of the chain */ + if (cl != 0) { + do { + /* Get a fragment */ + tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */ + do { + pcl = cl; ncl++; + cl = get_fat(&fp->obj, cl); + if (cl <= 1) ABORT(fs, FR_INT_ERR); + if (cl == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + } while (cl == pcl + 1); + if (ulen <= tlen) { /* Store the length and top of the fragment */ + *tbl++ = ncl; *tbl++ = tcl; + } + } while (cl < fs->n_fatent); /* Repeat until end of chain */ + } + *fp->cltbl = ulen; /* Number of items used */ + if (ulen <= tlen) { + *tbl = 0; /* Terminate table */ + } else { + res = FR_NOT_ENOUGH_CORE; /* Given table size is smaller than required */ + } + } else { /* Fast seek */ + if (ofs > fp->obj.objsize) ofs = fp->obj.objsize; /* Clip offset at the file size */ + fp->fptr = ofs; /* Set file pointer */ + if (ofs > 0) { + fp->clust = clmt_clust(fp, ofs - 1); + dsc = clst2sect(fs, fp->clust); + if (dsc == 0) ABORT(fs, FR_INT_ERR); + dsc += (DWORD)((ofs - 1) / SS(fs)) & (fs->csize - 1); + if (fp->fptr % SS(fs) && dsc != fp->sect) { /* Refill sector cache if needed */ +#if !FF_FS_TINY +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, fp->buf, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Load current sector */ +#endif + fp->sect = dsc; + } + } + } + } else +#endif + + /* Normal Seek */ + { +#if FF_FS_EXFAT + if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; /* Clip at 4 GiB - 1 if at FATxx */ +#endif + if (ofs > fp->obj.objsize && (FF_FS_READONLY || !(fp->flag & FA_WRITE))) { /* In read-only mode, clip offset with the file size */ + ofs = fp->obj.objsize; + } + ifptr = fp->fptr; + fp->fptr = nsect = 0; + if (ofs > 0) { + bcs = (DWORD)fs->csize * SS(fs); /* Cluster size (byte) */ + if (ifptr > 0 && + (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */ + fp->fptr = (ifptr - 1) & ~(FSIZE_t)(bcs - 1); /* start from the current cluster */ + ofs -= fp->fptr; + clst = fp->clust; + } else { /* When seek to back cluster, */ + clst = fp->obj.sclust; /* start from the first cluster */ +#if !FF_FS_READONLY + if (clst == 0) { /* If no cluster chain, create a new chain */ + clst = create_chain(&fp->obj, 0); + if (clst == 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->obj.sclust = clst; + } +#endif + fp->clust = clst; + } + if (clst != 0) { + while (ofs > bcs) { /* Cluster following loop */ + ofs -= bcs; fp->fptr += bcs; +#if !FF_FS_READONLY + if (fp->flag & FA_WRITE) { /* Check if in write mode or not */ + if (FF_FS_EXFAT && fp->fptr > fp->obj.objsize) { /* No FAT chain object needs correct objsize to generate FAT value */ + fp->obj.objsize = fp->fptr; + fp->flag |= FA_MODIFIED; + } + clst = create_chain(&fp->obj, clst); /* Follow chain with forceed stretch */ + if (clst == 0) { /* Clip file size in case of disk full */ + ofs = 0; break; + } + } else +#endif + { + clst = get_fat(&fp->obj, clst); /* Follow cluster chain if not in write mode */ + } + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + if (clst <= 1 || clst >= fs->n_fatent) ABORT(fs, FR_INT_ERR); + fp->clust = clst; + } + fp->fptr += ofs; + if (ofs % SS(fs)) { + nsect = clst2sect(fs, clst); /* Current sector */ + if (nsect == 0) ABORT(fs, FR_INT_ERR); + nsect += (DWORD)(ofs / SS(fs)); + } + } + } + if (!FF_FS_READONLY && fp->fptr > fp->obj.objsize) { /* Set file change flag if the file size is extended */ + fp->obj.objsize = fp->fptr; + fp->flag |= FA_MODIFIED; + } + if (fp->fptr % SS(fs) && nsect != fp->sect) { /* Fill sector cache if needed */ +#if !FF_FS_TINY +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, fp->buf, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ +#endif + fp->sect = nsect; + } + } + + LEAVE_FF(fs, res); +} + + + +#if FF_FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Create a Directory Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_opendir ( + DIR* dp, /* Pointer to directory object to create */ + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + FATFS *fs; + DEF_NAMBUF + + + if (!dp) return FR_INVALID_OBJECT; + + /* Get logical drive */ + res = mount_volume(&path, &fs, 0); + if (res == FR_OK) { + dp->obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(dp, path); /* Follow the path to the directory */ + if (res == FR_OK) { /* Follow completed */ + if (!(dp->fn[NSFLAG] & NS_NONAME)) { /* It is not the origin directory itself */ + if (dp->obj.attr & AM_DIR) { /* This object is a sub-directory */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + dp->obj.c_scl = dp->obj.sclust; /* Get containing directory inforamation */ + dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat; + dp->obj.c_ofs = dp->blk_ofs; + init_alloc_info(fs, &dp->obj); /* Get object allocation info */ + } else +#endif + { + dp->obj.sclust = ld_clust(fs, dp->dir); /* Get object allocation info */ + } + } else { /* This object is a file */ + res = FR_NO_PATH; + } + } + if (res == FR_OK) { + dp->obj.id = fs->id; + res = dir_sdi(dp, 0); /* Rewind directory */ +#if FF_FS_LOCK + if (res == FR_OK) { + if (dp->obj.sclust != 0) { + dp->obj.lockid = inc_share(dp, 0); /* Lock the sub directory */ + if (!dp->obj.lockid) res = FR_TOO_MANY_OPEN_FILES; + } else { + dp->obj.lockid = 0; /* Root directory need not to be locked */ + } + } +#endif + } + } + FREE_NAMBUF(); + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + if (res != FR_OK) dp->obj.fs = 0; /* Invalidate the directory object if function failed */ + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Close Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_closedir ( + DIR *dp /* Pointer to the directory object to be closed */ +) +{ + FRESULT res; + FATFS *fs; + + + res = validate(&dp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) { +#if FF_FS_LOCK + if (dp->obj.lockid) res = dec_share(dp->obj.lockid); /* Decrement sub-directory open counter */ + if (res == FR_OK) dp->obj.fs = 0; /* Invalidate directory object */ +#else + dp->obj.fs = 0; /* Invalidate directory object */ +#endif +#if FF_FS_REENTRANT + unlock_volume(fs, FR_OK); /* Unlock volume */ +#endif + } + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read Directory Entries in Sequence */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_readdir ( + DIR* dp, /* Pointer to the open directory object */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + FATFS *fs; + DEF_NAMBUF + + + res = validate(&dp->obj, &fs); /* Check validity of the directory object */ + if (res == FR_OK) { + if (!fno) { + res = dir_sdi(dp, 0); /* Rewind the directory object */ + } else { + INIT_NAMBUF(fs); + res = DIR_READ_FILE(dp); /* Read an item */ + if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory */ + if (res == FR_OK) { /* A valid entry is found */ + get_fileinfo(dp, fno); /* Get the object information */ + res = dir_next(dp, 0); /* Increment index for next */ + if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory now */ + } + FREE_NAMBUF(); + } + } + LEAVE_FF(fs, res); +} + + + +#if FF_USE_FIND +/*-----------------------------------------------------------------------*/ +/* Find Next File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_findnext ( + DIR* dp, /* Pointer to the open directory object */ + FILINFO* fno /* Pointer to the file information structure */ +) +{ + FRESULT res; + + + for (;;) { + res = f_readdir(dp, fno); /* Get a directory item */ + if (res != FR_OK || !fno || !fno->fname[0]) break; /* Terminate if any error or end of directory */ + if (pattern_match(dp->pat, fno->fname, 0, FIND_RECURS)) break; /* Test for the file name */ +#if FF_USE_LFN && FF_USE_FIND == 2 + if (pattern_match(dp->pat, fno->altname, 0, FIND_RECURS)) break; /* Test for alternative name if exist */ +#endif + } + return res; +} + + + +/*-----------------------------------------------------------------------*/ +/* Find First File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_findfirst ( + DIR* dp, /* Pointer to the blank directory object */ + FILINFO* fno, /* Pointer to the file information structure */ + const TCHAR* path, /* Pointer to the directory to open */ + const TCHAR* pattern /* Pointer to the matching pattern */ +) +{ + FRESULT res; + + + dp->pat = pattern; /* Save pointer to pattern string */ + res = f_opendir(dp, path); /* Open the target directory */ + if (res == FR_OK) { + res = f_findnext(dp, fno); /* Find the first item */ + } + return res; +} + +#endif /* FF_USE_FIND */ + + + +#if FF_FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Get File Status */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_stat ( + const TCHAR* path, /* Pointer to the file path */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DIR dj; + DEF_NAMBUF + + + /* Get logical drive */ + res = mount_volume(&path, &dj.obj.fs, 0); + if (res == FR_OK) { + INIT_NAMBUF(dj.obj.fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.fn[NSFLAG] & NS_NONAME) { /* It is origin directory */ + res = FR_INVALID_NAME; + } else { /* Found an object */ + if (fno) get_fileinfo(&dj, fno); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(dj.obj.fs, res); +} + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Get Number of Free Clusters */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getfree ( + const TCHAR* path, /* Logical drive number */ + DWORD* nclst, /* Pointer to a variable to return number of free clusters */ + FATFS** fatfs /* Pointer to return pointer to corresponding filesystem object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD nfree, clst, stat; + LBA_t sect; + UINT i; + FFOBJID obj; + + + /* Get logical drive */ + res = mount_volume(&path, &fs, 0); + if (res == FR_OK) { + *fatfs = fs; /* Return ptr to the fs object */ + /* If free_clst is valid, return it without full FAT scan */ + if (fs->free_clst <= fs->n_fatent - 2) { + *nclst = fs->free_clst; + } else { + /* Scan FAT to obtain number of free clusters */ + nfree = 0; + if (fs->fs_type == FS_FAT12) { /* FAT12: Scan bit field FAT entries */ + clst = 2; obj.fs = fs; + do { + stat = get_fat(&obj, clst); + if (stat == 0xFFFFFFFF) { + res = FR_DISK_ERR; break; + } + if (stat == 1) { + res = FR_INT_ERR; break; + } + if (stat == 0) nfree++; + } while (++clst < fs->n_fatent); + } else { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* exFAT: Scan allocation bitmap */ + BYTE bm; + UINT b; + + clst = fs->n_fatent - 2; /* Number of clusters */ + sect = fs->bitbase; /* Bitmap sector */ + i = 0; /* Offset in the sector */ + do { /* Counts numbuer of bits with zero in the bitmap */ + if (i == 0) { /* New sector? */ + res = move_window(fs, sect++); + if (res != FR_OK) break; + } + for (b = 8, bm = ~fs->win[i]; b && clst; b--, clst--) { + nfree += bm & 1; + bm >>= 1; + } + i = (i + 1) % SS(fs); + } while (clst); + } else +#endif + { /* FAT16/32: Scan WORD/DWORD FAT entries */ + clst = fs->n_fatent; /* Number of entries */ + sect = fs->fatbase; /* Top of the FAT */ + i = 0; /* Offset in the sector */ + do { /* Counts numbuer of entries with zero in the FAT */ + if (i == 0) { /* New sector? */ + res = move_window(fs, sect++); + if (res != FR_OK) break; + } + if (fs->fs_type == FS_FAT16) { + if (ld_word(fs->win + i) == 0) nfree++; + i += 2; + } else { + if ((ld_dword(fs->win + i) & 0x0FFFFFFF) == 0) nfree++; + i += 4; + } + i %= SS(fs); + } while (--clst); + } + } + if (res == FR_OK) { /* Update parameters if succeeded */ + *nclst = nfree; /* Return the free clusters */ + fs->free_clst = nfree; /* Now free_clst is valid */ + fs->fsi_flag |= 1; /* FAT32: FSInfo is to be updated */ + } + } + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD ncl; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + if (fp->fptr < fp->obj.objsize) { /* Process when fptr is not on the eof */ + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + res = remove_chain(&fp->obj, fp->obj.sclust, 0); + fp->obj.sclust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_fat(&fp->obj, fp->clust); + res = FR_OK; + if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (ncl == 1) res = FR_INT_ERR; + if (res == FR_OK && ncl < fs->n_fatent) { + res = remove_chain(&fp->obj, ncl, fp->clust); + } + } + fp->obj.objsize = fp->fptr; /* Set file size to current read/write point */ + fp->flag |= FA_MODIFIED; +#if !FF_FS_TINY + if (res == FR_OK && (fp->flag & FA_DIRTY)) { + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) { + res = FR_DISK_ERR; + } else { + fp->flag &= (BYTE)~FA_DIRTY; + } + } +#endif + if (res != FR_OK) ABORT(fs, res); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Delete a File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_unlink ( + const TCHAR* path /* Pointer to the file or directory path */ +) +{ + FRESULT res; + FATFS *fs; + DIR dj, sdj; + DWORD dclst = 0; +#if FF_FS_EXFAT + FFOBJID obj; +#endif + DEF_NAMBUF + + + /* Get logical drive */ + res = mount_volume(&path, &fs, FA_WRITE); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (FF_FS_RPATH && res == FR_OK && (dj.fn[NSFLAG] & NS_DOT)) { + res = FR_INVALID_NAME; /* Cannot remove dot entry */ + } +#if FF_FS_LOCK + if (res == FR_OK) res = chk_share(&dj, 2); /* Check if it is an open object */ +#endif + if (res == FR_OK) { /* The object is accessible */ + if (dj.fn[NSFLAG] & NS_NONAME) { + res = FR_INVALID_NAME; /* Cannot remove the origin directory */ + } else { + if (dj.obj.attr & AM_RDO) { + res = FR_DENIED; /* Cannot remove R/O object */ + } + } + if (res == FR_OK) { +#if FF_FS_EXFAT + obj.fs = fs; + if (fs->fs_type == FS_EXFAT) { + init_alloc_info(fs, &obj); + dclst = obj.sclust; + } else +#endif + { + dclst = ld_clust(fs, dj.dir); + } + if (dj.obj.attr & AM_DIR) { /* Is it a sub-directory? */ +#if FF_FS_RPATH != 0 + if (dclst == fs->cdir) { /* Is it the current directory? */ + res = FR_DENIED; + } else +#endif + { + sdj.obj.fs = fs; /* Open the sub-directory */ + sdj.obj.sclust = dclst; +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + sdj.obj.objsize = obj.objsize; + sdj.obj.stat = obj.stat; + } +#endif + res = dir_sdi(&sdj, 0); + if (res == FR_OK) { + res = DIR_READ_FILE(&sdj); /* Test if the directory is empty */ + if (res == FR_OK) res = FR_DENIED; /* Not empty? */ + if (res == FR_NO_FILE) res = FR_OK; /* Empty? */ + } + } + } + } + if (res == FR_OK) { + res = dir_remove(&dj); /* Remove the directory entry */ + if (res == FR_OK && dclst != 0) { /* Remove the cluster chain if exist */ +#if FF_FS_EXFAT + res = remove_chain(&obj, dclst, 0); +#else + res = remove_chain(&dj.obj, dclst, 0); +#endif + } + if (res == FR_OK) res = sync_fs(fs); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create a Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkdir ( + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + FATFS *fs; + DIR dj; + FFOBJID sobj; + DWORD dcl, pcl, tm; + DEF_NAMBUF + + + res = mount_volume(&path, &fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) res = FR_EXIST; /* Name collision? */ + if (FF_FS_RPATH && res == FR_NO_FILE && (dj.fn[NSFLAG] & NS_DOT)) { /* Invalid name? */ + res = FR_INVALID_NAME; + } + if (res == FR_NO_FILE) { /* It is clear to create a new directory */ + sobj.fs = fs; /* New object id to create a new chain */ + dcl = create_chain(&sobj, 0); /* Allocate a cluster for the new directory */ + res = FR_OK; + if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster? */ + if (dcl == 1) res = FR_INT_ERR; /* Any insanity? */ + if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; /* Disk error? */ + tm = GET_FATTIME(); + if (res == FR_OK) { + res = dir_clear(fs, dcl); /* Clean up the new table */ + if (res == FR_OK) { + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* Create dot entries (FAT only) */ + memset(fs->win + DIR_Name, ' ', 11); /* Create "." entry */ + fs->win[DIR_Name] = '.'; + fs->win[DIR_Attr] = AM_DIR; + st_dword(fs->win + DIR_ModTime, tm); + st_clust(fs, fs->win, dcl); + memcpy(fs->win + SZDIRE, fs->win, SZDIRE); /* Create ".." entry */ + fs->win[SZDIRE + 1] = '.'; pcl = dj.obj.sclust; + st_clust(fs, fs->win + SZDIRE, pcl); + fs->wflag = 1; + } + res = dir_register(&dj); /* Register the object to the parent directoy */ + } + } + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* Initialize directory entry block */ + st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Created time */ + st_dword(fs->dirbuf + XDIR_FstClus, dcl); /* Table start cluster */ + st_dword(fs->dirbuf + XDIR_FileSize, (DWORD)fs->csize * SS(fs)); /* Directory size needs to be valid */ + st_dword(fs->dirbuf + XDIR_ValidFileSize, (DWORD)fs->csize * SS(fs)); + fs->dirbuf[XDIR_GenFlags] = 3; /* Initialize the object flag */ + fs->dirbuf[XDIR_Attr] = AM_DIR; /* Attribute */ + res = store_xdir(&dj); + } else +#endif + { + st_dword(dj.dir + DIR_ModTime, tm); /* Created time */ + st_clust(fs, dj.dir, dcl); /* Table start cluster */ + dj.dir[DIR_Attr] = AM_DIR; /* Attribute */ + fs->wflag = 1; + } + if (res == FR_OK) { + res = sync_fs(fs); + } + } else { + remove_chain(&sobj, dcl, 0); /* Could not register, remove the allocated cluster */ + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Rename a File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_rename ( + const TCHAR* path_old, /* Pointer to the object name to be renamed */ + const TCHAR* path_new /* Pointer to the new name */ +) +{ + FRESULT res; + FATFS *fs; + DIR djo, djn; + BYTE buf[FF_FS_EXFAT ? SZDIRE * 2 : SZDIRE], *dir; + LBA_t sect; + DEF_NAMBUF + + + get_ldnumber(&path_new); /* Snip the drive number of new name off */ + res = mount_volume(&path_old, &fs, FA_WRITE); /* Get logical drive of the old object */ + if (res == FR_OK) { + djo.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&djo, path_old); /* Check old object */ + if (res == FR_OK && (djo.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check validity of name */ +#if FF_FS_LOCK + if (res == FR_OK) { + res = chk_share(&djo, 2); + } +#endif + if (res == FR_OK) { /* Object to be renamed is found */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* At exFAT volume */ + BYTE nf, nn; + WORD nh; + + memcpy(buf, fs->dirbuf, SZDIRE * 2); /* Save 85+C0 entry of old object */ + memcpy(&djn, &djo, sizeof djo); + res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */ + if (res == FR_OK) { /* Is new name already in use by any other object? */ + res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST; + } + if (res == FR_NO_FILE) { /* It is a valid path and no name collision */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + nf = fs->dirbuf[XDIR_NumSec]; nn = fs->dirbuf[XDIR_NumName]; + nh = ld_word(fs->dirbuf + XDIR_NameHash); + memcpy(fs->dirbuf, buf, SZDIRE * 2); /* Restore 85+C0 entry */ + fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn; + st_word(fs->dirbuf + XDIR_NameHash, nh); + if (!(fs->dirbuf[XDIR_Attr] & AM_DIR)) fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */ +/* Start of critical section where an interruption can cause a cross-link */ + res = store_xdir(&djn); + } + } + } else +#endif + { /* At FAT/FAT32 volume */ + memcpy(buf, djo.dir, SZDIRE); /* Save directory entry of the object */ + memcpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */ + res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */ + if (res == FR_OK) { /* Is new name already in use by any other object? */ + res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST; + } + if (res == FR_NO_FILE) { /* It is a valid path and no name collision */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + dir = djn.dir; /* Copy directory entry of the object except name */ + memcpy(dir + 13, buf + 13, SZDIRE - 13); + dir[DIR_Attr] = buf[DIR_Attr]; + if (!(dir[DIR_Attr] & AM_DIR)) dir[DIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */ + fs->wflag = 1; + if ((dir[DIR_Attr] & AM_DIR) && djo.obj.sclust != djn.obj.sclust) { /* Update .. entry in the sub-directory if needed */ + sect = clst2sect(fs, ld_clust(fs, dir)); + if (sect == 0) { + res = FR_INT_ERR; + } else { +/* Start of critical section where an interruption can cause a cross-link */ + res = move_window(fs, sect); + dir = fs->win + SZDIRE * 1; /* Ptr to .. entry */ + if (res == FR_OK && dir[1] == '.') { + st_clust(fs, dir, djn.obj.sclust); + fs->wflag = 1; + } + } + } + } + } + } + if (res == FR_OK) { + res = dir_remove(&djo); /* Remove old entry */ + if (res == FR_OK) { + res = sync_fs(fs); + } + } +/* End of the critical section */ + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_FS_MINIMIZE == 0 */ +#endif /* FF_FS_MINIMIZE <= 1 */ +#endif /* FF_FS_MINIMIZE <= 2 */ + + + +#if FF_USE_CHMOD && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Change Attribute */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chmod ( + const TCHAR* path, /* Pointer to the file path */ + BYTE attr, /* Attribute bits */ + BYTE mask /* Attribute mask to change */ +) +{ + FRESULT res; + FATFS *fs; + DIR dj; + DEF_NAMBUF + + + res = mount_volume(&path, &fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */ + if (res == FR_OK) { + mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->dirbuf[XDIR_Attr] = (attr & mask) | (fs->dirbuf[XDIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + res = store_xdir(&dj); + } else +#endif + { + dj.dir[DIR_Attr] = (attr & mask) | (dj.dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + fs->wflag = 1; + } + if (res == FR_OK) { + res = sync_fs(fs); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + const TCHAR* path, /* Pointer to the file/directory name */ + const FILINFO* fno /* Pointer to the timestamp to be set */ +) +{ + FRESULT res; + FATFS *fs; + DIR dj; + DEF_NAMBUF + + + res = mount_volume(&path, &fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */ + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + st_dword(fs->dirbuf + XDIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime); + res = store_xdir(&dj); + } else +#endif + { + st_dword(dj.dir + DIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime); + fs->wflag = 1; + } + if (res == FR_OK) { + res = sync_fs(fs); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* FF_USE_CHMOD && !FF_FS_READONLY */ + + + +#if FF_USE_LABEL +/*-----------------------------------------------------------------------*/ +/* Get Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getlabel ( + const TCHAR* path, /* Logical drive number */ + TCHAR* label, /* Buffer to store the volume label */ + DWORD* vsn /* Variable to store the volume serial number */ +) +{ + FRESULT res; + FATFS *fs; + DIR dj; + UINT si, di; + WCHAR wc; + + /* Get logical drive */ + res = mount_volume(&path, &fs, 0); + + /* Get volume label */ + if (res == FR_OK && label) { + dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = DIR_READ_LABEL(&dj); /* Find a volume label entry */ + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + WCHAR hs; + UINT nw; + + for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */ + wc = ld_word(dj.dir + XDIR_Label + si * 2); + if (hs == 0 && IsSurrogate(wc)) { /* Is the code a surrogate? */ + hs = wc; continue; + } + nw = put_utf((DWORD)hs << 16 | wc, &label[di], 4); /* Store it in API encoding */ + if (nw == 0) { /* Encode error? */ + di = 0; break; + } + di += nw; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + label[di] = 0; + } else +#endif + { + si = di = 0; /* Extract volume label from AM_VOL entry */ + while (si < 11) { + wc = dj.dir[si++]; +#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode output */ + if (dbc_1st((BYTE)wc) && si < 11) wc = wc << 8 | dj.dir[si++]; /* Is it a DBC? */ + wc = ff_oem2uni(wc, CODEPAGE); /* Convert it into Unicode */ + if (wc == 0) { /* Invalid char in current code page? */ + di = 0; break; + } + di += put_utf(wc, &label[di], 4); /* Store it in Unicode */ +#else /* ANSI/OEM output */ + label[di++] = (TCHAR)wc; +#endif + } + do { /* Truncate trailing spaces */ + label[di] = 0; + if (di == 0) break; + } while (label[--di] == ' '); + } + } + } + if (res == FR_NO_FILE) { /* No label entry and return nul string */ + label[0] = 0; + res = FR_OK; + } + } + + /* Get volume serial number */ + if (res == FR_OK && vsn) { + res = move_window(fs, fs->volbase); + if (res == FR_OK) { + switch (fs->fs_type) { + case FS_EXFAT: + di = BPB_VolIDEx; + break; + + case FS_FAT32: + di = BS_VolID32; + break; + + default: + di = BS_VolID; + } + *vsn = ld_dword(fs->win + di); + } + } + + LEAVE_FF(fs, res); +} + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Set Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setlabel ( + const TCHAR* label /* Volume label to set with heading logical drive number */ +) +{ + FRESULT res; + FATFS *fs; + DIR dj; + BYTE dirvn[22]; + UINT di; + WCHAR wc; + static const char badchr[18] = "+.,;=[]" "/*:<>|\\\"\?\x7F"; /* [0..16] for FAT, [7..16] for exFAT */ +#if FF_USE_LFN + DWORD dc; +#endif + + /* Get logical drive */ + res = mount_volume(&label, &fs, FA_WRITE); + if (res != FR_OK) LEAVE_FF(fs, res); + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + memset(dirvn, 0, 22); + di = 0; + while ((UINT)*label >= ' ') { /* Create volume label */ + dc = tchar2uni(&label); /* Get a Unicode character */ + if (dc >= 0x10000) { + if (dc == 0xFFFFFFFF || di >= 10) { /* Wrong surrogate or buffer overflow */ + dc = 0; + } else { + st_word(dirvn + di * 2, (WCHAR)(dc >> 16)); di++; + } + } + if (dc == 0 || strchr(&badchr[7], (int)dc) || di >= 11) { /* Check validity of the volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + st_word(dirvn + di * 2, (WCHAR)dc); di++; + } + } else +#endif + { /* On the FAT/FAT32 volume */ + memset(dirvn, ' ', 11); + di = 0; + while ((UINT)*label >= ' ') { /* Create volume label */ +#if FF_USE_LFN + dc = tchar2uni(&label); + wc = (dc < 0x10000) ? ff_uni2oem(ff_wtoupper(dc), CODEPAGE) : 0; +#else /* ANSI/OEM input */ + wc = (BYTE)*label++; + if (dbc_1st((BYTE)wc)) wc = dbc_2nd((BYTE)*label) ? wc << 8 | (BYTE)*label++ : 0; + if (IsLower(wc)) wc -= 0x20; /* To upper ASCII characters */ +#if FF_CODE_PAGE == 0 + if (ExCvt && wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */ +#elif FF_CODE_PAGE < 900 + if (wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */ +#endif +#endif + if (wc == 0 || strchr(&badchr[0], (int)wc) || di >= (UINT)((wc >= 0x100) ? 10 : 11)) { /* Reject invalid characters for volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + if (wc >= 0x100) dirvn[di++] = (BYTE)(wc >> 8); + dirvn[di++] = (BYTE)wc; + } + if (dirvn[0] == DDEM) LEAVE_FF(fs, FR_INVALID_NAME); /* Reject illegal name (heading DDEM) */ + while (di && dirvn[di - 1] == ' ') di--; /* Snip trailing spaces */ + } + + /* Set volume label */ + dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = DIR_READ_LABEL(&dj); /* Get volume label entry */ + if (res == FR_OK) { + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_NumLabel] = (BYTE)di; /* Change the volume label */ + memcpy(dj.dir + XDIR_Label, dirvn, 22); + } else { + if (di != 0) { + memcpy(dj.dir, dirvn, 11); /* Change the volume label */ + } else { + dj.dir[DIR_Name] = DDEM; /* Remove the volume label */ + } + } + fs->wflag = 1; + res = sync_fs(fs); + } else { /* No volume label entry or an error */ + if (res == FR_NO_FILE) { + res = FR_OK; + if (di != 0) { /* Create a volume label entry */ + res = dir_alloc(&dj, 1); /* Allocate an entry */ + if (res == FR_OK) { + memset(dj.dir, 0, SZDIRE); /* Clean the entry */ + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_Type] = ET_VLABEL; /* Create volume label entry */ + dj.dir[XDIR_NumLabel] = (BYTE)di; + memcpy(dj.dir + XDIR_Label, dirvn, 22); + } else { + dj.dir[DIR_Attr] = AM_VOL; /* Create volume label entry */ + memcpy(dj.dir, dirvn, 11); + } + fs->wflag = 1; + res = sync_fs(fs); + } + } + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_USE_LABEL */ + + + +#if FF_USE_EXPAND && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Allocate a Contiguous Blocks to the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_expand ( + FIL* fp, /* Pointer to the file object */ + FSIZE_t fsz, /* File size to be expanded to */ + BYTE opt /* Operation mode 0:Find and prepare or 1:Find and allocate */ +) +{ + FRESULT res; + FATFS *fs; + DWORD n, clst, stcl, scl, ncl, tcl, lclst; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (fsz == 0 || fp->obj.objsize != 0 || !(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); +#if FF_FS_EXFAT + if (fs->fs_type != FS_EXFAT && fsz >= 0x100000000) LEAVE_FF(fs, FR_DENIED); /* Check if in size limit */ +#endif + n = (DWORD)fs->csize * SS(fs); /* Cluster size */ + tcl = (DWORD)(fsz / n) + ((fsz & (n - 1)) ? 1 : 0); /* Number of clusters required */ + stcl = fs->last_clst; lclst = 0; + if (stcl < 2 || stcl >= fs->n_fatent) stcl = 2; + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + scl = find_bitmap(fs, stcl, tcl); /* Find a contiguous cluster block */ + if (scl == 0) res = FR_DENIED; /* No contiguous cluster block was found */ + if (scl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (res == FR_OK) { /* A contiguous free area is found */ + if (opt) { /* Allocate it now */ + res = change_bitmap(fs, scl, tcl, 1); /* Mark the cluster block 'in use' */ + lclst = scl + tcl - 1; + } else { /* Set it as suggested point for next allocation */ + lclst = scl - 1; + } + } + } else +#endif + { + scl = clst = stcl; ncl = 0; + for (;;) { /* Find a contiguous cluster block */ + n = get_fat(&fp->obj, clst); + if (++clst >= fs->n_fatent) clst = 2; + if (n == 1) { + res = FR_INT_ERR; break; + } + if (n == 0xFFFFFFFF) { + res = FR_DISK_ERR; break; + } + if (n == 0) { /* Is it a free cluster? */ + if (++ncl == tcl) break; /* Break if a contiguous cluster block is found */ + } else { + scl = clst; ncl = 0; /* Not a free cluster */ + } + if (clst == stcl) { /* No contiguous cluster? */ + res = FR_DENIED; break; + } + } + if (res == FR_OK) { /* A contiguous free area is found */ + if (opt) { /* Allocate it now */ + for (clst = scl, n = tcl; n; clst++, n--) { /* Create a cluster chain on the FAT */ + res = put_fat(fs, clst, (n == 1) ? 0xFFFFFFFF : clst + 1); + if (res != FR_OK) break; + lclst = clst; + } + } else { /* Set it as suggested point for next allocation */ + lclst = scl - 1; + } + } + } + + if (res == FR_OK) { + fs->last_clst = lclst; /* Set suggested start cluster to start next */ + if (opt) { /* Is it allocated now? */ + fp->obj.sclust = scl; /* Update object allocation information */ + fp->obj.objsize = fsz; + if (FF_FS_EXFAT) fp->obj.stat = 2; /* Set status 'contiguous chain' */ + fp->flag |= FA_MODIFIED; + if (fs->free_clst <= fs->n_fatent - 2) { /* Update FSINFO */ + fs->free_clst -= tcl; + fs->fsi_flag |= 1; + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* FF_USE_EXPAND && !FF_FS_READONLY */ + + + +#if FF_USE_FORWARD +/*-----------------------------------------------------------------------*/ +/* Forward Data to the Stream Directly */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_forward ( + FIL* fp, /* Pointer to the file object */ + UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */ + UINT btf, /* Number of bytes to forward */ + UINT* bf /* Pointer to number of bytes forwarded */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst; + LBA_t sect; + FSIZE_t remain; + UINT rcnt, csect; + BYTE *dbuf; + + + *bf = 0; /* Clear transfer byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + remain = fp->obj.objsize - fp->fptr; + if (btf > remain) btf = (UINT)remain; /* Truncate btf by remaining bytes */ + + for ( ; btf > 0 && (*func)(0, 0); fp->fptr += rcnt, *bf += rcnt, btf -= rcnt) { /* Repeat until all data transferred or stream goes busy */ + csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */ + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + if (csect == 0) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->obj.sclust : get_fat(&fp->obj, fp->clust); + if (clst <= 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + } + sect = clst2sect(fs, fp->clust); /* Get current data sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; +#if FF_FS_TINY + if (move_window(fs, sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window to the file data */ + dbuf = fs->win; +#else + if (fp->sect != sect) { /* Fill sector cache with file data */ +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + } + dbuf = fp->buf; +#endif + fp->sect = sect; + rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */ + if (rcnt > btf) rcnt = btf; /* Clip it by btr if needed */ + rcnt = (*func)(dbuf + ((UINT)fp->fptr % SS(fs)), rcnt); /* Forward the file data */ + if (rcnt == 0) ABORT(fs, FR_INT_ERR); + } + + LEAVE_FF(fs, FR_OK); +} +#endif /* FF_USE_FORWARD */ + + + +#if !FF_FS_READONLY && FF_USE_MKFS +/*-----------------------------------------------------------------------*/ +/* Create FAT/exFAT volume (with sub-functions) */ +/*-----------------------------------------------------------------------*/ + +#define N_SEC_TRACK 63 /* Sectors per track for determination of drive CHS */ +#define GPT_ALIGN 0x100000 /* Alignment of partitions in GPT [byte] (>=128KB) */ +#define GPT_ITEMS 128 /* Number of GPT table size (>=128, sector aligned) */ + + +/* Create partitions on the physical drive in format of MBR or GPT */ + +static FRESULT create_partition ( + BYTE drv, /* Physical drive number */ + const LBA_t plst[], /* Partition list */ + BYTE sys, /* System ID for each partition (for only MBR) */ + BYTE *buf /* Working buffer for a sector */ +) +{ + UINT i, cy; + LBA_t sz_drv; + DWORD sz_drv32, nxt_alloc32, sz_part32; + BYTE *pte; + BYTE hd, n_hd, sc, n_sc; + + /* Get physical drive size */ + if (disk_ioctl(drv, GET_SECTOR_COUNT, &sz_drv) != RES_OK) return FR_DISK_ERR; + +#if FF_LBA64 + if (sz_drv >= FF_MIN_GPT) { /* Create partitions in GPT format */ + WORD ss; + UINT sz_ptbl, pi, si, ofs; + DWORD bcc, rnd, align; + QWORD nxt_alloc, sz_part, sz_pool, top_bpt; + static const BYTE gpt_mbr[16] = {0x00, 0x00, 0x02, 0x00, 0xEE, 0xFE, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}; + +#if FF_MAX_SS != FF_MIN_SS + if (disk_ioctl(drv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR; /* Get sector size */ + if (ss > FF_MAX_SS || ss < FF_MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR; +#else + ss = FF_MAX_SS; +#endif + rnd = (DWORD)sz_drv + GET_FATTIME(); /* Random seed */ + align = GPT_ALIGN / ss; /* Partition alignment for GPT [sector] */ + sz_ptbl = GPT_ITEMS * SZ_GPTE / ss; /* Size of partition table [sector] */ + top_bpt = sz_drv - sz_ptbl - 1; /* Backup partition table start sector */ + nxt_alloc = 2 + sz_ptbl; /* First allocatable sector */ + sz_pool = top_bpt - nxt_alloc; /* Size of allocatable area */ + bcc = 0xFFFFFFFF; sz_part = 1; + pi = si = 0; /* partition table index, size table index */ + do { + if (pi * SZ_GPTE % ss == 0) memset(buf, 0, ss); /* Clean the buffer if needed */ + if (sz_part != 0) { /* Is the size table not termintated? */ + nxt_alloc = (nxt_alloc + align - 1) & ((QWORD)0 - align); /* Align partition start */ + sz_part = plst[si++]; /* Get a partition size */ + if (sz_part <= 100) { /* Is the size in percentage? */ + sz_part = sz_pool * sz_part / 100; + sz_part = (sz_part + align - 1) & ((QWORD)0 - align); /* Align partition end (only if in percentage) */ + } + if (nxt_alloc + sz_part > top_bpt) { /* Clip the size at end of the pool */ + sz_part = (nxt_alloc < top_bpt) ? top_bpt - nxt_alloc : 0; + } + } + if (sz_part != 0) { /* Add a partition? */ + ofs = pi * SZ_GPTE % ss; + memcpy(buf + ofs + GPTE_PtGuid, GUID_MS_Basic, 16); /* Set partition GUID (Microsoft Basic Data) */ + rnd = make_rand(rnd, buf + ofs + GPTE_UpGuid, 16); /* Set unique partition GUID */ + st_qword(buf + ofs + GPTE_FstLba, nxt_alloc); /* Set partition start sector */ + st_qword(buf + ofs + GPTE_LstLba, nxt_alloc + sz_part - 1); /* Set partition end sector */ + nxt_alloc += sz_part; /* Next allocatable sector */ + } + if ((pi + 1) * SZ_GPTE % ss == 0) { /* Write the buffer if it is filled up */ + for (i = 0; i < ss; bcc = crc32(bcc, buf[i++])) ; /* Calculate table check sum */ + if (disk_write(drv, buf, 2 + pi * SZ_GPTE / ss, 1) != RES_OK) return FR_DISK_ERR; /* Write to primary table */ + if (disk_write(drv, buf, top_bpt + pi * SZ_GPTE / ss, 1) != RES_OK) return FR_DISK_ERR; /* Write to secondary table */ + } + } while (++pi < GPT_ITEMS); + + /* Create primary GPT header */ + memset(buf, 0, ss); + memcpy(buf + GPTH_Sign, "EFI PART" "\0\0\1\0" "\x5C\0\0", 16); /* Signature, version (1.0) and size (92) */ + st_dword(buf + GPTH_PtBcc, ~bcc); /* Table check sum */ + st_qword(buf + GPTH_CurLba, 1); /* LBA of this header */ + st_qword(buf + GPTH_BakLba, sz_drv - 1); /* LBA of secondary header */ + st_qword(buf + GPTH_FstLba, 2 + sz_ptbl); /* LBA of first allocatable sector */ + st_qword(buf + GPTH_LstLba, top_bpt - 1); /* LBA of last allocatable sector */ + st_dword(buf + GPTH_PteSize, SZ_GPTE); /* Size of a table entry */ + st_dword(buf + GPTH_PtNum, GPT_ITEMS); /* Number of table entries */ + st_dword(buf + GPTH_PtOfs, 2); /* LBA of this table */ + rnd = make_rand(rnd, buf + GPTH_DskGuid, 16); /* Disk GUID */ + for (i = 0, bcc= 0xFFFFFFFF; i < 92; bcc = crc32(bcc, buf[i++])) ; /* Calculate header check sum */ + st_dword(buf + GPTH_Bcc, ~bcc); /* Header check sum */ + if (disk_write(drv, buf, 1, 1) != RES_OK) return FR_DISK_ERR; + + /* Create secondary GPT header */ + st_qword(buf + GPTH_CurLba, sz_drv - 1); /* LBA of this header */ + st_qword(buf + GPTH_BakLba, 1); /* LBA of primary header */ + st_qword(buf + GPTH_PtOfs, top_bpt); /* LBA of this table */ + st_dword(buf + GPTH_Bcc, 0); + for (i = 0, bcc= 0xFFFFFFFF; i < 92; bcc = crc32(bcc, buf[i++])) ; /* Calculate header check sum */ + st_dword(buf + GPTH_Bcc, ~bcc); /* Header check sum */ + if (disk_write(drv, buf, sz_drv - 1, 1) != RES_OK) return FR_DISK_ERR; + + /* Create protective MBR */ + memset(buf, 0, ss); + memcpy(buf + MBR_Table, gpt_mbr, 16); /* Create a GPT partition */ + st_word(buf + BS_55AA, 0xAA55); + if (disk_write(drv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; + + } else +#endif + { /* Create partitions in MBR format */ + sz_drv32 = (DWORD)sz_drv; + n_sc = N_SEC_TRACK; /* Determine drive CHS without any consideration of the drive geometry */ + for (n_hd = 8; n_hd != 0 && sz_drv32 / n_hd / n_sc > 1024; n_hd *= 2) ; + if (n_hd == 0) n_hd = 255; /* Number of heads needs to be <256 */ + + memset(buf, 0, FF_MAX_SS); /* Clear MBR */ + pte = buf + MBR_Table; /* Partition table in the MBR */ + for (i = 0, nxt_alloc32 = n_sc; i < 4 && nxt_alloc32 != 0 && nxt_alloc32 < sz_drv32; i++, nxt_alloc32 += sz_part32) { + sz_part32 = (DWORD)plst[i]; /* Get partition size */ + if (sz_part32 <= 100) sz_part32 = (sz_part32 == 100) ? sz_drv32 : sz_drv32 / 100 * sz_part32; /* Size in percentage? */ + if (nxt_alloc32 + sz_part32 > sz_drv32 || nxt_alloc32 + sz_part32 < nxt_alloc32) sz_part32 = sz_drv32 - nxt_alloc32; /* Clip at drive size */ + if (sz_part32 == 0) break; /* End of table or no sector to allocate? */ + + st_dword(pte + PTE_StLba, nxt_alloc32); /* Start LBA */ + st_dword(pte + PTE_SizLba, sz_part32); /* Number of sectors */ + pte[PTE_System] = sys; /* System type */ + + cy = (UINT)(nxt_alloc32 / n_sc / n_hd); /* Start cylinder */ + hd = (BYTE)(nxt_alloc32 / n_sc % n_hd); /* Start head */ + sc = (BYTE)(nxt_alloc32 % n_sc + 1); /* Start sector */ + pte[PTE_StHead] = hd; + pte[PTE_StSec] = (BYTE)((cy >> 2 & 0xC0) | sc); + pte[PTE_StCyl] = (BYTE)cy; + + cy = (UINT)((nxt_alloc32 + sz_part32 - 1) / n_sc / n_hd); /* End cylinder */ + hd = (BYTE)((nxt_alloc32 + sz_part32 - 1) / n_sc % n_hd); /* End head */ + sc = (BYTE)((nxt_alloc32 + sz_part32 - 1) % n_sc + 1); /* End sector */ + pte[PTE_EdHead] = hd; + pte[PTE_EdSec] = (BYTE)((cy >> 2 & 0xC0) | sc); + pte[PTE_EdCyl] = (BYTE)cy; + + pte += SZ_PTE; /* Next entry */ + } + + st_word(buf + BS_55AA, 0xAA55); /* MBR signature */ + if (disk_write(drv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the MBR */ + } + + return FR_OK; +} + + + +FRESULT f_mkfs ( + const TCHAR* path, /* Logical drive number */ + const MKFS_PARM* opt, /* Format options */ + void* work, /* Pointer to working buffer (null: use len bytes of heap memory) */ + UINT len /* Size of working buffer [byte] */ +) +{ + static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT volume (4Ks unit) */ + static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */ + static const MKFS_PARM defopt = {FM_ANY, 0, 0, 0, 0}; /* Default parameter */ + BYTE fsopt, fsty, sys, pdrv, ipart; + BYTE *buf; + BYTE *pte; + WORD ss; /* Sector size */ + DWORD sz_buf, sz_blk, n_clst, pau, nsect, n, vsn; + LBA_t sz_vol, b_vol, b_fat, b_data; /* Size of volume, Base LBA of volume, fat, data */ + LBA_t sect, lba[2]; + DWORD sz_rsv, sz_fat, sz_dir, sz_au; /* Size of reserved, fat, dir, data, cluster */ + UINT n_fat, n_root, i; /* Index, Number of FATs and Number of roor dir entries */ + int vol; + DSTATUS ds; + FRESULT res; + + + /* Check mounted drive and clear work area */ + vol = get_ldnumber(&path); /* Get target logical drive */ + if (vol < 0) return FR_INVALID_DRIVE; + if (FatFs[vol]) FatFs[vol]->fs_type = 0; /* Clear the fs object if mounted */ + pdrv = LD2PD(vol); /* Hosting physical drive */ + ipart = LD2PT(vol); /* Hosting partition (0:create as new, 1..:existing partition) */ + + /* Initialize the hosting physical drive */ + ds = disk_initialize(pdrv); + if (ds & STA_NOINIT) return FR_NOT_READY; + if (ds & STA_PROTECT) return FR_WRITE_PROTECTED; + + /* Get physical drive parameters (sz_drv, sz_blk and ss) */ + if (!opt) opt = &defopt; /* Use default parameter if it is not given */ + sz_blk = opt->align; + if (sz_blk == 0) disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk); /* Block size from the paramter or lower layer */ + if (sz_blk == 0 || sz_blk > 0x8000 || (sz_blk & (sz_blk - 1))) sz_blk = 1; /* Use default if the block size is invalid */ +#if FF_MAX_SS != FF_MIN_SS + if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR; + if (ss > FF_MAX_SS || ss < FF_MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR; +#else + ss = FF_MAX_SS; +#endif + + /* Options for FAT sub-type and FAT parameters */ + fsopt = opt->fmt & (FM_ANY | FM_SFD); + n_fat = (opt->n_fat >= 1 && opt->n_fat <= 2) ? opt->n_fat : 1; + n_root = (opt->n_root >= 1 && opt->n_root <= 32768 && (opt->n_root % (ss / SZDIRE)) == 0) ? opt->n_root : 512; + sz_au = (opt->au_size <= 0x1000000 && (opt->au_size & (opt->au_size - 1)) == 0) ? opt->au_size : 0; + sz_au /= ss; /* Byte --> Sector */ + + /* Get working buffer */ + sz_buf = len / ss; /* Size of working buffer [sector] */ + if (sz_buf == 0) return FR_NOT_ENOUGH_CORE; + buf = (BYTE*)work; /* Working buffer */ +#if FF_USE_LFN == 3 + if (!buf) buf = ff_memalloc(sz_buf * ss); /* Use heap memory for working buffer */ +#endif + if (!buf) return FR_NOT_ENOUGH_CORE; + + /* Determine where the volume to be located (b_vol, sz_vol) */ + b_vol = sz_vol = 0; + if (FF_MULTI_PARTITION && ipart != 0) { /* Is the volume associated with any specific partition? */ + /* Get partition location from the existing partition table */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load MBR */ + if (ld_word(buf + BS_55AA) != 0xAA55) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if MBR is valid */ +#if FF_LBA64 + if (buf[MBR_Table + PTE_System] == 0xEE) { /* GPT protective MBR? */ + DWORD n_ent, ofs; + QWORD pt_lba; + + /* Get the partition location from GPT */ + if (disk_read(pdrv, buf, 1, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load GPT header sector (next to MBR) */ + if (!test_gpt_header(buf)) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if GPT header is valid */ + n_ent = ld_dword(buf + GPTH_PtNum); /* Number of entries */ + pt_lba = ld_qword(buf + GPTH_PtOfs); /* Table start sector */ + ofs = i = 0; + while (n_ent) { /* Find MS Basic partition with order of ipart */ + if (ofs == 0 && disk_read(pdrv, buf, pt_lba++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Get PT sector */ + if (!memcmp(buf + ofs + GPTE_PtGuid, GUID_MS_Basic, 16) && ++i == ipart) { /* MS basic data partition? */ + b_vol = ld_qword(buf + ofs + GPTE_FstLba); + sz_vol = ld_qword(buf + ofs + GPTE_LstLba) - b_vol + 1; + break; + } + n_ent--; ofs = (ofs + SZ_GPTE) % ss; /* Next entry */ + } + if (n_ent == 0) LEAVE_MKFS(FR_MKFS_ABORTED); /* Partition not found */ + fsopt |= 0x80; /* Partitioning is in GPT */ + } else +#endif + { /* Get the partition location from MBR partition table */ + pte = buf + (MBR_Table + (ipart - 1) * SZ_PTE); + if (ipart > 4 || pte[PTE_System] == 0) LEAVE_MKFS(FR_MKFS_ABORTED); /* No partition? */ + b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */ + sz_vol = ld_dword(pte + PTE_SizLba); /* Get volume size */ + } + } else { /* The volume is associated with a physical drive */ + if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + if (!(fsopt & FM_SFD)) { /* To be partitioned? */ + /* Create a single-partition on the drive in this function */ +#if FF_LBA64 + if (sz_vol >= FF_MIN_GPT) { /* Which partition type to create, MBR or GPT? */ + fsopt |= 0x80; /* Partitioning is in GPT */ + b_vol = GPT_ALIGN / ss; sz_vol -= b_vol + GPT_ITEMS * SZ_GPTE / ss + 1; /* Estimated partition offset and size */ + } else +#endif + { /* Partitioning is in MBR */ + if (sz_vol > N_SEC_TRACK) { + b_vol = N_SEC_TRACK; sz_vol -= b_vol; /* Estimated partition offset and size */ + } + } + } + } + if (sz_vol < 128) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >=128s */ + + /* Now start to create an FAT volume at b_vol and sz_vol */ + + do { /* Pre-determine the FAT type */ + if (FF_FS_EXFAT && (fsopt & FM_EXFAT)) { /* exFAT possible? */ + if ((fsopt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || sz_au > 128) { /* exFAT only, vol >= 64MS or sz_au > 128S ? */ + fsty = FS_EXFAT; break; + } + } +#if FF_LBA64 + if (sz_vol >= 0x100000000) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too large volume for FAT/FAT32 */ +#endif + if (sz_au > 128) sz_au = 128; /* Invalid AU for FAT/FAT32? */ + if (fsopt & FM_FAT32) { /* FAT32 possible? */ + if (!(fsopt & FM_FAT)) { /* no-FAT? */ + fsty = FS_FAT32; break; + } + } + if (!(fsopt & FM_FAT)) LEAVE_MKFS(FR_INVALID_PARAMETER); /* no-FAT? */ + fsty = FS_FAT16; + } while (0); + + vsn = (DWORD)sz_vol + GET_FATTIME(); /* VSN generated from current time and partitiion size */ + +#if FF_FS_EXFAT + if (fsty == FS_EXFAT) { /* Create an exFAT volume */ + DWORD szb_bit, szb_case, sum, nbit, clu, clen[3]; + WCHAR ch, si; + UINT j, st; + + if (sz_vol < 0x1000) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume for exFAT? */ +#if FF_USE_TRIM + lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */ + disk_ioctl(pdrv, CTRL_TRIM, lba); +#endif + /* Determine FAT location, data location and number of clusters */ + if (sz_au == 0) { /* AU auto-selection */ + sz_au = 8; + if (sz_vol >= 0x80000) sz_au = 64; /* >= 512Ks */ + if (sz_vol >= 0x4000000) sz_au = 256; /* >= 64Ms */ + } + b_fat = b_vol + 32; /* FAT start at offset 32 */ + sz_fat = (DWORD)((sz_vol / sz_au + 2) * 4 + ss - 1) / ss; /* Number of FAT sectors */ + b_data = (b_fat + sz_fat + sz_blk - 1) & ~((LBA_t)sz_blk - 1); /* Align data area to the erase block boundary */ + if (b_data - b_vol >= sz_vol / 2) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ + n_clst = (DWORD)((sz_vol - (b_data - b_vol)) / sz_au); /* Number of clusters */ + if (n_clst <16) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too few clusters? */ + if (n_clst > MAX_EXFAT) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters? */ + + szb_bit = (n_clst + 7) / 8; /* Size of allocation bitmap */ + clen[0] = (szb_bit + sz_au * ss - 1) / (sz_au * ss); /* Number of allocation bitmap clusters */ + + /* Create a compressed up-case table */ + sect = b_data + sz_au * clen[0]; /* Table start sector */ + sum = 0; /* Table checksum to be stored in the 82 entry */ + st = 0; si = 0; i = 0; j = 0; szb_case = 0; + do { + switch (st) { + case 0: + ch = (WCHAR)ff_wtoupper(si); /* Get an up-case char */ + if (ch != si) { + si++; break; /* Store the up-case char if exist */ + } + for (j = 1; (WCHAR)(si + j) && (WCHAR)(si + j) == ff_wtoupper((WCHAR)(si + j)); j++) ; /* Get run length of no-case block */ + if (j >= 128) { + ch = 0xFFFF; st = 2; break; /* Compress the no-case block if run is >= 128 chars */ + } + st = 1; /* Do not compress short run */ + /* FALLTHROUGH */ + case 1: + ch = si++; /* Fill the short run */ + if (--j == 0) st = 0; + break; + + default: + ch = (WCHAR)j; si += (WCHAR)j; /* Number of chars to skip */ + st = 0; + } + sum = xsum32(buf[i + 0] = (BYTE)ch, sum); /* Put it into the write buffer */ + sum = xsum32(buf[i + 1] = (BYTE)(ch >> 8), sum); + i += 2; szb_case += 2; + if (si == 0 || i == sz_buf * ss) { /* Write buffered data when buffer full or end of process */ + n = (i + ss - 1) / ss; + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; i = 0; + } + } while (si); + clen[1] = (szb_case + sz_au * ss - 1) / (sz_au * ss); /* Number of up-case table clusters */ + clen[2] = 1; /* Number of root dir clusters */ + + /* Initialize the allocation bitmap */ + sect = b_data; nsect = (szb_bit + ss - 1) / ss; /* Start of bitmap and number of bitmap sectors */ + nbit = clen[0] + clen[1] + clen[2]; /* Number of clusters in-use by system (bitmap, up-case and root-dir) */ + do { + memset(buf, 0, sz_buf * ss); /* Initialize bitmap buffer */ + for (i = 0; nbit != 0 && i / 8 < sz_buf * ss; buf[i / 8] |= 1 << (i % 8), i++, nbit--) ; /* Mark used clusters */ + n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */ + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + + /* Initialize the FAT */ + sect = b_fat; nsect = sz_fat; /* Start of FAT and number of FAT sectors */ + j = nbit = clu = 0; + do { + memset(buf, 0, sz_buf * ss); i = 0; /* Clear work area and reset write offset */ + if (clu == 0) { /* Initialize FAT [0] and FAT[1] */ + st_dword(buf + i, 0xFFFFFFF8); i += 4; clu++; + st_dword(buf + i, 0xFFFFFFFF); i += 4; clu++; + } + do { /* Create chains of bitmap, up-case and root dir */ + while (nbit != 0 && i < sz_buf * ss) { /* Create a chain */ + st_dword(buf + i, (nbit > 1) ? clu + 1 : 0xFFFFFFFF); + i += 4; clu++; nbit--; + } + if (nbit == 0 && j < 3) nbit = clen[j++]; /* Get next chain length */ + } while (nbit != 0 && i < sz_buf * ss); + n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */ + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + + /* Initialize the root directory */ + memset(buf, 0, sz_buf * ss); + buf[SZDIRE * 0 + 0] = ET_VLABEL; /* Volume label entry (no label) */ + buf[SZDIRE * 1 + 0] = ET_BITMAP; /* Bitmap entry */ + st_dword(buf + SZDIRE * 1 + 20, 2); /* cluster */ + st_dword(buf + SZDIRE * 1 + 24, szb_bit); /* size */ + buf[SZDIRE * 2 + 0] = ET_UPCASE; /* Up-case table entry */ + st_dword(buf + SZDIRE * 2 + 4, sum); /* sum */ + st_dword(buf + SZDIRE * 2 + 20, 2 + clen[0]); /* cluster */ + st_dword(buf + SZDIRE * 2 + 24, szb_case); /* size */ + sect = b_data + sz_au * (clen[0] + clen[1]); nsect = sz_au; /* Start of the root directory and number of sectors */ + do { /* Fill root directory sectors */ + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + memset(buf, 0, ss); /* Rest of entries are filled with zero */ + sect += n; nsect -= n; + } while (nsect); + + /* Create two set of the exFAT VBR blocks */ + sect = b_vol; + for (n = 0; n < 2; n++) { + /* Main record (+0) */ + memset(buf, 0, ss); + memcpy(buf + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11); /* Boot jump code (x86), OEM name */ + st_qword(buf + BPB_VolOfsEx, b_vol); /* Volume offset in the physical drive [sector] */ + st_qword(buf + BPB_TotSecEx, sz_vol); /* Volume size [sector] */ + st_dword(buf + BPB_FatOfsEx, (DWORD)(b_fat - b_vol)); /* FAT offset [sector] */ + st_dword(buf + BPB_FatSzEx, sz_fat); /* FAT size [sector] */ + st_dword(buf + BPB_DataOfsEx, (DWORD)(b_data - b_vol)); /* Data offset [sector] */ + st_dword(buf + BPB_NumClusEx, n_clst); /* Number of clusters */ + st_dword(buf + BPB_RootClusEx, 2 + clen[0] + clen[1]); /* Root dir cluster # */ + st_dword(buf + BPB_VolIDEx, vsn); /* VSN */ + st_word(buf + BPB_FSVerEx, 0x100); /* Filesystem version (1.00) */ + for (buf[BPB_BytsPerSecEx] = 0, i = ss; i >>= 1; buf[BPB_BytsPerSecEx]++) ; /* Log2 of sector size [byte] */ + for (buf[BPB_SecPerClusEx] = 0, i = sz_au; i >>= 1; buf[BPB_SecPerClusEx]++) ; /* Log2 of cluster size [sector] */ + buf[BPB_NumFATsEx] = 1; /* Number of FATs */ + buf[BPB_DrvNumEx] = 0x80; /* Drive number (for int13) */ + st_word(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */ + st_word(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */ + for (i = sum = 0; i < ss; i++) { /* VBR checksum */ + if (i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx) sum = xsum32(buf[i], sum); + } + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + /* Extended bootstrap record (+1..+8) */ + memset(buf, 0, ss); + st_word(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */ + for (j = 1; j < 9; j++) { + for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + } + /* OEM/Reserved record (+9..+10) */ + memset(buf, 0, ss); + for ( ; j < 11; j++) { + for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + } + /* Sum record (+11) */ + for (i = 0; i < ss; i += 4) st_dword(buf + i, sum); /* Fill with checksum value */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + } + + } else +#endif /* FF_FS_EXFAT */ + { /* Create an FAT/FAT32 volume */ + do { + pau = sz_au; + /* Pre-determine number of clusters and FAT sub-type */ + if (fsty == FS_FAT32) { /* FAT32 volume */ + if (pau == 0) { /* AU auto-selection */ + n = (DWORD)sz_vol / 0x20000; /* Volume size in unit of 128KS */ + for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ; /* Get from table */ + } + n_clst = (DWORD)sz_vol / pau; /* Number of clusters */ + sz_fat = (n_clst * 4 + 8 + ss - 1) / ss; /* FAT size [sector] */ + sz_rsv = 32; /* Number of reserved sectors */ + sz_dir = 0; /* No static directory */ + if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) LEAVE_MKFS(FR_MKFS_ABORTED); + } else { /* FAT volume */ + if (pau == 0) { /* au auto-selection */ + n = (DWORD)sz_vol / 0x1000; /* Volume size in unit of 4KS */ + for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ; /* Get from table */ + } + n_clst = (DWORD)sz_vol / pau; + if (n_clst > MAX_FAT12) { + n = n_clst * 2 + 4; /* FAT size [byte] */ + } else { + fsty = FS_FAT12; + n = (n_clst * 3 + 1) / 2 + 3; /* FAT size [byte] */ + } + sz_fat = (n + ss - 1) / ss; /* FAT size [sector] */ + sz_rsv = 1; /* Number of reserved sectors */ + sz_dir = (DWORD)n_root * SZDIRE / ss; /* Root dir size [sector] */ + } + b_fat = b_vol + sz_rsv; /* FAT base */ + b_data = b_fat + sz_fat * n_fat + sz_dir; /* Data base */ + + /* Align data area to erase block boundary (for flash memory media) */ + n = (DWORD)(((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data); /* Sectors to next nearest from current data base */ + if (fsty == FS_FAT32) { /* FAT32: Move FAT */ + sz_rsv += n; b_fat += n; + } else { /* FAT: Expand FAT */ + if (n % n_fat) { /* Adjust fractional error if needed */ + n--; sz_rsv++; b_fat++; + } + sz_fat += n / n_fat; + } + + /* Determine number of clusters and final check of validity of the FAT sub-type */ + if (sz_vol < b_data + pau * 16 - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ + n_clst = ((DWORD)sz_vol - sz_rsv - sz_fat * n_fat - sz_dir) / pau; + if (fsty == FS_FAT32) { + if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32? */ + if (sz_au == 0 && (sz_au = pau / 2) != 0) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + } + if (fsty == FS_FAT16) { + if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */ + if (sz_au == 0 && (pau * 2) <= 64) { + sz_au = pau * 2; continue; /* Adjust cluster size and retry */ + } + if ((fsopt & FM_FAT32)) { + fsty = FS_FAT32; continue; /* Switch type to FAT32 and retry */ + } + if (sz_au == 0 && (sz_au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */ + if (sz_au == 0 && (sz_au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + } + if (fsty == FS_FAT12 && n_clst > MAX_FAT12) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters for FAT12 */ + + /* Ok, it is the valid cluster configuration */ + break; + } while (1); + +#if FF_USE_TRIM + lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */ + disk_ioctl(pdrv, CTRL_TRIM, lba); +#endif + /* Create FAT VBR */ + memset(buf, 0, ss); + memcpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11); /* Boot jump code (x86), OEM name */ + st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */ + buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */ + st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */ + buf[BPB_NumFATs] = (BYTE)n_fat; /* Number of FATs */ + st_word(buf + BPB_RootEntCnt, (WORD)((fsty == FS_FAT32) ? 0 : n_root)); /* Number of root directory entries */ + if (sz_vol < 0x10000) { + st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */ + } else { + st_dword(buf + BPB_TotSec32, (DWORD)sz_vol); /* Volume size in 32-bit LBA */ + } + buf[BPB_Media] = 0xF8; /* Media descriptor byte */ + st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */ + st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */ + st_dword(buf + BPB_HiddSec, (DWORD)b_vol); /* Volume offset in the physical drive [sector] */ + if (fsty == FS_FAT32) { + st_dword(buf + BS_VolID32, vsn); /* VSN */ + st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */ + st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */ + st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */ + st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */ + buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */ + buf[BS_BootSig32] = 0x29; /* Extended boot signature */ + memcpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */ + } else { + st_dword(buf + BS_VolID, vsn); /* VSN */ + st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */ + buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */ + buf[BS_BootSig] = 0x29; /* Extended boot signature */ + memcpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */ + } + st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */ + if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the VBR sector */ + + /* Create FSINFO record if needed */ + if (fsty == FS_FAT32) { + disk_write(pdrv, buf, b_vol + 6, 1); /* Write backup VBR (VBR + 6) */ + memset(buf, 0, ss); + st_dword(buf + FSI_LeadSig, 0x41615252); + st_dword(buf + FSI_StrucSig, 0x61417272); + st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */ + st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */ + st_word(buf + BS_55AA, 0xAA55); + disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */ + disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */ + } + + /* Initialize FAT area */ + memset(buf, 0, sz_buf * ss); + sect = b_fat; /* FAT start sector */ + for (i = 0; i < n_fat; i++) { /* Initialize FATs each */ + if (fsty == FS_FAT32) { + st_dword(buf + 0, 0xFFFFFFF8); /* FAT[0] */ + st_dword(buf + 4, 0xFFFFFFFF); /* FAT[1] */ + st_dword(buf + 8, 0x0FFFFFFF); /* FAT[2] (root directory) */ + } else { + st_dword(buf + 0, (fsty == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8); /* FAT[0] and FAT[1] */ + } + nsect = sz_fat; /* Number of FAT sectors */ + do { /* Fill FAT sectors */ + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + memset(buf, 0, ss); /* Rest of FAT all are cleared */ + sect += n; nsect -= n; + } while (nsect); + } + + /* Initialize root directory (fill with zero) */ + nsect = (fsty == FS_FAT32) ? pau : sz_dir; /* Number of root directory sectors */ + do { + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + } + + /* A FAT volume has been created here */ + + /* Determine system ID in the MBR partition table */ + if (FF_FS_EXFAT && fsty == FS_EXFAT) { + sys = 0x07; /* exFAT */ + } else if (fsty == FS_FAT32) { + sys = 0x0C; /* FAT32X */ + } else if (sz_vol >= 0x10000) { + sys = 0x06; /* FAT12/16 (large) */ + } else if (fsty == FS_FAT16) { + sys = 0x04; /* FAT16 */ + } else { + sys = 0x01; /* FAT12 */ + } + + /* Update partition information */ + if (FF_MULTI_PARTITION && ipart != 0) { /* Volume is in the existing partition */ + if (!FF_LBA64 || !(fsopt & 0x80)) { /* Is the partition in MBR? */ + /* Update system ID in the partition table */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Read the MBR */ + buf[MBR_Table + (ipart - 1) * SZ_PTE + PTE_System] = sys; /* Set system ID */ + if (disk_write(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it back to the MBR */ + } + } else { /* Volume as a new single partition */ + if (!(fsopt & FM_SFD)) { /* Create partition table if not in SFD format */ + lba[0] = sz_vol; lba[1] = 0; + res = create_partition(pdrv, lba, sys, buf); + if (res != FR_OK) LEAVE_MKFS(res); + } + } + + if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + + LEAVE_MKFS(FR_OK); +} + + + + +#if FF_MULTI_PARTITION +/*-----------------------------------------------------------------------*/ +/* Create Partition Table on the Physical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_fdisk ( + BYTE pdrv, /* Physical drive number */ + const LBA_t ptbl[], /* Pointer to the size table for each partitions */ + void* work /* Pointer to the working buffer (null: use heap memory) */ +) +{ + BYTE *buf = (BYTE*)work; + DSTATUS stat; + FRESULT res; + + + /* Initialize the physical drive */ + stat = disk_initialize(pdrv); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; + +#if FF_USE_LFN == 3 + if (!buf) buf = ff_memalloc(FF_MAX_SS); /* Use heap memory for working buffer */ +#endif + if (!buf) return FR_NOT_ENOUGH_CORE; + + res = create_partition(pdrv, ptbl, 0x07, buf); /* Create partitions (system ID is temporary setting and determined by f_mkfs) */ + + LEAVE_MKFS(res); +} + +#endif /* FF_MULTI_PARTITION */ +#endif /* !FF_FS_READONLY && FF_USE_MKFS */ + + + + +#if FF_USE_STRFUNC +#if FF_USE_LFN && FF_LFN_UNICODE && (FF_STRF_ENCODE < 0 || FF_STRF_ENCODE > 3) +#error Wrong FF_STRF_ENCODE setting +#endif +/*-----------------------------------------------------------------------*/ +/* Get a String from the File */ +/*-----------------------------------------------------------------------*/ + +TCHAR* f_gets ( + TCHAR* buff, /* Pointer to the buffer to store read string */ + int len, /* Size of string buffer (items) */ + FIL* fp /* Pointer to the file object */ +) +{ + int nc = 0; + TCHAR *p = buff; + BYTE s[4]; + UINT rc; + DWORD dc; +#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE <= 2 + WCHAR wc; +#endif +#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE == 3 + UINT ct; +#endif + +#if FF_USE_LFN && FF_LFN_UNICODE /* With code conversion (Unicode API) */ + /* Make a room for the character and terminator */ + if (FF_LFN_UNICODE == 1) len -= (FF_STRF_ENCODE == 0) ? 1 : 2; + if (FF_LFN_UNICODE == 2) len -= (FF_STRF_ENCODE == 0) ? 3 : 4; + if (FF_LFN_UNICODE == 3) len -= 1; + while (nc < len) { +#if FF_STRF_ENCODE == 0 /* Read a character in ANSI/OEM */ + f_read(fp, s, 1, &rc); /* Get a code unit */ + if (rc != 1) break; /* EOF? */ + wc = s[0]; + if (dbc_1st((BYTE)wc)) { /* DBC 1st byte? */ + f_read(fp, s, 1, &rc); /* Get 2nd byte */ + if (rc != 1 || !dbc_2nd(s[0])) continue; /* Wrong code? */ + wc = wc << 8 | s[0]; + } + dc = ff_oem2uni(wc, CODEPAGE); /* Convert ANSI/OEM into Unicode */ + if (dc == 0) continue; /* Conversion error? */ +#elif FF_STRF_ENCODE == 1 || FF_STRF_ENCODE == 2 /* Read a character in UTF-16LE/BE */ + f_read(fp, s, 2, &rc); /* Get a code unit */ + if (rc != 2) break; /* EOF? */ + dc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1]; + if (IsSurrogateL(dc)) continue; /* Broken surrogate pair? */ + if (IsSurrogateH(dc)) { /* High surrogate? */ + f_read(fp, s, 2, &rc); /* Get low surrogate */ + if (rc != 2) break; /* EOF? */ + wc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1]; + if (!IsSurrogateL(wc)) continue; /* Broken surrogate pair? */ + dc = ((dc & 0x3FF) + 0x40) << 10 | (wc & 0x3FF); /* Merge surrogate pair */ + } +#else /* Read a character in UTF-8 */ + f_read(fp, s, 1, &rc); /* Get a code unit */ + if (rc != 1) break; /* EOF? */ + dc = s[0]; + if (dc >= 0x80) { /* Multi-byte sequence? */ + ct = 0; + if ((dc & 0xE0) == 0xC0) { /* 2-byte sequence? */ + dc &= 0x1F; ct = 1; + } + if ((dc & 0xF0) == 0xE0) { /* 3-byte sequence? */ + dc &= 0x0F; ct = 2; + } + if ((dc & 0xF8) == 0xF0) { /* 4-byte sequence? */ + dc &= 0x07; ct = 3; + } + if (ct == 0) continue; + f_read(fp, s, ct, &rc); /* Get trailing bytes */ + if (rc != ct) break; + rc = 0; + do { /* Merge the byte sequence */ + if ((s[rc] & 0xC0) != 0x80) break; + dc = dc << 6 | (s[rc] & 0x3F); + } while (++rc < ct); + if (rc != ct || dc < 0x80 || IsSurrogate(dc) || dc >= 0x110000) continue; /* Wrong encoding? */ + } +#endif + /* A code point is avaialble in dc to be output */ + + if (FF_USE_STRFUNC == 2 && dc == '\r') continue; /* Strip \r off if needed */ +#if FF_LFN_UNICODE == 1 || FF_LFN_UNICODE == 3 /* Output it in UTF-16/32 encoding */ + if (FF_LFN_UNICODE == 1 && dc >= 0x10000) { /* Out of BMP at UTF-16? */ + *p++ = (TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */ + dc = 0xDC00 | (dc & 0x3FF); /* Make low surrogate */ + } + *p++ = (TCHAR)dc; nc++; + if (dc == '\n') break; /* End of line? */ +#elif FF_LFN_UNICODE == 2 /* Output it in UTF-8 encoding */ + if (dc < 0x80) { /* Single byte? */ + *p++ = (TCHAR)dc; + nc++; + if (dc == '\n') break; /* End of line? */ + } else if (dc < 0x800) { /* 2-byte sequence? */ + *p++ = (TCHAR)(0xC0 | (dc >> 6 & 0x1F)); + *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + nc += 2; + } else if (dc < 0x10000) { /* 3-byte sequence? */ + *p++ = (TCHAR)(0xE0 | (dc >> 12 & 0x0F)); + *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + nc += 3; + } else { /* 4-byte sequence */ + *p++ = (TCHAR)(0xF0 | (dc >> 18 & 0x07)); + *p++ = (TCHAR)(0x80 | (dc >> 12 & 0x3F)); + *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + nc += 4; + } +#endif + } + +#else /* Byte-by-byte read without any conversion (ANSI/OEM API) */ + len -= 1; /* Make a room for the terminator */ + while (nc < len) { + f_read(fp, s, 1, &rc); /* Get a byte */ + if (rc != 1) break; /* EOF? */ + dc = s[0]; + if (FF_USE_STRFUNC == 2 && dc == '\r') continue; + *p++ = (TCHAR)dc; nc++; + if (dc == '\n') break; + } +#endif + + *p = 0; /* Terminate the string */ + return nc ? buff : 0; /* When no data read due to EOF or error, return with error. */ +} + + + + +#if !FF_FS_READONLY +#include +#define SZ_PUTC_BUF 64 +#define SZ_NUM_BUF 32 + +/*-----------------------------------------------------------------------*/ +/* Put a Character to the File (with sub-functions) */ +/*-----------------------------------------------------------------------*/ + +/* Output buffer and work area */ + +typedef struct { + FIL *fp; /* Ptr to the writing file */ + int idx, nchr; /* Write index of buf[] (-1:error), number of encoding units written */ +#if FF_USE_LFN && FF_LFN_UNICODE == 1 + WCHAR hs; +#elif FF_USE_LFN && FF_LFN_UNICODE == 2 + BYTE bs[4]; + UINT wi, ct; +#endif + BYTE buf[SZ_PUTC_BUF]; /* Write buffer */ +} putbuff; + + +/* Buffered file write with code conversion */ + +static void putc_bfd (putbuff* pb, TCHAR c) +{ + UINT n; + int i, nc; +#if FF_USE_LFN && FF_LFN_UNICODE + WCHAR hs, wc; +#if FF_LFN_UNICODE == 2 + DWORD dc; + const TCHAR* tp; +#endif +#endif + + if (FF_USE_STRFUNC == 2 && c == '\n') { /* LF -> CRLF conversion */ + putc_bfd(pb, '\r'); + } + + i = pb->idx; /* Write index of pb->buf[] */ + if (i < 0) return; /* In write error? */ + nc = pb->nchr; /* Write unit counter */ + +#if FF_USE_LFN && FF_LFN_UNICODE +#if FF_LFN_UNICODE == 1 /* UTF-16 input */ + if (IsSurrogateH(c)) { /* Is this a high-surrogate? */ + pb->hs = c; return; /* Save it for next */ + } + hs = pb->hs; pb->hs = 0; + if (hs != 0) { /* Is there a leading high-surrogate? */ + if (!IsSurrogateL(c)) hs = 0; /* Discard high-surrogate if not a surrogate pair */ + } else { + if (IsSurrogateL(c)) return; /* Discard stray low-surrogate */ + } + wc = c; +#elif FF_LFN_UNICODE == 2 /* UTF-8 input */ + for (;;) { + if (pb->ct == 0) { /* Out of multi-byte sequence? */ + pb->bs[pb->wi = 0] = (BYTE)c; /* Save 1st byte */ + if ((BYTE)c < 0x80) break; /* Single byte code? */ + if (((BYTE)c & 0xE0) == 0xC0) pb->ct = 1; /* 2-byte sequence? */ + if (((BYTE)c & 0xF0) == 0xE0) pb->ct = 2; /* 3-byte sequence? */ + if (((BYTE)c & 0xF8) == 0xF0) pb->ct = 3; /* 4-byte sequence? */ + return; /* Wrong leading byte (discard it) */ + } else { /* In the multi-byte sequence */ + if (((BYTE)c & 0xC0) != 0x80) { /* Broken sequence? */ + pb->ct = 0; continue; /* Discard the sequense */ + } + pb->bs[++pb->wi] = (BYTE)c; /* Save the trailing byte */ + if (--pb->ct == 0) break; /* End of the sequence? */ + return; + } + } + tp = (const TCHAR*)pb->bs; + dc = tchar2uni(&tp); /* UTF-8 ==> UTF-16 */ + if (dc == 0xFFFFFFFF) return; /* Wrong code? */ + hs = (WCHAR)(dc >> 16); + wc = (WCHAR)dc; +#elif FF_LFN_UNICODE == 3 /* UTF-32 input */ + if (IsSurrogate(c) || c >= 0x110000) return; /* Discard invalid code */ + if (c >= 0x10000) { /* Out of BMP? */ + hs = (WCHAR)(0xD800 | ((c >> 10) - 0x40)); /* Make high surrogate */ + wc = 0xDC00 | (c & 0x3FF); /* Make low surrogate */ + } else { + hs = 0; + wc = (WCHAR)c; + } +#endif + /* A code point in UTF-16 is available in hs and wc */ + +#if FF_STRF_ENCODE == 1 /* Write a code point in UTF-16LE */ + if (hs != 0) { /* Surrogate pair? */ + st_word(&pb->buf[i], hs); + i += 2; + nc++; + } + st_word(&pb->buf[i], wc); + i += 2; +#elif FF_STRF_ENCODE == 2 /* Write a code point in UTF-16BE */ + if (hs != 0) { /* Surrogate pair? */ + pb->buf[i++] = (BYTE)(hs >> 8); + pb->buf[i++] = (BYTE)hs; + nc++; + } + pb->buf[i++] = (BYTE)(wc >> 8); + pb->buf[i++] = (BYTE)wc; +#elif FF_STRF_ENCODE == 3 /* Write a code point in UTF-8 */ + if (hs != 0) { /* 4-byte sequence? */ + nc += 3; + hs = (hs & 0x3FF) + 0x40; + pb->buf[i++] = (BYTE)(0xF0 | hs >> 8); + pb->buf[i++] = (BYTE)(0x80 | (hs >> 2 & 0x3F)); + pb->buf[i++] = (BYTE)(0x80 | (hs & 3) << 4 | (wc >> 6 & 0x0F)); + pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F)); + } else { + if (wc < 0x80) { /* Single byte? */ + pb->buf[i++] = (BYTE)wc; + } else { + if (wc < 0x800) { /* 2-byte sequence? */ + nc += 1; + pb->buf[i++] = (BYTE)(0xC0 | wc >> 6); + } else { /* 3-byte sequence */ + nc += 2; + pb->buf[i++] = (BYTE)(0xE0 | wc >> 12); + pb->buf[i++] = (BYTE)(0x80 | (wc >> 6 & 0x3F)); + } + pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F)); + } + } +#else /* Write a code point in ANSI/OEM */ + if (hs != 0) return; + wc = ff_uni2oem(wc, CODEPAGE); /* UTF-16 ==> ANSI/OEM */ + if (wc == 0) return; + if (wc >= 0x100) { + pb->buf[i++] = (BYTE)(wc >> 8); nc++; + } + pb->buf[i++] = (BYTE)wc; +#endif + +#else /* ANSI/OEM input (without re-encoding) */ + pb->buf[i++] = (BYTE)c; +#endif + + if (i >= (int)(sizeof pb->buf) - 4) { /* Write buffered characters to the file */ + f_write(pb->fp, pb->buf, (UINT)i, &n); + i = (n == (UINT)i) ? 0 : -1; + } + pb->idx = i; + pb->nchr = nc + 1; +} + + +/* Flush remaining characters in the buffer */ + +static int putc_flush (putbuff* pb) +{ + UINT nw; + + if ( pb->idx >= 0 /* Flush buffered characters to the file */ + && f_write(pb->fp, pb->buf, (UINT)pb->idx, &nw) == FR_OK + && (UINT)pb->idx == nw) return pb->nchr; + return -1; +} + + +/* Initialize write buffer */ + +static void putc_init (putbuff* pb, FIL* fp) +{ + memset(pb, 0, sizeof (putbuff)); + pb->fp = fp; +} + + + +int f_putc ( + TCHAR c, /* A character to be output */ + FIL* fp /* Pointer to the file object */ +) +{ + putbuff pb; + + + putc_init(&pb, fp); + putc_bfd(&pb, c); /* Put the character */ + return putc_flush(&pb); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a String to the File */ +/*-----------------------------------------------------------------------*/ + +int f_puts ( + const TCHAR* str, /* Pointer to the string to be output */ + FIL* fp /* Pointer to the file object */ +) +{ + putbuff pb; + + + putc_init(&pb, fp); + while (*str) putc_bfd(&pb, *str++); /* Put the string */ + return putc_flush(&pb); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a Formatted String to the File (with sub-functions) */ +/*-----------------------------------------------------------------------*/ +#if FF_PRINT_FLOAT && FF_INTDEF == 2 +#include + +static int ilog10 (double n) /* Calculate log10(n) in integer output */ +{ + int rv = 0; + + while (n >= 10) { /* Decimate digit in right shift */ + if (n >= 100000) { + n /= 100000; rv += 5; + } else { + n /= 10; rv++; + } + } + while (n < 1) { /* Decimate digit in left shift */ + if (n < 0.00001) { + n *= 100000; rv -= 5; + } else { + n *= 10; rv--; + } + } + return rv; +} + + +static double i10x (int n) /* Calculate 10^n in integer input */ +{ + double rv = 1; + + while (n > 0) { /* Left shift */ + if (n >= 5) { + rv *= 100000; n -= 5; + } else { + rv *= 10; n--; + } + } + while (n < 0) { /* Right shift */ + if (n <= -5) { + rv /= 100000; n += 5; + } else { + rv /= 10; n++; + } + } + return rv; +} + + +static void ftoa ( + char* buf, /* Buffer to output the floating point string */ + double val, /* Value to output */ + int prec, /* Number of fractional digits */ + TCHAR fmt /* Notation */ +) +{ + int d; + int e = 0, m = 0; + char sign = 0; + double w; + const char *er = 0; + const char ds = FF_PRINT_FLOAT == 2 ? ',' : '.'; + + + if (isnan(val)) { /* Not a number? */ + er = "NaN"; + } else { + if (prec < 0) prec = 6; /* Default precision? (6 fractional digits) */ + if (val < 0) { /* Negative? */ + val = 0 - val; sign = '-'; + } else { + sign = '+'; + } + if (isinf(val)) { /* Infinite? */ + er = "INF"; + } else { + if (fmt == 'f') { /* Decimal notation? */ + val += i10x(0 - prec) / 2; /* Round (nearest) */ + m = ilog10(val); + if (m < 0) m = 0; + if (m + prec + 3 >= SZ_NUM_BUF) er = "OV"; /* Buffer overflow? */ + } else { /* E notation */ + if (val != 0) { /* Not a true zero? */ + val += i10x(ilog10(val) - prec) / 2; /* Round (nearest) */ + e = ilog10(val); + if (e > 99 || prec + 7 >= SZ_NUM_BUF) { /* Buffer overflow or E > +99? */ + er = "OV"; + } else { + if (e < -99) e = -99; + val /= i10x(e); /* Normalize */ + } + } + } + } + if (!er) { /* Not error condition */ + if (sign == '-') *buf++ = sign; /* Add a - if negative value */ + do { /* Put decimal number */ + if (m == -1) *buf++ = ds; /* Insert a decimal separator when get into fractional part */ + w = i10x(m); /* Snip the highest digit d */ + d = (int)(val / w); val -= d * w; + *buf++ = (char)('0' + d); /* Put the digit */ + } while (--m >= -prec); /* Output all digits specified by prec */ + if (fmt != 'f') { /* Put exponent if needed */ + *buf++ = (char)fmt; + if (e < 0) { + e = 0 - e; *buf++ = '-'; + } else { + *buf++ = '+'; + } + *buf++ = (char)('0' + e / 10); + *buf++ = (char)('0' + e % 10); + } + } + } + if (er) { /* Error condition */ + if (sign) *buf++ = sign; /* Add sign if needed */ + do { /* Put error symbol */ + *buf++ = *er++; + } while (*er); + } + *buf = 0; /* Term */ +} +#endif /* FF_PRINT_FLOAT && FF_INTDEF == 2 */ + + + +int f_printf ( + FIL* fp, /* Pointer to the file object */ + const TCHAR* fmt, /* Pointer to the format string */ + ... /* Optional arguments... */ +) +{ + va_list arp; + putbuff pb; + UINT i, j, w, f, r; + int prec; +#if FF_PRINT_LLI && FF_INTDEF == 2 + QWORD v; +#else + DWORD v; +#endif + TCHAR *tp; + TCHAR tc, pad; + TCHAR nul = 0; + char d, str[SZ_NUM_BUF]; + + + putc_init(&pb, fp); + + va_start(arp, fmt); + + for (;;) { + tc = *fmt++; + if (tc == 0) break; /* End of format string */ + if (tc != '%') { /* Not an escape character (pass-through) */ + putc_bfd(&pb, tc); + continue; + } + f = w = 0; pad = ' '; prec = -1; /* Initialize parms */ + tc = *fmt++; + if (tc == '0') { /* Flag: '0' padded */ + pad = '0'; tc = *fmt++; + } else if (tc == '-') { /* Flag: Left aligned */ + f = 2; tc = *fmt++; + } + if (tc == '*') { /* Minimum width from an argument */ + w = va_arg(arp, int); + tc = *fmt++; + } else { + while (IsDigit(tc)) { /* Minimum width */ + w = w * 10 + tc - '0'; + tc = *fmt++; + } + } + if (tc == '.') { /* Precision */ + tc = *fmt++; + if (tc == '*') { /* Precision from an argument */ + prec = va_arg(arp, int); + tc = *fmt++; + } else { + prec = 0; + while (IsDigit(tc)) { /* Precision */ + prec = prec * 10 + tc - '0'; + tc = *fmt++; + } + } + } + if (tc == 'l') { /* Size: long int */ + f |= 4; tc = *fmt++; +#if FF_PRINT_LLI && FF_INTDEF == 2 + if (tc == 'l') { /* Size: long long int */ + f |= 8; tc = *fmt++; + } +#endif + } + if (tc == 0) break; /* End of format string */ + switch (tc) { /* Atgument type is... */ + case 'b': /* Unsigned binary */ + r = 2; break; + + case 'o': /* Unsigned octal */ + r = 8; break; + + case 'd': /* Signed decimal */ + case 'u': /* Unsigned decimal */ + r = 10; break; + + case 'x': /* Unsigned hexadecimal (lower case) */ + case 'X': /* Unsigned hexadecimal (upper case) */ + r = 16; break; + + case 'c': /* Character */ + putc_bfd(&pb, (TCHAR)va_arg(arp, int)); + continue; + + case 's': /* String */ + tp = va_arg(arp, TCHAR*); /* Get a pointer argument */ + if (!tp) tp = &nul; /* Null ptr generates a null string */ + for (j = 0; tp[j]; j++) ; /* j = tcslen(tp) */ + if (prec >= 0 && j > (UINT)prec) j = prec; /* Limited length of string body */ + for ( ; !(f & 2) && j < w; j++) putc_bfd(&pb, pad); /* Left pads */ + while (*tp && prec--) putc_bfd(&pb, *tp++); /* Body */ + while (j++ < w) putc_bfd(&pb, ' '); /* Right pads */ + continue; +#if FF_PRINT_FLOAT && FF_INTDEF == 2 + case 'f': /* Floating point (decimal) */ + case 'e': /* Floating point (e) */ + case 'E': /* Floating point (E) */ + ftoa(str, va_arg(arp, double), prec, tc); /* Make a floating point string */ + for (j = strlen(str); !(f & 2) && j < w; j++) putc_bfd(&pb, pad); /* Left pads */ + for (i = 0; str[i]; putc_bfd(&pb, str[i++])) ; /* Body */ + while (j++ < w) putc_bfd(&pb, ' '); /* Right pads */ + continue; +#endif + default: /* Unknown type (pass-through) */ + putc_bfd(&pb, tc); continue; + } + + /* Get an integer argument and put it in numeral */ +#if FF_PRINT_LLI && FF_INTDEF == 2 + if (f & 8) { /* long long argument? */ + v = (QWORD)va_arg(arp, long long); + } else if (f & 4) { /* long argument? */ + v = (tc == 'd') ? (QWORD)(long long)va_arg(arp, long) : (QWORD)va_arg(arp, unsigned long); + } else { /* int/short/char argument */ + v = (tc == 'd') ? (QWORD)(long long)va_arg(arp, int) : (QWORD)va_arg(arp, unsigned int); + } + if (tc == 'd' && (v & 0x8000000000000000)) { /* Negative value? */ + v = 0 - v; f |= 1; + } +#else + if (f & 4) { /* long argument? */ + v = (DWORD)va_arg(arp, long); + } else { /* int/short/char argument */ + v = (tc == 'd') ? (DWORD)(long)va_arg(arp, int) : (DWORD)va_arg(arp, unsigned int); + } + if (tc == 'd' && (v & 0x80000000)) { /* Negative value? */ + v = 0 - v; f |= 1; + } +#endif + i = 0; + do { /* Make an integer number string */ + d = (char)(v % r); v /= r; + if (d > 9) d += (tc == 'x') ? 0x27 : 0x07; + str[i++] = d + '0'; + } while (v && i < SZ_NUM_BUF); + if (f & 1) str[i++] = '-'; /* Sign */ + /* Write it */ + for (j = i; !(f & 2) && j < w; j++) { /* Left pads */ + putc_bfd(&pb, pad); + } + do { /* Body */ + putc_bfd(&pb, (TCHAR)str[--i]); + } while (i); + while (j++ < w) { /* Right pads */ + putc_bfd(&pb, ' '); + } + } + + va_end(arp); + + return putc_flush(&pb); +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_USE_STRFUNC */ + + + +#if FF_CODE_PAGE == 0 +/*-----------------------------------------------------------------------*/ +/* Set Active Codepage for the Path Name */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setcp ( + WORD cp /* Value to be set as active code page */ +) +{ + static const WORD validcp[22] = { 437, 720, 737, 771, 775, 850, 852, 855, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950, 0}; + static const BYTE *const tables[22] = {Ct437, Ct720, Ct737, Ct771, Ct775, Ct850, Ct852, Ct855, Ct857, Ct860, Ct861, Ct862, Ct863, Ct864, Ct865, Ct866, Ct869, Dc932, Dc936, Dc949, Dc950, 0}; + UINT i; + + + for (i = 0; validcp[i] != 0 && validcp[i] != cp; i++) ; /* Find the code page */ + if (validcp[i] != cp) return FR_INVALID_PARAMETER; /* Not found? */ + + CodePage = cp; + if (cp >= 900) { /* DBCS */ + ExCvt = 0; + DbcTbl = tables[i]; + } else { /* SBCS */ + ExCvt = tables[i]; + DbcTbl = 0; + } + return FR_OK; +} +#endif /* FF_CODE_PAGE == 0 */ + diff --git a/Lib/FatFs/ff.h b/Lib/FatFs/ff.h new file mode 100644 index 0000000..e1c7e84 --- /dev/null +++ b/Lib/FatFs/ff.h @@ -0,0 +1,429 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem module R0.15 / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2022, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: +/ +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/ +/----------------------------------------------------------------------------*/ + + +#ifndef FF_DEFINED +#define FF_DEFINED 80286 /* Revision ID */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ffconf.h" /* FatFs configuration options */ + +#if FF_DEFINED != FFCONF_DEF +#error Wrong configuration file (ffconf.h). +#endif + + +/* Integer types used for FatFs API */ + +#if defined(_WIN32) /* Windows VC++ (for development only) */ +#define FF_INTDEF 2 +#include +typedef unsigned __int64 QWORD; +#include +#define isnan(v) _isnan(v) +#define isinf(v) (!_finite(v)) + +#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ +#define FF_INTDEF 2 +#include +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef uint16_t WORD; /* 16-bit unsigned integer */ +typedef uint32_t DWORD; /* 32-bit unsigned integer */ +typedef uint64_t QWORD; /* 64-bit unsigned integer */ +typedef WORD WCHAR; /* UTF-16 character type */ + +#else /* Earlier than C99 */ +#define FF_INTDEF 1 +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef unsigned short WORD; /* 16-bit unsigned integer */ +typedef unsigned long DWORD; /* 32-bit unsigned integer */ +typedef WORD WCHAR; /* UTF-16 character type */ +#endif + + +/* Type of file size and LBA variables */ + +#if FF_FS_EXFAT +#if FF_INTDEF != 2 +#error exFAT feature wants C99 or later +#endif +typedef QWORD FSIZE_t; +#if FF_LBA64 +typedef QWORD LBA_t; +#else +typedef DWORD LBA_t; +#endif +#else +#if FF_LBA64 +#error exFAT needs to be enabled when enable 64-bit LBA +#endif +typedef DWORD FSIZE_t; +typedef DWORD LBA_t; +#endif + + + +/* Type of path name strings on FatFs API (TCHAR) */ + +#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ +typedef char TCHAR; +#define _T(x) u8 ## x +#define _TEXT(x) u8 ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ +typedef DWORD TCHAR; +#define _T(x) U ## x +#define _TEXT(x) U ## x +#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) +#error Wrong FF_LFN_UNICODE setting +#else /* ANSI/OEM code in SBCS/DBCS */ +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif + + + +/* Definitions of volume management */ + +#if FF_MULTI_PARTITION /* Multiple partition configuration */ +typedef struct { + BYTE pd; /* Physical drive number */ + BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */ +} PARTITION; +extern PARTITION VolToPart[]; /* Volume - Partition mapping table */ +#endif + +#if FF_STR_VOLUME_ID +#ifndef FF_VOLUME_STRS +extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ +#endif +#endif + + + +/* Filesystem object structure (FATFS) */ + +typedef struct { + BYTE fs_type; /* Filesystem type (0:not mounted) */ + BYTE pdrv; /* Volume hosting physical drive */ + BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */ + BYTE n_fats; /* Number of FATs (1 or 2) */ + BYTE wflag; /* win[] status (b0:dirty) */ + BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */ + WORD id; /* Volume mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ + WORD csize; /* Cluster size [sectors] */ +#if FF_MAX_SS != FF_MIN_SS + WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ +#endif +#if FF_USE_LFN + WCHAR* lfnbuf; /* LFN working buffer */ +#endif +#if FF_FS_EXFAT + BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */ +#endif +#if !FF_FS_READONLY + DWORD last_clst; /* Last allocated cluster */ + DWORD free_clst; /* Number of free clusters */ +#endif +#if FF_FS_RPATH + DWORD cdir; /* Current directory start cluster (0:root) */ +#if FF_FS_EXFAT + DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */ + DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */ + DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */ +#endif +#endif + DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */ + DWORD fsize; /* Number of sectors per FAT */ + LBA_t volbase; /* Volume base sector */ + LBA_t fatbase; /* FAT base sector */ + LBA_t dirbase; /* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */ + LBA_t database; /* Data base sector */ +#if FF_FS_EXFAT + LBA_t bitbase; /* Allocation bitmap base sector */ +#endif + LBA_t winsect; /* Current sector appearing in the win[] */ + BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +} FATFS; + + + +/* Object ID and allocation information (FFOBJID) */ + +typedef struct { + FATFS* fs; /* Pointer to the hosting volume of this object */ + WORD id; /* Hosting volume's mount ID */ + BYTE attr; /* Object attribute */ + BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ + DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ + FSIZE_t objsize; /* Object size (valid when sclust != 0) */ +#if FF_FS_EXFAT + DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */ + DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */ + DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */ + DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */ + DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */ +#endif +#if FF_FS_LOCK + UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ +#endif +} FFOBJID; + + + +/* File object structure (FIL) */ + +typedef struct { + FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */ + BYTE flag; /* File status flags */ + BYTE err; /* Abort flag (error code) */ + FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */ + DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */ + LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */ +#if !FF_FS_READONLY + LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */ + BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */ +#endif +#if FF_USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ +#endif +#if !FF_FS_TINY + BYTE buf[FF_MAX_SS]; /* File private data read/write window */ +#endif +} FIL; + + + +/* Directory object structure (DIR) */ + +typedef struct { + FFOBJID obj; /* Object identifier */ + DWORD dptr; /* Current read/write offset */ + DWORD clust; /* Current cluster */ + LBA_t sect; /* Current sector (0:Read operation has terminated) */ + BYTE* dir; /* Pointer to the directory item in the win[] */ + BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ +#if FF_USE_LFN + DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ +#endif +#if FF_USE_FIND + const TCHAR* pat; /* Pointer to the name matching pattern */ +#endif +} DIR; + + + +/* File information structure (FILINFO) */ + +typedef struct { + FSIZE_t fsize; /* File size */ + WORD fdate; /* Modified date */ + WORD ftime; /* Modified time */ + BYTE fattrib; /* File attribute */ +#if FF_USE_LFN + TCHAR altname[FF_SFN_BUF + 1];/* Alternative file name */ + TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ +#else + TCHAR fname[12 + 1]; /* File name */ +#endif +} FILINFO; + + + +/* Format parameter structure (MKFS_PARM) */ + +typedef struct { + BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */ + BYTE n_fat; /* Number of FATs */ + UINT align; /* Data area alignment (sector) */ + UINT n_root; /* Number of root directory entries */ + DWORD au_size; /* Cluster size (byte) */ +} MKFS_PARM; + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, /* (0) Succeeded */ + FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ + FR_INT_ERR, /* (2) Assertion failed */ + FR_NOT_READY, /* (3) The physical drive cannot work */ + FR_NO_FILE, /* (4) Could not find the file */ + FR_NO_PATH, /* (5) Could not find the path */ + FR_INVALID_NAME, /* (6) The path name format is invalid */ + FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ + FR_EXIST, /* (8) Access denied due to prohibited access */ + FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ + FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ + FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ + FR_NOT_ENABLED, /* (12) The volume has no work area */ + FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ + FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ + FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ + FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ + FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ + FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ +} FRESULT; + + + + +/*--------------------------------------------------------------*/ +/* FatFs Module Application Interface */ +/*--------------------------------------------------------------*/ + +FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ +FRESULT f_close (FIL* fp); /* Close an open file object */ +FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ +FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ +FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ +FRESULT f_truncate (FIL* fp); /* Truncate the file */ +FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ +FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_closedir (DIR* dp); /* Close an open directory */ +FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */ +FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ +FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ +FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */ +FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ +FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */ +FRESULT f_chdir (const TCHAR* path); /* Change current directory */ +FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ +FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */ +FRESULT f_setlabel (const TCHAR* label); /* Set volume label */ +FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ +FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */ +FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ +FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ +FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */ +FRESULT f_setcp (WORD cp); /* Set current code page */ +int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */ +int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */ +int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ +TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */ + +/* Some API fucntions are implemented as macro */ + +#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) +#define f_error(fp) ((fp)->err) +#define f_tell(fp) ((fp)->fptr) +#define f_size(fp) ((fp)->obj.objsize) +#define f_rewind(fp) f_lseek((fp), 0) +#define f_rewinddir(dp) f_readdir((dp), 0) +#define f_rmdir(path) f_unlink(path) +#define f_unmount(path) f_mount(0, path, 0) + + + + +/*--------------------------------------------------------------*/ +/* Additional Functions */ +/*--------------------------------------------------------------*/ + +/* RTC function (provided by user) */ +#if !FF_FS_READONLY && !FF_FS_NORTC +DWORD get_fattime (void); /* Get current time */ +#endif + + +/* LFN support functions (defined in ffunicode.c) */ + +#if FF_USE_LFN >= 1 +WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ +WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ +DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ +#endif + + +/* O/S dependent functions (samples available in ffsystem.c) */ + +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ +void* ff_memalloc (UINT msize); /* Allocate memory block */ +void ff_memfree (void* mblock); /* Free memory block */ +#endif +#if FF_FS_REENTRANT /* Sync functions */ +int ff_mutex_create (int vol); /* Create a sync object */ +void ff_mutex_delete (int vol); /* Delete a sync object */ +int ff_mutex_take (int vol); /* Lock sync object */ +void ff_mutex_give (int vol); /* Unlock sync object */ +#endif + + + + +/*--------------------------------------------------------------*/ +/* Flags and Offset Address */ +/*--------------------------------------------------------------*/ + +/* File access mode and open method flags (3rd argument of f_open) */ +#define FA_READ 0x01 +#define FA_WRITE 0x02 +#define FA_OPEN_EXISTING 0x00 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA_OPEN_APPEND 0x30 + +/* Fast seek controls (2nd argument of f_lseek) */ +#define CREATE_LINKMAP ((FSIZE_t)0 - 1) + +/* Format options (2nd argument of f_mkfs) */ +#define FM_FAT 0x01 +#define FM_FAT32 0x02 +#define FM_EXFAT 0x04 +#define FM_ANY 0x07 +#define FM_SFD 0x08 + +/* Filesystem type (FATFS.fs_type) */ +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 +#define FS_EXFAT 4 + +/* File attribute bits for directory entry (FILINFO.fattrib) */ +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ + + +#ifdef __cplusplus +} +#endif + +#endif /* FF_DEFINED */ diff --git a/Lib/FatFs/ffconf.h b/Lib/FatFs/ffconf.h new file mode 100644 index 0000000..1f61d51 --- /dev/null +++ b/Lib/FatFs/ffconf.h @@ -0,0 +1,41 @@ +#define FFCONF_DEF 80286 + +#define FF_FS_READONLY 0 +#define FF_FS_MINIMIZE 0 +#define FF_USE_FIND 0 +#define FF_USE_MKFS 1 +#define FF_USE_FASTSEEK 0 +#define FF_USE_EXPAND 0 +#define FF_USE_CHMOD 0 +#define FF_USE_LABEL 0 +#define FF_USE_FORWARD 0 +#define FF_USE_STRFUNC 0 +#define FF_PRINT_LLI 1 +#define FF_PRINT_FLOAT 1 +#define FF_STRF_ENCODE 3 +#define FF_CODE_PAGE 437 +#define FF_USE_LFN 0 +#define FF_MAX_LFN 255 +#define FF_LFN_UNICODE 0 +#define FF_LFN_BUF 255 +#define FF_SFN_BUF 12 +#define FF_FS_RPATH 0 +#define FF_VOLUMES 1 +#define FF_STR_VOLUME_ID 0 +#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" +#define FF_MULTI_PARTITION 0 +#define FF_MIN_SS 512 +#define FF_MAX_SS 512 +#define FF_LBA64 0 +#define FF_MIN_GPT 0x10000000 +#define FF_USE_TRIM 0 +#define FF_FS_TINY 0 +#define FF_FS_EXFAT 0 +#define FF_FS_NORTC 1 +#define FF_NORTC_MON 1 +#define FF_NORTC_MDAY 1 +#define FF_NORTC_YEAR 2026 +#define FF_FS_NOFSINFO 0 +#define FF_FS_LOCK 0 +#define FF_FS_REENTRANT 0 +#define FF_FS_TIMEOUT 1000 diff --git a/Lib/dhara/bytes.h b/Lib/dhara/bytes.h new file mode 100644 index 0000000..50bbdd7 --- /dev/null +++ b/Lib/dhara/bytes.h @@ -0,0 +1,50 @@ +/* Dhara - NAND flash management layer + * Copyright (C) 2013 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DHARA_BYTES_H_ +#define DHARA_BYTES_H_ + +#include + +static inline uint16_t dhara_r16(const uint8_t *data) +{ + return ((uint16_t)data[0]) | + (((uint16_t)data[1]) << 8); +} + +static inline void dhara_w16(uint8_t *data, uint16_t v) +{ + data[0] = v; + data[1] = v >> 8; +} + +static inline uint32_t dhara_r32(const uint8_t *data) +{ + return ((uint32_t)data[0]) | + (((uint32_t)data[1]) << 8) | + (((uint32_t)data[2]) << 16) | + (((uint32_t)data[3]) << 24); +} + +static inline void dhara_w32(uint8_t *data, uint32_t v) +{ + data[0] = v; + data[1] = v >> 8; + data[2] = v >> 16; + data[3] = v >> 24; +} + +#endif diff --git a/Lib/dhara/error.h b/Lib/dhara/error.h new file mode 100644 index 0000000..f2d9a8a --- /dev/null +++ b/Lib/dhara/error.h @@ -0,0 +1,45 @@ +/* Dhara - NAND flash management layer + * Copyright (C) 2013 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DHARA_ERROR_H_ +#define DHARA_ERROR_H_ + +typedef enum { + DHARA_E_NONE = 0, + DHARA_E_BAD_BLOCK, + DHARA_E_ECC, + DHARA_E_TOO_BAD, + DHARA_E_RECOVER, + DHARA_E_JOURNAL_FULL, + DHARA_E_NOT_FOUND, + DHARA_E_MAP_FULL, + DHARA_E_CORRUPT_MAP, + DHARA_E_MAX +} dhara_error_t; + +/* Produce a human-readable error message. This function is kept in a + * separate compilation unit and can be omitted to reduce binary size. + */ +const char *dhara_strerror(dhara_error_t err); + +/* Save an error */ +static inline void dhara_set_error(dhara_error_t *err, dhara_error_t v) +{ + if (err) + *err = v; +} + +#endif diff --git a/Lib/dhara/journal.c b/Lib/dhara/journal.c new file mode 100644 index 0000000..87743c3 --- /dev/null +++ b/Lib/dhara/journal.c @@ -0,0 +1,884 @@ +/* Dhara - NAND flash management layer + * Copyright (C) 2013 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "journal.h" +#include "bytes.h" + +/************************************************************************ + * Metapage binary format + */ + +/* Does the page buffer contain a valid checkpoint page? */ +static inline int hdr_has_magic(const uint8_t *buf) +{ + return (buf[0] == 'D') && + (buf[1] == 'h') && + (buf[2] == 'a'); +} + +static inline void hdr_put_magic(uint8_t *buf) +{ + buf[0] = 'D'; + buf[1] = 'h'; + buf[2] = 'a'; +} + +/* What epoch is this page? */ +static inline uint8_t hdr_get_epoch(const uint8_t *buf) +{ + return buf[3]; +} + +static inline void hdr_set_epoch(uint8_t *buf, uint8_t e) +{ + buf[3] = e; +} + +static inline dhara_page_t hdr_get_tail(const uint8_t *buf) +{ + return dhara_r32(buf + 4); +} + +static inline void hdr_set_tail(uint8_t *buf, dhara_page_t tail) +{ + dhara_w32(buf + 4, tail); +} + +static inline dhara_page_t hdr_get_bb_current(const uint8_t *buf) +{ + return dhara_r32(buf + 8); +} + +static inline void hdr_set_bb_current(uint8_t *buf, dhara_page_t count) +{ + dhara_w32(buf + 8, count); +} + +static inline dhara_page_t hdr_get_bb_last(const uint8_t *buf) +{ + return dhara_r32(buf + 12); +} + +static inline void hdr_set_bb_last(uint8_t *buf, dhara_page_t count) +{ + dhara_w32(buf + 12, count); +} + +/* Clear user metadata */ +static inline void hdr_clear_user(uint8_t *buf, uint8_t log2_page_size) +{ + memset(buf + DHARA_HEADER_SIZE + DHARA_COOKIE_SIZE, 0xff, + (1 << log2_page_size) - DHARA_HEADER_SIZE - DHARA_COOKIE_SIZE); +} + +/* Obtain pointers to user data */ +static inline size_t hdr_user_offset(uint8_t which) +{ + return DHARA_HEADER_SIZE + DHARA_COOKIE_SIZE + + which * DHARA_META_SIZE; +} + +/************************************************************************ + * Page geometry helpers + */ + +/* Is this page index aligned to N bits? */ +static inline int is_aligned(dhara_page_t p, int n) +{ + return !(p & ((1 << n) - 1)); +} + +/* Are these two pages from the same alignment group? */ +static inline int align_eq(dhara_page_t a, dhara_page_t b, + int n) +{ + return !((a ^ b) >> n); +} + +/* What is the successor of this block? */ +static dhara_block_t next_block(const struct dhara_nand *n, dhara_block_t blk) +{ + blk++; + if (blk >= n->num_blocks) + blk = 0; + + return blk; +} + +static dhara_page_t next_upage(const struct dhara_journal *j, + dhara_page_t p) +{ + p++; + if (is_aligned(p + 1, j->log2_ppc)) + p++; + + if (p >= (j->nand->num_blocks << j->nand->log2_ppb)) + p = 0; + + return p; +} + +/* Calculate a checkpoint period: the largest value of ppc such that + * (2**ppc - 1) metadata blocks can fit on a page with one journal + * header. + */ +static int choose_ppc(int log2_page_size, int max) +{ + const int max_meta = (1 << log2_page_size) - + DHARA_HEADER_SIZE - DHARA_COOKIE_SIZE; + int total_meta = DHARA_META_SIZE; + int ppc = 1; + + while (ppc < max) { + total_meta <<= 1; + total_meta += DHARA_META_SIZE; + + if (total_meta > max_meta) + break; + + ppc++; + } + + return ppc; +} + +/************************************************************************ + * Journal setup/resume + */ + +/* Clear recovery status */ +static void clear_recovery(struct dhara_journal *j) +{ + j->recover_next = DHARA_PAGE_NONE; + j->recover_root = DHARA_PAGE_NONE; + j->recover_meta = DHARA_PAGE_NONE; + j->flags &= ~(DHARA_JOURNAL_F_BAD_META | + DHARA_JOURNAL_F_RECOVERY | + DHARA_JOURNAL_F_ENUM_DONE); +} + +/* Set up an empty journal */ +static void reset_journal(struct dhara_journal *j) +{ + /* We don't yet have a bad block estimate, so make a + * conservative guess. + */ + j->epoch = 0; + j->bb_last = j->nand->num_blocks >> 6; + j->bb_current = 0; + + j->flags = 0; + + /* Empty journal */ + j->head = 0; + j->tail = 0; + j->tail_sync = 0; + j->root = DHARA_PAGE_NONE; + + /* No recovery required */ + clear_recovery(j); + + /* Empty metadata buffer */ + memset(j->page_buf, 0xff, 1 << j->nand->log2_page_size); +} + +static void roll_stats(struct dhara_journal *j) +{ + j->bb_last = j->bb_current; + j->bb_current = 0; + j->epoch++; +} + +void dhara_journal_init(struct dhara_journal *j, + const struct dhara_nand *n, + uint8_t *page_buf) +{ + /* Set fixed parameters */ + j->nand = n; + j->page_buf = page_buf; + j->log2_ppc = choose_ppc(n->log2_page_size, n->log2_ppb); + + reset_journal(j); +} + +/* Find the first checkpoint-containing block. If a block contains any + * checkpoints at all, then it must contain one in the first checkpoint + * location -- otherwise, we would have considered the block eraseable. + */ +static int find_checkblock(struct dhara_journal *j, + dhara_block_t blk, dhara_block_t *where, + dhara_error_t *err) +{ + int i; + + for (i = 0; (blk < j->nand->num_blocks) && + (i < DHARA_MAX_RETRIES); i++) { + const dhara_page_t p = + (blk << j->nand->log2_ppb) | + ((1 << j->log2_ppc) - 1); + + if (!(dhara_nand_is_bad(j->nand, blk) || + dhara_nand_read(j->nand, p, + 0, 1 << j->nand->log2_page_size, + j->page_buf, err)) && + hdr_has_magic(j->page_buf)) { + *where = blk; + return 0; + } + + blk++; + } + + dhara_set_error(err, DHARA_E_TOO_BAD); + return -1; +} + +static dhara_block_t find_last_checkblock(struct dhara_journal *j, + dhara_block_t first) +{ + dhara_block_t low = first; + dhara_block_t high = j->nand->num_blocks - 1; + + while (low <= high) { + const dhara_block_t mid = (low + high) >> 1; + dhara_block_t found; + + if ((find_checkblock(j, mid, &found, NULL) < 0) || + (hdr_get_epoch(j->page_buf) != j->epoch)) { + if (!mid) + return first; + + high = mid - 1; + } else { + dhara_block_t nf; + + if (((found + 1) >= j->nand->num_blocks) || + (find_checkblock(j, found + 1, + &nf, NULL) < 0) || + (hdr_get_epoch(j->page_buf) != j->epoch)) + return found; + + low = nf; + } + } + + return first; +} + +/* Test whether a checkpoint group is in a state fit for reprogramming, + * but allow for the fact that is_free() might not have any way of + * distinguishing between an unprogrammed page, and a page programmed + * with all-0xff bytes (but if so, it must be ok to reprogram such a + * page). + * + * We used to test for an unprogrammed checkpoint group by checking to + * see if the first user-page had been programmed since last erase (by + * testing only the first page with is_free). This works if is_free is + * precise, because the pages are written in order. + * + * If is_free is imprecise, we need to check all pages in the group. + * That also works, because the final page in a checkpoint group is + * guaranteed to contain non-0xff bytes. Therefore, we return 1 only if + * the group is truly unprogrammed, or if it was partially programmed + * with some all-0xff user pages (which changes nothing for us). + */ +static int cp_free(struct dhara_journal *j, dhara_page_t first_user) +{ + const int count = 1 << j->log2_ppc; + int i; + + for (i = 0; i < count; i++) + if (!dhara_nand_is_free(j->nand, first_user + i)) + return 0; + + return 1; +} + +static dhara_page_t find_last_group(struct dhara_journal *j, + dhara_block_t blk) +{ + const int num_groups = 1 << (j->nand->log2_ppb - j->log2_ppc); + int low = 0; + int high = num_groups - 1; + + /* If a checkpoint group is completely unprogrammed, everything + * following it will be completely unprogrammed also. + * + * Therefore, binary search checkpoint groups until we find the + * last programmed one. + */ + while (low <= high) { + int mid = (low + high) >> 1; + const dhara_page_t p = (mid << j->log2_ppc) | + (blk << j->nand->log2_ppb); + + if (cp_free(j, p)) { + high = mid - 1; + } else if (((mid + 1) >= num_groups) || + cp_free(j, p + (1 << j->log2_ppc))) { + return p; + } else { + low = mid + 1; + } + } + + return blk << j->nand->log2_ppb; +} + +static int find_root(struct dhara_journal *j, dhara_page_t start, + dhara_error_t *err) +{ + const dhara_block_t blk = start >> j->nand->log2_ppb; + int i = (start & ((1 << j->nand->log2_ppb) - 1)) >> j->log2_ppc; + + while (i >= 0) { + const dhara_page_t p = (blk << j->nand->log2_ppb) + + ((i + 1) << j->log2_ppc) - 1; + + if (!dhara_nand_read(j->nand, p, + 0, 1 << j->nand->log2_page_size, + j->page_buf, err) && + (hdr_has_magic(j->page_buf)) && + (hdr_get_epoch(j->page_buf) == j->epoch)) { + j->root = p - 1; + return 0; + } + + i--; + } + + dhara_set_error(err, DHARA_E_TOO_BAD); + return -1; +} + +static int find_head(struct dhara_journal *j, dhara_page_t start, + dhara_error_t *err) +{ + j->head = next_upage(j, start); + if (!j->head) + roll_stats(j); + + /* Starting from the last good checkpoint, find either: + * + * (a) the next free user-page in the same block + * (b) or, the first page of the next block + * + * The block we end up on might be bad, but that's ok -- we'll + * skip it when we go to prepare the next write. + */ + for (;;) { + /* How many free pages trail this checkpoint group? */ + const unsigned int ppc = 1 << j->log2_ppc; + unsigned int n = 0; + dhara_page_t first = j->head & ~(dhara_page_t)(ppc - 1); + + while (n < ppc && + dhara_nand_is_free(j->nand, first + ppc - n - 1)) + n++; + + /* If we have some, then we've found our next free + * userpage. + */ + if (n > 1) { + j->head = first + ppc - n; + break; + } + + /* Skip to the next checkpoint group */ + j->head = first + ppc; + if (j->head >= (j->nand->num_blocks << j->nand->log2_ppb)) { + j->head = 0; + roll_stats(j); + } + + /* If we hit the end of the block, we're done */ + if (is_aligned(j->head, j->nand->log2_ppb)) { + /* Make sure we don't chase over the tail */ + if (align_eq(j->head, j->tail, j->nand->log2_ppb)) + j->tail = next_block(j->nand, + j->tail >> j->nand->log2_ppb) << + j->nand->log2_ppb; + break; + } + } + + return 0; +} + +int dhara_journal_resume(struct dhara_journal *j, dhara_error_t *err) +{ + dhara_block_t first, last; + dhara_page_t last_group; + + /* Find the first checkpoint-containing block */ + if (find_checkblock(j, 0, &first, err) < 0) { + reset_journal(j); + return -1; + } + + /* Find the last checkpoint-containing block in this epoch */ + j->epoch = hdr_get_epoch(j->page_buf); + last = find_last_checkblock(j, first); + + /* Find the last programmed checkpoint group in the block */ + last_group = find_last_group(j, last); + + /* Perform a linear scan to find the last good checkpoint (and + * therefore the root). + */ + if (find_root(j, last_group, err) < 0) { + reset_journal(j); + return -1; + } + + /* Restore settings from checkpoint */ + j->tail = hdr_get_tail(j->page_buf); + j->bb_current = hdr_get_bb_current(j->page_buf); + j->bb_last = hdr_get_bb_last(j->page_buf); + hdr_clear_user(j->page_buf, j->nand->log2_page_size); + + /* Perform another linear scan to find the next free user page */ + if (find_head(j, last_group, err) < 0) { + reset_journal(j); + return -1; + } + + j->flags = 0; + j->tail_sync = j->tail; + + clear_recovery(j); + return 0; +} + +/************************************************************************** + * Public interface + */ + +dhara_page_t dhara_journal_capacity(const struct dhara_journal *j) +{ + const dhara_block_t max_bad = j->bb_last > j->bb_current ? + j->bb_last : j->bb_current; + const dhara_block_t good_blocks = j->nand->num_blocks - max_bad - 1; + const int log2_cpb = j->nand->log2_ppb - j->log2_ppc; + const dhara_page_t good_cps = good_blocks << log2_cpb; + + /* Good checkpoints * (checkpoint period - 1) */ + return (good_cps << j->log2_ppc) - good_cps; +} + +dhara_page_t dhara_journal_size(const struct dhara_journal *j) +{ + /* Find the number of raw pages, and the number of checkpoints + * between the head and the tail. The difference between the two + * is the number of user pages (upper limit). + */ + dhara_page_t num_pages = j->head; + dhara_page_t num_cps = j->head >> j->log2_ppc; + + if (j->head < j->tail_sync) { + const dhara_page_t total_pages = + j->nand->num_blocks << j->nand->log2_ppb; + + num_pages += total_pages; + num_cps += total_pages >> j->log2_ppc; + } + + num_pages -= j->tail_sync; + num_cps -= j->tail_sync >> j->log2_ppc; + + return num_pages - num_cps; +} + +int dhara_journal_read_meta(struct dhara_journal *j, dhara_page_t p, + uint8_t *buf, dhara_error_t *err) +{ + /* Offset of metadata within the metadata page */ + const dhara_page_t ppc_mask = (1 << j->log2_ppc) - 1; + const size_t offset = hdr_user_offset(p & ppc_mask); + + /* Special case: buffered metadata */ + if (align_eq(p, j->head, j->log2_ppc)) { + memcpy(buf, j->page_buf + offset, DHARA_META_SIZE); + return 0; + } + + /* Special case: incomplete metadata dumped at start of + * recovery. + */ + if ((j->recover_meta != DHARA_PAGE_NONE) && + align_eq(p, j->recover_root, j->log2_ppc)) + return dhara_nand_read(j->nand, j->recover_meta, + offset, DHARA_META_SIZE, + buf, err); + + /* General case: fetch from metadata page for checkpoint group */ + return dhara_nand_read(j->nand, p | ppc_mask, + offset, DHARA_META_SIZE, + buf, err); +} + +dhara_page_t dhara_journal_peek(struct dhara_journal *j) +{ + if (j->head == j->tail) + return DHARA_PAGE_NONE; + + if (is_aligned(j->tail, j->nand->log2_ppb)) { + dhara_block_t blk = j->tail >> j->nand->log2_ppb; + int i; + + for (i = 0; i < DHARA_MAX_RETRIES; i++) { + if ((blk == (j->head >> j->nand->log2_ppb)) || + !dhara_nand_is_bad(j->nand, blk)) { + j->tail = blk << j->nand->log2_ppb; + + if (j->tail == j->head) + j->root = DHARA_PAGE_NONE; + + return j->tail; + } + + blk = next_block(j->nand, blk); + } + } + + return j->tail; +} + +static dhara_page_t wrap(dhara_page_t a, dhara_page_t b) +{ + return a >= b ? (a - b) : a; +} + +void dhara_journal_dequeue(struct dhara_journal *j) +{ + if (j->head == j->tail) + return; + + j->tail = next_upage(j, j->tail); + + /* If the journal is clean at the time of dequeue, then this + * data was always obsolete, and can be reused immediately. + */ + if (!(j->flags & (DHARA_JOURNAL_F_DIRTY | DHARA_JOURNAL_F_RECOVERY))) + j->tail_sync = j->tail; + + const dhara_page_t chip_size = j->nand->num_blocks << j->nand->log2_ppb; + const dhara_page_t raw_size = wrap(j->head + chip_size - j->tail, + chip_size); + const dhara_page_t root_offset = wrap(j->head + chip_size - j->root, + chip_size); + + if (root_offset > raw_size) + j->root = DHARA_PAGE_NONE; +} + +void dhara_journal_clear(struct dhara_journal *j) +{ + j->tail = j->head; + j->root = DHARA_PAGE_NONE; + j->flags |= DHARA_JOURNAL_F_DIRTY; + + hdr_clear_user(j->page_buf, j->nand->log2_page_size); +} + +static int skip_block(struct dhara_journal *j, dhara_error_t *err) +{ + const dhara_block_t next = next_block(j->nand, + j->head >> j->nand->log2_ppb); + + /* We can't roll onto the same block as the tail */ + if ((j->tail_sync >> j->nand->log2_ppb) == next) { + dhara_set_error(err, DHARA_E_JOURNAL_FULL); + return -1; + } + + j->head = next << j->nand->log2_ppb; + if (!j->head) + roll_stats(j); + + return 0; +} + +/* Make sure the head pointer is on a ready-to-program page. */ +static int prepare_head(struct dhara_journal *j, dhara_error_t *err) +{ + const dhara_page_t next = next_upage(j, j->head); + int i; + + /* We can't write if doing so would cause the head pointer to + * roll onto the same block as the last-synced tail. + */ + if (align_eq(next, j->tail_sync, j->nand->log2_ppb) && + !align_eq(next, j->head, j->nand->log2_ppb)) { + dhara_set_error(err, DHARA_E_JOURNAL_FULL); + return -1; + } + + j->flags |= DHARA_JOURNAL_F_DIRTY; + if (!is_aligned(j->head, j->nand->log2_ppb)) + return 0; + + for (i = 0; i < DHARA_MAX_RETRIES; i++) { + const dhara_block_t blk = j->head >> j->nand->log2_ppb; + + if (!dhara_nand_is_bad(j->nand, blk)) + return dhara_nand_erase(j->nand, blk, err); + + j->bb_current++; + if (skip_block(j, err) < 0) + return -1; + } + + dhara_set_error(err, DHARA_E_TOO_BAD); + return -1; +} + +static void restart_recovery(struct dhara_journal *j, dhara_page_t old_head) +{ + /* Mark the current head bad immediately, unless we're also + * using it to hold our dumped metadata (it will then be marked + * bad at the end of recovery). + */ + if ((j->recover_meta == DHARA_PAGE_NONE) || + !align_eq(j->recover_meta, old_head, j->nand->log2_ppb)) + dhara_nand_mark_bad(j->nand, old_head >> j->nand->log2_ppb); + else + j->flags |= DHARA_JOURNAL_F_BAD_META; + + /* Start recovery again. Reset the source enumeration to + * the start of the original bad block, and reset the + * destination enumeration to the newly found good + * block. + */ + j->flags &= ~DHARA_JOURNAL_F_ENUM_DONE; + j->recover_next = + j->recover_root & ~((1 << j->nand->log2_ppb) - 1); + + j->root = j->recover_root; +} + +static int dump_meta(struct dhara_journal *j, dhara_error_t *err) +{ + int i; + + /* We've just begun recovery on a new erasable block, but we + * have buffered metadata from the failed block. + */ + for (i = 0; i < DHARA_MAX_RETRIES; i++) { + dhara_error_t my_err; + + /* Try to dump metadata on this page */ + if (!(prepare_head(j, &my_err) || + dhara_nand_prog(j->nand, j->head, + j->page_buf, &my_err))) { + j->recover_meta = j->head; + j->head = next_upage(j, j->head); + if (!j->head) + roll_stats(j); + hdr_clear_user(j->page_buf, j->nand->log2_page_size); + return 0; + } + + /* Report fatal errors */ + if (my_err != DHARA_E_BAD_BLOCK) { + dhara_set_error(err, my_err); + return -1; + } + + j->bb_current++; + dhara_nand_mark_bad(j->nand, j->head >> j->nand->log2_ppb); + + if (skip_block(j, err) < 0) + return -1; + } + + dhara_set_error(err, DHARA_E_TOO_BAD); + return -1; +} + +static int recover_from(struct dhara_journal *j, + dhara_error_t write_err, + dhara_error_t *err) +{ + const dhara_page_t old_head = j->head; + + if (write_err != DHARA_E_BAD_BLOCK) { + dhara_set_error(err, write_err); + return -1; + } + + /* Advance to the next free page */ + j->bb_current++; + if (skip_block(j, err) < 0) + return -1; + + /* Are we already in the middle of a recovery? */ + if (dhara_journal_in_recovery(j)) { + restart_recovery(j, old_head); + dhara_set_error(err, DHARA_E_RECOVER); + return -1; + } + + /* Were we block aligned? No recovery required! */ + if (is_aligned(old_head, j->nand->log2_ppb)) { + dhara_nand_mark_bad(j->nand, old_head >> j->nand->log2_ppb); + return 0; + } + + j->recover_root = j->root; + j->recover_next = + j->recover_root & ~((1 << j->nand->log2_ppb) - 1); + + /* Are we holding buffered metadata? Dump it first. */ + if (!is_aligned(old_head, j->log2_ppc) && + dump_meta(j, err) < 0) + return -1; + + j->flags |= DHARA_JOURNAL_F_RECOVERY; + dhara_set_error(err, DHARA_E_RECOVER); + return -1; +} + +static void finish_recovery(struct dhara_journal *j) +{ + /* We just recovered the last page. Mark the recovered + * block as bad. + */ + dhara_nand_mark_bad(j->nand, + j->recover_root >> j->nand->log2_ppb); + + /* If we had to dump metadata, and the page on which we + * did this also went bad, mark it bad too. + */ + if (j->flags & DHARA_JOURNAL_F_BAD_META) + dhara_nand_mark_bad(j->nand, + j->recover_meta >> j->nand->log2_ppb); + + /* Was the tail on this page? Skip it forward */ + clear_recovery(j); +} + +static int push_meta(struct dhara_journal *j, const uint8_t *meta, + dhara_error_t *err) +{ + const dhara_page_t old_head = j->head; + dhara_error_t my_err; + const size_t offset = + hdr_user_offset(j->head & ((1 << j->log2_ppc) - 1)); + + /* We've just written a user page. Add the metadata to the + * buffer. + */ + if (meta) + memcpy(j->page_buf + offset, meta, DHARA_META_SIZE); + else + memset(j->page_buf + offset, 0xff, DHARA_META_SIZE); + + /* Unless we've filled the buffer, don't do any IO */ + if (!is_aligned(j->head + 2, j->log2_ppc)) { + j->root = j->head; + j->head++; + return 0; + } + + /* We don't need to check for immediate recover, because that'll + * never happen -- we're not block-aligned. + */ + hdr_put_magic(j->page_buf); + hdr_set_epoch(j->page_buf, j->epoch); + hdr_set_tail(j->page_buf, j->tail); + hdr_set_bb_current(j->page_buf, j->bb_current); + hdr_set_bb_last(j->page_buf, j->bb_last); + + if (dhara_nand_prog(j->nand, j->head + 1, j->page_buf, &my_err) < 0) + return recover_from(j, my_err, err); + + j->flags &= ~DHARA_JOURNAL_F_DIRTY; + + j->root = old_head; + j->head = next_upage(j, j->head); + + if (!j->head) + roll_stats(j); + + if (j->flags & DHARA_JOURNAL_F_ENUM_DONE) + finish_recovery(j); + + if (!(j->flags & DHARA_JOURNAL_F_RECOVERY)) + j->tail_sync = j->tail; + + return 0; +} + +int dhara_journal_enqueue(struct dhara_journal *j, + const uint8_t *data, const uint8_t *meta, + dhara_error_t *err) +{ + dhara_error_t my_err; + int i; + + for (i = 0; i < DHARA_MAX_RETRIES; i++) { + if (!(prepare_head(j, &my_err) || + (data && dhara_nand_prog(j->nand, j->head, data, + &my_err)))) + return push_meta(j, meta, err); + + if (recover_from(j, my_err, err) < 0) + return -1; + } + + dhara_set_error(err, DHARA_E_TOO_BAD); + return -1; +} + +int dhara_journal_copy(struct dhara_journal *j, + dhara_page_t p, const uint8_t *meta, + dhara_error_t *err) +{ + dhara_error_t my_err; + int i; + + for (i = 0; i < DHARA_MAX_RETRIES; i++) { + if (!(prepare_head(j, &my_err) || + dhara_nand_copy(j->nand, p, j->head, &my_err))) + return push_meta(j, meta, err); + + if (recover_from(j, my_err, err) < 0) + return -1; + } + + dhara_set_error(err, DHARA_E_TOO_BAD); + return -1; +} + +dhara_page_t dhara_journal_next_recoverable(struct dhara_journal *j) +{ + const dhara_page_t n = j->recover_next; + + if (!dhara_journal_in_recovery(j)) + return DHARA_PAGE_NONE; + + if (j->flags & DHARA_JOURNAL_F_ENUM_DONE) + return DHARA_PAGE_NONE; + + if (j->recover_next == j->recover_root) + j->flags |= DHARA_JOURNAL_F_ENUM_DONE; + else + j->recover_next = next_upage(j, j->recover_next); + + return n; +} diff --git a/Lib/dhara/journal.h b/Lib/dhara/journal.h new file mode 100644 index 0000000..6eb455b --- /dev/null +++ b/Lib/dhara/journal.h @@ -0,0 +1,256 @@ +/* Dhara - NAND flash management layer + * Copyright (C) 2013 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DHARA_JOURNAL_H_ +#define DHARA_JOURNAL_H_ + +#include +#include "nand.h" + +/* Number of bytes used by the journal checkpoint header. */ +#define DHARA_HEADER_SIZE 16 + +/* Global metadata available for a higher layer. This metadata is + * persistent once the journal reaches a checkpoint, and is restored on + * startup. + */ +#define DHARA_COOKIE_SIZE 4 + +/* This is the size of the metadata slice which accompanies each written + * page. This is independent of the underlying page/OOB size. + */ +#define DHARA_META_SIZE 132 + +/* When a block fails, or garbage is encountered, we try again on the + * next block/checkpoint. We can do this up to the given number of + * times. + */ +#define DHARA_MAX_RETRIES 8 + +/* This is a page number which can be used to represent "no such page". + * It's guaranteed to never be a valid user page. + */ +#define DHARA_PAGE_NONE ((dhara_page_t)0xffffffff) + +/* State flags */ +#define DHARA_JOURNAL_F_DIRTY 0x01 +#define DHARA_JOURNAL_F_BAD_META 0x02 +#define DHARA_JOURNAL_F_RECOVERY 0x04 +#define DHARA_JOURNAL_F_ENUM_DONE 0x08 + +/* The journal layer presents the NAND pages as a double-ended queue. + * Pages, with associated metadata may be pushed onto the end of the + * queue, and pages may be popped from the end. + * + * Block erase, metadata storage are handled automatically. Bad blocks + * are handled by relocating data to the next available non-bad page in + * the sequence. + * + * It's up to the user to ensure that the queue doesn't grow beyond the + * capacity of the NAND chip, but helper functions are provided to + * assist with this. If the head meets the tail, the journal will refuse + * to enqueue more pages. + */ +struct dhara_journal { + const struct dhara_nand *nand; + uint8_t *page_buf; + + /* In the journal, user data is grouped into checkpoints of + * 2**log2_ppc contiguous aligned pages. + * + * The last page of each checkpoint contains the journal header + * and the metadata for the other pages in the period (the user + * pages). + */ + uint8_t log2_ppc; + + /* Epoch counter. This is incremented whenever the journal head + * passes the end of the chip and wraps around. + */ + uint8_t epoch; + + /* General purpose flags field */ + uint8_t flags; + + /* Bad-block counters. bb_last is our best estimate of the + * number of bad blocks in the chip as a whole. bb_current is + * the number of bad blocks in all blocks before the current + * head. + */ + dhara_block_t bb_current; + dhara_block_t bb_last; + + /* Log head and tail. The tail pointer points to the last user + * page in the log, and the head pointer points to the next free + * raw page. The root points to the last written user page. + */ + dhara_page_t tail_sync; + dhara_page_t tail; + dhara_page_t head; + + /* This points to the last written user page in the journal */ + dhara_page_t root; + + /* Recovery mode: recover_root points to the last valid user + * page in the block requiring recovery. recover_next points to + * the next user page needing recovery. + * + * If we had buffered metadata before recovery started, it will + * have been dumped to a free page, indicated by recover_meta. + * If this block later goes bad, we will have to defer bad-block + * marking until recovery is complete (F_BAD_META). + */ + dhara_page_t recover_next; + dhara_page_t recover_root; + dhara_page_t recover_meta; +}; + +/* Initialize a journal. You must supply a pointer to a NAND chip + * driver, and a single page buffer. This page buffer will be used + * exclusively by the journal, but you are responsible for allocating + * it, and freeing it (if necessary) at the end. + * + * No NAND operations are performed at this point. + */ +void dhara_journal_init(struct dhara_journal *j, + const struct dhara_nand *n, + uint8_t *page_buf); + +/* Start up the journal -- search the NAND for the journal head, or + * initialize a blank journal if one isn't found. Returns 0 on success + * or -1 if a (fatal) error occurs. + * + * This operation is O(log N), where N is the number of pages in the + * NAND chip. All other operations are O(1). + * + * If this operation fails, the journal will be reset to an empty state. + */ +int dhara_journal_resume(struct dhara_journal *j, dhara_error_t *err); + +/* Obtain an upper bound on the number of user pages storable in the + * journal. + */ +dhara_page_t dhara_journal_capacity(const struct dhara_journal *j); + +/* Obtain an upper bound on the number of user pages consumed by the + * journal. + */ +dhara_page_t dhara_journal_size(const struct dhara_journal *j); + +/* Obtain a pointer to the cookie data */ +static inline uint8_t *dhara_journal_cookie(const struct dhara_journal *j) +{ + return j->page_buf + DHARA_HEADER_SIZE; +} + +/* Obtain the locations of the first and last pages in the journal. + */ +static inline dhara_page_t dhara_journal_root(const struct dhara_journal *j) +{ + return j->root; +} + +/* Read metadata associated with a page. This assumes that the page + * provided is a valid data page. The actual page data is read via the + * normal NAND interface. + */ +int dhara_journal_read_meta(struct dhara_journal *j, dhara_page_t p, + uint8_t *buf, dhara_error_t *err); + +/* Advance the tail to the next non-bad block and return the page that's + * ready to read. If no page is ready, return DHARA_PAGE_NONE. + */ +dhara_page_t dhara_journal_peek(struct dhara_journal *j); + +/* Remove the last page from the journal. This doesn't take permanent + * effect until the next checkpoint. + */ +void dhara_journal_dequeue(struct dhara_journal *j); + +/* Remove all pages form the journal. This doesn't take permanent effect + * until the next checkpoint. + */ +void dhara_journal_clear(struct dhara_journal *j); + +/* Append a page to the journal. Both raw page data and metadata must be + * specified. The push operation is not persistent until a checkpoint is + * reached. + * + * This operation may fail with the error code E_RECOVER. If this + * occurs, the upper layer must complete the assisted recovery procedure + * and then try again. + * + * This operation may be used as part of a recovery. If further errors + * occur during recovery, E_RECOVER is returned, and the procedure must + * be restarted. + */ +int dhara_journal_enqueue(struct dhara_journal *j, + const uint8_t *data, const uint8_t *meta, + dhara_error_t *err); + +/* Copy an existing page to the front of the journal. New metadata must + * be specified. This operation is not persistent until a checkpoint is + * reached. + * + * This operation may fail with the error code E_RECOVER. If this + * occurs, the upper layer must complete the assisted recovery procedure + * and then try again. + * + * This operation may be used as part of a recovery. If further errors + * occur during recovery, E_RECOVER is returned, and the procedure must + * be restarted. + */ +int dhara_journal_copy(struct dhara_journal *j, + dhara_page_t p, const uint8_t *meta, + dhara_error_t *err); + +/* Mark the journal dirty. */ +static inline void dhara_journal_mark_dirty(struct dhara_journal *j) +{ + j->flags |= DHARA_JOURNAL_F_DIRTY; +} + +/* Is the journal checkpointed? If true, then all pages enqueued are now + * persistent. + */ +static inline int dhara_journal_is_clean(const struct dhara_journal *j) +{ + return !(j->flags & DHARA_JOURNAL_F_DIRTY); +} + +/* If an operation returns E_RECOVER, you must begin the recovery + * procedure. You must then: + * + * - call dhara_journal_next_recoverable() to obtain the next block + * to be recovered (if any). If there are no blocks remaining to be + * recovered, DHARA_JOURNAL_PAGE_NONE is returned. + * + * - proceed to the next checkpoint. Once the journal is clean, + * recovery will finish automatically. + * + * If any operation during recovery fails due to a bad block, E_RECOVER + * is returned again, and recovery restarts. Do not add new data to the + * journal (rewrites of recovered data are fine) until recovery is + * complete. + */ +static inline int dhara_journal_in_recovery(const struct dhara_journal *j) +{ + return j->flags & DHARA_JOURNAL_F_RECOVERY; +} + +dhara_page_t dhara_journal_next_recoverable(struct dhara_journal *j); + +#endif diff --git a/Lib/dhara/map.c b/Lib/dhara/map.c new file mode 100644 index 0000000..48ec45e --- /dev/null +++ b/Lib/dhara/map.c @@ -0,0 +1,530 @@ +/* Dhara - NAND flash management layer + * Copyright (C) 2013 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "bytes.h" +#include "map.h" + +#define DHARA_RADIX_DEPTH (sizeof(dhara_sector_t) << 3) + +static inline dhara_sector_t d_bit(int depth) +{ + return ((dhara_sector_t)1) << (DHARA_RADIX_DEPTH - depth - 1); +} + +/************************************************************************ + * Metadata/cookie layout + */ + +static inline void ck_set_count(uint8_t *cookie, dhara_sector_t count) +{ + dhara_w32(cookie, count); +} + +static inline dhara_sector_t ck_get_count(const uint8_t *cookie) +{ + return dhara_r32(cookie); +} + +static inline void meta_clear(uint8_t *meta) +{ + memset(meta, 0xff, DHARA_META_SIZE); +} + +static inline dhara_sector_t meta_get_id(const uint8_t *meta) +{ + return dhara_r32(meta); +} + +static inline void meta_set_id(uint8_t *meta, dhara_sector_t id) +{ + dhara_w32(meta, id); +} + +static inline dhara_page_t meta_get_alt(const uint8_t *meta, int level) +{ + return dhara_r32(meta + 4 + (level << 2)); +} + +static inline void meta_set_alt(uint8_t *meta, int level, dhara_page_t alt) +{ + dhara_w32(meta + 4 + (level << 2), alt); +} + +/************************************************************************ + * Public interface + */ + +void dhara_map_init(struct dhara_map *m, const struct dhara_nand *n, + uint8_t *page_buf, uint8_t gc_ratio) +{ + if (!gc_ratio) + gc_ratio = 1; + + dhara_journal_init(&m->journal, n, page_buf); + m->gc_ratio = gc_ratio; +} + +int dhara_map_resume(struct dhara_map *m, dhara_error_t *err) +{ + if (dhara_journal_resume(&m->journal, err) < 0) { + m->count = 0; + return -1; + } + + m->count = ck_get_count(dhara_journal_cookie(&m->journal)); + return 0; +} + +void dhara_map_clear(struct dhara_map *m) +{ + if (m->count) { + m->count = 0; + dhara_journal_clear(&m->journal); + } +} + +dhara_sector_t dhara_map_capacity(const struct dhara_map *m) +{ + const dhara_sector_t cap = dhara_journal_capacity(&m->journal); + const dhara_sector_t reserve = cap / (m->gc_ratio + 1); + const dhara_sector_t safety_margin = + DHARA_MAX_RETRIES << m->journal.nand->log2_ppb; + + if (reserve + safety_margin >= cap) + return 0; + + return cap - reserve - safety_margin; +} + +/* Trace the path from the root to the given sector, emitting + * alt-pointers and alt-full bits in the given metadata buffer. This + * also returns the physical page containing the given sector, if it + * exists. + * + * If the page can't be found, a suitable path will be constructed + * (containing PAGE_NONE alt-pointers), and DHARA_E_NOT_FOUND will be + * returned. + */ +static int trace_path(struct dhara_map *m, dhara_sector_t target, + dhara_page_t *loc, uint8_t *new_meta, + dhara_error_t *err) +{ + uint8_t meta[DHARA_META_SIZE]; + int depth = 0; + dhara_page_t p = dhara_journal_root(&m->journal); + + if (new_meta) + meta_set_id(new_meta, target); + + if (p == DHARA_PAGE_NONE) + goto not_found; + + if (dhara_journal_read_meta(&m->journal, p, meta, err) < 0) + return -1; + + while (depth < DHARA_RADIX_DEPTH) { + const dhara_sector_t id = meta_get_id(meta); + + if (id == DHARA_SECTOR_NONE) + goto not_found; + + if ((target ^ id) & d_bit(depth)) { + if (new_meta) + meta_set_alt(new_meta, depth, p); + + p = meta_get_alt(meta, depth); + if (p == DHARA_PAGE_NONE) { + depth++; + goto not_found; + } + + if (dhara_journal_read_meta(&m->journal, p, + meta, err) < 0) + return -1; + } else { + if (new_meta) + meta_set_alt(new_meta, depth, + meta_get_alt(meta, depth)); + } + + depth++; + } + + if (loc) + *loc = p; + + return 0; + +not_found: + if (new_meta) { + while (depth < DHARA_RADIX_DEPTH) + meta_set_alt(new_meta, depth++, DHARA_SECTOR_NONE); + } + + dhara_set_error(err, DHARA_E_NOT_FOUND); + return -1; +} + +int dhara_map_find(struct dhara_map *m, dhara_sector_t target, + dhara_page_t *loc, dhara_error_t *err) +{ + return trace_path(m, target, loc, NULL, err); +} + +int dhara_map_read(struct dhara_map *m, dhara_sector_t s, + uint8_t *data, dhara_error_t *err) +{ + const struct dhara_nand *n = m->journal.nand; + dhara_error_t my_err; + dhara_page_t p; + + if (dhara_map_find(m, s, &p, &my_err) < 0) { + if (my_err == DHARA_E_NOT_FOUND) { + memset(data, 0xff, 1 << n->log2_page_size); + return 0; + } + + dhara_set_error(err, my_err); + return -1; + } + + return dhara_nand_read(n, p, 0, 1 << n->log2_page_size, data, err); +} + +/* Check the given page. If it's garbage, do nothing. Otherwise, rewrite + * it at the front of the map. Return raw errors from the journal (do + * not perform recovery). + */ +static int raw_gc(struct dhara_map *m, dhara_page_t src, + dhara_error_t *err) +{ + dhara_sector_t target; + dhara_page_t current; + dhara_error_t my_err; + uint8_t meta[DHARA_META_SIZE]; + + if (dhara_journal_read_meta(&m->journal, src, meta, err) < 0) + return -1; + + /* Is the page just filler/garbage? */ + target = meta_get_id(meta); + if (target == DHARA_SECTOR_NONE) + return 0; + + /* Find out where the sector once represented by this page + * currently resides (if anywhere). + */ + if (trace_path(m, target, ¤t, meta, &my_err) < 0) { + if (my_err == DHARA_E_NOT_FOUND) + return 0; + + dhara_set_error(err, my_err); + return -1; + } + + /* Is this page still the most current representative? If not, + * do nothing. + */ + if (current != src) + return 0; + + /* Rewrite it at the front of the journal with updated metadata */ + ck_set_count(dhara_journal_cookie(&m->journal), m->count); + if (dhara_journal_copy(&m->journal, src, meta, err) < 0) + return -1; + + return 0; +} + +static int pad_queue(struct dhara_map *m, dhara_error_t *err) +{ + dhara_page_t p = dhara_journal_root(&m->journal); + uint8_t root_meta[DHARA_META_SIZE]; + + ck_set_count(dhara_journal_cookie(&m->journal), m->count); + + if (p == DHARA_PAGE_NONE) + return dhara_journal_enqueue(&m->journal, NULL, NULL, err); + + if (dhara_journal_read_meta(&m->journal, p, root_meta, err) < 0) + return -1; + + return dhara_journal_copy(&m->journal, p, root_meta, err); +} + +/* Attempt to recover the journal */ +static int try_recover(struct dhara_map *m, dhara_error_t cause, + dhara_error_t *err) +{ + int restart_count = 0; + + if (cause != DHARA_E_RECOVER) { + dhara_set_error(err, cause); + return -1; + } + + while (dhara_journal_in_recovery(&m->journal)) { + dhara_page_t p = dhara_journal_next_recoverable(&m->journal); + dhara_error_t my_err; + int ret; + + if (p == DHARA_PAGE_NONE) + ret = pad_queue(m, &my_err); + else + ret = raw_gc(m, p, &my_err); + + if (ret < 0) { + if (my_err != DHARA_E_RECOVER) { + dhara_set_error(err, my_err); + return -1; + } + + if (restart_count >= DHARA_MAX_RETRIES) { + dhara_set_error(err, DHARA_E_TOO_BAD); + return -1; + } + + restart_count++; + } + } + + return 0; +} + +static int auto_gc(struct dhara_map *m, dhara_error_t *err) +{ + int i; + + if (dhara_journal_size(&m->journal) < dhara_map_capacity(m)) + return 0; + + for (i = 0; i <= m->gc_ratio; i++) + if (dhara_map_gc(m, err) < 0) + return -1; + + return 0; +} + +static int prepare_write(struct dhara_map *m, dhara_sector_t dst, + uint8_t *meta, dhara_error_t *err) +{ + dhara_error_t my_err; + + if (auto_gc(m, err) < 0) + return -1; + + if (trace_path(m, dst, NULL, meta, &my_err) < 0) { + if (my_err != DHARA_E_NOT_FOUND) { + dhara_set_error(err, my_err); + return -1; + } + + if (m->count >= dhara_map_capacity(m)) { + dhara_set_error(err, DHARA_E_MAP_FULL); + return -1; + } + + m->count++; + } + + ck_set_count(dhara_journal_cookie(&m->journal), m->count); + return 0; +} + +int dhara_map_write(struct dhara_map *m, dhara_sector_t dst, + const uint8_t *data, dhara_error_t *err) +{ + for (;;) { + uint8_t meta[DHARA_META_SIZE]; + dhara_error_t my_err; + const dhara_sector_t old_count = m->count; + + if (prepare_write(m, dst, meta, err) < 0) + return -1; + + if (!dhara_journal_enqueue(&m->journal, data, meta, &my_err)) + break; + + m->count = old_count; + + if (try_recover(m, my_err, err) < 0) + return -1; + } + + return 0; +} + +int dhara_map_copy_page(struct dhara_map *m, dhara_page_t src, + dhara_sector_t dst, dhara_error_t *err) +{ + for (;;) { + uint8_t meta[DHARA_META_SIZE]; + dhara_error_t my_err; + const dhara_sector_t old_count = m->count; + + if (prepare_write(m, dst, meta, err) < 0) + return -1; + + if (!dhara_journal_copy(&m->journal, src, meta, &my_err)) + break; + + m->count = old_count; + + if (try_recover(m, my_err, err) < 0) + return -1; + } + + return 0; +} + +int dhara_map_copy_sector(struct dhara_map *m, dhara_sector_t src, + dhara_sector_t dst, dhara_error_t *err) +{ + dhara_error_t my_err; + dhara_page_t p; + + if (dhara_map_find(m, src, &p, &my_err) < 0) { + if (my_err == DHARA_E_NOT_FOUND) + return dhara_map_trim(m, dst, err); + + dhara_set_error(err, my_err); + return -1; + } + + return dhara_map_copy_page(m, p, dst, err); +} + +static int try_delete(struct dhara_map *m, dhara_sector_t s, + dhara_error_t *err) +{ + dhara_error_t my_err; + uint8_t meta[DHARA_META_SIZE]; + dhara_page_t alt_page; + uint8_t alt_meta[DHARA_META_SIZE]; + int level = DHARA_RADIX_DEPTH - 1; + int i; + + if (trace_path(m, s, NULL, meta, &my_err) < 0) { + if (my_err == DHARA_E_NOT_FOUND) + return 0; + + dhara_set_error(err, my_err); + return -1; + } + + /* Select any of the closest cousins of this node which are + * subtrees of at least the requested order. + */ + while (level >= 0) { + alt_page = meta_get_alt(meta, level); + if (alt_page != DHARA_PAGE_NONE) + break; + level--; + } + + /* Special case: deletion of last sector */ + if (level < 0) { + m->count = 0; + dhara_journal_clear(&m->journal); + return 0; + } + + /* Rewrite the cousin with an up-to-date path which doesn't + * point to the original node. + */ + if (dhara_journal_read_meta(&m->journal, alt_page, alt_meta, err) < 0) + return -1; + + meta_set_id(meta, meta_get_id(alt_meta)); + + meta_set_alt(meta, level, DHARA_PAGE_NONE); + for (i = level + 1; i < DHARA_RADIX_DEPTH; i++) + meta_set_alt(meta, i, meta_get_alt(alt_meta, i)); + + meta_set_alt(meta, level, DHARA_PAGE_NONE); + + ck_set_count(dhara_journal_cookie(&m->journal), m->count - 1); + if (dhara_journal_copy(&m->journal, alt_page, meta, err) < 0) + return -1; + + m->count--; + return 0; +} + +int dhara_map_trim(struct dhara_map *m, dhara_sector_t s, dhara_error_t *err) +{ + for (;;) { + dhara_error_t my_err; + + if (auto_gc(m, err) < 0) + return -1; + + if (!try_delete(m, s, &my_err)) + break; + + if (try_recover(m, my_err, err) < 0) + return -1; + } + + return 0; +} + +int dhara_map_sync(struct dhara_map *m, dhara_error_t *err) +{ + while (!dhara_journal_is_clean(&m->journal)) { + dhara_page_t p = dhara_journal_peek(&m->journal); + dhara_error_t my_err; + int ret; + + if (p == DHARA_PAGE_NONE) { + ret = pad_queue(m, &my_err); + } else { + ret = raw_gc(m, p, &my_err); + if (!ret) + dhara_journal_dequeue(&m->journal); + } + + if ((ret < 0) && (try_recover(m, my_err, err) < 0)) + return -1; + } + + return 0; +} + +int dhara_map_gc(struct dhara_map *m, dhara_error_t *err) +{ + if (!m->count) + return 0; + + for (;;) { + dhara_page_t tail = dhara_journal_peek(&m->journal); + dhara_error_t my_err; + + if (tail == DHARA_PAGE_NONE) + break; + + if (!raw_gc(m, tail, &my_err)) { + dhara_journal_dequeue(&m->journal); + break; + } + + if (try_recover(m, my_err, err) < 0) + return -1; + } + + return 0; +} diff --git a/Lib/dhara/map.h b/Lib/dhara/map.h new file mode 100644 index 0000000..38e1962 --- /dev/null +++ b/Lib/dhara/map.h @@ -0,0 +1,114 @@ +/* Dhara - NAND flash management layer + * Copyright (C) 2013 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DHARA_MAP_H_ +#define DHARA_MAP_H_ + +#include "journal.h" + +/* The map is a journal indexing format. It maps virtual sectors to + * pages of data in flash memory. + */ +typedef uint32_t dhara_sector_t; + +/* This sector value is reserved */ +#define DHARA_SECTOR_NONE 0xffffffff + +struct dhara_map { + struct dhara_journal journal; + + uint8_t gc_ratio; + dhara_sector_t count; +}; + +/* Initialize a map. You need to supply a buffer for page metadata, and + * a garbage collection ratio. This is the ratio of garbage collection + * operations to real writes when automatic collection is active. + * + * Smaller values lead to faster and more predictable IO, at the + * expense of capacity. You should always initialize the same chip with + * the same garbage collection ratio. + */ +void dhara_map_init(struct dhara_map *m, const struct dhara_nand *n, + uint8_t *page_buf, uint8_t gc_ratio); + +/* Recover stored state, if possible. If there is no valid stored state + * on the chip, -1 is returned, and an empty map is initialized. + */ +int dhara_map_resume(struct dhara_map *m, dhara_error_t *err); + +/* Clear the map (delete all sectors). */ +void dhara_map_clear(struct dhara_map *m); + +/* Obtain the maximum capacity of the map. */ +dhara_sector_t dhara_map_capacity(const struct dhara_map *m); + +/* Obtain the current number of allocated sectors. */ +static inline dhara_sector_t dhara_map_size(const struct dhara_map *m) +{ + return m->count; +} + +/* Find the physical page which holds the current data for this sector. + * Returns 0 on success or -1 if an error occurs. If the sector doesn't + * exist, the error is E_NOT_FOUND. + */ +int dhara_map_find(struct dhara_map *m, dhara_sector_t s, + dhara_page_t *loc, dhara_error_t *err); + +/* Read from the given logical sector. If the sector is unmapped, a + * blank page (0xff) will be returned. + */ +int dhara_map_read(struct dhara_map *m, dhara_sector_t s, + uint8_t *data, dhara_error_t *err); + +/* Write data to a logical sector. */ +int dhara_map_write(struct dhara_map *m, dhara_sector_t s, + const uint8_t *data, dhara_error_t *err); + +/* Copy any flash page to a logical sector. */ +int dhara_map_copy_page(struct dhara_map *m, dhara_page_t src, + dhara_sector_t dst, dhara_error_t *err); + +/* Copy one sector to another. If the source sector is unmapped, the + * destination sector will be trimmed. + */ +int dhara_map_copy_sector(struct dhara_map *m, dhara_sector_t src, + dhara_sector_t dst, dhara_error_t *err); + +/* Delete a logical sector. You don't necessarily need to do this, but + * it's a useful hint if you no longer require the sector's data to be + * kept. + * + * If order is non-zero, it specifies that all sectors in the + * (2**order)-aligned group of s are to be deleted. + */ +int dhara_map_trim(struct dhara_map *m, dhara_sector_t s, + dhara_error_t *err); + +/* Synchronize the map. Once this returns successfully, all changes to + * date are persistent and durable. Conversely, there is no guarantee + * that unsynchronized changes will be persistent. + */ +int dhara_map_sync(struct dhara_map *m, dhara_error_t *err); + +/* Perform one garbage collection step. You can do this whenever you + * like, but it's not necessary -- garbage collection happens + * automatically and is interleaved with other operations. + */ +int dhara_map_gc(struct dhara_map *m, dhara_error_t *err); + +#endif diff --git a/Lib/dhara/nand.h b/Lib/dhara/nand.h new file mode 100644 index 0000000..34bb98d --- /dev/null +++ b/Lib/dhara/nand.h @@ -0,0 +1,105 @@ +/* Dhara - NAND flash management layer + * Copyright (C) 2013 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DHARA_NAND_H_ +#define DHARA_NAND_H_ + +#include +#include +#include "error.h" + +/* Each page in a NAND device is indexed, starting at 0. It's required + * that there be a power-of-two number of pages in a eraseblock, so you can + * view a page number is being a concatenation (in binary) of a block + * number and the number of a page within a block. + */ +typedef uint32_t dhara_page_t; + +/* Blocks are also indexed, starting at 0. */ +typedef uint32_t dhara_block_t; + +/* Each NAND chip must be represented by one of these structures. It's + * intended that this structure be embedded in a larger structure for + * context. + * + * The functions declared below are not implemented -- they must be + * provided and satisfy the documented conditions. + */ +struct dhara_nand { + /* Base-2 logarithm of the page size. If your device supports + * partial programming, you may want to subdivide the actual + * pages into separate ECC-correctable regions and present those + * as pages. + */ + uint8_t log2_page_size; + + /* Base-2 logarithm of the number of pages within an eraseblock */ + uint8_t log2_ppb; + + /* Total number of eraseblocks */ + unsigned int num_blocks; +}; + +/* Is the given block bad? */ +int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b); + +/* Mark bad the given block (or attempt to). No return value is + * required, because there's nothing that can be done in response. + */ +void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b); + +/* Erase the given block. This function should return 0 on success or -1 + * on failure. + * + * The status reported by the chip should be checked. If an erase + * operation fails, return -1 and set err to E_BAD_BLOCK. + */ +int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, + dhara_error_t *err); + +/* Program the given page. The data pointer is a pointer to an entire + * page ((1 << log2_page_size) bytes). The operation status should be + * checked. If the operation fails, return -1 and set err to + * E_BAD_BLOCK. + * + * Pages will be programmed sequentially within a block, and will not be + * reprogrammed. + */ +int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, + const uint8_t *data, + dhara_error_t *err); + +/* Check that the given page is erased */ +int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p); + +/* Read a portion of a page. ECC must be handled by the NAND + * implementation. Returns 0 on sucess or -1 if an error occurs. If an + * uncorrectable ECC error occurs, return -1 and set err to E_ECC. + */ +int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, + size_t offset, size_t length, + uint8_t *data, + dhara_error_t *err); + +/* Read a page from one location and reprogram it in another location. + * This might be done using the chip's internal buffers, but it must use + * ECC. + */ +int dhara_nand_copy(const struct dhara_nand *n, + dhara_page_t src, dhara_page_t dst, + dhara_error_t *err); + +#endif diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 0749d72..765829b 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -338,9 +338,9 @@ 0 - USE_HAL_DRIVER,STM32F407xx + USE_HAL_DRIVER,STM32F407xx,NDEBUG - ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Lib/FlashDB/port/fal/inc;../Lib/FlashDB/inc;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../App;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F + ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Lib/FlashDB/port/fal/inc;../Lib/FlashDB/inc;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../App;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs @@ -761,9 +761,9 @@ ..\App\app_main.c - sys_time.c + sys_clock.c 1 - ..\App\sys_time.c + ..\App\sys_clock.c net_task.c @@ -1256,6 +1256,11 @@ 1 ..\Drivers\BSP\NET\net_socket.c + + nand_ftl.c + 1 + ..\Drivers\BSP\GD5F2GQ5UE\nand_ftl.c + @@ -1932,6 +1937,100 @@ + + lib/dhara + + + 0 + 0 + 0 + 0 + 0 + 2 + 2 + 2 + 2 + 2 + 11 + + + 1 + + + + 2 + 0 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4 + 2 + 2 + 2 + 2 + 2 + 0 + 0 + 2 + 2 + 2 + 2 + 2 + + --diag_suppress=177 + + + + + + + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 1 + + + + + + + + + + + + journal.c + 1 + ..\Lib\dhara\journal.c + + + map.c + 1 + ..\Lib\dhara\map.c + + + + + lib/FatFs + + + ff.c + 1 + ..\Lib\FatFs\ff.c + + + ::CMSIS diff --git a/MDK-ARM/_build.cmd b/MDK-ARM/_build.cmd new file mode 100644 index 0000000..d17858c --- /dev/null +++ b/MDK-ARM/_build.cmd @@ -0,0 +1 @@ +@echo off && cd /d "%~dp0" && C:\Keil_v5\UV4\UV4.exe -r "STM32F407-Demo.uvprojx" -t STM32F407-Demo -j0 diff --git a/MDK-ARM/build_log.txt b/MDK-ARM/build_log.txt index bda1137..a04748a 100644 --- a/MDK-ARM/build_log.txt +++ b/MDK-ARM/build_log.txt @@ -1,71 +1,75 @@ *** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin' Rebuild target 'STM32F407-Demo' assembling startup_stm32f407xx.s... -compiling net_task.c... -compiling app_main.c... -compiling crc.c... compiling ringbuf.c... -compiling dma.c... -compiling stm32f4xx_hal_flash_ex.c... -compiling sys_time.c... +compiling crc.c... compiling stm32f4xx_hal_msp.c... -compiling stm32f4xx_hal_timebase_tim.c... -compiling stm32f4xx_hal_flash_ramfunc.c... -compiling i2c.c... -compiling spi.c... -compiling gpio.c... compiling stm32f4xx_it.c... -compiling stm32f4xx_hal_rcc_ex.c... -compiling usart.c... -compiling stm32f4xx_hal_dma.c... +compiling i2c.c... +compiling gpio.c... +compiling spi.c... +compiling stm32f4xx_hal_timebase_tim.c... +compiling app_main.c... +compiling sys_clock.c... +compiling dma.c... +compiling stm32f4xx_hal_flash_ramfunc.c... compiling stm32f4xx_hal_gpio.c... -compiling freertos.c... +compiling usart.c... compiling stm32f4xx_hal_flash.c... +compiling stm32f4xx_hal_flash_ex.c... +compiling stm32f4xx_hal_rcc_ex.c... compiling rs485_task.c... -compiling stm32f4xx_hal_rcc.c... +compiling net_task.c... compiling adc_task.c... -compiling main.c... -compiling stm32f4xx_hal_dma_ex.c... +compiling stm32f4xx_hal_rcc.c... compiling stm32f4xx_hal_tim_ex.c... +compiling freertos.c... +compiling main.c... compiling stm32f4xx_hal_tim.c... compiling fdb.c... -compiling fdb_utils.c... -compiling fdb_tsdb.c... -compiling fal.c... compiling fdb_kvdb.c... +compiling fdb_tsdb.c... +compiling fdb_utils.c... +compiling fal.c... compiling fal_flash.c... -compiling croutine.c... compiling fal_partition.c... -compiling list.c... +compiling croutine.c... compiling event_groups.c... -compiling stream_buffer.c... -compiling queue.c... -compiling timers.c... -compiling tasks.c... -compiling port.c... -compiling heap_4.c... -compiling stm32f4xx_hal_pwr.c... -compiling stm32f4xx_hal_i2c_ex.c... -compiling stm32f4xx_hal_pwr_ex.c... +compiling list.c... +compiling stm32f4xx_hal_dma.c... compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_i2c_ex.c... compiling stm32f4xx_hal_exti.c... -compiling fal_flash_gd5f2gq5ue.c... -compiling stm32f4xx_hal.c... +compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_pwr_ex.c... compiling stm32f4xx_hal_cortex.c... -compiling rs485.c... +compiling stm32f4xx_hal.c... compiling sd2506.c... +compiling rs485.c... +compiling stm32f4xx_hal_dma_ex.c... +compiling fal_flash_gd5f2gq5ue.c... compiling tpafe5160.c... -compiling net_select.c... -compiling ch395f_test.c... compiling ch395f.c... +compiling nand_ftl.c... compiling gd5f2gq5ue.c... compiling stm32f4xx_hal_spi.c... -compiling net_socket.c... +compiling ch395f_test.c... compiling stm32f4xx_hal_uart.c... +compiling queue.c... +compiling net_select.c... +compiling stream_buffer.c... +compiling timers.c... +compiling heap_4.c... +compiling net_socket.c... +compiling map.c... +compiling tasks.c... +compiling journal.c... +compiling port.c... compiling stm32f4xx_hal_i2c.c... +compiling ff.c... compiling cmsis_os2.c... linking... -Program Size: Code=29372 RO-data=888 RW-data=180 ZI-data=41420 +Program Size: Code=58468 RO-data=3488 RW-data=248 ZI-data=54792 FromELF: creating hex file... ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). -Build Time Elapsed: 00:00:16 +Build Time Elapsed: 00:00:19 diff --git a/MDK-ARM/err.txt b/MDK-ARM/err.txt new file mode 100644 index 0000000..e69de29 diff --git a/MDK-ARM/out.txt b/MDK-ARM/out.txt new file mode 100644 index 0000000..e69de29 diff --git a/STM32F407-Demo.ioc b/STM32F407-Demo.ioc index 03bac96..580b20b 100644 --- a/STM32F407-Demo.ioc +++ b/STM32F407-Demo.ioc @@ -6,7 +6,29 @@ Dma.Request0=USART1_TX Dma.Request1=USART1_RX Dma.Request2=SPI2_RX Dma.Request3=SPI2_TX -Dma.RequestsNb=4 +Dma.Request4=SPI1_RX +Dma.Request5=SPI1_TX +Dma.RequestsNb=6 +Dma.SPI1_RX.4.Direction=DMA_PERIPH_TO_MEMORY +Dma.SPI1_RX.4.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.SPI1_RX.4.Instance=DMA2_Stream0 +Dma.SPI1_RX.4.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.SPI1_RX.4.MemInc=DMA_MINC_ENABLE +Dma.SPI1_RX.4.Mode=DMA_NORMAL +Dma.SPI1_RX.4.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.SPI1_RX.4.PeriphInc=DMA_PINC_DISABLE +Dma.SPI1_RX.4.Priority=DMA_PRIORITY_LOW +Dma.SPI1_RX.4.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +Dma.SPI1_TX.5.Direction=DMA_MEMORY_TO_PERIPH +Dma.SPI1_TX.5.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.SPI1_TX.5.Instance=DMA2_Stream3 +Dma.SPI1_TX.5.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.SPI1_TX.5.MemInc=DMA_MINC_ENABLE +Dma.SPI1_TX.5.Mode=DMA_NORMAL +Dma.SPI1_TX.5.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.SPI1_TX.5.PeriphInc=DMA_PINC_DISABLE +Dma.SPI1_TX.5.Priority=DMA_PRIORITY_LOW +Dma.SPI1_TX.5.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode Dma.SPI2_RX.2.Direction=DMA_PERIPH_TO_MEMORY Dma.SPI2_RX.2.FIFOMode=DMA_FIFOMODE_DISABLE Dma.SPI2_RX.2.Instance=DMA1_Stream3 @@ -48,7 +70,8 @@ Dma.USART1_TX.0.PeriphInc=DMA_PINC_DISABLE Dma.USART1_TX.0.Priority=DMA_PRIORITY_LOW Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode FREERTOS.FootprintOK=true -FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE +FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE,Queues01 +FREERTOS.Queues01=netMsgQueue,8,net_msg_t,0,Dynamic,NULL,NULL FREERTOS.Tasks01=defaultTask,24,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;netTask,24,1024,StartNetTask,Default,NULL,Dynamic,NULL,NULL FREERTOS.configENABLE_FPU=1 FREERTOS.configTOTAL_HEAP_SIZE=30720 @@ -141,7 +164,9 @@ MxDb.Version=DB.6.0.170 NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false NVIC.DMA1_Stream3_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DMA1_Stream4_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true +NVIC.DMA2_Stream0_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DMA2_Stream2_IRQn=true\:9\:0\:true\:false\:true\:true\:false\:true\:true +NVIC.DMA2_Stream3_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DMA2_Stream7_IRQn=true\:9\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false NVIC.EXTI9_5_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:true\:true @@ -151,6 +176,7 @@ NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\: NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false NVIC.PendSV_IRQn=true\:15\:0\:false\:false\:false\:true\:false\:false\:false NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SPI1_IRQn=true\:7\:0\:true\:false\:true\:true\:true\:true\:true NVIC.SPI2_IRQn=true\:7\:0\:true\:false\:true\:true\:true\:true\:true NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:false\:false\:false\:false\:false NVIC.SavedPendsvIrqHandlerGenerated=true @@ -301,9 +327,10 @@ PCC.PartNumber=STM32F407ZGTx PCC.Series=STM32F4 PCC.Temperature=25 PCC.Vdd=3.3 -PD0.GPIOParameters=GPIO_PuPd,GPIO_Label +PD0.GPIOParameters=GPIO_Speed,GPIO_PuPd,GPIO_Label PD0.GPIO_Label=ST_DIR4 PD0.GPIO_PuPd=GPIO_NOPULL +PD0.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH PD0.Locked=true PD0.Signal=GPIO_Output PD1.GPIOParameters=GPIO_Label @@ -524,8 +551,8 @@ RCC.VCOOutputFreq_Value=336000000 RCC.VcooutputI2S=192000000 SH.GPXTI7.0=GPIO_EXTI7 SH.GPXTI7.ConfNb=1 -SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8 -SPI1.CalculateBaudRate=10.5 MBits/s +SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_2 +SPI1.CalculateBaudRate=42.0 MBits/s SPI1.Direction=SPI_DIRECTION_2LINES SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler SPI1.Mode=SPI_MODE_MASTER @@ -551,3 +578,4 @@ VP_FREERTOS_VS_CMSIS_V2.Signal=FREERTOS_VS_CMSIS_V2 VP_SYS_VS_tim7.Mode=TIM7 VP_SYS_VS_tim7.Signal=SYS_VS_tim7 board=custom +rtos.0.ip=FREERTOS diff --git a/Src/dma.c b/Src/dma.c index a4a8b5c..9b224df 100644 --- a/Src/dma.c +++ b/Src/dma.c @@ -50,9 +50,15 @@ void MX_DMA_Init(void) /* DMA1_Stream4_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 7, 0); HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); + /* DMA2_Stream0_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 7, 0); + HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); /* DMA2_Stream2_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 9, 0); HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); + /* DMA2_Stream3_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 7, 0); + HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn); /* DMA2_Stream7_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 9, 0); HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn); diff --git a/Src/freertos.c b/Src/freertos.c index a4a4f98..60df5ce 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -25,9 +25,14 @@ /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ +#include #include "net_task.h" #include "ch395f.h" #include "dbg_log.h" +#include "net_socket.h" +#include "usart.h" +#include "ff.h" +#include "flashdb.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -38,6 +43,29 @@ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ +/* 存储测试统计 */ +typedef struct { + uint16_t total; + uint16_t passed; + uint16_t failed; +} storage_test_stats_t; + +#define STORAGE_TEST_CHECK(cond, fmt, ...) do { \ + g_storage_stats.total++; \ + if (cond) { \ + g_storage_stats.passed++; \ + DBG_INFO(" [PASS] " fmt, ##__VA_ARGS__); \ + } else { \ + g_storage_stats.failed++; \ + DBG_ERROR(" [FAIL] " fmt, ##__VA_ARGS__); \ + } \ +} while (0) + +#define STORAGE_TEST_REPORT(name) do { \ + DBG_INFO("=== %s: %d/%d PASSED (failed=%d) ===", \ + name, g_storage_stats.passed, g_storage_stats.total, g_storage_stats.failed); \ +} while (0) + /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ @@ -47,7 +75,9 @@ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN Variables */ - +static struct fdb_kvdb s_kvdb; +static storage_test_stats_t g_storage_stats; +static uint8_t s_perf_buf[4096]; /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; @@ -60,9 +90,14 @@ const osThreadAttr_t defaultTask_attributes = { osThreadId_t netTaskHandle; const osThreadAttr_t netTask_attributes = { .name = "netTask", - .stack_size = 1024 * 8, + .stack_size = 1024 * 4, .priority = (osPriority_t) osPriorityNormal, }; +/* Definitions for netMsgQueue */ +osMessageQueueId_t netMsgQueueHandle; +const osMessageQueueAttr_t netMsgQueue_attributes = { + .name = "netMsgQueue" +}; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ @@ -96,6 +131,10 @@ void MX_FREERTOS_Init(void) { /* start timers, add new ones, ... */ /* USER CODE END RTOS_TIMERS */ + /* Create the queue(s) */ + /* creation of netMsgQueue */ + netMsgQueueHandle = osMessageQueueNew (8, sizeof(net_msg_t), &netMsgQueue_attributes); + /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ /* USER CODE END RTOS_QUEUES */ @@ -127,20 +166,147 @@ void MX_FREERTOS_Init(void) { void StartDefaultTask(void *argument) { /* USER CODE BEGIN StartDefaultTask */ - /* Infinite loop */ - for(;;) + + static FATFS fs; + static FIL fil; + static uint8_t work[FF_MAX_SS]; + char read_buf[64]; + const char *test_str = "Hello FatFS + dhara FTL!"; + fdb_err_t fdb_ret; + + memset(&g_storage_stats, 0, sizeof(g_storage_stats)); + + /* ==================== FTL + FatFS 文件系统测试 ==================== */ + + DBG_INFO("=== Storage Test: FTL + FatFS ==="); + + FRESULT res = f_mount(&fs, "", 1); + if (res == FR_NO_FILESYSTEM) { + MKFS_PARM opts = {FM_FAT32, 0, 0, 0, 0}; + res = f_mkfs("", &opts, work, sizeof(work)); + STORAGE_TEST_CHECK(res == FR_OK, "f_mkfs"); + res = f_mount(&fs, "", 1); + } + STORAGE_TEST_CHECK(res == FR_OK, "f_mount"); + + if (res == FR_OK) { + UINT bw, br; + + res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE); + STORAGE_TEST_CHECK(res == FR_OK, "f_open(write)"); + if (res == FR_OK) { + f_write(&fil, test_str, strlen(test_str), &bw); + STORAGE_TEST_CHECK(bw == strlen(test_str), "f_write (%u bytes)", bw); + f_close(&fil); + + res = f_open(&fil, "test.txt", FA_READ); + STORAGE_TEST_CHECK(res == FR_OK, "f_open(read)"); + if (res == FR_OK) { + f_read(&fil, read_buf, sizeof(read_buf) - 1, &br); + f_close(&fil); + read_buf[br] = '\0'; + STORAGE_TEST_CHECK(br == strlen(test_str) && memcmp(test_str, read_buf, br) == 0, + "data verify \"%s\"", read_buf); + } + } + f_unlink("test.txt"); + } + + /* ==================== FlashDB KVDB 测试 ==================== */ + + DBG_INFO("=== Storage Test: FlashDB KVDB ==="); + + fdb_ret = fdb_kvdb_init(&s_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL); + STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kvdb_init"); + + if (fdb_ret == FDB_NO_ERR) { + fdb_ret = fdb_kv_set(&s_kvdb, "hello", "FlashDB KVDB!"); + STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kv_set(hello)"); + + char *val = fdb_kv_get(&s_kvdb, "hello"); + STORAGE_TEST_CHECK(val != NULL && strcmp(val, "FlashDB KVDB!") == 0, + "fdb_kv_get(hello) -> \"%s\"", val ? val : "NULL"); + + uint32_t blob_data = 0xDEADBEEF; + struct fdb_blob blob; + fdb_blob_make(&blob, &blob_data, sizeof(blob_data)); + fdb_ret = fdb_kv_set_blob(&s_kvdb, "counter", &blob); + STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kv_set_blob(counter)"); + + uint32_t read_data = 0; + struct fdb_blob read_blob; + fdb_blob_make(&read_blob, &read_data, sizeof(read_data)); + size_t len = fdb_kv_get_blob(&s_kvdb, "counter", &read_blob); + STORAGE_TEST_CHECK(len == sizeof(read_data) && read_data == 0xDEADBEEF, + "fdb_kv_get_blob(counter) -> 0x%08lX", (unsigned long)read_data); + + fdb_kv_del(&s_kvdb, "hello"); + fdb_kv_del(&s_kvdb, "counter"); + } + + /* ==================== FTL + FatFS 性能测试 ==================== */ + + DBG_INFO("=== Storage Test: Performance ==="); + { - osDelay(1); + FIL pfile; + UINT pw, pr; + uint32_t t0, tw, tr; + uint32_t i; + uint32_t total_kb = 128; + uint32_t chunk = sizeof(s_perf_buf); + uint32_t count = (total_kb * 1024) / chunk; + + memset(s_perf_buf, 0xA5, sizeof(s_perf_buf)); + + /* 写入 */ + t0 = HAL_GetTick(); + if (f_open(&pfile, "perf.dat", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) { + for (i = 0; i < count; i++) { + f_write(&pfile, s_perf_buf, chunk, &pw); + } + f_close(&pfile); + } + tw = HAL_GetTick() - t0; + + /* 读取 */ + t0 = HAL_GetTick(); + if (f_open(&pfile, "perf.dat", FA_READ) == FR_OK) { + for (i = 0; i < count; i++) { + f_read(&pfile, s_perf_buf, chunk, &pr); + } + f_close(&pfile); + } + tr = HAL_GetTick() - t0; + + f_unlink("perf.dat"); + + if (tw > 0) { + DBG_INFO(" Write: %lu KB in %lu ms = %lu KB/s", + total_kb, tw, (total_kb * 1000) / tw); + } + if (tr > 0) { + DBG_INFO(" Read: %lu KB in %lu ms = %lu KB/s", + total_kb, tr, (total_kb * 1000) / tr); + } + } + + /* ==================== 汇总报告 ==================== */ + + STORAGE_TEST_REPORT("Storage Tests"); + + for (;;) { + osDelay(1000); } /* USER CODE END StartDefaultTask */ } /* USER CODE BEGIN Header_StartNetTask */ /** - * @brief Function implementing the netTask thread. - * @param argument: Not used - * @retval None - */ + * @brief Function implementing the netTask thread. + * @param argument: Not used + * @retval None + */ /* USER CODE END Header_StartNetTask */ void StartNetTask(void *argument) { diff --git a/Src/gpio.c b/Src/gpio.c index 37094b2..d62e88e 100644 --- a/Src/gpio.c +++ b/Src/gpio.c @@ -128,12 +128,12 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(CH395_SCS_GPIO_Port, &GPIO_InitStruct); - /*Configure GPIO pin : ST_DIR4_Pin */ - GPIO_InitStruct.Pin = ST_DIR4_Pin; + /*Configure GPIO pins : ST_DIR4_Pin TP_RD_Pin TP_CONVST_Pin */ + GPIO_InitStruct.Pin = ST_DIR4_Pin|TP_RD_Pin|TP_CONVST_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(ST_DIR4_GPIO_Port, &GPIO_InitStruct); + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); /*Configure GPIO pin : TP_FRSTDATA_Pin */ GPIO_InitStruct.Pin = TP_FRSTDATA_Pin; @@ -141,13 +141,6 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(TP_FRSTDATA_GPIO_Port, &GPIO_InitStruct); - /*Configure GPIO pins : TP_RD_Pin TP_CONVST_Pin */ - GPIO_InitStruct.Pin = TP_RD_Pin|TP_CONVST_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); - /*Configure GPIO pin : TP_BUSY_Pin */ GPIO_InitStruct.Pin = TP_BUSY_Pin; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; diff --git a/Src/main.c b/Src/main.c index 885a379..d403531 100644 --- a/Src/main.c +++ b/Src/main.c @@ -37,6 +37,8 @@ #include "net_socket.h" #include "net_select.h" #include "ch395f_test.h" +#include "app_main.h" +#include "sys_clock.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -118,6 +120,21 @@ int main(void) /* USER CODE BEGIN 2 */ HAL_Delay(100); + /* + * 时间子系统初始化(尽早完成,后续所有日志带时间戳) + * sd2506_init() 从 RTC 读取初始时间 + * sys_clock_init() 将 RTC 时间转为 Unix epoch,建立墙钟基准 + */ + app_main_init(); + sd2506_init(); + sys_clock_init(); + + /* RS-485 初始化(UART5 + PD0 方向控制) */ + rs485_init(&g_rs485, &huart5, ST_DIR4_GPIO_Port, ST_DIR4_Pin); + DBG_INFO("RS485 init: OK (UART5, DE=PD0)"); + + /* 启动第一次 RS-485 接收 */ + rs485_receive_start(&g_rs485, g_rs485_rx_buf, RS485_RX_BUF_SIZE); /* BSD Socket API 初始化(包含 CH395F 复位/配置/协议栈初始化/多连接模式) */ DBG_INFO("net init: %s", (net_init("192.168.1.100", "255.255.255.0", "192.168.1.1") == 0) ? "OK" : "FAIL"); @@ -155,12 +172,8 @@ int main(void) DBG_INFO("ADC init: %s", (tp_ret == TPAFE5160_OK) ? "OK" : "TIMEOUT"); - /* SD2506 RTC 初始化测试 */ - int sd_ret = sd2506_init(); - DBG_INFO("SD2506 init: %s", - (sd_ret == SD2506_OK) ? "OK" : "I2C ERROR"); - - if (sd_ret == SD2506_OK) { + /* SD2506 RTC 诊断信息 */ + { /* 读取 ID */ uint8_t sd_id[8]; sd2506_get_id(sd_id); @@ -188,13 +201,6 @@ int main(void) sd_vbat / 1000, (sd_vbat % 1000) / 10); } - /* RS-485 初始化(UART5 + PD0 方向控制) */ - rs485_init(&g_rs485, &huart5, ST_DIR4_GPIO_Port, ST_DIR4_Pin); - DBG_INFO("RS485 init: OK (UART5, DE=PD0)"); - - /* 启动第一次 RS-485 接收 */ - rs485_receive_start(&g_rs485, g_rs485_rx_buf, RS485_RX_BUF_SIZE); - /* 创建 TCP Server Echo 测试 */ { s_listen_sock = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0); @@ -256,10 +262,8 @@ int main(void) /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ - /* 网络轮询已移到 FreeRTOS netTask 中 */ - ; - /* USER CODE END 3 */ } + /* USER CODE END 3 */ } /** diff --git a/Src/spi.c b/Src/spi.c index 9ede488..bd35db9 100644 --- a/Src/spi.c +++ b/Src/spi.c @@ -26,6 +26,8 @@ SPI_HandleTypeDef hspi1; SPI_HandleTypeDef hspi2; +DMA_HandleTypeDef hdma_spi1_rx; +DMA_HandleTypeDef hdma_spi1_tx; DMA_HandleTypeDef hdma_spi2_rx; DMA_HandleTypeDef hdma_spi2_tx; @@ -47,7 +49,7 @@ void MX_SPI1_Init(void) hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; - hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; + hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; @@ -119,6 +121,46 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + /* SPI1 DMA Init */ + /* SPI1_RX Init */ + hdma_spi1_rx.Instance = DMA2_Stream0; + hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3; + hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE; + hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + hdma_spi1_rx.Init.Mode = DMA_NORMAL; + hdma_spi1_rx.Init.Priority = DMA_PRIORITY_LOW; + hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK) + { + Error_Handler(); + } + + __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi1_rx); + + /* SPI1_TX Init */ + hdma_spi1_tx.Instance = DMA2_Stream3; + hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3; + hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; + hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE; + hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + hdma_spi1_tx.Init.Mode = DMA_NORMAL; + hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW; + hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK) + { + Error_Handler(); + } + + __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi1_tx); + + /* SPI1 interrupt Init */ + HAL_NVIC_SetPriority(SPI1_IRQn, 7, 0); + HAL_NVIC_EnableIRQ(SPI1_IRQn); /* USER CODE BEGIN SPI1_MspInit 1 */ /* USER CODE END SPI1_MspInit 1 */ @@ -215,6 +257,12 @@ void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle) */ HAL_GPIO_DeInit(GPIOB, GD_SCLK_Pin|GD_SO_Pin|GD_SI_Pin); + /* SPI1 DMA DeInit */ + HAL_DMA_DeInit(spiHandle->hdmarx); + HAL_DMA_DeInit(spiHandle->hdmatx); + + /* SPI1 interrupt Deinit */ + HAL_NVIC_DisableIRQ(SPI1_IRQn); /* USER CODE BEGIN SPI1_MspDeInit 1 */ /* USER CODE END SPI1_MspDeInit 1 */ diff --git a/Src/stm32f4xx_it.c b/Src/stm32f4xx_it.c index bfcf2dd..c30bc10 100644 --- a/Src/stm32f4xx_it.c +++ b/Src/stm32f4xx_it.c @@ -55,8 +55,11 @@ /* USER CODE END 0 */ /* External variables --------------------------------------------------------*/ +extern DMA_HandleTypeDef hdma_spi1_rx; +extern DMA_HandleTypeDef hdma_spi1_tx; extern DMA_HandleTypeDef hdma_spi2_rx; extern DMA_HandleTypeDef hdma_spi2_tx; +extern SPI_HandleTypeDef hspi1; extern SPI_HandleTypeDef hspi2; extern DMA_HandleTypeDef hdma_usart1_tx; extern DMA_HandleTypeDef hdma_usart1_rx; @@ -211,6 +214,20 @@ void EXTI9_5_IRQHandler(void) /* USER CODE END EXTI9_5_IRQn 1 */ } +/** + * @brief This function handles SPI1 global interrupt. + */ +void SPI1_IRQHandler(void) +{ + /* USER CODE BEGIN SPI1_IRQn 0 */ + + /* USER CODE END SPI1_IRQn 0 */ + HAL_SPI_IRQHandler(&hspi1); + /* USER CODE BEGIN SPI1_IRQn 1 */ + + /* USER CODE END SPI1_IRQn 1 */ +} + /** * @brief This function handles SPI2 global interrupt. */ @@ -309,6 +326,20 @@ void TIM7_IRQHandler(void) /* USER CODE END TIM7_IRQn 1 */ } +/** + * @brief This function handles DMA2 stream0 global interrupt. + */ +void DMA2_Stream0_IRQHandler(void) +{ + /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */ + + /* USER CODE END DMA2_Stream0_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_spi1_rx); + /* USER CODE BEGIN DMA2_Stream0_IRQn 1 */ + + /* USER CODE END DMA2_Stream0_IRQn 1 */ +} + /** * @brief This function handles DMA2 stream2 global interrupt. */ @@ -323,6 +354,20 @@ void DMA2_Stream2_IRQHandler(void) /* USER CODE END DMA2_Stream2_IRQn 1 */ } +/** + * @brief This function handles DMA2 stream3 global interrupt. + */ +void DMA2_Stream3_IRQHandler(void) +{ + /* USER CODE BEGIN DMA2_Stream3_IRQn 0 */ + + /* USER CODE END DMA2_Stream3_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_spi1_tx); + /* USER CODE BEGIN DMA2_Stream3_IRQn 1 */ + + /* USER CODE END DMA2_Stream3_IRQn 1 */ +} + /** * @brief This function handles DMA2 stream7 global interrupt. */ diff --git a/docs/BSD_Socket_API_使用指南.md b/docs/BSD_Socket_API_使用指南.md index 9b92a54..48b704c 100644 --- a/docs/BSD_Socket_API_使用指南.md +++ b/docs/BSD_Socket_API_使用指南.md @@ -4,15 +4,16 @@ 1. [概述](#1-概述) 2. [架构说明](#2-架构说明) -3. [快速开始](#3-快速开始) -4. [TCP 编程](#4-tcp-编程) -5. [UDP 编程](#5-udp-编程) -6. [I/O 多路复用](#6-io-多路复用) -7. [事件回调机制](#7-事件回调机制) -8. [字节序处理](#8-字节序处理) -9. [API 参考](#9-api-参考) -10. [完整示例](#10-完整示例) -11. [常见问题](#11-常见问题) +3. [线程安全设计](#3-线程安全设计) +4. [快速开始](#4-快速开始) +5. [TCP 编程](#5-tcp-编程) +6. [UDP 编程](#6-udp-编程) +7. [I/O 多路复用](#7-io-多路复用) +8. [事件回调机制](#8-事件回调机制) +9. [字节序处理](#9-字节序处理) +10. [API 参考](#10-api-参考) +11. [完整示例](#11-完整示例) +12. [常见问题](#12-常见问题) --- @@ -46,32 +47,42 @@ ## 2. 架构说明 +### 2.1 分层架构 + ``` -┌─────────────────────────────────────────────────────────┐ -│ 应用层 (App) │ -├─────────────────────────────────────────────────────────┤ -│ net_socket.h (BSD Socket API) │ ← 用户接口 -├─────────────────────────────────────────────────────────┤ -│ net_socket.c + net_select.c (状态机+select) │ ← 核心逻辑 -├─────────────────────────────────────────────────────────┤ -│ ch395f.c/h (底层驱动) │ ← 硬件驱动 -├─────────────────────────────────────────────────────────┤ -│ CH395F 硬件 │ -└─────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────┐ +│ 应用层任务 (多个 FreeRTOS Task) │ +│ net_send() / net_recv() / net_close() (线程安全封装) │ +├──────────────────────────────────────────────────────────────┤ +│ 消息队列 (osMessageQueue) │ +│ 由 CubeMX 生成,在 MX_FREERTOS_Init() 中创建 │ +├──────────────────────────────────────────────────────────────┤ +│ netTask (网络任务线程) │ +│ net_process_messages() + net_poll() — 串行化执行 │ +│ net_send_sock() / net_recv_sock() / net_close_sock() │ +├──────────────────────────────────────────────────────────────┤ +│ net_socket.c (BSD Socket API 核心) │ +│ Socket 状态机 + CH395F 命令封装 │ +├──────────────────────────────────────────────────────────────┤ +│ ch395f.c/h (底层 SPI 驱动) │ +│ 所有 SPI 事务必须由 netTask 发起 │ +├──────────────────────────────────────────────────────────────┤ +│ CH395F 硬件 (SPI2) │ +└──────────────────────────────────────────────────────────────┘ ``` -### 2.1 文件说明 +### 2.2 文件说明 | 文件 | 说明 | |------|------| | `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) | | `net_types.h` | 类型定义(地址结构、Socket 控制块等) | -| `net_socket.h` | BSD Socket API 头文件 | -| `net_socket.c` | API 核心实现 | +| `net_socket.h` | BSD Socket API 头文件,含 `net_msg_t` 消息类型定义 | +| `net_socket.c` | API 核心实现,含消息队列处理、线程安全封装、内部 `_sock()` 版本 | | `net_select.h` | select/poll API 头文件 | | `net_select.c` | select/poll 实现 | -### 2.1a 底层驱动改进(v2.1) +### 2.3 底层驱动改进(v2.1) ch395f.c/h 已完成以下优化: @@ -84,7 +95,7 @@ ch395f.c/h 已完成以下优化: | 新增函数 | `ch395f_set_arp()` / `ch395f_set_ttl()` / `ch395f_get_unreach_info()` / `ch395f_poll_cmd_status()` | | KeepAlive | 参数单位为毫秒(非秒),必须为 500 的倍数 | -### 2.2 Socket 状态机 +### 2.4 Socket 状态机 ``` net_socket() @@ -120,16 +131,86 @@ ch395f.c/h 已完成以下优化: --- -## 3. 快速开始 +## 3. 线程安全设计 -### 3.1 包含头文件 +### 3.1 设计背景 + +NET 层(`net_socket.c` + `ch395f.c`)不是线程安全的: +- Socket 控制块(`s_net_socks[]`)无互斥保护 +- CH395F SPI 事务不重入(STM32 HAL SPI 无内部锁) +- 状态机在多个任务中并发访问会崩溃 + +引入消息队列将所有的 CH395F 操作串行化到 `netTask` 中执行。 + +### 3.2 消息队列模式 + +``` + App Task A App Task B netTask + | | | + net_send() net_recv() net_process_messages() + | | + net_poll() + ▼ ▼ │ + ┌─────────┐ ┌─────────┐ │ + │send msg │ │recv msg │ │ + └────┬────┘ └────┬────┘ │ + │ │ │ + └─────────┬─────────┘ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────────┐ + │ 消息队列 │──────────│ 取出消息 │ + │ osMessageQueue │ dequeue │ 执行操作 │ + └──────────────────┘ │ 写结果 │ + ▲ │ 通知调用方 │ + │ └──────┬───────┘ + │ ulTaskNotifyTake() │ + │ (阻塞等待结果) │ xTaskNotifyGive() + │ │ + ┌─────┴──────┐ │ + │ 返回结果给 │◄──────────────────────┘ + │ 调用方 │ + └────────────┘ +``` + +### 3.3 关键规则 + +| 规则 | 说明 | +|------|------| +| **`netTask` 外调用** | 使用 `net_send()` / `net_recv()` / `net_close()` 公共 API | +| **`netTask` 内调用** | 使用 `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` 内部 API | +| **创建/绑定/监听** | 在 `main()` 初始化阶段完成(`osKernelStart()` 之前),无需队列 | +| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环中调用 | + +### 3.4 内部 API 说明 + +内部 API(带 `_sock` 后缀)直接操作 Socket 控制块和 CH395F,不经过消息队列。用于 `netTask` 中的网络业务逻辑,避免向自身发送消息造成死锁: + +```c +/* 在 netTask 中直接操作 Socket,绕过消息队列 */ +int net_send_sock(net_sock_t *p_sock, const void *buf, int len); +int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags); +int net_close_sock(net_sock_t *p_sock); +``` + +### 3.5 注意事项 + +- **消息队列由 CubeMX 创建**:在 `MX_FREERTOS_Init()`(`osKernelInitialize()` 之后)中调用 `osMessageQueueNew(8, sizeof(net_msg_t), NULL)` 创建 +- **队列句柄全局可见**:通过 `extern osMessageQueueId_t netMsgQueueHandle` 引用 +- **队列满时**:`net_send()` / `net_recv()` 会阻塞等待,直到 `netTask` 处理完消息腾出空间 +- **`netTask` 不可向自身发消息**:`netTask` 中直接调 `net_send()` 会死锁(等待自己处理消息),必须在 `netTask` 中使用 `net_send_sock()` 等内部版本 +- **`net_socket()` / `net_bind()` / `net_listen()` 无需线程安全**:这些仅在初始化阶段调用,消息队列仅保护数据收发和连接关闭 + +--- + +## 4. 快速开始 + +### 4.1 包含头文件 ```c #include "net_socket.h" #include "net_select.h" ``` -### 3.2 初始化网络 +### 4.2 初始化网络 ```c int main(void) @@ -154,29 +235,55 @@ int main(void) } ``` -### 3.3 主循环要求 +### 4.3 主循环要求 -**重要**:`net_poll()` 必须在主循环中定期调用,它负责: -- 轮询 CH395F INT# 引脚电平(GPIO 轮询,无需 EXTI 中断) -- 通过 `GET_GLOB_INT_STATUS_ALL`(2字节版)读取所有 8 个 Socket 的中断状态 -- 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT) -- 触发事件回调 -- 处理数据接收 +**重要**:本项目使用 FreeRTOS 多任务架构,`net_poll()` 和 `net_process_messages()` 均在 `netTask`(FreeRTOS 线程)中定期调用,而非主循环。 + +- **主循环**:系统初始化完成后进入空循环,等待 `osKernelStart()` 启动调度器 +- **netTask**:在任务函数中持续运行,每 10ms 调用一次 `net_poll()` + `net_process_messages()`,负责: + - 轮询 CH395F INT# 引脚电平(GPIO 轮询,无需 EXTI 中断) + - 通过 `GET_GLOB_INT_STATUS_ALL`(2字节版)读取所有 8 个 Socket 的中断状态 + - 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT) + - 触发事件回调 + - 处理数据接收 ```c -while (1) +int main(void) { - net_poll(); /* 建议至少每 10ms 调用一次 */ + /* 外设初始化... */ - /* 其他应用代码... */ + /* BSD Socket API 初始化 */ + net_init("192.168.1.100", "255.255.255.0", "192.168.1.1"); + + /* 创建 Socket、绑定、监听... */ + + osKernelInitialize(); + MX_FREERTOS_Init(); /* 创建 netTask 等 FreeRTOS 对象 */ + osKernelStart(); /* 启动调度器 */ + + while (1) { } /* 空循环,控制流已交给调度器 */ +} + +/* netTask 中(freertos.c): */ +void StartNetTask(void *argument) +{ + for (;;) + { + net_poll(); /* CH395F 中断轮询 + Socket 状态更新 */ + net_process_messages(); /* 处理其他任务发送的网络操作请求 */ + + /* 应用层网络业务逻辑... */ + + osDelay(10); /* 10ms 周期 */ + } } ``` --- -## 4. TCP 编程 +## 5. TCP 编程 -### 4.1 TCP Server(单连接) +### 5.1 TCP Server(单连接) 单连接模式下,1 个 Socket 既做监听又做数据通信: @@ -258,7 +365,7 @@ void tcp_server_single(void) } ``` -### 4.2 TCP Server(多连接) +### 5.2 TCP Server(多连接) 多连接模式使用 1 个监听 Socket + N 个数据 Socket,支持并发连接: @@ -348,7 +455,7 @@ void tcp_server_multi(void) } ``` -### 4.3 TCP Client +### 5.3 TCP Client ```c #include "net_socket.h" @@ -413,9 +520,9 @@ void tcp_client(void) --- -## 5. UDP 编程 +## 6. UDP 编程 -### 5.1 UDP Client +### 6.1 UDP Client ```c #include "net_socket.h" @@ -472,7 +579,7 @@ void udp_client(void) } ``` -### 5.2 UDP Server +### 6.2 UDP Server ```c #include "net_socket.h" @@ -531,9 +638,9 @@ void udp_server(void) --- -## 6. I/O 多路复用 +## 7. I/O 多路复用 -### 6.1 select 使用 +### 7.1 select 使用 `net_select()` 可以同时监控多个 Socket 的读写事件: @@ -578,7 +685,7 @@ void select_example(void) } ``` -### 6.2 poll 使用 +### 7.2 poll 使用 `net_poll_events()` 使用 `pollfd` 结构体数组: @@ -616,7 +723,7 @@ void poll_example(void) } ``` -### 6.3 事件掩码说明 +### 7.3 事件掩码说明 | 事件 | 说明 | |------|------| @@ -628,11 +735,11 @@ void poll_example(void) --- -## 7. 事件回调机制 +## 8. 事件回调机制 可以通过注册回调函数,异步获取 Socket 事件通知: -### 7.1 定义回调函数 +### 8.1 定义回调函数 ```c #include "net_socket.h" @@ -669,7 +776,7 @@ void my_event_callback(int sockfd, net_event_t event, void *arg) } ``` -### 7.2 注册回调 +### 8.2 注册回调 ```c void event_callback_example(void) @@ -691,11 +798,11 @@ void event_callback_example(void) --- -## 8. 字节序处理 +## 9. 字节序处理 网络协议使用**大端序**(网络字节序),而 STM32 使用**小端序**(主机字节序)。发送和接收数据时需要进行字节序转换。 -### 8.1 端口号转换 +### 9.1 端口号转换 ```c uint16_t port = 8080; @@ -707,7 +814,7 @@ uint16_t net_port = net_htons(port); uint16_t host_port = net_ntohs(net_port); ``` -### 8.2 IP 地址转换 +### 9.2 IP 地址转换 ```c /* 字符串转网络序 IP */ @@ -718,7 +825,7 @@ char ip_str[16]; net_inet_ntoa(ip, ip_str); /* ip_str = "192.168.1.100" */ ``` -### 8.3 填充地址结构体 +### 9.3 填充地址结构体 ```c struct net_sockaddr_in addr; @@ -733,7 +840,7 @@ addr.sin_addr.s_addr = net_inet_addr("0.0.0.0"); addr.sin_addr.s_addr = INADDR_ANY; ``` -### 8.4 读取地址信息 +### 9.4 读取地址信息 ```c struct net_sockaddr_in client_addr; @@ -757,9 +864,9 @@ if (client_sock >= 0) --- -## 9. API 参考 +## 10. API 参考 -### 9.1 核心 API +### 10.1 核心 API #### net_init - 网络初始化 @@ -898,7 +1005,41 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags, int net_close(int sockfd); ``` -### 9.2 辅助 API +### 10.2 消息队列 API + +#### net_process_messages - 处理消息队列 + +```c +void net_process_messages(void); +``` + +处理其他任务通过 `net_send()` / `net_recv()` / `net_close()` 发送到消息队列的请求。必须在 `netTask` 主循环中调用。 + +#### net_send_sock - 发送数据(内部版本) + +```c +int net_send_sock(net_sock_t *p_sock, const void *buf, int len); +``` + +直接操作 Socket 控制块进行发送,不经过消息队列。**只能在 `netTask` 中调用**,用于 `netTask` 内的网络业务逻辑。 + +#### net_recv_sock - 接收数据(内部版本) + +```c +int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags); +``` + +直接操作 Socket 控制块进行接收,不经过消息队列。**只能在 `netTask` 中调用**。 + +#### net_close_sock - 关闭 Socket(内部版本) + +```c +int net_close_sock(net_sock_t *p_sock); +``` + +直接操作 Socket 控制块关闭连接,不经过消息队列。**只能在 `netTask` 中调用**。 + +### 10.3 辅助 API #### net_set_event_cb - 注册事件回调 @@ -918,7 +1059,7 @@ net_sock_t *net_get_sock(int sockfd); int net_get_errno(void); ``` -### 9.3 字节序转换 +### 10.4 字节序转换 ```c uint16_t net_htons(uint16_t hostshort); /* 主机序 -> 网络序 */ @@ -929,7 +1070,7 @@ uint32_t net_inet_addr(const char *cp); /* 字符串 -> 网络序 IP */ char *net_inet_ntoa(uint32_t addr, char *buf); /* 网络序 IP -> 字符串 */ ``` -### 9.4 select/poll +### 10.5 select/poll ```c int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, @@ -938,7 +1079,7 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, int net_poll_events(net_pollfd *fds, int nfds, int timeout); ``` -### 9.5 fd_set 操作宏 +### 10.6 fd_set 操作宏 ```c NET_FD_ZERO(fdset) /* 清空集合 */ @@ -949,9 +1090,9 @@ NET_FD_ISSET(fd, fdset) /* 检查 fd 是否在集合中 */ --- -## 10. 完整示例 +## 11. 完整示例 -### 10.1 TCP Echo Server(多连接) +### 11.1 TCP Echo Server(多连接) ```c #include "net_socket.h" @@ -1047,7 +1188,7 @@ void tcp_echo_server(void) } ``` -### 10.2 TCP Client 自动重连 +### 11.2 TCP Client 自动重连 ```c #include "net_socket.h" @@ -1135,22 +1276,22 @@ void tcp_client_reconnect(void) --- -## 11. 常见问题 +## 12. 常见问题 -### 11.1 net_poll() 必须调用吗? +### 12.1 net_poll() 必须调用吗? **是的**。`net_poll()` 负责轮询 CH395F 的中断状态并更新所有 Socket 的状态。如果不调用,连接状态不会更新,数据也无法接收。 建议至少每 10ms 调用一次。 -### 11.2 Socket 数量有限制吗? +### 12.2 Socket 数量有限制吗? 是的,CH395F 最多支持 8 个 Socket(索引 0~7)。其中: - TCP Server 多连接模式:1 个监听 Socket + 最多 7 个数据 Socket - TCP Client:最多 8 个 - UDP:最多 8 个 -### 11.3 TCP Server 多连接模式有什么要求? +### 12.3 TCP Server 多连接模式有什么要求? **模式工作原理:** - CH395F 多连接模式下,Socket 0 专职监听,Socket 1~7 由芯片自动分配 @@ -1160,13 +1301,13 @@ void tcp_client_reconnect(void) **硬件要求:** - CH395F 固件版本 >= 0x44(支持 Socket 4~7) -### 11.4 发送数据有什么限制? +### 12.4 发送数据有什么限制? - 每次发送的数据长度不能超过 `NET_SEND_BUF_SIZE`(默认 4096 字节) - TCP 需要等待发送缓冲区空闲(`send_ready` 标志) - UDP 单次发送最大 1460 字节 -### 11.5 如何处理连接断开? +### 12.5 如何处理连接断开? **方式一:阻塞接收** ```c @@ -1197,7 +1338,7 @@ void callback(int sockfd, net_event_t event, void *arg) } ``` -### 11.6 select 和 poll 的区别? +### 12.6 select 和 poll 的区别? | 特性 | select | poll | |------|--------|------| @@ -1207,7 +1348,7 @@ void callback(int sockfd, net_event_t event, void *arg) 两者功能相同,推荐使用 `net_select()`,更接近 Linux 编程习惯。 -### 11.7 错误码说明 +### 12.7 错误码说明 | 错误码 | 宏定义 | 说明 | |--------|--------|------| @@ -1224,7 +1365,7 @@ void callback(int sockfd, net_event_t event, void *arg) | -10 | NET_ERR_NOSPACE | 发送缓冲区无空间 | | -11 | NET_ERR_BUSY | 操作未完成(BUSY) | -### 11.8 CH395F 命令执行状态码 +### 12.8 CH395F 命令执行状态码 CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_poll_cmd_status()`): @@ -1249,7 +1390,7 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po | 0x5F | CH395F_CMD_RET_ABORT | 命令中止 | | 0xFA | CH395F_ERR_UNKNOW | 未知错误 | -### 11.9 故障排查 +### 12.9 故障排查 **问题:TCP Client 连接超时,无 CONNECT 中断** - 检查多连接模式下数据 Socket(1~7)是否设置了 `SET_PROTO_TCP` + `SET_SOUR_PORT`(与监听端口相同) @@ -1266,3 +1407,21 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po **问题:CH395F 初始化超时** - 芯片 INIT 需要约 200ms,轮询间隔应为 20ms(过短的查询可能干扰内部处理) - 超时值建议设为 4 秒以上 + +### 12.10 线程安全问题 + +**问题:`net_send()` / `net_recv()` / `net_close()` 可以在任意任务中调用吗?** + +可以。这些公共 API 通过消息队列将请求委托给 `netTask` 串行执行,自身阻塞等待结果,是线程安全的。 + +**问题:`netTask` 内可以直接调用 `net_send()` / `net_recv()` 吗?** + +不可以。`netTask` 中必须使用 `net_send_sock()` / `net_recv_sock()` 等内部版本,否则会死锁(任务向自身发送消息,永远无人处理)。内部版本直接操作 Socket 控制块,不经过消息队列。 + +**问题:`net_socket()` / `net_bind()` / `net_listen()` 线程安全吗?** + +这些函数仅在系统初始化阶段(`osKernelStart()` 之前)调用,不存在并发访问。消息队列仅保护数据收发和连接关闭操作。 + +**问题:消息队列由谁创建?在哪里创建?** + +由 CubeMX 在 `MX_FREERTOS_Init()` 中创建(`osMessageQueueNew(8, sizeof(net_msg_t), NULL)`),在 `osKernelInitialize()` 之后执行,确保 FreeRTOS 内核已就绪。 diff --git a/docs/STM32F4-Base存储架构说明.md b/docs/STM32F4-Base存储架构说明.md new file mode 100644 index 0000000..b0d4d5e --- /dev/null +++ b/docs/STM32F4-Base存储架构说明.md @@ -0,0 +1,611 @@ +# STM32F4-Base 存储架构说明 + +## 1. 概述 + +本项目在 GD5F2GQ5UE SPI NAND Flash(256MB)上实现了四层存储软件栈: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 应用程序 (main, freertos) │ +├──────────────────────┬──────────────────┬──────────────────┤ +│ │ │ │ +│ FlashDB KVDB │ FlashDB TSDB │ FatFs │ +│ (键值数据库) │ (时序数据库) │ (文件系统) │ +│ │ │ │ +├──────────┬───────────┴──────┬───────────┴──────────────────┤ +│ │ │ │ +│ FAL 抽象层 (分区访问) │ dhara FTL │ +│ (Flash Abstraction Layer)│ (地址映射 · 磨损均衡 │ +│ 直接转发到底层驱动) │ 坏块管理 · 垃圾回收) │ +│ │ │ │ +├──────────┴──────────────────┴──────────────────────────────┤ +│ GD5F2GQ5UE NAND Flash 驱动 (底层 SPI) │ +│ 硬件 SPI · 页读写 · 块擦除 · BBT · 内部 ECC 使能 │ +├─────────────────────────────────────────────────────────────┤ +│ SPI1 硬件外设 (PB3 SCK, PB4 MISO, PB5 MOSI) │ +│ 42MHz · Mode 0 · MSB First · DMA2 (S0-RX, S3-TX) │ +│ 小包轮询(≤32B) · 页数据 DMA (>32B) │ +└─────────────────────────────────────────────────────────────┘ +``` + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 物理分区布局 (256MB) │ +│ │ +│ ┌──────────────────────┬──────────────────┬───────────────┐ │ +│ │ fdb_kvdb1 (64MB) │ fdb_tsdb1 (64MB) │ ftl_fatfs │ │ +│ │ Block 0~511 │ Block 512~1023 │ Block 1024~2047│ │ +│ │ FlashDB 键值数据库 │ FlashDB 时序数据库│ dhara + FatFS │ │ +│ └──────────────────────┴──────────────────┴───────────────┘ │ +│ 偏移: 0 64MB 128MB 256MB│ +└─────────────────────────────────────────────────────────────┘ +``` + +**两条独立路径:** +- **FlashDB 路径**:`FlashDB → FAL → gd5f2gq5ue 驱动`,FAL 是薄封装层,将 FAL 读写请求直接转发给底层 NAND 驱动。FlashDB 在 FAL 之上自行管理磨损均衡和掉电安全。 +- **FatFS 路径**:`FatFS → dhara FTL → gd5f2gq5ue 驱动`,FTL 提供 LBA 到物理页的映射、磨损均衡、垃圾回收和坏块管理,FatFS 通过标准 `disk_*` 接口访问 FTL 提供的块设备。 + +两个路径共享最底层 NAND 驱动,但各自管理不同的物理分区(FlashDB 管理 Block 0~1023,FTL 管理 Block 1024~2047),互不干扰。 + +**初始化顺序:** +``` +SPI1_Init → gd5f2gq5ue_init() → fdb_kvdb_init() [可选,FlashDB 路径] + → fdb_tsdb_init() [可选,FlashDB 路径] + → f_mount() [触发 FTL 初始化,FatFS 路径] +``` + +--- + +## 2. 硬件规格:GD5F2GQ5UE + +| 参数 | 值 | +|------|-----| +| 容量 | 256 MB (2 Gb) | +| 页大小 (主区) | 2048 bytes (2 KB) | +| 备校区 (Spare) | 64 bytes/页 | +| 每块页数 | 64 | +| 块大小 | **128 KB** (64 × 2 KB) | +| 总块数 | 2048 | +| 总页面数 | 131072 | +| 内部 ECC | 支持 (每页 8bit ECC) | +| 接口 | SPI (Mode 0, CPOL=0 CPHA=0) | +| 最高时钟 | 42 MHz | +| 读页延迟 | < 60 μs | +| 编程延迟 | < 600 μs | +| 块擦除延迟 | < 3 ms | + +**SPI 引脚分配:** + +| 信号 | GPIO | 说明 | +|------|------|------| +| CS# | PE0 | 片选 (低有效) | +| SCK | PB3 | SPI1 SCK | +| MISO | PB4 | SPI1 MISO (主入从出) | +| MOSI | PB5 | SPI1 MOSI (主出从入) | +| WP# | PB8 | 写保护 (本驱动恒拉高) | +| HOLD# | PE1 | 保持 (本驱动恒拉高) | + +--- + +## 3. 分区布局 + +三个分区物理隔离、互不重叠,配置集中在 `Drivers/BSP/GD5F2GQ5UE/fal_cfg.h:18-30`: + +```c +#define FDB_KVDB1_OFFSET 0 +#define FDB_KVDB1_SIZE (64 * 1024 * 1024) // 64MB +#define FDB_TSDB1_OFFSET (FDB_KVDB1_OFFSET + FDB_KVDB1_SIZE) +#define FDB_TSDB1_SIZE (64 * 1024 * 1024) // 64MB +#define FTL_FATFS_OFFSET (FDB_TSDB1_OFFSET + FDB_TSDB1_SIZE) // 128MB +#define FTL_FATFS_SIZE (128 * 1024 * 1024) // 128MB +``` + +### 3.1 分区对照表 + +| 分区名 | 偏移 | 大小 | 物理块范围 | 逻辑用途 | +|--------|------|------|-----------|----------| +| `fdb_kvdb1` | 0 | 64 MB | Block 0~511 | FlashDB 键值数据库 (KVDB) | +| `fdb_tsdb1` | 64 MB | 64 MB | Block 512~1023 | FlashDB 时序数据库 (TSDB) | +| `ftl_fatfs` | 128 MB | 128 MB | Block 1024~2047 | dhara FTL + FatFS | + +### 3.2 配置联动 + +所有分区边界在 `fal_cfg.h` 定义后,自动传播到各子模块: + +- **FAL 分区表** — `FAL_PART_TABLE` 宏直接引用 `FDB_KVDB1_OFFSET/SIZE`、`FDB_TSDB1_OFFSET/SIZE` +- **FTL** — `nand_ftl.c` 通过 `#include "fal_cfg.h"` 引用 `FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE` 计算 `FTL_START_BLOCK = 1024` +- **FatFS** — 通过 `disk_ioctl(GET_SECTOR_COUNT)` 获取 FTL 管理的扇区数 + +> 调整分区大小时只需修改 `fal_cfg.h` 顶部 6 个宏,所有下游模块自动适配。 + +--- + +## 4. 驱动层:gd5f2gq5ue.c/h + +### 4.1 SPI 命令集 + +| 命令 | 编码 | 用途 | +|------|------|------| +| `WRITE_ENABLE` | `0x06` | 写使能 (每个写/擦除前必须发送) | +| `WRITE_DISABLE` | `0x04` | 写禁止 | +| `GET_FEATURE` | `0x0F` | 读状态/feature 寄存器 | +| `SET_FEATURE` | `0x1F` | 写 feature 寄存器 (前需写使能) | +| `READ_ID` | `0x9F` | 读芯片 ID | +| `PAGE_READ` | `0x13` | 将页数据从存储阵列载入内部缓存 | +| `READ_FROM_CACHE` | `0x0B` | 从内部缓存读取数据 | +| `PROGRAM_LOAD` | `0x02` | 将数据写入内部缓存 | +| `PROGRAM_EXEC` | `0x10` | 将缓存数据编程到存储阵列 | +| `BLOCK_ERASE` | `0xD8` | 块擦除 (128KB) | +| `RESET` | `0xFF` | 芯片复位 | + +### 4.2 Feature 寄存器 + +| 地址 | 名称 | 说明 | +|------|------|------| +| `0xA0` | Protect | 块保护 (写入 `0x00` 解除全部保护) | +| `0xB0` | Feature | ECC 使能 (bit4=1 启用) | +| `0xC0` | Status | 状态标志 (OIP/WEL/E_FAIL/P_FAIL/ECC) | +| `0xD0` | Driver | 驱动强度 | + +### 4.3 初始化序列 + +``` +1. RESET (FFh) + └─ 等待 5ms +2. READ_ID (9Fh) + └─ 校验 MID=0xC8, DID=0x52 +3. BBT 扫描 + └─ 读每块最后一页 (page 63) spare byte 0 + └─ 非 0xFF 即出厂坏块,写入 s_bbt[] +4. SET_FEATURE (B0h=10h) — 使能内部 8bit ECC +5. SET_FEATURE (A0h=00h) — 解除全部块保护 +``` + +### 4.4 读写擦除操作 + +**页读取**(任何字节偏移均可,驱动自动定位到页): +``` +PAGE_READ (13h + 3字节行地址) → 等待 OIP 清零 → READ_FROM_CACHE (0Bh + 2字节列地址 + dummy) +``` + +**页编程**(NAND 只能将 bit 从 1→0 翻转,编程前必须擦除): +``` +WRITE_ENABLE → PROGRAM_LOAD (02h + 列地址 + 数据) → PROGRAM_EXEC (10h + 行地址) → 等待完成 → 检查 P_FAIL +``` + +**块擦除**(最小擦除单位 128KB,参数是字节地址而非块编号): +``` +WRITE_ENABLE → BLOCK_ERASE (D8h + 3字节字节地址) → 等待完成 → 检查 E_FAIL +``` + +### 4.5 DMA 传输策略(2026-07-21 新增) + +SPI1 使用 DMA2 实现页数据级别的不阻塞传输,配置如下: + +| 通道 | DMA | 流 | 通道 | 方向 | 优先级 | 模式 | +|------|-----|----|------|------|--------|------| +| SPI1_RX | DMA2 | Stream 0 | CH3 | 外设→内存 | LOW | NORMAL | +| SPI1_TX | DMA2 | Stream 3 | CH3 | 内存→外设 | LOW | NORMAL | + +**传输策略:** + +- **小包轮询(≤ 32 bytes)**:命令字、地址、状态寄存器等短数据使用 `HAL_SPI_Transmit/Receive` 轮询模式,避免 DMA 初始化开销 +- **页数据 DMA(> 32 bytes)**:`READ_FROM_CACHE` 和 `PROGRAM_LOAD` 的页数据段使用 `HAL_SPI_Receive_DMA` / `HAL_SPI_Transmit_DMA` + +**HAL 内部路由说明:** + +`HAL_SPI_Receive_DMA()` 在 2 线 Master 模式内部调用 `HAL_SPI_TransmitReceive_DMA()`,但由于 SPI 状态设置为 `HAL_SPI_STATE_BUSY_RX`,HAL 的完成回调为 `HAL_SPI_RxCpltCallback`(而非 `TxRxCpltCallback`),需确保该回调在 `gd5f2gq5ue.c` 中实现。 + +**完整回调链条:** + +| 操作 | 触发回调 | 实现位置 | +|------|---------|---------| +| `PROGRAM_LOAD` TX DMA | `HAL_SPI_TxCpltCallback` | `gd5f2gq5ue.c` | +| `READ_FROM_CACHE` RX DMA | `HAL_SPI_RxCpltCallback` | `gd5f2gq5ue.c` | +| 任意外设错误 | `HAL_SPI_ErrorCallback` | `ch395f.c`(含 SPI1 分支) | + +**性能(顺序 128KB 读写,FatFS + FTL):** + +| 模式 | 写 | 读 | +|------|----|----| +| 轮询(改造前) | 330 KB/s | 547 KB/s | +| DMA(改造后) | **831 KB/s** | **1855 KB/s** | +| 42MHz SPI 理论极限 | ~5.25 MB/s(受 NAND tPROG ≈ 500μs/页 限制) | ~5.25 MB/s | + +DMA 消除了轮询模式下 SPI 状态寄存器查检的逐字节 CPU 开销,读写性能分别提升 **2.5×** 和 **3.4×**。进一步优化需考虑 Cache Read 模式(重叠 NAND 内部 tR 延迟)或批量编程(减少 tPROG 次数)。 + +### 4.6 BBT (Bad Block Table) + +- 初始化时扫描全部 2048 块最后一页的 spare byte 0 +- `s_bbt[256]` 位图数组,1 bit 标识 1 个块 (0=好, 1=坏) +- `gd5f2gq5ue_is_block_bad(block)` — 查询坏块状态 +- `gd5f2gq5ue_mark_block_bad(block)` — 标记坏块 (FTL 层在擦除/编程失败时调用) + +--- + +## 5. FAL 抽象层 + +FAL (Flash Abstraction Layer) 是 FlashDB 自带的 flash 抽象层,**仅 FlashDB 路径使用**。它是一个薄封装层,将 FAL 的 read/write/erase 请求直接转发给底层 NAND 驱动,不做地址映射或磨损均衡(这些由 FlashDB 自身在 FAL 之上完成)。 + +FAL 不参与 FatFS/FTL 路径。FTL 有自己的 HAL 回调直接对接底层 NAND 驱动,与 FAL 无关。 + +``` +FlashDB ──→ FAL ──→ gd5f2gq5ue_read/write/erase (直接透传) +FatFs ──→ dhara FTL ──→ dhara_nand_* 回调 ──→ gd5f2gq5ue_* (独立路径) +``` + +### 5.1 设备注册 (fal_flash_gd5f2gq5ue.c) + +```c +const struct fal_flash_dev g_gd5f2gq5ue_flash = { + .name = "gd5f2gq5ue", + .addr = 0, + .len = GD5F_TOTAL_SIZE, // 256MB + .blk_size = GD5F_BLOCK_SIZE, // 128KB + .ops = { .init, .read, .write, .erase }, + .write_gran = 8, // byte programmable +}; +``` + +### 5.2 分区表 (fal_cfg.h) + +```c +#define FAL_PART_TABLE \ +{ \ + {FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", FDB_KVDB1_OFFSET, FDB_KVDB1_SIZE, 0}, \ + {FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", FDB_TSDB1_OFFSET, FDB_TSDB1_SIZE, 0}, \ +} +``` + +FlashDB 通过分区名 (`"fdb_kvdb1"`, `"fdb_tsdb1"`) 绑定到 FAL 分区。 + +--- + +## 6. FlashDB 数据库 + +### 6.1 配置 (fdb_cfg.h) + +```c +#define FDB_USING_KVDB // 启用键值数据库 +#define FDB_USING_TSDB // 启用时序数据库 +#define FDB_USING_FAL_MODE // 使用 FAL 存储模式 +#define FDB_WRITE_GRAN 8 // 写粒度 8bit (字节可编程) +``` + +### 6.2 查询接口 + +FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动额外的设备注册。 + +### 6.3 KVDB 键值数据库 + +适用于存储配置参数、运行状态等少量键值对。 + +**特点:** +- 掉电安全 (写操作带 CRC 校验) +- 支持 blob (二进制数据块) +- 自动磨损均衡 +- 支持默认值 (首次启动自动写入) + +### 6.4 TSDB 时序数据库 + +适用于存储采样数据、日志记录等时间序列数据。 + +**特点:** +- 固定长度记录 +- 时间戳索引 (需提供 `get_time` 回调) +- 自动擦除 (新数据覆盖最旧数据) +- 支持按时间范围查询 + +--- + +## 7. dhara FTL (Flash Translation Layer) + +### 7.1 概述 + +FTL 是 NAND Flash 上方最重要的组件,功能包括: + +| 功能 | 说明 | +|------|------| +| **地址映射** | 将 FatFS 的逻辑扇区号 (LBA) 映射到 NAND 物理页 | +| **磨损均衡** | 均匀分布擦除次数,延长芯片寿命 | +| **垃圾回收** | 回收无效页面空间 (GC) | +| **坏块管理** | 擦除/编程失败时自动跳过并标记坏块 | +| **ECC 处理** | 检测和上报硬件 ECC 不可纠正错误 | + +### 7.2 数据结构 + +```c +struct dhara_nand { + int log2_page_size; // 页大小对数 (2KB → 11) + int log2_ppb; // 每块页数对数 (64 → 6) + int num_blocks; // 管理的物理块数 (1024) +}; + +struct dhara_map { + // 内部状态:GC 队列、journal、映射表等 +}; +``` + +### 7.3 NAND HAL (nand_ftl.c) + +dhara 通过 7 个回调函数访问物理 NAND: + +| 回调 | 功能 | 对应底层函数 | +|------|------|-------------| +| `dhara_nand_is_bad` | 查询坏块 | `gd5f2gq5ue_is_block_bad(block + 1024)` | +| `dhara_nand_mark_bad` | 标记坏块 | `gd5f2gq5ue_mark_block_bad(block + 1024)` | +| `dhara_nand_erase` | 擦除块 | `nand_block_erase(block + 1024)` | +| `dhara_nand_prog` | 写页 | `nand_program_load + nand_program_exec` | +| `dhara_nand_read` | 读页 | `nand_page_read_to_cache + nand_read_from_cache` | +| `dhara_nand_is_free` | 检查页空闲 | 读前 64 字节判断全为 0xFF | +| `dhara_nand_copy` | 页拷贝 (GC 用) | read + prog 组合 | + +所有回调自动将 dhara 逻辑块/页加 `FTL_START_BLOCK` 偏移转换为物理地址。 + +### 7.4 初始化流程 (disk_initialize) + +``` +1. 设置 nand 参数 (log2_page_size=11, log2_ppb=6, num_blocks=1024) +2. dhara_map_init(&s_map, &s_nand, s_page_buf, 4) + ├─ s_page_buf: dhara 内部使用的 2KB 工作缓冲区 + └─ 4: journal 页面数量 (影响 GC 效率, 增大可减少写入放大) +3. dhara_map_resume(&s_map) + ├─ 成功: 加载已有映射表 + └─ 失败: dhara_map_clear 创建空映射表 +4. 页面缓存初始化 (s_cache_buf, s_cached_lpn, s_cache_dirty) +``` + +### 7.5 页面缓存策略 + +FTL 之上还有一个 **单页写回缓存 (write-back cache)**: + +- **读命中**: 直接返回 s_cache_buf 数据 +- **读未命中**: 刷出脏页 → 读新页到缓存 +- **写**: 写入缓存 → 标记脏 +- **全页写入**: 直接刷出 (跳过缓存) +- **同步 (CTRL_SYNC)**: 刷出脏页 + dhara_map_sync + +--- + +## 8. FatFS 集成 + +### 8.1 配置 (ffconf.h) + +```c +#define FF_FS_READONLY 0 // 读写模式 +#define FF_USE_MKFS 1 // 启用格式化 +#define FF_MIN_SS 512 // 最小扇区大小 +#define FF_MAX_SS 512 // 最大扇区大小 +#define FF_VOLUMES 1 // 单卷 +#define FF_FS_TINY 0 // 非 tiny 模式 +#define FF_FS_NORTC 1 // 无 RTC (固定时间戳) +``` + +### 8.2 disk I/O 接口 + +| 函数 | 功能 | 关键实现 | +|------|------|----------| +| `disk_initialize` | 初始化 FTL | 见 7.4 节 | +| `disk_status` | 查询状态 | 返回初始化状态 | +| `disk_read` | 读扇区 | 通过 FTL 映射读物理页 | +| `disk_write` | 写扇区 | 通过 FTL 映射写 (缓存优化) | +| `disk_ioctl` | 控制命令 | GET_SECTOR_COUNT/SIZE/BLOCK_SIZE + CTRL_SYNC | + +### 8.3 容量计算 + +``` +FTL 管理块数 = 1024 (Block 1024~2047) +每块页数 = 64 +每页扇区数 (512B) = 4 +总扇区数 = 1024 × 64 × 4 = 262144 +总容量 = 262144 × 512 = 128MB (原始容量) +FTL 开销后 ≈ 93 MB (随 GC 和 journal 使用量波动) +``` + +--- + +## 9. 完整数据流 + +### 9.1 写文件流程 + +``` +f_write("hello.txt") + └─ FatFS: 计算 LBA, 写扇区 + └─ disk_write(0, data, sector=100, count=2) + ├─ 计算 LPN = sector / 4 = 25 + ├─ 缓存未命中 → ftl_flush_cache() → ftl_read_page(25) + ├─ 拷贝数据到 s_cache_buf → 标记脏 + └─ 全页面写入 → ftl_flush_cache() + └─ dhara_map_write(&s_map, 25, s_cache_buf) + ├─ 查找页映射 (或分配新页) + ├─ dhara_nand_prog(pg, data) → 物理写 + └─ 更新映射表 + +f_close → disk_ioctl(CTRL_SYNC) + └─ ftl_flush_cache() → dhara_map_sync() + └─ 写 journal 到 NAND (持久化映射表) +``` + +### 9.2 f_mkfs 格式化流程 + +``` +f_mkfs("", &opts, work, size) + ├─ disk_initialize(0) → FTL 初始化 + ├─ disk_write: 写入引导扇区 (MBR/PBR) + ├─ disk_write: 写入 FAT 表 + ├─ disk_write: 创建根目录 + └─ disk_ioctl(CTRL_SYNC) → FTL sync +``` + +--- + +## 10. API 参考 + +### 10.1 底层 NAND 驱动 (gd5f2gq5ue.h) + +| 函数 | 说明 | +|------|------| +| `gd5f2gq5ue_init()` | 初始化 NAND (含 ECC 使能、块保护解除) | +| `gd5f2gq5ue_read_id(mid, did)` | 读芯片 ID | +| `gd5f2gq5ue_read(offset, buf, size)` | 读数据 (支持跨页) | +| `gd5f2gq5ue_write(offset, buf, size)` | 写数据 (支持跨页) | +| `gd5f2gq5ue_erase(offset, size)` | 块擦除 (需块对齐) | +| `gd5f2gq5ue_reset()` | 复位芯片 | +| `gd5f2gq5ue_is_block_bad(block)` | 查询坏块 | +| `gd5f2gq5ue_mark_block_bad(block)` | 标记坏块 | + +### 10.2 FAL 接口 (via fal_flash_gd5f2gq5ue.c) + +| 函数 | 说明 | +|------|------| +| `g_gd5f2gq5ue_flash` | FAL 设备实例 | +| `fal_flash_device_find("gd5f2gq5ue")` | 查找 Flash 设备 | +| `fal_partition_find("fdb_kvdb1")` | 查找分区 | +| `fal_partition_read/write/erase` | 分区读写擦除 | + +### 10.3 FlashDB (via FlashDB 库) + +| 函数 | 说明 | +|------|------| +| `fdb_kvdb_init(&db, "kvdb1", "fdb_kvdb1", NULL, NULL)` | 初始化 KVDB | +| `fdb_kvdb_set(&db, "key", "value")` | 写键值 (字符串) | +| `fdb_kvdb_get(&db, "key", &data)` | 读键值 | +| `fdb_kvdb_del(&db, "key")` | 删除键值 | +| `fdb_kvdb_set_blob(&db, "key", &blob)` | 写二进制 blob | +| `fdb_tsdb_init(&db, "tsdb1", "fdb_tsdb1", get_time, 1024, NULL)` | 初始化 TSDB | +| `fdb_tsl_append(&db, data)` | 追加时序记录 | +| `fdb_tsl_iter_by_time(&db, from, to, cb, cb_arg)` | 时间范围查询 | + +### 10.4 FTL/FatFS (via nand_ftl.c / ff.h) + +| 函数 | 说明 | +|------|------| +| `f_mount(&fs, "", 1)` | 挂载文件系统 (首次调用自动初始化 FTL) | +| `f_mkfs("", &opts, work, size)` | 格式化 FAT32 | +| `f_open/f_close/f_read/f_write` | 标准文件操作 | +| `f_unlink("test.txt")` | 删除文件 | +| `disk_initialize(0)` | 显式初始化 FTL (通常由 f_mount 自动调用) | +| `nand_ftl_format()` | 直接格式化 FTL (清空映射表, 慎用) | + +--- + +## 11. 使用示例 + +### 11.1 KVDB 初始化与使用 + +```c +#include "flashdb.h" +#include "fdb_cfg.h" + +/* KVDB 实例 */ +static fdb_kvdb_t g_kvdb; + +static int kvdb_init(void) { + fdb_err_t ret = fdb_kvdb_init(&g_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL); + return (ret == FDB_NO_ERR) ? 0 : -1; +} + +static void kvdb_example(void) { + /* 写字符串 */ + fdb_kvdb_set(&g_kvdb, "device_id", "STM32F407-001"); + + /* 读字符串 */ + char buf[64]; + fdb_kvdb_get(&g_kvdb, "device_id", buf, sizeof(buf)); + + /* 写二进制 blob */ + struct fdb_blob blob; + uint32_t value = 42; + fdb_blob_make(&blob, &value, sizeof(value)); + fdb_kvdb_set_blob(&g_kvdb, "counter", &blob); +} +``` + +### 11.2 TSDB 初始化与使用 + +```c +static fdb_tsdb_t g_tsdb; + +static time_t get_timestamp(void) { + return sys_clock_now(); // 从 RTC 获取 Unix 时间戳 +} + +static int tsdb_init(void) { + fdb_err_t ret = fdb_tsdb_init(&g_tsdb, "tsdb1", "fdb_tsdb1", + get_timestamp, 1024, NULL); + return (ret == FDB_NO_ERR) ? 0 : -1; +} + +typedef struct { + float voltage; + float current; + float temperature; +} sensor_data_t; + +static void tsdb_example(void) { + sensor_data_t data = {3.3f, 0.5f, 25.6f}; + fdb_tsl_append(&g_tsdb, &data); +} +``` + +### 11.3 FatFS 文件操作 + +```c +#include "ff.h" + +static FATFS fs; + +static int fatfs_init(void) { + FRESULT res = f_mount(&fs, "", 1); + if (res == FR_NO_FILESYSTEM) { + /* 首次使用需格式化 */ + MKFS_PARM opts = {FM_FAT32, 0, 0, 0, 0}; + uint8_t work[512]; + res = f_mkfs("", &opts, work, sizeof(work)); + if (res != FR_OK) return -1; + res = f_mount(&fs, "", 1); + } + return (res == FR_OK) ? 0 : -1; +} + +static void fatfs_write_read(void) { + FIL fil; + UINT bw, br; + const char *msg = "Hello Storage!"; + char buf[32]; + + /* 写文件 */ + f_open(&fil, "data.txt", FA_CREATE_ALWAYS | FA_WRITE); + f_write(&fil, msg, strlen(msg), &bw); + f_close(&fil); + + /* 读文件 */ + f_open(&fil, "data.txt", FA_READ); + f_read(&fil, buf, sizeof(buf), &br); + buf[br] = '\0'; + f_close(&fil); +} +``` + +### 11.4 FTL 格式化 + +```c +#include "nand_ftl.h" + +/* 注意: 此操作将清空 FTL 分区全部数据, 慎用 */ +if (nand_ftl_format() == 0) { + DBG_INFO("FTL formatted"); +} +``` + +--- + +## 12. 注意事项 + +1. **擦除对齐** — `gd5f2gq5ue_erase()` 的 offset 和 size 必须严格按 GD5F_BLOCK_SIZE (128KB) 对齐和整数倍。 +2. **写前擦除** — NAND 不能原地覆写,FTL 和 FlashDB 内部自动管理擦除,但直接调用 `gd5f2gq5ue_write()` 前必须确保目标块已擦除。 +3. **FTL 首个扇区** — `dhara_map_init` 的第 5 个参数 (journal 页数) 影响 GC 效率,当前为 4,增大可减少写入放大但占用更多内存。 +4. **坏块传播** — FTL 在擦除/编程失败后自动调用 `dhara_nand_mark_bad` → `gd5f2gq5ue_mark_block_bad`,BBT 在 RAM 中更新,下次复位后重新扫描出厂坏块并叠加运行时坏块。 +5. **功耗** — 擦除操作最大耗时约 5ms (驱动超时设为 5s),页编程约 600ms (超时 1s),读写操作快。在低功耗场景需注意合理安排操作时序。 +6. **分区隔离** — 三个分区物理隔离,FlashDB 操作不会影响 FTL 数据,反之亦然。修改分区布局时需同步更新 `fal_cfg.h` 和确认 FTL 宏自动适配。 +7. **缓存一致性** — 单页缓存 (s_cache_buf) 仅对 FatFS 层可见,多任务读写同一文件需在应用层同步。