[RFC PATCH 44/76] ssdfs: PEB mapping table cache's modification operations

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



"Physical" Erase Block (PEB) mapping table cache supports
operations:
(1) convert LEB to PEB - convert LEB to PEB if mapping table
                         is not initialized yet
(2) map LEB to PEB - cache information about LEB to PEB mapping
(3) forget LEB to PEB - exclude information about LEB to PEB mapping
                        from the cache
(4) change PEB state - update cached information about LEB to PEB mapping
(5) add migration PEB - cache information about migration destination
(6) exclude migration PEB - exclude information about migration destination

Signed-off-by: Viacheslav Dubeyko <slava@xxxxxxxxxxx>
CC: Viacheslav Dubeyko <viacheslav.dubeyko@xxxxxxxxxxxxx>
CC: Luka Perkov <luka.perkov@xxxxxxxxxx>
CC: Bruno Banelli <bruno.banelli@xxxxxxxxxx>
---
 fs/ssdfs/peb_mapping_table_cache.c | 3205 ++++++++++++++++++++++++++++
 1 file changed, 3205 insertions(+)

diff --git a/fs/ssdfs/peb_mapping_table_cache.c b/fs/ssdfs/peb_mapping_table_cache.c
index e83e07947743..4242d4a5f9ac 100644
--- a/fs/ssdfs/peb_mapping_table_cache.c
+++ b/fs/ssdfs/peb_mapping_table_cache.c
@@ -1495,3 +1495,3208 @@ int ssdfs_maptbl_cache_convert_leb2peb(struct ssdfs_maptbl_cache *cache,
 finish_leb2peb_conversion:
 	return err;
 }
