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 | ||
912e8875 DLM |
59 | static int dm_report_zones_cb(struct blk_zone *zone, unsigned int idx, |
60 | void *data) | |
7fc18728 DLM |
61 | { |
62 | struct dm_report_zones_args *args = data; | |
63 | sector_t sector_diff = args->tgt->begin - args->start; | |
64 | ||
65 | /* | |
66 | * Ignore zones beyond the target range. | |
67 | */ | |
68 | if (zone->start >= args->start + args->tgt->len) | |
69 | return 0; | |
70 | ||
71 | /* | |
72 | * Remap the start sector and write pointer position of the zone | |
73 | * to match its position in the target range. | |
74 | */ | |
75 | zone->start += sector_diff; | |
76 | if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL) { | |
77 | if (zone->cond == BLK_ZONE_COND_FULL) | |
78 | zone->wp = zone->start + zone->len; | |
79 | else if (zone->cond == BLK_ZONE_COND_EMPTY) | |
80 | zone->wp = zone->start; | |
81 | else | |
82 | zone->wp += sector_diff; | |
83 | } | |
84 | ||
85 | args->next_sector = zone->start + zone->len; | |
86 | return args->orig_cb(zone, args->zone_idx++, args->orig_data); | |
87 | } | |
912e8875 DLM |
88 | |
89 | /* | |
90 | * Helper for drivers of zoned targets to implement struct target_type | |
91 | * report_zones operation. | |
92 | */ | |
93 | int dm_report_zones(struct block_device *bdev, sector_t start, sector_t sector, | |
94 | struct dm_report_zones_args *args, unsigned int nr_zones) | |
95 | { | |
96 | /* | |
97 | * Set the target mapping start sector first so that | |
98 | * dm_report_zones_cb() can correctly remap zone information. | |
99 | */ | |
100 | args->start = start; | |
101 | ||
102 | return blkdev_report_zones(bdev, sector, nr_zones, | |
103 | dm_report_zones_cb, args); | |
104 | } | |
105 | EXPORT_SYMBOL_GPL(dm_report_zones); | |
7fc18728 | 106 | |
bf14e2b2 DLM |
107 | bool dm_is_zone_write(struct mapped_device *md, struct bio *bio) |
108 | { | |
109 | struct request_queue *q = md->queue; | |
110 | ||
111 | if (!blk_queue_is_zoned(q)) | |
112 | return false; | |
113 | ||
114 | switch (bio_op(bio)) { | |
115 | case REQ_OP_WRITE_ZEROES: | |
116 | case REQ_OP_WRITE_SAME: | |
117 | case REQ_OP_WRITE: | |
118 | return !op_is_flush(bio->bi_opf) && bio_sectors(bio); | |
119 | default: | |
120 | return false; | |
121 | } | |
122 | } | |
123 | ||
7fc18728 DLM |
124 | void dm_set_zones_restrictions(struct dm_table *t, struct request_queue *q) |
125 | { | |
126 | if (!blk_queue_is_zoned(q)) | |
127 | return; | |
128 | ||
129 | /* | |
130 | * For a zoned target, the number of zones should be updated for the | |
131 | * correct value to be exposed in sysfs queue/nr_zones. For a BIO based | |
132 | * target, this is all that is needed. | |
133 | */ | |
134 | WARN_ON_ONCE(queue_is_mq(q)); | |
135 | q->nr_zones = blkdev_nr_zones(t->md->disk); | |
136 | } |