/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include "msm_iommu_perfmon.h" static LIST_HEAD(iommu_list); static struct dentry *msm_iommu_root_debugfs_dir; static const char *NO_EVENT_CLASS_NAME = "none"; static const unsigned int MAX_EVEN_CLASS_NAME_LEN = 36; struct event_class { unsigned int event_number; const char *desc; }; static struct event_class pmu_event_classes[] = { { 0x00, "cycle_count" }, { 0x01, "cycle_count64" }, { 0x08, "tlb_refill" }, { 0x09, "tlb_refill_read" }, { 0x0A, "tlb_refill_write" }, { 0x10, "access" }, { 0x11, "access_read" }, { 0x12, "access_write" }, { 0x80, "full_misses" }, { 0x81, "partial_miss_1lbfb_hit" }, { 0x82, "partial_miss_2lbfb_hit" }, { 0x83, "full_hit" }, { 0x90, "pred_req_full_miss" }, { 0x91, "pred_req_partial_miss_1lbfb_hit" }, { 0x92, "pred_req_partial_miss_2lbfb_hit" }, { 0xb0, "tot_num_miss_axi_htw_read_req" }, { 0xb1, "tot_num_pred_axi_htw_read_req" }, }; static unsigned int iommu_pm_create_sup_cls_str(char **buf, struct iommu_pmon *pmon) { unsigned long buf_size = ARRAY_SIZE(pmu_event_classes) * MAX_EVEN_CLASS_NAME_LEN; unsigned int pos = 0; unsigned int nevent_cls = pmon->nevent_cls_supported; *buf = kzalloc(buf_size, GFP_KERNEL); if (*buf) { unsigned int j; int i; struct event_class *ptr; size_t array_len = ARRAY_SIZE(pmu_event_classes); ptr = pmu_event_classes; for (j = 0; j < nevent_cls; ++j) { for (i = 0; i < array_len; ++i) { if (ptr[i].event_number != pmon->event_cls_supported[j]) continue; if (pos < buf_size) { pos += snprintf(&(*buf)[pos], buf_size-pos, "[%u] %s\n", ptr[i].event_number, ptr[i].desc); } break; } } } return pos; } static int iommu_pm_event_class_supported(struct iommu_pmon *pmon, int event_class) { unsigned int nevent_cls = pmon->nevent_cls_supported; unsigned int i; for (i = 0; i < nevent_cls; ++i) { if (event_class == pmon->event_cls_supported[i]) return event_class; } return MSM_IOMMU_PMU_NO_EVENT_CLASS; } static const char *iommu_pm_find_event_class_name(int event_class) { size_t array_len; struct event_class *ptr; int i; const char *event_class_name = NO_EVENT_CLASS_NAME; if (event_class < 0) goto out; array_len = ARRAY_SIZE(pmu_event_classes); ptr = pmu_event_classes; for (i = 0; i < array_len; ++i) { if (ptr[i].event_number == event_class) { event_class_name = ptr[i].desc; break; } } out: return event_class_name; } static int iommu_pm_find_event_class(struct iommu_pmon *pmon, const char *event_class_name) { size_t array_len; struct event_class *ptr; int i; int event_class = MSM_IOMMU_PMU_NO_EVENT_CLASS; if (strcmp(event_class_name, NO_EVENT_CLASS_NAME) == 0) goto out; array_len = ARRAY_SIZE(pmu_event_classes); ptr = pmu_event_classes; for (i = 0; i < array_len; ++i) { if (strcmp(ptr[i].desc, event_class_name) == 0) { event_class = ptr[i].event_number; goto out; } } out: event_class = iommu_pm_event_class_supported(pmon, event_class); return event_class; } static inline void iommu_pm_add_to_iommu_list(struct iommu_pmon *iommu_pmon) { list_add(&iommu_pmon->iommu_list, &iommu_list); } static inline void iommu_pm_del_from_iommu_list(struct iommu_pmon *iommu_pmon) { list_del(&iommu_pmon->iommu_list); } static struct iommu_pmon *iommu_pm_get_pm_by_dev(struct device *dev) { struct iommu_pmon *pmon; struct iommu_info *info; struct list_head *ent; list_for_each(ent, &iommu_list) { pmon = list_entry(ent, struct iommu_pmon, iommu_list); info = &pmon->iommu; if (dev == info->iommu_dev) return pmon; } return NULL; } static void iommu_pm_set_event_type(struct iommu_pmon *pmon, struct iommu_pmon_counter *counter) { int event_class; unsigned int count_no; struct iommu_info *iommu = &pmon->iommu; event_class = counter->current_event_class; count_no = counter->absolute_counter_no; if (event_class == MSM_IOMMU_PMU_NO_EVENT_CLASS) { if (iommu->hw_ops->is_hw_access_OK(pmon)) { iommu->ops->iommu_lock_acquire(1); iommu->hw_ops->counter_disable(iommu, counter); iommu->hw_ops->ovfl_int_disable(iommu, counter); iommu->hw_ops->set_event_class(pmon, count_no, 0); iommu->ops->iommu_lock_release(1); } counter->overflow_count = 0; counter->value = 0; } else { counter->overflow_count = 0; counter->value = 0; if (iommu->hw_ops->is_hw_access_OK(pmon)) { iommu->ops->iommu_lock_acquire(1); iommu->hw_ops->set_event_class(pmon, count_no, event_class); iommu->hw_ops->ovfl_int_enable(iommu, counter); iommu->hw_ops->counter_enable(iommu, counter); iommu->ops->iommu_lock_release(1); } } } static void iommu_pm_reset_counts(struct iommu_pmon *pmon) { unsigned int i; unsigned int j; for (i = 0; i < pmon->num_groups; ++i) { struct iommu_pmon_cnt_group *cnt_grp = &pmon->cnt_grp[i]; for (j = 0; j < cnt_grp->num_counters; ++j) { cnt_grp->counters[j].value = 0; cnt_grp->counters[j].overflow_count = 0; } } } static void iommu_pm_set_all_counters(struct iommu_pmon *pmon) { unsigned int i; unsigned int j; for (i = 0; i < pmon->num_groups; ++i) { struct iommu_pmon_cnt_group *cnt_grp = &pmon->cnt_grp[i]; for (j = 0; j < cnt_grp->num_counters; ++j) iommu_pm_set_event_type(pmon, &cnt_grp->counters[j]); } } static void iommu_pm_read_all_counters(struct iommu_pmon *pmon) { unsigned int i; unsigned int j; struct iommu_info *iommu = &pmon->iommu; for (i = 0; i < pmon->num_groups; ++i) { struct iommu_pmon_cnt_group *cnt_grp = &pmon->cnt_grp[i]; for (j = 0; j < cnt_grp->num_counters; ++j) { struct iommu_pmon_counter *counter; counter = &cnt_grp->counters[j]; counter->value = iommu->hw_ops->read_counter(counter); } } } static void iommu_pm_on(struct iommu_pmon *pmon) { unsigned int i; struct iommu_info *iommu = &pmon->iommu; struct msm_iommu_drvdata *iommu_drvdata = dev_get_drvdata(iommu->iommu_dev); iommu->ops->iommu_power_on(iommu_drvdata); iommu->ops->iommu_bus_vote(iommu_drvdata, 1); iommu->ops->iommu_clk_on(iommu_drvdata); /* Reset counters in HW */ iommu->ops->iommu_lock_acquire(1); iommu->hw_ops->reset_counters(&pmon->iommu); iommu->ops->iommu_lock_release(1); /* Reset SW counters */ iommu_pm_reset_counts(pmon); pmon->enabled = 1; iommu_pm_set_all_counters(pmon); iommu->ops->iommu_lock_acquire(1); /* enable all counter group */ for (i = 0; i < pmon->num_groups; ++i) iommu->hw_ops->grp_enable(iommu, i); /* enable global counters */ iommu->hw_ops->enable_pm(iommu); iommu->ops->iommu_lock_release(1); pr_info("%s: TLB performance monitoring turned ON\n", pmon->iommu.iommu_name); } static void iommu_pm_off(struct iommu_pmon *pmon) { unsigned int i; struct iommu_info *iommu = &pmon->iommu; struct msm_iommu_drvdata *iommu_drvdata = dev_get_drvdata(iommu->iommu_dev); pmon->enabled = 0; iommu->ops->iommu_lock_acquire(1); /* disable global counters */ iommu->hw_ops->disable_pm(iommu); /* Check if we overflowed just before turning off pmon */ iommu->hw_ops->check_for_overflow(pmon); /* disable all counter group */ for (i = 0; i < pmon->num_groups; ++i) iommu->hw_ops->grp_disable(iommu, i); /* Update cached copy of counters before turning off power */ iommu_pm_read_all_counters(pmon); iommu->ops->iommu_lock_release(1); iommu->ops->iommu_clk_off(iommu_drvdata); iommu->ops->iommu_bus_vote(iommu_drvdata, 0); iommu->ops->iommu_power_off(iommu_drvdata); pr_info("%s: TLB performance monitoring turned OFF\n", pmon->iommu.iommu_name); } static int iommu_pm_debug_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; } static ssize_t iommu_pm_count_value_read(struct file *fp, char __user *user_buff, size_t count, loff_t *pos) { size_t rd_cnt; unsigned long long full_count; struct iommu_pmon_counter *counter = fp->private_data; struct iommu_pmon *pmon = counter->cnt_group->pmon; struct iommu_info *iommu = &pmon->iommu; char buf[50]; size_t len; mutex_lock(&pmon->lock); if (iommu->hw_ops->is_hw_access_OK(pmon)) { iommu->ops->iommu_lock_acquire(1); counter->value = iommu->hw_ops->read_counter(counter); iommu->ops->iommu_lock_release(1); } full_count = (unsigned long long) counter->value + ((unsigned long long)counter->overflow_count * 0x100000000ULL); len = snprintf(buf, 50, "%llu\n", full_count); rd_cnt = simple_read_from_buffer(user_buff, count, pos, buf, len); mutex_unlock(&pmon->lock); return rd_cnt; } static const struct file_operations cnt_value_file_ops = { .open = iommu_pm_debug_open, .read = iommu_pm_count_value_read, }; static ssize_t iommu_pm_event_class_read(struct file *fp, char __user *user_buff, size_t count, loff_t *pos) { size_t rd_cnt; struct iommu_pmon_counter *counter = fp->private_data; struct iommu_pmon *pmon = counter->cnt_group->pmon; char buf[50]; const char *event_class_name; size_t len; mutex_lock(&pmon->lock); event_class_name = iommu_pm_find_event_class_name( counter->current_event_class); len = snprintf(buf, 50, "%s\n", event_class_name); rd_cnt = simple_read_from_buffer(user_buff, count, pos, buf, len); mutex_unlock(&pmon->lock); return rd_cnt; } static ssize_t iommu_pm_event_class_write(struct file *fp, const char __user *user_buff, size_t count, loff_t *pos) { size_t wr_cnt; char buf[50]; size_t buf_size = sizeof(buf); struct iommu_pmon_counter *counter = fp->private_data; struct iommu_pmon *pmon = counter->cnt_group->pmon; int current_event_class; if ((count + *pos) >= buf_size) return -EINVAL; mutex_lock(&pmon->lock); current_event_class = counter->current_event_class; wr_cnt = simple_write_to_buffer(buf, buf_size, pos, user_buff, count); if (wr_cnt >= 1) { int rv; long value; buf[wr_cnt-1] = '\0'; rv = kstrtol(buf, 10, &value); if (!rv) { counter->current_event_class = iommu_pm_find_event_class(pmon, iommu_pm_find_event_class_name(value)); } else { counter->current_event_class = iommu_pm_find_event_class(pmon, buf); } } if (current_event_class != counter->current_event_class) iommu_pm_set_event_type(pmon, counter); mutex_unlock(&pmon->lock); return wr_cnt; } static const struct file_operations event_class_file_ops = { .open = iommu_pm_debug_open, .read = iommu_pm_event_class_read, .write = iommu_pm_event_class_write, }; static ssize_t iommu_reset_counters_write(struct file *fp, const char __user *user_buff, size_t count, loff_t *pos) { size_t wr_cnt; char buf[10]; size_t buf_size = sizeof(buf); struct iommu_pmon *pmon = fp->private_data; struct iommu_info *iommu = &pmon->iommu; if ((count + *pos) >= buf_size) return -EINVAL; mutex_lock(&pmon->lock); wr_cnt = simple_write_to_buffer(buf, buf_size, pos, user_buff, count); if (wr_cnt >= 1) { unsigned long cmd = 0; int rv; buf[wr_cnt-1] = '\0'; rv = kstrtoul(buf, 10, &cmd); if (!rv && (cmd == 1)) { if (iommu->hw_ops->is_hw_access_OK(pmon)) { iommu->ops->iommu_lock_acquire(1); iommu->hw_ops->reset_counters(&pmon->iommu); iommu->ops->iommu_lock_release(1); } iommu_pm_reset_counts(pmon); pr_info("TLB performance counters reset\n"); } else { pr_err("Unknown performance monitor command: %lu\n", cmd); } } mutex_unlock(&pmon->lock); return wr_cnt; } static const struct file_operations reset_file_ops = { .open = iommu_pm_debug_open, .write = iommu_reset_counters_write, }; static ssize_t iommu_pm_enable_counters_read(struct file *fp, char __user *user_buff, size_t count, loff_t *pos) { size_t rd_cnt; char buf[5]; size_t len; struct iommu_pmon *pmon = fp->private_data; mutex_lock(&pmon->lock); len = snprintf(buf, 5, "%u\n", pmon->enabled); rd_cnt = simple_read_from_buffer(user_buff, count, pos, buf, len); mutex_unlock(&pmon->lock); return rd_cnt; } static ssize_t iommu_pm_enable_counters_write(struct file *fp, const char __user *user_buff, size_t count, loff_t *pos) { size_t wr_cnt; char buf[10]; size_t buf_size = sizeof(buf); struct iommu_pmon *pmon = fp->private_data; if ((count + *pos) >= buf_size) return -EINVAL; mutex_lock(&pmon->lock); wr_cnt = simple_write_to_buffer(buf, buf_size, pos, user_buff, count); if (wr_cnt >= 1) { unsigned long cmd; int rv; buf[wr_cnt-1] = '\0'; rv = kstrtoul(buf, 10, &cmd); if (!rv && (cmd < 2)) { if (pmon->enabled == 1 && cmd == 0) { if (pmon->iommu.always_on || pmon->iommu_attach_count > 0) iommu_pm_off(pmon); } else if (pmon->enabled == 0 && cmd == 1) { /* We can only turn on perf. monitoring if * iommu is attached (if not always on). * Delay turning on perf. monitoring until * we are attached. */ if (pmon->iommu.always_on || pmon->iommu_attach_count > 0) iommu_pm_on(pmon); else pmon->enabled = 1; } } else { pr_err("Unknown performance monitor command: %lu\n", cmd); } } mutex_unlock(&pmon->lock); return wr_cnt; } static const struct file_operations event_enable_file_ops = { .open = iommu_pm_debug_open, .read = iommu_pm_enable_counters_read, .write = iommu_pm_enable_counters_write, }; static ssize_t iommu_pm_avail_event_cls_read(struct file *fp, char __user *user_buff, size_t count, loff_t *pos) { size_t rd_cnt = 0; struct iommu_pmon *pmon = fp->private_data; char *buf; size_t len; mutex_lock(&pmon->lock); len = iommu_pm_create_sup_cls_str(&buf, pmon); if (buf) { rd_cnt = simple_read_from_buffer(user_buff, count, pos, buf, len); kfree(buf); } mutex_unlock(&pmon->lock); return rd_cnt; } static const struct file_operations available_event_cls_file_ops = { .open = iommu_pm_debug_open, .read = iommu_pm_avail_event_cls_read, }; static int iommu_pm_create_grp_debugfs_counters_hierarchy( struct iommu_pmon_cnt_group *cnt_grp, unsigned int *abs_counter_no) { int ret = 0; int j; char name[20]; for (j = 0; j < cnt_grp->num_counters; ++j) { struct dentry *grp_dir = cnt_grp->group_dir; struct dentry *counter_dir; cnt_grp->counters[j].cnt_group = cnt_grp; cnt_grp->counters[j].counter_no = j; cnt_grp->counters[j].absolute_counter_no = *abs_counter_no; (*abs_counter_no)++; cnt_grp->counters[j].value = 0; cnt_grp->counters[j].overflow_count = 0; cnt_grp->counters[j].current_event_class = MSM_IOMMU_PMU_NO_EVENT_CLASS; snprintf(name, 20, "counter%u", j); counter_dir = debugfs_create_dir(name, grp_dir); if (IS_ERR_OR_NULL(counter_dir)) { pr_err("unable to create counter debugfs dir %s\n", name); ret = -ENOMEM; goto out; } cnt_grp->counters[j].counter_dir = counter_dir; if (!debugfs_create_file("value", 0644, counter_dir, &cnt_grp->counters[j], &cnt_value_file_ops)) { ret = -EIO; goto out; } if (!debugfs_create_file("current_event_class", 0644, counter_dir, &cnt_grp->counters[j], &event_class_file_ops)) { ret = -EIO; goto out; } } out: return ret; } static int iommu_pm_create_group_debugfs_hierarchy(struct iommu_info *iommu, struct iommu_pmon *pmon_entry) { int i; int ret = 0; char name[20]; unsigned int abs_counter_no = 0; for (i = 0; i < pmon_entry->num_groups; ++i) { pmon_entry->cnt_grp[i].pmon = pmon_entry; pmon_entry->cnt_grp[i].grp_no = i; pmon_entry->cnt_grp[i].num_counters = pmon_entry->num_counters; pmon_entry->cnt_grp[i].counters = kzalloc(sizeof(*pmon_entry->cnt_grp[i].counters) * pmon_entry->cnt_grp[i].num_counters, GFP_KERNEL); if (!pmon_entry->cnt_grp[i].counters) { pr_err("Unable to allocate memory for counters\n"); ret = -ENOMEM; goto out; } snprintf(name, 20, "group%u", i); pmon_entry->cnt_grp[i].group_dir = debugfs_create_dir(name, pmon_entry->iommu_dir); if (IS_ERR_OR_NULL(pmon_entry->cnt_grp[i].group_dir)) { pr_err("unable to create group debugfs dir %s\n", name); ret = -ENOMEM; goto out; } ret = iommu_pm_create_grp_debugfs_counters_hierarchy( &pmon_entry->cnt_grp[i], &abs_counter_no); if (ret) goto out; } out: return ret; } int msm_iommu_pm_iommu_register(struct iommu_pmon *pmon_entry) { int ret = 0; struct iommu_info *iommu = &pmon_entry->iommu; int i; if (!iommu->ops || !iommu->iommu_name || !iommu->base || !iommu->iommu_dev) { ret = -EINVAL; goto out; } if (!msm_iommu_root_debugfs_dir) { msm_iommu_root_debugfs_dir = debugfs_create_dir("iommu", NULL); if (IS_ERR_OR_NULL(msm_iommu_root_debugfs_dir)) { pr_err("Failed creating iommu debugfs dir \"iommu\"\n"); ret = -EIO; goto out; } } pmon_entry->cnt_grp = kzalloc(sizeof(*pmon_entry->cnt_grp) * pmon_entry->num_groups, GFP_KERNEL); if (!pmon_entry->cnt_grp) { pr_err("Unable to allocate memory for counter groups\n"); ret = -ENOMEM; goto file_err; } pmon_entry->iommu_dir = debugfs_create_dir(iommu->iommu_name, msm_iommu_root_debugfs_dir); if (IS_ERR_OR_NULL(pmon_entry->iommu_dir)) { pr_err("unable to create iommu debugfs dir %s\n", iommu->iommu_name); ret = -ENOMEM; goto free_mem; } if (!debugfs_create_file("reset_counters", 0644, pmon_entry->iommu_dir, pmon_entry, &reset_file_ops)) { ret = -EIO; goto free_mem; } if (!debugfs_create_file("enable_counters", 0644, pmon_entry->iommu_dir, pmon_entry, &event_enable_file_ops)) { ret = -EIO; goto free_mem; } if (!debugfs_create_file("available_event_classes", 0644, pmon_entry->iommu_dir, pmon_entry, &available_event_cls_file_ops)) { ret = -EIO; goto free_mem; } ret = iommu_pm_create_group_debugfs_hierarchy(iommu, pmon_entry); if (ret) goto free_mem; iommu->hw_ops->initialize_hw(pmon_entry); if (iommu->evt_irq > 0) { ret = request_threaded_irq(iommu->evt_irq, NULL, iommu->hw_ops->evt_ovfl_int_handler, IRQF_ONESHOT | IRQF_SHARED, "msm_iommu_pmon_nonsecure_irq", pmon_entry); if (ret) { pr_err("Request IRQ %d failed with ret=%d\n", iommu->evt_irq, ret); goto free_mem; } } else { pr_info("%s: Overflow interrupt not available\n", __func__); } dev_dbg(iommu->iommu_dev, "%s iommu registered\n", iommu->iommu_name); goto out; free_mem: if (pmon_entry->cnt_grp) { for (i = 0; i < pmon_entry->num_groups; ++i) { kfree(pmon_entry->cnt_grp[i].counters); pmon_entry->cnt_grp[i].counters = 0; } } kfree(pmon_entry->cnt_grp); pmon_entry->cnt_grp = 0; file_err: debugfs_remove_recursive(msm_iommu_root_debugfs_dir); out: return ret; } EXPORT_SYMBOL(msm_iommu_pm_iommu_register); void msm_iommu_pm_iommu_unregister(struct device *dev) { int i; struct iommu_pmon *pmon_entry = iommu_pm_get_pm_by_dev(dev); if (!pmon_entry) return; free_irq(pmon_entry->iommu.evt_irq, pmon_entry->iommu.iommu_dev); if (!pmon_entry) goto remove_debugfs; if (pmon_entry->cnt_grp) { for (i = 0; i < pmon_entry->num_groups; ++i) kfree(pmon_entry->cnt_grp[i].counters); } kfree(pmon_entry->cnt_grp); remove_debugfs: debugfs_remove_recursive(msm_iommu_root_debugfs_dir); return; } EXPORT_SYMBOL(msm_iommu_pm_iommu_unregister); struct iommu_pmon *msm_iommu_pm_alloc(struct device *dev) { struct iommu_pmon *pmon_entry; struct iommu_info *info; pmon_entry = devm_kzalloc(dev, sizeof(*pmon_entry), GFP_KERNEL); if (!pmon_entry) return NULL; info = &pmon_entry->iommu; info->iommu_dev = dev; mutex_init(&pmon_entry->lock); iommu_pm_add_to_iommu_list(pmon_entry); return pmon_entry; } EXPORT_SYMBOL(msm_iommu_pm_alloc); void msm_iommu_pm_free(struct device *dev) { struct iommu_pmon *pmon = iommu_pm_get_pm_by_dev(dev); if (pmon) iommu_pm_del_from_iommu_list(pmon); } EXPORT_SYMBOL(msm_iommu_pm_free); void msm_iommu_attached(struct device *dev) { struct iommu_pmon *pmon = iommu_pm_get_pm_by_dev(dev); if (pmon) { mutex_lock(&pmon->lock); ++pmon->iommu_attach_count; if (pmon->iommu_attach_count == 1) { /* If perf. mon was enabled before we attached we do * the actual enabling after we attach. */ if (pmon->enabled && !pmon->iommu.always_on) iommu_pm_on(pmon); } mutex_unlock(&pmon->lock); } } EXPORT_SYMBOL(msm_iommu_attached); void msm_iommu_detached(struct device *dev) { struct iommu_pmon *pmon = iommu_pm_get_pm_by_dev(dev); if (pmon) { mutex_lock(&pmon->lock); if (pmon->iommu_attach_count == 1) { /* If perf. mon is still enabled we have to disable * before we do the detach if iommu is not always on. */ if (pmon->enabled && !pmon->iommu.always_on) iommu_pm_off(pmon); } BUG_ON(pmon->iommu_attach_count == 0); --pmon->iommu_attach_count; mutex_unlock(&pmon->lock); } } EXPORT_SYMBOL(msm_iommu_detached);