// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2020 MediaTek Inc. */ #define pr_fmt(fmt) "pob_qos: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pob_int.h" #include "pob_qos.h" #include "pob_pfm.h" #define TRACELOG_SIZE 512 struct pob_qos_trace_info { uint32_t flag; int typemap; char *prefix; void (*pHdlfun)(struct pob_qos_trace_info *pqti, struct pob_qos_info *pqi); }; static BLOCKING_NOTIFIER_HEAD(pob_qos_notifier_list); static BLOCKING_NOTIFIER_HEAD(pob_qos_ind_notifier_list); static DEFINE_MUTEX(pob_qos_ntf_mutex); static int pob_qos_ntf_cnt; static inline void _trace_pob_log(char *log) { preempt_disable(); trace_pob_log(log); preempt_enable(); } static void pob_fn_tracelog(void *pfn, char *prefix) { char log[TRACELOG_SIZE]; int tmplen; tmplen = snprintf(log, TRACELOG_SIZE, "%s %pS", prefix, pfn); if (tmplen < 0 || tmplen >= sizeof(log)) return; _trace_pob_log(log); } static int __pob_dump_ntf_callchain(struct notifier_block **nl, char *prefix) { int ret = NOTIFY_DONE; struct notifier_block *nb, *next_nb; nb = rcu_dereference_raw(*nl); while (nb) { next_nb = rcu_dereference_raw(nb->next); pob_fn_tracelog(nb->notifier_call, prefix); nb = next_nb; } return ret; } static int pob_dump_ntf_callchain(struct blocking_notifier_head *nh, char *prefix) { int ret = NOTIFY_DONE; /* * We check the head outside the lock, but if this access is * racy then it does not matter what the result of the test * is, we re-check the list after having taken the lock anyway: */ if (rcu_access_pointer(nh->head)) { down_read(&nh->rwsem); ret = __pob_dump_ntf_callchain(&nh->head, prefix); up_read(&nh->rwsem); } return ret; } int pob_qos_register_client(struct notifier_block *nb) { mutex_lock(&pob_qos_ntf_mutex); if (!pob_qos_ntf_cnt) pob_qos_pfm_enable(); pob_qos_ntf_cnt++; mutex_unlock(&pob_qos_ntf_mutex); pob_fn_tracelog(nb->notifier_call, "pob_qos register_client"); pob_dump_ntf_callchain(&pob_qos_notifier_list, "pob_qos register_client before"); return blocking_notifier_chain_register(&pob_qos_notifier_list, nb); } int pob_qos_unregister_client(struct notifier_block *nb) { mutex_lock(&pob_qos_ntf_mutex); if (pob_qos_ntf_cnt) pob_qos_ntf_cnt--; if (!pob_qos_ntf_cnt) pob_qos_pfm_disable(); mutex_unlock(&pob_qos_ntf_mutex); pob_fn_tracelog(nb->notifier_call, "pob_qos unregister_client"); pob_dump_ntf_callchain(&pob_qos_notifier_list, "pob_qos unregister_client before"); return blocking_notifier_chain_unregister(&pob_qos_notifier_list, nb); } int pob_qos_notifier_call_chain(unsigned long val, void *v) { return blocking_notifier_call_chain(&pob_qos_notifier_list, val, v); } int pob_qos_monitor_update(enum pob_qos_info_num info_num, void *v) { pob_qos_notifier_call_chain(info_num, v); return 0; } int pob_qos_ind_register_client(struct notifier_block *nb) { return blocking_notifier_chain_register(&pob_qos_ind_notifier_list, nb); } int pob_qos_ind_unregister_client(struct notifier_block *nb) { return blocking_notifier_chain_unregister(&pob_qos_ind_notifier_list, nb); } int pob_qos_ind_notifier_call_chain(unsigned long val, void *v) { return blocking_notifier_call_chain(&pob_qos_ind_notifier_list, val, v); } int pob_qos_ind_monitor_update(enum pob_qos_ind_info_num info_num, void *v) { pob_qos_ind_notifier_call_chain(info_num, v); return 0; } int pob_qos_ind_client_isemtpy(void) { if (pob_qos_ind_notifier_list.head) return 0; return 1; } enum { POB_QOS_DEBUG_QOSSEQ = 1U << 0, POB_QOS_DEBUG_BWBOUND = 1U << 1, POB_QOS_DEBUG_BWTOTAL = 1U << 2, POB_QOS_DEBUG_BWCPU = 1U << 3, POB_QOS_DEBUG_BWGPU = 1U << 4, POB_QOS_DEBUG_BWMM = 1U << 5, POB_QOS_DEBUG_BWMD = 1U << 6, POB_QOS_DEBUG_BWAPU = 1U << 7, POB_QOS_DEBUG_BWVPU = 1U << 8, POB_QOS_DEBUG_BWMDLA = 1U << 9, POB_QOS_DEBUG_BWCAM = 1U << 10, POB_QOS_DEBUG_BWVENC = 1U << 11, POB_QOS_DEBUG_BWIMG = 1U << 12, POB_QOS_DEBUG_BWMDP = 1U << 13, POB_QOS_DEBUG_LATCPU = 1U << 14, POB_QOS_DEBUG_LATVPU = 1U << 15, POB_QOS_DEBUG_LATMDLA = 1U << 16, POB_QOS_DEBUG_MAX = 1U << 17, }; static const char * const mask_string[] = { "QOSSEQ", "BWBOUND", "BWTOTAL", "BWCPU", "BWGPU", "BWMM", "BWMD", "BWAPU", "BWVPU", "BWMDLA", "BWCAM", "BWVENC", "BWIMG", "BWMDP", "LATCPU", "LATVPU", "LATMDLA", "MAX", }; static const char * const PQBP_str[] = { "EMI", "SMI" }; static const char * const PQBS_str[] = { "MON", "REQ" }; static const char * const PQLS_str[] = { "MON", }; static void _pob_qos_seq_log(struct pob_qos_trace_info *pqti, struct pob_qos_info *pqi) { int i; char log[TRACELOG_SIZE]; char valuelog[8]; int tmplen; int sublen; tmplen = snprintf(log, TRACELOG_SIZE, "%s size=%d,", pqti->prefix, pqi->size); if (tmplen < 0 || tmplen >= sizeof(log)) return; for (i = 0; i < pqi->size; i++) { sublen = snprintf(valuelog, 8, " %d", pob_qosseq_get(pqi->pstats, i)); if (sublen >= 8) break; if (tmplen + sublen >= TRACELOG_SIZE - 1) break; strncpy(log + tmplen, valuelog, TRACELOG_SIZE - 1 - tmplen); tmplen += sublen; } _trace_pob_log(log); } static void _pob_qos_bm_log(struct pob_qos_trace_info *pqti, struct pob_qos_info *pqi) { int i, j, k; int x; int super, sub; char log[TRACELOG_SIZE]; char valuelog[8]; int tmplen; int vRet; int sublen; for (i = 0; i < NR_PQBP; i++) { for (j = 0; j < NR_PQBS; j++) { vRet = pob_qosbm_get_cap(pqti->typemap, i, j, &super, &sub); if (vRet) continue; if (super) { tmplen = snprintf(log, TRACELOG_SIZE, "%s probe=%s, source=%s, size=%d,", pqti->prefix, PQBP_str[i], PQBS_str[j], pqi->size); if (tmplen < 0 || tmplen >= sizeof(log)) return; for (k = 0; k < pqi->size; k++) { sublen = snprintf(valuelog, 8, " %d", pob_qosbm_get_stat(pqi->pstats, k, pqti->typemap, i, j, 0, 0)); if (sublen >= 8) break; if (tmplen + sublen >= TRACELOG_SIZE - 1) break; strncpy(log + tmplen, valuelog, TRACELOG_SIZE - 1 - tmplen); tmplen += sublen; } _trace_pob_log(log); } for (x = 0; x < sub; x++) { tmplen = snprintf(log, TRACELOG_SIZE, "%s%d probe=%s, source=%s, size=%d,", pqti->prefix, x, PQBP_str[i], PQBS_str[j], pqi->size); if (tmplen < 0 || tmplen >= sizeof(log)) return; for (k = 0; k < pqi->size; k++) { sublen = snprintf(valuelog, 8, " %d", pob_qosbm_get_stat(pqi->pstats, k, pqti->typemap, i, j, 1, x)); if (sublen >= 8) break; if (tmplen + sublen >= TRACELOG_SIZE - 1) break; strncpy(log + tmplen, valuelog, TRACELOG_SIZE - 1 - tmplen); tmplen += sublen; } _trace_pob_log(log); } } } } static void _pob_qos_lat_log(struct pob_qos_trace_info *pqti, struct pob_qos_info *pqi) { int i, k; int x; int super, sub; char log[TRACELOG_SIZE]; char valuelog[8]; int tmplen; int vRet; int sublen; for (i = 0; i < NR_PQLS; i++) { vRet = pob_qoslat_get_cap(pqti->typemap, i, &super, &sub); if (vRet) continue; if (super) { tmplen = snprintf(log, TRACELOG_SIZE, "%s source=%s, size=%d,", pqti->prefix, PQLS_str[i], pqi->size); if (tmplen < 0 || tmplen >= sizeof(log)) return; for (k = 0; k < pqi->size; k++) { sublen = snprintf(valuelog, 8, " %d", pob_qoslat_get_stat(pqi->pstats, k, pqti->typemap, i, 0, 0)); if (sublen >= 8) break; if (tmplen + sublen >= TRACELOG_SIZE - 1) break; strncpy(log + tmplen, valuelog, TRACELOG_SIZE - 1 - tmplen); tmplen += sublen; } _trace_pob_log(log); } for (x = 0; x < sub; x++) { tmplen = snprintf(log, TRACELOG_SIZE, "%s%d source=%s, size=%d,", pqti->prefix, x, PQLS_str[i], pqi->size); if (tmplen < 0 || tmplen >= sizeof(log)) return; for (k = 0; k < pqi->size; k++) { sublen = snprintf(valuelog, 8, " %d", pob_qoslat_get_stat(pqi->pstats, k, pqti->typemap, i, 1, x)); if (sublen >= 8) break; if (tmplen + sublen >= TRACELOG_SIZE - 1) break; strncpy(log + tmplen, valuelog, TRACELOG_SIZE - 1 - tmplen); tmplen += sublen; } _trace_pob_log(log); } } } static struct pob_qos_trace_info pqti[] = { {POB_QOS_DEBUG_QOSSEQ, -1, "QOS_SEQ", _pob_qos_seq_log}, {POB_QOS_DEBUG_BWBOUND, PQBT_BOUND, "BW_BOUND", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWTOTAL, PQBT_TOTAL, "BW_TOTAL", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWCPU, PQBT_CPU, "BW_CPU", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWGPU, PQBT_GPU, "BW_GPU", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWMM, PQBT_MM, "BW_MM", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWMD, PQBT_MD, "BW_MD", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWAPU, PQBT_APU, "BW_APU", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWVPU, PQBT_VPU, "BW_VPU", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWMDLA, PQBT_MDLA, "BW_MDLA", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWCAM, PQBT_CAM, "BW_CAM", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWVENC, PQBT_VENC, "BW_VENC", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWIMG, PQBT_IMG, "BW_IMG", _pob_qos_bm_log}, {POB_QOS_DEBUG_BWMDP, PQBT_MDP, "BW_MDP", _pob_qos_bm_log}, {POB_QOS_DEBUG_LATCPU, PQLT_CPU, "LAT_CPU", _pob_qos_lat_log}, {POB_QOS_DEBUG_LATVPU, PQLT_VPU, "LAT_VPU", _pob_qos_lat_log}, {POB_QOS_DEBUG_LATMDLA, PQLT_MDLA, "LAT_MDLA", _pob_qos_lat_log}, {POB_QOS_DEBUG_MAX, -1, NULL, 0}, }; static uint32_t pob_qos_systrace_mask; static uint32_t pob_qos_forceon; void pob_qos_tracelog(unsigned long val, void *data) { int i; struct pob_qos_info *pqi = (struct pob_qos_info *) data; switch (val) { case POB_QOS_EMI_ALL: for (i = 0; pqti[i].flag != POB_QOS_DEBUG_MAX; i++) { if (pob_qos_systrace_mask & pqti[i].flag) pqti[i].pHdlfun(&pqti[i], pqi); } break; default: break; } } static int _pob_qos_cb(struct notifier_block *nb, unsigned long val, void *data) { return NOTIFY_OK; } static struct notifier_block pob_qos_notifier = { .notifier_call = _pob_qos_cb, }; static inline void _pob_qos_enable(void) { pob_qos_register_client(&pob_qos_notifier); } static inline void _pob_qos_disable(void) { pob_qos_unregister_client(&pob_qos_notifier); } static ssize_t pob_mask_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int i; char temp[POB_SYSFS_MAX_BUFF_SIZE]; int pos = 0; int length; length = scnprintf(temp + pos, POB_SYSFS_MAX_BUFF_SIZE - pos, " Current enabled systrace:\n"); pos += length; for (i = 0; (1U << i) < POB_QOS_DEBUG_MAX; i++) { length = scnprintf(temp + pos, POB_SYSFS_MAX_BUFF_SIZE - pos, " %-*s ... %s\n", 12, mask_string[i], pob_qos_systrace_mask & (1U << i) ? "On" : "Off"); pos += length; } return scnprintf(buf, PAGE_SIZE, "%s", temp); } static ssize_t pob_mask_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val = -1; char acBuffer[POB_SYSFS_MAX_BUFF_SIZE]; int arg; if ((count > 0) && (count < POB_SYSFS_MAX_BUFF_SIZE)) { if (scnprintf(acBuffer, POB_SYSFS_MAX_BUFF_SIZE, "%s", buf)) { if (kstrtoint(acBuffer, 0, &arg) == 0) val = arg; else return count; } } val = val & (POB_QOS_DEBUG_MAX - 1U); pob_qos_systrace_mask = val; return count; } KOBJ_ATTR_RW(pob_mask); static ssize_t pob_forceon_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int val = -1; val = pob_qos_forceon; return scnprintf(buf, PAGE_SIZE, "%d\n", val); } static ssize_t pob_forceon_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val = -1; char acBuffer[POB_SYSFS_MAX_BUFF_SIZE]; int arg; if ((count > 0) && (count < POB_SYSFS_MAX_BUFF_SIZE)) { if (scnprintf(acBuffer, POB_SYSFS_MAX_BUFF_SIZE, "%s", buf)) { if (kstrtoint(acBuffer, 0, &arg) == 0) val = arg; else return count; } } if (val > 1 || val < 0) return count; if (val && !pob_qos_forceon) _pob_qos_enable(); else if (!val && pob_qos_forceon) _pob_qos_disable(); pob_qos_forceon = val; return count; } KOBJ_ATTR_RW(pob_forceon); int __init pob_qos_init(struct kobject *pob_kobj) { if (pob_qos_pfm_init()) return -EFAULT; pob_qos_systrace_mask = POB_QOS_DEBUG_QOSSEQ | POB_QOS_DEBUG_BWBOUND | POB_QOS_DEBUG_BWTOTAL; if (!pob_kobj) return -ENODEV; pob_sysfs_create_file(pob_kobj, &kobj_attr_pob_forceon); pob_sysfs_create_file(pob_kobj, &kobj_attr_pob_mask); return 0; } void __exit pob_qos_exit(struct kobject *pob_kobj) { pob_sysfs_remove_file(pob_kobj, &kobj_attr_pob_forceon); pob_sysfs_remove_file(pob_kobj, &kobj_attr_pob_mask); pob_qos_pfm_exit(); }