Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  segment.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * fs/f2fs/segment.c
 *
 * Copyright (c) 2012 Samsung Electronics Co., Ltd.
 *             http://www.samsung.com/
 */

#include <linux/fs.h>
#include <linux/f2fs_fs.h>
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <linux/sched/mm.h>
#include <linux/prefetch.h>
#include <linux/kthread.h>
#include <linux/swap.h>
#include <linux/timer.h>
#include <linux/freezer.h>
#include <linux/sched/signal.h>
#include <linux/random.h>

#include "f2fs.h"
#include "segment.h"
#include "node.h"
#include "gc.h"
#include "iostat.h"
#include <trace/events/f2fs.h>

#define __reverse_ffz(x) __reverse_ffs(~(x))

static struct kmem_cache *discard_entry_slab;
static struct kmem_cache *discard_cmd_slab;
static struct kmem_cache *sit_entry_set_slab;
static struct kmem_cache *revoke_entry_slab;

static unsigned long __reverse_ulong(unsigned char *str)
{
 unsigned long tmp = 0;
 int shift = 24, idx = 0;

#if BITS_PER_LONG == 64
 shift = 56;
#endif
 while (shift >= 0) {
  tmp |= (unsigned long)str[idx++] << shift;
  shift -= BITS_PER_BYTE;
 }
 return tmp;
}

/*
 * __reverse_ffs is copied from include/asm-generic/bitops/__ffs.h since
 * MSB and LSB are reversed in a byte by f2fs_set_bit.
 */

static inline unsigned long __reverse_ffs(unsigned long word)
{
 int num = 0;

#if BITS_PER_LONG == 64
 if ((word & 0xffffffff00000000UL) == 0)
  num += 32;
 else
  word >>= 32;
#endif
 if ((word & 0xffff0000) == 0)
  num += 16;
 else
  word >>= 16;

 if ((word & 0xff00) == 0)
  num += 8;
 else
  word >>= 8;

 if ((word & 0xf0) == 0)
  num += 4;
 else
  word >>= 4;

 if ((word & 0xc) == 0)
  num += 2;
 else
  word >>= 2;

 if ((word & 0x2) == 0)
  num += 1;
 return num;
}

/*
 * __find_rev_next(_zero)_bit is copied from lib/find_next_bit.c because
 * f2fs_set_bit makes MSB and LSB reversed in a byte.
 * @size must be integral times of unsigned long.
 * Example:
 *                             MSB <--> LSB
 *   f2fs_set_bit(0, bitmap) => 1000 0000
 *   f2fs_set_bit(7, bitmap) => 0000 0001
 */

static unsigned long __find_rev_next_bit(const unsigned long *addr,
   unsigned long size, unsigned long offset)
{
 const unsigned long *p = addr + BIT_WORD(offset);
 unsigned long result = size;
 unsigned long tmp;

 if (offset >= size)
  return size;

 size -= (offset & ~(BITS_PER_LONG - 1));
 offset %= BITS_PER_LONG;

 while (1) {
  if (*p == 0)
   goto pass;

  tmp = __reverse_ulong((unsigned char *)p);

  tmp &= ~0UL >> offset;
  if (size < BITS_PER_LONG)
   tmp &= (~0UL << (BITS_PER_LONG - size));
  if (tmp)
   goto found;
pass:
  if (size <= BITS_PER_LONG)
   break;
  size -= BITS_PER_LONG;
  offset = 0;
  p++;
 }
 return result;
found:
 return result - size + __reverse_ffs(tmp);
}

static unsigned long __find_rev_next_zero_bit(const unsigned long *addr,
   unsigned long size, unsigned long offset)
{
 const unsigned long *p = addr + BIT_WORD(offset);
 unsigned long result = size;
 unsigned long tmp;

 if (offset >= size)
  return size;

 size -= (offset & ~(BITS_PER_LONG - 1));
 offset %= BITS_PER_LONG;

 while (1) {
  if (*p == ~0UL)
   goto pass;

  tmp = __reverse_ulong((unsigned char *)p);

  if (offset)
   tmp |= ~0UL << (BITS_PER_LONG - offset);
  if (size < BITS_PER_LONG)
   tmp |= ~0UL >> size;
  if (tmp != ~0UL)
   goto found;
pass:
  if (size <= BITS_PER_LONG)
   break;
  size -= BITS_PER_LONG;
  offset = 0;
  p++;
 }
 return result;
found:
 return result - size + __reverse_ffz(tmp);
}

bool f2fs_need_SSR(struct f2fs_sb_info *sbi)
{
 int node_secs = get_blocktype_secs(sbi, F2FS_DIRTY_NODES);
 int dent_secs = get_blocktype_secs(sbi, F2FS_DIRTY_DENTS);
 int imeta_secs = get_blocktype_secs(sbi, F2FS_DIRTY_IMETA);

 if (f2fs_lfs_mode(sbi))
  return false;
 if (sbi->gc_mode == GC_URGENT_HIGH)
  return true;
 if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED)))
  return true;

 return free_sections(sbi) <= (node_secs + 2 * dent_secs + imeta_secs +
   SM_I(sbi)->min_ssr_sections + reserved_sections(sbi));
}

void f2fs_abort_atomic_write(struct inode *inode, bool clean)
{
 struct f2fs_inode_info *fi = F2FS_I(inode);

 if (!f2fs_is_atomic_file(inode))
  return;

 if (clean)
  truncate_inode_pages_final(inode->i_mapping);

 release_atomic_write_cnt(inode);
 clear_inode_flag(inode, FI_ATOMIC_COMMITTED);
 clear_inode_flag(inode, FI_ATOMIC_REPLACE);
 clear_inode_flag(inode, FI_ATOMIC_FILE);
 if (is_inode_flag_set(inode, FI_ATOMIC_DIRTIED)) {
  clear_inode_flag(inode, FI_ATOMIC_DIRTIED);
  /*
 * The vfs inode keeps clean during commit, but the f2fs inode
 * doesn't. So clear the dirty state after commit and let
 * f2fs_mark_inode_dirty_sync ensure a consistent dirty state.
 */

  f2fs_inode_synced(inode);
  f2fs_mark_inode_dirty_sync(inode, true);
 }
 stat_dec_atomic_inode(inode);

 F2FS_I(inode)->atomic_write_task = NULL;

 if (clean) {
  f2fs_i_size_write(inode, fi->original_i_size);
  fi->original_i_size = 0;
 }
 /* avoid stale dirty inode during eviction */
 sync_inode_metadata(inode, 0);
}

static int __replace_atomic_write_block(struct inode *inode, pgoff_t index,
   block_t new_addr, block_t *old_addr, bool recover)
{
 struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 struct dnode_of_data dn;
 struct node_info ni;
 int err;

retry:
 set_new_dnode(&dn, inode, NULL, NULL, 0);
 err = f2fs_get_dnode_of_data(&dn, index, ALLOC_NODE);
 if (err) {
  if (err == -ENOMEM) {
   f2fs_io_schedule_timeout(DEFAULT_IO_TIMEOUT);
   goto retry;
  }
  return err;
 }

 err = f2fs_get_node_info(sbi, dn.nid, &ni, false);
 if (err) {
  f2fs_put_dnode(&dn);
  return err;
 }

 if (recover) {
  /* dn.data_blkaddr is always valid */
  if (!__is_valid_data_blkaddr(new_addr)) {
   if (new_addr == NULL_ADDR)
    dec_valid_block_count(sbi, inode, 1);
   f2fs_invalidate_blocks(sbi, dn.data_blkaddr, 1);
   f2fs_update_data_blkaddr(&dn, new_addr);
  } else {
   f2fs_replace_block(sbi, &dn, dn.data_blkaddr,
    new_addr, ni.version, truetrue);
  }
 } else {
  blkcnt_t count = 1;

  err = inc_valid_block_count(sbi, inode, &count, true);
  if (err) {
   f2fs_put_dnode(&dn);
   return err;
  }

  *old_addr = dn.data_blkaddr;
  f2fs_truncate_data_blocks_range(&dn, 1);
  dec_valid_block_count(sbi, F2FS_I(inode)->cow_inode, count);

  f2fs_replace_block(sbi, &dn, dn.data_blkaddr, new_addr,
     ni.version, truefalse);
 }

 f2fs_put_dnode(&dn);

 trace_f2fs_replace_atomic_write_block(inode, F2FS_I(inode)->cow_inode,
   index, old_addr ? *old_addr : 0, new_addr, recover);
 return 0;
}

static void __complete_revoke_list(struct inode *inode, struct list_head *head,
     bool revoke)
{
 struct revoke_entry *cur, *tmp;
 pgoff_t start_index = 0;
 bool truncate = is_inode_flag_set(inode, FI_ATOMIC_REPLACE);

 list_for_each_entry_safe(cur, tmp, head, list) {
  if (revoke) {
   __replace_atomic_write_block(inode, cur->index,
      cur->old_addr, NULL, true);
  } else if (truncate) {
   f2fs_truncate_hole(inode, start_index, cur->index);
   start_index = cur->index + 1;
  }

  list_del(&cur->list);
  kmem_cache_free(revoke_entry_slab, cur);
 }

 if (!revoke && truncate)
  f2fs_do_truncate_blocks(inode, start_index * PAGE_SIZE, false);
}

static int __f2fs_commit_atomic_write(struct inode *inode)
{
 struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 struct f2fs_inode_info *fi = F2FS_I(inode);
 struct inode *cow_inode = fi->cow_inode;
 struct revoke_entry *new;
 struct list_head revoke_list;
 block_t blkaddr;
 struct dnode_of_data dn;
 pgoff_t len = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE);
 pgoff_t off = 0, blen, index;
 int ret = 0, i;

 INIT_LIST_HEAD(&revoke_list);

 while (len) {
  blen = min_t(pgoff_t, ADDRS_PER_BLOCK(cow_inode), len);

  set_new_dnode(&dn, cow_inode, NULL, NULL, 0);
  ret = f2fs_get_dnode_of_data(&dn, off, LOOKUP_NODE_RA);
  if (ret && ret != -ENOENT) {
   goto out;
  } else if (ret == -ENOENT) {
   ret = 0;
   if (dn.max_level == 0)
    goto out;
   goto next;
  }

  blen = min((pgoff_t)ADDRS_PER_PAGE(dn.node_folio, cow_inode),
    len);
  index = off;
  for (i = 0; i < blen; i++, dn.ofs_in_node++, index++) {
   blkaddr = f2fs_data_blkaddr(&dn);

   if (!__is_valid_data_blkaddr(blkaddr)) {
    continue;
   } else if (!f2fs_is_valid_blkaddr(sbi, blkaddr,
     DATA_GENERIC_ENHANCE)) {
    f2fs_put_dnode(&dn);
    ret = -EFSCORRUPTED;
    goto out;
   }

   new = f2fs_kmem_cache_alloc(revoke_entry_slab, GFP_NOFS,
       true, NULL);

   ret = __replace_atomic_write_block(inode, index, blkaddr,
       &new->old_addr, false);
   if (ret) {
    f2fs_put_dnode(&dn);
    kmem_cache_free(revoke_entry_slab, new);
    goto out;
   }

   f2fs_update_data_blkaddr(&dn, NULL_ADDR);
   new->index = index;
   list_add_tail(&new->list, &revoke_list);
  }
  f2fs_put_dnode(&dn);
next:
  off += blen;
  len -= blen;
 }

