Merge branch 'CR_2862_gt9xx_ts_515_changhuang.liang' into 'vf2-515-devel'
authorandy.hu <andy.hu@starfivetech.com>
Wed, 11 Jan 2023 12:24:22 +0000 (12:24 +0000)
committerandy.hu <andy.hu@starfivetech.com>
Wed, 11 Jan 2023 12:24:22 +0000 (12:24 +0000)
CR_2862_gt9xx_ts_515_changhuang.liang input: touchscreen: Add gt9xx driver support

See merge request sbc/linux!56

50 files changed:
Documentation/riscv/pmu.rst [deleted file]
MAINTAINERS
arch/riscv/Kconfig
arch/riscv/boot/dts/starfive/jh7110-evb-can-pdm-pwmdac.dts
arch/riscv/boot/dts/starfive/jh7110-evb-dvp-rgb2hdmi.dts
arch/riscv/boot/dts/starfive/jh7110-evb-i2s-ac108.dts
arch/riscv/boot/dts/starfive/jh7110-evb-pcie-i2s-sd.dts
arch/riscv/boot/dts/starfive/jh7110-evb-spi-uart2.dts
arch/riscv/boot/dts/starfive/jh7110-evb-uart1-rgb2hdmi.dts
arch/riscv/boot/dts/starfive/jh7110-evb-uart4-emmc-spdif.dts
arch/riscv/boot/dts/starfive/jh7110-evb-uart5-pwm-i2c-tdm.dts
arch/riscv/boot/dts/starfive/jh7110-visionfive-v2.dts
arch/riscv/configs/starfive_jh7110_defconfig
arch/riscv/configs/starfive_visionfive2_defconfig
arch/riscv/include/asm/csr.h
arch/riscv/include/asm/hwcap.h
arch/riscv/include/asm/page.h
arch/riscv/include/asm/perf_event.h
arch/riscv/include/asm/sbi.h
arch/riscv/include/asm/sections.h
arch/riscv/include/asm/suspend.h
arch/riscv/kernel/Makefile
arch/riscv/kernel/asm-offsets.c
arch/riscv/kernel/cpu.c
arch/riscv/kernel/cpufeature.c
arch/riscv/kernel/hibernate-asm.S [new file with mode: 0644]
arch/riscv/kernel/hibernate.c [new file with mode: 0644]
arch/riscv/kernel/perf_event.c [deleted file]
arch/riscv/kernel/sbi.c
arch/riscv/kernel/suspend.c
arch/riscv/mm/init.c
arch/riscv/mm/pageattr.c
drivers/clocksource/timer-starfive.c [changed mode: 0755->0644]
drivers/clocksource/timer-starfive.h [changed mode: 0755->0644]
drivers/irqchip/irq-sifive-plic.c [changed mode: 0755->0644]
drivers/net/can/ipms_canfd.c
drivers/perf/Kconfig
drivers/perf/Makefile
drivers/perf/riscv_pmu.c [new file with mode: 0644]
drivers/perf/riscv_pmu_legacy.c [new file with mode: 0644]
drivers/perf/riscv_pmu_sbi.c [new file with mode: 0644]
include/linux/cpuhotplug.h
include/linux/perf/riscv_pmu.h [new file with mode: 0644]
tools/perf/arch/riscv/util/Build
tools/perf/arch/riscv/util/header.c [new file with mode: 0644]
tools/perf/pmu-events/arch/riscv/mapfile.csv [new file with mode: 0644]
tools/perf/pmu-events/arch/riscv/riscv-generic.json [new file with mode: 0644]
tools/perf/pmu-events/arch/riscv/sifive/u74/instructions.json [new file with mode: 0644]
tools/perf/pmu-events/arch/riscv/sifive/u74/memory.json [new file with mode: 0644]
tools/perf/pmu-events/arch/riscv/sifive/u74/microarch.json [new file with mode: 0644]

diff --git a/Documentation/riscv/pmu.rst b/Documentation/riscv/pmu.rst
deleted file mode 100644 (file)
index acb216b..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-===================================
-Supporting PMUs on RISC-V platforms
-===================================
-
-Alan Kao <alankao@andestech.com>, Mar 2018
-
-Introduction
-------------
-
-As of this writing, perf_event-related features mentioned in The RISC-V ISA
-Privileged Version 1.10 are as follows:
-(please check the manual for more details)
-
-* [m|s]counteren
-* mcycle[h], cycle[h]
-* minstret[h], instret[h]
-* mhpeventx, mhpcounterx[h]
-
-With such function set only, porting perf would require a lot of work, due to
-the lack of the following general architectural performance monitoring features:
-
-* Enabling/Disabling counters
-  Counters are just free-running all the time in our case.
-* Interrupt caused by counter overflow
-  No such feature in the spec.
-* Interrupt indicator
-  It is not possible to have many interrupt ports for all counters, so an
-  interrupt indicator is required for software to tell which counter has
-  just overflowed.
-* Writing to counters
-  There will be an SBI to support this since the kernel cannot modify the
-  counters [1].  Alternatively, some vendor considers to implement
-  hardware-extension for M-S-U model machines to write counters directly.
-
-This document aims to provide developers a quick guide on supporting their
-PMUs in the kernel.  The following sections briefly explain perf' mechanism
-and todos.
-
-You may check previous discussions here [1][2].  Also, it might be helpful
-to check the appendix for related kernel structures.
-
-
-1. Initialization
------------------
-
-*riscv_pmu* is a global pointer of type *struct riscv_pmu*, which contains
-various methods according to perf's internal convention and PMU-specific
-parameters.  One should declare such instance to represent the PMU.  By default,
-*riscv_pmu* points to a constant structure *riscv_base_pmu*, which has very
-basic support to a baseline QEMU model.
-
-Then he/she can either assign the instance's pointer to *riscv_pmu* so that
-the minimal and already-implemented logic can be leveraged, or invent his/her
-own *riscv_init_platform_pmu* implementation.
-
-In other words, existing sources of *riscv_base_pmu* merely provide a
-reference implementation.  Developers can flexibly decide how many parts they
-can leverage, and in the most extreme case, they can customize every function
-according to their needs.
-
-
-2. Event Initialization
------------------------
-
-When a user launches a perf command to monitor some events, it is first
-interpreted by the userspace perf tool into multiple *perf_event_open*
-system calls, and then each of them calls to the body of *event_init*
-member function that was assigned in the previous step.  In *riscv_base_pmu*'s
-case, it is *riscv_event_init*.
-
-The main purpose of this function is to translate the event provided by user
-into bitmap, so that HW-related control registers or counters can directly be
-manipulated.  The translation is based on the mappings and methods provided in
-*riscv_pmu*.
-
-Note that some features can be done in this stage as well:
-
-(1) interrupt setting, which is stated in the next section;
-(2) privilege level setting (user space only, kernel space only, both);
-(3) destructor setting.  Normally it is sufficient to apply *riscv_destroy_event*;
-(4) tweaks for non-sampling events, which will be utilized by functions such as
-    *perf_adjust_period*, usually something like the follows::
-
-      if (!is_sampling_event(event)) {
-              hwc->sample_period = x86_pmu.max_period;
-              hwc->last_period = hwc->sample_period;
-              local64_set(&hwc->period_left, hwc->sample_period);
-      }
-
-In the case of *riscv_base_pmu*, only (3) is provided for now.
-
-
-3. Interrupt
-------------
-
-3.1. Interrupt Initialization
-
-This often occurs at the beginning of the *event_init* method. In common
-practice, this should be a code segment like::
-
-  int x86_reserve_hardware(void)
-  {
-        int err = 0;
-
-        if (!atomic_inc_not_zero(&pmc_refcount)) {
-                mutex_lock(&pmc_reserve_mutex);
-                if (atomic_read(&pmc_refcount) == 0) {
-                        if (!reserve_pmc_hardware())
-                                err = -EBUSY;
-                        else
-                                reserve_ds_buffers();
-                }
-                if (!err)
-                        atomic_inc(&pmc_refcount);
-                mutex_unlock(&pmc_reserve_mutex);
-        }
-
-        return err;
-  }
-
-And the magic is in *reserve_pmc_hardware*, which usually does atomic
-operations to make implemented IRQ accessible from some global function pointer.
-*release_pmc_hardware* serves the opposite purpose, and it is used in event
-destructors mentioned in previous section.
-
-(Note: From the implementations in all the architectures, the *reserve/release*
-pair are always IRQ settings, so the *pmc_hardware* seems somehow misleading.
-It does NOT deal with the binding between an event and a physical counter,
-which will be introduced in the next section.)
-
-3.2. IRQ Structure
-
-Basically, a IRQ runs the following pseudo code::
-
-  for each hardware counter that triggered this overflow
-
-      get the event of this counter
-
-      // following two steps are defined as *read()*,
-      // check the section Reading/Writing Counters for details.
-      count the delta value since previous interrupt
-      update the event->count (# event occurs) by adding delta, and
-                 event->hw.period_left by subtracting delta
-
-      if the event overflows
-          sample data
-          set the counter appropriately for the next overflow
-
-          if the event overflows again
-              too frequently, throttle this event
-          fi
-      fi
-
-  end for
-
-However as of this writing, none of the RISC-V implementations have designed an
-interrupt for perf, so the details are to be completed in the future.
-
-4. Reading/Writing Counters
----------------------------
-
-They seem symmetric but perf treats them quite differently.  For reading, there
-is a *read* interface in *struct pmu*, but it serves more than just reading.
-According to the context, the *read* function not only reads the content of the
-counter (event->count), but also updates the left period to the next interrupt
-(event->hw.period_left).
-
-But the core of perf does not need direct write to counters.  Writing counters
-is hidden behind the abstraction of 1) *pmu->start*, literally start counting so one
-has to set the counter to a good value for the next interrupt; 2) inside the IRQ
-it should set the counter to the same resonable value.
-
-Reading is not a problem in RISC-V but writing would need some effort, since
-counters are not allowed to be written by S-mode.
-
-
-5. add()/del()/start()/stop()
------------------------------
-
-Basic idea: add()/del() adds/deletes events to/from a PMU, and start()/stop()
-starts/stop the counter of some event in the PMU.  All of them take the same
-arguments: *struct perf_event *event* and *int flag*.
-
-Consider perf as a state machine, then you will find that these functions serve
-as the state transition process between those states.
-Three states (event->hw.state) are defined:
-
-* PERF_HES_STOPPED:    the counter is stopped
-* PERF_HES_UPTODATE:   the event->count is up-to-date
-* PERF_HES_ARCH:       arch-dependent usage ... we don't need this for now
-
-A normal flow of these state transitions are as follows:
-
-* A user launches a perf event, resulting in calling to *event_init*.
-* When being context-switched in, *add* is called by the perf core, with a flag
-  PERF_EF_START, which means that the event should be started after it is added.
-  At this stage, a general event is bound to a physical counter, if any.
-  The state changes to PERF_HES_STOPPED and PERF_HES_UPTODATE, because it is now
-  stopped, and the (software) event count does not need updating.
-
-  - *start* is then called, and the counter is enabled.
-    With flag PERF_EF_RELOAD, it writes an appropriate value to the counter (check
-    previous section for detail).
-    Nothing is written if the flag does not contain PERF_EF_RELOAD.
-    The state now is reset to none, because it is neither stopped nor updated
-    (the counting already started)
-
-* When being context-switched out, *del* is called.  It then checks out all the
-  events in the PMU and calls *stop* to update their counts.
-
-  - *stop* is called by *del*
-    and the perf core with flag PERF_EF_UPDATE, and it often shares the same
-    subroutine as *read* with the same logic.
-    The state changes to PERF_HES_STOPPED and PERF_HES_UPTODATE, again.
-
-  - Life cycle of these two pairs: *add* and *del* are called repeatedly as
-    tasks switch in-and-out; *start* and *stop* is also called when the perf core
-    needs a quick stop-and-start, for instance, when the interrupt period is being
-    adjusted.
-
-Current implementation is sufficient for now and can be easily extended to
-features in the future.
-
-A. Related Structures
----------------------
-
-* struct pmu: include/linux/perf_event.h
-* struct riscv_pmu: arch/riscv/include/asm/perf_event.h
-
-  Both structures are designed to be read-only.
-
-  *struct pmu* defines some function pointer interfaces, and most of them take
-  *struct perf_event* as a main argument, dealing with perf events according to
-  perf's internal state machine (check kernel/events/core.c for details).
-
-  *struct riscv_pmu* defines PMU-specific parameters.  The naming follows the
-  convention of all other architectures.
-
-* struct perf_event: include/linux/perf_event.h
-* struct hw_perf_event
-
-  The generic structure that represents perf events, and the hardware-related
-  details.
-
-* struct riscv_hw_events: arch/riscv/include/asm/perf_event.h
-
-  The structure that holds the status of events, has two fixed members:
-  the number of events and the array of the events.
-
-References
-----------
-
-[1] https://github.com/riscv/riscv-linux/pull/124
-
-[2] https://groups.google.com/a/groups.riscv.org/forum/#!topic/sw-dev/f19TmCNP6yA
index 3ef811f..3815c47 100644 (file)
@@ -16094,6 +16094,15 @@ S:     Maintained
 F:     drivers/mtd/nand/raw/r852.c
 F:     drivers/mtd/nand/raw/r852.h
 
+RISC-V PMU DRIVERS
+M:     Atish Patra <atishp@atishpatra.org>
+R:     Anup Patel <anup@brainfault.org>
+L:     linux-riscv@lists.infradead.org
+S:     Supported
+F:     drivers/perf/riscv_pmu.c
+F:     drivers/perf/riscv_pmu_legacy.c
+F:     drivers/perf/riscv_pmu_sbi.c
+
 RISC-V ARCHITECTURE
 M:     Paul Walmsley <paul.walmsley@sifive.com>
 M:     Palmer Dabbelt <palmer@dabbelt.com>
index 6549dca..6fe5d24 100644 (file)
@@ -369,19 +369,6 @@ config RISCV_ISA_C
 
           If you don't know what to do here, say Y.
 
-menu "supported PMU type"
-       depends on PERF_EVENTS
-
-config RISCV_BASE_PMU
-       bool "Base Performance Monitoring Unit"
-       def_bool y
-       help
-         A base PMU that serves as a reference implementation and has limited
-         feature of perf.  It can run on any RISC-V machines so serves as the
-         fallback, but this option can also be disable to reduce kernel size.
-
-endmenu
-
 config FPU
        bool "FPU support"
        default y
@@ -557,6 +544,16 @@ config XIP_PHYS_ADDR
          be linked for and stored to.  This address is dependent on your
          own flash usage.
 
+config ARCH_SUSPEND_POSSIBLE
+        def_bool y
+
+config ARCH_HIBERNATION_POSSIBLE
+       def_bool y
+
+config ARCH_HIBERNATION_HEADER
+       def_bool y
+       depends on HIBERNATION
+
 endmenu
 
 config BUILTIN_DTB
index 2c543cc..cf18657 100644 (file)
 
 /* default sd card */
 &sdio0 {
-       clock-frequency = <102400000>;
-       max-frequency = <200000000>;
+       max-frequency = <100000000>;
        card-detect-delay = <300>;
        bus-width = <4>;
        broken-cd;
        cap-sd-highspeed;
        post-power-on-delay-ms = <200>;
-       pinctrl-names = "default";
-       pinctrl-0 = <&sdcard0_pins>;
        status = "okay";
 };
 
index ab3eee1..b4bf910 100644 (file)
        compatible = "starfive,jh7110-evb", "starfive,jh7110";
 };
 
+/* default sd card */
+&sdio0 {
+       max-frequency = <100000000>;
+       card-detect-delay = <300>;
+       bus-width = <4>;
+       broken-cd;
+       cap-sd-highspeed;
+       post-power-on-delay-ms = <200>;
+       status = "okay";
+};
+
 &vin_sysctl {
        pinctrl-names = "default";
        pinctrl-0 = <&dvp_pins>;
index 3516040..7d49c50 100644 (file)
        compatible = "starfive,jh7110-evb", "starfive,jh7110";
 };
 
+/* default sd card */
+&sdio0 {
+       max-frequency = <100000000>;
+       card-detect-delay = <300>;
+       bus-width = <4>;
+       broken-cd;
+       cap-sd-highspeed;
+       post-power-on-delay-ms = <200>;
+       status = "okay";
+};
 
 &i2c0 {
        status = "okay";
index 152ef0a..2639a0f 100644 (file)
 
 /* default sd card */
 &sdio0 {
-       clock-frequency = <102400000>;
-       max-frequency = <200000000>;
+       max-frequency = <100000000>;
        card-detect-delay = <300>;
        bus-width = <4>;
        broken-cd;
        cap-sd-highspeed;
        post-power-on-delay-ms = <200>;
-       pinctrl-names = "default";
-       pinctrl-0 = <&sdcard0_pins>;
        status = "okay";
 };
 
index e9ad788..0218522 100644 (file)
@@ -21,8 +21,6 @@
        broken-cd;
        cap-sd-highspeed;
        post-power-on-delay-ms = <200>;
-       pinctrl-names = "default";
-       pinctrl-0 = <&sdcard0_pins>;
        status = "okay";
 };
 
index 3580cd0..b72135b 100644 (file)
 
 /* default sd card */
 &sdio0 {
-       clock-frequency = <102400000>;
-       max-frequency = <200000000>;
+       max-frequency = <100000000>;
        card-detect-delay = <300>;
        bus-width = <4>;
        broken-cd;
        cap-sd-highspeed;
        post-power-on-delay-ms = <200>;
-       pinctrl-names = "default";
-       pinctrl-0 = <&sdcard0_pins>;
        status = "okay";
 };
 
index 46846c8..e0e4e1a 100755 (executable)
        non-removable;
        cap-mmc-hw-reset;
        post-power-on-delay-ms = <200>;
-       pinctrl-names = "default";
-       pinctrl-0 = <&mmc0_pins>;
        status = "okay";
 };
 
 &sdio1 {
-       clock-frequency = <102400000>;
        max-frequency = <100000000>;
        card-detect-delay = <300>;
        bus-width = <8>;
@@ -50,8 +47,6 @@
        non-removable;
        cap-mmc-highspeed;
        post-power-on-delay-ms = <200>;
-       pinctrl-names = "default";
-       pinctrl-0 = <&emmc1_pins>;
        status = "okay";
 };
 
index 2da364b..ce21610 100644 (file)
 
 /* default sd card */
 &sdio0 {
-       clock-frequency = <102400000>;
-       max-frequency = <200000000>;
+       max-frequency = <100000000>;
        card-detect-delay = <300>;
        bus-width = <4>;
        broken-cd;
        cap-sd-highspeed;
        post-power-on-delay-ms = <200>;
-       pinctrl-names = "default";
-       pinctrl-0 = <&sdcard0_pins>;
        status = "okay";
 };
 
index b0499a7..cf36ed3 100644 (file)
@@ -15,7 +15,7 @@
        gpio-restart {
                compatible = "gpio-restart";
                gpios = <&gpio 35 GPIO_ACTIVE_HIGH>;
-               priority = <224>;
+               priority = <160>;
        };
 
 };
index df1ec23..bafd407 100644 (file)
@@ -16,13 +16,16 @@ CONFIG_USER_NS=y
 CONFIG_CHECKPOINT_RESTORE=y
 CONFIG_BLK_DEV_INITRD=y
 CONFIG_EXPERT=y
+CONFIG_PERF_EVENTS=y
 CONFIG_SOC_STARFIVE=y
 CONFIG_SOC_STARFIVE_JH7110=y
 CONFIG_SMP=y
 CONFIG_HZ_100=y
-CONFIG_PM=y
+CONFIG_HIBERNATION=y
+CONFIG_PM_STD_PARTITION="PARTLABEL=hibernation"
 CONFIG_PM_DEBUG=y
 CONFIG_PM_ADVANCED_DEBUG=y
+CONFIG_PM_TEST_SUSPEND=y
 CONFIG_CPU_IDLE=y
 CONFIG_RISCV_SBI_CPUIDLE=y
 # CONFIG_SECCOMP is not set
index 86dafe2..7ca56e5 100755 (executable)
@@ -16,6 +16,7 @@ CONFIG_USER_NS=y
 CONFIG_CHECKPOINT_RESTORE=y
 CONFIG_BLK_DEV_INITRD=y
 CONFIG_EXPERT=y
+CONFIG_PERF_EVENTS=y
 CONFIG_SOC_STARFIVE=y
 CONFIG_SOC_STARFIVE_JH7110=y
 CONFIG_SMP=y
index 87ac656..8518eb0 100644 (file)
@@ -63,6 +63,7 @@
 #define IRQ_M_TIMER            7
 #define IRQ_S_EXT              9
 #define IRQ_M_EXT              11
+#define IRQ_PMU_OVF            13
 
 /* Exception causes */
 #define EXC_INST_MISALIGNED    0
 #define CSR_CYCLE              0xc00
 #define CSR_TIME               0xc01
 #define CSR_INSTRET            0xc02
+#define CSR_HPMCOUNTER3                0xc03
+#define CSR_HPMCOUNTER4                0xc04
+#define CSR_HPMCOUNTER5                0xc05
+#define CSR_HPMCOUNTER6                0xc06
+#define CSR_HPMCOUNTER7                0xc07
+#define CSR_HPMCOUNTER8                0xc08
+#define CSR_HPMCOUNTER9                0xc09
+#define CSR_HPMCOUNTER10       0xc0a
+#define CSR_HPMCOUNTER11       0xc0b
+#define CSR_HPMCOUNTER12       0xc0c
+#define CSR_HPMCOUNTER13       0xc0d
+#define CSR_HPMCOUNTER14       0xc0e
+#define CSR_HPMCOUNTER15       0xc0f
+#define CSR_HPMCOUNTER16       0xc10
+#define CSR_HPMCOUNTER17       0xc11
+#define CSR_HPMCOUNTER18       0xc12
+#define CSR_HPMCOUNTER19       0xc13
+#define CSR_HPMCOUNTER20       0xc14
+#define CSR_HPMCOUNTER21       0xc15
+#define CSR_HPMCOUNTER22       0xc16
+#define CSR_HPMCOUNTER23       0xc17
+#define CSR_HPMCOUNTER24       0xc18
+#define CSR_HPMCOUNTER25       0xc19
+#define CSR_HPMCOUNTER26       0xc1a
+#define CSR_HPMCOUNTER27       0xc1b
+#define CSR_HPMCOUNTER28       0xc1c
+#define CSR_HPMCOUNTER29       0xc1d
+#define CSR_HPMCOUNTER30       0xc1e
+#define CSR_HPMCOUNTER31       0xc1f
 #define CSR_CYCLEH             0xc80
 #define CSR_TIMEH              0xc81
 #define CSR_INSTRETH           0xc82
+#define CSR_HPMCOUNTER3H       0xc83
+#define CSR_HPMCOUNTER4H       0xc84
+#define CSR_HPMCOUNTER5H       0xc85
+#define CSR_HPMCOUNTER6H       0xc86
+#define CSR_HPMCOUNTER7H       0xc87
+#define CSR_HPMCOUNTER8H       0xc88
+#define CSR_HPMCOUNTER9H       0xc89
+#define CSR_HPMCOUNTER10H      0xc8a
+#define CSR_HPMCOUNTER11H      0xc8b
+#define CSR_HPMCOUNTER12H      0xc8c
+#define CSR_HPMCOUNTER13H      0xc8d
+#define CSR_HPMCOUNTER14H      0xc8e
+#define CSR_HPMCOUNTER15H      0xc8f
+#define CSR_HPMCOUNTER16H      0xc90
+#define CSR_HPMCOUNTER17H      0xc91
+#define CSR_HPMCOUNTER18H      0xc92
+#define CSR_HPMCOUNTER19H      0xc93
+#define CSR_HPMCOUNTER20H      0xc94
+#define CSR_HPMCOUNTER21H      0xc95
+#define CSR_HPMCOUNTER22H      0xc96
+#define CSR_HPMCOUNTER23H      0xc97
+#define CSR_HPMCOUNTER24H      0xc98
+#define CSR_HPMCOUNTER25H      0xc99
+#define CSR_HPMCOUNTER26H      0xc9a
+#define CSR_HPMCOUNTER27H      0xc9b
+#define CSR_HPMCOUNTER28H      0xc9c
+#define CSR_HPMCOUNTER29H      0xc9d
+#define CSR_HPMCOUNTER30H      0xc9e
+#define CSR_HPMCOUNTER31H      0xc9f
+
+#define CSR_SSCOUNTOVF         0xda0
 
 #define CSR_SSTATUS            0x100
 #define CSR_SIE                        0x104
 # define RV_IRQ_SOFT           IRQ_S_SOFT
 # define RV_IRQ_TIMER  IRQ_S_TIMER
 # define RV_IRQ_EXT            IRQ_S_EXT
-#endif /* CONFIG_RISCV_M_MODE */
+# define RV_IRQ_PMU    IRQ_PMU_OVF
+# define SIP_LCOFIP     (_AC(0x1, UL) << IRQ_PMU_OVF)
+
+#endif /* !CONFIG_RISCV_M_MODE */
 
 /* IE/IP (Supervisor/Machine Interrupt Enable/Pending) flags */
 #define IE_SIE         (_AC(0x1, UL) << RV_IRQ_SOFT)
index 5ce5046..0734e42 100644 (file)
@@ -34,7 +34,33 @@ extern unsigned long elf_hwcap;
 #define RISCV_ISA_EXT_s                ('s' - 'a')
 #define RISCV_ISA_EXT_u                ('u' - 'a')
 
+/*
+ * Increse this to higher value as kernel support more ISA extensions.
+ */
 #define RISCV_ISA_EXT_MAX      64
+#define RISCV_ISA_EXT_NAME_LEN_MAX 32
+
+/* The base ID for multi-letter ISA extensions */
+#define RISCV_ISA_EXT_BASE 26
+
+/*
+ * This enum represent the logical ID for each multi-letter RISC-V ISA extension.
+ * The logical ID should start from RISCV_ISA_EXT_BASE and must not exceed
+ * RISCV_ISA_EXT_MAX. 0-25 range is reserved for single letter
+ * extensions while all the multi-letter extensions should define the next
+ * available logical extension id.
+ */
+enum riscv_isa_ext_id {
+       RISCV_ISA_EXT_SSCOFPMF = RISCV_ISA_EXT_BASE,
+       RISCV_ISA_EXT_ID_MAX = RISCV_ISA_EXT_MAX,
+};
+
+struct riscv_isa_ext_data {
+       /* Name of the extension displayed to userspace via /proc/cpuinfo */
+       char uprop[RISCV_ISA_EXT_NAME_LEN_MAX];
+       /* The logical ISA extension ID */
+       unsigned int isa_ext_id;
+};
 
 unsigned long riscv_isa_extension_base(const unsigned long *isa_bitmap);
 
index 109c97e..b3e5ff0 100644 (file)
@@ -157,6 +157,8 @@ extern phys_addr_t __phys_addr_symbol(unsigned long x);
 #define page_to_bus(page)      (page_to_phys(page))
 #define phys_to_page(paddr)    (pfn_to_page(phys_to_pfn(paddr)))
 
+#define sym_to_pfn(x)           __phys_to_pfn(__pa_symbol(x))
+
 #ifdef CONFIG_FLATMEM
 #define pfn_valid(pfn) \
        (((pfn) >= ARCH_PFN_OFFSET) && (((pfn) - ARCH_PFN_OFFSET) < max_mapnr))
index 062efd3..d42c901 100644 (file)
@@ -9,77 +9,5 @@
 #define _ASM_RISCV_PERF_EVENT_H
 
 #include <linux/perf_event.h>
-#include <linux/ptrace.h>
-#include <linux/interrupt.h>
-
-#ifdef CONFIG_RISCV_BASE_PMU
-#define RISCV_BASE_COUNTERS    2
-
-/*
- * The RISCV_MAX_COUNTERS parameter should be specified.
- */
-
-#define RISCV_MAX_COUNTERS     2
-
-/*
- * These are the indexes of bits in counteren register *minus* 1,
- * except for cycle.  It would be coherent if it can directly mapped
- * to counteren bit definition, but there is a *time* register at
- * counteren[1].  Per-cpu structure is scarce resource here.
- *
- * According to the spec, an implementation can support counter up to
- * mhpmcounter31, but many high-end processors has at most 6 general
- * PMCs, we give the definition to MHPMCOUNTER8 here.
- */
-#define RISCV_PMU_CYCLE                0
-#define RISCV_PMU_INSTRET      1
-#define RISCV_PMU_MHPMCOUNTER3 2
-#define RISCV_PMU_MHPMCOUNTER4 3
-#define RISCV_PMU_MHPMCOUNTER5 4
-#define RISCV_PMU_MHPMCOUNTER6 5
-#define RISCV_PMU_MHPMCOUNTER7 6
-#define RISCV_PMU_MHPMCOUNTER8 7
-
-#define RISCV_OP_UNSUPP                (-EOPNOTSUPP)
-
-struct cpu_hw_events {
-       /* # currently enabled events*/
-       int                     n_events;
-       /* currently enabled events */
-       struct perf_event       *events[RISCV_MAX_COUNTERS];
-       /* vendor-defined PMU data */
-       void                    *platform;
-};
-
-struct riscv_pmu {
-       struct pmu      *pmu;
-
-       /* generic hw/cache events table */
-       const int       *hw_events;
-       const int       (*cache_events)[PERF_COUNT_HW_CACHE_MAX]
-                                      [PERF_COUNT_HW_CACHE_OP_MAX]
-                                      [PERF_COUNT_HW_CACHE_RESULT_MAX];
-       /* method used to map hw/cache events */
-       int             (*map_hw_event)(u64 config);
-       int             (*map_cache_event)(u64 config);
-
-       /* max generic hw events in map */
-       int             max_events;
-       /* number total counters, 2(base) + x(general) */
-       int             num_counters;
-       /* the width of the counter */
-       int             counter_width;
-
-       /* vendor-defined PMU features */
-       void            *platform;
-
-       irqreturn_t     (*handle_irq)(int irq_num, void *dev);
-       int             irq;
-};
-
-#endif
-#ifdef CONFIG_PERF_EVENTS
 #define perf_arch_bpf_user_pt_regs(regs) (struct user_regs_struct *)regs
-#endif
-
 #endif /* _ASM_RISCV_PERF_EVENT_H */
index 3338ca2..5f4ae4c 100644 (file)
@@ -28,6 +28,7 @@ enum sbi_ext_id {
        SBI_EXT_RFENCE = 0x52464E43,
        SBI_EXT_HSM = 0x48534D,
        SBI_EXT_SRST = 0x53525354,
+       SBI_EXT_PMU = 0x504D55,
 };
 
 enum sbi_ext_base_fid {
@@ -102,6 +103,98 @@ enum sbi_srst_reset_reason {
        SBI_SRST_RESET_REASON_SYS_FAILURE,
 };
 
+enum sbi_ext_pmu_fid {
+       SBI_EXT_PMU_NUM_COUNTERS = 0,
+       SBI_EXT_PMU_COUNTER_GET_INFO,
+       SBI_EXT_PMU_COUNTER_CFG_MATCH,
+       SBI_EXT_PMU_COUNTER_START,
+       SBI_EXT_PMU_COUNTER_STOP,
+       SBI_EXT_PMU_COUNTER_FW_READ,
+};
+
+#define RISCV_PMU_RAW_EVENT_MASK GENMASK_ULL(55, 0)
+#define RISCV_PMU_RAW_EVENT_IDX 0x20000
+
+/** General pmu event codes specified in SBI PMU extension */
+enum sbi_pmu_hw_generic_events_t {
+       SBI_PMU_HW_NO_EVENT                     = 0,
+       SBI_PMU_HW_CPU_CYCLES                   = 1,
+       SBI_PMU_HW_INSTRUCTIONS                 = 2,
+       SBI_PMU_HW_CACHE_REFERENCES             = 3,
+       SBI_PMU_HW_CACHE_MISSES                 = 4,
+       SBI_PMU_HW_BRANCH_INSTRUCTIONS          = 5,
+       SBI_PMU_HW_BRANCH_MISSES                = 6,
+       SBI_PMU_HW_BUS_CYCLES                   = 7,
+       SBI_PMU_HW_STALLED_CYCLES_FRONTEND      = 8,
+       SBI_PMU_HW_STALLED_CYCLES_BACKEND       = 9,
+       SBI_PMU_HW_REF_CPU_CYCLES               = 10,
+
+       SBI_PMU_HW_GENERAL_MAX,
+};
+
+/**
+ * Special "firmware" events provided by the firmware, even if the hardware
+ * does not support performance events. These events are encoded as a raw
+ * event type in Linux kernel perf framework.
+ */
+enum sbi_pmu_fw_generic_events_t {
+       SBI_PMU_FW_MISALIGNED_LOAD      = 0,
+       SBI_PMU_FW_MISALIGNED_STORE     = 1,
+       SBI_PMU_FW_ACCESS_LOAD          = 2,
+       SBI_PMU_FW_ACCESS_STORE         = 3,
+       SBI_PMU_FW_ILLEGAL_INSN         = 4,
+       SBI_PMU_FW_SET_TIMER            = 5,
+       SBI_PMU_FW_IPI_SENT             = 6,
+       SBI_PMU_FW_IPI_RECVD            = 7,
+       SBI_PMU_FW_FENCE_I_SENT         = 8,
+       SBI_PMU_FW_FENCE_I_RECVD        = 9,
+       SBI_PMU_FW_SFENCE_VMA_SENT      = 10,
+       SBI_PMU_FW_SFENCE_VMA_RCVD      = 11,
+       SBI_PMU_FW_SFENCE_VMA_ASID_SENT = 12,
+       SBI_PMU_FW_SFENCE_VMA_ASID_RCVD = 13,
+
+       SBI_PMU_FW_HFENCE_GVMA_SENT     = 14,
+       SBI_PMU_FW_HFENCE_GVMA_RCVD     = 15,
+       SBI_PMU_FW_HFENCE_GVMA_VMID_SENT = 16,
+       SBI_PMU_FW_HFENCE_GVMA_VMID_RCVD = 17,
+
+       SBI_PMU_FW_HFENCE_VVMA_SENT     = 18,
+       SBI_PMU_FW_HFENCE_VVMA_RCVD     = 19,
+       SBI_PMU_FW_HFENCE_VVMA_ASID_SENT = 20,
+       SBI_PMU_FW_HFENCE_VVMA_ASID_RCVD = 21,
+       SBI_PMU_FW_MAX,
+};
+
+/* SBI PMU event types */
+enum sbi_pmu_event_type {
+       SBI_PMU_EVENT_TYPE_HW = 0x0,
+       SBI_PMU_EVENT_TYPE_CACHE = 0x1,
+       SBI_PMU_EVENT_TYPE_RAW = 0x2,
+       SBI_PMU_EVENT_TYPE_FW = 0xf,
+};
+
+/* SBI PMU event types */
+enum sbi_pmu_ctr_type {
+       SBI_PMU_CTR_TYPE_HW = 0x0,
+       SBI_PMU_CTR_TYPE_FW,
+};
+
+/* Flags defined for config matching function */
+#define SBI_PMU_CFG_FLAG_SKIP_MATCH    (1 << 0)
+#define SBI_PMU_CFG_FLAG_CLEAR_VALUE   (1 << 1)
+#define SBI_PMU_CFG_FLAG_AUTO_START    (1 << 2)
+#define SBI_PMU_CFG_FLAG_SET_VUINH     (1 << 3)
+#define SBI_PMU_CFG_FLAG_SET_VSNH      (1 << 4)
+#define SBI_PMU_CFG_FLAG_SET_UINH      (1 << 5)
+#define SBI_PMU_CFG_FLAG_SET_SINH      (1 << 6)
+#define SBI_PMU_CFG_FLAG_SET_MINH      (1 << 7)
+
+/* Flags defined for counter start function */
+#define SBI_PMU_START_FLAG_SET_INIT_VALUE (1 << 0)
+
+/* Flags defined for counter stop function */
+#define SBI_PMU_STOP_FLAG_RESET (1 << 0)
+
 #define SBI_SPEC_VERSION_DEFAULT       0x1
 #define SBI_SPEC_VERSION_MAJOR_SHIFT   24
 #define SBI_SPEC_VERSION_MAJOR_MASK    0x7f
@@ -114,6 +207,9 @@ enum sbi_srst_reset_reason {
 #define SBI_ERR_INVALID_PARAM  -3
 #define SBI_ERR_DENIED         -4
 #define SBI_ERR_INVALID_ADDRESS        -5
+#define SBI_ERR_ALREADY_AVAILABLE -6
+#define SBI_ERR_ALREADY_STARTED -7
+#define SBI_ERR_ALREADY_STOPPED -8
 
 extern unsigned long sbi_spec_version;
 struct sbiret {
index 32336e8..ad1588d 100644 (file)
@@ -13,6 +13,7 @@ extern char _start_kernel[];
 extern char __init_data_begin[], __init_data_end[];
 extern char __init_text_begin[], __init_text_end[];
 extern char __alt_start[], __alt_end[];
+extern phys_addr_t end_linear_map;
 
 static inline bool is_va_kernel_text(uintptr_t va)
 {
index 8be391c..de8b4ea 100644 (file)
@@ -21,6 +21,11 @@ struct suspend_context {
 #endif
 };
 
+/* This value will be assigned to 0 during resume and will be used by
+ * hibernation core for the subsequent resume sequence
+ */
+extern int in_suspend;
+
 /* Low-level CPU suspend entry function */
 int __cpu_suspend_enter(struct suspend_context *context);
 
@@ -33,4 +38,21 @@ int cpu_suspend(unsigned long arg,
 /* Low-level CPU resume entry function */
 int __cpu_resume_enter(unsigned long hartid, unsigned long context);
 
+/* Low-level API to support hibernation */
+int swsusp_arch_suspend(void);
+int swsusp_arch_resume(void);
+int arch_hibernation_header_save(void *addr, unsigned int max_size);
+int arch_hibernation_header_restore(void *addr);
+int __hibernate_cpu_resume(unsigned long context);
+
+/* Used to resume on the CPU we hibernated on */
+int hibernate_resume_nonboot_cpu_disable(void);
+
+/* Used to save and restore the csr */
+void suspend_save_csrs(struct suspend_context *context);
+void suspend_restore_csrs(struct suspend_context *context);
+
+asmlinkage void restore_image(unsigned long resume_satp, unsigned long satp_temp,
+                               unsigned long cpu_resume, unsigned long resume_context);
+asmlinkage int core_restore_code(void);
 #endif
index 4320629..9163091 100644 (file)
@@ -48,11 +48,10 @@ obj-$(CONFIG_MODULES)               += module.o
 obj-$(CONFIG_MODULE_SECTIONS)  += module-sections.o
 
 obj-$(CONFIG_CPU_PM)           += suspend_entry.o suspend.o
-
+obj-$(CONFIG_HIBERNATION)      += hibernate.o hibernate-asm.o
 obj-$(CONFIG_FUNCTION_TRACER)  += mcount.o ftrace.o
 obj-$(CONFIG_DYNAMIC_FTRACE)   += mcount-dyn.o
 
-obj-$(CONFIG_RISCV_BASE_PMU)   += perf_event.o
 obj-$(CONFIG_PERF_EVENTS)      += perf_callchain.o
 obj-$(CONFIG_HAVE_PERF_REGS)   += perf_regs.o
 obj-$(CONFIG_RISCV_SBI)                += sbi.o
index 9ec6731..6758453 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <linux/kbuild.h>
 #include <linux/sched.h>
+#include <linux/suspend.h>
 #include <asm/thread_info.h>
 #include <asm/ptrace.h>
 #include <asm/suspend.h>
@@ -115,6 +116,10 @@ void asm_offsets(void)
 
        OFFSET(SUSPEND_CONTEXT_REGS, suspend_context, regs);
 
+       OFFSET(HIBERN_PBE_ADDR, pbe, address);
+       OFFSET(HIBERN_PBE_ORIG, pbe, orig_address);
+       OFFSET(HIBERN_PBE_NEXT, pbe, next);
+
        /*
         * THREAD_{F,X}* might be larger than a S-type offset can handle, but
         * these are used in performance-sensitive assembly so we can't resort
index 6d59e69..921b859 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/init.h>
 #include <linux/seq_file.h>
 #include <linux/of.h>
+#include <asm/hwcap.h>
 #include <asm/smp.h>
 
 /*
@@ -61,12 +62,51 @@ int riscv_of_parent_hartid(struct device_node *node)
 }
 
 #ifdef CONFIG_PROC_FS
+#define __RISCV_ISA_EXT_DATA(UPROP, EXTID) \
+       {                                                       \
+               .uprop = #UPROP,                                \
+               .isa_ext_id = EXTID,                            \
+       }
+
+static struct riscv_isa_ext_data isa_ext_arr[] = {
+       __RISCV_ISA_EXT_DATA(sscofpmf, RISCV_ISA_EXT_SSCOFPMF),
+       __RISCV_ISA_EXT_DATA("", RISCV_ISA_EXT_MAX),
+};
+
+static void print_isa_ext(struct seq_file *f)
+{
+       struct riscv_isa_ext_data *edata;
+       int i = 0, arr_sz;
+
+       arr_sz = ARRAY_SIZE(isa_ext_arr) - 1;
+
+       /* No extension support available */
+       if (arr_sz <= 0)
+               return;
+
+       seq_puts(f, "isa-ext\t\t: ");
+       for (i = 0; i <= arr_sz; i++) {
+               edata = &isa_ext_arr[i];
+               if (!__riscv_isa_extension_available(NULL, edata->isa_ext_id))
+                       continue;
+               seq_printf(f, "%s ", edata->uprop);
+       }
+       seq_puts(f, "\n");
+}
 
 static void print_isa(struct seq_file *f, const char *isa)
 {
-       /* Print the entire ISA as it is */
+       char *ext_start;
+       int isa_len = strlen(isa);
+       int base_isa_len = isa_len;
+
+       ext_start = strnchr(isa, isa_len, '_');
+       if (ext_start)
+               base_isa_len = isa_len - strlen(ext_start);
+
+       /* Print only the base ISA as it is */
        seq_puts(f, "isa\t\t: ");
-       seq_write(f, isa, strlen(isa));
+       seq_write(f, isa, base_isa_len);
        seq_puts(f, "\n");
 }
 
@@ -114,6 +154,9 @@ static int c_show(struct seq_file *m, void *v)
                print_isa(m, isa);
        if (!of_property_read_string(node, "mmu-type", &mmu))
                print_mmu(m, mmu);
+
+       print_isa_ext(m);
+
        if (!of_property_read_string(node, "compatible", &compat)
            && strcmp(compat, "riscv"))
                seq_printf(m, "uarch\t\t: %s\n", compat);
index d959d20..eabf82a 100644 (file)
@@ -7,12 +7,15 @@
  */
 
 #include <linux/bitmap.h>
+#include <linux/ctype.h>
 #include <linux/of.h>
 #include <asm/processor.h>
 #include <asm/hwcap.h>
 #include <asm/smp.h>
 #include <asm/switch_to.h>
 
+#define NUM_ALPHA_EXTS ('z' - 'a' + 1)
+
 unsigned long elf_hwcap __read_mostly;
 
 /* Host ISA bitmap */
@@ -63,8 +66,8 @@ void __init riscv_fill_hwcap(void)
 {
        struct device_node *node;
        const char *isa;
-       char print_str[BITS_PER_LONG + 1];
-       size_t i, j, isa_len;
+       char print_str[NUM_ALPHA_EXTS + 1];
+       int i, j;
        static unsigned long isa2hwcap[256] = {0};
 
        isa2hwcap['i'] = isa2hwcap['I'] = COMPAT_HWCAP_ISA_I;
@@ -80,7 +83,8 @@ void __init riscv_fill_hwcap(void)
 
        for_each_of_cpu_node(node) {
                unsigned long this_hwcap = 0;
-               unsigned long this_isa = 0;
+               DECLARE_BITMAP(this_isa, RISCV_ISA_EXT_MAX);
+               const char *temp;
 
                if (riscv_of_processor_hartid(node) < 0)
                        continue;
@@ -90,23 +94,103 @@ void __init riscv_fill_hwcap(void)
                        continue;
                }
 
-               i = 0;
-               isa_len = strlen(isa);
+               temp = isa;
 #if IS_ENABLED(CONFIG_32BIT)
                if (!strncmp(isa, "rv32", 4))
-                       i += 4;
+                       isa += 4;
 #elif IS_ENABLED(CONFIG_64BIT)
                if (!strncmp(isa, "rv64", 4))
-                       i += 4;
+                       isa += 4;
 #endif
-               for (; i < isa_len; ++i) {
-                       this_hwcap |= isa2hwcap[(unsigned char)(isa[i])];
-                       /*
-                        * TODO: X, Y and Z extension parsing for Host ISA
-                        * bitmap will be added in-future.
-                        */
-                       if ('a' <= isa[i] && isa[i] < 'x')
-                               this_isa |= (1UL << (isa[i] - 'a'));
+               /* The riscv,isa DT property must start with rv64 or rv32 */
+               if (temp == isa)
+                       continue;
+               bitmap_zero(this_isa, RISCV_ISA_EXT_MAX);
+               for (; *isa; ++isa) {
+                       const char *ext = isa++;
+                       const char *ext_end = isa;
+                       bool ext_long = false, ext_err = false;
+
+                       switch (*ext) {
+                       case 's':
+                       case 'x':
+                       case 'z':
+                               /**
+                                * Workaround for invalid single-letter 's' (QEMU).
+                                * It works until multi-letter extension starting
+                                * with "Su" appears.
+                                */
+                               if (*ext == 's' && ext[-1] != '_' && ext[1] == 'u')
+                                       break;
+                               ext_long = true;
+                               /* Multi-letter extension must be delimited */
+                               for (; *isa && *isa != '_'; ++isa)
+                                       if (unlikely(!islower(*isa)
+                                                    && !isdigit(*isa)))
+                                               ext_err = true;
+                               /* Parse backwards */
+                               ext_end = isa;
+                               if (unlikely(ext_err))
+                                       break;
+                               if (!isdigit(ext_end[-1]))
+                                       break;
+                               /* Skip the minor version */
+                               while (isdigit(*--ext_end))
+                                       ;
+                               if (ext_end[0] != 'p'
+                                   || !isdigit(ext_end[-1])) {
+                                       /* Advance it to offset the pre-decrement */
+                                       ++ext_end;
+                                       break;
+                               }
+                               /* Skip the major version */
+                               while (isdigit(*--ext_end))
+                                       ;
+                               ++ext_end;
+                               break;
+                       default:
+                               if (unlikely(!islower(*ext))) {
+                                       ext_err = true;
+                                       break;
+                               }
+                               /* Find next extension */
+                               if (!isdigit(*isa))
+                                       break;
+                               /* Skip the minor version */
+                               while (isdigit(*++isa))
+                                       ;
+                               if (*isa != 'p')
+                                       break;
+                               if (!isdigit(*++isa)) {
+                                       --isa;
+                                       break;
+                               }
+                               /* Skip the major version */
+                               while (isdigit(*++isa))
+                                       ;
+                               break;
+                       }
+                       if (*isa != '_')
+                               --isa;
+
+#define SET_ISA_EXT_MAP(name, bit)                                             \
+                       do {                                                    \
+                               if ((ext_end - ext == sizeof(name) - 1) &&      \
+                                    !memcmp(ext, name, sizeof(name) - 1)) {    \
+                                       set_bit(bit, this_isa);                 \
+                                       pr_info("Found ISA extension %s", name);\
+                               }                                               \
+                       } while (false)                                         \
+
+                       if (unlikely(ext_err))
+                               continue;
+                       if (!ext_long) {
+                               this_hwcap |= isa2hwcap[(unsigned char)(*ext)];
+                               set_bit(*ext - 'a', this_isa);
+                       } else {
+                               SET_ISA_EXT_MAP("sscofpmf", RISCV_ISA_EXT_SSCOFPMF);
+                       }
+#undef SET_ISA_EXT_MAP
                }
 
                /*
@@ -119,10 +203,11 @@ void __init riscv_fill_hwcap(void)
                else
                        elf_hwcap = this_hwcap;
 
-               if (riscv_isa[0])
-                       riscv_isa[0] &= this_isa;
+               if (bitmap_weight(riscv_isa, RISCV_ISA_EXT_MAX))
+                       bitmap_and(riscv_isa, riscv_isa, this_isa, RISCV_ISA_EXT_MAX);
                else
-                       riscv_isa[0] = this_isa;
+                       bitmap_copy(riscv_isa, this_isa, RISCV_ISA_EXT_MAX);
+
        }
 
        /* We don't support systems with F but without D, so mask those out
@@ -133,13 +218,13 @@ void __init riscv_fill_hwcap(void)
        }
 
        memset(print_str, 0, sizeof(print_str));
-       for (i = 0, j = 0; i < BITS_PER_LONG; i++)
+       for (i = 0, j = 0; i < NUM_ALPHA_EXTS; i++)
                if (riscv_isa[0] & BIT_MASK(i))
                        print_str[j++] = (char)('a' + i);
-       pr_info("riscv: ISA extensions %s\n", print_str);
+       pr_info("riscv: base ISA extensions %s\n", print_str);
 
        memset(print_str, 0, sizeof(print_str));
-       for (i = 0, j = 0; i < BITS_PER_LONG; i++)
+       for (i = 0, j = 0; i < NUM_ALPHA_EXTS; i++)
                if (elf_hwcap & BIT_MASK(i))
                        print_str[j++] = (char)('a' + i);
        pr_info("riscv: ELF capabilities %s\n", print_str);
diff --git a/arch/riscv/kernel/hibernate-asm.S b/arch/riscv/kernel/hibernate-asm.S
new file mode 100644 (file)
index 0000000..60a9891
--- /dev/null
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Hibernate support specific for RISCV
+ *
+ * Copyright (C) 2022 Shanghai StarFive Technology Co., Ltd.
+ *
+ * Author: Jee Heng Sia <jeeheng.sia@starfivetech.com>
+ *
+ */
+
+
+#include <linux/linkage.h>
+#include <asm/asm.h>
+#include <asm/asm-offsets.h>
+#include <asm/csr.h>
+
+       .text
+       .altmacro
+       .option norelax
+
+ENTRY(__hibernate_cpu_resume)
+       /* Load the global pointer */
+       .option push
+       .option norelax
+       la gp, __global_pointer$
+       .option pop
+
+        /* switch to root  page table */
+        csrw CSR_SATP, s0
+        sfence.vma
+
+       ld      a0, hibernate_cpu_context
+
+       /* Restore CSRs */
+       REG_L   t0, (SUSPEND_CONTEXT_REGS + PT_EPC)(a0)
+       csrw    CSR_EPC, t0
+       REG_L   t0, (SUSPEND_CONTEXT_REGS + PT_STATUS)(a0)
+       csrw    CSR_STATUS, t0
+       REG_L   t0, (SUSPEND_CONTEXT_REGS + PT_BADADDR)(a0)
+       csrw    CSR_TVAL, t0
+       REG_L   t0, (SUSPEND_CONTEXT_REGS + PT_CAUSE)(a0)
+       csrw    CSR_CAUSE, t0
+
+       /* Restore registers (except A0 and T0-T6) */
+       REG_L   ra, (SUSPEND_CONTEXT_REGS + PT_RA)(a0)
+       REG_L   sp, (SUSPEND_CONTEXT_REGS + PT_SP)(a0)
+       REG_L   gp, (SUSPEND_CONTEXT_REGS + PT_GP)(a0)
+       REG_L   tp, (SUSPEND_CONTEXT_REGS + PT_TP)(a0)
+
+       REG_L   s0, (SUSPEND_CONTEXT_REGS + PT_S0)(a0)
+       REG_L   s1, (SUSPEND_CONTEXT_REGS + PT_S1)(a0)
+       REG_L   a1, (SUSPEND_CONTEXT_REGS + PT_A1)(a0)
+       REG_L   a2, (SUSPEND_CONTEXT_REGS + PT_A2)(a0)
+       REG_L   a3, (SUSPEND_CONTEXT_REGS + PT_A3)(a0)
+       REG_L   a4, (SUSPEND_CONTEXT_REGS + PT_A4)(a0)
+       REG_L   a5, (SUSPEND_CONTEXT_REGS + PT_A5)(a0)
+       REG_L   a6, (SUSPEND_CONTEXT_REGS + PT_A6)(a0)
+       REG_L   a7, (SUSPEND_CONTEXT_REGS + PT_A7)(a0)
+       REG_L   s2, (SUSPEND_CONTEXT_REGS + PT_S2)(a0)
+       REG_L   s3, (SUSPEND_CONTEXT_REGS + PT_S3)(a0)
+       REG_L   s4, (SUSPEND_CONTEXT_REGS + PT_S4)(a0)
+       REG_L   s5, (SUSPEND_CONTEXT_REGS + PT_S5)(a0)
+       REG_L   s6, (SUSPEND_CONTEXT_REGS + PT_S6)(a0)
+       REG_L   s7, (SUSPEND_CONTEXT_REGS + PT_S7)(a0)
+       REG_L   s8, (SUSPEND_CONTEXT_REGS + PT_S8)(a0)
+       REG_L   s9, (SUSPEND_CONTEXT_REGS + PT_S9)(a0)
+       REG_L   s10, (SUSPEND_CONTEXT_REGS + PT_S10)(a0)
+       REG_L   s11, (SUSPEND_CONTEXT_REGS + PT_S11)(a0)
+
+       /* Return zero value */
+       add     a0, zero, zero
+
+       ret
+END(__hibernate_cpu_resume)
+
+/* a0: satp of saved page tables
+ * a1: satp of temporary page tables
+ * a2: cpu_resume
+ * a3: saved cpu_context
+ */
+ENTRY(restore_image)
+       mv      s0, a0
+       mv      s1, a1
+       mv      s2, a2
+       mv      s3, a3
+       ld      s4, restore_pblist
+       ld      a1, relocated_restore_code
+
+       jalr    a1
+END(restore_image)
+
+ENTRY(core_restore_code)
+       /* switch to temp page table */
+       csrw satp, s1
+       sfence.vma
+       beqz    s4, done
+loop:
+       ld      a1, HIBERN_PBE_ADDR(s4)
+       ld      a0, HIBERN_PBE_ORIG(s4)
+
+       lui     a4, 0x1
+       add     a4, a4, a0
+copy:  ld      a5, 0(a1)
+       addi    a0, a0, 8
+       addi    a1, a1, 8
+       sd      a5, -8(a0)
+       bne     a4, a0, copy
+
+       ld      s4, HIBERN_PBE_NEXT(s4)
+       bnez    s4, loop
+done:
+       mv      a0, s3
+       jalr    s2
+END(core_restore_code)
diff --git a/arch/riscv/kernel/hibernate.c b/arch/riscv/kernel/hibernate.c
new file mode 100644 (file)
index 0000000..7a27d94
--- /dev/null
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*:
+ * Hibernate support specific for RISCV
+ *
+ * Copyright (C) 2022 Shanghai StarFive Technology Co., Ltd.
+ *
+ * Author: Jee Heng Sia <jeeheng.sia@starfivetech.com>
+ *
+ */
+
+#include <linux/cpu.h>
+#include <linux/memblock.h>
+#include <linux/pm.h>
+#include <linux/sched.h>
+#include <linux/suspend.h>
+#include <linux/utsname.h>
+
+#include <asm/barrier.h>
+#include <asm/cacheflush.h>
+#include <asm/irqflags.h>
+#include <asm/kexec.h>
+#include <asm/mmu_context.h>
+#include <asm/page.h>
+#include <asm/sections.h>
+#include <asm/set_memory.h>
+#include <asm/smp.h>
+#include <asm/suspend.h>
+
+#include <soc/sifive/sifive_l2_cache.h>
+
+/*
+ * The logical cpu number we should resume on, initialised to a non-cpu
+ * number.
+ */
+static int sleep_cpu = -EINVAL;
+
+/* CPU context to be saved */
+struct suspend_context *hibernate_cpu_context;
+
+unsigned long relocated_restore_code;
+
+/* Pointer to the temporary resume page tables */
+pgd_t *resume_pg_dir;
+
+/*
+ * Values that may not change over hibernate/resume. We put the build number
+ * and date in here so that we guarantee not to resume with a different
+ * kernel.
+ */
+struct arch_hibernate_hdr_invariants {
+       char            uts_version[__NEW_UTS_LEN + 1];
+};
+
+/* These values need to be known across a hibernate/restore. */
+static struct arch_hibernate_hdr {
+       struct arch_hibernate_hdr_invariants invariants;
+       unsigned long   hartid;
+       unsigned long   saved_satp;
+       unsigned long   restore_cpu_addr;
+} resume_hdr;
+
+static inline void arch_hdr_invariants(struct arch_hibernate_hdr_invariants *i)
+{
+       memset(i, 0, sizeof(*i));
+       memcpy(i->uts_version, init_utsname()->version, sizeof(i->uts_version));
+}
+
+int pfn_is_nosave(unsigned long pfn)
+{
+       unsigned long nosave_begin_pfn = sym_to_pfn(&__nosave_begin);
+       unsigned long nosave_end_pfn = sym_to_pfn(&__nosave_end - 1);
+
+       return ((pfn >= nosave_begin_pfn) && (pfn <= nosave_end_pfn));
+}
+
+void notrace save_processor_state(void)
+{
+       WARN_ON(num_online_cpus() != 1);
+}
+
+void notrace restore_processor_state(void)
+{
+}
+
+
+int arch_hibernation_header_save(void *addr, unsigned int max_size)
+{
+       struct arch_hibernate_hdr *hdr = addr;
+
+       if (max_size < sizeof(*hdr))
+               return -EOVERFLOW;
+
+       arch_hdr_invariants(&hdr->invariants);
+
+       hdr->hartid = cpuid_to_hartid_map(sleep_cpu);
+       hdr->saved_satp = csr_read(CSR_SATP);
+       hdr->restore_cpu_addr = (unsigned long) __hibernate_cpu_resume;
+
+       return 0;
+}
+EXPORT_SYMBOL(arch_hibernation_header_save);
+
+int arch_hibernation_header_restore(void *addr)
+{
+       struct arch_hibernate_hdr_invariants invariants;
+       struct arch_hibernate_hdr *hdr = addr;
+       int ret = 0;
+
+       arch_hdr_invariants(&invariants);
+
+       if (memcmp(&hdr->invariants, &invariants, sizeof(invariants))) {
+               pr_crit("Hibernate image not generated by this kernel!\n");
+               return -EINVAL;
+       }
+
+       sleep_cpu = riscv_hartid_to_cpuid(hdr->hartid);
+       if (sleep_cpu < 0) {
+               pr_crit("Hibernated on a CPU not known to this kernel!\n");
+               sleep_cpu = -EINVAL;
+               return -EINVAL;
+       }
+#ifdef CONFIG_SMP
+       ret = bringup_hibernate_cpu(sleep_cpu);
+       if (ret) {
+               sleep_cpu = -EINVAL;
+               return ret;
+       }
+#endif
+       resume_hdr = *hdr;
+
+       return ret;
+}
+EXPORT_SYMBOL(arch_hibernation_header_restore);
+
+int swsusp_arch_suspend(void)
+{
+       int ret = 0;
+
+       if (__cpu_suspend_enter(hibernate_cpu_context)) {
+               sleep_cpu = smp_processor_id();
+               suspend_save_csrs(hibernate_cpu_context);
+               ret = swsusp_save();
+       } else {
+               suspend_restore_csrs(hibernate_cpu_context);
+               flush_tlb_all();
+
+               /* Invalidated Icache */
+               flush_icache_all();
+
+               /*
+                * Tell the hibernation core that we've just restored
+                * the memory
+                */
+               in_suspend = 0;
+               sleep_cpu = -EINVAL;
+       }
+
+       return ret;
+}
+
+void temp_page_mapping(pgd_t *pgdp, unsigned long va, pgprot_t prot)
+{
+       uintptr_t pgd_idx = pgd_index(va);
+       phys_addr_t pmd_phys;
+       phys_addr_t pte_phys;
+       uintptr_t pmd_idx;
+       uintptr_t pte_idx;
+       pmd_t *pmdp;
+       pte_t *ptep;
+
+       if (pgd_val(pgdp[pgd_idx]) == 0) {
+               pmdp = (pmd_t *)get_safe_page(GFP_ATOMIC);
+               if (!pmdp)
+                       return;
+
+               memset(pmdp, 0, PAGE_SIZE);
+               pmd_phys = __pa(pmdp);
+               pgdp[pgd_idx] = pfn_pgd(PFN_DOWN(pmd_phys), PAGE_TABLE);
+       } else {
+               pmd_phys = PFN_PHYS(_pgd_pfn(pgdp[pgd_idx]));
+               pmdp = (pmd_t *) __va(pmd_phys);
+       }
+
+       pmd_idx = pmd_index(va);
+
+       if (pmd_none(pmdp[pmd_idx])) {
+               ptep = (pte_t *)get_safe_page(GFP_ATOMIC);
+               if (!ptep)
+                       return;
+
+               memset(ptep, 0, PAGE_SIZE);
+               pte_phys = __pa(ptep);
+               pmdp[pmd_idx] = pfn_pmd(PFN_DOWN(pte_phys), PAGE_TABLE);
+       } else {
+               pte_phys = PFN_PHYS(_pmd_pfn(pmdp[pmd_idx]));
+               ptep = (pte_t *) __va(pte_phys);
+       }
+
+       pte_idx = pte_index(va);
+
+       ptep[pte_idx] = pfn_pte(PFN_DOWN(__pa(va)), prot);
+}
+
+unsigned long relocate_restore_code(void)
+{
+       void *page = (void *)get_safe_page(GFP_ATOMIC);
+
+       if (!page)
+               return -ENOMEM;
+
+       memcpy(page, core_restore_code, PAGE_SIZE);
+
+       /* Make the page containing the relocated code executable */
+       set_memory_x((unsigned long)page, 1);
+
+       temp_page_mapping(resume_pg_dir, (unsigned long)page, PAGE_KERNEL_READ_EXEC);
+
+       return (unsigned long)page;
+}
+
+int swsusp_arch_resume(void)
+{
+       unsigned long addr;
+
+       resume_pg_dir = (pgd_t *)get_safe_page(GFP_ATOMIC);
+       if (!resume_pg_dir)
+               return -ENOMEM;
+
+       for (addr = PAGE_OFFSET; addr <= (unsigned long)__va(end_linear_map); addr += PAGE_SIZE)
+               temp_page_mapping(resume_pg_dir, addr, PAGE_KERNEL);
+
+       relocated_restore_code = relocate_restore_code();
+       temp_page_mapping(resume_pg_dir, (unsigned long)resume_hdr.restore_cpu_addr,
+                               PAGE_KERNEL_READ_EXEC);
+
+       restore_image(resume_hdr.saved_satp, (PFN_DOWN(__pa(resume_pg_dir)) | SATP_MODE),
+                       resume_hdr.restore_cpu_addr, (unsigned long)hibernate_cpu_context);
+
+       return 0;
+}
+
+#ifdef CONFIG_SMP
+int hibernate_resume_nonboot_cpu_disable(void)
+{
+       if (sleep_cpu < 0) {
+               pr_err("Failing to resume from hibernate on an unknown CPU.\n");
+               return -ENODEV;
+       }
+
+       return freeze_secondary_cpus(sleep_cpu);
+}
+#endif
+
+static int __init riscv_hibernate__init(void)
+{
+       hibernate_cpu_context = kcalloc(1, sizeof(struct suspend_context), GFP_KERNEL);
+
+       if (WARN_ON(!hibernate_cpu_context))
+               return -ENOMEM;
+
+       return 0;
+}
+
+early_initcall(riscv_hibernate__init);
+
diff --git a/arch/riscv/kernel/perf_event.c b/arch/riscv/kernel/perf_event.c
deleted file mode 100644 (file)
index c835f03..0000000
+++ /dev/null
@@ -1,485 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2008 Thomas Gleixner <tglx@linutronix.de>
- * Copyright (C) 2008-2009 Red Hat, Inc., Ingo Molnar
- * Copyright (C) 2009 Jaswinder Singh Rajput
- * Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter
- * Copyright (C) 2008-2009 Red Hat, Inc., Peter Zijlstra
- * Copyright (C) 2009 Intel Corporation, <markus.t.metzger@intel.com>
- * Copyright (C) 2009 Google, Inc., Stephane Eranian
- * Copyright 2014 Tilera Corporation. All Rights Reserved.
- * Copyright (C) 2018 Andes Technology Corporation
- *
- * Perf_events support for RISC-V platforms.
- *
- * Since the spec. (as of now, Priv-Spec 1.10) does not provide enough
- * functionality for perf event to fully work, this file provides
- * the very basic framework only.
- *
- * For platform portings, please check Documentations/riscv/pmu.txt.
- *
- * The Copyright line includes x86 and tile ones.
- */
-
-#include <linux/kprobes.h>
-#include <linux/kernel.h>
-#include <linux/kdebug.h>
-#include <linux/mutex.h>
-#include <linux/bitmap.h>
-#include <linux/irq.h>
-#include <linux/perf_event.h>
-#include <linux/atomic.h>
-#include <linux/of.h>
-#include <asm/perf_event.h>
-
-static const struct riscv_pmu *riscv_pmu __read_mostly;
-static DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events);
-
-/*
- * Hardware & cache maps and their methods
- */
-
-static const int riscv_hw_event_map[] = {
-       [PERF_COUNT_HW_CPU_CYCLES]              = RISCV_PMU_CYCLE,
-       [PERF_COUNT_HW_INSTRUCTIONS]            = RISCV_PMU_INSTRET,
-       [PERF_COUNT_HW_CACHE_REFERENCES]        = RISCV_OP_UNSUPP,
-       [PERF_COUNT_HW_CACHE_MISSES]            = RISCV_OP_UNSUPP,
-       [PERF_COUNT_HW_BRANCH_INSTRUCTIONS]     = RISCV_OP_UNSUPP,
-       [PERF_COUNT_HW_BRANCH_MISSES]           = RISCV_OP_UNSUPP,
-       [PERF_COUNT_HW_BUS_CYCLES]              = RISCV_OP_UNSUPP,
-};
-
-#define C(x) PERF_COUNT_HW_CACHE_##x
-static const int riscv_cache_event_map[PERF_COUNT_HW_CACHE_MAX]
-[PERF_COUNT_HW_CACHE_OP_MAX]
-[PERF_COUNT_HW_CACHE_RESULT_MAX] = {
-       [C(L1D)] = {
-               [C(OP_READ)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_WRITE)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_PREFETCH)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-       },
-       [C(L1I)] = {
-               [C(OP_READ)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_WRITE)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_PREFETCH)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-       },
-       [C(LL)] = {
-               [C(OP_READ)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_WRITE)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_PREFETCH)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-       },
-       [C(DTLB)] = {
-               [C(OP_READ)] = {
-                       [C(RESULT_ACCESS)] =  RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] =  RISCV_OP_UNSUPP,
-               },
-               [C(OP_WRITE)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_PREFETCH)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-       },
-       [C(ITLB)] = {
-               [C(OP_READ)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_WRITE)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_PREFETCH)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-       },
-       [C(BPU)] = {
-               [C(OP_READ)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_WRITE)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-               [C(OP_PREFETCH)] = {
-                       [C(RESULT_ACCESS)] = RISCV_OP_UNSUPP,
-                       [C(RESULT_MISS)] = RISCV_OP_UNSUPP,
-               },
-       },
-};
-
-static int riscv_map_hw_event(u64 config)
-{
-       if (config >= riscv_pmu->max_events)
-               return -EINVAL;
-
-       return riscv_pmu->hw_events[config];
-}
-
-static int riscv_map_cache_decode(u64 config, unsigned int *type,
-                          unsigned int *op, unsigned int *result)
-{
-       return -ENOENT;
-}
-
-static int riscv_map_cache_event(u64 config)
-{
-       unsigned int type, op, result;
-       int err = -ENOENT;
-               int code;
-
-       err = riscv_map_cache_decode(config, &type, &op, &result);
-       if (!riscv_pmu->cache_events || err)
-               return err;
-
-       if (type >= PERF_COUNT_HW_CACHE_MAX ||
-           op >= PERF_COUNT_HW_CACHE_OP_MAX ||
-           result >= PERF_COUNT_HW_CACHE_RESULT_MAX)
-               return -EINVAL;
-
-       code = (*riscv_pmu->cache_events)[type][op][result];
-       if (code == RISCV_OP_UNSUPP)
-               return -EINVAL;
-
-       return code;
-}
-
-/*
- * Low-level functions: reading/writing counters
- */
-
-static inline u64 read_counter(int idx)
-{
-       u64 val = 0;
-
-       switch (idx) {
-       case RISCV_PMU_CYCLE:
-               val = csr_read(CSR_CYCLE);
-               break;
-       case RISCV_PMU_INSTRET:
-               val = csr_read(CSR_INSTRET);
-               break;
-       default:
-               WARN_ON_ONCE(idx < 0 || idx > RISCV_MAX_COUNTERS);
-               return -EINVAL;
-       }
-
-       return val;
-}
-
-static inline void write_counter(int idx, u64 value)
-{
-       /* currently not supported */
-       WARN_ON_ONCE(1);
-}
-
-/*
- * pmu->read: read and update the counter
- *
- * Other architectures' implementation often have a xxx_perf_event_update
- * routine, which can return counter values when called in the IRQ, but
- * return void when being called by the pmu->read method.
- */
-static void riscv_pmu_read(struct perf_event *event)
-{
-       struct hw_perf_event *hwc = &event->hw;
-       u64 prev_raw_count, new_raw_count;
-       u64 oldval;
-       int idx = hwc->idx;
-       u64 delta;
-
-       do {
-               prev_raw_count = local64_read(&hwc->prev_count);
-               new_raw_count = read_counter(idx);
-
-               oldval = local64_cmpxchg(&hwc->prev_count, prev_raw_count,
-                                        new_raw_count);
-       } while (oldval != prev_raw_count);
-
-       /*
-        * delta is the value to update the counter we maintain in the kernel.
-        */
-       delta = (new_raw_count - prev_raw_count) &
-               ((1ULL << riscv_pmu->counter_width) - 1);
-       local64_add(delta, &event->count);
-       /*
-        * Something like local64_sub(delta, &hwc->period_left) here is
-        * needed if there is an interrupt for perf.
-        */
-}
-
-/*
- * State transition functions:
- *
- * stop()/start() & add()/del()
- */
-
-/*
- * pmu->stop: stop the counter
- */
-static void riscv_pmu_stop(struct perf_event *event, int flags)
-{
-       struct hw_perf_event *hwc = &event->hw;
-
-       WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED);
-       hwc->state |= PERF_HES_STOPPED;
-
-       if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) {
-               riscv_pmu->pmu->read(event);
-               hwc->state |= PERF_HES_UPTODATE;
-       }
-}
-
-/*
- * pmu->start: start the event.
- */
-static void riscv_pmu_start(struct perf_event *event, int flags)
-{
-       struct hw_perf_event *hwc = &event->hw;
-
-       if (WARN_ON_ONCE(!(event->hw.state & PERF_HES_STOPPED)))
-               return;
-
-       if (flags & PERF_EF_RELOAD) {
-               WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE));
-
-               /*
-                * Set the counter to the period to the next interrupt here,
-                * if you have any.
-                */
-       }
-
-       hwc->state = 0;
-       perf_event_update_userpage(event);
-
-       /*
-        * Since we cannot write to counters, this serves as an initialization
-        * to the delta-mechanism in pmu->read(); otherwise, the delta would be
-        * wrong when pmu->read is called for the first time.
-        */
-       local64_set(&hwc->prev_count, read_counter(hwc->idx));
-}
-
-/*
- * pmu->add: add the event to PMU.
- */
-static int riscv_pmu_add(struct perf_event *event, int flags)
-{
-       struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
-       struct hw_perf_event *hwc = &event->hw;
-
-       if (cpuc->n_events == riscv_pmu->num_counters)
-               return -ENOSPC;
-
-       /*
-        * We don't have general conunters, so no binding-event-to-counter
-        * process here.
-        *
-        * Indexing using hwc->config generally not works, since config may
-        * contain extra information, but here the only info we have in
-        * hwc->config is the event index.
-        */
-       hwc->idx = hwc->config;
-       cpuc->events[hwc->idx] = event;
-       cpuc->n_events++;
-
-       hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
-
-       if (flags & PERF_EF_START)
-               riscv_pmu->pmu->start(event, PERF_EF_RELOAD);
-
-       return 0;
-}
-
-/*
- * pmu->del: delete the event from PMU.
- */
-static void riscv_pmu_del(struct perf_event *event, int flags)
-{
-       struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
-       struct hw_perf_event *hwc = &event->hw;
-
-       cpuc->events[hwc->idx] = NULL;
-       cpuc->n_events--;
-       riscv_pmu->pmu->stop(event, PERF_EF_UPDATE);
-       perf_event_update_userpage(event);
-}
-
-/*
- * Interrupt: a skeletion for reference.
- */
-
-static DEFINE_MUTEX(pmc_reserve_mutex);
-
-static irqreturn_t riscv_base_pmu_handle_irq(int irq_num, void *dev)
-{
-       return IRQ_NONE;
-}
-
-static int reserve_pmc_hardware(void)
-{
-       int err = 0;
-
-       mutex_lock(&pmc_reserve_mutex);
-       if (riscv_pmu->irq >= 0 && riscv_pmu->handle_irq) {
-               err = request_irq(riscv_pmu->irq, riscv_pmu->handle_irq,
-                                 IRQF_PERCPU, "riscv-base-perf", NULL);
-       }
-       mutex_unlock(&pmc_reserve_mutex);
-
-       return err;
-}
-
-static void release_pmc_hardware(void)
-{
-       mutex_lock(&pmc_reserve_mutex);
-       if (riscv_pmu->irq >= 0)
-               free_irq(riscv_pmu->irq, NULL);
-       mutex_unlock(&pmc_reserve_mutex);
-}
-
-/*
- * Event Initialization/Finalization
- */
-
-static atomic_t riscv_active_events = ATOMIC_INIT(0);
-
-static void riscv_event_destroy(struct perf_event *event)
-{
-       if (atomic_dec_return(&riscv_active_events) == 0)
-               release_pmc_hardware();
-}
-
-static int riscv_event_init(struct perf_event *event)
-{
-       struct perf_event_attr *attr = &event->attr;
-       struct hw_perf_event *hwc = &event->hw;
-       int err;
-       int code;
-
-       if (atomic_inc_return(&riscv_active_events) == 1) {
-               err = reserve_pmc_hardware();
-
-               if (err) {
-                       pr_warn("PMC hardware not available\n");
-                       atomic_dec(&riscv_active_events);
-                       return -EBUSY;
-               }
-       }
-
-       switch (event->attr.type) {
-       case PERF_TYPE_HARDWARE:
-               code = riscv_pmu->map_hw_event(attr->config);
-               break;
-       case PERF_TYPE_HW_CACHE:
-               code = riscv_pmu->map_cache_event(attr->config);
-               break;
-       case PERF_TYPE_RAW:
-               return -EOPNOTSUPP;
-       default:
-               return -ENOENT;
-       }
-
-       event->destroy = riscv_event_destroy;
-       if (code < 0) {
-               event->destroy(event);
-               return code;
-       }
-
-       /*
-        * idx is set to -1 because the index of a general event should not be
-        * decided until binding to some counter in pmu->add().
-        *
-        * But since we don't have such support, later in pmu->add(), we just
-        * use hwc->config as the index instead.
-        */
-       hwc->config = code;
-       hwc->idx = -1;
-
-       return 0;
-}
-
-/*
- * Initialization
- */
-
-static struct pmu min_pmu = {
-       .name           = "riscv-base",
-       .event_init     = riscv_event_init,
-       .add            = riscv_pmu_add,
-       .del            = riscv_pmu_del,
-       .start          = riscv_pmu_start,
-       .stop           = riscv_pmu_stop,
-       .read           = riscv_pmu_read,
-};
-
-static const struct riscv_pmu riscv_base_pmu = {
-       .pmu = &min_pmu,
-       .max_events = ARRAY_SIZE(riscv_hw_event_map),
-       .map_hw_event = riscv_map_hw_event,
-       .hw_events = riscv_hw_event_map,
-       .map_cache_event = riscv_map_cache_event,
-       .cache_events = &riscv_cache_event_map,
-       .counter_width = 63,
-       .num_counters = RISCV_BASE_COUNTERS + 0,
-       .handle_irq = &riscv_base_pmu_handle_irq,
-
-       /* This means this PMU has no IRQ. */
-       .irq = -1,
-};
-
-static const struct of_device_id riscv_pmu_of_ids[] = {
-       {.compatible = "riscv,base-pmu",        .data = &riscv_base_pmu},
-       { /* sentinel value */ }
-};
-
-static int __init init_hw_perf_events(void)
-{
-       struct device_node *node = of_find_node_by_type(NULL, "pmu");
-       const struct of_device_id *of_id;
-
-       riscv_pmu = &riscv_base_pmu;
-
-       if (node) {
-               of_id = of_match_node(riscv_pmu_of_ids, node);
-
-               if (of_id)
-                       riscv_pmu = of_id->data;
-               of_node_put(node);
-       }
-
-       perf_pmu_register(riscv_pmu->pmu, "cpu", PERF_TYPE_RAW);
-       return 0;
-}
-arch_initcall(init_hw_perf_events);
index 9a84f0c..eac3038 100644 (file)
@@ -578,16 +578,19 @@ long sbi_get_mvendorid(void)
 {
        return __sbi_base_ecall(SBI_EXT_BASE_GET_MVENDORID);
 }
+EXPORT_SYMBOL(sbi_get_mvendorid);
 
 long sbi_get_marchid(void)
 {
        return __sbi_base_ecall(SBI_EXT_BASE_GET_MARCHID);
 }
+EXPORT_SYMBOL(sbi_get_marchid);
 
 long sbi_get_mimpid(void)
 {
        return __sbi_base_ecall(SBI_EXT_BASE_GET_MIMPID);
 }
+EXPORT_SYMBOL(sbi_get_mimpid);
 
 static void sbi_send_cpumask_ipi(const struct cpumask *target)
 {
index 9ba24fb..3c89b8e 100644 (file)
@@ -8,7 +8,7 @@
 #include <asm/csr.h>
 #include <asm/suspend.h>
 
-static void suspend_save_csrs(struct suspend_context *context)
+void suspend_save_csrs(struct suspend_context *context)
 {
        context->scratch = csr_read(CSR_SCRATCH);
        context->tvec = csr_read(CSR_TVEC);
@@ -29,7 +29,7 @@ static void suspend_save_csrs(struct suspend_context *context)
 #endif
 }
 
-static void suspend_restore_csrs(struct suspend_context *context)
+void suspend_restore_csrs(struct suspend_context *context)
 {
        csr_write(CSR_SCRATCH, context->scratch);
        csr_write(CSR_TVEC, context->tvec);
index c0cddf0..ce07555 100644 (file)
@@ -48,6 +48,8 @@ unsigned long empty_zero_page[PAGE_SIZE / sizeof(unsigned long)]
                                                        __page_aligned_bss;
 EXPORT_SYMBOL(empty_zero_page);
 
+phys_addr_t end_linear_map;
+
 extern char _start[];
 #define DTB_EARLY_BASE_VA      PGDIR_SIZE
 void *_dtb_early_va __initdata;
@@ -726,7 +728,7 @@ static void __init setup_vm_final(void)
                        start = __pa(PAGE_OFFSET);
                if (end >= __pa(PAGE_OFFSET) + memory_limit)
                        end = __pa(PAGE_OFFSET) + memory_limit;
-
+               end_linear_map = end;
                map_size = best_map_size(start, end - start);
                for (pa = start; pa < end; pa += map_size) {
                        va = (uintptr_t)__va(pa);
index 5e49e4b..58c61df 100644 (file)
@@ -229,6 +229,8 @@ bool kernel_page_present(struct page *page)
        pmd = pmd_offset(pud, addr);
        if (!pmd_present(*pmd))
                return false;
+       if (pmd_leaf(*pmd))
+               return true;
 
        pte = pte_offset_kernel(pmd, addr);
        return pte_present(*pte);
old mode 100755 (executable)
new mode 100644 (file)
index 62399f8..46003d1
@@ -44,12 +44,23 @@ struct starfive_timer __initdata jh7110_starfive_timer = {
                        TIMER_BASE(6), TIMER_BASE(7)},
 };
 
+struct starfive_timer_misc_count sfcmisc = {
+       .clk_count = 0,
+       .flg_init_clk = false,
+};
+
 static inline struct starfive_clkevt *
 to_starfive_clkevt(struct clock_event_device *evt)
 {
        return container_of(evt, struct starfive_clkevt, evt);
 }
 
+static inline struct starfive_clkevt *
+to_starfive_clksrc(struct clocksource *cs)
+{
+       return container_of(cs, struct starfive_clkevt, cs);
+}
+
 static inline void timer_set_mod(struct starfive_clkevt *clkevt, int mod)
 {
        writel(mod, clkevt->ctrl);
@@ -96,6 +107,11 @@ static inline u32 timer_get_val(struct starfive_clkevt *clkevt)
        return readl(clkevt->value);
 }
 
+static inline u32 timer_get_load_val(struct starfive_clkevt *clkevt)
+{
+       return readl(clkevt->load);
+}
+
 /*
  * Write RELOAD register to reload preset value to counter.
  * (Write 0 and write 1 are both ok)
@@ -120,39 +136,71 @@ static void timer_shutdown(struct starfive_clkevt *clkevt)
 {
        timer_int_disable(clkevt);
        timer_disable(clkevt);
-       timer_int_clear(clkevt);
 }
 
-static void starfive_timer_suspend(struct clock_event_device *evt)
+#ifdef CONFIG_PM_SLEEP
+
+static void starfive_timer_suspend(struct clocksource *cs)
 {
        struct starfive_clkevt *clkevt;
+       struct clk *pclk;
 
-       clkevt = to_starfive_clkevt(evt);
-
-       clkevt->reload_val = timer_get_val(clkevt);
+       clkevt = to_starfive_clksrc(cs);
 
+       clkevt->reload_val = timer_get_load_val(clkevt);
        timer_disable(clkevt);
        timer_int_disable(clkevt);
-       timer_int_clear(clkevt);
+
+       clkevt->misc->clk_count--;
+
+       if (clkevt->misc->clk_count < 1)
+               pclk = of_clk_get_by_name(clkevt->device_node, "apb_clk");
+
+       if (!clkevt->misc->flg_init_clk) {
+               const char *name = NULL;
+
+               of_property_read_string_index(clkevt->device_node,
+                               "clock-names", clkevt->index, &name);
+               clkevt->clk = of_clk_get_by_name(clkevt->device_node, name);
+               clk_prepare_enable(clkevt->clk);
+               clk_disable_unprepare(clkevt->clk);
+
+               if (clkevt->misc->clk_count < 1) {
+                       clk_prepare_enable(pclk);
+                       clk_disable_unprepare(pclk);
+               }
+       } else {
+               clk_disable_unprepare(clkevt->clk);
+
+               if (clkevt->misc->clk_count < 1)
+                       clk_disable_unprepare(pclk);
+       }
 }
 
-static void starfive_timer_resume(struct clock_event_device *evt)
+static void starfive_timer_resume(struct clocksource *cs)
 {
        struct starfive_clkevt *clkevt;
 
-       clkevt = to_starfive_clkevt(evt);
+       clkevt = to_starfive_clksrc(cs);
+
+       clkevt->misc->flg_init_clk = true;
+
+       if (clkevt->misc->clk_count < 1) {
+               struct clk *pclk;
+
+               pclk = of_clk_get_by_name(clkevt->device_node, "apb_clk");
+               clk_prepare_enable(pclk);
+       }
+       clk_prepare_enable(clkevt->clk);
+       clkevt->misc->clk_count++;
+
        timer_set_val(clkevt, clkevt->reload_val);
        timer_set_reload(clkevt);
        timer_int_enable(clkevt);
        timer_enable(clkevt);
 }
 
-static int starfive_timer_tick_resume(struct clock_event_device *evt)
-{
-       starfive_timer_resume(evt);
-
-       return 0;
-}
+#endif /*CONIFG PM SLEEP*/
 
 static int starfive_timer_shutdown(struct clock_event_device *evt)
 {
@@ -191,9 +239,26 @@ starfive_get_clock_rate(struct starfive_clkevt *clkevt, struct device_node *np)
        return -ENOENT;
 }
 
+static u64 starfive_clocksource_mmio_readl_down(struct clocksource *c)
+{
+       return ~(u64)readl_relaxed(to_starfive_clksrc(c)->value) & c->mask;
+}
+
 static int starfive_clocksource_init(struct starfive_clkevt *clkevt,
                                const char *name, struct device_node *np)
 {
+
+       if (VALID_BITS > 64 || VALID_BITS < 16)
+               return -EINVAL;
+
+       clkevt->cs.name = name;
+       clkevt->cs.rating = CLOCK_SOURCE_RATE;
+       clkevt->cs.read = starfive_clocksource_mmio_readl_down;
+       clkevt->cs.mask = CLOCKSOURCE_MASK(VALID_BITS);
+       clkevt->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
+       clkevt->cs.suspend = starfive_timer_suspend;
+       clkevt->cs.resume = starfive_timer_resume;
+
        timer_set_mod(clkevt, MOD_CONTIN);
        timer_set_val(clkevt, MAX_TICKS);  /* val = rate --> 1s */
        timer_int_disable(clkevt);
@@ -201,9 +266,7 @@ static int starfive_clocksource_init(struct starfive_clkevt *clkevt,
        timer_int_enable(clkevt);
        timer_enable(clkevt);
 
-       clocksource_mmio_init(clkevt->value, name, clkevt->rate,
-                       CLOCK_SOURCE_RATE, VALID_BITS,
-                       clocksource_mmio_readl_down);
+       clocksource_register_hz(&clkevt->cs, clkevt->rate);
 
        return 0;
 }
@@ -217,7 +280,6 @@ static irqreturn_t starfive_timer_interrupt(int irq, void *priv)
        struct starfive_clkevt *clkevt = to_starfive_clkevt(evt);
 
        timer_int_clear(clkevt);
-
        if (evt->event_handler)
                evt->event_handler(evt);
 
@@ -282,10 +344,7 @@ static void starfive_set_clockevent(struct clock_event_device *evt)
        evt->set_state_periodic = starfive_timer_set_periodic;
        evt->set_state_oneshot  = starfive_timer_set_oneshot;
        evt->set_state_oneshot_stopped = starfive_timer_shutdown;
-       evt->tick_resume        = starfive_timer_tick_resume;
        evt->set_next_event     = starfive_timer_set_next_event;
-       evt->suspend            = starfive_timer_suspend;
-       evt->resume             = starfive_timer_resume;
        evt->rating             = CLOCKEVENT_RATING;
 }
 
@@ -342,7 +401,7 @@ static int __init do_starfive_timer_of_init(struct device_node *np,
        struct clk *pclk;
        struct reset_control *prst;
        struct reset_control *rst;
-       struct starfive_clkevt *clkevt;
+       struct starfive_clkevt *clkevt[4];
        void __iomem *base;
 
        base = of_iomap(np, 0);
@@ -378,25 +437,32 @@ static int __init do_starfive_timer_of_init(struct device_node *np,
                if (strncmp(name, "timer", strlen("timer")))
                        continue;
 
-               clkevt = kzalloc(sizeof(*clkevt), GFP_KERNEL);
-               if (!clkevt) {
+               clkevt[index] = kzalloc(sizeof(*clkevt[index]), GFP_KERNEL);
+               if (!clkevt[index]) {
                        ret = -ENOMEM;
                        goto clkevt_err;
                }
+               clkevt[index]->device_node = np;
+               clkevt[index]->index = index;
+               clkevt[index]->misc = &sfcmisc;
 
-               starfive_clkevt_init(timer, clkevt, base, index);
+               starfive_clkevt_init(timer, clkevt[index], base, index);
 
                /* Ensure timers are disabled */
-               timer_disable(clkevt);
+               timer_disable(clkevt[index]);
 
                clk = of_clk_get_by_name(np, name);
                if (!IS_ERR(clk)) {
-                       clkevt->clk = clk;
-                       if (clk_prepare_enable(clk))
+                       clkevt[index]->clk = clk;
+
+                       if (clk_prepare_enable(clk)) {
                                pr_warn("clk for %pOFn is present,"
                                        "but could not be activated\n", np);
+                       }
                }
 
+               clkevt[index]->misc->clk_count++;
+
                rst = of_reset_control_get(np, name);
                if (!IS_ERR(rst)) {
                        reset_control_assert(rst);
@@ -409,17 +475,17 @@ static int __init do_starfive_timer_of_init(struct device_node *np,
                        goto irq_err;
                }
 
-               snprintf(clkevt->name, sizeof(clkevt->name), "%s.ch%d",
+               snprintf(clkevt[index]->name, sizeof(clkevt[index]->name), "%s.ch%d",
                                        np->full_name, index);
 
-               ret = starfive_clockevents_register(clkevt, irq, np, clkevt->name);
+               ret = starfive_clockevents_register(clkevt[index], irq, np, clkevt[index]->name);
                if (ret) {
-                       pr_err("%s: init clockevents failed.\n", clkevt->name);
+                       pr_err("%s: init clockevents failed.\n", clkevt[index]->name);
                        goto register_err;
                }
-               clkevt->irq = irq;
+               clkevt[index]->irq = irq;
 
-               ret = starfive_clocksource_init(clkevt, clkevt->name, np);
+               ret = starfive_clocksource_init(clkevt[index], clkevt[index]->name, np);
                if (ret)
                        goto init_err;
        }
@@ -430,17 +496,17 @@ static int __init do_starfive_timer_of_init(struct device_node *np,
 
 init_err:
 register_err:
-       free_irq(clkevt->irq, &clkevt->evt);
+       free_irq(clkevt[index]->irq, &clkevt[index]->evt);
 irq_err:
        if (!rst) {
                reset_control_assert(rst);
                reset_control_put(rst);
        }
-       if (!clkevt->clk) {
-               clk_disable_unprepare(clkevt->clk);
-               clk_put(clkevt->clk);
+       if (!clkevt[index]->clk) {
+               clk_disable_unprepare(clkevt[index]->clk);
+               clk_put(clkevt[index]->clk);
        }
-       kfree(clkevt);
+       kfree(clkevt[index]);
 clkevt_err:
 count_err:
        if (!IS_ERR(pclk)) {
old mode 100755 (executable)
new mode 100644 (file)
index 7315a0f..f2ccc07
@@ -81,10 +81,19 @@ struct starfive_timer {
        u32 timer_base[NR_TIMERS];
 };
 
+struct starfive_timer_misc_count {
+       u8 clk_count;
+       bool flg_init_clk;
+};
+
 struct starfive_clkevt {
        struct clock_event_device evt;
+       struct clocksource cs;
+       struct device_node *device_node;
+       struct starfive_timer_misc_count *misc;
        struct clk *clk;
        char name[20];
+       int index;
        int irq;
        u64 periodic;
        u64 rate;
old mode 100755 (executable)
new mode 100644 (file)
index 259065d..92ec59b
@@ -17,6 +17,7 @@
 #include <linux/of_irq.h>
 #include <linux/platform_device.h>
 #include <linux/spinlock.h>
+#include <linux/syscore_ops.h>
 #include <asm/smp.h>
 
 /*
@@ -64,6 +65,8 @@ struct plic_priv {
        struct cpumask lmask;
        struct irq_domain *irqdomain;
        void __iomem *regs;
+       unsigned int nr_irqs;
+       u32 *priority_reg;
 };
 
 struct plic_handler {
@@ -76,10 +79,13 @@ struct plic_handler {
        raw_spinlock_t          enable_lock;
        void __iomem            *enable_base;
        struct plic_priv        *priv;
+       /* To record interrupts that are enabled before suspend. */
+       u32 enable_reg[MAX_DEVICES / 32];
 };
 static int plic_parent_irq __ro_after_init;
 static bool plic_cpuhp_setup_done __ro_after_init;
 static DEFINE_PER_CPU(struct plic_handler, plic_handlers);
+static struct plic_priv *priv_data;
 
 static inline void plic_toggle(struct plic_handler *handler,
                                int hwirq, int enable)
@@ -182,6 +188,78 @@ static struct irq_chip plic_chip = {
 #endif
 };
 
+static void plic_irq_resume(void)
+{
+       unsigned int i, cpu;
+       u32 __iomem *reg;
+
+       for (i = 0; i < priv_data->nr_irqs; i++)
+               writel(priv_data->priority_reg[i],
+                               priv_data->regs + PRIORITY_BASE + i * PRIORITY_PER_ID);
+
+       for_each_cpu(cpu, cpu_present_mask) {
+               struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);
+
+               if (!handler->present)
+                       continue;
+
+               for (i = 0; i < DIV_ROUND_UP(priv_data->nr_irqs, 32); i++) {
+                       reg = handler->enable_base + i * sizeof(u32);
+                       raw_spin_lock(&handler->enable_lock);
+                       writel(handler->enable_reg[i], reg);
+                       raw_spin_unlock(&handler->enable_lock);
+               }
+       }
+}
+
+static int plic_irq_suspend(void)
+{
+       unsigned int i, cpu;
+       u32 __iomem *reg;
+
+       for (i = 0; i < priv_data->nr_irqs; i++)
+               priv_data->priority_reg[i] =
+                       readl(priv_data->regs + PRIORITY_BASE + i * PRIORITY_PER_ID);
+
+       for_each_cpu(cpu, cpu_present_mask) {
+               struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);
+
+               if (!handler->present)
+                       continue;
+
+               for (i = 0; i < DIV_ROUND_UP(priv_data->nr_irqs, 32); i++) {
+                       reg = handler->enable_base + i * sizeof(u32);
+                       raw_spin_lock(&handler->enable_lock);
+                       handler->enable_reg[i] = readl(reg);
+                       raw_spin_unlock(&handler->enable_lock);
+               }
+       }
+
+       return 0;
+}
+
+static struct syscore_ops plic_irq_syscore_ops = {
+       .suspend        = plic_irq_suspend,
+       .resume         = plic_irq_resume,
+};
+
+static void plic_irq_pm_init(void)
+{
+       unsigned int cpu;
+
+       for_each_cpu(cpu, cpu_present_mask) {
+               struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);
+
+               if (!handler->present)
+                       continue;
+
+               memset(&handler->enable_reg[0], 0,
+                       sizeof(handler->enable_reg));
+       }
+
+       register_syscore_ops(&plic_irq_syscore_ops);
+}
+
 static int plic_irqdomain_map(struct irq_domain *d, unsigned int irq,
                              irq_hw_number_t hwirq)
 {
@@ -295,20 +373,27 @@ static int __init plic_init(struct device_node *node,
                goto out_free_priv;
        }
 
+       priv_data = priv;
+
        error = -EINVAL;
        of_property_read_u32(node, "riscv,ndev", &nr_irqs);
        if (WARN_ON(!nr_irqs))
                goto out_iounmap;
+       priv->nr_irqs = nr_irqs;
+
+       priv->priority_reg = kcalloc(nr_irqs, sizeof(u32), GFP_KERNEL);
+       if (!priv->priority_reg)
+               goto out_free_priority_reg;
 
        nr_contexts = of_irq_count(node);
        if (WARN_ON(!nr_contexts))
-               goto out_iounmap;
+               goto out_free_priority_reg;
 
        error = -ENOMEM;
        priv->irqdomain = irq_domain_add_linear(node, nr_irqs + 1,
                        &plic_irqdomain_ops, priv);
        if (WARN_ON(!priv->irqdomain))
-               goto out_iounmap;
+               goto out_free_priority_reg;
 
        for (i = 0; i < nr_contexts; i++) {
                struct of_phandle_args parent;
@@ -385,10 +470,14 @@ done:
                plic_cpuhp_setup_done = true;
        }
 
+       plic_irq_pm_init();
+
        pr_info("%pOFP: mapped %d interrupts with %d handlers for"
                " %d contexts.\n", node, nr_irqs, nr_handlers, nr_contexts);
        return 0;
 
+out_free_priority_reg:
+       kfree(priv->priority_reg);
 out_iounmap:
        iounmap(priv->regs);
 out_free_priv:
index 0212b43..5fc9d16 100644 (file)
@@ -959,7 +959,8 @@ static int canfd_driver_open(struct net_device *ndev)
 
        ret = pm_runtime_get_sync(priv->dev);
        if (ret < 0) {
-               dev_err(priv->dev, " %s: pm_runtime_get failed\n", __func__);
+               netdev_err(ndev, "%s: pm_runtime_get failed(%d)\n",
+                          __func__, ret);
                goto err;
        }
 
@@ -1172,6 +1173,46 @@ static int canfd_driver_remove(struct platform_device *pdev)
        return 0;
 }
 
+#ifdef CONFIG_PM_SLEEP
+static int __maybe_unused canfd_suspend(struct device *dev)
+{
+       struct net_device *ndev = dev_get_drvdata(dev);
+
+       if (netif_running(ndev)) {
+               netif_stop_queue(ndev);
+               netif_device_detach(ndev);
+               canfd_driver_stop(ndev);
+       }
+
+       return pm_runtime_force_suspend(dev);
+}
+
+static int __maybe_unused canfd_resume(struct device *dev)
+{
+       struct net_device *ndev = dev_get_drvdata(dev);
+       int ret;
+
+       ret = pm_runtime_force_resume(dev);
+       if (ret) {
+               dev_err(dev, "pm_runtime_force_resume failed on resume\n");
+               return ret;
+       }
+
+       if (netif_running(ndev)) {
+               ret = canfd_chip_start(ndev);
+               if (ret) {
+                       dev_err(dev, "canfd_chip_start failed on resume\n");
+                       return ret;
+               }
+
+               netif_device_attach(ndev);
+               netif_start_queue(ndev);
+       }
+
+       return 0;
+}
+#endif
+
 #ifdef CONFIG_PM
 static int canfd_runtime_suspend(struct device *dev)
 {
@@ -1205,8 +1246,7 @@ static int canfd_runtime_resume(struct device *dev)
 #endif
 
 static const struct dev_pm_ops canfd_pm_ops = {
-       SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
-                               pm_runtime_force_resume)
+       SET_SYSTEM_SLEEP_PM_OPS(canfd_suspend, canfd_resume)
        SET_RUNTIME_PM_OPS(canfd_runtime_suspend,
                           canfd_runtime_resume, NULL)
 };
index 77522e5..b2b78bd 100644 (file)
@@ -56,6 +56,36 @@ config ARM_PMU
          Say y if you want to use CPU performance monitors on ARM-based
          systems.
 
+config RISCV_PMU
+       depends on RISCV
+       bool "RISC-V PMU framework"
+       default y
+       help
+         Say y if you want to use CPU performance monitors on RISCV-based
+         systems. This provides the core PMU framework that abstracts common
+         PMU functionalities in a core library so that different PMU drivers
+         can reuse it.
+
+config RISCV_PMU_LEGACY
+       depends on RISCV_PMU
+       bool "RISC-V legacy PMU implementation"
+       default y
+       help
+         Say y if you want to use the legacy CPU performance monitor
+         implementation on RISC-V based systems. This only allows counting
+         of cycle/instruction counter and doesn't support counter overflow,
+         or programmable counters. It will be removed in future.
+
+config RISCV_PMU_SBI
+       depends on RISCV_PMU && RISCV_SBI
+       bool "RISC-V PMU based on SBI PMU extension"
+       default y
+       help
+         Say y if you want to use the CPU performance monitor
+         using SBI PMU extension on RISC-V based systems. This option provides
+         full perf feature support i.e. counter overflow, privilege mode
+         filtering, counter configuration.
+
 config ARM_PMU_ACPI
        depends on ARM_PMU && ACPI
        def_bool y
index 5260b11..884ed76 100644 (file)
@@ -10,6 +10,9 @@ obj-$(CONFIG_FSL_IMX8_DDR_PMU) += fsl_imx8_ddr_perf.o
 obj-$(CONFIG_HISI_PMU) += hisilicon/
 obj-$(CONFIG_QCOM_L2_PMU)      += qcom_l2_pmu.o
 obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o
+obj-$(CONFIG_RISCV_PMU) += riscv_pmu.o
+obj-$(CONFIG_RISCV_PMU_LEGACY) += riscv_pmu_legacy.o
+obj-$(CONFIG_RISCV_PMU_SBI) += riscv_pmu_sbi.o
 obj-$(CONFIG_THUNDERX2_PMU) += thunderx2_pmu.o
 obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o
 obj-$(CONFIG_ARM_SPE_PMU) += arm_spe_pmu.o
diff --git a/drivers/perf/riscv_pmu.c b/drivers/perf/riscv_pmu.c
new file mode 100644 (file)
index 0000000..d1aa4e0
--- /dev/null
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RISC-V performance counter support.
+ *
+ * Copyright (C) 2021 Western Digital Corporation or its affiliates.
+ *
+ * This implementation is based on old RISC-V perf and ARM perf event code
+ * which are in turn based on sparc64 and x86 code.
+ */
+
+#include <linux/cpumask.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/perf/riscv_pmu.h>
+#include <linux/printk.h>
+#include <linux/smp.h>
+
+#include <asm/sbi.h>
+
+PMU_FORMAT_ATTR(event, "config:0-63");
+
+static struct attribute *riscv_arch_formats_attr[] = {
+       &format_attr_event.attr,
+       NULL,
+};
+
+static struct attribute_group riscv_pmu_format_group = {
+       .name = "format",
+       .attrs = riscv_arch_formats_attr,
+};
+
+static const struct attribute_group *riscv_pmu_attr_groups[] = {
+       &riscv_pmu_format_group,
+       NULL,
+};
+
+static unsigned long csr_read_num(int csr_num)
+{
+#define switchcase_csr_read(__csr_num, __val)          {\
+       case __csr_num:                                 \
+               __val = csr_read(__csr_num);            \
+               break; }
+#define switchcase_csr_read_2(__csr_num, __val)                {\
+       switchcase_csr_read(__csr_num + 0, __val)        \
+       switchcase_csr_read(__csr_num + 1, __val)}
+#define switchcase_csr_read_4(__csr_num, __val)                {\
+       switchcase_csr_read_2(__csr_num + 0, __val)      \
+       switchcase_csr_read_2(__csr_num + 2, __val)}
+#define switchcase_csr_read_8(__csr_num, __val)                {\
+       switchcase_csr_read_4(__csr_num + 0, __val)      \
+       switchcase_csr_read_4(__csr_num + 4, __val)}
+#define switchcase_csr_read_16(__csr_num, __val)       {\
+       switchcase_csr_read_8(__csr_num + 0, __val)      \
+       switchcase_csr_read_8(__csr_num + 8, __val)}
+#define switchcase_csr_read_32(__csr_num, __val)       {\
+       switchcase_csr_read_16(__csr_num + 0, __val)     \
+       switchcase_csr_read_16(__csr_num + 16, __val)}
+
+       unsigned long ret = 0;
+
+       switch (csr_num) {
+       switchcase_csr_read_32(CSR_CYCLE, ret)
+       switchcase_csr_read_32(CSR_CYCLEH, ret)
+       default :
+               break;
+       }
+
+       return ret;
+#undef switchcase_csr_read_32
+#undef switchcase_csr_read_16
+#undef switchcase_csr_read_8
+#undef switchcase_csr_read_4
+#undef switchcase_csr_read_2
+#undef switchcase_csr_read
+}
+
+/*
+ * Read the CSR of a corresponding counter.
+ */
+unsigned long riscv_pmu_ctr_read_csr(unsigned long csr)
+{
+       if (csr < CSR_CYCLE || csr > CSR_HPMCOUNTER31H ||
+          (csr > CSR_HPMCOUNTER31 && csr < CSR_CYCLEH)) {
+               pr_err("Invalid performance counter csr %lx\n", csr);
+               return -EINVAL;
+       }
+
+       return csr_read_num(csr);
+}
+
+u64 riscv_pmu_ctr_get_width_mask(struct perf_event *event)
+{
+       int cwidth;
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       struct hw_perf_event *hwc = &event->hw;
+
+       if (!rvpmu->ctr_get_width)
+       /**
+        * If the pmu driver doesn't support counter width, set it to default
+        * maximum allowed by the specification.
+        */
+               cwidth = 63;
+       else {
+               if (hwc->idx == -1)
+                       /* Handle init case where idx is not initialized yet */
+                       cwidth = rvpmu->ctr_get_width(0);
+               else
+                       cwidth = rvpmu->ctr_get_width(hwc->idx);
+       }
+
+       return GENMASK_ULL(cwidth, 0);
+}
+
+u64 riscv_pmu_event_update(struct perf_event *event)
+{
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       struct hw_perf_event *hwc = &event->hw;
+       u64 prev_raw_count, new_raw_count;
+       unsigned long cmask;
+       u64 oldval, delta;
+
+       if (!rvpmu->ctr_read)
+               return 0;
+
+       cmask = riscv_pmu_ctr_get_width_mask(event);
+
+       do {
+               prev_raw_count = local64_read(&hwc->prev_count);
+               new_raw_count = rvpmu->ctr_read(event);
+               oldval = local64_cmpxchg(&hwc->prev_count, prev_raw_count,
+                                        new_raw_count);
+       } while (oldval != prev_raw_count);
+
+       delta = (new_raw_count - prev_raw_count) & cmask;
+       local64_add(delta, &event->count);
+       local64_sub(delta, &hwc->period_left);
+
+       return delta;
+}
+
+static void riscv_pmu_stop(struct perf_event *event, int flags)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+
+       WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED);
+
+       if (!(hwc->state & PERF_HES_STOPPED)) {
+               if (rvpmu->ctr_stop) {
+                       rvpmu->ctr_stop(event, 0);
+                       hwc->state |= PERF_HES_STOPPED;
+               }
+               riscv_pmu_event_update(event);
+               hwc->state |= PERF_HES_UPTODATE;
+       }
+}
+
+int riscv_pmu_event_set_period(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       s64 left = local64_read(&hwc->period_left);
+       s64 period = hwc->sample_period;
+       int overflow = 0;
+       uint64_t max_period = riscv_pmu_ctr_get_width_mask(event);
+
+       if (unlikely(left <= -period)) {
+               left = period;
+               local64_set(&hwc->period_left, left);
+               hwc->last_period = period;
+               overflow = 1;
+       }
+
+       if (unlikely(left <= 0)) {
+               left += period;
+               local64_set(&hwc->period_left, left);
+               hwc->last_period = period;
+               overflow = 1;
+       }
+
+       /*
+        * Limit the maximum period to prevent the counter value
+        * from overtaking the one we are about to program. In
+        * effect we are reducing max_period to account for
+        * interrupt latency (and we are being very conservative).
+        */
+       if (left > (max_period >> 1))
+               left = (max_period >> 1);
+
+       local64_set(&hwc->prev_count, (u64)-left);
+       perf_event_update_userpage(event);
+
+       return overflow;
+}
+
+static void riscv_pmu_start(struct perf_event *event, int flags)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       uint64_t max_period = riscv_pmu_ctr_get_width_mask(event);
+       u64 init_val;
+
+       if (WARN_ON_ONCE(!(event->hw.state & PERF_HES_STOPPED)))
+               return;
+
+       if (flags & PERF_EF_RELOAD)
+               WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE));
+
+       hwc->state = 0;
+       riscv_pmu_event_set_period(event);
+       init_val = local64_read(&hwc->prev_count) & max_period;
+       rvpmu->ctr_start(event, init_val);
+       perf_event_update_userpage(event);
+}
+
+static int riscv_pmu_add(struct perf_event *event, int flags)
+{
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events);
+       struct hw_perf_event *hwc = &event->hw;
+       int idx;
+
+       idx = rvpmu->ctr_get_idx(event);
+       if (idx < 0)
+               return idx;
+
+       hwc->idx = idx;
+       cpuc->events[idx] = event;
+       cpuc->n_events++;
+       hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
+       if (flags & PERF_EF_START)
+               riscv_pmu_start(event, PERF_EF_RELOAD);
+
+       /* Propagate our changes to the userspace mapping. */
+       perf_event_update_userpage(event);
+
+       return 0;
+}
+
+static void riscv_pmu_del(struct perf_event *event, int flags)
+{
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events);
+       struct hw_perf_event *hwc = &event->hw;
+
+       riscv_pmu_stop(event, PERF_EF_UPDATE);
+       cpuc->events[hwc->idx] = NULL;
+       /* The firmware need to reset the counter mapping */
+       if (rvpmu->ctr_stop)
+               rvpmu->ctr_stop(event, RISCV_PMU_STOP_FLAG_RESET);
+       cpuc->n_events--;
+       if (rvpmu->ctr_clear_idx)
+               rvpmu->ctr_clear_idx(event);
+       perf_event_update_userpage(event);
+       hwc->idx = -1;
+}
+
+static void riscv_pmu_read(struct perf_event *event)
+{
+       riscv_pmu_event_update(event);
+}
+
+static int riscv_pmu_event_init(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       int mapped_event;
+       u64 event_config = 0;
+       uint64_t cmask;
+
+       hwc->flags = 0;
+       mapped_event = rvpmu->event_map(event, &event_config);
+       if (mapped_event < 0) {
+               pr_debug("event %x:%llx not supported\n", event->attr.type,
+                        event->attr.config);
+               return mapped_event;
+       }
+
+       /*
+        * idx is set to -1 because the index of a general event should not be
+        * decided until binding to some counter in pmu->add().
+        * config will contain the information about counter CSR
+        * the idx will contain the counter index
+        */
+       hwc->config = event_config;
+       hwc->idx = -1;
+       hwc->event_base = mapped_event;
+
+       if (!is_sampling_event(event)) {
+               /*
+                * For non-sampling runs, limit the sample_period to half
+                * of the counter width. That way, the new counter value
+                * is far less likely to overtake the previous one unless
+                * you have some serious IRQ latency issues.
+                */
+               cmask = riscv_pmu_ctr_get_width_mask(event);
+               hwc->sample_period  =  cmask >> 1;
+               hwc->last_period    = hwc->sample_period;
+               local64_set(&hwc->period_left, hwc->sample_period);
+       }
+
+       return 0;
+}
+
+struct riscv_pmu *riscv_pmu_alloc(void)
+{
+       struct riscv_pmu *pmu;
+       int cpuid, i;
+       struct cpu_hw_events *cpuc;
+
+       pmu = kzalloc(sizeof(*pmu), GFP_KERNEL);
+       if (!pmu)
+               goto out;
+
+       pmu->hw_events = alloc_percpu_gfp(struct cpu_hw_events, GFP_KERNEL);
+       if (!pmu->hw_events) {
+               pr_info("failed to allocate per-cpu PMU data.\n");
+               goto out_free_pmu;
+       }
+
+       for_each_possible_cpu(cpuid) {
+               cpuc = per_cpu_ptr(pmu->hw_events, cpuid);
+               cpuc->n_events = 0;
+               for (i = 0; i < RISCV_MAX_COUNTERS; i++)
+                       cpuc->events[i] = NULL;
+       }
+       pmu->pmu = (struct pmu) {
+               .attr_groups    = riscv_pmu_attr_groups,
+               .event_init     = riscv_pmu_event_init,
+               .add            = riscv_pmu_add,
+               .del            = riscv_pmu_del,
+               .start          = riscv_pmu_start,
+               .stop           = riscv_pmu_stop,
+               .read           = riscv_pmu_read,
+       };
+
+       return pmu;
+
+out_free_pmu:
+       kfree(pmu);
+out:
+       return NULL;
+}
diff --git a/drivers/perf/riscv_pmu_legacy.c b/drivers/perf/riscv_pmu_legacy.c
new file mode 100644 (file)
index 0000000..3427787
--- /dev/null
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RISC-V performance counter support.
+ *
+ * Copyright (C) 2021 Western Digital Corporation or its affiliates.
+ *
+ * This implementation is based on old RISC-V perf and ARM perf event code
+ * which are in turn based on sparc64 and x86 code.
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/perf/riscv_pmu.h>
+#include <linux/platform_device.h>
+
+#define RISCV_PMU_LEGACY_CYCLE         0
+#define RISCV_PMU_LEGACY_INSTRET       1
+#define RISCV_PMU_LEGACY_NUM_CTR       2
+
+static bool pmu_init_done;
+
+static int pmu_legacy_ctr_get_idx(struct perf_event *event)
+{
+       struct perf_event_attr *attr = &event->attr;
+
+       if (event->attr.type != PERF_TYPE_HARDWARE)
+               return -EOPNOTSUPP;
+       if (attr->config == PERF_COUNT_HW_CPU_CYCLES)
+               return RISCV_PMU_LEGACY_CYCLE;
+       else if (attr->config == PERF_COUNT_HW_INSTRUCTIONS)
+               return RISCV_PMU_LEGACY_INSTRET;
+       else
+               return -EOPNOTSUPP;
+}
+
+/* For legacy config & counter index are same */
+static int pmu_legacy_event_map(struct perf_event *event, u64 *config)
+{
+       return pmu_legacy_ctr_get_idx(event);
+}
+
+static u64 pmu_legacy_read_ctr(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       int idx = hwc->idx;
+       u64 val;
+
+       if (idx == RISCV_PMU_LEGACY_CYCLE) {
+               val = riscv_pmu_ctr_read_csr(CSR_CYCLE);
+               if (IS_ENABLED(CONFIG_32BIT))
+                       val = (u64)riscv_pmu_ctr_read_csr(CSR_CYCLEH) << 32 | val;
+       } else if (idx == RISCV_PMU_LEGACY_INSTRET) {
+               val = riscv_pmu_ctr_read_csr(CSR_INSTRET);
+               if (IS_ENABLED(CONFIG_32BIT))
+                       val = ((u64)riscv_pmu_ctr_read_csr(CSR_INSTRETH)) << 32 | val;
+       } else
+               return 0;
+
+       return val;
+}
+
+static void pmu_legacy_ctr_start(struct perf_event *event, u64 ival)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       u64 initial_val = pmu_legacy_read_ctr(event);
+
+       /**
+        * The legacy method doesn't really have a start/stop method.
+        * It also can not update the counter with a initial value.
+        * But we still need to set the prev_count so that read() can compute
+        * the delta. Just use the current counter value to set the prev_count.
+        */
+       local64_set(&hwc->prev_count, initial_val);
+}
+
+/**
+ * This is just a simple implementation to allow legacy implementations
+ * compatible with new RISC-V PMU driver framework.
+ * This driver only allows reading two counters i.e CYCLE & INSTRET.
+ * However, it can not start or stop the counter. Thus, it is not very useful
+ * will be removed in future.
+ */
+static void pmu_legacy_init(struct riscv_pmu *pmu)
+{
+       pr_info("Legacy PMU implementation is available\n");
+
+       pmu->num_counters = RISCV_PMU_LEGACY_NUM_CTR;
+       pmu->ctr_start = pmu_legacy_ctr_start;
+       pmu->ctr_stop = NULL;
+       pmu->event_map = pmu_legacy_event_map;
+       pmu->ctr_get_idx = pmu_legacy_ctr_get_idx;
+       pmu->ctr_get_width = NULL;
+       pmu->ctr_clear_idx = NULL;
+       pmu->ctr_read = pmu_legacy_read_ctr;
+
+       perf_pmu_register(&pmu->pmu, "cpu", PERF_TYPE_RAW);
+}
+
+static int pmu_legacy_device_probe(struct platform_device *pdev)
+{
+       struct riscv_pmu *pmu = NULL;
+
+       pmu = riscv_pmu_alloc();
+       if (!pmu)
+               return -ENOMEM;
+       pmu_legacy_init(pmu);
+
+       return 0;
+}
+
+static struct platform_driver pmu_legacy_driver = {
+       .probe          = pmu_legacy_device_probe,
+       .driver         = {
+               .name   = RISCV_PMU_LEGACY_PDEV_NAME,
+       },
+};
+
+static int __init riscv_pmu_legacy_devinit(void)
+{
+       int ret;
+       struct platform_device *pdev;
+
+       if (likely(pmu_init_done))
+               return 0;
+
+       ret = platform_driver_register(&pmu_legacy_driver);
+       if (ret)
+               return ret;
+
+       pdev = platform_device_register_simple(RISCV_PMU_LEGACY_PDEV_NAME, -1, NULL, 0);
+       if (IS_ERR(pdev)) {
+               platform_driver_unregister(&pmu_legacy_driver);
+               return PTR_ERR(pdev);
+       }
+
+       return ret;
+}
+late_initcall(riscv_pmu_legacy_devinit);
+
+void riscv_pmu_legacy_skip_init(void)
+{
+       pmu_init_done = true;
+}
diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c
new file mode 100644 (file)
index 0000000..15ab3dc
--- /dev/null
@@ -0,0 +1,837 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RISC-V performance counter support.
+ *
+ * Copyright (C) 2021 Western Digital Corporation or its affiliates.
+ *
+ * This code is based on ARM perf event code which is in turn based on
+ * sparc64 and x86 code.
+ */
+
+#define pr_fmt(fmt) "riscv-pmu-sbi: " fmt
+
+#include <linux/mod_devicetable.h>
+#include <linux/perf/riscv_pmu.h>
+#include <linux/platform_device.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_irq.h>
+#include <linux/of.h>
+
+#include <asm/sbi.h>
+#include <asm/hwcap.h>
+
+union sbi_pmu_ctr_info {
+       unsigned long value;
+       struct {
+               unsigned long csr:12;
+               unsigned long width:6;
+#if __riscv_xlen == 32
+               unsigned long reserved:13;
+#else
+               unsigned long reserved:45;
+#endif
+               unsigned long type:1;
+       };
+};
+
+/**
+ * RISC-V doesn't have hetergenous harts yet. This need to be part of
+ * per_cpu in case of harts with different pmu counters
+ */
+static union sbi_pmu_ctr_info *pmu_ctr_list;
+static unsigned int riscv_pmu_irq;
+
+struct sbi_pmu_event_data {
+       union {
+               union {
+                       struct hw_gen_event {
+                               uint32_t event_code:16;
+                               uint32_t event_type:4;
+                               uint32_t reserved:12;
+                       } hw_gen_event;
+                       struct hw_cache_event {
+                               uint32_t result_id:1;
+                               uint32_t op_id:2;
+                               uint32_t cache_id:13;
+                               uint32_t event_type:4;
+                               uint32_t reserved:12;
+                       } hw_cache_event;
+               };
+               uint32_t event_idx;
+       };
+};
+
+static const struct sbi_pmu_event_data pmu_hw_event_map[] = {
+       [PERF_COUNT_HW_CPU_CYCLES]              = {.hw_gen_event = {
+                                                       SBI_PMU_HW_CPU_CYCLES,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_INSTRUCTIONS]            = {.hw_gen_event = {
+                                                       SBI_PMU_HW_INSTRUCTIONS,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_CACHE_REFERENCES]        = {.hw_gen_event = {
+                                                       SBI_PMU_HW_CACHE_REFERENCES,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_CACHE_MISSES]            = {.hw_gen_event = {
+                                                       SBI_PMU_HW_CACHE_MISSES,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_BRANCH_INSTRUCTIONS]     = {.hw_gen_event = {
+                                                       SBI_PMU_HW_BRANCH_INSTRUCTIONS,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_BRANCH_MISSES]           = {.hw_gen_event = {
+                                                       SBI_PMU_HW_BRANCH_MISSES,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_BUS_CYCLES]              = {.hw_gen_event = {
+                                                       SBI_PMU_HW_BUS_CYCLES,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_STALLED_CYCLES_FRONTEND] = {.hw_gen_event = {
+                                                       SBI_PMU_HW_STALLED_CYCLES_FRONTEND,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_STALLED_CYCLES_BACKEND]  = {.hw_gen_event = {
+                                                       SBI_PMU_HW_STALLED_CYCLES_BACKEND,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+       [PERF_COUNT_HW_REF_CPU_CYCLES]          = {.hw_gen_event = {
+                                                       SBI_PMU_HW_REF_CPU_CYCLES,
+                                                       SBI_PMU_EVENT_TYPE_HW, 0}},
+};
+
+#define C(x) PERF_COUNT_HW_CACHE_##x
+static const struct sbi_pmu_event_data pmu_cache_event_map[PERF_COUNT_HW_CACHE_MAX]
+[PERF_COUNT_HW_CACHE_OP_MAX]
+[PERF_COUNT_HW_CACHE_RESULT_MAX] = {
+       [C(L1D)] = {
+               [C(OP_READ)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_READ), C(L1D), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_READ), C(L1D), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_WRITE)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_WRITE), C(L1D), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_WRITE), C(L1D), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_PREFETCH)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_PREFETCH), C(L1D), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_PREFETCH), C(L1D), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+       },
+       [C(L1I)] = {
+               [C(OP_READ)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_READ), C(L1I), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS), C(OP_READ),
+                                       C(L1I), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_WRITE)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_WRITE), C(L1I), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_WRITE), C(L1I), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_PREFETCH)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_PREFETCH), C(L1I), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_PREFETCH), C(L1I), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+       },
+       [C(LL)] = {
+               [C(OP_READ)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_READ), C(LL), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_READ), C(LL), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_WRITE)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_WRITE), C(LL), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_WRITE), C(LL), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_PREFETCH)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_PREFETCH), C(LL), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_PREFETCH), C(LL), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+       },
+       [C(DTLB)] = {
+               [C(OP_READ)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_READ), C(DTLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_READ), C(DTLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_WRITE)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_WRITE), C(DTLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_WRITE), C(DTLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_PREFETCH)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_PREFETCH), C(DTLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_PREFETCH), C(DTLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+       },
+       [C(ITLB)] = {
+               [C(OP_READ)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_READ), C(ITLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_READ), C(ITLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_WRITE)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_WRITE), C(ITLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_WRITE), C(ITLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_PREFETCH)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_PREFETCH), C(ITLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_PREFETCH), C(ITLB), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+       },
+       [C(BPU)] = {
+               [C(OP_READ)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_READ), C(BPU), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_READ), C(BPU), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_WRITE)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_WRITE), C(BPU), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_WRITE), C(BPU), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_PREFETCH)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_PREFETCH), C(BPU), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_PREFETCH), C(BPU), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+       },
+       [C(NODE)] = {
+               [C(OP_READ)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_READ), C(NODE), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_READ), C(NODE), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_WRITE)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_WRITE), C(NODE), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_WRITE), C(NODE), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+               [C(OP_PREFETCH)] = {
+                       [C(RESULT_ACCESS)] = {.hw_cache_event = {C(RESULT_ACCESS),
+                                       C(OP_PREFETCH), C(NODE), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+                       [C(RESULT_MISS)] = {.hw_cache_event = {C(RESULT_MISS),
+                                       C(OP_PREFETCH), C(NODE), SBI_PMU_EVENT_TYPE_CACHE, 0}},
+               },
+       },
+};
+
+static int pmu_sbi_ctr_get_width(int idx)
+{
+       return pmu_ctr_list[idx].width;
+}
+
+static bool pmu_sbi_ctr_is_fw(int cidx)
+{
+       union sbi_pmu_ctr_info *info;
+
+       info = &pmu_ctr_list[cidx];
+       if (!info)
+               return false;
+
+       return (info->type == SBI_PMU_CTR_TYPE_FW) ? true : false;
+}
+
+static int pmu_sbi_ctr_get_idx(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events);
+       struct sbiret ret;
+       int idx;
+       uint64_t cbase = 0;
+       uint64_t cmask = GENMASK_ULL(rvpmu->num_counters - 1, 0);
+       unsigned long cflags = 0;
+
+       if (event->attr.exclude_kernel)
+               cflags |= SBI_PMU_CFG_FLAG_SET_SINH;
+       if (event->attr.exclude_user)
+               cflags |= SBI_PMU_CFG_FLAG_SET_UINH;
+
+       /* retrieve the available counter index */
+       ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, cmask,
+                       cflags, hwc->event_base, hwc->config, 0);
+       if (ret.error) {
+               pr_debug("Not able to find a counter for event %lx config %llx\n",
+                       hwc->event_base, hwc->config);
+               return sbi_err_map_linux_errno(ret.error);
+       }
+
+       idx = ret.value;
+       if (idx >= rvpmu->num_counters || !pmu_ctr_list[idx].value)
+               return -ENOENT;
+
+       /* Additional sanity check for the counter id */
+       if (pmu_sbi_ctr_is_fw(idx)) {
+               if (!test_and_set_bit(idx, cpuc->used_fw_ctrs))
+                       return idx;
+       } else {
+               if (!test_and_set_bit(idx, cpuc->used_hw_ctrs))
+                       return idx;
+       }
+
+       return -ENOENT;
+}
+
+static void pmu_sbi_ctr_clear_idx(struct perf_event *event)
+{
+
+       struct hw_perf_event *hwc = &event->hw;
+       struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu);
+       struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events);
+       int idx = hwc->idx;
+
+       if (pmu_sbi_ctr_is_fw(idx))
+               clear_bit(idx, cpuc->used_fw_ctrs);
+       else
+               clear_bit(idx, cpuc->used_hw_ctrs);
+}
+
+static int pmu_event_find_cache(u64 config)
+{
+       unsigned int cache_type, cache_op, cache_result, ret;
+
+       cache_type = (config >>  0) & 0xff;
+       if (cache_type >= PERF_COUNT_HW_CACHE_MAX)
+               return -EINVAL;
+
+       cache_op = (config >>  8) & 0xff;
+       if (cache_op >= PERF_COUNT_HW_CACHE_OP_MAX)
+               return -EINVAL;
+
+       cache_result = (config >> 16) & 0xff;
+       if (cache_result >= PERF_COUNT_HW_CACHE_RESULT_MAX)
+               return -EINVAL;
+
+       ret = pmu_cache_event_map[cache_type][cache_op][cache_result].event_idx;
+
+       return ret;
+}
+
+static bool pmu_sbi_is_fw_event(struct perf_event *event)
+{
+       u32 type = event->attr.type;
+       u64 config = event->attr.config;
+
+       if ((type == PERF_TYPE_RAW) && ((config >> 63) == 1))
+               return true;
+       else
+               return false;
+}
+
+static int pmu_sbi_event_map(struct perf_event *event, u64 *econfig)
+{
+       u32 type = event->attr.type;
+       u64 config = event->attr.config;
+       int bSoftware;
+       u64 raw_config_val;
+       int ret;
+
+       switch (type) {
+       case PERF_TYPE_HARDWARE:
+               if (config >= PERF_COUNT_HW_MAX)
+                       return -EINVAL;
+               ret = pmu_hw_event_map[event->attr.config].event_idx;
+               break;
+       case PERF_TYPE_HW_CACHE:
+               ret = pmu_event_find_cache(config);
+               break;
+       case PERF_TYPE_RAW:
+               /*
+                * As per SBI specification, the upper 16 bits must be unused for
+                * a raw event. Use the MSB (63b) to distinguish between hardware
+                * raw event and firmware events.
+                */
+               bSoftware = config >> 63;
+               raw_config_val = config & RISCV_PMU_RAW_EVENT_MASK;
+               if (bSoftware) {
+                       if (raw_config_val < SBI_PMU_FW_MAX)
+                               ret = (raw_config_val & 0xFFFF) |
+                                     (SBI_PMU_EVENT_TYPE_FW << 16);
+                       else
+                               return -EINVAL;
+               } else {
+                       ret = RISCV_PMU_RAW_EVENT_IDX;
+                       *econfig = raw_config_val;
+               }
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static u64 pmu_sbi_ctr_read(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       int idx = hwc->idx;
+       struct sbiret ret;
+       union sbi_pmu_ctr_info info;
+       u64 val = 0;
+
+       if (pmu_sbi_is_fw_event(event)) {
+               ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_FW_READ,
+                               hwc->idx, 0, 0, 0, 0, 0);
+               if (!ret.error)
+                       val = ret.value;
+       } else {
+               info = pmu_ctr_list[idx];
+               val = riscv_pmu_ctr_read_csr(info.csr);
+               if (IS_ENABLED(CONFIG_32BIT))
+                       val = ((u64)riscv_pmu_ctr_read_csr(info.csr + 0x80)) << 31 | val;
+       }
+
+       return val;
+}
+
+static void pmu_sbi_ctr_start(struct perf_event *event, u64 ival)
+{
+       struct sbiret ret;
+       struct hw_perf_event *hwc = &event->hw;
+       unsigned long flag = SBI_PMU_START_FLAG_SET_INIT_VALUE;
+
+       ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, hwc->idx,
+                       1, flag, ival, ival >> 32, 0);
+       if (ret.error && (ret.error != SBI_ERR_ALREADY_STARTED))
+               pr_err("Starting counter idx %d failed with error %d\n",
+                       hwc->idx, sbi_err_map_linux_errno(ret.error));
+}
+
+static void pmu_sbi_ctr_stop(struct perf_event *event, unsigned long flag)
+{
+       struct sbiret ret;
+       struct hw_perf_event *hwc = &event->hw;
+
+       ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, hwc->idx, 1, flag, 0, 0, 0);
+       if (ret.error && (ret.error != SBI_ERR_ALREADY_STOPPED) &&
+               flag != SBI_PMU_STOP_FLAG_RESET)
+               pr_err("Stopping counter idx %d failed with error %d\n",
+                       hwc->idx, sbi_err_map_linux_errno(ret.error));
+}
+
+static int pmu_sbi_find_num_ctrs(void)
+{
+       struct sbiret ret;
+
+       ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_NUM_COUNTERS, 0, 0, 0, 0, 0, 0);
+       if (!ret.error)
+               return ret.value;
+       else
+               return sbi_err_map_linux_errno(ret.error);
+}
+
+static int pmu_sbi_get_ctrinfo(int nctr)
+{
+       struct sbiret ret;
+       int i, num_hw_ctr = 0, num_fw_ctr = 0;
+       union sbi_pmu_ctr_info cinfo;
+
+       pmu_ctr_list = kcalloc(nctr, sizeof(*pmu_ctr_list), GFP_KERNEL);
+       if (!pmu_ctr_list)
+               return -ENOMEM;
+
+       for (i = 0; i <= nctr; i++) {
+               ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_GET_INFO, i, 0, 0, 0, 0, 0);
+               if (ret.error)
+                       /* The logical counter ids are not expected to be contiguous */
+                       continue;
+               cinfo.value = ret.value;
+               if (cinfo.type == SBI_PMU_CTR_TYPE_FW)
+                       num_fw_ctr++;
+               else
+                       num_hw_ctr++;
+               pmu_ctr_list[i].value = cinfo.value;
+       }
+
+       pr_info("%d firmware and %d hardware counters\n", num_fw_ctr, num_hw_ctr);
+
+       return 0;
+}
+
+static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu)
+{
+       /**
+        * No need to check the error because we are disabling all the counters
+        * which may include counters that are not enabled yet.
+        */
+       sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP,
+                 0, GENMASK_ULL(pmu->num_counters - 1, 0), 0, 0, 0, 0);
+}
+
+static inline void pmu_sbi_stop_hw_ctrs(struct riscv_pmu *pmu)
+{
+       struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
+
+       /* No need to check the error here as we can't do anything about the error */
+       sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, 0,
+                 cpu_hw_evt->used_hw_ctrs[0], 0, 0, 0, 0);
+}
+
+/**
+ * This function starts all the used counters in two step approach.
+ * Any counter that did not overflow can be start in a single step
+ * while the overflowed counters need to be started with updated initialization
+ * value.
+ */
+static inline void pmu_sbi_start_overflow_mask(struct riscv_pmu *pmu,
+                                              unsigned long ctr_ovf_mask)
+{
+       int idx = 0;
+       struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
+       struct perf_event *event;
+       unsigned long flag = SBI_PMU_START_FLAG_SET_INIT_VALUE;
+       unsigned long ctr_start_mask = 0;
+       uint64_t max_period;
+       struct hw_perf_event *hwc;
+       u64 init_val = 0;
+
+       ctr_start_mask = cpu_hw_evt->used_hw_ctrs[0] & ~ctr_ovf_mask;
+
+       /* Start all the counters that did not overflow in a single shot */
+       sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, 0, ctr_start_mask,
+                 0, 0, 0, 0);
+
+       /* Reinitialize and start all the counter that overflowed */
+       while (ctr_ovf_mask) {
+               if (ctr_ovf_mask & 0x01) {
+                       event = cpu_hw_evt->events[idx];
+                       hwc = &event->hw;
+                       max_period = riscv_pmu_ctr_get_width_mask(event);
+                       init_val = local64_read(&hwc->prev_count) & max_period;
+                       sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_START, idx, 1,
+                                 flag, init_val, 0, 0);
+               }
+               ctr_ovf_mask = ctr_ovf_mask >> 1;
+               idx++;
+       }
+}
+
+static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev)
+{
+       struct perf_sample_data data;
+       struct pt_regs *regs;
+       struct hw_perf_event *hw_evt;
+       union sbi_pmu_ctr_info *info;
+       int lidx, hidx, fidx;
+       struct riscv_pmu *pmu;
+       struct perf_event *event;
+       unsigned long overflow;
+       unsigned long overflowed_ctrs = 0;
+       struct cpu_hw_events *cpu_hw_evt = dev;
+
+       if (WARN_ON_ONCE(!cpu_hw_evt))
+               return IRQ_NONE;
+
+       /* Firmware counter don't support overflow yet */
+       fidx = find_first_bit(cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS);
+       event = cpu_hw_evt->events[fidx];
+       if (!event) {
+               csr_clear(CSR_SIP, SIP_LCOFIP);
+               return IRQ_NONE;
+       }
+
+       pmu = to_riscv_pmu(event->pmu);
+       pmu_sbi_stop_hw_ctrs(pmu);
+
+       /* Overflow status register should only be read after counter are stopped */
+       overflow = csr_read(CSR_SSCOUNTOVF);
+
+       /**
+        * Overflow interrupt pending bit should only be cleared after stopping
+        * all the counters to avoid any race condition.
+        */
+       csr_clear(CSR_SIP, SIP_LCOFIP);
+
+       /* No overflow bit is set */
+       if (!overflow)
+               return IRQ_NONE;
+
+       regs = get_irq_regs();
+
+       for_each_set_bit(lidx, cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS) {
+               struct perf_event *event = cpu_hw_evt->events[lidx];
+
+               /* Skip if invalid event or user did not request a sampling */
+               if (!event || !is_sampling_event(event))
+                       continue;
+
+               info = &pmu_ctr_list[lidx];
+               /* Do a sanity check */
+               if (!info || info->type != SBI_PMU_CTR_TYPE_HW)
+                       continue;
+
+               /* compute hardware counter index */
+               hidx = info->csr - CSR_CYCLE;
+               /* check if the corresponding bit is set in sscountovf */
+               if (!(overflow & (1 << hidx)))
+                       continue;
+
+               /*
+                * Keep a track of overflowed counters so that they can be started
+                * with updated initial value.
+                */
+               overflowed_ctrs |= 1 << lidx;
+               hw_evt = &event->hw;
+               riscv_pmu_event_update(event);
+               perf_sample_data_init(&data, 0, hw_evt->last_period);
+               if (riscv_pmu_event_set_period(event)) {
+                       /*
+                        * Unlike other ISAs, RISC-V don't have to disable interrupts
+                        * to avoid throttling here. As per the specification, the
+                        * interrupt remains disabled until the OF bit is set.
+                        * Interrupts are enabled again only during the start.
+                        * TODO: We will need to stop the guest counters once
+                        * virtualization support is added.
+                        */
+                       perf_event_overflow(event, &data, regs);
+               }
+       }
+       pmu_sbi_start_overflow_mask(pmu, overflowed_ctrs);
+
+       return IRQ_HANDLED;
+}
+
+static int pmu_sbi_starting_cpu(unsigned int cpu, struct hlist_node *node)
+{
+       struct riscv_pmu *pmu = hlist_entry_safe(node, struct riscv_pmu, node);
+       struct cpu_hw_events *cpu_hw_evt = this_cpu_ptr(pmu->hw_events);
+
+       /* Enable the access for TIME csr only from the user mode now */
+       csr_write(CSR_SCOUNTEREN, 0x2);
+
+       /* Stop all the counters so that they can be enabled from perf */
+       pmu_sbi_stop_all(pmu);
+
+       if (riscv_isa_extension_available(NULL, SSCOFPMF)) {
+               cpu_hw_evt->irq = riscv_pmu_irq;
+               csr_clear(CSR_IP, BIT(RV_IRQ_PMU));
+               csr_set(CSR_IE, BIT(RV_IRQ_PMU));
+               enable_percpu_irq(riscv_pmu_irq, IRQ_TYPE_NONE);
+       }
+
+       return 0;
+}
+
+static int pmu_sbi_dying_cpu(unsigned int cpu, struct hlist_node *node)
+{
+       if (riscv_isa_extension_available(NULL, SSCOFPMF)) {
+               disable_percpu_irq(riscv_pmu_irq);
+               csr_clear(CSR_IE, BIT(RV_IRQ_PMU));
+       }
+
+       /* Disable all counters access for user mode now */
+       csr_write(CSR_SCOUNTEREN, 0x0);
+
+       return 0;
+}
+
+static int pmu_sbi_setup_irqs(struct riscv_pmu *pmu, struct platform_device *pdev)
+{
+       int ret;
+       struct cpu_hw_events __percpu *hw_events = pmu->hw_events;
+       struct device_node *cpu, *child;
+       struct irq_domain *domain = NULL;
+
+       if (!riscv_isa_extension_available(NULL, SSCOFPMF))
+               return -EOPNOTSUPP;
+
+       for_each_of_cpu_node(cpu) {
+               child = of_get_compatible_child(cpu, "riscv,cpu-intc");
+               if (!child) {
+                       pr_err("Failed to find INTC node\n");
+                       return -ENODEV;
+               }
+               domain = irq_find_host(child);
+               of_node_put(child);
+               if (domain)
+                       break;
+       }
+       if (!domain) {
+               pr_err("Failed to find INTC IRQ root domain\n");
+               return -ENODEV;
+       }
+
+       riscv_pmu_irq = irq_create_mapping(domain, RV_IRQ_PMU);
+       if (!riscv_pmu_irq) {
+               pr_err("Failed to map PMU interrupt for node\n");
+               return -ENODEV;
+       }
+
+       ret = request_percpu_irq(riscv_pmu_irq, pmu_sbi_ovf_handler, "riscv-pmu", hw_events);
+       if (ret) {
+               pr_err("registering percpu irq failed [%d]\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static uint64_t pmu_sbi_get_pmu_id(void)
+{
+       union sbi_pmu_id {
+               uint64_t value;
+               struct {
+                       uint16_t imp:16;
+                       uint16_t arch:16;
+                       uint32_t vendor:32;
+               };
+       } pmuid;
+
+       pmuid.value = 0;
+       pmuid.vendor = (uint32_t) sbi_get_mvendorid();
+       pmuid.arch = (sbi_get_marchid() >> (63 - 15) & (1 << 15)) | (sbi_get_marchid() & 0x7FFF);
+       pmuid.imp = (sbi_get_mimpid() >> 16);
+
+       return pmuid.value;
+}
+
+static ssize_t pmu_sbi_id_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       int len;
+
+       len = sprintf(buf, "0x%llx\n", pmu_sbi_get_pmu_id());
+       if (len <= 0)
+               dev_err(dev, "mydrv: Invalid sprintf len: %dn", len);
+
+       return len;
+}
+
+static DEVICE_ATTR(id, S_IRUGO | S_IWUSR, pmu_sbi_id_show, 0);
+
+static struct attribute *pmu_sbi_attrs[] = {
+       &dev_attr_id.attr,
+       NULL
+};
+
+ATTRIBUTE_GROUPS(pmu_sbi);
+
+static int pmu_sbi_device_probe(struct platform_device *pdev)
+{
+       struct riscv_pmu *pmu = NULL;
+       int num_counters;
+       int ret = -ENODEV;
+
+       pr_info("SBI PMU extension is available\n");
+       pmu = riscv_pmu_alloc();
+       if (!pmu)
+               return -ENOMEM;
+
+       num_counters = pmu_sbi_find_num_ctrs();
+       if (num_counters < 0) {
+               pr_err("SBI PMU extension doesn't provide any counters\n");
+               goto out_free;
+       }
+
+       /* cache all the information about counters now */
+       if (pmu_sbi_get_ctrinfo(num_counters))
+               goto out_free;
+
+       ret = pmu_sbi_setup_irqs(pmu, pdev);
+       if (ret < 0) {
+               pr_info("Perf sampling/filtering is not supported as sscof extension is not available\n");
+               pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
+               pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE;
+       }
+       pmu->num_counters = num_counters;
+       pmu->ctr_start = pmu_sbi_ctr_start;
+       pmu->ctr_stop = pmu_sbi_ctr_stop;
+       pmu->event_map = pmu_sbi_event_map;
+       pmu->ctr_get_idx = pmu_sbi_ctr_get_idx;
+       pmu->ctr_get_width = pmu_sbi_ctr_get_width;
+       pmu->ctr_clear_idx = pmu_sbi_ctr_clear_idx;
+       pmu->ctr_read = pmu_sbi_ctr_read;
+
+       ret = sysfs_create_group(&pdev->dev.kobj, &pmu_sbi_group);
+       if (ret) {
+               dev_err(&pdev->dev, "sysfs creation failed\n");
+               return ret;
+       }
+       pdev->dev.groups = pmu_sbi_groups;
+
+       ret = cpuhp_state_add_instance(CPUHP_AP_PERF_RISCV_STARTING, &pmu->node);
+       if (ret)
+               return ret;
+
+       ret = perf_pmu_register(&pmu->pmu, "cpu", PERF_TYPE_RAW);
+       if (ret) {
+               cpuhp_state_remove_instance(CPUHP_AP_PERF_RISCV_STARTING, &pmu->node);
+               return ret;
+       }
+
+       return 0;
+
+out_free:
+       kfree(pmu);
+       return ret;
+}
+
+static struct platform_driver pmu_sbi_driver = {
+       .probe          = pmu_sbi_device_probe,
+       .driver         = {
+               .name   = RISCV_PMU_PDEV_NAME,
+       },
+};
+
+static int __init pmu_sbi_devinit(void)
+{
+       int ret;
+       struct platform_device *pdev;
+
+       if (sbi_spec_version < sbi_mk_version(0, 3) ||
+           sbi_probe_extension(SBI_EXT_PMU) <= 0) {
+               return 0;
+       }
+
+       ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_RISCV_STARTING,
+                                     "perf/riscv/pmu:starting",
+                                     pmu_sbi_starting_cpu, pmu_sbi_dying_cpu);
+       if (ret) {
+               pr_err("CPU hotplug notifier could not be registered: %d\n",
+                      ret);
+               return ret;
+       }
+
+       ret = platform_driver_register(&pmu_sbi_driver);
+       if (ret)
+               return ret;
+
+       pdev = platform_device_register_simple(RISCV_PMU_PDEV_NAME, -1, NULL, 0);
+       if (IS_ERR(pdev)) {
+               platform_driver_unregister(&pmu_sbi_driver);
+               return PTR_ERR(pdev);
+       }
+
+       /* Notify legacy implementation that SBI pmu is available*/
+       riscv_pmu_legacy_skip_init();
+
+       return ret;
+}
+device_initcall(pmu_sbi_devinit)
index 9919110..29d9e6c 100644 (file)
@@ -164,6 +164,7 @@ enum cpuhp_state {
        CPUHP_AP_PERF_ARM_HW_BREAKPOINT_STARTING,
        CPUHP_AP_PERF_ARM_ACPI_STARTING,
        CPUHP_AP_PERF_ARM_STARTING,
+       CPUHP_AP_PERF_RISCV_STARTING,
        CPUHP_AP_ARM_L2X0_STARTING,
        CPUHP_AP_EXYNOS4_MCT_TIMER_STARTING,
        CPUHP_AP_ARM_ARCH_TIMER_STARTING,
diff --git a/include/linux/perf/riscv_pmu.h b/include/linux/perf/riscv_pmu.h
new file mode 100644 (file)
index 0000000..46f9b6f
--- /dev/null
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 SiFive
+ * Copyright (C) 2018 Andes Technology Corporation
+ * Copyright (C) 2021 Western Digital Corporation or its affiliates.
+ *
+ */
+
+#ifndef _ASM_RISCV_PERF_EVENT_H
+#define _ASM_RISCV_PERF_EVENT_H
+
+#include <linux/perf_event.h>
+#include <linux/ptrace.h>
+#include <linux/interrupt.h>
+
+#ifdef CONFIG_RISCV_PMU
+
+/*
+ * The RISCV_MAX_COUNTERS parameter should be specified.
+ */
+
+#define RISCV_MAX_COUNTERS     64
+#define RISCV_OP_UNSUPP                (-EOPNOTSUPP)
+#define RISCV_PMU_PDEV_NAME    "riscv-pmu"
+#define RISCV_PMU_LEGACY_PDEV_NAME     "riscv-pmu-legacy"
+
+#define RISCV_PMU_STOP_FLAG_RESET 1
+
+struct cpu_hw_events {
+       /* currently enabled events */
+       int                     n_events;
+       /* Counter overflow interrupt */
+       int             irq;
+       /* currently enabled events */
+       struct perf_event       *events[RISCV_MAX_COUNTERS];
+       /* currently enabled hardware counters */
+       DECLARE_BITMAP(used_hw_ctrs, RISCV_MAX_COUNTERS);
+       /* currently enabled firmware counters */
+       DECLARE_BITMAP(used_fw_ctrs, RISCV_MAX_COUNTERS);
+};
+
+struct riscv_pmu {
+       struct pmu      pmu;
+       char            *name;
+
+       irqreturn_t     (*handle_irq)(int irq_num, void *dev);
+
+       int             num_counters;
+       u64             (*ctr_read)(struct perf_event *event);
+       int             (*ctr_get_idx)(struct perf_event *event);
+       int             (*ctr_get_width)(int idx);
+       void            (*ctr_clear_idx)(struct perf_event *event);
+       void            (*ctr_start)(struct perf_event *event, u64 init_val);
+       void            (*ctr_stop)(struct perf_event *event, unsigned long flag);
+       int             (*event_map)(struct perf_event *event, u64 *config);
+
+       struct cpu_hw_events    __percpu *hw_events;
+       struct hlist_node       node;
+};
+
+#define to_riscv_pmu(p) (container_of(p, struct riscv_pmu, pmu))
+unsigned long riscv_pmu_ctr_read_csr(unsigned long csr);
+int riscv_pmu_event_set_period(struct perf_event *event);
+uint64_t riscv_pmu_ctr_get_width_mask(struct perf_event *event);
+u64 riscv_pmu_event_update(struct perf_event *event);
+#ifdef CONFIG_RISCV_PMU_LEGACY
+void riscv_pmu_legacy_skip_init(void);
+#else
+static inline void riscv_pmu_legacy_skip_init(void) {};
+#endif
+struct riscv_pmu *riscv_pmu_alloc(void);
+
+#endif /* CONFIG_RISCV_PMU */
+
+#endif /* _ASM_RISCV_PERF_EVENT_H */
index 7d30501..603dbb5 100644 (file)
@@ -1,4 +1,5 @@
 perf-y += perf_regs.o
+perf-y += header.o
 
 perf-$(CONFIG_DWARF) += dwarf-regs.o
 perf-$(CONFIG_LIBDW_DWARF_UNWIND) += unwind-libdw.o
diff --git a/tools/perf/arch/riscv/util/header.c b/tools/perf/arch/riscv/util/header.c
new file mode 100644 (file)
index 0000000..98d40b8
--- /dev/null
@@ -0,0 +1,66 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <api/fs/fs.h>
+#include <errno.h>
+#include "../../util/debug.h"
+#include "../../util/header.h"
+
+#define STR_LEN 1024
+#define ID_SIZE 64
+
+static int _get_cpuid(char *buf, size_t sz)
+{
+       const char *sysfs = sysfs__mountpoint();
+       u64 id = 0;
+       char path[PATH_MAX];
+       FILE *file;
+
+       if (!sysfs || sz < ID_SIZE)
+               return -EINVAL;
+
+       scnprintf(path, PATH_MAX, "%s/devices/platform/riscv-pmu/id",
+                       sysfs);
+
+       file = fopen(path, "r");
+       if (!file) {
+               pr_debug("fopen failed for file %s\n", path);
+               return -EINVAL;
+       }
+       if (!fgets(buf, ID_SIZE, file)) {
+               fclose(file);
+               return -EINVAL;
+       }
+
+       fclose(file);
+
+       /*Check if value is numeric and remove special characters*/
+       id = strtoul(buf, NULL, 16);
+       if (!id)
+               return -EINVAL;
+       scnprintf(buf, ID_SIZE, "0x%lx", id);
+
+       return 0;
+}
+
+char *get_cpuid_str(struct perf_pmu *pmu __maybe_unused)
+{
+       char *buf = NULL;
+       int res;
+
+       if (!pmu)
+               return NULL;
+
+       buf = malloc(ID_SIZE);
+       if (!buf)
+               return NULL;
+
+       /* read id */
+       res = _get_cpuid(buf, ID_SIZE);
+       if (res) {
+               pr_err("failed to get cpuid string for PMU %s\n", pmu->name);
+               free(buf);
+               buf = NULL;
+       }
+
+       return buf;
+}
diff --git a/tools/perf/pmu-events/arch/riscv/mapfile.csv b/tools/perf/pmu-events/arch/riscv/mapfile.csv
new file mode 100644 (file)
index 0000000..bda3fb9
--- /dev/null
@@ -0,0 +1,15 @@
+# Format:
+#      MIDR,Version,JSON/file/pathname,Type
+#
+# where
+#      MIDR    Processor version
+#              Variant[23:20] and Revision [3:0] should be zero.
+#      Version could be used to track version of JSON file
+#              but currently unused.
+#      JSON/file/pathname is the path to JSON file, relative
+#              to tools/perf/pmu-events/arch/riscv/.
+#      Type is core, uncore etc
+#
+#
+#Family-model,Version,Filename,EventType
+0x48980072018,v1,sifive/u74,core
diff --git a/tools/perf/pmu-events/arch/riscv/riscv-generic.json b/tools/perf/pmu-events/arch/riscv/riscv-generic.json
new file mode 100644 (file)
index 0000000..013e50e
--- /dev/null
@@ -0,0 +1,20 @@
+[
+  {
+    "PublicDescription": "CPU Cycles",
+    "EventCode": "0x00",
+    "EventName": "riscv_cycles",
+    "BriefDescription": "CPU cycles RISC-V generic counter"
+  },
+  {
+    "PublicDescription": "CPU Time",
+      "EventCode": "0x01",
+      "EventName": "riscv_time",
+      "BriefDescription": "CPU time RISC-V generic counter"
+  },
+  {
+    "PublicDescription": "CPU Instructions",
+      "EventCode": "0x02",
+      "EventName": "riscv_instret",
+      "BriefDescription": "CPU retired instructions RISC-V generic counter"
+  }
+]
\ No newline at end of file
diff --git a/tools/perf/pmu-events/arch/riscv/sifive/u74/instructions.json b/tools/perf/pmu-events/arch/riscv/sifive/u74/instructions.json
new file mode 100644 (file)
index 0000000..5eab718
--- /dev/null
@@ -0,0 +1,92 @@
+[
+  {
+    "EventName": "EXCEPTION_TAKEN",
+    "EventCode": "0x0000100",
+    "BriefDescription": "Exception taken"
+  },
+  {
+    "EventName": "INTEGER_LOAD_RETIRED",
+    "EventCode": "0x0000200",
+    "BriefDescription": "Integer load instruction retired"
+  },
+  {
+    "EventName": "INTEGER_STORE_RETIRED",
+    "EventCode": "0x0000400",
+    "BriefDescription": "Integer store instruction retired"
+  },
+  {
+    "EventName": "ATOMIC_MEMORY_RETIRED",
+    "EventCode": "0x0000800",
+    "BriefDescription": "Atomic memory operation retired"
+  },
+  {
+    "EventName": "SYSTEM_INSTRUCTION_RETIRED",
+    "EventCode": "0x0001000",
+    "BriefDescription": "System instruction retired"
+  },
+  {
+    "EventName": "INTEGER_ARITHMETIC_RETIRED",
+    "EventCode": "0x0002000",
+    "BriefDescription": "Integer arithmetic instruction retired"
+  },
+  {
+    "EventName": "CONDITIONAL_BRANCH_RETIRED",
+    "EventCode": "0x0004000",
+    "BriefDescription": "Conditional branch retired"
+  },
+  {
+    "EventName": "JAL_INSTRUCTION_RETIRED",
+    "EventCode": "0x0008000",
+    "BriefDescription": "JAL instruction retired"
+  },
+  {
+    "EventName": "JALR_INSTRUCTION_RETIRED",
+    "EventCode": "0x0010000",
+    "BriefDescription": "JALR instruction retired"
+  },
+  {
+    "EventName": "INTEGER_MULTIPLICATION_RETIRED",
+    "EventCode": "0x0020000",
+    "BriefDescription": "Integer multiplication instruction retired"
+  },
+  {
+    "EventName": "INTEGER_DIVISION_RETIRED",
+    "EventCode": "0x0040000",
+    "BriefDescription": "Integer division instruction retired"
+  },
+  {
+    "EventName": "FP_LOAD_RETIRED",
+    "EventCode": "0x0080000",
+    "BriefDescription": "Floating-point load instruction retired"
+  },
+  {
+    "EventName": "FP_STORE_RETIRED",
+    "EventCode": "0x0100000",
+    "BriefDescription": "Floating-point store instruction retired"
+  },
+  {
+    "EventName": "FP_ADDITION_RETIRED",
+    "EventCode": "0x0200000",
+    "BriefDescription": "Floating-point addition retired"
+  },
+  {
+    "EventName": "FP_MULTIPLICATION_RETIRED",
+    "EventCode": "0x0400000",
+    "BriefDescription": "Floating-point multiplication retired"
+  },
+  {
+    "EventName": "FP_FUSEDMADD_RETIRED",
+    "EventCode": "0x0800000",
+    "BriefDescription": "Floating-point fused multiply-add retired"
+  },
+  {
+    "EventName": "FP_DIV_SQRT_RETIRED",
+    "EventCode": "0x1000000",
+    "BriefDescription": "Floating-point division or square-root retired"
+  },
+  {
+    "EventName": "OTHER_FP_RETIRED",
+    "EventCode": "0x2000000",
+    "BriefDescription": "Other floating-point instruction retired"
+  }
+]
\ No newline at end of file
diff --git a/tools/perf/pmu-events/arch/riscv/sifive/u74/memory.json b/tools/perf/pmu-events/arch/riscv/sifive/u74/memory.json
new file mode 100644 (file)
index 0000000..be1a463
--- /dev/null
@@ -0,0 +1,32 @@
+[
+  {
+    "EventName": "ICACHE_RETIRED",
+    "EventCode": "0x0000102",
+    "BriefDescription": "Instruction cache miss"
+  },
+  {
+    "EventName": "DCACHE_MISS_MMIO_ACCESSES",
+    "EventCode": "0x0000202",
+    "BriefDescription": "Data cache miss or memory-mapped I/O access"
+  },
+  {
+    "EventName": "DCACHE_WRITEBACK",
+    "EventCode": "0x0000402",
+    "BriefDescription": "Data cache write-back"
+  },
+  {
+    "EventName": "INST_TLB_MISS",
+    "EventCode": "0x0000802",
+    "BriefDescription": "Instruction TLB miss"
+  },
+  {
+    "EventName": "DATA_TLB_MISS",
+    "EventCode": "0x0001002",
+    "BriefDescription": "Data TLB miss"
+  },
+  {
+    "EventName": "UTLB_MISS",
+    "EventCode": "0x0002002",
+    "BriefDescription": "UTLB miss"
+  }
+]
\ No newline at end of file
diff --git a/tools/perf/pmu-events/arch/riscv/sifive/u74/microarch.json b/tools/perf/pmu-events/arch/riscv/sifive/u74/microarch.json
new file mode 100644 (file)
index 0000000..50ffa55
--- /dev/null
@@ -0,0 +1,57 @@
+[
+  {
+    "EventName": "ADDRESSGEN_INTERLOCK",
+    "EventCode": "0x0000101",
+    "BriefDescription": "Address-generation interlock"
+  },
+  {
+    "EventName": "LONGLAT_INTERLOCK",
+    "EventCode": "0x0000201",
+    "BriefDescription": "Long-latency interlock"
+  },
+  {
+    "EventName": "CSR_READ_INTERLOCK",
+    "EventCode": "0x0000401",
+    "BriefDescription": "CSR read interlock"
+  },
+  {
+    "EventName": "ICACHE_ITIM_BUSY",
+    "EventCode": "0x0000801",
+    "BriefDescription": "Instruction cache/ITIM busy"
+  },
+  {
+    "EventName": "DCACHE_DTIM_BUSY",
+    "EventCode": "0x0001001",
+    "BriefDescription": "Data cache/DTIM busy"
+  },
+  {
+    "EventName": "BRANCH_DIRECTION_MISPREDICTION",
+    "EventCode": "0x0002001",
+    "BriefDescription": "Branch direction misprediction"
+  },
+  {
+    "EventName": "BRANCH_TARGET_MISPREDICTION",
+    "EventCode": "0x0004001",
+    "BriefDescription": "Branch/jump target misprediction"
+  },
+  {
+    "EventName": "PIPE_FLUSH_CSR_WRITE",
+    "EventCode": "0x0008001",
+    "BriefDescription": "Pipeline flush from CSR write"
+  },
+  {
+    "EventName": "PIPE_FLUSH_OTHER_EVENT",
+    "EventCode": "0x0010001",
+    "BriefDescription": "Pipeline flush from other event"
+  },
+  {
+    "EventName": "INTEGER_MULTIPLICATION_INTERLOCK",
+    "EventCode": "0x0020001",
+    "BriefDescription": "Integer multiplication interlock"
+  },
+  {
+    "EventName": "FP_INTERLOCK",
+    "EventCode": "0x0040001",
+    "BriefDescription": "Floating-point interlock"
+  }
+]
\ No newline at end of file