1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * dcssblk.c -- the S/390 block driver for dcss memory |
4 | * |
5 | * Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer |
6 | */ |
7 | |
8 | #define KMSG_COMPONENT "dcssblk" |
9 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/moduleparam.h> |
13 | #include <linux/ctype.h> |
14 | #include <linux/errno.h> |
15 | #include <linux/init.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/blkdev.h> |
18 | #include <linux/completion.h> |
19 | #include <linux/interrupt.h> |
20 | #include <linux/pfn_t.h> |
21 | #include <linux/uio.h> |
22 | #include <linux/dax.h> |
23 | #include <linux/io.h> |
24 | #include <asm/extmem.h> |
25 | |
26 | #define DCSSBLK_NAME "dcssblk" |
27 | #define DCSSBLK_MINORS_PER_DISK 1 |
28 | #define DCSSBLK_PARM_LEN 400 |
29 | #define DCSS_BUS_ID_SIZE 20 |
30 | |
31 | static int dcssblk_open(struct gendisk *disk, blk_mode_t mode); |
32 | static void dcssblk_release(struct gendisk *disk); |
33 | static void dcssblk_submit_bio(struct bio *bio); |
34 | static long dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, |
35 | long nr_pages, enum dax_access_mode mode, void **kaddr, |
36 | pfn_t *pfn); |
37 | |
38 | static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0" ; |
39 | |
40 | static int dcssblk_major; |
41 | static const struct block_device_operations dcssblk_devops = { |
42 | .owner = THIS_MODULE, |
43 | .submit_bio = dcssblk_submit_bio, |
44 | .open = dcssblk_open, |
45 | .release = dcssblk_release, |
46 | }; |
47 | |
48 | static int dcssblk_dax_zero_page_range(struct dax_device *dax_dev, |
49 | pgoff_t pgoff, size_t nr_pages) |
50 | { |
51 | long rc; |
52 | void *kaddr; |
53 | |
54 | rc = dax_direct_access(dax_dev, pgoff, nr_pages, mode: DAX_ACCESS, |
55 | kaddr: &kaddr, NULL); |
56 | if (rc < 0) |
57 | return dax_mem2blk_err(err: rc); |
58 | |
59 | memset(kaddr, 0, nr_pages << PAGE_SHIFT); |
60 | dax_flush(dax_dev, addr: kaddr, size: nr_pages << PAGE_SHIFT); |
61 | return 0; |
62 | } |
63 | |
64 | static const struct dax_operations dcssblk_dax_ops = { |
65 | .direct_access = dcssblk_dax_direct_access, |
66 | .zero_page_range = dcssblk_dax_zero_page_range, |
67 | }; |
68 | |
69 | struct dcssblk_dev_info { |
70 | struct list_head lh; |
71 | struct device dev; |
72 | char segment_name[DCSS_BUS_ID_SIZE]; |
73 | atomic_t use_count; |
74 | struct gendisk *gd; |
75 | unsigned long start; |
76 | unsigned long end; |
77 | int segment_type; |
78 | unsigned char save_pending; |
79 | unsigned char is_shared; |
80 | int num_of_segments; |
81 | struct list_head seg_list; |
82 | struct dax_device *dax_dev; |
83 | }; |
84 | |
85 | struct segment_info { |
86 | struct list_head lh; |
87 | char segment_name[DCSS_BUS_ID_SIZE]; |
88 | unsigned long start; |
89 | unsigned long end; |
90 | int segment_type; |
91 | }; |
92 | |
93 | static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf, |
94 | size_t count); |
95 | static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf, |
96 | size_t count); |
97 | |
98 | static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store); |
99 | static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store); |
100 | |
101 | static struct device *dcssblk_root_dev; |
102 | |
103 | static LIST_HEAD(dcssblk_devices); |
104 | static struct rw_semaphore dcssblk_devices_sem; |
105 | |
106 | /* |
107 | * release function for segment device. |
108 | */ |
109 | static void |
110 | dcssblk_release_segment(struct device *dev) |
111 | { |
112 | struct dcssblk_dev_info *dev_info; |
113 | struct segment_info *entry, *temp; |
114 | |
115 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); |
116 | list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) { |
117 | list_del(entry: &entry->lh); |
118 | kfree(objp: entry); |
119 | } |
120 | kfree(objp: dev_info); |
121 | module_put(THIS_MODULE); |
122 | } |
123 | |
124 | /* |
125 | * get a minor number. needs to be called with |
126 | * down_write(&dcssblk_devices_sem) and the |
127 | * device needs to be enqueued before the semaphore is |
128 | * freed. |
129 | */ |
130 | static int |
131 | dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info) |
132 | { |
133 | int minor, found; |
134 | struct dcssblk_dev_info *entry; |
135 | |
136 | if (dev_info == NULL) |
137 | return -EINVAL; |
138 | for (minor = 0; minor < (1<<MINORBITS); minor++) { |
139 | found = 0; |
140 | // test if minor available |
141 | list_for_each_entry(entry, &dcssblk_devices, lh) |
142 | if (minor == entry->gd->first_minor) |
143 | found++; |
144 | if (!found) break; // got unused minor |
145 | } |
146 | if (found) |
147 | return -EBUSY; |
148 | dev_info->gd->first_minor = minor; |
149 | return 0; |
150 | } |
151 | |
152 | /* |
153 | * get the struct dcssblk_dev_info from dcssblk_devices |
154 | * for the given name. |
155 | * down_read(&dcssblk_devices_sem) must be held. |
156 | */ |
157 | static struct dcssblk_dev_info * |
158 | dcssblk_get_device_by_name(char *name) |
159 | { |
160 | struct dcssblk_dev_info *entry; |
161 | |
162 | list_for_each_entry(entry, &dcssblk_devices, lh) { |
163 | if (!strcmp(name, entry->segment_name)) { |
164 | return entry; |
165 | } |
166 | } |
167 | return NULL; |
168 | } |
169 | |
170 | /* |
171 | * get the struct segment_info from seg_list |
172 | * for the given name. |
173 | * down_read(&dcssblk_devices_sem) must be held. |
174 | */ |
175 | static struct segment_info * |
176 | dcssblk_get_segment_by_name(char *name) |
177 | { |
178 | struct dcssblk_dev_info *dev_info; |
179 | struct segment_info *entry; |
180 | |
181 | list_for_each_entry(dev_info, &dcssblk_devices, lh) { |
182 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
183 | if (!strcmp(name, entry->segment_name)) |
184 | return entry; |
185 | } |
186 | } |
187 | return NULL; |
188 | } |
189 | |
190 | /* |
191 | * get the highest address of the multi-segment block. |
192 | */ |
193 | static unsigned long |
194 | dcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info) |
195 | { |
196 | unsigned long highest_addr; |
197 | struct segment_info *entry; |
198 | |
199 | highest_addr = 0; |
200 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
201 | if (highest_addr < entry->end) |
202 | highest_addr = entry->end; |
203 | } |
204 | return highest_addr; |
205 | } |
206 | |
207 | /* |
208 | * get the lowest address of the multi-segment block. |
209 | */ |
210 | static unsigned long |
211 | dcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info) |
212 | { |
213 | int set_first; |
214 | unsigned long lowest_addr; |
215 | struct segment_info *entry; |
216 | |
217 | set_first = 0; |
218 | lowest_addr = 0; |
219 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
220 | if (set_first == 0) { |
221 | lowest_addr = entry->start; |
222 | set_first = 1; |
223 | } else { |
224 | if (lowest_addr > entry->start) |
225 | lowest_addr = entry->start; |
226 | } |
227 | } |
228 | return lowest_addr; |
229 | } |
230 | |
231 | /* |
232 | * Check continuity of segments. |
233 | */ |
234 | static int |
235 | dcssblk_is_continuous(struct dcssblk_dev_info *dev_info) |
236 | { |
237 | int i, j, rc; |
238 | struct segment_info *sort_list, *entry, temp; |
239 | |
240 | if (dev_info->num_of_segments <= 1) |
241 | return 0; |
242 | |
243 | sort_list = kcalloc(n: dev_info->num_of_segments, |
244 | size: sizeof(struct segment_info), |
245 | GFP_KERNEL); |
246 | if (sort_list == NULL) |
247 | return -ENOMEM; |
248 | i = 0; |
249 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
250 | memcpy(&sort_list[i], entry, sizeof(struct segment_info)); |
251 | i++; |
252 | } |
253 | |
254 | /* sort segments */ |
255 | for (i = 0; i < dev_info->num_of_segments; i++) |
256 | for (j = 0; j < dev_info->num_of_segments; j++) |
257 | if (sort_list[j].start > sort_list[i].start) { |
258 | memcpy(&temp, &sort_list[i], |
259 | sizeof(struct segment_info)); |
260 | memcpy(&sort_list[i], &sort_list[j], |
261 | sizeof(struct segment_info)); |
262 | memcpy(&sort_list[j], &temp, |
263 | sizeof(struct segment_info)); |
264 | } |
265 | |
266 | /* check continuity */ |
267 | for (i = 0; i < dev_info->num_of_segments - 1; i++) { |
268 | if ((sort_list[i].end + 1) != sort_list[i+1].start) { |
269 | pr_err("Adjacent DCSSs %s and %s are not " |
270 | "contiguous\n" , sort_list[i].segment_name, |
271 | sort_list[i+1].segment_name); |
272 | rc = -EINVAL; |
273 | goto out; |
274 | } |
275 | /* EN and EW are allowed in a block device */ |
276 | if (sort_list[i].segment_type != sort_list[i+1].segment_type) { |
277 | if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) || |
278 | (sort_list[i].segment_type == SEG_TYPE_ER) || |
279 | !(sort_list[i+1].segment_type & |
280 | SEGMENT_EXCLUSIVE) || |
281 | (sort_list[i+1].segment_type == SEG_TYPE_ER)) { |
282 | pr_err("DCSS %s and DCSS %s have " |
283 | "incompatible types\n" , |
284 | sort_list[i].segment_name, |
285 | sort_list[i+1].segment_name); |
286 | rc = -EINVAL; |
287 | goto out; |
288 | } |
289 | } |
290 | } |
291 | rc = 0; |
292 | out: |
293 | kfree(objp: sort_list); |
294 | return rc; |
295 | } |
296 | |
297 | /* |
298 | * Load a segment |
299 | */ |
300 | static int |
301 | dcssblk_load_segment(char *name, struct segment_info **seg_info) |
302 | { |
303 | int rc; |
304 | |
305 | /* already loaded? */ |
306 | down_read(sem: &dcssblk_devices_sem); |
307 | *seg_info = dcssblk_get_segment_by_name(name); |
308 | up_read(sem: &dcssblk_devices_sem); |
309 | if (*seg_info != NULL) |
310 | return -EEXIST; |
311 | |
312 | /* get a struct segment_info */ |
313 | *seg_info = kzalloc(size: sizeof(struct segment_info), GFP_KERNEL); |
314 | if (*seg_info == NULL) |
315 | return -ENOMEM; |
316 | |
317 | strcpy(p: (*seg_info)->segment_name, q: name); |
318 | |
319 | /* load the segment */ |
320 | rc = segment_load(name, SEGMENT_SHARED, |
321 | &(*seg_info)->start, &(*seg_info)->end); |
322 | if (rc < 0) { |
323 | segment_warning(rc, (*seg_info)->segment_name); |
324 | kfree(objp: *seg_info); |
325 | } else { |
326 | INIT_LIST_HEAD(list: &(*seg_info)->lh); |
327 | (*seg_info)->segment_type = rc; |
328 | } |
329 | return rc; |
330 | } |
331 | |
332 | /* |
333 | * device attribute for switching shared/nonshared (exclusive) |
334 | * operation (show + store) |
335 | */ |
336 | static ssize_t |
337 | dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf) |
338 | { |
339 | struct dcssblk_dev_info *dev_info; |
340 | |
341 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); |
342 | return sprintf(buf, fmt: dev_info->is_shared ? "1\n" : "0\n" ); |
343 | } |
344 | |
345 | static ssize_t |
346 | dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count) |
347 | { |
348 | struct dcssblk_dev_info *dev_info; |
349 | struct segment_info *entry, *temp; |
350 | int rc; |
351 | |
352 | if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) |
353 | return -EINVAL; |
354 | down_write(sem: &dcssblk_devices_sem); |
355 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); |
356 | if (atomic_read(v: &dev_info->use_count)) { |
357 | rc = -EBUSY; |
358 | goto out; |
359 | } |
360 | if (inbuf[0] == '1') { |
361 | /* reload segments in shared mode */ |
362 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
363 | rc = segment_modify_shared(entry->segment_name, |
364 | SEGMENT_SHARED); |
365 | if (rc < 0) { |
366 | BUG_ON(rc == -EINVAL); |
367 | if (rc != -EAGAIN) |
368 | goto removeseg; |
369 | } |
370 | } |
371 | dev_info->is_shared = 1; |
372 | switch (dev_info->segment_type) { |
373 | case SEG_TYPE_SR: |
374 | case SEG_TYPE_ER: |
375 | case SEG_TYPE_SC: |
376 | set_disk_ro(dev_info->gd, 1); |
377 | } |
378 | } else if (inbuf[0] == '0') { |
379 | /* reload segments in exclusive mode */ |
380 | if (dev_info->segment_type == SEG_TYPE_SC) { |
381 | pr_err("DCSS %s is of type SC and cannot be " |
382 | "loaded as exclusive-writable\n" , |
383 | dev_info->segment_name); |
384 | rc = -EINVAL; |
385 | goto out; |
386 | } |
387 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
388 | rc = segment_modify_shared(entry->segment_name, |
389 | SEGMENT_EXCLUSIVE); |
390 | if (rc < 0) { |
391 | BUG_ON(rc == -EINVAL); |
392 | if (rc != -EAGAIN) |
393 | goto removeseg; |
394 | } |
395 | } |
396 | dev_info->is_shared = 0; |
397 | set_disk_ro(disk: dev_info->gd, read_only: 0); |
398 | } else { |
399 | rc = -EINVAL; |
400 | goto out; |
401 | } |
402 | rc = count; |
403 | goto out; |
404 | |
405 | removeseg: |
406 | pr_err("DCSS device %s is removed after a failed access mode " |
407 | "change\n" , dev_info->segment_name); |
408 | temp = entry; |
409 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
410 | if (entry != temp) |
411 | segment_unload(entry->segment_name); |
412 | } |
413 | list_del(entry: &dev_info->lh); |
414 | up_write(sem: &dcssblk_devices_sem); |
415 | |
416 | dax_remove_host(disk: dev_info->gd); |
417 | kill_dax(dax_dev: dev_info->dax_dev); |
418 | put_dax(dax_dev: dev_info->dax_dev); |
419 | del_gendisk(gp: dev_info->gd); |
420 | put_disk(disk: dev_info->gd); |
421 | |
422 | if (device_remove_file_self(dev, attr)) { |
423 | device_unregister(dev); |
424 | put_device(dev); |
425 | } |
426 | return rc; |
427 | out: |
428 | up_write(sem: &dcssblk_devices_sem); |
429 | return rc; |
430 | } |
431 | static DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show, |
432 | dcssblk_shared_store); |
433 | |
434 | /* |
435 | * device attribute for save operation on current copy |
436 | * of the segment. If the segment is busy, saving will |
437 | * become pending until it gets released, which can be |
438 | * undone by storing a non-true value to this entry. |
439 | * (show + store) |
440 | */ |
441 | static ssize_t |
442 | dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf) |
443 | { |
444 | struct dcssblk_dev_info *dev_info; |
445 | |
446 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); |
447 | return sprintf(buf, fmt: dev_info->save_pending ? "1\n" : "0\n" ); |
448 | } |
449 | |
450 | static ssize_t |
451 | dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count) |
452 | { |
453 | struct dcssblk_dev_info *dev_info; |
454 | struct segment_info *entry; |
455 | |
456 | if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) |
457 | return -EINVAL; |
458 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); |
459 | |
460 | down_write(sem: &dcssblk_devices_sem); |
461 | if (inbuf[0] == '1') { |
462 | if (atomic_read(v: &dev_info->use_count) == 0) { |
463 | // device is idle => we save immediately |
464 | pr_info("All DCSSs that map to device %s are " |
465 | "saved\n" , dev_info->segment_name); |
466 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
467 | if (entry->segment_type == SEG_TYPE_EN || |
468 | entry->segment_type == SEG_TYPE_SN) |
469 | pr_warn("DCSS %s is of type SN or EN" |
470 | " and cannot be saved\n" , |
471 | entry->segment_name); |
472 | else |
473 | segment_save(entry->segment_name); |
474 | } |
475 | } else { |
476 | // device is busy => we save it when it becomes |
477 | // idle in dcssblk_release |
478 | pr_info("Device %s is in use, its DCSSs will be " |
479 | "saved when it becomes idle\n" , |
480 | dev_info->segment_name); |
481 | dev_info->save_pending = 1; |
482 | } |
483 | } else if (inbuf[0] == '0') { |
484 | if (dev_info->save_pending) { |
485 | // device is busy & the user wants to undo his save |
486 | // request |
487 | dev_info->save_pending = 0; |
488 | pr_info("A pending save request for device %s " |
489 | "has been canceled\n" , |
490 | dev_info->segment_name); |
491 | } |
492 | } else { |
493 | up_write(sem: &dcssblk_devices_sem); |
494 | return -EINVAL; |
495 | } |
496 | up_write(sem: &dcssblk_devices_sem); |
497 | return count; |
498 | } |
499 | static DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show, |
500 | dcssblk_save_store); |
501 | |
502 | /* |
503 | * device attribute for showing all segments in a device |
504 | */ |
505 | static ssize_t |
506 | dcssblk_seglist_show(struct device *dev, struct device_attribute *attr, |
507 | char *buf) |
508 | { |
509 | int i; |
510 | |
511 | struct dcssblk_dev_info *dev_info; |
512 | struct segment_info *entry; |
513 | |
514 | down_read(sem: &dcssblk_devices_sem); |
515 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); |
516 | i = 0; |
517 | buf[0] = '\0'; |
518 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
519 | strcpy(p: &buf[i], q: entry->segment_name); |
520 | i += strlen(entry->segment_name); |
521 | buf[i] = '\n'; |
522 | i++; |
523 | } |
524 | up_read(sem: &dcssblk_devices_sem); |
525 | return i; |
526 | } |
527 | static DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL); |
528 | |
529 | static struct attribute *dcssblk_dev_attrs[] = { |
530 | &dev_attr_shared.attr, |
531 | &dev_attr_save.attr, |
532 | &dev_attr_seglist.attr, |
533 | NULL, |
534 | }; |
535 | static struct attribute_group dcssblk_dev_attr_group = { |
536 | .attrs = dcssblk_dev_attrs, |
537 | }; |
538 | static const struct attribute_group *dcssblk_dev_attr_groups[] = { |
539 | &dcssblk_dev_attr_group, |
540 | NULL, |
541 | }; |
542 | |
543 | /* |
544 | * device attribute for adding devices |
545 | */ |
546 | static ssize_t |
547 | dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) |
548 | { |
549 | int rc, i, j, num_of_segments; |
550 | struct dcssblk_dev_info *dev_info; |
551 | struct segment_info *seg_info, *temp; |
552 | char *local_buf; |
553 | unsigned long seg_byte_size; |
554 | |
555 | dev_info = NULL; |
556 | seg_info = NULL; |
557 | if (dev != dcssblk_root_dev) { |
558 | rc = -EINVAL; |
559 | goto out_nobuf; |
560 | } |
561 | if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) { |
562 | rc = -ENAMETOOLONG; |
563 | goto out_nobuf; |
564 | } |
565 | |
566 | local_buf = kmalloc(size: count + 1, GFP_KERNEL); |
567 | if (local_buf == NULL) { |
568 | rc = -ENOMEM; |
569 | goto out_nobuf; |
570 | } |
571 | |
572 | /* |
573 | * parse input |
574 | */ |
575 | num_of_segments = 0; |
576 | for (i = 0; (i < count && (buf[i] != '\0') && (buf[i] != '\n')); i++) { |
577 | for (j = i; j < count && |
578 | (buf[j] != ':') && |
579 | (buf[j] != '\0') && |
580 | (buf[j] != '\n'); j++) { |
581 | local_buf[j-i] = toupper(buf[j]); |
582 | } |
583 | local_buf[j-i] = '\0'; |
584 | if (((j - i) == 0) || ((j - i) > 8)) { |
585 | rc = -ENAMETOOLONG; |
586 | goto seg_list_del; |
587 | } |
588 | |
589 | rc = dcssblk_load_segment(name: local_buf, seg_info: &seg_info); |
590 | if (rc < 0) |
591 | goto seg_list_del; |
592 | /* |
593 | * get a struct dcssblk_dev_info |
594 | */ |
595 | if (num_of_segments == 0) { |
596 | dev_info = kzalloc(size: sizeof(struct dcssblk_dev_info), |
597 | GFP_KERNEL); |
598 | if (dev_info == NULL) { |
599 | rc = -ENOMEM; |
600 | goto out; |
601 | } |
602 | strcpy(p: dev_info->segment_name, q: local_buf); |
603 | dev_info->segment_type = seg_info->segment_type; |
604 | INIT_LIST_HEAD(list: &dev_info->seg_list); |
605 | } |
606 | list_add_tail(new: &seg_info->lh, head: &dev_info->seg_list); |
607 | num_of_segments++; |
608 | i = j; |
609 | |
610 | if ((buf[j] == '\0') || (buf[j] == '\n')) |
611 | break; |
612 | } |
613 | |
614 | /* no trailing colon at the end of the input */ |
615 | if ((i > 0) && (buf[i-1] == ':')) { |
616 | rc = -ENAMETOOLONG; |
617 | goto seg_list_del; |
618 | } |
619 | strscpy(p: local_buf, q: buf, size: i + 1); |
620 | dev_info->num_of_segments = num_of_segments; |
621 | rc = dcssblk_is_continuous(dev_info); |
622 | if (rc < 0) |
623 | goto seg_list_del; |
624 | |
625 | dev_info->start = dcssblk_find_lowest_addr(dev_info); |
626 | dev_info->end = dcssblk_find_highest_addr(dev_info); |
627 | |
628 | dev_set_name(dev: &dev_info->dev, name: "%s" , dev_info->segment_name); |
629 | dev_info->dev.release = dcssblk_release_segment; |
630 | dev_info->dev.groups = dcssblk_dev_attr_groups; |
631 | INIT_LIST_HEAD(list: &dev_info->lh); |
632 | dev_info->gd = blk_alloc_disk(NUMA_NO_NODE); |
633 | if (dev_info->gd == NULL) { |
634 | rc = -ENOMEM; |
635 | goto seg_list_del; |
636 | } |
637 | dev_info->gd->major = dcssblk_major; |
638 | dev_info->gd->minors = DCSSBLK_MINORS_PER_DISK; |
639 | dev_info->gd->fops = &dcssblk_devops; |
640 | dev_info->gd->private_data = dev_info; |
641 | dev_info->gd->flags |= GENHD_FL_NO_PART; |
642 | blk_queue_logical_block_size(dev_info->gd->queue, 4096); |
643 | blk_queue_flag_set(QUEUE_FLAG_DAX, q: dev_info->gd->queue); |
644 | |
645 | seg_byte_size = (dev_info->end - dev_info->start + 1); |
646 | set_capacity(disk: dev_info->gd, size: seg_byte_size >> 9); // size in sectors |
647 | pr_info("Loaded %s with total size %lu bytes and capacity %lu " |
648 | "sectors\n" , local_buf, seg_byte_size, seg_byte_size >> 9); |
649 | |
650 | dev_info->save_pending = 0; |
651 | dev_info->is_shared = 1; |
652 | dev_info->dev.parent = dcssblk_root_dev; |
653 | |
654 | /* |
655 | *get minor, add to list |
656 | */ |
657 | down_write(sem: &dcssblk_devices_sem); |
658 | if (dcssblk_get_segment_by_name(name: local_buf)) { |
659 | rc = -EEXIST; |
660 | goto release_gd; |
661 | } |
662 | rc = dcssblk_assign_free_minor(dev_info); |
663 | if (rc) |
664 | goto release_gd; |
665 | sprintf(buf: dev_info->gd->disk_name, fmt: "dcssblk%d" , |
666 | dev_info->gd->first_minor); |
667 | list_add_tail(new: &dev_info->lh, head: &dcssblk_devices); |
668 | |
669 | if (!try_module_get(THIS_MODULE)) { |
670 | rc = -ENODEV; |
671 | goto dev_list_del; |
672 | } |
673 | /* |
674 | * register the device |
675 | */ |
676 | rc = device_register(dev: &dev_info->dev); |
677 | if (rc) |
678 | goto put_dev; |
679 | |
680 | dev_info->dax_dev = alloc_dax(private: dev_info, ops: &dcssblk_dax_ops); |
681 | if (IS_ERR(ptr: dev_info->dax_dev)) { |
682 | rc = PTR_ERR(ptr: dev_info->dax_dev); |
683 | dev_info->dax_dev = NULL; |
684 | goto put_dev; |
685 | } |
686 | set_dax_synchronous(dev_info->dax_dev); |
687 | rc = dax_add_host(dax_dev: dev_info->dax_dev, disk: dev_info->gd); |
688 | if (rc) |
689 | goto out_dax; |
690 | |
691 | get_device(dev: &dev_info->dev); |
692 | rc = device_add_disk(parent: &dev_info->dev, disk: dev_info->gd, NULL); |
693 | if (rc) |
694 | goto out_dax_host; |
695 | |
696 | switch (dev_info->segment_type) { |
697 | case SEG_TYPE_SR: |
698 | case SEG_TYPE_ER: |
699 | case SEG_TYPE_SC: |
700 | set_disk_ro(dev_info->gd,1); |
701 | break; |
702 | default: |
703 | set_disk_ro(dev_info->gd,0); |
704 | break; |
705 | } |
706 | up_write(sem: &dcssblk_devices_sem); |
707 | rc = count; |
708 | goto out; |
709 | |
710 | out_dax_host: |
711 | put_device(dev: &dev_info->dev); |
712 | dax_remove_host(disk: dev_info->gd); |
713 | out_dax: |
714 | kill_dax(dax_dev: dev_info->dax_dev); |
715 | put_dax(dax_dev: dev_info->dax_dev); |
716 | put_dev: |
717 | list_del(entry: &dev_info->lh); |
718 | put_disk(disk: dev_info->gd); |
719 | list_for_each_entry(seg_info, &dev_info->seg_list, lh) { |
720 | segment_unload(seg_info->segment_name); |
721 | } |
722 | put_device(dev: &dev_info->dev); |
723 | up_write(sem: &dcssblk_devices_sem); |
724 | goto out; |
725 | dev_list_del: |
726 | list_del(entry: &dev_info->lh); |
727 | release_gd: |
728 | put_disk(disk: dev_info->gd); |
729 | up_write(sem: &dcssblk_devices_sem); |
730 | seg_list_del: |
731 | if (dev_info == NULL) |
732 | goto out; |
733 | list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) { |
734 | list_del(entry: &seg_info->lh); |
735 | segment_unload(seg_info->segment_name); |
736 | kfree(objp: seg_info); |
737 | } |
738 | kfree(objp: dev_info); |
739 | out: |
740 | kfree(objp: local_buf); |
741 | out_nobuf: |
742 | return rc; |
743 | } |
744 | |
745 | /* |
746 | * device attribute for removing devices |
747 | */ |
748 | static ssize_t |
749 | dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) |
750 | { |
751 | struct dcssblk_dev_info *dev_info; |
752 | struct segment_info *entry; |
753 | int rc, i; |
754 | char *local_buf; |
755 | |
756 | if (dev != dcssblk_root_dev) { |
757 | return -EINVAL; |
758 | } |
759 | local_buf = kmalloc(size: count + 1, GFP_KERNEL); |
760 | if (local_buf == NULL) { |
761 | return -ENOMEM; |
762 | } |
763 | /* |
764 | * parse input |
765 | */ |
766 | for (i = 0; (i < count && (*(buf+i)!='\0') && (*(buf+i)!='\n')); i++) { |
767 | local_buf[i] = toupper(buf[i]); |
768 | } |
769 | local_buf[i] = '\0'; |
770 | if ((i == 0) || (i > 8)) { |
771 | rc = -ENAMETOOLONG; |
772 | goto out_buf; |
773 | } |
774 | |
775 | down_write(sem: &dcssblk_devices_sem); |
776 | dev_info = dcssblk_get_device_by_name(name: local_buf); |
777 | if (dev_info == NULL) { |
778 | up_write(sem: &dcssblk_devices_sem); |
779 | pr_warn("Device %s cannot be removed because it is not a known device\n" , |
780 | local_buf); |
781 | rc = -ENODEV; |
782 | goto out_buf; |
783 | } |
784 | if (atomic_read(v: &dev_info->use_count) != 0) { |
785 | up_write(sem: &dcssblk_devices_sem); |
786 | pr_warn("Device %s cannot be removed while it is in use\n" , |
787 | local_buf); |
788 | rc = -EBUSY; |
789 | goto out_buf; |
790 | } |
791 | |
792 | list_del(entry: &dev_info->lh); |
793 | /* unload all related segments */ |
794 | list_for_each_entry(entry, &dev_info->seg_list, lh) |
795 | segment_unload(entry->segment_name); |
796 | up_write(sem: &dcssblk_devices_sem); |
797 | |
798 | dax_remove_host(disk: dev_info->gd); |
799 | kill_dax(dax_dev: dev_info->dax_dev); |
800 | put_dax(dax_dev: dev_info->dax_dev); |
801 | del_gendisk(gp: dev_info->gd); |
802 | put_disk(disk: dev_info->gd); |
803 | |
804 | device_unregister(dev: &dev_info->dev); |
805 | put_device(dev: &dev_info->dev); |
806 | |
807 | rc = count; |
808 | out_buf: |
809 | kfree(objp: local_buf); |
810 | return rc; |
811 | } |
812 | |
813 | static int |
814 | dcssblk_open(struct gendisk *disk, blk_mode_t mode) |
815 | { |
816 | struct dcssblk_dev_info *dev_info = disk->private_data; |
817 | int rc; |
818 | |
819 | if (NULL == dev_info) { |
820 | rc = -ENODEV; |
821 | goto out; |
822 | } |
823 | atomic_inc(v: &dev_info->use_count); |
824 | rc = 0; |
825 | out: |
826 | return rc; |
827 | } |
828 | |
829 | static void |
830 | dcssblk_release(struct gendisk *disk) |
831 | { |
832 | struct dcssblk_dev_info *dev_info = disk->private_data; |
833 | struct segment_info *entry; |
834 | |
835 | if (!dev_info) { |
836 | WARN_ON(1); |
837 | return; |
838 | } |
839 | down_write(sem: &dcssblk_devices_sem); |
840 | if (atomic_dec_and_test(v: &dev_info->use_count) |
841 | && (dev_info->save_pending)) { |
842 | pr_info("Device %s has become idle and is being saved " |
843 | "now\n" , dev_info->segment_name); |
844 | list_for_each_entry(entry, &dev_info->seg_list, lh) { |
845 | if (entry->segment_type == SEG_TYPE_EN || |
846 | entry->segment_type == SEG_TYPE_SN) |
847 | pr_warn("DCSS %s is of type SN or EN and cannot" |
848 | " be saved\n" , entry->segment_name); |
849 | else |
850 | segment_save(entry->segment_name); |
851 | } |
852 | dev_info->save_pending = 0; |
853 | } |
854 | up_write(sem: &dcssblk_devices_sem); |
855 | } |
856 | |
857 | static void |
858 | dcssblk_submit_bio(struct bio *bio) |
859 | { |
860 | struct dcssblk_dev_info *dev_info; |
861 | struct bio_vec bvec; |
862 | struct bvec_iter iter; |
863 | unsigned long index; |
864 | void *page_addr; |
865 | unsigned long source_addr; |
866 | unsigned long bytes_done; |
867 | |
868 | bytes_done = 0; |
869 | dev_info = bio->bi_bdev->bd_disk->private_data; |
870 | if (dev_info == NULL) |
871 | goto fail; |
872 | if (!IS_ALIGNED(bio->bi_iter.bi_sector, 8) || |
873 | !IS_ALIGNED(bio->bi_iter.bi_size, PAGE_SIZE)) |
874 | /* Request is not page-aligned. */ |
875 | goto fail; |
876 | /* verify data transfer direction */ |
877 | if (dev_info->is_shared) { |
878 | switch (dev_info->segment_type) { |
879 | case SEG_TYPE_SR: |
880 | case SEG_TYPE_ER: |
881 | case SEG_TYPE_SC: |
882 | /* cannot write to these segments */ |
883 | if (bio_data_dir(bio) == WRITE) { |
884 | pr_warn("Writing to %s failed because it is a read-only device\n" , |
885 | dev_name(&dev_info->dev)); |
886 | goto fail; |
887 | } |
888 | } |
889 | } |
890 | |
891 | index = (bio->bi_iter.bi_sector >> 3); |
892 | bio_for_each_segment(bvec, bio, iter) { |
893 | page_addr = bvec_virt(bvec: &bvec); |
894 | source_addr = dev_info->start + (index<<12) + bytes_done; |
895 | if (unlikely(!IS_ALIGNED((unsigned long)page_addr, PAGE_SIZE) || |
896 | !IS_ALIGNED(bvec.bv_len, PAGE_SIZE))) |
897 | // More paranoia. |
898 | goto fail; |
899 | if (bio_data_dir(bio) == READ) |
900 | memcpy(page_addr, __va(source_addr), bvec.bv_len); |
901 | else |
902 | memcpy(__va(source_addr), page_addr, bvec.bv_len); |
903 | bytes_done += bvec.bv_len; |
904 | } |
905 | bio_endio(bio); |
906 | return; |
907 | fail: |
908 | bio_io_error(bio); |
909 | } |
910 | |
911 | static long |
912 | __dcssblk_direct_access(struct dcssblk_dev_info *dev_info, pgoff_t pgoff, |
913 | long nr_pages, void **kaddr, pfn_t *pfn) |
914 | { |
915 | resource_size_t offset = pgoff * PAGE_SIZE; |
916 | unsigned long dev_sz; |
917 | |
918 | dev_sz = dev_info->end - dev_info->start + 1; |
919 | if (kaddr) |
920 | *kaddr = (void *) dev_info->start + offset; |
921 | if (pfn) |
922 | *pfn = __pfn_to_pfn_t(PFN_DOWN(dev_info->start + offset), |
923 | PFN_DEV|PFN_SPECIAL); |
924 | |
925 | return (dev_sz - offset) / PAGE_SIZE; |
926 | } |
927 | |
928 | static long |
929 | dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, |
930 | long nr_pages, enum dax_access_mode mode, void **kaddr, |
931 | pfn_t *pfn) |
932 | { |
933 | struct dcssblk_dev_info *dev_info = dax_get_private(dax_dev); |
934 | |
935 | return __dcssblk_direct_access(dev_info, pgoff, nr_pages, kaddr, pfn); |
936 | } |
937 | |
938 | static void |
939 | dcssblk_check_params(void) |
940 | { |
941 | int rc, i, j, k; |
942 | char buf[DCSSBLK_PARM_LEN + 1]; |
943 | struct dcssblk_dev_info *dev_info; |
944 | |
945 | for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0'); |
946 | i++) { |
947 | for (j = i; (j < DCSSBLK_PARM_LEN) && |
948 | (dcssblk_segments[j] != ',') && |
949 | (dcssblk_segments[j] != '\0') && |
950 | (dcssblk_segments[j] != '('); j++) |
951 | { |
952 | buf[j-i] = dcssblk_segments[j]; |
953 | } |
954 | buf[j-i] = '\0'; |
955 | rc = dcssblk_add_store(dev: dcssblk_root_dev, NULL, buf, count: j-i); |
956 | if ((rc >= 0) && (dcssblk_segments[j] == '(')) { |
957 | for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++) |
958 | buf[k] = toupper(buf[k]); |
959 | buf[k] = '\0'; |
960 | if (!strncmp(&dcssblk_segments[j], "(local)" , 7)) { |
961 | down_read(sem: &dcssblk_devices_sem); |
962 | dev_info = dcssblk_get_device_by_name(name: buf); |
963 | up_read(sem: &dcssblk_devices_sem); |
964 | if (dev_info) |
965 | dcssblk_shared_store(dev: &dev_info->dev, |
966 | NULL, inbuf: "0\n" , count: 2); |
967 | } |
968 | } |
969 | while ((dcssblk_segments[j] != ',') && |
970 | (dcssblk_segments[j] != '\0')) |
971 | { |
972 | j++; |
973 | } |
974 | if (dcssblk_segments[j] == '\0') |
975 | break; |
976 | i = j; |
977 | } |
978 | } |
979 | |
980 | /* |
981 | * The init/exit functions. |
982 | */ |
983 | static void __exit |
984 | dcssblk_exit(void) |
985 | { |
986 | root_device_unregister(root: dcssblk_root_dev); |
987 | unregister_blkdev(major: dcssblk_major, DCSSBLK_NAME); |
988 | } |
989 | |
990 | static int __init |
991 | dcssblk_init(void) |
992 | { |
993 | int rc; |
994 | |
995 | dcssblk_root_dev = root_device_register("dcssblk" ); |
996 | if (IS_ERR(ptr: dcssblk_root_dev)) |
997 | return PTR_ERR(ptr: dcssblk_root_dev); |
998 | rc = device_create_file(device: dcssblk_root_dev, entry: &dev_attr_add); |
999 | if (rc) |
1000 | goto out_root; |
1001 | rc = device_create_file(device: dcssblk_root_dev, entry: &dev_attr_remove); |
1002 | if (rc) |
1003 | goto out_root; |
1004 | rc = register_blkdev(0, DCSSBLK_NAME); |
1005 | if (rc < 0) |
1006 | goto out_root; |
1007 | dcssblk_major = rc; |
1008 | init_rwsem(&dcssblk_devices_sem); |
1009 | |
1010 | dcssblk_check_params(); |
1011 | return 0; |
1012 | |
1013 | out_root: |
1014 | root_device_unregister(root: dcssblk_root_dev); |
1015 | |
1016 | return rc; |
1017 | } |
1018 | |
1019 | module_init(dcssblk_init); |
1020 | module_exit(dcssblk_exit); |
1021 | |
1022 | module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444); |
1023 | MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, " |
1024 | "comma-separated list, names in each set separated " |
1025 | "by commas are separated by colons, each set contains " |
1026 | "names of contiguous segments and each name max. 8 chars.\n" |
1027 | "Adding \"(local)\" to the end of each set equals echoing 0 " |
1028 | "to /sys/devices/dcssblk/<device name>/shared after loading " |
1029 | "the contiguous segments - \n" |
1030 | "e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"" ); |
1031 | |
1032 | MODULE_LICENSE("GPL" ); |
1033 | |