out:
 if (time_to_inject(sbi, FAULT_TIMEOUT))
  f2fs_io_schedule_timeout_killable(DEFAULT_FAULT_TIMEOUT);

 if (ret) {
  sbi->revoked_atomic_block += fi->atomic_write_cnt;
 } else {
  sbi->committed_atomic_block += fi->atomic_write_cnt;
  set_inode_flag(inode, FI_ATOMIC_COMMITTED);

  /*
 * inode may has no FI_ATOMIC_DIRTIED flag due to no write
 * before commit.
 */

  if (is_inode_flag_set(inode, FI_ATOMIC_DIRTIED)) {
   /* clear atomic dirty status and set vfs dirty status */
   clear_inode_flag(inode, FI_ATOMIC_DIRTIED);
   f2fs_mark_inode_dirty_sync(inode, true);
  }
 }

 __complete_revoke_list(inode, &revoke_list, ret ? true : false);

 return ret;
}

int f2fs_commit_atomic_write(struct inode *inode)
{
 struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 struct f2fs_inode_info *fi = F2FS_I(inode);
 int err;

 err = filemap_write_and_wait_range(inode->i_mapping, 0, LLONG_MAX);
 if (err)
  return err;

 f2fs_down_write(&fi->i_gc_rwsem[WRITE]);
 f2fs_lock_op(sbi);

 err = __f2fs_commit_atomic_write(inode);

 f2fs_unlock_op(sbi);
 f2fs_up_write(&fi->i_gc_rwsem[WRITE]);

 return err;
}

/*
 * This function balances dirty node and dentry pages.
 * In addition, it controls garbage collection.
 */

void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need)
{
 if (f2fs_cp_error(sbi))
  return;

 if (time_to_inject(sbi, FAULT_CHECKPOINT))
  f2fs_stop_checkpoint(sbi, false, STOP_CP_REASON_FAULT_INJECT);

 /* balance_fs_bg is able to be pending */
 if (need && excess_cached_nats(sbi))
  f2fs_balance_fs_bg(sbi, false);

 if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED)))
  return;

 /*
 * We should do GC or end up with checkpoint, if there are so many dirty
 * dir/node pages without enough free segments.
 */

 if (has_enough_free_secs(sbi, 0, 0))
  return;

 if (test_opt(sbi, GC_MERGE) && sbi->gc_thread &&
    sbi->gc_thread->f2fs_gc_task) {
  DEFINE_WAIT(wait);

  prepare_to_wait(&sbi->gc_thread->fggc_wq, &wait,
     TASK_UNINTERRUPTIBLE);
  wake_up(&sbi->gc_thread->gc_wait_queue_head);
  io_schedule();
  finish_wait(&sbi->gc_thread->fggc_wq, &wait);
 } else {
  struct f2fs_gc_control gc_control = {
   .victim_segno = NULL_SEGNO,
   .init_gc_type = f2fs_sb_has_blkzoned(sbi) ?
    FG_GC : BG_GC,
   .no_bg_gc = true,
   .should_migrate_blocks = false,
   .err_gc_skipped = false,
   .nr_free_secs = 1 };
  f2fs_down_write(&sbi->gc_lock);
  stat_inc_gc_call_count(sbi, FOREGROUND);
  f2fs_gc(sbi, &gc_control);
 }
}

static inline bool excess_dirty_threshold(struct f2fs_sb_info *sbi)
{
 int factor = f2fs_rwsem_is_locked(&sbi->cp_rwsem) ? 3 : 2;
 unsigned int dents = get_pages(sbi, F2FS_DIRTY_DENTS);
 unsigned int qdata = get_pages(sbi, F2FS_DIRTY_QDATA);
 unsigned int nodes = get_pages(sbi, F2FS_DIRTY_NODES);
 unsigned int meta = get_pages(sbi, F2FS_DIRTY_META);
 unsigned int imeta = get_pages(sbi, F2FS_DIRTY_IMETA);
 unsigned int threshold =
  SEGS_TO_BLKS(sbi, (factor * DEFAULT_DIRTY_THRESHOLD));
 unsigned int global_threshold = threshold * 3 / 2;

 if (dents >= threshold || qdata >= threshold ||
  nodes >= threshold || meta >= threshold ||
  imeta >= threshold)
  return true;
 return dents + qdata + nodes + meta + imeta >  global_threshold;
}

void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi, bool from_bg)
{
 if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
  return;

 /* try to shrink extent cache when there is no enough memory */
 if (!f2fs_available_free_memory(sbi, READ_EXTENT_CACHE))
  f2fs_shrink_read_extent_tree(sbi,
    READ_EXTENT_CACHE_SHRINK_NUMBER);

 /* try to shrink age extent cache when there is no enough memory */
 if (!f2fs_available_free_memory(sbi, AGE_EXTENT_CACHE))
  f2fs_shrink_age_extent_tree(sbi,
    AGE_EXTENT_CACHE_SHRINK_NUMBER);

 /* check the # of cached NAT entries */
 if (!f2fs_available_free_memory(sbi, NAT_ENTRIES))
  f2fs_try_to_free_nats(sbi, NAT_ENTRY_PER_BLOCK);

 if (!f2fs_available_free_memory(sbi, FREE_NIDS))
  f2fs_try_to_free_nids(sbi, MAX_FREE_NIDS);
 else
  f2fs_build_free_nids(sbi, falsefalse);

 if (excess_dirty_nats(sbi) || excess_dirty_threshold(sbi) ||
  excess_prefree_segs(sbi) || !f2fs_space_for_roll_forward(sbi))
  goto do_sync;

 /* there is background inflight IO or foreground operation recently */
 if (is_inflight_io(sbi, REQ_TIME) ||
  (!f2fs_time_over(sbi, REQ_TIME) && f2fs_rwsem_is_locked(&sbi->cp_rwsem)))
  return;

 /* exceed periodical checkpoint timeout threshold */
 if (f2fs_time_over(sbi, CP_TIME))
  goto do_sync;

 /* checkpoint is the only way to shrink partial cached entries */
 if (f2fs_available_free_memory(sbi, NAT_ENTRIES) &&
  f2fs_available_free_memory(sbi, INO_ENTRIES))
  return;

do_sync:
 if (test_opt(sbi, DATA_FLUSH) && from_bg) {
  struct blk_plug plug;

  mutex_lock(&sbi->flush_lock);

  blk_start_plug(&plug);
  f2fs_sync_dirty_inodes(sbi, FILE_INODE, false);
  blk_finish_plug(&plug);

  mutex_unlock(&sbi->flush_lock);
 }
 stat_inc_cp_call_count(sbi, BACKGROUND);
 f2fs_sync_fs(sbi->sb, 1);
}

static int __submit_flush_wait(struct f2fs_sb_info *sbi,
    struct block_device *bdev)
{
 int ret = blkdev_issue_flush(bdev);

 trace_f2fs_issue_flush(bdev, test_opt(sbi, NOBARRIER),
    test_opt(sbi, FLUSH_MERGE), ret);
 if (!ret)
  f2fs_update_iostat(sbi, NULL, FS_FLUSH_IO, 0);
 return ret;
}

static int submit_flush_wait(struct f2fs_sb_info *sbi, nid_t ino)
{
 int ret = 0;
 int i;

 if (!f2fs_is_multi_device(sbi))
  return __submit_flush_wait(sbi, sbi->sb->s_bdev);

 for (i = 0; i < sbi->s_ndevs; i++) {
  if (!f2fs_is_dirty_device(sbi, ino, i, FLUSH_INO))
   continue;
  ret = __submit_flush_wait(sbi, FDEV(i).bdev);
  if (ret)
   break;
 }
 return ret;
}

static int issue_flush_thread(void *data)
{
 struct f2fs_sb_info *sbi = data;
 struct flush_cmd_control *fcc = SM_I(sbi)->fcc_info;
 wait_queue_head_t *q = &fcc->flush_wait_queue;
repeat:
 if (kthread_should_stop())
  return 0;

 if (!llist_empty(&fcc->issue_list)) {
  struct flush_cmd *cmd, *next;
  int ret;

  fcc->dispatch_list = llist_del_all(&fcc->issue_list);
  fcc->dispatch_list = llist_reverse_order(fcc->dispatch_list);

  cmd = llist_entry(fcc->dispatch_list, struct flush_cmd, llnode);

  ret = submit_flush_wait(sbi, cmd->ino);
  atomic_inc(&fcc->issued_flush);

  llist_for_each_entry_safe(cmd, next,
       fcc->dispatch_list, llnode) {
   cmd->ret = ret;
   complete(&cmd->wait);
  }
  fcc->dispatch_list = NULL;
 }

 wait_event_interruptible(*q,
  kthread_should_stop() || !llist_empty(&fcc->issue_list));
 goto repeat;
}

int f2fs_issue_flush(struct f2fs_sb_info *sbi, nid_t ino)
{
 struct flush_cmd_control *fcc = SM_I(sbi)->fcc_info;
 struct flush_cmd cmd;
 int ret;

 if (test_opt(sbi, NOBARRIER))
  return 0;

 if (!test_opt(sbi, FLUSH_MERGE)) {
  atomic_inc(&fcc->queued_flush);
  ret = submit_flush_wait(sbi, ino);
  atomic_dec(&fcc->queued_flush);
  atomic_inc(&fcc->issued_flush);
  return ret;
 }

 if (atomic_inc_return(&fcc->queued_flush) == 1 ||
     f2fs_is_multi_device(sbi)) {
  ret = submit_flush_wait(sbi, ino);
  atomic_dec(&fcc->queued_flush);

  atomic_inc(&fcc->issued_flush);
  return ret;
 }

 cmd.ino = ino;
 init_completion(&cmd.wait);

 llist_add(&cmd.llnode, &fcc->issue_list);

 /*
 * update issue_list before we wake up issue_flush thread, this
 * smp_mb() pairs with another barrier in ___wait_event(), see
 * more details in comments of waitqueue_active().
 */

 smp_mb();

 if (waitqueue_active(&fcc->flush_wait_queue))
  wake_up(&fcc->flush_wait_queue);

 if (fcc->f2fs_issue_flush) {
  wait_for_completion(&cmd.wait);
  atomic_dec(&fcc->queued_flush);
 } else {
  struct llist_node *list;

  list = llist_del_all(&fcc->issue_list);
  if (!list) {
   wait_for_completion(&cmd.wait);
   atomic_dec(&fcc->queued_flush);
  } else {
   struct flush_cmd *tmp, *next;

   ret = submit_flush_wait(sbi, ino);

   llist_for_each_entry_safe(tmp, next, list, llnode) {
    if (tmp == &cmd) {
     cmd.ret = ret;
     atomic_dec(&fcc->queued_flush);
     continue;
    }
    tmp->ret = ret;
    complete(&tmp->wait);
   }
  }
 }

 return cmd.ret;
}

