Zephyr Flash Map实现分析

在嵌入式系统中,通常会根据功能对Flash区域进行划分,然后按照区域进行使用。Zephyr的Flash Map模块对该功能进行抽象并API封装,方便对Flash区域的封装和使用。

分区

在zephyr的include/storage/flash_map.h中定义了描述分区的结构体struct flash_area, 各描述字段如下

struct flash_area {
    uint8_t fa_id;              //flash分区ID
    uint8_t fa_device_id;       //兼容mcuboot定义的flash分区
    uint16_t pad16;
    off_t fa_off;               //该flash分区在flash中的偏移地址
    size_t fa_size;             //该flash分区的大小
    const char *fa_dev_name;    //flash分区所在flash的设备名
};

zephyr的Flash Map就是由struct flash_area构成,我们可以通过设备树创建分区也可以在运行时自己指定struct flash_area

设备树创建分区

zephyr使用设备树中的fixed-partitions绑定生成分区表,该flash map的分区表在编译时决定,运行时无法被修改。 下面示例设备树:

&flexspi {
    status = "okay";
    ahb-prefetch;
    ahb-read-addr-opt;
    rx-clock-source = <1>;
    reg = <0x402a8000 0x4000>, <0x60000000 DT_SIZE_M(8)>;
    is25wp064: is25wp064@0 {
        compatible = "nxp,imx-flexspi-nor";
        size = <67108864>;
        label = "IS25WP064";
        reg = <0>;
        spi-max-frequency = <133000000>;
        status = "okay";
        jedec-id = [9d 70 17];
        erase-block-size = <65536>;
        write-block-size = <1>;

        partitions {
            compatible = "fixed-partitions";
            #address-cells = <1>;
            #size-cells = <1>;

            boot_partition: partition@0 {
                label = "bootloader";
                reg = <0x00000000 DT_SIZE_K(512)>;
            };

            data_partition: partition@80000 {
                label = "data";
                reg = <0x00080000 DT_SIZE_K(512)>;
            };
            app_partition: partition@100000 {
                label = "app";
                reg = <0x00100000 DT_SIZE_M(7)>;
            };
        };
    };
};

上面示例将一片8M的Flash分成了3个分区:

  • boot_partition: flash内偏于地址为0x0, 大小为512K

  • data_partition: flash内偏于地址为0x80000, 大小为512K

  • app_partition: flash内偏于地址为0x100000, 大小为7M

subsys/storage/flash_map/flash_map_default.c通过设备树的宏读取设备树中的partition信息并转换成struct flash_area结构体数组default_flash_map

//读取设备树
#define FLASH_AREA_FOO(part)                                                \
    {.fa_id = DT_FIXED_PARTITION_ID(part),                          \
    .fa_off = DT_REG_ADDR(part),                                    \
    .fa_dev_name = DT_LABEL(DT_MTD_FROM_FIXED_PARTITION(part)),     \
    .fa_size = DT_REG_SIZE(part),},

//遍历所有分区
#define FOREACH_PARTITION(n) DT_FOREACH_CHILD(DT_DRV_INST(n), FLASH_AREA_FOO)

//生成flash map数组
const struct flash_area default_flash_map[] = {
    DT_INST_FOREACH_STATUS_OKAY(FOREACH_PARTITION)
};

//flash map中含有分区的总数
const int flash_map_entries = ARRAY_SIZE(default_flash_map);

//flash map的指针
const struct flash_area *flash_map = default_flash_map;

上面示例设备树被转化成的default_flash_map为下面内容

const struct flash_area default_flash_map[] = {
    //bootloadr分区
    {.fa_id = 0,
    .fa_off = 0x0,
    .fa_dev_name = "IS25WP064",
    .fa_size = 0x80000,},

    //data分区
    {.fa_id = 1,
    .fa_off = 0x80000,
    .fa_dev_name = "IS25WP064",
    .fa_size = 0x80000,},

    //app分区
    {.fa_id = 2,
    .fa_off = 0x100000,
    .fa_dev_name = "IS25WP064",
    .fa_size = 0x700000,},

};

备注

fa_id是由DT_FIXED_PARTITION_ID(part)生成,设备树被解析产生在devicetree_unfixed.h中对应如下,也就是按照设备树partition的顺序进行排列的

#define DT_N_S_soc_S_spi_402a8000_S_is25wp064_0_S_partitions_S_partition_0_PARTITION_ID 0
#define DT_N_S_soc_S_spi_402a8000_S_is25wp064_0_S_partitions_S_partition_80000_PARTITION_ID 1
#define DT_N_S_soc_S_spi_402a8000_S_is25wp064_0_S_partitions_S_partition_100000_PARTITION_ID 2

运行时创建分区

运行时创建分区非常简单,直接定义一个struct flash_area变量,并按需求赋值即可。例如在实际写代码过程中,我想在data分区首部4k放一个零时的配置分区,那么可以

struct flash_area cfg_partition={
    .fa_id = 6,
    .fa_off = 0x80000,
    .fa_dev_name = "IS25WP064",
    .fa_size = 0x1000,
};

API

使用示例

