forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1296 lines
32 KiB
1296 lines
32 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright(C) 2015 Linaro Limited. All rights reserved. |
|
* Author: Mathieu Poirier <[email protected]> |
|
*/ |
|
|
|
#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); |
|
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 |= BIT(25); |
|
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) |
|
{ |
|
unsigned long val; |
|
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
|
|
|
val = etm_get_trace_id(drvdata); |
|
|
|
return sprintf(buf, "%#lx\n", val); |
|
} |
|
|
|
static ssize_t traceid_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); |
|
|
|
ret = kstrtoul(buf, 16, &val); |
|
if (ret) |
|
return ret; |
|
|
|
drvdata->traceid = val & ETM_TRACEID_MASK; |
|
return size; |
|
} |
|
static DEVICE_ATTR_RW(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, |
|
}; |
|
|
|
#define coresight_etm3x_reg(name, offset) \ |
|
coresight_simple_reg32(struct etm_drvdata, name, offset) |
|
|
|
coresight_etm3x_reg(etmccr, ETMCCR); |
|
coresight_etm3x_reg(etmccer, ETMCCER); |
|
coresight_etm3x_reg(etmscr, ETMSCR); |
|
coresight_etm3x_reg(etmidr, ETMIDR); |
|
coresight_etm3x_reg(etmcr, ETMCR); |
|
coresight_etm3x_reg(etmtraceidr, ETMTRACEIDR); |
|
coresight_etm3x_reg(etmteevr, ETMTEEVR); |
|
coresight_etm3x_reg(etmtssvr, ETMTSSCR); |
|
coresight_etm3x_reg(etmtecr1, ETMTECR1); |
|
coresight_etm3x_reg(etmtecr2, ETMTECR2); |
|
|
|
static struct attribute *coresight_etm_mgmt_attrs[] = { |
|
&dev_attr_etmccr.attr, |
|
&dev_attr_etmccer.attr, |
|
&dev_attr_etmscr.attr, |
|
&dev_attr_etmidr.attr, |
|
&dev_attr_etmcr.attr, |
|
&dev_attr_etmtraceidr.attr, |
|
&dev_attr_etmteevr.attr, |
|
&dev_attr_etmtssvr.attr, |
|
&dev_attr_etmtecr1.attr, |
|
&dev_attr_etmtecr2.attr, |
|
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, |
|
};
|
|
|