NVMe-eMMC Bridge 芯片固件开发(四):空间优化技术详解

1. 内存资源约束与挑战

BayBridge 芯片基于 SPARC LEON2 架构,内置 SRAM 空间仅为 160KB。在这有限的空间内需要容纳:

  • Bootloader 代码(约 10-12KB)
  • Firmware 代码 + 只读数据(约 80-100KB)
  • 运行时数据(全局变量、BSS 段)
  • 任务队列(32 x 2 个 eMMC 通道)
  • 堆栈空间
  • 固件参数区(1KB)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+---------------------------+ 0x40000000
| Trap Table | 1KB
+---------------------------+
| .text (代码段) | ~80KB
+---------------------------+
| .rodata (只读数据) | ~10KB
+---------------------------+
| .data (已初始化数据) | ~5KB
+---------------------------+
| .bss (未初始化数据) | ~20KB
+---------------------------+
| Stack Space | ~40KB
+---------------------------+
| Frame Space | 128B
+---------------------------+
| fw_param (固件参数) | 1KB
+---------------------------+ 0x40028000 (160KB)

2. Linker Script 内存布局设计

2.1 Firmware Linker Script 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/* fw_sys.lds */

OUTPUT_ARCH(sparc)
__DYNAMIC = 0;

MEMORY
{
ram : ORIGIN = 0x40000000, LENGTH = 160K
}

SECTIONS
{
/* ===== 代码段 ===== */
.common_text :
{
_data_start = . ;
_common_text_start = . ;
. = ALIGN(0x4);

/* Trap Table 必须在最前面 (地址 0x40000000) */
src/system/trap/trap_table.ao(.text)
src/system/trap/*.ao(.text)

/* 其他代码 */
*(.text)

/* 只读数据紧跟代码段 */
_rodata_start = . ;
*(.rodata*)
*(.gnu.linkonce.r*)
*(.init)
*(.fini)
*(.lit)
*(.shdata)
. = ALIGN(0x4);
} > ram

_common_text_size = SIZEOF(.common_text);
_common_text_end = _common_text_start + _common_text_size;

/* ===== 已初始化数据段 ===== */
.data : AT (_common_text_start + _common_text_size)
{
. = ALIGN(0x4) + 0x8; /* 8 字节保护间隙 */
*(.data)
. = ALIGN(0x4);
} > ram

_data_size = SIZEOF(.data) + _common_text_size;
_data_end = _data_start + _data_size;

/* ===== BSS 段 ===== */
.bss :
{
_bss_start = . ;
. = ALIGN(0x4) + 0x8; /* 8 字节保护间隙 */
*(.bss)
. = ALIGN(0x4);
} > ram

_bss_size = SIZEOF(.bss);
_bss_end = _bss_start + _bss_size;

/* ===== 堆栈段 ===== */
.stack :
{
_stack_bottom = . ;
. = ALIGN(0x4) + 0x10;
} > ram
}

2.2 Bootloader 多版本支持

项目为不同启动场景设计了独立的 Linker Script:

Linker Script ORIGIN 用途
boot_rom.lds 0x00000000 内置 ROM 启动
boot_flash.lds 0x50000000 SPI Flash XIP 启动
fw_sys.lds 0x40000000 RAM 中运行的 Firmware
fw_simu_ram.lds 0x40000000 仿真调试版本

3. 数据结构紧凑设计

3.1 位域 (Bit Field) 优化

1
2
3
4
5
6
7
8
9
10
11
12
13
// card.h - CID 结构使用位域节省空间
typedef struct {
u8 ManufacturerId; // [127:120]
u8 Reserved:6; // [119:114] - 仅 6 bits
u8 DeviceType:2; // [113:112] - 仅 2 bits
u8 OemId; // [111:104]
u8 ProductName[6]; // [103:56]
u8 ProductRevision; // [55:48]
u8 ProductSerialNumber[4]; // [47:16]
u8 ManufacturingDate; // [15:8]
u8 Crc:7; // [7:1] - 仅 7 bits
u8 NotUsed:1; // [0:0] - 仅 1 bit
} card_cid_struct; // 总大小: 16 字节

