Merge tag 'kvm-5.6-2' of git://git.kernel.org/pub/scm/virt/kvm/kvm
[linux-2.6-microblaze.git] / fs / f2fs / segment.c
index 56e8144..cf0eb00 100644 (file)
@@ -334,7 +334,6 @@ void f2fs_drop_inmem_pages(struct inode *inode)
        }
 
        fi->i_gc_failures[GC_FAILURE_ATOMIC] = 0;
-       stat_dec_atomic_write(inode);
 
        spin_lock(&sbi->inode_lock[ATOMIC_FILE]);
        if (!list_empty(&fi->inmem_ilist))
@@ -505,7 +504,7 @@ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need)
         * dir/node pages without enough free segments.
         */
        if (has_not_enough_free_secs(sbi, 0, 0)) {
-               mutex_lock(&sbi->gc_mutex);
+               down_write(&sbi->gc_lock);
                f2fs_gc(sbi, false, false, NULL_SEGNO);
        }
 }
@@ -2225,7 +2224,7 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr)
        struct sit_info *sit_i = SIT_I(sbi);
 
        f2fs_bug_on(sbi, addr == NULL_ADDR);
-       if (addr == NEW_ADDR)
+       if (addr == NEW_ADDR || addr == COMPRESS_ADDR)
                return;
 
        invalidate_mapping_pages(META_MAPPING(sbi), addr, addr);
@@ -2861,9 +2860,9 @@ int f2fs_trim_fs(struct f2fs_sb_info *sbi, struct fstrim_range *range)
        if (sbi->discard_blks == 0)
                goto out;
 
-       mutex_lock(&sbi->gc_mutex);
+       down_write(&sbi->gc_lock);
        err = f2fs_write_checkpoint(sbi, &cpc);
-       mutex_unlock(&sbi->gc_mutex);
+       up_write(&sbi->gc_lock);
        if (err)
                goto out;
 
