/** * cmm_free_pages - Free pages and mark them as active * @nr: number of pages to free * * Return value: * number of pages requested to be freed which were not **/ static long cmm_free_pages(long nr) { struct cmm_page_array *pa; unsigned long addr; cmm_dbg("Begin free of %ld pages.\n", nr); spin_lock(&cmm_lock); pa = cmm_page_list; while (nr) { if (!pa || pa->index <= 0) break; addr = pa->page[--pa->index]; if (pa->index == 0) { pa = pa->next; free_page((unsigned long) cmm_page_list); cmm_page_list = pa; } plpar_page_set_active(__pa(addr)); free_page(addr); loaned_pages--; nr--; totalram_pages++; } spin_unlock(&cmm_lock); cmm_dbg("End request with %ld pages unfulfilled\n", nr); return nr; }
/** * cmm_alloc_pages - Allocate pages and mark them as loaned * @nr: number of pages to allocate * * Return value: * number of pages requested to be allocated which were not **/ static long cmm_alloc_pages(long nr) { struct cmm_page_array *pa, *npa; unsigned long addr; long rc; cmm_dbg("Begin request for %ld pages\n", nr); while (nr) { addr = __get_free_page(GFP_NOIO | __GFP_NOWARN | __GFP_NORETRY | __GFP_NOMEMALLOC); if (!addr) break; spin_lock(&cmm_lock); pa = cmm_page_list; if (!pa || pa->index >= CMM_NR_PAGES) { /* Need a new page for the page list. */ spin_unlock(&cmm_lock); npa = (struct cmm_page_array *)__get_free_page(GFP_NOIO | __GFP_NOWARN | __GFP_NORETRY | __GFP_NOMEMALLOC); if (!npa) { pr_info("%s: Can not allocate new page list\n", __func__); free_page(addr); break; } spin_lock(&cmm_lock); pa = cmm_page_list; if (!pa || pa->index >= CMM_NR_PAGES) { npa->next = pa; npa->index = 0; pa = npa; cmm_page_list = pa; } else free_page((unsigned long) npa); } if ((rc = plpar_page_set_loaned(__pa(addr)))) { pr_err("%s: Can not set page to loaned. rc=%ld\n", __func__, rc); spin_unlock(&cmm_lock); free_page(addr); break; } pa->page[pa->index++] = addr; loaned_pages++; totalram_pages--; spin_unlock(&cmm_lock); nr--; } cmm_dbg("End request with %ld pages unfulfilled\n", nr); return nr; }
/** * cmm_oom_notify - OOM notifier * @self: notifier block struct * @dummy: not used * @parm: returned - number of pages freed * * Return value: * NOTIFY_OK **/ static int cmm_oom_notify(struct notifier_block *self, unsigned long dummy, void *parm) { unsigned long *freed = parm; long nr = KB2PAGES(oom_kb); cmm_dbg("OOM processing started\n"); nr = cmm_free_pages(nr); loaned_pages_target = loaned_pages; *freed += KB2PAGES(oom_kb) - nr; oom_freed_pages += KB2PAGES(oom_kb) - nr; cmm_dbg("OOM processing complete\n"); return NOTIFY_OK; }
/** * cmm_get_mpp - Read memory performance parameters * * Makes hcall to query the current page loan request from the hypervisor. * * Return value: * nothing **/ static void cmm_get_mpp(void) { int rc; struct hvcall_mpp_data mpp_data; unsigned long active_pages_target; signed long page_loan_request; rc = h_get_mpp(&mpp_data); if (rc != H_SUCCESS) return; page_loan_request = div_s64((s64)mpp_data.loan_request, PAGE_SIZE); loaned_pages_target = page_loan_request + loaned_pages; if (loaned_pages_target > oom_freed_pages) loaned_pages_target -= oom_freed_pages; else loaned_pages_target = 0; active_pages_target = totalram_pages + loaned_pages - loaned_pages_target; if ((min_mem_mb * 1024 * 1024) > (active_pages_target * PAGE_SIZE)) loaned_pages_target = totalram_pages + loaned_pages - ((min_mem_mb * 1024 * 1024) / PAGE_SIZE); cmm_dbg("delta = %ld, loaned = %lu, target = %lu, oom = %lu, totalram = %lu\n", page_loan_request, loaned_pages, loaned_pages_target, oom_freed_pages, totalram_pages); }
/** * cmm_thread - CMM task thread * @dummy: not used * * Return value: * 0 **/ static int cmm_thread(void *dummy) { unsigned long timeleft; while (1) { timeleft = msleep_interruptible(delay * 1000); if (kthread_should_stop() || timeleft) break; if (mutex_trylock(&hotplug_mutex)) { if (hotplug_occurred) { hotplug_occurred = 0; mutex_unlock(&hotplug_mutex); cmm_dbg("Hotplug operation has occurred, " "loaning activity suspended " "for %d seconds.\n", hotplug_delay); timeleft = msleep_interruptible(hotplug_delay * 1000); if (kthread_should_stop() || timeleft) break; continue; } mutex_unlock(&hotplug_mutex); } else { cmm_dbg("Hotplug operation in progress, activity " "suspended\n"); continue; } cmm_get_mpp(); if (loaned_pages_target > loaned_pages) { if (cmm_alloc_pages(loaned_pages_target - loaned_pages)) loaned_pages_target = loaned_pages; } else if (loaned_pages_target < loaned_pages) cmm_free_pages(loaned_pages - loaned_pages_target); } return 0; }
/** * cmm_get_mpp - Read memory performance parameters * * Makes hcall to query the current page loan request from the hypervisor. * * Return value: * nothing **/ static void cmm_get_mpp(void) { int rc; struct hvcall_mpp_data mpp_data; signed long active_pages_target, page_loan_request, target; signed long total_pages = totalram_pages + loaned_pages; signed long min_mem_pages = (min_mem_mb * 1024 * 1024) / PAGE_SIZE; rc = h_get_mpp(&mpp_data); if (rc != H_SUCCESS) return; page_loan_request = div_s64((s64)mpp_data.loan_request, PAGE_SIZE); target = page_loan_request + (signed long)loaned_pages; if (target < 0 || total_pages < min_mem_pages) target = 0; if (target > oom_freed_pages) target -= oom_freed_pages; else target = 0; active_pages_target = total_pages - target; if (min_mem_pages > active_pages_target) target = total_pages - min_mem_pages; if (target < 0) target = 0; loaned_pages_target = target; cmm_dbg("delta = %ld, loaned = %lu, target = %lu, oom = %lu, totalram = %lu\n", page_loan_request, loaned_pages, loaned_pages_target, oom_freed_pages, totalram_pages); }
/** * cmm_memory_cb - Handle memory hotplug notifier calls * @self: notifier block struct * @action: action to take * @arg: struct memory_notify data for handler * * Return value: * NOTIFY_OK or notifier error based on subfunction return value * **/ static int cmm_memory_cb(struct notifier_block *self, unsigned long action, void *arg) { int ret = 0; switch (action) { case MEM_GOING_OFFLINE: mutex_lock(&hotplug_mutex); hotplug_occurred = 1; ret = cmm_mem_going_offline(arg); break; case MEM_OFFLINE: case MEM_CANCEL_OFFLINE: mutex_unlock(&hotplug_mutex); cmm_dbg("Memory offline operation complete.\n"); break; case MEM_GOING_ONLINE: case MEM_ONLINE: case MEM_CANCEL_ONLINE: break; } return notifier_from_errno(ret); }
/** * cmm_mem_going_offline - Unloan pages where memory is to be removed * @arg: memory_notify structure with page range to be offlined * * Return value: * 0 on success **/ static int cmm_mem_going_offline(void *arg) { struct memory_notify *marg = arg; unsigned long start_page = (unsigned long)pfn_to_kaddr(marg->start_pfn); unsigned long end_page = start_page + (marg->nr_pages << PAGE_SHIFT); struct cmm_page_array *pa_curr, *pa_last, *npa; unsigned long idx; unsigned long freed = 0; cmm_dbg("Memory going offline, searching 0x%lx (%ld pages).\n", start_page, marg->nr_pages); spin_lock(&cmm_lock); /* Search the page list for pages in the range to be offlined */ pa_last = pa_curr = cmm_page_list; while (pa_curr) { for (idx = (pa_curr->index - 1); (idx + 1) > 0; idx--) { if ((pa_curr->page[idx] < start_page) || (pa_curr->page[idx] >= end_page)) continue; plpar_page_set_active(__pa(pa_curr->page[idx])); free_page(pa_curr->page[idx]); freed++; loaned_pages--; totalram_pages++; pa_curr->page[idx] = pa_last->page[--pa_last->index]; if (pa_last->index == 0) { if (pa_curr == pa_last) pa_curr = pa_last->next; pa_last = pa_last->next; free_page((unsigned long)cmm_page_list); cmm_page_list = pa_last; continue; } } pa_curr = pa_curr->next; } /* Search for page list structures in the range to be offlined */ pa_last = NULL; pa_curr = cmm_page_list; while (pa_curr) { if (((unsigned long)pa_curr >= start_page) && ((unsigned long)pa_curr < end_page)) { npa = (struct cmm_page_array *)__get_free_page( GFP_NOIO | __GFP_NOWARN | __GFP_NORETRY | __GFP_NOMEMALLOC); if (!npa) { spin_unlock(&cmm_lock); cmm_dbg("Failed to allocate memory for list " "management. Memory hotplug " "failed.\n"); return ENOMEM; } memcpy(npa, pa_curr, PAGE_SIZE); if (pa_curr == cmm_page_list) cmm_page_list = npa; if (pa_last) pa_last->next = npa; free_page((unsigned long) pa_curr); freed++; pa_curr = npa; } pa_last = pa_curr; pa_curr = pa_curr->next; } spin_unlock(&cmm_lock); cmm_dbg("Released %ld pages in the search range.\n", freed); return 0; }