int f2fs_create_flush_cmd_control(struct f2fs_sb_info *sbi)
{
 dev_t dev = sbi->sb->s_bdev->bd_dev;
 struct flush_cmd_control *fcc;

 if (SM_I(sbi)->fcc_info) {
  fcc = SM_I(sbi)->fcc_info;
  if (fcc->f2fs_issue_flush)
   return 0;
  goto init_thread;
 }

 fcc = f2fs_kzalloc(sbi, sizeof(struct flush_cmd_control), GFP_KERNEL);
 if (!fcc)
  return -ENOMEM;
 atomic_set(&fcc->issued_flush, 0);
 atomic_set(&fcc->queued_flush, 0);
 init_waitqueue_head(&fcc->flush_wait_queue);
 init_llist_head(&fcc->issue_list);
 SM_I(sbi)->fcc_info = fcc;
 if (!test_opt(sbi, FLUSH_MERGE))
  return 0;

init_thread:
 fcc->f2fs_issue_flush = kthread_run(issue_flush_thread, sbi,
    "f2fs_flush-%u:%u", MAJOR(dev), MINOR(dev));
 if (IS_ERR(fcc->f2fs_issue_flush)) {
  int err = PTR_ERR(fcc->f2fs_issue_flush);

  fcc->f2fs_issue_flush = NULL;
  return err;
 }

 return 0;
}

void f2fs_destroy_flush_cmd_control(struct f2fs_sb_info *sbi, bool free)
{
 struct flush_cmd_control *fcc = SM_I(sbi)->fcc_info;

 if (fcc && fcc->f2fs_issue_flush) {
  struct task_struct *flush_thread = fcc->f2fs_issue_flush;

  fcc->f2fs_issue_flush = NULL;
  kthread_stop(flush_thread);
 }
 if (free) {
  kfree(fcc);
  SM_I(sbi)->fcc_info = NULL;
 }
}

int f2fs_flush_device_cache(struct f2fs_sb_info *sbi)
{
 int ret = 0, i;

 if (!f2fs_is_multi_device(sbi))
  return 0;

 if (test_opt(sbi, NOBARRIER))
  return 0;

 for (i = 1; i < sbi->s_ndevs; i++) {
  int count = DEFAULT_RETRY_IO_COUNT;

  if (!f2fs_test_bit(i, (char *)&sbi->dirty_device))
   continue;

  do {
   ret = __submit_flush_wait(sbi, FDEV(i).bdev);
   if (ret)
    f2fs_io_schedule_timeout(DEFAULT_IO_TIMEOUT);
  } while (ret && --count);

  if (ret) {
   f2fs_stop_checkpoint(sbi, false,
     STOP_CP_REASON_FLUSH_FAIL);
   break;
  }

  spin_lock(&sbi->dev_lock);
  f2fs_clear_bit(i, (char *)&sbi->dirty_device);
  spin_unlock(&sbi->dev_lock);
 }

 return ret;
}

static void __locate_dirty_segment(struct f2fs_sb_info *sbi, unsigned int segno,
  enum dirty_type dirty_type)
{
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);

 /* need not be added */
 if (is_curseg(sbi, segno))
  return;

 if (!test_and_set_bit(segno, dirty_i->dirty_segmap[dirty_type]))
  dirty_i->nr_dirty[dirty_type]++;

 if (dirty_type == DIRTY) {
  struct seg_entry *sentry = get_seg_entry(sbi, segno);
  enum dirty_type t = sentry->type;

  if (unlikely(t >= DIRTY)) {
   f2fs_bug_on(sbi, 1);
   return;
  }
  if (!test_and_set_bit(segno, dirty_i->dirty_segmap[t]))
   dirty_i->nr_dirty[t]++;

  if (__is_large_section(sbi)) {
   unsigned int secno = GET_SEC_FROM_SEG(sbi, segno);
   block_t valid_blocks =
    get_valid_blocks(sbi, segno, true);

   f2fs_bug_on(sbi,
    (!is_sbi_flag_set(sbi, SBI_CP_DISABLED) &&
    !valid_blocks) ||
    valid_blocks == CAP_BLKS_PER_SEC(sbi));

   if (!is_cursec(sbi, secno))
    set_bit(secno, dirty_i->dirty_secmap);
  }
 }
}

static void __remove_dirty_segment(struct f2fs_sb_info *sbi, unsigned int segno,
  enum dirty_type dirty_type)
{
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 block_t valid_blocks;

 if (test_and_clear_bit(segno, dirty_i->dirty_segmap[dirty_type]))
  dirty_i->nr_dirty[dirty_type]--;

 if (dirty_type == DIRTY) {
  struct seg_entry *sentry = get_seg_entry(sbi, segno);
  enum dirty_type t = sentry->type;

  if (test_and_clear_bit(segno, dirty_i->dirty_segmap[t]))
   dirty_i->nr_dirty[t]--;

  valid_blocks = get_valid_blocks(sbi, segno, true);
  if (valid_blocks == 0) {
   clear_bit(GET_SEC_FROM_SEG(sbi, segno),
      dirty_i->victim_secmap);
#ifdef CONFIG_F2FS_CHECK_FS
   clear_bit(segno, SIT_I(sbi)->invalid_segmap);
#endif
  }
  if (__is_large_section(sbi)) {
   unsigned int secno = GET_SEC_FROM_SEG(sbi, segno);

   if (!valid_blocks ||
     valid_blocks == CAP_BLKS_PER_SEC(sbi)) {
    clear_bit(secno, dirty_i->dirty_secmap);
    return;
   }

   if (!is_cursec(sbi, secno))
    set_bit(secno, dirty_i->dirty_secmap);
  }
 }
}

/*
 * Should not occur error such as -ENOMEM.
 * Adding dirty entry into seglist is not critical operation.
 * If a given segment is one of current working segments, it won't be added.
 */

static void locate_dirty_segment(struct f2fs_sb_info *sbi, unsigned int segno)
{
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 unsigned short valid_blocks, ckpt_valid_blocks;
 unsigned int usable_blocks;

 if (segno == NULL_SEGNO || is_curseg(sbi, segno))
  return;

 usable_blocks = f2fs_usable_blks_in_seg(sbi, segno);
 mutex_lock(&dirty_i->seglist_lock);

 valid_blocks = get_valid_blocks(sbi, segno, false);
 ckpt_valid_blocks = get_ckpt_valid_blocks(sbi, segno, false);

 if (valid_blocks == 0 && (!is_sbi_flag_set(sbi, SBI_CP_DISABLED) ||
  ckpt_valid_blocks == usable_blocks)) {
  __locate_dirty_segment(sbi, segno, PRE);
  __remove_dirty_segment(sbi, segno, DIRTY);
 } else if (valid_blocks < usable_blocks) {
  __locate_dirty_segment(sbi, segno, DIRTY);
 } else {
  /* Recovery routine with SSR needs this */
  __remove_dirty_segment(sbi, segno, DIRTY);
 }

 mutex_unlock(&dirty_i->seglist_lock);
}

/* This moves currently empty dirty blocks to prefree. Must hold seglist_lock */
void f2fs_dirty_to_prefree(struct f2fs_sb_info *sbi)
{
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 unsigned int segno;

 mutex_lock(&dirty_i->seglist_lock);
 for_each_set_bit(segno, dirty_i->dirty_segmap[DIRTY], MAIN_SEGS(sbi)) {
  if (get_valid_blocks(sbi, segno, false))
   continue;
  if (is_curseg(sbi, segno))
   continue;
  __locate_dirty_segment(sbi, segno, PRE);
  __remove_dirty_segment(sbi, segno, DIRTY);
 }
 mutex_unlock(&dirty_i->seglist_lock);
}

block_t f2fs_get_unusable_blocks(struct f2fs_sb_info *sbi)
{
 int ovp_hole_segs =
  (overprovision_segments(sbi) - reserved_segments(sbi));
 block_t ovp_holes = SEGS_TO_BLKS(sbi, ovp_hole_segs);
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 block_t holes[2] = {0, 0}; /* DATA and NODE */
 block_t unusable;
 struct seg_entry *se;
 unsigned int segno;

 mutex_lock(&dirty_i->seglist_lock);
 for_each_set_bit(segno, dirty_i->dirty_segmap[DIRTY], MAIN_SEGS(sbi)) {
  se = get_seg_entry(sbi, segno);
  if (IS_NODESEG(se->type))
   holes[NODE] += f2fs_usable_blks_in_seg(sbi, segno) -
       se->valid_blocks;
  else
   holes[DATA] += f2fs_usable_blks_in_seg(sbi, segno) -
       se->valid_blocks;
 }
 mutex_unlock(&dirty_i->seglist_lock);

 unusable = max(holes[DATA], holes[NODE]);
 if (unusable > ovp_holes)
  return unusable - ovp_holes;
 return 0;
}

int f2fs_disable_cp_again(struct f2fs_sb_info *sbi, block_t unusable)
{
 int ovp_hole_segs =
  (overprovision_segments(sbi) - reserved_segments(sbi));

 if (F2FS_OPTION(sbi).unusable_cap_perc == 100)
  return 0;
 if (unusable > F2FS_OPTION(sbi).unusable_cap)
  return -EAGAIN;
 if (is_sbi_flag_set(sbi, SBI_CP_DISABLED_QUICK) &&
  dirty_segments(sbi) > ovp_hole_segs)
  return -EAGAIN;
 if (has_not_enough_free_secs(sbi, 0, 0))
  return -EAGAIN;
 return 0;
}

/* This is only used by SBI_CP_DISABLED */
static unsigned int get_free_segment(struct f2fs_sb_info *sbi)
{
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 unsigned int segno = 0;

 mutex_lock(&dirty_i->seglist_lock);
 for_each_set_bit(segno, dirty_i->dirty_segmap[DIRTY], MAIN_SEGS(sbi)) {
  if (get_valid_blocks(sbi, segno, false))
   continue;
  if (get_ckpt_valid_blocks(sbi, segno, false))
   continue;
  mutex_unlock(&dirty_i->seglist_lock);
  return segno;
 }
 mutex_unlock(&dirty_i->seglist_lock);
 return NULL_SEGNO;
}

