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