You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.7 KiB
134 lines
3.7 KiB
#include <flash.h>
|
|
#include <ftl.h>
|
|
#include <macros.h>
|
|
|
|
#include "gc.h"
|
|
|
|
int read_page_desc(struct ftl_map *map,
|
|
struct ftl_page_desc *page_desc, uint32_t upage);
|
|
int trace_path(struct ftl_map *map,
|
|
struct ftl_page_desc *new_page_desc, uint32_t *loc, uint32_t va);
|
|
int write_upage(struct ftl_map *map, const uint8_t *page,
|
|
const struct ftl_page_desc *page_desc);
|
|
|
|
/* For a given user page, attempt to claim more free space by checking if no
|
|
* recent mapping has obsoleted the older mapping. If a more recent mapping
|
|
* exists, the page can be safely ignored and erased. Otherwise, we preserve
|
|
* the page by copying the page to create a new mapping such that the old page
|
|
* can be ignored and erased.
|
|
*/
|
|
static int free_page(struct ftl_map *map, uint32_t upage)
|
|
{
|
|
struct ftl_page_desc page_desc;
|
|
uint32_t found_upage, va;
|
|
|
|
if (read_page_desc(map, &page_desc, upage) < 0)
|
|
return -1;
|
|
|
|
va = page_desc.va;
|
|
|
|
if (trace_path(map, &page_desc, &found_upage, va) < 0)
|
|
return -1;
|
|
|
|
if (upage != found_upage)
|
|
return 0;
|
|
|
|
page_desc.nused_pages = map->nused_pages;
|
|
|
|
if (flash_copy(map->dev, map->head << map->log2_page_size,
|
|
upage << map->log2_page_size, 1 << map->log2_page_size) == 0)
|
|
return -1;
|
|
|
|
return write_upage(map, NULL, &page_desc);
|
|
}
|
|
|
|
/* Claim more free space by checking which user pages in a page group are
|
|
* mapped and for which the mappings have been obsoleted by a more recent
|
|
* mapping. The mapped user pages are preserved by copying.
|
|
*/
|
|
static int free_group(struct ftl_map *map, uint32_t group)
|
|
{
|
|
uint32_t npages = UINT32_C(1) << map->log2_pages_per_group;
|
|
uint32_t page = group << map->log2_pages_per_group;
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < npages; ++i) {
|
|
if (free_page(map, page + i) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Claim more free space by checking which user pages in a block are mapped and
|
|
* for which the mappings have been obsoleted by a more recent mapping. The
|
|
* mapped user pages are preserved by copying.
|
|
*/
|
|
static int free_block(struct ftl_map *map, uint32_t block)
|
|
{
|
|
uint32_t ngroups = UINT32_C(1) << map->log2_groups_per_block;
|
|
uint32_t group = block << map->log2_groups_per_block;
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < ngroups; ++i) {
|
|
if (free_group(map, group + i) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Checks if there are sufficient pages available for writing. Otherwise this
|
|
* function attempts to claim more free space from unmapped pages for which
|
|
* newer pages have obsoleted the mapping. Further, we move the user pages that
|
|
* are still mapped as these should be preserved.
|
|
*/
|
|
static int free_tail(struct ftl_map *map)
|
|
{
|
|
size_t log2_pages_per_block = map->log2_pages_per_group +
|
|
map->log2_groups_per_block;
|
|
size_t npages = map->nblocks << log2_pages_per_block;
|
|
size_t dist;
|
|
|
|
if (map->head < map->tail)
|
|
dist = map->tail - map->head;
|
|
else
|
|
dist = npages - map->head + map->tail;
|
|
|
|
if (dist > (UINT32_C(1) << log2_pages_per_block))
|
|
return 0;
|
|
|
|
if (free_block(map, map->tail >> log2_pages_per_block) < 0)
|
|
return -1;
|
|
|
|
map->tail += 1 << log2_pages_per_block;
|
|
|
|
if (map->tail >= npages)
|
|
map->tail -= npages;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Prepare the head for writing. If the user page to be written to is not
|
|
* aligned on a block boundary, the block must already be erased and there is
|
|
* nothing to be done. Otherwise, we free the tail if necessary and erase the
|
|
* block for writing.
|
|
*/
|
|
#ifdef prepare_head
|
|
#undef prepare_head
|
|
#define prepare_head __real_prepare_head
|
|
#endif
|
|
|
|
int prepare_head(struct ftl_map *map)
|
|
{
|
|
size_t log2_pages_per_block = map->log2_pages_per_group +
|
|
map->log2_groups_per_block;
|
|
|
|
if (!is_aligned(map->head, log2_pages_per_block))
|
|
return 0;
|
|
|
|
if (free_tail(map) < 0)
|
|
return -1;
|
|
|
|
return flash_erase(map->dev, map->head, 1 << log2_pages_per_block);
|
|
}
|
|
|