xref: /linux/arch/xtensa/platforms/iss/simdisk.c (revision 570f7e331f5febb30f1384817463c7e42b65ca7d)
1 /*
2  * arch/xtensa/platforms/iss/simdisk.c
3  *
4  * This file is subject to the terms and conditions of the GNU General Public
5  * License.  See the file "COPYING" in the main directory of this archive
6  * for more details.
7  *
8  * Copyright (C) 2001-2013 Tensilica Inc.
9  *   Authors	Victor Prupis
10  */
11 
12 #include <linux/module.h>
13 #include <linux/moduleparam.h>
14 #include <linux/kernel.h>
15 #include <linux/init.h>
16 #include <linux/string.h>
17 #include <linux/string_choices.h>
18 #include <linux/blkdev.h>
19 #include <linux/bio.h>
20 #include <linux/proc_fs.h>
21 #include <linux/uaccess.h>
22 #include <platform/simcall.h>
23 
24 #define SIMDISK_MAJOR 240
25 #define SIMDISK_MINORS 1
26 #define MAX_SIMDISK_COUNT 10
27 
28 struct simdisk {
29 	const char *filename;
30 	spinlock_t lock;
31 	struct gendisk *gd;
32 	struct proc_dir_entry *procfile;
33 	int users;
34 	unsigned long size;
35 	int fd;
36 };
37 
38 
39 static int simdisk_count = CONFIG_BLK_DEV_SIMDISK_COUNT;
40 module_param(simdisk_count, int, S_IRUGO);
41 MODULE_PARM_DESC(simdisk_count, "Number of simdisk units.");
42 
43 static int n_files;
44 static char *filename[MAX_SIMDISK_COUNT] = {
45 #ifdef CONFIG_SIMDISK0_FILENAME
46 	CONFIG_SIMDISK0_FILENAME,
47 #ifdef CONFIG_SIMDISK1_FILENAME
48 	CONFIG_SIMDISK1_FILENAME,
49 #endif
50 #endif
51 };
52 
53 /*
54  * The simdisk code can be built either into the kernel or as a loadable module.
55  * When built-in, CONFIG_SIMDISK{0,1}_FILENAME can be used to specify the
56  * initial simdisk filenames and additional filenames can be provided on the
57  * kernel command line. These arguments are parsed during early boot when slab
58  * is not yet available, but the command line itself is preserved for the
59  * lifetime of the kernel, so the incoming pointer is stored directly.
60  * When built as a loadable module, each value is copied with kstrdup() and all
61  * allocated memory is freed in simdisk_param_free_filename() when the module is
62  * unloaded.
63  */
64 static int simdisk_param_set_filename(const char *val,
65 		const struct kernel_param *kp)
66 {
67 	char *str;
68 
69 	if (n_files >= ARRAY_SIZE(filename))
70 		return -EINVAL;
71 
72 #ifdef MODULE
73 	str = kstrdup(val, GFP_KERNEL);
74 	if (!str)
75 		return -ENOMEM;
76 #else
77 	str = (char *)val;
78 #endif
79 
80 	filename[n_files++] = str;
81 	return 0;
82 }
83 
84 static void simdisk_param_free_filename(void *arg)
85 {
86 	for (int i = 0; i < n_files; i++)
87 		kfree(filename[i]);
88 }
89 
90 static const struct kernel_param_ops simdisk_param_ops_filename = {
91 	.set = simdisk_param_set_filename,
92 	.free = simdisk_param_free_filename,
93 };
94 module_param_cb(filename, &simdisk_param_ops_filename, NULL, 0);
95 MODULE_PARM_DESC(filename, "Backing storage filename.");
96 
97 static int simdisk_major = SIMDISK_MAJOR;
98 
99 static void simdisk_transfer(struct simdisk *dev, unsigned long sector,
100 		unsigned long nsect, char *buffer, int write)
101 {
102 	unsigned long offset = sector << SECTOR_SHIFT;
103 	unsigned long nbytes = nsect << SECTOR_SHIFT;
104 
105 	if (offset > dev->size || dev->size - offset < nbytes) {
106 		pr_notice("Beyond-end %s (%ld %ld)\n",
107 				str_write_read(write), offset, nbytes);
108 		return;
109 	}
110 
111 	spin_lock(&dev->lock);
112 	while (nbytes > 0) {
113 		unsigned long io;
114 
115 		simc_lseek(dev->fd, offset, SEEK_SET);
116 		READ_ONCE(*buffer);
117 		if (write)
118 			io = simc_write(dev->fd, buffer, nbytes);
119 		else
120 			io = simc_read(dev->fd, buffer, nbytes);
121 		if (io == -1) {
122 			pr_err("SIMDISK: IO error %d\n", errno);
123 			break;
124 		}
125 		buffer += io;
126 		offset += io;
127 		nbytes -= io;
128 	}
129 	spin_unlock(&dev->lock);
130 }
131 
132 static void simdisk_submit_bio(struct bio *bio)
133 {
134 	struct simdisk *dev = bio->bi_bdev->bd_disk->private_data;
135 	struct bio_vec bvec;
136 	struct bvec_iter iter;
137 	sector_t sector = bio->bi_iter.bi_sector;
138 
139 	bio_for_each_segment(bvec, bio, iter) {
140 		char *buffer = bvec_kmap_local(&bvec);
141 		unsigned len = bvec.bv_len >> SECTOR_SHIFT;
142 
143 		simdisk_transfer(dev, sector, len, buffer,
144 				bio_data_dir(bio) == WRITE);
145 		sector += len;
146 		kunmap_local(buffer);
147 	}
148 
149 	bio_endio(bio);
150 }
151 
152 static int simdisk_open(struct gendisk *disk, blk_mode_t mode)
153 {
154 	struct simdisk *dev = disk->private_data;
155 
156 	spin_lock(&dev->lock);
157 	++dev->users;
158 	spin_unlock(&dev->lock);
159 	return 0;
160 }
161 
162 static void simdisk_release(struct gendisk *disk)
163 {
164 	struct simdisk *dev = disk->private_data;
165 	spin_lock(&dev->lock);
166 	--dev->users;
167 	spin_unlock(&dev->lock);
168 }
169 
170 static const struct block_device_operations simdisk_ops = {
171 	.owner		= THIS_MODULE,
172 	.submit_bio	= simdisk_submit_bio,
173 	.open		= simdisk_open,
174 	.release	= simdisk_release,
175 };
176 
177 static struct simdisk *sddev;
178 static struct proc_dir_entry *simdisk_procdir;
179 
180 static int simdisk_attach(struct simdisk *dev, const char *filename)
181 {
182 	int err = 0;
183 
184 	filename = kstrdup(filename, GFP_KERNEL);
185 	if (filename == NULL)
186 		return -ENOMEM;
187 
188 	spin_lock(&dev->lock);
189 
190 	if (dev->fd != -1) {
191 		err = -EBUSY;
192 		goto out;
193 	}
194 	dev->fd = simc_open(filename, O_RDWR, 0);
195 	if (dev->fd == -1) {
196 		pr_err("SIMDISK: Can't open %s: %d\n", filename, errno);
197 		err = -ENODEV;
198 		goto out;
199 	}
200 	dev->size = simc_lseek(dev->fd, 0, SEEK_END);
201 	set_capacity(dev->gd, dev->size >> SECTOR_SHIFT);
202 	dev->filename = filename;
203 	pr_info("SIMDISK: %s=%s\n", dev->gd->disk_name, dev->filename);
204 out:
205 	if (err)
206 		kfree(filename);
207 	spin_unlock(&dev->lock);
208 
209 	return err;
210 }
211 
212 static int simdisk_detach(struct simdisk *dev)
213 {
214 	int err = 0;
215 
216 	spin_lock(&dev->lock);
217 
218 	if (dev->users != 0) {
219 		err = -EBUSY;
220 	} else if (dev->fd != -1) {
221 		if (simc_close(dev->fd)) {
222 			pr_err("SIMDISK: error closing %s: %d\n",
223 					dev->filename, errno);
224 			err = -EIO;
225 		} else {
226 			pr_info("SIMDISK: %s detached from %s\n",
227 					dev->gd->disk_name, dev->filename);
228 			dev->fd = -1;
229 			kfree(dev->filename);
230 			dev->filename = NULL;
231 		}
232 	}
233 	spin_unlock(&dev->lock);
234 	return err;
235 }
236 
237 static ssize_t proc_read_simdisk(struct file *file, char __user *buf,
238 			size_t size, loff_t *ppos)
239 {
240 	struct simdisk *dev = pde_data(file_inode(file));
241 	const char *s = dev->filename;
242 	if (s) {
243 		ssize_t len = strlen(s);
244 		char *temp = kmalloc(len + 2, GFP_KERNEL);
245 
246 		if (!temp)
247 			return -ENOMEM;
248 
249 		len = scnprintf(temp, len + 2, "%s\n", s);
250 		len = simple_read_from_buffer(buf, size, ppos,
251 					      temp, len);
252 
253 		kfree(temp);
254 		return len;
255 	}
256 	return simple_read_from_buffer(buf, size, ppos, "\n", 1);
257 }
258 
259 static ssize_t proc_write_simdisk(struct file *file, const char __user *buf,
260 			size_t count, loff_t *ppos)
261 {
262 	char *tmp;
263 	struct simdisk *dev = pde_data(file_inode(file));
264 	int err;
265 
266 	if (count == 0 || count > PAGE_SIZE)
267 		return -EINVAL;
268 
269 	tmp = memdup_user_nul(buf, count);
270 	if (IS_ERR(tmp))
271 		return PTR_ERR(tmp);
272 
273 	err = simdisk_detach(dev);
274 	if (err != 0)
275 		goto out_free;
276 
277 	if (count > 0 && tmp[count - 1] == '\n')
278 		tmp[count - 1] = 0;
279 
280 	if (tmp[0])
281 		err = simdisk_attach(dev, tmp);
282 
283 	if (err == 0)
284 		err = count;
285 out_free:
286 	kfree(tmp);
287 	return err;
288 }
289 
290 static const struct proc_ops simdisk_proc_ops = {
291 	.proc_read	= proc_read_simdisk,
292 	.proc_write	= proc_write_simdisk,
293 	.proc_lseek	= default_llseek,
294 };
295 
296 static int __init simdisk_setup(struct simdisk *dev, int which,
297 		struct proc_dir_entry *procdir)
298 {
299 	struct queue_limits lim = {
300 		.features		= BLK_FEAT_ROTATIONAL,
301 	};
302 	char tmp[2] = { '0' + which, 0 };
303 	int err;
304 
305 	dev->fd = -1;
306 	dev->filename = NULL;
307 	spin_lock_init(&dev->lock);
308 	dev->users = 0;
309 
310 	dev->gd = blk_alloc_disk(&lim, NUMA_NO_NODE);
311 	if (IS_ERR(dev->gd)) {
312 		err = PTR_ERR(dev->gd);
313 		goto out;
314 	}
315 	dev->gd->major = simdisk_major;
316 	dev->gd->first_minor = which;
317 	dev->gd->minors = SIMDISK_MINORS;
318 	dev->gd->fops = &simdisk_ops;
319 	dev->gd->private_data = dev;
320 	snprintf(dev->gd->disk_name, 32, "simdisk%d", which);
321 	set_capacity(dev->gd, 0);
322 	err = add_disk(dev->gd);
323 	if (err)
324 		goto out_cleanup_disk;
325 
326 	dev->procfile = proc_create_data(tmp, 0644, procdir, &simdisk_proc_ops, dev);
327 
328 	return 0;
329 
330 out_cleanup_disk:
331 	put_disk(dev->gd);
332 out:
333 	return err;
334 }
335 
336 static int __init simdisk_init(void)
337 {
338 	int i;
339 
340 	if (register_blkdev(simdisk_major, "simdisk") < 0) {
341 		pr_err("SIMDISK: register_blkdev: %d\n", simdisk_major);
342 		return -EIO;
343 	}
344 	pr_info("SIMDISK: major: %d\n", simdisk_major);
345 
346 	if (n_files > simdisk_count)
347 		simdisk_count = n_files;
348 	if (simdisk_count > MAX_SIMDISK_COUNT)
349 		simdisk_count = MAX_SIMDISK_COUNT;
350 
351 	sddev = kmalloc_objs(*sddev, simdisk_count);
352 	if (sddev == NULL)
353 		goto out_unregister;
354 
355 	simdisk_procdir = proc_mkdir("simdisk", 0);
356 	if (simdisk_procdir == NULL)
357 		goto out_free_unregister;
358 
359 	for (i = 0; i < simdisk_count; ++i) {
360 		if (simdisk_setup(sddev + i, i, simdisk_procdir) == 0) {
361 			if (filename[i] != NULL && filename[i][0] != 0 &&
362 					(n_files == 0 || i < n_files))
363 				simdisk_attach(sddev + i, filename[i]);
364 		}
365 	}
366 
367 	return 0;
368 
369 out_free_unregister:
370 	kfree(sddev);
371 out_unregister:
372 	unregister_blkdev(simdisk_major, "simdisk");
373 	return -ENOMEM;
374 }
375 module_init(simdisk_init);
376 
377 static void simdisk_teardown(struct simdisk *dev, int which,
378 		struct proc_dir_entry *procdir)
379 {
380 	char tmp[2] = { '0' + which, 0 };
381 
382 	simdisk_detach(dev);
383 	if (dev->gd) {
384 		del_gendisk(dev->gd);
385 		put_disk(dev->gd);
386 	}
387 	remove_proc_entry(tmp, procdir);
388 }
389 
390 static void __exit simdisk_exit(void)
391 {
392 	int i;
393 
394 	for (i = 0; i < simdisk_count; ++i)
395 		simdisk_teardown(sddev + i, i, simdisk_procdir);
396 	remove_proc_entry("simdisk", 0);
397 	kfree(sddev);
398 	unregister_blkdev(simdisk_major, "simdisk");
399 }
400 module_exit(simdisk_exit);
401 
402 MODULE_ALIAS_BLOCKDEV_MAJOR(SIMDISK_MAJOR);
403 
404 MODULE_LICENSE("GPL");
405