Commit | Line | Data |
---|---|---|
28eda5b8 BH |
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); |