bug-hurd
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH-1] A new irq device interface


From: Junling Ma
Subject: [PATCH-1] A new irq device interface
Date: Sat, 1 Aug 2020 18:59:58 -0700

Hi all,

When I am playing with the current irq device and user interrupt handling, I 
see that each device must implement the device_intr_register and 
device_intr_ack calls. However, they are only meaningful for the irq device. So 
when writing device translators, this seems a little confusing to me. Starting 
from this email, I am sending 3 patches that propose a new interface for user 
space irq handling:
1. A program requests for irq by opening one of the irq devices irq0 - irq15. 
2. The program performs a read (of any size), which blocks until an interrupt 
happens on the requested line. The read returns with 0-byte data.
3. The interrupt is acked by writing to the device (of any sized data).
4. Go to step 2 to wait for the next irq.

PATCH 1 is included in this email: it prepares the stage by
1. Change devicer_user_intr to be a linux interrupt handler. 
2. Make user_intr_t represent a specific interrupt line, so that notifications 
queue on each line.
3. Make struct irqdev store a vector user_intr_t, indexed by interrupt line.
4. The n_unacked counter only increases when successfully delivered an 
interrupt.

Any comments?

Best,
Junling Ma

---
 device/device_init.c             |   1 +
 device/ds_routines.c             |   6 +-
 device/intr.c                    | 375 +++++++++++++++----------------
 device/intr.h                    |  28 ++-
 i386/i386/irq.c                  |  15 +-
 linux/dev/arch/i386/kernel/irq.c |  68 +-----
 6 files changed, 203 insertions(+), 290 deletions(-)

diff --git a/device/device_init.c b/device/device_init.c
index 794186ee..ee3e6dab 100644
--- a/device/device_init.c
+++ b/device/device_init.c
@@ -60,6 +60,7 @@ device_service_create(void)
        net_io_init();
        device_pager_init();
        chario_init();
+  irq_init();
 
        (void) kernel_thread(kernel_task, io_done_thread, 0);
        (void) kernel_thread(kernel_task, net_thread, 0);
