1 // SPDX-License-Identifier: GPL-2.0-only
3 * Author: Erik Kaneda <erik.kaneda@intel.com>
4 * Copyright 2020 Intel Corporation
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
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.
17 #include <linux/kernel.h>
18 #include <linux/efi.h>
19 #include <linux/acpi.h>
20 #include <linux/prmt.h>
24 struct prm_mmio_addr_range
{
30 struct prm_mmio_info
{
32 struct prm_mmio_addr_range addr_ranges
[];
42 struct prm_context_buffer
{
43 char signature
[ACPI_NAMESEG_SIZE
];
47 u64 static_data_buffer
;
48 struct prm_mmio_info
*mmio_ranges
;
52 static LIST_HEAD(prm_module_list
);
54 struct prm_handler_info
{
56 efi_status_t (__efiapi
*handler_addr
)(u64
, void *);
57 u64 static_data_buffer_addr
;
58 u64 acpi_param_buffer_addr
;
60 struct list_head handler_list
;
63 struct prm_module_info
{
68 struct prm_mmio_info
*mmio_info
;
71 struct list_head module_list
;
72 struct prm_handler_info handlers
[] __counted_by(handler_count
);
75 static u64
efi_pa_va_lookup(u64 pa
)
77 efi_memory_desc_t
*md
;
78 u64 pa_offset
= pa
& ~PAGE_MASK
;
79 u64 page
= pa
& PAGE_MASK
;
81 for_each_efi_memory_desc(md
) {
82 if (md
->phys_addr
< pa
&& pa
< md
->phys_addr
+ PAGE_SIZE
* md
->num_pages
)
83 return pa_offset
+ md
->virt_addr
+ page
- md
->phys_addr
;
89 #define get_first_handler(a) ((struct acpi_prmt_handler_info *) ((char *) (a) + a->handler_info_offset))
90 #define get_next_handler(a) ((struct acpi_prmt_handler_info *) (sizeof(struct acpi_prmt_handler_info) + (char *) a))
93 acpi_parse_prmt(union acpi_subtable_headers
*header
, const unsigned long end
)
95 struct acpi_prmt_module_info
*module_info
;
96 struct acpi_prmt_handler_info
*handler_info
;
97 struct prm_handler_info
*th
;
98 struct prm_module_info
*tm
;
101 u32 module_info_size
= 0;
102 u64 mmio_range_size
= 0;
105 module_info
= (struct acpi_prmt_module_info
*) header
;
106 module_info_size
= struct_size(tm
, handlers
, module_info
->handler_info_count
);
107 tm
= kmalloc(module_info_size
, GFP_KERNEL
);
109 goto parse_prmt_out1
;
111 guid_copy(&tm
->guid
, (guid_t
*) module_info
->module_guid
);
112 tm
->major_rev
= module_info
->major_rev
;
113 tm
->minor_rev
= module_info
->minor_rev
;
114 tm
->handler_count
= module_info
->handler_info_count
;
115 tm
->updatable
= true;
117 if (module_info
->mmio_list_pointer
) {
119 * Each module is associated with a list of addr
120 * ranges that it can use during the service
122 mmio_count
= (u64
*) memremap(module_info
->mmio_list_pointer
, 8, MEMREMAP_WB
);
124 goto parse_prmt_out2
;
126 mmio_range_size
= struct_size(tm
->mmio_info
, addr_ranges
, *mmio_count
);
127 tm
->mmio_info
= kmalloc(mmio_range_size
, GFP_KERNEL
);
129 goto parse_prmt_out3
;
131 temp_mmio
= memremap(module_info
->mmio_list_pointer
, mmio_range_size
, MEMREMAP_WB
);
133 goto parse_prmt_out4
;
134 memmove(tm
->mmio_info
, temp_mmio
, mmio_range_size
);
136 tm
->mmio_info
= kmalloc(sizeof(*tm
->mmio_info
), GFP_KERNEL
);
138 goto parse_prmt_out2
;
140 tm
->mmio_info
->mmio_count
= 0;
143 INIT_LIST_HEAD(&tm
->module_list
);
144 list_add(&tm
->module_list
, &prm_module_list
);
146 handler_info
= get_first_handler(module_info
);
148 th
= &tm
->handlers
[cur_handler
];
150 guid_copy(&th
->guid
, (guid_t
*)handler_info
->handler_guid
);
151 th
->handler_addr
= (void *)efi_pa_va_lookup(handler_info
->handler_address
);
152 th
->static_data_buffer_addr
= efi_pa_va_lookup(handler_info
->static_data_buffer_address
);
153 th
->acpi_param_buffer_addr
= efi_pa_va_lookup(handler_info
->acpi_param_buffer_address
);
154 } while (++cur_handler
< tm
->handler_count
&& (handler_info
= get_next_handler(handler_info
)));
159 kfree(tm
->mmio_info
);
161 memunmap(mmio_count
);
169 #define GET_HANDLER 1
171 static void *find_guid_info(const guid_t
*guid
, u8 mode
)
173 struct prm_handler_info
*cur_handler
;
174 struct prm_module_info
*cur_module
;
177 list_for_each_entry(cur_module
, &prm_module_list
, module_list
) {
178 for (i
= 0; i
< cur_module
->handler_count
; ++i
) {
179 cur_handler
= &cur_module
->handlers
[i
];
180 if (guid_equal(guid
, &cur_handler
->guid
)) {
181 if (mode
== GET_MODULE
)
182 return (void *)cur_module
;
184 return (void *)cur_handler
;
192 static struct prm_module_info
*find_prm_module(const guid_t
*guid
)
194 return (struct prm_module_info
*)find_guid_info(guid
, GET_MODULE
);
197 static struct prm_handler_info
*find_prm_handler(const guid_t
*guid
)
199 return (struct prm_handler_info
*) find_guid_info(guid
, GET_HANDLER
);
202 /* In-coming PRM commands */
204 #define PRM_CMD_RUN_SERVICE 0
205 #define PRM_CMD_START_TRANSACTION 1
206 #define PRM_CMD_END_TRANSACTION 2
208 /* statuses that can be passed back to ASL */
210 #define PRM_HANDLER_SUCCESS 0
211 #define PRM_HANDLER_ERROR 1
212 #define INVALID_PRM_COMMAND 2
213 #define PRM_HANDLER_GUID_NOT_FOUND 3
214 #define UPDATE_LOCK_ALREADY_HELD 4
215 #define UPDATE_UNLOCK_WITHOUT_LOCK 5
218 * This is the PlatformRtMechanism opregion space handler.
219 * @function: indicates the read/write. In fact as the PlatformRtMechanism
220 * message is driven by command, only write is meaningful.
224 * @value : it is an in/out parameter. It points to the PRM message buffer.
225 * @handler_context: not used
227 static acpi_status
acpi_platformrt_space_handler(u32 function
,
228 acpi_physical_address addr
,
229 u32 bits
, acpi_integer
*value
,
230 void *handler_context
,
231 void *region_context
)
233 struct prm_buffer
*buffer
= ACPI_CAST_PTR(struct prm_buffer
, value
);
234 struct prm_handler_info
*handler
;
235 struct prm_module_info
*module
;
237 struct prm_context_buffer context
;
239 if (!efi_enabled(EFI_RUNTIME_SERVICES
)) {
240 pr_err_ratelimited("PRM: EFI runtime services no longer available\n");
241 return AE_NO_HANDLER
;
245 * The returned acpi_status will always be AE_OK. Error values will be
246 * saved in the first byte of the PRM message buffer to be used by ASL.
248 switch (buffer
->prm_cmd
) {
249 case PRM_CMD_RUN_SERVICE
:
251 handler
= find_prm_handler(&buffer
->handler_guid
);
252 module
= find_prm_module(&buffer
->handler_guid
);
253 if (!handler
|| !module
)
256 ACPI_COPY_NAMESEG(context
.signature
, "PRMC");
257 context
.revision
= 0x0;
258 context
.reserved
= 0x0;
259 context
.identifier
= handler
->guid
;
260 context
.static_data_buffer
= handler
->static_data_buffer_addr
;
261 context
.mmio_ranges
= module
->mmio_info
;
263 status
= efi_call_acpi_prm_handler(handler
->handler_addr
,
264 handler
->acpi_param_buffer_addr
,
266 if (status
== EFI_SUCCESS
) {
267 buffer
->prm_status
= PRM_HANDLER_SUCCESS
;
269 buffer
->prm_status
= PRM_HANDLER_ERROR
;
270 buffer
->efi_status
= status
;
274 case PRM_CMD_START_TRANSACTION
:
276 module
= find_prm_module(&buffer
->handler_guid
);
280 if (module
->updatable
)
281 module
->updatable
= false;
283 buffer
->prm_status
= UPDATE_LOCK_ALREADY_HELD
;
286 case PRM_CMD_END_TRANSACTION
:
288 module
= find_prm_module(&buffer
->handler_guid
);
292 if (module
->updatable
)
293 buffer
->prm_status
= UPDATE_UNLOCK_WITHOUT_LOCK
;
295 module
->updatable
= true;
300 buffer
->prm_status
= INVALID_PRM_COMMAND
;
307 buffer
->prm_status
= PRM_HANDLER_GUID_NOT_FOUND
;
311 void __init
init_prmt(void)
313 struct acpi_table_header
*tbl
;
317 status
= acpi_get_table(ACPI_SIG_PRMT
, 0, &tbl
);
318 if (ACPI_FAILURE(status
))
321 mc
= acpi_table_parse_entries(ACPI_SIG_PRMT
, sizeof(struct acpi_table_prmt
) +
322 sizeof (struct acpi_table_prmt_header
),
323 0, acpi_parse_prmt
, 0);
326 * Return immediately if PRMT table is not present or no PRM module found.
331 pr_info("PRM: found %u modules\n", mc
);
333 if (!efi_enabled(EFI_RUNTIME_SERVICES
)) {
334 pr_err("PRM: EFI runtime services unavailable\n");
338 status
= acpi_install_address_space_handler(ACPI_ROOT_OBJECT
,
339 ACPI_ADR_SPACE_PLATFORM_RT
,
340 &acpi_platformrt_space_handler
,
342 if (ACPI_FAILURE(status
))
343 pr_alert("PRM: OperationRegion handler could not be installed\n");