Skip to content

Commit cefc7ca

Browse files
Erik Kanedarafaeljw
authored andcommitted
ACPI: PRM: implement OperationRegion handler for the PlatformRtMechanism subtype
Platform Runtime Mechanism (PRM) is a firmware interface that exposes a set of binary executables that can either be called from the AML interpreter or device drivers by bypassing the AML interpreter. This change implements the AML interpreter path. According to the specification [1], PRM services are listed in an ACPI table called the PRMT. This patch parses module and handler information listed in the PRMT and registers the PlatformRtMechanism OpRegion handler before ACPI tables are loaded. Each service is defined by a 16-byte GUID and called from writing a 26-byte ASL buffer containing the identifier to a FieldUnit object defined inside a PlatformRtMechanism OperationRegion. OperationRegion (PRMR, PlatformRtMechanism, 0, 26) Field (PRMR, BufferAcc, NoLock, Preserve) { PRMF, 208 // Write to this field to invoke the OperationRegion Handler } The 26-byte ASL buffer is defined as the following: Byte Offset Byte Length Description ============================================================= 0 1 PRM OperationRegion handler status 1 8 PRM service status 9 1 PRM command 10 16 PRM handler GUID The ASL caller fills out a 26-byte buffer containing the PRM command and the PRM handler GUID like so: /* Local0 is the PRM data buffer */ Local0 = buffer (26){} /* Create byte fields over the buffer */ CreateByteField (Local0, 0x9, CMD) CreateField (Local0, 0x50, 0x80, GUID) /* Fill in the command and data fields of the data buffer */ CMD = 0 // run command GUID = ToUUID("xxxx-xx-xxx-xxxx") /* * Invoke PRM service with an ID that matches GUID and save the * result. */ Local0 = (\_SB.PRMT.PRMF = Local0) Byte offset 0 - 8 are written by the handler as a status passed back to AML and used by ASL like so: /* Create byte fields over the buffer */ CreateByteField (Local0, 0x0, PSTA) CreateQWordField (Local0, 0x1, USTA) In this ASL code, PSTA contains a status from the OperationRegion and USTA contains a status from the PRM service. The 26-byte buffer is recieved by acpi_platformrt_space_handler. This handler will look at the command value and the handler guid and take the approperiate actions. Command value Action ===================================================================== 0 Run the PRM service indicated by the PRM handler GUID (bytes 10-26) 1 Prevent PRM runtime updates from happening to the service's parent module 2 Allow PRM updates from happening to the service's parent module This patch enables command value 0. Link: https://uefi.org/sites/default/files/resources/Platform%20Runtime%20Mechanism%20-%20with%20legal%20notice.pdf # [1] Signed-off-by: Erik Kaneda <[email protected]> [ rjw: Subject and changelog edits ] Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 9f8c7ba commit cefc7ca

File tree

7 files changed

+328
-0
lines changed

7 files changed

+328
-0
lines changed

drivers/acpi/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,3 +543,8 @@ config X86_PM_TIMER
543543

544544
You should nearly always say Y here because many modern
545545
systems require this timer.
546+
547+
config ACPI_PRMT
548+
bool "Platform Runtime Mechanism Support"
549+
depends on EFI && X86_64
550+
default y

drivers/acpi/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ acpi-$(CONFIG_ACPI_FPDT) += acpi_fpdt.o
6161
acpi-$(CONFIG_ACPI_LPIT) += acpi_lpit.o
6262
acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o
6363
acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o
64+
acpi-$(CONFIG_ACPI_PRMT) += prmt.o
6465

6566
# Address translation
6667
acpi-$(CONFIG_ACPI_ADXL) += acpi_adxl.o

drivers/acpi/bus.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <linux/pci.h>
3131
#include <acpi/apei.h>
3232
#include <linux/suspend.h>
33+
#include <linux/prmt.h>
3334

3435
#include "internal.h"
3536

@@ -1330,6 +1331,7 @@ static int __init acpi_init(void)
13301331
acpi_kobj = NULL;
13311332
}
13321333

