Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: Add architecture specific ACPI FFH Opregion callbacks

FFH Operation Region space can be used to trigger SMC or HVC calls,
using the Arm SMC Calling Convention (SMCCC). The choice of conduit
(SMC or HVC) is based on what the kernel choose based on PSCI as with
any other users of SMCCC within the kernel.

Function identifiers only in the SMCCC SiP Service, OEM Service and FF-A
specific call range are allowed in FFH Opregions.

Offset can be either 0(32 bit calling convention) or 1(64 bit calling
convention). The length must be set with the range applicable based
on the value of the offset.

Acked-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Sudeep Holla and committed by
Rafael J. Wysocki
1d280ce0 e81c782c

+106
+106
arch/arm64/kernel/acpi.c
··· 13 13 #define pr_fmt(fmt) "ACPI: " fmt 14 14 15 15 #include <linux/acpi.h> 16 + #include <linux/arm-smccc.h> 16 17 #include <linux/cpumask.h> 17 18 #include <linux/efi.h> 18 19 #include <linux/efi-bgrt.h> ··· 412 411 { 413 412 memblock_mark_nomap(addr, size); 414 413 } 414 + 415 + #ifdef CONFIG_ACPI_FFH 416 + /* 417 + * Implements ARM64 specific callbacks to support ACPI FFH Operation Region as 418 + * specified in https://developer.arm.com/docs/den0048/latest 419 + */ 420 + struct acpi_ffh_data { 421 + struct acpi_ffh_info info; 422 + void (*invoke_ffh_fn)(unsigned long a0, unsigned long a1, 423 + unsigned long a2, unsigned long a3, 424 + unsigned long a4, unsigned long a5, 425 + unsigned long a6, unsigned long a7, 426 + struct arm_smccc_res *args, 427 + struct arm_smccc_quirk *res); 428 + void (*invoke_ffh64_fn)(const struct arm_smccc_1_2_regs *args, 429 + struct arm_smccc_1_2_regs *res); 430 + }; 431 + 432 + int acpi_ffh_address_space_arch_setup(void *handler_ctxt, void **region_ctxt) 433 + { 434 + enum arm_smccc_conduit conduit; 435 + struct acpi_ffh_data *ffh_ctxt; 436 + 437 + ffh_ctxt = kzalloc(sizeof(*ffh_ctxt), GFP_KERNEL); 438 + if (!ffh_ctxt) 439 + return -ENOMEM; 440 + 441 + if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2) 442 + return -EOPNOTSUPP; 443 + 444 + conduit = arm_smccc_1_1_get_conduit(); 445 + if (conduit == SMCCC_CONDUIT_NONE) { 446 + pr_err("%s: invalid SMCCC conduit\n", __func__); 447 + return -EOPNOTSUPP; 448 + } 449 + 450 + if (conduit == SMCCC_CONDUIT_SMC) { 451 + ffh_ctxt->invoke_ffh_fn = __arm_smccc_smc; 452 + ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_smc; 453 + } else { 454 + ffh_ctxt->invoke_ffh_fn = __arm_smccc_hvc; 455 + ffh_ctxt->invoke_ffh64_fn = arm_smccc_1_2_hvc; 456 + } 457 + 458 + memcpy(ffh_ctxt, handler_ctxt, sizeof(ffh_ctxt->info)); 459 + 460 + *region_ctxt = ffh_ctxt; 461 + return AE_OK; 462 + } 463 + 464 + static bool acpi_ffh_smccc_owner_allowed(u32 fid) 465 + { 466 + int owner = ARM_SMCCC_OWNER_NUM(fid); 467 + 468 + if (owner == ARM_SMCCC_OWNER_STANDARD || 469 + owner == ARM_SMCCC_OWNER_SIP || owner == ARM_SMCCC_OWNER_OEM) 470 + return true; 471 + 472 + return false; 473 + } 474 + 475 + int acpi_ffh_address_space_arch_handler(acpi_integer *value, void *region_context) 476 + { 477 + int ret = 0; 478 + struct acpi_ffh_data *ffh_ctxt = region_context; 479 + 480 + if (ffh_ctxt->info.offset == 0) { 481 + /* SMC/HVC 32bit call */ 482 + struct arm_smccc_res res; 483 + u32 a[8] = { 0 }, *ptr = (u32 *)value; 484 + 485 + if (!ARM_SMCCC_IS_FAST_CALL(*ptr) || ARM_SMCCC_IS_64(*ptr) || 486 + !acpi_ffh_smccc_owner_allowed(*ptr) || 487 + ffh_ctxt->info.length > 32) { 488 + ret = AE_ERROR; 489 + } else { 490 + int idx, len = ffh_ctxt->info.length >> 2; 491 + 492 + for (idx = 0; idx < len; idx++) 493 + a[idx] = *(ptr + idx); 494 + 495 + ffh_ctxt->invoke_ffh_fn(a[0], a[1], a[2], a[3], a[4], 496 + a[5], a[6], a[7], &res, NULL); 497 + memcpy(value, &res, sizeof(res)); 498 + } 499 + 500 + } else if (ffh_ctxt->info.offset == 1) { 501 + /* SMC/HVC 64bit call */ 502 + struct arm_smccc_1_2_regs *r = (struct arm_smccc_1_2_regs *)value; 503 + 504 + if (!ARM_SMCCC_IS_FAST_CALL(r->a0) || !ARM_SMCCC_IS_64(r->a0) || 505 + !acpi_ffh_smccc_owner_allowed(r->a0) || 506 + ffh_ctxt->info.length > sizeof(*r)) { 507 + ret = AE_ERROR; 508 + } else { 509 + ffh_ctxt->invoke_ffh64_fn(r, r); 510 + memcpy(value, r, ffh_ctxt->info.length); 511 + } 512 + } else { 513 + ret = AE_ERROR; 514 + } 515 + 516 + return ret; 517 + } 518 + #endif /* CONFIG_ACPI_FFH */