[ROMAPI]XPI系列:开启写保护 – 功能分析与实战
XPI系列:开启写保护 - 功能分析与实战
1. 前言
在实际应用中,通常需要使用写保护
功能来保护FLASH
中的关键代码和数据,例如:
- 保护片上的Bootloader,防止其被意外擦除。
- 保护系统的关键参数,防止被修改。
本文以GD25Q32E
为例,应用面向xSPI接口的API,介绍如何基于软件保护模式来实现FLASH
的写保护功能。
2. 功能分析
2.1 信息收集
查阅GD25Q32E
数据手册可知,该FLASH支持如下数据保护方式:
禁用Write Enable
禁用Write Enable可禁用对FLASH区域的写操作。这是芯片上电后或擦写操作后的默认状态。软件保护模式
通过控制状态寄存器中的非易失性块保护位(BP),控制某个区域的写保护。当对应的写保护生效后,即使使能了Write Enable,也无法写入受保护的区域。硬件保护模式
通过拉低WP#
引脚来保护BP位
及状态寄存器的保护位
。当BP位
和状态寄存器的保护位
都处于禁止写状态后,即使使能了Write Enable,也无法改变这些寄存器的值,从而更大程度地保护受保护区域。但当QE位
置1
后,该功能不再支持。上电锁定模式
该功能为FLASH出厂前定制功能。可与软件保护模式结合,实现重新上电前的永久写保护。单次编程模式
该功能为FLASH出厂前定制功能。可与软件保护模式结合,实现永久写保护。
相关信息如下图所示:
本文仅讨论SRP1
和SRP0
为0
的情形,此时,写保护仅由BP4-BP0
以及CMP
来控制。
根据数据手册,Status Register 1
中的BP4-BP0
以及Status Register 2
中的CMP
位控制着FLASH保护区域的大小。
详细的保护区域组合如下所示:
当CMP=0
时(默认值)
当CMP=1
时
2.2 实现思路
由于该功能需要改变Status Register No.1
和Status Register No.2
寄存器中的特定比特位,因此,我们需要实现以下功能:
- 读
Status Register 1
寄存器值(Read Status Register 1: 0x05) - 读
Status Register 2
寄存器的值(Read Status Register 1: 0x35) - 写
Status Register 1
寄存器的值(Write Status Register 1: 0x01) - 写
Status Register 2
寄存器的值(Write Status Register 1: 0x31) 写使能
(Write Enable: 0x06)忙等待
(Read Status Register 1: 0x05)
3. 具体实现
以下为具体代码实现:
3.1 读 Status Register 1 寄存器
ATTR_RAMFUNC hpm_stat_t gd25q32e_read_status_reg1(XPI_Type *base,
xpi_xfer_channel_t chn, uint8_t *status_reg)
{
/* 创建命令序列 */
xpi_instr_seq_ctx_t ctx;
xpi_instr_seq_init(&ctx);
xpi_instr_seq_add_cmd_phase(&ctx, 0x05, 1, false);
xpi_instr_seq_add_read_phase(&ctx, 1, false);
hpm_stat_t status = xpi_read_data(base, chn, &ctx, 0, status_reg, 1);
return status;
}
3.2 读 Status Register 2 寄存器
ATTR_RAMFUNC hpm_stat_t gd25q32e_read_status_reg2(XPI_Type *base,
xpi_xfer_channel_t chn, uint8_t *status_reg)
{
/* 创建命令序列 */
xpi_instr_seq_ctx_t ctx;
xpi_instr_seq_init(&ctx);
xpi_instr_seq_add_cmd_phase(&ctx, 0x35, 1, false);
xpi_instr_seq_add_read_phase(&ctx, 1, false);
hpm_stat_t status = xpi_read_data(base, chn, &ctx, 0, status_reg, 1);
return status;
}
3.3 使能写操作
ATTR_RAMFUNC hpm_stat_t gd25q32e_write_enable(XPI_Type *base,
xpi_xfer_channel_t chn)
{
/* 创建命令序列 */
xpi_instr_seq_ctx_t ctx;
xpi_instr_seq_init(&ctx);
xpi_instr_seq_add_cmd_phase(&ctx, 0x06, 1, false);
hpm_stat_t status = xpi_send_cmd(base, chn, &ctx, 0);
return status;
}
3.4 写 Status Register1 寄存器
ATTR_RAMFUNC hpm_stat_t gd25q32e_write_status_reg1(XPI_Type *base,
xpi_xfer_channel_t chn,
uint8_t reg_val)
{
/* 创建命令序列 */
xpi_instr_seq_ctx_t ctx;
xpi_instr_seq_init(&ctx);
xpi_instr_seq_add_cmd_phase(&ctx, 0x1, 1, false);
xpi_instr_seq_add_write_phase(&ctx, 1, false);
gd25q32e_write_enable(base, chn);
/* 写寄存器 */
hpm_stat_t status = xpi_write_data(base, chn, &ctx, 0, ®_val, 1);
if (status == status_success) {
return status;
}
/* 等待状态寄存器中的 WIP 为 0 */
uint8_t status_reg = 0;
do {
status = gd25q32e_read_status_reg1(base, chn, &status_reg);
} while((status == status_success) && ((status_reg & 0x1) !=0));
return status;
}
3.5 写 Status Register2 寄存器
ATTR_RAMFUNC hpm_stat_t gd25q32e_write_status_reg2(XPI_Type *base,
xpi_xfer_channel_t chn,
uint8_t reg_val)
{
/* 创建命令序列 */
xpi_instr_seq_ctx_t ctx;
xpi_instr_seq_init(&ctx);
xpi_instr_seq_add_cmd_phase(&ctx, 0x31, 1, false);
xpi_instr_seq_add_write_phase(&ctx, 1, false);
gd25q32e_write_enable(base, chn);
/* 写寄存器 */
hpm_stat_t status = xpi_write_data(base, chn, &ctx, 0, ®_val, 1);
if (status == status_success) {
return status;
}
/* 等待状态寄存器中的 WIP 为 0 */
uint8_t status_reg = 0;
do {
status = gd25q32e_read_status_reg1(base, chn, &status_reg);
} while((status == status_success) && ((status_reg & 0x1) !=0));
return status;
}
3.6 使能写保护
ATTR_RAMFUNC hpm_stat_t gd25q32e_enable_block_protection(XPI_Type *base,
xpi_xfer_channel_t chn,
uint8_t bp_val,
uint8_t cmp_val)
{
hpm_stat_t status;
uint8_t status_reg1 = 0;
uint8_t status_reg2 = 0;
status = gd25q32e_read_status_reg1(base, chn, &status_reg1);
if (status != status_success) {
return status;
}
status = gd25q32e_read_status_reg2(base, chn, &status_reg2);
if (status != status_success) {
return status;
}
bool srp0 = ((status_reg1 & (1U << 7)) != 0); /* 位于status_reg1[7] */
bool srp1 = ((status_reg2 & (1U << 0)) != 0); /* 位于status_reg2[0] */
if (srp0 && srp1) {
/* Permanantly protected */
return status_fail;
}
if (!srp0 && srp1) {
/* Power-supply Lock-down */
return status_fail;
}
/* 更新BP4-BP0的值,位于status_reg1[6:2] */
status_reg1 = (status_reg1 & (~(0x1FU << 2))) | ((bp_val & 0x1FU) << 2);
/* 更新CMP的值,位于status_reg2[6] */
status_reg2 = (status_reg2 & ~(1UL << 6)) | ((cmp_val == 1) ? (1UL << 6) : 0);
status = gd25q32e_write_status_reg1(base, chn, status_reg1);
if (status != status_success) {
return status;
}
status = gd25q32e_write_status_reg2(base, chn, status_reg2);
if (status != status_success) {
return status;
}
return status;
}
4. 实战
假设有以下应用场景:
- 用户在FLASH的前64KB存储了Bootloader。
- 第64KB-127KB区域存储了系统的关键参数。
- 在后续应用中,以上数据无需再被更改。
如何快速保护这些无需更改的程序和参数?
借助上述代码,在完成FLASH初始化后,只需一行函数调用即可实现:
// 详见上文中`CMP=0`时BP4-BP0的值为5'b01010值时的保护区域(即:前128KB)
gd25q32e_enable_block_protection(base, chn, 0x0a, 0);
5. 扩展
本文基于GD25Q32E
的特性,实现了相应的块保护功能代码。虽然不同厂商的FLASH在写保护的具体实现方式上可能略有差异,但基本流程大致相同,因此可以采用类似的方法快速实现所需的写保护功能。