// SPDX-License-Identifier: GPL-2.0 /* * DMABUF mtk_sec heap exporter * * Copyright (C) 2021 MediaTek Inc. * */ #define pr_fmt(fmt) "dma_heap: mtk_sec "fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mtk_heap_priv.h" #include "mtk_heap.h" #include "mtk_iommu.h" enum sec_heap_region_type { /* MM heap */ MM_HEAP_START, SVP_REGION, PROT_REGION, PROT_2D_FR_REGION, WFD_REGION, TUI_REGION, MM_HEAP_END, /* APU heap */ APU_HEAP_START, SAPU_DATA_SHM_REGION, SAPU_ENGINE_SHM_REGION, APU_HEAP_END, REGION_HEAPS_NUM, }; enum sec_heap_page_type { SVP_PAGE, PROT_PAGE, WFD_PAGE, PAGE_HEAPS_NUM, }; enum HEAP_BASE_TYPE { HEAP_TYPE_INVALID = 0, REGION_BASE, PAGE_BASE, HEAP_BASE_NUM }; #define NAME_LEN 32 enum REGION_TYPE { REGION_HEAP_NORMAL, REGION_HEAP_ALIGN, REGION_TYPE_NUM, }; struct secure_heap_region { bool heap_mapped; /* indicate whole region if it is mapped */ bool heap_filled; /* indicate whole region if it is filled */ struct mutex heap_lock; const char *heap_name[REGION_TYPE_NUM]; atomic64_t total_size; phys_addr_t region_pa; u32 region_size; struct sg_table *region_table; struct dma_heap *heap[REGION_TYPE_NUM]; struct device *heap_dev; enum TRUSTED_MEM_REQ_TYPE tmem_type; enum HEAP_BASE_TYPE heap_type; }; struct secure_heap_page { char heap_name[NAME_LEN]; atomic64_t total_size; struct dma_heap *heap; struct device *heap_dev; enum TRUSTED_MEM_REQ_TYPE tmem_type; enum HEAP_BASE_TYPE heap_type; }; struct mtk_sec_heap_buffer { struct dma_heap *heap; struct list_head attachments; struct mutex lock; unsigned long len; struct sg_table sg_table; int vmap_cnt; void *vaddr; bool uncached; /* helper function */ int (*show)(const struct dma_buf *dmabuf, struct seq_file *s); /* secure heap will not strore sgtable here */ bool mapped[MTK_M4U_TAB_NR_MAX][MTK_M4U_DOM_NR_MAX]; struct mtk_heap_dev_info dev_info[MTK_M4U_TAB_NR_MAX][MTK_M4U_DOM_NR_MAX]; struct sg_table *mapped_table[MTK_M4U_TAB_NR_MAX][MTK_M4U_DOM_NR_MAX]; struct mutex map_lock; /* map iova lock */ pid_t pid; pid_t tid; char pid_name[TASK_COMM_LEN]; char tid_name[TASK_COMM_LEN]; unsigned long long ts; /* us */ /* private part for secure heap */ u64 sec_handle;/* keep same type with tmem */ struct ssheap_buf_info *ssheap; /* for page base */ }; static struct secure_heap_page mtk_sec_heap_page[PAGE_HEAPS_NUM] = { [SVP_PAGE] = { .heap_name = "mtk_svp_page-uncached", .tmem_type = TRUSTED_MEM_REQ_SVP_PAGE, .heap_type = PAGE_BASE, }, [PROT_PAGE] = { .heap_name = "mtk_prot_page-uncached", .tmem_type = TRUSTED_MEM_REQ_PROT_PAGE, .heap_type = PAGE_BASE, }, [WFD_PAGE] = { .heap_name = "mtk_wfd_page-uncached", .tmem_type = TRUSTED_MEM_REQ_WFD_PAGE, .heap_type = PAGE_BASE, }, }; static struct secure_heap_region mtk_sec_heap_region[REGION_HEAPS_NUM] = { [SVP_REGION] = { .heap_name = {"mtk_svp_region", "mtk_svp_region-aligned"}, .tmem_type = TRUSTED_MEM_REQ_SVP_REGION, .heap_type = REGION_BASE, }, [PROT_REGION] = { .heap_name = {"mtk_prot_region", "mtk_prot_region-aligned"}, .tmem_type = TRUSTED_MEM_REQ_PROT_REGION, .heap_type = REGION_BASE, }, [PROT_2D_FR_REGION] = { .heap_name = {"mtk_2d_fr_region", "mtk_2d_fr_region-aligned"}, .tmem_type = TRUSTED_MEM_REQ_2D_FR, .heap_type = REGION_BASE, }, [WFD_REGION] = { .heap_name = {"mtk_wfd_region", "mtk_wfd_region-aligned"}, .tmem_type = TRUSTED_MEM_REQ_WFD_REGION, .heap_type = REGION_BASE, }, [SAPU_DATA_SHM_REGION] = { .heap_name = {"mtk_sapu_data_shm_region", "mtk_sapu_data_shm_region-aligned"}, .tmem_type = TRUSTED_MEM_REQ_SAPU_DATA_SHM, .heap_type = REGION_BASE, }, [SAPU_ENGINE_SHM_REGION] = { .heap_name = {"mtk_sapu_engine_shm_region", "mtk_sapu_engine_shm_region-aligned"}, .tmem_type = TRUSTED_MEM_REQ_SAPU_ENGINE_SHM, .heap_type = REGION_BASE, }, [TUI_REGION] = { .heap_name = {"mtk_tui_region", "mtk_tui_region-aligned"}, .tmem_type = TRUSTED_MEM_REQ_TUI_REGION, .heap_type = REGION_BASE, }, }; /* function declare */ static int is_region_base_dmabuf(const struct dma_buf *dmabuf); static int is_page_base_dmabuf(const struct dma_buf *dmabuf); static int sec_buf_priv_dump(const struct dma_buf *dmabuf, struct seq_file *s); static bool region_heap_is_aligned(struct dma_heap *heap) { if (strstr(dma_heap_get_name(heap), "aligned")) return true; return false; } static int get_heap_base_type(const struct dma_heap *heap) { int i, j, k; for (i = SVP_REGION; i < REGION_HEAPS_NUM; i++) { for (j = REGION_HEAP_NORMAL; j < REGION_TYPE_NUM; j++) { if (mtk_sec_heap_region[i].heap[j] == heap) return REGION_BASE; } } for (k = SVP_PAGE; k < PAGE_HEAPS_NUM; k++) { if (mtk_sec_heap_page[k].heap == heap) return PAGE_BASE; } return HEAP_TYPE_INVALID; } static struct secure_heap_region *sec_heap_region_get(const struct dma_heap *heap) { int i, j; for (i = SVP_REGION; i < REGION_HEAPS_NUM; i++) { for (j = REGION_HEAP_NORMAL; j < REGION_TYPE_NUM; j++) { if (mtk_sec_heap_region[i].heap[j] == heap) return &mtk_sec_heap_region[i]; } } return NULL; } static struct secure_heap_page *sec_heap_page_get(const struct dma_heap *heap) { int i = 0; for (i = SVP_PAGE; i < PAGE_HEAPS_NUM; i++) { if (mtk_sec_heap_page[i].heap == heap) return &mtk_sec_heap_page[i]; } return NULL; } static struct sg_table *dup_sg_table_sec(struct sg_table *table) { struct sg_table *new_table; int ret, i; struct scatterlist *sg, *new_sg; new_table = kzalloc(sizeof(*new_table), GFP_KERNEL); if (!new_table) return ERR_PTR(-ENOMEM); ret = sg_alloc_table(new_table, table->orig_nents, GFP_KERNEL); if (ret) { kfree(new_table); return ERR_PTR(-ENOMEM); } new_sg = new_table->sgl; for_each_sgtable_sg(table, sg, i) { /* skip copy dma_address, need get via map_attachment */ sg_set_page(new_sg, sg_page(sg), sg->length, sg->offset); new_sg = sg_next(new_sg); } return new_table; } static int region_base_free(struct secure_heap_region *sec_heap, struct mtk_sec_heap_buffer *buffer) { int i, j, ret; u64 sec_handle = 0; /* remove all domains' sgtable */ for (i = 0; i < MTK_M4U_TAB_NR_MAX; i++) { for (j = 0; j < MTK_M4U_DOM_NR_MAX; j++) { struct sg_table *table = buffer->mapped_table[i][j]; struct mtk_heap_dev_info dev_info = buffer->dev_info[i][j]; unsigned long attrs = dev_info.map_attrs | DMA_ATTR_SKIP_CPU_SYNC; if (!buffer->mapped[i][j] || dev_is_normal_region(dev_info.dev)) continue; pr_debug("%s: free tab:%d, dom:%d iova:0x%lx, dev:%s\n", __func__, i, j, (unsigned long)sg_dma_address(table->sgl), dev_name(dev_info.dev)); dma_unmap_sgtable(dev_info.dev, table, dev_info.direction, attrs); buffer->mapped[i][j] = false; sg_free_table(table); kfree(table); } } sec_handle = buffer->sec_handle; ret = trusted_mem_api_unref(sec_heap->tmem_type, sec_handle, (uint8_t *)dma_heap_get_name(buffer->heap), 0, NULL); if (ret) { pr_err("%s error, trusted_mem_api_unref failed, heap:%s, ret:%d\n", __func__, dma_heap_get_name(buffer->heap), ret); return ret; } if (atomic64_sub_return(buffer->len, &sec_heap->total_size) < 0) pr_warn("%s warn!, total memory overflow, 0x%lx!!\n", __func__, atomic64_read(&sec_heap->total_size)); if (!atomic64_read(&sec_heap->total_size)) { mutex_lock(&sec_heap->heap_lock); if (sec_heap->heap_filled && sec_heap->region_table) { if (sec_heap->heap_mapped) { dma_unmap_sgtable(sec_heap->heap_dev, sec_heap->region_table, DMA_BIDIRECTIONAL, DMA_ATTR_SKIP_CPU_SYNC); sec_heap->heap_mapped = false; } sg_free_table(sec_heap->region_table); kfree(sec_heap->region_table); sec_heap->region_table = NULL; sec_heap->heap_filled = false; pr_debug("%s: all secure memory already free, unmap heap_region iova\n", __func__); } mutex_unlock(&sec_heap->heap_lock); } pr_debug("%s done, [%s] size:0x%lx, total_size:0x%lx\n", __func__, dma_heap_get_name(buffer->heap), buffer->len, atomic64_read(&sec_heap->total_size)); return ret; } static int page_base_free(struct secure_heap_page *sec_heap, struct mtk_sec_heap_buffer *buffer) { int i, j, ret; /* remove all domains' sgtable */ for (i = 0; i < MTK_M4U_TAB_NR_MAX; i++) { for (j = 0; j < MTK_M4U_DOM_NR_MAX; j++) { struct sg_table *table = buffer->mapped_table[i][j]; struct mtk_heap_dev_info dev_info = buffer->dev_info[i][j]; unsigned long attrs = dev_info.map_attrs | DMA_ATTR_SKIP_CPU_SYNC; if (!buffer->mapped[i][j]) continue; pr_debug("%s: free tab:%d, region:%d iova:0x%lx, dev:%s\n", __func__, i, j, (unsigned long)sg_dma_address(table->sgl), dev_name(dev_info.dev)); dma_unmap_sgtable(dev_info.dev, table, dev_info.direction, attrs); buffer->mapped[i][j] = false; sg_free_table(table); kfree(table); } } ret = trusted_mem_api_unref(sec_heap->tmem_type, buffer->sec_handle, (uint8_t *)dma_heap_get_name(buffer->heap), 0, buffer->ssheap); if (ret) { pr_err("%s error, trusted_mem_api_unref failed, heap:%s, ret:%d\n", __func__, dma_heap_get_name(buffer->heap), ret); return ret; } if (atomic64_sub_return(buffer->len, &sec_heap->total_size) < 0) pr_warn("%s, total memory overflow, 0x%lx!!\n", __func__, atomic64_read(&sec_heap->total_size)); pr_debug("%s done, [%s] size:0x%lx, total_size:0x%lx\n", __func__, dma_heap_get_name(buffer->heap), buffer->len, atomic64_read(&sec_heap->total_size)); return ret; } static void tmem_region_free(struct dma_buf *dmabuf) { int ret = -EINVAL; struct secure_heap_region *sec_heap; struct mtk_sec_heap_buffer *buffer = NULL; dmabuf_release_check(dmabuf); buffer = dmabuf->priv; sec_heap = sec_heap_region_get(buffer->heap); if (!sec_heap) { pr_err("%s, can not find secure heap!!\n", __func__, buffer->heap ? dma_heap_get_name(buffer->heap) : "null ptr"); return; } ret = region_base_free(sec_heap, buffer); if (ret) { pr_err("%s fail, heap:%u\n", __func__, sec_heap->heap_type); return; } sg_free_table(&buffer->sg_table); kfree(buffer); } static void tmem_page_free(struct dma_buf *dmabuf) { int ret = -EINVAL; struct secure_heap_page *sec_heap; struct mtk_sec_heap_buffer *buffer = NULL; dmabuf_release_check(dmabuf); buffer = dmabuf->priv; sec_heap = sec_heap_page_get(buffer->heap); if (!sec_heap) { pr_err("%s, can not find secure heap!!\n", __func__, buffer->heap ? dma_heap_get_name(buffer->heap) : "null ptr"); return; } ret = page_base_free(sec_heap, buffer); if (ret) { pr_err("%s fail, heap:%u\n", __func__, sec_heap->heap_type); return; } sg_free_table(&buffer->sg_table); kfree(buffer); } static int mtk_sec_heap_attach(struct dma_buf *dmabuf, struct dma_buf_attachment *attachment) { struct mtk_sec_heap_buffer *buffer = dmabuf->priv; struct dma_heap_attachment *a; struct sg_table *table; a = kzalloc(sizeof(*a), GFP_KERNEL); if (!a) return -ENOMEM; table = dup_sg_table_sec(&buffer->sg_table); if (IS_ERR(table)) { kfree(a); return -ENOMEM; } a->table = table; a->dev = attachment->dev; INIT_LIST_HEAD(&a->list); a->mapped = false; a->uncached = buffer->uncached; attachment->priv = a; mutex_lock(&buffer->lock); list_add(&a->list, &buffer->attachments); mutex_unlock(&buffer->lock); return 0; } static void mtk_sec_heap_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attachment) { struct mtk_sec_heap_buffer *buffer = dmabuf->priv; struct dma_heap_attachment *a = attachment->priv; mutex_lock(&buffer->lock); list_del(&a->list); mutex_unlock(&buffer->lock); sg_free_table(a->table); kfree(a->table); kfree(a); } /* source copy to dest */ static int copy_sec_sg_table(struct sg_table *source, struct sg_table *dest) { int i; struct scatterlist *sgl, *dest_sgl; if (source->orig_nents != dest->orig_nents) { pr_err("%s err, nents not match %d-%d\n", __func__, source->orig_nents, dest->orig_nents); return -EINVAL; } /* copy mapped nents */ dest->nents = source->nents; dest_sgl = dest->sgl; for_each_sg(source->sgl, sgl, source->orig_nents, i) { memcpy(dest_sgl, sgl, sizeof(*sgl)); dest_sgl = sg_next(dest_sgl); } return 0; }; /* * must check domain info before call fill_buffer_info * @Return 0: pass */ static int fill_sec_buffer_info(struct mtk_sec_heap_buffer *buf, struct sg_table *table, struct dma_buf_attachment *a, enum dma_data_direction dir, unsigned int tab_id, unsigned int dom_id) { struct sg_table *new_table = NULL; int ret = 0; /* * devices without iommus attribute, * use common flow, skip set buf_info */ if (tab_id >= MTK_M4U_TAB_NR_MAX || dom_id >= MTK_M4U_DOM_NR_MAX) return 0; if (buf->mapped[tab_id][dom_id]) { pr_info("%s err: already mapped, no need fill again\n", __func__); return -EINVAL; } new_table = kzalloc(sizeof(*new_table), GFP_KERNEL); if (!new_table) return -ENOMEM; ret = sg_alloc_table(new_table, table->orig_nents, GFP_KERNEL); if (ret) { kfree(new_table); return -ENOMEM; } ret = copy_sec_sg_table(table, new_table); if (ret) return ret; buf->mapped_table[tab_id][dom_id] = new_table; buf->mapped[tab_id][dom_id] = true; buf->dev_info[tab_id][dom_id].dev = a->dev; buf->dev_info[tab_id][dom_id].direction = dir; buf->dev_info[tab_id][dom_id].map_attrs = a->dma_map_attrs; return 0; } static int check_map_alignment(struct sg_table *table) { int i; struct scatterlist *sgl; for_each_sg(table->sgl, sgl, table->orig_nents, i) { unsigned int len = sgl->length; phys_addr_t s_phys = sg_phys(sgl); if (!IS_ALIGNED(len, SZ_1M)) { pr_err("%s err, size(0x%x) is not 1MB alignment\n", __func__, len); return -EINVAL; } if (!IS_ALIGNED(s_phys, SZ_1M)) { pr_err("%s err, s_phys(%pa) is not 1MB alignment\n", __func__, &s_phys); return -EINVAL; } } return 0; } static struct sg_table *mtk_sec_heap_page_map_dma_buf(struct dma_buf_attachment *attachment, enum dma_data_direction direction) { int ret; struct dma_heap_attachment *a = attachment->priv; struct sg_table *table = a->table; struct dma_buf *dmabuf = attachment->dmabuf; struct mtk_sec_heap_buffer *buffer; struct secure_heap_page *sec_heap; struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(attachment->dev); unsigned int tab_id = MTK_M4U_TAB_NR_MAX, dom_id = MTK_M4U_DOM_NR_MAX; int attr = attachment->dma_map_attrs | DMA_ATTR_SKIP_CPU_SYNC; /* non-iommu master */ if (!fwspec) { ret = dma_map_sgtable(attachment->dev, table, direction, attr); if (ret) { pr_err("%s err, non-iommu-dev(%s) dma_map_sgtable failed\n", __func__, dev_name(attachment->dev)); return ERR_PTR(ret); } a->mapped = true; pr_debug("%s done, non-iommu-dev(%s)\n", __func__, dev_name(attachment->dev)); return table; } buffer = dmabuf->priv; sec_heap = sec_heap_page_get(buffer->heap); if (!sec_heap) { pr_err("%s, dma_sec_heap_get failed\n", __func__); return NULL; } tab_id = MTK_M4U_TO_TAB(fwspec->ids[0]); dom_id = MTK_M4U_TO_DOM(fwspec->ids[0]); mutex_lock(&buffer->map_lock); /* device with iommus attribute and mapped before */ if (buffer->mapped[tab_id][dom_id]) { /* mapped before, return saved table */ ret = copy_sec_sg_table(buffer->mapped_table[tab_id][dom_id], table); if (ret) { pr_err("%s err, copy_sec_sg_table failed, dev:%s\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(-EINVAL); } a->mapped = true; pr_debug("%s done(has mapped), dev:%s(%s), sec_handle:%u, len:0x%lx, iova:0x%lx, id:(%d,%d)\n", __func__, dev_name(buffer->dev_info[tab_id][dom_id].dev), dev_name(attachment->dev), buffer->sec_handle, buffer->len, (unsigned long)sg_dma_address(table->sgl), tab_id, dom_id); mutex_unlock(&buffer->map_lock); return table; } ret = check_map_alignment(table); if (ret) { pr_err("%s err, size or PA is not 1MB alignment, dev:%s\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(ret); } ret = dma_map_sgtable(attachment->dev, table, direction, attr); if (ret) { pr_err("%s err, iommu-dev(%s) dma_map_sgtable failed\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(ret); } ret = fill_sec_buffer_info(buffer, table, attachment, direction, tab_id, dom_id); if (ret) { pr_err("%s failed, fill_sec_buffer_info failed, dev:%s\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(-ENOMEM); } a->mapped = true; pr_debug("%s done, dev:%s, sec_handle:%u, len:0x%lx, iova:0x%lx, id:(%d,%d)\n", __func__, dev_name(attachment->dev), buffer->sec_handle, buffer->len, (unsigned long)sg_dma_address(table->sgl), tab_id, dom_id); mutex_unlock(&buffer->map_lock); return table; } static struct sg_table *mtk_sec_heap_region_map_dma_buf(struct dma_buf_attachment *attachment, enum dma_data_direction direction) { struct dma_heap_attachment *a = attachment->priv; struct sg_table *table = a->table; struct dma_buf *dmabuf = attachment->dmabuf; struct mtk_sec_heap_buffer *buffer; struct secure_heap_region *sec_heap; struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(attachment->dev); unsigned int tab_id = MTK_M4U_TAB_NR_MAX, dom_id = MTK_M4U_DOM_NR_MAX; int ret; dma_addr_t dma_address; uint64_t phy_addr = 0; /* for iommu mapping, should be skip cache sync */ int attr = attachment->dma_map_attrs | DMA_ATTR_SKIP_CPU_SYNC; /* non-iommu master */ if (!fwspec) { ret = dma_map_sgtable(attachment->dev, table, direction, attr); if (ret) { pr_err("%s err, non-iommu-dev(%s) dma_map_sgtable failed\n", __func__, dev_name(attachment->dev)); return ERR_PTR(ret); } a->mapped = true; pr_debug("%s done, non-iommu-dev(%s), pa:0x%lx\n", __func__, dev_name(attachment->dev), (unsigned long)sg_dma_address(table->sgl)); return table; } buffer = dmabuf->priv; sec_heap = sec_heap_region_get(buffer->heap); if (!sec_heap) { pr_err("%s, sec_heap_region_get failed\n", __func__); return NULL; } mutex_lock(&sec_heap->heap_lock); if (!sec_heap->heap_mapped) { ret = check_map_alignment(sec_heap->region_table); if (ret) { pr_err("%s err, heap_region size or PA is not 1MB alignment, dev:%s\n", __func__, dev_name(sec_heap->heap_dev)); mutex_unlock(&sec_heap->heap_lock); return ERR_PTR(ret); } if (dma_map_sgtable(sec_heap->heap_dev, sec_heap->region_table, DMA_BIDIRECTIONAL, DMA_ATTR_SKIP_CPU_SYNC)) { pr_err("%s err, heap_region(%s) dma_map_sgtable failed\n", __func__, dev_name(sec_heap->heap_dev)); mutex_unlock(&sec_heap->heap_lock); return ERR_PTR(-ENOMEM); } sec_heap->heap_mapped = true; pr_debug("%s heap_region map success, heap:%s, iova:0x%lx, sz:0x%lx\n", __func__, dma_heap_get_name(buffer->heap), (unsigned long)sg_dma_address(sec_heap->region_table->sgl), (unsigned long)sg_dma_len(sec_heap->region_table->sgl)); } mutex_unlock(&sec_heap->heap_lock); tab_id = MTK_M4U_TO_TAB(fwspec->ids[0]); dom_id = MTK_M4U_TO_DOM(fwspec->ids[0]); phy_addr = (uint64_t)sg_phys(table->sgl); mutex_lock(&buffer->map_lock); /* device with iommus attribute and mapped before */ if (buffer->mapped[tab_id][dom_id]) { /* mapped before, return saved table */ ret = copy_sec_sg_table(buffer->mapped_table[tab_id][dom_id], table); if (ret) { pr_err("%s err, copy_sec_sg_table failed, dev:%s\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(-EINVAL); } a->mapped = true; pr_debug("%s done(has mapped), dev:%s(%s), sec_handle:%u, len:0x%lx, pa:0x%llx, iova:0x%lx, id:(%d,%d)\n", __func__, dev_name(buffer->dev_info[tab_id][dom_id].dev), dev_name(attachment->dev), buffer->sec_handle, buffer->len, phy_addr, (unsigned long)sg_dma_address(table->sgl), tab_id, dom_id); mutex_unlock(&buffer->map_lock); return table; } if (!dev_is_normal_region(attachment->dev)) { ret = check_map_alignment(table); if (ret) { pr_err("%s err, size or PA is not 1MB alignment, dev:%s\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(ret); } ret = dma_map_sgtable(attachment->dev, table, direction, attr); if (ret) { pr_err("%s err, iommu-dev(%s) dma_map_sgtable failed\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(ret); } pr_debug("%s reserve_iommu-dev(%s) dma_map_sgtable done, iova:0x%lx, id:(%d,%d)\n", __func__, dev_name(attachment->dev), sg_dma_address(table->sgl), tab_id, dom_id); goto map_done; } if (buffer->len <= 0 || buffer->len > sec_heap->region_size || phy_addr < sec_heap->region_pa || phy_addr > sec_heap->region_pa + sec_heap->region_size || phy_addr + buffer->len > sec_heap->region_pa + sec_heap->region_size || phy_addr + buffer->len <= sec_heap->region_pa) { pr_err("%s err. req size/pa is invalid! heap:%s\n", __func__, dma_heap_get_name(buffer->heap)); mutex_unlock(&buffer->map_lock); return ERR_PTR(-ENOMEM); } dma_address = phy_addr - sec_heap->region_pa + sg_dma_address(sec_heap->region_table->sgl); sg_dma_address(table->sgl) = dma_address; sg_dma_len(table->sgl) = buffer->len; map_done: ret = fill_sec_buffer_info(buffer, table, attachment, direction, tab_id, dom_id); if (ret) { pr_err("%s failed, fill_sec_buffer_info failed, dev:%s\n", __func__, dev_name(attachment->dev)); mutex_unlock(&buffer->map_lock); return ERR_PTR(-ENOMEM); } a->mapped = true; pr_debug("%s done, dev:%s, sec_handle:%u, len:0x%lx(0x%x), pa:0x%llx, iova:0x%lx, id:(%d,%d)\n", __func__, dev_name(attachment->dev), buffer->sec_handle, buffer->len, sg_dma_len(table->sgl), phy_addr, (unsigned long)sg_dma_address(table->sgl), tab_id, dom_id); mutex_unlock(&buffer->map_lock); return table; } static void mtk_sec_heap_unmap_dma_buf(struct dma_buf_attachment *attachment, struct sg_table *table, enum dma_data_direction direction) { struct dma_heap_attachment *a = attachment->priv; int attr = attachment->dma_map_attrs | DMA_ATTR_SKIP_CPU_SYNC; struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(attachment->dev); if (!fwspec) { pr_info("%s, non-iommu-dev unmap, dev:%s\n", __func__, dev_name(attachment->dev)); dma_unmap_sgtable(attachment->dev, table, direction, attr); } a->mapped = false; } static bool heap_is_region_base(enum HEAP_BASE_TYPE heap_type) { if (heap_type == REGION_BASE) return true; return false; } static int fill_heap_sgtable(struct secure_heap_region *sec_heap, struct mtk_sec_heap_buffer *buffer) { int ret; if (!heap_is_region_base(sec_heap->heap_type)) { pr_info("%s skip page base filled\n", __func__); return 0; } mutex_lock(&sec_heap->heap_lock); if (sec_heap->heap_filled) { mutex_unlock(&sec_heap->heap_lock); pr_debug("%s, %s already filled\n", __func__, dma_heap_get_name(buffer->heap)); return 0; } ret = trusted_mem_api_get_region_info(sec_heap->tmem_type, &sec_heap->region_pa, &sec_heap->region_size); if (!ret) { mutex_unlock(&sec_heap->heap_lock); pr_err("%s, [%s],get_region_info failed!\n", __func__, dma_heap_get_name(buffer->heap)); return -EINVAL; } sec_heap->region_table = kzalloc(sizeof(*sec_heap->region_table), GFP_KERNEL); if (!sec_heap->region_table) { mutex_unlock(&sec_heap->heap_lock); pr_err("%s, [%s] kzalloc_sgtable failed\n", __func__, dma_heap_get_name(buffer->heap)); return ret; } ret = sg_alloc_table(sec_heap->region_table, 1, GFP_KERNEL); if (ret) { mutex_unlock(&sec_heap->heap_lock); pr_err("%s, [%s] alloc_sgtable failed\n", __func__, dma_heap_get_name(buffer->heap)); return ret; } sg_set_page(sec_heap->region_table->sgl, phys_to_page(sec_heap->region_pa), sec_heap->region_size, 0); sec_heap->heap_filled = true; mutex_unlock(&sec_heap->heap_lock); pr_debug("%s [%s] fill done, region_pa:%pa, region_size:0x%x\n", __func__, dma_heap_get_name(buffer->heap), &sec_heap->region_pa, sec_heap->region_size); return 0; } static int mtk_sec_heap_dma_buf_get_flags(struct dma_buf *dmabuf, unsigned long *flags) { struct mtk_sec_heap_buffer *buffer = dmabuf->priv; *flags = buffer->uncached; return 0; } static const struct dma_buf_ops sec_buf_region_ops = { /* one attachment can only map once */ .cache_sgt_mapping = 1, .attach = mtk_sec_heap_attach, .detach = mtk_sec_heap_detach, .map_dma_buf = mtk_sec_heap_region_map_dma_buf, .unmap_dma_buf = mtk_sec_heap_unmap_dma_buf, .release = tmem_region_free, .get_flags = mtk_sec_heap_dma_buf_get_flags, }; static const struct dma_buf_ops sec_buf_page_ops = { /* one attachment can only map once */ .cache_sgt_mapping = 1, .attach = mtk_sec_heap_attach, .detach = mtk_sec_heap_detach, .map_dma_buf = mtk_sec_heap_page_map_dma_buf, .unmap_dma_buf = mtk_sec_heap_unmap_dma_buf, .release = tmem_page_free, .get_flags = mtk_sec_heap_dma_buf_get_flags, }; static int is_region_base_dmabuf(const struct dma_buf *dmabuf) { return dmabuf && dmabuf->ops == &sec_buf_region_ops; } static int is_page_base_dmabuf(const struct dma_buf *dmabuf) { return dmabuf && dmabuf->ops == &sec_buf_page_ops; } /* region base size is 4K alignment */ static int region_base_alloc(struct secure_heap_region *sec_heap, struct mtk_sec_heap_buffer *buffer, unsigned long req_sz, bool aligned) { int ret; u64 sec_handle = 0; u32 refcount = 0;/* tmem refcount */ u32 alignment = aligned ? SZ_1M : 0; uint64_t phy_addr = 0; struct sg_table *table; if (buffer->len > UINT_MAX) { pr_err("%s error. len more than UINT_MAX\n", __func__); return -EINVAL; } ret = trusted_mem_api_alloc(sec_heap->tmem_type, alignment, (unsigned int *)&buffer->len, &refcount, &sec_handle, (uint8_t *)dma_heap_get_name(buffer->heap), 0, NULL); if (ret == -ENOMEM) { pr_err("%s error: security out of memory!! heap:%s\n", __func__, dma_heap_get_name(buffer->heap)); return -ENOMEM; } if (!sec_handle) { pr_err("%s alloc security memory failed, req_size:0x%lx, total_size 0x%lx\n", __func__, req_sz, atomic64_read(&sec_heap->total_size)); return -ENOMEM; } table = &buffer->sg_table; /* region base PA is continuous, so nent = 1 */ ret = sg_alloc_table(table, 1, GFP_KERNEL); if (ret) { /* free buffer */ pr_err("%s#%d Error. Allocate mem failed.\n", __func__, __LINE__); goto free_buffer; } #if IS_ENABLED(CONFIG_ARM_FFA_TRANSPORT) ret = trusted_mem_ffa_query_pa(&sec_handle, &phy_addr); #else ret = trusted_mem_api_query_pa(sec_heap->tmem_type, 0, buffer->len, &refcount, (u32 *)&sec_handle, (u8 *)dma_heap_get_name(buffer->heap), 0, 0, &phy_addr); #endif if (ret) { /* free buffer */ pr_err("%s#%d Error. query pa failed.\n", __func__, __LINE__); goto free_buffer_struct; } sg_set_page(table->sgl, phys_to_page(phy_addr), buffer->len, 0); /* store seucre handle */ buffer->sec_handle = sec_handle; ret = fill_heap_sgtable(sec_heap, buffer); if (ret) { pr_err("%s#%d Error. fill_heap_sgtable failed.\n", __func__, __LINE__); goto free_buffer_struct; } atomic64_add(buffer->len, &sec_heap->total_size); pr_debug("%s done: [%s], req_size:0x%lx, align_sz:0x%lx, handle:0x%llx, pa:0x%lx, total_sz:0x%lx\n", __func__, dma_heap_get_name(buffer->heap), req_sz, buffer->len, buffer->sec_handle, phy_addr, atomic64_read(&sec_heap->total_size)); return 0; free_buffer_struct: sg_free_table(table); free_buffer: /* free secure handle */ trusted_mem_api_unref(sec_heap->tmem_type, sec_handle, (uint8_t *)dma_heap_get_name(buffer->heap), 0, NULL); return ret; } static int page_base_alloc(struct secure_heap_page *sec_heap, struct mtk_sec_heap_buffer *buffer, unsigned long req_sz) { int ret; u64 sec_handle = 0; u32 refcount = 0;/* tmem refcount */ struct ssheap_buf_info *ssheap = NULL; struct sg_table *table = &buffer->sg_table; if (buffer->len > UINT_MAX) { pr_err("%s error. len more than UINT_MAX\n", __func__); return -EINVAL; } ret = trusted_mem_api_alloc(sec_heap->tmem_type, 0, (unsigned int *)&buffer->len, &refcount, &sec_handle, (uint8_t *)dma_heap_get_name(sec_heap->heap), 0, &ssheap); if (!ssheap) { pr_err("%s error, alloc page base failed\n", __func__); return -ENOMEM; } ret = sg_alloc_table(table, ssheap->table->orig_nents, GFP_KERNEL); if (ret) { pr_err("%s error. sg_alloc_table failed\n", __func__); goto free_tmem; } ret = copy_sec_sg_table(ssheap->table, table); if (ret) { pr_err("%s error. copy_sec_sg_table failed\n", __func__); goto free_table; } buffer->len = ssheap->aligned_req_size; buffer->ssheap = ssheap; atomic64_add(buffer->len, &sec_heap->total_size); pr_debug("%s done: [%s], req_size:0x%lx(0x%lx), align_sz:0x%lx, nent:%u--%lu, align:0x%lx, total_sz:0x%lx\n", __func__, dma_heap_get_name(sec_heap->heap), buffer->ssheap->req_size, req_sz, buffer->len, buffer->ssheap->table->orig_nents, buffer->ssheap->elems, buffer->ssheap->alignment, atomic64_read(&sec_heap->total_size)); return 0; free_table: sg_free_table(table); free_tmem: trusted_mem_api_unref(sec_heap->tmem_type, sec_handle, (uint8_t *)dma_heap_get_name(buffer->heap), 0, ssheap); return ret; } static void init_buffer_info(struct dma_heap *heap, struct mtk_sec_heap_buffer *buffer) { struct task_struct *task = current->group_leader; INIT_LIST_HEAD(&buffer->attachments); mutex_init(&buffer->lock); mutex_init(&buffer->map_lock); /* add alloc pid & tid info */ get_task_comm(buffer->pid_name, task); get_task_comm(buffer->tid_name, current); buffer->pid = task_pid_nr(task); buffer->tid = task_pid_nr(current); buffer->ts = sched_clock() / 1000; } static struct dma_buf *alloc_dmabuf(struct dma_heap *heap, struct mtk_sec_heap_buffer *buffer, const struct dma_buf_ops *ops, unsigned long fd_flags) { DEFINE_DMA_BUF_EXPORT_INFO(exp_info); /* create the dmabuf */ exp_info.exp_name = dma_heap_get_name(heap); exp_info.ops = ops; exp_info.size = buffer->len; exp_info.flags = fd_flags; exp_info.priv = buffer; return dma_buf_export(&exp_info); } static struct dma_buf *tmem_page_allocate(struct dma_heap *heap, unsigned long len, unsigned long fd_flags, unsigned long heap_flags) { int ret = -ENOMEM; struct dma_buf *dmabuf; struct mtk_sec_heap_buffer *buffer; struct secure_heap_page *sec_heap = sec_heap_page_get(heap); if (!sec_heap) { pr_err("%s, can not find secure heap!!\n", __func__, heap ? dma_heap_get_name(heap) : "null ptr"); return ERR_PTR(-EINVAL); } buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); if (!buffer) return ERR_PTR(-ENOMEM); buffer->len = len; buffer->heap = heap; /* all page base memory set as noncached buffer */ buffer->uncached = true; buffer->show = sec_buf_priv_dump; ret = page_base_alloc(sec_heap, buffer, len); if (ret) goto free_buffer; dmabuf = alloc_dmabuf(heap, buffer, &sec_buf_page_ops, fd_flags); if (IS_ERR(dmabuf)) { ret = PTR_ERR(dmabuf); pr_err("%s alloc_dmabuf fail\n", __func__); goto free_tmem; } init_buffer_info(heap, buffer); return dmabuf; free_tmem: trusted_mem_api_unref(sec_heap->tmem_type, buffer->sec_handle, (uint8_t *)dma_heap_get_name(buffer->heap), 0, buffer->ssheap); sg_free_table(&buffer->sg_table); free_buffer: kfree(buffer); return ERR_PTR(ret); } static struct dma_buf *tmem_region_allocate(struct dma_heap *heap, unsigned long len, unsigned long fd_flags, unsigned long heap_flags) { int ret = -ENOMEM; struct dma_buf *dmabuf; struct mtk_sec_heap_buffer *buffer; bool aligned = region_heap_is_aligned(heap); struct secure_heap_region *sec_heap = sec_heap_region_get(heap); if (!sec_heap) { pr_err("%s, can not find secure heap(%s)!!\n", __func__, (heap ? dma_heap_get_name(heap) : "null ptr")); return ERR_PTR(-EINVAL); } buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); if (!buffer) return ERR_PTR(-ENOMEM); buffer->len = len; buffer->heap = heap; buffer->show = sec_buf_priv_dump; ret = region_base_alloc(sec_heap, buffer, len, aligned); if (ret) goto free_buffer; dmabuf = alloc_dmabuf(heap, buffer, &sec_buf_region_ops, fd_flags); if (IS_ERR(dmabuf)) { ret = PTR_ERR(dmabuf); pr_err("%s alloc_dmabuf fail\n", __func__); goto free_tmem; } init_buffer_info(heap, buffer); return dmabuf; free_tmem: trusted_mem_api_unref(sec_heap->tmem_type, buffer->sec_handle, (uint8_t *)dma_heap_get_name(buffer->heap), 0, NULL); sg_free_table(&buffer->sg_table); free_buffer: kfree(buffer); return ERR_PTR(ret); } static const struct dma_heap_ops sec_heap_page_ops = { .allocate = tmem_page_allocate, }; static const struct dma_heap_ops sec_heap_region_ops = { .allocate = tmem_region_allocate, }; static int sec_buf_priv_dump(const struct dma_buf *dmabuf, struct seq_file *s) { unsigned int i = 0, j = 0; dma_addr_t iova = 0; int region_buf = 0; struct mtk_sec_heap_buffer *buf = dmabuf->priv; u64 sec_handle = 0; dmabuf_dump(s, "\t\tbuf_priv: uncached:%d alloc_pid:%d(%s)tid:%d(%s) alloc_time:%luus\n", !!buf->uncached, buf->pid, buf->pid_name, buf->tid, buf->tid_name, buf->ts); /* region base, only has secure handle */ if (is_page_base_dmabuf(dmabuf)) { region_buf = 0; } else if (is_region_base_dmabuf(dmabuf)) { region_buf = 1; } else { WARN_ON(1); return 0; } for (i = 0; i < MTK_M4U_TAB_NR_MAX; i++) { for (j = 0; j < MTK_M4U_DOM_NR_MAX; j++) { bool mapped = buf->mapped[i][j]; struct device *dev = buf->dev_info[i][j].dev; struct sg_table *sgt = buf->mapped_table[i][j]; char tmp_str[40]; int len = 0; if (!sgt || !sgt->sgl || !dev || !dev_iommu_fwspec_get(dev)) continue; iova = sg_dma_address(sgt->sgl); if (region_buf) { sec_handle = (dma_addr_t)dmabuf_to_secure_handle(dmabuf); len = scnprintf(tmp_str, 39, "sec_handle:0x%x", sec_handle); if (len >= 0) tmp_str[len] = '\0';/* No need memset */ } dmabuf_dump(s, "\t\tbuf_priv: tab:%-2u dom:%-2u map:%d iova:0x%-12lx %s attr:0x%-4lx dir:%-2d dev:%s\n", i, j, mapped, iova, region_buf ? tmp_str : "", buf->dev_info[i][j].map_attrs, buf->dev_info[i][j].direction, dev_name(buf->dev_info[i][j].dev)); } } return 0; } /** * return none-zero value means dump fail. * maybe the input dmabuf isn't this heap buffer, no need dump * * return 0 means dump pass */ static int sec_heap_buf_priv_dump(const struct dma_buf *dmabuf, struct dma_heap *heap, void *priv) { struct mtk_sec_heap_buffer *buf = dmabuf->priv; struct seq_file *s = priv; if (!is_mtk_sec_heap_dmabuf(dmabuf) || heap != buf->heap) return -EINVAL; if (buf->show) return buf->show(dmabuf, s); return -EINVAL; } static struct mtk_heap_priv_info mtk_sec_heap_priv = { .buf_priv_dump = sec_heap_buf_priv_dump, }; int is_mtk_sec_heap_dmabuf(const struct dma_buf *dmabuf) { if (IS_ERR_OR_NULL(dmabuf)) return 0; if (dmabuf->ops == &sec_buf_page_ops || dmabuf->ops == &sec_buf_region_ops) return 1; return 0; } EXPORT_SYMBOL_GPL(is_mtk_sec_heap_dmabuf); int is_support_secure_handle(const struct dma_buf *dmabuf) { if (IS_ERR_OR_NULL(dmabuf)) return 0; return is_region_base_dmabuf(dmabuf); } EXPORT_SYMBOL_GPL(is_support_secure_handle); /* return 0 means error */ u64 dmabuf_to_secure_handle(const struct dma_buf *dmabuf) { int heap_base; struct mtk_sec_heap_buffer *buffer; if (!is_mtk_sec_heap_dmabuf(dmabuf)) { pr_err("%s err, dmabuf is not secure\n", __func__); return 0; } buffer = dmabuf->priv; heap_base = get_heap_base_type(buffer->heap); if (heap_base != REGION_BASE) { pr_warn("%s failed, heap(%s) not support sec_handle\n", __func__, dma_heap_get_name(buffer->heap)); return 0; } return buffer->sec_handle; } EXPORT_SYMBOL_GPL(dmabuf_to_secure_handle); /* * NOTE: the range of heap_id is (s, e), not [s, e] or [s, e) */ static int mtk_region_heap_create(struct device *dev, enum sec_heap_region_type s, enum sec_heap_region_type e) { struct dma_heap_export_info exp_info; int i, j, ret; /* region base & page base use same heap show */ exp_info.priv = (void *)&mtk_sec_heap_priv; /* No need pagepool for secure heap */ exp_info.ops = &sec_heap_region_ops; for (i = (s + 1); i < e; i++) { for (j = REGION_HEAP_NORMAL; j < REGION_TYPE_NUM; j++) { exp_info.name = mtk_sec_heap_region[i].heap_name[j]; mtk_sec_heap_region[i].heap[j] = dma_heap_add(&exp_info); if (IS_ERR(mtk_sec_heap_region[i].heap[j])) return PTR_ERR(mtk_sec_heap_region[i].heap[j]); mtk_sec_heap_region[i].heap_dev = dev; ret = dma_set_mask_and_coherent(mtk_sec_heap_region[i].heap_dev, DMA_BIT_MASK(34)); if (ret) { dev_info(dev, "dma_set_mask_and_coherent failed: %d\n", ret); return ret; } mutex_init(&mtk_sec_heap_region[i].heap_lock); pr_info("%s add heap[%s][%d] dev:%s, success\n", __func__, exp_info.name, mtk_sec_heap_region[i].tmem_type, dev_name(mtk_sec_heap_region[i].heap_dev)); } } return 0; } static int apu_region_heap_probe(struct platform_device *pdev) { int ret; ret = mtk_region_heap_create(&pdev->dev, APU_HEAP_START, APU_HEAP_END); if (ret) pr_err("%s failed\n", __func__); return ret; } static int mm_region_heap_probe(struct platform_device *pdev) { int ret; ret = mtk_region_heap_create(&pdev->dev, MM_HEAP_START, MM_HEAP_END); if (ret) pr_err("%s failed\n", __func__); return ret; } static const struct of_device_id mm_region_match_table[] = { {.compatible = "mediatek,dmaheap-region-base"}, {}, }; static const struct of_device_id apu_region_match_table[] = { {.compatible = "mediatek,dmaheap-apu-region-base"}, {}, }; static struct platform_driver mm_region_base = { .probe = mm_region_heap_probe, .driver = { .name = "mm-region-base", .of_match_table = mm_region_match_table, }, }; static struct platform_driver apu_region_base = { .probe = apu_region_heap_probe, .driver = { .name = "apu-region-base", .of_match_table = apu_region_match_table, }, }; static struct platform_driver *const mtk_region_heap_drivers[] = { &mm_region_base, &apu_region_base, }; static int mtk_page_heap_create(void) { struct dma_heap_export_info exp_info; int i; /* No need pagepool for secure heap */ exp_info.ops = &sec_heap_page_ops; for (i = SVP_PAGE; i < PAGE_HEAPS_NUM; i++) { /* param check */ if (mtk_sec_heap_page[i].heap_type != PAGE_BASE) { pr_info("invalid heap param, %s, %d\n", mtk_sec_heap_page[i].heap_name, mtk_sec_heap_page[i].heap_type); continue; } exp_info.name = mtk_sec_heap_page[i].heap_name; mtk_sec_heap_page[i].heap = dma_heap_add(&exp_info); if (IS_ERR(mtk_sec_heap_page[i].heap)) { pr_err("%s error, dma_heap_add failed, heap:%s\n", __func__, mtk_sec_heap_page[i].heap_name); return PTR_ERR(mtk_sec_heap_page[i].heap); } pr_info("%s add heap[%s][%d] success\n", __func__, exp_info.name, mtk_sec_heap_page[i].tmem_type); } return 0; } static int __init mtk_sec_heap_init(void) { int ret; int i; pr_info("%s+\n", __func__); ret = mtk_page_heap_create(); if (ret) { pr_err("page_base_heap_create failed\n"); return ret; } for (i = 0; i < ARRAY_SIZE(mtk_region_heap_drivers); i++) { pr_info("%s, register %d\n", __func__, i); ret = platform_driver_register(mtk_region_heap_drivers[i]); if (ret < 0) { pr_err("Failed to register %s driver: %d\n", mtk_region_heap_drivers[i]->driver.name, ret); goto err; } } pr_info("%s-\n", __func__); return 0; err: while (--i >= 0) platform_driver_unregister(mtk_region_heap_drivers[i]); return ret; } static void __exit mtk_sec_heap_exit(void) { int i; for (i = ARRAY_SIZE(mtk_region_heap_drivers) - 1; i >= 0; i--) platform_driver_unregister(mtk_region_heap_drivers[i]); } MODULE_SOFTDEP("pre: apusys"); module_init(mtk_sec_heap_init); module_exit(mtk_sec_heap_exit); MODULE_LICENSE("GPL v2");