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 <acpi/acpi_bus.h>
27 #include <acpi/acpi_drivers.h>
30 MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
31 MODULE_LICENSE("GPL");
32 MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
34 static int force_register
;
35 module_param_named(force
, force_register
, bool, 0);
36 MODULE_PARM_DESC(force
, "Install opregion handler even without HPQ5001 device");
38 #define AML_NFW_SPACE 0xA1
46 * N.B. The layout of this structure is defined in the HP SPPA FW EAS, and
47 * the member offsets are embedded in AML methods.
49 struct ia64_nfw_context
{
51 struct ia64_sal_retval ret
;
57 static void *virt_map(u64 address
)
59 if (address
& (1UL << 63))
60 return (void *) (__IA64_UNCACHED_OFFSET
| address
);
65 static void aml_nfw_execute(struct ia64_nfw_context
*c
)
67 struct ia64_pdesc virt_entry
;
68 ia64_sal_handler entry
;
70 virt_entry
.ip
= virt_map(c
->ip
);
71 virt_entry
.gp
= virt_map(c
->gp
);
73 entry
= (ia64_sal_handler
) &virt_entry
;
75 IA64_FW_CALL(entry
, c
->ret
,
76 c
->arg
[0], c
->arg
[1], c
->arg
[2], c
->arg
[3],
77 c
->arg
[4], c
->arg
[5], c
->arg
[6], c
->arg
[7]);
80 static void aml_nfw_read_arg(u8
*offset
, u32 bit_width
, u64
*value
)
84 *value
= *(u8
*)offset
;
87 *value
= *(u16
*)offset
;
90 *value
= *(u32
*)offset
;
93 *value
= *(u64
*)offset
;
98 static void aml_nfw_write_arg(u8
*offset
, u32 bit_width
, u64
*value
)
102 *(u8
*) offset
= *value
;
105 *(u16
*) offset
= *value
;
108 *(u32
*) offset
= *value
;
111 *(u64
*) offset
= *value
;
116 static acpi_status
aml_nfw_handler(u32 function
, acpi_physical_address address
,
117 u32 bit_width
, u64
*value
, void *handler_context
,
118 void *region_context
)
120 struct ia64_nfw_context
*context
= handler_context
;
121 u8
*offset
= (u8
*) context
+ address
;
123 if (bit_width
!= 8 && bit_width
!= 16 &&
124 bit_width
!= 32 && bit_width
!= 64)
125 return AE_BAD_PARAMETER
;
127 if (address
+ (bit_width
>> 3) > sizeof(struct ia64_nfw_context
))
128 return AE_BAD_PARAMETER
;
132 if (address
== offsetof(struct ia64_nfw_context
, ret
))
133 aml_nfw_execute(context
);
134 aml_nfw_read_arg(offset
, bit_width
, value
);
137 aml_nfw_write_arg(offset
, bit_width
, value
);
144 static struct ia64_nfw_context global_context
;
145 static int global_handler_registered
;
147 static int aml_nfw_add_global_handler(void)
151 if (global_handler_registered
)
154 status
= acpi_install_address_space_handler(ACPI_ROOT_OBJECT
,
155 AML_NFW_SPACE
, aml_nfw_handler
, NULL
, &global_context
);
156 if (ACPI_FAILURE(status
))
159 global_handler_registered
= 1;
160 printk(KERN_INFO
"Global 0x%02X opregion handler registered\n",
165 static int aml_nfw_remove_global_handler(void)
169 if (!global_handler_registered
)
172 status
= acpi_remove_address_space_handler(ACPI_ROOT_OBJECT
,
173 AML_NFW_SPACE
, aml_nfw_handler
);
174 if (ACPI_FAILURE(status
))
177 global_handler_registered
= 0;
178 printk(KERN_INFO
"Global 0x%02X opregion handler removed\n",
183 static int aml_nfw_add(struct acpi_device
*device
)
186 * We would normally allocate a new context structure and install
187 * the address space handler for the specific device we found.
188 * But the HP-UX implementation shares a single global context
189 * and always puts the handler at the root, so we'll do the same.
191 return aml_nfw_add_global_handler();
194 static int aml_nfw_remove(struct acpi_device
*device
, int type
)
196 return aml_nfw_remove_global_handler();
199 static const struct acpi_device_id aml_nfw_ids
[] = {
204 static struct acpi_driver acpi_aml_nfw_driver
= {
205 .name
= "native firmware",
209 .remove
= aml_nfw_remove
,
213 static int __init
aml_nfw_init(void)
218 aml_nfw_add_global_handler();
220 result
= acpi_bus_register_driver(&acpi_aml_nfw_driver
);
222 aml_nfw_remove_global_handler();
229 static void __exit
aml_nfw_exit(void)
231 acpi_bus_unregister_driver(&acpi_aml_nfw_driver
);
232 aml_nfw_remove_global_handler();
235 module_init(aml_nfw_init
);
236 module_exit(aml_nfw_exit
);