xref: /openbmc/linux/arch/ia64/hp/common/aml_nfw.c (revision 74be2d3b)
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);
233