250 lines
5.0 KiB
C
250 lines
5.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) Huawei Technologies Co., Ltd. 2023. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "page eject: " fmt
|
|
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
|
|
static struct list_head eject_page_list = LIST_HEAD_INIT(eject_page_list);
|
|
static DEFINE_MUTEX(eject_page_mutex);
|
|
static struct kobject *eject_page_kobj;
|
|
|
|
struct ejected_pfn {
|
|
struct list_head list;
|
|
unsigned long pfn;
|
|
};
|
|
|
|
static struct ejected_pfn *page_eject_remove_pfn_locked(unsigned long pfn)
|
|
{
|
|
struct ejected_pfn *item, *next, *ret = NULL;
|
|
|
|
mutex_lock(&eject_page_mutex);
|
|
list_for_each_entry_safe(item, next, &eject_page_list, list) {
|
|
if (pfn == item->pfn) {
|
|
list_del(&item->list);
|
|
ret = item;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&eject_page_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void page_eject_add_pfn_locked(struct ejected_pfn *item)
|
|
{
|
|
mutex_lock(&eject_page_mutex);
|
|
list_add_tail(&item->list, &eject_page_list);
|
|
mutex_unlock(&eject_page_mutex);
|
|
}
|
|
|
|
static void page_eject_clear_list_locked(void)
|
|
{
|
|
struct ejected_pfn *item, *next;
|
|
|
|
mutex_lock(&eject_page_mutex);
|
|
list_for_each_entry_safe(item, next, &eject_page_list, list) {
|
|
list_del(&item->list);
|
|
kfree(item);
|
|
}
|
|
mutex_unlock(&eject_page_mutex);
|
|
}
|
|
|
|
static int page_eject_offline_page(unsigned long pfn)
|
|
{
|
|
struct ejected_pfn *item;
|
|
struct page *page;
|
|
int ret;
|
|
|
|
page = pfn_to_online_page(pfn);
|
|
if (!page)
|
|
return -EINVAL;
|
|
|
|
if (PageHWPoison(page)) {
|
|
pr_err("page fail to be offlined, page is already offlined, pfn: %#lx\n", pfn);
|
|
return -EINVAL;
|
|
}
|
|
|
|
item = kzalloc(sizeof(struct ejected_pfn), GFP_KERNEL);
|
|
if (!item)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* if soft_offline_page return 0 because PageHWPoison, this pfn
|
|
* will add to list and this add will be removed during online
|
|
* since it is poisoned.
|
|
*/
|
|
ret = soft_offline_page(pfn, 0);
|
|
if (ret) {
|
|
pr_err("page fail to be offlined, soft_offline_page failed(%d), pfn=%#lx\n",
|
|
ret, pfn);
|
|
kfree(item);
|
|
return ret;
|
|
}
|
|
|
|
item->pfn = pfn;
|
|
|
|
page_eject_add_pfn_locked(item);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int page_eject_online_page(unsigned long pfn)
|
|
{
|
|
struct ejected_pfn *item;
|
|
struct page *page;
|
|
int ret;
|
|
|
|
page = pfn_to_online_page(pfn);
|
|
if (!page)
|
|
return -EINVAL;
|
|
|
|
item = page_eject_remove_pfn_locked(pfn);
|
|
if (!item) {
|
|
pr_err("page failed to be onlined, pfn: %#lx\n", pfn);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = soft_online_page(pfn);
|
|
if (!ret) {
|
|
kfree(item);
|
|
return ret;
|
|
}
|
|
|
|
/* re-add pfn to list if unpoison failed */
|
|
page_eject_add_pfn_locked(item);
|
|
pr_err("page failed to be onlined, online error(%d), pfn: %#lx\n",
|
|
ret, pfn);
|
|
return ret;
|
|
}
|
|
|
|
static int page_eject_remove_page(unsigned long pfn)
|
|
{
|
|
struct ejected_pfn *item;
|
|
|
|
item = page_eject_remove_pfn_locked(pfn);
|
|
if (!item) {
|
|
pr_info("page fail to be removed, pfn: %#lx\n", pfn);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kfree(item);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t offline_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
u64 paddr;
|
|
int res;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
if (kstrtoull(buf, 16, &paddr))
|
|
return -EINVAL;
|
|
|
|
res = page_eject_offline_page(paddr >> PAGE_SHIFT);
|
|
if (res)
|
|
return res;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t online_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
u64 paddr;
|
|
int res;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
if (kstrtoull(buf, 16, &paddr))
|
|
return -EINVAL;
|
|
|
|
res = page_eject_online_page(paddr >> PAGE_SHIFT);
|
|
if (res)
|
|
return res;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t remove_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
u64 paddr;
|
|
int res;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
if (kstrtoull(buf, 16, &paddr))
|
|
return -EINVAL;
|
|
|
|
res = page_eject_remove_page(paddr >> PAGE_SHIFT);
|
|
if (res)
|
|
return res;
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct kobj_attribute online_attr =
|
|
__ATTR(online_page, 0200, NULL, online_store);
|
|
static struct kobj_attribute offline_attr =
|
|
__ATTR(offline_page, 0200, NULL, offline_store);
|
|
static struct kobj_attribute remove_attr =
|
|
__ATTR(remove_page, 0200, NULL, remove_store);
|
|
|
|
static struct attribute *eject_page_attrs[] = {
|
|
&offline_attr.attr,
|
|
&online_attr.attr,
|
|
&remove_attr.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group eject_page_attr_group = {
|
|
.attrs = eject_page_attrs,
|
|
};
|
|
|
|
static int __init page_eject_init(void)
|
|
{
|
|
int ret = -ENOMEM;
|
|
|
|
eject_page_kobj = kobject_create_and_add("page_eject", kernel_kobj);
|
|
if (!eject_page_kobj)
|
|
return ret;
|
|
|
|
ret = sysfs_create_group(eject_page_kobj, &eject_page_attr_group);
|
|
if (ret) {
|
|
kobject_put(eject_page_kobj);
|
|
return ret;
|
|
}
|
|
|
|
mutex_init(&eject_page_mutex);
|
|
|
|
pr_info("init page eject succeed\n");
|
|
return ret;
|
|
}
|
|
|
|
static void __exit page_eject_exit(void)
|
|
{
|
|
page_eject_clear_list_locked();
|
|
|
|
kobject_put(eject_page_kobj);
|
|
|
|
pr_info("exit page eject succeed\n");
|
|
}
|
|
|
|
module_init(page_eject_init);
|
|
module_exit(page_eject_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Ma Wupeng <mawupeng1@huawei.com>");
|
|
MODULE_DESCRIPTION("page eject");
|