Skip to content

Commit 73a74af

Browse files
damien-lemoalaxboe
authored andcommitted
dm: Improve zone resource limits handling
The generic stacking of limits implemented in the block layer cannot correctly handle stacking of zone resource limits (max open zones and max active zones) because these limits are for an entire device but the stacking may be for a portion of that device (e.g. a dm-linear target that does not cover an entire block device). As a result, when DM devices are created on top of zoned block devices, the DM device never has any zone resource limits advertized, which is only correct if all underlying target devices also have no zone resource limits. If at least one target device has resource limits, the user may see either performance issues (if the max open zone limit of the device is exceeded) or write I/O errors if the max active zone limit of one of the underlying target devices is exceeded. While it is very difficult to correctly and reliably stack zone resource limits in general, cases where targets are not sharing zone resources of the same device can be dealt with relatively easily. Such situation happens when a target maps all sequential zones of a zoned block device: for such mapping, other targets mapping other parts of the same zoned block device can only contain conventional zones and thus will not require any zone resource to correctly handle write operations. For a mapped device constructed with such targets, which includes mapped devices constructed with targets mapping entire zoned block devices, the zone resource limits can be reliably determined using the non-zero minimum of the zone resource limits of all targets. For mapped devices that include targets partially mapping the set of sequential write required zones of zoned block devices, instead of advertizing no zone resource limits, it is also better to set the mapped device limits to the non-zero minimum of the limits of all targets. In this case the limits for a target depend on the number of sequential zones being mapped: if this number of zone is larger than the limits, then the limits of the device apply and can be used. If on the other hand the target maps a number of zones smaller than the limits, then no limits is needed and we can assume that the target has no limits (limits set to 0). This commit improves zone resource limits handling as described above by modifying dm_set_zones_restrictions() to iterate the targets of a mapped device to evaluate the max open and max active zone limits. This relies on an internal "stacking" of the limits of the target devices combined with a direct counting of the number of sequential zones mapped by the targets. 1) For a target mapping an entire zoned block device, the limits for the target are set to the limits of the device. 2) For a target partially mapping a zoned block device, the number of mapped sequential zones is used to determine the limits: if the target maps more sequential write required zones than the device limits, then the limits of the device are used as-is. If the number of mapped sequential zones is lower than the limits, then we assume that the target has no limits (limits set to 0). As this evaluation is done for each target, the zone resource limits for the mapped device are evaluated as the non-zero minimum of the limits of all the targets. For configurations resulting in unreliable limits, i.e. a table containing a target partially mapping a zoned device, a warning message is issued. The counting of mapped sequential zones for the target is done using the new function dm_device_count_zones() which performs a report zones on the entire block device with the callback dm_device_count_zones_cb(). This count of mapped sequential zones is also used to determine if the mapped device contains only conventional zones. This allows simplifying dm_set_zones_restrictions() to not do a report zones just for this. For mapped devices mapping only conventional zones, as before, the mapped device is changed to a regular device by setting its zoned limit to false and clearing all its zone related limits. Signed-off-by: Damien Le Moal <[email protected]> Reviewed-by: Christoph Hellwig <[email protected]> Reviewed-by: Benjamin Marzinski <[email protected]> Reviewed-by: Niklas Cassel <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Jens Axboe <[email protected]>
1 parent 7f91ccd commit 73a74af

File tree

1 file changed

+150
-30
lines changed

1 file changed

+150
-30
lines changed

drivers/md/dm-zone.c

Lines changed: 150 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -145,21 +145,6 @@ bool dm_is_zone_write(struct mapped_device *md, struct bio *bio)
145145
}
146146
}
147147

148-
/*
149-
* Count conventional zones of a mapped zoned device. If the device
150-
* only has conventional zones, do not expose it as zoned.
151-
*/
152-
static int dm_check_zoned_cb(struct blk_zone *zone, unsigned int idx,
153-
void *data)
154-
{
155-
unsigned int *nr_conv_zones = data;
156-
157-
if (zone->type == BLK_ZONE_TYPE_CONVENTIONAL)
158-
(*nr_conv_zones)++;
159-
160-
return 0;
161-
}
162-
163148
/*
164149
* Revalidate the zones of a mapped device to initialize resource necessary
165150
* for zone append emulation. Note that we cannot simply use the block layer
@@ -228,13 +213,127 @@ static bool dm_table_supports_zone_append(struct dm_table *t)
228213
return true;
229214
}
230215

216+
struct dm_device_zone_count {
217+
sector_t start;
218+
sector_t len;
219+
unsigned int total_nr_seq_zones;
220+
unsigned int target_nr_seq_zones;
221+
};
222+
223+
/*
224+
* Count the total number of and the number of mapped sequential zones of a
225+
* target zoned device.
226+
*/
227+
static int dm_device_count_zones_cb(struct blk_zone *zone,
228+
unsigned int idx, void *data)
229+
{
230+
struct dm_device_zone_count *zc = data;
231+
232+
if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL) {
233+
zc->total_nr_seq_zones++;
234+
if (zone->start >= zc->start &&
235+
zone->start < zc->start + zc->len)
236+
zc->target_nr_seq_zones++;
237+
}
238+
239+
return 0;
240+
}
241+
242+
static int dm_device_count_zones(struct dm_dev *dev,
243+
struct dm_device_zone_count *zc)
244+
{
245+
int ret;
246+
247+
ret = blkdev_report_zones(dev->bdev, 0, BLK_ALL_ZONES,
248+
dm_device_count_zones_cb, zc);
249+
if (ret < 0)
250+
return ret;
251+
if (!ret)
252+
return -EIO;
253+
return 0;
254+
}
255+
256+
struct dm_zone_resource_limits {
257+
unsigned int mapped_nr_seq_zones;
258+
struct queue_limits *lim;
259+
bool reliable_limits;
260+
};
261+
262+
static int device_get_zone_resource_limits(struct dm_target *ti,
263+
struct dm_dev *dev, sector_t start,
264+
sector_t len, void *data)
265+
{
266+
struct dm_zone_resource_limits *zlim = data;
267+
struct gendisk *disk = dev->bdev->bd_disk;
268+
unsigned int max_open_zones, max_active_zones;
269+
int ret;
270+
struct dm_device_zone_count zc = {
271+
.start = start,
272+
.len = len,
273+
};
274+
275+
/*
276+
* If the target is not the whole device, the device zone resources may
277+
* be shared between different targets. Check this by counting the
278+
* number of mapped sequential zones: if this number is smaller than the
279+
* total number of sequential zones of the target device, then resource
280+
* sharing may happen and the zone limits will not be reliable.
281+
*/
282+
ret = dm_device_count_zones(dev, &zc);
283+
if (ret) {
284+
DMERR("Count %s zones failed %d", disk->disk_name, ret);
285+
return ret;
286+
}
287+
288+
/*
289+
* If the target does not map any sequential zones, then we do not need
290+
* any zone resource limits.
291+
*/
292+
if (!zc.target_nr_seq_zones)
293+
return 0;
294+
295+
/*
296+
* If the target does not map all sequential zones, the limits
297+
* will not be reliable.
298+
*/
299+
if (zc.target_nr_seq_zones < zc.total_nr_seq_zones)
300+
zlim->reliable_limits = false;
301+
302+
/*
303+
* If the target maps less sequential zones than the limit values, then
304+
* we do not have limits for this target.
305+
*/
306+
max_active_zones = disk->queue->limits.max_active_zones;
307+
if (max_active_zones >= zc.target_nr_seq_zones)
308+
max_active_zones = 0;
309+
zlim->lim->max_active_zones =
310+
min_not_zero(max_active_zones, zlim->lim->max_active_zones);
311+
312+
max_open_zones = disk->queue->limits.max_open_zones;
313+
if (max_open_zones >= zc.target_nr_seq_zones)
314+
max_open_zones = 0;
315+
zlim->lim->max_open_zones =
316+
min_not_zero(max_open_zones, zlim->lim->max_open_zones);
317+
318+
/*
319+
* Also count the total number of sequential zones for the mapped
320+
* device so that when we are done inspecting all its targets, we are
321+
* able to check if the mapped device actually has any sequential zones.
322+
*/
323+
zlim->mapped_nr_seq_zones += zc.target_nr_seq_zones;
324+
325+
return 0;
326+
}
327+
231328
int dm_set_zones_restrictions(struct dm_table *t, struct request_queue *q,
232329
struct queue_limits *lim)
233330
{
234331
struct mapped_device *md = t->md;
235332
struct gendisk *disk = md->disk;
236-
unsigned int nr_conv_zones = 0;
237-
int ret;
333+
struct dm_zone_resource_limits zlim = {
334+
.reliable_limits = true,
335+
.lim = lim,
336+
};
238337

239338
/*
240339
* Check if zone append is natively supported, and if not, set the
@@ -249,32 +348,53 @@ int dm_set_zones_restrictions(struct dm_table *t, struct request_queue *q,
249348
}
250349

251350
/*
252-
* Count conventional zones to check that the mapped device will indeed
253-
* have sequential write required zones.
351+
* Determine the max open and max active zone limits for the mapped
352+
* device by inspecting the zone resource limits and the zones mapped
353+
* by each target.
254354
*/
255-
md->zone_revalidate_map = t;
256-
ret = dm_blk_report_zones(disk, 0, UINT_MAX,
257-
dm_check_zoned_cb, &nr_conv_zones);
258-
md->zone_revalidate_map = NULL;
259-
if (ret < 0) {
260-
DMERR("Check zoned failed %d", ret);
261-
return ret;
355+
for (unsigned int i = 0; i < t->num_targets; i++) {
356+
struct dm_target *ti = dm_table_get_target(t, i);
357+
358+
if (!ti->type->iterate_devices ||
359+
ti->type->iterate_devices(ti,
360+
device_get_zone_resource_limits, &zlim)) {
361+
DMERR("Could not determine %s zone resource limits",
362+
disk->disk_name);
363+
return -ENODEV;
364+
}
262365
}
263366

264367
/*
265-
* If we only have conventional zones, expose the mapped device as
266-
* a regular device.
368+
* If we only have conventional zones mapped, expose the mapped device
369+
+ as a regular device.
267370
*/
268-
if (nr_conv_zones >= ret) {
371+
if (!zlim.mapped_nr_seq_zones) {
269372
lim->max_open_zones = 0;
270373
lim->max_active_zones = 0;
374+
lim->max_zone_append_sectors = 0;
375+
lim->zone_write_granularity = 0;
376+
lim->chunk_sectors = 0;
271377
lim->zoned = false;
272378
clear_bit(DMF_EMULATE_ZONE_APPEND, &md->flags);
379+
md->nr_zones = 0;
273380
disk->nr_zones = 0;
274381
return 0;
275382
}
276383

277-
if (!static_key_enabled(&zoned_enabled.key))
384+
/*
385+
* Warn once (when the capacity is not yet set) if the mapped device is
386+
* partially using zone resources of the target devices as that leads to
387+
* unreliable limits, i.e. if another mapped device uses the same
388+
* underlying devices, we cannot enforce zone limits to guarantee that
389+
* writing will not lead to errors. Note that we really should return
390+
* an error for such case but there is no easy way to find out if
391+
* another mapped device uses the same underlying zoned devices.
392+
*/
393+
if (!get_capacity(disk) && !zlim.reliable_limits)
394+
DMWARN("%s zone resource limits may be unreliable",
395+
disk->disk_name);
396+
397+
if (lim->zoned && !static_key_enabled(&zoned_enabled.key))
278398
static_branch_enable(&zoned_enabled);
279399
return 0;
280400
}

0 commit comments

Comments
 (0)