diff --git a/device/ds_routines.c b/device/ds_routines.c
index e94e5ca8..91787950 100644
--- a/device/ds_routines.c
+++ b/device/ds_routines.c
@@ -343,13 +343,9 @@ ds_device_intr_register (device_t dev, int id,
   if (! name_equal(mdev->dev_ops->d_name, 3, "irq"))
     return D_INVALID_OPERATION;
 
-  user_intr_t *e = insert_intr_entry (&irqtab, id, receive_port);
-  if (!e)
-    return D_NO_MEMORY;
-
   // TODO detect when the port get destroyed because the driver crashes and
   // restart, to replace it when the same device driver calls it again.
-  err = install_user_intr_handler (&irqtab, id, flags, e);
+  err = install_user_intr_handler (id, receive_port);
   if (err == D_SUCCESS)
     {
       /* If the port is installed successfully, increase its reference by 1.
diff --git a/device/intr.c b/device/intr.c
index fbb9f495..682b12d7 100644
--- a/device/intr.c
+++ b/device/intr.c
@@ -20,219 +20,164 @@
 #include <machine/spl.h>
 #include <machine/irq.h>
 #include <ipc/ipc_space.h>
+#include "io_req.h"
+#include "ds_routines.h"
 
 #ifndef MACH_XEN
 
-queue_head_t main_intr_queue;
-static boolean_t deliver_intr (int id, ipc_port_t dst_port);
+#define SA_SHIRQ 0x04000000
+#define SA_INTERRUPT 0x20000000
 
-static user_intr_t *
-search_intr (struct irqdev *dev, ipc_port_t dst_port)
-{
-  user_intr_t *e;
-  queue_iterate (dev->intr_queue, e, user_intr_t *, chain)
-    {
-      if (e->dst_port == dst_port)
-       return e;
-    }
-  return NULL;
+struct irqdev irqtab;
+static boolean_t deliver_intr (int id, ipc_port_t dst_port);
+struct pt_regs;
+extern int request_irq (unsigned int irq, void (*handler) (int, void *, struct 
pt_regs *),
+            unsigned long flags, const char *device, void *dev_id);
+extern void free_irq (unsigned int irq, void *dev_id);
+
+#define SPLHIGH(critical_section) \
+{ \
+       spl_t s = splhigh(); \
+       critical_section; \
+       splx(s); \
 }
 
-kern_return_t
-irq_acknowledge (ipc_port_t receive_port)
-{
-  user_intr_t *e;
-  kern_return_t ret = 0;
-
-  spl_t s = splhigh ();
-  e = search_intr (&irqtab, receive_port);
-
-  if (!e)
-    printf("didn't find user intr for interrupt !?\n");
-  else
-    {
-      if (!e->n_unacked)
-        ret = D_INVALID_OPERATION;
-      else
-        e->n_unacked--;
-    }
-  splx (s);
-
-  if (ret)
-    return ret;
-
-  if (irqtab.irqdev_ack)
-    (*(irqtab.irqdev_ack)) (&irqtab, e->id);
-
-  __enable_irq (irqtab.irq[e->id]);
-
-  return D_SUCCESS;
+#define PROTECT(lock, critical_section) \
+{ \
+       simple_lock(&(lock)); \
+       critical_section; \
+       simple_unlock(&(lock)); \
 }
 
-/* This function can only be used in the interrupt handler. */
-static void
-queue_intr (struct irqdev *dev, int id, user_intr_t *e)
+static user_intr_notification_t
+search_notification (user_intr_t *handler, ipc_port_t dst_port)
 {
-  /* Until userland has handled the IRQ in the driver, we have to keep it
-   * disabled. Level-triggered interrupts would keep raising otherwise. */
-  __disable_irq (dev->irq[id]);
-
-  spl_t s = splhigh ();
-  e->n_unacked++;
-  e->interrupts++;
-  dev->tot_num_intr++;
-  splx (s);
-
-  thread_wakeup ((event_t) &intr_thread);
+  user_intr_notification_t n = NULL, p;
+  PROTECT(handler->lock, 
+       {
+                       queue_iterate (&handler->notification_queue, p, 
user_intr_notification_t, chain)
+                               {
+                                       if (p->dst_port == dst_port) {
+                                               n = p;
+                                               break;
+                                       }
+                               }
+               });
+  return n;
 }
 
-int
-deliver_user_intr (struct irqdev *dev, int id, user_intr_t *e)
+kern_return_t
+irq_acknowledge (ipc_port_t receive_port)
 {
-  /* The reference of the port was increased
-   * when the port was installed.
-   * If the reference is 1, it means the port should
-   * have been destroyed and I destroy it now. */
-  if (e->dst_port
-      && e->dst_port->ip_references == 1)
-    {
-      printf ("irq handler [%d]: release a dead delivery port %p entry %p\n", 
id, e->dst_port, e);
-      ipc_port_release (e->dst_port);
-      e->dst_port = MACH_PORT_NULL;
-      thread_wakeup ((event_t) &intr_thread);
-      return 0;
-    }
-  else
-    {
-      queue_intr (dev, id, e);
-      return 1;
-    }
+  user_intr_t *e;
+  unsigned id;
+       for (id = 0; id < NINTR; ++id)
+               {
+                       e = irqtab.handler[id];
+                       if (e && search_notification (e, receive_port))
+                               break;
+               }
+       if (id == NINTR)
+               return D_INVALID_OPERATION;
+
+  kern_return_t ret = D_SUCCESS;
+  PROTECT(e->lock, 
+       {
+                       if (!e->n_unacked)
+                               ret = D_INVALID_OPERATION;
+                       else {
+                               e->n_unacked--;
+                               if (e->n_unacked == 0) 
+                                       __enable_irq(id);
+                       }
+               });
+  return ret;
 }
 
-/* insert an interrupt entry in the queue.
- * This entry exists in the queue until
- * the corresponding interrupt port is removed.*/
-user_intr_t *
-insert_intr_entry (struct irqdev *dev, int id, ipc_port_t dst_port)
+static void 
+deliver_user_intr (int id, void *dev_id, struct pt_regs *regs)
 {
-  user_intr_t *e, *new, *ret;
-  int free = 0;
-
-  new = (user_intr_t *) kalloc (sizeof (*new));
-  if (new == NULL)
-    return NULL;
-
-  /* check whether the intr entry has been in the queue. */
-  spl_t s = splhigh ();
-  e = search_intr (dev, dst_port);
-  if (e)
-    {
-      printf ("the interrupt entry for irq[%d] and port %p has already been 
inserted\n", id, dst_port);
-      free = 1;
-      ret = NULL;
-      goto out;
-    }
-  printf("irq handler [%d]: new delivery port %p entry %p\n", id, dst_port, 
new);
-  ret = new;
-  new->id = id;
-  new->dst_port = dst_port;
-  new->interrupts = 0;
-  new->n_unacked = 0;
-
-  queue_enter (dev->intr_queue, new, user_intr_t *, chain);
-out:
-  splx (s);
-  if (free)
-    kfree ((vm_offset_t) new, sizeof (*new));
-  return ret;
+       user_intr_t *e;
+       SPLHIGH({
+               e = irqtab.handler[id];
+               if (e && !queue_empty(&e->notification_queue)) {
+                       ++e->interrupts;
+                       __disable_irq(id);
+               }
+       });
+       if (!e) return;
+       if (queue_empty(&e->notification_queue))
+               {
+                       irqtab.handler[id] = NULL;
+                       if (e->n_unacked)
+                               __enable_irq (id);
+                       free_irq(id, &irqtab);
+                       e = NULL;
+               }
+       else if (e->interrupts)
+               thread_wakeup ((event_t) &intr_thread);
 }
 
+/* main service thread for firing irqs */
 void
 intr_thread (void)
 {
-  user_intr_t *e;
-  int id;
-  ipc_port_t dst_port;
-  queue_init (&main_intr_queue);
-
+       unsigned total_interrupts;
   for (;;)
     {
-      assert_wait ((event_t) &intr_thread, FALSE);
-      /* Make sure we wake up from times to times to check for aborted 
processes */
+       assert_wait ((event_t) &intr_thread, FALSE);
+       /* Make sure we wake up from times to times to check for aborted 
processes */
       thread_set_timeout (hz);
-      spl_t s = splhigh ();
-
-      /* Check for aborted processes */
-      queue_iterate (&main_intr_queue, e, user_intr_t *, chain)
-       {
-         if ((!e->dst_port || e->dst_port->ip_references == 1) && e->n_unacked)
-           {
-             printf ("irq handler [%d]: release dead delivery %d unacked irqs 
port %p entry %p\n", e->id, e->n_unacked, e->dst_port, e);
-             /* The reference of the port was increased
-              * when the port was installed.
-              * If the reference is 1, it means the port should
-              * have been destroyed and I clear unacked irqs now, so the Linux
-              * handling can trigger, and we will cleanup later after the Linux
-              * handler is cleared. */
-             /* TODO: rather immediately remove from Linux handler */
-             while (e->n_unacked)
-             {
-               __enable_irq (irqtab.irq[e->id]);
-               e->n_unacked--;
-             }
-           }
-       }
-
-      /* Now check for interrupts */
-      while (irqtab.tot_num_intr)
-       {
-         int del = 0;
-
-         queue_iterate (&main_intr_queue, e, user_intr_t *, chain)
-           {
-             /* if an entry doesn't have dest port,
-              * we should remove it. */
-             if (e->dst_port == MACH_PORT_NULL)
-               {
-                 clear_wait (current_thread (), 0, 0);
-                 del = 1;
-                 break;
-               }
-
-             if (e->interrupts)
-               {
-                 clear_wait (current_thread (), 0, 0);
-                 id = e->id;
-                 dst_port = e->dst_port;
-                 e->interrupts--;
-                 irqtab.tot_num_intr--;
-
-                 splx (s);
-                 deliver_intr (id, dst_port);
-                 s = splhigh ();
-               }
-           }
-
-         /* remove the entry without dest port from the queue and free it. */
-         if (del)
-           {
-             assert (!queue_empty (&main_intr_queue));
-             queue_remove (&main_intr_queue, e, user_intr_t *, chain);
-             if (e->n_unacked)
-               printf("irq handler [%d]: still %d unacked irqs in entry %p\n", 
e->id, e->n_unacked, e);
-             while (e->n_unacked)
-             {
-               __enable_irq (irqtab.irq[e->id]);
-               e->n_unacked--;
-             }
-             printf("irq handler [%d]: removed entry %p\n", e->id, e);
-             splx (s);
-             kfree ((vm_offset_t) e, sizeof (*e));
-             s = splhigh ();
-           }
-       }
-      splx (s);
-      thread_block (NULL);
+      thread_block(NULL);
+       do
+                               {
+                                       total_interrupts = 0;
+                                       for (unsigned id = 0; id < NINTR; ++id)
+                                               {
+                                                       user_intr_t *e = 
irqtab.handler[id];
+                                                       if (!e) continue;
+                                                       clear_wait 
(current_thread (), 0, 0);
+                                                       
user_intr_notification_t next;
+                                                       
user_intr_notification_t n;
+                                                       /* go through each 
notification to fire or remove dead ones */
+                                                       PROTECT(e->lock,
+                                                               {
+                                                                       
queue_iterate (&e->notification_queue, n, user_intr_notification_t, chain)
+                                                                               
{
+                                                                               
        /* is the notification port dead? */
+                                                                               
        if (n->dst_port->ip_references == 1) 
+                                                                               
                {
+                                                                               
                        /* dead, move it to the dead queue */
+                                                                               
                        next = (user_intr_notification_t)queue_next(&n->chain);
+                                                                               
                        queue_remove(&e->notification_queue, n, 
user_intr_notification_t, chain);
+                                                                               
                        ipc_port_release (n->dst_port);
+                                                                               
                        kfree((vm_offset_t)n, sizeof(*n));
+                                                                               
                        --e->n_unacked;
+                                                                               
                        if (e->n_unacked == 0)
+                                                                               
                                __enable_irq(id);
+                                                                               
                        printf("dead notification port %p released \n", 
n->dst_port);
+                                                                               
                        if (queue_empty(&e->notification_queue)) 
+                                                                               
                                {
+                                                                               
                                        printf("irq %d has no registered 
notifications. remove\n", id);
+                                                                               
                                        kfree((vm_offset_t)e, sizeof(*e));
+                                                                               
                                        irqtab.handler[id] = NULL;
+                                                                               
                                        break;
+                                                                               
                                }
+                                                                               
                        n = next;
+                                                                               
                }
+                                                                               
        else if (e->interrupts)
+                                                                               
                {
+                                                                               
                        SPLHIGH(e->interrupts--);
+                                                                               
                        total_interrupts += e->interrupts;
+                                                                               
                        if (deliver_intr(id, n->dst_port))
+                                                                               
                                /* n_unacked is increased when firing. Without 
firing, there is no ack */
+                                                                               
                                e->n_unacked++;
+                                                                               
                }
+                                                                               
} /* end of queue_iterate */
+                                                               }); /* end of 
PROTECT */
+                                               }
+               }
+           while (total_interrupts);
     }
 }
 
@@ -280,4 +225,54 @@ deliver_intr (int id, ipc_port_t dst_port)
   return TRUE;
 }
 
+int
+install_user_intr_handler (int id, ipc_port_t dst_port)
+{
+       if (id > NINTR || dst_port == NULL)
+               return D_INVALID_OPERATION;
+  user_intr_t *e = irqtab.handler[id];
+  if (e == NULL) {
+               e = (user_intr_t *) kalloc (sizeof (*e));
+               if (e == NULL)
+                       return D_NO_MEMORY;
+               queue_init(&e->notification_queue);
+               e->interrupts = 0;
+               e->n_unacked = 0;
+               simple_lock_init(&e->lock);
+               irqtab.handler[id] = e;
+               /* install the new handler */
+               kern_return_t r = request_irq (id, deliver_user_intr, SA_SHIRQ, 
NULL, &irqtab);
+               if (r) {
+                       printf("could not register irq handler: %d(%08x)\n", r, 
r);
+                       irqtab.handler[id] = NULL;
+                       kfree((vm_size_t)e, sizeof(*e));
+                       return r;
+               }
+  }
+
+  /* check whether the intr entry has been in the queue. */
+  user_intr_notification_t n = search_notification (e, dst_port);
+  if (n)
+    {
+      printf ("the interrupt entry for irq %d and port %p has already been 
inserted\n", id, dst_port);
+      return D_ALREADY_OPEN;
+    }
+
+       n = (user_intr_notification_t) kalloc (sizeof (*n));
+       if (n == NULL)
+               return D_NO_MEMORY;
+
+  n->dst_port = dst_port;
+  PROTECT(e->lock, queue_enter (&e->notification_queue, n, 
user_intr_notification_t, chain));
+  printf("irq handler [%d]: new delivery port %p\n", id, dst_port);
+  return D_SUCCESS;
+}
+
+void irq_init()
+{
+       irqtab.name = "irq";
+       for (int i = 0; i < NINTR; ++i)
+               irqtab.handler[i] = NULL;
+}
+
 #endif /* MACH_XEN */
diff --git a/device/intr.h b/device/intr.h
index cd3e0bce..90bc6f4c 100644
--- a/device/intr.h
+++ b/device/intr.h
@@ -30,29 +30,27 @@
 struct irqdev;
 #include <machine/irq.h>
 
-typedef struct {
+/* a struct to hold notifications */
+struct user_intr_notification {
   queue_chain_t chain;
-  int interrupts; /* Number of interrupts occurred since last run of 
intr_thread */
-  int n_unacked;  /* Number of times irqs were disabled for this */
   ipc_port_t dst_port; /* Notification port */
-  int id; /* Mapping to machine dependent irq_t array elem */
+};
+typedef struct user_intr_notification * user_intr_notification_t;
+
+typedef struct {
+       queue_head_t notification_queue;
+  unsigned interrupts; /* Number of interrupts occurred since last run of 
intr_thread */
+  unsigned n_unacked;  /* Number of times irqs were disabled for this */
+  decl_simple_lock_data(, lock); /* a lock to protect the queues */
 } user_intr_t;
 
 struct irqdev {
   char *name;
-  void (*irqdev_ack)(struct irqdev *dev, int id);
-
-  queue_head_t *intr_queue;
-  int tot_num_intr; /* Total number of unprocessed interrupts */
-
-  /* Machine dependent */
-  irq_t irq[NINTR];
+       user_intr_t *handler[NINTR]; /* irq handlers for device_open */
 };
 
-extern queue_head_t main_intr_queue;
-extern int install_user_intr_handler (struct irqdev *dev, int id, unsigned 
long flags, user_intr_t *e);
-extern int deliver_user_intr (struct irqdev *dev, int id, user_intr_t *e);
-extern user_intr_t *insert_intr_entry (struct irqdev *dev, int id, ipc_port_t 
receive_port);
+extern int install_user_intr_handler (int id, ipc_port_t dst_port);
+extern void irq_init();
 
 void intr_thread (void);
 kern_return_t irq_acknowledge (ipc_port_t receive_port);
diff --git a/i386/i386/irq.c b/i386/i386/irq.c
index 35681191..c13138a8 100644
--- a/i386/i386/irq.c
+++ b/i386/i386/irq.c
@@ -24,15 +24,7 @@
 #include <kern/assert.h>
 #include <machine/machspl.h>
 
-extern queue_head_t main_intr_queue;
-
-static void
-irq_eoi (struct irqdev *dev, int id)
-{
-  /* TODO EOI(dev->irq[id]) */
-}
-
-static unsigned int ndisabled_irq[NINTR];
+static unsigned int ndisabled_irq[NINTR] = {0};
 
 void
 __disable_irq (irq_t irq_nr)
@@ -60,8 +52,3 @@ __enable_irq (irq_t irq_nr)
   splx(s);
 }
 
-struct irqdev irqtab = {
-  "irq", irq_eoi, &main_intr_queue, 0,
-  {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
-};
-
diff --git a/linux/dev/arch/i386/kernel/irq.c b/linux/dev/arch/i386/kernel/irq.c
index 1e911f33..6f433acb 100644
--- a/linux/dev/arch/i386/kernel/irq.c
+++ b/linux/dev/arch/i386/kernel/irq.c
@@ -78,7 +78,6 @@ struct linux_action
   void *dev_id;
   struct linux_action *next;
   unsigned long flags;
-  user_intr_t *user_intr;
 };
 
 static struct linux_action *irq_action[16] =
@@ -98,7 +97,6 @@ linux_intr (int irq)
 {
   struct pt_regs regs;
   struct linux_action *action = *(irq_action + irq);
-  struct linux_action **prev = &irq_action[irq];
   unsigned long flags;
 
   kstat.interrupts[irq]++;
@@ -110,21 +108,8 @@ linux_intr (int irq)
 
   while (action)
     {
-      // TODO I might need to check whether the interrupt belongs to
-      // the current device. But I don't do it for now.
-      if (action->user_intr)
-       {
-         if (!deliver_user_intr(&irqtab, irq, action->user_intr))
-         {
-           *prev = action->next;
-           linux_kfree(action);
-           action = *prev;
-           continue;
-         }
-       }
-      else if (action->handler)
-       action->handler (irq, action->dev_id, &regs);
-      prev = &action->next;
+      if (action->handler)
+        action->handler (irq, action->dev_id, &regs);
       action = action->next;
     }
 
@@ -194,7 +179,6 @@ setup_x86_irq (int irq, struct linux_action *new)
       /* Can't share interrupts unless both are same type */
       if ((old->flags ^ new->flags) & SA_INTERRUPT)
        return (-EBUSY);
-
       /* add new interrupt at end of irq queue */
       do
        {
@@ -219,53 +203,6 @@ setup_x86_irq (int irq, struct linux_action *new)
   return 0;
 }
 
-int
-install_user_intr_handler (struct irqdev *dev, int id, unsigned long flags,
-                         user_intr_t *user_intr)
-{
-  struct linux_action *action;
-  struct linux_action *old;
-  int retval;
-
-  unsigned int irq = dev->irq[id];
-
-  assert (irq < 16);
-
-  /* Test whether the irq handler has been set */
-  // TODO I need to protect the array when iterating it.
-  old = irq_action[irq];
-  while (old)
-    {
-      if (old->user_intr && old->user_intr->dst_port == user_intr->dst_port)
-       {
-         printk ("The interrupt handler has already been installed on line 
%d", irq);
-         return linux_to_mach_error (-EAGAIN);
-       }
-      old = old->next;
-    }
-
-  /*
-   * Hmm... Should I use `kalloc()' ?
-   * By OKUJI Yoshinori.
-   */
-  action = (struct linux_action *)
-    linux_kmalloc (sizeof (struct linux_action), GFP_KERNEL);
-  if (action == NULL)
-    return linux_to_mach_error (-ENOMEM);
-  
-  action->handler = NULL;
-  action->next = NULL;
-  action->dev_id = NULL;
-  action->flags = SA_SHIRQ;
-  action->user_intr = user_intr;
-  
-  retval = setup_x86_irq (irq, action);
-  if (retval)
-    linux_kfree (action);
-  
-  return linux_to_mach_error (retval);
-}
-
 /*
  * Attach a handler to an IRQ.
  */
@@ -294,7 +231,6 @@ request_irq (unsigned int irq, void (*handler) (int, void 
*, struct pt_regs *),
   action->next = NULL;
   action->dev_id = dev_id;
   action->flags = flags;
-  action->user_intr = NULL;
   
   retval = setup_x86_irq (irq, action);
   if (retval)
-- 
2.28.0.rc1





reply via email to

[Prev in Thread] Current Thread [Next in Thread]