blob: b1b204c448ed2c303025f0fe9cc4712b67c71d46 [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
Kay Sievers98df67b2008-12-25 13:38:55 +010024#define DCSS_BUS_ID_SIZE 20
Linus Torvalds1da177e2005-04-16 15:20:36 -070025
26#ifdef DCSSBLK_DEBUG
27#define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSSBLK_NAME " debug: " x)
28#else
29#define PRINT_DEBUG(x...) do {} while (0)
30#endif
31#define PRINT_INFO(x...) printk(KERN_INFO DCSSBLK_NAME " info: " x)
32#define PRINT_WARN(x...) printk(KERN_WARNING DCSSBLK_NAME " warning: " x)
33#define PRINT_ERR(x...) printk(KERN_ERR DCSSBLK_NAME " error: " x)
34
Al Viro46d74322008-03-02 10:37:41 -050035static int dcssblk_open(struct block_device *bdev, fmode_t mode);
36static int dcssblk_release(struct gendisk *disk, fmode_t mode);
Linus Torvalds1da177e2005-04-16 15:20:36 -070037static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
Carsten Otte420edbc2005-06-23 22:05:23 -070038static int dcssblk_direct_access(struct block_device *bdev, sector_t secnum,
Jared Hulbert30afcb42008-04-28 02:13:02 -070039 void **kaddr, unsigned long *pfn);
Linus Torvalds1da177e2005-04-16 15:20:36 -070040
41static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
42
43static int dcssblk_major;
44static struct block_device_operations dcssblk_devops = {
Carsten Otte420edbc2005-06-23 22:05:23 -070045 .owner = THIS_MODULE,
Al Viro46d74322008-03-02 10:37:41 -050046 .open = dcssblk_open,
47 .release = dcssblk_release,
Carsten Otte420edbc2005-06-23 22:05:23 -070048 .direct_access = dcssblk_direct_access,
Linus Torvalds1da177e2005-04-16 15:20:36 -070049};
50
Linus Torvalds1da177e2005-04-16 15:20:36 -070051struct dcssblk_dev_info {
52 struct list_head lh;
53 struct device dev;
Kay Sievers98df67b2008-12-25 13:38:55 +010054 char segment_name[DCSS_BUS_ID_SIZE];
Linus Torvalds1da177e2005-04-16 15:20:36 -070055 atomic_t use_count;
56 struct gendisk *gd;
57 unsigned long start;
58 unsigned long end;
59 int segment_type;
60 unsigned char save_pending;
61 unsigned char is_shared;
62 struct request_queue *dcssblk_queue;
Hongjie Yangb2300b92008-10-10 21:33:21 +020063 int num_of_segments;
64 struct list_head seg_list;
Linus Torvalds1da177e2005-04-16 15:20:36 -070065};
66
Hongjie Yangb2300b92008-10-10 21:33:21 +020067struct segment_info {
68 struct list_head lh;
Kay Sievers98df67b2008-12-25 13:38:55 +010069 char segment_name[DCSS_BUS_ID_SIZE];
Hongjie Yangb2300b92008-10-10 21:33:21 +020070 unsigned long start;
71 unsigned long end;
72 int segment_type;
73};
74
75static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
76 size_t count);
77static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
78 size_t count);
79static ssize_t dcssblk_save_store(struct device * dev, struct device_attribute *attr, const char * buf,
80 size_t count);
81static ssize_t dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf);
82static ssize_t dcssblk_shared_store(struct device * dev, struct device_attribute *attr, const char * buf,
83 size_t count);
84static ssize_t dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf);
85static ssize_t dcssblk_seglist_show(struct device *dev,
86 struct device_attribute *attr,
87 char *buf);
88
89static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
90static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
91static DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show,
92 dcssblk_save_store);
93static DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show,
94 dcssblk_shared_store);
95static DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL);
96
97static struct device *dcssblk_root_dev;
98
Denis Chengc11ca972008-01-26 14:11:13 +010099static LIST_HEAD(dcssblk_devices);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100static struct rw_semaphore dcssblk_devices_sem;
101
102/*
103 * release function for segment device.
104 */
105static void
106dcssblk_release_segment(struct device *dev)
107{
Hongjie Yangb2300b92008-10-10 21:33:21 +0200108 struct dcssblk_dev_info *dev_info;
109 struct segment_info *entry, *temp;
110
111 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
112 list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) {
113 list_del(&entry->lh);
114 kfree(entry);
115 }
116 kfree(dev_info);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117 module_put(THIS_MODULE);
118}
119
120/*
121 * get a minor number. needs to be called with
122 * down_write(&dcssblk_devices_sem) and the
123 * device needs to be enqueued before the semaphore is
124 * freed.
125 */
Heiko Carstens4d284ca2007-02-05 21:18:53 +0100126static int
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
128{
129 int minor, found;
130 struct dcssblk_dev_info *entry;
131
132 if (dev_info == NULL)
133 return -EINVAL;
134 for (minor = 0; minor < (1<<MINORBITS); minor++) {
135 found = 0;
136 // test if minor available
137 list_for_each_entry(entry, &dcssblk_devices, lh)
Tejun Heof331c022008-09-03 09:01:48 +0200138 if (minor == MINOR(disk_devt(entry->gd)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700139 found++;
140 if (!found) break; // got unused minor
141 }
142 if (found)
143 return -EBUSY;
144 dev_info->gd->first_minor = minor;
145 return 0;
146}
147
148/*
149 * get the struct dcssblk_dev_info from dcssblk_devices
150 * for the given name.
151 * down_read(&dcssblk_devices_sem) must be held.
152 */
153static struct dcssblk_dev_info *
154dcssblk_get_device_by_name(char *name)
155{
156 struct dcssblk_dev_info *entry;
157
158 list_for_each_entry(entry, &dcssblk_devices, lh) {
159 if (!strcmp(name, entry->segment_name)) {
160 return entry;
161 }
162 }
163 return NULL;
164}
165
Hongjie Yangb2300b92008-10-10 21:33:21 +0200166/*
167 * get the struct segment_info from seg_list
168 * for the given name.
169 * down_read(&dcssblk_devices_sem) must be held.
170 */
171static struct segment_info *
172dcssblk_get_segment_by_name(char *name)
173{
174 struct dcssblk_dev_info *dev_info;
175 struct segment_info *entry;
176
177 list_for_each_entry(dev_info, &dcssblk_devices, lh) {
178 list_for_each_entry(entry, &dev_info->seg_list, lh) {
179 if (!strcmp(name, entry->segment_name))
180 return entry;
181 }
182 }
183 return NULL;
184}
185
186/*
187 * get the highest address of the multi-segment block.
188 */
189static unsigned long
190dcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info)
191{
192 unsigned long highest_addr;
193 struct segment_info *entry;
194
195 highest_addr = 0;
196 list_for_each_entry(entry, &dev_info->seg_list, lh) {
197 if (highest_addr < entry->end)
198 highest_addr = entry->end;
199 }
200 return highest_addr;
201}
202
203/*
204 * get the lowest address of the multi-segment block.
205 */
206static unsigned long
207dcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info)
208{
209 int set_first;
210 unsigned long lowest_addr;
211 struct segment_info *entry;
212
213 set_first = 0;
214 lowest_addr = 0;
215 list_for_each_entry(entry, &dev_info->seg_list, lh) {
216 if (set_first == 0) {
217 lowest_addr = entry->start;
218 set_first = 1;
219 } else {
220 if (lowest_addr > entry->start)
221 lowest_addr = entry->start;
222 }
223 }
224 return lowest_addr;
225}
226
227/*
228 * Check continuity of segments.
229 */
230static int
231dcssblk_is_continuous(struct dcssblk_dev_info *dev_info)
232{
233 int i, j, rc;
234 struct segment_info *sort_list, *entry, temp;
235
236 if (dev_info->num_of_segments <= 1)
237 return 0;
238
239 sort_list = kzalloc(
240 sizeof(struct segment_info) * dev_info->num_of_segments,
241 GFP_KERNEL);
242 if (sort_list == NULL)
243 return -ENOMEM;
244 i = 0;
245 list_for_each_entry(entry, &dev_info->seg_list, lh) {
246 memcpy(&sort_list[i], entry, sizeof(struct segment_info));
247 i++;
248 }
249
250 /* sort segments */
251 for (i = 0; i < dev_info->num_of_segments; i++)
252 for (j = 0; j < dev_info->num_of_segments; j++)
253 if (sort_list[j].start > sort_list[i].start) {
254 memcpy(&temp, &sort_list[i],
255 sizeof(struct segment_info));
256 memcpy(&sort_list[i], &sort_list[j],
257 sizeof(struct segment_info));
258 memcpy(&sort_list[j], &temp,
259 sizeof(struct segment_info));
260 }
261
262 /* check continuity */
263 for (i = 0; i < dev_info->num_of_segments - 1; i++) {
264 if ((sort_list[i].end + 1) != sort_list[i+1].start) {
265 PRINT_ERR("Segment %s is not contiguous with "
266 "segment %s\n",
267 sort_list[i].segment_name,
268 sort_list[i+1].segment_name);
269 rc = -EINVAL;
270 goto out;
271 }
272 /* EN and EW are allowed in a block device */
273 if (sort_list[i].segment_type != sort_list[i+1].segment_type) {
274 if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) ||
275 (sort_list[i].segment_type == SEG_TYPE_ER) ||
276 !(sort_list[i+1].segment_type &
277 SEGMENT_EXCLUSIVE) ||
278 (sort_list[i+1].segment_type == SEG_TYPE_ER)) {
279 PRINT_ERR("Segment %s has different type from "
280 "segment %s\n",
281 sort_list[i].segment_name,
282 sort_list[i+1].segment_name);
283 rc = -EINVAL;
284 goto out;
285 }
286 }
287 }
288 rc = 0;
289out:
290 kfree(sort_list);
291 return rc;
292}
293
294/*
295 * Load a segment
296 */
297static int
298dcssblk_load_segment(char *name, struct segment_info **seg_info)
299{
300 int rc;
301
302 /* already loaded? */
303 down_read(&dcssblk_devices_sem);
304 *seg_info = dcssblk_get_segment_by_name(name);
305 up_read(&dcssblk_devices_sem);
306 if (*seg_info != NULL)
307 return -EEXIST;
308
309 /* get a struct segment_info */
310 *seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL);
311 if (*seg_info == NULL)
312 return -ENOMEM;
313
314 strcpy((*seg_info)->segment_name, name);
315
316 /* load the segment */
317 rc = segment_load(name, SEGMENT_SHARED,
318 &(*seg_info)->start, &(*seg_info)->end);
319 if (rc < 0) {
320 segment_warning(rc, (*seg_info)->segment_name);
321 kfree(*seg_info);
322 } else {
323 INIT_LIST_HEAD(&(*seg_info)->lh);
324 (*seg_info)->segment_type = rc;
325 }
326 return rc;
327}
328
Gerald Schaefer931bb682007-11-05 11:10:09 +0100329static void dcssblk_unregister_callback(struct device *dev)
330{
331 device_unregister(dev);
332 put_device(dev);
333}
334
Linus Torvalds1da177e2005-04-16 15:20:36 -0700335/*
336 * device attribute for switching shared/nonshared (exclusive)
337 * operation (show + store)
338 */
339static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400340dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700341{
342 struct dcssblk_dev_info *dev_info;
343
344 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
345 return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
346}
347
348static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400349dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700350{
351 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200352 struct segment_info *entry, *temp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700353 int rc;
354
Hongjie Yangded77fb2008-07-14 09:59:39 +0200355 if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700356 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700357 down_write(&dcssblk_devices_sem);
358 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
359 if (atomic_read(&dev_info->use_count)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700360 rc = -EBUSY;
361 goto out;
362 }
363 if (inbuf[0] == '1') {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200364 /* reload segments in shared mode */
365 list_for_each_entry(entry, &dev_info->seg_list, lh) {
366 rc = segment_modify_shared(entry->segment_name,
367 SEGMENT_SHARED);
368 if (rc < 0) {
369 BUG_ON(rc == -EINVAL);
370 if (rc != -EAGAIN)
371 goto removeseg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700372 }
373 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200374 dev_info->is_shared = 1;
375 switch (dev_info->segment_type) {
376 case SEG_TYPE_SR:
377 case SEG_TYPE_ER:
378 case SEG_TYPE_SC:
379 set_disk_ro(dev_info->gd, 1);
380 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700381 } else if (inbuf[0] == '0') {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200382 /* reload segments in exclusive mode */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700383 if (dev_info->segment_type == SEG_TYPE_SC) {
384 PRINT_ERR("Segment type SC (%s) cannot be loaded in "
Hongjie Yangb2300b92008-10-10 21:33:21 +0200385 "non-shared mode\n", dev_info->segment_name);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700386 rc = -EINVAL;
387 goto out;
388 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200389 list_for_each_entry(entry, &dev_info->seg_list, lh) {
390 rc = segment_modify_shared(entry->segment_name,
391 SEGMENT_EXCLUSIVE);
392 if (rc < 0) {
393 BUG_ON(rc == -EINVAL);
394 if (rc != -EAGAIN)
395 goto removeseg;
396 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700397 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200398 dev_info->is_shared = 0;
399 set_disk_ro(dev_info->gd, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700400 } else {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700401 rc = -EINVAL;
402 goto out;
403 }
404 rc = count;
405 goto out;
406
407removeseg:
Hongjie Yangb2300b92008-10-10 21:33:21 +0200408 PRINT_ERR("Could not reload segment(s) of the device %s, removing "
409 "segment(s) now!\n",
410 dev_info->segment_name);
411 temp = entry;
412 list_for_each_entry(entry, &dev_info->seg_list, lh) {
413 if (entry != temp)
414 segment_unload(entry->segment_name);
415 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700416 list_del(&dev_info->lh);
417
418 del_gendisk(dev_info->gd);
Al Viro1312f402006-03-12 11:02:03 -0500419 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700420 dev_info->gd->queue = NULL;
421 put_disk(dev_info->gd);
Gerald Schaefer931bb682007-11-05 11:10:09 +0100422 rc = device_schedule_callback(dev, dcssblk_unregister_callback);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700423out:
424 up_write(&dcssblk_devices_sem);
425 return rc;
426}
427
428/*
429 * device attribute for save operation on current copy
430 * of the segment. If the segment is busy, saving will
431 * become pending until it gets released, which can be
432 * undone by storing a non-true value to this entry.
433 * (show + store)
434 */
435static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400436dcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700437{
438 struct dcssblk_dev_info *dev_info;
439
440 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
441 return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
442}
443
444static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400445dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700446{
447 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200448 struct segment_info *entry;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700449
Hongjie Yangded77fb2008-07-14 09:59:39 +0200450 if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700451 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700452 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
453
454 down_write(&dcssblk_devices_sem);
455 if (inbuf[0] == '1') {
456 if (atomic_read(&dev_info->use_count) == 0) {
457 // device is idle => we save immediately
Hongjie Yangb2300b92008-10-10 21:33:21 +0200458 PRINT_INFO("Saving segment(s) of the device %s\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700459 dev_info->segment_name);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200460 list_for_each_entry(entry, &dev_info->seg_list, lh) {
461 segment_save(entry->segment_name);
462 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700463 } else {
464 // device is busy => we save it when it becomes
465 // idle in dcssblk_release
Hongjie Yangb2300b92008-10-10 21:33:21 +0200466 PRINT_INFO("Device %s is currently busy, segment(s) "
467 "will be saved when it becomes idle...\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700468 dev_info->segment_name);
469 dev_info->save_pending = 1;
470 }
471 } else if (inbuf[0] == '0') {
472 if (dev_info->save_pending) {
473 // device is busy & the user wants to undo his save
474 // request
475 dev_info->save_pending = 0;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200476 PRINT_INFO("Pending save for segment(s) of the device "
477 "%s deactivated\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700478 dev_info->segment_name);
479 }
480 } else {
481 up_write(&dcssblk_devices_sem);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700482 return -EINVAL;
483 }
484 up_write(&dcssblk_devices_sem);
485 return count;
486}
487
488/*
Hongjie Yangb2300b92008-10-10 21:33:21 +0200489 * device attribute for showing all segments in a device
490 */
491static ssize_t
492dcssblk_seglist_show(struct device *dev, struct device_attribute *attr,
493 char *buf)
494{
495 int i;
496
497 struct dcssblk_dev_info *dev_info;
498 struct segment_info *entry;
499
500 down_read(&dcssblk_devices_sem);
501 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
502 i = 0;
503 buf[0] = '\0';
504 list_for_each_entry(entry, &dev_info->seg_list, lh) {
505 strcpy(&buf[i], entry->segment_name);
506 i += strlen(entry->segment_name);
507 buf[i] = '\n';
508 i++;
509 }
510 up_read(&dcssblk_devices_sem);
511 return i;
512}
513
514/*
Linus Torvalds1da177e2005-04-16 15:20:36 -0700515 * device attribute for adding devices
516 */
517static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400518dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700519{
Hongjie Yangb2300b92008-10-10 21:33:21 +0200520 int rc, i, j, num_of_segments;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700521 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200522 struct segment_info *seg_info, *temp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700523 char *local_buf;
524 unsigned long seg_byte_size;
525
526 dev_info = NULL;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200527 seg_info = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700528 if (dev != dcssblk_root_dev) {
529 rc = -EINVAL;
530 goto out_nobuf;
531 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200532 if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) {
533 rc = -ENAMETOOLONG;
534 goto out_nobuf;
535 }
536
Linus Torvalds1da177e2005-04-16 15:20:36 -0700537 local_buf = kmalloc(count + 1, GFP_KERNEL);
538 if (local_buf == NULL) {
539 rc = -ENOMEM;
540 goto out_nobuf;
541 }
Hongjie Yangb2300b92008-10-10 21:33:21 +0200542
Linus Torvalds1da177e2005-04-16 15:20:36 -0700543 /*
544 * parse input
545 */
Hongjie Yangb2300b92008-10-10 21:33:21 +0200546 num_of_segments = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700547 for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
Hongjie Yangb2300b92008-10-10 21:33:21 +0200548 for (j = i; (buf[j] != ':') &&
549 (buf[j] != '\0') &&
550 (buf[j] != '\n') &&
551 j < count; j++) {
552 local_buf[j-i] = toupper(buf[j]);
553 }
554 local_buf[j-i] = '\0';
555 if (((j - i) == 0) || ((j - i) > 8)) {
556 rc = -ENAMETOOLONG;
557 goto seg_list_del;
558 }
559
560 rc = dcssblk_load_segment(local_buf, &seg_info);
561 if (rc < 0)
562 goto seg_list_del;
563 /*
564 * get a struct dcssblk_dev_info
565 */
566 if (num_of_segments == 0) {
567 dev_info = kzalloc(sizeof(struct dcssblk_dev_info),
568 GFP_KERNEL);
569 if (dev_info == NULL) {
570 rc = -ENOMEM;
571 goto out;
572 }
573 strcpy(dev_info->segment_name, local_buf);
574 dev_info->segment_type = seg_info->segment_type;
575 INIT_LIST_HEAD(&dev_info->seg_list);
576 }
577 list_add_tail(&seg_info->lh, &dev_info->seg_list);
578 num_of_segments++;
579 i = j;
580
581 if ((buf[j] == '\0') || (buf[j] == '\n'))
582 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700583 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700584
Hongjie Yangb2300b92008-10-10 21:33:21 +0200585 /* no trailing colon at the end of the input */
586 if ((i > 0) && (buf[i-1] == ':')) {
587 rc = -ENAMETOOLONG;
588 goto seg_list_del;
589 }
590 strlcpy(local_buf, buf, i + 1);
591 dev_info->num_of_segments = num_of_segments;
592 rc = dcssblk_is_continuous(dev_info);
593 if (rc < 0)
594 goto seg_list_del;
595
596 dev_info->start = dcssblk_find_lowest_addr(dev_info);
597 dev_info->end = dcssblk_find_highest_addr(dev_info);
598
599 dev_set_name(&dev_info->dev, dev_info->segment_name);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700600 dev_info->dev.release = dcssblk_release_segment;
601 INIT_LIST_HEAD(&dev_info->lh);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700602 dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
603 if (dev_info->gd == NULL) {
604 rc = -ENOMEM;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200605 goto seg_list_del;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700606 }
607 dev_info->gd->major = dcssblk_major;
608 dev_info->gd->fops = &dcssblk_devops;
609 dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL);
610 dev_info->gd->queue = dev_info->dcssblk_queue;
611 dev_info->gd->private_data = dev_info;
612 dev_info->gd->driverfs_dev = &dev_info->dev;
Heiko Carstensc5411db2008-02-05 16:50:50 +0100613 blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
614 blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200615
Linus Torvalds1da177e2005-04-16 15:20:36 -0700616 seg_byte_size = (dev_info->end - dev_info->start + 1);
617 set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
Hongjie Yangb2300b92008-10-10 21:33:21 +0200618 PRINT_INFO("Loaded segment(s) %s, size = %lu Byte, "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700619 "capacity = %lu (512 Byte) sectors\n", local_buf,
620 seg_byte_size, seg_byte_size >> 9);
621
Linus Torvalds1da177e2005-04-16 15:20:36 -0700622 dev_info->save_pending = 0;
623 dev_info->is_shared = 1;
624 dev_info->dev.parent = dcssblk_root_dev;
625
626 /*
Hongjie Yangb2300b92008-10-10 21:33:21 +0200627 *get minor, add to list
Linus Torvalds1da177e2005-04-16 15:20:36 -0700628 */
629 down_write(&dcssblk_devices_sem);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200630 if (dcssblk_get_segment_by_name(local_buf)) {
Gerald Schaefer04f64b52008-08-21 19:46:40 +0200631 rc = -EEXIST;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200632 goto release_gd;
Gerald Schaefer04f64b52008-08-21 19:46:40 +0200633 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700634 rc = dcssblk_assign_free_minor(dev_info);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200635 if (rc)
636 goto release_gd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700637 sprintf(dev_info->gd->disk_name, "dcssblk%d",
Tejun Heof331c022008-09-03 09:01:48 +0200638 MINOR(disk_devt(dev_info->gd)));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700639 list_add_tail(&dev_info->lh, &dcssblk_devices);
640
641 if (!try_module_get(THIS_MODULE)) {
642 rc = -ENODEV;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200643 goto dev_list_del;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700644 }
645 /*
646 * register the device
647 */
648 rc = device_register(&dev_info->dev);
649 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700650 module_put(THIS_MODULE);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200651 goto dev_list_del;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700652 }
653 get_device(&dev_info->dev);
654 rc = device_create_file(&dev_info->dev, &dev_attr_shared);
655 if (rc)
656 goto unregister_dev;
657 rc = device_create_file(&dev_info->dev, &dev_attr_save);
658 if (rc)
659 goto unregister_dev;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200660 rc = device_create_file(&dev_info->dev, &dev_attr_seglist);
661 if (rc)
662 goto unregister_dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700663
Christian Borntraeger436d1bc2007-12-04 16:09:03 +0100664 add_disk(dev_info->gd);
665
Linus Torvalds1da177e2005-04-16 15:20:36 -0700666 switch (dev_info->segment_type) {
667 case SEG_TYPE_SR:
668 case SEG_TYPE_ER:
669 case SEG_TYPE_SC:
670 set_disk_ro(dev_info->gd,1);
671 break;
672 default:
673 set_disk_ro(dev_info->gd,0);
674 break;
675 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700676 up_write(&dcssblk_devices_sem);
677 rc = count;
678 goto out;
679
680unregister_dev:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700681 list_del(&dev_info->lh);
Al Viro1312f402006-03-12 11:02:03 -0500682 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700683 dev_info->gd->queue = NULL;
684 put_disk(dev_info->gd);
685 device_unregister(&dev_info->dev);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200686 list_for_each_entry(seg_info, &dev_info->seg_list, lh) {
687 segment_unload(seg_info->segment_name);
688 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700689 put_device(&dev_info->dev);
690 up_write(&dcssblk_devices_sem);
691 goto out;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200692dev_list_del:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700693 list_del(&dev_info->lh);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200694release_gd:
Al Viro1312f402006-03-12 11:02:03 -0500695 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700696 dev_info->gd->queue = NULL;
697 put_disk(dev_info->gd);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200698 up_write(&dcssblk_devices_sem);
699seg_list_del:
700 if (dev_info == NULL)
701 goto out;
702 list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) {
703 list_del(&seg_info->lh);
704 segment_unload(seg_info->segment_name);
705 kfree(seg_info);
706 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700707 kfree(dev_info);
708out:
709 kfree(local_buf);
710out_nobuf:
711 return rc;
712}
713
714/*
715 * device attribute for removing devices
716 */
717static ssize_t
Yani Ioannoue404e272005-05-17 06:42:58 -0400718dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700719{
720 struct dcssblk_dev_info *dev_info;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200721 struct segment_info *entry;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700722 int rc, i;
723 char *local_buf;
724
725 if (dev != dcssblk_root_dev) {
726 return -EINVAL;
727 }
728 local_buf = kmalloc(count + 1, GFP_KERNEL);
729 if (local_buf == NULL) {
730 return -ENOMEM;
731 }
732 /*
733 * parse input
734 */
735 for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) {
736 local_buf[i] = toupper(buf[i]);
737 }
738 local_buf[i] = '\0';
739 if ((i == 0) || (i > 8)) {
740 rc = -ENAMETOOLONG;
741 goto out_buf;
742 }
743
744 down_write(&dcssblk_devices_sem);
745 dev_info = dcssblk_get_device_by_name(local_buf);
746 if (dev_info == NULL) {
747 up_write(&dcssblk_devices_sem);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200748 PRINT_WARN("Device %s is not loaded!\n", local_buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700749 rc = -ENODEV;
750 goto out_buf;
751 }
752 if (atomic_read(&dev_info->use_count) != 0) {
753 up_write(&dcssblk_devices_sem);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200754 PRINT_WARN("Device %s is in use!\n", local_buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700755 rc = -EBUSY;
756 goto out_buf;
757 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700758
Hongjie Yangb2300b92008-10-10 21:33:21 +0200759 list_del(&dev_info->lh);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700760 del_gendisk(dev_info->gd);
Al Viro1312f402006-03-12 11:02:03 -0500761 blk_cleanup_queue(dev_info->dcssblk_queue);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700762 dev_info->gd->queue = NULL;
763 put_disk(dev_info->gd);
764 device_unregister(&dev_info->dev);
Hongjie Yangb2300b92008-10-10 21:33:21 +0200765
766 /* unload all related segments */
767 list_for_each_entry(entry, &dev_info->seg_list, lh)
768 segment_unload(entry->segment_name);
769
Linus Torvalds1da177e2005-04-16 15:20:36 -0700770 put_device(&dev_info->dev);
771 up_write(&dcssblk_devices_sem);
772
773 rc = count;
774out_buf:
775 kfree(local_buf);
776 return rc;
777}
778
779static int
Al Viro46d74322008-03-02 10:37:41 -0500780dcssblk_open(struct block_device *bdev, fmode_t mode)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700781{
782 struct dcssblk_dev_info *dev_info;
783 int rc;
784
Al Viro46d74322008-03-02 10:37:41 -0500785 dev_info = bdev->bd_disk->private_data;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700786 if (NULL == dev_info) {
787 rc = -ENODEV;
788 goto out;
789 }
790 atomic_inc(&dev_info->use_count);
Al Viro46d74322008-03-02 10:37:41 -0500791 bdev->bd_block_size = 4096;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700792 rc = 0;
793out:
794 return rc;
795}
796
797static int
Al Viro46d74322008-03-02 10:37:41 -0500798dcssblk_release(struct gendisk *disk, fmode_t mode)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700799{
Al Viro46d74322008-03-02 10:37:41 -0500800 struct dcssblk_dev_info *dev_info = disk->private_data;
Hongjie Yangb2300b92008-10-10 21:33:21 +0200801 struct segment_info *entry;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700802 int rc;
803
Al Viro46d74322008-03-02 10:37:41 -0500804 if (!dev_info) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700805 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");