]>
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> | |
8b48463f | 26 | #include <linux/acpi.h> |
28eda5b8 BH |
27 | #include <asm/sal.h> |
28 | ||
29 | MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>"); | |
30 | MODULE_LICENSE("GPL"); | |
31 | MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls"); | |
32 | ||
476bc001 | 33 | static bool force_register; |
28eda5b8 BH |
34 | module_param_named(force, force_register, bool, 0); |
35 | MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device"); | |
36 | ||
37 | #define AML_NFW_SPACE 0xA1 | |
38 | ||
39 | struct ia64_pdesc { | |
40 | void *ip; | |
41 | void *gp; | |
42 | }; | |
43 | ||
44 | /* | |
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. | |
47 | */ | |
48 | struct ia64_nfw_context { | |
49 | u64 arg[8]; | |
50 | struct ia64_sal_retval ret; | |
51 | u64 ip; | |
52 | u64 gp; | |
53 | u64 pad[2]; | |
54 | }; | |
55 | ||
56 | static void *virt_map(u64 address) | |
57 | { | |
58 | if (address & (1UL << 63)) | |
59 | return (void *) (__IA64_UNCACHED_OFFSET | address); | |
60 | ||
61 | return __va(address); | |
62 | } | |
63 | ||
64 | static void aml_nfw_execute(struct ia64_nfw_context *c) | |
65 | { | |
66 | struct ia64_pdesc virt_entry; | |
67 | ia64_sal_handler entry; | |
68 | ||
69 | virt_entry.ip = virt_map(c->ip); | |
70 | virt_entry.gp = virt_map(c->gp); | |
71 | ||
72 | entry = (ia64_sal_handler) &virt_entry; | |
73 | ||
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]); | |
77 | } | |
78 | ||
439913ff | 79 | static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value) |
28eda5b8 BH |
80 | { |
81 | switch (bit_width) { | |
82 | case 8: | |
83 | *value = *(u8 *)offset; | |
84 | break; | |
85 | case 16: | |
86 | *value = *(u16 *)offset; | |
87 | break; | |
88 | case 32: | |
89 | *value = *(u32 *)offset; | |
90 | break; | |
91 | case 64: | |
92 | *value = *(u64 *)offset; | |
93 | break; | |
94 | } | |
95 | } | |
96 | ||
439913ff | 97 | static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value) |
28eda5b8 BH |
98 | { |
99 | switch (bit_width) { | |
100 | case 8: | |
101 | *(u8 *) offset = *value; | |
102 | break; | |
103 | case 16: | |
104 | *(u16 *) offset = *value; | |
105 | break; | |
106 | case 32: | |
107 | *(u32 *) offset = *value; | |
108 | break; | |
109 | case 64: | |
110 | *(u64 *) offset = *value; | |
111 | break; | |
112 | } | |
113 | } | |
114 | ||
115 | static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address, | |
439913ff | 116 | u32 bit_width, u64 *value, void *handler_context, |
28eda5b8 BH |
117 | void *region_context) |
118 | { | |
119 | struct ia64_nfw_context *context = handler_context; | |
120 | u8 *offset = (u8 *) context + address; | |
121 | ||
122 | if (bit_width != 8 && bit_width != 16 && | |
123 | bit_width != 32 && bit_width != 64) | |
124 | return AE_BAD_PARAMETER; | |
125 | ||
126 | if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context)) | |
127 | return AE_BAD_PARAMETER; | |
128 | ||
129 | switch (function) { | |
130 | case ACPI_READ: | |
131 | if (address == offsetof(struct ia64_nfw_context, ret)) | |
132 | aml_nfw_execute(context); | |
133 | aml_nfw_read_arg(offset, bit_width, value); | |
134 | break; | |
135 | case ACPI_WRITE: | |
136 | aml_nfw_write_arg(offset, bit_width, value); | |
137 | break; | |
138 | } | |
139 | ||
140 | return AE_OK; | |
141 | } | |
142 | ||
143 | static struct ia64_nfw_context global_context; | |
144 | static int global_handler_registered; | |
145 | ||
146 | static int aml_nfw_add_global_handler(void) | |
147 | { | |
148 | acpi_status status; | |
149 | ||
150 | if (global_handler_registered) | |
151 | return 0; | |
152 | ||
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)) | |
156 | return -ENODEV; | |
157 | ||
158 | global_handler_registered = 1; | |
159 | printk(KERN_INFO "Global 0x%02X opregion handler registered\n", | |
160 | AML_NFW_SPACE); | |
161 | return 0; | |
162 | } | |
163 | ||
164 | static int aml_nfw_remove_global_handler(void) | |
165 | { | |
166 | acpi_status status; | |
167 | ||
168 | if (!global_handler_registered) | |
169 | return 0; | |
170 | ||
171 | status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT, | |
172 | AML_NFW_SPACE, aml_nfw_handler); | |
173 | if (ACPI_FAILURE(status)) | |
174 | return -ENODEV; | |
175 | ||
176 | global_handler_registered = 0; | |
177 | printk(KERN_INFO "Global 0x%02X opregion handler removed\n", | |
178 | AML_NFW_SPACE); | |
179 | return 0; | |
180 | } | |
181 | ||
182 | static int aml_nfw_add(struct acpi_device *device) | |
183 | { | |
184 | /* | |
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. | |
189 | */ | |
190 | return aml_nfw_add_global_handler(); | |
191 | } | |
192 | ||
51fac838 | 193 | static int aml_nfw_remove(struct acpi_device *device) |
28eda5b8 BH |
194 | { |
195 | return aml_nfw_remove_global_handler(); | |
196 | } | |
197 | ||
198 | static const struct acpi_device_id aml_nfw_ids[] = { | |
199 | {"HPQ5001", 0}, | |
200 | {"", 0} | |
201 | }; | |
202 | ||
203 | static struct acpi_driver acpi_aml_nfw_driver = { | |
204 | .name = "native firmware", | |
205 | .ids = aml_nfw_ids, | |
206 | .ops = { | |
207 | .add = aml_nfw_add, | |
208 | .remove = aml_nfw_remove, | |
209 | }, | |
210 | }; | |
211 | ||
212 | static int __init aml_nfw_init(void) | |
213 | { | |
214 | int result; | |
215 | ||
216 | if (force_register) | |
217 | aml_nfw_add_global_handler(); | |
218 | ||
219 | result = acpi_bus_register_driver(&acpi_aml_nfw_driver); | |
220 | if (result < 0) { | |
221 | aml_nfw_remove_global_handler(); | |
222 | return result; | |
223 | } | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | static void __exit aml_nfw_exit(void) | |
229 | { | |
230 | acpi_bus_unregister_driver(&acpi_aml_nfw_driver); | |
231 | aml_nfw_remove_global_handler(); | |
232 | } | |
233 | ||
234 | module_init(aml_nfw_init); | |
235 | module_exit(aml_nfw_exit); |