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

cmm.c (9840B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 *  Collaborative memory management interface.
      4 *
      5 *    Copyright IBM Corp 2003, 2010
      6 *    Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>,
      7 *
      8 */
      9
     10#include <linux/errno.h>
     11#include <linux/fs.h>
     12#include <linux/init.h>
     13#include <linux/module.h>
     14#include <linux/moduleparam.h>
     15#include <linux/gfp.h>
     16#include <linux/sched.h>
     17#include <linux/string_helpers.h>
     18#include <linux/sysctl.h>
     19#include <linux/swap.h>
     20#include <linux/kthread.h>
     21#include <linux/oom.h>
     22#include <linux/uaccess.h>
     23
     24#include <asm/diag.h>
     25
     26#ifdef CONFIG_CMM_IUCV
     27static char *cmm_default_sender = "VMRMSVM";
     28#endif
     29static char *sender;
     30module_param(sender, charp, 0400);
     31MODULE_PARM_DESC(sender,
     32		 "Guest name that may send SMSG messages (default VMRMSVM)");
     33
     34#include "../../../drivers/s390/net/smsgiucv.h"
     35
     36#define CMM_NR_PAGES ((PAGE_SIZE / sizeof(unsigned long)) - 2)
     37
     38struct cmm_page_array {
     39	struct cmm_page_array *next;
     40	unsigned long index;
     41	unsigned long pages[CMM_NR_PAGES];
     42};
     43
     44static long cmm_pages;
     45static long cmm_timed_pages;
     46static volatile long cmm_pages_target;
     47static volatile long cmm_timed_pages_target;
     48static long cmm_timeout_pages;
     49static long cmm_timeout_seconds;
     50
     51static struct cmm_page_array *cmm_page_list;
     52static struct cmm_page_array *cmm_timed_page_list;
     53static DEFINE_SPINLOCK(cmm_lock);
     54
     55static struct task_struct *cmm_thread_ptr;
     56static DECLARE_WAIT_QUEUE_HEAD(cmm_thread_wait);
     57
     58static void cmm_timer_fn(struct timer_list *);
     59static void cmm_set_timer(void);
     60static DEFINE_TIMER(cmm_timer, cmm_timer_fn);
     61
     62static long cmm_alloc_pages(long nr, long *counter,
     63			    struct cmm_page_array **list)
     64{
     65	struct cmm_page_array *pa, *npa;
     66	unsigned long addr;
     67
     68	while (nr) {
     69		addr = __get_free_page(GFP_NOIO);
     70		if (!addr)
     71			break;
     72		spin_lock(&cmm_lock);
     73		pa = *list;
     74		if (!pa || pa->index >= CMM_NR_PAGES) {
     75			/* Need a new page for the page list. */
     76			spin_unlock(&cmm_lock);
     77			npa = (struct cmm_page_array *)
     78				__get_free_page(GFP_NOIO);
     79			if (!npa) {
     80				free_page(addr);
     81				break;
     82			}
     83			spin_lock(&cmm_lock);
     84			pa = *list;
     85			if (!pa || pa->index >= CMM_NR_PAGES) {
     86				npa->next = pa;
     87				npa->index = 0;
     88				pa = npa;
     89				*list = pa;
     90			} else
     91				free_page((unsigned long) npa);
     92		}
     93		diag10_range(virt_to_pfn(addr), 1);
     94		pa->pages[pa->index++] = addr;
     95		(*counter)++;
     96		spin_unlock(&cmm_lock);
     97		nr--;
     98	}
     99	return nr;
    100}
    101
    102static long cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
    103{
    104	struct cmm_page_array *pa;
    105	unsigned long addr;
    106
    107	spin_lock(&cmm_lock);
    108	pa = *list;
    109	while (nr) {
    110		if (!pa || pa->index <= 0)
    111			break;
    112		addr = pa->pages[--pa->index];
    113		if (pa->index == 0) {
    114			pa = pa->next;
    115			free_page((unsigned long) *list);
    116			*list = pa;
    117		}
    118		free_page(addr);
    119		(*counter)--;
    120		nr--;
    121	}
    122	spin_unlock(&cmm_lock);
    123	return nr;
    124}
    125
    126static int cmm_oom_notify(struct notifier_block *self,
    127			  unsigned long dummy, void *parm)
    128{
    129	unsigned long *freed = parm;
    130	long nr = 256;
    131
    132	nr = cmm_free_pages(nr, &cmm_timed_pages, &cmm_timed_page_list);
    133	if (nr > 0)
    134		nr = cmm_free_pages(nr, &cmm_pages, &cmm_page_list);
    135	cmm_pages_target = cmm_pages;
    136	cmm_timed_pages_target = cmm_timed_pages;
    137	*freed += 256 - nr;
    138	return NOTIFY_OK;
    139}
    140
    141static struct notifier_block cmm_oom_nb = {
    142	.notifier_call = cmm_oom_notify,
    143};
    144
    145static int cmm_thread(void *dummy)
    146{
    147	int rc;
    148
    149	while (1) {
    150		rc = wait_event_interruptible(cmm_thread_wait,
    151			cmm_pages != cmm_pages_target ||
    152			cmm_timed_pages != cmm_timed_pages_target ||
    153			kthread_should_stop());
    154		if (kthread_should_stop() || rc == -ERESTARTSYS) {
    155			cmm_pages_target = cmm_pages;
    156			cmm_timed_pages_target = cmm_timed_pages;
    157			break;
    158		}
    159		if (cmm_pages_target > cmm_pages) {
    160			if (cmm_alloc_pages(1, &cmm_pages, &cmm_page_list))
    161				cmm_pages_target = cmm_pages;
    162		} else if (cmm_pages_target < cmm_pages) {
    163			cmm_free_pages(1, &cmm_pages, &cmm_page_list);
    164		}
    165		if (cmm_timed_pages_target > cmm_timed_pages) {
    166			if (cmm_alloc_pages(1, &cmm_timed_pages,
    167					   &cmm_timed_page_list))
    168				cmm_timed_pages_target = cmm_timed_pages;
    169		} else if (cmm_timed_pages_target < cmm_timed_pages) {
    170			cmm_free_pages(1, &cmm_timed_pages,
    171				       &cmm_timed_page_list);
    172		}
    173		if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
    174			cmm_set_timer();
    175	}
    176	return 0;
    177}
    178
    179static void cmm_kick_thread(void)
    180{
    181	wake_up(&cmm_thread_wait);
    182}
    183
    184static void cmm_set_timer(void)
    185{
    186	if (cmm_timed_pages_target <= 0 || cmm_timeout_seconds <= 0) {
    187		if (timer_pending(&cmm_timer))
    188			del_timer(&cmm_timer);
    189		return;
    190	}
    191	mod_timer(&cmm_timer, jiffies + msecs_to_jiffies(cmm_timeout_seconds * MSEC_PER_SEC));
    192}
    193
    194static void cmm_timer_fn(struct timer_list *unused)
    195{
    196	long nr;
    197
    198	nr = cmm_timed_pages_target - cmm_timeout_pages;
    199	if (nr < 0)
    200		cmm_timed_pages_target = 0;
    201	else
    202		cmm_timed_pages_target = nr;
    203	cmm_kick_thread();
    204	cmm_set_timer();
    205}
    206
    207static void cmm_set_pages(long nr)
    208{
    209	cmm_pages_target = nr;
    210	cmm_kick_thread();
    211}
    212
    213static long cmm_get_pages(void)
    214{
    215	return cmm_pages;
    216}
    217
    218static void cmm_add_timed_pages(long nr)
    219{
    220	cmm_timed_pages_target += nr;
    221	cmm_kick_thread();
    222}
    223
    224static long cmm_get_timed_pages(void)
    225{
    226	return cmm_timed_pages;
    227}
    228
    229static void cmm_set_timeout(long nr, long seconds)
    230{
    231	cmm_timeout_pages = nr;
    232	cmm_timeout_seconds = seconds;
    233	cmm_set_timer();
    234}
    235
    236static int cmm_skip_blanks(char *cp, char **endp)
    237{
    238	char *str;
    239
    240	for (str = cp; *str == ' ' || *str == '\t'; str++)
    241		;
    242	*endp = str;
    243	return str != cp;
    244}
    245
    246static int cmm_pages_handler(struct ctl_table *ctl, int write,
    247			     void *buffer, size_t *lenp, loff_t *ppos)
    248{
    249	long nr = cmm_get_pages();
    250	struct ctl_table ctl_entry = {
    251		.procname	= ctl->procname,
    252		.data		= &nr,
    253		.maxlen		= sizeof(long),
    254	};
    255	int rc;
    256
    257	rc = proc_doulongvec_minmax(&ctl_entry, write, buffer, lenp, ppos);
    258	if (rc < 0 || !write)
    259		return rc;
    260
    261	cmm_set_pages(nr);
    262	return 0;
    263}
    264
    265static int cmm_timed_pages_handler(struct ctl_table *ctl, int write,
    266				   void *buffer, size_t *lenp,
    267				   loff_t *ppos)
    268{
    269	long nr = cmm_get_timed_pages();
    270	struct ctl_table ctl_entry = {
    271		.procname	= ctl->procname,
    272		.data		= &nr,
    273		.maxlen		= sizeof(long),
    274	};
    275	int rc;
    276
    277	rc = proc_doulongvec_minmax(&ctl_entry, write, buffer, lenp, ppos);
    278	if (rc < 0 || !write)
    279		return rc;
    280
    281	cmm_add_timed_pages(nr);
    282	return 0;
    283}
    284
    285static int cmm_timeout_handler(struct ctl_table *ctl, int write,
    286			       void *buffer, size_t *lenp, loff_t *ppos)
    287{
    288	char buf[64], *p;
    289	long nr, seconds;
    290	unsigned int len;
    291
    292	if (!*lenp || (*ppos && !write)) {
    293		*lenp = 0;
    294		return 0;
    295	}
    296
    297	if (write) {
    298		len = min(*lenp, sizeof(buf));
    299		memcpy(buf, buffer, len);
    300		buf[len - 1] = '\0';
    301		cmm_skip_blanks(buf, &p);
    302		nr = simple_strtoul(p, &p, 0);
    303		cmm_skip_blanks(p, &p);
    304		seconds = simple_strtoul(p, &p, 0);
    305		cmm_set_timeout(nr, seconds);
    306		*ppos += *lenp;
    307	} else {
    308		len = sprintf(buf, "%ld %ld\n",
    309			      cmm_timeout_pages, cmm_timeout_seconds);
    310		if (len > *lenp)
    311			len = *lenp;
    312		memcpy(buffer, buf, len);
    313		*lenp = len;
    314		*ppos += len;
    315	}
    316	return 0;
    317}
    318
    319static struct ctl_table cmm_table[] = {
    320	{
    321		.procname	= "cmm_pages",
    322		.mode		= 0644,
    323		.proc_handler	= cmm_pages_handler,
    324	},
    325	{
    326		.procname	= "cmm_timed_pages",
    327		.mode		= 0644,
    328		.proc_handler	= cmm_timed_pages_handler,
    329	},
    330	{
    331		.procname	= "cmm_timeout",
    332		.mode		= 0644,
    333		.proc_handler	= cmm_timeout_handler,
    334	},
    335	{ }
    336};
    337
    338static struct ctl_table cmm_dir_table[] = {
    339	{
    340		.procname	= "vm",
    341		.maxlen		= 0,
    342		.mode		= 0555,
    343		.child		= cmm_table,
    344	},
    345	{ }
    346};
    347
    348#ifdef CONFIG_CMM_IUCV
    349#define SMSG_PREFIX "CMM"
    350static void cmm_smsg_target(const char *from, char *msg)
    351{
    352	long nr, seconds;
    353
    354	if (strlen(sender) > 0 && strcmp(from, sender) != 0)
    355		return;
    356	if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
    357		return;
    358	if (strncmp(msg, "SHRINK", 6) == 0) {
    359		if (!cmm_skip_blanks(msg + 6, &msg))
    360			return;
    361		nr = simple_strtoul(msg, &msg, 0);
    362		cmm_skip_blanks(msg, &msg);
    363		if (*msg == '\0')
    364			cmm_set_pages(nr);
    365	} else if (strncmp(msg, "RELEASE", 7) == 0) {
    366		if (!cmm_skip_blanks(msg + 7, &msg))
    367			return;
    368		nr = simple_strtoul(msg, &msg, 0);
    369		cmm_skip_blanks(msg, &msg);
    370		if (*msg == '\0')
    371			cmm_add_timed_pages(nr);
    372	} else if (strncmp(msg, "REUSE", 5) == 0) {
    373		if (!cmm_skip_blanks(msg + 5, &msg))
    374			return;
    375		nr = simple_strtoul(msg, &msg, 0);
    376		if (!cmm_skip_blanks(msg, &msg))
    377			return;
    378		seconds = simple_strtoul(msg, &msg, 0);
    379		cmm_skip_blanks(msg, &msg);
    380		if (*msg == '\0')
    381			cmm_set_timeout(nr, seconds);
    382	}
    383}
    384#endif
    385
    386static struct ctl_table_header *cmm_sysctl_header;
    387
    388static int __init cmm_init(void)
    389{
    390	int rc = -ENOMEM;
    391
    392	cmm_sysctl_header = register_sysctl_table(cmm_dir_table);
    393	if (!cmm_sysctl_header)
    394		goto out_sysctl;
    395#ifdef CONFIG_CMM_IUCV
    396	/* convert sender to uppercase characters */
    397	if (sender)
    398		string_upper(sender, sender);
    399	else
    400		sender = cmm_default_sender;
    401
    402	rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
    403	if (rc < 0)
    404		goto out_smsg;
    405#endif
    406	rc = register_oom_notifier(&cmm_oom_nb);
    407	if (rc < 0)
    408		goto out_oom_notify;
    409	cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
    410	if (!IS_ERR(cmm_thread_ptr))
    411		return 0;
    412
    413	rc = PTR_ERR(cmm_thread_ptr);
    414	unregister_oom_notifier(&cmm_oom_nb);
    415out_oom_notify:
    416#ifdef CONFIG_CMM_IUCV
    417	smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
    418out_smsg:
    419#endif
    420	unregister_sysctl_table(cmm_sysctl_header);
    421out_sysctl:
    422	del_timer_sync(&cmm_timer);
    423	return rc;
    424}
    425module_init(cmm_init);
    426
    427static void __exit cmm_exit(void)
    428{
    429	unregister_sysctl_table(cmm_sysctl_header);
    430#ifdef CONFIG_CMM_IUCV
    431	smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
    432#endif
    433	unregister_oom_notifier(&cmm_oom_nb);
    434	kthread_stop(cmm_thread_ptr);
    435	del_timer_sync(&cmm_timer);
    436	cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
    437	cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
    438}
    439module_exit(cmm_exit);
    440
    441MODULE_LICENSE("GPL");