Nordic fds 文件系统源码探究

 

        项目中使用到了flash,而Nordic为操作flash准备了很丰富的工具,包括fstorage 和fds,两者都是异步返回真实操作结果的,其中fds依赖于fstorage,是一个简单易用的文件系统。

        使用fds之前一般都需要初始化一下,包括使用 fds_register  函数来注册一个事件回调,用来获得异步操作的结果。然后还需要使用 fds_init 函数进行初始化,完了之后就可以进行各种文件操作了。

        为了与物理地址隔离,fds使用了虚拟页的概念。使用fds之前需要配置好一个虚拟页分配多大空间,必须是物理页的空间(一般是4096 byte)的整数倍。然后还需要设置总共使用多少个虚拟页,可用的虚拟页是分配的数量减一,因为其中有一页被用作GC的缓存。

        这里从fds_init 开始追溯。

        为了防止反复初始化,fds_init 会首先检查是否已经初始化过了,如果已经做过初始化操作,则直接返回FDS_SUCCESS。从这里可以看出,fds_init函数可以反复调用,来确保自己的模块在使用fds的时候将其正确的初始化。

        fds依赖于fstorage,任意的fds操作最终都会转化为fstorage操作,这里首先需要有一个fstorage的句柄。       

NRF_FSTORAGE_DEF(nrf_fstorage_t m_fs) =
{
    // The flash area boundaries are set in fds_init().
    .evt_handler = fs_event_handler,
};

        其中,fs_event_handler为fstorage的异步回调函数。

    if (m_flags.initialized)
    {
        // No initialization is necessary. Notify the application immediately.
        event_send(&evt_success);
        return FDS_SUCCESS;
    }

    if (nrf_atomic_flag_set_fetch(&m_flags.initializing))
    {
        // If we were already initializing, return.
        return FDS_SUCCESS;
    }

       接下来的一个操作是flash_subsystem_init,字面上无法理解是干嘛的。进去一看,该函数主要有两个操作,一个是flash_bounds_set,然后是nrf_fstorage_init。

        因为fds依赖于fstorage,因此fds的初始化操作必然要进行fstorage的初始化。

        然后从函数名可以知道flash_bounds_set是确定如何分配fds的真实页面地址和边界,这里隐含的信息就是,fds所分配的真实页面是一块连续的区域。

        flash_bounds_set函数主要内容如下:        

static void flash_bounds_set(void)
{
    uint32_t flash_size  = (FDS_PHY_PAGES * FDS_PHY_PAGE_SIZE * sizeof(uint32_t));
    m_fs.end_addr   = flash_end_addr();
    m_fs.start_addr = m_fs.end_addr - flash_size;
}

         这里的FDS_PHY_PAGE_SIZE 是以一个 word为单位的,占用4个字节, flash_size即为fds所占用的真实的flash空间大小,这里通过如下的映射关系来确定flash_size的大小:

// The size of a physical page, in 4-byte words.
#if defined(NRF51)
    #define FDS_PHY_PAGE_SIZE   (256)
#else
    #define FDS_PHY_PAGE_SIZE   (1024)
#endif

// The number of physical pages to be used. This value is configured indirectly.
#define FDS_PHY_PAGES               ((FDS_VIRTUAL_PAGES * FDS_VIRTUAL_PAGE_SIZE) / FDS_PHY_PAGE_SIZE)

        然后通过flash最末尾的地址间接计算出真实页的起始地址。flash_end_addr函数用于得到flash的末尾的地址,如果flash中含有bootloader,则该地址为bootloader的起始地址。

static uint32_t flash_end_addr(void)
{
    uint32_t const bootloader_addr = NRF_UICR->NRFFW[0];
    uint32_t const page_sz         = NRF_FICR->CODEPAGESIZE;
#ifndef NRF52810_XXAA
    uint32_t const code_sz         = NRF_FICR->CODESIZE;
#else
    // Number of flash pages, necessary to emulate the NRF52810 on NRF52832.
    uint32_t const code_sz         = 48;
#endif

    return (bootloader_addr != 0xFFFFFFFF) ? bootloader_addr : (code_sz * page_sz);
}

        这里NRF_UICR->NRFFW[0] 具体保存的是什么值,datasheet中并未说明,但是既然是uicr的域,应该是存储什么值都是可以的。但是sdk中怎么都搜索不到这个域。然后不死心去bootloader的源码中搜索,居然也没有!本着打破砂锅问到底的原则,不死心的重新去研究datasheet,注意到UICR的base address是 0x10001000,然后 NRFFW[0]的offset是 0x14。这下反应过来了,之前研究bootloader源码的时候,有一个define 是NRF_UICR_BOOTLOADER_START_ADDRESS,在nrf_bootloader_info.h中。查看定义:

/**
 * @brief Bootloader start address in UICR.
 *
 * Register location in UICR where the bootloader start address is stored.
 *
 * @note If the value at the given location is 0xFFFFFFFF, the bootloader address is not set.
 */
#define NRF_UICR_BOOTLOADER_START_ADDRESS       (NRF_UICR_BASE + 0x14)

        哈,这下清楚了,NRF_UICR->NRFFW[0] 指向的就是bootloader的起始地址,由bootloader负责初始化这个值,只要烧录的文件中含有bootloader,这里就会有一个不为0xFFFFFFFF的值。

        然后,初始化操作队列,nordic提供了 atfifo供大家使用。nordic的sdk中存在很多预定义的宏,使用起来极为方便,这里一句话就搞定了队列的初始化。

        然后,初始化的最后一步,是对页面进行初始化。这里需要将虚拟页一页一页的映射到真实的物理页,分配的虚拟页面中,有一页需要作为在GC的缓存页。

        完整的页面初始化函数如下:

