diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/block/dcssblk.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/s390/block/dcssblk.c')
-rw-r--r-- | drivers/s390/block/dcssblk.c | 775 |
1 files changed, 775 insertions, 0 deletions
diff --git a/drivers/s390/block/dcssblk.c b/drivers/s390/block/dcssblk.c new file mode 100644 index 000000000000..a66b17b65296 --- /dev/null +++ b/drivers/s390/block/dcssblk.c | |||
@@ -0,0 +1,775 @@ | |||
1 | /* | ||
2 | * dcssblk.c -- the S/390 block driver for dcss memory | ||
3 | * | ||
4 | * Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer | ||
5 | */ | ||
6 | |||
7 | #include <linux/module.h> | ||
8 | #include <linux/moduleparam.h> | ||
9 | #include <linux/ctype.h> | ||
10 | #include <linux/errno.h> | ||
11 | #include <linux/init.h> | ||
12 | #include <linux/slab.h> | ||
13 | #include <linux/blkdev.h> | ||
14 | #include <asm/extmem.h> | ||
15 | #include <asm/io.h> | ||
16 | #include <linux/completion.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <asm/ccwdev.h> // for s390_root_dev_(un)register() | ||
19 | |||
20 | //#define DCSSBLK_DEBUG /* Debug messages on/off */ | ||
21 | #define DCSSBLK_NAME "dcssblk" | ||
22 | #define DCSSBLK_MINORS_PER_DISK 1 | ||
23 | #define DCSSBLK_PARM_LEN 400 | ||
24 | |||
25 | #ifdef DCSSBLK_DEBUG | ||
26 | #define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSSBLK_NAME " debug: " x) | ||
27 | #else | ||
28 | #define PRINT_DEBUG(x...) do {} while (0) | ||
29 | #endif | ||
30 | #define PRINT_INFO(x...) printk(KERN_INFO DCSSBLK_NAME " info: " x) | ||
31 | #define PRINT_WARN(x...) printk(KERN_WARNING DCSSBLK_NAME " warning: " x) | ||
32 | #define PRINT_ERR(x...) printk(KERN_ERR DCSSBLK_NAME " error: " x) | ||
33 | |||
34 | |||
35 | static int dcssblk_open(struct inode *inode, struct file *filp); | ||
36 | static int dcssblk_release(struct inode *inode, struct file *filp); | ||
37 | static int dcssblk_make_request(struct request_queue *q, struct bio *bio); | ||
38 | |||
39 | static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0"; | ||
40 | |||
41 | static int dcssblk_major; | ||
42 | static struct block_device_operations dcssblk_devops = { | ||
43 | .owner = THIS_MODULE, | ||
44 | .open = dcssblk_open, | ||
45 | .release = dcssblk_release, | ||
46 | }; | ||
47 | |||
48 | static ssize_t dcssblk_add_store(struct device * dev, const char * buf, | ||
49 | size_t count); | ||
50 | static ssize_t dcssblk_remove_store(struct device * dev, const char * buf, | ||
51 | size_t count); | ||
52 | static ssize_t dcssblk_save_store(struct device * dev, const char * buf, | ||
53 | size_t count); | ||
54 | static ssize_t dcssblk_save_show(struct device *dev, char *buf); | ||
55 | static ssize_t dcssblk_shared_store(struct device * dev, const char * buf, | ||
56 | size_t count); | ||
57 | static ssize_t dcssblk_shared_show(struct device *dev, char *buf); | ||
58 | |||
59 | static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store); | ||
60 | static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store); | ||
61 | static DEVICE_ATTR(save, S_IWUSR | S_IRUGO, dcssblk_save_show, | ||
62 | dcssblk_save_store); | ||
63 | static DEVICE_ATTR(shared, S_IWUSR | S_IRUGO, dcssblk_shared_show, | ||
64 | dcssblk_shared_store); | ||
65 | |||
66 | static struct device *dcssblk_root_dev; | ||
67 | |||
68 | struct dcssblk_dev_info { | ||
69 | struct list_head lh; | ||
70 | struct device dev; | ||
71 | char segment_name[BUS_ID_SIZE]; | ||
72 | atomic_t use_count; | ||
73 | struct gendisk *gd; | ||
74 | unsigned long start; | ||
75 | unsigned long end; | ||
76 | int segment_type; | ||
77 | unsigned char save_pending; | ||
78 | unsigned char is_shared; | ||
79 | struct request_queue *dcssblk_queue; | ||
80 | }; | ||
81 | |||
82 | static struct list_head dcssblk_devices = LIST_HEAD_INIT(dcssblk_devices); | ||
83 | static struct rw_semaphore dcssblk_devices_sem; | ||
84 | |||
85 | /* | ||
86 | * release function for segment device. | ||
87 | */ | ||
88 | static void | ||
89 | dcssblk_release_segment(struct device *dev) | ||
90 | { | ||
91 | PRINT_DEBUG("segment release fn called for %s\n", dev->bus_id); | ||
92 | kfree(container_of(dev, struct dcssblk_dev_info, dev)); | ||
93 | module_put(THIS_MODULE); | ||
94 | } | ||
95 | |||
96 | /* | ||
97 | * get a minor number. needs to be called with | ||
98 | * down_write(&dcssblk_devices_sem) and the | ||
99 | * device needs to be enqueued before the semaphore is | ||
100 | * freed. | ||
101 | */ | ||
102 | static inline int | ||
103 | dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info) | ||
104 | { | ||
105 | int minor, found; | ||
106 | struct dcssblk_dev_info *entry; | ||
107 | |||
108 | if (dev_info == NULL) | ||
109 | return -EINVAL; | ||
110 | for (minor = 0; minor < (1<<MINORBITS); minor++) { | ||
111 | found = 0; | ||
112 | // test if minor available | ||
113 | list_for_each_entry(entry, &dcssblk_devices, lh) | ||
114 | if (minor == entry->gd->first_minor) | ||
115 | found++; | ||
116 | if (!found) break; // got unused minor | ||
117 | } | ||
118 | if (found) | ||
119 | return -EBUSY; | ||
120 | dev_info->gd->first_minor = minor; | ||
121 | return 0; | ||
122 | } | ||
123 | |||
124 | /* | ||
125 | * get the struct dcssblk_dev_info from dcssblk_devices | ||
126 | * for the given name. | ||
127 | * down_read(&dcssblk_devices_sem) must be held. | ||
128 | */ | ||
129 | static struct dcssblk_dev_info * | ||
130 | dcssblk_get_device_by_name(char *name) | ||
131 | { | ||
132 | struct dcssblk_dev_info *entry; | ||
133 | |||
134 | list_for_each_entry(entry, &dcssblk_devices, lh) { | ||
135 | if (!strcmp(name, entry->segment_name)) { | ||
136 | return entry; | ||
137 | } | ||
138 | } | ||
139 | return NULL; | ||
140 | } | ||
141 | |||
142 | /* | ||
143 | * print appropriate error message for segment_load()/segment_type() | ||
144 | * return code | ||
145 | */ | ||
146 | static void | ||
147 | dcssblk_segment_warn(int rc, char* seg_name) | ||
148 | { | ||
149 | switch (rc) { | ||
150 | case -ENOENT: | ||
151 | PRINT_WARN("cannot load/query segment %s, does not exist\n", | ||
152 | seg_name); | ||
153 | break; | ||
154 | case -ENOSYS: | ||
155 | PRINT_WARN("cannot load/query segment %s, not running on VM\n", | ||
156 | seg_name); | ||
157 | break; | ||
158 | case -EIO: | ||
159 | PRINT_WARN("cannot load/query segment %s, hardware error\n", | ||
160 | seg_name); | ||
161 | break; | ||
162 | case -ENOTSUPP: | ||
163 | PRINT_WARN("cannot load/query segment %s, is a multi-part " | ||
164 | "segment\n", seg_name); | ||
165 | break; | ||
166 | case -ENOSPC: | ||
167 | PRINT_WARN("cannot load/query segment %s, overlaps with " | ||
168 | "storage\n", seg_name); | ||
169 | break; | ||
170 | case -EBUSY: | ||
171 | PRINT_WARN("cannot load/query segment %s, overlaps with " | ||
172 | "already loaded dcss\n", seg_name); | ||
173 | break; | ||
174 | case -EPERM: | ||
175 | PRINT_WARN("cannot load/query segment %s, already loaded in " | ||
176 | "incompatible mode\n", seg_name); | ||
177 | break; | ||
178 | case -ENOMEM: | ||
179 | PRINT_WARN("cannot load/query segment %s, out of memory\n", | ||
180 | seg_name); | ||
181 | break; | ||
182 | case -ERANGE: | ||
183 | PRINT_WARN("cannot load/query segment %s, exceeds kernel " | ||
184 | "mapping range\n", seg_name); | ||
185 | break; | ||
186 | default: | ||
187 | PRINT_WARN("cannot load/query segment %s, return value %i\n", | ||
188 | seg_name, rc); | ||
189 | break; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | /* | ||
194 | * device attribute for switching shared/nonshared (exclusive) | ||
195 | * operation (show + store) | ||
196 | */ | ||
197 | static ssize_t | ||
198 | dcssblk_shared_show(struct device *dev, char *buf) | ||
199 | { | ||
200 | struct dcssblk_dev_info *dev_info; | ||
201 | |||
202 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); | ||
203 | return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n"); | ||
204 | } | ||
205 | |||
206 | static ssize_t | ||
207 | dcssblk_shared_store(struct device *dev, const char *inbuf, size_t count) | ||
208 | { | ||
209 | struct dcssblk_dev_info *dev_info; | ||
210 | int rc; | ||
211 | |||
212 | if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) { | ||
213 | PRINT_WARN("Invalid value, must be 0 or 1\n"); | ||
214 | return -EINVAL; | ||
215 | } | ||
216 | down_write(&dcssblk_devices_sem); | ||
217 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); | ||
218 | if (atomic_read(&dev_info->use_count)) { | ||
219 | PRINT_ERR("share: segment %s is busy!\n", | ||
220 | dev_info->segment_name); | ||
221 | rc = -EBUSY; | ||
222 | goto out; | ||
223 | } | ||
224 | if (inbuf[0] == '1') { | ||
225 | // reload segment in shared mode | ||
226 | rc = segment_modify_shared(dev_info->segment_name, | ||
227 | SEGMENT_SHARED); | ||
228 | if (rc < 0) { | ||
229 | BUG_ON(rc == -EINVAL); | ||
230 | if (rc == -EIO || rc == -ENOENT) | ||
231 | goto removeseg; | ||
232 | } else { | ||
233 | dev_info->is_shared = 1; | ||
234 | switch (dev_info->segment_type) { | ||
235 | case SEG_TYPE_SR: | ||
236 | case SEG_TYPE_ER: | ||
237 | case SEG_TYPE_SC: | ||
238 | set_disk_ro(dev_info->gd,1); | ||
239 | } | ||
240 | } | ||
241 | } else if (inbuf[0] == '0') { | ||
242 | // reload segment in exclusive mode | ||
243 | if (dev_info->segment_type == SEG_TYPE_SC) { | ||
244 | PRINT_ERR("Segment type SC (%s) cannot be loaded in " | ||
245 | "non-shared mode\n", dev_info->segment_name); | ||
246 | rc = -EINVAL; | ||
247 | goto out; | ||
248 | } | ||
249 | rc = segment_modify_shared(dev_info->segment_name, | ||
250 | SEGMENT_EXCLUSIVE); | ||
251 | if (rc < 0) { | ||
252 | BUG_ON(rc == -EINVAL); | ||
253 | if (rc == -EIO || rc == -ENOENT) | ||
254 | goto removeseg; | ||
255 | } else { | ||
256 | dev_info->is_shared = 0; | ||
257 | set_disk_ro(dev_info->gd, 0); | ||
258 | } | ||
259 | } else { | ||
260 | PRINT_WARN("Invalid value, must be 0 or 1\n"); | ||
261 | rc = -EINVAL; | ||
262 | goto out; | ||
263 | } | ||
264 | rc = count; | ||
265 | goto out; | ||
266 | |||
267 | removeseg: | ||
268 | PRINT_ERR("Could not reload segment %s, removing it now!\n", | ||
269 | dev_info->segment_name); | ||
270 | list_del(&dev_info->lh); | ||
271 | |||
272 | del_gendisk(dev_info->gd); | ||
273 | blk_put_queue(dev_info->dcssblk_queue); | ||
274 | dev_info->gd->queue = NULL; | ||
275 | put_disk(dev_info->gd); | ||
276 | device_unregister(dev); | ||
277 | put_device(dev); | ||
278 | out: | ||
279 | up_write(&dcssblk_devices_sem); | ||
280 | return rc; | ||
281 | } | ||
282 | |||
283 | /* | ||
284 | * device attribute for save operation on current copy | ||
285 | * of the segment. If the segment is busy, saving will | ||
286 | * become pending until it gets released, which can be | ||
287 | * undone by storing a non-true value to this entry. | ||
288 | * (show + store) | ||
289 | */ | ||
290 | static ssize_t | ||
291 | dcssblk_save_show(struct device *dev, char *buf) | ||
292 | { | ||
293 | struct dcssblk_dev_info *dev_info; | ||
294 | |||
295 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); | ||
296 | return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n"); | ||
297 | } | ||
298 | |||
299 | static ssize_t | ||
300 | dcssblk_save_store(struct device *dev, const char *inbuf, size_t count) | ||
301 | { | ||
302 | struct dcssblk_dev_info *dev_info; | ||
303 | |||
304 | if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) { | ||
305 | PRINT_WARN("Invalid value, must be 0 or 1\n"); | ||
306 | return -EINVAL; | ||
307 | } | ||
308 | dev_info = container_of(dev, struct dcssblk_dev_info, dev); | ||
309 | |||
310 | down_write(&dcssblk_devices_sem); | ||
311 | if (inbuf[0] == '1') { | ||
312 | if (atomic_read(&dev_info->use_count) == 0) { | ||
313 | // device is idle => we save immediately | ||
314 | PRINT_INFO("Saving segment %s\n", | ||
315 | dev_info->segment_name); | ||
316 | segment_save(dev_info->segment_name); | ||
317 | } else { | ||
318 | // device is busy => we save it when it becomes | ||
319 | // idle in dcssblk_release | ||
320 | PRINT_INFO("Segment %s is currently busy, it will " | ||
321 | "be saved when it becomes idle...\n", | ||
322 | dev_info->segment_name); | ||
323 | dev_info->save_pending = 1; | ||
324 | } | ||
325 | } else if (inbuf[0] == '0') { | ||
326 | if (dev_info->save_pending) { | ||
327 | // device is busy & the user wants to undo his save | ||
328 | // request | ||
329 | dev_info->save_pending = 0; | ||
330 | PRINT_INFO("Pending save for segment %s deactivated\n", | ||
331 | dev_info->segment_name); | ||
332 | } | ||
333 | } else { | ||
334 | up_write(&dcssblk_devices_sem); | ||
335 | PRINT_WARN("Invalid value, must be 0 or 1\n"); | ||
336 | return -EINVAL; | ||
337 | } | ||
338 | up_write(&dcssblk_devices_sem); | ||
339 | return count; | ||
340 | } | ||
341 | |||
342 | /* | ||
343 | * device attribute for adding devices | ||
344 | */ | ||
345 | static ssize_t | ||
346 | dcssblk_add_store(struct device *dev, const char *buf, size_t count) | ||
347 | { | ||
348 | int rc, i; | ||
349 | struct dcssblk_dev_info *dev_info; | ||
350 | char *local_buf; | ||
351 | unsigned long seg_byte_size; | ||
352 | |||
353 | dev_info = NULL; | ||
354 | if (dev != dcssblk_root_dev) { | ||
355 | rc = -EINVAL; | ||
356 | goto out_nobuf; | ||
357 | } | ||
358 | local_buf = kmalloc(count + 1, GFP_KERNEL); | ||
359 | if (local_buf == NULL) { | ||
360 | rc = -ENOMEM; | ||
361 | goto out_nobuf; | ||
362 | } | ||
363 | /* | ||
364 | * parse input | ||
365 | */ | ||
366 | for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) { | ||
367 | local_buf[i] = toupper(buf[i]); | ||
368 | } | ||
369 | local_buf[i] = '\0'; | ||
370 | if ((i == 0) || (i > 8)) { | ||
371 | rc = -ENAMETOOLONG; | ||
372 | goto out; | ||
373 | } | ||
374 | /* | ||
375 | * already loaded? | ||
376 | */ | ||
377 | down_read(&dcssblk_devices_sem); | ||
378 | dev_info = dcssblk_get_device_by_name(local_buf); | ||
379 | up_read(&dcssblk_devices_sem); | ||
380 | if (dev_info != NULL) { | ||
381 | PRINT_WARN("Segment %s already loaded!\n", local_buf); | ||
382 | rc = -EEXIST; | ||
383 | goto out; | ||
384 | } | ||
385 | /* | ||
386 | * get a struct dcssblk_dev_info | ||
387 | */ | ||
388 | dev_info = kmalloc(sizeof(struct dcssblk_dev_info), GFP_KERNEL); | ||
389 | if (dev_info == NULL) { | ||
390 | rc = -ENOMEM; | ||
391 | goto out; | ||
392 | } | ||
393 | memset(dev_info, 0, sizeof(struct dcssblk_dev_info)); | ||
394 | |||
395 | strcpy(dev_info->segment_name, local_buf); | ||
396 | strlcpy(dev_info->dev.bus_id, local_buf, BUS_ID_SIZE); | ||
397 | dev_info->dev.release = dcssblk_release_segment; | ||
398 | INIT_LIST_HEAD(&dev_info->lh); | ||
399 | |||
400 | dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK); | ||
401 | if (dev_info->gd == NULL) { | ||
402 | rc = -ENOMEM; | ||
403 | goto free_dev_info; | ||
404 | } | ||
405 | dev_info->gd->major = dcssblk_major; | ||
406 | dev_info->gd->fops = &dcssblk_devops; | ||
407 | dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL); | ||
408 | dev_info->gd->queue = dev_info->dcssblk_queue; | ||
409 | dev_info->gd->private_data = dev_info; | ||
410 | dev_info->gd->driverfs_dev = &dev_info->dev; | ||
411 | /* | ||
412 | * load the segment | ||
413 | */ | ||
414 | rc = segment_load(local_buf, SEGMENT_SHARED, | ||
415 | &dev_info->start, &dev_info->end); | ||
416 | if (rc < 0) { | ||
417 | dcssblk_segment_warn(rc, dev_info->segment_name); | ||
418 | goto dealloc_gendisk; | ||
419 | } | ||
420 | seg_byte_size = (dev_info->end - dev_info->start + 1); | ||
421 | set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors | ||
422 | PRINT_INFO("Loaded segment %s, size = %lu Byte, " | ||
423 | "capacity = %lu (512 Byte) sectors\n", local_buf, | ||
424 | seg_byte_size, seg_byte_size >> 9); | ||
425 | |||
426 | dev_info->segment_type = rc; | ||
427 | dev_info->save_pending = 0; | ||
428 | dev_info->is_shared = 1; | ||
429 | dev_info->dev.parent = dcssblk_root_dev; | ||
430 | |||
431 | /* | ||
432 | * get minor, add to list | ||
433 | */ | ||
434 | down_write(&dcssblk_devices_sem); | ||
435 | rc = dcssblk_assign_free_minor(dev_info); | ||
436 | if (rc) { | ||
437 | up_write(&dcssblk_devices_sem); | ||
438 | PRINT_ERR("No free minor number available! " | ||
439 | "Unloading segment...\n"); | ||
440 | goto unload_seg; | ||
441 | } | ||
442 | sprintf(dev_info->gd->disk_name, "dcssblk%d", | ||
443 | dev_info->gd->first_minor); | ||
444 | list_add_tail(&dev_info->lh, &dcssblk_devices); | ||
445 | |||
446 | if (!try_module_get(THIS_MODULE)) { | ||
447 | rc = -ENODEV; | ||
448 | goto list_del; | ||
449 | } | ||
450 | /* | ||
451 | * register the device | ||
452 | */ | ||
453 | rc = device_register(&dev_info->dev); | ||
454 | if (rc) { | ||
455 | PRINT_ERR("Segment %s could not be registered RC=%d\n", | ||
456 | local_buf, rc); | ||
457 | module_put(THIS_MODULE); | ||
458 | goto list_del; | ||
459 | } | ||
460 | get_device(&dev_info->dev); | ||
461 | rc = device_create_file(&dev_info->dev, &dev_attr_shared); | ||
462 | if (rc) | ||
463 | goto unregister_dev; | ||
464 | rc = device_create_file(&dev_info->dev, &dev_attr_save); | ||
465 | if (rc) | ||
466 | goto unregister_dev; | ||
467 | |||
468 | add_disk(dev_info->gd); | ||
469 | |||
470 | blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request); | ||
471 | blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096); | ||
472 | |||
473 | switch (dev_info->segment_type) { | ||
474 | case SEG_TYPE_SR: | ||
475 | case SEG_TYPE_ER: | ||
476 | case SEG_TYPE_SC: | ||
477 | set_disk_ro(dev_info->gd,1); | ||
478 | break; | ||
479 | default: | ||
480 | set_disk_ro(dev_info->gd,0); | ||
481 | break; | ||
482 | } | ||
483 | PRINT_DEBUG("Segment %s loaded successfully\n", local_buf); | ||
484 | up_write(&dcssblk_devices_sem); | ||
485 | rc = count; | ||
486 | goto out; | ||
487 | |||
488 | unregister_dev: | ||
489 | PRINT_ERR("device_create_file() failed!\n"); | ||
490 | list_del(&dev_info->lh); | ||
491 | blk_put_queue(dev_info->dcssblk_queue); | ||
492 | dev_info->gd->queue = NULL; | ||
493 | put_disk(dev_info->gd); | ||
494 | device_unregister(&dev_info->dev); | ||
495 | segment_unload(dev_info->segment_name); | ||
496 | put_device(&dev_info->dev); | ||
497 | up_write(&dcssblk_devices_sem); | ||
498 | goto out; | ||
499 | list_del: | ||
500 | list_del(&dev_info->lh); | ||
501 | up_write(&dcssblk_devices_sem); | ||
502 | unload_seg: | ||
503 | segment_unload(local_buf); | ||
504 | dealloc_gendisk: | ||
505 | blk_put_queue(dev_info->dcssblk_queue); | ||
506 | dev_info->gd->queue = NULL; | ||
507 | put_disk(dev_info->gd); | ||
508 | free_dev_info: | ||
509 | kfree(dev_info); | ||
510 | out: | ||
511 | kfree(local_buf); | ||
512 | out_nobuf: | ||
513 | return rc; | ||
514 | } | ||
515 | |||
516 | /* | ||
517 | * device attribute for removing devices | ||
518 | */ | ||
519 | static ssize_t | ||
520 | dcssblk_remove_store(struct device *dev, const char *buf, size_t count) | ||
521 | { | ||
522 | struct dcssblk_dev_info *dev_info; | ||
523 | int rc, i; | ||
524 | char *local_buf; | ||
525 | |||
526 | if (dev != dcssblk_root_dev) { | ||
527 | return -EINVAL; | ||
528 | } | ||
529 | local_buf = kmalloc(count + 1, GFP_KERNEL); | ||
530 | if (local_buf == NULL) { | ||
531 | return -ENOMEM; | ||
532 | } | ||
533 | /* | ||
534 | * parse input | ||
535 | */ | ||
536 | for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) { | ||
537 | local_buf[i] = toupper(buf[i]); | ||
538 | } | ||
539 | local_buf[i] = '\0'; | ||
540 | if ((i == 0) || (i > 8)) { | ||
541 | rc = -ENAMETOOLONG; | ||
542 | goto out_buf; | ||
543 | } | ||
544 | |||
545 | down_write(&dcssblk_devices_sem); | ||
546 | dev_info = dcssblk_get_device_by_name(local_buf); | ||
547 | if (dev_info == NULL) { | ||
548 | up_write(&dcssblk_devices_sem); | ||
549 | PRINT_WARN("Segment %s is not loaded!\n", local_buf); | ||
550 | rc = -ENODEV; | ||
551 | goto out_buf; | ||
552 | } | ||
553 | if (atomic_read(&dev_info->use_count) != 0) { | ||
554 | up_write(&dcssblk_devices_sem); | ||
555 | PRINT_WARN("Segment %s is in use!\n", local_buf); | ||
556 | rc = -EBUSY; | ||
557 | goto out_buf; | ||
558 | } | ||
559 | list_del(&dev_info->lh); | ||
560 | |||
561 | del_gendisk(dev_info->gd); | ||
562 | blk_put_queue(dev_info->dcssblk_queue); | ||
563 | dev_info->gd->queue = NULL; | ||
564 | put_disk(dev_info->gd); | ||
565 | device_unregister(&dev_info->dev); | ||
566 | segment_unload(dev_info->segment_name); | ||
567 | PRINT_DEBUG("Segment %s unloaded successfully\n", | ||
568 | dev_info->segment_name); | ||
569 | put_device(&dev_info->dev); | ||
570 | up_write(&dcssblk_devices_sem); | ||
571 | |||
572 | rc = count; | ||
573 | out_buf: | ||
574 | kfree(local_buf); | ||
575 | return rc; | ||
576 | } | ||
577 | |||
578 | static int | ||
579 | dcssblk_open(struct inode *inode, struct file *filp) | ||
580 | { | ||
581 | struct dcssblk_dev_info *dev_info; | ||
582 | int rc; | ||
583 | |||
584 | dev_info = inode->i_bdev->bd_disk->private_data; | ||
585 | if (NULL == dev_info) { | ||
586 | rc = -ENODEV; | ||
587 | goto out; | ||
588 | } | ||
589 | atomic_inc(&dev_info->use_count); | ||
590 | inode->i_bdev->bd_block_size = 4096; | ||
591 | rc = 0; | ||
592 | out: | ||
593 | return rc; | ||
594 | } | ||
595 | |||
596 | static int | ||
597 | dcssblk_release(struct inode *inode, struct file *filp) | ||
598 | { | ||
599 | struct dcssblk_dev_info *dev_info; | ||
600 | int rc; | ||
601 | |||
602 | dev_info = inode->i_bdev->bd_disk->private_data; | ||
603 | if (NULL == dev_info) { | ||
604 | rc = -ENODEV; | ||
605 | goto out; | ||
606 | } | ||
607 | down_write(&dcssblk_devices_sem); | ||
608 | if (atomic_dec_and_test(&dev_info->use_count) | ||
609 | && (dev_info->save_pending)) { | ||
610 | PRINT_INFO("Segment %s became idle and is being saved now\n", | ||
611 | dev_info->segment_name); | ||
612 | segment_save(dev_info->segment_name); | ||
613 | dev_info->save_pending = 0; | ||
614 | } | ||
615 | up_write(&dcssblk_devices_sem); | ||
616 | rc = 0; | ||
617 | out: | ||
618 | return rc; | ||
619 | } | ||
620 | |||
621 | static int | ||
622 | dcssblk_make_request(request_queue_t *q, struct bio *bio) | ||
623 | { | ||
624 | struct dcssblk_dev_info *dev_info; | ||
625 | struct bio_vec *bvec; | ||
626 | unsigned long index; | ||
627 | unsigned long page_addr; | ||
628 | unsigned long source_addr; | ||
629 | unsigned long bytes_done; | ||
630 | int i; | ||
631 | |||
632 | bytes_done = 0; | ||
633 | dev_info = bio->bi_bdev->bd_disk->private_data; | ||
634 | if (dev_info == NULL) | ||
635 | goto fail; | ||
636 | if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0) | ||
637 | /* Request is not page-aligned. */ | ||
638 | goto fail; | ||
639 | if (((bio->bi_size >> 9) + bio->bi_sector) | ||
640 | > get_capacity(bio->bi_bdev->bd_disk)) { | ||
641 | /* Request beyond end of DCSS segment. */ | ||
642 | goto fail; | ||
643 | } | ||
644 | index = (bio->bi_sector >> 3); | ||
645 | bio_for_each_segment(bvec, bio, i) { | ||
646 | page_addr = (unsigned long) | ||
647 | page_address(bvec->bv_page) + bvec->bv_offset; | ||
648 | source_addr = dev_info->start + (index<<12) + bytes_done; | ||
649 | if (unlikely(page_addr & 4095) != 0 || (bvec->bv_len & 4095) != 0) | ||
650 | // More paranoia. | ||
651 | goto fail; | ||
652 | if (bio_data_dir(bio) == READ) { | ||
653 | memcpy((void*)page_addr, (void*)source_addr, | ||
654 | bvec->bv_len); | ||
655 | } else { | ||
656 | memcpy((void*)source_addr, (void*)page_addr, | ||
657 | bvec->bv_len); | ||
658 | } | ||
659 | bytes_done += bvec->bv_len; | ||
660 | } | ||
661 | bio_endio(bio, bytes_done, 0); | ||
662 | return 0; | ||
663 | fail: | ||
664 | bio_io_error(bio, bytes_done); | ||
665 | return 0; | ||
666 | } | ||
667 | |||
668 | static void | ||
669 | dcssblk_check_params(void) | ||
670 | { | ||
671 | int rc, i, j, k; | ||
672 | char buf[9]; | ||
673 | struct dcssblk_dev_info *dev_info; | ||
674 | |||
675 | for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0'); | ||
676 | i++) { | ||
677 | for (j = i; (dcssblk_segments[j] != ',') && | ||
678 | (dcssblk_segments[j] != '\0') && | ||
679 | (dcssblk_segments[j] != '(') && | ||
680 | (j - i) < 8; j++) | ||
681 | { | ||
682 | buf[j-i] = dcssblk_segments[j]; | ||
683 | } | ||
684 | buf[j-i] = '\0'; | ||
685 | rc = dcssblk_add_store(dcssblk_root_dev, buf, j-i); | ||
686 | if ((rc >= 0) && (dcssblk_segments[j] == '(')) { | ||
687 | for (k = 0; buf[k] != '\0'; k++) | ||
688 | buf[k] = toupper(buf[k]); | ||
689 | if (!strncmp(&dcssblk_segments[j], "(local)", 7)) { | ||
690 | down_read(&dcssblk_devices_sem); | ||
691 | dev_info = dcssblk_get_device_by_name(buf); | ||
692 | up_read(&dcssblk_devices_sem); | ||
693 | if (dev_info) | ||
694 | dcssblk_shared_store(&dev_info->dev, | ||
695 | "0\n", 2); | ||
696 | } | ||
697 | } | ||
698 | while ((dcssblk_segments[j] != ',') && | ||
699 | (dcssblk_segments[j] != '\0')) | ||
700 | { | ||
701 | j++; | ||
702 | } | ||
703 | if (dcssblk_segments[j] == '\0') | ||
704 | break; | ||
705 | i = j; | ||
706 | } | ||
707 | } | ||
708 | |||
709 | /* | ||
710 | * The init/exit functions. | ||
711 | */ | ||
712 | static void __exit | ||
713 | dcssblk_exit(void) | ||
714 | { | ||
715 | int rc; | ||
716 | |||
717 | PRINT_DEBUG("DCSSBLOCK EXIT...\n"); | ||
718 | s390_root_dev_unregister(dcssblk_root_dev); | ||
719 | rc = unregister_blkdev(dcssblk_major, DCSSBLK_NAME); | ||
720 | if (rc) { | ||
721 | PRINT_ERR("unregister_blkdev() failed!\n"); | ||
722 | } | ||
723 | PRINT_DEBUG("...finished!\n"); | ||
724 | } | ||
725 | |||
726 | static int __init | ||
727 | dcssblk_init(void) | ||
728 | { | ||
729 | int rc; | ||
730 | |||
731 | PRINT_DEBUG("DCSSBLOCK INIT...\n"); | ||
732 | dcssblk_root_dev = s390_root_dev_register("dcssblk"); | ||
733 | if (IS_ERR(dcssblk_root_dev)) { | ||
734 | PRINT_ERR("device_register() failed!\n"); | ||
735 | return PTR_ERR(dcssblk_root_dev); | ||
736 | } | ||
737 | rc = device_create_file(dcssblk_root_dev, &dev_attr_add); | ||
738 | if (rc) { | ||
739 | PRINT_ERR("device_create_file(add) failed!\n"); | ||
740 | s390_root_dev_unregister(dcssblk_root_dev); | ||
741 | return rc; | ||
742 | } | ||
743 | rc = device_create_file(dcssblk_root_dev, &dev_attr_remove); | ||
744 | if (rc) { | ||
745 | PRINT_ERR("device_create_file(remove) failed!\n"); | ||
746 | s390_root_dev_unregister(dcssblk_root_dev); | ||
747 | return rc; | ||
748 | } | ||
749 | rc = register_blkdev(0, DCSSBLK_NAME); | ||
750 | if (rc < 0) { | ||
751 | PRINT_ERR("Can't get dynamic major!\n"); | ||
752 | s390_root_dev_unregister(dcssblk_root_dev); | ||
753 | return rc; | ||
754 | } | ||
755 | dcssblk_major = rc; | ||
756 | init_rwsem(&dcssblk_devices_sem); | ||
757 | |||
758 | dcssblk_check_params(); | ||
759 | |||
760 | PRINT_DEBUG("...finished!\n"); | ||
761 | return 0; | ||
762 | } | ||
763 | |||
764 | module_init(dcssblk_init); | ||
765 | module_exit(dcssblk_exit); | ||
766 | |||
767 | module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444); | ||
768 | MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, " | ||
769 | "comma-separated list, each name max. 8 chars.\n" | ||
770 | "Adding \"(local)\" to segment name equals echoing 0 to " | ||
771 | "/sys/devices/dcssblk/<segment name>/shared after loading " | ||
772 | "the segment - \n" | ||
773 | "e.g. segments=\"mydcss1,mydcss2,mydcss3(local)\""); | ||
774 | |||
775 | MODULE_LICENSE("GPL"); | ||