]> git.ipfire.org Git - people/pmueller/ipfire-2.x.git/blob - src/patches/suse-2.6.27.31/patches.arch/s390-01-02-dcss-64-v2.patch
Disable build of xen kernel.
[people/pmueller/ipfire-2.x.git] / src / patches / suse-2.6.27.31 / patches.arch / s390-01-02-dcss-64-v2.patch
1 From: Gerald Schaefer <geraldsc@de.ibm.com>
2 Subject: dcssblk (new function): Add support for >2G DCSS and stacked
3 contiguous DCSS support.
4 References: bnc#417246
5
6 Description: The DCSS block device driver is modified to add >2G DCSS support
7 and allow a DCSS block device to map to a set of contiguous DCSSs.
8 The extmem code is also modified to use new Diagnose x'64' subcodes
9 for >2G DCSSs.
10
11 Acked-by: John Jolly <jjolly@suse.de>
12
13 ---
14
15 arch/s390/mm/extmem.c | 251 +++++++++++++++++---
16 drivers/s390/block/dcssblk.c | 516 ++++++++++++++++++++++++++++++++-----------
17 2 files changed, 597 insertions(+), 170 deletions(-)
18
19 Index: linux-sles11/arch/s390/mm/extmem.c
20 ===================================================================
21 --- linux-sles11.orig/arch/s390/mm/extmem.c
22 +++ linux-sles11/arch/s390/mm/extmem.c
23 @@ -43,20 +43,40 @@
24 #define DCSS_FINDSEG 0x0c
25 #define DCSS_LOADNOLY 0x10
26 #define DCSS_SEGEXT 0x18
27 +#define DCSS_LOADSHRX 0x20
28 +#define DCSS_LOADNSRX 0x24
29 +#define DCSS_FINDSEGX 0x2c
30 +#define DCSS_SEGEXTX 0x38
31 #define DCSS_FINDSEGA 0x0c
32
33 struct qrange {
34 - unsigned int start; // 3byte start address, 1 byte type
35 - unsigned int end; // 3byte end address, 1 byte reserved
36 + unsigned long start; /* last byte type */
37 + unsigned long end; /* last byte reserved */
38 };
39
40 struct qout64 {
41 + unsigned long segstart;
42 + unsigned long segend;
43 + int segcnt;
44 + int segrcnt;
45 + struct qrange range[6];
46 +};
47 +
48 +#ifdef CONFIG_64BIT
49 +struct qrange_old {
50 + unsigned int start; /* last byte type */
51 + unsigned int end; /* last byte reserved */
52 +};
53 +
54 +/* output area format for the Diag x'64' old subcode x'18' */
55 +struct qout64_old {
56 int segstart;
57 int segend;
58 int segcnt;
59 int segrcnt;
60 - struct qrange range[6];
61 + struct qrange_old range[6];
62 };
63 +#endif
64
65 struct qin64 {
66 char qopcode;
67 @@ -86,6 +106,55 @@ static DEFINE_MUTEX(dcss_lock);
68 static LIST_HEAD(dcss_list);
69 static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
70 "EW/EN-MIXED" };
71 +static int loadshr_scode, loadnsr_scode, findseg_scode;
72 +static int segext_scode, purgeseg_scode;
73 +static int scode_set;
74 +
75 +/* set correct Diag x'64' subcodes. */
76 +static int
77 +dcss_set_subcodes(void)
78 +{
79 +#ifdef CONFIG_64BIT
80 + char *name = kmalloc(8 * sizeof(char), GFP_DMA);
81 + unsigned long rx, ry;
82 + int rc;
83 +
84 + if (name == NULL)
85 + return -ENOMEM;
86 +
87 + rx = (unsigned long) name;
88 + ry = DCSS_FINDSEGX;
89 +
90 + strcpy(name, "dummy");
91 + asm volatile(
92 + " diag %0,%1,0x64\n"
93 + "0: ipm %2\n"
94 + " srl %2,28\n"
95 + " j 2f\n"
96 + "1: la %2,3\n"
97 + "2:\n"
98 + EX_TABLE(0b, 1b)
99 + : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
100 +
101 + kfree(name);
102 + /* Diag x'64' new subcodes are supported, set to new subcodes */
103 + if (rc != 3) {
104 + loadshr_scode = DCSS_LOADSHRX;
105 + loadnsr_scode = DCSS_LOADNSRX;
106 + purgeseg_scode = DCSS_PURGESEG;
107 + findseg_scode = DCSS_FINDSEGX;
108 + segext_scode = DCSS_SEGEXTX;
109 + return 0;
110 + }
111 +#endif
112 + /* Diag x'64' new subcodes are not supported, set to old subcodes */
113 + loadshr_scode = DCSS_LOADNOLY;
114 + loadnsr_scode = DCSS_LOADNSR;
115 + purgeseg_scode = DCSS_PURGESEG;
116 + findseg_scode = DCSS_FINDSEG;
117 + segext_scode = DCSS_SEGEXT;
118 + return 0;
119 +}
120
121 /*
122 * Create the 8 bytes, ebcdic VM segment name from
123 @@ -135,25 +204,45 @@ segment_by_name (char *name)
124 * Perform a function on a dcss segment.
125 */
126 static inline int
127 -dcss_diag (__u8 func, void *parameter,
128 +dcss_diag(int *func, void *parameter,
129 unsigned long *ret1, unsigned long *ret2)
130 {
131 unsigned long rx, ry;
132 int rc;
133
134 + if (scode_set == 0) {
135 + rc = dcss_set_subcodes();
136 + if (rc < 0)
137 + return rc;
138 + scode_set = 1;
139 + }
140 rx = (unsigned long) parameter;
141 - ry = (unsigned long) func;
142 - asm volatile(
143 + ry = (unsigned long) *func;
144 +
145 #ifdef CONFIG_64BIT
146 - " sam31\n"
147 - " diag %0,%1,0x64\n"
148 - " sam64\n"
149 + /* 64-bit Diag x'64' new subcode, keep in 64-bit addressing mode */
150 + if (*func > DCSS_SEGEXT)
151 + asm volatile(
152 + " diag %0,%1,0x64\n"
153 + " ipm %2\n"
154 + " srl %2,28\n"
155 + : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
156 + /* 31-bit Diag x'64' old subcode, switch to 31-bit addressing mode */
157 + else
158 + asm volatile(
159 + " sam31\n"
160 + " diag %0,%1,0x64\n"
161 + " sam64\n"
162 + " ipm %2\n"
163 + " srl %2,28\n"
164 + : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
165 #else
166 + asm volatile(
167 " diag %0,%1,0x64\n"
168 -#endif
169 " ipm %2\n"
170 " srl %2,28\n"
171 : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
172 +#endif
173 *ret1 = rx;
174 *ret2 = ry;
175 return rc;
176 @@ -190,14 +279,45 @@ query_segment_type (struct dcss_segment
177 qin->qoutlen = sizeof(struct qout64);
178 memcpy (qin->qname, seg->dcss_name, 8);
179
180 - diag_cc = dcss_diag (DCSS_SEGEXT, qin, &dummy, &vmrc);
181 + diag_cc = dcss_diag(&segext_scode, qin, &dummy, &vmrc);
182
183 + if (diag_cc < 0) {
184 + rc = diag_cc;
185 + goto out_free;
186 + }
187 if (diag_cc > 1) {
188 PRINT_WARN ("segment_type: diag returned error %ld\n", vmrc);
189 rc = dcss_diag_translate_rc (vmrc);
190 goto out_free;
191 }
192
193 +#ifdef CONFIG_64BIT
194 + /* Only old format of output area of Diagnose x'64' is supported,
195 + copy data for the new format. */
196 + if (segext_scode == DCSS_SEGEXT) {
197 + struct qout64_old *qout_old;
198 + qout_old = kzalloc(sizeof(struct qout64_old), GFP_DMA);
199 + if (qout_old == NULL) {
200 + rc = -ENOMEM;
201 + goto out_free;
202 + }
203 + memcpy(qout_old, qout, sizeof(struct qout64_old));
204 + qout->segstart = (unsigned long) qout_old->segstart;
205 + qout->segend = (unsigned long) qout_old->segend;
206 + qout->segcnt = qout_old->segcnt;
207 + qout->segrcnt = qout_old->segrcnt;
208 +
209 + if (qout->segcnt > 6)
210 + qout->segrcnt = 6;
211 + for (i = 0; i < qout->segrcnt; i++) {
212 + qout->range[i].start =
213 + (unsigned long) qout_old->range[i].start;
214 + qout->range[i].end =
215 + (unsigned long) qout_old->range[i].end;
216 + }
217 + kfree(qout_old);
218 + }
219 +#endif
220 if (qout->segcnt > 6) {
221 rc = -ENOTSUPP;
222 goto out_free;
223 @@ -269,6 +389,30 @@ segment_type (char* name)
224 }
225
226 /*
227 + * check if segment collides with other segments that are currently loaded
228 + * returns 1 if this is the case, 0 if no collision was found
229 + */
230 +static int
231 +segment_overlaps_others (struct dcss_segment *seg)
232 +{
233 + struct list_head *l;
234 + struct dcss_segment *tmp;
235 +
236 + BUG_ON(!mutex_is_locked(&dcss_lock));
237 + list_for_each(l, &dcss_list) {
238 + tmp = list_entry(l, struct dcss_segment, list);
239 + if ((tmp->start_addr >> 20) > (seg->end >> 20))
240 + continue;
241 + if ((tmp->end >> 20) < (seg->start_addr >> 20))
242 + continue;
243 + if (seg == tmp)
244 + continue;
245 + return 1;
246 + }
247 + return 0;
248 +}
249 +
250 +/*
251 * real segment loading function, called from segment_load
252 */
253 static int
254 @@ -276,7 +420,8 @@ __segment_load (char *name, int do_nonsh
255 {
256 struct dcss_segment *seg = kmalloc(sizeof(struct dcss_segment),
257 GFP_DMA);
258 - int dcss_command, rc, diag_cc;
259 + int rc, diag_cc;
260 + unsigned long start_addr, end_addr, dummy;
261
262 if (seg == NULL) {
263 rc = -ENOMEM;
264 @@ -287,6 +432,13 @@ __segment_load (char *name, int do_nonsh
265 if (rc < 0)
266 goto out_free;
267
268 + if (loadshr_scode == DCSS_LOADSHRX) {
269 + if (segment_overlaps_others(seg)) {
270 + rc = -EBUSY;
271 + goto out_free;
272 + }
273 + }
274 +
275 rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
276
277 if (rc)
278 @@ -316,20 +468,28 @@ __segment_load (char *name, int do_nonsh
279 }
280
281 if (do_nonshared)
282 - dcss_command = DCSS_LOADNSR;
283 + diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
284 + &start_addr, &end_addr);
285 else
286 - dcss_command = DCSS_LOADNOLY;
287 -
288 - diag_cc = dcss_diag(dcss_command, seg->dcss_name,
289 - &seg->start_addr, &seg->end);
290 + diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
291 + &start_addr, &end_addr);
292 + if (diag_cc < 0) {
293 + dcss_diag(&purgeseg_scode, seg->dcss_name,
294 + &dummy, &dummy);
295 + rc = diag_cc;
296 + goto out_resource;
297 + }
298 if (diag_cc > 1) {
299 PRINT_WARN ("segment_load: could not load segment %s - "
300 - "diag returned error (%ld)\n",name,seg->end);
301 - rc = dcss_diag_translate_rc (seg->end);
302 - dcss_diag(DCSS_PURGESEG, seg->dcss_name,
303 - &seg->start_addr, &seg->end);
304 + "diag returned error (%ld)\n",
305 + name, end_addr);
306 + rc = dcss_diag_translate_rc(end_addr);
307 + dcss_diag(&purgeseg_scode, seg->dcss_name,
308 + &dummy, &dummy);
309 goto out_resource;
310 }
311 + seg->start_addr = start_addr;
312 + seg->end = end_addr;
313 seg->do_nonshared = do_nonshared;
314 atomic_set(&seg->ref_count, 1);
315 list_add(&seg->list, &dcss_list);
316 @@ -423,8 +583,8 @@ int
317 segment_modify_shared (char *name, int do_nonshared)
318 {
319 struct dcss_segment *seg;
320 - unsigned long dummy;
321 - int dcss_command, rc, diag_cc;
322 + unsigned long start_addr, end_addr, dummy;
323 + int rc, diag_cc;
324
325 mutex_lock(&dcss_lock);
326 seg = segment_by_name (name);
327 @@ -445,38 +605,51 @@ segment_modify_shared (char *name, int d
328 goto out_unlock;
329 }
330 release_resource(seg->res);
331 - if (do_nonshared) {
332 - dcss_command = DCSS_LOADNSR;
333 + if (do_nonshared)
334 seg->res->flags &= ~IORESOURCE_READONLY;
335 - } else {
336 - dcss_command = DCSS_LOADNOLY;
337 + else
338 if (seg->vm_segtype == SEG_TYPE_SR ||
339 seg->vm_segtype == SEG_TYPE_ER)
340 seg->res->flags |= IORESOURCE_READONLY;
341 - }
342 +
343 if (request_resource(&iomem_resource, seg->res)) {
344 PRINT_WARN("segment_modify_shared: could not reload segment %s"
345 " - overlapping resources\n", name);
346 rc = -EBUSY;
347 kfree(seg->res);
348 - goto out_del;
349 + goto out_del_mem;
350 + }
351 +
352 + dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
353 + if (do_nonshared)
354 + diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
355 + &start_addr, &end_addr);
356 + else
357 + diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
358 + &start_addr, &end_addr);
359 + if (diag_cc < 0) {
360 + rc = diag_cc;
361 + goto out_del_res;
362 }
363 - dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
364 - diag_cc = dcss_diag(dcss_command, seg->dcss_name,
365 - &seg->start_addr, &seg->end);
366 if (diag_cc > 1) {
367 PRINT_WARN ("segment_modify_shared: could not reload segment %s"
368 - " - diag returned error (%ld)\n",name,seg->end);
369 - rc = dcss_diag_translate_rc (seg->end);
370 - goto out_del;
371 + " - diag returned error (%ld)\n",
372 + name, end_addr);
373 + rc = dcss_diag_translate_rc(end_addr);
374 + goto out_del_res;
375 }
376 + seg->start_addr = start_addr;
377 + seg->end = end_addr;
378 seg->do_nonshared = do_nonshared;
379 rc = 0;
380 goto out_unlock;
381 - out_del:
382 + out_del_res:
383 + release_resource(seg->res);
384 + kfree(seg->res);
385 + out_del_mem:
386 vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
387 list_del(&seg->list);
388 - dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
389 + dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
390 kfree(seg);
391 out_unlock:
392 mutex_unlock(&dcss_lock);
393 @@ -510,7 +683,7 @@ segment_unload(char *name)
394 kfree(seg->res);
395 vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
396 list_del(&seg->list);
397 - dcss_diag(DCSS_PURGESEG, seg->dcss_name, &dummy, &dummy);
398 + dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
399 kfree(seg);
400 out_unlock:
401 mutex_unlock(&dcss_lock);
402 @@ -545,7 +718,7 @@ segment_save(char *name)
403 endpfn = (seg->end) >> PAGE_SHIFT;
404 sprintf(cmd1, "DEFSEG %s", name);
405 for (i=0; i<seg->segcnt; i++) {
406 - sprintf(cmd1+strlen(cmd1), " %X-%X %s",
407 + sprintf(cmd1+strlen(cmd1), " %lX-%lX %s",
408 seg->range[i].start >> PAGE_SHIFT,
409 seg->range[i].end >> PAGE_SHIFT,
410 segtype_string[seg->range[i].start & 0xff]);
411 Index: linux-sles11/drivers/s390/block/dcssblk.c
412 ===================================================================
413 --- linux-sles11.orig/drivers/s390/block/dcssblk.c
414 +++ linux-sles11/drivers/s390/block/dcssblk.c
415 @@ -31,7 +31,6 @@
416 #define PRINT_WARN(x...) printk(KERN_WARNING DCSSBLK_NAME " warning: " x)
417 #define PRINT_ERR(x...) printk(KERN_ERR DCSSBLK_NAME " error: " x)
418
419 -
420 static int dcssblk_open(struct inode *inode, struct file *filp);
421 static int dcssblk_release(struct inode *inode, struct file *filp);
422 static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
423 @@ -48,6 +47,30 @@ static struct block_device_operations dc
424 .direct_access = dcssblk_direct_access,
425 };
426
427 +struct dcssblk_dev_info {
428 + struct list_head lh;
429 + struct device dev;
430 + char segment_name[BUS_ID_SIZE];
431 + atomic_t use_count;
432 + struct gendisk *gd;
433 + unsigned long start;
434 + unsigned long end;
435 + int segment_type;
436 + unsigned char save_pending;
437 + unsigned char is_shared;
438 + struct request_queue *dcssblk_queue;
439 + int num_of_segments;
440 + struct list_head seg_list;
441 +};
442 +
443 +struct segment_info {
444 + struct list_head lh;
445 + char segment_name[BUS_ID_SIZE];
446 + unsigned long start;
447 + unsigned long end;
448 + int segment_type;
449 +};
450 +
451 static ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf,
452 size_t count);
453 static ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf,
454 @@ -58,30 +81,20 @@ static ssize_t dcssblk_save_show(struct
455 static ssize_t dcssblk_shared_store(struct device * dev, struct device_attribute *attr, const char * buf,
456 size_t count);
457 static ssize_t dcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf);
458 +static ssize_t dcssblk_seglist_show(struct device *dev,
459 + struct device_attribute *attr,
460 + char *buf);
461
462 static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
463 static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
464 -static DEVICE_ATTR(save, S_IWUSR | S_IRUGO, dcssblk_save_show,
465 +static DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show,
466 dcssblk_save_store);
467 -static DEVICE_ATTR(shared, S_IWUSR | S_IRUGO, dcssblk_shared_show,
468 +static DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show,
469 dcssblk_shared_store);
470 +static DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL);
471
472 static struct device *dcssblk_root_dev;
473
474 -struct dcssblk_dev_info {
475 - struct list_head lh;
476 - struct device dev;
477 - char segment_name[BUS_ID_SIZE];
478 - atomic_t use_count;
479 - struct gendisk *gd;
480 - unsigned long start;
481 - unsigned long end;
482 - int segment_type;
483 - unsigned char save_pending;
484 - unsigned char is_shared;
485 - struct request_queue *dcssblk_queue;
486 -};
487 -
488 static LIST_HEAD(dcssblk_devices);
489 static struct rw_semaphore dcssblk_devices_sem;
490
491 @@ -91,8 +104,15 @@ static struct rw_semaphore dcssblk_devic
492 static void
493 dcssblk_release_segment(struct device *dev)
494 {
495 - PRINT_DEBUG("segment release fn called for %s\n", dev->bus_id);
496 - kfree(container_of(dev, struct dcssblk_dev_info, dev));
497 + struct dcssblk_dev_info *dev_info;
498 + struct segment_info *entry, *temp;
499 +
500 + dev_info = container_of(dev, struct dcssblk_dev_info, dev);
501 + list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) {
502 + list_del(&entry->lh);
503 + kfree(entry);
504 + }
505 + kfree(dev_info);
506 module_put(THIS_MODULE);
507 }
508
509 @@ -142,6 +162,169 @@ dcssblk_get_device_by_name(char *name)
510 return NULL;
511 }
512
513 +/*
514 + * get the struct segment_info from seg_list
515 + * for the given name.
516 + * down_read(&dcssblk_devices_sem) must be held.
517 + */
518 +static struct segment_info *
519 +dcssblk_get_segment_by_name(char *name)
520 +{
521 + struct dcssblk_dev_info *dev_info;
522 + struct segment_info *entry;
523 +
524 + list_for_each_entry(dev_info, &dcssblk_devices, lh) {
525 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
526 + if (!strcmp(name, entry->segment_name))
527 + return entry;
528 + }
529 + }
530 + return NULL;
531 +}
532 +
533 +/*
534 + * get the highest address of the multi-segment block.
535 + */
536 +static unsigned long
537 +dcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info)
538 +{
539 + unsigned long highest_addr;
540 + struct segment_info *entry;
541 +
542 + highest_addr = 0;
543 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
544 + if (highest_addr < entry->end)
545 + highest_addr = entry->end;
546 + }
547 + return highest_addr;
548 +}
549 +
550 +/*
551 + * get the lowest address of the multi-segment block.
552 + */
553 +static unsigned long
554 +dcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info)
555 +{
556 + int set_first;
557 + unsigned long lowest_addr;
558 + struct segment_info *entry;
559 +
560 + set_first = 0;
561 + lowest_addr = 0;
562 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
563 + if (set_first == 0) {
564 + lowest_addr = entry->start;
565 + set_first = 1;
566 + } else {
567 + if (lowest_addr > entry->start)
568 + lowest_addr = entry->start;
569 + }
570 + }
571 + return lowest_addr;
572 +}
573 +
574 +/*
575 + * Check continuity of segments.
576 + */
577 +static int
578 +dcssblk_is_continuous(struct dcssblk_dev_info *dev_info)
579 +{
580 + int i, j, rc;
581 + struct segment_info *sort_list, *entry, temp;
582 +
583 + if (dev_info->num_of_segments <= 1)
584 + return 0;
585 +
586 + sort_list = kzalloc(
587 + sizeof(struct segment_info) * dev_info->num_of_segments,
588 + GFP_KERNEL);
589 + if (sort_list == NULL)
590 + return -ENOMEM;
591 + i = 0;
592 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
593 + memcpy(&sort_list[i], entry, sizeof(struct segment_info));
594 + i++;
595 + }
596 +
597 + /* sort segments */
598 + for (i = 0; i < dev_info->num_of_segments; i++)
599 + for (j = 0; j < dev_info->num_of_segments; j++)
600 + if (sort_list[j].start > sort_list[i].start) {
601 + memcpy(&temp, &sort_list[i],
602 + sizeof(struct segment_info));
603 + memcpy(&sort_list[i], &sort_list[j],
604 + sizeof(struct segment_info));
605 + memcpy(&sort_list[j], &temp,
606 + sizeof(struct segment_info));
607 + }
608 +
609 + /* check continuity */
610 + for (i = 0; i < dev_info->num_of_segments - 1; i++) {
611 + if ((sort_list[i].end + 1) != sort_list[i+1].start) {
612 + PRINT_ERR("Segment %s is not contiguous with "
613 + "segment %s\n",
614 + sort_list[i].segment_name,
615 + sort_list[i+1].segment_name);
616 + rc = -EINVAL;
617 + goto out;
618 + }
619 + /* EN and EW are allowed in a block device */
620 + if (sort_list[i].segment_type != sort_list[i+1].segment_type) {
621 + if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) ||
622 + (sort_list[i].segment_type == SEG_TYPE_ER) ||
623 + !(sort_list[i+1].segment_type &
624 + SEGMENT_EXCLUSIVE) ||
625 + (sort_list[i+1].segment_type == SEG_TYPE_ER)) {
626 + PRINT_ERR("Segment %s has different type from "
627 + "segment %s\n",
628 + sort_list[i].segment_name,
629 + sort_list[i+1].segment_name);
630 + rc = -EINVAL;
631 + goto out;
632 + }
633 + }
634 + }
635 + rc = 0;
636 +out:
637 + kfree(sort_list);
638 + return rc;
639 +}
640 +
641 +/*
642 + * Load a segment
643 + */
644 +static int
645 +dcssblk_load_segment(char *name, struct segment_info **seg_info)
646 +{
647 + int rc;
648 +
649 + /* already loaded? */
650 + down_read(&dcssblk_devices_sem);
651 + *seg_info = dcssblk_get_segment_by_name(name);
652 + up_read(&dcssblk_devices_sem);
653 + if (*seg_info != NULL)
654 + return -EEXIST;
655 +
656 + /* get a struct segment_info */
657 + *seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL);
658 + if (*seg_info == NULL)
659 + return -ENOMEM;
660 +
661 + strcpy((*seg_info)->segment_name, name);
662 +
663 + /* load the segment */
664 + rc = segment_load(name, SEGMENT_SHARED,
665 + &(*seg_info)->start, &(*seg_info)->end);
666 + if (rc < 0) {
667 + segment_warning(rc, (*seg_info)->segment_name);
668 + kfree(*seg_info);
669 + } else {
670 + INIT_LIST_HEAD(&(*seg_info)->lh);
671 + (*seg_info)->segment_type = rc;
672 + }
673 + return rc;
674 +}
675 +
676 static void dcssblk_unregister_callback(struct device *dev)
677 {
678 device_unregister(dev);
679 @@ -165,6 +348,7 @@ static ssize_t
680 dcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
681 {
682 struct dcssblk_dev_info *dev_info;
683 + struct segment_info *entry, *temp;
684 int rc;
685
686 if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
687 @@ -172,46 +356,46 @@ dcssblk_shared_store(struct device *dev,
688 down_write(&dcssblk_devices_sem);
689 dev_info = container_of(dev, struct dcssblk_dev_info, dev);
690 if (atomic_read(&dev_info->use_count)) {
691 - PRINT_ERR("share: segment %s is busy!\n",
692 - dev_info->segment_name);
693 rc = -EBUSY;
694 goto out;
695 }
696 if (inbuf[0] == '1') {
697 - // reload segment in shared mode
698 - rc = segment_modify_shared(dev_info->segment_name,
699 - SEGMENT_SHARED);
700 - if (rc < 0) {
701 - BUG_ON(rc == -EINVAL);
702 - if (rc != -EAGAIN)
703 - goto removeseg;
704 - } else {
705 - dev_info->is_shared = 1;
706 - switch (dev_info->segment_type) {
707 - case SEG_TYPE_SR:
708 - case SEG_TYPE_ER:
709 - case SEG_TYPE_SC:
710 - set_disk_ro(dev_info->gd,1);
711 + /* reload segments in shared mode */
712 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
713 + rc = segment_modify_shared(entry->segment_name,
714 + SEGMENT_SHARED);
715 + if (rc < 0) {
716 + BUG_ON(rc == -EINVAL);
717 + if (rc != -EAGAIN)
718 + goto removeseg;
719 }
720 }
721 + dev_info->is_shared = 1;
722 + switch (dev_info->segment_type) {
723 + case SEG_TYPE_SR:
724 + case SEG_TYPE_ER:
725 + case SEG_TYPE_SC:
726 + set_disk_ro(dev_info->gd, 1);
727 + }
728 } else if (inbuf[0] == '0') {
729 - // reload segment in exclusive mode
730 + /* reload segments in exclusive mode */
731 if (dev_info->segment_type == SEG_TYPE_SC) {
732 PRINT_ERR("Segment type SC (%s) cannot be loaded in "
733 - "non-shared mode\n", dev_info->segment_name);
734 + "non-shared mode\n", dev_info->segment_name);
735 rc = -EINVAL;
736 goto out;
737 }
738 - rc = segment_modify_shared(dev_info->segment_name,
739 - SEGMENT_EXCLUSIVE);
740 - if (rc < 0) {
741 - BUG_ON(rc == -EINVAL);
742 - if (rc != -EAGAIN)
743 - goto removeseg;
744 - } else {
745 - dev_info->is_shared = 0;
746 - set_disk_ro(dev_info->gd, 0);
747 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
748 + rc = segment_modify_shared(entry->segment_name,
749 + SEGMENT_EXCLUSIVE);
750 + if (rc < 0) {
751 + BUG_ON(rc == -EINVAL);
752 + if (rc != -EAGAIN)
753 + goto removeseg;
754 + }
755 }
756 + dev_info->is_shared = 0;
757 + set_disk_ro(dev_info->gd, 0);
758 } else {
759 rc = -EINVAL;
760 goto out;
761 @@ -220,8 +404,14 @@ dcssblk_shared_store(struct device *dev,
762 goto out;
763
764 removeseg:
765 - PRINT_ERR("Could not reload segment %s, removing it now!\n",
766 - dev_info->segment_name);
767 + PRINT_ERR("Could not reload segment(s) of the device %s, removing "
768 + "segment(s) now!\n",
769 + dev_info->segment_name);
770 + temp = entry;
771 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
772 + if (entry != temp)
773 + segment_unload(entry->segment_name);
774 + }
775 list_del(&dev_info->lh);
776
777 del_gendisk(dev_info->gd);
778 @@ -254,6 +444,7 @@ static ssize_t
779 dcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count)
780 {
781 struct dcssblk_dev_info *dev_info;
782 + struct segment_info *entry;
783
784 if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0'))
785 return -EINVAL;
786 @@ -263,14 +454,16 @@ dcssblk_save_store(struct device *dev, s
787 if (inbuf[0] == '1') {
788 if (atomic_read(&dev_info->use_count) == 0) {
789 // device is idle => we save immediately
790 - PRINT_INFO("Saving segment %s\n",
791 + PRINT_INFO("Saving segment(s) of the device %s\n",
792 dev_info->segment_name);
793 - segment_save(dev_info->segment_name);
794 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
795 + segment_save(entry->segment_name);
796 + }
797 } else {
798 // device is busy => we save it when it becomes
799 // idle in dcssblk_release
800 - PRINT_INFO("Segment %s is currently busy, it will "
801 - "be saved when it becomes idle...\n",
802 + PRINT_INFO("Device %s is currently busy, segment(s) "
803 + "will be saved when it becomes idle...\n",
804 dev_info->segment_name);
805 dev_info->save_pending = 1;
806 }
807 @@ -279,7 +472,8 @@ dcssblk_save_store(struct device *dev, s
808 // device is busy & the user wants to undo his save
809 // request
810 dev_info->save_pending = 0;
811 - PRINT_INFO("Pending save for segment %s deactivated\n",
812 + PRINT_INFO("Pending save for segment(s) of the device "
813 + "%s deactivated\n",
814 dev_info->segment_name);
815 }
816 } else {
817 @@ -291,66 +485,123 @@ dcssblk_save_store(struct device *dev, s
818 }
819
820 /*
821 + * device attribute for showing all segments in a device
822 + */
823 +static ssize_t
824 +dcssblk_seglist_show(struct device *dev, struct device_attribute *attr,
825 + char *buf)
826 +{
827 + int i;
828 +
829 + struct dcssblk_dev_info *dev_info;
830 + struct segment_info *entry;
831 +
832 + down_read(&dcssblk_devices_sem);
833 + dev_info = container_of(dev, struct dcssblk_dev_info, dev);
834 + i = 0;
835 + buf[0] = '\0';
836 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
837 + strcpy(&buf[i], entry->segment_name);
838 + i += strlen(entry->segment_name);
839 + buf[i] = '\n';
840 + i++;
841 + }
842 + up_read(&dcssblk_devices_sem);
843 + return i;
844 +}
845 +
846 +/*
847 * device attribute for adding devices
848 */
849 static ssize_t
850 dcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
851 {
852 - int rc, i;
853 + int rc, i, j, num_of_segments;
854 struct dcssblk_dev_info *dev_info;
855 + struct segment_info *seg_info, *temp;
856 char *local_buf;
857 unsigned long seg_byte_size;
858
859 dev_info = NULL;
860 + seg_info = NULL;
861 if (dev != dcssblk_root_dev) {
862 rc = -EINVAL;
863 goto out_nobuf;
864 }
865 + if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) {
866 + rc = -ENAMETOOLONG;
867 + goto out_nobuf;
868 + }
869 +
870 local_buf = kmalloc(count + 1, GFP_KERNEL);
871 if (local_buf == NULL) {
872 rc = -ENOMEM;
873 goto out_nobuf;
874 }
875 +
876 /*
877 * parse input
878 */
879 + num_of_segments = 0;
880 for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
881 - local_buf[i] = toupper(buf[i]);
882 + for (j = i; (buf[j] != ':') &&
883 + (buf[j] != '\0') &&
884 + (buf[j] != '\n') &&
885 + j < count; j++) {
886 + local_buf[j-i] = toupper(buf[j]);
887 + }
888 + local_buf[j-i] = '\0';
889 + if (((j - i) == 0) || ((j - i) > 8)) {
890 + rc = -ENAMETOOLONG;
891 + goto seg_list_del;
892 + }
893 +
894 + rc = dcssblk_load_segment(local_buf, &seg_info);
895 + if (rc < 0)
896 + goto seg_list_del;
897 + /*
898 + * get a struct dcssblk_dev_info
899 + */
900 + if (num_of_segments == 0) {
901 + dev_info = kzalloc(sizeof(struct dcssblk_dev_info),
902 + GFP_KERNEL);
903 + if (dev_info == NULL) {
904 + rc = -ENOMEM;
905 + goto out;
906 + }
907 + strcpy(dev_info->segment_name, local_buf);
908 + dev_info->segment_type = seg_info->segment_type;
909 + INIT_LIST_HEAD(&dev_info->seg_list);
910 + }
911 + list_add_tail(&seg_info->lh, &dev_info->seg_list);
912 + num_of_segments++;
913 + i = j;
914 +
915 + if ((buf[j] == '\0') || (buf[j] == '\n'))
916 + break;
917 }
918 - local_buf[i] = '\0';
919 - if ((i == 0) || (i > 8)) {
920 +
921 + /* no trailing colon at the end of the input */
922 + if ((i > 0) && (buf[i-1] == ':')) {
923 rc = -ENAMETOOLONG;
924 - goto out;
925 - }
926 - /*
927 - * already loaded?
928 - */
929 - down_read(&dcssblk_devices_sem);
930 - dev_info = dcssblk_get_device_by_name(local_buf);
931 - up_read(&dcssblk_devices_sem);
932 - if (dev_info != NULL) {
933 - PRINT_WARN("Segment %s already loaded!\n", local_buf);
934 - rc = -EEXIST;
935 - goto out;
936 - }
937 - /*
938 - * get a struct dcssblk_dev_info
939 - */
940 - dev_info = kzalloc(sizeof(struct dcssblk_dev_info), GFP_KERNEL);
941 - if (dev_info == NULL) {
942 - rc = -ENOMEM;
943 - goto out;
944 + goto seg_list_del;
945 }
946 + strlcpy(local_buf, buf, i + 1);
947 + dev_info->num_of_segments = num_of_segments;
948 + rc = dcssblk_is_continuous(dev_info);
949 + if (rc < 0)
950 + goto seg_list_del;
951
952 - strcpy(dev_info->segment_name, local_buf);
953 - strlcpy(dev_info->dev.bus_id, local_buf, BUS_ID_SIZE);
954 + dev_info->start = dcssblk_find_lowest_addr(dev_info);
955 + dev_info->end = dcssblk_find_highest_addr(dev_info);
956 +
957 + strlcpy(dev_info->dev.bus_id, dev_info->segment_name, BUS_ID_SIZE);
958 dev_info->dev.release = dcssblk_release_segment;
959 INIT_LIST_HEAD(&dev_info->lh);
960 -
961 dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
962 if (dev_info->gd == NULL) {
963 rc = -ENOMEM;
964 - goto free_dev_info;
965 + goto seg_list_del;
966 }
967 dev_info->gd->major = dcssblk_major;
968 dev_info->gd->fops = &dcssblk_devops;
969 @@ -360,59 +611,43 @@ dcssblk_add_store(struct device *dev, st
970 dev_info->gd->driverfs_dev = &dev_info->dev;
971 blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
972 blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
973 - /*
974 - * load the segment
975 - */
976 - rc = segment_load(local_buf, SEGMENT_SHARED,
977 - &dev_info->start, &dev_info->end);
978 - if (rc < 0) {
979 - segment_warning(rc, dev_info->segment_name);
980 - goto dealloc_gendisk;
981 - }
982 +
983 seg_byte_size = (dev_info->end - dev_info->start + 1);
984 set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
985 - PRINT_INFO("Loaded segment %s, size = %lu Byte, "
986 + PRINT_INFO("Loaded segment(s) %s, size = %lu Byte, "
987 "capacity = %lu (512 Byte) sectors\n", local_buf,
988 seg_byte_size, seg_byte_size >> 9);
989
990 - dev_info->segment_type = rc;
991 dev_info->save_pending = 0;
992 dev_info->is_shared = 1;
993 dev_info->dev.parent = dcssblk_root_dev;
994
995 /*
996 - * get minor, add to list
997 + *get minor, add to list
998 */
999 down_write(&dcssblk_devices_sem);
1000 - if (dcssblk_get_device_by_name(local_buf)) {
1001 - up_write(&dcssblk_devices_sem);
1002 + if (dcssblk_get_segment_by_name(local_buf)) {
1003 rc = -EEXIST;
1004 - goto unload_seg;
1005 + goto release_gd;
1006 }
1007 rc = dcssblk_assign_free_minor(dev_info);
1008 - if (rc) {
1009 - up_write(&dcssblk_devices_sem);
1010 - PRINT_ERR("No free minor number available! "
1011 - "Unloading segment...\n");
1012 - goto unload_seg;
1013 - }
1014 + if (rc)
1015 + goto release_gd;
1016 sprintf(dev_info->gd->disk_name, "dcssblk%d",
1017 dev_info->gd->first_minor);
1018 list_add_tail(&dev_info->lh, &dcssblk_devices);
1019
1020 if (!try_module_get(THIS_MODULE)) {
1021 rc = -ENODEV;
1022 - goto list_del;
1023 + goto dev_list_del;
1024 }
1025 /*
1026 * register the device
1027 */
1028 rc = device_register(&dev_info->dev);
1029 if (rc) {
1030 - PRINT_ERR("Segment %s could not be registered RC=%d\n",
1031 - local_buf, rc);
1032 module_put(THIS_MODULE);
1033 - goto list_del;
1034 + goto dev_list_del;
1035 }
1036 get_device(&dev_info->dev);
1037 rc = device_create_file(&dev_info->dev, &dev_attr_shared);
1038 @@ -421,6 +656,9 @@ dcssblk_add_store(struct device *dev, st
1039 rc = device_create_file(&dev_info->dev, &dev_attr_save);
1040 if (rc)
1041 goto unregister_dev;
1042 + rc = device_create_file(&dev_info->dev, &dev_attr_seglist);
1043 + if (rc)
1044 + goto unregister_dev;
1045
1046 add_disk(dev_info->gd);
1047
1048 @@ -434,7 +672,6 @@ dcssblk_add_store(struct device *dev, st
1049 set_disk_ro(dev_info->gd,0);
1050 break;
1051 }
1052 - PRINT_DEBUG("Segment %s loaded successfully\n", local_buf);
1053 up_write(&dcssblk_devices_sem);
1054 rc = count;
1055 goto out;
1056 @@ -445,20 +682,27 @@ unregister_dev:
1057 dev_info->gd->queue = NULL;
1058 put_disk(dev_info->gd);
1059 device_unregister(&dev_info->dev);
1060 - segment_unload(dev_info->segment_name);
1061 + list_for_each_entry(seg_info, &dev_info->seg_list, lh) {
1062 + segment_unload(seg_info->segment_name);
1063 + }
1064 put_device(&dev_info->dev);
1065 up_write(&dcssblk_devices_sem);
1066 goto out;
1067 -list_del:
1068 +dev_list_del:
1069 list_del(&dev_info->lh);
1070 - up_write(&dcssblk_devices_sem);
1071 -unload_seg:
1072 - segment_unload(local_buf);
1073 -dealloc_gendisk:
1074 +release_gd:
1075 blk_cleanup_queue(dev_info->dcssblk_queue);
1076 dev_info->gd->queue = NULL;
1077 put_disk(dev_info->gd);
1078 -free_dev_info:
1079 + up_write(&dcssblk_devices_sem);
1080 +seg_list_del:
1081 + if (dev_info == NULL)
1082 + goto out;
1083 + list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) {
1084 + list_del(&seg_info->lh);
1085 + segment_unload(seg_info->segment_name);
1086 + kfree(seg_info);
1087 + }
1088 kfree(dev_info);
1089 out:
1090 kfree(local_buf);
1091 @@ -473,6 +717,7 @@ static ssize_t
1092 dcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
1093 {
1094 struct dcssblk_dev_info *dev_info;
1095 + struct segment_info *entry;
1096 int rc, i;
1097 char *local_buf;
1098
1099 @@ -499,26 +744,28 @@ dcssblk_remove_store(struct device *dev,
1100 dev_info = dcssblk_get_device_by_name(local_buf);
1101 if (dev_info == NULL) {
1102 up_write(&dcssblk_devices_sem);
1103 - PRINT_WARN("Segment %s is not loaded!\n", local_buf);
1104 + PRINT_WARN("Device %s is not loaded!\n", local_buf);
1105 rc = -ENODEV;
1106 goto out_buf;
1107 }
1108 if (atomic_read(&dev_info->use_count) != 0) {
1109 up_write(&dcssblk_devices_sem);
1110 - PRINT_WARN("Segment %s is in use!\n", local_buf);
1111 + PRINT_WARN("Device %s is in use!\n", local_buf);
1112 rc = -EBUSY;
1113 goto out_buf;
1114 }
1115 - list_del(&dev_info->lh);
1116
1117 + list_del(&dev_info->lh);
1118 del_gendisk(dev_info->gd);
1119 blk_cleanup_queue(dev_info->dcssblk_queue);
1120 dev_info->gd->queue = NULL;
1121 put_disk(dev_info->gd);
1122 device_unregister(&dev_info->dev);
1123 - segment_unload(dev_info->segment_name);
1124 - PRINT_DEBUG("Segment %s unloaded successfully\n",
1125 - dev_info->segment_name);
1126 +
1127 + /* unload all related segments */
1128 + list_for_each_entry(entry, &dev_info->seg_list, lh)
1129 + segment_unload(entry->segment_name);
1130 +
1131 put_device(&dev_info->dev);
1132 up_write(&dcssblk_devices_sem);
1133
1134 @@ -550,6 +797,7 @@ static int
1135 dcssblk_release(struct inode *inode, struct file *filp)
1136 {
1137 struct dcssblk_dev_info *dev_info;
1138 + struct segment_info *entry;
1139 int rc;
1140
1141 dev_info = inode->i_bdev->bd_disk->private_data;
1142 @@ -560,9 +808,11 @@ dcssblk_release(struct inode *inode, str
1143 down_write(&dcssblk_devices_sem);
1144 if (atomic_dec_and_test(&dev_info->use_count)
1145 && (dev_info->save_pending)) {
1146 - PRINT_INFO("Segment %s became idle and is being saved now\n",
1147 + PRINT_INFO("Device %s became idle and is being saved now\n",
1148 dev_info->segment_name);
1149 - segment_save(dev_info->segment_name);
1150 + list_for_each_entry(entry, &dev_info->seg_list, lh) {
1151 + segment_save(entry->segment_name);
1152 + }
1153 dev_info->save_pending = 0;
1154 }
1155 up_write(&dcssblk_devices_sem);
1156 @@ -602,7 +852,8 @@ dcssblk_make_request(struct request_queu
1157 case SEG_TYPE_SC:
1158 /* cannot write to these segments */
1159 if (bio_data_dir(bio) == WRITE) {
1160 - PRINT_WARN("rejecting write to ro segment %s\n", dev_info->dev.bus_id);
1161 + PRINT_WARN("rejecting write to ro device %s\n",
1162 + dev_info->dev.bus_id);
1163 goto fail;
1164 }
1165 }
1166 @@ -657,7 +908,7 @@ static void
1167 dcssblk_check_params(void)
1168 {
1169 int rc, i, j, k;
1170 - char buf[9];
1171 + char buf[DCSSBLK_PARM_LEN + 1];
1172 struct dcssblk_dev_info *dev_info;
1173
1174 for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
1175 @@ -665,15 +916,16 @@ dcssblk_check_params(void)
1176 for (j = i; (dcssblk_segments[j] != ',') &&
1177 (dcssblk_segments[j] != '\0') &&
1178 (dcssblk_segments[j] != '(') &&
1179 - (j - i) < 8; j++)
1180 + (j < DCSSBLK_PARM_LEN); j++)
1181 {
1182 buf[j-i] = dcssblk_segments[j];
1183 }
1184 buf[j-i] = '\0';
1185 rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i);
1186 if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
1187 - for (k = 0; buf[k] != '\0'; k++)
1188 + for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++)
1189 buf[k] = toupper(buf[k]);
1190 + buf[k] = '\0';
1191 if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
1192 down_read(&dcssblk_devices_sem);
1193 dev_info = dcssblk_get_device_by_name(buf);
1194 @@ -740,10 +992,12 @@ module_exit(dcssblk_exit);
1195
1196 module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
1197 MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
1198 - "comma-separated list, each name max. 8 chars.\n"
1199 - "Adding \"(local)\" to segment name equals echoing 0 to "
1200 - "/sys/devices/dcssblk/<segment name>/shared after loading "
1201 - "the segment - \n"
1202 - "e.g. segments=\"mydcss1,mydcss2,mydcss3(local)\"");
1203 + "comma-separated list, names in each set separated "
1204 + "by commas are separated by colons, each set contains "
1205 + "names of contiguous segments and each name max. 8 chars.\n"
1206 + "Adding \"(local)\" to the end of each set equals echoing 0 "
1207 + "to /sys/devices/dcssblk/<device name>/shared after loading "
1208 + "the contiguous segments - \n"
1209 + "e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\"");
1210
1211 MODULE_LICENSE("GPL");