/* * Copyright (c) 2010-2016 Free Software Foundation. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "interrupt.h" #ifndef MACH_XEN /* The cache which holds our proxy memory objects. */ 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 new_style; /* version2 notifications?. */ 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; /* This function can only be used in the interrupt handler. */ boolean_t queue_intr (struct intr_entry *e) { unsigned long flags; cpu_intr_save (&flags); e->interrupts++; cpu_intr_restore (flags); thread_wakeup ((event_t) &intr_thread); return TRUE; } /* insert an interrupt entry in the queue. * This entry exists in the queue until * the corresponding interrupt port is removed.*/ kern_return_t insert_intr_entry (int line, ipc_port_t dest, int new_style, /* version2 notifications? */ struct intr_entry **entry) { kern_return_t err = 0; unsigned long flags; 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->new_style = new_style; 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. */ cpu_intr_save (&flags); queue_iterate (&intr_queue, e, struct intr_entry *, chain) if (e->dest == dest && e->line == line) { printf ("the interrupt entry for line %d and port %p " "has already been inserted before.\n", line, dest); free = 1; err = D_ALREADY_OPEN; goto out; } queue_enter (&intr_queue, new, struct intr_entry *, chain); out: cpu_intr_restore (flags); if (free) { 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) { struct intr_entry *entry; if (msg->msgh_id == MACH_NOTIFY_NO_SENDERS) { extern void enable_irq (unsigned int irq_nr); mach_no_senders_notification_t *ns; ns = (mach_no_senders_notification_t *) msg; entry = intr_entry_port_lookup ((ipc_port_t) ns->not_header.msgh_remote_port); assert (entry); enable_irq (entry->line); /* entry is not locked here */ return TRUE; } else if (msg->msgh_id == MACH_NOTIFY_DEAD_NAME) { int line; mach_dead_name_notification_t *dn; unsigned long flags; dn = (mach_dead_name_notification_t *) msg; entry = intr_entry_port_lookup ((ipc_port_t) dn->not_header.msgh_remote_port); assert (entry); cpu_intr_save (&flags); line = entry->line; assert (!queue_empty (&intr_queue)); queue_remove (&intr_queue, entry, struct intr_entry *, chain); remove_user_intr_handler (entry->line, entry); cpu_intr_restore (flags); 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 init_mach_intr_notification (mach_intr_notification_t *n) { 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, MACH_MSG_TYPE_PORT_SEND); m->msgh_size = sizeof *n; m->msgh_seqno = INTR_NOTIFY_MSGH_SEQNO; m->msgh_local_port = MACH_PORT_NULL; m->msgh_remote_port = MACH_PORT_NULL; m->msgh_id = MACH_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; } static boolean_t deliver_intr (int line, ipc_port_t dest_port, ipc_port_t interrupt_port) { ipc_kmsg_t kmsg; mach_intr_notification_t *n; ipc_port_t sright, sonce, old; mach_port_t dest = (mach_port_t) dest_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 = (mach_intr_notification_t *) &kmsg->ikm_header; *n = mach_intr_notification_template; /* Arrange no-senders notification. */ sright = ipc_port_make_send (interrupt_port); sonce = ipc_port_make_sonce (interrupt_port); ip_lock (interrupt_port); ipc_port_nsrequest (interrupt_port, interrupt_port->ip_mscount, sonce, &old); if (old != IP_NULL) ipc_port_release_sonce (old); n->intr_header.msgh_remote_port = dest; n->intr_header.msgh_local_port = (mach_port_t) sright; n->line = line; ipc_port_copy_send (dest_port); ipc_mqueue_send_always(kmsg); return TRUE; } mach_intr_notification2_t mach_intr_notification2_template; static void init_mach_intr_notification2 (mach_intr_notification2_t *n) { mach_msg_header_t *m = &n->intr_header; mach_msg_type_t *t = &n->line_type; m->msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, 0); m->msgh_size = sizeof *n; m->msgh_seqno = INTR_NOTIFY_MSGH_SEQNO; m->msgh_local_port = MACH_PORT_NULL; m->msgh_remote_port = MACH_PORT_NULL; m->msgh_id = MACH_INTR_NOTIFY2; 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; t = &n->count_type; 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; t = &n->port_type; t->msgt_name = MACH_MSG_TYPE_MOVE_SEND; t->msgt_size = 32; t->msgt_number = 1; t->msgt_inline = TRUE; t->msgt_longform = FALSE; t->msgt_deallocate = FALSE; t->msgt_unused = 0; } static boolean_t deliver_intr_notification2 (ipc_port_t notification_port, int line, int count, ipc_port_t interrupt_port) { ipc_kmsg_t kmsg; ipc_port_t sright, sonce, old; mach_intr_notification2_t *n; assert (notification_port); assert (interrupt_port); kmsg = ikm_alloc(sizeof *n); if (kmsg == IKM_NULL) return FALSE; ikm_init(kmsg, sizeof *n); n = (mach_intr_notification2_t *) &kmsg->ikm_header; *n = mach_intr_notification2_template; /* Arrange no-senders notification. */ sright = ipc_port_make_send (interrupt_port); sonce = ipc_port_make_sonce (interrupt_port); ip_lock (interrupt_port); ipc_port_nsrequest (interrupt_port, interrupt_port->ip_mscount, sonce, &old); if (old != IP_NULL) ipc_port_release_sonce (old); n->intr_header.msgh_remote_port = (mach_port_t) notification_port; n->line = line; n->count = count; n->interrupt_port = (mach_port_t) sright; ipc_port_copy_send (notification_port); ipc_mqueue_send_always(kmsg); return TRUE; } void intr_thread () { struct intr_entry *e; int line; ipc_port_t dest; queue_init (&intr_queue); init_mach_intr_notification (&mach_intr_notification_template); init_mach_intr_notification2 (&mach_intr_notification2_template); kmem_cache_init (&intr_entry_cache, "intr_entry", sizeof (struct intr_entry), 0, NULL, 0); for (;;) { unsigned long flags; assert_wait ((event_t) &intr_thread, FALSE); cpu_intr_save (&flags); restart: queue_iterate (&intr_queue, e, struct intr_entry *, chain) if (e->interrupts) { int ok; ipc_port_t interrupt_port = e->port; int count = e->interrupts; line = e->line; dest = e->dest; if (e->new_style) { e->interrupts = 0; cpu_intr_restore (flags); ok = deliver_intr_notification2 (dest, line, count, interrupt_port); printf ("new: count? %d ok? %d\n", count, ok); cpu_intr_save (&flags); } else { e->interrupts--; cpu_intr_restore (flags); deliver_intr (line, dest, interrupt_port); cpu_intr_save (&flags); assert (e->interrupts == 0); } /* We cannot assume that e still exists at this point because we released the lock. Hence we restart the iteration. */ goto restart; } cpu_intr_restore (flags); thread_block (thread_no_continuation); } } #else /* MACH_XEN */ boolean_t intr_entry_notify (mach_msg_header_t *msg) { panic ("not reached"); } #endif /* MACH_XEN */