Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <erik.kaneda@intel.com>
[ rjw: Subject and changelog edits ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Erik Kaneda and committed by
Rafael J. Wysocki
cefc7ca4 9f8c7bae

+328
+5
drivers/acpi/Kconfig
··· 543 543 544 544 You should nearly always say Y here because many modern 545 545 systems require this timer. 546 + 547 + config ACPI_PRMT 548 + bool "Platform Runtime Mechanism Support" 549 + depends on EFI && X86_64 550 + default y
+1
drivers/acpi/Makefile
··· 61 61 acpi-$(CONFIG_ACPI_LPIT) += acpi_lpit.o 62 62 acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o 63 63 acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o 64 + acpi-$(CONFIG_ACPI_PRMT) += prmt.o 64 65 65 66 # Address translation 66 67 acpi-$(CONFIG_ACPI_ADXL) += acpi_adxl.o
+2
drivers/acpi/bus.c
··· 30 30 #include <linux/pci.h> 31 31 #include <acpi/apei.h> 32 32 #include <linux/suspend.h> 33 + #include <linux/prmt.h> 33 34 34 35 #include "internal.h" 35 36 ··· 1331 1330 acpi_kobj = NULL; 1332 1331 } 1333 1332 1333 + init_prmt(); 1334 1334 result = acpi_bus_init(); 1335 1335 if (result) { 1336 1336 disable_acpi();
+303
drivers/acpi/prmt.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Author: Erik Kaneda <erik.kaneda@intel.com> 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 + }
+9
drivers/acpi/tables.c
··· 39 39 enum acpi_subtable_type { 40 40 ACPI_SUBTABLE_COMMON, 41 41 ACPI_SUBTABLE_HMAT, 42 + ACPI_SUBTABLE_PRMT, 42 43 }; 43 44 44 45 struct acpi_subtable_entry { ··· 223 222 return entry->hdr->common.type; 224 223 case ACPI_SUBTABLE_HMAT: 225 224 return entry->hdr->hmat.type; 225 + case ACPI_SUBTABLE_PRMT: 226 + return 0; 226 227 } 227 228 return 0; 228 229 } ··· 237 234 return entry->hdr->common.length; 238 235 case ACPI_SUBTABLE_HMAT: 239 236 return entry->hdr->hmat.length; 237 + case ACPI_SUBTABLE_PRMT: 238 + return entry->hdr->prmt.length; 240 239 } 241 240 return 0; 242 241 } ··· 251 246 return sizeof(entry->hdr->common); 252 247 case ACPI_SUBTABLE_HMAT: 253 248 return sizeof(entry->hdr->hmat); 249 + case ACPI_SUBTABLE_PRMT: 250 + return sizeof(entry->hdr->prmt); 254 251 } 255 252 return 0; 256 253 } ··· 262 255 { 263 256 if (strncmp(id, ACPI_SIG_HMAT, 4) == 0) 264 257 return ACPI_SUBTABLE_HMAT; 258 + if (strncmp(id, ACPI_SIG_PRMT, 4) == 0) 259 + return ACPI_SUBTABLE_PRMT; 265 260 return ACPI_SUBTABLE_COMMON; 266 261 } 267 262
+1
include/linux/acpi.h
··· 132 132 union acpi_subtable_headers { 133 133 struct acpi_subtable_header common; 134 134 struct acpi_hmat_structure hmat; 135 + struct acpi_prmt_module_header prmt; 135 136 }; 136 137 137 138 typedef int (*acpi_tbl_table_handler)(struct acpi_table_header *table);
+7
include/linux/prmt.h
··· 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