mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
5.3 KiB
232 lines
5.3 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* OpRegion handler to allow AML to call native firmware |
|
* |
|
* (c) Copyright 2007 Hewlett-Packard Development Company, L.P. |
|
* Bjorn Helgaas <[email protected]> |
|
* |
|
* This driver implements HP Open Source Review Board proposal 1842, |
|
* which was approved on 9/20/2006. |
|
* |
|
* For technical documentation, see the HP SPPA Firmware EAS, Appendix F. |
|
* |
|
* ACPI does not define a mechanism for AML methods to call native firmware |
|
* interfaces such as PAL or SAL. This OpRegion handler adds such a mechanism. |
|
* After the handler is installed, an AML method can call native firmware by |
|
* storing the arguments and firmware entry point to specific offsets in the |
|
* OpRegion. When AML reads the "return value" offset from the OpRegion, this |
|
* handler loads up the arguments, makes the firmware call, and returns the |
|
* result. |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/acpi.h> |
|
#include <asm/sal.h> |
|
|
|
MODULE_AUTHOR("Bjorn Helgaas <[email protected]>"); |
|
MODULE_LICENSE("GPL"); |
|
MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls"); |
|
|
|
static bool force_register; |
|
module_param_named(force, force_register, bool, 0); |
|
MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device"); |
|
|
|
#define AML_NFW_SPACE 0xA1 |
|
|
|
struct ia64_pdesc { |
|
void *ip; |
|
void *gp; |
|
}; |
|
|
|
/* |
|
* N.B. The layout of this structure is defined in the HP SPPA FW EAS, and |
|
* the member offsets are embedded in AML methods. |
|
*/ |
|
struct ia64_nfw_context { |
|
u64 arg[8]; |
|
struct ia64_sal_retval ret; |
|
u64 ip; |
|
u64 gp; |
|
u64 pad[2]; |
|
}; |
|
|
|
static void *virt_map(u64 address) |
|
{ |
|
if (address & (1UL << 63)) |
|
return (void *) (__IA64_UNCACHED_OFFSET | address); |
|
|
|
return __va(address); |
|
} |
|
|
|
static void aml_nfw_execute(struct ia64_nfw_context *c) |
|
{ |
|
struct ia64_pdesc virt_entry; |
|
ia64_sal_handler entry; |
|
|
|
virt_entry.ip = virt_map(c->ip); |
|
virt_entry.gp = virt_map(c->gp); |
|
|
|
entry = (ia64_sal_handler) &virt_entry; |
|
|
|
IA64_FW_CALL(entry, c->ret, |
|
c->arg[0], c->arg[1], c->arg[2], c->arg[3], |
|
c->arg[4], c->arg[5], c->arg[6], c->arg[7]); |
|
} |
|
|
|
static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value) |
|
{ |
|
switch (bit_width) { |
|
case 8: |
|
*value = *(u8 *)offset; |
|
break; |
|
case 16: |
|
*value = *(u16 *)offset; |
|
break; |
|
case 32: |
|
*value = *(u32 *)offset; |
|
break; |
|
case 64: |
|
*value = *(u64 *)offset; |
|
break; |
|
} |
|
} |
|
|
|
static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value) |
|
{ |
|
switch (bit_width) { |
|
case 8: |
|
*(u8 *) offset = *value; |
|
break; |
|
case 16: |
|
*(u16 *) offset = *value; |
|
break; |
|
case 32: |
|
*(u32 *) offset = *value; |
|
break; |
|
case 64: |
|
*(u64 *) offset = *value; |
|
break; |
|
} |
|
} |
|
|
|
static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address, |
|
u32 bit_width, u64 *value, void *handler_context, |
|
void *region_context) |
|
{ |
|
struct ia64_nfw_context *context = handler_context; |
|
u8 *offset = (u8 *) context + address; |
|
|
|
if (bit_width != 8 && bit_width != 16 && |
|
bit_width != 32 && bit_width != 64) |
|
return AE_BAD_PARAMETER; |
|
|
|
if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context)) |
|
return AE_BAD_PARAMETER; |
|
|
|
switch (function) { |
|
case ACPI_READ: |
|
if (address == offsetof(struct ia64_nfw_context, ret)) |
|
aml_nfw_execute(context); |
|
aml_nfw_read_arg(offset, bit_width, value); |
|
break; |
|
case ACPI_WRITE: |
|
aml_nfw_write_arg(offset, bit_width, value); |
|
break; |
|
} |
|
|
|
return AE_OK; |
|
} |
|
|
|
static struct ia64_nfw_context global_context; |
|
static int global_handler_registered; |
|
|
|
static int aml_nfw_add_global_handler(void) |
|
{ |
|
acpi_status status; |
|
|
|
if (global_handler_registered) |
|
return 0; |
|
|
|
status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT, |
|
AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context); |
|
if (ACPI_FAILURE(status)) |
|
return -ENODEV; |
|
|
|
global_handler_registered = 1; |
|
printk(KERN_INFO "Global 0x%02X opregion handler registered\n", |
|
AML_NFW_SPACE); |
|
return 0; |
|
} |
|
|
|
static int aml_nfw_remove_global_handler(void) |
|
{ |
|
acpi_status status; |
|
|
|
if (!global_handler_registered) |
|
return 0; |
|
|
|
status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT, |
|
AML_NFW_SPACE, aml_nfw_handler); |
|
if (ACPI_FAILURE(status)) |
|
return -ENODEV; |
|
|
|
global_handler_registered = 0; |
|
printk(KERN_INFO "Global 0x%02X opregion handler removed\n", |
|
AML_NFW_SPACE); |
|
return 0; |
|
} |
|
|
|
static int aml_nfw_add(struct acpi_device *device) |
|
{ |
|
/* |
|
* We would normally allocate a new context structure and install |
|
* the address space handler for the specific device we found. |
|
* But the HP-UX implementation shares a single global context |
|
* and always puts the handler at the root, so we'll do the same. |
|
*/ |
|
return aml_nfw_add_global_handler(); |
|
} |
|
|
|
static int aml_nfw_remove(struct acpi_device *device) |
|
{ |
|
return aml_nfw_remove_global_handler(); |
|
} |
|
|
|
static const struct acpi_device_id aml_nfw_ids[] = { |
|
{"HPQ5001", 0}, |
|
{"", 0} |
|
}; |
|
|
|
static struct acpi_driver acpi_aml_nfw_driver = { |
|
.name = "native firmware", |
|
.ids = aml_nfw_ids, |
|
.ops = { |
|
.add = aml_nfw_add, |
|
.remove = aml_nfw_remove, |
|
}, |
|
}; |
|
|
|
static int __init aml_nfw_init(void) |
|
{ |
|
int result; |
|
|
|
if (force_register) |
|
aml_nfw_add_global_handler(); |
|
|
|
result = acpi_bus_register_driver(&acpi_aml_nfw_driver); |
|
if (result < 0) { |
|
aml_nfw_remove_global_handler(); |
|
return result; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void __exit aml_nfw_exit(void) |
|
{ |
|
acpi_bus_unregister_driver(&acpi_aml_nfw_driver); |
|
aml_nfw_remove_global_handler(); |
|
} |
|
|
|
module_init(aml_nfw_init); |
|
module_exit(aml_nfw_exit);
|
|
|