1 // SPDX-License-Identifier: GPL-2.0-only 2 #include <linux/acpi.h> 3 #include <linux/arm-smccc.h> 4 #include <linux/slab.h> 5 6 /* 7 * Implements ARM64 specific callbacks to support ACPI FFH Operation Region as 8 * specified in https://developer.arm.com/docs/den0048/latest 9 */ 10 struct acpi_ffh_data { 11 struct acpi_ffh_info info; 12 void (*invoke_ffh_fn)(unsigned long a0, unsigned long a1, 13 unsigned long a2, unsigned long a3, 14 unsigned long a4, unsigned long a5, 15 unsigned long a6, unsigned long a7, 16 struct arm_smccc_res *args, 17 struct arm_smccc_quirk *res); 18 void (*invoke_ffh64_fn)(const struct arm_smccc_1_2_regs *args, 19 struct arm_smccc_1_2_regs *res); 20 }; 21 22 int acpi_ffh_address_space_arch_setup(void *handler_ctxt, void **region_ctxt) 23 { 24 enum arm_smccc_conduit conduit; 25 struct acpi_ffh_data *ffh_ctxt; 26 27 if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2) 28 return -EOPNOTSUPP; 29 30 conduit = arm_smccc_1_1_get_conduit(); 31 if (conduit == SMCCC_CONDUIT_NONE) { 32 pr_err("%s: invalid SMCCC conduit\n", __func__); 33 return -EOPNOTSUPP; 34 } 35 36 ffh_ctxt = kzalloc(sizeof(*ffh_ctxt), GFP_KERNEL); 37 if (!ffh_ctxt) 38 return -ENOMEM; 39 40 if (conduit == SMCCC_CONDUIT_SMC) { 41 ffh_ctxt->invoke_ffh_fn = __arm_smccc_smc; 42 ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_smc; 43 } else { 44 ffh_ctxt->invoke_ffh_fn = __arm_smccc_hvc; 45 ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_hvc; 46 } 47 48 memcpy(ffh_ctxt, handler_ctxt, sizeof(ffh_ctxt->info)); 49 50 *region_ctxt = ffh_ctxt; 51 return AE_OK; 52 } 53 54 static bool acpi_ffh_smccc_owner_allowed(u32 fid) 55 { 56 int owner = ARM_SMCCC_OWNER_NUM(fid); 57 58 if (owner == ARM_SMCCC_OWNER_STANDARD || 59 owner == ARM_SMCCC_OWNER_SIP || owner == ARM_SMCCC_OWNER_OEM) 60 return true; 61 62 return false; 63 } 64 65 int acpi_ffh_address_space_arch_handler(acpi_integer *value, void *region_context) 66 { 67 int ret = 0; 68 struct acpi_ffh_data *ffh_ctxt = region_context; 69 70 if (ffh_ctxt->info.offset == 0) { 71 /* SMC/HVC 32bit call */ 72 struct arm_smccc_res res; 73 u32 a[8] = { 0 }, *ptr = (u32 *)value; 74 75 if (!ARM_SMCCC_IS_FAST_CALL(*ptr) || ARM_SMCCC_IS_64(*ptr) || 76 !acpi_ffh_smccc_owner_allowed(*ptr) || 77 ffh_ctxt->info.length > 32) { 78 ret = AE_ERROR; 79 } else { 80 int idx, len = ffh_ctxt->info.length >> 2; 81 82 for (idx = 0; idx < len; idx++) 83 a[idx] = *(ptr + idx); 84 85 ffh_ctxt->invoke_ffh_fn(a[0], a[1], a[2], a[3], a[4], 86 a[5], a[6], a[7], &res, NULL); 87 memcpy(value, &res, sizeof(res)); 88 } 89 90 } else if (ffh_ctxt->info.offset == 1) { 91 /* SMC/HVC 64bit call */ 92 struct arm_smccc_1_2_regs *r = (struct arm_smccc_1_2_regs *)value; 93 94 if (!ARM_SMCCC_IS_FAST_CALL(r->a0) || !ARM_SMCCC_IS_64(r->a0) || 95 !acpi_ffh_smccc_owner_allowed(r->a0) || 96 ffh_ctxt->info.length > sizeof(*r)) { 97 ret = AE_ERROR; 98 } else { 99 ffh_ctxt->invoke_ffh64_fn(r, r); 100 memcpy(value, r, ffh_ctxt->info.length); 101 } 102 } else { 103 ret = AE_ERROR; 104 } 105 106 return ret; 107 } 108