1334+
init_prmt();
13331335
result = acpi_bus_init();
13341336
if (result) {
13351337
disable_acpi();

drivers/acpi/prmt.c

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Author: Erik Kaneda <[email protected]>
4+
* Copyright 2020 Intel Corporation
5+
*
6+
* prmt.c
7+
*
8+
* Each PRM service is an executable that is run in a restricted environment
9+
* that is invoked by writing to the PlatformRtMechanism OperationRegion from
10+
* AML bytecode.
11+
*
12+
* init_prmt initializes the Platform Runtime Mechanism (PRM) services by
13+
* processing data in the PRMT as well as registering an ACPI OperationRegion
14+
* handler for the PlatformRtMechanism subtype.
15+
*
16+
*/
17+
#include <linux/kernel.h>
18+
#include <linux/efi.h>
19+
#include <linux/acpi.h>
20+
#include <linux/prmt.h>
21+
#include <asm/efi.h>
22+
23+
#pragma pack(1)
24+
struct prm_mmio_addr_range {
25+
u64 phys_addr;
26+
u64 virt_addr;
27+
u32 length;
28+
};
29+
30+
struct prm_mmio_info {
31+
u64 mmio_count;
32+
struct prm_mmio_addr_range addr_ranges[];
33+
};
34+
35+
struct prm_buffer {
36+
u8 prm_status;
37+
u64 efi_status;
38+
u8 prm_cmd;
39+
guid_t handler_guid;
40+
};
41+
42+
struct prm_context_buffer {
43+
char signature[ACPI_NAMESEG_SIZE];
44+
u16 revision;
45+
u16 reserved;
46+
guid_t identifier;
47+
u64 static_data_buffer;
48+
struct prm_mmio_info *mmio_ranges;
49+
};
50+
#pragma pack()
51+
52+
53+
LIST_HEAD(prm_module_list);
54+
55+
struct prm_handler_info {
56+
guid_t guid;
57+
u64 handler_addr;
58+
u64 static_data_buffer_addr;
59+
u64 acpi_param_buffer_addr;
60+
61+
struct list_head handler_list;
62+
};
63+
64+
struct prm_module_info {
65+
guid_t guid;
66+
u16 major_rev;
67+
u16 minor_rev;
68+
u16 handler_count;
69+
struct prm_mmio_info *mmio_info;
70+
bool updatable;
71+
72+
struct list_head module_list;
73+
struct prm_handler_info handlers[];
74+
};
75+
76+
77+
static u64 efi_pa_va_lookup(u64 pa)
78+
{
79+
efi_memory_desc_t *md;
80+
u64 pa_offset = pa & ~PAGE_MASK;
81+
u64 page = pa & PAGE_MASK;
82+
83+
for_each_efi_memory_desc(md) {
84+
if (md->phys_addr < pa && pa < md->phys_addr + PAGE_SIZE * md->num_pages)
85+
return pa_offset + md->virt_addr + page - md->phys_addr;
86+
}
87+
88+
return 0;
89+
}
90+
91+
92+
#define get_first_handler(a) ((struct acpi_prmt_handler_info *) ((char *) (a) + a->handler_info_offset))
93+
#define get_next_handler(a) ((struct acpi_prmt_handler_info *) (sizeof(struct acpi_prmt_handler_info) + (char *) a))
94+
95+
static int __init
96+
acpi_parse_prmt(union acpi_subtable_headers *header, const unsigned long end)
97+
{
98+
struct acpi_prmt_module_info *module_info;
99+
struct acpi_prmt_handler_info *handler_info;
100+
struct prm_handler_info *th;
101+
struct prm_module_info *tm;
102+
u64 mmio_count = 0;
103+
u64 cur_handler = 0;
104+
u32 module_info_size = 0;
105+
u64 mmio_range_size = 0;
106+
void *temp_mmio;
107+
108+
module_info = (struct acpi_prmt_module_info *) header;
109+
module_info_size = struct_size(tm, handlers, module_info->handler_info_count);
110+
tm = kmalloc(module_info_size, GFP_KERNEL);
111+
112+
guid_copy(&tm->guid, (guid_t *) module_info->module_guid);
113+
tm->major_rev = module_info->major_rev;
114+
tm->minor_rev = module_info->minor_rev;
115+
tm->handler_count = module_info->handler_info_count;
116+
tm->updatable = true;
117+
118+
if (module_info->mmio_list_pointer) {
119+
/*
120+
* Each module is associated with a list of addr
121+
* ranges that it can use during the service
122+
*/
123+
mmio_count = *(u64 *) memremap(module_info->mmio_list_pointer, 8, MEMREMAP_WB);
124+
mmio_range_size = struct_size(tm->mmio_info, addr_ranges, mmio_count);
125+
tm->mmio_info = kmalloc(mmio_range_size, GFP_KERNEL);
126+
temp_mmio = memremap(module_info->mmio_list_pointer, mmio_range_size, MEMREMAP_WB);
127+
memmove(tm->mmio_info, temp_mmio, mmio_range_size);
128+
} else {
129+
mmio_range_size = struct_size(tm->mmio_info, addr_ranges, mmio_count);
130+
tm->mmio_info = kmalloc(mmio_range_size, GFP_KERNEL);
131+
tm->mmio_info->mmio_count = 0;
132+
}
133+
134+
INIT_LIST_HEAD(&tm->module_list);
135+
list_add(&tm->module_list, &prm_module_list);
136+
137+
handler_info = get_first_handler(module_info);
138+
do {
139+
th = &tm->handlers[cur_handler];
140+
141+
guid_copy(&th->guid, (guid_t *)handler_info->handler_guid);
142+
th->handler_addr = efi_pa_va_lookup(handler_info->handler_address);
143+
th->static_data_buffer_addr = efi_pa_va_lookup(handler_info->static_data_buffer_address);
144+
th->acpi_param_buffer_addr = efi_pa_va_lookup(handler_info->acpi_param_buffer_address);
145+
} while (++cur_handler < tm->handler_count && (handler_info = get_next_handler(handler_info)));
146+
147+
return 0;
148+
}
149+
150+
#define GET_MODULE 0
151+
#define GET_HANDLER 1
152+
153+
static void *find_guid_info(const guid_t *guid, u8 mode)
154+
{
155+
struct prm_handler_info *cur_handler;
156+
struct prm_module_info *cur_module;
157+
int i = 0;
158+
159+
list_for_each_entry(cur_module, &prm_module_list, module_list) {
160+
for (i = 0; i < cur_module->handler_count; ++i) {
161+
cur_handler = &cur_module->handlers[i];
162+
if (guid_equal(guid, &cur_handler->guid)) {
163+
if (mode == GET_MODULE)
164+
return (void *)cur_module;
165+
else
166+
return (void *)cur_handler;
167+
}
168+
}
169+
}
170+
171+
return NULL;
172+
}
173+
174+
175+
static struct prm_module_info *find_prm_module(const guid_t *guid)
176+
{
177+
return (struct prm_module_info *)find_guid_info(guid, GET_MODULE);
178+
}
179+
180+
static struct prm_handler_info *find_prm_handler(const guid_t *guid)
181+
{
182+
return (struct prm_handler_info *) find_guid_info(guid, GET_HANDLER);
183+
}
184+
185+
/* In-coming PRM commands */
186+
187+
#define PRM_CMD_RUN_SERVICE 0
188+
#define PRM_CMD_START_TRANSACTION 1
189+
#define PRM_CMD_END_TRANSACTION 2
190+
191+
/* statuses that can be passed back to ASL */
192+
193+
#define PRM_HANDLER_SUCCESS 0
194+
#define PRM_HANDLER_ERROR 1
195+
#define INVALID_PRM_COMMAND 2
196+
#define PRM_HANDLER_GUID_NOT_FOUND 3
197+
#define UPDATE_LOCK_ALREADY_HELD 4
198+
#define UPDATE_UNLOCK_WITHOUT_LOCK 5
199+
200+
/*
201+
* This is the PlatformRtMechanism opregion space handler.
202+
* @function: indicates the read/write. In fact as the PlatformRtMechanism
203+
* message is driven by command, only write is meaningful.
204+
*
205+
* @addr : not used
206+
* @bits : not used.
207+
* @value : it is an in/out parameter. It points to the PRM message buffer.
208+
* @handler_context: not used
209+
*/
210+
static acpi_status acpi_platformrt_space_handler(u32 function,
211+
acpi_physical_address addr,
212+
u32 bits, acpi_integer *value,
213+
void *handler_context,
214+
void *region_context)
215+
{
216+
struct prm_buffer *buffer = ACPI_CAST_PTR(struct prm_buffer, value);
217+
struct prm_handler_info *handler;
218+
struct prm_module_info *module;
219+
efi_status_t status;
220+
struct prm_context_buffer context;
221+
222+
/*
223+
* The returned acpi_status will always be AE_OK. Error values will be
224+
* saved in the first byte of the PRM message buffer to be used by ASL.
225+
*/
226+
switch (buffer->prm_cmd) {
227+
case PRM_CMD_RUN_SERVICE:
228+
229+
handler = find_prm_handler(&buffer->handler_guid);
230+
module = find_prm_module(&buffer->handler_guid);
231+
if (!handler || !module)
232+
goto invalid_guid;
233+
234+
ACPI_COPY_NAMESEG(context.signature, "PRMC");
235+
context.revision = 0x0;
236+
context.reserved = 0x0;
237+
context.identifier = handler->guid;
238+
context.static_data_buffer = handler->static_data_buffer_addr;
239+
context.mmio_ranges = module->mmio_info;
240+
241+
status = efi_call_virt_pointer(handler, handler_addr,
242+
handler->acpi_param_buffer_addr,
243+
&context);
244+
if (status == EFI_SUCCESS) {
245+
buffer->prm_status = PRM_HANDLER_SUCCESS;
246+
} else {
247+
buffer->prm_status = PRM_HANDLER_ERROR;
248+
buffer->efi_status = status;
249+
}
250+
break;
251+
252+
case PRM_CMD_START_TRANSACTION:
253+
254+
module = find_prm_module(&buffer->handler_guid);
255+
if (!module)
256+
goto invalid_guid;
257+
258+
if (module->updatable)
259+
module->updatable = false;
260+
else
261+
buffer->prm_status = UPDATE_LOCK_ALREADY_HELD;
262+
break;
263+
264+
case PRM_CMD_END_TRANSACTION:
265+
266+
module = find_prm_module(&buffer->handler_guid);
267+
if (!module)
268+
goto invalid_guid;
269+
270+
if (module->updatable)
271+
buffer->prm_status = UPDATE_UNLOCK_WITHOUT_LOCK;
272+
else
273+
module->updatable = true;
274+
break;
275+
276+
default:
277+
278+
buffer->prm_status = INVALID_PRM_COMMAND;
279+
break;
280+
}
281+
282+
return AE_OK;
283+
284+
invalid_guid:
285+
buffer->prm_status = PRM_HANDLER_GUID_NOT_FOUND;
286+
return AE_OK;
287+
}
288+
289+
void __init init_prmt(void)
290+
{
291+
acpi_status status;
292+
int mc = acpi_table_parse_entries(ACPI_SIG_PRMT, sizeof(struct acpi_table_prmt) +
293+
sizeof (struct acpi_table_prmt_header),
294+
0, acpi_parse_prmt, 0);
295+
pr_info("PRM: found %u modules\n", mc);
296+
297+
status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
298+
ACPI_ADR_SPACE_PLATFORM_RT,
299+
&acpi_platformrt_space_handler,
300+
NULL, NULL);
301+
if (ACPI_FAILURE(status))
302+
pr_alert("PRM: OperationRegion handler could not be installed\n");
303+
}

