summaryrefslogtreecommitdiff
path: root/device/intr.c
diff options
context:
space:
mode:
Diffstat (limited to 'device/intr.c')
-rw-r--r--device/intr.c168
1 files changed, 122 insertions, 46 deletions
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 <device/intr.h>
#include <device/ds_routines.h>
+#include <ipc/ipc_space.h>
#include <kern/queue.h>
#include <kern/printf.h>
+#include <mach/notify.h>
#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);
}