summaryrefslogtreecommitdiff
path: root/device
diff options
context:
space:
mode:
authorJustus Winter <justus@gnupg.org>2016-02-26 15:24:45 +0100
committerJustus Winter <justus@gnupg.org>2016-02-26 15:24:45 +0100
commite5921e151ef397b25db7fe85a325728503a77e9b (patch)
treed0ed9042588d7cc0888cb982d8be30ec2f7be91f /device
parentad33ca5d3fbc25618d26b7af4d9a67be041c779a (diff)
Fix reference counting; use dead-name notification
Diffstat (limited to 'device')
-rw-r--r--device/ds_routines.c15
-rw-r--r--device/interrupt.h4
-rw-r--r--device/intr.c168
3 files changed, 130 insertions, 57 deletions
diff --git a/device/ds_routines.c b/device/ds_routines.c
index 7a5dda5..2b9869a 100644
--- a/device/ds_routines.c
+++ b/device/ds_routines.c
@@ -87,6 +87,7 @@
#include <device/dev_hdr.h>
#include <device/conf.h>
#include <device/io_req.h>
+#include <device/interrupt.h>
#include <device/ds_routines.h>
#include <device/net_status.h>
#include <device/device_port.h>
@@ -332,6 +333,9 @@ experimental_device_intr_register (ipc_port_t master_port, int line,
if (master_port != master_device_port)
return D_INVALID_OPERATION;
+ if (receive_port == MACH_PORT_NULL)
+ return D_INVALID_OPERATION;
+
/* XXX: move to arch-specific */
if (line < 0 || line >= 16)
return D_INVALID_OPERATION;
@@ -339,17 +343,8 @@ experimental_device_intr_register (ipc_port_t master_port, int line,
ret = insert_intr_entry (line, receive_port, &entry);
if (ret)
return ret;
- // 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, entry);
-
- /* If the port is installed successfully, increase its reference by 1.
- * Thus, the port won't be destroyed after its task is terminated. */
- if (ret == 0)
- ip_reference (receive_port);
- return ret;
+ return install_user_intr_handler (line, flags, entry);
#endif /* MACH_XEN */
}
diff --git a/device/interrupt.h b/device/interrupt.h
index 0de43c2..8adc02d 100644
--- a/device/interrupt.h
+++ b/device/interrupt.h
@@ -3,12 +3,14 @@
struct intr_entry;
boolean_t queue_intr (struct intr_entry *e);
-int insert_intr_entry (int line, ipc_port_t dest, struct intr_entry **entry);
+kern_return_t insert_intr_entry (int line, ipc_port_t dest,
+ struct intr_entry **entry);
int install_user_intr_handler (unsigned int line,
unsigned long flags,
struct intr_entry *entry);
+boolean_t intr_entry_notify (mach_msg_header_t *msg);
void intr_thread (void);
#endif /* DEVICE_INTERRUPT_H */
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);
}