aboutsummaryrefslogtreecommitdiffstats
path: root/arch/ia64/hp/common
diff options
context:
space:
mode:
authorBjorn Helgaas <bjorn.helgaas@hp.com>2007-09-20 16:22:03 -0400
committerTony Luck <tony.luck@intel.com>2007-10-12 18:11:43 -0400
commit28eda5b8b434b65e5feb0c58a7477f3f290d72d4 (patch)
tree1da72e320f15acf1649cef957de46360b780285d /arch/ia64/hp/common
parentedbe7075d144b35e8232ca32f373c1e0c26ff085 (diff)
[IA64] add driver for ACPI methods to call native firmware
This driver for HPQ5001 devices installs a global ACPI OpRegion handler. AML methods can use this OpRegion to call native firmware entry points. 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. Signed-off-by: Bjorn Helgaas <bjorn.helgaas@hp.com> Signed-off-by: Len Brown <len.brown@intel.com> Signed-off-by: Tony Luck <tony.luck@intel.com>
Diffstat (limited to 'arch/ia64/hp/common')
-rw-r--r--arch/ia64/hp/common/Makefile1
-rw-r--r--arch/ia64/hp/common/aml_nfw.c236
2 files changed, 237 insertions, 0 deletions
diff --git a/arch/ia64/hp/common/Makefile b/arch/ia64/hp/common/Makefile
index f61a60057ff..9e179dd06b8 100644
--- a/arch/ia64/hp/common/Makefile
+++ b/arch/ia64/hp/common/Makefile
@@ -8,3 +8,4 @@
8obj-y := sba_iommu.o 8obj-y := sba_iommu.o
9obj-$(CONFIG_IA64_HP_ZX1_SWIOTLB) += hwsw_iommu.o 9obj-$(CONFIG_IA64_HP_ZX1_SWIOTLB) += hwsw_iommu.o
10obj-$(CONFIG_IA64_GENERIC) += hwsw_iommu.o 10obj-$(CONFIG_IA64_GENERIC) += hwsw_iommu.o
11obj-$(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 00000000000..4abd2c79bb1
--- /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
30MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
31MODULE_LICENSE("GPL");
32MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
33
34static int force_register;
35module_param_named(force, force_register, bool, 0);
36MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
37
38#define AML_NFW_SPACE 0xA1
39
40struct 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 */
49struct 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
57static 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
65static 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
80static 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
98static 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
116static 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
144static struct ia64_nfw_context global_context;
145static int global_handler_registered;
146
147static 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
165static 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
183static 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
194static int aml_nfw_remove(struct acpi_device *device, int type)
195{
196 return aml_nfw_remove_global_handler();
197}
198
199static const struct acpi_device_id aml_nfw_ids[] = {
200 {"HPQ5001", 0},
201 {"", 0}
202};
203
204static 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
213static 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
229static void __exit aml_nfw_exit(void)
230{
231 acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
232 aml_nfw_remove_global_handler();
233}
234
235module_init(aml_nfw_init);
236module_exit(aml_nfw_exit);