2 * OpRegion handler to allow AML to call native firmware
4 * (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
5 * Bjorn Helgaas <bjorn.helgaas@hp.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This driver implements HP Open Source Review Board proposal 1842,
12 * which was approved on 9/20/2006.
14 * For technical documentation, see the HP SPPA Firmware EAS, Appendix F.
16 * ACPI does not define a mechanism for AML methods to call native firmware
17 * interfaces such as PAL or SAL. This OpRegion handler adds such a mechanism.
18 * After the handler is installed, an AML method can call native firmware by
19 * storing the arguments and firmware entry point to specific offsets in the
20 * OpRegion. When AML reads the "return value" offset from the OpRegion, this
21 * handler loads up the arguments, makes the firmware call, and returns the
25 #include <linux/module.h>
26 #include <linux/acpi.h>
29 MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
30 MODULE_LICENSE("GPL");
31 MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
33 static bool force_register;
34 module_param_named(force, force_register, bool, 0);
35 MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
37 #define AML_NFW_SPACE 0xA1
45 * N.B. The layout of this structure is defined in the HP SPPA FW EAS, and
46 * the member offsets are embedded in AML methods.
48 struct ia64_nfw_context {
50 struct ia64_sal_retval ret;
56 static void *virt_map(u64 address)
58 if (address & (1UL << 63))
59 return (void *) (__IA64_UNCACHED_OFFSET | address);
64 static void aml_nfw_execute(struct ia64_nfw_context *c)
66 struct ia64_pdesc virt_entry;
67 ia64_sal_handler entry;
69 virt_entry.ip = virt_map(c->ip);
70 virt_entry.gp = virt_map(c->gp);
72 entry = (ia64_sal_handler) &virt_entry;
74 IA64_FW_CALL(entry, c->ret,
75 c->arg[0], c->arg[1], c->arg[2], c->arg[3],
76 c->arg[4], c->arg[5], c->arg[6], c->arg[7]);
79 static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value)
83 *value = *(u8 *)offset;
86 *value = *(u16 *)offset;
89 *value = *(u32 *)offset;
92 *value = *(u64 *)offset;
97 static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value)
101 *(u8 *) offset = *value;
104 *(u16 *) offset = *value;
107 *(u32 *) offset = *value;
110 *(u64 *) offset = *value;
115 static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
116 u32 bit_width, u64 *value, void *handler_context,
117 void *region_context)
119 struct ia64_nfw_context *context = handler_context;
120 u8 *offset = (u8 *) context + address;
122 if (bit_width != 8 && bit_width != 16 &&
123 bit_width != 32 && bit_width != 64)
124 return AE_BAD_PARAMETER;
126 if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context))
127 return AE_BAD_PARAMETER;
131 if (address == offsetof(struct ia64_nfw_context, ret))
132 aml_nfw_execute(context);
133 aml_nfw_read_arg(offset, bit_width, value);
136 aml_nfw_write_arg(offset, bit_width, value);
143 static struct ia64_nfw_context global_context;
144 static int global_handler_registered;
146 static int aml_nfw_add_global_handler(void)
150 if (global_handler_registered)
153 status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
154 AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context);
155 if (ACPI_FAILURE(status))
158 global_handler_registered = 1;
159 printk(KERN_INFO "Global 0x%02X opregion handler registered\n",
164 static int aml_nfw_remove_global_handler(void)
168 if (!global_handler_registered)
171 status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
172 AML_NFW_SPACE, aml_nfw_handler);
173 if (ACPI_FAILURE(status))
176 global_handler_registered = 0;
177 printk(KERN_INFO "Global 0x%02X opregion handler removed\n",
182 static int aml_nfw_add(struct acpi_device *device)
185 * We would normally allocate a new context structure and install
186 * the address space handler for the specific device we found.
187 * But the HP-UX implementation shares a single global context
188 * and always puts the handler at the root, so we'll do the same.
190 return aml_nfw_add_global_handler();
193 static int aml_nfw_remove(struct acpi_device *device)
195 return aml_nfw_remove_global_handler();
198 static const struct acpi_device_id aml_nfw_ids[] = {
203 static struct acpi_driver acpi_aml_nfw_driver = {
204 .name = "native firmware",
208 .remove = aml_nfw_remove,
212 static int __init aml_nfw_init(void)
217 aml_nfw_add_global_handler();
219 result = acpi_bus_register_driver(&acpi_aml_nfw_driver);
221 aml_nfw_remove_global_handler();
228 static void __exit aml_nfw_exit(void)
230 acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
231 aml_nfw_remove_global_handler();
234 module_init(aml_nfw_init);
235 module_exit(aml_nfw_exit);