diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index b02fd51e55896e1d3ec291f1f515d96ec72c8a52..e9b9e2db32cf33e0d7ca5d618b61d8a09a4b29c6 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -56,6 +56,9 @@ struct acpi_memory_device { struct list_head res_list; }; +struct acpi_device *hotplug_mdev[MAX_NUMNODES]; +EXPORT_SYMBOL_GPL(hotplug_mdev); + static acpi_status acpi_memory_get_resource(struct acpi_resource *resource, void *context) { @@ -217,6 +220,8 @@ static int acpi_memory_enable_device(struct acpi_memory_device *mem_device) * Add num_enable even if add_memory() returns -EEXIST, so the * device is bound to this driver. */ + + hotplug_mdev[node] = mem_device->device; num_enabled++; } if (!num_enabled) { @@ -240,6 +245,7 @@ static void acpi_memory_remove_memory(struct acpi_memory_device *mem_device) struct acpi_memory_info *info, *n; int nid = acpi_get_node(handle); + hotplug_mdev[nid] = NULL; list_for_each_entry_safe(info, n, &mem_device->res_list, list) { if (!info->enabled) continue; diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index a6a6161474c5d46fdfe9785a2a313a2b7d9569bd..50a65ff16d5af81078e6895bb3c90417dda5d410 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -966,6 +966,33 @@ struct bus_type acpi_bus_type = { .uevent = acpi_device_uevent, }; +struct acpi_dev_walk_context { + int (*fn)(struct acpi_device *, void *); + void *data; +}; + +static int acpi_dev_for_one_check(struct device *dev, void *context) +{ + struct acpi_dev_walk_context *adwc = context; + + if (dev->bus != &acpi_bus_type) + return 0; + + return adwc->fn(to_acpi_device(dev), adwc->data); +} + +int acpi_dev_for_each_child(struct acpi_device *adev, + int (*fn)(struct acpi_device *, void *), void *data) +{ + struct acpi_dev_walk_context adwc = { + .fn = fn, + .data = data, + }; + + return device_for_each_child(&adev->dev, &adwc, acpi_dev_for_one_check); +} +EXPORT_SYMBOL_GPL(acpi_dev_for_each_child); + /* -------------------------------------------------------------------------- Initialization/Cleanup -------------------------------------------------------------------------- */ diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 125e4901c9b47834238778e61a7315108ab976a9..5ac34c0eeb475f5f82ec659357cea3ae23fa8b32 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -82,7 +82,6 @@ static inline void acpi_lpss_init(void) {} void acpi_apd_init(void); -acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src); bool acpi_queue_hotplug_work(struct work_struct *work); void acpi_device_hotplug(struct acpi_device *adev, u32 src); bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent); diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 0418febc5cf207d6ed888cd834820cdbd35c2cb9..b11c74f229a5440ad7130b10e0562412cfb94fba 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -1188,6 +1188,7 @@ acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src) } return AE_OK; } +EXPORT_SYMBOL_GPL(acpi_hotplug_schedule); bool acpi_queue_hotplug_work(struct work_struct *work) { diff --git a/drivers/base/container.c b/drivers/base/container.c index 1ba42d2d353223e683be57bad5154df07abda10e..12e572d0c69ba93a5e093a3082fd66889ed35567 100644 --- a/drivers/base/container.c +++ b/drivers/base/container.c @@ -30,6 +30,7 @@ struct bus_type container_subsys = { .online = trivial_online, .offline = container_offline, }; +EXPORT_SYMBOL_GPL(container_subsys); void __init container_dev_init(void) { diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index 425ab6f7e375f72263ae3138e8478d659646085e..f7c59b063321df2244cf265dbbc1af8e18a370fc 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -23,5 +23,6 @@ source "drivers/soc/versatile/Kconfig" source "drivers/soc/xilinx/Kconfig" source "drivers/soc/zte/Kconfig" source "drivers/soc/kendryte/Kconfig" +source "drivers/soc/hisilicon/Kconfig" endmenu diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 36452bed86efb38e7286f3392a6a6dbf7d964631..68f186e00e44c8957983f19f80110518435878df 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -29,3 +29,4 @@ obj-$(CONFIG_PLAT_VERSATILE) += versatile/ obj-y += xilinx/ obj-$(CONFIG_ARCH_ZX) += zte/ obj-$(CONFIG_SOC_KENDRYTE) += kendryte/ +obj-y += hisilicon/ diff --git a/drivers/soc/hisilicon/Kconfig b/drivers/soc/hisilicon/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..160fdadd76bebaf212932cb468f05c2c5883c743 --- /dev/null +++ b/drivers/soc/hisilicon/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Hisilicon SoC drivers +# +menu "Hisilicon SoC driver support" + +config HISI_HBMDEV + tristate "add extra support for hbm memory device" + depends on ACPI_HOTPLUG_MEMORY + select ACPI_CONTAINER + help + This driver add two extra supports for memory devices. The driver + provides methods for userpace to control the power of memory devices + in a container. Besides, it provides extra locality information + between cpus and memory devices for userspace, which can take + advantage of this functionality to select the closet memory device + to a certain cpu. + + To compile this driver as a module, choose M here: + the module will be called hisi_hbmdev. + +config HISI_HBMCACHE + tristate "HBM cache memory device" + depends on ACPI + help + This driver provids methods to control the power of hbm cache device + in hisi soc. Use hbm as a cache can take advantage of hbm's high + bandwidth in normal memory access. + + To compile the driver as a module, choose M here: + the module will be called hisi_hbmcache. + +endmenu diff --git a/drivers/soc/hisilicon/Makefile b/drivers/soc/hisilicon/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5a1ed2d535937c43779fe56983357b8d3617442e --- /dev/null +++ b/drivers/soc/hisilicon/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_HISI_HBMDEV) += hisi_hbmdev.o +obj-$(CONFIG_HISI_HBMCACHE) += hisi_hbmcache.o diff --git a/drivers/soc/hisilicon/hisi_hbmcache.c b/drivers/soc/hisilicon/hisi_hbmcache.c new file mode 100644 index 0000000000000000000000000000000000000000..34121320742ea6f151380470c8df0f88bfd8f129 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_hbmcache.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Huawei Technologies Co., Ltd. 2023. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "hisi_internal.h" + +#define MODULE_NAME "hbm_cache" + +static struct kobject *cache_kobj; + +static ssize_t state_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *adev = ACPI_COMPANION(d); + const int type = online_type_from_str(buf); + int ret = -EINVAL; + + switch (type) { + case STATE_ONLINE: + ret = acpi_device_set_power(adev, ACPI_STATE_D0); + break; + case STATE_OFFLINE: + ret = acpi_device_set_power(adev, ACPI_STATE_D3); + break; + default: + break; + } + + if (ret) + return ret; + + return count; +} + +static ssize_t state_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *adev = ACPI_COMPANION(d); + unsigned long long sta = 0; + acpi_status status; + const char *output; + + status = acpi_evaluate_integer(adev->handle, "_STA", NULL, &sta); + if (ACPI_FAILURE(status)) + return -EINVAL; + + output = (sta & 0x01) ? online_type_to_str[STATE_ONLINE] : + online_type_to_str[STATE_OFFLINE]; + + return sysfs_emit(buf, "%s\n", output); +} +static DEVICE_ATTR_RW(state); + +static ssize_t socket_id_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + int socket_id; + + if (device_property_read_u32(d, "socket_id", &socket_id)) + return -EINVAL; + + return sysfs_emit(buf, "%d\n", socket_id); +} +static DEVICE_ATTR_RO(socket_id); + +static struct attribute *attrs[] = { + &dev_attr_state.attr, + &dev_attr_socket_id.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static int cache_probe(struct platform_device *pdev) +{ + int ret; + + ret = sysfs_create_group(&pdev->dev.kobj, &attr_group); + if (ret) + return ret; + + ret = sysfs_create_link(cache_kobj, + &pdev->dev.kobj, + kobject_name(&pdev->dev.kobj)); + if (ret) { + sysfs_remove_group(&pdev->dev.kobj, &attr_group); + return ret; + } + + return 0; +} + +static int cache_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &attr_group); + sysfs_remove_link(&pdev->dev.kobj, + kobject_name(&pdev->dev.kobj)); + return 0; +} + +static const struct acpi_device_id cache_acpi_ids[] = { + {"HISI04A1"}, + {}, +}; + +static struct platform_driver hbm_cache_driver = { + .probe = cache_probe, + .remove = cache_remove, + .driver = { + .name = MODULE_NAME, + .acpi_match_table = ACPI_PTR(cache_acpi_ids), + }, +}; + +static int __init hbm_cache_module_init(void) +{ + int ret; + + cache_kobj = kobject_create_and_add("hbm_cache", kernel_kobj); + if (!cache_kobj) + return -ENOMEM; + + ret = platform_driver_register(&hbm_cache_driver); + if (ret) { + kobject_put(cache_kobj); + return ret; + } + return 0; +} +module_init(hbm_cache_module_init); + +static void __exit hbm_cache_module_exit(void) +{ + kobject_put(cache_kobj); + platform_driver_unregister(&hbm_cache_driver); +} +module_exit(hbm_cache_module_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/hisilicon/hisi_hbmdev.c b/drivers/soc/hisilicon/hisi_hbmdev.c new file mode 100644 index 0000000000000000000000000000000000000000..5b6b1618148c537df80ed541304bab63c6a05d90 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_hbmdev.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Huawei Technologies Co., Ltd. 2023. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hisi_internal.h" + +#define ACPI_MEMORY_DEVICE_HID "PNP0C80" +#define ACPI_GENERIC_CONTAINER_DEVICE_HID "PNP0A06" + +struct cdev_node { + struct device *dev; + struct list_head clist; +}; + +struct memory_dev { + struct kobject *memdev_kobj; + struct kobject *topo_kobj; + struct cdev_node cdev_list; + nodemask_t cluster_cpumask[MAX_NUMNODES]; +}; + +static struct memory_dev *mdev; + +static ssize_t memory_locality_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + int i, count = 0; + + for (i = 0; i < MAX_NUMNODES; i++) { + if (hotplug_mdev[i] != NULL && !nodes_empty(mdev->cluster_cpumask[i])) { + count += sysfs_emit_at(buf, count, "%d %*pbl\n", i, + nodemask_pr_args(&mdev->cluster_cpumask[i])); + } + } + + return count; +} + +static struct kobj_attribute memory_locality_attribute = + __ATTR(memory_locality, 0444, memory_locality_show, NULL); + +static void memory_topo_init(void) +{ + int ret, nid, cluster_id, cpu; + struct acpi_device *adev; + nodemask_t mask; + + for (nid = 0; nid < MAX_NUMNODES; nid++) { + if (!hotplug_mdev[nid]) + continue; + + adev = hotplug_mdev[nid]; + ret = fwnode_property_read_u32(acpi_fwnode_handle(adev), + "cluster-id", &cluster_id); + if (ret < 0) { + pr_debug("Failed to read cluster id\n"); + return; + } + + nodes_clear(mask); + for_each_possible_cpu(cpu) { + if (topology_cluster_id(cpu) == cluster_id) + node_set(cpu, mask); + } + mdev->cluster_cpumask[nid] = mask; + } + + mdev->topo_kobj = kobject_create_and_add("memory_topo", mdev->memdev_kobj); + if (!mdev->topo_kobj) + return; + + ret = sysfs_create_file(mdev->topo_kobj, &memory_locality_attribute.attr); + if (ret) + kobject_put(mdev->topo_kobj); +} + +static int get_pxm(struct acpi_device *acpi_device, void *arg) +{ + acpi_handle handle = acpi_device->handle; + nodemask_t *mask = arg; + unsigned long long sta; + acpi_status status; + int nid; + + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); + if (ACPI_SUCCESS(status) && (sta & ACPI_STA_DEVICE_ENABLED)) { + nid = acpi_get_node(handle); + if (nid >= 0) + node_set(nid, *mask); + } + + return 0; +} + +static ssize_t pxms_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + nodemask_t mask; + + nodes_clear(mask); + acpi_dev_for_each_child(adev, get_pxm, &mask); + + return sysfs_emit(buf, "%*pbl\n", + nodemask_pr_args(&mask)); +} +static DEVICE_ATTR_RO(pxms); + +static int memdev_power_on(struct acpi_device *adev) +{ + acpi_handle handle = adev->handle; + acpi_status status; + + status = acpi_evaluate_object(handle, "_ON", NULL, NULL); + if (ACPI_FAILURE(status)) { + acpi_handle_warn(handle, "Power on failed (0x%x)\n", status); + return -ENODEV; + } + + return 0; +} + +static int eject_device(struct acpi_device *acpi_device, void *not_used) +{ + acpi_object_type unused; + acpi_status status; + + status = acpi_get_type(acpi_device->handle, &unused); + if (ACPI_FAILURE(status) || !acpi_device->flags.ejectable) + return -ENODEV; + + get_device(&acpi_device->dev); + status = acpi_hotplug_schedule(acpi_device, ACPI_OST_EC_OSPM_EJECT); + if (ACPI_SUCCESS(status)) + return 0; + + put_device(&acpi_device->dev); + acpi_evaluate_ost(acpi_device->handle, ACPI_OST_EC_OSPM_EJECT, + ACPI_OST_SC_NON_SPECIFIC_FAILURE, NULL); + + return status == AE_NO_MEMORY ? -ENOMEM : -EAGAIN; +} + +static int memdev_power_off(struct acpi_device *adev) +{ + return acpi_dev_for_each_child(adev, eject_device, NULL); +} + +static ssize_t state_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + const int type = online_type_from_str(buf); + int ret = -EINVAL; + + switch (type) { + case STATE_ONLINE: + ret = memdev_power_on(adev); + break; + case STATE_OFFLINE: + ret = memdev_power_off(adev); + break; + default: + break; + } + + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(state); + +static int hbmdev_find(struct acpi_device *adev, void *arg) +{ + const char *hid = acpi_device_hid(adev); + bool *found = arg; + + if (!strcmp(hid, ACPI_MEMORY_DEVICE_HID)) { + *found = true; + return -1; + } + + return 0; +} + +static bool has_hbmdev(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + const char *hid = acpi_device_hid(adev); + bool found = false; + + if (strcmp(hid, ACPI_GENERIC_CONTAINER_DEVICE_HID)) + return found; + + acpi_dev_for_each_child(adev, hbmdev_find, &found); + + return found; +} + +static int container_add(struct device *dev, void *data) +{ + struct cdev_node *cnode; + + if (!has_hbmdev(dev)) + return 0; + + cnode = kmalloc(sizeof(struct cdev_node), GFP_KERNEL); + if (!cnode) + return -ENOMEM; + + cnode->dev = dev; + list_add_tail(&cnode->clist, &mdev->cdev_list.clist); + + return 0; +} + +static void container_remove(void) +{ + struct cdev_node *cnode, *tmp; + + list_for_each_entry_safe(cnode, tmp, &mdev->cdev_list.clist, clist) { + device_remove_file(cnode->dev, &dev_attr_state); + device_remove_file(cnode->dev, &dev_attr_pxms); + list_del(&cnode->clist); + kfree(cnode); + } +} + +static int container_init(void) +{ + struct cdev_node *cnode; + + INIT_LIST_HEAD(&mdev->cdev_list.clist); + + if (bus_for_each_dev(&container_subsys, NULL, NULL, container_add)) { + container_remove(); + return -ENOMEM; + } + + if (list_empty(&mdev->cdev_list.clist)) + return -ENODEV; + + list_for_each_entry(cnode, &mdev->cdev_list.clist, clist) { + device_create_file(cnode->dev, &dev_attr_state); + device_create_file(cnode->dev, &dev_attr_pxms); + } + + return 0; +} + + +static int __init mdev_init(void) +{ + int ret; + + mdev = kzalloc(sizeof(struct memory_dev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + ret = container_init(); + if (ret) { + kfree(mdev); + return ret; + } + + mdev->memdev_kobj = kobject_create_and_add("hbm_memory", kernel_kobj); + if (!mdev->memdev_kobj) { + container_remove(); + kfree(mdev); + return -ENOMEM; + } + + memory_topo_init(); + return ret; +} +module_init(mdev_init); + +static void __exit mdev_exit(void) +{ + container_remove(); + kobject_put(mdev->memdev_kobj); + kobject_put(mdev->topo_kobj); + kfree(mdev); +} +module_exit(mdev_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Zhang Zekun "); diff --git a/drivers/soc/hisilicon/hisi_internal.h b/drivers/soc/hisilicon/hisi_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..5345174f6b84f16400562c80ae87676e33820c39 --- /dev/null +++ b/drivers/soc/hisilicon/hisi_internal.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Huawei Technologies Co., Ltd. 2023. All rights reserved. + */ + +#ifndef _HISI_INTERNAL_H +#define _HISI_INTERNAL_H + +enum { + STATE_ONLINE, + STATE_OFFLINE, +}; + +static const char *const online_type_to_str[] = { + [STATE_ONLINE] = "online", + [STATE_OFFLINE] = "offline", +}; + +static inline int online_type_from_str(const char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(online_type_to_str); i++) { + if (sysfs_streq(str, online_type_to_str[i])) + return i; + } + + return -EINVAL; +} + +#endif diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index b5ff52052db3809190ba7f2695132bf30461e379..fc0693c1b87e97e18a1289161563ed4644c3c4b4 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -494,6 +494,8 @@ void acpi_bus_detach_private_data(acpi_handle); extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32); extern int register_acpi_notifier(struct notifier_block *); extern int unregister_acpi_notifier(struct notifier_block *); +int acpi_dev_for_each_child(struct acpi_device *adev, + int (*fn)(struct acpi_device *, void *), void *data); /* * External Functions diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 473dbb701700d370dfc2a25ac0501537429b717a..7c14b71464df3552c0c18123b173f50242f28e29 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1407,5 +1407,6 @@ acpi_platform_notify(struct device *dev, enum kobject_action action) struct acpi_pptt_processor * acpi_pptt_find_cache_backwards(struct acpi_table_header *table_hdr, struct acpi_pptt_cache *cache); +acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src); #endif /*_LINUX_ACPI_H*/ diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h index b9aeabcce49aaea251c7c3e515ed9fba8145e0d9..3debe1f7a6c192e78f52911d343aa0ee5165c8fb 100644 --- a/include/linux/memory_hotplug.h +++ b/include/linux/memory_hotplug.h @@ -316,6 +316,8 @@ extern void set_zone_contiguous(struct zone *zone); extern void clear_zone_contiguous(struct zone *zone); #ifdef CONFIG_MEMORY_HOTPLUG +extern struct acpi_device *hotplug_mdev[MAX_NUMNODES]; + extern void __ref free_area_init_core_hotplug(int nid); extern int __add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags); extern int add_memory(int nid, u64 start, u64 size, mhp_t mhp_flags);