下面示例简单演示在data_partition的0x2000处写入4096个byte的数据,再读出0x1000处4096个byte

const struct flash_area *partition;
//打开data_partition
flash_area_open(FLASH_AREA_ID(data_partition), &partition);
//擦除data_partition中偏移地址0x2000处4k大小的flash空间
flash_area_erase(partition, 0x2000, 4096);
//写入4K数据到data_partition中偏移地址0x2000
flase_area_write(partition, 0x2000, write_buf, 4096);
//从data_partition中偏移地址为0x1000处读出4K数据
flase_area_read(partition, 0x1000, read_buf, 4096);
//关闭partition
flase_area_close(partition);

下面示例运行时自定义的flase_area

//自定义flase_area
struct flash_area cfg_partition={
    .fa_id = 6,
    .fa_off = 0x80000,
    .fa_dev_name = "IS25WP064",
    .fa_size = 0x1000,
};
//擦除cfg_partition中偏移地址0x0处4k大小的flash空间
flash_area_erase(&cfg_partition, 0x0, 4096);
//写入4K数据到cfg_partition中偏移地址0x0
flase_area_write(&cfg_partition, 0x0, write_buf, 4096);

重要

注意: 运行时自定义的flase_area, 不需要使用flash_area_openflash_area_close进行管理,而能直接做读写操作.

API说明

flash_area的API几乎从API名和参数就可以自说明,这里就仅做简单注释说明,稍微复杂一点的API通过后文的API实现分析也能知道详细用途。

//判断flash partition是否存在
//例如FLASH_AREA_LABEL_EXISTS(data),会转换为一个宏,通过判断该宏是否被定义来判断label为data的partition是否存在
#define FLASH_AREA_LABEL_EXISTS(label) \
    DT_HAS_FIXED_PARTITION_LABEL(label)

//节点lbl的lable
//例如FLASH_AREA_LABEL_STR(data_partition)就是"data"
#define FLASH_AREA_LABEL_STR(lbl) \
    DT_PROP(DT_NODE_BY_FIXED_PARTITION_LABEL(lbl), label)

//指定partition的ID
//例如例如FLASH_AREA_LABEL_STR(data)就是1
#define FLASH_AREA_ID(label) \
    DT_FIXED_PARTITION_ID(DT_NODE_BY_FIXED_PARTITION_LABEL(label))

//分区在flash中的偏移地址
//例如FLASH_AREA_OFFSET(data)是0x8000
#define FLASH_AREA_OFFSET(label) \
    DT_REG_ADDR(DT_NODE_BY_FIXED_PARTITION_LABEL(label))

//分区的大小
//FLASH_AREA_SIZE(data)是512K
#define FLASH_AREA_SIZE(label) \
    DT_REG_SIZE(DT_NODE_BY_FIXED_PARTITION_LABEL(label))

//打开flash_area,获取指定id的flash_area
int flash_area_open(uint8_t id, const struct flash_area **fa);
//关闭flash_area
void flash_area_close(const struct flash_area *fa);
//读取flash_area内指定地址和长度的内容
int flash_area_read(const struct flash_area *fa, off_t off, void *dst,
            size_t len);
//向flash_area内指定地址写入指定长度的内容,写入长度要与flash_area_align返回的值大小对齐
int flash_area_write(const struct flash_area *fa, off_t off, const void *src,
            size_t len);
//擦除flash_arean内指定地址和长度的区域,擦除长度要满足flash本身或驱动的长度对齐限制,可以从flash_area_get_sectors得到。
int flash_area_erase(const struct flash_area *fa, off_t off, size_t len);
//获取flash_area写入对齐长度
uint8_t flash_area_align(const struct flash_area *fa);
//获取flash_area内扇区排列分布情况
int flash_area_get_sectors(int fa_id, uint32_t *count,
            struct flash_sector *sectors);

//使用user_cb遍历所有flash分区
void flash_area_foreach(flash_area_cb_t user_cb, void *user_data);

//坚持flash分区是否有驱动
int flash_area_has_driver(const struct flash_area *fa);

//获取flash分区的flash驱动device
const struct device *flash_area_get_device(const struct flash_area *fa);
uint8_t flash_area_erased_val(const struct flash_area *fa);

//对flash分区内指定区域的内容进行sha256校验比较
int flash_area_check_int_sha256(const struct flash_area *fa,
                const struct flash_area_check *fac);

实现分析

flash_area的实现比较简单,主要是对flash驱动进行了封装,并加入分区的管理。

open & close

flash_area_open就是根据id取得编译时确定好的struct flash_area数组成员指针

int flash_area_open(uint8_t id, const struct flash_area **fap)
{
    const struct flash_area *area;
    //没有flash_map
    if (flash_map == NULL) {
        return -EACCES;
    }

    //通过id找出匹配的flash_area
    //get_flash_area_from_id通过匹配flash_map中成员的fa_id和参数id,完成查找
    area = get_flash_area_from_id(id);
    if (area == NULL) {
        return -ENOENT;
    }

    *fap = area;
    return 0;
}

flash_area_close是一个空函数,未做任何事。

read & write & erase