3.2 Packed 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// system.h - 固件参数结构使用 packed 属性
typedef struct {
// dw0 - 4 个字节分解为多个字段
u32 manufacture_id: 8;
u32 oem_id: 8;
u32 fw_download_mode: 8;
u32 fw_download_mode_rev: 8;

// dw1
u32 fw_update_level: 4;
u32 fw_security_level: 4;
u32 resv_1_1: 24;

// ... 更多字段 ...

// dw100 - 位域混合使用
u32 emmc_io_ext_internal: 4;
u32 main_voltage_ext_internal: 4;
u32 resv_100_1: 16;
u32 pcie_sideband_io_vol: 1;
u32 resv_100_3: 7;

} __attribute__((packed, aligned(4))) fw_param_t;

优化效果packed 属性确保结构体成员无间隙对齐,aligned(4) 保证 4 字节对齐以满足 SPARC 访问要求。

3.3 任务信息结构优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// command_queue.h - 任务结构体精简设计
typedef struct {
u64 prp1; // 8 bytes - PRP Entry 1
u64 prp2; // 8 bytes - PRP Entry 2
u64 address; // 8 bytes - eMMC LBA
u16 block_len; // 2 bytes - 块数量
u16 nvme_sq_id; // 2 bytes - SQ ID
u16 nvme_cmd_id; // 2 bytes - Command ID
u8 direction; // 1 byte - 读/写方向
u8 fused_flag; // 1 byte - 跨 eMMC 标志
u8 next_pointer; // 1 byte - 链表指针
u8 start_card; // 1 byte - 起始 eMMC
} task_info_struct; // 总大小: 34 bytes

// 任务状态使用 1 字节枚举
#define EMPTY 0x00
#define PRE_CANDIDATE 0x01
#define CANDIDATE 0x02
#define EXECUTING 0x03
#define COMPLETED 0x04
#define READY 0x05
#define ERR_STATUS 0x7F

内存占用计算

  • 每个任务:34 bytes
  • 两个 eMMC 通道各 32 深度:34 × 32 × 2 = 2,176 bytes
  • 任务状态数组:1 × 32 × 2 = 64 bytes
  • 总计约 2.2KB

4. 静态内存分配策略

4.1 全局数组预分配

1
2
3
4
5
6
7
8
9
// 任务队列 - 静态分配,避免动态内存管理
task_info_struct emmc_firmware_task_queue[2][BB_CQ_DEPTH_DEFINE];
u8 emmc_firmware_task_status_flag[2][BB_CQ_DEPTH_DEFINE];
u8 global_emmc_firmware_task_status_map[2][BB_CQ_DEPTH_DEFINE];
u8 global_emmc_firmware_task_status_map_index[2];

// 深度配置 (config.h)
#define BB_CQ_DEPTH_DEFINE 0x20 // 32 深度
#define BB_NON_CQ_DEPTH_DEFINE 0x20

4.2 固定大小缓冲区

1
2
3
4
5
6
7
8
9
// controller.h - 缓冲区大小定义
#define DATA_BUFFER_SIZE (4*1024) // 4KB - Admin 命令数据
#define CMD_BUFFER_SIZE (1024) // 1KB - NVMe 命令缓存
#define PRP_SIZE (4*1024) // 4KB - 页面大小
#define PRP_LIST_SIZE (4*512) // 2KB - PRP List 最大
#define DATA_BLOCK_SIZE 512 // 512B - eMMC 块大小

// firmware_downloader.h - 固件下载缓冲区
#define FW_ARRAY_SIZE (BB_RAM_SIZE - (6*1024)) // 160KB - 6KB = 154KB

4.3 固件参数区设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 固件参数区位于 RAM 顶部 1KB
#define fw_param (*(volatile fw_param_t*)(0x40000000 + (BB_RAM_SIZE - 1024)))

/*
* 内存布局:
*
* 0x40027C00 - 0x40028000: fw_param (1KB)
* - Bootloader 从 SPI Flash/eMMC 复制固件参数到此区域
* - Firmware 运行时直接访问,无需额外拷贝
*
* 0x40027B00 - 0x40027C00: Frame Space (256B)
* - 函数调用栈帧
*
* 0x40027A00 - 0x40027B00: Stack Top (可变)
* - 向下增长的堆栈
*/

5. Log 字符串空间优化(重点)

在嵌入式固件中,调试打印的字符串常量会占用 .rodata 段,属于固件二进制的一部分。对于 160KB 的空间限制,字符串优化至关重要。

