diff options
author | Christoph Hellwig <hch@lst.de> | 2018-10-12 06:08:49 -0400 |
---|---|---|
committer | Jens Axboe <axboe@kernel.dk> | 2018-10-25 13:17:40 -0400 |
commit | e76239a3748c90a8b0e197f8f4544a8ce52f126e (patch) | |
tree | 4e555b7fe24f1642eecc1cfbca563897cfeb6d4a /block/blk-zoned.c | |
parent | 965b652e901886ea54f93c60027b5be76328d958 (diff) |
block: add a report_zones method
Dispatching a report zones command through the request queue is a major
pain due to the command reply payload rewriting necessary. Given that
blkdev_report_zones() is executing everything synchronously, implement
report zones as a block device file operation instead, allowing major
simplification of the code in many places.
sd, null-blk, dm-linear and dm-flakey being the only block device
drivers supporting exposing zoned block devices, these drivers are
modified to provide the device side implementation of the
report_zones() block device file operation.
For device mappers, a new report_zones() target type operation is
defined so that the upper block layer calls blkdev_report_zones() can
be propagated down to the underlying devices of the dm targets.
Implementation for this new operation is added to the dm-linear and
dm-flakey targets.
Reviewed-by: Hannes Reinecke <hare@suse.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
[Damien]
* Changed method block_device argument to gendisk
* Various bug fixes and improvements
* Added support for null_blk, dm-linear and dm-flakey.
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Mike Snitzer <snitzer@redhat.com>
Signed-off-by: Damien Le Moal <damien.lemoal@wdc.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Diffstat (limited to 'block/blk-zoned.c')
-rw-r--r-- | block/blk-zoned.c | 164 |
1 files changed, 51 insertions, 113 deletions
diff --git a/block/blk-zoned.c b/block/blk-zoned.c index 5d967fd39fbd..90cf503091d5 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c | |||
@@ -93,13 +93,10 @@ unsigned int blkdev_nr_zones(struct block_device *bdev) | |||
93 | EXPORT_SYMBOL_GPL(blkdev_nr_zones); | 93 | EXPORT_SYMBOL_GPL(blkdev_nr_zones); |
94 | 94 | ||
95 | /* | 95 | /* |
96 | * Check that a zone report belongs to the partition. | 96 | * Check that a zone report belongs to this partition, and if yes, fix its start |
97 | * If yes, fix its start sector and write pointer, copy it in the | 97 | * sector and write pointer and return true. Return false otherwise. |
98 | * zone information array and return true. Return false otherwise. | ||
99 | */ | 98 | */ |
100 | static bool blkdev_report_zone(struct block_device *bdev, | 99 | static bool blkdev_report_zone(struct block_device *bdev, struct blk_zone *rep) |
101 | struct blk_zone *rep, | ||
102 | struct blk_zone *zone) | ||
103 | { | 100 | { |
104 | sector_t offset = get_start_sect(bdev); | 101 | sector_t offset = get_start_sect(bdev); |
105 | 102 | ||
@@ -114,11 +111,36 @@ static bool blkdev_report_zone(struct block_device *bdev, | |||
114 | rep->wp = rep->start + rep->len; | 111 | rep->wp = rep->start + rep->len; |
115 | else | 112 | else |
116 | rep->wp -= offset; | 113 | rep->wp -= offset; |
117 | memcpy(zone, rep, sizeof(struct blk_zone)); | ||
118 | |||
119 | return true; | 114 | return true; |
120 | } | 115 | } |
121 | 116 | ||
117 | static int blk_report_zones(struct gendisk *disk, sector_t sector, | ||
118 | struct blk_zone *zones, unsigned int *nr_zones, | ||
119 | gfp_t gfp_mask) | ||
120 | { | ||
121 | struct request_queue *q = disk->queue; | ||
122 | unsigned int z = 0, n, nrz = *nr_zones; | ||
123 | sector_t capacity = get_capacity(disk); | ||
124 | int ret; | ||
125 | |||
126 | while (z < nrz && sector < capacity) { | ||
127 | n = nrz - z; | ||
128 | ret = disk->fops->report_zones(disk, sector, &zones[z], &n, | ||
129 | gfp_mask); | ||
130 | if (ret) | ||
131 | return ret; | ||
132 | if (!n) | ||
133 | break; | ||
134 | sector += blk_queue_zone_sectors(q) * n; | ||
135 | z += n; | ||
136 | } | ||
137 | |||
138 | WARN_ON(z > *nr_zones); | ||
139 | *nr_zones = z; | ||
140 | |||
141 | return 0; | ||
142 | } | ||
143 | |||
122 | /** | 144 | /** |
123 | * blkdev_report_zones - Get zones information | 145 | * blkdev_report_zones - Get zones information |
124 | * @bdev: Target block device | 146 | * @bdev: Target block device |
@@ -133,130 +155,46 @@ static bool blkdev_report_zone(struct block_device *bdev, | |||
133 | * requested by @nr_zones. The number of zones actually reported is | 155 | * requested by @nr_zones. The number of zones actually reported is |
134 | * returned in @nr_zones. | 156 | * returned in @nr_zones. |
135 | */ | 157 | */ |
136 | int blkdev_report_zones(struct block_device *bdev, | 158 | int blkdev_report_zones(struct block_device *bdev, sector_t sector, |
137 | sector_t sector, | 159 | struct blk_zone *zones, unsigned int *nr_zones, |
138 | struct blk_zone *zones, | ||
139 | unsigned int *nr_zones, | ||
140 | gfp_t gfp_mask) | 160 | gfp_t gfp_mask) |
141 | { | 161 | { |
142 | struct request_queue *q = bdev_get_queue(bdev); | 162 | struct request_queue *q = bdev_get_queue(bdev); |
143 | struct blk_zone_report_hdr *hdr; | 163 | unsigned int i, nrz; |
144 | unsigned int nrz = *nr_zones; | ||
145 | struct page *page; | ||
146 | unsigned int nr_rep; | ||
147 | size_t rep_bytes; | ||
148 | unsigned int nr_pages; | ||
149 | struct bio *bio; | ||
150 | struct bio_vec *bv; | ||
151 | unsigned int i, n, nz; | ||
152 | unsigned int ofst; | ||
153 | void *addr; | ||
154 | int ret; | 164 | int ret; |
155 | 165 | ||
156 | if (!q) | ||
157 | return -ENXIO; | ||
158 | |||
159 | if (!blk_queue_is_zoned(q)) | 166 | if (!blk_queue_is_zoned(q)) |
160 | return -EOPNOTSUPP; | 167 | return -EOPNOTSUPP; |
161 | 168 | ||
162 | if (!nrz) | ||
163 | return 0; | ||
164 | |||
165 | if (sector > bdev->bd_part->nr_sects) { | ||
166 | *nr_zones = 0; | ||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | /* | 169 | /* |
171 | * The zone report has a header. So make room for it in the | 170 | * A block device that advertized itself as zoned must have a |
172 | * payload. Also make sure that the report fits in a single BIO | 171 | * report_zones method. If it does not have one defined, the device |
173 | * that will not be split down the stack. | 172 | * driver has a bug. So warn about that. |
174 | */ | 173 | */ |
175 | rep_bytes = sizeof(struct blk_zone_report_hdr) + | 174 | if (WARN_ON_ONCE(!bdev->bd_disk->fops->report_zones)) |
176 | sizeof(struct blk_zone) * nrz; | 175 | return -EOPNOTSUPP; |
177 | rep_bytes = (rep_bytes + PAGE_SIZE - 1) & PAGE_MASK; | ||
178 | if (rep_bytes > (queue_max_sectors(q) << 9)) | ||
179 | rep_bytes = queue_max_sectors(q) << 9; | ||
180 | |||
181 | nr_pages = min_t(unsigned int, BIO_MAX_PAGES, | ||
182 | rep_bytes >> PAGE_SHIFT); | ||
183 | nr_pages = min_t(unsigned int, nr_pages, | ||
184 | queue_max_segments(q)); | ||
185 | |||
186 | bio = bio_alloc(gfp_mask, nr_pages); | ||
187 | if (!bio) | ||
188 | return -ENOMEM; | ||
189 | 176 | ||
190 | bio_set_dev(bio, bdev); | 177 | if (!*nr_zones || sector >= bdev->bd_part->nr_sects) { |
191 | bio->bi_iter.bi_sector = blk_zone_start(q, sector); | 178 | *nr_zones = 0; |
192 | bio_set_op_attrs(bio, REQ_OP_ZONE_REPORT, 0); | 179 | return 0; |
193 | |||
194 | for (i = 0; i < nr_pages; i++) { | ||
195 | page = alloc_page(gfp_mask); | ||
196 | if (!page) { | ||
197 | ret = -ENOMEM; | ||
198 | goto out; | ||
199 | } | ||
200 | if (!bio_add_page(bio, page, PAGE_SIZE, 0)) { | ||
201 | __free_page(page); | ||
202 | break; | ||
203 | } | ||
204 | } | 180 | } |
205 | 181 | ||
206 | if (i == 0) | 182 | nrz = min(*nr_zones, |
207 | ret = -ENOMEM; | 183 | __blkdev_nr_zones(q, bdev->bd_part->nr_sects - sector)); |
208 | else | 184 | ret = blk_report_zones(bdev->bd_disk, get_start_sect(bdev) + sector, |
209 | ret = submit_bio_wait(bio); | 185 | zones, &nrz, gfp_mask); |
210 | if (ret) | 186 | if (ret) |
211 | goto out; | 187 | return ret; |
212 | |||
213 | /* | ||
214 | * Process the report result: skip the header and go through the | ||
215 | * reported zones to fixup and fixup the zone information for | ||
216 | * partitions. At the same time, return the zone information into | ||
217 | * the zone array. | ||
218 | */ | ||
219 | n = 0; | ||
220 | nz = 0; | ||
221 | nr_rep = 0; | ||
222 | bio_for_each_segment_all(bv, bio, i) { | ||
223 | 188 | ||
224 | if (!bv->bv_page) | 189 | for (i = 0; i < nrz; i++) { |
190 | if (!blkdev_report_zone(bdev, zones)) | ||
225 | break; | 191 | break; |
226 | 192 | zones++; | |
227 | addr = kmap_atomic(bv->bv_page); | ||
228 | |||
229 | /* Get header in the first page */ | ||
230 | ofst = 0; | ||
231 | if (!nr_rep) { | ||
232 | hdr = addr; | ||
233 | nr_rep = hdr->nr_zones; | ||
234 | ofst = sizeof(struct blk_zone_report_hdr); | ||
235 | } | ||
236 | |||
237 | /* Fixup and report zones */ | ||
238 | while (ofst < bv->bv_len && | ||
239 | n < nr_rep && nz < nrz) { | ||
240 | if (blkdev_report_zone(bdev, addr + ofst, &zones[nz])) | ||
241 | nz++; | ||
242 | ofst += sizeof(struct blk_zone); | ||
243 | n++; | ||
244 | } | ||
245 | |||
246 | kunmap_atomic(addr); | ||
247 | |||
248 | if (n >= nr_rep || nz >= nrz) | ||
249 | break; | ||
250 | |||
251 | } | 193 | } |
252 | 194 | ||
253 | *nr_zones = nz; | 195 | *nr_zones = i; |
254 | out: | ||
255 | bio_for_each_segment_all(bv, bio, i) | ||
256 | __free_page(bv->bv_page); | ||
257 | bio_put(bio); | ||
258 | 196 | ||
259 | return ret; | 197 | return 0; |
260 | } | 198 | } |
261 | EXPORT_SYMBOL_GPL(blkdev_report_zones); | 199 | EXPORT_SYMBOL_GPL(blkdev_report_zones); |
262 | 200 | ||