RISC-V Updates for the 4.19 Merge Window
This tag contains some major improvements to the RISC-V port, including the necessary interrupt controller and timer support to actually make it to userspace. Support for three devices has been added: * Support for the ISA-mandated timers on RISC-V systems. * Support for the ISA-mandated first-level interrupt controller on RISC-V systems, which is handled as part of our core arch code because it's very small and tightly tied to the ISA. * Support for SiFive's platform-level interrupt controller, which talks to the actual devices. In addition to these new devices, there are a handful of cleanups all over the RISC-V tree: * Build fixes for various configurations * A fix to the vDSO build's makefile so it respects CFLAGS. * The addition of __lshrti3, a libgcc derived function necessary for some 32-bit configurations. * !SMP && PERF_EVENTS * Cleanups to the arch code to remove the remnants of old versions of the drivers that were just properly submitted. * Some dead code from the timer driver, most of which wasn't ever even compiled. * Cleanups of some interrupt #defines, which are now local to the interrupt handling code. * Fixes to ptrace(), which while not being sufficient to fully make GDB work are at least sufficient to get simple GDB tasks to work. * Early printk support via RISC-V's architecturally mandated SBI console device. * A fix to our early debug trap handler to ensure it's always aligned. These patches have all been through a fairly extensive review process, but as this enables a whole pile of functionality (ie, userspace) I'm confident we'll need to submit a few more patches. The only concrete issues I know about are the sys_riscv_flush_icache patches, but as I managed to screw those up on Friday I figured it'd be best to let them bake another week. This tag boots a Fedora root filesystem on QEMU's master branch for me, and before this morning's rebase (from 4.18-rc8 to 4.18) it booted on the HiFive Unleashed. Thanks to Christoph Hellwig and the other guys at WD for getting the new drivers in shape! -----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEAM520YNJYN/OiG3470yhUCzLq0EFAltx3HcTHHBhbG1lckBk YWJiZWx0LmNvbQAKCRDvTKFQLMurQc7nEACh8NCRLyXHOAQefomb+BUx+DJXweau lhTiPexB7+3ZAT6FvL8BgHFu3qMsgZ8iI5pxIz7tap2WRTlakRABLes7c3xQPI4a 3rDbZFE78lQDNY0Kj8iUpvYr0aOfMcC8aoD30qQHaWZVgYZvaZGD3Sar6VbTyaNe 5F5lRaiAtrMmHNio/fXQvnMP83nc1Nxzc4q8VeRjmufc0CvGZUs3L2ZRVx1phwav VedQFsrNHlcyulBv9rQXzaeyvVn+FNKlu4c/9sI6xsGZofGZjOqub1vjURuEfTc5 4AtdFMN0Xb2TYCK277Fr/FY/VEHGXCV+3hGc2U62hnpBtRgGERn7gQUimCJD5b+V gpXZGjtLvTXp9a4N6+ThC/oqvr72aLzInNap95MFK5xSMx/4AdCG7u63sd2qLtkL tlYho+Hd50ImIlUCTs6pfjzmgTMLW2huVJhDNx2lt9OUvNNYjTc4mjEK2WK8DUC7 aUMcHYZMn3hJFNwvd5xTxLPua4ahhhYTyfzHwnMiND4ZjdUnxtrKNj46HjSPqMp9 mgKOkv3G0a021gYODI/dweYI1SV2my814fQHZW4rcFYM2lLwrn2cPMMGezAJF9sR mbLHW6ZxJrtd9m+RZsJB9Z3QnBs68yIqTOBPRRFM5egwt9s9y+19HnBDVe1hj8/j OpmZ/qXCqQt+jA== =PfnC -----END PGP SIGNATURE----- Merge tag 'riscv-for-linus-4.19-mw0' of git://git.kernel.org/pub/scm/linux/kernel/git/palmer/riscv-linux Pull RISC-V updates from Palmer Dabbelt: "This contains some major improvements to the RISC-V port, including the necessary interrupt controller and timer support to actually make it to userspace. Support for three devices has been added: - the ISA-mandated timers on RISC-V systems. - the ISA-mandated first-level interrupt controller on RISC-V systems, which is handled as part of our core arch code because it's very small and tightly tied to the ISA. - SiFive's platform-level interrupt controller, which talks to the actual devices. In addition to these new devices, there are a handful of cleanups all over the RISC-V tree: - build fixes for various configurations: * A fix to the vDSO build's makefile so it respects CFLAGS. * The addition of __lshrti3, a libgcc derived function necessary for some 32-bit configurations. * !SMP && PERF_EVENTS - Cleanups to the arch code to remove the remnants of old versions of the drivers that were just properly submitted. * Some dead code from the timer driver, most of which wasn't ever even compiled. * Cleanups of some interrupt #defines, which are now local to the interrupt handling code. - Fixes to ptrace(), which while not being sufficient to fully make GDB work are at least sufficient to get simple GDB tasks to work. - Early printk support via RISC-V's architecturally mandated SBI console device. - A fix to our early debug trap handler to ensure it's always aligned. These patches have all been through a fairly extensive review process, but as this enables a whole pile of functionality (ie, userspace) I'm confident we'll need to submit a few more patches. The only concrete issues I know about are the sys_riscv_flush_icache patches, but as I managed to screw those up on Friday I figured it'd be best to let them bake another week. This tag boots a Fedora root filesystem on QEMU's master branch for me, and before this morning's rebase (from 4.18-rc8 to 4.18) it booted on the HiFive Unleashed. Thanks to Christoph Hellwig and the other guys at WD for getting the new drivers in shape!" * tag 'riscv-for-linus-4.19-mw0' of git://git.kernel.org/pub/scm/linux/kernel/git/palmer/riscv-linux: dt-bindings: interrupt-controller: SiFive Plaform Level Interrupt Controller dt-bindings: interrupt-controller: RISC-V local interrupt controller RISC-V: Fix !CONFIG_SMP compilation error irqchip: add a SiFive PLIC driver RISC-V: Add the directive for alignment of stvec's value clocksource: new RISC-V SBI timer driver RISC-V: implement low-level interrupt handling RISC-V: add a definition for the SIE SEIE bit RISC-V: remove INTERRUPT_CAUSE_* defines from asm/irq.h RISC-V: simplify software interrupt / IPI code RISC-V: remove timer leftovers RISC-V: Add early printk support via the SBI console RISC-V: Don't increment sepc after breakpoint. RISC-V: implement __lshrti3. RISC-V: Use KBUILD_CFLAGS instead of KCFLAGS when building the vDSO
This commit is contained in:
commit
1009aa1205
@ -0,0 +1,44 @@
|
||||
RISC-V Hart-Level Interrupt Controller (HLIC)
|
||||
---------------------------------------------
|
||||
|
||||
RISC-V cores include Control Status Registers (CSRs) which are local to each
|
||||
CPU core (HART in RISC-V terminology) and can be read or written by software.
|
||||
Some of these CSRs are used to control local interrupts connected to the core.
|
||||
Every interrupt is ultimately routed through a hart's HLIC before it
|
||||
interrupts that hart.
|
||||
|
||||
The RISC-V supervisor ISA manual specifies three interrupt sources that are
|
||||
attached to every HLIC: software interrupts, the timer interrupt, and external
|
||||
interrupts. Software interrupts are used to send IPIs between cores. The
|
||||
timer interrupt comes from an architecturally mandated real-time timer that is
|
||||
controller via Supervisor Binary Interface (SBI) calls and CSR reads. External
|
||||
interrupts connect all other device interrupts to the HLIC, which are routed
|
||||
via the platform-level interrupt controller (PLIC).
|
||||
|
||||
All RISC-V systems that conform to the supervisor ISA specification are
|
||||
required to have a HLIC with these three interrupt sources present. Since the
|
||||
interrupt map is defined by the ISA it's not listed in the HLIC's device tree
|
||||
entry, though external interrupt controllers (like the PLIC, for example) will
|
||||
need to define how their interrupts map to the relevant HLICs. This means
|
||||
a PLIC interrupt property will typically list the HLICs for all present HARTs
|
||||
in the system.
|
||||
|
||||
Required properties:
|
||||
- compatible : "riscv,cpu-intc"
|
||||
- #interrupt-cells : should be <1>
|
||||
- interrupt-controller : Identifies the node as an interrupt controller
|
||||
|
||||
Furthermore, this interrupt-controller MUST be embedded inside the cpu
|
||||
definition of the hart whose CSRs control these local interrupts.
|
||||
|
||||
An example device tree entry for a HLIC is show below.
|
||||
|
||||
cpu1: cpu@1 {
|
||||
compatible = "riscv";
|
||||
...
|
||||
cpu1-intc: interrupt-controller {
|
||||
#interrupt-cells = <1>;
|
||||
compatible = "riscv,cpu-intc", "sifive,fu540-c000-cpu-intc";
|
||||
interrupt-controller;
|
||||
};
|
||||
};
|
@ -0,0 +1,58 @@
|
||||
SiFive Platform-Level Interrupt Controller (PLIC)
|
||||
-------------------------------------------------
|
||||
|
||||
SiFive SOCs include an implementation of the Platform-Level Interrupt Controller
|
||||
(PLIC) high-level specification in the RISC-V Privileged Architecture
|
||||
specification. The PLIC connects all external interrupts in the system to all
|
||||
hart contexts in the system, via the external interrupt source in each hart.
|
||||
|
||||
A hart context is a privilege mode in a hardware execution thread. For example,
|
||||
in an 4 core system with 2-way SMT, you have 8 harts and probably at least two
|
||||
privilege modes per hart; machine mode and supervisor mode.
|
||||
|
||||
Each interrupt can be enabled on per-context basis. Any context can claim
|
||||
a pending enabled interrupt and then release it once it has been handled.
|
||||
|
||||
Each interrupt has a configurable priority. Higher priority interrupts are
|
||||
serviced first. Each context can specify a priority threshold. Interrupts
|
||||
with priority below this threshold will not cause the PLIC to raise its
|
||||
interrupt line leading to the context.
|
||||
|
||||
While the PLIC supports both edge-triggered and level-triggered interrupts,
|
||||
interrupt handlers are oblivious to this distinction and therefore it is not
|
||||
specified in the PLIC device-tree binding.
|
||||
|
||||
While the RISC-V ISA doesn't specify a memory layout for the PLIC, the
|
||||
"sifive,plic-1.0.0" device is a concrete implementation of the PLIC that
|
||||
contains a specific memory layout, which is documented in chapter 8 of the
|
||||
SiFive U5 Coreplex Series Manual <https://static.dev.sifive.com/U54-MC-RVCoreIP.pdf>.
|
||||
|
||||
Required properties:
|
||||
- compatible : "sifive,plic-1.0.0" and a string identifying the actual
|
||||
detailed implementation in case that specific bugs need to be worked around.
|
||||
- #address-cells : should be <0> or more.
|
||||
- #interrupt-cells : should be <1> or more.
|
||||
- interrupt-controller : Identifies the node as an interrupt controller.
|
||||
- reg : Should contain 1 register range (address and length).
|
||||
- interrupts-extended : Specifies which contexts are connected to the PLIC,
|
||||
with "-1" specifying that a context is not present. Each node pointed
|
||||
to should be a riscv,cpu-intc node, which has a riscv node as parent.
|
||||
- riscv,ndev: Specifies how many external interrupts are supported by
|
||||
this controller.
|
||||
|
||||
Example:
|
||||
|
||||
plic: interrupt-controller@c000000 {
|
||||
#address-cells = <0>;
|
||||
#interrupt-cells = <1>;
|
||||
compatible = "sifive,plic-1.0.0", "sifive,fu540-c000-plic";
|
||||
interrupt-controller;
|
||||
interrupts-extended = <
|
||||
&cpu0-intc 11
|
||||
&cpu1-intc 11 &cpu1-intc 9
|
||||
&cpu2-intc 11 &cpu2-intc 9
|
||||
&cpu3-intc 11 &cpu3-intc 9
|
||||
&cpu4-intc 11 &cpu4-intc 9>;
|
||||
reg = <0xc000000 0x4000000>;
|
||||
riscv,ndev = <10>;
|
||||
};
|
@ -25,6 +25,9 @@ ifeq ($(CONFIG_ARCH_RV64I),y)
|
||||
|
||||
KBUILD_CFLAGS += -mabi=lp64
|
||||
KBUILD_AFLAGS += -mabi=lp64
|
||||
|
||||
KBUILD_CFLAGS += $(call cc-ifversion, -ge, 0500, -DCONFIG_ARCH_SUPPORTS_INT128)
|
||||
|
||||
KBUILD_MARCH = rv64im
|
||||
LDFLAGS += -melf64lriscv
|
||||
else
|
||||
|
@ -76,3 +76,4 @@ CONFIG_ROOT_NFS=y
|
||||
CONFIG_CRYPTO_USER_API_HASH=y
|
||||
CONFIG_MODULES=y
|
||||
CONFIG_MODULE_UNLOAD=y
|
||||
CONFIG_SIFIVE_PLIC=y
|
||||
|
@ -54,6 +54,7 @@
|
||||
/* Interrupt Enable and Interrupt Pending flags */
|
||||
#define SIE_SSIE _AC(0x00000002, UL) /* Software Interrupt Enable */
|
||||
#define SIE_STIE _AC(0x00000020, UL) /* Timer Interrupt Enable */
|
||||
#define SIE_SEIE _AC(0x00000200, UL) /* External Interrupt Enable */
|
||||
|
||||
#define EXC_INST_MISALIGNED 0
|
||||
#define EXC_INST_ACCESS 1
|
||||
|
@ -17,11 +17,8 @@
|
||||
|
||||
#define NR_IRQS 0
|
||||
|
||||
#define INTERRUPT_CAUSE_SOFTWARE 1
|
||||
#define INTERRUPT_CAUSE_TIMER 5
|
||||
#define INTERRUPT_CAUSE_EXTERNAL 9
|
||||
|
||||
void riscv_timer_interrupt(void);
|
||||
void riscv_software_interrupt(void);
|
||||
|
||||
#include <asm-generic/irq.h>
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#define RISCV_BASE_COUNTERS 2
|
||||
|
||||
|
@ -24,9 +24,6 @@
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
|
||||
/* SMP initialization hook for setup_arch */
|
||||
void __init init_clockevent(void);
|
||||
|
||||
/* SMP initialization hook for setup_arch */
|
||||
void __init setup_smp(void);
|
||||
|
||||
@ -44,9 +41,6 @@ void arch_send_call_function_single_ipi(int cpu);
|
||||
*/
|
||||
#define raw_smp_processor_id() (*((int*)((char*)get_current() + TASK_TI_CPU)))
|
||||
|
||||
/* Interprocessor interrupt handler */
|
||||
irqreturn_t handle_ipi(void);
|
||||
|
||||
#endif /* CONFIG_SMP */
|
||||
|
||||
#endif /* _ASM_RISCV_SMP_H */
|
||||
|
@ -168,8 +168,8 @@ ENTRY(handle_exception)
|
||||
|
||||
/* Handle interrupts */
|
||||
move a0, sp /* pt_regs */
|
||||
REG_L a1, handle_arch_irq
|
||||
jr a1
|
||||
move a1, s4 /* scause */
|
||||
tail do_IRQ
|
||||
1:
|
||||
/* Exceptions run with interrupts enabled */
|
||||
csrs sstatus, SR_SIE
|
||||
|
@ -94,6 +94,7 @@ relocate:
|
||||
or a0, a0, a1
|
||||
sfence.vma
|
||||
csrw sptbr, a0
|
||||
.align 2
|
||||
1:
|
||||
/* Set trap vector to spin forever to help debug */
|
||||
la a0, .Lsecondary_park
|
||||
@ -143,6 +144,7 @@ relocate:
|
||||
tail smp_callin
|
||||
#endif
|
||||
|
||||
.align 2
|
||||
.Lsecondary_park:
|
||||
/* We lack SMP support or have too many harts, so park this hart */
|
||||
wfi
|
||||
|
@ -1,21 +1,58 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2012 Regents of the University of California
|
||||
* Copyright (C) 2017 SiFive
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* Copyright (C) 2018 Christoph Hellwig
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqdomain.h>
|
||||
|
||||
/*
|
||||
* Possible interrupt causes:
|
||||
*/
|
||||
#define INTERRUPT_CAUSE_SOFTWARE 1
|
||||
#define INTERRUPT_CAUSE_TIMER 5
|
||||
#define INTERRUPT_CAUSE_EXTERNAL 9
|
||||
|
||||
/*
|
||||
* The high order bit of the trap cause register is always set for
|
||||
* interrupts, which allows us to differentiate them from exceptions
|
||||
* quickly. The INTERRUPT_CAUSE_* macros don't contain that bit, so we
|
||||
* need to mask it off.
|
||||
*/
|
||||
#define INTERRUPT_CAUSE_FLAG (1UL << (__riscv_xlen - 1))
|
||||
|
||||
asmlinkage void __irq_entry do_IRQ(struct pt_regs *regs, unsigned long cause)
|
||||
{
|
||||
struct pt_regs *old_regs = set_irq_regs(regs);
|
||||
|
||||
irq_enter();
|
||||
switch (cause & ~INTERRUPT_CAUSE_FLAG) {
|
||||
case INTERRUPT_CAUSE_TIMER:
|
||||
riscv_timer_interrupt();
|
||||
break;
|
||||
#ifdef CONFIG_SMP
|
||||
case INTERRUPT_CAUSE_SOFTWARE:
|
||||
/*
|
||||
* We only use software interrupts to pass IPIs, so if a non-SMP
|
||||
* system gets one, then we don't know what to do.
|
||||
*/
|
||||
riscv_software_interrupt();
|
||||
break;
|
||||
#endif
|
||||
case INTERRUPT_CAUSE_EXTERNAL:
|
||||
handle_arch_irq(regs);
|
||||
break;
|
||||
default:
|
||||
panic("unexpected interrupt cause");
|
||||
}
|
||||
irq_exit();
|
||||
|
||||
set_irq_regs(old_regs);
|
||||
}
|
||||
|
||||
void __init init_IRQ(void)
|
||||
{
|
||||
irqchip_init();
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/of.h>
|
||||
|
@ -39,6 +39,27 @@
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/thread_info.h>
|
||||
|
||||
#ifdef CONFIG_EARLY_PRINTK
|
||||
static void sbi_console_write(struct console *co, const char *buf,
|
||||
unsigned int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
if (buf[i] == '\n')
|
||||
sbi_console_putchar('\r');
|
||||
sbi_console_putchar(buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
struct console riscv_sbi_early_console_dev __initdata = {
|
||||
.name = "early",
|
||||
.write = sbi_console_write,
|
||||
.flags = CON_PRINTBUFFER | CON_BOOT | CON_ANYTIME,
|
||||
.index = -1
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DUMMY_CONSOLE
|
||||
struct screen_info screen_info = {
|
||||
.orig_video_lines = 30,
|
||||
@ -195,6 +216,12 @@ static void __init setup_bootmem(void)
|
||||
|
||||
void __init setup_arch(char **cmdline_p)
|
||||
{
|
||||
#if defined(CONFIG_EARLY_PRINTK)
|
||||
if (likely(early_console == NULL)) {
|
||||
early_console = &riscv_sbi_early_console_dev;
|
||||
register_console(early_console);
|
||||
}
|
||||
#endif
|
||||
*cmdline_p = boot_command_line;
|
||||
|
||||
parse_early_param();
|
||||
|
@ -45,7 +45,7 @@ int setup_profiling_timer(unsigned int multiplier)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
irqreturn_t handle_ipi(void)
|
||||
void riscv_software_interrupt(void)
|
||||
{
|
||||
unsigned long *pending_ipis = &ipi_data[smp_processor_id()].bits;
|
||||
|
||||
@ -60,7 +60,7 @@ irqreturn_t handle_ipi(void)
|
||||
|
||||
ops = xchg(pending_ipis, 0);
|
||||
if (ops == 0)
|
||||
return IRQ_HANDLED;
|
||||
return;
|
||||
|
||||
if (ops & (1 << IPI_RESCHEDULE))
|
||||
scheduler_ipi();
|
||||
@ -73,8 +73,6 @@ irqreturn_t handle_ipi(void)
|
||||
/* Order data access and bit testing. */
|
||||
mb();
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -104,7 +104,6 @@ asmlinkage void __init smp_callin(void)
|
||||
current->active_mm = mm;
|
||||
|
||||
trap_init();
|
||||
init_clockevent();
|
||||
notify_cpu_starting(smp_processor_id());
|
||||
set_cpu_online(smp_processor_id(), 1);
|
||||
local_flush_tlb_all();
|
||||
|
@ -13,38 +13,11 @@
|
||||
*/
|
||||
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#ifdef CONFIG_RISCV_TIMER
|
||||
#include <linux/timer_riscv.h>
|
||||
#endif
|
||||
|
||||
#include <asm/sbi.h>
|
||||
|
||||
unsigned long riscv_timebase;
|
||||
|
||||
DECLARE_PER_CPU(struct clock_event_device, riscv_clock_event);
|
||||
|
||||
void riscv_timer_interrupt(void)
|
||||
{
|
||||
#ifdef CONFIG_RISCV_TIMER
|
||||
/*
|
||||
* FIXME: This needs to be cleaned up along with the rest of the IRQ
|
||||
* handling cleanup. See irq.c for more details.
|
||||
*/
|
||||
struct clock_event_device *evdev = this_cpu_ptr(&riscv_clock_event);
|
||||
|
||||
evdev->event_handler(evdev);
|
||||
#endif
|
||||
}
|
||||
|
||||
void __init init_clockevent(void)
|
||||
{
|
||||
timer_probe();
|
||||
csr_set(sie, SIE_STIE);
|
||||
}
|
||||
|
||||
void __init time_init(void)
|
||||
{
|
||||
struct device_node *cpu;
|
||||
@ -56,6 +29,5 @@ void __init time_init(void)
|
||||
riscv_timebase = prop;
|
||||
|
||||
lpj_fine = riscv_timebase / HZ;
|
||||
|
||||
init_clockevent();
|
||||
timer_probe();
|
||||
}
|
||||
|
@ -138,7 +138,6 @@ asmlinkage void do_trap_break(struct pt_regs *regs)
|
||||
#endif /* CONFIG_GENERIC_BUG */
|
||||
|
||||
force_sig_fault(SIGTRAP, TRAP_BRKPT, (void __user *)(regs->sepc), current);
|
||||
regs->sepc += 0x4;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GENERIC_BUG
|
||||
|
@ -52,8 +52,8 @@ $(obj)/%.so: $(obj)/%.so.dbg FORCE
|
||||
# Add -lgcc so rv32 gets static muldi3 and lshrdi3 definitions.
|
||||
# Make sure only to export the intended __vdso_xxx symbol offsets.
|
||||
quiet_cmd_vdsold = VDSOLD $@
|
||||
cmd_vdsold = $(CC) $(KCFLAGS) $(call cc-option, -no-pie) -nostdlib $(SYSCFLAGS_$(@F)) \
|
||||
-Wl,-T,$(filter-out FORCE,$^) -o $@.tmp -lgcc && \
|
||||
cmd_vdsold = $(CC) $(KBUILD_CFLAGS) $(call cc-option, -no-pie) -nostdlib -nostartfiles $(SYSCFLAGS_$(@F)) \
|
||||
-Wl,-T,$(filter-out FORCE,$^) -o $@.tmp && \
|
||||
$(CROSS_COMPILE)objcopy \
|
||||
$(patsubst %, -G __vdso_%, $(vdso-syms)) $@.tmp $@
|
||||
|
||||
|
@ -2,5 +2,6 @@ lib-y += delay.o
|
||||
lib-y += memcpy.o
|
||||
lib-y += memset.o
|
||||
lib-y += uaccess.o
|
||||
lib-y += tishift.o
|
||||
|
||||
lib-$(CONFIG_32BIT) += udivdi3.o
|
||||
|
42
arch/riscv/lib/tishift.S
Normal file
42
arch/riscv/lib/tishift.S
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Free Software Foundation, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
.globl __lshrti3
|
||||
__lshrti3:
|
||||
beqz a2, .L1
|
||||
li a5,64
|
||||
sub a5,a5,a2
|
||||
addi sp,sp,-16
|
||||
sext.w a4,a5
|
||||
blez a5, .L2
|
||||
sext.w a2,a2
|
||||
sll a4,a1,a4
|
||||
srl a0,a0,a2
|
||||
srl a1,a1,a2
|
||||
or a0,a0,a4
|
||||
sd a1,8(sp)
|
||||
sd a0,0(sp)
|
||||
ld a0,0(sp)
|
||||
ld a1,8(sp)
|
||||
addi sp,sp,16
|
||||
ret
|
||||
.L1:
|
||||
ret
|
||||
.L2:
|
||||
negw a4,a4
|
||||
srl a1,a1,a4
|
||||
sd a1,0(sp)
|
||||
sd zero,8(sp)
|
||||
ld a0,0(sp)
|
||||
ld a1,8(sp)
|
||||
addi sp,sp,16
|
||||
ret
|
@ -609,4 +609,15 @@ config ATCPIT100_TIMER
|
||||
help
|
||||
This option enables support for the Andestech ATCPIT100 timers.
|
||||
|
||||
config RISCV_TIMER
|
||||
bool "Timer for the RISC-V platform"
|
||||
depends on RISCV
|
||||
default y
|
||||
select TIMER_PROBE
|
||||
select TIMER_OF
|
||||
help
|
||||
This enables the per-hart timer built into all RISC-V systems, which
|
||||
is accessed via both the SBI and the rdcycle instruction. This is
|
||||
required for all RISC-V systems.
|
||||
|
||||
endmenu
|
||||
|
@ -78,3 +78,4 @@ obj-$(CONFIG_H8300_TPU) += h8300_tpu.o
|
||||
obj-$(CONFIG_CLKSRC_ST_LPC) += clksrc_st_lpc.o
|
||||
obj-$(CONFIG_X86_NUMACHIP) += numachip.o
|
||||
obj-$(CONFIG_ATCPIT100_TIMER) += timer-atcpit100.o
|
||||
obj-$(CONFIG_RISCV_TIMER) += riscv_timer.o
|
||||
|
105
drivers/clocksource/riscv_timer.c
Normal file
105
drivers/clocksource/riscv_timer.c
Normal file
@ -0,0 +1,105 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2012 Regents of the University of California
|
||||
* Copyright (C) 2017 SiFive
|
||||
*/
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/irq.h>
|
||||
#include <asm/sbi.h>
|
||||
|
||||
/*
|
||||
* All RISC-V systems have a timer attached to every hart. These timers can be
|
||||
* read by the 'rdcycle' pseudo instruction, and can use the SBI to setup
|
||||
* events. In order to abstract the architecture-specific timer reading and
|
||||
* setting functions away from the clock event insertion code, we provide
|
||||
* function pointers to the clockevent subsystem that perform two basic
|
||||
* operations: rdtime() reads the timer on the current CPU, and
|
||||
* next_event(delta) sets the next timer event to 'delta' cycles in the future.
|
||||
* As the timers are inherently a per-cpu resource, these callbacks perform
|
||||
* operations on the current hart. There is guaranteed to be exactly one timer
|
||||
* per hart on all RISC-V systems.
|
||||
*/
|
||||
|
||||
static int riscv_clock_next_event(unsigned long delta,
|
||||
struct clock_event_device *ce)
|
||||
{
|
||||
csr_set(sie, SIE_STIE);
|
||||
sbi_set_timer(get_cycles64() + delta);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_PER_CPU(struct clock_event_device, riscv_clock_event) = {
|
||||
.name = "riscv_timer_clockevent",
|
||||
.features = CLOCK_EVT_FEAT_ONESHOT,
|
||||
.rating = 100,
|
||||
.set_next_event = riscv_clock_next_event,
|
||||
};
|
||||
|
||||
/*
|
||||
* It is guaranteed that all the timers across all the harts are synchronized
|
||||
* within one tick of each other, so while this could technically go
|
||||
* backwards when hopping between CPUs, practically it won't happen.
|
||||
*/
|
||||
static unsigned long long riscv_clocksource_rdtime(struct clocksource *cs)
|
||||
{
|
||||
return get_cycles64();
|
||||
}
|
||||
|
||||
static DEFINE_PER_CPU(struct clocksource, riscv_clocksource) = {
|
||||
.name = "riscv_clocksource",
|
||||
.rating = 300,
|
||||
.mask = CLOCKSOURCE_MASK(BITS_PER_LONG),
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||
.read = riscv_clocksource_rdtime,
|
||||
};
|
||||
|
||||
static int riscv_timer_starting_cpu(unsigned int cpu)
|
||||
{
|
||||
struct clock_event_device *ce = per_cpu_ptr(&riscv_clock_event, cpu);
|
||||
|
||||
ce->cpumask = cpumask_of(cpu);
|
||||
clockevents_config_and_register(ce, riscv_timebase, 100, 0x7fffffff);
|
||||
|
||||
csr_set(sie, SIE_STIE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int riscv_timer_dying_cpu(unsigned int cpu)
|
||||
{
|
||||
csr_clear(sie, SIE_STIE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* called directly from the low-level interrupt handler */
|
||||
void riscv_timer_interrupt(void)
|
||||
{
|
||||
struct clock_event_device *evdev = this_cpu_ptr(&riscv_clock_event);
|
||||
|
||||
csr_clear(sie, SIE_STIE);
|
||||
evdev->event_handler(evdev);
|
||||
}
|
||||
|
||||
static int __init riscv_timer_init_dt(struct device_node *n)
|
||||
{
|
||||
int cpu_id = riscv_of_processor_hart(n), error;
|
||||
struct clocksource *cs;
|
||||
|
||||
if (cpu_id != smp_processor_id())
|
||||
return 0;
|
||||
|
||||
cs = per_cpu_ptr(&riscv_clocksource, cpu_id);
|
||||
clocksource_register_hz(cs, riscv_timebase);
|
||||
|
||||
error = cpuhp_setup_state(CPUHP_AP_RISCV_TIMER_STARTING,
|
||||
"clockevents/riscv/timer:starting",
|
||||
riscv_timer_starting_cpu, riscv_timer_dying_cpu);
|
||||
if (error)
|
||||
pr_err("RISCV timer register failed [%d] for cpu = [%d]\n",
|
||||
error, cpu_id);
|
||||
return error;
|
||||
}
|
||||
|
||||
TIMER_OF_DECLARE(riscv_timer, "riscv", riscv_timer_init_dt);
|
@ -372,3 +372,15 @@ config QCOM_PDC
|
||||
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
|
||||
|
||||
endmenu
|
||||
|
||||
config SIFIVE_PLIC
|
||||
bool "SiFive Platform-Level Interrupt Controller"
|
||||
depends on RISCV
|
||||
help
|
||||
This enables support for the PLIC chip found in SiFive (and
|
||||
potentially other) RISC-V systems. The PLIC controls devices
|
||||
interrupts and connects them to each core's local interrupt
|
||||
controller. Aside from timer and software interrupts, all other
|
||||
interrupt sources are subordinate to the PLIC.
|
||||
|
||||
If you don't know what to do here, say Y.
|
||||
|
@ -87,3 +87,4 @@ obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o
|
||||
obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o
|
||||
obj-$(CONFIG_NDS32) += irq-ativic32.o
|
||||
obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
|
||||
obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o
|
||||
|
260
drivers/irqchip/irq-sifive-plic.c
Normal file
260
drivers/irqchip/irq-sifive-plic.c
Normal file
@ -0,0 +1,260 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2017 SiFive
|
||||
* Copyright (C) 2018 Christoph Hellwig
|
||||
*/
|
||||
#define pr_fmt(fmt) "plic: " fmt
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
/*
|
||||
* This driver implements a version of the RISC-V PLIC with the actual layout
|
||||
* specified in chapter 8 of the SiFive U5 Coreplex Series Manual:
|
||||
*
|
||||
* https://static.dev.sifive.com/U54-MC-RVCoreIP.pdf
|
||||
*
|
||||
* The largest number supported by devices marked as 'sifive,plic-1.0.0', is
|
||||
* 1024, of which device 0 is defined as non-existent by the RISC-V Privileged
|
||||
* Spec.
|
||||
*/
|
||||
|
||||
#define MAX_DEVICES 1024
|
||||
#define MAX_CONTEXTS 15872
|
||||
|
||||
/*
|
||||
* Each interrupt source has a priority register associated with it.
|
||||
* We always hardwire it to one in Linux.
|
||||
*/
|
||||
#define PRIORITY_BASE 0
|
||||
#define PRIORITY_PER_ID 4
|
||||
|
||||
/*
|
||||
* Each hart context has a vector of interrupt enable bits associated with it.
|
||||
* There's one bit for each interrupt source.
|
||||
*/
|
||||
#define ENABLE_BASE 0x2000
|
||||
#define ENABLE_PER_HART 0x80
|
||||
|
||||
/*
|
||||
* Each hart context has a set of control registers associated with it. Right
|
||||
* now there's only two: a source priority threshold over which the hart will
|
||||
* take an interrupt, and a register to claim interrupts.
|
||||
*/
|
||||
#define CONTEXT_BASE 0x200000
|
||||
#define CONTEXT_PER_HART 0x1000
|
||||
#define CONTEXT_THRESHOLD 0x00
|
||||
#define CONTEXT_CLAIM 0x04
|
||||
|
||||
static void __iomem *plic_regs;
|
||||
|
||||
struct plic_handler {
|
||||
bool present;
|
||||
int ctxid;
|
||||
};
|
||||
static DEFINE_PER_CPU(struct plic_handler, plic_handlers);
|
||||
|
||||
static inline void __iomem *plic_hart_offset(int ctxid)
|
||||
{
|
||||
return plic_regs + CONTEXT_BASE + ctxid * CONTEXT_PER_HART;
|
||||
}
|
||||
|
||||
static inline u32 __iomem *plic_enable_base(int ctxid)
|
||||
{
|
||||
return plic_regs + ENABLE_BASE + ctxid * ENABLE_PER_HART;
|
||||
}
|
||||
|
||||
/*
|
||||
* Protect mask operations on the registers given that we can't assume that
|
||||
* atomic memory operations work on them.
|
||||
*/
|
||||
static DEFINE_RAW_SPINLOCK(plic_toggle_lock);
|
||||
|
||||
static inline void plic_toggle(int ctxid, int hwirq, int enable)
|
||||
{
|
||||
u32 __iomem *reg = plic_enable_base(ctxid) + (hwirq / 32);
|
||||
u32 hwirq_mask = 1 << (hwirq % 32);
|
||||
|
||||
raw_spin_lock(&plic_toggle_lock);
|
||||
if (enable)
|
||||
writel(readl(reg) | hwirq_mask, reg);
|
||||
else
|
||||
writel(readl(reg) & ~hwirq_mask, reg);
|
||||
raw_spin_unlock(&plic_toggle_lock);
|
||||
}
|
||||
|
||||
static inline void plic_irq_toggle(struct irq_data *d, int enable)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
writel(enable, plic_regs + PRIORITY_BASE + d->hwirq * PRIORITY_PER_ID);
|
||||
for_each_cpu(cpu, irq_data_get_affinity_mask(d)) {
|
||||
struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);
|
||||
|
||||
if (handler->present)
|
||||
plic_toggle(handler->ctxid, d->hwirq, enable);
|
||||
}
|
||||
}
|
||||
|
||||
static void plic_irq_enable(struct irq_data *d)
|
||||
{
|
||||
plic_irq_toggle(d, 1);
|
||||
}
|
||||
|
||||
static void plic_irq_disable(struct irq_data *d)
|
||||
{
|
||||
plic_irq_toggle(d, 0);
|
||||
}
|
||||
|
||||
static struct irq_chip plic_chip = {
|
||||
.name = "SiFive PLIC",
|
||||
/*
|
||||
* There is no need to mask/unmask PLIC interrupts. They are "masked"
|
||||
* by reading claim and "unmasked" when writing it back.
|
||||
*/
|
||||
.irq_enable = plic_irq_enable,
|
||||
.irq_disable = plic_irq_disable,
|
||||
};
|
||||
|
||||
static int plic_irqdomain_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
irq_set_chip_and_handler(irq, &plic_chip, handle_simple_irq);
|
||||
irq_set_chip_data(irq, NULL);
|
||||
irq_set_noprobe(irq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops plic_irqdomain_ops = {
|
||||
.map = plic_irqdomain_map,
|
||||
.xlate = irq_domain_xlate_onecell,
|
||||
};
|
||||
|
||||
static struct irq_domain *plic_irqdomain;
|
||||
|
||||
/*
|
||||
* Handling an interrupt is a two-step process: first you claim the interrupt
|
||||
* by reading the claim register, then you complete the interrupt by writing
|
||||
* that source ID back to the same claim register. This automatically enables
|
||||
* and disables the interrupt, so there's nothing else to do.
|
||||
*/
|
||||
static void plic_handle_irq(struct pt_regs *regs)
|
||||
{
|
||||
struct plic_handler *handler = this_cpu_ptr(&plic_handlers);
|
||||
void __iomem *claim = plic_hart_offset(handler->ctxid) + CONTEXT_CLAIM;
|
||||
irq_hw_number_t hwirq;
|
||||
|
||||
WARN_ON_ONCE(!handler->present);
|
||||
|
||||
csr_clear(sie, SIE_SEIE);
|
||||
while ((hwirq = readl(claim))) {
|
||||
int irq = irq_find_mapping(plic_irqdomain, hwirq);
|
||||
|
||||
if (unlikely(irq <= 0))
|
||||
pr_warn_ratelimited("can't find mapping for hwirq %lu\n",
|
||||
hwirq);
|
||||
else
|
||||
generic_handle_irq(irq);
|
||||
writel(hwirq, claim);
|
||||
}
|
||||
csr_set(sie, SIE_SEIE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk up the DT tree until we find an active RISC-V core (HART) node and
|
||||
* extract the cpuid from it.
|
||||
*/
|
||||
static int plic_find_hart_id(struct device_node *node)
|
||||
{
|
||||
for (; node; node = node->parent) {
|
||||
if (of_device_is_compatible(node, "riscv"))
|
||||
return riscv_of_processor_hart(node);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int __init plic_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
int error = 0, nr_handlers, nr_mapped = 0, i;
|
||||
u32 nr_irqs;
|
||||
|
||||
if (plic_regs) {
|
||||
pr_warn("PLIC already present.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
plic_regs = of_iomap(node, 0);
|
||||
if (WARN_ON(!plic_regs))
|
||||
return -EIO;
|
||||
|
||||
error = -EINVAL;
|
||||
of_property_read_u32(node, "riscv,ndev", &nr_irqs);
|
||||
if (WARN_ON(!nr_irqs))
|
||||
goto out_iounmap;
|
||||
|
||||
nr_handlers = of_irq_count(node);
|
||||
if (WARN_ON(!nr_handlers))
|
||||
goto out_iounmap;
|
||||
if (WARN_ON(nr_handlers < num_possible_cpus()))
|
||||
goto out_iounmap;
|
||||
|
||||
error = -ENOMEM;
|
||||
plic_irqdomain = irq_domain_add_linear(node, nr_irqs + 1,
|
||||
&plic_irqdomain_ops, NULL);
|
||||
if (WARN_ON(!plic_irqdomain))
|
||||
goto out_iounmap;
|
||||
|
||||
for (i = 0; i < nr_handlers; i++) {
|
||||
struct of_phandle_args parent;
|
||||
struct plic_handler *handler;
|
||||
irq_hw_number_t hwirq;
|
||||
int cpu;
|
||||
|
||||
if (of_irq_parse_one(node, i, &parent)) {
|
||||
pr_err("failed to parse parent for context %d.\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* skip context holes */
|
||||
if (parent.args[0] == -1)
|
||||
continue;
|
||||
|
||||
cpu = plic_find_hart_id(parent.np);
|
||||
if (cpu < 0) {
|
||||
pr_warn("failed to parse hart ID for context %d.\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
handler = per_cpu_ptr(&plic_handlers, cpu);
|
||||
handler->present = true;
|
||||
handler->ctxid = i;
|
||||
|
||||
/* priority must be > threshold to trigger an interrupt */
|
||||
writel(0, plic_hart_offset(i) + CONTEXT_THRESHOLD);
|
||||
for (hwirq = 1; hwirq <= nr_irqs; hwirq++)
|
||||
plic_toggle(i, hwirq, 0);
|
||||
nr_mapped++;
|
||||
}
|
||||
|
||||
pr_info("mapped %d interrupts to %d (out of %d) handlers.\n",
|
||||
nr_irqs, nr_mapped, nr_handlers);
|
||||
set_handle_irq(plic_handle_irq);
|
||||
return 0;
|
||||
|
||||
out_iounmap:
|
||||
iounmap(plic_regs);
|
||||
return error;
|
||||
}
|
||||
|
||||
IRQCHIP_DECLARE(sifive_plic, "sifive,plic-1.0.0", plic_init);
|
||||
IRQCHIP_DECLARE(riscv_plic0, "riscv,plic0", plic_init); /* for legacy systems */
|
@ -125,6 +125,7 @@ enum cpuhp_state {
|
||||
CPUHP_AP_MARCO_TIMER_STARTING,
|
||||
CPUHP_AP_MIPS_GIC_TIMER_STARTING,
|
||||
CPUHP_AP_ARC_TIMER_STARTING,
|
||||
CPUHP_AP_RISCV_TIMER_STARTING,
|
||||
CPUHP_AP_KVM_STARTING,
|
||||
CPUHP_AP_KVM_ARM_VGIC_INIT_STARTING,
|
||||
CPUHP_AP_KVM_ARM_VGIC_STARTING,
|
||||
|
Loading…
Reference in New Issue
Block a user