ACPI: PRM: implement OperationRegion handler for the PlatformRtMechanism subtype
authorErik Kaneda <erik.kaneda@intel.com>
Thu, 10 Jun 2021 03:41:52 +0000 (20:41 -0700)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Thu, 10 Jun 2021 13:06:54 +0000 (15:06 +0200)
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
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>
drivers/acpi/Kconfig
drivers/acpi/Makefile
drivers/acpi/bus.c
drivers/acpi/prmt.c [new file with mode: 0644]
drivers/acpi/tables.c
include/linux/acpi.h
include/linux/prmt.h [new file with mode: 0644]

index eedec61..3972de7 100644 (file)
@@ -543,3 +543,8 @@ config X86_PM_TIMER
 
          You should nearly always say Y here because many modern
          systems require this timer.
+
+config ACPI_PRMT
+       bool "Platform Runtime Mechanism Support"
+       depends on EFI && X86_64
+       default y
index 700b41a..efb0d1f 100644 (file)
@@ -61,6 +61,7 @@ acpi-$(CONFIG_ACPI_FPDT)      += acpi_fpdt.o
 acpi-$(CONFIG_ACPI_LPIT)       += acpi_lpit.o
 acpi-$(CONFIG_ACPI_GENERIC_GSI) += irq.o
 acpi-$(CONFIG_ACPI_WATCHDOG)   += acpi_watchdog.o
+acpi-$(CONFIG_ACPI_PRMT)       += prmt.o
 
 # Address translation
 acpi-$(CONFIG_ACPI_ADXL)       += acpi_adxl.o
index be7da23..3484497 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/pci.h>
 #include <acpi/apei.h>
 #include <linux/suspend.h>
+#include <linux/prmt.h>
 
 #include "internal.h"
 
@@ -1330,6 +1331,7 @@ static int __init acpi_init(void)
                acpi_kobj = NULL;
        }
 
