/*
* 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 */