static struct discard_cmd *__create_discard_cmd(struct f2fs_sb_info *sbi,
  struct block_device *bdev, block_t lstart,
  block_t start, block_t len)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct list_head *pend_list;
 struct discard_cmd *dc;

 f2fs_bug_on(sbi, !len);

 pend_list = &dcc->pend_list[plist_idx(len)];

 dc = f2fs_kmem_cache_alloc(discard_cmd_slab, GFP_NOFS, true, NULL);
 INIT_LIST_HEAD(&dc->list);
 dc->bdev = bdev;
 dc->di.lstart = lstart;
 dc->di.start = start;
 dc->di.len = len;
 dc->ref = 0;
 dc->state = D_PREP;
 dc->queued = 0;
 dc->error = 0;
 init_completion(&dc->wait);
 list_add_tail(&dc->list, pend_list);
 spin_lock_init(&dc->lock);
 dc->bio_ref = 0;
 atomic_inc(&dcc->discard_cmd_cnt);
 dcc->undiscard_blks += len;

 return dc;
}

static bool f2fs_check_discard_tree(struct f2fs_sb_info *sbi)
{
#ifdef CONFIG_F2FS_CHECK_FS
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct rb_node *cur = rb_first_cached(&dcc->root), *next;
 struct discard_cmd *cur_dc, *next_dc;

 while (cur) {
  next = rb_next(cur);
  if (!next)
   return true;

  cur_dc = rb_entry(cur, struct discard_cmd, rb_node);
  next_dc = rb_entry(next, struct discard_cmd, rb_node);

  if (cur_dc->di.lstart + cur_dc->di.len > next_dc->di.lstart) {
   f2fs_info(sbi, "broken discard_rbtree, "
    "cur(%u, %u) next(%u, %u)",
    cur_dc->di.lstart, cur_dc->di.len,
    next_dc->di.lstart, next_dc->di.len);
   return false;
  }
  cur = next;
 }
#endif
 return true;
}

static struct discard_cmd *__lookup_discard_cmd(struct f2fs_sb_info *sbi,
      block_t blkaddr)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct rb_node *node = dcc->root.rb_root.rb_node;
 struct discard_cmd *dc;

 while (node) {
  dc = rb_entry(node, struct discard_cmd, rb_node);

  if (blkaddr < dc->di.lstart)
   node = node->rb_left;
  else if (blkaddr >= dc->di.lstart + dc->di.len)
   node = node->rb_right;
  else
   return dc;
 }
 return NULL;
}

static struct discard_cmd *__lookup_discard_cmd_ret(struct rb_root_cached *root,
    block_t blkaddr,
    struct discard_cmd **prev_entry,
    struct discard_cmd **next_entry,
    struct rb_node ***insert_p,
    struct rb_node **insert_parent)
{
 struct rb_node **pnode = &root->rb_root.rb_node;
 struct rb_node *parent = NULL, *tmp_node;
 struct discard_cmd *dc;

 *insert_p = NULL;
 *insert_parent = NULL;
 *prev_entry = NULL;
 *next_entry = NULL;

 if (RB_EMPTY_ROOT(&root->rb_root))
  return NULL;

 while (*pnode) {
  parent = *pnode;
  dc = rb_entry(*pnode, struct discard_cmd, rb_node);

  if (blkaddr < dc->di.lstart)
   pnode = &(*pnode)->rb_left;
  else if (blkaddr >= dc->di.lstart + dc->di.len)
   pnode = &(*pnode)->rb_right;
  else
   goto lookup_neighbors;
 }

 *insert_p = pnode;
 *insert_parent = parent;

 dc = rb_entry(parent, struct discard_cmd, rb_node);
 tmp_node = parent;
 if (parent && blkaddr > dc->di.lstart)
  tmp_node = rb_next(parent);
 *next_entry = rb_entry_safe(tmp_node, struct discard_cmd, rb_node);

 tmp_node = parent;
 if (parent && blkaddr < dc->di.lstart)
  tmp_node = rb_prev(parent);
 *prev_entry = rb_entry_safe(tmp_node, struct discard_cmd, rb_node);
 return NULL;

lookup_neighbors:
 /* lookup prev node for merging backward later */
 tmp_node = rb_prev(&dc->rb_node);
 *prev_entry = rb_entry_safe(tmp_node, struct discard_cmd, rb_node);

 /* lookup next node for merging frontward later */
 tmp_node = rb_next(&dc->rb_node);
 *next_entry = rb_entry_safe(tmp_node, struct discard_cmd, rb_node);
 return dc;
}

static void __detach_discard_cmd(struct discard_cmd_control *dcc,
       struct discard_cmd *dc)
{
 if (dc->state == D_DONE)
  atomic_sub(dc->queued, &dcc->queued_discard);

 list_del(&dc->list);
 rb_erase_cached(&dc->rb_node, &dcc->root);
 dcc->undiscard_blks -= dc->di.len;

 kmem_cache_free(discard_cmd_slab, dc);

 atomic_dec(&dcc->discard_cmd_cnt);
}

static void __remove_discard_cmd(struct f2fs_sb_info *sbi,
       struct discard_cmd *dc)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 unsigned long flags;

 trace_f2fs_remove_discard(dc->bdev, dc->di.start, dc->di.len);

 spin_lock_irqsave(&dc->lock, flags);
 if (dc->bio_ref) {
  spin_unlock_irqrestore(&dc->lock, flags);
  return;
 }
 spin_unlock_irqrestore(&dc->lock, flags);

 f2fs_bug_on(sbi, dc->ref);

 if (dc->error == -EOPNOTSUPP)
  dc->error = 0;

 if (dc->error)
  f2fs_info_ratelimited(sbi,
   "Issue discard(%u, %u, %u) failed, ret: %d",
   dc->di.lstart, dc->di.start, dc->di.len, dc->error);
 __detach_discard_cmd(dcc, dc);
}

static void f2fs_submit_discard_endio(struct bio *bio)
{
 struct discard_cmd *dc = (struct discard_cmd *)bio->bi_private;
 unsigned long flags;

 spin_lock_irqsave(&dc->lock, flags);
 if (!dc->error)
  dc->error = blk_status_to_errno(bio->bi_status);
 dc->bio_ref--;
 if (!dc->bio_ref && dc->state == D_SUBMIT) {
  dc->state = D_DONE;
  complete_all(&dc->wait);
 }
 spin_unlock_irqrestore(&dc->lock, flags);
 bio_put(bio);
}

static void __check_sit_bitmap(struct f2fs_sb_info *sbi,
    block_t start, block_t end)
{
#ifdef CONFIG_F2FS_CHECK_FS
 struct seg_entry *sentry;
 unsigned int segno;
 block_t blk = start;
 unsigned long offset, size, *map;

 while (blk < end) {
  segno = GET_SEGNO(sbi, blk);
  sentry = get_seg_entry(sbi, segno);
  offset = GET_BLKOFF_FROM_SEG0(sbi, blk);

  if (end < START_BLOCK(sbi, segno + 1))
   size = GET_BLKOFF_FROM_SEG0(sbi, end);
  else
   size = BLKS_PER_SEG(sbi);
  map = (unsigned long *)(sentry->cur_valid_map);
  offset = __find_rev_next_bit(map, size, offset);
  f2fs_bug_on(sbi, offset != size);
  blk = START_BLOCK(sbi, segno + 1);
 }
#endif
}

static void __init_discard_policy(struct f2fs_sb_info *sbi,
    struct discard_policy *dpolicy,
    int discard_type, unsigned int granularity)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;

 /* common policy */
 dpolicy->type = discard_type;
 dpolicy->sync = true;
 dpolicy->ordered = false;
 dpolicy->granularity = granularity;

 dpolicy->max_requests = dcc->max_discard_request;
 dpolicy->io_aware_gran = dcc->discard_io_aware_gran;
 dpolicy->timeout = false;

 if (discard_type == DPOLICY_BG) {
  dpolicy->min_interval = dcc->min_discard_issue_time;
  dpolicy->mid_interval = dcc->mid_discard_issue_time;
  dpolicy->max_interval = dcc->max_discard_issue_time;
  if (dcc->discard_io_aware == DPOLICY_IO_AWARE_ENABLE)
   dpolicy->io_aware = true;
  else if (dcc->discard_io_aware == DPOLICY_IO_AWARE_DISABLE)
   dpolicy->io_aware = false;
  dpolicy->sync = false;
  dpolicy->ordered = true;
  if (utilization(sbi) > dcc->discard_urgent_util) {
   dpolicy->granularity = MIN_DISCARD_GRANULARITY;
   if (atomic_read(&dcc->discard_cmd_cnt))
    dpolicy->max_interval =
     dcc->min_discard_issue_time;
  }
 } else if (discard_type == DPOLICY_FORCE) {
  dpolicy->min_interval = dcc->min_discard_issue_time;
  dpolicy->mid_interval = dcc->mid_discard_issue_time;
  dpolicy->max_interval = dcc->max_discard_issue_time;
  dpolicy->io_aware = false;
 } else if (discard_type == DPOLICY_FSTRIM) {
  dpolicy->io_aware = false;
 } else if (discard_type == DPOLICY_UMOUNT) {
  dpolicy->io_aware = false;
  /* we need to issue all to keep CP_TRIMMED_FLAG */
  dpolicy->granularity = MIN_DISCARD_GRANULARITY;
  dpolicy->timeout = true;
 }
}

static void __update_discard_tree_range(struct f2fs_sb_info *sbi,
    struct block_device *bdev, block_t lstart,
    block_t start, block_t len);

#ifdef CONFIG_BLK_DEV_ZONED
static void __submit_zone_reset_cmd(struct f2fs_sb_info *sbi,
       struct discard_cmd *dc, blk_opf_t flag,
       struct list_head *wait_list,
       unsigned int *issued)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct block_device *bdev = dc->bdev;
 struct bio *bio = bio_alloc(bdev, 0, REQ_OP_ZONE_RESET | flag, GFP_NOFS);
 unsigned long flags;

 trace_f2fs_issue_reset_zone(bdev, dc->di.start);

 spin_lock_irqsave(&dc->lock, flags);
 dc->state = D_SUBMIT;
 dc->bio_ref++;
 spin_unlock_irqrestore(&dc->lock, flags);

 if (issued)
  (*issued)++;

 atomic_inc(&dcc->queued_discard);
 dc->queued++;
 list_move_tail(&dc->list, wait_list);

 /* sanity check on discard range */
 __check_sit_bitmap(sbi, dc->di.lstart, dc->di.lstart + dc->di.len);

 bio->bi_iter.bi_sector = SECTOR_FROM_BLOCK(dc->di.start);
 bio->bi_private = dc;
 bio->bi_end_io = f2fs_submit_discard_endio;
 submit_bio(bio);

 atomic_inc(&dcc->issued_discard);
 f2fs_update_iostat(sbi, NULL, FS_ZONE_RESET_IO, dc->di.len * F2FS_BLKSIZE);
}
#endif

