GNU Linux-libre 5.19-rc6-gnu
[releases.git] / arch / ia64 / hp / common / aml_nfw.c
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
26 MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
27 MODULE_LICENSE("GPL");
28 MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
29
30 static bool force_register;
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
76 static 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
94 static 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
112 static 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
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
190 static int aml_nfw_remove(struct acpi_device *device)
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);