// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2014-2018 Etnaviv Project */ #include #include #include #include #include #include "etnaviv_gpu.h" #include "etnaviv_mmu.h" #include "state_hi.xml.h" #define PT_SIZE SZ_2M #define PT_ENTRIES (PT_SIZE / sizeof(u32)) #define GPU_MEM_START 0x80000000 struct etnaviv_iommuv1_context { struct etnaviv_iommu_context base; u32 *pgtable_cpu; dma_addr_t pgtable_dma; }; static struct etnaviv_iommuv1_context * to_v1_context(struct etnaviv_iommu_context *context) { return container_of(context, struct etnaviv_iommuv1_context, base); } static void etnaviv_iommuv1_free(struct etnaviv_iommu_context *context) { struct etnaviv_iommuv1_context *v1_context = to_v1_context(context); drm_mm_takedown(&context->mm); dma_free_wc(context->global->dev, PT_SIZE, v1_context->pgtable_cpu, v1_context->pgtable_dma); context->global->v1.shared_context = NULL; kfree(v1_context); } static int etnaviv_iommuv1_map(struct etnaviv_iommu_context *context, unsigned long iova, phys_addr_t paddr, size_t size, int prot) { struct etnaviv_iommuv1_context *v1_context = to_v1_context(context); unsigned int index = (iova - GPU_MEM_START) / SZ_4K; if (size != SZ_4K) return -EINVAL; v1_context->pgtable_cpu[index] = paddr; return 0; } static size_t etnaviv_iommuv1_unmap(struct etnaviv_iommu_context *context, unsigned long iova, size_t size) { struct etnaviv_iommuv1_context *v1_context = to_v1_context(context); unsigned int index = (iova - GPU_MEM_START) / SZ_4K; if (size != SZ_4K) return -EINVAL; v1_context->pgtable_cpu[index] = context->global->bad_page_dma; return SZ_4K; } static size_t etnaviv_iommuv1_dump_size(struct etnaviv_iommu_context *context) { return PT_SIZE; } static void etnaviv_iommuv1_dump(struct etnaviv_iommu_context *context, void *buf) { struct etnaviv_iommuv1_context *v1_context = to_v1_context(context); memcpy(buf, v1_context->pgtable_cpu, PT_SIZE); } static void etnaviv_iommuv1_restore(struct etnaviv_gpu *gpu, struct etnaviv_iommu_context *context) { struct etnaviv_iommuv1_context *v1_context = to_v1_context(context); u32 pgtable; if (gpu->mmu_context) etnaviv_iommu_context_put(gpu->mmu_context); gpu->mmu_context = etnaviv_iommu_context_get(context); /* set base addresses */ gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_RA, context->global->memory_base); gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_FE, context->global->memory_base); gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_TX, context->global->memory_base); gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_PEZ, context->global->memory_base); gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_PE, context->global->memory_base); /* set page table address in MC */ pgtable = (u32)v1_context->pgtable_dma; gpu_write(gpu, VIVS_MC_MMU_FE_PAGE_TABLE, pgtable); gpu_write(gpu, VIVS_MC_MMU_TX_PAGE_TABLE, pgtable); gpu_write(gpu, VIVS_MC_MMU_PE_PAGE_TABLE, pgtable); gpu_write(gpu, VIVS_MC_MMU_PEZ_PAGE_TABLE, pgtable); gpu_write(gpu, VIVS_MC_MMU_RA_PAGE_TABLE, pgtable); } const struct etnaviv_iommu_ops etnaviv_iommuv1_ops = { .free = etnaviv_iommuv1_free, .map = etnaviv_iommuv1_map, .unmap = etnaviv_iommuv1_unmap, .dump_size = etnaviv_iommuv1_dump_size, .dump = etnaviv_iommuv1_dump, .restore = etnaviv_iommuv1_restore, }; struct etnaviv_iommu_context * etnaviv_iommuv1_context_alloc(struct etnaviv_iommu_global *global) { struct etnaviv_iommuv1_context *v1_context; struct etnaviv_iommu_context *context; mutex_lock(&global->lock); /* * MMUv1 does not support switching between different contexts without * a stop the world operation, so we only support a single shared * context with this version. */ if (global->v1.shared_context) { context = global->v1.shared_context; etnaviv_iommu_context_get(context); mutex_unlock(&global->lock); return context; } v1_context = kzalloc(sizeof(*v1_context), GFP_KERNEL); if (!v1_context) { mutex_unlock(&global->lock); return NULL; } v1_context->pgtable_cpu = dma_alloc_wc(global->dev, PT_SIZE, &v1_context->pgtable_dma, GFP_KERNEL); if (!v1_context->pgtable_cpu) goto out_free; memset32(v1_context->pgtable_cpu, global->bad_page_dma, PT_ENTRIES); context = &v1_context->base; context->global = global; kref_init(&context->refcount); mutex_init(&context->lock); INIT_LIST_HEAD(&context->mappings); drm_mm_init(&context->mm, GPU_MEM_START, PT_ENTRIES * SZ_4K); context->global->v1.shared_context = context; mutex_unlock(&global->lock); return context; out_free: mutex_unlock(&global->lock); kfree(v1_context); return NULL; }