From 115cbcc1b2b513e2be29d78a785783506d49b72c Mon Sep 17 00:00:00 2001 From: Frank Li Date: Sat, 26 Mar 2022 12:49:16 +0800 Subject: [PATCH] doc: flash stream Analysis of the implementation mechanism of flash stream. Signed-off-by: Frank Li --- .../develop/subsys/storage/flash_stream.rst | 583 ++++++++++++++++++ .../develop/subsys/storage/stream_flash.png | Bin 0 -> 28575 bytes doc/source/index.rst | 2 +- 3 files changed, 584 insertions(+), 1 deletion(-) create mode 100644 doc/source/develop/subsys/storage/flash_stream.rst create mode 100644 doc/source/images/develop/subsys/storage/stream_flash.png diff --git a/doc/source/develop/subsys/storage/flash_stream.rst b/doc/source/develop/subsys/storage/flash_stream.rst new file mode 100644 index 0000000..aca5466 --- /dev/null +++ b/doc/source/develop/subsys/storage/flash_stream.rst @@ -0,0 +1,583 @@ +.. _develop_subsys_storage_flash_stream: + +Zephyr flash流式操作 +######################### + +本文分析Zephyr的flash流式操作如何实现。 + +概述 +===== + +Flash在写入前必须擦除,擦除有page的限制,写入根据设备的不同可能会有对齐的限制。由于其操作的特殊性Flash无法直接接受流式数据的写入(连续的大小不定的数据),为应对该情况Zephyr对flash驱动进行了封装提供stream_flash,用于支持流式数据的写入。 +stream_flash模块的特性如下: + +* 提供缓存,将流式数据收集到指定量后再写入到Flash +* 支持缓存回读 +* 支持断电恢复机制 + +stream_flash最典型的应用就是DFU升级,在Zephyr中DFU升级和coredump写入flash都使用了stream_flash。 + +API +==== + +API说明 +~~~~~~~ + +flash_stream的API声明在\ ``include/storage/stream_flash.h``\中,这里做注释说明 + +.. code:: c + + /** + * @brief 初始化flash_stream. + * + * @param ctx flash_stream的上下文,相关的管理数据都保存在其中 + * @param fdev flash_stream使用的flash设备,流式数据将写入该设备 + * @param buf flash_stream使用的缓存,写入的流式数据将先保存到该缓存 + * @param buf_len 缓存大小,不能大于flash page尺寸,并按照flash的最小可写单位write-block-size对齐。 + * @param offset flash设备中的偏移地址 + * @param size 最大可写入量。当设置为0时表示可以从offset开始一直写到flash末尾 + * @param cb 每次flash写操作完成后就会调用这个回调 + * + * @return 0为成功,负数为失败 + */ + int stream_flash_init(struct stream_flash_ctx *ctx, const struct device *fdev, + uint8_t *buf, size_t buf_len, size_t offset, size_t size, + stream_flash_callback_t cb); + + /** + * @brief 写入数据到flash stream + * + * @param ctx flash_stream的上下文 + * @param data 写入数据指针 + * @param len 写入数据长度 + * @param flush 最后一笔写入时使用true强制写入到flash内,当为false时只有在stream_flash满时才会写flash + * + * @return 0为成功,负数为失败 + */ + int stream_flash_buffered_write(struct stream_flash_ctx *ctx, const uint8_t *data, + size_t len, bool flush); + + /** + * @brief 获取目前有多少数据被写入到flash. + * + * @param ctx flash_stream的上下文 + * + * @return 已写入到flash的数据长度. + */ + size_t stream_flash_bytes_written(struct stream_flash_ctx *ctx); + + +在配置`CONFIG_STREAM_FLASH_ERASE=y`的情况下提供擦除API + +.. code:: c + + /** + * @brief 擦除flash页 + * + * @param ctx flash_stream的上下文 + * @param off 擦除off所在页面 + * + * @return 0为成功,负数为失败 + */ + int stream_flash_erase_page(struct stream_flash_ctx *ctx, off_t off); + + +在配置`CONFIG_STREAM_FLASH_PROGRESS=y`的情况下提供恢复机制API + +.. code:: c + + /** + * @brief 加载上一次保存的写入进度到flash_stream上下文中 + * + * @param ctx flash_stream的上下文 + * @param settings_key setting模块中标识进度的key + * + * @return 0为成功,负数为失败 + */ + int stream_flash_progress_load(struct stream_flash_ctx *ctx, + const char *settings_key); + + /** + * @brief 保存当前flash_stream的写进度 . + * + * @param ctx flash_stream的上下文 + * @param settings_key setting模块中标识进度的key + * + * @return 0为成功,负数为失败 + */ + int stream_flash_progress_save(struct stream_flash_ctx *ctx, + const char *settings_key); + + /** + * @brief 清除flash_stream保存的进度. + * + * @param ctx flash_stream的上下文 + * @param settings_key setting模块中标识进度的key + * + * @return 0为成功,负数为失败 + */ + int stream_flash_progress_clear(struct stream_flash_ctx *ctx, + const char *settings_key); + + +使用示例 +~~~~~~~~ + +这里做简单的使用示例如下注释,当不需要做断电恢复时,移除\ ``stream_flash_progress_xxx``\相关流程即可 + +.. code:: c + + #define STREAM_FLASH_KEY "prop/stream_flash_key" + + struct stream_flash_ctx stream_flash; + + int data_verify(uint8_t *buf, size_t len, size_t offset) + { + //校验回读数据 + ... + + //通知显示当前下载进度last_writed + size_t last_writed = stream_flash_bytes_written() + .... + + //保存当前下载进度 + stream_flash_progress_save(&stream_flash, STREAM_FLASH_KEY); + + //返回0表示校验通过 + return 0; + } + + void download_task(void) + { + const struct device *fdev = device_get_binding(FLASH_NAME); + uint_t buf[64*1024]; + + //设用FLASH_NAME设备 + //缓存为buf,大小为64K + //stream_flash,位于flash的0x100000处,大小为3M + int stream_flash_init(&stream_flash, fdev, + buf, sizeof(buf), 0x100000, 3*1024*1024, + data_verify); + + //加载上次位置 + stream_flash_progress_load(&stream_flash, STREAM_FLASH_KEY); + + //读取上次写入的位置,通知从该处下载数据 + size_t last_writed = stream_flash_bytes_written() + .... nodify(last_writed) + + while(1){ + uint8 rev_buf[32]; + size_t rev_len; + //接收数据 + int finish = revice(rev_buf, &rev_len); + + if(finish){ + //下载完所有数据成做flush写 + stream_flash_buffered_write(&stream_flash, rev_buf, rev_len, true); + //清除下载进度,退出下载 + stream_flash_progress_clear(&stream_flash, STREAM_FLASH_KEY); + break; + }else{ + //写入当前下载数据,继续下载 + stream_flash_buffered_write(&stream_flash, rev_buf, rev_len, false); + } + } + } + + + +实现分析 +======== + +stream flash实现的核心就是将数据缓存到buffer中,当buffer满后一次性写入到flash。通过\ ``struct stream_flash_ctx``\进行管理 + +.. code:: c + + struct stream_flash_ctx { + uint8_t *buf; //写缓存,写入数据时先保存在该buffer中 + size_t buf_len; //写缓存的长度 + size_t buf_bytes; //写缓存中有效数据的长度 + const struct device *fdev; //stream_flash操控的flash device,数据实际要被写入到该flash设备上 + size_t bytes_written; //总计写了多少数据到flash + size_t offset; //stream_flash从flash内该偏移地址开始使用 + size_t available; //flash内还剩多少区域可以被stream_flash使用 + stream_flash_callback_t callback; //每写一次flash,调用一次该callback + #ifdef CONFIG_STREAM_FLASH_ERASE + off_t last_erased_page_start_offset; //上一次擦除flash page所在的地址 + #endif + }; + +各字段图示如下,后面的分析可以参考该图进行理解 + +.. image:: ../../../images/develop/subsys/storage/stream_flash.png + +初始化 +~~~~~~ + +stream flash初始化主要完成:对setting系统的初始化用于存储下载进度,完成对\ ``struct stream_flash_ctx``\的初始化赋值。 + +.. code:: c + + int stream_flash_init(struct stream_flash_ctx *ctx, const struct device *fdev, + uint8_t *buf, size_t buf_len, size_t offset, size_t size, + stream_flash_callback_t cb) + { + if (!ctx || !fdev || !buf) { + return -EFAULT; + } + + //初始化setting子系统,为存储下载进度做准备 + #ifdef CONFIG_STREAM_FLASH_PROGRESS + int rc = settings_subsys_init(); + + if (rc != 0) { + LOG_ERR("Error %d initializing settings subsystem", rc); + return rc; + } + #endif + + struct _inspect_flash inspect_flash_ctx = { + .buf_len = buf_len, + .total_size = 0 + }; + + //缓存的长度必须与write-block-size对齐 + if (buf_len % flash_get_write_block_size(fdev)) { + LOG_ERR("Buffer size is not aligned to minimal write-block-size"); + return -EFAULT; + } + + //遍历flash的页,计算flash的大小 + flash_page_foreach(fdev, find_flash_total_size, &inspect_flash_ctx); + + /* The flash size counted should never be equal zero */ + if (inspect_flash_ctx.total_size == 0) { + return -EFAULT; + } + + //检查stream_flash使用的范围是否在flash内 + if ((offset + size) > inspect_flash_ctx.total_size || + offset % flash_get_write_block_size(fdev)) { + LOG_ERR("Incorrect parameter"); + return -EFAULT; + } + + //初始化flash_stream管理上下文 + ctx->fdev = fdev; + ctx->buf = buf; + ctx->buf_len = buf_len; + ctx->bytes_written = 0; + ctx->buf_bytes = 0U; + ctx->offset = offset; + //计算flash_stream实际可使用的flash空间,size为0时,就是从offset开始到flash结束的长度 + ctx->available = (size == 0 ? inspect_flash_ctx.total_size - offset : + size); + ctx->callback = cb; + + #ifdef CONFIG_STREAM_FLASH_ERASE + ctx->last_erased_page_start_offset = -1; + #endif + + return 0; + } + + +写入 +~~~~ + +.. code:: c + + int stream_flash_buffered_write(struct stream_flash_ctx *ctx, const uint8_t *data, + size_t len, bool flush) + { + int processed = 0; + int rc = 0; + int buf_empty_bytes; + + if (!ctx) { + return -EFAULT; + } + + //计算flash内剩余的空间是否能容纳还没写入的数据总长度 + if (ctx->bytes_written + ctx->buf_bytes + len > ctx->available) { + return -ENOMEM; + } + + //写入flash_stream数据比缓存空间大时,先将缓存空间填满,再写入到flash + while ((len - processed) >= + (buf_empty_bytes = ctx->buf_len - ctx->buf_bytes)) { + //填满缓存空间 + memcpy(ctx->buf + ctx->buf_bytes, data + processed, + buf_empty_bytes); + + //将缓存空间数据写到flash + ctx->buf_bytes = ctx->buf_len; + rc = flash_sync(ctx); + + if (rc != 0) { + return rc; + } + + processed += buf_empty_bytes; + } + + //剩余的数据不足以填满缓存空间的,就保留在缓存空间 + if (processed < len) { + memcpy(ctx->buf + ctx->buf_bytes, + data + processed, len - processed); + ctx->buf_bytes += len - processed; + } + + //如果指定flush,无论缓存空间剩多少都一次性写入到flash中 + if (flush && ctx->buf_bytes > 0) { + rc = flash_sync(ctx); + } + + return rc; + } + + +flash写入流程,由于缓存的大小小于页,因此写入可以直接以页来操作 + +.. code:: c + + static int flash_sync(struct stream_flash_ctx *ctx) + { + int rc = 0; + //计算flash内写入地址 + size_t write_addr = ctx->offset + ctx->bytes_written; + size_t buf_bytes_aligned; + size_t fill_length; + uint8_t filler; + + + if (ctx->buf_bytes == 0) { + return 0; + } + + //擦除页,页所在位置由写入数据最后一byte位置计算而得 + if (IS_ENABLED(CONFIG_STREAM_FLASH_ERASE)) { + + rc = stream_flash_erase_page(ctx, + write_addr + ctx->buf_bytes - 1); + if (rc < 0) { + LOG_ERR("stream_flash_erase_page err %d offset=0x%08zx", + rc, write_addr); + return rc; + } + } + + //写入的数据不能和write-block-size对齐时,未对齐部分填入擦除flash后的值,通常时0xff + //该操作只会在最后一笔数据flush发生 + fill_length = flash_get_write_block_size(ctx->fdev); + if (ctx->buf_bytes % fill_length) { + fill_length -= ctx->buf_bytes % fill_length; + filler = flash_get_parameters(ctx->fdev)->erase_value; //获取擦除flash后的值,通常是0xff + + memset(ctx->buf + ctx->buf_bytes, filler, fill_length); + } else { + fill_length = 0; + } + + //写入flash + buf_bytes_aligned = ctx->buf_bytes + fill_length; + rc = flash_write(ctx->fdev, write_addr, ctx->buf, buf_bytes_aligned); + + if (rc != 0) { + LOG_ERR("flash_write error %d offset=0x%08zx", rc, + write_addr); + return rc; + } + + if (ctx->callback) { + /* Invert to ensure that caller is able to discover a faulty + * flash_read() even if no error code is returned. + */ + for (int i = 0; i < ctx->buf_bytes; i++) { + ctx->buf[i] = ~ctx->buf[i]; + } + + rc = flash_read(ctx->fdev, write_addr, ctx->buf, + ctx->buf_bytes); + if (rc != 0) { + LOG_ERR("flash read failed: %d", rc); + return rc; + } + + rc = ctx->callback(ctx->buf, ctx->buf_bytes, write_addr); + if (rc != 0) { + LOG_ERR("callback failed: %d", rc); + return rc; + } + } + + ctx->bytes_written += ctx->buf_bytes; + ctx->buf_bytes = 0U; + + return rc; + } + + +上面你可能会发现,无论缓存是多大,即使小于一个page都会被执行\ ``stream_flash_erase_page``\,这会不会导致前面写入在同一页得数据被擦除呢? 我们来看一下其实现 + +.. code:: c + + int stream_flash_erase_page(struct stream_flash_ctx *ctx, off_t off) + { + int rc; + struct flash_pages_info page; + + //找到要擦除的页 + rc = flash_get_page_info_by_offs(ctx->fdev, off, &page); + if (rc != 0) { + LOG_ERR("Error %d while getting page info", rc); + return rc; + } + + //如果该页的地址和上一次擦除的一致,就不再执行擦除,从而避免丢失 + if (ctx->last_erased_page_start_offset == page.start_offset) { + return 0; + } + + LOG_DBG("Erasing page at offset 0x%08lx", (long)page.start_offset); + //执行擦除 + rc = flash_erase(ctx->fdev, page.start_offset, page.size); + + if (rc != 0) { + LOG_ERR("Error %d while erasing page", rc); + } else { + //更新上一次擦除地址 + ctx->last_erased_page_start_offset = page.start_offset; + } + + return rc; + } + + +恢复机制实现 +~~~~~~~~~~~~ + +恢复机制使用setting模块存储和改写\ ``struct stream_flash_ctx``\中的\ ``bytes_written``\字段完成,setting是一个可读写模块后端可以对接nvs或者文件等,这里不做展开说明,只用指定setting以key+value的map形式保存即可。 + +.. code:: c + + int stream_flash_progress_save(struct stream_flash_ctx *ctx, + const char *settings_key) + { + if (!ctx || !settings_key) { + return -EFAULT; + } + //将bytes_written写入到setting中 + int rc = settings_save_one(settings_key, + &ctx->bytes_written, + sizeof(ctx->bytes_written)); + + if (rc != 0) { + LOG_ERR("Error %d while storing progress for \"%s\"", + rc, settings_key); + } + + return rc; + } + + + int stream_flash_progress_load(struct stream_flash_ctx *ctx, + const char *settings_key) + { + if (!ctx || !settings_key) { + return -EFAULT; + } + + //在回调settings_direct_loader中对cts中的bytes_written进行更新 + int rc = settings_load_subtree_direct(settings_key, + settings_direct_loader, + (void *) ctx); + + if (rc != 0) { + LOG_ERR("Error %d while loading progress for \"%s\"", + rc, settings_key); + } + + return rc; + } + + static int settings_direct_loader(const char *key, size_t len, + settings_read_cb read_cb, void *cb_arg, + void *param) + { + struct stream_flash_ctx *ctx = (struct stream_flash_ctx *) param; + + //查找满足条件的key + if (settings_name_next(key, NULL) == 0) { + size_t bytes_written = 0; + + //通过setting的callback和key读出bytes_written + ssize_t len = read_cb(cb_arg, &bytes_written, + sizeof(bytes_written)); + + //判断读出的长度是否合法 + if (len != sizeof(ctx->bytes_written)) { + LOG_ERR("Unable to read bytes_written from storage"); + return len; + } + + //更新ctx中的bytes_written + if (bytes_written >= ctx->bytes_written) { + ctx->bytes_written = bytes_written; + } else { + LOG_WRN("Loaded outdated bytes_written %zu < %zu", + bytes_written, ctx->bytes_written); + return 0; + } + + //更新last_erased_page_start_offset + #ifdef CONFIG_STREAM_FLASH_ERASE + int rc; + struct flash_pages_info page; + off_t offset = (off_t) (ctx->offset + ctx->bytes_written) - 1; + + /* Update the last erased page to avoid deleting already + * written data. + */ + if (ctx->bytes_written > 0) { + rc = flash_get_page_info_by_offs(ctx->fdev, offset, + &page); + if (rc != 0) { + LOG_ERR("Error %d while getting page info", rc); + return rc; + } + ctx->last_erased_page_start_offset = page.start_offset; + } else { + ctx->last_erased_page_start_offset = -1; + } + #endif /* CONFIG_STREAM_FLASH_ERASE */ + } + + return 0; + } + + + int stream_flash_progress_clear(struct stream_flash_ctx *ctx, + const char *settings_key) + { + if (!ctx || !settings_key) { + return -EFAULT; + } + //删除存储的进度 + int rc = settings_delete(settings_key); + + if (rc != 0) { + LOG_ERR("Error %d while deleting progress for \"%s\"", + rc, settings_key); + } + + return rc; + } + + +参考 +==== + +https://docs.zephyrproject.org/latest/reference/storage/stream/stream_flash.html + diff --git a/doc/source/images/develop/subsys/storage/stream_flash.png b/doc/source/images/develop/subsys/storage/stream_flash.png new file mode 100644 index 0000000000000000000000000000000000000000..5989166f9fd78dda8815d55a699826baa8a240cb GIT binary patch literal 28575 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