| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * HugeTLB sysfs interfaces. | |
| * (C) Nadia Yvette Chambers, April 2004 | |
| */ | |
| #include <linux/swap.h> | |
| #include <linux/page_owner.h> | |
| #include <linux/page-isolation.h> | |
| #include "hugetlb_vmemmap.h" | |
| #include "hugetlb_internal.h" | |
| #define HSTATE_ATTR_RO(_name) \ | |
| static struct kobj_attribute _name##_attr = __ATTR_RO(_name) | |
| #define HSTATE_ATTR_WO(_name) \ | |
| static struct kobj_attribute _name##_attr = __ATTR_WO(_name) | |
| #define HSTATE_ATTR(_name) \ | |
| static struct kobj_attribute _name##_attr = __ATTR_RW(_name) | |
| static struct kobject *hugepages_kobj; | |
| static struct kobject *hstate_kobjs[HUGE_MAX_HSTATE]; | |
| static struct hstate *kobj_to_node_hstate(struct kobject *kobj, int *nidp); | |
| static struct hstate *kobj_to_hstate(struct kobject *kobj, int *nidp) | |
| { | |
| int i; | |
| for (i = 0; i < HUGE_MAX_HSTATE; i++) | |
| if (hstate_kobjs[i] == kobj) { | |
| if (nidp) | |
| *nidp = NUMA_NO_NODE; | |
| return &hstates[i]; | |
| } | |
| return kobj_to_node_hstate(kobj, nidp); | |
| } | |
| static ssize_t nr_hugepages_show_common(struct kobject *kobj, | |
| struct kobj_attribute *attr, char *buf) | |
| { | |
| struct hstate *h; | |
| unsigned long nr_huge_pages; | |
| int nid; | |
| h = kobj_to_hstate(kobj, &nid); | |
| if (nid == NUMA_NO_NODE) | |
| nr_huge_pages = h->nr_huge_pages; | |
| else | |
| nr_huge_pages = h->nr_huge_pages_node[nid]; | |
| return sysfs_emit(buf, "%lu\n", nr_huge_pages); | |
| } | |
| static ssize_t nr_hugepages_store_common(bool obey_mempolicy, | |
| struct kobject *kobj, const char *buf, | |
| size_t len) | |
| { | |
| struct hstate *h; | |
| unsigned long count; | |
| int nid; | |
| int err; | |
| err = kstrtoul(buf, 10, &count); | |
| if (err) | |
| return err; | |
| h = kobj_to_hstate(kobj, &nid); | |
| return __nr_hugepages_store_common(obey_mempolicy, h, nid, count, len); | |
| } | |
| static ssize_t nr_hugepages_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, char *buf) | |
| { | |
| return nr_hugepages_show_common(kobj, attr, buf); | |
| } | |
| static ssize_t nr_hugepages_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, const char *buf, size_t len) | |
| { | |
| return nr_hugepages_store_common(false, kobj, buf, len); | |
| } | |
| HSTATE_ATTR(nr_hugepages); | |
| #ifdef CONFIG_NUMA | |
| /* | |
| * hstate attribute for optionally mempolicy-based constraint on persistent | |
| * huge page alloc/free. | |
| */ | |
| static ssize_t nr_hugepages_mempolicy_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, | |
| char *buf) | |
| { | |
| return nr_hugepages_show_common(kobj, attr, buf); | |
| } | |
| static ssize_t nr_hugepages_mempolicy_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, const char *buf, size_t len) | |
| { | |
| return nr_hugepages_store_common(true, kobj, buf, len); | |
| } | |
| HSTATE_ATTR(nr_hugepages_mempolicy); | |
| #endif | |
| static ssize_t nr_overcommit_hugepages_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, char *buf) | |
| { | |
| struct hstate *h = kobj_to_hstate(kobj, NULL); | |
| return sysfs_emit(buf, "%lu\n", h->nr_overcommit_huge_pages); | |
| } | |
| static ssize_t nr_overcommit_hugepages_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, const char *buf, size_t count) | |
| { | |
| int err; | |
| unsigned long input; | |
| struct hstate *h = kobj_to_hstate(kobj, NULL); | |
| if (hstate_is_gigantic_no_runtime(h)) | |
| return -EINVAL; | |
| err = kstrtoul(buf, 10, &input); | |
| if (err) | |
| return err; | |
| spin_lock_irq(&hugetlb_lock); | |
| h->nr_overcommit_huge_pages = input; | |
| spin_unlock_irq(&hugetlb_lock); | |
| return count; | |
| } | |
| HSTATE_ATTR(nr_overcommit_hugepages); | |
| static ssize_t free_hugepages_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, char *buf) | |
| { | |
| struct hstate *h; | |
| unsigned long free_huge_pages; | |
| int nid; | |
| h = kobj_to_hstate(kobj, &nid); | |
| if (nid == NUMA_NO_NODE) | |
| free_huge_pages = h->free_huge_pages; | |
| else | |
| free_huge_pages = h->free_huge_pages_node[nid]; | |
| return sysfs_emit(buf, "%lu\n", free_huge_pages); | |
| } | |
| HSTATE_ATTR_RO(free_hugepages); | |
| static ssize_t resv_hugepages_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, char *buf) | |
| { | |
| struct hstate *h = kobj_to_hstate(kobj, NULL); | |
| return sysfs_emit(buf, "%lu\n", h->resv_huge_pages); | |
| } | |
| HSTATE_ATTR_RO(resv_hugepages); | |
| static ssize_t surplus_hugepages_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, char *buf) | |
| { | |
| struct hstate *h; | |
| unsigned long surplus_huge_pages; | |
| int nid; | |
| h = kobj_to_hstate(kobj, &nid); | |
| if (nid == NUMA_NO_NODE) | |
| surplus_huge_pages = h->surplus_huge_pages; | |
| else | |
| surplus_huge_pages = h->surplus_huge_pages_node[nid]; | |
| return sysfs_emit(buf, "%lu\n", surplus_huge_pages); | |
| } | |
| HSTATE_ATTR_RO(surplus_hugepages); | |
| static ssize_t demote_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, const char *buf, size_t len) | |
| { | |
| unsigned long nr_demote; | |
| unsigned long nr_available; | |
| nodemask_t nodes_allowed, *n_mask; | |
| struct hstate *h; | |
| int err; | |
| int nid; | |
| err = kstrtoul(buf, 10, &nr_demote); | |
| if (err) | |
| return err; | |
| h = kobj_to_hstate(kobj, &nid); | |
| if (nid != NUMA_NO_NODE) { | |
| init_nodemask_of_node(&nodes_allowed, nid); | |
| n_mask = &nodes_allowed; | |
| } else { | |
| n_mask = &node_states[N_MEMORY]; | |
| } | |
| /* Synchronize with other sysfs operations modifying huge pages */ | |
| mutex_lock(&h->resize_lock); | |
| spin_lock_irq(&hugetlb_lock); | |
| while (nr_demote) { | |
| long rc; | |
| /* | |
| * Check for available pages to demote each time thorough the | |
| * loop as demote_pool_huge_page will drop hugetlb_lock. | |
| */ | |
| if (nid != NUMA_NO_NODE) | |
| nr_available = h->free_huge_pages_node[nid]; | |
| else | |
| nr_available = h->free_huge_pages; | |
| nr_available -= h->resv_huge_pages; | |
| if (!nr_available) | |
| break; | |
| rc = demote_pool_huge_page(h, n_mask, nr_demote); | |
| if (rc < 0) { | |
| err = rc; | |
| break; | |
| } | |
| nr_demote -= rc; | |
| } | |
| spin_unlock_irq(&hugetlb_lock); | |
| mutex_unlock(&h->resize_lock); | |
| if (err) | |
| return err; | |
| return len; | |
| } | |
| HSTATE_ATTR_WO(demote); | |
| static ssize_t demote_size_show(struct kobject *kobj, | |
| struct kobj_attribute *attr, char *buf) | |
| { | |
| struct hstate *h = kobj_to_hstate(kobj, NULL); | |
| unsigned long demote_size = (PAGE_SIZE << h->demote_order) / SZ_1K; | |
| return sysfs_emit(buf, "%lukB\n", demote_size); | |
| } | |
| static ssize_t demote_size_store(struct kobject *kobj, | |
| struct kobj_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct hstate *h, *demote_hstate; | |
| unsigned long demote_size; | |
| unsigned int demote_order; | |
| demote_size = (unsigned long)memparse(buf, NULL); | |
| demote_hstate = size_to_hstate(demote_size); | |
| if (!demote_hstate) | |
| return -EINVAL; | |
| demote_order = demote_hstate->order; | |
| if (demote_order < HUGETLB_PAGE_ORDER) | |
| return -EINVAL; | |
| /* demote order must be smaller than hstate order */ | |
| h = kobj_to_hstate(kobj, NULL); | |
| if (demote_order >= h->order) | |
| return -EINVAL; | |
| /* resize_lock synchronizes access to demote size and writes */ | |
| mutex_lock(&h->resize_lock); | |
| h->demote_order = demote_order; | |
| mutex_unlock(&h->resize_lock); | |
| return count; | |
| } | |
| HSTATE_ATTR(demote_size); | |
| static struct attribute *hstate_attrs[] = { | |
| &nr_hugepages_attr.attr, | |
| &nr_overcommit_hugepages_attr.attr, | |
| &free_hugepages_attr.attr, | |
| &resv_hugepages_attr.attr, | |
| &surplus_hugepages_attr.attr, | |
| #ifdef CONFIG_NUMA | |
| &nr_hugepages_mempolicy_attr.attr, | |
| #endif | |
| NULL, | |
| }; | |
| static const struct attribute_group hstate_attr_group = { | |
| .attrs = hstate_attrs, | |
| }; | |
| static struct attribute *hstate_demote_attrs[] = { | |
| &demote_size_attr.attr, | |
| &demote_attr.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group hstate_demote_attr_group = { | |
| .attrs = hstate_demote_attrs, | |
| }; | |
| static int hugetlb_sysfs_add_hstate(struct hstate *h, struct kobject *parent, | |
| struct kobject **hstate_kobjs, | |
| const struct attribute_group *hstate_attr_group) | |
| { | |
| int retval; | |
| int hi = hstate_index(h); | |
| hstate_kobjs[hi] = kobject_create_and_add(h->name, parent); | |
| if (!hstate_kobjs[hi]) | |
| return -ENOMEM; | |
| retval = sysfs_create_group(hstate_kobjs[hi], hstate_attr_group); | |
| if (retval) { | |
| kobject_put(hstate_kobjs[hi]); | |
| hstate_kobjs[hi] = NULL; | |
| return retval; | |
| } | |
| if (h->demote_order) { | |
| retval = sysfs_create_group(hstate_kobjs[hi], | |
| &hstate_demote_attr_group); | |
| if (retval) { | |
| pr_warn("HugeTLB unable to create demote interfaces for %s\n", h->name); | |
| sysfs_remove_group(hstate_kobjs[hi], hstate_attr_group); | |
| kobject_put(hstate_kobjs[hi]); | |
| hstate_kobjs[hi] = NULL; | |
| return retval; | |
| } | |
| } | |
| return 0; | |
| } | |
| #ifdef CONFIG_NUMA | |
| static bool hugetlb_sysfs_initialized __ro_after_init; | |
| /* | |
| * node_hstate/s - associate per node hstate attributes, via their kobjects, | |
| * with node devices in node_devices[] using a parallel array. The array | |
| * index of a node device or _hstate == node id. | |
| * This is here to avoid any static dependency of the node device driver, in | |
| * the base kernel, on the hugetlb module. | |
| */ | |
| struct node_hstate { | |
| struct kobject *hugepages_kobj; | |
| struct kobject *hstate_kobjs[HUGE_MAX_HSTATE]; | |
| }; | |
| static struct node_hstate node_hstates[MAX_NUMNODES]; | |
| /* | |
| * A subset of global hstate attributes for node devices | |
| */ | |
| static struct attribute *per_node_hstate_attrs[] = { | |
| &nr_hugepages_attr.attr, | |
| &free_hugepages_attr.attr, | |
| &surplus_hugepages_attr.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group per_node_hstate_attr_group = { | |
| .attrs = per_node_hstate_attrs, | |
| }; | |
| /* | |
| * kobj_to_node_hstate - lookup global hstate for node device hstate attr kobj. | |
| * Returns node id via non-NULL nidp. | |
| */ | |
| static struct hstate *kobj_to_node_hstate(struct kobject *kobj, int *nidp) | |
| { | |
| int nid; | |
| for (nid = 0; nid < nr_node_ids; nid++) { | |
| struct node_hstate *nhs = &node_hstates[nid]; | |
| int i; | |
| for (i = 0; i < HUGE_MAX_HSTATE; i++) | |
| if (nhs->hstate_kobjs[i] == kobj) { | |
| if (nidp) | |
| *nidp = nid; | |
| return &hstates[i]; | |
| } | |
| } | |
| BUG(); | |
| return NULL; | |
| } | |
| /* | |
| * Unregister hstate attributes from a single node device. | |
| * No-op if no hstate attributes attached. | |
| */ | |
| void hugetlb_unregister_node(struct node *node) | |
| { | |
| struct hstate *h; | |
| struct node_hstate *nhs = &node_hstates[node->dev.id]; | |
| if (!nhs->hugepages_kobj) | |
| return; /* no hstate attributes */ | |
| for_each_hstate(h) { | |
| int idx = hstate_index(h); | |
| struct kobject *hstate_kobj = nhs->hstate_kobjs[idx]; | |
| if (!hstate_kobj) | |
| continue; | |
| if (h->demote_order) | |
| sysfs_remove_group(hstate_kobj, &hstate_demote_attr_group); | |
| sysfs_remove_group(hstate_kobj, &per_node_hstate_attr_group); | |
| kobject_put(hstate_kobj); | |
| nhs->hstate_kobjs[idx] = NULL; | |
| } | |
| kobject_put(nhs->hugepages_kobj); | |
| nhs->hugepages_kobj = NULL; | |
| } | |
| /* | |
| * Register hstate attributes for a single node device. | |
| * No-op if attributes already registered. | |
| */ | |
| void hugetlb_register_node(struct node *node) | |
| { | |
| struct hstate *h; | |
| struct node_hstate *nhs = &node_hstates[node->dev.id]; | |
| int err; | |
| if (!hugetlb_sysfs_initialized) | |
| return; | |
| if (nhs->hugepages_kobj) | |
| return; /* already allocated */ | |
| nhs->hugepages_kobj = kobject_create_and_add("hugepages", | |
| &node->dev.kobj); | |
| if (!nhs->hugepages_kobj) | |
| return; | |
| for_each_hstate(h) { | |
| err = hugetlb_sysfs_add_hstate(h, nhs->hugepages_kobj, | |
| nhs->hstate_kobjs, | |
| &per_node_hstate_attr_group); | |
| if (err) { | |
| pr_err("HugeTLB: Unable to add hstate %s for node %d\n", | |
| h->name, node->dev.id); | |
| hugetlb_unregister_node(node); | |
| break; | |
| } | |
| } | |
| } | |
| /* | |
| * hugetlb init time: register hstate attributes for all registered node | |
| * devices of nodes that have memory. All on-line nodes should have | |
| * registered their associated device by this time. | |
| */ | |
| static void __init hugetlb_register_all_nodes(void) | |
| { | |
| int nid; | |
| for_each_online_node(nid) | |
| hugetlb_register_node(node_devices[nid]); | |
| } | |
| #else /* !CONFIG_NUMA */ | |
| static struct hstate *kobj_to_node_hstate(struct kobject *kobj, int *nidp) | |
| { | |
| BUG(); | |
| if (nidp) | |
| *nidp = -1; | |
| return NULL; | |
| } | |
| static void hugetlb_register_all_nodes(void) { } | |
| #endif | |
| void __init hugetlb_sysfs_init(void) | |
| { | |
| struct hstate *h; | |
| int err; | |
| hugepages_kobj = kobject_create_and_add("hugepages", mm_kobj); | |
| if (!hugepages_kobj) | |
| return; | |
| for_each_hstate(h) { | |
| err = hugetlb_sysfs_add_hstate(h, hugepages_kobj, | |
| hstate_kobjs, &hstate_attr_group); | |
| if (err) | |
| pr_err("HugeTLB: Unable to add hstate %s\n", h->name); | |
| } | |
| #ifdef CONFIG_NUMA | |
| hugetlb_sysfs_initialized = true; | |
| #endif | |
| hugetlb_register_all_nodes(); | |
| } |