summaryrefslogtreecommitdiff
path: root/device
diff options
context:
space:
mode:
authorSamuel Thibault <samuel.thibault@ens-lyon.org>2020-07-10 00:23:32 +0200
committerSamuel Thibault <samuel.thibault@ens-lyon.org>2020-07-10 00:31:38 +0200
commit6054cda4de2341b9a77ec4421411725f3684006b (patch)
tree02622e721dffe9fdb73e35805de2fbf8a2a5aea8 /device
parent2dbf108457d0a0057cc63d5b3b89fd4da48d2a72 (diff)
Add hardware interrupt notification mechanism
This allows privileged userland drivers to get notifications of hardware interrupts. Initial work by Zheng Da, reworked by Damien Zammit and myself. * Makefrag.am (libkernel_a_SOURCES): Add device/intr.c and device/intr.h. (include_device_HEADERS): Add include/device/notify.defs and include/device/notify.h. * device/dev_hdr.h (name_equal): Add declaration. * device/ds_routines.c: Include <device/intr.h> (ds_device_intr_register, ds_device_intr_ack): New functions. * device/intr.c, device/intr.h: New files. * doc/mach.texi (Device Interrupt): New section. * i386/Makefrag.am (libkernel_a_SOURCES): Add i386/i386/irq.c and i386/i386/irq.h. * i386/i386/irq.c, i386/i386/irq.h: New files. * i386/i386at/conf.c: Include <device/intr.h>. (irqname): New macro. (dev_name_list): Add irq device. * include/device/device.defs (device_intr_register, device_intr_ack): New RPCs. * include/device/notify.defs, include/device/notify.h: New files. * kern/startup.c: Include <device/intr.h> (start_kernel_threads): Start intr_thread thread. * linux/dev/arch/i386/kernel/irq.c: Include <device/intr.h> (linux_action): Add user_intr field. (linux_intr): Call user_intr action if any. (mask_irq, unmask_irq): Move functions to i386/i386/pic.c (__disable_irq, __enable_irq): Move functions to i386/i386/irq.c. (install_user_intr_handler): New function. (request_irq): Initialize user_intr field. * linux/src/include/asm-i386/irq.h (__disable_irq, __enable_irq): Remove prototypes. * i386/i386/pic.c (mask_irq, unmask_irq): New functions. * i386/i386/pic.h (mask_irq, unmask_irq): New prototypes.
Diffstat (limited to 'device')
-rw-r--r--device/dev_hdr.h9
-rw-r--r--device/ds_routines.c54
-rw-r--r--device/intr.c283
-rw-r--r--device/intr.h62
4 files changed, 408 insertions, 0 deletions
diff --git a/device/dev_hdr.h b/device/dev_hdr.h
index ad98e0bb..4bd12c1c 100644
--- a/device/dev_hdr.h
+++ b/device/dev_hdr.h
@@ -146,4 +146,13 @@ extern void dev_set_indirection(
dev_ops_t ops,
int unit);
+/*
+ * compare device name
+ */
+extern boolean_t __attribute__ ((pure))
+name_equal(
+ const char *src,
+ int len,
+ const char *target);
+
#endif /* _DEVICE_DEV_HDR_H_ */
diff --git a/device/ds_routines.c b/device/ds_routines.c
index fc051e8f..78ff51fe 100644
--- a/device/ds_routines.c
+++ b/device/ds_routines.c
@@ -92,6 +92,7 @@
#include <device/device_port.h>
#include <device/device_reply.user.h>
#include <device/device_emul.h>
+#include <device/intr.h>
#include <machine/machspl.h>
@@ -319,6 +320,59 @@ 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 (device_t dev, int id,
+ int flags, ipc_port_t receive_port)
+{
+ kern_return_t err;
+ mach_device_t mdev = dev->emul_data;
+
+ /* Refuse if device is dead or not completely open. */
+ if (dev == DEVICE_NULL)
+ return D_NO_SUCH_DEVICE;
+
+ /* No flag is defined for now */
+ if (flags != 0)
+ return D_INVALID_OPERATION;
+
+ /* Must be called on the irq device only */
+ if (! name_equal(mdev->dev_ops->d_name, 3, "irq"))
+ return D_INVALID_OPERATION;
+
+ user_intr_t *e = insert_intr_entry (&irqtab, id, receive_port);
+ if (!e)
+ 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.
+ err = install_user_intr_handler (&irqtab, id, flags, e);
+ if (err == D_SUCCESS)
+ {
+ /* If the port is installed successfully, increase its reference by 1.
+ * Thus, the port won't be destroyed after its task is terminated. */
+ ip_reference (receive_port);
+ }
+ return err;
+}
+
+kern_return_t
+ds_device_intr_ack (device_t dev, ipc_port_t receive_port)
+{
+ mach_device_t mdev = dev->emul_data;
+
+ /* Refuse if device is dead or not completely open. */
+ if (dev == DEVICE_NULL)
+ return D_NO_SUCH_DEVICE;
+
+ /* Must be called on the irq device only */
+ if (! name_equal(mdev->dev_ops->d_name, 3, "irq"))
+ return D_INVALID_OPERATION;
+
+ return irq_acknowledge(receive_port);
+}
+
boolean_t
ds_notify (mach_msg_header_t *msg)
{
diff --git a/device/intr.c b/device/intr.c
new file mode 100644
index 00000000..fbb9f495
--- /dev/null
+++ b/device/intr.c
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2010, 2011, 2016, 2019 Free Software Foundation, Inc.
+ *
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ *
+ * THE FREE SOFTWARE FOUNDATIONALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION. THE FREE SOFTWARE FOUNDATION DISCLAIMS ANY LIABILITY OF ANY KIND
+ * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ */
+
+#include <device/intr.h>
+#include <device/device_types.h>
+#include <device/device_port.h>
+#include <device/notify.h>
+#include <kern/printf.h>
+#include <machine/spl.h>
+#include <machine/irq.h>
+#include <ipc/ipc_space.h>
+
+#ifndef MACH_XEN
+
+queue_head_t main_intr_queue;
+static boolean_t deliver_intr (int id, ipc_port_t dst_port);
+
+static user_intr_t *
+search_intr (struct irqdev *dev, ipc_port_t dst_port)
+{
+ user_intr_t *e;
+ queue_iterate (dev->intr_queue, e, user_intr_t *, chain)
+ {
+ if (e->dst_port == dst_port)
+ return e;
+ }
+ return NULL;
+}
+
+kern_return_t
+irq_acknowledge (ipc_port_t receive_port)
+{
+ user_intr_t *e;
+ kern_return_t ret = 0;
+
+ spl_t s = splhigh ();
+ e = search_intr (&irqtab, receive_port);
+
+ if (!e)
+ printf("didn't find user intr for interrupt !?\n");
+ else
+ {
+ if (!e->n_unacked)
+ ret = D_INVALID_OPERATION;
+ else
+ e->n_unacked--;
+ }
+ splx (s);
+
+ if (ret)
+ return ret;
+
+ if (irqtab.irqdev_ack)
+ (*(irqtab.irqdev_ack)) (&irqtab, e->id);
+
+ __enable_irq (irqtab.irq[e->id]);
+
+ return D_SUCCESS;
+}
+
+/* This function can only be used in the interrupt handler. */
+static void
+queue_intr (struct irqdev *dev, int id, 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 (dev->irq[id]);
+
+ spl_t s = splhigh ();
+ e->n_unacked++;
+ e->interrupts++;
+ dev->tot_num_intr++;
+ splx (s);
+
+ thread_wakeup ((event_t) &intr_thread);
+}
+
+int
+deliver_user_intr (struct irqdev *dev, int id, user_intr_t *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->dst_port
+ && e->dst_port->ip_references == 1)
+ {
+ printf ("irq handler [%d]: release a dead delivery port %p entry %p\n", id, e->dst_port, e);
+ ipc_port_release (e->dst_port);
+ e->dst_port = MACH_PORT_NULL;
+ thread_wakeup ((event_t) &intr_thread);
+ return 0;
+ }
+ else
+ {
+ queue_intr (dev, id, e);
+ return 1;
+ }
+}
+
+/* insert an interrupt entry in the queue.
+ * This entry exists in the queue until
+ * the corresponding interrupt port is removed.*/
+user_intr_t *
+insert_intr_entry (struct irqdev *dev, int id, ipc_port_t dst_port)
+{
+ user_intr_t *e, *new, *ret;
+ int free = 0;
+
+ new = (user_intr_t *) kalloc (sizeof (*new));
+ if (new == NULL)
+ return NULL;
+
+ /* check whether the intr entry has been in the queue. */
+ spl_t s = splhigh ();
+ e = search_intr (dev, dst_port);
+ if (e)
+ {
+ printf ("the interrupt entry for irq[%d] and port %p has already been inserted\n", id, dst_port);
+ free = 1;
+ ret = NULL;
+ goto out;
+ }
+ printf("irq handler [%d]: new delivery port %p entry %p\n", id, dst_port, new);
+ ret = new;
+ new->id = id;
+ new->dst_port = dst_port;
+ new->interrupts = 0;
+ new->n_unacked = 0;
+
+ queue_enter (dev->intr_queue, new, user_intr_t *, chain);
+out:
+ splx (s);
+ if (free)
+ kfree ((vm_offset_t) new, sizeof (*new));
+ return ret;
+}
+
+void
+intr_thread (void)
+{
+ user_intr_t *e;
+ int id;
+ ipc_port_t dst_port;
+ queue_init (&main_intr_queue);
+
+ 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);
+ spl_t s = splhigh ();
+
+ /* Check for aborted processes */
+ queue_iterate (&main_intr_queue, e, user_intr_t *, chain)
+ {
+ if ((!e->dst_port || e->dst_port->ip_references == 1) && e->n_unacked)
+ {
+ printf ("irq handler [%d]: release dead delivery %d unacked irqs port %p entry %p\n", e->id, e->n_unacked, e->dst_port, 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 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->n_unacked)
+ {
+ __enable_irq (irqtab.irq[e->id]);
+ e->n_unacked--;
+ }
+ }
+ }
+
+ /* Now check for interrupts */
+ while (irqtab.tot_num_intr)
+ {
+ int del = 0;
+
+ queue_iterate (&main_intr_queue, e, user_intr_t *, chain)
+ {
+ /* if an entry doesn't have dest port,
+ * we should remove it. */
+ if (e->dst_port == MACH_PORT_NULL)
+ {
+ clear_wait (current_thread (), 0, 0);
+ del = 1;
+ break;
+ }
+
+ if (e->interrupts)
+ {
+ clear_wait (current_thread (), 0, 0);
+ id = e->id;
+ dst_port = e->dst_port;
+ e->interrupts--;
+ irqtab.tot_num_intr--;
+
+ splx (s);
+ deliver_intr (id, dst_port);
+ s = splhigh ();
+ }
+ }
+
+ /* remove the entry without dest port from the queue and free it. */
+ if (del)
+ {
+ assert (!queue_empty (&main_intr_queue));
+ queue_remove (&main_intr_queue, e, user_intr_t *, chain);
+ if (e->n_unacked)
+ printf("irq handler [%d]: still %d unacked irqs in entry %p\n", e->id, e->n_unacked, e);
+ while (e->n_unacked)
+ {
+ __enable_irq (irqtab.irq[e->id]);
+ e->n_unacked--;
+ }
+ printf("irq handler [%d]: removed entry %p\n", e->id, e);
+ splx (s);
+ kfree ((vm_offset_t) e, sizeof (*e));
+ s = splhigh ();
+ }
+ }
+ splx (s);
+ thread_block (NULL);
+ }
+}
+
+static boolean_t
+deliver_intr (int id, ipc_port_t dst_port)
+{
+ ipc_kmsg_t kmsg;
+ device_intr_notification_t *n;
+ mach_port_t dest = (mach_port_t) dst_port;
+
+ if (dest == MACH_PORT_NULL)
+ return FALSE;
+
+ kmsg = ikm_alloc(sizeof *n);
+ if (kmsg == IKM_NULL)
+ return FALSE;
+
+ ikm_init(kmsg, sizeof *n);
+ n = (device_intr_notification_t *) &kmsg->ikm_header;
+
+ mach_msg_header_t *m = &n->intr_header;
+ mach_msg_type_t *t = &n->intr_type;
+
+ m->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, 0);
+ m->msgh_size = sizeof *n;
+ m->msgh_seqno = DEVICE_NOTIFY_MSGH_SEQNO;
+ m->msgh_local_port = MACH_PORT_NULL;
+ m->msgh_remote_port = MACH_PORT_NULL;
+ m->msgh_id = DEVICE_INTR_NOTIFY;
+
+ t->msgt_name = MACH_MSG_TYPE_INTEGER_32;
+ t->msgt_size = 32;
+ t->msgt_number = 1;
+ t->msgt_inline = TRUE;
+ t->msgt_longform = FALSE;
+ t->msgt_deallocate = FALSE;
+ t->msgt_unused = 0;
+
+ n->intr_header.msgh_remote_port = dest;
+ n->id = id;
+
+ ipc_port_copy_send (dst_port);
+ ipc_mqueue_send_always(kmsg);
+
+ return TRUE;
+}
+
+#endif /* MACH_XEN */
diff --git a/device/intr.h b/device/intr.h
new file mode 100644
index 00000000..cd3e0bce
--- /dev/null
+++ b/device/intr.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010, 2011, 2019 Free Software Foundation, Inc.
+ *
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ *
+ * THE FREE SOFTWARE FOUNDATIONALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION. THE FREE SOFTWARE FOUNDATION DISCLAIMS ANY LIABILITY OF ANY KIND
+ * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ */
+
+#ifndef __INTR_H__
+#define __INTR_H__
+
+#ifndef MACH_XEN
+
+#include <mach/kern_return.h>
+#include <mach/port.h>
+#include <kern/queue.h>
+#include <ipc/ipc_port.h>
+#include <device/conf.h>
+
+#define DEVICE_NOTIFY_MSGH_SEQNO 0
+
+#include <sys/types.h>
+
+struct irqdev;
+#include <machine/irq.h>
+
+typedef struct {
+ queue_chain_t chain;
+ int interrupts; /* Number of interrupts occurred since last run of intr_thread */
+ int n_unacked; /* Number of times irqs were disabled for this */
+ ipc_port_t dst_port; /* Notification port */
+ int id; /* Mapping to machine dependent irq_t array elem */
+} user_intr_t;
+
+struct irqdev {
+ char *name;
+ void (*irqdev_ack)(struct irqdev *dev, int id);
+
+ queue_head_t *intr_queue;
+ int tot_num_intr; /* Total number of unprocessed interrupts */
+
+ /* Machine dependent */
+ irq_t irq[NINTR];
+};
+
+extern queue_head_t main_intr_queue;
+extern int install_user_intr_handler (struct irqdev *dev, int id, unsigned long flags, user_intr_t *e);
+extern int deliver_user_intr (struct irqdev *dev, int id, user_intr_t *e);
+extern user_intr_t *insert_intr_entry (struct irqdev *dev, int id, ipc_port_t receive_port);
+
+void intr_thread (void);
+kern_return_t irq_acknowledge (ipc_port_t receive_port);
+
+#endif /* MACH_XEN */
+
+#endif