Segment bitmap implements API: (1) create - create empty segment bitmap object (2) destroy - destroy segment bitmap object (3) fragment_init - init fragment of segment bitmap (4) flush - flush dirty segment bitmap (5) check_state - check that segment has particular state (6) get_state - get current state of particular segment (7) change_state - change state of segment (8) find - find segment for requested state or state mask (9) find_and_set - find segment for requested state and change state 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/segment_bitmap.c | 3014 +++++++++++++++++++++++++++++++++++++ 1 file changed, 3014 insertions(+) diff --git a/fs/ssdfs/segment_bitmap.c b/fs/ssdfs/segment_bitmap.c index 633cd4cfca0a..50a7cc692fe3 100644 --- a/fs/ssdfs/segment_bitmap.c +++ b/fs/ssdfs/segment_bitmap.c @@ -1805,3 +1805,3017 @@ int ssdfs_segbmap_issue_fragments_update(struct ssdfs_segment_bmap *segbmap, return err; } + +/* + * ssdfs_segbmap_flush_dirty_fragments() - flush dirty fragments + * @segbmap: pointer on segment bitmap object + * @fragments_count: count of fragments in segbmap + * @fragment_size: size of fragment in bytes + * + * This method tries to flush all dirty fragments. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ENODATA - segbmap hasn't dirty fragments. + * %-ERANGE - internal error. + */ +static +int ssdfs_segbmap_flush_dirty_fragments(struct ssdfs_segment_bmap *segbmap, + u16 fragments_count, + u16 fragment_size) +{ + unsigned long *fbmap; + int size; + unsigned long *found; + u16 start_fragment; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, fragments_count %u, fragment_size %u\n", + segbmap, fragments_count, fragment_size); +#endif /* CONFIG_SSDFS_DEBUG */ + + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_MODIFICATION_FBMAP]; + + size = fragments_count; + err = ssdfs_find_first_dirty_fragment(fbmap, size, &found); + if (err == -ENODATA) { + SSDFS_DBG("segbmap hasn't dirty fragments\n"); + return err; + } else if (unlikely(err)) { + SSDFS_ERR("fail to find dirty fragments: " + "err %d\n", + err); + return err; + } else if (!found) { + SSDFS_ERR("invalid bitmap pointer\n"); + return -ERANGE; + } + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(((found - fbmap) * BITS_PER_LONG) >= U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + start_fragment = (u16)((found - fbmap) * BITS_PER_LONG); + + err = ssdfs_segbmap_issue_fragments_update(segbmap, start_fragment, + fragment_size, *found); + if (unlikely(err)) { + SSDFS_ERR("fail to issue fragments update: " + "start_fragment %u, found %#lx, err %d\n", + start_fragment, *found, err); + return err; + } + + err = ssdfs_clear_dirty_state(found); + if (unlikely(err)) { + SSDFS_ERR("fail to clear dirty state: " + "err %d\n", + err); + return err; + } + + size = fragments_count - (start_fragment + BITS_PER_LONG); + while (size > 0) { + err = ssdfs_find_first_dirty_fragment(++found, size, + &found); + if (err == -ENODATA) + return 0; + else if (unlikely(err)) { + SSDFS_ERR("fail to find dirty fragments: " + "err %d\n", + err); + return err; + } else if (!found) { + SSDFS_ERR("invalid bitmap pointer\n"); + return -ERANGE; + } + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(((found - fbmap) * BITS_PER_LONG) >= U16_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + start_fragment = (u16)((found - fbmap) * BITS_PER_LONG); + + err = ssdfs_segbmap_issue_fragments_update(segbmap, + start_fragment, + fragment_size, + *found); + if (unlikely(err)) { + SSDFS_ERR("fail to issue fragments update: " + "start_fragment %u, found %#lx, err %d\n", + start_fragment, *found, err); + return err; + } + + err = ssdfs_clear_dirty_state(found); + if (unlikely(err)) { + SSDFS_ERR("fail to clear dirty state: " + "err %d\n", + err); + return err; + } + + size = fragments_count - (start_fragment + BITS_PER_LONG); + } + + return 0; +} + +/* + * ssdfs_segbmap_wait_flush_end() - wait flush ending + * @segbmap: pointer on segment bitmap object + * @fragments_count: count of fragments in segbmap + * + * This method is waiting the end of flush operation. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +static +int ssdfs_segbmap_wait_flush_end(struct ssdfs_segment_bmap *segbmap, + u16 fragments_count) +{ + struct ssdfs_segbmap_fragment_desc *fragment; + struct ssdfs_segment_request *req1 = NULL, *req2 = NULL; + bool has_backup; + wait_queue_head_t *wq = NULL; + int i; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, fragments_count %u\n", + segbmap, fragments_count); +#endif /* CONFIG_SSDFS_DEBUG */ + + has_backup = segbmap->flags & SSDFS_SEGBMAP_HAS_COPY; + + for (i = 0; i < fragments_count; i++) { + fragment = &segbmap->desc_array[i]; + + switch (fragment->state) { + case SSDFS_SEGBMAP_FRAG_DIRTY: + SSDFS_ERR("found unprocessed dirty fragment: " + "index %d\n", i); + return -ERANGE; + + case SSDFS_SEGBMAP_FRAG_TOWRITE: + req1 = &fragment->flush_req1; + req2 = &fragment->flush_req2; + +check_req1_state: + switch (atomic_read(&req1->result.state)) { + case SSDFS_REQ_CREATED: + case SSDFS_REQ_STARTED: + wq = &req1->private.wait_queue; + + err = wait_event_killable_timeout(*wq, + has_request_been_executed(req1), + SSDFS_DEFAULT_TIMEOUT); + if (err < 0) + WARN_ON(err < 0); + else + err = 0; + + goto check_req1_state; + break; + + case SSDFS_REQ_FINISHED: + /* do nothing */ + break; + + case SSDFS_REQ_FAILED: + err = req1->result.err; + + if (!err) { + err = -ERANGE; + SSDFS_ERR("error code is absent\n"); + } + + SSDFS_ERR("flush request is failed: " + "err %d\n", err); + return err; + + default: + SSDFS_ERR("invalid result's state %#x\n", + atomic_read(&req1->result.state)); + return -ERANGE; + } + + if (!has_backup) + goto finish_fragment_check; + +check_req2_state: + switch (atomic_read(&req2->result.state)) { + case SSDFS_REQ_CREATED: + case SSDFS_REQ_STARTED: + wq = &req2->private.wait_queue; + + err = wait_event_killable_timeout(*wq, + has_request_been_executed(req2), + SSDFS_DEFAULT_TIMEOUT); + if (err < 0) + WARN_ON(err < 0); + else + err = 0; + + goto check_req2_state; + break; + + case SSDFS_REQ_FINISHED: + /* do nothing */ + break; + + case SSDFS_REQ_FAILED: + err = req2->result.err; + + if (!err) { + err = -ERANGE; + SSDFS_ERR("error code is absent\n"); + } + + SSDFS_ERR("flush request failed: " + "err %d\n", err); + return err; + + default: + SSDFS_ERR("invalid result's state %#x\n", + atomic_read(&req2->result.state)); + return -ERANGE; + } + +finish_fragment_check: + break; + + default: + /* do nothing */ + break; + } + } + + return 0; +} + +/* + * ssdfs_segbmap_issue_commit_logs() - request logs commit + * @segbmap: pointer on segment bitmap object + * @fragments_count: count of fragments in segbmap + * @fragment_size: size of fragment in bytes + * + * This method tries to issue the commit logs operation. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +static +int ssdfs_segbmap_issue_commit_logs(struct ssdfs_segment_bmap *segbmap, + u16 fragments_count, + u16 fragment_size) +{ + struct ssdfs_segbmap_fragment_desc *fragment; + struct ssdfs_segbmap_fragment_header *hdr; + struct ssdfs_segment_request *req1 = NULL, *req2 = NULL; + struct ssdfs_segment_info *si; + struct page *page; + void *kaddr; + size_t extent_size = sizeof(struct ssdfs_volume_extent); + u64 ino = SSDFS_SEG_BMAP_INO; + bool has_backup; + u64 offset; + u16 seg_index; + int copy_id; + u16 i; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, fragments_count %u, fragment_size %u\n", + segbmap, fragments_count, fragment_size); +#endif /* CONFIG_SSDFS_DEBUG */ + + has_backup = segbmap->flags & SSDFS_SEGBMAP_HAS_COPY; + + for (i = 0; i < fragments_count; i++) { + fragment = &segbmap->desc_array[i]; + + switch (fragment->state) { + case SSDFS_SEGBMAP_FRAG_DIRTY: + SSDFS_ERR("found unprocessed dirty fragment: " + "index %d\n", i); + return -ERANGE; + + case SSDFS_SEGBMAP_FRAG_TOWRITE: + req1 = &fragment->flush_req1; + req2 = &fragment->flush_req2; + + ssdfs_request_init(req1); + ssdfs_get_request(req1); + + offset = (u64)i; + offset *= fragment_size; + + ssdfs_request_prepare_logical_extent(ino, offset, + 0, 0, 0, req1); + + page = find_lock_page(&segbmap->pages, i); + if (!page) { + err = -ERANGE; + SSDFS_ERR("fail to find page: " + "fragment_index %u\n", + i); + goto fail_issue_commit_logs; + } + + ssdfs_account_locked_page(page); + kaddr = kmap_local_page(page); + + hdr = SSDFS_SBMP_FRAG_HDR(kaddr); + + err = ssdfs_segbmap_define_volume_extent(segbmap, req1, + hdr, 1, + &seg_index); + if (unlikely(err)) { + SSDFS_ERR("fail to define volume extent: " + "err %d\n", + err); + } + + kunmap_local(kaddr); + ssdfs_unlock_page(page); + ssdfs_put_page(page); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("page %p, count %d\n", + page, page_ref_count(page)); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (unlikely(err)) + goto fail_issue_commit_logs; + + copy_id = SSDFS_MAIN_SEGBMAP_SEG; + si = segbmap->segs[seg_index][copy_id]; + + err = ssdfs_segment_commit_log_async(si, + SSDFS_REQ_ASYNC_NO_FREE, + req1); + if (unlikely(err)) { + SSDFS_ERR("fail to issue the commit log: " + "seg_index %u, err %d\n", + seg_index, err); + goto fail_issue_commit_logs; + } + + if (has_backup) { + ssdfs_request_init(req2); + ssdfs_get_request(req2); + + ssdfs_request_prepare_logical_extent(ino, + offset, + 0, 0, 0, + req2); + + ssdfs_memcpy(&req2->place, 0, extent_size, + &req1->place, 0, extent_size, + extent_size); + + copy_id = SSDFS_COPY_SEGBMAP_SEG; + si = segbmap->segs[seg_index][copy_id]; + + err = ssdfs_segment_commit_log_async(si, + SSDFS_REQ_ASYNC_NO_FREE, + req2); + if (unlikely(err)) { + SSDFS_ERR("fail to issue log commit: " + "seg_index %u, err %d\n", + seg_index, err); + goto fail_issue_commit_logs; + } + } + break; + + default: + /* do nothing */ + break; + } + } + + return 0; + +fail_issue_commit_logs: + ssdfs_put_request(req1); + + if (has_backup) + ssdfs_put_request(req2); + + return err; +} + +/* + * ssdfs_segbmap_wait_finish_commit_logs() - wait commit logs ending + * @segbmap: pointer on segment bitmap object + * @fragments_count: count of fragments in segbmap + * + * This method is waiting the end of commit logs operation. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-ERANGE - internal error. + */ +static +int ssdfs_segbmap_wait_finish_commit_logs(struct ssdfs_segment_bmap *segbmap, + u16 fragments_count) +{ + struct ssdfs_segbmap_fragment_desc *fragment; + struct ssdfs_segment_request *req1 = NULL, *req2 = NULL; + bool has_backup; + wait_queue_head_t *wq = NULL; + int i; + int err; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, fragments_count %u\n", + segbmap, fragments_count); +#endif /* CONFIG_SSDFS_DEBUG */ + + has_backup = segbmap->flags & SSDFS_SEGBMAP_HAS_COPY; + + for (i = 0; i < fragments_count; i++) { + fragment = &segbmap->desc_array[i]; + + switch (fragment->state) { + case SSDFS_SEGBMAP_FRAG_DIRTY: + SSDFS_ERR("found unprocessed dirty fragment: " + "index %d\n", i); + return -ERANGE; + + case SSDFS_SEGBMAP_FRAG_TOWRITE: + req1 = &fragment->flush_req1; + req2 = &fragment->flush_req2; + +check_req1_state: + switch (atomic_read(&req1->result.state)) { + case SSDFS_REQ_CREATED: + case SSDFS_REQ_STARTED: + wq = &req1->private.wait_queue; + + err = wait_event_killable_timeout(*wq, + has_request_been_executed(req1), + SSDFS_DEFAULT_TIMEOUT); + if (err < 0) + WARN_ON(err < 0); + else + err = 0; + + goto check_req1_state; + break; + + case SSDFS_REQ_FINISHED: + /* do nothing */ + break; + + case SSDFS_REQ_FAILED: + err = req1->result.err; + + if (!err) { + err = -ERANGE; + SSDFS_ERR("error code is absent\n"); + } + + SSDFS_ERR("flush request is failed: " + "err %d\n", err); + return err; + + default: + SSDFS_ERR("invalid result's state %#x\n", + atomic_read(&req1->result.state)); + return -ERANGE; + } + + if (!has_backup) + goto finish_fragment_check; + +check_req2_state: + switch (atomic_read(&req2->result.state)) { + case SSDFS_REQ_CREATED: + case SSDFS_REQ_STARTED: + wq = &req2->private.wait_queue; + + err = wait_event_killable_timeout(*wq, + has_request_been_executed(req2), + SSDFS_DEFAULT_TIMEOUT); + if (err < 0) + WARN_ON(err < 0); + else + err = 0; + + goto check_req2_state; + break; + + case SSDFS_REQ_FINISHED: + /* do nothing */ + break; + + case SSDFS_REQ_FAILED: + err = req2->result.err; + + if (!err) { + err = -ERANGE; + SSDFS_ERR("error code is absent\n"); + } + + SSDFS_ERR("flush request is failed: " + "err %d\n", err); + return err; + + default: + SSDFS_ERR("invalid result's state %#x\n", + atomic_read(&req2->result.state)); + return -ERANGE; + } + +finish_fragment_check: + fragment->state = SSDFS_SEGBMAP_FRAG_INITIALIZED; + break; + + default: + /* do nothing */ + break; + } + } + + return 0; +} + +/* TODO: copy all fragments' headers into checkpoint */ +/* TODO: mark superblock as dirty */ +/* TODO: new checkpoint should be stored into superblock segment */ +static +int ssdfs_segbmap_create_checkpoint(struct ssdfs_segment_bmap *segbmap) +{ +#ifdef CONFIG_SSDFS_DEBUG + /* TODO: implement */ + SSDFS_DBG("TODO: implement %s\n", __func__); +#endif /* CONFIG_SSDFS_DEBUG */ + + return 0; +} + +/* + * ssdfs_segbmap_flush() - flush segbmap current state + * @segbmap: pointer on segment bitmap object + * + * This method tries to flush current state of segbmap. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EFAULT - segbmap has corrupted state. + * %-ERANGE - internal error. + */ +int ssdfs_segbmap_flush(struct ssdfs_segment_bmap *segbmap) +{ + u16 fragments_count; + u16 fragment_size; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); +#endif /* CONFIG_SSDFS_DEBUG */ + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("segbmap %p\n", + segbmap); +#else + SSDFS_DBG("segbmap %p\n", + segbmap); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + inode_lock_shared(segbmap->fsi->segbmap_inode); + down_read(&segbmap->resize_lock); + + if (segbmap->flags & SSDFS_SEGBMAP_ERROR) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "segbmap has corrupted state\n"); + goto finish_segbmap_flush; + } + + fragments_count = segbmap->fragments_count; + fragment_size = segbmap->fragment_size; + + ssdfs_sb_segbmap_header_correct_state(segbmap); + + down_write(&segbmap->search_lock); + + err = ssdfs_segbmap_flush_dirty_fragments(segbmap, + fragments_count, + fragment_size); + if (err == -ENODATA) { + err = 0; + up_write(&segbmap->search_lock); + SSDFS_DBG("segbmap hasn't dirty fragments\n"); + goto finish_segbmap_flush; + } else if (unlikely(err)) { + up_write(&segbmap->search_lock); + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fail to flush segbmap: err %d\n", + err); + goto finish_segbmap_flush; + } + + err = ssdfs_segbmap_wait_flush_end(segbmap, fragments_count); + if (unlikely(err)) { + up_write(&segbmap->search_lock); + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fail to flush segbmap: err %d\n", + err); + goto finish_segbmap_flush; + } + + err = ssdfs_segbmap_issue_commit_logs(segbmap, + fragments_count, + fragment_size); + if (unlikely(err)) { + up_write(&segbmap->search_lock); + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fail to flush segbmap: err %d\n", + err); + goto finish_segbmap_flush; + } + + err = ssdfs_segbmap_wait_finish_commit_logs(segbmap, + fragments_count); + if (unlikely(err)) { + up_write(&segbmap->search_lock); + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fail to flush segbmap: err %d\n", + err); + goto finish_segbmap_flush; + } + + downgrade_write(&segbmap->search_lock); + + err = ssdfs_segbmap_create_checkpoint(segbmap); + if (unlikely(err)) { + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fail to create segbmap's checkpoint: " + "err %d\n", + err); + } + + up_read(&segbmap->search_lock); + +finish_segbmap_flush: + up_read(&segbmap->resize_lock); + inode_unlock_shared(segbmap->fsi->segbmap_inode); + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("finished\n"); +#else + SSDFS_DBG("finished\n"); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + return err; +} + +int ssdfs_segbmap_resize(struct ssdfs_segment_bmap *segbmap, + u64 new_items_count) +{ +#ifdef CONFIG_SSDFS_DEBUG + /* TODO: implement */ + SSDFS_DBG("TODO: implement %s\n", __func__); +#endif /* CONFIG_SSDFS_DEBUG */ + + return -ENOSYS; +} + +/* + * ssdfs_segbmap_check_fragment_validity() - check fragment validity + * @segbmap: pointer on segment bitmap object + * @fragment_index: fragment index + * + * This method checks that fragment is ready for operations. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EAGAIN - fragment is under initialization yet. + * %-EFAULT - fragment initialization has failed. + */ +static +int ssdfs_segbmap_check_fragment_validity(struct ssdfs_segment_bmap *segbmap, + pgoff_t fragment_index) +{ + struct ssdfs_segbmap_fragment_desc *fragment; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, fragment_index %lu\n", + segbmap, fragment_index); +#endif /* CONFIG_SSDFS_DEBUG */ + + fragment = &segbmap->desc_array[fragment_index]; + + switch (fragment->state) { + case SSDFS_SEGBMAP_FRAG_CREATED: + return -EAGAIN; + + case SSDFS_SEGBMAP_FRAG_INIT_FAILED: + return -EFAULT; + + case SSDFS_SEGBMAP_FRAG_INITIALIZED: + case SSDFS_SEGBMAP_FRAG_DIRTY: + /* do nothing */ + break; + + default: + BUG(); + } + + return 0; +} + +/* + * ssdfs_segbmap_get_state() - get segment state + * @segbmap: pointer on segment bitmap object + * @seg: segment number + * @end: pointer on completion for waiting init ending [out] + * + * This method tries to get state of @seg. + * + * RETURN: + * [success] - segment state + * [failure] - error code: + * + * %-EAGAIN - fragment is under initialization yet. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + */ +int ssdfs_segbmap_get_state(struct ssdfs_segment_bmap *segbmap, + u64 seg, struct completion **end) +{ + u32 items_per_byte = SSDFS_ITEMS_PER_BYTE(SSDFS_SEG_STATE_BITS); + u32 hdr_size = sizeof(struct ssdfs_segbmap_fragment_header); + u64 items_count; + u16 fragments_count; + u16 fragment_size; + pgoff_t fragment_index; + struct page *page; + u64 page_item; + u32 byte_offset; + void *kaddr; + u8 *byte_ptr; + u32 byte_item; + int state = SSDFS_SEG_STATE_MAX; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + + SSDFS_DBG("segbmap %p, seg %llu\n", + segbmap, seg); +#endif /* CONFIG_SSDFS_DEBUG */ + + *end = NULL; + + inode_lock_shared(segbmap->fsi->segbmap_inode); + down_read(&segbmap->resize_lock); + + items_count = segbmap->items_count; + fragments_count = segbmap->fragments_count; + fragment_size = segbmap->fragment_size; + + if (segbmap->flags & SSDFS_SEGBMAP_ERROR) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "segbmap has corrupted state\n"); + goto finish_segment_check; + } + + if (seg >= items_count) { + err = -ERANGE; + SSDFS_ERR("seg %llu >= items_count %llu\n", + seg, items_count); + goto finish_segment_check; + } + + fragment_index = ssdfs_segbmap_seg_2_fragment_index(seg); + if (fragment_index >= fragments_count) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fragment_index %lu >= fragments_count %u\n", + fragment_index, fragments_count); + goto finish_segment_check; + } + + down_read(&segbmap->search_lock); + + *end = &segbmap->desc_array[fragment_index].init_end; + + err = ssdfs_segbmap_check_fragment_validity(segbmap, fragment_index); + if (err == -EAGAIN) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %lu is not initialized yet\n", + fragment_index); +#endif /* CONFIG_SSDFS_DEBUG */ + goto finish_get_state; + } else if (unlikely(err)) { + SSDFS_ERR("fragment %lu init has failed\n", + fragment_index); + goto finish_get_state; + } + + page = find_lock_page(&segbmap->pages, fragment_index); + if (!page) { + err = -ERANGE; + SSDFS_ERR("fail to get fragment %lu page\n", + fragment_index); + goto finish_get_state; + } + + ssdfs_account_locked_page(page); + + page_item = ssdfs_segbmap_define_first_fragment_item(fragment_index, + fragment_size); + if (seg < page_item) { + err = -ERANGE; + SSDFS_ERR("seg %llu < page_item %llu\n", + seg, page_item); + goto free_page; + } + + page_item = seg - page_item; + + if (page_item >= ssdfs_segbmap_items_per_fragment(fragment_size)) { + err = -ERANGE; + SSDFS_ERR("invalid page_item %llu\n", + page_item); + goto free_page; + } + + byte_offset = ssdfs_segbmap_get_item_byte_offset(page_item); + + if (byte_offset >= PAGE_SIZE) { + err = -ERANGE; + SSDFS_ERR("invalid byte_offset %u\n", + byte_offset); + goto free_page; + } + + byte_item = page_item - ((byte_offset - hdr_size) * items_per_byte); + + kaddr = kmap_local_page(page); + byte_ptr = (u8 *)kaddr + byte_offset; + state = ssdfs_segbmap_get_state_from_byte(byte_ptr, byte_item); + kunmap_local(kaddr); + +free_page: + ssdfs_unlock_page(page); + ssdfs_put_page(page); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("page %p, count %d\n", + page, page_ref_count(page)); +#endif /* CONFIG_SSDFS_DEBUG */ + +finish_get_state: + up_read(&segbmap->search_lock); + +finish_segment_check: + up_read(&segbmap->resize_lock); + inode_unlock_shared(segbmap->fsi->segbmap_inode); + + if (unlikely(err)) + return err; + + return state; +} + +/* + * ssdfs_segbmap_check_state() - check segment state + * @segbmap: pointer on segment bitmap object + * @seg: segment number + * @state: checking state + * @end: pointer on completion for waiting init ending [out] + * + * This method checks that @seg has @state. + * + * RETURN: + * [success] - segment has (1) or hasn't (0) requested @state + * [failure] - error code: + * + * %-EAGAIN - fragment is under initialization yet. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + */ +int ssdfs_segbmap_check_state(struct ssdfs_segment_bmap *segbmap, + u64 seg, int state, + struct completion **end) +{ + int res; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(state < SSDFS_SEG_CLEAN || + state >= SSDFS_SEG_STATE_MAX); + + SSDFS_DBG("segbmap %p, seg %llu, state %#x\n", + segbmap, seg, state); +#endif /* CONFIG_SSDFS_DEBUG */ + + res = ssdfs_segbmap_get_state(segbmap, seg, end); + if (res == -EAGAIN) { + SSDFS_DBG("fragment is not initialized yet\n"); + return res; + } else if (unlikely(res < 0)) { + SSDFS_WARN("fail to get segment %llu state: err %d\n", + seg, res); + return res; + } else if (res != state) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("res %#x != state %#x\n", + res, state); +#endif /* CONFIG_SSDFS_DEBUG */ + return 0; + } + + return 1; +} + +/* + * ssdfs_segbmap_set_state_in_byte() - set state of item in byte + * @byte_ptr: pointer on byte + * @byte_item: index of item in byte + * @old_state: pointer on old state value [in|out] + * @new_state: new state value + */ +static inline +int ssdfs_segbmap_set_state_in_byte(u8 *byte_ptr, u32 byte_item, + int *old_state, int new_state) +{ + u8 value; + int shift = byte_item * SSDFS_SEG_STATE_BITS; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("byte_ptr %p, byte_item %u, " + "old_state %p, new_state %#x\n", + byte_ptr, byte_item, + old_state, new_state); + + BUG_ON(!byte_ptr || !old_state); + BUG_ON(byte_item >= SSDFS_ITEMS_PER_BYTE(SSDFS_SEG_STATE_BITS)); +#endif /* CONFIG_SSDFS_DEBUG */ + + *old_state = (int)((*byte_ptr >> shift) & SSDFS_SEG_STATE_MASK); + + if (*old_state < SSDFS_SEG_CLEAN || + *old_state >= SSDFS_SEG_STATE_MAX) { + SSDFS_ERR("invalid old_state %#x\n", + *old_state); + return -ERANGE; + } + + if (*old_state == new_state) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("old_state %#x == new_state %#x\n", + *old_state, new_state); +#endif /* CONFIG_SSDFS_DEBUG */ + return -EEXIST; + } + + value = new_state & SSDFS_SEG_STATE_MASK; + value <<= shift; + + *byte_ptr &= ~(SSDFS_SEG_STATE_MASK << shift); + *byte_ptr |= value; + + return 0; +} + +/* + * ssdfs_segbmap_correct_fragment_header() - correct fragment's header + * @segbmap: pointer on segment bitmap object + * @fragment_index: fragment index + * @old_state: old state value + * @new_state: new state value + * @kaddr: pointer on fragment's buffer + */ +static +void ssdfs_segbmap_correct_fragment_header(struct ssdfs_segment_bmap *segbmap, + pgoff_t fragment_index, + int old_state, int new_state, + void *kaddr) +{ + struct ssdfs_segbmap_fragment_desc *fragment; + struct ssdfs_segbmap_fragment_header *hdr; + unsigned long *fbmap; + u16 fragment_bytes; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap || !kaddr); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, fragment_index %lu, " + "old_state %#x, new_state %#x, kaddr %p\n", + segbmap, fragment_index, + old_state, new_state, kaddr); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (old_state == new_state) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("old_state %#x == new_state %#x\n", + old_state, new_state); +#endif /* CONFIG_SSDFS_DEBUG */ + return; + } + + fragment = &segbmap->desc_array[fragment_index]; + hdr = SSDFS_SBMP_FRAG_HDR(kaddr); + fragment_bytes = le16_to_cpu(hdr->fragment_bytes); + + fragment->state = SSDFS_SEGBMAP_FRAG_DIRTY; + + switch (old_state) { + case SSDFS_SEG_CLEAN: + switch (new_state) { + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + case SSDFS_SEG_USED: + case SSDFS_SEG_PRE_DIRTY: + case SSDFS_SEG_DIRTY: + case SSDFS_SEG_RESERVED: + case SSDFS_SEG_BAD: + /* expected state */ + break; + + default: + SSDFS_WARN("unexpected change: " + "old_state %#x, new_state %#x\n", + old_state, new_state); + break; + } + break; + + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + switch (new_state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_USED: + case SSDFS_SEG_PRE_DIRTY: + case SSDFS_SEG_DIRTY: + /* expected state */ + break; + + default: + SSDFS_WARN("unexpected change: " + "old_state %#x, new_state %#x\n", + old_state, new_state); + break; + } + break; + + case SSDFS_SEG_USED: + switch (new_state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + case SSDFS_SEG_PRE_DIRTY: + case SSDFS_SEG_DIRTY: + /* expected state */ + break; + + default: + SSDFS_WARN("unexpected change: " + "old_state %#x, new_state %#x\n", + old_state, new_state); + break; + } + break; + + case SSDFS_SEG_PRE_DIRTY: + switch (new_state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + case SSDFS_SEG_USED: + case SSDFS_SEG_DIRTY: + /* expected state */ + break; + + default: + SSDFS_WARN("unexpected change: " + "old_state %#x, new_state %#x\n", + old_state, new_state); + break; + } + break; + + case SSDFS_SEG_RESERVED: + switch (new_state) { + case SSDFS_SEG_DIRTY: + /* expected state */ + break; + + default: + SSDFS_WARN("unexpected change: " + "old_state %#x, new_state %#x\n", + old_state, new_state); + break; + } + break; + + case SSDFS_SEG_DIRTY: + switch (new_state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + case SSDFS_SEG_USED: + case SSDFS_SEG_PRE_DIRTY: + /* expected state */ + break; + + default: + SSDFS_WARN("unexpected change: " + "old_state %#x, new_state %#x\n", + old_state, new_state); + break; + } + break; + + case SSDFS_SEG_BAD: + switch (new_state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_BAD: + /* expected state */ + break; + + default: + SSDFS_WARN("unexpected change: " + "old_state %#x, new_state %#x\n", + old_state, new_state); + break; + } + break; + + + default: + SSDFS_WARN("unexpected state: " + "old_state %#x\n", + old_state); + break; + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("BEFORE: total_segs %u, " + "clean_or_using_segs %u, " + "used_or_dirty_segs %u, " + "bad_segs %u\n", + fragment->total_segs, + fragment->clean_or_using_segs, + fragment->used_or_dirty_segs, + fragment->bad_segs); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (old_state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + case SSDFS_SEG_RESERVED: + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_CLEAN_USING_FBMAP]; + BUG_ON(fragment->clean_or_using_segs == 0); + fragment->clean_or_using_segs--; + if (fragment->clean_or_using_segs == 0) + bitmap_clear(fbmap, fragment_index, 1); + break; + + case SSDFS_SEG_USED: + case SSDFS_SEG_PRE_DIRTY: + case SSDFS_SEG_DIRTY: + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_USED_DIRTY_FBMAP]; + BUG_ON(fragment->used_or_dirty_segs == 0); + fragment->used_or_dirty_segs--; + if (fragment->used_or_dirty_segs == 0) + bitmap_clear(fbmap, fragment_index, 1); + break; + + case SSDFS_SEG_BAD: + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_BAD_FBMAP]; + BUG_ON(fragment->bad_segs == 0); + fragment->bad_segs--; + if (fragment->bad_segs == 0) + bitmap_clear(fbmap, fragment_index, 1); + break; + + default: + BUG(); + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("OLD_STATE: total_segs %u, " + "clean_or_using_segs %u, " + "used_or_dirty_segs %u, " + "bad_segs %u\n", + fragment->total_segs, + fragment->clean_or_using_segs, + fragment->used_or_dirty_segs, + fragment->bad_segs); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (new_state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + case SSDFS_SEG_RESERVED: + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_CLEAN_USING_FBMAP]; + if (fragment->clean_or_using_segs == 0) + bitmap_set(fbmap, fragment_index, 1); + BUG_ON((fragment->clean_or_using_segs + 1) == U16_MAX); + fragment->clean_or_using_segs++; + break; + + case SSDFS_SEG_USED: + case SSDFS_SEG_PRE_DIRTY: + case SSDFS_SEG_DIRTY: + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_USED_DIRTY_FBMAP]; + if (fragment->used_or_dirty_segs == 0) + bitmap_set(fbmap, fragment_index, 1); + BUG_ON((fragment->used_or_dirty_segs + 1) == U16_MAX); + fragment->used_or_dirty_segs++; + break; + + case SSDFS_SEG_BAD: + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_BAD_FBMAP]; + if (fragment->bad_segs == 0) + bitmap_set(fbmap, fragment_index, 1); + BUG_ON((fragment->bad_segs + 1) == U16_MAX); + fragment->bad_segs++; + break; + + default: + BUG(); + } + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("NEW_STATE: total_segs %u, " + "clean_or_using_segs %u, " + "used_or_dirty_segs %u, " + "bad_segs %u\n", + fragment->total_segs, + fragment->clean_or_using_segs, + fragment->used_or_dirty_segs, + fragment->bad_segs); +#endif /* CONFIG_SSDFS_DEBUG */ + + hdr->clean_or_using_segs = cpu_to_le16(fragment->clean_or_using_segs); + hdr->used_or_dirty_segs = cpu_to_le16(fragment->used_or_dirty_segs); + hdr->bad_segs = cpu_to_le16(fragment->bad_segs); + + hdr->checksum = 0; + hdr->checksum = ssdfs_crc32_le(kaddr, fragment_bytes); + + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_MODIFICATION_FBMAP]; + bitmap_set(fbmap, fragment_index, 1); +} + +/* + * __ssdfs_segbmap_change_state() - change segment state + * @segbmap: pointer on segment bitmap object + * @seg: segment number + * @new_state: new state + * @fragment_index: index of fragment + * @fragment_size: size of fragment in bytes + * + * This method tries to change state of @seg. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EAGAIN - fragment is under initialization yet. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + */ +static +int __ssdfs_segbmap_change_state(struct ssdfs_segment_bmap *segbmap, + u64 seg, int new_state, + pgoff_t fragment_index, + u16 fragment_size) +{ + u32 items_per_byte = SSDFS_ITEMS_PER_BYTE(SSDFS_SEG_STATE_BITS); + struct page *page; + u64 page_item; + u32 byte_offset; + u32 byte_item; + void *kaddr; + u8 *byte_ptr; + int old_state; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, seg %llu, new_state %#x, " + "fragment_index %lu, fragment_size %u\n", + segbmap, seg, new_state, + fragment_index, fragment_size); +#endif /* CONFIG_SSDFS_DEBUG */ + + err = ssdfs_segbmap_check_fragment_validity(segbmap, fragment_index); + if (err == -EAGAIN) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %lu is not initialized yet\n", + fragment_index); +#endif /* CONFIG_SSDFS_DEBUG */ + goto finish_set_state; + } else if (unlikely(err)) { + SSDFS_ERR("fragment %lu init has failed\n", + fragment_index); + goto finish_set_state; + } + + page = find_lock_page(&segbmap->pages, fragment_index); + if (!page) { + err = -ERANGE; + SSDFS_ERR("fail to get fragment %lu page\n", + fragment_index); + goto finish_set_state; + } + + ssdfs_account_locked_page(page); + + page_item = ssdfs_segbmap_define_first_fragment_item(fragment_index, + fragment_size); + if (seg < page_item) { + err = -ERANGE; + SSDFS_ERR("seg %llu < page_item %llu\n", + seg, page_item); + goto free_page; + } + + page_item = seg - page_item; + + if (page_item >= ssdfs_segbmap_items_per_fragment(fragment_size)) { + err = -ERANGE; + SSDFS_ERR("invalid page_item %llu\n", + page_item); + goto free_page; + } + + byte_offset = ssdfs_segbmap_get_item_byte_offset(page_item); + + if (byte_offset >= PAGE_SIZE) { + err = -ERANGE; + SSDFS_ERR("invalid byte_offset %u\n", + byte_offset); + goto free_page; + } + + div_u64_rem(page_item, items_per_byte, &byte_item); + + kaddr = kmap_local_page(page); + byte_ptr = (u8 *)kaddr + byte_offset; + err = ssdfs_segbmap_set_state_in_byte(byte_ptr, byte_item, + &old_state, new_state); + if (!err) { + ssdfs_segbmap_correct_fragment_header(segbmap, fragment_index, + old_state, new_state, + kaddr); + } + kunmap_local(kaddr); + + if (err == -EEXIST) { + err = 0; + SetPageUptodate(page); +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("old_state %#x == new_state %#x\n", + old_state, new_state); +#endif /* CONFIG_SSDFS_DEBUG */ + } else if (unlikely(err)) { + SSDFS_ERR("fail to set state: " + "seg %llu, new_state %#x, err %d\n", + seg, new_state, err); + goto free_page; + } else { + SetPageUptodate(page); + if (!PageDirty(page)) + ssdfs_set_page_dirty(page); + } + +free_page: + ssdfs_unlock_page(page); + ssdfs_put_page(page); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("page %p, count %d\n", + page, page_ref_count(page)); +#endif /* CONFIG_SSDFS_DEBUG */ + +finish_set_state: + return err; +} + +/* + * ssdfs_segbmap_change_state() - change segment state + * @segbmap: pointer on segment bitmap object + * @seg: segment number + * @new_state: new state + * @end: pointer on completion for waiting init ending [out] + * + * This method tries to change state of @seg. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EAGAIN - fragment is under initialization yet. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + */ +int ssdfs_segbmap_change_state(struct ssdfs_segment_bmap *segbmap, + u64 seg, int new_state, + struct completion **end) +{ + u64 items_count; + u16 fragments_count; + u16 fragment_size; + pgoff_t fragment_index; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); +#endif /* CONFIG_SSDFS_DEBUG */ + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("segbmap %p, seg %llu, new_state %#x\n", + segbmap, seg, new_state); +#else + SSDFS_DBG("segbmap %p, seg %llu, new_state %#x\n", + segbmap, seg, new_state); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + *end = NULL; + + inode_lock_shared(segbmap->fsi->segbmap_inode); + down_read(&segbmap->resize_lock); + + items_count = segbmap->items_count; + fragments_count = segbmap->fragments_count; + fragment_size = segbmap->fragment_size; + + if (segbmap->flags & SSDFS_SEGBMAP_ERROR) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "segbmap has corrupted state\n"); + goto finish_segment_check; + } + + if (seg >= items_count) { + err = -ERANGE; + SSDFS_ERR("seg %llu >= items_count %llu\n", + seg, items_count); + goto finish_segment_check; + } + + fragment_index = ssdfs_segbmap_seg_2_fragment_index(seg); + if (fragment_index >= fragments_count) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fragment_index %lu >= fragments_count %u\n", + fragment_index, fragments_count); + goto finish_segment_check; + } + + down_write(&segbmap->search_lock); + *end = &segbmap->desc_array[fragment_index].init_end; + err = __ssdfs_segbmap_change_state(segbmap, seg, new_state, + fragment_index, fragment_size); + up_write(&segbmap->search_lock); + +finish_segment_check: + up_read(&segbmap->resize_lock); + inode_unlock_shared(segbmap->fsi->segbmap_inode); + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("finished\n"); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + return err; +} + +/* + * ssdfs_segbmap_choose_fbmap() - choose fragment bitmap + * @segbmap: pointer on segment bitmap object + * @state: requested state + * @mask: requested mask + * + * RETURN: + * [success] - pointer on fragment bitmap + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-EOPNOTSUPP - operation is not supported. + */ +static +unsigned long *ssdfs_segbmap_choose_fbmap(struct ssdfs_segment_bmap *segbmap, + int state, int mask) +{ + unsigned long *fbmap; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + if (state < SSDFS_SEG_CLEAN || state >= SSDFS_SEG_STATE_MAX) { + SSDFS_ERR("unknown segment state %#x\n", state); + return ERR_PTR(-EINVAL); + } + + if ((mask & SSDFS_SEG_CLEAN_USING_MASK) != mask && + (mask & SSDFS_SEG_USED_DIRTY_MASK) != mask && + (mask & SSDFS_SEG_BAD_STATE_MASK) != mask) { + SSDFS_ERR("unsupported set of flags %#x\n", + mask); + return ERR_PTR(-EOPNOTSUPP); + } + + SSDFS_DBG("segbmap %p, state %#x, mask %#x\n", + segbmap, state, mask); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (mask & SSDFS_SEG_CLEAN_USING_MASK) { + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_CLEAN_USING_FBMAP]; + + switch (state) { + case SSDFS_SEG_CLEAN: + case SSDFS_SEG_DATA_USING: + case SSDFS_SEG_LEAF_NODE_USING: + case SSDFS_SEG_HYBRID_NODE_USING: + case SSDFS_SEG_INDEX_NODE_USING: + return fbmap; + + default: + return ERR_PTR(-EOPNOTSUPP); + } + } else if (mask & SSDFS_SEG_USED_DIRTY_MASK) { + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_USED_DIRTY_FBMAP]; + + switch (state) { + case SSDFS_SEG_USED: + case SSDFS_SEG_PRE_DIRTY: + case SSDFS_SEG_DIRTY: + return fbmap; + + default: + return ERR_PTR(-EOPNOTSUPP); + } + } else if (mask & SSDFS_SEG_BAD_STATE_MASK) { + fbmap = segbmap->fbmap[SSDFS_SEGBMAP_BAD_FBMAP]; + + switch (state) { + case SSDFS_SEG_BAD: + return fbmap; + + default: + return ERR_PTR(-EOPNOTSUPP); + } + } + + return ERR_PTR(-EOPNOTSUPP); +} + +/* + * ssdfs_segbmap_find_fragment() - find fragment + * @segbmap: pointer on segment bitmap object + * @fbmap: bitmap of fragments + * @start_fragment: start fragment for search + * @max_fragment: upper bound for fragment search + * @found_fragment: found fragment index [out] + * + * This method tries to find fragment in bitmap of + * fragments. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-EAGAIN - fragment is under initialization yet. + * %-EFAULT - segbmap has inconsistent state. + * %-ENODATA - bitmap hasn't any valid fragment. + */ +static +int ssdfs_segbmap_find_fragment(struct ssdfs_segment_bmap *segbmap, + unsigned long *fbmap, + u16 start_fragment, u16 max_fragment, + int *found_fragment) +{ + unsigned long *addr; + u16 long_offset; + u16 first_fragment; + u16 checking_fragment; + u16 size, requested_size, checked_size; + unsigned long found; + u16 i; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap || !fbmap || !found_fragment); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("fbmap %p, start_fragment %u, max_fragment %u\n", + fbmap, start_fragment, max_fragment); +#endif /* CONFIG_SSDFS_DEBUG */ + + *found_fragment = U16_MAX; + + if (start_fragment >= max_fragment) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("start_fragment %u >= max_fragment %u\n", + start_fragment, max_fragment); +#endif /* CONFIG_SSDFS_DEBUG */ + return -ENODATA; + } + + long_offset = (start_fragment + BITS_PER_LONG - 1) / BITS_PER_LONG; + first_fragment = long_offset * BITS_PER_LONG; + + checking_fragment = min_t(u16, start_fragment, first_fragment); + checked_size = max_fragment - checking_fragment; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("start_fragment %u, max_fragment %u, " + "long_offset %u, first_fragment %u, " + "checking_fragment %u, checked_size %u\n", + start_fragment, max_fragment, + long_offset, first_fragment, + checking_fragment, checked_size); +#endif /* CONFIG_SSDFS_DEBUG */ + + for (i = 0; i < checked_size; i++) { + struct ssdfs_segbmap_fragment_desc *desc; + u16 index = checking_fragment + i; + + desc = &segbmap->desc_array[index]; + + switch (desc->state) { + case SSDFS_SEGBMAP_FRAG_INITIALIZED: + case SSDFS_SEGBMAP_FRAG_DIRTY: + /* + * We can use this fragment. + * Simply go ahead. + */ + break; + + case SSDFS_SEGBMAP_FRAG_CREATED: + /* It needs to wait the fragment's init */ + err = -EAGAIN; + checked_size = index - checking_fragment; + goto check_presence_valid_fragments; + break; + + case SSDFS_SEGBMAP_FRAG_INIT_FAILED: + err = -EFAULT; + *found_fragment = index; + SSDFS_ERR("fragment %u is corrupted\n", + index); + checked_size = 0; + goto check_presence_valid_fragments; + break; + + default: + err = -ERANGE; + SSDFS_ERR("invalid fragment's state %#x\n", + desc->state); + goto check_presence_valid_fragments; + break; + } + } + +check_presence_valid_fragments: + if (err == -ERANGE || err == -EFAULT) { + /* Simply return the error */ + return err; + } else if (err == -EAGAIN) { + if (checked_size == 0) { + SSDFS_DBG("no valid fragments yet\n"); + return err; + } else + err = 0; + } + + if (start_fragment < first_fragment) { + unsigned long value = *(fbmap + (long_offset - 1)); + + size = start_fragment - ((long_offset - 1) * BITS_PER_LONG); + size = min_t(u16, size, checked_size); + bitmap_clear(&value, 0, size); + + if (value != 0) { + found = __ffs(value); + *found_fragment = start_fragment + (u16)(found - size); + return 0; + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find fragment: " + "value %#lx\n", + value); +#endif /* CONFIG_SSDFS_DEBUG */ + return -ENODATA; + } + } else { + /* first_fragment <= start_fragment */ + addr = fbmap + long_offset; + requested_size = max_fragment - first_fragment; + size = min_t(u16, requested_size, checked_size); + + if (size == 0) { + SSDFS_DBG("no valid fragments yet\n"); + return -EAGAIN; + } + + found = find_first_bit(addr, size); + + found += first_fragment; + BUG_ON(found >= U16_MAX); + *found_fragment = found; + + if (found >= size) { + if (size < requested_size) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("Wait init of fragment %lu\n", + found); +#endif /* CONFIG_SSDFS_DEBUG */ + return -EAGAIN; + } else { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find fragment: " + "found %lu, size %u\n", + found, size); +#endif /* CONFIG_SSDFS_DEBUG */ + return -ENODATA; + } + } + + return 0; + } + + return -ERANGE; +} + +/* + * ssdfs_segbmap_correct_search_start() - correct start item for search + * @fragment_index: index of fragment + * @old_start: old start value + * @max: upper bound for search + * @fragment_size: size of fragment in bytes + */ +static +u64 ssdfs_segbmap_correct_search_start(u16 fragment_index, + u64 old_start, u64 max, + u16 fragment_size) +{ + u64 first_item, corrected_value; + +#ifdef CONFIG_SSDFS_DEBUG + if (old_start >= max) { + SSDFS_ERR("old_start %llu >= max %llu\n", + old_start, max); + return U64_MAX; + } + + SSDFS_DBG("fragment_index %u, old_start %llu, max %llu\n", + fragment_index, old_start, max); +#endif /* CONFIG_SSDFS_DEBUG */ + + first_item = ssdfs_segbmap_define_first_fragment_item(fragment_index, + fragment_size); + + if (first_item >= max) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("first_item %llu >= max %llu\n", + first_item, max); +#endif /* CONFIG_SSDFS_DEBUG */ + return U64_MAX; + } + + corrected_value = first_item > old_start ? first_item : old_start; + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("corrected_value %llu\n", corrected_value); +#endif /* CONFIG_SSDFS_DEBUG */ + + return corrected_value; +} + +/* + * ssdfs_segbmap_define_items_count() - define items count for state/mask + * @desc: fragment descriptor + * @state: requested state + * @mask: requested mask + */ +static inline +u16 ssdfs_segbmap_define_items_count(struct ssdfs_segbmap_fragment_desc *desc, + int state, int mask) +{ + int complex_mask; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!desc); + BUG_ON(!mask); + + SSDFS_DBG("desc %p, state %#x, mask %#x\n", + desc, state, mask); +#endif /* CONFIG_SSDFS_DEBUG */ + + switch (state) { + case SSDFS_SEG_CLEAN: + complex_mask = SSDFS_SEG_CLEAN_STATE_FLAG | mask; + break; + + case SSDFS_SEG_DATA_USING: + complex_mask = SSDFS_SEG_DATA_USING_STATE_FLAG | mask; + break; + + case SSDFS_SEG_LEAF_NODE_USING: + complex_mask = SSDFS_SEG_LEAF_NODE_USING_STATE_FLAG | mask; + break; + + case SSDFS_SEG_HYBRID_NODE_USING: + complex_mask = SSDFS_SEG_HYBRID_NODE_USING_STATE_FLAG | mask; + break; + + case SSDFS_SEG_INDEX_NODE_USING: + complex_mask = SSDFS_SEG_INDEX_NODE_USING_STATE_FLAG | mask; + break; + + case SSDFS_SEG_USED: + complex_mask = SSDFS_SEG_USED_STATE_FLAG | mask; + break; + + case SSDFS_SEG_PRE_DIRTY: + complex_mask = SSDFS_SEG_PRE_DIRTY_STATE_FLAG | mask; + break; + + case SSDFS_SEG_DIRTY: + complex_mask = SSDFS_SEG_DIRTY_STATE_FLAG | mask; + break; + + case SSDFS_SEG_BAD: + complex_mask = SSDFS_SEG_BAD_STATE_FLAG | mask; + break; + + default: + BUG(); + } + + if ((complex_mask & SSDFS_SEG_CLEAN_USING_MASK) != complex_mask && + (complex_mask & SSDFS_SEG_USED_DIRTY_MASK) != complex_mask && + (complex_mask & SSDFS_SEG_BAD_STATE_MASK) != complex_mask) { + SSDFS_ERR("unsupported set of flags %#x\n", + complex_mask); + return U16_MAX; + } + + if (complex_mask & SSDFS_SEG_CLEAN_USING_MASK) + return desc->clean_or_using_segs; + else if (complex_mask & SSDFS_SEG_USED_DIRTY_MASK) + return desc->used_or_dirty_segs; + else if (complex_mask & SSDFS_SEG_BAD_STATE_MASK) + return desc->bad_segs; + + return U16_MAX; +} + +/* + * BYTE_CONTAINS_STATE() - check that byte contains requested state + * @value: pointer on byte + * @state: requested state + */ +static inline +bool BYTE_CONTAINS_STATE(u8 *value, int state) +{ + switch (state) { + case SSDFS_SEG_CLEAN: + return detect_clean_seg[*value]; + + case SSDFS_SEG_DATA_USING: + return detect_data_using_seg[*value]; + + case SSDFS_SEG_LEAF_NODE_USING: + return detect_lnode_using_seg[*value]; + + case SSDFS_SEG_HYBRID_NODE_USING: + return detect_hnode_using_seg[*value]; + + case SSDFS_SEG_INDEX_NODE_USING: + return detect_idxnode_using_seg[*value]; + + case SSDFS_SEG_USED: + return detect_used_seg[*value]; + + case SSDFS_SEG_PRE_DIRTY: + return detect_pre_dirty_seg[*value]; + + case SSDFS_SEG_DIRTY: + return detect_dirty_seg[*value]; + + case SSDFS_SEG_BAD: + return detect_bad_seg[*value]; + }; + + return false; +} + +/* + * BYTE_CONTAINS_MASK() - check that byte contains any state under mask + * @value: pointer on byte + * @mask: requested mask + */ +static inline +bool BYTE_CONTAINS_MASK(u8 *value, int mask) +{ + if (mask & SSDFS_SEG_CLEAN_USING_MASK) + return detect_clean_using_mask[*value]; + else if (mask & SSDFS_SEG_USED_DIRTY_MASK) + return detect_used_dirty_mask[*value]; + else if (mask & SSDFS_SEG_BAD_STATE_MASK) + return detect_bad_seg[*value]; + + return false; +} + +/* + * FIRST_MASK_IN_BYTE() - determine first item's offset for requested mask + * @value: pointer on analysed byte + * @mask: requested mask + * @start_offset: starting item's offset for analysis beginning + * @state_bits: bits per state + * @state_mask: mask of a bitmap's state + * + * This function tries to determine an item for @mask in + * @value starting from @start_off. + * + * RETURN: + * [success] - found item's offset. + * [failure] - BITS_PER_BYTE. + */ +static inline +u8 FIRST_MASK_IN_BYTE(u8 *value, int mask, + u8 start_offset, u8 state_bits, + int state_mask) +{ + u8 i; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!value); + BUG_ON(state_bits > BITS_PER_BYTE); + BUG_ON((state_bits % 2) != 0); + BUG_ON(start_offset > SSDFS_ITEMS_PER_BYTE(state_bits)); + + SSDFS_DBG("value %#x, mask %#x, " + "start_offset %u, state_bits %u\n", + *value, mask, start_offset, state_bits); +#endif /* CONFIG_SSDFS_DEBUG */ + + i = start_offset * state_bits; + for (; i < BITS_PER_BYTE; i += state_bits) { + if (IS_STATE_GOOD_FOR_MASK(mask, (*value >> i) & state_mask)) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("found bit %u, found item %u\n", + i, i / state_bits); +#endif /* CONFIG_SSDFS_DEBUG */ + return i / state_bits; + } + } + + return SSDFS_ITEMS_PER_BYTE(state_bits); +} + +/* + * FIND_FIRST_ITEM_IN_FRAGMENT() - find first item in fragment + * @hdr: pointer on segbmap fragment's header + * @fragment: pointer on bitmap in fragment + * @start_item: start segment number for search + * @max_item: upper bound of segment number for search + * @state: primary state for search + * @mask: mask of additonal states that can be retrieved too + * @found_seg: found segment number [out] + * @found_for_mask: found segment number for mask [out] + * @found_state_for_mask: found state for mask [out] + * + * This method tries to find first item with requested + * state in fragment. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-ERANGE - internal error. + * %-ENOENT - found segment number for the mask. + * %-ENODATA - fragment doesn't include segment with requested state/mask. + */ +static +int FIND_FIRST_ITEM_IN_FRAGMENT(struct ssdfs_segbmap_fragment_header *hdr, + u8 *fragment, u64 start_item, u64 max_item, + int state, int mask, + u64 *found_seg, u64 *found_for_mask, + int *found_state_for_mask) +{ + u32 items_per_byte = SSDFS_ITEMS_PER_BYTE(SSDFS_SEG_STATE_BITS); + u64 fragment_start_item; + u64 aligned_start, aligned_end; + u32 byte_index, search_bytes; + u64 byte_range; + u8 start_offset; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!hdr || !fragment || !found_seg || !found_for_mask); + + if (start_item >= max_item) { + SSDFS_ERR("start_item %llu >= max_item %llu\n", + start_item, max_item); + return -EINVAL; + } + + SSDFS_DBG("hdr %p, fragment %p, " + "start_item %llu, max_item %llu, " + "state %#x, mask %#x, " + "found_seg %p, found_for_mask %p\n", + hdr, fragment, start_item, max_item, + state, mask, found_seg, found_for_mask); +#endif /* CONFIG_SSDFS_DEBUG */ + + *found_seg = U64_MAX; + *found_for_mask = U64_MAX; + *found_state_for_mask = SSDFS_SEG_STATE_MAX; + + fragment_start_item = le64_to_cpu(hdr->start_item); + + if (fragment_start_item == U64_MAX) { + SSDFS_ERR("invalid fragment start item\n"); + return -ERANGE; + } + + search_bytes = le16_to_cpu(hdr->fragment_bytes) - + sizeof(struct ssdfs_segbmap_fragment_header); + + if (search_bytes == 0 || search_bytes > PAGE_SIZE) { + SSDFS_ERR("invalid fragment_bytes %u\n", + search_bytes); + return -ERANGE; + } + + aligned_start = ALIGNED_START_ITEM(start_item, SSDFS_SEG_STATE_BITS); + aligned_end = ALIGNED_END_ITEM(max_item, SSDFS_SEG_STATE_BITS); + + byte_range = (aligned_end - fragment_start_item) / items_per_byte; +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(byte_range >= U32_MAX); +#endif /* CONFIG_SSDFS_DEBUG */ + + search_bytes = min_t(u32, search_bytes, (u32)byte_range); + + if (fragment_start_item <= aligned_start) { + u32 items_range = aligned_start - fragment_start_item; + byte_index = items_range / items_per_byte; + start_offset = (u8)(start_item - aligned_start); + } else { + byte_index = 0; + start_offset = 0; + } + + for (; byte_index < search_bytes; byte_index++) { + u8 *value = fragment + byte_index; + u8 found_offset; + + err = FIND_FIRST_ITEM_IN_BYTE(value, state, + SSDFS_SEG_STATE_BITS, + SSDFS_SEG_STATE_MASK, + start_offset, + BYTE_CONTAINS_STATE, + FIRST_STATE_IN_BYTE, + &found_offset); + + if (err != -ENODATA || *found_for_mask != U64_MAX) + goto ignore_search_for_mask; + + err = FIND_FIRST_ITEM_IN_BYTE(value, mask, + SSDFS_SEG_STATE_BITS, + SSDFS_SEG_STATE_MASK, + start_offset, + BYTE_CONTAINS_MASK, + FIRST_MASK_IN_BYTE, + &found_offset); + + if (!err && found_offset != U64_MAX) { + err = -ENOENT; + + *found_for_mask = fragment_start_item; + *found_for_mask += byte_index * items_per_byte; + *found_for_mask += found_offset; + + if (*found_for_mask >= max_item) { + *found_for_mask = U64_MAX; + goto ignore_search_for_mask; + } + + *found_state_for_mask = + ssdfs_segbmap_get_state_from_byte(value, + found_offset); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("found_for_mask %llu, " + "found_state_for_mask %#x\n", + *found_for_mask, + *found_state_for_mask); +#endif /* CONFIG_SSDFS_DEBUG */ + + if (IS_STATE_GOOD_FOR_MASK(mask, *found_state_for_mask)) + break; + else { + err = -ENODATA; + *found_for_mask = U64_MAX; + *found_state_for_mask = SSDFS_SEG_STATE_MAX; + } + } + +ignore_search_for_mask: + if (err == -ENODATA) { + start_offset = 0; + continue; + } else if (err == -ENOENT) { + /* + * Value for mask has been found. + * Simply end the search. + */ + break; + } else if (unlikely(err)) { + SSDFS_ERR("fail to find items in byte: " + "byte_index %u, state %#x, " + "err %d\n", + byte_index, state, err); + goto end_search; + } + + *found_seg = fragment_start_item; + *found_seg += byte_index * items_per_byte; + *found_seg += found_offset; + + if (*found_seg >= max_item) + *found_seg = U64_MAX; + + break; + } + + if (*found_seg == U64_MAX && *found_for_mask == U64_MAX) + err = -ENODATA; + else if (*found_seg == U64_MAX && *found_for_mask != U64_MAX) + err = -ENOENT; + +#ifdef CONFIG_SSDFS_DEBUG + if (!err || err == -ENOENT) { + SSDFS_DBG("found_seg %llu, found_for_mask %llu\n", + *found_seg, *found_for_mask); + } else + SSDFS_DBG("nothing was found: err %d\n", err); +#endif /* CONFIG_SSDFS_DEBUG */ + +end_search: + return err; +} + +/* + * ssdfs_segbmap_find_in_fragment() - find segment with state in fragment + * @segbmap: pointer on segment bitmap object + * @fragment_index: index of fragment + * @fragment_size: size of fragment in bytes + * @start: start segment number for search + * @max: upper bound of segment number for search + * @state: primary state for search + * @mask: mask of additonal states that can be retrieved too + * @found_seg: found segment number [out] + * @found_for_mask: found segment number for mask [out] + * @found_state_for_mask: found state for mask [out] + * + * This method tries to find segment number for requested state + * in fragment. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-ERANGE - internal error. + * %-EAGAIN - fragment is under initialization yet. + * %-EFAULT - fragment has inconsistent state. + */ +static +int ssdfs_segbmap_find_in_fragment(struct ssdfs_segment_bmap *segbmap, + u16 fragment_index, + u16 fragment_size, + u64 start, u64 max, + int state, int mask, + u64 *found_seg, u64 *found_for_mask, + int *found_state_for_mask) +{ + struct ssdfs_segbmap_fragment_desc *fragment; + size_t hdr_size = sizeof(struct ssdfs_segbmap_fragment_header); + struct page *page; + u64 first_item; + u32 items_per_fragment; + u16 items_count; + void *kaddr; + unsigned long *bmap; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap || !found_seg || !found_for_mask); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + if (start >= max) { + SSDFS_ERR("start %llu >= max %llu\n", + start, max); + return -EINVAL; + } + + SSDFS_DBG("segbmap %p, fragment_index %u, " + "fragment_size %u, start %llu, max %llu, " + "found_seg %p, found_for_mask %p\n", + segbmap, fragment_index, fragment_size, + start, max, + found_seg, found_for_mask); +#endif /* CONFIG_SSDFS_DEBUG */ + + *found_seg = U64_MAX; + *found_for_mask = U64_MAX; + + first_item = ssdfs_segbmap_define_first_fragment_item(fragment_index, + fragment_size); + items_per_fragment = ssdfs_segbmap_items_per_fragment(fragment_size); + + if (first_item >= max) { + SSDFS_ERR("first_item %llu >= max %llu\n", + first_item, max); + return -ERANGE; + } else if ((first_item + items_per_fragment) <= start) { + SSDFS_ERR("first_item %llu, items_per_fragment %u, " + "start %llu\n", + first_item, items_per_fragment, start); + return -ERANGE; + } + + err = ssdfs_segbmap_check_fragment_validity(segbmap, fragment_index); + if (err == -EAGAIN) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %u is not initilaized yet\n", + fragment_index); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; + } else if (err == -EFAULT) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %u initialization was failed\n", + fragment_index); +#endif /* CONFIG_SSDFS_DEBUG */ + return err; + } else if (unlikely(err)) { + SSDFS_ERR("fragment %u is corrupted: err %d\n", + fragment_index, err); + return err; + } + + fragment = &segbmap->desc_array[fragment_index]; + + items_count = ssdfs_segbmap_define_items_count(fragment, state, mask); + if (items_count == U16_MAX) { + SSDFS_ERR("segbmap has inconsistent state\n"); + return -ERANGE; + } else if (items_count == 0) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %u hasn't items for search\n", + fragment_index); +#endif /* CONFIG_SSDFS_DEBUG */ + return -ENODATA; + } + + items_count = fragment->total_segs; + + if (items_count == 0 || items_count > items_per_fragment) { + SSDFS_ERR("invalid total_segs %u\n", items_count); + return -ERANGE; + } + + page = find_lock_page(&segbmap->pages, fragment_index); + if (!page) { + SSDFS_ERR("fragment %u hasn't memory page\n", + fragment_index); + return -ERANGE; + } + + ssdfs_account_locked_page(page); + kaddr = kmap_local_page(page); + bmap = (unsigned long *)((u8 *)kaddr + hdr_size); + + err = FIND_FIRST_ITEM_IN_FRAGMENT(SSDFS_SBMP_FRAG_HDR(kaddr), + (u8 *)bmap, start, max, state, mask, + found_seg, found_for_mask, + found_state_for_mask); + + kunmap_local(kaddr); + ssdfs_unlock_page(page); + ssdfs_put_page(page); + +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("page %p, count %d\n", + page, page_ref_count(page)); +#endif /* CONFIG_SSDFS_DEBUG */ + + return err; +} + +/* + * __ssdfs_segbmap_find() - find segment with state + * @segbmap: pointer on segment bitmap object + * @start: start segment number for search + * @max: upper bound of segment number for search + * @state: primary state for search + * @mask: mask of additonal states that can be retrieved too + * @fragment_size: fragment size in bytes + * @seg: found segment number [out] + * @end: pointer on completion for waiting init ending [out] + * + * This method tries to find segment number for requested state. + * + * RETURN: + * [success] - found segment state + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-EAGAIN - fragment is under initialization yet. + * %-EOPNOTSUPP - operation is not supported. + * %-ENOMEM - fail to allocate memory. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + * %-ENODATA - unable to find segment as for state as for mask. + */ +static +int __ssdfs_segbmap_find(struct ssdfs_segment_bmap *segbmap, + u64 start, u64 max, + int state, int mask, + u16 fragment_size, + u64 *seg, struct completion **end) +{ + unsigned long *fbmap; + int start_fragment, max_fragment, found_fragment; + u64 found = U64_MAX, found_for_mask = U64_MAX; + int found_state_for_mask = SSDFS_SEG_STATE_MAX; + int err = -ENODATA; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap || !seg); + BUG_ON(!rwsem_is_locked(&segbmap->search_lock)); + + SSDFS_DBG("segbmap %p, start %llu, max %llu, " + "state %#x, mask %#x, fragment_size %u, seg %p\n", + segbmap, start, max, state, mask, + fragment_size, seg); +#endif /* CONFIG_SSDFS_DEBUG */ + + *end = NULL; + + if (start >= max) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("start %llu >= max %llu\n", + start, max); +#endif /* CONFIG_SSDFS_DEBUG */ + return -ENODATA; + } + + fbmap = ssdfs_segbmap_choose_fbmap(segbmap, state, mask); + if (IS_ERR_OR_NULL(fbmap)) { + err = (fbmap == NULL ? -ENOMEM : PTR_ERR(fbmap)); + SSDFS_ERR("unable to choose fragment bitmap: err %d\n", + err); + return err; + } + + start_fragment = SEG_BMAP_FRAGMENTS(start + 1); + if (start_fragment > 0) + start_fragment -= 1; + + max_fragment = SEG_BMAP_FRAGMENTS(max); + + do { + u64 found_for_iter = U64_MAX; + int found_state_for_iter = -1; + + err = ssdfs_segbmap_find_fragment(segbmap, + fbmap, + start_fragment, + max_fragment, + &found_fragment); + if (err == -ENODATA) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find fragment: " + "state %#x, mask %#x, " + "start_fragment %d, max_fragment %d\n", + state, mask, + start_fragment, max_fragment); +#endif /* CONFIG_SSDFS_DEBUG */ + goto finish_seg_search; + } else if (err == -EFAULT) { + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "segbmap inconsistent state: " + "found_fragment %d\n", + found_fragment); + goto finish_seg_search; + } else if (err == -EAGAIN) { + if (found_fragment >= U16_MAX) { + /* select the first fragment by default */ + found_fragment = 0; + } + + *end = &segbmap->desc_array[found_fragment].init_end; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %u is not initilaized yet\n", + found_fragment); +#endif /* CONFIG_SSDFS_DEBUG */ + goto finish_seg_search; + } else if (unlikely(err)) { + SSDFS_ERR("fail to find fragment: " + "start_fragment %d, max_fragment %d, " + "err %d\n", + start_fragment, max_fragment, err); + goto finish_seg_search; + } else if (found_fragment >= U16_MAX) { + err = -ERANGE; + SSDFS_ERR("fail to find fragment: " + "start_fragment %d, max_fragment %d, " + "err %d\n", + start_fragment, max_fragment, err); + goto finish_seg_search; + } + + start = ssdfs_segbmap_correct_search_start(found_fragment, + start, max, + fragment_size); + if (start == U64_MAX || start >= max) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("break search: start %llu, max %llu\n", + start, max); +#endif /* CONFIG_SSDFS_DEBUG */ + break; + } + + *end = &segbmap->desc_array[found_fragment].init_end; + + err = ssdfs_segbmap_find_in_fragment(segbmap, found_fragment, + fragment_size, + start, max, + state, mask, + &found, &found_for_iter, + &found_state_for_iter); + if (err == -ENODATA) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("unable to find segment: " + "fragment %d, " + "state %#x, mask %#x, " + "start %llu, max %llu\n", + found_fragment, + state, mask, + start, max); +#endif /* CONFIG_SSDFS_DEBUG */ + /* try next fragment */ + } else if (err == -ENOENT) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("mask %#x, found_for_mask %llu, " + "found_for_iter %llu, " + "found_state %#x\n", + mask, found_for_mask, found_for_iter, + found_state_for_iter); +#endif /* CONFIG_SSDFS_DEBUG */ + err = 0; + found_for_mask = found_for_iter; + found_state_for_mask = found_state_for_iter; + goto check_search_result; + } else if (err == -EFAULT) { + /* Just try another iteration */ +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %d is inconsistent\n", + found_fragment); +#endif /* CONFIG_SSDFS_DEBUG */ + } else if (err == -EAGAIN) { +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("fragment %u is not initilaized yet\n", + found_fragment); +#endif /* CONFIG_SSDFS_DEBUG */ + goto finish_seg_search; + } else if (unlikely(err < 0)) { + SSDFS_ERR("fail to find segment: " + "found_fragment %d, start %llu, " + "max %llu, err %d\n", + found_fragment, start, max, err); + goto finish_seg_search; + } else if (found == U64_MAX) { + err = -ERANGE; + SSDFS_ERR("invalid segment number: " + "found_fragment %d, start %llu, " + "max %llu\n", + found_fragment, start, max); + goto finish_seg_search; + } else + break; + + start_fragment = found_fragment + 1; + } while (start_fragment <= max_fragment); + +check_search_result: + if (unlikely(err < 0)) { + /* we have some error */ + goto finish_seg_search; + } else if (found == U64_MAX) { + if (found_for_mask == U64_MAX) { + err = -ENODATA; + SSDFS_DBG("fail to find segment\n"); + } else { + *seg = found_for_mask; + err = found_state_for_mask; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("found for mask %llu, state %#x\n", + *seg, err); +#endif /* CONFIG_SSDFS_DEBUG */ + } + } else { + *seg = found; + err = state; +#ifdef CONFIG_SSDFS_DEBUG + SSDFS_DBG("found segment %llu\n", *seg); +#endif /* CONFIG_SSDFS_DEBUG */ + } + +finish_seg_search: + return err; +} + +/* + * ssdfs_segbmap_find() - find segment with state + * @segbmap: pointer on segment bitmap object + * @start: start segment number for search + * @max: upper bound of segment number for search + * @state: primary state for search + * @mask: mask of additonal states that can be retrieved too + * @seg: found segment number [out] + * @end: pointer on completion for waiting init ending [out] + * + * This method tries to find segment number for requested state. + * + * RETURN: + * [success] - found segment state + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-EAGAIN - fragment is under initialization yet. + * %-EOPNOTSUPP - operation is not supported. + * %-ENOMEM - fail to allocate memory. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + * %-ENODATA - unable to find segment as for state as for mask. + */ +int ssdfs_segbmap_find(struct ssdfs_segment_bmap *segbmap, + u64 start, u64 max, + int state, int mask, + u64 *seg, struct completion **end) +{ + u64 items_count; + u16 fragment_size; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap || !seg); + + if (start >= segbmap->items_count) { + SSDFS_ERR("start %llu >= items_count %llu\n", + start, segbmap->items_count); + return -EINVAL; + } + + if (start >= max) { + SSDFS_ERR("start %llu >= max %llu\n", + start, max); + return -EINVAL; + } + + if (state < SSDFS_SEG_CLEAN || state >= SSDFS_SEG_STATE_MAX) { + SSDFS_ERR("unknown segment state %#x\n", state); + return -EINVAL; + } + + if ((mask & SSDFS_SEG_CLEAN_USING_MASK) != mask && + (mask & SSDFS_SEG_USED_DIRTY_MASK) != mask && + (mask & SSDFS_SEG_BAD_STATE_MASK) != mask) { + SSDFS_ERR("unsupported set of flags %#x\n", + mask); + return -EOPNOTSUPP; + } + + SSDFS_DBG("segbmap %p, start %llu, max %llu, " + "state %#x, mask %#x, seg %p\n", + segbmap, start, max, state, mask, seg); +#endif /* CONFIG_SSDFS_DEBUG */ + + *end = NULL; + + inode_lock_shared(segbmap->fsi->segbmap_inode); + down_read(&segbmap->resize_lock); + + items_count = segbmap->items_count; + fragment_size = segbmap->fragment_size; + + if (segbmap->flags & SSDFS_SEGBMAP_ERROR) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "segbmap has corrupted state\n"); + goto finish_search_preparation; + } + + max = min_t(u64, max, items_count); + + down_read(&segbmap->search_lock); + err = __ssdfs_segbmap_find(segbmap, start, max, state, mask, + fragment_size, seg, end); + up_read(&segbmap->search_lock); + +finish_search_preparation: + up_read(&segbmap->resize_lock); + inode_unlock_shared(segbmap->fsi->segbmap_inode); + + return err; +} + +/* + * ssdfs_segbmap_find_and_set() - find segment and change state + * @segbmap: pointer on segment bitmap object + * @start: start segment number for search + * @max: upper bound of segment number for search + * @state: primary state for search + * @mask: mask of additonal states that can be retrieved too + * @new_state: new state of segment + * @seg: found segment number [out] + * @end: pointer on completion for waiting init ending [out] + * + * This method tries to find segment number for requested state + * and to set segment state as @new_state. + * + * RETURN: + * [success] - found segment state before changing + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-EAGAIN - fragment is under initialization yet. + * %-EOPNOTSUPP - operation is not supported. + * %-ENOMEM - fail to allocate memory. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + * %-ENODATA - unable to find segment as for state as for mask. + */ +int ssdfs_segbmap_find_and_set(struct ssdfs_segment_bmap *segbmap, + u64 start, u64 max, + int state, int mask, + int new_state, + u64 *seg, struct completion **end) +{ + u64 items_count; + u16 fragments_count; + u16 fragment_size; + pgoff_t fragment_index; + int err = 0, res = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap || !seg); + + if (start >= segbmap->items_count) { + SSDFS_ERR("start %llu >= items_count %llu\n", + start, segbmap->items_count); + return -EINVAL; + } + + if (start >= max) { + SSDFS_ERR("start %llu >= max %llu\n", + start, max); + return -EINVAL; + } + + if (state < SSDFS_SEG_CLEAN || state >= SSDFS_SEG_STATE_MAX) { + SSDFS_ERR("unknown segment state %#x\n", state); + return -EINVAL; + } + + if ((mask & SSDFS_SEG_CLEAN_USING_MASK) != mask && + (mask & SSDFS_SEG_USED_DIRTY_MASK) != mask && + (mask & SSDFS_SEG_BAD_STATE_MASK) != mask) { + SSDFS_ERR("unsupported set of flags %#x\n", + mask); + return -EOPNOTSUPP; + } + + if (new_state < SSDFS_SEG_CLEAN || new_state >= SSDFS_SEG_STATE_MAX) { + SSDFS_ERR("unknown new segment state %#x\n", new_state); + return -EINVAL; + } +#endif /* CONFIG_SSDFS_DEBUG */ + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("segbmap %p, start %llu, max %llu, " + "state %#x, mask %#x, new_state %#x, seg %p\n", + segbmap, start, max, state, mask, new_state, seg); +#else + SSDFS_DBG("segbmap %p, start %llu, max %llu, " + "state %#x, mask %#x, new_state %#x, seg %p\n", + segbmap, start, max, state, mask, new_state, seg); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + *end = NULL; + + inode_lock_shared(segbmap->fsi->segbmap_inode); + down_read(&segbmap->resize_lock); + + items_count = segbmap->items_count; + fragments_count = segbmap->fragments_count; + fragment_size = segbmap->fragment_size; + + if (segbmap->flags & SSDFS_SEGBMAP_ERROR) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "segbmap has corrupted state\n"); + goto finish_search_preparation; + } + + max = min_t(u64, max, items_count); + + down_write(&segbmap->search_lock); + +try_to_find_seg_id: + res = __ssdfs_segbmap_find(segbmap, start, max, + state, mask, + fragment_size, seg, end); + if (res == -ENODATA) { + err = res; + SSDFS_DBG("unable to find any segment\n"); + goto finish_find_set; + } else if (res == -EAGAIN) { + err = res; + SSDFS_DBG("fragment is not initilaized yet\n"); + goto finish_find_set; + } else if (unlikely(res < 0)) { + err = res; + SSDFS_ERR("fail to find clean segment: err %d\n", + err); + goto finish_find_set; + } + + if (res == new_state) { + /* everything is done */ + goto finish_find_set; + } else if (res == SSDFS_SEG_CLEAN) { + /* + * we can change clean state on any other + */ + } else { + start = *seg + 1; + *seg = U64_MAX; + goto try_to_find_seg_id; + } + + if (*seg >= items_count) { + err = -ERANGE; + SSDFS_ERR("seg %llu >= items_count %llu\n", + *seg, items_count); + goto finish_find_set; + } + + fragment_index = ssdfs_segbmap_seg_2_fragment_index(*seg); + if (fragment_index >= fragments_count) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fragment_index %lu >= fragments_count %u\n", + fragment_index, fragments_count); + goto finish_find_set; + } + + err = __ssdfs_segbmap_change_state(segbmap, *seg, + new_state, + fragment_index, + fragment_size); + if (unlikely(err)) { + SSDFS_ERR("fail to reserve segment: err %d\n", + err); + goto finish_find_set; + } + +finish_find_set: + up_write(&segbmap->search_lock); + +finish_search_preparation: + up_read(&segbmap->resize_lock); + inode_unlock_shared(segbmap->fsi->segbmap_inode); + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("finished\n"); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + if (unlikely(err)) + return err; + + return res; +} + +/* + * ssdfs_segbmap_reserve_clean_segment() - reserve clean segment + * @segbmap: pointer on segment bitmap object + * @start: start segment number for search + * @max: upper bound of segment number for search + * @seg: found segment number [out] + * @end: pointer on completion for waiting init ending [out] + * + * This method tries to find clean segment and to reserve it. + * + * RETURN: + * [success] + * [failure] - error code: + * + * %-EINVAL - invalid input. + * %-EAGAIN - fragment is under initialization yet. + * %-EOPNOTSUPP - operation is not supported. + * %-ENOMEM - fail to allocate memory. + * %-EFAULT - segbmap has inconsistent state. + * %-ERANGE - internal error. + * %-ENODATA - unable to find segment. + */ +int ssdfs_segbmap_reserve_clean_segment(struct ssdfs_segment_bmap *segbmap, + u64 start, u64 max, + u64 *seg, struct completion **end) +{ + u64 items_count; + u16 fragments_count; + u16 fragment_size; + pgoff_t fragment_index; + int err = 0; + +#ifdef CONFIG_SSDFS_DEBUG + BUG_ON(!segbmap || !seg); + + if (start >= segbmap->items_count) { + SSDFS_ERR("start %llu >= items_count %llu\n", + start, segbmap->items_count); + return -EINVAL; + } + + if (start >= max) { + SSDFS_ERR("start %llu >= max %llu\n", + start, max); + return -EINVAL; + } +#endif /* CONFIG_SSDFS_DEBUG */ + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("segbmap %p, start %llu, max %llu, " + "seg %p\n", + segbmap, start, max, seg); +#else + SSDFS_DBG("segbmap %p, start %llu, max %llu, " + "seg %p\n", + segbmap, start, max, seg); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + *end = NULL; + + inode_lock_shared(segbmap->fsi->segbmap_inode); + down_read(&segbmap->resize_lock); + + items_count = segbmap->items_count; + fragments_count = segbmap->fragments_count; + fragment_size = segbmap->fragment_size; + + if (segbmap->flags & SSDFS_SEGBMAP_ERROR) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "segbmap has corrupted state\n"); + goto finish_segment_check; + } + + down_write(&segbmap->search_lock); + + err = __ssdfs_segbmap_find(segbmap, start, max, + SSDFS_SEG_CLEAN, + SSDFS_SEG_CLEAN_STATE_FLAG, + fragment_size, seg, end); + if (err == -ENODATA) { + SSDFS_DBG("unable to find clean segment\n"); + goto finish_reserve_segment; + } else if (err == -EAGAIN) { + SSDFS_DBG("fragment is not initilaized yet\n"); + goto finish_reserve_segment; + } else if (unlikely(err < 0)) { + SSDFS_ERR("fail to find clean segment: err %d\n", + err); + goto finish_reserve_segment; + } + + if (*seg >= items_count) { + err = -ERANGE; + SSDFS_ERR("seg %llu >= items_count %llu\n", + *seg, items_count); + goto finish_reserve_segment; + } + + fragment_index = ssdfs_segbmap_seg_2_fragment_index(*seg); + if (fragment_index >= fragments_count) { + err = -EFAULT; + ssdfs_fs_error(segbmap->fsi->sb, + __FILE__, __func__, __LINE__, + "fragment_index %lu >= fragments_count %u\n", + fragment_index, fragments_count); + goto finish_reserve_segment; + } + + err = __ssdfs_segbmap_change_state(segbmap, *seg, + SSDFS_SEG_RESERVED, + fragment_index, + fragment_size); + if (unlikely(err)) { + SSDFS_ERR("fail to reserve segment: err %d\n", + err); + goto finish_reserve_segment; + } + +finish_reserve_segment: + up_write(&segbmap->search_lock); + +finish_segment_check: + up_read(&segbmap->resize_lock); + inode_unlock_shared(segbmap->fsi->segbmap_inode); + +#ifdef CONFIG_SSDFS_TRACK_API_CALL + SSDFS_ERR("finished: seg %llu, err %d\n", *seg, err); +#else + SSDFS_DBG("finished: seg %llu, err %d\n", *seg, err); +#endif /* CONFIG_SSDFS_TRACK_API_CALL */ + + return err; +} -- 2.34.1