]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
28eda5b8 BH |
2 | /* |
3 | * OpRegion handler to allow AML to call native firmware | |
4 | * | |
5 | * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. | |
6 | * Bjorn Helgaas <bjorn.helgaas@hp.com> | |
7 | * | |
28eda5b8 BH |
8 | * This driver implements HP Open Source Review Board proposal 1842, |
9 | * which was approved on 9/20/2006. | |
10 | * | |
11 | * For technical documentation, see the HP SPPA Firmware EAS, Appendix F. | |
12 | * | |
13 | * ACPI does not define a mechanism for AML methods to call native firmware | |
14 | * interfaces such as PAL or SAL. This OpRegion handler adds such a mechanism. | |
15 | * After the handler is installed, an AML method can call native firmware by | |
16 | * storing the arguments and firmware entry point to specific offsets in the | |
17 | * OpRegion. When AML reads the "return value" offset from the OpRegion, this | |
18 | * handler loads up the arguments, makes the firmware call, and returns the | |
19 | * result. | |
20 | */ | |
21 | ||
22 | #include <linux/module.h> | |
8b48463f | 23 | #include <linux/acpi.h> |
28eda5b8 BH |
24 | #include <asm/sal.h> |
25 | ||
26 | MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>"); | |
27 | MODULE_LICENSE("GPL"); | |
28 | MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls"); | |
29 | ||
476bc001 | 30 | static bool force_register; |
28eda5b8 BH |
31 | module_param_named(force, force_register, bool, 0); |
32 | MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device"); | |
33 | ||
34 | #define AML_NFW_SPACE 0xA1 | |
35 | ||
36 | struct ia64_pdesc { | |
37 | void *ip; | |
38 | void *gp; | |
39 | }; | |
40 | ||
41 | /* | |
42 | * N.B. The layout of this structure is defined in the HP SPPA FW EAS, and | |
43 | * the member offsets are embedded in AML methods. | |
44 | */ | |
45 | struct ia64_nfw_context { | |
46 | u64 arg[8]; | |
47 | struct ia64_sal_retval ret; | |
48 | u64 ip; | |
49 | u64 gp; | |
50 | u64 pad[2]; | |
51 | }; | |
52 | ||
53 | static void *virt_map(u64 address) | |
54 | { | |
55 | if (address & (1UL << 63)) | |
56 | return (void *) (__IA64_UNCACHED_OFFSET | address); | |
57 | ||
58 | return __va(address); | |
59 | } | |
60 | ||
61 | static void aml_nfw_execute(struct ia64_nfw_context *c) | |
62 | { | |
63 | struct ia64_pdesc virt_entry; | |
64 | ia64_sal_handler entry; | |
65 | ||
66 | virt_entry.ip = virt_map(c->ip); | |
67 | virt_entry.gp = virt_map(c->gp); | |
68 | ||
69 | entry = (ia64_sal_handler) &virt_entry; | |
70 | ||
71 | IA64_FW_CALL(entry, c->ret, | |
72 | c->arg[0], c->arg[1], c->arg[2], c->arg[3], | |
73 | c->arg[4], c->arg[5], c->arg[6], c->arg[7]); | |
74 | } | |
75 | ||
439913ff | 76 | static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value) |
28eda5b8 BH |
77 | { |
78 | switch (bit_width) { | |
79 | case 8: | |
80 | *value = *(u8 *)offset; | |
81 | break; | |
82 | case 16: | |
83 | *value = *(u16 *)offset; | |
84 | break; | |
85 | case 32: | |
86 | *value = *(u32 *)offset; | |
87 | break; | |
88 | case 64: | |
89 | *value = *(u64 *)offset; | |
90 | break; | |
91 | } | |
92 | } | |
93 | ||
439913ff | 94 | static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value) |
28eda5b8 BH |
95 | { |
96 | switch (bit_width) { | |
97 | case 8: | |
98 | *(u8 *) offset = *value; | |
99 | break; | |
100 | case 16: | |
101 | *(u16 *) offset = *value; | |
102 | break; | |
103 | case 32: | |
104 | *(u32 *) offset = *value; | |
105 | break; | |
106 | case 64: | |
107 | *(u64 *) offset = *value; | |
108 | break; | |
109 | } | |
110 | } | |
111 | ||
112 | static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address, | |
439913ff | 113 | u32 bit_width, u64 *value, void *handler_context, |
28eda5b8 BH |
114 | void *region_context) |
115 | { | |
116 | struct ia64_nfw_context *context = handler_context; | |
117 | u8 *offset = (u8 *) context + address; | |
118 | ||
119 | if (bit_width != 8 && bit_width != 16 && | |
120 | bit_width != 32 && bit_width != 64) | |
121 | return AE_BAD_PARAMETER; | |
122 | ||
123 | if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context)) | |
124 | return AE_BAD_PARAMETER; | |
125 | ||
126 | switch (function) { | |
127 | case ACPI_READ: | |
128 | if (address == offsetof(struct ia64_nfw_context, ret)) | |
129 | aml_nfw_execute(context); | |
130 | aml_nfw_read_arg(offset, bit_width, value); | |
131 | break; | |
132 | case ACPI_WRITE: | |
133 | aml_nfw_write_arg(offset, bit_width, value); | |
134 | break; | |
135 | } | |
136 | ||
137 | return AE_OK; | |
138 | } | |
139 | ||
140 | static struct ia64_nfw_context global_context; | |
141 | static int global_handler_registered; | |
142 | ||
143 | static int aml_nfw_add_global_handler(void) | |
144 | { | |
145 | acpi_status status; | |
146 | ||
147 | if (global_handler_registered) | |
148 | return 0; | |
149 | ||
150 | status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT, | |
151 | AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context); | |
152 | if (ACPI_FAILURE(status)) | |
153 | return -ENODEV; | |
154 | ||
155 | global_handler_registered = 1; | |
156 | printk(KERN_INFO "Global 0x%02X opregion handler registered\n", | |
157 | AML_NFW_SPACE); | |
158 | return 0; | |
159 | } | |
160 | ||
161 | static int aml_nfw_remove_global_handler(void) | |
162 | { | |
163 | acpi_status status; | |
164 | ||
165 | if (!global_handler_registered) | |
166 | return 0; | |
167 | ||
168 | status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT, | |
169 | AML_NFW_SPACE, aml_nfw_handler); | |
170 | if (ACPI_FAILURE(status)) | |
171 | return -ENODEV; | |
172 | ||
173 | global_handler_registered = 0; | |
174 | printk(KERN_INFO "Global 0x%02X opregion handler removed\n", | |
175 | AML_NFW_SPACE); | |
176 | return 0; | |
177 | } | |
178 | ||
179 | static int aml_nfw_add(struct acpi_device *device) | |
180 | { | |
181 | /* | |
182 | * We would normally allocate a new context structure and install | |
183 | * the address space handler for the specific device we found. | |
184 | * But the HP-UX implementation shares a single global context | |
185 | * and always puts the handler at the root, so we'll do the same. | |
186 | */ | |
187 | return aml_nfw_add_global_handler(); | |
188 | } | |
189 | ||
51fac838 | 190 | static int aml_nfw_remove(struct acpi_device *device) |
28eda5b8 BH |
191 | { |
192 | return aml_nfw_remove_global_handler(); | |
193 | } | |
194 | ||
195 | static const struct acpi_device_id aml_nfw_ids[] = { | |
196 | {"HPQ5001", 0}, | |
197 | {"", 0} | |
198 | }; | |
199 | ||
200 | static struct acpi_driver acpi_aml_nfw_driver = { | |
201 | .name = "native firmware", | |
202 | .ids = aml_nfw_ids, | |
203 | .ops = { | |
204 | .add = aml_nfw_add, | |
205 | .remove = aml_nfw_remove, | |
206 | }, | |
207 | }; | |
208 | ||
209 | static int __init aml_nfw_init(void) | |
210 | { | |
211 | int result; | |
212 | ||
213 | if (force_register) | |
214 | aml_nfw_add_global_handler(); | |
215 | ||
216 | result = acpi_bus_register_driver(&acpi_aml_nfw_driver); | |
217 | if (result < 0) { | |
218 | aml_nfw_remove_global_handler(); | |
219 | return result; | |
220 | } | |
221 | ||
222 | return 0; | |
223 | } | |
224 | ||
225 | static void __exit aml_nfw_exit(void) | |
226 | { | |
227 | acpi_bus_unregister_driver(&acpi_aml_nfw_driver); | |
228 | aml_nfw_remove_global_handler(); | |
229 | } | |
230 | ||
231 | module_init(aml_nfw_init); | |
232 | module_exit(aml_nfw_exit); |