读写擦三个API,操作的方式都一样:通过device_get_binding获取flash device,然后通过flash API操作。只列出read的实现说明:

int flash_area_read(const struct flash_area *fa, off_t off, void *dst,
            size_t len)
{
    const struct device *dev;

    //坚持操作的offset和len是否超过分区范围
    if (!is_in_flash_area_bounds(fa, off, len)) {
        return -EINVAL;
    }

    //获取flash device
    dev = device_get_binding(fa->fa_dev_name);

    //进行flash操作,对于写和擦除的实现这里就调用flase_write和flash_erase
    return flash_read(dev, fa->fa_off + off, dst, len);
}

//分区范围检查非常简单,就是将offset和len与分区的大小进行对比
static inline bool is_in_flash_area_bounds(const struct flash_area *fa,
                    off_t off, size_t len)
{
    return (off >= 0) && ((off + len) <= fa->fa_size);
}

获取flash分区相关信息

检查是否有flash驱动支持该flash分区

int flash_area_has_driver(const struct flash_area *fa)
{
    //获取分区flash驱动设备名对应的flash device,如果不为NULL表示有驱动可用
    if (device_get_binding(fa->fa_dev_name) == NULL) {
        return -ENODEV;
    }

    return 1;
}

获取flash分区所使用的flash device

const struct device *flash_area_get_device(const struct flash_area *fa)
{
    return device_get_binding(fa->fa_dev_name);
}

获取flash分析写入对齐长度

uint8_t flash_area_align(const struct flash_area *fa)
{
    const struct device *dev;

    dev = device_get_binding(fa->fa_dev_name);
    //写入对齐的长度和分区所在的flash写入对齐长度一致
    return flash_get_write_block_size(dev);
}

flash分区中被擦除位置的值

uint8_t flash_area_erased_val(const struct flash_area *fa)
{
    const struct flash_parameters *param;
    //和flash驱动擦除flash后的值一致
    param = flash_get_parameters(device_get_binding(fa->fa_dev_name));

    return param->erase_value;
}

分区sha256校验对比

当配置CONFIG_FLASH_AREA_CHECK_INTEGRITY=y后flash map提供flash_area_check_int_sha256支持对分区内指定区域的sha256计算并比较。

struct flash_area_check {
    const uint8_t *match;           // 256bit比较样本
    size_t clen;                    // 比较内容的长度
    size_t off;                             // 比较内容在分区内的偏移地址
    uint8_t *rbuf;                  // 计算sha256用的buf
    size_t rblen;                   // 计算sha256用的buf长度
};

//计算flash_area内从off开始内容长度为clen的sha256,并与match比较,一致时返回0,不一致时返回负数
int flash_area_check_int_sha256(const struct flash_area *fa,
                const struct flash_area_check *fac)
{
    unsigned char hash[TC_SHA256_DIGEST_SIZE];
    struct tc_sha256_state_struct sha;
    const struct device *dev;
    int to_read;
    int pos;
    int rc;

    if (fa == NULL || fac == NULL || fac->match == NULL ||
        fac->rbuf == NULL || fac->clen == 0 || fac->rblen == 0) {
        return -EINVAL;
    }

    //检查的sha256的内容是否已经超出分区范围
    if (!is_in_flash_area_bounds(fa, fac->off, fac->clen)) {
        return -EINVAL;
    }

    //初始化sha256计算模块
    if (tc_sha256_init(&sha) != TC_CRYPTO_SUCCESS) {
        return -ESRCH;
    }

    dev = device_get_binding(fa->fa_dev_name);
    to_read = fac->rblen;

    for (pos = 0; pos < fac->clen; pos += to_read) {
        if (pos + to_read > fac->clen) {
            to_read = fac->clen - pos;
        }

        //读出flash分区内的内容
        rc = flash_read(dev, (fa->fa_off + fac->off + pos),
                fac->rbuf, to_read);
        if (rc != 0) {
            return rc;
        }

        //计算读出内容的sha256值
        if (tc_sha256_update(&sha,
                    fac->rbuf,
                    to_read) != TC_CRYPTO_SUCCESS) {
            return -ESRCH;
        }
    }

    //完成hash计算
    if (tc_sha256_final(hash, &sha) != TC_CRYPTO_SUCCESS) {
        return -ESRCH;
    }

    //比较sha256
    if (memcmp(hash, fac->match, TC_SHA256_DIGEST_SIZE)) {
        return -EILSEQ;
    }

    return 0;
}

遍历flash分区

通过flash_area_foreach可以遍历所有的flash分区,读取flash分区信息

//遍历分区回调
typedef void (*flash_area_cb_t)(const struct flash_area *fa,
                void *user_data);

//用户实现一个flash_area_cb_t类型的回调user_cb,在函数内对flash_map逐个遍历,将flash_area的信息送给回调
void flash_area_foreach(flash_area_cb_t user_cb, void *user_data)
{
    for (int i = 0; i < flash_map_entries; i++) {
        user_cb(&flash_map[i], user_data);
    }
}

参考

https://docs.zephyrproject.org/latest/reference/storage/flash_map/flash_map.html