+#define UNPLUGGED_PER_PAGE \
+ ((PAGE_SIZE - sizeof(struct list_head)) / sizeof(unsigned long))
+
+struct unplugged_pages {
+ struct list_head list;
+ void *pages[UNPLUGGED_PER_PAGE];
+};
+
+static DEFINE_MUTEX(plug_mem_mutex);
+static unsigned long long unplugged_pages_count = 0;
+static LIST_HEAD(unplugged_pages);
+static int unplug_index = UNPLUGGED_PER_PAGE;
+
+static int mem_config(char *str, char **error_out)
+{
+ unsigned long long diff;
+ int err = -EINVAL, i, add;
+ char *ret;
+
+ if (str[0] != '=') {
+ *error_out = "Expected '=' after 'mem'";
+ goto out;
+ }
+
+ str++;
+ if (str[0] == '-')
+ add = 0;
+ else if (str[0] == '+') {
+ add = 1;
+ }
+ else {
+ *error_out = "Expected increment to start with '-' or '+'";
+ goto out;
+ }
+
+ str++;
+ diff = memparse(str, &ret);
+ if (*ret != '\0') {
+ *error_out = "Failed to parse memory increment";
+ goto out;
+ }
+
+ diff /= PAGE_SIZE;
+
+ mutex_lock(&plug_mem_mutex);
+ for (i = 0; i < diff; i++) {
+ struct unplugged_pages *unplugged;
+ void *addr;
+
+ if (add) {
+ if (list_empty(&unplugged_pages))
+ break;
+
+ unplugged = list_entry(unplugged_pages.next,
+ struct unplugged_pages, list);
+ if (unplug_index > 0)
+ addr = unplugged->pages[--unplug_index];
+ else {
+ list_del(&unplugged->list);
+ addr = unplugged;
+ unplug_index = UNPLUGGED_PER_PAGE;
+ }
+
+ free_page((unsigned long) addr);
+ unplugged_pages_count--;
+ }
+ else {
+ struct page *page;
+
+ page = alloc_page(GFP_ATOMIC);
+ if (page == NULL)
+ break;
+
+ unplugged = page_address(page);
+ if (unplug_index == UNPLUGGED_PER_PAGE) {
+ list_add(&unplugged->list, &unplugged_pages);
+ unplug_index = 0;
+ }
+ else {
+ struct list_head *entry = unplugged_pages.next;
+ addr = unplugged;
+
+ unplugged = list_entry(entry,
+ struct unplugged_pages,
+ list);
+ err = os_drop_memory(addr, PAGE_SIZE);
+ if (err) {
+ printk(KERN_ERR "Failed to release "
+ "memory - errno = %d\n", err);
+ *error_out = "Failed to release memory";
+ goto out_unlock;
+ }
+ unplugged->pages[unplug_index++] = addr;
+ }
+
+ unplugged_pages_count++;
+ }
+ }
+
+ err = 0;
+out_unlock:
+ mutex_unlock(&plug_mem_mutex);
+out:
+ return err;
+}
+
+static int mem_get_config(char *name, char *str, int size, char **error_out)
+{
+ char buf[sizeof("18446744073709551615")];
+ int len = 0;
+
+ sprintf(buf, "%ld", uml_physmem);
+ CONFIG_CHUNK(str, size, len, buf, 1);
+
+ return len;
+}
+
+static int mem_id(char **str, int *start_out, int *end_out)
+{
+ *start_out = 0;
+ *end_out = 0;
+
+ return 0;
+}
+
+static int mem_remove(int n, char **error_out)
+{
+ *error_out = "Memory doesn't support the remove operation";
+ return -EBUSY;
+}
+
+static struct mc_device mem_mc = {
+ .list = LIST_HEAD_INIT(mem_mc.list),
+ .name = "mem",
+ .config = mem_config,
+ .get_config = mem_get_config,
+ .id = mem_id,
+ .remove = mem_remove,
+};
+
+static int __init mem_mc_init(void)
+{
+ if (can_drop_memory())
+ mconsole_register_dev(&mem_mc);
+ else printk(KERN_ERR "Can't release memory to the host - memory "
+ "hotplug won't be supported\n");
+ return 0;
+}
+
+__initcall(mem_mc_init);
+