/* this function is copied from blkdev_issue_discard from block/blk-lib.c */
static int __submit_discard_cmd(struct f2fs_sb_info *sbi,
    struct discard_policy *dpolicy,
    struct discard_cmd *dc, int *issued)
{
 struct block_device *bdev = dc->bdev;
 unsigned int max_discard_blocks =
   SECTOR_TO_BLOCK(bdev_max_discard_sectors(bdev));
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct list_head *wait_list = (dpolicy->type == DPOLICY_FSTRIM) ?
     &(dcc->fstrim_list) : &(dcc->wait_list);
 blk_opf_t flag = dpolicy->sync ? REQ_SYNC : 0;
 block_t lstart, start, len, total_len;
 int err = 0;

 if (dc->state != D_PREP)
  return 0;

 if (is_sbi_flag_set(sbi, SBI_NEED_FSCK))
  return 0;

#ifdef CONFIG_BLK_DEV_ZONED
 if (f2fs_sb_has_blkzoned(sbi) && bdev_is_zoned(bdev)) {
  int devi = f2fs_bdev_index(sbi, bdev);

  if (devi < 0)
   return -EINVAL;

  if (f2fs_blkz_is_seq(sbi, devi, dc->di.start)) {
   __submit_zone_reset_cmd(sbi, dc, flag,
      wait_list, issued);
   return 0;
  }
 }
#endif

 /*
 * stop issuing discard for any of below cases:
 * 1. device is conventional zone, but it doesn't support discard.
 * 2. device is regulare device, after snapshot it doesn't support
 * discard.
 */

 if (!bdev_max_discard_sectors(bdev))
  return -EOPNOTSUPP;

 trace_f2fs_issue_discard(bdev, dc->di.start, dc->di.len);

 lstart = dc->di.lstart;
 start = dc->di.start;
 len = dc->di.len;
 total_len = len;

 dc->di.len = 0;

 while (total_len && *issued < dpolicy->max_requests && !err) {
  struct bio *bio = NULL;
  unsigned long flags;
  bool last = true;

  if (len > max_discard_blocks) {
   len = max_discard_blocks;
   last = false;
  }

  (*issued)++;
  if (*issued == dpolicy->max_requests)
   last = true;

  dc->di.len += len;

  if (time_to_inject(sbi, FAULT_DISCARD)) {
   err = -EIO;
  } else {
   err = __blkdev_issue_discard(bdev,
     SECTOR_FROM_BLOCK(start),
     SECTOR_FROM_BLOCK(len),
     GFP_NOFS, &bio);
  }
  if (err) {
   spin_lock_irqsave(&dc->lock, flags);
   if (dc->state == D_PARTIAL)
    dc->state = D_SUBMIT;
   spin_unlock_irqrestore(&dc->lock, flags);

   break;
  }

  f2fs_bug_on(sbi, !bio);

  /*
 * should keep before submission to avoid D_DONE
 * right away
 */

  spin_lock_irqsave(&dc->lock, flags);
  if (last)
   dc->state = D_SUBMIT;
  else
   dc->state = D_PARTIAL;
  dc->bio_ref++;
  spin_unlock_irqrestore(&dc->lock, flags);

  atomic_inc(&dcc->queued_discard);
  dc->queued++;
  list_move_tail(&dc->list, wait_list);

  /* sanity check on discard range */
  __check_sit_bitmap(sbi, lstart, lstart + len);

  bio->bi_private = dc;
  bio->bi_end_io = f2fs_submit_discard_endio;
  bio->bi_opf |= flag;
  submit_bio(bio);

  atomic_inc(&dcc->issued_discard);

  f2fs_update_iostat(sbi, NULL, FS_DISCARD_IO, len * F2FS_BLKSIZE);

  lstart += len;
  start += len;
  total_len -= len;
  len = total_len;
 }

 if (!err && len) {
  dcc->undiscard_blks -= len;
  __update_discard_tree_range(sbi, bdev, lstart, start, len);
 }
 return err;
}

static void __insert_discard_cmd(struct f2fs_sb_info *sbi,
    struct block_device *bdev, block_t lstart,
    block_t start, block_t len)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct rb_node **p = &dcc->root.rb_root.rb_node;
 struct rb_node *parent = NULL;
 struct discard_cmd *dc;
 bool leftmost = true;

 /* look up rb tree to find parent node */
 while (*p) {
  parent = *p;
  dc = rb_entry(parent, struct discard_cmd, rb_node);

  if (lstart < dc->di.lstart) {
   p = &(*p)->rb_left;
  } else if (lstart >= dc->di.lstart + dc->di.len) {
   p = &(*p)->rb_right;
   leftmost = false;
  } else {
   /* Let's skip to add, if exists */
   return;
  }
 }

 dc = __create_discard_cmd(sbi, bdev, lstart, start, len);

 rb_link_node(&dc->rb_node, parent, p);
 rb_insert_color_cached(&dc->rb_node, &dcc->root, leftmost);
}

static void __relocate_discard_cmd(struct discard_cmd_control *dcc,
      struct discard_cmd *dc)
{
 list_move_tail(&dc->list, &dcc->pend_list[plist_idx(dc->di.len)]);
}

static void __punch_discard_cmd(struct f2fs_sb_info *sbi,
    struct discard_cmd *dc, block_t blkaddr)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct discard_info di = dc->di;
 bool modified = false;

 if (dc->state == D_DONE || dc->di.len == 1) {
  __remove_discard_cmd(sbi, dc);
  return;
 }

 dcc->undiscard_blks -= di.len;

 if (blkaddr > di.lstart) {
  dc->di.len = blkaddr - dc->di.lstart;
  dcc->undiscard_blks += dc->di.len;
  __relocate_discard_cmd(dcc, dc);
  modified = true;
 }

 if (blkaddr < di.lstart + di.len - 1) {
  if (modified) {
   __insert_discard_cmd(sbi, dc->bdev, blkaddr + 1,
     di.start + blkaddr + 1 - di.lstart,
     di.lstart + di.len - 1 - blkaddr);
  } else {
   dc->di.lstart++;
   dc->di.len--;
   dc->di.start++;
   dcc->undiscard_blks += dc->di.len;
   __relocate_discard_cmd(dcc, dc);
  }
 }
}

static void __update_discard_tree_range(struct f2fs_sb_info *sbi,
    struct block_device *bdev, block_t lstart,
    block_t start, block_t len)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct discard_cmd *prev_dc = NULL, *next_dc = NULL;
 struct discard_cmd *dc;
 struct discard_info di = {0};
 struct rb_node **insert_p = NULL, *insert_parent = NULL;
 unsigned int max_discard_blocks =
   SECTOR_TO_BLOCK(bdev_max_discard_sectors(bdev));
 block_t end = lstart + len;

 dc = __lookup_discard_cmd_ret(&dcc->root, lstart,
    &prev_dc, &next_dc, &insert_p, &insert_parent);
 if (dc)
  prev_dc = dc;

 if (!prev_dc) {
  di.lstart = lstart;
  di.len = next_dc ? next_dc->di.lstart - lstart : len;
  di.len = min(di.len, len);
  di.start = start;
 }

 while (1) {
  struct rb_node *node;
  bool merged = false;
  struct discard_cmd *tdc = NULL;

  if (prev_dc) {
   di.lstart = prev_dc->di.lstart + prev_dc->di.len;
   if (di.lstart < lstart)
    di.lstart = lstart;
   if (di.lstart >= end)
    break;

   if (!next_dc || next_dc->di.lstart > end)
    di.len = end - di.lstart;
   else
    di.len = next_dc->di.lstart - di.lstart;
   di.start = start + di.lstart - lstart;
  }

  if (!di.len)
   goto next;

  if (prev_dc && prev_dc->state == D_PREP &&
   prev_dc->bdev == bdev &&
   __is_discard_back_mergeable(&di, &prev_dc->di,
       max_discard_blocks)) {
   prev_dc->di.len += di.len;
   dcc->undiscard_blks += di.len;
   __relocate_discard_cmd(dcc, prev_dc);
   di = prev_dc->di;
   tdc = prev_dc;
   merged = true;
  }

  if (next_dc && next_dc->state == D_PREP &&
   next_dc->bdev == bdev &&
   __is_discard_front_mergeable(&di, &next_dc->di,
       max_discard_blocks)) {
   next_dc->di.lstart = di.lstart;
   next_dc->di.len += di.len;
   next_dc->di.start = di.start;
   dcc->undiscard_blks += di.len;
   __relocate_discard_cmd(dcc, next_dc);
   if (tdc)
    __remove_discard_cmd(sbi, tdc);
   merged = true;
  }

  if (!merged)
   __insert_discard_cmd(sbi, bdev,
      di.lstart, di.start, di.len);
 next:
  prev_dc = next_dc;
  if (!prev_dc)
   break;

  node = rb_next(&prev_dc->rb_node);
  next_dc = rb_entry_safe(node, struct discard_cmd, rb_node);
 }
}

#ifdef CONFIG_BLK_DEV_ZONED
static void __queue_zone_reset_cmd(struct f2fs_sb_info *sbi,
  struct block_device *bdev, block_t blkstart, block_t lblkstart,
  block_t blklen)
{
 trace_f2fs_queue_reset_zone(bdev, blkstart);

 mutex_lock(&SM_I(sbi)->dcc_info->cmd_lock);
 __insert_discard_cmd(sbi, bdev, lblkstart, blkstart, blklen);
 mutex_unlock(&SM_I(sbi)->dcc_info->cmd_lock);
}
#endif

static void __queue_discard_cmd(struct f2fs_sb_info *sbi,
  struct block_device *bdev, block_t blkstart, block_t blklen)
{
 block_t lblkstart = blkstart;

 if (!f2fs_bdev_support_discard(bdev))
  return;

 trace_f2fs_queue_discard(bdev, blkstart, blklen);

 if (f2fs_is_multi_device(sbi)) {
  int devi = f2fs_target_device_index(sbi, blkstart);

  blkstart -= FDEV(devi).start_blk;
 }
 mutex_lock(&SM_I(sbi)->dcc_info->cmd_lock);
 __update_discard_tree_range(sbi, bdev, lblkstart, blkstart, blklen);
 mutex_unlock(&SM_I(sbi)->dcc_info->cmd_lock);
}

