/* * 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 "interrupt.h" #ifndef MACH_XEN // TODO this is only for x86 system #define sti() __asm__ __volatile__ ("sti": : :"memory") #define cli() __asm__ __volatile__ ("cli": : :"memory") /* 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 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) { cli (); e->interrupts++; sti (); 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, struct intr_entry **entry) { 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) 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: sti (); 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) { 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 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, 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_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_kmsg_t kmsg; mach_intr_notification_t *n; 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; n->intr_header.msgh_remote_port = dest; n->line = line; ipc_port_copy_send (dest_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); kmem_cache_init (&intr_entry_cache, "intr_entry", sizeof (struct intr_entry), 0, NULL, 0); for (;;) { assert_wait ((event_t) &intr_thread, FALSE); cli (); restart: queue_iterate (&intr_queue, e, struct intr_entry *, chain) if (e->interrupts) { line = e->line; dest = e->dest; e->interrupts--; sti (); 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. */ goto restart; } sti (); thread_block (thread_no_continuation); } } #else /* MACH_XEN */ boolean_t intr_entry_notify (mach_msg_header_t *msg) { panic ("not reached"); } #endif /* MACH_XEN */