/* * Copyright (c) 2012-2022 Huawei Technologies Co., Ltd. * Description: dynamic Ion memory allocation and free. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "dynamic_ion_mem.h" #include #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifndef CONFIG_DMABUF_MM #include #endif #include #include #include #include #if ((defined CONFIG_ION_MM) || (defined CONFIG_ION_MM_SECSG)) #include #endif #ifdef CONFIG_DMABUF_MM #include #endif #include "tc_ns_log.h" #include "tc_ns_client.h" #include "smc_smp.h" #include "gp_ops.h" #include "teek_client_constants.h" #include "mailbox_mempool.h" #include "dynamic_ion_uuid.h" static DEFINE_MUTEX(dynamic_mem_lock); struct dynamic_mem_list { struct list_head list; }; static const struct dynamic_mem_config g_dyn_mem_config[] = { #ifdef DEF_ENG {TEE_SERVICE_UT, SEC_EID}, {TEE_SERVICE_TEST_DYNION, SEC_AI_ION}, #endif {TEE_SECIDENTIFICATION1, SEC_EID}, {TEE_SECIDENTIFICATION3, SEC_EID}, {TEE_SERVICE_AI, SEC_AI_ION}, {TEE_SERVICE_AI_TINY, SEC_AI_ION}, {TEE_SERVICE_VCODEC, SEC_DRM_TEE}, }; static struct dynamic_mem_list g_dynamic_mem_list; static const uint32_t g_dyn_mem_config_num = ARRAY_SIZE(g_dyn_mem_config); static int release_ion_srv(const struct tc_uuid *uuid) { struct tc_ns_smc_cmd smc_cmd = {{0}, 0}; smc_cmd.err_origin = TEEC_ORIGIN_COMMS; smc_cmd.cmd_type = CMD_TYPE_GLOBAL; smc_cmd.cmd_id = GLOBAL_CMD_ID_RELEASE_ION_SRV; if (memcpy_s(&smc_cmd.uuid, sizeof(smc_cmd.uuid), uuid, sizeof(*uuid))) { tloge("copy uuid failed\n"); return -ENOMEM; } if (tc_ns_smc(&smc_cmd)) { tloge("send release ion srv cmd failed\n"); return -EPERM; } return 0; } static int get_ion_sglist(struct dynamic_mem_item *mem_item) { struct sglist *tmp_sglist = NULL; struct scatterlist *sg = NULL; struct page *page = NULL; uint32_t sglist_size; uint32_t i = 0; struct sg_table *ion_sg_table = mem_item->memory.dyn_sg_table; if (!ion_sg_table) return -EINVAL; if (ion_sg_table->nents <= 0 || ion_sg_table->nents > MAX_ION_NENTS) return -EINVAL; for_each_sg(ion_sg_table->sgl, sg, ion_sg_table->nents, i) { if (!sg) { tloge("an error sg when get ion sglist\n"); return -EINVAL; } } sglist_size = sizeof(struct ion_page_info) * ion_sg_table->nents + sizeof(*tmp_sglist); tmp_sglist = (struct sglist *)mailbox_alloc(sglist_size, MB_FLAG_ZERO); if (!tmp_sglist) { tloge("mailbox alloc failed\n"); return -ENOMEM; } tmp_sglist->sglist_size = (uint64_t)sglist_size; tmp_sglist->ion_size = (uint64_t)mem_item->size; tmp_sglist->info_length = (uint64_t)ion_sg_table->nents; for_each_sg(ion_sg_table->sgl, sg, ion_sg_table->nents, i) { page = sg_page(sg); tmp_sglist->page_info[i].phys_addr = page_to_phys(page); tmp_sglist->page_info[i].npages = sg->length / PAGE_SIZE; } mem_item->memory.ion_phys_addr = mailbox_virt_to_phys((uintptr_t)(void *)tmp_sglist); mem_item->memory.len = sglist_size; return 0; } static int send_dyn_ion_cmd(struct dynamic_mem_item *mem_item, unsigned int cmd_id, int32_t *ret_origin) { struct tc_ns_smc_cmd smc_cmd = {{0}, 0}; int ret; struct mb_cmd_pack *mb_pack = NULL; if (!mem_item) { tloge("mem_item is null\n"); return -EINVAL; } ret = get_ion_sglist(mem_item); if (ret != 0) return ret; mb_pack = mailbox_alloc_cmd_pack(); if (!mb_pack) { mailbox_free(phys_to_virt(mem_item->memory.ion_phys_addr)); tloge("alloc cmd pack failed\n"); return -ENOMEM; } smc_cmd.cmd_type = CMD_TYPE_GLOBAL; smc_cmd.cmd_id = cmd_id; smc_cmd.err_origin = TEEC_ORIGIN_COMMS; mb_pack->operation.paramtypes = teec_param_types( TEE_PARAM_TYPE_ION_SGLIST_INPUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_NONE); mb_pack->operation.params[0].memref.size = (uint32_t)mem_item->memory.len; mb_pack->operation.params[0].memref.buffer = (uint32_t)(mem_item->memory.ion_phys_addr & 0xFFFFFFFF); mb_pack->operation.buffer_h_addr[0] = (uint64_t)(mem_item->memory.ion_phys_addr) >> ADDR_TRANS_NUM; mb_pack->operation.params[1].value.a = (uint32_t)mem_item->size; mb_pack->operation.params[2].value.a = mem_item->configid; smc_cmd.operation_phys = (unsigned int)mailbox_virt_to_phys((uintptr_t)&mb_pack->operation); smc_cmd.operation_h_phys = (uint64_t)mailbox_virt_to_phys((uintptr_t)&mb_pack->operation) >> ADDR_TRANS_NUM; if (tc_ns_smc(&smc_cmd)) { if (ret_origin) *ret_origin = smc_cmd.err_origin; ret = -EPERM; tlogd("send loadapp ion failed\n"); } mailbox_free(phys_to_virt(mem_item->memory.ion_phys_addr)); mailbox_free(mb_pack); return ret; } static struct dynamic_mem_item *find_memitem_by_configid_locked(uint32_t configid) { struct dynamic_mem_item *item = NULL; list_for_each_entry(item, &g_dynamic_mem_list.list, head) { if (item->configid == configid) return item; } return NULL; } static struct dynamic_mem_item *find_memitem_by_uuid_locked(const struct tc_uuid *uuid) { struct dynamic_mem_item *item = NULL; list_for_each_entry(item, &g_dynamic_mem_list.list, head) { if (!memcmp(&item->uuid, uuid, sizeof(*uuid))) return item; } return NULL; } #define BLOCK_64KB_SIZE (64 * 1024) /* 64 */ #define BLOCK_64KB_MASK 0xFFFFFFFFFFFF0000 /* size should be aligned with 64KB */ #define BLOCK_64KB_SIZE_MASK (BLOCK_64KB_SIZE -1) static int proc_alloc_dyn_mem(struct dynamic_mem_item *mem_item) { struct sg_table *ion_sg_table = NULL; if (mem_item->size + BLOCK_64KB_SIZE_MASK < mem_item->size) { tloge("ion size is error, size = %x\n", mem_item->size); return -EINVAL; } mem_item->memory.len = (mem_item ->size + BLOCK_64KB_SIZE_MASK) & BLOCK_64KB_MASK; ion_sg_table = mm_secmem_alloc(mem_item->addr_sec_region, mem_item->memory.len); if (!ion_sg_table) { tloge("failed to get ion page, configid = %d\n", mem_item->configid); return -ENOMEM; } mem_item->memory.dyn_sg_table = ion_sg_table; return 0; } static void proc_free_dyn_mem(struct dynamic_mem_item *mem_item) { if (!mem_item->memory.dyn_sg_table) { tloge("ion_phys_addr is NULL\n"); return; } mm_secmem_free(mem_item->ddr_sec_region, mem_item->memory.dyn_sg_table); mem_item->memory.dyn_sg_table = NULL; return; } int init_dynamic_mem(void) { INIT_LIST_HEAD(&(g_dynamic_mem_list.list)); return 0; } static int32_t find_ddr_sec_region_by_uuid(const struct tc_uuid *uuid, uint32_t *ddr_sec_region) { uint32_t i; for (i = 0; i < g_dyn_mem_config_num; i++) { if (!memcmp(&(g_dyn_mem_config[i].uuid), uuid, sizeof(*uuid))) { *ddr_sec_region = g_dyn_mem_config[i].ddr_sec_region; return 0; } } return -EINVAL; } static struct dynamic_mem_item *alloc_dyn_mem_item(uint32_t configid, uint32_t cafd, const struct tc_uuid *uuid, uint32_t size) { uint32_t ddr_sec_region; struct dynamic_mem_item *mem_item = NULL; int32_t result; result = find_ddr_sec_region_by_uuid(uuid, &ddr_sec_region); if (result != 0) { tloge("find ddr sec region failed\n"); return NULL; } mem_item = kzalloc(sizeof(*mem_item), GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)mem_item)) { tloge("alloc mem item failed\n"); return NULL; } mem_item->ddr_sec_region = ddr_sec_region; mem_item->configid = configid; mem_item->size = size; mem_item->cafd = cafd; result = memcpy_s(&mem_item->uuid, sizeof(mem_item->uuid), uuid, sizeof(*uuid)); if(result != EOK) { tloge("memcpy uuid failed\n"); kfree(mem_item); return NULL; } return mem_item; } static int trans_configid2memid(uint32_t configid, uint32_t cafd, const struct tc_uuid *uuid, uint32_t size, int32_t *ret_origin) { int result; if (!uuid) return -EINVAL; mutex_lock(&dynamic_mem_lock); do { struct dynamic_mem_item *mem_item = find_memitem_by_configid_locked(configid); if (mem_item) { result = -EINVAL; break; } mem_item = alloc_dyn_mem_item(configid, cafd, uuid, size); if (!mem_item) { tloge("alloc dyn mem item failed\n"); result = -ENOMEM; break; } result = proc_alloc_dyn_mem(mem_item); if (result != 0) { tloge("alloc dyn mem failed , ret = %d\n", result); kfree(mem_item); break; } /* register to tee */ result = send_dyn_ion_cmd(mem_item, GLOBAL_CMD_ID_ADD_DYNAMIC_ION, ret_origin); if (result != 0) { tloge("register to tee failed, result = %d\n", result); proc_free_dyn_mem(mem_item); kfree(mem_item); break; } list_add_tail(&mem_item->head, &g_dynamic_mem_list.list); tloge("log import:alloc ion configid=%d\n", mem_item->configid); } while (0); mutex_unlock(&dynamic_mem_lock); return result; } static void release_configid_mem_locked(uint32_t configid) { int result; /* if config id is memid map, and can reuse */ do { struct dynamic_mem_item *mem_item = find_memitem_by_configid_locked(configid); if (!mem_item) { tloge("fail to find memitem by configid\n"); break; } result = send_dyn_ion_cmd(mem_item, GLOBAL_CMD_ID_DEL_DYNAMIC_ION, NULL); if (result != 0) { tloge("unregister_from_tee configid=%d, result =%d\n", mem_item->configid, result); break; } proc_free_dyn_mem(mem_item); list_del(&mem_item->head); kfree(mem_item); tloge("log import: free ion\n"); } while (0); return; } int load_app_use_configid(uint32_t configid, uint32_t cafd, const struct tc_uuid *uuid, uint32_t size, int32_t *ret_origin) { int result; if (!uuid) return -EINVAL; result = trans_configid2memid(configid, cafd, uuid, size, ret_origin); if (result != 0) { tloge("trans_configid2memid failed ret = %d\n", result); if (release_ion_srv(uuid) != 0) tloge("release ion srv failed\n"); } return result; } void kill_ion_by_uuid(const struct tc_uuid *uuid) { if (!uuid) { tloge("uuid is null\n"); return; } mutex_lock(&dynamic_mem_lock); do { struct dynamic_mem_item *mem_item = find_memitem_by_uuid_locked(uuid); if (!mem_item) break; tlogd("kill ION by UUID\n"); release_configid_mem_locked(mem_item->configid); } while (0); mutex_unlock(&dynamic_mem_lock); } void kill_ion_by_cafd(unsigned int cafd) { struct dynamic_mem_item *item = NULL; struct dynamic_mem_item *temp = NULL; tlogd("kill_ion_by_cafd:\n"); mutex_lock(&dynamic_mem_lock); list_for_each_entry_safe(item, temp, &g_dynamic_mem_list.list, head) { if (item->cafd == cafd) release_configid_mem_locked(item->configid); } mutex_unlock(&dynamic_mem_lock); } int load_image_for_ion(const struct load_img_params *params, int32_t *ret_origin) { int ret = 0; if (!params) return -EFAULT; /* check need to add ionmem */ uint32_t configid = params->mb_pack->operation.params[1].value.a; uint32_t ion_size = params->mb_pack->operation.params[1].value.b; int32_t check_result = (configid != 0 && ion_size != 0); tloge("check load result=%d, cfgid=%d, ion_size=%d, uuid=%x\n", check_result, configid, ion_size, params->uuid_return->time_low); if (check_result) { ret = load_app_use_configid(configid, params->dev_file->dev_file_id, params->uuid_return, ion_size, ret_origin); if (ret != 0) { tloge("load app use configid failed ret=%d\n", ret); return -EFAULT; } } return ret; } bool is_ion_param(uint32_t param_type) { if (param_type == TEEC_ION_INPUT || param_type == TEEC_ION_SGLIST_INPUT) return true; return false; } static void fill_sg_list(struct sg_table *ion_table, uint32_t ion_list_num, struct sglist *tmp_sglist) { uint32_t i; struct page *page = NULL; struct scatterlist *sg = NULL; for_each_sg(ion_table->sgl, sg, ion_list_num, i) { page = sg_page(sg); tmp_sglist->page_info[i].phys_addr = page_to_phys(page); tmp_sglist->page_info[i].npages = sg->length / PAGE_SIZE; } } static int check_sg_list(const struct sg_table *ion_table, uint32_t ion_list_num) { struct scatterlist *sg = NULL; uint32_t i; for_each_sg(ion_table->sgl, sg, ion_list_num, i) { if (!sg) { tloge("an error sg when get ion sglist \n"); return -EFAULT; } } return 0; } static int get_ion_sg_list_from_fd(uint32_t ion_shared_fd, uint32_t ion_alloc_size, phys_addr_t *sglist_table, size_t *ion_sglist_size) { struct sg_table *ion_table = NULL; struct sglist *tmp_sglist = NULL; uint64_t ion_id = 0; enum SEC_SVC ion_type = 0; uint32_t ion_list_num = 0; uint32_t sglist_size; #ifdef CONFIG_DMABUF_MM if (mm_dma_heap_secmem_get_buffer(ion_shared_fd, &ion_table, &ion_id, &ion_type)) { #else if (secmem_get_buffer(ion_shared_fd, &ion_table, &ion_id, &ion_type)) { #endif tloge("get ion table failed. \n"); return -EFAULT; } if (ion_type != SEC_DRM_TEE) { if (ion_table->nents <= 0 || ion_table->nents > MAX_ION_NENTS) return -EFAULT; ion_list_num = (uint32_t)(ion_table->nents & INT_MAX); if (check_sg_list(ion_table, ion_list_num) != 0) return -EFAULT; } /* ion_list_num is less than 1024, so sglist_size won't flow */ sglist_size = sizeof(struct ion_page_info) * ion_list_num + sizeof(*tmp_sglist); tmp_sglist = (struct sglist *)mailbox_alloc(sglist_size, MB_FLAG_ZERO); if (!tmp_sglist) { tloge("sglist mem alloc failed\n"); return -ENOMEM; } tmp_sglist->sglist_size = (uint64_t)sglist_size; tmp_sglist->ion_size = (uint64_t)ion_alloc_size; tmp_sglist->info_length = (uint64_t)ion_list_num; if (ion_type != SEC_DRM_TEE) fill_sg_list(ion_table, ion_list_num, tmp_sglist); else tmp_sglist->ion_id = ion_id; *sglist_table = mailbox_virt_to_phys((uintptr_t)tmp_sglist); *ion_sglist_size = sglist_size; return 0; } int alloc_for_ion_sglist(const struct tc_call_params *call_params, struct tc_op_params *op_params, uint8_t kernel_params, uint32_t param_type, unsigned int index) { struct tc_ns_operation *operation = NULL; size_t ion_sglist_size = 0; phys_addr_t ion_sglist_addr = 0x0; union tc_ns_client_param *client_param = NULL; unsigned int ion_shared_fd = 0; unsigned int ion_alloc_size; uint64_t a_addr, b_addr; /* this never happens */ if (index >= TEE_PARAM_NUM || !call_params || !op_params) return -EINVAL; operation = &op_params->mb_pack->operation; client_param = &(call_params->context->params[index]); a_addr = client_param->value.a_addr | ((uint64_t)client_param->value.a_h_addr << ADDR_TRANS_NUM); b_addr = client_param->value.b_addr | ((uint64_t)client_param->value.b_h_addr << ADDR_TRANS_NUM); if (read_from_client(&operation->params[index].value.a, sizeof(operation->params[index].value.a), (void *)(uintptr_t)a_addr, sizeof(operation->params[index].value.a), kernel_params)) { tloge("valuea copy failed\n"); return -EFAULT; } if (read_from_client(&operation->params[index].value.b, sizeof(operation->params[index].value.b), (void *)(uintptr_t)b_addr, sizeof(operation->params[index].value.b), kernel_params)) { tloge("valueb copy failed\n"); return -EFAULT; } ion_shared_fd = operation->params[index].value.a; ion_alloc_size = operation->params[index].value.b; if(get_ion_sg_list_from_fd(ion_shared_fd, ion_alloc_size, &ion_sglist_addr, &ion_sglist_size)) { tloge("get ion sglist failed, fd=%u\n", ion_shared_fd); return -EFAULT; } op_params->local_tmpbuf[index].temp_buffer = phys_to_virt(ion_sglist_addr); op_params->local_tmpbuf[index].size = ion_sglist_size; operation->params[index].memref.buffer = (unsigned int)ion_sglist_addr; operation->buffer_h_addr[index] = (uint64_t)ion_sglist_addr >> ADDR_TRANS_NUM; operation->params[index].memref.size = (unsigned int)ion_sglist_size; op_params->trans_paramtype[index] = param_type; return 0; } static int transfer_ion_params(struct tc_ns_operation *operation, union tc_ns_client_param *client_param, uint8_t kernel_params, unsigned int index) { uint64_t a_addr = client_param->value.a_addr | ((uint64_t)client_param->value.a_h_addr << ADDR_TRANS_NUM); uint64_t b_addr = client_param->value.b_addr | ((uint64_t)client_param->value.b_h_addr << ADDR_TRANS_NUM); if (read_from_client(&operation->params[index].value.a, sizeof(operation->params[index].value.a), (void *)(uintptr_t)a_addr, sizeof(operation->params[index].value.a), kernel_params)) { tloge("value.a_addr copy failed\n"); return -EFAULT; } if (read_from_client(&operation->params[index].value.b, sizeof(operation->params[index].value.b), (void *)(uintptr_t)b_addr, sizeof(operation->params[index].value.b), kernel_params)) { tloge("value.b_addr copy failed\n"); return -EFAULT; } return 0; } int alloc_for_ion(const struct tc_call_params *call_params, struct tc_op_params *op_params, uint8_t kernel_params, uint32_t param_type, unsigned int index) { struct tc_ns_operation *operation = NULL; size_t drm_ion_size = 0; phys_addr_t drm_ion_phys = 0x0; struct dma_buf *drm_dma_buf = NULL; union tc_ns_client_param *client_param = NULL; unsigned int ion_shared_fd = 0; int ret = 0; /* this never happens */ if (index >= TEE_PARAM_NUM || !call_params || !op_params) return -EINVAL; operation = &op_params->mb_pack->operation; client_param = &(call_params->context->params[index]); if (transfer_ion_params(operation, client_param, kernel_params, index)) return -EFAULT; ion_shared_fd = operation->params[index].value.a; drm_dma_buf = dma_buf_get(ion_shared_fd); if (IS_ERR_OR_NULL(drm_dma_buf)) { tloge("drm dma buf is err, ret = %d fd = %u\n", ret, ion_shared_fd); return -EFAULT; } #ifdef CONFIG_DMABUF_MM ret = mm_dma_heap_secmem_get_phys(drm_dma_buf, &drm_ion_phys, &drm_ion_size); #else ret = ion_secmem_get_phys(drm_dma_buf, &drm_ion_phys, &drm_ion_size); #endif if (ret != 0) { tloge("in %s err:ret=%d fd=%u\n", __func__, ret, ion_shared_fd); dma_buf_put(drm_dma_buf); return -EFAULT; } if (drm_ion_size > operation->params[index].value.b) drm_ion_size = operation->params[index].value.b; operation->params[index].value.a = (unsigned int)drm_ion_phys; operation->params[index].value.b = (unsigned int)drm_ion_size; op_params->trans_paramtype[index] = param_type; dma_buf_put(drm_dma_buf); return ret; }