162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Author(s)......: Carsten Otte <cotte@de.ibm.com> 462306a36Sopenharmony_ci * Rob M van der Heij <rvdheij@nl.ibm.com> 562306a36Sopenharmony_ci * Steven Shultz <shultzss@us.ibm.com> 662306a36Sopenharmony_ci * Bugreports.to..: <Linux390@de.ibm.com> 762306a36Sopenharmony_ci * Copyright IBM Corp. 2002, 2004 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define KMSG_COMPONENT "extmem" 1162306a36Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/string.h> 1562306a36Sopenharmony_ci#include <linux/spinlock.h> 1662306a36Sopenharmony_ci#include <linux/list.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/export.h> 1962306a36Sopenharmony_ci#include <linux/memblock.h> 2062306a36Sopenharmony_ci#include <linux/ctype.h> 2162306a36Sopenharmony_ci#include <linux/ioport.h> 2262306a36Sopenharmony_ci#include <linux/refcount.h> 2362306a36Sopenharmony_ci#include <linux/pgtable.h> 2462306a36Sopenharmony_ci#include <asm/diag.h> 2562306a36Sopenharmony_ci#include <asm/page.h> 2662306a36Sopenharmony_ci#include <asm/ebcdic.h> 2762306a36Sopenharmony_ci#include <asm/errno.h> 2862306a36Sopenharmony_ci#include <asm/extmem.h> 2962306a36Sopenharmony_ci#include <asm/cpcmd.h> 3062306a36Sopenharmony_ci#include <asm/setup.h> 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define DCSS_PURGESEG 0x08 3362306a36Sopenharmony_ci#define DCSS_LOADSHRX 0x20 3462306a36Sopenharmony_ci#define DCSS_LOADNSRX 0x24 3562306a36Sopenharmony_ci#define DCSS_FINDSEGX 0x2c 3662306a36Sopenharmony_ci#define DCSS_SEGEXTX 0x38 3762306a36Sopenharmony_ci#define DCSS_FINDSEGA 0x0c 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct qrange { 4062306a36Sopenharmony_ci unsigned long start; /* last byte type */ 4162306a36Sopenharmony_ci unsigned long end; /* last byte reserved */ 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct qout64 { 4562306a36Sopenharmony_ci unsigned long segstart; 4662306a36Sopenharmony_ci unsigned long segend; 4762306a36Sopenharmony_ci int segcnt; 4862306a36Sopenharmony_ci int segrcnt; 4962306a36Sopenharmony_ci struct qrange range[6]; 5062306a36Sopenharmony_ci}; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistruct qin64 { 5362306a36Sopenharmony_ci char qopcode; 5462306a36Sopenharmony_ci char rsrv1[3]; 5562306a36Sopenharmony_ci char qrcode; 5662306a36Sopenharmony_ci char rsrv2[3]; 5762306a36Sopenharmony_ci char qname[8]; 5862306a36Sopenharmony_ci unsigned int qoutptr; 5962306a36Sopenharmony_ci short int qoutlen; 6062306a36Sopenharmony_ci}; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistruct dcss_segment { 6362306a36Sopenharmony_ci struct list_head list; 6462306a36Sopenharmony_ci char dcss_name[8]; 6562306a36Sopenharmony_ci char res_name[16]; 6662306a36Sopenharmony_ci unsigned long start_addr; 6762306a36Sopenharmony_ci unsigned long end; 6862306a36Sopenharmony_ci refcount_t ref_count; 6962306a36Sopenharmony_ci int do_nonshared; 7062306a36Sopenharmony_ci unsigned int vm_segtype; 7162306a36Sopenharmony_ci struct qrange range[6]; 7262306a36Sopenharmony_ci int segcnt; 7362306a36Sopenharmony_ci struct resource *res; 7462306a36Sopenharmony_ci}; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic DEFINE_MUTEX(dcss_lock); 7762306a36Sopenharmony_cistatic LIST_HEAD(dcss_list); 7862306a36Sopenharmony_cistatic char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC", 7962306a36Sopenharmony_ci "EW/EN-MIXED" }; 8062306a36Sopenharmony_cistatic int loadshr_scode = DCSS_LOADSHRX; 8162306a36Sopenharmony_cistatic int loadnsr_scode = DCSS_LOADNSRX; 8262306a36Sopenharmony_cistatic int purgeseg_scode = DCSS_PURGESEG; 8362306a36Sopenharmony_cistatic int segext_scode = DCSS_SEGEXTX; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci/* 8662306a36Sopenharmony_ci * Create the 8 bytes, ebcdic VM segment name from 8762306a36Sopenharmony_ci * an ascii name. 8862306a36Sopenharmony_ci */ 8962306a36Sopenharmony_cistatic void 9062306a36Sopenharmony_cidcss_mkname(char *name, char *dcss_name) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci int i; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 9562306a36Sopenharmony_ci if (name[i] == '\0') 9662306a36Sopenharmony_ci break; 9762306a36Sopenharmony_ci dcss_name[i] = toupper(name[i]); 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci for (; i < 8; i++) 10062306a36Sopenharmony_ci dcss_name[i] = ' '; 10162306a36Sopenharmony_ci ASCEBC(dcss_name, 8); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci/* 10662306a36Sopenharmony_ci * search all segments in dcss_list, and return the one 10762306a36Sopenharmony_ci * namend *name. If not found, return NULL. 10862306a36Sopenharmony_ci */ 10962306a36Sopenharmony_cistatic struct dcss_segment * 11062306a36Sopenharmony_cisegment_by_name (char *name) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci char dcss_name[9]; 11362306a36Sopenharmony_ci struct list_head *l; 11462306a36Sopenharmony_ci struct dcss_segment *tmp, *retval = NULL; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci BUG_ON(!mutex_is_locked(&dcss_lock)); 11762306a36Sopenharmony_ci dcss_mkname (name, dcss_name); 11862306a36Sopenharmony_ci list_for_each (l, &dcss_list) { 11962306a36Sopenharmony_ci tmp = list_entry (l, struct dcss_segment, list); 12062306a36Sopenharmony_ci if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) { 12162306a36Sopenharmony_ci retval = tmp; 12262306a36Sopenharmony_ci break; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci return retval; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/* 13062306a36Sopenharmony_ci * Perform a function on a dcss segment. 13162306a36Sopenharmony_ci */ 13262306a36Sopenharmony_cistatic inline int 13362306a36Sopenharmony_cidcss_diag(int *func, void *parameter, 13462306a36Sopenharmony_ci unsigned long *ret1, unsigned long *ret2) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci unsigned long rx, ry; 13762306a36Sopenharmony_ci int rc; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci rx = (unsigned long) parameter; 14062306a36Sopenharmony_ci ry = (unsigned long) *func; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci diag_stat_inc(DIAG_STAT_X064); 14362306a36Sopenharmony_ci asm volatile( 14462306a36Sopenharmony_ci " diag %0,%1,0x64\n" 14562306a36Sopenharmony_ci " ipm %2\n" 14662306a36Sopenharmony_ci " srl %2,28\n" 14762306a36Sopenharmony_ci : "+d" (rx), "+d" (ry), "=d" (rc) : : "cc"); 14862306a36Sopenharmony_ci *ret1 = rx; 14962306a36Sopenharmony_ci *ret2 = ry; 15062306a36Sopenharmony_ci return rc; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic inline int 15462306a36Sopenharmony_cidcss_diag_translate_rc (int vm_rc) { 15562306a36Sopenharmony_ci if (vm_rc == 44) 15662306a36Sopenharmony_ci return -ENOENT; 15762306a36Sopenharmony_ci return -EIO; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci/* do a diag to get info about a segment. 16262306a36Sopenharmony_ci * fills start_address, end and vm_segtype fields 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_cistatic int 16562306a36Sopenharmony_ciquery_segment_type (struct dcss_segment *seg) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci unsigned long dummy, vmrc; 16862306a36Sopenharmony_ci int diag_cc, rc, i; 16962306a36Sopenharmony_ci struct qout64 *qout; 17062306a36Sopenharmony_ci struct qin64 *qin; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci qin = kmalloc(sizeof(*qin), GFP_KERNEL | GFP_DMA); 17362306a36Sopenharmony_ci qout = kmalloc(sizeof(*qout), GFP_KERNEL | GFP_DMA); 17462306a36Sopenharmony_ci if ((qin == NULL) || (qout == NULL)) { 17562306a36Sopenharmony_ci rc = -ENOMEM; 17662306a36Sopenharmony_ci goto out_free; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* initialize diag input parameters */ 18062306a36Sopenharmony_ci qin->qopcode = DCSS_FINDSEGA; 18162306a36Sopenharmony_ci qin->qoutptr = (unsigned long) qout; 18262306a36Sopenharmony_ci qin->qoutlen = sizeof(struct qout64); 18362306a36Sopenharmony_ci memcpy (qin->qname, seg->dcss_name, 8); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci diag_cc = dcss_diag(&segext_scode, qin, &dummy, &vmrc); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci if (diag_cc < 0) { 18862306a36Sopenharmony_ci rc = diag_cc; 18962306a36Sopenharmony_ci goto out_free; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci if (diag_cc > 1) { 19262306a36Sopenharmony_ci pr_warn("Querying a DCSS type failed with rc=%ld\n", vmrc); 19362306a36Sopenharmony_ci rc = dcss_diag_translate_rc (vmrc); 19462306a36Sopenharmony_ci goto out_free; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (qout->segcnt > 6) { 19862306a36Sopenharmony_ci rc = -EOPNOTSUPP; 19962306a36Sopenharmony_ci goto out_free; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci if (qout->segcnt == 1) { 20362306a36Sopenharmony_ci seg->vm_segtype = qout->range[0].start & 0xff; 20462306a36Sopenharmony_ci } else { 20562306a36Sopenharmony_ci /* multi-part segment. only one type supported here: 20662306a36Sopenharmony_ci - all parts are contiguous 20762306a36Sopenharmony_ci - all parts are either EW or EN type 20862306a36Sopenharmony_ci - maximum 6 parts allowed */ 20962306a36Sopenharmony_ci unsigned long start = qout->segstart >> PAGE_SHIFT; 21062306a36Sopenharmony_ci for (i=0; i<qout->segcnt; i++) { 21162306a36Sopenharmony_ci if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) && 21262306a36Sopenharmony_ci ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) { 21362306a36Sopenharmony_ci rc = -EOPNOTSUPP; 21462306a36Sopenharmony_ci goto out_free; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci if (start != qout->range[i].start >> PAGE_SHIFT) { 21762306a36Sopenharmony_ci rc = -EOPNOTSUPP; 21862306a36Sopenharmony_ci goto out_free; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci start = (qout->range[i].end >> PAGE_SHIFT) + 1; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci seg->vm_segtype = SEG_TYPE_EWEN; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* analyze diag output and update seg */ 22662306a36Sopenharmony_ci seg->start_addr = qout->segstart; 22762306a36Sopenharmony_ci seg->end = qout->segend; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci memcpy (seg->range, qout->range, 6*sizeof(struct qrange)); 23062306a36Sopenharmony_ci seg->segcnt = qout->segcnt; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci rc = 0; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci out_free: 23562306a36Sopenharmony_ci kfree(qin); 23662306a36Sopenharmony_ci kfree(qout); 23762306a36Sopenharmony_ci return rc; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci/* 24162306a36Sopenharmony_ci * get info about a segment 24262306a36Sopenharmony_ci * possible return values: 24362306a36Sopenharmony_ci * -ENOSYS : we are not running on VM 24462306a36Sopenharmony_ci * -EIO : could not perform query diagnose 24562306a36Sopenharmony_ci * -ENOENT : no such segment 24662306a36Sopenharmony_ci * -EOPNOTSUPP: multi-part segment cannot be used with linux 24762306a36Sopenharmony_ci * -ENOMEM : out of memory 24862306a36Sopenharmony_ci * 0 .. 6 : type of segment as defined in include/asm-s390/extmem.h 24962306a36Sopenharmony_ci */ 25062306a36Sopenharmony_ciint 25162306a36Sopenharmony_cisegment_type (char* name) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci int rc; 25462306a36Sopenharmony_ci struct dcss_segment seg; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (!MACHINE_IS_VM) 25762306a36Sopenharmony_ci return -ENOSYS; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci dcss_mkname(name, seg.dcss_name); 26062306a36Sopenharmony_ci rc = query_segment_type (&seg); 26162306a36Sopenharmony_ci if (rc < 0) 26262306a36Sopenharmony_ci return rc; 26362306a36Sopenharmony_ci return seg.vm_segtype; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci/* 26762306a36Sopenharmony_ci * check if segment collides with other segments that are currently loaded 26862306a36Sopenharmony_ci * returns 1 if this is the case, 0 if no collision was found 26962306a36Sopenharmony_ci */ 27062306a36Sopenharmony_cistatic int 27162306a36Sopenharmony_cisegment_overlaps_others (struct dcss_segment *seg) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci struct list_head *l; 27462306a36Sopenharmony_ci struct dcss_segment *tmp; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci BUG_ON(!mutex_is_locked(&dcss_lock)); 27762306a36Sopenharmony_ci list_for_each(l, &dcss_list) { 27862306a36Sopenharmony_ci tmp = list_entry(l, struct dcss_segment, list); 27962306a36Sopenharmony_ci if ((tmp->start_addr >> 20) > (seg->end >> 20)) 28062306a36Sopenharmony_ci continue; 28162306a36Sopenharmony_ci if ((tmp->end >> 20) < (seg->start_addr >> 20)) 28262306a36Sopenharmony_ci continue; 28362306a36Sopenharmony_ci if (seg == tmp) 28462306a36Sopenharmony_ci continue; 28562306a36Sopenharmony_ci return 1; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci return 0; 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci/* 29162306a36Sopenharmony_ci * real segment loading function, called from segment_load 29262306a36Sopenharmony_ci * Must return either an error code < 0, or the segment type code >= 0 29362306a36Sopenharmony_ci */ 29462306a36Sopenharmony_cistatic int 29562306a36Sopenharmony_ci__segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci unsigned long start_addr, end_addr, dummy; 29862306a36Sopenharmony_ci struct dcss_segment *seg; 29962306a36Sopenharmony_ci int rc, diag_cc, segtype; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci start_addr = end_addr = 0; 30262306a36Sopenharmony_ci segtype = -1; 30362306a36Sopenharmony_ci seg = kmalloc(sizeof(*seg), GFP_KERNEL | GFP_DMA); 30462306a36Sopenharmony_ci if (seg == NULL) { 30562306a36Sopenharmony_ci rc = -ENOMEM; 30662306a36Sopenharmony_ci goto out; 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci dcss_mkname (name, seg->dcss_name); 30962306a36Sopenharmony_ci rc = query_segment_type (seg); 31062306a36Sopenharmony_ci if (rc < 0) 31162306a36Sopenharmony_ci goto out_free; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (segment_overlaps_others(seg)) { 31462306a36Sopenharmony_ci rc = -EBUSY; 31562306a36Sopenharmony_ci goto out_free; 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL); 31962306a36Sopenharmony_ci if (seg->res == NULL) { 32062306a36Sopenharmony_ci rc = -ENOMEM; 32162306a36Sopenharmony_ci goto out_free; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM; 32462306a36Sopenharmony_ci seg->res->start = seg->start_addr; 32562306a36Sopenharmony_ci seg->res->end = seg->end; 32662306a36Sopenharmony_ci memcpy(&seg->res_name, seg->dcss_name, 8); 32762306a36Sopenharmony_ci EBCASC(seg->res_name, 8); 32862306a36Sopenharmony_ci seg->res_name[8] = '\0'; 32962306a36Sopenharmony_ci strlcat(seg->res_name, " (DCSS)", sizeof(seg->res_name)); 33062306a36Sopenharmony_ci seg->res->name = seg->res_name; 33162306a36Sopenharmony_ci segtype = seg->vm_segtype; 33262306a36Sopenharmony_ci if (segtype == SEG_TYPE_SC || 33362306a36Sopenharmony_ci ((segtype == SEG_TYPE_SR || segtype == SEG_TYPE_ER) && !do_nonshared)) 33462306a36Sopenharmony_ci seg->res->flags |= IORESOURCE_READONLY; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci /* Check for overlapping resources before adding the mapping. */ 33762306a36Sopenharmony_ci if (request_resource(&iomem_resource, seg->res)) { 33862306a36Sopenharmony_ci rc = -EBUSY; 33962306a36Sopenharmony_ci goto out_free_resource; 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1); 34362306a36Sopenharmony_ci if (rc) 34462306a36Sopenharmony_ci goto out_resource; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci if (do_nonshared) 34762306a36Sopenharmony_ci diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name, 34862306a36Sopenharmony_ci &start_addr, &end_addr); 34962306a36Sopenharmony_ci else 35062306a36Sopenharmony_ci diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name, 35162306a36Sopenharmony_ci &start_addr, &end_addr); 35262306a36Sopenharmony_ci if (diag_cc < 0) { 35362306a36Sopenharmony_ci dcss_diag(&purgeseg_scode, seg->dcss_name, 35462306a36Sopenharmony_ci &dummy, &dummy); 35562306a36Sopenharmony_ci rc = diag_cc; 35662306a36Sopenharmony_ci goto out_mapping; 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci if (diag_cc > 1) { 35962306a36Sopenharmony_ci pr_warn("Loading DCSS %s failed with rc=%ld\n", name, end_addr); 36062306a36Sopenharmony_ci rc = dcss_diag_translate_rc(end_addr); 36162306a36Sopenharmony_ci dcss_diag(&purgeseg_scode, seg->dcss_name, 36262306a36Sopenharmony_ci &dummy, &dummy); 36362306a36Sopenharmony_ci goto out_mapping; 36462306a36Sopenharmony_ci } 36562306a36Sopenharmony_ci seg->start_addr = start_addr; 36662306a36Sopenharmony_ci seg->end = end_addr; 36762306a36Sopenharmony_ci seg->do_nonshared = do_nonshared; 36862306a36Sopenharmony_ci refcount_set(&seg->ref_count, 1); 36962306a36Sopenharmony_ci list_add(&seg->list, &dcss_list); 37062306a36Sopenharmony_ci *addr = seg->start_addr; 37162306a36Sopenharmony_ci *end = seg->end; 37262306a36Sopenharmony_ci if (do_nonshared) 37362306a36Sopenharmony_ci pr_info("DCSS %s of range %px to %px and type %s loaded as " 37462306a36Sopenharmony_ci "exclusive-writable\n", name, (void*) seg->start_addr, 37562306a36Sopenharmony_ci (void*) seg->end, segtype_string[seg->vm_segtype]); 37662306a36Sopenharmony_ci else { 37762306a36Sopenharmony_ci pr_info("DCSS %s of range %px to %px and type %s loaded in " 37862306a36Sopenharmony_ci "shared access mode\n", name, (void*) seg->start_addr, 37962306a36Sopenharmony_ci (void*) seg->end, segtype_string[seg->vm_segtype]); 38062306a36Sopenharmony_ci } 38162306a36Sopenharmony_ci goto out; 38262306a36Sopenharmony_ci out_mapping: 38362306a36Sopenharmony_ci vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1); 38462306a36Sopenharmony_ci out_resource: 38562306a36Sopenharmony_ci release_resource(seg->res); 38662306a36Sopenharmony_ci out_free_resource: 38762306a36Sopenharmony_ci kfree(seg->res); 38862306a36Sopenharmony_ci out_free: 38962306a36Sopenharmony_ci kfree(seg); 39062306a36Sopenharmony_ci out: 39162306a36Sopenharmony_ci return rc < 0 ? rc : segtype; 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci/* 39562306a36Sopenharmony_ci * this function loads a DCSS segment 39662306a36Sopenharmony_ci * name : name of the DCSS 39762306a36Sopenharmony_ci * do_nonshared : 0 indicates that the dcss should be shared with other linux images 39862306a36Sopenharmony_ci * 1 indicates that the dcss should be exclusive for this linux image 39962306a36Sopenharmony_ci * addr : will be filled with start address of the segment 40062306a36Sopenharmony_ci * end : will be filled with end address of the segment 40162306a36Sopenharmony_ci * return values: 40262306a36Sopenharmony_ci * -ENOSYS : we are not running on VM 40362306a36Sopenharmony_ci * -EIO : could not perform query or load diagnose 40462306a36Sopenharmony_ci * -ENOENT : no such segment 40562306a36Sopenharmony_ci * -EOPNOTSUPP: multi-part segment cannot be used with linux 40662306a36Sopenharmony_ci * -EBUSY : segment cannot be used (overlaps with dcss or storage) 40762306a36Sopenharmony_ci * -ERANGE : segment cannot be used (exceeds kernel mapping range) 40862306a36Sopenharmony_ci * -EPERM : segment is currently loaded with incompatible permissions 40962306a36Sopenharmony_ci * -ENOMEM : out of memory 41062306a36Sopenharmony_ci * 0 .. 6 : type of segment as defined in include/asm-s390/extmem.h 41162306a36Sopenharmony_ci */ 41262306a36Sopenharmony_ciint 41362306a36Sopenharmony_cisegment_load (char *name, int do_nonshared, unsigned long *addr, 41462306a36Sopenharmony_ci unsigned long *end) 41562306a36Sopenharmony_ci{ 41662306a36Sopenharmony_ci struct dcss_segment *seg; 41762306a36Sopenharmony_ci int rc; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci if (!MACHINE_IS_VM) 42062306a36Sopenharmony_ci return -ENOSYS; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci mutex_lock(&dcss_lock); 42362306a36Sopenharmony_ci seg = segment_by_name (name); 42462306a36Sopenharmony_ci if (seg == NULL) 42562306a36Sopenharmony_ci rc = __segment_load (name, do_nonshared, addr, end); 42662306a36Sopenharmony_ci else { 42762306a36Sopenharmony_ci if (do_nonshared == seg->do_nonshared) { 42862306a36Sopenharmony_ci refcount_inc(&seg->ref_count); 42962306a36Sopenharmony_ci *addr = seg->start_addr; 43062306a36Sopenharmony_ci *end = seg->end; 43162306a36Sopenharmony_ci rc = seg->vm_segtype; 43262306a36Sopenharmony_ci } else { 43362306a36Sopenharmony_ci *addr = *end = 0; 43462306a36Sopenharmony_ci rc = -EPERM; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci mutex_unlock(&dcss_lock); 43862306a36Sopenharmony_ci return rc; 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci/* 44262306a36Sopenharmony_ci * this function modifies the shared state of a DCSS segment. note that 44362306a36Sopenharmony_ci * name : name of the DCSS 44462306a36Sopenharmony_ci * do_nonshared : 0 indicates that the dcss should be shared with other linux images 44562306a36Sopenharmony_ci * 1 indicates that the dcss should be exclusive for this linux image 44662306a36Sopenharmony_ci * return values: 44762306a36Sopenharmony_ci * -EIO : could not perform load diagnose (segment gone!) 44862306a36Sopenharmony_ci * -ENOENT : no such segment (segment gone!) 44962306a36Sopenharmony_ci * -EAGAIN : segment is in use by other exploiters, try later 45062306a36Sopenharmony_ci * -EINVAL : no segment with the given name is currently loaded - name invalid 45162306a36Sopenharmony_ci * -EBUSY : segment can temporarily not be used (overlaps with dcss) 45262306a36Sopenharmony_ci * 0 : operation succeeded 45362306a36Sopenharmony_ci */ 45462306a36Sopenharmony_ciint 45562306a36Sopenharmony_cisegment_modify_shared (char *name, int do_nonshared) 45662306a36Sopenharmony_ci{ 45762306a36Sopenharmony_ci struct dcss_segment *seg; 45862306a36Sopenharmony_ci unsigned long start_addr, end_addr, dummy; 45962306a36Sopenharmony_ci int rc, diag_cc; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci start_addr = end_addr = 0; 46262306a36Sopenharmony_ci mutex_lock(&dcss_lock); 46362306a36Sopenharmony_ci seg = segment_by_name (name); 46462306a36Sopenharmony_ci if (seg == NULL) { 46562306a36Sopenharmony_ci rc = -EINVAL; 46662306a36Sopenharmony_ci goto out_unlock; 46762306a36Sopenharmony_ci } 46862306a36Sopenharmony_ci if (do_nonshared == seg->do_nonshared) { 46962306a36Sopenharmony_ci pr_info("DCSS %s is already in the requested access " 47062306a36Sopenharmony_ci "mode\n", name); 47162306a36Sopenharmony_ci rc = 0; 47262306a36Sopenharmony_ci goto out_unlock; 47362306a36Sopenharmony_ci } 47462306a36Sopenharmony_ci if (refcount_read(&seg->ref_count) != 1) { 47562306a36Sopenharmony_ci pr_warn("DCSS %s is in use and cannot be reloaded\n", name); 47662306a36Sopenharmony_ci rc = -EAGAIN; 47762306a36Sopenharmony_ci goto out_unlock; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci release_resource(seg->res); 48062306a36Sopenharmony_ci if (do_nonshared) 48162306a36Sopenharmony_ci seg->res->flags &= ~IORESOURCE_READONLY; 48262306a36Sopenharmony_ci else 48362306a36Sopenharmony_ci if (seg->vm_segtype == SEG_TYPE_SR || 48462306a36Sopenharmony_ci seg->vm_segtype == SEG_TYPE_ER) 48562306a36Sopenharmony_ci seg->res->flags |= IORESOURCE_READONLY; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci if (request_resource(&iomem_resource, seg->res)) { 48862306a36Sopenharmony_ci pr_warn("DCSS %s overlaps with used memory resources and cannot be reloaded\n", 48962306a36Sopenharmony_ci name); 49062306a36Sopenharmony_ci rc = -EBUSY; 49162306a36Sopenharmony_ci kfree(seg->res); 49262306a36Sopenharmony_ci goto out_del_mem; 49362306a36Sopenharmony_ci } 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy); 49662306a36Sopenharmony_ci if (do_nonshared) 49762306a36Sopenharmony_ci diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name, 49862306a36Sopenharmony_ci &start_addr, &end_addr); 49962306a36Sopenharmony_ci else 50062306a36Sopenharmony_ci diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name, 50162306a36Sopenharmony_ci &start_addr, &end_addr); 50262306a36Sopenharmony_ci if (diag_cc < 0) { 50362306a36Sopenharmony_ci rc = diag_cc; 50462306a36Sopenharmony_ci goto out_del_res; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci if (diag_cc > 1) { 50762306a36Sopenharmony_ci pr_warn("Reloading DCSS %s failed with rc=%ld\n", 50862306a36Sopenharmony_ci name, end_addr); 50962306a36Sopenharmony_ci rc = dcss_diag_translate_rc(end_addr); 51062306a36Sopenharmony_ci goto out_del_res; 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci seg->start_addr = start_addr; 51362306a36Sopenharmony_ci seg->end = end_addr; 51462306a36Sopenharmony_ci seg->do_nonshared = do_nonshared; 51562306a36Sopenharmony_ci rc = 0; 51662306a36Sopenharmony_ci goto out_unlock; 51762306a36Sopenharmony_ci out_del_res: 51862306a36Sopenharmony_ci release_resource(seg->res); 51962306a36Sopenharmony_ci kfree(seg->res); 52062306a36Sopenharmony_ci out_del_mem: 52162306a36Sopenharmony_ci vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1); 52262306a36Sopenharmony_ci list_del(&seg->list); 52362306a36Sopenharmony_ci dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy); 52462306a36Sopenharmony_ci kfree(seg); 52562306a36Sopenharmony_ci out_unlock: 52662306a36Sopenharmony_ci mutex_unlock(&dcss_lock); 52762306a36Sopenharmony_ci return rc; 52862306a36Sopenharmony_ci} 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci/* 53162306a36Sopenharmony_ci * Decrease the use count of a DCSS segment and remove 53262306a36Sopenharmony_ci * it from the address space if nobody is using it 53362306a36Sopenharmony_ci * any longer. 53462306a36Sopenharmony_ci */ 53562306a36Sopenharmony_civoid 53662306a36Sopenharmony_cisegment_unload(char *name) 53762306a36Sopenharmony_ci{ 53862306a36Sopenharmony_ci unsigned long dummy; 53962306a36Sopenharmony_ci struct dcss_segment *seg; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci if (!MACHINE_IS_VM) 54262306a36Sopenharmony_ci return; 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci mutex_lock(&dcss_lock); 54562306a36Sopenharmony_ci seg = segment_by_name (name); 54662306a36Sopenharmony_ci if (seg == NULL) { 54762306a36Sopenharmony_ci pr_err("Unloading unknown DCSS %s failed\n", name); 54862306a36Sopenharmony_ci goto out_unlock; 54962306a36Sopenharmony_ci } 55062306a36Sopenharmony_ci if (!refcount_dec_and_test(&seg->ref_count)) 55162306a36Sopenharmony_ci goto out_unlock; 55262306a36Sopenharmony_ci release_resource(seg->res); 55362306a36Sopenharmony_ci kfree(seg->res); 55462306a36Sopenharmony_ci vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1); 55562306a36Sopenharmony_ci list_del(&seg->list); 55662306a36Sopenharmony_ci dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy); 55762306a36Sopenharmony_ci kfree(seg); 55862306a36Sopenharmony_ciout_unlock: 55962306a36Sopenharmony_ci mutex_unlock(&dcss_lock); 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci/* 56362306a36Sopenharmony_ci * save segment content permanently 56462306a36Sopenharmony_ci */ 56562306a36Sopenharmony_civoid 56662306a36Sopenharmony_cisegment_save(char *name) 56762306a36Sopenharmony_ci{ 56862306a36Sopenharmony_ci struct dcss_segment *seg; 56962306a36Sopenharmony_ci char cmd1[160]; 57062306a36Sopenharmony_ci char cmd2[80]; 57162306a36Sopenharmony_ci int i, response; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci if (!MACHINE_IS_VM) 57462306a36Sopenharmony_ci return; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci mutex_lock(&dcss_lock); 57762306a36Sopenharmony_ci seg = segment_by_name (name); 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci if (seg == NULL) { 58062306a36Sopenharmony_ci pr_err("Saving unknown DCSS %s failed\n", name); 58162306a36Sopenharmony_ci goto out; 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci sprintf(cmd1, "DEFSEG %s", name); 58562306a36Sopenharmony_ci for (i=0; i<seg->segcnt; i++) { 58662306a36Sopenharmony_ci sprintf(cmd1+strlen(cmd1), " %lX-%lX %s", 58762306a36Sopenharmony_ci seg->range[i].start >> PAGE_SHIFT, 58862306a36Sopenharmony_ci seg->range[i].end >> PAGE_SHIFT, 58962306a36Sopenharmony_ci segtype_string[seg->range[i].start & 0xff]); 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci sprintf(cmd2, "SAVESEG %s", name); 59262306a36Sopenharmony_ci response = 0; 59362306a36Sopenharmony_ci cpcmd(cmd1, NULL, 0, &response); 59462306a36Sopenharmony_ci if (response) { 59562306a36Sopenharmony_ci pr_err("Saving a DCSS failed with DEFSEG response code " 59662306a36Sopenharmony_ci "%i\n", response); 59762306a36Sopenharmony_ci goto out; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci cpcmd(cmd2, NULL, 0, &response); 60062306a36Sopenharmony_ci if (response) { 60162306a36Sopenharmony_ci pr_err("Saving a DCSS failed with SAVESEG response code " 60262306a36Sopenharmony_ci "%i\n", response); 60362306a36Sopenharmony_ci goto out; 60462306a36Sopenharmony_ci } 60562306a36Sopenharmony_ciout: 60662306a36Sopenharmony_ci mutex_unlock(&dcss_lock); 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci/* 61062306a36Sopenharmony_ci * print appropriate error message for segment_load()/segment_type() 61162306a36Sopenharmony_ci * return code 61262306a36Sopenharmony_ci */ 61362306a36Sopenharmony_civoid segment_warning(int rc, char *seg_name) 61462306a36Sopenharmony_ci{ 61562306a36Sopenharmony_ci switch (rc) { 61662306a36Sopenharmony_ci case -ENOENT: 61762306a36Sopenharmony_ci pr_err("DCSS %s cannot be loaded or queried\n", seg_name); 61862306a36Sopenharmony_ci break; 61962306a36Sopenharmony_ci case -ENOSYS: 62062306a36Sopenharmony_ci pr_err("DCSS %s cannot be loaded or queried without " 62162306a36Sopenharmony_ci "z/VM\n", seg_name); 62262306a36Sopenharmony_ci break; 62362306a36Sopenharmony_ci case -EIO: 62462306a36Sopenharmony_ci pr_err("Loading or querying DCSS %s resulted in a " 62562306a36Sopenharmony_ci "hardware error\n", seg_name); 62662306a36Sopenharmony_ci break; 62762306a36Sopenharmony_ci case -EOPNOTSUPP: 62862306a36Sopenharmony_ci pr_err("DCSS %s has multiple page ranges and cannot be " 62962306a36Sopenharmony_ci "loaded or queried\n", seg_name); 63062306a36Sopenharmony_ci break; 63162306a36Sopenharmony_ci case -EBUSY: 63262306a36Sopenharmony_ci pr_err("%s needs used memory resources and cannot be " 63362306a36Sopenharmony_ci "loaded or queried\n", seg_name); 63462306a36Sopenharmony_ci break; 63562306a36Sopenharmony_ci case -EPERM: 63662306a36Sopenharmony_ci pr_err("DCSS %s is already loaded in a different access " 63762306a36Sopenharmony_ci "mode\n", seg_name); 63862306a36Sopenharmony_ci break; 63962306a36Sopenharmony_ci case -ENOMEM: 64062306a36Sopenharmony_ci pr_err("There is not enough memory to load or query " 64162306a36Sopenharmony_ci "DCSS %s\n", seg_name); 64262306a36Sopenharmony_ci break; 64362306a36Sopenharmony_ci case -ERANGE: { 64462306a36Sopenharmony_ci struct range mhp_range = arch_get_mappable_range(); 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci pr_err("DCSS %s exceeds the kernel mapping range (%llu) " 64762306a36Sopenharmony_ci "and cannot be loaded\n", seg_name, mhp_range.end + 1); 64862306a36Sopenharmony_ci break; 64962306a36Sopenharmony_ci } 65062306a36Sopenharmony_ci default: 65162306a36Sopenharmony_ci break; 65262306a36Sopenharmony_ci } 65362306a36Sopenharmony_ci} 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ciEXPORT_SYMBOL(segment_load); 65662306a36Sopenharmony_ciEXPORT_SYMBOL(segment_unload); 65762306a36Sopenharmony_ciEXPORT_SYMBOL(segment_save); 65862306a36Sopenharmony_ciEXPORT_SYMBOL(segment_type); 65962306a36Sopenharmony_ciEXPORT_SYMBOL(segment_modify_shared); 66062306a36Sopenharmony_ciEXPORT_SYMBOL(segment_warning); 661