diff mbox series

[1/3] ACPI / adxl: Address translation interface using ACPI DSM

Message ID 20181009183355.20597-2-tony.luck@intel.com (mailing list archive)
State Superseded, archived
Headers show
Series ACPI ADXL and associated EDAC driver change | expand

Commit Message

Tony Luck Oct. 9, 2018, 6:33 p.m. UTC
Some new servers provide an interface so that the OS can ask the
BIOS to translate a system physical address to a memory address
(socket, memory controller, channel, rank, dimm, etc.). This is
useful for EDAC drivers that want to take the address of an error
reported in a machine check bank and let the user know which
DIMM may need to be replaced.

Specification for this interface is available at:

    https://cdrdv2.intel.com/v1/dl/getContent/603354

[Based on earlier code by Qiuxu Zhuo <qiuxu.zhuo@intel.com>]

Signed-off-by: Tony Luck <tony.luck@intel.com>

---
Comments addressed since last version:
   Rafael:
	Added URL for specification (to commit & header comment)
	Fixed NULL error return from error case of adxl_dsm()

   Boris:
	Added sanity check on number of address components
	(also added check that number of values returned by
	 decode operation matches number of strings)
	g/pr_debug/s//pr_info/
	Added "." at end of sentences in comments
	Make return from adxl_get_component_names() immutable

 drivers/acpi/Kconfig     |  10 ++
 drivers/acpi/Makefile    |   3 +
 drivers/acpi/acpi_adxl.c | 199 +++++++++++++++++++++++++++++++++++++++
 include/linux/adxl.h     |  25 +++++
 4 files changed, 237 insertions(+)
 create mode 100644 drivers/acpi/acpi_adxl.c
 create mode 100644 include/linux/adxl.h

Comments

Rafael J. Wysocki Oct. 10, 2018, 9:08 p.m. UTC | #1
On Tue, Oct 9, 2018 at 8:35 PM Tony Luck <tony.luck@intel.com> wrote:
>
> Some new servers provide an interface so that the OS can ask the
> BIOS to translate a system physical address to a memory address
> (socket, memory controller, channel, rank, dimm, etc.). This is
> useful for EDAC drivers that want to take the address of an error
> reported in a machine check bank and let the user know which
> DIMM may need to be replaced.
>
> Specification for this interface is available at:
>
>     https://cdrdv2.intel.com/v1/dl/getContent/603354
>
> [Based on earlier code by Qiuxu Zhuo <qiuxu.zhuo@intel.com>]
>
> Signed-off-by: Tony Luck <tony.luck@intel.com>
>
> ---
> Comments addressed since last version:
>    Rafael:
>         Added URL for specification (to commit & header comment)
>         Fixed NULL error return from error case of adxl_dsm()
>
>    Boris:
>         Added sanity check on number of address components
>         (also added check that number of values returned by
>          decode operation matches number of strings)
>         g/pr_debug/s//pr_info/
>         Added "." at end of sentences in comments
>         Make return from adxl_get_component_names() immutable
>
>  drivers/acpi/Kconfig     |  10 ++
>  drivers/acpi/Makefile    |   3 +
>  drivers/acpi/acpi_adxl.c | 199 +++++++++++++++++++++++++++++++++++++++
>  include/linux/adxl.h     |  25 +++++
>  4 files changed, 237 insertions(+)
>  create mode 100644 drivers/acpi/acpi_adxl.c
>  create mode 100644 include/linux/adxl.h
>
> diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
> index dd1eea90f67f..327c93b51cb7 100644
> --- a/drivers/acpi/Kconfig
> +++ b/drivers/acpi/Kconfig
> @@ -498,6 +498,16 @@ config ACPI_EXTLOG
>           driver adds support for that functionality with corresponding
>           tracepoint which carries that information to userspace.
>
> +config ACPI_ADXL
> +       bool "Physical address to DIMM address translation"
> +       def_bool n
> +       help
> +         Enable interface that calls into BIOS using a DSM (device
> +         specific method) to convert system physical addresses
> +         to DIMM (socket, channel, rank, dimm, etc.).
> +         Only available on some servers.
> +         Used by newer EDAC drivers.

I'm still not sure why this has to be a user-selectable option.  If
the plan is to select it from the drivers that need it, why do we need
to ask about it?  And what if it is enabled, but no drivers use it?
Will it just be dead code then?

