mirror of
https://github.com/torvalds/linux.git
synced 2026-07-30 19:21:28 +02:00
Merge "coresight-tmc: Add usb support to coresight tmc etr"
This commit is contained in:
commit
82fe4ec23e
|
|
@ -9,7 +9,8 @@ coresight-y := coresight-core.o coresight-etm-perf.o coresight-platform.o \
|
|||
coresight-syscfg-configfs.o
|
||||
obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o
|
||||
coresight-tmc-y := coresight-tmc-core.o coresight-tmc-etf.o \
|
||||
coresight-tmc-etr.o coresight-byte-cntr.o
|
||||
coresight-tmc-etr.o coresight-byte-cntr.o \
|
||||
coresight-tmc-usb.o
|
||||
obj-$(CONFIG_CORESIGHT_SINK_TPIU) += coresight-tpiu.o
|
||||
obj-$(CONFIG_CORESIGHT_SINK_ETBV10) += coresight-etb10.o
|
||||
obj-$(CONFIG_CORESIGHT_LINKS_AND_SINKS) += coresight-funnel.o \
|
||||
|
|
|
|||
|
|
@ -40,35 +40,44 @@ static void tmc_etr_read_bytes(struct byte_cntr *byte_cntr_data, loff_t *ppos,
|
|||
static irqreturn_t etr_handler(int irq, void *data)
|
||||
{
|
||||
struct byte_cntr *byte_cntr_data = data;
|
||||
struct tmc_drvdata *tmcdrvdata = byte_cntr_data->tmcdrvdata;
|
||||
|
||||
atomic_inc(&byte_cntr_data->irq_cnt);
|
||||
|
||||
wake_up(&byte_cntr_data->wq);
|
||||
if (tmcdrvdata->out_mode == TMC_ETR_OUT_MODE_USB) {
|
||||
atomic_inc(&byte_cntr_data->irq_cnt);
|
||||
wake_up(&byte_cntr_data->usb_wait_wq);
|
||||
} else if (tmcdrvdata->out_mode == TMC_ETR_OUT_MODE_MEM) {
|
||||
atomic_inc(&byte_cntr_data->irq_cnt);
|
||||
wake_up(&byte_cntr_data->wq);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void tmc_etr_flush_bytes(struct byte_cntr *byte_cntr_data, loff_t *ppos,
|
||||
size_t bytes, size_t *len)
|
||||
|
||||
static long tmc_etr_flush_remaining_bytes(struct tmc_drvdata *tmcdrvdata, loff_t *ppos,
|
||||
char **bufpp)
|
||||
{
|
||||
uint32_t rwp = 0;
|
||||
struct tmc_drvdata *tmcdrvdata = byte_cntr_data->tmcdrvdata;
|
||||
dma_addr_t paddr = tmcdrvdata->sysfs_buf->hwaddr;
|
||||
long rwp_offset, req_size, actual = 0;
|
||||
struct etr_buf *etr_buf = tmcdrvdata->sysfs_buf;
|
||||
|
||||
rwp = readl_relaxed(tmcdrvdata->base + TMC_RWP);
|
||||
rwp_offset = tmc_get_rwp_offset(tmcdrvdata);
|
||||
req_size = ((rwp_offset < *ppos) ? tmcdrvdata->size : 0) +
|
||||
rwp_offset - *ppos;
|
||||
|
||||
if (rwp >= (paddr + *ppos)) {
|
||||
if (bytes > (rwp - paddr - *ppos))
|
||||
*len = rwp - paddr - *ppos;
|
||||
}
|
||||
if (req_size > 0)
|
||||
actual = tmc_etr_buf_get_data(etr_buf, *ppos, req_size, bufpp);
|
||||
|
||||
return actual;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t tmc_etr_byte_cntr_read(struct file *fp, char __user *data,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
struct byte_cntr *byte_cntr_data = fp->private_data;
|
||||
struct tmc_drvdata *tmcdrvdata = byte_cntr_data->tmcdrvdata;
|
||||
char *bufp;
|
||||
char *bufp = NULL;
|
||||
long actual;
|
||||
int ret = 0;
|
||||
|
||||
if (!data)
|
||||
|
|
@ -76,8 +85,14 @@ static ssize_t tmc_etr_byte_cntr_read(struct file *fp, char __user *data,
|
|||
|
||||
mutex_lock(&byte_cntr_data->byte_cntr_lock);
|
||||
if (!byte_cntr_data->read_active) {
|
||||
ret = -EINVAL;
|
||||
goto err0;
|
||||
actual = tmc_etr_flush_remaining_bytes(tmcdrvdata, ppos, &bufp);
|
||||
if (actual > 0) {
|
||||
len = actual;
|
||||
goto copy;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
goto err0;
|
||||
}
|
||||
}
|
||||
|
||||
if (byte_cntr_data->enable) {
|
||||
|
|
@ -89,30 +104,32 @@ static ssize_t tmc_etr_byte_cntr_read(struct file *fp, char __user *data,
|
|||
return -ERESTARTSYS;
|
||||
mutex_lock(&byte_cntr_data->byte_cntr_lock);
|
||||
if (!byte_cntr_data->read_active) {
|
||||
ret = -EINVAL;
|
||||
goto err0;
|
||||
actual = tmc_etr_flush_remaining_bytes(tmcdrvdata, ppos, &bufp);
|
||||
if (actual > 0) {
|
||||
len = actual;
|
||||
goto copy;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
goto err0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tmc_etr_read_bytes(byte_cntr_data, ppos,
|
||||
byte_cntr_data->block_size, &len, &bufp);
|
||||
|
||||
} else {
|
||||
if (!atomic_read(&byte_cntr_data->irq_cnt)) {
|
||||
tmc_etr_flush_bytes(byte_cntr_data, ppos, byte_cntr_data->block_size,
|
||||
&len);
|
||||
if (!len) {
|
||||
ret = -EINVAL;
|
||||
goto err0;
|
||||
}
|
||||
actual = tmc_etr_flush_remaining_bytes(tmcdrvdata, ppos, &bufp);
|
||||
if (actual > 0) {
|
||||
len = actual;
|
||||
goto copy;
|
||||
} else {
|
||||
tmc_etr_read_bytes(byte_cntr_data, ppos,
|
||||
byte_cntr_data->block_size,
|
||||
&len, &bufp);
|
||||
ret = -EINVAL;
|
||||
goto err0;
|
||||
}
|
||||
}
|
||||
|
||||
copy:
|
||||
if (copy_to_user(data, bufp, len)) {
|
||||
mutex_unlock(&byte_cntr_data->byte_cntr_lock);
|
||||
dev_dbg(&tmcdrvdata->csdev->dev,
|
||||
|
|
@ -180,8 +197,12 @@ static int tmc_etr_byte_cntr_release(struct inode *in, struct file *fp)
|
|||
byte_cntr_data->read_active = false;
|
||||
|
||||
atomic_set(&byte_cntr_data->irq_cnt, 0);
|
||||
coresight_csr_set_byte_cntr(byte_cntr_data->csr,
|
||||
|
||||
if (byte_cntr_data->enable)
|
||||
coresight_csr_set_byte_cntr(byte_cntr_data->csr,
|
||||
byte_cntr_data->irqctrl_offset, 0);
|
||||
|
||||
disable_irq_wake(byte_cntr_data->byte_cntr_irq);
|
||||
mutex_unlock(&byte_cntr_data->byte_cntr_lock);
|
||||
|
||||
return 0;
|
||||
|
|
@ -195,12 +216,18 @@ static int tmc_etr_byte_cntr_open(struct inode *in, struct file *fp)
|
|||
|
||||
mutex_lock(&byte_cntr_data->byte_cntr_lock);
|
||||
|
||||
if (byte_cntr_data->read_active) {
|
||||
mutex_unlock(&byte_cntr_data->byte_cntr_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (tmcdrvdata->mode != CS_MODE_SYSFS ||
|
||||
!byte_cntr_data->block_size) {
|
||||
mutex_unlock(&byte_cntr_data->byte_cntr_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
enable_irq_wake(byte_cntr_data->byte_cntr_irq);
|
||||
/* IRQ is a '8- byte' counter and to observe interrupt at
|
||||
* 'block_size' bytes of data
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -20,13 +20,20 @@ struct byte_cntr {
|
|||
uint32_t block_size;
|
||||
int byte_cntr_irq;
|
||||
atomic_t irq_cnt;
|
||||
atomic_t usb_free_buf;
|
||||
wait_queue_head_t wq;
|
||||
wait_queue_head_t usb_wait_wq;
|
||||
struct workqueue_struct *usb_wq;
|
||||
struct qdss_request *usb_req;
|
||||
struct work_struct read_work;
|
||||
struct mutex usb_bypass_lock;
|
||||
struct mutex byte_cntr_lock;
|
||||
struct coresight_csr *csr;
|
||||
struct tmc_drvdata *tmcdrvdata;
|
||||
const char *name;
|
||||
const char *class_name;
|
||||
int irqctrl_offset;
|
||||
unsigned long offset;
|
||||
};
|
||||
|
||||
extern void tmc_etr_byte_cntr_start(struct byte_cntr *byte_cntr_data);
|
||||
|
|
|
|||
|
|
@ -526,22 +526,42 @@ static struct coresight_device *coresight_get_source(struct list_head *path)
|
|||
static int coresight_set_csr_atid(struct list_head *path,
|
||||
struct coresight_device *sink_csdev, bool enable)
|
||||
{
|
||||
int atid, ret = 0;
|
||||
int i, num, ret = 0;
|
||||
struct coresight_device *src_csdev;
|
||||
u32 *atid;
|
||||
|
||||
src_csdev = coresight_get_source(path);
|
||||
if (!src_csdev)
|
||||
ret = -EINVAL;
|
||||
|
||||
atid = of_coresight_get_atid(src_csdev);
|
||||
if (atid < 0)
|
||||
ret = -EINVAL;
|
||||
|
||||
if (csr_set_atid_ops)
|
||||
ret = csr_set_atid_ops->set_atid(sink_csdev, atid, enable);
|
||||
else
|
||||
if (!src_csdev) {
|
||||
ret = -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
num = of_coresight_get_atid_number(src_csdev);
|
||||
if (num < 0)
|
||||
return num;
|
||||
|
||||
atid = kcalloc(num, sizeof(*atid), GFP_KERNEL);
|
||||
if (!atid)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_coresight_get_atid(src_csdev, atid, num);
|
||||
if (ret < 0) {
|
||||
kfree(atid);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (csr_set_atid_ops) {
|
||||
for (i = 0; i < num; i++) {
|
||||
ret = csr_set_atid_ops->set_atid(sink_csdev, atid[i], enable);
|
||||
if (ret < 0) {
|
||||
kfree(atid);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} else
|
||||
ret = -EINVAL;
|
||||
|
||||
kfree(atid);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -1013,6 +1033,7 @@ void coresight_release_path(struct list_head *path)
|
|||
}
|
||||
|
||||
kfree(path);
|
||||
path = NULL;
|
||||
}
|
||||
|
||||
/* return true if the device is a suitable type for a default sink */
|
||||
|
|
@ -1265,8 +1286,8 @@ int coresight_enable(struct coresight_device *csdev)
|
|||
|
||||
path = coresight_build_path(csdev, sink);
|
||||
if (IS_ERR(path)) {
|
||||
pr_err("building path(s) failed\n");
|
||||
ret = PTR_ERR(path);
|
||||
pr_err("building path(s) failed %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
|
@ -1306,6 +1327,9 @@ static void __coresight_disable(struct coresight_device *csdev)
|
|||
if (ret)
|
||||
return;
|
||||
|
||||
if (csdev->def_sink)
|
||||
csdev->def_sink = NULL;
|
||||
|
||||
if (!csdev->enable || !coresight_disable_source(csdev))
|
||||
return;
|
||||
|
||||
|
|
@ -1434,6 +1458,8 @@ static ssize_t sink_name_store(struct device *dev,
|
|||
}
|
||||
|
||||
sink_name = kstrdup(buf, GFP_KERNEL);
|
||||
if (!sink_name)
|
||||
return -ENOMEM;
|
||||
sink_name[size-1] = 0;
|
||||
|
||||
hash = hashlen_hash(hashlen_string(NULL, sink_name));
|
||||
|
|
|
|||
|
|
@ -204,14 +204,25 @@ static int of_coresight_get_cpu(struct device *dev)
|
|||
return cpu;
|
||||
}
|
||||
|
||||
int of_coresight_get_atid(struct coresight_device *csdev)
|
||||
/*
|
||||
* of_coresight_get_atid_number: Get the atid number of a source device.
|
||||
*
|
||||
* Returns the number of the atid. If the result is less than zero, it means
|
||||
* failure.
|
||||
*/
|
||||
int of_coresight_get_atid_number(struct coresight_device *csdev)
|
||||
{
|
||||
int atid, ret = 0;
|
||||
return of_property_count_u32_elems(csdev->dev.parent->of_node, "atid");
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(csdev->dev.parent->of_node, "atid", &atid);
|
||||
if (ret)
|
||||
return ret;
|
||||
return atid;
|
||||
/*
|
||||
* of_coresight_get_atid: Get the atid array of a source device.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
int of_coresight_get_atid(struct coresight_device *csdev, u32 *atid, int atid_num)
|
||||
{
|
||||
return of_property_read_u32_array(csdev->dev.parent->of_node, "atid", atid, atid_num);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -189,7 +189,8 @@ struct csr_set_atid_op {
|
|||
|
||||
extern void coresight_set_csr_ops(const struct csr_set_atid_op *csr_op);
|
||||
extern void coresight_remove_csr_ops(void);
|
||||
int of_coresight_get_atid(struct coresight_device *src_dev);
|
||||
int of_coresight_get_atid(struct coresight_device *src_dev, u32 *atid, int atid_num);
|
||||
int of_coresight_get_atid_number(struct coresight_device *csdev);
|
||||
|
||||
/*
|
||||
* Macros and inline functions to handle CoreSight UCI data and driver
|
||||
|
|
|
|||
|
|
@ -181,19 +181,25 @@ static ssize_t tmc_read(struct file *file, char __user *data, size_t len,
|
|||
ssize_t actual;
|
||||
struct tmc_drvdata *drvdata = container_of(file->private_data,
|
||||
struct tmc_drvdata, miscdev);
|
||||
|
||||
mutex_lock(&drvdata->mem_lock);
|
||||
actual = tmc_get_sysfs_trace(drvdata, *ppos, len, &bufp);
|
||||
if (actual <= 0)
|
||||
if (actual <= 0) {
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (copy_to_user(data, bufp, actual)) {
|
||||
dev_dbg(&drvdata->csdev->dev,
|
||||
"%s: copy_to_user failed\n", __func__);
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
*ppos += actual;
|
||||
dev_dbg(&drvdata->csdev->dev, "%zu bytes copied\n", actual);
|
||||
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return actual;
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +302,7 @@ static ssize_t trigger_cntr_show(struct device *dev,
|
|||
struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val = drvdata->trigger_cntr;
|
||||
|
||||
return sprintf(buf, "%#lx\n", val);
|
||||
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
|
||||
}
|
||||
|
||||
static ssize_t trigger_cntr_store(struct device *dev,
|
||||
|
|
@ -321,7 +327,7 @@ static ssize_t buffer_size_show(struct device *dev,
|
|||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
return sprintf(buf, "%#x\n", drvdata->size);
|
||||
return scnprintf(buf, PAGE_SIZE, "%#x\n", drvdata->size);
|
||||
}
|
||||
|
||||
static ssize_t buffer_size_store(struct device *dev,
|
||||
|
|
@ -389,24 +395,68 @@ static ssize_t block_size_store(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RW(block_size);
|
||||
|
||||
static struct attribute *coresight_tmc_attrs[] = {
|
||||
static ssize_t out_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n",
|
||||
str_tmc_etr_out_mode[drvdata->out_mode]);
|
||||
}
|
||||
|
||||
static ssize_t out_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
char str[10] = "";
|
||||
int ret;
|
||||
|
||||
if (strlen(buf) >= 10)
|
||||
return -EINVAL;
|
||||
if (sscanf(buf, "%s", str) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
ret = tmc_etr_switch_mode(drvdata, str);
|
||||
return ret ? ret : size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(out_mode);
|
||||
|
||||
static struct attribute *coresight_tmc_etr_attrs[] = {
|
||||
&dev_attr_trigger_cntr.attr,
|
||||
&dev_attr_buffer_size.attr,
|
||||
&dev_attr_block_size.attr,
|
||||
&dev_attr_out_mode.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group coresight_tmc_group = {
|
||||
.attrs = coresight_tmc_attrs,
|
||||
static struct attribute *coresight_tmc_etf_attrs[] = {
|
||||
&dev_attr_trigger_cntr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group coresight_tmc_etr_group = {
|
||||
.attrs = coresight_tmc_etr_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group coresight_tmc_etf_group = {
|
||||
.attrs = coresight_tmc_etf_attrs,
|
||||
};
|
||||
|
||||
|
||||
static const struct attribute_group coresight_tmc_mgmt_group = {
|
||||
.attrs = coresight_tmc_mgmt_attrs,
|
||||
.name = "mgmt",
|
||||
};
|
||||
|
||||
static const struct attribute_group *coresight_tmc_groups[] = {
|
||||
&coresight_tmc_group,
|
||||
static const struct attribute_group *coresight_tmc_etr_groups[] = {
|
||||
&coresight_tmc_etr_group,
|
||||
&coresight_tmc_mgmt_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group *coresight_tmc_etf_groups[] = {
|
||||
&coresight_tmc_etf_group,
|
||||
&coresight_tmc_mgmt_group,
|
||||
NULL,
|
||||
};
|
||||
|
|
@ -520,6 +570,7 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
desc.access = CSDEV_ACCESS_IOMEM(base);
|
||||
|
||||
spin_lock_init(&drvdata->spinlock);
|
||||
mutex_init(&drvdata->mem_lock);
|
||||
|
||||
devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID);
|
||||
drvdata->config_type = BMVAL(devid, 6, 7);
|
||||
|
|
@ -528,6 +579,7 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
drvdata->pid = -1;
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
drvdata->out_mode = TMC_ETR_OUT_MODE_MEM;
|
||||
drvdata->size = tmc_etr_get_default_buffer_size(dev);
|
||||
drvdata->max_burst_size = tmc_etr_get_max_burst_size(dev);
|
||||
} else {
|
||||
|
|
@ -546,23 +598,25 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
}
|
||||
|
||||
desc.dev = dev;
|
||||
desc.groups = coresight_tmc_groups;
|
||||
|
||||
switch (drvdata->config_type) {
|
||||
case TMC_CONFIG_TYPE_ETB:
|
||||
desc.type = CORESIGHT_DEV_TYPE_SINK;
|
||||
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
|
||||
desc.ops = &tmc_etb_cs_ops;
|
||||
desc.groups = coresight_tmc_etf_groups;
|
||||
dev_list = &etb_devs;
|
||||
break;
|
||||
case TMC_CONFIG_TYPE_ETR:
|
||||
desc.type = CORESIGHT_DEV_TYPE_SINK;
|
||||
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_SYSMEM;
|
||||
desc.ops = &tmc_etr_cs_ops;
|
||||
desc.groups = coresight_tmc_etr_groups;
|
||||
ret = tmc_etr_setup_caps(dev, devid,
|
||||
coresight_get_uci_data(id));
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
idr_init(&drvdata->idr);
|
||||
mutex_init(&drvdata->idr_mutex);
|
||||
dev_list = &etr_devs;
|
||||
|
|
@ -573,12 +627,17 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
|
||||
drvdata->byte_cntr = byte_cntr_init(adev, drvdata);
|
||||
|
||||
ret = tmc_etr_usb_init(adev, drvdata);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
break;
|
||||
case TMC_CONFIG_TYPE_ETF:
|
||||
desc.type = CORESIGHT_DEV_TYPE_LINKSINK;
|
||||
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
|
||||
desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_FIFO;
|
||||
desc.ops = &tmc_etf_cs_ops;
|
||||
desc.groups = coresight_tmc_etf_groups;
|
||||
dev_list = &etf_devs;
|
||||
break;
|
||||
default:
|
||||
|
|
@ -629,7 +688,10 @@ static void tmc_shutdown(struct amba_device *adev)
|
|||
if (drvdata->mode == CS_MODE_DISABLED)
|
||||
goto out;
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR)
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR &&
|
||||
(drvdata->out_mode == TMC_ETR_OUT_MODE_MEM ||
|
||||
(drvdata->out_mode == TMC_ETR_OUT_MODE_USB &&
|
||||
drvdata->usb_data->usb_mode == TMC_ETR_USB_SW)))
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -231,6 +231,9 @@ static int tmc_enable_etf_sink_perf(struct coresight_device *csdev, void *data)
|
|||
struct perf_output_handle *handle = data;
|
||||
struct cs_buffers *buf = etm_perf_sink_config(handle);
|
||||
|
||||
if (buf == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
do {
|
||||
ret = -EINVAL;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/iommu.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/qcom-iommu-util.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
|
@ -51,6 +52,9 @@ struct etr_perf_buffer {
|
|||
/* Lower limit for ETR hardware buffer */
|
||||
#define TMC_ETR_PERF_MIN_BUF_SIZE SZ_1M
|
||||
|
||||
/* SW USB reserved memory size */
|
||||
#define TMC_ETR_SW_USB_BUF_SIZE SZ_64M
|
||||
|
||||
/*
|
||||
* The TMC ETR SG has a page size of 4K. The SG table contains pointers
|
||||
* to 4KB buffers. However, the OS may use a PAGE_SIZE different from
|
||||
|
|
@ -171,8 +175,15 @@ static void tmc_pages_free(struct tmc_pages *tmc_pages,
|
|||
__free_page(tmc_pages->pages[i]);
|
||||
}
|
||||
|
||||
kfree(tmc_pages->pages);
|
||||
kfree(tmc_pages->daddrs);
|
||||
if (is_vmalloc_addr(tmc_pages->pages))
|
||||
vfree(tmc_pages->pages);
|
||||
else
|
||||
kfree(tmc_pages->pages);
|
||||
|
||||
if (is_vmalloc_addr(tmc_pages->daddrs))
|
||||
vfree(tmc_pages->daddrs);
|
||||
else
|
||||
kfree(tmc_pages->daddrs);
|
||||
tmc_pages->pages = NULL;
|
||||
tmc_pages->daddrs = NULL;
|
||||
tmc_pages->nr_pages = 0;
|
||||
|
|
@ -198,14 +209,24 @@ static int tmc_pages_alloc(struct tmc_pages *tmc_pages,
|
|||
nr_pages = tmc_pages->nr_pages;
|
||||
tmc_pages->daddrs = kcalloc(nr_pages, sizeof(*tmc_pages->daddrs),
|
||||
GFP_KERNEL);
|
||||
if (!tmc_pages->daddrs)
|
||||
return -ENOMEM;
|
||||
if (!tmc_pages->daddrs) {
|
||||
tmc_pages->daddrs = vmalloc(sizeof(*tmc_pages->daddrs) * nr_pages);
|
||||
if (!tmc_pages->daddrs)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
tmc_pages->pages = kcalloc(nr_pages, sizeof(*tmc_pages->pages),
|
||||
GFP_KERNEL);
|
||||
if (!tmc_pages->pages) {
|
||||
kfree(tmc_pages->daddrs);
|
||||
tmc_pages->daddrs = NULL;
|
||||
return -ENOMEM;
|
||||
tmc_pages->pages = vmalloc(sizeof(*tmc_pages->pages) * nr_pages);
|
||||
if (!tmc_pages->pages) {
|
||||
if (is_vmalloc_addr(tmc_pages->daddrs))
|
||||
vfree(tmc_pages->daddrs);
|
||||
else
|
||||
kfree(tmc_pages->daddrs);
|
||||
tmc_pages->daddrs = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_pages; i++) {
|
||||
|
|
@ -258,6 +279,39 @@ void tmc_free_sg_table(struct tmc_sg_table *sg_table)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(tmc_free_sg_table);
|
||||
|
||||
static long tmc_flat_get_rwp_offset(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
dma_addr_t paddr = drvdata->sysfs_buf->hwaddr;
|
||||
u64 rwp;
|
||||
|
||||
rwp = tmc_read_rwp(drvdata);
|
||||
return rwp - paddr;
|
||||
}
|
||||
|
||||
static long tmc_sg_get_rwp_offset(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
struct etr_buf *etr_buf = drvdata->sysfs_buf;
|
||||
struct etr_sg_table *etr_table = etr_buf->private;
|
||||
struct tmc_sg_table *table = etr_table->sg_table;
|
||||
u64 rwp;
|
||||
long w_offset;
|
||||
|
||||
rwp = tmc_read_rwp(drvdata);
|
||||
w_offset = tmc_sg_get_data_page_offset(table, rwp);
|
||||
|
||||
return w_offset;
|
||||
}
|
||||
|
||||
long tmc_get_rwp_offset(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
struct etr_buf *etr_buf = drvdata->sysfs_buf;
|
||||
|
||||
if (etr_buf->mode == ETR_MODE_FLAT)
|
||||
return tmc_flat_get_rwp_offset(drvdata);
|
||||
else
|
||||
return tmc_sg_get_rwp_offset(drvdata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Alloc pages for the table. Since this will be used by the device,
|
||||
* allocate the pages closer to the device (i.e, dev_to_node(dev)
|
||||
|
|
@ -879,9 +933,23 @@ static struct etr_buf *tmc_alloc_etr_buf(struct tmc_drvdata *drvdata,
|
|||
bool has_sg, has_catu;
|
||||
struct etr_buf *etr_buf;
|
||||
struct device *dev = &drvdata->csdev->dev;
|
||||
int mapping_config = 0;
|
||||
struct iommu_domain *domain;
|
||||
|
||||
has_etr_sg = tmc_etr_has_cap(drvdata, TMC_ETR_SG);
|
||||
has_iommu = iommu_get_domain_for_dev(dev->parent);
|
||||
domain = iommu_get_domain_for_dev(dev->parent);
|
||||
if (domain) {
|
||||
mapping_config = qcom_iommu_get_mappings_configuration(domain);
|
||||
if (mapping_config < 0)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (mapping_config & QCOM_IOMMU_MAPPING_CONF_S1_BYPASS)
|
||||
has_iommu = false;
|
||||
else
|
||||
has_iommu = true;
|
||||
} else {
|
||||
has_iommu = false;
|
||||
}
|
||||
|
||||
has_catu = !!tmc_etr_get_catu_device(drvdata);
|
||||
|
||||
has_sg = has_catu || has_etr_sg;
|
||||
|
|
@ -989,7 +1057,7 @@ static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata)
|
|||
return;
|
||||
}
|
||||
|
||||
etr_buf->full = !!(status & TMC_STS_FULL);
|
||||
etr_buf->full = status & TMC_STS_FULL;
|
||||
|
||||
WARN_ON(!etr_buf->ops || !etr_buf->ops->sync);
|
||||
|
||||
|
|
@ -1111,7 +1179,12 @@ ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
|
|||
static struct etr_buf *
|
||||
tmc_etr_setup_sysfs_buf(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
return tmc_alloc_etr_buf(drvdata, drvdata->size,
|
||||
if (drvdata->out_mode == TMC_ETR_OUT_MODE_USB &&
|
||||
drvdata->usb_data->usb_mode == TMC_ETR_USB_SW)
|
||||
return tmc_alloc_etr_buf(drvdata, TMC_ETR_SW_USB_BUF_SIZE,
|
||||
0, cpu_to_node(0), NULL);
|
||||
else
|
||||
return tmc_alloc_etr_buf(drvdata, drvdata->size,
|
||||
0, cpu_to_node(0), NULL);
|
||||
}
|
||||
|
||||
|
|
@ -1185,22 +1258,31 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
|
|||
* with the lock released.
|
||||
*/
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
|
||||
if (!sysfs_buf || (sysfs_buf->size != drvdata->size)) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
if ((drvdata->out_mode == TMC_ETR_OUT_MODE_MEM)
|
||||
|| (drvdata->out_mode == TMC_ETR_OUT_MODE_USB &&
|
||||
drvdata->usb_data->usb_mode ==
|
||||
TMC_ETR_USB_SW)) {
|
||||
sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
|
||||
if (!sysfs_buf || (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM
|
||||
&& sysfs_buf->size != drvdata->size)
|
||||
|| (drvdata->out_mode == TMC_ETR_OUT_MODE_USB
|
||||
&& drvdata->usb_data->usb_mode == TMC_ETR_USB_SW
|
||||
&& sysfs_buf->size != TMC_ETR_SW_USB_BUF_SIZE)) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/* Allocate memory with the locks released */
|
||||
free_buf = new_buf = tmc_etr_setup_sysfs_buf(drvdata);
|
||||
if (IS_ERR(new_buf))
|
||||
return PTR_ERR(new_buf);
|
||||
/* Allocate memory with the locks released */
|
||||
free_buf = new_buf = tmc_etr_setup_sysfs_buf(drvdata);
|
||||
if (IS_ERR(new_buf))
|
||||
return PTR_ERR(new_buf);
|
||||
|
||||
/* Let's try again */
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
/* Let's try again */
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
if (drvdata->reading || drvdata->mode == CS_MODE_PERF) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
goto unlock_out;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1210,33 +1292,52 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev)
|
|||
*/
|
||||
if (drvdata->mode == CS_MODE_SYSFS) {
|
||||
atomic_inc(csdev->refcnt);
|
||||
goto out;
|
||||
goto unlock_out;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we don't have a buffer or it doesn't match the requested size,
|
||||
* use the buffer allocated above. Otherwise reuse the existing buffer.
|
||||
*/
|
||||
sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
|
||||
if (!sysfs_buf || (new_buf && sysfs_buf->size != new_buf->size)) {
|
||||
free_buf = sysfs_buf;
|
||||
drvdata->sysfs_buf = new_buf;
|
||||
if ((drvdata->out_mode == TMC_ETR_OUT_MODE_MEM) ||
|
||||
(drvdata->out_mode == TMC_ETR_OUT_MODE_USB &&
|
||||
drvdata->usb_data->usb_mode ==
|
||||
TMC_ETR_USB_SW)) {
|
||||
sysfs_buf = READ_ONCE(drvdata->sysfs_buf);
|
||||
if (!sysfs_buf || (new_buf && sysfs_buf->size != new_buf->size)) {
|
||||
free_buf = sysfs_buf;
|
||||
drvdata->sysfs_buf = new_buf;
|
||||
}
|
||||
|
||||
ret = tmc_etr_enable_hw(drvdata, drvdata->sysfs_buf);
|
||||
if (ret)
|
||||
goto unlock_out;
|
||||
}
|
||||
|
||||
ret = tmc_etr_enable_hw(drvdata, drvdata->sysfs_buf);
|
||||
if (!ret) {
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
atomic_inc(csdev->refcnt);
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
atomic_inc(csdev->refcnt);
|
||||
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
if (drvdata->out_mode == TMC_ETR_OUT_MODE_USB) {
|
||||
ret = tmc_usb_enable(drvdata->usb_data);
|
||||
if (ret) {
|
||||
atomic_dec(csdev->refcnt);
|
||||
drvdata->mode = CS_MODE_DISABLED;
|
||||
}
|
||||
}
|
||||
out:
|
||||
goto out;
|
||||
|
||||
unlock_out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
out:
|
||||
/* Free memory outside the spinlock if need be */
|
||||
if (free_buf)
|
||||
tmc_etr_free_sysfs_buf(free_buf);
|
||||
|
||||
if (!ret) {
|
||||
tmc_etr_byte_cntr_start(drvdata->byte_cntr);
|
||||
if (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM)
|
||||
tmc_etr_byte_cntr_start(drvdata->byte_cntr);
|
||||
dev_dbg(&csdev->dev, "TMC-ETR enabled\n");
|
||||
}
|
||||
|
||||
|
|
@ -1670,9 +1771,15 @@ static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, void *data)
|
|||
static int tmc_enable_etr_sink(struct coresight_device *csdev,
|
||||
u32 mode, void *data)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
int ret;
|
||||
|
||||
switch (mode) {
|
||||
case CS_MODE_SYSFS:
|
||||
return tmc_enable_etr_sink_sysfs(csdev);
|
||||
mutex_lock(&drvdata->mem_lock);
|
||||
ret = tmc_enable_etr_sink_sysfs(csdev);
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return ret;
|
||||
case CS_MODE_PERF:
|
||||
return tmc_enable_etr_sink_perf(csdev, data);
|
||||
}
|
||||
|
|
@ -1681,7 +1788,8 @@ static int tmc_enable_etr_sink(struct coresight_device *csdev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int tmc_disable_etr_sink(struct coresight_device *csdev)
|
||||
static int _tmc_disable_etr_sink(struct coresight_device *csdev,
|
||||
bool mode_switch)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
|
@ -1693,14 +1801,28 @@ static int tmc_disable_etr_sink(struct coresight_device *csdev)
|
|||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (atomic_dec_return(csdev->refcnt)) {
|
||||
if (atomic_dec_return(csdev->refcnt) && !mode_switch) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* Complain if we (somehow) got out of sync */
|
||||
WARN_ON_ONCE(drvdata->mode == CS_MODE_DISABLED);
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
|
||||
if (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM ||
|
||||
(drvdata->out_mode == TMC_ETR_OUT_MODE_USB &&
|
||||
drvdata->usb_data->usb_mode == TMC_ETR_USB_SW)) {
|
||||
if (drvdata->out_mode == TMC_ETR_OUT_MODE_USB) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
tmc_usb_disable(drvdata->usb_data);
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
}
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
} else {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
tmc_usb_disable(drvdata->usb_data);
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
}
|
||||
/* Dissociate from monitored process. */
|
||||
drvdata->pid = -1;
|
||||
drvdata->mode = CS_MODE_DISABLED;
|
||||
|
|
@ -1714,6 +1836,67 @@ static int tmc_disable_etr_sink(struct coresight_device *csdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tmc_disable_etr_sink(struct coresight_device *csdev)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&drvdata->mem_lock);
|
||||
ret = _tmc_disable_etr_sink(csdev, false);
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int tmc_etr_switch_mode(struct tmc_drvdata *drvdata, const char *out_mode)
|
||||
{
|
||||
enum tmc_etr_out_mode new_mode, old_mode;
|
||||
|
||||
mutex_lock(&drvdata->mem_lock);
|
||||
if (!strcmp(out_mode, str_tmc_etr_out_mode[TMC_ETR_OUT_MODE_MEM]))
|
||||
new_mode = TMC_ETR_OUT_MODE_MEM;
|
||||
else if (!strcmp(out_mode, str_tmc_etr_out_mode[TMC_ETR_OUT_MODE_USB])) {
|
||||
if (drvdata->usb_data->usb_mode == TMC_ETR_USB_NONE) {
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"USB mode is not supported.\n");
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
new_mode = TMC_ETR_OUT_MODE_USB;
|
||||
} else {
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (new_mode == drvdata->out_mode) {
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (drvdata->mode == CS_MODE_DISABLED) {
|
||||
drvdata->out_mode = new_mode;
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
_tmc_disable_etr_sink(drvdata->csdev, true);
|
||||
old_mode = drvdata->out_mode;
|
||||
drvdata->out_mode = new_mode;
|
||||
|
||||
if (tmc_enable_etr_sink_sysfs(drvdata->csdev)) {
|
||||
drvdata->out_mode = old_mode;
|
||||
tmc_enable_etr_sink_sysfs(drvdata->csdev);
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"Switch to %s failed. Fall back to %s.\n",
|
||||
str_tmc_etr_out_mode[new_mode],
|
||||
str_tmc_etr_out_mode[old_mode]);
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink tmc_etr_sink_ops = {
|
||||
.enable = tmc_enable_etr_sink,
|
||||
.disable = tmc_disable_etr_sink,
|
||||
|
|
@ -1735,6 +1918,7 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
|
|||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&drvdata->mem_lock);
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
|
|
@ -1763,6 +1947,7 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
|
|||
drvdata->reading = true;
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1776,6 +1961,7 @@ int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata)
|
|||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&drvdata->mem_lock);
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
/* RE-enable the TMC if need be */
|
||||
|
|
@ -1802,5 +1988,7 @@ int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata)
|
|||
if (sysfs_buf)
|
||||
tmc_etr_free_sysfs_buf(sysfs_buf);
|
||||
|
||||
mutex_unlock(&drvdata->mem_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
463
drivers/hwtracing/coresight/coresight-tmc-usb.c
Normal file
463
drivers/hwtracing/coresight/coresight-tmc-usb.c
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight TMC USB driver
|
||||
*/
|
||||
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/qcom-iommu-util.h>
|
||||
#include <linux/usb/usb_qdss.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/slab.h>
|
||||
#include "coresight-tmc-usb.h"
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-common.h"
|
||||
#include "coresight-tmc.h"
|
||||
|
||||
#define USB_BLK_SIZE 65536
|
||||
#define USB_SG_NUM (USB_BLK_SIZE / PAGE_SIZE)
|
||||
#define USB_BUF_NUM 255
|
||||
#define USB_TIME_OUT (5 * HZ)
|
||||
|
||||
#define TMC_AXICTL_VALUE (0xf02)
|
||||
#define TMC_FFCR_VALUE (0x133)
|
||||
|
||||
static int usb_bypass_start(struct byte_cntr *byte_cntr_data)
|
||||
{
|
||||
long offset;
|
||||
struct tmc_drvdata *tmcdrvdata;
|
||||
|
||||
if (!byte_cntr_data)
|
||||
return -ENOMEM;
|
||||
|
||||
tmcdrvdata = byte_cntr_data->tmcdrvdata;
|
||||
mutex_lock(&byte_cntr_data->usb_bypass_lock);
|
||||
|
||||
|
||||
dev_info(&tmcdrvdata->csdev->dev,
|
||||
"%s: Start usb bypass\n", __func__);
|
||||
if (tmcdrvdata->mode != CS_MODE_SYSFS) {
|
||||
mutex_unlock(&byte_cntr_data->usb_bypass_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset = tmc_get_rwp_offset(tmcdrvdata);
|
||||
if (offset < 0) {
|
||||
dev_err(&tmcdrvdata->csdev->dev,
|
||||
"%s: invalid rwp offset value\n", __func__);
|
||||
mutex_unlock(&byte_cntr_data->usb_bypass_lock);
|
||||
return offset;
|
||||
}
|
||||
byte_cntr_data->offset = offset;
|
||||
|
||||
/*Ensure usbch is ready*/
|
||||
if (!tmcdrvdata->usb_data->usbch) {
|
||||
int i;
|
||||
|
||||
for (i = TIMEOUT_US; i > 0; i--) {
|
||||
if (tmcdrvdata->usb_data->usbch)
|
||||
break;
|
||||
|
||||
if (i - 1)
|
||||
udelay(1);
|
||||
else {
|
||||
dev_err(&tmcdrvdata->csdev->dev,
|
||||
"timeout while waiting usbch to be ready\n");
|
||||
mutex_unlock(&byte_cntr_data->usb_bypass_lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
atomic_set(&byte_cntr_data->usb_free_buf, USB_BUF_NUM);
|
||||
|
||||
byte_cntr_data->read_active = true;
|
||||
/*
|
||||
* IRQ is a '8- byte' counter and to observe interrupt at
|
||||
* 'block_size' bytes of data
|
||||
*/
|
||||
coresight_csr_set_byte_cntr(byte_cntr_data->csr,
|
||||
byte_cntr_data->irqctrl_offset,
|
||||
USB_BLK_SIZE / 8);
|
||||
|
||||
atomic_set(&byte_cntr_data->irq_cnt, 0);
|
||||
mutex_unlock(&byte_cntr_data->usb_bypass_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usb_bypass_stop(struct byte_cntr *byte_cntr_data)
|
||||
{
|
||||
if (!byte_cntr_data)
|
||||
return;
|
||||
|
||||
mutex_lock(&byte_cntr_data->usb_bypass_lock);
|
||||
if (byte_cntr_data->read_active)
|
||||
byte_cntr_data->read_active = false;
|
||||
else {
|
||||
mutex_unlock(&byte_cntr_data->usb_bypass_lock);
|
||||
return;
|
||||
}
|
||||
wake_up(&byte_cntr_data->usb_wait_wq);
|
||||
pr_info("coresight: stop usb bypass\n");
|
||||
coresight_csr_set_byte_cntr(byte_cntr_data->csr, byte_cntr_data->irqctrl_offset, 0);
|
||||
mutex_unlock(&byte_cntr_data->usb_bypass_lock);
|
||||
|
||||
}
|
||||
|
||||
static int usb_transfer_small_packet(struct byte_cntr *drvdata, size_t *small_size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct tmc_drvdata *tmcdrvdata = drvdata->tmcdrvdata;
|
||||
struct etr_buf *etr_buf = tmcdrvdata->sysfs_buf;
|
||||
struct qdss_request *usb_req = NULL;
|
||||
size_t req_size;
|
||||
long actual;
|
||||
long w_offset;
|
||||
|
||||
w_offset = tmc_get_rwp_offset(tmcdrvdata);
|
||||
if (w_offset < 0) {
|
||||
ret = w_offset;
|
||||
dev_err_ratelimited(&tmcdrvdata->csdev->dev,
|
||||
"%s: RWP offset is invalid\n", __func__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
req_size = ((w_offset < drvdata->offset) ? etr_buf->size : 0) +
|
||||
w_offset - drvdata->offset;
|
||||
req_size = ((req_size + *small_size) < USB_BLK_SIZE) ? req_size :
|
||||
(USB_BLK_SIZE - *small_size);
|
||||
|
||||
while (req_size > 0) {
|
||||
|
||||
usb_req = kzalloc(sizeof(*usb_req), GFP_KERNEL);
|
||||
if (!usb_req) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
actual = tmc_etr_buf_get_data(etr_buf, drvdata->offset,
|
||||
req_size, &usb_req->buf);
|
||||
|
||||
if (actual <= 0 || actual > req_size) {
|
||||
kfree(usb_req);
|
||||
usb_req = NULL;
|
||||
dev_err_ratelimited(&tmcdrvdata->csdev->dev,
|
||||
"%s: Invalid data in ETR\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
usb_req->length = actual;
|
||||
drvdata->usb_req = usb_req;
|
||||
req_size -= actual;
|
||||
|
||||
if ((drvdata->offset + actual) >=
|
||||
tmcdrvdata->sysfs_buf->size)
|
||||
drvdata->offset = 0;
|
||||
else
|
||||
drvdata->offset += actual;
|
||||
|
||||
*small_size += actual;
|
||||
|
||||
if (atomic_read(&drvdata->usb_free_buf) > 0) {
|
||||
ret = usb_qdss_write(tmcdrvdata->usb_data->usbch, usb_req);
|
||||
|
||||
if (ret) {
|
||||
kfree(usb_req);
|
||||
usb_req = NULL;
|
||||
drvdata->usb_req = NULL;
|
||||
dev_err_ratelimited(&tmcdrvdata->csdev->dev,
|
||||
"Write data failed:%d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
atomic_dec(&drvdata->usb_free_buf);
|
||||
} else {
|
||||
dev_dbg(&tmcdrvdata->csdev->dev,
|
||||
"Drop data, offset = %d, len = %d\n",
|
||||
drvdata->offset, req_size);
|
||||
kfree(usb_req);
|
||||
drvdata->usb_req = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usb_read_work_fn(struct work_struct *work)
|
||||
{
|
||||
int ret, i, seq = 0;
|
||||
struct qdss_request *usb_req = NULL;
|
||||
size_t req_size, req_sg_num, small_size = 0;
|
||||
long actual;
|
||||
ssize_t actual_total = 0;
|
||||
char *buf;
|
||||
struct byte_cntr *drvdata =
|
||||
container_of(work, struct byte_cntr, read_work);
|
||||
struct tmc_drvdata *tmcdrvdata = drvdata->tmcdrvdata;
|
||||
struct etr_buf *etr_buf = tmcdrvdata->sysfs_buf;
|
||||
|
||||
while (tmcdrvdata->mode == CS_MODE_SYSFS
|
||||
&& tmcdrvdata->out_mode == TMC_ETR_OUT_MODE_USB) {
|
||||
if (!atomic_read(&drvdata->irq_cnt)) {
|
||||
ret = wait_event_interruptible_timeout(
|
||||
drvdata->usb_wait_wq,
|
||||
atomic_read(&drvdata->irq_cnt) > 0
|
||||
|| tmcdrvdata->mode != CS_MODE_SYSFS || tmcdrvdata->out_mode
|
||||
!= TMC_ETR_OUT_MODE_USB
|
||||
|| !drvdata->read_active, USB_TIME_OUT);
|
||||
if (ret == -ERESTARTSYS || tmcdrvdata->mode != CS_MODE_SYSFS
|
||||
|| tmcdrvdata->out_mode != TMC_ETR_OUT_MODE_USB
|
||||
|| !drvdata->read_active)
|
||||
break;
|
||||
|
||||
if (ret == 0) {
|
||||
ret = usb_transfer_small_packet(drvdata, &small_size);
|
||||
if (ret && ret != -EAGAIN)
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
req_size = USB_BLK_SIZE - small_size;
|
||||
small_size = 0;
|
||||
actual_total = 0;
|
||||
|
||||
if (req_size > 0) {
|
||||
seq++;
|
||||
req_sg_num = (req_size - 1) / PAGE_SIZE + 1;
|
||||
usb_req = kzalloc(sizeof(*usb_req), GFP_KERNEL);
|
||||
if (!usb_req)
|
||||
return;
|
||||
usb_req->sg = kcalloc(req_sg_num,
|
||||
sizeof(*(usb_req->sg)), GFP_KERNEL);
|
||||
if (!usb_req->sg) {
|
||||
kfree(usb_req);
|
||||
usb_req = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < req_sg_num; i++) {
|
||||
actual = tmc_etr_buf_get_data(etr_buf,
|
||||
drvdata->offset,
|
||||
PAGE_SIZE, &buf);
|
||||
|
||||
if (actual <= 0 || actual > PAGE_SIZE) {
|
||||
kfree(usb_req->sg);
|
||||
kfree(usb_req);
|
||||
usb_req = NULL;
|
||||
dev_err_ratelimited(
|
||||
&tmcdrvdata->csdev->dev,
|
||||
"Invalid data in ETR\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sg_set_buf(&usb_req->sg[i], buf, actual);
|
||||
|
||||
if (i == 0)
|
||||
usb_req->buf = buf;
|
||||
if (i == req_sg_num - 1)
|
||||
sg_mark_end(&usb_req->sg[i]);
|
||||
|
||||
if ((drvdata->offset + actual) >=
|
||||
tmcdrvdata->sysfs_buf->size)
|
||||
drvdata->offset = 0;
|
||||
else
|
||||
drvdata->offset += actual;
|
||||
actual_total += actual;
|
||||
}
|
||||
|
||||
usb_req->length = actual_total;
|
||||
drvdata->usb_req = usb_req;
|
||||
usb_req->num_sgs = i;
|
||||
|
||||
if (atomic_read(&drvdata->usb_free_buf) > 0) {
|
||||
ret = usb_qdss_write(tmcdrvdata->usb_data->usbch,
|
||||
drvdata->usb_req);
|
||||
if (ret) {
|
||||
kfree(usb_req->sg);
|
||||
kfree(usb_req);
|
||||
usb_req = NULL;
|
||||
drvdata->usb_req = NULL;
|
||||
dev_err_ratelimited(
|
||||
&tmcdrvdata->csdev->dev,
|
||||
"Write data failed:%d\n", ret);
|
||||
if (ret == -EAGAIN)
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
atomic_dec(&drvdata->usb_free_buf);
|
||||
|
||||
} else {
|
||||
dev_dbg(&tmcdrvdata->csdev->dev,
|
||||
"Drop data, offset = %d, seq = %d, irq = %d\n",
|
||||
drvdata->offset, seq,
|
||||
atomic_read(&drvdata->irq_cnt));
|
||||
kfree(usb_req->sg);
|
||||
kfree(usb_req);
|
||||
drvdata->usb_req = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (atomic_read(&drvdata->irq_cnt) > 0)
|
||||
atomic_dec(&drvdata->irq_cnt);
|
||||
}
|
||||
dev_err(&tmcdrvdata->csdev->dev, "TMC has been stopped.\n");
|
||||
}
|
||||
|
||||
static void usb_write_done(struct byte_cntr *drvdata,
|
||||
struct qdss_request *d_req)
|
||||
{
|
||||
atomic_inc(&drvdata->usb_free_buf);
|
||||
if (d_req->status)
|
||||
pr_err_ratelimited("USB write failed err:%d\n", d_req->status);
|
||||
kfree(d_req->sg);
|
||||
kfree(d_req);
|
||||
}
|
||||
|
||||
static int usb_bypass_init(struct byte_cntr *byte_cntr_data)
|
||||
{
|
||||
byte_cntr_data->usb_wq = create_singlethread_workqueue("byte-cntr");
|
||||
if (!byte_cntr_data->usb_wq)
|
||||
return -ENOMEM;
|
||||
|
||||
byte_cntr_data->offset = 0;
|
||||
mutex_init(&byte_cntr_data->usb_bypass_lock);
|
||||
init_waitqueue_head(&byte_cntr_data->usb_wait_wq);
|
||||
atomic_set(&byte_cntr_data->usb_free_buf, USB_BUF_NUM);
|
||||
INIT_WORK(&(byte_cntr_data->read_work), usb_read_work_fn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void usb_notifier(void *priv, unsigned int event, struct qdss_request *d_req,
|
||||
struct usb_qdss_ch *ch)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = priv;
|
||||
int ret = 0;
|
||||
|
||||
if (!drvdata)
|
||||
return;
|
||||
|
||||
if (drvdata->out_mode != TMC_ETR_OUT_MODE_USB) {
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"%s: ETR is not USB mode.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case USB_QDSS_CONNECT:
|
||||
if (drvdata->mode == CS_MODE_DISABLED) {
|
||||
dev_err_ratelimited(&drvdata->csdev->dev,
|
||||
"%s: ETR is disabled.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (drvdata->usb_data->usb_mode == TMC_ETR_USB_SW) {
|
||||
ret = usb_bypass_start(drvdata->byte_cntr);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
usb_qdss_alloc_req(ch, USB_BUF_NUM);
|
||||
queue_work(drvdata->byte_cntr->usb_wq, &(drvdata->byte_cntr->read_work));
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_QDSS_DISCONNECT:
|
||||
if (drvdata->mode == CS_MODE_DISABLED) {
|
||||
dev_err_ratelimited(&drvdata->csdev->dev,
|
||||
"%s: ETR is disabled.\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (drvdata->usb_data->usb_mode == TMC_ETR_USB_SW) {
|
||||
usb_bypass_stop(drvdata->byte_cntr);
|
||||
flush_work(&((drvdata->byte_cntr->read_work)));
|
||||
usb_qdss_free_req(drvdata->usb_data->usbch);
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_QDSS_DATA_WRITE_DONE:
|
||||
if (drvdata->usb_data->usb_mode == TMC_ETR_USB_SW)
|
||||
usb_write_done(drvdata->byte_cntr, d_req);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool tmc_etr_support_usb_bypass(struct device *dev)
|
||||
{
|
||||
return fwnode_property_present(dev->fwnode, "qcom,sw-usb");
|
||||
}
|
||||
|
||||
int tmc_usb_enable(struct tmc_usb_data *usb_data)
|
||||
{
|
||||
struct tmc_drvdata *tmcdrvdata;
|
||||
|
||||
if (!usb_data)
|
||||
return -EINVAL;
|
||||
|
||||
tmcdrvdata = usb_data->tmcdrvdata;
|
||||
if (usb_data->usb_mode == TMC_ETR_USB_SW)
|
||||
usb_data->usbch = usb_qdss_open(USB_QDSS_CH_SW, tmcdrvdata, usb_notifier);
|
||||
|
||||
if (IS_ERR_OR_NULL(usb_data->usbch)) {
|
||||
dev_err(&tmcdrvdata->csdev->dev, "usb_qdss_open failed for qdss.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tmc_usb_disable(struct tmc_usb_data *usb_data)
|
||||
{
|
||||
struct tmc_drvdata *tmcdrvdata = usb_data->tmcdrvdata;
|
||||
|
||||
if (usb_data->usb_mode == TMC_ETR_USB_SW)
|
||||
usb_bypass_stop(tmcdrvdata->byte_cntr);
|
||||
|
||||
if (usb_data->usbch)
|
||||
usb_qdss_close(usb_data->usbch);
|
||||
else
|
||||
dev_err(&tmcdrvdata->csdev->dev, "usb channel is null.\n");
|
||||
}
|
||||
|
||||
int tmc_etr_usb_init(struct amba_device *adev,
|
||||
struct tmc_drvdata *drvdata)
|
||||
{
|
||||
struct device *dev = &adev->dev;
|
||||
struct tmc_usb_data *usb_data;
|
||||
struct byte_cntr *byte_cntr_data;
|
||||
int ret;
|
||||
|
||||
usb_data = devm_kzalloc(dev, sizeof(*usb_data), GFP_KERNEL);
|
||||
if (!usb_data)
|
||||
return -ENOMEM;
|
||||
|
||||
drvdata->usb_data = usb_data;
|
||||
drvdata->usb_data->tmcdrvdata = drvdata;
|
||||
byte_cntr_data = drvdata->byte_cntr;
|
||||
|
||||
if (tmc_etr_support_usb_bypass(dev)) {
|
||||
usb_data->usb_mode = TMC_ETR_USB_SW;
|
||||
|
||||
if (!byte_cntr_data)
|
||||
return -EINVAL;
|
||||
|
||||
ret = usb_bypass_init(byte_cntr_data);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
usb_data->usb_mode = TMC_ETR_USB_NONE;
|
||||
pr_err("%s: ETR usb property is not configured!\n",
|
||||
__func__, dev_name(dev));
|
||||
return 0;
|
||||
}
|
||||
29
drivers/hwtracing/coresight/coresight-tmc-usb.h
Normal file
29
drivers/hwtracing/coresight/coresight-tmc-usb.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CORESIGHT_TMC_USB_H
|
||||
#define _CORESIGHT_TMC_USB_H
|
||||
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/usb/usb_qdss.h>
|
||||
|
||||
#define TMC_USB_BAM_PIPE_INDEX 0
|
||||
#define TMC_USB_BAM_NR_PIPES 2
|
||||
|
||||
enum tmc_etr_usb_mode {
|
||||
TMC_ETR_USB_NONE,
|
||||
TMC_ETR_USB_SW,
|
||||
};
|
||||
|
||||
struct tmc_usb_data {
|
||||
struct usb_qdss_ch *usbch;
|
||||
enum tmc_etr_usb_mode usb_mode;
|
||||
struct tmc_drvdata *tmcdrvdata;
|
||||
};
|
||||
|
||||
extern int tmc_usb_enable(struct tmc_usb_data *usb_data);
|
||||
extern void tmc_usb_disable(struct tmc_usb_data *usb_data);
|
||||
|
||||
#endif
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/refcount.h>
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-byte-cntr.h"
|
||||
#include "coresight-tmc-usb.h"
|
||||
|
||||
#define TMC_RSZ 0x004
|
||||
#define TMC_STS 0x00c
|
||||
|
|
@ -141,6 +142,18 @@ enum etr_mode {
|
|||
|
||||
struct etr_buf_operations;
|
||||
|
||||
enum tmc_etr_out_mode {
|
||||
TMC_ETR_OUT_MODE_NONE,
|
||||
TMC_ETR_OUT_MODE_MEM,
|
||||
TMC_ETR_OUT_MODE_USB,
|
||||
};
|
||||
|
||||
static const char * const str_tmc_etr_out_mode[] = {
|
||||
[TMC_ETR_OUT_MODE_NONE] = "none",
|
||||
[TMC_ETR_OUT_MODE_MEM] = "mem",
|
||||
[TMC_ETR_OUT_MODE_USB] = "usb",
|
||||
};
|
||||
|
||||
/**
|
||||
* struct etr_buf - Details of the buffer used by ETR
|
||||
* refcount ; Number of sources currently using this etr_buf.
|
||||
|
|
@ -207,6 +220,7 @@ struct tmc_drvdata {
|
|||
u32 mode;
|
||||
enum tmc_config_type config_type;
|
||||
enum tmc_mem_intf_width memwidth;
|
||||
struct mutex mem_lock;
|
||||
u32 trigger_cntr;
|
||||
u32 etr_caps;
|
||||
struct idr idr;
|
||||
|
|
@ -217,6 +231,8 @@ struct tmc_drvdata {
|
|||
struct coresight_csr *csr;
|
||||
const char *csr_name;
|
||||
u32 atid_offset;
|
||||
enum tmc_etr_out_mode out_mode;
|
||||
struct tmc_usb_data *usb_data;
|
||||
};
|
||||
|
||||
struct etr_buf_operations {
|
||||
|
|
@ -264,6 +280,8 @@ struct tmc_sg_table {
|
|||
void tmc_wait_for_tmcready(struct tmc_drvdata *drvdata);
|
||||
void tmc_flush_and_stop(struct tmc_drvdata *drvdata);
|
||||
void tmc_enable_hw(struct tmc_drvdata *drvdata);
|
||||
extern int tmc_etr_usb_init(struct amba_device *adev,
|
||||
struct tmc_drvdata *drvdata);
|
||||
void tmc_disable_hw(struct tmc_drvdata *drvdata);
|
||||
u32 tmc_get_memwidth_mask(struct tmc_drvdata *drvdata);
|
||||
|
||||
|
|
@ -288,7 +306,8 @@ extern const struct coresight_ops tmc_etr_cs_ops;
|
|||
extern const struct csr_set_atid_op csr_atid_ops;
|
||||
ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
|
||||
loff_t pos, size_t len, char **bufpp);
|
||||
|
||||
long tmc_get_rwp_offset(struct tmc_drvdata *drvdata);
|
||||
int tmc_etr_switch_mode(struct tmc_drvdata *drvdata, const char *out_mode);
|
||||
|
||||
#define TMC_REG_PAIR(name, lo_off, hi_off) \
|
||||
static inline u64 \
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user