Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/arch/ia64/hp/common/aml_nfw.c
15126 views
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 <[email protected]>
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
30
MODULE_AUTHOR("Bjorn Helgaas <[email protected]>");
31
MODULE_LICENSE("GPL");
32
MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
33
34
static int force_register;
35
module_param_named(force, force_register, bool, 0);
36
MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
37
38
#define AML_NFW_SPACE 0xA1
39
40
struct 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
*/
49
struct 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
57
static 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
65
static 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
80
static 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
98
static 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
116
static 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
144
static struct ia64_nfw_context global_context;
145
static int global_handler_registered;
146
147
static 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
165
static 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
183
static 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
194
static int aml_nfw_remove(struct acpi_device *device, int type)
195
{
196
return aml_nfw_remove_global_handler();
197
}
198
199
static const struct acpi_device_id aml_nfw_ids[] = {
200
{"HPQ5001", 0},
201
{"", 0}
202
};
203
204
static 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
213
static 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
229
static void __exit aml_nfw_exit(void)
230
{
231
acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
232
aml_nfw_remove_global_handler();
233
}
234
235
module_init(aml_nfw_init);
236
module_exit(aml_nfw_exit);
237
238