| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright(C) 2015 Linaro Limited. All rights reserved. |
| * Author: Mathieu Poirier <mathieu.poirier@linaro.org> |
| */ |
| |
| #include <linux/pid_namespace.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/sysfs.h> |
| #include "coresight-etm.h" |
| #include "coresight-priv.h" |
| |
| static ssize_t nr_addr_cmp_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| |
| val = drvdata->nr_addr_cmp; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| static DEVICE_ATTR_RO(nr_addr_cmp); |
| |
| static ssize_t nr_cntr_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| |
| val = drvdata->nr_cntr; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| static DEVICE_ATTR_RO(nr_cntr); |
| |
| static ssize_t nr_ctxid_cmp_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| |
| val = drvdata->nr_ctxid_cmp; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| static DEVICE_ATTR_RO(nr_ctxid_cmp); |
| |
| static ssize_t etmsr_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long flags, val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| |
| pm_runtime_get_sync(dev->parent); |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| CS_UNLOCK(drvdata->base); |
| |
| val = etm_readl(drvdata, ETMSR); |
| |
| CS_LOCK(drvdata->base); |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| pm_runtime_put(dev->parent); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| static DEVICE_ATTR_RO(etmsr); |
| |
| static ssize_t reset_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int i, ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| if (val) { |
| spin_lock(&drvdata->spinlock); |
| memset(config, 0, sizeof(struct etm_config)); |
| config->mode = ETM_MODE_EXCLUDE; |
| config->trigger_event = ETM_DEFAULT_EVENT_VAL; |
| for (i = 0; i < drvdata->nr_addr_cmp; i++) { |
| config->addr_type[i] = ETM_ADDR_TYPE_NONE; |
| } |
| |
| etm_set_default(config); |
| etm_release_trace_id(drvdata); |
| spin_unlock(&drvdata->spinlock); |
| } |
| |
| return size; |
| } |
| static DEVICE_ATTR_WO(reset); |
| |
| static ssize_t mode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->mode; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t mode_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| config->mode = val & ETM_MODE_ALL; |
| |
| if (config->mode & ETM_MODE_EXCLUDE) |
| config->enable_ctrl1 |= ETMTECR1_INC_EXC; |
| else |
| config->enable_ctrl1 &= ~ETMTECR1_INC_EXC; |
| |
| if (config->mode & ETM_MODE_CYCACC) |
| config->ctrl |= ETMCR_CYC_ACC; |
| else |
| config->ctrl &= ~ETMCR_CYC_ACC; |
| |
| if (config->mode & ETM_MODE_STALL) { |
| if (!(drvdata->etmccr & ETMCCR_FIFOFULL)) { |
| dev_warn(dev, "stall mode not supported\n"); |
| ret = -EINVAL; |
| goto err_unlock; |
| } |
| config->ctrl |= ETMCR_STALL_MODE; |
| } else |
| config->ctrl &= ~ETMCR_STALL_MODE; |
| |
| if (config->mode & ETM_MODE_TIMESTAMP) { |
| if (!(drvdata->etmccer & ETMCCER_TIMESTAMP)) { |
| dev_warn(dev, "timestamp not supported\n"); |
| ret = -EINVAL; |
| goto err_unlock; |
| } |
| config->ctrl |= ETMCR_TIMESTAMP_EN; |
| } else |
| config->ctrl &= ~ETMCR_TIMESTAMP_EN; |
| |
| if (config->mode & ETM_MODE_CTXID) |
| config->ctrl |= ETMCR_CTXID_SIZE; |
| else |
| config->ctrl &= ~ETMCR_CTXID_SIZE; |
| |
| if (config->mode & ETM_MODE_BBROAD) |
| config->ctrl |= ETMCR_BRANCH_BROADCAST; |
| else |
| config->ctrl &= ~ETMCR_BRANCH_BROADCAST; |
| |
| if (config->mode & ETM_MODE_RET_STACK) |
| config->ctrl |= ETMCR_RETURN_STACK; |
| else |
| config->ctrl &= ~ETMCR_RETURN_STACK; |
| |
| if (config->mode & (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER)) |
| etm_config_trace_mode(config); |
| |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| |
| err_unlock: |
| spin_unlock(&drvdata->spinlock); |
| return ret; |
| } |
| static DEVICE_ATTR_RW(mode); |
| |
| static ssize_t trigger_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->trigger_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t trigger_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->trigger_event = val & ETM_EVENT_MASK; |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(trigger_event); |
| |
| static ssize_t enable_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->enable_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t enable_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->enable_event = val & ETM_EVENT_MASK; |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(enable_event); |
| |
| static ssize_t fifofull_level_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->fifofull_level; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t fifofull_level_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->fifofull_level = val; |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(fifofull_level); |
| |
| static ssize_t addr_idx_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->addr_idx; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t addr_idx_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| if (val >= drvdata->nr_addr_cmp) |
| return -EINVAL; |
| |
| /* |
| * Use spinlock to ensure index doesn't change while it gets |
| * dereferenced multiple times within a spinlock block elsewhere. |
| */ |
| spin_lock(&drvdata->spinlock); |
| config->addr_idx = val; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(addr_idx); |
| |
| static ssize_t addr_single_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| u8 idx; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || |
| config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { |
| spin_unlock(&drvdata->spinlock); |
| return -EINVAL; |
| } |
| |
| val = config->addr_val[idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t addr_single_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| u8 idx; |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || |
| config->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { |
| spin_unlock(&drvdata->spinlock); |
| return -EINVAL; |
| } |
| |
| config->addr_val[idx] = val; |
| config->addr_type[idx] = ETM_ADDR_TYPE_SINGLE; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(addr_single); |
| |
| static ssize_t addr_range_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| u8 idx; |
| unsigned long val1, val2; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (idx % 2 != 0) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE && |
| config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || |
| (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && |
| config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| |
| val1 = config->addr_val[idx]; |
| val2 = config->addr_val[idx + 1]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx %#lx\n", val1, val2); |
| } |
| |
| static ssize_t addr_range_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| u8 idx; |
| unsigned long val1, val2; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| if (sscanf(buf, "%lx %lx", &val1, &val2) != 2) |
| return -EINVAL; |
| /* Lower address comparator cannot have a higher address value */ |
| if (val1 > val2) |
| return -EINVAL; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (idx % 2 != 0) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| if (!((config->addr_type[idx] == ETM_ADDR_TYPE_NONE && |
| config->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || |
| (config->addr_type[idx] == ETM_ADDR_TYPE_RANGE && |
| config->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| |
| config->addr_val[idx] = val1; |
| config->addr_type[idx] = ETM_ADDR_TYPE_RANGE; |
| config->addr_val[idx + 1] = val2; |
| config->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; |
| config->enable_ctrl1 |= (1 << (idx/2)); |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(addr_range); |
| |
| static ssize_t addr_start_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| u8 idx; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || |
| config->addr_type[idx] == ETM_ADDR_TYPE_START)) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| |
| val = config->addr_val[idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t addr_start_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| u8 idx; |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || |
| config->addr_type[idx] == ETM_ADDR_TYPE_START)) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| |
| config->addr_val[idx] = val; |
| config->addr_type[idx] = ETM_ADDR_TYPE_START; |
| config->startstop_ctrl |= (1 << idx); |
| config->enable_ctrl1 |= ETMTECR1_START_STOP; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(addr_start); |
| |
| static ssize_t addr_stop_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| u8 idx; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || |
| config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| |
| val = config->addr_val[idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t addr_stop_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| u8 idx; |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| idx = config->addr_idx; |
| if (!(config->addr_type[idx] == ETM_ADDR_TYPE_NONE || |
| config->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { |
| spin_unlock(&drvdata->spinlock); |
| return -EPERM; |
| } |
| |
| config->addr_val[idx] = val; |
| config->addr_type[idx] = ETM_ADDR_TYPE_STOP; |
| config->startstop_ctrl |= (1 << (idx + 16)); |
| config->enable_ctrl1 |= ETMTECR1_START_STOP; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(addr_stop); |
| |
| static ssize_t addr_acctype_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| val = config->addr_acctype[config->addr_idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t addr_acctype_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| config->addr_acctype[config->addr_idx] = val; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(addr_acctype); |
| |
| static ssize_t cntr_idx_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->cntr_idx; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t cntr_idx_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| if (val >= drvdata->nr_cntr) |
| return -EINVAL; |
| /* |
| * Use spinlock to ensure index doesn't change while it gets |
| * dereferenced multiple times within a spinlock block elsewhere. |
| */ |
| spin_lock(&drvdata->spinlock); |
| config->cntr_idx = val; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(cntr_idx); |
| |
| static ssize_t cntr_rld_val_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| val = config->cntr_rld_val[config->cntr_idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t cntr_rld_val_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| config->cntr_rld_val[config->cntr_idx] = val; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(cntr_rld_val); |
| |
| static ssize_t cntr_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| val = config->cntr_event[config->cntr_idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t cntr_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| config->cntr_event[config->cntr_idx] = val & ETM_EVENT_MASK; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(cntr_event); |
| |
| static ssize_t cntr_rld_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| spin_lock(&drvdata->spinlock); |
| val = config->cntr_rld_event[config->cntr_idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t cntr_rld_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| config->cntr_rld_event[config->cntr_idx] = val & ETM_EVENT_MASK; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(cntr_rld_event); |
| |
| static ssize_t cntr_val_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int i, ret = 0; |
| u32 val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| if (!local_read(&drvdata->mode)) { |
| spin_lock(&drvdata->spinlock); |
| for (i = 0; i < drvdata->nr_cntr; i++) |
| ret += sprintf(buf, "counter %d: %x\n", |
| i, config->cntr_val[i]); |
| spin_unlock(&drvdata->spinlock); |
| return ret; |
| } |
| |
| for (i = 0; i < drvdata->nr_cntr; i++) { |
| val = etm_readl(drvdata, ETMCNTVRn(i)); |
| ret += sprintf(buf, "counter %d: %x\n", i, val); |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t cntr_val_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| config->cntr_val[config->cntr_idx] = val; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(cntr_val); |
| |
| static ssize_t seq_12_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->seq_12_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t seq_12_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->seq_12_event = val & ETM_EVENT_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(seq_12_event); |
| |
| static ssize_t seq_21_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->seq_21_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t seq_21_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->seq_21_event = val & ETM_EVENT_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(seq_21_event); |
| |
| static ssize_t seq_23_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->seq_23_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t seq_23_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->seq_23_event = val & ETM_EVENT_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(seq_23_event); |
| |
| static ssize_t seq_31_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->seq_31_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t seq_31_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->seq_31_event = val & ETM_EVENT_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(seq_31_event); |
| |
| static ssize_t seq_32_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->seq_32_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t seq_32_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->seq_32_event = val & ETM_EVENT_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(seq_32_event); |
| |
| static ssize_t seq_13_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->seq_13_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t seq_13_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->seq_13_event = val & ETM_EVENT_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(seq_13_event); |
| |
| static ssize_t seq_curr_state_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val, flags; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| if (!local_read(&drvdata->mode)) { |
| val = config->seq_curr_state; |
| goto out; |
| } |
| |
| pm_runtime_get_sync(dev->parent); |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| |
| CS_UNLOCK(drvdata->base); |
| val = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); |
| CS_LOCK(drvdata->base); |
| |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| pm_runtime_put(dev->parent); |
| out: |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t seq_curr_state_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| if (val > ETM_SEQ_STATE_MAX_VAL) |
| return -EINVAL; |
| |
| config->seq_curr_state = val; |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(seq_curr_state); |
| |
| static ssize_t ctxid_idx_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->ctxid_idx; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t ctxid_idx_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| if (val >= drvdata->nr_ctxid_cmp) |
| return -EINVAL; |
| |
| /* |
| * Use spinlock to ensure index doesn't change while it gets |
| * dereferenced multiple times within a spinlock block elsewhere. |
| */ |
| spin_lock(&drvdata->spinlock); |
| config->ctxid_idx = val; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(ctxid_idx); |
| |
| static ssize_t ctxid_pid_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| /* |
| * Don't use contextID tracing if coming from a PID namespace. See |
| * comment in ctxid_pid_store(). |
| */ |
| if (task_active_pid_ns(current) != &init_pid_ns) |
| return -EINVAL; |
| |
| spin_lock(&drvdata->spinlock); |
| val = config->ctxid_pid[config->ctxid_idx]; |
| spin_unlock(&drvdata->spinlock); |
| |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t ctxid_pid_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long pid; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| /* |
| * When contextID tracing is enabled the tracers will insert the |
| * value found in the contextID register in the trace stream. But if |
| * a process is in a namespace the PID of that process as seen from the |
| * namespace won't be what the kernel sees, something that makes the |
| * feature confusing and can potentially leak kernel only information. |
| * As such refuse to use the feature if @current is not in the initial |
| * PID namespace. |
| */ |
| if (task_active_pid_ns(current) != &init_pid_ns) |
| return -EINVAL; |
| |
| ret = kstrtoul(buf, 16, &pid); |
| if (ret) |
| return ret; |
| |
| spin_lock(&drvdata->spinlock); |
| config->ctxid_pid[config->ctxid_idx] = pid; |
| spin_unlock(&drvdata->spinlock); |
| |
| return size; |
| } |
| static DEVICE_ATTR_RW(ctxid_pid); |
| |
| static ssize_t ctxid_mask_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| /* |
| * Don't use contextID tracing if coming from a PID namespace. See |
| * comment in ctxid_pid_store(). |
| */ |
| if (task_active_pid_ns(current) != &init_pid_ns) |
| return -EINVAL; |
| |
| val = config->ctxid_mask; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t ctxid_mask_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| /* |
| * Don't use contextID tracing if coming from a PID namespace. See |
| * comment in ctxid_pid_store(). |
| */ |
| if (task_active_pid_ns(current) != &init_pid_ns) |
| return -EINVAL; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->ctxid_mask = val; |
| return size; |
| } |
| static DEVICE_ATTR_RW(ctxid_mask); |
| |
| static ssize_t sync_freq_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->sync_freq; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t sync_freq_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->sync_freq = val & ETM_SYNC_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(sync_freq); |
| |
| static ssize_t timestamp_event_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| val = config->timestamp_event; |
| return sprintf(buf, "%#lx\n", val); |
| } |
| |
| static ssize_t timestamp_event_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret; |
| unsigned long val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| struct etm_config *config = &drvdata->config; |
| |
| ret = kstrtoul(buf, 16, &val); |
| if (ret) |
| return ret; |
| |
| config->timestamp_event = val & ETM_EVENT_MASK; |
| return size; |
| } |
| static DEVICE_ATTR_RW(timestamp_event); |
| |
| static ssize_t cpu_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int val; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| |
| val = drvdata->cpu; |
| return scnprintf(buf, PAGE_SIZE, "%d\n", val); |
| |
| } |
| static DEVICE_ATTR_RO(cpu); |
| |
| static ssize_t traceid_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| int trace_id; |
| struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| |
| trace_id = etm_read_alloc_trace_id(drvdata); |
| if (trace_id < 0) |
| return trace_id; |
| |
| return sysfs_emit(buf, "%#x\n", trace_id); |
| } |
| static DEVICE_ATTR_RO(traceid); |
| |
| static struct attribute *coresight_etm_attrs[] = { |
| &dev_attr_nr_addr_cmp.attr, |
| &dev_attr_nr_cntr.attr, |
| &dev_attr_nr_ctxid_cmp.attr, |
| &dev_attr_etmsr.attr, |
| &dev_attr_reset.attr, |
| &dev_attr_mode.attr, |
| &dev_attr_trigger_event.attr, |
| &dev_attr_enable_event.attr, |
| &dev_attr_fifofull_level.attr, |
| &dev_attr_addr_idx.attr, |
| &dev_attr_addr_single.attr, |
| &dev_attr_addr_range.attr, |
| &dev_attr_addr_start.attr, |
| &dev_attr_addr_stop.attr, |
| &dev_attr_addr_acctype.attr, |
| &dev_attr_cntr_idx.attr, |
| &dev_attr_cntr_rld_val.attr, |
| &dev_attr_cntr_event.attr, |
| &dev_attr_cntr_rld_event.attr, |
| &dev_attr_cntr_val.attr, |
| &dev_attr_seq_12_event.attr, |
| &dev_attr_seq_21_event.attr, |
| &dev_attr_seq_23_event.attr, |
| &dev_attr_seq_31_event.attr, |
| &dev_attr_seq_32_event.attr, |
| &dev_attr_seq_13_event.attr, |
| &dev_attr_seq_curr_state.attr, |
| &dev_attr_ctxid_idx.attr, |
| &dev_attr_ctxid_pid.attr, |
| &dev_attr_ctxid_mask.attr, |
| &dev_attr_sync_freq.attr, |
| &dev_attr_timestamp_event.attr, |
| &dev_attr_traceid.attr, |
| &dev_attr_cpu.attr, |
| NULL, |
| }; |
| |
| static struct attribute *coresight_etm_mgmt_attrs[] = { |
| coresight_simple_reg32(etmccr, ETMCCR), |
| coresight_simple_reg32(etmccer, ETMCCER), |
| coresight_simple_reg32(etmscr, ETMSCR), |
| coresight_simple_reg32(etmidr, ETMIDR), |
| coresight_simple_reg32(etmcr, ETMCR), |
| coresight_simple_reg32(etmtraceidr, ETMTRACEIDR), |
| coresight_simple_reg32(etmteevr, ETMTEEVR), |
| coresight_simple_reg32(etmtssvr, ETMTSSCR), |
| coresight_simple_reg32(etmtecr1, ETMTECR1), |
| coresight_simple_reg32(etmtecr2, ETMTECR2), |
| NULL, |
| }; |
| |
| static const struct attribute_group coresight_etm_group = { |
| .attrs = coresight_etm_attrs, |
| }; |
| |
| static const struct attribute_group coresight_etm_mgmt_group = { |
| .attrs = coresight_etm_mgmt_attrs, |
| .name = "mgmt", |
| }; |
| |
| const struct attribute_group *coresight_etm_groups[] = { |
| &coresight_etm_group, |
| &coresight_etm_mgmt_group, |
| NULL, |
| }; |