Zephyr Flash Map实现分析

Creative Commons
本作品采用知识共享署名

本文说明Zephyr Flash Map的实现和API。

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

分区

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

1
2
3
4
5
6
7
8
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的分区表在编译时决定,运行时无法被修改。
下面示例设备树:

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
&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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//读取设备树
#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为下面内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的顺序进行排列的

1
2
3
#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放一个零时的配置分区,那么可以

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
8
9
10
11
//自定义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实现分析也能知道详细用途

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
//判断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数组成员指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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的实现说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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分区

1
2
3
4
5
6
7
8
9
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

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

获取flash分析写入对齐长度

1
2
3
4
5
6
7
8
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分区中被擦除位置的值

1
2
3
4
5
6
7
8
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计算并比较。

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
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分区信息

1
2
3
4
5
6
7
8
9
10
11
//遍历分区回调
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