This provides a new helper lib for registering interrupt handlers in userspace.
( -1, bus, dev, fun, ...): will look up gsi from ACPI (gsi, -1, -1, -1, ...): will use gsi and ignore pci commands I haven't been able to test this yet, but hope to introduce it into rumpkernel(pci-userspace) and libacpica to simplify the irq handling. --- Makefile | 4 + libirqhelp/Makefile | 28 ++++ libirqhelp/irqhelp.c | 376 +++++++++++++++++++++++++++++++++++++++++++ libirqhelp/irqhelp.h | 31 ++++ 4 files changed, 439 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..2d754114 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,10 @@ endif ifeq ($(HAVE_LIBACPICA),yes) prog-subdirs += acpi + ifeq ($(HAVE_LIBPCIACCESS),yes) + # Needs acpi translator and libpciaccess + lib-subdirs += libirqhelp + endif endif # Other directories diff --git a/libirqhelp/Makefile b/libirqhelp/Makefile new file mode 100644 index 00000000..fbc82072 --- /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 $(libpciaccess_LIBS) +libname = libirqhelp +installhdrs = irqhelp.h + +include ../Makeconf diff --git a/libirqhelp/irqhelp.c b/libirqhelp/irqhelp.c new file mode 100644 index 00000000..4eeba247 --- /dev/null +++ b/libirqhelp/irqhelp.c @@ -0,0 +1,376 @@ +/* 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. */ + +#define _GNU_SOURCE 1 + +#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 <pciaccess.h> +#include <device/notify.h> +#include <device/device.h> +#include "acpi_U.h" +#include <mach.h> + +#define MAX_PCI_DEVS 128 +#define PCI_COMMAND 0x04 +#define PCI_COMMAND_INT_DISABLE 0x400 +#define IRQ_THREAD_PRIORITY 2 + + +struct irq { + int bus; + int dev; + int fun; + int gsi; + int cookie; + int (*handler)(void *); + void *context; + mach_port_t port; + + 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 struct pci_device *pci_devices[MAX_PCI_DEVS]; +static int numdevs; + +static error_t +pci_init(void) +{ + int i = 0; + error_t err; + struct pci_device_iterator *dev_iter; + struct pci_device *pci_dev; + + err = pci_system_init (); + if (err) + return err; + + dev_iter = pci_slot_match_iterator_create (NULL); + while (((pci_dev = pci_device_next (dev_iter)) != NULL) + && (i < MAX_PCI_DEVS)) + { + pci_device_probe(pci_dev); + pci_devices[i++] = pci_dev; + } + numdevs = i; + return 0; +} + +static error_t +pci_confread(int bus, int dev, int fun, + int reg, unsigned int *rv) +{ + int i; + *rv = 0xffffffff; + + for (i = 0; i < numdevs; i++) + { + if ((pci_devices[i]->bus == bus) + && (pci_devices[i]->dev == dev) + && (pci_devices[i]->func == fun)) + goto found; + } + return ENODEV; + +found: + pci_device_cfg_read_u32(pci_devices[i], rv, reg); + return 0; +} + +static error_t +pci_confwrite(int bus, int dev, int fun, + int reg, unsigned int v) +{ + int i; + + for (i = 0; i < numdevs; i++) + { + if ((pci_devices[i]->bus == bus) + && (pci_devices[i]->dev == dev) + && (pci_devices[i]->func == fun)) + goto found; + } + return ENODEV; + +found: + pci_device_cfg_write_u32(pci_devices[i], v, reg); + return 0; +} + +static error_t +get_acpi(void) +{ + error_t err = 0; + mach_port_t tryacpi, device_master; + + acpidev = MACH_PORT_NULL; + + tryacpi = file_name_lookup (_SERVERS_ACPI, O_RDONLY, 0); + if (tryacpi == MACH_PORT_NULL) + { + err = get_privileged_ports (0, &device_master); + if (err) + return err; + + err = device_open (device_master, D_READ, "acpi", &tryacpi); + if (err) + { + mach_port_deallocate (mach_task_self (), device_master); + return err; + } + + mach_port_deallocate (mach_task_self (), device_master); + } + + acpidev = tryacpi; + return err; +} + +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; +} + +void * +irqhelp_server_loop(void *arg) +{ + struct irq *irq = (struct irq *)arg; + + if (!irq) + return NULL; + + int interrupt_server (mach_msg_header_t *inp, + mach_msg_header_t *outp) + { + char interrupt[4]; + + 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) + { + /* not an interrupt */ + return 0; + } + + /* FIXME: id <-> gsi now has an indirection, assuming 1:1 */ + if (n->id != irq->gsi) + { + /* interrupt not for us */ + return 0; + } + + /* enable pci interrupt if we know the device B/D/F and it is disabled */ + if ((irq->bus >= 0) && (irq->dev >= 0) && (irq->fun >= 0)) + { + unsigned int val; + pci_confread (irq->bus, irq->dev, irq->fun, PCI_COMMAND, &val); + if (val & PCI_COMMAND_INT_DISABLE) + { + val &= ~PCI_COMMAND_INT_DISABLE; + pci_confwrite (irq->bus, irq->dev, irq->fun, PCI_COMMAND, val); + } + } + + /* call handler */ + irq->handler(irq->context); + + /* ACK interrupt */ + device_intr_ack (irqdev, irq->port, MACH_MSG_TYPE_MAKE_SEND); + + return 1; + } + + /* Server loop */ + mach_msg_server (interrupt_server, 0, irq->port); + + return NULL; +} + +static struct irq * +interrupt_register(int gsi, + int bus, + int dev, + int fun, + int (*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->bus = bus; + irq->dev = dev; + irq->fun = fun; + irq->gsi = gsi; + irq->cookie = ++refcnt; + + 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; + + return irq; + +fail: + free(irq); + --refcnt; + 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 all -1, will not use PCI + to clear possibly disabled pci interrupt. + Accepts a pointer to a cookie to be used to + deregister the handler later. */ +void * +irqhelp_install_interrupt_handler(int gsi, + int bus, + int dev, + int fun, + int (*handler)(void *), + void *context, + int *cookie) +{ + struct irq *irq; + error_t err; + + if (!handler) + return NULL; + + if (!cookie) + return NULL; + + err = get_privileged_ports (&master_host, 0); + if (err) + 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; + } + + if ((bus >= 0) && (dev >= 0) && (fun >= 0)) + { + err = pci_init(); + if (err) + return NULL; + } + + irq = interrupt_register(gsi, bus, dev, fun, handler, context, cookie); + + return (void *)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..60f1a812 --- /dev/null +++ b/libirqhelp/irqhelp.h @@ -0,0 +1,31 @@ +/* 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> + +void * irqhelp_install_interrupt_handler(int gsi, int bus, int dev, int fun, + int (*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); + +#endif -- 2.34.1