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:
Linus Torvalds 2025-09-29 18:48:39 -07:00
commit feafee2845
96 changed files with 3781 additions and 767 deletions

View File

@ -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]

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -29,3 +29,4 @@ Performance monitor support
cxl cxl
ampere_cspmu ampere_cspmu
mrvl-pem-pmu mrvl-pem-pmu
fujitsu_uncore_pmu

View File

@ -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:

View File

@ -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
----------------------- -----------------------

View File

@ -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 | |
+----------------+-----------------+-----------------+-----------------------------+ +----------------+-----------------+-----------------+-----------------------------+

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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);

View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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 */

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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;
} }

View File

@ -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

View File

@ -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)
{ {

View File

@ -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);

View File

@ -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>

View File

@ -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)
{ {

View File

@ -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)
{ {

View File

@ -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;

View File

@ -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

View File

@ -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 */

View File

@ -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

View File

@ -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))

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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),

View File

@ -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;
} }

View File

@ -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);

View File

@ -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();

View File

@ -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;
} }

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;
} }

View File

@ -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[] = {

View File

@ -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);

View File

@ -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;
/* /*

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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),

View File

@ -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,

View File

@ -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

View File

@ -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",

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
} }

View File

@ -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;

View File

@ -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) &&

View File

@ -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),

View File

@ -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)

View File

@ -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 */ }
}; };

View File

@ -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");

View File

@ -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

View File

@ -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;

View File

@ -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>");

View File

@ -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>");

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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();
} }
} }

View File

@ -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);
} }
/* /*

View File

@ -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);
} }

View File

@ -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,

View File

@ -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");

View File

@ -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:

View File

@ -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)

View File

@ -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++) {

View File

@ -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;
} }

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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 $@

View File

@ -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);

View File

@ -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);

View File

@ -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++)

View File

@ -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