summaryrefslogtreecommitdiff
path: root/device/intr.c
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/intr.c
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/intr.c')
-rw-r--r--device/intr.c157
1 files changed, 123 insertions, 34 deletions
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 ();