/* * Copyright (c) 2021 Samsung Electronics Co., Ltd. * http://www.samsung.com * * IPs Traffic Monitor(ITMON) Driver for Samsung Exynos SOC * By Hosung Kim (hosung0.kim@samsung.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "exynos-itmon-local.h" #include static struct itmon_policy err_policy[] = { [TMOUT] = {"err_tmout", 0, 0, 0, false}, [PRTCHKER] = {"err_prtchker", 0, 0, 0, false}, [DECERR] = {"err_decerr", 0, 0, 0, false}, [SLVERR] = {"err_slverr", 0, 0, 0, false}, [FATAL] = {"err_fatal", 0, 0, 0, false}, }; static const char * const itmon_dpm_action[] = { GO_DEFAULT, GO_PANIC, GO_WATCHDOG, GO_S2D, GO_ARRAYDUMP, GO_SCANDUMP, GO_HALT, }; static const char *itmon_pathtype[] = { "DATA Path transaction", "Configuration(SFR) Path transaction", "BUS Path transaction", "DATA Path transaction", }; /* Error Code Description */ const static char *itmon_errcode[] = { "Error Detect by the Slave(SLVERR)", "Decode error(DECERR)", "Unsupported transaction error", "Power Down access error", "Unsupported transaction", "Unsupported transaction", "Timeout error - response timeout in timeout value", "Invalid errorcode", }; const static char *itmon_node_string[] = { "M_NODE", "TAXI_S_NODE", "TAXI_M_NODE", "S_NODE", }; const static char *itmon_cpu_node_string[] = { "CLUSTER0_P", "M_CPU", "SCI_IRPM", "SCI_CCM", "CCI", }; const static char *itmon_mid_mnode_string[] = { "M_PERI", "NOCL0_DP_M", }; const static char *itmon_mid_snode_string[] = { "NOCL0_DP", "PERI_SFR", }; const static unsigned int itmon_invalid_addr[] = { 0x03000000, 0x04000000, }; static struct itmon_dev *g_itmon; /* declare notifier_list */ static ATOMIC_NOTIFIER_HEAD(itmon_notifier_list); static const struct of_device_id itmon_dt_match[] = { {.compatible = "samsung,exynos-itmon", .data = NULL,}, {}, }; MODULE_DEVICE_TABLE(of, itmon_dt_match); static struct itmon_nodeinfo *itmon_get_nodeinfo_by_group(struct itmon_dev *itmon, struct itmon_nodegroup *group, const char *name) { struct itmon_nodeinfo *node = group->nodeinfo; int i; for (i = 0; i < group->nodesize; i++) if (!strncmp(node[i].name, name, strlen(name))) return &node[i]; return NULL; } static struct itmon_nodeinfo *itmon_get_nodeinfo(struct itmon_dev *itmon, struct itmon_nodegroup *group, const char *name) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_nodeinfo *node = NULL; int i; if (!name) return NULL; if (group) { return itmon_get_nodeinfo_by_group(itmon, group, name); } else { for (i = 0; i < pdata->num_nodegroup; i++) { group = &pdata->nodegroup[i]; node = itmon_get_nodeinfo_by_group(itmon, group, name); if (node) return node; } } return NULL; } static struct itmon_rpathinfo *itmon_get_rpathinfo(struct itmon_dev *itmon, unsigned int id, char *dest_name) { struct itmon_platdata *pdata = itmon->pdata; int i; if (!dest_name) return NULL; for (i = 0; i < pdata->num_rpathinfo; i++) { if (pdata->rpathinfo[i].id == (id & pdata->rpathinfo[i].bits)) { if (!strncmp(pdata->rpathinfo[i].dest_name, dest_name, strlen(pdata->rpathinfo[i].dest_name))) return &pdata->rpathinfo[i]; } } return NULL; } static struct itmon_masterinfo *itmon_get_masterinfo(struct itmon_dev *itmon, char *port_name, unsigned int user) { struct itmon_platdata *pdata = itmon->pdata; int i; if (!port_name) return NULL; for (i = 0; i < pdata->num_masterinfo; i++) { if ((user & pdata->masterinfo[i].bits) == pdata->masterinfo[i].user) { if (!strncmp(pdata->masterinfo[i].port_name, port_name, strlen(port_name))) return &pdata->masterinfo[i]; } } return NULL; } static void itmon_enable_addr_detect(struct itmon_dev *itmon, struct itmon_nodeinfo *node, bool enabled) { /* This feature is only for M_NODE */ unsigned int tmp, val; unsigned int offset = OFFSET_PRT_CHK; val = __raw_readl(node->regs + offset + REG_PRT_CHK_CTL); val |= INTEND_ACCESS_INT_ENABLE; __raw_writel(val, node->regs + offset + REG_PRT_CHK_CTL); val = ((unsigned int)INTEND_ADDR_START & U32_MAX); __raw_writel(val, node->regs + offset + REG_PRT_CHK_START_ADDR_LOW); val = (unsigned int)(((unsigned long)INTEND_ADDR_START >> 32) & U16_MAX); __raw_writel(val, node->regs + offset + REG_PRT_CHK_START_END_ADDR_UPPER); val = ((unsigned int)INTEND_ADDR_END & 0xFFFFFFFF); __raw_writel(val, node->regs + offset + REG_PRT_CHK_END_ADDR_LOW); val = ((unsigned int)(((unsigned long)INTEND_ADDR_END >> 32) & 0XFFFF0000) << 16); tmp = readl(node->regs + offset + REG_PRT_CHK_START_END_ADDR_UPPER); __raw_writel(tmp | val, node->regs + offset + REG_PRT_CHK_START_END_ADDR_UPPER); dev_dbg(itmon->dev, "ITMON - %s addr detect %sabled\n", node->name, enabled == true ? "en" : "dis"); } static void itmon_enable_prt_chk(struct itmon_dev *itmon, struct itmon_nodeinfo *node, bool enabled) { unsigned int offset = OFFSET_PRT_CHK; unsigned int val = 0; if (enabled) val = RD_RESP_INT_ENABLE | WR_RESP_INT_ENABLE | ARLEN_RLAST_INT_ENABLE | AWLEN_WLAST_INT_ENABLE; writel(val, node->regs + offset + REG_PRT_CHK_CTL); dev_dbg(itmon->dev, "ITMON - %s hw_assert %sabled\n", node->name, enabled == true ? "en" : "dis"); } static void itmon_enable_err_report(struct itmon_dev *itmon, struct itmon_nodeinfo *node, bool enabled) { struct itmon_platdata *pdata = itmon->pdata; unsigned int offset = OFFSET_REQ_R; if (!pdata->probed || !node->retention) writel(1, node->regs + offset + REG_INT_CLR); /* enable interrupt */ writel(enabled, node->regs + offset + REG_INT_MASK); /* clear previous interrupt of req_write */ offset = OFFSET_REQ_W; if (!pdata->probed || !node->retention) writel(1, node->regs + offset + REG_INT_CLR); /* enable interrupt */ writel(enabled, node->regs + offset + REG_INT_MASK); /* clear previous interrupt of response_read */ offset = OFFSET_RESP_R; if (!pdata->probed || !node->retention) writel(1, node->regs + offset + REG_INT_CLR); /* enable interrupt */ writel(enabled, node->regs + offset + REG_INT_MASK); /* clear previous interrupt of response_write */ offset = OFFSET_RESP_W; if (!pdata->probed || !node->retention) writel(1, node->regs + offset + REG_INT_CLR); /* enable interrupt */ writel(enabled, node->regs + offset + REG_INT_MASK); dev_dbg(itmon->dev, "ITMON - %s error reporting %sabled\n", node->name, enabled == true ? "en" : "dis"); } static void itmon_enable_timeout(struct itmon_dev *itmon, struct itmon_nodeinfo *node, bool enabled) { unsigned int offset = OFFSET_TMOUT_REG; /* Enable Timeout setting */ writel(enabled, node->regs + offset + REG_DBG_CTL); /* set tmout interval value */ writel(node->time_val, node->regs + offset + REG_TMOUT_INIT_VAL); /* Enable freezing */ writel(node->tmout_frz_enabled, node->regs + offset + REG_TMOUT_FRZ_EN); dev_dbg(itmon->dev, "ITMON - %s timeout %sabled\n", node->name, enabled == true ? "en" : "dis"); } static void itmon_enable_nodepolicy(struct itmon_dev *itmon, struct itmon_nodeinfo *node) { struct itmon_nodepolicy *policy = &node->policy; struct itmon_nodegroup *group = node->group; if (!policy->chk_set) return; if (group->pd_support && !group->pd_status) { dev_err(g_itmon->dev, "%s group - %s node NOT pd on\n", group, node); } if (policy->chk_errrpt) itmon_enable_err_report(itmon, node, policy->en_errrpt); if (policy->chk_tmout || policy->chk_tmout_val) itmon_enable_timeout(itmon, node, policy->en_tmout); if (policy->chk_prtchk) itmon_enable_prt_chk(itmon, node, policy->en_prtchk); } static void itmon_set_nodepolicy(struct itmon_dev *itmon, struct itmon_nodeinfo *node, struct itmon_nodepolicy policy, bool now) { node->policy.enabled = policy.enabled; if (policy.chk_errrpt) node->err_enabled = policy.en_errrpt; if (policy.chk_tmout) node->tmout_enabled = policy.en_tmout; if (policy.chk_prtchk) node->prt_chk_enabled = policy.en_prtchk; if (policy.chk_tmout_val) node->time_val = policy.en_tmout_val; if (policy.chk_freeze) node->tmout_frz_enabled = policy.en_freeze; if (policy.chk_errrpt || policy.chk_tmout || policy.chk_prtchk || policy.chk_tmout_val || policy.chk_errrpt || policy.chk_prtchk_job || policy.chk_tmout || policy.chk_freeze || policy.chk_decerr_job || policy.chk_slverr_job || policy.chk_tmout_job) node->policy.chk_set = 1; if (now) itmon_enable_nodepolicy(itmon, node); } int itmon_enable_by_name(const char *name, bool enabled) { struct itmon_nodeinfo *node; struct itmon_nodegroup *group; if (!g_itmon) return -ENODEV; node = itmon_get_nodeinfo(g_itmon, NULL, name); if (!node) { dev_err(g_itmon->dev, "%s node is not found\n", name); return -ENODEV; } group = node->group; if (!group) { dev_err(g_itmon->dev, "%s node's group is not found\n", name); return -ENODEV; } if (group->pd_support && !group->pd_status) { dev_err(g_itmon->dev, "%s group - %s node NOT pd on\n", group, node); return -EIO; } itmon_enable_err_report(g_itmon, node, enabled); itmon_enable_prt_chk(g_itmon, node, enabled); if (node->type == S_NODE) itmon_enable_timeout(g_itmon, node, enabled); return 0; } EXPORT_SYMBOL(itmon_enable_by_name); int itmon_set_nodepolicy_by_name(const char *name, u64 enabled, bool now) { struct itmon_nodeinfo *node; struct itmon_nodepolicy policy; if (!g_itmon) return -ENODEV; node = itmon_get_nodeinfo(g_itmon, NULL, name); if (!node) return -ENODEV; policy.enabled = enabled; itmon_set_nodepolicy(g_itmon, node, policy, now); return 0; } EXPORT_SYMBOL(itmon_set_nodepolicy_by_name); static void itmon_init_by_group(struct itmon_dev *itmon, struct itmon_nodegroup *group, bool enabled) { struct itmon_nodeinfo *node = group->nodeinfo; int i; dev_dbg(itmon->dev, "%s: group:%s enabled:%x\n", __func__, group->name, enabled); for (i = 0; i < group->nodesize; i++) { if (enabled) { if (node[i].err_enabled) itmon_enable_err_report(itmon, &node[i], enabled); if (node[i].prt_chk_enabled) itmon_enable_prt_chk(itmon, &node[i], enabled); } else { /* as default enable */ itmon_enable_err_report(itmon, &node[i], enabled); itmon_enable_prt_chk(itmon, &node[i], enabled); } if (node[i].type == S_NODE && node[i].tmout_enabled) itmon_enable_timeout(itmon, &node[i], enabled); if (node[i].addr_detect_enabled) itmon_enable_addr_detect(itmon, &node[i], enabled); } } static void itmon_init(struct itmon_dev *itmon, bool enabled) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_nodegroup *group; int i; for (i = 0; i < pdata->num_nodegroup; i++) { group = &pdata->nodegroup[i]; if (group->pd_support && !group->pd_status) continue; itmon_init_by_group(itmon, group, enabled); } pdata->enabled = enabled; dev_info(itmon->dev, "itmon %sabled\n", pdata->enabled ? "en" : "dis"); } void itmon_pd_sync(const char *pd_name, bool enabled) { struct itmon_dev *itmon = g_itmon; struct itmon_platdata *pdata = itmon->pdata; struct itmon_nodegroup *group; int i; if (!pdata->probed) return; for (i = 0; i < pdata->num_nodegroup; i++) { group = &pdata->nodegroup[i]; if (group->pd_support && !strncmp(pd_name, group->pd_name, strlen(pd_name))) { dev_dbg(itmon->dev, "%s: pd_name:%s enabled:%x, pd_status:%x\n", __func__, pd_name, enabled, group->pd_status); if (group->pd_status != enabled) { if (enabled) itmon_init_by_group(itmon, group, enabled); group->pd_status = enabled; } return; } } } EXPORT_SYMBOL(itmon_pd_sync); void itmon_enable(bool enabled) { if (g_itmon) itmon_init(g_itmon, enabled); } EXPORT_SYMBOL(itmon_enable); int itmon_get_dpm_policy(struct itmon_dev *itmon) { struct itmon_platdata *pdata = itmon->pdata; int i, policy = -1; for (i = 0; i < TYPE_MAX; i++) { if (pdata->policy[i].error && pdata->policy[i].policy > policy) policy = pdata->policy[i].policy; } return policy; } static void itmon_dump_dpm_policy(struct itmon_dev *itmon) { struct itmon_platdata *pdata = itmon->pdata; int i; pr_err("\t\tError Policy Information\n\n\t\t> NAME |Error |Policy-def |Policy-now\n"); for (i = 0; i < TYPE_MAX; i++) { pr_info("\t\t> %15s%10s%10s%10s\n", pdata->policy[i].name, pdata->policy[i].error == true ? "TRUE" : "FALSE", itmon_dpm_action[pdata->policy[i].policy_def], itmon_dpm_action[pdata->policy[i].policy]); } pr_err("\n"); } static void itmon_do_dpm_policy(struct itmon_dev *itmon, bool clear) { struct itmon_platdata *pdata = itmon->pdata; int i, sel, policy = -1; for (i = 0; i < TYPE_MAX; i++) { if (pdata->policy[i].error && pdata->policy[i].policy > policy) { policy = pdata->policy[i].policy; sel = i; } if (clear) { pdata->policy[i].policy = pdata->policy[i].policy_def; pdata->policy[i].prio = 0; } } if (policy >= GO_DEFAULT_ID) { dev_err(itmon->dev, "%s: %s: policy:%s\n", __func__, pdata->policy[sel].name, itmon_dpm_action[policy]); dbg_snapshot_do_dpm_policy(policy); } } static void itmon_reflect_policy_by_notifier(struct itmon_dev *itmon, struct itmon_traceinfo *info, int ret) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_policy *pl_policy; int sig, num; if ((ret & ITMON_NOTIFY_MASK) != ITMON_NOTIFY_MASK) return; sig = ret & ~ITMON_NOTIFY_MASK; switch (info->errcode) { case ERRCODE_DECERR: num = DECERR; break; case ERRCODE_SLVERR: num = SLVERR; break; case ERRCODE_TMOUT: num = TMOUT; break; default: dev_warn(itmon->dev, "%s: Invalid error code(%u)\n", __func__, info->errcode); return; } pl_policy = &pdata->policy[num]; pr_err("\nPolicy Changes: %d -> %d in %s by notifier-call\n\n", pl_policy->policy, sig, itmon_errcode[info->errcode]); pl_policy->policy = sig; pl_policy->prio = CUSTOM_MAX_PRIO; } static int itmon_notifier_handler(struct itmon_dev *itmon, struct itmon_traceinfo *info, unsigned int trans_type) { int ret = 0; /* After treatment by port */ if (!info->port || strlen(info->port) < 1) return -1; itmon->notifier_info.port = info->port; itmon->notifier_info.master = info->master; itmon->notifier_info.dest = info->dest; itmon->notifier_info.read = info->read; itmon->notifier_info.target_addr = info->target_addr; itmon->notifier_info.errcode = info->errcode; itmon->notifier_info.onoff = info->onoff; pr_err("----------------------------------------------------------------------------------\n\t\t+ITMON Notifier Call Information\n"); /* call notifier_call_chain of itmon */ ret = atomic_notifier_call_chain(&itmon_notifier_list, 0, &itmon->notifier_info); pr_err("\t\t-ITMON Notifier Call Information\n----------------------------------------------------------------------------------\n"); return ret; } static void itmon_reflect_policy_by_node(struct itmon_dev *itmon, struct itmon_nodeinfo *node, int job) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_policy *pl_policy = &pdata->policy[job]; struct itmon_nodepolicy *nd_policy = &node->policy; u64 chk_job = 0, en_job = 0, en_mask = 0; pl_policy = &pdata->policy[job]; pl_policy->error = true; nd_policy = &node->policy; switch (job) { case TMOUT: chk_job = nd_policy->chk_tmout_job; en_job = nd_policy->en_tmout_job; en_mask = nd_policy->en_irq_mask; if (nd_policy->en_irq_mask && node->type == S_NODE) itmon_enable_timeout(itmon, node, false); break; case PRTCHKER: chk_job = nd_policy->chk_prtchk_job; en_job = nd_policy->en_prtchk_job; if (nd_policy->en_irq_mask) itmon_enable_prt_chk(itmon, node, false); break; case DECERR: chk_job = nd_policy->chk_decerr_job; en_job = nd_policy->en_decerr_job; if (nd_policy->en_irq_mask) itmon_enable_err_report(itmon, node, false); break; case SLVERR: chk_job = nd_policy->chk_slverr_job; en_job = nd_policy->en_slverr_job; if (nd_policy->en_irq_mask) itmon_enable_err_report(itmon, node, false); break; } if (chk_job && pl_policy->prio <= nd_policy->prio) { pl_policy->policy = en_job; pl_policy->prio = nd_policy->prio; } } static void itmon_reflect_policy(struct itmon_dev *itmon, struct itmon_traceinfo *info) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_policy *pl_policy = NULL; struct itmon_nodepolicy nd_policy1 = {0,}, nd_policy2 = {0,}, nd_policy = {0,}; u64 chk_job1 = 0, chk_job2 = 0, en_job1 = 0, en_job2 = 0, en_job = 0; if (info->s_node) { nd_policy1.enabled = info->s_node->policy.enabled; if (nd_policy1.en_irq_mask) { if (info->errcode == ERRCODE_TMOUT) itmon_enable_timeout(itmon, info->s_node, false); if (info->errcode == ERRCODE_DECERR || info->errcode == ERRCODE_SLVERR) itmon_enable_err_report(itmon, info->s_node, false); } } if (info->m_node) { nd_policy2.enabled = info->m_node->policy.enabled; if (nd_policy2.en_irq_mask) { if (info->errcode == ERRCODE_DECERR || info->errcode == ERRCODE_SLVERR) itmon_enable_err_report(itmon, info->m_node, false); } } switch (info->errcode) { case ERRCODE_DECERR: pl_policy = &pdata->policy[DECERR]; pl_policy->error = true; if (nd_policy1.chk_set) { chk_job1 = nd_policy1.chk_decerr_job; en_job1 = nd_policy1.en_decerr_job; } if (nd_policy2.chk_set) { chk_job2 = nd_policy2.chk_decerr_job; en_job2 = nd_policy2.en_decerr_job; } break; case ERRCODE_SLVERR: pl_policy = &pdata->policy[SLVERR]; pl_policy->error = true; if (nd_policy1.chk_set) { chk_job1 = nd_policy1.chk_slverr_job; en_job1 = nd_policy1.en_slverr_job; } if (nd_policy2.chk_set) { chk_job2 = nd_policy2.chk_slverr_job; en_job2 = nd_policy2.en_slverr_job; } break; case ERRCODE_TMOUT: pl_policy = &pdata->policy[TMOUT]; pl_policy->error = true; if (nd_policy1.chk_set) { chk_job1 = nd_policy1.chk_tmout_job; en_job1 = nd_policy1.en_tmout_job; } if (nd_policy2.chk_set) { chk_job2 = nd_policy2.chk_tmout_job; en_job2 = nd_policy2.en_tmout_job; } break; default: dev_warn(itmon->dev, "%s: Invalid error code(%u)\n", __func__, info->errcode); return; } if (chk_job1 && chk_job2) { if (nd_policy1.prio >= nd_policy2.prio) { nd_policy.enabled = nd_policy1.enabled; en_job = en_job1; } else { nd_policy.enabled = nd_policy2.enabled; en_job = en_job2; } } else if (chk_job1) { nd_policy = nd_policy1; en_job = en_job1; } else if (chk_job2) { nd_policy = nd_policy2; en_job = en_job2; } /* nd_policy = NULL */ if (nd_policy.chk_set && pl_policy->prio <= nd_policy.prio) { pl_policy->policy = en_job; pl_policy->prio = nd_policy.prio; } } static void itmon_post_handler(struct itmon_dev *itmon, bool err) { struct itmon_platdata *pdata = itmon->pdata; unsigned long ts = pdata->last_time; unsigned long rem_nsec = do_div(ts, 1000000000); unsigned long cur_time = local_clock(); unsigned long delta; delta = pdata->last_time == 0 ? 0 : cur_time - pdata->last_time; dev_err(itmon->dev, "Before ITMON: [%5lu.%06lu], delta: %lu, last_errcnt: %d\n", (unsigned long)ts, rem_nsec / 1000, delta, pdata->last_errcnt); if (err) itmon_do_dpm_policy(itmon, true); /* delta < 1s */ if (delta > 0 && delta < 1000000000UL) { pdata->last_errcnt++; if (pdata->last_errcnt > ERR_THRESHOLD) dbg_snapshot_do_dpm_policy(GO_S2D_ID); } else { pdata->last_errcnt = 0; } pdata->last_time = cur_time; } static void itmon_report_timeout(struct itmon_dev *itmon, struct itmon_nodeinfo *node, unsigned int trans_type) { struct itmon_platdata *pdata = itmon->pdata; unsigned int id, payload0, payload1, payload2, payload3, payload4, payload5; unsigned int axid, valid, timeout; unsigned long addr, user; struct itmon_nodegroup *group = NULL; int i, num = (trans_type == TRANS_TYPE_READ ? SZ_128 : SZ_64); int rw_offset = (trans_type == TRANS_TYPE_READ ? 0 : REG_TMOUT_BUF_WR_OFFSET); u32 axburst, axprot, axlen, axsize, domain; if (!node) return; group = node->group; pr_err("\nITMON Report (%s) Timeout Error Occurred : Master --> %s\n\n", trans_type == TRANS_TYPE_READ ? "READ" : "WRITE", node->name); if (pdata->big_bus) pr_err("> NUM| MASTER BLOCK| MASTER|VALID|TMOUT| ID|DOMAIN|SIZE|PROT| ADDRESS|PAYLOAD0|PAYLOAD1|PAYLOAD2|PAYLOAD3|PAYLOAD4|PAYLOAD5"); else pr_err("> NUM| MASTER BLOCK| MASTER|VALID|TMOUT| ID|DOMAIN|SIZE|PROT| ADDRESS|PAYLOAD0|PAYLOAD1|PAYLOAD2|PAYLOAD3"); for (i = 0; i < num; i++) { struct itmon_rpathinfo *port = NULL; struct itmon_masterinfo *master = NULL; writel(i, node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_POINT_ADDR + rw_offset); id = readl(node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_ID + rw_offset); payload0 = readl(node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_PAYLOAD_0 + rw_offset); if (pdata->big_bus) { payload1 = readl(node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_PAYLOAD_1 + rw_offset); payload5 = readl(node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_PAYLOAD_5 + rw_offset); } payload2 = readl(node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_PAYLOAD_2 + rw_offset); /* mid/small: payload 1 */ payload3 = readl(node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_PAYLOAD_3 + rw_offset); /* mid/small: payload 2 */ payload4 = readl(node->regs + OFFSET_TMOUT_REG + REG_TMOUT_BUF_PAYLOAD_4 + rw_offset); /* mid/small: payload 3 */ if (pdata->big_bus) { timeout = (payload0 & (unsigned int)(GENMASK(7, 4))) >> 4; user = ((unsigned long)payload2 << 32ULL) | payload1; addr = (((unsigned long)payload3 & GENMASK(15, 0)) << 32ULL) | payload4; } else { timeout = (payload0 & (unsigned int)(GENMASK(19, 16))) >> 16; user = (payload0 & (unsigned int)(GENMASK(15, 8))) >> 8; addr = (((unsigned long)payload2 & GENMASK(15, 0)) << 32ULL) | payload3; payload5 = payload4; } axid = id & GENMASK(5, 0); /* ID[5:0] 6bit : R-PATH */ valid = payload0 & BIT(0); /* PAYLOAD[0] : Valid or Not valid */ axburst = BIT_AXBURST(payload5); axprot = BIT_AXPROT(payload5); axlen = (payload5 & (unsigned int)GENMASK(7, 4)) >> 4; axsize = (payload5 & (unsigned int)GENMASK(18, 16)) >> 16; domain = (payload5 & BIT(19)); port = itmon_get_rpathinfo(itmon, axid, node->name); if (port) master = itmon_get_masterinfo(itmon, port->port_name, user); if (pdata->big_bus) pr_err("> %03d|%16s|%16s|%5u|%5x|%06x|%6s|%4u|%4s|%016zx|%08x|%08x|%08x|%08x|%08x|%08x\n", i, port ? port->port_name : NO_NAME, master ? master->master_name : NO_NAME, valid, timeout, id, domain ? "SHARE" : "NSHARE", int_pow(2, axsize * (axlen + 1)), axprot & BIT(1) ? "NSEC" : "SEC", addr, payload0, payload1, payload2, payload3, payload4, payload5); else pr_err("> %03d|%16s|%16s|%5u|%5x|%06x|%6s|%4u|%4s|%016zx|%08x|%08x|%08x|%08x\n", i, port ? port->port_name : NO_NAME, master ? master->master_name : NO_NAME, valid, timeout, id, domain ? "SHARE" : "NSHARE", int_pow(2, axsize * (axlen + 1)), axprot & BIT(1) ? "NSEC" : "SEC", addr, payload0, payload2, payload3, payload4); } } static void itmon_report_prt_chk_rawdata(struct itmon_dev *itmon, struct itmon_tracedata *data) { struct itmon_nodeinfo *node = data->node; #if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO) char temp_buf[SZ_128]; #endif /* Output Raw register information */ dev_err(itmon->dev, "\n-----------------------------------------" "-----------------------------------------\n" "\t\tProtocol Checker Raw Register Information" "(ITMON information)\n\n"); dev_err(itmon->dev, "\t\t> %s(%s, 0x%08llX)\n" "\t\t> REG(0x100~0x10C) : 0x%08X, 0x%08X, 0x%08X, 0x%08X\n", node->name, itmon_node_string[node->type], node->phy_regs, data->dbg_mo_cnt, data->prt_chk_ctl, data->prt_chk_info, data->prt_chk_int_id); #if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO) snprintf(temp_buf, SZ_128, "%s/ %s/ 0x%08X/ %s/ 0x%08X, 0x%08X, 0x%08X, 0x%08X", "Protocol Error", node->name, node->phy_regs, itmon_node_string[node->type], data->dbg_mo_cnt, data->prt_chk_ctl, data->prt_chk_info, data->prt_chk_int_id); secdbg_exin_set_busmon(temp_buf); #endif } static void itmon_report_rawdata(struct itmon_dev *itmon, struct itmon_tracedata *data) { struct itmon_nodeinfo *node = data->node; if (BIT_PRT_CHK_ERR_OCCURRED(data->prt_chk_info) && (data->prt_chk_info & GENMASK(8, 1))) { itmon_report_prt_chk_rawdata(itmon, data); return; } /* Output Raw register information */ pr_err("\t\tRaw Register Information ---------------------------------------------------\n" "\t\t> %s(%s, 0x%08llX)\n" "\t\t> REG(0x08~0x18,0x80) : 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X\n" "\t\t> REG(0x100~0x10C) : 0x%08X, 0x%08X, 0x%08X, 0x%08X\n", node->name, itmon_node_string[node->type], node->phy_regs + data->offset, data->int_info, data->ext_info_0, data->ext_info_1, data->ext_info_2, data->ext_user, data->dbg_mo_cnt, data->prt_chk_ctl, data->prt_chk_info, data->prt_chk_int_id); } static void itmon_report_traceinfo(struct itmon_dev *itmon, struct itmon_traceinfo *info, unsigned int trans_type) { #if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO) char temp_buf[SZ_128]; #endif /* SEC DEBUG FEATURE */ int acon = (itmon_get_dpm_policy(itmon) > 0) ? 1 : 0; /* SEC DEBUG FEATURE */ if (!info->dirty) return; pr_auto_on(acon, ASL3, "\n-----------------------------------------" "-----------------------------------------\n" "\t\tTransaction Information\n\n" "\t\t> Master : %s %s\n" "\t\t> Target : %s\n" "\t\t> Target Address : 0x%llX %s\n" "\t\t> Type : %s\n" "\t\t> Error code : %s\n", info->port, info->master ? info->master : "", info->dest ? info->dest : NO_NAME, info->target_addr, info->baaw_prot == true ? "(BAAW Remapped address)" : "", trans_type == TRANS_TYPE_READ ? "READ" : "WRITE", itmon_errcode[info->errcode]); #if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO) if (acon) { snprintf(temp_buf, SZ_128, "%s %s/ %s/ 0x%zx %s/ %s/ %s", info->port, info->master ? info->master : "", info->dest ? info->dest : NO_NAME, info->target_addr, info->baaw_prot == true ? "(by CP maybe)" : "", trans_type == TRANS_TYPE_READ ? "READ" : "WRITE", itmon_errcode[info->errcode]); secdbg_exin_set_busmon(temp_buf); } #endif pr_auto_on(acon, ASL3, "\n-----------------------------------------" "-----------------------------------------\n" "\t\t> Size : %u bytes x %u burst => %u bytes\n" "\t\t> Burst Type : %u (0:FIXED, 1:INCR, 2:WRAP)\n" "\t\t> Level : %s\n" "\t\t> Protection : %s\n" "\t\t> Path Type : %s\n\n", int_pow(2, info->axsize), info->axlen + 1, int_pow(2, info->axsize) * (info->axlen + 1), info->axburst, info->axprot & BIT(0) ? "Privileged" : "Unprivileged", info->axprot & BIT(1) ? "Non-secure" : "Secure", itmon_pathtype[info->path_type]); if (acon) { dbg_snapshot_set_str_offset(OFFSET_DSS_CUSTOM, SZ_128, "%s %s/ %s/ 0x%zx %s/ %s/ %s", info->port, info->master ? info->master : "", info->dest ? info->dest : NO_NAME, info->target_addr, info->baaw_prot == true ? "(BAAW Remapped address)" : "", trans_type == TRANS_TYPE_READ ? "READ" : "WRITE", itmon_errcode[info->errcode]); } } static void itmon_report_pathinfo(struct itmon_dev *itmon, struct itmon_tracedata *data, struct itmon_traceinfo *info, unsigned int trans_type) { struct itmon_nodeinfo *node = data->node; /* SEC DEBUG FEATURE */ #if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO) int acon = (itmon_get_dpm_policy(itmon) > 0) ? 1 : 0; #endif /* SEC DEBUG FEATURE */ if (BIT_PRT_CHK_ERR_OCCURRED(data->prt_chk_info) && (data->prt_chk_info & GENMASK(8, 1))) return; if (!info->path_dirty) { pr_auto_on(acon, ASL3, "\n-----------------------------------------" "-----------------------------------------\n" "\t\tITMON Report (%s)\n" "-----------------------------------------" "-----------------------------------------\n" "\t\tPATH Information\n\n", trans_type == TRANS_TYPE_READ ? "READ" : "WRITE"); info->path_dirty = true; } switch (node->type) { case M_NODE: pr_auto_on(acon, ASL3, "\t\t> %14s, %8s(0x%08llX)\n", node->name, "M_NODE", node->phy_regs + data->offset); break; case T_S_NODE: pr_auto_on(acon, ASL3, "\t\t> %14s, %8s(0x%08llX)\n", node->name, "T_S_NODE", node->phy_regs + data->offset); break; case T_M_NODE: pr_auto_on(acon, ASL3, "\t\t> %14s, %8s(0x%08llX)\n", node->name, "T_M_NODE", node->phy_regs + data->offset); break; case S_NODE: pr_auto_on(acon, ASL3, "\t\t> %14s, %8s(0x%08llX)\n", node->name, "S_NODE", node->phy_regs + data->offset); break; } } static int itmon_parse_cpuinfo(struct itmon_dev *itmon, struct itmon_tracedata *data, struct itmon_traceinfo *info, unsigned int userbit) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_nodeinfo *node = data->node; int cluster_num, core_num, i; for (i = 0; i < (int)ARRAY_SIZE(itmon_cpu_node_string); i++) { if (!strncmp(node->name, itmon_cpu_node_string[i], strlen(itmon_cpu_node_string[i]))) { core_num = (userbit & (GENMASK(2, 0))); cluster_num = 0; if (pdata->cpu_parsing) snprintf(info->buf, sizeof(info->buf) - 1, "CPU%d Cluster%d", core_num, cluster_num); else snprintf(info->buf, sizeof(info->buf) - 1, "CPU"); info->port = info->buf; return 1; } }; return 0; } static int itmon_chk_midnode(struct itmon_nodeinfo *node) { const char **string; int i, num; if (node->type == M_NODE) { num = (int)ARRAY_SIZE(itmon_mid_mnode_string); string = (const char **)&itmon_mid_mnode_string[0]; } else { num = (int)ARRAY_SIZE(itmon_mid_snode_string); string = (const char **)&itmon_mid_snode_string[0]; } for (i = 0; i < num; i++) { if (!strncmp(node->name, string[i], strlen(string[i]))) return 1; } return 0; } static void itmon_parse_traceinfo(struct itmon_dev *itmon, struct itmon_tracedata *data, unsigned int trans_type) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_traceinfo *new_info = NULL; struct itmon_tracedata *find_data = NULL; struct itmon_masterinfo *master = NULL; struct itmon_rpathinfo *port = NULL; struct itmon_nodeinfo *node = data->node, *find_node = NULL; struct itmon_nodegroup *group = data->group; unsigned int errcode, axid, userbit; int i, ret; if (BIT_PRT_CHK_ERR_OCCURRED(data->prt_chk_info) && (data->prt_chk_info & GENMASK(8, 1))) return; if (node->type != S_NODE && node->type != M_NODE) return; if (!BIT_ERR_VALID(data->int_info)) return; errcode = BIT_ERR_CODE(data->int_info); if (node->type == M_NODE) { if (errcode != ERRCODE_DECERR) return; if (itmon_chk_midnode(node)) return; } if (node->type == S_NODE) { if (errcode != ERRCODE_DECERR && itmon_chk_midnode(node)) return; } new_info = kzalloc(sizeof(struct itmon_traceinfo), GFP_ATOMIC); if (!new_info) { dev_err(itmon->dev, "failed to kmalloc for %s node\n", node->name); return; } axid = (unsigned int)BIT_AXID(data->int_info); if (group->path_type == DATA_FROM_EXT) userbit = BIT_AXUSER(data->ext_user); else userbit = BIT_AXUSER_PERI(data->ext_info_2); switch (node->type) { case S_NODE: new_info->port = (char *)"See M_NODE of Raw registers"; new_info->master = (char *)""; new_info->dest = node->name; new_info->s_node = node; port = itmon_get_rpathinfo(itmon, axid, node->name); list_for_each_entry(find_data, &pdata->datalist[trans_type], list) { find_node = find_data->node; if (find_node->type != M_NODE) continue; ret = itmon_parse_cpuinfo(itmon, find_data, new_info, userbit); if (ret || (port && !strncmp(find_node->name, port->port_name, strlen(find_node->name)))) { new_info->m_node = find_node; break; } } if (port) { new_info->port = port->port_name; master = itmon_get_masterinfo(itmon, new_info->port, userbit); if (master) new_info->master = master->master_name; } break; case M_NODE: new_info->dest = (char *)"See Target address"; new_info->port = node->name; new_info->master = (char *)""; new_info->m_node = node; ret = itmon_parse_cpuinfo(itmon, data, new_info, userbit); if (ret) break; master = itmon_get_masterinfo(itmon, node->name, userbit); if (master) new_info->master = master->master_name; break; } /* Common Information */ new_info->path_type = group->path_type; new_info->target_addr = (((u64)data->ext_info_1 & GENMASK(15, 0)) << 32ULL); new_info->target_addr |= data->ext_info_0; new_info->errcode = errcode; new_info->dirty = true; new_info->axsize = BIT_AXSIZE(data->ext_info_1); new_info->axlen = BIT_AXLEN(data->ext_info_1); new_info->axburst = BIT_AXBURST(data->ext_info_2); new_info->axprot = BIT_AXPROT(data->ext_info_2); new_info->baaw_prot = false; for (i = 0; i < (int)ARRAY_SIZE(itmon_invalid_addr); i++) { if (new_info->target_addr == itmon_invalid_addr[i]) { new_info->baaw_prot = true; break; } } data->ref_info = new_info; list_add(&new_info->list, &pdata->infolist[trans_type]); } static void itmon_analyze_errnode(struct itmon_dev *itmon) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_traceinfo *info, *next_info; struct itmon_tracedata *data, *next_data; unsigned int trans_type; int i, ret; /* Parse */ for (trans_type = 0; trans_type < TRANS_TYPE_NUM; trans_type++) { list_for_each_entry(data, &pdata->datalist[trans_type], list) itmon_parse_traceinfo(itmon, data, trans_type); } /* Report and Clean-up traceinfo */ for (trans_type = 0; trans_type < TRANS_TYPE_NUM; trans_type++) { list_for_each_entry_safe(info, next_info, &pdata->infolist[trans_type], list) { info->path_dirty = false; itmon_reflect_policy(itmon, info); ret = itmon_notifier_handler(itmon, info, trans_type); itmon_reflect_policy_by_notifier(itmon, info, ret); list_for_each_entry(data, &pdata->datalist[trans_type], list) { if (data->ref_info == info) itmon_report_pathinfo(itmon, data, info, trans_type); } itmon_report_traceinfo(itmon, info, trans_type); list_del(&info->list); kfree(info); } } itmon_dump_dpm_policy(itmon); /* Report Raw all tracedata and Clean-up tracedata and node */ for (trans_type = 0; trans_type < TRANS_TYPE_NUM; trans_type++) { for (i = M_NODE; i < NODE_TYPE; i++) { list_for_each_entry_safe(data, next_data, &pdata->datalist[trans_type], list) { if (i == data->node->type) { itmon_report_rawdata(itmon, data); list_del(&data->list); kfree(data); } } } } } static void itmon_collect_errnode(struct itmon_dev *itmon, struct itmon_nodegroup *group, struct itmon_nodeinfo *node, unsigned int offset) { struct itmon_platdata *pdata = itmon->pdata; struct itmon_tracedata *new_data = NULL; unsigned int int_info, info0, info1, info2, user = 0; unsigned int prt_chk_ctl, prt_chk_info, prt_chk_int_id, dbg_mo_cnt; bool read = TRANS_TYPE_WRITE; int_info = readl(node->regs + offset + REG_INT_INFO); info0 = readl(node->regs + offset + REG_EXT_INFO_0); info1 = readl(node->regs + offset + REG_EXT_INFO_1); info2 = readl(node->regs + offset + REG_EXT_INFO_2); if (group->path_type == DATA_FROM_EXT) user = readl(node->regs + offset + REG_EXT_USER); dbg_mo_cnt = readl(node->regs + OFFSET_PRT_CHK); prt_chk_ctl = readl(node->regs + OFFSET_PRT_CHK + REG_PRT_CHK_CTL); prt_chk_info = readl(node->regs + OFFSET_PRT_CHK + REG_PRT_CHK_INT); prt_chk_int_id = readl(node->regs + OFFSET_PRT_CHK + REG_PRT_CHK_INT_ID); switch (offset) { case OFFSET_REQ_R: read = TRANS_TYPE_READ; /* fall down */ case OFFSET_REQ_W: /* Only S-Node is able to make log to registers */ break; case OFFSET_RESP_R: read = TRANS_TYPE_READ; /* fall down */ case OFFSET_RESP_W: /* Only NOT S-Node is able to make log to registers */ break; default: pr_auto(ASL3, "Unknown Error - node:%s offset:%u\n", node->name, offset); break; } new_data = kzalloc(sizeof(struct itmon_tracedata), GFP_ATOMIC); if (!new_data) { pr_auto(ASL3, "failed to kmalloc for %s node %x offset\n", node->name, offset); return; } new_data->int_info = int_info; new_data->ext_info_0 = info0; new_data->ext_info_1 = info1; new_data->ext_info_2 = info2; new_data->ext_user = user; new_data->dbg_mo_cnt = dbg_mo_cnt; new_data->prt_chk_ctl = prt_chk_ctl; new_data->prt_chk_info = prt_chk_info; new_data->prt_chk_int_id = prt_chk_int_id; new_data->offset = offset; new_data->read = read; new_data->ref_info = NULL; new_data->group = group; new_data->node = node; if (BIT_ERR_VALID(int_info)) new_data->logging = true; else new_data->logging = false; list_add(&new_data->list, &pdata->datalist[read]); } static int __itmon_search_node(struct itmon_dev *itmon, struct itmon_nodegroup *group, bool clear) { struct itmon_nodeinfo *node = NULL; unsigned int val, offset, freeze; unsigned long vec, bit = 0; int i, ret = 0; if (group->phy_regs) { if (group->ex_table) vec = (unsigned long)readq(group->regs); else vec = (unsigned long)readl(group->regs); } else { vec = GENMASK(group->nodesize, 0); } if (!vec) return 0; node = group->nodeinfo; for_each_set_bit(bit, &vec, group->nodesize) { /* exist array */ for (i = 0; i < OFFSET_NUM; i++) { offset = i * OFFSET_ERR_REPT; /* Check Request information */ val = readl(node[bit].regs + offset + REG_INT_INFO); if (BIT_ERR_OCCURRED(val)) { /* This node occurs the error */ itmon_collect_errnode(itmon, group, &node[bit], offset); if (clear) writel(1, node[bit].regs + offset + REG_INT_CLR); ret++; } } /* Check H/W assertion */ if (node[bit].prt_chk_enabled) { val = readl(node[bit].regs + OFFSET_PRT_CHK + REG_PRT_CHK_INT); /* if timeout_freeze is enable, * prt_chk interrupt is able to assert without any information */ if (BIT_PRT_CHK_ERR_OCCURRED(val) && (val & GENMASK(8, 1))) { itmon_collect_errnode(itmon, group, &node[bit], 0); itmon_reflect_policy_by_node(itmon, &node[bit], PRTCHKER); ret++; } } /* Check Timeout freeze enable node */ if (node[bit].tmout_enabled) { val = readl(node[bit].regs + OFFSET_TMOUT_REG + REG_TMOUT_FRZ_STATUS ); freeze = val & (unsigned int)GENMASK(1, 0); if (freeze) { if (freeze & BIT(0)) itmon_report_timeout(itmon, &node[bit], TRANS_TYPE_WRITE); if (freeze & BIT(1)) itmon_report_timeout(itmon, &node[bit], TRANS_TYPE_READ); itmon_collect_errnode(itmon, group, &node[bit], 0); itmon_reflect_policy_by_node(itmon, &node[bit], TMOUT); ret++; } } } return ret; } static int itmon_search_node(struct itmon_dev *itmon, struct itmon_nodegroup *group, bool clear) { struct itmon_platdata *pdata = itmon->pdata; int i, ret = 0; unsigned long flags; spin_lock_irqsave(&itmon->ctrl_lock, flags); if (group) { ret = __itmon_search_node(itmon, group, clear); } else { /* Processing all group & nodes */ for (i = 0; i < pdata->num_nodegroup; i++) { group = &pdata->nodegroup[i]; if (group->pd_support && !group->pd_status) continue; ret += __itmon_search_node(itmon, group, clear); } } if (ret) itmon_analyze_errnode(itmon); spin_unlock_irqrestore(&itmon->ctrl_lock, flags); return ret; } static irqreturn_t itmon_irq_handler(int irq, void *data) { struct itmon_dev *itmon = (struct itmon_dev *)data; struct itmon_platdata *pdata = itmon->pdata; struct itmon_nodegroup *group = NULL; bool ret; int i; /* Search itmon group */ for (i = 0; i < (int)pdata->num_nodegroup; i++) { group = &pdata->nodegroup[i]; if (!group->pd_support) { dev_err(itmon->dev, "%d irq, %s group, pd %s, 0x%x vec", irq, group->name, "on", readl(group->regs)); } else { dev_err(itmon->dev, "%d irq, %s group, pd %s, 0x%x vec", irq, group->name, group->pd_status ? "on" : "off", group->pd_status ? readl(group->regs) : 0); } } ret = itmon_search_node(itmon, NULL, true); if (!ret) dev_err(itmon->dev, "No errors found %s\n", __func__); else dev_err(itmon->dev, "Error detected %s, %d\n", __func__, ret); itmon_post_handler(itmon, ret); return IRQ_HANDLED; } void itmon_notifier_chain_register(struct notifier_block *block) { atomic_notifier_chain_register(&itmon_notifier_list, block); } EXPORT_SYMBOL(itmon_notifier_chain_register); static int itmon_logging_panic_handler(struct notifier_block *nb, unsigned long l, void *buf) { struct itmon_panic_block *itmon_panic = (struct itmon_panic_block *)nb; struct itmon_dev *itmon = itmon_panic->pdev; int ret; if (!IS_ERR_OR_NULL(itmon)) { /* Check error has been logged */ ret = itmon_search_node(itmon, NULL, true); if (!ret) { dev_info(itmon->dev, "No errors found %s\n", __func__); } else { dev_err(itmon->dev, "Error detected %s, %d\n", __func__, ret); itmon_do_dpm_policy(itmon, true); } } return 0; } #define MAX_CUSTOMIZE 64 static void itmon_parse_dt_customize(struct itmon_dev *itmon, struct device_node *np) { struct device_node *node_np; struct itmon_platdata *pdata = itmon->pdata; struct itmon_policy *plat_policy; struct itmon_nodepolicy nd_policy; struct itmon_nodeinfo *node; char snode_name[16]; const char *node_name; unsigned int val; int i; for (i = 0; i < MAX_CUSTOMIZE; i++) { nd_policy.enabled = 0; snprintf(snode_name, sizeof(snode_name), "node%d", i); node_np = of_get_child_by_name(np, (const char *)snode_name); if (!node_np) { dev_info(itmon->dev, "%s device node is not found\n", snode_name); break; } if(of_property_read_string(node_np, "node", &node_name)) continue; node = itmon_get_nodeinfo(itmon, NULL, node_name); if (!node) { dev_err(itmon->dev, "%s node is not found\n", node_name); continue; } else { dev_info(itmon->dev, "%s node found\n", node->name); } if (!of_property_read_u32(node_np, "irq_mask", &val)) { nd_policy.chk_irq_mask = 1; nd_policy.en_irq_mask = val; dev_info(itmon->dev, "%s node: irq mask -> [%d]\n", node->name, val); } if (!of_property_read_u32(node_np, "prio", &val)) { nd_policy.prio = val; dev_info(itmon->dev, "%s node: prio -> [%d]\n", node->name, val); } if (!of_property_read_u32(node_np, "errrpt", &val)) { nd_policy.chk_errrpt = 1; nd_policy.en_errrpt = val; dev_info(itmon->dev, "%s node: errrtp [%d] -> [%d]\n", node->name, node->err_enabled, val); } if (!of_property_read_u32(node_np, "prtchker", &val)) { nd_policy.chk_prtchk = 1; nd_policy.en_prtchk = val; dev_info(itmon->dev, "%s node: prtchker [%d] -> [%d]\n", node->name, node->prt_chk_enabled, val); } if (!of_property_read_u32(node_np, "tmout", &val)) { nd_policy.chk_tmout = 1; nd_policy.en_tmout = val; dev_info(itmon->dev, "%s node: tmout [%d] -> [%d]\n", node->name, node->tmout_enabled, val); } if (!of_property_read_u32(node_np, "tmout_val", &val)) { nd_policy.chk_tmout_val = 1; nd_policy.en_tmout_val = val; dev_info(itmon->dev, "%s node: tmout_val [%x] -> [%x]\n", node->name, node->time_val, val); } if (!of_property_read_u32(node_np, "freeze", &val)) { nd_policy.chk_freeze = 1; nd_policy.en_freeze = val; dev_info(itmon->dev, "%s node: freeze [%x] -> [%x]\n", node->name, node->tmout_frz_enabled, val); } if (!of_property_read_u32(node_np, "decerr_job", &val)) { plat_policy = &pdata->policy[DECERR]; nd_policy.chk_decerr_job = 1; nd_policy.en_decerr_job = val; dev_info(itmon->dev, "%s node: decerr_job [%s] -> [%d/%s]\n", node->name, itmon_dpm_action[plat_policy->policy_def], nd_policy.chk_decerr_job, itmon_dpm_action[nd_policy.en_decerr_job]); } if (!of_property_read_u32(node_np, "slverr_job", &val)) { plat_policy = &pdata->policy[SLVERR]; nd_policy.chk_slverr_job = 1; nd_policy.en_slverr_job = val; dev_info(itmon->dev, "%s node: slverr_job [%s] -> [%d/%s]\n", node->name, itmon_dpm_action[plat_policy->policy_def], nd_policy.chk_slverr_job, itmon_dpm_action[nd_policy.en_slverr_job]); } if (!of_property_read_u32(node_np, "tmout_job", &val)) { plat_policy = &pdata->policy[TMOUT]; nd_policy.chk_tmout_job = 1; nd_policy.en_tmout_job = val; dev_info(itmon->dev, "%s node: tmout_job [%s] -> [%d/%s]\n", node->name, itmon_dpm_action[plat_policy->policy_def], nd_policy.chk_tmout_job, itmon_dpm_action[nd_policy.en_tmout_job]); } if (!of_property_read_u32(node_np, "prtchker_job", &val)) { plat_policy = &pdata->policy[PRTCHKER]; nd_policy.chk_prtchk_job = 1; nd_policy.en_prtchk_job = val; dev_info(itmon->dev, "%s node: prtchker_job [%s] -> [%d/%s]\n", node->name, itmon_dpm_action[plat_policy->policy_def], nd_policy.chk_prtchk_job, itmon_dpm_action[nd_policy.en_prtchk_job]); } itmon_set_nodepolicy(itmon, node, nd_policy, false); } itmon_dump_dpm_policy(itmon); } enum itmon_ipc_cmd { eITMON_IPC_CMD_INIT, eITMON_IPC_CMD_SET_ENABLE, eITMON_IPC_CMD_GET_ENABLE, }; __maybe_unused static void adv_tracer_itmon_handler( struct adv_tracer_ipc_cmd *cmd, unsigned int len) { switch (cmd->cmd_raw.cmd) { default: break; } } static int itmon_adv_tracer_init(struct itmon_dev *itmon, struct device_node *np) { struct itmon_platdata *pdata = itmon->pdata; struct adv_tracer_plugin *itmon_adv = NULL; struct adv_tracer_ipc_cmd cmd; int ret = 0; itmon_adv = kzalloc(sizeof(struct adv_tracer_plugin), GFP_KERNEL); if (!itmon_adv) { ret = -ENOMEM; return ret; } ret = adv_tracer_ipc_request_channel(np, (ipc_callback)adv_tracer_itmon_handler, &itmon_adv->id, &itmon_adv->len); if (ret < 0) { dev_err(itmon->dev, "ipc request fail(%d): Request Channel\n", ret); ret = -ENODEV; kfree(itmon_adv); pdata->itmon_adv = NULL; return ret; } pdata->itmon_adv = itmon_adv; dev_err(itmon->dev, "%s successful - id:0x%x, len:0x%x\n", __func__, itmon_adv->id, itmon_adv->len); cmd.cmd_raw.cmd = eITMON_IPC_CMD_INIT; ret = adv_tracer_ipc_send_data(itmon_adv->id, &cmd); if (ret < 0) { dev_err(itmon->dev, "ipc request fail: CMD_INIT(%d)\n", ret); ret = -ENODEV; kfree(itmon_adv); pdata->itmon_adv = NULL; return ret; } pdata->adv_enabled = true; dev_err(itmon->dev, "MINITMON Activation\n"); return ret; } static int itmon_adv_tracer_set_enable(struct itmon_dev *itmon, bool enable) { struct itmon_platdata *pdata = itmon->pdata; struct adv_tracer_plugin *itmon_adv = pdata->itmon_adv; struct adv_tracer_ipc_cmd cmd; int ret = 0; if (!itmon_adv) return 0; cmd.cmd_raw.cmd = eITMON_IPC_CMD_SET_ENABLE; cmd.buffer[1] = enable; ret = adv_tracer_ipc_send_data(itmon_adv->id, &cmd); if (ret < 0) { dev_err(itmon->dev, "ipc request fail: CMD_SET_ENABLE(%d, %d)\n", enable, ret); return ret; } pdata->adv_enabled = enable; return 0; } static void itmon_parse_dt(struct itmon_dev *itmon) { struct device_node *np = itmon->dev->of_node; struct device_node *child_np; struct itmon_platdata *pdata = itmon->pdata; unsigned int val; int i, ret; if (of_property_read_bool(np, "big-bus-supporting")) { dev_info(itmon->dev, "big bus support\n"); pdata->big_bus = true; } else { pdata->big_bus = false; } if (of_property_read_bool(np, "support-irq-oring")) { dev_info(itmon->dev, "only 1 irq support\n"); pdata->irq_oring = true; } else { pdata->irq_oring = false; } if (of_property_read_bool(np, "no-support-cpu-parsing")) { dev_info(itmon->dev, "CPU Parser is not support\n"); pdata->cpu_parsing = false; } else { pdata->cpu_parsing = true; } for (i = 0; i < TYPE_MAX; i++) { if (!of_property_read_u32(np, pdata->policy[i].name, &val)) { pdata->policy[i].policy_def = val; pdata->policy[i].policy = val; } } child_np = of_get_child_by_name(np, "customize"); if (!child_np) { dev_info(itmon->dev, "customize is not found\n"); return; } itmon_parse_dt_customize(itmon, child_np); child_np = of_get_child_by_name(np, "dbgc"); if (!child_np) { dev_info(itmon->dev, "dbgc is not found\n"); return; } ret = itmon_adv_tracer_init(itmon, child_np); if (ret < 0) dev_info(itmon->dev, "minitmon is not support\n"); } static int itmon_load_keepdata(struct itmon_dev *itmon) { struct itmon_platdata *pdata = itmon->pdata; struct reserved_mem *rmem; struct device_node *rmem_np; struct device *dev = itmon->dev; struct itmon_keepdata *kdata; pgprot_t prot = __pgprot(PROT_NORMAL); int page_size, i; struct page *page, **pages; unsigned long flags = VM_NO_GUARD | VM_MAP; void *vaddr; rmem_np = of_parse_phandle(dev->of_node, "memory-region", 0); rmem = of_reserved_mem_lookup(rmem_np); if (!rmem) { dev_err(dev, "failed to acquire memory region\n"); return -ENOMEM; } page_size = rmem->size / PAGE_SIZE; pages = kzalloc(sizeof(struct page *) * page_size, GFP_KERNEL); page = phys_to_page(rmem->base); for (i = 0; i < page_size; i++) pages[i] = page++; vaddr = vmap(pages, page_size, flags, prot); kfree(pages); if (!vaddr) { dev_err(dev, "paddr:%x page_size:%x failed to vmap\n", rmem->base, page_size); return -ENOMEM; } kdata = (struct itmon_keepdata *)(vaddr); if (kdata->magic != 0xBEEFBEEF) { dev_err(dev, "Invalid Keep data of ITMON\n"); return -EFAULT; } dev_info(dev, "reserved - base: 0x%x, magic: 0x%x, rpathinfo: 0x%x," "masterinfo: 0x%x, nodegroup: 0x%x\n", (u64)kdata->base, kdata->magic, kdata->mem_rpathinfo, kdata->mem_masterinfo, kdata->mem_nodegroup); pdata->rpathinfo = (struct itmon_rpathinfo *) ((u64)vaddr + (u64)(kdata->mem_rpathinfo - kdata->base)); pdata->num_rpathinfo = kdata->num_rpathinfo; pdata->masterinfo = (struct itmon_masterinfo *) ((u64)vaddr + (u64)(kdata->mem_masterinfo - kdata->base)); pdata->num_masterinfo = kdata->num_masterinfo; pdata->nodegroup = (struct itmon_nodegroup *) ((u64)vaddr + (u64)(kdata->mem_nodegroup - kdata->base)); pdata->num_nodegroup = kdata->num_nodegroup; for (i = 0; i < (int)pdata->num_nodegroup; i++) { u64 offset = (((u64)(pdata->nodegroup[i].nodeinfo_phy)) - (u64)(kdata->base)); pdata->nodegroup[i].nodeinfo = (struct itmon_nodeinfo *)((u64)vaddr + (u64)offset); } return 0; } static ssize_t enable_all_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; return sysfs_emit(buf, "set itmon enabled: %d\n", pdata->enabled); } static ssize_t enable_all_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { unsigned long val = 0; if (!buf || size == 0) return -EINVAL; if ((kstrtoul(buf, SZ_16, &val) != 0) || (val != 1 && val != 0)) return -EINVAL; itmon_enable(val); return size; } static ssize_t timeout_fix_val_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; return sysfs_emit(buf, "set timeout val: %#x\n", pdata->sysfs_tmout_val); } static ssize_t timeout_fix_val_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; unsigned long val = 0; if (!buf || size == 0) return -EINVAL; if (kstrtoul(buf, SZ_16, &val) != 0 && val != (u32) val) return -EINVAL; pdata->sysfs_tmout_val = (unsigned int)val; return size; } static ssize_t timeout_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; unsigned int i; ssize_t n = 0; /* Processing all group & nodes */ for (i = 0; i < pdata->num_nodegroup; i++) { struct itmon_nodegroup *group = &pdata->nodegroup[i]; struct itmon_nodeinfo *node = group->nodeinfo; unsigned long vec = GENMASK(group->nodesize, 0), bit = 0; unsigned int offset = OFFSET_TMOUT_REG; if (group->pd_support && !group->pd_status) continue; for_each_set_bit(bit, &vec, group->nodesize) { if (node[bit].type == S_NODE && node[bit].tmout_enabled) { n += scnprintf(buf + n, PAGE_SIZE - n, "%-12s : 0x%08X, timeout : %x\n", node[bit].name, node[bit].phy_regs, readl_relaxed(node[bit].regs + offset + REG_DBG_CTL)); } } } return n; } static ssize_t timeout_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; unsigned int i; char *name; int len; if (!buf || size == 0) return -EINVAL; len = min(size, strlen(buf)); name = (char *)kstrndup(buf, len, GFP_KERNEL); if (!name) return -ENOMEM; for (i = 0; i < pdata->num_nodegroup; i++) { struct itmon_nodegroup *group = &pdata->nodegroup[i]; struct itmon_nodeinfo *node = group->nodeinfo; unsigned long vec = GENMASK(group->nodesize, 0), bit = 0; unsigned int offset = OFFSET_TMOUT_REG; if (group->pd_support && !group->pd_status) continue; for_each_set_bit(bit, &vec, group->nodesize) { if (node[bit].type == S_NODE && node[bit].tmout_enabled && !strncmp(name, node[bit].name, len)) { u32 val = readl_relaxed(node[bit].regs + offset + REG_DBG_CTL); writel_relaxed(val ? 0 : 1, node[bit].regs + offset + REG_DBG_CTL); node[bit].tmout_enabled = val; } } } kfree(name); return size; } static ssize_t timeout_val_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; unsigned int i; ssize_t n = 0; /* Processing all group & nodes */ for (i = 0; i < pdata->num_nodegroup; i++) { struct itmon_nodegroup *group = &pdata->nodegroup[i]; struct itmon_nodeinfo *node = group->nodeinfo; unsigned long vec = GENMASK(group->nodesize, 0), bit = 0; unsigned int offset = OFFSET_TMOUT_REG; if (group->pd_support && !group->pd_status) continue; for_each_set_bit(bit, &vec, group->nodesize) { if (node[bit].type == S_NODE && node[bit].tmout_enabled) { n += scnprintf(buf + n, PAGE_SIZE - n, "%-12s : 0x%08X, timeout : 0x%x\n", node[bit].name, node[bit].phy_regs, readl_relaxed(node[bit].regs + offset + REG_TMOUT_INIT_VAL)); } } } return n; } static ssize_t timeout_val_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; unsigned int i; char *name; int len; if (!buf || size == 0) return -EINVAL; len = min(size, strlen(buf)); name = (char *)kstrndup(buf, len, GFP_KERNEL); if (!name) return -ENOMEM; for (i = 0; i < pdata->num_nodegroup; i++) { struct itmon_nodegroup *group = &pdata->nodegroup[i]; struct itmon_nodeinfo *node = group->nodeinfo; unsigned long vec = GENMASK(group->nodesize, 0), bit = 0; unsigned int offset = OFFSET_TMOUT_REG; if (group->pd_support && !group->pd_status) continue; for_each_set_bit(bit, &vec, group->nodesize) { if (node[bit].type == S_NODE && node[bit].tmout_enabled && !strncmp(name, node[bit].name, len)) { writel_relaxed(pdata->sysfs_tmout_val, node[bit].regs + offset + REG_TMOUT_INIT_VAL); node[bit].time_val = pdata->sysfs_tmout_val; } } } kfree(name); return size; } static ssize_t timeout_freeze_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; unsigned int i; ssize_t n = 0; /* Processing all group & nodes */ for (i = 0; i < pdata->num_nodegroup; i++) { struct itmon_nodegroup *group = &pdata->nodegroup[i]; struct itmon_nodeinfo *node = group->nodeinfo; unsigned long vec = GENMASK(group->nodesize, 0), bit = 0; unsigned int offset = OFFSET_TMOUT_REG; if (group->pd_support && !group->pd_status) continue; for_each_set_bit(bit, &vec, group->nodesize) { if (node[bit].type == S_NODE && node[bit].tmout_enabled) { n += scnprintf(buf + n, PAGE_SIZE - n, "%-12s : 0x%08X, timeout_freeze : %x\n", node[bit].name, node[bit].phy_regs, readl_relaxed(node[bit].regs + offset + REG_TMOUT_FRZ_EN)); } } } return n; } static ssize_t timeout_freeze_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct itmon_platdata *pdata = g_itmon->pdata; unsigned int i; char *name; int len; if (!buf || size == 0) return -EINVAL; len = min(size, strlen(buf)); name = (char *)kstrndup(buf, len, GFP_KERNEL); if (!name) return -ENOMEM; for (i = 0; i < pdata->num_nodegroup; i++) { struct itmon_nodegroup *group = &pdata->nodegroup[i]; struct itmon_nodeinfo *node = group->nodeinfo; unsigned long vec = GENMASK(group->nodesize, 0), bit = 0; unsigned int offset = OFFSET_TMOUT_REG; if (group->pd_support && !group->pd_status) continue; for_each_set_bit(bit, &vec, group->nodesize) { if (node[bit].type == S_NODE && node[bit].tmout_enabled && !strncmp(name, node[bit].name, len)) { u32 val = readl_relaxed(node[bit].regs + offset + REG_TMOUT_FRZ_EN); writel_relaxed(val ? 0 : 1, node[bit].regs + offset + REG_TMOUT_FRZ_EN); node[bit].tmout_frz_enabled = val; } } } kfree(name); return size; } static DEVICE_ATTR_RW(enable_all); static DEVICE_ATTR_RW(timeout_fix_val); static DEVICE_ATTR_RW(timeout); static DEVICE_ATTR_RW(timeout_val); static DEVICE_ATTR_RW(timeout_freeze); static struct attribute *itmon_sysfs_attrs[] = { &dev_attr_enable_all.attr, &dev_attr_timeout_fix_val.attr, &dev_attr_timeout.attr, &dev_attr_timeout_val.attr, &dev_attr_timeout_freeze.attr, NULL, }; ATTRIBUTE_GROUPS(itmon_sysfs); static void itmon_set_irq_affinity(struct itmon_dev *itmon, unsigned int irq, unsigned long affinity) { struct cpumask affinity_mask; unsigned long bit; char *buf = (char *)__get_free_page(GFP_KERNEL); cpumask_clear(&affinity_mask); if (affinity) { for_each_set_bit(bit, &affinity, nr_cpu_ids) cpumask_set_cpu(bit, &affinity_mask); } else { cpumask_copy(&affinity_mask, cpu_online_mask); } irq_set_affinity_hint(irq, &affinity_mask); cpumap_print_to_pagebuf(true, buf, &affinity_mask); dev_dbg(itmon->dev, "affinity of irq%d is %s", irq, buf); free_page((unsigned long)buf); } static int itmon_probe(struct platform_device *pdev) { struct itmon_dev *itmon; struct itmon_panic_block *itmon_panic = NULL; struct itmon_platdata *pdata; struct itmon_nodeinfo *node; unsigned int irq, val = 0; char *dev_name; int ret, i, j; itmon = devm_kzalloc(&pdev->dev, sizeof(struct itmon_dev), GFP_KERNEL); if (!itmon) return -ENOMEM; itmon->dev = &pdev->dev; spin_lock_init(&itmon->ctrl_lock); pdata = devm_kzalloc(&pdev->dev, sizeof(struct itmon_platdata), GFP_KERNEL); if (!pdata) return -ENOMEM; itmon->pdata = pdata; if (itmon_load_keepdata(itmon) < 0) return -ENOMEM; itmon->pdata->policy = err_policy; itmon_parse_dt(itmon); for (i = 0; i < (int)pdata->num_nodegroup; i++) { dev_name = pdata->nodegroup[i].name; node = pdata->nodegroup[i].nodeinfo; if (pdata->nodegroup[i].phy_regs) { pdata->nodegroup[i].regs = devm_ioremap(&pdev->dev, pdata->nodegroup[i].phy_regs, SZ_16K); if (pdata->nodegroup[i].regs == NULL) { dev_err(&pdev->dev, "failed to claim register region - %s\n", dev_name); return -ENOENT; } } for (j = 0; j < pdata->nodegroup[i].nodesize; j++) { node[j].regs = devm_ioremap(&pdev->dev, node[j].phy_regs, SZ_16K); if (node[j].regs == NULL) { dev_err(&pdev->dev, "failed to claim register region - %s\n", dev_name); return -ENOENT; } node[j].group = &pdata->nodegroup[i]; } irq = irq_of_parse_and_map(pdev->dev.of_node, i); pdata->nodegroup[i].irq = SET_IRQ(irq); if (!irq) continue; of_property_read_u32_index(pdev->dev.of_node, "interrupt-affinity", i, &val); itmon_set_irq_affinity(itmon, irq, val); pdata->nodegroup[i].irq |= SET_AFFINITY(val); irq_set_status_flags(irq, IRQ_NOAUTOEN); disable_irq(irq); ret = devm_request_irq(&pdev->dev, irq, itmon_irq_handler, IRQF_NOBALANCING, dev_name, itmon); if (ret == 0) { dev_info(&pdev->dev, "success to register request irq%u - %s\n", irq, dev_name); } else { dev_err(&pdev->dev, "failed to request irq - %s\n", dev_name); return -ENOENT; } } itmon_panic = devm_kzalloc(&pdev->dev, sizeof(struct itmon_panic_block), GFP_KERNEL); if (!itmon_panic) { dev_err(&pdev->dev, "failed to allocate memory for driver's " "panic handler data\n"); } else { itmon_panic->nb_panic_block.notifier_call = itmon_logging_panic_handler; itmon_panic->pdev = itmon; atomic_notifier_chain_register(&panic_notifier_list, &itmon_panic->nb_panic_block); } platform_set_drvdata(pdev, itmon); itmon_init(itmon, true); pdata->last_time = 0; pdata->last_errcnt = 0; g_itmon = itmon; ret = sysfs_create_groups(&pdev->dev.kobj, itmon_sysfs_groups); if (ret) dev_info(&pdev->dev, "fail to register sysfs\n"); for (i = 0; i < TRANS_TYPE_NUM; i++) { INIT_LIST_HEAD(&pdata->datalist[i]); INIT_LIST_HEAD(&pdata->infolist[i]); } pdata->probed = true; dev_info(&pdev->dev, "success to probe Exynos ITMON driver\n"); for (i = 0; i < (int)pdata->num_nodegroup; i++) if (pdata->nodegroup[i].irq) enable_irq(GET_IRQ(pdata->nodegroup[i].irq)); return 0; } static int itmon_remove(struct platform_device *pdev) { platform_set_drvdata(pdev, NULL); return 0; } #ifdef CONFIG_PM_SLEEP static int itmon_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); itmon_adv_tracer_set_enable(itmon, false); dev_info(dev, "%s\n", __func__); return 0; } static int itmon_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct itmon_dev *itmon = platform_get_drvdata(pdev); struct itmon_platdata *pdata = itmon->pdata; int i; for (i = 0; i < (int)pdata->num_nodegroup; i++) { unsigned int irq = pdata->nodegroup[i].irq; if (irq) itmon_set_irq_affinity(itmon, GET_IRQ(irq), GET_AFFINITY(irq)); } itmon_init(itmon, true); itmon_adv_tracer_set_enable(itmon, true); dev_info(dev, "%s\n", __func__); return 0; } static SIMPLE_DEV_PM_OPS(itmon_pm_ops, itmon_suspend, itmon_resume); #define ITMON_PM (itmon_pm_ops) #else #define ITM_ONPM NULL #endif static struct platform_driver exynos_itmon_driver = { .probe = itmon_probe, .remove = itmon_remove, .driver = { .name = "exynos-itmon", .of_match_table = itmon_dt_match, .pm = &itmon_pm_ops, }, }; module_platform_driver(exynos_itmon_driver); MODULE_DESCRIPTION("Samsung Exynos ITMON COMMON DRIVER"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:exynos-itmon");