This patch implements PEB container's API logic. 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_container.c | 2980 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 2980 insertions(+) diff --git a/fs/ssdfs/peb_container.c b/fs/ssdfs/peb_container.c index 668ded673719..92798bcbe8b7 100644 --- a/fs/ssdfs/peb_container.c +++ b/fs/ssdfs/peb_container.c @@ -2667,3 +2667,2983 @@ void ssdfs_peb_container_destroy(struct ssdfs_peb_container *ptr) SSDFS_DBG("finished\n"); #endif /* CONFIG_SSDFS_TRACK_API_CALL */ } + +/* + * ssdfs_peb_container_prepare_relation() - prepare relation with destination + * @ptr: pointer on PEB container + * + * This method tries to create the relation between source of @ptr + * and existing destination in another PEB container. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +static +int ssdfs_peb_container_prepare_relation(struct ssdfs_peb_container *ptr) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_peb_mapping_table *maptbl; + struct ssdfs_migration_destination *destination; + struct ssdfs_peb_container *relation; + int shared_index; + int destination_state; + u16 peb_index, dst_peb_index; + u64 leb_id, dst_leb_id; + struct completion *end; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!mutex_is_locked(&ptr->migration_lock)); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + maptbl = fsi->maptbl; + si = ptr->parent_si; + peb_index = ptr->peb_index; + +try_define_relation: + destination = &si->migration.array[SSDFS_LAST_DESTINATION]; + + spin_lock(&si->migration.lock); + destination_state = destination->state; + shared_index = destination->shared_peb_index; + spin_unlock(&si->migration.lock); + + switch (destination_state) { + case SSDFS_VALID_DESTINATION: + /* do nothing here */ + break; + + case SSDFS_DESTINATION_UNDER_CREATION: + /* FALLTHRU */ + fallthrough; + case SSDFS_OBSOLETE_DESTINATION: { + DEFINE_WAIT(wait); + + mutex_unlock(&ptr->migration_lock); + prepare_to_wait(&ptr->migration_wq, &wait, + TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&ptr->migration_wq, &wait); + mutex_lock(&ptr->migration_lock); + goto try_define_relation; + } + break; + + case SSDFS_EMPTY_DESTINATION: + SSDFS_ERR("destination is empty\n"); + return -ERANGE; + + default: + BUG(); + } + + if (shared_index < 0 || shared_index >= si->pebs_count) { + SSDFS_ERR("invalid shared_index %d\n", + shared_index); + return -ERANGE; + } + + relation = &si->peb_array[shared_index]; + + destination_state = atomic_read(&relation->migration_state); + switch (destination_state) { + case SSDFS_PEB_MIGRATION_PREPARATION: + SSDFS_ERR("destination PEB is under preparation: " + "shared_index %d\n", + shared_index); + return -ERANGE; + + case SSDFS_PEB_UNDER_MIGRATION: + switch (atomic_read(&relation->items_state)) { + case SSDFS_PEB1_DST_CONTAINER: + case SSDFS_PEB2_DST_CONTAINER: + case SSDFS_PEB1_SRC_PEB2_DST_CONTAINER: + case SSDFS_PEB2_SRC_PEB1_DST_CONTAINER: + /* do nothing */ + break; + + default: + SSDFS_WARN("invalid relation state: " + "shared_index %d\n", + shared_index); + return -ERANGE; + } + + down_read(&relation->lock); + + if (!relation->dst_peb) { + err = -ERANGE; + SSDFS_ERR("dst_peb is NULL\n"); + goto finish_define_relation; + } + + ptr->dst_peb = relation->dst_peb; + atomic_inc(&relation->dst_peb_refs); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("peb_id %llu, dst_peb_refs %d\n", + relation->dst_peb->peb_id, + atomic_read(&relation->dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + +finish_define_relation: + up_read(&relation->lock); + + if (unlikely(err)) { + SSDFS_ERR("fail to define relation: " + "shared_index %d\n", + shared_index); + return err; + } + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, peb_index); + return -ERANGE; + } + + dst_peb_index = ptr->dst_peb->peb_index; + + dst_leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + dst_peb_index); + if (dst_leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, peb_index); + return -ERANGE; + } + + err = ssdfs_maptbl_set_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + dst_leb_id, + dst_peb_index, + &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + ptr->dst_peb = NULL; + atomic_dec(&relation->dst_peb_refs); +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dst_peb_refs %d\n", + atomic_read(&relation->dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; + } + + err = ssdfs_maptbl_set_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + dst_leb_id, + dst_peb_index, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to set relation LEB to PEB: " + "leb_id %llu, dst_peb_index %u" + "err %d\n", + leb_id, dst_peb_index, err); + ptr->dst_peb = NULL; + atomic_dec(&relation->dst_peb_refs); +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dst_peb_refs %d\n", + atomic_read(&relation->dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; + } + + switch (atomic_read(&ptr->items_state)) { + case SSDFS_PEB1_SRC_CONTAINER: + atomic_set(&ptr->items_state, + SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER); + break; + + case SSDFS_PEB2_SRC_CONTAINER: + atomic_set(&ptr->items_state, + SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER); + break; + + default: + BUG(); + } + break; + + case SSDFS_PEB_RELATION_PREPARATION: + SSDFS_WARN("peb not migrating: " + "shared_index %d\n", + shared_index); + return -ERANGE; + + case SSDFS_PEB_NOT_MIGRATING: + SSDFS_WARN("peb not migrating: " + "shared_index %d\n", + shared_index); + return -ERANGE; + + default: + BUG(); + } + + return 0; +} + +/* + * __ssdfs_peb_container_prepare_destination() - prepare destination + * @ptr: pointer on PEB container + * + * This method tries to create the destination PEB in requested + * container. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + * %-ENODATA - try to create a relation. + */ +static +int __ssdfs_peb_container_prepare_destination(struct ssdfs_peb_container *ptr) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_migration_destination *destination; + struct ssdfs_maptbl_peb_relation pebr; + struct ssdfs_peb_info *pebi; + struct ssdfs_peb_blk_bmap *peb_blkbmap; + int shared_index; + int destination_state; + int items_state; + u16 peb_index; + u64 leb_id; + u64 peb_id; + u64 seg; + u32 log_pages; + u8 peb_migration_id; + struct completion *end; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + seg = si->seg_id; + peb_index = ptr->peb_index; + log_pages = ptr->log_pages; + + spin_lock(&si->migration.lock); + destination = &si->migration.array[SSDFS_CREATING_DESTINATION]; + destination_state = destination->state; + shared_index = destination->shared_peb_index; + spin_unlock(&si->migration.lock); + + if (destination_state != SSDFS_DESTINATION_UNDER_CREATION && + shared_index != ptr->peb_index) { + SSDFS_ERR("destination_state %#x, " + "shared_index %d, " + "peb_index %u\n", + destination_state, + shared_index, + ptr->peb_index); + return -ERANGE; + } + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, si->seg_id, peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, peb_index); + return -ERANGE; + } + + err = ssdfs_maptbl_add_migration_peb(fsi, leb_id, ptr->peb_type, + &pebr, &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + goto fail_prepare_destination; + } + + err = ssdfs_maptbl_add_migration_peb(fsi, leb_id, + ptr->peb_type, + &pebr, &end); + } + + if (err == -ENODATA) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find PEB for migration: " + "leb_id %llu, peb_type %#x\n", + leb_id, ptr->peb_type); +#endif /* CONFIG_SSDFS_DEBUG */ + goto fail_prepare_destination; + } else if (err == -EBUSY) { + DEFINE_WAIT(wait); + +wait_erase_operation_end: +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("wait_erase_operation_end: " + "leb_id %llu, peb_type %#x\n", + leb_id, ptr->peb_type); +#endif /* CONFIG_SSDFS_DEBUG */ + + wake_up_all(&fsi->maptbl->wait_queue); + + mutex_unlock(&ptr->migration_lock); + prepare_to_wait(&fsi->maptbl->erase_ops_end_wq, &wait, + TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&fsi->maptbl->erase_ops_end_wq, &wait); + mutex_lock(&ptr->migration_lock); + + err = ssdfs_maptbl_add_migration_peb(fsi, leb_id, ptr->peb_type, + &pebr, &end); + if (err == -ENODATA) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find PEB for migration: " + "leb_id %llu, peb_type %#x\n", + leb_id, ptr->peb_type); +#endif /* CONFIG_SSDFS_DEBUG */ + goto fail_prepare_destination; + } else if (err == -EBUSY) { + /* + * We still have pre-erased PEBs. + * Let's wait more. + */ + goto wait_erase_operation_end; + } else if (unlikely(err)) { + SSDFS_ERR("fail to add migration PEB: " + "leb_id %llu, peb_type %#x, " + "err %d\n", + leb_id, ptr->peb_type, err); + goto fail_prepare_destination; + } + } else if (unlikely(err)) { + SSDFS_ERR("fail to add migration PEB: " + "leb_id %llu, peb_type %#x, " + "err %d\n", + leb_id, ptr->peb_type, err); + goto fail_prepare_destination; + } + + down_write(&ptr->lock); + + items_state = atomic_read(&ptr->items_state); + + switch (items_state) { + case SSDFS_PEB1_SRC_CONTAINER: + pebi = &ptr->items[SSDFS_SEG_PEB2]; + break; + + case SSDFS_PEB_CONTAINER_EMPTY: + case SSDFS_PEB2_SRC_CONTAINER: + pebi = &ptr->items[SSDFS_SEG_PEB1]; + break; + + default: + err = -ERANGE; + SSDFS_ERR("invalid container state: %#x\n", + atomic_read(&ptr->items_state)); + goto finish_prepare_destination; + break; + }; + + peb_id = pebr.pebs[SSDFS_MAPTBL_RELATION_INDEX].peb_id; + peb_migration_id = ssdfs_define_next_peb_migration_id(ptr->src_peb); + if (!is_peb_migration_id_valid(peb_migration_id)) { + err = -ERANGE; + SSDFS_ERR("fail to define peb_migration_id\n"); + goto finish_prepare_destination; + } + + err = ssdfs_peb_object_create(pebi, ptr, peb_id, + SSDFS_MAPTBL_CLEAN_PEB_STATE, + peb_migration_id); + if (unlikely(err)) { + SSDFS_ERR("fail to create PEB object: " + "seg %llu, peb_index %u, " + "peb_id %llu\n", + seg, peb_index, + peb_id); + goto finish_prepare_destination; + } + + ptr->dst_peb = pebi; + atomic_inc(&ptr->dst_peb_refs); + + atomic_set(&pebi->state, + SSDFS_PEB_OBJECT_INITIALIZED); + complete_all(&pebi->init_end); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("peb_id %llu, dst_peb_refs %d\n", + pebi->peb_id, + atomic_read(&ptr->dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (items_state) { + case SSDFS_PEB_CONTAINER_EMPTY: + atomic_set(&ptr->items_state, + SSDFS_PEB1_DST_CONTAINER); + break; + + case SSDFS_PEB1_SRC_CONTAINER: + atomic_set(&ptr->items_state, + SSDFS_PEB1_SRC_PEB2_DST_CONTAINER); + break; + + case SSDFS_PEB2_SRC_CONTAINER: + atomic_set(&ptr->items_state, + SSDFS_PEB2_SRC_PEB1_DST_CONTAINER); + break; + + default: + BUG(); + } + + if (atomic_read(&ptr->items_state) == SSDFS_PEB1_DST_CONTAINER) { + int free_blks; + + free_blks = ssdfs_peb_get_free_pages(ptr); + if (unlikely(free_blks < 0)) { + err = free_blks; + SSDFS_ERR("fail to get free_blks: " + "peb_index %u, err %d\n", + ptr->peb_index, err); + goto finish_prepare_destination; + } else if (free_blks == 0) { + err = -ERANGE; + SSDFS_ERR("PEB hasn't free blocks\n"); + goto finish_prepare_destination; + } + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(free_blks >= U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + atomic_set(&ptr->shared_free_dst_blks, (u16)free_blks); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("shared_free_dst_blks %d\n", + atomic_read(&ptr->shared_free_dst_blks)); +#endif /* CONFIG_SSDFS_DEBUG */ + } else { + if (ptr->peb_index >= si->blk_bmap.pebs_count) { + err = -ERANGE; + SSDFS_ERR("peb_index %u >= pebs_count %u\n", + ptr->peb_index, + si->blk_bmap.pebs_count); + goto finish_prepare_destination; + } + + peb_blkbmap = &si->blk_bmap.peb[ptr->peb_index]; + err = ssdfs_peb_blk_bmap_start_migration(peb_blkbmap); + if (unlikely(err)) { + SSDFS_ERR("fail to start PEB's block bitmap migration: " + "seg %llu, peb_index %u, err %d\n", + si->seg_id, ptr->peb_index, err); + goto finish_prepare_destination; + } + } + +finish_prepare_destination: + up_write(&ptr->lock); + + if (unlikely(err)) + goto fail_prepare_destination; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("peb_container_prepare_destination: " + "seg_id %llu, leb_id %llu, peb_id %llu, " + "free_blks %d, used_blks %d, invalid_blks %d\n", + si->seg_id, leb_id, peb_id, + ssdfs_peb_get_free_pages(ptr), + ssdfs_peb_get_used_data_pages(ptr), + ssdfs_peb_get_invalid_pages(ptr)); +#endif /* CONFIG_SSDFS_DEBUG */ + + spin_lock(&si->migration.lock); + ssdfs_memcpy(&si->migration.array[SSDFS_LAST_DESTINATION], + 0, sizeof(struct ssdfs_migration_destination), + &si->migration.array[SSDFS_CREATING_DESTINATION], + 0, sizeof(struct ssdfs_migration_destination), + sizeof(struct ssdfs_migration_destination)); + destination = &si->migration.array[SSDFS_LAST_DESTINATION]; + destination->state = SSDFS_VALID_DESTINATION; + memset(&si->migration.array[SSDFS_CREATING_DESTINATION], + 0xFF, sizeof(struct ssdfs_migration_destination)); + destination = &si->migration.array[SSDFS_CREATING_DESTINATION]; + destination->state = SSDFS_EMPTY_DESTINATION; + spin_unlock(&si->migration.lock); + + return 0; + +fail_prepare_destination: + spin_lock(&si->migration.lock); + + destination = &si->migration.array[SSDFS_CREATING_DESTINATION]; + destination->state = SSDFS_EMPTY_DESTINATION; + destination->shared_peb_index = -1; + + destination = &si->migration.array[SSDFS_LAST_DESTINATION]; + switch (destination->state) { + case SSDFS_OBSOLETE_DESTINATION: + destination->state = SSDFS_VALID_DESTINATION; + break; + + case SSDFS_EMPTY_DESTINATION: + /* do nothing */ + break; + + case SSDFS_VALID_DESTINATION: + SSDFS_DBG("old destination is valid\n"); + break; + + default: + BUG(); + }; + + spin_unlock(&si->migration.lock); + + return err; +} + +/* + * ssdfs_peb_container_prepare_zns_destination() - prepare ZNS destination + * @ptr: pointer on PEB container + * + * This method tries to create relation with shared segment for + * user data updates. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + * %-ENODATA - try to create a relation. + */ +static +int ssdfs_peb_container_prepare_zns_destination(struct ssdfs_peb_container *ptr) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_current_segment *cur_seg; + struct ssdfs_segment_info *si; + struct ssdfs_segment_info *dest_si = NULL; + struct ssdfs_peb_mapping_table *maptbl; + u64 start = U64_MAX; + int seg_type = SSDFS_USER_DATA_SEG_TYPE; + u16 peb_index, dst_peb_index; + u64 leb_id, dst_leb_id; + struct completion *end; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!mutex_is_locked(&ptr->migration_lock)); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + maptbl = fsi->maptbl; + si = ptr->parent_si; + peb_index = ptr->peb_index; + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, si->seg_id, peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, peb_index); + return -ERANGE; + } + + down_read(&fsi->cur_segs->lock); + + cur_seg = fsi->cur_segs->objects[SSDFS_CUR_DATA_UPDATE_SEG]; + + ssdfs_current_segment_lock(cur_seg); + + if (is_ssdfs_current_segment_empty(cur_seg)) { + start = cur_seg->seg_id; + dest_si = ssdfs_grab_segment(fsi, seg_type, U64_MAX, start); + if (IS_ERR_OR_NULL(dest_si)) { + err = (dest_si == NULL ? -ENOMEM : PTR_ERR(dest_si)); + if (err == -ENOSPC) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to create segment object: " + "err %d\n", err); +#endif /* CONFIG_SSDFS_DEBUG */ + } else { + SSDFS_ERR("fail to create segment object: " + "err %d\n", err); + } + + goto finish_get_current_segment; + } + + err = ssdfs_current_segment_add(cur_seg, dest_si); + /* + * ssdfs_grab_segment() has got object already. + */ + ssdfs_segment_put_object(dest_si); + if (unlikely(err)) { + SSDFS_ERR("fail to add segment %llu as current: " + "err %d\n", + dest_si->seg_id, err); + goto finish_get_current_segment; + } + } + + dst_peb_index = 0; + dst_leb_id = ssdfs_get_leb_id_for_peb_index(fsi, dest_si->seg_id, dst_peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + dest_si->seg_id, dst_peb_index); + return -ERANGE; + } + +finish_get_current_segment: + ssdfs_current_segment_unlock(cur_seg); + up_read(&fsi->cur_segs->lock); + + if (unlikely(err)) + return err; + + err = ssdfs_maptbl_set_zns_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + ptr->dst_peb = NULL; + return err; + } + + err = ssdfs_maptbl_set_zns_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to set relation LEB to PEB: " + "leb_id %llu, dst leb_id %llu" + "err %d\n", + leb_id, dst_leb_id, err); + ptr->dst_peb = NULL; + return err; + } + + switch (atomic_read(&ptr->items_state)) { + case SSDFS_PEB1_SRC_CONTAINER: + atomic_set(&ptr->items_state, + SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER); + break; + + case SSDFS_PEB2_SRC_CONTAINER: + atomic_set(&ptr->items_state, + SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER); + break; + + default: + BUG(); + } + + return 0; +} + +/* + * ssdfs_peb_container_prepare_destination() - prepare destination + * @ptr: pointer on PEB container + * + * This method tries to create the destination PEB in requested + * container. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + * %-ENODATA - try to create a relation. + */ +static +int ssdfs_peb_container_prepare_destination(struct ssdfs_peb_container *ptr) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + + if (fsi->is_zns_device && is_ssdfs_peb_containing_user_data(ptr)) + err = ssdfs_peb_container_prepare_zns_destination(ptr); + else + err = __ssdfs_peb_container_prepare_destination(ptr); + + if (err == -ENODATA) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to prepare destination: " + "seg %llu, peb_index %u, err %d\n", + si->seg_id, ptr->peb_index, err); +#endif /* CONFIG_SSDFS_DEBUG */ + } else if (unlikely(err)) { + SSDFS_ERR("fail to prepare destination: " + "seg %llu, peb_index %u, err %d\n", + si->seg_id, ptr->peb_index, err); + } + + return err; +} + +/* + * ssdfs_peb_container_create_destination() - create destination + * @ptr: pointer on PEB container + * + * This method tries to create the destination or relation + * with another PEB container. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +int ssdfs_peb_container_create_destination(struct ssdfs_peb_container *ptr) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_peb_container *relation; + struct ssdfs_migration_destination *destination; + bool need_create_relation = false; + u16 migration_threshold; + u16 pebs_per_destination; + u16 destination_index; + int migration_state; + int items_state; + int destination_pebs; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!mutex_is_locked(&ptr->migration_lock)); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + + spin_lock(&fsi->volume_state_lock); + migration_threshold = fsi->migration_threshold; + spin_unlock(&fsi->volume_state_lock); + + migration_state = atomic_read(&ptr->migration_state); + + if (migration_state != SSDFS_PEB_NOT_MIGRATING) { + err = -ERANGE; + SSDFS_ERR("invalid migration_state %#x\n", + migration_state); + goto finish_create_destination; + } + + items_state = atomic_read(&ptr->items_state); + + if (items_state != SSDFS_PEB1_SRC_CONTAINER && + items_state != SSDFS_PEB2_SRC_CONTAINER) { + err = -ERANGE; + SSDFS_ERR("invalid items_state %#x\n", + items_state); + goto finish_create_destination; + } + + pebs_per_destination = fsi->pebs_per_seg / migration_threshold; + destination_index = + atomic_inc_return(&si->migration.migrating_pebs) - 1; + destination_index /= pebs_per_destination; + +try_start_preparation_again: + spin_lock(&si->migration.lock); + + destination = &si->migration.array[SSDFS_LAST_DESTINATION]; + + switch (destination->state) { + case SSDFS_EMPTY_DESTINATION: + need_create_relation = false; + destination = &si->migration.array[SSDFS_CREATING_DESTINATION]; + destination->state = SSDFS_DESTINATION_UNDER_CREATION; + destination->destination_pebs++; + destination->shared_peb_index = ptr->peb_index; + break; + + case SSDFS_VALID_DESTINATION: + destination_pebs = destination->destination_pebs; + need_create_relation = destination_index < destination_pebs; + + if (need_create_relation) { +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(destination_index >= si->pebs_count); +#endif /* CONFIG_SSDFS_DEBUG */ + + relation = &si->peb_array[destination_index]; + if (atomic_read(&relation->shared_free_dst_blks) <= 0) { + /* destination hasn't free room */ + need_create_relation = false; + } + } + + if (!need_create_relation) { + destination = + &si->migration.array[SSDFS_CREATING_DESTINATION]; + destination->state = SSDFS_DESTINATION_UNDER_CREATION; + destination->destination_pebs++; + destination->shared_peb_index = ptr->peb_index; + } + break; + + case SSDFS_OBSOLETE_DESTINATION: + destination = &si->migration.array[SSDFS_CREATING_DESTINATION]; + + if (destination->state != SSDFS_DESTINATION_UNDER_CREATION) { + err = -ERANGE; + SSDFS_WARN("invalid destination state %#x\n", + destination->state); + goto finish_check_destination; + } + + destination_pebs = destination->destination_pebs; + need_create_relation = destination_index < destination_pebs; + + if (!need_create_relation) + err = -EAGAIN; + break; + + default: + BUG(); + }; + +finish_check_destination: + spin_unlock(&si->migration.lock); + + if (err == -EAGAIN) { + DEFINE_WAIT(wait); + + mutex_unlock(&ptr->migration_lock); + prepare_to_wait(&ptr->migration_wq, &wait, + TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&ptr->migration_wq, &wait); + mutex_lock(&ptr->migration_lock); + err = 0; + goto try_start_preparation_again; + } else if (unlikely(err)) + goto finish_create_destination; + + if (need_create_relation) { +create_relation: + atomic_set(&ptr->migration_state, + SSDFS_PEB_RELATION_PREPARATION); + + err = ssdfs_peb_container_prepare_relation(ptr); + if (unlikely(err)) { + SSDFS_ERR("fail to prepare relation: " + "err %d\n", + err); + goto finish_create_destination; + } + + atomic_set(&ptr->migration_state, + SSDFS_PEB_UNDER_MIGRATION); + } else { + atomic_set(&ptr->migration_state, + SSDFS_PEB_MIGRATION_PREPARATION); + + err = ssdfs_peb_container_prepare_destination(ptr); + if (err == -ENODATA) { + err = 0; + goto create_relation; + } else if (unlikely(err)) { + SSDFS_ERR("fail to prepare destination: " + "err %d\n", + err); + goto finish_create_destination; + } + + atomic_set(&ptr->migration_state, + SSDFS_PEB_UNDER_MIGRATION); + } + +finish_create_destination: + if (unlikely(err)) { + atomic_set(&ptr->migration_state, migration_state); + atomic_dec(&si->migration.migrating_pebs); + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("migration_state %d\n", + atomic_read(&ptr->migration_state)); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +/* + * ssdfs_peb_container_move_dest2source() - convert destination into source + * @ptr: pointer on PEB container + * @state: current state of items + * + * This method tries to transform destination PEB + * into source PEB. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + * %-ENODATA - destination PEB has references. + */ +static +int ssdfs_peb_container_move_dest2source(struct ssdfs_peb_container *ptr, + int state) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_segment_migration_info *mi; + struct ssdfs_migration_destination *mdest; + int new_state; + u64 leb_id; + u64 peb_create_time = U64_MAX; + u64 last_log_time = U64_MAX; + struct completion *end; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!rwsem_is_locked(&ptr->lock)); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u, " + "state %#x\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages, + state); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + ptr->peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, ptr->peb_index); + return -ERANGE; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("peb_id %llu, dst_peb_refs %d\n", + ptr->dst_peb->peb_id, + atomic_read(&ptr->dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (atomic_read(&ptr->dst_peb_refs) > 1) { + /* wait of absence of references */ +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("leb_id %llu, peb_index %u, " + "refs_count %u\n", + leb_id, ptr->peb_index, + atomic_read(&ptr->dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + return -ENODATA; + } + + switch (state) { + case SSDFS_PEB1_DST_CONTAINER: + case SSDFS_PEB2_SRC_PEB1_DST_CONTAINER: + new_state = SSDFS_PEB1_SRC_CONTAINER; + break; + + case SSDFS_PEB2_DST_CONTAINER: + case SSDFS_PEB1_SRC_PEB2_DST_CONTAINER: + new_state = SSDFS_PEB2_SRC_CONTAINER; + break; + + default: + SSDFS_WARN("invalid state: %#x\n", + state); + return -ERANGE; + } + + if (ptr->src_peb) { + peb_create_time = ptr->src_peb->peb_create_time; + + ssdfs_peb_current_log_lock(ptr->src_peb); + last_log_time = ptr->src_peb->current_log.last_log_time; + ssdfs_peb_current_log_unlock(ptr->src_peb); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("seg %llu, peb %llu, " + "peb_create_time %llx, last_log_time %llx\n", + si->seg_id, + ptr->src_peb->peb_id, + peb_create_time, + last_log_time); +#endif /* CONFIG_SSDFS_DEBUG */ + } else { + peb_create_time = ptr->dst_peb->peb_create_time; + + ssdfs_peb_current_log_lock(ptr->dst_peb); + last_log_time = ptr->dst_peb->current_log.last_log_time; + ssdfs_peb_current_log_unlock(ptr->dst_peb); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("seg %llu, peb %llu, " + "peb_create_time %llx, last_log_time %llx\n", + si->seg_id, + ptr->dst_peb->peb_id, + peb_create_time, + last_log_time); +#endif /* CONFIG_SSDFS_DEBUG */ + } + + err = ssdfs_maptbl_exclude_migration_peb(fsi, leb_id, + ptr->peb_type, + peb_create_time, + last_log_time, + &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + return err; + } + + err = ssdfs_maptbl_exclude_migration_peb(fsi, leb_id, + ptr->peb_type, + peb_create_time, + last_log_time, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to exclude migration PEB: " + "leb_id %llu, peb_type %#x, err %d\n", + leb_id, ptr->peb_type, err); + return err; + } + + atomic_dec(&si->peb_array[ptr->dst_peb->peb_index].dst_peb_refs); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("seg_id %llu, leb_id %llu, " + "peb_id %llu, dst_peb_refs %d\n", + si->seg_id, leb_id, + ptr->dst_peb->peb_id, + atomic_read(&si->peb_array[ptr->dst_peb->peb_index].dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (ptr->src_peb) { + err = ssdfs_peb_object_destroy(ptr->src_peb); + WARN_ON(err); + err = 0; + } + + memset(ptr->src_peb, 0, sizeof(struct ssdfs_peb_info)); + ptr->src_peb = ptr->dst_peb; + ptr->dst_peb = NULL; + + atomic_set(&ptr->items_state, new_state); + atomic_set(&ptr->migration_state, SSDFS_PEB_NOT_MIGRATING); + + mi = &ptr->parent_si->migration; + spin_lock(&mi->lock); + atomic_dec(&mi->migrating_pebs); + mdest = &mi->array[SSDFS_LAST_DESTINATION]; + switch (mdest->state) { + case SSDFS_VALID_DESTINATION: + case SSDFS_OBSOLETE_DESTINATION: + mdest->destination_pebs--; + break; + }; + mdest = &mi->array[SSDFS_CREATING_DESTINATION]; + switch (mdest->state) { + case SSDFS_DESTINATION_UNDER_CREATION: + mdest->destination_pebs--; + break; + }; + spin_unlock(&mi->lock); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return 0; +} + +/* + * ssdfs_peb_container_break_relation() - break relation with PEB + * @ptr: pointer on PEB container + * @state: current state of items + * @new_state: new state of items + * + * This method tries to break relation with destination PEB. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +static +int ssdfs_peb_container_break_relation(struct ssdfs_peb_container *ptr, + int state, int new_state) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_peb_mapping_table *maptbl; + u64 leb_id, dst_leb_id; + u16 dst_peb_index; + int dst_peb_refs; + struct completion *end; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb || !ptr->dst_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!rwsem_is_locked(&ptr->lock)); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u, " + "state %#x, new_state %#x\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages, + state, new_state); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + maptbl = fsi->maptbl; + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + ptr->peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, ptr->peb_index); + return -ERANGE; + } + + dst_peb_index = ptr->dst_peb->peb_index; + + dst_leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + dst_peb_index); + if (dst_leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, dst_peb_index); + return -ERANGE; + } + + dst_peb_refs = atomic_read(&si->peb_array[dst_peb_index].dst_peb_refs); + + err = ssdfs_maptbl_break_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + dst_leb_id, + dst_peb_refs, + &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + return err; + } + + err = ssdfs_maptbl_break_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + dst_leb_id, + dst_peb_refs, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to break relation: " + "leb_id %llu, peb_index %u, err %d\n", + leb_id, ptr->peb_index, err); + return err; + } + + atomic_dec(&si->peb_array[ptr->dst_peb->peb_index].dst_peb_refs); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("peb_id %llu, dst_peb_refs %d\n", + ptr->dst_peb->peb_id, + atomic_read(&si->peb_array[ptr->dst_peb->peb_index].dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (new_state == SSDFS_PEB_CONTAINER_EMPTY) { + err = ssdfs_peb_object_destroy(ptr->src_peb); + WARN_ON(err); + err = 0; + + memset(ptr->src_peb, 0, sizeof(struct ssdfs_peb_info)); + } else + ptr->dst_peb = NULL; + + atomic_set(&ptr->items_state, new_state); + atomic_set(&ptr->migration_state, SSDFS_PEB_NOT_MIGRATING); + atomic_dec(&ptr->parent_si->migration.migrating_pebs); + + return 0; +} + +/* + * ssdfs_peb_container_break_zns_relation() - break relation with PEB + * @ptr: pointer on PEB container + * @state: current state of items + * @new_state: new state of items + * + * This method tries to break relation with shared zone. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +static +int ssdfs_peb_container_break_zns_relation(struct ssdfs_peb_container *ptr, + int state, int new_state) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_peb_mapping_table *maptbl; + struct ssdfs_segment_blk_bmap *seg_blkbmap; + struct ssdfs_invextree_info *invextree; + struct ssdfs_btree_search *search; + struct ssdfs_raw_extent extent; + u64 leb_id; + int invalid_blks; + struct completion *end; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb || !ptr->dst_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!rwsem_is_locked(&ptr->lock)); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u, " + "state %#x, new_state %#x\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages, + state, new_state); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + maptbl = fsi->maptbl; + seg_blkbmap = &si->blk_bmap; + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + ptr->peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, ptr->peb_index); + return -ERANGE; + } + + err = ssdfs_maptbl_break_zns_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + return err; + } + + err = ssdfs_maptbl_break_zns_indirect_relation(maptbl, + leb_id, + ptr->peb_type, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to break relation: " + "leb_id %llu, peb_index %u, err %d\n", + leb_id, ptr->peb_index, err); + return err; + } + + invextree = fsi->invextree; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!invextree); +#endif /* CONFIG_SSDFS_DEBUG */ + + invalid_blks = ssdfs_segment_blk_bmap_get_invalid_pages(seg_blkbmap); + if (invalid_blks <= 0) { + SSDFS_ERR("invalid state: " + "leb_id %llu, invalid_blks %d\n", + leb_id, invalid_blks); + return -ERANGE; + } + + search = ssdfs_btree_search_alloc(); + if (!search) { + SSDFS_ERR("fail to allocate btree search object\n"); + return -ENOMEM; + } + + extent.seg_id = cpu_to_le64(si->seg_id); + extent.logical_blk = cpu_to_le32(0); + extent.len = cpu_to_le32(invalid_blks); + + ssdfs_btree_search_init(search); + err = ssdfs_invextree_delete(invextree, &extent, search); + ssdfs_btree_search_free(search); + + if (unlikely(err)) { + SSDFS_ERR("fail to delete invalidated extent: " + "leb_id %llu, len %d, err %d\n", + leb_id, invalid_blks, err); + return err; + } + + if (new_state == SSDFS_PEB_CONTAINER_EMPTY) { + err = ssdfs_peb_object_destroy(ptr->src_peb); + WARN_ON(err); + err = 0; + + memset(ptr->src_peb, 0, sizeof(struct ssdfs_peb_info)); + } else + ptr->dst_peb = NULL; + + atomic_set(&ptr->items_state, new_state); + atomic_set(&ptr->migration_state, SSDFS_PEB_NOT_MIGRATING); + atomic_dec(&ptr->parent_si->migration.migrating_pebs); + + return 0; +} + +/* + * ssdfs_peb_container_forget_source() - forget about dirty source PEB + * @ptr: pointer on PEB container + * + * This method tries to forget about dirty source PEB. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +int ssdfs_peb_container_forget_source(struct ssdfs_peb_container *ptr) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_segment_migration_info *mi; + struct ssdfs_migration_destination *mdest; + struct ssdfs_peb_mapping_table *maptbl; + struct ssdfs_peb_blk_bmap *peb_blkbmap; + int migration_state; + int items_state; + u64 leb_id; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!mutex_is_locked(&ptr->migration_lock)); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + maptbl = fsi->maptbl; + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + ptr->peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, ptr->peb_index); + return -ERANGE; + } + +#ifdef CONFIG_SSDFS_DEBUG + if (rwsem_is_locked(&ptr->lock)) { + SSDFS_DBG("PEB is locked: " + "leb_id %llu\n", leb_id); + } +#endif /* CONFIG_SSDFS_DEBUG */ + + down_write(&ptr->lock); + + migration_state = atomic_read(&ptr->migration_state); + if (migration_state != SSDFS_PEB_FINISHING_MIGRATION) { + err = -ERANGE; + SSDFS_WARN("invalid migration_state %#x\n", + migration_state); + goto finish_forget_source; + } + + items_state = atomic_read(&ptr->items_state); + switch (items_state) { + case SSDFS_PEB1_DST_CONTAINER: + case SSDFS_PEB1_SRC_PEB2_DST_CONTAINER: + case SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER: + case SSDFS_PEB2_DST_CONTAINER: + case SSDFS_PEB2_SRC_PEB1_DST_CONTAINER: + case SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER: + /* valid state */ + break; + + default: + err = -ERANGE; + SSDFS_WARN("invalid items_state %#x\n", + items_state); + goto finish_forget_source; + }; + +/* + * You cannot move destination into source PEB and + * try to create another one destination for existing + * relations. Otherwise, you will have two full PEBs + * for the same peb_index. So, in the case of full + * destination PEB and presence of relation with another + * source PEB it needs to wake up all threads and to wait + * decreasing the dst_peb_refs counter. + */ + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("items_state %#x\n", items_state); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (items_state) { + case SSDFS_PEB1_DST_CONTAINER: + case SSDFS_PEB2_DST_CONTAINER: +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(ptr->src_peb); + BUG_ON(!ptr->dst_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_peb_container_move_dest2source(ptr, + items_state); + if (err == -ENODATA) + goto finish_forget_source; + else if (unlikely(err)) { + SSDFS_ERR("fail to transform destination: " + "leb_id %llu, err %d\n", + leb_id, err); + goto finish_forget_source; + } + + WARN_ON(atomic_read(&ptr->shared_free_dst_blks) > 0); + break; + + case SSDFS_PEB1_SRC_PEB2_DST_CONTAINER: + case SSDFS_PEB2_SRC_PEB1_DST_CONTAINER: +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr->src_peb); + BUG_ON(!ptr->dst_peb); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_peb_container_move_dest2source(ptr, + items_state); + if (err == -ENODATA) + goto finish_forget_source; + else if (unlikely(err)) { + SSDFS_ERR("fail to transform destination: " + "leb_id %llu, err %d\n", + leb_id, err); + goto finish_forget_source; + } + + if (ptr->peb_index >= si->blk_bmap.pebs_count) { + err = -ERANGE; + SSDFS_ERR("peb_index %u >= pebs_count %u\n", + ptr->peb_index, + si->blk_bmap.pebs_count); + goto finish_forget_source; + } + + peb_blkbmap = &si->blk_bmap.peb[ptr->peb_index]; + err = ssdfs_peb_blk_bmap_finish_migration(peb_blkbmap); + if (unlikely(err)) { + SSDFS_ERR("fail to finish bmap migration: " + "seg %llu, peb_index %u, err %d\n", + ptr->parent_si->seg_id, + ptr->peb_index, err); + goto finish_forget_source; + } + break; + + case SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER: + case SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER: { + int new_state = SSDFS_PEB_CONTAINER_STATE_MAX; + int used_blks; + bool has_valid_blks = true; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr->src_peb); + BUG_ON(!ptr->dst_peb); + BUG_ON(atomic_read(&ptr->dst_peb_refs) != 0); +#endif /* CONFIG_SSDFS_DEBUG */ + + used_blks = ssdfs_peb_get_used_data_pages(ptr); + if (used_blks < 0) { + err = used_blks; + SSDFS_ERR("fail to get used_blks: " + "seg %llu, peb_index %u, err %d\n", + ptr->parent_si->seg_id, + ptr->peb_index, err); + goto finish_forget_source; + } + + has_valid_blks = used_blks > 0; + + switch (items_state) { + case SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER: + if (has_valid_blks) + new_state = SSDFS_PEB1_SRC_CONTAINER; + else + new_state = SSDFS_PEB_CONTAINER_EMPTY; + break; + + case SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER: + if (has_valid_blks) + new_state = SSDFS_PEB2_SRC_CONTAINER; + else + new_state = SSDFS_PEB_CONTAINER_EMPTY; + break; + + default: + err = -ERANGE; + SSDFS_WARN("invalid state: %#x\n", + new_state); + goto finish_forget_source; + } + + if (fsi->is_zns_device) { + err = ssdfs_peb_container_break_zns_relation(ptr, + items_state, + new_state); + } else { + err = ssdfs_peb_container_break_relation(ptr, + items_state, + new_state); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to break relation: " + "leb_id %llu, items_state %#x" + "new_state %#x\n", + leb_id, items_state, new_state); + goto finish_forget_source; + } + + if (new_state != SSDFS_PEB_CONTAINER_EMPTY) { + /* try create new destination */ + err = -ENOENT; + goto finish_forget_source; + } + break; + } + + default: + BUG(); + }; + +finish_forget_source: + up_write(&ptr->lock); + + if (err == -ENOENT) { /* create new destination or relation */ + err = ssdfs_peb_container_create_destination(ptr); + if (unlikely(err)) { + SSDFS_ERR("fail to create destination: " + "leb_id %llu, err %d\n", + leb_id, err); + return err; + } + } else if (err == -ENODATA) { + wake_up_all(&si->wait_queue[SSDFS_PEB_FLUSH_THREAD]); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("dst_peb_refs %d\n", + atomic_read(&ptr->dst_peb_refs)); +#endif /* CONFIG_SSDFS_DEBUG */ + + while (atomic_read(&ptr->dst_peb_refs) > 1) { + DEFINE_WAIT(wait); + + mutex_unlock(&ptr->migration_lock); + prepare_to_wait(&ptr->migration_wq, &wait, + TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&ptr->migration_wq, &wait); + mutex_lock(&ptr->migration_lock); + }; + + down_write(&ptr->lock); + + ptr->src_peb = ptr->dst_peb; + ptr->dst_peb = NULL; + + switch (items_state) { + case SSDFS_PEB1_DST_CONTAINER: + case SSDFS_PEB2_SRC_PEB1_DST_CONTAINER: + atomic_set(&ptr->items_state, SSDFS_PEB1_SRC_CONTAINER); + break; + + case SSDFS_PEB2_DST_CONTAINER: + case SSDFS_PEB1_SRC_PEB2_DST_CONTAINER: + atomic_set(&ptr->items_state, SSDFS_PEB2_SRC_CONTAINER); + break; + + default: + BUG(); + }; + + atomic_set(&ptr->migration_state, SSDFS_PEB_NOT_MIGRATING); + + up_write(&ptr->lock); + + mi = &ptr->parent_si->migration; + spin_lock(&mi->lock); + atomic_dec(&mi->migrating_pebs); + mdest = &mi->array[SSDFS_LAST_DESTINATION]; + switch (mdest->state) { + case SSDFS_VALID_DESTINATION: + case SSDFS_OBSOLETE_DESTINATION: + mdest->destination_pebs--; + break; + }; + mdest = &mi->array[SSDFS_CREATING_DESTINATION]; + switch (mdest->state) { + case SSDFS_DESTINATION_UNDER_CREATION: + mdest->destination_pebs--; + break; + }; + spin_unlock(&mi->lock); + } else if (unlikely(err)) + return err; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return 0; +} + +/* + * ssdfs_peb_container_forget_relation() - forget about relation + * @ptr: pointer on PEB container + * + * This method tries to forget about relation with + * destination PEB. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +int ssdfs_peb_container_forget_relation(struct ssdfs_peb_container *ptr) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_peb_mapping_table *maptbl; + int migration_state; + int items_state; + u64 leb_id; + int new_state = SSDFS_PEB_CONTAINER_STATE_MAX; + int used_blks; + bool has_valid_blks = true; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!ptr || !ptr->src_peb); + BUG_ON(!ptr->parent_si || !ptr->parent_si->fsi); + BUG_ON(!ptr->dst_peb); + BUG_ON(atomic_read(&ptr->dst_peb_refs) != 0); + + SSDFS_DBG("ptr %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + ptr, + ptr->peb_index, + ptr->peb_type, + ptr->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = ptr->parent_si->fsi; + si = ptr->parent_si; + maptbl = fsi->maptbl; + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, + si->seg_id, + ptr->peb_index); + if (leb_id >= U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, ptr->peb_index); + return -ERANGE; + } + + down_write(&ptr->lock); + + migration_state = atomic_read(&ptr->migration_state); + if (migration_state != SSDFS_PEB_FINISHING_MIGRATION) { + err = -ERANGE; + SSDFS_WARN("invalid migration_state %#x\n", + migration_state); + goto finish_forget_relation; + } + + used_blks = ssdfs_peb_get_used_data_pages(ptr); + if (used_blks < 0) { + err = used_blks; + SSDFS_ERR("fail to get used_blks: " + "seg %llu, peb_index %u, err %d\n", + ptr->parent_si->seg_id, + ptr->peb_index, err); + goto finish_forget_relation; + } + + has_valid_blks = used_blks > 0; + + items_state = atomic_read(&ptr->items_state); + switch (items_state) { + case SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER: + if (has_valid_blks) + new_state = SSDFS_PEB1_SRC_CONTAINER; + else + new_state = SSDFS_PEB_CONTAINER_EMPTY; + break; + + case SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER: + if (has_valid_blks) + new_state = SSDFS_PEB2_SRC_CONTAINER; + else + new_state = SSDFS_PEB_CONTAINER_EMPTY; + break; + + default: + err = -ERANGE; + SSDFS_WARN("invalid items_state %#x\n", + items_state); + goto finish_forget_relation; + }; + + err = ssdfs_peb_container_break_relation(ptr, + items_state, + new_state); + if (unlikely(err)) { + SSDFS_ERR("fail to break relation: " + "leb_id %llu, items_state %#x" + "new_state %#x\n", + leb_id, items_state, new_state); + } + +finish_forget_relation: + up_write(&ptr->lock); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +/* + * ssdfs_get_current_peb_locked() - lock PEB container and get PEB object + * @pebc: pointer on PEB container + */ +struct ssdfs_peb_info * +ssdfs_get_current_peb_locked(struct ssdfs_peb_container *pebc) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_peb_info *pebi = NULL; + bool is_peb_exhausted; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc || !pebc->parent_si || !pebc->parent_si->fsi); +#endif /* CONFIG_SSDFS_DEBUG */ + + fsi = pebc->parent_si->fsi; + +try_get_current_peb: + switch (atomic_read(&pebc->migration_state)) { + case SSDFS_PEB_NOT_MIGRATING: + down_read(&pebc->lock); + pebi = pebc->src_peb; + if (!pebi) { + err = -ERANGE; + SSDFS_WARN("source PEB is NULL\n"); + goto fail_to_get_current_peb; + } + + atomic_set(&pebc->migration_phase, + SSDFS_PEB_MIGRATION_STATUS_UNKNOWN); + break; + + case SSDFS_PEB_UNDER_MIGRATION: + down_read(&pebc->lock); + + pebi = pebc->src_peb; + if (!pebi) { + err = -ERANGE; + SSDFS_WARN("source PEB is NULL\n"); + goto fail_to_get_current_peb; + } + + ssdfs_peb_current_log_lock(pebi); + is_peb_exhausted = is_ssdfs_peb_exhausted(fsi, pebi); + ssdfs_peb_current_log_unlock(pebi); + + if (is_peb_exhausted) { + if (fsi->is_zns_device && + is_ssdfs_peb_containing_user_data(pebc)) { + atomic_set(&pebc->migration_phase, + SSDFS_SHARED_ZONE_RECEIVES_DATA); + } else { + pebi = pebc->dst_peb; + if (!pebi) { + err = -ERANGE; + SSDFS_WARN("destination PEB is NULL\n"); + goto fail_to_get_current_peb; + } + + atomic_set(&pebc->migration_phase, + SSDFS_DST_PEB_RECEIVES_DATA); + } + } else { + atomic_set(&pebc->migration_phase, + SSDFS_SRC_PEB_NOT_EXHAUSTED); + } + break; + + case SSDFS_PEB_MIGRATION_PREPARATION: + case SSDFS_PEB_RELATION_PREPARATION: + case SSDFS_PEB_FINISHING_MIGRATION: { + DEFINE_WAIT(wait); + + prepare_to_wait(&pebc->migration_wq, &wait, + TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&pebc->migration_wq, &wait); + goto try_get_current_peb; + } + break; + + default: + SSDFS_WARN("invalid state: %#x\n", + atomic_read(&pebc->migration_state)); + return ERR_PTR(-ERANGE); + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("seg %llu, peb %llu, " + "migration_state %#x, migration_phase %#x\n", + pebc->parent_si->seg_id, + pebi->peb_id, + atomic_read(&pebc->migration_state), + atomic_read(&pebc->migration_phase)); +#endif /* CONFIG_SSDFS_DEBUG */ + + return pebi; + +fail_to_get_current_peb: + up_read(&pebc->lock); + return ERR_PTR(err); +} + +/* + * ssdfs_unlock_current_peb() - unlock source and destination PEB objects + * @pebc: pointer on PEB container + */ +void ssdfs_unlock_current_peb(struct ssdfs_peb_container *pebc) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (!rwsem_is_locked(&pebc->lock)) { + SSDFS_WARN("PEB container hasn't been locked: " + "seg %llu, peb_index %u\n", + pebc->parent_si->seg_id, + pebc->peb_index); + } else + up_read(&pebc->lock); +} + +/* + * ssdfs_get_peb_for_migration_id() - get PEB object for migration ID + * @pebc: pointer on PEB container + */ +struct ssdfs_peb_info * +ssdfs_get_peb_for_migration_id(struct ssdfs_peb_container *pebc, + u8 migration_id) +{ + struct ssdfs_peb_info *pebi = NULL; + int known_migration_id; + u64 src_peb_id, dst_peb_id; + int src_migration_id, dst_migration_id; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc || !pebc->parent_si || !pebc->parent_si->fsi); + BUG_ON(!rwsem_is_locked(&pebc->lock)); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (atomic_read(&pebc->migration_state)) { + case SSDFS_PEB_NOT_MIGRATING: + pebi = pebc->src_peb; + if (!pebi) { + err = -ERANGE; + SSDFS_WARN("source PEB is NULL\n"); + goto fail_to_get_peb; + } + + known_migration_id = ssdfs_get_peb_migration_id_checked(pebi); + + if (migration_id != known_migration_id) { + err = -ERANGE; + SSDFS_WARN("peb %llu, " + "migration_id %u != known_migration_id %d\n", + pebi->peb_id, migration_id, + known_migration_id); + goto fail_to_get_peb; + } + break; + + case SSDFS_PEB_UNDER_MIGRATION: + case SSDFS_PEB_MIGRATION_PREPARATION: + case SSDFS_PEB_RELATION_PREPARATION: + case SSDFS_PEB_FINISHING_MIGRATION: + pebi = pebc->src_peb; + if (!pebi) { + err = -ERANGE; + SSDFS_WARN("source PEB is NULL\n"); + goto fail_to_get_peb; + } + + known_migration_id = ssdfs_get_peb_migration_id_checked(pebi); + + if (migration_id != known_migration_id) { + src_peb_id = pebi->peb_id; + src_migration_id = known_migration_id; + + pebi = pebc->dst_peb; + if (!pebi) { + err = -ERANGE; + SSDFS_WARN("destination PEB is NULL\n"); + goto fail_to_get_peb; + } + + known_migration_id = + ssdfs_get_peb_migration_id_checked(pebi); + + if (migration_id != known_migration_id) { + dst_peb_id = pebi->peb_id; + dst_migration_id = known_migration_id; + + err = -ERANGE; + SSDFS_WARN("fail to find PEB: " + "src_peb_id %llu, " + "src_migration_id %d, " + "dst_peb_id %llu, " + "dst_migration_id %d, " + "migration_id %u\n", + src_peb_id, src_migration_id, + dst_peb_id, dst_migration_id, + migration_id); + goto fail_to_get_peb; + } + } + break; + + default: + SSDFS_WARN("invalid state: %#x\n", + atomic_read(&pebc->migration_state)); + return ERR_PTR(-ERANGE); + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("seg %llu, peb %llu, migration_state %#x, " + "migration_phase %#x, migration_id %u\n", + pebc->parent_si->seg_id, + pebi->peb_id, + atomic_read(&pebc->migration_state), + atomic_read(&pebc->migration_phase), + migration_id); +#endif /* CONFIG_SSDFS_DEBUG */ + + return pebi; + +fail_to_get_peb: + return ERR_PTR(err); +} + +/* + * ssdfs_peb_get_free_pages() - get PEB's free pages count + * @ptr: pointer on PEB container + */ +int ssdfs_peb_get_free_pages(struct ssdfs_peb_container *pebc) +{ + struct ssdfs_segment_info *si; + struct ssdfs_segment_blk_bmap *seg_blkbmap; + struct ssdfs_peb_blk_bmap *peb_blkbmap; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc || !pebc->parent_si || !pebc->parent_si->fsi); + BUG_ON(!pebc->parent_si->blk_bmap.peb); + + SSDFS_DBG("pebc %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + pebc, pebc->peb_index, + pebc->peb_type, pebc->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + si = pebc->parent_si; + seg_blkbmap = &si->blk_bmap; + + if (pebc->peb_index >= seg_blkbmap->pebs_count) { + SSDFS_ERR("peb_index %u >= pebs_count %u\n", + pebc->peb_index, + seg_blkbmap->pebs_count); + return -ERANGE; + } + + peb_blkbmap = &seg_blkbmap->peb[pebc->peb_index]; + + return ssdfs_peb_blk_bmap_get_free_pages(peb_blkbmap); +} + +/* + * ssdfs_peb_get_used_pages() - get PEB's valid pages count + * @ptr: pointer on PEB container + */ +int ssdfs_peb_get_used_data_pages(struct ssdfs_peb_container *pebc) +{ + struct ssdfs_segment_info *si; + struct ssdfs_segment_blk_bmap *seg_blkbmap; + struct ssdfs_peb_blk_bmap *peb_blkbmap; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc || !pebc->parent_si || !pebc->parent_si->fsi); + BUG_ON(!pebc->parent_si->blk_bmap.peb); + + SSDFS_DBG("pebc %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + pebc, pebc->peb_index, + pebc->peb_type, pebc->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + si = pebc->parent_si; + seg_blkbmap = &si->blk_bmap; + + if (pebc->peb_index >= seg_blkbmap->pebs_count) { + SSDFS_ERR("peb_index %u >= pebs_count %u\n", + pebc->peb_index, + seg_blkbmap->pebs_count); + return -ERANGE; + } + + peb_blkbmap = &seg_blkbmap->peb[pebc->peb_index]; + + return ssdfs_peb_blk_bmap_get_used_pages(peb_blkbmap); +} + +/* + * ssdfs_peb_get_invalid_pages() - get PEB's invalid pages count + * @ptr: pointer on PEB container + */ +int ssdfs_peb_get_invalid_pages(struct ssdfs_peb_container *pebc) +{ + struct ssdfs_segment_info *si; + struct ssdfs_segment_blk_bmap *seg_blkbmap; + struct ssdfs_peb_blk_bmap *peb_blkbmap; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc || !pebc->parent_si || !pebc->parent_si->fsi); + BUG_ON(!pebc->parent_si->blk_bmap.peb); + + SSDFS_DBG("pebc %p, peb_index %u, " + "peb_type %#x, log_pages %u\n", + pebc, pebc->peb_index, + pebc->peb_type, pebc->log_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + si = pebc->parent_si; + seg_blkbmap = &si->blk_bmap; + + if (pebc->peb_index >= seg_blkbmap->pebs_count) { + SSDFS_ERR("peb_index %u >= pebs_count %u\n", + pebc->peb_index, + seg_blkbmap->pebs_count); + return -ERANGE; + } + + peb_blkbmap = &seg_blkbmap->peb[pebc->peb_index]; + + return ssdfs_peb_blk_bmap_get_invalid_pages(peb_blkbmap); +} + +/* + * ssdfs_peb_container_invalidate_block() - invalidate PEB's block + * @pebc: pointer on PEB container + * @desc: physical offset descriptor + * + * This method tries to invalidate PEB's block. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +int ssdfs_peb_container_invalidate_block(struct ssdfs_peb_container *pebc, + struct ssdfs_phys_offset_descriptor *desc) +{ + struct ssdfs_segment_info *si; + struct ssdfs_peb_info *pebi; + struct ssdfs_segment_blk_bmap *seg_blkbmap; + struct ssdfs_peb_blk_bmap *peb_blkbmap; + struct ssdfs_block_bmap_range range; + u16 peb_index; + u32 peb_page; + u8 peb_migration_id; + int id; + int items_state; + int bmap_index = SSDFS_PEB_BLK_BMAP_INDEX_MAX; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc || !desc); + BUG_ON(!pebc->parent_si || !pebc->parent_si->fsi); + BUG_ON(!pebc->parent_si->blk_bmap.peb); + + SSDFS_DBG("seg %llu, peb_index %u, peb_migration_id %u, " + "logical_offset %u, logical_blk %u, peb_page %u\n", + pebc->parent_si->seg_id, + pebc->peb_index, + desc->blk_state.peb_migration_id, + le32_to_cpu(desc->page_desc.logical_offset), + le16_to_cpu(desc->page_desc.logical_blk), + le16_to_cpu(desc->page_desc.peb_page)); +#endif /* CONFIG_SSDFS_DEBUG */ + + peb_index = pebc->peb_index; + peb_page = le16_to_cpu(desc->page_desc.peb_page); + peb_migration_id = desc->blk_state.peb_migration_id; + + down_read(&pebc->lock); + + items_state = atomic_read(&pebc->items_state); + switch (items_state) { + case SSDFS_PEB1_SRC_CONTAINER: + case SSDFS_PEB2_SRC_CONTAINER: + pebi = pebc->src_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: items_state %#x\n", + items_state); + err = -ERANGE; + goto finish_invalidate_block; + } + bmap_index = SSDFS_PEB_BLK_BMAP_SOURCE; + break; + + case SSDFS_PEB1_DST_CONTAINER: + case SSDFS_PEB2_DST_CONTAINER: + pebi = pebc->dst_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: items_state %#x\n", + items_state); + err = -ERANGE; + goto finish_invalidate_block; + } + bmap_index = SSDFS_PEB_BLK_BMAP_DESTINATION; + break; + + case SSDFS_PEB1_SRC_PEB2_DST_CONTAINER: + case SSDFS_PEB2_SRC_PEB1_DST_CONTAINER: + case SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER: + case SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER: + pebi = pebc->src_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: items_state %#x\n", + items_state); + err = -ERANGE; + goto finish_invalidate_block; + } + + bmap_index = SSDFS_PEB_BLK_BMAP_SOURCE; + id = ssdfs_get_peb_migration_id_checked(pebi); + + if (peb_migration_id != id) { + pebi = pebc->dst_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: " + "items_state %#x\n", + items_state); + err = -ERANGE; + goto finish_invalidate_block; + } + bmap_index = SSDFS_PEB_BLK_BMAP_DESTINATION; + } + break; + + default: + SSDFS_ERR("invalid PEB container's items_state: " + "%#x\n", + items_state); + err = -ERANGE; + goto finish_invalidate_block; + }; + + id = ssdfs_get_peb_migration_id_checked(pebi); + + if (peb_migration_id != id) { + SSDFS_ERR("peb_migration_id %u != pebi->peb_migration_id %u\n", + peb_migration_id, + ssdfs_get_peb_migration_id(pebi)); + err = -ERANGE; + goto finish_invalidate_block; + } + + si = pebc->parent_si; + seg_blkbmap = &si->blk_bmap; + + if (pebc->peb_index >= seg_blkbmap->pebs_count) { + SSDFS_ERR("peb_index %u >= pebs_count %u\n", + pebc->peb_index, + seg_blkbmap->pebs_count); + return -ERANGE; + } + + peb_blkbmap = &seg_blkbmap->peb[pebc->peb_index]; + + range.start = peb_page; + range.len = 1; + + err = ssdfs_peb_blk_bmap_invalidate(peb_blkbmap, + bmap_index, + &range); + if (unlikely(err)) { + SSDFS_ERR("fail to invalidate range: " + "peb %llu, " + "range (start %u, len %u), err %d\n", + pebi->peb_id, + range.start, range.len, err); + goto finish_invalidate_block; + } + +finish_invalidate_block: + up_read(&pebc->lock); + + return err; +} + +/* + * is_peb_joined_into_create_requests_queue() - is PEB joined into create queue? + * @pebc: pointer on PEB container + */ +bool is_peb_joined_into_create_requests_queue(struct ssdfs_peb_container *pebc) +{ + bool is_joined; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc); +#endif /* CONFIG_SSDFS_DEBUG */ + + spin_lock(&pebc->crq_ptr_lock); + is_joined = pebc->create_rq != NULL; + spin_unlock(&pebc->crq_ptr_lock); + + return is_joined; +} + +/* + * ssdfs_peb_join_create_requests_queue() - join to process new page requests + * @pebc: pointer on PEB container + * @create_rq: pointer on shared new page requests queue + * @wait: wait queue of threads that process new pages + * + * This function select PEB's flush thread for processing new page + * requests. Namely, selected PEB object keeps pointer on shared + * new page requests queue and to join into wait queue of another + * flush threads. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EINVAL - invalid input value. + */ +int ssdfs_peb_join_create_requests_queue(struct ssdfs_peb_container *pebc, + struct ssdfs_requests_queue *create_rq) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc); + BUG_ON(!create_rq); + + SSDFS_DBG("seg %llu, peb_index %u, create_rq %p\n", + pebc->parent_si->seg_id, + pebc->peb_index, create_rq); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (is_peb_joined_into_create_requests_queue(pebc)) { + SSDFS_ERR("PEB is joined into create requests queue yet: " + "seg %llu, peb_index %u\n", + pebc->parent_si->seg_id, pebc->peb_index); + return -EINVAL; + } + + if (pebc->thread[SSDFS_PEB_FLUSH_THREAD].task == NULL) { + SSDFS_ERR("PEB hasn't flush thread: " + "seg %llu, peb_index %u\n", + pebc->parent_si->seg_id, pebc->peb_index); + return -EINVAL; + } + + spin_lock(&pebc->crq_ptr_lock); + pebc->create_rq = create_rq; + spin_unlock(&pebc->crq_ptr_lock); + + return 0; +} + +/* + * ssdfs_peb_forget_create_requests_queue() - forget create requests queue + * @pebc: pointer on PEB container + */ +void ssdfs_peb_forget_create_requests_queue(struct ssdfs_peb_container *pebc) +{ +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc); + WARN_ON(!is_peb_joined_into_create_requests_queue(pebc)); +#endif /* CONFIG_SSDFS_DEBUG */ + + spin_lock(&pebc->crq_ptr_lock); + pebc->create_rq = NULL; + spin_unlock(&pebc->crq_ptr_lock); +} + +/* + * ssdfs_peb_container_change_state() - change PEB's state in mapping table + * @pebc: pointer on PEB container + * + * This method tries to change PEB's state in the mapping table. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-ERANGE - internal error. + */ +int ssdfs_peb_container_change_state(struct ssdfs_peb_container *pebc) +{ + struct ssdfs_fs_info *fsi; + struct ssdfs_segment_info *si; + struct ssdfs_segment_blk_bmap *seg_blkbmap; + struct ssdfs_peb_blk_bmap *peb_blkbmap; + struct ssdfs_peb_info *pebi; + struct ssdfs_peb_mapping_table *maptbl; + struct completion *end; + int items_state; + int used_pages, free_pages, invalid_pages; + int new_peb_state = SSDFS_MAPTBL_UNKNOWN_PEB_STATE; + u64 leb_id; + bool is_peb_exhausted = false; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!pebc || !pebc->parent_si || !pebc->parent_si->fsi); + BUG_ON(!rwsem_is_locked(&pebc->lock)); +#endif /* CONFIG_SSDFS_DEBUG */ + + si = pebc->parent_si; + fsi = pebc->parent_si->fsi; + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("pebc %p, seg %llu, peb_index %u, " + "peb_type %#x, log_pages %u\n", + pebc, si->seg_id, pebc->peb_index, + pebc->peb_type, pebc->log_pages); +#else + SSDFS_DBG("pebc %p, seg %llu, peb_index %u, " + "peb_type %#x, log_pages %u\n", + pebc, si->seg_id, pebc->peb_index, + pebc->peb_type, pebc->log_pages); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + seg_blkbmap = &si->blk_bmap; + maptbl = fsi->maptbl; + + if (pebc->peb_index >= seg_blkbmap->pebs_count) { + SSDFS_ERR("peb_index %u >= pebs_count %u\n", + pebc->peb_index, + seg_blkbmap->pebs_count); + return -ERANGE; + } + + peb_blkbmap = &seg_blkbmap->peb[pebc->peb_index]; + + leb_id = ssdfs_get_leb_id_for_peb_index(fsi, si->seg_id, + pebc->peb_index); + if (leb_id == U64_MAX) { + SSDFS_ERR("fail to convert PEB index into LEB ID: " + "seg %llu, peb_index %u\n", + si->seg_id, pebc->peb_index); + return -EINVAL; + } + + items_state = atomic_read(&pebc->items_state); + switch (items_state) { + case SSDFS_PEB1_SRC_CONTAINER: + case SSDFS_PEB2_SRC_CONTAINER: + pebi = pebc->src_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: items_state %#x\n", + items_state); + return -ERANGE; + } + + free_pages = ssdfs_peb_blk_bmap_get_free_pages(peb_blkbmap); + if (free_pages < 0) { + err = free_pages; + SSDFS_ERR("fail to get free pages: err %d\n", + err); + return err; + } + + used_pages = ssdfs_peb_blk_bmap_get_used_pages(peb_blkbmap); + if (used_pages < 0) { + err = used_pages; + SSDFS_ERR("fail to get used pages: err %d\n", + err); + return err; + } + + invalid_pages = + ssdfs_peb_blk_bmap_get_invalid_pages(peb_blkbmap); + if (invalid_pages < 0) { + err = invalid_pages; + SSDFS_ERR("fail to get invalid pages: err %d\n", + err); + return err; + } + + ssdfs_peb_current_log_lock(pebi); + is_peb_exhausted = is_ssdfs_peb_exhausted(fsi, pebi); + ssdfs_peb_current_log_unlock(pebi); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("free_pages %d, used_pages %d, " + "invalid_pages %d, is_peb_exhausted %#x\n", + free_pages, used_pages, + invalid_pages, is_peb_exhausted); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (free_pages == 0) { + if (!is_peb_exhausted) { + new_peb_state = + SSDFS_MAPTBL_USING_PEB_STATE; + } else if (invalid_pages == 0) { + if (used_pages == 0) { + SSDFS_ERR("invalid state: " + "free_pages %d, " + "used_pages %d, " + "invalid_pages %d\n", + free_pages, + used_pages, + invalid_pages); + return -ERANGE; + } + + new_peb_state = + SSDFS_MAPTBL_USED_PEB_STATE; + } else if (used_pages == 0) { + if (invalid_pages == 0) { + SSDFS_ERR("invalid state: " + "free_pages %d, " + "used_pages %d, " + "invalid_pages %d\n", + free_pages, + used_pages, + invalid_pages); + return -ERANGE; + } + + new_peb_state = + SSDFS_MAPTBL_DIRTY_PEB_STATE; + } else { + new_peb_state = + SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE; + } + } else if (used_pages == 0) { + if (invalid_pages == 0) { + new_peb_state = + SSDFS_MAPTBL_CLEAN_PEB_STATE; + } else { + new_peb_state = + SSDFS_MAPTBL_USING_PEB_STATE; + } + } else { + new_peb_state = + SSDFS_MAPTBL_USING_PEB_STATE; + } + + err = ssdfs_maptbl_change_peb_state(fsi, leb_id, + pebc->peb_type, + new_peb_state, &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + return err; + } + + err = ssdfs_maptbl_change_peb_state(fsi, + leb_id, + pebc->peb_type, + new_peb_state, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to change the PEB state: " + "peb_id %llu, new_state %#x, err %d\n", + pebi->peb_id, new_peb_state, err); + return err; + } + break; + + case SSDFS_PEB1_DST_CONTAINER: + case SSDFS_PEB2_DST_CONTAINER: + pebi = pebc->dst_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: items_state %#x\n", + items_state); + return -ERANGE; + } + + free_pages = ssdfs_peb_blk_bmap_get_free_pages(peb_blkbmap); + if (free_pages < 0) { + err = free_pages; + SSDFS_ERR("fail to get free pages: err %d\n", + err); + return err; + } + + used_pages = ssdfs_peb_blk_bmap_get_used_pages(peb_blkbmap); + if (used_pages < 0) { + err = used_pages; + SSDFS_ERR("fail to get used pages: err %d\n", + err); + return err; + } + + invalid_pages = + ssdfs_peb_blk_bmap_get_invalid_pages(peb_blkbmap); + if (invalid_pages < 0) { + err = invalid_pages; + SSDFS_ERR("fail to get invalid pages: err %d\n", + err); + return err; + } + + ssdfs_peb_current_log_lock(pebi); + is_peb_exhausted = is_ssdfs_peb_exhausted(fsi, pebi); + ssdfs_peb_current_log_unlock(pebi); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("free_pages %d, used_pages %d, " + "invalid_pages %d, is_peb_exhausted %#x\n", + free_pages, used_pages, + invalid_pages, is_peb_exhausted); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (free_pages == 0) { + if (!is_peb_exhausted) { + new_peb_state = + SSDFS_MAPTBL_USING_PEB_STATE; + } else if (invalid_pages == 0) { + if (used_pages == 0) { + SSDFS_ERR("invalid state: " + "free_pages %d, " + "used_pages %d, " + "invalid_pages %d\n", + free_pages, + used_pages, + invalid_pages); + return -ERANGE; + } + + new_peb_state = + SSDFS_MAPTBL_USED_PEB_STATE; + } else if (used_pages == 0) { + if (invalid_pages == 0) { + SSDFS_ERR("invalid state: " + "free_pages %d, " + "used_pages %d, " + "invalid_pages %d\n", + free_pages, + used_pages, + invalid_pages); + return -ERANGE; + } + + new_peb_state = + SSDFS_MAPTBL_DIRTY_PEB_STATE; + } else { + new_peb_state = + SSDFS_MAPTBL_PRE_DIRTY_PEB_STATE; + } + } else if (used_pages == 0) { + if (invalid_pages == 0) { + new_peb_state = + SSDFS_MAPTBL_CLEAN_PEB_STATE; + } else { + new_peb_state = + SSDFS_MAPTBL_USING_PEB_STATE; + } + } else { + new_peb_state = + SSDFS_MAPTBL_USING_PEB_STATE; + } + + err = ssdfs_maptbl_change_peb_state(fsi, leb_id, + pebc->peb_type, + new_peb_state, &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + return err; + } + + err = ssdfs_maptbl_change_peb_state(fsi, leb_id, + pebc->peb_type, + new_peb_state, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to change the PEB state: " + "peb_id %llu, new_state %#x, err %d\n", + pebi->peb_id, new_peb_state, err); + return err; + } + break; + + case SSDFS_PEB1_SRC_PEB2_DST_CONTAINER: + case SSDFS_PEB2_SRC_PEB1_DST_CONTAINER: + case SSDFS_PEB1_SRC_EXT_PTR_DST_CONTAINER: + case SSDFS_PEB2_SRC_EXT_PTR_DST_CONTAINER: + pebi = pebc->src_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: items_state %#x\n", + items_state); + return -ERANGE; + } + + free_pages = ssdfs_src_blk_bmap_get_free_pages(peb_blkbmap); + if (free_pages < 0) { + err = free_pages; + SSDFS_ERR("fail to get free pages: err %d\n", + err); + return err; + } + + used_pages = ssdfs_src_blk_bmap_get_used_pages(peb_blkbmap); + if (used_pages < 0) { + err = used_pages; + SSDFS_ERR("fail to get used pages: err %d\n", + err); + return err; + } + + invalid_pages = + ssdfs_src_blk_bmap_get_invalid_pages(peb_blkbmap); + if (invalid_pages < 0) { + err = invalid_pages; + SSDFS_ERR("fail to get invalid pages: err %d\n", + err); + return err; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("source PEB: free_pages %d, used_pages %d, " + "invalid_pages %d\n", + free_pages, used_pages, invalid_pages); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (invalid_pages == 0) { + if (used_pages == 0) { + SSDFS_ERR("invalid state: " + "used_pages %d, " + "invalid_pages %d\n", + used_pages, + invalid_pages); + return -ERANGE; + } + + new_peb_state = + SSDFS_MAPTBL_MIGRATION_SRC_USED_STATE; + } else if (used_pages == 0) { + new_peb_state = + SSDFS_MAPTBL_MIGRATION_SRC_DIRTY_STATE; + } else { + new_peb_state = + SSDFS_MAPTBL_MIGRATION_SRC_PRE_DIRTY_STATE; + } + + err = ssdfs_maptbl_change_peb_state(fsi, leb_id, + pebc->peb_type, + new_peb_state, &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + return err; + } + + err = ssdfs_maptbl_change_peb_state(fsi, leb_id, + pebc->peb_type, + new_peb_state, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to change the PEB state: " + "peb_id %llu, new_state %#x, err %d\n", + pebi->peb_id, new_peb_state, err); + return err; + } + + pebi = pebc->dst_peb; + if (!pebi) { + SSDFS_ERR("PEB pointer is NULL: " + "items_state %#x\n", + items_state); + return -ERANGE; + } + + free_pages = ssdfs_dst_blk_bmap_get_free_pages(peb_blkbmap); + if (free_pages < 0) { + err = free_pages; + SSDFS_ERR("fail to get free pages: err %d\n", + err); + return err; + } + + used_pages = ssdfs_dst_blk_bmap_get_used_pages(peb_blkbmap); + if (used_pages < 0) { + err = used_pages; + SSDFS_ERR("fail to get used pages: err %d\n", + err); + return err; + } + + invalid_pages = + ssdfs_dst_blk_bmap_get_invalid_pages(peb_blkbmap); + if (invalid_pages < 0) { + err = invalid_pages; + SSDFS_ERR("fail to get invalid pages: err %d\n", + err); + return err; + } + + ssdfs_peb_current_log_lock(pebi); + is_peb_exhausted = is_ssdfs_peb_exhausted(fsi, pebi); + ssdfs_peb_current_log_unlock(pebi); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("destination PEB: free_pages %d, used_pages %d, " + "invalid_pages %d, is_peb_exhausted %#x\n", + free_pages, used_pages, + invalid_pages, is_peb_exhausted); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (free_pages == 0) { + if (!is_peb_exhausted) { + new_peb_state = + SSDFS_MAPTBL_MIGRATION_DST_USING_STATE; + } else if (invalid_pages == 0) { + if (used_pages == 0) { + SSDFS_ERR("invalid state: " + "free_pages %d, " + "used_pages %d, " + "invalid_pages %d\n", + free_pages, + used_pages, + invalid_pages); + return -ERANGE; + } + + new_peb_state = + SSDFS_MAPTBL_MIGRATION_DST_USED_STATE; + } else if (used_pages == 0) { + if (invalid_pages == 0) { + SSDFS_ERR("invalid state: " + "free_pages %d, " + "used_pages %d, " + "invalid_pages %d\n", + free_pages, + used_pages, + invalid_pages); + return -ERANGE; + } + + new_peb_state = + SSDFS_MAPTBL_MIGRATION_DST_DIRTY_STATE; + } else { + new_peb_state = + SSDFS_MAPTBL_MIGRATION_DST_PRE_DIRTY_STATE; + } + } else if (used_pages == 0) { + if (invalid_pages == 0) { + new_peb_state = + SSDFS_MAPTBL_MIGRATION_DST_CLEAN_STATE; + } else { + new_peb_state = + SSDFS_MAPTBL_MIGRATION_DST_USING_STATE; + } + } else { + new_peb_state = + SSDFS_MAPTBL_MIGRATION_DST_USING_STATE; + } + + err = ssdfs_maptbl_change_peb_state(fsi, leb_id, + pebc->peb_type, + new_peb_state, &end); + if (err == -EAGAIN) { + err = SSDFS_WAIT_COMPLETION(end); + if (unlikely(err)) { + SSDFS_ERR("maptbl init failed: " + "err %d\n", err); + return err; + } + + err = ssdfs_maptbl_change_peb_state(fsi, leb_id, + pebc->peb_type, + new_peb_state, + &end); + } + + if (unlikely(err)) { + SSDFS_ERR("fail to change the PEB state: " + "peb_id %llu, new_state %#x, err %d\n", + pebi->peb_id, new_peb_state, err); + return err; + } + break; + + default: + SSDFS_ERR("invalid PEB container's items_state: " + "%#x\n", + items_state); + return -ERANGE; + }; + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("finished\n"); +#else + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + return 0; +} -- 2.34.1