5.1 问题分析:字符串的空间代价

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────────┐
│ 字符串常量在固件中的占用 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 源代码: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ db_printf("ERR: cmd44_cq_set_task_params failed with 0x%X\n"); │ │
│ │ db_printf("ERR: cmd45_cq_set_task_address failed with 0x%X\n"); │ │
│ │ db_printf("ERR: cmd46_cq_execute_read_task failed with 0x%X\n");│ │
│ │ db_printf("K_INFO: executing %d fused cmd, direction %d\n"); │ │
│ │ db_printf("ERR: error recovery failed with 0x%x\n"); │ │
│ │ ... │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ .rodata 段: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ "ERR: cmd44_cq_set_task_params failed with 0x%X\n\0" (47 B) │ │
│ │ "ERR: cmd45_cq_set_task_address failed with 0x%X\n\0" (48 B) │ │
│ │ "ERR: cmd46_cq_execute_read_task failed with 0x%X\n\0"(49 B) │ │
│ │ "K_INFO: executing %d fused cmd, direction %d\n\0" (46 B) │ │
│ │ "ERR: error recovery failed with 0x%x\n\0" (38 B) │ │
│ │ ... │ │
│ │ │ │
│ │ ⚠️ 数百条打印 → 可能占用 10~20KB .rodata 空间! │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

5.2 多级打印控制架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// log.h - 打印级别定义

/* 打印级别(从低到高) */
#define OFF (0) // 完全关闭 - 客户量产版本
#define FATAL (1) // 致命错误
#define ERROR (2) // 错误信息
#define WARNING (3) // 警告信息
#define INFO (4) // 关键信息
#define DEBUG (5) // 调试信息
#define TRACE (6) // 跟踪信息
#define ALL (7) // 全部打印 - 开发调试版本

/* 条件编译的 LOG 宏 */
#define LOG(level, ...) \
do { \
if (LOG_LEVEL >= level) { \
db_printf(__VA_ARGS__); \
} \
} while(0)

关键设计:当 LOG_LEVEL = OFF 时,所有 LOG() 调用在预处理阶段被移除,字符串常量不会进入编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────────────────┐
│ 打印级别与固件版本映射 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 开发阶段 量产阶段 │
│ ───────── ───────── │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ LOG_LEVEL = ALL │ │ LOG_LEVEL = OFF │ │
│ │ 或 DEBUG/TRACE │ │ 或 ERROR │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 包含所有字符串 │ │ 仅错误码,无字符串│ │
│ │ FW Size: ~120KB │ │ FW Size: ~100KB │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 用途: 问题定位、功能验证 用途: 客户交付、量产 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

5.3 错误码设计:用数值替代字符串

核心思想:用 32-bit 错误码 编码错误信息,替代冗长的字符串描述。

1
2
3
4
5
6
7
8
// log.h - 统一的错误码格式字符串(仅一条,~16字节)
#define LOG_ERROR_STR "ERROR: 0x%08X\n"
#define LOG_WARN_STR "WARN: 0x%08X\n"
#define LOG_DEBUG_STR "DEBUG: 0x%08X\n"

// 调用方式:只传递错误码
LOG(ERROR, LOG_ERROR_STR, NVME_ADMIN_FW_COMMIT_INVALID_FW_SLOT);
// 输出: "ERROR: 0x10000B06"

错误码编码规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────────────────┐
│ 32-bit 错误码编码格式 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 31 24 23 16 15 8 7 0 │
│ ┌──────────┬────────────┬────────────┬────────────┐ │
│ │ Level │ Module │ Function │ Detail │ │
│ │ (8-bit) │ (8-bit) │ (8-bit) │ (8-bit) │ │
│ └──────────┴────────────┴────────────┴────────────┘ │
│ │
│ Level: 0x1 = Admin, 0x2 = eMMC, 0xF = BayHub Custom │
│ Module: 0x0 = NVMe Admin, 0x1 = NVMe IO, 0x2 = Host Init... │
│ Function: 具体命令或函数编号 │
│ Detail: 错误细节编码 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

错误码定义示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// log.h - NVMe Admin 命令错误码
#define NVME_ADMIN_ABORT_LIMIT_EXCEED 0x10000103 // Admin/Abort/LimitExceed
#define NVME_ADMIN_CREATE_IOCQ_INVALID_QID 0x10000301 // Admin/CreateCQ/InvalidQID
#define NVME_ADMIN_CREATE_IOCQ_INVALID_QSIZE 0x10000302 // Admin/CreateCQ/InvalidSize
#define NVME_ADMIN_FW_COMMIT_INVALID_FW_SLOT 0x10000B06 // Admin/FWCommit/InvalidSlot
#define NVME_ADMIN_FW_COMMIT_INVALID_FW_IMAGE 0x10000B07 // Admin/FWCommit/InvalidImage
#define NVME_ADMIN_FW_DOWNLOAD_OVERLAP_RANGE 0x10000C14 // Admin/FWDownload/Overlap

