[PATCH RFC] ACPI: container: Add power domain control methods

From: Zhang Zekun
Date: Tue Oct 25 2022 - 02:17:17 EST


Platform devices which supports power control are often required to be
power off/on together with the devices in the same power domain. However,
there isn't a generic driver that support the power control logic of
these devices.

ACPI container seems to be a good place to hold these control logic. Add
platform devices in the same power domain in a ACPI container, we can
easily get the locality information about these devices and can moniter
the power of these devices in the same power domain together.

This patch provide three userspace control interface to control the power
of devices together in the container:
- on: power up the devices in the container and then online these devices
which will be triggered by BIOS.
- off: offline and eject the child devices in the container which are
ejectable.
- pxms: show the pxms of devices which are present in the container.

In our scenario, we need to control the power of HBM memory devices which
can be power consuming and will only be used in some specialized scenarios,
such as HPC. HBM memory devices in a socket are in the same power domain,
and should be power off/on together. We have come up with an idea that put
these power control logic in a specialized driver, but ACPI container seems
to be a more generic place to hold these control logic.

Signed-off-by: Zhang Zekun <zhangzekun11@xxxxxxxxxx>
---
drivers/acpi/Kconfig | 12 +++++
drivers/acpi/container.c | 112 +++++++++++++++++++++++++++++++++++++++
2 files changed, 124 insertions(+)

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 473241b5193f..ebb26d56dba0 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -584,6 +584,18 @@ config ACPI_PRMT
substantially increase computational overhead related to the
initialization of some server systems.

+config ACPI_POWER_DOMAIN_CTL
+ bool "acpi container power domain control support"
+ depends on ACPI_CONTAINER
+ default n
+ help
+ Add userspace power control interfaces in container which can be used
+ for manipulating the power of child devices in the same power domain.
+
+ To use this feature you need to put devices in the same power domain
+ in a container. Enable this feature if you want to control the power
+ of these devices together.
+
endif # ACPI

config X86_PM_TIMER
diff --git a/drivers/acpi/container.c b/drivers/acpi/container.c
index 5b7e3b9ae370..9ed2eb5a3dcc 100644
--- a/drivers/acpi/container.c
+++ b/drivers/acpi/container.c
@@ -42,6 +42,115 @@ static void acpi_container_release(struct device *dev)
kfree(to_container_dev(dev));
}

+#ifdef CONFIG_ACPI_POWER_DOMAIN_CTL
+
+static int get_pxm(struct acpi_device *acpi_device, void *arg)
+{
+ int nid;
+ unsigned long long sta;
+ acpi_handle handle;
+ nodemask_t *mask;
+ acpi_status status;
+
+ mask = arg;
+ handle = acpi_device->handle;
+
+ 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)
+{
+ nodemask_t mask;
+ acpi_status status;
+ struct acpi_device *adev;
+
+ adev = to_acpi_device(dev);
+ nodes_clear(mask);
+
+ status = acpi_dev_for_each_child(adev, get_pxm, &mask);
+
+ return sysfs_emit(buf, "%*pbl\n", nodemask_pr_args(&mask));
+}
+DEVICE_ATTR_RO(pxms);
+
+static ssize_t on_store(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ acpi_status status;
+ acpi_handle handle;
+ struct acpi_device *adev;
+
+ if (!count || buf[0] != '1')
+ return -EINVAL;
+
+ adev = to_acpi_device(d);
+ handle = adev->handle;
+ status = acpi_evaluate_object(handle, "_ON", NULL, NULL);
+ if (status == AE_NOT_FOUND)
+ acpi_handle_warn(handle, "No power on support for the container\n");
+ else if (ACPI_FAILURE(status))
+ acpi_handle_warn(handle, "Power on the device failed (0x%x)\n", status);
+
+ return count;
+}
+DEVICE_ATTR_WO(on);
+
+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;
+
+ acpi_dev_get(acpi_device);
+ status = acpi_hotplug_schedule(acpi_device, ACPI_OST_EC_OSPM_EJECT);
+ if (ACPI_SUCCESS(status))
+ return status;
+
+ acpi_dev_put(acpi_device);
+ 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 ssize_t off_store(struct device *d, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct acpi_device *adev;
+ acpi_status status;
+
+ if (!count || buf[0] != '1')
+ return -EINVAL;
+
+ adev = to_acpi_device(d);
+ status = acpi_dev_for_each_child(adev, eject_device, NULL);
+ if (ACPI_SUCCESS(status))
+ return count;
+
+ return status;
+}
+DEVICE_ATTR_WO(off);
+
+static void create_sysfs(struct device *dev)
+{
+ device_create_file(dev, &dev_attr_on);
+ device_create_file(dev, &dev_attr_off);
+ device_create_file(dev, &dev_attr_pxms);
+}
+#endif
+
static int container_device_attach(struct acpi_device *adev,
const struct acpi_device_id *not_used)
{
@@ -68,6 +177,9 @@ static int container_device_attach(struct acpi_device *adev,
return ret;
}
adev->driver_data = dev;
+#ifdef CONFIG_ACPI_POWER_DOMAIN_CTL
+ create_sysfs(&adev->dev);
+#endif
return 1;
}

--
2.30.0