Loading...
1// SPDX-License-Identifier: GPL-2.0-only
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 *
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>
23#include <linux/acpi.h>
24#include <asm/sal.h>
25
26MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
27MODULE_LICENSE("GPL");
28MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
29
30static bool force_register;
31module_param_named(force, force_register, bool, 0);
32MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
33
34#define AML_NFW_SPACE 0xA1
35
36struct 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 */
45struct 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
53static 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
61static 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
76static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value)
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
94static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value)
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
112static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
113 u32 bit_width, u64 *value, void *handler_context,
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
140static struct ia64_nfw_context global_context;
141static int global_handler_registered;
142
143static 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
161static 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
179static 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
190static int aml_nfw_remove(struct acpi_device *device)
191{
192 return aml_nfw_remove_global_handler();
193}
194
195static const struct acpi_device_id aml_nfw_ids[] = {
196 {"HPQ5001", 0},
197 {"", 0}
198};
199
200static 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
209static 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
225static void __exit aml_nfw_exit(void)
226{
227 acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
228 aml_nfw_remove_global_handler();
229}
230
231module_init(aml_nfw_init);
232module_exit(aml_nfw_exit);
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, u64 *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, u64 *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, u64 *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);