// eMMC 相关错误码 (0x2X)
#define EMMC_HOST_INIT_ERROR 0x21000000
#define EMMC_CARD_CHECK_ERROR 0x22000000
#define EMMC_CARD_IDENTIFY_ERROR 0x23000000
#define EMMC_RW_ERROR 0x26000000
#define EMMC_ERROR_RECOVERY_ERROR 0x2A000000

5.4 模块级打印开关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// log.h - 细粒度模块控制
#define TEST_NVME 0 // NVMe 协议调试
#define TEST_EMMC 0 // eMMC 命令调试
#define TEST_SYSTEM 0 // 系统初始化调试
#define TEST_FLUSH 1 // Flush 命令调试(保留)
#define TEST_FW_DOWNLOAD 0 // 固件下载调试
#define TEST_NAMESPACE 0 // Namespace 调试
#define TEST_SHUTDOWN 1 // Shutdown 调试(保留)

// 模块级打印宏
#define LOG_TEST(module_flag, ...) \
do { \
if (TEST_LOG_SWITCH && module_flag) { \
db_printf(__VA_ARGS__); \
} \
} while(0)

// 使用示例
LOG_TEST(TEST_EMMC, "CMD44 task_id=%d, block=%d\n", task_id, block_len);

5.5 Bootloader 打印分级

1
2
3
4
5
6
7
8
9
10
11
12
13
// log.h - Bootloader 专用打印控制
#ifdef BOOTLOADER_DEBUG
#define Info_Print_BL(...) // 关闭普通信息
#define Position_Print_BL(...) // 关闭位置信息
#define Key_Info_Print_BL(...) db_printf(__VA_ARGS__) // 仅关键信息
#define Err_Print_BL(...) db_printf(__VA_ARGS__) // 错误必须打印

#elif defined BOOTLOADER_RELEASE
#define Info_Print_BL(...) // 全部关闭
#define Position_Print_BL(...)
#define Key_Info_Print_BL(...)
#define Err_Print_BL(...) // 连错误也关闭
#endif

5.6 空间节省效果对比

版本类型 LOG_LEVEL 字符串数量 .rodata 占用 节省空间
开发调试版 ALL/DEBUG ~500 条 ~15KB -
测试版本 ERROR ~50 条 ~2KB ~13KB
量产版本 OFF 0 条 ~200B ~15KB
1
2
3
4
5
6
7
8
9
10
11
// config.h - 版本配置

#ifdef MP_ASIC // 量产 ASIC 版本
#define LOG_LEVEL OFF
#define TEST_LOG_SWITCH 0
#endif

#ifdef GENERAL_FPGA // FPGA 开发版本
#define LOG_LEVEL DEBUG
#define TEST_LOG_SWITCH 1
#endif

6. 代码空间优化

6.1 编译优化选项

1
2
3
4
5
6
7
# Makefile
CFLAGS = -c -O3 -Wall -msoft-float -mv8 ${INC_PATH}

# 优化说明:
# -O3 : 最高级别代码优化
# -msoft-float : 软件浮点(LEON2 无 FPU)
# -mv8 : SPARC V8 指令集

6.2 条件编译裁剪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// config.h - 功能开关
#define FW_SIM 0 // 仿真模式
#define SMART_INFO_ENABLE 1 // SMART 信息功能
#define BB_FFU_SUPPORTED 1 // 固件更新功能
#define DISABLE_DATASET_MANAGEMENT_COMMAND 1 // 禁用 TRIM

// 根据功能需求裁剪代码
#if SMART_INFO_ENABLE
smart_info_init(&smart_rw, &smart_data);
// ... SMART 相关代码 ...
#endif

#if !DISABLE_DATASET_MANAGEMENT_COMMAND
case 0x09: // Dataset Management
return dataset_management_process(queue_identifier, command);
#endif

6.3 内联函数与宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 关键路径使用内联优化
static inline u64 leon2_cpu_64bits_swap(u64 value)
{
u32 hi = (u32)(value >> 32);
u32 lo = (u32)(value);
hi = __builtin_bswap32(hi);
lo = __builtin_bswap32(lo);
return ((u64)lo << 32) | hi;
}

