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 29 MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>"); 30 MODULE_LICENSE("GPL"); 31 MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls"); 32 33 static bool force_register; 34 module_param_named(force, force_register, bool, 0); 35 MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device"); 36 37 #define AML_NFW_SPACE 0xA1 38 39 struct 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 */ 48 struct 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 56 static 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 64 static 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 79 static 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 97 static 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 115 static 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 143 static struct ia64_nfw_context global_context; 144 static int global_handler_registered; 145 146 static 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 164 static 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 182 static 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 193 static int aml_nfw_remove(struct acpi_device *device) 194 { 195 return aml_nfw_remove_global_handler(); 196 } 197 198 static const struct acpi_device_id aml_nfw_ids[] = { 199 {"HPQ5001", 0}, 200 {"", 0} 201 }; 202 203 static 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 212 static 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 228 static void __exit aml_nfw_exit(void) 229 { 230 acpi_bus_unregister_driver(&acpi_aml_nfw_driver); 231 aml_nfw_remove_global_handler(); 232 } 233 234 module_init(aml_nfw_init); 235 module_exit(aml_nfw_exit); 236