Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Author(s)......: Carsten Otte <cotte@de.ibm.com>
0004  *          Rob M van der Heij <rvdheij@nl.ibm.com>
0005  *          Steven Shultz <shultzss@us.ibm.com>
0006  * Bugreports.to..: <Linux390@de.ibm.com>
0007  * Copyright IBM Corp. 2002, 2004
0008  */
0009 
0010 #define KMSG_COMPONENT "extmem"
0011 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
0012 
0013 #include <linux/kernel.h>
0014 #include <linux/string.h>
0015 #include <linux/spinlock.h>
0016 #include <linux/list.h>
0017 #include <linux/slab.h>
0018 #include <linux/export.h>
0019 #include <linux/memblock.h>
0020 #include <linux/ctype.h>
0021 #include <linux/ioport.h>
0022 #include <linux/refcount.h>
0023 #include <linux/pgtable.h>
0024 #include <asm/diag.h>
0025 #include <asm/page.h>
0026 #include <asm/ebcdic.h>
0027 #include <asm/errno.h>
0028 #include <asm/extmem.h>
0029 #include <asm/cpcmd.h>
0030 #include <asm/setup.h>
0031 
0032 #define DCSS_PURGESEG   0x08
0033 #define DCSS_LOADSHRX   0x20
0034 #define DCSS_LOADNSRX   0x24
0035 #define DCSS_FINDSEGX   0x2c
0036 #define DCSS_SEGEXTX    0x38
0037 #define DCSS_FINDSEGA   0x0c
0038 
0039 struct qrange {
0040     unsigned long  start; /* last byte type */
0041     unsigned long  end;   /* last byte reserved */
0042 };
0043 
0044 struct qout64 {
0045     unsigned long segstart;
0046     unsigned long segend;
0047     int segcnt;
0048     int segrcnt;
0049     struct qrange range[6];
0050 };
0051 
0052 struct qin64 {
0053     char qopcode;
0054     char rsrv1[3];
0055     char qrcode;
0056     char rsrv2[3];
0057     char qname[8];
0058     unsigned int qoutptr;
0059     short int qoutlen;
0060 };
0061 
0062 struct dcss_segment {
0063     struct list_head list;
0064     char dcss_name[8];
0065     char res_name[16];
0066     unsigned long start_addr;
0067     unsigned long end;
0068     refcount_t ref_count;
0069     int do_nonshared;
0070     unsigned int vm_segtype;
0071     struct qrange range[6];
0072     int segcnt;
0073     struct resource *res;
0074 };
0075 
0076 static DEFINE_MUTEX(dcss_lock);
0077 static LIST_HEAD(dcss_list);
0078 static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
0079                     "EW/EN-MIXED" };
0080 static int loadshr_scode = DCSS_LOADSHRX;
0081 static int loadnsr_scode = DCSS_LOADNSRX;
0082 static int purgeseg_scode = DCSS_PURGESEG;
0083 static int segext_scode = DCSS_SEGEXTX;
0084 
0085 /*
0086  * Create the 8 bytes, ebcdic VM segment name from
0087  * an ascii name.
0088  */
0089 static void
0090 dcss_mkname(char *name, char *dcss_name)
0091 {
0092     int i;
0093 
0094     for (i = 0; i < 8; i++) {
0095         if (name[i] == '\0')
0096             break;
0097         dcss_name[i] = toupper(name[i]);
0098     }
0099     for (; i < 8; i++)
0100         dcss_name[i] = ' ';
0101     ASCEBC(dcss_name, 8);
0102 }
0103 
0104 
0105 /*
0106  * search all segments in dcss_list, and return the one
0107  * namend *name. If not found, return NULL.
0108  */
0109 static struct dcss_segment *
0110 segment_by_name (char *name)
0111 {
0112     char dcss_name[9];
0113     struct list_head *l;
0114     struct dcss_segment *tmp, *retval = NULL;
0115 
0116     BUG_ON(!mutex_is_locked(&dcss_lock));
0117     dcss_mkname (name, dcss_name);
0118     list_for_each (l, &dcss_list) {
0119         tmp = list_entry (l, struct dcss_segment, list);
0120         if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) {
0121             retval = tmp;
0122             break;
0123         }
0124     }
0125     return retval;
0126 }
0127 
0128 
0129 /*
0130  * Perform a function on a dcss segment.
0131  */
0132 static inline int
0133 dcss_diag(int *func, void *parameter,
0134            unsigned long *ret1, unsigned long *ret2)
0135 {
0136     unsigned long rx, ry;
0137     int rc;
0138 
0139     rx = (unsigned long) parameter;
0140     ry = (unsigned long) *func;
0141 
0142     diag_stat_inc(DIAG_STAT_X064);
0143     asm volatile(
0144         "   diag    %0,%1,0x64\n"
0145         "   ipm %2\n"
0146         "   srl %2,28\n"
0147         : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
0148     *ret1 = rx;
0149     *ret2 = ry;
0150     return rc;
0151 }
0152 
0153 static inline int
0154 dcss_diag_translate_rc (int vm_rc) {
0155     if (vm_rc == 44)
0156         return -ENOENT;
0157     return -EIO;
0158 }
0159 
0160 
0161 /* do a diag to get info about a segment.
0162  * fills start_address, end and vm_segtype fields
0163  */
0164 static int
0165 query_segment_type (struct dcss_segment *seg)
0166 {
0167     unsigned long dummy, vmrc;
0168     int diag_cc, rc, i;
0169     struct qout64 *qout;
0170     struct qin64 *qin;
0171 
0172     qin = kmalloc(sizeof(*qin), GFP_KERNEL | GFP_DMA);
0173     qout = kmalloc(sizeof(*qout), GFP_KERNEL | GFP_DMA);
0174     if ((qin == NULL) || (qout == NULL)) {
0175         rc = -ENOMEM;
0176         goto out_free;
0177     }
0178 
0179     /* initialize diag input parameters */
0180     qin->qopcode = DCSS_FINDSEGA;
0181     qin->qoutptr = (unsigned long) qout;
0182     qin->qoutlen = sizeof(struct qout64);
0183     memcpy (qin->qname, seg->dcss_name, 8);
0184 
0185     diag_cc = dcss_diag(&segext_scode, qin, &dummy, &vmrc);
0186 
0187     if (diag_cc < 0) {
0188         rc = diag_cc;
0189         goto out_free;
0190     }
0191     if (diag_cc > 1) {
0192         pr_warn("Querying a DCSS type failed with rc=%ld\n", vmrc);
0193         rc = dcss_diag_translate_rc (vmrc);
0194         goto out_free;
0195     }
0196 
0197     if (qout->segcnt > 6) {
0198         rc = -EOPNOTSUPP;
0199         goto out_free;
0200     }
0201 
0202     if (qout->segcnt == 1) {
0203         seg->vm_segtype = qout->range[0].start & 0xff;
0204     } else {
0205         /* multi-part segment. only one type supported here:
0206             - all parts are contiguous
0207             - all parts are either EW or EN type
0208             - maximum 6 parts allowed */
0209         unsigned long start = qout->segstart >> PAGE_SHIFT;
0210         for (i=0; i<qout->segcnt; i++) {
0211             if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) &&
0212                 ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) {
0213                 rc = -EOPNOTSUPP;
0214                 goto out_free;
0215             }
0216             if (start != qout->range[i].start >> PAGE_SHIFT) {
0217                 rc = -EOPNOTSUPP;
0218                 goto out_free;
0219             }
0220             start = (qout->range[i].end >> PAGE_SHIFT) + 1;
0221         }
0222         seg->vm_segtype = SEG_TYPE_EWEN;
0223     }
0224 
0225     /* analyze diag output and update seg */
0226     seg->start_addr = qout->segstart;
0227     seg->end = qout->segend;
0228 
0229     memcpy (seg->range, qout->range, 6*sizeof(struct qrange));
0230     seg->segcnt = qout->segcnt;
0231 
0232     rc = 0;
0233 
0234  out_free:
0235     kfree(qin);
0236     kfree(qout);
0237     return rc;
0238 }
0239 
0240 /*
0241  * get info about a segment
0242  * possible return values:
0243  * -ENOSYS  : we are not running on VM
0244  * -EIO     : could not perform query diagnose
0245  * -ENOENT  : no such segment
0246  * -EOPNOTSUPP: multi-part segment cannot be used with linux
0247  * -ENOMEM  : out of memory
0248  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
0249  */
0250 int
0251 segment_type (char* name)
0252 {
0253     int rc;
0254     struct dcss_segment seg;
0255 
0256     if (!MACHINE_IS_VM)
0257         return -ENOSYS;
0258 
0259     dcss_mkname(name, seg.dcss_name);
0260     rc = query_segment_type (&seg);
0261     if (rc < 0)
0262         return rc;
0263     return seg.vm_segtype;
0264 }
0265 
0266 /*
0267  * check if segment collides with other segments that are currently loaded
0268  * returns 1 if this is the case, 0 if no collision was found
0269  */
0270 static int
0271 segment_overlaps_others (struct dcss_segment *seg)
0272 {
0273     struct list_head *l;
0274     struct dcss_segment *tmp;
0275 
0276     BUG_ON(!mutex_is_locked(&dcss_lock));
0277     list_for_each(l, &dcss_list) {
0278         tmp = list_entry(l, struct dcss_segment, list);
0279         if ((tmp->start_addr >> 20) > (seg->end >> 20))
0280             continue;
0281         if ((tmp->end >> 20) < (seg->start_addr >> 20))
0282             continue;
0283         if (seg == tmp)
0284             continue;
0285         return 1;
0286     }
0287     return 0;
0288 }
0289 
0290 /*
0291  * real segment loading function, called from segment_load
0292  */
0293 static int
0294 __segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
0295 {
0296     unsigned long start_addr, end_addr, dummy;
0297     struct dcss_segment *seg;
0298     int rc, diag_cc;
0299 
0300     start_addr = end_addr = 0;
0301     seg = kmalloc(sizeof(*seg), GFP_KERNEL | GFP_DMA);
0302     if (seg == NULL) {
0303         rc = -ENOMEM;
0304         goto out;
0305     }
0306     dcss_mkname (name, seg->dcss_name);
0307     rc = query_segment_type (seg);
0308     if (rc < 0)
0309         goto out_free;
0310 
0311     if (segment_overlaps_others(seg)) {
0312         rc = -EBUSY;
0313         goto out_free;
0314     }
0315 
0316     seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL);
0317     if (seg->res == NULL) {
0318         rc = -ENOMEM;
0319         goto out_free;
0320     }
0321     seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM;
0322     seg->res->start = seg->start_addr;
0323     seg->res->end = seg->end;
0324     memcpy(&seg->res_name, seg->dcss_name, 8);
0325     EBCASC(seg->res_name, 8);
0326     seg->res_name[8] = '\0';
0327     strlcat(seg->res_name, " (DCSS)", sizeof(seg->res_name));
0328     seg->res->name = seg->res_name;
0329     rc = seg->vm_segtype;
0330     if (rc == SEG_TYPE_SC ||
0331         ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared))
0332         seg->res->flags |= IORESOURCE_READONLY;
0333 
0334     /* Check for overlapping resources before adding the mapping. */
0335     if (request_resource(&iomem_resource, seg->res)) {
0336         rc = -EBUSY;
0337         goto out_free_resource;
0338     }
0339 
0340     rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
0341     if (rc)
0342         goto out_resource;
0343 
0344     if (do_nonshared)
0345         diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
0346                 &start_addr, &end_addr);
0347     else
0348         diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
0349                 &start_addr, &end_addr);
0350     if (diag_cc < 0) {
0351         dcss_diag(&purgeseg_scode, seg->dcss_name,
0352                 &dummy, &dummy);
0353         rc = diag_cc;
0354         goto out_mapping;
0355     }
0356     if (diag_cc > 1) {
0357         pr_warn("Loading DCSS %s failed with rc=%ld\n", name, end_addr);
0358         rc = dcss_diag_translate_rc(end_addr);
0359         dcss_diag(&purgeseg_scode, seg->dcss_name,
0360                 &dummy, &dummy);
0361         goto out_mapping;
0362     }
0363     seg->start_addr = start_addr;
0364     seg->end = end_addr;
0365     seg->do_nonshared = do_nonshared;
0366     refcount_set(&seg->ref_count, 1);
0367     list_add(&seg->list, &dcss_list);
0368     *addr = seg->start_addr;
0369     *end  = seg->end;
0370     if (do_nonshared)
0371         pr_info("DCSS %s of range %px to %px and type %s loaded as "
0372             "exclusive-writable\n", name, (void*) seg->start_addr,
0373             (void*) seg->end, segtype_string[seg->vm_segtype]);
0374     else {
0375         pr_info("DCSS %s of range %px to %px and type %s loaded in "
0376             "shared access mode\n", name, (void*) seg->start_addr,
0377             (void*) seg->end, segtype_string[seg->vm_segtype]);
0378     }
0379     goto out;
0380  out_mapping:
0381     vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
0382  out_resource:
0383     release_resource(seg->res);
0384  out_free_resource:
0385     kfree(seg->res);
0386  out_free:
0387     kfree(seg);
0388  out:
0389     return rc;
0390 }
0391 
0392 /*
0393  * this function loads a DCSS segment
0394  * name         : name of the DCSS
0395  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
0396  *                1 indicates that the dcss should be exclusive for this linux image
0397  * addr         : will be filled with start address of the segment
0398  * end          : will be filled with end address of the segment
0399  * return values:
0400  * -ENOSYS  : we are not running on VM
0401  * -EIO     : could not perform query or load diagnose
0402  * -ENOENT  : no such segment
0403  * -EOPNOTSUPP: multi-part segment cannot be used with linux
0404  * -EBUSY   : segment cannot be used (overlaps with dcss or storage)
0405  * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
0406  * -EPERM   : segment is currently loaded with incompatible permissions
0407  * -ENOMEM  : out of memory
0408  * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
0409  */
0410 int
0411 segment_load (char *name, int do_nonshared, unsigned long *addr,
0412         unsigned long *end)
0413 {
0414     struct dcss_segment *seg;
0415     int rc;
0416 
0417     if (!MACHINE_IS_VM)
0418         return -ENOSYS;
0419 
0420     mutex_lock(&dcss_lock);
0421     seg = segment_by_name (name);
0422     if (seg == NULL)
0423         rc = __segment_load (name, do_nonshared, addr, end);
0424     else {
0425         if (do_nonshared == seg->do_nonshared) {
0426             refcount_inc(&seg->ref_count);
0427             *addr = seg->start_addr;
0428             *end  = seg->end;
0429             rc    = seg->vm_segtype;
0430         } else {
0431             *addr = *end = 0;
0432             rc    = -EPERM;
0433         }
0434     }
0435     mutex_unlock(&dcss_lock);
0436     return rc;
0437 }
0438 
0439 /*
0440  * this function modifies the shared state of a DCSS segment. note that
0441  * name         : name of the DCSS
0442  * do_nonshared : 0 indicates that the dcss should be shared with other linux images
0443  *                1 indicates that the dcss should be exclusive for this linux image
0444  * return values:
0445  * -EIO     : could not perform load diagnose (segment gone!)
0446  * -ENOENT  : no such segment (segment gone!)
0447  * -EAGAIN  : segment is in use by other exploiters, try later
0448  * -EINVAL  : no segment with the given name is currently loaded - name invalid
0449  * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
0450  * 0        : operation succeeded
0451  */
0452 int
0453 segment_modify_shared (char *name, int do_nonshared)
0454 {
0455     struct dcss_segment *seg;
0456     unsigned long start_addr, end_addr, dummy;
0457     int rc, diag_cc;
0458 
0459     start_addr = end_addr = 0;
0460     mutex_lock(&dcss_lock);
0461     seg = segment_by_name (name);
0462     if (seg == NULL) {
0463         rc = -EINVAL;
0464         goto out_unlock;
0465     }
0466     if (do_nonshared == seg->do_nonshared) {
0467         pr_info("DCSS %s is already in the requested access "
0468             "mode\n", name);
0469         rc = 0;
0470         goto out_unlock;
0471     }
0472     if (refcount_read(&seg->ref_count) != 1) {
0473         pr_warn("DCSS %s is in use and cannot be reloaded\n", name);
0474         rc = -EAGAIN;
0475         goto out_unlock;
0476     }
0477     release_resource(seg->res);
0478     if (do_nonshared)
0479         seg->res->flags &= ~IORESOURCE_READONLY;
0480     else
0481         if (seg->vm_segtype == SEG_TYPE_SR ||
0482             seg->vm_segtype == SEG_TYPE_ER)
0483             seg->res->flags |= IORESOURCE_READONLY;
0484 
0485     if (request_resource(&iomem_resource, seg->res)) {
0486         pr_warn("DCSS %s overlaps with used memory resources and cannot be reloaded\n",
0487             name);
0488         rc = -EBUSY;
0489         kfree(seg->res);
0490         goto out_del_mem;
0491     }
0492 
0493     dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
0494     if (do_nonshared)
0495         diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
0496                 &start_addr, &end_addr);
0497     else
0498         diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
0499                 &start_addr, &end_addr);
0500     if (diag_cc < 0) {
0501         rc = diag_cc;
0502         goto out_del_res;
0503     }
0504     if (diag_cc > 1) {
0505         pr_warn("Reloading DCSS %s failed with rc=%ld\n",
0506             name, end_addr);
0507         rc = dcss_diag_translate_rc(end_addr);
0508         goto out_del_res;
0509     }
0510     seg->start_addr = start_addr;
0511     seg->end = end_addr;
0512     seg->do_nonshared = do_nonshared;
0513     rc = 0;
0514     goto out_unlock;
0515  out_del_res:
0516     release_resource(seg->res);
0517     kfree(seg->res);
0518  out_del_mem:
0519     vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
0520     list_del(&seg->list);
0521     dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
0522     kfree(seg);
0523  out_unlock:
0524     mutex_unlock(&dcss_lock);
0525     return rc;
0526 }
0527 
0528 /*
0529  * Decrease the use count of a DCSS segment and remove
0530  * it from the address space if nobody is using it
0531  * any longer.
0532  */
0533 void
0534 segment_unload(char *name)
0535 {
0536     unsigned long dummy;
0537     struct dcss_segment *seg;
0538 
0539     if (!MACHINE_IS_VM)
0540         return;
0541 
0542     mutex_lock(&dcss_lock);
0543     seg = segment_by_name (name);
0544     if (seg == NULL) {
0545         pr_err("Unloading unknown DCSS %s failed\n", name);
0546         goto out_unlock;
0547     }
0548     if (!refcount_dec_and_test(&seg->ref_count))
0549         goto out_unlock;
0550     release_resource(seg->res);
0551     kfree(seg->res);
0552     vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
0553     list_del(&seg->list);
0554     dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
0555     kfree(seg);
0556 out_unlock:
0557     mutex_unlock(&dcss_lock);
0558 }
0559 
0560 /*
0561  * save segment content permanently
0562  */
0563 void
0564 segment_save(char *name)
0565 {
0566     struct dcss_segment *seg;
0567     char cmd1[160];
0568     char cmd2[80];
0569     int i, response;
0570 
0571     if (!MACHINE_IS_VM)
0572         return;
0573 
0574     mutex_lock(&dcss_lock);
0575     seg = segment_by_name (name);
0576 
0577     if (seg == NULL) {
0578         pr_err("Saving unknown DCSS %s failed\n", name);
0579         goto out;
0580     }
0581 
0582     sprintf(cmd1, "DEFSEG %s", name);
0583     for (i=0; i<seg->segcnt; i++) {
0584         sprintf(cmd1+strlen(cmd1), " %lX-%lX %s",
0585             seg->range[i].start >> PAGE_SHIFT,
0586             seg->range[i].end >> PAGE_SHIFT,
0587             segtype_string[seg->range[i].start & 0xff]);
0588     }
0589     sprintf(cmd2, "SAVESEG %s", name);
0590     response = 0;
0591     cpcmd(cmd1, NULL, 0, &response);
0592     if (response) {
0593         pr_err("Saving a DCSS failed with DEFSEG response code "
0594                "%i\n", response);
0595         goto out;
0596     }
0597     cpcmd(cmd2, NULL, 0, &response);
0598     if (response) {
0599         pr_err("Saving a DCSS failed with SAVESEG response code "
0600                "%i\n", response);
0601         goto out;
0602     }
0603 out:
0604     mutex_unlock(&dcss_lock);
0605 }
0606 
0607 /*
0608  * print appropriate error message for segment_load()/segment_type()
0609  * return code
0610  */
0611 void segment_warning(int rc, char *seg_name)
0612 {
0613     switch (rc) {
0614     case -ENOENT:
0615         pr_err("DCSS %s cannot be loaded or queried\n", seg_name);
0616         break;
0617     case -ENOSYS:
0618         pr_err("DCSS %s cannot be loaded or queried without "
0619                "z/VM\n", seg_name);
0620         break;
0621     case -EIO:
0622         pr_err("Loading or querying DCSS %s resulted in a "
0623                "hardware error\n", seg_name);
0624         break;
0625     case -EOPNOTSUPP:
0626         pr_err("DCSS %s has multiple page ranges and cannot be "
0627                "loaded or queried\n", seg_name);
0628         break;
0629     case -EBUSY:
0630         pr_err("%s needs used memory resources and cannot be "
0631                "loaded or queried\n", seg_name);
0632         break;
0633     case -EPERM:
0634         pr_err("DCSS %s is already loaded in a different access "
0635                "mode\n", seg_name);
0636         break;
0637     case -ENOMEM:
0638         pr_err("There is not enough memory to load or query "
0639                "DCSS %s\n", seg_name);
0640         break;
0641     case -ERANGE:
0642         pr_err("DCSS %s exceeds the kernel mapping range (%lu) "
0643                "and cannot be loaded\n", seg_name, VMEM_MAX_PHYS);
0644         break;
0645     default:
0646         break;
0647     }
0648 }
0649 
0650 EXPORT_SYMBOL(segment_load);
0651 EXPORT_SYMBOL(segment_unload);
0652 EXPORT_SYMBOL(segment_save);
0653 EXPORT_SYMBOL(segment_type);
0654 EXPORT_SYMBOL(segment_modify_shared);
0655 EXPORT_SYMBOL(segment_warning);