|
@@ -0,0 +1,236 @@
|
|
|
+/*
|
|
|
+ * OpRegion handler to allow AML to call native firmware
|
|
|
+ *
|
|
|
+ * (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
|
|
|
+ * Bjorn Helgaas <bjorn.helgaas@hp.com>
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU General Public License version 2 as
|
|
|
+ * published by the Free Software Foundation.
|
|
|
+ *
|
|
|
+ * This driver implements HP Open Source Review Board proposal 1842,
|
|
|
+ * which was approved on 9/20/2006.
|
|
|
+ *
|
|
|
+ * For technical documentation, see the HP SPPA Firmware EAS, Appendix F.
|
|
|
+ *
|
|
|
+ * ACPI does not define a mechanism for AML methods to call native firmware
|
|
|
+ * interfaces such as PAL or SAL. This OpRegion handler adds such a mechanism.
|
|
|
+ * After the handler is installed, an AML method can call native firmware by
|
|
|
+ * storing the arguments and firmware entry point to specific offsets in the
|
|
|
+ * OpRegion. When AML reads the "return value" offset from the OpRegion, this
|
|
|
+ * handler loads up the arguments, makes the firmware call, and returns the
|
|
|
+ * result.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <acpi/acpi_bus.h>
|
|
|
+#include <acpi/acpi_drivers.h>
|
|
|
+#include <asm/sal.h>
|
|
|
+
|
|
|
+MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
+MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
|
|
|
+
|
|
|
+static int force_register;
|
|
|
+module_param_named(force, force_register, bool, 0);
|
|
|
+MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
|
|
|
+
|
|
|
+#define AML_NFW_SPACE 0xA1
|
|
|
+
|
|
|
+struct ia64_pdesc {
|
|
|
+ void *ip;
|
|
|
+ void *gp;
|
|
|
+};
|
|
|
+
|
|
|
+/*
|
|
|
+ * N.B. The layout of this structure is defined in the HP SPPA FW EAS, and
|
|
|
+ * the member offsets are embedded in AML methods.
|
|
|
+ */
|
|
|
+struct ia64_nfw_context {
|
|
|
+ u64 arg[8];
|
|
|
+ struct ia64_sal_retval ret;
|
|
|
+ u64 ip;
|
|
|
+ u64 gp;
|
|
|
+ u64 pad[2];
|
|
|
+};
|
|
|
+
|
|
|
+static void *virt_map(u64 address)
|
|
|
+{
|
|
|
+ if (address & (1UL << 63))
|
|
|
+ return (void *) (__IA64_UNCACHED_OFFSET | address);
|
|
|
+
|
|
|
+ return __va(address);
|
|
|
+}
|
|
|
+
|
|
|
+static void aml_nfw_execute(struct ia64_nfw_context *c)
|
|
|
+{
|
|
|
+ struct ia64_pdesc virt_entry;
|
|
|
+ ia64_sal_handler entry;
|
|
|
+
|
|
|
+ virt_entry.ip = virt_map(c->ip);
|
|
|
+ virt_entry.gp = virt_map(c->gp);
|
|
|
+
|
|
|
+ entry = (ia64_sal_handler) &virt_entry;
|
|
|
+
|
|
|
+ IA64_FW_CALL(entry, c->ret,
|
|
|
+ c->arg[0], c->arg[1], c->arg[2], c->arg[3],
|
|
|
+ c->arg[4], c->arg[5], c->arg[6], c->arg[7]);
|
|
|
+}
|
|
|
+
|
|
|
+static void aml_nfw_read_arg(u8 *offset, u32 bit_width, acpi_integer *value)
|
|
|
+{
|
|
|
+ switch (bit_width) {
|
|
|
+ case 8:
|
|
|
+ *value = *(u8 *)offset;
|
|
|
+ break;
|
|
|
+ case 16:
|
|
|
+ *value = *(u16 *)offset;
|
|
|
+ break;
|
|
|
+ case 32:
|
|
|
+ *value = *(u32 *)offset;
|
|
|
+ break;
|
|
|
+ case 64:
|
|
|
+ *value = *(u64 *)offset;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void aml_nfw_write_arg(u8 *offset, u32 bit_width, acpi_integer *value)
|
|
|
+{
|
|
|
+ switch (bit_width) {
|
|
|
+ case 8:
|
|
|
+ *(u8 *) offset = *value;
|
|
|
+ break;
|
|
|
+ case 16:
|
|
|
+ *(u16 *) offset = *value;
|
|
|
+ break;
|
|
|
+ case 32:
|
|
|
+ *(u32 *) offset = *value;
|
|
|
+ break;
|
|
|
+ case 64:
|
|
|
+ *(u64 *) offset = *value;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
|
|
|
+ u32 bit_width, acpi_integer *value, void *handler_context,
|
|
|
+ void *region_context)
|
|
|
+{
|
|
|
+ struct ia64_nfw_context *context = handler_context;
|
|
|
+ u8 *offset = (u8 *) context + address;
|
|
|
+
|
|
|
+ if (bit_width != 8 && bit_width != 16 &&
|
|
|
+ bit_width != 32 && bit_width != 64)
|
|
|
+ return AE_BAD_PARAMETER;
|
|
|
+
|
|
|
+ if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context))
|
|
|
+ return AE_BAD_PARAMETER;
|
|
|
+
|
|
|
+ switch (function) {
|
|
|
+ case ACPI_READ:
|
|
|
+ if (address == offsetof(struct ia64_nfw_context, ret))
|
|
|
+ aml_nfw_execute(context);
|
|
|
+ aml_nfw_read_arg(offset, bit_width, value);
|
|
|
+ break;
|
|
|
+ case ACPI_WRITE:
|
|
|
+ aml_nfw_write_arg(offset, bit_width, value);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return AE_OK;
|
|
|
+}
|
|
|
+
|
|
|
+static struct ia64_nfw_context global_context;
|
|
|
+static int global_handler_registered;
|
|
|
+
|
|
|
+static int aml_nfw_add_global_handler(void)
|
|
|
+{
|
|
|
+ acpi_status status;
|
|
|
+
|
|
|
+ if (global_handler_registered)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
|
|
|
+ AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context);
|
|
|
+ if (ACPI_FAILURE(status))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ global_handler_registered = 1;
|
|
|
+ printk(KERN_INFO "Global 0x%02X opregion handler registered\n",
|
|
|
+ AML_NFW_SPACE);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int aml_nfw_remove_global_handler(void)
|
|
|
+{
|
|
|
+ acpi_status status;
|
|
|
+
|
|
|
+ if (!global_handler_registered)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
|
|
|
+ AML_NFW_SPACE, aml_nfw_handler);
|
|
|
+ if (ACPI_FAILURE(status))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ global_handler_registered = 0;
|
|
|
+ printk(KERN_INFO "Global 0x%02X opregion handler removed\n",
|
|
|
+ AML_NFW_SPACE);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int aml_nfw_add(struct acpi_device *device)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * We would normally allocate a new context structure and install
|
|
|
+ * the address space handler for the specific device we found.
|
|
|
+ * But the HP-UX implementation shares a single global context
|
|
|
+ * and always puts the handler at the root, so we'll do the same.
|
|
|
+ */
|
|
|
+ return aml_nfw_add_global_handler();
|
|
|
+}
|
|
|
+
|
|
|
+static int aml_nfw_remove(struct acpi_device *device, int type)
|
|
|
+{
|
|
|
+ return aml_nfw_remove_global_handler();
|
|
|
+}
|
|
|
+
|
|
|
+static const struct acpi_device_id aml_nfw_ids[] = {
|
|
|
+ {"HPQ5001", 0},
|
|
|
+ {"", 0}
|
|
|
+};
|
|
|
+
|
|
|
+static struct acpi_driver acpi_aml_nfw_driver = {
|
|
|
+ .name = "native firmware",
|
|
|
+ .ids = aml_nfw_ids,
|
|
|
+ .ops = {
|
|
|
+ .add = aml_nfw_add,
|
|
|
+ .remove = aml_nfw_remove,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+static int __init aml_nfw_init(void)
|
|
|
+{
|
|
|
+ int result;
|
|
|
+
|
|
|
+ if (force_register)
|
|
|
+ aml_nfw_add_global_handler();
|
|
|
+
|
|
|
+ result = acpi_bus_register_driver(&acpi_aml_nfw_driver);
|
|
|
+ if (result < 0) {
|
|
|
+ aml_nfw_remove_global_handler();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void __exit aml_nfw_exit(void)
|
|
|
+{
|
|
|
+ acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
|
|
|
+ aml_nfw_remove_global_handler();
|
|
|
+}
|
|
|
+
|
|
|
+module_init(aml_nfw_init);
|
|
|
+module_exit(aml_nfw_exit);
|