diff options
author | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2019-11-12 01:07:12 +0100 |
---|---|---|
committer | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2019-11-12 02:30:42 +0100 |
commit | 2b11bf45284dc1e2e5a716889982f922b9baa2f9 (patch) | |
tree | b43f8f738b4d254a7d1a6bb7908199349616f834 /device | |
parent | bf21e77af313cd9e11d8a494935cf01a9be1a555 (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.c | 21 | ||||
-rw-r--r-- | device/intr.c | 157 |
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 (); |