summaryrefslogtreecommitdiff
path: root/device
diff options
context:
space:
mode:
authorSamuel Thibault <samuel.thibault@ens-lyon.org>2019-11-12 01:07:12 +0100
committerSamuel Thibault <samuel.thibault@ens-lyon.org>2019-11-12 02:30:42 +0100
commit2b11bf45284dc1e2e5a716889982f922b9baa2f9 (patch)
treeb43f8f738b4d254a7d1a6bb7908199349616f834 /device
parentbf21e77af313cd9e11d8a494935cf01a9be1a555 (diff)
Move most user interrupt management to device/intr.c
Linux drivers should not have to care about delivery ports etc. Introducing a user_intr_t structure allows to clear stuff on userland process abortion.
Diffstat (limited to 'device')
-rw-r--r--device/ds_routines.c21
-rw-r--r--device/intr.c157
2 files changed, 129 insertions, 49 deletions
diff --git a/device/ds_routines.c b/device/ds_routines.c
index f6857547..13c9a63e 100644
--- a/device/ds_routines.c
+++ b/device/ds_routines.c
@@ -319,6 +319,7 @@ ds_device_map (device_t dev, vm_prot_t prot, vm_offset_t offset,
offset, size, pager, unmap);
}
+/* TODO: missing deregister support */
io_return_t
ds_device_intr_register (ipc_port_t master_port, int line,
int id, int flags, ipc_port_t receive_port)
@@ -326,9 +327,6 @@ ds_device_intr_register (ipc_port_t master_port, int line,
#ifdef MACH_XEN
return D_INVALID_OPERATION;
#else /* MACH_XEN */
- extern int install_user_intr_handler (unsigned int line,
- unsigned long flags,
- ipc_port_t dest);
io_return_t ret;
/* Open must be called on the master device port. */
@@ -339,13 +337,13 @@ ds_device_intr_register (ipc_port_t master_port, int line,
if (line < 0 || line >= 16)
return D_INVALID_OPERATION;
- ret = insert_intr_entry (line, receive_port);
- if (ret)
- return ret;
+ user_intr_t *user_intr = insert_intr_entry (line, receive_port);
+ if (!user_intr)
+ return D_NO_MEMORY;
// TODO The original port should be replaced
// when the same device driver calls it again,
// in order to handle the case that the device driver crashes and restarts.
- ret = install_user_intr_handler (line, flags, receive_port);
+ ret = install_user_intr_handler (line, flags, user_intr);
if (ret == 0)
{
@@ -1852,14 +1850,7 @@ ds_device_intr_enable(ipc_port_t master_port, int line, char status)
if (master_port != master_device_port)
return D_INVALID_OPERATION;
- /* FIXME: should count how many disable/enable was done for a given receiving
- * port, to be able to restore proper count on crashes */
- if (status)
- /* TODO: better name for generic-to-arch-specific call */
- __enable_irq (line);
- else
- __disable_irq (line);
- return 0;
+ return user_intr_enable(line, status);
#endif /* MACH_XEN */
}
diff --git a/device/intr.c b/device/intr.c
index e4c1ff96..95c36719 100644
--- a/device/intr.c
+++ b/device/intr.c
@@ -10,15 +10,6 @@
static boolean_t deliver_intr (int line, ipc_port_t dest_port);
-struct intr_entry
-{
- queue_chain_t chain;
- ipc_port_t dest;
- int line;
- /* The number of interrupts occur since last run of intr_thread. */
- int interrupts;
-};
-
static queue_head_t intr_queue;
/* The total number of unprocessed interrupts. */
static int tot_num_intr;
@@ -35,20 +26,71 @@ search_intr (int line, ipc_port_t dest)
return NULL;
}
-/* This function can only be used in the interrupt handler. */
-void
-queue_intr (int line, ipc_port_t dest)
+static struct intr_entry *
+search_intr_line (int line)
+{
+ struct intr_entry *e;
+ queue_iterate (&intr_queue, e, struct intr_entry *, chain)
+ {
+ if (e->line == line &&
+ (e->dest != MACH_PORT_NULL
+ && e->dest->ip_references != 1
+ && e->unacked_interrupts))
+ return e;
+ }
+ return NULL;
+}
+
+kern_return_t user_intr_enable (int line, char status)
{
- extern void intr_thread ();
struct intr_entry *e;
+ kern_return_t ret = D_SUCCESS;
+
+ cli();
+ /* FIXME: Use search_intr instead once we get the delivery port from ds_device_intr_enable, and get rid of search_intr_line */
+ e = search_intr_line (line);
+
+ if (!e)
+ printf("didn't find user intr for interrupt %d!?\n", line);
+ else if (status)
+ {
+ if (!e->unacked_interrupts)
+ ret = D_INVALID_OPERATION;
+ else
+ e->unacked_interrupts--;
+ }
+ else
+ {
+ e->unacked_interrupts++;
+ if (!e->unacked_interrupts)
+ {
+ ret = D_INVALID_OPERATION;
+ e->unacked_interrupts--;
+ }
+ }
+ sti();
+
+ if (ret)
+ return ret;
+ if (status)
+ /* TODO: better name for generic-to-arch-specific call */
+ __enable_irq (line);
+ else
+ __disable_irq (line);
+ return D_SUCCESS;
+}
+
+/* This function can only be used in the interrupt handler. */
+static void
+queue_intr (int line, user_intr_t *e)
+{
/* Until userland has handled the IRQ in the driver, we have to keep it
* disabled. Level-triggered interrupts would keep raising otherwise. */
__disable_irq (line);
cli ();
- e = search_intr (line, dest);
- assert (e);
+ e->unacked_interrupts++;
e->interrupts++;
tot_num_intr++;
sti ();
@@ -56,50 +98,67 @@ queue_intr (int line, ipc_port_t dest)
thread_wakeup ((event_t) &intr_thread);
}
+int deliver_user_intr (int line, user_intr_t *intr)
+{
+ /* 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 (intr->dest
+ && intr->dest->ip_references == 1)
+ {
+ ipc_port_release (intr->dest);
+ intr->dest = MACH_PORT_NULL;
+ printf ("irq handler %d: release a dead delivery port\n", line);
+ thread_wakeup ((event_t) &intr_thread);
+ return 0;
+ }
+ else
+ {
+ queue_intr (line, intr);
+ return 1;
+ }
+}
+
/* insert an interrupt entry in the queue.
* This entry exists in the queue until
* the corresponding interrupt port is removed.*/
-int
+user_intr_t *
insert_intr_entry (int line, ipc_port_t dest)
{
- int err = 0;
- struct intr_entry *e, *new;
+ struct intr_entry *e, *new, *ret;
int free = 0;
new = (struct intr_entry *) kalloc (sizeof (*new));
if (new == NULL)
- return D_NO_MEMORY;
+ return NULL;
/* check whether the intr entry has been in the queue. */
cli ();
e = search_intr (line, dest);
if (e)
{
- printf ("the interrupt entry for line %d and port %p has been inserted\n",
- line, dest);
+ printf ("the interrupt entry for line %d and port %p has already been inserted\n", line, dest);
free = 1;
- err = D_ALREADY_OPEN;
+ ret = NULL;
goto out;
}
+ ret = new;
new->line = line;
new->dest = dest;
new->interrupts = 0;
+
+ /* For now netdde calls device_intr_enable once after registration. Assume
+ * it does so for now. When we move to IRQ acknowledgment convention we will
+ * change this. */
+ new->unacked_interrupts = 1;
+
queue_enter (&intr_queue, new, struct intr_entry *, chain);
out:
sti ();
if (free)
kfree ((vm_offset_t) new, sizeof (*new));
- return err;
-}
-
-/* this function should be called when line is disabled. */
-void mark_intr_removed (int line, ipc_port_t dest)
-{
- struct intr_entry *e;
-
- e = search_intr (line, dest);
- if (e)
- e->dest = NULL;
+ return ret;
}
void
@@ -113,7 +172,32 @@ intr_thread (void)
for (;;)
{
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);
cli ();
+
+ /* Check for aborted processes */
+ queue_iterate (&intr_queue, e, struct intr_entry *, chain)
+ {
+ if (!e->dest || e->dest->ip_references == 1)
+ {
+ printf ("irq handler %d: release dead delivery %d unacked irqs\n", e->line, e->unacked_interrupts);
+ /* 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->unacked_interrupts)
+ {
+ __enable_irq(e->line);
+ e->unacked_interrupts--;
+ }
+ }
+ }
+
+ /* Now check for interrupts */
while (tot_num_intr)
{
int del = 0;
@@ -122,7 +206,7 @@ intr_thread (void)
{
/* if an entry doesn't have dest port,
* we should remove it. */
- if (e->dest == NULL)
+ if (e->dest == MACH_PORT_NULL)
{
clear_wait (current_thread (), 0, 0);
del = 1;
@@ -148,6 +232,11 @@ intr_thread (void)
{
assert (!queue_empty (&intr_queue));
queue_remove (&intr_queue, e, struct intr_entry *, chain);
+ while (e->unacked_interrupts)
+ {
+ __enable_irq(e->line);
+ e->unacked_interrupts--;
+ }
sti ();
kfree ((vm_offset_t) e, sizeof (*e));
cli ();