Linux Audio

Check our new training course

Loading...
v4.6
 
  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 <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 * N.B.  The layout of this structure is defined in the HP SPPA FW EAS, and
 46 *	 the member offsets are embedded in AML methods.
 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	 * We would normally allocate a new context structure and install
186	 * the address space handler for the specific device we found.
187	 * But the HP-UX implementation shares a single global context
188	 * and always puts the handler at the root, so we'll do the same.
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);
v6.2
  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 void aml_nfw_remove(struct acpi_device *device)
191{
192	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);