1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved.
4 */
5
6 #include <linux/arm-smccc.h>
7 #include <linux/gunyah.h>
8 #include <linux/mm.h>
9 #include <linux/module.h>
10 #include <linux/firmware/qcom/qcom_scm.h>
11 #include <linux/types.h>
12 #include <linux/uuid.h>
13
14 #define QCOM_SCM_RM_MANAGED_VMID 0x3A
15 #define QCOM_SCM_MAX_MANAGED_VMID 0x3F
16
17 static int
qcom_scm_gunyah_rm_pre_mem_share(struct gunyah_rm * rm,struct gunyah_rm_mem_parcel * mem_parcel)18 qcom_scm_gunyah_rm_pre_mem_share(struct gunyah_rm *rm,
19 struct gunyah_rm_mem_parcel *mem_parcel)
20 {
21 struct qcom_scm_vmperm *new_perms __free(kfree) = NULL;
22 u64 src, src_cpy;
23 int ret = 0, i, n;
24 u16 vmid;
25
26 new_perms = kcalloc(mem_parcel->n_acl_entries, sizeof(*new_perms),
27 GFP_KERNEL);
28 if (!new_perms)
29 return -ENOMEM;
30
31 for (n = 0; n < mem_parcel->n_acl_entries; n++) {
32 vmid = le16_to_cpu(mem_parcel->acl_entries[n].vmid);
33 if (vmid <= QCOM_SCM_MAX_MANAGED_VMID)
34 new_perms[n].vmid = vmid;
35 else
36 new_perms[n].vmid = QCOM_SCM_RM_MANAGED_VMID;
37 if (mem_parcel->acl_entries[n].perms & GUNYAH_RM_ACL_X)
38 new_perms[n].perm |= QCOM_SCM_PERM_EXEC;
39 if (mem_parcel->acl_entries[n].perms & GUNYAH_RM_ACL_W)
40 new_perms[n].perm |= QCOM_SCM_PERM_WRITE;
41 if (mem_parcel->acl_entries[n].perms & GUNYAH_RM_ACL_R)
42 new_perms[n].perm |= QCOM_SCM_PERM_READ;
43 }
44
45 src = BIT_ULL(QCOM_SCM_VMID_HLOS);
46
47 for (i = 0; i < mem_parcel->n_mem_entries; i++) {
48 src_cpy = src;
49 ret = qcom_scm_assign_mem(
50 le64_to_cpu(mem_parcel->mem_entries[i].phys_addr),
51 le64_to_cpu(mem_parcel->mem_entries[i].size), &src_cpy,
52 new_perms, mem_parcel->n_acl_entries);
53 if (ret)
54 break;
55 }
56
57 /* Did it work ok? */
58 if (!ret)
59 return 0;
60
61 src = 0;
62 for (n = 0; n < mem_parcel->n_acl_entries; n++) {
63 vmid = le16_to_cpu(mem_parcel->acl_entries[n].vmid);
64 if (vmid <= QCOM_SCM_MAX_MANAGED_VMID)
65 src |= BIT_ULL(vmid);
66 else
67 src |= BIT_ULL(QCOM_SCM_RM_MANAGED_VMID);
68 }
69
70 new_perms[0].vmid = QCOM_SCM_VMID_HLOS;
71 new_perms[0].perm = QCOM_SCM_PERM_EXEC | QCOM_SCM_PERM_WRITE |
72 QCOM_SCM_PERM_READ;
73
74 for (i--; i >= 0; i--) {
75 src_cpy = src;
76 WARN_ON_ONCE(qcom_scm_assign_mem(
77 le64_to_cpu(mem_parcel->mem_entries[i].phys_addr),
78 le64_to_cpu(mem_parcel->mem_entries[i].size), &src_cpy,
79 new_perms, 1));
80 }
81
82 return ret;
83 }
84
85 static int
qcom_scm_gunyah_rm_post_mem_reclaim(struct gunyah_rm * rm,struct gunyah_rm_mem_parcel * mem_parcel)86 qcom_scm_gunyah_rm_post_mem_reclaim(struct gunyah_rm *rm,
87 struct gunyah_rm_mem_parcel *mem_parcel)
88 {
89 struct qcom_scm_vmperm new_perms;
90 u64 src = 0, src_cpy;
91 int ret = 0, i, n;
92 u16 vmid;
93
94 new_perms.vmid = QCOM_SCM_VMID_HLOS;
95 new_perms.perm = QCOM_SCM_PERM_EXEC | QCOM_SCM_PERM_WRITE |
96 QCOM_SCM_PERM_READ;
97
98 for (n = 0; n < mem_parcel->n_acl_entries; n++) {
99 vmid = le16_to_cpu(mem_parcel->acl_entries[n].vmid);
100 if (vmid <= QCOM_SCM_MAX_MANAGED_VMID)
101 src |= (1ull << vmid);
102 else
103 src |= (1ull << QCOM_SCM_RM_MANAGED_VMID);
104 }
105
106 for (i = 0; i < mem_parcel->n_mem_entries; i++) {
107 src_cpy = src;
108 ret = qcom_scm_assign_mem(
109 le64_to_cpu(mem_parcel->mem_entries[i].phys_addr),
110 le64_to_cpu(mem_parcel->mem_entries[i].size), &src_cpy,
111 &new_perms, 1);
112 WARN_ON_ONCE(ret);
113 }
114
115 return ret;
116 }
117
118 static int
qcom_scm_gunyah_rm_pre_demand_page(struct gunyah_rm * rm,u16 vmid,enum gunyah_pagetable_access access,struct folio * folio)119 qcom_scm_gunyah_rm_pre_demand_page(struct gunyah_rm *rm, u16 vmid,
120 enum gunyah_pagetable_access access,
121 struct folio *folio)
122 {
123 struct qcom_scm_vmperm new_perms[2];
124 unsigned int n = 1;
125 u64 src;
126
127 new_perms[0].vmid = QCOM_SCM_RM_MANAGED_VMID;
128 new_perms[0].perm = QCOM_SCM_PERM_EXEC | QCOM_SCM_PERM_WRITE |
129 QCOM_SCM_PERM_READ;
130 if (access != GUNYAH_PAGETABLE_ACCESS_X &&
131 access != GUNYAH_PAGETABLE_ACCESS_RX &&
132 access != GUNYAH_PAGETABLE_ACCESS_RWX) {
133 new_perms[1].vmid = QCOM_SCM_VMID_HLOS;
134 new_perms[1].perm = QCOM_SCM_PERM_EXEC | QCOM_SCM_PERM_WRITE |
135 QCOM_SCM_PERM_READ;
136 n++;
137 }
138
139 src = BIT_ULL(QCOM_SCM_VMID_HLOS);
140
141 return qcom_scm_assign_mem(__pfn_to_phys(folio_pfn(folio)),
142 folio_size(folio), &src, new_perms, n);
143 }
144
145 static int
qcom_scm_gunyah_rm_release_demand_page(struct gunyah_rm * rm,u16 vmid,enum gunyah_pagetable_access access,struct folio * folio)146 qcom_scm_gunyah_rm_release_demand_page(struct gunyah_rm *rm, u16 vmid,
147 enum gunyah_pagetable_access access,
148 struct folio *folio)
149 {
150 struct qcom_scm_vmperm new_perms;
151 u64 src;
152
153 new_perms.vmid = QCOM_SCM_VMID_HLOS;
154 new_perms.perm = QCOM_SCM_PERM_EXEC | QCOM_SCM_PERM_WRITE |
155 QCOM_SCM_PERM_READ;
156
157 src = BIT_ULL(QCOM_SCM_RM_MANAGED_VMID);
158
159 if (access != GUNYAH_PAGETABLE_ACCESS_X &&
160 access != GUNYAH_PAGETABLE_ACCESS_RX &&
161 access != GUNYAH_PAGETABLE_ACCESS_RWX)
162 src |= BIT_ULL(QCOM_SCM_VMID_HLOS);
163
164 return qcom_scm_assign_mem(__pfn_to_phys(folio_pfn(folio)),
165 folio_size(folio), &src, &new_perms, 1);
166 }
167
168 static struct gunyah_rm_platform_ops qcom_scm_gunyah_rm_platform_ops = {
169 .pre_mem_share = qcom_scm_gunyah_rm_pre_mem_share,
170 .post_mem_reclaim = qcom_scm_gunyah_rm_post_mem_reclaim,
171 .pre_demand_page = qcom_scm_gunyah_rm_pre_demand_page,
172 .release_demand_page = qcom_scm_gunyah_rm_release_demand_page,
173 };
174
175 /* {19bd54bd-0b37-571b-946f-609b54539de6} */
176 static const uuid_t QCOM_EXT_UUID = UUID_INIT(0x19bd54bd, 0x0b37, 0x571b, 0x94,
177 0x6f, 0x60, 0x9b, 0x54, 0x53,
178 0x9d, 0xe6);
179
180 #define GUNYAH_QCOM_EXT_CALL_UUID_ID \
181 ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
182 ARM_SMCCC_OWNER_VENDOR_HYP, 0x3f01)
183
gunyah_has_qcom_extensions(void)184 static bool gunyah_has_qcom_extensions(void)
185 {
186 struct arm_smccc_res res;
187 uuid_t uuid;
188 u32 *up;
189
190 arm_smccc_1_1_smc(GUNYAH_QCOM_EXT_CALL_UUID_ID, &res);
191
192 up = (u32 *)&uuid.b[0];
193 up[0] = lower_32_bits(res.a0);
194 up[1] = lower_32_bits(res.a1);
195 up[2] = lower_32_bits(res.a2);
196 up[3] = lower_32_bits(res.a3);
197
198 return uuid_equal(&uuid, &QCOM_EXT_UUID);
199 }
200
qcom_gunyah_platform_hooks_register(void)201 static int __init qcom_gunyah_platform_hooks_register(void)
202 {
203 if (!gunyah_has_qcom_extensions())
204 return -ENODEV;
205
206 pr_info("Enabling Gunyah hooks for Qualcomm platforms.\n");
207
208 return gunyah_rm_register_platform_ops(
209 &qcom_scm_gunyah_rm_platform_ops);
210 }
211
qcom_gunyah_platform_hooks_unregister(void)212 static void __exit qcom_gunyah_platform_hooks_unregister(void)
213 {
214 gunyah_rm_unregister_platform_ops(&qcom_scm_gunyah_rm_platform_ops);
215 }
216
217 module_init(qcom_gunyah_platform_hooks_register);
218 module_exit(qcom_gunyah_platform_hooks_unregister);
219 MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Platform Hooks for Gunyah");
220 MODULE_LICENSE("GPL");
221