From 0b6ef7057a264f7834c18a56af44910e6ef00bdb Mon Sep 17 00:00:00 2001 From: Frank Li Date: Sun, 20 Mar 2022 22:31:38 +0800 Subject: [PATCH] doc: flash map Analysis of the implementation mechanism of flash map. Signed-off-by: Frank Li --- .../develop/subsys/storage/flash_map.rst | 484 ++++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 485 insertions(+) create mode 100644 doc/source/develop/subsys/storage/flash_map.rst diff --git a/doc/source/develop/subsys/storage/flash_map.rst b/doc/source/develop/subsys/storage/flash_map.rst new file mode 100644 index 0000000..4440ef0 --- /dev/null +++ b/doc/source/develop/subsys/storage/flash_map.rst @@ -0,0 +1,484 @@ +.. _develop_subsys_storage_flash_map: + +Zephyr Flash Map实现分析 +######################### + + 在嵌入式系统中,通常会根据功能对Flash区域进行划分,然后按照区域进行使用。Zephyr的Flash Map模块对该功能进行抽象并API封装,方便对Flash区域的封装和使用。 + +分区 +==== + +在zephyr的\ ``include/storage/flash_map.h``\中定义了描述分区的结构体\ ``struct flash_area``\, 各描述字段如下 + +.. code:: c + + 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的分区表在编译时决定,运行时无法被修改。 +下面示例设备树: + +.. code:: devicetree + + &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``\ + +.. code:: c + + //读取设备树 + #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``\为下面内容 + +.. code:: c + + 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,}, + + }; + +.. note:: + + fa_id是由\ ``DT_FIXED_PARTITION_ID(part)``\生成,设备树被解析产生在\ ``devicetree_unfixed.h``\中对应如下,也就是按照设备树partition的顺序进行排列的 +.. code:: c + + #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放一个零时的配置分区,那么可以 + +.. code:: c + + 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 + +.. code:: c + + 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 + +.. code:: c + + //自定义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); + +.. important:: + + 注意: 运行时自定义的flase_area, 不需要使用\ ``flash_area_open``\和\ ``flash_area_close``\进行管理,而能直接做读写操作. + +API说明 +~~~~~~~~~~~~~~~ + +flash_area的API几乎从API名和参数就可以自说明,这里就仅做简单注释说明,稍微复杂一点的API通过后文的API实现分析也能知道详细用途。 + +.. code:: c + + //判断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``\数组成员指针 + +.. code:: c + + 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的实现说明: + +.. code:: c + + 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分区 + +.. code:: c + + 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 + +.. code:: c + + const struct device *flash_area_get_device(const struct flash_area *fa) + { + return device_get_binding(fa->fa_dev_name); + } + +获取flash分析写入对齐长度 + +.. code:: c + + 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分区中被擦除位置的值 + +.. code:: c + + 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计算并比较。 + +.. code:: c + + 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分区信息 + +.. code:: c + + //遍历分区回调 + 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 \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst index 99bdfef..ad5cec8 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -19,6 +19,7 @@ Zephyr Project中文资料站 develop/vscode.rst develop/write_doc.rst develop/driver/flash_introduction.rst + develop/subsys/storage/flash_map.rst -- Gitee