Add a helper library for attaching interrupt handlers in userspace.

---
 Makefile             |   1 +
 libirqhelp/Makefile  |  28 ++++
 libirqhelp/irqhelp.c | 362 +++++++++++++++++++++++++++++++++++++++++++
 libirqhelp/irqhelp.h |  49 ++++++
 4 files changed, 440 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 874349c06..4d8482219 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,7 @@ lib-subdirs = libshouldbeinlibc libihash libiohelp libports \
              libhurd-slab \
              libbpf \
              libmachdev \
+             libirqhelp \
 
 # Hurd programs
 prog-subdirs = auth proc exec term \
diff --git a/libirqhelp/Makefile b/libirqhelp/Makefile
new file mode 100644
index 000000000..c32632abe
--- /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 000000000..32e59c92c
--- /dev/null
+++ b/libirqhelp/irqhelp.c
@@ -0,0 +1,362 @@
+/* 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
+#define log_error(fmt...)      fprintf(stderr, ## fmt)
+
+struct irq {
+  void (*handler)(void *);
+  void *context;
+  int gsi;
+  mach_port_t port;
+  bool enabled;
+  bool shutdown;
+  pthread_mutex_t irqlock;
+  pthread_cond_t irqcond;
+};
+
+static mach_port_t irqdev;
+static mach_port_t acpidev;
+
+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);
+      mach_port_deallocate (mach_task_self (), device_master);
+      if (!err)
+        {
+         acpidev = tryacpi;
+          return 0;
+        }
+    }
+
+  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);
+  mach_port_deallocate (mach_task_self (), device_master);
+  if (err)
+    return err;
+
+  irqdev = tryirq;
+  return err;
+}
+
+static void
+toggle_irq(struct irq *irq, bool on)
+{
+  pthread_mutex_lock (&irq->irqlock);
+  irq->enabled = on;
+  pthread_mutex_unlock (&irq->irqlock);
+
+  if (on)
+    pthread_cond_signal (&irq->irqcond);
+}
+
+error_t
+irqhelp_init(void)
+{
+  static bool inited = false;
+  error_t err;
+
+  if (inited)
+    {
+      log_error("already inited\n");
+      return 0;
+    }
+
+  err = get_irq();
+  if (err)
+    {
+      log_error("cannot grab irq device\n");
+      return err;
+    }
+
+  err = get_acpi();
+  if (err)
+    {
+      log_error("cannot grab acpi device\n");
+      return err;
+    }
+
+  inited = true;
+  return 0;
+}
+
+void
+irqhelp_disable_irq(struct irq *irq)
+{
+  if (!irq)
+    { 
+      log_error("cannot disable this irq\n");
+      return;
+    }
+
+  toggle_irq(irq, false);
+}
+
+void
+irqhelp_enable_irq(struct irq *irq)
+{
+  if (!irq)
+    {
+      log_error("cannot enable this irq\n");
+      return;
+    }
+
+  toggle_irq(irq, true);
+}
+
+void *
+irqhelp_server_loop(void *arg)
+{
+  struct irq *irq = (struct irq *)arg;
+  mach_port_t master_host;
+  mach_port_t pset, psetcntl;
+  error_t err;
+
+  if (!irq)
+    {
+      log_error("cannot start this irq thread\n");
+      return NULL;
+    }
+
+  err = get_privileged_ports (&master_host, 0);
+  if (err)
+    {
+      log_error("cannot get master_host port\n");
+      return NULL;
+    }
+
+  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;
+
+  mach_port_deallocate (mach_task_self (), master_host);
+
+  int interrupt_demuxer (mach_msg_header_t *inp,
+                        mach_msg_header_t *outp)
+  {
+    static bool printed0 = false;
+    static bool printed1 = false;
+    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)
+      {
+       if (!printed0)
+         {
+           log_error("msg received is not an interrupt\n");
+            printed0 = true;
+         }
+       return 0;
+      }
+
+    /* FIXME: id <-> gsi now has an indirection, assuming 1:1 */
+    if (n->id != irq->gsi)
+      {
+       if (!printed1)
+         {
+           log_error("interrupt expected on irq %d arrived on irq %d\n", 
irq->gsi, n->id);
+           printed1 = true;
+         }
+       return 0;
+      }
+
+    /* wait if irq disabled */
+    pthread_mutex_lock (&irq->irqlock);
+    while (!irq->enabled)
+      pthread_cond_wait (&irq->irqcond, &irq->irqlock);
+    pthread_mutex_unlock (&irq->irqlock);
+
+    /* call handler */
+    irq->handler(irq->context);
+
+    /* ACK interrupt */
+    device_intr_ack (irqdev, irq->port, MACH_MSG_TYPE_MAKE_SEND);
+
+    if (irq->shutdown)
+      mach_port_deallocate (mach_task_self (), irq->port);
+
+    return 1;
+  }
+
+  /* Server loop */
+  mach_msg_server (interrupt_demuxer, 0, irq->port);
+  goto done;
+
+fail:
+  log_error("failed to register irq %d\n", irq->gsi);
+
+done:
+  pthread_cond_destroy(&irq->irqcond);
+  pthread_mutex_destroy(&irq->irqlock);
+  free(irq);
+  return NULL;
+}
+
+static struct irq *
+interrupt_register(int gsi,
+                  void (*handler)(void *),
+                  void *context)
+{
+  error_t err;
+  struct irq *irq = NULL;
+  mach_port_t delivery_port;
+
+  irq = malloc(sizeof(struct irq));
+  if (!irq)
+    {
+      log_error("cannot malloc irq\n");
+      return NULL;
+    }
+
+  irq->handler = handler;
+  irq->context = context;
+  irq->gsi = gsi;
+  irq->enabled = true;  /* avoid deadlock by not requiring initial explicit 
enable */
+  irq->shutdown = false;
+  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)
+    {
+      log_error("cannot allocate mach port\n");
+      return NULL;
+    }
+
+  irq->port = delivery_port;
+
+  err = device_intr_register(irqdev, irq->gsi,
+                             0, irq->port,
+                             MACH_MSG_TYPE_MAKE_SEND);
+  if (err)
+    {
+      log_error("device_intr_register failed with %d\n", err);
+      return NULL;
+    }
+
+  return irq;
+}
+
+struct irq *
+irqhelp_install_interrupt_handler(int gsi,
+                                 int bus,
+                                 int dev,
+                                 int fun,
+                                 void (*handler)(void *),
+                                 void *context)
+{
+  error_t err;
+
+  if (!handler)
+    {
+      log_error("no handler\n");
+      return NULL;
+    }
+
+  if (gsi < 0)
+    {
+      if ((bus < 0) || (dev < 0) || (fun < 0))
+       {
+         log_error("invalid b/d/f\n");
+         return NULL;
+       }
+
+      /* We need to call acpi translator to get gsi */
+      err = acpi_get_pci_irq (acpidev, bus, dev, fun, &gsi);
+      if (err)
+       {
+         log_error("tried acpi to get pci gsi and failed for %02x:%02x.%d\n", 
bus, dev, fun);
+         return NULL;
+       }
+    }
+
+  return interrupt_register(gsi, handler, context);
+}
+
+error_t
+irqhelp_remove_interrupt_handler(struct irq *irq)
+{
+  if (!irq)
+    {
+      log_error("cannot deregister this irq\n");
+      return ENODEV;
+    }
+
+  irq->shutdown = true;
+  
+  /* enable irq to fall through to shutdown check */
+  toggle_irq(irq, true);
+
+  return 0;
+}
diff --git a/libirqhelp/irqhelp.h b/libirqhelp/irqhelp.h
new file mode 100644
index 000000000..4ebbed8b8
--- /dev/null
+++ b/libirqhelp/irqhelp.h
@@ -0,0 +1,49 @@
+/* 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 irq;
+
+/* Call once before using other functions */
+error_t irqhelp_init(void);
+
+/* 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.
+   Returns a pointer to be used with other functions. */
+struct irq * irqhelp_install_interrupt_handler(int gsi, int bus, int dev, int 
fun,
+                                              void (*handler)(void *), void 
*context);
+/* Install as pthread */
+void * irqhelp_server_loop(void *arg);
+
+/* Enable an irq */
+void irqhelp_enable_irq(struct irq *irq);
+
+/* Disable an irq */
+void irqhelp_disable_irq(struct irq *irq);
+
+/* Call to deregister a handler */
+error_t irqhelp_remove_interrupt_handler(struct irq *irq);
+
+#endif
-- 
2.43.0



Reply via email to