xref: /openbmc/linux/drivers/nvdimm/pmem.c (revision 0f51c4fa7f60838a87cd45e8ba144dddcd4c066c)
1 /*
2  * Persistent Memory Driver
3  *
4  * Copyright (c) 2014-2015, Intel Corporation.
5  * Copyright (c) 2015, Christoph Hellwig <hch@lst.de>.
6  * Copyright (c) 2015, Boaz Harrosh <boaz@plexistor.com>.
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms and conditions of the GNU General Public License,
10  * version 2, as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  */
17 
18 #include <asm/cacheflush.h>
19 #include <linux/blkdev.h>
20 #include <linux/hdreg.h>
21 #include <linux/init.h>
22 #include <linux/platform_device.h>
23 #include <linux/module.h>
24 #include <linux/moduleparam.h>
25 #include <linux/slab.h>
26 #include <linux/nd.h>
27 #include "nd.h"
28 
29 struct pmem_device {
30 	struct request_queue	*pmem_queue;
31 	struct gendisk		*pmem_disk;
32 
33 	/* One contiguous memory region per device */
34 	phys_addr_t		phys_addr;
35 	void			*virt_addr;
36 	size_t			size;
37 };
38 
39 static int pmem_major;
40 
41 static void pmem_do_bvec(struct pmem_device *pmem, struct page *page,
42 			unsigned int len, unsigned int off, int rw,
43 			sector_t sector)
44 {
45 	void *mem = kmap_atomic(page);
46 	size_t pmem_off = sector << 9;
47 
48 	if (rw == READ) {
49 		memcpy(mem + off, pmem->virt_addr + pmem_off, len);
50 		flush_dcache_page(page);
51 	} else {
52 		flush_dcache_page(page);
53 		memcpy(pmem->virt_addr + pmem_off, mem + off, len);
54 	}
55 
56 	kunmap_atomic(mem);
57 }
58 
59 static void pmem_make_request(struct request_queue *q, struct bio *bio)
60 {
61 	bool do_acct;
62 	unsigned long start;
63 	struct bio_vec bvec;
64 	struct bvec_iter iter;
65 	struct block_device *bdev = bio->bi_bdev;
66 	struct pmem_device *pmem = bdev->bd_disk->private_data;
67 
68 	do_acct = nd_iostat_start(bio, &start);
69 	bio_for_each_segment(bvec, bio, iter)
70 		pmem_do_bvec(pmem, bvec.bv_page, bvec.bv_len, bvec.bv_offset,
71 				bio_data_dir(bio), iter.bi_sector);
72 	if (do_acct)
73 		nd_iostat_end(bio, start);
74 	bio_endio(bio, 0);
75 }
76 
77 static int pmem_rw_page(struct block_device *bdev, sector_t sector,
78 		       struct page *page, int rw)
79 {
80 	struct pmem_device *pmem = bdev->bd_disk->private_data;
81 
82 	pmem_do_bvec(pmem, page, PAGE_CACHE_SIZE, 0, rw, sector);
83 	page_endio(page, rw & WRITE, 0);
84 
85 	return 0;
86 }
87 
88 static long pmem_direct_access(struct block_device *bdev, sector_t sector,
89 			      void **kaddr, unsigned long *pfn, long size)
90 {
91 	struct pmem_device *pmem = bdev->bd_disk->private_data;
92 	size_t offset = sector << 9;
93 
94 	if (!pmem)
95 		return -ENODEV;
96 
97 	*kaddr = pmem->virt_addr + offset;
98 	*pfn = (pmem->phys_addr + offset) >> PAGE_SHIFT;
99 
100 	return pmem->size - offset;
101 }
102 
103 static const struct block_device_operations pmem_fops = {
104 	.owner =		THIS_MODULE,
105 	.rw_page =		pmem_rw_page,
106 	.direct_access =	pmem_direct_access,
107 };
108 
109 static struct pmem_device *pmem_alloc(struct device *dev,
110 		struct resource *res, int id)
111 {
112 	struct pmem_device *pmem;
113 
114 	pmem = kzalloc(sizeof(*pmem), GFP_KERNEL);
115 	if (!pmem)
116 		return ERR_PTR(-ENOMEM);
117 
118 	pmem->phys_addr = res->start;
119 	pmem->size = resource_size(res);
120 
121 	if (!request_mem_region(pmem->phys_addr, pmem->size, dev_name(dev))) {
122 		dev_warn(dev, "could not reserve region [0x%pa:0x%zx]\n",
123 				&pmem->phys_addr, pmem->size);
124 		kfree(pmem);
125 		return ERR_PTR(-EBUSY);
126 	}
127 
128 	/*
129 	 * Map the memory as non-cachable, as we can't write back the contents
130 	 * of the CPU caches in case of a crash.
131 	 */
132 	pmem->virt_addr = ioremap_nocache(pmem->phys_addr, pmem->size);
133 	if (!pmem->virt_addr) {
134 		release_mem_region(pmem->phys_addr, pmem->size);
135 		kfree(pmem);
136 		return ERR_PTR(-ENXIO);
137 	}
138 
139 	return pmem;
140 }
141 
142 static void pmem_detach_disk(struct pmem_device *pmem)
143 {
144 	del_gendisk(pmem->pmem_disk);
145 	put_disk(pmem->pmem_disk);
146 	blk_cleanup_queue(pmem->pmem_queue);
147 }
148 
149 static int pmem_attach_disk(struct nd_namespace_common *ndns,
150 		struct pmem_device *pmem)
151 {
152 	struct gendisk *disk;
153 
154 	pmem->pmem_queue = blk_alloc_queue(GFP_KERNEL);
155 	if (!pmem->pmem_queue)
156 		return -ENOMEM;
157 
158 	blk_queue_make_request(pmem->pmem_queue, pmem_make_request);
159 	blk_queue_max_hw_sectors(pmem->pmem_queue, UINT_MAX);
160 	blk_queue_bounce_limit(pmem->pmem_queue, BLK_BOUNCE_ANY);
161 	queue_flag_set_unlocked(QUEUE_FLAG_NONROT, pmem->pmem_queue);
162 
163 	disk = alloc_disk(0);
164 	if (!disk) {
165 		blk_cleanup_queue(pmem->pmem_queue);
166 		return -ENOMEM;
167 	}
168 
169 	disk->major		= pmem_major;
170 	disk->first_minor	= 0;
171 	disk->fops		= &pmem_fops;
172 	disk->private_data	= pmem;
173 	disk->queue		= pmem->pmem_queue;
174 	disk->flags		= GENHD_FL_EXT_DEVT;
175 	nvdimm_namespace_disk_name(ndns, disk->disk_name);
176 	disk->driverfs_dev = &ndns->dev;
177 	set_capacity(disk, pmem->size >> 9);
178 	pmem->pmem_disk = disk;
179 
180 	add_disk(disk);
181 
182 	return 0;
183 }
184 
185 static int pmem_rw_bytes(struct nd_namespace_common *ndns,
186 		resource_size_t offset, void *buf, size_t size, int rw)
187 {
188 	struct pmem_device *pmem = dev_get_drvdata(ndns->claim);
189 
190 	if (unlikely(offset + size > pmem->size)) {
191 		dev_WARN_ONCE(&ndns->dev, 1, "request out of range\n");
192 		return -EFAULT;
193 	}
194 
195 	if (rw == READ)
196 		memcpy(buf, pmem->virt_addr + offset, size);
197 	else
198 		memcpy(pmem->virt_addr + offset, buf, size);
199 
200 	return 0;
201 }
202 
203 static void pmem_free(struct pmem_device *pmem)
204 {
205 	iounmap(pmem->virt_addr);
206 	release_mem_region(pmem->phys_addr, pmem->size);
207 	kfree(pmem);
208 }
209 
210 static int nd_pmem_probe(struct device *dev)
211 {
212 	struct nd_region *nd_region = to_nd_region(dev->parent);
213 	struct nd_namespace_common *ndns;
214 	struct nd_namespace_io *nsio;
215 	struct pmem_device *pmem;
216 	int rc;
217 
218 	ndns = nvdimm_namespace_common_probe(dev);
219 	if (IS_ERR(ndns))
220 		return PTR_ERR(ndns);
221 
222 	nsio = to_nd_namespace_io(&ndns->dev);
223 	pmem = pmem_alloc(dev, &nsio->res, nd_region->id);
224 	if (IS_ERR(pmem))
225 		return PTR_ERR(pmem);
226 
227 	dev_set_drvdata(dev, pmem);
228 	ndns->rw_bytes = pmem_rw_bytes;
229 	if (is_nd_btt(dev))
230 		rc = nvdimm_namespace_attach_btt(ndns);
231 	else if (nd_btt_probe(ndns, pmem) == 0) {
232 		/* we'll come back as btt-pmem */
233 		rc = -ENXIO;
234 	} else
235 		rc = pmem_attach_disk(ndns, pmem);
236 	if (rc)
237 		pmem_free(pmem);
238 	return rc;
239 }
240 
241 static int nd_pmem_remove(struct device *dev)
242 {
243 	struct pmem_device *pmem = dev_get_drvdata(dev);
244 
245 	if (is_nd_btt(dev))
246 		nvdimm_namespace_detach_btt(to_nd_btt(dev)->ndns);
247 	else
248 		pmem_detach_disk(pmem);
249 	pmem_free(pmem);
250 
251 	return 0;
252 }
253 
254 MODULE_ALIAS("pmem");
255 MODULE_ALIAS_ND_DEVICE(ND_DEVICE_NAMESPACE_IO);
256 MODULE_ALIAS_ND_DEVICE(ND_DEVICE_NAMESPACE_PMEM);
257 static struct nd_device_driver nd_pmem_driver = {
258 	.probe = nd_pmem_probe,
259 	.remove = nd_pmem_remove,
260 	.drv = {
261 		.name = "nd_pmem",
262 	},
263 	.type = ND_DRIVER_NAMESPACE_IO | ND_DRIVER_NAMESPACE_PMEM,
264 };
265 
266 static int __init pmem_init(void)
267 {
268 	int error;
269 
270 	pmem_major = register_blkdev(0, "pmem");
271 	if (pmem_major < 0)
272 		return pmem_major;
273 
274 	error = nd_driver_register(&nd_pmem_driver);
275 	if (error) {
276 		unregister_blkdev(pmem_major, "pmem");
277 		return error;
278 	}
279 
280 	return 0;
281 }
282 module_init(pmem_init);
283 
284 static void pmem_exit(void)
285 {
286 	driver_unregister(&nd_pmem_driver.drv);
287 	unregister_blkdev(pmem_major, "pmem");
288 }
289 module_exit(pmem_exit);
290 
291 MODULE_AUTHOR("Ross Zwisler <ross.zwisler@linux.intel.com>");
292 MODULE_LICENSE("GPL v2");
293