static void __issue_discard_cmd_orderly(struct f2fs_sb_info *sbi,
  struct discard_policy *dpolicy, int *issued)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct discard_cmd *prev_dc = NULL, *next_dc = NULL;
 struct rb_node **insert_p = NULL, *insert_parent = NULL;
 struct discard_cmd *dc;
 struct blk_plug plug;
 bool io_interrupted = false;

 mutex_lock(&dcc->cmd_lock);
 dc = __lookup_discard_cmd_ret(&dcc->root, dcc->next_pos,
    &prev_dc, &next_dc, &insert_p, &insert_parent);
 if (!dc)
  dc = next_dc;

 blk_start_plug(&plug);

 while (dc) {
  struct rb_node *node;
  int err = 0;

  if (dc->state != D_PREP)
   goto next;

  if (dpolicy->io_aware && !is_idle(sbi, DISCARD_TIME)) {
   io_interrupted = true;
   break;
  }

  dcc->next_pos = dc->di.lstart + dc->di.len;
  err = __submit_discard_cmd(sbi, dpolicy, dc, issued);

  if (*issued >= dpolicy->max_requests)
   break;
next:
  node = rb_next(&dc->rb_node);
  if (err)
   __remove_discard_cmd(sbi, dc);
  dc = rb_entry_safe(node, struct discard_cmd, rb_node);
 }

 blk_finish_plug(&plug);

 if (!dc)
  dcc->next_pos = 0;

 mutex_unlock(&dcc->cmd_lock);

 if (!(*issued) && io_interrupted)
  *issued = -1;
}
static unsigned int __wait_all_discard_cmd(struct f2fs_sb_info *sbi,
     struct discard_policy *dpolicy);

static int __issue_discard_cmd(struct f2fs_sb_info *sbi,
     struct discard_policy *dpolicy)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct list_head *pend_list;
 struct discard_cmd *dc, *tmp;
 struct blk_plug plug;
 int i, issued;
 bool io_interrupted = false;

 if (dpolicy->timeout)
  f2fs_update_time(sbi, UMOUNT_DISCARD_TIMEOUT);

retry:
 issued = 0;
 for (i = MAX_PLIST_NUM - 1; i >= 0; i--) {
  if (dpolicy->timeout &&
    f2fs_time_over(sbi, UMOUNT_DISCARD_TIMEOUT))
   break;

  if (i + 1 < dpolicy->granularity)
   break;

  if (i + 1 < dcc->max_ordered_discard && dpolicy->ordered) {
   __issue_discard_cmd_orderly(sbi, dpolicy, &issued);
   return issued;
  }

  pend_list = &dcc->pend_list[i];

  mutex_lock(&dcc->cmd_lock);
  if (list_empty(pend_list))
   goto next;
  if (unlikely(dcc->rbtree_check))
   f2fs_bug_on(sbi, !f2fs_check_discard_tree(sbi));
  blk_start_plug(&plug);
  list_for_each_entry_safe(dc, tmp, pend_list, list) {
   f2fs_bug_on(sbi, dc->state != D_PREP);

   if (dpolicy->timeout &&
    f2fs_time_over(sbi, UMOUNT_DISCARD_TIMEOUT))
    break;

   if (dpolicy->io_aware && i < dpolicy->io_aware_gran &&
      !is_idle(sbi, DISCARD_TIME)) {
    io_interrupted = true;
    break;
   }

   __submit_discard_cmd(sbi, dpolicy, dc, &issued);

   if (issued >= dpolicy->max_requests)
    break;
  }
  blk_finish_plug(&plug);
next:
  mutex_unlock(&dcc->cmd_lock);

  if (issued >= dpolicy->max_requests || io_interrupted)
   break;
 }

 if (dpolicy->type == DPOLICY_UMOUNT && issued) {
  __wait_all_discard_cmd(sbi, dpolicy);
  goto retry;
 }

 if (!issued && io_interrupted)
  issued = -1;

 return issued;
}

static bool __drop_discard_cmd(struct f2fs_sb_info *sbi)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct list_head *pend_list;
 struct discard_cmd *dc, *tmp;
 int i;
 bool dropped = false;

 mutex_lock(&dcc->cmd_lock);
 for (i = MAX_PLIST_NUM - 1; i >= 0; i--) {
  pend_list = &dcc->pend_list[i];
  list_for_each_entry_safe(dc, tmp, pend_list, list) {
   f2fs_bug_on(sbi, dc->state != D_PREP);
   __remove_discard_cmd(sbi, dc);
   dropped = true;
  }
 }
 mutex_unlock(&dcc->cmd_lock);

 return dropped;
}

void f2fs_drop_discard_cmd(struct f2fs_sb_info *sbi)
{
 __drop_discard_cmd(sbi);
}

static unsigned int __wait_one_discard_bio(struct f2fs_sb_info *sbi,
       struct discard_cmd *dc)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 unsigned int len = 0;

 wait_for_completion_io(&dc->wait);
 mutex_lock(&dcc->cmd_lock);
 f2fs_bug_on(sbi, dc->state != D_DONE);
 dc->ref--;
 if (!dc->ref) {
  if (!dc->error)
   len = dc->di.len;
  __remove_discard_cmd(sbi, dc);
 }
 mutex_unlock(&dcc->cmd_lock);

 return len;
}

static unsigned int __wait_discard_cmd_range(struct f2fs_sb_info *sbi,
      struct discard_policy *dpolicy,
      block_t start, block_t end)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct list_head *wait_list = (dpolicy->type == DPOLICY_FSTRIM) ?
     &(dcc->fstrim_list) : &(dcc->wait_list);
 struct discard_cmd *dc = NULL, *iter, *tmp;
 unsigned int trimmed = 0;

next:
 dc = NULL;

 mutex_lock(&dcc->cmd_lock);
 list_for_each_entry_safe(iter, tmp, wait_list, list) {
  if (iter->di.lstart + iter->di.len <= start ||
     end <= iter->di.lstart)
   continue;
  if (iter->di.len < dpolicy->granularity)
   continue;
  if (iter->state == D_DONE && !iter->ref) {
   wait_for_completion_io(&iter->wait);
   if (!iter->error)
    trimmed += iter->di.len;
   __remove_discard_cmd(sbi, iter);
  } else {
   iter->ref++;
   dc = iter;
   break;
  }
 }
 mutex_unlock(&dcc->cmd_lock);

 if (dc) {
  trimmed += __wait_one_discard_bio(sbi, dc);
  goto next;
 }

 return trimmed;
}

static unsigned int __wait_all_discard_cmd(struct f2fs_sb_info *sbi,
      struct discard_policy *dpolicy)
{
 struct discard_policy dp;
 unsigned int discard_blks;

 if (dpolicy)
  return __wait_discard_cmd_range(sbi, dpolicy, 0, UINT_MAX);

 /* wait all */
 __init_discard_policy(sbi, &dp, DPOLICY_FSTRIM, MIN_DISCARD_GRANULARITY);
 discard_blks = __wait_discard_cmd_range(sbi, &dp, 0, UINT_MAX);
 __init_discard_policy(sbi, &dp, DPOLICY_UMOUNT, MIN_DISCARD_GRANULARITY);
 discard_blks += __wait_discard_cmd_range(sbi, &dp, 0, UINT_MAX);

 return discard_blks;
}

/* This should be covered by global mutex, &sit_i->sentry_lock */
static void f2fs_wait_discard_bio(struct f2fs_sb_info *sbi, block_t blkaddr)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct discard_cmd *dc;
 bool need_wait = false;

 mutex_lock(&dcc->cmd_lock);
 dc = __lookup_discard_cmd(sbi, blkaddr);
#ifdef CONFIG_BLK_DEV_ZONED
 if (dc && f2fs_sb_has_blkzoned(sbi) && bdev_is_zoned(dc->bdev)) {
  int devi = f2fs_bdev_index(sbi, dc->bdev);

  if (devi < 0) {
   mutex_unlock(&dcc->cmd_lock);
   return;
  }

  if (f2fs_blkz_is_seq(sbi, devi, dc->di.start)) {
   /* force submit zone reset */
   if (dc->state == D_PREP)
    __submit_zone_reset_cmd(sbi, dc, REQ_SYNC,
       &dcc->wait_list, NULL);
   dc->ref++;
   mutex_unlock(&dcc->cmd_lock);
   /* wait zone reset */
   __wait_one_discard_bio(sbi, dc);
   return;
  }
 }
#endif
 if (dc) {
  if (dc->state == D_PREP) {
   __punch_discard_cmd(sbi, dc, blkaddr);
  } else {
   dc->ref++;
   need_wait = true;
  }
 }
 mutex_unlock(&dcc->cmd_lock);

 if (need_wait)
  __wait_one_discard_bio(sbi, dc);
}

void f2fs_stop_discard_thread(struct f2fs_sb_info *sbi)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;

 if (dcc && dcc->f2fs_issue_discard) {
  struct task_struct *discard_thread = dcc->f2fs_issue_discard;

  dcc->f2fs_issue_discard = NULL;
  kthread_stop(discard_thread);
 }
}

/**
 * f2fs_issue_discard_timeout() - Issue all discard cmd within UMOUNT_DISCARD_TIMEOUT
 * @sbi: the f2fs_sb_info data for discard cmd to issue
 *
 * When UMOUNT_DISCARD_TIMEOUT is exceeded, all remaining discard commands will be dropped
 *
 * Return true if issued all discard cmd or no discard cmd need issue, otherwise return false.
 */

bool f2fs_issue_discard_timeout(struct f2fs_sb_info *sbi)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct discard_policy dpolicy;
 bool dropped;

 if (!atomic_read(&dcc->discard_cmd_cnt))
  return true;

 __init_discard_policy(sbi, &dpolicy, DPOLICY_UMOUNT,
     dcc->discard_granularity);
 __issue_discard_cmd(sbi, &dpolicy);
 dropped = __drop_discard_cmd(sbi);

 /* just to make sure there is no pending discard commands */
 __wait_all_discard_cmd(sbi, NULL);

 f2fs_bug_on(sbi, atomic_read(&dcc->discard_cmd_cnt));
 return !dropped;
}