drivers/acpi/tables.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ static int acpi_apic_instance __initdata;
3939
enum acpi_subtable_type {
4040
ACPI_SUBTABLE_COMMON,
4141
ACPI_SUBTABLE_HMAT,
42+
ACPI_SUBTABLE_PRMT,
4243
};
4344

4445
struct acpi_subtable_entry {
@@ -222,6 +223,8 @@ acpi_get_entry_type(struct acpi_subtable_entry *entry)
222223
return entry->hdr->common.type;
223224
case ACPI_SUBTABLE_HMAT:
224225
return entry->hdr->hmat.type;
226+
case ACPI_SUBTABLE_PRMT:
227+
return 0;
225228
}
226229
return 0;
227230
}
@@ -234,6 +237,8 @@ acpi_get_entry_length(struct acpi_subtable_entry *entry)
234237
return entry->hdr->common.length;
235238
case ACPI_SUBTABLE_HMAT:
236239
return entry->hdr->hmat.length;
240+
case ACPI_SUBTABLE_PRMT:
241+
return entry->hdr->prmt.length;
237242
}
238243
return 0;
239244
}
@@ -246,6 +251,8 @@ acpi_get_subtable_header_length(struct acpi_subtable_entry *entry)
246251
return sizeof(entry->hdr->common);
247252
case ACPI_SUBTABLE_HMAT:
248253
return sizeof(entry->hdr->hmat);
254+
case ACPI_SUBTABLE_PRMT:
255+
return sizeof(entry->hdr->prmt);
249256
}
250257
return 0;
251258
}
@@ -255,6 +262,8 @@ acpi_get_subtable_type(char *id)
255262
{
256263
if (strncmp(id, ACPI_SIG_HMAT, 4) == 0)
257264
return ACPI_SUBTABLE_HMAT;
265+
if (strncmp(id, ACPI_SIG_PRMT, 4) == 0)
266+
return ACPI_SUBTABLE_PRMT;
258267
return ACPI_SUBTABLE_COMMON;
259268
}
260269

include/linux/acpi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ enum acpi_address_range_id {
132132
union acpi_subtable_headers {
133133
struct acpi_subtable_header common;
134134
struct acpi_hmat_structure hmat;
135+
struct acpi_prmt_module_header prmt;
135136
};
136137

137138
typedef int (*acpi_tbl_table_handler)(struct acpi_table_header *table);

include/linux/prmt.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* SPDX-License-Identifier: GPL-2.0-only */
2+
3+
#ifdef CONFIG_ACPI_PRMT
4+
void init_prmt(void);
5+
#else
6+
static inline void init_prmt(void) { }
7+
#endif

0 commit comments

Comments
 (0)