diff options
Diffstat (limited to 'arch')
-rw-r--r-- | arch/ia64/Kconfig | 10 | ||||
-rw-r--r-- | arch/ia64/hp/common/Makefile | 1 | ||||
-rw-r--r-- | arch/ia64/hp/common/aml_nfw.c | 236 |
3 files changed, 247 insertions, 0 deletions
diff --git a/arch/ia64/Kconfig b/arch/ia64/Kconfig index 8c39913d1729..2e6310b8eab7 100644 --- a/arch/ia64/Kconfig +++ b/arch/ia64/Kconfig | |||
@@ -461,6 +461,16 @@ config IA64_ESI | |||
461 | firmware extensions, such as the ability to inject memory-errors | 461 | firmware extensions, such as the ability to inject memory-errors |
462 | for test-purposes. If you're unsure, say N. | 462 | for test-purposes. If you're unsure, say N. |
463 | 463 | ||
464 | config IA64_HP_AML_NFW | ||
465 | bool "Support ACPI AML calls to native firmware" | ||
466 | help | ||
467 | This driver installs a global ACPI Operation Region handler for | ||
468 | region 0xA1. AML methods can use this OpRegion to call arbitrary | ||
469 | native firmware functions. The driver installs the OpRegion | ||
470 | handler if there is an HPQ5001 device or if the user supplies | ||
471 | the "force" module parameter, e.g., with the "aml_nfw.force" | ||
472 | kernel command line option. | ||
473 | |||
464 | source "drivers/sn/Kconfig" | 474 | source "drivers/sn/Kconfig" |
465 | 475 | ||
466 | config KEXEC | 476 | config KEXEC |
diff --git a/arch/ia64/hp/common/Makefile b/arch/ia64/hp/common/Makefile index f61a60057ff7..9e179dd06b85 100644 --- a/arch/ia64/hp/common/Makefile +++ b/arch/ia64/hp/common/Makefile | |||
@@ -8,3 +8,4 @@ | |||
8 | obj-y := sba_iommu.o | 8 | obj-y := sba_iommu.o |
9 | obj-$(CONFIG_IA64_HP_ZX1_SWIOTLB) += hwsw_iommu.o | 9 | obj-$(CONFIG_IA64_HP_ZX1_SWIOTLB) += hwsw_iommu.o |
10 | obj-$(CONFIG_IA64_GENERIC) += hwsw_iommu.o | 10 | obj-$(CONFIG_IA64_GENERIC) += hwsw_iommu.o |
11 | obj-$(CONFIG_IA64_HP_AML_NFW) += aml_nfw.o | ||
diff --git a/arch/ia64/hp/common/aml_nfw.c b/arch/ia64/hp/common/aml_nfw.c new file mode 100644 index 000000000000..4abd2c79bb1d --- /dev/null +++ b/arch/ia64/hp/common/aml_nfw.c | |||
@@ -0,0 +1,236 @@ | |||
1 | /* | ||
2 | * OpRegion handler to allow AML to call native firmware | ||
3 | * | ||
4 | * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. | ||
5 | * Bjorn Helgaas <bjorn.helgaas@hp.com> | ||
6 | * | ||
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. | ||
10 | * | ||
11 | * This driver implements HP Open Source Review Board proposal 1842, | ||
12 | * which was approved on 9/20/2006. | ||
13 | * | ||
14 | * For technical documentation, see the HP SPPA Firmware EAS, Appendix F. | ||
15 | * | ||
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 | ||
22 | * result. | ||
23 | */ | ||
24 | |||
25 | #include <linux/module.h> | ||
26 | #include <acpi/acpi_bus.h> | ||
27 | #include <acpi/acpi_drivers.h> | ||
28 | #include <asm/sal.h> | ||
29 | |||
30 | MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>"); | ||
31 | MODULE_LICENSE("GPL"); | ||
32 | MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls"); | ||
33 | |||
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"); | ||
37 | |||
38 | #define AML_NFW_SPACE 0xA1 | ||
39 | |||
40 | struct ia64_pdesc { | ||
41 | void *ip; | ||
42 | void *gp; | ||
43 | }; | ||
44 | |||
45 | /* | ||
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. | ||
48 | */ | ||
49 | struct ia64_nfw_context { | ||
50 | u64 arg[8]; | ||
51 | struct ia64_sal_retval ret; | ||
52 | u64 ip; | ||
53 | u64 gp; | ||
54 | u64 pad[2]; | ||
55 | }; | ||
56 | |||
57 | static void *virt_map(u64 address) | ||
58 | { | ||
59 | if (address & (1UL << 63)) | ||
60 | return (void *) (__IA64_UNCACHED_OFFSET | address); | ||
61 | |||
62 | return __va(address); | ||
63 | } | ||
64 | |||
65 | static void aml_nfw_execute(struct ia64_nfw_context *c) | ||
66 | { | ||
67 | struct ia64_pdesc virt_entry; | ||
68 | ia64_sal_handler entry; | ||
69 | |||
70 | virt_entry.ip = virt_map(c->ip); | ||
71 | virt_entry.gp = virt_map(c->gp); | ||
72 | |||
73 | entry = (ia64_sal_handler) &virt_entry; | ||
74 | |||
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]); | ||
78 | } | ||
79 | |||
80 | static void aml_nfw_read_arg(u8 *offset, u32 bit_width, acpi_integer *value) | ||
81 | { | ||
82 | switch (bit_width) { | ||
83 | case 8: | ||
84 | *value = *(u8 *)offset; | ||
85 | break; | ||
86 | case 16: | ||
87 | *value = *(u16 *)offset; | ||
88 | break; | ||
89 | case 32: | ||
90 | *value = *(u32 *)offset; | ||
91 | break; | ||
92 | case 64: | ||
93 | *value = *(u64 *)offset; | ||
94 | break; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | static void aml_nfw_write_arg(u8 *offset, u32 bit_width, acpi_integer *value) | ||
99 | { | ||
100 | switch (bit_width) { | ||
101 | case 8: | ||
102 | *(u8 *) offset = *value; | ||
103 | break; | ||
104 | case 16: | ||
105 | *(u16 *) offset = *value; | ||
106 | break; | ||
107 | case 32: | ||
108 | *(u32 *) offset = *value; | ||
109 | break; | ||
110 | case 64: | ||
111 | *(u64 *) offset = *value; | ||
112 | break; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address, | ||
117 | u32 bit_width, acpi_integer *value, void *handler_context, | ||
118 | void *region_context) | ||
119 | { | ||
120 | struct ia64_nfw_context *context = handler_context; | ||
121 | u8 *offset = (u8 *) context + address; | ||
122 | |||
123 | if (bit_width != 8 && bit_width != 16 && | ||
124 | bit_width != 32 && bit_width != 64) | ||
125 | return AE_BAD_PARAMETER; | ||
126 | |||
127 | if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context)) | ||
128 | return AE_BAD_PARAMETER; | ||
129 | |||
130 | switch (function) { | ||
131 | case ACPI_READ: | ||
132 | if (address == offsetof(struct ia64_nfw_context, ret)) | ||
133 | aml_nfw_execute(context); | ||
134 | aml_nfw_read_arg(offset, bit_width, value); | ||
135 | break; | ||
136 | case ACPI_WRITE: | ||
137 | aml_nfw_write_arg(offset, bit_width, value); | ||
138 | break; | ||
139 | } | ||
140 | |||
141 | return AE_OK; | ||
142 | } | ||
143 | |||
144 | static struct ia64_nfw_context global_context; | ||
145 | static int global_handler_registered; | ||
146 | |||
147 | static int aml_nfw_add_global_handler(void) | ||
148 | { | ||
149 | acpi_status status; | ||
150 | |||
151 | if (global_handler_registered) | ||
152 | return 0; | ||
153 | |||
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)) | ||
157 | return -ENODEV; | ||
158 | |||
159 | global_handler_registered = 1; | ||
160 | printk(KERN_INFO "Global 0x%02X opregion handler registered\n", | ||
161 | AML_NFW_SPACE); | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | static int aml_nfw_remove_global_handler(void) | ||
166 | { | ||
167 | acpi_status status; | ||
168 | |||
169 | if (!global_handler_registered) | ||
170 | return 0; | ||
171 | |||
172 | status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT, | ||
173 | AML_NFW_SPACE, aml_nfw_handler); | ||
174 | if (ACPI_FAILURE(status)) | ||
175 | return -ENODEV; | ||
176 | |||
177 | global_handler_registered = 0; | ||
178 | printk(KERN_INFO "Global 0x%02X opregion handler removed\n", | ||
179 | AML_NFW_SPACE); | ||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | static int aml_nfw_add(struct acpi_device *device) | ||
184 | { | ||
185 | /* | ||
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. | ||
190 | */ | ||
191 | return aml_nfw_add_global_handler(); | ||
192 | } | ||
193 | |||
194 | static int aml_nfw_remove(struct acpi_device *device, int type) | ||
195 | { | ||
196 | return aml_nfw_remove_global_handler(); | ||
197 | } | ||
198 | |||
199 | static const struct acpi_device_id aml_nfw_ids[] = { | ||
200 | {"HPQ5001", 0}, | ||
201 | {"", 0} | ||
202 | }; | ||
203 | |||
204 | static struct acpi_driver acpi_aml_nfw_driver = { | ||
205 | .name = "native firmware", | ||
206 | .ids = aml_nfw_ids, | ||
207 | .ops = { | ||
208 | .add = aml_nfw_add, | ||
209 | .remove = aml_nfw_remove, | ||
210 | }, | ||
211 | }; | ||
212 | |||
213 | static int __init aml_nfw_init(void) | ||
214 | { | ||
215 | int result; | ||
216 | |||
217 | if (force_register) | ||
218 | aml_nfw_add_global_handler(); | ||
219 | |||
220 | result = acpi_bus_register_driver(&acpi_aml_nfw_driver); | ||
221 | if (result < 0) { | ||
222 | aml_nfw_remove_global_handler(); | ||
223 | return result; | ||
224 | } | ||
225 | |||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | static void __exit aml_nfw_exit(void) | ||
230 | { | ||
231 | acpi_bus_unregister_driver(&acpi_aml_nfw_driver); | ||
232 | aml_nfw_remove_global_handler(); | ||
233 | } | ||
234 | |||
235 | module_init(aml_nfw_init); | ||
236 | module_exit(aml_nfw_exit); | ||