static int issue_discard_thread(void *data)
{
 struct f2fs_sb_info *sbi = data;
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 wait_queue_head_t *q = &dcc->discard_wait_queue;
 struct discard_policy dpolicy;
 unsigned int wait_ms = dcc->min_discard_issue_time;
 int issued;

 set_freezable();

 do {
  wait_event_freezable_timeout(*q,
    kthread_should_stop() || dcc->discard_wake,
    msecs_to_jiffies(wait_ms));

  if (sbi->gc_mode == GC_URGENT_HIGH ||
   !f2fs_available_free_memory(sbi, DISCARD_CACHE))
   __init_discard_policy(sbi, &dpolicy, DPOLICY_FORCE,
      MIN_DISCARD_GRANULARITY);
  else
   __init_discard_policy(sbi, &dpolicy, DPOLICY_BG,
      dcc->discard_granularity);

  if (dcc->discard_wake)
   dcc->discard_wake = false;

  /* clean up pending candidates before going to sleep */
  if (atomic_read(&dcc->queued_discard))
   __wait_all_discard_cmd(sbi, NULL);

  if (f2fs_readonly(sbi->sb))
   continue;
  if (kthread_should_stop())
   return 0;
  if (is_sbi_flag_set(sbi, SBI_NEED_FSCK) ||
   !atomic_read(&dcc->discard_cmd_cnt)) {
   wait_ms = dpolicy.max_interval;
   continue;
  }

  sb_start_intwrite(sbi->sb);

  issued = __issue_discard_cmd(sbi, &dpolicy);
  if (issued > 0) {
   __wait_all_discard_cmd(sbi, &dpolicy);
   wait_ms = dpolicy.min_interval;
  } else if (issued == -1) {
   wait_ms = f2fs_time_to_wait(sbi, DISCARD_TIME);
   if (!wait_ms)
    wait_ms = dpolicy.mid_interval;
  } else {
   wait_ms = dpolicy.max_interval;
  }
  if (!atomic_read(&dcc->discard_cmd_cnt))
   wait_ms = dpolicy.max_interval;

  sb_end_intwrite(sbi->sb);

 } while (!kthread_should_stop());
 return 0;
}

#ifdef CONFIG_BLK_DEV_ZONED
static int __f2fs_issue_discard_zone(struct f2fs_sb_info *sbi,
  struct block_device *bdev, block_t blkstart, block_t blklen)
{
 sector_t sector, nr_sects;
 block_t lblkstart = blkstart;
 int devi = 0;
 u64 remainder = 0;

 if (f2fs_is_multi_device(sbi)) {
  devi = f2fs_target_device_index(sbi, blkstart);
  if (blkstart < FDEV(devi).start_blk ||
      blkstart > FDEV(devi).end_blk) {
   f2fs_err(sbi, "Invalid block %x", blkstart);
   return -EIO;
  }
  blkstart -= FDEV(devi).start_blk;
 }

 /* For sequential zones, reset the zone write pointer */
 if (f2fs_blkz_is_seq(sbi, devi, blkstart)) {
  sector = SECTOR_FROM_BLOCK(blkstart);
  nr_sects = SECTOR_FROM_BLOCK(blklen);
  div64_u64_rem(sector, bdev_zone_sectors(bdev), &remainder);

  if (remainder || nr_sects != bdev_zone_sectors(bdev)) {
   f2fs_err(sbi, "(%d) %s: Unaligned zone reset attempted (block %x + %x)",
     devi, sbi->s_ndevs ? FDEV(devi).path : "",
     blkstart, blklen);
   return -EIO;
  }

  if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING))) {
   unsigned int nofs_flags;
   int ret;

   trace_f2fs_issue_reset_zone(bdev, blkstart);
   nofs_flags = memalloc_nofs_save();
   ret = blkdev_zone_mgmt(bdev, REQ_OP_ZONE_RESET,
      sector, nr_sects);
   memalloc_nofs_restore(nofs_flags);
   return ret;
  }

  __queue_zone_reset_cmd(sbi, bdev, blkstart, lblkstart, blklen);
  return 0;
 }

 /* For conventional zones, use regular discard if supported */
 __queue_discard_cmd(sbi, bdev, lblkstart, blklen);
 return 0;
}
#endif

static int __issue_discard_async(struct f2fs_sb_info *sbi,
  struct block_device *bdev, block_t blkstart, block_t blklen)
{
#ifdef CONFIG_BLK_DEV_ZONED
 if (f2fs_sb_has_blkzoned(sbi) && bdev_is_zoned(bdev))
  return __f2fs_issue_discard_zone(sbi, bdev, blkstart, blklen);
#endif
 __queue_discard_cmd(sbi, bdev, blkstart, blklen);
 return 0;
}

static int f2fs_issue_discard(struct f2fs_sb_info *sbi,
    block_t blkstart, block_t blklen)
{
 sector_t start = blkstart, len = 0;
 struct block_device *bdev;
 struct seg_entry *se;
 unsigned int offset;
 block_t i;
 int err = 0;

 bdev = f2fs_target_device(sbi, blkstart, NULL);

 for (i = blkstart; i < blkstart + blklen; i++, len++) {
  if (i != start) {
   struct block_device *bdev2 =
    f2fs_target_device(sbi, i, NULL);

   if (bdev2 != bdev) {
    err = __issue_discard_async(sbi, bdev,
      start, len);
    if (err)
     return err;
    bdev = bdev2;
    start = i;
    len = 0;
   }
  }

  se = get_seg_entry(sbi, GET_SEGNO(sbi, i));
  offset = GET_BLKOFF_FROM_SEG0(sbi, i);

  if (f2fs_block_unit_discard(sbi) &&
    !f2fs_test_and_set_bit(offset, se->discard_map))
   sbi->discard_blks--;
 }

 if (len)
  err = __issue_discard_async(sbi, bdev, start, len);
 return err;
}

static bool add_discard_addrs(struct f2fs_sb_info *sbi, struct cp_control *cpc,
       bool check_only)
{
 int entries = SIT_VBLOCK_MAP_SIZE / sizeof(unsigned long);
 struct seg_entry *se = get_seg_entry(sbi, cpc->trim_start);
 unsigned long *cur_map = (unsigned long *)se->cur_valid_map;
 unsigned long *ckpt_map = (unsigned long *)se->ckpt_valid_map;
 unsigned long *discard_map = (unsigned long *)se->discard_map;
 unsigned long *dmap = SIT_I(sbi)->tmp_map;
 unsigned int start = 0, end = -1;
 bool force = (cpc->reason & CP_DISCARD);
 struct discard_entry *de = NULL;
 struct list_head *head = &SM_I(sbi)->dcc_info->entry_list;
 int i;

 if (se->valid_blocks == BLKS_PER_SEG(sbi) ||
     !f2fs_hw_support_discard(sbi) ||
     !f2fs_block_unit_discard(sbi))
  return false;

 if (!force) {
  if (!f2fs_realtime_discard_enable(sbi) ||
   (!se->valid_blocks &&
    !is_curseg(sbi, cpc->trim_start)) ||
   SM_I(sbi)->dcc_info->nr_discards >=
    SM_I(sbi)->dcc_info->max_discards)
   return false;
 }

 /* SIT_VBLOCK_MAP_SIZE should be multiple of sizeof(unsigned long) */
 for (i = 0; i < entries; i++)
  dmap[i] = force ? ~ckpt_map[i] & ~discard_map[i] :
    (cur_map[i] ^ ckpt_map[i]) & ckpt_map[i];

 while (force || SM_I(sbi)->dcc_info->nr_discards <=
    SM_I(sbi)->dcc_info->max_discards) {
  start = __find_rev_next_bit(dmap, BLKS_PER_SEG(sbi), end + 1);
  if (start >= BLKS_PER_SEG(sbi))
   break;

  end = __find_rev_next_zero_bit(dmap,
      BLKS_PER_SEG(sbi), start + 1);
  if (force && start && end != BLKS_PER_SEG(sbi) &&
      (end - start) < cpc->trim_minlen)
   continue;

  if (check_only)
   return true;

  if (!de) {
   de = f2fs_kmem_cache_alloc(discard_entry_slab,
      GFP_F2FS_ZERO, true, NULL);
   de->start_blkaddr = START_BLOCK(sbi, cpc->trim_start);
   list_add_tail(&de->list, head);
  }

  for (i = start; i < end; i++)
   __set_bit_le(i, (void *)de->discard_map);

  SM_I(sbi)->dcc_info->nr_discards += end - start;
 }
 return false;
}

static void release_discard_addr(struct discard_entry *entry)
{
 list_del(&entry->list);
 kmem_cache_free(discard_entry_slab, entry);
}

void f2fs_release_discard_addrs(struct f2fs_sb_info *sbi)
{
 struct list_head *head = &(SM_I(sbi)->dcc_info->entry_list);
 struct discard_entry *entry, *this;

 /* drop caches */
 list_for_each_entry_safe(entry, this, head, list)
  release_discard_addr(entry);
}

/*
 * Should call f2fs_clear_prefree_segments after checkpoint is done.
 */

static void set_prefree_as_free_segments(struct f2fs_sb_info *sbi)
{
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 unsigned int segno;

 mutex_lock(&dirty_i->seglist_lock);
 for_each_set_bit(segno, dirty_i->dirty_segmap[PRE], MAIN_SEGS(sbi))
  __set_test_and_free(sbi, segno, false);
 mutex_unlock(&dirty_i->seglist_lock);
}

void f2fs_clear_prefree_segments(struct f2fs_sb_info *sbi,
      struct cp_control *cpc)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 struct list_head *head = &dcc->entry_list;
 struct discard_entry *entry, *this;
 struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
 unsigned long *prefree_map = dirty_i->dirty_segmap[PRE];
 unsigned int start = 0, end = -1;
 unsigned int secno, start_segno;
 bool force = (cpc->reason & CP_DISCARD);
 bool section_alignment = F2FS_OPTION(sbi).discard_unit ==
      DISCARD_UNIT_SECTION;

 if (f2fs_lfs_mode(sbi) && __is_large_section(sbi))
  section_alignment = true;

 mutex_lock(&dirty_i->seglist_lock);

 while (1) {
  int i;

  if (section_alignment && end != -1)
   end--;
  start = find_next_bit(prefree_map, MAIN_SEGS(sbi), end + 1);
  if (start >= MAIN_SEGS(sbi))
   break;
  end = find_next_zero_bit(prefree_map, MAIN_SEGS(sbi),
        start + 1);

  if (section_alignment) {
   start = rounddown(start, SEGS_PER_SEC(sbi));
   end = roundup(end, SEGS_PER_SEC(sbi));
  }

  for (i = start; i < end; i++) {
   if (test_and_clear_bit(i, prefree_map))
    dirty_i->nr_dirty[PRE]--;
  }

  if (!f2fs_realtime_discard_enable(sbi))
   continue;

  if (force && start >= cpc->trim_start &&
     (end - 1) <= cpc->trim_end)
   continue;

  /* Should cover 2MB zoned device for zone-based reset */
  if (!f2fs_sb_has_blkzoned(sbi) &&
      (!f2fs_lfs_mode(sbi) || !__is_large_section(sbi))) {
   f2fs_issue_discard(sbi, START_BLOCK(sbi, start),
    SEGS_TO_BLKS(sbi, end - start));
   continue;
  }
next:
  secno = GET_SEC_FROM_SEG(sbi, start);
  start_segno = GET_SEG_FROM_SEC(sbi, secno);
  if (!is_cursec(sbi, secno) &&
   !get_valid_blocks(sbi, start, true))
   f2fs_issue_discard(sbi, START_BLOCK(sbi, start_segno),
      BLKS_PER_SEC(sbi));

  start = start_segno + SEGS_PER_SEC(sbi);
  if (start < end)
   goto next;
  else
   end = start - 1;
 }
 mutex_unlock(&dirty_i->seglist_lock);

 if (!f2fs_block_unit_discard(sbi))
  goto wakeup;

 /* send small discards */
 list_for_each_entry_safe(entry, this, head, list) {
  unsigned int cur_pos = 0, next_pos, len, total_len = 0;
  bool is_valid = test_bit_le(0, entry->discard_map);

find_next:
  if (is_valid) {
   next_pos = find_next_zero_bit_le(entry->discard_map,
      BLKS_PER_SEG(sbi), cur_pos);
   len = next_pos - cur_pos;

   if (f2fs_sb_has_blkzoned(sbi) ||
       (force && len < cpc->trim_minlen))
    goto skip;

   f2fs_issue_discard(sbi, entry->start_blkaddr + cur_pos,
         len);
   total_len += len;
  } else {
   next_pos = find_next_bit_le(entry->discard_map,
      BLKS_PER_SEG(sbi), cur_pos);
  }
skip:
  cur_pos = next_pos;
  is_valid = !is_valid;

  if (cur_pos < BLKS_PER_SEG(sbi))
   goto find_next;

  release_discard_addr(entry);
  dcc->nr_discards -= total_len;
 }

