root/arch/ia64/hp/common/aml_nfw.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. virt_map
  2. aml_nfw_execute
  3. aml_nfw_read_arg
  4. aml_nfw_write_arg
  5. aml_nfw_handler
  6. aml_nfw_add_global_handler
  7. aml_nfw_remove_global_handler
  8. aml_nfw_add
  9. aml_nfw_remove
  10. aml_nfw_init
  11. aml_nfw_exit

   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);

/* [<][>][^][v][top][bottom][index][help] */