xref: /linux/drivers/acpi/arm64/ffh.c (revision 4b132aacb0768ac1e652cf517097ea6f237214b9)
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