// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2019 MediaTek Inc. */ #define DEBUG 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MTK_USE_RESERVED_EXT_MEM #include #endif #include "queue.h" #include "mtk_mmc_block.h" #include #define SECTOR_SHIFT 9 /* ring trace for debugfs */ struct mtk_blocktag *mtk_btag_mmc; /* context buffer of each request queue */ struct mt_bio_context *mt_ctx_map[MMC_BIOLOG_CONTEXTS] = { 0 }; /* context index for mt_ctx_map */ enum { CTX_MMCQD0 = 0, CTX_MMCQD1 = 1, CTX_MMCQD0_BOOT0 = 2, CTX_MMCQD0_BOOT1 = 3, CTX_MMCQD0_RPMB = 4, CTX_MMCCMDQD0 = 5, CTX_EXECQ = 9 }; /* context state for command queue */ enum { CMDQ_CTX_NOT_DMA = 0, CMDQ_CTX_IN_DMA = 1, CMDQ_CTX_QUEUE = 2 }; /* context state for mmcqd */ enum { MMCQD_NORMAL = 0, MMCQD_CMDQ_MODE_EN = 1 }; #define MT_BIO_TRACE_LATENCY (unsigned long long)(1000000000) #define REQ_EXECQ "exe_cq" #define REQ_MMCQD0 "kworker" #define REQ_MMCQD0_BOOT0 "mmcqd/0boot0" #define REQ_MMCQD0_BOOT1 "mmcqd/0boot1" #define REQ_MMCQD0_RPMB "mmcqd/0rpmb" #define REQ_MMCCMDQD0 "mmc-cmdqd/0" #define REQ_MMCQD1 "kworker" static void mt_bio_ctx_count_usage(struct mt_bio_context *ctx, __u64 start, __u64 end); static uint64_t mt_bio_get_period_busy(struct mt_bio_context *ctx); /* queue id: * 0=internal storage (emmc:mmcqd0/exe_cq), * 1=external storage (t-card:mmcqd1) */ static int get_qid_by_name(const char *str) { if (strncmp(str, REQ_EXECQ, strlen(REQ_EXECQ)) == 0) return BTAG_STORAGE_EMBEDDED; if (strncmp(str, REQ_MMCQD0, strlen(REQ_MMCQD0)) == 0) return BTAG_STORAGE_EMBEDDED; /* this includes boot0, boot1 */ if (strncmp(str, REQ_MMCCMDQD0, strlen(REQ_MMCCMDQD0)) == 0) return BTAG_STORAGE_EMBEDDED; if (strncmp(str, REQ_MMCQD1, strlen(REQ_MMCQD1)) == 0) return BTAG_STORAGE_EXTERNAL; return 99; } /* get context id to mt_ctx_map[] by name */ static int get_ctxid_by_name(const char *str) { if (strncmp(str, REQ_EXECQ, strlen(REQ_EXECQ)) == 0) return CTX_EXECQ; if (strncmp(str, REQ_MMCQD0_RPMB, strlen(REQ_MMCQD0_RPMB)) == 0) return CTX_MMCQD0_RPMB; if (strncmp(str, REQ_MMCQD0_BOOT0, strlen(REQ_MMCQD0_BOOT0)) == 0) return CTX_MMCQD0_BOOT0; if (strncmp(str, REQ_MMCQD0_BOOT1, strlen(REQ_MMCQD0_BOOT1)) == 0) return CTX_MMCQD0_BOOT1; if (strncmp(str, REQ_MMCCMDQD0, strlen(REQ_MMCCMDQD0)) == 0) return CTX_MMCCMDQD0; if (strncmp(str, REQ_MMCQD0, strlen(REQ_MMCQD0)) == 0) return CTX_MMCQD0; if (strncmp(str, REQ_MMCQD1, strlen(REQ_MMCQD1)) == 0) return CTX_MMCQD1; return -1; } static void mt_bio_init_task(struct mt_bio_context_task *tsk) { int i; tsk->task_id = -1; tsk->arg = 0; for (i = 0; i < tsk_max; i++) tsk->t[i] = 0; } static void mt_bio_init_ctx(struct mt_bio_context *ctx, struct task_struct *thread, struct request_queue *q, bool ext_sd) { int i; ctx->q = q; ctx->pid = task_pid_nr(thread); get_task_comm(ctx->comm, thread); ctx->qid = get_qid_by_name(ctx->comm); if (ext_sd) ctx->qid = BTAG_STORAGE_EXTERNAL; spin_lock_init(&ctx->lock); ctx->id = get_ctxid_by_name(ctx->comm); if (ext_sd) ctx->id = CTX_MMCQD1; if (ctx->id >= 0) mt_ctx_map[ctx->id] = ctx; ctx->period_start_t = sched_clock(); for (i = 0; i < MMC_BIOLOG_CONTEXT_TASKS; i++) mt_bio_init_task(&ctx->task[i]); } void mt_bio_queue_alloc(struct task_struct *thread, struct request_queue *q, bool ext_sd) { int i; pid_t pid; static bool ext_sd_setup_done; struct mt_bio_context *ctx = BTAG_CTX(mtk_btag_mmc); if (!ctx) return; pid = task_pid_nr(thread); for (i = 0; i < MMC_BIOLOG_CONTEXTS; i++) { if (ctx[i].pid == pid) break; /* bypass more SD wokers */ if (ext_sd_setup_done) break; /* bypass more emmc wokers */ #ifdef CONFIG_MTK_EMMC_CQ_SUPPORT /* SWCQ: 2 thread - exe_cq and kworker */ if ((i > 1) && !ext_sd) break; #else /* CQHCI: only one thread - kworker */ if ((i > 0) && !ext_sd) break; #endif if (ctx[i].pid == 0) { if (ext_sd) { ext_sd_setup_done = true; mt_bio_init_ctx(&ctx[i], thread, q, true); } else mt_bio_init_ctx(&ctx[i], thread, q, false); break; } } } void mt_bio_queue_free(struct task_struct *thread) { int i; pid_t pid; struct mt_bio_context *ctx = BTAG_CTX(mtk_btag_mmc); if (!ctx) return; pid = task_pid_nr(thread); for (i = 0; i < MMC_BIOLOG_CONTEXTS; i++) { if (ctx[i].pid == pid) { mt_ctx_map[ctx[i].id] = NULL; memset(&ctx[i], 0, sizeof(struct mt_bio_context)); break; } } } static struct mt_bio_context *mt_bio_curr_queue(struct request_queue *q, bool ext_sd) { int i; pid_t qd_pid; struct mt_bio_context *ctx = BTAG_CTX(mtk_btag_mmc); if (!ctx) return NULL; qd_pid = task_pid_nr(current); for (i = 0; i < MMC_BIOLOG_CONTEXTS; i++) { if (ctx[i].pid == 0) continue; if (!ext_sd && (!strncmp(ctx[i].comm, REQ_MMCQD0, strlen(REQ_MMCQD0)) || (qd_pid == ctx[i].pid) || (ctx[i].q && ctx[i].q == q))) return &ctx[i]; /* It means hardcore ctx[2] or ctx[1] as SD card, it's not elegant */ else if ((i < 3) && ext_sd && (!strncmp(ctx[2-i].comm, REQ_MMCQD0, strlen(REQ_MMCQD0)) || (qd_pid == ctx[2-i].pid) || (ctx[2-i].q && ctx[2-i].q == q))) return &ctx[2-i]; } return NULL; } /* get context correspond to current process */ static inline struct mt_bio_context *mt_bio_curr_ctx(bool ext_sd) { return mt_bio_curr_queue(NULL, ext_sd); } /* get other queue's context by context id */ static struct mt_bio_context *mt_bio_get_ctx(int id) { if (id < 0 || id >= MMC_BIOLOG_CONTEXTS) return NULL; return mt_ctx_map[id]; } /* append a pidlog to given context */ int mtk_btag_pidlog_add_mmc(struct request_queue *q, pid_t pid, __u32 len, int write) { unsigned long flags; struct mt_bio_context *ctx; struct mmc_queue *mq = NULL; struct mmc_host *host = NULL; if (q && q->queuedata) { mq = (struct mmc_queue *)(q->queuedata); host = mq ? mq->card->host : 0; } if (!host) return 0; if (host->caps2 & MMC_CAP2_NO_SD) { ctx = mt_bio_curr_queue(q, false); if (!ctx) return 0; spin_lock_irqsave(&ctx->lock, flags); mtk_mq_btag_pidlog_insert(&ctx->pidlog, pid, len, write, false); } else if (host->caps2 & MMC_CAP2_NO_MMC) { ctx = mt_bio_curr_queue(q, true); if (!ctx) return 0; spin_lock_irqsave(&ctx->lock, flags); mtk_mq_btag_pidlog_insert(&ctx->pidlog, pid, len, write, true); } else return 0; if (ctx->qid == BTAG_STORAGE_EMBEDDED) mtk_btag_mictx_eval_req(mtk_btag_mmc, write, 1, len, false); spin_unlock_irqrestore(&ctx->lock, flags); return 1; } EXPORT_SYMBOL_GPL(mtk_btag_pidlog_add_mmc); /* evaluate throughput and workload of given context */ static void mt_bio_context_eval(struct mt_bio_context *ctx) { struct mt_bio_context_task *tsk; uint64_t min, period, tsk_start, usage, result; int i; min = ctx->period_end_t; /* for all tasks if there is an on-going request */ for (i = 0; i < MMC_BIOLOG_CONTEXT_TASKS; i++) { tsk = &ctx->task[i]; if (tsk->task_id >= 0) { tsk_start = tsk->t[tsk_req_start]; if (tsk_start && tsk_start >= ctx->period_start_t && tsk_start < min) min = tsk_start; } } mt_bio_ctx_count_usage(ctx, min, ctx->period_end_t); ctx->workload.usage = mt_bio_get_period_busy(ctx); if (ctx->workload.period > (ctx->workload.usage * 100)) { ctx->workload.percent = 1; } else { period = ctx->workload.period; do_div(period, 100); ctx->workload.percent = (__u32) ctx->workload.usage / (__u32) period; } mtk_btag_throughput_eval(&ctx->throughput); } /* print context to trace ring buffer */ static void mt_bio_print_trace(struct mt_bio_context *ctx) { struct mtk_btag_ringtrace *rt = BTAG_RT(mtk_btag_mmc); struct mtk_btag_trace *tr; struct mt_bio_context *pid_ctx = ctx; unsigned long flags; if (!rt) return; if (ctx->id == CTX_EXECQ) pid_ctx = mt_bio_get_ctx(CTX_MMCQD0); spin_lock_irqsave(&rt->lock, flags); tr = mtk_btag_curr_trace(rt); if (!tr) goto out; memset(tr, 0, sizeof(struct mtk_btag_trace)); tr->pid = ctx->pid; tr->qid = ctx->qid; memcpy(&tr->throughput, &ctx->throughput, sizeof(struct mtk_btag_throughput)); memcpy(&tr->workload, &ctx->workload, sizeof(struct mtk_btag_workload)); if (pid_ctx) mtk_btag_pidlog_eval(&tr->pidlog, &pid_ctx->pidlog); mtk_btag_vmstat_eval(&tr->vmstat); mtk_btag_cpu_eval(&tr->cpu); tr->time = sched_clock(); mtk_btag_klog(mtk_btag_mmc, tr); mtk_btag_next_trace(rt); out: spin_unlock_irqrestore(&rt->lock, flags); } static struct mt_bio_context_task *mt_bio_get_task(struct mt_bio_context *ctx, unsigned int task_id) { struct mt_bio_context_task *tsk; if (!ctx) return NULL; if (task_id >= MMC_BIOLOG_CONTEXT_TASKS) { pr_notice("%s: invalid task id %d\n", __func__, task_id); return NULL; } tsk = &ctx->task[task_id]; tsk->task_id = task_id; return tsk; } static struct mt_bio_context_task *mt_bio_curr_task_by_ctx_id( unsigned int task_id, struct mt_bio_context **curr_ctx, int mt_ctx_map, bool ext_sd) { struct mt_bio_context *ctx; if (mt_ctx_map == -1) /* get ctx by current pid */ ctx = mt_bio_curr_ctx(ext_sd); else /* get ctx by ctx map id */ ctx = mt_bio_get_ctx(mt_ctx_map); if (curr_ctx) *curr_ctx = ctx; return mt_bio_get_task(ctx, task_id); } static struct mt_bio_context_task *mt_bio_curr_task(unsigned int task_id, struct mt_bio_context **curr_ctx, bool ext_sd) { /* get ctx by current pid */ return mt_bio_curr_task_by_ctx_id(task_id, curr_ctx, -1, ext_sd); } static const char *task_name[tsk_max] = { "req_start", "dma_start", "dma_end", "isdone_start", "isdone_end"}; static void mt_pr_cmdq_tsk(struct mt_bio_context_task *tsk, int stage) { int rw; int klogen = BTAG_KLOGEN(mtk_btag_mmc); __u32 bytes; char buf[256]; if (!((klogen == 2 && stage == tsk_isdone_end) || (klogen == 3))) return; rw = tsk->arg & (1 << 30); /* write: 0, read: 1 */ bytes = (tsk->arg & 0xFFFF) << SECTOR_SHIFT; mtk_btag_task_timetag(buf, 256, stage, tsk_max, task_name, tsk->t, bytes); pr_debug("[BLOCK_TAG] cmdq: tsk[%d],%d,%s,len=%d%s\n", tsk->task_id, stage+1, (rw)?"r":"w", bytes, buf); } void mt_biolog_cmdq_check(void) { struct mt_bio_context *ctx; __u64 end_time, period_time; unsigned long flags; ctx = mt_bio_curr_ctx(false); if (!ctx) return; spin_lock_irqsave(&ctx->lock, flags); end_time = sched_clock(); period_time = end_time - ctx->period_start_t; if (period_time >= MT_BIO_TRACE_LATENCY) { ctx->period_end_t = end_time; ctx->workload.period = period_time; mt_bio_context_eval(ctx); mt_bio_print_trace(ctx); ctx->period_start_t = end_time; ctx->period_end_t = 0; ctx->wl.period_busy = 0; ctx->wl.period_left_window_end_t = 0; ctx->wl.period_right_window_end_t = 0; ctx->wl.period_right_window_start_t = 0; memset(&ctx->throughput, 0, sizeof(struct mtk_btag_throughput)); memset(&ctx->workload, 0, sizeof(struct mtk_btag_workload)); } spin_unlock_irqrestore(&ctx->lock, flags); } /* Command Queue Hook: stage1: queue task */ void mt_biolog_cmdq_queue_task(unsigned int task_id, struct mmc_request *req) { struct mt_bio_context *ctx; struct mt_bio_context_task *tsk; int i; if (!req) return; if (!req->sbc) return; tsk = mt_bio_curr_task(task_id, &ctx, false); if (!tsk) return; if (ctx->state == CMDQ_CTX_NOT_DMA) ctx->state = CMDQ_CTX_QUEUE; tsk->arg = req->sbc->arg; tsk->t[tsk_req_start] = sched_clock(); ctx->q_depth++; mtk_btag_mictx_update_ctx(mtk_btag_mmc, ctx->q_depth); for (i = tsk_dma_start; i < tsk_max; i++) tsk->t[i] = 0; if (!ctx->period_start_t) ctx->period_start_t = tsk->t[tsk_req_start]; mt_pr_cmdq_tsk(tsk, tsk_req_start); } static void mt_bio_ctx_count_usage(struct mt_bio_context *ctx, __u64 start, __u64 end) { if (start <= ctx->period_start_t) { /* * if 'start' is located in previous period, * reset right window and period_busy, * and finally only left window will be existed. */ ctx->wl.period_left_window_end_t = end; ctx->wl.period_right_window_start_t = ctx->wl.period_right_window_end_t = ctx->wl.period_busy = 0; } else { /* if left window is existed */ if (ctx->wl.period_left_window_end_t) { if (start < ctx->wl.period_left_window_end_t) { /* * if 'start' is located inside left window, * reset right window and period_busy, * and finally only left window will be existed. */ ctx->wl.period_left_window_end_t = end; ctx->wl.period_right_window_start_t = ctx->wl.period_right_window_end_t = ctx->wl.period_busy = 0; } else goto new_window; } else goto new_window; } goto out; new_window: if (ctx->wl.period_right_window_start_t) { if (start > ctx->wl.period_right_window_end_t) { ctx->wl.period_busy += (ctx->wl.period_right_window_end_t - ctx->wl.period_right_window_start_t); ctx->wl.period_right_window_start_t = start; } ctx->wl.period_right_window_end_t = end; } else { ctx->wl.period_right_window_start_t = start; ctx->wl.period_right_window_end_t = end; } out: return; } static uint64_t mt_bio_get_period_busy(struct mt_bio_context *ctx) { uint64_t busy; busy = ctx->wl.period_busy; if (ctx->wl.period_left_window_end_t) { busy += (ctx->wl.period_left_window_end_t - ctx->period_start_t); } if (ctx->wl.period_right_window_start_t) { busy += (ctx->wl.period_right_window_end_t - ctx->wl.period_right_window_start_t); } return busy; } /* Command Queue Hook: stage2: dma start */ void mt_biolog_cmdq_dma_start(unsigned int task_id) { struct mt_bio_context_task *tsk; struct mt_bio_context *ctx; tsk = mt_bio_curr_task(task_id, &ctx, false); if (!tsk) return; tsk->t[tsk_dma_start] = sched_clock(); /* count first queue task time in workload usage, * if it was not overlapped with DMA */ if (ctx->state == CMDQ_CTX_QUEUE) mt_bio_ctx_count_usage(ctx, tsk->t[tsk_req_start], tsk->t[tsk_dma_start]); ctx->state = CMDQ_CTX_IN_DMA; mt_pr_cmdq_tsk(tsk, tsk_dma_start); } /* Command Queue Hook: stage3: dma end */ void mt_biolog_cmdq_dma_end(unsigned int task_id) { struct mt_bio_context_task *tsk; struct mt_bio_context *ctx; tsk = mt_bio_curr_task(task_id, &ctx, false); if (!tsk) return; tsk->t[tsk_dma_end] = sched_clock(); ctx->state = CMDQ_CTX_NOT_DMA; mt_pr_cmdq_tsk(tsk, tsk_dma_end); } /* Command Queue Hook: stage4: isdone start */ void mt_biolog_cmdq_isdone_start(unsigned int task_id, struct mmc_request *req) { struct mt_bio_context_task *tsk; tsk = mt_bio_curr_task(task_id, NULL, false); if (!tsk) return; tsk->t[tsk_isdone_start] = sched_clock(); mt_pr_cmdq_tsk(tsk, tsk_isdone_start); } /* Command Queue Hook: stage5: isdone end */ void mt_biolog_cmdq_isdone_end(unsigned int task_id) { int write, i; __u32 bytes; __u64 end_time, busy_time; struct mt_bio_context *ctx; struct mt_bio_context_task *tsk; struct mtk_btag_throughput_rw *tp; tsk = mt_bio_curr_task(task_id, &ctx, false); if (!tsk) return; /* return if there's no on-going request */ for (i = 0; i < tsk_isdone_end; i++) if (!tsk->t[i]) return; tsk->t[tsk_isdone_end] = end_time = sched_clock(); ctx->q_depth--; mtk_btag_mictx_update_ctx(mtk_btag_mmc, ctx->q_depth); /* throughput usage := duration of handling this request */ /* tsk->arg & (1 << 30): 0 means write */ write = tsk->arg & (1 << 30); write = (write) ? 0 : 1; bytes = tsk->arg & 0xFFFF; bytes = bytes << SECTOR_SHIFT; busy_time = end_time - tsk->t[tsk_req_start]; tp = (write) ? &ctx->throughput.w : &ctx->throughput.r; tp->usage += busy_time; tp->size += bytes; mtk_btag_mictx_eval_tp(mtk_btag_mmc, write, busy_time, bytes); /* workload statistics */ ctx->workload.count++; /* count DMA time in workload usage */ /* count isdone time in workload usage, * if it was not overlapped with DMA */ if (ctx->state == CMDQ_CTX_NOT_DMA) mt_bio_ctx_count_usage(ctx, tsk->t[tsk_dma_start], end_time); else mt_bio_ctx_count_usage(ctx, tsk->t[tsk_dma_start], tsk->t[tsk_dma_end]); mt_pr_cmdq_tsk(tsk, tsk_isdone_end); mt_bio_init_task(tsk); } void mt_biolog_cqhci_check(void) { mt_biolog_cmdq_check(); } /* * parameter "host" needed due to req->host * doesn't initial when enter here */ void mt_biolog_cqhci_queue_task(struct mmc_host *host, unsigned int task_id, struct mmc_request *mrq) { struct mt_bio_context *ctx; struct mt_bio_context_task *tsk; struct mmc_queue_req *mqrq; struct request *req; u32 req_flags; unsigned long flags; if (!mrq || !mrq->data) return; req_flags = mrq->data->flags; tsk = mt_bio_curr_task_by_ctx_id(task_id, &ctx, -1, false); if (!tsk) return; if (mrq) { mqrq = container_of(mrq, struct mmc_queue_req, brq.mrq); req = blk_mq_rq_from_pdu(mqrq); mtk_btag_commit_req(req); } spin_lock_irqsave(&ctx->lock, flags); /* CANNOT used req->host here, it doesn't been initial when go here */ if (host && (host->caps2 & MMC_CAP2_CQE)) { /* convert cqhci to legacy sbc arg */ if (req_flags & MMC_DATA_READ) tsk->arg = 1 << 30 | (mrq->data->blocks & 0xFFFF); else if (req_flags & MMC_DATA_WRITE) { tsk->arg = (mrq->data->blocks & 0xFFFF); tsk->arg = tsk->arg & ~(1 << 30); } } else { if (mrq->sbc) tsk->arg = mrq->sbc->arg; } tsk->t[tsk_req_start] = sched_clock(); ctx->q_depth++; mtk_btag_mictx_update_ctx(mtk_btag_mmc, ctx->q_depth); if (!ctx->period_start_t) ctx->period_start_t = tsk->t[tsk_req_start]; spin_unlock_irqrestore(&ctx->lock, flags); mt_pr_cmdq_tsk(tsk, tsk_req_start); } void mt_biolog_cqhci_complete(unsigned int task_id) { int write; __u32 bytes; __u64 end_time, busy_time; struct mt_bio_context *ctx; struct mt_bio_context_task *tsk; struct mtk_btag_throughput_rw *tp; unsigned long flags; tsk = mt_bio_curr_task_by_ctx_id(task_id, &ctx, -1, false); if (!tsk) return; spin_lock_irqsave(&ctx->lock, flags); tsk->t[tsk_isdone_end] = end_time = sched_clock(); ctx->q_depth--; mtk_btag_mictx_update_ctx(mtk_btag_mmc, ctx->q_depth); /* throughput usage := duration of handling this request */ /* tsk->arg & (1 << 30): 0 means write */ write = tsk->arg & (1 << 30); write = (write) ? 0 : 1; bytes = tsk->arg & 0xFFFF; bytes = bytes << SECTOR_SHIFT; busy_time = end_time - tsk->t[tsk_req_start]; /* * workaround: skip the IO which size is 0, please noted * that this issue doesn't exist in non-standard CQHCI driver, * it only happened in standard CQHCI driver. */ if (!bytes) goto end; tp = (write) ? &ctx->throughput.w : &ctx->throughput.r; tp->usage += busy_time; tp->size += bytes; mtk_btag_mictx_eval_tp(mtk_btag_mmc, write, busy_time, bytes); /* workload statistics */ ctx->workload.count++; /* count doorbell to complete time in workload usage */ mt_bio_ctx_count_usage(ctx, tsk->t[tsk_req_start], end_time); end: mt_pr_cmdq_tsk(tsk, tsk_isdone_end); mt_bio_init_task(tsk); spin_unlock_irqrestore(&ctx->lock, flags); } /* MMC Queue Hook: check function at mmc_blk_issue_rw_rq() */ void mt_biolog_mmcqd_req_check(bool ext_sd) { struct mt_bio_context *ctx; __u64 end_time, period_time; ctx = mt_bio_curr_ctx(ext_sd); if (!ctx) return; /* skip mmcqd0, if command queue is applied */ if ((ctx->id == CTX_MMCQD0) && (ctx->state == MMCQD_CMDQ_MODE_EN)) return; end_time = sched_clock(); period_time = end_time - ctx->period_start_t; if (period_time >= MT_BIO_TRACE_LATENCY) { ctx->period_end_t = end_time; ctx->workload.period = period_time; mt_bio_context_eval(ctx); mt_bio_print_trace(ctx); ctx->period_start_t = end_time; ctx->period_end_t = 0; ctx->wl.period_busy = 0; ctx->wl.period_left_window_end_t = 0; ctx->wl.period_right_window_end_t = 0; ctx->wl.period_right_window_start_t = 0; memset(&ctx->throughput, 0, sizeof(struct mtk_btag_throughput)); memset(&ctx->workload, 0, sizeof(struct mtk_btag_workload)); } } /* MMC Queue Hook: request start function at mmc_start_req() */ void mt_biolog_mmcqd_req_start(struct mmc_host *host, struct request *req, bool ext_sd) { struct mt_bio_context *ctx; struct mt_bio_context_task *tsk; tsk = mt_bio_curr_task(0, &ctx, ext_sd); if (!tsk) return; if (req) mtk_btag_commit_req(req); tsk->t[tsk_req_start] = sched_clock(); #ifdef CONFIG_MTK_EMMC_CQ_SUPPORT if ((ctx->id == CTX_MMCQD0) && (ctx->state == MMCQD_NORMAL) && host->card->ext_csd.cmdq_en) ctx->state = MMCQD_CMDQ_MODE_EN; /* * CMDQ mode, embedded eMMC mictx will be * updated in mt_biolog_cmdq_*, so bypass it here. */ #else /* Legacy mode. Update mictx for embedded eMMC only */ if (ctx->qid == BTAG_STORAGE_EMBEDDED) mtk_btag_mictx_update_ctx(mtk_btag_mmc, 1); #endif } /* MMC Queue Hook: request end function at mmc_start_req() */ void mt_biolog_mmcqd_req_end(struct mmc_data *data, bool ext_sd) { int rw; __u32 size; struct mt_bio_context *ctx; struct mt_bio_context_task *tsk; struct mtk_btag_throughput_rw *tp; __u64 end_time, busy_time; end_time = sched_clock(); if (!data) return; if (data->flags == MMC_DATA_WRITE) rw = 0; else if (data->flags == MMC_DATA_READ) rw = 1; else return; tsk = mt_bio_curr_task(0, &ctx, ext_sd); if (!tsk) return; /* return if there's no on-going request */ if (!tsk->t[tsk_req_start]) return; size = (data->blocks * data->blksz); busy_time = end_time - tsk->t[tsk_req_start]; /* workload statistics */ ctx->workload.count++; /* count request handling time in workload usage */ mt_bio_ctx_count_usage(ctx, tsk->t[tsk_req_start], end_time); /* throughput statistics */ /* write: 0, read: 1 */ tp = (rw) ? &ctx->throughput.r : &ctx->throughput.w; tp->usage += busy_time; tp->size += size; /* update mictx for embedded eMMC only */ if (ctx->qid == BTAG_STORAGE_EMBEDDED) { mtk_btag_mictx_eval_tp(mtk_btag_mmc, !rw, busy_time, size); mtk_btag_mictx_update_ctx(mtk_btag_mmc, 0); } /* re-init task to indicate no on-going request */ mt_bio_init_task(tsk); } /* * snprintf may return a value of size or "more" to indicate * that the output was truncated, thus be careful of "more" * case. */ #define SPREAD_PRINTF(buff, size, evt, fmt, args...) \ do { \ if (buff && size && *(size)) { \ unsigned long var = snprintf(*(buff), *(size),\ fmt, ##args); \ if (var > 0) { \ if (var > *(size)) \ var = *(size); \ *(size) -= var; \ *(buff) += var; \ } \ } \ if (evt) \ seq_printf(evt, fmt, ##args); \ if (!buff && !evt) { \ pr_info(fmt, ##args); \ } \ } while (0) static size_t mt_bio_seq_debug_show_info(char **buff, unsigned long *size, struct seq_file *seq) { int i; struct mt_bio_context *ctx = BTAG_CTX(mtk_btag_mmc); if (!ctx) return 0; for (i = 0; i < MMC_BIOLOG_CONTEXTS; i++) { if (ctx[i].pid == 0) continue; SPREAD_PRINTF(buff, size, seq, "ctx[%d]=ctx_map[%d]=%s,pid:%4d,q:%d\n", i, ctx[i].id, ctx[i].comm, ctx[i].pid, ctx[i].qid); } return 0; } static struct mtk_btag_vops mt_mmc_btag_vops = { .seq_show = mt_bio_seq_debug_show_info, }; int mt_mmc_biolog_init(void) { struct mtk_blocktag *btag; struct mt_bio_context *ctx; btag = mtk_btag_alloc("mmc", MMC_BIOLOG_RINGBUF_MAX, sizeof(struct mt_bio_context), MMC_BIOLOG_CONTEXTS, &mt_mmc_btag_vops); if (btag) mtk_btag_mmc = btag; ctx = BTAG_CTX(mtk_btag_mmc); memset(ctx, 0, sizeof(struct mt_bio_context) * MMC_BIOLOG_CONTEXTS); return 0; } EXPORT_SYMBOL_GPL(mt_mmc_biolog_init); int mt_mmc_biolog_exit(void) { mtk_btag_free(mtk_btag_mmc); return 0; } MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Mediatek MMC Block IO Log");