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