]>
Commit | Line | Data |
---|---|---|
2025cf9e | 1 | // SPDX-License-Identifier: GPL-2.0-only |
047fc8a1 RZ |
2 | /* |
3 | * NVDIMM Block Window Driver | |
4 | * Copyright (c) 2014, Intel Corporation. | |
047fc8a1 RZ |
5 | */ |
6 | ||
7 | #include <linux/blkdev.h> | |
8 | #include <linux/fs.h> | |
9 | #include <linux/genhd.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/moduleparam.h> | |
12 | #include <linux/nd.h> | |
13 | #include <linux/sizes.h> | |
14 | #include "nd.h" | |
15 | ||
9d90725d DW |
16 | static u32 nsblk_meta_size(struct nd_namespace_blk *nsblk) |
17 | { | |
18 | return nsblk->lbasize - ((nsblk->lbasize >= 4096) ? 4096 : 512); | |
19 | } | |
047fc8a1 | 20 | |
9d90725d | 21 | static u32 nsblk_internal_lbasize(struct nd_namespace_blk *nsblk) |
fcae6957 | 22 | { |
9d90725d DW |
23 | return roundup(nsblk->lbasize, INT_LBASIZE_ALIGNMENT); |
24 | } | |
25 | ||
26 | static u32 nsblk_sector_size(struct nd_namespace_blk *nsblk) | |
27 | { | |
28 | return nsblk->lbasize - nsblk_meta_size(nsblk); | |
fcae6957 VV |
29 | } |
30 | ||
047fc8a1 RZ |
31 | static resource_size_t to_dev_offset(struct nd_namespace_blk *nsblk, |
32 | resource_size_t ns_offset, unsigned int len) | |
33 | { | |
34 | int i; | |
35 | ||
36 | for (i = 0; i < nsblk->num_resources; i++) { | |
37 | if (ns_offset < resource_size(nsblk->res[i])) { | |
38 | if (ns_offset + len > resource_size(nsblk->res[i])) { | |
39 | dev_WARN_ONCE(&nsblk->common.dev, 1, | |
40 | "illegal request\n"); | |
41 | return SIZE_MAX; | |
42 | } | |
43 | return nsblk->res[i]->start + ns_offset; | |
44 | } | |
45 | ns_offset -= resource_size(nsblk->res[i]); | |
46 | } | |
47 | ||
48 | dev_WARN_ONCE(&nsblk->common.dev, 1, "request out of range\n"); | |
49 | return SIZE_MAX; | |
50 | } | |
51 | ||
9d90725d DW |
52 | static struct nd_blk_region *to_ndbr(struct nd_namespace_blk *nsblk) |
53 | { | |
54 | struct nd_region *nd_region; | |
55 | struct device *parent; | |
56 | ||
57 | parent = nsblk->common.dev.parent; | |
58 | nd_region = container_of(parent, struct nd_region, dev); | |
59 | return container_of(nd_region, struct nd_blk_region, nd_region); | |
60 | } | |
61 | ||
fcae6957 | 62 | #ifdef CONFIG_BLK_DEV_INTEGRITY |
9d90725d DW |
63 | static int nd_blk_rw_integrity(struct nd_namespace_blk *nsblk, |
64 | struct bio_integrity_payload *bip, u64 lba, int rw) | |
fcae6957 | 65 | { |
9d90725d DW |
66 | struct nd_blk_region *ndbr = to_ndbr(nsblk); |
67 | unsigned int len = nsblk_meta_size(nsblk); | |
fcae6957 | 68 | resource_size_t dev_offset, ns_offset; |
9d90725d | 69 | u32 internal_lbasize, sector_size; |
fcae6957 VV |
70 | int err = 0; |
71 | ||
9d90725d DW |
72 | internal_lbasize = nsblk_internal_lbasize(nsblk); |
73 | sector_size = nsblk_sector_size(nsblk); | |
74 | ns_offset = lba * internal_lbasize + sector_size; | |
fcae6957 VV |
75 | dev_offset = to_dev_offset(nsblk, ns_offset, len); |
76 | if (dev_offset == SIZE_MAX) | |
77 | return -EIO; | |
78 | ||
79 | while (len) { | |
80 | unsigned int cur_len; | |
81 | struct bio_vec bv; | |
82 | void *iobuf; | |
83 | ||
84 | bv = bvec_iter_bvec(bip->bip_vec, bip->bip_iter); | |
85 | /* | |
86 | * The 'bv' obtained from bvec_iter_bvec has its .bv_len and | |
87 | * .bv_offset already adjusted for iter->bi_bvec_done, and we | |
88 | * can use those directly | |
89 | */ | |
90 | ||
91 | cur_len = min(len, bv.bv_len); | |
92 | iobuf = kmap_atomic(bv.bv_page); | |
93 | err = ndbr->do_io(ndbr, dev_offset, iobuf + bv.bv_offset, | |
94 | cur_len, rw); | |
95 | kunmap_atomic(iobuf); | |
96 | if (err) | |
97 | return err; | |
98 | ||
99 | len -= cur_len; | |
100 | dev_offset += cur_len; | |
b1fb2c52 DM |
101 | if (!bvec_iter_advance(bip->bip_vec, &bip->bip_iter, cur_len)) |
102 | return -EIO; | |
fcae6957 VV |
103 | } |
104 | ||
105 | return err; | |
106 | } | |
107 | ||
108 | #else /* CONFIG_BLK_DEV_INTEGRITY */ | |
9d90725d DW |
109 | static int nd_blk_rw_integrity(struct nd_namespace_blk *nsblk, |
110 | struct bio_integrity_payload *bip, u64 lba, int rw) | |
fcae6957 VV |
111 | { |
112 | return 0; | |
113 | } | |
114 | #endif | |
115 | ||
9d90725d DW |
116 | static int nsblk_do_bvec(struct nd_namespace_blk *nsblk, |
117 | struct bio_integrity_payload *bip, struct page *page, | |
118 | unsigned int len, unsigned int off, int rw, sector_t sector) | |
fcae6957 | 119 | { |
9d90725d | 120 | struct nd_blk_region *ndbr = to_ndbr(nsblk); |
fcae6957 | 121 | resource_size_t dev_offset, ns_offset; |
9d90725d | 122 | u32 internal_lbasize, sector_size; |
fcae6957 VV |
123 | int err = 0; |
124 | void *iobuf; | |
125 | u64 lba; | |
126 | ||
9d90725d DW |
127 | internal_lbasize = nsblk_internal_lbasize(nsblk); |
128 | sector_size = nsblk_sector_size(nsblk); | |
fcae6957 VV |
129 | while (len) { |
130 | unsigned int cur_len; | |
131 | ||
132 | /* | |
133 | * If we don't have an integrity payload, we don't have to | |
134 | * split the bvec into sectors, as this would cause unnecessary | |
135 | * Block Window setup/move steps. the do_io routine is capable | |
136 | * of handling len <= PAGE_SIZE. | |
137 | */ | |
9d90725d | 138 | cur_len = bip ? min(len, sector_size) : len; |
fcae6957 | 139 | |
9d90725d DW |
140 | lba = div_u64(sector << SECTOR_SHIFT, sector_size); |
141 | ns_offset = lba * internal_lbasize; | |
142 | dev_offset = to_dev_offset(nsblk, ns_offset, cur_len); | |
fcae6957 VV |
143 | if (dev_offset == SIZE_MAX) |
144 | return -EIO; | |
145 | ||
146 | iobuf = kmap_atomic(page); | |
147 | err = ndbr->do_io(ndbr, dev_offset, iobuf + off, cur_len, rw); | |
148 | kunmap_atomic(iobuf); | |
149 | if (err) | |
150 | return err; | |
151 | ||
152 | if (bip) { | |
9d90725d | 153 | err = nd_blk_rw_integrity(nsblk, bip, lba, rw); |
fcae6957 VV |
154 | if (err) |
155 | return err; | |
156 | } | |
157 | len -= cur_len; | |
158 | off += cur_len; | |
9d90725d | 159 | sector += sector_size >> SECTOR_SHIFT; |
fcae6957 VV |
160 | } |
161 | ||
162 | return err; | |
163 | } | |
164 | ||
dece1635 | 165 | static blk_qc_t nd_blk_make_request(struct request_queue *q, struct bio *bio) |
047fc8a1 | 166 | { |
fcae6957 | 167 | struct bio_integrity_payload *bip; |
9d90725d | 168 | struct nd_namespace_blk *nsblk; |
047fc8a1 | 169 | struct bvec_iter iter; |
f0dc089c | 170 | unsigned long start; |
047fc8a1 RZ |
171 | struct bio_vec bvec; |
172 | int err = 0, rw; | |
f0dc089c | 173 | bool do_acct; |
047fc8a1 | 174 | |
e23947bd DM |
175 | if (!bio_integrity_prep(bio)) |
176 | return BLK_QC_T_NONE; | |
fcae6957 VV |
177 | |
178 | bip = bio_integrity(bio); | |
9d90725d | 179 | nsblk = q->queuedata; |
047fc8a1 | 180 | rw = bio_data_dir(bio); |
0fd92f89 CH |
181 | do_acct = blk_queue_io_stat(bio->bi_disk->queue); |
182 | if (do_acct) | |
183 | start = bio_start_io_acct(bio); | |
047fc8a1 RZ |
184 | bio_for_each_segment(bvec, bio, iter) { |
185 | unsigned int len = bvec.bv_len; | |
047fc8a1 RZ |
186 | |
187 | BUG_ON(len > PAGE_SIZE); | |
9d90725d DW |
188 | err = nsblk_do_bvec(nsblk, bip, bvec.bv_page, len, |
189 | bvec.bv_offset, rw, iter.bi_sector); | |
fcae6957 | 190 | if (err) { |
9d90725d | 191 | dev_dbg(&nsblk->common.dev, |
fcae6957 VV |
192 | "io error in %s sector %lld, len %d,\n", |
193 | (rw == READ) ? "READ" : "WRITE", | |
194 | (unsigned long long) iter.bi_sector, len); | |
4e4cbee9 | 195 | bio->bi_status = errno_to_blk_status(err); |
f0dc089c | 196 | break; |
047fc8a1 | 197 | } |
047fc8a1 | 198 | } |
f0dc089c | 199 | if (do_acct) |
0fd92f89 | 200 | bio_end_io_acct(bio, start); |
047fc8a1 | 201 | |
4246a0b6 | 202 | bio_endio(bio); |
dece1635 | 203 | return BLK_QC_T_NONE; |
047fc8a1 RZ |
204 | } |
205 | ||
9d90725d | 206 | static int nsblk_rw_bytes(struct nd_namespace_common *ndns, |
3ae3d67b VV |
207 | resource_size_t offset, void *iobuf, size_t n, int rw, |
208 | unsigned long flags) | |
047fc8a1 | 209 | { |
9d90725d DW |
210 | struct nd_namespace_blk *nsblk = to_nd_namespace_blk(&ndns->dev); |
211 | struct nd_blk_region *ndbr = to_ndbr(nsblk); | |
047fc8a1 RZ |
212 | resource_size_t dev_offset; |
213 | ||
214 | dev_offset = to_dev_offset(nsblk, offset, n); | |
215 | ||
9d90725d | 216 | if (unlikely(offset + n > nsblk->size)) { |
047fc8a1 RZ |
217 | dev_WARN_ONCE(&ndns->dev, 1, "request out of range\n"); |
218 | return -EFAULT; | |
219 | } | |
220 | ||
221 | if (dev_offset == SIZE_MAX) | |
222 | return -EIO; | |
223 | ||
224 | return ndbr->do_io(ndbr, dev_offset, iobuf, n, rw); | |
225 | } | |
226 | ||
227 | static const struct block_device_operations nd_blk_fops = { | |
228 | .owner = THIS_MODULE, | |
58138820 | 229 | .revalidate_disk = nvdimm_revalidate_disk, |
047fc8a1 RZ |
230 | }; |
231 | ||
d29cee12 DW |
232 | static void nd_blk_release_queue(void *q) |
233 | { | |
234 | blk_cleanup_queue(q); | |
235 | } | |
236 | ||
237 | static void nd_blk_release_disk(void *disk) | |
238 | { | |
239 | del_gendisk(disk); | |
240 | put_disk(disk); | |
241 | } | |
242 | ||
9d90725d | 243 | static int nsblk_attach_disk(struct nd_namespace_blk *nsblk) |
047fc8a1 | 244 | { |
9d90725d | 245 | struct device *dev = &nsblk->common.dev; |
fcae6957 | 246 | resource_size_t available_disk_size; |
d29cee12 | 247 | struct request_queue *q; |
047fc8a1 | 248 | struct gendisk *disk; |
fcae6957 VV |
249 | u64 internal_nlba; |
250 | ||
9d90725d DW |
251 | internal_nlba = div_u64(nsblk->size, nsblk_internal_lbasize(nsblk)); |
252 | available_disk_size = internal_nlba * nsblk_sector_size(nsblk); | |
047fc8a1 | 253 | |
3d745ea5 | 254 | q = blk_alloc_queue(nd_blk_make_request, NUMA_NO_NODE); |
d29cee12 DW |
255 | if (!q) |
256 | return -ENOMEM; | |
f02716db | 257 | if (devm_add_action_or_reset(dev, nd_blk_release_queue, q)) |
047fc8a1 RZ |
258 | return -ENOMEM; |
259 | ||
d29cee12 | 260 | blk_queue_max_hw_sectors(q, UINT_MAX); |
9d90725d | 261 | blk_queue_logical_block_size(q, nsblk_sector_size(nsblk)); |
8b904b5b | 262 | blk_queue_flag_set(QUEUE_FLAG_NONROT, q); |
9d90725d | 263 | q->queuedata = nsblk; |
047fc8a1 | 264 | |
d29cee12 DW |
265 | disk = alloc_disk(0); |
266 | if (!disk) | |
267 | return -ENOMEM; | |
047fc8a1 | 268 | |
047fc8a1 RZ |
269 | disk->first_minor = 0; |
270 | disk->fops = &nd_blk_fops; | |
d29cee12 | 271 | disk->queue = q; |
047fc8a1 | 272 | disk->flags = GENHD_FL_EXT_DEVT; |
9d90725d | 273 | nvdimm_namespace_disk_name(&nsblk->common, disk->disk_name); |
047fc8a1 | 274 | |
f02716db DW |
275 | if (devm_add_action_or_reset(dev, nd_blk_release_disk, disk)) |
276 | return -ENOMEM; | |
277 | ||
9d90725d DW |
278 | if (nsblk_meta_size(nsblk)) { |
279 | int rc = nd_integrity_init(disk, nsblk_meta_size(nsblk)); | |
fcae6957 | 280 | |
d29cee12 | 281 | if (rc) |
fcae6957 | 282 | return rc; |
fcae6957 VV |
283 | } |
284 | ||
285 | set_capacity(disk, available_disk_size >> SECTOR_SHIFT); | |
fef912bf | 286 | device_add_disk(dev, disk, NULL); |
58138820 | 287 | revalidate_disk(disk); |
047fc8a1 RZ |
288 | return 0; |
289 | } | |
290 | ||
291 | static int nd_blk_probe(struct device *dev) | |
292 | { | |
293 | struct nd_namespace_common *ndns; | |
fcae6957 | 294 | struct nd_namespace_blk *nsblk; |
047fc8a1 RZ |
295 | |
296 | ndns = nvdimm_namespace_common_probe(dev); | |
297 | if (IS_ERR(ndns)) | |
298 | return PTR_ERR(ndns); | |
299 | ||
fcae6957 | 300 | nsblk = to_nd_namespace_blk(&ndns->dev); |
9d90725d DW |
301 | nsblk->size = nvdimm_namespace_capacity(ndns); |
302 | dev_set_drvdata(dev, nsblk); | |
303 | ||
304 | ndns->rw_bytes = nsblk_rw_bytes; | |
047fc8a1 | 305 | if (is_nd_btt(dev)) |
d29cee12 | 306 | return nvdimm_namespace_attach_btt(ndns); |
200c79da | 307 | else if (nd_btt_probe(dev, ndns) == 0) { |
047fc8a1 | 308 | /* we'll come back as btt-blk */ |
d29cee12 | 309 | return -ENXIO; |
047fc8a1 | 310 | } else |
9d90725d | 311 | return nsblk_attach_disk(nsblk); |
047fc8a1 RZ |
312 | } |
313 | ||
314 | static int nd_blk_remove(struct device *dev) | |
315 | { | |
047fc8a1 | 316 | if (is_nd_btt(dev)) |
298f2bc5 | 317 | nvdimm_namespace_detach_btt(to_nd_btt(dev)); |
047fc8a1 RZ |
318 | return 0; |
319 | } | |
320 | ||
321 | static struct nd_device_driver nd_blk_driver = { | |
322 | .probe = nd_blk_probe, | |
323 | .remove = nd_blk_remove, | |
324 | .drv = { | |
325 | .name = "nd_blk", | |
326 | }, | |
327 | .type = ND_DRIVER_NAMESPACE_BLK, | |
328 | }; | |
329 | ||
330 | static int __init nd_blk_init(void) | |
331 | { | |
ec56151d | 332 | return nd_driver_register(&nd_blk_driver); |
047fc8a1 RZ |
333 | } |
334 | ||
335 | static void __exit nd_blk_exit(void) | |
336 | { | |
337 | driver_unregister(&nd_blk_driver.drv); | |
047fc8a1 RZ |
338 | } |
339 | ||
340 | MODULE_AUTHOR("Ross Zwisler <[email protected]>"); | |
341 | MODULE_LICENSE("GPL v2"); | |
342 | MODULE_ALIAS_ND_DEVICE(ND_DEVICE_NAMESPACE_BLK); | |
343 | module_init(nd_blk_init); | |
344 | module_exit(nd_blk_exit); |