> +
>  menuconfig PMIC_OPREGION
>         bool "PMIC (Power Management Integrated Circuit) operation region support"
>         help
> diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
> index 6d59aa109a91..edc039313cd6 100644
> --- a/drivers/acpi/Makefile
> +++ b/drivers/acpi/Makefile
> @@ -61,6 +61,9 @@ acpi-$(CONFIG_ACPI_LPIT)      += acpi_lpit.o
>  acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o
>  acpi-$(CONFIG_ACPI_WATCHDOG)   += acpi_watchdog.o
>
> +# Address translation
> +acpi-$(CONFIG_ACPI_ADXL)       += acpi_adxl.o
> +
>  # These are (potentially) separate modules
>
>  # IPMI may be used by other drivers, so it has to initialise before them
> diff --git a/drivers/acpi/acpi_adxl.c b/drivers/acpi/acpi_adxl.c
> new file mode 100644
> index 000000000000..a58bd8ec396e
> --- /dev/null
> +++ b/drivers/acpi/acpi_adxl.c
> @@ -0,0 +1,199 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Address translation interface via ACPI DSM.
> + * Copyright (C) 2018 Intel Corporation
> + *
> + * Specification for this interface is available at:
> + *
> + *     https://cdrdv2.intel.com/v1/dl/getContent/603354
> + */
> +
> +#ifdef CONFIG_ACPI_ADXL
> +#include <linux/acpi.h>
> +#include <linux/adxl.h>
> +
> +#define ADXL_REVISION                  0x1
> +#define ADXL_IDX_GET_ADDR_PARAMS       0x1
> +#define ADXL_IDX_FORWARD_TRANSLATE     0x2
> +#define ACPI_ADXL_PATH                 "\\_SB.ADXL"
> +
> +/*
> + * The specification doesn't provide a limit on how many
> + * components are in a memory address. But since we allocate
> + * memory based on the number the BIOS tells us, we should
> + * defend against insane values.
> + */
> +#define ADXL_MAX_COMPONENTS            500
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "ADXL: " fmt
> +
> +static acpi_handle handle;
> +static union acpi_object *params;
> +static const guid_t adxl_guid =
> +       GUID_INIT(0xAA3C050A, 0x7EA4, 0x4C1F,
> +                 0xAF, 0xDA, 0x12, 0x67, 0xDF, 0xD3, 0xD4, 0x8D);
> +
> +static int adxl_count;
> +static char **adxl_component_names;
> +
> +static union acpi_object *adxl_dsm(int cmd, union acpi_object argv[])
> +{
> +       union acpi_object *obj, *o;
> +
> +       obj = acpi_evaluate_dsm_typed(handle, &adxl_guid, ADXL_REVISION,
> +                                     cmd, argv, ACPI_TYPE_PACKAGE);
> +       if (!obj) {
> +               pr_info("Empty obj\n");

pr_debug() perhaps?  It is somewhat devoid of details anyway.

Thanks,
Rafael
Tony Luck Oct. 10, 2018, 9:25 p.m. UTC | #2
On Wed, Oct 10, 2018 at 11:08:37PM +0200, Rafael J. Wysocki wrote:
> > +config ACPI_ADXL
> > +       bool "Physical address to DIMM address translation"
> > +       def_bool n
> > +       help
> > +         Enable interface that calls into BIOS using a DSM (device
> > +         specific method) to convert system physical addresses
> > +         to DIMM (socket, channel, rank, dimm, etc.).
> > +         Only available on some servers.
> > +         Used by newer EDAC drivers.
> 
> I'm still not sure why this has to be a user-selectable option.  If
> the plan is to select it from the drivers that need it, why do we need
> to ask about it?  And what if it is enabled, but no drivers use it?
> Will it just be dead code then?

Ah, now I see what you mean.  I should make this:

config ACPI_ADXL
        def_bool n

right? So the user isn't plagued by a question they don't understand.
It will only be set if some other driver that need it selects it. So
no way to include dead code.

> > +       obj = acpi_evaluate_dsm_typed(handle, &adxl_guid, ADXL_REVISION,
> > +                                     cmd, argv, ACPI_TYPE_PACKAGE);
> > +       if (!obj) {
> > +               pr_info("Empty obj\n");
> 
> pr_debug() perhaps?  It is somewhat devoid of details anyway.

I'll make the message more meaningful. I think I want to keep it
at higher than "debug" threshold so that we can diagnose problems
from the first report, without having to ask for a rerun with a
new "dmesg" level.

Realistically this should never happen. If it does, its likely
a BIOS bug.

Thanks for the review.

-Tony
Rafael J. Wysocki Oct. 10, 2018, 9:43 p.m. UTC | #3
On Wed, Oct 10, 2018 at 11:25 PM Luck, Tony <tony.luck@intel.com> wrote:
>
> On Wed, Oct 10, 2018 at 11:08:37PM +0200, Rafael J. Wysocki wrote:
> > > +config ACPI_ADXL
> > > +       bool "Physical address to DIMM address translation"
> > > +       def_bool n
> > > +       help
> > > +         Enable interface that calls into BIOS using a DSM (device
> > > +         specific method) to convert system physical addresses
> > > +         to DIMM (socket, channel, rank, dimm, etc.).
> > > +         Only available on some servers.
> > > +         Used by newer EDAC drivers.
> >
> > I'm still not sure why this has to be a user-selectable option.  If
> > the plan is to select it from the drivers that need it, why do we need
> > to ask about it?  And what if it is enabled, but no drivers use it?
> > Will it just be dead code then?
>
> Ah, now I see what you mean.  I should make this:
>
> config ACPI_ADXL
>         def_bool n
>
> right?

Yes, and you can simply do

config ACPI_ADXL
         bool

as it won't be enabled when not explicitly selected anyway.

> So the user isn't plagued by a question they don't understand.
> It will only be set if some other driver that need it selects it. So
> no way to include dead code.

Right.
diff mbox series

Patch

diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index dd1eea90f67f..327c93b51cb7 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -498,6 +498,16 @@  config ACPI_EXTLOG
 	  driver adds support for that functionality with corresponding
 	  tracepoint which carries that information to userspace.
 
+config ACPI_ADXL
+	bool "Physical address to DIMM address translation"
+	def_bool n
+	help
+	  Enable interface that calls into BIOS using a DSM (device
+	  specific method) to convert system physical addresses
+	  to DIMM (socket, channel, rank, dimm, etc.).
+	  Only available on some servers.
+	  Used by newer EDAC drivers.
+
 menuconfig PMIC_OPREGION
 	bool "PMIC (Power Management Integrated Circuit) operation region support"
 	help
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 6d59aa109a91..edc039313cd6 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -61,6 +61,9 @@  acpi-$(CONFIG_ACPI_LPIT)	+= acpi_lpit.o
 acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o
 acpi-$(CONFIG_ACPI_WATCHDOG)	+= acpi_watchdog.o
 
+# Address translation
+acpi-$(CONFIG_ACPI_ADXL)	+= acpi_adxl.o
+
 # These are (potentially) separate modules
 
 # IPMI may be used by other drivers, so it has to initialise before them
diff --git a/drivers/acpi/acpi_adxl.c b/drivers/acpi/acpi_adxl.c
new file mode 100644
index 000000000000..a58bd8ec396e
--- /dev/null
+++ b/drivers/acpi/acpi_adxl.c
@@ -0,0 +1,199 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Address translation interface via ACPI DSM.
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Specification for this interface is available at:
+ *
+ *	https://cdrdv2.intel.com/v1/dl/getContent/603354
+ */
+
+#ifdef CONFIG_ACPI_ADXL
+#include <linux/acpi.h>
+#include <linux/adxl.h>
+
+#define ADXL_REVISION			0x1
+#define ADXL_IDX_GET_ADDR_PARAMS	0x1
+#define ADXL_IDX_FORWARD_TRANSLATE	0x2
+#define ACPI_ADXL_PATH			"\\_SB.ADXL"
+
+/*
+ * The specification doesn't provide a limit on how many
+ * components are in a memory address. But since we allocate
+ * memory based on the number the BIOS tells us, we should
+ * defend against insane values.
+ */
+#define ADXL_MAX_COMPONENTS		500
+
+#undef pr_fmt
+#define pr_fmt(fmt) "ADXL: " fmt
+
+static acpi_handle handle;
+static union acpi_object *params;
+static const guid_t adxl_guid =
+	GUID_INIT(0xAA3C050A, 0x7EA4, 0x4C1F,
+		  0xAF, 0xDA, 0x12, 0x67, 0xDF, 0xD3, 0xD4, 0x8D);
+
+static int adxl_count;
+static char **adxl_component_names;
+
+static union acpi_object *adxl_dsm(int cmd, union acpi_object argv[])
+{
+	union acpi_object *obj, *o;
+
+	obj = acpi_evaluate_dsm_typed(handle, &adxl_guid, ADXL_REVISION,
+				      cmd, argv, ACPI_TYPE_PACKAGE);
+	if (!obj) {
+		pr_info("Empty obj\n");
+		return NULL;
+	}
+
+	if (obj->package.count != 2) {
+		pr_info("Bad pkg count %d\n", obj->package.count);
+		goto err;
+	}
+
+	o = obj->package.elements;
+	if (o->type != ACPI_TYPE_INTEGER) {
+		pr_info("Bad 1st element type %d\n", o->type);
+		goto err;
+	}
+	if (o->integer.value) {
+		pr_info("Bad ret val %llu\n", o->integer.value);
+		goto err;
+	}
+
+	o = obj->package.elements + 1;
+	if (o->type != ACPI_TYPE_PACKAGE) {
+		pr_info("Bad 2nd element type %d\n", o->type);
+		goto err;
+	}
+	return obj;
+
+err:
+	ACPI_FREE(obj);
+	return NULL;
+}
+
+/**
+ * adxl_get_component_names - get list of memory component names
+ * Returns NULL terminated list of string names
+ *
+ * Give the caller a pointer to the list of memory component names
+ * e.g. { "SystemAddress", "ProcessorSocketId", "ChannelId", ... NULL }
+ * Caller should count how many strings in order to allocate a buffer
+ * for the return from adxl_decode().
+ */
+const char * const *adxl_get_component_names(void)
+{
+	return (const char * const *)adxl_component_names;
+}
+EXPORT_SYMBOL_GPL(adxl_get_component_names);
+
+/**
+ * adxl_decode - ask BIOS to decode a system address to memory address
+ * @addr: the address to decode
+ * @component_values: pointer to array of values for each component
+ * Returns 0 on success, negative error code otherwise
+ *
+ * The index of each value returned in the array matches the index of
+ * each component name returned by adxl_get_component_names().
+ * Components that are not defined for this address translation (e.g.
+ * mirror channel number for a non-mirrored address) are set to ~0ull.
+ */
+int adxl_decode(u64 addr, u64 component_values[])
+{
+	union acpi_object argv4[2], *results, *r;
+	int i, cnt;
+
+	if (!adxl_component_names)
+		return -EOPNOTSUPP;
+
+	argv4[0].type = ACPI_TYPE_PACKAGE;
+	argv4[0].package.count = 1;
+	argv4[0].package.elements = &argv4[1];
+	argv4[1].integer.type = ACPI_TYPE_INTEGER;
+	argv4[1].integer.value = addr;
+
+	results = adxl_dsm(ADXL_IDX_FORWARD_TRANSLATE, argv4);
+	if (!results)
+		return -EINVAL;
+
+	r = results->package.elements + 1;
+	cnt = r->package.count;
+	if (cnt != adxl_count) {
+		ACPI_FREE(results);
+		return -EINVAL;
+	}
+	r = r->package.elements;
+
+	for (i = 0; i < cnt; i++)
+		component_values[i] = r[i].integer.value;
+
+	ACPI_FREE(results);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(adxl_decode);
+
+static bool adxl_detect(void)
+{
+	char *path = ACPI_ADXL_PATH;
+	union acpi_object *p;
+	acpi_status status;
+	int i;
+
+	status = acpi_get_handle(NULL, path, &handle);
+	if (ACPI_FAILURE(status)) {
+		pr_info("No ACPI handle for path %s\n", path);
+		return false;
+	}
+
+	if (!acpi_has_method(handle, "_DSM")) {
+		pr_info("No DSM method\n");
+		return false;
+	}
+
+	if (!acpi_check_dsm(handle, &adxl_guid, ADXL_REVISION,
+			    ADXL_IDX_GET_ADDR_PARAMS |
+			    ADXL_IDX_FORWARD_TRANSLATE)) {
+		pr_info("No ADXL DSM methods\n");
+		return false;
+	}
+
+	params = adxl_dsm(ADXL_IDX_GET_ADDR_PARAMS, NULL);
+	if (!params) {
+		pr_info("Failed to get params\n");
+		return false;
+	}
+
+	p = params->package.elements + 1;
+	adxl_count = p->package.count;
+	if (adxl_count > ADXL_MAX_COMPONENTS) {
+		pr_info("Insane number of address component names %d\n", adxl_count);
+		ACPI_FREE(params);
+		return false;
+	}
+	p = p->package.elements;
+
+	adxl_component_names = kcalloc(adxl_count + 1, sizeof(char *), GFP_KERNEL);
+	if (!adxl_component_names) {
+		ACPI_FREE(params);
+		return false;
+	}
+
+	for (i = 0; i < adxl_count; i++)
+		adxl_component_names[i] = p[i].string.pointer;
+
+	return true;
+}
+
+static int __init adxl_init(void)
+{
+	if (!adxl_detect())
+		return -ENODEV;
+	return 0;
+}
+subsys_initcall(adxl_init);
+
+#endif /* CONFIG_ACPI_ADXL */
diff --git a/include/linux/adxl.h b/include/linux/adxl.h
new file mode 100644
index 000000000000..6023704e5d0b
--- /dev/null
+++ b/include/linux/adxl.h
@@ -0,0 +1,25 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Address translation interface via ACPI DSM.
+ * Copyright (C) 2018 Intel Corporation
+ */
+
+#ifndef _LINUX_ADXL_H
+#define _LINUX_ADXL_H
+
+#ifdef CONFIG_ACPI_ADXL
+const char * const *adxl_get_component_names(void);
+int adxl_decode(u64 addr, u64 component_values[]);
+#else
+static inline const char * const *adxl_get_component_names(void)
+{
+	return NULL;
+}
+
+static inline int adxl_decode(u64 addr, u64 component_values[])
+{
+	return -EOPNOTSUPP;
+}
+#endif
+
+#endif /* _LINUX_ADXL_H */