[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出厂前定制功能。可与软件保护模式结合,实现永久写保护。

相关信息如下图所示:
alt text

本文仅讨论SRP1SRP00的情形,此时,写保护仅由BP4-BP0以及CMP来控制。

根据数据手册,Status Register 1中的BP4-BP0以及Status Register 2中的CMP位控制着FLASH保护区域的大小。

详细的保护区域组合如下所示:

CMP=0时(默认值)

alt text

CMP=1

alt text

2.2 实现思路

由于该功能需要改变Status Register No.1Status 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, &reg_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, &reg_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在写保护的具体实现方式上可能略有差异,但基本流程大致相同,因此可以采用类似的方法快速实现所需的写保护功能。

1
0
发表回复 0

Your email address will not be published. Required fields are marked *