// This function is called during initialization to setup the page structure (m_pages) and
// provide additional information regarding eventual further initialization steps.
static fds_init_opts_t pages_init(void)
{
    uint32_t ret                    = NO_PAGES;
    uint16_t page                   = 0;
    uint16_t total_pages_available  = FDS_VIRTUAL_PAGES;
    bool     swap_set_but_not_found = false;

    for (uint16_t i = 0; i < FDS_VIRTUAL_PAGES; i++)
    {
        uint32_t        const * const p_page_addr = (uint32_t*)m_fs.start_addr + (i * FDS_PAGE_SIZE);
        fds_page_type_t const         page_type   = page_identify(p_page_addr);

        switch (page_type)
        {
            case FDS_PAGE_UNDEFINED:
            {
                if (page_is_erased(p_page_addr))
                {
                    if (m_swap_page.p_addr != NULL)
                    {
                        // If a swap page is already set, flag the page as erased (in m_pages)
                        // and try to tag it as data (in flash) later on during initialization.
                        m_pages[page].page_type    = FDS_PAGE_ERASED;
                        m_pages[page].p_addr       = p_page_addr;
                        m_pages[page].write_offset = FDS_PAGE_TAG_SIZE;

                        // This is a candidate for a potential new swap page, in case the
                        // current swap is going to be promoted to complete a GC instance.
                        m_gc.cur_page = page;
                        page++;
                    }
                    else
                    {
                        // If there is no swap page yet, use this one.
                        m_swap_page.p_addr       = p_page_addr;
                        m_swap_page.write_offset = FDS_PAGE_TAG_SIZE;
                        swap_set_but_not_found   = true;
                    }

                    ret |= PAGE_ERASED;
                }
                else
                {
                    // The page contains non-FDS data.
                    // Do not initialize or use this page.
                    total_pages_available--;
                    m_pages[page].p_addr    = p_page_addr;
                    m_pages[page].page_type = FDS_PAGE_UNDEFINED;
                    page++;
                }
            } break;

            case FDS_PAGE_DATA:
            {
                m_pages[page].page_type = FDS_PAGE_DATA;
                m_pages[page].p_addr    = p_page_addr;

                // Scan the page to compute its write offset and determine whether or not the page
                // can be garbage collected. Additionally, update the latest kwown record ID.
                page_scan(p_page_addr, &m_pages[page].write_offset, &m_pages[page].can_gc);

                ret |= PAGE_DATA;
                page++;
            } break;

            case FDS_PAGE_SWAP:
            {
                if (swap_set_but_not_found)
                {
                    m_pages[page].page_type    = FDS_PAGE_ERASED;
                    m_pages[page].p_addr       = m_swap_page.p_addr;
                    m_pages[page].write_offset = FDS_PAGE_TAG_SIZE;

                    page++;
                }

                m_swap_page.p_addr = p_page_addr;
                // If the swap is promoted, this offset should be kept, otherwise,
                // it should be set to FDS_PAGE_TAG_SIZE.
                page_scan(p_page_addr, &m_swap_page.write_offset, NULL);

                ret |= (m_swap_page.write_offset == FDS_PAGE_TAG_SIZE) ?
                        PAGE_SWAP_CLEAN : PAGE_SWAP_DIRTY;
            } break;

            default:
                // Shouldn't happen.
                break;
        }
    }

    if (total_pages_available < 2)
    {
        ret &= NO_PAGES;
    }

    return (fds_init_opts_t)ret;
}

        这里没什么好说的,初始化flash当然要日常遍历一下,需要注意的是,每个页面的地址是由(uint32_t*)m_fs.start_addr+ i*1024,注意这里将start_addr转换成了 uint32_t 的指针类型,所以每增加一个1024,实际的地址增量是 1024*4。

uint32_t        const * const p_page_addr = (uint32_t*)m_fs.start_addr + (i * FDS_PAGE_SIZE);

        然后,得到每个页面的状态,可见fds会在每个页面的预设地址上标记页面的状态,就和fds的每个record都有一个header一样,可以理解为每个页面也有一个类似header的这么个玩意。page_indentify函数没有什么好说的,就是查询p_page_addr对应的某个固定便宜地址上的tag是什么值。

fds_page_type_t const         page_type   = page_identify(p_page_addr);

        正常情况下,烧录程序前会彻底擦除一遍flash,因此这里所有的页面都是FDS_PAGE_UNDEFINED 状态(0xFF)。但是这里考虑了非fds存入数据的情况,比如在非fds标记页面头的位置写入了一些数据。所以这里page_is_erased(p_page_addr) 函数会暴力遍历该页面的每一个字节,只有整个页面完全为空的情况下才会将本页面分配给fds。其实月面头的标记就两个,一个是数据页,一个是交换页,被标记为交换页的页面用于GC缓存。

        到这里,页面初始化的操作就完成了,后面还有一些队列的操作就会彻底完成fds的初始化。fds还有很多实现细节后边如果有时间写会有后续更新。

 

你可能感兴趣的:(#,BLE,物联网)