Message ID | 9d6f93f061b2b248c0fa0a7f1530792936f8e7be.1580882335.git.thehajime@gmail.com |
---|---|
State | Not Applicable |
Headers | show |
Series | None | expand |
On 05/02/2020 07:30, Hajime Tazaki wrote: > From: Octavian Purdila <tavi.purdila@gmail.com> > > Add APIs that allows the host to reserve and free and interrupt number > and also to trigger an interrupt. > > The trigger operation will simply store the interrupt data in > queue. The interrupt handler is run later, at the first opportunity it > has to avoid races with any kernel threads. > > Currently, interrupts are run on the first interrupt enable operation > if interrupts are disabled and if we are not already in interrupt > context. > > When triggering an interrupt, it uses GCC's built-in functions for > atomic memory access to synchronize and simple boolean flags. > > Cc: Michael Zimmermann <sigmaepsilon92@gmail.com> > Signed-off-by: Hajime Tazaki <thehajime@gmail.com> > Signed-off-by: Octavian Purdila <tavi.purdila@gmail.com> > --- > arch/um/lkl/include/asm/irq.h | 13 ++ > arch/um/lkl/include/uapi/asm/irq.h | 36 ++++ > arch/um/lkl/include/uapi/asm/sigcontext.h | 16 ++ > arch/um/lkl/kernel/irq.c | 193 ++++++++++++++++++++++ > 4 files changed, 258 insertions(+) > create mode 100644 arch/um/lkl/include/asm/irq.h > create mode 100644 arch/um/lkl/include/uapi/asm/irq.h > create mode 100644 arch/um/lkl/include/uapi/asm/sigcontext.h > create mode 100644 arch/um/lkl/kernel/irq.c > > diff --git a/arch/um/lkl/include/asm/irq.h b/arch/um/lkl/include/asm/irq.h > new file mode 100644 > index 000000000000..948fc54cb76c > --- /dev/null > +++ b/arch/um/lkl/include/asm/irq.h > @@ -0,0 +1,13 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +#ifndef _ASM_LKL_IRQ_H > +#define _ASM_LKL_IRQ_H > + > +#define IRQ_STATUS_BITS (sizeof(long) * 8) > +#define NR_IRQS ((int)(IRQ_STATUS_BITS * IRQ_STATUS_BITS)) > + > +void run_irqs(void); > +void set_irq_pending(int irq); > + > +#include <uapi/asm/irq.h> > + > +#endif > diff --git a/arch/um/lkl/include/uapi/asm/irq.h b/arch/um/lkl/include/uapi/asm/irq.h > new file mode 100644 > index 000000000000..666628b233eb > --- /dev/null > +++ b/arch/um/lkl/include/uapi/asm/irq.h > @@ -0,0 +1,36 @@ > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > +#ifndef _ASM_UAPI_LKL_IRQ_H > +#define _ASM_UAPI_LKL_IRQ_H > + > +/** > + * lkl_trigger_irq - generate an interrupt > + * > + * This function is used by the device host side to signal its Linux counterpart > + * that some event happened. > + * > + * @irq - the irq number to signal > + */ > +int lkl_trigger_irq(int irq); > + > +/** > + * lkl_get_free_irq - find and reserve a free IRQ number > + * > + * This function is called by the host device code to find an unused IRQ number > + * and reserved it for its own use. > + * > + * @user - a string to identify the user > + * @returns - and irq number that can be used by request_irq or an negative > + * value in case of an error > + */ > +int lkl_get_free_irq(const char *user); > + > +/** > + * lkl_put_irq - release an IRQ number previously obtained with lkl_get_free_irq > + * > + * @irq - irq number to release > + * @user - string identifying the user; should be the same as the one passed to > + * lkl_get_free_irq when the irq number was obtained > + */ > +void lkl_put_irq(int irq, const char *name); > + > +#endif > diff --git a/arch/um/lkl/include/uapi/asm/sigcontext.h b/arch/um/lkl/include/uapi/asm/sigcontext.h > new file mode 100644 > index 000000000000..2f4848843d1d > --- /dev/null > +++ b/arch/um/lkl/include/uapi/asm/sigcontext.h > @@ -0,0 +1,16 @@ > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > +#ifndef _ASM_UAPI_LKL_SIGCONTEXT_H > +#define _ASM_UAPI_LKL_SIGCONTEXT_H > + > +#include <asm/ptrace.h> > + > +struct pt_regs { > + void *irq_data; > +}; > + > +struct sigcontext { > + struct pt_regs regs; > + unsigned long oldmask; > +}; > + > +#endif > diff --git a/arch/um/lkl/kernel/irq.c b/arch/um/lkl/kernel/irq.c > new file mode 100644 > index 000000000000..e3b59e46ca50 > --- /dev/null > +++ b/arch/um/lkl/kernel/irq.c > @@ -0,0 +1,193 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/irq.h> > +#include <linux/hardirq.h> > +#include <asm/irq_regs.h> > +#include <linux/sched.h> > +#include <linux/seq_file.h> > +#include <linux/tick.h> > +#include <asm/irqflags.h> > +#include <asm/host_ops.h> > +#include <asm/cpu.h> > + > +/* > + * To avoid much overhead we use an indirect approach: the irqs are marked using > + * a bitmap (array of longs) and a summary of the modified bits is kept in a > + * separate "index" long - one bit for each sizeof(long). Thus we can support > + * 4096 irqs on 64bit platforms and 1024 irqs on 32bit platforms. > + * > + * Whenever an irq is trigger both the array and the index is updated. To find > + * which irqs were triggered we first search the index and then the > + * corresponding part of the arrary. > + */ > +static unsigned long irq_status[NR_IRQS / IRQ_STATUS_BITS]; > +static unsigned long irq_index_status; > + > +static inline unsigned long test_and_clear_irq_index_status(void) > +{ > + if (!irq_index_status) > + return 0; > + return __sync_fetch_and_and(&irq_index_status, 0); > +} > + > +static inline unsigned long test_and_clear_irq_status(int index) > +{ > + if (!&irq_status[index]) > + return 0; > + return __sync_fetch_and_and(&irq_status[index], 0); > +} > + > +void set_irq_pending(int irq) > +{ > + int index = irq / IRQ_STATUS_BITS; > + int bit = irq % IRQ_STATUS_BITS; > + > + __sync_fetch_and_or(&irq_status[index], BIT(bit)); > + __sync_fetch_and_or(&irq_index_status, BIT(index)); > +} > + > +static struct irq_info { > + const char *user; > +} irqs[NR_IRQS]; > + > +static bool irqs_enabled; > + > +static struct pt_regs dummy; > + > +static void run_irq(int irq) > +{ > + unsigned long flags; > + struct pt_regs *old_regs = set_irq_regs((struct pt_regs *)&dummy); > + > + /* interrupt handlers need to run with interrupts disabled */ > + local_irq_save(flags); > + irq_enter(); > + generic_handle_irq(irq); > + irq_exit(); > + set_irq_regs(old_regs); > + local_irq_restore(flags); > +} > + > +/** > + * This function can be called from arbitrary host threads, so do not > + * issue any Linux calls (e.g. prink) if lkl_cpu_get() was not issued > + * before. > + */ > +int lkl_trigger_irq(int irq) > +{ > + int ret; > + > + if (!irq || irq > NR_IRQS) > + return -EINVAL; > + > + ret = lkl_cpu_try_run_irq(irq); > + if (ret <= 0) > + return ret; > + > + /* > + * Since this can be called from Linux context (e.g. lkl_trigger_irq -> > + * IRQ -> softirq -> lkl_trigger_irq) make sure we are actually allowed > + * to run irqs at this point > + */ > + if (!irqs_enabled) { > + set_irq_pending(irq); > + lkl_cpu_put(); > + return 0; > + } > + > + run_irq(irq); > + > + lkl_cpu_put(); > + > + return 0; Isn't that just: if (irqs_enabled) run_irq(irq); else set_irq_pending(irq); lkl_cpu_put(); return 0; > +} > + > +static inline void for_each_bit(unsigned long word, void (*f)(int, int), int j) > +{ > + int i = 0; > + > + while (word) { > + if (word & 1) > + f(i, j); > + word >>= 1; > + i++; > + } > +} > + > +static inline void deliver_irq(int bit, int index) > +{ > + run_irq(index * IRQ_STATUS_BITS + bit); > +} > + > +static inline void check_irq_status(int i, int unused) > +{ > + for_each_bit(test_and_clear_irq_status(i), deliver_irq, i); > +} > + > +void run_irqs(void) > +{ > + for_each_bit(test_and_clear_irq_index_status(), check_irq_status, 0); > +} > + > +int show_interrupts(struct seq_file *p, void *v) > +{ > + return 0; > +} > + > +int lkl_get_free_irq(const char *user) > +{ > + int i; > + int ret = -EBUSY; > + > + /* 0 is not a valid IRQ */ > + for (i = 1; i < NR_IRQS; i++) { > + if (!irqs[i].user) { > + irqs[i].user = user; > + irq_set_chip_and_handler(i, &dummy_irq_chip, > + handle_simple_irq); > + ret = i; > + break; > + } > + } > + > + return ret; > +} > + > +void lkl_put_irq(int i, const char *user) > +{ > + if (!irqs[i].user || strcmp(irqs[i].user, user) != 0) { > + WARN("%s tried to release %s's irq %d", user, irqs[i].user, i); > + return; > + } > + > + irqs[i].user = NULL; > +} > + > +unsigned long arch_local_save_flags(void) > +{ > + return irqs_enabled; > +} > + > +void arch_local_irq_restore(unsigned long flags) > +{ > + if (flags == ARCH_IRQ_ENABLED && irqs_enabled == ARCH_IRQ_DISABLED && > + !in_interrupt()) > + run_irqs(); > + irqs_enabled = flags; > +} > + > +void init_IRQ(void) > +{ > + int i; > + > + for (i = 0; i < NR_IRQS; i++) > + irq_set_chip_and_handler(i, &dummy_irq_chip, handle_simple_irq); > + > + pr_info("lkl: irqs initialized\n"); > +} > + > +void cpu_yield_to_irqs(void) > +{ > + cpu_relax(); > +} >
On Wed, 05 Feb 2020 19:47:36 +0900, Anton Ivanov wrote: > > +/** > > + * This function can be called from arbitrary host threads, so do not > > + * issue any Linux calls (e.g. prink) if lkl_cpu_get() was not issued > > + * before. > > + */ > > +int lkl_trigger_irq(int irq) > > +{ > > + int ret; > > + > > + if (!irq || irq > NR_IRQS) > > + return -EINVAL; > > + > > + ret = lkl_cpu_try_run_irq(irq); > > + if (ret <= 0) > > + return ret; > > + > > + /* > > + * Since this can be called from Linux context (e.g. lkl_trigger_irq -> > > + * IRQ -> softirq -> lkl_trigger_irq) make sure we are actually allowed > > + * to run irqs at this point > > + */ > > + if (!irqs_enabled) { > > + set_irq_pending(irq); > > + lkl_cpu_put(); > > + return 0; > > + } > > + > > + run_irq(irq); > > + > > + lkl_cpu_put(); > > + > > + return 0; > > Isn't that just: > > if (irqs_enabled) > run_irq(irq); > else > set_irq_pending(irq); > > lkl_cpu_put(); > > return 0; Thanks, this is much cleaner. I will fix this in the next turn. -- Hajime
diff --git a/arch/um/lkl/include/asm/irq.h b/arch/um/lkl/include/asm/irq.h new file mode 100644 index 000000000000..948fc54cb76c --- /dev/null +++ b/arch/um/lkl/include/asm/irq.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_LKL_IRQ_H +#define _ASM_LKL_IRQ_H + +#define IRQ_STATUS_BITS (sizeof(long) * 8) +#define NR_IRQS ((int)(IRQ_STATUS_BITS * IRQ_STATUS_BITS)) + +void run_irqs(void); +void set_irq_pending(int irq); + +#include <uapi/asm/irq.h> + +#endif diff --git a/arch/um/lkl/include/uapi/asm/irq.h b/arch/um/lkl/include/uapi/asm/irq.h new file mode 100644 index 000000000000..666628b233eb --- /dev/null +++ b/arch/um/lkl/include/uapi/asm/irq.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _ASM_UAPI_LKL_IRQ_H +#define _ASM_UAPI_LKL_IRQ_H + +/** + * lkl_trigger_irq - generate an interrupt + * + * This function is used by the device host side to signal its Linux counterpart + * that some event happened. + * + * @irq - the irq number to signal + */ +int lkl_trigger_irq(int irq); + +/** + * lkl_get_free_irq - find and reserve a free IRQ number + * + * This function is called by the host device code to find an unused IRQ number + * and reserved it for its own use. + * + * @user - a string to identify the user + * @returns - and irq number that can be used by request_irq or an negative + * value in case of an error + */ +int lkl_get_free_irq(const char *user); + +/** + * lkl_put_irq - release an IRQ number previously obtained with lkl_get_free_irq + * + * @irq - irq number to release + * @user - string identifying the user; should be the same as the one passed to + * lkl_get_free_irq when the irq number was obtained + */ +void lkl_put_irq(int irq, const char *name); + +#endif diff --git a/arch/um/lkl/include/uapi/asm/sigcontext.h b/arch/um/lkl/include/uapi/asm/sigcontext.h new file mode 100644 index 000000000000..2f4848843d1d --- /dev/null +++ b/arch/um/lkl/include/uapi/asm/sigcontext.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _ASM_UAPI_LKL_SIGCONTEXT_H +#define _ASM_UAPI_LKL_SIGCONTEXT_H + +#include <asm/ptrace.h> + +struct pt_regs { + void *irq_data; +}; + +struct sigcontext { + struct pt_regs regs; + unsigned long oldmask; +}; + +#endif diff --git a/arch/um/lkl/kernel/irq.c b/arch/um/lkl/kernel/irq.c new file mode 100644 index 000000000000..e3b59e46ca50 --- /dev/null +++ b/arch/um/lkl/kernel/irq.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/hardirq.h> +#include <asm/irq_regs.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/tick.h> +#include <asm/irqflags.h> +#include <asm/host_ops.h> +#include <asm/cpu.h> + +/* + * To avoid much overhead we use an indirect approach: the irqs are marked using + * a bitmap (array of longs) and a summary of the modified bits is kept in a + * separate "index" long - one bit for each sizeof(long). Thus we can support + * 4096 irqs on 64bit platforms and 1024 irqs on 32bit platforms. + * + * Whenever an irq is trigger both the array and the index is updated. To find + * which irqs were triggered we first search the index and then the + * corresponding part of the arrary. + */ +static unsigned long irq_status[NR_IRQS / IRQ_STATUS_BITS]; +static unsigned long irq_index_status; + +static inline unsigned long test_and_clear_irq_index_status(void) +{ + if (!irq_index_status) + return 0; + return __sync_fetch_and_and(&irq_index_status, 0); +} + +static inline unsigned long test_and_clear_irq_status(int index) +{ + if (!&irq_status[index]) + return 0; + return __sync_fetch_and_and(&irq_status[index], 0); +} + +void set_irq_pending(int irq) +{ + int index = irq / IRQ_STATUS_BITS; + int bit = irq % IRQ_STATUS_BITS; + + __sync_fetch_and_or(&irq_status[index], BIT(bit)); + __sync_fetch_and_or(&irq_index_status, BIT(index)); +} + +static struct irq_info { + const char *user; +} irqs[NR_IRQS]; + +static bool irqs_enabled; + +static struct pt_regs dummy; + +static void run_irq(int irq) +{ + unsigned long flags; + struct pt_regs *old_regs = set_irq_regs((struct pt_regs *)&dummy); + + /* interrupt handlers need to run with interrupts disabled */ + local_irq_save(flags); + irq_enter(); + generic_handle_irq(irq); + irq_exit(); + set_irq_regs(old_regs); + local_irq_restore(flags); +} + +/** + * This function can be called from arbitrary host threads, so do not + * issue any Linux calls (e.g. prink) if lkl_cpu_get() was not issued + * before. + */ +int lkl_trigger_irq(int irq) +{ + int ret; + + if (!irq || irq > NR_IRQS) + return -EINVAL; + + ret = lkl_cpu_try_run_irq(irq); + if (ret <= 0) + return ret; + + /* + * Since this can be called from Linux context (e.g. lkl_trigger_irq -> + * IRQ -> softirq -> lkl_trigger_irq) make sure we are actually allowed + * to run irqs at this point + */ + if (!irqs_enabled) { + set_irq_pending(irq); + lkl_cpu_put(); + return 0; + } + + run_irq(irq); + + lkl_cpu_put(); + + return 0; +} + +static inline void for_each_bit(unsigned long word, void (*f)(int, int), int j) +{ + int i = 0; + + while (word) { + if (word & 1) + f(i, j); + word >>= 1; + i++; + } +} + +static inline void deliver_irq(int bit, int index) +{ + run_irq(index * IRQ_STATUS_BITS + bit); +} + +static inline void check_irq_status(int i, int unused) +{ + for_each_bit(test_and_clear_irq_status(i), deliver_irq, i); +} + +void run_irqs(void) +{ + for_each_bit(test_and_clear_irq_index_status(), check_irq_status, 0); +} + +int show_interrupts(struct seq_file *p, void *v) +{ + return 0; +} + +int lkl_get_free_irq(const char *user) +{ + int i; + int ret = -EBUSY; + + /* 0 is not a valid IRQ */ + for (i = 1; i < NR_IRQS; i++) { + if (!irqs[i].user) { + irqs[i].user = user; + irq_set_chip_and_handler(i, &dummy_irq_chip, + handle_simple_irq); + ret = i; + break; + } + } + + return ret; +} + +void lkl_put_irq(int i, const char *user) +{ + if (!irqs[i].user || strcmp(irqs[i].user, user) != 0) { + WARN("%s tried to release %s's irq %d", user, irqs[i].user, i); + return; + } + + irqs[i].user = NULL; +} + +unsigned long arch_local_save_flags(void) +{ + return irqs_enabled; +} + +void arch_local_irq_restore(unsigned long flags) +{ + if (flags == ARCH_IRQ_ENABLED && irqs_enabled == ARCH_IRQ_DISABLED && + !in_interrupt()) + run_irqs(); + irqs_enabled = flags; +} + +void init_IRQ(void) +{ + int i; + + for (i = 0; i < NR_IRQS; i++) + irq_set_chip_and_handler(i, &dummy_irq_chip, handle_simple_irq); + + pr_info("lkl: irqs initialized\n"); +} + +void cpu_yield_to_irqs(void) +{ + cpu_relax(); +}