// SPDX-License-Identifier: GPL-2.0 #include #include #include "ctree.h" #include "volumes.h" #include "zoned.h" #include "rcu-string.h" /* Maximum number of zones to report per blkdev_report_zones() call */ #define BTRFS_REPORT_NR_ZONES 4096 static int copy_zone_info_cb(struct blk_zone *zone, unsigned int idx, void *data) { struct blk_zone *zones = data; memcpy(&zones[idx], zone, sizeof(*zone)); return 0; } static int btrfs_get_dev_zones(struct btrfs_device *device, u64 pos, struct blk_zone *zones, unsigned int *nr_zones) { int ret; if (!*nr_zones) return 0; ret = blkdev_report_zones(device->bdev, pos >> SECTOR_SHIFT, *nr_zones, copy_zone_info_cb, zones); if (ret < 0) { btrfs_err_in_rcu(device->fs_info, "zoned: failed to read zone %llu on %s (devid %llu)", pos, rcu_str_deref(device->name), device->devid); return ret; } *nr_zones = ret; if (!ret) return -EIO; return 0; } int btrfs_get_dev_zone_info(struct btrfs_device *device) { struct btrfs_zoned_device_info *zone_info = NULL; struct block_device *bdev = device->bdev; sector_t nr_sectors; sector_t sector = 0; struct blk_zone *zones = NULL; unsigned int i, nreported = 0, nr_zones; unsigned int zone_sectors; int ret; if (!bdev_is_zoned(bdev)) return 0; if (device->zone_info) return 0; zone_info = kzalloc(sizeof(*zone_info), GFP_KERNEL); if (!zone_info) return -ENOMEM; nr_sectors = bdev->bd_part->nr_sects; zone_sectors = bdev_zone_sectors(bdev); /* Check if it's power of 2 (see is_power_of_2) */ ASSERT(zone_sectors != 0 && (zone_sectors & (zone_sectors - 1)) == 0); zone_info->zone_size = zone_sectors << SECTOR_SHIFT; zone_info->zone_size_shift = ilog2(zone_info->zone_size); zone_info->nr_zones = nr_sectors >> ilog2(zone_sectors); if (!IS_ALIGNED(nr_sectors, zone_sectors)) zone_info->nr_zones++; zone_info->seq_zones = bitmap_zalloc(zone_info->nr_zones, GFP_KERNEL); if (!zone_info->seq_zones) { ret = -ENOMEM; goto out; } zone_info->empty_zones = bitmap_zalloc(zone_info->nr_zones, GFP_KERNEL); if (!zone_info->empty_zones) { ret = -ENOMEM; goto out; } zones = kcalloc(BTRFS_REPORT_NR_ZONES, sizeof(struct blk_zone), GFP_KERNEL); if (!zones) { ret = -ENOMEM; goto out; } /* Get zones type */ while (sector < nr_sectors) { nr_zones = BTRFS_REPORT_NR_ZONES; ret = btrfs_get_dev_zones(device, sector << SECTOR_SHIFT, zones, &nr_zones); if (ret) goto out; for (i = 0; i < nr_zones; i++) { if (zones[i].type == BLK_ZONE_TYPE_SEQWRITE_REQ) __set_bit(nreported, zone_info->seq_zones); if (zones[i].cond == BLK_ZONE_COND_EMPTY) __set_bit(nreported, zone_info->empty_zones); nreported++; } sector = zones[nr_zones - 1].start + zones[nr_zones - 1].len; } if (nreported != zone_info->nr_zones) { btrfs_err_in_rcu(device->fs_info, "inconsistent number of zones on %s (%u/%u)", rcu_str_deref(device->name), nreported, zone_info->nr_zones); ret = -EIO; goto out; } kfree(zones); device->zone_info = zone_info; /* device->fs_info is not safe to use for printing messages */ btrfs_info_in_rcu(NULL, "host-%s zoned block device %s, %u zones of %llu bytes", bdev_zoned_model(bdev) == BLK_ZONED_HM ? "managed" : "aware", rcu_str_deref(device->name), zone_info->nr_zones, zone_info->zone_size); return 0; out: kfree(zones); bitmap_free(zone_info->empty_zones); bitmap_free(zone_info->seq_zones); kfree(zone_info); return ret; } void btrfs_destroy_dev_zone_info(struct btrfs_device *device) { struct btrfs_zoned_device_info *zone_info = device->zone_info; if (!zone_info) return; bitmap_free(zone_info->seq_zones); bitmap_free(zone_info->empty_zones); kfree(zone_info); device->zone_info = NULL; } int btrfs_get_dev_zone(struct btrfs_device *device, u64 pos, struct blk_zone *zone) { unsigned int nr_zones = 1; int ret; ret = btrfs_get_dev_zones(device, pos, zone, &nr_zones); if (ret != 0 || !nr_zones) return ret ? ret : -EIO; return 0; } int btrfs_check_zoned_mode(struct btrfs_fs_info *fs_info) { struct btrfs_fs_devices *fs_devices = fs_info->fs_devices; struct btrfs_device *device; u64 zoned_devices = 0; u64 nr_devices = 0; u64 zone_size = 0; const bool incompat_zoned = btrfs_is_zoned(fs_info); int ret = 0; /* Count zoned devices */ list_for_each_entry(device, &fs_devices->devices, dev_list) { enum blk_zoned_model model; if (!device->bdev) continue; model = bdev_zoned_model(device->bdev); if (model == BLK_ZONED_HM || (model == BLK_ZONED_HA && incompat_zoned)) { zoned_devices++; if (!zone_size) { zone_size = device->zone_info->zone_size; } else if (device->zone_info->zone_size != zone_size) { btrfs_err(fs_info, "zoned: unequal block device zone sizes: have %llu found %llu", device->zone_info->zone_size, zone_size); ret = -EINVAL; goto out; } } nr_devices++; } if (!zoned_devices && !incompat_zoned) goto out; if (!zoned_devices && incompat_zoned) { /* No zoned block device found on ZONED filesystem */ btrfs_err(fs_info, "zoned: no zoned devices found on a zoned filesystem"); ret = -EINVAL; goto out; } if (zoned_devices && !incompat_zoned) { btrfs_err(fs_info, "zoned: mode not enabled but zoned device found"); ret = -EINVAL; goto out; } if (zoned_devices != nr_devices) { btrfs_err(fs_info, "zoned: cannot mix zoned and regular devices"); ret = -EINVAL; goto out; } /* * stripe_size is always aligned to BTRFS_STRIPE_LEN in * __btrfs_alloc_chunk(). Since we want stripe_len == zone_size, * check the alignment here. */ if (!IS_ALIGNED(zone_size, BTRFS_STRIPE_LEN)) { btrfs_err(fs_info, "zoned: zone size %llu not aligned to stripe %u", zone_size, BTRFS_STRIPE_LEN); ret = -EINVAL; goto out; } fs_info->zone_size = zone_size; btrfs_info(fs_info, "zoned mode enabled with zone size %llu", zone_size); out: return ret; }