coresight: Updates for v6.3
- Dynamic TraceID allocation scheme for CoreSight trace source. Allows systems
with > 44 CPUs to use the ETMs. TraceID is advertised via AUX_OUTPUT_HWID
packets in perf.data. Also allows allocating trace-ids for non-CPU bound trace
components (e.g., Qualcomm TPDA).
- Support for Qualcomm TPDA and TPDM CoreSight devices.
- Support for Ultrasoc SMB CoreSight Sink buffer.
- Fixes for HiSilicon PTT driver
- MAINTAINERS update: Add Reviewer for HiSilicon PTT driver
- Bug fixes for CTI power management and sysfs mode
- Fix CoreSight ETM4x TRCSEQRSTEVRn access
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEuFy0byloRoXZHaWBxcXRZPKyBqEFAmPY/2QACgkQxcXRZPKy
BqHfYRAAsO3uJ+Q4dRdNIftKKVppbmDGehzmRorJKXPgc7eosXVWQlFkiEFLUGVt
VOwh3eZzW9g1PYZ2HoB2u5aW1/mXL9SLa0lepEovqi2BVxzrpoBh/cw8KT2C0UtU
jfkirNk4xaJl3kiwy8WTZ3vFCPa8SRkoxXQEZYD/8NWfgWC4JwOi5yRrCIDvves8
0aMHGuU9benT/CBUNhRE558WILG+71QGQoYzekSp/4CustEk1AM0DpnI0wfYRHB1
GvDNoTUGoD1GhDvfaiggBj6YWPmrdwgim3xLSLU7OsEK3hRWy3QwAeVN49W0wAtm
zhrkuHsETT7c81I+IFh170imueCj42aDgJS9Y5Z/xWfI0B5kzaKVQdYLIO0Lyu6p
SZkdvQXG2YlrYlmUOWepbglhMnZmw0DVI2lt0Pwns/7ebqL5nJMAD6c8iyWV0if+
9BJ9gqNpFAJBbjXJtFVe/T7eaYkeBBO2Q5ysLRaE60JgN7nvrmvIxZ11yIeauEAD
fahlepOT4bxOkG88pn+9lXXzXe4Xyq/kU33TtbRtL1OxIXKws+vw/4gUUeZHGnxT
1kUTcHHfT6SAogRa2mYzVAHXNVLrc5J79BvdYQ/zNgQ1hQgJ1cghHGNim5JlvuYz
AZZO2DfX8XRXJ/5f3AibM77SPR+PDhfGnpPYm7ZHP1FAavP/o9k=
=FS9z
-----END PGP SIGNATURE-----
Merge tag 'coresight-next-v6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux into char-misc-next
Suzuki writes:
coresight: Updates for v6.3
- Dynamic TraceID allocation scheme for CoreSight trace source. Allows systems
with > 44 CPUs to use the ETMs. TraceID is advertised via AUX_OUTPUT_HWID
packets in perf.data. Also allows allocating trace-ids for non-CPU bound trace
components (e.g., Qualcomm TPDA).
- Support for Qualcomm TPDA and TPDM CoreSight devices.
- Support for Ultrasoc SMB CoreSight Sink buffer.
- Fixes for HiSilicon PTT driver
- MAINTAINERS update: Add Reviewer for HiSilicon PTT driver
- Bug fixes for CTI power management and sysfs mode
- Fix CoreSight ETM4x TRCSEQRSTEVRn access
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
* tag 'coresight-next-v6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux: (35 commits)
coresight: tmc: Don't enable TMC when it's not ready.
coresight: tpda: fix return value check in tpda_probe()
Coresight: tpda/tpdm: remove incorrect __exit annotation
coresight: perf: Output trace id only once
coresight: Fix uninitialised variable use in coresight_disable
Documentation: coresight: tpdm: Add dummy comment after sysfs list
Documentation: coresight: Extend title heading syntax in TPDM and TPDA documentation
Documentation: trace: Add documentation for TPDM and TPDA
dt-bindings: arm: Adds CoreSight TPDA hardware definitions
Coresight: Add TPDA link driver
coresight-tpdm: Add integration test support
coresight-tpdm: Add DSB dataset support
dt-bindings: arm: Add CoreSight TPDM hardware
Coresight: Add coresight TPDM source driver
coresight: core: Use IDR for non-cpu bound sources' paths.
coresight: trace-id: Add debug & test macros to Trace ID allocation
coresight: events: PERF_RECORD_AUX_OUTPUT_HW_ID used for Trace ID
kernel: events: Export perf_report_aux_output_id()
coresight: trace id: Remove legacy get trace ID function.
coresight: etmX.X: stm: Remove trace_id() callback
...
This commit is contained in:
commit
d45fed4ff6
@ -236,7 +236,7 @@ What: /sys/bus/coresight/devices/<memory_map>.[etm|ptm]/traceid
|
||||
Date: November 2014
|
||||
KernelVersion: 3.19
|
||||
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
Description: (RW) Holds the trace ID that will appear in the trace stream
|
||||
Description: (RO) Holds the trace ID that will appear in the trace stream
|
||||
coming from this trace entity.
|
||||
|
||||
What: /sys/bus/coresight/devices/<memory_map>.[etm|ptm]/trigger_event
|
||||
|
||||
13
Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm
Normal file
13
Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm
Normal file
@ -0,0 +1,13 @@
|
||||
What: /sys/bus/coresight/devices/<tpdm-name>/integration_test
|
||||
Date: January 2023
|
||||
KernelVersion 6.2
|
||||
Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com>
|
||||
Description:
|
||||
(Write) Run integration test for tpdm. Integration test
|
||||
will generate test data for tpdm. It can help to make
|
||||
sure that the trace path is enabled and the link configurations
|
||||
are fine.
|
||||
|
||||
Accepts only one of the 2 values - 1 or 2.
|
||||
1 : Generate 64 bits data
|
||||
2 : Generate 32 bits data
|
||||
@ -0,0 +1,31 @@
|
||||
What: /sys/bus/coresight/devices/ultra_smb<N>/enable_sink
|
||||
Date: January 2023
|
||||
KernelVersion: 6.3
|
||||
Contact: Junhao He <hejunhao3@huawei.com>
|
||||
Description: (RW) Add/remove a SMB device from a trace path. There can be
|
||||
multiple sources for a single SMB device.
|
||||
|
||||
What: /sys/bus/coresight/devices/ultra_smb<N>/mgmt/buf_size
|
||||
Date: January 2023
|
||||
KernelVersion: 6.3
|
||||
Contact: Junhao He <hejunhao3@huawei.com>
|
||||
Description: (RO) Shows the buffer size of each UltraSoc SMB device.
|
||||
|
||||
What: /sys/bus/coresight/devices/ultra_smb<N>/mgmt/buf_status
|
||||
Date: January 2023
|
||||
KernelVersion: 6.3
|
||||
Contact: Junhao He <hejunhao3@huawei.com>
|
||||
Description: (RO) Shows the value of UltraSoc SMB status register.
|
||||
BIT(0) is zero means buffer is empty.
|
||||
|
||||
What: /sys/bus/coresight/devices/ultra_smb<N>/mgmt/read_pos
|
||||
Date: January 2023
|
||||
KernelVersion: 6.3
|
||||
Contact: Junhao He <hejunhao3@huawei.com>
|
||||
Description: (RO) Shows the value of UltraSoc SMB Read Pointer register.
|
||||
|
||||
What: /sys/bus/coresight/devices/ultra_smb<N>/mgmt/write_pos
|
||||
Date: January 2023
|
||||
KernelVersion: 6.3
|
||||
Contact: Junhao He <hejunhao3@huawei.com>
|
||||
Description: (RO) Shows the value of UltraSoc SMB Write Pointer register.
|
||||
129
Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml
Normal file
129
Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml
Normal file
@ -0,0 +1,129 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
|
||||
# Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/arm/qcom,coresight-tpda.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Trace, Profiling and Diagnostics Aggregator - TPDA
|
||||
|
||||
description: |
|
||||
TPDAs are responsible for packetization and timestamping of data sets
|
||||
utilizing the MIPI STPv2 packet protocol. Pulling data sets from one or
|
||||
more attached TPDM and pushing the resultant (packetized) data out a
|
||||
master ATB interface. Performing an arbitrated ATB interleaving (funneling)
|
||||
task for free-flowing data from TPDM (i.e. CMB and DSB data set flows).
|
||||
|
||||
There is no strict binding between TPDM and TPDA. TPDA can have multiple
|
||||
TPDMs connect to it. But There must be only one TPDA in the path from the
|
||||
TPDM source to TMC sink. TPDM can directly connect to TPDA's inport or
|
||||
connect to funnel which will connect to TPDA's inport.
|
||||
|
||||
We can use the commands are similar to the below to validate TPDMs.
|
||||
Enable coresight sink first.
|
||||
|
||||
echo 1 > /sys/bus/coresight/devices/tmc_etf0/enable_sink
|
||||
echo 1 > /sys/bus/coresight/devices/tpdm0/enable_source
|
||||
echo 1 > /sys/bus/coresight/devices/tpdm0/integration_test
|
||||
echo 2 > /sys/bus/coresight/devices/tpdm0/integration_test
|
||||
|
||||
The test data will be collected in the coresight sink which is enabled.
|
||||
If rwp register of the sink is keeping updating when do integration_test
|
||||
(by cat tmc_etf0/mgmt/rwp), it means there is data generated from TPDM
|
||||
to sink.
|
||||
|
||||
maintainers:
|
||||
- Mao Jinlong <quic_jinlmao@quicinc.com>
|
||||
- Tao Zhang <quic_taozha@quicinc.com>
|
||||
|
||||
# Need a custom select here or 'arm,primecell' will match on lots of nodes
|
||||
select:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,coresight-tpda
|
||||
required:
|
||||
- compatible
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^tpda(@[0-9a-f]+)$"
|
||||
compatible:
|
||||
items:
|
||||
- const: qcom,coresight-tpda
|
||||
- const: arm,primecell
|
||||
|
||||
reg:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: apb_pclk
|
||||
|
||||
in-ports:
|
||||
type: object
|
||||
description: |
|
||||
Input connections from TPDM to TPDA
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
out-ports:
|
||||
type: object
|
||||
description: |
|
||||
Output connections from the TPDA to legacy CoreSight trace bus.
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
properties:
|
||||
port:
|
||||
description:
|
||||
Output connection from the TPDA to legacy CoreSight Trace bus.
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- clock-names
|
||||
- in-ports
|
||||
- out-ports
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
# minimum tpda definition.
|
||||
- |
|
||||
tpda@6004000 {
|
||||
compatible = "qcom,coresight-tpda", "arm,primecell";
|
||||
reg = <0x6004000 0x1000>;
|
||||
|
||||
clocks = <&aoss_qmp>;
|
||||
clock-names = "apb_pclk";
|
||||
|
||||
in-ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
tpda_qdss_0_in_tpdm_dcc: endpoint {
|
||||
remote-endpoint =
|
||||
<&tpdm_dcc_out_tpda_qdss_0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
out-ports {
|
||||
port {
|
||||
tpda_qdss_out_funnel_in0: endpoint {
|
||||
remote-endpoint =
|
||||
<&funnel_in0_in_tpda_qdss>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
@ -0,0 +1,93 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause
|
||||
# Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/arm/qcom,coresight-tpdm.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Trace, Profiling and Diagnostics Monitor - TPDM
|
||||
|
||||
description: |
|
||||
The TPDM or Monitor serves as data collection component for various dataset
|
||||
types specified in the QPMDA spec. It covers Implementation defined ((ImplDef),
|
||||
Basic Counts (BC), Tenure Counts (TC), Continuous Multi-Bit (CMB), and Discrete
|
||||
Single Bit (DSB). It performs data collection in the data producing clock
|
||||
domain and transfers it to the data collection time domain, generally ATB
|
||||
clock domain.
|
||||
|
||||
The primary use case of the TPDM is to collect data from different data
|
||||
sources and send it to a TPDA for packetization, timestamping, and funneling.
|
||||
|
||||
maintainers:
|
||||
- Mao Jinlong <quic_jinlmao@quicinc.com>
|
||||
- Tao Zhang <quic_taozha@quicinc.com>
|
||||
|
||||
# Need a custom select here or 'arm,primecell' will match on lots of nodes
|
||||
select:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- qcom,coresight-tpdm
|
||||
required:
|
||||
- compatible
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^tpdm(@[0-9a-f]+)$"
|
||||
compatible:
|
||||
items:
|
||||
- const: qcom,coresight-tpdm
|
||||
- const: arm,primecell
|
||||
|
||||
reg:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: apb_pclk
|
||||
|
||||
out-ports:
|
||||
description: |
|
||||
Output connections from the TPDM to coresight funnel/TPDA.
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
properties:
|
||||
port:
|
||||
description: Output connection from the TPDM to coresight
|
||||
funnel/TPDA.
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
# minimum TPDM definition. TPDM connect to coresight TPDA.
|
||||
- |
|
||||
tpdm@684c000 {
|
||||
compatible = "qcom,coresight-tpdm", "arm,primecell";
|
||||
reg = <0x0684c000 0x1000>;
|
||||
|
||||
clocks = <&aoss_qmp>;
|
||||
clock-names = "apb_pclk";
|
||||
|
||||
out-ports {
|
||||
port {
|
||||
tpdm_prng_out_tpda_qdss: endpoint {
|
||||
remote-endpoint =
|
||||
<&tpda_qdss_in_tpdm_prng>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
52
Documentation/trace/coresight/coresight-tpda.rst
Normal file
52
Documentation/trace/coresight/coresight-tpda.rst
Normal file
@ -0,0 +1,52 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=================================================================
|
||||
The trace performance monitoring and diagnostics aggregator(TPDA)
|
||||
=================================================================
|
||||
|
||||
:Author: Jinlong Mao <quic_jinlmao@quicinc.com>
|
||||
:Date: January 2023
|
||||
|
||||
Hardware Description
|
||||
--------------------
|
||||
|
||||
TPDA - The trace performance monitoring and diagnostics aggregator or
|
||||
TPDA in short serves as an arbitration and packetization engine for the
|
||||
performance monitoring and diagnostics network specification.
|
||||
The primary use case of the TPDA is to provide packetization, funneling
|
||||
and timestamping of Monitor data.
|
||||
|
||||
|
||||
Sysfs files and directories
|
||||
---------------------------
|
||||
Root: ``/sys/bus/coresight/devices/tpda<N>``
|
||||
|
||||
Config details
|
||||
---------------------------
|
||||
|
||||
The tpdm and tpda nodes should be observed at the coresight path
|
||||
"/sys/bus/coresight/devices".
|
||||
e.g.
|
||||
/sys/bus/coresight/devices # ls -l | grep tpd
|
||||
tpda0 -> ../../../devices/platform/soc@0/6004000.tpda/tpda0
|
||||
tpdm0 -> ../../../devices/platform/soc@0/6c08000.mm.tpdm/tpdm0
|
||||
|
||||
We can use the commands are similar to the below to validate TPDMs.
|
||||
Enable coresight sink first. The port of tpda which is connected to
|
||||
the tpdm will be enabled after commands below.
|
||||
|
||||
echo 1 > /sys/bus/coresight/devices/tmc_etf0/enable_sink
|
||||
echo 1 > /sys/bus/coresight/devices/tpdm0/enable_source
|
||||
echo 1 > /sys/bus/coresight/devices/tpdm0/integration_test
|
||||
echo 2 > /sys/bus/coresight/devices/tpdm0/integration_test
|
||||
|
||||
The test data will be collected in the coresight sink which is enabled.
|
||||
If rwp register of the sink is keeping updating when do
|
||||
integration_test (by cat tmc_etf0/mgmt/rwp), it means there is data
|
||||
generated from TPDM to sink.
|
||||
|
||||
There must be a tpda between tpdm and the sink. When there are some
|
||||
other trace event hw components in the same HW block with tpdm, tpdm
|
||||
and these hw components will connect to the coresight funnel. When
|
||||
there is only tpdm trace hw in the HW block, tpdm will connect to
|
||||
tpda directly.
|
||||
45
Documentation/trace/coresight/coresight-tpdm.rst
Normal file
45
Documentation/trace/coresight/coresight-tpdm.rst
Normal file
@ -0,0 +1,45 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
==========================================================
|
||||
Trace performance monitoring and diagnostics monitor(TPDM)
|
||||
==========================================================
|
||||
|
||||
:Author: Jinlong Mao <quic_jinlmao@quicinc.com>
|
||||
:Date: January 2023
|
||||
|
||||
Hardware Description
|
||||
--------------------
|
||||
TPDM - The trace performance monitoring and diagnostics monitor or TPDM in
|
||||
short serves as data collection component for various dataset types.
|
||||
The primary use case of the TPDM is to collect data from different data
|
||||
sources and send it to a TPDA for packetization, timestamping and funneling.
|
||||
|
||||
Sysfs files and directories
|
||||
---------------------------
|
||||
Root: ``/sys/bus/coresight/devices/tpdm<N>``
|
||||
|
||||
----
|
||||
|
||||
:File: ``enable_source`` (RW)
|
||||
:Notes:
|
||||
- > 0 : enable the datasets of TPDM.
|
||||
|
||||
- = 0 : disable the datasets of TPDM.
|
||||
|
||||
:Syntax:
|
||||
``echo 1 > enable_source``
|
||||
|
||||
----
|
||||
|
||||
:File: ``integration_test`` (wo)
|
||||
:Notes:
|
||||
Integration test will generate test data for tpdm.
|
||||
|
||||
:Syntax:
|
||||
``echo value > integration_test``
|
||||
|
||||
value - 1 or 2.
|
||||
|
||||
----
|
||||
|
||||
.. This text is intentionally added to make Sphinx happy.
|
||||
83
Documentation/trace/coresight/ultrasoc-smb.rst
Normal file
83
Documentation/trace/coresight/ultrasoc-smb.rst
Normal file
@ -0,0 +1,83 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
======================================
|
||||
UltraSoc - HW Assisted Tracing on SoC
|
||||
======================================
|
||||
:Author: Qi Liu <liuqi115@huawei.com>
|
||||
:Date: January 2023
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
UltraSoc SMB is a per SCCL (Super CPU Cluster) hardware. It provides a
|
||||
way to buffer and store CPU trace messages in a region of shared system
|
||||
memory. The device acts as a coresight sink device and the
|
||||
corresponding trace generators (ETM) are attached as source devices.
|
||||
|
||||
Sysfs files and directories
|
||||
---------------------------
|
||||
|
||||
The SMB devices appear on the existing coresight bus alongside other
|
||||
devices::
|
||||
|
||||
$# ls /sys/bus/coresight/devices/
|
||||
ultra_smb0 ultra_smb1 ultra_smb2 ultra_smb3
|
||||
|
||||
The ``ultra_smb<N>`` names SMB device associated with SCCL.::
|
||||
|
||||
$# ls /sys/bus/coresight/devices/ultra_smb0
|
||||
enable_sink mgmt
|
||||
$# ls /sys/bus/coresight/devices/ultra_smb0/mgmt
|
||||
buf_size buf_status read_pos write_pos
|
||||
|
||||
Key file items are:
|
||||
|
||||
* ``read_pos``: Shows the value on the read pointer register.
|
||||
* ``write_pos``: Shows the value on the write pointer register.
|
||||
* ``buf_status``: Shows the value on the status register.
|
||||
BIT(0) is zero value which means the buffer is empty.
|
||||
* ``buf_size``: Shows the buffer size of each device.
|
||||
|
||||
Firmware Bindings
|
||||
-----------------
|
||||
|
||||
The device is only supported with ACPI. Its binding describes device
|
||||
identifier, resource information and graph structure.
|
||||
|
||||
The device is identified as ACPI HID "HISI03A1". Device resources are allocated
|
||||
using the _CRS method. Each device must present two base address; the first one
|
||||
is the configuration base address of the device, the second one is the 32-bit
|
||||
base address of shared system memory.
|
||||
|
||||
Example::
|
||||
|
||||
Device(USMB) { \
|
||||
Name(_HID, "HISI03A1") \
|
||||
Name(_CRS, ResourceTemplate() { \
|
||||
QWordMemory (ResourceConsumer, , MinFixed, MaxFixed, NonCacheable, \
|
||||
ReadWrite, 0x0, 0x95100000, 0x951FFFFF, 0x0, 0x100000) \
|
||||
QWordMemory (ResourceConsumer, , MinFixed, MaxFixed, Cacheable, \
|
||||
ReadWrite, 0x0, 0x50000000, 0x53FFFFFF, 0x0, 0x4000000) \
|
||||
}) \
|
||||
Name(_DSD, Package() { \
|
||||
ToUUID("ab02a46b-74c7-45a2-bd68-f7d344ef2153"), \
|
||||
/* Use CoreSight Graph ACPI bindings to describe connections topology */
|
||||
Package() { \
|
||||
0, \
|
||||
1, \
|
||||
Package() { \
|
||||
1, \
|
||||
ToUUID("3ecbc8b6-1d0e-4fb3-8107-e627f805c6cd"), \
|
||||
8, \
|
||||
Package() {0x8, 0, \_SB.S00.SL11.CL28.F008, 0}, \
|
||||
Package() {0x9, 0, \_SB.S00.SL11.CL29.F009, 0}, \
|
||||
Package() {0xa, 0, \_SB.S00.SL11.CL2A.F010, 0}, \
|
||||
Package() {0xb, 0, \_SB.S00.SL11.CL2B.F011, 0}, \
|
||||
Package() {0xc, 0, \_SB.S00.SL11.CL2C.F012, 0}, \
|
||||
Package() {0xd, 0, \_SB.S00.SL11.CL2D.F013, 0}, \
|
||||
Package() {0xe, 0, \_SB.S00.SL11.CL2E.F014, 0}, \
|
||||
Package() {0xf, 0, \_SB.S00.SL11.CL2F.F015, 0}, \
|
||||
} \
|
||||
} \
|
||||
}) \
|
||||
}
|
||||
@ -2123,6 +2123,7 @@ S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux.git
|
||||
F: Documentation/ABI/testing/sysfs-bus-coresight-devices-*
|
||||
F: Documentation/devicetree/bindings/arm/arm,coresight-*.yaml
|
||||
F: Documentation/devicetree/bindings/arm/qcom,coresight-*.yaml
|
||||
F: Documentation/devicetree/bindings/arm/arm,embedded-trace-extension.yaml
|
||||
F: Documentation/devicetree/bindings/arm/arm,trace-buffer-extension.yaml
|
||||
F: Documentation/trace/coresight/*
|
||||
@ -9374,11 +9375,15 @@ F: drivers/perf/hisilicon/hns3_pmu.c
|
||||
|
||||
HISILICON PTT DRIVER
|
||||
M: Yicong Yang <yangyicong@hisilicon.com>
|
||||
M: Jonathan Cameron <jonathan.cameron@huawei.com>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-devices-hisi_ptt
|
||||
F: Documentation/trace/hisi-ptt.rst
|
||||
F: drivers/hwtracing/ptt/
|
||||
F: tools/perf/arch/arm64/util/hisi-ptt.c
|
||||
F: tools/perf/util/hisi-ptt*
|
||||
F: tools/perf/util/hisi-ptt-decoder/*
|
||||
|
||||
HISILICON QM DRIVER
|
||||
M: Weili Qian <qianweili@huawei.com>
|
||||
|
||||
@ -201,4 +201,39 @@ config CORESIGHT_TRBE
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called coresight-trbe.
|
||||
|
||||
config ULTRASOC_SMB
|
||||
tristate "Ultrasoc system memory buffer drivers"
|
||||
depends on ACPI || COMPILE_TEST
|
||||
depends on ARM64 && CORESIGHT_LINKS_AND_SINKS
|
||||
help
|
||||
This driver provides support for the Ultrasoc system memory buffer (SMB).
|
||||
SMB is responsible for receiving the trace data from Coresight ETM devices
|
||||
and storing them to a system buffer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called ultrasoc-smb.
|
||||
|
||||
config CORESIGHT_TPDM
|
||||
tristate "CoreSight Trace, Profiling & Diagnostics Monitor driver"
|
||||
select CORESIGHT_LINKS_AND_SINKS
|
||||
select CORESIGHT_TPDA
|
||||
help
|
||||
This driver provides support for configuring monitor. Monitors are
|
||||
primarily responsible for data set collection and support the
|
||||
ability to collect any permutation of data set types.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called coresight-tpdm.
|
||||
|
||||
config CORESIGHT_TPDA
|
||||
tristate "CoreSight Trace, Profiling & Diagnostics Aggregator driver"
|
||||
help
|
||||
This driver provides support for configuring aggregator. This is
|
||||
primarily useful for pulling the data sets from one or more
|
||||
attached monitors and pushing the resultant data out. Multiple
|
||||
monitors are connected on different input ports of TPDA.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called coresight-tpda.
|
||||
endif
|
||||
|
||||
@ -6,7 +6,7 @@ obj-$(CONFIG_CORESIGHT) += coresight.o
|
||||
coresight-y := coresight-core.o coresight-etm-perf.o coresight-platform.o \
|
||||
coresight-sysfs.o coresight-syscfg.o coresight-config.o \
|
||||
coresight-cfg-preload.o coresight-cfg-afdo.o \
|
||||
coresight-syscfg-configfs.o
|
||||
coresight-syscfg-configfs.o coresight-trace-id.o
|
||||
obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o
|
||||
coresight-tmc-y := coresight-tmc-core.o coresight-tmc-etf.o \
|
||||
coresight-tmc-etr.o
|
||||
@ -25,5 +25,8 @@ obj-$(CONFIG_CORESIGHT_CPU_DEBUG) += coresight-cpu-debug.o
|
||||
obj-$(CONFIG_CORESIGHT_CATU) += coresight-catu.o
|
||||
obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o
|
||||
obj-$(CONFIG_CORESIGHT_TRBE) += coresight-trbe.o
|
||||
obj-$(CONFIG_CORESIGHT_TPDM) += coresight-tpdm.o
|
||||
obj-$(CONFIG_CORESIGHT_TPDA) += coresight-tpda.o
|
||||
coresight-cti-y := coresight-cti-core.o coresight-cti-platform.o \
|
||||
coresight-cti-sysfs.o
|
||||
obj-$(CONFIG_ULTRASOC_SMB) += ultrasoc-smb.o
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
@ -26,6 +27,13 @@
|
||||
static DEFINE_MUTEX(coresight_mutex);
|
||||
static DEFINE_PER_CPU(struct coresight_device *, csdev_sink);
|
||||
|
||||
/*
|
||||
* Use IDR to map the hash of the source's device name
|
||||
* to the pointer of path for the source. The idr is for
|
||||
* the sources which aren't associated with CPU.
|
||||
*/
|
||||
static DEFINE_IDR(path_idr);
|
||||
|
||||
/**
|
||||
* struct coresight_node - elements of a path, from source to sink
|
||||
* @csdev: Address of an element.
|
||||
@ -42,14 +50,6 @@ struct coresight_node {
|
||||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
* When losing synchronisation a new barrier packet needs to be inserted at the
|
||||
* beginning of the data collected in a buffer. That way the decoder knows that
|
||||
@ -112,45 +112,6 @@ struct coresight_device *coresight_get_percpu_sink(int cpu)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_get_percpu_sink);
|
||||
|
||||
static int coresight_id_match(struct device *dev, void *data)
|
||||
{
|
||||
int trace_id, i_trace_id;
|
||||
struct coresight_device *csdev, *i_csdev;
|
||||
|
||||
csdev = data;
|
||||
i_csdev = to_coresight_device(dev);
|
||||
|
||||
/*
|
||||
* No need to care about oneself and components that are not
|
||||
* sources or not enabled
|
||||
*/
|
||||
if (i_csdev == csdev || !i_csdev->enable ||
|
||||
i_csdev->type != CORESIGHT_DEV_TYPE_SOURCE)
|
||||
return 0;
|
||||
|
||||
/* Get the source ID for both components */
|
||||
trace_id = source_ops(csdev)->trace_id(csdev);
|
||||
i_trace_id = source_ops(i_csdev)->trace_id(i_csdev);
|
||||
|
||||
/* All you need is one */
|
||||
if (trace_id == i_trace_id)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int coresight_source_is_unique(struct coresight_device *csdev)
|
||||
{
|
||||
int trace_id = source_ops(csdev)->trace_id(csdev);
|
||||
|
||||
/* this shouldn't happen */
|
||||
if (trace_id < 0)
|
||||
return 0;
|
||||
|
||||
return !bus_for_each_dev(&coresight_bustype, NULL,
|
||||
csdev, coresight_id_match);
|
||||
}
|
||||
|
||||
static int coresight_find_link_inport(struct coresight_device *csdev,
|
||||
struct coresight_device *parent)
|
||||
{
|
||||
@ -459,12 +420,6 @@ static int coresight_enable_source(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!coresight_source_is_unique(csdev)) {
|
||||
dev_warn(&csdev->dev, "traceID %d not unique\n",
|
||||
source_ops(csdev)->trace_id(csdev));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!csdev->enable) {
|
||||
if (source_ops(csdev)->enable) {
|
||||
ret = coresight_control_assoc_ectdev(csdev, true);
|
||||
@ -1106,7 +1061,8 @@ static int coresight_validate_source(struct coresight_device *csdev,
|
||||
}
|
||||
|
||||
if (subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_PROC &&
|
||||
subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE) {
|
||||
subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE &&
|
||||
subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS) {
|
||||
dev_err(&csdev->dev, "wrong device subtype in %s\n", function);
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -1120,6 +1076,7 @@ int coresight_enable(struct coresight_device *csdev)
|
||||
struct coresight_device *sink;
|
||||
struct list_head *path;
|
||||
enum coresight_dev_subtype_source subtype;
|
||||
u32 hash;
|
||||
|
||||
subtype = csdev->subtype.source_subtype;
|
||||
|
||||
@ -1174,7 +1131,15 @@ int coresight_enable(struct coresight_device *csdev)
|
||||
per_cpu(tracer_path, cpu) = path;
|
||||
break;
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:
|
||||
stm_path = path;
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS:
|
||||
/*
|
||||
* Use the hash of source's device name as ID
|
||||
* and map the ID to the pointer of the path.
|
||||
*/
|
||||
hash = hashlen_hash(hashlen_string(NULL, dev_name(&csdev->dev)));
|
||||
ret = idr_alloc_u32(&path_idr, path, &hash, hash, GFP_KERNEL);
|
||||
if (ret)
|
||||
goto err_source;
|
||||
break;
|
||||
default:
|
||||
/* We can't be here */
|
||||
@ -1198,6 +1163,7 @@ void coresight_disable(struct coresight_device *csdev)
|
||||
{
|
||||
int cpu, ret;
|
||||
struct list_head *path = NULL;
|
||||
u32 hash;
|
||||
|
||||
mutex_lock(&coresight_mutex);
|
||||
|
||||
@ -1215,8 +1181,15 @@ void coresight_disable(struct coresight_device *csdev)
|
||||
per_cpu(tracer_path, cpu) = NULL;
|
||||
break;
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:
|
||||
path = stm_path;
|
||||
stm_path = NULL;
|
||||
case CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS:
|
||||
hash = hashlen_hash(hashlen_string(NULL, dev_name(&csdev->dev)));
|
||||
/* Find the path by the hash. */
|
||||
path = idr_find(&path_idr, hash);
|
||||
if (path == NULL) {
|
||||
pr_err("Path is not found for %s\n", dev_name(&csdev->dev));
|
||||
goto out;
|
||||
}
|
||||
idr_remove(&path_idr, hash);
|
||||
break;
|
||||
default:
|
||||
/* We can't be here */
|
||||
|
||||
@ -107,12 +107,12 @@ static int cti_enable_hw(struct cti_drvdata *drvdata)
|
||||
cti_write_all_hw_regs(drvdata);
|
||||
|
||||
config->hw_enabled = true;
|
||||
atomic_inc(&drvdata->config.enable_req_count);
|
||||
drvdata->config.enable_req_count++;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return rc;
|
||||
|
||||
cti_state_unchanged:
|
||||
atomic_inc(&drvdata->config.enable_req_count);
|
||||
drvdata->config.enable_req_count++;
|
||||
|
||||
/* cannot enable due to error */
|
||||
cti_err_not_enabled:
|
||||
@ -129,7 +129,7 @@ static void cti_cpuhp_enable_hw(struct cti_drvdata *drvdata)
|
||||
config->hw_powered = true;
|
||||
|
||||
/* no need to do anything if no enable request */
|
||||
if (!atomic_read(&drvdata->config.enable_req_count))
|
||||
if (!drvdata->config.enable_req_count)
|
||||
goto cti_hp_not_enabled;
|
||||
|
||||
/* try to claim the device */
|
||||
@ -151,11 +151,18 @@ static int cti_disable_hw(struct cti_drvdata *drvdata)
|
||||
{
|
||||
struct cti_config *config = &drvdata->config;
|
||||
struct coresight_device *csdev = drvdata->csdev;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
|
||||
/* don't allow negative refcounts, return an error */
|
||||
if (!drvdata->config.enable_req_count) {
|
||||
ret = -EINVAL;
|
||||
goto cti_not_disabled;
|
||||
}
|
||||
|
||||
/* check refcount - disable on 0 */
|
||||
if (atomic_dec_return(&drvdata->config.enable_req_count) > 0)
|
||||
if (--drvdata->config.enable_req_count > 0)
|
||||
goto cti_not_disabled;
|
||||
|
||||
/* no need to do anything if disabled or cpu unpowered */
|
||||
@ -171,12 +178,12 @@ static int cti_disable_hw(struct cti_drvdata *drvdata)
|
||||
coresight_disclaim_device_unlocked(csdev);
|
||||
CS_LOCK(drvdata->base);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
return 0;
|
||||
return ret;
|
||||
|
||||
/* not disabled this call */
|
||||
cti_not_disabled:
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void cti_write_single_reg(struct cti_drvdata *drvdata, int offset, u32 value)
|
||||
@ -232,7 +239,7 @@ static void cti_set_default_config(struct device *dev,
|
||||
/* Most regs default to 0 as zalloc'ed except...*/
|
||||
config->trig_filter_enable = true;
|
||||
config->ctigate = GENMASK(config->nr_ctm_channels - 1, 0);
|
||||
atomic_set(&config->enable_req_count, 0);
|
||||
config->enable_req_count = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -689,7 +696,7 @@ static int cti_cpu_pm_notify(struct notifier_block *nb, unsigned long cmd,
|
||||
drvdata->config.hw_enabled = false;
|
||||
|
||||
/* check enable reference count to enable HW */
|
||||
if (atomic_read(&drvdata->config.enable_req_count)) {
|
||||
if (drvdata->config.enable_req_count) {
|
||||
/* check we can claim the device as we re-power */
|
||||
if (coresight_claim_device(csdev))
|
||||
goto cti_notify_exit;
|
||||
|
||||
@ -84,8 +84,8 @@ static ssize_t enable_show(struct device *dev,
|
||||
bool enabled, powered;
|
||||
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
enable_req = atomic_read(&drvdata->config.enable_req_count);
|
||||
spin_lock(&drvdata->spinlock);
|
||||
enable_req = drvdata->config.enable_req_count;
|
||||
powered = drvdata->config.hw_powered;
|
||||
enabled = drvdata->config.hw_enabled;
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
@ -108,10 +108,19 @@ static ssize_t enable_store(struct device *dev,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val)
|
||||
if (val) {
|
||||
ret = pm_runtime_resume_and_get(dev->parent);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = cti_enable(drvdata->csdev);
|
||||
else
|
||||
if (ret)
|
||||
pm_runtime_put(dev->parent);
|
||||
} else {
|
||||
ret = cti_disable(drvdata->csdev);
|
||||
if (!ret)
|
||||
pm_runtime_put(dev->parent);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
return size;
|
||||
|
||||
@ -141,7 +141,7 @@ struct cti_config {
|
||||
int nr_trig_max;
|
||||
|
||||
/* cti enable control */
|
||||
atomic_t enable_req_count;
|
||||
int enable_req_count;
|
||||
bool hw_enabled;
|
||||
bool hw_powered;
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/coresight-pmu.h>
|
||||
#include <linux/cpumask.h>
|
||||
@ -22,6 +23,7 @@
|
||||
#include "coresight-etm-perf.h"
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-syscfg.h"
|
||||
#include "coresight-trace-id.h"
|
||||
|
||||
static struct pmu etm_pmu;
|
||||
static bool etm_perf_up;
|
||||
@ -228,8 +230,12 @@ static void free_event_data(struct work_struct *work)
|
||||
if (!(IS_ERR_OR_NULL(*ppath)))
|
||||
coresight_release_path(*ppath);
|
||||
*ppath = NULL;
|
||||
coresight_trace_id_put_cpu_id(cpu);
|
||||
}
|
||||
|
||||
/* mark perf event as done for trace id allocator */
|
||||
coresight_trace_id_perf_stop();
|
||||
|
||||
free_percpu(event_data->path);
|
||||
kfree(event_data);
|
||||
}
|
||||
@ -300,6 +306,7 @@ static void *etm_setup_aux(struct perf_event *event, void **pages,
|
||||
{
|
||||
u32 id, cfg_hash;
|
||||
int cpu = event->cpu;
|
||||
int trace_id;
|
||||
cpumask_t *mask;
|
||||
struct coresight_device *sink = NULL;
|
||||
struct coresight_device *user_sink = NULL, *last_sink = NULL;
|
||||
@ -316,6 +323,9 @@ static void *etm_setup_aux(struct perf_event *event, void **pages,
|
||||
sink = user_sink = coresight_get_sink_by_id(id);
|
||||
}
|
||||
|
||||
/* tell the trace ID allocator that a perf event is starting up */
|
||||
coresight_trace_id_perf_start();
|
||||
|
||||
/* check if user wants a coresight configuration selected */
|
||||
cfg_hash = (u32)((event->attr.config2 & GENMASK_ULL(63, 32)) >> 32);
|
||||
if (cfg_hash) {
|
||||
@ -388,6 +398,13 @@ static void *etm_setup_aux(struct perf_event *event, void **pages,
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ensure we can allocate a trace ID for this CPU */
|
||||
trace_id = coresight_trace_id_get_cpu_id(cpu);
|
||||
if (!IS_VALID_CS_TRACE_ID(trace_id)) {
|
||||
cpumask_clear_cpu(cpu, mask);
|
||||
continue;
|
||||
}
|
||||
|
||||
*etm_event_cpu_path_ptr(event_data, cpu) = path;
|
||||
}
|
||||
|
||||
@ -432,6 +449,7 @@ static void etm_event_start(struct perf_event *event, int flags)
|
||||
struct perf_output_handle *handle = &ctxt->handle;
|
||||
struct coresight_device *sink, *csdev = per_cpu(csdev_src, cpu);
|
||||
struct list_head *path;
|
||||
u64 hw_id;
|
||||
|
||||
if (!csdev)
|
||||
goto fail;
|
||||
@ -477,6 +495,19 @@ static void etm_event_start(struct perf_event *event, int flags)
|
||||
if (source_ops(csdev)->enable(csdev, event, CS_MODE_PERF))
|
||||
goto fail_disable_path;
|
||||
|
||||
/*
|
||||
* output cpu / trace ID in perf record, once for the lifetime
|
||||
* of the event.
|
||||
*/
|
||||
if (!cpumask_test_cpu(cpu, &event_data->aux_hwid_done)) {
|
||||
cpumask_set_cpu(cpu, &event_data->aux_hwid_done);
|
||||
hw_id = FIELD_PREP(CS_AUX_HW_ID_VERSION_MASK,
|
||||
CS_AUX_HW_ID_CURR_VERSION);
|
||||
hw_id |= FIELD_PREP(CS_AUX_HW_ID_TRACE_ID_MASK,
|
||||
coresight_trace_id_read_cpu_id(cpu));
|
||||
perf_report_aux_output_id(event, hw_id);
|
||||
}
|
||||
|
||||
out:
|
||||
/* Tell the perf core the event is alive */
|
||||
event->hw.state = 0;
|
||||
|
||||
@ -48,6 +48,7 @@ struct etm_filters {
|
||||
* 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.
|
||||
* @aux_hwid_done: Whether a CPU has emitted the TraceID packet or not.
|
||||
* @snk_config: The sink configuration.
|
||||
* @cfg_hash: The hash id of any coresight config selected.
|
||||
* @path: An array of path, each slot for one CPU.
|
||||
@ -55,6 +56,7 @@ struct etm_filters {
|
||||
struct etm_event_data {
|
||||
struct work_struct work;
|
||||
cpumask_t mask;
|
||||
cpumask_t aux_hwid_done;
|
||||
void *snk_config;
|
||||
u32 cfg_hash;
|
||||
struct list_head * __percpu *path;
|
||||
|
||||
@ -283,8 +283,9 @@ static inline unsigned int etm_readl(struct etm_drvdata *drvdata, u32 off)
|
||||
}
|
||||
|
||||
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);
|
||||
int etm_read_alloc_trace_id(struct etm_drvdata *drvdata);
|
||||
void etm_release_trace_id(struct etm_drvdata *drvdata);
|
||||
#endif
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
|
||||
#include "coresight-etm.h"
|
||||
#include "coresight-etm-perf.h"
|
||||
#include "coresight-trace-id.h"
|
||||
|
||||
/*
|
||||
* Not really modular but using module_param is the easiest way to
|
||||
@ -454,52 +455,59 @@ static int etm_cpu_id(struct coresight_device *csdev)
|
||||
return drvdata->cpu;
|
||||
}
|
||||
|
||||
int etm_get_trace_id(struct etm_drvdata *drvdata)
|
||||
int etm_read_alloc_trace_id(struct etm_drvdata *drvdata)
|
||||
{
|
||||
unsigned long flags;
|
||||
int trace_id = -1;
|
||||
struct device *etm_dev;
|
||||
int trace_id;
|
||||
|
||||
if (!drvdata)
|
||||
goto out;
|
||||
|
||||
etm_dev = drvdata->csdev->dev.parent;
|
||||
if (!local_read(&drvdata->mode))
|
||||
return drvdata->traceid;
|
||||
|
||||
pm_runtime_get_sync(etm_dev);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
trace_id = (etm_readl(drvdata, ETMTRACEIDR) & ETM_TRACEID_MASK);
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
pm_runtime_put(etm_dev);
|
||||
|
||||
out:
|
||||
/*
|
||||
* This will allocate a trace ID to the cpu,
|
||||
* or return the one currently allocated.
|
||||
*
|
||||
* trace id function has its own lock
|
||||
*/
|
||||
trace_id = coresight_trace_id_get_cpu_id(drvdata->cpu);
|
||||
if (IS_VALID_CS_TRACE_ID(trace_id))
|
||||
drvdata->traceid = (u8)trace_id;
|
||||
else
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"Failed to allocate trace ID for %s on CPU%d\n",
|
||||
dev_name(&drvdata->csdev->dev), drvdata->cpu);
|
||||
return trace_id;
|
||||
|
||||
}
|
||||
|
||||
static int etm_trace_id(struct coresight_device *csdev)
|
||||
void etm_release_trace_id(struct etm_drvdata *drvdata)
|
||||
{
|
||||
struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
return etm_get_trace_id(drvdata);
|
||||
coresight_trace_id_put_cpu_id(drvdata->cpu);
|
||||
}
|
||||
|
||||
static int etm_enable_perf(struct coresight_device *csdev,
|
||||
struct perf_event *event)
|
||||
{
|
||||
struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
int trace_id;
|
||||
|
||||
if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id()))
|
||||
return -EINVAL;
|
||||
|
||||
/* Configure the tracer based on the session's specifics */
|
||||
etm_parse_event_config(drvdata, event);
|
||||
|
||||
/*
|
||||
* perf allocates cpu ids as part of _setup_aux() - device needs to use
|
||||
* the allocated ID. This reads the current version without allocation.
|
||||
*
|
||||
* This does not use the trace id lock to prevent lock_dep issues
|
||||
* with perf locks - we know the ID cannot change until perf shuts down
|
||||
* the session
|
||||
*/
|
||||
trace_id = coresight_trace_id_read_cpu_id(drvdata->cpu);
|
||||
if (!IS_VALID_CS_TRACE_ID(trace_id)) {
|
||||
dev_err(&drvdata->csdev->dev, "Failed to set trace ID for %s on CPU%d\n",
|
||||
dev_name(&drvdata->csdev->dev), drvdata->cpu);
|
||||
return -EINVAL;
|
||||
}
|
||||
drvdata->traceid = (u8)trace_id;
|
||||
|
||||
/* And enable it */
|
||||
return etm_enable_hw(drvdata);
|
||||
}
|
||||
@ -512,6 +520,11 @@ static int etm_enable_sysfs(struct coresight_device *csdev)
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
|
||||
/* sysfs needs to allocate and set a trace ID */
|
||||
ret = etm_read_alloc_trace_id(drvdata);
|
||||
if (ret < 0)
|
||||
goto unlock_enable_sysfs;
|
||||
|
||||
/*
|
||||
* Configure the ETM only if the CPU is online. If it isn't online
|
||||
* hw configuration will take place on the local CPU during bring up.
|
||||
@ -528,6 +541,10 @@ static int etm_enable_sysfs(struct coresight_device *csdev)
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
etm_release_trace_id(drvdata);
|
||||
|
||||
unlock_enable_sysfs:
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
if (!ret)
|
||||
@ -611,6 +628,12 @@ static void etm_disable_perf(struct coresight_device *csdev)
|
||||
coresight_disclaim_device_unlocked(csdev);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
/*
|
||||
* perf will release trace ids when _free_aux()
|
||||
* is called at the end of the session
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
static void etm_disable_sysfs(struct coresight_device *csdev)
|
||||
@ -635,6 +658,13 @@ static void etm_disable_sysfs(struct coresight_device *csdev)
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
cpus_read_unlock();
|
||||
|
||||
/*
|
||||
* we only release trace IDs when resetting sysfs.
|
||||
* This permits sysfs users to read the trace ID after the trace
|
||||
* session has completed. This maintains operational behaviour with
|
||||
* prior trace id allocation method
|
||||
*/
|
||||
|
||||
dev_dbg(&csdev->dev, "ETM tracing disabled\n");
|
||||
}
|
||||
|
||||
@ -671,7 +701,6 @@ static void etm_disable(struct coresight_device *csdev,
|
||||
|
||||
static const struct coresight_ops_source etm_source_ops = {
|
||||
.cpu_id = etm_cpu_id,
|
||||
.trace_id = etm_trace_id,
|
||||
.enable = etm_enable,
|
||||
.disable = etm_disable,
|
||||
};
|
||||
@ -781,11 +810,6 @@ static void etm_init_arch_data(void *info)
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void etm_init_trace_id(struct etm_drvdata *drvdata)
|
||||
{
|
||||
drvdata->traceid = coresight_get_trace_id(drvdata->cpu);
|
||||
}
|
||||
|
||||
static int __init etm_hp_setup(void)
|
||||
{
|
||||
int ret;
|
||||
@ -871,7 +895,6 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
if (etm_arch_supported(drvdata->arch) == false)
|
||||
return -EINVAL;
|
||||
|
||||
etm_init_trace_id(drvdata);
|
||||
etm_set_default(&drvdata->config);
|
||||
|
||||
pdata = coresight_get_platform_data(dev);
|
||||
|
||||
@ -85,6 +85,7 @@ static ssize_t reset_store(struct device *dev,
|
||||
}
|
||||
|
||||
etm_set_default(config);
|
||||
etm_release_trace_id(drvdata);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
}
|
||||
|
||||
@ -1189,30 +1190,16 @@ static DEVICE_ATTR_RO(cpu);
|
||||
static ssize_t traceid_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned long val;
|
||||
int trace_id;
|
||||
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
val = etm_get_trace_id(drvdata);
|
||||
trace_id = etm_read_alloc_trace_id(drvdata);
|
||||
if (trace_id < 0)
|
||||
return trace_id;
|
||||
|
||||
return sprintf(buf, "%#lx\n", val);
|
||||
return sysfs_emit(buf, "%#x\n", trace_id);
|
||||
}
|
||||
|
||||
static ssize_t traceid_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
int ret;
|
||||
unsigned long val;
|
||||
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
ret = kstrtoul(buf, 16, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
drvdata->traceid = val & ETM_TRACEID_MASK;
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(traceid);
|
||||
static DEVICE_ATTR_RO(traceid);
|
||||
|
||||
static struct attribute *coresight_etm_attrs[] = {
|
||||
&dev_attr_nr_addr_cmp.attr,
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
#include "coresight-etm4x-cfg.h"
|
||||
#include "coresight-self-hosted-trace.h"
|
||||
#include "coresight-syscfg.h"
|
||||
#include "coresight-trace-id.h"
|
||||
|
||||
static int boot_enable;
|
||||
module_param(boot_enable, int, 0444);
|
||||
@ -230,11 +231,28 @@ static int etm4_cpu_id(struct coresight_device *csdev)
|
||||
return drvdata->cpu;
|
||||
}
|
||||
|
||||
static int etm4_trace_id(struct coresight_device *csdev)
|
||||
int etm4_read_alloc_trace_id(struct etmv4_drvdata *drvdata)
|
||||
{
|
||||
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
int trace_id;
|
||||
|
||||
return drvdata->trcid;
|
||||
/*
|
||||
* This will allocate a trace ID to the cpu,
|
||||
* or return the one currently allocated.
|
||||
* The trace id function has its own lock
|
||||
*/
|
||||
trace_id = coresight_trace_id_get_cpu_id(drvdata->cpu);
|
||||
if (IS_VALID_CS_TRACE_ID(trace_id))
|
||||
drvdata->trcid = (u8)trace_id;
|
||||
else
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"Failed to allocate trace ID for %s on CPU%d\n",
|
||||
dev_name(&drvdata->csdev->dev), drvdata->cpu);
|
||||
return trace_id;
|
||||
}
|
||||
|
||||
void etm4_release_trace_id(struct etmv4_drvdata *drvdata)
|
||||
{
|
||||
coresight_trace_id_put_cpu_id(drvdata->cpu);
|
||||
}
|
||||
|
||||
struct etm4_enable_arg {
|
||||
@ -427,8 +445,10 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata)
|
||||
etm4x_relaxed_write32(csa, config->vipcssctlr, TRCVIPCSSCTLR);
|
||||
for (i = 0; i < drvdata->nrseqstate - 1; i++)
|
||||
etm4x_relaxed_write32(csa, config->seq_ctrl[i], TRCSEQEVRn(i));
|
||||
etm4x_relaxed_write32(csa, config->seq_rst, TRCSEQRSTEVR);
|
||||
etm4x_relaxed_write32(csa, config->seq_state, TRCSEQSTR);
|
||||
if (drvdata->nrseqstate) {
|
||||
etm4x_relaxed_write32(csa, config->seq_rst, TRCSEQRSTEVR);
|
||||
etm4x_relaxed_write32(csa, config->seq_state, TRCSEQSTR);
|
||||
}
|
||||
etm4x_relaxed_write32(csa, config->ext_inp, TRCEXTINSELR);
|
||||
for (i = 0; i < drvdata->nr_cntr; i++) {
|
||||
etm4x_relaxed_write32(csa, config->cntrldvr[i], TRCCNTRLDVRn(i));
|
||||
@ -720,7 +740,7 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
|
||||
static int etm4_enable_perf(struct coresight_device *csdev,
|
||||
struct perf_event *event)
|
||||
{
|
||||
int ret = 0;
|
||||
int ret = 0, trace_id;
|
||||
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id())) {
|
||||
@ -732,6 +752,24 @@ static int etm4_enable_perf(struct coresight_device *csdev,
|
||||
ret = etm4_parse_event_config(csdev, event);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* perf allocates cpu ids as part of _setup_aux() - device needs to use
|
||||
* the allocated ID. This reads the current version without allocation.
|
||||
*
|
||||
* This does not use the trace id lock to prevent lock_dep issues
|
||||
* with perf locks - we know the ID cannot change until perf shuts down
|
||||
* the session
|
||||
*/
|
||||
trace_id = coresight_trace_id_read_cpu_id(drvdata->cpu);
|
||||
if (!IS_VALID_CS_TRACE_ID(trace_id)) {
|
||||
dev_err(&drvdata->csdev->dev, "Failed to set trace ID for %s on CPU%d\n",
|
||||
dev_name(&drvdata->csdev->dev), drvdata->cpu);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
drvdata->trcid = (u8)trace_id;
|
||||
|
||||
/* And enable it */
|
||||
ret = etm4_enable_hw(drvdata);
|
||||
|
||||
@ -756,6 +794,11 @@ static int etm4_enable_sysfs(struct coresight_device *csdev)
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
|
||||
/* sysfs needs to read and allocate a trace ID */
|
||||
ret = etm4_read_alloc_trace_id(drvdata);
|
||||
if (ret < 0)
|
||||
goto unlock_sysfs_enable;
|
||||
|
||||
/*
|
||||
* Executing etm4_enable_hw on the cpu whose ETM is being enabled
|
||||
* ensures that register writes occur when cpu is powered.
|
||||
@ -767,6 +810,11 @@ static int etm4_enable_sysfs(struct coresight_device *csdev)
|
||||
ret = arg.rc;
|
||||
if (!ret)
|
||||
drvdata->sticky_enable = true;
|
||||
|
||||
if (ret)
|
||||
etm4_release_trace_id(drvdata);
|
||||
|
||||
unlock_sysfs_enable:
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
if (!ret)
|
||||
@ -898,6 +946,11 @@ static int etm4_disable_perf(struct coresight_device *csdev,
|
||||
/* TRCVICTLR::SSSTATUS, bit[9] */
|
||||
filters->ssstatus = (control & BIT(9));
|
||||
|
||||
/*
|
||||
* perf will release trace ids when _free_aux() is
|
||||
* called at the end of the session.
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -923,6 +976,13 @@ static void etm4_disable_sysfs(struct coresight_device *csdev)
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
cpus_read_unlock();
|
||||
|
||||
/*
|
||||
* we only release trace IDs when resetting sysfs.
|
||||
* This permits sysfs users to read the trace ID after the trace
|
||||
* session has completed. This maintains operational behaviour with
|
||||
* prior trace id allocation method
|
||||
*/
|
||||
|
||||
dev_dbg(&csdev->dev, "ETM tracing disabled\n");
|
||||
}
|
||||
|
||||
@ -956,7 +1016,6 @@ static void etm4_disable(struct coresight_device *csdev,
|
||||
|
||||
static const struct coresight_ops_source etm4_source_ops = {
|
||||
.cpu_id = etm4_cpu_id,
|
||||
.trace_id = etm4_trace_id,
|
||||
.enable = etm4_enable,
|
||||
.disable = etm4_disable,
|
||||
};
|
||||
@ -1565,11 +1624,6 @@ static int etm4_dying_cpu(unsigned int cpu)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void etm4_init_trace_id(struct etmv4_drvdata *drvdata)
|
||||
{
|
||||
drvdata->trcid = coresight_get_trace_id(drvdata->cpu);
|
||||
}
|
||||
|
||||
static int __etm4_cpu_save(struct etmv4_drvdata *drvdata)
|
||||
{
|
||||
int i, ret = 0;
|
||||
@ -1634,8 +1688,10 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata)
|
||||
for (i = 0; i < drvdata->nrseqstate - 1; i++)
|
||||
state->trcseqevr[i] = etm4x_read32(csa, TRCSEQEVRn(i));
|
||||
|
||||
state->trcseqrstevr = etm4x_read32(csa, TRCSEQRSTEVR);
|
||||
state->trcseqstr = etm4x_read32(csa, TRCSEQSTR);
|
||||
if (drvdata->nrseqstate) {
|
||||
state->trcseqrstevr = etm4x_read32(csa, TRCSEQRSTEVR);
|
||||
state->trcseqstr = etm4x_read32(csa, TRCSEQSTR);
|
||||
}
|
||||
state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR);
|
||||
|
||||
for (i = 0; i < drvdata->nr_cntr; i++) {
|
||||
@ -1763,8 +1819,10 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata)
|
||||
for (i = 0; i < drvdata->nrseqstate - 1; i++)
|
||||
etm4x_relaxed_write32(csa, state->trcseqevr[i], TRCSEQEVRn(i));
|
||||
|
||||
etm4x_relaxed_write32(csa, state->trcseqrstevr, TRCSEQRSTEVR);
|
||||
etm4x_relaxed_write32(csa, state->trcseqstr, TRCSEQSTR);
|
||||
if (drvdata->nrseqstate) {
|
||||
etm4x_relaxed_write32(csa, state->trcseqrstevr, TRCSEQRSTEVR);
|
||||
etm4x_relaxed_write32(csa, state->trcseqstr, TRCSEQSTR);
|
||||
}
|
||||
etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR);
|
||||
|
||||
for (i = 0; i < drvdata->nr_cntr; i++) {
|
||||
@ -1946,7 +2004,6 @@ static int etm4_add_coresight_dev(struct etm4_init_arg *init_arg)
|
||||
if (!desc.name)
|
||||
return -ENOMEM;
|
||||
|
||||
etm4_init_trace_id(drvdata);
|
||||
etm4_set_default(&drvdata->config);
|
||||
|
||||
pdata = coresight_get_platform_data(dev);
|
||||
|
||||
@ -266,10 +266,11 @@ static ssize_t reset_store(struct device *dev,
|
||||
config->vmid_mask0 = 0x0;
|
||||
config->vmid_mask1 = 0x0;
|
||||
|
||||
drvdata->trcid = drvdata->cpu + 1;
|
||||
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
/* for sysfs - only release trace id when resetting */
|
||||
etm4_release_trace_id(drvdata);
|
||||
|
||||
cscfg_csdev_reset_feats(to_coresight_device(dev));
|
||||
|
||||
return size;
|
||||
@ -2392,6 +2393,26 @@ static struct attribute *coresight_etmv4_attrs[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Trace ID allocated dynamically on enable - but also allocate on read
|
||||
* in case sysfs or perf read before enable to ensure consistent metadata
|
||||
* information for trace decode
|
||||
*/
|
||||
static ssize_t trctraceid_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int trace_id;
|
||||
struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
trace_id = etm4_read_alloc_trace_id(drvdata);
|
||||
if (trace_id < 0)
|
||||
return trace_id;
|
||||
|
||||
return sysfs_emit(buf, "0x%x\n", trace_id);
|
||||
}
|
||||
static DEVICE_ATTR_RO(trctraceid);
|
||||
|
||||
struct etmv4_reg {
|
||||
struct coresight_device *csdev;
|
||||
u32 offset;
|
||||
@ -2528,7 +2549,7 @@ static struct attribute *coresight_etmv4_mgmt_attrs[] = {
|
||||
coresight_etm4x_reg(trcpidr3, TRCPIDR3),
|
||||
coresight_etm4x_reg(trcoslsr, TRCOSLSR),
|
||||
coresight_etm4x_reg(trcconfig, TRCCONFIGR),
|
||||
coresight_etm4x_reg(trctraceid, TRCTRACEIDR),
|
||||
&dev_attr_trctraceid.attr,
|
||||
coresight_etm4x_reg(trcdevarch, TRCDEVARCH),
|
||||
NULL,
|
||||
};
|
||||
|
||||
@ -1095,4 +1095,7 @@ static inline bool etm4x_is_ete(struct etmv4_drvdata *drvdata)
|
||||
{
|
||||
return drvdata->arch >= ETM_ARCH_ETE;
|
||||
}
|
||||
|
||||
int etm4_read_alloc_trace_id(struct etmv4_drvdata *drvdata);
|
||||
void etm4_release_trace_id(struct etmv4_drvdata *drvdata);
|
||||
#endif
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
#include <linux/stm.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-trace-id.h"
|
||||
|
||||
#define STMDMASTARTR 0xc04
|
||||
#define STMDMASTOPR 0xc08
|
||||
@ -280,15 +281,7 @@ static void stm_disable(struct coresight_device *csdev,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
@ -615,24 +608,7 @@ static ssize_t traceid_show(struct device *dev,
|
||||
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);
|
||||
static DEVICE_ATTR_RO(traceid);
|
||||
|
||||
static struct attribute *coresight_stm_attrs[] = {
|
||||
&dev_attr_hwevent_enable.attr,
|
||||
@ -803,14 +779,6 @@ static void stm_init_default_data(struct stm_drvdata *drvdata)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@ -838,7 +806,7 @@ static void stm_init_generic_data(struct stm_drvdata *drvdata,
|
||||
|
||||
static int stm_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
{
|
||||
int ret;
|
||||
int ret, trace_id;
|
||||
void __iomem *base;
|
||||
struct device *dev = &adev->dev;
|
||||
struct coresight_platform_data *pdata = NULL;
|
||||
@ -922,12 +890,22 @@ static int stm_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
goto stm_unregister;
|
||||
}
|
||||
|
||||
trace_id = coresight_trace_id_get_system_id();
|
||||
if (trace_id < 0) {
|
||||
ret = trace_id;
|
||||
goto cs_unregister;
|
||||
}
|
||||
drvdata->traceid = (u8)trace_id;
|
||||
|
||||
pm_runtime_put(&adev->dev);
|
||||
|
||||
dev_info(&drvdata->csdev->dev, "%s initialized\n",
|
||||
(char *)coresight_get_uci_data(id));
|
||||
return 0;
|
||||
|
||||
cs_unregister:
|
||||
coresight_unregister(drvdata->csdev);
|
||||
|
||||
stm_unregister:
|
||||
stm_unregister_device(&drvdata->stm);
|
||||
return ret;
|
||||
@ -937,6 +915,7 @@ static void stm_remove(struct amba_device *adev)
|
||||
{
|
||||
struct stm_drvdata *drvdata = dev_get_drvdata(&adev->dev);
|
||||
|
||||
coresight_trace_id_put_system_id(drvdata->traceid);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
|
||||
stm_unregister_device(&drvdata->stm);
|
||||
|
||||
@ -31,7 +31,7 @@ DEFINE_CORESIGHT_DEVLIST(etb_devs, "tmc_etb");
|
||||
DEFINE_CORESIGHT_DEVLIST(etf_devs, "tmc_etf");
|
||||
DEFINE_CORESIGHT_DEVLIST(etr_devs, "tmc_etr");
|
||||
|
||||
void tmc_wait_for_tmcready(struct tmc_drvdata *drvdata)
|
||||
int tmc_wait_for_tmcready(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
struct coresight_device *csdev = drvdata->csdev;
|
||||
struct csdev_access *csa = &csdev->access;
|
||||
@ -40,7 +40,9 @@ void tmc_wait_for_tmcready(struct tmc_drvdata *drvdata)
|
||||
if (coresight_timeout(csa, TMC_STS, TMC_STS_TMCREADY_BIT, 1)) {
|
||||
dev_err(&csdev->dev,
|
||||
"timeout while waiting for TMC to be Ready\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tmc_flush_and_stop(struct tmc_drvdata *drvdata)
|
||||
|
||||
@ -16,12 +16,20 @@
|
||||
static int tmc_set_etf_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle);
|
||||
|
||||
static void __tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
static int __tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Wait for TMCSReady bit to be set */
|
||||
tmc_wait_for_tmcready(drvdata);
|
||||
rc = tmc_wait_for_tmcready(drvdata);
|
||||
if (rc) {
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"Failed to enable: TMC not ready\n");
|
||||
CS_LOCK(drvdata->base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI |
|
||||
@ -33,6 +41,7 @@ static void __tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
@ -42,8 +51,10 @@ static int tmc_etb_enable_hw(struct tmc_drvdata *drvdata)
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
__tmc_etb_enable_hw(drvdata);
|
||||
return 0;
|
||||
rc = __tmc_etb_enable_hw(drvdata);
|
||||
if (rc)
|
||||
coresight_disclaim_device(drvdata->csdev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata)
|
||||
@ -91,12 +102,20 @@ static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata)
|
||||
coresight_disclaim_device(drvdata->csdev);
|
||||
}
|
||||
|
||||
static void __tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
static int __tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Wait for TMCSReady bit to be set */
|
||||
tmc_wait_for_tmcready(drvdata);
|
||||
rc = tmc_wait_for_tmcready(drvdata);
|
||||
if (rc) {
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"Failed to enable : TMC is not ready\n");
|
||||
CS_LOCK(drvdata->base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
writel_relaxed(TMC_MODE_HARDWARE_FIFO, drvdata->base + TMC_MODE);
|
||||
writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI,
|
||||
@ -105,6 +124,7 @@ static void __tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
@ -114,8 +134,10 @@ static int tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
__tmc_etf_enable_hw(drvdata);
|
||||
return 0;
|
||||
rc = __tmc_etf_enable_hw(drvdata);
|
||||
if (rc)
|
||||
coresight_disclaim_device(drvdata->csdev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata)
|
||||
@ -639,6 +661,7 @@ int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata)
|
||||
char *buf = NULL;
|
||||
enum tmc_mode mode;
|
||||
unsigned long flags;
|
||||
int rc = 0;
|
||||
|
||||
/* config types are set a boot time and never change */
|
||||
if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETB &&
|
||||
@ -664,7 +687,11 @@ int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata)
|
||||
* can't be NULL.
|
||||
*/
|
||||
memset(drvdata->buf, 0, drvdata->size);
|
||||
__tmc_etb_enable_hw(drvdata);
|
||||
rc = __tmc_etb_enable_hw(drvdata);
|
||||
if (rc) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* The ETB/ETF is not tracing and the buffer was just read.
|
||||
|
||||
@ -983,15 +983,22 @@ static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata)
|
||||
etr_buf->ops->sync(etr_buf, rrp, rwp);
|
||||
}
|
||||
|
||||
static void __tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
||||
static int __tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
||||
{
|
||||
u32 axictl, sts;
|
||||
struct etr_buf *etr_buf = drvdata->etr_buf;
|
||||
int rc = 0;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Wait for TMCSReady bit to be set */
|
||||
tmc_wait_for_tmcready(drvdata);
|
||||
rc = tmc_wait_for_tmcready(drvdata);
|
||||
if (rc) {
|
||||
dev_err(&drvdata->csdev->dev,
|
||||
"Failed to enable : TMC not ready\n");
|
||||
CS_LOCK(drvdata->base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
writel_relaxed(etr_buf->size / 4, drvdata->base + TMC_RSZ);
|
||||
writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE);
|
||||
@ -1032,6 +1039,7 @@ static void __tmc_etr_enable_hw(struct tmc_drvdata *drvdata)
|
||||
tmc_enable_hw(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int tmc_etr_enable_hw(struct tmc_drvdata *drvdata,
|
||||
@ -1060,7 +1068,12 @@ static int tmc_etr_enable_hw(struct tmc_drvdata *drvdata,
|
||||
rc = coresight_claim_device(drvdata->csdev);
|
||||
if (!rc) {
|
||||
drvdata->etr_buf = etr_buf;
|
||||
__tmc_etr_enable_hw(drvdata);
|
||||
rc = __tmc_etr_enable_hw(drvdata);
|
||||
if (rc) {
|
||||
drvdata->etr_buf = NULL;
|
||||
coresight_disclaim_device(drvdata->csdev);
|
||||
tmc_etr_disable_catu(drvdata);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
||||
@ -255,7 +255,7 @@ struct tmc_sg_table {
|
||||
};
|
||||
|
||||
/* Generic functions */
|
||||
void tmc_wait_for_tmcready(struct tmc_drvdata *drvdata);
|
||||
int 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);
|
||||
|
||||
211
drivers/hwtracing/coresight/coresight-tpda.c
Normal file
211
drivers/hwtracing/coresight/coresight-tpda.c
Normal file
@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-tpda.h"
|
||||
#include "coresight-trace-id.h"
|
||||
|
||||
DEFINE_CORESIGHT_DEVLIST(tpda_devs, "tpda");
|
||||
|
||||
/* Settings pre enabling port control register */
|
||||
static void tpda_enable_pre_port(struct tpda_drvdata *drvdata)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(drvdata->base + TPDA_CR);
|
||||
val &= ~TPDA_CR_ATID;
|
||||
val |= FIELD_PREP(TPDA_CR_ATID, drvdata->atid);
|
||||
writel_relaxed(val, drvdata->base + TPDA_CR);
|
||||
}
|
||||
|
||||
static void tpda_enable_port(struct tpda_drvdata *drvdata, int port)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(drvdata->base + TPDA_Pn_CR(port));
|
||||
/* Enable the port */
|
||||
val |= TPDA_Pn_CR_ENA;
|
||||
writel_relaxed(val, drvdata->base + TPDA_Pn_CR(port));
|
||||
}
|
||||
|
||||
static void __tpda_enable(struct tpda_drvdata *drvdata, int port)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
if (!drvdata->csdev->enable)
|
||||
tpda_enable_pre_port(drvdata);
|
||||
|
||||
tpda_enable_port(drvdata, port);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tpda_enable(struct coresight_device *csdev, int inport, int outport)
|
||||
{
|
||||
struct tpda_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
if (atomic_read(&csdev->refcnt[inport]) == 0)
|
||||
__tpda_enable(drvdata, inport);
|
||||
|
||||
atomic_inc(&csdev->refcnt[inport]);
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_dbg(drvdata->dev, "TPDA inport %d enabled.\n", inport);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __tpda_disable(struct tpda_drvdata *drvdata, int port)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
val = readl_relaxed(drvdata->base + TPDA_Pn_CR(port));
|
||||
val &= ~TPDA_Pn_CR_ENA;
|
||||
writel_relaxed(val, drvdata->base + TPDA_Pn_CR(port));
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tpda_disable(struct coresight_device *csdev, int inport,
|
||||
int outport)
|
||||
{
|
||||
struct tpda_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
if (atomic_dec_return(&csdev->refcnt[inport]) == 0)
|
||||
__tpda_disable(drvdata, inport);
|
||||
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_dbg(drvdata->dev, "TPDA inport %d disabled\n", inport);
|
||||
}
|
||||
|
||||
static const struct coresight_ops_link tpda_link_ops = {
|
||||
.enable = tpda_enable,
|
||||
.disable = tpda_disable,
|
||||
};
|
||||
|
||||
static const struct coresight_ops tpda_cs_ops = {
|
||||
.link_ops = &tpda_link_ops,
|
||||
};
|
||||
|
||||
static int tpda_init_default_data(struct tpda_drvdata *drvdata)
|
||||
{
|
||||
int atid;
|
||||
/*
|
||||
* TPDA must has a unique atid. This atid can uniquely
|
||||
* identify the TPDM trace source connected to the TPDA.
|
||||
* The TPDMs which are connected to same TPDA share the
|
||||
* same trace-id. When TPDA does packetization, different
|
||||
* port will have unique channel number for decoding.
|
||||
*/
|
||||
atid = coresight_trace_id_get_system_id();
|
||||
if (atid < 0)
|
||||
return atid;
|
||||
|
||||
drvdata->atid = atid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpda_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct device *dev = &adev->dev;
|
||||
struct coresight_platform_data *pdata;
|
||||
struct tpda_drvdata *drvdata;
|
||||
struct coresight_desc desc = { 0 };
|
||||
void __iomem *base;
|
||||
|
||||
pdata = coresight_get_platform_data(dev);
|
||||
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;
|
||||
dev_set_drvdata(dev, drvdata);
|
||||
|
||||
base = devm_ioremap_resource(dev, &adev->res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
drvdata->base = base;
|
||||
|
||||
spin_lock_init(&drvdata->spinlock);
|
||||
|
||||
ret = tpda_init_default_data(drvdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
desc.name = coresight_alloc_device_name(&tpda_devs, dev);
|
||||
if (!desc.name)
|
||||
return -ENOMEM;
|
||||
desc.type = CORESIGHT_DEV_TYPE_LINK;
|
||||
desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG;
|
||||
desc.ops = &tpda_cs_ops;
|
||||
desc.pdata = adev->dev.platform_data;
|
||||
desc.dev = &adev->dev;
|
||||
desc.access = CSDEV_ACCESS_IOMEM(base);
|
||||
drvdata->csdev = coresight_register(&desc);
|
||||
if (IS_ERR(drvdata->csdev))
|
||||
return PTR_ERR(drvdata->csdev);
|
||||
|
||||
pm_runtime_put(&adev->dev);
|
||||
|
||||
dev_dbg(drvdata->dev, "TPDA initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tpda_remove(struct amba_device *adev)
|
||||
{
|
||||
struct tpda_drvdata *drvdata = dev_get_drvdata(&adev->dev);
|
||||
|
||||
coresight_trace_id_put_system_id(drvdata->atid);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Different TPDA has different periph id.
|
||||
* The difference is 0-7 bits' value. So ignore 0-7 bits.
|
||||
*/
|
||||
static struct amba_id tpda_ids[] = {
|
||||
{
|
||||
.id = 0x000f0f00,
|
||||
.mask = 0x000fff00,
|
||||
},
|
||||
{ 0, 0},
|
||||
};
|
||||
|
||||
static struct amba_driver tpda_driver = {
|
||||
.drv = {
|
||||
.name = "coresight-tpda",
|
||||
.owner = THIS_MODULE,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tpda_probe,
|
||||
.remove = tpda_remove,
|
||||
.id_table = tpda_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(tpda_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Trace, Profiling & Diagnostic Aggregator driver");
|
||||
35
drivers/hwtracing/coresight/coresight-tpda.h
Normal file
35
drivers/hwtracing/coresight/coresight-tpda.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CORESIGHT_CORESIGHT_TPDA_H
|
||||
#define _CORESIGHT_CORESIGHT_TPDA_H
|
||||
|
||||
#define TPDA_CR (0x000)
|
||||
#define TPDA_Pn_CR(n) (0x004 + (n * 4))
|
||||
/* Aggregator port enable bit */
|
||||
#define TPDA_Pn_CR_ENA BIT(0)
|
||||
|
||||
#define TPDA_MAX_INPORTS 32
|
||||
|
||||
/* Bits 6 ~ 12 is for atid value */
|
||||
#define TPDA_CR_ATID GENMASK(12, 6)
|
||||
|
||||
/**
|
||||
* struct tpda_drvdata - specifics associated to an TPDA 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: lock for the drvdata value.
|
||||
* @enable: enable status of the component.
|
||||
*/
|
||||
struct tpda_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
u8 atid;
|
||||
};
|
||||
|
||||
#endif /* _CORESIGHT_CORESIGHT_TPDA_H */
|
||||
259
drivers/hwtracing/coresight/coresight-tpdm.c
Normal file
259
drivers/hwtracing/coresight/coresight-tpdm.c
Normal file
@ -0,0 +1,259 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/coresight-pmu.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
#include "coresight-tpdm.h"
|
||||
|
||||
DEFINE_CORESIGHT_DEVLIST(tpdm_devs, "tpdm");
|
||||
|
||||
static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Set the enable bit of DSB control register to 1 */
|
||||
val = readl_relaxed(drvdata->base + TPDM_DSB_CR);
|
||||
val |= TPDM_DSB_CR_ENA;
|
||||
writel_relaxed(val, drvdata->base + TPDM_DSB_CR);
|
||||
}
|
||||
|
||||
/* TPDM enable operations */
|
||||
static void __tpdm_enable(struct tpdm_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Check if DSB datasets is present for TPDM. */
|
||||
if (drvdata->datasets & TPDM_PIDR0_DS_DSB)
|
||||
tpdm_enable_dsb(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tpdm_enable(struct coresight_device *csdev,
|
||||
struct perf_event *event, u32 mode)
|
||||
{
|
||||
struct tpdm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
if (drvdata->enable) {
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
__tpdm_enable(drvdata);
|
||||
drvdata->enable = true;
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_dbg(drvdata->dev, "TPDM tracing enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tpdm_disable_dsb(struct tpdm_drvdata *drvdata)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Set the enable bit of DSB control register to 0 */
|
||||
val = readl_relaxed(drvdata->base + TPDM_DSB_CR);
|
||||
val &= ~TPDM_DSB_CR_ENA;
|
||||
writel_relaxed(val, drvdata->base + TPDM_DSB_CR);
|
||||
}
|
||||
|
||||
/* TPDM disable operations */
|
||||
static void __tpdm_disable(struct tpdm_drvdata *drvdata)
|
||||
{
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/* Check if DSB datasets is present for TPDM. */
|
||||
if (drvdata->datasets & TPDM_PIDR0_DS_DSB)
|
||||
tpdm_disable_dsb(drvdata);
|
||||
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static void tpdm_disable(struct coresight_device *csdev,
|
||||
struct perf_event *event)
|
||||
{
|
||||
struct tpdm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
spin_lock(&drvdata->spinlock);
|
||||
if (!drvdata->enable) {
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
return;
|
||||
}
|
||||
|
||||
__tpdm_disable(drvdata);
|
||||
drvdata->enable = false;
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
|
||||
dev_dbg(drvdata->dev, "TPDM tracing disabled\n");
|
||||
}
|
||||
|
||||
static const struct coresight_ops_source tpdm_source_ops = {
|
||||
.enable = tpdm_enable,
|
||||
.disable = tpdm_disable,
|
||||
};
|
||||
|
||||
static const struct coresight_ops tpdm_cs_ops = {
|
||||
.source_ops = &tpdm_source_ops,
|
||||
};
|
||||
|
||||
static void tpdm_init_default_data(struct tpdm_drvdata *drvdata)
|
||||
{
|
||||
u32 pidr;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
/* Get the datasets present on the TPDM. */
|
||||
pidr = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR0);
|
||||
drvdata->datasets |= pidr & GENMASK(TPDM_DATASETS - 1, 0);
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
/*
|
||||
* value 1: 64 bits test data
|
||||
* value 2: 32 bits test data
|
||||
*/
|
||||
static ssize_t integration_test_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t size)
|
||||
{
|
||||
int i, ret = 0;
|
||||
unsigned long val;
|
||||
struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
ret = kstrtoul(buf, 10, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val != 1 && val != 2)
|
||||
return -EINVAL;
|
||||
|
||||
if (!drvdata->enable)
|
||||
return -EINVAL;
|
||||
|
||||
if (val == 1)
|
||||
val = ATBCNTRL_VAL_64;
|
||||
else
|
||||
val = ATBCNTRL_VAL_32;
|
||||
CS_UNLOCK(drvdata->base);
|
||||
writel_relaxed(0x1, drvdata->base + TPDM_ITCNTRL);
|
||||
|
||||
for (i = 0; i < INTEGRATION_TEST_CYCLE; i++)
|
||||
writel_relaxed(val, drvdata->base + TPDM_ITATBCNTRL);
|
||||
|
||||
writel_relaxed(0, drvdata->base + TPDM_ITCNTRL);
|
||||
CS_LOCK(drvdata->base);
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_WO(integration_test);
|
||||
|
||||
static struct attribute *tpdm_attrs[] = {
|
||||
&dev_attr_integration_test.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group tpdm_attr_grp = {
|
||||
.attrs = tpdm_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *tpdm_attr_grps[] = {
|
||||
&tpdm_attr_grp,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int tpdm_probe(struct amba_device *adev, const struct amba_id *id)
|
||||
{
|
||||
void __iomem *base;
|
||||
struct device *dev = &adev->dev;
|
||||
struct coresight_platform_data *pdata;
|
||||
struct tpdm_drvdata *drvdata;
|
||||
struct coresight_desc desc = { 0 };
|
||||
|
||||
pdata = coresight_get_platform_data(dev);
|
||||
if (IS_ERR(pdata))
|
||||
return PTR_ERR(pdata);
|
||||
adev->dev.platform_data = pdata;
|
||||
|
||||
/* driver data*/
|
||||
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
drvdata->dev = &adev->dev;
|
||||
dev_set_drvdata(dev, drvdata);
|
||||
|
||||
base = devm_ioremap_resource(dev, &adev->res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
drvdata->base = base;
|
||||
|
||||
/* Set up coresight component description */
|
||||
desc.name = coresight_alloc_device_name(&tpdm_devs, dev);
|
||||
if (!desc.name)
|
||||
return -ENOMEM;
|
||||
desc.type = CORESIGHT_DEV_TYPE_SOURCE;
|
||||
desc.subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS;
|
||||
desc.ops = &tpdm_cs_ops;
|
||||
desc.pdata = adev->dev.platform_data;
|
||||
desc.dev = &adev->dev;
|
||||
desc.access = CSDEV_ACCESS_IOMEM(base);
|
||||
desc.groups = tpdm_attr_grps;
|
||||
drvdata->csdev = coresight_register(&desc);
|
||||
if (IS_ERR(drvdata->csdev))
|
||||
return PTR_ERR(drvdata->csdev);
|
||||
|
||||
spin_lock_init(&drvdata->spinlock);
|
||||
tpdm_init_default_data(drvdata);
|
||||
/* Decrease pm refcount when probe is done.*/
|
||||
pm_runtime_put(&adev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tpdm_remove(struct amba_device *adev)
|
||||
{
|
||||
struct tpdm_drvdata *drvdata = dev_get_drvdata(&adev->dev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Different TPDM has different periph id.
|
||||
* The difference is 0-7 bits' value. So ignore 0-7 bits.
|
||||
*/
|
||||
static struct amba_id tpdm_ids[] = {
|
||||
{
|
||||
.id = 0x000f0e00,
|
||||
.mask = 0x000fff00,
|
||||
},
|
||||
{ 0, 0},
|
||||
};
|
||||
|
||||
static struct amba_driver tpdm_driver = {
|
||||
.drv = {
|
||||
.name = "coresight-tpdm",
|
||||
.owner = THIS_MODULE,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tpdm_probe,
|
||||
.id_table = tpdm_ids,
|
||||
.remove = tpdm_remove,
|
||||
};
|
||||
|
||||
module_amba_driver(tpdm_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Trace, Profiling & Diagnostic Monitor driver");
|
||||
62
drivers/hwtracing/coresight/coresight-tpdm.h
Normal file
62
drivers/hwtracing/coresight/coresight-tpdm.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CORESIGHT_CORESIGHT_TPDM_H
|
||||
#define _CORESIGHT_CORESIGHT_TPDM_H
|
||||
|
||||
/* The max number of the datasets that TPDM supports */
|
||||
#define TPDM_DATASETS 7
|
||||
|
||||
/* DSB Subunit Registers */
|
||||
#define TPDM_DSB_CR (0x780)
|
||||
/* Enable bit for DSB subunit */
|
||||
#define TPDM_DSB_CR_ENA BIT(0)
|
||||
|
||||
/* TPDM integration test registers */
|
||||
#define TPDM_ITATBCNTRL (0xEF0)
|
||||
#define TPDM_ITCNTRL (0xF00)
|
||||
|
||||
/* Register value for integration test */
|
||||
#define ATBCNTRL_VAL_32 0xC00F1409
|
||||
#define ATBCNTRL_VAL_64 0xC01F1409
|
||||
|
||||
/*
|
||||
* Number of cycles to write value when
|
||||
* integration test.
|
||||
*/
|
||||
#define INTEGRATION_TEST_CYCLE 10
|
||||
|
||||
/**
|
||||
* The bits of PERIPHIDR0 register.
|
||||
* The fields [6:0] of PERIPHIDR0 are used to determine what
|
||||
* interfaces and subunits are present on a given TPDM.
|
||||
*
|
||||
* PERIPHIDR0[0] : Fix to 1 if ImplDef subunit present, else 0
|
||||
* PERIPHIDR0[1] : Fix to 1 if DSB subunit present, else 0
|
||||
*/
|
||||
|
||||
#define TPDM_PIDR0_DS_IMPDEF BIT(0)
|
||||
#define TPDM_PIDR0_DS_DSB BIT(1)
|
||||
|
||||
/**
|
||||
* struct tpdm_drvdata - specifics associated to an TPDM 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: lock for the drvdata value.
|
||||
* @enable: enable status of the component.
|
||||
* @datasets: The datasets types present of the TPDM.
|
||||
*/
|
||||
|
||||
struct tpdm_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
bool enable;
|
||||
unsigned long datasets;
|
||||
};
|
||||
|
||||
#endif /* _CORESIGHT_CORESIGHT_TPDM_H */
|
||||
297
drivers/hwtracing/coresight/coresight-trace-id.c
Normal file
297
drivers/hwtracing/coresight/coresight-trace-id.c
Normal file
@ -0,0 +1,297 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2022, Linaro Limited, All rights reserved.
|
||||
* Author: Mike Leach <mike.leach@linaro.org>
|
||||
*/
|
||||
#include <linux/coresight-pmu.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "coresight-trace-id.h"
|
||||
|
||||
/* Default trace ID map. Used on systems that don't require per sink mappings */
|
||||
static struct coresight_trace_id_map id_map_default;
|
||||
|
||||
/* maintain a record of the mapping of IDs and pending releases per cpu */
|
||||
static DEFINE_PER_CPU(atomic_t, cpu_id) = ATOMIC_INIT(0);
|
||||
static cpumask_t cpu_id_release_pending;
|
||||
|
||||
/* perf session active counter */
|
||||
static atomic_t perf_cs_etm_session_active = ATOMIC_INIT(0);
|
||||
|
||||
/* lock to protect id_map and cpu data */
|
||||
static DEFINE_SPINLOCK(id_map_lock);
|
||||
|
||||
/* #define TRACE_ID_DEBUG 1 */
|
||||
#if defined(TRACE_ID_DEBUG) || defined(CONFIG_COMPILE_TEST)
|
||||
|
||||
static void coresight_trace_id_dump_table(struct coresight_trace_id_map *id_map,
|
||||
const char *func_name)
|
||||
{
|
||||
pr_debug("%s id_map::\n", func_name);
|
||||
pr_debug("Used = %*pb\n", CORESIGHT_TRACE_IDS_MAX, id_map->used_ids);
|
||||
pr_debug("Pend = %*pb\n", CORESIGHT_TRACE_IDS_MAX, id_map->pend_rel_ids);
|
||||
}
|
||||
#define DUMP_ID_MAP(map) coresight_trace_id_dump_table(map, __func__)
|
||||
#define DUMP_ID_CPU(cpu, id) pr_debug("%s called; cpu=%d, id=%d\n", __func__, cpu, id)
|
||||
#define DUMP_ID(id) pr_debug("%s called; id=%d\n", __func__, id)
|
||||
#define PERF_SESSION(n) pr_debug("%s perf count %d\n", __func__, n)
|
||||
#else
|
||||
#define DUMP_ID_MAP(map)
|
||||
#define DUMP_ID(id)
|
||||
#define DUMP_ID_CPU(cpu, id)
|
||||
#define PERF_SESSION(n)
|
||||
#endif
|
||||
|
||||
/* unlocked read of current trace ID value for given CPU */
|
||||
static int _coresight_trace_id_read_cpu_id(int cpu)
|
||||
{
|
||||
return atomic_read(&per_cpu(cpu_id, cpu));
|
||||
}
|
||||
|
||||
/* look for next available odd ID, return 0 if none found */
|
||||
static int coresight_trace_id_find_odd_id(struct coresight_trace_id_map *id_map)
|
||||
{
|
||||
int found_id = 0, bit = 1, next_id;
|
||||
|
||||
while ((bit < CORESIGHT_TRACE_ID_RES_TOP) && !found_id) {
|
||||
/*
|
||||
* bitmap length of CORESIGHT_TRACE_ID_RES_TOP,
|
||||
* search from offset `bit`.
|
||||
*/
|
||||
next_id = find_next_zero_bit(id_map->used_ids,
|
||||
CORESIGHT_TRACE_ID_RES_TOP, bit);
|
||||
if ((next_id < CORESIGHT_TRACE_ID_RES_TOP) && (next_id & 0x1))
|
||||
found_id = next_id;
|
||||
else
|
||||
bit = next_id + 1;
|
||||
}
|
||||
return found_id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate new ID and set in use
|
||||
*
|
||||
* if @preferred_id is a valid id then try to use that value if available.
|
||||
* if @preferred_id is not valid and @prefer_odd_id is true, try for odd id.
|
||||
*
|
||||
* Otherwise allocate next available ID.
|
||||
*/
|
||||
static int coresight_trace_id_alloc_new_id(struct coresight_trace_id_map *id_map,
|
||||
int preferred_id, bool prefer_odd_id)
|
||||
{
|
||||
int id = 0;
|
||||
|
||||
/* for backwards compatibility, cpu IDs may use preferred value */
|
||||
if (IS_VALID_CS_TRACE_ID(preferred_id) &&
|
||||
!test_bit(preferred_id, id_map->used_ids)) {
|
||||
id = preferred_id;
|
||||
goto trace_id_allocated;
|
||||
} else if (prefer_odd_id) {
|
||||
/* may use odd ids to avoid preferred legacy cpu IDs */
|
||||
id = coresight_trace_id_find_odd_id(id_map);
|
||||
if (id)
|
||||
goto trace_id_allocated;
|
||||
}
|
||||
|
||||
/*
|
||||
* skip reserved bit 0, look at bitmap length of
|
||||
* CORESIGHT_TRACE_ID_RES_TOP from offset of bit 1.
|
||||
*/
|
||||
id = find_next_zero_bit(id_map->used_ids, CORESIGHT_TRACE_ID_RES_TOP, 1);
|
||||
if (id >= CORESIGHT_TRACE_ID_RES_TOP)
|
||||
return -EINVAL;
|
||||
|
||||
/* mark as used */
|
||||
trace_id_allocated:
|
||||
set_bit(id, id_map->used_ids);
|
||||
return id;
|
||||
}
|
||||
|
||||
static void coresight_trace_id_free(int id, struct coresight_trace_id_map *id_map)
|
||||
{
|
||||
if (WARN(!IS_VALID_CS_TRACE_ID(id), "Invalid Trace ID %d\n", id))
|
||||
return;
|
||||
if (WARN(!test_bit(id, id_map->used_ids), "Freeing unused ID %d\n", id))
|
||||
return;
|
||||
clear_bit(id, id_map->used_ids);
|
||||
}
|
||||
|
||||
static void coresight_trace_id_set_pend_rel(int id, struct coresight_trace_id_map *id_map)
|
||||
{
|
||||
if (WARN(!IS_VALID_CS_TRACE_ID(id), "Invalid Trace ID %d\n", id))
|
||||
return;
|
||||
set_bit(id, id_map->pend_rel_ids);
|
||||
}
|
||||
|
||||
/*
|
||||
* release all pending IDs for all current maps & clear CPU associations
|
||||
*
|
||||
* This currently operates on the default id map, but may be extended to
|
||||
* operate on all registered id maps if per sink id maps are used.
|
||||
*/
|
||||
static void coresight_trace_id_release_all_pending(void)
|
||||
{
|
||||
struct coresight_trace_id_map *id_map = &id_map_default;
|
||||
unsigned long flags;
|
||||
int cpu, bit;
|
||||
|
||||
spin_lock_irqsave(&id_map_lock, flags);
|
||||
for_each_set_bit(bit, id_map->pend_rel_ids, CORESIGHT_TRACE_ID_RES_TOP) {
|
||||
clear_bit(bit, id_map->used_ids);
|
||||
clear_bit(bit, id_map->pend_rel_ids);
|
||||
}
|
||||
for_each_cpu(cpu, &cpu_id_release_pending) {
|
||||
atomic_set(&per_cpu(cpu_id, cpu), 0);
|
||||
cpumask_clear_cpu(cpu, &cpu_id_release_pending);
|
||||
}
|
||||
spin_unlock_irqrestore(&id_map_lock, flags);
|
||||
DUMP_ID_MAP(id_map);
|
||||
}
|
||||
|
||||
static int coresight_trace_id_map_get_cpu_id(int cpu, struct coresight_trace_id_map *id_map)
|
||||
{
|
||||
unsigned long flags;
|
||||
int id;
|
||||
|
||||
spin_lock_irqsave(&id_map_lock, flags);
|
||||
|
||||
/* check for existing allocation for this CPU */
|
||||
id = _coresight_trace_id_read_cpu_id(cpu);
|
||||
if (id)
|
||||
goto get_cpu_id_clr_pend;
|
||||
|
||||
/*
|
||||
* Find a new ID.
|
||||
*
|
||||
* Use legacy values where possible in the dynamic trace ID allocator to
|
||||
* allow older tools to continue working if they are not upgraded at the
|
||||
* same time as the kernel drivers.
|
||||
*
|
||||
* If the generated legacy ID is invalid, or not available then the next
|
||||
* available dynamic ID will be used.
|
||||
*/
|
||||
id = coresight_trace_id_alloc_new_id(id_map,
|
||||
CORESIGHT_LEGACY_CPU_TRACE_ID(cpu),
|
||||
false);
|
||||
if (!IS_VALID_CS_TRACE_ID(id))
|
||||
goto get_cpu_id_out_unlock;
|
||||
|
||||
/* allocate the new id to the cpu */
|
||||
atomic_set(&per_cpu(cpu_id, cpu), id);
|
||||
|
||||
get_cpu_id_clr_pend:
|
||||
/* we are (re)using this ID - so ensure it is not marked for release */
|
||||
cpumask_clear_cpu(cpu, &cpu_id_release_pending);
|
||||
clear_bit(id, id_map->pend_rel_ids);
|
||||
|
||||
get_cpu_id_out_unlock:
|
||||
spin_unlock_irqrestore(&id_map_lock, flags);
|
||||
|
||||
DUMP_ID_CPU(cpu, id);
|
||||
DUMP_ID_MAP(id_map);
|
||||
return id;
|
||||
}
|
||||
|
||||
static void coresight_trace_id_map_put_cpu_id(int cpu, struct coresight_trace_id_map *id_map)
|
||||
{
|
||||
unsigned long flags;
|
||||
int id;
|
||||
|
||||
/* check for existing allocation for this CPU */
|
||||
id = _coresight_trace_id_read_cpu_id(cpu);
|
||||
if (!id)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&id_map_lock, flags);
|
||||
|
||||
if (atomic_read(&perf_cs_etm_session_active)) {
|
||||
/* set release at pending if perf still active */
|
||||
coresight_trace_id_set_pend_rel(id, id_map);
|
||||
cpumask_set_cpu(cpu, &cpu_id_release_pending);
|
||||
} else {
|
||||
/* otherwise clear id */
|
||||
coresight_trace_id_free(id, id_map);
|
||||
atomic_set(&per_cpu(cpu_id, cpu), 0);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&id_map_lock, flags);
|
||||
DUMP_ID_CPU(cpu, id);
|
||||
DUMP_ID_MAP(id_map);
|
||||
}
|
||||
|
||||
static int coresight_trace_id_map_get_system_id(struct coresight_trace_id_map *id_map)
|
||||
{
|
||||
unsigned long flags;
|
||||
int id;
|
||||
|
||||
spin_lock_irqsave(&id_map_lock, flags);
|
||||
/* prefer odd IDs for system components to avoid legacy CPU IDS */
|
||||
id = coresight_trace_id_alloc_new_id(id_map, 0, true);
|
||||
spin_unlock_irqrestore(&id_map_lock, flags);
|
||||
|
||||
DUMP_ID(id);
|
||||
DUMP_ID_MAP(id_map);
|
||||
return id;
|
||||
}
|
||||
|
||||
static void coresight_trace_id_map_put_system_id(struct coresight_trace_id_map *id_map, int id)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&id_map_lock, flags);
|
||||
coresight_trace_id_free(id, id_map);
|
||||
spin_unlock_irqrestore(&id_map_lock, flags);
|
||||
|
||||
DUMP_ID(id);
|
||||
DUMP_ID_MAP(id_map);
|
||||
}
|
||||
|
||||
/* API functions */
|
||||
|
||||
int coresight_trace_id_get_cpu_id(int cpu)
|
||||
{
|
||||
return coresight_trace_id_map_get_cpu_id(cpu, &id_map_default);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_trace_id_get_cpu_id);
|
||||
|
||||
void coresight_trace_id_put_cpu_id(int cpu)
|
||||
{
|
||||
coresight_trace_id_map_put_cpu_id(cpu, &id_map_default);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_trace_id_put_cpu_id);
|
||||
|
||||
int coresight_trace_id_read_cpu_id(int cpu)
|
||||
{
|
||||
return _coresight_trace_id_read_cpu_id(cpu);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_trace_id_read_cpu_id);
|
||||
|
||||
int coresight_trace_id_get_system_id(void)
|
||||
{
|
||||
return coresight_trace_id_map_get_system_id(&id_map_default);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_trace_id_get_system_id);
|
||||
|
||||
void coresight_trace_id_put_system_id(int id)
|
||||
{
|
||||
coresight_trace_id_map_put_system_id(&id_map_default, id);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_trace_id_put_system_id);
|
||||
|
||||
void coresight_trace_id_perf_start(void)
|
||||
{
|
||||
atomic_inc(&perf_cs_etm_session_active);
|
||||
PERF_SESSION(atomic_read(&perf_cs_etm_session_active));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_trace_id_perf_start);
|
||||
|
||||
void coresight_trace_id_perf_stop(void)
|
||||
{
|
||||
if (!atomic_dec_return(&perf_cs_etm_session_active))
|
||||
coresight_trace_id_release_all_pending();
|
||||
PERF_SESSION(atomic_read(&perf_cs_etm_session_active));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_trace_id_perf_stop);
|
||||
156
drivers/hwtracing/coresight/coresight-trace-id.h
Normal file
156
drivers/hwtracing/coresight/coresight-trace-id.h
Normal file
@ -0,0 +1,156 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright(C) 2022 Linaro Limited. All rights reserved.
|
||||
* Author: Mike Leach <mike.leach@linaro.org>
|
||||
*/
|
||||
|
||||
#ifndef _CORESIGHT_TRACE_ID_H
|
||||
#define _CORESIGHT_TRACE_ID_H
|
||||
|
||||
/*
|
||||
* Coresight trace ID allocation API
|
||||
*
|
||||
* With multi cpu systems, and more additional trace sources a scalable
|
||||
* trace ID reservation system is required.
|
||||
*
|
||||
* The system will allocate Ids on a demand basis, and allow them to be
|
||||
* released when done.
|
||||
*
|
||||
* In order to ensure that a consistent cpu / ID matching is maintained
|
||||
* throughout a perf cs_etm event session - a session in progress flag will
|
||||
* be maintained, and released IDs not cleared until the perf session is
|
||||
* complete. This allows the same CPU to be re-allocated its prior ID.
|
||||
*
|
||||
*
|
||||
* Trace ID maps will be created and initialised to prevent architecturally
|
||||
* reserved IDs from being allocated.
|
||||
*
|
||||
* API permits multiple maps to be maintained - for large systems where
|
||||
* different sets of cpus trace into different independent sinks.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
|
||||
/* architecturally we have 128 IDs some of which are reserved */
|
||||
#define CORESIGHT_TRACE_IDS_MAX 128
|
||||
|
||||
/* ID 0 is reserved */
|
||||
#define CORESIGHT_TRACE_ID_RES_0 0
|
||||
|
||||
/* ID 0x70 onwards are reserved */
|
||||
#define CORESIGHT_TRACE_ID_RES_TOP 0x70
|
||||
|
||||
/* check an ID is in the valid range */
|
||||
#define IS_VALID_CS_TRACE_ID(id) \
|
||||
((id > CORESIGHT_TRACE_ID_RES_0) && (id < CORESIGHT_TRACE_ID_RES_TOP))
|
||||
|
||||
/**
|
||||
* Trace ID map.
|
||||
*
|
||||
* @used_ids: Bitmap to register available (bit = 0) and in use (bit = 1) IDs.
|
||||
* Initialised so that the reserved IDs are permanently marked as
|
||||
* in use.
|
||||
* @pend_rel_ids: CPU IDs that have been released by the trace source but not
|
||||
* yet marked as available, to allow re-allocation to the same
|
||||
* CPU during a perf session.
|
||||
*/
|
||||
struct coresight_trace_id_map {
|
||||
DECLARE_BITMAP(used_ids, CORESIGHT_TRACE_IDS_MAX);
|
||||
DECLARE_BITMAP(pend_rel_ids, CORESIGHT_TRACE_IDS_MAX);
|
||||
};
|
||||
|
||||
/* Allocate and release IDs for a single default trace ID map */
|
||||
|
||||
/**
|
||||
* Read and optionally allocate a CoreSight trace ID and associate with a CPU.
|
||||
*
|
||||
* Function will read the current trace ID for the associated CPU,
|
||||
* allocating an new ID if one is not currently allocated.
|
||||
*
|
||||
* Numeric ID values allocated use legacy allocation algorithm if possible,
|
||||
* otherwise any available ID is used.
|
||||
*
|
||||
* @cpu: The CPU index to allocate for.
|
||||
*
|
||||
* return: CoreSight trace ID or -EINVAL if allocation impossible.
|
||||
*/
|
||||
int coresight_trace_id_get_cpu_id(int cpu);
|
||||
|
||||
/**
|
||||
* Release an allocated trace ID associated with the CPU.
|
||||
*
|
||||
* This will release the CoreSight trace ID associated with the CPU,
|
||||
* unless a perf session is in operation.
|
||||
*
|
||||
* If a perf session is in operation then the ID will be marked as pending
|
||||
* release.
|
||||
*
|
||||
* @cpu: The CPU index to release the associated trace ID.
|
||||
*/
|
||||
void coresight_trace_id_put_cpu_id(int cpu);
|
||||
|
||||
/**
|
||||
* Read the current allocated CoreSight Trace ID value for the CPU.
|
||||
*
|
||||
* Fast read of the current value that does not allocate if no ID allocated
|
||||
* for the CPU.
|
||||
*
|
||||
* Used in perf context where it is known that the value for the CPU will not
|
||||
* be changing, when perf starts and event on a core and outputs the Trace ID
|
||||
* for the CPU as a packet in the data file. IDs cannot change during a perf
|
||||
* session.
|
||||
*
|
||||
* This function does not take the lock protecting the ID lists, avoiding
|
||||
* locking dependency issues with perf locks.
|
||||
*
|
||||
* @cpu: The CPU index to read.
|
||||
*
|
||||
* return: current value, will be 0 if unallocated.
|
||||
*/
|
||||
int coresight_trace_id_read_cpu_id(int cpu);
|
||||
|
||||
/**
|
||||
* Allocate a CoreSight trace ID for a system component.
|
||||
*
|
||||
* Unconditionally allocates a Trace ID, without associating the ID with a CPU.
|
||||
*
|
||||
* Used to allocate IDs for system trace sources such as STM.
|
||||
*
|
||||
* return: Trace ID or -EINVAL if allocation is impossible.
|
||||
*/
|
||||
int coresight_trace_id_get_system_id(void);
|
||||
|
||||
/**
|
||||
* Release an allocated system trace ID.
|
||||
*
|
||||
* Unconditionally release a trace ID allocated to a system component.
|
||||
*
|
||||
* @id: value of trace ID allocated.
|
||||
*/
|
||||
void coresight_trace_id_put_system_id(int id);
|
||||
|
||||
/* notifiers for perf session start and stop */
|
||||
|
||||
/**
|
||||
* Notify the Trace ID allocator that a perf session is starting.
|
||||
*
|
||||
* Increase the perf session reference count - called by perf when setting up
|
||||
* a trace event.
|
||||
*
|
||||
* This reference count is used by the ID allocator to ensure that trace IDs
|
||||
* associated with a CPU cannot change or be released during a perf session.
|
||||
*/
|
||||
void coresight_trace_id_perf_start(void);
|
||||
|
||||
/**
|
||||
* Notify the ID allocator that a perf session is stopping.
|
||||
*
|
||||
* Decrease the perf session reference count.
|
||||
* if this causes the count to go to zero, then all Trace IDs marked as pending
|
||||
* release, will be released.
|
||||
*/
|
||||
void coresight_trace_id_perf_stop(void);
|
||||
|
||||
#endif /* _CORESIGHT_TRACE_ID_H */
|
||||
648
drivers/hwtracing/coresight/ultrasoc-smb.c
Normal file
648
drivers/hwtracing/coresight/ultrasoc-smb.c
Normal file
@ -0,0 +1,648 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
|
||||
/*
|
||||
* Siemens System Memory Buffer driver.
|
||||
* Copyright(c) 2022, HiSilicon Limited.
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "coresight-etm-perf.h"
|
||||
#include "coresight-priv.h"
|
||||
#include "ultrasoc-smb.h"
|
||||
|
||||
DEFINE_CORESIGHT_DEVLIST(sink_devs, "ultra_smb");
|
||||
|
||||
#define ULTRASOC_SMB_DSM_UUID "82ae1283-7f6a-4cbe-aa06-53e8fb24db18"
|
||||
|
||||
static bool smb_buffer_not_empty(struct smb_drv_data *drvdata)
|
||||
{
|
||||
u32 buf_status = readl(drvdata->base + SMB_LB_INT_STS_REG);
|
||||
|
||||
return FIELD_GET(SMB_LB_INT_STS_NOT_EMPTY_MSK, buf_status);
|
||||
}
|
||||
|
||||
static void smb_update_data_size(struct smb_drv_data *drvdata)
|
||||
{
|
||||
struct smb_data_buffer *sdb = &drvdata->sdb;
|
||||
u32 buf_wrptr;
|
||||
|
||||
buf_wrptr = readl(drvdata->base + SMB_LB_WR_ADDR_REG) -
|
||||
sdb->buf_hw_base;
|
||||
|
||||
/* Buffer is full */
|
||||
if (buf_wrptr == sdb->buf_rdptr && smb_buffer_not_empty(drvdata)) {
|
||||
sdb->data_size = sdb->buf_size;
|
||||
return;
|
||||
}
|
||||
|
||||
/* The buffer mode is circular buffer mode */
|
||||
sdb->data_size = CIRC_CNT(buf_wrptr, sdb->buf_rdptr,
|
||||
sdb->buf_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* The read pointer adds @nbytes bytes (may round up to the beginning)
|
||||
* after the data is read or discarded, while needing to update the
|
||||
* available data size.
|
||||
*/
|
||||
static void smb_update_read_ptr(struct smb_drv_data *drvdata, u32 nbytes)
|
||||
{
|
||||
struct smb_data_buffer *sdb = &drvdata->sdb;
|
||||
|
||||
sdb->buf_rdptr += nbytes;
|
||||
sdb->buf_rdptr %= sdb->buf_size;
|
||||
writel(sdb->buf_hw_base + sdb->buf_rdptr,
|
||||
drvdata->base + SMB_LB_RD_ADDR_REG);
|
||||
|
||||
sdb->data_size -= nbytes;
|
||||
}
|
||||
|
||||
static void smb_reset_buffer(struct smb_drv_data *drvdata)
|
||||
{
|
||||
struct smb_data_buffer *sdb = &drvdata->sdb;
|
||||
u32 write_ptr;
|
||||
|
||||
/*
|
||||
* We must flush and discard any data left in hardware path
|
||||
* to avoid corrupting the next session.
|
||||
* Note: The write pointer will never exceed the read pointer.
|
||||
*/
|
||||
writel(SMB_LB_PURGE_PURGED, drvdata->base + SMB_LB_PURGE_REG);
|
||||
|
||||
/* Reset SMB logical buffer status flags */
|
||||
writel(SMB_LB_INT_STS_RESET, drvdata->base + SMB_LB_INT_STS_REG);
|
||||
|
||||
write_ptr = readl(drvdata->base + SMB_LB_WR_ADDR_REG);
|
||||
|
||||
/* Do nothing, not data left in hardware path */
|
||||
if (!write_ptr || write_ptr == sdb->buf_rdptr + sdb->buf_hw_base)
|
||||
return;
|
||||
|
||||
/*
|
||||
* The SMB_LB_WR_ADDR_REG register is read-only,
|
||||
* Synchronize the read pointer to write pointer.
|
||||
*/
|
||||
writel(write_ptr, drvdata->base + SMB_LB_RD_ADDR_REG);
|
||||
sdb->buf_rdptr = write_ptr - sdb->buf_hw_base;
|
||||
}
|
||||
|
||||
static int smb_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct smb_drv_data *drvdata = container_of(file->private_data,
|
||||
struct smb_drv_data, miscdev);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&drvdata->mutex);
|
||||
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (atomic_read(drvdata->csdev->refcnt)) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
smb_update_data_size(drvdata);
|
||||
|
||||
drvdata->reading = true;
|
||||
out:
|
||||
mutex_unlock(&drvdata->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t smb_read(struct file *file, char __user *data, size_t len,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct smb_drv_data *drvdata = container_of(file->private_data,
|
||||
struct smb_drv_data, miscdev);
|
||||
struct smb_data_buffer *sdb = &drvdata->sdb;
|
||||
struct device *dev = &drvdata->csdev->dev;
|
||||
ssize_t to_copy = 0;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&drvdata->mutex);
|
||||
|
||||
if (!sdb->data_size)
|
||||
goto out;
|
||||
|
||||
to_copy = min(sdb->data_size, len);
|
||||
|
||||
/* Copy parts of trace data when read pointer wrap around SMB buffer */
|
||||
if (sdb->buf_rdptr + to_copy > sdb->buf_size)
|
||||
to_copy = sdb->buf_size - sdb->buf_rdptr;
|
||||
|
||||
if (copy_to_user(data, sdb->buf_base + sdb->buf_rdptr, to_copy)) {
|
||||
dev_dbg(dev, "Failed to copy data to user\n");
|
||||
to_copy = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*ppos += to_copy;
|
||||
|
||||
smb_update_read_ptr(drvdata, to_copy);
|
||||
|
||||
dev_dbg(dev, "%zu bytes copied\n", to_copy);
|
||||
out:
|
||||
if (!sdb->data_size)
|
||||
smb_reset_buffer(drvdata);
|
||||
mutex_unlock(&drvdata->mutex);
|
||||
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
static int smb_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct smb_drv_data *drvdata = container_of(file->private_data,
|
||||
struct smb_drv_data, miscdev);
|
||||
|
||||
mutex_lock(&drvdata->mutex);
|
||||
drvdata->reading = false;
|
||||
mutex_unlock(&drvdata->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations smb_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = smb_open,
|
||||
.read = smb_read,
|
||||
.release = smb_release,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static ssize_t buf_size_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct smb_drv_data *drvdata = dev_get_drvdata(dev->parent);
|
||||
|
||||
return sysfs_emit(buf, "0x%lx\n", drvdata->sdb.buf_size);
|
||||
}
|
||||
static DEVICE_ATTR_RO(buf_size);
|
||||
|
||||
static struct attribute *smb_sink_attrs[] = {
|
||||
coresight_simple_reg32(read_pos, SMB_LB_RD_ADDR_REG),
|
||||
coresight_simple_reg32(write_pos, SMB_LB_WR_ADDR_REG),
|
||||
coresight_simple_reg32(buf_status, SMB_LB_INT_STS_REG),
|
||||
&dev_attr_buf_size.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group smb_sink_group = {
|
||||
.attrs = smb_sink_attrs,
|
||||
.name = "mgmt",
|
||||
};
|
||||
|
||||
static const struct attribute_group *smb_sink_groups[] = {
|
||||
&smb_sink_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
static void smb_enable_hw(struct smb_drv_data *drvdata)
|
||||
{
|
||||
writel(SMB_GLB_EN_HW_ENABLE, drvdata->base + SMB_GLB_EN_REG);
|
||||
}
|
||||
|
||||
static void smb_disable_hw(struct smb_drv_data *drvdata)
|
||||
{
|
||||
writel(0x0, drvdata->base + SMB_GLB_EN_REG);
|
||||
}
|
||||
|
||||
static void smb_enable_sysfs(struct coresight_device *csdev)
|
||||
{
|
||||
struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (drvdata->mode != CS_MODE_DISABLED)
|
||||
return;
|
||||
|
||||
smb_enable_hw(drvdata);
|
||||
drvdata->mode = CS_MODE_SYSFS;
|
||||
}
|
||||
|
||||
static int smb_enable_perf(struct coresight_device *csdev, void *data)
|
||||
{
|
||||
struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct perf_output_handle *handle = data;
|
||||
struct cs_buffers *buf = etm_perf_sink_config(handle);
|
||||
pid_t pid;
|
||||
|
||||
if (!buf)
|
||||
return -EINVAL;
|
||||
|
||||
/* Get a handle on the pid of the target process */
|
||||
pid = buf->pid;
|
||||
|
||||
/* Device is already in used by other session */
|
||||
if (drvdata->pid != -1 && drvdata->pid != pid)
|
||||
return -EBUSY;
|
||||
|
||||
if (drvdata->pid == -1) {
|
||||
smb_enable_hw(drvdata);
|
||||
drvdata->pid = pid;
|
||||
drvdata->mode = CS_MODE_PERF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smb_enable(struct coresight_device *csdev, u32 mode, void *data)
|
||||
{
|
||||
struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&drvdata->mutex);
|
||||
|
||||
/* Do nothing, the trace data is reading by other interface now */
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Do nothing, the SMB is already enabled as other mode */
|
||||
if (drvdata->mode != CS_MODE_DISABLED && drvdata->mode != mode) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case CS_MODE_SYSFS:
|
||||
smb_enable_sysfs(csdev);
|
||||
break;
|
||||
case CS_MODE_PERF:
|
||||
ret = smb_enable_perf(csdev, data);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
atomic_inc(csdev->refcnt);
|
||||
|
||||
dev_dbg(&csdev->dev, "Ultrasoc SMB enabled\n");
|
||||
out:
|
||||
mutex_unlock(&drvdata->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int smb_disable(struct coresight_device *csdev)
|
||||
{
|
||||
struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&drvdata->mutex);
|
||||
|
||||
if (drvdata->reading) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (atomic_dec_return(csdev->refcnt)) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Complain if we (somehow) got out of sync */
|
||||
WARN_ON_ONCE(drvdata->mode == CS_MODE_DISABLED);
|
||||
|
||||
smb_disable_hw(drvdata);
|
||||
|
||||
/* Dissociate from the target process. */
|
||||
drvdata->pid = -1;
|
||||
drvdata->mode = CS_MODE_DISABLED;
|
||||
|
||||
dev_dbg(&csdev->dev, "Ultrasoc SMB disabled\n");
|
||||
out:
|
||||
mutex_unlock(&drvdata->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *smb_alloc_buffer(struct coresight_device *csdev,
|
||||
struct perf_event *event, void **pages,
|
||||
int nr_pages, bool overwrite)
|
||||
{
|
||||
struct cs_buffers *buf;
|
||||
int node;
|
||||
|
||||
node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->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;
|
||||
buf->pid = task_pid_nr(event->owner);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void smb_free_buffer(void *config)
|
||||
{
|
||||
struct cs_buffers *buf = config;
|
||||
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static void smb_sync_perf_buffer(struct smb_drv_data *drvdata,
|
||||
struct cs_buffers *buf,
|
||||
unsigned long head)
|
||||
{
|
||||
struct smb_data_buffer *sdb = &drvdata->sdb;
|
||||
char **dst_pages = (char **)buf->data_pages;
|
||||
unsigned long to_copy;
|
||||
long pg_idx, pg_offset;
|
||||
|
||||
pg_idx = head >> PAGE_SHIFT;
|
||||
pg_offset = head & (PAGE_SIZE - 1);
|
||||
|
||||
while (sdb->data_size) {
|
||||
unsigned long pg_space = PAGE_SIZE - pg_offset;
|
||||
|
||||
to_copy = min(sdb->data_size, pg_space);
|
||||
|
||||
/* Copy parts of trace data when read pointer wrap around */
|
||||
if (sdb->buf_rdptr + to_copy > sdb->buf_size)
|
||||
to_copy = sdb->buf_size - sdb->buf_rdptr;
|
||||
|
||||
memcpy(dst_pages[pg_idx] + pg_offset,
|
||||
sdb->buf_base + sdb->buf_rdptr, to_copy);
|
||||
|
||||
pg_offset += to_copy;
|
||||
if (pg_offset >= PAGE_SIZE) {
|
||||
pg_offset = 0;
|
||||
pg_idx++;
|
||||
pg_idx %= buf->nr_pages;
|
||||
}
|
||||
smb_update_read_ptr(drvdata, to_copy);
|
||||
}
|
||||
|
||||
smb_reset_buffer(drvdata);
|
||||
}
|
||||
|
||||
static unsigned long smb_update_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
struct smb_data_buffer *sdb = &drvdata->sdb;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
unsigned long data_size = 0;
|
||||
bool lost = false;
|
||||
|
||||
if (!buf)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&drvdata->mutex);
|
||||
|
||||
/* Don't do anything if another tracer is using this sink. */
|
||||
if (atomic_read(csdev->refcnt) != 1)
|
||||
goto out;
|
||||
|
||||
smb_disable_hw(drvdata);
|
||||
smb_update_data_size(drvdata);
|
||||
|
||||
/*
|
||||
* The SMB buffer may be bigger than the space available in the
|
||||
* perf ring buffer (handle->size). If so advance the offset so
|
||||
* that we get the latest trace data.
|
||||
*/
|
||||
if (sdb->data_size > handle->size) {
|
||||
smb_update_read_ptr(drvdata, sdb->data_size - handle->size);
|
||||
lost = true;
|
||||
}
|
||||
|
||||
data_size = sdb->data_size;
|
||||
smb_sync_perf_buffer(drvdata, buf, handle->head);
|
||||
if (!buf->snapshot && lost)
|
||||
perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
|
||||
out:
|
||||
mutex_unlock(&drvdata->mutex);
|
||||
|
||||
return data_size;
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink smb_cs_ops = {
|
||||
.enable = smb_enable,
|
||||
.disable = smb_disable,
|
||||
.alloc_buffer = smb_alloc_buffer,
|
||||
.free_buffer = smb_free_buffer,
|
||||
.update_buffer = smb_update_buffer,
|
||||
};
|
||||
|
||||
static const struct coresight_ops cs_ops = {
|
||||
.sink_ops = &smb_cs_ops,
|
||||
};
|
||||
|
||||
static int smb_init_data_buffer(struct platform_device *pdev,
|
||||
struct smb_data_buffer *sdb)
|
||||
{
|
||||
struct resource *res;
|
||||
void *base;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, SMB_BUF_ADDR_RES);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "SMB device failed to get resource\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sdb->buf_rdptr = 0;
|
||||
sdb->buf_hw_base = FIELD_GET(SMB_BUF_ADDR_LO_MSK, res->start);
|
||||
sdb->buf_size = resource_size(res);
|
||||
if (sdb->buf_size == 0)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* This is a chunk of memory, use classic mapping with better
|
||||
* performance.
|
||||
*/
|
||||
base = devm_memremap(&pdev->dev, sdb->buf_hw_base, sdb->buf_size,
|
||||
MEMREMAP_WB);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
sdb->buf_base = base;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void smb_init_hw(struct smb_drv_data *drvdata)
|
||||
{
|
||||
smb_disable_hw(drvdata);
|
||||
smb_reset_buffer(drvdata);
|
||||
|
||||
writel(SMB_LB_CFG_LO_DEFAULT, drvdata->base + SMB_LB_CFG_LO_REG);
|
||||
writel(SMB_LB_CFG_HI_DEFAULT, drvdata->base + SMB_LB_CFG_HI_REG);
|
||||
writel(SMB_GLB_CFG_DEFAULT, drvdata->base + SMB_GLB_CFG_REG);
|
||||
writel(SMB_GLB_INT_CFG, drvdata->base + SMB_GLB_INT_REG);
|
||||
writel(SMB_LB_INT_CTRL_CFG, drvdata->base + SMB_LB_INT_CTRL_REG);
|
||||
}
|
||||
|
||||
static int smb_register_sink(struct platform_device *pdev,
|
||||
struct smb_drv_data *drvdata)
|
||||
{
|
||||
struct coresight_platform_data *pdata = NULL;
|
||||
struct coresight_desc desc = { 0 };
|
||||
int ret;
|
||||
|
||||
pdata = coresight_get_platform_data(&pdev->dev);
|
||||
if (IS_ERR(pdata))
|
||||
return PTR_ERR(pdata);
|
||||
|
||||
desc.type = CORESIGHT_DEV_TYPE_SINK;
|
||||
desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER;
|
||||
desc.ops = &cs_ops;
|
||||
desc.pdata = pdata;
|
||||
desc.dev = &pdev->dev;
|
||||
desc.groups = smb_sink_groups;
|
||||
desc.name = coresight_alloc_device_name(&sink_devs, &pdev->dev);
|
||||
if (!desc.name) {
|
||||
dev_err(&pdev->dev, "Failed to alloc coresight device name");
|
||||
return -ENOMEM;
|
||||
}
|
||||
desc.access = CSDEV_ACCESS_IOMEM(drvdata->base);
|
||||
|
||||
drvdata->csdev = coresight_register(&desc);
|
||||
if (IS_ERR(drvdata->csdev))
|
||||
return PTR_ERR(drvdata->csdev);
|
||||
|
||||
drvdata->miscdev.name = desc.name;
|
||||
drvdata->miscdev.minor = MISC_DYNAMIC_MINOR;
|
||||
drvdata->miscdev.fops = &smb_fops;
|
||||
ret = misc_register(&drvdata->miscdev);
|
||||
if (ret) {
|
||||
coresight_unregister(drvdata->csdev);
|
||||
dev_err(&pdev->dev, "Failed to register misc, ret=%d\n", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void smb_unregister_sink(struct smb_drv_data *drvdata)
|
||||
{
|
||||
misc_deregister(&drvdata->miscdev);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
}
|
||||
|
||||
static int smb_config_inport(struct device *dev, bool enable)
|
||||
{
|
||||
u64 func = enable ? 1 : 0;
|
||||
union acpi_object *obj;
|
||||
guid_t guid;
|
||||
u64 rev = 0;
|
||||
|
||||
/*
|
||||
* Using DSM calls to enable/disable ultrasoc hardwares on
|
||||
* tracing path, to prevent ultrasoc packet format being exposed.
|
||||
*/
|
||||
if (guid_parse(ULTRASOC_SMB_DSM_UUID, &guid)) {
|
||||
dev_err(dev, "Get GUID failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
obj = acpi_evaluate_dsm(ACPI_HANDLE(dev), &guid, rev, func, NULL);
|
||||
if (!obj) {
|
||||
dev_err(dev, "ACPI handle failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ACPI_FREE(obj);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct smb_drv_data *drvdata;
|
||||
int ret;
|
||||
|
||||
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
|
||||
drvdata->base = devm_platform_ioremap_resource(pdev, SMB_REG_ADDR_RES);
|
||||
if (IS_ERR(drvdata->base)) {
|
||||
dev_err(dev, "Failed to ioremap resource\n");
|
||||
return PTR_ERR(drvdata->base);
|
||||
}
|
||||
|
||||
smb_init_hw(drvdata);
|
||||
|
||||
ret = smb_init_data_buffer(pdev, &drvdata->sdb);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to init buffer, ret = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&drvdata->mutex);
|
||||
drvdata->pid = -1;
|
||||
|
||||
ret = smb_register_sink(pdev, drvdata);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register SMB sink\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = smb_config_inport(dev, true);
|
||||
if (ret) {
|
||||
smb_unregister_sink(drvdata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, drvdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct smb_drv_data *drvdata = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = smb_config_inport(&pdev->dev, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
smb_unregister_sink(drvdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct acpi_device_id ultrasoc_smb_acpi_match[] = {
|
||||
{"HISI03A1", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, ultrasoc_smb_acpi_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver smb_driver = {
|
||||
.driver = {
|
||||
.name = "ultrasoc-smb",
|
||||
.acpi_match_table = ACPI_PTR(ultrasoc_smb_acpi_match),
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = smb_probe,
|
||||
.remove = smb_remove,
|
||||
};
|
||||
module_platform_driver(smb_driver);
|
||||
|
||||
MODULE_DESCRIPTION("UltraSoc SMB CoreSight driver");
|
||||
MODULE_LICENSE("Dual MIT/GPL");
|
||||
MODULE_AUTHOR("Jonathan Zhou <jonathan.zhouwen@huawei.com>");
|
||||
MODULE_AUTHOR("Qi Liu <liuqi115@huawei.com>");
|
||||
125
drivers/hwtracing/coresight/ultrasoc-smb.h
Normal file
125
drivers/hwtracing/coresight/ultrasoc-smb.h
Normal file
@ -0,0 +1,125 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
|
||||
/*
|
||||
* Siemens System Memory Buffer driver.
|
||||
* Copyright(c) 2022, HiSilicon Limited.
|
||||
*/
|
||||
|
||||
#ifndef _ULTRASOC_SMB_H
|
||||
#define _ULTRASOC_SMB_H
|
||||
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
/* Offset of SMB global registers */
|
||||
#define SMB_GLB_CFG_REG 0x00
|
||||
#define SMB_GLB_EN_REG 0x04
|
||||
#define SMB_GLB_INT_REG 0x08
|
||||
|
||||
/* Offset of SMB logical buffer registers */
|
||||
#define SMB_LB_CFG_LO_REG 0x40
|
||||
#define SMB_LB_CFG_HI_REG 0x44
|
||||
#define SMB_LB_INT_CTRL_REG 0x48
|
||||
#define SMB_LB_INT_STS_REG 0x4c
|
||||
#define SMB_LB_RD_ADDR_REG 0x5c
|
||||
#define SMB_LB_WR_ADDR_REG 0x60
|
||||
#define SMB_LB_PURGE_REG 0x64
|
||||
|
||||
/* Set global config register */
|
||||
#define SMB_GLB_CFG_BURST_LEN_MSK GENMASK(11, 4)
|
||||
#define SMB_GLB_CFG_IDLE_PRD_MSK GENMASK(15, 12)
|
||||
#define SMB_GLB_CFG_MEM_WR_MSK GENMASK(21, 16)
|
||||
#define SMB_GLB_CFG_MEM_RD_MSK GENMASK(27, 22)
|
||||
#define SMB_GLB_CFG_DEFAULT (FIELD_PREP(SMB_GLB_CFG_BURST_LEN_MSK, 0xf) | \
|
||||
FIELD_PREP(SMB_GLB_CFG_IDLE_PRD_MSK, 0xf) | \
|
||||
FIELD_PREP(SMB_GLB_CFG_MEM_WR_MSK, 0x3) | \
|
||||
FIELD_PREP(SMB_GLB_CFG_MEM_RD_MSK, 0x1b))
|
||||
|
||||
#define SMB_GLB_EN_HW_ENABLE BIT(0)
|
||||
|
||||
/* Set global interrupt control register */
|
||||
#define SMB_GLB_INT_EN BIT(0)
|
||||
#define SMB_GLB_INT_PULSE BIT(1) /* Interrupt type: 1 - Pulse */
|
||||
#define SMB_GLB_INT_ACT_H BIT(2) /* Interrupt polarity: 1 - Active high */
|
||||
#define SMB_GLB_INT_CFG (SMB_GLB_INT_EN | SMB_GLB_INT_PULSE | \
|
||||
SMB_GLB_INT_ACT_H)
|
||||
|
||||
/* Set logical buffer config register lower 32 bits */
|
||||
#define SMB_LB_CFG_LO_EN BIT(0)
|
||||
#define SMB_LB_CFG_LO_SINGLE_END BIT(1)
|
||||
#define SMB_LB_CFG_LO_INIT BIT(8)
|
||||
#define SMB_LB_CFG_LO_CONT BIT(11)
|
||||
#define SMB_LB_CFG_LO_FLOW_MSK GENMASK(19, 16)
|
||||
#define SMB_LB_CFG_LO_DEFAULT (SMB_LB_CFG_LO_EN | SMB_LB_CFG_LO_SINGLE_END | \
|
||||
SMB_LB_CFG_LO_INIT | SMB_LB_CFG_LO_CONT | \
|
||||
FIELD_PREP(SMB_LB_CFG_LO_FLOW_MSK, 0xf))
|
||||
|
||||
/* Set logical buffer config register upper 32 bits */
|
||||
#define SMB_LB_CFG_HI_RANGE_UP_MSK GENMASK(15, 8)
|
||||
#define SMB_LB_CFG_HI_DEFAULT FIELD_PREP(SMB_LB_CFG_HI_RANGE_UP_MSK, 0xff)
|
||||
|
||||
/*
|
||||
* Set logical buffer interrupt control register.
|
||||
* The register control the validity of both real-time events and
|
||||
* interrupts. When logical buffer status changes causes to issue
|
||||
* an interrupt at the same time as it issues a real-time event.
|
||||
* Real-time events are used in SMB driver, which needs to get the buffer
|
||||
* status. Interrupts are used in debugger mode.
|
||||
* SMB_LB_INT_CTRL_BUF_NOTE_MASK control which events flags or interrupts
|
||||
* are valid.
|
||||
*/
|
||||
#define SMB_LB_INT_CTRL_EN BIT(0)
|
||||
#define SMB_LB_INT_CTRL_BUF_NOTE_MSK GENMASK(11, 8)
|
||||
#define SMB_LB_INT_CTRL_CFG (SMB_LB_INT_CTRL_EN | \
|
||||
FIELD_PREP(SMB_LB_INT_CTRL_BUF_NOTE_MSK, 0xf))
|
||||
|
||||
/* Set logical buffer interrupt status register */
|
||||
#define SMB_LB_INT_STS_NOT_EMPTY_MSK BIT(0)
|
||||
#define SMB_LB_INT_STS_BUF_RESET_MSK GENMASK(3, 0)
|
||||
#define SMB_LB_INT_STS_RESET FIELD_PREP(SMB_LB_INT_STS_BUF_RESET_MSK, 0xf)
|
||||
|
||||
#define SMB_LB_PURGE_PURGED BIT(0)
|
||||
|
||||
#define SMB_REG_ADDR_RES 0
|
||||
#define SMB_BUF_ADDR_RES 1
|
||||
#define SMB_BUF_ADDR_LO_MSK GENMASK(31, 0)
|
||||
|
||||
/**
|
||||
* struct smb_data_buffer - Details of the buffer used by SMB
|
||||
* @buf_base: Memory mapped base address of SMB.
|
||||
* @buf_hw_base: SMB buffer start Physical base address, only used 32bits.
|
||||
* @buf_size: Size of the buffer.
|
||||
* @data_size: Size of the available trace data for SMB.
|
||||
* @buf_rdptr: Current read position (index) within the buffer.
|
||||
*/
|
||||
struct smb_data_buffer {
|
||||
void *buf_base;
|
||||
u32 buf_hw_base;
|
||||
unsigned long buf_size;
|
||||
unsigned long data_size;
|
||||
unsigned long buf_rdptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct smb_drv_data - specifics associated to an SMB component
|
||||
* @base: Memory mapped base address for SMB component.
|
||||
* @csdev: Component vitals needed by the framework.
|
||||
* @sdb: Data buffer for SMB.
|
||||
* @miscdev: Specifics to handle "/dev/xyz.smb" entry.
|
||||
* @mutex: Control data access to one at a time.
|
||||
* @reading: Synchronise user space access to SMB buffer.
|
||||
* @pid: Process ID of the process being monitored by the
|
||||
* session that is using this component.
|
||||
* @mode: How this SMB is being used, perf mode or sysfs mode.
|
||||
*/
|
||||
struct smb_drv_data {
|
||||
void __iomem *base;
|
||||
struct coresight_device *csdev;
|
||||
struct smb_data_buffer sdb;
|
||||
struct miscdevice miscdev;
|
||||
struct mutex mutex;
|
||||
bool reading;
|
||||
pid_t pid;
|
||||
u32 mode;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -356,8 +356,18 @@ static int hisi_ptt_register_irq(struct hisi_ptt *hisi_ptt)
|
||||
|
||||
static int hisi_ptt_init_filters(struct pci_dev *pdev, void *data)
|
||||
{
|
||||
struct pci_dev *root_port = pcie_find_root_port(pdev);
|
||||
struct hisi_ptt_filter_desc *filter;
|
||||
struct hisi_ptt *hisi_ptt = data;
|
||||
u32 port_devid;
|
||||
|
||||
if (!root_port)
|
||||
return 0;
|
||||
|
||||
port_devid = PCI_DEVID(root_port->bus->number, root_port->devfn);
|
||||
if (port_devid < hisi_ptt->lower_bdf ||
|
||||
port_devid > hisi_ptt->upper_bdf)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We won't fail the probe if filter allocation failed here. The filters
|
||||
|
||||
@ -7,8 +7,19 @@
|
||||
#ifndef _LINUX_CORESIGHT_PMU_H
|
||||
#define _LINUX_CORESIGHT_PMU_H
|
||||
|
||||
#include <linux/bits.h>
|
||||
|
||||
#define CORESIGHT_ETM_PMU_NAME "cs_etm"
|
||||
#define CORESIGHT_ETM_PMU_SEED 0x10
|
||||
|
||||
/*
|
||||
* The legacy Trace ID system based on fixed calculation from the cpu
|
||||
* number. This has been replaced by drivers using a dynamic allocation
|
||||
* system - but need to retain the legacy algorithm for backward comparibility
|
||||
* in certain situations:-
|
||||
* a) new perf running on older systems that generate the legacy mapping
|
||||
* b) older tools that may not update at the same time as the kernel.
|
||||
*/
|
||||
#define CORESIGHT_LEGACY_CPU_TRACE_ID(cpu) (0x10 + (cpu * 2))
|
||||
|
||||
/*
|
||||
* Below are the definition of bit offsets for perf option, and works as
|
||||
@ -34,15 +45,16 @@
|
||||
#define ETM4_CFG_BIT_RETSTK 12
|
||||
#define ETM4_CFG_BIT_VMID_OPT 15
|
||||
|
||||
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));
|
||||
}
|
||||
/*
|
||||
* Interpretation of the PERF_RECORD_AUX_OUTPUT_HW_ID payload.
|
||||
* Used to associate a CPU with the CoreSight Trace ID.
|
||||
* [07:00] - Trace ID - uses 8 bits to make value easy to read in file.
|
||||
* [59:08] - Unused (SBZ)
|
||||
* [63:60] - Version
|
||||
*/
|
||||
#define CS_AUX_HW_ID_TRACE_ID_MASK GENMASK_ULL(7, 0)
|
||||
#define CS_AUX_HW_ID_VERSION_MASK GENMASK_ULL(63, 60)
|
||||
|
||||
#define CS_AUX_HW_ID_CURR_VERSION 0
|
||||
|
||||
#endif
|
||||
|
||||
@ -61,6 +61,7 @@ enum coresight_dev_subtype_source {
|
||||
CORESIGHT_DEV_SUBTYPE_SOURCE_PROC,
|
||||
CORESIGHT_DEV_SUBTYPE_SOURCE_BUS,
|
||||
CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE,
|
||||
CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS,
|
||||
};
|
||||
|
||||
enum coresight_dev_subtype_helper {
|
||||
@ -314,14 +315,11 @@ struct coresight_ops_link {
|
||||
* 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.
|
||||
* @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,
|
||||
struct perf_event *event, u32 mode);
|
||||
void (*disable)(struct coresight_device *csdev,
|
||||
|
||||
@ -9399,6 +9399,7 @@ void perf_report_aux_output_id(struct perf_event *event, u64 hw_id)
|
||||
|
||||
perf_output_end(&handle);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_report_aux_output_id);
|
||||
|
||||
static int
|
||||
__perf_event_account_interrupt(struct perf_event *event, int throttle)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user