From 6054cda4de2341b9a77ec4421411725f3684006b Mon Sep 17 00:00:00 2001 From: Samuel Thibault Date: Fri, 10 Jul 2020 00:23:32 +0200 Subject: 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 (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 . (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 (start_kernel_threads): Start intr_thread thread. * linux/dev/arch/i386/kernel/irq.c: Include (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. --- device/dev_hdr.h | 9 ++ device/ds_routines.c | 54 ++++++++++ device/intr.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++ device/intr.h | 62 +++++++++++ 4 files changed, 408 insertions(+) create mode 100644 device/intr.c create mode 100644 device/intr.h (limited to 'device') 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 #include #include +#include #include @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#define DEVICE_NOTIFY_MSGH_SEQNO 0 + +#include + +struct irqdev; +#include + +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 -- cgit v1.2.3