wakeup:
 wake_up_discard_thread(sbi, false);
}

int f2fs_start_discard_thread(struct f2fs_sb_info *sbi)
{
 dev_t dev = sbi->sb->s_bdev->bd_dev;
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
 int err = 0;

 if (f2fs_sb_has_readonly(sbi)) {
  f2fs_info(sbi,
   "Skip to start discard thread for readonly image");
  return 0;
 }

 if (!f2fs_realtime_discard_enable(sbi))
  return 0;

 dcc->f2fs_issue_discard = kthread_run(issue_discard_thread, sbi,
    "f2fs_discard-%u:%u", MAJOR(dev), MINOR(dev));
 if (IS_ERR(dcc->f2fs_issue_discard)) {
  err = PTR_ERR(dcc->f2fs_issue_discard);
  dcc->f2fs_issue_discard = NULL;
 }

 return err;
}

static int create_discard_cmd_control(struct f2fs_sb_info *sbi)
{
 struct discard_cmd_control *dcc;
 int err = 0, i;

 if (SM_I(sbi)->dcc_info) {
  dcc = SM_I(sbi)->dcc_info;
  goto init_thread;
 }

 dcc = f2fs_kzalloc(sbi, sizeof(struct discard_cmd_control), GFP_KERNEL);
 if (!dcc)
  return -ENOMEM;

 dcc->discard_io_aware_gran = MAX_PLIST_NUM;
 dcc->discard_granularity = DEFAULT_DISCARD_GRANULARITY;
 dcc->max_ordered_discard = DEFAULT_MAX_ORDERED_DISCARD_GRANULARITY;
 dcc->discard_io_aware = DPOLICY_IO_AWARE_ENABLE;
 if (F2FS_OPTION(sbi).discard_unit == DISCARD_UNIT_SEGMENT ||
  F2FS_OPTION(sbi).discard_unit == DISCARD_UNIT_SECTION)
  dcc->discard_granularity = BLKS_PER_SEG(sbi);

 INIT_LIST_HEAD(&dcc->entry_list);
 for (i = 0; i < MAX_PLIST_NUM; i++)
  INIT_LIST_HEAD(&dcc->pend_list[i]);
 INIT_LIST_HEAD(&dcc->wait_list);
 INIT_LIST_HEAD(&dcc->fstrim_list);
 mutex_init(&dcc->cmd_lock);
 atomic_set(&dcc->issued_discard, 0);
 atomic_set(&dcc->queued_discard, 0);
 atomic_set(&dcc->discard_cmd_cnt, 0);
 dcc->nr_discards = 0;
 dcc->max_discards = SEGS_TO_BLKS(sbi, MAIN_SEGS(sbi));
 dcc->max_discard_request = DEF_MAX_DISCARD_REQUEST;
 dcc->min_discard_issue_time = DEF_MIN_DISCARD_ISSUE_TIME;
 dcc->mid_discard_issue_time = DEF_MID_DISCARD_ISSUE_TIME;
 dcc->max_discard_issue_time = DEF_MAX_DISCARD_ISSUE_TIME;
 dcc->discard_urgent_util = DEF_DISCARD_URGENT_UTIL;
 dcc->undiscard_blks = 0;
 dcc->next_pos = 0;
 dcc->root = RB_ROOT_CACHED;
 dcc->rbtree_check = false;

 init_waitqueue_head(&dcc->discard_wait_queue);
 SM_I(sbi)->dcc_info = dcc;
init_thread:
 err = f2fs_start_discard_thread(sbi);
 if (err) {
  kfree(dcc);
  SM_I(sbi)->dcc_info = NULL;
 }

 return err;
}

static void destroy_discard_cmd_control(struct f2fs_sb_info *sbi)
{
 struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;

 if (!dcc)
  return;

 f2fs_stop_discard_thread(sbi);

 /*
 * Recovery can cache discard commands, so in error path of
 * fill_super(), it needs to give a chance to handle them.
 */

 f2fs_issue_discard_timeout(sbi);

 kfree(dcc);
 SM_I(sbi)->dcc_info = NULL;
}

static bool __mark_sit_entry_dirty(struct f2fs_sb_info *sbi, unsigned int segno)
{
 struct sit_info *sit_i = SIT_I(sbi);

 if (!__test_and_set_bit(segno, sit_i->dirty_sentries_bitmap)) {
  sit_i->dirty_sentries++;
  return false;
 }

 return true;
}

static void __set_sit_entry_type(struct f2fs_sb_info *sbi, int type,
     unsigned int segno, int modified)
{
 struct seg_entry *se = get_seg_entry(sbi, segno);

 se->type = type;
 if (modified)
  __mark_sit_entry_dirty(sbi, segno);
}

static inline unsigned long long get_segment_mtime(struct f2fs_sb_info *sbi,
        block_t blkaddr)
{
 unsigned int segno = GET_SEGNO(sbi, blkaddr);

 if (segno == NULL_SEGNO)
  return 0;
 return get_seg_entry(sbi, segno)->mtime;
}

static void update_segment_mtime(struct f2fs_sb_info *sbi, block_t blkaddr,
      unsigned long long old_mtime)
{
 struct seg_entry *se;
 unsigned int segno = GET_SEGNO(sbi, blkaddr);
 unsigned long long ctime = get_mtime(sbi, false);
 unsigned long long mtime = old_mtime ? old_mtime : ctime;

 if (segno == NULL_SEGNO)
  return;

 se = get_seg_entry(sbi, segno);

 if (!se->mtime)
  se->mtime = mtime;
 else
  se->mtime = div_u64(se->mtime * se->valid_blocks + mtime,
      se->valid_blocks + 1);

 if (ctime > SIT_I(sbi)->max_mtime)
  SIT_I(sbi)->max_mtime = ctime;
}

/*
 * NOTE: when updating multiple blocks at the same time, please ensure
 * that the consecutive input blocks belong to the same segment.
 */

static int update_sit_entry_for_release(struct f2fs_sb_info *sbi, struct seg_entry *se,
    unsigned int segno, block_t blkaddr, unsigned int offset, int del)
{
 bool exist;
#ifdef CONFIG_F2FS_CHECK_FS
 bool mir_exist;
#endif
 int i;
 int del_count = -del;

 f2fs_bug_on(sbi, GET_SEGNO(sbi, blkaddr) != GET_SEGNO(sbi, blkaddr + del_count - 1));

 for (i = 0; i < del_count; i++) {
  exist = f2fs_test_and_clear_bit(offset + i, se->cur_valid_map);
#ifdef CONFIG_F2FS_CHECK_FS
  mir_exist = f2fs_test_and_clear_bit(offset + i,
      se->cur_valid_map_mir);
  if (unlikely(exist != mir_exist)) {
   f2fs_err(sbi, "Inconsistent error when clearing bitmap, blk:%u, old bit:%d",
    blkaddr + i, exist);
   f2fs_bug_on(sbi, 1);
  }
#endif
  if (unlikely(!exist)) {
   f2fs_err(sbi, "Bitmap was wrongly cleared, blk:%u", blkaddr + i);
   f2fs_bug_on(sbi, 1);
   se->valid_blocks++;
   del += 1;
  } else if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) {
   /*
 * If checkpoints are off, we must not reuse data that
 * was used in the previous checkpoint. If it was used
 * before, we must track that to know how much space we
 * really have.
 */

   if (f2fs_test_bit(offset + i, se->ckpt_valid_map)) {
    spin_lock(&sbi->stat_lock);
    sbi->unusable_block_count++;
    spin_unlock(&sbi->stat_lock);
   }
  }

  if (f2fs_block_unit_discard(sbi) &&
    f2fs_test_and_clear_bit(offset + i, se->discard_map))
   sbi->discard_blks++;

  if (!f2fs_test_bit(offset + i, se->ckpt_valid_map)) {
   se->ckpt_valid_blocks -= 1;
   if (__is_large_section(sbi))
    get_sec_entry(sbi, segno)->ckpt_valid_blocks -= 1;
  }
 }

 if (__is_large_section(sbi))
  sanity_check_valid_blocks(sbi, segno);

 return del;
}

static int update_sit_entry_for_alloc(struct f2fs_sb_info *sbi, struct seg_entry *se,
    unsigned int segno, block_t blkaddr, unsigned int offset, int del)
{
 bool exist;
#ifdef CONFIG_F2FS_CHECK_FS
 bool mir_exist;
#endif

 exist = f2fs_test_and_set_bit(offset, se->cur_valid_map);
#ifdef CONFIG_F2FS_CHECK_FS
 mir_exist = f2fs_test_and_set_bit(offset,
     se->cur_valid_map_mir);
 if (unlikely(exist != mir_exist)) {
  f2fs_err(sbi, "Inconsistent error when setting bitmap, blk:%u, old bit:%d",
   blkaddr, exist);
  f2fs_bug_on(sbi, 1);
 }
#endif
 if (unlikely(exist)) {
  f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr);
  f2fs_bug_on(sbi, 1);
  se->valid_blocks--;
  del = 0;
 }

 if (f2fs_block_unit_discard(sbi) &&
   !f2fs_test_and_set_bit(offset, se->discard_map))
  sbi->discard_blks--;

 /*
 * SSR should never reuse block which is checkpointed
 * or newly invalidated.
 */

 if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) {
  if (!f2fs_test_and_set_bit(offset, se->ckpt_valid_map)) {
   se->ckpt_valid_blocks++;
   if (__is_large_section(sbi))
    get_sec_entry(sbi, segno)->ckpt_valid_blocks++;
  }
 }

 if (!f2fs_test_bit(offset, se->ckpt_valid_map)) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=88 G=91

¤ Dauer der Verarbeitung: 0.30 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge