mirror of https://github.com/torvalds/linux.git
arm64 updates for 6.18
Confidential computing:
- Add support for accepting secrets from firmware (e.g. ACPI CCEL)
and mapping them with appropriate attributes.
CPU features:
- Advertise atomic floating-point instructions to userspace.
- Extend Spectre workarounds to cover additional Arm CPU variants.
- Extend list of CPUs that support break-before-make level 2 and
guarantee not to generate TLB conflict aborts for changes of mapping
granularity (BBML2_NOABORT).
- Add GCS support to our uprobes implementation.
Documentation:
- Remove bogus SME documentation concerning register state when
entering/exiting streaming mode.
Entry code:
- Switch over to the generic IRQ entry code (GENERIC_IRQ_ENTRY).
- Micro-optimise syscall entry path with a compiler branch hint.
Memory management:
- Enable huge mappings in vmalloc space even when kernel page-table
dumping is enabled.
- Tidy up the types used in our early MMU setup code.
- Rework rodata= for closer parity with the behaviour on x86.
- For CPUs implementing BBML2_NOABORT, utilise block mappings in the
linear map even when rodata= applies to virtual aliases.
- Don't re-allocate the virtual region between '_text' and '_stext',
as doing so confused tools parsing /proc/vmcore.
Miscellaneous:
- Clean-up Kconfig menuconfig text for architecture features.
- Avoid redundant bitmap_empty() during determination of supported
SME vector lengths.
- Re-enable warnings when building the 32-bit vDSO object.
- Avoid breaking our eggs at the wrong end.
Perf and PMUs:
- Support for v3 of the Hisilicon L3C PMU.
- Support for Hisilicon's MN and NoC PMUs.
- Support for Fujitsu's Uncore PMU.
- Support for SPE's extended event filtering feature.
- Preparatory work to enable data source filtering in SPE.
- Support for multiple lanes in the DWC PCIe PMU.
- Support for i.MX94 in the IMX DDR PMU driver.
- MAINTAINERS update (Thank you, Yicong).
- Minor driver fixes (PERF_IDX2OFF() overflow, CMN register offsets).
Selftests:
- Add basic LSFE check to the existing hwcaps test.
- Support nolibc in GCS tests.
- Extend SVE ptrace test to pass unsupported regsets and invalid vector
lengths.
- Minor cleanups (typos, cosmetic changes).
System registers:
- Fix ID_PFR1_EL1 definition.
- Fix incorrect signedness of some fields in ID_AA64MMFR4_EL1.
- Sync TCR_EL1 definition with the latest Arm ARM (L.b).
- Be stricter about the input fed into our AWK sysreg generator script.
- Typo fixes and removal of redundant definitions.
ACPI, EFI and PSCI:
- Decouple Arm's "Software Delegated Exception Interface" (SDEI)
support from the ACPI GHES code so that it can be used by platforms
booted with device-tree.
- Remove unnecessary per-CPU tracking of the FPSIMD state across EFI
runtime calls.
- Fix a node refcount imbalance in the PSCI device-tree code.
CPU Features:
- Ensure register sanitisation is applied to fields in ID_AA64MMFR4.
- Expose AIDR_EL1 to userspace via sysfs, primarily so that KVM guests
can reliably query the underlying CPU types from the VMM.
- Re-enabling of SME support (CONFIG_ARM64_SME) as a result of fixes
to our context-switching, signal handling and ptrace code.
-----BEGIN PGP SIGNATURE-----
iQFEBAABCgAuFiEEPxTL6PPUbjXGY88ct6xw3ITBYzQFAmjWlMIQHHdpbGxAa2Vy
bmVsLm9yZwAKCRC3rHDchMFjNIVYB/sHaFYmToEaK4ldMfTn4ZQ8yr9ZuTMLr/ji
zOm7wULP08wKIcp6t+1N6e7A8Wb3YSErxTywc4b9MqctIel1WvBMewjJd38xb2qO
hFCuRuWemfyt6Rw/EGMCP54yueLzKbnN4q+/Aks8jedWtS+AXY6McGCJOzMjuECL
zKb68Hqqb09YQ2b2BPSAiU9g42rotiIYppLaLbEdxUnw/eqfEN3wSrl92/LuBlqH
r2wZDnJwA5q8Iy9HWlnhg4Jax0Cs86jSJPIQLB6v3WWhc3HR49pm5cqYzYkUht9L
nA6eaWJiBl/+k3S+ftPKNzU8n04r15lqyNZE2FYfRk+0BPSacJOf
=kO15
-----END PGP SIGNATURE-----
Merge tag 'arm64-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux
Pull arm64 updates from Will Deacon:
"There's good stuff across the board, including some nice mm
improvements for CPUs with the 'noabort' BBML2 feature and a clever
patch to allow ptdump to play nicely with block mappings in the
vmalloc area.
Confidential computing:
- Add support for accepting secrets from firmware (e.g. ACPI CCEL)
and mapping them with appropriate attributes.
CPU features:
- Advertise atomic floating-point instructions to userspace
- Extend Spectre workarounds to cover additional Arm CPU variants
- Extend list of CPUs that support break-before-make level 2 and
guarantee not to generate TLB conflict aborts for changes of
mapping granularity (BBML2_NOABORT)
- Add GCS support to our uprobes implementation.
Documentation:
- Remove bogus SME documentation concerning register state when
entering/exiting streaming mode.
Entry code:
- Switch over to the generic IRQ entry code (GENERIC_IRQ_ENTRY)
- Micro-optimise syscall entry path with a compiler branch hint.
Memory management:
- Enable huge mappings in vmalloc space even when kernel page-table
dumping is enabled
- Tidy up the types used in our early MMU setup code
- Rework rodata= for closer parity with the behaviour on x86
- For CPUs implementing BBML2_NOABORT, utilise block mappings in the
linear map even when rodata= applies to virtual aliases
- Don't re-allocate the virtual region between '_text' and '_stext',
as doing so confused tools parsing /proc/vmcore.
Miscellaneous:
- Clean-up Kconfig menuconfig text for architecture features
- Avoid redundant bitmap_empty() during determination of supported
SME vector lengths
- Re-enable warnings when building the 32-bit vDSO object
- Avoid breaking our eggs at the wrong end.
Perf and PMUs:
- Support for v3 of the Hisilicon L3C PMU
- Support for Hisilicon's MN and NoC PMUs
- Support for Fujitsu's Uncore PMU
- Support for SPE's extended event filtering feature
- Preparatory work to enable data source filtering in SPE
- Support for multiple lanes in the DWC PCIe PMU
- Support for i.MX94 in the IMX DDR PMU driver
- MAINTAINERS update (Thank you, Yicong)
- Minor driver fixes (PERF_IDX2OFF() overflow, CMN register offsets).
Selftests:
- Add basic LSFE check to the existing hwcaps test
- Support nolibc in GCS tests
- Extend SVE ptrace test to pass unsupported regsets and invalid
vector lengths
- Minor cleanups (typos, cosmetic changes).
System registers:
- Fix ID_PFR1_EL1 definition
- Fix incorrect signedness of some fields in ID_AA64MMFR4_EL1
- Sync TCR_EL1 definition with the latest Arm ARM (L.b)
- Be stricter about the input fed into our AWK sysreg generator
script
- Typo fixes and removal of redundant definitions.
ACPI, EFI and PSCI:
- Decouple Arm's "Software Delegated Exception Interface" (SDEI)
support from the ACPI GHES code so that it can be used by platforms
booted with device-tree
- Remove unnecessary per-CPU tracking of the FPSIMD state across EFI
runtime calls
- Fix a node refcount imbalance in the PSCI device-tree code.
CPU Features:
- Ensure register sanitisation is applied to fields in ID_AA64MMFR4
- Expose AIDR_EL1 to userspace via sysfs, primarily so that KVM
guests can reliably query the underlying CPU types from the VMM
- Re-enabling of SME support (CONFIG_ARM64_SME) as a result of fixes
to our context-switching, signal handling and ptrace code"
* tag 'arm64-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux: (93 commits)
arm64: cpufeature: Remove duplicate asm/mmu.h header
arm64: Kconfig: Make CPU_BIG_ENDIAN depend on BROKEN
perf/dwc_pcie: Fix use of uninitialized variable
arm/syscalls: mark syscall invocation as likely in invoke_syscall
Documentation: hisi-pmu: Add introduction to HiSilicon V3 PMU
Documentation: hisi-pmu: Fix of minor format error
drivers/perf: hisi: Add support for L3C PMU v3
drivers/perf: hisi: Refactor the event configuration of L3C PMU
drivers/perf: hisi: Extend the field of tt_core
drivers/perf: hisi: Extract the event filter check of L3C PMU
drivers/perf: hisi: Simplify the probe process of each L3C PMU version
drivers/perf: hisi: Export hisi_uncore_pmu_isr()
drivers/perf: hisi: Relax the event ID check in the framework
perf: Fujitsu: Add the Uncore PMU driver
arm64: map [_text, _stext) virtual address range non-executable+read-only
arm64/sysreg: Update TCR_EL1 register
arm64: Enable vmalloc-huge with ptdump
arm64: cpufeature: add Neoverse-V3AE to BBML2 allow list
arm64: errata: Apply workarounds for Neoverse-V3AE
arm64: cputype: Add Neoverse-V3AE definitions
...
This commit is contained in:
commit
feafee2845
|
|
@ -6406,8 +6406,9 @@
|
||||||
rodata= [KNL,EARLY]
|
rodata= [KNL,EARLY]
|
||||||
on Mark read-only kernel memory as read-only (default).
|
on Mark read-only kernel memory as read-only (default).
|
||||||
off Leave read-only kernel memory writable for debugging.
|
off Leave read-only kernel memory writable for debugging.
|
||||||
full Mark read-only kernel memory and aliases as read-only
|
noalias Mark read-only kernel memory as read-only but retain
|
||||||
[arm64]
|
writable aliases in the direct map for regions outside
|
||||||
|
of the kernel image. [arm64]
|
||||||
|
|
||||||
rockchip.usb_uart
|
rockchip.usb_uart
|
||||||
[EARLY]
|
[EARLY]
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ provides the following two features:
|
||||||
|
|
||||||
- one 64-bit counter for Time Based Analysis (RX/TX data throughput and
|
- one 64-bit counter for Time Based Analysis (RX/TX data throughput and
|
||||||
time spent in each low-power LTSSM state) and
|
time spent in each low-power LTSSM state) and
|
||||||
- one 32-bit counter for Event Counting (error and non-error events for
|
- one 32-bit counter per event for Event Counting (error and non-error
|
||||||
a specified lane)
|
events for a specified lane)
|
||||||
|
|
||||||
Note: There is no interrupt for counter overflow.
|
Note: There is no interrupt for counter overflow.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
.. SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
|
||||||
|
================================================
|
||||||
|
Fujitsu Uncore Performance Monitoring Unit (PMU)
|
||||||
|
================================================
|
||||||
|
|
||||||
|
This driver supports the Uncore MAC PMUs and the Uncore PCI PMUs found
|
||||||
|
in Fujitsu chips.
|
||||||
|
Each MAC PMU on these chips is exposed as a uncore perf PMU with device name
|
||||||
|
mac_iod<iod>_mac<mac>_ch<ch>.
|
||||||
|
And each PCI PMU on these chips is exposed as a uncore perf PMU with device name
|
||||||
|
pci_iod<iod>_pci<pci>.
|
||||||
|
|
||||||
|
The driver provides a description of its available events and configuration
|
||||||
|
options in sysfs, see /sys/bus/event_sources/devices/mac_iod<iod>_mac<mac>_ch<ch>/
|
||||||
|
and /sys/bus/event_sources/devices/pci_iod<iod>_pci<pci>/.
|
||||||
|
This driver exports:
|
||||||
|
- formats, used by perf user space and other tools to configure events
|
||||||
|
- events, used by perf user space and other tools to create events
|
||||||
|
symbolically, e.g.:
|
||||||
|
perf stat -a -e mac_iod0_mac0_ch0/event=0x21/ ls
|
||||||
|
perf stat -a -e pci_iod0_pci0/event=0x24/ ls
|
||||||
|
- cpumask, used by perf user space and other tools to know on which CPUs
|
||||||
|
to open the events
|
||||||
|
|
||||||
|
This driver supports the following events for MAC:
|
||||||
|
- cycles
|
||||||
|
This event counts MAC cycles at MAC frequency.
|
||||||
|
- read-count
|
||||||
|
This event counts the number of read requests to MAC.
|
||||||
|
- read-count-request
|
||||||
|
This event counts the number of read requests including retry to MAC.
|
||||||
|
- read-count-return
|
||||||
|
This event counts the number of responses to read requests to MAC.
|
||||||
|
- read-count-request-pftgt
|
||||||
|
This event counts the number of read requests including retry with PFTGT
|
||||||
|
flag.
|
||||||
|
- read-count-request-normal
|
||||||
|
This event counts the number of read requests including retry without PFTGT
|
||||||
|
flag.
|
||||||
|
- read-count-return-pftgt-hit
|
||||||
|
This event counts the number of responses to read requests which hit the
|
||||||
|
PFTGT buffer.
|
||||||
|
- read-count-return-pftgt-miss
|
||||||
|
This event counts the number of responses to read requests which miss the
|
||||||
|
PFTGT buffer.
|
||||||
|
- read-wait
|
||||||
|
This event counts outstanding read requests issued by DDR memory controller
|
||||||
|
per cycle.
|
||||||
|
- write-count
|
||||||
|
This event counts the number of write requests to MAC (including zero write,
|
||||||
|
full write, partial write, write cancel).
|
||||||
|
- write-count-write
|
||||||
|
This event counts the number of full write requests to MAC (not including
|
||||||
|
zero write).
|
||||||
|
- write-count-pwrite
|
||||||
|
This event counts the number of partial write requests to MAC.
|
||||||
|
- memory-read-count
|
||||||
|
This event counts the number of read requests from MAC to memory.
|
||||||
|
- memory-write-count
|
||||||
|
This event counts the number of full write requests from MAC to memory.
|
||||||
|
- memory-pwrite-count
|
||||||
|
This event counts the number of partial write requests from MAC to memory.
|
||||||
|
- ea-mac
|
||||||
|
This event counts energy consumption of MAC.
|
||||||
|
- ea-memory
|
||||||
|
This event counts energy consumption of memory.
|
||||||
|
- ea-memory-mac-write
|
||||||
|
This event counts the number of write requests from MAC to memory.
|
||||||
|
- ea-ha
|
||||||
|
This event counts energy consumption of HA.
|
||||||
|
|
||||||
|
'ea' is the abbreviation for 'Energy Analyzer'.
|
||||||
|
|
||||||
|
Examples for use with perf::
|
||||||
|
|
||||||
|
perf stat -e mac_iod0_mac0_ch0/ea-mac/ ls
|
||||||
|
|
||||||
|
And, this driver supports the following events for PCI:
|
||||||
|
- pci-port0-cycles
|
||||||
|
This event counts PCI cycles at PCI frequency in port0.
|
||||||
|
- pci-port0-read-count
|
||||||
|
This event counts read transactions for data transfer in port0.
|
||||||
|
- pci-port0-read-count-bus
|
||||||
|
This event counts read transactions for bus usage in port0.
|
||||||
|
- pci-port0-write-count
|
||||||
|
This event counts write transactions for data transfer in port0.
|
||||||
|
- pci-port0-write-count-bus
|
||||||
|
This event counts write transactions for bus usage in port0.
|
||||||
|
- pci-port1-cycles
|
||||||
|
This event counts PCI cycles at PCI frequency in port1.
|
||||||
|
- pci-port1-read-count
|
||||||
|
This event counts read transactions for data transfer in port1.
|
||||||
|
- pci-port1-read-count-bus
|
||||||
|
This event counts read transactions for bus usage in port1.
|
||||||
|
- pci-port1-write-count
|
||||||
|
This event counts write transactions for data transfer in port1.
|
||||||
|
- pci-port1-write-count-bus
|
||||||
|
This event counts write transactions for bus usage in port1.
|
||||||
|
- ea-pci
|
||||||
|
This event counts energy consumption of PCI.
|
||||||
|
|
||||||
|
'ea' is the abbreviation for 'Energy Analyzer'.
|
||||||
|
|
||||||
|
Examples for use with perf::
|
||||||
|
|
||||||
|
perf stat -e pci_iod0_pci0/ea-pci/ ls
|
||||||
|
|
||||||
|
Given that these are uncore PMUs the driver does not support sampling, therefore
|
||||||
|
"perf record" will not work. Per-task perf sessions are not supported.
|
||||||
|
|
@ -18,9 +18,10 @@ HiSilicon SoC uncore PMU driver
|
||||||
Each device PMU has separate registers for event counting, control and
|
Each device PMU has separate registers for event counting, control and
|
||||||
interrupt, and the PMU driver shall register perf PMU drivers like L3C,
|
interrupt, and the PMU driver shall register perf PMU drivers like L3C,
|
||||||
HHA and DDRC etc. The available events and configuration options shall
|
HHA and DDRC etc. The available events and configuration options shall
|
||||||
be described in the sysfs, see:
|
be described in the sysfs, see::
|
||||||
|
|
||||||
|
/sys/bus/event_source/devices/hisi_sccl{X}_<l3c{Y}/hha{Y}/ddrc{Y}>
|
||||||
|
|
||||||
/sys/bus/event_source/devices/hisi_sccl{X}_<l3c{Y}/hha{Y}/ddrc{Y}>.
|
|
||||||
The "perf list" command shall list the available events from sysfs.
|
The "perf list" command shall list the available events from sysfs.
|
||||||
|
|
||||||
Each L3C, HHA and DDRC is registered as a separate PMU with perf. The PMU
|
Each L3C, HHA and DDRC is registered as a separate PMU with perf. The PMU
|
||||||
|
|
@ -112,6 +113,50 @@ uring channel. It is 2 bits. Some important codes are as follows:
|
||||||
- 2'b00: default value, count the events which sent to the both uring and
|
- 2'b00: default value, count the events which sent to the both uring and
|
||||||
uring_ext channel;
|
uring_ext channel;
|
||||||
|
|
||||||
|
6. ch: NoC PMU supports filtering the event counts of certain transaction
|
||||||
|
channel with this option. The current supported channels are as follows:
|
||||||
|
|
||||||
|
- 3'b010: Request channel
|
||||||
|
- 3'b100: Snoop channel
|
||||||
|
- 3'b110: Response channel
|
||||||
|
- 3'b111: Data channel
|
||||||
|
|
||||||
|
7. tt_en: NoC PMU supports counting only transactions that have tracetag set
|
||||||
|
if this option is set. See the 2nd list for more information about tracetag.
|
||||||
|
|
||||||
|
For HiSilicon uncore PMU v3 whose identifier is 0x40, some uncore PMUs are
|
||||||
|
further divided into parts for finer granularity of tracing, each part has its
|
||||||
|
own dedicated PMU, and all such PMUs together cover the monitoring job of events
|
||||||
|
on particular uncore device. Such PMUs are described in sysfs with name format
|
||||||
|
slightly changed::
|
||||||
|
|
||||||
|
/sys/bus/event_source/devices/hisi_sccl{X}_<l3c{Y}_{Z}/ddrc{Y}_{Z}/noc{Y}_{Z}>
|
||||||
|
|
||||||
|
Z is the sub-id, indicating different PMUs for part of hardware device.
|
||||||
|
|
||||||
|
Usage of most PMUs with different sub-ids are identical. Specially, L3C PMU
|
||||||
|
provides ``ext`` option to allow exploration of even finer granual statistics
|
||||||
|
of L3C PMU. L3C PMU driver uses that as hint of termination when delivering
|
||||||
|
perf command to hardware:
|
||||||
|
|
||||||
|
- ext=0: Default, could be used with event names.
|
||||||
|
- ext=1 and ext=2: Must be used with event codes, event names are not supported.
|
||||||
|
|
||||||
|
An example of perf command could be::
|
||||||
|
|
||||||
|
$# perf stat -a -e hisi_sccl0_l3c1_0/rd_spipe/ sleep 5
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
$# perf stat -a -e hisi_sccl0_l3c1_0/event=0x1,ext=1/ sleep 5
|
||||||
|
|
||||||
|
As above, ``hisi_sccl0_l3c1_0`` locates PMU of Super CPU CLuster 0, L3 cache 1
|
||||||
|
pipe0.
|
||||||
|
|
||||||
|
First command locates the first part of L3C since ``ext=0`` is implied by
|
||||||
|
default. Second command issues the counting on another part of L3C with the
|
||||||
|
event ``0x1``.
|
||||||
|
|
||||||
Users could configure IDs to count data come from specific CCL/ICL, by setting
|
Users could configure IDs to count data come from specific CCL/ICL, by setting
|
||||||
srcid_cmd & srcid_msk, and data desitined for specific CCL/ICL by setting
|
srcid_cmd & srcid_msk, and data desitined for specific CCL/ICL by setting
|
||||||
tgtid_cmd & tgtid_msk. A set bit in srcid_msk/tgtid_msk means the PMU will not
|
tgtid_cmd & tgtid_msk. A set bit in srcid_msk/tgtid_msk means the PMU will not
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,4 @@ Performance monitor support
|
||||||
cxl
|
cxl
|
||||||
ampere_cspmu
|
ampere_cspmu
|
||||||
mrvl-pem-pmu
|
mrvl-pem-pmu
|
||||||
|
fujitsu_uncore_pmu
|
||||||
|
|
|
||||||
|
|
@ -466,6 +466,17 @@ Before jumping into the kernel, the following conditions must be met:
|
||||||
- HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1.
|
- HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1.
|
||||||
- HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1.
|
- HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1.
|
||||||
|
|
||||||
|
For CPUs with SPE data source filtering (FEAT_SPE_FDS):
|
||||||
|
|
||||||
|
- If EL3 is present:
|
||||||
|
|
||||||
|
- MDCR_EL3.EnPMS3 (bit 42) must be initialised to 0b1.
|
||||||
|
|
||||||
|
- If the kernel is entered at EL1 and EL2 is present:
|
||||||
|
|
||||||
|
- HDFGRTR2_EL2.nPMSDSFR_EL1 (bit 19) must be initialised to 0b1.
|
||||||
|
- HDFGWTR2_EL2.nPMSDSFR_EL1 (bit 19) must be initialised to 0b1.
|
||||||
|
|
||||||
For CPUs with Memory Copy and Memory Set instructions (FEAT_MOPS):
|
For CPUs with Memory Copy and Memory Set instructions (FEAT_MOPS):
|
||||||
|
|
||||||
- If the kernel is entered at EL1 and EL2 is present:
|
- If the kernel is entered at EL1 and EL2 is present:
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,10 @@ HWCAP3_MTE_FAR
|
||||||
HWCAP3_MTE_STORE_ONLY
|
HWCAP3_MTE_STORE_ONLY
|
||||||
Functionality implied by ID_AA64PFR2_EL1.MTESTOREONLY == 0b0001.
|
Functionality implied by ID_AA64PFR2_EL1.MTESTOREONLY == 0b0001.
|
||||||
|
|
||||||
|
HWCAP3_LSFE
|
||||||
|
Functionality implied by ID_AA64ISAR3_EL1.LSFE == 0b0001
|
||||||
|
|
||||||
|
|
||||||
4. Unused AT_HWCAP bits
|
4. Unused AT_HWCAP bits
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,8 @@ stable kernels.
|
||||||
+----------------+-----------------+-----------------+-----------------------------+
|
+----------------+-----------------+-----------------+-----------------------------+
|
||||||
| ARM | Neoverse-V3 | #3312417 | ARM64_ERRATUM_3194386 |
|
| ARM | Neoverse-V3 | #3312417 | ARM64_ERRATUM_3194386 |
|
||||||
+----------------+-----------------+-----------------+-----------------------------+
|
+----------------+-----------------+-----------------+-----------------------------+
|
||||||
|
| ARM | Neoverse-V3AE | #3312417 | ARM64_ERRATUM_3194386 |
|
||||||
|
+----------------+-----------------+-----------------+-----------------------------+
|
||||||
| ARM | MMU-500 | #841119,826419 | ARM_SMMU_MMU_500_CPRE_ERRATA|
|
| ARM | MMU-500 | #841119,826419 | ARM_SMMU_MMU_500_CPRE_ERRATA|
|
||||||
| | | #562869,1047329 | |
|
| | | #562869,1047329 | |
|
||||||
+----------------+-----------------+-----------------+-----------------------------+
|
+----------------+-----------------+-----------------+-----------------------------+
|
||||||
|
|
|
||||||
|
|
@ -81,17 +81,7 @@ The ZA matrix is square with each side having as many bytes as a streaming
|
||||||
mode SVE vector.
|
mode SVE vector.
|
||||||
|
|
||||||
|
|
||||||
3. Sharing of streaming and non-streaming mode SVE state
|
3. System call behaviour
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
It is implementation defined which if any parts of the SVE state are shared
|
|
||||||
between streaming and non-streaming modes. When switching between modes
|
|
||||||
via software interfaces such as ptrace if no register content is provided as
|
|
||||||
part of switching no state will be assumed to be shared and everything will
|
|
||||||
be zeroed.
|
|
||||||
|
|
||||||
|
|
||||||
4. System call behaviour
|
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
* On syscall PSTATE.ZA is preserved, if PSTATE.ZA==1 then the contents of the
|
* On syscall PSTATE.ZA is preserved, if PSTATE.ZA==1 then the contents of the
|
||||||
|
|
@ -112,7 +102,7 @@ be zeroed.
|
||||||
exceptions for execve() described in section 6.
|
exceptions for execve() described in section 6.
|
||||||
|
|
||||||
|
|
||||||
5. Signal handling
|
4. Signal handling
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
* Signal handlers are invoked with PSTATE.SM=0, PSTATE.ZA=0, and TPIDR2_EL0=0.
|
* Signal handlers are invoked with PSTATE.SM=0, PSTATE.ZA=0, and TPIDR2_EL0=0.
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ properties:
|
||||||
- items:
|
- items:
|
||||||
- enum:
|
- enum:
|
||||||
- fsl,imx91-ddr-pmu
|
- fsl,imx91-ddr-pmu
|
||||||
|
- fsl,imx94-ddr-pmu
|
||||||
- fsl,imx95-ddr-pmu
|
- fsl,imx95-ddr-pmu
|
||||||
- const: fsl,imx93-ddr-pmu
|
- const: fsl,imx93-ddr-pmu
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9758,11 +9758,14 @@ F: drivers/video/fbdev/imxfb.c
|
||||||
|
|
||||||
FREESCALE IMX DDR PMU DRIVER
|
FREESCALE IMX DDR PMU DRIVER
|
||||||
M: Frank Li <Frank.li@nxp.com>
|
M: Frank Li <Frank.li@nxp.com>
|
||||||
|
M: Xu Yang <xu.yang_2@nxp.com>
|
||||||
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: Documentation/admin-guide/perf/imx-ddr.rst
|
F: Documentation/admin-guide/perf/imx-ddr.rst
|
||||||
F: Documentation/devicetree/bindings/perf/fsl-imx-ddr.yaml
|
F: Documentation/devicetree/bindings/perf/fsl-imx-ddr.yaml
|
||||||
F: drivers/perf/fsl_imx8_ddr_perf.c
|
F: drivers/perf/fsl_imx8_ddr_perf.c
|
||||||
|
F: drivers/perf/fsl_imx9_ddr_perf.c
|
||||||
|
F: tools/perf/pmu-events/arch/arm64/freescale/
|
||||||
|
|
||||||
FREESCALE IMX I2C DRIVER
|
FREESCALE IMX I2C DRIVER
|
||||||
M: Oleksij Rempel <o.rempel@pengutronix.de>
|
M: Oleksij Rempel <o.rempel@pengutronix.de>
|
||||||
|
|
@ -11078,7 +11081,6 @@ F: Documentation/devicetree/bindings/net/hisilicon*.txt
|
||||||
F: drivers/net/ethernet/hisilicon/
|
F: drivers/net/ethernet/hisilicon/
|
||||||
|
|
||||||
HISILICON PMU DRIVER
|
HISILICON PMU DRIVER
|
||||||
M: Yicong Yang <yangyicong@hisilicon.com>
|
|
||||||
M: Jonathan Cameron <jonathan.cameron@huawei.com>
|
M: Jonathan Cameron <jonathan.cameron@huawei.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
W: http://www.hisilicon.com
|
W: http://www.hisilicon.com
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ config ARM64
|
||||||
select GENERIC_EARLY_IOREMAP
|
select GENERIC_EARLY_IOREMAP
|
||||||
select GENERIC_IDLE_POLL_SETUP
|
select GENERIC_IDLE_POLL_SETUP
|
||||||
select GENERIC_IOREMAP
|
select GENERIC_IOREMAP
|
||||||
|
select GENERIC_IRQ_ENTRY
|
||||||
select GENERIC_IRQ_IPI
|
select GENERIC_IRQ_IPI
|
||||||
select GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD
|
select GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD
|
||||||
select GENERIC_IRQ_PROBE
|
select GENERIC_IRQ_PROBE
|
||||||
|
|
@ -1138,6 +1139,7 @@ config ARM64_ERRATUM_3194386
|
||||||
* ARM Neoverse-V1 erratum 3324341
|
* ARM Neoverse-V1 erratum 3324341
|
||||||
* ARM Neoverse V2 erratum 3324336
|
* ARM Neoverse V2 erratum 3324336
|
||||||
* ARM Neoverse-V3 erratum 3312417
|
* ARM Neoverse-V3 erratum 3312417
|
||||||
|
* ARM Neoverse-V3AE erratum 3312417
|
||||||
|
|
||||||
On affected cores "MSR SSBS, #0" instructions may not affect
|
On affected cores "MSR SSBS, #0" instructions may not affect
|
||||||
subsequent speculative instructions, which may permit unexepected
|
subsequent speculative instructions, which may permit unexepected
|
||||||
|
|
@ -1493,7 +1495,7 @@ choice
|
||||||
config CPU_BIG_ENDIAN
|
config CPU_BIG_ENDIAN
|
||||||
bool "Build big-endian kernel"
|
bool "Build big-endian kernel"
|
||||||
# https://github.com/llvm/llvm-project/commit/1379b150991f70a5782e9a143c2ba5308da1161c
|
# https://github.com/llvm/llvm-project/commit/1379b150991f70a5782e9a143c2ba5308da1161c
|
||||||
depends on AS_IS_GNU || AS_VERSION >= 150000
|
depends on (AS_IS_GNU || AS_VERSION >= 150000) && BROKEN
|
||||||
help
|
help
|
||||||
Say Y if you plan on running a kernel with a big-endian userspace.
|
Say Y if you plan on running a kernel with a big-endian userspace.
|
||||||
|
|
||||||
|
|
@ -1698,20 +1700,6 @@ config MITIGATE_SPECTRE_BRANCH_HISTORY
|
||||||
When taking an exception from user-space, a sequence of branches
|
When taking an exception from user-space, a sequence of branches
|
||||||
or a firmware call overwrites the branch history.
|
or a firmware call overwrites the branch history.
|
||||||
|
|
||||||
config RODATA_FULL_DEFAULT_ENABLED
|
|
||||||
bool "Apply r/o permissions of VM areas also to their linear aliases"
|
|
||||||
default y
|
|
||||||
help
|
|
||||||
Apply read-only attributes of VM areas to the linear alias of
|
|
||||||
the backing pages as well. This prevents code or read-only data
|
|
||||||
from being modified (inadvertently or intentionally) via another
|
|
||||||
mapping of the same memory page. This additional enhancement can
|
|
||||||
be turned off at runtime by passing rodata=[off|on] (and turned on
|
|
||||||
with rodata=full if this option is set to 'n')
|
|
||||||
|
|
||||||
This requires the linear region to be mapped down to pages,
|
|
||||||
which may adversely affect performance in some cases.
|
|
||||||
|
|
||||||
config ARM64_SW_TTBR0_PAN
|
config ARM64_SW_TTBR0_PAN
|
||||||
bool "Emulate Privileged Access Never using TTBR0_EL1 switching"
|
bool "Emulate Privileged Access Never using TTBR0_EL1 switching"
|
||||||
depends on !KCSAN
|
depends on !KCSAN
|
||||||
|
|
@ -2218,14 +2206,13 @@ config ARM64_HAFT
|
||||||
|
|
||||||
endmenu # "ARMv8.9 architectural features"
|
endmenu # "ARMv8.9 architectural features"
|
||||||
|
|
||||||
menu "v9.4 architectural features"
|
menu "ARMv9.4 architectural features"
|
||||||
|
|
||||||
config ARM64_GCS
|
config ARM64_GCS
|
||||||
bool "Enable support for Guarded Control Stack (GCS)"
|
bool "Enable support for Guarded Control Stack (GCS)"
|
||||||
default y
|
default y
|
||||||
select ARCH_HAS_USER_SHADOW_STACK
|
select ARCH_HAS_USER_SHADOW_STACK
|
||||||
select ARCH_USES_HIGH_VMA_FLAGS
|
select ARCH_USES_HIGH_VMA_FLAGS
|
||||||
depends on !UPROBES
|
|
||||||
help
|
help
|
||||||
Guarded Control Stack (GCS) provides support for a separate
|
Guarded Control Stack (GCS) provides support for a separate
|
||||||
stack with restricted access which contains only return
|
stack with restricted access which contains only return
|
||||||
|
|
@ -2237,7 +2224,7 @@ config ARM64_GCS
|
||||||
The feature is detected at runtime, and will remain disabled
|
The feature is detected at runtime, and will remain disabled
|
||||||
if the system does not implement the feature.
|
if the system does not implement the feature.
|
||||||
|
|
||||||
endmenu # "v9.4 architectural features"
|
endmenu # "ARMv9.4 architectural features"
|
||||||
|
|
||||||
config ARM64_SVE
|
config ARM64_SVE
|
||||||
bool "ARM Scalable Vector Extension support"
|
bool "ARM Scalable Vector Extension support"
|
||||||
|
|
|
||||||
|
|
@ -871,6 +871,8 @@ static inline bool system_supports_pmuv3(void)
|
||||||
return cpus_have_final_cap(ARM64_HAS_PMUV3);
|
return cpus_have_final_cap(ARM64_HAS_PMUV3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cpu_supports_bbml2_noabort(void);
|
||||||
|
|
||||||
static inline bool system_supports_bbml2_noabort(void)
|
static inline bool system_supports_bbml2_noabort(void)
|
||||||
{
|
{
|
||||||
return alternative_has_cap_unlikely(ARM64_HAS_BBML2_NOABORT);
|
return alternative_has_cap_unlikely(ARM64_HAS_BBML2_NOABORT);
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@
|
||||||
#define ARM_CPU_PART_CORTEX_A78AE 0xD42
|
#define ARM_CPU_PART_CORTEX_A78AE 0xD42
|
||||||
#define ARM_CPU_PART_CORTEX_X1 0xD44
|
#define ARM_CPU_PART_CORTEX_X1 0xD44
|
||||||
#define ARM_CPU_PART_CORTEX_A510 0xD46
|
#define ARM_CPU_PART_CORTEX_A510 0xD46
|
||||||
#define ARM_CPU_PART_CORTEX_X1C 0xD4C
|
|
||||||
#define ARM_CPU_PART_CORTEX_A520 0xD80
|
#define ARM_CPU_PART_CORTEX_A520 0xD80
|
||||||
#define ARM_CPU_PART_CORTEX_A710 0xD47
|
#define ARM_CPU_PART_CORTEX_A710 0xD47
|
||||||
#define ARM_CPU_PART_CORTEX_A715 0xD4D
|
#define ARM_CPU_PART_CORTEX_A715 0xD4D
|
||||||
|
|
@ -93,9 +92,11 @@
|
||||||
#define ARM_CPU_PART_NEOVERSE_V2 0xD4F
|
#define ARM_CPU_PART_NEOVERSE_V2 0xD4F
|
||||||
#define ARM_CPU_PART_CORTEX_A720 0xD81
|
#define ARM_CPU_PART_CORTEX_A720 0xD81
|
||||||
#define ARM_CPU_PART_CORTEX_X4 0xD82
|
#define ARM_CPU_PART_CORTEX_X4 0xD82
|
||||||
|
#define ARM_CPU_PART_NEOVERSE_V3AE 0xD83
|
||||||
#define ARM_CPU_PART_NEOVERSE_V3 0xD84
|
#define ARM_CPU_PART_NEOVERSE_V3 0xD84
|
||||||
#define ARM_CPU_PART_CORTEX_X925 0xD85
|
#define ARM_CPU_PART_CORTEX_X925 0xD85
|
||||||
#define ARM_CPU_PART_CORTEX_A725 0xD87
|
#define ARM_CPU_PART_CORTEX_A725 0xD87
|
||||||
|
#define ARM_CPU_PART_CORTEX_A720AE 0xD89
|
||||||
#define ARM_CPU_PART_NEOVERSE_N3 0xD8E
|
#define ARM_CPU_PART_NEOVERSE_N3 0xD8E
|
||||||
|
|
||||||
#define APM_CPU_PART_XGENE 0x000
|
#define APM_CPU_PART_XGENE 0x000
|
||||||
|
|
@ -129,6 +130,7 @@
|
||||||
|
|
||||||
#define NVIDIA_CPU_PART_DENVER 0x003
|
#define NVIDIA_CPU_PART_DENVER 0x003
|
||||||
#define NVIDIA_CPU_PART_CARMEL 0x004
|
#define NVIDIA_CPU_PART_CARMEL 0x004
|
||||||
|
#define NVIDIA_CPU_PART_OLYMPUS 0x010
|
||||||
|
|
||||||
#define FUJITSU_CPU_PART_A64FX 0x001
|
#define FUJITSU_CPU_PART_A64FX 0x001
|
||||||
|
|
||||||
|
|
@ -170,7 +172,6 @@
|
||||||
#define MIDR_CORTEX_A78AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A78AE)
|
#define MIDR_CORTEX_A78AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A78AE)
|
||||||
#define MIDR_CORTEX_X1 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X1)
|
#define MIDR_CORTEX_X1 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X1)
|
||||||
#define MIDR_CORTEX_A510 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A510)
|
#define MIDR_CORTEX_A510 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A510)
|
||||||
#define MIDR_CORTEX_X1C MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X1C)
|
|
||||||
#define MIDR_CORTEX_A520 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A520)
|
#define MIDR_CORTEX_A520 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A520)
|
||||||
#define MIDR_CORTEX_A710 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A710)
|
#define MIDR_CORTEX_A710 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A710)
|
||||||
#define MIDR_CORTEX_A715 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A715)
|
#define MIDR_CORTEX_A715 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A715)
|
||||||
|
|
@ -182,9 +183,11 @@
|
||||||
#define MIDR_NEOVERSE_V2 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V2)
|
#define MIDR_NEOVERSE_V2 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V2)
|
||||||
#define MIDR_CORTEX_A720 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A720)
|
#define MIDR_CORTEX_A720 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A720)
|
||||||
#define MIDR_CORTEX_X4 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X4)
|
#define MIDR_CORTEX_X4 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X4)
|
||||||
|
#define MIDR_NEOVERSE_V3AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3AE)
|
||||||
#define MIDR_NEOVERSE_V3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3)
|
#define MIDR_NEOVERSE_V3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3)
|
||||||
#define MIDR_CORTEX_X925 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X925)
|
#define MIDR_CORTEX_X925 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X925)
|
||||||
#define MIDR_CORTEX_A725 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A725)
|
#define MIDR_CORTEX_A725 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A725)
|
||||||
|
#define MIDR_CORTEX_A720AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A720AE)
|
||||||
#define MIDR_NEOVERSE_N3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_N3)
|
#define MIDR_NEOVERSE_N3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_N3)
|
||||||
#define MIDR_THUNDERX MIDR_CPU_MODEL(ARM_CPU_IMP_CAVIUM, CAVIUM_CPU_PART_THUNDERX)
|
#define MIDR_THUNDERX MIDR_CPU_MODEL(ARM_CPU_IMP_CAVIUM, CAVIUM_CPU_PART_THUNDERX)
|
||||||
#define MIDR_THUNDERX_81XX MIDR_CPU_MODEL(ARM_CPU_IMP_CAVIUM, CAVIUM_CPU_PART_THUNDERX_81XX)
|
#define MIDR_THUNDERX_81XX MIDR_CPU_MODEL(ARM_CPU_IMP_CAVIUM, CAVIUM_CPU_PART_THUNDERX_81XX)
|
||||||
|
|
@ -220,6 +223,7 @@
|
||||||
|
|
||||||
#define MIDR_NVIDIA_DENVER MIDR_CPU_MODEL(ARM_CPU_IMP_NVIDIA, NVIDIA_CPU_PART_DENVER)
|
#define MIDR_NVIDIA_DENVER MIDR_CPU_MODEL(ARM_CPU_IMP_NVIDIA, NVIDIA_CPU_PART_DENVER)
|
||||||
#define MIDR_NVIDIA_CARMEL MIDR_CPU_MODEL(ARM_CPU_IMP_NVIDIA, NVIDIA_CPU_PART_CARMEL)
|
#define MIDR_NVIDIA_CARMEL MIDR_CPU_MODEL(ARM_CPU_IMP_NVIDIA, NVIDIA_CPU_PART_CARMEL)
|
||||||
|
#define MIDR_NVIDIA_OLYMPUS MIDR_CPU_MODEL(ARM_CPU_IMP_NVIDIA, NVIDIA_CPU_PART_OLYMPUS)
|
||||||
#define MIDR_FUJITSU_A64FX MIDR_CPU_MODEL(ARM_CPU_IMP_FUJITSU, FUJITSU_CPU_PART_A64FX)
|
#define MIDR_FUJITSU_A64FX MIDR_CPU_MODEL(ARM_CPU_IMP_FUJITSU, FUJITSU_CPU_PART_A64FX)
|
||||||
#define MIDR_HISI_TSV110 MIDR_CPU_MODEL(ARM_CPU_IMP_HISI, HISI_CPU_PART_TSV110)
|
#define MIDR_HISI_TSV110 MIDR_CPU_MODEL(ARM_CPU_IMP_HISI, HISI_CPU_PART_TSV110)
|
||||||
#define MIDR_HISI_HIP09 MIDR_CPU_MODEL(ARM_CPU_IMP_HISI, HISI_CPU_PART_HIP09)
|
#define MIDR_HISI_HIP09 MIDR_CPU_MODEL(ARM_CPU_IMP_HISI, HISI_CPU_PART_HIP09)
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ static inline void local_daif_inherit(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
unsigned long flags = regs->pstate & DAIF_MASK;
|
unsigned long flags = regs->pstate & DAIF_MASK;
|
||||||
|
|
||||||
if (interrupts_enabled(regs))
|
if (!regs_irqs_disabled(regs))
|
||||||
trace_hardirqs_on();
|
trace_hardirqs_on();
|
||||||
|
|
||||||
if (system_uses_irq_prio_masking())
|
if (system_uses_irq_prio_masking())
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,14 @@
|
||||||
msr cntvoff_el2, xzr // Clear virtual offset
|
msr cntvoff_el2, xzr // Clear virtual offset
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
|
/* Branch to skip_label if SPE version is less than given version */
|
||||||
|
.macro __spe_vers_imp skip_label, version, tmp
|
||||||
|
mrs \tmp, id_aa64dfr0_el1
|
||||||
|
ubfx \tmp, \tmp, #ID_AA64DFR0_EL1_PMSVer_SHIFT, #4
|
||||||
|
cmp \tmp, \version
|
||||||
|
b.lt \skip_label
|
||||||
|
.endm
|
||||||
|
|
||||||
.macro __init_el2_debug
|
.macro __init_el2_debug
|
||||||
mrs x1, id_aa64dfr0_el1
|
mrs x1, id_aa64dfr0_el1
|
||||||
ubfx x0, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4
|
ubfx x0, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4
|
||||||
|
|
@ -103,8 +111,7 @@
|
||||||
csel x2, xzr, x0, eq // all PMU counters from EL1
|
csel x2, xzr, x0, eq // all PMU counters from EL1
|
||||||
|
|
||||||
/* Statistical profiling */
|
/* Statistical profiling */
|
||||||
ubfx x0, x1, #ID_AA64DFR0_EL1_PMSVer_SHIFT, #4
|
__spe_vers_imp .Lskip_spe_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x0 // Skip if SPE not present
|
||||||
cbz x0, .Lskip_spe_\@ // Skip if SPE not present
|
|
||||||
|
|
||||||
mrs_s x0, SYS_PMBIDR_EL1 // If SPE available at EL2,
|
mrs_s x0, SYS_PMBIDR_EL1 // If SPE available at EL2,
|
||||||
and x0, x0, #(1 << PMBIDR_EL1_P_SHIFT)
|
and x0, x0, #(1 << PMBIDR_EL1_P_SHIFT)
|
||||||
|
|
@ -263,10 +270,8 @@
|
||||||
|
|
||||||
mov x0, xzr
|
mov x0, xzr
|
||||||
mov x2, xzr
|
mov x2, xzr
|
||||||
mrs x1, id_aa64dfr0_el1
|
/* If SPEv1p2 is implemented, */
|
||||||
ubfx x1, x1, #ID_AA64DFR0_EL1_PMSVer_SHIFT, #4
|
__spe_vers_imp .Lskip_spe_fgt_\@, #ID_AA64DFR0_EL1_PMSVer_V1P2, x1
|
||||||
cmp x1, #3
|
|
||||||
b.lt .Lskip_spe_fgt_\@
|
|
||||||
/* Disable PMSNEVFR_EL1 read and write traps */
|
/* Disable PMSNEVFR_EL1 read and write traps */
|
||||||
orr x0, x0, #HDFGRTR_EL2_nPMSNEVFR_EL1_MASK
|
orr x0, x0, #HDFGRTR_EL2_nPMSNEVFR_EL1_MASK
|
||||||
orr x2, x2, #HDFGWTR_EL2_nPMSNEVFR_EL1_MASK
|
orr x2, x2, #HDFGWTR_EL2_nPMSNEVFR_EL1_MASK
|
||||||
|
|
@ -387,6 +392,17 @@
|
||||||
orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0
|
orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0
|
||||||
orr x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1
|
orr x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1
|
||||||
.Lskip_pmuv3p9_\@:
|
.Lskip_pmuv3p9_\@:
|
||||||
|
/* If SPE is implemented, */
|
||||||
|
__spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1
|
||||||
|
/* we can read PMSIDR and */
|
||||||
|
mrs_s x1, SYS_PMSIDR_EL1
|
||||||
|
and x1, x1, #PMSIDR_EL1_FDS
|
||||||
|
/* if FEAT_SPE_FDS is implemented, */
|
||||||
|
cbz x1, .Lskip_spefds_\@
|
||||||
|
/* disable traps of PMSDSFR to EL2. */
|
||||||
|
orr x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1
|
||||||
|
|
||||||
|
.Lskip_spefds_\@:
|
||||||
msr_s SYS_HDFGRTR2_EL2, x0
|
msr_s SYS_HDFGRTR2_EL2, x0
|
||||||
msr_s SYS_HDFGWTR2_EL2, x0
|
msr_s SYS_HDFGWTR2_EL2, x0
|
||||||
msr_s SYS_HFGRTR2_EL2, xzr
|
msr_s SYS_HFGRTR2_EL2, xzr
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
|
||||||
|
#ifndef _ASM_ARM64_ENTRY_COMMON_H
|
||||||
|
#define _ASM_ARM64_ENTRY_COMMON_H
|
||||||
|
|
||||||
|
#include <linux/thread_info.h>
|
||||||
|
|
||||||
|
#include <asm/cpufeature.h>
|
||||||
|
#include <asm/daifflags.h>
|
||||||
|
#include <asm/fpsimd.h>
|
||||||
|
#include <asm/mte.h>
|
||||||
|
#include <asm/stacktrace.h>
|
||||||
|
|
||||||
|
#define ARCH_EXIT_TO_USER_MODE_WORK (_TIF_MTE_ASYNC_FAULT | _TIF_FOREIGN_FPSTATE)
|
||||||
|
|
||||||
|
static __always_inline void arch_exit_to_user_mode_work(struct pt_regs *regs,
|
||||||
|
unsigned long ti_work)
|
||||||
|
{
|
||||||
|
if (ti_work & _TIF_MTE_ASYNC_FAULT) {
|
||||||
|
clear_thread_flag(TIF_MTE_ASYNC_FAULT);
|
||||||
|
send_sig_fault(SIGSEGV, SEGV_MTEAERR, (void __user *)NULL, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ti_work & _TIF_FOREIGN_FPSTATE)
|
||||||
|
fpsimd_restore_current_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define arch_exit_to_user_mode_work arch_exit_to_user_mode_work
|
||||||
|
|
||||||
|
static inline bool arch_irqentry_exit_need_resched(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* DAIF.DA are cleared at the start of IRQ/FIQ handling, and when GIC
|
||||||
|
* priority masking is used the GIC irqchip driver will clear DAIF.IF
|
||||||
|
* using gic_arch_enable_irqs() for normal IRQs. If anything is set in
|
||||||
|
* DAIF we must have handled an NMI, so skip preemption.
|
||||||
|
*/
|
||||||
|
if (system_uses_irq_prio_masking() && read_sysreg(daif))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Preempting a task from an IRQ means we leave copies of PSTATE
|
||||||
|
* on the stack. cpufeature's enable calls may modify PSTATE, but
|
||||||
|
* resuming one of these preempted tasks would undo those changes.
|
||||||
|
*
|
||||||
|
* Only allow a task to be preempted once cpufeatures have been
|
||||||
|
* enabled.
|
||||||
|
*/
|
||||||
|
if (!system_capabilities_finalized())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define arch_irqentry_exit_need_resched arch_irqentry_exit_need_resched
|
||||||
|
|
||||||
|
#endif /* _ASM_ARM64_ENTRY_COMMON_H */
|
||||||
|
|
@ -89,7 +89,6 @@ void do_el1_fpac(struct pt_regs *regs, unsigned long esr);
|
||||||
void do_el0_mops(struct pt_regs *regs, unsigned long esr);
|
void do_el0_mops(struct pt_regs *regs, unsigned long esr);
|
||||||
void do_el1_mops(struct pt_regs *regs, unsigned long esr);
|
void do_el1_mops(struct pt_regs *regs, unsigned long esr);
|
||||||
void do_serror(struct pt_regs *regs, unsigned long esr);
|
void do_serror(struct pt_regs *regs, unsigned long esr);
|
||||||
void do_signal(struct pt_regs *regs);
|
|
||||||
|
|
||||||
void __noreturn panic_bad_stack(struct pt_regs *regs, unsigned long esr, unsigned long far);
|
void __noreturn panic_bad_stack(struct pt_regs *regs, unsigned long esr, unsigned long far);
|
||||||
#endif /* __ASM_EXCEPTION_H */
|
#endif /* __ASM_EXCEPTION_H */
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ static inline void gcsstr(u64 *addr, u64 val)
|
||||||
register u64 *_addr __asm__ ("x0") = addr;
|
register u64 *_addr __asm__ ("x0") = addr;
|
||||||
register long _val __asm__ ("x1") = val;
|
register long _val __asm__ ("x1") = val;
|
||||||
|
|
||||||
/* GCSSTTR x1, x0 */
|
/* GCSSTTR x1, [x0] */
|
||||||
asm volatile(
|
asm volatile(
|
||||||
".inst 0xd91f1c01\n"
|
".inst 0xd91f1c01\n"
|
||||||
:
|
:
|
||||||
|
|
@ -81,6 +81,82 @@ static inline int gcs_check_locked(struct task_struct *task,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int gcssttr(unsigned long __user *addr, unsigned long val)
|
||||||
|
{
|
||||||
|
register unsigned long __user *_addr __asm__ ("x0") = addr;
|
||||||
|
register unsigned long _val __asm__ ("x1") = val;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
/* GCSSTTR x1, [x0] */
|
||||||
|
asm volatile(
|
||||||
|
"1: .inst 0xd91f1c01\n"
|
||||||
|
"2: \n"
|
||||||
|
_ASM_EXTABLE_UACCESS_ERR(1b, 2b, %w0)
|
||||||
|
: "+r" (err)
|
||||||
|
: "rZ" (_val), "r" (_addr)
|
||||||
|
: "memory");
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void put_user_gcs(unsigned long val, unsigned long __user *addr,
|
||||||
|
int *err)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!access_ok((char __user *)addr, sizeof(u64))) {
|
||||||
|
*err = -EFAULT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uaccess_ttbr0_enable();
|
||||||
|
ret = gcssttr(addr, val);
|
||||||
|
if (ret != 0)
|
||||||
|
*err = ret;
|
||||||
|
uaccess_ttbr0_disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void push_user_gcs(unsigned long val, int *err)
|
||||||
|
{
|
||||||
|
u64 gcspr = read_sysreg_s(SYS_GCSPR_EL0);
|
||||||
|
|
||||||
|
gcspr -= sizeof(u64);
|
||||||
|
put_user_gcs(val, (unsigned long __user *)gcspr, err);
|
||||||
|
if (!*err)
|
||||||
|
write_sysreg_s(gcspr, SYS_GCSPR_EL0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unlike put/push_user_gcs() above, get/pop_user_gsc() doesn't
|
||||||
|
* validate the GCS permission is set on the page being read. This
|
||||||
|
* differs from how the hardware works when it consumes data stored at
|
||||||
|
* GCSPR. Callers should ensure this is acceptable.
|
||||||
|
*/
|
||||||
|
static inline u64 get_user_gcs(unsigned long __user *addr, int *err)
|
||||||
|
{
|
||||||
|
unsigned long ret;
|
||||||
|
u64 load = 0;
|
||||||
|
|
||||||
|
/* Ensure previous GCS operation are visible before we read the page */
|
||||||
|
gcsb_dsync();
|
||||||
|
ret = copy_from_user(&load, addr, sizeof(load));
|
||||||
|
if (ret != 0)
|
||||||
|
*err = ret;
|
||||||
|
return load;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u64 pop_user_gcs(int *err)
|
||||||
|
{
|
||||||
|
u64 gcspr = read_sysreg_s(SYS_GCSPR_EL0);
|
||||||
|
u64 read_val;
|
||||||
|
|
||||||
|
read_val = get_user_gcs((__force unsigned long __user *)gcspr, err);
|
||||||
|
if (!*err)
|
||||||
|
write_sysreg_s(gcspr + sizeof(u64), SYS_GCSPR_EL0);
|
||||||
|
|
||||||
|
return read_val;
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static inline bool task_gcs_el0_enabled(struct task_struct *task)
|
static inline bool task_gcs_el0_enabled(struct task_struct *task)
|
||||||
|
|
@ -91,6 +167,10 @@ static inline bool task_gcs_el0_enabled(struct task_struct *task)
|
||||||
static inline void gcs_set_el0_mode(struct task_struct *task) { }
|
static inline void gcs_set_el0_mode(struct task_struct *task) { }
|
||||||
static inline void gcs_free(struct task_struct *task) { }
|
static inline void gcs_free(struct task_struct *task) { }
|
||||||
static inline void gcs_preserve_current_state(void) { }
|
static inline void gcs_preserve_current_state(void) { }
|
||||||
|
static inline void put_user_gcs(unsigned long val, unsigned long __user *addr,
|
||||||
|
int *err) { }
|
||||||
|
static inline void push_user_gcs(unsigned long val, int *err) { }
|
||||||
|
|
||||||
static inline unsigned long gcs_alloc_thread_stack(struct task_struct *tsk,
|
static inline unsigned long gcs_alloc_thread_stack(struct task_struct *tsk,
|
||||||
const struct kernel_clone_args *args)
|
const struct kernel_clone_args *args)
|
||||||
{
|
{
|
||||||
|
|
@ -101,6 +181,15 @@ static inline int gcs_check_locked(struct task_struct *task,
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
static inline u64 get_user_gcs(unsigned long __user *addr, int *err)
|
||||||
|
{
|
||||||
|
*err = -EFAULT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
static inline u64 pop_user_gcs(int *err)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,7 @@
|
||||||
#define __khwcap3_feature(x) (const_ilog2(HWCAP3_ ## x) + 128)
|
#define __khwcap3_feature(x) (const_ilog2(HWCAP3_ ## x) + 128)
|
||||||
#define KERNEL_HWCAP_MTE_FAR __khwcap3_feature(MTE_FAR)
|
#define KERNEL_HWCAP_MTE_FAR __khwcap3_feature(MTE_FAR)
|
||||||
#define KERNEL_HWCAP_MTE_STORE_ONLY __khwcap3_feature(MTE_STORE_ONLY)
|
#define KERNEL_HWCAP_MTE_STORE_ONLY __khwcap3_feature(MTE_STORE_ONLY)
|
||||||
|
#define KERNEL_HWCAP_LSFE __khwcap3_feature(LSFE)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This yields a mask that user programs can use to figure out what
|
* This yields a mask that user programs can use to figure out what
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,10 @@ int arm64_ioremap_prot_hook_register(const ioremap_prot_hook_t hook);
|
||||||
#define ioremap_np(addr, size) \
|
#define ioremap_np(addr, size) \
|
||||||
ioremap_prot((addr), (size), __pgprot(PROT_DEVICE_nGnRnE))
|
ioremap_prot((addr), (size), __pgprot(PROT_DEVICE_nGnRnE))
|
||||||
|
|
||||||
|
|
||||||
|
#define ioremap_encrypted(addr, size) \
|
||||||
|
ioremap_prot((addr), (size), PAGE_KERNEL)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* io{read,write}{16,32,64}be() macros
|
* io{read,write}{16,32,64}be() macros
|
||||||
*/
|
*/
|
||||||
|
|
@ -311,7 +315,7 @@ extern bool arch_memremap_can_ram_remap(resource_size_t offset, size_t size,
|
||||||
static inline bool arm64_is_protected_mmio(phys_addr_t phys_addr, size_t size)
|
static inline bool arm64_is_protected_mmio(phys_addr_t phys_addr, size_t size)
|
||||||
{
|
{
|
||||||
if (unlikely(is_realm_world()))
|
if (unlikely(is_realm_world()))
|
||||||
return __arm64_is_protected_mmio(phys_addr, size);
|
return arm64_rsi_is_protected(phys_addr, size);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,9 @@ extern void create_pgd_mapping(struct mm_struct *mm, phys_addr_t phys,
|
||||||
pgprot_t prot, bool page_mappings_only);
|
pgprot_t prot, bool page_mappings_only);
|
||||||
extern void *fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot);
|
extern void *fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot);
|
||||||
extern void mark_linear_text_alias_ro(void);
|
extern void mark_linear_text_alias_ro(void);
|
||||||
|
extern int split_kernel_leaf_mapping(unsigned long start, unsigned long end);
|
||||||
|
extern void init_idmap_kpti_bbml2_flag(void);
|
||||||
|
extern void linear_map_maybe_split_to_ptes(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This check is triggered during the early boot before the cpufeature
|
* This check is triggered during the early boot before the cpufeature
|
||||||
|
|
|
||||||
|
|
@ -371,6 +371,11 @@ static inline pmd_t pmd_mkcont(pmd_t pmd)
|
||||||
return __pmd(pmd_val(pmd) | PMD_SECT_CONT);
|
return __pmd(pmd_val(pmd) | PMD_SECT_CONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline pmd_t pmd_mknoncont(pmd_t pmd)
|
||||||
|
{
|
||||||
|
return __pmd(pmd_val(pmd) & ~PMD_SECT_CONT);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_HAVE_ARCH_USERFAULTFD_WP
|
#ifdef CONFIG_HAVE_ARCH_USERFAULTFD_WP
|
||||||
static inline int pte_uffd_wp(pte_t pte)
|
static inline int pte_uffd_wp(pte_t pte)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#ifndef __ASM_PREEMPT_H
|
#ifndef __ASM_PREEMPT_H
|
||||||
#define __ASM_PREEMPT_H
|
#define __ASM_PREEMPT_H
|
||||||
|
|
||||||
#include <linux/jump_label.h>
|
|
||||||
#include <linux/thread_info.h>
|
#include <linux/thread_info.h>
|
||||||
|
|
||||||
#define PREEMPT_NEED_RESCHED BIT(32)
|
#define PREEMPT_NEED_RESCHED BIT(32)
|
||||||
|
|
@ -87,7 +86,6 @@ void preempt_schedule_notrace(void);
|
||||||
|
|
||||||
#ifdef CONFIG_PREEMPT_DYNAMIC
|
#ifdef CONFIG_PREEMPT_DYNAMIC
|
||||||
|
|
||||||
DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
|
|
||||||
void dynamic_preempt_schedule(void);
|
void dynamic_preempt_schedule(void);
|
||||||
#define __preempt_schedule() dynamic_preempt_schedule()
|
#define __preempt_schedule() dynamic_preempt_schedule()
|
||||||
void dynamic_preempt_schedule_notrace(void);
|
void dynamic_preempt_schedule_notrace(void);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#include <linux/ptdump.h>
|
#include <linux/ptdump.h>
|
||||||
|
|
||||||
|
DECLARE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
|
||||||
|
|
||||||
#ifdef CONFIG_PTDUMP
|
#ifdef CONFIG_PTDUMP
|
||||||
|
|
||||||
#include <linux/mm_types.h>
|
#include <linux/mm_types.h>
|
||||||
|
|
|
||||||
|
|
@ -169,10 +169,6 @@ struct pt_regs {
|
||||||
|
|
||||||
u64 sdei_ttbr1;
|
u64 sdei_ttbr1;
|
||||||
struct frame_record_meta stackframe;
|
struct frame_record_meta stackframe;
|
||||||
|
|
||||||
/* Only valid for some EL1 exceptions. */
|
|
||||||
u64 lockdep_hardirqs;
|
|
||||||
u64 exit_rcu;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* For correct stack alignment, pt_regs has to be a multiple of 16 bytes. */
|
/* For correct stack alignment, pt_regs has to be a multiple of 16 bytes. */
|
||||||
|
|
@ -214,11 +210,12 @@ static inline void forget_syscall(struct pt_regs *regs)
|
||||||
(regs)->pmr == GIC_PRIO_IRQON : \
|
(regs)->pmr == GIC_PRIO_IRQON : \
|
||||||
true)
|
true)
|
||||||
|
|
||||||
#define interrupts_enabled(regs) \
|
static __always_inline bool regs_irqs_disabled(const struct pt_regs *regs)
|
||||||
(!((regs)->pstate & PSR_I_BIT) && irqs_priority_unmasked(regs))
|
{
|
||||||
|
return (regs->pstate & PSR_I_BIT) || !irqs_priority_unmasked(regs);
|
||||||
|
}
|
||||||
|
|
||||||
#define fast_interrupts_enabled(regs) \
|
#define interrupts_enabled(regs) (!regs_irqs_disabled(regs))
|
||||||
(!((regs)->pstate & PSR_F_BIT))
|
|
||||||
|
|
||||||
static inline unsigned long user_stack_pointer(struct pt_regs *regs)
|
static inline unsigned long user_stack_pointer(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ DECLARE_STATIC_KEY_FALSE(rsi_present);
|
||||||
|
|
||||||
void __init arm64_rsi_init(void);
|
void __init arm64_rsi_init(void);
|
||||||
|
|
||||||
bool __arm64_is_protected_mmio(phys_addr_t base, size_t size);
|
bool arm64_rsi_is_protected(phys_addr_t base, size_t size);
|
||||||
|
|
||||||
static inline bool is_realm_world(void)
|
static inline bool is_realm_world(void)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ static inline bool arch_parse_debug_rodata(char *arg)
|
||||||
if (!arg)
|
if (!arg)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!strcmp(arg, "full")) {
|
if (!strcmp(arg, "on")) {
|
||||||
rodata_enabled = rodata_full = true;
|
rodata_enabled = rodata_full = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ static inline bool arch_parse_debug_rodata(char *arg)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(arg, "on")) {
|
if (!strcmp(arg, "noalias")) {
|
||||||
rodata_enabled = true;
|
rodata_enabled = true;
|
||||||
rodata_full = false;
|
rodata_full = false;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,6 @@
|
||||||
#define SYS_RGSR_EL1 sys_reg(3, 0, 1, 0, 5)
|
#define SYS_RGSR_EL1 sys_reg(3, 0, 1, 0, 5)
|
||||||
#define SYS_GCR_EL1 sys_reg(3, 0, 1, 0, 6)
|
#define SYS_GCR_EL1 sys_reg(3, 0, 1, 0, 6)
|
||||||
|
|
||||||
#define SYS_TCR_EL1 sys_reg(3, 0, 2, 0, 2)
|
|
||||||
|
|
||||||
#define SYS_APIAKEYLO_EL1 sys_reg(3, 0, 2, 1, 0)
|
#define SYS_APIAKEYLO_EL1 sys_reg(3, 0, 2, 1, 0)
|
||||||
#define SYS_APIAKEYHI_EL1 sys_reg(3, 0, 2, 1, 1)
|
#define SYS_APIAKEYHI_EL1 sys_reg(3, 0, 2, 1, 1)
|
||||||
#define SYS_APIBKEYLO_EL1 sys_reg(3, 0, 2, 1, 2)
|
#define SYS_APIBKEYLO_EL1 sys_reg(3, 0, 2, 1, 2)
|
||||||
|
|
@ -344,15 +342,6 @@
|
||||||
#define SYS_PAR_EL1_ATTR GENMASK_ULL(63, 56)
|
#define SYS_PAR_EL1_ATTR GENMASK_ULL(63, 56)
|
||||||
#define SYS_PAR_EL1_F0_RES0 (GENMASK_ULL(6, 1) | GENMASK_ULL(55, 52))
|
#define SYS_PAR_EL1_F0_RES0 (GENMASK_ULL(6, 1) | GENMASK_ULL(55, 52))
|
||||||
|
|
||||||
/*** Statistical Profiling Extension ***/
|
|
||||||
#define PMSEVFR_EL1_RES0_IMP \
|
|
||||||
(GENMASK_ULL(47, 32) | GENMASK_ULL(23, 16) | GENMASK_ULL(11, 8) |\
|
|
||||||
BIT_ULL(6) | BIT_ULL(4) | BIT_ULL(2) | BIT_ULL(0))
|
|
||||||
#define PMSEVFR_EL1_RES0_V1P1 \
|
|
||||||
(PMSEVFR_EL1_RES0_IMP & ~(BIT_ULL(18) | BIT_ULL(17) | BIT_ULL(11)))
|
|
||||||
#define PMSEVFR_EL1_RES0_V1P2 \
|
|
||||||
(PMSEVFR_EL1_RES0_V1P1 & ~BIT_ULL(6))
|
|
||||||
|
|
||||||
/* Buffer error reporting */
|
/* Buffer error reporting */
|
||||||
#define PMBSR_EL1_FAULT_FSC_SHIFT PMBSR_EL1_MSS_SHIFT
|
#define PMBSR_EL1_FAULT_FSC_SHIFT PMBSR_EL1_MSS_SHIFT
|
||||||
#define PMBSR_EL1_FAULT_FSC_MASK PMBSR_EL1_MSS_MASK
|
#define PMBSR_EL1_FAULT_FSC_MASK PMBSR_EL1_MSS_MASK
|
||||||
|
|
|
||||||
|
|
@ -502,44 +502,4 @@ static inline size_t probe_subpage_writeable(const char __user *uaddr,
|
||||||
|
|
||||||
#endif /* CONFIG_ARCH_HAS_SUBPAGE_FAULTS */
|
#endif /* CONFIG_ARCH_HAS_SUBPAGE_FAULTS */
|
||||||
|
|
||||||
#ifdef CONFIG_ARM64_GCS
|
|
||||||
|
|
||||||
static inline int gcssttr(unsigned long __user *addr, unsigned long val)
|
|
||||||
{
|
|
||||||
register unsigned long __user *_addr __asm__ ("x0") = addr;
|
|
||||||
register unsigned long _val __asm__ ("x1") = val;
|
|
||||||
int err = 0;
|
|
||||||
|
|
||||||
/* GCSSTTR x1, x0 */
|
|
||||||
asm volatile(
|
|
||||||
"1: .inst 0xd91f1c01\n"
|
|
||||||
"2: \n"
|
|
||||||
_ASM_EXTABLE_UACCESS_ERR(1b, 2b, %w0)
|
|
||||||
: "+r" (err)
|
|
||||||
: "rZ" (_val), "r" (_addr)
|
|
||||||
: "memory");
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void put_user_gcs(unsigned long val, unsigned long __user *addr,
|
|
||||||
int *err)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!access_ok((char __user *)addr, sizeof(u64))) {
|
|
||||||
*err = -EFAULT;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uaccess_ttbr0_enable();
|
|
||||||
ret = gcssttr(addr, val);
|
|
||||||
if (ret != 0)
|
|
||||||
*err = ret;
|
|
||||||
uaccess_ttbr0_disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* CONFIG_ARM64_GCS */
|
|
||||||
|
|
||||||
#endif /* __ASM_UACCESS_H */
|
#endif /* __ASM_UACCESS_H */
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,13 @@
|
||||||
#define arch_vmap_pud_supported arch_vmap_pud_supported
|
#define arch_vmap_pud_supported arch_vmap_pud_supported
|
||||||
static inline bool arch_vmap_pud_supported(pgprot_t prot)
|
static inline bool arch_vmap_pud_supported(pgprot_t prot)
|
||||||
{
|
{
|
||||||
/*
|
return pud_sect_supported();
|
||||||
* SW table walks can't handle removal of intermediate entries.
|
|
||||||
*/
|
|
||||||
return pud_sect_supported() &&
|
|
||||||
!IS_ENABLED(CONFIG_PTDUMP_DEBUGFS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define arch_vmap_pmd_supported arch_vmap_pmd_supported
|
#define arch_vmap_pmd_supported arch_vmap_pmd_supported
|
||||||
static inline bool arch_vmap_pmd_supported(pgprot_t prot)
|
static inline bool arch_vmap_pmd_supported(pgprot_t prot)
|
||||||
{
|
{
|
||||||
/* See arch_vmap_pud_supported() */
|
return true;
|
||||||
return !IS_ENABLED(CONFIG_PTDUMP_DEBUGFS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define arch_vmap_pte_range_map_size arch_vmap_pte_range_map_size
|
#define arch_vmap_pte_range_map_size arch_vmap_pte_range_map_size
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ enum ipi_vector {
|
||||||
|
|
||||||
static inline int xen_irqs_disabled(struct pt_regs *regs)
|
static inline int xen_irqs_disabled(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
return !interrupts_enabled(regs);
|
return regs_irqs_disabled(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define xchg_xen_ulong(ptr, val) xchg((ptr), (val))
|
#define xchg_xen_ulong(ptr, val) xchg((ptr), (val))
|
||||||
|
|
|
||||||
|
|
@ -145,5 +145,6 @@
|
||||||
*/
|
*/
|
||||||
#define HWCAP3_MTE_FAR (1UL << 0)
|
#define HWCAP3_MTE_FAR (1UL << 0)
|
||||||
#define HWCAP3_MTE_STORE_ONLY (1UL << 1)
|
#define HWCAP3_MTE_STORE_ONLY (1UL << 1)
|
||||||
|
#define HWCAP3_LSFE (1UL << 2)
|
||||||
|
|
||||||
#endif /* _UAPI__ASM_HWCAP_H */
|
#endif /* _UAPI__ASM_HWCAP_H */
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,16 @@ void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size)
|
||||||
* as long as we take care not to create a writable
|
* as long as we take care not to create a writable
|
||||||
* mapping for executable code.
|
* mapping for executable code.
|
||||||
*/
|
*/
|
||||||
|
fallthrough;
|
||||||
|
|
||||||
|
case EFI_ACPI_MEMORY_NVS:
|
||||||
|
/*
|
||||||
|
* ACPI NVS marks an area reserved for use by the
|
||||||
|
* firmware, even after exiting the boot service.
|
||||||
|
* This may be used by the firmware for sharing dynamic
|
||||||
|
* tables/data (e.g., ACPI CCEL) with the OS. Map it
|
||||||
|
* as read-only.
|
||||||
|
*/
|
||||||
prot = PAGE_KERNEL_RO;
|
prot = PAGE_KERNEL_RO;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -407,7 +417,7 @@ int apei_claim_sea(struct pt_regs *regs)
|
||||||
return_to_irqs_enabled = !irqs_disabled_flags(arch_local_save_flags());
|
return_to_irqs_enabled = !irqs_disabled_flags(arch_local_save_flags());
|
||||||
|
|
||||||
if (regs)
|
if (regs)
|
||||||
return_to_irqs_enabled = interrupts_enabled(regs);
|
return_to_irqs_enabled = !regs_irqs_disabled(regs);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SEA can interrupt SError, mask it and describe this as an NMI so
|
* SEA can interrupt SError, mask it and describe this as an NMI so
|
||||||
|
|
|
||||||
|
|
@ -531,6 +531,7 @@ static const struct midr_range erratum_spec_ssbs_list[] = {
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_A710),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A710),
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_A715),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A715),
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_A720),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A720),
|
||||||
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A720AE),
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_A725),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A725),
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_X1),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_X1),
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_X1C),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_X1C),
|
||||||
|
|
@ -545,6 +546,7 @@ static const struct midr_range erratum_spec_ssbs_list[] = {
|
||||||
MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V1),
|
MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V1),
|
||||||
MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V2),
|
MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V2),
|
||||||
MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V3),
|
MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V3),
|
||||||
|
MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V3AE),
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,7 @@ static const struct arm64_ftr_bits ftr_id_aa64isar2[] = {
|
||||||
|
|
||||||
static const struct arm64_ftr_bits ftr_id_aa64isar3[] = {
|
static const struct arm64_ftr_bits ftr_id_aa64isar3[] = {
|
||||||
ARM64_FTR_BITS(FTR_VISIBLE, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64ISAR3_EL1_FPRCVT_SHIFT, 4, 0),
|
ARM64_FTR_BITS(FTR_VISIBLE, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64ISAR3_EL1_FPRCVT_SHIFT, 4, 0),
|
||||||
|
ARM64_FTR_BITS(FTR_VISIBLE, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64ISAR3_EL1_LSFE_SHIFT, 4, 0),
|
||||||
ARM64_FTR_BITS(FTR_VISIBLE, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64ISAR3_EL1_FAMINMAX_SHIFT, 4, 0),
|
ARM64_FTR_BITS(FTR_VISIBLE, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64ISAR3_EL1_FAMINMAX_SHIFT, 4, 0),
|
||||||
ARM64_FTR_END,
|
ARM64_FTR_END,
|
||||||
};
|
};
|
||||||
|
|
@ -2028,6 +2029,7 @@ static void __init kpti_install_ng_mappings(void)
|
||||||
if (arm64_use_ng_mappings)
|
if (arm64_use_ng_mappings)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
init_idmap_kpti_bbml2_flag();
|
||||||
stop_machine(__kpti_install_ng_mappings, NULL, cpu_online_mask);
|
stop_machine(__kpti_install_ng_mappings, NULL, cpu_online_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2218,7 +2220,7 @@ static bool hvhe_possible(const struct arm64_cpu_capabilities *entry,
|
||||||
return arm64_test_sw_feature_override(ARM64_SW_FEATURE_OVERRIDE_HVHE);
|
return arm64_test_sw_feature_override(ARM64_SW_FEATURE_OVERRIDE_HVHE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool has_bbml2_noabort(const struct arm64_cpu_capabilities *caps, int scope)
|
bool cpu_supports_bbml2_noabort(void)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* We want to allow usage of BBML2 in as wide a range of kernel contexts
|
* We want to allow usage of BBML2 in as wide a range of kernel contexts
|
||||||
|
|
@ -2235,6 +2237,10 @@ static bool has_bbml2_noabort(const struct arm64_cpu_capabilities *caps, int sco
|
||||||
static const struct midr_range supports_bbml2_noabort_list[] = {
|
static const struct midr_range supports_bbml2_noabort_list[] = {
|
||||||
MIDR_REV_RANGE(MIDR_CORTEX_X4, 0, 3, 0xf),
|
MIDR_REV_RANGE(MIDR_CORTEX_X4, 0, 3, 0xf),
|
||||||
MIDR_REV_RANGE(MIDR_NEOVERSE_V3, 0, 2, 0xf),
|
MIDR_REV_RANGE(MIDR_NEOVERSE_V3, 0, 2, 0xf),
|
||||||
|
MIDR_REV_RANGE(MIDR_NEOVERSE_V3AE, 0, 2, 0xf),
|
||||||
|
MIDR_ALL_VERSIONS(MIDR_NVIDIA_OLYMPUS),
|
||||||
|
MIDR_ALL_VERSIONS(MIDR_AMPERE1),
|
||||||
|
MIDR_ALL_VERSIONS(MIDR_AMPERE1A),
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2250,6 +2256,11 @@ static bool has_bbml2_noabort(const struct arm64_cpu_capabilities *caps, int sco
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool has_bbml2_noabort(const struct arm64_cpu_capabilities *caps, int scope)
|
||||||
|
{
|
||||||
|
return cpu_supports_bbml2_noabort();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_ARM64_PAN
|
#ifdef CONFIG_ARM64_PAN
|
||||||
static void cpu_enable_pan(const struct arm64_cpu_capabilities *__unused)
|
static void cpu_enable_pan(const struct arm64_cpu_capabilities *__unused)
|
||||||
{
|
{
|
||||||
|
|
@ -3277,6 +3288,7 @@ static const struct arm64_cpu_capabilities arm64_elf_hwcaps[] = {
|
||||||
HWCAP_CAP(ID_AA64ISAR1_EL1, I8MM, IMP, CAP_HWCAP, KERNEL_HWCAP_I8MM),
|
HWCAP_CAP(ID_AA64ISAR1_EL1, I8MM, IMP, CAP_HWCAP, KERNEL_HWCAP_I8MM),
|
||||||
HWCAP_CAP(ID_AA64ISAR2_EL1, LUT, IMP, CAP_HWCAP, KERNEL_HWCAP_LUT),
|
HWCAP_CAP(ID_AA64ISAR2_EL1, LUT, IMP, CAP_HWCAP, KERNEL_HWCAP_LUT),
|
||||||
HWCAP_CAP(ID_AA64ISAR3_EL1, FAMINMAX, IMP, CAP_HWCAP, KERNEL_HWCAP_FAMINMAX),
|
HWCAP_CAP(ID_AA64ISAR3_EL1, FAMINMAX, IMP, CAP_HWCAP, KERNEL_HWCAP_FAMINMAX),
|
||||||
|
HWCAP_CAP(ID_AA64ISAR3_EL1, LSFE, IMP, CAP_HWCAP, KERNEL_HWCAP_LSFE),
|
||||||
HWCAP_CAP(ID_AA64MMFR2_EL1, AT, IMP, CAP_HWCAP, KERNEL_HWCAP_USCAT),
|
HWCAP_CAP(ID_AA64MMFR2_EL1, AT, IMP, CAP_HWCAP, KERNEL_HWCAP_USCAT),
|
||||||
#ifdef CONFIG_ARM64_SVE
|
#ifdef CONFIG_ARM64_SVE
|
||||||
HWCAP_CAP(ID_AA64PFR0_EL1, SVE, IMP, CAP_HWCAP, KERNEL_HWCAP_SVE),
|
HWCAP_CAP(ID_AA64PFR0_EL1, SVE, IMP, CAP_HWCAP, KERNEL_HWCAP_SVE),
|
||||||
|
|
@ -3948,6 +3960,7 @@ void __init setup_system_features(void)
|
||||||
{
|
{
|
||||||
setup_system_capabilities();
|
setup_system_capabilities();
|
||||||
|
|
||||||
|
linear_map_maybe_split_to_ptes();
|
||||||
kpti_install_ng_mappings();
|
kpti_install_ng_mappings();
|
||||||
|
|
||||||
sve_setup();
|
sve_setup();
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,7 @@ static const char *const hwcap_str[] = {
|
||||||
[KERNEL_HWCAP_SME_SMOP4] = "smesmop4",
|
[KERNEL_HWCAP_SME_SMOP4] = "smesmop4",
|
||||||
[KERNEL_HWCAP_MTE_FAR] = "mtefar",
|
[KERNEL_HWCAP_MTE_FAR] = "mtefar",
|
||||||
[KERNEL_HWCAP_MTE_STORE_ONLY] = "mtestoreonly",
|
[KERNEL_HWCAP_MTE_STORE_ONLY] = "mtestoreonly",
|
||||||
|
[KERNEL_HWCAP_LSFE] = "lsfe",
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_COMPAT
|
#ifdef CONFIG_COMPAT
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ static void send_user_sigtrap(int si_code)
|
||||||
if (WARN_ON(!user_mode(regs)))
|
if (WARN_ON(!user_mode(regs)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (interrupts_enabled(regs))
|
if (!regs_irqs_disabled(regs))
|
||||||
local_irq_enable();
|
local_irq_enable();
|
||||||
|
|
||||||
arm64_force_sig_fault(SIGTRAP, si_code, instruction_pointer(regs),
|
arm64_force_sig_fault(SIGTRAP, si_code, instruction_pointer(regs),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/context_tracking.h>
|
#include <linux/context_tracking.h>
|
||||||
|
#include <linux/irq-entry-common.h>
|
||||||
#include <linux/kasan.h>
|
#include <linux/kasan.h>
|
||||||
#include <linux/linkage.h>
|
#include <linux/linkage.h>
|
||||||
#include <linux/livepatch.h>
|
#include <linux/livepatch.h>
|
||||||
|
|
@ -37,29 +38,20 @@
|
||||||
* This is intended to match the logic in irqentry_enter(), handling the kernel
|
* This is intended to match the logic in irqentry_enter(), handling the kernel
|
||||||
* mode transitions only.
|
* mode transitions only.
|
||||||
*/
|
*/
|
||||||
static __always_inline void __enter_from_kernel_mode(struct pt_regs *regs)
|
static __always_inline irqentry_state_t __enter_from_kernel_mode(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
regs->exit_rcu = false;
|
return irqentry_enter(regs);
|
||||||
|
|
||||||
if (!IS_ENABLED(CONFIG_TINY_RCU) && is_idle_task(current)) {
|
|
||||||
lockdep_hardirqs_off(CALLER_ADDR0);
|
|
||||||
ct_irq_enter();
|
|
||||||
trace_hardirqs_off_finish();
|
|
||||||
|
|
||||||
regs->exit_rcu = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lockdep_hardirqs_off(CALLER_ADDR0);
|
|
||||||
rcu_irq_enter_check_tick();
|
|
||||||
trace_hardirqs_off_finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr enter_from_kernel_mode(struct pt_regs *regs)
|
static noinstr irqentry_state_t enter_from_kernel_mode(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
__enter_from_kernel_mode(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = __enter_from_kernel_mode(regs);
|
||||||
mte_check_tfsr_entry();
|
mte_check_tfsr_entry();
|
||||||
mte_disable_tco_entry(current);
|
mte_disable_tco_entry(current);
|
||||||
|
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -70,30 +62,17 @@ static void noinstr enter_from_kernel_mode(struct pt_regs *regs)
|
||||||
* This is intended to match the logic in irqentry_exit(), handling the kernel
|
* This is intended to match the logic in irqentry_exit(), handling the kernel
|
||||||
* mode transitions only, and with preemption handled elsewhere.
|
* mode transitions only, and with preemption handled elsewhere.
|
||||||
*/
|
*/
|
||||||
static __always_inline void __exit_to_kernel_mode(struct pt_regs *regs)
|
static __always_inline void __exit_to_kernel_mode(struct pt_regs *regs,
|
||||||
|
irqentry_state_t state)
|
||||||
{
|
{
|
||||||
lockdep_assert_irqs_disabled();
|
irqentry_exit(regs, state);
|
||||||
|
|
||||||
if (interrupts_enabled(regs)) {
|
|
||||||
if (regs->exit_rcu) {
|
|
||||||
trace_hardirqs_on_prepare();
|
|
||||||
lockdep_hardirqs_on_prepare();
|
|
||||||
ct_irq_exit();
|
|
||||||
lockdep_hardirqs_on(CALLER_ADDR0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trace_hardirqs_on();
|
|
||||||
} else {
|
|
||||||
if (regs->exit_rcu)
|
|
||||||
ct_irq_exit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr exit_to_kernel_mode(struct pt_regs *regs)
|
static void noinstr exit_to_kernel_mode(struct pt_regs *regs,
|
||||||
|
irqentry_state_t state)
|
||||||
{
|
{
|
||||||
mte_check_tfsr_exit();
|
mte_check_tfsr_exit();
|
||||||
__exit_to_kernel_mode(regs);
|
__exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -101,18 +80,15 @@ static void noinstr exit_to_kernel_mode(struct pt_regs *regs)
|
||||||
* Before this function is called it is not safe to call regular kernel code,
|
* Before this function is called it is not safe to call regular kernel code,
|
||||||
* instrumentable code, or any code which may trigger an exception.
|
* instrumentable code, or any code which may trigger an exception.
|
||||||
*/
|
*/
|
||||||
static __always_inline void __enter_from_user_mode(void)
|
static __always_inline void __enter_from_user_mode(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
lockdep_hardirqs_off(CALLER_ADDR0);
|
enter_from_user_mode(regs);
|
||||||
CT_WARN_ON(ct_state() != CT_STATE_USER);
|
|
||||||
user_exit_irqoff();
|
|
||||||
trace_hardirqs_off_finish();
|
|
||||||
mte_disable_tco_entry(current);
|
mte_disable_tco_entry(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
static __always_inline void enter_from_user_mode(struct pt_regs *regs)
|
static __always_inline void arm64_enter_from_user_mode(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
__enter_from_user_mode();
|
__enter_from_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -120,113 +96,19 @@ static __always_inline void enter_from_user_mode(struct pt_regs *regs)
|
||||||
* After this function returns it is not safe to call regular kernel code,
|
* After this function returns it is not safe to call regular kernel code,
|
||||||
* instrumentable code, or any code which may trigger an exception.
|
* instrumentable code, or any code which may trigger an exception.
|
||||||
*/
|
*/
|
||||||
static __always_inline void __exit_to_user_mode(void)
|
|
||||||
|
static __always_inline void arm64_exit_to_user_mode(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
trace_hardirqs_on_prepare();
|
|
||||||
lockdep_hardirqs_on_prepare();
|
|
||||||
user_enter_irqoff();
|
|
||||||
lockdep_hardirqs_on(CALLER_ADDR0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void do_notify_resume(struct pt_regs *regs, unsigned long thread_flags)
|
|
||||||
{
|
|
||||||
do {
|
|
||||||
local_irq_enable();
|
|
||||||
|
|
||||||
if (thread_flags & (_TIF_NEED_RESCHED | _TIF_NEED_RESCHED_LAZY))
|
|
||||||
schedule();
|
|
||||||
|
|
||||||
if (thread_flags & _TIF_UPROBE)
|
|
||||||
uprobe_notify_resume(regs);
|
|
||||||
|
|
||||||
if (thread_flags & _TIF_MTE_ASYNC_FAULT) {
|
|
||||||
clear_thread_flag(TIF_MTE_ASYNC_FAULT);
|
|
||||||
send_sig_fault(SIGSEGV, SEGV_MTEAERR,
|
|
||||||
(void __user *)NULL, current);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thread_flags & _TIF_PATCH_PENDING)
|
|
||||||
klp_update_patch_state(current);
|
|
||||||
|
|
||||||
if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
|
|
||||||
do_signal(regs);
|
|
||||||
|
|
||||||
if (thread_flags & _TIF_NOTIFY_RESUME)
|
|
||||||
resume_user_mode_work(regs);
|
|
||||||
|
|
||||||
if (thread_flags & _TIF_FOREIGN_FPSTATE)
|
|
||||||
fpsimd_restore_current_state();
|
|
||||||
|
|
||||||
local_irq_disable();
|
|
||||||
thread_flags = read_thread_flags();
|
|
||||||
} while (thread_flags & _TIF_WORK_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
static __always_inline void exit_to_user_mode_prepare(struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
local_irq_disable();
|
local_irq_disable();
|
||||||
|
|
||||||
flags = read_thread_flags();
|
|
||||||
if (unlikely(flags & _TIF_WORK_MASK))
|
|
||||||
do_notify_resume(regs, flags);
|
|
||||||
|
|
||||||
local_daif_mask();
|
|
||||||
|
|
||||||
lockdep_sys_exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
static __always_inline void exit_to_user_mode(struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
exit_to_user_mode_prepare(regs);
|
exit_to_user_mode_prepare(regs);
|
||||||
|
local_daif_mask();
|
||||||
mte_check_tfsr_exit();
|
mte_check_tfsr_exit();
|
||||||
__exit_to_user_mode();
|
exit_to_user_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage void noinstr asm_exit_to_user_mode(struct pt_regs *regs)
|
asmlinkage void noinstr asm_exit_to_user_mode(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle IRQ/context state management when entering an NMI from user/kernel
|
|
||||||
* mode. Before this function is called it is not safe to call regular kernel
|
|
||||||
* code, instrumentable code, or any code which may trigger an exception.
|
|
||||||
*/
|
|
||||||
static void noinstr arm64_enter_nmi(struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
regs->lockdep_hardirqs = lockdep_hardirqs_enabled();
|
|
||||||
|
|
||||||
__nmi_enter();
|
|
||||||
lockdep_hardirqs_off(CALLER_ADDR0);
|
|
||||||
lockdep_hardirq_enter();
|
|
||||||
ct_nmi_enter();
|
|
||||||
|
|
||||||
trace_hardirqs_off_finish();
|
|
||||||
ftrace_nmi_enter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle IRQ/context state management when exiting an NMI from user/kernel
|
|
||||||
* mode. After this function returns it is not safe to call regular kernel
|
|
||||||
* code, instrumentable code, or any code which may trigger an exception.
|
|
||||||
*/
|
|
||||||
static void noinstr arm64_exit_nmi(struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
bool restore = regs->lockdep_hardirqs;
|
|
||||||
|
|
||||||
ftrace_nmi_exit();
|
|
||||||
if (restore) {
|
|
||||||
trace_hardirqs_on_prepare();
|
|
||||||
lockdep_hardirqs_on_prepare();
|
|
||||||
}
|
|
||||||
|
|
||||||
ct_nmi_exit();
|
|
||||||
lockdep_hardirq_exit();
|
|
||||||
if (restore)
|
|
||||||
lockdep_hardirqs_on(CALLER_ADDR0);
|
|
||||||
__nmi_exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -234,14 +116,18 @@ static void noinstr arm64_exit_nmi(struct pt_regs *regs)
|
||||||
* kernel mode. Before this function is called it is not safe to call regular
|
* kernel mode. Before this function is called it is not safe to call regular
|
||||||
* kernel code, instrumentable code, or any code which may trigger an exception.
|
* kernel code, instrumentable code, or any code which may trigger an exception.
|
||||||
*/
|
*/
|
||||||
static void noinstr arm64_enter_el1_dbg(struct pt_regs *regs)
|
static noinstr irqentry_state_t arm64_enter_el1_dbg(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
regs->lockdep_hardirqs = lockdep_hardirqs_enabled();
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state.lockdep = lockdep_hardirqs_enabled();
|
||||||
|
|
||||||
lockdep_hardirqs_off(CALLER_ADDR0);
|
lockdep_hardirqs_off(CALLER_ADDR0);
|
||||||
ct_nmi_enter();
|
ct_nmi_enter();
|
||||||
|
|
||||||
trace_hardirqs_off_finish();
|
trace_hardirqs_off_finish();
|
||||||
|
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -249,62 +135,19 @@ static void noinstr arm64_enter_el1_dbg(struct pt_regs *regs)
|
||||||
* kernel mode. After this function returns it is not safe to call regular
|
* kernel mode. After this function returns it is not safe to call regular
|
||||||
* kernel code, instrumentable code, or any code which may trigger an exception.
|
* kernel code, instrumentable code, or any code which may trigger an exception.
|
||||||
*/
|
*/
|
||||||
static void noinstr arm64_exit_el1_dbg(struct pt_regs *regs)
|
static void noinstr arm64_exit_el1_dbg(struct pt_regs *regs,
|
||||||
|
irqentry_state_t state)
|
||||||
{
|
{
|
||||||
bool restore = regs->lockdep_hardirqs;
|
if (state.lockdep) {
|
||||||
|
|
||||||
if (restore) {
|
|
||||||
trace_hardirqs_on_prepare();
|
trace_hardirqs_on_prepare();
|
||||||
lockdep_hardirqs_on_prepare();
|
lockdep_hardirqs_on_prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
ct_nmi_exit();
|
ct_nmi_exit();
|
||||||
if (restore)
|
if (state.lockdep)
|
||||||
lockdep_hardirqs_on(CALLER_ADDR0);
|
lockdep_hardirqs_on(CALLER_ADDR0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PREEMPT_DYNAMIC
|
|
||||||
DEFINE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched);
|
|
||||||
#define need_irq_preemption() \
|
|
||||||
(static_branch_unlikely(&sk_dynamic_irqentry_exit_cond_resched))
|
|
||||||
#else
|
|
||||||
#define need_irq_preemption() (IS_ENABLED(CONFIG_PREEMPTION))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void __sched arm64_preempt_schedule_irq(void)
|
|
||||||
{
|
|
||||||
if (!need_irq_preemption())
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Note: thread_info::preempt_count includes both thread_info::count
|
|
||||||
* and thread_info::need_resched, and is not equivalent to
|
|
||||||
* preempt_count().
|
|
||||||
*/
|
|
||||||
if (READ_ONCE(current_thread_info()->preempt_count) != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* DAIF.DA are cleared at the start of IRQ/FIQ handling, and when GIC
|
|
||||||
* priority masking is used the GIC irqchip driver will clear DAIF.IF
|
|
||||||
* using gic_arch_enable_irqs() for normal IRQs. If anything is set in
|
|
||||||
* DAIF we must have handled an NMI, so skip preemption.
|
|
||||||
*/
|
|
||||||
if (system_uses_irq_prio_masking() && read_sysreg(daif))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Preempting a task from an IRQ means we leave copies of PSTATE
|
|
||||||
* on the stack. cpufeature's enable calls may modify PSTATE, but
|
|
||||||
* resuming one of these preempted tasks would undo those changes.
|
|
||||||
*
|
|
||||||
* Only allow a task to be preempted once cpufeatures have been
|
|
||||||
* enabled.
|
|
||||||
*/
|
|
||||||
if (system_capabilities_finalized())
|
|
||||||
preempt_schedule_irq();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void do_interrupt_handler(struct pt_regs *regs,
|
static void do_interrupt_handler(struct pt_regs *regs,
|
||||||
void (*handler)(struct pt_regs *))
|
void (*handler)(struct pt_regs *))
|
||||||
{
|
{
|
||||||
|
|
@ -324,7 +167,7 @@ extern void (*handle_arch_fiq)(struct pt_regs *);
|
||||||
static void noinstr __panic_unhandled(struct pt_regs *regs, const char *vector,
|
static void noinstr __panic_unhandled(struct pt_regs *regs, const char *vector,
|
||||||
unsigned long esr)
|
unsigned long esr)
|
||||||
{
|
{
|
||||||
arm64_enter_nmi(regs);
|
irqentry_nmi_enter(regs);
|
||||||
|
|
||||||
console_verbose();
|
console_verbose();
|
||||||
|
|
||||||
|
|
@ -475,73 +318,87 @@ UNHANDLED(el1t, 64, error)
|
||||||
static void noinstr el1_abort(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_abort(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
unsigned long far = read_sysreg(far_el1);
|
unsigned long far = read_sysreg(far_el1);
|
||||||
|
irqentry_state_t state;
|
||||||
|
|
||||||
enter_from_kernel_mode(regs);
|
state = enter_from_kernel_mode(regs);
|
||||||
local_daif_inherit(regs);
|
local_daif_inherit(regs);
|
||||||
do_mem_abort(far, esr, regs);
|
do_mem_abort(far, esr, regs);
|
||||||
local_daif_mask();
|
local_daif_mask();
|
||||||
exit_to_kernel_mode(regs);
|
exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_pc(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_pc(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
unsigned long far = read_sysreg(far_el1);
|
unsigned long far = read_sysreg(far_el1);
|
||||||
|
irqentry_state_t state;
|
||||||
|
|
||||||
enter_from_kernel_mode(regs);
|
state = enter_from_kernel_mode(regs);
|
||||||
local_daif_inherit(regs);
|
local_daif_inherit(regs);
|
||||||
do_sp_pc_abort(far, esr, regs);
|
do_sp_pc_abort(far, esr, regs);
|
||||||
local_daif_mask();
|
local_daif_mask();
|
||||||
exit_to_kernel_mode(regs);
|
exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_undef(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_undef(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_kernel_mode(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = enter_from_kernel_mode(regs);
|
||||||
local_daif_inherit(regs);
|
local_daif_inherit(regs);
|
||||||
do_el1_undef(regs, esr);
|
do_el1_undef(regs, esr);
|
||||||
local_daif_mask();
|
local_daif_mask();
|
||||||
exit_to_kernel_mode(regs);
|
exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_bti(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_bti(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_kernel_mode(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = enter_from_kernel_mode(regs);
|
||||||
local_daif_inherit(regs);
|
local_daif_inherit(regs);
|
||||||
do_el1_bti(regs, esr);
|
do_el1_bti(regs, esr);
|
||||||
local_daif_mask();
|
local_daif_mask();
|
||||||
exit_to_kernel_mode(regs);
|
exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_gcs(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_gcs(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_kernel_mode(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = enter_from_kernel_mode(regs);
|
||||||
local_daif_inherit(regs);
|
local_daif_inherit(regs);
|
||||||
do_el1_gcs(regs, esr);
|
do_el1_gcs(regs, esr);
|
||||||
local_daif_mask();
|
local_daif_mask();
|
||||||
exit_to_kernel_mode(regs);
|
exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_mops(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_mops(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_kernel_mode(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = enter_from_kernel_mode(regs);
|
||||||
local_daif_inherit(regs);
|
local_daif_inherit(regs);
|
||||||
do_el1_mops(regs, esr);
|
do_el1_mops(regs, esr);
|
||||||
local_daif_mask();
|
local_daif_mask();
|
||||||
exit_to_kernel_mode(regs);
|
exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_breakpt(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_breakpt(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
arm64_enter_el1_dbg(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = arm64_enter_el1_dbg(regs);
|
||||||
debug_exception_enter(regs);
|
debug_exception_enter(regs);
|
||||||
do_breakpoint(esr, regs);
|
do_breakpoint(esr, regs);
|
||||||
debug_exception_exit(regs);
|
debug_exception_exit(regs);
|
||||||
arm64_exit_el1_dbg(regs);
|
arm64_exit_el1_dbg(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_softstp(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_softstp(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
arm64_enter_el1_dbg(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = arm64_enter_el1_dbg(regs);
|
||||||
if (!cortex_a76_erratum_1463225_debug_handler(regs)) {
|
if (!cortex_a76_erratum_1463225_debug_handler(regs)) {
|
||||||
debug_exception_enter(regs);
|
debug_exception_enter(regs);
|
||||||
/*
|
/*
|
||||||
|
|
@ -554,37 +411,42 @@ static void noinstr el1_softstp(struct pt_regs *regs, unsigned long esr)
|
||||||
do_el1_softstep(esr, regs);
|
do_el1_softstep(esr, regs);
|
||||||
debug_exception_exit(regs);
|
debug_exception_exit(regs);
|
||||||
}
|
}
|
||||||
arm64_exit_el1_dbg(regs);
|
arm64_exit_el1_dbg(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_watchpt(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_watchpt(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
/* Watchpoints are the only debug exception to write FAR_EL1 */
|
/* Watchpoints are the only debug exception to write FAR_EL1 */
|
||||||
unsigned long far = read_sysreg(far_el1);
|
unsigned long far = read_sysreg(far_el1);
|
||||||
|
irqentry_state_t state;
|
||||||
|
|
||||||
arm64_enter_el1_dbg(regs);
|
state = arm64_enter_el1_dbg(regs);
|
||||||
debug_exception_enter(regs);
|
debug_exception_enter(regs);
|
||||||
do_watchpoint(far, esr, regs);
|
do_watchpoint(far, esr, regs);
|
||||||
debug_exception_exit(regs);
|
debug_exception_exit(regs);
|
||||||
arm64_exit_el1_dbg(regs);
|
arm64_exit_el1_dbg(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_brk64(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_brk64(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
arm64_enter_el1_dbg(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = arm64_enter_el1_dbg(regs);
|
||||||
debug_exception_enter(regs);
|
debug_exception_enter(regs);
|
||||||
do_el1_brk64(esr, regs);
|
do_el1_brk64(esr, regs);
|
||||||
debug_exception_exit(regs);
|
debug_exception_exit(regs);
|
||||||
arm64_exit_el1_dbg(regs);
|
arm64_exit_el1_dbg(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el1_fpac(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el1_fpac(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_kernel_mode(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = enter_from_kernel_mode(regs);
|
||||||
local_daif_inherit(regs);
|
local_daif_inherit(regs);
|
||||||
do_el1_fpac(regs, esr);
|
do_el1_fpac(regs, esr);
|
||||||
local_daif_mask();
|
local_daif_mask();
|
||||||
exit_to_kernel_mode(regs);
|
exit_to_kernel_mode(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
|
asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
|
||||||
|
|
@ -639,30 +501,32 @@ asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
|
||||||
static __always_inline void __el1_pnmi(struct pt_regs *regs,
|
static __always_inline void __el1_pnmi(struct pt_regs *regs,
|
||||||
void (*handler)(struct pt_regs *))
|
void (*handler)(struct pt_regs *))
|
||||||
{
|
{
|
||||||
arm64_enter_nmi(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = irqentry_nmi_enter(regs);
|
||||||
do_interrupt_handler(regs, handler);
|
do_interrupt_handler(regs, handler);
|
||||||
arm64_exit_nmi(regs);
|
irqentry_nmi_exit(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static __always_inline void __el1_irq(struct pt_regs *regs,
|
static __always_inline void __el1_irq(struct pt_regs *regs,
|
||||||
void (*handler)(struct pt_regs *))
|
void (*handler)(struct pt_regs *))
|
||||||
{
|
{
|
||||||
enter_from_kernel_mode(regs);
|
irqentry_state_t state;
|
||||||
|
|
||||||
|
state = enter_from_kernel_mode(regs);
|
||||||
|
|
||||||
irq_enter_rcu();
|
irq_enter_rcu();
|
||||||
do_interrupt_handler(regs, handler);
|
do_interrupt_handler(regs, handler);
|
||||||
irq_exit_rcu();
|
irq_exit_rcu();
|
||||||
|
|
||||||
arm64_preempt_schedule_irq();
|
exit_to_kernel_mode(regs, state);
|
||||||
|
|
||||||
exit_to_kernel_mode(regs);
|
|
||||||
}
|
}
|
||||||
static void noinstr el1_interrupt(struct pt_regs *regs,
|
static void noinstr el1_interrupt(struct pt_regs *regs,
|
||||||
void (*handler)(struct pt_regs *))
|
void (*handler)(struct pt_regs *))
|
||||||
{
|
{
|
||||||
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
|
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && !interrupts_enabled(regs))
|
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && regs_irqs_disabled(regs))
|
||||||
__el1_pnmi(regs, handler);
|
__el1_pnmi(regs, handler);
|
||||||
else
|
else
|
||||||
__el1_irq(regs, handler);
|
__el1_irq(regs, handler);
|
||||||
|
|
@ -681,21 +545,22 @@ asmlinkage void noinstr el1h_64_fiq_handler(struct pt_regs *regs)
|
||||||
asmlinkage void noinstr el1h_64_error_handler(struct pt_regs *regs)
|
asmlinkage void noinstr el1h_64_error_handler(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
unsigned long esr = read_sysreg(esr_el1);
|
unsigned long esr = read_sysreg(esr_el1);
|
||||||
|
irqentry_state_t state;
|
||||||
|
|
||||||
local_daif_restore(DAIF_ERRCTX);
|
local_daif_restore(DAIF_ERRCTX);
|
||||||
arm64_enter_nmi(regs);
|
state = irqentry_nmi_enter(regs);
|
||||||
do_serror(regs, esr);
|
do_serror(regs, esr);
|
||||||
arm64_exit_nmi(regs);
|
irqentry_nmi_exit(regs, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_da(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_da(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
unsigned long far = read_sysreg(far_el1);
|
unsigned long far = read_sysreg(far_el1);
|
||||||
|
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_mem_abort(far, esr, regs);
|
do_mem_abort(far, esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_ia(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_ia(struct pt_regs *regs, unsigned long esr)
|
||||||
|
|
@ -710,50 +575,50 @@ static void noinstr el0_ia(struct pt_regs *regs, unsigned long esr)
|
||||||
if (!is_ttbr0_addr(far))
|
if (!is_ttbr0_addr(far))
|
||||||
arm64_apply_bp_hardening();
|
arm64_apply_bp_hardening();
|
||||||
|
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_mem_abort(far, esr, regs);
|
do_mem_abort(far, esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_fpsimd_acc(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_fpsimd_acc(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_fpsimd_acc(esr, regs);
|
do_fpsimd_acc(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_sve_acc(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_sve_acc(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_sve_acc(esr, regs);
|
do_sve_acc(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_sme_acc(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_sme_acc(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_sme_acc(esr, regs);
|
do_sme_acc(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_fpsimd_exc(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_fpsimd_exc(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_fpsimd_exc(esr, regs);
|
do_fpsimd_exc(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_sys(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_sys(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_sys(esr, regs);
|
do_el0_sys(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_pc(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_pc(struct pt_regs *regs, unsigned long esr)
|
||||||
|
|
@ -763,58 +628,58 @@ static void noinstr el0_pc(struct pt_regs *regs, unsigned long esr)
|
||||||
if (!is_ttbr0_addr(instruction_pointer(regs)))
|
if (!is_ttbr0_addr(instruction_pointer(regs)))
|
||||||
arm64_apply_bp_hardening();
|
arm64_apply_bp_hardening();
|
||||||
|
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_sp_pc_abort(far, esr, regs);
|
do_sp_pc_abort(far, esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_sp(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_sp(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_sp_pc_abort(regs->sp, esr, regs);
|
do_sp_pc_abort(regs->sp, esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_undef(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_undef(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_undef(regs, esr);
|
do_el0_undef(regs, esr);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_bti(struct pt_regs *regs)
|
static void noinstr el0_bti(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_bti(regs);
|
do_el0_bti(regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_mops(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_mops(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_mops(regs, esr);
|
do_el0_mops(regs, esr);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_gcs(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_gcs(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_gcs(regs, esr);
|
do_el0_gcs(regs, esr);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_inv(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_inv(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
bad_el0_sync(regs, 0, esr);
|
bad_el0_sync(regs, 0, esr);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_breakpt(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_breakpt(struct pt_regs *regs, unsigned long esr)
|
||||||
|
|
@ -822,12 +687,12 @@ static void noinstr el0_breakpt(struct pt_regs *regs, unsigned long esr)
|
||||||
if (!is_ttbr0_addr(regs->pc))
|
if (!is_ttbr0_addr(regs->pc))
|
||||||
arm64_apply_bp_hardening();
|
arm64_apply_bp_hardening();
|
||||||
|
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
debug_exception_enter(regs);
|
debug_exception_enter(regs);
|
||||||
do_breakpoint(esr, regs);
|
do_breakpoint(esr, regs);
|
||||||
debug_exception_exit(regs);
|
debug_exception_exit(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
|
||||||
|
|
@ -835,7 +700,7 @@ static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
|
||||||
if (!is_ttbr0_addr(regs->pc))
|
if (!is_ttbr0_addr(regs->pc))
|
||||||
arm64_apply_bp_hardening();
|
arm64_apply_bp_hardening();
|
||||||
|
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
/*
|
/*
|
||||||
* After handling a breakpoint, we suspend the breakpoint
|
* After handling a breakpoint, we suspend the breakpoint
|
||||||
* and use single-step to move to the next instruction.
|
* and use single-step to move to the next instruction.
|
||||||
|
|
@ -846,7 +711,7 @@ static void noinstr el0_softstp(struct pt_regs *regs, unsigned long esr)
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_softstep(esr, regs);
|
do_el0_softstep(esr, regs);
|
||||||
}
|
}
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
|
||||||
|
|
@ -854,39 +719,39 @@ static void noinstr el0_watchpt(struct pt_regs *regs, unsigned long esr)
|
||||||
/* Watchpoints are the only debug exception to write FAR_EL1 */
|
/* Watchpoints are the only debug exception to write FAR_EL1 */
|
||||||
unsigned long far = read_sysreg(far_el1);
|
unsigned long far = read_sysreg(far_el1);
|
||||||
|
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
debug_exception_enter(regs);
|
debug_exception_enter(regs);
|
||||||
do_watchpoint(far, esr, regs);
|
do_watchpoint(far, esr, regs);
|
||||||
debug_exception_exit(regs);
|
debug_exception_exit(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_brk64(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_brk64(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_brk64(esr, regs);
|
do_el0_brk64(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_svc(struct pt_regs *regs)
|
static void noinstr el0_svc(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
cortex_a76_erratum_1463225_svc_handler();
|
cortex_a76_erratum_1463225_svc_handler();
|
||||||
fpsimd_syscall_enter();
|
fpsimd_syscall_enter();
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_svc(regs);
|
do_el0_svc(regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
fpsimd_syscall_exit();
|
fpsimd_syscall_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_fpac(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_fpac(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_fpac(regs, esr);
|
do_el0_fpac(regs, esr);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
|
asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
|
||||||
|
|
@ -960,7 +825,7 @@ asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
|
||||||
static void noinstr el0_interrupt(struct pt_regs *regs,
|
static void noinstr el0_interrupt(struct pt_regs *regs,
|
||||||
void (*handler)(struct pt_regs *))
|
void (*handler)(struct pt_regs *))
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
|
|
||||||
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
|
write_sysreg(DAIF_PROCCTX_NOIRQ, daif);
|
||||||
|
|
||||||
|
|
@ -971,7 +836,7 @@ static void noinstr el0_interrupt(struct pt_regs *regs,
|
||||||
do_interrupt_handler(regs, handler);
|
do_interrupt_handler(regs, handler);
|
||||||
irq_exit_rcu();
|
irq_exit_rcu();
|
||||||
|
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
|
static void noinstr __el0_irq_handler_common(struct pt_regs *regs)
|
||||||
|
|
@ -997,14 +862,15 @@ asmlinkage void noinstr el0t_64_fiq_handler(struct pt_regs *regs)
|
||||||
static void noinstr __el0_error_handler_common(struct pt_regs *regs)
|
static void noinstr __el0_error_handler_common(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
unsigned long esr = read_sysreg(esr_el1);
|
unsigned long esr = read_sysreg(esr_el1);
|
||||||
|
irqentry_state_t state;
|
||||||
|
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_ERRCTX);
|
local_daif_restore(DAIF_ERRCTX);
|
||||||
arm64_enter_nmi(regs);
|
state = irqentry_nmi_enter(regs);
|
||||||
do_serror(regs, esr);
|
do_serror(regs, esr);
|
||||||
arm64_exit_nmi(regs);
|
irqentry_nmi_exit(regs, state);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage void noinstr el0t_64_error_handler(struct pt_regs *regs)
|
asmlinkage void noinstr el0t_64_error_handler(struct pt_regs *regs)
|
||||||
|
|
@ -1015,27 +881,27 @@ asmlinkage void noinstr el0t_64_error_handler(struct pt_regs *regs)
|
||||||
#ifdef CONFIG_COMPAT
|
#ifdef CONFIG_COMPAT
|
||||||
static void noinstr el0_cp15(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_cp15(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_cp15(esr, regs);
|
do_el0_cp15(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_svc_compat(struct pt_regs *regs)
|
static void noinstr el0_svc_compat(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
cortex_a76_erratum_1463225_svc_handler();
|
cortex_a76_erratum_1463225_svc_handler();
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_el0_svc_compat(regs);
|
do_el0_svc_compat(regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinstr el0_bkpt32(struct pt_regs *regs, unsigned long esr)
|
static void noinstr el0_bkpt32(struct pt_regs *regs, unsigned long esr)
|
||||||
{
|
{
|
||||||
enter_from_user_mode(regs);
|
arm64_enter_from_user_mode(regs);
|
||||||
local_daif_restore(DAIF_PROCCTX);
|
local_daif_restore(DAIF_PROCCTX);
|
||||||
do_bkpt32(esr, regs);
|
do_bkpt32(esr, regs);
|
||||||
exit_to_user_mode(regs);
|
arm64_exit_to_user_mode(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
|
asmlinkage void noinstr el0t_32_sync_handler(struct pt_regs *regs)
|
||||||
|
|
@ -1114,7 +980,7 @@ asmlinkage void noinstr __noreturn handle_bad_stack(struct pt_regs *regs)
|
||||||
unsigned long esr = read_sysreg(esr_el1);
|
unsigned long esr = read_sysreg(esr_el1);
|
||||||
unsigned long far = read_sysreg(far_el1);
|
unsigned long far = read_sysreg(far_el1);
|
||||||
|
|
||||||
arm64_enter_nmi(regs);
|
irqentry_nmi_enter(regs);
|
||||||
panic_bad_stack(regs, esr, far);
|
panic_bad_stack(regs, esr, far);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1122,6 +988,7 @@ asmlinkage void noinstr __noreturn handle_bad_stack(struct pt_regs *regs)
|
||||||
asmlinkage noinstr unsigned long
|
asmlinkage noinstr unsigned long
|
||||||
__sdei_handler(struct pt_regs *regs, struct sdei_registered_event *arg)
|
__sdei_handler(struct pt_regs *regs, struct sdei_registered_event *arg)
|
||||||
{
|
{
|
||||||
|
irqentry_state_t state;
|
||||||
unsigned long ret;
|
unsigned long ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1146,9 +1013,9 @@ __sdei_handler(struct pt_regs *regs, struct sdei_registered_event *arg)
|
||||||
else if (cpu_has_pan())
|
else if (cpu_has_pan())
|
||||||
set_pstate_pan(0);
|
set_pstate_pan(0);
|
||||||
|
|
||||||
arm64_enter_nmi(regs);
|
state = irqentry_nmi_enter(regs);
|
||||||
ret = do_sdei_event(regs, arg);
|
ret = do_sdei_event(regs, arg);
|
||||||
arm64_exit_nmi(regs);
|
irqentry_nmi_exit(regs, state);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1265,6 +1265,8 @@ void __init sme_setup(void)
|
||||||
if (!system_supports_sme())
|
if (!system_supports_sme())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
min_bit = find_last_bit(info->vq_map, SVE_VQ_MAX);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SME doesn't require any particular vector length be
|
* SME doesn't require any particular vector length be
|
||||||
* supported but it does require at least one. We should have
|
* supported but it does require at least one. We should have
|
||||||
|
|
@ -1272,9 +1274,8 @@ void __init sme_setup(void)
|
||||||
* let's double check here. The bitmap is SVE_VQ_MAP sized for
|
* let's double check here. The bitmap is SVE_VQ_MAP sized for
|
||||||
* sharing with SVE.
|
* sharing with SVE.
|
||||||
*/
|
*/
|
||||||
WARN_ON(bitmap_empty(info->vq_map, SVE_VQ_MAX));
|
WARN_ON(min_bit >= SVE_VQ_MAX);
|
||||||
|
|
||||||
min_bit = find_last_bit(info->vq_map, SVE_VQ_MAX);
|
|
||||||
info->min_vl = sve_vl_from_vq(__bit_to_vq(min_bit));
|
info->min_vl = sve_vl_from_vq(__bit_to_vq(min_bit));
|
||||||
|
|
||||||
max_bit = find_first_bit(info->vq_map, SVE_VQ_MAX);
|
max_bit = find_first_bit(info->vq_map, SVE_VQ_MAX);
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
extern const u8 __eh_frame_start[], __eh_frame_end[];
|
extern const u8 __eh_frame_start[], __eh_frame_end[];
|
||||||
|
|
||||||
extern void idmap_cpu_replace_ttbr1(void *pgdir);
|
extern void idmap_cpu_replace_ttbr1(phys_addr_t pgdir);
|
||||||
|
|
||||||
static void __init map_segment(pgd_t *pg_dir, u64 *pgd, u64 va_offset,
|
static void __init map_segment(pgd_t *pg_dir, phys_addr_t *pgd, u64 va_offset,
|
||||||
void *start, void *end, pgprot_t prot,
|
void *start, void *end, pgprot_t prot,
|
||||||
bool may_use_cont, int root_level)
|
bool may_use_cont, int root_level)
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +40,7 @@ static void __init map_kernel(u64 kaslr_offset, u64 va_offset, int root_level)
|
||||||
{
|
{
|
||||||
bool enable_scs = IS_ENABLED(CONFIG_UNWIND_PATCH_PAC_INTO_SCS);
|
bool enable_scs = IS_ENABLED(CONFIG_UNWIND_PATCH_PAC_INTO_SCS);
|
||||||
bool twopass = IS_ENABLED(CONFIG_RELOCATABLE);
|
bool twopass = IS_ENABLED(CONFIG_RELOCATABLE);
|
||||||
u64 pgdp = (u64)init_pg_dir + PAGE_SIZE;
|
phys_addr_t pgdp = (phys_addr_t)init_pg_dir + PAGE_SIZE;
|
||||||
pgprot_t text_prot = PAGE_KERNEL_ROX;
|
pgprot_t text_prot = PAGE_KERNEL_ROX;
|
||||||
pgprot_t data_prot = PAGE_KERNEL;
|
pgprot_t data_prot = PAGE_KERNEL;
|
||||||
pgprot_t prot;
|
pgprot_t prot;
|
||||||
|
|
@ -78,6 +78,12 @@ static void __init map_kernel(u64 kaslr_offset, u64 va_offset, int root_level)
|
||||||
twopass |= enable_scs;
|
twopass |= enable_scs;
|
||||||
prot = twopass ? data_prot : text_prot;
|
prot = twopass ? data_prot : text_prot;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* [_stext, _text) isn't executed after boot and contains some
|
||||||
|
* non-executable, unpredictable data, so map it non-executable.
|
||||||
|
*/
|
||||||
|
map_segment(init_pg_dir, &pgdp, va_offset, _text, _stext, data_prot,
|
||||||
|
false, root_level);
|
||||||
map_segment(init_pg_dir, &pgdp, va_offset, _stext, _etext, prot,
|
map_segment(init_pg_dir, &pgdp, va_offset, _stext, _etext, prot,
|
||||||
!twopass, root_level);
|
!twopass, root_level);
|
||||||
map_segment(init_pg_dir, &pgdp, va_offset, __start_rodata,
|
map_segment(init_pg_dir, &pgdp, va_offset, __start_rodata,
|
||||||
|
|
@ -90,7 +96,7 @@ static void __init map_kernel(u64 kaslr_offset, u64 va_offset, int root_level)
|
||||||
true, root_level);
|
true, root_level);
|
||||||
dsb(ishst);
|
dsb(ishst);
|
||||||
|
|
||||||
idmap_cpu_replace_ttbr1(init_pg_dir);
|
idmap_cpu_replace_ttbr1((phys_addr_t)init_pg_dir);
|
||||||
|
|
||||||
if (twopass) {
|
if (twopass) {
|
||||||
if (IS_ENABLED(CONFIG_RELOCATABLE))
|
if (IS_ENABLED(CONFIG_RELOCATABLE))
|
||||||
|
|
@ -129,10 +135,10 @@ static void __init map_kernel(u64 kaslr_offset, u64 va_offset, int root_level)
|
||||||
/* Copy the root page table to its final location */
|
/* Copy the root page table to its final location */
|
||||||
memcpy((void *)swapper_pg_dir + va_offset, init_pg_dir, PAGE_SIZE);
|
memcpy((void *)swapper_pg_dir + va_offset, init_pg_dir, PAGE_SIZE);
|
||||||
dsb(ishst);
|
dsb(ishst);
|
||||||
idmap_cpu_replace_ttbr1(swapper_pg_dir);
|
idmap_cpu_replace_ttbr1((phys_addr_t)swapper_pg_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void noinline __section(".idmap.text") set_ttbr0_for_lpa2(u64 ttbr)
|
static void noinline __section(".idmap.text") set_ttbr0_for_lpa2(phys_addr_t ttbr)
|
||||||
{
|
{
|
||||||
u64 sctlr = read_sysreg(sctlr_el1);
|
u64 sctlr = read_sysreg(sctlr_el1);
|
||||||
u64 tcr = read_sysreg(tcr_el1) | TCR_DS;
|
u64 tcr = read_sysreg(tcr_el1) | TCR_DS;
|
||||||
|
|
@ -172,30 +178,30 @@ static void __init remap_idmap_for_lpa2(void)
|
||||||
*/
|
*/
|
||||||
create_init_idmap(init_pg_dir, mask);
|
create_init_idmap(init_pg_dir, mask);
|
||||||
dsb(ishst);
|
dsb(ishst);
|
||||||
set_ttbr0_for_lpa2((u64)init_pg_dir);
|
set_ttbr0_for_lpa2((phys_addr_t)init_pg_dir);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Recreate the initial ID map with the same granularity as before.
|
* Recreate the initial ID map with the same granularity as before.
|
||||||
* Don't bother with the FDT, we no longer need it after this.
|
* Don't bother with the FDT, we no longer need it after this.
|
||||||
*/
|
*/
|
||||||
memset(init_idmap_pg_dir, 0,
|
memset(init_idmap_pg_dir, 0,
|
||||||
(u64)init_idmap_pg_end - (u64)init_idmap_pg_dir);
|
(char *)init_idmap_pg_end - (char *)init_idmap_pg_dir);
|
||||||
|
|
||||||
create_init_idmap(init_idmap_pg_dir, mask);
|
create_init_idmap(init_idmap_pg_dir, mask);
|
||||||
dsb(ishst);
|
dsb(ishst);
|
||||||
|
|
||||||
/* switch back to the updated initial ID map */
|
/* switch back to the updated initial ID map */
|
||||||
set_ttbr0_for_lpa2((u64)init_idmap_pg_dir);
|
set_ttbr0_for_lpa2((phys_addr_t)init_idmap_pg_dir);
|
||||||
|
|
||||||
/* wipe the temporary ID map from memory */
|
/* wipe the temporary ID map from memory */
|
||||||
memset(init_pg_dir, 0, (u64)init_pg_end - (u64)init_pg_dir);
|
memset(init_pg_dir, 0, (char *)init_pg_end - (char *)init_pg_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __init map_fdt(u64 fdt)
|
static void *__init map_fdt(phys_addr_t fdt)
|
||||||
{
|
{
|
||||||
static u8 ptes[INIT_IDMAP_FDT_SIZE] __initdata __aligned(PAGE_SIZE);
|
static u8 ptes[INIT_IDMAP_FDT_SIZE] __initdata __aligned(PAGE_SIZE);
|
||||||
u64 efdt = fdt + MAX_FDT_SIZE;
|
phys_addr_t efdt = fdt + MAX_FDT_SIZE;
|
||||||
u64 ptep = (u64)ptes;
|
phys_addr_t ptep = (phys_addr_t)ptes; /* We're idmapped when called */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Map up to MAX_FDT_SIZE bytes, but avoid overlap with
|
* Map up to MAX_FDT_SIZE bytes, but avoid overlap with
|
||||||
|
|
@ -205,6 +211,8 @@ static void __init map_fdt(u64 fdt)
|
||||||
fdt, PAGE_KERNEL, IDMAP_ROOT_LEVEL,
|
fdt, PAGE_KERNEL, IDMAP_ROOT_LEVEL,
|
||||||
(pte_t *)init_idmap_pg_dir, false, 0);
|
(pte_t *)init_idmap_pg_dir, false, 0);
|
||||||
dsb(ishst);
|
dsb(ishst);
|
||||||
|
|
||||||
|
return (void *)fdt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -230,7 +238,7 @@ static bool __init ng_mappings_allowed(void)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage void __init early_map_kernel(u64 boot_status, void *fdt)
|
asmlinkage void __init early_map_kernel(u64 boot_status, phys_addr_t fdt)
|
||||||
{
|
{
|
||||||
static char const chosen_str[] __initconst = "/chosen";
|
static char const chosen_str[] __initconst = "/chosen";
|
||||||
u64 va_base, pa_base = (u64)&_text;
|
u64 va_base, pa_base = (u64)&_text;
|
||||||
|
|
@ -238,15 +246,14 @@ asmlinkage void __init early_map_kernel(u64 boot_status, void *fdt)
|
||||||
int root_level = 4 - CONFIG_PGTABLE_LEVELS;
|
int root_level = 4 - CONFIG_PGTABLE_LEVELS;
|
||||||
int va_bits = VA_BITS;
|
int va_bits = VA_BITS;
|
||||||
int chosen;
|
int chosen;
|
||||||
|
void *fdt_mapped = map_fdt(fdt);
|
||||||
map_fdt((u64)fdt);
|
|
||||||
|
|
||||||
/* Clear BSS and the initial page tables */
|
/* Clear BSS and the initial page tables */
|
||||||
memset(__bss_start, 0, (u64)init_pg_end - (u64)__bss_start);
|
memset(__bss_start, 0, (char *)init_pg_end - (char *)__bss_start);
|
||||||
|
|
||||||
/* Parse the command line for CPU feature overrides */
|
/* Parse the command line for CPU feature overrides */
|
||||||
chosen = fdt_path_offset(fdt, chosen_str);
|
chosen = fdt_path_offset(fdt_mapped, chosen_str);
|
||||||
init_feature_override(boot_status, fdt, chosen);
|
init_feature_override(boot_status, fdt_mapped, chosen);
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_ARM64_64K_PAGES) && !cpu_has_lva()) {
|
if (IS_ENABLED(CONFIG_ARM64_64K_PAGES) && !cpu_has_lva()) {
|
||||||
va_bits = VA_BITS_MIN;
|
va_bits = VA_BITS_MIN;
|
||||||
|
|
@ -266,7 +273,7 @@ asmlinkage void __init early_map_kernel(u64 boot_status, void *fdt)
|
||||||
* fill in the high bits from the seed.
|
* fill in the high bits from the seed.
|
||||||
*/
|
*/
|
||||||
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
|
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
|
||||||
u64 kaslr_seed = kaslr_early_init(fdt, chosen);
|
u64 kaslr_seed = kaslr_early_init(fdt_mapped, chosen);
|
||||||
|
|
||||||
if (kaslr_seed && kaslr_requires_kpti())
|
if (kaslr_seed && kaslr_requires_kpti())
|
||||||
arm64_use_ng_mappings = ng_mappings_allowed();
|
arm64_use_ng_mappings = ng_mappings_allowed();
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@
|
||||||
* @va_offset: Offset between a physical page and its current mapping
|
* @va_offset: Offset between a physical page and its current mapping
|
||||||
* in the VA space
|
* in the VA space
|
||||||
*/
|
*/
|
||||||
void __init map_range(u64 *pte, u64 start, u64 end, u64 pa, pgprot_t prot,
|
void __init map_range(phys_addr_t *pte, u64 start, u64 end, phys_addr_t pa,
|
||||||
int level, pte_t *tbl, bool may_use_cont, u64 va_offset)
|
pgprot_t prot, int level, pte_t *tbl, bool may_use_cont,
|
||||||
|
u64 va_offset)
|
||||||
{
|
{
|
||||||
u64 cmask = (level == 3) ? CONT_PTE_SIZE - 1 : U64_MAX;
|
u64 cmask = (level == 3) ? CONT_PTE_SIZE - 1 : U64_MAX;
|
||||||
ptdesc_t protval = pgprot_val(prot) & ~PTE_TYPE_MASK;
|
ptdesc_t protval = pgprot_val(prot) & ~PTE_TYPE_MASK;
|
||||||
|
|
@ -87,19 +88,22 @@ void __init map_range(u64 *pte, u64 start, u64 end, u64 pa, pgprot_t prot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmlinkage u64 __init create_init_idmap(pgd_t *pg_dir, ptdesc_t clrmask)
|
asmlinkage phys_addr_t __init create_init_idmap(pgd_t *pg_dir, ptdesc_t clrmask)
|
||||||
{
|
{
|
||||||
u64 ptep = (u64)pg_dir + PAGE_SIZE;
|
phys_addr_t ptep = (phys_addr_t)pg_dir + PAGE_SIZE; /* MMU is off */
|
||||||
pgprot_t text_prot = PAGE_KERNEL_ROX;
|
pgprot_t text_prot = PAGE_KERNEL_ROX;
|
||||||
pgprot_t data_prot = PAGE_KERNEL;
|
pgprot_t data_prot = PAGE_KERNEL;
|
||||||
|
|
||||||
pgprot_val(text_prot) &= ~clrmask;
|
pgprot_val(text_prot) &= ~clrmask;
|
||||||
pgprot_val(data_prot) &= ~clrmask;
|
pgprot_val(data_prot) &= ~clrmask;
|
||||||
|
|
||||||
map_range(&ptep, (u64)_stext, (u64)__initdata_begin, (u64)_stext,
|
/* MMU is off; pointer casts to phys_addr_t are safe */
|
||||||
text_prot, IDMAP_ROOT_LEVEL, (pte_t *)pg_dir, false, 0);
|
map_range(&ptep, (u64)_stext, (u64)__initdata_begin,
|
||||||
map_range(&ptep, (u64)__initdata_begin, (u64)_end, (u64)__initdata_begin,
|
(phys_addr_t)_stext, text_prot, IDMAP_ROOT_LEVEL,
|
||||||
data_prot, IDMAP_ROOT_LEVEL, (pte_t *)pg_dir, false, 0);
|
(pte_t *)pg_dir, false, 0);
|
||||||
|
map_range(&ptep, (u64)__initdata_begin, (u64)_end,
|
||||||
|
(phys_addr_t)__initdata_begin, data_prot, IDMAP_ROOT_LEVEL,
|
||||||
|
(pte_t *)pg_dir, false, 0);
|
||||||
|
|
||||||
return ptep;
|
return ptep;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,10 @@ u64 kaslr_early_init(void *fdt, int chosen);
|
||||||
void relocate_kernel(u64 offset);
|
void relocate_kernel(u64 offset);
|
||||||
int scs_patch(const u8 eh_frame[], int size);
|
int scs_patch(const u8 eh_frame[], int size);
|
||||||
|
|
||||||
void map_range(u64 *pgd, u64 start, u64 end, u64 pa, pgprot_t prot,
|
void map_range(phys_addr_t *pte, u64 start, u64 end, phys_addr_t pa,
|
||||||
int level, pte_t *tbl, bool may_use_cont, u64 va_offset);
|
pgprot_t prot, int level, pte_t *tbl, bool may_use_cont,
|
||||||
|
u64 va_offset);
|
||||||
|
|
||||||
asmlinkage void early_map_kernel(u64 boot_status, void *fdt);
|
asmlinkage void early_map_kernel(u64 boot_status, phys_addr_t fdt);
|
||||||
|
|
||||||
asmlinkage u64 create_init_idmap(pgd_t *pgd, ptdesc_t clrmask);
|
asmlinkage phys_addr_t create_init_idmap(pgd_t *pgd, ptdesc_t clrmask);
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,10 @@ arm_probe_decode_insn(u32 insn, struct arch_probe_insn *api)
|
||||||
aarch64_insn_is_bl(insn)) {
|
aarch64_insn_is_bl(insn)) {
|
||||||
api->handler = simulate_b_bl;
|
api->handler = simulate_b_bl;
|
||||||
} else if (aarch64_insn_is_br(insn) ||
|
} else if (aarch64_insn_is_br(insn) ||
|
||||||
aarch64_insn_is_blr(insn) ||
|
aarch64_insn_is_blr(insn)) {
|
||||||
aarch64_insn_is_ret(insn)) {
|
api->handler = simulate_br_blr;
|
||||||
api->handler = simulate_br_blr_ret;
|
} else if (aarch64_insn_is_ret(insn)) {
|
||||||
|
api->handler = simulate_ret;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* Instruction cannot be stepped out-of-line and we don't
|
* Instruction cannot be stepped out-of-line and we don't
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <asm/traps.h>
|
#include <asm/traps.h>
|
||||||
|
|
||||||
#include "simulate-insn.h"
|
#include "simulate-insn.h"
|
||||||
|
#include "asm/gcs.h"
|
||||||
|
|
||||||
#define bbl_displacement(insn) \
|
#define bbl_displacement(insn) \
|
||||||
sign_extend32(((insn) & 0x3ffffff) << 2, 27)
|
sign_extend32(((insn) & 0x3ffffff) << 2, 27)
|
||||||
|
|
@ -49,6 +50,21 @@ static inline u32 get_w_reg(struct pt_regs *regs, int reg)
|
||||||
return lower_32_bits(pt_regs_read_reg(regs, reg));
|
return lower_32_bits(pt_regs_read_reg(regs, reg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int update_lr(struct pt_regs *regs, long addr)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (user_mode(regs) && task_gcs_el0_enabled(current)) {
|
||||||
|
push_user_gcs(addr, &err);
|
||||||
|
if (err) {
|
||||||
|
force_sig(SIGSEGV);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
procedure_link_pointer_set(regs, addr);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
static bool __kprobes check_cbz(u32 opcode, struct pt_regs *regs)
|
static bool __kprobes check_cbz(u32 opcode, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
int xn = opcode & 0x1f;
|
int xn = opcode & 0x1f;
|
||||||
|
|
@ -107,9 +123,9 @@ simulate_b_bl(u32 opcode, long addr, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
int disp = bbl_displacement(opcode);
|
int disp = bbl_displacement(opcode);
|
||||||
|
|
||||||
/* Link register is x30 */
|
|
||||||
if (opcode & (1 << 31))
|
if (opcode & (1 << 31))
|
||||||
set_x_reg(regs, 30, addr + 4);
|
if (update_lr(regs, addr + 4))
|
||||||
|
return;
|
||||||
|
|
||||||
instruction_pointer_set(regs, addr + disp);
|
instruction_pointer_set(regs, addr + disp);
|
||||||
}
|
}
|
||||||
|
|
@ -126,16 +142,34 @@ simulate_b_cond(u32 opcode, long addr, struct pt_regs *regs)
|
||||||
}
|
}
|
||||||
|
|
||||||
void __kprobes
|
void __kprobes
|
||||||
simulate_br_blr_ret(u32 opcode, long addr, struct pt_regs *regs)
|
simulate_br_blr(u32 opcode, long addr, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
int xn = (opcode >> 5) & 0x1f;
|
int xn = (opcode >> 5) & 0x1f;
|
||||||
|
u64 b_target = get_x_reg(regs, xn);
|
||||||
|
|
||||||
/* update pc first in case we're doing a "blr lr" */
|
|
||||||
instruction_pointer_set(regs, get_x_reg(regs, xn));
|
|
||||||
|
|
||||||
/* Link register is x30 */
|
|
||||||
if (((opcode >> 21) & 0x3) == 1)
|
if (((opcode >> 21) & 0x3) == 1)
|
||||||
set_x_reg(regs, 30, addr + 4);
|
if (update_lr(regs, addr + 4))
|
||||||
|
return;
|
||||||
|
|
||||||
|
instruction_pointer_set(regs, b_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __kprobes
|
||||||
|
simulate_ret(u32 opcode, long addr, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
u64 ret_addr;
|
||||||
|
int err = 0;
|
||||||
|
int xn = (opcode >> 5) & 0x1f;
|
||||||
|
u64 r_target = get_x_reg(regs, xn);
|
||||||
|
|
||||||
|
if (user_mode(regs) && task_gcs_el0_enabled(current)) {
|
||||||
|
ret_addr = pop_user_gcs(&err);
|
||||||
|
if (err || ret_addr != r_target) {
|
||||||
|
force_sig(SIGSEGV);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instruction_pointer_set(regs, r_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __kprobes
|
void __kprobes
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
void simulate_adr_adrp(u32 opcode, long addr, struct pt_regs *regs);
|
void simulate_adr_adrp(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
void simulate_b_bl(u32 opcode, long addr, struct pt_regs *regs);
|
void simulate_b_bl(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
void simulate_b_cond(u32 opcode, long addr, struct pt_regs *regs);
|
void simulate_b_cond(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
void simulate_br_blr_ret(u32 opcode, long addr, struct pt_regs *regs);
|
void simulate_br_blr(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
|
void simulate_ret(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
void simulate_cbz_cbnz(u32 opcode, long addr, struct pt_regs *regs);
|
void simulate_cbz_cbnz(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
void simulate_tbz_tbnz(u32 opcode, long addr, struct pt_regs *regs);
|
void simulate_tbz_tbnz(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
void simulate_ldr_literal(u32 opcode, long addr, struct pt_regs *regs);
|
void simulate_ldr_literal(u32 opcode, long addr, struct pt_regs *regs);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <linux/ptrace.h>
|
#include <linux/ptrace.h>
|
||||||
#include <linux/uprobes.h>
|
#include <linux/uprobes.h>
|
||||||
#include <asm/cacheflush.h>
|
#include <asm/cacheflush.h>
|
||||||
|
#include <asm/gcs.h>
|
||||||
|
|
||||||
#include "decode-insn.h"
|
#include "decode-insn.h"
|
||||||
|
|
||||||
|
|
@ -159,11 +160,43 @@ arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr,
|
||||||
struct pt_regs *regs)
|
struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
unsigned long orig_ret_vaddr;
|
unsigned long orig_ret_vaddr;
|
||||||
|
unsigned long gcs_ret_vaddr;
|
||||||
|
int err = 0;
|
||||||
|
u64 gcspr;
|
||||||
|
|
||||||
orig_ret_vaddr = procedure_link_pointer(regs);
|
orig_ret_vaddr = procedure_link_pointer(regs);
|
||||||
|
|
||||||
|
if (task_gcs_el0_enabled(current)) {
|
||||||
|
gcspr = read_sysreg_s(SYS_GCSPR_EL0);
|
||||||
|
gcs_ret_vaddr = get_user_gcs((__force unsigned long __user *)gcspr, &err);
|
||||||
|
if (err) {
|
||||||
|
force_sig(SIGSEGV);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the LR and GCS return addr don't match, then some kind of PAC
|
||||||
|
* signing or control flow occurred since entering the probed function.
|
||||||
|
* Likely because the user is attempting to retprobe on an instruction
|
||||||
|
* that isn't a function boundary or inside a leaf function. Explicitly
|
||||||
|
* abort this retprobe because it will generate a GCS exception.
|
||||||
|
*/
|
||||||
|
if (gcs_ret_vaddr != orig_ret_vaddr) {
|
||||||
|
orig_ret_vaddr = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
put_user_gcs(trampoline_vaddr, (__force unsigned long __user *)gcspr, &err);
|
||||||
|
if (err) {
|
||||||
|
force_sig(SIGSEGV);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Replace the return addr with trampoline addr */
|
/* Replace the return addr with trampoline addr */
|
||||||
procedure_link_pointer_set(regs, trampoline_vaddr);
|
procedure_link_pointer_set(regs, trampoline_vaddr);
|
||||||
|
|
||||||
|
out:
|
||||||
return orig_ret_vaddr;
|
return orig_ret_vaddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -884,6 +884,7 @@ static u8 spectre_bhb_loop_affected(void)
|
||||||
static const struct midr_range spectre_bhb_k38_list[] = {
|
static const struct midr_range spectre_bhb_k38_list[] = {
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_A715),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A715),
|
||||||
MIDR_ALL_VERSIONS(MIDR_CORTEX_A720),
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A720),
|
||||||
|
MIDR_ALL_VERSIONS(MIDR_CORTEX_A720AE),
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
static const struct midr_range spectre_bhb_k32_list[] = {
|
static const struct midr_range spectre_bhb_k32_list[] = {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,25 @@ static void __init arm64_rsi_setup_memory(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool __arm64_is_protected_mmio(phys_addr_t base, size_t size)
|
/*
|
||||||
|
* Check if a given PA range is Trusted (e.g., Protected memory, a Trusted Device
|
||||||
|
* mapping, or an MMIO emulated in the Realm world).
|
||||||
|
*
|
||||||
|
* We can rely on the RIPAS value of the region to detect if a given region is
|
||||||
|
* protected.
|
||||||
|
*
|
||||||
|
* RIPAS_DEV - A trusted device memory or a trusted emulated MMIO (in the Realm
|
||||||
|
* world
|
||||||
|
* RIPAS_RAM - Memory (RAM), protected by the RMM guarantees. (e.g., Firmware
|
||||||
|
* reserved regions for data sharing).
|
||||||
|
*
|
||||||
|
* RIPAS_DESTROYED is a special case of one of the above, where the host did
|
||||||
|
* something without our permission and as such we can't do anything about it.
|
||||||
|
*
|
||||||
|
* The only case where something is emulated by the untrusted hypervisor or is
|
||||||
|
* backed by shared memory is indicated by RSI_RIPAS_EMPTY.
|
||||||
|
*/
|
||||||
|
bool arm64_rsi_is_protected(phys_addr_t base, size_t size)
|
||||||
{
|
{
|
||||||
enum ripas ripas;
|
enum ripas ripas;
|
||||||
phys_addr_t end, top;
|
phys_addr_t end, top;
|
||||||
|
|
@ -101,18 +119,18 @@ bool __arm64_is_protected_mmio(phys_addr_t base, size_t size)
|
||||||
break;
|
break;
|
||||||
if (WARN_ON(top <= base))
|
if (WARN_ON(top <= base))
|
||||||
break;
|
break;
|
||||||
if (ripas != RSI_RIPAS_DEV)
|
if (ripas == RSI_RIPAS_EMPTY)
|
||||||
break;
|
break;
|
||||||
base = top;
|
base = top;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base >= end;
|
return base >= end;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__arm64_is_protected_mmio);
|
EXPORT_SYMBOL(arm64_rsi_is_protected);
|
||||||
|
|
||||||
static int realm_ioremap_hook(phys_addr_t phys, size_t size, pgprot_t *prot)
|
static int realm_ioremap_hook(phys_addr_t phys, size_t size, pgprot_t *prot)
|
||||||
{
|
{
|
||||||
if (__arm64_is_protected_mmio(phys, size))
|
if (arm64_rsi_is_protected(phys, size))
|
||||||
*prot = pgprot_encrypted(*prot);
|
*prot = pgprot_encrypted(*prot);
|
||||||
else
|
else
|
||||||
*prot = pgprot_decrypted(*prot);
|
*prot = pgprot_decrypted(*prot);
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ unsigned long __kprobes do_sdei_event(struct pt_regs *regs,
|
||||||
* If we interrupted the kernel with interrupts masked, we always go
|
* If we interrupted the kernel with interrupts masked, we always go
|
||||||
* back to wherever we came from.
|
* back to wherever we came from.
|
||||||
*/
|
*/
|
||||||
if (mode == kernel_mode && !interrupts_enabled(regs))
|
if (mode == kernel_mode && regs_irqs_disabled(regs))
|
||||||
return SDEI_EV_HANDLED;
|
return SDEI_EV_HANDLED;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ static void __init request_standard_resources(void)
|
||||||
unsigned long i = 0;
|
unsigned long i = 0;
|
||||||
size_t res_size;
|
size_t res_size;
|
||||||
|
|
||||||
kernel_code.start = __pa_symbol(_stext);
|
kernel_code.start = __pa_symbol(_text);
|
||||||
kernel_code.end = __pa_symbol(__init_begin - 1);
|
kernel_code.end = __pa_symbol(__init_begin - 1);
|
||||||
kernel_data.start = __pa_symbol(_sdata);
|
kernel_data.start = __pa_symbol(_sdata);
|
||||||
kernel_data.end = __pa_symbol(_end - 1);
|
kernel_data.end = __pa_symbol(_end - 1);
|
||||||
|
|
@ -280,7 +280,7 @@ u64 cpu_logical_map(unsigned int cpu)
|
||||||
|
|
||||||
void __init __no_sanitize_address setup_arch(char **cmdline_p)
|
void __init __no_sanitize_address setup_arch(char **cmdline_p)
|
||||||
{
|
{
|
||||||
setup_initial_init_mm(_stext, _etext, _edata, _end);
|
setup_initial_init_mm(_text, _etext, _edata, _end);
|
||||||
|
|
||||||
*cmdline_p = boot_command_line;
|
*cmdline_p = boot_command_line;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <linux/cache.h>
|
#include <linux/cache.h>
|
||||||
#include <linux/compat.h>
|
#include <linux/compat.h>
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
|
#include <linux/irq-entry-common.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/signal.h>
|
#include <linux/signal.h>
|
||||||
#include <linux/freezer.h>
|
#include <linux/freezer.h>
|
||||||
|
|
@ -1576,7 +1577,7 @@ static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
|
||||||
* the kernel can handle, and then we build all the user-level signal handling
|
* the kernel can handle, and then we build all the user-level signal handling
|
||||||
* stack-frames in one go after that.
|
* stack-frames in one go after that.
|
||||||
*/
|
*/
|
||||||
void do_signal(struct pt_regs *regs)
|
void arch_do_signal_or_restart(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
unsigned long continue_addr = 0, restart_addr = 0;
|
unsigned long continue_addr = 0, restart_addr = 0;
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
|
||||||
|
|
||||||
add_random_kstack_offset();
|
add_random_kstack_offset();
|
||||||
|
|
||||||
if (scno < sc_nr) {
|
if (likely(scno < sc_nr)) {
|
||||||
syscall_fn_t syscall_fn;
|
syscall_fn_t syscall_fn;
|
||||||
syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
|
syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
|
||||||
ret = __invoke_syscall(regs, syscall_fn);
|
ret = __invoke_syscall(regs, syscall_fn);
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ endif
|
||||||
|
|
||||||
cc32-option = $(call try-run,\
|
cc32-option = $(call try-run,\
|
||||||
$(CC_COMPAT) $(1) -c -x c /dev/null -o "$$TMP",$(1),$(2))
|
$(CC_COMPAT) $(1) -c -x c /dev/null -o "$$TMP",$(1),$(2))
|
||||||
cc32-disable-warning = $(call try-run,\
|
|
||||||
$(CC_COMPAT) -W$(strip $(1)) -c -x c /dev/null -o "$$TMP",-Wno-$(strip $(1)))
|
|
||||||
|
|
||||||
# We cannot use the global flags to compile the vDSO files, the main reason
|
# We cannot use the global flags to compile the vDSO files, the main reason
|
||||||
# being that the 32-bit compiler may be older than the main (64-bit) compiler
|
# being that the 32-bit compiler may be older than the main (64-bit) compiler
|
||||||
|
|
@ -63,6 +61,7 @@ VDSO_CFLAGS += -DENABLE_COMPAT_VDSO=1
|
||||||
# KBUILD_CFLAGS from top-level Makefile
|
# KBUILD_CFLAGS from top-level Makefile
|
||||||
VDSO_CFLAGS += -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
|
VDSO_CFLAGS += -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
|
||||||
-fno-strict-aliasing -fno-common \
|
-fno-strict-aliasing -fno-common \
|
||||||
|
$(filter -Werror,$(KBUILD_CPPFLAGS)) \
|
||||||
-Werror-implicit-function-declaration \
|
-Werror-implicit-function-declaration \
|
||||||
-Wno-format-security \
|
-Wno-format-security \
|
||||||
-std=gnu11
|
-std=gnu11
|
||||||
|
|
@ -74,16 +73,6 @@ VDSO_CFLAGS += $(call cc32-option,-Werror=strict-prototypes)
|
||||||
VDSO_CFLAGS += -Werror=date-time
|
VDSO_CFLAGS += -Werror=date-time
|
||||||
VDSO_CFLAGS += $(call cc32-option,-Werror=incompatible-pointer-types)
|
VDSO_CFLAGS += $(call cc32-option,-Werror=incompatible-pointer-types)
|
||||||
|
|
||||||
# The 32-bit compiler does not provide 128-bit integers, which are used in
|
|
||||||
# some headers that are indirectly included from the vDSO code.
|
|
||||||
# This hack makes the compiler happy and should trigger a warning/error if
|
|
||||||
# variables of such type are referenced.
|
|
||||||
VDSO_CFLAGS += -D__uint128_t='void*'
|
|
||||||
# Silence some warnings coming from headers that operate on long's
|
|
||||||
# (on GCC 4.8 or older, there is unfortunately no way to silence this warning)
|
|
||||||
VDSO_CFLAGS += $(call cc32-disable-warning,shift-count-overflow)
|
|
||||||
VDSO_CFLAGS += -Wno-int-to-pointer-cast
|
|
||||||
|
|
||||||
# Compile as THUMB2 or ARM. Unwinding via frame-pointers in THUMB2 is
|
# Compile as THUMB2 or ARM. Unwinding via frame-pointers in THUMB2 is
|
||||||
# unreliable.
|
# unreliable.
|
||||||
ifeq ($(CONFIG_THUMB2_COMPAT_VDSO), y)
|
ifeq ($(CONFIG_THUMB2_COMPAT_VDSO), y)
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ void __init arm64_memblock_init(void)
|
||||||
*/
|
*/
|
||||||
if (memory_limit != PHYS_ADDR_MAX) {
|
if (memory_limit != PHYS_ADDR_MAX) {
|
||||||
memblock_mem_limit_remove_map(memory_limit);
|
memblock_mem_limit_remove_map(memory_limit);
|
||||||
memblock_add(__pa_symbol(_text), (u64)(_end - _text));
|
memblock_add(__pa_symbol(_text), (resource_size_t)(_end - _text));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
|
if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
|
||||||
|
|
@ -252,8 +252,8 @@ void __init arm64_memblock_init(void)
|
||||||
* initrd to become inaccessible via the linear mapping.
|
* initrd to become inaccessible via the linear mapping.
|
||||||
* Otherwise, this is a no-op
|
* Otherwise, this is a no-op
|
||||||
*/
|
*/
|
||||||
u64 base = phys_initrd_start & PAGE_MASK;
|
phys_addr_t base = phys_initrd_start & PAGE_MASK;
|
||||||
u64 size = PAGE_ALIGN(phys_initrd_start + phys_initrd_size) - base;
|
resource_size_t size = PAGE_ALIGN(phys_initrd_start + phys_initrd_size) - base;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We can only add back the initrd memory if we don't end up
|
* We can only add back the initrd memory if we don't end up
|
||||||
|
|
@ -279,7 +279,7 @@ void __init arm64_memblock_init(void)
|
||||||
* Register the kernel text, kernel data, initrd, and initial
|
* Register the kernel text, kernel data, initrd, and initial
|
||||||
* pagetables with memblock.
|
* pagetables with memblock.
|
||||||
*/
|
*/
|
||||||
memblock_reserve(__pa_symbol(_stext), _end - _stext);
|
memblock_reserve(__pa_symbol(_text), _end - _text);
|
||||||
if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
|
if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
|
||||||
/* the generic initrd code expects virtual addresses */
|
/* the generic initrd code expects virtual addresses */
|
||||||
initrd_start = __phys_to_virt(phys_initrd_start);
|
initrd_start = __phys_to_virt(phys_initrd_start);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@
|
||||||
#include <linux/kfence.h>
|
#include <linux/kfence.h>
|
||||||
#include <linux/pkeys.h>
|
#include <linux/pkeys.h>
|
||||||
#include <linux/mm_inline.h>
|
#include <linux/mm_inline.h>
|
||||||
|
#include <linux/pagewalk.h>
|
||||||
|
#include <linux/stop_machine.h>
|
||||||
|
|
||||||
#include <asm/barrier.h>
|
#include <asm/barrier.h>
|
||||||
#include <asm/cputype.h>
|
#include <asm/cputype.h>
|
||||||
|
|
@ -47,6 +49,8 @@
|
||||||
#define NO_CONT_MAPPINGS BIT(1)
|
#define NO_CONT_MAPPINGS BIT(1)
|
||||||
#define NO_EXEC_MAPPINGS BIT(2) /* assumes FEAT_HPDS is not used */
|
#define NO_EXEC_MAPPINGS BIT(2) /* assumes FEAT_HPDS is not used */
|
||||||
|
|
||||||
|
DEFINE_STATIC_KEY_FALSE(arm64_ptdump_lock_key);
|
||||||
|
|
||||||
u64 kimage_voffset __ro_after_init;
|
u64 kimage_voffset __ro_after_init;
|
||||||
EXPORT_SYMBOL(kimage_voffset);
|
EXPORT_SYMBOL(kimage_voffset);
|
||||||
|
|
||||||
|
|
@ -474,14 +478,18 @@ void create_kpti_ng_temp_pgd(pgd_t *pgdir, phys_addr_t phys, unsigned long virt,
|
||||||
int flags);
|
int flags);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static phys_addr_t __pgd_pgtable_alloc(struct mm_struct *mm,
|
#define INVALID_PHYS_ADDR (-1ULL)
|
||||||
|
|
||||||
|
static phys_addr_t __pgd_pgtable_alloc(struct mm_struct *mm, gfp_t gfp,
|
||||||
enum pgtable_type pgtable_type)
|
enum pgtable_type pgtable_type)
|
||||||
{
|
{
|
||||||
/* Page is zeroed by init_clear_pgtable() so don't duplicate effort. */
|
/* Page is zeroed by init_clear_pgtable() so don't duplicate effort. */
|
||||||
struct ptdesc *ptdesc = pagetable_alloc(GFP_PGTABLE_KERNEL & ~__GFP_ZERO, 0);
|
struct ptdesc *ptdesc = pagetable_alloc(gfp & ~__GFP_ZERO, 0);
|
||||||
phys_addr_t pa;
|
phys_addr_t pa;
|
||||||
|
|
||||||
BUG_ON(!ptdesc);
|
if (!ptdesc)
|
||||||
|
return INVALID_PHYS_ADDR;
|
||||||
|
|
||||||
pa = page_to_phys(ptdesc_page(ptdesc));
|
pa = page_to_phys(ptdesc_page(ptdesc));
|
||||||
|
|
||||||
switch (pgtable_type) {
|
switch (pgtable_type) {
|
||||||
|
|
@ -502,16 +510,392 @@ static phys_addr_t __pgd_pgtable_alloc(struct mm_struct *mm,
|
||||||
return pa;
|
return pa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static phys_addr_t
|
||||||
|
try_pgd_pgtable_alloc_init_mm(enum pgtable_type pgtable_type, gfp_t gfp)
|
||||||
|
{
|
||||||
|
return __pgd_pgtable_alloc(&init_mm, gfp, pgtable_type);
|
||||||
|
}
|
||||||
|
|
||||||
static phys_addr_t __maybe_unused
|
static phys_addr_t __maybe_unused
|
||||||
pgd_pgtable_alloc_init_mm(enum pgtable_type pgtable_type)
|
pgd_pgtable_alloc_init_mm(enum pgtable_type pgtable_type)
|
||||||
{
|
{
|
||||||
return __pgd_pgtable_alloc(&init_mm, pgtable_type);
|
phys_addr_t pa;
|
||||||
|
|
||||||
|
pa = __pgd_pgtable_alloc(&init_mm, GFP_PGTABLE_KERNEL, pgtable_type);
|
||||||
|
BUG_ON(pa == INVALID_PHYS_ADDR);
|
||||||
|
return pa;
|
||||||
}
|
}
|
||||||
|
|
||||||
static phys_addr_t
|
static phys_addr_t
|
||||||
pgd_pgtable_alloc_special_mm(enum pgtable_type pgtable_type)
|
pgd_pgtable_alloc_special_mm(enum pgtable_type pgtable_type)
|
||||||
{
|
{
|
||||||
return __pgd_pgtable_alloc(NULL, pgtable_type);
|
phys_addr_t pa;
|
||||||
|
|
||||||
|
pa = __pgd_pgtable_alloc(NULL, GFP_PGTABLE_KERNEL, pgtable_type);
|
||||||
|
BUG_ON(pa == INVALID_PHYS_ADDR);
|
||||||
|
return pa;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void split_contpte(pte_t *ptep)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ptep = PTR_ALIGN_DOWN(ptep, sizeof(*ptep) * CONT_PTES);
|
||||||
|
for (i = 0; i < CONT_PTES; i++, ptep++)
|
||||||
|
__set_pte(ptep, pte_mknoncont(__ptep_get(ptep)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int split_pmd(pmd_t *pmdp, pmd_t pmd, gfp_t gfp, bool to_cont)
|
||||||
|
{
|
||||||
|
pmdval_t tableprot = PMD_TYPE_TABLE | PMD_TABLE_UXN | PMD_TABLE_AF;
|
||||||
|
unsigned long pfn = pmd_pfn(pmd);
|
||||||
|
pgprot_t prot = pmd_pgprot(pmd);
|
||||||
|
phys_addr_t pte_phys;
|
||||||
|
pte_t *ptep;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
pte_phys = try_pgd_pgtable_alloc_init_mm(TABLE_PTE, gfp);
|
||||||
|
if (pte_phys == INVALID_PHYS_ADDR)
|
||||||
|
return -ENOMEM;
|
||||||
|
ptep = (pte_t *)phys_to_virt(pte_phys);
|
||||||
|
|
||||||
|
if (pgprot_val(prot) & PMD_SECT_PXN)
|
||||||
|
tableprot |= PMD_TABLE_PXN;
|
||||||
|
|
||||||
|
prot = __pgprot((pgprot_val(prot) & ~PTE_TYPE_MASK) | PTE_TYPE_PAGE);
|
||||||
|
prot = __pgprot(pgprot_val(prot) & ~PTE_CONT);
|
||||||
|
if (to_cont)
|
||||||
|
prot = __pgprot(pgprot_val(prot) | PTE_CONT);
|
||||||
|
|
||||||
|
for (i = 0; i < PTRS_PER_PTE; i++, ptep++, pfn++)
|
||||||
|
__set_pte(ptep, pfn_pte(pfn, prot));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure the pte entries are visible to the table walker by the time
|
||||||
|
* the pmd entry that points to the ptes is visible.
|
||||||
|
*/
|
||||||
|
dsb(ishst);
|
||||||
|
__pmd_populate(pmdp, pte_phys, tableprot);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void split_contpmd(pmd_t *pmdp)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
pmdp = PTR_ALIGN_DOWN(pmdp, sizeof(*pmdp) * CONT_PMDS);
|
||||||
|
for (i = 0; i < CONT_PMDS; i++, pmdp++)
|
||||||
|
set_pmd(pmdp, pmd_mknoncont(pmdp_get(pmdp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int split_pud(pud_t *pudp, pud_t pud, gfp_t gfp, bool to_cont)
|
||||||
|
{
|
||||||
|
pudval_t tableprot = PUD_TYPE_TABLE | PUD_TABLE_UXN | PUD_TABLE_AF;
|
||||||
|
unsigned int step = PMD_SIZE >> PAGE_SHIFT;
|
||||||
|
unsigned long pfn = pud_pfn(pud);
|
||||||
|
pgprot_t prot = pud_pgprot(pud);
|
||||||
|
phys_addr_t pmd_phys;
|
||||||
|
pmd_t *pmdp;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
pmd_phys = try_pgd_pgtable_alloc_init_mm(TABLE_PMD, gfp);
|
||||||
|
if (pmd_phys == INVALID_PHYS_ADDR)
|
||||||
|
return -ENOMEM;
|
||||||
|
pmdp = (pmd_t *)phys_to_virt(pmd_phys);
|
||||||
|
|
||||||
|
if (pgprot_val(prot) & PMD_SECT_PXN)
|
||||||
|
tableprot |= PUD_TABLE_PXN;
|
||||||
|
|
||||||
|
prot = __pgprot((pgprot_val(prot) & ~PMD_TYPE_MASK) | PMD_TYPE_SECT);
|
||||||
|
prot = __pgprot(pgprot_val(prot) & ~PTE_CONT);
|
||||||
|
if (to_cont)
|
||||||
|
prot = __pgprot(pgprot_val(prot) | PTE_CONT);
|
||||||
|
|
||||||
|
for (i = 0; i < PTRS_PER_PMD; i++, pmdp++, pfn += step)
|
||||||
|
set_pmd(pmdp, pfn_pmd(pfn, prot));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure the pmd entries are visible to the table walker by the time
|
||||||
|
* the pud entry that points to the pmds is visible.
|
||||||
|
*/
|
||||||
|
dsb(ishst);
|
||||||
|
__pud_populate(pudp, pmd_phys, tableprot);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int split_kernel_leaf_mapping_locked(unsigned long addr)
|
||||||
|
{
|
||||||
|
pgd_t *pgdp, pgd;
|
||||||
|
p4d_t *p4dp, p4d;
|
||||||
|
pud_t *pudp, pud;
|
||||||
|
pmd_t *pmdp, pmd;
|
||||||
|
pte_t *ptep, pte;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PGD: If addr is PGD aligned then addr already describes a leaf
|
||||||
|
* boundary. If not present then there is nothing to split.
|
||||||
|
*/
|
||||||
|
if (ALIGN_DOWN(addr, PGDIR_SIZE) == addr)
|
||||||
|
goto out;
|
||||||
|
pgdp = pgd_offset_k(addr);
|
||||||
|
pgd = pgdp_get(pgdp);
|
||||||
|
if (!pgd_present(pgd))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* P4D: If addr is P4D aligned then addr already describes a leaf
|
||||||
|
* boundary. If not present then there is nothing to split.
|
||||||
|
*/
|
||||||
|
if (ALIGN_DOWN(addr, P4D_SIZE) == addr)
|
||||||
|
goto out;
|
||||||
|
p4dp = p4d_offset(pgdp, addr);
|
||||||
|
p4d = p4dp_get(p4dp);
|
||||||
|
if (!p4d_present(p4d))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PUD: If addr is PUD aligned then addr already describes a leaf
|
||||||
|
* boundary. If not present then there is nothing to split. Otherwise,
|
||||||
|
* if we have a pud leaf, split to contpmd.
|
||||||
|
*/
|
||||||
|
if (ALIGN_DOWN(addr, PUD_SIZE) == addr)
|
||||||
|
goto out;
|
||||||
|
pudp = pud_offset(p4dp, addr);
|
||||||
|
pud = pudp_get(pudp);
|
||||||
|
if (!pud_present(pud))
|
||||||
|
goto out;
|
||||||
|
if (pud_leaf(pud)) {
|
||||||
|
ret = split_pud(pudp, pud, GFP_PGTABLE_KERNEL, true);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CONTPMD: If addr is CONTPMD aligned then addr already describes a
|
||||||
|
* leaf boundary. If not present then there is nothing to split.
|
||||||
|
* Otherwise, if we have a contpmd leaf, split to pmd.
|
||||||
|
*/
|
||||||
|
if (ALIGN_DOWN(addr, CONT_PMD_SIZE) == addr)
|
||||||
|
goto out;
|
||||||
|
pmdp = pmd_offset(pudp, addr);
|
||||||
|
pmd = pmdp_get(pmdp);
|
||||||
|
if (!pmd_present(pmd))
|
||||||
|
goto out;
|
||||||
|
if (pmd_leaf(pmd)) {
|
||||||
|
if (pmd_cont(pmd))
|
||||||
|
split_contpmd(pmdp);
|
||||||
|
/*
|
||||||
|
* PMD: If addr is PMD aligned then addr already describes a
|
||||||
|
* leaf boundary. Otherwise, split to contpte.
|
||||||
|
*/
|
||||||
|
if (ALIGN_DOWN(addr, PMD_SIZE) == addr)
|
||||||
|
goto out;
|
||||||
|
ret = split_pmd(pmdp, pmd, GFP_PGTABLE_KERNEL, true);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CONTPTE: If addr is CONTPTE aligned then addr already describes a
|
||||||
|
* leaf boundary. If not present then there is nothing to split.
|
||||||
|
* Otherwise, if we have a contpte leaf, split to pte.
|
||||||
|
*/
|
||||||
|
if (ALIGN_DOWN(addr, CONT_PTE_SIZE) == addr)
|
||||||
|
goto out;
|
||||||
|
ptep = pte_offset_kernel(pmdp, addr);
|
||||||
|
pte = __ptep_get(ptep);
|
||||||
|
if (!pte_present(pte))
|
||||||
|
goto out;
|
||||||
|
if (pte_cont(pte))
|
||||||
|
split_contpte(ptep);
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(pgtable_split_lock);
|
||||||
|
|
||||||
|
int split_kernel_leaf_mapping(unsigned long start, unsigned long end)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* !BBML2_NOABORT systems should not be trying to change permissions on
|
||||||
|
* anything that is not pte-mapped in the first place. Just return early
|
||||||
|
* and let the permission change code raise a warning if not already
|
||||||
|
* pte-mapped.
|
||||||
|
*/
|
||||||
|
if (!system_supports_bbml2_noabort())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure start and end are at least page-aligned since this is the
|
||||||
|
* finest granularity we can split to.
|
||||||
|
*/
|
||||||
|
if (start != PAGE_ALIGN(start) || end != PAGE_ALIGN(end))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
mutex_lock(&pgtable_split_lock);
|
||||||
|
arch_enter_lazy_mmu_mode();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The split_kernel_leaf_mapping_locked() may sleep, it is not a
|
||||||
|
* problem for ARM64 since ARM64's lazy MMU implementation allows
|
||||||
|
* sleeping.
|
||||||
|
*
|
||||||
|
* Optimize for the common case of splitting out a single page from a
|
||||||
|
* larger mapping. Here we can just split on the "least aligned" of
|
||||||
|
* start and end and this will guarantee that there must also be a split
|
||||||
|
* on the more aligned address since the both addresses must be in the
|
||||||
|
* same contpte block and it must have been split to ptes.
|
||||||
|
*/
|
||||||
|
if (end - start == PAGE_SIZE) {
|
||||||
|
start = __ffs(start) < __ffs(end) ? start : end;
|
||||||
|
ret = split_kernel_leaf_mapping_locked(start);
|
||||||
|
} else {
|
||||||
|
ret = split_kernel_leaf_mapping_locked(start);
|
||||||
|
if (!ret)
|
||||||
|
ret = split_kernel_leaf_mapping_locked(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
arch_leave_lazy_mmu_mode();
|
||||||
|
mutex_unlock(&pgtable_split_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init split_to_ptes_pud_entry(pud_t *pudp, unsigned long addr,
|
||||||
|
unsigned long next,
|
||||||
|
struct mm_walk *walk)
|
||||||
|
{
|
||||||
|
pud_t pud = pudp_get(pudp);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (pud_leaf(pud))
|
||||||
|
ret = split_pud(pudp, pud, GFP_ATOMIC, false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init split_to_ptes_pmd_entry(pmd_t *pmdp, unsigned long addr,
|
||||||
|
unsigned long next,
|
||||||
|
struct mm_walk *walk)
|
||||||
|
{
|
||||||
|
pmd_t pmd = pmdp_get(pmdp);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (pmd_leaf(pmd)) {
|
||||||
|
if (pmd_cont(pmd))
|
||||||
|
split_contpmd(pmdp);
|
||||||
|
ret = split_pmd(pmdp, pmd, GFP_ATOMIC, false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have split the pmd directly to ptes so there is no need to
|
||||||
|
* visit each pte to check if they are contpte.
|
||||||
|
*/
|
||||||
|
walk->action = ACTION_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init split_to_ptes_pte_entry(pte_t *ptep, unsigned long addr,
|
||||||
|
unsigned long next,
|
||||||
|
struct mm_walk *walk)
|
||||||
|
{
|
||||||
|
pte_t pte = __ptep_get(ptep);
|
||||||
|
|
||||||
|
if (pte_cont(pte))
|
||||||
|
split_contpte(ptep);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct mm_walk_ops split_to_ptes_ops __initconst = {
|
||||||
|
.pud_entry = split_to_ptes_pud_entry,
|
||||||
|
.pmd_entry = split_to_ptes_pmd_entry,
|
||||||
|
.pte_entry = split_to_ptes_pte_entry,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool linear_map_requires_bbml2 __initdata;
|
||||||
|
|
||||||
|
u32 idmap_kpti_bbml2_flag;
|
||||||
|
|
||||||
|
void __init init_idmap_kpti_bbml2_flag(void)
|
||||||
|
{
|
||||||
|
WRITE_ONCE(idmap_kpti_bbml2_flag, 1);
|
||||||
|
/* Must be visible to other CPUs before stop_machine() is called. */
|
||||||
|
smp_mb();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init linear_map_split_to_ptes(void *__unused)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Repainting the linear map must be done by CPU0 (the boot CPU) because
|
||||||
|
* that's the only CPU that we know supports BBML2. The other CPUs will
|
||||||
|
* be held in a waiting area with the idmap active.
|
||||||
|
*/
|
||||||
|
if (!smp_processor_id()) {
|
||||||
|
unsigned long lstart = _PAGE_OFFSET(vabits_actual);
|
||||||
|
unsigned long lend = PAGE_END;
|
||||||
|
unsigned long kstart = (unsigned long)lm_alias(_stext);
|
||||||
|
unsigned long kend = (unsigned long)lm_alias(__init_begin);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for all secondary CPUs to be put into the waiting area.
|
||||||
|
*/
|
||||||
|
smp_cond_load_acquire(&idmap_kpti_bbml2_flag, VAL == num_online_cpus());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Walk all of the linear map [lstart, lend), except the kernel
|
||||||
|
* linear map alias [kstart, kend), and split all mappings to
|
||||||
|
* PTE. The kernel alias remains static throughout runtime so
|
||||||
|
* can continue to be safely mapped with large mappings.
|
||||||
|
*/
|
||||||
|
ret = walk_kernel_page_table_range_lockless(lstart, kstart,
|
||||||
|
&split_to_ptes_ops, NULL, NULL);
|
||||||
|
if (!ret)
|
||||||
|
ret = walk_kernel_page_table_range_lockless(kend, lend,
|
||||||
|
&split_to_ptes_ops, NULL, NULL);
|
||||||
|
if (ret)
|
||||||
|
panic("Failed to split linear map\n");
|
||||||
|
flush_tlb_kernel_range(lstart, lend);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Relies on dsb in flush_tlb_kernel_range() to avoid reordering
|
||||||
|
* before any page table split operations.
|
||||||
|
*/
|
||||||
|
WRITE_ONCE(idmap_kpti_bbml2_flag, 0);
|
||||||
|
} else {
|
||||||
|
typedef void (wait_split_fn)(void);
|
||||||
|
extern wait_split_fn wait_linear_map_split_to_ptes;
|
||||||
|
wait_split_fn *wait_fn;
|
||||||
|
|
||||||
|
wait_fn = (void *)__pa_symbol(wait_linear_map_split_to_ptes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At least one secondary CPU doesn't support BBML2 so cannot
|
||||||
|
* tolerate the size of the live mappings changing. So have the
|
||||||
|
* secondary CPUs wait for the boot CPU to make the changes
|
||||||
|
* with the idmap active and init_mm inactive.
|
||||||
|
*/
|
||||||
|
cpu_install_idmap();
|
||||||
|
wait_fn();
|
||||||
|
cpu_uninstall_idmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __init linear_map_maybe_split_to_ptes(void)
|
||||||
|
{
|
||||||
|
if (linear_map_requires_bbml2 && !system_supports_bbml2_noabort()) {
|
||||||
|
init_idmap_kpti_bbml2_flag();
|
||||||
|
stop_machine(linear_map_split_to_ptes, NULL, cpu_online_mask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -574,8 +958,8 @@ void __init mark_linear_text_alias_ro(void)
|
||||||
/*
|
/*
|
||||||
* Remove the write permissions from the linear alias of .text/.rodata
|
* Remove the write permissions from the linear alias of .text/.rodata
|
||||||
*/
|
*/
|
||||||
update_mapping_prot(__pa_symbol(_stext), (unsigned long)lm_alias(_stext),
|
update_mapping_prot(__pa_symbol(_text), (unsigned long)lm_alias(_text),
|
||||||
(unsigned long)__init_begin - (unsigned long)_stext,
|
(unsigned long)__init_begin - (unsigned long)_text,
|
||||||
PAGE_KERNEL_RO);
|
PAGE_KERNEL_RO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -633,10 +1017,20 @@ static inline void arm64_kfence_map_pool(phys_addr_t kfence_pool, pgd_t *pgdp) {
|
||||||
|
|
||||||
#endif /* CONFIG_KFENCE */
|
#endif /* CONFIG_KFENCE */
|
||||||
|
|
||||||
|
static inline bool force_pte_mapping(void)
|
||||||
|
{
|
||||||
|
bool bbml2 = system_capabilities_finalized() ?
|
||||||
|
system_supports_bbml2_noabort() : cpu_supports_bbml2_noabort();
|
||||||
|
|
||||||
|
return (!bbml2 && (rodata_full || arm64_kfence_can_set_direct_map() ||
|
||||||
|
is_realm_world())) ||
|
||||||
|
debug_pagealloc_enabled();
|
||||||
|
}
|
||||||
|
|
||||||
static void __init map_mem(pgd_t *pgdp)
|
static void __init map_mem(pgd_t *pgdp)
|
||||||
{
|
{
|
||||||
static const u64 direct_map_end = _PAGE_END(VA_BITS_MIN);
|
static const u64 direct_map_end = _PAGE_END(VA_BITS_MIN);
|
||||||
phys_addr_t kernel_start = __pa_symbol(_stext);
|
phys_addr_t kernel_start = __pa_symbol(_text);
|
||||||
phys_addr_t kernel_end = __pa_symbol(__init_begin);
|
phys_addr_t kernel_end = __pa_symbol(__init_begin);
|
||||||
phys_addr_t start, end;
|
phys_addr_t start, end;
|
||||||
phys_addr_t early_kfence_pool;
|
phys_addr_t early_kfence_pool;
|
||||||
|
|
@ -658,7 +1052,9 @@ static void __init map_mem(pgd_t *pgdp)
|
||||||
|
|
||||||
early_kfence_pool = arm64_kfence_alloc_pool();
|
early_kfence_pool = arm64_kfence_alloc_pool();
|
||||||
|
|
||||||
if (can_set_direct_map())
|
linear_map_requires_bbml2 = !force_pte_mapping() && can_set_direct_map();
|
||||||
|
|
||||||
|
if (force_pte_mapping())
|
||||||
flags |= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;
|
flags |= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -683,7 +1079,7 @@ static void __init map_mem(pgd_t *pgdp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Map the linear alias of the [_stext, __init_begin) interval
|
* Map the linear alias of the [_text, __init_begin) interval
|
||||||
* as non-executable now, and remove the write permission in
|
* as non-executable now, and remove the write permission in
|
||||||
* mark_linear_text_alias_ro() below (which will be called after
|
* mark_linear_text_alias_ro() below (which will be called after
|
||||||
* alternative patching has completed). This makes the contents
|
* alternative patching has completed). This makes the contents
|
||||||
|
|
@ -710,6 +1106,10 @@ void mark_rodata_ro(void)
|
||||||
WRITE_ONCE(rodata_is_rw, false);
|
WRITE_ONCE(rodata_is_rw, false);
|
||||||
update_mapping_prot(__pa_symbol(__start_rodata), (unsigned long)__start_rodata,
|
update_mapping_prot(__pa_symbol(__start_rodata), (unsigned long)__start_rodata,
|
||||||
section_size, PAGE_KERNEL_RO);
|
section_size, PAGE_KERNEL_RO);
|
||||||
|
/* mark the range between _text and _stext as read only. */
|
||||||
|
update_mapping_prot(__pa_symbol(_text), (unsigned long)_text,
|
||||||
|
(unsigned long)_stext - (unsigned long)_text,
|
||||||
|
PAGE_KERNEL_RO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __init declare_vma(struct vm_struct *vma,
|
static void __init declare_vma(struct vm_struct *vma,
|
||||||
|
|
@ -780,38 +1180,41 @@ static void __init declare_kernel_vmas(void)
|
||||||
{
|
{
|
||||||
static struct vm_struct vmlinux_seg[KERNEL_SEGMENT_COUNT];
|
static struct vm_struct vmlinux_seg[KERNEL_SEGMENT_COUNT];
|
||||||
|
|
||||||
declare_vma(&vmlinux_seg[0], _stext, _etext, VM_NO_GUARD);
|
declare_vma(&vmlinux_seg[0], _text, _etext, VM_NO_GUARD);
|
||||||
declare_vma(&vmlinux_seg[1], __start_rodata, __inittext_begin, VM_NO_GUARD);
|
declare_vma(&vmlinux_seg[1], __start_rodata, __inittext_begin, VM_NO_GUARD);
|
||||||
declare_vma(&vmlinux_seg[2], __inittext_begin, __inittext_end, VM_NO_GUARD);
|
declare_vma(&vmlinux_seg[2], __inittext_begin, __inittext_end, VM_NO_GUARD);
|
||||||
declare_vma(&vmlinux_seg[3], __initdata_begin, __initdata_end, VM_NO_GUARD);
|
declare_vma(&vmlinux_seg[3], __initdata_begin, __initdata_end, VM_NO_GUARD);
|
||||||
declare_vma(&vmlinux_seg[4], _data, _end, 0);
|
declare_vma(&vmlinux_seg[4], _data, _end, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __pi_map_range(u64 *pgd, u64 start, u64 end, u64 pa, pgprot_t prot,
|
void __pi_map_range(phys_addr_t *pte, u64 start, u64 end, phys_addr_t pa,
|
||||||
int level, pte_t *tbl, bool may_use_cont, u64 va_offset);
|
pgprot_t prot, int level, pte_t *tbl, bool may_use_cont,
|
||||||
|
u64 va_offset);
|
||||||
|
|
||||||
static u8 idmap_ptes[IDMAP_LEVELS - 1][PAGE_SIZE] __aligned(PAGE_SIZE) __ro_after_init,
|
static u8 idmap_ptes[IDMAP_LEVELS - 1][PAGE_SIZE] __aligned(PAGE_SIZE) __ro_after_init,
|
||||||
kpti_ptes[IDMAP_LEVELS - 1][PAGE_SIZE] __aligned(PAGE_SIZE) __ro_after_init;
|
kpti_bbml2_ptes[IDMAP_LEVELS - 1][PAGE_SIZE] __aligned(PAGE_SIZE) __ro_after_init;
|
||||||
|
|
||||||
static void __init create_idmap(void)
|
static void __init create_idmap(void)
|
||||||
{
|
{
|
||||||
u64 start = __pa_symbol(__idmap_text_start);
|
phys_addr_t start = __pa_symbol(__idmap_text_start);
|
||||||
u64 end = __pa_symbol(__idmap_text_end);
|
phys_addr_t end = __pa_symbol(__idmap_text_end);
|
||||||
u64 ptep = __pa_symbol(idmap_ptes);
|
phys_addr_t ptep = __pa_symbol(idmap_ptes);
|
||||||
|
|
||||||
__pi_map_range(&ptep, start, end, start, PAGE_KERNEL_ROX,
|
__pi_map_range(&ptep, start, end, start, PAGE_KERNEL_ROX,
|
||||||
IDMAP_ROOT_LEVEL, (pte_t *)idmap_pg_dir, false,
|
IDMAP_ROOT_LEVEL, (pte_t *)idmap_pg_dir, false,
|
||||||
__phys_to_virt(ptep) - ptep);
|
__phys_to_virt(ptep) - ptep);
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_UNMAP_KERNEL_AT_EL0) && !arm64_use_ng_mappings) {
|
if (linear_map_requires_bbml2 ||
|
||||||
extern u32 __idmap_kpti_flag;
|
(IS_ENABLED(CONFIG_UNMAP_KERNEL_AT_EL0) && !arm64_use_ng_mappings)) {
|
||||||
u64 pa = __pa_symbol(&__idmap_kpti_flag);
|
phys_addr_t pa = __pa_symbol(&idmap_kpti_bbml2_flag);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The KPTI G-to-nG conversion code needs a read-write mapping
|
* The KPTI G-to-nG conversion code needs a read-write mapping
|
||||||
* of its synchronization flag in the ID map.
|
* of its synchronization flag in the ID map. This is also used
|
||||||
|
* when splitting the linear map to ptes if a secondary CPU
|
||||||
|
* doesn't support bbml2.
|
||||||
*/
|
*/
|
||||||
ptep = __pa_symbol(kpti_ptes);
|
ptep = __pa_symbol(kpti_bbml2_ptes);
|
||||||
__pi_map_range(&ptep, pa, pa + sizeof(u32), pa, PAGE_KERNEL,
|
__pi_map_range(&ptep, pa, pa + sizeof(u32), pa, PAGE_KERNEL,
|
||||||
IDMAP_ROOT_LEVEL, (pte_t *)idmap_pg_dir, false,
|
IDMAP_ROOT_LEVEL, (pte_t *)idmap_pg_dir, false,
|
||||||
__phys_to_virt(ptep) - ptep);
|
__phys_to_virt(ptep) - ptep);
|
||||||
|
|
@ -1261,7 +1664,8 @@ int pmd_clear_huge(pmd_t *pmdp)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
|
static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
|
||||||
|
bool acquire_mmap_lock)
|
||||||
{
|
{
|
||||||
pte_t *table;
|
pte_t *table;
|
||||||
pmd_t pmd;
|
pmd_t pmd;
|
||||||
|
|
@ -1273,13 +1677,25 @@ int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* See comment in pud_free_pmd_page for static key logic */
|
||||||
table = pte_offset_kernel(pmdp, addr);
|
table = pte_offset_kernel(pmdp, addr);
|
||||||
pmd_clear(pmdp);
|
pmd_clear(pmdp);
|
||||||
__flush_tlb_kernel_pgtable(addr);
|
__flush_tlb_kernel_pgtable(addr);
|
||||||
|
if (static_branch_unlikely(&arm64_ptdump_lock_key) && acquire_mmap_lock) {
|
||||||
|
mmap_read_lock(&init_mm);
|
||||||
|
mmap_read_unlock(&init_mm);
|
||||||
|
}
|
||||||
|
|
||||||
pte_free_kernel(NULL, table);
|
pte_free_kernel(NULL, table);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int pmd_free_pte_page(pmd_t *pmdp, unsigned long addr)
|
||||||
|
{
|
||||||
|
/* If ptdump is walking the pagetables, acquire init_mm.mmap_lock */
|
||||||
|
return __pmd_free_pte_page(pmdp, addr, /* acquire_mmap_lock = */ true);
|
||||||
|
}
|
||||||
|
|
||||||
int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
|
int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
|
||||||
{
|
{
|
||||||
pmd_t *table;
|
pmd_t *table;
|
||||||
|
|
@ -1295,16 +1711,36 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
table = pmd_offset(pudp, addr);
|
table = pmd_offset(pudp, addr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Our objective is to prevent ptdump from reading a PMD table which has
|
||||||
|
* been freed. In this race, if pud_free_pmd_page observes the key on
|
||||||
|
* (which got flipped by ptdump) then the mmap lock sequence here will,
|
||||||
|
* as a result of the mmap write lock/unlock sequence in ptdump, give
|
||||||
|
* us the correct synchronization. If not, this means that ptdump has
|
||||||
|
* yet not started walking the pagetables - the sequence of barriers
|
||||||
|
* issued by __flush_tlb_kernel_pgtable() guarantees that ptdump will
|
||||||
|
* observe an empty PUD.
|
||||||
|
*/
|
||||||
|
pud_clear(pudp);
|
||||||
|
__flush_tlb_kernel_pgtable(addr);
|
||||||
|
if (static_branch_unlikely(&arm64_ptdump_lock_key)) {
|
||||||
|
mmap_read_lock(&init_mm);
|
||||||
|
mmap_read_unlock(&init_mm);
|
||||||
|
}
|
||||||
|
|
||||||
pmdp = table;
|
pmdp = table;
|
||||||
next = addr;
|
next = addr;
|
||||||
end = addr + PUD_SIZE;
|
end = addr + PUD_SIZE;
|
||||||
do {
|
do {
|
||||||
if (pmd_present(pmdp_get(pmdp)))
|
if (pmd_present(pmdp_get(pmdp)))
|
||||||
pmd_free_pte_page(pmdp, next);
|
/*
|
||||||
|
* PMD has been isolated, so ptdump won't see it. No
|
||||||
|
* need to acquire init_mm.mmap_lock.
|
||||||
|
*/
|
||||||
|
__pmd_free_pte_page(pmdp, next, /* acquire_mmap_lock = */ false);
|
||||||
} while (pmdp++, next += PMD_SIZE, next != end);
|
} while (pmdp++, next += PMD_SIZE, next != end);
|
||||||
|
|
||||||
pud_clear(pudp);
|
|
||||||
__flush_tlb_kernel_pgtable(addr);
|
|
||||||
pmd_free(NULL, table);
|
pmd_free(NULL, table);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -1324,8 +1760,8 @@ static void __remove_pgd_mapping(pgd_t *pgdir, unsigned long start, u64 size)
|
||||||
struct range arch_get_mappable_range(void)
|
struct range arch_get_mappable_range(void)
|
||||||
{
|
{
|
||||||
struct range mhp_range;
|
struct range mhp_range;
|
||||||
u64 start_linear_pa = __pa(_PAGE_OFFSET(vabits_actual));
|
phys_addr_t start_linear_pa = __pa(_PAGE_OFFSET(vabits_actual));
|
||||||
u64 end_linear_pa = __pa(PAGE_END - 1);
|
phys_addr_t end_linear_pa = __pa(PAGE_END - 1);
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
|
if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
|
||||||
/*
|
/*
|
||||||
|
|
@ -1360,7 +1796,7 @@ int arch_add_memory(int nid, u64 start, u64 size,
|
||||||
|
|
||||||
VM_BUG_ON(!mhp_range_allowed(start, size, true));
|
VM_BUG_ON(!mhp_range_allowed(start, size, true));
|
||||||
|
|
||||||
if (can_set_direct_map())
|
if (force_pte_mapping())
|
||||||
flags |= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;
|
flags |= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;
|
||||||
|
|
||||||
__create_pgd_mapping(swapper_pg_dir, start, __phys_to_virt(start),
|
__create_pgd_mapping(swapper_pg_dir, start, __phys_to_virt(start),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <linux/mem_encrypt.h>
|
#include <linux/mem_encrypt.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/vmalloc.h>
|
#include <linux/vmalloc.h>
|
||||||
|
#include <linux/pagewalk.h>
|
||||||
|
|
||||||
#include <asm/cacheflush.h>
|
#include <asm/cacheflush.h>
|
||||||
#include <asm/pgtable-prot.h>
|
#include <asm/pgtable-prot.h>
|
||||||
|
|
@ -20,7 +21,66 @@ struct page_change_data {
|
||||||
pgprot_t clear_mask;
|
pgprot_t clear_mask;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool rodata_full __ro_after_init = IS_ENABLED(CONFIG_RODATA_FULL_DEFAULT_ENABLED);
|
static ptdesc_t set_pageattr_masks(ptdesc_t val, struct mm_walk *walk)
|
||||||
|
{
|
||||||
|
struct page_change_data *masks = walk->private;
|
||||||
|
|
||||||
|
val &= ~(pgprot_val(masks->clear_mask));
|
||||||
|
val |= (pgprot_val(masks->set_mask));
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pageattr_pud_entry(pud_t *pud, unsigned long addr,
|
||||||
|
unsigned long next, struct mm_walk *walk)
|
||||||
|
{
|
||||||
|
pud_t val = pudp_get(pud);
|
||||||
|
|
||||||
|
if (pud_sect(val)) {
|
||||||
|
if (WARN_ON_ONCE((next - addr) != PUD_SIZE))
|
||||||
|
return -EINVAL;
|
||||||
|
val = __pud(set_pageattr_masks(pud_val(val), walk));
|
||||||
|
set_pud(pud, val);
|
||||||
|
walk->action = ACTION_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pageattr_pmd_entry(pmd_t *pmd, unsigned long addr,
|
||||||
|
unsigned long next, struct mm_walk *walk)
|
||||||
|
{
|
||||||
|
pmd_t val = pmdp_get(pmd);
|
||||||
|
|
||||||
|
if (pmd_sect(val)) {
|
||||||
|
if (WARN_ON_ONCE((next - addr) != PMD_SIZE))
|
||||||
|
return -EINVAL;
|
||||||
|
val = __pmd(set_pageattr_masks(pmd_val(val), walk));
|
||||||
|
set_pmd(pmd, val);
|
||||||
|
walk->action = ACTION_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pageattr_pte_entry(pte_t *pte, unsigned long addr,
|
||||||
|
unsigned long next, struct mm_walk *walk)
|
||||||
|
{
|
||||||
|
pte_t val = __ptep_get(pte);
|
||||||
|
|
||||||
|
val = __pte(set_pageattr_masks(pte_val(val), walk));
|
||||||
|
__set_pte(pte, val);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct mm_walk_ops pageattr_ops = {
|
||||||
|
.pud_entry = pageattr_pud_entry,
|
||||||
|
.pmd_entry = pageattr_pmd_entry,
|
||||||
|
.pte_entry = pageattr_pte_entry,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool rodata_full __ro_after_init = true;
|
||||||
|
|
||||||
bool can_set_direct_map(void)
|
bool can_set_direct_map(void)
|
||||||
{
|
{
|
||||||
|
|
@ -37,23 +97,8 @@ bool can_set_direct_map(void)
|
||||||
arm64_kfence_can_set_direct_map() || is_realm_world();
|
arm64_kfence_can_set_direct_map() || is_realm_world();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int change_page_range(pte_t *ptep, unsigned long addr, void *data)
|
static int update_range_prot(unsigned long start, unsigned long size,
|
||||||
{
|
pgprot_t set_mask, pgprot_t clear_mask)
|
||||||
struct page_change_data *cdata = data;
|
|
||||||
pte_t pte = __ptep_get(ptep);
|
|
||||||
|
|
||||||
pte = clear_pte_bit(pte, cdata->clear_mask);
|
|
||||||
pte = set_pte_bit(pte, cdata->set_mask);
|
|
||||||
|
|
||||||
__set_pte(ptep, pte);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This function assumes that the range is mapped with PAGE_SIZE pages.
|
|
||||||
*/
|
|
||||||
static int __change_memory_common(unsigned long start, unsigned long size,
|
|
||||||
pgprot_t set_mask, pgprot_t clear_mask)
|
|
||||||
{
|
{
|
||||||
struct page_change_data data;
|
struct page_change_data data;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
@ -61,8 +106,30 @@ static int __change_memory_common(unsigned long start, unsigned long size,
|
||||||
data.set_mask = set_mask;
|
data.set_mask = set_mask;
|
||||||
data.clear_mask = clear_mask;
|
data.clear_mask = clear_mask;
|
||||||
|
|
||||||
ret = apply_to_page_range(&init_mm, start, size, change_page_range,
|
ret = split_kernel_leaf_mapping(start, start + size);
|
||||||
&data);
|
if (WARN_ON_ONCE(ret))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
arch_enter_lazy_mmu_mode();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The caller must ensure that the range we are operating on does not
|
||||||
|
* partially overlap a block mapping, or a cont mapping. Any such case
|
||||||
|
* must be eliminated by splitting the mapping.
|
||||||
|
*/
|
||||||
|
ret = walk_kernel_page_table_range_lockless(start, start + size,
|
||||||
|
&pageattr_ops, NULL, &data);
|
||||||
|
arch_leave_lazy_mmu_mode();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __change_memory_common(unsigned long start, unsigned long size,
|
||||||
|
pgprot_t set_mask, pgprot_t clear_mask)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = update_range_prot(start, size, set_mask, clear_mask);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the memory is being made valid without changing any other bits
|
* If the memory is being made valid without changing any other bits
|
||||||
|
|
@ -174,32 +241,26 @@ int set_memory_valid(unsigned long addr, int numpages, int enable)
|
||||||
|
|
||||||
int set_direct_map_invalid_noflush(struct page *page)
|
int set_direct_map_invalid_noflush(struct page *page)
|
||||||
{
|
{
|
||||||
struct page_change_data data = {
|
pgprot_t clear_mask = __pgprot(PTE_VALID);
|
||||||
.set_mask = __pgprot(0),
|
pgprot_t set_mask = __pgprot(0);
|
||||||
.clear_mask = __pgprot(PTE_VALID),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!can_set_direct_map())
|
if (!can_set_direct_map())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return apply_to_page_range(&init_mm,
|
return update_range_prot((unsigned long)page_address(page),
|
||||||
(unsigned long)page_address(page),
|
PAGE_SIZE, set_mask, clear_mask);
|
||||||
PAGE_SIZE, change_page_range, &data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_direct_map_default_noflush(struct page *page)
|
int set_direct_map_default_noflush(struct page *page)
|
||||||
{
|
{
|
||||||
struct page_change_data data = {
|
pgprot_t set_mask = __pgprot(PTE_VALID | PTE_WRITE);
|
||||||
.set_mask = __pgprot(PTE_VALID | PTE_WRITE),
|
pgprot_t clear_mask = __pgprot(PTE_RDONLY);
|
||||||
.clear_mask = __pgprot(PTE_RDONLY),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!can_set_direct_map())
|
if (!can_set_direct_map())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return apply_to_page_range(&init_mm,
|
return update_range_prot((unsigned long)page_address(page),
|
||||||
(unsigned long)page_address(page),
|
PAGE_SIZE, set_mask, clear_mask);
|
||||||
PAGE_SIZE, change_page_range, &data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __set_memory_enc_dec(unsigned long addr,
|
static int __set_memory_enc_dec(unsigned long addr,
|
||||||
|
|
|
||||||
|
|
@ -245,10 +245,6 @@ SYM_FUNC_ALIAS(__pi_idmap_cpu_replace_ttbr1, idmap_cpu_replace_ttbr1)
|
||||||
*
|
*
|
||||||
* Called exactly once from stop_machine context by each CPU found during boot.
|
* Called exactly once from stop_machine context by each CPU found during boot.
|
||||||
*/
|
*/
|
||||||
.pushsection ".data", "aw", %progbits
|
|
||||||
SYM_DATA(__idmap_kpti_flag, .long 1)
|
|
||||||
.popsection
|
|
||||||
|
|
||||||
SYM_TYPED_FUNC_START(idmap_kpti_install_ng_mappings)
|
SYM_TYPED_FUNC_START(idmap_kpti_install_ng_mappings)
|
||||||
cpu .req w0
|
cpu .req w0
|
||||||
temp_pte .req x0
|
temp_pte .req x0
|
||||||
|
|
@ -273,7 +269,7 @@ SYM_TYPED_FUNC_START(idmap_kpti_install_ng_mappings)
|
||||||
|
|
||||||
mov x5, x3 // preserve temp_pte arg
|
mov x5, x3 // preserve temp_pte arg
|
||||||
mrs swapper_ttb, ttbr1_el1
|
mrs swapper_ttb, ttbr1_el1
|
||||||
adr_l flag_ptr, __idmap_kpti_flag
|
adr_l flag_ptr, idmap_kpti_bbml2_flag
|
||||||
|
|
||||||
cbnz cpu, __idmap_kpti_secondary
|
cbnz cpu, __idmap_kpti_secondary
|
||||||
|
|
||||||
|
|
@ -416,7 +412,25 @@ alternative_else_nop_endif
|
||||||
__idmap_kpti_secondary:
|
__idmap_kpti_secondary:
|
||||||
/* Uninstall swapper before surgery begins */
|
/* Uninstall swapper before surgery begins */
|
||||||
__idmap_cpu_set_reserved_ttbr1 x16, x17
|
__idmap_cpu_set_reserved_ttbr1 x16, x17
|
||||||
|
b scondary_cpu_wait
|
||||||
|
|
||||||
|
.unreq swapper_ttb
|
||||||
|
.unreq flag_ptr
|
||||||
|
SYM_FUNC_END(idmap_kpti_install_ng_mappings)
|
||||||
|
.popsection
|
||||||
|
#endif
|
||||||
|
|
||||||
|
.pushsection ".idmap.text", "a"
|
||||||
|
SYM_TYPED_FUNC_START(wait_linear_map_split_to_ptes)
|
||||||
|
/* Must be same registers as in idmap_kpti_install_ng_mappings */
|
||||||
|
swapper_ttb .req x3
|
||||||
|
flag_ptr .req x4
|
||||||
|
|
||||||
|
mrs swapper_ttb, ttbr1_el1
|
||||||
|
adr_l flag_ptr, idmap_kpti_bbml2_flag
|
||||||
|
__idmap_cpu_set_reserved_ttbr1 x16, x17
|
||||||
|
|
||||||
|
scondary_cpu_wait:
|
||||||
/* Increment the flag to let the boot CPU we're ready */
|
/* Increment the flag to let the boot CPU we're ready */
|
||||||
1: ldxr w16, [flag_ptr]
|
1: ldxr w16, [flag_ptr]
|
||||||
add w16, w16, #1
|
add w16, w16, #1
|
||||||
|
|
@ -436,9 +450,8 @@ __idmap_kpti_secondary:
|
||||||
|
|
||||||
.unreq swapper_ttb
|
.unreq swapper_ttb
|
||||||
.unreq flag_ptr
|
.unreq flag_ptr
|
||||||
SYM_FUNC_END(idmap_kpti_install_ng_mappings)
|
SYM_FUNC_END(wait_linear_map_split_to_ptes)
|
||||||
.popsection
|
.popsection
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* __cpu_setup
|
* __cpu_setup
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,13 @@ void note_page_flush(struct ptdump_state *pt_st)
|
||||||
note_page(pt_st, 0, -1, pte_val(pte_zero));
|
note_page(pt_st, 0, -1, pte_val(pte_zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void arm64_ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
|
||||||
|
{
|
||||||
|
static_branch_inc(&arm64_ptdump_lock_key);
|
||||||
|
ptdump_walk_pgd(st, mm, NULL);
|
||||||
|
static_branch_dec(&arm64_ptdump_lock_key);
|
||||||
|
}
|
||||||
|
|
||||||
void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
|
void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
|
||||||
{
|
{
|
||||||
unsigned long end = ~0UL;
|
unsigned long end = ~0UL;
|
||||||
|
|
@ -311,7 +318,7 @@ void ptdump_walk(struct seq_file *s, struct ptdump_info *info)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ptdump_walk_pgd(&st.ptdump, info->mm, NULL);
|
arm64_ptdump_walk_pgd(&st.ptdump, info->mm);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __init ptdump_initialize(void)
|
static void __init ptdump_initialize(void)
|
||||||
|
|
@ -353,7 +360,7 @@ bool ptdump_check_wx(void)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
|
arm64_ptdump_walk_pgd(&st.ptdump, &init_mm);
|
||||||
|
|
||||||
if (st.wx_pages || st.uxn_pages) {
|
if (st.wx_pages || st.uxn_pages) {
|
||||||
pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
|
pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n",
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,10 @@ $1 == "SysregFields" && block_current() == "Root" {
|
||||||
res1 = "UL(0)"
|
res1 = "UL(0)"
|
||||||
unkn = "UL(0)"
|
unkn = "UL(0)"
|
||||||
|
|
||||||
|
if (reg in defined_fields)
|
||||||
|
fatal("Duplicate SysregFields definition for " reg)
|
||||||
|
defined_fields[reg] = 1
|
||||||
|
|
||||||
next_bit = 63
|
next_bit = 63
|
||||||
|
|
||||||
next
|
next
|
||||||
|
|
@ -162,6 +166,10 @@ $1 == "Sysreg" && block_current() == "Root" {
|
||||||
res1 = "UL(0)"
|
res1 = "UL(0)"
|
||||||
unkn = "UL(0)"
|
unkn = "UL(0)"
|
||||||
|
|
||||||
|
if (reg in defined_regs)
|
||||||
|
fatal("Duplicate Sysreg definition for " reg)
|
||||||
|
defined_regs[reg] = 1
|
||||||
|
|
||||||
define("REG_" reg, "S" op0 "_" op1 "_C" crn "_C" crm "_" op2)
|
define("REG_" reg, "S" op0 "_" op1 "_C" crn "_C" crm "_" op2)
|
||||||
define("SYS_" reg, "sys_reg(" op0 ", " op1 ", " crn ", " crm ", " op2 ")")
|
define("SYS_" reg, "sys_reg(" op0 ", " op1 ", " crn ", " crm ", " op2 ")")
|
||||||
|
|
||||||
|
|
@ -284,6 +292,8 @@ $1 == "SignedEnum" && (block_current() == "Sysreg" || block_current() == "Sysreg
|
||||||
define_field(reg, field, msb, lsb)
|
define_field(reg, field, msb, lsb)
|
||||||
define_field_sign(reg, field, "true")
|
define_field_sign(reg, field, "true")
|
||||||
|
|
||||||
|
delete seen_enum_vals
|
||||||
|
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,6 +307,8 @@ $1 == "UnsignedEnum" && (block_current() == "Sysreg" || block_current() == "Sysr
|
||||||
define_field(reg, field, msb, lsb)
|
define_field(reg, field, msb, lsb)
|
||||||
define_field_sign(reg, field, "false")
|
define_field_sign(reg, field, "false")
|
||||||
|
|
||||||
|
delete seen_enum_vals
|
||||||
|
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,6 +321,8 @@ $1 == "Enum" && (block_current() == "Sysreg" || block_current() == "SysregFields
|
||||||
|
|
||||||
define_field(reg, field, msb, lsb)
|
define_field(reg, field, msb, lsb)
|
||||||
|
|
||||||
|
delete seen_enum_vals
|
||||||
|
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,6 +334,8 @@ $1 == "EndEnum" && block_current() == "Enum" {
|
||||||
lsb = null
|
lsb = null
|
||||||
print ""
|
print ""
|
||||||
|
|
||||||
|
delete seen_enum_vals
|
||||||
|
|
||||||
block_pop()
|
block_pop()
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
|
@ -329,6 +345,10 @@ $1 == "EndEnum" && block_current() == "Enum" {
|
||||||
val = $1
|
val = $1
|
||||||
name = $2
|
name = $2
|
||||||
|
|
||||||
|
if (val in seen_enum_vals)
|
||||||
|
fatal("Duplicate Enum value " val " for " name)
|
||||||
|
seen_enum_vals[val] = 1
|
||||||
|
|
||||||
define(reg "_" field "_" name, "UL(" val ")")
|
define(reg "_" field "_" name, "UL(" val ")")
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
# Mapping <name_EL1>
|
# Mapping <name_EL1>
|
||||||
# EndSysreg
|
# EndSysreg
|
||||||
|
|
||||||
# Where multiple system regsiters are not VHE aliases but share a
|
# Where multiple system registers are not VHE aliases but share a
|
||||||
# common layout, a SysregFields block can be used to describe the
|
# common layout, a SysregFields block can be used to describe the
|
||||||
# shared layout:
|
# shared layout:
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
#
|
#
|
||||||
# In general it is recommended that new enumeration items be named for the
|
# In general it is recommended that new enumeration items be named for the
|
||||||
# feature that introduces them (eg, FEAT_LS64_ACCDATA introduces enumeration
|
# feature that introduces them (eg, FEAT_LS64_ACCDATA introduces enumeration
|
||||||
# item ACCDATA) though it may be more taseful to do something else.
|
# item ACCDATA) though it may be more tasteful to do something else.
|
||||||
|
|
||||||
Sysreg OSDTRRX_EL1 2 0 0 0 2
|
Sysreg OSDTRRX_EL1 2 0 0 0 2
|
||||||
Res0 63:32
|
Res0 63:32
|
||||||
|
|
@ -474,7 +474,7 @@ EndEnum
|
||||||
Enum 7:4 Security
|
Enum 7:4 Security
|
||||||
0b0000 NI
|
0b0000 NI
|
||||||
0b0001 EL3
|
0b0001 EL3
|
||||||
0b0001 NSACR_RFR
|
0b0010 NSACR_RFR
|
||||||
EndEnum
|
EndEnum
|
||||||
UnsignedEnum 3:0 ProgMod
|
UnsignedEnum 3:0 ProgMod
|
||||||
0b0000 NI
|
0b0000 NI
|
||||||
|
|
@ -1693,7 +1693,7 @@ UnsignedEnum 43:40 TraceFilt
|
||||||
0b0000 NI
|
0b0000 NI
|
||||||
0b0001 IMP
|
0b0001 IMP
|
||||||
EndEnum
|
EndEnum
|
||||||
UnsignedEnum 39:36 DoubleLock
|
SignedEnum 39:36 DoubleLock
|
||||||
0b0000 IMP
|
0b0000 IMP
|
||||||
0b1111 NI
|
0b1111 NI
|
||||||
EndEnum
|
EndEnum
|
||||||
|
|
@ -2409,7 +2409,7 @@ UnsignedEnum 11:8 ASID2
|
||||||
0b0000 NI
|
0b0000 NI
|
||||||
0b0001 IMP
|
0b0001 IMP
|
||||||
EndEnum
|
EndEnum
|
||||||
SignedEnum 7:4 EIESB
|
UnsignedEnum 7:4 EIESB
|
||||||
0b0000 NI
|
0b0000 NI
|
||||||
0b0001 ToEL3
|
0b0001 ToEL3
|
||||||
0b0010 ToELx
|
0b0010 ToELx
|
||||||
|
|
@ -2528,10 +2528,6 @@ Field 17:16 ZEN
|
||||||
Res0 15:0
|
Res0 15:0
|
||||||
EndSysreg
|
EndSysreg
|
||||||
|
|
||||||
Sysreg CPACR_EL12 3 5 1 0 2
|
|
||||||
Mapping CPACR_EL1
|
|
||||||
EndSysreg
|
|
||||||
|
|
||||||
Sysreg CPACRALIAS_EL1 3 0 1 4 4
|
Sysreg CPACRALIAS_EL1 3 0 1 4 4
|
||||||
Mapping CPACR_EL1
|
Mapping CPACR_EL1
|
||||||
EndSysreg
|
EndSysreg
|
||||||
|
|
@ -2576,10 +2572,6 @@ Sysreg PFAR_EL12 3 5 6 0 5
|
||||||
Mapping PFAR_EL1
|
Mapping PFAR_EL1
|
||||||
EndSysreg
|
EndSysreg
|
||||||
|
|
||||||
Sysreg RCWSMASK_EL1 3 0 13 0 3
|
|
||||||
Field 63:0 RCWSMASK
|
|
||||||
EndSysreg
|
|
||||||
|
|
||||||
Sysreg SCTLR2_EL1 3 0 1 0 3
|
Sysreg SCTLR2_EL1 3 0 1 0 3
|
||||||
Res0 63:13
|
Res0 63:13
|
||||||
Field 12 CPTM0
|
Field 12 CPTM0
|
||||||
|
|
@ -2994,11 +2986,20 @@ Field 0 RND
|
||||||
EndSysreg
|
EndSysreg
|
||||||
|
|
||||||
Sysreg PMSFCR_EL1 3 0 9 9 4
|
Sysreg PMSFCR_EL1 3 0 9 9 4
|
||||||
Res0 63:19
|
Res0 63:53
|
||||||
|
Field 52 SIMDm
|
||||||
|
Field 51 FPm
|
||||||
|
Field 50 STm
|
||||||
|
Field 49 LDm
|
||||||
|
Field 48 Bm
|
||||||
|
Res0 47:21
|
||||||
|
Field 20 SIMD
|
||||||
|
Field 19 FP
|
||||||
Field 18 ST
|
Field 18 ST
|
||||||
Field 17 LD
|
Field 17 LD
|
||||||
Field 16 B
|
Field 16 B
|
||||||
Res0 15:4
|
Res0 15:5
|
||||||
|
Field 4 FDS
|
||||||
Field 3 FnE
|
Field 3 FnE
|
||||||
Field 2 FL
|
Field 2 FL
|
||||||
Field 1 FT
|
Field 1 FT
|
||||||
|
|
@ -4756,17 +4757,53 @@ Field 37 TBI0
|
||||||
Field 36 AS
|
Field 36 AS
|
||||||
Res0 35
|
Res0 35
|
||||||
Field 34:32 IPS
|
Field 34:32 IPS
|
||||||
Field 31:30 TG1
|
Enum 31:30 TG1
|
||||||
Field 29:28 SH1
|
0b01 16K
|
||||||
Field 27:26 ORGN1
|
0b10 4K
|
||||||
Field 25:24 IRGN1
|
0b11 64K
|
||||||
|
EndEnum
|
||||||
|
Enum 29:28 SH1
|
||||||
|
0b00 NONE
|
||||||
|
0b10 OUTER
|
||||||
|
0b11 INNER
|
||||||
|
EndEnum
|
||||||
|
Enum 27:26 ORGN1
|
||||||
|
0b00 NC
|
||||||
|
0b01 WBWA
|
||||||
|
0b10 WT
|
||||||
|
0b11 WBnWA
|
||||||
|
EndEnum
|
||||||
|
Enum 25:24 IRGN1
|
||||||
|
0b00 NC
|
||||||
|
0b01 WBWA
|
||||||
|
0b10 WT
|
||||||
|
0b11 WBnWA
|
||||||
|
EndEnum
|
||||||
Field 23 EPD1
|
Field 23 EPD1
|
||||||
Field 22 A1
|
Field 22 A1
|
||||||
Field 21:16 T1SZ
|
Field 21:16 T1SZ
|
||||||
Field 15:14 TG0
|
Enum 15:14 TG0
|
||||||
Field 13:12 SH0
|
0b00 4K
|
||||||
Field 11:10 ORGN0
|
0b01 64K
|
||||||
Field 9:8 IRGN0
|
0b10 16K
|
||||||
|
EndEnum
|
||||||
|
Enum 13:12 SH0
|
||||||
|
0b00 NONE
|
||||||
|
0b10 OUTER
|
||||||
|
0b11 INNER
|
||||||
|
EndEnum
|
||||||
|
Enum 11:10 ORGN0
|
||||||
|
0b00 NC
|
||||||
|
0b01 WBWA
|
||||||
|
0b10 WT
|
||||||
|
0b11 WBnWA
|
||||||
|
EndEnum
|
||||||
|
Enum 9:8 IRGN0
|
||||||
|
0b00 NC
|
||||||
|
0b01 WBWA
|
||||||
|
0b10 WT
|
||||||
|
0b11 WBnWA
|
||||||
|
EndEnum
|
||||||
Field 7 EPD0
|
Field 7 EPD0
|
||||||
Res0 6
|
Res0 6
|
||||||
Field 5:0 T0SZ
|
Field 5:0 T0SZ
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@
|
||||||
#include "coresight-self-hosted-trace.h"
|
#include "coresight-self-hosted-trace.h"
|
||||||
#include "coresight-trbe.h"
|
#include "coresight-trbe.h"
|
||||||
|
|
||||||
#define PERF_IDX2OFF(idx, buf) ((idx) % ((buf)->nr_pages << PAGE_SHIFT))
|
#define PERF_IDX2OFF(idx, buf) \
|
||||||
|
((idx) % ((unsigned long)(buf)->nr_pages << PAGE_SHIFT))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A padding packet that will help the user space tools
|
* A padding packet that will help the user space tools
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,15 @@ config FSL_IMX9_DDR_PMU
|
||||||
can give information about memory throughput and other related
|
can give information about memory throughput and other related
|
||||||
events.
|
events.
|
||||||
|
|
||||||
|
config FUJITSU_UNCORE_PMU
|
||||||
|
tristate "Fujitsu Uncore PMU"
|
||||||
|
depends on (ARM64 && ACPI) || (COMPILE_TEST && 64BIT)
|
||||||
|
help
|
||||||
|
Provides support for the Uncore performance monitor unit (PMU)
|
||||||
|
in Fujitsu processors.
|
||||||
|
Adds the Uncore PMU into the perf events subsystem for
|
||||||
|
monitoring Uncore events.
|
||||||
|
|
||||||
config QCOM_L2_PMU
|
config QCOM_L2_PMU
|
||||||
bool "Qualcomm Technologies L2-cache PMU"
|
bool "Qualcomm Technologies L2-cache PMU"
|
||||||
depends on ARCH_QCOM && ARM64 && ACPI
|
depends on ARCH_QCOM && ARM64 && ACPI
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ obj-$(CONFIG_ARM_XSCALE_PMU) += arm_xscale_pmu.o
|
||||||
obj-$(CONFIG_ARM_SMMU_V3_PMU) += arm_smmuv3_pmu.o
|
obj-$(CONFIG_ARM_SMMU_V3_PMU) += arm_smmuv3_pmu.o
|
||||||
obj-$(CONFIG_FSL_IMX8_DDR_PMU) += fsl_imx8_ddr_perf.o
|
obj-$(CONFIG_FSL_IMX8_DDR_PMU) += fsl_imx8_ddr_perf.o
|
||||||
obj-$(CONFIG_FSL_IMX9_DDR_PMU) += fsl_imx9_ddr_perf.o
|
obj-$(CONFIG_FSL_IMX9_DDR_PMU) += fsl_imx9_ddr_perf.o
|
||||||
|
obj-$(CONFIG_FUJITSU_UNCORE_PMU) += fujitsu_uncore_pmu.o
|
||||||
obj-$(CONFIG_HISI_PMU) += hisilicon/
|
obj-$(CONFIG_HISI_PMU) += hisilicon/
|
||||||
obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o
|
obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o
|
||||||
obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o
|
obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o
|
||||||
|
|
|
||||||
|
|
@ -565,7 +565,7 @@ module_param_named(pmu_poll_period_us, arm_ccn_pmu_poll_period_us, uint,
|
||||||
|
|
||||||
static ktime_t arm_ccn_pmu_timer_period(void)
|
static ktime_t arm_ccn_pmu_timer_period(void)
|
||||||
{
|
{
|
||||||
return ns_to_ktime((u64)arm_ccn_pmu_poll_period_us * 1000);
|
return us_to_ktime((u64)arm_ccn_pmu_poll_period_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
/* PMU registers occupy the 3rd 4KB page of each node's region */
|
/* PMU registers occupy the 3rd 4KB page of each node's region */
|
||||||
#define CMN_PMU_OFFSET 0x2000
|
#define CMN_PMU_OFFSET 0x2000
|
||||||
/* ...except when they don't :( */
|
/* ...except when they don't :( */
|
||||||
#define CMN_S3_DTM_OFFSET 0xa000
|
#define CMN_S3_R1_DTM_OFFSET 0xa000
|
||||||
#define CMN_S3_PMU_OFFSET 0xd900
|
#define CMN_S3_PMU_OFFSET 0xd900
|
||||||
|
|
||||||
/* For most nodes, this is all there is */
|
/* For most nodes, this is all there is */
|
||||||
|
|
@ -233,6 +233,9 @@ enum cmn_revision {
|
||||||
REV_CMN700_R1P0,
|
REV_CMN700_R1P0,
|
||||||
REV_CMN700_R2P0,
|
REV_CMN700_R2P0,
|
||||||
REV_CMN700_R3P0,
|
REV_CMN700_R3P0,
|
||||||
|
REV_CMNS3_R0P0 = 0,
|
||||||
|
REV_CMNS3_R0P1,
|
||||||
|
REV_CMNS3_R1P0,
|
||||||
REV_CI700_R0P0 = 0,
|
REV_CI700_R0P0 = 0,
|
||||||
REV_CI700_R1P0,
|
REV_CI700_R1P0,
|
||||||
REV_CI700_R2P0,
|
REV_CI700_R2P0,
|
||||||
|
|
@ -425,8 +428,8 @@ static enum cmn_model arm_cmn_model(const struct arm_cmn *cmn)
|
||||||
static int arm_cmn_pmu_offset(const struct arm_cmn *cmn, const struct arm_cmn_node *dn)
|
static int arm_cmn_pmu_offset(const struct arm_cmn *cmn, const struct arm_cmn_node *dn)
|
||||||
{
|
{
|
||||||
if (cmn->part == PART_CMN_S3) {
|
if (cmn->part == PART_CMN_S3) {
|
||||||
if (dn->type == CMN_TYPE_XP)
|
if (cmn->rev >= REV_CMNS3_R1P0 && dn->type == CMN_TYPE_XP)
|
||||||
return CMN_S3_DTM_OFFSET;
|
return CMN_S3_R1_DTM_OFFSET;
|
||||||
return CMN_S3_PMU_OFFSET;
|
return CMN_S3_PMU_OFFSET;
|
||||||
}
|
}
|
||||||
return CMN_PMU_OFFSET;
|
return CMN_PMU_OFFSET;
|
||||||
|
|
|
||||||
|
|
@ -978,6 +978,32 @@ static int armv8pmu_get_chain_idx(struct pmu_hw_events *cpuc,
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool armv8pmu_can_use_pmccntr(struct pmu_hw_events *cpuc,
|
||||||
|
struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
|
unsigned long evtype = hwc->config_base & ARMV8_PMU_EVTYPE_EVENT;
|
||||||
|
|
||||||
|
if (evtype != ARMV8_PMUV3_PERFCTR_CPU_CYCLES)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A CPU_CYCLES event with threshold counting cannot use PMCCNTR_EL0
|
||||||
|
* since it lacks threshold support.
|
||||||
|
*/
|
||||||
|
if (armv8pmu_event_get_threshold(&event->attr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PMCCNTR_EL0 is not affected by BRBE controls like BRBCR_ELx.FZP.
|
||||||
|
* So don't use it for branch events.
|
||||||
|
*/
|
||||||
|
if (has_branch_stack(event))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc,
|
static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc,
|
||||||
struct perf_event *event)
|
struct perf_event *event)
|
||||||
{
|
{
|
||||||
|
|
@ -986,8 +1012,7 @@ static int armv8pmu_get_event_idx(struct pmu_hw_events *cpuc,
|
||||||
unsigned long evtype = hwc->config_base & ARMV8_PMU_EVTYPE_EVENT;
|
unsigned long evtype = hwc->config_base & ARMV8_PMU_EVTYPE_EVENT;
|
||||||
|
|
||||||
/* Always prefer to place a cycle counter into the cycle counter. */
|
/* Always prefer to place a cycle counter into the cycle counter. */
|
||||||
if ((evtype == ARMV8_PMUV3_PERFCTR_CPU_CYCLES) &&
|
if (armv8pmu_can_use_pmccntr(cpuc, event)) {
|
||||||
!armv8pmu_event_get_threshold(&event->attr) && !has_branch_stack(event)) {
|
|
||||||
if (!test_and_set_bit(ARMV8_PMU_CYCLE_IDX, cpuc->used_mask))
|
if (!test_and_set_bit(ARMV8_PMU_CYCLE_IDX, cpuc->used_mask))
|
||||||
return ARMV8_PMU_CYCLE_IDX;
|
return ARMV8_PMU_CYCLE_IDX;
|
||||||
else if (armv8pmu_event_is_64bit(event) &&
|
else if (armv8pmu_event_is_64bit(event) &&
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,11 @@ struct arm_spe_pmu {
|
||||||
#define SPE_PMU_FEAT_ERND (1UL << 5)
|
#define SPE_PMU_FEAT_ERND (1UL << 5)
|
||||||
#define SPE_PMU_FEAT_INV_FILT_EVT (1UL << 6)
|
#define SPE_PMU_FEAT_INV_FILT_EVT (1UL << 6)
|
||||||
#define SPE_PMU_FEAT_DISCARD (1UL << 7)
|
#define SPE_PMU_FEAT_DISCARD (1UL << 7)
|
||||||
|
#define SPE_PMU_FEAT_EFT (1UL << 8)
|
||||||
#define SPE_PMU_FEAT_DEV_PROBED (1UL << 63)
|
#define SPE_PMU_FEAT_DEV_PROBED (1UL << 63)
|
||||||
u64 features;
|
u64 features;
|
||||||
|
|
||||||
|
u64 pmsevfr_res0;
|
||||||
u16 max_record_sz;
|
u16 max_record_sz;
|
||||||
u16 align;
|
u16 align;
|
||||||
struct perf_output_handle __percpu *handle;
|
struct perf_output_handle __percpu *handle;
|
||||||
|
|
@ -97,7 +99,8 @@ struct arm_spe_pmu {
|
||||||
#define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
|
#define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
|
||||||
|
|
||||||
/* Convert a free-running index from perf into an SPE buffer offset */
|
/* Convert a free-running index from perf into an SPE buffer offset */
|
||||||
#define PERF_IDX2OFF(idx, buf) ((idx) % ((buf)->nr_pages << PAGE_SHIFT))
|
#define PERF_IDX2OFF(idx, buf) \
|
||||||
|
((idx) % ((unsigned long)(buf)->nr_pages << PAGE_SHIFT))
|
||||||
|
|
||||||
/* Keep track of our dynamic hotplug state */
|
/* Keep track of our dynamic hotplug state */
|
||||||
static enum cpuhp_state arm_spe_pmu_online;
|
static enum cpuhp_state arm_spe_pmu_online;
|
||||||
|
|
@ -115,6 +118,7 @@ enum arm_spe_pmu_capabilities {
|
||||||
SPE_PMU_CAP_FEAT_MAX,
|
SPE_PMU_CAP_FEAT_MAX,
|
||||||
SPE_PMU_CAP_CNT_SZ = SPE_PMU_CAP_FEAT_MAX,
|
SPE_PMU_CAP_CNT_SZ = SPE_PMU_CAP_FEAT_MAX,
|
||||||
SPE_PMU_CAP_MIN_IVAL,
|
SPE_PMU_CAP_MIN_IVAL,
|
||||||
|
SPE_PMU_CAP_EVENT_FILTER,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int arm_spe_pmu_feat_caps[SPE_PMU_CAP_FEAT_MAX] = {
|
static int arm_spe_pmu_feat_caps[SPE_PMU_CAP_FEAT_MAX] = {
|
||||||
|
|
@ -122,7 +126,7 @@ static int arm_spe_pmu_feat_caps[SPE_PMU_CAP_FEAT_MAX] = {
|
||||||
[SPE_PMU_CAP_ERND] = SPE_PMU_FEAT_ERND,
|
[SPE_PMU_CAP_ERND] = SPE_PMU_FEAT_ERND,
|
||||||
};
|
};
|
||||||
|
|
||||||
static u32 arm_spe_pmu_cap_get(struct arm_spe_pmu *spe_pmu, int cap)
|
static u64 arm_spe_pmu_cap_get(struct arm_spe_pmu *spe_pmu, int cap)
|
||||||
{
|
{
|
||||||
if (cap < SPE_PMU_CAP_FEAT_MAX)
|
if (cap < SPE_PMU_CAP_FEAT_MAX)
|
||||||
return !!(spe_pmu->features & arm_spe_pmu_feat_caps[cap]);
|
return !!(spe_pmu->features & arm_spe_pmu_feat_caps[cap]);
|
||||||
|
|
@ -132,6 +136,8 @@ static u32 arm_spe_pmu_cap_get(struct arm_spe_pmu *spe_pmu, int cap)
|
||||||
return spe_pmu->counter_sz;
|
return spe_pmu->counter_sz;
|
||||||
case SPE_PMU_CAP_MIN_IVAL:
|
case SPE_PMU_CAP_MIN_IVAL:
|
||||||
return spe_pmu->min_period;
|
return spe_pmu->min_period;
|
||||||
|
case SPE_PMU_CAP_EVENT_FILTER:
|
||||||
|
return ~spe_pmu->pmsevfr_res0;
|
||||||
default:
|
default:
|
||||||
WARN(1, "unknown cap %d\n", cap);
|
WARN(1, "unknown cap %d\n", cap);
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +154,19 @@ static ssize_t arm_spe_pmu_cap_show(struct device *dev,
|
||||||
container_of(attr, struct dev_ext_attribute, attr);
|
container_of(attr, struct dev_ext_attribute, attr);
|
||||||
int cap = (long)ea->var;
|
int cap = (long)ea->var;
|
||||||
|
|
||||||
return sysfs_emit(buf, "%u\n", arm_spe_pmu_cap_get(spe_pmu, cap));
|
return sysfs_emit(buf, "%llu\n", arm_spe_pmu_cap_get(spe_pmu, cap));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t arm_spe_pmu_cap_show_hex(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct arm_spe_pmu *spe_pmu = dev_get_drvdata(dev);
|
||||||
|
struct dev_ext_attribute *ea =
|
||||||
|
container_of(attr, struct dev_ext_attribute, attr);
|
||||||
|
int cap = (long)ea->var;
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "0x%llx\n", arm_spe_pmu_cap_get(spe_pmu, cap));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SPE_EXT_ATTR_ENTRY(_name, _func, _var) \
|
#define SPE_EXT_ATTR_ENTRY(_name, _func, _var) \
|
||||||
|
|
@ -158,12 +176,15 @@ static ssize_t arm_spe_pmu_cap_show(struct device *dev,
|
||||||
|
|
||||||
#define SPE_CAP_EXT_ATTR_ENTRY(_name, _var) \
|
#define SPE_CAP_EXT_ATTR_ENTRY(_name, _var) \
|
||||||
SPE_EXT_ATTR_ENTRY(_name, arm_spe_pmu_cap_show, _var)
|
SPE_EXT_ATTR_ENTRY(_name, arm_spe_pmu_cap_show, _var)
|
||||||
|
#define SPE_CAP_EXT_ATTR_ENTRY_HEX(_name, _var) \
|
||||||
|
SPE_EXT_ATTR_ENTRY(_name, arm_spe_pmu_cap_show_hex, _var)
|
||||||
|
|
||||||
static struct attribute *arm_spe_pmu_cap_attr[] = {
|
static struct attribute *arm_spe_pmu_cap_attr[] = {
|
||||||
SPE_CAP_EXT_ATTR_ENTRY(arch_inst, SPE_PMU_CAP_ARCH_INST),
|
SPE_CAP_EXT_ATTR_ENTRY(arch_inst, SPE_PMU_CAP_ARCH_INST),
|
||||||
SPE_CAP_EXT_ATTR_ENTRY(ernd, SPE_PMU_CAP_ERND),
|
SPE_CAP_EXT_ATTR_ENTRY(ernd, SPE_PMU_CAP_ERND),
|
||||||
SPE_CAP_EXT_ATTR_ENTRY(count_size, SPE_PMU_CAP_CNT_SZ),
|
SPE_CAP_EXT_ATTR_ENTRY(count_size, SPE_PMU_CAP_CNT_SZ),
|
||||||
SPE_CAP_EXT_ATTR_ENTRY(min_interval, SPE_PMU_CAP_MIN_IVAL),
|
SPE_CAP_EXT_ATTR_ENTRY(min_interval, SPE_PMU_CAP_MIN_IVAL),
|
||||||
|
SPE_CAP_EXT_ATTR_ENTRY_HEX(event_filter, SPE_PMU_CAP_EVENT_FILTER),
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -197,6 +218,27 @@ static const struct attribute_group arm_spe_pmu_cap_group = {
|
||||||
#define ATTR_CFG_FLD_discard_CFG config /* PMBLIMITR_EL1.FM = DISCARD */
|
#define ATTR_CFG_FLD_discard_CFG config /* PMBLIMITR_EL1.FM = DISCARD */
|
||||||
#define ATTR_CFG_FLD_discard_LO 35
|
#define ATTR_CFG_FLD_discard_LO 35
|
||||||
#define ATTR_CFG_FLD_discard_HI 35
|
#define ATTR_CFG_FLD_discard_HI 35
|
||||||
|
#define ATTR_CFG_FLD_branch_filter_mask_CFG config /* PMSFCR_EL1.Bm */
|
||||||
|
#define ATTR_CFG_FLD_branch_filter_mask_LO 36
|
||||||
|
#define ATTR_CFG_FLD_branch_filter_mask_HI 36
|
||||||
|
#define ATTR_CFG_FLD_load_filter_mask_CFG config /* PMSFCR_EL1.LDm */
|
||||||
|
#define ATTR_CFG_FLD_load_filter_mask_LO 37
|
||||||
|
#define ATTR_CFG_FLD_load_filter_mask_HI 37
|
||||||
|
#define ATTR_CFG_FLD_store_filter_mask_CFG config /* PMSFCR_EL1.STm */
|
||||||
|
#define ATTR_CFG_FLD_store_filter_mask_LO 38
|
||||||
|
#define ATTR_CFG_FLD_store_filter_mask_HI 38
|
||||||
|
#define ATTR_CFG_FLD_simd_filter_CFG config /* PMSFCR_EL1.SIMD */
|
||||||
|
#define ATTR_CFG_FLD_simd_filter_LO 39
|
||||||
|
#define ATTR_CFG_FLD_simd_filter_HI 39
|
||||||
|
#define ATTR_CFG_FLD_simd_filter_mask_CFG config /* PMSFCR_EL1.SIMDm */
|
||||||
|
#define ATTR_CFG_FLD_simd_filter_mask_LO 40
|
||||||
|
#define ATTR_CFG_FLD_simd_filter_mask_HI 40
|
||||||
|
#define ATTR_CFG_FLD_float_filter_CFG config /* PMSFCR_EL1.FP */
|
||||||
|
#define ATTR_CFG_FLD_float_filter_LO 41
|
||||||
|
#define ATTR_CFG_FLD_float_filter_HI 41
|
||||||
|
#define ATTR_CFG_FLD_float_filter_mask_CFG config /* PMSFCR_EL1.FPm */
|
||||||
|
#define ATTR_CFG_FLD_float_filter_mask_LO 42
|
||||||
|
#define ATTR_CFG_FLD_float_filter_mask_HI 42
|
||||||
|
|
||||||
#define ATTR_CFG_FLD_event_filter_CFG config1 /* PMSEVFR_EL1 */
|
#define ATTR_CFG_FLD_event_filter_CFG config1 /* PMSEVFR_EL1 */
|
||||||
#define ATTR_CFG_FLD_event_filter_LO 0
|
#define ATTR_CFG_FLD_event_filter_LO 0
|
||||||
|
|
@ -215,8 +257,15 @@ GEN_PMU_FORMAT_ATTR(pa_enable);
|
||||||
GEN_PMU_FORMAT_ATTR(pct_enable);
|
GEN_PMU_FORMAT_ATTR(pct_enable);
|
||||||
GEN_PMU_FORMAT_ATTR(jitter);
|
GEN_PMU_FORMAT_ATTR(jitter);
|
||||||
GEN_PMU_FORMAT_ATTR(branch_filter);
|
GEN_PMU_FORMAT_ATTR(branch_filter);
|
||||||
|
GEN_PMU_FORMAT_ATTR(branch_filter_mask);
|
||||||
GEN_PMU_FORMAT_ATTR(load_filter);
|
GEN_PMU_FORMAT_ATTR(load_filter);
|
||||||
|
GEN_PMU_FORMAT_ATTR(load_filter_mask);
|
||||||
GEN_PMU_FORMAT_ATTR(store_filter);
|
GEN_PMU_FORMAT_ATTR(store_filter);
|
||||||
|
GEN_PMU_FORMAT_ATTR(store_filter_mask);
|
||||||
|
GEN_PMU_FORMAT_ATTR(simd_filter);
|
||||||
|
GEN_PMU_FORMAT_ATTR(simd_filter_mask);
|
||||||
|
GEN_PMU_FORMAT_ATTR(float_filter);
|
||||||
|
GEN_PMU_FORMAT_ATTR(float_filter_mask);
|
||||||
GEN_PMU_FORMAT_ATTR(event_filter);
|
GEN_PMU_FORMAT_ATTR(event_filter);
|
||||||
GEN_PMU_FORMAT_ATTR(inv_event_filter);
|
GEN_PMU_FORMAT_ATTR(inv_event_filter);
|
||||||
GEN_PMU_FORMAT_ATTR(min_latency);
|
GEN_PMU_FORMAT_ATTR(min_latency);
|
||||||
|
|
@ -228,8 +277,15 @@ static struct attribute *arm_spe_pmu_formats_attr[] = {
|
||||||
&format_attr_pct_enable.attr,
|
&format_attr_pct_enable.attr,
|
||||||
&format_attr_jitter.attr,
|
&format_attr_jitter.attr,
|
||||||
&format_attr_branch_filter.attr,
|
&format_attr_branch_filter.attr,
|
||||||
|
&format_attr_branch_filter_mask.attr,
|
||||||
&format_attr_load_filter.attr,
|
&format_attr_load_filter.attr,
|
||||||
|
&format_attr_load_filter_mask.attr,
|
||||||
&format_attr_store_filter.attr,
|
&format_attr_store_filter.attr,
|
||||||
|
&format_attr_store_filter_mask.attr,
|
||||||
|
&format_attr_simd_filter.attr,
|
||||||
|
&format_attr_simd_filter_mask.attr,
|
||||||
|
&format_attr_float_filter.attr,
|
||||||
|
&format_attr_float_filter_mask.attr,
|
||||||
&format_attr_event_filter.attr,
|
&format_attr_event_filter.attr,
|
||||||
&format_attr_inv_event_filter.attr,
|
&format_attr_inv_event_filter.attr,
|
||||||
&format_attr_min_latency.attr,
|
&format_attr_min_latency.attr,
|
||||||
|
|
@ -250,6 +306,16 @@ static umode_t arm_spe_pmu_format_attr_is_visible(struct kobject *kobj,
|
||||||
if (attr == &format_attr_inv_event_filter.attr && !(spe_pmu->features & SPE_PMU_FEAT_INV_FILT_EVT))
|
if (attr == &format_attr_inv_event_filter.attr && !(spe_pmu->features & SPE_PMU_FEAT_INV_FILT_EVT))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if ((attr == &format_attr_branch_filter_mask.attr ||
|
||||||
|
attr == &format_attr_load_filter_mask.attr ||
|
||||||
|
attr == &format_attr_store_filter_mask.attr ||
|
||||||
|
attr == &format_attr_simd_filter.attr ||
|
||||||
|
attr == &format_attr_simd_filter_mask.attr ||
|
||||||
|
attr == &format_attr_float_filter.attr ||
|
||||||
|
attr == &format_attr_float_filter_mask.attr) &&
|
||||||
|
!(spe_pmu->features & SPE_PMU_FEAT_EFT))
|
||||||
|
return 0;
|
||||||
|
|
||||||
return attr->mode;
|
return attr->mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,8 +411,15 @@ static u64 arm_spe_event_to_pmsfcr(struct perf_event *event)
|
||||||
u64 reg = 0;
|
u64 reg = 0;
|
||||||
|
|
||||||
reg |= FIELD_PREP(PMSFCR_EL1_LD, ATTR_CFG_GET_FLD(attr, load_filter));
|
reg |= FIELD_PREP(PMSFCR_EL1_LD, ATTR_CFG_GET_FLD(attr, load_filter));
|
||||||
|
reg |= FIELD_PREP(PMSFCR_EL1_LDm, ATTR_CFG_GET_FLD(attr, load_filter_mask));
|
||||||
reg |= FIELD_PREP(PMSFCR_EL1_ST, ATTR_CFG_GET_FLD(attr, store_filter));
|
reg |= FIELD_PREP(PMSFCR_EL1_ST, ATTR_CFG_GET_FLD(attr, store_filter));
|
||||||
|
reg |= FIELD_PREP(PMSFCR_EL1_STm, ATTR_CFG_GET_FLD(attr, store_filter_mask));
|
||||||
reg |= FIELD_PREP(PMSFCR_EL1_B, ATTR_CFG_GET_FLD(attr, branch_filter));
|
reg |= FIELD_PREP(PMSFCR_EL1_B, ATTR_CFG_GET_FLD(attr, branch_filter));
|
||||||
|
reg |= FIELD_PREP(PMSFCR_EL1_Bm, ATTR_CFG_GET_FLD(attr, branch_filter_mask));
|
||||||
|
reg |= FIELD_PREP(PMSFCR_EL1_SIMD, ATTR_CFG_GET_FLD(attr, simd_filter));
|
||||||
|
reg |= FIELD_PREP(PMSFCR_EL1_SIMDm, ATTR_CFG_GET_FLD(attr, simd_filter_mask));
|
||||||
|
reg |= FIELD_PREP(PMSFCR_EL1_FP, ATTR_CFG_GET_FLD(attr, float_filter));
|
||||||
|
reg |= FIELD_PREP(PMSFCR_EL1_FPm, ATTR_CFG_GET_FLD(attr, float_filter_mask));
|
||||||
|
|
||||||
if (reg)
|
if (reg)
|
||||||
reg |= PMSFCR_EL1_FT;
|
reg |= PMSFCR_EL1_FT;
|
||||||
|
|
@ -697,20 +770,6 @@ static irqreturn_t arm_spe_pmu_irq_handler(int irq, void *dev)
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u64 arm_spe_pmsevfr_res0(u16 pmsver)
|
|
||||||
{
|
|
||||||
switch (pmsver) {
|
|
||||||
case ID_AA64DFR0_EL1_PMSVer_IMP:
|
|
||||||
return PMSEVFR_EL1_RES0_IMP;
|
|
||||||
case ID_AA64DFR0_EL1_PMSVer_V1P1:
|
|
||||||
return PMSEVFR_EL1_RES0_V1P1;
|
|
||||||
case ID_AA64DFR0_EL1_PMSVer_V1P2:
|
|
||||||
/* Return the highest version we support in default */
|
|
||||||
default:
|
|
||||||
return PMSEVFR_EL1_RES0_V1P2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Perf callbacks */
|
/* Perf callbacks */
|
||||||
static int arm_spe_pmu_event_init(struct perf_event *event)
|
static int arm_spe_pmu_event_init(struct perf_event *event)
|
||||||
{
|
{
|
||||||
|
|
@ -726,10 +785,10 @@ static int arm_spe_pmu_event_init(struct perf_event *event)
|
||||||
!cpumask_test_cpu(event->cpu, &spe_pmu->supported_cpus))
|
!cpumask_test_cpu(event->cpu, &spe_pmu->supported_cpus))
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
|
|
||||||
if (arm_spe_event_to_pmsevfr(event) & arm_spe_pmsevfr_res0(spe_pmu->pmsver))
|
if (arm_spe_event_to_pmsevfr(event) & spe_pmu->pmsevfr_res0)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
if (arm_spe_event_to_pmsnevfr(event) & arm_spe_pmsevfr_res0(spe_pmu->pmsver))
|
if (arm_spe_event_to_pmsnevfr(event) & spe_pmu->pmsevfr_res0)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
if (attr->exclude_idle)
|
if (attr->exclude_idle)
|
||||||
|
|
@ -762,6 +821,16 @@ static int arm_spe_pmu_event_init(struct perf_event *event)
|
||||||
!(spe_pmu->features & SPE_PMU_FEAT_FILT_LAT))
|
!(spe_pmu->features & SPE_PMU_FEAT_FILT_LAT))
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
if ((FIELD_GET(PMSFCR_EL1_LDm, reg) ||
|
||||||
|
FIELD_GET(PMSFCR_EL1_STm, reg) ||
|
||||||
|
FIELD_GET(PMSFCR_EL1_Bm, reg) ||
|
||||||
|
FIELD_GET(PMSFCR_EL1_SIMD, reg) ||
|
||||||
|
FIELD_GET(PMSFCR_EL1_SIMDm, reg) ||
|
||||||
|
FIELD_GET(PMSFCR_EL1_FP, reg) ||
|
||||||
|
FIELD_GET(PMSFCR_EL1_FPm, reg)) &&
|
||||||
|
!(spe_pmu->features & SPE_PMU_FEAT_EFT))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
if (ATTR_CFG_GET_FLD(&event->attr, discard) &&
|
if (ATTR_CFG_GET_FLD(&event->attr, discard) &&
|
||||||
!(spe_pmu->features & SPE_PMU_FEAT_DISCARD))
|
!(spe_pmu->features & SPE_PMU_FEAT_DISCARD))
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
@ -1053,6 +1122,9 @@ static void __arm_spe_pmu_dev_probe(void *info)
|
||||||
if (spe_pmu->pmsver >= ID_AA64DFR0_EL1_PMSVer_V1P2)
|
if (spe_pmu->pmsver >= ID_AA64DFR0_EL1_PMSVer_V1P2)
|
||||||
spe_pmu->features |= SPE_PMU_FEAT_DISCARD;
|
spe_pmu->features |= SPE_PMU_FEAT_DISCARD;
|
||||||
|
|
||||||
|
if (FIELD_GET(PMSIDR_EL1_EFT, reg))
|
||||||
|
spe_pmu->features |= SPE_PMU_FEAT_EFT;
|
||||||
|
|
||||||
/* This field has a spaced out encoding, so just use a look-up */
|
/* This field has a spaced out encoding, so just use a look-up */
|
||||||
fld = FIELD_GET(PMSIDR_EL1_INTERVAL, reg);
|
fld = FIELD_GET(PMSIDR_EL1_INTERVAL, reg);
|
||||||
switch (fld) {
|
switch (fld) {
|
||||||
|
|
@ -1107,6 +1179,10 @@ static void __arm_spe_pmu_dev_probe(void *info)
|
||||||
spe_pmu->counter_sz = 16;
|
spe_pmu->counter_sz = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Write all 1s and then read back. Unsupported filter bits are RAZ/WI. */
|
||||||
|
write_sysreg_s(U64_MAX, SYS_PMSEVFR_EL1);
|
||||||
|
spe_pmu->pmsevfr_res0 = ~read_sysreg_s(SYS_PMSEVFR_EL1);
|
||||||
|
|
||||||
dev_info(dev,
|
dev_info(dev,
|
||||||
"probed SPEv1.%d for CPUs %*pbl [max_record_sz %u, align %u, features 0x%llx]\n",
|
"probed SPEv1.%d for CPUs %*pbl [max_record_sz %u, align %u, features 0x%llx]\n",
|
||||||
spe_pmu->pmsver - 1, cpumask_pr_args(&spe_pmu->supported_cpus),
|
spe_pmu->pmsver - 1, cpumask_pr_args(&spe_pmu->supported_cpus),
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,10 @@
|
||||||
#define DWC_PCIE_EVENT_CLEAR GENMASK(1, 0)
|
#define DWC_PCIE_EVENT_CLEAR GENMASK(1, 0)
|
||||||
#define DWC_PCIE_EVENT_PER_CLEAR 0x1
|
#define DWC_PCIE_EVENT_PER_CLEAR 0x1
|
||||||
|
|
||||||
|
/* Event Selection Field has two subfields */
|
||||||
|
#define DWC_PCIE_CNT_EVENT_SEL_GROUP GENMASK(11, 8)
|
||||||
|
#define DWC_PCIE_CNT_EVENT_SEL_EVID GENMASK(7, 0)
|
||||||
|
|
||||||
#define DWC_PCIE_EVENT_CNT_DATA 0xC
|
#define DWC_PCIE_EVENT_CNT_DATA 0xC
|
||||||
|
|
||||||
#define DWC_PCIE_TIME_BASED_ANAL_CTL 0x10
|
#define DWC_PCIE_TIME_BASED_ANAL_CTL 0x10
|
||||||
|
|
@ -73,6 +77,10 @@ enum dwc_pcie_event_type {
|
||||||
DWC_PCIE_EVENT_TYPE_MAX,
|
DWC_PCIE_EVENT_TYPE_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define DWC_PCIE_LANE_GROUP_6 6
|
||||||
|
#define DWC_PCIE_LANE_GROUP_7 7
|
||||||
|
#define DWC_PCIE_LANE_MAX_EVENTS_PER_GROUP 256
|
||||||
|
|
||||||
#define DWC_PCIE_LANE_EVENT_MAX_PERIOD GENMASK_ULL(31, 0)
|
#define DWC_PCIE_LANE_EVENT_MAX_PERIOD GENMASK_ULL(31, 0)
|
||||||
#define DWC_PCIE_MAX_PERIOD GENMASK_ULL(63, 0)
|
#define DWC_PCIE_MAX_PERIOD GENMASK_ULL(63, 0)
|
||||||
|
|
||||||
|
|
@ -82,8 +90,11 @@ struct dwc_pcie_pmu {
|
||||||
u16 ras_des_offset;
|
u16 ras_des_offset;
|
||||||
u32 nr_lanes;
|
u32 nr_lanes;
|
||||||
|
|
||||||
|
/* Groups #6 and #7 */
|
||||||
|
DECLARE_BITMAP(lane_events, 2 * DWC_PCIE_LANE_MAX_EVENTS_PER_GROUP);
|
||||||
|
struct perf_event *time_based_event;
|
||||||
|
|
||||||
struct hlist_node cpuhp_node;
|
struct hlist_node cpuhp_node;
|
||||||
struct perf_event *event[DWC_PCIE_EVENT_TYPE_MAX];
|
|
||||||
int on_cpu;
|
int on_cpu;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -246,19 +257,26 @@ static const struct attribute_group *dwc_pcie_attr_groups[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static void dwc_pcie_pmu_lane_event_enable(struct dwc_pcie_pmu *pcie_pmu,
|
static void dwc_pcie_pmu_lane_event_enable(struct dwc_pcie_pmu *pcie_pmu,
|
||||||
|
struct perf_event *event,
|
||||||
bool enable)
|
bool enable)
|
||||||
{
|
{
|
||||||
struct pci_dev *pdev = pcie_pmu->pdev;
|
struct pci_dev *pdev = pcie_pmu->pdev;
|
||||||
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
||||||
|
int event_id = DWC_PCIE_EVENT_ID(event);
|
||||||
|
int lane = DWC_PCIE_EVENT_LANE(event);
|
||||||
|
u32 ctrl;
|
||||||
|
|
||||||
|
ctrl = FIELD_PREP(DWC_PCIE_CNT_EVENT_SEL, event_id) |
|
||||||
|
FIELD_PREP(DWC_PCIE_CNT_LANE_SEL, lane) |
|
||||||
|
FIELD_PREP(DWC_PCIE_EVENT_CLEAR, DWC_PCIE_EVENT_PER_CLEAR);
|
||||||
|
|
||||||
if (enable)
|
if (enable)
|
||||||
pci_clear_and_set_config_dword(pdev,
|
ctrl |= FIELD_PREP(DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_ON);
|
||||||
ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
|
||||||
DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_ON);
|
|
||||||
else
|
else
|
||||||
pci_clear_and_set_config_dword(pdev,
|
ctrl |= FIELD_PREP(DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_OFF);
|
||||||
ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
|
||||||
DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_OFF);
|
pci_write_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
||||||
|
ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dwc_pcie_pmu_time_based_event_enable(struct dwc_pcie_pmu *pcie_pmu,
|
static void dwc_pcie_pmu_time_based_event_enable(struct dwc_pcie_pmu *pcie_pmu,
|
||||||
|
|
@ -276,11 +294,22 @@ static u64 dwc_pcie_pmu_read_lane_event_counter(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
||||||
struct pci_dev *pdev = pcie_pmu->pdev;
|
struct pci_dev *pdev = pcie_pmu->pdev;
|
||||||
|
int event_id = DWC_PCIE_EVENT_ID(event);
|
||||||
|
int lane = DWC_PCIE_EVENT_LANE(event);
|
||||||
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
||||||
u32 val;
|
u32 val, ctrl;
|
||||||
|
|
||||||
|
ctrl = FIELD_PREP(DWC_PCIE_CNT_EVENT_SEL, event_id) |
|
||||||
|
FIELD_PREP(DWC_PCIE_CNT_LANE_SEL, lane) |
|
||||||
|
FIELD_PREP(DWC_PCIE_CNT_ENABLE, DWC_PCIE_PER_EVENT_ON);
|
||||||
|
pci_write_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
||||||
|
ctrl);
|
||||||
pci_read_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_DATA, &val);
|
pci_read_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_DATA, &val);
|
||||||
|
|
||||||
|
ctrl |= FIELD_PREP(DWC_PCIE_EVENT_CLEAR, DWC_PCIE_EVENT_PER_CLEAR);
|
||||||
|
pci_write_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
||||||
|
ctrl);
|
||||||
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,26 +358,77 @@ static void dwc_pcie_pmu_event_update(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct hw_perf_event *hwc = &event->hw;
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
|
||||||
u64 delta, prev, now = 0;
|
u64 delta, prev, now;
|
||||||
|
|
||||||
|
if (type == DWC_PCIE_LANE_EVENT) {
|
||||||
|
now = dwc_pcie_pmu_read_lane_event_counter(event) &
|
||||||
|
DWC_PCIE_LANE_EVENT_MAX_PERIOD;
|
||||||
|
local64_add(now, &event->count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
prev = local64_read(&hwc->prev_count);
|
prev = local64_read(&hwc->prev_count);
|
||||||
|
now = dwc_pcie_pmu_read_time_based_counter(event);
|
||||||
if (type == DWC_PCIE_LANE_EVENT)
|
|
||||||
now = dwc_pcie_pmu_read_lane_event_counter(event);
|
|
||||||
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
|
||||||
now = dwc_pcie_pmu_read_time_based_counter(event);
|
|
||||||
|
|
||||||
} while (local64_cmpxchg(&hwc->prev_count, prev, now) != prev);
|
} while (local64_cmpxchg(&hwc->prev_count, prev, now) != prev);
|
||||||
|
|
||||||
delta = (now - prev) & DWC_PCIE_MAX_PERIOD;
|
delta = (now - prev) & DWC_PCIE_MAX_PERIOD;
|
||||||
/* 32-bit counter for Lane Event Counting */
|
|
||||||
if (type == DWC_PCIE_LANE_EVENT)
|
|
||||||
delta &= DWC_PCIE_LANE_EVENT_MAX_PERIOD;
|
|
||||||
|
|
||||||
local64_add(delta, &event->count);
|
local64_add(delta, &event->count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int dwc_pcie_pmu_validate_add_lane_event(struct perf_event *event,
|
||||||
|
unsigned long val_lane_events[])
|
||||||
|
{
|
||||||
|
int event_id, event_nr, group;
|
||||||
|
|
||||||
|
event_id = DWC_PCIE_EVENT_ID(event);
|
||||||
|
event_nr = FIELD_GET(DWC_PCIE_CNT_EVENT_SEL_EVID, event_id);
|
||||||
|
group = FIELD_GET(DWC_PCIE_CNT_EVENT_SEL_GROUP, event_id);
|
||||||
|
|
||||||
|
if (group != DWC_PCIE_LANE_GROUP_6 && group != DWC_PCIE_LANE_GROUP_7)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
group -= DWC_PCIE_LANE_GROUP_6;
|
||||||
|
|
||||||
|
if (test_and_set_bit(group * DWC_PCIE_LANE_MAX_EVENTS_PER_GROUP + event_nr,
|
||||||
|
val_lane_events))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dwc_pcie_pmu_validate_group(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct perf_event *sibling, *leader = event->group_leader;
|
||||||
|
DECLARE_BITMAP(val_lane_events, 2 * DWC_PCIE_LANE_MAX_EVENTS_PER_GROUP);
|
||||||
|
bool time_event = false;
|
||||||
|
int type;
|
||||||
|
|
||||||
|
type = DWC_PCIE_EVENT_TYPE(leader);
|
||||||
|
if (type == DWC_PCIE_TIME_BASE_EVENT)
|
||||||
|
time_event = true;
|
||||||
|
else
|
||||||
|
if (dwc_pcie_pmu_validate_add_lane_event(leader, val_lane_events))
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
|
for_each_sibling_event(sibling, leader) {
|
||||||
|
type = DWC_PCIE_EVENT_TYPE(sibling);
|
||||||
|
if (type == DWC_PCIE_TIME_BASE_EVENT) {
|
||||||
|
if (time_event)
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
|
time_event = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dwc_pcie_pmu_validate_add_lane_event(sibling, val_lane_events))
|
||||||
|
return -ENOSPC;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int dwc_pcie_pmu_event_init(struct perf_event *event)
|
static int dwc_pcie_pmu_event_init(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
|
||||||
|
|
@ -367,10 +447,6 @@ static int dwc_pcie_pmu_event_init(struct perf_event *event)
|
||||||
if (event->cpu < 0 || event->attach_state & PERF_ATTACH_TASK)
|
if (event->cpu < 0 || event->attach_state & PERF_ATTACH_TASK)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (event->group_leader != event &&
|
|
||||||
!is_software_event(event->group_leader))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
for_each_sibling_event(sibling, event->group_leader) {
|
for_each_sibling_event(sibling, event->group_leader) {
|
||||||
if (sibling->pmu != event->pmu && !is_software_event(sibling))
|
if (sibling->pmu != event->pmu && !is_software_event(sibling))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
@ -385,6 +461,9 @@ static int dwc_pcie_pmu_event_init(struct perf_event *event)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dwc_pcie_pmu_validate_group(event))
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
event->cpu = pcie_pmu->on_cpu;
|
event->cpu = pcie_pmu->on_cpu;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -400,7 +479,7 @@ static void dwc_pcie_pmu_event_start(struct perf_event *event, int flags)
|
||||||
local64_set(&hwc->prev_count, 0);
|
local64_set(&hwc->prev_count, 0);
|
||||||
|
|
||||||
if (type == DWC_PCIE_LANE_EVENT)
|
if (type == DWC_PCIE_LANE_EVENT)
|
||||||
dwc_pcie_pmu_lane_event_enable(pcie_pmu, true);
|
dwc_pcie_pmu_lane_event_enable(pcie_pmu, event, true);
|
||||||
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
||||||
dwc_pcie_pmu_time_based_event_enable(pcie_pmu, true);
|
dwc_pcie_pmu_time_based_event_enable(pcie_pmu, true);
|
||||||
}
|
}
|
||||||
|
|
@ -414,12 +493,13 @@ static void dwc_pcie_pmu_event_stop(struct perf_event *event, int flags)
|
||||||
if (event->hw.state & PERF_HES_STOPPED)
|
if (event->hw.state & PERF_HES_STOPPED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
dwc_pcie_pmu_event_update(event);
|
||||||
|
|
||||||
if (type == DWC_PCIE_LANE_EVENT)
|
if (type == DWC_PCIE_LANE_EVENT)
|
||||||
dwc_pcie_pmu_lane_event_enable(pcie_pmu, false);
|
dwc_pcie_pmu_lane_event_enable(pcie_pmu, event, false);
|
||||||
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
else if (type == DWC_PCIE_TIME_BASE_EVENT)
|
||||||
dwc_pcie_pmu_time_based_event_enable(pcie_pmu, false);
|
dwc_pcie_pmu_time_based_event_enable(pcie_pmu, false);
|
||||||
|
|
||||||
dwc_pcie_pmu_event_update(event);
|
|
||||||
hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -434,14 +514,17 @@ static int dwc_pcie_pmu_event_add(struct perf_event *event, int flags)
|
||||||
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
u16 ras_des_offset = pcie_pmu->ras_des_offset;
|
||||||
u32 ctrl;
|
u32 ctrl;
|
||||||
|
|
||||||
/* one counter for each type and it is in use */
|
|
||||||
if (pcie_pmu->event[type])
|
|
||||||
return -ENOSPC;
|
|
||||||
|
|
||||||
pcie_pmu->event[type] = event;
|
|
||||||
hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
||||||
|
|
||||||
if (type == DWC_PCIE_LANE_EVENT) {
|
if (type == DWC_PCIE_LANE_EVENT) {
|
||||||
|
int event_nr = FIELD_GET(DWC_PCIE_CNT_EVENT_SEL_EVID, event_id);
|
||||||
|
int group = FIELD_GET(DWC_PCIE_CNT_EVENT_SEL_GROUP, event_id) -
|
||||||
|
DWC_PCIE_LANE_GROUP_6;
|
||||||
|
|
||||||
|
if (test_and_set_bit(group * DWC_PCIE_LANE_MAX_EVENTS_PER_GROUP + event_nr,
|
||||||
|
pcie_pmu->lane_events))
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
/* EVENT_COUNTER_DATA_REG needs clear manually */
|
/* EVENT_COUNTER_DATA_REG needs clear manually */
|
||||||
ctrl = FIELD_PREP(DWC_PCIE_CNT_EVENT_SEL, event_id) |
|
ctrl = FIELD_PREP(DWC_PCIE_CNT_EVENT_SEL, event_id) |
|
||||||
FIELD_PREP(DWC_PCIE_CNT_LANE_SEL, lane) |
|
FIELD_PREP(DWC_PCIE_CNT_LANE_SEL, lane) |
|
||||||
|
|
@ -450,6 +533,11 @@ static int dwc_pcie_pmu_event_add(struct perf_event *event, int flags)
|
||||||
pci_write_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
pci_write_config_dword(pdev, ras_des_offset + DWC_PCIE_EVENT_CNT_CTL,
|
||||||
ctrl);
|
ctrl);
|
||||||
} else if (type == DWC_PCIE_TIME_BASE_EVENT) {
|
} else if (type == DWC_PCIE_TIME_BASE_EVENT) {
|
||||||
|
if (pcie_pmu->time_based_event)
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
|
pcie_pmu->time_based_event = event;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TIME_BASED_ANAL_DATA_REG is a 64 bit register, we can safely
|
* TIME_BASED_ANAL_DATA_REG is a 64 bit register, we can safely
|
||||||
* use it with any manually controlled duration. And it is
|
* use it with any manually controlled duration. And it is
|
||||||
|
|
@ -478,7 +566,18 @@ static void dwc_pcie_pmu_event_del(struct perf_event *event, int flags)
|
||||||
|
|
||||||
dwc_pcie_pmu_event_stop(event, flags | PERF_EF_UPDATE);
|
dwc_pcie_pmu_event_stop(event, flags | PERF_EF_UPDATE);
|
||||||
perf_event_update_userpage(event);
|
perf_event_update_userpage(event);
|
||||||
pcie_pmu->event[type] = NULL;
|
|
||||||
|
if (type == DWC_PCIE_TIME_BASE_EVENT) {
|
||||||
|
pcie_pmu->time_based_event = NULL;
|
||||||
|
} else {
|
||||||
|
int event_id = DWC_PCIE_EVENT_ID(event);
|
||||||
|
int event_nr = FIELD_GET(DWC_PCIE_CNT_EVENT_SEL_EVID, event_id);
|
||||||
|
int group = FIELD_GET(DWC_PCIE_CNT_EVENT_SEL_GROUP, event_id) -
|
||||||
|
DWC_PCIE_LANE_GROUP_6;
|
||||||
|
|
||||||
|
clear_bit(group * DWC_PCIE_LANE_MAX_EVENTS_PER_GROUP + event_nr,
|
||||||
|
pcie_pmu->lane_events);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dwc_pcie_pmu_remove_cpuhp_instance(void *hotplug_node)
|
static void dwc_pcie_pmu_remove_cpuhp_instance(void *hotplug_node)
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,11 @@ static const struct imx_ddr_devtype_data imx93_devtype_data = {
|
||||||
.filter_ver = DDR_PERF_AXI_FILTER_V1
|
.filter_ver = DDR_PERF_AXI_FILTER_V1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct imx_ddr_devtype_data imx94_devtype_data = {
|
||||||
|
.identifier = "imx94",
|
||||||
|
.filter_ver = DDR_PERF_AXI_FILTER_V2
|
||||||
|
};
|
||||||
|
|
||||||
static const struct imx_ddr_devtype_data imx95_devtype_data = {
|
static const struct imx_ddr_devtype_data imx95_devtype_data = {
|
||||||
.identifier = "imx95",
|
.identifier = "imx95",
|
||||||
.filter_ver = DDR_PERF_AXI_FILTER_V2
|
.filter_ver = DDR_PERF_AXI_FILTER_V2
|
||||||
|
|
@ -122,6 +127,7 @@ static inline bool axi_filter_v2(struct ddr_pmu *pmu)
|
||||||
static const struct of_device_id imx_ddr_pmu_dt_ids[] = {
|
static const struct of_device_id imx_ddr_pmu_dt_ids[] = {
|
||||||
{ .compatible = "fsl,imx91-ddr-pmu", .data = &imx91_devtype_data },
|
{ .compatible = "fsl,imx91-ddr-pmu", .data = &imx91_devtype_data },
|
||||||
{ .compatible = "fsl,imx93-ddr-pmu", .data = &imx93_devtype_data },
|
{ .compatible = "fsl,imx93-ddr-pmu", .data = &imx93_devtype_data },
|
||||||
|
{ .compatible = "fsl,imx94-ddr-pmu", .data = &imx94_devtype_data },
|
||||||
{ .compatible = "fsl,imx95-ddr-pmu", .data = &imx95_devtype_data },
|
{ .compatible = "fsl,imx95-ddr-pmu", .data = &imx95_devtype_data },
|
||||||
{ /* sentinel */ }
|
{ /* sentinel */ }
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,613 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Driver for the Uncore PMUs in Fujitsu chips.
|
||||||
|
*
|
||||||
|
* See Documentation/admin-guide/perf/fujitsu_uncore_pmu.rst for more details.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Fujitsu. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/mod_devicetable.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/perf_event.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
/* Number of counters on each PMU */
|
||||||
|
#define MAC_NUM_COUNTERS 8
|
||||||
|
#define PCI_NUM_COUNTERS 8
|
||||||
|
/* Mask for the event type field within perf_event_attr.config and EVTYPE reg */
|
||||||
|
#define UNCORE_EVTYPE_MASK 0xFF
|
||||||
|
|
||||||
|
/* Perfmon registers */
|
||||||
|
#define PM_EVCNTR(__cntr) (0x000 + (__cntr) * 8)
|
||||||
|
#define PM_CNTCTL(__cntr) (0x100 + (__cntr) * 8)
|
||||||
|
#define PM_CNTCTL_RESET 0
|
||||||
|
#define PM_EVTYPE(__cntr) (0x200 + (__cntr) * 8)
|
||||||
|
#define PM_EVTYPE_EVSEL(__val) FIELD_GET(UNCORE_EVTYPE_MASK, __val)
|
||||||
|
#define PM_CR 0x400
|
||||||
|
#define PM_CR_RESET BIT(1)
|
||||||
|
#define PM_CR_ENABLE BIT(0)
|
||||||
|
#define PM_CNTENSET 0x410
|
||||||
|
#define PM_CNTENSET_IDX(__cntr) BIT(__cntr)
|
||||||
|
#define PM_CNTENCLR 0x418
|
||||||
|
#define PM_CNTENCLR_IDX(__cntr) BIT(__cntr)
|
||||||
|
#define PM_CNTENCLR_RESET 0xFF
|
||||||
|
#define PM_INTENSET 0x420
|
||||||
|
#define PM_INTENSET_IDX(__cntr) BIT(__cntr)
|
||||||
|
#define PM_INTENCLR 0x428
|
||||||
|
#define PM_INTENCLR_IDX(__cntr) BIT(__cntr)
|
||||||
|
#define PM_INTENCLR_RESET 0xFF
|
||||||
|
#define PM_OVSR 0x440
|
||||||
|
#define PM_OVSR_OVSRCLR_RESET 0xFF
|
||||||
|
|
||||||
|
enum fujitsu_uncore_pmu {
|
||||||
|
FUJITSU_UNCORE_PMU_MAC = 1,
|
||||||
|
FUJITSU_UNCORE_PMU_PCI = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct uncore_pmu {
|
||||||
|
int num_counters;
|
||||||
|
struct pmu pmu;
|
||||||
|
struct hlist_node node;
|
||||||
|
void __iomem *regs;
|
||||||
|
struct perf_event **events;
|
||||||
|
unsigned long *used_mask;
|
||||||
|
int cpu;
|
||||||
|
int irq;
|
||||||
|
struct device *dev;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_uncore_pmu(p) (container_of(p, struct uncore_pmu, pmu))
|
||||||
|
|
||||||
|
static int uncore_pmu_cpuhp_state;
|
||||||
|
|
||||||
|
static void fujitsu_uncore_counter_start(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(event->pmu);
|
||||||
|
int idx = event->hw.idx;
|
||||||
|
|
||||||
|
/* Initialize the hardware counter and reset prev_count*/
|
||||||
|
local64_set(&event->hw.prev_count, 0);
|
||||||
|
writeq_relaxed(0, uncorepmu->regs + PM_EVCNTR(idx));
|
||||||
|
|
||||||
|
/* Set the event type */
|
||||||
|
writeq_relaxed(PM_EVTYPE_EVSEL(event->attr.config), uncorepmu->regs + PM_EVTYPE(idx));
|
||||||
|
|
||||||
|
/* Enable interrupt generation by this counter */
|
||||||
|
writeq_relaxed(PM_INTENSET_IDX(idx), uncorepmu->regs + PM_INTENSET);
|
||||||
|
|
||||||
|
/* Finally, enable the counter */
|
||||||
|
writeq_relaxed(PM_CNTCTL_RESET, uncorepmu->regs + PM_CNTCTL(idx));
|
||||||
|
writeq_relaxed(PM_CNTENSET_IDX(idx), uncorepmu->regs + PM_CNTENSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_counter_stop(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(event->pmu);
|
||||||
|
int idx = event->hw.idx;
|
||||||
|
|
||||||
|
/* Disable the counter */
|
||||||
|
writeq_relaxed(PM_CNTENCLR_IDX(idx), uncorepmu->regs + PM_CNTENCLR);
|
||||||
|
|
||||||
|
/* Disable interrupt generation by this counter */
|
||||||
|
writeq_relaxed(PM_INTENCLR_IDX(idx), uncorepmu->regs + PM_INTENCLR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_counter_update(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(event->pmu);
|
||||||
|
int idx = event->hw.idx;
|
||||||
|
u64 prev, new;
|
||||||
|
|
||||||
|
do {
|
||||||
|
prev = local64_read(&event->hw.prev_count);
|
||||||
|
new = readq_relaxed(uncorepmu->regs + PM_EVCNTR(idx));
|
||||||
|
} while (local64_cmpxchg(&event->hw.prev_count, prev, new) != prev);
|
||||||
|
|
||||||
|
local64_add(new - prev, &event->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void fujitsu_uncore_init(struct uncore_pmu *uncorepmu)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
writeq_relaxed(PM_CR_RESET, uncorepmu->regs + PM_CR);
|
||||||
|
|
||||||
|
writeq_relaxed(PM_CNTENCLR_RESET, uncorepmu->regs + PM_CNTENCLR);
|
||||||
|
writeq_relaxed(PM_INTENCLR_RESET, uncorepmu->regs + PM_INTENCLR);
|
||||||
|
writeq_relaxed(PM_OVSR_OVSRCLR_RESET, uncorepmu->regs + PM_OVSR);
|
||||||
|
|
||||||
|
for (i = 0; i < uncorepmu->num_counters; ++i) {
|
||||||
|
writeq_relaxed(PM_CNTCTL_RESET, uncorepmu->regs + PM_CNTCTL(i));
|
||||||
|
writeq_relaxed(PM_EVTYPE_EVSEL(0), uncorepmu->regs + PM_EVTYPE(i));
|
||||||
|
}
|
||||||
|
writeq_relaxed(PM_CR_ENABLE, uncorepmu->regs + PM_CR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t fujitsu_uncore_handle_irq(int irq_num, void *data)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = data;
|
||||||
|
/* Read the overflow status register */
|
||||||
|
long status = readq_relaxed(uncorepmu->regs + PM_OVSR);
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
if (status == 0)
|
||||||
|
return IRQ_NONE;
|
||||||
|
|
||||||
|
/* Clear the bits we read on the overflow status register */
|
||||||
|
writeq_relaxed(status, uncorepmu->regs + PM_OVSR);
|
||||||
|
|
||||||
|
for_each_set_bit(idx, &status, uncorepmu->num_counters) {
|
||||||
|
struct perf_event *event;
|
||||||
|
|
||||||
|
event = uncorepmu->events[idx];
|
||||||
|
if (!event)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fujitsu_uncore_counter_update(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_pmu_enable(struct pmu *pmu)
|
||||||
|
{
|
||||||
|
writeq_relaxed(PM_CR_ENABLE, to_uncore_pmu(pmu)->regs + PM_CR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_pmu_disable(struct pmu *pmu)
|
||||||
|
{
|
||||||
|
writeq_relaxed(0, to_uncore_pmu(pmu)->regs + PM_CR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fujitsu_uncore_validate_event_group(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(event->pmu);
|
||||||
|
struct perf_event *leader = event->group_leader;
|
||||||
|
struct perf_event *sibling;
|
||||||
|
int counters = 1;
|
||||||
|
|
||||||
|
if (leader == event)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (leader->pmu == event->pmu)
|
||||||
|
counters++;
|
||||||
|
|
||||||
|
for_each_sibling_event(sibling, leader) {
|
||||||
|
if (sibling->pmu == event->pmu)
|
||||||
|
counters++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the group requires more counters than the HW has, it
|
||||||
|
* cannot ever be scheduled.
|
||||||
|
*/
|
||||||
|
return counters <= uncorepmu->num_counters;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fujitsu_uncore_event_init(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(event->pmu);
|
||||||
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
|
|
||||||
|
/* Is the event for this PMU? */
|
||||||
|
if (event->attr.type != event->pmu->type)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sampling not supported since these events are not
|
||||||
|
* core-attributable.
|
||||||
|
*/
|
||||||
|
if (is_sampling_event(event))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Task mode not available, we run the counters as socket counters,
|
||||||
|
* not attributable to any CPU and therefore cannot attribute per-task.
|
||||||
|
*/
|
||||||
|
if (event->cpu < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Validate the group */
|
||||||
|
if (!fujitsu_uncore_validate_event_group(event))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
hwc->idx = -1;
|
||||||
|
|
||||||
|
event->cpu = uncorepmu->cpu;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_event_start(struct perf_event *event, int flags)
|
||||||
|
{
|
||||||
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
|
|
||||||
|
hwc->state = 0;
|
||||||
|
fujitsu_uncore_counter_start(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_event_stop(struct perf_event *event, int flags)
|
||||||
|
{
|
||||||
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
|
|
||||||
|
if (hwc->state & PERF_HES_STOPPED)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fujitsu_uncore_counter_stop(event);
|
||||||
|
if (flags & PERF_EF_UPDATE)
|
||||||
|
fujitsu_uncore_counter_update(event);
|
||||||
|
hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fujitsu_uncore_event_add(struct perf_event *event, int flags)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(event->pmu);
|
||||||
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
/* Try to allocate a counter. */
|
||||||
|
idx = bitmap_find_free_region(uncorepmu->used_mask, uncorepmu->num_counters, 0);
|
||||||
|
if (idx < 0)
|
||||||
|
/* The counters are all in use. */
|
||||||
|
return -EAGAIN;
|
||||||
|
|
||||||
|
hwc->idx = idx;
|
||||||
|
hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
||||||
|
uncorepmu->events[idx] = event;
|
||||||
|
|
||||||
|
if (flags & PERF_EF_START)
|
||||||
|
fujitsu_uncore_event_start(event, 0);
|
||||||
|
|
||||||
|
/* Propagate changes to the userspace mapping. */
|
||||||
|
perf_event_update_userpage(event);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_event_del(struct perf_event *event, int flags)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(event->pmu);
|
||||||
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
|
|
||||||
|
/* Stop and clean up */
|
||||||
|
fujitsu_uncore_event_stop(event, flags | PERF_EF_UPDATE);
|
||||||
|
uncorepmu->events[hwc->idx] = NULL;
|
||||||
|
bitmap_release_region(uncorepmu->used_mask, hwc->idx, 0);
|
||||||
|
|
||||||
|
/* Propagate changes to the userspace mapping. */
|
||||||
|
perf_event_update_userpage(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_event_read(struct perf_event *event)
|
||||||
|
{
|
||||||
|
fujitsu_uncore_counter_update(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define UNCORE_PMU_FORMAT_ATTR(_name, _config) \
|
||||||
|
(&((struct dev_ext_attribute[]) { \
|
||||||
|
{ .attr = __ATTR(_name, 0444, device_show_string, NULL), \
|
||||||
|
.var = (void *)_config, } \
|
||||||
|
})[0].attr.attr)
|
||||||
|
|
||||||
|
static struct attribute *fujitsu_uncore_pmu_formats[] = {
|
||||||
|
UNCORE_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fujitsu_uncore_pmu_format_group = {
|
||||||
|
.name = "format",
|
||||||
|
.attrs = fujitsu_uncore_pmu_formats,
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t fujitsu_uncore_pmu_event_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *page)
|
||||||
|
{
|
||||||
|
struct perf_pmu_events_attr *pmu_attr;
|
||||||
|
|
||||||
|
pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr);
|
||||||
|
return sysfs_emit(page, "event=0x%02llx\n", pmu_attr->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAC_EVENT_ATTR(_name, _id) \
|
||||||
|
PMU_EVENT_ATTR_ID(_name, fujitsu_uncore_pmu_event_show, _id)
|
||||||
|
|
||||||
|
static struct attribute *fujitsu_uncore_mac_pmu_events[] = {
|
||||||
|
MAC_EVENT_ATTR(cycles, 0x00),
|
||||||
|
MAC_EVENT_ATTR(read-count, 0x10),
|
||||||
|
MAC_EVENT_ATTR(read-count-request, 0x11),
|
||||||
|
MAC_EVENT_ATTR(read-count-return, 0x12),
|
||||||
|
MAC_EVENT_ATTR(read-count-request-pftgt, 0x13),
|
||||||
|
MAC_EVENT_ATTR(read-count-request-normal, 0x14),
|
||||||
|
MAC_EVENT_ATTR(read-count-return-pftgt-hit, 0x15),
|
||||||
|
MAC_EVENT_ATTR(read-count-return-pftgt-miss, 0x16),
|
||||||
|
MAC_EVENT_ATTR(read-wait, 0x17),
|
||||||
|
MAC_EVENT_ATTR(write-count, 0x20),
|
||||||
|
MAC_EVENT_ATTR(write-count-write, 0x21),
|
||||||
|
MAC_EVENT_ATTR(write-count-pwrite, 0x22),
|
||||||
|
MAC_EVENT_ATTR(memory-read-count, 0x40),
|
||||||
|
MAC_EVENT_ATTR(memory-write-count, 0x50),
|
||||||
|
MAC_EVENT_ATTR(memory-pwrite-count, 0x60),
|
||||||
|
MAC_EVENT_ATTR(ea-mac, 0x80),
|
||||||
|
MAC_EVENT_ATTR(ea-memory, 0x90),
|
||||||
|
MAC_EVENT_ATTR(ea-memory-mac-write, 0x92),
|
||||||
|
MAC_EVENT_ATTR(ea-ha, 0xa0),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PCI_EVENT_ATTR(_name, _id) \
|
||||||
|
PMU_EVENT_ATTR_ID(_name, fujitsu_uncore_pmu_event_show, _id)
|
||||||
|
|
||||||
|
static struct attribute *fujitsu_uncore_pci_pmu_events[] = {
|
||||||
|
PCI_EVENT_ATTR(pci-port0-cycles, 0x00),
|
||||||
|
PCI_EVENT_ATTR(pci-port0-read-count, 0x10),
|
||||||
|
PCI_EVENT_ATTR(pci-port0-read-count-bus, 0x14),
|
||||||
|
PCI_EVENT_ATTR(pci-port0-write-count, 0x20),
|
||||||
|
PCI_EVENT_ATTR(pci-port0-write-count-bus, 0x24),
|
||||||
|
PCI_EVENT_ATTR(pci-port1-cycles, 0x40),
|
||||||
|
PCI_EVENT_ATTR(pci-port1-read-count, 0x50),
|
||||||
|
PCI_EVENT_ATTR(pci-port1-read-count-bus, 0x54),
|
||||||
|
PCI_EVENT_ATTR(pci-port1-write-count, 0x60),
|
||||||
|
PCI_EVENT_ATTR(pci-port1-write-count-bus, 0x64),
|
||||||
|
PCI_EVENT_ATTR(ea-pci, 0x80),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fujitsu_uncore_mac_pmu_events_group = {
|
||||||
|
.name = "events",
|
||||||
|
.attrs = fujitsu_uncore_mac_pmu_events,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fujitsu_uncore_pci_pmu_events_group = {
|
||||||
|
.name = "events",
|
||||||
|
.attrs = fujitsu_uncore_pci_pmu_events,
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t cpumask_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = to_uncore_pmu(dev_get_drvdata(dev));
|
||||||
|
|
||||||
|
return cpumap_print_to_pagebuf(true, buf, cpumask_of(uncorepmu->cpu));
|
||||||
|
}
|
||||||
|
static DEVICE_ATTR_RO(cpumask);
|
||||||
|
|
||||||
|
static struct attribute *fujitsu_uncore_pmu_cpumask_attrs[] = {
|
||||||
|
&dev_attr_cpumask.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fujitsu_uncore_pmu_cpumask_attr_group = {
|
||||||
|
.attrs = fujitsu_uncore_pmu_cpumask_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group *fujitsu_uncore_mac_pmu_attr_grps[] = {
|
||||||
|
&fujitsu_uncore_pmu_format_group,
|
||||||
|
&fujitsu_uncore_mac_pmu_events_group,
|
||||||
|
&fujitsu_uncore_pmu_cpumask_attr_group,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group *fujitsu_uncore_pci_pmu_attr_grps[] = {
|
||||||
|
&fujitsu_uncore_pmu_format_group,
|
||||||
|
&fujitsu_uncore_pci_pmu_events_group,
|
||||||
|
&fujitsu_uncore_pmu_cpumask_attr_group,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static void fujitsu_uncore_pmu_migrate(struct uncore_pmu *uncorepmu, unsigned int cpu)
|
||||||
|
{
|
||||||
|
perf_pmu_migrate_context(&uncorepmu->pmu, uncorepmu->cpu, cpu);
|
||||||
|
irq_set_affinity(uncorepmu->irq, cpumask_of(cpu));
|
||||||
|
uncorepmu->cpu = cpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fujitsu_uncore_pmu_online_cpu(unsigned int cpu, struct hlist_node *cpuhp_node)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu;
|
||||||
|
int node;
|
||||||
|
|
||||||
|
uncorepmu = hlist_entry_safe(cpuhp_node, struct uncore_pmu, node);
|
||||||
|
node = dev_to_node(uncorepmu->dev);
|
||||||
|
if (cpu_to_node(uncorepmu->cpu) != node && cpu_to_node(cpu) == node)
|
||||||
|
fujitsu_uncore_pmu_migrate(uncorepmu, cpu);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fujitsu_uncore_pmu_offline_cpu(unsigned int cpu, struct hlist_node *cpuhp_node)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu;
|
||||||
|
unsigned int target;
|
||||||
|
int node;
|
||||||
|
|
||||||
|
uncorepmu = hlist_entry_safe(cpuhp_node, struct uncore_pmu, node);
|
||||||
|
if (cpu != uncorepmu->cpu)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
node = dev_to_node(uncorepmu->dev);
|
||||||
|
target = cpumask_any_and_but(cpumask_of_node(node), cpu_online_mask, cpu);
|
||||||
|
if (target >= nr_cpu_ids)
|
||||||
|
target = cpumask_any_but(cpu_online_mask, cpu);
|
||||||
|
|
||||||
|
if (target < nr_cpu_ids)
|
||||||
|
fujitsu_uncore_pmu_migrate(uncorepmu, target);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fujitsu_uncore_pmu_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
unsigned long device_type = (unsigned long)device_get_match_data(dev);
|
||||||
|
const struct attribute_group **attr_groups;
|
||||||
|
struct uncore_pmu *uncorepmu;
|
||||||
|
struct resource *memrc;
|
||||||
|
size_t alloc_size;
|
||||||
|
char *name;
|
||||||
|
int ret;
|
||||||
|
int irq;
|
||||||
|
u64 uid;
|
||||||
|
|
||||||
|
ret = acpi_dev_uid_to_integer(ACPI_COMPANION(dev), &uid);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "unable to read ACPI uid\n");
|
||||||
|
|
||||||
|
uncorepmu = devm_kzalloc(dev, sizeof(*uncorepmu), GFP_KERNEL);
|
||||||
|
if (!uncorepmu)
|
||||||
|
return -ENOMEM;
|
||||||
|
uncorepmu->dev = dev;
|
||||||
|
uncorepmu->cpu = cpumask_local_spread(0, dev_to_node(dev));
|
||||||
|
platform_set_drvdata(pdev, uncorepmu);
|
||||||
|
|
||||||
|
switch (device_type) {
|
||||||
|
case FUJITSU_UNCORE_PMU_MAC:
|
||||||
|
uncorepmu->num_counters = MAC_NUM_COUNTERS;
|
||||||
|
attr_groups = fujitsu_uncore_mac_pmu_attr_grps;
|
||||||
|
name = devm_kasprintf(dev, GFP_KERNEL, "mac_iod%llu_mac%llu_ch%llu",
|
||||||
|
(uid >> 8) & 0xF, (uid >> 4) & 0xF, uid & 0xF);
|
||||||
|
break;
|
||||||
|
case FUJITSU_UNCORE_PMU_PCI:
|
||||||
|
uncorepmu->num_counters = PCI_NUM_COUNTERS;
|
||||||
|
attr_groups = fujitsu_uncore_pci_pmu_attr_grps;
|
||||||
|
name = devm_kasprintf(dev, GFP_KERNEL, "pci_iod%llu_pci%llu",
|
||||||
|
(uid >> 4) & 0xF, uid & 0xF);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return dev_err_probe(dev, -EINVAL, "illegal device type: %lu\n", device_type);
|
||||||
|
}
|
||||||
|
if (!name)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
uncorepmu->pmu = (struct pmu) {
|
||||||
|
.parent = dev,
|
||||||
|
.task_ctx_nr = perf_invalid_context,
|
||||||
|
|
||||||
|
.attr_groups = attr_groups,
|
||||||
|
|
||||||
|
.pmu_enable = fujitsu_uncore_pmu_enable,
|
||||||
|
.pmu_disable = fujitsu_uncore_pmu_disable,
|
||||||
|
.event_init = fujitsu_uncore_event_init,
|
||||||
|
.add = fujitsu_uncore_event_add,
|
||||||
|
.del = fujitsu_uncore_event_del,
|
||||||
|
.start = fujitsu_uncore_event_start,
|
||||||
|
.stop = fujitsu_uncore_event_stop,
|
||||||
|
.read = fujitsu_uncore_event_read,
|
||||||
|
|
||||||
|
.capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT,
|
||||||
|
};
|
||||||
|
|
||||||
|
alloc_size = sizeof(uncorepmu->events[0]) * uncorepmu->num_counters;
|
||||||
|
uncorepmu->events = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
|
||||||
|
if (!uncorepmu->events)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
alloc_size = sizeof(uncorepmu->used_mask[0]) * BITS_TO_LONGS(uncorepmu->num_counters);
|
||||||
|
uncorepmu->used_mask = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
|
||||||
|
if (!uncorepmu->used_mask)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
uncorepmu->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &memrc);
|
||||||
|
if (IS_ERR(uncorepmu->regs))
|
||||||
|
return PTR_ERR(uncorepmu->regs);
|
||||||
|
|
||||||
|
fujitsu_uncore_init(uncorepmu);
|
||||||
|
|
||||||
|
irq = platform_get_irq(pdev, 0);
|
||||||
|
if (irq < 0)
|
||||||
|
return irq;
|
||||||
|
|
||||||
|
ret = devm_request_irq(dev, irq, fujitsu_uncore_handle_irq,
|
||||||
|
IRQF_NOBALANCING | IRQF_NO_THREAD,
|
||||||
|
name, uncorepmu);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Failed to request IRQ:%d\n", irq);
|
||||||
|
|
||||||
|
ret = irq_set_affinity(irq, cpumask_of(uncorepmu->cpu));
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Failed to set irq affinity:%d\n", irq);
|
||||||
|
|
||||||
|
uncorepmu->irq = irq;
|
||||||
|
|
||||||
|
/* Add this instance to the list used by the offline callback */
|
||||||
|
ret = cpuhp_state_add_instance(uncore_pmu_cpuhp_state, &uncorepmu->node);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Error registering hotplug");
|
||||||
|
|
||||||
|
ret = perf_pmu_register(&uncorepmu->pmu, name, -1);
|
||||||
|
if (ret < 0) {
|
||||||
|
cpuhp_state_remove_instance_nocalls(uncore_pmu_cpuhp_state, &uncorepmu->node);
|
||||||
|
return dev_err_probe(dev, ret, "Failed to register %s PMU\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_dbg(dev, "Registered %s, type: %d\n", name, uncorepmu->pmu.type);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fujitsu_uncore_pmu_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct uncore_pmu *uncorepmu = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
writeq_relaxed(0, uncorepmu->regs + PM_CR);
|
||||||
|
|
||||||
|
perf_pmu_unregister(&uncorepmu->pmu);
|
||||||
|
cpuhp_state_remove_instance_nocalls(uncore_pmu_cpuhp_state, &uncorepmu->node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct acpi_device_id fujitsu_uncore_pmu_acpi_match[] = {
|
||||||
|
{ "FUJI200C", FUJITSU_UNCORE_PMU_MAC },
|
||||||
|
{ "FUJI200D", FUJITSU_UNCORE_PMU_PCI },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, fujitsu_uncore_pmu_acpi_match);
|
||||||
|
|
||||||
|
static struct platform_driver fujitsu_uncore_pmu_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "fujitsu-uncore-pmu",
|
||||||
|
.acpi_match_table = fujitsu_uncore_pmu_acpi_match,
|
||||||
|
.suppress_bind_attrs = true,
|
||||||
|
},
|
||||||
|
.probe = fujitsu_uncore_pmu_probe,
|
||||||
|
.remove = fujitsu_uncore_pmu_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init fujitsu_uncore_pmu_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Install a hook to update the reader CPU in case it goes offline */
|
||||||
|
ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN,
|
||||||
|
"perf/fujitsu/uncore:online",
|
||||||
|
fujitsu_uncore_pmu_online_cpu,
|
||||||
|
fujitsu_uncore_pmu_offline_cpu);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
uncore_pmu_cpuhp_state = ret;
|
||||||
|
|
||||||
|
ret = platform_driver_register(&fujitsu_uncore_pmu_driver);
|
||||||
|
if (ret)
|
||||||
|
cpuhp_remove_multi_state(uncore_pmu_cpuhp_state);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit fujitsu_uncore_pmu_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&fujitsu_uncore_pmu_driver);
|
||||||
|
cpuhp_remove_multi_state(uncore_pmu_cpuhp_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(fujitsu_uncore_pmu_init);
|
||||||
|
module_exit(fujitsu_uncore_pmu_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Koichi Okuno <fj2767dz@fujitsu.com>");
|
||||||
|
MODULE_DESCRIPTION("Fujitsu Uncore PMU driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c_pmu.o \
|
obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c_pmu.o \
|
||||||
hisi_uncore_hha_pmu.o hisi_uncore_ddrc_pmu.o hisi_uncore_sllc_pmu.o \
|
hisi_uncore_hha_pmu.o hisi_uncore_ddrc_pmu.o hisi_uncore_sllc_pmu.o \
|
||||||
hisi_uncore_pa_pmu.o hisi_uncore_cpa_pmu.o hisi_uncore_uc_pmu.o
|
hisi_uncore_pa_pmu.o hisi_uncore_cpa_pmu.o hisi_uncore_uc_pmu.o \
|
||||||
|
hisi_uncore_noc_pmu.o hisi_uncore_mn_pmu.o
|
||||||
|
|
||||||
obj-$(CONFIG_HISI_PCIE_PMU) += hisi_pcie_pmu.o
|
obj-$(CONFIG_HISI_PCIE_PMU) += hisi_pcie_pmu.o
|
||||||
obj-$(CONFIG_HNS3_PMU) += hns3_pmu.o
|
obj-$(CONFIG_HNS3_PMU) += hns3_pmu.o
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
|
|
||||||
/* L3C has 8-counters */
|
/* L3C has 8-counters */
|
||||||
#define L3C_NR_COUNTERS 0x8
|
#define L3C_NR_COUNTERS 0x8
|
||||||
|
#define L3C_MAX_EXT 2
|
||||||
|
|
||||||
#define L3C_PERF_CTRL_EN 0x10000
|
#define L3C_PERF_CTRL_EN 0x10000
|
||||||
#define L3C_TRACETAG_EN BIT(31)
|
#define L3C_TRACETAG_EN BIT(31)
|
||||||
|
|
@ -55,59 +56,152 @@
|
||||||
#define L3C_V1_NR_EVENTS 0x59
|
#define L3C_V1_NR_EVENTS 0x59
|
||||||
#define L3C_V2_NR_EVENTS 0xFF
|
#define L3C_V2_NR_EVENTS 0xFF
|
||||||
|
|
||||||
HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_core, config1, 7, 0);
|
HISI_PMU_EVENT_ATTR_EXTRACTOR(ext, config, 17, 16);
|
||||||
HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_req, config1, 10, 8);
|
HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_req, config1, 10, 8);
|
||||||
HISI_PMU_EVENT_ATTR_EXTRACTOR(datasrc_cfg, config1, 15, 11);
|
HISI_PMU_EVENT_ATTR_EXTRACTOR(datasrc_cfg, config1, 15, 11);
|
||||||
HISI_PMU_EVENT_ATTR_EXTRACTOR(datasrc_skt, config1, 16, 16);
|
HISI_PMU_EVENT_ATTR_EXTRACTOR(datasrc_skt, config1, 16, 16);
|
||||||
|
HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_core, config2, 15, 0);
|
||||||
|
|
||||||
|
struct hisi_l3c_pmu {
|
||||||
|
struct hisi_pmu l3c_pmu;
|
||||||
|
|
||||||
|
/* MMIO and IRQ resources for extension events */
|
||||||
|
void __iomem *ext_base[L3C_MAX_EXT];
|
||||||
|
int ext_irq[L3C_MAX_EXT];
|
||||||
|
int ext_num;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_hisi_l3c_pmu(_l3c_pmu) \
|
||||||
|
container_of(_l3c_pmu, struct hisi_l3c_pmu, l3c_pmu)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The hardware counter idx used in counter enable/disable,
|
||||||
|
* interrupt enable/disable and status check, etc.
|
||||||
|
*/
|
||||||
|
#define L3C_HW_IDX(_cntr_idx) ((_cntr_idx) % L3C_NR_COUNTERS)
|
||||||
|
|
||||||
|
/* Range of ext counters in used mask. */
|
||||||
|
#define L3C_CNTR_EXT_L(_ext) (((_ext) + 1) * L3C_NR_COUNTERS)
|
||||||
|
#define L3C_CNTR_EXT_H(_ext) (((_ext) + 2) * L3C_NR_COUNTERS)
|
||||||
|
|
||||||
|
struct hisi_l3c_pmu_ext {
|
||||||
|
bool support_ext;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool support_ext(struct hisi_l3c_pmu *pmu)
|
||||||
|
{
|
||||||
|
struct hisi_l3c_pmu_ext *l3c_pmu_ext = pmu->l3c_pmu.dev_info->private;
|
||||||
|
|
||||||
|
return l3c_pmu_ext->support_ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hisi_l3c_pmu_get_event_idx(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
unsigned long *used_mask = l3c_pmu->pmu_events.used_mask;
|
||||||
|
int ext = hisi_get_ext(event);
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For an L3C PMU that supports extension events, we can monitor
|
||||||
|
* maximum 2 * num_counters to 3 * num_counters events, depending on
|
||||||
|
* the number of ext regions supported by hardware. Thus use bit
|
||||||
|
* [0, num_counters - 1] for normal events and bit
|
||||||
|
* [ext * num_counters, (ext + 1) * num_counters - 1] for extension
|
||||||
|
* events. The idx allocation will keep unchanged for normal events and
|
||||||
|
* we can also use the idx to distinguish whether it's an extension
|
||||||
|
* event or not.
|
||||||
|
*
|
||||||
|
* Since normal events and extension events locates on the different
|
||||||
|
* address space, save the base address to the event->hw.event_base.
|
||||||
|
*/
|
||||||
|
if (ext && !support_ext(hisi_l3c_pmu))
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
if (ext)
|
||||||
|
event->hw.event_base = (unsigned long)hisi_l3c_pmu->ext_base[ext - 1];
|
||||||
|
else
|
||||||
|
event->hw.event_base = (unsigned long)l3c_pmu->base;
|
||||||
|
|
||||||
|
ext -= 1;
|
||||||
|
idx = find_next_zero_bit(used_mask, L3C_CNTR_EXT_H(ext), L3C_CNTR_EXT_L(ext));
|
||||||
|
|
||||||
|
if (idx >= L3C_CNTR_EXT_H(ext))
|
||||||
|
return -EAGAIN;
|
||||||
|
|
||||||
|
set_bit(idx, used_mask);
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 hisi_l3c_pmu_event_readl(struct hw_perf_event *hwc, u32 reg)
|
||||||
|
{
|
||||||
|
return readl((void __iomem *)hwc->event_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_l3c_pmu_event_writel(struct hw_perf_event *hwc, u32 reg, u32 val)
|
||||||
|
{
|
||||||
|
writel(val, (void __iomem *)hwc->event_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 hisi_l3c_pmu_event_readq(struct hw_perf_event *hwc, u32 reg)
|
||||||
|
{
|
||||||
|
return readq((void __iomem *)hwc->event_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_l3c_pmu_event_writeq(struct hw_perf_event *hwc, u32 reg, u64 val)
|
||||||
|
{
|
||||||
|
writeq(val, (void __iomem *)hwc->event_base + reg);
|
||||||
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_config_req_tracetag(struct perf_event *event)
|
static void hisi_l3c_pmu_config_req_tracetag(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
u32 tt_req = hisi_get_tt_req(event);
|
u32 tt_req = hisi_get_tt_req(event);
|
||||||
|
|
||||||
if (tt_req) {
|
if (tt_req) {
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
/* Set request-type for tracetag */
|
/* Set request-type for tracetag */
|
||||||
val = readl(l3c_pmu->base + L3C_TRACETAG_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_TRACETAG_CTRL);
|
||||||
val |= tt_req << L3C_TRACETAG_REQ_SHIFT;
|
val |= tt_req << L3C_TRACETAG_REQ_SHIFT;
|
||||||
val |= L3C_TRACETAG_REQ_EN;
|
val |= L3C_TRACETAG_REQ_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_TRACETAG_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_TRACETAG_CTRL, val);
|
||||||
|
|
||||||
/* Enable request-tracetag statistics */
|
/* Enable request-tracetag statistics */
|
||||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_PERF_CTRL);
|
||||||
val |= L3C_TRACETAG_EN;
|
val |= L3C_TRACETAG_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_PERF_CTRL, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_clear_req_tracetag(struct perf_event *event)
|
static void hisi_l3c_pmu_clear_req_tracetag(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
u32 tt_req = hisi_get_tt_req(event);
|
u32 tt_req = hisi_get_tt_req(event);
|
||||||
|
|
||||||
if (tt_req) {
|
if (tt_req) {
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
/* Clear request-type */
|
/* Clear request-type */
|
||||||
val = readl(l3c_pmu->base + L3C_TRACETAG_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_TRACETAG_CTRL);
|
||||||
val &= ~(tt_req << L3C_TRACETAG_REQ_SHIFT);
|
val &= ~(tt_req << L3C_TRACETAG_REQ_SHIFT);
|
||||||
val &= ~L3C_TRACETAG_REQ_EN;
|
val &= ~L3C_TRACETAG_REQ_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_TRACETAG_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_TRACETAG_CTRL, val);
|
||||||
|
|
||||||
/* Disable request-tracetag statistics */
|
/* Disable request-tracetag statistics */
|
||||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_PERF_CTRL);
|
||||||
val &= ~L3C_TRACETAG_EN;
|
val &= ~L3C_TRACETAG_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_PERF_CTRL, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_write_ds(struct perf_event *event, u32 ds_cfg)
|
static void hisi_l3c_pmu_write_ds(struct perf_event *event, u32 ds_cfg)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
|
||||||
struct hw_perf_event *hwc = &event->hw;
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
u32 reg, reg_idx, shift, val;
|
u32 reg, reg_idx, shift, val;
|
||||||
int idx = hwc->idx;
|
int idx = L3C_HW_IDX(hwc->idx);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select the appropriate datasource register(L3C_DATSRC_TYPE0/1).
|
* Select the appropriate datasource register(L3C_DATSRC_TYPE0/1).
|
||||||
|
|
@ -120,15 +214,15 @@ static void hisi_l3c_pmu_write_ds(struct perf_event *event, u32 ds_cfg)
|
||||||
reg_idx = idx % 4;
|
reg_idx = idx % 4;
|
||||||
shift = 8 * reg_idx;
|
shift = 8 * reg_idx;
|
||||||
|
|
||||||
val = readl(l3c_pmu->base + reg);
|
val = hisi_l3c_pmu_event_readl(hwc, reg);
|
||||||
val &= ~(L3C_DATSRC_MASK << shift);
|
val &= ~(L3C_DATSRC_MASK << shift);
|
||||||
val |= ds_cfg << shift;
|
val |= ds_cfg << shift;
|
||||||
writel(val, l3c_pmu->base + reg);
|
hisi_l3c_pmu_event_writel(hwc, reg, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_config_ds(struct perf_event *event)
|
static void hisi_l3c_pmu_config_ds(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
u32 ds_cfg = hisi_get_datasrc_cfg(event);
|
u32 ds_cfg = hisi_get_datasrc_cfg(event);
|
||||||
u32 ds_skt = hisi_get_datasrc_skt(event);
|
u32 ds_skt = hisi_get_datasrc_skt(event);
|
||||||
|
|
||||||
|
|
@ -138,15 +232,15 @@ static void hisi_l3c_pmu_config_ds(struct perf_event *event)
|
||||||
if (ds_skt) {
|
if (ds_skt) {
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
val = readl(l3c_pmu->base + L3C_DATSRC_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_DATSRC_CTRL);
|
||||||
val |= L3C_DATSRC_SKT_EN;
|
val |= L3C_DATSRC_SKT_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_DATSRC_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_DATSRC_CTRL, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_clear_ds(struct perf_event *event)
|
static void hisi_l3c_pmu_clear_ds(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
u32 ds_cfg = hisi_get_datasrc_cfg(event);
|
u32 ds_cfg = hisi_get_datasrc_cfg(event);
|
||||||
u32 ds_skt = hisi_get_datasrc_skt(event);
|
u32 ds_skt = hisi_get_datasrc_skt(event);
|
||||||
|
|
||||||
|
|
@ -156,57 +250,63 @@ static void hisi_l3c_pmu_clear_ds(struct perf_event *event)
|
||||||
if (ds_skt) {
|
if (ds_skt) {
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
val = readl(l3c_pmu->base + L3C_DATSRC_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_DATSRC_CTRL);
|
||||||
val &= ~L3C_DATSRC_SKT_EN;
|
val &= ~L3C_DATSRC_SKT_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_DATSRC_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_DATSRC_CTRL, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_config_core_tracetag(struct perf_event *event)
|
static void hisi_l3c_pmu_config_core_tracetag(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
u32 core = hisi_get_tt_core(event);
|
u32 core = hisi_get_tt_core(event);
|
||||||
|
|
||||||
if (core) {
|
if (core) {
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
/* Config and enable core information */
|
/* Config and enable core information */
|
||||||
writel(core, l3c_pmu->base + L3C_CORE_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_CORE_CTRL, core);
|
||||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_PERF_CTRL);
|
||||||
val |= L3C_CORE_EN;
|
val |= L3C_CORE_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_PERF_CTRL, val);
|
||||||
|
|
||||||
/* Enable core-tracetag statistics */
|
/* Enable core-tracetag statistics */
|
||||||
val = readl(l3c_pmu->base + L3C_TRACETAG_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_TRACETAG_CTRL);
|
||||||
val |= L3C_TRACETAG_CORE_EN;
|
val |= L3C_TRACETAG_CORE_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_TRACETAG_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_TRACETAG_CTRL, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_clear_core_tracetag(struct perf_event *event)
|
static void hisi_l3c_pmu_clear_core_tracetag(struct perf_event *event)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
u32 core = hisi_get_tt_core(event);
|
u32 core = hisi_get_tt_core(event);
|
||||||
|
|
||||||
if (core) {
|
if (core) {
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
/* Clear core information */
|
/* Clear core information */
|
||||||
writel(L3C_COER_NONE, l3c_pmu->base + L3C_CORE_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_CORE_CTRL, L3C_COER_NONE);
|
||||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_PERF_CTRL);
|
||||||
val &= ~L3C_CORE_EN;
|
val &= ~L3C_CORE_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_PERF_CTRL, val);
|
||||||
|
|
||||||
/* Disable core-tracetag statistics */
|
/* Disable core-tracetag statistics */
|
||||||
val = readl(l3c_pmu->base + L3C_TRACETAG_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_TRACETAG_CTRL);
|
||||||
val &= ~L3C_TRACETAG_CORE_EN;
|
val &= ~L3C_TRACETAG_CORE_EN;
|
||||||
writel(val, l3c_pmu->base + L3C_TRACETAG_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_TRACETAG_CTRL, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool hisi_l3c_pmu_have_filter(struct perf_event *event)
|
||||||
|
{
|
||||||
|
return hisi_get_tt_req(event) || hisi_get_tt_core(event) ||
|
||||||
|
hisi_get_datasrc_cfg(event) || hisi_get_datasrc_skt(event);
|
||||||
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_enable_filter(struct perf_event *event)
|
static void hisi_l3c_pmu_enable_filter(struct perf_event *event)
|
||||||
{
|
{
|
||||||
if (event->attr.config1 != 0x0) {
|
if (hisi_l3c_pmu_have_filter(event)) {
|
||||||
hisi_l3c_pmu_config_req_tracetag(event);
|
hisi_l3c_pmu_config_req_tracetag(event);
|
||||||
hisi_l3c_pmu_config_core_tracetag(event);
|
hisi_l3c_pmu_config_core_tracetag(event);
|
||||||
hisi_l3c_pmu_config_ds(event);
|
hisi_l3c_pmu_config_ds(event);
|
||||||
|
|
@ -215,38 +315,53 @@ static void hisi_l3c_pmu_enable_filter(struct perf_event *event)
|
||||||
|
|
||||||
static void hisi_l3c_pmu_disable_filter(struct perf_event *event)
|
static void hisi_l3c_pmu_disable_filter(struct perf_event *event)
|
||||||
{
|
{
|
||||||
if (event->attr.config1 != 0x0) {
|
if (hisi_l3c_pmu_have_filter(event)) {
|
||||||
hisi_l3c_pmu_clear_ds(event);
|
hisi_l3c_pmu_clear_ds(event);
|
||||||
hisi_l3c_pmu_clear_core_tracetag(event);
|
hisi_l3c_pmu_clear_core_tracetag(event);
|
||||||
hisi_l3c_pmu_clear_req_tracetag(event);
|
hisi_l3c_pmu_clear_req_tracetag(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int hisi_l3c_pmu_check_filter(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *l3c_pmu = to_hisi_pmu(event->pmu);
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
int ext = hisi_get_ext(event);
|
||||||
|
|
||||||
|
if (ext < 0 || ext > hisi_l3c_pmu->ext_num)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select the counter register offset using the counter index
|
* Select the counter register offset using the counter index
|
||||||
*/
|
*/
|
||||||
static u32 hisi_l3c_pmu_get_counter_offset(int cntr_idx)
|
static u32 hisi_l3c_pmu_get_counter_offset(int cntr_idx)
|
||||||
{
|
{
|
||||||
return (L3C_CNTR0_LOWER + (cntr_idx * 8));
|
return L3C_CNTR0_LOWER + L3C_HW_IDX(cntr_idx) * 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u64 hisi_l3c_pmu_read_counter(struct hisi_pmu *l3c_pmu,
|
static u64 hisi_l3c_pmu_read_counter(struct hisi_pmu *l3c_pmu,
|
||||||
struct hw_perf_event *hwc)
|
struct hw_perf_event *hwc)
|
||||||
{
|
{
|
||||||
return readq(l3c_pmu->base + hisi_l3c_pmu_get_counter_offset(hwc->idx));
|
return hisi_l3c_pmu_event_readq(hwc, hisi_l3c_pmu_get_counter_offset(hwc->idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_write_counter(struct hisi_pmu *l3c_pmu,
|
static void hisi_l3c_pmu_write_counter(struct hisi_pmu *l3c_pmu,
|
||||||
struct hw_perf_event *hwc, u64 val)
|
struct hw_perf_event *hwc, u64 val)
|
||||||
{
|
{
|
||||||
writeq(val, l3c_pmu->base + hisi_l3c_pmu_get_counter_offset(hwc->idx));
|
hisi_l3c_pmu_event_writeq(hwc, hisi_l3c_pmu_get_counter_offset(hwc->idx), val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_write_evtype(struct hisi_pmu *l3c_pmu, int idx,
|
static void hisi_l3c_pmu_write_evtype(struct hisi_pmu *l3c_pmu, int idx,
|
||||||
u32 type)
|
u32 type)
|
||||||
{
|
{
|
||||||
|
struct hw_perf_event *hwc = &l3c_pmu->pmu_events.hw_events[idx]->hw;
|
||||||
u32 reg, reg_idx, shift, val;
|
u32 reg, reg_idx, shift, val;
|
||||||
|
|
||||||
|
idx = L3C_HW_IDX(idx);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select the appropriate event select register(L3C_EVENT_TYPE0/1).
|
* Select the appropriate event select register(L3C_EVENT_TYPE0/1).
|
||||||
* There are 2 event select registers for the 8 hardware counters.
|
* There are 2 event select registers for the 8 hardware counters.
|
||||||
|
|
@ -259,36 +374,72 @@ static void hisi_l3c_pmu_write_evtype(struct hisi_pmu *l3c_pmu, int idx,
|
||||||
shift = 8 * reg_idx;
|
shift = 8 * reg_idx;
|
||||||
|
|
||||||
/* Write event code to L3C_EVENT_TYPEx Register */
|
/* Write event code to L3C_EVENT_TYPEx Register */
|
||||||
val = readl(l3c_pmu->base + reg);
|
val = hisi_l3c_pmu_event_readl(hwc, reg);
|
||||||
val &= ~(L3C_EVTYPE_NONE << shift);
|
val &= ~(L3C_EVTYPE_NONE << shift);
|
||||||
val |= (type << shift);
|
val |= type << shift;
|
||||||
writel(val, l3c_pmu->base + reg);
|
hisi_l3c_pmu_event_writel(hwc, reg, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_start_counters(struct hisi_pmu *l3c_pmu)
|
static void hisi_l3c_pmu_start_counters(struct hisi_pmu *l3c_pmu)
|
||||||
{
|
{
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
unsigned long *used_mask = l3c_pmu->pmu_events.used_mask;
|
||||||
|
unsigned long used_cntr = find_first_bit(used_mask, l3c_pmu->num_counters);
|
||||||
u32 val;
|
u32 val;
|
||||||
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set perf_enable bit in L3C_PERF_CTRL register to start counting
|
* Check if any counter belongs to the normal range (instead of ext
|
||||||
* for all enabled counters.
|
* range). If so, enable it.
|
||||||
*/
|
*/
|
||||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
if (used_cntr < L3C_NR_COUNTERS) {
|
||||||
val |= L3C_PERF_CTRL_EN;
|
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
||||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
val |= L3C_PERF_CTRL_EN;
|
||||||
|
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If not, do enable it on ext ranges. */
|
||||||
|
for (i = 0; i < hisi_l3c_pmu->ext_num; i++) {
|
||||||
|
/* Find used counter in this ext range, skip the range if not. */
|
||||||
|
used_cntr = find_next_bit(used_mask, L3C_CNTR_EXT_H(i), L3C_CNTR_EXT_L(i));
|
||||||
|
if (used_cntr >= L3C_CNTR_EXT_H(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
val = readl(hisi_l3c_pmu->ext_base[i] + L3C_PERF_CTRL);
|
||||||
|
val |= L3C_PERF_CTRL_EN;
|
||||||
|
writel(val, hisi_l3c_pmu->ext_base[i] + L3C_PERF_CTRL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_stop_counters(struct hisi_pmu *l3c_pmu)
|
static void hisi_l3c_pmu_stop_counters(struct hisi_pmu *l3c_pmu)
|
||||||
{
|
{
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
unsigned long *used_mask = l3c_pmu->pmu_events.used_mask;
|
||||||
|
unsigned long used_cntr = find_first_bit(used_mask, l3c_pmu->num_counters);
|
||||||
u32 val;
|
u32 val;
|
||||||
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Clear perf_enable bit in L3C_PERF_CTRL register to stop counting
|
* Check if any counter belongs to the normal range (instead of ext
|
||||||
* for all enabled counters.
|
* range). If so, stop it.
|
||||||
*/
|
*/
|
||||||
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
if (used_cntr < L3C_NR_COUNTERS) {
|
||||||
val &= ~(L3C_PERF_CTRL_EN);
|
val = readl(l3c_pmu->base + L3C_PERF_CTRL);
|
||||||
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
val &= ~L3C_PERF_CTRL_EN;
|
||||||
|
writel(val, l3c_pmu->base + L3C_PERF_CTRL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If not, do stop it on ext ranges. */
|
||||||
|
for (i = 0; i < hisi_l3c_pmu->ext_num; i++) {
|
||||||
|
/* Find used counter in this ext range, skip the range if not. */
|
||||||
|
used_cntr = find_next_bit(used_mask, L3C_CNTR_EXT_H(i), L3C_CNTR_EXT_L(i));
|
||||||
|
if (used_cntr >= L3C_CNTR_EXT_H(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
val = readl(hisi_l3c_pmu->ext_base[i] + L3C_PERF_CTRL);
|
||||||
|
val &= ~L3C_PERF_CTRL_EN;
|
||||||
|
writel(val, hisi_l3c_pmu->ext_base[i] + L3C_PERF_CTRL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_enable_counter(struct hisi_pmu *l3c_pmu,
|
static void hisi_l3c_pmu_enable_counter(struct hisi_pmu *l3c_pmu,
|
||||||
|
|
@ -297,9 +448,9 @@ static void hisi_l3c_pmu_enable_counter(struct hisi_pmu *l3c_pmu,
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
/* Enable counter index in L3C_EVENT_CTRL register */
|
/* Enable counter index in L3C_EVENT_CTRL register */
|
||||||
val = readl(l3c_pmu->base + L3C_EVENT_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_EVENT_CTRL);
|
||||||
val |= (1 << hwc->idx);
|
val |= 1 << L3C_HW_IDX(hwc->idx);
|
||||||
writel(val, l3c_pmu->base + L3C_EVENT_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_EVENT_CTRL, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_disable_counter(struct hisi_pmu *l3c_pmu,
|
static void hisi_l3c_pmu_disable_counter(struct hisi_pmu *l3c_pmu,
|
||||||
|
|
@ -308,9 +459,9 @@ static void hisi_l3c_pmu_disable_counter(struct hisi_pmu *l3c_pmu,
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
/* Clear counter index in L3C_EVENT_CTRL register */
|
/* Clear counter index in L3C_EVENT_CTRL register */
|
||||||
val = readl(l3c_pmu->base + L3C_EVENT_CTRL);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_EVENT_CTRL);
|
||||||
val &= ~(1 << hwc->idx);
|
val &= ~(1 << L3C_HW_IDX(hwc->idx));
|
||||||
writel(val, l3c_pmu->base + L3C_EVENT_CTRL);
|
hisi_l3c_pmu_event_writel(hwc, L3C_EVENT_CTRL, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_enable_counter_int(struct hisi_pmu *l3c_pmu,
|
static void hisi_l3c_pmu_enable_counter_int(struct hisi_pmu *l3c_pmu,
|
||||||
|
|
@ -318,10 +469,10 @@ static void hisi_l3c_pmu_enable_counter_int(struct hisi_pmu *l3c_pmu,
|
||||||
{
|
{
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
val = readl(l3c_pmu->base + L3C_INT_MASK);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_INT_MASK);
|
||||||
/* Write 0 to enable interrupt */
|
/* Write 0 to enable interrupt */
|
||||||
val &= ~(1 << hwc->idx);
|
val &= ~(1 << L3C_HW_IDX(hwc->idx));
|
||||||
writel(val, l3c_pmu->base + L3C_INT_MASK);
|
hisi_l3c_pmu_event_writel(hwc, L3C_INT_MASK, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_disable_counter_int(struct hisi_pmu *l3c_pmu,
|
static void hisi_l3c_pmu_disable_counter_int(struct hisi_pmu *l3c_pmu,
|
||||||
|
|
@ -329,28 +480,37 @@ static void hisi_l3c_pmu_disable_counter_int(struct hisi_pmu *l3c_pmu,
|
||||||
{
|
{
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
val = readl(l3c_pmu->base + L3C_INT_MASK);
|
val = hisi_l3c_pmu_event_readl(hwc, L3C_INT_MASK);
|
||||||
/* Write 1 to mask interrupt */
|
/* Write 1 to mask interrupt */
|
||||||
val |= (1 << hwc->idx);
|
val |= 1 << L3C_HW_IDX(hwc->idx);
|
||||||
writel(val, l3c_pmu->base + L3C_INT_MASK);
|
hisi_l3c_pmu_event_writel(hwc, L3C_INT_MASK, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 hisi_l3c_pmu_get_int_status(struct hisi_pmu *l3c_pmu)
|
static u32 hisi_l3c_pmu_get_int_status(struct hisi_pmu *l3c_pmu)
|
||||||
{
|
{
|
||||||
return readl(l3c_pmu->base + L3C_INT_STATUS);
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
u32 ext_int, status, status_ext = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
status = readl(l3c_pmu->base + L3C_INT_STATUS);
|
||||||
|
|
||||||
|
if (!support_ext(hisi_l3c_pmu))
|
||||||
|
return status;
|
||||||
|
|
||||||
|
for (i = 0; i < hisi_l3c_pmu->ext_num; i++) {
|
||||||
|
ext_int = readl(hisi_l3c_pmu->ext_base[i] + L3C_INT_STATUS);
|
||||||
|
status_ext |= ext_int << (L3C_NR_COUNTERS * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status | (status_ext << L3C_NR_COUNTERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hisi_l3c_pmu_clear_int_status(struct hisi_pmu *l3c_pmu, int idx)
|
static void hisi_l3c_pmu_clear_int_status(struct hisi_pmu *l3c_pmu, int idx)
|
||||||
{
|
{
|
||||||
writel(1 << idx, l3c_pmu->base + L3C_INT_CLEAR);
|
struct hw_perf_event *hwc = &l3c_pmu->pmu_events.hw_events[idx]->hw;
|
||||||
}
|
|
||||||
|
|
||||||
static const struct acpi_device_id hisi_l3c_pmu_acpi_match[] = {
|
hisi_l3c_pmu_event_writel(hwc, L3C_INT_CLEAR, 1 << L3C_HW_IDX(idx));
|
||||||
{ "HISI0213", },
|
}
|
||||||
{ "HISI0214", },
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
MODULE_DEVICE_TABLE(acpi, hisi_l3c_pmu_acpi_match);
|
|
||||||
|
|
||||||
static int hisi_l3c_pmu_init_data(struct platform_device *pdev,
|
static int hisi_l3c_pmu_init_data(struct platform_device *pdev,
|
||||||
struct hisi_pmu *l3c_pmu)
|
struct hisi_pmu *l3c_pmu)
|
||||||
|
|
@ -371,6 +531,10 @@ static int hisi_l3c_pmu_init_data(struct platform_device *pdev,
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l3c_pmu->dev_info = device_get_match_data(&pdev->dev);
|
||||||
|
if (!l3c_pmu->dev_info)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
l3c_pmu->base = devm_platform_ioremap_resource(pdev, 0);
|
l3c_pmu->base = devm_platform_ioremap_resource(pdev, 0);
|
||||||
if (IS_ERR(l3c_pmu->base)) {
|
if (IS_ERR(l3c_pmu->base)) {
|
||||||
dev_err(&pdev->dev, "ioremap failed for l3c_pmu resource\n");
|
dev_err(&pdev->dev, "ioremap failed for l3c_pmu resource\n");
|
||||||
|
|
@ -382,6 +546,50 @@ static int hisi_l3c_pmu_init_data(struct platform_device *pdev,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int hisi_l3c_pmu_init_ext(struct hisi_pmu *l3c_pmu, struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
int ret, irq, ext_num, i;
|
||||||
|
char *irqname;
|
||||||
|
|
||||||
|
/* HiSilicon L3C PMU supporting ext should have more than 1 irq resources. */
|
||||||
|
ext_num = platform_irq_count(pdev);
|
||||||
|
if (ext_num < L3C_MAX_EXT)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The number of ext supported equals the number of irq - 1, since one
|
||||||
|
* of the irqs belongs to the normal part of PMU.
|
||||||
|
*/
|
||||||
|
hisi_l3c_pmu->ext_num = ext_num - 1;
|
||||||
|
|
||||||
|
for (i = 0; i < hisi_l3c_pmu->ext_num; i++) {
|
||||||
|
hisi_l3c_pmu->ext_base[i] = devm_platform_ioremap_resource(pdev, i + 1);
|
||||||
|
if (IS_ERR(hisi_l3c_pmu->ext_base[i]))
|
||||||
|
return PTR_ERR(hisi_l3c_pmu->ext_base[i]);
|
||||||
|
|
||||||
|
irq = platform_get_irq(pdev, i + 1);
|
||||||
|
if (irq < 0)
|
||||||
|
return irq;
|
||||||
|
|
||||||
|
irqname = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s ext%d",
|
||||||
|
dev_name(&pdev->dev), i + 1);
|
||||||
|
if (!irqname)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = devm_request_irq(&pdev->dev, irq, hisi_uncore_pmu_isr,
|
||||||
|
IRQF_NOBALANCING | IRQF_NO_THREAD,
|
||||||
|
irqname, l3c_pmu);
|
||||||
|
if (ret < 0)
|
||||||
|
return dev_err_probe(&pdev->dev, ret,
|
||||||
|
"Fail to request EXT IRQ: %d.\n", irq);
|
||||||
|
|
||||||
|
hisi_l3c_pmu->ext_irq[i] = irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static struct attribute *hisi_l3c_pmu_v1_format_attr[] = {
|
static struct attribute *hisi_l3c_pmu_v1_format_attr[] = {
|
||||||
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -394,7 +602,7 @@ static const struct attribute_group hisi_l3c_pmu_v1_format_group = {
|
||||||
|
|
||||||
static struct attribute *hisi_l3c_pmu_v2_format_attr[] = {
|
static struct attribute *hisi_l3c_pmu_v2_format_attr[] = {
|
||||||
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||||
HISI_PMU_FORMAT_ATTR(tt_core, "config1:0-7"),
|
HISI_PMU_FORMAT_ATTR(tt_core, "config2:0-15"),
|
||||||
HISI_PMU_FORMAT_ATTR(tt_req, "config1:8-10"),
|
HISI_PMU_FORMAT_ATTR(tt_req, "config1:8-10"),
|
||||||
HISI_PMU_FORMAT_ATTR(datasrc_cfg, "config1:11-15"),
|
HISI_PMU_FORMAT_ATTR(datasrc_cfg, "config1:11-15"),
|
||||||
HISI_PMU_FORMAT_ATTR(datasrc_skt, "config1:16"),
|
HISI_PMU_FORMAT_ATTR(datasrc_skt, "config1:16"),
|
||||||
|
|
@ -406,6 +614,19 @@ static const struct attribute_group hisi_l3c_pmu_v2_format_group = {
|
||||||
.attrs = hisi_l3c_pmu_v2_format_attr,
|
.attrs = hisi_l3c_pmu_v2_format_attr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct attribute *hisi_l3c_pmu_v3_format_attr[] = {
|
||||||
|
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||||
|
HISI_PMU_FORMAT_ATTR(ext, "config:16-17"),
|
||||||
|
HISI_PMU_FORMAT_ATTR(tt_req, "config1:8-10"),
|
||||||
|
HISI_PMU_FORMAT_ATTR(tt_core, "config2:0-15"),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group hisi_l3c_pmu_v3_format_group = {
|
||||||
|
.name = "format",
|
||||||
|
.attrs = hisi_l3c_pmu_v3_format_attr,
|
||||||
|
};
|
||||||
|
|
||||||
static struct attribute *hisi_l3c_pmu_v1_events_attr[] = {
|
static struct attribute *hisi_l3c_pmu_v1_events_attr[] = {
|
||||||
HISI_PMU_EVENT_ATTR(rd_cpipe, 0x00),
|
HISI_PMU_EVENT_ATTR(rd_cpipe, 0x00),
|
||||||
HISI_PMU_EVENT_ATTR(wr_cpipe, 0x01),
|
HISI_PMU_EVENT_ATTR(wr_cpipe, 0x01),
|
||||||
|
|
@ -441,6 +662,26 @@ static const struct attribute_group hisi_l3c_pmu_v2_events_group = {
|
||||||
.attrs = hisi_l3c_pmu_v2_events_attr,
|
.attrs = hisi_l3c_pmu_v2_events_attr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct attribute *hisi_l3c_pmu_v3_events_attr[] = {
|
||||||
|
HISI_PMU_EVENT_ATTR(rd_spipe, 0x18),
|
||||||
|
HISI_PMU_EVENT_ATTR(rd_hit_spipe, 0x19),
|
||||||
|
HISI_PMU_EVENT_ATTR(wr_spipe, 0x1a),
|
||||||
|
HISI_PMU_EVENT_ATTR(wr_hit_spipe, 0x1b),
|
||||||
|
HISI_PMU_EVENT_ATTR(io_rd_spipe, 0x1c),
|
||||||
|
HISI_PMU_EVENT_ATTR(io_rd_hit_spipe, 0x1d),
|
||||||
|
HISI_PMU_EVENT_ATTR(io_wr_spipe, 0x1e),
|
||||||
|
HISI_PMU_EVENT_ATTR(io_wr_hit_spipe, 0x1f),
|
||||||
|
HISI_PMU_EVENT_ATTR(cycles, 0x7f),
|
||||||
|
HISI_PMU_EVENT_ATTR(l3c_ref, 0xbc),
|
||||||
|
HISI_PMU_EVENT_ATTR(l3c2ring, 0xbd),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group hisi_l3c_pmu_v3_events_group = {
|
||||||
|
.name = "events",
|
||||||
|
.attrs = hisi_l3c_pmu_v3_events_attr,
|
||||||
|
};
|
||||||
|
|
||||||
static const struct attribute_group *hisi_l3c_pmu_v1_attr_groups[] = {
|
static const struct attribute_group *hisi_l3c_pmu_v1_attr_groups[] = {
|
||||||
&hisi_l3c_pmu_v1_format_group,
|
&hisi_l3c_pmu_v1_format_group,
|
||||||
&hisi_l3c_pmu_v1_events_group,
|
&hisi_l3c_pmu_v1_events_group,
|
||||||
|
|
@ -457,9 +698,46 @@ static const struct attribute_group *hisi_l3c_pmu_v2_attr_groups[] = {
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group *hisi_l3c_pmu_v3_attr_groups[] = {
|
||||||
|
&hisi_l3c_pmu_v3_format_group,
|
||||||
|
&hisi_l3c_pmu_v3_events_group,
|
||||||
|
&hisi_pmu_cpumask_attr_group,
|
||||||
|
&hisi_pmu_identifier_group,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct hisi_l3c_pmu_ext hisi_l3c_pmu_support_ext = {
|
||||||
|
.support_ext = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct hisi_l3c_pmu_ext hisi_l3c_pmu_not_support_ext = {
|
||||||
|
.support_ext = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hisi_pmu_dev_info hisi_l3c_pmu_v1 = {
|
||||||
|
.attr_groups = hisi_l3c_pmu_v1_attr_groups,
|
||||||
|
.counter_bits = 48,
|
||||||
|
.check_event = L3C_V1_NR_EVENTS,
|
||||||
|
.private = &hisi_l3c_pmu_not_support_ext,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hisi_pmu_dev_info hisi_l3c_pmu_v2 = {
|
||||||
|
.attr_groups = hisi_l3c_pmu_v2_attr_groups,
|
||||||
|
.counter_bits = 64,
|
||||||
|
.check_event = L3C_V2_NR_EVENTS,
|
||||||
|
.private = &hisi_l3c_pmu_not_support_ext,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hisi_pmu_dev_info hisi_l3c_pmu_v3 = {
|
||||||
|
.attr_groups = hisi_l3c_pmu_v3_attr_groups,
|
||||||
|
.counter_bits = 64,
|
||||||
|
.check_event = L3C_V2_NR_EVENTS,
|
||||||
|
.private = &hisi_l3c_pmu_support_ext,
|
||||||
|
};
|
||||||
|
|
||||||
static const struct hisi_uncore_ops hisi_uncore_l3c_ops = {
|
static const struct hisi_uncore_ops hisi_uncore_l3c_ops = {
|
||||||
.write_evtype = hisi_l3c_pmu_write_evtype,
|
.write_evtype = hisi_l3c_pmu_write_evtype,
|
||||||
.get_event_idx = hisi_uncore_pmu_get_event_idx,
|
.get_event_idx = hisi_l3c_pmu_get_event_idx,
|
||||||
.start_counters = hisi_l3c_pmu_start_counters,
|
.start_counters = hisi_l3c_pmu_start_counters,
|
||||||
.stop_counters = hisi_l3c_pmu_stop_counters,
|
.stop_counters = hisi_l3c_pmu_stop_counters,
|
||||||
.enable_counter = hisi_l3c_pmu_enable_counter,
|
.enable_counter = hisi_l3c_pmu_enable_counter,
|
||||||
|
|
@ -472,11 +750,14 @@ static const struct hisi_uncore_ops hisi_uncore_l3c_ops = {
|
||||||
.clear_int_status = hisi_l3c_pmu_clear_int_status,
|
.clear_int_status = hisi_l3c_pmu_clear_int_status,
|
||||||
.enable_filter = hisi_l3c_pmu_enable_filter,
|
.enable_filter = hisi_l3c_pmu_enable_filter,
|
||||||
.disable_filter = hisi_l3c_pmu_disable_filter,
|
.disable_filter = hisi_l3c_pmu_disable_filter,
|
||||||
|
.check_filter = hisi_l3c_pmu_check_filter,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int hisi_l3c_pmu_dev_probe(struct platform_device *pdev,
|
static int hisi_l3c_pmu_dev_probe(struct platform_device *pdev,
|
||||||
struct hisi_pmu *l3c_pmu)
|
struct hisi_pmu *l3c_pmu)
|
||||||
{
|
{
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
struct hisi_l3c_pmu_ext *l3c_pmu_dev_ext;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = hisi_l3c_pmu_init_data(pdev, l3c_pmu);
|
ret = hisi_l3c_pmu_init_data(pdev, l3c_pmu);
|
||||||
|
|
@ -487,42 +768,55 @@ static int hisi_l3c_pmu_dev_probe(struct platform_device *pdev,
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (l3c_pmu->identifier >= HISI_PMU_V2) {
|
l3c_pmu->pmu_events.attr_groups = l3c_pmu->dev_info->attr_groups;
|
||||||
l3c_pmu->counter_bits = 64;
|
l3c_pmu->counter_bits = l3c_pmu->dev_info->counter_bits;
|
||||||
l3c_pmu->check_event = L3C_V2_NR_EVENTS;
|
l3c_pmu->check_event = l3c_pmu->dev_info->check_event;
|
||||||
l3c_pmu->pmu_events.attr_groups = hisi_l3c_pmu_v2_attr_groups;
|
|
||||||
} else {
|
|
||||||
l3c_pmu->counter_bits = 48;
|
|
||||||
l3c_pmu->check_event = L3C_V1_NR_EVENTS;
|
|
||||||
l3c_pmu->pmu_events.attr_groups = hisi_l3c_pmu_v1_attr_groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
l3c_pmu->num_counters = L3C_NR_COUNTERS;
|
l3c_pmu->num_counters = L3C_NR_COUNTERS;
|
||||||
l3c_pmu->ops = &hisi_uncore_l3c_ops;
|
l3c_pmu->ops = &hisi_uncore_l3c_ops;
|
||||||
l3c_pmu->dev = &pdev->dev;
|
l3c_pmu->dev = &pdev->dev;
|
||||||
l3c_pmu->on_cpu = -1;
|
l3c_pmu->on_cpu = -1;
|
||||||
|
|
||||||
|
l3c_pmu_dev_ext = l3c_pmu->dev_info->private;
|
||||||
|
if (l3c_pmu_dev_ext->support_ext) {
|
||||||
|
ret = hisi_l3c_pmu_init_ext(l3c_pmu, pdev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
/*
|
||||||
|
* The extension events have their own counters with the
|
||||||
|
* same number of the normal events counters. So we can
|
||||||
|
* have at maximum num_counters * ext events monitored.
|
||||||
|
*/
|
||||||
|
l3c_pmu->num_counters += hisi_l3c_pmu->ext_num * L3C_NR_COUNTERS;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hisi_l3c_pmu_probe(struct platform_device *pdev)
|
static int hisi_l3c_pmu_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu;
|
||||||
struct hisi_pmu *l3c_pmu;
|
struct hisi_pmu *l3c_pmu;
|
||||||
char *name;
|
char *name;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
l3c_pmu = devm_kzalloc(&pdev->dev, sizeof(*l3c_pmu), GFP_KERNEL);
|
hisi_l3c_pmu = devm_kzalloc(&pdev->dev, sizeof(*hisi_l3c_pmu), GFP_KERNEL);
|
||||||
if (!l3c_pmu)
|
if (!hisi_l3c_pmu)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
l3c_pmu = &hisi_l3c_pmu->l3c_pmu;
|
||||||
platform_set_drvdata(pdev, l3c_pmu);
|
platform_set_drvdata(pdev, l3c_pmu);
|
||||||
|
|
||||||
ret = hisi_l3c_pmu_dev_probe(pdev, l3c_pmu);
|
ret = hisi_l3c_pmu_dev_probe(pdev, l3c_pmu);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%d_l3c%d",
|
if (l3c_pmu->topo.sub_id >= 0)
|
||||||
l3c_pmu->topo.sccl_id, l3c_pmu->topo.ccl_id);
|
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%d_l3c%d_%d",
|
||||||
|
l3c_pmu->topo.sccl_id, l3c_pmu->topo.ccl_id,
|
||||||
|
l3c_pmu->topo.sub_id);
|
||||||
|
else
|
||||||
|
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_sccl%d_l3c%d",
|
||||||
|
l3c_pmu->topo.sccl_id, l3c_pmu->topo.ccl_id);
|
||||||
if (!name)
|
if (!name)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
|
@ -554,6 +848,14 @@ static void hisi_l3c_pmu_remove(struct platform_device *pdev)
|
||||||
&l3c_pmu->node);
|
&l3c_pmu->node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct acpi_device_id hisi_l3c_pmu_acpi_match[] = {
|
||||||
|
{ "HISI0213", (kernel_ulong_t)&hisi_l3c_pmu_v1 },
|
||||||
|
{ "HISI0214", (kernel_ulong_t)&hisi_l3c_pmu_v2 },
|
||||||
|
{ "HISI0215", (kernel_ulong_t)&hisi_l3c_pmu_v3 },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, hisi_l3c_pmu_acpi_match);
|
||||||
|
|
||||||
static struct platform_driver hisi_l3c_pmu_driver = {
|
static struct platform_driver hisi_l3c_pmu_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "hisi_l3c_pmu",
|
.name = "hisi_l3c_pmu",
|
||||||
|
|
@ -564,14 +866,60 @@ static struct platform_driver hisi_l3c_pmu_driver = {
|
||||||
.remove = hisi_l3c_pmu_remove,
|
.remove = hisi_l3c_pmu_remove,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int hisi_l3c_pmu_online_cpu(unsigned int cpu, struct hlist_node *node)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *l3c_pmu = hlist_entry_safe(node, struct hisi_pmu, node);
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
int ret, i;
|
||||||
|
|
||||||
|
ret = hisi_uncore_pmu_online_cpu(cpu, node);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Avoid L3C pmu not supporting ext from ext irq migrating. */
|
||||||
|
if (!support_ext(hisi_l3c_pmu))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 0; i < hisi_l3c_pmu->ext_num; i++)
|
||||||
|
WARN_ON(irq_set_affinity(hisi_l3c_pmu->ext_irq[i],
|
||||||
|
cpumask_of(l3c_pmu->on_cpu)));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hisi_l3c_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *l3c_pmu = hlist_entry_safe(node, struct hisi_pmu, node);
|
||||||
|
struct hisi_l3c_pmu *hisi_l3c_pmu = to_hisi_l3c_pmu(l3c_pmu);
|
||||||
|
int ret, i;
|
||||||
|
|
||||||
|
ret = hisi_uncore_pmu_offline_cpu(cpu, node);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* If failed to find any available CPU, skip irq migration. */
|
||||||
|
if (l3c_pmu->on_cpu < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Avoid L3C pmu not supporting ext from ext irq migrating. */
|
||||||
|
if (!support_ext(hisi_l3c_pmu))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 0; i < hisi_l3c_pmu->ext_num; i++)
|
||||||
|
WARN_ON(irq_set_affinity(hisi_l3c_pmu->ext_irq[i],
|
||||||
|
cpumask_of(l3c_pmu->on_cpu)));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int __init hisi_l3c_pmu_module_init(void)
|
static int __init hisi_l3c_pmu_module_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE,
|
ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE,
|
||||||
"AP_PERF_ARM_HISI_L3_ONLINE",
|
"AP_PERF_ARM_HISI_L3_ONLINE",
|
||||||
hisi_uncore_pmu_online_cpu,
|
hisi_l3c_pmu_online_cpu,
|
||||||
hisi_uncore_pmu_offline_cpu);
|
hisi_l3c_pmu_offline_cpu);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err("L3C PMU: Error setup hotplug, ret = %d\n", ret);
|
pr_err("L3C PMU: Error setup hotplug, ret = %d\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,411 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* HiSilicon SoC MN uncore Hardware event counters support
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 HiSilicon Technologies Co., Ltd.
|
||||||
|
*/
|
||||||
|
#include <linux/cpuhotplug.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/iopoll.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/mod_devicetable.h>
|
||||||
|
#include <linux/property.h>
|
||||||
|
|
||||||
|
#include "hisi_uncore_pmu.h"
|
||||||
|
|
||||||
|
/* Dynamic CPU hotplug state used by MN PMU */
|
||||||
|
static enum cpuhp_state hisi_mn_pmu_online;
|
||||||
|
|
||||||
|
/* MN register definition */
|
||||||
|
#define HISI_MN_DYNAMIC_CTRL_REG 0x400
|
||||||
|
#define HISI_MN_DYNAMIC_CTRL_EN BIT(0)
|
||||||
|
#define HISI_MN_PERF_CTRL_REG 0x408
|
||||||
|
#define HISI_MN_PERF_CTRL_EN BIT(6)
|
||||||
|
#define HISI_MN_INT_MASK_REG 0x800
|
||||||
|
#define HISI_MN_INT_STATUS_REG 0x808
|
||||||
|
#define HISI_MN_INT_CLEAR_REG 0x80C
|
||||||
|
#define HISI_MN_EVENT_CTRL_REG 0x1C00
|
||||||
|
#define HISI_MN_VERSION_REG 0x1C04
|
||||||
|
#define HISI_MN_EVTYPE0_REG 0x1d00
|
||||||
|
#define HISI_MN_EVTYPE_MASK GENMASK(7, 0)
|
||||||
|
#define HISI_MN_CNTR0_REG 0x1e00
|
||||||
|
#define HISI_MN_EVTYPE_REGn(evtype0, n) ((evtype0) + (n) * 4)
|
||||||
|
#define HISI_MN_CNTR_REGn(cntr0, n) ((cntr0) + (n) * 8)
|
||||||
|
|
||||||
|
#define HISI_MN_NR_COUNTERS 4
|
||||||
|
#define HISI_MN_TIMEOUT_US 500U
|
||||||
|
|
||||||
|
struct hisi_mn_pmu_regs {
|
||||||
|
u32 version;
|
||||||
|
u32 dyn_ctrl;
|
||||||
|
u32 perf_ctrl;
|
||||||
|
u32 int_mask;
|
||||||
|
u32 int_clear;
|
||||||
|
u32 int_status;
|
||||||
|
u32 event_ctrl;
|
||||||
|
u32 event_type0;
|
||||||
|
u32 event_cntr0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each event request takes a certain amount of time to complete. If
|
||||||
|
* we counting the latency related event, we need to wait for the all
|
||||||
|
* requests complete. Otherwise, the value of counter is slightly larger.
|
||||||
|
*/
|
||||||
|
static void hisi_mn_pmu_counter_flush(struct hisi_pmu *mn_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
int ret;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(mn_pmu->base + reg_info->dyn_ctrl);
|
||||||
|
val |= HISI_MN_DYNAMIC_CTRL_EN;
|
||||||
|
writel(val, mn_pmu->base + reg_info->dyn_ctrl);
|
||||||
|
|
||||||
|
ret = readl_poll_timeout_atomic(mn_pmu->base + reg_info->dyn_ctrl,
|
||||||
|
val, !(val & HISI_MN_DYNAMIC_CTRL_EN),
|
||||||
|
1, HISI_MN_TIMEOUT_US);
|
||||||
|
if (ret)
|
||||||
|
dev_warn(mn_pmu->dev, "Counter flush timeout\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 hisi_mn_pmu_read_counter(struct hisi_pmu *mn_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
|
||||||
|
return readq(mn_pmu->base + HISI_MN_CNTR_REGn(reg_info->event_cntr0, hwc->idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_write_counter(struct hisi_pmu *mn_pmu,
|
||||||
|
struct hw_perf_event *hwc, u64 val)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
|
||||||
|
writeq(val, mn_pmu->base + HISI_MN_CNTR_REGn(reg_info->event_cntr0, hwc->idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_write_evtype(struct hisi_pmu *mn_pmu, int idx, u32 type)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Select the appropriate event select register.
|
||||||
|
* There are 2 32-bit event select registers for the
|
||||||
|
* 8 hardware counters, each event code is 8-bit wide.
|
||||||
|
*/
|
||||||
|
val = readl(mn_pmu->base + HISI_MN_EVTYPE_REGn(reg_info->event_type0, idx / 4));
|
||||||
|
val &= ~(HISI_MN_EVTYPE_MASK << HISI_PMU_EVTYPE_SHIFT(idx));
|
||||||
|
val |= (type << HISI_PMU_EVTYPE_SHIFT(idx));
|
||||||
|
writel(val, mn_pmu->base + HISI_MN_EVTYPE_REGn(reg_info->event_type0, idx / 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_start_counters(struct hisi_pmu *mn_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(mn_pmu->base + reg_info->perf_ctrl);
|
||||||
|
val |= HISI_MN_PERF_CTRL_EN;
|
||||||
|
writel(val, mn_pmu->base + reg_info->perf_ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_stop_counters(struct hisi_pmu *mn_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(mn_pmu->base + reg_info->perf_ctrl);
|
||||||
|
val &= ~HISI_MN_PERF_CTRL_EN;
|
||||||
|
writel(val, mn_pmu->base + reg_info->perf_ctrl);
|
||||||
|
|
||||||
|
hisi_mn_pmu_counter_flush(mn_pmu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_enable_counter(struct hisi_pmu *mn_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(mn_pmu->base + reg_info->event_ctrl);
|
||||||
|
val |= BIT(hwc->idx);
|
||||||
|
writel(val, mn_pmu->base + reg_info->event_ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_disable_counter(struct hisi_pmu *mn_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(mn_pmu->base + reg_info->event_ctrl);
|
||||||
|
val &= ~BIT(hwc->idx);
|
||||||
|
writel(val, mn_pmu->base + reg_info->event_ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_enable_counter_int(struct hisi_pmu *mn_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(mn_pmu->base + reg_info->int_mask);
|
||||||
|
val &= ~BIT(hwc->idx);
|
||||||
|
writel(val, mn_pmu->base + reg_info->int_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_disable_counter_int(struct hisi_pmu *mn_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
val = readl(mn_pmu->base + reg_info->int_mask);
|
||||||
|
val |= BIT(hwc->idx);
|
||||||
|
writel(val, mn_pmu->base + reg_info->int_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 hisi_mn_pmu_get_int_status(struct hisi_pmu *mn_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
|
||||||
|
return readl(mn_pmu->base + reg_info->int_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_clear_int_status(struct hisi_pmu *mn_pmu, int idx)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info = mn_pmu->dev_info->private;
|
||||||
|
|
||||||
|
writel(BIT(idx), mn_pmu->base + reg_info->int_clear);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct attribute *hisi_mn_pmu_format_attr[] = {
|
||||||
|
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group hisi_mn_pmu_format_group = {
|
||||||
|
.name = "format",
|
||||||
|
.attrs = hisi_mn_pmu_format_attr,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute *hisi_mn_pmu_events_attr[] = {
|
||||||
|
HISI_PMU_EVENT_ATTR(req_eobarrier_num, 0x00),
|
||||||
|
HISI_PMU_EVENT_ATTR(req_ecbarrier_num, 0x01),
|
||||||
|
HISI_PMU_EVENT_ATTR(req_dvmop_num, 0x02),
|
||||||
|
HISI_PMU_EVENT_ATTR(req_dvmsync_num, 0x03),
|
||||||
|
HISI_PMU_EVENT_ATTR(req_retry_num, 0x04),
|
||||||
|
HISI_PMU_EVENT_ATTR(req_writenosnp_num, 0x05),
|
||||||
|
HISI_PMU_EVENT_ATTR(req_readnosnp_num, 0x06),
|
||||||
|
HISI_PMU_EVENT_ATTR(snp_dvm_num, 0x07),
|
||||||
|
HISI_PMU_EVENT_ATTR(snp_dvmsync_num, 0x08),
|
||||||
|
HISI_PMU_EVENT_ATTR(l3t_req_dvm_num, 0x09),
|
||||||
|
HISI_PMU_EVENT_ATTR(l3t_req_dvmsync_num, 0x0A),
|
||||||
|
HISI_PMU_EVENT_ATTR(mn_req_dvm_num, 0x0B),
|
||||||
|
HISI_PMU_EVENT_ATTR(mn_req_dvmsync_num, 0x0C),
|
||||||
|
HISI_PMU_EVENT_ATTR(pa_req_dvm_num, 0x0D),
|
||||||
|
HISI_PMU_EVENT_ATTR(pa_req_dvmsync_num, 0x0E),
|
||||||
|
HISI_PMU_EVENT_ATTR(snp_dvm_latency, 0x80),
|
||||||
|
HISI_PMU_EVENT_ATTR(snp_dvmsync_latency, 0x81),
|
||||||
|
HISI_PMU_EVENT_ATTR(l3t_req_dvm_latency, 0x82),
|
||||||
|
HISI_PMU_EVENT_ATTR(l3t_req_dvmsync_latency, 0x83),
|
||||||
|
HISI_PMU_EVENT_ATTR(mn_req_dvm_latency, 0x84),
|
||||||
|
HISI_PMU_EVENT_ATTR(mn_req_dvmsync_latency, 0x85),
|
||||||
|
HISI_PMU_EVENT_ATTR(pa_req_dvm_latency, 0x86),
|
||||||
|
HISI_PMU_EVENT_ATTR(pa_req_dvmsync_latency, 0x87),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group hisi_mn_pmu_events_group = {
|
||||||
|
.name = "events",
|
||||||
|
.attrs = hisi_mn_pmu_events_attr,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group *hisi_mn_pmu_attr_groups[] = {
|
||||||
|
&hisi_mn_pmu_format_group,
|
||||||
|
&hisi_mn_pmu_events_group,
|
||||||
|
&hisi_pmu_cpumask_attr_group,
|
||||||
|
&hisi_pmu_identifier_group,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hisi_uncore_ops hisi_uncore_mn_ops = {
|
||||||
|
.write_evtype = hisi_mn_pmu_write_evtype,
|
||||||
|
.get_event_idx = hisi_uncore_pmu_get_event_idx,
|
||||||
|
.start_counters = hisi_mn_pmu_start_counters,
|
||||||
|
.stop_counters = hisi_mn_pmu_stop_counters,
|
||||||
|
.enable_counter = hisi_mn_pmu_enable_counter,
|
||||||
|
.disable_counter = hisi_mn_pmu_disable_counter,
|
||||||
|
.enable_counter_int = hisi_mn_pmu_enable_counter_int,
|
||||||
|
.disable_counter_int = hisi_mn_pmu_disable_counter_int,
|
||||||
|
.write_counter = hisi_mn_pmu_write_counter,
|
||||||
|
.read_counter = hisi_mn_pmu_read_counter,
|
||||||
|
.get_int_status = hisi_mn_pmu_get_int_status,
|
||||||
|
.clear_int_status = hisi_mn_pmu_clear_int_status,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int hisi_mn_pmu_dev_init(struct platform_device *pdev,
|
||||||
|
struct hisi_pmu *mn_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_mn_pmu_regs *reg_info;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
hisi_uncore_pmu_init_topology(mn_pmu, &pdev->dev);
|
||||||
|
|
||||||
|
if (mn_pmu->topo.scl_id < 0)
|
||||||
|
return dev_err_probe(&pdev->dev, -EINVAL,
|
||||||
|
"Failed to read MN scl id\n");
|
||||||
|
|
||||||
|
if (mn_pmu->topo.index_id < 0)
|
||||||
|
return dev_err_probe(&pdev->dev, -EINVAL,
|
||||||
|
"Failed to read MN index id\n");
|
||||||
|
|
||||||
|
mn_pmu->base = devm_platform_ioremap_resource(pdev, 0);
|
||||||
|
if (IS_ERR(mn_pmu->base))
|
||||||
|
return dev_err_probe(&pdev->dev, PTR_ERR(mn_pmu->base),
|
||||||
|
"Failed to ioremap resource\n");
|
||||||
|
|
||||||
|
ret = hisi_uncore_pmu_init_irq(mn_pmu, pdev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
mn_pmu->dev_info = device_get_match_data(&pdev->dev);
|
||||||
|
if (!mn_pmu->dev_info)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
mn_pmu->pmu_events.attr_groups = mn_pmu->dev_info->attr_groups;
|
||||||
|
mn_pmu->counter_bits = mn_pmu->dev_info->counter_bits;
|
||||||
|
mn_pmu->check_event = mn_pmu->dev_info->check_event;
|
||||||
|
mn_pmu->num_counters = HISI_MN_NR_COUNTERS;
|
||||||
|
mn_pmu->ops = &hisi_uncore_mn_ops;
|
||||||
|
mn_pmu->dev = &pdev->dev;
|
||||||
|
mn_pmu->on_cpu = -1;
|
||||||
|
|
||||||
|
reg_info = mn_pmu->dev_info->private;
|
||||||
|
mn_pmu->identifier = readl(mn_pmu->base + reg_info->version);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_remove_cpuhp(void *hotplug_node)
|
||||||
|
{
|
||||||
|
cpuhp_state_remove_instance_nocalls(hisi_mn_pmu_online, hotplug_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_mn_pmu_unregister(void *pmu)
|
||||||
|
{
|
||||||
|
perf_pmu_unregister(pmu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hisi_mn_pmu_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *mn_pmu;
|
||||||
|
char *name;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mn_pmu = devm_kzalloc(&pdev->dev, sizeof(*mn_pmu), GFP_KERNEL);
|
||||||
|
if (!mn_pmu)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, mn_pmu);
|
||||||
|
|
||||||
|
ret = hisi_mn_pmu_dev_init(pdev, mn_pmu);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "hisi_scl%d_mn%d",
|
||||||
|
mn_pmu->topo.scl_id, mn_pmu->topo.index_id);
|
||||||
|
if (!name)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = cpuhp_state_add_instance(hisi_mn_pmu_online, &mn_pmu->node);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(&pdev->dev, ret, "Failed to register cpu hotplug\n");
|
||||||
|
|
||||||
|
ret = devm_add_action_or_reset(&pdev->dev, hisi_mn_pmu_remove_cpuhp, &mn_pmu->node);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hisi_pmu_init(mn_pmu, THIS_MODULE);
|
||||||
|
|
||||||
|
ret = perf_pmu_register(&mn_pmu->pmu, name, -1);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(mn_pmu->dev, ret, "Failed to register MN PMU\n");
|
||||||
|
|
||||||
|
return devm_add_action_or_reset(&pdev->dev, hisi_mn_pmu_unregister, &mn_pmu->pmu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct hisi_mn_pmu_regs hisi_mn_v1_pmu_regs = {
|
||||||
|
.version = HISI_MN_VERSION_REG,
|
||||||
|
.dyn_ctrl = HISI_MN_DYNAMIC_CTRL_REG,
|
||||||
|
.perf_ctrl = HISI_MN_PERF_CTRL_REG,
|
||||||
|
.int_mask = HISI_MN_INT_MASK_REG,
|
||||||
|
.int_clear = HISI_MN_INT_CLEAR_REG,
|
||||||
|
.int_status = HISI_MN_INT_STATUS_REG,
|
||||||
|
.event_ctrl = HISI_MN_EVENT_CTRL_REG,
|
||||||
|
.event_type0 = HISI_MN_EVTYPE0_REG,
|
||||||
|
.event_cntr0 = HISI_MN_CNTR0_REG,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hisi_pmu_dev_info hisi_mn_v1 = {
|
||||||
|
.attr_groups = hisi_mn_pmu_attr_groups,
|
||||||
|
.counter_bits = 48,
|
||||||
|
.check_event = HISI_MN_EVTYPE_MASK,
|
||||||
|
.private = &hisi_mn_v1_pmu_regs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct acpi_device_id hisi_mn_pmu_acpi_match[] = {
|
||||||
|
{ "HISI0222", (kernel_ulong_t) &hisi_mn_v1 },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, hisi_mn_pmu_acpi_match);
|
||||||
|
|
||||||
|
static struct platform_driver hisi_mn_pmu_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "hisi_mn_pmu",
|
||||||
|
.acpi_match_table = hisi_mn_pmu_acpi_match,
|
||||||
|
/*
|
||||||
|
* We have not worked out a safe bind/unbind process,
|
||||||
|
* Forcefully unbinding during sampling will lead to a
|
||||||
|
* kernel panic, so this is not supported yet.
|
||||||
|
*/
|
||||||
|
.suppress_bind_attrs = true,
|
||||||
|
},
|
||||||
|
.probe = hisi_mn_pmu_probe,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init hisi_mn_pmu_module_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/hisi/mn:online",
|
||||||
|
hisi_uncore_pmu_online_cpu,
|
||||||
|
hisi_uncore_pmu_offline_cpu);
|
||||||
|
if (ret < 0) {
|
||||||
|
pr_err("hisi_mn_pmu: Failed to setup MN PMU hotplug: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
hisi_mn_pmu_online = ret;
|
||||||
|
|
||||||
|
ret = platform_driver_register(&hisi_mn_pmu_driver);
|
||||||
|
if (ret)
|
||||||
|
cpuhp_remove_multi_state(hisi_mn_pmu_online);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
module_init(hisi_mn_pmu_module_init);
|
||||||
|
|
||||||
|
static void __exit hisi_mn_pmu_module_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&hisi_mn_pmu_driver);
|
||||||
|
cpuhp_remove_multi_state(hisi_mn_pmu_online);
|
||||||
|
}
|
||||||
|
module_exit(hisi_mn_pmu_module_exit);
|
||||||
|
|
||||||
|
MODULE_IMPORT_NS("HISI_PMU");
|
||||||
|
MODULE_DESCRIPTION("HiSilicon SoC MN uncore PMU driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Junhao He <hejunhao3@huawei.com>");
|
||||||
|
|
@ -0,0 +1,443 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Driver for HiSilicon Uncore NoC (Network on Chip) PMU device
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 HiSilicon Technologies Co., Ltd.
|
||||||
|
* Author: Yicong Yang <yangyicong@hisilicon.com>
|
||||||
|
*/
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/cpuhotplug.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/mod_devicetable.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/property.h>
|
||||||
|
#include <linux/sysfs.h>
|
||||||
|
|
||||||
|
#include "hisi_uncore_pmu.h"
|
||||||
|
|
||||||
|
#define NOC_PMU_VERSION 0x1e00
|
||||||
|
#define NOC_PMU_GLOBAL_CTRL 0x1e04
|
||||||
|
#define NOC_PMU_GLOBAL_CTRL_PMU_EN BIT(0)
|
||||||
|
#define NOC_PMU_GLOBAL_CTRL_TT_EN BIT(1)
|
||||||
|
#define NOC_PMU_CNT_INFO 0x1e08
|
||||||
|
#define NOC_PMU_CNT_INFO_OVERFLOW(n) BIT(n)
|
||||||
|
#define NOC_PMU_EVENT_CTRL0 0x1e20
|
||||||
|
#define NOC_PMU_EVENT_CTRL_TYPE GENMASK(4, 0)
|
||||||
|
/*
|
||||||
|
* Note channel of 0x0 will reset the counter value, so don't do it before
|
||||||
|
* we read out the counter.
|
||||||
|
*/
|
||||||
|
#define NOC_PMU_EVENT_CTRL_CHANNEL GENMASK(10, 8)
|
||||||
|
#define NOC_PMU_EVENT_CTRL_EN BIT(11)
|
||||||
|
#define NOC_PMU_EVENT_COUNTER0 0x1e80
|
||||||
|
|
||||||
|
#define NOC_PMU_NR_COUNTERS 4
|
||||||
|
#define NOC_PMU_CH_DEFAULT 0x7
|
||||||
|
|
||||||
|
#define NOC_PMU_EVENT_CTRLn(ctrl0, n) ((ctrl0) + 4 * (n))
|
||||||
|
#define NOC_PMU_EVENT_CNTRn(cntr0, n) ((cntr0) + 8 * (n))
|
||||||
|
|
||||||
|
HISI_PMU_EVENT_ATTR_EXTRACTOR(ch, config1, 2, 0);
|
||||||
|
HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_en, config1, 3, 3);
|
||||||
|
|
||||||
|
/* Dynamic CPU hotplug state used by this PMU driver */
|
||||||
|
static enum cpuhp_state hisi_noc_pmu_cpuhp_state;
|
||||||
|
|
||||||
|
struct hisi_noc_pmu_regs {
|
||||||
|
u32 version;
|
||||||
|
u32 pmu_ctrl;
|
||||||
|
u32 event_ctrl0;
|
||||||
|
u32 event_cntr0;
|
||||||
|
u32 overflow_status;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tracetag filtering is not per event and all the events should keep
|
||||||
|
* the consistence. Return true if the new comer doesn't match the
|
||||||
|
* tracetag filtering configuration of the current scheduled events.
|
||||||
|
*/
|
||||||
|
static bool hisi_noc_pmu_check_global_filter(struct perf_event *curr,
|
||||||
|
struct perf_event *new)
|
||||||
|
{
|
||||||
|
return hisi_get_tt_en(curr) == hisi_get_tt_en(new);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_write_evtype(struct hisi_pmu *noc_pmu, int idx, u32 type)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, idx));
|
||||||
|
reg &= ~NOC_PMU_EVENT_CTRL_TYPE;
|
||||||
|
reg |= FIELD_PREP(NOC_PMU_EVENT_CTRL_TYPE, type);
|
||||||
|
writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hisi_noc_pmu_get_event_idx(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *noc_pmu = to_hisi_pmu(event->pmu);
|
||||||
|
struct hisi_pmu_hwevents *pmu_events = &noc_pmu->pmu_events;
|
||||||
|
int cur_idx;
|
||||||
|
|
||||||
|
cur_idx = find_first_bit(pmu_events->used_mask, noc_pmu->num_counters);
|
||||||
|
if (cur_idx != noc_pmu->num_counters &&
|
||||||
|
!hisi_noc_pmu_check_global_filter(pmu_events->hw_events[cur_idx], event))
|
||||||
|
return -EAGAIN;
|
||||||
|
|
||||||
|
return hisi_uncore_pmu_get_event_idx(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 hisi_noc_pmu_read_counter(struct hisi_pmu *noc_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
|
||||||
|
return readq(noc_pmu->base + NOC_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_write_counter(struct hisi_pmu *noc_pmu,
|
||||||
|
struct hw_perf_event *hwc, u64 val)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
|
||||||
|
writeq(val, noc_pmu->base + NOC_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_enable_counter(struct hisi_pmu *noc_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
|
||||||
|
reg |= NOC_PMU_EVENT_CTRL_EN;
|
||||||
|
writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_disable_counter(struct hisi_pmu *noc_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
|
||||||
|
reg &= ~NOC_PMU_EVENT_CTRL_EN;
|
||||||
|
writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_enable_counter_int(struct hisi_pmu *noc_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
/* We don't support interrupt, so a stub here. */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_disable_counter_int(struct hisi_pmu *noc_pmu,
|
||||||
|
struct hw_perf_event *hwc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_start_counters(struct hisi_pmu *noc_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
reg |= NOC_PMU_GLOBAL_CTRL_PMU_EN;
|
||||||
|
writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_stop_counters(struct hisi_pmu *noc_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
reg &= ~NOC_PMU_GLOBAL_CTRL_PMU_EN;
|
||||||
|
writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 hisi_noc_pmu_get_int_status(struct hisi_pmu *noc_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
|
||||||
|
return readl(noc_pmu->base + reg_info->overflow_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_clear_int_status(struct hisi_pmu *noc_pmu, int idx)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
reg = readl(noc_pmu->base + reg_info->overflow_status);
|
||||||
|
reg &= ~NOC_PMU_CNT_INFO_OVERFLOW(idx);
|
||||||
|
writel(reg, noc_pmu->base + reg_info->overflow_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_enable_filter(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *noc_pmu = to_hisi_pmu(event->pmu);
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
struct hw_perf_event *hwc = &event->hw;
|
||||||
|
u32 tt_en = hisi_get_tt_en(event);
|
||||||
|
u32 ch = hisi_get_ch(event);
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
if (!ch)
|
||||||
|
ch = NOC_PMU_CH_DEFAULT;
|
||||||
|
|
||||||
|
reg = readl(noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
|
||||||
|
reg &= ~NOC_PMU_EVENT_CTRL_CHANNEL;
|
||||||
|
reg |= FIELD_PREP(NOC_PMU_EVENT_CTRL_CHANNEL, ch);
|
||||||
|
writel(reg, noc_pmu->base + NOC_PMU_EVENT_CTRLn(reg_info->event_ctrl0, hwc->idx));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since tracetag filter applies to all the counters, don't touch it
|
||||||
|
* if user doesn't specify it explicitly.
|
||||||
|
*/
|
||||||
|
if (tt_en) {
|
||||||
|
reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
reg |= NOC_PMU_GLOBAL_CTRL_TT_EN;
|
||||||
|
writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_disable_filter(struct perf_event *event)
|
||||||
|
{
|
||||||
|
struct hisi_pmu *noc_pmu = to_hisi_pmu(event->pmu);
|
||||||
|
struct hisi_noc_pmu_regs *reg_info = noc_pmu->dev_info->private;
|
||||||
|
u32 tt_en = hisi_get_tt_en(event);
|
||||||
|
u32 reg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we're not the last counter, don't touch the global tracetag
|
||||||
|
* configuration.
|
||||||
|
*/
|
||||||
|
if (bitmap_weight(noc_pmu->pmu_events.used_mask, noc_pmu->num_counters) > 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tt_en) {
|
||||||
|
reg = readl(noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
reg &= ~NOC_PMU_GLOBAL_CTRL_TT_EN;
|
||||||
|
writel(reg, noc_pmu->base + reg_info->pmu_ctrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hisi_uncore_ops hisi_uncore_noc_ops = {
|
||||||
|
.write_evtype = hisi_noc_pmu_write_evtype,
|
||||||
|
.get_event_idx = hisi_noc_pmu_get_event_idx,
|
||||||
|
.read_counter = hisi_noc_pmu_read_counter,
|
||||||
|
.write_counter = hisi_noc_pmu_write_counter,
|
||||||
|
.enable_counter = hisi_noc_pmu_enable_counter,
|
||||||
|
.disable_counter = hisi_noc_pmu_disable_counter,
|
||||||
|
.enable_counter_int = hisi_noc_pmu_enable_counter_int,
|
||||||
|
.disable_counter_int = hisi_noc_pmu_disable_counter_int,
|
||||||
|
.start_counters = hisi_noc_pmu_start_counters,
|
||||||
|
.stop_counters = hisi_noc_pmu_stop_counters,
|
||||||
|
.get_int_status = hisi_noc_pmu_get_int_status,
|
||||||
|
.clear_int_status = hisi_noc_pmu_clear_int_status,
|
||||||
|
.enable_filter = hisi_noc_pmu_enable_filter,
|
||||||
|
.disable_filter = hisi_noc_pmu_disable_filter,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute *hisi_noc_pmu_format_attrs[] = {
|
||||||
|
HISI_PMU_FORMAT_ATTR(event, "config:0-7"),
|
||||||
|
HISI_PMU_FORMAT_ATTR(ch, "config1:0-2"),
|
||||||
|
HISI_PMU_FORMAT_ATTR(tt_en, "config1:3"),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group hisi_noc_pmu_format_group = {
|
||||||
|
.name = "format",
|
||||||
|
.attrs = hisi_noc_pmu_format_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute *hisi_noc_pmu_events_attrs[] = {
|
||||||
|
HISI_PMU_EVENT_ATTR(cycles, 0x0e),
|
||||||
|
/* Flux on/off the ring */
|
||||||
|
HISI_PMU_EVENT_ATTR(ingress_flow_sum, 0x1a),
|
||||||
|
HISI_PMU_EVENT_ATTR(egress_flow_sum, 0x17),
|
||||||
|
/* Buffer full duration on/off the ring */
|
||||||
|
HISI_PMU_EVENT_ATTR(ingress_buf_full, 0x19),
|
||||||
|
HISI_PMU_EVENT_ATTR(egress_buf_full, 0x12),
|
||||||
|
/* Failure packets count on/off the ring */
|
||||||
|
HISI_PMU_EVENT_ATTR(cw_ingress_fail, 0x01),
|
||||||
|
HISI_PMU_EVENT_ATTR(cc_ingress_fail, 0x09),
|
||||||
|
HISI_PMU_EVENT_ATTR(cw_egress_fail, 0x03),
|
||||||
|
HISI_PMU_EVENT_ATTR(cc_egress_fail, 0x0b),
|
||||||
|
/* Flux of the ring */
|
||||||
|
HISI_PMU_EVENT_ATTR(cw_main_flow_sum, 0x05),
|
||||||
|
HISI_PMU_EVENT_ATTR(cc_main_flow_sum, 0x0d),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group hisi_noc_pmu_events_group = {
|
||||||
|
.name = "events",
|
||||||
|
.attrs = hisi_noc_pmu_events_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group *hisi_noc_pmu_attr_groups[] = {
|
||||||
|
&hisi_noc_pmu_format_group,
|
||||||
|
&hisi_noc_pmu_events_group,
|
||||||
|
&hisi_pmu_cpumask_attr_group,
|
||||||
|
&hisi_pmu_identifier_group,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static int hisi_noc_pmu_dev_init(struct platform_device *pdev, struct hisi_pmu *noc_pmu)
|
||||||
|
{
|
||||||
|
struct hisi_noc_pmu_regs *reg_info;
|
||||||
|
|
||||||
|
hisi_uncore_pmu_init_topology(noc_pmu, &pdev->dev);
|
||||||
|
|
||||||
|
if (noc_pmu->topo.scl_id < 0)
|
||||||
|
return dev_err_probe(&pdev->dev, -EINVAL, "failed to get scl-id\n");
|
||||||
|
|
||||||
|
if (noc_pmu->topo.index_id < 0)
|
||||||
|
return dev_err_probe(&pdev->dev, -EINVAL, "failed to get idx-id\n");
|
||||||
|
|
||||||
|
if (noc_pmu->topo.sub_id < 0)
|
||||||
|
return dev_err_probe(&pdev->dev, -EINVAL, "failed to get sub-id\n");
|
||||||
|
|
||||||
|
noc_pmu->base = devm_platform_ioremap_resource(pdev, 0);
|
||||||
|
if (IS_ERR(noc_pmu->base))
|
||||||
|
return dev_err_probe(&pdev->dev, PTR_ERR(noc_pmu->base),
|
||||||
|
"fail to remap io memory\n");
|
||||||
|
|
||||||
|
noc_pmu->dev_info = device_get_match_data(&pdev->dev);
|
||||||
|
if (!noc_pmu->dev_info)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
noc_pmu->pmu_events.attr_groups = noc_pmu->dev_info->attr_groups;
|
||||||
|
noc_pmu->counter_bits = noc_pmu->dev_info->counter_bits;
|
||||||
|
noc_pmu->check_event = noc_pmu->dev_info->check_event;
|
||||||
|
noc_pmu->num_counters = NOC_PMU_NR_COUNTERS;
|
||||||
|
noc_pmu->ops = &hisi_uncore_noc_ops;
|
||||||
|
noc_pmu->dev = &pdev->dev;
|
||||||
|
noc_pmu->on_cpu = -1;
|
||||||
|
|
||||||
|
reg_info = noc_pmu->dev_info->private;
|
||||||
|
noc_pmu->identifier = readl(noc_pmu->base + reg_info->version);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_remove_cpuhp_instance(void *hotplug_node)
|
||||||
|
{
|
||||||
|
cpuhp_state_remove_instance_nocalls(hisi_noc_pmu_cpuhp_state, hotplug_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hisi_noc_pmu_unregister_pmu(void *pmu)
|
||||||
|
{
|
||||||
|
perf_pmu_unregister(pmu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hisi_noc_pmu_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct hisi_pmu *noc_pmu;
|
||||||
|
char *name;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
noc_pmu = devm_kzalloc(dev, sizeof(*noc_pmu), GFP_KERNEL);
|
||||||
|
if (!noc_pmu)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HiSilicon Uncore PMU framework needs to get common hisi_pmu device
|
||||||
|
* from device's drvdata.
|
||||||
|
*/
|
||||||
|
platform_set_drvdata(pdev, noc_pmu);
|
||||||
|
|
||||||
|
ret = hisi_noc_pmu_dev_init(pdev, noc_pmu);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = cpuhp_state_add_instance(hisi_noc_pmu_cpuhp_state, &noc_pmu->node);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Fail to register cpuhp instance\n");
|
||||||
|
|
||||||
|
ret = devm_add_action_or_reset(dev, hisi_noc_pmu_remove_cpuhp_instance,
|
||||||
|
&noc_pmu->node);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
hisi_pmu_init(noc_pmu, THIS_MODULE);
|
||||||
|
|
||||||
|
name = devm_kasprintf(dev, GFP_KERNEL, "hisi_scl%d_noc%d_%d",
|
||||||
|
noc_pmu->topo.scl_id, noc_pmu->topo.index_id,
|
||||||
|
noc_pmu->topo.sub_id);
|
||||||
|
if (!name)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = perf_pmu_register(&noc_pmu->pmu, name, -1);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Fail to register PMU\n");
|
||||||
|
|
||||||
|
return devm_add_action_or_reset(dev, hisi_noc_pmu_unregister_pmu,
|
||||||
|
&noc_pmu->pmu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct hisi_noc_pmu_regs hisi_noc_v1_pmu_regs = {
|
||||||
|
.version = NOC_PMU_VERSION,
|
||||||
|
.pmu_ctrl = NOC_PMU_GLOBAL_CTRL,
|
||||||
|
.event_ctrl0 = NOC_PMU_EVENT_CTRL0,
|
||||||
|
.event_cntr0 = NOC_PMU_EVENT_COUNTER0,
|
||||||
|
.overflow_status = NOC_PMU_CNT_INFO,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hisi_pmu_dev_info hisi_noc_v1 = {
|
||||||
|
.attr_groups = hisi_noc_pmu_attr_groups,
|
||||||
|
.counter_bits = 64,
|
||||||
|
.check_event = NOC_PMU_EVENT_CTRL_TYPE,
|
||||||
|
.private = &hisi_noc_v1_pmu_regs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct acpi_device_id hisi_noc_pmu_ids[] = {
|
||||||
|
{ "HISI04E0", (kernel_ulong_t) &hisi_noc_v1 },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, hisi_noc_pmu_ids);
|
||||||
|
|
||||||
|
static struct platform_driver hisi_noc_pmu_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "hisi_noc_pmu",
|
||||||
|
.acpi_match_table = hisi_noc_pmu_ids,
|
||||||
|
.suppress_bind_attrs = true,
|
||||||
|
},
|
||||||
|
.probe = hisi_noc_pmu_probe,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init hisi_noc_pmu_module_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/hisi/noc:online",
|
||||||
|
hisi_uncore_pmu_online_cpu,
|
||||||
|
hisi_uncore_pmu_offline_cpu);
|
||||||
|
if (ret < 0) {
|
||||||
|
pr_err("hisi_noc_pmu: Fail to setup cpuhp callbacks, ret = %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
hisi_noc_pmu_cpuhp_state = ret;
|
||||||
|
|
||||||
|
ret = platform_driver_register(&hisi_noc_pmu_driver);
|
||||||
|
if (ret)
|
||||||
|
cpuhp_remove_multi_state(hisi_noc_pmu_cpuhp_state);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
module_init(hisi_noc_pmu_module_init);
|
||||||
|
|
||||||
|
static void __exit hisi_noc_pmu_module_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&hisi_noc_pmu_driver);
|
||||||
|
cpuhp_remove_multi_state(hisi_noc_pmu_cpuhp_state);
|
||||||
|
}
|
||||||
|
module_exit(hisi_noc_pmu_module_exit);
|
||||||
|
|
||||||
|
MODULE_IMPORT_NS("HISI_PMU");
|
||||||
|
MODULE_DESCRIPTION("HiSilicon SoC Uncore NoC PMU driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Yicong Yang <yangyicong@hisilicon.com>");
|
||||||
|
|
@ -149,7 +149,7 @@ static void hisi_uncore_pmu_clear_event_idx(struct hisi_pmu *hisi_pmu, int idx)
|
||||||
clear_bit(idx, hisi_pmu->pmu_events.used_mask);
|
clear_bit(idx, hisi_pmu->pmu_events.used_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t hisi_uncore_pmu_isr(int irq, void *data)
|
irqreturn_t hisi_uncore_pmu_isr(int irq, void *data)
|
||||||
{
|
{
|
||||||
struct hisi_pmu *hisi_pmu = data;
|
struct hisi_pmu *hisi_pmu = data;
|
||||||
struct perf_event *event;
|
struct perf_event *event;
|
||||||
|
|
@ -178,6 +178,7 @@ static irqreturn_t hisi_uncore_pmu_isr(int irq, void *data)
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(hisi_uncore_pmu_isr, "HISI_PMU");
|
||||||
|
|
||||||
int hisi_uncore_pmu_init_irq(struct hisi_pmu *hisi_pmu,
|
int hisi_uncore_pmu_init_irq(struct hisi_pmu *hisi_pmu,
|
||||||
struct platform_device *pdev)
|
struct platform_device *pdev)
|
||||||
|
|
@ -234,7 +235,7 @@ int hisi_uncore_pmu_event_init(struct perf_event *event)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
hisi_pmu = to_hisi_pmu(event->pmu);
|
hisi_pmu = to_hisi_pmu(event->pmu);
|
||||||
if (event->attr.config > hisi_pmu->check_event)
|
if ((event->attr.config & HISI_EVENTID_MASK) > hisi_pmu->check_event)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (hisi_pmu->on_cpu == -1)
|
if (hisi_pmu->on_cpu == -1)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
#define pr_fmt(fmt) "hisi_pmu: " fmt
|
#define pr_fmt(fmt) "hisi_pmu: " fmt
|
||||||
|
|
||||||
#define HISI_PMU_V2 0x30
|
#define HISI_PMU_V2 0x30
|
||||||
#define HISI_MAX_COUNTERS 0x10
|
#define HISI_MAX_COUNTERS 0x18
|
||||||
#define to_hisi_pmu(p) (container_of(p, struct hisi_pmu, pmu))
|
#define to_hisi_pmu(p) (container_of(p, struct hisi_pmu, pmu))
|
||||||
|
|
||||||
#define HISI_PMU_ATTR(_name, _func, _config) \
|
#define HISI_PMU_ATTR(_name, _func, _config) \
|
||||||
|
|
@ -43,7 +43,8 @@
|
||||||
return FIELD_GET(GENMASK_ULL(hi, lo), event->attr.config); \
|
return FIELD_GET(GENMASK_ULL(hi, lo), event->attr.config); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HISI_GET_EVENTID(ev) (ev->hw.config_base & 0xff)
|
#define HISI_EVENTID_MASK GENMASK(7, 0)
|
||||||
|
#define HISI_GET_EVENTID(ev) ((ev)->hw.config_base & HISI_EVENTID_MASK)
|
||||||
|
|
||||||
#define HISI_PMU_EVTYPE_BITS 8
|
#define HISI_PMU_EVTYPE_BITS 8
|
||||||
#define HISI_PMU_EVTYPE_SHIFT(idx) ((idx) % 4 * HISI_PMU_EVTYPE_BITS)
|
#define HISI_PMU_EVTYPE_SHIFT(idx) ((idx) % 4 * HISI_PMU_EVTYPE_BITS)
|
||||||
|
|
@ -164,6 +165,7 @@ int hisi_uncore_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node);
|
||||||
ssize_t hisi_uncore_pmu_identifier_attr_show(struct device *dev,
|
ssize_t hisi_uncore_pmu_identifier_attr_show(struct device *dev,
|
||||||
struct device_attribute *attr,
|
struct device_attribute *attr,
|
||||||
char *page);
|
char *page);
|
||||||
|
irqreturn_t hisi_uncore_pmu_isr(int irq, void *data);
|
||||||
int hisi_uncore_pmu_init_irq(struct hisi_pmu *hisi_pmu,
|
int hisi_uncore_pmu_init_irq(struct hisi_pmu *hisi_pmu,
|
||||||
struct platform_device *pdev);
|
struct platform_device *pdev);
|
||||||
void hisi_uncore_pmu_init_topology(struct hisi_pmu *hisi_pmu, struct device *dev);
|
void hisi_uncore_pmu_init_topology(struct hisi_pmu *hisi_pmu, struct device *dev);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
config EFI_SECRET
|
config EFI_SECRET
|
||||||
tristate "EFI secret area securityfs support"
|
tristate "EFI secret area securityfs support"
|
||||||
depends on EFI && X86_64
|
depends on EFI && (X86_64 || ARM64)
|
||||||
select EFI_COCO_SECRET
|
select EFI_COCO_SECRET
|
||||||
select SECURITYFS
|
select SECURITYFS
|
||||||
help
|
help
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,9 @@ int walk_page_range(struct mm_struct *mm, unsigned long start,
|
||||||
int walk_kernel_page_table_range(unsigned long start,
|
int walk_kernel_page_table_range(unsigned long start,
|
||||||
unsigned long end, const struct mm_walk_ops *ops,
|
unsigned long end, const struct mm_walk_ops *ops,
|
||||||
pgd_t *pgd, void *private);
|
pgd_t *pgd, void *private);
|
||||||
|
int walk_kernel_page_table_range_lockless(unsigned long start,
|
||||||
|
unsigned long end, const struct mm_walk_ops *ops,
|
||||||
|
pgd_t *pgd, void *private);
|
||||||
int walk_page_range_vma(struct vm_area_struct *vma, unsigned long start,
|
int walk_page_range_vma(struct vm_area_struct *vma, unsigned long start,
|
||||||
unsigned long end, const struct mm_walk_ops *ops,
|
unsigned long end, const struct mm_walk_ops *ops,
|
||||||
void *private);
|
void *private);
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,20 @@ noinstr irqentry_state_t irqentry_enter(struct pt_regs *regs)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* arch_irqentry_exit_need_resched - Architecture specific need resched function
|
||||||
|
*
|
||||||
|
* Invoked from raw_irqentry_exit_cond_resched() to check if resched is needed.
|
||||||
|
* Defaults return true.
|
||||||
|
*
|
||||||
|
* The main purpose is to permit arch to avoid preemption of a task from an IRQ.
|
||||||
|
*/
|
||||||
|
static inline bool arch_irqentry_exit_need_resched(void);
|
||||||
|
|
||||||
|
#ifndef arch_irqentry_exit_need_resched
|
||||||
|
static inline bool arch_irqentry_exit_need_resched(void) { return true; }
|
||||||
|
#endif
|
||||||
|
|
||||||
void raw_irqentry_exit_cond_resched(void)
|
void raw_irqentry_exit_cond_resched(void)
|
||||||
{
|
{
|
||||||
if (!preempt_count()) {
|
if (!preempt_count()) {
|
||||||
|
|
@ -150,7 +164,7 @@ void raw_irqentry_exit_cond_resched(void)
|
||||||
rcu_irq_exit_check_preempt();
|
rcu_irq_exit_check_preempt();
|
||||||
if (IS_ENABLED(CONFIG_DEBUG_ENTRY))
|
if (IS_ENABLED(CONFIG_DEBUG_ENTRY))
|
||||||
WARN_ON_ONCE(!on_thread_stack());
|
WARN_ON_ONCE(!on_thread_stack());
|
||||||
if (need_resched())
|
if (need_resched() && arch_irqentry_exit_need_resched())
|
||||||
preempt_schedule_irq();
|
preempt_schedule_irq();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ struct xol_area {
|
||||||
|
|
||||||
static void uprobe_warn(struct task_struct *t, const char *msg)
|
static void uprobe_warn(struct task_struct *t, const char *msg)
|
||||||
{
|
{
|
||||||
pr_warn("uprobe: %s:%d failed to %s\n", current->comm, current->pid, msg);
|
pr_warn("uprobe: %s:%d failed to %s\n", t->comm, t->pid, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -606,10 +606,32 @@ int walk_page_range(struct mm_struct *mm, unsigned long start,
|
||||||
int walk_kernel_page_table_range(unsigned long start, unsigned long end,
|
int walk_kernel_page_table_range(unsigned long start, unsigned long end,
|
||||||
const struct mm_walk_ops *ops, pgd_t *pgd, void *private)
|
const struct mm_walk_ops *ops, pgd_t *pgd, void *private)
|
||||||
{
|
{
|
||||||
struct mm_struct *mm = &init_mm;
|
/*
|
||||||
|
* Kernel intermediate page tables are usually not freed, so the mmap
|
||||||
|
* read lock is sufficient. But there are some exceptions.
|
||||||
|
* E.g. memory hot-remove. In which case, the mmap lock is insufficient
|
||||||
|
* to prevent the intermediate kernel pages tables belonging to the
|
||||||
|
* specified address range from being freed. The caller should take
|
||||||
|
* other actions to prevent this race.
|
||||||
|
*/
|
||||||
|
mmap_assert_locked(&init_mm);
|
||||||
|
|
||||||
|
return walk_kernel_page_table_range_lockless(start, end, ops, pgd,
|
||||||
|
private);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use this function to walk the kernel page tables locklessly. It should be
|
||||||
|
* guaranteed that the caller has exclusive access over the range they are
|
||||||
|
* operating on - that there should be no concurrent access, for example,
|
||||||
|
* changing permissions for vmalloc objects.
|
||||||
|
*/
|
||||||
|
int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end,
|
||||||
|
const struct mm_walk_ops *ops, pgd_t *pgd, void *private)
|
||||||
|
{
|
||||||
struct mm_walk walk = {
|
struct mm_walk walk = {
|
||||||
.ops = ops,
|
.ops = ops,
|
||||||
.mm = mm,
|
.mm = &init_mm,
|
||||||
.pgd = pgd,
|
.pgd = pgd,
|
||||||
.private = private,
|
.private = private,
|
||||||
.no_vma = true
|
.no_vma = true
|
||||||
|
|
@ -620,16 +642,6 @@ int walk_kernel_page_table_range(unsigned long start, unsigned long end,
|
||||||
if (!check_ops_valid(ops))
|
if (!check_ops_valid(ops))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
/*
|
|
||||||
* Kernel intermediate page tables are usually not freed, so the mmap
|
|
||||||
* read lock is sufficient. But there are some exceptions.
|
|
||||||
* E.g. memory hot-remove. In which case, the mmap lock is insufficient
|
|
||||||
* to prevent the intermediate kernel pages tables belonging to the
|
|
||||||
* specified address range from being freed. The caller should take
|
|
||||||
* other actions to prevent this race.
|
|
||||||
*/
|
|
||||||
mmap_assert_locked(mm);
|
|
||||||
|
|
||||||
return walk_pgd_range(start, end, &walk);
|
return walk_pgd_range(start, end, &walk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
#include <asm/sigcontext.h>
|
#include <asm/sigcontext.h>
|
||||||
#include <asm/unistd.h>
|
#include <asm/unistd.h>
|
||||||
|
|
||||||
|
#include <linux/auxvec.h>
|
||||||
|
|
||||||
#include "../../kselftest.h"
|
#include "../../kselftest.h"
|
||||||
|
|
||||||
#define TESTS_PER_HWCAP 3
|
#define TESTS_PER_HWCAP 3
|
||||||
|
|
@ -55,7 +57,6 @@ static void cmpbr_sigill(void)
|
||||||
/* Not implemented, too complicated and unreliable anyway */
|
/* Not implemented, too complicated and unreliable anyway */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void crc32_sigill(void)
|
static void crc32_sigill(void)
|
||||||
{
|
{
|
||||||
/* CRC32W W0, W0, W1 */
|
/* CRC32W W0, W0, W1 */
|
||||||
|
|
@ -169,6 +170,18 @@ static void lse128_sigill(void)
|
||||||
: "cc", "memory");
|
: "cc", "memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void lsfe_sigill(void)
|
||||||
|
{
|
||||||
|
float __attribute__ ((aligned (16))) mem;
|
||||||
|
register float *memp asm ("x0") = &mem;
|
||||||
|
|
||||||
|
/* STFADD H0, [X0] */
|
||||||
|
asm volatile(".inst 0x7c20801f"
|
||||||
|
: "+r" (memp)
|
||||||
|
:
|
||||||
|
: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
static void lut_sigill(void)
|
static void lut_sigill(void)
|
||||||
{
|
{
|
||||||
/* LUTI2 V0.16B, { V0.16B }, V[0] */
|
/* LUTI2 V0.16B, { V0.16B }, V[0] */
|
||||||
|
|
@ -762,6 +775,13 @@ static const struct hwcap_data {
|
||||||
.cpuinfo = "lse128",
|
.cpuinfo = "lse128",
|
||||||
.sigill_fn = lse128_sigill,
|
.sigill_fn = lse128_sigill,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "LSFE",
|
||||||
|
.at_hwcap = AT_HWCAP3,
|
||||||
|
.hwcap_bit = HWCAP3_LSFE,
|
||||||
|
.cpuinfo = "lsfe",
|
||||||
|
.sigill_fn = lsfe_sigill,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.name = "LUT",
|
.name = "LUT",
|
||||||
.at_hwcap = AT_HWCAP2,
|
.at_hwcap = AT_HWCAP2,
|
||||||
|
|
|
||||||
|
|
@ -227,10 +227,10 @@ int main(int argc, char **argv)
|
||||||
ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0);
|
ret = open("/proc/sys/abi/sme_default_vector_length", O_RDONLY, 0);
|
||||||
if (ret >= 0) {
|
if (ret >= 0) {
|
||||||
ksft_test_result(default_value(), "default_value\n");
|
ksft_test_result(default_value(), "default_value\n");
|
||||||
ksft_test_result(write_read, "write_read\n");
|
ksft_test_result(write_read(), "write_read\n");
|
||||||
ksft_test_result(write_sleep_read, "write_sleep_read\n");
|
ksft_test_result(write_sleep_read(), "write_sleep_read\n");
|
||||||
ksft_test_result(write_fork_read, "write_fork_read\n");
|
ksft_test_result(write_fork_read(), "write_fork_read\n");
|
||||||
ksft_test_result(write_clone_read, "write_clone_read\n");
|
ksft_test_result(write_clone_read(), "write_clone_read\n");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ksft_print_msg("SME support not present\n");
|
ksft_print_msg("SME support not present\n");
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
#define GNU_PROPERTY_AARCH64_FEATURE_1_BTI (1U << 0)
|
#define GNU_PROPERTY_AARCH64_FEATURE_1_BTI (1U << 0)
|
||||||
#define GNU_PROPERTY_AARCH64_FEATURE_1_PAC (1U << 1)
|
#define GNU_PROPERTY_AARCH64_FEATURE_1_PAC (1U << 1)
|
||||||
|
|
||||||
|
|
||||||
.macro startfn name:req
|
.macro startfn name:req
|
||||||
.globl \name
|
.globl \name
|
||||||
\name:
|
\name:
|
||||||
|
|
|
||||||
|
|
@ -1568,7 +1568,6 @@ static void run_sve_tests(void)
|
||||||
&test_config);
|
&test_config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void run_sme_tests(void)
|
static void run_sme_tests(void)
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,8 @@ static void child_start(struct child_data *child, const char *program)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read from the startup pipe, there should be no data
|
* Read from the startup pipe, there should be no data
|
||||||
* and we should block until it is closed. We just
|
* and we should block until it is closed. We just
|
||||||
* carry on on error since this isn't super critical.
|
* carry-on on error since this isn't super critical.
|
||||||
*/
|
*/
|
||||||
ret = read(3, &i, sizeof(i));
|
ret = read(3, &i, sizeof(i));
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
|
@ -549,7 +549,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
evs = calloc(tests, sizeof(*evs));
|
evs = calloc(tests, sizeof(*evs));
|
||||||
if (!evs)
|
if (!evs)
|
||||||
ksft_exit_fail_msg("Failed to allocated %d epoll events\n",
|
ksft_exit_fail_msg("Failed to allocate %d epoll events\n",
|
||||||
tests);
|
tests);
|
||||||
|
|
||||||
for (i = 0; i < cpus; i++) {
|
for (i = 0; i < cpus; i++) {
|
||||||
|
|
|
||||||
|
|
@ -188,13 +188,13 @@ static bool create_socket(void)
|
||||||
|
|
||||||
ref = malloc(digest_len);
|
ref = malloc(digest_len);
|
||||||
if (!ref) {
|
if (!ref) {
|
||||||
printf("Failed to allocated %d byte reference\n", digest_len);
|
printf("Failed to allocate %d byte reference\n", digest_len);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
digest = malloc(digest_len);
|
digest = malloc(digest_len);
|
||||||
if (!digest) {
|
if (!digest) {
|
||||||
printf("Failed to allocated %d byte digest\n", digest_len);
|
printf("Failed to allocate %d byte digest\n", digest_len);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ static const struct vec_type vec_types[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
#define VL_TESTS (((TEST_VQ_MAX - SVE_VQ_MIN) + 1) * 4)
|
#define VL_TESTS (((TEST_VQ_MAX - SVE_VQ_MIN) + 1) * 4)
|
||||||
#define FLAG_TESTS 2
|
#define FLAG_TESTS 4
|
||||||
#define FPSIMD_TESTS 2
|
#define FPSIMD_TESTS 2
|
||||||
|
|
||||||
#define EXPECTED_TESTS ((VL_TESTS + FLAG_TESTS + FPSIMD_TESTS) * ARRAY_SIZE(vec_types))
|
#define EXPECTED_TESTS ((VL_TESTS + FLAG_TESTS + FPSIMD_TESTS) * ARRAY_SIZE(vec_types))
|
||||||
|
|
@ -95,19 +95,27 @@ static int do_child(void)
|
||||||
static int get_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
|
static int get_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
|
||||||
{
|
{
|
||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
|
int ret;
|
||||||
|
|
||||||
iov.iov_base = fpsimd;
|
iov.iov_base = fpsimd;
|
||||||
iov.iov_len = sizeof(*fpsimd);
|
iov.iov_len = sizeof(*fpsimd);
|
||||||
return ptrace(PTRACE_GETREGSET, pid, NT_PRFPREG, &iov);
|
ret = ptrace(PTRACE_GETREGSET, pid, NT_PRFPREG, &iov);
|
||||||
|
if (ret == -1)
|
||||||
|
ksft_perror("ptrace(PTRACE_GETREGSET)");
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int set_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
|
static int set_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
|
||||||
{
|
{
|
||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
|
int ret;
|
||||||
|
|
||||||
iov.iov_base = fpsimd;
|
iov.iov_base = fpsimd;
|
||||||
iov.iov_len = sizeof(*fpsimd);
|
iov.iov_len = sizeof(*fpsimd);
|
||||||
return ptrace(PTRACE_SETREGSET, pid, NT_PRFPREG, &iov);
|
ret = ptrace(PTRACE_SETREGSET, pid, NT_PRFPREG, &iov);
|
||||||
|
if (ret == -1)
|
||||||
|
ksft_perror("ptrace(PTRACE_SETREGSET)");
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct user_sve_header *get_sve(pid_t pid, const struct vec_type *type,
|
static struct user_sve_header *get_sve(pid_t pid, const struct vec_type *type,
|
||||||
|
|
@ -115,8 +123,9 @@ static struct user_sve_header *get_sve(pid_t pid, const struct vec_type *type,
|
||||||
{
|
{
|
||||||
struct user_sve_header *sve;
|
struct user_sve_header *sve;
|
||||||
void *p;
|
void *p;
|
||||||
size_t sz = sizeof *sve;
|
size_t sz = sizeof(*sve);
|
||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
|
int ret;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
if (*size < sz) {
|
if (*size < sz) {
|
||||||
|
|
@ -132,8 +141,11 @@ static struct user_sve_header *get_sve(pid_t pid, const struct vec_type *type,
|
||||||
|
|
||||||
iov.iov_base = *buf;
|
iov.iov_base = *buf;
|
||||||
iov.iov_len = sz;
|
iov.iov_len = sz;
|
||||||
if (ptrace(PTRACE_GETREGSET, pid, type->regset, &iov))
|
ret = ptrace(PTRACE_GETREGSET, pid, type->regset, &iov);
|
||||||
|
if (ret) {
|
||||||
|
ksft_perror("ptrace(PTRACE_GETREGSET)");
|
||||||
goto error;
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
sve = *buf;
|
sve = *buf;
|
||||||
if (sve->size <= sz)
|
if (sve->size <= sz)
|
||||||
|
|
@ -152,10 +164,46 @@ static int set_sve(pid_t pid, const struct vec_type *type,
|
||||||
const struct user_sve_header *sve)
|
const struct user_sve_header *sve)
|
||||||
{
|
{
|
||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
|
int ret;
|
||||||
|
|
||||||
iov.iov_base = (void *)sve;
|
iov.iov_base = (void *)sve;
|
||||||
iov.iov_len = sve->size;
|
iov.iov_len = sve->size;
|
||||||
return ptrace(PTRACE_SETREGSET, pid, type->regset, &iov);
|
ret = ptrace(PTRACE_SETREGSET, pid, type->regset, &iov);
|
||||||
|
if (ret == -1)
|
||||||
|
ksft_perror("ptrace(PTRACE_SETREGSET)");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A read operation fails */
|
||||||
|
static void read_fails(pid_t child, const struct vec_type *type)
|
||||||
|
{
|
||||||
|
struct user_sve_header *new_sve = NULL;
|
||||||
|
size_t new_sve_size = 0;
|
||||||
|
void *ret;
|
||||||
|
|
||||||
|
ret = get_sve(child, type, (void **)&new_sve, &new_sve_size);
|
||||||
|
|
||||||
|
ksft_test_result(ret == NULL, "%s unsupported read fails\n",
|
||||||
|
type->name);
|
||||||
|
|
||||||
|
free(new_sve);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A write operation fails */
|
||||||
|
static void write_fails(pid_t child, const struct vec_type *type)
|
||||||
|
{
|
||||||
|
struct user_sve_header sve;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Just the header, no data */
|
||||||
|
memset(&sve, 0, sizeof(sve));
|
||||||
|
sve.size = sizeof(sve);
|
||||||
|
sve.flags = SVE_PT_REGS_SVE;
|
||||||
|
sve.vl = SVE_VL_MIN;
|
||||||
|
ret = set_sve(child, type, &sve);
|
||||||
|
|
||||||
|
ksft_test_result(ret != 0, "%s unsupported write fails\n",
|
||||||
|
type->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validate setting and getting the inherit flag */
|
/* Validate setting and getting the inherit flag */
|
||||||
|
|
@ -270,6 +318,25 @@ static void check_u32(unsigned int vl, const char *reg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set out of range VLs */
|
||||||
|
static void ptrace_set_vl_ranges(pid_t child, const struct vec_type *type)
|
||||||
|
{
|
||||||
|
struct user_sve_header sve;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
memset(&sve, 0, sizeof(sve));
|
||||||
|
sve.flags = SVE_PT_REGS_SVE;
|
||||||
|
sve.size = sizeof(sve);
|
||||||
|
|
||||||
|
ret = set_sve(child, type, &sve);
|
||||||
|
ksft_test_result(ret != 0, "%s Set invalid VL 0\n", type->name);
|
||||||
|
|
||||||
|
sve.vl = SVE_VL_MAX + SVE_VQ_BYTES;
|
||||||
|
ret = set_sve(child, type, &sve);
|
||||||
|
ksft_test_result(ret != 0, "%s Set invalid VL %d\n", type->name,
|
||||||
|
SVE_VL_MAX + SVE_VQ_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
/* Access the FPSIMD registers via the SVE regset */
|
/* Access the FPSIMD registers via the SVE regset */
|
||||||
static void ptrace_sve_fpsimd(pid_t child, const struct vec_type *type)
|
static void ptrace_sve_fpsimd(pid_t child, const struct vec_type *type)
|
||||||
{
|
{
|
||||||
|
|
@ -683,6 +750,20 @@ static int do_parent(pid_t child)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(vec_types); i++) {
|
for (i = 0; i < ARRAY_SIZE(vec_types); i++) {
|
||||||
|
/*
|
||||||
|
* If the vector type isn't supported reads and writes
|
||||||
|
* should fail.
|
||||||
|
*/
|
||||||
|
if (!(getauxval(vec_types[i].hwcap_type) & vec_types[i].hwcap)) {
|
||||||
|
read_fails(child, &vec_types[i]);
|
||||||
|
write_fails(child, &vec_types[i]);
|
||||||
|
} else {
|
||||||
|
ksft_test_result_skip("%s unsupported read fails\n",
|
||||||
|
vec_types[i].name);
|
||||||
|
ksft_test_result_skip("%s unsupported write fails\n",
|
||||||
|
vec_types[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
/* FPSIMD via SVE regset */
|
/* FPSIMD via SVE regset */
|
||||||
if (getauxval(vec_types[i].hwcap_type) & vec_types[i].hwcap) {
|
if (getauxval(vec_types[i].hwcap_type) & vec_types[i].hwcap) {
|
||||||
ptrace_sve_fpsimd(child, &vec_types[i]);
|
ptrace_sve_fpsimd(child, &vec_types[i]);
|
||||||
|
|
@ -703,6 +784,17 @@ static int do_parent(pid_t child)
|
||||||
vec_types[i].name);
|
vec_types[i].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Setting out of bounds VLs should fail */
|
||||||
|
if (getauxval(vec_types[i].hwcap_type) & vec_types[i].hwcap) {
|
||||||
|
ptrace_set_vl_ranges(child, &vec_types[i]);
|
||||||
|
} else {
|
||||||
|
ksft_test_result_skip("%s Set invalid VL 0\n",
|
||||||
|
vec_types[i].name);
|
||||||
|
ksft_test_result_skip("%s Set invalid VL %d\n",
|
||||||
|
vec_types[i].name,
|
||||||
|
SVE_VL_MAX + SVE_VQ_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
/* Step through every possible VQ */
|
/* Step through every possible VQ */
|
||||||
for (vq = SVE_VQ_MIN; vq <= TEST_VQ_MAX; vq++) {
|
for (vq = SVE_VQ_MIN; vq <= TEST_VQ_MAX; vq++) {
|
||||||
vl = sve_vl_from_vq(vq);
|
vl = sve_vl_from_vq(vq);
|
||||||
|
|
|
||||||
|
|
@ -690,7 +690,6 @@ static inline void smstop(void)
|
||||||
asm volatile("msr S0_3_C4_C6_3, xzr");
|
asm volatile("msr S0_3_C4_C6_3, xzr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify we can change the SVE vector length while SME is active and
|
* Verify we can change the SVE vector length while SME is active and
|
||||||
* continue to use SME afterwards.
|
* continue to use SME afterwards.
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,6 @@ static int get_zt(pid_t pid, char zt[ZT_SIG_REG_BYTES])
|
||||||
return ptrace(PTRACE_GETREGSET, pid, NT_ARM_ZT, &iov);
|
return ptrace(PTRACE_GETREGSET, pid, NT_ARM_ZT, &iov);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int set_zt(pid_t pid, const char zt[ZT_SIG_REG_BYTES])
|
static int set_zt(pid_t pid, const char zt[ZT_SIG_REG_BYTES])
|
||||||
{
|
{
|
||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ LDLIBS+=-lpthread
|
||||||
include ../../lib.mk
|
include ../../lib.mk
|
||||||
|
|
||||||
$(OUTPUT)/basic-gcs: basic-gcs.c
|
$(OUTPUT)/basic-gcs: basic-gcs.c
|
||||||
$(CC) -g -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \
|
$(CC) $(CFLAGS) -fno-asynchronous-unwind-tables -fno-ident -s -nostdlib -nostdinc \
|
||||||
-static -include ../../../../include/nolibc/nolibc.h \
|
-static -I../../../../include/nolibc -include ../../../../include/nolibc/nolibc.h \
|
||||||
-I../../../../../usr/include \
|
-I../../../../../usr/include \
|
||||||
-std=gnu99 -I../.. -g \
|
-std=gnu99 -I../.. -g \
|
||||||
-ffreestanding -Wall $^ -o $@ -lgcc
|
-ffreestanding $^ -o $@ -lgcc
|
||||||
|
|
||||||
$(OUTPUT)/gcs-stress-thread: gcs-stress-thread.S
|
$(OUTPUT)/gcs-stress-thread: gcs-stress-thread.S
|
||||||
$(CC) -nostdlib $^ -o $@
|
$(CC) -nostdlib $^ -o $@
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <asm/mman.h>
|
#include <asm/mman.h>
|
||||||
|
#include <asm/hwcap.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
|
|
||||||
#include "kselftest.h"
|
#include "kselftest.h"
|
||||||
|
|
@ -386,14 +387,13 @@ int main(void)
|
||||||
|
|
||||||
ksft_print_header();
|
ksft_print_header();
|
||||||
|
|
||||||
/*
|
if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
|
||||||
* We don't have getauxval() with nolibc so treat a failure to
|
ksft_exit_skip("SKIP GCS not supported\n");
|
||||||
* read GCS state as a lack of support and skip.
|
|
||||||
*/
|
|
||||||
ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
|
ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
|
||||||
&gcs_mode, 0, 0, 0);
|
&gcs_mode, 0, 0, 0);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
ksft_exit_skip("Failed to read GCS state: %d\n", ret);
|
ksft_exit_fail_msg("Failed to read GCS state: %d\n", ret);
|
||||||
|
|
||||||
if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
|
if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
|
||||||
gcs_mode = PR_SHADOW_STACK_ENABLE;
|
gcs_mode = PR_SHADOW_STACK_ENABLE;
|
||||||
|
|
@ -410,7 +410,7 @@ int main(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* One last test: disable GCS, we can do this one time */
|
/* One last test: disable GCS, we can do this one time */
|
||||||
my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0);
|
ret = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
ksft_print_msg("Failed to disable GCS: %d\n", ret);
|
ksft_print_msg("Failed to disable GCS: %d\n", ret);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,6 @@ TEST_F(valid_modes, lock_enable_disable_others)
|
||||||
ASSERT_EQ(ret, 0);
|
ASSERT_EQ(ret, 0);
|
||||||
ASSERT_EQ(mode, PR_SHADOW_STACK_ALL_MODES);
|
ASSERT_EQ(mode, PR_SHADOW_STACK_ALL_MODES);
|
||||||
|
|
||||||
|
|
||||||
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
|
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
|
||||||
variant->mode);
|
variant->mode);
|
||||||
ASSERT_EQ(ret, 0);
|
ASSERT_EQ(ret, 0);
|
||||||
|
|
|
||||||
|
|
@ -433,7 +433,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
evs = calloc(tests, sizeof(*evs));
|
evs = calloc(tests, sizeof(*evs));
|
||||||
if (!evs)
|
if (!evs)
|
||||||
ksft_exit_fail_msg("Failed to allocated %d epoll events\n",
|
ksft_exit_fail_msg("Failed to allocate %d epoll events\n",
|
||||||
tests);
|
tests);
|
||||||
|
|
||||||
for (i = 0; i < gcs_threads; i++)
|
for (i = 0; i < gcs_threads; i++)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,12 @@ int main(void)
|
||||||
unsigned long hwcaps;
|
unsigned long hwcaps;
|
||||||
size_t val;
|
size_t val;
|
||||||
|
|
||||||
fread(&val, sizeof(size_t), 1, stdin);
|
size_t size = fread(&val, sizeof(size_t), 1, stdin);
|
||||||
|
|
||||||
|
if (size != 1) {
|
||||||
|
fprintf(stderr, "Could not read input from stdin\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
/* don't try to execute illegal (unimplemented) instructions) caller
|
/* don't try to execute illegal (unimplemented) instructions) caller
|
||||||
* should have checked this and keep worker simple
|
* should have checked this and keep worker simple
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue