// SPDX-License-Identifier: GPL-2.0-only #include #include #include /* * 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(sizeof(*ffh_ctxt), GFP_KERNEL); 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; }