+
+/*
+ * ssdfs_maptbl_cache_init_page() - init page of maptbl cache
+ * @kaddr: pointer on maptbl cache's fragment
+ * @sequence_id: fragment's sequence ID number
+ *
+ * This method initialize empty maptbl cache fragment's page.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ */
+static
+int ssdfs_maptbl_cache_init_page(void *kaddr, unsigned sequence_id)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	size_t hdr_size = sizeof(struct ssdfs_maptbl_cache_header);
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	size_t magic_size = peb_state_size;
+	size_t threshold_size = hdr_size + magic_size;
+	__le32 *magic;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p, sequence_id %u\n",
+		  kaddr, sequence_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (sequence_id >= PAGEVEC_SIZE) {
+		SSDFS_ERR("invalid sequence_id %u\n",
+			  sequence_id);
+		return -EINVAL;
+	}
+
+	memset(kaddr, 0, PAGE_SIZE);
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	hdr->magic.common = cpu_to_le32(SSDFS_SUPER_MAGIC);
+	hdr->magic.key = cpu_to_le16(SSDFS_MAPTBL_CACHE_MAGIC);
+	hdr->magic.version.major = SSDFS_MAJOR_REVISION;
+	hdr->magic.version.minor = SSDFS_MINOR_REVISION;
+
+	hdr->sequence_id = cpu_to_le16((u16)sequence_id);
+	hdr->items_count = 0;
+	hdr->bytes_count = cpu_to_le16((u16)threshold_size);
+
+	hdr->start_leb = cpu_to_le64(U64_MAX);
+	hdr->end_leb = cpu_to_le64(U64_MAX);
+
+	magic = (__le32 *)((u8 *)kaddr + hdr_size);
+	*magic = cpu_to_le32(SSDFS_MAPTBL_CACHE_PEB_STATE_MAGIC);
+
+	return 0;
+}
+
+/*
+ * ssdfs_shift_right_peb_state_area() - shift the whole PEB state area
+ * @kaddr: pointer on maptbl cache's fragment
+ * @shift: size of shift in bytes
+ *
+ * This method tries to shift the whole PEB state area
+ * to the right in the fragment.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE     - internal error.
+ */
+static inline
+int ssdfs_shift_right_peb_state_area(void *kaddr, size_t shift)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	void *area = NULL;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	size_t diff_count;
+	int area_size;
+	u32 area_offset = U32_MAX;
+	size_t bytes_count, new_bytes_count;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p, shift %zu\n", kaddr, shift);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (shift % pair_size) {
+		SSDFS_ERR("invalid request: "
+			  "shift %zu, pair_size %zu\n",
+			  shift, pair_size);
+		return -ERANGE;
+	}
+
+	diff_count = shift / pair_size;
+
+	if (diff_count == 0) {
+		SSDFS_ERR("invalid diff_count %zu\n", diff_count);
+		return -ERANGE;
+	}
+
+	area = PEB_STATE_AREA(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(area)) {
+		err = !area ? PTR_ERR(area) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+	bytes_count = le16_to_cpu(hdr->bytes_count);
+
+	area_size = ssdfs_peb_state_area_size(hdr);
+	if (area_size < 0) {
+		err = area_size;
+		SSDFS_ERR("fail to calculate PEB state area's size: "
+			  "err %d\n", err);
+		return err;
+	} else if (area_size == 0) {
+		SSDFS_ERR("invalid PEB state area's size %d\n",
+			  area_size);
+		return -ERANGE;
+	}
+
+	new_bytes_count = bytes_count;
+	new_bytes_count += diff_count * pair_size;
+	new_bytes_count += diff_count * peb_state_size;
+
+	if (new_bytes_count > PAGE_SIZE) {
+		SSDFS_ERR("shift is out of memory page: "
+			  "new_bytes_count %zu, shift %zu\n",
+			  new_bytes_count, shift);
+		return -ERANGE;
+	}
+
+	err = ssdfs_memmove(area, shift, PAGE_SIZE - area_offset,
+			    area, 0, PAGE_SIZE - area_offset,
+			    area_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to move: err %d\n", err);
+		return err;
+	}
+
+	hdr->bytes_count = cpu_to_le16((u16)new_bytes_count);
+
+	return 0;
+}
+
+/*
+ * ssdfs_shift_left_peb_state_area() - shift the whole PEB state area
+ * @kaddr: pointer on maptbl cache's fragment
+ * @shift: size of shift in bytes
+ *
+ * This method tries to shift the whole PEB state area
+ * to the left in the fragment.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE     - internal error.
+ */
+static inline
+int ssdfs_shift_left_peb_state_area(void *kaddr, size_t shift)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	void *area = NULL;
+	size_t hdr_size = sizeof(struct ssdfs_maptbl_cache_header);
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	size_t magic_size = peb_state_size;
+	size_t threshold_size = hdr_size + magic_size;
+	size_t diff_count;
+	int area_size;
+	u32 area_offset = U32_MAX;
+	size_t bytes_count;
+	size_t calculated;
+	size_t new_bytes_count;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p, shift %zu\n", kaddr, shift);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (shift % pair_size) {
+		SSDFS_ERR("invalid request: "
+			  "shift %zu, pair_size %zu\n",
+			  shift, pair_size);
+		return -ERANGE;
+	}
+
+	diff_count = shift / pair_size;
+
+	if (diff_count == 0) {
+		SSDFS_ERR("invalid diff_count %zu\n", diff_count);
+		return -ERANGE;
+	}
+
+	area = PEB_STATE_AREA(kaddr, &area_offset);
+
+	if (IS_ERR_OR_NULL(area)) {
+		err = !area ? PTR_ERR(area) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+	bytes_count = le16_to_cpu(hdr->bytes_count);
+
+	area_size = ssdfs_peb_state_area_size(hdr);
+	if (area_size < 0) {
+		err = area_size;
+		SSDFS_ERR("fail to calculate PEB state area's size: "
+			  "err %d\n", err);
+		return err;
+	} else if (area_size == 0) {
+		SSDFS_ERR("invalid PEB state area's size %d\n",
+			  area_size);
+		return -ERANGE;
+	}
+
+	new_bytes_count = bytes_count;
+
+	calculated = diff_count * pair_size;
+	if (new_bytes_count <= calculated) {
+		SSDFS_ERR("invalid diff_count %zu\n",
+			  diff_count);
+		return -ERANGE;
+	}
+
+	new_bytes_count -= calculated;
+
+	calculated = diff_count * peb_state_size;
+
+	if (new_bytes_count <= calculated) {
+		SSDFS_ERR("invalid diff_count %zu\n",
+			  diff_count);
+		return -ERANGE;
+	}
+
+	new_bytes_count -= calculated;
+
+	if (new_bytes_count < threshold_size) {
+		SSDFS_ERR("shift is inside of header: "
+			  "new_bytes_count %zu, threshold_size %zu\n",
+			  new_bytes_count, threshold_size);
+		return -ERANGE;
+	}
+
+	if ((threshold_size + shift) >= area_offset) {
+		SSDFS_ERR("invalid shift: "
+			  "threshold_size %zu, shift %zu, "
+			  "area_offset %u\n",
+			  threshold_size, shift, area_offset);
+		return -ERANGE;
+	}
+
+	err = ssdfs_memmove((u8 *)area - shift, 0, PAGE_SIZE - area_offset,
+			    area, 0, PAGE_SIZE - area_offset,
+			    area_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to move: err %d\n", err);
+		return err;
+	}
+
+	hdr->bytes_count = cpu_to_le16((u16)new_bytes_count);
+
+	return 0;
+}
+
+/*
+ * ssdfs_maptbl_cache_add_leb() - add LEB/PEB pair into maptbl cache
+ * @kaddr: pointer on maptbl cache's fragment
+ * @item_index: index of item in the fragment
+ * @src_pair: inserting LEB/PEB pair
+ * @src_state: inserting PEB state
+ *
+ * This method tries to insert LEB/PEB pair and PEB state
+ * into the maptbl cache.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+static
+int ssdfs_maptbl_cache_add_leb(void *kaddr, u16 item_index,
+				struct ssdfs_leb2peb_pair *src_pair,
+				struct ssdfs_maptbl_cache_peb_state *src_state)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *dest_pair;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	struct ssdfs_maptbl_cache_peb_state *dest_state;
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	u16 items_count;
+	u32 area_offset = U32_MAX;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr || !src_pair || !src_state);
+
+	SSDFS_DBG("kaddr %p, item_index %u, "
+		  "leb_id %llu, peb_id %llu\n",
+		  kaddr, item_index,
+		  le64_to_cpu(src_pair->leb_id),
+		  le64_to_cpu(src_pair->peb_id));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (item_index != items_count) {
+		SSDFS_ERR("item_index %u != items_count %u\n",
+			  item_index, items_count);
+		return -EINVAL;
+	}
+
+	err = ssdfs_shift_right_peb_state_area(kaddr, pair_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to shift the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	dest_pair = LEB2PEB_PAIR_AREA(kaddr);
+	dest_pair += item_index;
+
+	ssdfs_memcpy(dest_pair, 0, pair_size,
+		     src_pair, 0, pair_size,
+		     pair_size);
+
+	dest_state = FIRST_PEB_STATE(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(dest_state)) {
+		err = !dest_state ? PTR_ERR(dest_state) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	dest_state += item_index;
+
+	ssdfs_memcpy(dest_state, 0, peb_state_size,
+		     src_state, 0, peb_state_size,
+		     peb_state_size);
+
+	items_count++;
+	hdr->items_count = cpu_to_le16(items_count);
+
+	if (item_index == 0)
+		hdr->start_leb = src_pair->leb_id;
+
+	if ((item_index + 1) == items_count)
+		hdr->end_leb = src_pair->leb_id;
+
+	return 0;
+}
+
+struct page *
+ssdfs_maptbl_cache_add_pagevec_page(struct ssdfs_maptbl_cache *cache)
+{
+	struct page *page;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache);
+
+	SSDFS_DBG("cache %p\n", cache);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	page = ssdfs_map_cache_add_pagevec_page(&cache->pvec);
+	if (unlikely(IS_ERR_OR_NULL(page))) {
+		err = !page ? -ENOMEM : PTR_ERR(page);
+		SSDFS_ERR("fail to add pagevec page: err %d\n",
+			  err);
+	}
+
+	return page;
+}
+
+/*
+ * ssdfs_maptbl_cache_add_page() - add fragment into maptbl cache
+ * @cache: maptbl cache object
+ * @pair: adding LEB/PEB pair
+ * @state: adding PEB state
+ *
+ * This method tries to add fragment into maptbl cache,
+ * initialize it and insert LEB/PEB pair + PEB state.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ * %-ENOMEM     - fail to add empty page into maptbl cache.
+ */
+static
+int ssdfs_maptbl_cache_add_page(struct ssdfs_maptbl_cache *cache,
+				struct ssdfs_leb2peb_pair *pair,
+				struct ssdfs_maptbl_cache_peb_state *state)
+{
+	struct page *page;
+	void *kaddr;
+	u16 item_index;
+	unsigned page_index;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache || !pair || !state);
+
+	SSDFS_DBG("cache %p, leb_id %llu, peb_id %llu\n",
+		  cache, le64_to_cpu(pair->leb_id),
+		  le64_to_cpu(pair->peb_id));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	item_index = 0;
+	page_index = pagevec_count(&cache->pvec);
+
+	page = ssdfs_map_cache_add_pagevec_page(&cache->pvec);
+	if (unlikely(IS_ERR_OR_NULL(page))) {
+		err = !page ? -ENOMEM : PTR_ERR(page);
+		SSDFS_ERR("fail to add pagevec page: err %d\n",
+			  err);
+		return err;
+	}
+
+	ssdfs_lock_page(page);
+	kaddr = kmap_local_page(page);
+
+	err = ssdfs_maptbl_cache_init_page(kaddr, page_index);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to init maptbl cache's page: "
+			  "page_index %u, err %d\n",
+			  page_index, err);
+		goto finish_add_page;
+	}
+
+	atomic_add(PAGE_SIZE, &cache->bytes_count);
+
+	err = ssdfs_maptbl_cache_add_leb(kaddr, item_index, pair, state);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to add leb_id: "
+			  "page_index %u, item_index %u, err %d\n",
+			  page_index, item_index, err);
+		goto finish_add_page;
+	}
+
+finish_add_page:
+	flush_dcache_page(page);
+	kunmap_local(kaddr);
+	ssdfs_unlock_page(page);
+
+	return err;
+}
+
+/*
+ * is_fragment_full() - check that fragment is full
+ * @kaddr: pointer on maptbl cache's fragment
+ */
+static inline
+bool is_fragment_full(void *kaddr)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	size_t bytes_count;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p\n", kaddr);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	bytes_count = le16_to_cpu(hdr->bytes_count);
+	bytes_count += pair_size + peb_state_size;
+
+	return bytes_count > PAGE_SIZE;
+}
+
+/*
+ * ssdfs_maptbl_cache_get_last_item() - get last item of the fragment
+ * @kaddr: pointer on maptbl cache's fragment
+ * @pair: pointer on LEB2PEB pair's buffer [out]
+ * @state: pointer on PEB state's buffer [out]
+ *
+ * This method tries to extract the last item
+ * (LEB2PEB pair + PEB state) from the fragment.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ * %-ENODATA    - empty maptbl cache's page.
+ */
+static
+int ssdfs_maptbl_cache_get_last_item(void *kaddr,
+				     struct ssdfs_leb2peb_pair *pair,
+				     struct ssdfs_maptbl_cache_peb_state *state)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *found_pair = NULL;
+	struct ssdfs_maptbl_cache_peb_state *found_state = NULL;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	u16 items_count;
+	u32 area_offset = U32_MAX;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr || !pair || !state);
+
+	SSDFS_DBG("kaddr %p, pair %p, peb_state %p\n",
+		  kaddr, pair, state);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (items_count == 0) {
+		SSDFS_ERR("empty maptbl cache's page\n");
+		return -ENODATA;
+	}
+
+	found_pair = LEB2PEB_PAIR_AREA(kaddr);
+	found_pair += items_count - 1;
+
+	ssdfs_memcpy(pair, 0, pair_size,
+		     found_pair, 0, pair_size,
+		     pair_size);
+
+	found_state = FIRST_PEB_STATE(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(found_state)) {
+		err = !found_state ? PTR_ERR(found_state) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	found_state += items_count - 1;
+	ssdfs_memcpy(state, 0, peb_state_size,
+		     found_state, 0, peb_state_size,
+		     peb_state_size);
+
+	return 0;
+}
+
+/*
+ * ssdfs_maptbl_cache_move_right_leb2peb_pairs() - move LEB2PEB pairs
+ * @kaddr: pointer on maptbl cache's fragment
+ * @item_index: starting index
+ *
+ * This method tries to move LEB2PEB pairs to the right
+ * starting from @item_index.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+static
+int ssdfs_maptbl_cache_move_right_leb2peb_pairs(void *kaddr,
+						u16 item_index)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *area;
+	size_t hdr_size = sizeof(struct ssdfs_maptbl_cache_header);
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	u16 items_count;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p, item_index %u\n",
+		  kaddr, item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (items_count == 0) {
+		SSDFS_ERR("empty maptbl cache page\n");
+		return -ERANGE;
+	}
+
+	if (item_index >= items_count) {
+		SSDFS_ERR("item_index %u > items_count %u\n",
+			  item_index, items_count);
+		return -EINVAL;
+	}
+
+	area = LEB2PEB_PAIR_AREA(kaddr);
+	err = ssdfs_memmove(area,
+			    (item_index + 1) * pair_size,
+			    PAGE_SIZE - hdr_size,
+			    area,
+			    item_index * pair_size,
+			    PAGE_SIZE - hdr_size,
+			    (items_count - item_index) * pair_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to move: err %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+/*
+ * ssdfs_maptbl_cache_move_right_peb_states() - move PEB states
+ * @kaddr: pointer on maptbl cache's fragment
+ * @item_index: starting index
+ *
+ * This method tries to move PEB states to the right
+ * starting from @item_index.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+static
+int ssdfs_maptbl_cache_move_right_peb_states(void *kaddr,
+					     u16 item_index)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_maptbl_cache_peb_state *area;
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	u16 items_count;
+	u32 area_offset = U32_MAX;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p, item_index %u\n",
+		  kaddr, item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (items_count == 0) {
+		SSDFS_ERR("empty maptbl cache page\n");
+		return -ERANGE;
+	}
+
+	if (item_index >= items_count) {
+		SSDFS_ERR("item_index %u > items_count %u\n",
+			  item_index, items_count);
+		return -EINVAL;
+	}
+
+	area = FIRST_PEB_STATE(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(area)) {
+		err = !area ? PTR_ERR(area) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	err = ssdfs_memmove(area,
+			    (item_index + 1) * peb_state_size,
+			    PAGE_SIZE - area_offset,
+			    area,
+			    item_index * peb_state_size,
+			    PAGE_SIZE - area_offset,
+			    (items_count - item_index) * peb_state_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to move: err %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+/*
+ * __ssdfs_maptbl_cache_insert_leb() - insert item into the fragment
+ * @kaddr: pointer on maptbl cache's fragment
+ * @item_index: starting index
+ * @pair: adding LEB2PEB pair
+ * @state: adding PEB state
+ *
+ * This method tries to insert the item (LEB2PEB pair + PEB state)
+ * into the fragment in @item_index position.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+static
+int __ssdfs_maptbl_cache_insert_leb(void *kaddr, u16 item_index,
+				    struct ssdfs_leb2peb_pair *pair,
+				    struct ssdfs_maptbl_cache_peb_state *state)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *dst_pair = NULL;
+	struct ssdfs_maptbl_cache_peb_state *dst_state = NULL;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	u16 items_count;
+	u32 area_offset = U32_MAX;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr || !pair || !state);
+
+	SSDFS_DBG("kaddr %p, item_index %u, pair %p, state %p\n",
+		  kaddr, item_index, pair, state);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (items_count == 0) {
+		SSDFS_ERR("empty maptbl cache page\n");
+		return -ERANGE;
+	}
+
+	if (item_index >= items_count) {
+		SSDFS_ERR("item_index %u > items_count %u\n",
+			  item_index, items_count);
+		return -EINVAL;
+	}
+
+	dst_pair = LEB2PEB_PAIR_AREA(kaddr);
+	dst_pair += item_index;
+
+	ssdfs_memcpy(dst_pair, 0, pair_size,
+		     pair, 0, pair_size,
+		     pair_size);
+
+	dst_state = FIRST_PEB_STATE(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(dst_state)) {
+		err = !dst_state ? PTR_ERR(dst_state) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	dst_state += item_index;
+
+	ssdfs_memcpy(dst_state, 0, peb_state_size,
+		     state, 0, peb_state_size,
+		     peb_state_size);
+
+	items_count++;
+	hdr->items_count = cpu_to_le16(items_count);
+
+	if (item_index == 0)
+		hdr->start_leb = pair->leb_id;
+
+	if ((item_index + 1) == items_count)
+		hdr->end_leb = pair->leb_id;
+
+	return 0;
+}
+
+/*
+ * ssdfs_maptbl_cache_remove_leb() - remove item from the fragment
+ * @cache: maptbl cache object
+ * @page_index: index of the page
+ * @item_index: index of the item
+ *
+ * This method tries to remove the item (LEB/PEB pair + PEB state)
+ * from the fragment.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE     - internal error.
+ */
+static
+int ssdfs_maptbl_cache_remove_leb(struct ssdfs_maptbl_cache *cache,
+				  unsigned page_index,
+				  u16 item_index)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *cur_pair;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	struct ssdfs_maptbl_cache_peb_state *cur_state;
+	struct page *page;
+	void *kaddr;
+	u16 items_count;
+	size_t size;
+	u32 area_offset = U32_MAX;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache);
+	BUG_ON(page_index >= pagevec_count(&cache->pvec));
+
+	SSDFS_DBG("cache %p, page_index %u, item_index %u\n",
+		  cache, page_index, item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	page = cache->pvec.pages[page_index];
+
+	ssdfs_lock_page(page);
+	kaddr = kmap_local_page(page);
+
+#ifdef CONFIG_SSDFS_DEBUG
+	print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				kaddr, PAGE_SIZE);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (item_index >= items_count) {
+		err = -ERANGE;
+		SSDFS_ERR("item_index %u >= items_count %u\n",
+			  item_index, items_count);
+		goto finish_remove_item;
+	} else if (items_count == 0) {
+		err = -ERANGE;
+		SSDFS_ERR("items_count %u\n", items_count);
+		goto finish_remove_item;
+	}
+
+	cur_pair = LEB2PEB_PAIR_AREA(kaddr);
+	cur_pair += item_index;
+
+	if ((item_index + 1) < items_count) {
+		size = items_count - item_index;
+		size *= pair_size;
+
+		memmove(cur_pair, cur_pair + 1, size);
+	}
+
+	cur_pair = LEB2PEB_PAIR_AREA(kaddr);
+	cur_pair += items_count - 1;
+	memset(cur_pair, 0xFF, pair_size);
+
+	cur_state = FIRST_PEB_STATE(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(cur_state)) {
+		err = !cur_state ? PTR_ERR(cur_state) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		goto finish_remove_item;
+	}
+
+	cur_state += item_index;
+
+	if ((item_index + 1) < items_count) {
+		size = items_count - item_index;
+		size *= sizeof(struct ssdfs_maptbl_cache_peb_state);
+
+		memmove(cur_state, cur_state + 1, size);
+	}
+
+	cur_state = FIRST_PEB_STATE(kaddr, &area_offset);
+	cur_state += items_count - 1;
+	memset(cur_state, 0xFF, sizeof(struct ssdfs_maptbl_cache_peb_state));
+
+	items_count--;
+	hdr->items_count = cpu_to_le16(items_count);
+
+	err = ssdfs_shift_left_peb_state_area(kaddr, pair_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to shift PEB state area: "
+			  "err %d\n", err);
+		goto finish_remove_item;
+	}
+
+	if (items_count == 0) {
+		hdr->start_leb = U64_MAX;
+		hdr->end_leb = U64_MAX;
+	} else {
+		cur_pair = LEB2PEB_PAIR_AREA(kaddr);
+		hdr->start_leb = cur_pair->leb_id;
+
+		cur_pair += items_count - 1;
+		hdr->end_leb = cur_pair->leb_id;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				kaddr, PAGE_SIZE);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+finish_remove_item:
+	flush_dcache_page(page);
+	kunmap_local(kaddr);
+	ssdfs_unlock_page(page);
+
+	return err;
+}
+
+/*
+ * ssdfs_check_pre_deleted_peb_state() - check pre-deleted state of the item
+ * @cache: maptbl cache object
+ * @page_index: index of the page
+ * @item_index: index of the item
+ * @pair: adding LEB2PEB pair
+ *
+ * This method tries to check that requested item for @item_index
+ * has the PRE-DELETED consistency. If it's true then this item
+ * has to be deleted.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-ERANGE     - internal error.
+ * %-ENODATA    - requested LEB is absent.
+ * %-ENOENT     - requested LEB exists and should be saved.
+ */
+static
+int ssdfs_check_pre_deleted_peb_state(struct ssdfs_maptbl_cache *cache,
+				     unsigned page_index,
+				     u16 item_index,
+				     struct ssdfs_leb2peb_pair *pair)
+{
+	struct ssdfs_leb2peb_pair *cur_pair = NULL;
+	struct ssdfs_maptbl_cache_peb_state *cur_state = NULL;
+	struct page *page;
+	void *kaddr;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache || !pair);
+	BUG_ON(le64_to_cpu(pair->leb_id) == U64_MAX);
+	BUG_ON(le64_to_cpu(pair->peb_id) == U64_MAX);
+
+	SSDFS_DBG("cache %p, start_page %u, item_index %u\n",
+		  cache, page_index, item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	page = cache->pvec.pages[page_index];
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	ssdfs_lock_page(page);
+	kaddr = kmap_local_page(page);
+
+	err = ssdfs_maptbl_cache_get_leb2peb_pair(kaddr, item_index, &cur_pair);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to get LEB2PEB pair: err %d\n", err);
+		goto finish_check_pre_deleted_state;
+	}
+
+	if (le64_to_cpu(pair->leb_id) != le64_to_cpu(cur_pair->leb_id)) {
+		err = -ENODATA;
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("pair->leb_id %llu != cur_pair->leb_id %llu\n",
+			  le64_to_cpu(pair->leb_id),
+			  le64_to_cpu(cur_pair->leb_id));
+#endif /* CONFIG_SSDFS_DEBUG */
+		goto finish_check_pre_deleted_state;
+	}
+
+	err = ssdfs_maptbl_cache_get_peb_state(kaddr, item_index, &cur_state);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to get PEB state: err %d\n", err);
+		goto finish_check_pre_deleted_state;
+	}
+
+	switch (cur_state->consistency) {
+	case SSDFS_PEB_STATE_CONSISTENT:
+	case SSDFS_PEB_STATE_INCONSISTENT:
+		err = -ENOENT;
+		goto finish_check_pre_deleted_state;
+
+	case SSDFS_PEB_STATE_PRE_DELETED:
+		/* continue to delete */
+		break;
+
+	default:
+		err = -ERANGE;
+		SSDFS_ERR("unexpected PEB's state %#x\n",
+			  cur_state->state);
+		goto finish_check_pre_deleted_state;
+	}
+
+finish_check_pre_deleted_state:
+	kunmap_local(kaddr);
+	ssdfs_unlock_page(page);
+
+	if (err)
+		return err;
+
+	err = ssdfs_maptbl_cache_remove_leb(cache,
+					    page_index,
+					    item_index);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to delete LEB: "
+			  "page_index %d, item_index %u, err %d\n",
+			  page_index, item_index, err);
+		return err;
+	}
+
+	return 0;
+}
+
+/*
+ * ssdfs_maptbl_cache_insert_leb() - insert item into the fragment
+ * @cache: maptbl cache object
+ * @start_page: page index
+ * @item_index: index of the item
+ * @pair: adding LEB/PEB pair
+ * @state: adding PEB state
+ *
+ * This method tries to insert the item (LEB2PEB pair + PEB state)
+ * into the mapping table cache.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+static
+int ssdfs_maptbl_cache_insert_leb(struct ssdfs_maptbl_cache *cache,
+				  unsigned start_page,
+				  u16 item_index,
+				  struct ssdfs_leb2peb_pair *pair,
+				  struct ssdfs_maptbl_cache_peb_state *state)
+{
+	struct ssdfs_leb2peb_pair cur_pair, saved_pair;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	struct ssdfs_maptbl_cache_peb_state cur_state, saved_state;
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	struct page *page;
+	void *kaddr;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache || !pair || !state);
+	BUG_ON(le64_to_cpu(pair->leb_id) == U64_MAX);
+	BUG_ON(le64_to_cpu(pair->peb_id) == U64_MAX);
+
+	SSDFS_DBG("cache %p, start_page %u, item_index %u, "
+		  "leb_id %llu, peb_id %llu\n",
+		  cache, start_page, item_index,
+		  le64_to_cpu(pair->leb_id),
+		  le64_to_cpu(pair->peb_id));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = ssdfs_check_pre_deleted_peb_state(cache, start_page,
+						item_index, pair);
+	if (err == -ENODATA) {
+		err = 0;
+		/*
+		 * No pre-deleted item was found.
+		 * Continue the logic.
+		 */
+	} else if (err == -ENOENT) {
+		/*
+		 * Valid item was found.
+		 */
+		err = 0;
+		item_index++;
+	} else if (unlikely(err)) {
+		SSDFS_ERR("fail to check the pre-deleted state: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	ssdfs_memcpy(&cur_pair, 0, pair_size,
+		     pair, 0, pair_size,
+		     pair_size);
+	ssdfs_memcpy(&cur_state, 0, peb_state_size,
+		     state, 0, peb_state_size,
+		     peb_state_size);
+
+	memset(&saved_pair, 0xFF, pair_size);
+	memset(&saved_state, 0xFF, peb_state_size);
+
+	for (; start_page < pagevec_count(&cache->pvec); start_page++) {
+		bool need_move_item = false;
+
+		page = cache->pvec.pages[start_page];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+
+		need_move_item = is_fragment_full(kaddr);
+
+		if (need_move_item) {
+			err = ssdfs_maptbl_cache_get_last_item(kaddr,
+							       &saved_pair,
+							       &saved_state);
+			if (unlikely(err)) {
+				SSDFS_ERR("fail to get last item: "
+					  "err %d\n", err);
+				goto finish_page_modification;
+			}
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     kaddr, PAGE_SIZE);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		err = ssdfs_shift_right_peb_state_area(kaddr, pair_size);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to shift the PEB state area: "
+				  "err %d\n", err);
+			goto finish_page_modification;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     kaddr, PAGE_SIZE);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		err = ssdfs_maptbl_cache_move_right_leb2peb_pairs(kaddr,
+								item_index);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to move LEB2PEB pairs: "
+				  "page_index %u, item_index %u, "
+				  "err %d\n",
+				  start_page, item_index, err);
+			goto finish_page_modification;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     kaddr, PAGE_SIZE);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		err = ssdfs_maptbl_cache_move_right_peb_states(kaddr,
+								item_index);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to move PEB states: "
+				  "page_index %u, item_index %u, "
+				  "err %d\n",
+				  start_page, item_index, err);
+			goto finish_page_modification;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     kaddr, PAGE_SIZE);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		err = __ssdfs_maptbl_cache_insert_leb(kaddr, item_index,
+						      &cur_pair, &cur_state);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to insert leb descriptor: "
+				  "page_index %u, item_index %u, err %d\n",
+				  start_page, item_index, err);
+			goto finish_page_modification;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET,
+				     kaddr, PAGE_SIZE);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+finish_page_modification:
+		flush_dcache_page(page);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (err || !need_move_item)
+			goto finish_insert_leb;
+
+		item_index = 0;
+
+		if (need_move_item) {
+			ssdfs_memcpy(&cur_pair, 0, pair_size,
+				     &saved_pair, 0, pair_size,
+				     pair_size);
+			ssdfs_memcpy(&cur_state, 0, peb_state_size,
+				     &saved_state, 0, peb_state_size,
+				     peb_state_size);
+		}
+	}
+
+	err = ssdfs_maptbl_cache_add_page(cache, &cur_pair, &cur_state);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to add page into maptbl cache: "
+			  "err %d\n",
+			  err);
+	}
+
+finish_insert_leb:
+	return err;
+}
+
+/*
+ * ssdfs_maptbl_cache_map_leb2peb() - save LEB/PEB pair into maptbl cache
+ * @cache: maptbl cache object
+ * @leb_id: LEB ID number
+ * @pebr: descriptor of mapped LEB/PEB pair
+ * @consistency: consistency of the item
+ *
+ * This method tries to save the item (LEB/PEB pair + PEB state)
+ * into maptbl cache. If the item is consistent then it means that
+ * as mapping table cache as mapping table contain the same
+ * information about the item. Otherwise, for the case of inconsistent
+ * state, the mapping table cache contains the actual info about
+ * the item.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ * %-EEXIST     - LEB/PEB pair is cached already.
+ */
+int ssdfs_maptbl_cache_map_leb2peb(struct ssdfs_maptbl_cache *cache,
+				   u64 leb_id,
+				   struct ssdfs_maptbl_peb_relation *pebr,
+				   int consistency)
+{
+	struct ssdfs_maptbl_cache_search_result res;
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *tmp_pair = NULL;
+	u16 item_index = U16_MAX;
+	struct ssdfs_leb2peb_pair cur_pair;
+	struct ssdfs_maptbl_cache_peb_state cur_state;
+	struct page *page;
+	void *kaddr;
+	unsigned i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache || !pebr);
+	BUG_ON(leb_id == U64_MAX);
+
+	SSDFS_DBG("cache %p, leb_id %llu, pebr %p, consistency %#x\n",
+		  cache, leb_id, pebr, consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	memset(&res, 0xFF, sizeof(struct ssdfs_maptbl_cache_search_result));
+	res.pebs[SSDFS_MAPTBL_MAIN_INDEX].state =
+				SSDFS_MAPTBL_CACHE_ITEM_UNKNOWN;
+	res.pebs[SSDFS_MAPTBL_RELATION_INDEX].state =
+				SSDFS_MAPTBL_CACHE_ITEM_UNKNOWN;
+
+	cur_pair.leb_id = cpu_to_le64(leb_id);
+	cur_pair.peb_id =
+		cpu_to_le64(pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].peb_id);
+
+	switch (consistency) {
+	case SSDFS_PEB_STATE_CONSISTENT:
+	case SSDFS_PEB_STATE_INCONSISTENT:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected consistency %#x\n",
+			  consistency);
+		return -EINVAL;
+	}
+
+	cur_state.consistency = (u8)consistency;
+	cur_state.state = pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state;
+	cur_state.flags = pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].flags;
+	cur_state.shared_peb_index =
+		pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].shared_peb_index;
+
+	down_write(&cache->lock);
+
+	for (i = 0; i < pagevec_count(&cache->pvec); i++) {
+		page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+		err = __ssdfs_maptbl_cache_find_leb(kaddr, i, leb_id, &res);
+		item_index = res.pebs[SSDFS_MAPTBL_MAIN_INDEX].item_index;
+		tmp_pair = &res.pebs[SSDFS_MAPTBL_MAIN_INDEX].found;
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (err == -EEXIST) {
+			SSDFS_ERR("maptbl cache contains leb_id %llu\n",
+				  leb_id);
+			break;
+		} else if (err == -EFAULT) {
+			/* we've found place */
+			break;
+		} else if (!err)
+			BUG();
+	}
+
+	if (i >= pagevec_count(&cache->pvec)) {
+		if (err == -ENODATA) {
+			/* correct page index */
+			i = pagevec_count(&cache->pvec) - 1;
+		} else {
+			err = -ERANGE;
+			SSDFS_ERR("i %u >= pages_count %u\n",
+				  i, pagevec_count(&cache->pvec));
+			goto finish_leb_caching;
+		}
+	}
+
+	if (err == -EEXIST)
+		goto finish_leb_caching;
+	else if (err == -E2BIG) {
+		err = ssdfs_maptbl_cache_add_page(cache, &cur_pair, &cur_state);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to add page into maptbl cache: "
+				  "err %d\n",
+				  err);
+			goto finish_leb_caching;
+		}
+	} else if (err == -ENODATA) {
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(i >= pagevec_count(&cache->pvec));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+		hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+		item_index = le16_to_cpu(hdr->items_count);
+		err = ssdfs_maptbl_cache_add_leb(kaddr, item_index,
+						 &cur_pair, &cur_state);
+		flush_dcache_page(page);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to add leb_id: "
+				  "page_index %u, item_index %u, err %d\n",
+				  i, item_index, err);
+		}
+	} else if (err == -EFAULT) {
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(i >= pagevec_count(&cache->pvec));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		err = ssdfs_maptbl_cache_insert_leb(cache, i, item_index,
+						    &cur_pair, &cur_state);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to add LEB with shift: "
+				  "page_index %u, item_index %u, err %d\n",
+				  i, item_index, err);
+			goto finish_leb_caching;
+		}
+	} else
+		BUG();
+
+finish_leb_caching:
+	up_write(&cache->lock);
+
+	return err;
+}
+
+/*
+ * __ssdfs_maptbl_cache_change_peb_state() - change PEB state of the item
+ * @cache: maptbl cache object
+ * @page_index: index of memory page
+ * @item_index: index of the item in the page
+ * @peb_state: new state of the PEB
+ * @consistency: consistency of the item
+ *
+ * This method tries to change the PEB state.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - unable to get peb state.
+ * %-ERANGE     - internal error.
+ */
+static inline
+int __ssdfs_maptbl_cache_change_peb_state(struct ssdfs_maptbl_cache *cache,
+					  unsigned page_index,
+					  u16 item_index,
+					  int peb_state,
+					  int consistency)
+{
+	struct ssdfs_maptbl_cache_peb_state *found_state = NULL;
+	struct page *page;
+	void *kaddr;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache);
+	BUG_ON(!rwsem_is_locked(&cache->lock));
+
+	SSDFS_DBG("cache %p, page_index %u, item_index %u, "
+		  "peb_state %#x, consistency %#x\n",
+		  cache, page_index, item_index,
+		  peb_state, consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (page_index >= pagevec_count(&cache->pvec)) {
+		SSDFS_ERR("invalid page index %u\n", page_index);
+		return -ERANGE;
+	}
+
+	page = cache->pvec.pages[page_index];
+	ssdfs_lock_page(page);
+	kaddr = kmap_local_page(page);
+
+	err = ssdfs_maptbl_cache_get_peb_state(kaddr, item_index,
+						&found_state);
+	if (err == -EINVAL) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("unable to get peb state: "
+			  "item_index %u\n",
+			  item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+		goto finish_page_modification;
+	} else if (unlikely(err)) {
+		SSDFS_ERR("fail to get peb state: "
+			  "item_index %u, err %d\n",
+			  item_index, err);
+		goto finish_page_modification;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!found_state);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	switch (consistency) {
+	case SSDFS_PEB_STATE_CONSISTENT:
+		found_state->consistency = (u8)consistency;
+		found_state->state = (u8)peb_state;
+		break;
+
+	case SSDFS_PEB_STATE_INCONSISTENT:
+		if (found_state->state != (u8)peb_state) {
+			found_state->consistency = (u8)consistency;
+			found_state->state = (u8)peb_state;
+		}
+		break;
+
+	case SSDFS_PEB_STATE_PRE_DELETED:
+		found_state->consistency = (u8)consistency;
+		found_state->state = (u8)peb_state;
+		break;
+
+	default:
+		SSDFS_ERR("unexpected consistency %#x\n",
+			  consistency);
+		return -EINVAL;
+	}
+
+finish_page_modification:
+	flush_dcache_page(page);
+	kunmap_local(kaddr);
+	ssdfs_unlock_page(page);
+
+	return err;
+}
+
+/*
+ * ssdfs_maptbl_cache_define_relation_index() - define relation index
+ * @pebr: descriptor of mapped LEB/PEB pair
+ * @peb_state: new state of the PEB
+ * @relation_index: index of the item in relation [out]
+ */
+static int
+ssdfs_maptbl_cache_define_relation_index(struct ssdfs_maptbl_peb_relation *pebr,
+					 int peb_state,
+					 int *relation_index)
+{
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!pebr || !relation_index);
+
+	SSDFS_DBG("MAIN_INDEX: peb_id %llu, type %#x, "
+		  "state %#x, consistency %#x; "
+		  "RELATION_INDEX: peb_id %llu, type %#x, "
+		  "state %#x, consistency %#x\n",
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].peb_id,
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].type,
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].consistency,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].peb_id,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].type,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	*relation_index = SSDFS_MAPTBL_RELATION_MAX;
+
+	switch (peb_state) {
+	case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+	case SSDFS_MAPTBL_USING_PEB_STATE:
+	case SSDFS_MAPTBL_USED_PEB_STATE:
+	case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+	case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+		switch (pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].consistency) {
+		case SSDFS_PEB_STATE_CONSISTENT:
+		case SSDFS_PEB_STATE_INCONSISTENT:
+			*relation_index = SSDFS_MAPTBL_MAIN_INDEX;
+			break;
+
+		case SSDFS_PEB_STATE_PRE_DELETED:
+			*relation_index = SSDFS_MAPTBL_RELATION_INDEX;
+			break;
+
+		default:
+			BUG();
+		}
+		break;
+
+	case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+	case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+	case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+		switch (pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].consistency) {
+		case SSDFS_PEB_STATE_CONSISTENT:
+		case SSDFS_PEB_STATE_INCONSISTENT:
+			*relation_index = SSDFS_MAPTBL_MAIN_INDEX;
+			break;
+
+		case SSDFS_PEB_STATE_PRE_DELETED:
+			SSDFS_ERR("main index is pre-deleted\n");
+			break;
+
+		default:
+			BUG();
+		}
+		break;
+
+	case SSDFS_MAPTBL_MIGRATION_DST_CLEAN_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+		switch (pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].consistency) {
+		case SSDFS_PEB_STATE_CONSISTENT:
+		case SSDFS_PEB_STATE_INCONSISTENT:
+			*relation_index = SSDFS_MAPTBL_RELATION_INDEX;
+			break;
+
+		case SSDFS_PEB_STATE_PRE_DELETED:
+			SSDFS_ERR("main index is pre-deleted\n");
+			break;
+
+		default:
+			BUG();
+		}
+		break;
+
+	default:
+		SSDFS_ERR("unexpected peb_state %#x\n", peb_state);
+		return -EINVAL;
+	}
+
+	if (*relation_index == SSDFS_MAPTBL_RELATION_MAX) {
+		SSDFS_ERR("fail to define relation index\n");
+		return -ERANGE;
+	}
+
+	return 0;
+}
+
+/*
+ * can_peb_state_be_changed() - check that PEB state can be changed
+ * @pebr: descriptor of mapped LEB/PEB pair
+ * @peb_state: new state of the PEB
+ * @consistency: consistency of the item
+ * @relation_index: index of the item in relation
+ */
+static
+bool can_peb_state_be_changed(struct ssdfs_maptbl_peb_relation *pebr,
+				int peb_state,
+				int consistency,
+				int relation_index)
+{
+	int old_consistency = SSDFS_PEB_STATE_UNKNOWN;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!pebr);
+
+	SSDFS_DBG("peb_state %#x, consistency %#x, relation_index %d\n",
+		  peb_state, consistency, relation_index);
+
+	SSDFS_DBG("MAIN_INDEX: peb_id %llu, type %#x, "
+		  "state %#x, consistency %#x; "
+		  "RELATION_INDEX: peb_id %llu, type %#x, "
+		  "state %#x, consistency %#x\n",
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].peb_id,
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].type,
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+		  pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].consistency,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].peb_id,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].type,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+		  pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	switch (relation_index) {
+	case SSDFS_MAPTBL_MAIN_INDEX:
+		old_consistency =
+			pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].consistency;
+
+		switch (consistency) {
+		case SSDFS_PEB_STATE_CONSISTENT:
+		case SSDFS_PEB_STATE_INCONSISTENT:
+			switch (old_consistency) {
+			case SSDFS_PEB_STATE_PRE_DELETED:
+				SSDFS_WARN("invalid consistency: "
+					   "peb_state %#x, consistency %#x, "
+					   "relation_index %d\n",
+					   peb_state,
+					   consistency,
+					   relation_index);
+				return false;
+
+			case SSDFS_PEB_STATE_CONSISTENT:
+			case SSDFS_PEB_STATE_INCONSISTENT:
+				/* valid consistency */
+				break;
+
+			default:
+				SSDFS_WARN("invalid old consistency %#x\n",
+					   old_consistency);
+				return false;
+			}
+
+		case SSDFS_PEB_STATE_PRE_DELETED:
+			/* valid consistency */
+			break;
+
+		default:
+			SSDFS_WARN("invalid consistency: "
+				   "peb_state %#x, consistency %#x, "
+				   "relation_index %d\n",
+				   peb_state,
+				   consistency,
+				   relation_index);
+			return false;
+		}
+
+		switch (pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state) {
+		case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_USING_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_USED_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_CLEAN_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_DIRTY_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		default:
+			BUG();
+		}
+		break;
+
+	case SSDFS_MAPTBL_RELATION_INDEX:
+		old_consistency =
+			pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].consistency;
+
+		switch (consistency) {
+		case SSDFS_PEB_STATE_CONSISTENT:
+		case SSDFS_PEB_STATE_INCONSISTENT:
+			switch (old_consistency) {
+			case SSDFS_PEB_STATE_CONSISTENT:
+			case SSDFS_PEB_STATE_INCONSISTENT:
+				/* valid consistency */
+				break;
+
+			default:
+				SSDFS_WARN("invalid old consistency %#x\n",
+					   old_consistency);
+				return false;
+			}
+			break;
+
+		default:
+			SSDFS_WARN("invalid consistency: "
+				   "peb_state %#x, consistency %#x, "
+				   "relation_index %d\n",
+				   peb_state,
+				   consistency,
+				   relation_index);
+			return false;
+		}
+
+		switch (pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state) {
+		case SSDFS_MAPTBL_MIGRATION_DST_CLEAN_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_DST_CLEAN_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_DIRTY_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			case SSDFS_MAPTBL_MIGRATION_DST_DIRTY_STATE:
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_USING_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_USED_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+			switch (peb_state) {
+			case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+			case SSDFS_MAPTBL_USING_PEB_STATE:
+			case SSDFS_MAPTBL_USED_PEB_STATE:
+			case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+			case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+				goto finish_check;
+
+			default:
+				SSDFS_ERR("invalid change: "
+					  "old peb_state %#x, "
+					  "new peb_state %#x\n",
+				    pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+				    peb_state);
+				return false;
+			}
+			break;
+
+		default:
+			BUG();
+		}
+		break;
+
+	default:
+		BUG();
+	}
+
+finish_check:
+	return true;
+}
+
+/*
+ * ssdfs_maptbl_cache_change_peb_state_nolock() - change PEB state of the item
+ * @cache: maptbl cache object
+ * @leb_id: LEB ID number
+ * @peb_state: new state of the PEB
+ * @consistency: consistency of the item
+ *
+ * This method tries to change the PEB state. If the item is consistent
+ * then it means that as mapping table cache as mapping table
+ * contain the same information about the item. Otherwise,
+ * for the case of inconsistent state, the mapping table cache contains
+ * the actual info about the item.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+int ssdfs_maptbl_cache_change_peb_state_nolock(struct ssdfs_maptbl_cache *cache,
+						u64 leb_id, int peb_state,
+						int consistency)
+{
+	struct ssdfs_maptbl_cache_search_result res;
+	struct ssdfs_maptbl_peb_relation pebr;
+	int relation_index = SSDFS_MAPTBL_RELATION_MAX;
+	int state;
+	unsigned page_index;
+	u16 item_index = U16_MAX;
+	unsigned i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache);
+	BUG_ON(leb_id == U64_MAX);
+	BUG_ON(!rwsem_is_locked(&cache->lock));
+
+	SSDFS_DBG("cache %p, leb_id %llu, peb_state %#x, consistency %#x\n",
+		  cache, leb_id, peb_state, consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	switch (consistency) {
+	case SSDFS_PEB_STATE_CONSISTENT:
+	case SSDFS_PEB_STATE_INCONSISTENT:
+	case SSDFS_PEB_STATE_PRE_DELETED:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected consistency %#x\n",
+			  consistency);
+		return -EINVAL;
+	}
+
+	switch (peb_state) {
+	case SSDFS_MAPTBL_CLEAN_PEB_STATE:
+	case SSDFS_MAPTBL_USING_PEB_STATE:
+	case SSDFS_MAPTBL_USED_PEB_STATE:
+	case SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE:
+	case SSDFS_MAPTBL_DIRTY_PEB_STATE:
+	case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+	case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+	case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_CLEAN_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+	case SSDFS_MAPTBL_MIGRATION_DST_DIRTY_STATE:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected peb_state %#x\n", peb_state);
+		return -EINVAL;
+	}
+
+	err = ssdfs_maptbl_cache_find_leb(cache, leb_id, &res, &pebr);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to find: leb_id %llu, err %d\n",
+			  leb_id, err);
+		goto finish_peb_state_change;
+	}
+
+#ifdef CONFIG_SSDFS_DEBUG
+	SSDFS_DBG("MAIN_INDEX: state %#x, page_index %u, item_index %u; "
+		  "RELATION_INDEX: state %#x, page_index %u, item_index %u\n",
+		  res.pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+		  res.pebs[SSDFS_MAPTBL_MAIN_INDEX].page_index,
+		  res.pebs[SSDFS_MAPTBL_MAIN_INDEX].item_index,
+		  res.pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+		  res.pebs[SSDFS_MAPTBL_RELATION_INDEX].page_index,
+		  res.pebs[SSDFS_MAPTBL_RELATION_INDEX].item_index);
+
+	SSDFS_DBG("MAIN_INDEX: peb_id %llu, type %#x, "
+		  "state %#x, consistency %#x; "
+		  "RELATION_INDEX: peb_id %llu, type %#x, "
+		  "state %#x, consistency %#x\n",
+		  pebr.pebs[SSDFS_MAPTBL_MAIN_INDEX].peb_id,
+		  pebr.pebs[SSDFS_MAPTBL_MAIN_INDEX].type,
+		  pebr.pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+		  pebr.pebs[SSDFS_MAPTBL_MAIN_INDEX].consistency,
+		  pebr.pebs[SSDFS_MAPTBL_RELATION_INDEX].peb_id,
+		  pebr.pebs[SSDFS_MAPTBL_RELATION_INDEX].type,
+		  pebr.pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+		  pebr.pebs[SSDFS_MAPTBL_RELATION_INDEX].consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	err = ssdfs_maptbl_cache_define_relation_index(&pebr, peb_state,
+							&relation_index);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to define relation index: "
+			  "leb_id %llu, peb_state %#x, err %d\n",
+			  leb_id, peb_state, err);
+		goto finish_peb_state_change;
+	}
+
+	if (!can_peb_state_be_changed(&pebr, peb_state,
+					consistency, relation_index)) {
+		err = -ERANGE;
+		SSDFS_ERR("PEB state cannot be changed: "
+			  "leb_id %llu, peb_state %#x, "
+			  "consistency %#x, relation_index %d\n",
+			  leb_id, peb_state, consistency, relation_index);
+		goto finish_peb_state_change;
+	}
+
+	state = res.pebs[relation_index].state;
+	if (state != SSDFS_MAPTBL_CACHE_ITEM_FOUND) {
+		err = -ERANGE;
+		SSDFS_ERR("fail to change peb state: "
+			  "state %#x\n",
+			  state);
+		goto finish_peb_state_change;
+	}
+
+	page_index = res.pebs[relation_index].page_index;
+	item_index = res.pebs[relation_index].item_index;
+
+	err = __ssdfs_maptbl_cache_change_peb_state(cache,
+						    page_index,
+						    item_index,
+						    peb_state,
+						    consistency);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to change peb state: "
+			  "page_index %u, item_index %u, "
+			  "err %d\n",
+			  page_index, item_index, err);
+		goto finish_peb_state_change;
+	}
+
+finish_peb_state_change:
+	if (unlikely(err)) {
+		struct page *page;
+		void *kaddr;
+
+		for (i = 0; i < pagevec_count(&cache->pvec); i++) {
+			page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_lock_page(page);
+			kaddr = kmap_local_page(page);
+			ssdfs_maptbl_cache_show_items(kaddr);
+			kunmap_local(kaddr);
+			ssdfs_unlock_page(page);
+		}
+	}
+
+	return err;
+}
+
+/*
+ * ssdfs_maptbl_cache_change_peb_state() - change PEB state of the item
+ * @cache: maptbl cache object
+ * @leb_id: LEB ID number
+ * @peb_state: new state of the PEB
+ * @consistency: consistency of the item
+ *
+ * This method tries to change the PEB state. If the item is consistent
+ * then it means that as mapping table cache as mapping table
+ * contain the same information about the item. Otherwise,
+ * for the case of inconsistent state, the mapping table cache contains
+ * the actual info about the item.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+int ssdfs_maptbl_cache_change_peb_state(struct ssdfs_maptbl_cache *cache,
+					u64 leb_id, int peb_state,
+					int consistency)
+{
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache);
+	BUG_ON(leb_id == U64_MAX);
+
+	SSDFS_DBG("cache %p, leb_id %llu, peb_state %#x, consistency %#x\n",
+		  cache, leb_id, peb_state, consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	down_write(&cache->lock);
+	err = ssdfs_maptbl_cache_change_peb_state_nolock(cache,
+							 leb_id,
+							 peb_state,
+							 consistency);
+	up_write(&cache->lock);
+
+	return err;
+}
+
+/*
+ * ssdfs_maptbl_cache_add_migration_peb() - add item for migration PEB
+ * @cache: maptbl cache object
+ * @leb_id: LEB ID number
+ * @pebr: descriptor of mapped LEB/PEB pair
+ * @consistency: consistency of the item
+ *
+ * This method tries to add the item (LEB2PEB pair + PEB state)
+ * for the migration PEB. If the item is consistent
+ * then it means that as mapping table cache as mapping table
+ * contain the same information about the item. Otherwise,
+ * for the case of inconsistent state, the mapping table cache contains
+ * the actual info about the item.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+int ssdfs_maptbl_cache_add_migration_peb(struct ssdfs_maptbl_cache *cache,
+					 u64 leb_id,
+					 struct ssdfs_maptbl_peb_relation *pebr,
+					 int consistency)
+{
+	struct ssdfs_maptbl_cache_search_result res;
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *tmp_pair = NULL;
+	u16 item_index = U16_MAX, items_count = U16_MAX;
+	struct ssdfs_leb2peb_pair cur_pair;
+	struct ssdfs_maptbl_cache_peb_state cur_state;
+	struct page *page;
+	void *kaddr;
+	unsigned i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache || !pebr);
+	BUG_ON(leb_id == U64_MAX);
+
+	SSDFS_DBG("cache %p, leb_id %llu, pebr %p, consistency %#x\n",
+		  cache, leb_id, pebr, consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	memset(&res, 0xFF, sizeof(struct ssdfs_maptbl_cache_search_result));
+	res.pebs[SSDFS_MAPTBL_MAIN_INDEX].state =
+				SSDFS_MAPTBL_CACHE_ITEM_UNKNOWN;
+	res.pebs[SSDFS_MAPTBL_RELATION_INDEX].state =
+				SSDFS_MAPTBL_CACHE_ITEM_UNKNOWN;
+
+	cur_pair.leb_id = cpu_to_le64(leb_id);
+	cur_pair.peb_id =
+		cpu_to_le64(pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].peb_id);
+
+	switch (consistency) {
+	case SSDFS_PEB_STATE_CONSISTENT:
+	case SSDFS_PEB_STATE_INCONSISTENT:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected consistency %#x\n",
+			  consistency);
+		return -EINVAL;
+	}
+
+	cur_state.consistency = (u8)consistency;
+	cur_state.state = pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].state;
+	cur_state.flags = pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].flags;
+	cur_state.shared_peb_index =
+		pebr->pebs[SSDFS_MAPTBL_RELATION_INDEX].shared_peb_index;
+
+	down_write(&cache->lock);
+
+	for (i = 0; i < pagevec_count(&cache->pvec); i++) {
+		page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+		hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+		items_count = le16_to_cpu(hdr->items_count);
+		err = __ssdfs_maptbl_cache_find_leb(kaddr, i, leb_id, &res);
+		item_index = res.pebs[SSDFS_MAPTBL_MAIN_INDEX].item_index;
+		tmp_pair = &res.pebs[SSDFS_MAPTBL_MAIN_INDEX].found;
+		flush_dcache_page(page);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (err == -EEXIST || err == -EFAULT)
+			break;
+		else if (err != -E2BIG && err != -ENODATA)
+			break;
+		else if (err == -EAGAIN)
+			continue;
+		else if (!err)
+			BUG();
+	}
+
+	if (err != -EEXIST && err != -EAGAIN) {
+		SSDFS_ERR("maptbl cache hasn't item for leb_id %llu, err %d\n",
+			  leb_id, err);
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+		ssdfs_maptbl_cache_show_items(kaddr);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		goto finish_add_migration_peb;
+	}
+
+	if ((item_index + 1) >= ssdfs_maptbl_cache_fragment_capacity()) {
+		err = ssdfs_maptbl_cache_add_page(cache, &cur_pair, &cur_state);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to add page into maptbl cache: "
+				  "err %d\n",
+				  err);
+			goto finish_add_migration_peb;
+		}
+	} else if ((item_index + 1) < items_count) {
+		err = ssdfs_maptbl_cache_insert_leb(cache, i, item_index,
+						    &cur_pair, &cur_state);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to insert LEB: "
+				  "page_index %u, item_index %u, err %d\n",
+				  i, item_index, err);
+			goto finish_add_migration_peb;
+		}
+	} else {
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(i >= pagevec_count(&cache->pvec));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+		hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+		item_index = le16_to_cpu(hdr->items_count);
+		err = ssdfs_maptbl_cache_add_leb(kaddr, item_index,
+						 &cur_pair, &cur_state);
+		flush_dcache_page(page);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to add leb_id: "
+				  "page_index %u, item_index %u, err %d\n",
+				  i, item_index, err);
+			goto finish_add_migration_peb;
+		}
+	}
+
+finish_add_migration_peb:
+	up_write(&cache->lock);
+
+	return err;
+}
+
+/*
+ * ssdfs_maptbl_cache_get_first_item() - get first item of the fragment
+ * @kaddr: pointer on maptbl cache's fragment
+ * @pair: pointer on LEB2PEB pair's buffer [out]
+ * @state: pointer on PEB state's buffer [out]
+ *
+ * This method tries to retrieve the first item of the fragment.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ * %-ENODATA    - empty maptbl cache page.
+ */
+static
+int ssdfs_maptbl_cache_get_first_item(void *kaddr,
+				     struct ssdfs_leb2peb_pair *pair,
+				     struct ssdfs_maptbl_cache_peb_state *state)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *found_pair = NULL;
+	struct ssdfs_maptbl_cache_peb_state *found_state = NULL;
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	u16 items_count;
+	u32 area_offset = U32_MAX;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr || !pair || !state);
+
+	SSDFS_DBG("kaddr %p, pair %p, peb_state %p\n",
+		  kaddr, pair, state);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (items_count == 0) {
+		SSDFS_ERR("empty maptbl cache page\n");
+		return -ENODATA;
+	}
+
+	found_pair = LEB2PEB_PAIR_AREA(kaddr);
+	ssdfs_memcpy(pair, 0, pair_size,
+		     found_pair, 0, pair_size,
+		     pair_size);
+
+	found_state = FIRST_PEB_STATE(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(found_state)) {
+		err = !found_state ? PTR_ERR(found_state) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	ssdfs_memcpy(state, 0, peb_state_size,
+		     found_state, 0, peb_state_size,
+		     peb_state_size);
+
+	return 0;
+}
+
+/*
+ * ssdfs_maptbl_cache_move_left_leb2peb_pairs() - move LEB2PEB pairs
+ * @kaddr: pointer on maptbl cache's fragment
+ * @item_index: starting index
+ *
+ * This method tries to move the LEB2PEB pairs on one position
+ * to the left starting from @item_index.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+#ifdef CONFIG_SSDFS_UNDER_DEVELOPMENT_FUNC
+static
+int ssdfs_maptbl_cache_move_left_leb2peb_pairs(void *kaddr,
+						u16 item_index)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *area;
+	size_t hdr_size = sizeof(struct ssdfs_maptbl_cache_header);
+	size_t pair_size = sizeof(struct ssdfs_leb2peb_pair);
+	u16 items_count;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p, item_index %u\n",
+		  kaddr, item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (item_index == 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("do nothing: item_index %u\n",
+			  item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+		return 0;
+	}
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (items_count == 0) {
+		SSDFS_ERR("empty maptbl cache page\n");
+		return -ERANGE;
+	}
+
+	if (item_index >= items_count) {
+		SSDFS_ERR("item_index %u > items_count %u\n",
+			  item_index, items_count);
+		return -EINVAL;
+	}
+
+	area = LEB2PEB_PAIR_AREA(kaddr);
+	err = ssdfs_memmove(area,
+			    (item_index - 1) * pair_size,
+			    PAGE_SIZE - hdr_size,
+			    area,
+			    item_index * pair_size,
+			    PAGE_SIZE - hdr_size,
+			    (items_count - item_index) * pair_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to move: err %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+#endif /* CONFIG_SSDFS_UNDER_DEVELOPMENT_FUNC */
+
+/*
+ * ssdfs_maptbl_cache_move_left_peb_states() - move PEB states
+ * @kaddr: pointer on maptbl cache's fragment
+ * @item_index: starting index
+ *
+ * This method tries to move the PEB states on one position
+ * to the left starting from @item_index.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+#ifdef CONFIG_SSDFS_UNDER_DEVELOPMENT_FUNC
+static
+int ssdfs_maptbl_cache_move_left_peb_states(void *kaddr,
+					     u16 item_index)
+{
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_maptbl_cache_peb_state *area;
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	u16 items_count;
+	u32 area_offset = U32_MAX;
+	int err;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!kaddr);
+
+	SSDFS_DBG("kaddr %p, item_index %u\n",
+		  kaddr, item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	if (item_index == 0) {
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("do nothing: item_index %u\n",
+			  item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+		return 0;
+	}
+
+	hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+
+	items_count = le16_to_cpu(hdr->items_count);
+
+	if (items_count == 0) {
+		SSDFS_ERR("empty maptbl cache page\n");
+		return -ERANGE;
+	}
+
+	if (item_index >= items_count) {
+		SSDFS_ERR("item_index %u > items_count %u\n",
+			  item_index, items_count);
+		return -EINVAL;
+	}
+
+	area = FIRST_PEB_STATE(kaddr, &area_offset);
+	if (IS_ERR_OR_NULL(area)) {
+		err = !area ? PTR_ERR(area) : -ERANGE;
+		SSDFS_ERR("fail to get the PEB state area: "
+			  "err %d\n", err);
+		return err;
+	}
+
+	err = ssdfs_memmove(area,
+			    (item_index - 1) * peb_state_size,
+			    PAGE_SIZE - area_offset,
+			    area,
+			    item_index * peb_state_size,
+			    PAGE_SIZE - area_offset,
+			    (items_count - item_index) * peb_state_size);
+	if (unlikely(err)) {
+		SSDFS_ERR("fail to move: err %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+#endif /* CONFIG_SSDFS_UNDER_DEVELOPMENT_FUNC */
+
+/*
+ * ssdfs_maptbl_cache_forget_leb2peb_nolock() - exclude LEB/PEB pair from cache
+ * @cache: maptbl cache object
+ * @leb_id: LEB ID number
+ * @consistency: consistency of the item
+ *
+ * This method tries to exclude LEB/PEB pair from the cache.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+int ssdfs_maptbl_cache_forget_leb2peb_nolock(struct ssdfs_maptbl_cache *cache,
+					     u64 leb_id,
+					     int consistency)
+{
+	struct ssdfs_maptbl_cache_search_result res;
+	struct ssdfs_maptbl_cache_header *hdr;
+	struct ssdfs_leb2peb_pair *found_pair = NULL;
+	struct ssdfs_leb2peb_pair saved_pair;
+	struct ssdfs_maptbl_cache_peb_state *found_state = NULL;
+	struct ssdfs_maptbl_cache_peb_state saved_state;
+	size_t peb_state_size = sizeof(struct ssdfs_maptbl_cache_peb_state);
+	struct page *page;
+	void *kaddr;
+	u16 item_index, items_count;
+	unsigned i;
+	int err = 0;
+
+#ifdef CONFIG_SSDFS_DEBUG
+	BUG_ON(!cache);
+	BUG_ON(leb_id == U64_MAX);
+	BUG_ON(!rwsem_is_locked(&cache->lock));
+
+	SSDFS_DBG("cache %p, leb_id %llu, consistency %#x\n",
+		  cache, leb_id, consistency);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+	memset(&res, 0xFF, sizeof(struct ssdfs_maptbl_cache_search_result));
+	res.pebs[SSDFS_MAPTBL_MAIN_INDEX].state =
+				SSDFS_MAPTBL_CACHE_ITEM_UNKNOWN;
+	res.pebs[SSDFS_MAPTBL_RELATION_INDEX].state =
+				SSDFS_MAPTBL_CACHE_ITEM_UNKNOWN;
+
+	switch (consistency) {
+	case SSDFS_PEB_STATE_CONSISTENT:
+	case SSDFS_PEB_STATE_PRE_DELETED:
+		/* expected state */
+		break;
+
+	default:
+		SSDFS_ERR("unexpected consistency %#x\n",
+			  consistency);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < pagevec_count(&cache->pvec); i++) {
+		struct ssdfs_maptbl_cache_header *hdr;
+		int search_state;
+
+		page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+
+		hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+		items_count = le16_to_cpu(hdr->items_count);
+
+		err = __ssdfs_maptbl_cache_find_leb(kaddr, i, leb_id, &res);
+		item_index = res.pebs[SSDFS_MAPTBL_MAIN_INDEX].item_index;
+		found_pair = &res.pebs[SSDFS_MAPTBL_MAIN_INDEX].found;
+		search_state = res.pebs[SSDFS_MAPTBL_RELATION_INDEX].state;
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("MAIN_INDEX: state %#x, "
+			  "page_index %u, item_index %u; "
+			  "RELATION_INDEX: state %#x, "
+			  "page_index %u, item_index %u\n",
+			  res.pebs[SSDFS_MAPTBL_MAIN_INDEX].state,
+			  res.pebs[SSDFS_MAPTBL_MAIN_INDEX].page_index,
+			  res.pebs[SSDFS_MAPTBL_MAIN_INDEX].item_index,
+			  res.pebs[SSDFS_MAPTBL_RELATION_INDEX].state,
+			  res.pebs[SSDFS_MAPTBL_RELATION_INDEX].page_index,
+			  res.pebs[SSDFS_MAPTBL_RELATION_INDEX].item_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		if (err == -EEXIST || err == -EAGAIN) {
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(le64_to_cpu(found_pair->leb_id) != leb_id);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			switch (search_state) {
+			case SSDFS_MAPTBL_CACHE_ITEM_FOUND:
+				if ((item_index + 1) >= items_count) {
+					err = -ERANGE;
+					SSDFS_ERR("invalid position found: "
+						  "item_index %u, "
+						  "items_count %u\n",
+						  item_index, items_count);
+				}
+				break;
+
+			case SSDFS_MAPTBL_CACHE_ITEM_ABSENT:
+				if ((item_index + 1) > items_count) {
+					err = -ERANGE;
+					SSDFS_ERR("invalid position found: "
+						  "item_index %u, "
+						  "items_count %u\n",
+						  item_index, items_count);
+				}
+				break;
+
+			default:
+				SSDFS_ERR("unexpected state %#x\n",
+					  search_state);
+				break;
+			}
+
+			err = ssdfs_maptbl_cache_get_peb_state(kaddr,
+							       item_index,
+							       &found_state);
+			if (unlikely(err)) {
+				SSDFS_ERR("fail to get peb state: "
+					  "item_index %u, err %d\n",
+					  item_index, err);
+			} else {
+				ssdfs_memcpy(&saved_state, 0, peb_state_size,
+					     found_state, 0, peb_state_size,
+					     peb_state_size);
+			}
+
+			/* it is expected existence of the item */
+			err = -EEXIST;
+		}
+
+		flush_dcache_page(page);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (err == -EEXIST || err == -EFAULT)
+			break;
+		else if (err != -E2BIG && err != -ENODATA)
+			break;
+		else if (!err)
+			BUG();
+	}
+
+	if (err != -EEXIST)
+		goto finish_exclude_migration_peb;
+
+	if (consistency == SSDFS_PEB_STATE_PRE_DELETED) {
+		/* simply change the state */
+		goto finish_exclude_migration_peb;
+	} else {
+		unsigned page_index = i;
+		u16 deleted_item = item_index;
+		u8 new_peb_state = SSDFS_MAPTBL_UNKNOWN_PEB_STATE;
+
+		err = ssdfs_maptbl_cache_remove_leb(cache, i, item_index);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to remove LEB: "
+				  "page_index %u, item_index %u, err %d\n",
+				  i, item_index, err);
+			goto finish_exclude_migration_peb;
+		}
+
+		for (++i; i < pagevec_count(&cache->pvec); i++) {
+			page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_lock_page(page);
+			kaddr = kmap_local_page(page);
+			err = ssdfs_maptbl_cache_get_first_item(kaddr,
+							       &saved_pair,
+							       &saved_state);
+			kunmap_local(kaddr);
+			ssdfs_unlock_page(page);
+
+			if (unlikely(err)) {
+				SSDFS_ERR("fail to get first item: "
+					  "err %d\n", err);
+				goto finish_exclude_migration_peb;
+			}
+
+			page = cache->pvec.pages[i - 1];
+
+#ifdef CONFIG_SSDFS_DEBUG
+			BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_lock_page(page);
+			kaddr = kmap_local_page(page);
+
+			hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+			items_count = le16_to_cpu(hdr->items_count);
+			if (items_count == 0)
+				item_index = 0;
+			else
+				item_index = items_count;
+
+			err = ssdfs_maptbl_cache_add_leb(kaddr, item_index,
+							 &saved_pair,
+							 &saved_state);
+
+			flush_dcache_page(page);
+			kunmap_local(kaddr);
+			ssdfs_unlock_page(page);
+
+			if (unlikely(err)) {
+				SSDFS_ERR("fail to add leb_id: "
+					  "page_index %u, item_index %u, "
+					  "err %d\n",
+					  i, item_index, err);
+				goto finish_exclude_migration_peb;
+			}
+
+			item_index = 0;
+			err = ssdfs_maptbl_cache_remove_leb(cache, i,
+							    item_index);
+			if (unlikely(err)) {
+				SSDFS_ERR("fail to remove LEB: "
+					  "page_index %u, item_index %u, "
+					  "err %d\n",
+					  i, item_index, err);
+				goto finish_exclude_migration_peb;
+			}
+		}
+
+		i = pagevec_count(&cache->pvec);
+		if (i == 0) {
+			err = -ERANGE;
+			SSDFS_ERR("invalid number of fragments %u\n", i);
+			goto finish_exclude_migration_peb;
+		} else
+			i--;
+
+		if (i < page_index) {
+			err = -ERANGE;
+			SSDFS_ERR("invalid page index: "
+				  "i %u, page_index %u\n",
+				  i, page_index);
+			goto finish_exclude_migration_peb;
+		}
+
+		page = cache->pvec.pages[i];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+		hdr = (struct ssdfs_maptbl_cache_header *)kaddr;
+		items_count = le16_to_cpu(hdr->items_count);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (items_count == 0) {
+			cache->pvec.pages[i] = NULL;
+			cache->pvec.nr--;
+			ssdfs_put_page(page);
+
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("page %p, count %d\n",
+				  page, page_ref_count(page));
+#endif /* CONFIG_SSDFS_DEBUG */
+
+			ssdfs_map_cache_free_page(page);
+			atomic_sub(PAGE_SIZE, &cache->bytes_count);
+
+			if (i == page_index) {
+#ifdef CONFIG_SSDFS_DEBUG
+				SSDFS_DBG("do nothing: "
+					  "page %u was deleted\n",
+					  page_index);
+#endif /* CONFIG_SSDFS_DEBUG */
+				goto finish_exclude_migration_peb;
+			}
+		}
+
+		switch (saved_state.state) {
+		case SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE:
+		case SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE:
+		case SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE:
+			/* continue logic */
+			break;
+
+		default:
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("do not change PEB state: "
+				  "page_index %u, deleted_item %u, "
+				  "state %#x\n",
+				  page_index, deleted_item,
+				  saved_state.state);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto finish_exclude_migration_peb;
+		}
+
+		if (deleted_item >= items_count) {
+			err = -ERANGE;
+			SSDFS_ERR("deleted_item %u >= items_count %u\n",
+				  deleted_item, items_count);
+			goto finish_exclude_migration_peb;
+		}
+
+		page = cache->pvec.pages[page_index];
+
+#ifdef CONFIG_SSDFS_DEBUG
+		BUG_ON(!page);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		ssdfs_lock_page(page);
+		kaddr = kmap_local_page(page);
+		err = ssdfs_maptbl_cache_get_peb_state(kaddr,
+						       deleted_item,
+						       &found_state);
+		kunmap_local(kaddr);
+		ssdfs_unlock_page(page);
+
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to get peb state: "
+				  "item_index %u, err %d\n",
+				  deleted_item, err);
+			goto finish_exclude_migration_peb;
+		}
+
+#ifdef CONFIG_SSDFS_DEBUG
+		SSDFS_DBG("found_state->state %#x\n",
+			  found_state->state);
+#endif /* CONFIG_SSDFS_DEBUG */
+
+		switch (found_state->state) {
+		case SSDFS_MAPTBL_MIGRATION_DST_CLEAN_STATE:
+			new_peb_state = SSDFS_MAPTBL_CLEAN_PEB_STATE;
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_USING_STATE:
+			new_peb_state = SSDFS_MAPTBL_USING_PEB_STATE;
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_USED_STATE:
+			new_peb_state = SSDFS_MAPTBL_USED_PEB_STATE;
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE:
+			new_peb_state = SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE;
+			break;
+
+		case SSDFS_MAPTBL_MIGRATION_DST_DIRTY_STATE:
+			new_peb_state = SSDFS_MAPTBL_DIRTY_PEB_STATE;
+			break;
+
+		default:
+			/* do nothing */
+#ifdef CONFIG_SSDFS_DEBUG
+			SSDFS_DBG("PEB not under migration: "
+				  "state %#x\n",
+				  found_state->state);
+#endif /* CONFIG_SSDFS_DEBUG */
+			goto finish_exclude_migration_peb;
+		}
+
+		err = __ssdfs_maptbl_cache_change_peb_state(cache,
+							    page_index,
+							    deleted_item,
+							    new_peb_state,
+							    consistency);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to change peb state: "
+				  "page_index %u, item_index %u, "
+				  "err %d\n",
+				  page_index, deleted_item, err);
+			goto finish_exclude_migration_peb;
+		}
+	}
+
+finish_exclude_migration_peb:
+	if (consistency == SSDFS_PEB_STATE_PRE_DELETED) {
+		err = ssdfs_maptbl_cache_change_peb_state_nolock(cache, leb_id,
+							    saved_state.state,
+							    consistency);
+		if (unlikely(err)) {
+			SSDFS_ERR("fail to change PEB state: err %d\n", err);
+			return err;
+		}
+	}
+
+	return err;
+}
+
+/*
+ * ssdfs_maptbl_cache_exclude_migration_peb() - exclude migration PEB
+ * @cache: maptbl cache object
+ * @leb_id: LEB ID number
+ * @consistency: consistency of the item
+ *
+ * This method tries to exclude LEB/PEB pair after
+ * finishing the migration.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+int ssdfs_maptbl_cache_exclude_migration_peb(struct ssdfs_maptbl_cache *cache,
+					     u64 leb_id,
+					     int consistency)
+{
+	int err;
+
+	down_write(&cache->lock);
+	err = ssdfs_maptbl_cache_forget_leb2peb_nolock(cache, leb_id,
+							consistency);
+	up_write(&cache->lock);
+
+	return err;
+}
+
+/*
+ * ssdfs_maptbl_cache_forget_leb2peb() - exclude LEB/PEB pair from cache
+ * @cache: maptbl cache object
+ * @leb_id: LEB ID number
+ * @consistency: consistency of the item
+ *
+ * This method tries to exclude LEB/PEB pair from the cache.
+ *
+ * RETURN:
+ * [success]
+ * [failure] - error code:
+ *
+ * %-EINVAL     - invalid input.
+ * %-ERANGE     - internal error.
+ */
+int ssdfs_maptbl_cache_forget_leb2peb(struct ssdfs_maptbl_cache *cache,
+				      u64 leb_id,
+				      int consistency)
+{
+	int err;
+
+	down_write(&cache->lock);
+	err = ssdfs_maptbl_cache_forget_leb2peb_nolock(cache, leb_id,
+							consistency);
+	up_write(&cache->lock);
+
+	return err;
+}
-- 
2.34.1




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux