mirror of
https://github.com/torvalds/linux.git
synced 2026-06-08 06:25:52 +02:00
Merge branch 'linux-linaro-lsk-v4.4' into linux-linaro-lsk-v4.4-android
This commit is contained in:
commit
e1599ccfad
|
|
@ -6,13 +6,6 @@ Description: (RW) Add/remove a sink from a trace path. There can be multiple
|
|||
source for a single sink.
|
||||
ex: echo 1 > /sys/bus/coresight/devices/20010000.etb/enable_sink
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/status
|
||||
Date: November 2014
|
||||
KernelVersion: 3.19
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) List various control and status registers. The specific
|
||||
layout and content is driver specific.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/trigger_cntr
|
||||
Date: November 2014
|
||||
KernelVersion: 3.19
|
||||
|
|
@ -22,3 +15,65 @@ Description: (RW) Disables write access to the Trace RAM by stopping the
|
|||
following the trigger event. The number of 32-bit words written
|
||||
into the Trace RAM following the trigger event is equal to the
|
||||
value stored in this register+1 (from ARM ETB-TRM).
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/rdp
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Defines the depth, in words, of the trace RAM in powers of
|
||||
2. The value is read directly from HW register RDP, 0x004.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/sts
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the ETB status register. The value
|
||||
is read directly from HW register STS, 0x00C.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/rrp
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the ETB RAM Read Pointer register
|
||||
that is used to read entries from the Trace RAM over the APB
|
||||
interface. The value is read directly from HW register RRP,
|
||||
0x014.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/rwp
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the ETB RAM Write Pointer register
|
||||
that is used to sets the write pointer to write entries from
|
||||
the CoreSight bus into the Trace RAM. The value is read directly
|
||||
from HW register RWP, 0x018.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/trg
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Similar to "trigger_cntr" above except that this value is
|
||||
read directly from HW register TRG, 0x01C.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/ctl
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the ETB Control register. The value
|
||||
is read directly from HW register CTL, 0x020.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/ffsr
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the ETB Formatter and Flush Status
|
||||
register. The value is read directly from HW register FFSR,
|
||||
0x300.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/ffcr
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the ETB Formatter and Flush Control
|
||||
register. The value is read directly from HW register FFCR,
|
||||
0x304.
|
||||
|
|
|
|||
|
|
@ -359,6 +359,19 @@ Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
|||
Description: (R) Print the content of the Peripheral ID3 Register
|
||||
(0xFEC). The value is taken directly from the HW.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcconfig
|
||||
Date: February 2016
|
||||
KernelVersion: 4.07
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Print the content of the trace configuration register
|
||||
(0x010) as currently set by SW.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trctraceid
|
||||
Date: February 2016
|
||||
KernelVersion: 4.07
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Print the content of the trace ID register (0x040).
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr0
|
||||
Date: April 2015
|
||||
KernelVersion: 4.01
|
||||
|
|
|
|||
53
Documentation/ABI/testing/sysfs-bus-coresight-devices-stm
Normal file
53
Documentation/ABI/testing/sysfs-bus-coresight-devices-stm
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
What: /sys/bus/coresight/devices/<memory_map>.stm/enable_source
|
||||
Date: April 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (RW) Enable/disable tracing on this specific trace macrocell.
|
||||
Enabling the trace macrocell implies it has been configured
|
||||
properly and a sink has been identified for it. The path
|
||||
of coresight components linking the source to the sink is
|
||||
configured and managed automatically by the coresight framework.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.stm/hwevent_enable
|
||||
Date: April 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (RW) Provides access to the HW event enable register, used in
|
||||
conjunction with HW event bank select register.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.stm/hwevent_select
|
||||
Date: April 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (RW) Gives access to the HW event block select register
|
||||
(STMHEBSR) in order to configure up to 256 channels. Used in
|
||||
conjunction with "hwevent_enable" register as described above.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.stm/port_enable
|
||||
Date: April 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (RW) Provides access to the stimulus port enable register
|
||||
(STMSPER). Used in conjunction with "port_select" described
|
||||
below.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.stm/port_select
|
||||
Date: April 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (RW) Used to determine which bank of stimulus port bit in
|
||||
register STMSPER (see above) apply to.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.stm/status
|
||||
Date: April 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) List various control and status registers. The specific
|
||||
layout and content is driver specific.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.stm/traceid
|
||||
Date: April 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (RW) Holds the trace ID that will appear in the trace stream
|
||||
coming from this trace entity.
|
||||
|
|
@ -6,3 +6,80 @@ Description: (RW) Disables write access to the Trace RAM by stopping the
|
|||
formatter after a defined number of words have been stored
|
||||
following the trigger event. Additional interface for this
|
||||
driver are expected to be added as it matures.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/rsz
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Defines the size, in 32-bit words, of the local RAM buffer.
|
||||
The value is read directly from HW register RSZ, 0x004.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/sts
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the TMC status register. The value
|
||||
is read directly from HW register STS, 0x00C.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/rrp
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the TMC RAM Read Pointer register
|
||||
that is used to read entries from the Trace RAM over the APB
|
||||
interface. The value is read directly from HW register RRP,
|
||||
0x014.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/rwp
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the TMC RAM Write Pointer register
|
||||
that is used to sets the write pointer to write entries from
|
||||
the CoreSight bus into the Trace RAM. The value is read directly
|
||||
from HW register RWP, 0x018.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/trg
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Similar to "trigger_cntr" above except that this value is
|
||||
read directly from HW register TRG, 0x01C.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/ctl
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the TMC Control register. The value
|
||||
is read directly from HW register CTL, 0x020.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/ffsr
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the TMC Formatter and Flush Status
|
||||
register. The value is read directly from HW register FFSR,
|
||||
0x300.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/ffcr
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the TMC Formatter and Flush Control
|
||||
register. The value is read directly from HW register FFCR,
|
||||
0x304.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/mode
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Shows the value held by the TMC Mode register, which
|
||||
indicate the mode the device has been configured to enact. The
|
||||
The value is read directly from the MODE register, 0x028.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.tmc/mgmt/devid
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (R) Indicates the capabilities of the Coresight TMC.
|
||||
The value is read directly from the DEVID register, 0xFC8,
|
||||
|
|
|
|||
|
|
@ -12,3 +12,13 @@ KernelVersion: 4.3
|
|||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description:
|
||||
Shows the number of channels per master on this STM device.
|
||||
|
||||
What: /sys/class/stm/<stm>/hw_override
|
||||
Date: March 2016
|
||||
KernelVersion: 4.7
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description:
|
||||
Reads as 0 if master numbers in the STP stream produced by
|
||||
this stm device will match the master numbers assigned by
|
||||
the software or 1 if the stm hardware overrides software
|
||||
assigned masters.
|
||||
|
|
|
|||
|
|
@ -190,8 +190,8 @@ expected to be accessed and controlled using those entries.
|
|||
Last but not least, "struct module *owner" is expected to be set to reflect
|
||||
the information carried in "THIS_MODULE".
|
||||
|
||||
How to use
|
||||
----------
|
||||
How to use the tracer modules
|
||||
-----------------------------
|
||||
|
||||
Before trace collection can start, a coresight sink needs to be identify.
|
||||
There is no limit on the amount of sinks (nor sources) that can be enabled at
|
||||
|
|
@ -297,3 +297,36 @@ Info Tracing enabled
|
|||
Instruction 13570831 0x8026B584 E28DD00C false ADD sp,sp,#0xc
|
||||
Instruction 0 0x8026B588 E8BD8000 true LDM sp!,{pc}
|
||||
Timestamp Timestamp: 17107041535
|
||||
|
||||
How to use the STM module
|
||||
-------------------------
|
||||
|
||||
Using the System Trace Macrocell module is the same as the tracers - the only
|
||||
difference is that clients are driving the trace capture rather
|
||||
than the program flow through the code.
|
||||
|
||||
As with any other CoreSight component, specifics about the STM tracer can be
|
||||
found in sysfs with more information on each entry being found in [1]:
|
||||
|
||||
root@genericarmv8:~# ls /sys/bus/coresight/devices/20100000.stm
|
||||
enable_source hwevent_select port_enable subsystem uevent
|
||||
hwevent_enable mgmt port_select traceid
|
||||
root@genericarmv8:~#
|
||||
|
||||
Like any other source a sink needs to be identified and the STM enabled before
|
||||
being used:
|
||||
|
||||
root@genericarmv8:~# echo 1 > /sys/bus/coresight/devices/20010000.etf/enable_sink
|
||||
root@genericarmv8:~# echo 1 > /sys/bus/coresight/devices/20100000.stm/enable_source
|
||||
|
||||
From there user space applications can request and use channels using the devfs
|
||||
interface provided for that purpose by the generic STM API:
|
||||
|
||||
root@genericarmv8:~# ls -l /dev/20100000.stm
|
||||
crw------- 1 root root 10, 61 Jan 3 18:11 /dev/20100000.stm
|
||||
root@genericarmv8:~#
|
||||
|
||||
Details on how to use the generic STM API can be found here [2].
|
||||
|
||||
[1]. Documentation/ABI/testing/sysfs-bus-coresight-devices-stm
|
||||
[2]. Documentation/trace/stm.txt
|
||||
|
|
|
|||
|
|
@ -9356,6 +9356,7 @@ F: drivers/mmc/host/dw_mmc*
|
|||
SYSTEM TRACE MODULE CLASS
|
||||
M: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/ash/stm.git
|
||||
F: Documentation/trace/stm.txt
|
||||
F: drivers/hwtracing/stm/
|
||||
F: include/linux/stm.h
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@
|
|||
menuconfig CORESIGHT
|
||||
bool "CoreSight Tracing Support"
|
||||
select ARM_AMBA
|
||||
select PERF_EVENTS
|
||||
help
|
||||
This framework provides a kernel interface for the CoreSight debug
|
||||
and trace drivers to register themselves with. It's intended to build
|
||||
a topological view of the CoreSight components based on a DT
|
||||
specification and configure the right serie of components when a
|
||||
specification and configure the right series of components when a
|
||||
trace source gets enabled.
|
||||
|
||||
if CORESIGHT
|
||||
|
|
@ -77,4 +78,15 @@ config CORESIGHT_QCOM_REPLICATOR
|
|||
programmable ATB replicator sends the ATB trace stream from the
|
||||
ETB/ETF to the TPIUi and ETR.
|
||||
|
||||
config CORESIGHT_STM
|
||||
bool "CoreSight System Trace Macrocell driver"
|
||||
depends on (ARM && !(CPU_32v3 || CPU_32v4 || CPU_32v4T)) || ARM64
|
||||
select CORESIGHT_LINKS_AND_SINKS
|
||||
select STM
|
||||
help
|
||||
This driver provides support for hardware assisted software
|
||||
instrumentation based tracing. This is primarily used for
|
||||
logging useful software events or data coming from various entities
|
||||
in the system, possibly running different OSs
|
||||
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
#
|
||||
# Makefile for CoreSight drivers.
|
||||
#
|
||||
obj-$(CONFIG_CORESIGHT) += coresight.o
|
||||
obj-$(CONFIG_CORESIGHT) += coresight.o coresight-etm-perf.o
|
||||
obj-$(CONFIG_OF) += of_coresight.o
|
||||
obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o
|
||||
obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o \
|
||||
coresight-tmc-etf.o \
|
||||
coresight-tmc-etr.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 \
|
||||
coresight-replicator.o
|
||||
obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o coresight-etm-cp14.o
|
||||
obj-$(CONFIG_CORESIGHT_SOURCE_ETM4X) += coresight-etm4x.o
|
||||
obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o coresight-etm-cp14.o \
|
||||
coresight-etm3x-sysfs.o
|
||||
obj-$(CONFIG_CORESIGHT_SOURCE_ETM4X) += coresight-etm4x.o \
|
||||
coresight-etm4x-sysfs.o
|
||||
obj-$(CONFIG_CORESIGHT_QCOM_REPLICATOR) += coresight-replicator-qcom.o
|
||||
obj-$(CONFIG_CORESIGHT_STM) += coresight-stm.o
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Embedded Trace Buffer driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
|
|
@ -10,8 +12,8 @@
|
|||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <asm/local.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
|
@ -27,6 +29,11 @@
|
|||
#include <linux/coresight.h>
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/perf_event.h>
|
||||
|
||||
#include <asm/local.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
|
||||
|
|
@ -71,10 +78,10 @@
|
|||
* @csdev: component vitals needed by the framework.
|
||||
* @miscdev: specifics to handle "/dev/xyz.etb" entry.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @in_use: synchronise user space access to etb buffer.
|
||||
* @reading: synchronise user space access to etb buffer.
|
||||
* @mode: this ETB is being used.
|
||||
* @buf: area of memory where ETB buffer content gets sent.
|
||||
* @buffer_depth: size of @buf.
|
||||
* @enable: this ETB is being used.
|
||||
* @trigger_cntr: amount of words to store after a trigger.
|
||||
*/
|
||||
struct etb_drvdata {
|
||||
|
|
@ -84,10 +91,10 @@ struct etb_drvdata {
|
|||
struct coresight_device *csdev;
|
||||
struct miscdevice miscdev;
|
||||
spinlock_t spinlock;
|
||||
atomic_t in_use;
|
||||
local_t reading;
|
||||
local_t mode;
|
||||
u8 *buf;
|
||||
u32 buffer_depth;
|
||||
bool enable;
|
||||
u32 trigger_cntr;
|
||||
};
|
||||
|
||||
|
|
@ -132,18 +139,31 @@ static void etb_enable_hw(struct etb_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int etb_enable(struct coresight_device *csdev)
|
||||
static int etb_enable(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
u32 val;
|
||||
unsigned long flags;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
val = local_cmpxchg(&drvdata->mode,
|
||||
CS_MODE_DISABLED, mode);
|
||||
/*
|
||||
* When accessing from Perf, a HW buffer can be handled
|
||||
* by a single trace entity. In sysFS mode many tracers
|
||||
* can be logging to the same HW buffer.
|
||||
*/
|
||||
if (val == CS_MODE_PERF)
|
||||
return -EBUSY;
|
||||
|
||||
/* Nothing to do, the tracer is already enabled. */
|
||||
if (val == CS_MODE_SYSFS)
|
||||
goto out;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
etb_enable_hw(drvdata);
|
||||
drvdata->enable = true;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
out:
|
||||
dev_info(drvdata->dev, "ETB enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -244,17 +264,226 @@ static void etb_disable(struct coresight_device *csdev)
|
|||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
etb_disable_hw(drvdata);
|
||||
etb_dump_hw(drvdata);
|
||||
drvdata->enable = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
local_set(&drvdata->mode, CS_MODE_DISABLED);
|
||||
|
||||
dev_info(drvdata->dev, "ETB disabled\n");
|
||||
}
|
||||
|
||||
static void *etb_alloc_buffer(struct coresight_device *csdev, int cpu,
|
||||
void **pages, int nr_pages, bool overwrite)
|
||||
{
|
||||
int node;
|
||||
struct cs_buffers *buf;
|
||||
|
||||
if (cpu == -1)
|
||||
cpu = smp_processor_id();
|
||||
node = cpu_to_node(cpu);
|
||||
|
||||
buf = kzalloc_node(sizeof(struct cs_buffers), GFP_KERNEL, node);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
buf->snapshot = overwrite;
|
||||
buf->nr_pages = nr_pages;
|
||||
buf->data_pages = pages;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void etb_free_buffer(void *config)
|
||||
{
|
||||
struct cs_buffers *buf = config;
|
||||
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static int etb_set_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long head;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
/* wrap head around to the amount of space we have */
|
||||
head = handle->head & ((buf->nr_pages << PAGE_SHIFT) - 1);
|
||||
|
||||
/* find the page to write to */
|
||||
buf->cur = head / PAGE_SIZE;
|
||||
|
||||
/* and offset within that page */
|
||||
buf->offset = head % PAGE_SIZE;
|
||||
|
||||
local_set(&buf->data_size, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned long etb_reset_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config, bool *lost)
|
||||
{
|
||||
unsigned long size = 0;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
if (buf) {
|
||||
/*
|
||||
* In snapshot mode ->data_size holds the new address of the
|
||||
* ring buffer's head. The size itself is the whole address
|
||||
* range since we want the latest information.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
handle->head = local_xchg(&buf->data_size,
|
||||
buf->nr_pages << PAGE_SHIFT);
|
||||
|
||||
/*
|
||||
* Tell the tracer PMU how much we got in this run and if
|
||||
* something went wrong along the way. Nobody else can use
|
||||
* this cs_buffers instance until we are done. As such
|
||||
* resetting parameters here and squaring off with the ring
|
||||
* buffer API in the tracer PMU is fine.
|
||||
*/
|
||||
*lost = !!local_xchg(&buf->lost, 0);
|
||||
size = local_xchg(&buf->data_size, 0);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void etb_update_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
int i, cur;
|
||||
u8 *buf_ptr;
|
||||
u32 read_ptr, write_ptr, capacity;
|
||||
u32 status, read_data, to_read;
|
||||
unsigned long offset;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
capacity = drvdata->buffer_depth * ETB_FRAME_SIZE_WORDS;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
etb_disable_hw(drvdata);
|
||||
|
||||
/* unit is in words, not bytes */
|
||||
read_ptr = readl_relaxed(drvdata->base + ETB_RAM_READ_POINTER);
|
||||
write_ptr = readl_relaxed(drvdata->base + ETB_RAM_WRITE_POINTER);
|
||||
|
||||
/*
|
||||
* Entries should be aligned to the frame size. If they are not
|
||||
* go back to the last alignement point to give decoding tools a
|
||||
* chance to fix things.
|
||||
*/
|
||||
if (write_ptr % ETB_FRAME_SIZE_WORDS) {
|
||||
dev_err(drvdata->dev,
|
||||
"write_ptr: %lu not aligned to formatter frame size\n",
|
||||
(unsigned long)write_ptr);
|
||||
|
||||
write_ptr &= ~(ETB_FRAME_SIZE_WORDS - 1);
|
||||
local_inc(&buf->lost);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a hold of the status register and see if a wrap around
|
||||
* has occurred. If so adjust things accordingly. Otherwise
|
||||
* start at the beginning and go until the write pointer has
|
||||
* been reached.
|
||||
*/
|
||||
status = readl_relaxed(drvdata->base + ETB_STATUS_REG);
|
||||
if (status & ETB_STATUS_RAM_FULL) {
|
||||
local_inc(&buf->lost);
|
||||
to_read = capacity;
|
||||
read_ptr = write_ptr;
|
||||
} else {
|
||||
to_read = CIRC_CNT(write_ptr, read_ptr, drvdata->buffer_depth);
|
||||
to_read *= ETB_FRAME_SIZE_WORDS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure we don't overwrite data that hasn't been consumed yet.
|
||||
* It is entirely possible that the HW buffer has more data than the
|
||||
* ring buffer can currently handle. If so adjust the start address
|
||||
* to take only the last traces.
|
||||
*
|
||||
* In snapshot mode we are looking to get the latest traces only and as
|
||||
* such, we don't care about not overwriting data that hasn't been
|
||||
* processed by user space.
|
||||
*/
|
||||
if (!buf->snapshot && to_read > handle->size) {
|
||||
u32 mask = ~(ETB_FRAME_SIZE_WORDS - 1);
|
||||
|
||||
/* The new read pointer must be frame size aligned */
|
||||
to_read = handle->size & mask;
|
||||
/*
|
||||
* Move the RAM read pointer up, keeping in mind that
|
||||
* everything is in frame size units.
|
||||
*/
|
||||
read_ptr = (write_ptr + drvdata->buffer_depth) -
|
||||
to_read / ETB_FRAME_SIZE_WORDS;
|
||||
/* Wrap around if need be*/
|
||||
if (read_ptr > (drvdata->buffer_depth - 1))
|
||||
read_ptr -= drvdata->buffer_depth;
|
||||
/* let the decoder know we've skipped ahead */
|
||||
local_inc(&buf->lost);
|
||||
}
|
||||
|
||||
/* finally tell HW where we want to start reading from */
|
||||
writel_relaxed(read_ptr, drvdata->base + ETB_RAM_READ_POINTER);
|
||||
|
||||
cur = buf->cur;
|
||||
offset = buf->offset;
|
||||
for (i = 0; i < to_read; i += 4) {
|
||||
buf_ptr = buf->data_pages[cur] + offset;
|
||||
read_data = readl_relaxed(drvdata->base +
|
||||
ETB_RAM_READ_DATA_REG);
|
||||
*buf_ptr++ = read_data >> 0;
|
||||
*buf_ptr++ = read_data >> 8;
|
||||
*buf_ptr++ = read_data >> 16;
|
||||
*buf_ptr++ = read_data >> 24;
|
||||
|
||||
offset += 4;
|
||||
if (offset >= PAGE_SIZE) {
|
||||
offset = 0;
|
||||
cur++;
|
||||
/* wrap around at the end of the buffer */
|
||||
cur &= buf->nr_pages - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* reset ETB buffer for next run */
|
||||
writel_relaxed(0x0, drvdata->base + ETB_RAM_READ_POINTER);
|
||||
writel_relaxed(0x0, drvdata->base + ETB_RAM_WRITE_POINTER);
|
||||
|
||||
/*
|
||||
* In snapshot mode all we have to do is communicate to
|
||||
* perf_aux_output_end() the address of the current head. In full
|
||||
* trace mode the same function expects a size to move rb->aux_head
|
||||
* forward.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
local_set(&buf->data_size, (cur * PAGE_SIZE) + offset);
|
||||
else
|
||||
local_add(to_read, &buf->data_size);
|
||||
|
||||
etb_enable_hw(drvdata);
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink etb_sink_ops = {
|
||||
.enable = etb_enable,
|
||||
.disable = etb_disable,
|
||||
.alloc_buffer = etb_alloc_buffer,
|
||||
.free_buffer = etb_free_buffer,
|
||||
.set_buffer = etb_set_buffer,
|
||||
.reset_buffer = etb_reset_buffer,
|
||||
.update_buffer = etb_update_buffer,
|
||||
};
|
||||
|
||||
static const struct coresight_ops etb_cs_ops = {
|
||||
|
|
@ -266,7 +495,7 @@ static void etb_dump(struct etb_drvdata *drvdata)
|
|||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->enable) {
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS) {
|
||||
etb_disable_hw(drvdata);
|
||||
etb_dump_hw(drvdata);
|
||||
etb_enable_hw(drvdata);
|
||||
|
|
@ -281,7 +510,7 @@ static int etb_open(struct inode *inode, struct file *file)
|
|||
struct etb_drvdata *drvdata = container_of(file->private_data,
|
||||
struct etb_drvdata, miscdev);
|
||||
|
||||
if (atomic_cmpxchg(&drvdata->in_use, 0, 1))
|
||||
if (local_cmpxchg(&drvdata->reading, 0, 1))
|
||||
return -EBUSY;
|
||||
|
||||
dev_dbg(drvdata->dev, "%s: successfully opened\n", __func__);
|
||||
|
|
@ -317,7 +546,7 @@ static int etb_release(struct inode *inode, struct file *file)
|
|||
{
|
||||
struct etb_drvdata *drvdata = container_of(file->private_data,
|
||||
struct etb_drvdata, miscdev);
|
||||
atomic_set(&drvdata->in_use, 0);
|
||||
local_set(&drvdata->reading, 0);
|
||||
|
||||
dev_dbg(drvdata->dev, "%s: released\n", __func__);
|
||||
return 0;
|
||||
|
|
@ -331,47 +560,29 @@ static const struct file_operations etb_fops = {
|
|||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static ssize_t status_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 etb_rdr, etb_sr, etb_rrp, etb_rwp;
|
||||
u32 etb_trg, etb_cr, etb_ffsr, etb_ffcr;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
#define coresight_etb10_simple_func(name, offset) \
|
||||
coresight_simple_func(struct etb_drvdata, name, offset)
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
CS_UNLOCK(drvdata->base);
|
||||
coresight_etb10_simple_func(rdp, ETB_RAM_DEPTH_REG);
|
||||
coresight_etb10_simple_func(sts, ETB_STATUS_REG);
|
||||
coresight_etb10_simple_func(rrp, ETB_RAM_READ_POINTER);
|
||||
coresight_etb10_simple_func(rwp, ETB_RAM_WRITE_POINTER);
|
||||
coresight_etb10_simple_func(trg, ETB_TRG);
|
||||
coresight_etb10_simple_func(ctl, ETB_CTL_REG);
|
||||
coresight_etb10_simple_func(ffsr, ETB_FFSR);
|
||||
coresight_etb10_simple_func(ffcr, ETB_FFCR);
|
||||
|
||||
etb_rdr = readl_relaxed(drvdata->base + ETB_RAM_DEPTH_REG);
|
||||
etb_sr = readl_relaxed(drvdata->base + ETB_STATUS_REG);
|
||||
etb_rrp = readl_relaxed(drvdata->base + ETB_RAM_READ_POINTER);
|
||||
etb_rwp = readl_relaxed(drvdata->base + ETB_RAM_WRITE_POINTER);
|
||||
etb_trg = readl_relaxed(drvdata->base + ETB_TRG);
|
||||
etb_cr = readl_relaxed(drvdata->base + ETB_CTL_REG);
|
||||
etb_ffsr = readl_relaxed(drvdata->base + ETB_FFSR);
|
||||
etb_ffcr = readl_relaxed(drvdata->base + ETB_FFCR);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
return sprintf(buf,
|
||||
"Depth:\t\t0x%x\n"
|
||||
"Status:\t\t0x%x\n"
|
||||
"RAM read ptr:\t0x%x\n"
|
||||
"RAM wrt ptr:\t0x%x\n"
|
||||
"Trigger cnt:\t0x%x\n"
|
||||
"Control:\t0x%x\n"
|
||||
"Flush status:\t0x%x\n"
|
||||
"Flush ctrl:\t0x%x\n",
|
||||
etb_rdr, etb_sr, etb_rrp, etb_rwp,
|
||||
etb_trg, etb_cr, etb_ffsr, etb_ffcr);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
static DEVICE_ATTR_RO(status);
|
||||
static struct attribute *coresight_etb_mgmt_attrs[] = {
|
||||
&dev_attr_rdp.attr,
|
||||
&dev_attr_sts.attr,
|
||||
&dev_attr_rrp.attr,
|
||||
&dev_attr_rwp.attr,
|
||||
&dev_attr_trg.attr,
|
||||
&dev_attr_ctl.attr,
|
||||
&dev_attr_ffsr.attr,
|
||||
&dev_attr_ffcr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static ssize_t trigger_cntr_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
|
|
@ -401,10 +612,23 @@ static DEVICE_ATTR_RW(trigger_cntr);
|
|||
|
||||
static struct attribute *coresight_etb_attrs[] = {
|
||||
&dev_attr_trigger_cntr.attr,
|
||||
&dev_attr_status.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(coresight_etb);
|
||||
|
||||
static const struct attribute_group coresight_etb_group = {
|
||||
.attrs = coresight_etb_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group coresight_etb_mgmt_group = {
|
||||
.attrs = coresight_etb_mgmt_attrs,
|
||||
.name = "mgmt",
|
||||
};
|
||||
|
||||
const struct attribute_group *coresight_etb_groups[] = {
|
||||
&coresight_etb_group,
|
||||
&coresight_etb_mgmt_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int etb_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
{
|
||||
|
|
@ -481,7 +705,6 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
if (ret)
|
||||
goto err_misc_register;
|
||||
|
||||
dev_info(dev, "ETB initialized\n");
|
||||
return 0;
|
||||
|
||||
err_misc_register:
|
||||
|
|
@ -489,15 +712,6 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int etb_remove(struct amba_device *adev)
|
||||
{
|
||||
struct etb_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
misc_deregister(&drvdata->miscdev);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int etb_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
|
@ -537,14 +751,10 @@ static struct amba_driver etb_driver = {
|
|||
.name = "coresight-etb10",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &etb_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
|
||||
},
|
||||
.probe = etb_probe,
|
||||
.remove = etb_remove,
|
||||
.id_table = etb_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(etb_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Embedded Trace Buffer driver");
|
||||
builtin_amba_driver(etb_driver);
|
||||
|
|
|
|||
393
drivers/hwtracing/coresight/coresight-etm-perf.c
Normal file
393
drivers/hwtracing/coresight/coresight-etm-perf.c
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* Copyright(C) 2015 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/coresight-pmu.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
|
||||
static struct pmu etm_pmu;
|
||||
static bool etm_perf_up;
|
||||
|
||||
/**
|
||||
* struct etm_event_data - Coresight specifics associated to an event
|
||||
* @work: Handle to free allocated memory outside IRQ context.
|
||||
* @mask: Hold the CPU(s) this event was set for.
|
||||
* @snk_config: The sink configuration.
|
||||
* @path: An array of path, each slot for one CPU.
|
||||
*/
|
||||
struct etm_event_data {
|
||||
struct work_struct work;
|
||||
cpumask_t mask;
|
||||
void *snk_config;
|
||||
struct list_head **path;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct perf_output_handle, ctx_handle);
|
||||
static DEFINE_PER_CPU(struct coresight_device *, csdev_src);
|
||||
|
||||
/* ETMv3.5/PTM's ETMCR is 'config' */
|
||||
PMU_FORMAT_ATTR(cycacc, "config:" __stringify(ETM_OPT_CYCACC));
|
||||
PMU_FORMAT_ATTR(timestamp, "config:" __stringify(ETM_OPT_TS));
|
||||
|
||||
static struct attribute *etm_config_formats_attr[] = {
|
||||
&format_attr_cycacc.attr,
|
||||
&format_attr_timestamp.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group etm_pmu_format_group = {
|
||||
.name = "format",
|
||||
.attrs = etm_config_formats_attr,
|
||||
};
|
||||
|
||||
static const struct attribute_group *etm_pmu_attr_groups[] = {
|
||||
&etm_pmu_format_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void etm_event_read(struct perf_event *event) {}
|
||||
|
||||
static int etm_event_init(struct perf_event *event)
|
||||
{
|
||||
if (event->attr.type != etm_pmu.type)
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_event_data(struct work_struct *work)
|
||||
{
|
||||
int cpu;
|
||||
cpumask_t *mask;
|
||||
struct etm_event_data *event_data;
|
||||
struct coresight_device *sink;
|
||||
|
||||
event_data = container_of(work, struct etm_event_data, work);
|
||||
mask = &event_data->mask;
|
||||
/*
|
||||
* First deal with the sink configuration. See comment in
|
||||
* etm_setup_aux() about why we take the first available path.
|
||||
*/
|
||||
if (event_data->snk_config) {
|
||||
cpu = cpumask_first(mask);
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (sink_ops(sink)->free_buffer)
|
||||
sink_ops(sink)->free_buffer(event_data->snk_config);
|
||||
}
|
||||
|
||||
for_each_cpu(cpu, mask) {
|
||||
if (event_data->path[cpu])
|
||||
coresight_release_path(event_data->path[cpu]);
|
||||
}
|
||||
|
||||
kfree(event_data->path);
|
||||
kfree(event_data);
|
||||
}
|
||||
|
||||
static void *alloc_event_data(int cpu)
|
||||
{
|
||||
int size;
|
||||
cpumask_t *mask;
|
||||
struct etm_event_data *event_data;
|
||||
|
||||
/* First get memory for the session's data */
|
||||
event_data = kzalloc(sizeof(struct etm_event_data), GFP_KERNEL);
|
||||
if (!event_data)
|
||||
return NULL;
|
||||
|
||||
/* Make sure nothing disappears under us */
|
||||
get_online_cpus();
|
||||
size = num_online_cpus();
|
||||
|
||||
mask = &event_data->mask;
|
||||
if (cpu != -1)
|
||||
cpumask_set_cpu(cpu, mask);
|
||||
else
|
||||
cpumask_copy(mask, cpu_online_mask);
|
||||
put_online_cpus();
|
||||
|
||||
/*
|
||||
* Each CPU has a single path between source and destination. As such
|
||||
* allocate an array using CPU numbers as indexes. That way a path
|
||||
* for any CPU can easily be accessed at any given time. We proceed
|
||||
* the same way for sessions involving a single CPU. The cost of
|
||||
* unused memory when dealing with single CPU trace scenarios is small
|
||||
* compared to the cost of searching through an optimized array.
|
||||
*/
|
||||
event_data->path = kcalloc(size,
|
||||
sizeof(struct list_head *), GFP_KERNEL);
|
||||
if (!event_data->path) {
|
||||
kfree(event_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return event_data;
|
||||
}
|
||||
|
||||
static void etm_free_aux(void *data)
|
||||
{
|
||||
struct etm_event_data *event_data = data;
|
||||
|
||||
schedule_work(&event_data->work);
|
||||
}
|
||||
|
||||
static void *etm_setup_aux(int event_cpu, void **pages,
|
||||
int nr_pages, bool overwrite)
|
||||
{
|
||||
int cpu;
|
||||
cpumask_t *mask;
|
||||
struct coresight_device *sink;
|
||||
struct etm_event_data *event_data = NULL;
|
||||
|
||||
event_data = alloc_event_data(event_cpu);
|
||||
if (!event_data)
|
||||
return NULL;
|
||||
|
||||
INIT_WORK(&event_data->work, free_event_data);
|
||||
|
||||
mask = &event_data->mask;
|
||||
|
||||
/* Setup the path for each CPU in a trace session */
|
||||
for_each_cpu(cpu, mask) {
|
||||
struct coresight_device *csdev;
|
||||
|
||||
csdev = per_cpu(csdev_src, cpu);
|
||||
if (!csdev)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* Building a path doesn't enable it, it simply builds a
|
||||
* list of devices from source to sink that can be
|
||||
* referenced later when the path is actually needed.
|
||||
*/
|
||||
event_data->path[cpu] = coresight_build_path(csdev);
|
||||
if (!event_data->path[cpu])
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* In theory nothing prevent tracers in a trace session from being
|
||||
* associated with different sinks, nor having a sink per tracer. But
|
||||
* until we have HW with this kind of topology and a way to convey
|
||||
* sink assignement from the perf cmd line we need to assume tracers
|
||||
* in a trace session are using the same sink. Therefore pick the sink
|
||||
* found at the end of the first available path.
|
||||
*/
|
||||
cpu = cpumask_first(mask);
|
||||
/* Grab the sink at the end of the path */
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (!sink)
|
||||
goto err;
|
||||
|
||||
if (!sink_ops(sink)->alloc_buffer)
|
||||
goto err;
|
||||
|
||||
/* Get the AUX specific data from the sink buffer */
|
||||
event_data->snk_config =
|
||||
sink_ops(sink)->alloc_buffer(sink, cpu, pages,
|
||||
nr_pages, overwrite);
|
||||
if (!event_data->snk_config)
|
||||
goto err;
|
||||
|
||||
out:
|
||||
return event_data;
|
||||
|
||||
err:
|
||||
etm_free_aux(event_data);
|
||||
event_data = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static void etm_event_start(struct perf_event *event, int flags)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
struct etm_event_data *event_data;
|
||||
struct perf_output_handle *handle = this_cpu_ptr(&ctx_handle);
|
||||
struct coresight_device *sink, *csdev = per_cpu(csdev_src, cpu);
|
||||
|
||||
if (!csdev)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Deal with the ring buffer API and get a handle on the
|
||||
* session's information.
|
||||
*/
|
||||
event_data = perf_aux_output_begin(handle, event);
|
||||
if (!event_data)
|
||||
goto fail;
|
||||
|
||||
/* We need a sink, no need to continue without one */
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (WARN_ON_ONCE(!sink || !sink_ops(sink)->set_buffer))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Configure the sink */
|
||||
if (sink_ops(sink)->set_buffer(sink, handle,
|
||||
event_data->snk_config))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Nothing will happen without a path */
|
||||
if (coresight_enable_path(event_data->path[cpu], CS_MODE_PERF))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Tell the perf core the event is alive */
|
||||
event->hw.state = 0;
|
||||
|
||||
/* Finally enable the tracer */
|
||||
if (source_ops(csdev)->enable(csdev, &event->attr, CS_MODE_PERF))
|
||||
goto fail_end_stop;
|
||||
|
||||
out:
|
||||
return;
|
||||
|
||||
fail_end_stop:
|
||||
perf_aux_output_end(handle, 0, true);
|
||||
fail:
|
||||
event->hw.state = PERF_HES_STOPPED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static void etm_event_stop(struct perf_event *event, int mode)
|
||||
{
|
||||
bool lost;
|
||||
int cpu = smp_processor_id();
|
||||
unsigned long size;
|
||||
struct coresight_device *sink, *csdev = per_cpu(csdev_src, cpu);
|
||||
struct perf_output_handle *handle = this_cpu_ptr(&ctx_handle);
|
||||
struct etm_event_data *event_data = perf_get_aux(handle);
|
||||
|
||||
if (event->hw.state == PERF_HES_STOPPED)
|
||||
return;
|
||||
|
||||
if (!csdev)
|
||||
return;
|
||||
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (!sink)
|
||||
return;
|
||||
|
||||
/* stop tracer */
|
||||
source_ops(csdev)->disable(csdev);
|
||||
|
||||
/* tell the core */
|
||||
event->hw.state = PERF_HES_STOPPED;
|
||||
|
||||
if (mode & PERF_EF_UPDATE) {
|
||||
if (WARN_ON_ONCE(handle->event != event))
|
||||
return;
|
||||
|
||||
/* update trace information */
|
||||
if (!sink_ops(sink)->update_buffer)
|
||||
return;
|
||||
|
||||
sink_ops(sink)->update_buffer(sink, handle,
|
||||
event_data->snk_config);
|
||||
|
||||
if (!sink_ops(sink)->reset_buffer)
|
||||
return;
|
||||
|
||||
size = sink_ops(sink)->reset_buffer(sink, handle,
|
||||
event_data->snk_config,
|
||||
&lost);
|
||||
|
||||
perf_aux_output_end(handle, size, lost);
|
||||
}
|
||||
|
||||
/* Disabling the path make its elements available to other sessions */
|
||||
coresight_disable_path(event_data->path[cpu]);
|
||||
}
|
||||
|
||||
static int etm_event_add(struct perf_event *event, int mode)
|
||||
{
|
||||
int ret = 0;
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
if (mode & PERF_EF_START) {
|
||||
etm_event_start(event, 0);
|
||||
if (hwc->state & PERF_HES_STOPPED)
|
||||
ret = -EINVAL;
|
||||
} else {
|
||||
hwc->state = PERF_HES_STOPPED;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void etm_event_del(struct perf_event *event, int mode)
|
||||
{
|
||||
etm_event_stop(event, PERF_EF_UPDATE);
|
||||
}
|
||||
|
||||
int etm_perf_symlink(struct coresight_device *csdev, bool link)
|
||||
{
|
||||
char entry[sizeof("cpu9999999")];
|
||||
int ret = 0, cpu = source_ops(csdev)->cpu_id(csdev);
|
||||
struct device *pmu_dev = etm_pmu.dev;
|
||||
struct device *cs_dev = &csdev->dev;
|
||||
|
||||
sprintf(entry, "cpu%d", cpu);
|
||||
|
||||
if (!etm_perf_up)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
if (link) {
|
||||
ret = sysfs_create_link(&pmu_dev->kobj, &cs_dev->kobj, entry);
|
||||
if (ret)
|
||||
return ret;
|
||||
per_cpu(csdev_src, cpu) = csdev;
|
||||
} else {
|
||||
sysfs_remove_link(&pmu_dev->kobj, entry);
|
||||
per_cpu(csdev_src, cpu) = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init etm_perf_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
etm_pmu.capabilities = PERF_PMU_CAP_EXCLUSIVE;
|
||||
|
||||
etm_pmu.attr_groups = etm_pmu_attr_groups;
|
||||
etm_pmu.task_ctx_nr = perf_sw_context;
|
||||
etm_pmu.read = etm_event_read;
|
||||
etm_pmu.event_init = etm_event_init;
|
||||
etm_pmu.setup_aux = etm_setup_aux;
|
||||
etm_pmu.free_aux = etm_free_aux;
|
||||
etm_pmu.start = etm_event_start;
|
||||
etm_pmu.stop = etm_event_stop;
|
||||
etm_pmu.add = etm_event_add;
|
||||
etm_pmu.del = etm_event_del;
|
||||
|
||||
ret = perf_pmu_register(&etm_pmu, CORESIGHT_ETM_PMU_NAME, -1);
|
||||
if (ret == 0)
|
||||
etm_perf_up = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
device_initcall(etm_perf_init);
|
||||
32
drivers/hwtracing/coresight/coresight-etm-perf.h
Normal file
32
drivers/hwtracing/coresight/coresight-etm-perf.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright(C) 2015 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _CORESIGHT_ETM_PERF_H
|
||||
#define _CORESIGHT_ETM_PERF_H
|
||||
|
||||
struct coresight_device;
|
||||
|
||||
#ifdef CONFIG_CORESIGHT
|
||||
int etm_perf_symlink(struct coresight_device *csdev, bool link);
|
||||
|
||||
#else
|
||||
static inline int etm_perf_symlink(struct coresight_device *csdev, bool link)
|
||||
{ return -EINVAL; }
|
||||
|
||||
#endif /* CONFIG_CORESIGHT */
|
||||
|
||||
#endif
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
#ifndef _CORESIGHT_CORESIGHT_ETM_H
|
||||
#define _CORESIGHT_CORESIGHT_ETM_H
|
||||
|
||||
#include <asm/local.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include "coresight-priv.h"
|
||||
|
||||
|
|
@ -109,7 +110,10 @@
|
|||
#define ETM_MODE_STALL BIT(2)
|
||||
#define ETM_MODE_TIMESTAMP BIT(3)
|
||||
#define ETM_MODE_CTXID BIT(4)
|
||||
#define ETM_MODE_ALL 0x1f
|
||||
#define ETM_MODE_ALL (ETM_MODE_EXCLUDE | ETM_MODE_CYCACC | \
|
||||
ETM_MODE_STALL | ETM_MODE_TIMESTAMP | \
|
||||
ETM_MODE_CTXID | ETM_MODE_EXCL_KERN | \
|
||||
ETM_MODE_EXCL_USER)
|
||||
|
||||
#define ETM_SQR_MASK 0x3
|
||||
#define ETM_TRACEID_MASK 0x3f
|
||||
|
|
@ -136,35 +140,16 @@
|
|||
#define ETM_DEFAULT_EVENT_VAL (ETM_HARD_WIRE_RES_A | \
|
||||
ETM_ADD_COMP_0 | \
|
||||
ETM_EVENT_NOT_A)
|
||||
|
||||
/**
|
||||
* struct etm_drvdata - specifics associated to an ETM component
|
||||
* @base: memory mapped base address for this component.
|
||||
* @dev: the device entity associated to this component.
|
||||
* @atclk: optional clock for the core parts of the ETM.
|
||||
* @csdev: component vitals needed by the framework.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @cpu: the cpu this component is affined to.
|
||||
* @port_size: port size as reported by ETMCR bit 4-6 and 21.
|
||||
* @arch: ETM/PTM version number.
|
||||
* @use_cpu14: true if management registers need to be accessed via CP14.
|
||||
* @enable: is this ETM/PTM currently tracing.
|
||||
* @sticky_enable: true if ETM base configuration has been done.
|
||||
* @boot_enable:true if we should start tracing at boot time.
|
||||
* @os_unlock: true if access to management registers is allowed.
|
||||
* @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR.
|
||||
* @nr_cntr: Number of counters as found in ETMCCR bit 13-15.
|
||||
* @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19.
|
||||
* @nr_ext_out: Number of external output as found in ETMCCR bit 20-22.
|
||||
* @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25.
|
||||
* @etmccr: value of register ETMCCR.
|
||||
* @etmccer: value of register ETMCCER.
|
||||
* @traceid: value of the current ID for this component.
|
||||
* struct etm_config - configuration information related to an ETM
|
||||
* @mode: controls various modes supported by this ETM/PTM.
|
||||
* @ctrl: used in conjunction with @mode.
|
||||
* @trigger_event: setting for register ETMTRIGGER.
|
||||
* @startstop_ctrl: setting for register ETMTSSCR.
|
||||
* @enable_event: setting for register ETMTEEVR.
|
||||
* @enable_ctrl1: setting for register ETMTECR1.
|
||||
* @enable_ctrl2: setting for register ETMTECR2.
|
||||
* @fifofull_level: setting for register ETMFFLR.
|
||||
* @addr_idx: index for the address comparator selection.
|
||||
* @addr_val: value for address comparator register.
|
||||
|
|
@ -189,36 +174,16 @@
|
|||
* @ctxid_mask: mask applicable to all the context IDs.
|
||||
* @sync_freq: Synchronisation frequency.
|
||||
* @timestamp_event: Defines an event that requests the insertion
|
||||
of a timestamp into the trace stream.
|
||||
* of a timestamp into the trace stream.
|
||||
*/
|
||||
struct etm_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct clk *atclk;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
int cpu;
|
||||
int port_size;
|
||||
u8 arch;
|
||||
bool use_cp14;
|
||||
bool enable;
|
||||
bool sticky_enable;
|
||||
bool boot_enable;
|
||||
bool os_unlock;
|
||||
u8 nr_addr_cmp;
|
||||
u8 nr_cntr;
|
||||
u8 nr_ext_inp;
|
||||
u8 nr_ext_out;
|
||||
u8 nr_ctxid_cmp;
|
||||
u32 etmccr;
|
||||
u32 etmccer;
|
||||
u32 traceid;
|
||||
struct etm_config {
|
||||
u32 mode;
|
||||
u32 ctrl;
|
||||
u32 trigger_event;
|
||||
u32 startstop_ctrl;
|
||||
u32 enable_event;
|
||||
u32 enable_ctrl1;
|
||||
u32 enable_ctrl2;
|
||||
u32 fifofull_level;
|
||||
u8 addr_idx;
|
||||
u32 addr_val[ETM_MAX_ADDR_CMP];
|
||||
|
|
@ -244,6 +209,56 @@ struct etm_drvdata {
|
|||
u32 timestamp_event;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct etm_drvdata - specifics associated to an ETM component
|
||||
* @base: memory mapped base address for this component.
|
||||
* @dev: the device entity associated to this component.
|
||||
* @atclk: optional clock for the core parts of the ETM.
|
||||
* @csdev: component vitals needed by the framework.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @cpu: the cpu this component is affined to.
|
||||
* @port_size: port size as reported by ETMCR bit 4-6 and 21.
|
||||
* @arch: ETM/PTM version number.
|
||||
* @use_cpu14: true if management registers need to be accessed via CP14.
|
||||
* @mode: this tracer's mode, i.e sysFS, Perf or disabled.
|
||||
* @sticky_enable: true if ETM base configuration has been done.
|
||||
* @boot_enable:true if we should start tracing at boot time.
|
||||
* @os_unlock: true if access to management registers is allowed.
|
||||
* @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR.
|
||||
* @nr_cntr: Number of counters as found in ETMCCR bit 13-15.
|
||||
* @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19.
|
||||
* @nr_ext_out: Number of external output as found in ETMCCR bit 20-22.
|
||||
* @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25.
|
||||
* @etmccr: value of register ETMCCR.
|
||||
* @etmccer: value of register ETMCCER.
|
||||
* @traceid: value of the current ID for this component.
|
||||
* @config: structure holding configuration parameters.
|
||||
*/
|
||||
struct etm_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct clk *atclk;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
int cpu;
|
||||
int port_size;
|
||||
u8 arch;
|
||||
bool use_cp14;
|
||||
local_t mode;
|
||||
bool sticky_enable;
|
||||
bool boot_enable;
|
||||
bool os_unlock;
|
||||
u8 nr_addr_cmp;
|
||||
u8 nr_cntr;
|
||||
u8 nr_ext_inp;
|
||||
u8 nr_ext_out;
|
||||
u8 nr_ctxid_cmp;
|
||||
u32 etmccr;
|
||||
u32 etmccer;
|
||||
u32 traceid;
|
||||
struct etm_config config;
|
||||
};
|
||||
|
||||
enum etm_addr_type {
|
||||
ETM_ADDR_TYPE_NONE,
|
||||
ETM_ADDR_TYPE_SINGLE,
|
||||
|
|
@ -251,4 +266,39 @@ enum etm_addr_type {
|
|||
ETM_ADDR_TYPE_START,
|
||||
ETM_ADDR_TYPE_STOP,
|
||||
};
|
||||
|
||||
static inline void etm_writel(struct etm_drvdata *drvdata,
|
||||
u32 val, u32 off)
|
||||
{
|
||||
if (drvdata->use_cp14) {
|
||||
if (etm_writel_cp14(off, val)) {
|
||||
dev_err(drvdata->dev,
|
||||
"invalid CP14 access to ETM reg: %#x", off);
|
||||
}
|
||||
} else {
|
||||
writel_relaxed(val, drvdata->base + off);
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned int etm_readl(struct etm_drvdata *drvdata, u32 off)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (drvdata->use_cp14) {
|
||||
if (etm_readl_cp14(off, &val)) {
|
||||
dev_err(drvdata->dev,
|
||||
"invalid CP14 access to ETM reg: %#x", off);
|
||||
}
|
||||
} else {
|
||||
val = readl_relaxed(drvdata->base + off);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
extern const struct attribute_group *coresight_etm_groups[];
|
||||
int etm_get_trace_id(struct etm_drvdata *drvdata);
|
||||
void etm_set_default(struct etm_config *config);
|
||||
void etm_config_trace_mode(struct etm_config *config);
|
||||
struct etm_config *get_etm_config(struct etm_drvdata *drvdata);
|
||||
#endif
|
||||
|
|
|
|||
1265
drivers/hwtracing/coresight/coresight-etm3x-sysfs.c
Normal file
1265
drivers/hwtracing/coresight/coresight-etm3x-sysfs.c
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2126
drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
Normal file
2126
drivers/hwtracing/coresight/coresight-etm4x-sysfs.c
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -13,6 +13,7 @@
|
|||
#ifndef _CORESIGHT_CORESIGHT_ETM_H
|
||||
#define _CORESIGHT_CORESIGHT_ETM_H
|
||||
|
||||
#include <asm/local.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include "coresight-priv.h"
|
||||
|
||||
|
|
@ -175,71 +176,38 @@
|
|||
#define ETM_MODE_TRACE_RESET BIT(25)
|
||||
#define ETM_MODE_TRACE_ERR BIT(26)
|
||||
#define ETM_MODE_VIEWINST_STARTSTOP BIT(27)
|
||||
#define ETMv4_MODE_ALL 0xFFFFFFF
|
||||
#define ETMv4_MODE_ALL (GENMASK(27, 0) | \
|
||||
ETM_MODE_EXCL_KERN | \
|
||||
ETM_MODE_EXCL_USER)
|
||||
|
||||
#define TRCSTATR_IDLE_BIT 0
|
||||
#define ETM_DEFAULT_ADDR_COMP 0
|
||||
|
||||
/* secure state access levels */
|
||||
#define ETM_EXLEVEL_S_APP BIT(8)
|
||||
#define ETM_EXLEVEL_S_OS BIT(9)
|
||||
#define ETM_EXLEVEL_S_NA BIT(10)
|
||||
#define ETM_EXLEVEL_S_HYP BIT(11)
|
||||
/* non-secure state access levels */
|
||||
#define ETM_EXLEVEL_NS_APP BIT(12)
|
||||
#define ETM_EXLEVEL_NS_OS BIT(13)
|
||||
#define ETM_EXLEVEL_NS_HYP BIT(14)
|
||||
#define ETM_EXLEVEL_NS_NA BIT(15)
|
||||
|
||||
/**
|
||||
* struct etm4_drvdata - specifics associated to an ETM component
|
||||
* @base: Memory mapped base address for this component.
|
||||
* @dev: The device entity associated to this component.
|
||||
* @csdev: Component vitals needed by the framework.
|
||||
* @spinlock: Only one at a time pls.
|
||||
* @cpu: The cpu this component is affined to.
|
||||
* @arch: ETM version number.
|
||||
* @enable: Is this ETM currently tracing.
|
||||
* @sticky_enable: true if ETM base configuration has been done.
|
||||
* @boot_enable:True if we should start tracing at boot time.
|
||||
* @os_unlock: True if access to management registers is allowed.
|
||||
* @nr_pe: The number of processing entity available for tracing.
|
||||
* @nr_pe_cmp: The number of processing entity comparator inputs that are
|
||||
* available for tracing.
|
||||
* @nr_addr_cmp:Number of pairs of address comparators available
|
||||
* as found in ETMIDR4 0-3.
|
||||
* @nr_cntr: Number of counters as found in ETMIDR5 bit 28-30.
|
||||
* @nr_ext_inp: Number of external input.
|
||||
* @numcidc: Number of contextID comparators.
|
||||
* @numvmidc: Number of VMID comparators.
|
||||
* @nrseqstate: The number of sequencer states that are implemented.
|
||||
* @nr_event: Indicates how many events the trace unit support.
|
||||
* @nr_resource:The number of resource selection pairs available for tracing.
|
||||
* @nr_ss_cmp: Number of single-shot comparator controls that are available.
|
||||
* struct etmv4_config - configuration information related to an ETMv4
|
||||
* @mode: Controls various modes supported by this ETM.
|
||||
* @trcid: value of the current ID for this component.
|
||||
* @trcid_size: Indicates the trace ID width.
|
||||
* @instrp0: Tracing of load and store instructions
|
||||
* as P0 elements is supported.
|
||||
* @trccond: If the trace unit supports conditional
|
||||
* instruction tracing.
|
||||
* @retstack: Indicates if the implementation supports a return stack.
|
||||
* @trc_error: Whether a trace unit can trace a system
|
||||
* error exception.
|
||||
* @atbtrig: If the implementation can support ATB triggers
|
||||
* @lpoverride: If the implementation can support low-power state over.
|
||||
* @pe_sel: Controls which PE to trace.
|
||||
* @cfg: Controls the tracing options.
|
||||
* @eventctrl0: Controls the tracing of arbitrary events.
|
||||
* @eventctrl1: Controls the behavior of the events that @event_ctrl0 selects.
|
||||
* @stallctl: If functionality that prevents trace unit buffer overflows
|
||||
* is available.
|
||||
* @sysstall: Does the system support stall control of the PE?
|
||||
* @nooverflow: Indicate if overflow prevention is supported.
|
||||
* @stall_ctrl: Enables trace unit functionality that prevents trace
|
||||
* unit buffer overflows.
|
||||
* @ts_size: Global timestamp size field.
|
||||
* @ts_ctrl: Controls the insertion of global timestamps in the
|
||||
* trace streams.
|
||||
* @syncpr: Indicates if an implementation has a fixed
|
||||
* synchronization period.
|
||||
* @syncfreq: Controls how often trace synchronization requests occur.
|
||||
* @trccci: Indicates if the trace unit supports cycle counting
|
||||
* for instruction.
|
||||
* @ccsize: Indicates the size of the cycle counter in bits.
|
||||
* @ccitmin: minimum value that can be programmed in
|
||||
* the TRCCCCTLR register.
|
||||
* @ccctlr: Sets the threshold value for cycle counting.
|
||||
* @trcbb: Indicates if the trace unit supports branch broadcast tracing.
|
||||
* @q_support: Q element support characteristics.
|
||||
* @vinst_ctrl: Controls instruction trace filtering.
|
||||
* @viiectlr: Set or read, the address range comparators.
|
||||
* @vissctlr: Set, or read, the single address comparators that control the
|
||||
|
|
@ -264,73 +232,28 @@
|
|||
* @addr_acc: Address comparator access type.
|
||||
* @addr_type: Current status of the comparator register.
|
||||
* @ctxid_idx: Context ID index selector.
|
||||
* @ctxid_size: Size of the context ID field to consider.
|
||||
* @ctxid_pid: Value of the context ID comparator.
|
||||
* @ctxid_vpid: Virtual PID seen by users if PID namespace is enabled, otherwise
|
||||
* the same value of ctxid_pid.
|
||||
* @ctxid_mask0:Context ID comparator mask for comparator 0-3.
|
||||
* @ctxid_mask1:Context ID comparator mask for comparator 4-7.
|
||||
* @vmid_idx: VM ID index selector.
|
||||
* @vmid_size: Size of the VM ID comparator to consider.
|
||||
* @vmid_val: Value of the VM ID comparator.
|
||||
* @vmid_mask0: VM ID comparator mask for comparator 0-3.
|
||||
* @vmid_mask1: VM ID comparator mask for comparator 4-7.
|
||||
* @s_ex_level: In secure state, indicates whether instruction tracing is
|
||||
* supported for the corresponding Exception level.
|
||||
* @ns_ex_level:In non-secure state, indicates whether instruction tracing is
|
||||
* supported for the corresponding Exception level.
|
||||
* @ext_inp: External input selection.
|
||||
*/
|
||||
struct etmv4_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
int cpu;
|
||||
u8 arch;
|
||||
bool enable;
|
||||
bool sticky_enable;
|
||||
bool boot_enable;
|
||||
bool os_unlock;
|
||||
u8 nr_pe;
|
||||
u8 nr_pe_cmp;
|
||||
u8 nr_addr_cmp;
|
||||
u8 nr_cntr;
|
||||
u8 nr_ext_inp;
|
||||
u8 numcidc;
|
||||
u8 numvmidc;
|
||||
u8 nrseqstate;
|
||||
u8 nr_event;
|
||||
u8 nr_resource;
|
||||
u8 nr_ss_cmp;
|
||||
struct etmv4_config {
|
||||
u32 mode;
|
||||
u8 trcid;
|
||||
u8 trcid_size;
|
||||
bool instrp0;
|
||||
bool trccond;
|
||||
bool retstack;
|
||||
bool trc_error;
|
||||
bool atbtrig;
|
||||
bool lpoverride;
|
||||
u32 pe_sel;
|
||||
u32 cfg;
|
||||
u32 eventctrl0;
|
||||
u32 eventctrl1;
|
||||
bool stallctl;
|
||||
bool sysstall;
|
||||
bool nooverflow;
|
||||
u32 stall_ctrl;
|
||||
u8 ts_size;
|
||||
u32 ts_ctrl;
|
||||
bool syncpr;
|
||||
u32 syncfreq;
|
||||
bool trccci;
|
||||
u8 ccsize;
|
||||
u8 ccitmin;
|
||||
u32 ccctlr;
|
||||
bool trcbb;
|
||||
u32 bb_ctrl;
|
||||
bool q_support;
|
||||
u32 vinst_ctrl;
|
||||
u32 viiectlr;
|
||||
u32 vissctlr;
|
||||
|
|
@ -353,19 +276,119 @@ struct etmv4_drvdata {
|
|||
u64 addr_acc[ETM_MAX_SINGLE_ADDR_CMP];
|
||||
u8 addr_type[ETM_MAX_SINGLE_ADDR_CMP];
|
||||
u8 ctxid_idx;
|
||||
u8 ctxid_size;
|
||||
u64 ctxid_pid[ETMv4_MAX_CTXID_CMP];
|
||||
u64 ctxid_vpid[ETMv4_MAX_CTXID_CMP];
|
||||
u32 ctxid_mask0;
|
||||
u32 ctxid_mask1;
|
||||
u8 vmid_idx;
|
||||
u8 vmid_size;
|
||||
u64 vmid_val[ETM_MAX_VMID_CMP];
|
||||
u32 vmid_mask0;
|
||||
u32 vmid_mask1;
|
||||
u32 ext_inp;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct etm4_drvdata - specifics associated to an ETM component
|
||||
* @base: Memory mapped base address for this component.
|
||||
* @dev: The device entity associated to this component.
|
||||
* @csdev: Component vitals needed by the framework.
|
||||
* @spinlock: Only one at a time pls.
|
||||
* @mode: This tracer's mode, i.e sysFS, Perf or disabled.
|
||||
* @cpu: The cpu this component is affined to.
|
||||
* @arch: ETM version number.
|
||||
* @nr_pe: The number of processing entity available for tracing.
|
||||
* @nr_pe_cmp: The number of processing entity comparator inputs that are
|
||||
* available for tracing.
|
||||
* @nr_addr_cmp:Number of pairs of address comparators available
|
||||
* as found in ETMIDR4 0-3.
|
||||
* @nr_cntr: Number of counters as found in ETMIDR5 bit 28-30.
|
||||
* @nr_ext_inp: Number of external input.
|
||||
* @numcidc: Number of contextID comparators.
|
||||
* @numvmidc: Number of VMID comparators.
|
||||
* @nrseqstate: The number of sequencer states that are implemented.
|
||||
* @nr_event: Indicates how many events the trace unit support.
|
||||
* @nr_resource:The number of resource selection pairs available for tracing.
|
||||
* @nr_ss_cmp: Number of single-shot comparator controls that are available.
|
||||
* @trcid: value of the current ID for this component.
|
||||
* @trcid_size: Indicates the trace ID width.
|
||||
* @ts_size: Global timestamp size field.
|
||||
* @ctxid_size: Size of the context ID field to consider.
|
||||
* @vmid_size: Size of the VM ID comparator to consider.
|
||||
* @ccsize: Indicates the size of the cycle counter in bits.
|
||||
* @ccitmin: minimum value that can be programmed in
|
||||
* @s_ex_level: In secure state, indicates whether instruction tracing is
|
||||
* supported for the corresponding Exception level.
|
||||
* @ns_ex_level:In non-secure state, indicates whether instruction tracing is
|
||||
* supported for the corresponding Exception level.
|
||||
* @sticky_enable: true if ETM base configuration has been done.
|
||||
* @boot_enable:True if we should start tracing at boot time.
|
||||
* @os_unlock: True if access to management registers is allowed.
|
||||
* @instrp0: Tracing of load and store instructions
|
||||
* as P0 elements is supported.
|
||||
* @trcbb: Indicates if the trace unit supports branch broadcast tracing.
|
||||
* @trccond: If the trace unit supports conditional
|
||||
* instruction tracing.
|
||||
* @retstack: Indicates if the implementation supports a return stack.
|
||||
* @trccci: Indicates if the trace unit supports cycle counting
|
||||
* for instruction.
|
||||
* @q_support: Q element support characteristics.
|
||||
* @trc_error: Whether a trace unit can trace a system
|
||||
* error exception.
|
||||
* @syncpr: Indicates if an implementation has a fixed
|
||||
* synchronization period.
|
||||
* @stall_ctrl: Enables trace unit functionality that prevents trace
|
||||
* unit buffer overflows.
|
||||
* @sysstall: Does the system support stall control of the PE?
|
||||
* @nooverflow: Indicate if overflow prevention is supported.
|
||||
* @atbtrig: If the implementation can support ATB triggers
|
||||
* @lpoverride: If the implementation can support low-power state over.
|
||||
* @config: structure holding configuration parameters.
|
||||
*/
|
||||
struct etmv4_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
local_t mode;
|
||||
int cpu;
|
||||
u8 arch;
|
||||
u8 nr_pe;
|
||||
u8 nr_pe_cmp;
|
||||
u8 nr_addr_cmp;
|
||||
u8 nr_cntr;
|
||||
u8 nr_ext_inp;
|
||||
u8 numcidc;
|
||||
u8 numvmidc;
|
||||
u8 nrseqstate;
|
||||
u8 nr_event;
|
||||
u8 nr_resource;
|
||||
u8 nr_ss_cmp;
|
||||
u8 trcid;
|
||||
u8 trcid_size;
|
||||
u8 ts_size;
|
||||
u8 ctxid_size;
|
||||
u8 vmid_size;
|
||||
u8 ccsize;
|
||||
u8 ccitmin;
|
||||
u8 s_ex_level;
|
||||
u8 ns_ex_level;
|
||||
u32 ext_inp;
|
||||
u8 q_support;
|
||||
bool sticky_enable;
|
||||
bool boot_enable;
|
||||
bool os_unlock;
|
||||
bool instrp0;
|
||||
bool trcbb;
|
||||
bool trccond;
|
||||
bool retstack;
|
||||
bool trccci;
|
||||
bool trc_error;
|
||||
bool syncpr;
|
||||
bool stallctl;
|
||||
bool sysstall;
|
||||
bool nooverflow;
|
||||
bool atbtrig;
|
||||
bool lpoverride;
|
||||
struct etmv4_config config;
|
||||
};
|
||||
|
||||
/* Address comparator access types */
|
||||
|
|
@ -391,4 +414,7 @@ enum etm_addr_type {
|
|||
ETM_ADDR_TYPE_START,
|
||||
ETM_ADDR_TYPE_STOP,
|
||||
};
|
||||
|
||||
extern const struct attribute_group *coresight_etmv4_groups[];
|
||||
void etm4_config_trace_mode(struct etmv4_config *config);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Funnel driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
|
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
|
@ -69,7 +70,6 @@ static int funnel_enable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
funnel_enable_hw(drvdata, inport);
|
||||
|
||||
dev_info(drvdata->dev, "FUNNEL inport %d enabled\n", inport);
|
||||
|
|
@ -95,7 +95,6 @@ static void funnel_disable(struct coresight_device *csdev, int inport,
|
|||
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
funnel_disable_hw(drvdata, inport);
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
dev_info(drvdata->dev, "FUNNEL inport %d disabled\n", inport);
|
||||
}
|
||||
|
|
@ -222,15 +221,6 @@ static int funnel_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
if (IS_ERR(drvdata->csdev))
|
||||
return PTR_ERR(drvdata->csdev);
|
||||
|
||||
dev_info(dev, "FUNNEL initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int funnel_remove(struct amba_device *adev)
|
||||
{
|
||||
struct funnel_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -273,13 +263,9 @@ static struct amba_driver funnel_driver = {
|
|||
.name = "coresight-funnel",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &funnel_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = funnel_probe,
|
||||
.remove = funnel_remove,
|
||||
.id_table = funnel_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(funnel_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Funnel driver");
|
||||
builtin_amba_driver(funnel_driver);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,45 @@
|
|||
#define TIMEOUT_US 100
|
||||
#define BMVAL(val, lsb, msb) ((val & GENMASK(msb, lsb)) >> lsb)
|
||||
|
||||
#define ETM_MODE_EXCL_KERN BIT(30)
|
||||
#define ETM_MODE_EXCL_USER BIT(31)
|
||||
|
||||
#define coresight_simple_func(type, name, offset) \
|
||||
static ssize_t name##_show(struct device *_dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
type *drvdata = dev_get_drvdata(_dev->parent); \
|
||||
return scnprintf(buf, PAGE_SIZE, "0x%x\n", \
|
||||
readl_relaxed(drvdata->base + offset)); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(name)
|
||||
|
||||
enum cs_mode {
|
||||
CS_MODE_DISABLED,
|
||||
CS_MODE_SYSFS,
|
||||
CS_MODE_PERF,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct cs_buffer - keep track of a recording session' specifics
|
||||
* @cur: index of the current buffer
|
||||
* @nr_pages: max number of pages granted to us
|
||||
* @offset: offset within the current buffer
|
||||
* @data_size: how much we collected in this run
|
||||
* @lost: other than zero if we had a HW buffer wrap around
|
||||
* @snapshot: is this run in snapshot mode
|
||||
* @data_pages: a handle the ring buffer
|
||||
*/
|
||||
struct cs_buffers {
|
||||
unsigned int cur;
|
||||
unsigned int nr_pages;
|
||||
unsigned long offset;
|
||||
local_t data_size;
|
||||
local_t lost;
|
||||
bool snapshot;
|
||||
void **data_pages;
|
||||
};
|
||||
|
||||
static inline void CS_LOCK(void __iomem *addr)
|
||||
{
|
||||
do {
|
||||
|
|
@ -52,6 +91,12 @@ static inline void CS_UNLOCK(void __iomem *addr)
|
|||
} while (0);
|
||||
}
|
||||
|
||||
void coresight_disable_path(struct list_head *path);
|
||||
int coresight_enable_path(struct list_head *path, u32 mode);
|
||||
struct coresight_device *coresight_get_sink(struct list_head *path);
|
||||
struct list_head *coresight_build_path(struct coresight_device *csdev);
|
||||
void coresight_release_path(struct list_head *path);
|
||||
|
||||
#ifdef CONFIG_CORESIGHT_SOURCE_ETM3X
|
||||
extern int etm_readl_cp14(u32 off, unsigned int *val);
|
||||
extern int etm_writel_cp14(u32 off, u32 val);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
|
|
@ -48,8 +47,6 @@ static int replicator_enable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_state *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/*
|
||||
|
|
@ -86,8 +83,6 @@ static void replicator_disable(struct coresight_device *csdev, int inport,
|
|||
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
dev_info(drvdata->dev, "REPLICATOR disabled\n");
|
||||
}
|
||||
|
||||
|
|
@ -156,15 +151,6 @@ static int replicator_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int replicator_remove(struct amba_device *adev)
|
||||
{
|
||||
struct replicator_state *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
pm_runtime_disable(&adev->dev);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int replicator_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
|
@ -206,10 +192,9 @@ static struct amba_driver replicator_driver = {
|
|||
.drv = {
|
||||
.name = "coresight-replicator-qcom",
|
||||
.pm = &replicator_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = replicator_probe,
|
||||
.remove = replicator_remove,
|
||||
.id_table = replicator_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(replicator_driver);
|
||||
builtin_amba_driver(replicator_driver);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Replicator driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
|
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
|
|
@ -41,7 +42,6 @@ static int replicator_enable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
dev_info(drvdata->dev, "REPLICATOR enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -51,7 +51,6 @@ static void replicator_disable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
dev_info(drvdata->dev, "REPLICATOR disabled\n");
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +114,6 @@ static int replicator_probe(struct platform_device *pdev)
|
|||
|
||||
pm_runtime_put(&pdev->dev);
|
||||
|
||||
dev_info(dev, "REPLICATOR initialized\n");
|
||||
return 0;
|
||||
|
||||
out_disable_pm:
|
||||
|
|
@ -127,20 +125,6 @@ static int replicator_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int replicator_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct replicator_drvdata *drvdata = platform_get_drvdata(pdev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
if (!IS_ERR(drvdata->atclk))
|
||||
clk_disable_unprepare(drvdata->atclk);
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int replicator_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
|
@ -175,15 +159,11 @@ static const struct of_device_id replicator_match[] = {
|
|||
|
||||
static struct platform_driver replicator_driver = {
|
||||
.probe = replicator_probe,
|
||||
.remove = replicator_remove,
|
||||
.driver = {
|
||||
.name = "coresight-replicator",
|
||||
.of_match_table = replicator_match,
|
||||
.pm = &replicator_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
|
||||
builtin_platform_driver(replicator_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Replicator driver");
|
||||
|
|
|
|||
920
drivers/hwtracing/coresight/coresight-stm.c
Normal file
920
drivers/hwtracing/coresight/coresight-stm.c
Normal file
|
|
@ -0,0 +1,920 @@
|
|||
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight System Trace Macrocell driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Initial implementation by Pratik Patel
|
||||
* (C) 2014-2015 Pratik Patel <pratikp@codeaurora.org>
|
||||
*
|
||||
* Serious refactoring, code cleanup and upgrading to the Coresight upstream
|
||||
* framework by Mathieu Poirier
|
||||
* (C) 2015-2016 Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* Guaranteed timing and support for various packet type coming from the
|
||||
* generic STM API by Chunyan Zhang
|
||||
* (C) 2015-2016 Chunyan Zhang <zhang.chunyan@linaro.org>
|
||||
*/
|
||||
#include <asm/local.h>
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/coresight-stm.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/stm.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
|
||||
#define STMDMASTARTR 0xc04
|
||||
#define STMDMASTOPR 0xc08
|
||||
#define STMDMASTATR 0xc0c
|
||||
#define STMDMACTLR 0xc10
|
||||
#define STMDMAIDR 0xcfc
|
||||
#define STMHEER 0xd00
|
||||
#define STMHETER 0xd20
|
||||
#define STMHEBSR 0xd60
|
||||
#define STMHEMCR 0xd64
|
||||
#define STMHEMASTR 0xdf4
|
||||
#define STMHEFEAT1R 0xdf8
|
||||
#define STMHEIDR 0xdfc
|
||||
#define STMSPER 0xe00
|
||||
#define STMSPTER 0xe20
|
||||
#define STMPRIVMASKR 0xe40
|
||||
#define STMSPSCR 0xe60
|
||||
#define STMSPMSCR 0xe64
|
||||
#define STMSPOVERRIDER 0xe68
|
||||
#define STMSPMOVERRIDER 0xe6c
|
||||
#define STMSPTRIGCSR 0xe70
|
||||
#define STMTCSR 0xe80
|
||||
#define STMTSSTIMR 0xe84
|
||||
#define STMTSFREQR 0xe8c
|
||||
#define STMSYNCR 0xe90
|
||||
#define STMAUXCR 0xe94
|
||||
#define STMSPFEAT1R 0xea0
|
||||
#define STMSPFEAT2R 0xea4
|
||||
#define STMSPFEAT3R 0xea8
|
||||
#define STMITTRIGGER 0xee8
|
||||
#define STMITATBDATA0 0xeec
|
||||
#define STMITATBCTR2 0xef0
|
||||
#define STMITATBID 0xef4
|
||||
#define STMITATBCTR0 0xef8
|
||||
|
||||
#define STM_32_CHANNEL 32
|
||||
#define BYTES_PER_CHANNEL 256
|
||||
#define STM_TRACE_BUF_SIZE 4096
|
||||
#define STM_SW_MASTER_END 127
|
||||
|
||||
/* Register bit definition */
|
||||
#define STMTCSR_BUSY_BIT 23
|
||||
/* Reserve the first 10 channels for kernel usage */
|
||||
#define STM_CHANNEL_OFFSET 0
|
||||
|
||||
enum stm_pkt_type {
|
||||
STM_PKT_TYPE_DATA = 0x98,
|
||||
STM_PKT_TYPE_FLAG = 0xE8,
|
||||
STM_PKT_TYPE_TRIG = 0xF8,
|
||||
};
|
||||
|
||||
#define stm_channel_addr(drvdata, ch) (drvdata->chs.base + \
|
||||
(ch * BYTES_PER_CHANNEL))
|
||||
#define stm_channel_off(type, opts) (type & ~opts)
|
||||
|
||||
static int boot_nr_channel;
|
||||
|
||||
/*
|
||||
* Not really modular but using module_param is the easiest way to
|
||||
* remain consistent with existing use cases for now.
|
||||
*/
|
||||
module_param_named(
|
||||
boot_nr_channel, boot_nr_channel, int, S_IRUGO
|
||||
);
|
||||
|
||||
/**
|
||||
* struct channel_space - central management entity for extended ports
|
||||
* @base: memory mapped base address where channels start.
|
||||
* @guaraneed: is the channel delivery guaranteed.
|
||||
*/
|
||||
struct channel_space {
|
||||
void __iomem *base;
|
||||
unsigned long *guaranteed;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct stm_drvdata - specifics associated to an STM component
|
||||
* @base: memory mapped base address for this component.
|
||||
* @dev: the device entity associated to this component.
|
||||
* @atclk: optional clock for the core parts of the STM.
|
||||
* @csdev: component vitals needed by the framework.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @chs: the channels accociated to this STM.
|
||||
* @stm: structure associated to the generic STM interface.
|
||||
* @mode: this tracer's mode, i.e sysFS, or disabled.
|
||||
* @traceid: value of the current ID for this component.
|
||||
* @write_bytes: Maximus bytes this STM can write at a time.
|
||||
* @stmsper: settings for register STMSPER.
|
||||
* @stmspscr: settings for register STMSPSCR.
|
||||
* @numsp: the total number of stimulus port support by this STM.
|
||||
* @stmheer: settings for register STMHEER.
|
||||
* @stmheter: settings for register STMHETER.
|
||||
* @stmhebsr: settings for register STMHEBSR.
|
||||
*/
|
||||
struct stm_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct clk *atclk;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
struct channel_space chs;
|
||||
struct stm_data stm;
|
||||
local_t mode;
|
||||
u8 traceid;
|
||||
u32 write_bytes;
|
||||
u32 stmsper;
|
||||
u32 stmspscr;
|
||||
u32 numsp;
|
||||
u32 stmheer;
|
||||
u32 stmheter;
|
||||
u32 stmhebsr;
|
||||
};
|
||||
|
||||
static void stm_hwevent_enable_hw(struct stm_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
writel_relaxed(drvdata->stmhebsr, drvdata->base + STMHEBSR);
|
||||
writel_relaxed(drvdata->stmheter, drvdata->base + STMHETER);
|
||||
writel_relaxed(drvdata->stmheer, drvdata->base + STMHEER);
|
||||
writel_relaxed(0x01 | /* Enable HW event tracing */
|
||||
0x04, /* Error detection on event tracing */
|
||||
drvdata->base + STMHEMCR);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void stm_port_enable_hw(struct stm_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
/* ATB trigger enable on direct writes to TRIG locations */
|
||||
writel_relaxed(0x10,
|
||||
drvdata->base + STMSPTRIGCSR);
|
||||
writel_relaxed(drvdata->stmspscr, drvdata->base + STMSPSCR);
|
||||
writel_relaxed(drvdata->stmsper, drvdata->base + STMSPER);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void stm_enable_hw(struct stm_drvdata *drvdata)
|
||||
{
|
||||
if (drvdata->stmheer)
|
||||
stm_hwevent_enable_hw(drvdata);
|
||||
|
||||
stm_port_enable_hw(drvdata);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* 4096 byte between synchronisation packets */
|
||||
writel_relaxed(0xFFF, drvdata->base + STMSYNCR);
|
||||
writel_relaxed((drvdata->traceid << 16 | /* trace id */
|
||||
0x02 | /* timestamp enable */
|
||||
0x01), /* global STM enable */
|
||||
drvdata->base + STMTCSR);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int stm_enable(struct coresight_device *csdev,
|
||||
struct perf_event_attr *attr, u32 mode)
|
||||
{
|
||||
u32 val;
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (mode != CS_MODE_SYSFS)
|
||||
return -EINVAL;
|
||||
|
||||
val = local_cmpxchg(&drvdata->mode, CS_MODE_DISABLED, mode);
|
||||
|
||||
/* Someone is already using the tracer */
|
||||
if (val)
|
||||
return -EBUSY;
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
stm_enable_hw(drvdata);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_info(drvdata->dev, "STM tracing enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm_hwevent_disable_hw(struct stm_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
writel_relaxed(0x0, drvdata->base + STMHEMCR);
|
||||
writel_relaxed(0x0, drvdata->base + STMHEER);
|
||||
writel_relaxed(0x0, drvdata->base + STMHETER);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void stm_port_disable_hw(struct stm_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
writel_relaxed(0x0, drvdata->base + STMSPER);
|
||||
writel_relaxed(0x0, drvdata->base + STMSPTRIGCSR);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void stm_disable_hw(struct stm_drvdata *drvdata)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
val = readl_relaxed(drvdata->base + STMTCSR);
|
||||
val &= ~0x1; /* clear global STM enable [0] */
|
||||
writel_relaxed(val, drvdata->base + STMTCSR);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
stm_port_disable_hw(drvdata);
|
||||
if (drvdata->stmheer)
|
||||
stm_hwevent_disable_hw(drvdata);
|
||||
}
|
||||
|
||||
static void stm_disable(struct coresight_device *csdev)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
/*
|
||||
* For as long as the tracer isn't disabled another entity can't
|
||||
* change its status. As such we can read the status here without
|
||||
* fearing it will change under us.
|
||||
*/
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS) {
|
||||
spin_lock(&drvdata->spinlock);
|
||||
stm_disable_hw(drvdata);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
/* Wait until the engine has completely stopped */
|
||||
coresight_timeout(drvdata, STMTCSR, STMTCSR_BUSY_BIT, 0);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
local_set(&drvdata->mode, CS_MODE_DISABLED);
|
||||
dev_info(drvdata->dev, "STM tracing disabled\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int stm_trace_id(struct coresight_device *csdev)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
return drvdata->traceid;
|
||||
}
|
||||
|
||||
static const struct coresight_ops_source stm_source_ops = {
|
||||
.trace_id = stm_trace_id,
|
||||
.enable = stm_enable,
|
||||
.disable = stm_disable,
|
||||
};
|
||||
|
||||
static const struct coresight_ops stm_cs_ops = {
|
||||
.source_ops = &stm_source_ops,
|
||||
};
|
||||
|
||||
static inline bool stm_addr_unaligned(const void *addr, u8 write_bytes)
|
||||
{
|
||||
return ((unsigned long)addr & (write_bytes - 1));
|
||||
}
|
||||
|
||||
static void stm_send(void *addr, const void *data, u32 size, u8 write_bytes)
|
||||
{
|
||||
u8 paload[8];
|
||||
|
||||
if (stm_addr_unaligned(data, write_bytes)) {
|
||||
memcpy(paload, data, size);
|
||||
data = paload;
|
||||
}
|
||||
|
||||
/* now we are 64bit/32bit aligned */
|
||||
switch (size) {
|
||||
#ifdef CONFIG_64BIT
|
||||
case 8:
|
||||
writeq_relaxed(*(u64 *)data, addr);
|
||||
break;
|
||||
#endif
|
||||
case 4:
|
||||
writel_relaxed(*(u32 *)data, addr);
|
||||
break;
|
||||
case 2:
|
||||
writew_relaxed(*(u16 *)data, addr);
|
||||
break;
|
||||
case 1:
|
||||
writeb_relaxed(*(u8 *)data, addr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int stm_generic_link(struct stm_data *stm_data,
|
||||
unsigned int master, unsigned int channel)
|
||||
{
|
||||
struct stm_drvdata *drvdata = container_of(stm_data,
|
||||
struct stm_drvdata, stm);
|
||||
if (!drvdata || !drvdata->csdev)
|
||||
return -EINVAL;
|
||||
|
||||
return coresight_enable(drvdata->csdev);
|
||||
}
|
||||
|
||||
static void stm_generic_unlink(struct stm_data *stm_data,
|
||||
unsigned int master, unsigned int channel)
|
||||
{
|
||||
struct stm_drvdata *drvdata = container_of(stm_data,
|
||||
struct stm_drvdata, stm);
|
||||
if (!drvdata || !drvdata->csdev)
|
||||
return;
|
||||
|
||||
stm_disable(drvdata->csdev);
|
||||
}
|
||||
|
||||
static long stm_generic_set_options(struct stm_data *stm_data,
|
||||
unsigned int master,
|
||||
unsigned int channel,
|
||||
unsigned int nr_chans,
|
||||
unsigned long options)
|
||||
{
|
||||
struct stm_drvdata *drvdata = container_of(stm_data,
|
||||
struct stm_drvdata, stm);
|
||||
if (!(drvdata && local_read(&drvdata->mode)))
|
||||
return -EINVAL;
|
||||
|
||||
if (channel >= drvdata->numsp)
|
||||
return -EINVAL;
|
||||
|
||||
switch (options) {
|
||||
case STM_OPTION_GUARANTEED:
|
||||
set_bit(channel, drvdata->chs.guaranteed);
|
||||
break;
|
||||
|
||||
case STM_OPTION_INVARIANT:
|
||||
clear_bit(channel, drvdata->chs.guaranteed);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t stm_generic_packet(struct stm_data *stm_data,
|
||||
unsigned int master,
|
||||
unsigned int channel,
|
||||
unsigned int packet,
|
||||
unsigned int flags,
|
||||
unsigned int size,
|
||||
const unsigned char *payload)
|
||||
{
|
||||
unsigned long ch_addr;
|
||||
struct stm_drvdata *drvdata = container_of(stm_data,
|
||||
struct stm_drvdata, stm);
|
||||
|
||||
if (!(drvdata && local_read(&drvdata->mode)))
|
||||
return 0;
|
||||
|
||||
if (channel >= drvdata->numsp)
|
||||
return 0;
|
||||
|
||||
ch_addr = (unsigned long)stm_channel_addr(drvdata, channel);
|
||||
|
||||
flags = (flags == STP_PACKET_TIMESTAMPED) ? STM_FLAG_TIMESTAMPED : 0;
|
||||
flags |= test_bit(channel, drvdata->chs.guaranteed) ?
|
||||
STM_FLAG_GUARANTEED : 0;
|
||||
|
||||
if (size > drvdata->write_bytes)
|
||||
size = drvdata->write_bytes;
|
||||
else
|
||||
size = rounddown_pow_of_two(size);
|
||||
|
||||
switch (packet) {
|
||||
case STP_PACKET_FLAG:
|
||||
ch_addr |= stm_channel_off(STM_PKT_TYPE_FLAG, flags);
|
||||
|
||||
/*
|
||||
* The generic STM core sets a size of '0' on flag packets.
|
||||
* As such send a flag packet of size '1' and tell the
|
||||
* core we did so.
|
||||
*/
|
||||
stm_send((void *)ch_addr, payload, 1, drvdata->write_bytes);
|
||||
size = 1;
|
||||
break;
|
||||
|
||||
case STP_PACKET_DATA:
|
||||
ch_addr |= stm_channel_off(STM_PKT_TYPE_DATA, flags);
|
||||
stm_send((void *)ch_addr, payload, size,
|
||||
drvdata->write_bytes);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t hwevent_enable_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val = drvdata->stmheer;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
|
||||
}
|
||||
|
||||
static ssize_t hwevent_enable_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val;
|
||||
int ret = 0;
|
||||
|
||||
ret = kstrtoul(buf, 16, &val);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
drvdata->stmheer = val;
|
||||
/* HW event enable and trigger go hand in hand */
|
||||
drvdata->stmheter = val;
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(hwevent_enable);
|
||||
|
||||
static ssize_t hwevent_select_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val = drvdata->stmhebsr;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
|
||||
}
|
||||
|
||||
static ssize_t hwevent_select_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val;
|
||||
int ret = 0;
|
||||
|
||||
ret = kstrtoul(buf, 16, &val);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
drvdata->stmhebsr = val;
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(hwevent_select);
|
||||
|
||||
static ssize_t port_select_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val;
|
||||
|
||||
if (!local_read(&drvdata->mode)) {
|
||||
val = drvdata->stmspscr;
|
||||
} else {
|
||||
spin_lock(&drvdata->spinlock);
|
||||
val = readl_relaxed(drvdata->base + STMSPSCR);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
}
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
|
||||
}
|
||||
|
||||
static ssize_t port_select_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val, stmsper;
|
||||
int ret = 0;
|
||||
|
||||
ret = kstrtoul(buf, 16, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
drvdata->stmspscr = val;
|
||||
|
||||
if (local_read(&drvdata->mode)) {
|
||||
CS_UNLOCK(drvdata->base);
|
||||
/* Process as per ARM's TRM recommendation */
|
||||
stmsper = readl_relaxed(drvdata->base + STMSPER);
|
||||
writel_relaxed(0x0, drvdata->base + STMSPER);
|
||||
writel_relaxed(drvdata->stmspscr, drvdata->base + STMSPSCR);
|
||||
writel_relaxed(stmsper, drvdata->base + STMSPER);
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(port_select);
|
||||
|
||||
static ssize_t port_enable_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val;
|
||||
|
||||
if (!local_read(&drvdata->mode)) {
|
||||
val = drvdata->stmsper;
|
||||
} else {
|
||||
spin_lock(&drvdata->spinlock);
|
||||
val = readl_relaxed(drvdata->base + STMSPER);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
}
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
|
||||
}
|
||||
|
||||
static ssize_t port_enable_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val;
|
||||
int ret = 0;
|
||||
|
||||
ret = kstrtoul(buf, 16, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
drvdata->stmsper = val;
|
||||
|
||||
if (local_read(&drvdata->mode)) {
|
||||
CS_UNLOCK(drvdata->base);
|
||||
writel_relaxed(drvdata->stmsper, drvdata->base + STMSPER);
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(port_enable);
|
||||
|
||||
static ssize_t traceid_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned long val;
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
val = drvdata->traceid;
|
||||
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 stm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
ret = kstrtoul(buf, 16, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* traceid field is 7bit wide on STM32 */
|
||||
drvdata->traceid = val & 0x7f;
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(traceid);
|
||||
|
||||
#define coresight_stm_simple_func(name, offset) \
|
||||
coresight_simple_func(struct stm_drvdata, name, offset)
|
||||
|
||||
coresight_stm_simple_func(tcsr, STMTCSR);
|
||||
coresight_stm_simple_func(tsfreqr, STMTSFREQR);
|
||||
coresight_stm_simple_func(syncr, STMSYNCR);
|
||||
coresight_stm_simple_func(sper, STMSPER);
|
||||
coresight_stm_simple_func(spter, STMSPTER);
|
||||
coresight_stm_simple_func(privmaskr, STMPRIVMASKR);
|
||||
coresight_stm_simple_func(spscr, STMSPSCR);
|
||||
coresight_stm_simple_func(spmscr, STMSPMSCR);
|
||||
coresight_stm_simple_func(spfeat1r, STMSPFEAT1R);
|
||||
coresight_stm_simple_func(spfeat2r, STMSPFEAT2R);
|
||||
coresight_stm_simple_func(spfeat3r, STMSPFEAT3R);
|
||||
coresight_stm_simple_func(devid, CORESIGHT_DEVID);
|
||||
|
||||
static struct attribute *coresight_stm_attrs[] = {
|
||||
&dev_attr_hwevent_enable.attr,
|
||||
&dev_attr_hwevent_select.attr,
|
||||
&dev_attr_port_enable.attr,
|
||||
&dev_attr_port_select.attr,
|
||||
&dev_attr_traceid.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute *coresight_stm_mgmt_attrs[] = {
|
||||
&dev_attr_tcsr.attr,
|
||||
&dev_attr_tsfreqr.attr,
|
||||
&dev_attr_syncr.attr,
|
||||
&dev_attr_sper.attr,
|
||||
&dev_attr_spter.attr,
|
||||
&dev_attr_privmaskr.attr,
|
||||
&dev_attr_spscr.attr,
|
||||
&dev_attr_spmscr.attr,
|
||||
&dev_attr_spfeat1r.attr,
|
||||
&dev_attr_spfeat2r.attr,
|
||||
&dev_attr_spfeat3r.attr,
|
||||
&dev_attr_devid.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group coresight_stm_group = {
|
||||
.attrs = coresight_stm_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group coresight_stm_mgmt_group = {
|
||||
.attrs = coresight_stm_mgmt_attrs,
|
||||
.name = "mgmt",
|
||||
};
|
||||
|
||||
static const struct attribute_group *coresight_stm_groups[] = {
|
||||
&coresight_stm_group,
|
||||
&coresight_stm_mgmt_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int stm_get_resource_byname(struct device_node *np,
|
||||
char *ch_base, struct resource *res)
|
||||
{
|
||||
const char *name = NULL;
|
||||
int index = 0, found = 0;
|
||||
|
||||
while (!of_property_read_string_index(np, "reg-names", index, &name)) {
|
||||
if (strcmp(ch_base, name)) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We have a match and @index is where it's at */
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return -EINVAL;
|
||||
|
||||
return of_address_to_resource(np, index, res);
|
||||
}
|
||||
|
||||
static u32 stm_fundamental_data_size(struct stm_drvdata *drvdata)
|
||||
{
|
||||
u32 stmspfeat2r;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_64BIT))
|
||||
return 4;
|
||||
|
||||
stmspfeat2r = readl_relaxed(drvdata->base + STMSPFEAT2R);
|
||||
|
||||
/*
|
||||
* bit[15:12] represents the fundamental data size
|
||||
* 0 - 32-bit data
|
||||
* 1 - 64-bit data
|
||||
*/
|
||||
return BMVAL(stmspfeat2r, 12, 15) ? 8 : 4;
|
||||
}
|
||||
|
||||
static u32 stm_num_stimulus_port(struct stm_drvdata *drvdata)
|
||||
{
|
||||
u32 numsp;
|
||||
|
||||
numsp = readl_relaxed(drvdata->base + CORESIGHT_DEVID);
|
||||
/*
|
||||
* NUMPS in STMDEVID is 17 bit long and if equal to 0x0,
|
||||
* 32 stimulus ports are supported.
|
||||
*/
|
||||
numsp &= 0x1ffff;
|
||||
if (!numsp)
|
||||
numsp = STM_32_CHANNEL;
|
||||
return numsp;
|
||||
}
|
||||
|
||||
static void stm_init_default_data(struct stm_drvdata *drvdata)
|
||||
{
|
||||
/* Don't use port selection */
|
||||
drvdata->stmspscr = 0x0;
|
||||
/*
|
||||
* Enable all channel regardless of their number. When port
|
||||
* selection isn't used (see above) STMSPER applies to all
|
||||
* 32 channel group available, hence setting all 32 bits to 1
|
||||
*/
|
||||
drvdata->stmsper = ~0x0;
|
||||
|
||||
/*
|
||||
* The trace ID value for *ETM* tracers start at CPU_ID * 2 + 0x10 and
|
||||
* anything equal to or higher than 0x70 is reserved. Since 0x00 is
|
||||
* also reserved the STM trace ID needs to be higher than 0x00 and
|
||||
* lowner than 0x10.
|
||||
*/
|
||||
drvdata->traceid = 0x1;
|
||||
|
||||
/* Set invariant transaction timing on all channels */
|
||||
bitmap_clear(drvdata->chs.guaranteed, 0, drvdata->numsp);
|
||||
}
|
||||
|
||||
static void stm_init_generic_data(struct stm_drvdata *drvdata)
|
||||
{
|
||||
drvdata->stm.name = dev_name(drvdata->dev);
|
||||
|
||||
/*
|
||||
* MasterIDs are assigned at HW design phase. As such the core is
|
||||
* using a single master for interaction with this device.
|
||||
*/
|
||||
drvdata->stm.sw_start = 1;
|
||||
drvdata->stm.sw_end = 1;
|
||||
drvdata->stm.hw_override = true;
|
||||
drvdata->stm.sw_nchannels = drvdata->numsp;
|
||||
drvdata->stm.packet = stm_generic_packet;
|
||||
drvdata->stm.link = stm_generic_link;
|
||||
drvdata->stm.unlink = stm_generic_unlink;
|
||||
drvdata->stm.set_options = stm_generic_set_options;
|
||||
}
|
||||
|
||||
static int stm_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *base;
|
||||
unsigned long *guaranteed;
|
||||
struct device *dev = &adev->dev;
|
||||
struct coresight_platform_data *pdata = NULL;
|
||||
struct stm_drvdata *drvdata;
|
||||
struct resource *res = &adev->res;
|
||||
struct resource ch_res;
|
||||
size_t res_size, bitmap_size;
|
||||
struct coresight_desc *desc;
|
||||
struct device_node *np = adev->dev.of_node;
|
||||
|
||||
if (np) {
|
||||
pdata = of_get_coresight_platform_data(dev, np);
|
||||
if (IS_ERR(pdata))
|
||||
return PTR_ERR(pdata);
|
||||
adev->dev.platform_data = pdata;
|
||||
}
|
||||
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
|
||||
drvdata->dev = &adev->dev;
|
||||
drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
|
||||
if (!IS_ERR(drvdata->atclk)) {
|
||||
ret = clk_prepare_enable(drvdata->atclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
dev_set_drvdata(dev, drvdata);
|
||||
|
||||
base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
drvdata->base = base;
|
||||
|
||||
ret = stm_get_resource_byname(np, "stm-stimulus-base", &ch_res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
base = devm_ioremap_resource(dev, &ch_res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
drvdata->chs.base = base;
|
||||
|
||||
drvdata->write_bytes = stm_fundamental_data_size(drvdata);
|
||||
|
||||
if (boot_nr_channel) {
|
||||
drvdata->numsp = boot_nr_channel;
|
||||
res_size = min((resource_size_t)(boot_nr_channel *
|
||||
BYTES_PER_CHANNEL), resource_size(res));
|
||||
} else {
|
||||
drvdata->numsp = stm_num_stimulus_port(drvdata);
|
||||
res_size = min((resource_size_t)(drvdata->numsp *
|
||||
BYTES_PER_CHANNEL), resource_size(res));
|
||||
}
|
||||
bitmap_size = BITS_TO_LONGS(drvdata->numsp) * sizeof(long);
|
||||
|
||||
guaranteed = devm_kzalloc(dev, bitmap_size, GFP_KERNEL);
|
||||
if (!guaranteed)
|
||||
return -ENOMEM;
|
||||
drvdata->chs.guaranteed = guaranteed;
|
||||
|
||||
spin_lock_init(&drvdata->spinlock);
|
||||
|
||||
stm_init_default_data(drvdata);
|
||||
stm_init_generic_data(drvdata);
|
||||
|
||||
if (stm_register_device(dev, &drvdata->stm, THIS_MODULE)) {
|
||||
dev_info(dev,
|
||||
"stm_register_device failed, probing deffered\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
|
||||
if (!desc) {
|
||||
ret = -ENOMEM;
|
||||
goto stm_unregister;
|
||||
}
|
||||
|
||||
desc->type = CORESIGHT_DEV_TYPE_SOURCE;
|
||||
desc->subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE;
|
||||
desc->ops = &stm_cs_ops;
|
||||
desc->pdata = pdata;
|
||||
desc->dev = dev;
|
||||
desc->groups = coresight_stm_groups;
|
||||
drvdata->csdev = coresight_register(desc);
|
||||
if (IS_ERR(drvdata->csdev)) {
|
||||
ret = PTR_ERR(drvdata->csdev);
|
||||
goto stm_unregister;
|
||||
}
|
||||
|
||||
pm_runtime_put(&adev->dev);
|
||||
|
||||
dev_info(dev, "%s initialized\n", (char *)id->data);
|
||||
return 0;
|
||||
|
||||
stm_unregister:
|
||||
stm_unregister_device(&drvdata->stm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int stm_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev);
|
||||
|
||||
if (drvdata && !IS_ERR(drvdata->atclk))
|
||||
clk_disable_unprepare(drvdata->atclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(dev);
|
||||
|
||||
if (drvdata && !IS_ERR(drvdata->atclk))
|
||||
clk_prepare_enable(drvdata->atclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops stm_dev_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(stm_runtime_suspend, stm_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct amba_id stm_ids[] = {
|
||||
{
|
||||
.id = 0x0003b962,
|
||||
.mask = 0x0003ffff,
|
||||
.data = "STM32",
|
||||
},
|
||||
{ 0, 0},
|
||||
};
|
||||
|
||||
static struct amba_driver stm_driver = {
|
||||
.drv = {
|
||||
.name = "coresight-stm",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &stm_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = stm_probe,
|
||||
.id_table = stm_ids,
|
||||
};
|
||||
|
||||
builtin_amba_driver(stm_driver);
|
||||
604
drivers/hwtracing/coresight/coresight-tmc-etf.c
Normal file
604
drivers/hwtracing/coresight/coresight-tmc-etf.c
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* Copyright(C) 2016 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/slab.h>
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-tmc.h"
|
||||
|
||||
void tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Wait for TMCSReady bit to be set */
|
||||
tmc_wait_for_tmcready(drvdata);
|
||||
|
||||
writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI |
|
||||
TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT |
|
||||
TMC_FFCR_TRIGON_TRIGIN,
|
||||
drvdata->base + TMC_FFCR);
|
||||
|
||||
writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
char *bufp;
|
||||
u32 read_data;
|
||||
int i;
|
||||
|
||||
bufp = drvdata->buf;
|
||||
while (1) {
|
||||
for (i = 0; i < drvdata->memwidth; i++) {
|
||||
read_data = readl_relaxed(drvdata->base + TMC_RRD);
|
||||
if (read_data == 0xFFFFFFFF)
|
||||
return;
|
||||
memcpy(bufp, &read_data, 4);
|
||||
bufp += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
/*
|
||||
* When operating in sysFS mode the content of the buffer needs to be
|
||||
* read before the TMC is disabled.
|
||||
*/
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS)
|
||||
tmc_etb_dump_hw(drvdata);
|
||||
tmc_disable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Wait for TMCSReady bit to be set */
|
||||
tmc_wait_for_tmcready(drvdata);
|
||||
|
||||
writel_relaxed(TMC_MODE_HARDWARE_FIFO, drvdata->base + TMC_MODE);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI,
|
||||
drvdata->base + TMC_FFCR);
|
||||
writel_relaxed(0x0, drvdata->base + TMC_BUFWM);
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
tmc_disable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret = 0;
|
||||
bool used = false;
|
||||
char *buf = NULL;
|
||||
long val;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
/* This shouldn't be happening */
|
||||
if (WARN_ON(mode != CS_MODE_SYSFS))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* If we don't have a buffer release the lock and allocate memory.
|
||||
* Otherwise keep the lock and move along.
|
||||
*/
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (!drvdata->buf) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/* Allocating the memory here while outside of the spinlock */
|
||||
buf = kzalloc(drvdata->size, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Let's try again */
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
}
|
||||
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = local_xchg(&drvdata->mode, mode);
|
||||
/*
|
||||
* In sysFS mode we can have multiple writers per sink. Since this
|
||||
* sink is already enabled no memory is needed and the HW need not be
|
||||
* touched.
|
||||
*/
|
||||
if (val == CS_MODE_SYSFS)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* If drvdata::buf isn't NULL, memory was allocated for a previous
|
||||
* trace run but wasn't read. If so simply zero-out the memory.
|
||||
* Otherwise use the memory allocated above.
|
||||
*
|
||||
* The memory is freed when users read the buffer using the
|
||||
* /dev/xyz.{etf|etb} interface. See tmc_read_unprepare_etf() for
|
||||
* details.
|
||||
*/
|
||||
if (drvdata->buf) {
|
||||
memset(drvdata->buf, 0, drvdata->size);
|
||||
} else {
|
||||
used = true;
|
||||
drvdata->buf = buf;
|
||||
}
|
||||
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/* Free memory outside the spinlock if need be */
|
||||
if (!used && buf)
|
||||
kfree(buf);
|
||||
|
||||
if (!ret)
|
||||
dev_info(drvdata->dev, "TMC-ETB/ETF enabled\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_enable_etf_sink_perf(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret = 0;
|
||||
long val;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
/* This shouldn't be happening */
|
||||
if (WARN_ON(mode != CS_MODE_PERF))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = local_xchg(&drvdata->mode, mode);
|
||||
/*
|
||||
* In Perf mode there can be only one writer per sink. There
|
||||
* is also no need to continue if the ETB/ETR is already operated
|
||||
* from sysFS.
|
||||
*/
|
||||
if (val != CS_MODE_DISABLED) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_enable_etf_sink(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case CS_MODE_SYSFS:
|
||||
return tmc_enable_etf_sink_sysfs(csdev, mode);
|
||||
case CS_MODE_PERF:
|
||||
return tmc_enable_etf_sink_perf(csdev, mode);
|
||||
}
|
||||
|
||||
/* We shouldn't be here */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void tmc_disable_etf_sink(struct coresight_device *csdev)
|
||||
{
|
||||
long val;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
val = local_xchg(&drvdata->mode, CS_MODE_DISABLED);
|
||||
/* Disable the TMC only if it needs to */
|
||||
if (val != CS_MODE_DISABLED)
|
||||
tmc_etb_disable_hw(drvdata);
|
||||
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETB/ETF disabled\n");
|
||||
}
|
||||
|
||||
static int tmc_enable_etf_link(struct coresight_device *csdev,
|
||||
int inport, int outport)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
tmc_etf_enable_hw(drvdata);
|
||||
local_set(&drvdata->mode, CS_MODE_SYSFS);
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETF enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tmc_disable_etf_link(struct coresight_device *csdev,
|
||||
int inport, int outport)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
tmc_etf_disable_hw(drvdata);
|
||||
local_set(&drvdata->mode, CS_MODE_DISABLED);
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC disabled\n");
|
||||
}
|
||||
|
||||
static void *tmc_alloc_etf_buffer(struct coresight_device *csdev, int cpu,
|
||||
void **pages, int nr_pages, bool overwrite)
|
||||
{
|
||||
int node;
|
||||
struct cs_buffers *buf;
|
||||
|
||||
if (cpu == -1)
|
||||
cpu = smp_processor_id();
|
||||
node = cpu_to_node(cpu);
|
||||
|
||||
/* Allocate memory structure for interaction with Perf */
|
||||
buf = kzalloc_node(sizeof(struct cs_buffers), GFP_KERNEL, node);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
buf->snapshot = overwrite;
|
||||
buf->nr_pages = nr_pages;
|
||||
buf->data_pages = pages;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void tmc_free_etf_buffer(void *config)
|
||||
{
|
||||
struct cs_buffers *buf = config;
|
||||
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static int tmc_set_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long head;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
/* wrap head around to the amount of space we have */
|
||||
head = handle->head & ((buf->nr_pages << PAGE_SHIFT) - 1);
|
||||
|
||||
/* find the page to write to */
|
||||
buf->cur = head / PAGE_SIZE;
|
||||
|
||||
/* and offset within that page */
|
||||
buf->offset = head % PAGE_SIZE;
|
||||
|
||||
local_set(&buf->data_size, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned long tmc_reset_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config, bool *lost)
|
||||
{
|
||||
long size = 0;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
if (buf) {
|
||||
/*
|
||||
* In snapshot mode ->data_size holds the new address of the
|
||||
* ring buffer's head. The size itself is the whole address
|
||||
* range since we want the latest information.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
handle->head = local_xchg(&buf->data_size,
|
||||
buf->nr_pages << PAGE_SHIFT);
|
||||
/*
|
||||
* Tell the tracer PMU how much we got in this run and if
|
||||
* something went wrong along the way. Nobody else can use
|
||||
* this cs_buffers instance until we are done. As such
|
||||
* resetting parameters here and squaring off with the ring
|
||||
* buffer API in the tracer PMU is fine.
|
||||
*/
|
||||
*lost = !!local_xchg(&buf->lost, 0);
|
||||
size = local_xchg(&buf->data_size, 0);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void tmc_update_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
int i, cur;
|
||||
u32 *buf_ptr;
|
||||
u32 read_ptr, write_ptr;
|
||||
u32 status, to_read;
|
||||
unsigned long offset;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
/* This shouldn't happen */
|
||||
if (WARN_ON_ONCE(local_read(&drvdata->mode) != CS_MODE_PERF))
|
||||
return;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
|
||||
read_ptr = readl_relaxed(drvdata->base + TMC_RRP);
|
||||
write_ptr = readl_relaxed(drvdata->base + TMC_RWP);
|
||||
|
||||
/*
|
||||
* Get a hold of the status register and see if a wrap around
|
||||
* has occurred. If so adjust things accordingly.
|
||||
*/
|
||||
status = readl_relaxed(drvdata->base + TMC_STS);
|
||||
if (status & TMC_STS_FULL) {
|
||||
local_inc(&buf->lost);
|
||||
to_read = drvdata->size;
|
||||
} else {
|
||||
to_read = CIRC_CNT(write_ptr, read_ptr, drvdata->size);
|
||||
}
|
||||
|
||||
/*
|
||||
* The TMC RAM buffer may be bigger than the space available in the
|
||||
* perf ring buffer (handle->size). If so advance the RRP so that we
|
||||
* get the latest trace data.
|
||||
*/
|
||||
if (to_read > handle->size) {
|
||||
u32 mask = 0;
|
||||
|
||||
/*
|
||||
* The value written to RRP must be byte-address aligned to
|
||||
* the width of the trace memory databus _and_ to a frame
|
||||
* boundary (16 byte), whichever is the biggest. For example,
|
||||
* for 32-bit, 64-bit and 128-bit wide trace memory, the four
|
||||
* LSBs must be 0s. For 256-bit wide trace memory, the five
|
||||
* LSBs must be 0s.
|
||||
*/
|
||||
switch (drvdata->memwidth) {
|
||||
case TMC_MEM_INTF_WIDTH_32BITS:
|
||||
case TMC_MEM_INTF_WIDTH_64BITS:
|
||||
case TMC_MEM_INTF_WIDTH_128BITS:
|
||||
mask = GENMASK(31, 5);
|
||||
break;
|
||||
case TMC_MEM_INTF_WIDTH_256BITS:
|
||||
mask = GENMASK(31, 6);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the new size is aligned in accordance with the
|
||||
* requirement explained above.
|
||||
*/
|
||||
to_read = handle->size & mask;
|
||||
/* Move the RAM read pointer up */
|
||||
read_ptr = (write_ptr + drvdata->size) - to_read;
|
||||
/* Make sure we are still within our limits */
|
||||
if (read_ptr > (drvdata->size - 1))
|
||||
read_ptr -= drvdata->size;
|
||||
/* Tell the HW */
|
||||
writel_relaxed(read_ptr, drvdata->base + TMC_RRP);
|
||||
local_inc(&buf->lost);
|
||||
}
|
||||
|
||||
cur = buf->cur;
|
||||
offset = buf->offset;
|
||||
|
||||
/* for every byte to read */
|
||||
for (i = 0; i < to_read; i += 4) {
|
||||
buf_ptr = buf->data_pages[cur] + offset;
|
||||
*buf_ptr = readl_relaxed(drvdata->base + TMC_RRD);
|
||||
|
||||
offset += 4;
|
||||
if (offset >= PAGE_SIZE) {
|
||||
offset = 0;
|
||||
cur++;
|
||||
/* wrap around at the end of the buffer */
|
||||
cur &= buf->nr_pages - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In snapshot mode all we have to do is communicate to
|
||||
* perf_aux_output_end() the address of the current head. In full
|
||||
* trace mode the same function expects a size to move rb->aux_head
|
||||
* forward.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
local_set(&buf->data_size, (cur * PAGE_SIZE) + offset);
|
||||
else
|
||||
local_add(to_read, &buf->data_size);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink tmc_etf_sink_ops = {
|
||||
.enable = tmc_enable_etf_sink,
|
||||
.disable = tmc_disable_etf_sink,
|
||||
.alloc_buffer = tmc_alloc_etf_buffer,
|
||||
.free_buffer = tmc_free_etf_buffer,
|
||||
.set_buffer = tmc_set_etf_buffer,
|
||||
.reset_buffer = tmc_reset_etf_buffer,
|
||||
.update_buffer = tmc_update_etf_buffer,
|
||||
};
|
||||
|
||||
static const struct coresight_ops_link tmc_etf_link_ops = {
|
||||
.enable = tmc_enable_etf_link,
|
||||
.disable = tmc_disable_etf_link,
|
||||
};
|
||||
|
||||
const struct coresight_ops tmc_etb_cs_ops = {
|
||||
.sink_ops = &tmc_etf_sink_ops,
|
||||
};
|
||||
|
||||
const struct coresight_ops tmc_etf_cs_ops = {
|
||||
.sink_ops = &tmc_etf_sink_ops,
|
||||
.link_ops = &tmc_etf_link_ops,
|
||||
};
|
||||
|
||||
int tmc_read_prepare_etb(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
long val;
|
||||
enum tmc_mode mode;
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
|
||||
/* config types are set a boot time and never change */
|
||||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETB &&
|
||||
drvdata->config_type != TMC_CONFIG_TYPE_ETF))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* There is no point in reading a TMC in HW FIFO mode */
|
||||
mode = readl_relaxed(drvdata->base + TMC_MODE);
|
||||
if (mode != TMC_MODE_CIRCULAR_BUFFER) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = local_read(&drvdata->mode);
|
||||
/* Don't interfere if operated from Perf */
|
||||
if (val == CS_MODE_PERF) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If drvdata::buf is NULL the trace data has been read already */
|
||||
if (drvdata->buf == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Disable the TMC if need be */
|
||||
if (val == CS_MODE_SYSFS)
|
||||
tmc_etb_disable_hw(drvdata);
|
||||
|
||||
drvdata->reading = true;
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
char *buf = NULL;
|
||||
enum tmc_mode mode;
|
||||
unsigned long flags;
|
||||
|
||||
/* config types are set a boot time and never change */
|
||||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETB &&
|
||||
drvdata->config_type != TMC_CONFIG_TYPE_ETF))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
/* There is no point in reading a TMC in HW FIFO mode */
|
||||
mode = readl_relaxed(drvdata->base + TMC_MODE);
|
||||
if (mode != TMC_MODE_CIRCULAR_BUFFER) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Re-enable the TMC if need be */
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS) {
|
||||
/*
|
||||
* The trace run will continue with the same allocated trace
|
||||
* buffer. As such zero-out the buffer so that we don't end
|
||||
* up with stale data.
|
||||
*
|
||||
* Since the tracer is still enabled drvdata::buf
|
||||
* can't be NULL.
|
||||
*/
|
||||
memset(drvdata->buf, 0, drvdata->size);
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
} else {
|
||||
/*
|
||||
* The ETB/ETF is not tracing and the buffer was just read.
|
||||
* As such prepare to free the trace buffer.
|
||||
*/
|
||||
buf = drvdata->buf;
|
||||
drvdata->buf = NULL;
|
||||
}
|
||||
|
||||
drvdata->reading = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/*
|
||||
* Free allocated memory outside of the spinlock. There is no need
|
||||
* to assert the validity of 'buf' since calling kfree(NULL) is safe.
|
||||
*/
|
||||
kfree(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
329
drivers/hwtracing/coresight/coresight-tmc-etr.c
Normal file
329
drivers/hwtracing/coresight/coresight-tmc-etr.c
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* Copyright(C) 2016 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-tmc.h"
|
||||
|
||||
void tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
u32 axictl;
|
||||
|
||||
/* Zero out the memory to help with debug */
|
||||
memset(drvdata->vaddr, 0, drvdata->size);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Wait for TMCSReady bit to be set */
|
||||
tmc_wait_for_tmcready(drvdata);
|
||||
|
||||
writel_relaxed(drvdata->size / 4, drvdata->base + TMC_RSZ);
|
||||
writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
|
||||
|
||||
axictl = readl_relaxed(drvdata->base + TMC_AXICTL);
|
||||
axictl |= TMC_AXICTL_WR_BURST_16;
|
||||
writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
|
||||
axictl &= ~TMC_AXICTL_SCT_GAT_MODE;
|
||||
writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
|
||||
axictl = (axictl &
|
||||
~(TMC_AXICTL_PROT_CTL_B0 | TMC_AXICTL_PROT_CTL_B1)) |
|
||||
TMC_AXICTL_PROT_CTL_B1;
|
||||
writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
|
||||
|
||||
writel_relaxed(drvdata->paddr, drvdata->base + TMC_DBALO);
|
||||
writel_relaxed(0x0, drvdata->base + TMC_DBAHI);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI |
|
||||
TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT |
|
||||
TMC_FFCR_TRIGON_TRIGIN,
|
||||
drvdata->base + TMC_FFCR);
|
||||
writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
u32 rwp, val;
|
||||
|
||||
rwp = readl_relaxed(drvdata->base + TMC_RWP);
|
||||
val = readl_relaxed(drvdata->base + TMC_STS);
|
||||
|
||||
/* How much memory do we still have */
|
||||
if (val & BIT(0))
|
||||
drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr;
|
||||
else
|
||||
drvdata->buf = drvdata->vaddr;
|
||||
}
|
||||
|
||||
static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
/*
|
||||
* When operating in sysFS mode the content of the buffer needs to be
|
||||
* read before the TMC is disabled.
|
||||
*/
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS)
|
||||
tmc_etr_dump_hw(drvdata);
|
||||
tmc_disable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret = 0;
|
||||
bool used = false;
|
||||
long val;
|
||||
unsigned long flags;
|
||||
void __iomem *vaddr = NULL;
|
||||
dma_addr_t paddr;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
/* This shouldn't be happening */
|
||||
if (WARN_ON(mode != CS_MODE_SYSFS))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* If we don't have a buffer release the lock and allocate memory.
|
||||
* Otherwise keep the lock and move along.
|
||||
*/
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (!drvdata->vaddr) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/*
|
||||
* Contiguous memory can't be allocated while a spinlock is
|
||||
* held. As such allocate memory here and free it if a buffer
|
||||
* has already been allocated (from a previous session).
|
||||
*/
|
||||
vaddr = dma_alloc_coherent(drvdata->dev, drvdata->size,
|
||||
&paddr, GFP_KERNEL);
|
||||
if (!vaddr)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Let's try again */
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
}
|
||||
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = local_xchg(&drvdata->mode, mode);
|
||||
/*
|
||||
* In sysFS mode we can have multiple writers per sink. Since this
|
||||
* sink is already enabled no memory is needed and the HW need not be
|
||||
* touched.
|
||||
*/
|
||||
if (val == CS_MODE_SYSFS)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* If drvdata::buf == NULL, use the memory allocated above.
|
||||
* Otherwise a buffer still exists from a previous session, so
|
||||
* simply use that.
|
||||
*/
|
||||
if (drvdata->buf == NULL) {
|
||||
used = true;
|
||||
drvdata->vaddr = vaddr;
|
||||
drvdata->paddr = paddr;
|
||||
drvdata->buf = drvdata->vaddr;
|
||||
}
|
||||
|
||||
memset(drvdata->vaddr, 0, drvdata->size);
|
||||
|
||||
tmc_etr_enable_hw(drvdata);
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/* Free memory outside the spinlock if need be */
|
||||
if (!used && vaddr)
|
||||
dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr);
|
||||
|
||||
if (!ret)
|
||||
dev_info(drvdata->dev, "TMC-ETR enabled\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink_perf(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret = 0;
|
||||
long val;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
/* This shouldn't be happening */
|
||||
if (WARN_ON(mode != CS_MODE_PERF))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = local_xchg(&drvdata->mode, mode);
|
||||
/*
|
||||
* In Perf mode there can be only one writer per sink. There
|
||||
* is also no need to continue if the ETR is already operated
|
||||
* from sysFS.
|
||||
*/
|
||||
if (val != CS_MODE_DISABLED) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
tmc_etr_enable_hw(drvdata);
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_enable_etr_sink(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case CS_MODE_SYSFS:
|
||||
return tmc_enable_etr_sink_sysfs(csdev, mode);
|
||||
case CS_MODE_PERF:
|
||||
return tmc_enable_etr_sink_perf(csdev, mode);
|
||||
}
|
||||
|
||||
/* We shouldn't be here */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void tmc_disable_etr_sink(struct coresight_device *csdev)
|
||||
{
|
||||
long val;
|
||||
unsigned long flags;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
val = local_xchg(&drvdata->mode, CS_MODE_DISABLED);
|
||||
/* Disable the TMC only if it needs to */
|
||||
if (val != CS_MODE_DISABLED)
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC-ETR disabled\n");
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink tmc_etr_sink_ops = {
|
||||
.enable = tmc_enable_etr_sink,
|
||||
.disable = tmc_disable_etr_sink,
|
||||
};
|
||||
|
||||
const struct coresight_ops tmc_etr_cs_ops = {
|
||||
.sink_ops = &tmc_etr_sink_ops,
|
||||
};
|
||||
|
||||
int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
int ret = 0;
|
||||
long val;
|
||||
unsigned long flags;
|
||||
|
||||
/* config types are set a boot time and never change */
|
||||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
val = local_read(&drvdata->mode);
|
||||
/* Don't interfere if operated from Perf */
|
||||
if (val == CS_MODE_PERF) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If drvdata::buf is NULL the trace data has been read already */
|
||||
if (drvdata->buf == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Disable the TMC if need be */
|
||||
if (val == CS_MODE_SYSFS)
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
|
||||
drvdata->reading = true;
|
||||
out:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
unsigned long flags;
|
||||
dma_addr_t paddr;
|
||||
void __iomem *vaddr = NULL;
|
||||
|
||||
/* config types are set a boot time and never change */
|
||||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
/* RE-enable the TMC if need be */
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS) {
|
||||
/*
|
||||
* The trace run will continue with the same allocated trace
|
||||
* buffer. As such zero-out the buffer so that we don't end
|
||||
* up with stale data.
|
||||
*
|
||||
* Since the tracer is still enabled drvdata::buf
|
||||
* can't be NULL.
|
||||
*/
|
||||
memset(drvdata->buf, 0, drvdata->size);
|
||||
tmc_etr_enable_hw(drvdata);
|
||||
} else {
|
||||
/*
|
||||
* The ETR is not tracing and the buffer was just read.
|
||||
* As such prepare to free the trace buffer.
|
||||
*/
|
||||
vaddr = drvdata->vaddr;
|
||||
paddr = drvdata->paddr;
|
||||
drvdata->buf = NULL;
|
||||
}
|
||||
|
||||
drvdata->reading = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
/* Free allocated memory out side of the spinlock */
|
||||
if (vaddr)
|
||||
dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Trace Memory Controller driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
|
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
|
@ -29,127 +30,27 @@
|
|||
#include <linux/amba/bus.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-tmc.h"
|
||||
|
||||
#define TMC_RSZ 0x004
|
||||
#define TMC_STS 0x00c
|
||||
#define TMC_RRD 0x010
|
||||
#define TMC_RRP 0x014
|
||||
#define TMC_RWP 0x018
|
||||
#define TMC_TRG 0x01c
|
||||
#define TMC_CTL 0x020
|
||||
#define TMC_RWD 0x024
|
||||
#define TMC_MODE 0x028
|
||||
#define TMC_LBUFLEVEL 0x02c
|
||||
#define TMC_CBUFLEVEL 0x030
|
||||
#define TMC_BUFWM 0x034
|
||||
#define TMC_RRPHI 0x038
|
||||
#define TMC_RWPHI 0x03c
|
||||
#define TMC_AXICTL 0x110
|
||||
#define TMC_DBALO 0x118
|
||||
#define TMC_DBAHI 0x11c
|
||||
#define TMC_FFSR 0x300
|
||||
#define TMC_FFCR 0x304
|
||||
#define TMC_PSCR 0x308
|
||||
#define TMC_ITMISCOP0 0xee0
|
||||
#define TMC_ITTRFLIN 0xee8
|
||||
#define TMC_ITATBDATA0 0xeec
|
||||
#define TMC_ITATBCTR2 0xef0
|
||||
#define TMC_ITATBCTR1 0xef4
|
||||
#define TMC_ITATBCTR0 0xef8
|
||||
|
||||
/* register description */
|
||||
/* TMC_CTL - 0x020 */
|
||||
#define TMC_CTL_CAPT_EN BIT(0)
|
||||
/* TMC_STS - 0x00C */
|
||||
#define TMC_STS_TRIGGERED BIT(1)
|
||||
/* TMC_AXICTL - 0x110 */
|
||||
#define TMC_AXICTL_PROT_CTL_B0 BIT(0)
|
||||
#define TMC_AXICTL_PROT_CTL_B1 BIT(1)
|
||||
#define TMC_AXICTL_SCT_GAT_MODE BIT(7)
|
||||
#define TMC_AXICTL_WR_BURST_LEN 0xF00
|
||||
/* TMC_FFCR - 0x304 */
|
||||
#define TMC_FFCR_EN_FMT BIT(0)
|
||||
#define TMC_FFCR_EN_TI BIT(1)
|
||||
#define TMC_FFCR_FON_FLIN BIT(4)
|
||||
#define TMC_FFCR_FON_TRIG_EVT BIT(5)
|
||||
#define TMC_FFCR_FLUSHMAN BIT(6)
|
||||
#define TMC_FFCR_TRIGON_TRIGIN BIT(8)
|
||||
#define TMC_FFCR_STOP_ON_FLUSH BIT(12)
|
||||
|
||||
#define TMC_STS_TRIGGERED_BIT 2
|
||||
#define TMC_FFCR_FLUSHMAN_BIT 6
|
||||
|
||||
enum tmc_config_type {
|
||||
TMC_CONFIG_TYPE_ETB,
|
||||
TMC_CONFIG_TYPE_ETR,
|
||||
TMC_CONFIG_TYPE_ETF,
|
||||
};
|
||||
|
||||
enum tmc_mode {
|
||||
TMC_MODE_CIRCULAR_BUFFER,
|
||||
TMC_MODE_SOFTWARE_FIFO,
|
||||
TMC_MODE_HARDWARE_FIFO,
|
||||
};
|
||||
|
||||
enum tmc_mem_intf_width {
|
||||
TMC_MEM_INTF_WIDTH_32BITS = 0x2,
|
||||
TMC_MEM_INTF_WIDTH_64BITS = 0x3,
|
||||
TMC_MEM_INTF_WIDTH_128BITS = 0x4,
|
||||
TMC_MEM_INTF_WIDTH_256BITS = 0x5,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tmc_drvdata - specifics associated to an TMC component
|
||||
* @base: memory mapped base address for this component.
|
||||
* @dev: the device entity associated to this component.
|
||||
* @csdev: component vitals needed by the framework.
|
||||
* @miscdev: specifics to handle "/dev/xyz.tmc" entry.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @read_count: manages preparation of buffer for reading.
|
||||
* @buf: area of memory where trace data get sent.
|
||||
* @paddr: DMA start location in RAM.
|
||||
* @vaddr: virtual representation of @paddr.
|
||||
* @size: @buf size.
|
||||
* @enable: this TMC is being used.
|
||||
* @config_type: TMC variant, must be of type @tmc_config_type.
|
||||
* @trigger_cntr: amount of words to store after a trigger.
|
||||
*/
|
||||
struct tmc_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct coresight_device *csdev;
|
||||
struct miscdevice miscdev;
|
||||
spinlock_t spinlock;
|
||||
int read_count;
|
||||
bool reading;
|
||||
char *buf;
|
||||
dma_addr_t paddr;
|
||||
void __iomem *vaddr;
|
||||
u32 size;
|
||||
bool enable;
|
||||
enum tmc_config_type config_type;
|
||||
u32 trigger_cntr;
|
||||
};
|
||||
|
||||
static void tmc_wait_for_ready(struct tmc_drvdata *drvdata)
|
||||
void tmc_wait_for_tmcready(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
/* Ensure formatter, unformatter and hardware fifo are empty */
|
||||
if (coresight_timeout(drvdata->base,
|
||||
TMC_STS, TMC_STS_TRIGGERED_BIT, 1)) {
|
||||
TMC_STS, TMC_STS_TMCREADY_BIT, 1)) {
|
||||
dev_err(drvdata->dev,
|
||||
"timeout observed when probing at offset %#x\n",
|
||||
TMC_STS);
|
||||
}
|
||||
}
|
||||
|
||||
static void tmc_flush_and_stop(struct tmc_drvdata *drvdata)
|
||||
void tmc_flush_and_stop(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
u32 ffcr;
|
||||
|
||||
ffcr = readl_relaxed(drvdata->base + TMC_FFCR);
|
||||
ffcr |= TMC_FFCR_STOP_ON_FLUSH;
|
||||
writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
|
||||
ffcr |= TMC_FFCR_FLUSHMAN;
|
||||
ffcr |= BIT(TMC_FFCR_FLUSHMAN_BIT);
|
||||
writel_relaxed(ffcr, drvdata->base + TMC_FFCR);
|
||||
/* Ensure flush completes */
|
||||
if (coresight_timeout(drvdata->base,
|
||||
|
|
@ -159,343 +60,73 @@ static void tmc_flush_and_stop(struct tmc_drvdata *drvdata)
|
|||
TMC_FFCR);
|
||||
}
|
||||
|
||||
tmc_wait_for_ready(drvdata);
|
||||
tmc_wait_for_tmcready(drvdata);
|
||||
}
|
||||
|
||||
static void tmc_enable_hw(struct tmc_drvdata *drvdata)
|
||||
void tmc_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
writel_relaxed(TMC_CTL_CAPT_EN, drvdata->base + TMC_CTL);
|
||||
}
|
||||
|
||||
static void tmc_disable_hw(struct tmc_drvdata *drvdata)
|
||||
void tmc_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
writel_relaxed(0x0, drvdata->base + TMC_CTL);
|
||||
}
|
||||
|
||||
static void tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
/* Zero out the memory to help with debug */
|
||||
memset(drvdata->buf, 0, drvdata->size);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI |
|
||||
TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT |
|
||||
TMC_FFCR_TRIGON_TRIGIN,
|
||||
drvdata->base + TMC_FFCR);
|
||||
|
||||
writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
u32 axictl;
|
||||
|
||||
/* Zero out the memory to help with debug */
|
||||
memset(drvdata->vaddr, 0, drvdata->size);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
writel_relaxed(drvdata->size / 4, drvdata->base + TMC_RSZ);
|
||||
writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
|
||||
|
||||
axictl = readl_relaxed(drvdata->base + TMC_AXICTL);
|
||||
axictl |= TMC_AXICTL_WR_BURST_LEN;
|
||||
writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
|
||||
axictl &= ~TMC_AXICTL_SCT_GAT_MODE;
|
||||
writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
|
||||
axictl = (axictl &
|
||||
~(TMC_AXICTL_PROT_CTL_B0 | TMC_AXICTL_PROT_CTL_B1)) |
|
||||
TMC_AXICTL_PROT_CTL_B1;
|
||||
writel_relaxed(axictl, drvdata->base + TMC_AXICTL);
|
||||
|
||||
writel_relaxed(drvdata->paddr, drvdata->base + TMC_DBALO);
|
||||
writel_relaxed(0x0, drvdata->base + TMC_DBAHI);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI |
|
||||
TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT |
|
||||
TMC_FFCR_TRIGON_TRIGIN,
|
||||
drvdata->base + TMC_FFCR);
|
||||
writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG);
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
writel_relaxed(TMC_MODE_HARDWARE_FIFO, drvdata->base + TMC_MODE);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI,
|
||||
drvdata->base + TMC_FFCR);
|
||||
writel_relaxed(0x0, drvdata->base + TMC_BUFWM);
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
pm_runtime_put(drvdata->dev);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) {
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
} else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
tmc_etr_enable_hw(drvdata);
|
||||
} else {
|
||||
if (mode == TMC_MODE_CIRCULAR_BUFFER)
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
else
|
||||
tmc_etf_enable_hw(drvdata);
|
||||
}
|
||||
drvdata->enable = true;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmc_enable_sink(struct coresight_device *csdev)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
return tmc_enable(drvdata, TMC_MODE_CIRCULAR_BUFFER);
|
||||
}
|
||||
|
||||
static int tmc_enable_link(struct coresight_device *csdev, int inport,
|
||||
int outport)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
return tmc_enable(drvdata, TMC_MODE_HARDWARE_FIFO);
|
||||
}
|
||||
|
||||
static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
enum tmc_mem_intf_width memwidth;
|
||||
u8 memwords;
|
||||
char *bufp;
|
||||
u32 read_data;
|
||||
int i;
|
||||
|
||||
memwidth = BMVAL(readl_relaxed(drvdata->base + CORESIGHT_DEVID), 8, 10);
|
||||
if (memwidth == TMC_MEM_INTF_WIDTH_32BITS)
|
||||
memwords = 1;
|
||||
else if (memwidth == TMC_MEM_INTF_WIDTH_64BITS)
|
||||
memwords = 2;
|
||||
else if (memwidth == TMC_MEM_INTF_WIDTH_128BITS)
|
||||
memwords = 4;
|
||||
else
|
||||
memwords = 8;
|
||||
|
||||
bufp = drvdata->buf;
|
||||
while (1) {
|
||||
for (i = 0; i < memwords; i++) {
|
||||
read_data = readl_relaxed(drvdata->base + TMC_RRD);
|
||||
if (read_data == 0xFFFFFFFF)
|
||||
return;
|
||||
memcpy(bufp, &read_data, 4);
|
||||
bufp += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
tmc_etb_dump_hw(drvdata);
|
||||
tmc_disable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
u32 rwp, val;
|
||||
|
||||
rwp = readl_relaxed(drvdata->base + TMC_RWP);
|
||||
val = readl_relaxed(drvdata->base + TMC_STS);
|
||||
|
||||
/* How much memory do we still have */
|
||||
if (val & BIT(0))
|
||||
drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr;
|
||||
else
|
||||
drvdata->buf = drvdata->vaddr;
|
||||
}
|
||||
|
||||
static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
tmc_etr_dump_hw(drvdata);
|
||||
tmc_disable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
tmc_flush_and_stop(drvdata);
|
||||
tmc_disable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tmc_disable(struct tmc_drvdata *drvdata, enum tmc_mode mode)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading)
|
||||
goto out;
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) {
|
||||
tmc_etb_disable_hw(drvdata);
|
||||
} else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
} else {
|
||||
if (mode == TMC_MODE_CIRCULAR_BUFFER)
|
||||
tmc_etb_disable_hw(drvdata);
|
||||
else
|
||||
tmc_etf_disable_hw(drvdata);
|
||||
}
|
||||
out:
|
||||
drvdata->enable = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
dev_info(drvdata->dev, "TMC disabled\n");
|
||||
}
|
||||
|
||||
static void tmc_disable_sink(struct coresight_device *csdev)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
tmc_disable(drvdata, TMC_MODE_CIRCULAR_BUFFER);
|
||||
}
|
||||
|
||||
static void tmc_disable_link(struct coresight_device *csdev, int inport,
|
||||
int outport)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
tmc_disable(drvdata, TMC_MODE_HARDWARE_FIFO);
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink tmc_sink_ops = {
|
||||
.enable = tmc_enable_sink,
|
||||
.disable = tmc_disable_sink,
|
||||
};
|
||||
|
||||
static const struct coresight_ops_link tmc_link_ops = {
|
||||
.enable = tmc_enable_link,
|
||||
.disable = tmc_disable_link,
|
||||
};
|
||||
|
||||
static const struct coresight_ops tmc_etb_cs_ops = {
|
||||
.sink_ops = &tmc_sink_ops,
|
||||
};
|
||||
|
||||
static const struct coresight_ops tmc_etr_cs_ops = {
|
||||
.sink_ops = &tmc_sink_ops,
|
||||
};
|
||||
|
||||
static const struct coresight_ops tmc_etf_cs_ops = {
|
||||
.sink_ops = &tmc_sink_ops,
|
||||
.link_ops = &tmc_link_ops,
|
||||
};
|
||||
|
||||
static int tmc_read_prepare(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
enum tmc_mode mode;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (!drvdata->enable)
|
||||
goto out;
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) {
|
||||
tmc_etb_disable_hw(drvdata);
|
||||
} else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
tmc_etr_disable_hw(drvdata);
|
||||
} else {
|
||||
mode = readl_relaxed(drvdata->base + TMC_MODE);
|
||||
if (mode == TMC_MODE_CIRCULAR_BUFFER) {
|
||||
tmc_etb_disable_hw(drvdata);
|
||||
} else {
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
switch (drvdata->config_type) {
|
||||
case TMC_CONFIG_TYPE_ETB:
|
||||
case TMC_CONFIG_TYPE_ETF:
|
||||
ret = tmc_read_prepare_etb(drvdata);
|
||||
break;
|
||||
case TMC_CONFIG_TYPE_ETR:
|
||||
ret = tmc_read_prepare_etr(drvdata);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
out:
|
||||
drvdata->reading = true;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC read start\n");
|
||||
return 0;
|
||||
err:
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
if (!ret)
|
||||
dev_info(drvdata->dev, "TMC read start\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tmc_read_unprepare(struct tmc_drvdata *drvdata)
|
||||
static int tmc_read_unprepare(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
unsigned long flags;
|
||||
enum tmc_mode mode;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (!drvdata->enable)
|
||||
goto out;
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) {
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
} else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
tmc_etr_enable_hw(drvdata);
|
||||
} else {
|
||||
mode = readl_relaxed(drvdata->base + TMC_MODE);
|
||||
if (mode == TMC_MODE_CIRCULAR_BUFFER)
|
||||
tmc_etb_enable_hw(drvdata);
|
||||
switch (drvdata->config_type) {
|
||||
case TMC_CONFIG_TYPE_ETB:
|
||||
case TMC_CONFIG_TYPE_ETF:
|
||||
ret = tmc_read_unprepare_etb(drvdata);
|
||||
break;
|
||||
case TMC_CONFIG_TYPE_ETR:
|
||||
ret = tmc_read_unprepare_etr(drvdata);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
out:
|
||||
drvdata->reading = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
dev_info(drvdata->dev, "TMC read end\n");
|
||||
if (!ret)
|
||||
dev_info(drvdata->dev, "TMC read end\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int ret;
|
||||
struct tmc_drvdata *drvdata = container_of(file->private_data,
|
||||
struct tmc_drvdata, miscdev);
|
||||
int ret = 0;
|
||||
|
||||
if (drvdata->read_count++)
|
||||
goto out;
|
||||
|
||||
ret = tmc_read_prepare(drvdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
out:
|
||||
|
||||
nonseekable_open(inode, file);
|
||||
|
||||
dev_dbg(drvdata->dev, "%s: successfully opened\n", __func__);
|
||||
|
|
@ -535,19 +166,14 @@ static ssize_t tmc_read(struct file *file, char __user *data, size_t len,
|
|||
|
||||
static int tmc_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
int ret;
|
||||
struct tmc_drvdata *drvdata = container_of(file->private_data,
|
||||
struct tmc_drvdata, miscdev);
|
||||
|
||||
if (--drvdata->read_count) {
|
||||
if (drvdata->read_count < 0) {
|
||||
dev_err(drvdata->dev, "mismatched close\n");
|
||||
drvdata->read_count = 0;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
ret = tmc_read_unprepare(drvdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tmc_read_unprepare(drvdata);
|
||||
out:
|
||||
dev_dbg(drvdata->dev, "%s: released\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -560,56 +186,71 @@ static const struct file_operations tmc_fops = {
|
|||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static ssize_t status_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
static enum tmc_mem_intf_width tmc_get_memwidth(u32 devid)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg;
|
||||
u32 tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr;
|
||||
u32 devid;
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
enum tmc_mem_intf_width memwidth;
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
CS_UNLOCK(drvdata->base);
|
||||
/*
|
||||
* Excerpt from the TRM:
|
||||
*
|
||||
* DEVID::MEMWIDTH[10:8]
|
||||
* 0x2 Memory interface databus is 32 bits wide.
|
||||
* 0x3 Memory interface databus is 64 bits wide.
|
||||
* 0x4 Memory interface databus is 128 bits wide.
|
||||
* 0x5 Memory interface databus is 256 bits wide.
|
||||
*/
|
||||
switch (BMVAL(devid, 8, 10)) {
|
||||
case 0x2:
|
||||
memwidth = TMC_MEM_INTF_WIDTH_32BITS;
|
||||
break;
|
||||
case 0x3:
|
||||
memwidth = TMC_MEM_INTF_WIDTH_64BITS;
|
||||
break;
|
||||
case 0x4:
|
||||
memwidth = TMC_MEM_INTF_WIDTH_128BITS;
|
||||
break;
|
||||
case 0x5:
|
||||
memwidth = TMC_MEM_INTF_WIDTH_256BITS;
|
||||
break;
|
||||
default:
|
||||
memwidth = 0;
|
||||
}
|
||||
|
||||
tmc_rsz = readl_relaxed(drvdata->base + TMC_RSZ);
|
||||
tmc_sts = readl_relaxed(drvdata->base + TMC_STS);
|
||||
tmc_rrp = readl_relaxed(drvdata->base + TMC_RRP);
|
||||
tmc_rwp = readl_relaxed(drvdata->base + TMC_RWP);
|
||||
tmc_trg = readl_relaxed(drvdata->base + TMC_TRG);
|
||||
tmc_ctl = readl_relaxed(drvdata->base + TMC_CTL);
|
||||
tmc_ffsr = readl_relaxed(drvdata->base + TMC_FFSR);
|
||||
tmc_ffcr = readl_relaxed(drvdata->base + TMC_FFCR);
|
||||
tmc_mode = readl_relaxed(drvdata->base + TMC_MODE);
|
||||
tmc_pscr = readl_relaxed(drvdata->base + TMC_PSCR);
|
||||
devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
return sprintf(buf,
|
||||
"Depth:\t\t0x%x\n"
|
||||
"Status:\t\t0x%x\n"
|
||||
"RAM read ptr:\t0x%x\n"
|
||||
"RAM wrt ptr:\t0x%x\n"
|
||||
"Trigger cnt:\t0x%x\n"
|
||||
"Control:\t0x%x\n"
|
||||
"Flush status:\t0x%x\n"
|
||||
"Flush ctrl:\t0x%x\n"
|
||||
"Mode:\t\t0x%x\n"
|
||||
"PSRC:\t\t0x%x\n"
|
||||
"DEVID:\t\t0x%x\n",
|
||||
tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg,
|
||||
tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr, devid);
|
||||
|
||||
return -EINVAL;
|
||||
return memwidth;
|
||||
}
|
||||
static DEVICE_ATTR_RO(status);
|
||||
|
||||
static ssize_t trigger_cntr_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
#define coresight_tmc_simple_func(name, offset) \
|
||||
coresight_simple_func(struct tmc_drvdata, name, offset)
|
||||
|
||||
coresight_tmc_simple_func(rsz, TMC_RSZ);
|
||||
coresight_tmc_simple_func(sts, TMC_STS);
|
||||
coresight_tmc_simple_func(rrp, TMC_RRP);
|
||||
coresight_tmc_simple_func(rwp, TMC_RWP);
|
||||
coresight_tmc_simple_func(trg, TMC_TRG);
|
||||
coresight_tmc_simple_func(ctl, TMC_CTL);
|
||||
coresight_tmc_simple_func(ffsr, TMC_FFSR);
|
||||
coresight_tmc_simple_func(ffcr, TMC_FFCR);
|
||||
coresight_tmc_simple_func(mode, TMC_MODE);
|
||||
coresight_tmc_simple_func(pscr, TMC_PSCR);
|
||||
coresight_tmc_simple_func(devid, CORESIGHT_DEVID);
|
||||
|
||||
static struct attribute *coresight_tmc_mgmt_attrs[] = {
|
||||
&dev_attr_rsz.attr,
|
||||
&dev_attr_sts.attr,
|
||||
&dev_attr_rrp.attr,
|
||||
&dev_attr_rwp.attr,
|
||||
&dev_attr_trg.attr,
|
||||
&dev_attr_ctl.attr,
|
||||
&dev_attr_ffsr.attr,
|
||||
&dev_attr_ffcr.attr,
|
||||
&dev_attr_mode.attr,
|
||||
&dev_attr_pscr.attr,
|
||||
&dev_attr_devid.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
ssize_t trigger_cntr_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
unsigned long val = drvdata->trigger_cntr;
|
||||
|
|
@ -634,26 +275,25 @@ static ssize_t trigger_cntr_store(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RW(trigger_cntr);
|
||||
|
||||
static struct attribute *coresight_etb_attrs[] = {
|
||||
static struct attribute *coresight_tmc_attrs[] = {
|
||||
&dev_attr_trigger_cntr.attr,
|
||||
&dev_attr_status.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(coresight_etb);
|
||||
|
||||
static struct attribute *coresight_etr_attrs[] = {
|
||||
&dev_attr_trigger_cntr.attr,
|
||||
&dev_attr_status.attr,
|
||||
NULL,
|
||||
static const struct attribute_group coresight_tmc_group = {
|
||||
.attrs = coresight_tmc_attrs,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(coresight_etr);
|
||||
|
||||
static struct attribute *coresight_etf_attrs[] = {
|
||||
&dev_attr_trigger_cntr.attr,
|
||||
&dev_attr_status.attr,
|
||||
static const struct attribute_group coresight_tmc_mgmt_group = {
|
||||
.attrs = coresight_tmc_mgmt_attrs,
|
||||
.name = "mgmt",
|
||||
};
|
||||
|
||||
const struct attribute_group *coresight_tmc_groups[] = {
|
||||
&coresight_tmc_group,
|
||||
&coresight_tmc_mgmt_group,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(coresight_etf);
|
||||
|
||||
static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
{
|
||||
|
|
@ -692,6 +332,7 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
|
||||
devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID);
|
||||
drvdata->config_type = BMVAL(devid, 6, 7);
|
||||
drvdata->memwidth = tmc_get_memwidth(devid);
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
if (np)
|
||||
|
|
@ -706,20 +347,6 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
|
||||
pm_runtime_put(&adev->dev);
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
drvdata->vaddr = dma_alloc_coherent(dev, drvdata->size,
|
||||
&drvdata->paddr, GFP_KERNEL);
|
||||
if (!drvdata->vaddr)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(drvdata->vaddr, 0, drvdata->size);
|
||||
drvdata->buf = drvdata->vaddr;
|
||||
} else {
|
||||
drvdata->buf = devm_kzalloc(dev, drvdata->size, GFP_KERNEL);
|
||||
if (!drvdata->buf)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
|
||||
if (!desc) {
|
||||
ret = -ENOMEM;
|
||||
|
|
@ -729,20 +356,18 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
desc->pdata = pdata;
|
||||
desc->dev = dev;
|
||||
desc->subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
|
||||
desc->groups = coresight_tmc_groups;
|
||||
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) {
|
||||
desc->type = CORESIGHT_DEV_TYPE_SINK;
|
||||
desc->ops = &tmc_etb_cs_ops;
|
||||
desc->groups = coresight_etb_groups;
|
||||
} else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
|
||||
desc->type = CORESIGHT_DEV_TYPE_SINK;
|
||||
desc->ops = &tmc_etr_cs_ops;
|
||||
desc->groups = coresight_etr_groups;
|
||||
} else {
|
||||
desc->type = CORESIGHT_DEV_TYPE_LINKSINK;
|
||||
desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_FIFO;
|
||||
desc->ops = &tmc_etf_cs_ops;
|
||||
desc->groups = coresight_etf_groups;
|
||||
}
|
||||
|
||||
drvdata->csdev = coresight_register(desc);
|
||||
|
|
@ -758,7 +383,6 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
if (ret)
|
||||
goto err_misc_register;
|
||||
|
||||
dev_info(dev, "TMC initialized\n");
|
||||
return 0;
|
||||
|
||||
err_misc_register:
|
||||
|
|
@ -766,23 +390,10 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
err_devm_kzalloc:
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR)
|
||||
dma_free_coherent(dev, drvdata->size,
|
||||
&drvdata->paddr, GFP_KERNEL);
|
||||
drvdata->vaddr, drvdata->paddr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_remove(struct amba_device *adev)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
misc_deregister(&drvdata->miscdev);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR)
|
||||
dma_free_coherent(drvdata->dev, drvdata->size,
|
||||
&drvdata->paddr, GFP_KERNEL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct amba_id tmc_ids[] = {
|
||||
{
|
||||
.id = 0x0003b961,
|
||||
|
|
@ -795,13 +406,9 @@ static struct amba_driver tmc_driver = {
|
|||
.drv = {
|
||||
.name = "coresight-tmc",
|
||||
.owner = THIS_MODULE,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tmc_probe,
|
||||
.remove = tmc_remove,
|
||||
.id_table = tmc_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(tmc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Trace Memory Controller driver");
|
||||
builtin_amba_driver(tmc_driver);
|
||||
|
|
|
|||
140
drivers/hwtracing/coresight/coresight-tmc.h
Normal file
140
drivers/hwtracing/coresight/coresight-tmc.h
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright(C) 2015 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _CORESIGHT_TMC_H
|
||||
#define _CORESIGHT_TMC_H
|
||||
|
||||
#include <linux/miscdevice.h>
|
||||
|
||||
#define TMC_RSZ 0x004
|
||||
#define TMC_STS 0x00c
|
||||
#define TMC_RRD 0x010
|
||||
#define TMC_RRP 0x014
|
||||
#define TMC_RWP 0x018
|
||||
#define TMC_TRG 0x01c
|
||||
#define TMC_CTL 0x020
|
||||
#define TMC_RWD 0x024
|
||||
#define TMC_MODE 0x028
|
||||
#define TMC_LBUFLEVEL 0x02c
|
||||
#define TMC_CBUFLEVEL 0x030
|
||||
#define TMC_BUFWM 0x034
|
||||
#define TMC_RRPHI 0x038
|
||||
#define TMC_RWPHI 0x03c
|
||||
#define TMC_AXICTL 0x110
|
||||
#define TMC_DBALO 0x118
|
||||
#define TMC_DBAHI 0x11c
|
||||
#define TMC_FFSR 0x300
|
||||
#define TMC_FFCR 0x304
|
||||
#define TMC_PSCR 0x308
|
||||
#define TMC_ITMISCOP0 0xee0
|
||||
#define TMC_ITTRFLIN 0xee8
|
||||
#define TMC_ITATBDATA0 0xeec
|
||||
#define TMC_ITATBCTR2 0xef0
|
||||
#define TMC_ITATBCTR1 0xef4
|
||||
#define TMC_ITATBCTR0 0xef8
|
||||
|
||||
/* register description */
|
||||
/* TMC_CTL - 0x020 */
|
||||
#define TMC_CTL_CAPT_EN BIT(0)
|
||||
/* TMC_STS - 0x00C */
|
||||
#define TMC_STS_TMCREADY_BIT 2
|
||||
#define TMC_STS_FULL BIT(0)
|
||||
#define TMC_STS_TRIGGERED BIT(1)
|
||||
/* TMC_AXICTL - 0x110 */
|
||||
#define TMC_AXICTL_PROT_CTL_B0 BIT(0)
|
||||
#define TMC_AXICTL_PROT_CTL_B1 BIT(1)
|
||||
#define TMC_AXICTL_SCT_GAT_MODE BIT(7)
|
||||
#define TMC_AXICTL_WR_BURST_16 0xF00
|
||||
/* TMC_FFCR - 0x304 */
|
||||
#define TMC_FFCR_FLUSHMAN_BIT 6
|
||||
#define TMC_FFCR_EN_FMT BIT(0)
|
||||
#define TMC_FFCR_EN_TI BIT(1)
|
||||
#define TMC_FFCR_FON_FLIN BIT(4)
|
||||
#define TMC_FFCR_FON_TRIG_EVT BIT(5)
|
||||
#define TMC_FFCR_TRIGON_TRIGIN BIT(8)
|
||||
#define TMC_FFCR_STOP_ON_FLUSH BIT(12)
|
||||
|
||||
|
||||
enum tmc_config_type {
|
||||
TMC_CONFIG_TYPE_ETB,
|
||||
TMC_CONFIG_TYPE_ETR,
|
||||
TMC_CONFIG_TYPE_ETF,
|
||||
};
|
||||
|
||||
enum tmc_mode {
|
||||
TMC_MODE_CIRCULAR_BUFFER,
|
||||
TMC_MODE_SOFTWARE_FIFO,
|
||||
TMC_MODE_HARDWARE_FIFO,
|
||||
};
|
||||
|
||||
enum tmc_mem_intf_width {
|
||||
TMC_MEM_INTF_WIDTH_32BITS = 1,
|
||||
TMC_MEM_INTF_WIDTH_64BITS = 2,
|
||||
TMC_MEM_INTF_WIDTH_128BITS = 4,
|
||||
TMC_MEM_INTF_WIDTH_256BITS = 8,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tmc_drvdata - specifics associated to an TMC component
|
||||
* @base: memory mapped base address for this component.
|
||||
* @dev: the device entity associated to this component.
|
||||
* @csdev: component vitals needed by the framework.
|
||||
* @miscdev: specifics to handle "/dev/xyz.tmc" entry.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @buf: area of memory where trace data get sent.
|
||||
* @paddr: DMA start location in RAM.
|
||||
* @vaddr: virtual representation of @paddr.
|
||||
* @size: @buf size.
|
||||
* @mode: how this TMC is being used.
|
||||
* @config_type: TMC variant, must be of type @tmc_config_type.
|
||||
* @memwidth: width of the memory interface databus, in bytes.
|
||||
* @trigger_cntr: amount of words to store after a trigger.
|
||||
*/
|
||||
struct tmc_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct coresight_device *csdev;
|
||||
struct miscdevice miscdev;
|
||||
spinlock_t spinlock;
|
||||
bool reading;
|
||||
char *buf;
|
||||
dma_addr_t paddr;
|
||||
void __iomem *vaddr;
|
||||
u32 size;
|
||||
local_t mode;
|
||||
enum tmc_config_type config_type;
|
||||
enum tmc_mem_intf_width memwidth;
|
||||
u32 trigger_cntr;
|
||||
};
|
||||
|
||||
/* Generic functions */
|
||||
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);
|
||||
void tmc_disable_hw(struct tmc_drvdata *drvdata);
|
||||
|
||||
/* ETB/ETF functions */
|
||||
int tmc_read_prepare_etb(struct tmc_drvdata *drvdata);
|
||||
int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata);
|
||||
extern const struct coresight_ops tmc_etb_cs_ops;
|
||||
extern const struct coresight_ops tmc_etf_cs_ops;
|
||||
|
||||
/* ETR functions */
|
||||
int tmc_read_prepare_etr(struct tmc_drvdata *drvdata);
|
||||
int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata);
|
||||
extern const struct coresight_ops tmc_etr_cs_ops;
|
||||
#endif
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Trace Port Interface Unit driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
|
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
|
|
@ -70,11 +71,10 @@ static void tpiu_enable_hw(struct tpiu_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tpiu_enable(struct coresight_device *csdev)
|
||||
static int tpiu_enable(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(csdev->dev.parent);
|
||||
tpiu_enable_hw(drvdata);
|
||||
|
||||
dev_info(drvdata->dev, "TPIU enabled\n");
|
||||
|
|
@ -98,7 +98,6 @@ static void tpiu_disable(struct coresight_device *csdev)
|
|||
struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
tpiu_disable_hw(drvdata);
|
||||
pm_runtime_put(csdev->dev.parent);
|
||||
|
||||
dev_info(drvdata->dev, "TPIU disabled\n");
|
||||
}
|
||||
|
|
@ -168,15 +167,6 @@ static int tpiu_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
if (IS_ERR(drvdata->csdev))
|
||||
return PTR_ERR(drvdata->csdev);
|
||||
|
||||
dev_info(dev, "TPIU initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpiu_remove(struct amba_device *adev)
|
||||
{
|
||||
struct tpiu_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -223,13 +213,9 @@ static struct amba_driver tpiu_driver = {
|
|||
.name = "coresight-tpiu",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &tpiu_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tpiu_probe,
|
||||
.remove = tpiu_remove,
|
||||
.id_table = tpiu_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(tpiu_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Trace Port Interface Unit driver");
|
||||
builtin_amba_driver(tpiu_driver);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
|
@ -24,11 +23,36 @@
|
|||
#include <linux/coresight.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
|
||||
static DEFINE_MUTEX(coresight_mutex);
|
||||
|
||||
/**
|
||||
* struct coresight_node - elements of a path, from source to sink
|
||||
* @csdev: Address of an element.
|
||||
* @link: hook to the list.
|
||||
*/
|
||||
struct coresight_node {
|
||||
struct coresight_device *csdev;
|
||||
struct list_head link;
|
||||
};
|
||||
|
||||
/*
|
||||
* When operating Coresight drivers from the sysFS interface, only a single
|
||||
* path can exist from a tracer (associated to a CPU) to a sink.
|
||||
*/
|
||||
static DEFINE_PER_CPU(struct list_head *, tracer_path);
|
||||
|
||||
/*
|
||||
* As of this writing only a single STM can be found in CS topologies. Since
|
||||
* there is no way to know if we'll ever see more and what kind of
|
||||
* configuration they will enact, for the time being only define a single path
|
||||
* for STM.
|
||||
*/
|
||||
static struct list_head *stm_path;
|
||||
|
||||
static int coresight_id_match(struct device *dev, void *data)
|
||||
{
|
||||
int trace_id, i_trace_id;
|
||||
|
|
@ -68,15 +92,12 @@ static int coresight_source_is_unique(struct coresight_device *csdev)
|
|||
csdev, coresight_id_match);
|
||||
}
|
||||
|
||||
static int coresight_find_link_inport(struct coresight_device *csdev)
|
||||
static int coresight_find_link_inport(struct coresight_device *csdev,
|
||||
struct coresight_device *parent)
|
||||
{
|
||||
int i;
|
||||
struct coresight_device *parent;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
parent = container_of(csdev->path_link.next,
|
||||
struct coresight_device, path_link);
|
||||
|
||||
for (i = 0; i < parent->nr_outport; i++) {
|
||||
conn = &parent->conns[i];
|
||||
if (conn->child_dev == csdev)
|
||||
|
|
@ -89,15 +110,12 @@ static int coresight_find_link_inport(struct coresight_device *csdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int coresight_find_link_outport(struct coresight_device *csdev)
|
||||
static int coresight_find_link_outport(struct coresight_device *csdev,
|
||||
struct coresight_device *child)
|
||||
{
|
||||
int i;
|
||||
struct coresight_device *child;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
child = container_of(csdev->path_link.prev,
|
||||
struct coresight_device, path_link);
|
||||
|
||||
for (i = 0; i < csdev->nr_outport; i++) {
|
||||
conn = &csdev->conns[i];
|
||||
if (conn->child_dev == child)
|
||||
|
|
@ -110,13 +128,13 @@ static int coresight_find_link_outport(struct coresight_device *csdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int coresight_enable_sink(struct coresight_device *csdev)
|
||||
static int coresight_enable_sink(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!csdev->enable) {
|
||||
if (sink_ops(csdev)->enable) {
|
||||
ret = sink_ops(csdev)->enable(csdev);
|
||||
ret = sink_ops(csdev)->enable(csdev, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -138,14 +156,19 @@ static void coresight_disable_sink(struct coresight_device *csdev)
|
|||
}
|
||||
}
|
||||
|
||||
static int coresight_enable_link(struct coresight_device *csdev)
|
||||
static int coresight_enable_link(struct coresight_device *csdev,
|
||||
struct coresight_device *parent,
|
||||
struct coresight_device *child)
|
||||
{
|
||||
int ret;
|
||||
int link_subtype;
|
||||
int refport, inport, outport;
|
||||
|
||||
inport = coresight_find_link_inport(csdev);
|
||||
outport = coresight_find_link_outport(csdev);
|
||||
if (!parent || !child)
|
||||
return -EINVAL;
|
||||
|
||||
inport = coresight_find_link_inport(csdev, parent);
|
||||
outport = coresight_find_link_outport(csdev, child);
|
||||
link_subtype = csdev->subtype.link_subtype;
|
||||
|
||||
if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG)
|
||||
|
|
@ -168,14 +191,19 @@ static int coresight_enable_link(struct coresight_device *csdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void coresight_disable_link(struct coresight_device *csdev)
|
||||
static void coresight_disable_link(struct coresight_device *csdev,
|
||||
struct coresight_device *parent,
|
||||
struct coresight_device *child)
|
||||
{
|
||||
int i, nr_conns;
|
||||
int link_subtype;
|
||||
int refport, inport, outport;
|
||||
|
||||
inport = coresight_find_link_inport(csdev);
|
||||
outport = coresight_find_link_outport(csdev);
|
||||
if (!parent || !child)
|
||||
return;
|
||||
|
||||
inport = coresight_find_link_inport(csdev, parent);
|
||||
outport = coresight_find_link_outport(csdev, child);
|
||||
link_subtype = csdev->subtype.link_subtype;
|
||||
|
||||
if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) {
|
||||
|
|
@ -201,7 +229,7 @@ static void coresight_disable_link(struct coresight_device *csdev)
|
|||
csdev->enable = false;
|
||||
}
|
||||
|
||||
static int coresight_enable_source(struct coresight_device *csdev)
|
||||
static int coresight_enable_source(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
|
@ -213,7 +241,7 @@ static int coresight_enable_source(struct coresight_device *csdev)
|
|||
|
||||
if (!csdev->enable) {
|
||||
if (source_ops(csdev)->enable) {
|
||||
ret = source_ops(csdev)->enable(csdev);
|
||||
ret = source_ops(csdev)->enable(csdev, NULL, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -235,147 +263,328 @@ static void coresight_disable_source(struct coresight_device *csdev)
|
|||
}
|
||||
}
|
||||
|
||||
static int coresight_enable_path(struct list_head *path)
|
||||
void coresight_disable_path(struct list_head *path)
|
||||
{
|
||||
u32 type;
|
||||
struct coresight_node *nd;
|
||||
struct coresight_device *csdev, *parent, *child;
|
||||
|
||||
list_for_each_entry(nd, path, link) {
|
||||
csdev = nd->csdev;
|
||||
type = csdev->type;
|
||||
|
||||
/*
|
||||
* ETF devices are tricky... They can be a link or a sink,
|
||||
* depending on how they are configured. If an ETF has been
|
||||
* "activated" it will be configured as a sink, otherwise
|
||||
* go ahead with the link configuration.
|
||||
*/
|
||||
if (type == CORESIGHT_DEV_TYPE_LINKSINK)
|
||||
type = (csdev == coresight_get_sink(path)) ?
|
||||
CORESIGHT_DEV_TYPE_SINK :
|
||||
CORESIGHT_DEV_TYPE_LINK;
|
||||
|
||||
switch (type) {
|
||||
case CORESIGHT_DEV_TYPE_SINK:
|
||||
coresight_disable_sink(csdev);
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_SOURCE:
|
||||
/* sources are disabled from either sysFS or Perf */
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_LINK:
|
||||
parent = list_prev_entry(nd, link)->csdev;
|
||||
child = list_next_entry(nd, link)->csdev;
|
||||
coresight_disable_link(csdev, parent, child);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int coresight_enable_path(struct list_head *path, u32 mode)
|
||||
{
|
||||
|
||||
int ret = 0;
|
||||
struct coresight_device *cd;
|
||||
u32 type;
|
||||
struct coresight_node *nd;
|
||||
struct coresight_device *csdev, *parent, *child;
|
||||
|
||||
/*
|
||||
* At this point we have a full @path, from source to sink. The
|
||||
* sink is the first entry and the source the last one. Go through
|
||||
* all the components and enable them one by one.
|
||||
*/
|
||||
list_for_each_entry(cd, path, path_link) {
|
||||
if (cd == list_first_entry(path, struct coresight_device,
|
||||
path_link)) {
|
||||
ret = coresight_enable_sink(cd);
|
||||
} else if (list_is_last(&cd->path_link, path)) {
|
||||
/*
|
||||
* Don't enable the source just yet - this needs to
|
||||
* happen at the very end when all links and sink
|
||||
* along the path have been configured properly.
|
||||
*/
|
||||
;
|
||||
} else {
|
||||
ret = coresight_enable_link(cd);
|
||||
}
|
||||
if (ret)
|
||||
list_for_each_entry_reverse(nd, path, link) {
|
||||
csdev = nd->csdev;
|
||||
type = csdev->type;
|
||||
|
||||
/*
|
||||
* ETF devices are tricky... They can be a link or a sink,
|
||||
* depending on how they are configured. If an ETF has been
|
||||
* "activated" it will be configured as a sink, otherwise
|
||||
* go ahead with the link configuration.
|
||||
*/
|
||||
if (type == CORESIGHT_DEV_TYPE_LINKSINK)
|
||||
type = (csdev == coresight_get_sink(path)) ?
|
||||
CORESIGHT_DEV_TYPE_SINK :
|
||||
CORESIGHT_DEV_TYPE_LINK;
|
||||
|
||||
switch (type) {
|
||||
case CORESIGHT_DEV_TYPE_SINK:
|
||||
ret = coresight_enable_sink(csdev, mode);
|
||||
if (ret)
|
||||
goto err;
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_SOURCE:
|
||||
/* sources are enabled from either sysFS or Perf */
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_LINK:
|
||||
parent = list_prev_entry(nd, link)->csdev;
|
||||
child = list_next_entry(nd, link)->csdev;
|
||||
ret = coresight_enable_link(csdev, parent, child);
|
||||
if (ret)
|
||||
goto err;
|
||||
break;
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
list_for_each_entry_continue_reverse(cd, path, path_link) {
|
||||
if (cd == list_first_entry(path, struct coresight_device,
|
||||
path_link)) {
|
||||
coresight_disable_sink(cd);
|
||||
} else if (list_is_last(&cd->path_link, path)) {
|
||||
;
|
||||
} else {
|
||||
coresight_disable_link(cd);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
err:
|
||||
coresight_disable_path(path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int coresight_disable_path(struct list_head *path)
|
||||
struct coresight_device *coresight_get_sink(struct list_head *path)
|
||||
{
|
||||
struct coresight_device *cd;
|
||||
struct coresight_device *csdev;
|
||||
|
||||
list_for_each_entry_reverse(cd, path, path_link) {
|
||||
if (cd == list_first_entry(path, struct coresight_device,
|
||||
path_link)) {
|
||||
coresight_disable_sink(cd);
|
||||
} else if (list_is_last(&cd->path_link, path)) {
|
||||
/*
|
||||
* The source has already been stopped, no need
|
||||
* to do it again here.
|
||||
*/
|
||||
;
|
||||
} else {
|
||||
coresight_disable_link(cd);
|
||||
}
|
||||
}
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
return 0;
|
||||
csdev = list_last_entry(path, struct coresight_node, link)->csdev;
|
||||
if (csdev->type != CORESIGHT_DEV_TYPE_SINK &&
|
||||
csdev->type != CORESIGHT_DEV_TYPE_LINKSINK)
|
||||
return NULL;
|
||||
|
||||
return csdev;
|
||||
}
|
||||
|
||||
static int coresight_build_paths(struct coresight_device *csdev,
|
||||
struct list_head *path,
|
||||
bool enable)
|
||||
/**
|
||||
* _coresight_build_path - recursively build a path from a @csdev to a sink.
|
||||
* @csdev: The device to start from.
|
||||
* @path: The list to add devices to.
|
||||
*
|
||||
* The tree of Coresight device is traversed until an activated sink is
|
||||
* found. From there the sink is added to the list along with all the
|
||||
* devices that led to that point - the end result is a list from source
|
||||
* to sink. In that list the source is the first device and the sink the
|
||||
* last one.
|
||||
*/
|
||||
static int _coresight_build_path(struct coresight_device *csdev,
|
||||
struct list_head *path)
|
||||
{
|
||||
int i, ret = -EINVAL;
|
||||
int i;
|
||||
bool found = false;
|
||||
struct coresight_node *node;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
list_add(&csdev->path_link, path);
|
||||
|
||||
/* An activated sink has been found. Enqueue the element */
|
||||
if ((csdev->type == CORESIGHT_DEV_TYPE_SINK ||
|
||||
csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) &&
|
||||
csdev->activated) {
|
||||
if (enable)
|
||||
ret = coresight_enable_path(path);
|
||||
else
|
||||
ret = coresight_disable_path(path);
|
||||
} else {
|
||||
for (i = 0; i < csdev->nr_outport; i++) {
|
||||
conn = &csdev->conns[i];
|
||||
if (coresight_build_paths(conn->child_dev,
|
||||
path, enable) == 0)
|
||||
ret = 0;
|
||||
csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) && csdev->activated)
|
||||
goto out;
|
||||
|
||||
/* Not a sink - recursively explore each port found on this element */
|
||||
for (i = 0; i < csdev->nr_outport; i++) {
|
||||
conn = &csdev->conns[i];
|
||||
if (_coresight_build_path(conn->child_dev, path) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (list_first_entry(path, struct coresight_device, path_link) != csdev)
|
||||
dev_err(&csdev->dev, "wrong device in %s\n", __func__);
|
||||
if (!found)
|
||||
return -ENODEV;
|
||||
|
||||
list_del(&csdev->path_link);
|
||||
out:
|
||||
/*
|
||||
* A path from this element to a sink has been found. The elements
|
||||
* leading to the sink are already enqueued, all that is left to do
|
||||
* is tell the PM runtime core we need this element and add a node
|
||||
* for it.
|
||||
*/
|
||||
node = kzalloc(sizeof(struct coresight_node), GFP_KERNEL);
|
||||
if (!node)
|
||||
return -ENOMEM;
|
||||
|
||||
return ret;
|
||||
node->csdev = csdev;
|
||||
list_add(&node->link, path);
|
||||
pm_runtime_get_sync(csdev->dev.parent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct list_head *coresight_build_path(struct coresight_device *csdev)
|
||||
{
|
||||
struct list_head *path;
|
||||
|
||||
path = kzalloc(sizeof(struct list_head), GFP_KERNEL);
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
INIT_LIST_HEAD(path);
|
||||
|
||||
if (_coresight_build_path(csdev, path)) {
|
||||
kfree(path);
|
||||
path = NULL;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* coresight_release_path - release a previously built path.
|
||||
* @path: the path to release.
|
||||
*
|
||||
* Go through all the elements of a path and 1) removed it from the list and
|
||||
* 2) free the memory allocated for each node.
|
||||
*/
|
||||
void coresight_release_path(struct list_head *path)
|
||||
{
|
||||
struct coresight_device *csdev;
|
||||
struct coresight_node *nd, *next;
|
||||
|
||||
list_for_each_entry_safe(nd, next, path, link) {
|
||||
csdev = nd->csdev;
|
||||
|
||||
pm_runtime_put_sync(csdev->dev.parent);
|
||||
list_del(&nd->link);
|
||||
kfree(nd);
|
||||
}
|
||||
|
||||
kfree(path);
|
||||
path = NULL;
|
||||
}
|
||||
|
||||
/** coresight_validate_source - make sure a source has the right credentials
|
||||
* @csdev: the device structure for a source.
|
||||
* @function: the function this was called from.
|
||||
*
|
||||
* Assumes the coresight_mutex is held.
|
||||
*/
|
||||
static int coresight_validate_source(struct coresight_device *csdev,
|
||||
const char *function)
|
||||
{
|
||||
u32 type, subtype;
|
||||
|
||||
type = csdev->type;
|
||||
subtype = csdev->subtype.source_subtype;
|
||||
|
||||
if (type != CORESIGHT_DEV_TYPE_SOURCE) {
|
||||
dev_err(&csdev->dev, "wrong device type in %s\n", function);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_PROC &&
|
||||
subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE) {
|
||||
dev_err(&csdev->dev, "wrong device subtype in %s\n", function);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int coresight_enable(struct coresight_device *csdev)
|
||||
{
|
||||
int ret = 0;
|
||||
LIST_HEAD(path);
|
||||
int cpu, ret = 0;
|
||||
struct list_head *path;
|
||||
|
||||
mutex_lock(&coresight_mutex);
|
||||
if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) {
|
||||
ret = -EINVAL;
|
||||
dev_err(&csdev->dev, "wrong device type in %s\n", __func__);
|
||||
|
||||
ret = coresight_validate_source(csdev, __func__);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (csdev->enable)
|
||||
goto out;
|
||||
|
||||
if (coresight_build_paths(csdev, &path, true)) {
|
||||
dev_err(&csdev->dev, "building path(s) failed\n");
|
||||
path = coresight_build_path(csdev);
|
||||
if (!path) {
|
||||
pr_err("building path(s) failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (coresight_enable_source(csdev))
|
||||
dev_err(&csdev->dev, "source enable failed\n");
|
||||
ret = coresight_enable_path(path, CS_MODE_SYSFS);
|
||||
if (ret)
|
||||
goto err_path;
|
||||
|
||||
ret = coresight_enable_source(csdev, CS_MODE_SYSFS);
|
||||
if (ret)
|
||||
goto err_source;
|
||||
|
||||
switch (csdev->subtype.source_subtype) {
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:
|
||||
/*
|
||||
* When working from sysFS it is important to keep track
|
||||
* of the paths that were created so that they can be
|
||||
* undone in 'coresight_disable()'. Since there can only
|
||||
* be a single session per tracer (when working from sysFS)
|
||||
* a per-cpu variable will do just fine.
|
||||
*/
|
||||
cpu = source_ops(csdev)->cpu_id(csdev);
|
||||
per_cpu(tracer_path, cpu) = path;
|
||||
break;
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:
|
||||
stm_path = path;
|
||||
break;
|
||||
default:
|
||||
/* We can't be here */
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&coresight_mutex);
|
||||
return ret;
|
||||
|
||||
err_source:
|
||||
coresight_disable_path(path);
|
||||
|
||||
err_path:
|
||||
coresight_release_path(path);
|
||||
goto out;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_enable);
|
||||
|
||||
void coresight_disable(struct coresight_device *csdev)
|
||||
{
|
||||
LIST_HEAD(path);
|
||||
int cpu, ret;
|
||||
struct list_head *path = NULL;
|
||||
|
||||
mutex_lock(&coresight_mutex);
|
||||
if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) {
|
||||
dev_err(&csdev->dev, "wrong device type in %s\n", __func__);
|
||||
|
||||
ret = coresight_validate_source(csdev, __func__);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!csdev->enable)
|
||||
goto out;
|
||||
|
||||
switch (csdev->subtype.source_subtype) {
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:
|
||||
cpu = source_ops(csdev)->cpu_id(csdev);
|
||||
path = per_cpu(tracer_path, cpu);
|
||||
per_cpu(tracer_path, cpu) = NULL;
|
||||
break;
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:
|
||||
path = stm_path;
|
||||
stm_path = NULL;
|
||||
break;
|
||||
default:
|
||||
/* We can't be here */
|
||||
break;
|
||||
}
|
||||
|
||||
coresight_disable_source(csdev);
|
||||
if (coresight_build_paths(csdev, &path, false))
|
||||
dev_err(&csdev->dev, "releasing path(s) failed\n");
|
||||
coresight_disable_path(path);
|
||||
coresight_release_path(path);
|
||||
|
||||
out:
|
||||
mutex_unlock(&coresight_mutex);
|
||||
|
|
@ -387,7 +596,7 @@ static ssize_t enable_sink_show(struct device *dev,
|
|||
{
|
||||
struct coresight_device *csdev = to_coresight_device(dev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", (unsigned)csdev->activated);
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", csdev->activated);
|
||||
}
|
||||
|
||||
static ssize_t enable_sink_store(struct device *dev,
|
||||
|
|
@ -417,7 +626,7 @@ static ssize_t enable_source_show(struct device *dev,
|
|||
{
|
||||
struct coresight_device *csdev = to_coresight_device(dev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", (unsigned)csdev->enable);
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", csdev->enable);
|
||||
}
|
||||
|
||||
static ssize_t enable_source_store(struct device *dev,
|
||||
|
|
@ -481,6 +690,8 @@ static void coresight_device_release(struct device *dev)
|
|||
{
|
||||
struct coresight_device *csdev = to_coresight_device(dev);
|
||||
|
||||
kfree(csdev->conns);
|
||||
kfree(csdev->refcnt);
|
||||
kfree(csdev);
|
||||
}
|
||||
|
||||
|
|
@ -536,7 +747,7 @@ static void coresight_fixup_orphan_conns(struct coresight_device *csdev)
|
|||
* are hooked-up with each newly added component.
|
||||
*/
|
||||
bus_for_each_dev(&coresight_bustype, NULL,
|
||||
csdev, coresight_orphan_match);
|
||||
csdev, coresight_orphan_match);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -568,6 +779,8 @@ static void coresight_fixup_device_conns(struct coresight_device *csdev)
|
|||
|
||||
if (dev) {
|
||||
conn->child_dev = to_coresight_device(dev);
|
||||
/* and put reference from 'bus_find_device()' */
|
||||
put_device(dev);
|
||||
} else {
|
||||
csdev->orphan = true;
|
||||
conn->child_dev = NULL;
|
||||
|
|
@ -575,6 +788,50 @@ static void coresight_fixup_device_conns(struct coresight_device *csdev)
|
|||
}
|
||||
}
|
||||
|
||||
static int coresight_remove_match(struct device *dev, void *data)
|
||||
{
|
||||
int i;
|
||||
struct coresight_device *csdev, *iterator;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
csdev = data;
|
||||
iterator = to_coresight_device(dev);
|
||||
|
||||
/* No need to check oneself */
|
||||
if (csdev == iterator)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Circle throuch all the connection of that component. If we find
|
||||
* a connection whose name matches @csdev, remove it.
|
||||
*/
|
||||
for (i = 0; i < iterator->nr_outport; i++) {
|
||||
conn = &iterator->conns[i];
|
||||
|
||||
if (conn->child_dev == NULL)
|
||||
continue;
|
||||
|
||||
if (!strcmp(dev_name(&csdev->dev), conn->child_name)) {
|
||||
iterator->orphan = true;
|
||||
conn->child_dev = NULL;
|
||||
/* No need to continue */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returning '0' ensures that all known component on the
|
||||
* bus will be checked.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void coresight_remove_conns(struct coresight_device *csdev)
|
||||
{
|
||||
bus_for_each_dev(&coresight_bustype, NULL,
|
||||
csdev, coresight_remove_match);
|
||||
}
|
||||
|
||||
/**
|
||||
* coresight_timeout - loop until a bit has changed to a specific state.
|
||||
* @addr: base address of the area of interest.
|
||||
|
|
@ -713,13 +970,8 @@ EXPORT_SYMBOL_GPL(coresight_register);
|
|||
|
||||
void coresight_unregister(struct coresight_device *csdev)
|
||||
{
|
||||
mutex_lock(&coresight_mutex);
|
||||
|
||||
kfree(csdev->conns);
|
||||
/* Remove references of that device in the topology */
|
||||
coresight_remove_conns(csdev);
|
||||
device_unregister(&csdev->dev);
|
||||
|
||||
mutex_unlock(&coresight_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_unregister);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
|
|
@ -86,7 +85,7 @@ static int of_coresight_alloc_memory(struct device *dev,
|
|||
return -ENOMEM;
|
||||
|
||||
/* Children connected to this component via @outports */
|
||||
pdata->child_names = devm_kzalloc(dev, pdata->nr_outport *
|
||||
pdata->child_names = devm_kzalloc(dev, pdata->nr_outport *
|
||||
sizeof(*pdata->child_names),
|
||||
GFP_KERNEL);
|
||||
if (!pdata->child_names)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ config STM
|
|||
|
||||
Say Y here to enable System Trace Module device support.
|
||||
|
||||
if STM
|
||||
|
||||
config STM_DUMMY
|
||||
tristate "Dummy STM driver"
|
||||
help
|
||||
|
|
@ -25,3 +27,16 @@ config STM_SOURCE_CONSOLE
|
|||
|
||||
If you want to send kernel console messages over STM devices,
|
||||
say Y.
|
||||
|
||||
config STM_SOURCE_HEARTBEAT
|
||||
tristate "Heartbeat over STM devices"
|
||||
help
|
||||
This is a kernel space trace source that sends periodic
|
||||
heartbeat messages to trace hosts over STM devices. It is
|
||||
also useful for testing stm class drivers and the stm class
|
||||
framework itself.
|
||||
|
||||
If you want to send heartbeat messages over STM devices,
|
||||
say Y.
|
||||
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -5,5 +5,7 @@ stm_core-y := core.o policy.o
|
|||
obj-$(CONFIG_STM_DUMMY) += dummy_stm.o
|
||||
|
||||
obj-$(CONFIG_STM_SOURCE_CONSOLE) += stm_console.o
|
||||
obj-$(CONFIG_STM_SOURCE_HEARTBEAT) += stm_heartbeat.o
|
||||
|
||||
stm_console-y := console.o
|
||||
stm_heartbeat-y := heartbeat.o
|
||||
|
|
|
|||
|
|
@ -67,9 +67,24 @@ static ssize_t channels_show(struct device *dev,
|
|||
|
||||
static DEVICE_ATTR_RO(channels);
|
||||
|
||||
static ssize_t hw_override_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct stm_device *stm = to_stm_device(dev);
|
||||
int ret;
|
||||
|
||||
ret = sprintf(buf, "%u\n", stm->data->hw_override);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(hw_override);
|
||||
|
||||
static struct attribute *stm_attrs[] = {
|
||||
&dev_attr_masters.attr,
|
||||
&dev_attr_channels.attr,
|
||||
&dev_attr_hw_override.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
|
@ -113,6 +128,7 @@ struct stm_device *stm_find_device(const char *buf)
|
|||
|
||||
stm = to_stm_device(dev);
|
||||
if (!try_module_get(stm->owner)) {
|
||||
/* matches class_find_device() above */
|
||||
put_device(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -125,7 +141,7 @@ struct stm_device *stm_find_device(const char *buf)
|
|||
* @stm: stm device, previously acquired by stm_find_device()
|
||||
*
|
||||
* This drops the module reference and device reference taken by
|
||||
* stm_find_device().
|
||||
* stm_find_device() or stm_char_open().
|
||||
*/
|
||||
void stm_put_device(struct stm_device *stm)
|
||||
{
|
||||
|
|
@ -185,6 +201,9 @@ static void stm_output_claim(struct stm_device *stm, struct stm_output *output)
|
|||
{
|
||||
struct stp_master *master = stm_master(stm, output->master);
|
||||
|
||||
lockdep_assert_held(&stm->mc_lock);
|
||||
lockdep_assert_held(&output->lock);
|
||||
|
||||
if (WARN_ON_ONCE(master->nr_free < output->nr_chans))
|
||||
return;
|
||||
|
||||
|
|
@ -199,6 +218,9 @@ stm_output_disclaim(struct stm_device *stm, struct stm_output *output)
|
|||
{
|
||||
struct stp_master *master = stm_master(stm, output->master);
|
||||
|
||||
lockdep_assert_held(&stm->mc_lock);
|
||||
lockdep_assert_held(&output->lock);
|
||||
|
||||
bitmap_release_region(&master->chan_map[0], output->channel,
|
||||
ilog2(output->nr_chans));
|
||||
|
||||
|
|
@ -288,6 +310,7 @@ static int stm_output_assign(struct stm_device *stm, unsigned int width,
|
|||
}
|
||||
|
||||
spin_lock(&stm->mc_lock);
|
||||
spin_lock(&output->lock);
|
||||
/* output is already assigned -- shouldn't happen */
|
||||
if (WARN_ON_ONCE(output->nr_chans))
|
||||
goto unlock;
|
||||
|
|
@ -304,6 +327,7 @@ static int stm_output_assign(struct stm_device *stm, unsigned int width,
|
|||
|
||||
ret = 0;
|
||||
unlock:
|
||||
spin_unlock(&output->lock);
|
||||
spin_unlock(&stm->mc_lock);
|
||||
|
||||
return ret;
|
||||
|
|
@ -312,11 +336,18 @@ static int stm_output_assign(struct stm_device *stm, unsigned int width,
|
|||
static void stm_output_free(struct stm_device *stm, struct stm_output *output)
|
||||
{
|
||||
spin_lock(&stm->mc_lock);
|
||||
spin_lock(&output->lock);
|
||||
if (output->nr_chans)
|
||||
stm_output_disclaim(stm, output);
|
||||
spin_unlock(&output->lock);
|
||||
spin_unlock(&stm->mc_lock);
|
||||
}
|
||||
|
||||
static void stm_output_init(struct stm_output *output)
|
||||
{
|
||||
spin_lock_init(&output->lock);
|
||||
}
|
||||
|
||||
static int major_match(struct device *dev, const void *data)
|
||||
{
|
||||
unsigned int major = *(unsigned int *)data;
|
||||
|
|
@ -339,6 +370,7 @@ static int stm_char_open(struct inode *inode, struct file *file)
|
|||
if (!stmf)
|
||||
return -ENOMEM;
|
||||
|
||||
stm_output_init(&stmf->output);
|
||||
stmf->stm = to_stm_device(dev);
|
||||
|
||||
if (!try_module_get(stmf->stm->owner))
|
||||
|
|
@ -349,6 +381,8 @@ static int stm_char_open(struct inode *inode, struct file *file)
|
|||
return nonseekable_open(inode, file);
|
||||
|
||||
err_free:
|
||||
/* matches class_find_device() above */
|
||||
put_device(dev);
|
||||
kfree(stmf);
|
||||
|
||||
return err;
|
||||
|
|
@ -357,9 +391,19 @@ static int stm_char_open(struct inode *inode, struct file *file)
|
|||
static int stm_char_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct stm_file *stmf = file->private_data;
|
||||
struct stm_device *stm = stmf->stm;
|
||||
|
||||
stm_output_free(stmf->stm, &stmf->output);
|
||||
stm_put_device(stmf->stm);
|
||||
if (stm->data->unlink)
|
||||
stm->data->unlink(stm->data, stmf->output.master,
|
||||
stmf->output.channel);
|
||||
|
||||
stm_output_free(stm, &stmf->output);
|
||||
|
||||
/*
|
||||
* matches the stm_char_open()'s
|
||||
* class_find_device() + try_module_get()
|
||||
*/
|
||||
stm_put_device(stm);
|
||||
kfree(stmf);
|
||||
|
||||
return 0;
|
||||
|
|
@ -380,8 +424,8 @@ static int stm_file_assign(struct stm_file *stmf, char *id, unsigned int width)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void stm_write(struct stm_data *data, unsigned int master,
|
||||
unsigned int channel, const char *buf, size_t count)
|
||||
static ssize_t stm_write(struct stm_data *data, unsigned int master,
|
||||
unsigned int channel, const char *buf, size_t count)
|
||||
{
|
||||
unsigned int flags = STP_PACKET_TIMESTAMPED;
|
||||
const unsigned char *p = buf, nil = 0;
|
||||
|
|
@ -393,9 +437,14 @@ static void stm_write(struct stm_data *data, unsigned int master,
|
|||
sz = data->packet(data, master, channel, STP_PACKET_DATA, flags,
|
||||
sz, p);
|
||||
flags = 0;
|
||||
|
||||
if (sz < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
data->packet(data, master, channel, STP_PACKET_FLAG, 0, 0, &nil);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
||||
|
|
@ -406,6 +455,9 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
|||
char *kbuf;
|
||||
int err;
|
||||
|
||||
if (count + 1 > PAGE_SIZE)
|
||||
count = PAGE_SIZE - 1;
|
||||
|
||||
/*
|
||||
* if no m/c have been assigned to this writer up to this
|
||||
* point, use "default" policy entry
|
||||
|
|
@ -430,8 +482,8 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
|||
return -EFAULT;
|
||||
}
|
||||
|
||||
stm_write(stm->data, stmf->output.master, stmf->output.channel, kbuf,
|
||||
count);
|
||||
count = stm_write(stm->data, stmf->output.master, stmf->output.channel,
|
||||
kbuf, count);
|
||||
|
||||
kfree(kbuf);
|
||||
|
||||
|
|
@ -509,16 +561,12 @@ static int stm_char_policy_set_ioctl(struct stm_file *stmf, void __user *arg)
|
|||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
ret = 0;
|
||||
|
||||
if (stm->data->link)
|
||||
ret = stm->data->link(stm->data, stmf->output.master,
|
||||
stmf->output.channel);
|
||||
|
||||
if (ret) {
|
||||
if (ret)
|
||||
stm_output_free(stmf->stm, &stmf->output);
|
||||
stm_put_device(stmf->stm);
|
||||
}
|
||||
|
||||
err_free:
|
||||
kfree(id);
|
||||
|
|
@ -633,6 +681,18 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data,
|
|||
stm->dev.parent = parent;
|
||||
stm->dev.release = stm_device_release;
|
||||
|
||||
mutex_init(&stm->link_mutex);
|
||||
spin_lock_init(&stm->link_lock);
|
||||
INIT_LIST_HEAD(&stm->link_list);
|
||||
|
||||
/* initialize the object before it is accessible via sysfs */
|
||||
spin_lock_init(&stm->mc_lock);
|
||||
mutex_init(&stm->policy_mutex);
|
||||
stm->sw_nmasters = nmasters;
|
||||
stm->owner = owner;
|
||||
stm->data = stm_data;
|
||||
stm_data->stm = stm;
|
||||
|
||||
err = kobject_set_name(&stm->dev.kobj, "%s", stm_data->name);
|
||||
if (err)
|
||||
goto err_device;
|
||||
|
|
@ -641,19 +701,12 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data,
|
|||
if (err)
|
||||
goto err_device;
|
||||
|
||||
spin_lock_init(&stm->link_lock);
|
||||
INIT_LIST_HEAD(&stm->link_list);
|
||||
|
||||
spin_lock_init(&stm->mc_lock);
|
||||
mutex_init(&stm->policy_mutex);
|
||||
stm->sw_nmasters = nmasters;
|
||||
stm->owner = owner;
|
||||
stm->data = stm_data;
|
||||
stm_data->stm = stm;
|
||||
|
||||
return 0;
|
||||
|
||||
err_device:
|
||||
unregister_chrdev(stm->major, stm_data->name);
|
||||
|
||||
/* matches device_initialize() above */
|
||||
put_device(&stm->dev);
|
||||
err_free:
|
||||
kfree(stm);
|
||||
|
|
@ -662,20 +715,28 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(stm_register_device);
|
||||
|
||||
static void __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm);
|
||||
static int __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm);
|
||||
|
||||
void stm_unregister_device(struct stm_data *stm_data)
|
||||
{
|
||||
struct stm_device *stm = stm_data->stm;
|
||||
struct stm_source_device *src, *iter;
|
||||
int i;
|
||||
int i, ret;
|
||||
|
||||
spin_lock(&stm->link_lock);
|
||||
mutex_lock(&stm->link_mutex);
|
||||
list_for_each_entry_safe(src, iter, &stm->link_list, link_entry) {
|
||||
__stm_source_link_drop(src, stm);
|
||||
ret = __stm_source_link_drop(src, stm);
|
||||
/*
|
||||
* src <-> stm link must not change under the same
|
||||
* stm::link_mutex, so complain loudly if it has;
|
||||
* also in this situation ret!=0 means this src is
|
||||
* not connected to this stm and it should be otherwise
|
||||
* safe to proceed with the tear-down of stm.
|
||||
*/
|
||||
WARN_ON_ONCE(ret);
|
||||
}
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
|
||||
synchronize_srcu(&stm_source_srcu);
|
||||
|
||||
|
|
@ -694,6 +755,17 @@ void stm_unregister_device(struct stm_data *stm_data)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(stm_unregister_device);
|
||||
|
||||
/*
|
||||
* stm::link_list access serialization uses a spinlock and a mutex; holding
|
||||
* either of them guarantees that the list is stable; modification requires
|
||||
* holding both of them.
|
||||
*
|
||||
* Lock ordering is as follows:
|
||||
* stm::link_mutex
|
||||
* stm::link_lock
|
||||
* src::link_lock
|
||||
*/
|
||||
|
||||
/**
|
||||
* stm_source_link_add() - connect an stm_source device to an stm device
|
||||
* @src: stm_source device
|
||||
|
|
@ -710,6 +782,7 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
char *id;
|
||||
int err;
|
||||
|
||||
mutex_lock(&stm->link_mutex);
|
||||
spin_lock(&stm->link_lock);
|
||||
spin_lock(&src->link_lock);
|
||||
|
||||
|
|
@ -719,6 +792,7 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
|
||||
spin_unlock(&src->link_lock);
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
|
||||
id = kstrdup(src->data->name, GFP_KERNEL);
|
||||
if (id) {
|
||||
|
|
@ -753,9 +827,9 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
|
||||
fail_free_output:
|
||||
stm_output_free(stm, &src->output);
|
||||
stm_put_device(stm);
|
||||
|
||||
fail_detach:
|
||||
mutex_lock(&stm->link_mutex);
|
||||
spin_lock(&stm->link_lock);
|
||||
spin_lock(&src->link_lock);
|
||||
|
||||
|
|
@ -764,6 +838,7 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
|
||||
spin_unlock(&src->link_lock);
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
@ -776,28 +851,55 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
* If @stm is @src::link, disconnect them from one another and put the
|
||||
* reference on the @stm device.
|
||||
*
|
||||
* Caller must hold stm::link_lock.
|
||||
* Caller must hold stm::link_mutex.
|
||||
*/
|
||||
static void __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm)
|
||||
static int __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm)
|
||||
{
|
||||
struct stm_device *link;
|
||||
int ret = 0;
|
||||
|
||||
lockdep_assert_held(&stm->link_mutex);
|
||||
|
||||
/* for stm::link_list modification, we hold both mutex and spinlock */
|
||||
spin_lock(&stm->link_lock);
|
||||
spin_lock(&src->link_lock);
|
||||
link = srcu_dereference_check(src->link, &stm_source_srcu, 1);
|
||||
if (WARN_ON_ONCE(link != stm)) {
|
||||
spin_unlock(&src->link_lock);
|
||||
return;
|
||||
|
||||
/*
|
||||
* The linked device may have changed since we last looked, because
|
||||
* we weren't holding the src::link_lock back then; if this is the
|
||||
* case, tell the caller to retry.
|
||||
*/
|
||||
if (link != stm) {
|
||||
ret = -EAGAIN;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
stm_output_free(link, &src->output);
|
||||
/* caller must hold stm::link_lock */
|
||||
list_del_init(&src->link_entry);
|
||||
/* matches stm_find_device() from stm_source_link_store() */
|
||||
stm_put_device(link);
|
||||
rcu_assign_pointer(src->link, NULL);
|
||||
|
||||
unlock:
|
||||
spin_unlock(&src->link_lock);
|
||||
spin_unlock(&stm->link_lock);
|
||||
|
||||
/*
|
||||
* Call the unlink callbacks for both source and stm, when we know
|
||||
* that we have actually performed the unlinking.
|
||||
*/
|
||||
if (!ret) {
|
||||
if (src->data->unlink)
|
||||
src->data->unlink(src->data);
|
||||
|
||||
if (stm->data->unlink)
|
||||
stm->data->unlink(stm->data, src->output.master,
|
||||
src->output.channel);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -813,21 +915,29 @@ static void __stm_source_link_drop(struct stm_source_device *src,
|
|||
static void stm_source_link_drop(struct stm_source_device *src)
|
||||
{
|
||||
struct stm_device *stm;
|
||||
int idx;
|
||||
int idx, ret;
|
||||
|
||||
retry:
|
||||
idx = srcu_read_lock(&stm_source_srcu);
|
||||
/*
|
||||
* The stm device will be valid for the duration of this
|
||||
* read section, but the link may change before we grab
|
||||
* the src::link_lock in __stm_source_link_drop().
|
||||
*/
|
||||
stm = srcu_dereference(src->link, &stm_source_srcu);
|
||||
|
||||
ret = 0;
|
||||
if (stm) {
|
||||
if (src->data->unlink)
|
||||
src->data->unlink(src->data);
|
||||
|
||||
spin_lock(&stm->link_lock);
|
||||
__stm_source_link_drop(src, stm);
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_lock(&stm->link_mutex);
|
||||
ret = __stm_source_link_drop(src, stm);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
}
|
||||
|
||||
srcu_read_unlock(&stm_source_srcu, idx);
|
||||
|
||||
/* if it did change, retry */
|
||||
if (ret == -EAGAIN)
|
||||
goto retry;
|
||||
}
|
||||
|
||||
static ssize_t stm_source_link_show(struct device *dev,
|
||||
|
|
@ -862,8 +972,10 @@ static ssize_t stm_source_link_store(struct device *dev,
|
|||
return -EINVAL;
|
||||
|
||||
err = stm_source_link_add(src, link);
|
||||
if (err)
|
||||
if (err) {
|
||||
/* matches the stm_find_device() above */
|
||||
stm_put_device(link);
|
||||
}
|
||||
|
||||
return err ? : count;
|
||||
}
|
||||
|
|
@ -925,6 +1037,7 @@ int stm_source_register_device(struct device *parent,
|
|||
if (err)
|
||||
goto err;
|
||||
|
||||
stm_output_init(&src->output);
|
||||
spin_lock_init(&src->link_lock);
|
||||
INIT_LIST_HEAD(&src->link_entry);
|
||||
src->data = data;
|
||||
|
|
@ -973,9 +1086,9 @@ int stm_source_write(struct stm_source_data *data, unsigned int chan,
|
|||
|
||||
stm = srcu_dereference(src->link, &stm_source_srcu);
|
||||
if (stm)
|
||||
stm_write(stm->data, src->output.master,
|
||||
src->output.channel + chan,
|
||||
buf, count);
|
||||
count = stm_write(stm->data, src->output.master,
|
||||
src->output.channel + chan,
|
||||
buf, count);
|
||||
else
|
||||
count = -ENODEV;
|
||||
|
||||
|
|
|
|||
|
|
@ -40,22 +40,71 @@ dummy_stm_packet(struct stm_data *stm_data, unsigned int master,
|
|||
return size;
|
||||
}
|
||||
|
||||
static struct stm_data dummy_stm = {
|
||||
.name = "dummy_stm",
|
||||
.sw_start = 0x0000,
|
||||
.sw_end = 0xffff,
|
||||
.sw_nchannels = 0xffff,
|
||||
.packet = dummy_stm_packet,
|
||||
};
|
||||
#define DUMMY_STM_MAX 32
|
||||
|
||||
static struct stm_data dummy_stm[DUMMY_STM_MAX];
|
||||
|
||||
static int nr_dummies = 4;
|
||||
|
||||
module_param(nr_dummies, int, 0400);
|
||||
|
||||
static unsigned int fail_mode;
|
||||
|
||||
module_param(fail_mode, int, 0600);
|
||||
|
||||
static int dummy_stm_link(struct stm_data *data, unsigned int master,
|
||||
unsigned int channel)
|
||||
{
|
||||
if (fail_mode && (channel & fail_mode))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy_stm_init(void)
|
||||
{
|
||||
return stm_register_device(NULL, &dummy_stm, THIS_MODULE);
|
||||
int i, ret = -ENOMEM;
|
||||
|
||||
if (nr_dummies < 0 || nr_dummies > DUMMY_STM_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < nr_dummies; i++) {
|
||||
dummy_stm[i].name = kasprintf(GFP_KERNEL, "dummy_stm.%d", i);
|
||||
if (!dummy_stm[i].name)
|
||||
goto fail_unregister;
|
||||
|
||||
dummy_stm[i].sw_start = 0x0000;
|
||||
dummy_stm[i].sw_end = 0xffff;
|
||||
dummy_stm[i].sw_nchannels = 0xffff;
|
||||
dummy_stm[i].packet = dummy_stm_packet;
|
||||
dummy_stm[i].link = dummy_stm_link;
|
||||
|
||||
ret = stm_register_device(NULL, &dummy_stm[i], THIS_MODULE);
|
||||
if (ret)
|
||||
goto fail_free;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_unregister:
|
||||
for (i--; i >= 0; i--) {
|
||||
stm_unregister_device(&dummy_stm[i]);
|
||||
fail_free:
|
||||
kfree(dummy_stm[i].name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static void dummy_stm_exit(void)
|
||||
{
|
||||
stm_unregister_device(&dummy_stm);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_dummies; i++) {
|
||||
stm_unregister_device(&dummy_stm[i]);
|
||||
kfree(dummy_stm[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(dummy_stm_init);
|
||||
|
|
|
|||
126
drivers/hwtracing/stm/heartbeat.c
Normal file
126
drivers/hwtracing/stm/heartbeat.c
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Simple heartbeat STM source driver
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* Heartbeat STM source will send repetitive messages over STM devices to a
|
||||
* trace host.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stm.h>
|
||||
|
||||
#define STM_HEARTBEAT_MAX 32
|
||||
|
||||
static int nr_devs = 4;
|
||||
static int interval_ms = 10;
|
||||
|
||||
module_param(nr_devs, int, 0400);
|
||||
module_param(interval_ms, int, 0600);
|
||||
|
||||
static struct stm_heartbeat {
|
||||
struct stm_source_data data;
|
||||
struct hrtimer hrtimer;
|
||||
unsigned int active;
|
||||
} stm_heartbeat[STM_HEARTBEAT_MAX];
|
||||
|
||||
static const char str[] = "heartbeat stm source driver is here to serve you";
|
||||
|
||||
static enum hrtimer_restart stm_heartbeat_hrtimer_handler(struct hrtimer *hr)
|
||||
{
|
||||
struct stm_heartbeat *heartbeat = container_of(hr, struct stm_heartbeat,
|
||||
hrtimer);
|
||||
|
||||
stm_source_write(&heartbeat->data, 0, str, sizeof str);
|
||||
if (heartbeat->active)
|
||||
hrtimer_forward_now(hr, ms_to_ktime(interval_ms));
|
||||
|
||||
return heartbeat->active ? HRTIMER_RESTART : HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static int stm_heartbeat_link(struct stm_source_data *data)
|
||||
{
|
||||
struct stm_heartbeat *heartbeat =
|
||||
container_of(data, struct stm_heartbeat, data);
|
||||
|
||||
heartbeat->active = 1;
|
||||
hrtimer_start(&heartbeat->hrtimer, ms_to_ktime(interval_ms),
|
||||
HRTIMER_MODE_ABS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm_heartbeat_unlink(struct stm_source_data *data)
|
||||
{
|
||||
struct stm_heartbeat *heartbeat =
|
||||
container_of(data, struct stm_heartbeat, data);
|
||||
|
||||
heartbeat->active = 0;
|
||||
hrtimer_cancel(&heartbeat->hrtimer);
|
||||
}
|
||||
|
||||
static int stm_heartbeat_init(void)
|
||||
{
|
||||
int i, ret = -ENOMEM;
|
||||
|
||||
if (nr_devs < 0 || nr_devs > STM_HEARTBEAT_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < nr_devs; i++) {
|
||||
stm_heartbeat[i].data.name =
|
||||
kasprintf(GFP_KERNEL, "heartbeat.%d", i);
|
||||
if (!stm_heartbeat[i].data.name)
|
||||
goto fail_unregister;
|
||||
|
||||
stm_heartbeat[i].data.nr_chans = 1;
|
||||
stm_heartbeat[i].data.link = stm_heartbeat_link;
|
||||
stm_heartbeat[i].data.unlink = stm_heartbeat_unlink;
|
||||
hrtimer_init(&stm_heartbeat[i].hrtimer, CLOCK_MONOTONIC,
|
||||
HRTIMER_MODE_ABS);
|
||||
stm_heartbeat[i].hrtimer.function =
|
||||
stm_heartbeat_hrtimer_handler;
|
||||
|
||||
ret = stm_source_register_device(NULL, &stm_heartbeat[i].data);
|
||||
if (ret)
|
||||
goto fail_free;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_unregister:
|
||||
for (i--; i >= 0; i--) {
|
||||
stm_source_unregister_device(&stm_heartbeat[i].data);
|
||||
fail_free:
|
||||
kfree(stm_heartbeat[i].data.name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void stm_heartbeat_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_devs; i++) {
|
||||
stm_source_unregister_device(&stm_heartbeat[i].data);
|
||||
kfree(stm_heartbeat[i].data.name);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(stm_heartbeat_init);
|
||||
module_exit(stm_heartbeat_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("stm_heartbeat driver");
|
||||
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>");
|
||||
|
|
@ -272,13 +272,17 @@ void stp_policy_unbind(struct stp_policy *policy)
|
|||
{
|
||||
struct stm_device *stm = policy->stm;
|
||||
|
||||
/*
|
||||
* stp_policy_release() will not call here if the policy is already
|
||||
* unbound; other users should not either, as no link exists between
|
||||
* this policy and anything else in that case
|
||||
*/
|
||||
if (WARN_ON_ONCE(!policy->stm))
|
||||
return;
|
||||
|
||||
mutex_lock(&stm->policy_mutex);
|
||||
stm->policy = NULL;
|
||||
mutex_unlock(&stm->policy_mutex);
|
||||
lockdep_assert_held(&stm->policy_mutex);
|
||||
|
||||
stm->policy = NULL;
|
||||
policy->stm = NULL;
|
||||
|
||||
stm_put_device(stm);
|
||||
|
|
@ -287,8 +291,16 @@ void stp_policy_unbind(struct stp_policy *policy)
|
|||
static void stp_policy_release(struct config_item *item)
|
||||
{
|
||||
struct stp_policy *policy = to_stp_policy(item);
|
||||
struct stm_device *stm = policy->stm;
|
||||
|
||||
/* a policy *can* be unbound and still exist in configfs tree */
|
||||
if (!stm)
|
||||
return;
|
||||
|
||||
mutex_lock(&stm->policy_mutex);
|
||||
stp_policy_unbind(policy);
|
||||
mutex_unlock(&stm->policy_mutex);
|
||||
|
||||
kfree(policy);
|
||||
}
|
||||
|
||||
|
|
@ -320,16 +332,17 @@ stp_policies_make(struct config_group *group, const char *name)
|
|||
|
||||
/*
|
||||
* node must look like <device_name>.<policy_name>, where
|
||||
* <device_name> is the name of an existing stm device and
|
||||
* <policy_name> is an arbitrary string
|
||||
* <device_name> is the name of an existing stm device; may
|
||||
* contain dots;
|
||||
* <policy_name> is an arbitrary string; may not contain dots
|
||||
*/
|
||||
p = strchr(devname, '.');
|
||||
p = strrchr(devname, '.');
|
||||
if (!p) {
|
||||
kfree(devname);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
*p++ = '\0';
|
||||
*p = '\0';
|
||||
|
||||
stm = stm_find_device(devname);
|
||||
kfree(devname);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ struct stm_device {
|
|||
int major;
|
||||
unsigned int sw_nmasters;
|
||||
struct stm_data *data;
|
||||
struct mutex link_mutex;
|
||||
spinlock_t link_lock;
|
||||
struct list_head link_list;
|
||||
/* master allocation */
|
||||
|
|
@ -56,6 +57,7 @@ struct stm_device {
|
|||
container_of((_d), struct stm_device, dev)
|
||||
|
||||
struct stm_output {
|
||||
spinlock_t lock;
|
||||
unsigned int master;
|
||||
unsigned int channel;
|
||||
unsigned int nr_chans;
|
||||
|
|
|
|||
|
|
@ -163,4 +163,13 @@ struct amba_device name##_device = { \
|
|||
#define module_amba_driver(__amba_drv) \
|
||||
module_driver(__amba_drv, amba_driver_register, amba_driver_unregister)
|
||||
|
||||
/*
|
||||
* builtin_amba_driver() - Helper macro for drivers that don't do anything
|
||||
* special in driver initcall. This eliminates a lot of boilerplate. Each
|
||||
* driver may only use this macro once, and calling it replaces the instance
|
||||
* device_initcall().
|
||||
*/
|
||||
#define builtin_amba_driver(__amba_drv) \
|
||||
builtin_driver(__amba_drv, amba_driver_register)
|
||||
|
||||
#endif
|
||||
|
|
|
|||
39
include/linux/coresight-pmu.h
Normal file
39
include/linux/coresight-pmu.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright(C) 2015 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_CORESIGHT_PMU_H
|
||||
#define _LINUX_CORESIGHT_PMU_H
|
||||
|
||||
#define CORESIGHT_ETM_PMU_NAME "cs_etm"
|
||||
#define CORESIGHT_ETM_PMU_SEED 0x10
|
||||
|
||||
/* ETMv3.5/PTM's ETMCR config bit */
|
||||
#define ETM_OPT_CYCACC 12
|
||||
#define ETM_OPT_TS 28
|
||||
|
||||
static inline int coresight_get_trace_id(int cpu)
|
||||
{
|
||||
/*
|
||||
* A trace ID of value 0 is invalid, so let's start at some
|
||||
* random value that fits in 7 bits and go from there. Since
|
||||
* the common convention is to have data trace IDs be I(N) + 1,
|
||||
* set instruction trace IDs as a function of the CPU number.
|
||||
*/
|
||||
return (CORESIGHT_ETM_PMU_SEED + (cpu * 2));
|
||||
}
|
||||
|
||||
#endif
|
||||
6
include/linux/coresight-stm.h
Normal file
6
include/linux/coresight-stm.h
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef __LINUX_CORESIGHT_STM_H_
|
||||
#define __LINUX_CORESIGHT_STM_H_
|
||||
|
||||
#include <uapi/linux/coresight-stm.h>
|
||||
|
||||
#endif
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
#define _LINUX_CORESIGHT_H
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
/* Peripheral id registers (0xFD0-0xFEC) */
|
||||
|
|
@ -152,7 +153,6 @@ struct coresight_connection {
|
|||
by @coresight_ops.
|
||||
* @dev: The device entity associated to this component.
|
||||
* @refcnt: keep track of what is in use.
|
||||
* @path_link: link of current component into the path being enabled.
|
||||
* @orphan: true if the component has connections that haven't been linked.
|
||||
* @enable: 'true' if component is currently part of an active path.
|
||||
* @activated: 'true' only if a _sink_ has been activated. A sink can be
|
||||
|
|
@ -168,7 +168,6 @@ struct coresight_device {
|
|||
const struct coresight_ops *ops;
|
||||
struct device dev;
|
||||
atomic_t *refcnt;
|
||||
struct list_head path_link;
|
||||
bool orphan;
|
||||
bool enable; /* true only if configured as part of a path */
|
||||
bool activated; /* true only if a sink is part of a path */
|
||||
|
|
@ -183,12 +182,29 @@ struct coresight_device {
|
|||
/**
|
||||
* struct coresight_ops_sink - basic operations for a sink
|
||||
* Operations available for sinks
|
||||
* @enable: enables the sink.
|
||||
* @disable: disables the sink.
|
||||
* @enable: enables the sink.
|
||||
* @disable: disables the sink.
|
||||
* @alloc_buffer: initialises perf's ring buffer for trace collection.
|
||||
* @free_buffer: release memory allocated in @get_config.
|
||||
* @set_buffer: initialises buffer mechanic before a trace session.
|
||||
* @reset_buffer: finalises buffer mechanic after a trace session.
|
||||
* @update_buffer: update buffer pointers after a trace session.
|
||||
*/
|
||||
struct coresight_ops_sink {
|
||||
int (*enable)(struct coresight_device *csdev);
|
||||
int (*enable)(struct coresight_device *csdev, u32 mode);
|
||||
void (*disable)(struct coresight_device *csdev);
|
||||
void *(*alloc_buffer)(struct coresight_device *csdev, int cpu,
|
||||
void **pages, int nr_pages, bool overwrite);
|
||||
void (*free_buffer)(void *config);
|
||||
int (*set_buffer)(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config);
|
||||
unsigned long (*reset_buffer)(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config, bool *lost);
|
||||
void (*update_buffer)(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -205,14 +221,18 @@ struct coresight_ops_link {
|
|||
/**
|
||||
* struct coresight_ops_source - basic operations for a source
|
||||
* Operations available for sources.
|
||||
* @cpu_id: returns the value of the CPU number this component
|
||||
* is associated to.
|
||||
* @trace_id: returns the value of the component's trace ID as known
|
||||
to the HW.
|
||||
* to the HW.
|
||||
* @enable: enables tracing for a source.
|
||||
* @disable: disables tracing for a source.
|
||||
*/
|
||||
struct coresight_ops_source {
|
||||
int (*cpu_id)(struct coresight_device *csdev);
|
||||
int (*trace_id)(struct coresight_device *csdev);
|
||||
int (*enable)(struct coresight_device *csdev);
|
||||
int (*enable)(struct coresight_device *csdev,
|
||||
struct perf_event_attr *attr, u32 mode);
|
||||
void (*disable)(struct coresight_device *csdev);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ struct stm_device;
|
|||
* @sw_end: last STP master available to software
|
||||
* @sw_nchannels: number of STP channels per master
|
||||
* @sw_mmiosz: size of one channel's IO space, for mmap, optional
|
||||
* @hw_override: masters in the STP stream will not match the ones
|
||||
* assigned by software, but are up to the STM hardware
|
||||
* @packet: callback that sends an STP packet
|
||||
* @mmio_addr: mmap callback, optional
|
||||
* @link: called when a new stm_source gets linked to us, optional
|
||||
|
|
@ -67,6 +69,16 @@ struct stm_device;
|
|||
* description. That is, the lowest master that can be allocated to software
|
||||
* writers is @sw_start and data from this writer will appear is @sw_start
|
||||
* master in the STP stream.
|
||||
*
|
||||
* The @packet callback should adhere to the following rules:
|
||||
* 1) it must return the number of bytes it consumed from the payload;
|
||||
* 2) therefore, if it sent a packet that does not have payload (like FLAG),
|
||||
* it must return zero;
|
||||
* 3) if it does not support the requested packet type/flag combination,
|
||||
* it must return -ENOTSUPP.
|
||||
*
|
||||
* The @unlink callback is called when there are no more active writers so
|
||||
* that the master/channel can be quiesced.
|
||||
*/
|
||||
struct stm_data {
|
||||
const char *name;
|
||||
|
|
@ -75,6 +87,7 @@ struct stm_data {
|
|||
unsigned int sw_end;
|
||||
unsigned int sw_nchannels;
|
||||
unsigned int sw_mmiosz;
|
||||
unsigned int hw_override;
|
||||
ssize_t (*packet)(struct stm_data *, unsigned int,
|
||||
unsigned int, unsigned int,
|
||||
unsigned int, unsigned int,
|
||||
|
|
|
|||
21
include/uapi/linux/coresight-stm.h
Normal file
21
include/uapi/linux/coresight-stm.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef __UAPI_CORESIGHT_STM_H_
|
||||
#define __UAPI_CORESIGHT_STM_H_
|
||||
|
||||
#define STM_FLAG_TIMESTAMPED BIT(3)
|
||||
#define STM_FLAG_GUARANTEED BIT(7)
|
||||
|
||||
/*
|
||||
* The CoreSight STM supports guaranteed and invariant timing
|
||||
* transactions. Guaranteed transactions are guaranteed to be
|
||||
* traced, this might involve stalling the bus or system to
|
||||
* ensure the transaction is accepted by the STM. While invariant
|
||||
* timing transactions are not guaranteed to be traced, they
|
||||
* will take an invariant amount of time regardless of the
|
||||
* state of the STM.
|
||||
*/
|
||||
enum {
|
||||
STM_OPTION_GUARANTEED = 0,
|
||||
STM_OPTION_INVARIANT,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1884,8 +1884,13 @@ event_sched_in(struct perf_event *event,
|
|||
if (event->state <= PERF_EVENT_STATE_OFF)
|
||||
return 0;
|
||||
|
||||
event->state = PERF_EVENT_STATE_ACTIVE;
|
||||
event->oncpu = smp_processor_id();
|
||||
WRITE_ONCE(event->oncpu, smp_processor_id());
|
||||
/*
|
||||
* Order event::oncpu write to happen before the ACTIVE state
|
||||
* is visible.
|
||||
*/
|
||||
smp_wmb();
|
||||
WRITE_ONCE(event->state, PERF_EVENT_STATE_ACTIVE);
|
||||
|
||||
/*
|
||||
* Unthrottle events, since we scheduled we might have missed several
|
||||
|
|
@ -2366,6 +2371,29 @@ void perf_event_enable(struct perf_event *event)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(perf_event_enable);
|
||||
|
||||
static int __perf_event_stop(void *info)
|
||||
{
|
||||
struct perf_event *event = info;
|
||||
|
||||
/* for AUX events, our job is done if the event is already inactive */
|
||||
if (READ_ONCE(event->state) != PERF_EVENT_STATE_ACTIVE)
|
||||
return 0;
|
||||
|
||||
/* matches smp_wmb() in event_sched_in() */
|
||||
smp_rmb();
|
||||
|
||||
/*
|
||||
* There is a window with interrupts enabled before we get here,
|
||||
* so we need to check again lest we try to stop another CPU's event.
|
||||
*/
|
||||
if (READ_ONCE(event->oncpu) != smp_processor_id())
|
||||
return -EAGAIN;
|
||||
|
||||
event->pmu->stop(event, PERF_EF_UPDATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _perf_event_refresh(struct perf_event *event, int refresh)
|
||||
{
|
||||
/*
|
||||
|
|
@ -4625,6 +4653,8 @@ static void perf_mmap_open(struct vm_area_struct *vma)
|
|||
event->pmu->event_mapped(event);
|
||||
}
|
||||
|
||||
static void perf_pmu_output_stop(struct perf_event *event);
|
||||
|
||||
/*
|
||||
* A buffer can be mmap()ed multiple times; either directly through the same
|
||||
* event, or through other events by use of perf_event_set_output().
|
||||
|
|
@ -4652,10 +4682,22 @@ static void perf_mmap_close(struct vm_area_struct *vma)
|
|||
*/
|
||||
if (rb_has_aux(rb) && vma->vm_pgoff == rb->aux_pgoff &&
|
||||
atomic_dec_and_mutex_lock(&rb->aux_mmap_count, &event->mmap_mutex)) {
|
||||
/*
|
||||
* Stop all AUX events that are writing to this buffer,
|
||||
* so that we can free its AUX pages and corresponding PMU
|
||||
* data. Note that after rb::aux_mmap_count dropped to zero,
|
||||
* they won't start any more (see perf_aux_output_begin()).
|
||||
*/
|
||||
perf_pmu_output_stop(event);
|
||||
|
||||
/* now it's safe to free the pages */
|
||||
atomic_long_sub(rb->aux_nr_pages, &mmap_user->locked_vm);
|
||||
vma->vm_mm->pinned_vm -= rb->aux_mmap_locked;
|
||||
|
||||
/* this has to be the last one */
|
||||
rb_free_aux(rb);
|
||||
WARN_ON_ONCE(atomic_read(&rb->aux_refcount));
|
||||
|
||||
mutex_unlock(&event->mmap_mutex);
|
||||
}
|
||||
|
||||
|
|
@ -5726,6 +5768,80 @@ perf_event_aux(perf_event_aux_output_cb output, void *data,
|
|||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
struct remote_output {
|
||||
struct ring_buffer *rb;
|
||||
int err;
|
||||
};
|
||||
|
||||
static void __perf_event_output_stop(struct perf_event *event, void *data)
|
||||
{
|
||||
struct perf_event *parent = event->parent;
|
||||
struct remote_output *ro = data;
|
||||
struct ring_buffer *rb = ro->rb;
|
||||
|
||||
if (!has_aux(event))
|
||||
return;
|
||||
|
||||
if (!parent)
|
||||
parent = event;
|
||||
|
||||
/*
|
||||
* In case of inheritance, it will be the parent that links to the
|
||||
* ring-buffer, but it will be the child that's actually using it:
|
||||
*/
|
||||
if (rcu_dereference(parent->rb) == rb)
|
||||
ro->err = __perf_event_stop(event);
|
||||
}
|
||||
|
||||
static int __perf_pmu_output_stop(void *info)
|
||||
{
|
||||
struct perf_event *event = info;
|
||||
struct pmu *pmu = event->pmu;
|
||||
struct perf_cpu_context *cpuctx = get_cpu_ptr(pmu->pmu_cpu_context);
|
||||
struct remote_output ro = {
|
||||
.rb = event->rb,
|
||||
};
|
||||
|
||||
rcu_read_lock();
|
||||
perf_event_aux_ctx(&cpuctx->ctx, __perf_event_output_stop, &ro);
|
||||
if (cpuctx->task_ctx)
|
||||
perf_event_aux_ctx(cpuctx->task_ctx, __perf_event_output_stop,
|
||||
&ro);
|
||||
rcu_read_unlock();
|
||||
|
||||
return ro.err;
|
||||
}
|
||||
|
||||
static void perf_pmu_output_stop(struct perf_event *event)
|
||||
{
|
||||
struct perf_event *iter;
|
||||
int err, cpu;
|
||||
|
||||
restart:
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(iter, &event->rb->event_list, rb_entry) {
|
||||
/*
|
||||
* For per-CPU events, we need to make sure that neither they
|
||||
* nor their children are running; for cpu==-1 events it's
|
||||
* sufficient to stop the event itself if it's active, since
|
||||
* it can't have children.
|
||||
*/
|
||||
cpu = iter->cpu;
|
||||
if (cpu == -1)
|
||||
cpu = READ_ONCE(iter->oncpu);
|
||||
|
||||
if (cpu == -1)
|
||||
continue;
|
||||
|
||||
err = cpu_function_call(cpu, __perf_pmu_output_stop, event);
|
||||
if (err == -EAGAIN) {
|
||||
rcu_read_unlock();
|
||||
goto restart;
|
||||
}
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
/*
|
||||
* task tracking -- fork/exit
|
||||
*
|
||||
|
|
@ -8457,6 +8573,7 @@ SYSCALL_DEFINE5(perf_event_open,
|
|||
f_flags);
|
||||
if (IS_ERR(event_file)) {
|
||||
err = PTR_ERR(event_file);
|
||||
event_file = NULL;
|
||||
goto err_context;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
struct ring_buffer {
|
||||
atomic_t refcount;
|
||||
struct rcu_head rcu_head;
|
||||
struct irq_work irq_work;
|
||||
#ifdef CONFIG_PERF_USE_VMALLOC
|
||||
struct work_struct work;
|
||||
int page_order; /* allocation order */
|
||||
|
|
|
|||
|
|
@ -221,8 +221,6 @@ void perf_output_end(struct perf_output_handle *handle)
|
|||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static void rb_irq_work(struct irq_work *work);
|
||||
|
||||
static void
|
||||
ring_buffer_init(struct ring_buffer *rb, long watermark, int flags)
|
||||
{
|
||||
|
|
@ -243,16 +241,6 @@ ring_buffer_init(struct ring_buffer *rb, long watermark, int flags)
|
|||
|
||||
INIT_LIST_HEAD(&rb->event_list);
|
||||
spin_lock_init(&rb->event_lock);
|
||||
init_irq_work(&rb->irq_work, rb_irq_work);
|
||||
}
|
||||
|
||||
static void ring_buffer_put_async(struct ring_buffer *rb)
|
||||
{
|
||||
if (!atomic_dec_and_test(&rb->refcount))
|
||||
return;
|
||||
|
||||
rb->rcu_head.next = (void *)rb;
|
||||
irq_work_queue(&rb->irq_work);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -264,6 +252,10 @@ static void ring_buffer_put_async(struct ring_buffer *rb)
|
|||
* The ordering is similar to that of perf_output_{begin,end}, with
|
||||
* the exception of (B), which should be taken care of by the pmu
|
||||
* driver, since ordering rules will differ depending on hardware.
|
||||
*
|
||||
* Call this from pmu::start(); see the comment in perf_aux_output_end()
|
||||
* about its use in pmu callbacks. Both can also be called from the PMI
|
||||
* handler if needed.
|
||||
*/
|
||||
void *perf_aux_output_begin(struct perf_output_handle *handle,
|
||||
struct perf_event *event)
|
||||
|
|
@ -287,6 +279,13 @@ void *perf_aux_output_begin(struct perf_output_handle *handle,
|
|||
if (!rb_has_aux(rb) || !atomic_inc_not_zero(&rb->aux_refcount))
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* If rb::aux_mmap_count is zero (and rb_has_aux() above went through),
|
||||
* the aux buffer is in perf_mmap_close(), about to get freed.
|
||||
*/
|
||||
if (!atomic_read(&rb->aux_mmap_count))
|
||||
goto err_put;
|
||||
|
||||
/*
|
||||
* Nesting is not supported for AUX area, make sure nested
|
||||
* writers are caught early
|
||||
|
|
@ -328,10 +327,11 @@ void *perf_aux_output_begin(struct perf_output_handle *handle,
|
|||
return handle->rb->aux_priv;
|
||||
|
||||
err_put:
|
||||
/* can't be last */
|
||||
rb_free_aux(rb);
|
||||
|
||||
err:
|
||||
ring_buffer_put_async(rb);
|
||||
ring_buffer_put(rb);
|
||||
handle->event = NULL;
|
||||
|
||||
return NULL;
|
||||
|
|
@ -342,6 +342,10 @@ void *perf_aux_output_begin(struct perf_output_handle *handle,
|
|||
* aux_head and posting a PERF_RECORD_AUX into the perf buffer. It is the
|
||||
* pmu driver's responsibility to observe ordering rules of the hardware,
|
||||
* so that all the data is externally visible before this is called.
|
||||
*
|
||||
* Note: this has to be called from pmu::stop() callback, as the assumption
|
||||
* of the AUX buffer management code is that after pmu::stop(), the AUX
|
||||
* transaction must be stopped and therefore drop the AUX reference count.
|
||||
*/
|
||||
void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size,
|
||||
bool truncated)
|
||||
|
|
@ -389,8 +393,9 @@ void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size,
|
|||
handle->event = NULL;
|
||||
|
||||
local_set(&rb->aux_nest, 0);
|
||||
/* can't be last */
|
||||
rb_free_aux(rb);
|
||||
ring_buffer_put_async(rb);
|
||||
ring_buffer_put(rb);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -467,6 +472,33 @@ static void rb_free_aux_page(struct ring_buffer *rb, int idx)
|
|||
__free_page(page);
|
||||
}
|
||||
|
||||
static void __rb_free_aux(struct ring_buffer *rb)
|
||||
{
|
||||
int pg;
|
||||
|
||||
/*
|
||||
* Should never happen, the last reference should be dropped from
|
||||
* perf_mmap_close() path, which first stops aux transactions (which
|
||||
* in turn are the atomic holders of aux_refcount) and then does the
|
||||
* last rb_free_aux().
|
||||
*/
|
||||
WARN_ON_ONCE(in_atomic());
|
||||
|
||||
if (rb->aux_priv) {
|
||||
rb->free_aux(rb->aux_priv);
|
||||
rb->free_aux = NULL;
|
||||
rb->aux_priv = NULL;
|
||||
}
|
||||
|
||||
if (rb->aux_nr_pages) {
|
||||
for (pg = 0; pg < rb->aux_nr_pages; pg++)
|
||||
rb_free_aux_page(rb, pg);
|
||||
|
||||
kfree(rb->aux_pages);
|
||||
rb->aux_nr_pages = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
||||
pgoff_t pgoff, int nr_pages, long watermark, int flags)
|
||||
{
|
||||
|
|
@ -555,45 +587,15 @@ int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
|||
if (!ret)
|
||||
rb->aux_pgoff = pgoff;
|
||||
else
|
||||
rb_free_aux(rb);
|
||||
__rb_free_aux(rb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __rb_free_aux(struct ring_buffer *rb)
|
||||
{
|
||||
int pg;
|
||||
|
||||
if (rb->aux_priv) {
|
||||
rb->free_aux(rb->aux_priv);
|
||||
rb->free_aux = NULL;
|
||||
rb->aux_priv = NULL;
|
||||
}
|
||||
|
||||
if (rb->aux_nr_pages) {
|
||||
for (pg = 0; pg < rb->aux_nr_pages; pg++)
|
||||
rb_free_aux_page(rb, pg);
|
||||
|
||||
kfree(rb->aux_pages);
|
||||
rb->aux_nr_pages = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void rb_free_aux(struct ring_buffer *rb)
|
||||
{
|
||||
if (atomic_dec_and_test(&rb->aux_refcount))
|
||||
irq_work_queue(&rb->irq_work);
|
||||
}
|
||||
|
||||
static void rb_irq_work(struct irq_work *work)
|
||||
{
|
||||
struct ring_buffer *rb = container_of(work, struct ring_buffer, irq_work);
|
||||
|
||||
if (!atomic_read(&rb->aux_refcount))
|
||||
__rb_free_aux(rb);
|
||||
|
||||
if (rb->rcu_head.next == (void *)rb)
|
||||
call_rcu(&rb->rcu_head, rb_free_rcu);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_PERF_USE_VMALLOC
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ struct branch {
|
|||
u64 misc;
|
||||
};
|
||||
|
||||
static size_t intel_bts_info_priv_size(struct auxtrace_record *itr __maybe_unused)
|
||||
static size_t
|
||||
intel_bts_info_priv_size(struct auxtrace_record *itr __maybe_unused,
|
||||
struct perf_evlist *evlist __maybe_unused)
|
||||
{
|
||||
return INTEL_BTS_AUXTRACE_PRIV_SIZE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,7 +273,9 @@ intel_pt_pmu_default_config(struct perf_pmu *intel_pt_pmu)
|
|||
return attr;
|
||||
}
|
||||
|
||||
static size_t intel_pt_info_priv_size(struct auxtrace_record *itr __maybe_unused)
|
||||
static size_t
|
||||
intel_pt_info_priv_size(struct auxtrace_record *itr __maybe_unused,
|
||||
struct perf_evlist *evlist __maybe_unused)
|
||||
{
|
||||
return INTEL_PT_AUXTRACE_PRIV_SIZE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -626,12 +626,16 @@ static int __cmd_inject(struct perf_inject *inject)
|
|||
ret = perf_session__process_events(session);
|
||||
|
||||
if (!file_out->is_pipe) {
|
||||
if (inject->build_ids) {
|
||||
if (inject->build_ids)
|
||||
perf_header__set_feat(&session->header,
|
||||
HEADER_BUILD_ID);
|
||||
if (inject->have_auxtrace)
|
||||
dsos__hit_all(session);
|
||||
}
|
||||
/*
|
||||
* Keep all buildids when there is unprocessed AUX data because
|
||||
* it is not known which ones the AUX trace hits.
|
||||
*/
|
||||
if (perf_header__has_feat(&session->header, HEADER_BUILD_ID) &&
|
||||
inject->have_auxtrace && !inject->itrace_synth_opts.set)
|
||||
dsos__hit_all(session);
|
||||
/*
|
||||
* The AUX areas have been removed and replaced with
|
||||
* synthesized hardware events, so clear the feature flag and
|
||||
|
|
|
|||
|
|
@ -478,10 +478,11 @@ void auxtrace_heap__pop(struct auxtrace_heap *heap)
|
|||
heap_array[last].ordinal);
|
||||
}
|
||||
|
||||
size_t auxtrace_record__info_priv_size(struct auxtrace_record *itr)
|
||||
size_t auxtrace_record__info_priv_size(struct auxtrace_record *itr,
|
||||
struct perf_evlist *evlist)
|
||||
{
|
||||
if (itr)
|
||||
return itr->info_priv_size(itr);
|
||||
return itr->info_priv_size(itr, evlist);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -852,7 +853,7 @@ int perf_event__synthesize_auxtrace_info(struct auxtrace_record *itr,
|
|||
int err;
|
||||
|
||||
pr_debug2("Synthesizing auxtrace information\n");
|
||||
priv_size = auxtrace_record__info_priv_size(itr);
|
||||
priv_size = auxtrace_record__info_priv_size(itr, session->evlist);
|
||||
ev = zalloc(sizeof(struct auxtrace_info_event) + priv_size);
|
||||
if (!ev)
|
||||
return -ENOMEM;
|
||||
|
|
|
|||
|
|
@ -293,7 +293,8 @@ struct auxtrace_record {
|
|||
int (*recording_options)(struct auxtrace_record *itr,
|
||||
struct perf_evlist *evlist,
|
||||
struct record_opts *opts);
|
||||
size_t (*info_priv_size)(struct auxtrace_record *itr);
|
||||
size_t (*info_priv_size)(struct auxtrace_record *itr,
|
||||
struct perf_evlist *evlist);
|
||||
int (*info_fill)(struct auxtrace_record *itr,
|
||||
struct perf_session *session,
|
||||
struct auxtrace_info_event *auxtrace_info,
|
||||
|
|
@ -429,7 +430,8 @@ int auxtrace_parse_snapshot_options(struct auxtrace_record *itr,
|
|||
int auxtrace_record__options(struct auxtrace_record *itr,
|
||||
struct perf_evlist *evlist,
|
||||
struct record_opts *opts);
|
||||
size_t auxtrace_record__info_priv_size(struct auxtrace_record *itr);
|
||||
size_t auxtrace_record__info_priv_size(struct auxtrace_record *itr,
|
||||
struct perf_evlist *evlist);
|
||||
int auxtrace_record__info_fill(struct auxtrace_record *itr,
|
||||
struct perf_session *session,
|
||||
struct auxtrace_info_event *auxtrace_info,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
#include <stdlib.h>
|
||||
#include "asm/bug.h"
|
||||
|
||||
static int max_cpu_num;
|
||||
static int max_node_num;
|
||||
static int *cpunode_map;
|
||||
|
||||
static struct cpu_map *cpu_map__default_new(void)
|
||||
{
|
||||
struct cpu_map *cpus;
|
||||
|
|
@ -435,6 +439,32 @@ static void set_max_node_num(void)
|
|||
pr_err("Failed to read max nodes, using default of %d\n", max_node_num);
|
||||
}
|
||||
|
||||
int cpu__max_node(void)
|
||||
{
|
||||
if (unlikely(!max_node_num))
|
||||
set_max_node_num();
|
||||
|
||||
return max_node_num;
|
||||
}
|
||||
|
||||
int cpu__max_cpu(void)
|
||||
{
|
||||
if (unlikely(!max_cpu_num))
|
||||
set_max_cpu_num();
|
||||
|
||||
return max_cpu_num;
|
||||
}
|
||||
|
||||
int cpu__get_node(int cpu)
|
||||
{
|
||||
if (unlikely(cpunode_map == NULL)) {
|
||||
pr_debug("cpu_map not initialized\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cpunode_map[cpu];
|
||||
}
|
||||
|
||||
static int init_cpunode_map(void)
|
||||
{
|
||||
int i;
|
||||
|
|
|
|||
|
|
@ -56,37 +56,11 @@ static inline bool cpu_map__empty(const struct cpu_map *map)
|
|||
return map ? map->map[0] == -1 : true;
|
||||
}
|
||||
|
||||
int max_cpu_num;
|
||||
int max_node_num;
|
||||
int *cpunode_map;
|
||||
|
||||
int cpu__setup_cpunode_map(void);
|
||||
|
||||
static inline int cpu__max_node(void)
|
||||
{
|
||||
if (unlikely(!max_node_num))
|
||||
pr_debug("cpu_map not initialized\n");
|
||||
|
||||
return max_node_num;
|
||||
}
|
||||
|
||||
static inline int cpu__max_cpu(void)
|
||||
{
|
||||
if (unlikely(!max_cpu_num))
|
||||
pr_debug("cpu_map not initialized\n");
|
||||
|
||||
return max_cpu_num;
|
||||
}
|
||||
|
||||
static inline int cpu__get_node(int cpu)
|
||||
{
|
||||
if (unlikely(cpunode_map == NULL)) {
|
||||
pr_debug("cpu_map not initialized\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cpunode_map[cpu];
|
||||
}
|
||||
int cpu__max_node(void);
|
||||
int cpu__max_cpu(void);
|
||||
int cpu__get_node(int cpu);
|
||||
|
||||
int cpu_map__build_map(struct cpu_map *cpus, struct cpu_map **res,
|
||||
int (*f)(struct cpu_map *map, int cpu, void *data),
|
||||
|
|
|
|||
|
|
@ -1486,7 +1486,7 @@ int perf_evlist__open(struct perf_evlist *evlist)
|
|||
perf_evlist__update_id_pos(evlist);
|
||||
|
||||
evlist__for_each(evlist, evsel) {
|
||||
err = perf_evsel__open(evsel, evlist->cpus, evlist->threads);
|
||||
err = perf_evsel__open(evsel, evsel->cpus, evsel->threads);
|
||||
if (err < 0)
|
||||
goto out_err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -988,6 +988,16 @@ int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads)
|
|||
0);
|
||||
}
|
||||
|
||||
int perf_evsel__disable(struct perf_evsel *evsel)
|
||||
{
|
||||
int nthreads = thread_map__nr(evsel->threads);
|
||||
int ncpus = cpu_map__nr(evsel->cpus);
|
||||
|
||||
return perf_evsel__run_ioctl(evsel, ncpus, nthreads,
|
||||
PERF_EVENT_IOC_DISABLE,
|
||||
0);
|
||||
}
|
||||
|
||||
int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads)
|
||||
{
|
||||
if (ncpus == 0 || nthreads == 0)
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ int perf_evsel__append_filter(struct perf_evsel *evsel,
|
|||
int perf_evsel__apply_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
|
||||
const char *filter);
|
||||
int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads);
|
||||
int perf_evsel__disable(struct perf_evsel *evsel);
|
||||
|
||||
int perf_evsel__open_per_cpu(struct perf_evsel *evsel,
|
||||
struct cpu_map *cpus);
|
||||
|
|
|
|||
|
|
@ -224,14 +224,6 @@ static int process_event_stub(struct perf_tool *tool __maybe_unused,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int process_build_id_stub(struct perf_tool *tool __maybe_unused,
|
||||
union perf_event *event __maybe_unused,
|
||||
struct perf_session *session __maybe_unused)
|
||||
{
|
||||
dump_printf(": unhandled!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_finished_round_stub(struct perf_tool *tool __maybe_unused,
|
||||
union perf_event *event __maybe_unused,
|
||||
struct ordered_events *oe __maybe_unused)
|
||||
|
|
@ -244,23 +236,6 @@ static int process_finished_round(struct perf_tool *tool,
|
|||
union perf_event *event,
|
||||
struct ordered_events *oe);
|
||||
|
||||
static int process_id_index_stub(struct perf_tool *tool __maybe_unused,
|
||||
union perf_event *event __maybe_unused,
|
||||
struct perf_session *perf_session
|
||||
__maybe_unused)
|
||||
{
|
||||
dump_printf(": unhandled!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_event_auxtrace_info_stub(struct perf_tool *tool __maybe_unused,
|
||||
union perf_event *event __maybe_unused,
|
||||
struct perf_session *session __maybe_unused)
|
||||
{
|
||||
dump_printf(": unhandled!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int skipn(int fd, off_t n)
|
||||
{
|
||||
char buf[4096];
|
||||
|
|
@ -287,10 +262,9 @@ static s64 process_event_auxtrace_stub(struct perf_tool *tool __maybe_unused,
|
|||
return event->auxtrace.size;
|
||||
}
|
||||
|
||||
static
|
||||
int process_event_auxtrace_error_stub(struct perf_tool *tool __maybe_unused,
|
||||
union perf_event *event __maybe_unused,
|
||||
struct perf_session *session __maybe_unused)
|
||||
static int process_event_op2_stub(struct perf_tool *tool __maybe_unused,
|
||||
union perf_event *event __maybe_unused,
|
||||
struct perf_session *session __maybe_unused)
|
||||
{
|
||||
dump_printf(": unhandled!\n");
|
||||
return 0;
|
||||
|
|
@ -331,7 +305,7 @@ void perf_tool__fill_defaults(struct perf_tool *tool)
|
|||
if (tool->tracing_data == NULL)
|
||||
tool->tracing_data = process_event_synth_tracing_data_stub;
|
||||
if (tool->build_id == NULL)
|
||||
tool->build_id = process_build_id_stub;
|
||||
tool->build_id = process_event_op2_stub;
|
||||
if (tool->finished_round == NULL) {
|
||||
if (tool->ordered_events)
|
||||
tool->finished_round = process_finished_round;
|
||||
|
|
@ -339,13 +313,13 @@ void perf_tool__fill_defaults(struct perf_tool *tool)
|
|||
tool->finished_round = process_finished_round_stub;
|
||||
}
|
||||
if (tool->id_index == NULL)
|
||||
tool->id_index = process_id_index_stub;
|
||||
tool->id_index = process_event_op2_stub;
|
||||
if (tool->auxtrace_info == NULL)
|
||||
tool->auxtrace_info = process_event_auxtrace_info_stub;
|
||||
tool->auxtrace_info = process_event_op2_stub;
|
||||
if (tool->auxtrace == NULL)
|
||||
tool->auxtrace = process_event_auxtrace_stub;
|
||||
if (tool->auxtrace_error == NULL)
|
||||
tool->auxtrace_error = process_event_auxtrace_error_stub;
|
||||
tool->auxtrace_error = process_event_op2_stub;
|
||||
}
|
||||
|
||||
static void swap_sample_id_all(union perf_event *event, void *data)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user