| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/acpi.h> | |
| #include <linux/arm-smccc.h> | |
| #include <linux/slab.h> | |
| /* | |
| * Implements ARM64 specific callbacks to support ACPI FFH Operation Region as | |
| * specified in https://developer.arm.com/docs/den0048/latest | |
| */ | |
| struct acpi_ffh_data { | |
| struct acpi_ffh_info info; | |
| void (*invoke_ffh_fn)(unsigned long a0, unsigned long a1, | |
| unsigned long a2, unsigned long a3, | |
| unsigned long a4, unsigned long a5, | |
| unsigned long a6, unsigned long a7, | |
| struct arm_smccc_res *args, | |
| struct arm_smccc_quirk *res); | |
| void (*invoke_ffh64_fn)(const struct arm_smccc_1_2_regs *args, | |
| struct arm_smccc_1_2_regs *res); | |
| }; | |
| int acpi_ffh_address_space_arch_setup(void *handler_ctxt, void **region_ctxt) | |
| { | |
| enum arm_smccc_conduit conduit; | |
| struct acpi_ffh_data *ffh_ctxt; | |
| if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2) | |
| return -EOPNOTSUPP; | |
| conduit = arm_smccc_1_1_get_conduit(); | |
| if (conduit == SMCCC_CONDUIT_NONE) { | |
| pr_err("%s: invalid SMCCC conduit\n", __func__); | |
| return -EOPNOTSUPP; | |
| } | |
| ffh_ctxt = kzalloc_obj(*ffh_ctxt); | |
| if (!ffh_ctxt) | |
| return -ENOMEM; | |
| if (conduit == SMCCC_CONDUIT_SMC) { | |
| ffh_ctxt->invoke_ffh_fn = __arm_smccc_smc; | |
| ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_smc; | |
| } else { | |
| ffh_ctxt->invoke_ffh_fn = __arm_smccc_hvc; | |
| ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_hvc; | |
| } | |
| memcpy(ffh_ctxt, handler_ctxt, sizeof(ffh_ctxt->info)); | |
| *region_ctxt = ffh_ctxt; | |
| return AE_OK; | |
| } | |
| static bool acpi_ffh_smccc_owner_allowed(u32 fid) | |
| { | |
| int owner = ARM_SMCCC_OWNER_NUM(fid); | |
| if (owner == ARM_SMCCC_OWNER_STANDARD || | |
| owner == ARM_SMCCC_OWNER_SIP || owner == ARM_SMCCC_OWNER_OEM) | |
| return true; | |
| return false; | |
| } | |
| int acpi_ffh_address_space_arch_handler(acpi_integer *value, void *region_context) | |
| { | |
| int ret = 0; | |
| struct acpi_ffh_data *ffh_ctxt = region_context; | |
| if (ffh_ctxt->info.offset == 0) { | |
| /* SMC/HVC 32bit call */ | |
| struct arm_smccc_res res; | |
| u32 a[8] = { 0 }, *ptr = (u32 *)value; | |
| if (!ARM_SMCCC_IS_FAST_CALL(*ptr) || ARM_SMCCC_IS_64(*ptr) || | |
| !acpi_ffh_smccc_owner_allowed(*ptr) || | |
| ffh_ctxt->info.length > 32) { | |
| ret = AE_ERROR; | |
| } else { | |
| int idx, len = ffh_ctxt->info.length >> 2; | |
| for (idx = 0; idx < len; idx++) | |
| a[idx] = *(ptr + idx); | |
| ffh_ctxt->invoke_ffh_fn(a[0], a[1], a[2], a[3], a[4], | |
| a[5], a[6], a[7], &res, NULL); | |
| memcpy(value, &res, sizeof(res)); | |
| } | |
| } else if (ffh_ctxt->info.offset == 1) { | |
| /* SMC/HVC 64bit call */ | |
| struct arm_smccc_1_2_regs *r = (struct arm_smccc_1_2_regs *)value; | |
| if (!ARM_SMCCC_IS_FAST_CALL(r->a0) || !ARM_SMCCC_IS_64(r->a0) || | |
| !acpi_ffh_smccc_owner_allowed(r->a0) || | |
| ffh_ctxt->info.length > sizeof(*r)) { | |
| ret = AE_ERROR; | |
| } else { | |
| ffh_ctxt->invoke_ffh64_fn(r, r); | |
| memcpy(value, r, ffh_ctxt->info.length); | |
| } | |
| } else { | |
| ret = AE_ERROR; | |
| } | |
| return ret; | |
| } |