diff options
author | Tony Luck <tony.luck@intel.com> | 2018-10-15 19:11:31 -0400 |
---|---|---|
committer | Borislav Petkov <bp@suse.de> | 2018-10-16 04:03:00 -0400 |
commit | 4cf841e398503990df640f7a7c5b2ea56f11c08c (patch) | |
tree | 9dc246175a02518b6dc87ef923486bedfdf5de64 | |
parent | d8c27ba86a2fd806d3957e5a9b30e66dfca2a61d (diff) |
ACPI/ADXL: Add address translation interface using an ACPI DSM
Some new Intel 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>. ]
[ bp: Make the first pr_info() in adxl_init() pr_debug() so that it
doesn't pollute every dmesg. ]
Signed-off-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Tested-by: Qiuxu Zhuo <qiuxu.zhuo@intel.com>
CC: Len Brown <lenb@kernel.org>
CC: linux-acpi@vger.kernel.org
CC: linux-edac@vger.kernel.org
Link: http://lkml.kernel.org/r/20181015202620.23610-1-tony.luck@intel.com
-rw-r--r-- | drivers/acpi/Kconfig | 3 | ||||
-rw-r--r-- | drivers/acpi/Makefile | 3 | ||||
-rw-r--r-- | drivers/acpi/acpi_adxl.c | 192 | ||||
-rw-r--r-- | include/linux/adxl.h | 13 |
4 files changed, 211 insertions, 0 deletions
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index dd1eea90f67f..09991cc91b89 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig | |||
@@ -498,6 +498,9 @@ config ACPI_EXTLOG | |||
498 | driver adds support for that functionality with corresponding | 498 | driver adds support for that functionality with corresponding |
499 | tracepoint which carries that information to userspace. | 499 | tracepoint which carries that information to userspace. |
500 | 500 | ||
501 | config ACPI_ADXL | ||
502 | bool | ||
503 | |||
501 | menuconfig PMIC_OPREGION | 504 | menuconfig PMIC_OPREGION |
502 | bool "PMIC (Power Management Integrated Circuit) operation region support" | 505 | bool "PMIC (Power Management Integrated Circuit) operation region support" |
503 | help | 506 | 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 | |||
61 | acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o | 61 | acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o |
62 | acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o | 62 | acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o |
63 | 63 | ||
64 | # Address translation | ||
65 | acpi-$(CONFIG_ACPI_ADXL) += acpi_adxl.o | ||
66 | |||
64 | # These are (potentially) separate modules | 67 | # These are (potentially) separate modules |
65 | 68 | ||
66 | # IPMI may be used by other drivers, so it has to initialise before them | 69 | # 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..13c8f7b50c46 --- /dev/null +++ b/drivers/acpi/acpi_adxl.c | |||
@@ -0,0 +1,192 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Address translation interface via ACPI DSM. | ||
4 | * Copyright (C) 2018 Intel Corporation | ||
5 | * | ||
6 | * Specification for this interface is available at: | ||
7 | * | ||
8 | * https://cdrdv2.intel.com/v1/dl/getContent/603354 | ||
9 | */ | ||
10 | |||
11 | #include <linux/acpi.h> | ||
12 | #include <linux/adxl.h> | ||
13 | |||
14 | #define ADXL_REVISION 0x1 | ||
15 | #define ADXL_IDX_GET_ADDR_PARAMS 0x1 | ||
16 | #define ADXL_IDX_FORWARD_TRANSLATE 0x2 | ||
17 | #define ACPI_ADXL_PATH "\\_SB.ADXL" | ||
18 | |||
19 | /* | ||
20 | * The specification doesn't provide a limit on how many | ||
21 | * components are in a memory address. But since we allocate | ||
22 | * memory based on the number the BIOS tells us, we should | ||
23 | * defend against insane values. | ||
24 | */ | ||
25 | #define ADXL_MAX_COMPONENTS 500 | ||
26 | |||
27 | #undef pr_fmt | ||
28 | #define pr_fmt(fmt) "ADXL: " fmt | ||
29 | |||
30 | static acpi_handle handle; | ||
31 | static union acpi_object *params; | ||
32 | static const guid_t adxl_guid = | ||
33 | GUID_INIT(0xAA3C050A, 0x7EA4, 0x4C1F, | ||
34 | 0xAF, 0xDA, 0x12, 0x67, 0xDF, 0xD3, 0xD4, 0x8D); | ||
35 | |||
36 | static int adxl_count; | ||
37 | static char **adxl_component_names; | ||
38 | |||
39 | static union acpi_object *adxl_dsm(int cmd, union acpi_object argv[]) | ||
40 | { | ||
41 | union acpi_object *obj, *o; | ||
42 | |||
43 | obj = acpi_evaluate_dsm_typed(handle, &adxl_guid, ADXL_REVISION, | ||
44 | cmd, argv, ACPI_TYPE_PACKAGE); | ||
45 | if (!obj) { | ||
46 | pr_info("DSM call failed for cmd=%d\n", cmd); | ||
47 | return NULL; | ||
48 | } | ||
49 | |||
50 | if (obj->package.count != 2) { | ||
51 | pr_info("Bad pkg count %d\n", obj->package.count); | ||
52 | goto err; | ||
53 | } | ||
54 | |||
55 | o = obj->package.elements; | ||
56 | if (o->type != ACPI_TYPE_INTEGER) { | ||
57 | pr_info("Bad 1st element type %d\n", o->type); | ||
58 | goto err; | ||
59 | } | ||
60 | if (o->integer.value) { | ||
61 | pr_info("Bad ret val %llu\n", o->integer.value); | ||
62 | goto err; | ||
63 | } | ||
64 | |||
65 | o = obj->package.elements + 1; | ||
66 | if (o->type != ACPI_TYPE_PACKAGE) { | ||
67 | pr_info("Bad 2nd element type %d\n", o->type); | ||
68 | goto err; | ||
69 | } | ||
70 | return obj; | ||
71 | |||
72 | err: | ||
73 | ACPI_FREE(obj); | ||
74 | return NULL; | ||
75 | } | ||
76 | |||
77 | /** | ||
78 | * adxl_get_component_names - get list of memory component names | ||
79 | * Returns NULL terminated list of string names | ||
80 | * | ||
81 | * Give the caller a pointer to the list of memory component names | ||
82 | * e.g. { "SystemAddress", "ProcessorSocketId", "ChannelId", ... NULL } | ||
83 | * Caller should count how many strings in order to allocate a buffer | ||
84 | * for the return from adxl_decode(). | ||
85 | */ | ||
86 | const char * const *adxl_get_component_names(void) | ||
87 | { | ||
88 | return (const char * const *)adxl_component_names; | ||
89 | } | ||
90 | EXPORT_SYMBOL_GPL(adxl_get_component_names); | ||
91 | |||
92 | /** | ||
93 | * adxl_decode - ask BIOS to decode a system address to memory address | ||
94 | * @addr: the address to decode | ||
95 | * @component_values: pointer to array of values for each component | ||
96 | * Returns 0 on success, negative error code otherwise | ||
97 | * | ||
98 | * The index of each value returned in the array matches the index of | ||
99 | * each component name returned by adxl_get_component_names(). | ||
100 | * Components that are not defined for this address translation (e.g. | ||
101 | * mirror channel number for a non-mirrored address) are set to ~0ull. | ||
102 | */ | ||
103 | int adxl_decode(u64 addr, u64 component_values[]) | ||
104 | { | ||
105 | union acpi_object argv4[2], *results, *r; | ||
106 | int i, cnt; | ||
107 | |||
108 | if (!adxl_component_names) | ||
109 | return -EOPNOTSUPP; | ||
110 | |||
111 | argv4[0].type = ACPI_TYPE_PACKAGE; | ||
112 | argv4[0].package.count = 1; | ||
113 | argv4[0].package.elements = &argv4[1]; | ||
114 | argv4[1].integer.type = ACPI_TYPE_INTEGER; | ||
115 | argv4[1].integer.value = addr; | ||
116 | |||
117 | results = adxl_dsm(ADXL_IDX_FORWARD_TRANSLATE, argv4); | ||
118 | if (!results) | ||
119 | return -EINVAL; | ||
120 | |||
121 | r = results->package.elements + 1; | ||
122 | cnt = r->package.count; | ||
123 | if (cnt != adxl_count) { | ||
124 | ACPI_FREE(results); | ||
125 | return -EINVAL; | ||
126 | } | ||
127 | r = r->package.elements; | ||
128 | |||
129 | for (i = 0; i < cnt; i++) | ||
130 | component_values[i] = r[i].integer.value; | ||
131 | |||
132 | ACPI_FREE(results); | ||
133 | |||
134 | return 0; | ||
135 | } | ||
136 | EXPORT_SYMBOL_GPL(adxl_decode); | ||
137 | |||
138 | static int __init adxl_init(void) | ||
139 | { | ||
140 | char *path = ACPI_ADXL_PATH; | ||
141 | union acpi_object *p; | ||
142 | acpi_status status; | ||
143 | int i; | ||
144 | |||
145 | status = acpi_get_handle(NULL, path, &handle); | ||
146 | if (ACPI_FAILURE(status)) { | ||
147 | pr_debug("No ACPI handle for path %s\n", path); | ||
148 | return -ENODEV; | ||
149 | } | ||
150 | |||
151 | if (!acpi_has_method(handle, "_DSM")) { | ||
152 | pr_info("No DSM method\n"); | ||
153 | return -ENODEV; | ||
154 | } | ||
155 | |||
156 | if (!acpi_check_dsm(handle, &adxl_guid, ADXL_REVISION, | ||
157 | ADXL_IDX_GET_ADDR_PARAMS | | ||
158 | ADXL_IDX_FORWARD_TRANSLATE)) { | ||
159 | pr_info("DSM method does not support forward translate\n"); | ||
160 | return -ENODEV; | ||
161 | } | ||
162 | |||
163 | params = adxl_dsm(ADXL_IDX_GET_ADDR_PARAMS, NULL); | ||
164 | if (!params) { | ||
165 | pr_info("Failed to get component names\n"); | ||
166 | return -ENODEV; | ||
167 | } | ||
168 | |||
169 | p = params->package.elements + 1; | ||
170 | adxl_count = p->package.count; | ||
171 | if (adxl_count > ADXL_MAX_COMPONENTS) { | ||
172 | pr_info("Insane number of address component names %d\n", adxl_count); | ||
173 | ACPI_FREE(params); | ||
174 | return -ENODEV; | ||
175 | } | ||
176 | p = p->package.elements; | ||
177 | |||
178 | /* | ||
179 | * Allocate one extra for NULL termination. | ||
180 | */ | ||
181 | adxl_component_names = kcalloc(adxl_count + 1, sizeof(char *), GFP_KERNEL); | ||
182 | if (!adxl_component_names) { | ||
183 | ACPI_FREE(params); | ||
184 | return -ENOMEM; | ||
185 | } | ||
186 | |||
187 | for (i = 0; i < adxl_count; i++) | ||
188 | adxl_component_names[i] = p[i].string.pointer; | ||
189 | |||
190 | return 0; | ||
191 | } | ||
192 | subsys_initcall(adxl_init); | ||
diff --git a/include/linux/adxl.h b/include/linux/adxl.h new file mode 100644 index 000000000000..2a629acb4c3f --- /dev/null +++ b/include/linux/adxl.h | |||
@@ -0,0 +1,13 @@ | |||
1 | /* SPDX-License-Identifier: GPL-2.0 */ | ||
2 | /* | ||
3 | * Address translation interface via ACPI DSM. | ||
4 | * Copyright (C) 2018 Intel Corporation | ||
5 | */ | ||
6 | |||
7 | #ifndef _LINUX_ADXL_H | ||
8 | #define _LINUX_ADXL_H | ||
9 | |||
10 | const char * const *adxl_get_component_names(void); | ||
11 | int adxl_decode(u64 addr, u64 component_values[]); | ||
12 | |||
13 | #endif /* _LINUX_ADXL_H */ | ||