1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25#include <linux/module.h>
26#include <linux/acpi.h>
27#include <asm/sal.h>
28
29MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
30MODULE_LICENSE("GPL");
31MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
32
33static bool force_register;
34module_param_named(force, force_register, bool, 0);
35MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
36
37#define AML_NFW_SPACE 0xA1
38
39struct ia64_pdesc {
40 void *ip;
41 void *gp;
42};
43
44
45
46
47
48struct 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
56static 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
64static 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
79static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value)
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
97static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value)
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
115static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
116 u32 bit_width, u64 *value, void *handler_context,
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
143static struct ia64_nfw_context global_context;
144static int global_handler_registered;
145
146static 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
164static 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
182static int aml_nfw_add(struct acpi_device *device)
183{
184
185
186
187
188
189
190 return aml_nfw_add_global_handler();
191}
192
193static int aml_nfw_remove(struct acpi_device *device)
194{
195 return aml_nfw_remove_global_handler();
196}
197
198static const struct acpi_device_id aml_nfw_ids[] = {
199 {"HPQ5001", 0},
200 {"", 0}
201};
202
203static 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
212static 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
228static void __exit aml_nfw_exit(void)
229{
230 acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
231 aml_nfw_remove_global_handler();
232}
233
234module_init(aml_nfw_init);
235module_exit(aml_nfw_exit);
236