* [PATCH v6 09/16] arm64: uaccess: Add PAN helper
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
Add __uaccess_{en,dis}able_hw_pan() helpers to set/clear the PSTATE.PAN
bit.
Signed-off-by: James Morse <james.morse@arm.com>
---
arch/arm64/include/asm/uaccess.h | 12 ++++++++++++
arch/arm64/kernel/suspend.c | 4 ++--
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/uaccess.h b/arch/arm64/include/asm/uaccess.h
index 6eadf55ebaf0..3821fab01d7d 100644
--- a/arch/arm64/include/asm/uaccess.h
+++ b/arch/arm64/include/asm/uaccess.h
@@ -168,6 +168,18 @@ static inline bool uaccess_ttbr0_enable(void)
}
#endif
+static inline void __uaccess_disable_hw_pan(void)
+{
+ asm(ALTERNATIVE("nop", SET_PSTATE_PAN(0), ARM64_HAS_PAN,
+ CONFIG_ARM64_PAN));
+}
+
+static inline void __uaccess_enable_hw_pan(void)
+{
+ asm(ALTERNATIVE("nop", SET_PSTATE_PAN(1), ARM64_HAS_PAN,
+ CONFIG_ARM64_PAN));
+}
+
#define __uaccess_disable(alt) \
do { \
if (!uaccess_ttbr0_disable()) \
diff --git a/arch/arm64/kernel/suspend.c b/arch/arm64/kernel/suspend.c
index 3fe5ad884418..a307b9e13392 100644
--- a/arch/arm64/kernel/suspend.c
+++ b/arch/arm64/kernel/suspend.c
@@ -2,6 +2,7 @@
#include <linux/ftrace.h>
#include <linux/percpu.h>
#include <linux/slab.h>
+#include <linux/uaccess.h>
#include <asm/alternative.h>
#include <asm/cacheflush.h>
#include <asm/cpufeature.h>
@@ -51,8 +52,7 @@ void notrace __cpu_suspend_exit(void)
* PSTATE was not saved over suspend/resume, re-enable any detected
* features that might not have been set correctly.
*/
- asm(ALTERNATIVE("nop", SET_PSTATE_PAN(1), ARM64_HAS_PAN,
- CONFIG_ARM64_PAN));
+ __uaccess_enable_hw_pan();
uao_thread_switch(current);
/*
--
2.15.0
^ permalink raw reply related
* [PATCH v6 10/16] arm64: kernel: Add arch-specific SDEI entry code and CPU masking
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
The Software Delegated Exception Interface (SDEI) is an ARM standard
for registering callbacks from the platform firmware into the OS.
This is typically used to implement RAS notifications.
Such notifications enter the kernel at the registered entry-point
with the register values of the interrupted CPU context. Because this
is not a CPU exception, it cannot reuse the existing entry code.
(crucially we don't implicitly know which exception level we interrupted),
Add the entry point to entry.S to set us up for calling into C code. If
the event interrupted code that had interrupts masked, we always return
to that location. Otherwise we pretend this was an IRQ, and use SDEI's
complete_and_resume call to return to vbar_el1 + offset.
This allows the kernel to deliver signals to user space processes. For
KVM this triggers the world switch, a quick spin round vcpu_run, then
back into the guest, unless there are pending signals.
Add sdei_mask_local_cpu() calls to the smp_send_stop() code, this covers
the panic() code-path, which doesn't invoke cpuhotplug notifiers.
Because we can interrupt entry-from/exit-to another EL, we can't trust the
value in sp_el0 or x29, even if we interrupted the kernel, in this case
the code in entry.S will save/restore sp_el0 and use the value in
__entry_task.
When we have VMAP stacks we can interrupt the stack-overflow test, which
stirs x0 into sp, meaning we have to have our own VMAP stacks. For now
these are allocated when we probe the interface. Future patches will add
refcounting hooks to allow the arch code to allocate them lazily.
Signed-off-by: James Morse <james.morse@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
---
Changes since v4:
* Moved asm code into kernel/entry.S to make the kaiser interaction smaller
* Added preempt.h for in_nmi() declaration
* Made use of __uaccess_enable_hw_pan()
* Forced GFP_KERNEL means we can lazily allocate stacks, fixed comments saying
we can't do this, patches to follow...
Changes since v3:
* Added const to clobbered_registers,
* Removed extern from C function declarations.
* Header file names changed
arch/arm64/include/asm/sdei.h | 47 +++++++-
arch/arm64/include/asm/stacktrace.h | 3 +
arch/arm64/kernel/Makefile | 1 +
arch/arm64/kernel/asm-offsets.c | 5 +
arch/arm64/kernel/entry.S | 101 +++++++++++++++++
arch/arm64/kernel/sdei.c | 219 ++++++++++++++++++++++++++++++++++++
arch/arm64/kernel/smp.c | 11 +-
7 files changed, 384 insertions(+), 3 deletions(-)
create mode 100644 arch/arm64/kernel/sdei.c
diff --git a/arch/arm64/include/asm/sdei.h b/arch/arm64/include/asm/sdei.h
index 59f26b6e673d..d58a31ab525a 100644
--- a/arch/arm64/include/asm/sdei.h
+++ b/arch/arm64/include/asm/sdei.h
@@ -3,6 +3,49 @@
#ifndef __ASM_SDEI_H
#define __ASM_SDEI_H
-/* Later patches add the arch specific bits */
+/* Values for sdei_exit_mode */
+#define SDEI_EXIT_HVC 0
+#define SDEI_EXIT_SMC 1
-#endif /* __ASM_SDEI_H */
+#define SDEI_STACK_SIZE IRQ_STACK_SIZE
+
+#ifndef __ASSEMBLY__
+
+#include <linux/linkage.h>
+#include <linux/preempt.h>
+#include <linux/types.h>
+
+#include <asm/virt.h>
+
+extern unsigned long sdei_exit_mode;
+
+/* Software Delegated Exception entry point from firmware*/
+asmlinkage void __sdei_asm_handler(unsigned long event_num, unsigned long arg,
+ unsigned long pc, unsigned long pstate);
+
+/*
+ * The above entry point does the minimum to call C code. This function does
+ * anything else, before calling the driver.
+ */
+struct sdei_registered_event;
+asmlinkage unsigned long __sdei_handler(struct pt_regs *regs,
+ struct sdei_registered_event *arg);
+
+unsigned long sdei_arch_get_entry_point(int conduit);
+#define sdei_arch_get_entry_point(x) sdei_arch_get_entry_point(x)
+
+bool _on_sdei_stack(unsigned long sp);
+static inline bool on_sdei_stack(unsigned long sp)
+{
+ if (!IS_ENABLED(CONFIG_VMAP_STACK))
+ return false;
+ if (!IS_ENABLED(CONFIG_ARM_SDE_INTERFACE))
+ return false;
+ if (in_nmi())
+ return _on_sdei_stack(sp);
+
+ return false;
+}
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ASM_SDEI_H */
diff --git a/arch/arm64/include/asm/stacktrace.h b/arch/arm64/include/asm/stacktrace.h
index 6ad30776e984..472ef944e932 100644
--- a/arch/arm64/include/asm/stacktrace.h
+++ b/arch/arm64/include/asm/stacktrace.h
@@ -22,6 +22,7 @@
#include <asm/memory.h>
#include <asm/ptrace.h>
+#include <asm/sdei.h>
struct stackframe {
unsigned long fp;
@@ -85,6 +86,8 @@ static inline bool on_accessible_stack(struct task_struct *tsk, unsigned long sp
return true;
if (on_overflow_stack(sp))
return true;
+ if (on_sdei_stack(sp))
+ return true;
return false;
}
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 0c760db04858..b87541360f43 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -52,6 +52,7 @@ arm64-obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o \
arm64-obj-$(CONFIG_ARM64_RELOC_TEST) += arm64-reloc-test.o
arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o
arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o
+arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o
ifeq ($(CONFIG_KVM),y)
arm64-obj-$(CONFIG_HARDEN_BRANCH_PREDICTOR) += bpi.o
diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c
index af247d10252f..1dcc493f5765 100644
--- a/arch/arm64/kernel/asm-offsets.c
+++ b/arch/arm64/kernel/asm-offsets.c
@@ -18,6 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <linux/arm_sdei.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
@@ -157,6 +158,10 @@ int main(void)
BLANK();
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
DEFINE(TRAMP_VALIAS, TRAMP_VALIAS);
+#endif
+#ifdef CONFIG_ARM_SDE_INTERFACE
+ DEFINE(SDEI_EVENT_INTREGS, offsetof(struct sdei_registered_event, interrupted_regs));
+ DEFINE(SDEI_EVENT_PRIORITY, offsetof(struct sdei_registered_event, priority));
#endif
return 0;
}
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index 62500d371b06..3d26650ef1ef 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -1150,3 +1150,104 @@ ENTRY(ret_from_fork)
b ret_to_user
ENDPROC(ret_from_fork)
NOKPROBE(ret_from_fork)
+
+#ifdef CONFIG_ARM_SDE_INTERFACE
+
+#include <asm/sdei.h>
+#include <uapi/linux/arm_sdei.h>
+
+/*
+ * Software Delegated Exception entry point.
+ *
+ * x0: Event number
+ * x1: struct sdei_registered_event argument from registration time.
+ * x2: interrupted PC
+ * x3: interrupted PSTATE
+ *
+ * Firmware has preserved x0->x17 for us, we must save/restore the rest to
+ * follow SMC-CC. We save (or retrieve) all the registers as the handler may
+ * want them.
+ */
+ENTRY(__sdei_asm_handler)
+ stp x2, x3, [x1, #SDEI_EVENT_INTREGS + S_PC]
+ stp x4, x5, [x1, #SDEI_EVENT_INTREGS + 16 * 2]
+ stp x6, x7, [x1, #SDEI_EVENT_INTREGS + 16 * 3]
+ stp x8, x9, [x1, #SDEI_EVENT_INTREGS + 16 * 4]
+ stp x10, x11, [x1, #SDEI_EVENT_INTREGS + 16 * 5]
+ stp x12, x13, [x1, #SDEI_EVENT_INTREGS + 16 * 6]
+ stp x14, x15, [x1, #SDEI_EVENT_INTREGS + 16 * 7]
+ stp x16, x17, [x1, #SDEI_EVENT_INTREGS + 16 * 8]
+ stp x18, x19, [x1, #SDEI_EVENT_INTREGS + 16 * 9]
+ stp x20, x21, [x1, #SDEI_EVENT_INTREGS + 16 * 10]
+ stp x22, x23, [x1, #SDEI_EVENT_INTREGS + 16 * 11]
+ stp x24, x25, [x1, #SDEI_EVENT_INTREGS + 16 * 12]
+ stp x26, x27, [x1, #SDEI_EVENT_INTREGS + 16 * 13]
+ stp x28, x29, [x1, #SDEI_EVENT_INTREGS + 16 * 14]
+ mov x4, sp
+ stp lr, x4, [x1, #SDEI_EVENT_INTREGS + S_LR]
+
+ mov x19, x1
+
+#ifdef CONFIG_VMAP_STACK
+ /*
+ * entry.S may have been using sp as a scratch register, find whether
+ * this is a normal or critical event and switch to the appropriate
+ * stack for this CPU.
+ */
+ ldrb w4, [x19, #SDEI_EVENT_PRIORITY]
+ cbnz w4, 1f
+ ldr_this_cpu dst=x5, sym=sdei_stack_normal_ptr, tmp=x6
+ b 2f
+1: ldr_this_cpu dst=x5, sym=sdei_stack_critical_ptr, tmp=x6
+2: mov x6, #SDEI_STACK_SIZE
+ add x5, x5, x6
+ mov sp, x5
+#endif
+
+ /*
+ * We may have interrupted userspace, or a guest, or exit-from or
+ * return-to either of these. We can't trust sp_el0, restore it.
+ */
+ mrs x28, sp_el0
+ ldr_this_cpu dst=x0, sym=__entry_task, tmp=x1
+ msr sp_el0, x0
+
+ /* If we interrupted the kernel point to the previous stack/frame. */
+ and x0, x3, #0xc
+ mrs x1, CurrentEL
+ cmp x0, x1
+ csel x29, x29, xzr, eq // fp, or zero
+ csel x4, x2, xzr, eq // elr, or zero
+
+ stp x29, x4, [sp, #-16]!
+ mov x29, sp
+
+ add x0, x19, #SDEI_EVENT_INTREGS
+ mov x1, x19
+ bl __sdei_handler
+
+ msr sp_el0, x28
+ /* restore regs >x17 that we clobbered */
+ ldp x28, x29, [x19, #SDEI_EVENT_INTREGS + 16 * 14]
+ ldp lr, x4, [x19, #SDEI_EVENT_INTREGS + S_LR]
+ mov sp, x4
+ ldp x18, x19, [x19, #SDEI_EVENT_INTREGS + 16 * 9]
+
+ mov x1, x0 // address to complete_and_resume
+ /* x0 = (x0 <= 1) ? EVENT_COMPLETE:EVENT_COMPLETE_AND_RESUME */
+ cmp x0, #1
+ mov_q x2, SDEI_1_0_FN_SDEI_EVENT_COMPLETE
+ mov_q x3, SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME
+ csel x0, x2, x3, ls
+
+ /* On success, this call never returns... */
+ ldr_l x2, sdei_exit_mode
+ cmp x2, #SDEI_EXIT_SMC
+ b.ne 1f
+ smc #0
+ b .
+1: hvc #0
+ b .
+ENDPROC(__sdei_asm_handler)
+NOKPROBE(__sdei_asm_handler)
+#endif /* CONFIG_ARM_SDE_INTERFACE */
diff --git a/arch/arm64/kernel/sdei.c b/arch/arm64/kernel/sdei.c
new file mode 100644
index 000000000000..f9dffacaa5d6
--- /dev/null
+++ b/arch/arm64/kernel/sdei.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2017 Arm Ltd.
+#define pr_fmt(fmt) "sdei: " fmt
+
+#include <linux/arm_sdei.h>
+#include <linux/hardirq.h>
+#include <linux/irqflags.h>
+#include <linux/sched/task_stack.h>
+#include <linux/uaccess.h>
+
+#include <asm/alternative.h>
+#include <asm/kprobes.h>
+#include <asm/ptrace.h>
+#include <asm/sysreg.h>
+#include <asm/vmap_stack.h>
+
+unsigned long sdei_exit_mode;
+
+/*
+ * VMAP'd stacks checking for stack overflow on exception using sp as a scratch
+ * register, meaning SDEI has to switch to its own stack. We need two stacks as
+ * a critical event may interrupt a normal event that has just taken a
+ * synchronous exception, and is using sp as scratch register. For a critical
+ * event interrupting a normal event, we can't reliably tell if we were on the
+ * sdei stack.
+ * For now, we allocate stacks when the driver is probed.
+ */
+DECLARE_PER_CPU(unsigned long *, sdei_stack_normal_ptr);
+DECLARE_PER_CPU(unsigned long *, sdei_stack_critical_ptr);
+
+#ifdef CONFIG_VMAP_STACK
+DEFINE_PER_CPU(unsigned long *, sdei_stack_normal_ptr);
+DEFINE_PER_CPU(unsigned long *, sdei_stack_critical_ptr);
+#endif
+
+static void _free_sdei_stack(unsigned long * __percpu *ptr, int cpu)
+{
+ unsigned long *p;
+
+ p = per_cpu(*ptr, cpu);
+ if (p) {
+ per_cpu(*ptr, cpu) = NULL;
+ vfree(p);
+ }
+}
+
+static void free_sdei_stacks(void)
+{
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ _free_sdei_stack(&sdei_stack_normal_ptr, cpu);
+ _free_sdei_stack(&sdei_stack_critical_ptr, cpu);
+ }
+}
+
+static int _init_sdei_stack(unsigned long * __percpu *ptr, int cpu)
+{
+ unsigned long *p;
+
+ p = arch_alloc_vmap_stack(SDEI_STACK_SIZE, cpu_to_node(cpu));
+ if (!p)
+ return -ENOMEM;
+ per_cpu(*ptr, cpu) = p;
+
+ return 0;
+}
+
+static int init_sdei_stacks(void)
+{
+ int cpu;
+ int err = 0;
+
+ for_each_possible_cpu(cpu) {
+ err = _init_sdei_stack(&sdei_stack_normal_ptr, cpu);
+ if (err)
+ break;
+ err = _init_sdei_stack(&sdei_stack_critical_ptr, cpu);
+ if (err)
+ break;
+ }
+
+ if (err)
+ free_sdei_stacks();
+
+ return err;
+}
+
+bool _on_sdei_stack(unsigned long sp)
+{
+ unsigned long low, high;
+
+ if (!IS_ENABLED(CONFIG_VMAP_STACK))
+ return false;
+
+ low = (unsigned long)raw_cpu_read(sdei_stack_critical_ptr);
+ high = low + SDEI_STACK_SIZE;
+
+ if (low <= sp && sp < high)
+ return true;
+
+ low = (unsigned long)raw_cpu_read(sdei_stack_normal_ptr);
+ high = low + SDEI_STACK_SIZE;
+
+ return (low <= sp && sp < high);
+}
+
+unsigned long sdei_arch_get_entry_point(int conduit)
+{
+ /*
+ * SDEI works between adjacent exception levels. If we booted at EL1 we
+ * assume a hypervisor is marshalling events. If we booted@EL2 and
+ * dropped to EL1 because we don't support VHE, then we can't support
+ * SDEI.
+ */
+ if (is_hyp_mode_available() && !is_kernel_in_hyp_mode()) {
+ pr_err("Not supported on this hardware/boot configuration\n");
+ return 0;
+ }
+
+ if (IS_ENABLED(CONFIG_VMAP_STACK)) {
+ if (init_sdei_stacks())
+ return 0;
+ }
+
+ sdei_exit_mode = (conduit == CONDUIT_HVC) ? SDEI_EXIT_HVC : SDEI_EXIT_SMC;
+ return (unsigned long)__sdei_asm_handler;
+}
+
+/*
+ * __sdei_handler() returns one of:
+ * SDEI_EV_HANDLED - success, return to the interrupted context.
+ * SDEI_EV_FAILED - failure, return this error code to firmare.
+ * virtual-address - success, return to this address.
+ */
+static __kprobes unsigned long _sdei_handler(struct pt_regs *regs,
+ struct sdei_registered_event *arg)
+{
+ u32 mode;
+ int i, err = 0;
+ const int clobbered_registers = 4;
+ u64 elr = read_sysreg(elr_el1);
+ u32 kernel_mode = read_sysreg(CurrentEL) | 1; /* +SPSel */
+ unsigned long vbar = read_sysreg(vbar_el1);
+
+ /* Retrieve the missing registers values */
+ for (i = 0; i < clobbered_registers; i++) {
+ /* from within the handler, this call always succeeds */
+ sdei_api_event_context(i, ®s->regs[i]);
+ }
+
+ /*
+ * We didn't take an exception to get here, set PAN. UAO will be cleared
+ * by sdei_event_handler()s set_fs(USER_DS) call.
+ */
+ __uaccess_enable_hw_pan();
+
+ err = sdei_event_handler(regs, arg);
+ if (err)
+ return SDEI_EV_FAILED;
+
+ if (elr != read_sysreg(elr_el1)) {
+ /*
+ * We took a synchronous exception from the SDEI handler.
+ * This could deadlock, and if you interrupt KVM it will
+ * hyp-panic instead.
+ */
+ pr_warn("unsafe: exception during handler\n");
+ }
+
+ mode = regs->pstate & (PSR_MODE32_BIT | PSR_MODE_MASK);
+
+ /*
+ * If we interrupted the kernel with interrupts masked, we always go
+ * back to wherever we came from.
+ */
+ if (mode == kernel_mode && !interrupts_enabled(regs))
+ return SDEI_EV_HANDLED;
+
+ /*
+ * Otherwise, we pretend this was an IRQ. This lets user space tasks
+ * receive signals before we return to them, and KVM to invoke it's
+ * world switch to do the same.
+ *
+ * See DDI0487B.a Table D1-7 'Vector offsets from vector table base
+ * address'.
+ */
+ if (mode == kernel_mode)
+ return vbar + 0x280;
+ else if (mode & PSR_MODE32_BIT)
+ return vbar + 0x680;
+
+ return vbar + 0x480;
+}
+
+
+asmlinkage __kprobes notrace unsigned long
+__sdei_handler(struct pt_regs *regs, struct sdei_registered_event *arg)
+{
+ unsigned long ret;
+ bool do_nmi_exit = false;
+
+ /*
+ * nmi_enter() deals with printk() re-entrance and use of RCU when
+ * RCU believed this CPU was idle. Because critical events can
+ * interrupt normal events, we may already be in_nmi().
+ */
+ if (!in_nmi()) {
+ nmi_enter();
+ do_nmi_exit = true;
+ }
+
+ ret = _sdei_handler(regs, arg);
+
+ if (do_nmi_exit)
+ nmi_exit();
+
+ return ret;
+}
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index 551eb07c53b6..3b8ad7be9c33 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -18,6 +18,7 @@
*/
#include <linux/acpi.h>
+#include <linux/arm_sdei.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/spinlock.h>
@@ -836,6 +837,7 @@ static void ipi_cpu_stop(unsigned int cpu)
set_cpu_online(cpu, false);
local_daif_mask();
+ sdei_mask_local_cpu();
while (1)
cpu_relax();
@@ -853,6 +855,7 @@ static void ipi_cpu_crash_stop(unsigned int cpu, struct pt_regs *regs)
atomic_dec(&waiting_for_crash_ipi);
local_irq_disable();
+ sdei_mask_local_cpu();
#ifdef CONFIG_HOTPLUG_CPU
if (cpu_ops[cpu]->cpu_die)
@@ -972,6 +975,8 @@ void smp_send_stop(void)
if (num_online_cpus() > 1)
pr_warning("SMP: failed to stop secondary CPUs %*pbl\n",
cpumask_pr_args(cpu_online_mask));
+
+ sdei_mask_local_cpu();
}
#ifdef CONFIG_KEXEC_CORE
@@ -990,8 +995,10 @@ void crash_smp_send_stop(void)
cpus_stopped = 1;
- if (num_online_cpus() == 1)
+ if (num_online_cpus() == 1) {
+ sdei_mask_local_cpu();
return;
+ }
cpumask_copy(&mask, cpu_online_mask);
cpumask_clear_cpu(smp_processor_id(), &mask);
@@ -1009,6 +1016,8 @@ void crash_smp_send_stop(void)
if (atomic_read(&waiting_for_crash_ipi) > 0)
pr_warning("SMP: failed to stop secondary CPUs %*pbl\n",
cpumask_pr_args(&mask));
+
+ sdei_mask_local_cpu();
}
bool smp_crash_stop_failed(void)
--
2.15.0
^ permalink raw reply related
* [PATCH v6 11/16] firmware: arm_sdei: Add support for CPU and system power states
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
When a CPU enters an idle lower-power state or is powering off, we
need to mask SDE events so that no events can be delivered while we
are messing with the MMU as the registered entry points won't be valid.
If the system reboots, we want to unregister all events and mask the CPUs.
For kexec this allows us to hand a clean slate to the next kernel
instead of relying on it to call sdei_{private,system}_data_reset().
For hibernate we unregister all events and re-register them on restore,
in case we restored with the SDE code loaded at a different address.
(e.g. KASLR).
Add all the notifiers necessary to do this. We only support shared events
so all events are left registered and enabled over CPU hotplug.
Signed-off-by: James Morse <james.morse@arm.com>
---
Changes since v4:
* Moved cpuhotplug callbacks later to prevent it pre-empting sdei_probe()
before we are ready to unmask CPUs.
* Changed cpuhotplug callbacks to make the in-memory state true, instead of
save/restoring firmware state. This makes private events simpler.
* Change unregister_all()/reregister_events() to only operate on shared
events and remove the cpuhotplug state in the freeze/thaw callbacks.
* Moved the was_enabled into the event:{reregister,reenable}
* Dropped Catalin's ack.
Changes since v3:
* Renamed CPUHP enum entry to have an ARM_ prefix.
drivers/firmware/arm_sdei.c | 255 +++++++++++++++++++++++++++++++++++++++++++-
include/linux/cpuhotplug.h | 1 +
2 files changed, 255 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c
index 8da173cc7e43..0a4c75320840 100644
--- a/drivers/firmware/arm_sdei.c
+++ b/drivers/firmware/arm_sdei.c
@@ -7,6 +7,8 @@
#include <linux/arm-smccc.h>
#include <linux/bitops.h>
#include <linux/compiler.h>
+#include <linux/cpuhotplug.h>
+#include <linux/cpu_pm.h>
#include <linux/errno.h>
#include <linux/hardirq.h>
#include <linux/kernel.h>
@@ -14,12 +16,15 @@
#include <linux/kvm_host.h>
#include <linux/list.h>
#include <linux/mutex.h>
+#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/percpu.h>
#include <linux/platform_device.h>
+#include <linux/pm.h>
#include <linux/ptrace.h>
#include <linux/preempt.h>
+#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/spinlock.h>
@@ -37,7 +42,11 @@ static asmlinkage void (*sdei_firmware_call)(unsigned long function_id,
static unsigned long sdei_entry_point;
struct sdei_event {
+ /* These three are protected by the sdei_list_lock */
struct list_head list;
+ bool reregister;
+ bool reenable;
+
u32 event_num;
u8 type;
u8 priority;
@@ -253,6 +262,11 @@ static void _ipi_mask_cpu(void *ignored)
sdei_mask_local_cpu();
}
+static int sdei_cpuhp_down(unsigned int ignored)
+{
+ return sdei_mask_local_cpu();
+}
+
int sdei_unmask_local_cpu(void)
{
int err;
@@ -274,6 +288,11 @@ static void _ipi_unmask_cpu(void *ignored)
sdei_unmask_local_cpu();
}
+static int sdei_cpuhp_up(unsigned int ignored)
+{
+ return sdei_unmask_local_cpu();
+}
+
static void _ipi_private_reset(void *ignored)
{
int err;
@@ -330,6 +349,10 @@ int sdei_event_enable(u32 event_num)
return -ENOENT;
}
+ spin_lock(&sdei_list_lock);
+ event->reenable = true;
+ spin_unlock(&sdei_list_lock);
+
if (event->type == SDEI_EVENT_TYPE_SHARED)
err = sdei_api_event_enable(event->event_num);
mutex_unlock(&sdei_events_lock);
@@ -356,6 +379,10 @@ int sdei_event_disable(u32 event_num)
return -ENOENT;
}
+ spin_lock(&sdei_list_lock);
+ event->reenable = false;
+ spin_unlock(&sdei_list_lock);
+
if (event->type == SDEI_EVENT_TYPE_SHARED)
err = sdei_api_event_disable(event->event_num);
mutex_unlock(&sdei_events_lock);
@@ -374,6 +401,11 @@ static int _sdei_event_unregister(struct sdei_event *event)
{
lockdep_assert_held(&sdei_events_lock);
+ spin_lock(&sdei_list_lock);
+ event->reregister = false;
+ event->reenable = false;
+ spin_unlock(&sdei_list_lock);
+
if (event->type == SDEI_EVENT_TYPE_SHARED)
return sdei_api_event_unregister(event->event_num);
@@ -408,6 +440,31 @@ int sdei_event_unregister(u32 event_num)
}
EXPORT_SYMBOL(sdei_event_unregister);
+/*
+ * unregister events, but don't destroy them as they are re-registered by
+ * sdei_reregister_shared().
+ */
+static int sdei_unregister_shared(void)
+{
+ int err = 0;
+ struct sdei_event *event;
+
+ mutex_lock(&sdei_events_lock);
+ spin_lock(&sdei_list_lock);
+ list_for_each_entry(event, &sdei_list, list) {
+ if (event->type != SDEI_EVENT_TYPE_SHARED)
+ continue;
+
+ err = _sdei_event_unregister(event);
+ if (err)
+ break;
+ }
+ spin_unlock(&sdei_list_lock);
+ mutex_unlock(&sdei_events_lock);
+
+ return err;
+}
+
static int sdei_api_event_register(u32 event_num, unsigned long entry_point,
void *arg, u64 flags, u64 affinity)
{
@@ -465,6 +522,174 @@ int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg)
}
EXPORT_SYMBOL(sdei_event_register);
+static int sdei_reregister_event(struct sdei_event *event)
+{
+ int err;
+
+ lockdep_assert_held(&sdei_events_lock);
+
+ err = _sdei_event_register(event);
+ if (err) {
+ pr_err("Failed to re-register event %u\n", event->event_num);
+ sdei_event_destroy(event);
+ return err;
+ }
+
+ if (event->reenable) {
+ if (event->type == SDEI_EVENT_TYPE_SHARED)
+ err = sdei_api_event_enable(event->event_num);
+ }
+
+ if (err)
+ pr_err("Failed to re-enable event %u\n", event->event_num);
+
+ return err;
+}
+
+static int sdei_reregister_shared(void)
+{
+ int err = 0;
+ struct sdei_event *event;
+
+ mutex_lock(&sdei_events_lock);
+ spin_lock(&sdei_list_lock);
+ list_for_each_entry(event, &sdei_list, list) {
+ if (event->type != SDEI_EVENT_TYPE_SHARED)
+ continue;
+
+ if (event->reregister) {
+ err = sdei_reregister_event(event);
+ if (err)
+ break;
+ }
+ }
+ spin_unlock(&sdei_list_lock);
+ mutex_unlock(&sdei_events_lock);
+
+ return err;
+}
+
+/* When entering idle, mask/unmask events for this cpu */
+static int sdei_pm_notifier(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ int rv;
+
+ switch (action) {
+ case CPU_PM_ENTER:
+ rv = sdei_mask_local_cpu();
+ break;
+ case CPU_PM_EXIT:
+ rv = sdei_unmask_local_cpu();
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ if (rv)
+ return notifier_from_errno(rv);
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block sdei_pm_nb = {
+ .notifier_call = sdei_pm_notifier,
+};
+
+static int sdei_device_suspend(struct device *dev)
+{
+ on_each_cpu(_ipi_mask_cpu, NULL, true);
+
+ return 0;
+}
+
+static int sdei_device_resume(struct device *dev)
+{
+ on_each_cpu(_ipi_unmask_cpu, NULL, true);
+
+ return 0;
+}
+
+/*
+ * We need all events to be reregistered when we resume from hibernate.
+ *
+ * The sequence is freeze->thaw. Reboot. freeze->restore. We unregister
+ * events during freeze, then re-register and re-enable them during thaw
+ * and restore.
+ */
+static int sdei_device_freeze(struct device *dev)
+{
+ int err;
+
+ cpuhp_remove_state(CPUHP_AP_ARM_SDEI_STARTING);
+
+ err = sdei_unregister_shared();
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int sdei_device_thaw(struct device *dev)
+{
+ int err;
+
+ /* re-register shared events */
+ err = sdei_reregister_shared();
+ if (err) {
+ pr_warn("Failed to re-register shared events...\n");
+ sdei_mark_interface_broken();
+ return err;
+ }
+
+ err = cpuhp_setup_state(CPUHP_AP_ARM_SDEI_STARTING, "SDEI",
+ &sdei_cpuhp_up, &sdei_cpuhp_down);
+ if (err)
+ pr_warn("Failed to re-register CPU hotplug notifier...\n");
+
+ return err;
+}
+
+static int sdei_device_restore(struct device *dev)
+{
+ int err;
+
+ err = sdei_platform_reset();
+ if (err)
+ return err;
+
+ return sdei_device_thaw(dev);
+}
+
+static const struct dev_pm_ops sdei_pm_ops = {
+ .suspend = sdei_device_suspend,
+ .resume = sdei_device_resume,
+ .freeze = sdei_device_freeze,
+ .thaw = sdei_device_thaw,
+ .restore = sdei_device_restore,
+};
+
+/*
+ * Mask all CPUs and unregister all events on panic, reboot or kexec.
+ */
+static int sdei_reboot_notifier(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ /*
+ * We are going to reset the interface, after this there is no point
+ * doing work when we take CPUs offline.
+ */
+ cpuhp_remove_state(CPUHP_AP_ARM_SDEI_STARTING);
+
+ sdei_platform_reset();
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block sdei_reboot_nb = {
+ .notifier_call = sdei_reboot_notifier,
+};
+
static void sdei_smccc_smc(unsigned long function_id,
unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3,
@@ -547,9 +772,36 @@ static int sdei_probe(struct platform_device *pdev)
return 0;
}
- on_each_cpu(&_ipi_unmask_cpu, NULL, false);
+ err = cpu_pm_register_notifier(&sdei_pm_nb);
+ if (err) {
+ pr_warn("Failed to register CPU PM notifier...\n");
+ goto error;
+ }
+
+ err = register_reboot_notifier(&sdei_reboot_nb);
+ if (err) {
+ pr_warn("Failed to register reboot notifier...\n");
+ goto remove_cpupm;
+ }
+
+ err = cpuhp_setup_state(CPUHP_AP_ARM_SDEI_STARTING, "SDEI",
+ &sdei_cpuhp_up, &sdei_cpuhp_down);
+ if (err) {
+ pr_warn("Failed to register CPU hotplug notifier...\n");
+ goto remove_reboot;
+ }
return 0;
+
+remove_reboot:
+ unregister_reboot_notifier(&sdei_reboot_nb);
+
+remove_cpupm:
+ cpu_pm_unregister_notifier(&sdei_pm_nb);
+
+error:
+ sdei_mark_interface_broken();
+ return err;
}
static const struct of_device_id sdei_of_match[] = {
@@ -560,6 +812,7 @@ static const struct of_device_id sdei_of_match[] = {
static struct platform_driver sdei_driver = {
.driver = {
.name = "sdei",
+ .pm = &sdei_pm_ops,
.of_match_table = sdei_of_match,
},
.probe = sdei_probe,
diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
index 201ab7267986..87b505a48a94 100644
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -109,6 +109,7 @@ enum cpuhp_state {
CPUHP_AP_PERF_XTENSA_STARTING,
CPUHP_AP_PERF_METAG_STARTING,
CPUHP_AP_MIPS_OP_LOONGSON3_STARTING,
+ CPUHP_AP_ARM_SDEI_STARTING,
CPUHP_AP_ARM_VFP_STARTING,
CPUHP_AP_ARM64_DEBUG_MONITORS_STARTING,
CPUHP_AP_PERF_ARM_HW_BREAKPOINT_STARTING,
--
2.15.0
^ permalink raw reply related
* [PATCH v6 12/16] firmware: arm_sdei: add support for CPU private events
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
Private SDE events are per-cpu, and need to be registered and enabled
on each CPU.
Hide this detail from the caller by adapting our {,un}register and
{en,dis}able calls to send an IPI to each CPU if the event is private.
CPU private events are unregistered when the CPU is powered-off, and
re-registered when the CPU is brought back online. This saves bringing
secondary cores back online to call private_reset() on shutdown, kexec
and resume from hibernate.
Signed-off-by: James Morse <james.morse@arm.com>
---
Changes since v4:
* rip out the racy frozen flag.
* hotplug hooks honour the event reregister/renable flags instead of
save/restoring the firmware state, these flags are protected by the
spinlock.
* Repurposed the event-enable ipi-call instead of having a redundant
re-enable call.
* Dropped Catalin's ack
drivers/firmware/arm_sdei.c | 206 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 193 insertions(+), 13 deletions(-)
diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c
index 0a4c75320840..10a8bfa7339a 100644
--- a/drivers/firmware/arm_sdei.c
+++ b/drivers/firmware/arm_sdei.c
@@ -5,9 +5,11 @@
#include <linux/acpi.h>
#include <linux/arm_sdei.h>
#include <linux/arm-smccc.h>
+#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/compiler.h>
#include <linux/cpuhotplug.h>
+#include <linux/cpu.h>
#include <linux/cpu_pm.h>
#include <linux/errno.h>
#include <linux/hardirq.h>
@@ -52,7 +54,13 @@ struct sdei_event {
u8 priority;
/* This pointer is handed to firmware as the event argument. */
- struct sdei_registered_event *registered;
+ union {
+ /* Shared events */
+ struct sdei_registered_event *registered;
+
+ /* CPU private events */
+ struct sdei_registered_event __percpu *private_registered;
+ };
};
/* Take the mutex for any API call or modification. Take the mutex first. */
@@ -62,6 +70,34 @@ static DEFINE_MUTEX(sdei_events_lock);
static DEFINE_SPINLOCK(sdei_list_lock);
static LIST_HEAD(sdei_list);
+/* Private events are registered/enabled via IPI passing one of these */
+struct sdei_crosscall_args {
+ struct sdei_event *event;
+ atomic_t errors;
+ int first_error;
+};
+
+#define CROSSCALL_INIT(arg, event) (arg.event = event, \
+ arg.first_error = 0, \
+ atomic_set(&arg.errors, 0))
+
+static inline int sdei_do_cross_call(void *fn, struct sdei_event * event)
+{
+ struct sdei_crosscall_args arg;
+
+ CROSSCALL_INIT(arg, event);
+ on_each_cpu(fn, &arg, true);
+
+ return arg.first_error;
+}
+
+static inline void
+sdei_cross_call_return(struct sdei_crosscall_args *arg, int err)
+{
+ if (err && (atomic_inc_return(&arg->errors) == 1))
+ arg->first_error = err;
+}
+
static int sdei_to_linux_errno(unsigned long sdei_err)
{
switch (sdei_err) {
@@ -207,6 +243,26 @@ static struct sdei_event *sdei_event_create(u32 event_num,
reg->callback = cb;
reg->callback_arg = cb_arg;
event->registered = reg;
+ } else {
+ int cpu;
+ struct sdei_registered_event __percpu *regs;
+
+ regs = alloc_percpu(struct sdei_registered_event);
+ if (!regs) {
+ kfree(event);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ for_each_possible_cpu(cpu) {
+ reg = per_cpu_ptr(regs, cpu);
+
+ reg->event_num = event->event_num;
+ reg->priority = event->priority;
+ reg->callback = cb;
+ reg->callback_arg = cb_arg;
+ }
+
+ event->private_registered = regs;
}
if (sdei_event_find(event_num)) {
@@ -232,6 +288,8 @@ static void sdei_event_destroy(struct sdei_event *event)
if (event->type == SDEI_EVENT_TYPE_SHARED)
kfree(event->registered);
+ else
+ free_percpu(event->private_registered);
kfree(event);
}
@@ -262,11 +320,6 @@ static void _ipi_mask_cpu(void *ignored)
sdei_mask_local_cpu();
}
-static int sdei_cpuhp_down(unsigned int ignored)
-{
- return sdei_mask_local_cpu();
-}
-
int sdei_unmask_local_cpu(void)
{
int err;
@@ -288,11 +341,6 @@ static void _ipi_unmask_cpu(void *ignored)
sdei_unmask_local_cpu();
}
-static int sdei_cpuhp_up(unsigned int ignored)
-{
- return sdei_unmask_local_cpu();
-}
-
static void _ipi_private_reset(void *ignored)
{
int err;
@@ -337,6 +385,19 @@ static int sdei_api_event_enable(u32 event_num)
0, NULL);
}
+/* Called directly by the hotplug callbacks */
+static void _local_event_enable(void *data)
+{
+ int err;
+ struct sdei_crosscall_args *arg = data;
+
+ WARN_ON_ONCE(preemptible());
+
+ err = sdei_api_event_enable(arg->event->event_num);
+
+ sdei_cross_call_return(arg, err);
+}
+
int sdei_event_enable(u32 event_num)
{
int err = -EINVAL;
@@ -355,6 +416,8 @@ int sdei_event_enable(u32 event_num)
if (event->type == SDEI_EVENT_TYPE_SHARED)
err = sdei_api_event_enable(event->event_num);
+ else
+ err = sdei_do_cross_call(_local_event_enable, event);
mutex_unlock(&sdei_events_lock);
return err;
@@ -367,6 +430,16 @@ static int sdei_api_event_disable(u32 event_num)
0, 0, NULL);
}
+static void _ipi_event_disable(void *data)
+{
+ int err;
+ struct sdei_crosscall_args *arg = data;
+
+ err = sdei_api_event_disable(arg->event->event_num);
+
+ sdei_cross_call_return(arg, err);
+}
+
int sdei_event_disable(u32 event_num)
{
int err = -EINVAL;
@@ -385,6 +458,8 @@ int sdei_event_disable(u32 event_num)
if (event->type == SDEI_EVENT_TYPE_SHARED)
err = sdei_api_event_disable(event->event_num);
+ else
+ err = sdei_do_cross_call(_ipi_event_disable, event);
mutex_unlock(&sdei_events_lock);
return err;
@@ -397,6 +472,19 @@ static int sdei_api_event_unregister(u32 event_num)
0, 0, 0, NULL);
}
+/* Called directly by the hotplug callbacks */
+static void _local_event_unregister(void *data)
+{
+ int err;
+ struct sdei_crosscall_args *arg = data;
+
+ WARN_ON_ONCE(preemptible());
+
+ err = sdei_api_event_unregister(arg->event->event_num);
+
+ sdei_cross_call_return(arg, err);
+}
+
static int _sdei_event_unregister(struct sdei_event *event)
{
lockdep_assert_held(&sdei_events_lock);
@@ -409,7 +497,7 @@ static int _sdei_event_unregister(struct sdei_event *event)
if (event->type == SDEI_EVENT_TYPE_SHARED)
return sdei_api_event_unregister(event->event_num);
- return -EINVAL;
+ return sdei_do_cross_call(_local_event_unregister, event);
}
int sdei_event_unregister(u32 event_num)
@@ -473,17 +561,50 @@ static int sdei_api_event_register(u32 event_num, unsigned long entry_point,
flags, affinity, NULL);
}
+/* Called directly by the hotplug callbacks */
+static void _local_event_register(void *data)
+{
+ int err;
+ struct sdei_registered_event *reg;
+ struct sdei_crosscall_args *arg = data;
+
+ WARN_ON(preemptible());
+
+ reg = per_cpu_ptr(arg->event->private_registered, smp_processor_id());
+ err = sdei_api_event_register(arg->event->event_num, sdei_entry_point,
+ reg, 0, 0);
+
+ sdei_cross_call_return(arg, err);
+}
+
static int _sdei_event_register(struct sdei_event *event)
{
+ int err;
+
lockdep_assert_held(&sdei_events_lock);
+ spin_lock(&sdei_list_lock);
+ event->reregister = true;
+ spin_unlock(&sdei_list_lock);
+
if (event->type == SDEI_EVENT_TYPE_SHARED)
return sdei_api_event_register(event->event_num,
sdei_entry_point,
event->registered,
SDEI_EVENT_REGISTER_RM_ANY, 0);
- return -EINVAL;
+
+ err = sdei_do_cross_call(_local_event_register, event);
+ if (err) {
+ spin_lock(&sdei_list_lock);
+ event->reregister = false;
+ event->reenable = false;
+ spin_unlock(&sdei_list_lock);
+
+ sdei_do_cross_call(_local_event_unregister, event);
+ }
+
+ return err;
}
int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg)
@@ -538,6 +659,8 @@ static int sdei_reregister_event(struct sdei_event *event)
if (event->reenable) {
if (event->type == SDEI_EVENT_TYPE_SHARED)
err = sdei_api_event_enable(event->event_num);
+ else
+ err = sdei_do_cross_call(_local_event_enable, event);
}
if (err)
@@ -569,6 +692,62 @@ static int sdei_reregister_shared(void)
return err;
}
+static int sdei_cpuhp_down(unsigned int cpu)
+{
+ struct sdei_event *event;
+ struct sdei_crosscall_args arg;
+
+ /* un-register private events */
+ spin_lock(&sdei_list_lock);
+ list_for_each_entry(event, &sdei_list, list) {
+ if (event->type == SDEI_EVENT_TYPE_SHARED)
+ continue;
+
+ CROSSCALL_INIT(arg, event);
+ /* call the cross-call function locally... */
+ _local_event_unregister(&arg);
+ if (arg.first_error)
+ pr_err("Failed to unregister event %u: %d\n",
+ event->event_num, arg.first_error);
+ }
+ spin_unlock(&sdei_list_lock);
+
+ return sdei_mask_local_cpu();
+}
+
+static int sdei_cpuhp_up(unsigned int cpu)
+{
+ struct sdei_event *event;
+ struct sdei_crosscall_args arg;
+
+ /* re-register/enable private events */
+ spin_lock(&sdei_list_lock);
+ list_for_each_entry(event, &sdei_list, list) {
+ if (event->type == SDEI_EVENT_TYPE_SHARED)
+ continue;
+
+ if (event->reregister) {
+ CROSSCALL_INIT(arg, event);
+ /* call the cross-call function locally... */
+ _local_event_register(&arg);
+ if (arg.first_error)
+ pr_err("Failed to re-register event %u: %d\n",
+ event->event_num, arg.first_error);
+ }
+
+ if (event->reenable) {
+ CROSSCALL_INIT(arg, event);
+ _local_event_enable(&arg);
+ if (arg.first_error)
+ pr_err("Failed to re-enable event %u: %d\n",
+ event->event_num, arg.first_error);
+ }
+ }
+ spin_unlock(&sdei_list_lock);
+
+ return sdei_unmask_local_cpu();
+}
+
/* When entering idle, mask/unmask events for this cpu */
static int sdei_pm_notifier(struct notifier_block *nb, unsigned long action,
void *data)
@@ -621,6 +800,7 @@ static int sdei_device_freeze(struct device *dev)
{
int err;
+ /* unregister private events */
cpuhp_remove_state(CPUHP_AP_ARM_SDEI_STARTING);
err = sdei_unregister_shared();
--
2.15.0
^ permalink raw reply related
* [PATCH v6 13/16] arm64: acpi: Remove __init from acpi_psci_use_hvc() for use by SDEI
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
SDEI inherits the 'use hvc' bit that is also used by PSCI. PSCI does all
its initialisation early, SDEI does its late.
Remove the __init annotation from acpi_psci_use_hvc().
Signed-off-by: James Morse <james.morse@arm.com>
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
---
The function name is unchanged as this bit is named 'PSCI_USE_HVC'
in table 5-37 of ACPIv6.2.
arch/arm64/kernel/acpi.c | 2 +-
include/linux/psci.h | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c
index b3162715ed78..252396a96c78 100644
--- a/arch/arm64/kernel/acpi.c
+++ b/arch/arm64/kernel/acpi.c
@@ -117,7 +117,7 @@ bool __init acpi_psci_present(void)
}
/* Whether HVC must be used instead of SMC as the PSCI conduit */
-bool __init acpi_psci_use_hvc(void)
+bool acpi_psci_use_hvc(void)
{
return acpi_gbl_FADT.arm_boot_flags & ACPI_FADT_PSCI_USE_HVC;
}
diff --git a/include/linux/psci.h b/include/linux/psci.h
index 6306ab10af18..f724fd8c78e8 100644
--- a/include/linux/psci.h
+++ b/include/linux/psci.h
@@ -47,10 +47,11 @@ static inline int psci_dt_init(void) { return 0; }
#if defined(CONFIG_ARM_PSCI_FW) && defined(CONFIG_ACPI)
int __init psci_acpi_init(void);
bool __init acpi_psci_present(void);
-bool __init acpi_psci_use_hvc(void);
+bool acpi_psci_use_hvc(void);
#else
static inline int psci_acpi_init(void) { return 0; }
static inline bool acpi_psci_present(void) { return false; }
+static inline bool acpi_psci_use_hvc(void) {return false; }
#endif
#endif /* __LINUX_PSCI_H */
--
2.15.0
^ permalink raw reply related
* [PATCH v6 14/16] firmware: arm_sdei: Discover SDEI support via ACPI
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
SDEI defines a new ACPI table to indicate the presence of the interface.
The conduit is discovered in the same way as PSCI.
For ACPI we need to create the platform device ourselves as SDEI doesn't
have an entry in the DSDT.
The SDEI platform device should be created after ACPI has been initialised
so that we can parse the table, but before GHES devices are created, which
may register SDE events if they use SDEI as their notification type.
Signed-off-by: James Morse <james.morse@arm.com>
Acked-by: Catalin Marinas <catalin.marinas@arm.com>
---
drivers/firmware/arm_sdei.c | 41 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c
index 10a8bfa7339a..fb7caa3628b9 100644
--- a/drivers/firmware/arm_sdei.c
+++ b/drivers/firmware/arm_sdei.c
@@ -907,6 +907,14 @@ static int sdei_get_conduit(struct platform_device *pdev)
}
pr_warn("invalid \"method\" property: %s\n", method);
+ } else if (IS_ENABLED(CONFIG_ACPI) && !acpi_disabled) {
+ if (acpi_psci_use_hvc()) {
+ sdei_firmware_call = &sdei_smccc_hvc;
+ return CONDUIT_HVC;
+ } else {
+ sdei_firmware_call = &sdei_smccc_smc;
+ return CONDUIT_SMC;
+ }
}
return CONDUIT_INVALID;
@@ -1020,14 +1028,45 @@ static bool __init sdei_present_dt(void)
return true;
}
+static bool __init sdei_present_acpi(void)
+{
+ acpi_status status;
+ struct platform_device *pdev;
+ struct acpi_table_header *sdei_table_header;
+
+ if (acpi_disabled)
+ return false;
+
+ status = acpi_get_table(ACPI_SIG_SDEI, 0, &sdei_table_header);
+ if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) {
+ const char *msg = acpi_format_exception(status);
+
+ pr_info("Failed to get ACPI:SDEI table, %s\n", msg);
+ }
+ if (ACPI_FAILURE(status))
+ return false;
+
+ pdev = platform_device_register_simple(sdei_driver.driver.name, 0, NULL,
+ 0);
+ if (IS_ERR(pdev))
+ return false;
+
+ return true;
+}
+
static int __init sdei_init(void)
{
- if (sdei_present_dt())
+ if (sdei_present_dt() || sdei_present_acpi())
platform_driver_register(&sdei_driver);
return 0;
}
+/*
+ * On an ACPI system SDEI needs to be ready before HEST:GHES tries to register
+ * its events. ACPI is initialised from a subsys_initcall(), GHES is initialised
+ * by device_initcall(). We want to be called in the middle.
+ */
subsys_initcall_sync(sdei_init);
int sdei_event_handler(struct pt_regs *regs,
--
2.15.0
^ permalink raw reply related
* [PATCH v6 15/16] arm64: mmu: add the entry trampolines start/end section markers into sections.h
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
SDEI needs to calculate an offset in the trampoline page too. Move
the extern char[] to sections.h.
This patch just moves code around.
Signed-off-by: James Morse <james.morse@arm.com>
---
(fixed typo in commit message)
arch/arm64/include/asm/sections.h | 1 +
arch/arm64/mm/mmu.c | 2 --
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/sections.h b/arch/arm64/include/asm/sections.h
index 941267caa39c..caab039d6305 100644
--- a/arch/arm64/include/asm/sections.h
+++ b/arch/arm64/include/asm/sections.h
@@ -28,5 +28,6 @@ extern char __initdata_begin[], __initdata_end[];
extern char __inittext_begin[], __inittext_end[];
extern char __irqentry_text_start[], __irqentry_text_end[];
extern char __mmuoff_data_start[], __mmuoff_data_end[];
+extern char __entry_tramp_text_start[], __entry_tramp_text_end[];
#endif /* __ASM_SECTIONS_H */
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 916d9ced1c3f..9d21835c99d9 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -528,8 +528,6 @@ early_param("rodata", parse_rodata);
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
static int __init map_entry_trampoline(void)
{
- extern char __entry_tramp_text_start[];
-
pgprot_t prot = rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;
phys_addr_t pa_start = __pa_symbol(__entry_tramp_text_start);
--
2.15.0
^ permalink raw reply related
* [PATCH v6 16/16] arm64: sdei: Add trampoline code for remapping the kernel
From: James Morse @ 2018-01-08 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-1-james.morse@arm.com>
When CONFIG_UNMAP_KERNEL_AT_EL0 is set the SDEI entry point and the rest
of the kernel may be unmapped when we take an event. If this may be the
case, use an entry trampoline that can switch to the kernel page tables.
We can't use the provided PSTATE to determine whether to switch page
tables as we may have interrupted the kernel's entry trampoline, (or a
normal-priority event that interrupted the kernel's entry trampoline).
Instead test for a user ASID in ttbr1_el1.
Save a value in regs->addr_limit to indicate whether we need to restore
the original ASID when returning from this event. This value is only used
by do_page_fault(), which we don't call with the SDEI regs.
Signed-off-by: James Morse <james.morse@arm.com>
---
Changes since v5:
* spelling mistake in subject
arch/arm64/include/asm/mmu.h | 3 +-
arch/arm64/include/asm/sdei.h | 6 +++
arch/arm64/kernel/entry.S | 98 ++++++++++++++++++++++++++++++++++++++-----
arch/arm64/kernel/sdei.c | 20 ++++++++-
4 files changed, 113 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/include/asm/mmu.h b/arch/arm64/include/asm/mmu.h
index 6dd83d75b82a..a050d4f3615d 100644
--- a/arch/arm64/include/asm/mmu.h
+++ b/arch/arm64/include/asm/mmu.h
@@ -17,7 +17,8 @@
#define __ASM_MMU_H
#define MMCF_AARCH32 0x1 /* mm context flag for AArch32 executables */
-#define USER_ASID_FLAG (UL(1) << 48)
+#define USER_ASID_BIT 48
+#define USER_ASID_FLAG (UL(1) << USER_ASID_BIT)
#define TTBR_ASID_MASK (UL(0xffff) << 48)
#ifndef __ASSEMBLY__
diff --git a/arch/arm64/include/asm/sdei.h b/arch/arm64/include/asm/sdei.h
index d58a31ab525a..e073e6886685 100644
--- a/arch/arm64/include/asm/sdei.h
+++ b/arch/arm64/include/asm/sdei.h
@@ -23,6 +23,12 @@ extern unsigned long sdei_exit_mode;
asmlinkage void __sdei_asm_handler(unsigned long event_num, unsigned long arg,
unsigned long pc, unsigned long pstate);
+/* and its CONFIG_UNMAP_KERNEL_AT_EL0 trampoline */
+asmlinkage void __sdei_asm_entry_trampoline(unsigned long event_num,
+ unsigned long arg,
+ unsigned long pc,
+ unsigned long pstate);
+
/*
* The above entry point does the minimum to call C code. This function does
* anything else, before calling the driver.
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index 3d26650ef1ef..84ef12b0defa 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -1156,6 +1156,78 @@ NOKPROBE(ret_from_fork)
#include <asm/sdei.h>
#include <uapi/linux/arm_sdei.h>
+.macro sdei_handler_exit exit_mode
+ /* On success, this call never returns... */
+ cmp \exit_mode, #SDEI_EXIT_SMC
+ b.ne 99f
+ smc #0
+ b .
+99: hvc #0
+ b .
+.endm
+
+#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
+/*
+ * The regular SDEI entry point may have been unmapped along with the rest of
+ * the kernel. This trampoline restores the kernel mapping to make the x1 memory
+ * argument accessible.
+ *
+ * This clobbers x4, __sdei_handler() will restore this from firmware's
+ * copy.
+ */
+.ltorg
+.pushsection ".entry.tramp.text", "ax"
+ENTRY(__sdei_asm_entry_trampoline)
+ mrs x4, ttbr1_el1
+ tbz x4, #USER_ASID_BIT, 1f
+
+ tramp_map_kernel tmp=x4
+ isb
+ mov x4, xzr
+
+ /*
+ * Use reg->interrupted_regs.addr_limit to remember whether to unmap
+ * the kernel on exit.
+ */
+1: str x4, [x1, #(SDEI_EVENT_INTREGS + S_ORIG_ADDR_LIMIT)]
+
+#ifdef CONFIG_RANDOMIZE_BASE
+ adr x4, tramp_vectors + PAGE_SIZE
+ add x4, x4, #:lo12:__sdei_asm_trampoline_next_handler
+ ldr x4, [x4]
+#else
+ ldr x4, =__sdei_asm_handler
+#endif
+ br x4
+ENDPROC(__sdei_asm_entry_trampoline)
+NOKPROBE(__sdei_asm_entry_trampoline)
+
+/*
+ * Make the exit call and restore the original ttbr1_el1
+ *
+ * x0 & x1: setup for the exit API call
+ * x2: exit_mode
+ * x4: struct sdei_registered_event argument from registration time.
+ */
+ENTRY(__sdei_asm_exit_trampoline)
+ ldr x4, [x4, #(SDEI_EVENT_INTREGS + S_ORIG_ADDR_LIMIT)]
+ cbnz x4, 1f
+
+ tramp_unmap_kernel tmp=x4
+
+1: sdei_handler_exit exit_mode=x2
+ENDPROC(__sdei_asm_exit_trampoline)
+NOKPROBE(__sdei_asm_exit_trampoline)
+ .ltorg
+.popsection // .entry.tramp.text
+#ifdef CONFIG_RANDOMIZE_BASE
+.pushsection ".rodata", "a"
+__sdei_asm_trampoline_next_handler:
+ .quad __sdei_asm_handler
+.popsection // .rodata
+#endif /* CONFIG_RANDOMIZE_BASE */
+#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
+
/*
* Software Delegated Exception entry point.
*
@@ -1163,6 +1235,7 @@ NOKPROBE(ret_from_fork)
* x1: struct sdei_registered_event argument from registration time.
* x2: interrupted PC
* x3: interrupted PSTATE
+ * x4: maybe clobbered by the trampoline
*
* Firmware has preserved x0->x17 for us, we must save/restore the rest to
* follow SMC-CC. We save (or retrieve) all the registers as the handler may
@@ -1228,10 +1301,11 @@ ENTRY(__sdei_asm_handler)
msr sp_el0, x28
/* restore regs >x17 that we clobbered */
- ldp x28, x29, [x19, #SDEI_EVENT_INTREGS + 16 * 14]
- ldp lr, x4, [x19, #SDEI_EVENT_INTREGS + S_LR]
- mov sp, x4
- ldp x18, x19, [x19, #SDEI_EVENT_INTREGS + 16 * 9]
+ mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline
+ ldp x28, x29, [x4, #SDEI_EVENT_INTREGS + 16 * 14]
+ ldp x18, x19, [x4, #SDEI_EVENT_INTREGS + 16 * 9]
+ ldp lr, x1, [x4, #SDEI_EVENT_INTREGS + S_LR]
+ mov sp, x1
mov x1, x0 // address to complete_and_resume
/* x0 = (x0 <= 1) ? EVENT_COMPLETE:EVENT_COMPLETE_AND_RESUME */
@@ -1240,14 +1314,16 @@ ENTRY(__sdei_asm_handler)
mov_q x3, SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME
csel x0, x2, x3, ls
- /* On success, this call never returns... */
ldr_l x2, sdei_exit_mode
- cmp x2, #SDEI_EXIT_SMC
- b.ne 1f
- smc #0
- b .
-1: hvc #0
- b .
+
+alternative_if_not ARM64_UNMAP_KERNEL_AT_EL0
+ sdei_handler_exit exit_mode=x2
+alternative_else_nop_endif
+
+#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
+ tramp_alias dst=x5, sym=__sdei_asm_exit_trampoline
+ br x5
+#endif
ENDPROC(__sdei_asm_handler)
NOKPROBE(__sdei_asm_handler)
#endif /* CONFIG_ARM_SDE_INTERFACE */
diff --git a/arch/arm64/kernel/sdei.c b/arch/arm64/kernel/sdei.c
index f9dffacaa5d6..6b8d90d5ceae 100644
--- a/arch/arm64/kernel/sdei.c
+++ b/arch/arm64/kernel/sdei.c
@@ -10,7 +10,9 @@
#include <asm/alternative.h>
#include <asm/kprobes.h>
+#include <asm/mmu.h>
#include <asm/ptrace.h>
+#include <asm/sections.h>
#include <asm/sysreg.h>
#include <asm/vmap_stack.h>
@@ -124,7 +126,18 @@ unsigned long sdei_arch_get_entry_point(int conduit)
}
sdei_exit_mode = (conduit == CONDUIT_HVC) ? SDEI_EXIT_HVC : SDEI_EXIT_SMC;
- return (unsigned long)__sdei_asm_handler;
+
+#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
+ if (arm64_kernel_unmapped_at_el0()) {
+ unsigned long offset;
+
+ offset = (unsigned long)__sdei_asm_entry_trampoline -
+ (unsigned long)__entry_tramp_text_start;
+ return TRAMP_VALIAS + offset;
+ } else
+#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
+ return (unsigned long)__sdei_asm_handler;
+
}
/*
@@ -138,11 +151,14 @@ static __kprobes unsigned long _sdei_handler(struct pt_regs *regs,
{
u32 mode;
int i, err = 0;
- const int clobbered_registers = 4;
+ int clobbered_registers = 4;
u64 elr = read_sysreg(elr_el1);
u32 kernel_mode = read_sysreg(CurrentEL) | 1; /* +SPSel */
unsigned long vbar = read_sysreg(vbar_el1);
+ if (arm64_kernel_unmapped_at_el0())
+ clobbered_registers++;
+
/* Retrieve the missing registers values */
for (i = 0; i < clobbered_registers; i++) {
/* from within the handler, this call always succeeds */
--
2.15.0
^ permalink raw reply related
* [PATCH 0/5] Add capture functionality to OMAP pwm driver
From: Ladislav Michl @ 2018-01-08 15:39 UTC (permalink / raw)
To: linux-arm-kernel
Folowing patchset is based on Keerthy's "v6 omap: dmtimer: Move driver
out of plat-omap" minus last patch which is here corrected.
Keerthy, feel free to pick cleanup/bugfix patches into your serie
if you find it usefull.
Tony, I wanted to do something easy to understand as a base for
event capture interrupt, but either I do not understand hardware,
or it indeed cannot do pulse capture.
Please see comments in the last patch in the serie.
Ladislav Michl (5):
clocksource: timer-dm: Make unexported functions static
clocksource: timer-dm: Check prescaler value
pwm: pwm-omap-dmtimer: Fix frequency when using prescaler
clocksource: timer-dm: Add event capture
pwm: pwm-omap-dmtimer: Add capture functionality
drivers/clocksource/timer-dm.c | 269 +++++++++++++++++------------
drivers/pwm/pwm-omap-dmtimer.c | 200 +++++++++++++++++----
include/clocksource/dmtimer.h | 24 ---
include/linux/platform_data/dmtimer-omap.h | 8 +
4 files changed, 334 insertions(+), 167 deletions(-)
--
2.15.1
^ permalink raw reply
* [PATCH 1/5] clocksource: timer-dm: Make unexported functions static
From: Ladislav Michl @ 2018-01-08 15:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153926.GA3916@lenoch>
As dmtimer no longer exports functions, make those previously
exported static.
Signed-off-by: Ladislav Michl <ladis@linux-mips.org>
diff --git a/drivers/clocksource/timer-dm.c b/drivers/clocksource/timer-dm.c
index 60db1734ea3b..43531eecbe54 100644
--- a/drivers/clocksource/timer-dm.c
+++ b/drivers/clocksource/timer-dm.c
@@ -163,6 +163,92 @@ static int omap_dm_timer_of_set_source(struct omap_dm_timer *timer)
return ret;
}
+static int omap_dm_timer_set_source(struct omap_dm_timer *timer, int source)
+{
+ int ret;
+ char *parent_name = NULL;
+ struct clk *parent;
+ struct dmtimer_platform_data *pdata;
+
+ if (unlikely(!timer))
+ return -EINVAL;
+
+ pdata = timer->pdev->dev.platform_data;
+
+ if (source < 0 || source >= 3)
+ return -EINVAL;
+
+ /*
+ * FIXME: Used for OMAP1 devices only because they do not currently
+ * use the clock framework to set the parent clock. To be removed
+ * once OMAP1 migrated to using clock framework for dmtimers
+ */
+ if (pdata && pdata->set_timer_src)
+ return pdata->set_timer_src(timer->pdev, source);
+
+ if (IS_ERR(timer->fclk))
+ return -EINVAL;
+
+#if defined(CONFIG_COMMON_CLK)
+ /* Check if the clock has configurable parents */
+ if (clk_hw_get_num_parents(__clk_get_hw(timer->fclk)) < 2)
+ return 0;
+#endif
+
+ switch (source) {
+ case OMAP_TIMER_SRC_SYS_CLK:
+ parent_name = "timer_sys_ck";
+ break;
+
+ case OMAP_TIMER_SRC_32_KHZ:
+ parent_name = "timer_32k_ck";
+ break;
+
+ case OMAP_TIMER_SRC_EXT_CLK:
+ parent_name = "timer_ext_ck";
+ break;
+ }
+
+ parent = clk_get(&timer->pdev->dev, parent_name);
+ if (IS_ERR(parent)) {
+ pr_err("%s: %s not found\n", __func__, parent_name);
+ return -EINVAL;
+ }
+
+ ret = clk_set_parent(timer->fclk, parent);
+ if (ret < 0)
+ pr_err("%s: failed to set %s as parent\n", __func__,
+ parent_name);
+
+ clk_put(parent);
+
+ return ret;
+}
+
+static void omap_dm_timer_enable(struct omap_dm_timer *timer)
+{
+ int c;
+
+ pm_runtime_get_sync(&timer->pdev->dev);
+
+ if (!(timer->capability & OMAP_TIMER_ALWON)) {
+ if (timer->get_context_loss_count) {
+ c = timer->get_context_loss_count(&timer->pdev->dev);
+ if (c != timer->ctx_loss_count) {
+ omap_timer_restore_context(timer);
+ timer->ctx_loss_count = c;
+ }
+ } else {
+ omap_timer_restore_context(timer);
+ }
+ }
+}
+
+static void omap_dm_timer_disable(struct omap_dm_timer *timer)
+{
+ pm_runtime_put_sync(&timer->pdev->dev);
+}
+
static int omap_dm_timer_prepare(struct omap_dm_timer *timer)
{
int rc;
@@ -298,16 +384,16 @@ static struct omap_dm_timer *_omap_dm_timer_request(int req_type, void *data)
return timer;
}
-struct omap_dm_timer *omap_dm_timer_request(void)
+static struct omap_dm_timer *omap_dm_timer_request(void)
{
return _omap_dm_timer_request(REQUEST_ANY, NULL);
}
-struct omap_dm_timer *omap_dm_timer_request_specific(int id)
+static struct omap_dm_timer *omap_dm_timer_request_specific(int id)
{
/* Requesting timer by ID is not supported when device tree is used */
if (of_have_populated_dt()) {
- pr_warn("%s: Please use omap_dm_timer_request_by_cap/node()\n",
+ pr_warn("%s: Please use omap_dm_timer_request_by_node()\n",
__func__);
return NULL;
}
@@ -336,7 +422,7 @@ struct omap_dm_timer *omap_dm_timer_request_by_cap(u32 cap)
* Request a timer based upon a device node pointer. Returns pointer to
* timer handle on success and a NULL pointer on failure.
*/
-struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *np)
+static struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *np)
{
if (!np)
return NULL;
@@ -344,7 +430,7 @@ struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *np)
return _omap_dm_timer_request(REQUEST_BY_NODE, np);
}
-int omap_dm_timer_free(struct omap_dm_timer *timer)
+static int omap_dm_timer_free(struct omap_dm_timer *timer)
{
if (unlikely(!timer))
return -EINVAL;
@@ -356,30 +442,6 @@ int omap_dm_timer_free(struct omap_dm_timer *timer)
return 0;
}
-void omap_dm_timer_enable(struct omap_dm_timer *timer)
-{
- int c;
-
- pm_runtime_get_sync(&timer->pdev->dev);
-
- if (!(timer->capability & OMAP_TIMER_ALWON)) {
- if (timer->get_context_loss_count) {
- c = timer->get_context_loss_count(&timer->pdev->dev);
- if (c != timer->ctx_loss_count) {
- omap_timer_restore_context(timer);
- timer->ctx_loss_count = c;
- }
- } else {
- omap_timer_restore_context(timer);
- }
- }
-}
-
-void omap_dm_timer_disable(struct omap_dm_timer *timer)
-{
- pm_runtime_put_sync(&timer->pdev->dev);
-}
-
int omap_dm_timer_get_irq(struct omap_dm_timer *timer)
{
if (timer)
@@ -424,7 +486,7 @@ __u32 omap_dm_timer_modify_idlect_mask(__u32 inputmask)
#else
-struct clk *omap_dm_timer_get_fclk(struct omap_dm_timer *timer)
+static struct clk *omap_dm_timer_get_fclk(struct omap_dm_timer *timer)
{
if (timer && !IS_ERR(timer->fclk))
return timer->fclk;
@@ -451,7 +513,7 @@ int omap_dm_timer_trigger(struct omap_dm_timer *timer)
return 0;
}
-int omap_dm_timer_start(struct omap_dm_timer *timer)
+static int omap_dm_timer_start(struct omap_dm_timer *timer)
{
u32 l;
@@ -471,7 +533,7 @@ int omap_dm_timer_start(struct omap_dm_timer *timer)
return 0;
}
-int omap_dm_timer_stop(struct omap_dm_timer *timer)
+static int omap_dm_timer_stop(struct omap_dm_timer *timer)
{
unsigned long rate = 0;
@@ -494,70 +556,8 @@ int omap_dm_timer_stop(struct omap_dm_timer *timer)
return 0;
}
-int omap_dm_timer_set_source(struct omap_dm_timer *timer, int source)
-{
- int ret;
- char *parent_name = NULL;
- struct clk *parent;
- struct dmtimer_platform_data *pdata;
-
- if (unlikely(!timer))
- return -EINVAL;
-
- pdata = timer->pdev->dev.platform_data;
-
- if (source < 0 || source >= 3)
- return -EINVAL;
-
- /*
- * FIXME: Used for OMAP1 devices only because they do not currently
- * use the clock framework to set the parent clock. To be removed
- * once OMAP1 migrated to using clock framework for dmtimers
- */
- if (pdata && pdata->set_timer_src)
- return pdata->set_timer_src(timer->pdev, source);
-
- if (IS_ERR(timer->fclk))
- return -EINVAL;
-
-#if defined(CONFIG_COMMON_CLK)
- /* Check if the clock has configurable parents */
- if (clk_hw_get_num_parents(__clk_get_hw(timer->fclk)) < 2)
- return 0;
-#endif
-
- switch (source) {
- case OMAP_TIMER_SRC_SYS_CLK:
- parent_name = "timer_sys_ck";
- break;
-
- case OMAP_TIMER_SRC_32_KHZ:
- parent_name = "timer_32k_ck";
- break;
-
- case OMAP_TIMER_SRC_EXT_CLK:
- parent_name = "timer_ext_ck";
- break;
- }
-
- parent = clk_get(&timer->pdev->dev, parent_name);
- if (IS_ERR(parent)) {
- pr_err("%s: %s not found\n", __func__, parent_name);
- return -EINVAL;
- }
-
- ret = clk_set_parent(timer->fclk, parent);
- if (ret < 0)
- pr_err("%s: failed to set %s as parent\n", __func__,
- parent_name);
-
- clk_put(parent);
-
- return ret;
-}
-
-int omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload,
- unsigned int load)
+static int omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload,
+ unsigned int load)
{
u32 l;
@@ -582,8 +582,8 @@ int omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload,
}
/* Optimized set_load which removes costly spin wait in timer_start */
-int omap_dm_timer_set_load_start(struct omap_dm_timer *timer, int autoreload,
- unsigned int load)
+static int omap_dm_timer_set_load_start(struct omap_dm_timer *timer,
+ int autoreload, unsigned int load)
{
u32 l;
@@ -609,9 +609,8 @@ int omap_dm_timer_set_load_start(struct omap_dm_timer *timer, int autoreload,
timer->context.tcrr = load;
return 0;
}
-
-int omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable,
- unsigned int match)
+static int omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable,
+ unsigned int match)
{
u32 l;
@@ -634,8 +633,8 @@ int omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable,
return 0;
}
-int omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on,
- int toggle, int trigger)
+static int omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on,
+ int toggle, int trigger)
{
u32 l;
@@ -659,7 +658,8 @@ int omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on,
return 0;
}
-int omap_dm_timer_set_prescaler(struct omap_dm_timer *timer, int prescaler)
+static int omap_dm_timer_set_prescaler(struct omap_dm_timer *timer,
+ int prescaler)
{
u32 l;
@@ -681,8 +681,8 @@ int omap_dm_timer_set_prescaler(struct omap_dm_timer *timer, int prescaler)
return 0;
}
-int omap_dm_timer_set_int_enable(struct omap_dm_timer *timer,
- unsigned int value)
+static int omap_dm_timer_set_int_enable(struct omap_dm_timer *timer,
+ unsigned int value)
{
if (unlikely(!timer))
return -EINVAL;
@@ -704,7 +704,7 @@ int omap_dm_timer_set_int_enable(struct omap_dm_timer *timer,
*
* Disables the specified timer interrupts for a timer.
*/
-int omap_dm_timer_set_int_disable(struct omap_dm_timer *timer, u32 mask)
+static int omap_dm_timer_set_int_disable(struct omap_dm_timer *timer, u32 mask)
{
u32 l = mask;
@@ -727,7 +727,7 @@ int omap_dm_timer_set_int_disable(struct omap_dm_timer *timer, u32 mask)
return 0;
}
-unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer)
+static unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer)
{
unsigned int l;
@@ -741,7 +741,7 @@ unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer)
return l;
}
-int omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value)
+static int omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value)
{
if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev)))
return -EINVAL;
@@ -751,7 +751,7 @@ int omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value)
return 0;
}
-unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer)
+static unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer)
{
if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) {
pr_err("%s: timer not iavailable or enabled.\n", __func__);
@@ -761,7 +761,7 @@ unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer)
return __omap_dm_timer_read_counter(timer, timer->posted);
}
-int omap_dm_timer_write_counter(struct omap_dm_timer *timer, unsigned int value)
+static int omap_dm_timer_write_counter(struct omap_dm_timer *timer, unsigned int value)
{
if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) {
pr_err("%s: timer not available or enabled.\n", __func__);
diff --git a/include/clocksource/dmtimer.h b/include/clocksource/dmtimer.h
index 862ad62dab9d..ce596172a93a 100644
--- a/include/clocksource/dmtimer.h
+++ b/include/clocksource/dmtimer.h
@@ -125,37 +125,13 @@ struct omap_dm_timer {
};
int omap_dm_timer_reserve_systimer(int id);
-struct omap_dm_timer *omap_dm_timer_request(void);
-struct omap_dm_timer *omap_dm_timer_request_specific(int timer_id);
struct omap_dm_timer *omap_dm_timer_request_by_cap(u32 cap);
-struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *np);
-int omap_dm_timer_free(struct omap_dm_timer *timer);
-void omap_dm_timer_enable(struct omap_dm_timer *timer);
-void omap_dm_timer_disable(struct omap_dm_timer *timer);
int omap_dm_timer_get_irq(struct omap_dm_timer *timer);
u32 omap_dm_timer_modify_idlect_mask(u32 inputmask);
-struct clk *omap_dm_timer_get_fclk(struct omap_dm_timer *timer);
int omap_dm_timer_trigger(struct omap_dm_timer *timer);
-int omap_dm_timer_start(struct omap_dm_timer *timer);
-int omap_dm_timer_stop(struct omap_dm_timer *timer);
-
-int omap_dm_timer_set_source(struct omap_dm_timer *timer, int source);
-int omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload, unsigned int value);
-int omap_dm_timer_set_load_start(struct omap_dm_timer *timer, int autoreload, unsigned int value);
-int omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable, unsigned int match);
-int omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on, int toggle, int trigger);
-int omap_dm_timer_set_prescaler(struct omap_dm_timer *timer, int prescaler);
-
-int omap_dm_timer_set_int_enable(struct omap_dm_timer *timer, unsigned int value);
-int omap_dm_timer_set_int_disable(struct omap_dm_timer *timer, u32 mask);
-
-unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer);
-int omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value);
-unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer);
-int omap_dm_timer_write_counter(struct omap_dm_timer *timer, unsigned int value);
int omap_dm_timers_active(void);
--
2.15.1
^ permalink raw reply related
* [PATCH 2/5] clocksource: timer-dm: Check prescaler value
From: Ladislav Michl @ 2018-01-08 15:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153926.GA3916@lenoch>
Invalid value silently disables use of the prescaler.
Use -1 explicitely for that purpose and error out on
invalid value.
Signed-off-by: Ladislav Michl <ladis@linux-mips.org>
diff --git a/drivers/clocksource/timer-dm.c b/drivers/clocksource/timer-dm.c
index 43531eecbe54..bde1014308f9 100644
--- a/drivers/clocksource/timer-dm.c
+++ b/drivers/clocksource/timer-dm.c
@@ -663,13 +663,13 @@ static int omap_dm_timer_set_prescaler(struct omap_dm_timer *timer,
{
u32 l;
- if (unlikely(!timer))
+ if (unlikely(!timer) || prescaler < -1 || prescaler > 7)
return -EINVAL;
omap_dm_timer_enable(timer);
l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
l &= ~(OMAP_TIMER_CTRL_PRE | (0x07 << 2));
- if (prescaler >= 0x00 && prescaler <= 0x07) {
+ if (prescaler >= 0) {
l |= OMAP_TIMER_CTRL_PRE;
l |= prescaler << 2;
}
--
2.15.1
^ permalink raw reply related
* [PATCH 3/5] pwm: pwm-omap-dmtimer: Fix frequency when using prescaler
From: Ladislav Michl @ 2018-01-08 15:41 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153926.GA3916@lenoch>
Prescaler setting is currently not taken into account.
Fix that by introducing freq member variable and initialize
it at device probe time. This also avoids frequency
recomputing at each pwm configure time.
Signed-off-by: Ladislav Michl <ladis@linux-mips.org>
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c
index 3b27aff585b7..ee1cd92b1744 100644
--- a/drivers/pwm/pwm-omap-dmtimer.c
+++ b/drivers/pwm/pwm-omap-dmtimer.c
@@ -40,6 +40,7 @@ struct pwm_omap_dmtimer_chip {
pwm_omap_dmtimer *dm_timer;
struct omap_dm_timer_ops *pdata;
struct platform_device *dm_timer_pdev;
+ unsigned long freq;
};
static inline struct pwm_omap_dmtimer_chip *
@@ -48,9 +49,10 @@ to_pwm_omap_dmtimer_chip(struct pwm_chip *chip)
return container_of(chip, struct pwm_omap_dmtimer_chip, chip);
}
-static u32 pwm_omap_dmtimer_get_clock_cycles(unsigned long clk_rate, int ns)
+static inline u32
+pwm_omap_dmtimer_get_clock_cycles(struct pwm_omap_dmtimer_chip *omap, int ns)
{
- return DIV_ROUND_CLOSEST_ULL((u64)clk_rate * ns, NSEC_PER_SEC);
+ return DIV_ROUND_CLOSEST_ULL((u64)omap->freq * ns, NSEC_PER_SEC);
}
static void pwm_omap_dmtimer_start(struct pwm_omap_dmtimer_chip *omap)
@@ -99,8 +101,6 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip);
u32 period_cycles, duty_cycles;
u32 load_value, match_value;
- struct clk *fclk;
- unsigned long clk_rate;
bool timer_active;
dev_dbg(chip->dev, "requested duty cycle: %d ns, period: %d ns\n",
@@ -114,19 +114,6 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
return 0;
}
- fclk = omap->pdata->get_fclk(omap->dm_timer);
- if (!fclk) {
- dev_err(chip->dev, "invalid pmtimer fclk\n");
- goto err_einval;
- }
-
- clk_rate = clk_get_rate(fclk);
- if (!clk_rate) {
- dev_err(chip->dev, "invalid pmtimer fclk rate\n");
- goto err_einval;
- }
-
- dev_dbg(chip->dev, "clk rate: %luHz\n", clk_rate);
/*
* Calculate the appropriate load and match values based on the
@@ -144,35 +131,35 @@ static int pwm_omap_dmtimer_config(struct pwm_chip *chip,
* OMAP4430/60/70 TRM sections 22.2.4.10 and 22.2.4.11
* AM335x Sitara TRM sections 20.1.3.5 and 20.1.3.6
*/
- period_cycles = pwm_omap_dmtimer_get_clock_cycles(clk_rate, period_ns);
- duty_cycles = pwm_omap_dmtimer_get_clock_cycles(clk_rate, duty_ns);
+ period_cycles = pwm_omap_dmtimer_get_clock_cycles(omap, period_ns);
+ duty_cycles = pwm_omap_dmtimer_get_clock_cycles(omap, duty_ns);
if (period_cycles < 2) {
dev_info(chip->dev,
"period %d ns too short for clock rate %lu Hz\n",
- period_ns, clk_rate);
+ period_ns, omap->freq);
goto err_einval;
}
if (duty_cycles < 1) {
dev_dbg(chip->dev,
"duty cycle %d ns is too short for clock rate %lu Hz\n",
- duty_ns, clk_rate);
+ duty_ns, omap->freq);
dev_dbg(chip->dev, "using minimum of 1 clock cycle\n");
duty_cycles = 1;
} else if (duty_cycles >= period_cycles) {
dev_dbg(chip->dev,
"duty cycle %d ns is too long for period %d ns at clock rate %lu Hz\n",
- duty_ns, period_ns, clk_rate);
+ duty_ns, period_ns, omap->freq);
dev_dbg(chip->dev, "using maximum of 1 clock cycle less than period\n");
duty_cycles = period_cycles - 1;
}
dev_dbg(chip->dev, "effective duty cycle: %lld ns, period: %lld ns\n",
DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * duty_cycles,
- clk_rate),
+ omap->freq),
DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * period_cycles,
- clk_rate));
+ omap->freq));
load_value = (DM_TIMER_MAX - period_cycles) + 1;
match_value = load_value + duty_cycles - 1;
@@ -248,6 +235,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
struct dmtimer_platform_data *timer_pdata;
struct omap_dm_timer_ops *pdata;
pwm_omap_dmtimer *dm_timer;
+ struct clk *fclk;
u32 v;
int status;
@@ -311,12 +299,37 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
if (pm_runtime_active(&omap->dm_timer_pdev->dev))
omap->pdata->stop(omap->dm_timer);
- if (!of_property_read_u32(pdev->dev.of_node, "ti,prescaler", &v))
- omap->pdata->set_prescaler(omap->dm_timer, v);
-
/* setup dmtimer clock source */
- if (!of_property_read_u32(pdev->dev.of_node, "ti,clock-source", &v))
- omap->pdata->set_source(omap->dm_timer, v);
+ if (!of_property_read_u32(pdev->dev.of_node, "ti,clock-source", &v)) {
+ status = omap->pdata->set_source(omap->dm_timer, v);
+ if (status) {
+ dev_err(&pdev->dev, "invalid clock-source\n");
+ return status;
+ }
+ }
+
+ fclk = omap->pdata->get_fclk(omap->dm_timer);
+ if (!fclk) {
+ dev_err(&pdev->dev, "invalid fclk\n");
+ return -EINVAL;
+ }
+
+ omap->freq = clk_get_rate(fclk);
+ if (!omap->freq) {
+ dev_err(&pdev->dev, "invalid fclk rate\n");
+ return -EINVAL;
+ }
+
+ if (!of_property_read_u32(pdev->dev.of_node, "ti,prescaler", &v)) {
+ status = omap->pdata->set_prescaler(omap->dm_timer, v);
+ if (status) {
+ dev_err(&pdev->dev, "invalid prescaler\n");
+ return status;
+ }
+ omap->freq >>= v + 1;
+ }
+
+ dev_dbg(&pdev->dev, "clk rate: %luHz\n", omap->freq);
omap->chip.dev = &pdev->dev;
omap->chip.ops = &pwm_omap_dmtimer_ops;
--
2.15.1
^ permalink raw reply related
* [PATCH 4/5] clocksource: timer-dm: Add event capture
From: Ladislav Michl @ 2018-01-08 15:42 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153926.GA3916@lenoch>
Implement event capture functions.
Signed-off-by: Ladislav Michl <ladis@linux-mips.org>
diff --git a/drivers/clocksource/timer-dm.c b/drivers/clocksource/timer-dm.c
index bde1014308f9..dbf2b1f6a941 100644
--- a/drivers/clocksource/timer-dm.c
+++ b/drivers/clocksource/timer-dm.c
@@ -633,6 +633,30 @@ static int omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable,
return 0;
}
+static int omap_dm_timer_set_capture(struct omap_dm_timer *timer,
+ int captmode, int edges)
+{
+ u32 l;
+
+ if (unlikely(!timer))
+ return -EINVAL;
+
+ omap_dm_timer_enable(timer);
+ l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
+ l &= ~(OMAP_TIMER_CTRL_CAPTMODE | OMAP_TIMER_CTRL_SCPWM |
+ OMAP_TIMER_CTRL_PT | OMAP_TIMER_CTRL_TCM_BOTHEDGES);
+ l |= OMAP_TIMER_CTRL_GPOCFG;
+ if (captmode)
+ l |= OMAP_TIMER_CTRL_CAPTMODE;
+ l |= edges << 8;
+ omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l);
+
+ /* Save the context */
+ timer->context.tclr = l;
+ omap_dm_timer_disable(timer);
+ return 0;
+}
+
static int omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on,
int toggle, int trigger)
{
@@ -791,6 +815,22 @@ int omap_dm_timers_active(void)
return 0;
}
+static int omap_dm_timer_read_capture(struct omap_dm_timer *timer,
+ unsigned int *reg,
+ unsigned int *reg2)
+{
+ if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) {
+ pr_err("%s: timer not available or enabled.\n", __func__);
+ return -EINVAL;
+ }
+
+ *reg = omap_dm_timer_read_reg(timer, OMAP_TIMER_CAPTURE_REG);
+ if (reg2)
+ *reg2 = omap_dm_timer_read_reg(timer, OMAP_TIMER_CAPTURE2_REG);
+
+ return 0;
+}
+
static const struct of_device_id omap_timer_match[];
/**
@@ -939,11 +979,14 @@ static struct omap_dm_timer_ops dmtimer_ops = {
.start = omap_dm_timer_start,
.stop = omap_dm_timer_stop,
.set_load = omap_dm_timer_set_load,
+ .set_load_start = omap_dm_timer_set_load_start,
.set_match = omap_dm_timer_set_match,
+ .set_capture = omap_dm_timer_set_capture,
.set_pwm = omap_dm_timer_set_pwm,
.set_prescaler = omap_dm_timer_set_prescaler,
.read_counter = omap_dm_timer_read_counter,
.write_counter = omap_dm_timer_write_counter,
+ .read_capture = omap_dm_timer_read_capture,
.read_status = omap_dm_timer_read_status,
.write_status = omap_dm_timer_write_status,
};
diff --git a/include/linux/platform_data/dmtimer-omap.h b/include/linux/platform_data/dmtimer-omap.h
index a3e17945a0e9..ad247d45bc08 100644
--- a/include/linux/platform_data/dmtimer-omap.h
+++ b/include/linux/platform_data/dmtimer-omap.h
@@ -43,8 +43,12 @@ struct omap_dm_timer_ops {
int (*set_load)(struct omap_dm_timer *timer, int autoreload,
unsigned int value);
+ int (*set_load_start)(struct omap_dm_timer *timer, int autoreload,
+ unsigned int value);
int (*set_match)(struct omap_dm_timer *timer, int enable,
unsigned int match);
+ int (*set_capture)(struct omap_dm_timer *timer, int captmode,
+ int edges);
int (*set_pwm)(struct omap_dm_timer *timer, int def_on,
int toggle, int trigger);
int (*set_prescaler)(struct omap_dm_timer *timer, int prescaler);
@@ -52,6 +56,10 @@ struct omap_dm_timer_ops {
unsigned int (*read_counter)(struct omap_dm_timer *timer);
int (*write_counter)(struct omap_dm_timer *timer,
unsigned int value);
+
+ int (*read_capture)(struct omap_dm_timer *timer, unsigned int *reg,
+ unsigned int *reg2);
+
unsigned int (*read_status)(struct omap_dm_timer *timer);
int (*write_status)(struct omap_dm_timer *timer,
unsigned int value);
--
2.15.1
^ permalink raw reply related
* [net-next: PATCH 0/8] Armada 7k/8k PP2 ACPI support
From: Andrew Lunn @ 2018-01-08 15:42 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108151453.GB31502@xora-haswell>
w> I am not familiar with MDIO, but if its similar or a specific
> implementation of a serial bus that does sound sane!
It is a two wire serial bus. A good overview can be found here:
https://www.totalphase.com/support/articles/200349206-MDIO-Background
Andrew
^ permalink raw reply
* [PATCH v4 4/4] ARM: pinctrl: sunxi-pinctrl: fix pin funtion can not be match correctly.
From: Maxime Ripard @ 2018-01-08 15:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAJeuY79_w+8Gv=jRxK4tajiecNSAZkMGYY_d7PJmsjyOZHPkJw@mail.gmail.com>
On Mon, Jan 08, 2018 at 07:08:53PM +0800, Hao Zhang wrote:
> 2017-12-13 23:45 GMT+08:00 Maxime Ripard <maxime.ripard@free-electrons.com>:
> > Hi,
> >
> > Thanks for your patch!
> >
> > On Wed, Dec 13, 2017 at 10:47:48PM +0800, hao_zhang wrote:
> >> Pin function can not be match correctly when SUNXI_PIN describe with
> >> mutiple variant and same function.
> >>
> >> such as:
> >> on pinctrl-sun4i-a10.c
> >>
> >> SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 2),
> >> SUNXI_FUNCTION(0x0, "gpio_in"),
> >> SUNXI_FUNCTION(0x1, "gpio_out"),
> >> SUNXI_FUNCTION_VARIANT(0x2, "pwm", /* PWM0 */
> >> PINCTRL_SUN4I_A10 |
> >> PINCTRL_SUN7I_A20),
> >> SUNXI_FUNCTION_VARIANT(0x3, "pwm", /* PWM0 */
> >> PINCTRL_SUN8I_R40)),
> >>
> >> it would always match to the first variant function
> >> (PINCTRL_SUN4I_A10, PINCTRL_SUN7I_A20)
> >>
> >> so we should add variant compare on it.
> >>
> >> Signed-off-by: hao_zhang <hao5781286@gmail.com>
> >> ---
> >> drivers/pinctrl/sunxi/pinctrl-sunxi.c | 6 ++++--
> >> 1 file changed, 4 insertions(+), 2 deletions(-)
> >>
> >> diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
> >> index 4b6cb25..f23e74e 100644
> >> --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c
> >> +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
> >> @@ -83,9 +83,11 @@ sunxi_pinctrl_desc_find_function_by_name(struct sunxi_pinctrl *pctl,
> >> struct sunxi_desc_function *func = pin->functions;
> >>
> >> while (func->name) {
> >> - if (!strcmp(func->name, func_name))
> >> + if (!strcmp(func->name, func_name)) {
> >> + if (!(func->variant) ||
> >> + (func->variant & pctl->variant))
> >
> > I guess it would be better to have:
> > if (!strcmp(func->name, func_name) &&
> > (!func->variant || (func->variant & pctl->variant)))
>
> It would over 80 characters, can i change it by this ?
> if (!strcmp(func->name, func_name) &&
> (func->variant & pctl->variant ||
> !func->variant))
It feels more natural to have !func->variant first, but feel free to
have it split that way yes.
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20180108/a819570d/attachment-0001.sig>
^ permalink raw reply
* [PATCH] imx6: fix pcie enumeration
From: Lorenzo Pieralisi @ 2018-01-08 15:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1684b8c6-1006-948b-f4f9-c9aaf9cf26a8@ncentric.com>
On Mon, Jan 08, 2018 at 12:13:34PM +0100, Koen Vandeputte wrote:
>
>
> On 2018-01-08 12:00, Lorenzo Pieralisi wrote:
> >[+cc Joao, Jingoo]
> >
> >On Mon, Jan 08, 2018 at 09:51:37AM +0100, Koen Vandeputte wrote:
> >
> >[...]
> >
> >>[ Node 4 | node-4 ] lspci -v
> >>00:00.0 PCI bridge: Synopsys, Inc. Device abcd (rev 01) (prog-if 00
> >>[Normal decode])
> >> ??? Flags: bus master, fast devsel, latency 0, IRQ 298
> >> ??? Memory at 01000000 (32-bit, non-prefetchable) [size=1M]
> >> ??? Bus: primary=00, secondary=01, subordinate=01, sec-latency=0
> > ^^^^^^^^^^^^^^
> >
> >So basically, the subordinate number in the root port does not
> >affect config space forwarding from what I see and it has always
> >been like that for dwc.
> >
> >You are forced to update it to 0xff because otherwise the kernel
> >stops enumerating bus numbers > 1
> Indeed, which affects all devices using Designware PCIe init + a
> PCIe bridge downstream
> >but that's a software issue
> >not HW - the subordinate bus number does not seem to affect anything
> >here.
>
> >Sigh.
> >
> >Another option would consist in forcing the kernel to reassign
> >all bus numbers by setting the PCI_REASSIGN_ALL_BUS flag but
> >that's not a good idea given how inconsistent that flag usage is.
> >
> >I think that updating the subordinate bus numbers in the DWC
> >config register is the correct solution to make sure the kernel
> >won't get confused anymore by what seems to be a fake root port,
> >I need input from DWC maintainers to confirm my understanding.
> >
> >Thanks,
> >Lorenzo
> >
>
> The patch I'm currently using internally:
>
>
> --- a/drivers/pci/host/pcie-designware.c
> +++ b/drivers/pci/host/pcie-designware.c
> @@ -861,7 +861,7 @@ void dw_pcie_setup_rc(struct pcie_port *
> ???? /* setup bus numbers */
> ???? val = dw_pcie_readl_rc(pp, PCI_PRIMARY_BUS);
> ???? val &= 0xff000000;
> -??? val |= 0x00010100;
> +??? val |= 0x00ff0100;
> ???? dw_pcie_writel_rc(pp, PCI_PRIMARY_BUS, val);
>
> ???? /* setup command register */
>
>
> Above version logically fixes it for all dwc devices using a bridge
> after the RC, not only imx6.
> If this is fine, I would submit the patch above and drop the current one.
It is fine by me but I won't merge it till I get ACKs and tested-by
from the respective maintainers - it can have potential widespread
impact.
> Backporting this to stable kernels (4.9 .. 4.4 .. etc) will fix all
> nasty warnings on these setups during boot without any change in
> functionality.
> These kernels will require a separate patch as this source file got
> moved & renamed.
> Thanks for your time and analysis so far,
Thank you for reporting it and fixing it.
Lorenzo
^ permalink raw reply
* [PATCH v5 3/9] drm: Add Content Protection property
From: Sean Paul @ 2018-01-08 15:50 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108155029.244552-1-seanpaul@chromium.org>
This patch adds a new optional connector property to allow userspace to enable
protection over the content it is displaying. This will typically be implemented
by the driver using HDCP.
The property is a tri-state with the following values:
- OFF: Self explanatory, no content protection
- DESIRED: Userspace requests that the driver enable protection
- ENABLED: Once the driver has authenticated the link, it sets this value
The driver is responsible for downgrading ENABLED to DESIRED if the link becomes
unprotected. The driver should also maintain the desiredness of protection
across hotplug/dpms/suspend.
If this looks familiar, I posted [1] this 3 years ago. We have been using this
in ChromeOS across exynos, mediatek, and rockchip over that time.
Changes in v2:
- Pimp kerneldoc for content_protection_property (Daniel)
- Drop sysfs attribute
Changes in v3:
- None
Changes in v4:
- Changed kerneldoc to recommend userspace polling (Daniel)
- Changed kerneldoc to briefly describe how to attach the property (Daniel)
Changes in v5:
- checkpatch whitespace noise
- Change DRM_MODE_CONTENT_PROTECTION_OFF to DRM_MODE_CONTENT_PROTECTION_UNDESIRED
Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
[1] https://lists.freedesktop.org/archives/dri-devel/2014-December/073336.html
---
drivers/gpu/drm/drm_atomic.c | 8 +++++
drivers/gpu/drm/drm_connector.c | 78 +++++++++++++++++++++++++++++++++++++++++
include/drm/drm_connector.h | 16 +++++++++
include/uapi/drm/drm_mode.h | 4 +++
4 files changed, 106 insertions(+)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index b76d49218cf1..69ff763a834e 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -1224,6 +1224,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
state->picture_aspect_ratio = val;
} else if (property == connector->scaling_mode_property) {
state->scaling_mode = val;
+ } else if (property == connector->content_protection_property) {
+ if (val == DRM_MODE_CONTENT_PROTECTION_ENABLED) {
+ DRM_DEBUG_KMS("only drivers can set CP Enabled\n");
+ return -EINVAL;
+ }
+ state->content_protection = val;
} else if (connector->funcs->atomic_set_property) {
return connector->funcs->atomic_set_property(connector,
state, property, val);
@@ -1303,6 +1309,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
*val = state->picture_aspect_ratio;
} else if (property == connector->scaling_mode_property) {
*val = state->scaling_mode;
+ } else if (property == connector->content_protection_property) {
+ *val = state->content_protection;
} else if (connector->funcs->atomic_get_property) {
return connector->funcs->atomic_get_property(connector,
state, property, val);
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 2559c615d984..b85a7749709d 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -756,6 +756,13 @@ static const struct drm_prop_enum_list drm_tv_subconnector_enum_list[] = {
DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name,
drm_tv_subconnector_enum_list)
+static struct drm_prop_enum_list drm_cp_enum_list[] = {
+ { DRM_MODE_CONTENT_PROTECTION_UNDESIRED, "Undesired" },
+ { DRM_MODE_CONTENT_PROTECTION_DESIRED, "Desired" },
+ { DRM_MODE_CONTENT_PROTECTION_ENABLED, "Enabled" },
+};
+DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list)
+
/**
* DOC: standard connector properties
*
@@ -826,6 +833,41 @@ DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name,
* Indicates the output should be ignored for purposes of displaying a
* standard desktop environment or console. This is most likely because
* the output device is not rectilinear.
+ * Content Protection:
+ * This property is used by userspace to request the kernel protect future
+ * content communicated over the link. When requested, kernel will apply
+ * the appropriate means of protection (most often HDCP), and use the
+ * property to tell userspace the protection is active.
+ *
+ * Drivers can set this up by calling
+ * drm_connector_attach_content_protection_property() on initialization.
+ *
+ * The value of this property can be one of the following:
+ *
+ * - DRM_MODE_CONTENT_PROTECTION_UNDESIRED = 0
+ * The link is not protected, content is transmitted in the clear.
+ * - DRM_MODE_CONTENT_PROTECTION_DESIRED = 1
+ * Userspace has requested content protection, but the link is not
+ * currently protected. When in this state, kernel should enable
+ * Content Protection as soon as possible.
+ * - DRM_MODE_CONTENT_PROTECTION_ENABLED = 2
+ * Userspace has requested content protection, and the link is
+ * protected. Only the driver can set the property to this value.
+ * If userspace attempts to set to ENABLED, kernel will return
+ * -EINVAL.
+ *
+ * A few guidelines:
+ *
+ * - DESIRED state should be preserved until userspace de-asserts it by
+ * setting the property to UNDESIRED. This means ENABLED should only
+ * transition to UNDESIRED when the user explicitly requests it.
+ * - If the state is DESIRED, kernel should attempt to re-authenticate the
+ * link whenever possible. This includes across disable/enable, dpms,
+ * hotplug, downstream device changes, link status failures, etc..
+ * - Userspace is responsible for polling the property to determine when
+ * the value transitions from ENABLED to DESIRED. This signifies the link
+ * is no longer protected and userspace should take appropriate action
+ * (whatever that might be).
*
* Connectors also have one standardized atomic property:
*
@@ -1126,6 +1168,42 @@ int drm_connector_attach_scaling_mode_property(struct drm_connector *connector,
}
EXPORT_SYMBOL(drm_connector_attach_scaling_mode_property);
+/**
+ * drm_connector_attach_content_protection_property - attach content protection
+ * property
+ *
+ * @connector: connector to attach CP property on.
+ *
+ * This is used to add support for content protection on select connectors.
+ * Content Protection is intentionally vague to allow for different underlying
+ * technologies, however it is most implemented by HDCP.
+ *
+ * The content protection will be set to &drm_connector_state.content_protection
+ *
+ * Returns:
+ * Zero on success, negative errno on failure.
+ */
+int drm_connector_attach_content_protection_property(
+ struct drm_connector *connector)
+{
+ struct drm_device *dev = connector->dev;
+ struct drm_property *prop;
+
+ prop = drm_property_create_enum(dev, 0, "Content Protection",
+ drm_cp_enum_list,
+ ARRAY_SIZE(drm_cp_enum_list));
+ if (!prop)
+ return -ENOMEM;
+
+ drm_object_attach_property(&connector->base, prop,
+ DRM_MODE_CONTENT_PROTECTION_UNDESIRED);
+
+ connector->content_protection_property = prop;
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_connector_attach_content_protection_property);
+
/**
* drm_mode_create_aspect_ratio_property - create aspect ratio property
* @dev: DRM device
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index ed38df4ac204..758a176e7b57 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -419,6 +419,12 @@ struct drm_connector_state {
* upscaling, mostly used for built-in panels.
*/
unsigned int scaling_mode;
+
+ /**
+ * @content_protection: Connector property to request content
+ * protection. This is most commonly used for HDCP.
+ */
+ unsigned int content_protection;
};
/**
@@ -766,6 +772,7 @@ struct drm_cmdline_mode {
* @tile_h_size: horizontal size of this tile.
* @tile_v_size: vertical size of this tile.
* @scaling_mode_property: Optional atomic property to control the upscaling.
+ * @content_protection_property: Optional property to control content protection
*
* Each connector may be connected to one or more CRTCs, or may be clonable by
* another connector if they can share a CRTC. Each connector also has a specific
@@ -856,6 +863,12 @@ struct drm_connector {
struct drm_property *scaling_mode_property;
+ /**
+ * @content_protection_property: DRM ENUM property for content
+ * protection
+ */
+ struct drm_property *content_protection_property;
+
/**
* @path_blob_ptr:
*
@@ -1065,6 +1078,7 @@ const char *drm_get_dvi_i_subconnector_name(int val);
const char *drm_get_dvi_i_select_name(int val);
const char *drm_get_tv_subconnector_name(int val);
const char *drm_get_tv_select_name(int val);
+const char *drm_get_content_protection_name(int val);
int drm_mode_create_dvi_i_properties(struct drm_device *dev);
int drm_mode_create_tv_properties(struct drm_device *dev,
@@ -1073,6 +1087,8 @@ int drm_mode_create_tv_properties(struct drm_device *dev,
int drm_mode_create_scaling_mode_property(struct drm_device *dev);
int drm_connector_attach_scaling_mode_property(struct drm_connector *connector,
u32 scaling_mode_mask);
+int drm_connector_attach_content_protection_property(
+ struct drm_connector *connector);
int drm_mode_create_aspect_ratio_property(struct drm_device *dev);
int drm_mode_create_suggested_offset_properties(struct drm_device *dev);
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index 5597a87154e5..d1a69ff24fe8 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -173,6 +173,10 @@ extern "C" {
DRM_MODE_REFLECT_X | \
DRM_MODE_REFLECT_Y)
+/* Content Protection Flags */
+#define DRM_MODE_CONTENT_PROTECTION_UNDESIRED 0
+#define DRM_MODE_CONTENT_PROTECTION_DESIRED 1
+#define DRM_MODE_CONTENT_PROTECTION_ENABLED 2
struct drm_mode_modeinfo {
__u32 clock;
--
2.16.0.rc0.223.g4a4ac83678-goog
^ permalink raw reply related
* [PATCH v6 13/16] arm64: acpi: Remove __init from acpi_psci_use_hvc() for use by SDEI
From: Lorenzo Pieralisi @ 2018-01-08 15:51 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180108153818.22743-14-james.morse@arm.com>
On Mon, Jan 08, 2018 at 03:38:15PM +0000, James Morse wrote:
> SDEI inherits the 'use hvc' bit that is also used by PSCI. PSCI does all
> its initialisation early, SDEI does its late.
>
> Remove the __init annotation from acpi_psci_use_hvc().
>
> Signed-off-by: James Morse <james.morse@arm.com>
> Acked-by: Catalin Marinas <catalin.marinas@arm.com>
> ---
> The function name is unchanged as this bit is named 'PSCI_USE_HVC'
> in table 5-37 of ACPIv6.2.
>
> arch/arm64/kernel/acpi.c | 2 +-
> include/linux/psci.h | 3 ++-
> 2 files changed, 3 insertions(+), 2 deletions(-)
Acked-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
> diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c
> index b3162715ed78..252396a96c78 100644
> --- a/arch/arm64/kernel/acpi.c
> +++ b/arch/arm64/kernel/acpi.c
> @@ -117,7 +117,7 @@ bool __init acpi_psci_present(void)
> }
>
> /* Whether HVC must be used instead of SMC as the PSCI conduit */
> -bool __init acpi_psci_use_hvc(void)
> +bool acpi_psci_use_hvc(void)
> {
> return acpi_gbl_FADT.arm_boot_flags & ACPI_FADT_PSCI_USE_HVC;
> }
> diff --git a/include/linux/psci.h b/include/linux/psci.h
> index 6306ab10af18..f724fd8c78e8 100644
> --- a/include/linux/psci.h
> +++ b/include/linux/psci.h
> @@ -47,10 +47,11 @@ static inline int psci_dt_init(void) { return 0; }
> #if defined(CONFIG_ARM_PSCI_FW) && defined(CONFIG_ACPI)
> int __init psci_acpi_init(void);
> bool __init acpi_psci_present(void);
> -bool __init acpi_psci_use_hvc(void);
> +bool acpi_psci_use_hvc(void);
> #else
> static inline int psci_acpi_init(void) { return 0; }
> static inline bool acpi_psci_present(void) { return false; }
> +static inline bool acpi_psci_use_hvc(void) {return false; }
> #endif
>
> #endif /* __LINUX_PSCI_H */
> --
> 2.15.0
>
^ permalink raw reply
* [PATCH v6 08/10] pwm: pwm-omap-dmtimer: Adapt driver to utilize dmtimer pdata ops
From: Keerthy @ 2018-01-08 16:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <eb51e181-8203-0469-d013-dfd40f7da95d@microchip.com>
On 1/8/2018 8:17 PM, Claudiu Beznea wrote:
>
>
> On 08.01.2018 16:44, Neil Armstrong wrote:
>> On 08/01/2018 15:33, Keerthy wrote:
>>>
>>>
>>> On Monday 08 January 2018 02:14 PM, Claudiu Beznea wrote:
>>>>
>>>>
>>>> On 02.01.2018 12:09, Keerthy wrote:
>>>>> Adapt driver to utilize dmtimer pdata ops instead of pdata-quirks.
>>>>>
>>>>> Signed-off-by: Keerthy <j-keerthy@ti.com>
>>>>> Tested-by: Ladislav Michl <ladis@linux-mips.org>
>>>>> ---
>>>>> drivers/pwm/pwm-omap-dmtimer.c | 39 ++++++++++++++++++++++-----------------
>>>>> 1 file changed, 22 insertions(+), 17 deletions(-)
>>>>>
>>>>> diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c
>>>>> index 5ad42f3..3b27aff 100644
>>>>> --- a/drivers/pwm/pwm-omap-dmtimer.c
>>>>> +++ b/drivers/pwm/pwm-omap-dmtimer.c
>>>>> @@ -23,6 +23,7 @@
>>>>> #include <linux/mutex.h>
>>>>> #include <linux/of.h>
>>>>> #include <linux/of_platform.h>
>>>>> +#include <linux/platform_data/dmtimer-omap.h>
>>>>> #include <linux/platform_data/pwm_omap_dmtimer.h>
>>>>> #include <linux/platform_device.h>
>>>>> #include <linux/pm_runtime.h>
>>>>> @@ -37,7 +38,7 @@ struct pwm_omap_dmtimer_chip {
>>>>> struct pwm_chip chip;
>>>>> struct mutex mutex;
>>>>> pwm_omap_dmtimer *dm_timer;
>>>>> - struct pwm_omap_dmtimer_pdata *pdata;
>>>>> + struct omap_dm_timer_ops *pdata;
>>>>> struct platform_device *dm_timer_pdev;
>>>>> };
>>>>>
>>>>> @@ -242,19 +243,33 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
>>>>> {
>>>>> struct device_node *np = pdev->dev.of_node;
>>>>> struct device_node *timer;
>>>>> + struct platform_device *timer_pdev;
>>>>> struct pwm_omap_dmtimer_chip *omap;
>>>>> - struct pwm_omap_dmtimer_pdata *pdata;
>>>>> + struct dmtimer_platform_data *timer_pdata;
>>>>> + struct omap_dm_timer_ops *pdata;
>>>>> pwm_omap_dmtimer *dm_timer;
>>>>> u32 v;
>>>>> int status;
>>>>>
>>>>> - pdata = dev_get_platdata(&pdev->dev);
>>>>> - if (!pdata) {
>>>>> - dev_err(&pdev->dev, "Missing dmtimer platform data\n");
>>>>> + timer = of_parse_phandle(np, "ti,timers", 0);
>>>> of_node_put() should be called when done with device_node pointer returned
>>>> by of_parse_phandle() (you may want to check the return ERROR cases below
>>>> regarding this statement):
>>>>> + if (!timer)
>>>>> + return -ENODEV;
>>>>> +
>>>>> + timer_pdev = of_find_device_by_node(timer);
>>>>> + if (!timer_pdev) {
>>>>> + dev_err(&pdev->dev, "Unable to find Timer pdev\n");
>>>> here
>>>>> + return -ENODEV;
>>>>> + }
>>>>> +
>>>>> + timer_pdata = dev_get_platdata(&timer_pdev->dev);
>>>>> + if (!timer_pdata) {
>>>>> + dev_err(&pdev->dev, "dmtimer pdata structure NULL\n");
>>>> here
>>>>> return -EINVAL;
>>>>> }
>>>>>
>>>>> - if (!pdata->request_by_node ||
>>>>> + pdata = timer_pdata->timer_ops;
>>>>> +
>>>>> + if (!pdata || !pdata->request_by_node ||
>>>>> !pdata->free ||
>>>>> !pdata->enable ||
>>>>> !pdata->disable ||
>>>>> @@ -270,10 +285,6 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
>>>>> return -EINVAL;
>>>>> }
>>>>>
>>>>> - timer = of_parse_phandle(np, "ti,timers", 0);
>>>>> - if (!timer)
>>>>> - return -ENODEV;
>>>>> -
>>>>> if (!of_get_property(timer, "ti,timer-pwm", NULL)) {
>>>> here
>>>>> dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n");
>>>>> return -ENODEV;
>>>>> @@ -291,13 +302,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev)
>>>>>
>>>>> omap->pdata = pdata;
>>>>> omap->dm_timer = dm_timer;
>>>>> -
>>>>> - omap->dm_timer_pdev = of_find_device_by_node(timer);
>>>>> - if (!omap->dm_timer_pdev) {
>>>>> - dev_err(&pdev->dev, "Unable to find timer pdev\n");
>>>>> - omap->pdata->free(dm_timer);
>>>>> - return -EINVAL;
>>>>> - }
>>>>> + omap->dm_timer_pdev = timer_pdev;
>>>>>
>>>>> /*
>>>>> * Ensure that the timer is stopped before we allow PWM core to call
>>>>>
>>>> And all the other return instructions from probe function not listed by git diff
>>>
>>> Thanks for reviewing. I will add the of_node_put call for all the error
>>> paths.
> After that you can add: Reviwed-by: Claudiu Beznea <claudiu.beznea@microchip.com>
>>>
>>>>
>>>
>>> _______________________________________________
>>> linux-arm-kernel mailing list
>>> linux-arm-kernel at lists.infradead.org
>>> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
>>>
>>
>> Apart the missing of_node_put() :
>>
>> Acked-by: Neil Armstrong <narmstrong@baylibre.com>
Thanks Neil and Claudiu.
>>
^ permalink raw reply
* [PATCH v5 07/13] KVM: arm/arm64: mask/unmask daif around VHE guests
From: James Morse @ 2018-01-08 16:26 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20171215155101.23505-8-james.morse@arm.com>
Hi,
On 15/12/17 15:50, James Morse wrote:
> Non-VHE systems take an exception to EL2 in order to world-switch into the
> guest. When returning from the guest KVM implicitly restores the DAIF
> flags when it returns to the kernel at EL1.
>
> With VHE none of this exception-level jumping happens, so KVMs
> world-switch code is exposed to the host kernel's DAIF values, and KVM
> spills the guest-exit DAIF values back into the host kernel.
> On entry to a guest we have Debug and SError exceptions unmasked, KVM
> has switched VBAR but isn't prepared to handle these. On guest exit
> Debug exceptions are left disabled once we return to the host and will
> stay this way until we enter user space.
>
> Add a helper to mask/unmask DAIF around VHE guests. The unmask can only
> happen after the hosts VBAR value has been synchronised by the isb in
> __vhe_hyp_call (via kvm_call_hyp()). Masking could be as late as
> setting KVMs VBAR value, but is kept here for symmetry.
>
> Signed-off-by: James Morse <james.morse@arm.com>
> ---
> This isn't backportable because of the 'daif' helpers, I will produce a
> backport once its merged.
>
> Changes since v4:
> * Added empty declarations for 32bit. (how did I miss that?)
v4 of this patch had a Reviewed-by Christoffer, which I didn't pick up as I then
went on to confuse everyone...
https://patchwork.kernel.org/patch/10017467/
(Sorry Christoffer!)
Thanks,
James
^ permalink raw reply
* [PATCH v5 13/13] KVM: arm64: Emulate RAS error registers and set HCR_EL2's TERR & TEA
From: James Morse @ 2018-01-08 16:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20171215155101.23505-14-james.morse@arm.com>
Hi,
On 15/12/17 15:51, James Morse wrote:
> From: Dongjiu Geng <gengdongjiu@huawei.com>
>
> ARMv8.2 adds a new bit HCR_EL2.TEA which routes synchronous external
> aborts to EL2, and adds a trap control bit HCR_EL2.TERR which traps
> all Non-secure EL1&0 error record accesses to EL2.
>
> This patch enables the two bits for the guest OS, guaranteeing that
> KVM takes external aborts and traps attempts to access the physical
> error registers.
>
> ERRIDR_EL1 advertises the number of error records, we return
> zero meaning we can treat all the other registers as RAZ/WI too.
>
> Signed-off-by: Dongjiu Geng <gengdongjiu@huawei.com>
> [removed specific emulation, use trap_raz_wi() directly for everything,
> rephrased parts of the commit message]
> Signed-off-by: James Morse <james.morse@arm.com>
v4 of this patch had a Reviewed-by Marc and Christoffer, which I didn't pick up
because I'm an idiot:
https://patchwork.kernel.org/patch/10017537/
Thanks,
James
^ permalink raw reply
* [PATCH v5 01/44] dt-bindings: clock: Add new bindings for TI Davinci PLL clocks
From: David Lechner @ 2018-01-08 16:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <ebd677dc-44a4-5053-2509-e3ee948ccf98@ti.com>
On 01/08/2018 08:00 AM, Sekhar Nori wrote:
> On Monday 08 January 2018 07:47 AM, David Lechner wrote:
>> This adds a new binding for the PLL IP blocks in the mach-davinci family
>> of processors. Currently, only the SYSCLKn and AUXCLK outputs are needed,
>> but in the future additional child nodes could be added for OBSCLK and
>> BPDIV.
>>
>> Note: Although these PLL controllers are very similar to the TI Keystone
>> SoCs, we are not re-using those bindings. The Keystone bindings use a
>> legacy one-node-per-clock binding. Furthermore, the mach-davinici SoCs
>
> Not sure what is meant by "legacy one-node-per-clock binding"
It's a term I picked up from of_clk_detect_critical()
* Do not use this function. It exists only for legacy Device Tree
* bindings, such as the one-clock-per-node style that are outdated.
* Those bindings typically put all clock data into .dts and the Linux
* driver has no clock data, thus making it impossible to set this flag
* correctly from the driver. Only those drivers may call
* of_clk_detect_critical from their setup functions.
>
>> have a slightly different PLL register layout and a number of quirks that
>> can't be handled by the existing bindings, so the keystone bindings could
>> not be used as-is anyway.
>
> Right, I think different register layout between the processors is the
> main reason for a new driver. This should be sufficient reason IMO.
>
>>
>> Signed-off-by: David Lechner <david@lechnology.com>
>> ---
>> .../devicetree/bindings/clock/ti/davinci/pll.txt | 47 ++++++++++++++++++++++
>> 1 file changed, 47 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/clock/ti/davinci/pll.txt
>>
>> diff --git a/Documentation/devicetree/bindings/clock/ti/davinci/pll.txt b/Documentation/devicetree/bindings/clock/ti/davinci/pll.txt
>> new file mode 100644
>> index 0000000..99bf5da
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/clock/ti/davinci/pll.txt
>> @@ -0,0 +1,47 @@
>> +Binding for TI DaVinci PLL Controllers
>> +
>> +The PLL provides clocks to most of the components on the SoC. In addition
>> +to the PLL itself, this controller also contains bypasses, gates, dividers,
>> +an multiplexers for various clock signals.
>> +
>> +Required properties:
>> +- compatible: shall be one of:
>> + - "ti,da850-pll0" for PLL0 on DA850/OMAP-L138/AM18XX
>> + - "ti,da850-pll1" for PLL1 on DA850/OMAP-L138/AM18XX
>
> These PLLs are same IP so they should use the same compatible. You can
> initialize both PLLs for DA850 based on the same compatible.
>
But they are not exactly the same. For example, PLL0 has 7 PLLDIV clocks while
PLL1 only has 3. PLL0 has PREDIV while PLL1 does not. PLL0 has certain SYSCLKs
that are fixed-ratio but PLL1 does not have any of these. There are even more
differences, but these are the ones we are actually using.
So, if we use the same compatible, we either have to come up with device tree
bindings to describe all of this (yuck) or I suppose we can look at the REVID
register to electronically determine exactly what we have. I went with the
simpler option of just creating two different compatible strings.
^ permalink raw reply
* [PATCH] ARM: realview: remove eb-mp clcd IRQ
From: Robin Murphy @ 2018-01-08 16:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CACRpkdbCWZB1ZkrdGe0s0GfNVWEYY+NThy4ce-+SBtU-s=WNbQ@mail.gmail.com>
Hi Linus,
On 21/12/17 22:08, Linus Walleij wrote:
> On Thu, Dec 21, 2017 at 10:31 PM, Arnd Bergmann <arnd@arndb.de> wrote:
>
>> We get a dtc warning about the CLCD interrupt being invalid:
>>
>> arch/arm/boot/dts/arm-realview-eb-11mp-ctrevb.dtb: Warning (interrupts_property): interrupts size is (8), expected multiple of 12 in /fpga/charlcd at 10008000
>>
>> According to the datasheet I found and the old board file, this
>> line is not connected, so I'm removing the respective properties here.
>>
>> Link: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0411d/index.html
>> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
>
> There is some confusion here. There is CLCD "Color LCD"
> which is just a code name for PrimeCell PL111 and there is the actual
> character LCD which is a hardware thin to talk to a character LCD with
> some characters on.
>
>> diff --git a/arch/arm/boot/dts/arm-realview-eb-mp.dtsi b/arch/arm/boot/dts/arm-realview-eb-mp.dtsi
>
> So this DTS is for the ARM 11 MP core tile which is described in
> DUI0318F. It doesn't even list an IRQ for the character LCD.
>
>> &charlcd {
>> - interrupt-parent = <&intc>;
>> - interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
>
> This was probably me thinking to go back and fill in the right
> IRQ and forgetting to actually do it. Sorry :(
>
>> + /* CLCD is not connected here */
>
> Call it character LCD instead to avoid confusion please.
>
>> + /delete-property/interrupt-parent;
>> + /delete-property/interrupts;
>
> I don't understand this delete-property business (first time
> I see it, but the top level
> DTSI (arm-realview-eb.dtsi) does not define any interrupt
> so can't you just delete this whole &charlcd?
>
> I do think the reference design has a character LCD, and I
> do think it has an interrupt, it's just undocumented so
> someone with this board would have to test it manually
> to figure out which line it is. Whoever uses this design
> will get to it if ever.
FWIW the EB baseboard is *physically* the same regardless of the CPU,
it's just flashed with a Core-Tile-specific FPGA bitstream. I've just
tried firing up an 11MPCore one, and indeed the character LCD does light
up with the kernel version. I can't convince the recalcitrant beast to
actually get to userspace, though, so I can't confirm what the
interrupt's deal is.
The baseboard manual (DUI0303E) says it's interrupt 22 on the
board-level secondary GICs, and since neither the CT11MP nor its
corresponding FPGA (AN152) mention any alternate routing direct to the
Core Tile GIC, I'd guess it probably still is. On the other hand,
though, it also says this:
"... However this interrupt signal is reserved for future use and you
must use a polling routine instead of an interrupt service routine."
So maybe it's appropriate to just remove the interrupt everywhere :/
Robin.
^ permalink raw reply
* [PATCH 2/2] arm64: Branch predictor hardening for Cavium ThunderX2
From: Will Deacon @ 2018-01-08 16:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1515394416-166994-2-git-send-email-jnair@caviumnetworks.com>
On Sun, Jan 07, 2018 at 10:53:36PM -0800, Jayachandran C wrote:
> Use PSCI based mitigation for speculative execution attacks targeting
> the branch predictor. The approach is similar to the one used for
> Cortex-A CPUs, but in case of ThunderX2 we add another SMC call to
> test if the firmware supports the capability.
>
> If the secure firmware has been updated with the mitigation code to
> invalidate the branch target buffer, we use the PSCI version call to
> invoke it.
>
> Signed-off-by: Jayachandran C <jnair@caviumnetworks.com>
> ---
> arch/arm64/kernel/cpu_errata.c | 38 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 38 insertions(+)
>
> diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
> index cb0fb37..abceb5d 100644
> --- a/arch/arm64/kernel/cpu_errata.c
> +++ b/arch/arm64/kernel/cpu_errata.c
> @@ -124,6 +124,7 @@ static void install_bp_hardening_cb(const struct arm64_cpu_capabilities *entry,
> __install_bp_hardening_cb(fn, hyp_vecs_start, hyp_vecs_end);
> }
>
> +#include <linux/arm-smccc.h>
> #include <linux/psci.h>
>
> static int enable_psci_bp_hardening(void *data)
> @@ -138,6 +139,33 @@ static int enable_psci_bp_hardening(void *data)
>
> return 0;
> }
> +
> +#define CAVIUM_TX2_SIP_SMC_CALL 0xC200FF00
> +#define CAVIUM_TX2_BTB_HARDEN_CAP 0xB0A0
> +
> +static int enable_tx2_psci_bp_hardening(void *data)
> +{
> + const struct arm64_cpu_capabilities *entry = data;
> + struct arm_smccc_res res;
> +
> + if (!entry->matches(entry, SCOPE_LOCAL_CPU))
> + return;
> +
> + arm_smccc_smc(CAVIUM_TX2_SIP_SMC_CALL, CAVIUM_TX2_BTB_HARDEN_CAP, 0, 0, 0, 0, 0, 0, &res);
One thing to be aware of here is that if somebody configures qemu to emulate
a TX2, this may actually disappear into EL3 and not return. You're better
off sticking with PSCI GET_VERSION in terms of portability, but it's your
call -- I'd expect you to deal with any breakage reports on the list due
to the SMC above. Fair?
> + if (res.a0 != 0) {
> + pr_warn("Error: CONFIG_HARDEN_BRANCH_PREDICTOR enabled, but firmware does not support it\n");
> + return 0;
> + }
Please don't print this here; see below.
> + if (res.a1 == 1 && psci_ops.get_version) {
> + pr_info("CPU%d: Branch predictor hardening enabled\n", smp_processor_id());
If you want to print a message, please put it in the capability structure
.desc field.
Will
^ permalink raw reply
* [PATCH v2 1/5] pinctrl: imx: use struct imx_pinctrl_soc_info as a const
From: Gary Bisson @ 2018-01-08 16:48 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180106142553.15322-2-stefan@agner.ch>
Hi Stefan,
On Sat, Jan 06, 2018 at 03:25:49PM +0100, Stefan Agner wrote:
> For some SoCs the struct imx_pinctrl_soc_info is passed through
> of_device_id.data which is const. Most variables are already const
> or otherwise not written. However, some fields are modified at
> runtime. Move those fields to the dynamically allocated struct
> imx_pinctrl.
>
> Fixes: b3060044e495 ("pinctrl: freescale: imx7d: make of_device_ids const")
> Cc: Shawn Guo <shawnguo@kernel.org>
> Cc: Arvind Yadav <arvind.yadav.cs@gmail.com>
> Cc: Dong Aisheng <aisheng.dong@nxp.com>
> Cc: Gary Bisson <gary.bisson@boundarydevices.com>
> Signed-off-by: Stefan Agner <stefan@agner.ch>
This is actually more or less a revert of a previous commit:
b28742be4709 pinctrl: imx: remove const qualifier of imx_pinctrl_soc_info
Note that the idea for this commit was to get dt-overlays working and
able to do pinctrl changes using configfs interface to load an overlay
(using Pantelis patch). Not sure where we stand on loading such overlay
from user-space, is it still something that will happen?
Regards,
Gary
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox