From e5921e151ef397b25db7fe85a325728503a77e9b Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Fri, 26 Feb 2016 15:24:45 +0100 Subject: Fix reference counting; use dead-name notification --- device/intr.c | 168 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 122 insertions(+), 46 deletions(-) (limited to 'device/intr.c') diff --git a/device/intr.c b/device/intr.c index 0fa43c5..7a1eccb 100644 --- a/device/intr.c +++ b/device/intr.c @@ -1,7 +1,9 @@ #include #include +#include #include #include +#include #include "interrupt.h" @@ -15,12 +17,14 @@ static struct kmem_cache intr_entry_cache; struct intr_entry { + ipc_port_t port; /* We receive notifications on this port. */ queue_chain_t chain; ipc_port_t dest; int line; /* The number of interrupts occur since last run of intr_thread. */ int interrupts; }; +typedef struct intr_entry *intr_entry_t; static queue_head_t intr_queue; /* The total number of unprocessed interrupts. */ @@ -30,18 +34,6 @@ static int tot_num_intr; boolean_t queue_intr (struct intr_entry *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 destroy it now. */ - if (e->dest && e->dest->ip_references == 1) - { - /* JW: I don't like running this from an interrupt handler. */ - ipc_port_release (e->dest); - e->dest = NULL; - printf ("irq handler %d: release an dead delivery port\n", e->line); - return FALSE; - } - cli (); e->interrupts++; tot_num_intr++; @@ -54,20 +46,58 @@ queue_intr (struct intr_entry *e) /* insert an interrupt entry in the queue. * This entry exists in the queue until * the corresponding interrupt port is removed.*/ -int +kern_return_t insert_intr_entry (int line, ipc_port_t dest, struct intr_entry **entry) { - int err = 0; + kern_return_t err = 0; struct intr_entry *e, *new; int free = 0; + ipc_port_t dnnotify; + ipc_port_request_index_t dnindex; new = (struct intr_entry *) kmem_cache_alloc (&intr_entry_cache); if (new == NULL) return D_NO_MEMORY; + + /* Allocate port, keeping a reference for it. */ + new->port = ipc_port_alloc_kernel (); + if (new->port == IP_NULL) + { + kmem_cache_free (&intr_entry_cache, (vm_offset_t) new); + return KERN_RESOURCE_SHORTAGE; + } + + /* Associate the port with the object. */ + ipc_kobject_set (new->port, (ipc_kobject_t) new, IKOT_INTR_ENTRY); + new->line = line; new->dest = dest; new->interrupts = 0; + /* Register a dead-name notification so that we are notified if the + userspace handler dies. */ + dnnotify = ipc_port_make_sonce (new->port); + ip_lock (dest); + /* We use a bogus port name. We don't need it to distinguish the + notifications because we register just one per object. */ + retry: + err = ipc_port_dnrequest (dest, (mach_port_t) 1, dnnotify, &dnindex); + if (err) + { + err = ipc_port_dngrow (dest); + /* dest is unlocked */ + if (err != KERN_SUCCESS) + { + ipc_port_release_sonce (dnnotify); + kmem_cache_free (&intr_entry_cache, (vm_offset_t) new); + return err; + } + ip_lock (dest); + goto retry; + } + /* dest is locked. dnindex is only valid until we unlock it and we + might decide to cancel. */ + /* check whether the intr entry has been in the queue. */ cli (); queue_iterate (&intr_queue, e, struct intr_entry *, chain) @@ -84,11 +114,74 @@ insert_intr_entry (int line, ipc_port_t dest, struct intr_entry **entry) out: sti (); if (free) - kmem_cache_free (&intr_entry_cache, (vm_offset_t) new); + { + ipc_port_dncancel (new->dest, (mach_port_t) 1, dnindex); + ip_unlock (new->dest); + ipc_port_release_sonce (dnnotify); + ipc_port_dealloc_kernel (new->port); + ipc_port_release_send (new->dest); + kmem_cache_free (&intr_entry_cache, (vm_offset_t) new); + } + else + ip_unlock (new->dest); *entry = new; return err; } +/* Lookup a intr_entry object by its port. */ +static intr_entry_t +intr_entry_port_lookup (ipc_port_t port) +{ + struct intr_entry *entry; + + if (!IP_VALID(port)) + return 0; + + ip_lock (port); + if (ip_active (port) && (ip_kotype (port) == IKOT_INTR_ENTRY)) + entry = (struct intr_entry *) port->ip_kobject; + else + entry = 0; + ip_unlock (port); + return entry; +} + +/* Process a dead-name notification for a userspace interrupt handler + notification port. */ +boolean_t +intr_entry_notify (mach_msg_header_t *msg) +{ + if (msg->msgh_id == MACH_NOTIFY_DEAD_NAME) + { + struct intr_entry *entry; + int line; + mach_dead_name_notification_t *dn; + + dn = (mach_dead_name_notification_t *) msg; + entry = intr_entry_port_lookup + ((ipc_port_t) dn->not_header.msgh_remote_port); + assert (entry); + + cli (); + line = entry->line; + assert (!queue_empty (&intr_queue)); + queue_remove (&intr_queue, entry, struct intr_entry *, chain); + sti (); + + ipc_port_dealloc_kernel (entry->port); + ipc_port_release_send (entry->dest); + kmem_cache_free (&intr_entry_cache, (vm_offset_t) entry); + + printf ("irq handler %d: userspace handler died\n", line); + return TRUE; + } + + printf ("intr_entry_notify: strange notification %d\n", + msg->msgh_id); + return FALSE; +} + + mach_intr_notification_t mach_intr_notification_template; static void @@ -156,43 +249,26 @@ intr_thread () { assert_wait ((event_t) &intr_thread, FALSE); cli (); - while (tot_num_intr) - { - int del = 0; - queue_iterate (&intr_queue, e, struct intr_entry *, chain) + while (tot_num_intr) + queue_iterate (&intr_queue, e, struct intr_entry *, chain) + if (e->interrupts) { - /* if an entry doesn't have dest port, - * we should remove it. */ - if (e->dest == NULL) - { - del = 1; - break; - } - - if (e->interrupts) - { - line = e->line; - dest = e->dest; - e->interrupts--; - tot_num_intr--; - - sti (); - deliver_intr (line, dest); - cli (); - } - } + line = e->line; + dest = e->dest; + e->interrupts--; + tot_num_intr--; - /* remove the entry without dest port from the queue and free it. */ - if (del) - { - assert (!queue_empty (&intr_queue)); - queue_remove (&intr_queue, e, struct intr_entry *, chain); sti (); - kmem_cache_free (&intr_entry_cache, (vm_offset_t) e); + deliver_intr (line, dest); cli (); + + /* We cannot assume that e still exists at this point + because we released the lock. Hence we restart the + iteration. */ + break; } - } + sti (); thread_block (thread_no_continuation); } -- cgit v1.2.3