From 4960e05e22604ee270a023f968e0e4f9bd0c6fef Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 8 May 2013 14:18:37 +0200 Subject: Driver core: Introduce offline/online callbacks for memory blocks Introduce .offline() and .online() callbacks for memory_subsys that will allow the generic device_offline() and device_online() to be used with device objects representing memory blocks. That, in turn, allows the ACPI subsystem to use device_offline() to put removable memory blocks offline, if possible, before removing memory modules holding them. The 'online' sysfs attribute of memory block devices will attempt to put them offline if 0 is written to it and will attempt to apply the previously used online type when onlining them (i.e. when 1 is written to it). Signed-off-by: Rafael J. Wysocki Tested-by: Vasilis Liaskovitis Acked-by: Greg Kroah-Hartman Reviewed-by: Toshi Kani --- drivers/base/memory.c | 112 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 25 deletions(-) (limited to 'drivers/base/memory.c') diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 14f8a6954da..c8f3b63fcac 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -37,9 +37,14 @@ static inline int base_memory_block_id(int section_nr) return section_nr / sections_per_block; } +static int memory_subsys_online(struct device *dev); +static int memory_subsys_offline(struct device *dev); + static struct bus_type memory_subsys = { .name = MEMORY_CLASS_NAME, .dev_name = MEMORY_CLASS_NAME, + .online = memory_subsys_online, + .offline = memory_subsys_offline, }; static BLOCKING_NOTIFIER_HEAD(memory_chain); @@ -88,6 +93,7 @@ int register_memory(struct memory_block *memory) memory->dev.bus = &memory_subsys; memory->dev.id = memory->start_section_nr / sections_per_block; memory->dev.release = memory_block_release; + memory->dev.offline = memory->state == MEM_OFFLINE; error = device_register(&memory->dev); return error; @@ -278,33 +284,70 @@ static int __memory_block_change_state(struct memory_block *mem, { int ret = 0; - if (mem->state != from_state_req) { - ret = -EINVAL; - goto out; - } + if (mem->state != from_state_req) + return -EINVAL; if (to_state == MEM_OFFLINE) mem->state = MEM_GOING_OFFLINE; ret = memory_block_action(mem->start_section_nr, to_state, online_type); - if (ret) { mem->state = from_state_req; - goto out; + } else { + mem->state = to_state; + if (to_state == MEM_ONLINE) + mem->last_online = online_type; } + return ret; +} - mem->state = to_state; - switch (mem->state) { - case MEM_OFFLINE: - kobject_uevent(&mem->dev.kobj, KOBJ_OFFLINE); - break; - case MEM_ONLINE: - kobject_uevent(&mem->dev.kobj, KOBJ_ONLINE); - break; - default: - break; +static int memory_subsys_online(struct device *dev) +{ + struct memory_block *mem = container_of(dev, struct memory_block, dev); + int ret; + + mutex_lock(&mem->state_mutex); + + ret = mem->state == MEM_ONLINE ? 0 : + __memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE, + mem->last_online); + + mutex_unlock(&mem->state_mutex); + return ret; +} + +static int memory_subsys_offline(struct device *dev) +{ + struct memory_block *mem = container_of(dev, struct memory_block, dev); + int ret; + + mutex_lock(&mem->state_mutex); + + ret = mem->state == MEM_OFFLINE ? 0 : + __memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE, -1); + + mutex_unlock(&mem->state_mutex); + return ret; +} + +static int __memory_block_change_state_uevent(struct memory_block *mem, + unsigned long to_state, unsigned long from_state_req, + int online_type) +{ + int ret = __memory_block_change_state(mem, to_state, from_state_req, + online_type); + if (!ret) { + switch (mem->state) { + case MEM_OFFLINE: + kobject_uevent(&mem->dev.kobj, KOBJ_OFFLINE); + break; + case MEM_ONLINE: + kobject_uevent(&mem->dev.kobj, KOBJ_ONLINE); + break; + default: + break; + } } -out: return ret; } @@ -315,8 +358,8 @@ static int memory_block_change_state(struct memory_block *mem, int ret; mutex_lock(&mem->state_mutex); - ret = __memory_block_change_state(mem, to_state, from_state_req, - online_type); + ret = __memory_block_change_state_uevent(mem, to_state, from_state_req, + online_type); mutex_unlock(&mem->state_mutex); return ret; @@ -326,22 +369,34 @@ store_mem_state(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct memory_block *mem; + bool offline; int ret = -EINVAL; mem = container_of(dev, struct memory_block, dev); - if (!strncmp(buf, "online_kernel", min_t(int, count, 13))) + lock_device_hotplug(); + + if (!strncmp(buf, "online_kernel", min_t(int, count, 13))) { + offline = false; ret = memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE, ONLINE_KERNEL); - else if (!strncmp(buf, "online_movable", min_t(int, count, 14))) + } else if (!strncmp(buf, "online_movable", min_t(int, count, 14))) { + offline = false; ret = memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE, ONLINE_MOVABLE); - else if (!strncmp(buf, "online", min_t(int, count, 6))) + } else if (!strncmp(buf, "online", min_t(int, count, 6))) { + offline = false; ret = memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE, ONLINE_KEEP); - else if(!strncmp(buf, "offline", min_t(int, count, 7))) + } else if(!strncmp(buf, "offline", min_t(int, count, 7))) { + offline = true; ret = memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE, -1); + } + if (!ret) + dev->offline = offline; + + unlock_device_hotplug(); if (ret) return ret; @@ -563,6 +618,7 @@ static int init_memory_block(struct memory_block **memory, base_memory_block_id(scn_nr) * sections_per_block; mem->end_section_nr = mem->start_section_nr + sections_per_block - 1; mem->state = state; + mem->last_online = ONLINE_KEEP; mem->section_count++; mutex_init(&mem->state_mutex); start_pfn = section_nr_to_pfn(mem->start_section_nr); @@ -681,14 +737,20 @@ int unregister_memory_section(struct mem_section *section) /* * offline one memory block. If the memory block has been offlined, do nothing. + * + * Call under device_hotplug_lock. */ int offline_memory_block(struct memory_block *mem) { int ret = 0; mutex_lock(&mem->state_mutex); - if (mem->state != MEM_OFFLINE) - ret = __memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE, -1); + if (mem->state != MEM_OFFLINE) { + ret = __memory_block_change_state_uevent(mem, MEM_OFFLINE, + MEM_ONLINE, -1); + if (!ret) + mem->dev.offline = true; + } mutex_unlock(&mem->state_mutex); return ret; -- cgit v1.2.3-70-g09d2 From b2c064b25ad07169b2892a733918e6b941bf3366 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 23 May 2013 10:38:55 +0200 Subject: Driver core / memory: Simplify __memory_block_change_state() As noted by Tang Chen, the last_online field in struct memory_block introduced by commit 4960e05 (Driver core: Introduce offline/online callbacks for memory blocks) is not really necessary, because online_pages() restores the previous state if passed ONLINE_KEEP as the last argument. Therefore, remove that field along with the code referring to it. References: http://marc.info/?l=linux-kernel&m=136919777305599&w=2 Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman Reviewed-by: Tang Chen --- drivers/base/memory.c | 11 ++--------- include/linux/memory.h | 1 - 2 files changed, 2 insertions(+), 10 deletions(-) (limited to 'drivers/base/memory.c') diff --git a/drivers/base/memory.c b/drivers/base/memory.c index c8f3b63fcac..c7092bc3c01 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -291,13 +291,7 @@ static int __memory_block_change_state(struct memory_block *mem, mem->state = MEM_GOING_OFFLINE; ret = memory_block_action(mem->start_section_nr, to_state, online_type); - if (ret) { - mem->state = from_state_req; - } else { - mem->state = to_state; - if (to_state == MEM_ONLINE) - mem->last_online = online_type; - } + mem->state = ret ? from_state_req : to_state; return ret; } @@ -310,7 +304,7 @@ static int memory_subsys_online(struct device *dev) ret = mem->state == MEM_ONLINE ? 0 : __memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE, - mem->last_online); + ONLINE_KEEP); mutex_unlock(&mem->state_mutex); return ret; @@ -618,7 +612,6 @@ static int init_memory_block(struct memory_block **memory, base_memory_block_id(scn_nr) * sections_per_block; mem->end_section_nr = mem->start_section_nr + sections_per_block - 1; mem->state = state; - mem->last_online = ONLINE_KEEP; mem->section_count++; mutex_init(&mem->state_mutex); start_pfn = section_nr_to_pfn(mem->start_section_nr); diff --git a/include/linux/memory.h b/include/linux/memory.h index 3d534658302..85c31a8e290 100644 --- a/include/linux/memory.h +++ b/include/linux/memory.h @@ -26,7 +26,6 @@ struct memory_block { unsigned long start_section_nr; unsigned long end_section_nr; unsigned long state; - int last_online; int section_count; /* -- cgit v1.2.3-70-g09d2 From ea50be59345a2b714fd3ed43e1bba89906c177c3 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 23 May 2013 10:41:50 +0200 Subject: Driver core / MM: Drop offline_memory_block() Since offline_memory_block(mem) is functionally equivalent to device_offline(&mem->dev), make the only caller of the former use the latter instead and drop offline_memory_block() entirely. Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman Acked-by: Toshi Kani --- drivers/base/memory.c | 21 --------------------- include/linux/memory_hotplug.h | 1 - mm/memory_hotplug.c | 2 +- 3 files changed, 1 insertion(+), 23 deletions(-) (limited to 'drivers/base/memory.c') diff --git a/drivers/base/memory.c b/drivers/base/memory.c index c7092bc3c01..4ebf97f99fa 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -728,27 +728,6 @@ int unregister_memory_section(struct mem_section *section) } #endif /* CONFIG_MEMORY_HOTREMOVE */ -/* - * offline one memory block. If the memory block has been offlined, do nothing. - * - * Call under device_hotplug_lock. - */ -int offline_memory_block(struct memory_block *mem) -{ - int ret = 0; - - mutex_lock(&mem->state_mutex); - if (mem->state != MEM_OFFLINE) { - ret = __memory_block_change_state_uevent(mem, MEM_OFFLINE, - MEM_ONLINE, -1); - if (!ret) - mem->dev.offline = true; - } - mutex_unlock(&mem->state_mutex); - - return ret; -} - /* return true if the memory block is offlined, otherwise, return false */ bool is_memblock_offlined(struct memory_block *mem) { diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h index 2975b7b2a9d..ae5480a0096 100644 --- a/include/linux/memory_hotplug.h +++ b/include/linux/memory_hotplug.h @@ -251,7 +251,6 @@ extern int mem_online_node(int nid); extern int add_memory(int nid, u64 start, u64 size); extern int arch_add_memory(int nid, u64 start, u64 size); extern int offline_pages(unsigned long start_pfn, unsigned long nr_pages); -extern int offline_memory_block(struct memory_block *mem); extern bool is_memblock_offlined(struct memory_block *mem); extern int remove_memory(int nid, u64 start, u64 size); extern int sparse_add_one_section(struct zone *zone, unsigned long start_pfn, diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c index 5ea1287ee91..a39841d240e 100644 --- a/mm/memory_hotplug.c +++ b/mm/memory_hotplug.c @@ -1680,7 +1680,7 @@ int walk_memory_range(unsigned long start_pfn, unsigned long end_pfn, static int offline_memory_block_cb(struct memory_block *mem, void *arg) { int *ret = arg; - int error = offline_memory_block(mem); + int error = device_offline(&mem->dev); if (error != 0 && *ret == 0) *ret = error; -- cgit v1.2.3-70-g09d2 From 96b2c0fc8e74a615888e2bedfe55b439aa4695e1 Mon Sep 17 00:00:00 2001 From: Nathan Fontenot Date: Tue, 4 Jun 2013 14:42:28 -0500 Subject: drivers/base: Use attribute groups to create sysfs memory files Update the sysfs memory code to create/delete files at the time of device and subsystem registration. The current code creates files in the root memory directory explicitly through the use of init_* routines. The files for each memory block are created and deleted explicitly using the mem_[create|delete]_simple_file macros. This patch creates attribute groups for the memory root files and files in each memory block directory so that they are created and deleted implicitly at subsys and device register and unregister time. This did necessitate moving the register_memory() updating it to set the dev.groups field. Signed-off-by: Nathan Fontenot Signed-off-by: Greg Kroah-Hartman --- drivers/base/memory.c | 143 ++++++++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 81 deletions(-) (limited to 'drivers/base/memory.c') diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 14f8a6954da..e315051cfee 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -77,22 +77,6 @@ static void memory_block_release(struct device *dev) kfree(mem); } -/* - * register_memory - Setup a sysfs device for a memory block - */ -static -int register_memory(struct memory_block *memory) -{ - int error; - - memory->dev.bus = &memory_subsys; - memory->dev.id = memory->start_section_nr / sections_per_block; - memory->dev.release = memory_block_release; - - error = device_register(&memory->dev); - return error; -} - unsigned long __weak memory_block_size_bytes(void) { return MIN_MEMORY_BLOCK_SIZE; @@ -371,11 +355,6 @@ static DEVICE_ATTR(state, 0644, show_mem_state, store_mem_state); static DEVICE_ATTR(phys_device, 0444, show_phys_device, NULL); static DEVICE_ATTR(removable, 0444, show_mem_removable, NULL); -#define mem_create_simple_file(mem, attr_name) \ - device_create_file(&mem->dev, &dev_attr_##attr_name) -#define mem_remove_simple_file(mem, attr_name) \ - device_remove_file(&mem->dev, &dev_attr_##attr_name) - /* * Block size attribute stuff */ @@ -388,12 +367,6 @@ print_block_size(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(block_size_bytes, 0444, print_block_size, NULL); -static int block_size_init(void) -{ - return device_create_file(memory_subsys.dev_root, - &dev_attr_block_size_bytes); -} - /* * Some architectures will have custom drivers to do this, and * will not need to do it from userspace. The fake hot-add code @@ -429,17 +402,8 @@ memory_probe_store(struct device *dev, struct device_attribute *attr, out: return ret; } -static DEVICE_ATTR(probe, S_IWUSR, NULL, memory_probe_store); -static int memory_probe_init(void) -{ - return device_create_file(memory_subsys.dev_root, &dev_attr_probe); -} -#else -static inline int memory_probe_init(void) -{ - return 0; -} +static DEVICE_ATTR(probe, S_IWUSR, NULL, memory_probe_store); #endif #ifdef CONFIG_MEMORY_FAILURE @@ -485,23 +449,6 @@ store_hard_offline_page(struct device *dev, static DEVICE_ATTR(soft_offline_page, S_IWUSR, NULL, store_soft_offline_page); static DEVICE_ATTR(hard_offline_page, S_IWUSR, NULL, store_hard_offline_page); - -static __init int memory_fail_init(void) -{ - int err; - - err = device_create_file(memory_subsys.dev_root, - &dev_attr_soft_offline_page); - if (!err) - err = device_create_file(memory_subsys.dev_root, - &dev_attr_hard_offline_page); - return err; -} -#else -static inline int memory_fail_init(void) -{ - return 0; -} #endif /* @@ -546,6 +493,41 @@ struct memory_block *find_memory_block(struct mem_section *section) return find_memory_block_hinted(section, NULL); } +static struct attribute *memory_memblk_attrs[] = { + &dev_attr_phys_index.attr, + &dev_attr_end_phys_index.attr, + &dev_attr_state.attr, + &dev_attr_phys_device.attr, + &dev_attr_removable.attr, + NULL +}; + +static struct attribute_group memory_memblk_attr_group = { + .attrs = memory_memblk_attrs, +}; + +static const struct attribute_group *memory_memblk_attr_groups[] = { + &memory_memblk_attr_group, + NULL, +}; + +/* + * register_memory - Setup a sysfs device for a memory block + */ +static +int register_memory(struct memory_block *memory) +{ + int error; + + memory->dev.bus = &memory_subsys; + memory->dev.id = memory->start_section_nr / sections_per_block; + memory->dev.release = memory_block_release; + memory->dev.groups = memory_memblk_attr_groups; + + error = device_register(&memory->dev); + return error; +} + static int init_memory_block(struct memory_block **memory, struct mem_section *section, unsigned long state) { @@ -569,16 +551,6 @@ static int init_memory_block(struct memory_block **memory, mem->phys_device = arch_get_memory_phys_device(start_pfn); ret = register_memory(mem); - if (!ret) - ret = mem_create_simple_file(mem, phys_index); - if (!ret) - ret = mem_create_simple_file(mem, end_phys_index); - if (!ret) - ret = mem_create_simple_file(mem, state); - if (!ret) - ret = mem_create_simple_file(mem, phys_device); - if (!ret) - ret = mem_create_simple_file(mem, removable); *memory = mem; return ret; @@ -656,14 +628,9 @@ static int remove_memory_block(unsigned long node_id, unregister_mem_sect_under_nodes(mem, __section_nr(section)); mem->section_count--; - if (mem->section_count == 0) { - mem_remove_simple_file(mem, phys_index); - mem_remove_simple_file(mem, end_phys_index); - mem_remove_simple_file(mem, state); - mem_remove_simple_file(mem, phys_device); - mem_remove_simple_file(mem, removable); + if (mem->section_count == 0) unregister_memory(mem); - } else + else kobject_put(&mem->dev.kobj); mutex_unlock(&mem_sysfs_mutex); @@ -700,6 +667,29 @@ bool is_memblock_offlined(struct memory_block *mem) return mem->state == MEM_OFFLINE; } +static struct attribute *memory_root_attrs[] = { +#ifdef CONFIG_ARCH_MEMORY_PROBE + &dev_attr_probe.attr, +#endif + +#ifdef CONFIG_MEMORY_FAILURE + &dev_attr_soft_offline_page.attr, + &dev_attr_hard_offline_page.attr, +#endif + + &dev_attr_block_size_bytes.attr, + NULL +}; + +static struct attribute_group memory_root_attr_group = { + .attrs = memory_root_attrs, +}; + +static const struct attribute_group *memory_root_attr_groups[] = { + &memory_root_attr_group, + NULL, +}; + /* * Initialize the sysfs support for memory devices... */ @@ -711,7 +701,7 @@ int __init memory_dev_init(void) unsigned long block_sz; struct memory_block *mem = NULL; - ret = subsys_system_register(&memory_subsys, NULL); + ret = subsys_system_register(&memory_subsys, memory_root_attr_groups); if (ret) goto out; @@ -734,15 +724,6 @@ int __init memory_dev_init(void) ret = err; } - err = memory_probe_init(); - if (!ret) - ret = err; - err = memory_fail_init(); - if (!ret) - ret = err; - err = block_size_init(); - if (!ret) - ret = err; out: if (ret) printk(KERN_ERR "%s() failed: %d\n", __func__, ret); -- cgit v1.2.3-70-g09d2