diff options
-rw-r--r-- | Makefrag.am | 4 | ||||
-rw-r--r-- | device/ds_routines.c | 56 | ||||
-rw-r--r-- | device/ds_routines.h | 3 | ||||
-rw-r--r-- | device/intr.c | 309 | ||||
-rw-r--r-- | device/intr.h | 50 | ||||
-rw-r--r-- | include/device/device.defs | 20 | ||||
-rw-r--r-- | include/device/notify.defs | 36 | ||||
-rw-r--r-- | include/device/notify.h | 34 | ||||
-rw-r--r-- | kern/startup.c | 4 | ||||
-rw-r--r-- | linux/dev/arch/i386/kernel/irq.c | 130 | ||||
-rw-r--r-- | linux/dev/drivers/block/genhd.c | 4 | ||||
-rw-r--r-- | linux/src/include/asm-i386/irq.h | 2 |
12 files changed, 620 insertions, 32 deletions
diff --git a/Makefrag.am b/Makefrag.am index 9cf976a5..ea612275 100644 --- a/Makefrag.am +++ b/Makefrag.am @@ -314,6 +314,8 @@ libkernel_a_SOURCES += \ device/ds_routines.h \ device/if_ether.h \ device/if_hdr.h \ + device/intr.c \ + device/intr.h \ device/io_req.h \ device/net_io.c \ device/net_io.h \ @@ -361,6 +363,8 @@ include_device_HEADERS = \ include/device/device_types.h \ include/device/disk_status.h \ include/device/net_status.h \ + include/device/notify.defs \ + include/device/notify.h \ include/device/tape_status.h \ include/device/tty_status.h diff --git a/device/ds_routines.c b/device/ds_routines.c index fc051e8f..1cc38f98 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,48 @@ 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 (ipc_port_t master_port, int line, + int id, int flags, ipc_port_t receive_port) +{ +#ifdef MACH_XEN + return D_INVALID_OPERATION; +#else /* MACH_XEN */ + io_return_t ret; + + /* Open must be called on the master device port. */ + if (master_port != master_device_port) + return D_INVALID_OPERATION; + + /* XXX: move to arch-specific */ + if (line < 0 || line >= 16) + return D_INVALID_OPERATION; + + user_intr_t *user_intr = insert_intr_entry (line, receive_port); + if (!user_intr) + 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. + ret = install_user_intr_handler (line, flags, user_intr); + + if (ret == 0) + { + /* 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); + + /* For now netdde calls device_intr_enable once after registration. Assume + * it does so for now. When we move to IRQ acknowledgment convention we will + * change this. */ + __disable_irq (line); + } + + return ret; +#endif /* MACH_XEN */ +} + boolean_t ds_notify (mach_msg_header_t *msg) { @@ -1799,6 +1842,19 @@ device_writev_trap (mach_device_t device, dev_mode_t mode, return (result); } +kern_return_t +ds_device_intr_enable(ipc_port_t master_port, int line, char status) +{ +#ifdef MACH_XEN + return D_INVALID_OPERATION; +#else /* MACH_XEN */ + if (master_port != master_device_port) + return D_INVALID_OPERATION; + + return user_intr_enable(line, status); +#endif /* MACH_XEN */ +} + struct device_emulation_ops mach_device_emulation_ops = { (void*) mach_device_reference, diff --git a/device/ds_routines.h b/device/ds_routines.h index c0543cbc..e9f115fc 100644 --- a/device/ds_routines.h +++ b/device/ds_routines.h @@ -83,4 +83,7 @@ io_return_t ds_device_writev_trap( io_buf_vec_t *iovec, vm_size_t count); +/* XXX arch-specific */ +extern ipc_port_t intr_rcv_ports[16]; + #endif /* DS_ROUTINES_H */ diff --git a/device/intr.c b/device/intr.c new file mode 100644 index 00000000..bbbdc92d --- /dev/null +++ b/device/intr.c @@ -0,0 +1,309 @@ +/* + * 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/ds_routines.h> +#include <kern/queue.h> +#include <kern/printf.h> +#include <machine/spl.h> + +#ifndef MACH_XEN + +static boolean_t deliver_intr (int line, ipc_port_t dest_port); + +static queue_head_t intr_queue; +/* The total number of unprocessed interrupts. */ +static int tot_num_intr; + +static struct intr_entry * +search_intr (int line, ipc_port_t dest) +{ + struct intr_entry *e; + queue_iterate (&intr_queue, e, struct intr_entry *, chain) + { + if (e->dest == dest && e->line == line) + return e; + } + return NULL; +} + +static struct intr_entry * +search_intr_line (int line) +{ + struct intr_entry *e; + queue_iterate (&intr_queue, e, struct intr_entry *, chain) + { + if (e->line == line && + (e->dest != MACH_PORT_NULL + && e->dest->ip_references != 1 + && e->unacked_interrupts)) + return e; + } + return NULL; +} + +kern_return_t user_intr_enable (int line, char status) +{ + struct intr_entry *e; + kern_return_t ret = D_SUCCESS; + + spl_t s = splhigh (); + /* FIXME: Use search_intr instead once we get the delivery port from ds_device_intr_enable, and get rid of search_intr_line */ + e = search_intr_line (line); + + if (!e) + printf("didn't find user intr for interrupt %d!?\n", line); + else if (status) + { + if (!e->unacked_interrupts) + ret = D_INVALID_OPERATION; + else + e->unacked_interrupts--; + } + else + { + e->unacked_interrupts++; + if (!e->unacked_interrupts) + { + ret = D_INVALID_OPERATION; + e->unacked_interrupts--; + } + } + splx (s); + + if (ret) + return ret; + + if (status) + /* TODO: better name for generic-to-arch-specific call */ + __enable_irq (line); + else + __disable_irq (line); + return D_SUCCESS; +} + +/* This function can only be used in the interrupt handler. */ +static void +queue_intr (int line, 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 (line); + + spl_t s = splhigh (); + e->unacked_interrupts++; + e->interrupts++; + tot_num_intr++; + splx (s); + + thread_wakeup ((event_t) &intr_thread); +} + +int deliver_user_intr (int line, user_intr_t *intr) +{ + /* 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 (intr->dest + && intr->dest->ip_references == 1) + { + printf ("irq handler %d: release a dead delivery port %p entry %p\n", line, intr->dest, intr); + ipc_port_release (intr->dest); + intr->dest = MACH_PORT_NULL; + thread_wakeup ((event_t) &intr_thread); + return 0; + } + else + { + queue_intr (line, intr); + 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 (int line, ipc_port_t dest) +{ + struct intr_entry *e, *new, *ret; + int free = 0; + + new = (struct intr_entry *) 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 (line, dest); + if (e) + { + printf ("the interrupt entry for line %d and port %p has already been inserted\n", line, dest); + free = 1; + ret = NULL; + goto out; + } + printf("irq handler %d: new delivery port %p entry %p\n", line, dest, new); + ret = new; + new->line = line; + new->dest = dest; + new->interrupts = 0; + + /* For now netdde calls device_intr_enable once after registration. Assume + * it does so for now. When we move to IRQ acknowledgment convention we will + * change this. */ + new->unacked_interrupts = 1; + + queue_enter (&intr_queue, new, struct intr_entry *, chain); +out: + splx (s); + if (free) + kfree ((vm_offset_t) new, sizeof (*new)); + return ret; +} + +void +intr_thread (void) +{ + struct intr_entry *e; + int line; + ipc_port_t dest; + queue_init (&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 (&intr_queue, e, struct intr_entry *, chain) + { + if ((!e->dest || e->dest->ip_references == 1) && e->unacked_interrupts) + { + printf ("irq handler %d: release dead delivery %d unacked irqs port %p entry %p\n", e->line, e->unacked_interrupts, e->dest, 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->unacked_interrupts) + { + __enable_irq(e->line); + e->unacked_interrupts--; + } + } + } + + /* Now check for interrupts */ + while (tot_num_intr) + { + int del = 0; + + queue_iterate (&intr_queue, e, struct intr_entry *, chain) + { + /* if an entry doesn't have dest port, + * we should remove it. */ + if (e->dest == MACH_PORT_NULL) + { + clear_wait (current_thread (), 0, 0); + del = 1; + break; + } + + if (e->interrupts) + { + clear_wait (current_thread (), 0, 0); + line = e->line; + dest = e->dest; + e->interrupts--; + tot_num_intr--; + + splx (s); + deliver_intr (line, dest); + s = splhigh (); + } + } + + /* remove the entry without dest port from the queue and free it. */ + if (del) + { + assert (!queue_empty (&intr_queue)); + queue_remove (&intr_queue, e, struct intr_entry *, chain); + if (e->unacked_interrupts) + printf("irq handler %d: still %d unacked irqs in entry %p\n", e->line, e->unacked_interrupts, e); + while (e->unacked_interrupts) + { + __enable_irq(e->line); + e->unacked_interrupts--; + } + printf("irq handler %d: removed entry %p\n", e->line, e); + splx (s); + kfree ((vm_offset_t) e, sizeof (*e)); + s = splhigh (); + } + } + splx (s); + thread_block (NULL); + } +} + +static boolean_t +deliver_intr (int line, ipc_port_t dest_port) +{ + ipc_kmsg_t kmsg; + device_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 = (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->line = line; + + ipc_port_copy_send (dest_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..df282c05 --- /dev/null +++ b/device/intr.h @@ -0,0 +1,50 @@ +/* + * 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__ + +#include <device/device_types.h> +#include <kern/queue.h> +#include <device/notify.h> + +typedef struct intr_entry +{ + queue_chain_t chain; + ipc_port_t dest; + int line; + int interrupts; /* The number of interrupts occur since last run of intr_thread. */ + int unacked_interrupts; /* Number of times irqs were disabled for this */ +} user_intr_t; + +#define DEVICE_NOTIFY_MSGH_SEQNO 0 + +int install_user_intr_handler (unsigned int line, + unsigned long flags, + user_intr_t *user_intr); + +/* Returns 0 if action should be removed */ +int deliver_user_intr (int line, user_intr_t *intr); + +user_intr_t *insert_intr_entry (int line, ipc_port_t dest); + +/* TODO: should rather take delivery port */ +kern_return_t user_intr_enable (int line, char status); + +void intr_thread (void); + +void __disable_irq(unsigned int); +void __enable_irq(unsigned int); + +#endif diff --git a/include/device/device.defs b/include/device/device.defs index 409146f5..dca1be4e 100644 --- a/include/device/device.defs +++ b/include/device/device.defs @@ -142,3 +142,23 @@ routine device_set_filter( in filter : filter_array_t ); +routine device_intr_register( + master_port : mach_port_t; + in line : int; + in id : int; + in flags : int; + in receive_port : mach_port_send_t + ); + +/* + * enable/disable the specified line. + */ +/* XXX: Naming a function taht can disable something "xxx_enable" is confusing. */ +/* Is the disable part actually used at all? AIUI, the kernel IRQ handler +should always disable the line; and the userspace driver only has to +reenable it, after acknowledging and handling the interrupt... +*/ +routine device_intr_enable( + master_port : mach_port_t; + line : int; + status : char); diff --git a/include/device/notify.defs b/include/device/notify.defs new file mode 100644 index 00000000..ea374d26 --- /dev/null +++ b/include/device/notify.defs @@ -0,0 +1,36 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * 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. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ + +subsystem notify 100; + +#include <mach/std_types.defs> + +serverprefix do_; +serverdemux device_intr_notify_server; + +simpleroutine device_intr_notify( + notify : notify_port_t; + name : int); diff --git a/include/device/notify.h b/include/device/notify.h new file mode 100644 index 00000000..b6907b03 --- /dev/null +++ b/include/device/notify.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010 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. + */ + +/* + * Device notification definitions. + */ + +#ifndef _MACH_DEVICE_NOTIFY_H_ +#define _MACH_DEVICE_NOTIFY_H_ + +#include <mach/port.h> +#include <mach/message.h> + +typedef struct +{ + mach_msg_header_t intr_header; + mach_msg_type_t intr_type; + int line; +} device_intr_notification_t; + +#define DEVICE_INTR_NOTIFY 100 + +#endif /* _MACH_DEVICE_NOTIFY_H_ */ diff --git a/kern/startup.c b/kern/startup.c index 56e05b3f..1f873192 100644 --- a/kern/startup.c +++ b/kern/startup.c @@ -63,6 +63,7 @@ #include <machine/model_dep.h> #include <mach/version.h> #include <device/device_init.h> +#include <device/intr.h> #if MACH_KDB #include <device/cons.h> @@ -228,6 +229,9 @@ void start_kernel_threads(void) (void) kernel_thread(kernel_task, reaper_thread, (char *) 0); (void) kernel_thread(kernel_task, swapin_thread, (char *) 0); (void) kernel_thread(kernel_task, sched_thread, (char *) 0); +#ifndef MACH_XEN + (void) kernel_thread(kernel_task, intr_thread, (char *)0); +#endif /* MACH_XEN */ #if NCPUS > 1 /* diff --git a/linux/dev/arch/i386/kernel/irq.c b/linux/dev/arch/i386/kernel/irq.c index 18448638..bc752013 100644 --- a/linux/dev/arch/i386/kernel/irq.c +++ b/linux/dev/arch/i386/kernel/irq.c @@ -50,6 +50,8 @@ #include <linux/dev/glue/glue.h> #include <machine/machspl.h> +#include <device/intr.h> + #if 0 /* XXX: This is the way it's done in linux 2.2. GNU Mach currently uses intr_count. It should be made using local_{bh/irq}_count instead (through hardirq_enter/exit) for SMP support. */ unsigned int local_bh_count[NR_CPUS]; @@ -76,6 +78,7 @@ struct linux_action void *dev_id; struct linux_action *next; unsigned long flags; + user_intr_t *user_intr; }; static struct linux_action *irq_action[16] = @@ -87,35 +90,6 @@ static struct linux_action *irq_action[16] = }; /* - * Generic interrupt handler for Linux devices. - * Set up a fake `struct pt_regs' then call the real handler. - */ -static void -linux_intr (int irq) -{ - struct pt_regs regs; - struct linux_action *action = *(irq_action + irq); - unsigned long flags; - - kstat.interrupts[irq]++; - intr_count++; - - save_flags (flags); - if (action && (action->flags & SA_INTERRUPT)) - cli (); - - while (action) - { - action->handler (irq, action->dev_id, ®s); - action = action->next; - } - - restore_flags (flags); - - intr_count--; -} - -/* * Mask an IRQ. */ static inline void @@ -158,6 +132,58 @@ unmask_irq (unsigned int irq_nr) } } +/* + * Generic interrupt handler for Linux devices. + * Set up a fake `struct pt_regs' then call the real handler. + */ +static void +linux_intr (int irq) +{ + struct pt_regs regs; + struct linux_action *action = *(irq_action + irq); + struct linux_action **prev = &irq_action[irq]; + unsigned long flags; + + kstat.interrupts[irq]++; + intr_count++; + + save_flags (flags); + if (action && (action->flags & SA_INTERRUPT)) + cli (); + + while (action) + { + // TODO I might need to check whether the interrupt belongs to + // the current device. But I don't do it for now. + if (action->user_intr) + { + if (!deliver_user_intr(irq, action->user_intr)) + { + *prev = action->next; + linux_kfree(action); + action = *prev; + continue; + } + } + else if (action->handler) + action->handler (irq, action->dev_id, ®s); + prev = &action->next; + action = action->next; + } + + if (!irq_action[irq]) + { + /* No handler any more, disable interrupt */ + mask_irq (irq); + ivect[irq] = intnull; + iunit[irq] = irq; + } + + restore_flags (flags); + + intr_count--; +} + /* Count how many subsystems requested to disable each IRQ */ static unsigned ndisabled_irq[NR_IRQS]; @@ -273,6 +299,51 @@ setup_x86_irq (int irq, struct linux_action *new) return 0; } +int +install_user_intr_handler (unsigned int irq, unsigned long flags, + user_intr_t *user_intr) +{ + struct linux_action *action; + struct linux_action *old; + int retval; + + assert (irq < 16); + + /* Test whether the irq handler has been set */ + // TODO I need to protect the array when iterating it. + old = irq_action[irq]; + while (old) + { + if (old->user_intr && old->user_intr->dest == user_intr->dest) + { + printk ("The interrupt handler has already been installed on line %d", irq); + return linux_to_mach_error (-EAGAIN); + } + old = old->next; + } + + /* + * Hmm... Should I use `kalloc()' ? + * By OKUJI Yoshinori. + */ + action = (struct linux_action *) + linux_kmalloc (sizeof (struct linux_action), GFP_KERNEL); + if (action == NULL) + return linux_to_mach_error (-ENOMEM); + + action->handler = NULL; + action->next = NULL; + action->dev_id = NULL; + action->flags = flags; + action->user_intr = user_intr; + + retval = setup_x86_irq (irq, action); + if (retval) + linux_kfree (action); + + return linux_to_mach_error (retval); +} + /* * Attach a handler to an IRQ. */ @@ -301,6 +372,7 @@ request_irq (unsigned int irq, void (*handler) (int, void *, struct pt_regs *), action->next = NULL; action->dev_id = dev_id; action->flags = flags; + action->user_intr = NULL; retval = setup_x86_irq (irq, action); if (retval) diff --git a/linux/dev/drivers/block/genhd.c b/linux/dev/drivers/block/genhd.c index 23c1e120..44ba6533 100644 --- a/linux/dev/drivers/block/genhd.c +++ b/linux/dev/drivers/block/genhd.c @@ -1057,7 +1057,9 @@ void device_setup(void) scsi_dev_init(); #endif #ifdef CONFIG_INET - net_dev_init(); + extern char *kernel_cmdline; + if (!strstr(kernel_cmdline, " nonetdev")) + net_dev_init(); #endif #ifndef MACH console_map_init(); diff --git a/linux/src/include/asm-i386/irq.h b/linux/src/include/asm-i386/irq.h index d7d1e3c5..c75744a5 100644 --- a/linux/src/include/asm-i386/irq.h +++ b/linux/src/include/asm-i386/irq.h @@ -16,8 +16,6 @@ #define TIMER_IRQ 0 -extern void __disable_irq(unsigned int); -extern void __enable_irq(unsigned int); extern void disable_irq(unsigned int); extern void enable_irq(unsigned int); |