Commit | Line | Data |
---|---|---|
7fc18728 DLM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2021 Western Digital Corporation or its affiliates. | |
4 | */ | |
5 | ||
6 | #include <linux/blkdev.h> | |
7 | ||
8 | #include "dm-core.h" | |
9 | ||
10 | /* | |
11 | * User facing dm device block device report zone operation. This calls the | |
12 | * report_zones operation for each target of a device table. This operation is | |
13 | * generally implemented by targets using dm_report_zones(). | |
14 | */ | |
15 | int dm_blk_report_zones(struct gendisk *disk, sector_t sector, | |
16 | unsigned int nr_zones, report_zones_cb cb, void *data) | |
17 | { | |
18 | struct mapped_device *md = disk->private_data; | |
19 | struct dm_table *map; | |
20 | int srcu_idx, ret; | |
21 | struct dm_report_zones_args args = { | |
22 | .next_sector = sector, | |
23 | .orig_data = data, | |
24 | .orig_cb = cb, | |
25 | }; | |
26 | ||
27 | if (dm_suspended_md(md)) | |
28 | return -EAGAIN; | |
29 | ||
30 | map = dm_get_live_table(md, &srcu_idx); | |
31 | if (!map) { | |
32 | ret = -EIO; | |
33 | goto out; | |
34 | } | |
35 | ||
36 | do { | |
37 | struct dm_target *tgt; | |
38 | ||
39 | tgt = dm_table_find_target(map, args.next_sector); | |
40 | if (WARN_ON_ONCE(!tgt->type->report_zones)) { | |
41 | ret = -EIO; | |
42 | goto out; | |
43 | } | |
44 | ||
45 | args.tgt = tgt; | |
46 | ret = tgt->type->report_zones(tgt, &args, | |
47 | nr_zones - args.zone_idx); | |
48 | if (ret < 0) | |
49 | goto out; | |
50 | } while (args.zone_idx < nr_zones && | |
51 | args.next_sector < get_capacity(disk)); | |
52 | ||
53 | ret = args.zone_idx; | |
54 | out: | |
55 | dm_put_live_table(md, srcu_idx); | |
56 | return ret; | |
57 | } | |
58 | ||
59 | int dm_report_zones_cb(struct blk_zone *zone, unsigned int idx, void *data) | |
60 | { | |
61 | struct dm_report_zones_args *args = data; | |
62 | sector_t sector_diff = args->tgt->begin - args->start; | |
63 | ||
64 | /* | |
65 | * Ignore zones beyond the target range. | |
66 | */ | |
67 | if (zone->start >= args->start + args->tgt->len) | |
68 | return 0; | |
69 | ||
70 | /* | |
71 | * Remap the start sector and write pointer position of the zone | |
72 | * to match its position in the target range. | |
73 | */ | |
74 | zone->start += sector_diff; | |
75 | if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL) { | |
76 | if (zone->cond == BLK_ZONE_COND_FULL) | |
77 | zone->wp = zone->start + zone->len; | |
78 | else if (zone->cond == BLK_ZONE_COND_EMPTY) | |
79 | zone->wp = zone->start; | |
80 | else | |
81 | zone->wp += sector_diff; | |
82 | } | |
83 | ||
84 | args->next_sector = zone->start + zone->len; | |
85 | return args->orig_cb(zone, args->zone_idx++, args->orig_data); | |
86 | } | |
87 | EXPORT_SYMBOL_GPL(dm_report_zones_cb); | |
88 | ||
89 | void dm_set_zones_restrictions(struct dm_table *t, struct request_queue *q) | |
90 | { | |
91 | if (!blk_queue_is_zoned(q)) | |
92 | return; | |
93 | ||
94 | /* | |
95 | * For a zoned target, the number of zones should be updated for the | |
96 | * correct value to be exposed in sysfs queue/nr_zones. For a BIO based | |
97 | * target, this is all that is needed. | |
98 | */ | |
99 | WARN_ON_ONCE(queue_is_mq(q)); | |
100 | q->nr_zones = blkdev_nr_zones(t->md->disk); | |
101 | } |