cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

extmem.c (16041B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      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 * Copyright IBM Corp. 2002, 2004
      8 */
      9
     10#define KMSG_COMPONENT "extmem"
     11#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
     12
     13#include <linux/kernel.h>
     14#include <linux/string.h>
     15#include <linux/spinlock.h>
     16#include <linux/list.h>
     17#include <linux/slab.h>
     18#include <linux/export.h>
     19#include <linux/memblock.h>
     20#include <linux/ctype.h>
     21#include <linux/ioport.h>
     22#include <linux/refcount.h>
     23#include <linux/pgtable.h>
     24#include <asm/diag.h>
     25#include <asm/page.h>
     26#include <asm/ebcdic.h>
     27#include <asm/errno.h>
     28#include <asm/extmem.h>
     29#include <asm/cpcmd.h>
     30#include <asm/setup.h>
     31
     32#define DCSS_PURGESEG   0x08
     33#define DCSS_LOADSHRX	0x20
     34#define DCSS_LOADNSRX	0x24
     35#define DCSS_FINDSEGX	0x2c
     36#define DCSS_SEGEXTX	0x38
     37#define DCSS_FINDSEGA   0x0c
     38
     39struct qrange {
     40	unsigned long  start; /* last byte type */
     41	unsigned long  end;   /* last byte reserved */
     42};
     43
     44struct qout64 {
     45	unsigned long segstart;
     46	unsigned long segend;
     47	int segcnt;
     48	int segrcnt;
     49	struct qrange range[6];
     50};
     51
     52struct qin64 {
     53	char qopcode;
     54	char rsrv1[3];
     55	char qrcode;
     56	char rsrv2[3];
     57	char qname[8];
     58	unsigned int qoutptr;
     59	short int qoutlen;
     60};
     61
     62struct dcss_segment {
     63	struct list_head list;
     64	char dcss_name[8];
     65	char res_name[16];
     66	unsigned long start_addr;
     67	unsigned long end;
     68	refcount_t ref_count;
     69	int do_nonshared;
     70	unsigned int vm_segtype;
     71	struct qrange range[6];
     72	int segcnt;
     73	struct resource *res;
     74};
     75
     76static DEFINE_MUTEX(dcss_lock);
     77static LIST_HEAD(dcss_list);
     78static char *segtype_string[] = { "SW", "EW", "SR", "ER", "SN", "EN", "SC",
     79					"EW/EN-MIXED" };
     80static int loadshr_scode = DCSS_LOADSHRX;
     81static int loadnsr_scode = DCSS_LOADNSRX;
     82static int purgeseg_scode = DCSS_PURGESEG;
     83static int segext_scode = DCSS_SEGEXTX;
     84
     85/*
     86 * Create the 8 bytes, ebcdic VM segment name from
     87 * an ascii name.
     88 */
     89static void
     90dcss_mkname(char *name, char *dcss_name)
     91{
     92	int i;
     93
     94	for (i = 0; i < 8; i++) {
     95		if (name[i] == '\0')
     96			break;
     97		dcss_name[i] = toupper(name[i]);
     98	}
     99	for (; i < 8; i++)
    100		dcss_name[i] = ' ';
    101	ASCEBC(dcss_name, 8);
    102}
    103
    104
    105/*
    106 * search all segments in dcss_list, and return the one
    107 * namend *name. If not found, return NULL.
    108 */
    109static struct dcss_segment *
    110segment_by_name (char *name)
    111{
    112	char dcss_name[9];
    113	struct list_head *l;
    114	struct dcss_segment *tmp, *retval = NULL;
    115
    116	BUG_ON(!mutex_is_locked(&dcss_lock));
    117	dcss_mkname (name, dcss_name);
    118	list_for_each (l, &dcss_list) {
    119		tmp = list_entry (l, struct dcss_segment, list);
    120		if (memcmp(tmp->dcss_name, dcss_name, 8) == 0) {
    121			retval = tmp;
    122			break;
    123		}
    124	}
    125	return retval;
    126}
    127
    128
    129/*
    130 * Perform a function on a dcss segment.
    131 */
    132static inline int
    133dcss_diag(int *func, void *parameter,
    134           unsigned long *ret1, unsigned long *ret2)
    135{
    136	unsigned long rx, ry;
    137	int rc;
    138
    139	rx = (unsigned long) parameter;
    140	ry = (unsigned long) *func;
    141
    142	diag_stat_inc(DIAG_STAT_X064);
    143	asm volatile(
    144		"	diag	%0,%1,0x64\n"
    145		"	ipm	%2\n"
    146		"	srl	%2,28\n"
    147		: "+d" (rx), "+d" (ry), "=d" (rc) : : "cc");
    148	*ret1 = rx;
    149	*ret2 = ry;
    150	return rc;
    151}
    152
    153static inline int
    154dcss_diag_translate_rc (int vm_rc) {
    155	if (vm_rc == 44)
    156		return -ENOENT;
    157	return -EIO;
    158}
    159
    160
    161/* do a diag to get info about a segment.
    162 * fills start_address, end and vm_segtype fields
    163 */
    164static int
    165query_segment_type (struct dcss_segment *seg)
    166{
    167	unsigned long dummy, vmrc;
    168	int diag_cc, rc, i;
    169	struct qout64 *qout;
    170	struct qin64 *qin;
    171
    172	qin = kmalloc(sizeof(*qin), GFP_KERNEL | GFP_DMA);
    173	qout = kmalloc(sizeof(*qout), GFP_KERNEL | GFP_DMA);
    174	if ((qin == NULL) || (qout == NULL)) {
    175		rc = -ENOMEM;
    176		goto out_free;
    177	}
    178
    179	/* initialize diag input parameters */
    180	qin->qopcode = DCSS_FINDSEGA;
    181	qin->qoutptr = (unsigned long) qout;
    182	qin->qoutlen = sizeof(struct qout64);
    183	memcpy (qin->qname, seg->dcss_name, 8);
    184
    185	diag_cc = dcss_diag(&segext_scode, qin, &dummy, &vmrc);
    186
    187	if (diag_cc < 0) {
    188		rc = diag_cc;
    189		goto out_free;
    190	}
    191	if (diag_cc > 1) {
    192		pr_warn("Querying a DCSS type failed with rc=%ld\n", vmrc);
    193		rc = dcss_diag_translate_rc (vmrc);
    194		goto out_free;
    195	}
    196
    197	if (qout->segcnt > 6) {
    198		rc = -EOPNOTSUPP;
    199		goto out_free;
    200	}
    201
    202	if (qout->segcnt == 1) {
    203		seg->vm_segtype = qout->range[0].start & 0xff;
    204	} else {
    205		/* multi-part segment. only one type supported here:
    206		    - all parts are contiguous
    207		    - all parts are either EW or EN type
    208		    - maximum 6 parts allowed */
    209		unsigned long start = qout->segstart >> PAGE_SHIFT;
    210		for (i=0; i<qout->segcnt; i++) {
    211			if (((qout->range[i].start & 0xff) != SEG_TYPE_EW) &&
    212			    ((qout->range[i].start & 0xff) != SEG_TYPE_EN)) {
    213				rc = -EOPNOTSUPP;
    214				goto out_free;
    215			}
    216			if (start != qout->range[i].start >> PAGE_SHIFT) {
    217				rc = -EOPNOTSUPP;
    218				goto out_free;
    219			}
    220			start = (qout->range[i].end >> PAGE_SHIFT) + 1;
    221		}
    222		seg->vm_segtype = SEG_TYPE_EWEN;
    223	}
    224
    225	/* analyze diag output and update seg */
    226	seg->start_addr = qout->segstart;
    227	seg->end = qout->segend;
    228
    229	memcpy (seg->range, qout->range, 6*sizeof(struct qrange));
    230	seg->segcnt = qout->segcnt;
    231
    232	rc = 0;
    233
    234 out_free:
    235	kfree(qin);
    236	kfree(qout);
    237	return rc;
    238}
    239
    240/*
    241 * get info about a segment
    242 * possible return values:
    243 * -ENOSYS  : we are not running on VM
    244 * -EIO     : could not perform query diagnose
    245 * -ENOENT  : no such segment
    246 * -EOPNOTSUPP: multi-part segment cannot be used with linux
    247 * -ENOMEM  : out of memory
    248 * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
    249 */
    250int
    251segment_type (char* name)
    252{
    253	int rc;
    254	struct dcss_segment seg;
    255
    256	if (!MACHINE_IS_VM)
    257		return -ENOSYS;
    258
    259	dcss_mkname(name, seg.dcss_name);
    260	rc = query_segment_type (&seg);
    261	if (rc < 0)
    262		return rc;
    263	return seg.vm_segtype;
    264}
    265
    266/*
    267 * check if segment collides with other segments that are currently loaded
    268 * returns 1 if this is the case, 0 if no collision was found
    269 */
    270static int
    271segment_overlaps_others (struct dcss_segment *seg)
    272{
    273	struct list_head *l;
    274	struct dcss_segment *tmp;
    275
    276	BUG_ON(!mutex_is_locked(&dcss_lock));
    277	list_for_each(l, &dcss_list) {
    278		tmp = list_entry(l, struct dcss_segment, list);
    279		if ((tmp->start_addr >> 20) > (seg->end >> 20))
    280			continue;
    281		if ((tmp->end >> 20) < (seg->start_addr >> 20))
    282			continue;
    283		if (seg == tmp)
    284			continue;
    285		return 1;
    286	}
    287	return 0;
    288}
    289
    290/*
    291 * real segment loading function, called from segment_load
    292 */
    293static int
    294__segment_load (char *name, int do_nonshared, unsigned long *addr, unsigned long *end)
    295{
    296	unsigned long start_addr, end_addr, dummy;
    297	struct dcss_segment *seg;
    298	int rc, diag_cc;
    299
    300	start_addr = end_addr = 0;
    301	seg = kmalloc(sizeof(*seg), GFP_KERNEL | GFP_DMA);
    302	if (seg == NULL) {
    303		rc = -ENOMEM;
    304		goto out;
    305	}
    306	dcss_mkname (name, seg->dcss_name);
    307	rc = query_segment_type (seg);
    308	if (rc < 0)
    309		goto out_free;
    310
    311	if (segment_overlaps_others(seg)) {
    312		rc = -EBUSY;
    313		goto out_free;
    314	}
    315
    316	seg->res = kzalloc(sizeof(struct resource), GFP_KERNEL);
    317	if (seg->res == NULL) {
    318		rc = -ENOMEM;
    319		goto out_free;
    320	}
    321	seg->res->flags = IORESOURCE_BUSY | IORESOURCE_MEM;
    322	seg->res->start = seg->start_addr;
    323	seg->res->end = seg->end;
    324	memcpy(&seg->res_name, seg->dcss_name, 8);
    325	EBCASC(seg->res_name, 8);
    326	seg->res_name[8] = '\0';
    327	strlcat(seg->res_name, " (DCSS)", sizeof(seg->res_name));
    328	seg->res->name = seg->res_name;
    329	rc = seg->vm_segtype;
    330	if (rc == SEG_TYPE_SC ||
    331	    ((rc == SEG_TYPE_SR || rc == SEG_TYPE_ER) && !do_nonshared))
    332		seg->res->flags |= IORESOURCE_READONLY;
    333
    334	/* Check for overlapping resources before adding the mapping. */
    335	if (request_resource(&iomem_resource, seg->res)) {
    336		rc = -EBUSY;
    337		goto out_free_resource;
    338	}
    339
    340	rc = vmem_add_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
    341	if (rc)
    342		goto out_resource;
    343
    344	if (do_nonshared)
    345		diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
    346				&start_addr, &end_addr);
    347	else
    348		diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
    349				&start_addr, &end_addr);
    350	if (diag_cc < 0) {
    351		dcss_diag(&purgeseg_scode, seg->dcss_name,
    352				&dummy, &dummy);
    353		rc = diag_cc;
    354		goto out_mapping;
    355	}
    356	if (diag_cc > 1) {
    357		pr_warn("Loading DCSS %s failed with rc=%ld\n", name, end_addr);
    358		rc = dcss_diag_translate_rc(end_addr);
    359		dcss_diag(&purgeseg_scode, seg->dcss_name,
    360				&dummy, &dummy);
    361		goto out_mapping;
    362	}
    363	seg->start_addr = start_addr;
    364	seg->end = end_addr;
    365	seg->do_nonshared = do_nonshared;
    366	refcount_set(&seg->ref_count, 1);
    367	list_add(&seg->list, &dcss_list);
    368	*addr = seg->start_addr;
    369	*end  = seg->end;
    370	if (do_nonshared)
    371		pr_info("DCSS %s of range %px to %px and type %s loaded as "
    372			"exclusive-writable\n", name, (void*) seg->start_addr,
    373			(void*) seg->end, segtype_string[seg->vm_segtype]);
    374	else {
    375		pr_info("DCSS %s of range %px to %px and type %s loaded in "
    376			"shared access mode\n", name, (void*) seg->start_addr,
    377			(void*) seg->end, segtype_string[seg->vm_segtype]);
    378	}
    379	goto out;
    380 out_mapping:
    381	vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
    382 out_resource:
    383	release_resource(seg->res);
    384 out_free_resource:
    385	kfree(seg->res);
    386 out_free:
    387	kfree(seg);
    388 out:
    389	return rc;
    390}
    391
    392/*
    393 * this function loads a DCSS segment
    394 * name         : name of the DCSS
    395 * do_nonshared : 0 indicates that the dcss should be shared with other linux images
    396 *                1 indicates that the dcss should be exclusive for this linux image
    397 * addr         : will be filled with start address of the segment
    398 * end          : will be filled with end address of the segment
    399 * return values:
    400 * -ENOSYS  : we are not running on VM
    401 * -EIO     : could not perform query or load diagnose
    402 * -ENOENT  : no such segment
    403 * -EOPNOTSUPP: multi-part segment cannot be used with linux
    404 * -EBUSY   : segment cannot be used (overlaps with dcss or storage)
    405 * -ERANGE  : segment cannot be used (exceeds kernel mapping range)
    406 * -EPERM   : segment is currently loaded with incompatible permissions
    407 * -ENOMEM  : out of memory
    408 * 0 .. 6   : type of segment as defined in include/asm-s390/extmem.h
    409 */
    410int
    411segment_load (char *name, int do_nonshared, unsigned long *addr,
    412		unsigned long *end)
    413{
    414	struct dcss_segment *seg;
    415	int rc;
    416
    417	if (!MACHINE_IS_VM)
    418		return -ENOSYS;
    419
    420	mutex_lock(&dcss_lock);
    421	seg = segment_by_name (name);
    422	if (seg == NULL)
    423		rc = __segment_load (name, do_nonshared, addr, end);
    424	else {
    425		if (do_nonshared == seg->do_nonshared) {
    426			refcount_inc(&seg->ref_count);
    427			*addr = seg->start_addr;
    428			*end  = seg->end;
    429			rc    = seg->vm_segtype;
    430		} else {
    431			*addr = *end = 0;
    432			rc    = -EPERM;
    433		}
    434	}
    435	mutex_unlock(&dcss_lock);
    436	return rc;
    437}
    438
    439/*
    440 * this function modifies the shared state of a DCSS segment. note that
    441 * name         : name of the DCSS
    442 * do_nonshared : 0 indicates that the dcss should be shared with other linux images
    443 *                1 indicates that the dcss should be exclusive for this linux image
    444 * return values:
    445 * -EIO     : could not perform load diagnose (segment gone!)
    446 * -ENOENT  : no such segment (segment gone!)
    447 * -EAGAIN  : segment is in use by other exploiters, try later
    448 * -EINVAL  : no segment with the given name is currently loaded - name invalid
    449 * -EBUSY   : segment can temporarily not be used (overlaps with dcss)
    450 * 0	    : operation succeeded
    451 */
    452int
    453segment_modify_shared (char *name, int do_nonshared)
    454{
    455	struct dcss_segment *seg;
    456	unsigned long start_addr, end_addr, dummy;
    457	int rc, diag_cc;
    458
    459	start_addr = end_addr = 0;
    460	mutex_lock(&dcss_lock);
    461	seg = segment_by_name (name);
    462	if (seg == NULL) {
    463		rc = -EINVAL;
    464		goto out_unlock;
    465	}
    466	if (do_nonshared == seg->do_nonshared) {
    467		pr_info("DCSS %s is already in the requested access "
    468			"mode\n", name);
    469		rc = 0;
    470		goto out_unlock;
    471	}
    472	if (refcount_read(&seg->ref_count) != 1) {
    473		pr_warn("DCSS %s is in use and cannot be reloaded\n", name);
    474		rc = -EAGAIN;
    475		goto out_unlock;
    476	}
    477	release_resource(seg->res);
    478	if (do_nonshared)
    479		seg->res->flags &= ~IORESOURCE_READONLY;
    480	else
    481		if (seg->vm_segtype == SEG_TYPE_SR ||
    482		    seg->vm_segtype == SEG_TYPE_ER)
    483			seg->res->flags |= IORESOURCE_READONLY;
    484
    485	if (request_resource(&iomem_resource, seg->res)) {
    486		pr_warn("DCSS %s overlaps with used memory resources and cannot be reloaded\n",
    487			name);
    488		rc = -EBUSY;
    489		kfree(seg->res);
    490		goto out_del_mem;
    491	}
    492
    493	dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
    494	if (do_nonshared)
    495		diag_cc = dcss_diag(&loadnsr_scode, seg->dcss_name,
    496				&start_addr, &end_addr);
    497	else
    498		diag_cc = dcss_diag(&loadshr_scode, seg->dcss_name,
    499				&start_addr, &end_addr);
    500	if (diag_cc < 0) {
    501		rc = diag_cc;
    502		goto out_del_res;
    503	}
    504	if (diag_cc > 1) {
    505		pr_warn("Reloading DCSS %s failed with rc=%ld\n",
    506			name, end_addr);
    507		rc = dcss_diag_translate_rc(end_addr);
    508		goto out_del_res;
    509	}
    510	seg->start_addr = start_addr;
    511	seg->end = end_addr;
    512	seg->do_nonshared = do_nonshared;
    513	rc = 0;
    514	goto out_unlock;
    515 out_del_res:
    516	release_resource(seg->res);
    517	kfree(seg->res);
    518 out_del_mem:
    519	vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
    520	list_del(&seg->list);
    521	dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
    522	kfree(seg);
    523 out_unlock:
    524	mutex_unlock(&dcss_lock);
    525	return rc;
    526}
    527
    528/*
    529 * Decrease the use count of a DCSS segment and remove
    530 * it from the address space if nobody is using it
    531 * any longer.
    532 */
    533void
    534segment_unload(char *name)
    535{
    536	unsigned long dummy;
    537	struct dcss_segment *seg;
    538
    539	if (!MACHINE_IS_VM)
    540		return;
    541
    542	mutex_lock(&dcss_lock);
    543	seg = segment_by_name (name);
    544	if (seg == NULL) {
    545		pr_err("Unloading unknown DCSS %s failed\n", name);
    546		goto out_unlock;
    547	}
    548	if (!refcount_dec_and_test(&seg->ref_count))
    549		goto out_unlock;
    550	release_resource(seg->res);
    551	kfree(seg->res);
    552	vmem_remove_mapping(seg->start_addr, seg->end - seg->start_addr + 1);
    553	list_del(&seg->list);
    554	dcss_diag(&purgeseg_scode, seg->dcss_name, &dummy, &dummy);
    555	kfree(seg);
    556out_unlock:
    557	mutex_unlock(&dcss_lock);
    558}
    559
    560/*
    561 * save segment content permanently
    562 */
    563void
    564segment_save(char *name)
    565{
    566	struct dcss_segment *seg;
    567	char cmd1[160];
    568	char cmd2[80];
    569	int i, response;
    570
    571	if (!MACHINE_IS_VM)
    572		return;
    573
    574	mutex_lock(&dcss_lock);
    575	seg = segment_by_name (name);
    576
    577	if (seg == NULL) {
    578		pr_err("Saving unknown DCSS %s failed\n", name);
    579		goto out;
    580	}
    581
    582	sprintf(cmd1, "DEFSEG %s", name);
    583	for (i=0; i<seg->segcnt; i++) {
    584		sprintf(cmd1+strlen(cmd1), " %lX-%lX %s",
    585			seg->range[i].start >> PAGE_SHIFT,
    586			seg->range[i].end >> PAGE_SHIFT,
    587			segtype_string[seg->range[i].start & 0xff]);
    588	}
    589	sprintf(cmd2, "SAVESEG %s", name);
    590	response = 0;
    591	cpcmd(cmd1, NULL, 0, &response);
    592	if (response) {
    593		pr_err("Saving a DCSS failed with DEFSEG response code "
    594		       "%i\n", response);
    595		goto out;
    596	}
    597	cpcmd(cmd2, NULL, 0, &response);
    598	if (response) {
    599		pr_err("Saving a DCSS failed with SAVESEG response code "
    600		       "%i\n", response);
    601		goto out;
    602	}
    603out:
    604	mutex_unlock(&dcss_lock);
    605}
    606
    607/*
    608 * print appropriate error message for segment_load()/segment_type()
    609 * return code
    610 */
    611void segment_warning(int rc, char *seg_name)
    612{
    613	switch (rc) {
    614	case -ENOENT:
    615		pr_err("DCSS %s cannot be loaded or queried\n", seg_name);
    616		break;
    617	case -ENOSYS:
    618		pr_err("DCSS %s cannot be loaded or queried without "
    619		       "z/VM\n", seg_name);
    620		break;
    621	case -EIO:
    622		pr_err("Loading or querying DCSS %s resulted in a "
    623		       "hardware error\n", seg_name);
    624		break;
    625	case -EOPNOTSUPP:
    626		pr_err("DCSS %s has multiple page ranges and cannot be "
    627		       "loaded or queried\n", seg_name);
    628		break;
    629	case -EBUSY:
    630		pr_err("%s needs used memory resources and cannot be "
    631		       "loaded or queried\n", seg_name);
    632		break;
    633	case -EPERM:
    634		pr_err("DCSS %s is already loaded in a different access "
    635		       "mode\n", seg_name);
    636		break;
    637	case -ENOMEM:
    638		pr_err("There is not enough memory to load or query "
    639		       "DCSS %s\n", seg_name);
    640		break;
    641	case -ERANGE:
    642		pr_err("DCSS %s exceeds the kernel mapping range (%lu) "
    643		       "and cannot be loaded\n", seg_name, VMEM_MAX_PHYS);
    644		break;
    645	default:
    646		break;
    647	}
    648}
    649
    650EXPORT_SYMBOL(segment_load);
    651EXPORT_SYMBOL(segment_unload);
    652EXPORT_SYMBOL(segment_save);
    653EXPORT_SYMBOL(segment_type);
    654EXPORT_SYMBOL(segment_modify_shared);
    655EXPORT_SYMBOL(segment_warning);