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