// 频繁使用的位操作使用宏
#define set_reg(base, offset, mask, value) \
(*(volatile u32 *)((base) + (offset)) = \
((*(volatile u32 *)((base) + (offset))) & ~(mask)) | ((value) & (mask)))

7. 运行时内存优化

7.1 任务状态位图设计

1
2
3
4
5
6
7
// 原始设计: 每个任务使用 1 字节状态
u8 emmc_firmware_task_status_flag[2][32]; // 64 bytes

// 优化思路 (可进一步优化):
// 使用 64-bit 位图表示 32 个任务状态
// u64 task_status_bitmap[2]; // 仅 16 bytes
// 每 2 bits 表示一个任务状态 (0~3)

7.2 共享缓冲区复用

1
2
3
4
5
6
7
8
9
10
11
// Data Buffer 在不同场景下复用
#define BAYBRIDGE_DATA_BUFFER ((volatile u8 *)(BAYBRIDGE_WORKRAM_BASE))

// 场景 1: Admin 命令数据 (Identify, Get Log Page)
// 场景 2: 固件更新数据缓存
// 场景 3: SMART 信息临时存储

// 通过状态机确保不会同时使用
if (global_admin_command_req_dma_channel_flag == 0) {
// 可以安全使用 Data Buffer 进行 IO 操作
}

7.3 堆栈深度控制

1
2
3
4
5
6
7
// boot.S - 堆栈初始化
set STACK_BASE, %g3 // RAM 顶部
sub %g3, 1024, %g3 // 预留 1KB 给 fw_param
sub %g3, 128, %g3 // 预留 128B 给函数参数
mov %g3, %fp // 设置帧指针
sub %g3, 96, %sp // 设置栈指针(预留 96B 本地变量)
andn %sp, 0x0f, %sp // 16 字节对齐

堆栈优化建议

  1. 避免深层递归调用
  2. 大数组使用全局静态分配
  3. 关键函数使用 __attribute__((noinline)) 控制内联

8. 空间监控与调试

8.1 Map 文件分析

1
2
# 生成 Map 文件
$(CROSS_COMPILE)ld -T $(LINKFILE_DIR)/fw_sys.lds -Map=build/debug/fw_sys.map ...

Map 文件关键段分析:

1
2
3
4
5
6
.common_text   0x40000000    0x14a8c
.data 0x40014a94 0x0124
.bss 0x40014bb8 0x4c20
^^^^^^^^^^^^^^
总计: ~100KB 代码 + 数据
剩余: ~60KB 堆栈空间

8.2 运行时堆栈检查

1
2
3
4
5
6
7
#if TEST_RAM_SPACE
// 内存测试填充
for (u32 i = 0; i < BB_RAM_SIZE/4 - 256; i++) {
*(volatile u32 *)(BAYBRIDGE_WORKRAM_BASE + i*4) = 0xDEADBEEF;
}
// 验证后比较哪些区域被覆盖,判断实际堆栈使用量
#endif

9. 总结

BayBridge 固件空间优化的核心策略:

优化方向 技术手段 节省空间
字符串优化 错误码替代、多级打印控制 ~10-15KB
数据结构 位域、packed、最小类型 ~5KB
内存分配 静态预分配、共享复用 ~3KB
代码裁剪 条件编译、功能开关 ~5KB
编译优化 -O3、内联、宏 ~2KB
布局设计 Linker Script 精确控制 -

关键设计原则

  1. 零动态分配:所有内存静态预分配,无 malloc/free
  2. 固定深度队列:任务队列深度编译时确定
  3. 缓冲区复用:Data Buffer 根据上下文复用
  4. 精确布局:Linker Script 控制每个 Section 位置
  5. 功能可裁剪:通过宏开关移除不需要的功能
  6. 字符串最小化:量产版用错误码替代字符串描述

字符串优化总结

1
2
3
4
开发版 (LOG_LEVEL=ALL)     量产版 (LOG_LEVEL=OFF)
───────────────────── ─────────────────────
db_printf("ERR: ...") → LOG(ERROR, LOG_ERROR_STR, 0x26000005)
~500条字符串, 15KB → 1条格式串 + 错误码, 200B

下一篇将分析 IO 性能优化和任务调度的实现细节。