// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include "cmdq-util.h" #include "cmdq-sec.h" #include "cmdq-sec-mailbox.h" #define ADDR_METADATA_MAX_COUNT_ORIGIN (8) #define CMDQ_IMMEDIATE_VALUE (0) #define CMDQ_REG_TYPE (1) #define CMDQ_PREDUMP_TIMEOUT_MS 200 static s32 cmdq_sec_realloc_addr_list(struct cmdq_pkt *pkt, const u32 count) { struct cmdq_sec_data *sec_data = (struct cmdq_sec_data *)pkt->sec_data; void *prev = (void *)(unsigned long)sec_data->addrMetadatas, *curr; if (count <= sec_data->addrMetadataMaxCount) return 0; curr = kcalloc(count, sizeof(*sec_data), GFP_KERNEL); if (!curr) return -ENOMEM; if (count && sec_data->addrMetadatas) memcpy(curr, prev, sizeof(*sec_data) * sec_data->addrMetadataMaxCount); kfree(prev); sec_data->addrMetadatas = (u64)curr; sec_data->addrMetadataMaxCount = count; return 0; } static s32 cmdq_sec_check_sec(struct cmdq_pkt *pkt) { struct cmdq_sec_data *sec_data; if (pkt->sec_data) return 0; sec_data = kzalloc(sizeof(*sec_data), GFP_KERNEL); if (!sec_data) return -ENOMEM; pkt->sec_data = (void *)sec_data; return 0; } static s32 cmdq_sec_append_metadata( struct cmdq_pkt *pkt, const enum CMDQ_IWC_ADDR_METADATA_TYPE type, const u64 base, const u32 offset, const u32 size, const u32 port, uint32_t sec_id) { struct cmdq_sec_data *sec_data; struct cmdq_sec_addr_meta *meta; s32 idx, max, ret; cmdq_log("pkt:%p type:%u base:%#llx offset:%#x size:%#x port:%#x sec_id:%d", pkt, type, base, offset, size, port, sec_id); cmdq_msg("%s pkt:%p type:%u base:%#llx offset:%#x size:%#x port:%#x sec_id:%d", __func__, pkt, type, base, offset, size, port, sec_id); ret = cmdq_sec_check_sec(pkt); if (ret < 0) return ret; sec_data = (struct cmdq_sec_data *)pkt->sec_data; idx = sec_data->addrMetadataCount; if (idx >= CMDQ_IWC_MAX_ADDR_LIST_LENGTH) { cmdq_err("idx:%u reach over:%u", idx, CMDQ_IWC_MAX_ADDR_LIST_LENGTH); return -EFAULT; } if (!sec_data->addrMetadataMaxCount) max = ADDR_METADATA_MAX_COUNT_ORIGIN; else if (idx >= sec_data->addrMetadataMaxCount) max = sec_data->addrMetadataMaxCount * 2; else max = sec_data->addrMetadataMaxCount; ret = cmdq_sec_realloc_addr_list(pkt, max); if (ret) return ret; if (!sec_data->addrMetadatas) { cmdq_log("addrMetadatas is missing"); meta = kzalloc(sizeof(*meta), GFP_KERNEL); if (!meta) return -ENOMEM; sec_data->addrMetadatas = (u64)(void *)meta; } meta = (struct cmdq_sec_addr_meta *) (unsigned long)sec_data->addrMetadatas; meta[idx].instrIndex = pkt->cmd_buf_size / CMDQ_INST_SIZE - 1; meta[idx].type = type; meta[idx].baseHandle = base; meta[idx].offset = offset; meta[idx].size = size; meta[idx].port = port; meta[idx].useSecIdinMeta = 1; meta[idx].sec_id = sec_id; sec_data->addrMetadataCount += 1; return 0; } s32 cmdq_sec_pkt_set_data(struct cmdq_pkt *pkt, const u64 dapc_engine, const u64 port_sec_engine, const enum CMDQ_SEC_SCENARIO scenario, const enum cmdq_sec_meta_type meta_type) { struct cmdq_sec_data *sec_data; s32 ret; if (!pkt) { cmdq_err("invalid pkt:%p", pkt); return -EINVAL; } ret = cmdq_sec_check_sec(pkt); if (ret < 0) return ret; cmdq_log( "pkt:%p sec_data:%p dapc:%llu port_sec:%llu scen:%u", pkt, pkt->sec_data, dapc_engine, port_sec_engine, scenario); sec_data = (struct cmdq_sec_data *)pkt->sec_data; sec_data->enginesNeedDAPC |= dapc_engine; sec_data->enginesNeedPortSecurity |= port_sec_engine; sec_data->scenario = scenario; sec_data->client_meta_type = meta_type; return 0; } EXPORT_SYMBOL(cmdq_sec_pkt_set_data); void cmdq_sec_pkt_set_mtee(struct cmdq_pkt *pkt, const bool enable, const int32_t sec_id) { struct cmdq_sec_data *sec_data = (struct cmdq_sec_data *)pkt->sec_data; sec_data->mtee = enable; sec_data->sec_id = sec_id; cmdq_msg("%s pkt:%p mtee:%d sec_id:%d\n", __func__, pkt, ((struct cmdq_sec_data *)pkt->sec_data)->mtee, ((struct cmdq_sec_data *)pkt->sec_data)->sec_id); } EXPORT_SYMBOL(cmdq_sec_pkt_set_mtee); void cmdq_sec_pkt_free_data(struct cmdq_pkt *pkt) { if (pkt->sec_data == NULL) return; kfree((void *)((struct cmdq_sec_data *)pkt->sec_data)->addrMetadatas); kfree(pkt->sec_data); } EXPORT_SYMBOL(cmdq_sec_pkt_free_data); s32 cmdq_sec_pkt_set_payload(struct cmdq_pkt *pkt, u8 idx, const u32 meta_size, u32 *meta) { struct cmdq_sec_data *sec_data; s32 ret; if (idx == 0) { cmdq_err("not allow set reserved payload 0"); return -EINVAL; } if (!meta_size || !meta) { cmdq_err("not allow empty size or buffer"); return -EINVAL; } ret = cmdq_sec_check_sec(pkt); if (ret < 0) return ret; if (idx == CMDQ_IWC_MSG1 && meta_size >= sizeof(struct iwcCmdqMessageEx_t)) { cmdq_err("not enough size payload 1:%u msg size:%zu", meta_size, sizeof(struct iwcCmdqMessageEx_t)); return -EINVAL; } if (idx == CMDQ_IWC_MSG2 && meta_size >= sizeof(struct iwcCmdqMessageEx2_t)) { cmdq_err("not enough size payload 2:%u msg size:%zu", meta_size, sizeof(struct iwcCmdqMessageEx2_t)); return -EINVAL; } sec_data = (struct cmdq_sec_data *)pkt->sec_data; sec_data->client_meta_size[idx] = meta_size; sec_data->client_meta[idx] = meta; return 0; } EXPORT_SYMBOL(cmdq_sec_pkt_set_payload); s32 cmdq_sec_pkt_write_reg(struct cmdq_pkt *pkt, u32 addr, u64 base, const enum CMDQ_IWC_ADDR_METADATA_TYPE type, const u32 offset, const u32 size, const u32 port, uint32_t sec_id) { s32 ret; ret = cmdq_pkt_assign_command(pkt, CMDQ_SPR_FOR_TEMP, addr); if (ret) return ret; ret = cmdq_pkt_append_command(pkt, base & 0xffff, base >> 16, CMDQ_SPR_FOR_TEMP, 0, CMDQ_IMMEDIATE_VALUE, CMDQ_IMMEDIATE_VALUE, CMDQ_REG_TYPE, CMDQ_CODE_WRITE_S); if (ret) return ret; /* check boundary size and append at first before append metadata */ if (unlikely(!pkt->avail_buf_size)) { if (cmdq_pkt_add_cmd_buffer(pkt) < 0) return -ENOMEM; } return cmdq_sec_append_metadata(pkt, type, base, offset, size, port, sec_id); } EXPORT_SYMBOL(cmdq_sec_pkt_write_reg); s32 cmdq_sec_pkt_assign_metadata(struct cmdq_pkt *pkt, u32 count, void *meta_array) { struct cmdq_sec_data *data; void *pkt_meta_array; size_t size; s32 ret; if (!count) return -EINVAL; ret = cmdq_sec_check_sec(pkt); if (ret < 0) return ret; data = (struct cmdq_sec_data *)pkt->sec_data; size = count * sizeof(struct cmdq_sec_addr_meta); pkt_meta_array = kzalloc(size, GFP_KERNEL); if (!pkt_meta_array) return -ENOMEM; memcpy(pkt_meta_array, meta_array, size); data->addrMetadatas = (unsigned long)pkt_meta_array; data->addrMetadataCount = count; return 0; } EXPORT_SYMBOL(cmdq_sec_pkt_assign_metadata); void cmdq_sec_dump_secure_data(struct cmdq_pkt *pkt) { struct cmdq_sec_data *data; struct cmdq_sec_addr_meta *meta; s32 i; u64 *inst; if (!pkt || !pkt->sec_data) { cmdq_msg("pkt without sec_data"); return; } data = (struct cmdq_sec_data *)pkt->sec_data; cmdq_util_msg( "meta cnt:%u addr:%#llx max:%u scen:%d dapc:%#llx port:%#llx wait:%d reset:%d metatype:%u", data->addrMetadataCount, data->addrMetadatas, data->addrMetadataMaxCount, data->scenario, data->enginesNeedDAPC, data->enginesNeedPortSecurity, data->waitCookie, data->resetExecCnt, (u32)data->client_meta_type); meta = (struct cmdq_sec_addr_meta *)(unsigned long) data->addrMetadatas; for (i = 0; i < data->addrMetadataCount; i++) { inst = cmdq_pkt_get_va_by_offset(pkt, meta[i].instrIndex * CMDQ_INST_SIZE); cmdq_util_msg( "meta:%d instr:%u type:%u base:%#llx block:%u ofst:%u size:%u port:%u inst:%#018llx", i, meta[i].instrIndex, meta[i].type, meta[i].baseHandle, meta[i].blockOffset, meta[i].offset, meta[i].size, meta[i].port, inst ? *inst : 0); } } EXPORT_SYMBOL(cmdq_sec_dump_secure_data); int cmdq_sec_pkt_wait_complete(struct cmdq_pkt *pkt) { struct cmdq_client *client = pkt->cl; unsigned long ret; u8 cnt = 0; s32 thread_id = cmdq_sec_mbox_chan_id(client->chan); #if IS_ENABLED(CONFIG_MMPROFILE) cmdq_sec_mmp_wait(client->chan, pkt); #endif cmdq_sec_mbox_enable(client->chan); do { ret = wait_for_completion_timeout(&pkt->cmplt, msecs_to_jiffies(CMDQ_PREDUMP_TIMEOUT_MS)); if (ret) break; cmdq_util_dump_lock(); cmdq_msg("===== SW timeout Pre-dump %hhu =====", cnt); cnt++; cmdq_dump_core(client->chan); cmdq_msg("thd:%d Hidden thread info since it's secure", thread_id); cmdq_sec_dump_operation(client->chan); cmdq_sec_dump_secure_thread_cookie(client->chan); cmdq_dump_pkt(pkt, 0, false); cmdq_sec_dump_notify_loop(client->chan); cmdq_util_dump_unlock(); } while (1); cmdq_sec_mbox_disable(client->chan); #if IS_ENABLED(CONFIG_MMPROFILE) cmdq_sec_mmp_wait_done(client->chan, pkt); #endif return 0; } EXPORT_SYMBOL(cmdq_sec_pkt_wait_complete); void cmdq_sec_err_dump(struct cmdq_pkt *pkt, struct cmdq_client *client, u64 **inst, const char **dispatch) { cmdq_sec_dump_operation(client->chan); cmdq_sec_dump_secure_thread_cookie(client->chan); cmdq_sec_dump_notify_loop(client->chan); cmdq_sec_dump_secure_data(pkt); cmdq_sec_dump_response(client->chan, pkt, inst, dispatch); } EXPORT_SYMBOL(cmdq_sec_err_dump);