linux/arch/s390/mm/extmem.c
<<
>>
Prefs
   1/*
   2 * File...........: arch/s390/mm/extmem.c
   3 * Author(s)......: Carsten Otte <cotte@de.ibm.com>
   4 *                  Rob M van der Heij <rvdheij@nl.ibm.com>
   5 *                  Steven Shultz <shultzss@us.ibm.com>
   6 * Bugreports.to..: <Linux390@de.ibm.com>
   7 * (C) IBM Corporation 2002-2004
   8 */
   9
  10#include <linux/kernel.h>
  11#include <linux/string.h>
  12#include <linux/spinlock.h>
  13#include <linux/list.h>
  14#include <linux/slab.h>
  15#include <linux/module.h>
  16#include <linux/bootmem.h>
  17#include <asm/page.h>
  18#include <asm/ebcdic.h>
  19#include <asm/errno.h>
  20#include <asm/extmem.h>
  21#include <asm/cpcmd.h>
  22#include <linux/ctype.h>
  23
  24#define DCSS_DEBUG      /* Debug messages on/off */
  25
  26#define DCSS_NAME "extmem"
  27#ifdef DCSS_DEBUG
  28#define PRINT_DEBUG(x...)       printk(KERN_DEBUG DCSS_NAME " debug:" x)
  29#else
  30#define PRINT_DEBUG(x...)   do {} while (0)
  31#endif
  32#define PRINT_INFO(x...)        printk(KERN_INFO DCSS_NAME " info:" x)
  33#define PRINT_WARN(x...)        printk(KERN_WARNING DCSS_NAME " warning:" x)
  34#define PRINT_ERR(x...)         printk(KERN_ERR DCSS_NAME " error:" x)
  35
  36
  37#define DCSS_LOADSHR    0x00
  38#define DCSS_LOADNSR    0x04
  39#define DCSS_PURGESEG   0x08
  40#define DCSS_FINDSEG    0x0c
  41#define DCSS_LOADNOLY   0x10
  42#define DCSS_SEGEXT     0x18
  43#define DCSS_FINDSEGA   0x0c
  44
  45struct qrange {
  46        unsigned int  start; // 3byte start address, 1 byte type
  47        unsigned int  end;   // 3byte end address, 1 byte reserved
  48};
  49
  50struct qout64 {
  51        int segstart;
  52        int segend;
  53        int segcnt;
  54        int segrcnt;
  55        struct qrange range[6];
  56};
  57
  58struct qin64 {
  59        char qopcode;
  60        char rsrv1[3];
  61        char qrcode;
  62        char rsrv2[3];
  63        char qname[8];
  64        unsigned int qoutptr;
  65        short int qoutlen;
  66};
  67
  68struct dcss_segment {
  69        struct list_head list;
  70        char dcss_name[8];
  71        unsigned long start_addr;
  72        unsigned long end;
  73        atomic_t ref_count;
  74        int do_nonshared;
  75        unsigned int vm_segtype;
  76        struct qrange range[6];
  77        int segcnt;
  78};
  79
  80static DEFINE_SPINLOCK(dcss_lock);
  81static struct list_head dcss_list = LIST_HEAD_INIT(dcss_list);
  82static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
  83                                        "EW/EN-MIXED" };
  84
  85extern struct {
  86        unsigned long addr, size, type;
  87} memory_chunk[MEMORY_CHUNKS];
  88
  89/*
  90 * Create the 8 bytes, ebcdic VM segment name from
  91 * an ascii name.
  92 */
  93static void inline
  94dcss_mkname(char *name, char *dcss_name)
  95{
  96        int i;
  97
  98        for (i = 0; i < 8; i++) {
  99                if (name[i] == '\0')
 100                        break;
 101                dcss_name[i] = toupper(name[i]);
 102        };
 103        for (; i < 8; i++)
 104                dcss_name[i] = ' ';
 105        ASCEBC(dcss_name, 8);
 106}
 107
 108
 109/*
 110 * search all segments in dcss_list, and return the one
 111 * namend *name. If not found, return NULL.
 112 */
 113static struct dcss_segment *
 114segment_by_name (char *name)
 115{
 116        char dcss_name[9];
 117        struct list_head *l;
 118        struct dcss_segment *tmp, *retval = NULL;
 119
 120        assert_spin_locked(&dcss_lock);
 121        dcss_mkname (name, dcss_name);
 122        list_for_each (l, &dcss_list) {
 123                tmp = list_entry (l, struct dcss_segment, list);
 124                if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) {
 125                        retval = tmp;
 126                        break;
 127                }
 128        }
 129        return retval;
 130}
 131
 132
 133/*
 134 * Perform a function on a dcss segment.
 135 */
 136static inline int
 137dcss_diag (__u8 func, void *parameter,
 138           unsigned long *ret1, unsigned long *ret2)
 139{
 140        unsigned long rx, ry;
 141        int rc;
 142
 143        rx = (unsigned long) parameter;
 144        ry = (unsigned long) func;
 145        __asm__ __volatile__(
 146#ifdef CONFIG_ARCH_S390X
 147                "   sam31\n" // switch to 31 bit
 148                "   diag    %0,%1,0x64\n"
 149                "   sam64\n" // switch back to 64 bit
 150#else
 151                "   diag    %0,%1,0x64\n"
 152#endif
 153                "   ipm     %2\n"
 154                "   srl     %2,28\n"
 155                : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc" );
 156        *ret1 = rx;
 157        *ret2 = ry;
 158        return rc;
 159}
 160
 161static inline int
 162dcss_diag_translate_rc (int vm_rc) {
 163        if (vm_rc == 44)
 164                return -ENOENT;
 165        return -EIO;
 166}
 167
 168
 169/* do a diag to get info about a segment.
 170 * fills start_address, end and vm_segtype fields
 171 */
 172static int
 173query_segment_type (struct dcss_segment *seg)
 174{
 175        struct qin64  *qin = kmalloc (sizeof(struct qin64), GFP_DMA);
 176        struct qout64 *qout = kmalloc (sizeof(struct qout64), GFP_DMA);
 177
 178        int diag_cc, rc, i;
 179        unsigned long dummy, vmrc;
 180
 181        if ((qin == NULL) || (qout == NULL)) {
 182                rc = -ENOMEM;
 183                goto out_free;
 184        }
 185
 186        /* initialize diag input parameters */
 187        qin->qopcode = DCSS_FINDSEGA;
 188        qin->qoutptr = (unsigned long) qout;
 189        qin->qoutlen = sizeof(struct qout64);
 190        memcpy (qin->qname, seg->dcss_name, 8);
 191
 192        diag_cc = dcss_diag (DCSS_SEGEXT, qin, &dummy, &vmrc);
 193
 194        if (diag_cc > 1) {
 195                rc = dcss_diag_translate_rc (vmrc);
 196                goto out_free;
 197        }
 198
 199        if (qout->segcnt > 6) {
 200                rc = -ENOTSUPP;
 201                goto out_free;
 202        }
 203
 204        if (qout->segcnt == 1) {
 205                seg->vm_segtype = qout->range[0].start & 0xff;
 206        } else {
 207                /* multi-part segment. only one type supported here:
 208                    - all parts are contiguous
 209                    - all parts are either EW or EN type
 210                    - maximum 6 parts allowed */
 211                unsigned long start = qout->segstart >> PAGE_SHIFT;
 212                for (i=0; i<qout->segcnt; i++) {
 213                        if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) &&
 214                            ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) {
 215                                rc = -ENOTSUPP;
 216                                goto out_free;
 217                        }
 218                        if (start != qout->range[i].start >> PAGE_SHIFT) {
 219                                rc = -ENOTSUPP;
 220                                goto out_free;
 221                        }
 222                        start = (qout->range[i].end >> PAGE_SHIFT) + 1;
 223                }
 224                seg->vm_segtype = SEG_TYPE_EWEN;
 225        }
 226
 227        /* analyze diag output and update seg */
 228        seg->start_addr = qout->segstart;
 229        seg->end = qout->segend;
 230
 231        memcpy (seg->range, qout->range, 6*sizeof(struct qrange));
 232        seg->segcnt = qout->segcnt;
 233
 234        rc = 0;
 235
 236 out_free:
 237        if (qin) kfree(qin);
 238        if (qout) kfree(qout);
 239        return rc;
 240}
 241
 242/*
 243 * check if the given segment collides with guest storage.
 244 * returns 1 if this is the case, 0 if no collision was found
 245 */
 246static int
 247segment_overlaps_storage(struct dcss_segment *seg)
 248{
 249        int i;
 250
 251        for (i=0; i < MEMORY_CHUNKS && memory_chunk[i].size > 0; i++) {
 252                if (memory_chunk[i].type != 0)
 253                        continue;
 254                if ((memory_chunk[i].addr >> 20) > (seg->end >> 20))
 255                        continue;
 256                if (((memory_chunk[i].addr + memory_chunk[i].size - 1) >> 20)
 257                                < (seg->start_addr >> 20))
 258                        continue;
 259                return 1;
 260        }
 261        return 0;
 262}
 263
 264/*
 265 * check if segment collides with other segments that are currently loaded
 266 * returns 1 if this is the case, 0 if no collision was found
 267 */
 268static int
 269segment_overlaps_others (struct dcss_segment *seg)
 270{
 271        struct list_head *l;
 272        struct dcss_segment *tmp;
 273
 274        assert_spin_locked(&dcss_lock);
 275        list_for_each(l, &dcss_list) {
 276                tmp = list_entry(l, struct dcss_segment, list);
 277                if ((tmp->start_addr >> 20) > (seg->end >> 20))
 278                        continue;
 279                if ((tmp->end >> 20) < (seg->start_addr >> 20))
 280                        continue;
 281                if (seg == tmp)
 282                        continue;
 283                return 1;
 284        }
 285        return 0;
 286}
 287
 288/*
 289 * check if segment exceeds the kernel mapping range (detected or set via mem=)
 290 * returns 1 if this is the case, 0 if segment fits into the range
 291 */
 292static inline int
 293segment_exceeds_range (struct dcss_segment *seg)
 294{
 295        int seg_last_pfn = (seg->end) >> PAGE_SHIFT;
 296        if (seg_last_pfn > max_pfn)
 297                return 1;
 298        return 0;
 299}
 300
 301/*
 302 * get info about a segment
 303 * possible return values:
 304 * -ENOSYS  : we are not running on VM
 305 * -EIO     : could not perform query diagnose
 306 * -ENOENT  : no such segment
 307 * -ENOTSUPP: multi-part segment cannot be used with linux
 308 * -ENOSPC  : segment cannot be used (overlaps with storage)
 309 * -ENOMEM  : out of memory
 310 * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
 311 */
 312int
 313segment_type (char* name)
 314{
 315        int rc;
 316        struct dcss_segment seg;
 317
 318        if (!MACHINE_IS_VM)
 319                return -ENOSYS;
 320
 321        dcss_mkname(name, seg.dcss_name);
 322        rc = query_segment_type (&seg);
 323        if (rc < 0)
 324                return rc;
 325        return seg.vm_segtype;
 326}
 327
 328/*
 329 * real segment loading function, called from segment_load
 330 */
 331static int
 332__segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
 333{
 334        struct dcss_segment *seg = kmalloc(sizeof(struct dcss_segment),
 335                        GFP_DMA);
 336        int dcss_command, rc, diag_cc;
 337
 338        if (seg == NULL) {
 339                rc = -ENOMEM;
 340                goto out;
 341        }
 342        dcss_mkname (name, seg->dcss_name);
 343        rc = query_segment_type (seg);
 344        if (rc < 0)
 345                goto out_free;
 346        if (segment_exceeds_range(seg)) {
 347                PRINT_WARN ("segment_load: not loading segment %s - exceeds"
 348                                " kernel mapping range\n",name);
 349                rc = -ERANGE;
 350                goto out_free;
 351        }
 352        if (segment_overlaps_storage(seg)) {
 353                PRINT_WARN ("segment_load: not loading segment %s - overlaps"
 354                                " storage\n",name);
 355                rc = -ENOSPC;
 356                goto out_free;
 357        }
 358        if (segment_overlaps_others(seg)) {
 359                PRINT_WARN ("segment_load: not loading segment %s - overlaps"
 360                                " other segments\n",name);
 361                rc = -EBUSY;
 362                goto out_free;
 363        }
 364        if (do_nonshared)
 365                dcss_command = DCSS_LOADNSR;
 366        else
 367                dcss_command = DCSS_LOADNOLY;
 368
 369        diag_cc = dcss_diag(dcss_command, seg->dcss_name,
 370                        &seg->start_addr, &seg->end);
 371        if (diag_cc > 1) {
 372                PRINT_WARN ("segment_load: could not load segment %s - "
 373                                "diag returned error (%ld)\n",name,seg->end);
 374                rc = dcss_diag_translate_rc (seg->end);
 375                dcss_diag(DCSS_PURGESEG, seg->dcss_name,
 376                                &seg->start_addr, &seg->end);
 377                goto out_free;
 378        }
 379        seg->do_nonshared = do_nonshared;
 380        atomic_set(&seg->ref_count, 1);
 381        list_add(&seg->list, &dcss_list);
 382        rc = seg->vm_segtype;
 383        *addr = seg->start_addr;
 384        *end  = seg->end;
 385        if (do_nonshared)
 386                PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
 387                                "type %s in non-shared mode\n", name,
 388                                (void*)seg->start_addr, (void*)seg->end,
 389                                segtype_string[seg->vm_segtype]);
 390        else
 391                PRINT_INFO ("segment_load: loaded segment %s range %p .. %p "
 392                                "type %s in shared mode\n", name,
 393                                (void*)seg->start_addr, (void*)seg->end,
 394                                segtype_string[seg->vm_segtype]);
 395        goto out;
 396 out_free:
 397        kfree (seg);
 398 out:
 399        return rc;
 400}
 401
 402/*
 403 * this function loads a DCSS segment
 404 * name         : name of the DCSS
 405 * do_nonshared : 0 indicates that the dcss should be shared with other linux images
 406 *                1 indicates that the dcss should be exclusive for this linux image
 407 * addr         : will be filled with start address of the segment
 408 * end          : will be filled with end address of the segment
 409 * return values:
 410 * -ENOSYS  : we are not running on VM
 411 * -EIO     : could not perform query or load diagnose
 412 * -ENOENT  : no such segment
 413 * -ENOTSUPP: multi-part segment cannot be used with linux
 414 * -ENOSPC  : segment cannot be used (overlaps with storage)
 415 * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
 416 * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
 417 * -EPERM   : segment is currently loaded with incompatible permissions
 418 * -ENOMEM  : out of memory
 419 * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
 420 */
 421int
 422segment_load (char *name, int do_nonshared, unsigned long *addr,
 423                unsigned long *end)
 424{
 425        struct dcss_segment *seg;
 426        int rc;
 427
 428        if (!MACHINE_IS_VM)
 429                return -ENOSYS;
 430
 431        spin_lock (&dcss_lock);
 432        seg = segment_by_name (name);
 433        if (seg == NULL)
 434                rc = __segment_load (name, do_nonshared, addr, end);
 435        else {
 436                if (do_nonshared == seg->do_nonshared) {
 437                        atomic_inc(&seg->ref_count);
 438                        *addr = seg->start_addr;
 439                        *end  = seg->end;
 440                        rc    = seg->vm_segtype;
 441                } else {
 442                        *addr = *end = 0;
 443                        rc    = -EPERM;
 444                }
 445        }
 446        spin_unlock (&dcss_lock);
 447        return rc;
 448}
 449
 450/*
 451 * this function modifies the shared state of a DCSS segment. note that
 452 * name         : name of the DCSS
 453 * do_nonshared : 0 indicates that the dcss should be shared with other linux images
 454 *                1 indicates that the dcss should be exclusive for this linux image
 455 * return values:
 456 * -EIO     : could not perform load diagnose (segment gone!)
 457 * -ENOENT  : no such segment (segment gone!)
 458 * -EAGAIN  : segment is in use by other exploiters, try later
 459 * -EINVAL  : no segment with the given name is currently loaded - name invalid
 460 * 0        : operation succeeded
 461 */
 462int
 463segment_modify_shared (char *name, int do_nonshared)
 464{
 465        struct dcss_segment *seg;
 466        unsigned long dummy;
 467        int dcss_command, rc, diag_cc;
 468
 469        spin_lock (&dcss_lock);
 470        seg = segment_by_name (name);
 471        if (seg == NULL) {
 472                rc = -EINVAL;
 473                goto out_unlock;
 474        }
 475        if (do_nonshared == seg->do_nonshared) {
 476                PRINT_INFO ("segment_modify_shared: not reloading segment %s"
 477                                " - already in requested mode\n",name);
 478                rc = 0;
 479                goto out_unlock;
 480        }
 481        if (atomic_read (&seg->ref_count) != 1) {
 482                PRINT_WARN ("segment_modify_shared: not reloading segment %s - "
 483                                "segment is in use by other driver(s)\n",name);
 484                rc = -EAGAIN;
 485                goto out_unlock;
 486        }
 487        dcss_diag(DCSS_PURGESEG, seg->dcss_name,
 488                  &dummy, &dummy);
 489        if (do_nonshared)
 490                dcss_command = DCSS_LOADNSR;
 491        else
 492        dcss_command = DCSS_LOADNOLY;
 493        diag_cc = dcss_diag(dcss_command, seg->dcss_name,
 494                        &seg->start_addr, &seg->end);
 495        if (diag_cc > 1) {
 496                PRINT_WARN ("segment_modify_shared: could not reload segment %s"
 497                                " - diag returned error (%ld)\n",name,seg->end);
 498                rc = dcss_diag_translate_rc (seg->end);
 499                goto out_del;
 500        }
 501        seg->do_nonshared = do_nonshared;
 502        rc = 0;
 503        goto out_unlock;
 504 out_del:
 505        list_del(&seg->list);
 506        dcss_diag(DCSS_PURGESEG, seg->dcss_name,
 507                  &dummy, &dummy);
 508        kfree (seg);
 509 out_unlock:
 510        spin_unlock(&dcss_lock);
 511        return rc;
 512}
 513
 514/*
 515 * Decrease the use count of a DCSS segment and remove
 516 * it from the address space if nobody is using it
 517 * any longer.
 518 */
 519void
 520segment_unload(char *name)
 521{
 522        unsigned long dummy;
 523        struct dcss_segment *seg;
 524
 525        if (!MACHINE_IS_VM)
 526                return;
 527
 528        spin_lock(&dcss_lock);
 529        seg = segment_by_name (name);
 530        if (seg == NULL) {
 531                PRINT_ERR ("could not find segment %s in segment_unload, "
 532                                "please report to linux390@de.ibm.com\n",name);
 533                goto out_unlock;
 534        }
 535        if (atomic_dec_return(&seg->ref_count) == 0) {
 536                list_del(&seg->list);
 537                dcss_diag(DCSS_PURGESEG, seg->dcss_name,
 538                          &dummy, &dummy);
 539                kfree(seg);
 540        }
 541out_unlock:
 542        spin_unlock(&dcss_lock);
 543}
 544
 545/*
 546 * save segment content permanently
 547 */
 548void
 549segment_save(char *name)
 550{
 551        struct dcss_segment *seg;
 552        int startpfn = 0;
 553        int endpfn = 0;
 554        char cmd1[160];
 555        char cmd2[80];
 556        int i;
 557
 558        if (!MACHINE_IS_VM)
 559                return;
 560
 561        spin_lock(&dcss_lock);
 562        seg = segment_by_name (name);
 563
 564        if (seg == NULL) {
 565                PRINT_ERR ("could not find segment %s in segment_save, please report to linux390@de.ibm.com\n",name);
 566                return;
 567        }
 568
 569        startpfn = seg->start_addr >> PAGE_SHIFT;
 570        endpfn = (seg->end) >> PAGE_SHIFT;
 571        sprintf(cmd1, "DEFSEG %s", name);
 572        for (i=0; i<seg->segcnt; i++) {
 573                sprintf(cmd1+strlen(cmd1), " %X-%X %s",
 574                        seg->range[i].start >> PAGE_SHIFT,
 575                        seg->range[i].end >> PAGE_SHIFT,
 576                        segtype_string[seg->range[i].start & 0xff]);
 577        }
 578        sprintf(cmd2, "SAVESEG %s", name);
 579        cpcmd(cmd1, NULL, 0);
 580        cpcmd(cmd2, NULL, 0);
 581        spin_unlock(&dcss_lock);
 582}
 583
 584EXPORT_SYMBOL(segment_load);
 585EXPORT_SYMBOL(segment_unload);
 586EXPORT_SYMBOL(segment_save);
 587EXPORT_SYMBOL(segment_type);
 588EXPORT_SYMBOL(segment_modify_shared);
 589
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.