| .. | .. |
|---|
| 16 | 16 | #include <linux/list.h> |
|---|
| 17 | 17 | #include <linux/slab.h> |
|---|
| 18 | 18 | #include <linux/export.h> |
|---|
| 19 | | -#include <linux/bootmem.h> |
|---|
| 19 | +#include <linux/memblock.h> |
|---|
| 20 | 20 | #include <linux/ctype.h> |
|---|
| 21 | 21 | #include <linux/ioport.h> |
|---|
| 22 | +#include <linux/refcount.h> |
|---|
| 23 | +#include <linux/pgtable.h> |
|---|
| 22 | 24 | #include <asm/diag.h> |
|---|
| 23 | 25 | #include <asm/page.h> |
|---|
| 24 | | -#include <asm/pgtable.h> |
|---|
| 25 | 26 | #include <asm/ebcdic.h> |
|---|
| 26 | 27 | #include <asm/errno.h> |
|---|
| 27 | 28 | #include <asm/extmem.h> |
|---|
| 28 | 29 | #include <asm/cpcmd.h> |
|---|
| 29 | 30 | #include <asm/setup.h> |
|---|
| 30 | 31 | |
|---|
| 31 | | -#define DCSS_LOADSHR 0x00 |
|---|
| 32 | | -#define DCSS_LOADNSR 0x04 |
|---|
| 33 | 32 | #define DCSS_PURGESEG 0x08 |
|---|
| 34 | | -#define DCSS_FINDSEG 0x0c |
|---|
| 35 | | -#define DCSS_LOADNOLY 0x10 |
|---|
| 36 | | -#define DCSS_SEGEXT 0x18 |
|---|
| 37 | 33 | #define DCSS_LOADSHRX 0x20 |
|---|
| 38 | 34 | #define DCSS_LOADNSRX 0x24 |
|---|
| 39 | 35 | #define DCSS_FINDSEGX 0x2c |
|---|
| .. | .. |
|---|
| 53 | 49 | struct qrange range[6]; |
|---|
| 54 | 50 | }; |
|---|
| 55 | 51 | |
|---|
| 56 | | -struct qrange_old { |
|---|
| 57 | | - unsigned int start; /* last byte type */ |
|---|
| 58 | | - unsigned int end; /* last byte reserved */ |
|---|
| 59 | | -}; |
|---|
| 60 | | - |
|---|
| 61 | | -/* output area format for the Diag x'64' old subcode x'18' */ |
|---|
| 62 | | -struct qout64_old { |
|---|
| 63 | | - int segstart; |
|---|
| 64 | | - int segend; |
|---|
| 65 | | - int segcnt; |
|---|
| 66 | | - int segrcnt; |
|---|
| 67 | | - struct qrange_old range[6]; |
|---|
| 68 | | -}; |
|---|
| 69 | | - |
|---|
| 70 | 52 | struct qin64 { |
|---|
| 71 | 53 | char qopcode; |
|---|
| 72 | 54 | char rsrv1[3]; |
|---|
| .. | .. |
|---|
| 83 | 65 | char res_name[16]; |
|---|
| 84 | 66 | unsigned long start_addr; |
|---|
| 85 | 67 | unsigned long end; |
|---|
| 86 | | - atomic_t ref_count; |
|---|
| 68 | + refcount_t ref_count; |
|---|
| 87 | 69 | int do_nonshared; |
|---|
| 88 | 70 | unsigned int vm_segtype; |
|---|
| 89 | 71 | struct qrange range[6]; |
|---|
| .. | .. |
|---|
| 95 | 77 | static LIST_HEAD(dcss_list); |
|---|
| 96 | 78 | static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC", |
|---|
| 97 | 79 | "EW/EN-MIXED" }; |
|---|
| 98 | | -static int loadshr_scode, loadnsr_scode; |
|---|
| 99 | | -static int segext_scode, purgeseg_scode; |
|---|
| 100 | | -static int scode_set; |
|---|
| 101 | | - |
|---|
| 102 | | -/* set correct Diag x'64' subcodes. */ |
|---|
| 103 | | -static int |
|---|
| 104 | | -dcss_set_subcodes(void) |
|---|
| 105 | | -{ |
|---|
| 106 | | - char *name = kmalloc(8, GFP_KERNEL | GFP_DMA); |
|---|
| 107 | | - unsigned long rx, ry; |
|---|
| 108 | | - int rc; |
|---|
| 109 | | - |
|---|
| 110 | | - if (name == NULL) |
|---|
| 111 | | - return -ENOMEM; |
|---|
| 112 | | - |
|---|
| 113 | | - rx = (unsigned long) name; |
|---|
| 114 | | - ry = DCSS_FINDSEGX; |
|---|
| 115 | | - |
|---|
| 116 | | - strcpy(name, "dummy"); |
|---|
| 117 | | - diag_stat_inc(DIAG_STAT_X064); |
|---|
| 118 | | - asm volatile( |
|---|
| 119 | | - " diag %0,%1,0x64\n" |
|---|
| 120 | | - "0: ipm %2\n" |
|---|
| 121 | | - " srl %2,28\n" |
|---|
| 122 | | - " j 2f\n" |
|---|
| 123 | | - "1: la %2,3\n" |
|---|
| 124 | | - "2:\n" |
|---|
| 125 | | - EX_TABLE(0b, 1b) |
|---|
| 126 | | - : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc", "memory"); |
|---|
| 127 | | - |
|---|
| 128 | | - kfree(name); |
|---|
| 129 | | - /* Diag x'64' new subcodes are supported, set to new subcodes */ |
|---|
| 130 | | - if (rc != 3) { |
|---|
| 131 | | - loadshr_scode = DCSS_LOADSHRX; |
|---|
| 132 | | - loadnsr_scode = DCSS_LOADNSRX; |
|---|
| 133 | | - purgeseg_scode = DCSS_PURGESEG; |
|---|
| 134 | | - segext_scode = DCSS_SEGEXTX; |
|---|
| 135 | | - return 0; |
|---|
| 136 | | - } |
|---|
| 137 | | - /* Diag x'64' new subcodes are not supported, set to old subcodes */ |
|---|
| 138 | | - loadshr_scode = DCSS_LOADNOLY; |
|---|
| 139 | | - loadnsr_scode = DCSS_LOADNSR; |
|---|
| 140 | | - purgeseg_scode = DCSS_PURGESEG; |
|---|
| 141 | | - segext_scode = DCSS_SEGEXT; |
|---|
| 142 | | - return 0; |
|---|
| 143 | | -} |
|---|
| 80 | +static int loadshr_scode = DCSS_LOADSHRX; |
|---|
| 81 | +static int loadnsr_scode = DCSS_LOADNSRX; |
|---|
| 82 | +static int purgeseg_scode = DCSS_PURGESEG; |
|---|
| 83 | +static int segext_scode = DCSS_SEGEXTX; |
|---|
| 144 | 84 | |
|---|
| 145 | 85 | /* |
|---|
| 146 | 86 | * Create the 8 bytes, ebcdic VM segment name from |
|---|
| .. | .. |
|---|
| 196 | 136 | unsigned long rx, ry; |
|---|
| 197 | 137 | int rc; |
|---|
| 198 | 138 | |
|---|
| 199 | | - if (scode_set == 0) { |
|---|
| 200 | | - rc = dcss_set_subcodes(); |
|---|
| 201 | | - if (rc < 0) |
|---|
| 202 | | - return rc; |
|---|
| 203 | | - scode_set = 1; |
|---|
| 204 | | - } |
|---|
| 205 | 139 | rx = (unsigned long) parameter; |
|---|
| 206 | 140 | ry = (unsigned long) *func; |
|---|
| 207 | 141 | |
|---|
| 208 | | - /* 64-bit Diag x'64' new subcode, keep in 64-bit addressing mode */ |
|---|
| 209 | 142 | diag_stat_inc(DIAG_STAT_X064); |
|---|
| 210 | | - if (*func > DCSS_SEGEXT) |
|---|
| 211 | | - asm volatile( |
|---|
| 212 | | - " diag %0,%1,0x64\n" |
|---|
| 213 | | - " ipm %2\n" |
|---|
| 214 | | - " srl %2,28\n" |
|---|
| 215 | | - : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc"); |
|---|
| 216 | | - /* 31-bit Diag x'64' old subcode, switch to 31-bit addressing mode */ |
|---|
| 217 | | - else |
|---|
| 218 | | - asm volatile( |
|---|
| 219 | | - " sam31\n" |
|---|
| 220 | | - " diag %0,%1,0x64\n" |
|---|
| 221 | | - " sam64\n" |
|---|
| 222 | | - " ipm %2\n" |
|---|
| 223 | | - " srl %2,28\n" |
|---|
| 224 | | - : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc"); |
|---|
| 143 | + asm volatile( |
|---|
| 144 | + " diag %0,%1,0x64\n" |
|---|
| 145 | + " ipm %2\n" |
|---|
| 146 | + " srl %2,28\n" |
|---|
| 147 | + : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc"); |
|---|
| 225 | 148 | *ret1 = rx; |
|---|
| 226 | 149 | *ret2 = ry; |
|---|
| 227 | 150 | return rc; |
|---|
| .. | .. |
|---|
| 271 | 194 | goto out_free; |
|---|
| 272 | 195 | } |
|---|
| 273 | 196 | |
|---|
| 274 | | - /* Only old format of output area of Diagnose x'64' is supported, |
|---|
| 275 | | - copy data for the new format. */ |
|---|
| 276 | | - if (segext_scode == DCSS_SEGEXT) { |
|---|
| 277 | | - struct qout64_old *qout_old; |
|---|
| 278 | | - qout_old = kzalloc(sizeof(*qout_old), GFP_KERNEL | GFP_DMA); |
|---|
| 279 | | - if (qout_old == NULL) { |
|---|
| 280 | | - rc = -ENOMEM; |
|---|
| 281 | | - goto out_free; |
|---|
| 282 | | - } |
|---|
| 283 | | - memcpy(qout_old, qout, sizeof(struct qout64_old)); |
|---|
| 284 | | - qout->segstart = (unsigned long) qout_old->segstart; |
|---|
| 285 | | - qout->segend = (unsigned long) qout_old->segend; |
|---|
| 286 | | - qout->segcnt = qout_old->segcnt; |
|---|
| 287 | | - qout->segrcnt = qout_old->segrcnt; |
|---|
| 288 | | - |
|---|
| 289 | | - if (qout->segcnt > 6) |
|---|
| 290 | | - qout->segrcnt = 6; |
|---|
| 291 | | - for (i = 0; i < qout->segrcnt; i++) { |
|---|
| 292 | | - qout->range[i].start = |
|---|
| 293 | | - (unsigned long) qout_old->range[i].start; |
|---|
| 294 | | - qout->range[i].end = |
|---|
| 295 | | - (unsigned long) qout_old->range[i].end; |
|---|
| 296 | | - } |
|---|
| 297 | | - kfree(qout_old); |
|---|
| 298 | | - } |
|---|
| 299 | 197 | if (qout->segcnt > 6) { |
|---|
| 300 | 198 | rc = -EOPNOTSUPP; |
|---|
| 301 | 199 | goto out_free; |
|---|
| .. | .. |
|---|
| 391 | 289 | |
|---|
| 392 | 290 | /* |
|---|
| 393 | 291 | * real segment loading function, called from segment_load |
|---|
| 292 | + * Must return either an error code < 0, or the segment type code >= 0 |
|---|
| 394 | 293 | */ |
|---|
| 395 | 294 | static int |
|---|
| 396 | 295 | __segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end) |
|---|
| 397 | 296 | { |
|---|
| 398 | 297 | unsigned long start_addr, end_addr, dummy; |
|---|
| 399 | 298 | struct dcss_segment *seg; |
|---|
| 400 | | - int rc, diag_cc; |
|---|
| 299 | + int rc, diag_cc, segtype; |
|---|
| 401 | 300 | |
|---|
| 402 | 301 | start_addr = end_addr = 0; |
|---|
| 302 | + segtype = -1; |
|---|
| 403 | 303 | seg = kmalloc(sizeof(*seg), GFP_KERNEL | GFP_DMA); |
|---|
| 404 | 304 | if (seg == NULL) { |
|---|
| 405 | 305 | rc = -ENOMEM; |
|---|
| .. | .. |
|---|
| 410 | 310 | if (rc < 0) |
|---|
| 411 | 311 | goto out_free; |
|---|
| 412 | 312 | |
|---|
| 413 | | - if (loadshr_scode == DCSS_LOADSHRX) { |
|---|
| 414 | | - if (segment_overlaps_others(seg)) { |
|---|
| 415 | | - rc = -EBUSY; |
|---|
| 416 | | - goto out_free; |
|---|
| 417 | | - } |
|---|
| 418 | | - } |
|---|
| 419 | | - |
|---|
| 420 | | - rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1); |
|---|
| 421 | | - |
|---|
| 422 | | - if (rc) |
|---|
| 313 | + if (segment_overlaps_others(seg)) { |
|---|
| 314 | + rc = -EBUSY; |
|---|
| 423 | 315 | goto out_free; |
|---|
| 316 | + } |
|---|
| 424 | 317 | |
|---|
| 425 | 318 | seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL); |
|---|
| 426 | 319 | if (seg->res == NULL) { |
|---|
| 427 | 320 | rc = -ENOMEM; |
|---|
| 428 | | - goto out_shared; |
|---|
| 321 | + goto out_free; |
|---|
| 429 | 322 | } |
|---|
| 430 | 323 | seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM; |
|---|
| 431 | 324 | seg->res->start = seg->start_addr; |
|---|
| .. | .. |
|---|
| 435 | 328 | seg->res_name[8] = '\0'; |
|---|
| 436 | 329 | strlcat(seg->res_name, " (DCSS)", sizeof(seg->res_name)); |
|---|
| 437 | 330 | seg->res->name = seg->res_name; |
|---|
| 438 | | - rc = seg->vm_segtype; |
|---|
| 439 | | - if (rc == SEG_TYPE_SC || |
|---|
| 440 | | - ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared)) |
|---|
| 331 | + segtype = seg->vm_segtype; |
|---|
| 332 | + if (segtype == SEG_TYPE_SC || |
|---|
| 333 | + ((segtype == SEG_TYPE_SR || segtype == SEG_TYPE_ER) && !do_nonshared)) |
|---|
| 441 | 334 | seg->res->flags |= IORESOURCE_READONLY; |
|---|
| 335 | + |
|---|
| 336 | + /* Check for overlapping resources before adding the mapping. */ |
|---|
| 442 | 337 | if (request_resource(&iomem_resource, seg->res)) { |
|---|
| 443 | 338 | rc = -EBUSY; |
|---|
| 444 | | - kfree(seg->res); |
|---|
| 445 | | - goto out_shared; |
|---|
| 339 | + goto out_free_resource; |
|---|
| 446 | 340 | } |
|---|
| 341 | + |
|---|
| 342 | + rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1); |
|---|
| 343 | + if (rc) |
|---|
| 344 | + goto out_resource; |
|---|
| 447 | 345 | |
|---|
| 448 | 346 | if (do_nonshared) |
|---|
| 449 | 347 | diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name, |
|---|
| .. | .. |
|---|
| 455 | 353 | dcss_diag(&purgeseg_scode, seg->dcss_name, |
|---|
| 456 | 354 | &dummy, &dummy); |
|---|
| 457 | 355 | rc = diag_cc; |
|---|
| 458 | | - goto out_resource; |
|---|
| 356 | + goto out_mapping; |
|---|
| 459 | 357 | } |
|---|
| 460 | 358 | if (diag_cc > 1) { |
|---|
| 461 | 359 | pr_warn("Loading DCSS %s failed with rc=%ld\n", name, end_addr); |
|---|
| 462 | 360 | rc = dcss_diag_translate_rc(end_addr); |
|---|
| 463 | 361 | dcss_diag(&purgeseg_scode, seg->dcss_name, |
|---|
| 464 | 362 | &dummy, &dummy); |
|---|
| 465 | | - goto out_resource; |
|---|
| 363 | + goto out_mapping; |
|---|
| 466 | 364 | } |
|---|
| 467 | 365 | seg->start_addr = start_addr; |
|---|
| 468 | 366 | seg->end = end_addr; |
|---|
| 469 | 367 | seg->do_nonshared = do_nonshared; |
|---|
| 470 | | - atomic_set(&seg->ref_count, 1); |
|---|
| 368 | + refcount_set(&seg->ref_count, 1); |
|---|
| 471 | 369 | list_add(&seg->list, &dcss_list); |
|---|
| 472 | 370 | *addr = seg->start_addr; |
|---|
| 473 | 371 | *end = seg->end; |
|---|
| 474 | 372 | if (do_nonshared) |
|---|
| 475 | | - pr_info("DCSS %s of range %p to %p and type %s loaded as " |
|---|
| 373 | + pr_info("DCSS %s of range %px to %px and type %s loaded as " |
|---|
| 476 | 374 | "exclusive-writable\n", name, (void*) seg->start_addr, |
|---|
| 477 | 375 | (void*) seg->end, segtype_string[seg->vm_segtype]); |
|---|
| 478 | 376 | else { |
|---|
| 479 | | - pr_info("DCSS %s of range %p to %p and type %s loaded in " |
|---|
| 377 | + pr_info("DCSS %s of range %px to %px and type %s loaded in " |
|---|
| 480 | 378 | "shared access mode\n", name, (void*) seg->start_addr, |
|---|
| 481 | 379 | (void*) seg->end, segtype_string[seg->vm_segtype]); |
|---|
| 482 | 380 | } |
|---|
| 483 | 381 | goto out; |
|---|
| 382 | + out_mapping: |
|---|
| 383 | + vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1); |
|---|
| 484 | 384 | out_resource: |
|---|
| 485 | 385 | release_resource(seg->res); |
|---|
| 386 | + out_free_resource: |
|---|
| 486 | 387 | kfree(seg->res); |
|---|
| 487 | | - out_shared: |
|---|
| 488 | | - vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1); |
|---|
| 489 | 388 | out_free: |
|---|
| 490 | 389 | kfree(seg); |
|---|
| 491 | 390 | out: |
|---|
| 492 | | - return rc; |
|---|
| 391 | + return rc < 0 ? rc : segtype; |
|---|
| 493 | 392 | } |
|---|
| 494 | 393 | |
|---|
| 495 | 394 | /* |
|---|
| .. | .. |
|---|
| 504 | 403 | * -EIO : could not perform query or load diagnose |
|---|
| 505 | 404 | * -ENOENT : no such segment |
|---|
| 506 | 405 | * -EOPNOTSUPP: multi-part segment cannot be used with linux |
|---|
| 507 | | - * -ENOSPC : segment cannot be used (overlaps with storage) |
|---|
| 508 | | - * -EBUSY : segment can temporarily not be used (overlaps with dcss) |
|---|
| 406 | + * -EBUSY : segment cannot be used (overlaps with dcss or storage) |
|---|
| 509 | 407 | * -ERANGE : segment cannot be used (exceeds kernel mapping range) |
|---|
| 510 | 408 | * -EPERM : segment is currently loaded with incompatible permissions |
|---|
| 511 | 409 | * -ENOMEM : out of memory |
|---|
| .. | .. |
|---|
| 527 | 425 | rc = __segment_load (name, do_nonshared, addr, end); |
|---|
| 528 | 426 | else { |
|---|
| 529 | 427 | if (do_nonshared == seg->do_nonshared) { |
|---|
| 530 | | - atomic_inc(&seg->ref_count); |
|---|
| 428 | + refcount_inc(&seg->ref_count); |
|---|
| 531 | 429 | *addr = seg->start_addr; |
|---|
| 532 | 430 | *end = seg->end; |
|---|
| 533 | 431 | rc = seg->vm_segtype; |
|---|
| .. | .. |
|---|
| 573 | 471 | rc = 0; |
|---|
| 574 | 472 | goto out_unlock; |
|---|
| 575 | 473 | } |
|---|
| 576 | | - if (atomic_read (&seg->ref_count) != 1) { |
|---|
| 474 | + if (refcount_read(&seg->ref_count) != 1) { |
|---|
| 577 | 475 | pr_warn("DCSS %s is in use and cannot be reloaded\n", name); |
|---|
| 578 | 476 | rc = -EAGAIN; |
|---|
| 579 | 477 | goto out_unlock; |
|---|
| .. | .. |
|---|
| 649 | 547 | pr_err("Unloading unknown DCSS %s failed\n", name); |
|---|
| 650 | 548 | goto out_unlock; |
|---|
| 651 | 549 | } |
|---|
| 652 | | - if (atomic_dec_return(&seg->ref_count) != 0) |
|---|
| 550 | + if (!refcount_dec_and_test(&seg->ref_count)) |
|---|
| 653 | 551 | goto out_unlock; |
|---|
| 654 | 552 | release_resource(seg->res); |
|---|
| 655 | 553 | kfree(seg->res); |
|---|
| .. | .. |
|---|
| 729 | 627 | case -EOPNOTSUPP: |
|---|
| 730 | 628 | pr_err("DCSS %s has multiple page ranges and cannot be " |
|---|
| 731 | 629 | "loaded or queried\n", seg_name); |
|---|
| 732 | | - break; |
|---|
| 733 | | - case -ENOSPC: |
|---|
| 734 | | - pr_err("DCSS %s overlaps with used storage and cannot " |
|---|
| 735 | | - "be loaded\n", seg_name); |
|---|
| 736 | 630 | break; |
|---|
| 737 | 631 | case -EBUSY: |
|---|
| 738 | 632 | pr_err("%s needs used memory resources and cannot be " |
|---|