diff options
Diffstat (limited to 'services/std_svc/drtm/drtm_dma_prot.c')
-rw-r--r-- | services/std_svc/drtm/drtm_dma_prot.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/services/std_svc/drtm/drtm_dma_prot.c b/services/std_svc/drtm/drtm_dma_prot.c new file mode 100644 index 000000000..e41f36073 --- /dev/null +++ b/services/std_svc/drtm/drtm_dma_prot.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * DRTM DMA protection. + * + * Authors: + * Lucian Paul-Trifu <lucian.paultrifu@gmail.com> + * + */ +#include <stdint.h> +#include <string.h> + +#include <common/debug.h> +#include <drivers/arm/smmu_v3.h> +#include <services/drtm_svc_plat.h> +#include <smccc_helpers.h> + +#include "drtm_dma_prot.h" +#include "drtm_remediation.h" +#include "drtm_main.h" + + +/* Values for DRTM_PROTECT_MEMORY */ +enum dma_prot_type { + PROTECT_NONE = -1, + PROTECT_MEM_ALL = 0, + PROTECT_MEM_REGION = 1, +}; + +struct dma_prot { + enum dma_prot_type type; +}; + +/* + * ________________________ LAUNCH success ________________________ + * | Initial | -------------------> | Prot engaged | + * |````````````````````````| |````````````````````````| + * | request.type == NONE | | request.type != NONE | + * | | <------------------- | | + * `________________________' UNPROTECT_MEM `________________________' + * + * Transitions that are not shown correspond to ABI calls that do not change + * state and result in an error being returned to the caller. + */ +static struct dma_prot active_prot = { + .type = PROTECT_NONE, +}; + +/* Version-independent type. */ +typedef struct drtm_dl_dma_prot_args_v1 struct_drtm_dl_dma_prot_args; + + +int drtm_dma_prot_init(void) +{ + bool must_init_fail = false; + const uintptr_t *smmus; + size_t num_smmus = 0; + unsigned int num_smmus_total; + + /* Report presence of non-host platforms, for info only. */ + if (plat_has_non_host_platforms()) { + WARN("DRTM: the platform includes trusted DMA-capable devices" + " (non-host platforms)\n"); + } + + /* + * DLME protection is uncertain on platforms with peripherals whose + * DMA is not managed by an SMMU. DRTM doesn't work on such platforms. + */ + if (plat_has_unmanaged_dma_peripherals()) { + ERROR("DRTM: this platform does not provide DMA protection\n"); + must_init_fail = true; + } + + /* + * Check that the platform reported all SMMUs. + * It is acceptable if the platform doesn't have any SMMUs when it + * doesn't have any DMA-capable devices. + */ + num_smmus_total = plat_get_total_num_smmus(); + plat_enumerate_smmus((const uintptr_t (*)[])&smmus, &num_smmus); + if (num_smmus != num_smmus_total) { + ERROR("DRTM: could not discover all SMMUs\n"); + must_init_fail = true; + } + + /* Check any SMMUs enumerated. */ + for (const uintptr_t *smmu = smmus; smmu < smmus + num_smmus; smmu++) { + if (*smmu == 0) { + WARN("DRTM: SMMU reported at unusual PA 0x0\n"); + } + } + + return (int)must_init_fail; +} + +uint64_t drtm_features_dma_prot(void *ctx) +{ + SMC_RET2(ctx, 1ULL, /* DMA protection feature is supported */ + 1u /* DMA protection support: Complete DMA protection. */ + ); +} + +/* + * Checks that the DMA protection arguments are valid and that the given + * protected regions would be covered by DMA protection. + */ +enum drtm_retc drtm_dma_prot_check_args(const struct_drtm_dl_dma_prot_args *a, + int a_dma_prot_type, + struct __protected_regions p) +{ + switch ((enum dma_prot_type)a_dma_prot_type) { + case PROTECT_MEM_ALL: + if (a->dma_prot_table_paddr || a->dma_prot_table_size) { + ERROR("DRTM: invalid launch due to inconsistent" + " DMA protection arguments\n"); + return MEM_PROTECT_INVALID; + } + /* + * Full DMA protection ought to ensure that the DLME and NWd + * DCE regions are protected, no further checks required. + */ + return SUCCESS; + + default: + ERROR("DRTM: invalid launch due to unsupported DMA protection type\n"); + return MEM_PROTECT_INVALID; + } +} + +enum drtm_retc drtm_dma_prot_engage(const struct_drtm_dl_dma_prot_args *a, + int a_dma_prot_type) +{ + const uintptr_t *smmus; + size_t num_smmus = 0; + + if (active_prot.type != PROTECT_NONE) { + ERROR("DRTM: launch denied as previous DMA protection" + " is still engaged\n"); + return DENIED; + } + + if (a_dma_prot_type == PROTECT_NONE) { + return SUCCESS; + /* Only PROTECT_MEM_ALL is supported currently. */ + } else if (a_dma_prot_type != PROTECT_MEM_ALL) { + ERROR("%s(): unimplemented DMA protection type\n", __func__); + panic(); + } + + /* + * Engage SMMUs in accordance with the request we have previously received. + * Only PROTECT_MEM_ALL is implemented currently. + */ + plat_enumerate_smmus((const uintptr_t (*)[])&smmus, &num_smmus); + for (const uintptr_t *smmu = smmus; smmu < smmus+num_smmus; smmu++) { + int rc; + + /* + * TODO: Invalidate SMMU's Stage-1 and Stage-2 TLB entries. This ensures + * that any outstanding device transactions are completed, see Section + * 3.21.1, specification IHI_0070_C_a for an approximate reference. + */ + + if ((rc = smmuv3_ns_set_abort_all(*smmu))) { + ERROR("DRTM: SMMU at PA 0x%lx failed to engage DMA protection" + " rc=%d\n", *smmu, rc); + return INTERNAL_ERROR; + } + } + + /* + * TODO: Restrict DMA from the GIC. + * + * Full DMA protection may be achieved as follows: + * + * With a GICv3: + * - Set GICR_CTLR.EnableLPIs to 0, for each GICR; + * GICR_CTLR.RWP == 0 must be the case before finishing, for each GICR. + * - Set GITS_CTLR.Enabled to 0; + * GITS_CTLR.Quiescent == 1 must be the case before finishing. + * + * In addition, with a GICv4: + * - Set GICR_VPENDBASER.Valid to 0, for each GICR; + * GICR_CTLR.RWP == 0 must be the case before finishing, for each GICR. + * + * Alternatively, e.g. if some bit values cannot be changed at runtime, + * this procedure should return an error if the LPI Pending and + * Configuration tables overlap the regions being protected. + */ + + active_prot.type = a_dma_prot_type; + + return SUCCESS; +} + +/* + * Undo what has previously been done in drtm_dma_prot_engage(), or enter + * remediation if it is not possible. + */ +enum drtm_retc drtm_dma_prot_disengage(void) +{ + const uintptr_t *smmus; + size_t num_smmus = 0; + + if (active_prot.type == PROTECT_NONE) { + return SUCCESS; + /* Only PROTECT_MEM_ALL is supported currently. */ + } else if (active_prot.type != PROTECT_MEM_ALL) { + ERROR("%s(): unimplemented DMA protection type\n", __func__); + panic(); + } + + /* + * For PROTECT_MEM_ALL, undo the SMMU configuration for "abort all" mode + * done during engage(). + */ + /* Simply enter remediation for now. */ + (void)smmus; + (void)num_smmus; + drtm_enter_remediation(1, "cannot undo PROTECT_MEM_ALL SMMU configuration"); + + /* TODO: Undo GIC DMA restrictions. */ + + active_prot.type = PROTECT_NONE; + + return SUCCESS; +} + +uint64_t drtm_unprotect_mem(void *ctx) +{ + enum drtm_retc ret; + + switch (active_prot.type) { + case PROTECT_NONE: + ERROR("DRTM: invalid UNPROTECT_MEM, no DMA protection has" + " previously been engaged\n"); + ret = DENIED; + break; + + case PROTECT_MEM_ALL: + /* + * UNPROTECT_MEM is a no-op for PROTECT_MEM_ALL: DRTM must not touch + * the NS SMMU as it is expected that the DLME has configured it. + */ + active_prot.type = PROTECT_NONE; + + ret = SUCCESS; + break; + + default: + ret = drtm_dma_prot_disengage(); + break; + } + + SMC_RET1(ctx, ret); +} + +void drtm_dma_prot_serialise_table(char *dst, size_t *size_out) +{ + if (active_prot.type == PROTECT_NONE) { + if (size_out) { + *size_out = 0; + } + return; + } else if (active_prot.type != PROTECT_MEM_ALL) { + ERROR("%s(): unimplemented DMA protection type\n", __func__); + panic(); + } + + struct __packed descr_table_1 { + struct_drtm_mem_region_descr_table header; + struct_drtm_mem_region_descr regions[1]; + } prot_table = { + .header = { + .version = 1, + .num_regions = sizeof(((struct descr_table_1 *)NULL)->regions) / + sizeof(((struct descr_table_1 *)NULL)->regions[0]) + }, + #define PAGES_AND_TYPE(pages, type) \ + .pages_and_type = DRTM_MEM_REGION_PAGES_AND_TYPE(pages, type) + .regions = { + {.paddr = 0, PAGES_AND_TYPE(UINT64_MAX, 0x3)}, + } + }; + + if (dst) { + (void)memcpy(dst, &prot_table, sizeof(prot_table)); + } + if (size_out) { + *size_out = sizeof(prot_table); + } +} |