+       init_prmt();
        result = acpi_bus_init();
        if (result) {
                disable_acpi();
diff --git a/drivers/acpi/prmt.c b/drivers/acpi/prmt.c
new file mode 100644 (file)
index 0000000..33c2746
--- /dev/null
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Author: Erik Kaneda <erik.kaneda@intel.com>
+ * Copyright 2020 Intel Corporation
+ *
+ * prmt.c
+ *
+ * Each PRM service is an executable that is run in a restricted environment
+ * that is invoked by writing to the PlatformRtMechanism OperationRegion from
+ * AML bytecode.
+ *
+ * init_prmt initializes the Platform Runtime Mechanism (PRM) services by
+ * processing data in the PRMT as well as registering an ACPI OperationRegion
+ * handler for the PlatformRtMechanism subtype.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/efi.h>
+#include <linux/acpi.h>
+#include <linux/prmt.h>
+#include <asm/efi.h>
+
+#pragma pack(1)
+struct prm_mmio_addr_range {
+       u64 phys_addr;
+       u64 virt_addr;
+       u32 length;
+};
+
+struct prm_mmio_info {
+       u64 mmio_count;
+       struct prm_mmio_addr_range addr_ranges[];
+};
+
+struct prm_buffer {
+       u8 prm_status;
+       u64 efi_status;
+       u8 prm_cmd;
+       guid_t handler_guid;
+};
+
+struct prm_context_buffer {
+       char signature[ACPI_NAMESEG_SIZE];
+       u16 revision;
+       u16 reserved;
+       guid_t identifier;
+       u64 static_data_buffer;
+       struct prm_mmio_info *mmio_ranges;
+};
+#pragma pack()
+
+
+LIST_HEAD(prm_module_list);
+
+struct prm_handler_info {
+       guid_t guid;
+       u64 handler_addr;
+       u64 static_data_buffer_addr;
+       u64 acpi_param_buffer_addr;
+
+       struct list_head handler_list;
+};
+
+struct prm_module_info {
+       guid_t guid;
+       u16 major_rev;
+       u16 minor_rev;
+       u16 handler_count;
+       struct prm_mmio_info *mmio_info;
+       bool updatable;
+
+       struct list_head module_list;
+       struct prm_handler_info handlers[];
+};
+
+
+static u64 efi_pa_va_lookup(u64 pa)
+{
+       efi_memory_desc_t *md;
+       u64 pa_offset = pa & ~PAGE_MASK;
+       u64 page = pa & PAGE_MASK;
+
+       for_each_efi_memory_desc(md) {
+               if (md->phys_addr < pa && pa < md->phys_addr + PAGE_SIZE * md->num_pages)
+                       return pa_offset + md->virt_addr + page - md->phys_addr;
+       }
+
+       return 0;
+}
+
+
+#define get_first_handler(a) ((struct acpi_prmt_handler_info *) ((char *) (a) + a->handler_info_offset))
+#define get_next_handler(a) ((struct acpi_prmt_handler_info *) (sizeof(struct acpi_prmt_handler_info) + (char *) a))
+
+static int __init
+acpi_parse_prmt(union acpi_subtable_headers *header, const unsigned long end)
+{
+       struct acpi_prmt_module_info *module_info;
+       struct acpi_prmt_handler_info *handler_info;
+       struct prm_handler_info *th;
+       struct prm_module_info *tm;
+       u64 mmio_count = 0;
+       u64 cur_handler = 0;
+       u32 module_info_size = 0;
+       u64 mmio_range_size = 0;
+       void *temp_mmio;
+
+       module_info = (struct acpi_prmt_module_info *) header;
+       module_info_size = struct_size(tm, handlers, module_info->handler_info_count);
+       tm = kmalloc(module_info_size, GFP_KERNEL);
+
+       guid_copy(&tm->guid, (guid_t *) module_info->module_guid);
+       tm->major_rev = module_info->major_rev;
+       tm->minor_rev = module_info->minor_rev;
+       tm->handler_count = module_info->handler_info_count;
+       tm->updatable = true;
+
+       if (module_info->mmio_list_pointer) {
+               /*
+                * Each module is associated with a list of addr
+                * ranges that it can use during the service
+                */
+               mmio_count = *(u64 *) memremap(module_info->mmio_list_pointer, 8, MEMREMAP_WB);
+               mmio_range_size = struct_size(tm->mmio_info, addr_ranges, mmio_count);
+               tm->mmio_info = kmalloc(mmio_range_size, GFP_KERNEL);
+               temp_mmio = memremap(module_info->mmio_list_pointer, mmio_range_size, MEMREMAP_WB);
+               memmove(tm->mmio_info, temp_mmio, mmio_range_size);
+       } else {
+               mmio_range_size = struct_size(tm->mmio_info, addr_ranges, mmio_count);
+               tm->mmio_info = kmalloc(mmio_range_size, GFP_KERNEL);
+               tm->mmio_info->mmio_count = 0;
+       }
+
+       INIT_LIST_HEAD(&tm->module_list);
+       list_add(&tm->module_list, &prm_module_list);
+
+       handler_info = get_first_handler(module_info);
+       do {
+               th = &tm->handlers[cur_handler];
+
+               guid_copy(&th->guid, (guid_t *)handler_info->handler_guid);
+               th->handler_addr = efi_pa_va_lookup(handler_info->handler_address);
+               th->static_data_buffer_addr = efi_pa_va_lookup(handler_info->static_data_buffer_address);
+               th->acpi_param_buffer_addr = efi_pa_va_lookup(handler_info->acpi_param_buffer_address);
+       } while (++cur_handler < tm->handler_count && (handler_info = get_next_handler(handler_info)));
+
+       return 0;
+}
+
+#define GET_MODULE     0
+#define GET_HANDLER    1
+
+static void *find_guid_info(const guid_t *guid, u8 mode)
+{
+       struct prm_handler_info *cur_handler;
+       struct prm_module_info *cur_module;
+       int i = 0;
+
+       list_for_each_entry(cur_module, &prm_module_list, module_list) {
+               for (i = 0; i < cur_module->handler_count; ++i) {
+                       cur_handler = &cur_module->handlers[i];
+                       if (guid_equal(guid, &cur_handler->guid)) {
+                               if (mode == GET_MODULE)
+                                       return (void *)cur_module;
+                               else
+                                       return (void *)cur_handler;
+                       }
+               }
+       }
+
+       return NULL;
+}
+
+
+static struct prm_module_info *find_prm_module(const guid_t *guid)
+{
+       return (struct prm_module_info *)find_guid_info(guid, GET_MODULE);
+}
+
+static struct prm_handler_info *find_prm_handler(const guid_t *guid)
+{
+       return (struct prm_handler_info *) find_guid_info(guid, GET_HANDLER);
+}
+
+/* In-coming PRM commands */
+
+#define PRM_CMD_RUN_SERVICE            0
+#define PRM_CMD_START_TRANSACTION      1
+#define PRM_CMD_END_TRANSACTION                2
+
+/* statuses that can be passed back to ASL */
+
+#define PRM_HANDLER_SUCCESS            0
+#define PRM_HANDLER_ERROR              1
+#define INVALID_PRM_COMMAND            2
+#define PRM_HANDLER_GUID_NOT_FOUND     3
+#define UPDATE_LOCK_ALREADY_HELD       4
+#define UPDATE_UNLOCK_WITHOUT_LOCK     5
+
+/*
+ * This is the PlatformRtMechanism opregion space handler.
+ * @function: indicates the read/write. In fact as the PlatformRtMechanism
+ * message is driven by command, only write is meaningful.
+ *
+ * @addr   : not used
+ * @bits   : not used.
+ * @value  : it is an in/out parameter. It points to the PRM message buffer.
+ * @handler_context: not used
+ */
+static acpi_status acpi_platformrt_space_handler(u32 function,
+                                                acpi_physical_address addr,
+                                                u32 bits, acpi_integer *value,
+                                                void *handler_context,
+                                                void *region_context)
+{
+       struct prm_buffer *buffer = ACPI_CAST_PTR(struct prm_buffer, value);
+       struct prm_handler_info *handler;
+       struct prm_module_info *module;
+       efi_status_t status;
+       struct prm_context_buffer context;
+
+       /*
+        * The returned acpi_status will always be AE_OK. Error values will be
+        * saved in the first byte of the PRM message buffer to be used by ASL.
+        */
+       switch (buffer->prm_cmd) {
+       case PRM_CMD_RUN_SERVICE:
+
+               handler = find_prm_handler(&buffer->handler_guid);
+               module = find_prm_module(&buffer->handler_guid);
+               if (!handler || !module)
+                       goto invalid_guid;
+
+               ACPI_COPY_NAMESEG(context.signature, "PRMC");
+               context.revision = 0x0;
+               context.reserved = 0x0;
+               context.identifier = handler->guid;
+               context.static_data_buffer = handler->static_data_buffer_addr;
+               context.mmio_ranges = module->mmio_info;
+
+               status = efi_call_virt_pointer(handler, handler_addr,
+                                              handler->acpi_param_buffer_addr,
+                                              &context);
+               if (status == EFI_SUCCESS) {
+                       buffer->prm_status = PRM_HANDLER_SUCCESS;
+               } else {
+                       buffer->prm_status = PRM_HANDLER_ERROR;
+                       buffer->efi_status = status;
+               }
+               break;
+
+       case PRM_CMD_START_TRANSACTION:
+
+               module = find_prm_module(&buffer->handler_guid);
+               if (!module)
+                       goto invalid_guid;
+
+               if (module->updatable)
+                       module->updatable = false;
+               else
+                       buffer->prm_status = UPDATE_LOCK_ALREADY_HELD;
+               break;
+
+       case PRM_CMD_END_TRANSACTION:
+
+               module = find_prm_module(&buffer->handler_guid);
+               if (!module)
+                       goto invalid_guid;
+
+               if (module->updatable)
+                       buffer->prm_status = UPDATE_UNLOCK_WITHOUT_LOCK;
+               else
+                       module->updatable = true;
+               break;
+
+       default:
+
+               buffer->prm_status = INVALID_PRM_COMMAND;
+               break;
+       }
+
+       return AE_OK;
+
+invalid_guid:
+       buffer->prm_status = PRM_HANDLER_GUID_NOT_FOUND;
+       return AE_OK;
+}
+
+void __init init_prmt(void)
+{
+       acpi_status status;
+       int mc = acpi_table_parse_entries(ACPI_SIG_PRMT, sizeof(struct acpi_table_prmt) +
+                                         sizeof (struct acpi_table_prmt_header),
+                                         0, acpi_parse_prmt, 0);
+       pr_info("PRM: found %u modules\n", mc);
+
+       status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
+                                                   ACPI_ADR_SPACE_PLATFORM_RT,
+                                                   &acpi_platformrt_space_handler,
+                                                   NULL, NULL);
+       if (ACPI_FAILURE(status))
+               pr_alert("PRM: OperationRegion handler could not be installed\n");
+}
index 9d58104..a37a153 100644 (file)
@@ -39,6 +39,7 @@ static int acpi_apic_instance __initdata;
 enum acpi_subtable_type {
        ACPI_SUBTABLE_COMMON,
        ACPI_SUBTABLE_HMAT,
+       ACPI_SUBTABLE_PRMT,
 };
 
 struct acpi_subtable_entry {
@@ -222,6 +223,8 @@ acpi_get_entry_type(struct acpi_subtable_entry *entry)
                return entry->hdr->common.type;
        case ACPI_SUBTABLE_HMAT:
                return entry->hdr->hmat.type;
+       case ACPI_SUBTABLE_PRMT:
+               return 0;
        }
        return 0;
 }
