| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| #include <linux/arm-smccc.h> |
| #include <linux/gunyah_rsc_mgr.h> |
| #include <linux/module.h> |
| #include <linux/qcom_scm.h> |
| #include <linux/types.h> |
| #include <linux/uuid.h> |
| |
| #define QCOM_SCM_RM_MANAGED_VMID 0x3A |
| #define QCOM_SCM_MAX_MANAGED_VMID 0x3F |
| |
| static int qcom_scm_gh_rm_pre_mem_share(void *rm, struct gh_rm_mem_parcel *mem_parcel) |
| { |
| struct qcom_scm_vmperm *new_perms; |
| u64 src, src_cpy; |
| int ret = 0, i, n; |
| u16 vmid; |
| |
| new_perms = kcalloc(mem_parcel->n_acl_entries, sizeof(*new_perms), GFP_KERNEL); |
| if (!new_perms) |
| return -ENOMEM; |
| |
| for (n = 0; n < mem_parcel->n_acl_entries; n++) { |
| vmid = le16_to_cpu(mem_parcel->acl_entries[n].vmid); |
| if (vmid <= QCOM_SCM_MAX_MANAGED_VMID) |
| new_perms[n].vmid = vmid; |
| else |
| new_perms[n].vmid = QCOM_SCM_RM_MANAGED_VMID; |
| if (mem_parcel->acl_entries[n].perms & GH_RM_ACL_X) |
| new_perms[n].perm |= QCOM_SCM_PERM_EXEC; |
| if (mem_parcel->acl_entries[n].perms & GH_RM_ACL_W) |
| new_perms[n].perm |= QCOM_SCM_PERM_WRITE; |
| if (mem_parcel->acl_entries[n].perms & GH_RM_ACL_R) |
| new_perms[n].perm |= QCOM_SCM_PERM_READ; |
| } |
| |
| src = BIT_ULL(QCOM_SCM_VMID_HLOS); |
| |
| for (i = 0; i < mem_parcel->n_mem_entries; i++) { |
| src_cpy = src; |
| ret = qcom_scm_assign_mem(le64_to_cpu(mem_parcel->mem_entries[i].phys_addr), |
| le64_to_cpu(mem_parcel->mem_entries[i].size), |
| &src_cpy, new_perms, mem_parcel->n_acl_entries); |
| if (ret) |
| break; |
| } |
| |
| if (!ret) |
| goto out; |
| |
| src = 0; |
| for (n = 0; n < mem_parcel->n_acl_entries; n++) { |
| vmid = le16_to_cpu(mem_parcel->acl_entries[n].vmid); |
| if (vmid <= QCOM_SCM_MAX_MANAGED_VMID) |
| src |= BIT_ULL(vmid); |
| else |
| src |= BIT_ULL(QCOM_SCM_RM_MANAGED_VMID); |
| } |
| |
| new_perms[0].vmid = QCOM_SCM_VMID_HLOS; |
| |
| for (i--; i >= 0; i--) { |
| src_cpy = src; |
| WARN_ON_ONCE(qcom_scm_assign_mem( |
| le64_to_cpu(mem_parcel->mem_entries[i].phys_addr), |
| le64_to_cpu(mem_parcel->mem_entries[i].size), |
| &src_cpy, new_perms, 1)); |
| } |
| |
| out: |
| kfree(new_perms); |
| return ret; |
| } |
| |
| static int qcom_scm_gh_rm_post_mem_reclaim(void *rm, struct gh_rm_mem_parcel *mem_parcel) |
| { |
| struct qcom_scm_vmperm new_perms; |
| u64 src = 0, src_cpy; |
| int ret = 0, i, n; |
| u16 vmid; |
| |
| new_perms.vmid = QCOM_SCM_VMID_HLOS; |
| new_perms.perm = QCOM_SCM_PERM_EXEC | QCOM_SCM_PERM_WRITE | QCOM_SCM_PERM_READ; |
| |
| for (n = 0; n < mem_parcel->n_acl_entries; n++) { |
| vmid = le16_to_cpu(mem_parcel->acl_entries[n].vmid); |
| if (vmid <= QCOM_SCM_MAX_MANAGED_VMID) |
| src |= (1ull << vmid); |
| else |
| src |= (1ull << QCOM_SCM_RM_MANAGED_VMID); |
| } |
| |
| for (i = 0; i < mem_parcel->n_mem_entries; i++) { |
| src_cpy = src; |
| ret = qcom_scm_assign_mem(le64_to_cpu(mem_parcel->mem_entries[i].phys_addr), |
| le64_to_cpu(mem_parcel->mem_entries[i].size), |
| &src_cpy, &new_perms, 1); |
| WARN_ON_ONCE(ret); |
| } |
| |
| return ret; |
| } |
| |
| static struct gh_rm_platform_ops qcom_scm_gh_rm_platform_ops = { |
| .pre_mem_share = qcom_scm_gh_rm_pre_mem_share, |
| .post_mem_reclaim = qcom_scm_gh_rm_post_mem_reclaim, |
| }; |
| |
| /* {19bd54bd-0b37-571b-946f-609b54539de6} */ |
| static const uuid_t QCOM_EXT_UUID = |
| UUID_INIT(0x19bd54bd, 0x0b37, 0x571b, 0x94, 0x6f, 0x60, 0x9b, 0x54, 0x53, 0x9d, 0xe6); |
| |
| #define GH_QCOM_EXT_CALL_UUID_ID ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \ |
| ARM_SMCCC_OWNER_VENDOR_HYP, 0x3f01) |
| |
| static bool gh_has_qcom_extensions(void) |
| { |
| struct arm_smccc_res res; |
| uuid_t uuid; |
| u32 *up; |
| |
| arm_smccc_1_1_smc(GH_QCOM_EXT_CALL_UUID_ID, &res); |
| |
| up = (u32 *)&uuid.b[0]; |
| up[0] = lower_32_bits(res.a0); |
| up[1] = lower_32_bits(res.a1); |
| up[2] = lower_32_bits(res.a2); |
| up[3] = lower_32_bits(res.a3); |
| |
| return uuid_equal(&uuid, &QCOM_EXT_UUID); |
| } |
| |
| static int __init qcom_gh_platform_hooks_register(void) |
| { |
| if (!gh_has_qcom_extensions()) |
| return -ENODEV; |
| |
| return gh_rm_register_platform_ops(&qcom_scm_gh_rm_platform_ops); |
| } |
| |
| static void __exit qcom_gh_platform_hooks_unregister(void) |
| { |
| gh_rm_unregister_platform_ops(&qcom_scm_gh_rm_platform_ops); |
| } |
| |
| module_init(qcom_gh_platform_hooks_register); |
| module_exit(qcom_gh_platform_hooks_unregister); |
| MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Platform Hooks for Gunyah"); |
| MODULE_LICENSE("GPL"); |