// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "cmdq-util.h" #include "cmdq-sec.h" #include "../../mdp/mdp_cmdq_helper_ext.h" #ifdef CMDQ_SECURE_MTEE_SUPPORT #include "tz_m4u.h" #endif #define CMDQ_THR_SPR3(base, id) ((base) + (0x80 * (id)) + 0x16c) #define CMDQ_GPR_R32(base, id) ((base) + (0x4 * (id)) + 0x80) #define CMDQ_SYNC_TOKEN_UPD(base) ((base) + 0x68) #define CMDQ_TPR_MASK(base) ((base) + 0xd0) #define CMDQ_TPR_TIMEOUT_EN(base) ((base) + 0xdc) #define CMDQ_TEST_CNT 8 #define CMDQ_GPR_DEBUG_TIMER CMDQ_GPR_R14 #define CMDQ_GPR_DEBUG_DUMMY CMDQ_GPR_R15 enum { CMDQ_TEST_SUBSYS_GCE, CMDQ_TEST_SUBSYS_MMSYS, CMDQ_TEST_SUBSYS_NR, CMDQ_TEST_SUBSYS_ERR = 99 }; struct test_node { struct device *dev; void __iomem *va; phys_addr_t pa; struct clk *clk; struct clk *clk_timer; }; struct cmdq_test { struct device *dev; struct mutex lock; struct test_node gce; struct test_node mmsys; struct cmdq_client *clt; struct cmdq_client *loop; struct cmdq_client *sec; u32 iter; u32 subsys[CMDQ_TEST_SUBSYS_NR]; struct dentry *fs; bool tick; struct timer_list timer; u16 token_user0; u16 token_gpr_set4; }; static struct cmdq_test *gtest; static void cmdq_test_mbox_cb(struct cmdq_cb_data data) { struct cmdq_flush_completion *cmplt = data.data; if (data.err < 0) cmdq_err("pkt:%p err:%d", cmplt->pkt, data.err); cmplt->err = !data.err ? false : true; complete(&cmplt->cmplt); } static void cmdq_test_mbox_cb_destroy(struct cmdq_cb_data data) { struct cmdq_pkt *pkt = (struct cmdq_pkt *)data.data; if (data.err < 0) cmdq_err("pkt:%p err:%d", pkt, data.err); cmdq_pkt_destroy(pkt); } static void cmdq_test_mbox_cb_dump(struct cmdq_cb_data data) { cmdq_msg("pkt:0x%p err:%d done", data.data, data.err); } static void cmdq_test_mbox_cb_dump_err(struct cmdq_cb_data data) { cmdq_err("pkt:0x%p err:%d during err", data.data, data.err); } static void cmdq_test_mbox_err_dump(struct cmdq_test *test, const bool sec) { struct cmdq_pkt *pkt; struct cmdq_flush_completion cmplt; s32 ret; u64 *inst; dma_addr_t pc; struct cmdq_client *clt = sec ? test->sec : test->clt; if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } cmdq_clear_event(clt->chan, test->token_user0); pkt = cmdq_pkt_create(clt); #ifdef CMDQ_SECURE_SUPPORT if (sec) cmdq_sec_pkt_set_data(pkt, 0, 0, CMDQ_SEC_DEBUG, CMDQ_METAEX_NONE); #endif cmdq_pkt_wfe(pkt, test->token_user0); init_completion(&cmplt.cmplt); cmplt.pkt = pkt; cmdq_pkt_flush_async(pkt, cmdq_test_mbox_cb, &cmplt); cmdq_thread_dump(clt->chan, pkt, &inst, &pc); cmdq_set_event(clt->chan, test->token_user0); ret = cmdq_pkt_wait_complete(pkt); cmdq_msg("wait complete pkt:0x%p ret:%d", pkt, ret); ret = wait_for_completion_timeout( &cmplt.cmplt, msecs_to_jiffies(CMDQ_TIMEOUT_DEFAULT)); if (!ret) cmdq_err( "wait_for_completion_timeout pkt:0x%p ret:%d inst:0x%016llx pc:%pa", pkt, ret, inst ? *inst : 0, &pc); else cmdq_msg("%s round 1 done", __func__); /* second round, use flush async ex with wait, pre-dump and timeout */ pkt->err_cb.cb = cmdq_test_mbox_cb_dump_err; pkt->err_cb.data = pkt; cmdq_clear_event(clt->chan, test->token_user0); ret = cmdq_pkt_flush_async(pkt, cmdq_test_mbox_cb_dump, (void *)pkt); cmdq_msg("flush pkt:0x%p ret:%d", pkt, ret); ret = cmdq_pkt_wait_complete(pkt); cmdq_msg("wait complete pkt:0x%p ret:%d", pkt, ret); cmdq_pkt_destroy(pkt); clk_disable_unprepare(test->gce.clk); cmdq_msg("%s done", __func__); } static void cmdq_test_mbox_gpr_sleep(struct cmdq_test *test, const bool sleep) { struct cmdq_pkt *pkt; struct cmdq_pkt_buffer *buf; struct cmdq_operand l_op = {.reg = true, .idx = CMDQ_TPR_ID}; struct cmdq_operand r_op = {.reg = false, .idx = 100}; dma_addr_t out_pa; u32 *out_va, gce_time; u64 cpu_time; const u16 event = (u16)CMDQ_EVENT_GPR_TIMER + CMDQ_GPR_DEBUG_TIMER; if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } if (clk_prepare_enable(test->gce.clk_timer)) { cmdq_err("clk fail"); return; } writel(0x80000000, (void *)CMDQ_TPR_MASK(test->gce.va)); writel(1 << CMDQ_GPR_DEBUG_TIMER, (void *)CMDQ_TPR_TIMEOUT_EN(test->gce.va)); pkt = cmdq_pkt_create(test->clt); if (!sleep) { cmdq_pkt_write(pkt, NULL, CMDQ_TPR_TIMEOUT_EN(test->gce.pa), 1 << CMDQ_GPR_DEBUG_TIMER, 1 << CMDQ_GPR_DEBUG_TIMER); cmdq_pkt_clear_event(pkt, event); } else cmdq_pkt_wfe(pkt, test->token_gpr_set4); buf = list_last_entry(&pkt->buf, typeof(*buf), list_entry); out_pa = buf->pa_base + 3096; out_va = (u32 *)(buf->va_base + 3096); *out_va = 0; *(out_va + 1) = 0; *(out_va + 2) = 0; if (!sleep) { cmdq_pkt_write_indriect(pkt, NULL, out_pa, CMDQ_TPR_ID, ~0); cmdq_pkt_logic_command(pkt, CMDQ_LOGIC_ADD, CMDQ_GPR_CNT_ID + CMDQ_GPR_DEBUG_TIMER, &l_op, &r_op); cmdq_pkt_wfe(pkt, event); cmdq_pkt_write_indriect(pkt, NULL, out_pa + 4, CMDQ_TPR_ID, ~0); } else { cmdq_pkt_write_indriect(pkt, NULL, out_pa, CMDQ_TPR_ID, ~0); cmdq_pkt_sleep(pkt, 100, CMDQ_GPR_DEBUG_TIMER); cmdq_pkt_write_indriect(pkt, NULL, out_pa + 4, CMDQ_TPR_ID, ~0); cmdq_pkt_set_event(pkt, test->token_gpr_set4); cmdq_pkt_write_indriect(pkt, NULL, out_pa + 8, CMDQ_GPR_CNT_ID + CMDQ_GPR_DEBUG_TIMER, ~0); } cmdq_pkt_dump_buf(pkt, 0); cpu_time = sched_clock(); cmdq_pkt_flush(pkt); cpu_time = div_u64(sched_clock() - cpu_time, 1000000); if (*out_va <= *(out_va + 1)) gce_time = *(out_va + 1) - *out_va; else gce_time = 0xffffffff - *out_va + *(out_va + 1); if (cpu_time > 100 || gce_time > 300) cmdq_err("sleep:%d cpu:%llu gce:%u out:%u %u %u", sleep, cpu_time, gce_time, *out_va, *(out_va + 1), *(out_va + 2)); else cmdq_msg("sleep:%d cpu:%llu gce:%u out:%u %u %u", sleep, cpu_time, gce_time, *out_va, *(out_va + 1), *(out_va + 2)); cmdq_pkt_destroy(pkt); writel(0, (void *)CMDQ_TPR_MASK(test->gce.va)); clk_disable_unprepare(test->gce.clk_timer); clk_disable_unprepare(test->gce.clk); } static void cmdq_test_mbox_cpr(struct cmdq_test *test) { unsigned long va = (unsigned long)CMDQ_GPR_R32(test->gce.va, CMDQ_GPR_DEBUG_DUMMY); unsigned long pa = CMDQ_GPR_R32(test->gce.pa, CMDQ_GPR_DEBUG_DUMMY); u32 pttn = 0xdeaddead, *buf_va, mark_assign, mark_write; s32 i, ret; struct cmdq_pkt *pkt; struct cmdq_pkt_buffer *buf; if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } pkt = cmdq_pkt_create(test->clt); mark_assign = pkt->cmd_buf_size >> 2; cmdq_pkt_assign_command(pkt, CMDQ_THR_SPR_IDX3, pttn); mark_write = (pkt->cmd_buf_size + CMDQ_INST_SIZE) >> 2; cmdq_pkt_write_indriect(pkt, NULL, pa, CMDQ_THR_SPR_IDX3, ~0); cmdq_pkt_finalize(pkt); cmdq_pkt_dump_buf(pkt, 0); buf = list_first_entry(&pkt->buf, typeof(*buf), list_entry); buf_va = (u32 *)buf->va_base; /* (1024 - 256 - 4 * 24 - 16) * 2 = 1312 */ for (i = 0; i < 1312; i++) { // CPR_CNT writel(0, (void *)va); buf_va[mark_assign + 1] = (buf_va[1] & 0xffff0000) | (CMDQ_CPR_STRAT_ID + i); buf_va[mark_write] = ((CMDQ_CPR_STRAT_ID + i) << 16) | (buf_va[5] & 0xffff); ret = cmdq_pkt_flush(pkt); if (ret) cmdq_err("cmdq_pkt_flush failed:%d i:%d", ret, i); ret = readl((void *)va); if (ret != pttn) cmdq_err("ret:%#x not equal to pttn:%#x for idx:%d", ret, pttn, i + CMDQ_CPR_STRAT_ID); else cmdq_msg("ret:%#x equals to pttn:%#x for idx:%d", ret, pttn, i + CMDQ_CPR_STRAT_ID); } cmdq_pkt_destroy(pkt); clk_disable_unprepare(test->gce.clk); cmdq_msg("%s end", __func__); } u32 *cmdq_test_mbox_polling_timeout_unit(struct cmdq_pkt *pkt, const unsigned long pa, const u32 pttn, const u32 mask, const bool aee) { struct cmdq_pkt_buffer *buf; u32 *out_va; dma_addr_t out_pa; buf = list_last_entry(&pkt->buf, typeof(*buf), list_entry); // last 1k as output buffer out_pa = buf->pa_base + 3096; out_va = (u32 *)(buf->va_base + 3096); *out_va = 0; *(out_va + 1) = 0; cmdq_pkt_write_indriect(pkt, NULL, out_pa, CMDQ_CPR_STRAT_ID, ~0); cmdq_pkt_poll_timeout(pkt, pttn & mask, SUBSYS_NO_SUPPORT, pa, mask, aee ? U16_MAX : 100, CMDQ_GPR_DEBUG_TIMER); cmdq_pkt_write_indriect(pkt, NULL, out_pa + 4, CMDQ_CPR_STRAT_ID, ~0); return out_va; } void cmdq_test_mbox_polling( struct cmdq_test *test, const bool secure, const bool timeout, const bool aee) { unsigned long va = (unsigned long)(secure ? CMDQ_THR_SPR3(test->gce.va, 3) : CMDQ_GPR_R32(test->gce.va, CMDQ_GPR_DEBUG_DUMMY)); unsigned long pa = secure ? CMDQ_THR_SPR3(test->gce.pa, 3) : CMDQ_GPR_R32(test->gce.pa, CMDQ_GPR_DEBUG_DUMMY); const u32 pttn[CMDQ_TEST_CNT] = { 0xdada1818, 0xdada1818, 0xdada1818, 0x00001818}; const u32 mask[CMDQ_TEST_CNT] = { 0xff00ff00, 0xffffffff, 0x0000ff00, 0xffffffff}; struct cmdq_client *clt = secure ? test->sec : test->clt; struct cmdq_pkt *pkt[CMDQ_TEST_CNT]; u64 cpu_time; u32 i, val = 0, *out_va, gce_time; cmdq_msg("%s: secure:%d timeout:%d va:%#lx pa:%#lx", __func__, secure, timeout, va, pa); if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } if (clk_prepare_enable(test->gce.clk_timer)) { cmdq_err("clk fail"); return; } writel(0, (void *)va); for (i = 0; i < CMDQ_TEST_CNT && pttn[i]; i++) { if (timeout) writel(0x80000000, (void *)CMDQ_TPR_MASK(test->gce.va)); pkt[i] = cmdq_pkt_create(clt); #ifdef CMDQ_SECURE_SUPPORT if (secure) cmdq_sec_pkt_set_data(pkt[i], 0, 0, CMDQ_SEC_DEBUG, CMDQ_METAEX_NONE); #endif cmdq_pkt_wfe(pkt[i], test->token_gpr_set4); if (timeout) out_va = cmdq_test_mbox_polling_timeout_unit( pkt[i], pa, pttn[i], mask[i], aee); else cmdq_pkt_poll(pkt[i], NULL, pttn[i] & mask[i], pa, mask[i], CMDQ_GPR_DEBUG_TIMER); cmdq_pkt_set_event(pkt[i], test->token_gpr_set4); cpu_time = sched_clock(); cmdq_pkt_flush_async(pkt[i], NULL, NULL); if (!timeout) { writel(pttn[i] & mask[i], (void *)va); val = readl((void *)va); } cmdq_pkt_wait_complete(pkt[i]); cpu_time = div_u64(sched_clock() - cpu_time, 1000000); if (!timeout) gce_time = 0; else if (*out_va <= *(out_va + 1)) gce_time = *(out_va + 1) - *out_va; else gce_time = 0xffffffff - *out_va + *(out_va + 1); cmdq_msg("%d: pkt:%p pttn:%#x mask:%#x val:%#x cpu:%llu gce:%u", i, pkt[i], pttn[i], mask[i], val, cpu_time, gce_time); cmdq_pkt_dump_buf(pkt[i], 0); cmdq_pkt_destroy(pkt[i]); if (timeout) writel(0, (void *)CMDQ_TPR_MASK(test->gce.va)); } clk_disable_unprepare(test->gce.clk_timer); clk_disable_unprepare(test->gce.clk); cmdq_msg("%s end", __func__); } static void cmdq_test_mbox_large_cmd(struct cmdq_test *test) { unsigned long va = (unsigned long)(CMDQ_GPR_R32( test->gce.va, CMDQ_GPR_DEBUG_DUMMY)); unsigned long pa = CMDQ_GPR_R32( test->gce.pa, CMDQ_GPR_DEBUG_DUMMY); struct cmdq_pkt *pkt; s32 i, val; bool perf_en = cmdq_util_is_feature_en((u8) CMDQ_LOG_FEAT_PERF); if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } writel(0xdeaddead, (void *)va); pkt = cmdq_pkt_create(test->clt); if (!perf_en) cmdq_pkt_perf_begin(pkt); for (i = 0; i < 64 * 1024 / 8; i++) // 64k instructions cmdq_pkt_write(pkt, NULL, pa, i, ~0); if (!perf_en) cmdq_pkt_perf_end(pkt); cmdq_pkt_flush(pkt); cmdq_pkt_destroy(pkt); val = readl((void *)va); clk_disable_unprepare(test->gce.clk); if (val != --i) cmdq_err("val:%#x not equal to i:%#x", val, i); else cmdq_msg("val:%#x equals to i:%#x", val, i); } static void cmdq_test_mbox_sync_token_loop_iter(struct timer_list *t) { /*struct cmdq_test *test = from_timer(test, t, timer);*/ if (!gtest->tick) del_timer(>est->timer); else { mod_timer(>est->timer, jiffies + msecs_to_jiffies(300)); gtest->iter += 1; } } static void cmdq_test_mbox_loop(struct cmdq_test *test) { struct cmdq_pkt *pkt; struct cmdq_thread *thread = (struct cmdq_thread *)test->loop->chan->con_priv; s32 ret; if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } pkt = cmdq_pkt_create(test->loop); cmdq_pkt_wfe(pkt, test->token_user0); cmdq_pkt_finalize_loop(pkt); cmdq_dump_pkt(pkt, 0, true); test->iter = 0; test->tick = true; timer_setup(&test->timer, cmdq_test_mbox_sync_token_loop_iter, 0); mod_timer(&test->timer, jiffies + msecs_to_jiffies(300)); writel(test->token_user0, (void *)CMDQ_SYNC_TOKEN_UPD(test->gce.va)); ret = cmdq_pkt_flush_async(pkt, NULL, 0); while (test->iter < CMDQ_TEST_CNT) { cmdq_msg("loop thrd-idx:%u pkt:%p iter:%u", thread->idx, pkt, test->iter); msleep_interruptible(1000); } cmdq_mbox_stop(test->loop); clk_disable_unprepare(test->gce.clk); test->tick = false; del_timer(&test->timer); } static void cmdq_test_mbox_dma_access(struct cmdq_test *test, const bool secure) { unsigned long va = (unsigned long)(secure ? CMDQ_THR_SPR3(test->gce.va, 3) : CMDQ_GPR_R32(test->gce.va, CMDQ_GPR_DEBUG_DUMMY)); unsigned long pa = secure ? CMDQ_THR_SPR3(test->gce.pa, 3) : CMDQ_GPR_R32(test->gce.pa, CMDQ_GPR_DEBUG_DUMMY); const u32 ofst = 0xabc, pttn[CMDQ_TEST_CNT] = { 0xabcdabcd, 0xaabbccdd, 0xdeaddead}; struct cmdq_client *clt = secure ? test->sec : test->clt; struct cmdq_pkt *pkt; u32 *dma_va; dma_addr_t dma_pa; u32 val; dma_va = cmdq_mbox_buf_alloc(clt->client.dev, &dma_pa); if (!dma_va || !dma_pa) { cmdq_err("cmdq_mbox_buf_alloc failed"); return; } dma_va[0] = pttn[0]; dma_va[1] = pttn[0]; dma_va[2] = pttn[1]; if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } writel(pttn[2], (void *)va); pkt = cmdq_pkt_create(clt); #ifdef CMDQ_SECURE_SUPPORT if (secure) cmdq_sec_pkt_set_data(pkt, 0, 0, CMDQ_SEC_DEBUG, CMDQ_METAEX_NONE); #endif cmdq_pkt_mem_move(pkt, NULL, pa, dma_pa, CMDQ_THR_SPR_IDX1); cmdq_pkt_mem_move(pkt, NULL, dma_pa, dma_pa + 4, CMDQ_THR_SPR_IDX1); cmdq_pkt_mem_move(pkt, NULL, dma_pa + 8, pa, CMDQ_THR_SPR_IDX1); cmdq_pkt_flush(pkt); cmdq_pkt_dump_buf(pkt, 0); cmdq_pkt_destroy(pkt); if (dma_va[0] != pttn[2]) cmdq_err("move pa:%#x to dma-0:%#x dft:%#x", pttn[2], dma_va[0], pttn[0]); if (dma_va[1] != pttn[2]) cmdq_err("move dma-0:%#x to dma-1:%#x dft:%#x", pttn[2], dma_va[1], pttn[0]); val = readl((void *)va); if (dma_va[2] != val) cmdq_err("move dma-2:%#x to pa:%#x dft:%#x", dma_va[2], val, pttn[2]); pkt = cmdq_pkt_create(clt); #ifdef CMDQ_SECURE_SUPPORT if (secure) cmdq_sec_pkt_set_data(pkt, 0, 0, CMDQ_SEC_DEBUG, CMDQ_METAEX_NONE); #endif cmdq_pkt_jump(pkt, 8); cmdq_pkt_write_value_addr(pkt, dma_pa + ofst, pttn[2], ~0); cmdq_pkt_read_addr(pkt, dma_pa + ofst, CMDQ_THR_SPR_IDX1); cmdq_pkt_write_reg_addr(pkt, dma_pa + ofst + 4, CMDQ_THR_SPR_IDX1, ~0); cmdq_pkt_flush(pkt); cmdq_pkt_dump_buf(pkt, 2); cmdq_pkt_destroy(pkt); if (dma_va[ofst / 4] != dma_va[ofst / 4 + 1] || dma_va[ofst / 4 + 1] != pttn[2]) cmdq_err("pa:%pa offset:%#x va:%p val:%#x %#x pttn:%#x", &dma_pa, ofst, &dma_va[ofst / 4], dma_va[ofst / 4], dma_va[ofst / 4 + 1], pttn[2]); clk_disable_unprepare(test->gce.clk); cmdq_mbox_buf_free(clt->client.dev, dma_va, dma_pa); cmdq_msg("%s done", __func__); } static void cmdq_test_mbox_sync_token_flush(struct timer_list *t) { u32 val; struct cmdq_test *test = from_timer(test, t, timer); if (clk_prepare_enable(gtest->gce.clk)) { cmdq_err("clk fail"); return; } writel((1L << 16) | test->token_user0, (void *)CMDQ_SYNC_TOKEN_UPD(gtest->gce.va)); val = readl((void *)CMDQ_SYNC_TOKEN_UPD(gtest->gce.va)); cmdq_log("data:%#hx event:%#x val:%#x", test->token_user0, (1 << 16), val); if (!gtest->tick) del_timer(>est->timer); else mod_timer(>est->timer, jiffies + msecs_to_jiffies(10)); clk_disable_unprepare(gtest->gce.clk); } void cmdq_test_mbox_flush( struct cmdq_test *test, const s32 secure, const bool threaded) { struct cmdq_client *clt = secure ? test->sec : test->clt; struct cmdq_pkt *pkt[CMDQ_TEST_CNT] = {0}; s32 i, err; cmdq_msg("%s sec:%d threaded:%d", __func__, secure, threaded); test->tick = true; timer_setup(&test->timer, cmdq_test_mbox_sync_token_flush, 0); mod_timer(&test->timer, jiffies + msecs_to_jiffies(10)); for (i = 0; i < CMDQ_TEST_CNT; i++) { pkt[i] = cmdq_pkt_create(clt); #ifdef CMDQ_SECURE_SUPPORT if (secure) { cmdq_sec_pkt_set_data(pkt[i], 0, 0, CMDQ_SEC_DEBUG, CMDQ_METAEX_NONE); #ifdef CMDQ_SECURE_MTEE_SUPPORT if (!~secure) cmdq_sec_pkt_set_mtee(pkt[i], true, SEC_ID_SVP); #endif } #endif cmdq_pkt_wfe(pkt[i], test->token_user0); pkt[i]->priority = i; if (!threaded) cmdq_pkt_flush_async(pkt[i], NULL, NULL); else cmdq_pkt_flush_threaded(pkt[i], cmdq_test_mbox_cb_destroy, (void *)pkt[i]); } for (i = 0; i < CMDQ_TEST_CNT; i++) { if (!pkt[i]) { cmdq_err("NULL pkt:%d", i); continue; } msleep_interruptible(100); if (!threaded) err = cmdq_pkt_wait_complete(pkt[i]); else err = 0; if (err < 0) { cmdq_err("wait complete pkt[%d]:%p err:%d", i, pkt[i], err); continue; } if (!threaded) cmdq_pkt_destroy(pkt[i]); } test->tick = false; del_timer(&test->timer); cmdq_msg("%s end", __func__); } static void cmdq_test_mbox_write( struct cmdq_test *test, const s32 secure, const bool need_mask) { const u32 mask = need_mask ? (1 << 16) : ~0; const u32 pttn = (1 << 0) | (1 << 2) | (1 << 16); unsigned long va = (unsigned long)(secure ? CMDQ_THR_SPR3(test->gce.va, 3) : CMDQ_GPR_R32(test->gce.va, CMDQ_GPR_DEBUG_DUMMY)); unsigned long pa = secure ? CMDQ_THR_SPR3(test->gce.pa, 3) : CMDQ_GPR_R32(test->gce.pa, CMDQ_GPR_DEBUG_DUMMY); struct cmdq_client *clt = secure ? test->sec : test->clt; struct cmdq_pkt *pkt; s32 val; cmdq_msg("sec:%d va:%#lx pa:%#lx pttn:%#x mask:%#x clt:%p", secure, va, pa, pttn, mask, clt); if (clk_prepare_enable(test->gce.clk)) { cmdq_err("clk fail"); return; } writel(0, (void *)va); pkt = cmdq_pkt_create(clt); #ifdef CMDQ_SECURE_SUPPORT if (secure) { cmdq_sec_pkt_set_data(pkt, 0, 0, CMDQ_SEC_DEBUG, CMDQ_METAEX_NONE); #ifdef CMDQ_SECURE_MTEE_SUPPORT if (!~secure) cmdq_sec_pkt_set_mtee(pkt, true, SEC_ID_SVP); #endif } #endif cmdq_pkt_write(pkt, NULL, pa, pttn, mask); cmdq_pkt_flush(pkt); val = readl((void *)va); if (val != (pttn & mask)) { cmdq_err("wrong val:%#x ans:%#x", val, pttn & mask); cmdq_pkt_dump_buf(pkt, 0); } else cmdq_msg("right val:%#x ans:%#x", val, pttn & mask); cmdq_pkt_destroy(pkt); clk_disable_unprepare(test->gce.clk); cmdq_msg("%s end", __func__); } static void cmdq_test_mbox_handshake_event(struct cmdq_test *test) { struct cmdq_client *clt1 = test->clt, *clt2 = test->loop; struct cmdq_pkt *pkt_wait, *pkt_shake; int ret; if (cmdq_mbox_get_base_pa(clt1->chan) == cmdq_mbox_get_base_pa(clt2->chan)) { cmdq_msg("no handshake for 2 same client"); return; } pkt_wait = cmdq_pkt_create(clt1); cmdq_pkt_wfe(pkt_wait, CMDQ_EVENT_HANDSHAKE); cmdq_pkt_handshake_event(pkt_wait, CMDQ_EVENT_HANDSHAKE + 1); pkt_shake = cmdq_pkt_create(clt2); cmdq_pkt_handshake_event(pkt_shake, CMDQ_EVENT_HANDSHAKE); cmdq_pkt_wfe(pkt_shake, CMDQ_EVENT_HANDSHAKE + 1); cmdq_pkt_flush_async(pkt_wait, NULL, NULL); ret = cmdq_pkt_flush(pkt_shake); cmdq_pkt_wait_complete(pkt_wait); if (ret < 0) cmdq_err("shake event fail:%d", ret); cmdq_msg("%s end", __func__); } u32 cmdq_test_get_subsys_list(u32 **regs_out); static void cmdq_access_sub_impl(struct cmdq_test *test, struct cmdq_client *clt, const char *tag) { struct cmdq_pkt *pkt; u32 *regs, count, *va, i; dma_addr_t pa; u8 swap_reg = CMDQ_THR_SPR_IDX1; u32 pat_init = 0xdeaddead, pat_src = 0xbeefbeef; va = cmdq_mbox_buf_alloc(clt->client.dev, &pa); if (!va) { cmdq_err("cmdq_mbox_buf_alloc failed"); return; } count = cmdq_test_get_subsys_list(®s); for (i = 0; i < count; i++) { va[0] = pat_init; pkt = cmdq_pkt_create(test->clt); cmdq_pkt_write_value_addr(pkt, regs[i], pat_src, ~0); cmdq_pkt_mem_move(pkt, NULL, regs[i], pa, swap_reg); cmdq_pkt_flush(pkt); if (va[0] != pat_src) cmdq_err( "%s access reg fail addr:%#x val:%#x should be:%#x", tag, regs[i], va[0], pat_src); cmdq_pkt_destroy(pkt); } cmdq_mbox_buf_free(test->clt->client.dev, va, pa); cmdq_msg("%s end", __func__); } static void cmdq_test_mbox_subsys_access(struct cmdq_test *test) { cmdq_access_sub_impl(test, test->clt, "clt"); cmdq_access_sub_impl(test, test->loop, "loop"); } static void cmdq_test_err_irq(struct cmdq_test *test) { struct cmdq_pkt *pkt; struct cmdq_pkt_buffer *buf; u64 *inst; pkt = cmdq_pkt_create(test->clt); cmdq_pkt_jump(pkt, 0); buf = list_first_entry(&pkt->buf, typeof(*buf), list_entry); inst = (u64 *)buf->va_base; *inst = 0xffffbeefdeadbeef; cmdq_pkt_flush(pkt); cmdq_pkt_destroy(pkt); cmdq_msg("%s end", __func__); } static void cmdq_test_devapc_vio(struct cmdq_test *test) { struct cmdq_pkt *pkt; int ret; cmdq_msg("%s", __func__); pkt = cmdq_pkt_create(test->clt); cmdq_pkt_read(pkt, NULL, 0x14000000, CMDQ_THR_SPR_IDX3); ret = cmdq_pkt_flush(pkt); cmdq_pkt_destroy(pkt); cmdq_msg("%s ret:%d end", __func__, ret); } static void cmdq_test_mbox_stop(struct cmdq_test *test) { struct cmdq_pkt *pkt[3]; int i; cmdq_msg("%s", __func__); for (i = 0; i < ARRAY_SIZE(pkt); i++) { pkt[i] = cmdq_pkt_create(test->clt); cmdq_pkt_wfe(pkt[i], test->token_user0); cmdq_pkt_flush_async(pkt[i], NULL, NULL); } cmdq_msg("%s stop channel", __func__); cmdq_mbox_channel_stop(test->clt->chan); cmdq_msg("%s still wait it", __func__); for (i = 0; i < ARRAY_SIZE(pkt); i++) { cmdq_pkt_wait_complete(pkt[i]); cmdq_pkt_destroy(pkt[i]); } cmdq_msg("%s end", __func__); } static void cmdq_test_show_events(struct cmdq_test *test) { u32 i; cmdq_msg("%s scan all active event ...", __func__); cmdq_mbox_enable(test->clt->chan); for (i = 0; i < CMDQ_EVENT_MAX; i++) if (cmdq_get_event(test->clt->chan, i)) cmdq_msg("event set:%u", i); cmdq_mbox_disable(test->clt->chan); cmdq_msg("%s end", __func__); } static void cmdq_test_trigger(struct cmdq_test *test, const s32 sec, const s32 id) { struct cmdq_thread *thread_clt = (struct cmdq_thread *)test->clt->chan->con_priv; s32 backup_clt = cmdq_thread_timeout_backup(thread_clt, CMDQ_TIMEOUT_DEFAULT); struct cmdq_thread *thread_loop = (struct cmdq_thread *)test->loop->chan->con_priv; s32 backup_loop = cmdq_thread_timeout_backup(thread_loop, CMDQ_NO_TIMEOUT); #ifdef CMDQ_SECURE_SUPPORT struct cmdq_thread *thread_sec = (struct cmdq_thread *)test->sec->chan->con_priv; s32 backup_sec = cmdq_thread_timeout_backup(thread_sec, CMDQ_TIMEOUT_DEFAULT); #endif #ifndef CMDQ_SECURE_SUPPORT if (sec) { cmdq_err("CMDQ_SECURE not support"); return; } #endif switch (id) { case 0: cmdq_test_mbox_write(test, sec, false); cmdq_test_mbox_write(test, sec, true); cmdq_test_mbox_flush(test, sec, false); cmdq_test_mbox_flush(test, sec, true); cmdq_test_mbox_polling(test, sec, false, false); cmdq_test_mbox_dma_access(test, sec); cmdq_test_mbox_gpr_sleep(test, false); cmdq_test_mbox_gpr_sleep(test, true); cmdq_test_mbox_loop(test); cmdq_test_mbox_large_cmd(test); cmdq_test_mbox_cpr(test); break; case 1: cmdq_test_mbox_write(test, sec, false); cmdq_test_mbox_write(test, sec, true); break; case 2: cmdq_test_mbox_flush(test, sec, false); cmdq_test_mbox_flush(test, sec, true); break; case 3: cmdq_test_mbox_polling(test, sec, false, false); cmdq_test_mbox_polling(test, sec, true, false); break; case 4: cmdq_test_mbox_dma_access(test, sec); break; case 5: cmdq_test_mbox_gpr_sleep(test, false); cmdq_test_mbox_gpr_sleep(test, true); break; case 6: cmdq_test_mbox_loop(test); break; case 7: cmdq_test_mbox_large_cmd(test); break; case 8: cmdq_test_mbox_cpr(test); break; case 9: cmdq_test_mbox_err_dump(test, sec); break; case 10: cmdq_test_mbox_handshake_event(test); break; case 11: cmdq_test_mbox_subsys_access(test); break; case 12: cmdq_test_err_irq(test); break; case 13: cmdq_test_devapc_vio(test); break; case 14: cmdq_test_mbox_polling(test, sec, true, true); break; case 15: cmdq_test_mbox_stop(test); break; case 16: cmdq_test_show_events(test); break; default: break; } cmdq_thread_timeout_restore(thread_clt, backup_clt); cmdq_thread_timeout_restore(thread_loop, backup_loop); #ifdef CMDQ_SECURE_SUPPORT cmdq_thread_timeout_restore(thread_sec, backup_sec); #endif } #define MAX_SCAN 30 static ssize_t cmdq_test_write(struct file *filp, const char *buf, size_t count, loff_t *offp) { struct cmdq_test *test = (struct cmdq_test *)filp->f_inode->i_private; char str[MAX_SCAN] = {0}; s32 sec, id = 0; u32 len; len = (count < MAX_SCAN - 1) ? count : (MAX_SCAN - 1); if (copy_from_user(str, buf, len)) { cmdq_err("copy_from_user failed len:%d", len); return count; } str[len] = '\0'; if (sscanf(str, "%d %d", &sec, &id) != 2) { cmdq_err("sscanf failed str:%s sec:%d id:%d", str, sec, id); return count; } cmdq_msg("test:%p len:%d sec:%d id:%d str:%s", test, len, sec, id, str); mutex_lock(&test->lock); cmdq_test_trigger(test, sec, id); mutex_unlock(&test->lock); return count; } static const struct file_operations cmdq_test_fops = { .write = cmdq_test_write, }; static s32 cmdq_test_client_get(struct cmdq_client **clt, const u32 end_idx) { struct cmdq_client *mdp = NULL; s32 i; for (i = end_idx; i >= 0 && !mdp; i--) mdp = cmdq_helper_mbox_client(i); if (mdp) *clt = mdp; return i; } static int cmdq_test_probe(struct platform_device *pdev) { struct cmdq_test *test; struct device_node *np; struct platform_device *np_pdev; struct resource res; struct dentry *dir; s32 i, ret; test = devm_kzalloc(&pdev->dev, sizeof(*test), GFP_KERNEL); if (!test) return -ENOMEM; test->dev = &pdev->dev; mutex_init(&test->lock); test->tick = false; gtest = test; // gce np = of_parse_phandle(pdev->dev.of_node, "mediatek,gce", 0); if (!np) { cmdq_err("of_parse_phandle mediatek,gce failed"); return -EINVAL; } np_pdev = of_find_device_by_node(np); of_node_put(np); if (!np_pdev) return -EINVAL; test->gce.dev = &np_pdev->dev; test->gce.va = of_iomap(np_pdev->dev.of_node, 0); if (!test->gce.va) return -EINVAL; ret = of_address_to_resource(np_pdev->dev.of_node, 0, &res); if (ret) return ret; test->gce.pa = res.start; test->gce.clk = devm_clk_get(&np_pdev->dev, "gce"); if (IS_ERR(test->gce.clk)) { cmdq_err("devm_clk_get gce clk failed:%ld", PTR_ERR(test->gce.clk)); test->gce.clk = NULL; } test->gce.clk_timer = devm_clk_get(&np_pdev->dev, "gce-timer"); if (IS_ERR(test->gce.clk_timer)) { cmdq_err("devm_clk_get gce clk_timer failed:%ld", PTR_ERR(test->gce.clk_timer)); test->gce.clk_timer = NULL; } cmdq_msg("gce dev:%p va:%p pa:%pa", test->gce.dev, test->gce.va, &test->gce.pa); // mmsys np = of_parse_phandle(pdev->dev.of_node, "mmsys_config", 0); if (!np) { cmdq_err("of_parse_phandle mmsys_config failed"); return -EINVAL; } np_pdev = of_find_device_by_node(np); of_node_put(np); if (!np_pdev) { cmdq_err("of_find_device_by_node to mmsys_config failed"); return -EINVAL; } test->mmsys.dev = &np_pdev->dev; test->mmsys.va = of_iomap(np_pdev->dev.of_node, 0); if (!test->mmsys.va) return -EINVAL; ret = of_address_to_resource(np_pdev->dev.of_node, 0, &res); if (ret) { cmdq_err( "of_address_to_resource to mmsys_config failed ret:%d", ret); return ret; } test->mmsys.pa = res.start; cmdq_msg("mmsys dev:%p va:%p pa:%pa", test->mmsys.dev, test->mmsys.va, &test->mmsys.pa); // clt ret = CMDQ_MAX_THREAD_COUNT; test->clt = cmdq_mbox_create(&pdev->dev, 0); if (IS_ERR(test->clt) || !test->clt) { ret = cmdq_test_client_get(&test->clt, ret - 1); if (!test->clt) return -ENXIO; } test->loop = cmdq_mbox_create(&pdev->dev, 1); if (IS_ERR(test->loop) || !test->loop) { ret = cmdq_test_client_get(&test->loop, ret - 1); if (!test->loop) return -ENXIO; } #ifdef CMDQ_SECURE_SUPPORT test->sec = cmdq_mbox_create(&pdev->dev, 2); if (IS_ERR(test->sec) || !test->sec) { ret = cmdq_test_client_get(&test->sec, 10); if (!test->sec) return -ENXIO; } #endif cmdq_msg("%s test:%p dev:%p clt:%p loop:%p sec:%p", __func__, test, test->dev, test->clt, test->loop, test->sec); // subsys i = of_property_count_u32_elems( pdev->dev.of_node, "mediatek,gce-subsys"); if (i < 0) { cmdq_err("of_property_count_u32_elems gce-subsys failed:%d", i); for (i = 0; i < CMDQ_TEST_SUBSYS_NR; i++) test->subsys[i] = CMDQ_TEST_SUBSYS_ERR; } else { ret = of_property_read_u32_array(pdev->dev.of_node, "mediatek,gce-subsys", test->subsys, i); if (ret) { cmdq_err("of_property_read_u32_array failed:%d", ret); for (i = 0; i < CMDQ_TEST_SUBSYS_NR; i++) test->subsys[i] = CMDQ_TEST_SUBSYS_ERR; } } for (i = 0; i < CMDQ_TEST_SUBSYS_NR; i++) cmdq_msg("subsys[%d]:%u", i, test->subsys[i]); ret = of_property_read_u16(pdev->dev.of_node, "token_user0", &test->token_user0); if (ret < 0) { cmdq_err("no token_user0 err:%d", ret); test->token_user0 = CMDQ_EVENT_MAX; } ret = of_property_read_u16(pdev->dev.of_node, "token_gpr_set4", &test->token_gpr_set4); if (ret < 0) { cmdq_err("no token_gpr_set4 err:%d", ret); test->token_gpr_set4 = CMDQ_EVENT_MAX; } // fs dir = debugfs_create_dir("cmdq", NULL); if (IS_ERR(dir) && PTR_ERR(dir) != -EEXIST) { cmdq_err("debugfs_create_dir cmdq failed:%ld", PTR_ERR(dir)); return PTR_ERR(dir); } test->fs = debugfs_create_file( "cmdq-test", 0444, dir, test, &cmdq_test_fops); if (IS_ERR(test->fs)) { cmdq_err("debugfs_create_file cmdq-test failed:%ld", PTR_ERR(test->fs)); return PTR_ERR(test->fs); } platform_set_drvdata(pdev, test); return 0; } static int cmdq_test_remove(struct platform_device *pdev) { struct cmdq_test *test = (struct cmdq_test *)platform_get_drvdata(pdev); cmdq_mbox_destroy(test->clt); cmdq_mbox_destroy(test->loop); cmdq_mbox_destroy(test->sec); return 0; } static const struct of_device_id cmdq_test_of_ids[] = { { .compatible = "mediatek,cmdq-test", }, {} }; MODULE_DEVICE_TABLE(of, cmdq_test_of_ids); static struct platform_driver cmdq_test_drv = { .probe = cmdq_test_probe, .remove = cmdq_test_remove, .driver = { .name = "cmdq-test", .of_match_table = cmdq_test_of_ids, }, }; static int __init cmdq_test_init(void) { return platform_driver_register(&cmdq_test_drv); } static void __exit cmdq_test_exit(void) { return platform_driver_unregister(&cmdq_test_drv); } device_initcall_sync(cmdq_test_init); module_exit(cmdq_test_exit); MODULE_DESCRIPTION("MEDIATEK Module Cmdq-test driver"); MODULE_AUTHOR("Mediatek"); MODULE_LICENSE("GPL");