@@ -234,6 +237,8 @@ acpi_get_entry_length(struct acpi_subtable_entry *entry)
                return entry->hdr->common.length;
        case ACPI_SUBTABLE_HMAT:
                return entry->hdr->hmat.length;
+       case ACPI_SUBTABLE_PRMT:
+               return entry->hdr->prmt.length;
        }
        return 0;
 }
@@ -246,6 +251,8 @@ acpi_get_subtable_header_length(struct acpi_subtable_entry *entry)
                return sizeof(entry->hdr->common);
        case ACPI_SUBTABLE_HMAT:
                return sizeof(entry->hdr->hmat);
+       case ACPI_SUBTABLE_PRMT:
+               return sizeof(entry->hdr->prmt);
        }
        return 0;
 }
@@ -255,6 +262,8 @@ acpi_get_subtable_type(char *id)
 {
        if (strncmp(id, ACPI_SIG_HMAT, 4) == 0)
                return ACPI_SUBTABLE_HMAT;
+       if (strncmp(id, ACPI_SIG_PRMT, 4) == 0)
+               return ACPI_SUBTABLE_PRMT;
        return ACPI_SUBTABLE_COMMON;
 }
 
index c60745f..4c07ac2 100644 (file)
@@ -132,6 +132,7 @@ enum acpi_address_range_id {
 union acpi_subtable_headers {
        struct acpi_subtable_header common;
        struct acpi_hmat_structure hmat;
+       struct acpi_prmt_module_header prmt;
 };
 
 typedef int (*acpi_tbl_table_handler)(struct acpi_table_header *table);
diff --git a/include/linux/prmt.h b/include/linux/prmt.h
new file mode 100644 (file)
index 0000000..24da836
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifdef CONFIG_ACPI_PRMT
+void init_prmt(void);
+#else
+static inline void init_prmt(void) { }
+#endif