This provides a new helper lib for registering interrupt handlers in userspace, libirqhelp.
libirqhelp does not depend on libpciaccess. ( -1, bus, dev, fun, ...): will look up gsi from ACPI (gsi, -1, -1, -1, ...): will use given gsi --- Makefile | 1 + libirqhelp/Makefile | 28 ++++ libirqhelp/irqhelp.c | 350 +++++++++++++++++++++++++++++++++++++++++++ libirqhelp/irqhelp.h | 39 +++++ 4 files changed, 418 insertions(+) create mode 100644 libirqhelp/Makefile create mode 100644 libirqhelp/irqhelp.c create mode 100644 libirqhelp/irqhelp.h diff --git a/Makefile b/Makefile index 874349c0..cc044712 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ endif ifeq ($(HAVE_LIBACPICA),yes) prog-subdirs += acpi +lib-subdirs += libirqhelp endif # Other directories diff --git a/libirqhelp/Makefile b/libirqhelp/Makefile new file mode 100644 index 00000000..c32632ab --- /dev/null +++ b/libirqhelp/Makefile @@ -0,0 +1,28 @@ +# Copyright (C) 2022 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +dir := libirqhelp +makemode := library + +SRCS = irqhelp.c acpiUser.c + +OBJS = $(SRCS:.c=.o) +HURDLIBS = +LDLIBS += -lpthread +libname = libirqhelp +installhdrs = irqhelp.h + +include ../Makeconf diff --git a/libirqhelp/irqhelp.c b/libirqhelp/irqhelp.c new file mode 100644 index 00000000..eba64cf7 --- /dev/null +++ b/libirqhelp/irqhelp.c @@ -0,0 +1,350 @@ +/* Library providing helper functions for userspace irq handling. + Copyright (C) 2022 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "irqhelp.h" + +#include <sys/types.h> +#include <sys/queue.h> + +#include <fcntl.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <hurd.h> +#include <hurd/paths.h> +#include <device/notify.h> +#include <device/device.h> +#include "acpi_U.h" +#include <mach.h> +#include <stdbool.h> + +#define IRQ_THREAD_PRIORITY 2 + +struct irq { + /* public */ + void (*init_hook)(void *); + void (*pre_hook)(void *); + void (*post_hook)(void *); + + /* private */ + void (*handler)(void *); + void *context; + int gsi; + int cookie; + mach_port_t port; + bool enabled; + pthread_mutex_t irqlock; + pthread_cond_t irqcond; + + LIST_ENTRY(irq) entries; +}; + +static LIST_HEAD(, irq) irqs = LIST_HEAD_INITIALIZER(&irqs); + +static mach_port_t master_host; +static mach_port_t irqdev; +static mach_port_t acpidev; +static int refcnt; + +static inline int +atomic_increment(int *global) +{ + return __atomic_add_fetch(global, 1, __ATOMIC_SEQ_CST); +} + +static error_t +get_acpi(void) +{ + error_t err = 0; + mach_port_t tryacpi, device_master; + + acpidev = MACH_PORT_NULL; + err = get_privileged_ports (0, &device_master); + if (!err) + { + err = device_open (device_master, D_READ, "acpi", &tryacpi); + if (!err) + { + mach_port_deallocate (mach_task_self (), device_master); + acpidev = tryacpi; + return 0; + } + + mach_port_deallocate (mach_task_self (), device_master); + } + + tryacpi = file_name_lookup (_SERVERS_ACPI, O_RDONLY, 0); + if (tryacpi == MACH_PORT_NULL) + return ENODEV; + + acpidev = tryacpi; + return 0; +} + +static error_t +get_irq(void) +{ + error_t err = 0; + mach_port_t tryirq, device_master; + + irqdev = MACH_PORT_NULL; + + err = get_privileged_ports (0, &device_master); + if (err) + return err; + + err = device_open (device_master, D_READ|D_WRITE, "irq", &tryirq); + if (err) + { + mach_port_deallocate (mach_task_self (), device_master); + return err; + } + + mach_port_deallocate (mach_task_self (), device_master); + + irqdev = tryirq; + return err; +} + +static struct irq * +lookup_irq_structure(int gsi) +{ + struct irq *i; + + LIST_FOREACH(i, &irqs, entries) + { + if (i->gsi == gsi) + return i; + } + return NULL; +} + +static void +toggle_irq(struct irq *irq, bool on) +{ + pthread_mutex_lock (&irq->irqlock); + irq->enabled = on; + pthread_cond_signal (&irq->irqcond); + pthread_mutex_unlock (&irq->irqlock); +} + +void +irqhelp_disable_irq(int gsi) +{ + struct irq *irq = lookup_irq_structure(gsi); + if (!irq) + return; + + toggle_irq(irq, false); +} + +void +irqhelp_enable_irq(int gsi) +{ + struct irq *irq = lookup_irq_structure(gsi); + if (!irq) + return; + + toggle_irq(irq, true); +} + +void * +irqhelp_server_loop(void *arg) +{ + struct irq *irq = (struct irq *)arg; + + if (!irq) + return NULL; + + /* init hook */ + if (irq->init_hook) + irq->init_hook(irq->context); + + int interrupt_demuxer (mach_msg_header_t *inp, + mach_msg_header_t *outp) + { + device_intr_notification_t *n = (device_intr_notification_t *) inp; + + ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY; + if (n->intr_header.msgh_id != DEVICE_INTR_NOTIFY) + return 0; /* not an interrupt */ + + /* FIXME: id <-> gsi now has an indirection, assuming 1:1 */ + if (n->id != irq->gsi) + return 0; /* interrupt not for us */ + + /* wait if irq disabled */ + pthread_mutex_lock (&irq->irqlock); + while (!irq->enabled) + pthread_cond_wait (&irq->irqcond, &irq->irqlock); + pthread_mutex_unlock (&irq->irqlock); + + /* pre-handler */ + if (irq->pre_hook) + irq->pre_hook(irq->context); + + /* call handler */ + irq->handler(irq->context); + + /* post-handler */ + if (irq->post_hook) + irq->post_hook(irq->context); + + /* ACK interrupt */ + device_intr_ack (irqdev, irq->port, MACH_MSG_TYPE_MAKE_SEND); + + return 1; + } + + /* Server loop */ + mach_msg_server (interrupt_demuxer, 0, irq->port); + + return NULL; +} + +static struct irq * +interrupt_register(int gsi, + int bus, + int dev, + int fun, + void (*handler)(void *), + void *context, + int *cookie) +{ + mach_port_t delivery_port; + mach_port_t pset, psetcntl; + error_t err; + struct irq *irq = NULL; + + irq = malloc(sizeof(struct irq)); + if (!irq) + return NULL; + + LIST_INSERT_HEAD(&irqs, irq, entries); + + irq->handler = handler; + irq->context = context; + irq->gsi = gsi; + irq->init_hook = NULL; /* can be overriden later by caller */ + irq->pre_hook = NULL; /* can be overriden later by caller */ + irq->post_hook = NULL; /* can be overriden later by caller */ + irq->enabled = true; /* don't require initial explicit enable */ + pthread_mutex_init (&irq->irqlock, NULL); + pthread_cond_init (&irq->irqcond, NULL); + + err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, + &delivery_port); + if (err) + goto fail; + + irq->port = delivery_port; + + err = thread_get_assignment (mach_thread_self (), &pset); + if (err) + goto fail; + + err = host_processor_set_priv (master_host, pset, &psetcntl); + if (err) + goto fail; + + thread_max_priority (mach_thread_self (), psetcntl, 0); + err = thread_priority (mach_thread_self (), IRQ_THREAD_PRIORITY, 0); + if (err) + goto fail; + + err = device_intr_register(irqdev, irq->gsi, + 0, irq->port, + MACH_MSG_TYPE_MAKE_SEND); + if (err) + goto fail; + + *cookie = irq->cookie = atomic_increment (&refcnt); + return irq; + +fail: + pthread_cond_destroy(&irq->irqcond); + pthread_mutex_destroy(&irq->irqlock); + free(irq); + return NULL; +} + + +/* Accepts gsi or bus/dev/fun or both, but cant be all -1. + If gsi is -1, will lookup the gsi via ACPI. + If bus/dev/fun are -1, must pass in gsi. + Accepts a pointer to a cookie to be used to + deregister the handler later. */ +struct irqhelp * +irqhelp_install_interrupt_handler(int gsi, + int bus, + int dev, + int fun, + void (*handler)(void *), + void *context, + int *cookie) +{ + struct irq *irq; + error_t err; + + if (!handler) + return NULL; + + if (!cookie) + return NULL; + + err = get_irq(); + if (err) + return NULL; + + if (gsi < 0) + { + if ((bus < 0) || (dev < 0) || (fun < 0)) + return NULL; + + err = get_acpi(); + if (err) + return NULL; + + /* We need to call acpi translator to get gsi */ + err = acpi_get_pci_irq (acpidev, bus, dev, fun, &gsi); + if (err) + return NULL; + } + + err = get_privileged_ports (&master_host, 0); + if (err) + return NULL; + + irq = interrupt_register(gsi, bus, dev, fun, handler, context, cookie); + + mach_port_deallocate (mach_task_self (), master_host); + return (struct irqhelp *)irq; +} + +error_t +irqhelp_remove_interrupt_handler(int gsi, + int bus, + int dev, + int fun, + int cookie) +{ + /* Not implemented yet, but don't fail */ + return 0; +} diff --git a/libirqhelp/irqhelp.h b/libirqhelp/irqhelp.h new file mode 100644 index 00000000..292d912d --- /dev/null +++ b/libirqhelp/irqhelp.h @@ -0,0 +1,39 @@ +/* Library providing helper functions for userspace irq handling. + Copyright (C) 2022 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef _HURD_IRQHELP_ +#define _HURD_IRQHELP_ + +#include <mach.h> +#include <hurd/hurd_types.h> +#include <pthread.h> +#include <stdlib.h> + +struct irqhelp { + void (*init_hook)(void *); + void (*pre_hook)(void *); + void (*post_hook)(void *); +}; + +struct irqhelp * irqhelp_install_interrupt_handler(int gsi, int bus, int dev, int fun, + void (*handler)(void *), void *context, int *cookie); +error_t irqhelp_remove_interrupt_handler(int gsi, int bus, int dev, int fun, int cookie); +void * irqhelp_server_loop(void *arg); +void irqhelp_enable_irq(int gsi); +void irqhelp_disable_irq(int gsi); + +#endif -- 2.34.1