Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/arch/s390/mm/cmm.c
26424 views
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 <[email protected]>,
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
27
static char *cmm_default_sender = "VMRMSVM";
28
#endif
29
static char *sender;
30
module_param(sender, charp, 0400);
31
MODULE_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
38
struct cmm_page_array {
39
struct cmm_page_array *next;
40
unsigned long index;
41
unsigned long pages[CMM_NR_PAGES];
42
};
43
44
static long cmm_pages;
45
static long cmm_timed_pages;
46
static volatile long cmm_pages_target;
47
static volatile long cmm_timed_pages_target;
48
static long cmm_timeout_pages;
49
static long cmm_timeout_seconds;
50
51
static struct cmm_page_array *cmm_page_list;
52
static struct cmm_page_array *cmm_timed_page_list;
53
static DEFINE_SPINLOCK(cmm_lock);
54
55
static struct task_struct *cmm_thread_ptr;
56
static DECLARE_WAIT_QUEUE_HEAD(cmm_thread_wait);
57
58
static void cmm_timer_fn(struct timer_list *);
59
static void cmm_set_timer(void);
60
static DEFINE_TIMER(cmm_timer, cmm_timer_fn);
61
62
static 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((void *)addr), 1);
94
pa->pages[pa->index++] = addr;
95
(*counter)++;
96
spin_unlock(&cmm_lock);
97
nr--;
98
cond_resched();
99
}
100
return nr;
101
}
102
103
static long __cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
104
{
105
struct cmm_page_array *pa;
106
unsigned long addr;
107
108
spin_lock(&cmm_lock);
109
pa = *list;
110
while (nr) {
111
if (!pa || pa->index <= 0)
112
break;
113
addr = pa->pages[--pa->index];
114
if (pa->index == 0) {
115
pa = pa->next;
116
free_page((unsigned long) *list);
117
*list = pa;
118
}
119
free_page(addr);
120
(*counter)--;
121
nr--;
122
}
123
spin_unlock(&cmm_lock);
124
return nr;
125
}
126
127
static long cmm_free_pages(long nr, long *counter, struct cmm_page_array **list)
128
{
129
long inc = 0;
130
131
while (nr) {
132
inc = min(256L, nr);
133
nr -= inc;
134
inc = __cmm_free_pages(inc, counter, list);
135
if (inc)
136
break;
137
cond_resched();
138
}
139
return nr + inc;
140
}
141
142
static int cmm_oom_notify(struct notifier_block *self,
143
unsigned long dummy, void *parm)
144
{
145
unsigned long *freed = parm;
146
long nr = 256;
147
148
nr = cmm_free_pages(nr, &cmm_timed_pages, &cmm_timed_page_list);
149
if (nr > 0)
150
nr = cmm_free_pages(nr, &cmm_pages, &cmm_page_list);
151
cmm_pages_target = cmm_pages;
152
cmm_timed_pages_target = cmm_timed_pages;
153
*freed += 256 - nr;
154
return NOTIFY_OK;
155
}
156
157
static struct notifier_block cmm_oom_nb = {
158
.notifier_call = cmm_oom_notify,
159
};
160
161
static int cmm_thread(void *dummy)
162
{
163
int rc;
164
165
while (1) {
166
rc = wait_event_interruptible(cmm_thread_wait,
167
cmm_pages != cmm_pages_target ||
168
cmm_timed_pages != cmm_timed_pages_target ||
169
kthread_should_stop());
170
if (kthread_should_stop() || rc == -ERESTARTSYS) {
171
cmm_pages_target = cmm_pages;
172
cmm_timed_pages_target = cmm_timed_pages;
173
break;
174
}
175
if (cmm_pages_target > cmm_pages) {
176
if (cmm_alloc_pages(1, &cmm_pages, &cmm_page_list))
177
cmm_pages_target = cmm_pages;
178
} else if (cmm_pages_target < cmm_pages) {
179
cmm_free_pages(1, &cmm_pages, &cmm_page_list);
180
}
181
if (cmm_timed_pages_target > cmm_timed_pages) {
182
if (cmm_alloc_pages(1, &cmm_timed_pages,
183
&cmm_timed_page_list))
184
cmm_timed_pages_target = cmm_timed_pages;
185
} else if (cmm_timed_pages_target < cmm_timed_pages) {
186
cmm_free_pages(1, &cmm_timed_pages,
187
&cmm_timed_page_list);
188
}
189
if (cmm_timed_pages > 0 && !timer_pending(&cmm_timer))
190
cmm_set_timer();
191
}
192
return 0;
193
}
194
195
static void cmm_kick_thread(void)
196
{
197
wake_up(&cmm_thread_wait);
198
}
199
200
static void cmm_set_timer(void)
201
{
202
if (cmm_timed_pages_target <= 0 || cmm_timeout_seconds <= 0) {
203
if (timer_pending(&cmm_timer))
204
timer_delete(&cmm_timer);
205
return;
206
}
207
mod_timer(&cmm_timer, jiffies + secs_to_jiffies(cmm_timeout_seconds));
208
}
209
210
static void cmm_timer_fn(struct timer_list *unused)
211
{
212
long nr;
213
214
nr = cmm_timed_pages_target - cmm_timeout_pages;
215
if (nr < 0)
216
cmm_timed_pages_target = 0;
217
else
218
cmm_timed_pages_target = nr;
219
cmm_kick_thread();
220
cmm_set_timer();
221
}
222
223
static void cmm_set_pages(long nr)
224
{
225
cmm_pages_target = nr;
226
cmm_kick_thread();
227
}
228
229
static long cmm_get_pages(void)
230
{
231
return cmm_pages;
232
}
233
234
static void cmm_add_timed_pages(long nr)
235
{
236
cmm_timed_pages_target += nr;
237
cmm_kick_thread();
238
}
239
240
static long cmm_get_timed_pages(void)
241
{
242
return cmm_timed_pages;
243
}
244
245
static void cmm_set_timeout(long nr, long seconds)
246
{
247
cmm_timeout_pages = nr;
248
cmm_timeout_seconds = seconds;
249
cmm_set_timer();
250
}
251
252
static int cmm_skip_blanks(char *cp, char **endp)
253
{
254
char *str;
255
256
for (str = cp; *str == ' ' || *str == '\t'; str++)
257
;
258
*endp = str;
259
return str != cp;
260
}
261
262
static int cmm_pages_handler(const struct ctl_table *ctl, int write,
263
void *buffer, size_t *lenp, loff_t *ppos)
264
{
265
long nr = cmm_get_pages();
266
struct ctl_table ctl_entry = {
267
.procname = ctl->procname,
268
.data = &nr,
269
.maxlen = sizeof(long),
270
};
271
int rc;
272
273
rc = proc_doulongvec_minmax(&ctl_entry, write, buffer, lenp, ppos);
274
if (rc < 0 || !write)
275
return rc;
276
277
cmm_set_pages(nr);
278
return 0;
279
}
280
281
static int cmm_timed_pages_handler(const struct ctl_table *ctl, int write,
282
void *buffer, size_t *lenp,
283
loff_t *ppos)
284
{
285
long nr = cmm_get_timed_pages();
286
struct ctl_table ctl_entry = {
287
.procname = ctl->procname,
288
.data = &nr,
289
.maxlen = sizeof(long),
290
};
291
int rc;
292
293
rc = proc_doulongvec_minmax(&ctl_entry, write, buffer, lenp, ppos);
294
if (rc < 0 || !write)
295
return rc;
296
297
cmm_add_timed_pages(nr);
298
return 0;
299
}
300
301
static int cmm_timeout_handler(const struct ctl_table *ctl, int write,
302
void *buffer, size_t *lenp, loff_t *ppos)
303
{
304
char buf[64], *p;
305
long nr, seconds;
306
unsigned int len;
307
308
if (!*lenp || (*ppos && !write)) {
309
*lenp = 0;
310
return 0;
311
}
312
313
if (write) {
314
len = min(*lenp, sizeof(buf));
315
memcpy(buf, buffer, len);
316
buf[len - 1] = '\0';
317
cmm_skip_blanks(buf, &p);
318
nr = simple_strtoul(p, &p, 0);
319
cmm_skip_blanks(p, &p);
320
seconds = simple_strtoul(p, &p, 0);
321
cmm_set_timeout(nr, seconds);
322
*ppos += *lenp;
323
} else {
324
len = sprintf(buf, "%ld %ld\n",
325
cmm_timeout_pages, cmm_timeout_seconds);
326
if (len > *lenp)
327
len = *lenp;
328
memcpy(buffer, buf, len);
329
*lenp = len;
330
*ppos += len;
331
}
332
return 0;
333
}
334
335
static const struct ctl_table cmm_table[] = {
336
{
337
.procname = "cmm_pages",
338
.mode = 0644,
339
.proc_handler = cmm_pages_handler,
340
},
341
{
342
.procname = "cmm_timed_pages",
343
.mode = 0644,
344
.proc_handler = cmm_timed_pages_handler,
345
},
346
{
347
.procname = "cmm_timeout",
348
.mode = 0644,
349
.proc_handler = cmm_timeout_handler,
350
},
351
};
352
353
#ifdef CONFIG_CMM_IUCV
354
#define SMSG_PREFIX "CMM"
355
static void cmm_smsg_target(const char *from, char *msg)
356
{
357
long nr, seconds;
358
359
if (strlen(sender) > 0 && strcmp(from, sender) != 0)
360
return;
361
if (!cmm_skip_blanks(msg + strlen(SMSG_PREFIX), &msg))
362
return;
363
if (strncmp(msg, "SHRINK", 6) == 0) {
364
if (!cmm_skip_blanks(msg + 6, &msg))
365
return;
366
nr = simple_strtoul(msg, &msg, 0);
367
cmm_skip_blanks(msg, &msg);
368
if (*msg == '\0')
369
cmm_set_pages(nr);
370
} else if (strncmp(msg, "RELEASE", 7) == 0) {
371
if (!cmm_skip_blanks(msg + 7, &msg))
372
return;
373
nr = simple_strtoul(msg, &msg, 0);
374
cmm_skip_blanks(msg, &msg);
375
if (*msg == '\0')
376
cmm_add_timed_pages(nr);
377
} else if (strncmp(msg, "REUSE", 5) == 0) {
378
if (!cmm_skip_blanks(msg + 5, &msg))
379
return;
380
nr = simple_strtoul(msg, &msg, 0);
381
if (!cmm_skip_blanks(msg, &msg))
382
return;
383
seconds = simple_strtoul(msg, &msg, 0);
384
cmm_skip_blanks(msg, &msg);
385
if (*msg == '\0')
386
cmm_set_timeout(nr, seconds);
387
}
388
}
389
#endif
390
391
static struct ctl_table_header *cmm_sysctl_header;
392
393
static int __init cmm_init(void)
394
{
395
int rc = -ENOMEM;
396
397
cmm_sysctl_header = register_sysctl("vm", cmm_table);
398
if (!cmm_sysctl_header)
399
goto out_sysctl;
400
#ifdef CONFIG_CMM_IUCV
401
/* convert sender to uppercase characters */
402
if (sender)
403
string_upper(sender, sender);
404
else
405
sender = cmm_default_sender;
406
407
rc = smsg_register_callback(SMSG_PREFIX, cmm_smsg_target);
408
if (rc < 0)
409
goto out_smsg;
410
#endif
411
rc = register_oom_notifier(&cmm_oom_nb);
412
if (rc < 0)
413
goto out_oom_notify;
414
cmm_thread_ptr = kthread_run(cmm_thread, NULL, "cmmthread");
415
if (!IS_ERR(cmm_thread_ptr))
416
return 0;
417
418
rc = PTR_ERR(cmm_thread_ptr);
419
unregister_oom_notifier(&cmm_oom_nb);
420
out_oom_notify:
421
#ifdef CONFIG_CMM_IUCV
422
smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
423
out_smsg:
424
#endif
425
unregister_sysctl_table(cmm_sysctl_header);
426
out_sysctl:
427
timer_delete_sync(&cmm_timer);
428
return rc;
429
}
430
module_init(cmm_init);
431
432
static void __exit cmm_exit(void)
433
{
434
unregister_sysctl_table(cmm_sysctl_header);
435
#ifdef CONFIG_CMM_IUCV
436
smsg_unregister_callback(SMSG_PREFIX, cmm_smsg_target);
437
#endif
438
unregister_oom_notifier(&cmm_oom_nb);
439
kthread_stop(cmm_thread_ptr);
440
timer_delete_sync(&cmm_timer);
441
cmm_free_pages(cmm_pages, &cmm_pages, &cmm_page_list);
442
cmm_free_pages(cmm_timed_pages, &cmm_timed_pages, &cmm_timed_page_list);
443
}
444
module_exit(cmm_exit);
445
446
MODULE_DESCRIPTION("Cooperative memory management interface");
447
MODULE_LICENSE("GPL");
448
449