@@ -3036,7 +3035,8 @@ static int __get_segment_type_6(struct f2fs_io_info *fio)
        if (fio->type == DATA) {
                struct inode *inode = fio->page->mapping->host;
 
-               if (is_cold_data(fio->page) || file_is_cold(inode))
+               if (is_cold_data(fio->page) || file_is_cold(inode) ||
+                               f2fs_compressed_file(inode))
                        return CURSEG_COLD_DATA;
                if (file_is_hot(inode) ||
                                is_inode_flag_set(inode, FI_HOT_DATA) ||
@@ -3289,7 +3289,7 @@ int f2fs_inplace_write_data(struct f2fs_io_info *fio)
 
        stat_inc_inplace_blocks(fio->sbi);
 
-       if (fio->bio)
+       if (fio->bio && !(SM_I(sbi)->ipu_policy & (1 << F2FS_IPU_NOCACHE)))
                err = f2fs_merge_page_bio(fio);
        else
                err = f2fs_submit_page_bio(fio);
@@ -4368,6 +4368,263 @@ out:
        return 0;
 }
 
+#ifdef CONFIG_BLK_DEV_ZONED
+
+static int check_zone_write_pointer(struct f2fs_sb_info *sbi,
+                                   struct f2fs_dev_info *fdev,
+                                   struct blk_zone *zone)
+{
+       unsigned int wp_segno, wp_blkoff, zone_secno, zone_segno, segno;
+       block_t zone_block, wp_block, last_valid_block;
+       unsigned int log_sectors_per_block = sbi->log_blocksize - SECTOR_SHIFT;
+       int i, s, b, ret;
+       struct seg_entry *se;
+
+       if (zone->type != BLK_ZONE_TYPE_SEQWRITE_REQ)
+               return 0;
+
+       wp_block = fdev->start_blk + (zone->wp >> log_sectors_per_block);
+       wp_segno = GET_SEGNO(sbi, wp_block);
+       wp_blkoff = wp_block - START_BLOCK(sbi, wp_segno);
+       zone_block = fdev->start_blk + (zone->start >> log_sectors_per_block);
+       zone_segno = GET_SEGNO(sbi, zone_block);
+       zone_secno = GET_SEC_FROM_SEG(sbi, zone_segno);
+
+       if (zone_segno >= MAIN_SEGS(sbi))
+               return 0;
+
+       /*
+        * Skip check of zones cursegs point to, since
+        * fix_curseg_write_pointer() checks them.
+        */
+       for (i = 0; i < NO_CHECK_TYPE; i++)
+               if (zone_secno == GET_SEC_FROM_SEG(sbi,
+                                                  CURSEG_I(sbi, i)->segno))
+                       return 0;
+
+       /*
+        * Get last valid block of the zone.
+        */
+       last_valid_block = zone_block - 1;
+       for (s = sbi->segs_per_sec - 1; s >= 0; s--) {
+               segno = zone_segno + s;
+               se = get_seg_entry(sbi, segno);
+               for (b = sbi->blocks_per_seg - 1; b >= 0; b--)
+                       if (f2fs_test_bit(b, se->cur_valid_map)) {
+                               last_valid_block = START_BLOCK(sbi, segno) + b;
+                               break;
+                       }
+               if (last_valid_block >= zone_block)
+                       break;
+       }
+
+       /*
+        * If last valid block is beyond the write pointer, report the
+        * inconsistency. This inconsistency does not cause write error
+        * because the zone will not be selected for write operation until
+        * it get discarded. Just report it.
+        */
+       if (last_valid_block >= wp_block) {
+               f2fs_notice(sbi, "Valid block beyond write pointer: "
+                           "valid block[0x%x,0x%x] wp[0x%x,0x%x]",
+                           GET_SEGNO(sbi, last_valid_block),
+                           GET_BLKOFF_FROM_SEG0(sbi, last_valid_block),
+                           wp_segno, wp_blkoff);
+               return 0;
+       }
+
+       /*
+        * If there is no valid block in the zone and if write pointer is
+        * not at zone start, reset the write pointer.
+        */
+       if (last_valid_block + 1 == zone_block && zone->wp != zone->start) {
+               f2fs_notice(sbi,
+                           "Zone without valid block has non-zero write "
+                           "pointer. Reset the write pointer: wp[0x%x,0x%x]",
+                           wp_segno, wp_blkoff);
+               ret = __f2fs_issue_discard_zone(sbi, fdev->bdev, zone_block,
+                                       zone->len >> log_sectors_per_block);
+               if (ret) {
+                       f2fs_err(sbi, "Discard zone failed: %s (errno=%d)",
+                                fdev->path, ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static struct f2fs_dev_info *get_target_zoned_dev(struct f2fs_sb_info *sbi,
+                                                 block_t zone_blkaddr)
+{
+       int i;
+
+       for (i = 0; i < sbi->s_ndevs; i++) {
+               if (!bdev_is_zoned(FDEV(i).bdev))
+                       continue;
+               if (sbi->s_ndevs == 1 || (FDEV(i).start_blk <= zone_blkaddr &&
+                               zone_blkaddr <= FDEV(i).end_blk))
+                       return &FDEV(i);
+       }
+
+       return NULL;
+}
+
+static int report_one_zone_cb(struct blk_zone *zone, unsigned int idx,
+                             void *data) {
+       memcpy(data, zone, sizeof(struct blk_zone));
+       return 0;
+}
+
+static int fix_curseg_write_pointer(struct f2fs_sb_info *sbi, int type)
+{
+       struct curseg_info *cs = CURSEG_I(sbi, type);
+       struct f2fs_dev_info *zbd;
+       struct blk_zone zone;
+       unsigned int cs_section, wp_segno, wp_blkoff, wp_sector_off;
+       block_t cs_zone_block, wp_block;
+       unsigned int log_sectors_per_block = sbi->log_blocksize - SECTOR_SHIFT;
+       sector_t zone_sector;
+       int err;
+
+       cs_section = GET_SEC_FROM_SEG(sbi, cs->segno);
+       cs_zone_block = START_BLOCK(sbi, GET_SEG_FROM_SEC(sbi, cs_section));
+
+       zbd = get_target_zoned_dev(sbi, cs_zone_block);
+       if (!zbd)
+               return 0;
+
+       /* report zone for the sector the curseg points to */
+       zone_sector = (sector_t)(cs_zone_block - zbd->start_blk)
+               << log_sectors_per_block;
+       err = blkdev_report_zones(zbd->bdev, zone_sector, 1,
+                                 report_one_zone_cb, &zone);
+       if (err != 1) {
+               f2fs_err(sbi, "Report zone failed: %s errno=(%d)",
+                        zbd->path, err);
+               return err;
+       }
+
+       if (zone.type != BLK_ZONE_TYPE_SEQWRITE_REQ)
+               return 0;
+
+       wp_block = zbd->start_blk + (zone.wp >> log_sectors_per_block);
+       wp_segno = GET_SEGNO(sbi, wp_block);
+       wp_blkoff = wp_block - START_BLOCK(sbi, wp_segno);
+       wp_sector_off = zone.wp & GENMASK(log_sectors_per_block - 1, 0);
+
+       if (cs->segno == wp_segno && cs->next_blkoff == wp_blkoff &&
+               wp_sector_off == 0)
+               return 0;
+
+       f2fs_notice(sbi, "Unaligned curseg[%d] with write pointer: "
+                   "curseg[0x%x,0x%x] wp[0x%x,0x%x]",
+                   type, cs->segno, cs->next_blkoff, wp_segno, wp_blkoff);
+
+       f2fs_notice(sbi, "Assign new section to curseg[%d]: "
+                   "curseg[0x%x,0x%x]", type, cs->segno, cs->next_blkoff);
+       allocate_segment_by_default(sbi, type, true);
+
+       /* check consistency of the zone curseg pointed to */
+       if (check_zone_write_pointer(sbi, zbd, &zone))
+               return -EIO;
+
+       /* check newly assigned zone */
+       cs_section = GET_SEC_FROM_SEG(sbi, cs->segno);
+       cs_zone_block = START_BLOCK(sbi, GET_SEG_FROM_SEC(sbi, cs_section));
+
+       zbd = get_target_zoned_dev(sbi, cs_zone_block);
+       if (!zbd)
+               return 0;
+
+       zone_sector = (sector_t)(cs_zone_block - zbd->start_blk)
+               << log_sectors_per_block;
+       err = blkdev_report_zones(zbd->bdev, zone_sector, 1,
+                                 report_one_zone_cb, &zone);
+       if (err != 1) {
+               f2fs_err(sbi, "Report zone failed: %s errno=(%d)",
+                        zbd->path, err);
+               return err;
+       }
+
+       if (zone.type != BLK_ZONE_TYPE_SEQWRITE_REQ)
+               return 0;
+
+       if (zone.wp != zone.start) {
+               f2fs_notice(sbi,
+                           "New zone for curseg[%d] is not yet discarded. "
+                           "Reset the zone: curseg[0x%x,0x%x]",
+                           type, cs->segno, cs->next_blkoff);
+               err = __f2fs_issue_discard_zone(sbi, zbd->bdev,
+                               zone_sector >> log_sectors_per_block,
+                               zone.len >> log_sectors_per_block);
+               if (err) {
+                       f2fs_err(sbi, "Discard zone failed: %s (errno=%d)",
+                                zbd->path, err);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
+int f2fs_fix_curseg_write_pointer(struct f2fs_sb_info *sbi)
+{
+       int i, ret;
+
+       for (i = 0; i < NO_CHECK_TYPE; i++) {
+               ret = fix_curseg_write_pointer(sbi, i);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+struct check_zone_write_pointer_args {
+       struct f2fs_sb_info *sbi;
+       struct f2fs_dev_info *fdev;
+};
+
+static int check_zone_write_pointer_cb(struct blk_zone *zone, unsigned int idx,
+                                     void *data) {
+       struct check_zone_write_pointer_args *args;
+       args = (struct check_zone_write_pointer_args *)data;
+
+       return check_zone_write_pointer(args->sbi, args->fdev, zone);
+}
+
+int f2fs_check_write_pointer(struct f2fs_sb_info *sbi)
+{
+       int i, ret;
+       struct check_zone_write_pointer_args args;
+
+       for (i = 0; i < sbi->s_ndevs; i++) {
+               if (!bdev_is_zoned(FDEV(i).bdev))
+                       continue;
+
+               args.sbi = sbi;
+               args.fdev = &FDEV(i);
+               ret = blkdev_report_zones(FDEV(i).bdev, 0, BLK_ALL_ZONES,
+                                         check_zone_write_pointer_cb, &args);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+#else
+int f2fs_fix_curseg_write_pointer(struct f2fs_sb_info *sbi)
+{
+       return 0;
+}
+
+int f2fs_check_write_pointer(struct f2fs_sb_info *sbi)
+{
+       return 0;
+}
+#endif
+
 /*
  * Update min, max modified time for cost-benefit GC algorithm
  */