blob: a7ff167d5b81089c781a3f3c9aa9e736cc8da48e [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
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>
Carsten Ottecfb1b552006-01-06 00:19:14 -080018#include <asm/s390_rdev.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070019
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
Linus Torvalds1da177e2005-04-16 15:20:36 -070034static int dcssblk_open(struct inode *inode, struct file *filp);
35static int dcssblk_release(struct inode *inode, struct file *filp);
36static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
Carsten Otte420edbc2005-06-23 22:05:23 -070037static int dcssblk_direct_access(struct block_device *bdev, sector_t secnum,
Jared Hulbert30afcb42008-04-28 02:13:02 -070038 void **kaddr, unsigned long *pfn);
Linus Torvalds1da177e2005-04-16 15:20:36 -070039
40static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
41
42static int dcssblk_major;
43static struct block_device_operations dcssblk_devops = {
Carsten Otte420edbc2005-06-23 22:05:23 -070044 .owner = THIS_MODULE,
45 .open = dcssblk_open,
46 .release = dcssblk_release,
47 .direct_access = dcssblk_direct_access,
Linus Torvalds1da177e2005-04-16 15:20:36 -070048};
49
Linus Torvalds1da177e2005-04-16 15:20:36 -070050struct dcssblk_dev_info {
51 struct list_head lh;
52 struct device dev;
53 char segment_name[BUS_ID_SIZE];
54 atomic_t use_count;
55 struct gendisk *gd;
56 unsigned long start;
57 unsigned long end;
58 int segment_type;
59 unsigned char save_pending;
60 unsigned char is_shared;
61 struct request_queue *dcssblk_queue;
Hongjie Yangb2300b92008-10-10 21:33:21 +020062 int num_of_segments;
63 struct list_head seg_list;
Linus Torvalds1da177e2005-04-16 15:20:36 -070064};
65
Hongjie Yangb2300b92008-10-10 21:33:21 +020066struct segment_info {
67 struct list_head lh;
68 char segment_name[BUS_ID_SIZE];
69 unsigned long start;
70 unsigned long end;
71 int segment_type;
72};
73
74static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
75 size_t count);
76static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
77 size_t count);
78static ssize_t dcssblk_save_store(struct device * dev, struct device_attribute *attr, const char * buf,
79 size_t count);
80static ssize_t dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf);
81static ssize_t dcssblk_shared_store(struct device * dev, struct device_attribute *attr, const char * buf,
82 size_t count);
83static ssize_t dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf);
84static ssize_t dcssblk_seglist_show(struct device *dev,
85 struct device_attribute *attr,
86 char *buf);
87
88static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
89static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
90static DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show,
91 dcssblk_save_store);
92static DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show,
93 dcssblk_shared_store);
94static DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL);
95
96static struct device *dcssblk_root_dev;
97
Denis Chengc11ca972008-01-26 14:11:13 +010098static LIST_HEAD(dcssblk_devices);
Linus Torvalds1da177e2005-04-16 15:20:36 -070099static struct rw_semaphore dcssblk_devices_sem;
100
101/*
102 * release function for segment device.
103 */
104static void
105dcssblk_release_segment(struct device *dev)
106{
Hongjie Yangb2300b92008-10-10 21:33:21 +0200107 struct dcssblk_dev_info *dev_info;
108 struct segment_info *entry, *temp;
109
110 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
111 list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) {
112 list_del(&entry->lh);
113 kfree(entry);
114 }
115 kfree(dev_info);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700116 module_put(THIS_MODULE);
117}
118
119/*
120 * get a minor number. needs to be called with
121 * down_write(&dcssblk_devices_sem) and the
122 * device needs to be enqueued before the semaphore is
123 * freed.
124 */
Heiko Carstens4d284ca2007-02-05 21:18:53 +0100125static int
Linus Torvalds1da177e2005-04-16 15:20:36 -0700126dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
127{
128 int minor, found;
129 struct dcssblk_dev_info *entry;
130
131 if (dev_info == NULL)
132 return -EINVAL;
133 for (minor = 0; minor < (1<<MINORBITS); minor++) {
134 found = 0;
135 // test if minor available
136 list_for_each_entry(entry, &dcssblk_devices, lh)
Tejun Heof331c022008-09-03 09:01:48 +0200137 if (minor == MINOR(disk_devt(entry->gd)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138 found++;
139 if (!found) break; // got unused minor
140 }
141 if (found)
142 return -EBUSY;
143 dev_info->gd->first_minor = minor;
144 return 0;
145}
146
147/*
148 * get the struct dcssblk_dev_info from dcssblk_devices
149 * for the given name.
150 * down_read(&dcssblk_devices_sem) must be held.
151 */
152static struct dcssblk_dev_info *
153dcssblk_get_device_by_name(char *name)
154{
155 struct dcssblk_dev_info *entry;
156
157 list_for_each_entry(entry, &dcssblk_devices, lh) {
158 if (!strcmp(name, entry->segment_name)) {
159 return entry;
160 }
161 }
162 return NULL;
163}
164
Hongjie Yangb2300b92008-10-10 21:33:21 +0200165/*
166 * get the struct segment_info from seg_list
167 * for the given name.
168 * down_read(&dcssblk_devices_sem) must be held.
169 */
170static struct segment_info *
171dcssblk_get_segment_by_name(char *name)
172{
173 struct dcssblk_dev_info *dev_info;
174 struct segment_info *entry;
175
176 list_for_each_entry(dev_info, &dcssblk_devices, lh) {
177 list_for_each_entry(entry, &dev_info->seg_list, lh) {
178 if (!strcmp(name, entry->segment_name))
179 return entry;
180 }
181 }
182 return NULL;
183}
184
185/*
186 * get the highest address of the multi-segment block.
187 */
188static unsigned long
189dcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info)
190{
191 unsigned long highest_addr;
192 struct segment_info *entry;
193
194 highest_addr = 0;
195 list_for_each_entry(entry, &dev_info->seg_list, lh) {
196 if (highest_addr < entry->end)
197 highest_addr = entry->end;
198 }
199 return highest_addr;
200}
201
202/*
203 * get the lowest address of the multi-segment block.
204 */
205static unsigned long
206dcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info)
207{
208 int set_first;
209 unsigned long lowest_addr;
210 struct segment_info *entry;
211
212 set_first = 0;
213 lowest_addr = 0;
214 list_for_each_entry(entry, &dev_info->seg_list, lh) {
215 if (set_first == 0) {
216 lowest_addr = entry->start;
217 set_first = 1;
218 } else {
219 if (lowest_addr > entry->start)
220 lowest_addr = entry->start;
221 }
222 }
223 return lowest_addr;
224}
225
226/*
227 * Check continuity of segments.
228 */
229static int
230dcssblk_is_continuous(struct dcssblk_dev_info *dev_info)
231{
232 int i, j, rc;
233 struct segment_info *sort_list, *entry, temp;
234
235 if (dev_info->num_of_segments <= 1)
236 return 0;
237
238 sort_list = kzalloc(
239 sizeof(struct segment_info) * dev_info->num_of_segments,
240 GFP_KERNEL);
241 if (sort_list == NULL)
242 return -ENOMEM;
243 i = 0;
244 list_for_each_entry(entry, &dev_info->seg_list, lh) {
245 memcpy(&sort_list[i], entry, sizeof(struct segment_info));
246 i++;
247 }
248
249 /* sort segments */
250 for (i = 0; i < dev_info->num_of_segments; i++)
251 for (j = 0; j < dev_info->num_of_segments; j++)
252 if (sort_list[j].start > sort_list[i].start) {
253 memcpy(&temp, &sort_list[i],
254 sizeof(struct segment_info));
255 memcpy(&sort_list[i], &sort_list[j],
256 sizeof(struct segment_info));
257 memcpy(&sort_list[j], &temp,
258 sizeof(struct segment_info));
259 }
260
261 /* check continuity */
262 for (i = 0; i < dev_info->num_of_segments - 1; i++) {
263 if ((sort_list[i].end + 1) != sort_list[i+1].start) {
264 PRINT_ERR("Segment %s is not contiguous with "
265 "segment %s\n",
266 sort_list[i].segment_name,
267 sort_list[i+1].segment_name);
268 rc = -EINVAL;
269 goto out;
270 }
271 /* EN and EW are allowed in a block device */
272 if (sort_list[i].segment_type != sort_list[i+1].segment_type) {
273 if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) ||
274 (sort_list[i].segment_type == SEG_TYPE_ER) ||
275 !(sort_list[i+1].segment_type &
276 SEGMENT_EXCLUSIVE) ||
277 (sort_list[i+1].segment_type == SEG_TYPE_ER)) {
278 PRINT_ERR("Segment %s has different type from "
279 "segment %s\n",
280 sort_list[i].segment_name,
281 sort_list[i+1].segment_name);
282 rc = -EINVAL;
283 goto out;
284 }
285 }
286 }
287 rc = 0;
288out:
289 kfree(sort_list);
290 return rc;
291}
292
293/*
294 * Load a segment
295 */
296static int
297dcssblk_load_segment(char *name, struct segment_info **seg_info)
298{
299 int rc;
300
301 /* already loaded? */
302 down_read(&dcssblk_devices_sem);
303 *seg_info = dcssblk_get_segment_by_name(name);
304 up_read(&dcssblk_devices_sem);
305 if (*seg_info != NULL)
306 return -EEXIST;
307
308 /* get a struct segment_info */
309 *seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL);
310 if (*seg_info == NULL)
311 return -ENOMEM;
312
313 strcpy((*seg_info)->segment_name, name);
314
315 /* load the segment */
316 rc = segment_load(name, SEGMENT_SHARED,
317 &(*seg_info)->start, &(*seg_info)->end);
318 if (rc < 0) {
319 segment_warning(rc, (*seg_info)->segment_name);
320 kfree(*seg_info);
321 } else {
322 INIT_LIST_HEAD(&(*seg_info)->lh);
323 (*seg_info)->segment_type = rc;
324 }
325 return rc;
326}
327
Gerald Schaefer931bb682007-11-05 11:10:09 +0100328static void dcssblk_unregister_callback(struct device *dev)
329{
330 device_unregister(dev);
331 put_device(dev);
332}
333
Linus Torvalds1da177e2005-04-16 15:20:36 -0700334/*
335 * device attribute for switching shared/nonshared (exclusive)
336 * operation (show + store)
337 */
338static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400339dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340{
341 struct dcssblk_dev_info *dev_info;
342
343 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
344 return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
345}
346
347static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400348dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700349{
350 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200351 struct segment_info *entry, *temp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700352 int rc;
353
Hongjie Yangded77fb2008-07-14 09:59:39 +0200354 if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700355 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700356 down_write(&dcssblk_devices_sem);
357 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
358 if (atomic_read(&dev_info->use_count)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700359 rc = -EBUSY;
360 goto out;
361 }
362 if (inbuf[0] == '1') {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200363 /* reload segments in shared mode */
364 list_for_each_entry(entry, &dev_info->seg_list, lh) {
365 rc = segment_modify_shared(entry->segment_name,
366 SEGMENT_SHARED);
367 if (rc < 0) {
368 BUG_ON(rc == -EINVAL);
369 if (rc != -EAGAIN)
370 goto removeseg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700371 }
372 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200373 dev_info->is_shared = 1;
374 switch (dev_info->segment_type) {
375 case SEG_TYPE_SR:
376 case SEG_TYPE_ER:
377 case SEG_TYPE_SC:
378 set_disk_ro(dev_info->gd, 1);
379 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700380 } else if (inbuf[0] == '0') {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200381 /* reload segments in exclusive mode */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700382 if (dev_info->segment_type == SEG_TYPE_SC) {
383 PRINT_ERR("Segment type SC (%s) cannot be loaded in "
Hongjie Yangb2300b92008-10-10 21:33:21 +0200384 "non-shared mode\n", dev_info->segment_name);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700385 rc = -EINVAL;
386 goto out;
387 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200388 list_for_each_entry(entry, &dev_info->seg_list, lh) {
389 rc = segment_modify_shared(entry->segment_name,
390 SEGMENT_EXCLUSIVE);
391 if (rc < 0) {
392 BUG_ON(rc == -EINVAL);
393 if (rc != -EAGAIN)
394 goto removeseg;
395 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700396 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200397 dev_info->is_shared = 0;
398 set_disk_ro(dev_info->gd, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700399 } else {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700400 rc = -EINVAL;
401 goto out;
402 }
403 rc = count;
404 goto out;
405
406removeseg:
Hongjie Yangb2300b92008-10-10 21:33:21 +0200407 PRINT_ERR("Could not reload segment(s) of the device %s, removing "
408 "segment(s) now!\n",
409 dev_info->segment_name);
410 temp = entry;
411 list_for_each_entry(entry, &dev_info->seg_list, lh) {
412 if (entry != temp)
413 segment_unload(entry->segment_name);
414 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700415 list_del(&dev_info->lh);
416
417 del_gendisk(dev_info->gd);
Al Viro1312f402006-03-12 11:02:03 -0500418 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700419 dev_info->gd->queue = NULL;
420 put_disk(dev_info->gd);
Gerald Schaefer931bb682007-11-05 11:10:09 +0100421 rc = device_schedule_callback(dev, dcssblk_unregister_callback);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700422out:
423 up_write(&dcssblk_devices_sem);
424 return rc;
425}
426
427/*
428 * device attribute for save operation on current copy
429 * of the segment. If the segment is busy, saving will
430 * become pending until it gets released, which can be
431 * undone by storing a non-true value to this entry.
432 * (show + store)
433 */
434static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400435dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700436{
437 struct dcssblk_dev_info *dev_info;
438
439 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
440 return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
441}
442
443static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400444dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700445{
446 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200447 struct segment_info *entry;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700448
Hongjie Yangded77fb2008-07-14 09:59:39 +0200449 if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700450 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700451 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
452
453 down_write(&dcssblk_devices_sem);
454 if (inbuf[0] == '1') {
455 if (atomic_read(&dev_info->use_count) == 0) {
456 // device is idle => we save immediately
Hongjie Yangb2300b92008-10-10 21:33:21 +0200457 PRINT_INFO("Saving segment(s) of the device %s\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700458 dev_info->segment_name);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200459 list_for_each_entry(entry, &dev_info->seg_list, lh) {
460 segment_save(entry->segment_name);
461 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700462 } else {
463 // device is busy => we save it when it becomes
464 // idle in dcssblk_release
Hongjie Yangb2300b92008-10-10 21:33:21 +0200465 PRINT_INFO("Device %s is currently busy, segment(s) "
466 "will be saved when it becomes idle...\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700467 dev_info->segment_name);
468 dev_info->save_pending = 1;
469 }
470 } else if (inbuf[0] == '0') {
471 if (dev_info->save_pending) {
472 // device is busy & the user wants to undo his save
473 // request
474 dev_info->save_pending = 0;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200475 PRINT_INFO("Pending save for segment(s) of the device "
476 "%s deactivated\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700477 dev_info->segment_name);
478 }
479 } else {
480 up_write(&dcssblk_devices_sem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700481 return -EINVAL;
482 }
483 up_write(&dcssblk_devices_sem);
484 return count;
485}
486
487/*
Hongjie Yangb2300b92008-10-10 21:33:21 +0200488 * device attribute for showing all segments in a device
489 */
490static ssize_t
491dcssblk_seglist_show(struct device *dev, struct device_attribute *attr,
492 char *buf)
493{
494 int i;
495
496 struct dcssblk_dev_info *dev_info;
497 struct segment_info *entry;
498
499 down_read(&dcssblk_devices_sem);
500 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
501 i = 0;
502 buf[0] = '\0';
503 list_for_each_entry(entry, &dev_info->seg_list, lh) {
504 strcpy(&buf[i], entry->segment_name);
505 i += strlen(entry->segment_name);
506 buf[i] = '\n';
507 i++;
508 }
509 up_read(&dcssblk_devices_sem);
510 return i;
511}
512
513/*
Linus Torvalds1da177e2005-04-16 15:20:36 -0700514 * device attribute for adding devices
515 */
516static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400517dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700518{
Hongjie Yangb2300b92008-10-10 21:33:21 +0200519 int rc, i, j, num_of_segments;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700520 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200521 struct segment_info *seg_info, *temp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700522 char *local_buf;
523 unsigned long seg_byte_size;
524
525 dev_info = NULL;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200526 seg_info = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700527 if (dev != dcssblk_root_dev) {
528 rc = -EINVAL;
529 goto out_nobuf;
530 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200531 if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) {
532 rc = -ENAMETOOLONG;
533 goto out_nobuf;
534 }
535
Linus Torvalds1da177e2005-04-16 15:20:36 -0700536 local_buf = kmalloc(count + 1, GFP_KERNEL);
537 if (local_buf == NULL) {
538 rc = -ENOMEM;
539 goto out_nobuf;
540 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200541
Linus Torvalds1da177e2005-04-16 15:20:36 -0700542 /*
543 * parse input
544 */
Hongjie Yangb2300b92008-10-10 21:33:21 +0200545 num_of_segments = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700546 for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200547 for (j = i; (buf[j] != ':') &&
548 (buf[j] != '\0') &&
549 (buf[j] != '\n') &&
550 j < count; j++) {
551 local_buf[j-i] = toupper(buf[j]);
552 }
553 local_buf[j-i] = '\0';
554 if (((j - i) == 0) || ((j - i) > 8)) {
555 rc = -ENAMETOOLONG;
556 goto seg_list_del;
557 }
558
559 rc = dcssblk_load_segment(local_buf, &seg_info);
560 if (rc < 0)
561 goto seg_list_del;
562 /*
563 * get a struct dcssblk_dev_info
564 */
565 if (num_of_segments == 0) {
566 dev_info = kzalloc(sizeof(struct dcssblk_dev_info),
567 GFP_KERNEL);
568 if (dev_info == NULL) {
569 rc = -ENOMEM;
570 goto out;
571 }
572 strcpy(dev_info->segment_name, local_buf);
573 dev_info->segment_type = seg_info->segment_type;
574 INIT_LIST_HEAD(&dev_info->seg_list);
575 }
576 list_add_tail(&seg_info->lh, &dev_info->seg_list);
577 num_of_segments++;
578 i = j;
579
580 if ((buf[j] == '\0') || (buf[j] == '\n'))
581 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700582 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700583
Hongjie Yangb2300b92008-10-10 21:33:21 +0200584 /* no trailing colon at the end of the input */
585 if ((i > 0) && (buf[i-1] == ':')) {
586 rc = -ENAMETOOLONG;
587 goto seg_list_del;
588 }
589 strlcpy(local_buf, buf, i + 1);
590 dev_info->num_of_segments = num_of_segments;
591 rc = dcssblk_is_continuous(dev_info);
592 if (rc < 0)
593 goto seg_list_del;
594
595 dev_info->start = dcssblk_find_lowest_addr(dev_info);
596 dev_info->end = dcssblk_find_highest_addr(dev_info);
597
598 dev_set_name(&dev_info->dev, dev_info->segment_name);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700599 dev_info->dev.release = dcssblk_release_segment;
600 INIT_LIST_HEAD(&dev_info->lh);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700601 dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
602 if (dev_info->gd == NULL) {
603 rc = -ENOMEM;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200604 goto seg_list_del;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700605 }
606 dev_info->gd->major = dcssblk_major;
607 dev_info->gd->fops = &dcssblk_devops;
608 dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL);
609 dev_info->gd->queue = dev_info->dcssblk_queue;
610 dev_info->gd->private_data = dev_info;
611 dev_info->gd->driverfs_dev = &dev_info->dev;
Heiko Carstensc5411db2008-02-05 16:50:50 +0100612 blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
613 blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200614
Linus Torvalds1da177e2005-04-16 15:20:36 -0700615 seg_byte_size = (dev_info->end - dev_info->start + 1);
616 set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
Hongjie Yangb2300b92008-10-10 21:33:21 +0200617 PRINT_INFO("Loaded segment(s) %s, size = %lu Byte, "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700618 "capacity = %lu (512 Byte) sectors\n", local_buf,
619 seg_byte_size, seg_byte_size >> 9);
620
Linus Torvalds1da177e2005-04-16 15:20:36 -0700621 dev_info->save_pending = 0;
622 dev_info->is_shared = 1;
623 dev_info->dev.parent = dcssblk_root_dev;
624
625 /*
Hongjie Yangb2300b92008-10-10 21:33:21 +0200626 *get minor, add to list
Linus Torvalds1da177e2005-04-16 15:20:36 -0700627 */
628 down_write(&dcssblk_devices_sem);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200629 if (dcssblk_get_segment_by_name(local_buf)) {
Gerald Schaefer04f64b52008-08-21 19:46:40 +0200630 rc = -EEXIST;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200631 goto release_gd;
Gerald Schaefer04f64b52008-08-21 19:46:40 +0200632 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700633 rc = dcssblk_assign_free_minor(dev_info);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200634 if (rc)
635 goto release_gd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700636 sprintf(dev_info->gd->disk_name, "dcssblk%d",
Tejun Heof331c022008-09-03 09:01:48 +0200637 MINOR(disk_devt(dev_info->gd)));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700638 list_add_tail(&dev_info->lh, &dcssblk_devices);
639
640 if (!try_module_get(THIS_MODULE)) {
641 rc = -ENODEV;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200642 goto dev_list_del;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700643 }
644 /*
645 * register the device
646 */
647 rc = device_register(&dev_info->dev);
648 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700649 module_put(THIS_MODULE);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200650 goto dev_list_del;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700651 }
652 get_device(&dev_info->dev);
653 rc = device_create_file(&dev_info->dev, &dev_attr_shared);
654 if (rc)
655 goto unregister_dev;
656 rc = device_create_file(&dev_info->dev, &dev_attr_save);
657 if (rc)
658 goto unregister_dev;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200659 rc = device_create_file(&dev_info->dev, &dev_attr_seglist);
660 if (rc)
661 goto unregister_dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700662
Christian Borntraeger436d1bc2007-12-04 16:09:03 +0100663 add_disk(dev_info->gd);
664
Linus Torvalds1da177e2005-04-16 15:20:36 -0700665 switch (dev_info->segment_type) {
666 case SEG_TYPE_SR:
667 case SEG_TYPE_ER:
668 case SEG_TYPE_SC:
669 set_disk_ro(dev_info->gd,1);
670 break;
671 default:
672 set_disk_ro(dev_info->gd,0);
673 break;
674 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700675 up_write(&dcssblk_devices_sem);
676 rc = count;
677 goto out;
678
679unregister_dev:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700680 list_del(&dev_info->lh);
Al Viro1312f402006-03-12 11:02:03 -0500681 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700682 dev_info->gd->queue = NULL;
683 put_disk(dev_info->gd);
684 device_unregister(&dev_info->dev);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200685 list_for_each_entry(seg_info, &dev_info->seg_list, lh) {
686 segment_unload(seg_info->segment_name);
687 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700688 put_device(&dev_info->dev);
689 up_write(&dcssblk_devices_sem);
690 goto out;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200691dev_list_del:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700692 list_del(&dev_info->lh);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200693release_gd:
Al Viro1312f402006-03-12 11:02:03 -0500694 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700695 dev_info->gd->queue = NULL;
696 put_disk(dev_info->gd);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200697 up_write(&dcssblk_devices_sem);
698seg_list_del:
699 if (dev_info == NULL)
700 goto out;
701 list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) {
702 list_del(&seg_info->lh);
703 segment_unload(seg_info->segment_name);
704 kfree(seg_info);
705 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700706 kfree(dev_info);
707out:
708 kfree(local_buf);
709out_nobuf:
710 return rc;
711}
712
713/*
714 * device attribute for removing devices
715 */
716static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400717dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700718{
719 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200720 struct segment_info *entry;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700721 int rc, i;
722 char *local_buf;
723
724 if (dev != dcssblk_root_dev) {
725 return -EINVAL;
726 }
727 local_buf = kmalloc(count + 1, GFP_KERNEL);
728 if (local_buf == NULL) {
729 return -ENOMEM;
730 }
731 /*
732 * parse input
733 */
734 for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) {
735 local_buf[i] = toupper(buf[i]);
736 }
737 local_buf[i] = '\0';
738 if ((i == 0) || (i > 8)) {
739 rc = -ENAMETOOLONG;
740 goto out_buf;
741 }
742
743 down_write(&dcssblk_devices_sem);
744 dev_info = dcssblk_get_device_by_name(local_buf);
745 if (dev_info == NULL) {
746 up_write(&dcssblk_devices_sem);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200747 PRINT_WARN("Device %s is not loaded!\n", local_buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700748 rc = -ENODEV;
749 goto out_buf;
750 }
751 if (atomic_read(&dev_info->use_count) != 0) {
752 up_write(&dcssblk_devices_sem);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200753 PRINT_WARN("Device %s is in use!\n", local_buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700754 rc = -EBUSY;
755 goto out_buf;
756 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700757
Hongjie Yangb2300b92008-10-10 21:33:21 +0200758 list_del(&dev_info->lh);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700759 del_gendisk(dev_info->gd);
Al Viro1312f402006-03-12 11:02:03 -0500760 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700761 dev_info->gd->queue = NULL;
762 put_disk(dev_info->gd);
763 device_unregister(&dev_info->dev);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200764
765 /* unload all related segments */
766 list_for_each_entry(entry, &dev_info->seg_list, lh)
767 segment_unload(entry->segment_name);
768
Linus Torvalds1da177e2005-04-16 15:20:36 -0700769 put_device(&dev_info->dev);
770 up_write(&dcssblk_devices_sem);
771
772 rc = count;
773out_buf:
774 kfree(local_buf);
775 return rc;
776}
777
778static int
779dcssblk_open(struct inode *inode, struct file *filp)
780{
781 struct dcssblk_dev_info *dev_info;
782 int rc;
783
784 dev_info = inode->i_bdev->bd_disk->private_data;
785 if (NULL == dev_info) {
786 rc = -ENODEV;
787 goto out;
788 }
789 atomic_inc(&dev_info->use_count);
790 inode->i_bdev->bd_block_size = 4096;
791 rc = 0;
792out:
793 return rc;
794}
795
796static int
797dcssblk_release(struct inode *inode, struct file *filp)
798{
799 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200800 struct segment_info *entry;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700801 int rc;
802
803 dev_info = inode->i_bdev->bd_disk->private_data;
804 if (NULL == dev_info) {
805 rc = -ENODEV;
806 goto out;
807 }
808 down_write(&dcssblk_devices_sem);
809 if (atomic_dec_and_test(&dev_info->use_count)
810 && (dev_info->save_pending)) {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200811 PRINT_INFO("Device %s became idle and is being saved now\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700812 dev_info->segment_name);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200813 list_for_each_entry(entry, &dev_info->seg_list, lh) {
814 segment_save(entry->segment_name);
815 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700816 dev_info->save_pending = 0;
817 }
818 up_write(&dcssblk_devices_sem);
819 rc = 0;
820out:
821 return rc;
822}
823
824static int
Jens Axboe165125e2007-07-24 09:28:11 +0200825dcssblk_make_request(struct request_queue *q, struct bio *bio)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700826{
827 struct dcssblk_dev_info *dev_info;
828 struct bio_vec *bvec;
829 unsigned long index;
830 unsigned long page_addr;
831 unsigned long source_addr;
832 unsigned long bytes_done;
833 int i;
834
835 bytes_done = 0;
836 dev_info = bio->bi_bdev->bd_disk->private_data;
837 if (dev_info == NULL)
838 goto fail;
839 if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
840 /* Request is not page-aligned. */
841 goto fail;
842 if (((bio->bi_size >> 9) + bio->bi_sector)
843 > get_capacity(bio->bi_bdev->bd_disk)) {
844 /* Request beyond end of DCSS segment. */
845 goto fail;
846 }
Carsten Otte420edbc2005-06-23 22:05:23 -0700847 /* verify data transfer direction */
848 if (dev_info->is_shared) {
849 switch (dev_info->segment_type) {
850 case SEG_TYPE_SR:
851 case SEG_TYPE_ER:
852 case SEG_TYPE_SC:
853 /* cannot write to these segments */
854 if (bio_data_dir(bio) == WRITE) {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200855 PRINT_WARN("rejecting write to ro device %s\n",
Kay Sievers2a0217d2008-10-10 21:33:09 +0200856 dev_name(&dev_info->dev));
Carsten Otte420edbc2005-06-23 22:05:23 -0700857 goto fail;
858 }
859 }
860 }
861
Linus Torvalds1da177e2005-04-16 15:20:36 -0700862 index = (bio->bi_sector >> 3);
863 bio_for_each_segment(bvec, bio, i) {
864 page_addr = (unsigned long)
865 page_address(bvec->bv_page) + bvec->bv_offset;
866 source_addr = dev_info->start + (index<<12) + bytes_done;
Roel Kluin39f73b22008-02-19 15:29:33 +0100867 if (unlikely((page_addr & 4095) != 0) || (bvec->bv_len & 4095) != 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700868 // More paranoia.
869 goto fail;
870 if (bio_data_dir(bio) == READ) {
871 memcpy((void*)page_addr, (void*)source_addr,
872 bvec->bv_len);
873 } else {
874 memcpy((void*)source_addr, (void*)page_addr,
875 bvec->bv_len);
876 }
877 bytes_done += bvec->bv_len;
878 }
NeilBrown6712ecf2007-09-27 12:47:43 +0200879 bio_endio(bio, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700880 return 0;
881fail:
NeilBrown6712ecf2007-09-27 12:47:43 +0200882 bio_io_error(bio);
Carsten Otte420edbc2005-06-23 22:05:23 -0700883 return 0;
884}
885
886static int
887dcssblk_direct_access (struct block_device *bdev, sector_t secnum,
Jared Hulbert30afcb42008-04-28 02:13:02 -0700888 void **kaddr, unsigned long *pfn)
Carsten Otte420edbc2005-06-23 22:05:23 -0700889{
890 struct dcssblk_dev_info *dev_info;
891 unsigned long pgoff;
892
893 dev_info = bdev->bd_disk->private_data;
894 if (!dev_info)
895 return -ENODEV;
896 if (secnum % (PAGE_SIZE/512))
897 return -EINVAL;
898 pgoff = secnum / (PAGE_SIZE / 512);
899 if ((pgoff+1)*PAGE_SIZE-1 > dev_info->end - dev_info->start)
900 return -ERANGE;
Jared Hulbert30afcb42008-04-28 02:13:02 -0700901 *kaddr = (void *) (dev_info->start+pgoff*PAGE_SIZE);
902 *pfn = virt_to_phys(*kaddr) >> PAGE_SHIFT;
903
Linus Torvalds1da177e2005-04-16 15:20:36 -0700904 return 0;
905}
906
907static void
908dcssblk_check_params(void)
909{
910 int rc, i, j, k;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200911 char buf[DCSSBLK_PARM_LEN + 1];
Linus Torvalds1da177e2005-04-16 15:20:36 -0700912 struct dcssblk_dev_info *dev_info;
913
914 for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
915 i++) {
916 for (j = i; (dcssblk_segments[j] != ',') &&
917 (dcssblk_segments[j] != '\0') &&
918 (dcssblk_segments[j] != '(') &&
Hongjie Yangb2300b92008-10-10 21:33:21 +0200919 (j < DCSSBLK_PARM_LEN); j++)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700920 {
921 buf[j-i] = dcssblk_segments[j];
922 }
923 buf[j-i] = '\0';
Cornelia Huckf901e5d2005-06-25 14:55:29 -0700924 rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700925 if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200926 for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700927 buf[k] = toupper(buf[k]);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200928 buf[k] = '\0';
Linus Torvalds1da177e2005-04-16 15:20:36 -0700929 if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
930 down_read(&dcssblk_devices_sem);
931 dev_info = dcssblk_get_device_by_name(buf);
932 up_read(&dcssblk_devices_sem);
933 if (dev_info)
934 dcssblk_shared_store(&dev_info->dev,
Cornelia Huckf901e5d2005-06-25 14:55:29 -0700935 NULL, "0\n", 2);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700936 }
937 }
938 while ((dcssblk_segments[j] != ',') &&
939 (dcssblk_segments[j] != '\0'))
940 {
941 j++;
942 }
943 if (dcssblk_segments[j] == '\0')
944 break;
945 i = j;
946 }
947}
948
949/*
950 * The init/exit functions.
951 */
952static void __exit
953dcssblk_exit(void)
954{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700955 s390_root_dev_unregister(dcssblk_root_dev);
Akinobu Mita00d59402007-07-17 04:03:46 -0700956 unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700957}
958
959static int __init
960dcssblk_init(void)
961{
962 int rc;
963
Linus Torvalds1da177e2005-04-16 15:20:36 -0700964 dcssblk_root_dev = s390_root_dev_register("dcssblk");
Hongjie Yangded77fb2008-07-14 09:59:39 +0200965 if (IS_ERR(dcssblk_root_dev))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700966 return PTR_ERR(dcssblk_root_dev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700967 rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
968 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700969 s390_root_dev_unregister(dcssblk_root_dev);
970 return rc;
971 }
972 rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);
973 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700974 s390_root_dev_unregister(dcssblk_root_dev);
975 return rc;
976 }
977 rc = register_blkdev(0, DCSSBLK_NAME);
978 if (rc < 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700979 s390_root_dev_unregister(dcssblk_root_dev);
980 return rc;
981 }
982 dcssblk_major = rc;
983 init_rwsem(&dcssblk_devices_sem);
984
985 dcssblk_check_params();
986
Linus Torvalds1da177e2005-04-16 15:20:36 -0700987 return 0;
988}
989
990module_init(dcssblk_init);
991module_exit(dcssblk_exit);
992
993module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
994MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
Hongjie Yangb2300b92008-10-10 21:33:21 +0200995 "comma-separated list, names in each set separated "
996 "by commas are separated by colons, each set contains "
997 "names of contiguous segments and each name max. 8 chars.\n"
998 "Adding \"(local)\" to the end of each set equals echoing 0 "
999 "to /sys/devices/dcssblk/<device name>/shared after loading "
1000 "the contiguous segments - \n"
1001 "e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"");
Linus Torvalds1da177e2005-04-16 15:20:36 -07001002
1003MODULE_LICENSE("GPL");