From e85ae9d7befbfe7a79235e68fb0423d40b6baf6c Mon Sep 17 00:00:00 2001 From: Damien Zammit Date: Thu, 9 Jul 2020 23:11:44 +0200 Subject: Fix RPC interface --- device/dev_hdr.h | 9 + device/ds_routines.c | 80 ++-- device/ds_routines.h | 3 - device/intr.c | 183 ++++----- device/intr.h | 52 ++- doc/mach.texi | 22 + i386/Makefrag.am | 2 + i386/i386/irq.c | 66 +++ i386/i386/irq.h | 27 ++ i386/i386/pic.c | 72 ++++ i386/i386/pic.h | 2 + i386/i386at/conf.c | 8 + include/device/device.defs | 22 +- include/device/notify.defs | 2 +- include/device/notify.h | 2 +- linux/dev/arch/i386/kernel/irq.c | 90 +---- linux/dev/arch/i386/kernel/irq.c.orig | 732 ++++++++++++++++++++++++++++++++++ 17 files changed, 1107 insertions(+), 267 deletions(-) create mode 100644 i386/i386/irq.c create mode 100644 i386/i386/irq.h create mode 100644 linux/dev/arch/i386/kernel/irq.c.orig 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 1cc38f98..af9c7617 100644 --- a/device/ds_routines.c +++ b/device/ds_routines.c @@ -322,44 +322,55 @@ ds_device_map (device_t dev, vm_prot_t prot, vm_offset_t offset, /* 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) +ds_device_intr_register (device_t dev, int id, + int flags, ipc_port_t receive_port) { -#ifdef MACH_XEN - return D_INVALID_OPERATION; -#else /* MACH_XEN */ - io_return_t ret; + kern_return_t err; + mach_device_t mdev = dev->emul_data; - /* Open must be called on the master device port. */ - if (master_port != master_device_port) + /* 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; - /* XXX: move to arch-specific */ - if (line < 0 || line >= 16) + /* 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 *user_intr = insert_intr_entry (line, receive_port); - if (!user_intr) + 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. - 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 */ + 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 @@ -1842,19 +1853,6 @@ 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 e9f115fc..c0543cbc 100644 --- a/device/ds_routines.h +++ b/device/ds_routines.h @@ -83,7 +83,4 @@ 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 index bbbdc92d..56f0c32c 100644 --- a/device/intr.c +++ b/device/intr.c @@ -13,121 +13,98 @@ */ #include -#include -#include +#include +#include +#include #include #include +#include +#include #ifndef MACH_XEN -static boolean_t deliver_intr (int line, ipc_port_t dest_port); +queue_head_t main_intr_queue; +static boolean_t deliver_intr (int id, ipc_port_t dst_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) +static user_intr_t * +search_intr (struct irqdev *dev, ipc_port_t dst_port) { - struct intr_entry *e; - queue_iterate (&intr_queue, e, struct intr_entry *, chain) + user_intr_t *e; + queue_iterate (dev->intr_queue, e, user_intr_t *, chain) { - if (e->dest == dest && e->line == line) + if (e->dst_port == dst_port) return e; } return NULL; } -static struct intr_entry * -search_intr_line (int line) +kern_return_t +irq_acknowledge (ipc_port_t receive_port) { - 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; + user_intr_t *e; + kern_return_t ret; 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); + e = search_intr (&irqtab, receive_port); 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--; - } + printf("didn't find user intr for interrupt !?\n"); else - { - e->unacked_interrupts++; - if (!e->unacked_interrupts) { - ret = D_INVALID_OPERATION; - e->unacked_interrupts--; + if (!e->n_unacked) + ret = D_INVALID_OPERATION; + else + e->n_unacked--; } - } splx (s); if (ret) return ret; - if (status) - /* TODO: better name for generic-to-arch-specific call */ - __enable_irq (line); - else - __disable_irq (line); + 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 (int line, user_intr_t *e) +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 (line); + __disable_irq (dev->irq[id]); spl_t s = splhigh (); - e->unacked_interrupts++; + e->n_unacked++; e->interrupts++; - tot_num_intr++; + dev->tot_num_intr++; splx (s); thread_wakeup ((event_t) &intr_thread); } -int deliver_user_intr (int line, user_intr_t *intr) +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 (intr->dest - && intr->dest->ip_references == 1) + if (e->dst_port + && e->dst_port->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; + 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 (line, intr); + queue_intr (dev, id, e); return 1; } } @@ -136,37 +113,32 @@ int deliver_user_intr (int line, user_intr_t *intr) * 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) +insert_intr_entry (struct irqdev *dev, int id, ipc_port_t dst_port) { - struct intr_entry *e, *new, *ret; + user_intr_t *e, *new, *ret; int free = 0; - new = (struct intr_entry *) kalloc (sizeof (*new)); + 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 (line, dest); + e = search_intr (dev, dst_port); if (e) { - printf ("the interrupt entry for line %d and port %p has already been inserted\n", line, dest); + 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", line, dest, new); + printf("irq handler [%d]: new delivery port %p entry %p\n", id, dst_port, new); ret = new; - new->line = line; - new->dest = dest; + new->id = id; + new->dst_port = dst_port; 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); + queue_enter (dev->intr_queue, new, user_intr_t *, chain); out: splx (s); if (free) @@ -177,10 +149,10 @@ out: void intr_thread (void) { - struct intr_entry *e; - int line; - ipc_port_t dest; - queue_init (&intr_queue); + user_intr_t *e; + int id; + ipc_port_t dst_port; + queue_init (&main_intr_queue); for (;;) { @@ -190,11 +162,11 @@ intr_thread (void) spl_t s = splhigh (); /* Check for aborted processes */ - queue_iterate (&intr_queue, e, struct intr_entry *, chain) + queue_iterate (&main_intr_queue, e, user_intr_t *, chain) { - if ((!e->dest || e->dest->ip_references == 1) && e->unacked_interrupts) + 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->line, e->unacked_interrupts, e->dest, e); + 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 @@ -202,24 +174,24 @@ intr_thread (void) * 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) + while (e->n_unacked) { - __enable_irq(e->line); - e->unacked_interrupts--; + __enable_irq (irqtab.irq[e->id]); + e->n_unacked--; } } } /* Now check for interrupts */ - while (tot_num_intr) + while (irqtab.tot_num_intr) { int del = 0; - queue_iterate (&intr_queue, e, struct intr_entry *, chain) + queue_iterate (&main_intr_queue, e, user_intr_t *, chain) { /* if an entry doesn't have dest port, * we should remove it. */ - if (e->dest == MACH_PORT_NULL) + if (e->dst_port == MACH_PORT_NULL) { clear_wait (current_thread (), 0, 0); del = 1; @@ -229,13 +201,13 @@ intr_thread (void) if (e->interrupts) { clear_wait (current_thread (), 0, 0); - line = e->line; - dest = e->dest; + id = e->id; + dst_port = e->dst_port; e->interrupts--; - tot_num_intr--; + irqtab.tot_num_intr--; splx (s); - deliver_intr (line, dest); + deliver_intr (id, dst_port); s = splhigh (); } } @@ -243,16 +215,16 @@ intr_thread (void) /* 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) + 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(e->line); - e->unacked_interrupts--; + __enable_irq (irqtab.irq[e->id]); + e->n_unacked--; } - printf("irq handler %d: removed entry %p\n", e->line, e); + printf("irq handler [%d]: removed entry %p\n", e->id, e); splx (s); kfree ((vm_offset_t) e, sizeof (*e)); s = splhigh (); @@ -264,11 +236,11 @@ intr_thread (void) } static boolean_t -deliver_intr (int line, ipc_port_t dest_port) +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) dest_port; + mach_port_t dest = (mach_port_t) dst_port; if (dest == MACH_PORT_NULL) return FALSE; @@ -299,11 +271,12 @@ deliver_intr (int line, ipc_port_t dest_port) t->msgt_unused = 0; n->intr_header.msgh_remote_port = dest; - n->line = line; + n->id = id; - ipc_port_copy_send (dest_port); + 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 index df282c05..cd3e0bce 100644 --- a/device/intr.h +++ b/device/intr.h @@ -15,36 +15,48 @@ #ifndef __INTR_H__ #define __INTR_H__ -#include +#ifndef MACH_XEN + +#include +#include #include -#include +#include +#include + +#define DEVICE_NOTIFY_MSGH_SEQNO 0 + +#include -typedef struct intr_entry -{ +struct irqdev; +#include + +typedef struct { 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 */ + 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; -#define DEVICE_NOTIFY_MSGH_SEQNO 0 - -int install_user_intr_handler (unsigned int line, - unsigned long flags, - user_intr_t *user_intr); +struct irqdev { + char *name; + void (*irqdev_ack)(struct irqdev *dev, int id); -/* Returns 0 if action should be removed */ -int deliver_user_intr (int line, user_intr_t *intr); + queue_head_t *intr_queue; + int tot_num_intr; /* Total number of unprocessed interrupts */ -user_intr_t *insert_intr_entry (int line, ipc_port_t dest); + /* Machine dependent */ + irq_t irq[NINTR]; +}; -/* TODO: should rather take delivery port */ -kern_return_t user_intr_enable (int line, char status); +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); -void __disable_irq(unsigned int); -void __enable_irq(unsigned int); +#endif /* MACH_XEN */ #endif diff --git a/doc/mach.texi b/doc/mach.texi index 91ec96ee..11b0a066 100644 --- a/doc/mach.texi +++ b/doc/mach.texi @@ -286,6 +286,7 @@ Device Interface * Device Map:: Mapping devices into virtual memory. * Device Status:: Querying and manipulating a device. * Device Filter:: Filtering packets arriving on a device. +* Device Interrupt:: Getting hardware interrupt notifications. Kernel Debugger @@ -6305,6 +6306,7 @@ All constants and functions in this chapter are defined in * Device Map:: Mapping devices into virtual memory. * Device Status:: Querying and manipulating a device. * Device Filter:: Filtering packets arriving on a device. +* Device Interrupt:: Getting hardware interrupt notifications. @end menu @@ -6699,6 +6701,26 @@ a device port or the device is dead or not completely open. @end deftypefun +@node Device Interrupt +@section Device Interrupt + +@deftypefun kern_return_t device_intr_register (@w{device_t @var{device}}, @w{int @var{id}}, @w{int @var{flags}}, @w{mach_port_t @var{receive_port}}) +The function @code{device_intr_register} registers for receiving hardware +interrupt events through @var{device_intr_notify} notifications. The hardware +interrupt identifier is specified by @var{id}. @var{flags} must be set to 0. The +notifications will be sent on the @var{receive_port} send right. +@code{device_intr_register} is only available on the dedicated @code{irq} device. +@end deftypefun + +@deftypefun kern_return_t device_intr_ack (@w{device_t @var{device}}, @w{mach_port_t @var{receive_port}}) +On a hardware interrupt, the kernel disables the interrupt line before sending +notifications. To prevent from interrupt losses, the interrupt is kept disabled +until @code{device_intr_ack} is called to acknowledge the interrupt. +@var{receive_port} is the send right on which the interrupt notification was +received. +@end deftypefun + + @node Kernel Debugger @chapter Kernel Debugger diff --git a/i386/Makefrag.am b/i386/Makefrag.am index f38c0785..59571416 100644 --- a/i386/Makefrag.am +++ b/i386/Makefrag.am @@ -102,6 +102,8 @@ libkernel_a_SOURCES += \ i386/i386/io_perm.c \ i386/i386/io_perm.h \ i386/i386/ipl.h \ + i386/i386/irq.c \ + i386/i386/irq.h \ i386/i386/ktss.c \ i386/i386/ktss.h \ i386/i386/kttd_interface.c \ diff --git a/i386/i386/irq.c b/i386/i386/irq.c new file mode 100644 index 00000000..c65d2ea2 --- /dev/null +++ b/i386/i386/irq.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 1995 Shantanu Goel + * Copyright (C) 2020 Free Software Foundation, Inc + * + * 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, 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, write to the Free Software + * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +extern queue_head_t main_intr_queue; + +static void +irq_eoi (struct irqdev *dev, int id) +{ + /* TODO EOI(dev->irq[id]) */ +} + +static unsigned int ndisabled_irq[NINTR]; + +void +__disable_irq (irq_t irq_nr) +{ + assert (irq_nr < NINTR); + + spl_t s = splhigh(); + ndisabled_irq[irq_nr]++; + assert (ndisabled_irq[irq_nr] > 0); + if (ndisabled_irq[irq_nr] == 1) + mask_irq (irq_nr); + splx(s); +} + +void +__enable_irq (irq_t irq_nr) +{ + assert (irq_nr < NINTR); + + spl_t s = splhigh(); + assert (ndisabled_irq[irq_nr] > 0); + ndisabled_irq[irq_nr]--; + if (ndisabled_irq[irq_nr] == 0) + unmask_irq (irq_nr); + splx(s); +} + +struct irqdev irqtab = { + "irq", irq_eoi, &main_intr_queue, 0, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, +}; + diff --git a/i386/i386/irq.h b/i386/i386/irq.h new file mode 100644 index 00000000..d48a8e92 --- /dev/null +++ b/i386/i386/irq.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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 FOUNDATION ALLOWS 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 _I386_IRQ_H +#define _I386_IRQ_H + +#include + +typedef unsigned int irq_t; + +void __enable_irq (irq_t irq); +void __disable_irq (irq_t irq); + +extern struct irqdev irqtab; + +#endif diff --git a/i386/i386/pic.c b/i386/i386/pic.c index 0feebc6f..62ed9ed1 100644 --- a/i386/i386/pic.c +++ b/i386/i386/pic.c @@ -49,6 +49,24 @@ OTHER TORTIOUS ACTION, ARISING OUR OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/* + * Copyright (C) 1995 Shantanu Goel. + * + * 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, 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, write to the Free Software + * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + #include #include #include @@ -185,3 +203,57 @@ intnull(int unit_dev) } } + +/* + * Mask a PIC IRQ. + */ +inline void +mask_irq (unsigned int irq_nr) +{ + int new_pic_mask = curr_pic_mask | 1 << irq_nr; + + if (curr_pic_mask != new_pic_mask) + { + curr_pic_mask = new_pic_mask; + if (irq_nr < 8) + { + outb (PIC_MASTER_OCW, curr_pic_mask & 0xff); + } + else + { + outb (PIC_SLAVE_OCW, curr_pic_mask >> 8); + } + } +} + +/* + * Unmask a PIC IRQ. + */ +inline void +unmask_irq (unsigned int irq_nr) +{ + int mask; + int new_pic_mask; + + mask = 1 << irq_nr; + if (irq_nr >= 8) + { + mask |= 1 << 2; + } + + new_pic_mask = curr_pic_mask & ~mask; + + if (curr_pic_mask != new_pic_mask) + { + curr_pic_mask = new_pic_mask; + if (irq_nr < 8) + { + outb (PIC_MASTER_OCW, curr_pic_mask & 0xff); + } + else + { + outb (PIC_SLAVE_OCW, curr_pic_mask >> 8); + } + } +} + diff --git a/i386/i386/pic.h b/i386/i386/pic.h index f492de5e..6434bf08 100644 --- a/i386/i386/pic.h +++ b/i386/i386/pic.h @@ -180,6 +180,8 @@ WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. extern void picinit (void); extern int curr_pic_mask; extern void intnull(int unit); +extern inline void mask_irq (unsigned int irq_nr); +extern inline void unmask_irq (unsigned int irq_nr); #endif /* __ASSEMBLER__ */ #endif /* _I386_PIC_H_ */ diff --git a/i386/i386at/conf.c b/i386/i386at/conf.c index fe7c7c09..ca5d0dfb 100644 --- a/i386/i386at/conf.c +++ b/i386/i386at/conf.c @@ -68,6 +68,9 @@ #define hypcnname "hyp" #endif /* MACH_HYP */ +#include +#define irqname "irq" + /* * List of devices - console must be at slot 0 */ @@ -149,6 +152,11 @@ struct dev_ops dev_name_list[] = nodev }, #endif /* MACH_HYP */ + { irqname, nulldev_open, nulldev_close, nulldev_read, + nulldev_write,nulldev_getstat,nulldev_setstat, nomap, + nodev, nulldev, nulldev_portdeath,0, + nodev }, + }; int dev_name_count = sizeof(dev_name_list)/sizeof(dev_name_list[0]); diff --git a/include/device/device.defs b/include/device/device.defs index dca1be4e..ec4b5bf8 100644 --- a/include/device/device.defs +++ b/include/device/device.defs @@ -143,22 +143,20 @@ routine device_set_filter( ); routine device_intr_register( - master_port : mach_port_t; - in line : int; + device : device_t; in id : int; in flags : int; in receive_port : mach_port_send_t ); /* - * enable/disable the specified line. + * Acknowledge the specified interrupt notification. */ -/* 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); +/* + * When an IRQ happens and an intr notification is thus sent, the IRQ line + * is kept disabled until the notification is acknowledged with this RPC + */ +routine device_intr_ack( + device : device_t; + in receive_port : mach_port_send_t); + diff --git a/include/device/notify.defs b/include/device/notify.defs index ea374d26..7919b339 100644 --- a/include/device/notify.defs +++ b/include/device/notify.defs @@ -33,4 +33,4 @@ serverdemux device_intr_notify_server; simpleroutine device_intr_notify( notify : notify_port_t; - name : int); + id : int); diff --git a/include/device/notify.h b/include/device/notify.h index b6907b03..addf9114 100644 --- a/include/device/notify.h +++ b/include/device/notify.h @@ -26,7 +26,7 @@ typedef struct { mach_msg_header_t intr_header; mach_msg_type_t intr_type; - int line; + int id; } device_intr_notification_t; #define DEVICE_INTR_NOTIFY 100 diff --git a/linux/dev/arch/i386/kernel/irq.c b/linux/dev/arch/i386/kernel/irq.c index bc752013..1e911f33 100644 --- a/linux/dev/arch/i386/kernel/irq.c +++ b/linux/dev/arch/i386/kernel/irq.c @@ -89,49 +89,6 @@ static struct linux_action *irq_action[16] = NULL, NULL, NULL, NULL }; -/* - * Mask an IRQ. - */ -static inline void -mask_irq (unsigned int irq_nr) -{ - int new_pic_mask = curr_pic_mask | 1 << irq_nr; - - if (curr_pic_mask != new_pic_mask) - { - curr_pic_mask = new_pic_mask; - if (irq_nr < 8) - outb (curr_pic_mask & 0xff, PIC_MASTER_OCW); - else - outb (curr_pic_mask >> 8, PIC_SLAVE_OCW); - } -} - -/* - * Unmask an IRQ. - */ -static inline void -unmask_irq (unsigned int irq_nr) -{ - int mask; - int new_pic_mask; - - mask = 1 << irq_nr; - if (irq_nr >= 8) - mask |= 1 << 2; - - new_pic_mask = curr_pic_mask & ~mask; - - if (curr_pic_mask != new_pic_mask) - { - curr_pic_mask = new_pic_mask; - if (irq_nr < 8) - outb (curr_pic_mask & 0xff, PIC_MASTER_OCW); - else - outb (curr_pic_mask >> 8, PIC_SLAVE_OCW); - } -} - /* * Generic interrupt handler for Linux devices. * Set up a fake `struct pt_regs' then call the real handler. @@ -157,7 +114,7 @@ linux_intr (int irq) // the current device. But I don't do it for now. if (action->user_intr) { - if (!deliver_user_intr(irq, action->user_intr)) + if (!deliver_user_intr(&irqtab, irq, action->user_intr)) { *prev = action->next; linux_kfree(action); @@ -184,43 +141,6 @@ linux_intr (int irq) intr_count--; } -/* Count how many subsystems requested to disable each IRQ */ -static unsigned ndisabled_irq[NR_IRQS]; - -/* These disable/enable IRQs for real after counting how many subsystems - * requested that */ -void -__disable_irq (unsigned int irq_nr) -{ - unsigned long flags; - - assert (irq_nr < NR_IRQS); - - save_flags (flags); - cli (); - ndisabled_irq[irq_nr]++; - assert (ndisabled_irq[irq_nr] > 0); - if (ndisabled_irq[irq_nr] == 1) - mask_irq (irq_nr); - restore_flags (flags); -} - -void -__enable_irq (unsigned int irq_nr) -{ - unsigned long flags; - - assert (irq_nr < NR_IRQS); - - save_flags (flags); - cli (); - assert (ndisabled_irq[irq_nr] > 0); - ndisabled_irq[irq_nr]--; - if (ndisabled_irq[irq_nr] == 0) - unmask_irq (irq_nr); - restore_flags (flags); -} - /* IRQ mask according to Linux drivers */ static unsigned linux_pic_mask; @@ -300,13 +220,15 @@ setup_x86_irq (int irq, struct linux_action *new) } int -install_user_intr_handler (unsigned int irq, unsigned long flags, +install_user_intr_handler (struct irqdev *dev, int id, unsigned long flags, user_intr_t *user_intr) { struct linux_action *action; struct linux_action *old; int retval; + unsigned int irq = dev->irq[id]; + assert (irq < 16); /* Test whether the irq handler has been set */ @@ -314,7 +236,7 @@ install_user_intr_handler (unsigned int irq, unsigned long flags, old = irq_action[irq]; while (old) { - if (old->user_intr && old->user_intr->dest == user_intr->dest) + if (old->user_intr && old->user_intr->dst_port == user_intr->dst_port) { printk ("The interrupt handler has already been installed on line %d", irq); return linux_to_mach_error (-EAGAIN); @@ -334,7 +256,7 @@ install_user_intr_handler (unsigned int irq, unsigned long flags, action->handler = NULL; action->next = NULL; action->dev_id = NULL; - action->flags = flags; + action->flags = SA_SHIRQ; action->user_intr = user_intr; retval = setup_x86_irq (irq, action); diff --git a/linux/dev/arch/i386/kernel/irq.c.orig b/linux/dev/arch/i386/kernel/irq.c.orig new file mode 100644 index 00000000..75f8f812 --- /dev/null +++ b/linux/dev/arch/i386/kernel/irq.c.orig @@ -0,0 +1,732 @@ +/* + * Linux IRQ management. + * Copyright (C) 1995 Shantanu Goel. + * + * 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, 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, write to the Free Software + * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * linux/arch/i386/kernel/irq.c + * + * Copyright (C) 1992 Linus Torvalds + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define MACH_INCLUDE +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#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]; +unsigned int local_irq_count[NR_CPUS]; +#endif + +/* + * XXX Move this into more suitable place... + * Set if the machine has an EISA bus. + */ +int EISA_bus = 0; + +/* + * Flag indicating an interrupt is being handled. + */ +unsigned int intr_count = 0; + +/* + * List of Linux interrupt handlers. + */ +struct linux_action +{ + void (*handler) (int, void *, struct pt_regs *); + void *dev_id; + struct linux_action *next; + unsigned long flags; +}; + +static struct linux_action *irq_action[16] = +{ + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL +}; + +/* + * 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 +mask_irq (unsigned int irq_nr) +{ + int new_pic_mask = curr_pic_mask | 1 << irq_nr; + + if (curr_pic_mask != new_pic_mask) + { + curr_pic_mask = new_pic_mask; + if (irq_nr < 8) + outb (curr_pic_mask & 0xff, PIC_MASTER_OCW); + else + outb (curr_pic_mask >> 8, PIC_SLAVE_OCW); + } +} + +/* + * Unmask an IRQ. + */ +static inline void +unmask_irq (unsigned int irq_nr) +{ + int mask; + int new_pic_mask; + + mask = 1 << irq_nr; + if (irq_nr >= 8) + mask |= 1 << 2; + + new_pic_mask = curr_pic_mask & ~mask; + + if (curr_pic_mask != new_pic_mask) + { + curr_pic_mask = new_pic_mask; + if (irq_nr < 8) + outb (curr_pic_mask & 0xff, PIC_MASTER_OCW); + else + outb (curr_pic_mask >> 8, PIC_SLAVE_OCW); + } +} + +void +disable_irq (unsigned int irq_nr) +{ + unsigned long flags; + + assert (irq_nr < NR_IRQS); + + save_flags (flags); + cli (); + mask_irq (irq_nr); + restore_flags (flags); +} + +void +enable_irq (unsigned int irq_nr) +{ + unsigned long flags; + + assert (irq_nr < NR_IRQS); + + save_flags (flags); + cli (); + unmask_irq (irq_nr); + restore_flags (flags); +} + +static int +setup_x86_irq (int irq, struct linux_action *new) +{ + int shared = 0; + struct linux_action *old, **p; + unsigned long flags; + + p = irq_action + irq; + if ((old = *p) != NULL) + { + /* Can't share interrupts unless both agree to */ + if (!(old->flags & new->flags & SA_SHIRQ)) + return (-EBUSY); + + /* Can't share interrupts unless both are same type */ + if ((old->flags ^ new->flags) & SA_INTERRUPT) + return (-EBUSY); + + /* add new interrupt at end of irq queue */ + do + { + p = &old->next; + old = *p; + } + while (old); + shared = 1; + } + + save_flags (flags); + cli (); + *p = new; + + if (!shared) + { + ivect[irq] = linux_intr; + iunit[irq] = irq; + unmask_irq (irq); + } + restore_flags (flags); + return 0; +} + +/* + * Attach a handler to an IRQ. + */ +int +request_irq (unsigned int irq, void (*handler) (int, void *, struct pt_regs *), + unsigned long flags, const char *device, void *dev_id) +{ + struct linux_action *action; + int retval; + + assert (irq < 16); + + if (!handler) + return -EINVAL; + + /* + * Hmm... Should I use `kalloc()' ? + * By OKUJI Yoshinori. + */ + action = (struct linux_action *) + linux_kmalloc (sizeof (struct linux_action), GFP_KERNEL); + if (action == NULL) + return -ENOMEM; + + action->handler = handler; + action->next = NULL; + action->dev_id = dev_id; + action->flags = flags; + + retval = setup_x86_irq (irq, action); + if (retval) + linux_kfree (action); + + return retval; +} + +/* + * Deallocate an irq. + */ +void +free_irq (unsigned int irq, void *dev_id) +{ + struct linux_action *action, **p; + unsigned long flags; + + if (irq > 15) + panic ("free_irq: bad irq number"); + + for (p = irq_action + irq; (action = *p) != NULL; p = &action->next) + { + if (action->dev_id != dev_id) + continue; + + save_flags (flags); + cli (); + *p = action->next; + if (!irq_action[irq]) + { + mask_irq (irq); + ivect[irq] = intnull; + iunit[irq] = irq; + } + restore_flags (flags); + linux_kfree (action); + return; + } + + panic ("free_irq: bad irq number"); +} + +/* + * Set for an irq probe. + */ +unsigned long +probe_irq_on (void) +{ + unsigned i, irqs = 0; + unsigned long delay; + + assert (curr_ipl == 0); + + /* + * Allocate all available IRQs. + */ + for (i = 15; i > 0; i--) + { + if (!irq_action[i] && ivect[i] == intnull) + { + enable_irq (i); + irqs |= 1 << i; + } + } + + /* + * Wait for spurious interrupts to mask themselves out. + */ + for (delay = jiffies + HZ / 10; delay > jiffies;) + ; + + return (irqs & ~curr_pic_mask); +} + +/* + * Return the result of an irq probe. + */ +int +probe_irq_off (unsigned long irqs) +{ + unsigned int i; + + assert (curr_ipl == 0); + + irqs &= curr_pic_mask; + + /* + * Disable unnecessary IRQs. + */ + for (i = 15; i > 0; i--) + { + if (!irq_action[i] && ivect[i] == intnull) + { + disable_irq (i); + } + } + + /* + * Return IRQ number. + */ + if (!irqs) + return 0; + i = ffz (~irqs); + if (irqs != (irqs & (1 << i))) + i = -i; + return i; +} + +/* + * Reserve IRQs used by Mach drivers. + * Must be called before Linux IRQ detection, after Mach IRQ detection. + */ + +static void reserved_mach_handler (int line, void *cookie, struct pt_regs *regs) +{ + /* These interrupts are actually handled in Mach. */ + assert (! "reached"); +} + +static const struct linux_action reserved_mach = + { + reserved_mach_handler, NULL, NULL, 0 + }; + +static void +reserve_mach_irqs (void) +{ + unsigned int i; + + for (i = 0; i < 16; i++) + { + if (ivect[i] != intnull) + /* This dummy action does not specify SA_SHIRQ, so + setup_x86_irq will not try to add a handler to this + slot. Therefore, the cast is safe. */ + irq_action[i] = (struct linux_action *) &reserved_mach; + } +} + +#ifdef __SMP__ +unsigned char global_irq_holder = NO_PROC_ID; +unsigned volatile int global_irq_lock; +atomic_t global_irq_count; + +atomic_t global_bh_count; +atomic_t global_bh_lock; + +/* + * "global_cli()" is a special case, in that it can hold the + * interrupts disabled for a longish time, and also because + * we may be doing TLB invalidates when holding the global + * IRQ lock for historical reasons. Thus we may need to check + * SMP invalidate events specially by hand here (but not in + * any normal spinlocks) + */ +#if 0 +/* XXX: check how Mach handles this */ +static inline void check_smp_invalidate(int cpu) +{ + if (test_bit(cpu, &smp_invalidate_needed)) { + clear_bit(cpu, &smp_invalidate_needed); + local_flush_tlb(); + } +} +#endif + +static void show(char * str) +{ + int i; + unsigned long *stack; + int cpu = smp_processor_id(); + + printk("\n%s, CPU %d:\n", str, cpu); + printk("irq: %d [%d %d]\n", + atomic_read(&global_irq_count), local_irq_count[0], local_irq_count[1]); + printk("bh: %d [%d %d]\n", + atomic_read(&global_bh_count), local_bh_count[0], local_bh_count[1]); + stack = (unsigned long *) &stack; + for (i = 40; i ; i--) { + unsigned long x = *++stack; + //if (x > (unsigned long) &get_options && x < (unsigned long) &vsprintf) { + printk("<[%08lx]> ", x); + //} + } +} + +#define MAXCOUNT 100000000 + +static inline void wait_on_bh(void) +{ + int count = MAXCOUNT; + do { + if (!--count) { + show("wait_on_bh"); + count = ~0; + } + /* nothing .. wait for the other bh's to go away */ + } while (atomic_read(&global_bh_count) != 0); +} + +/* + * I had a lockup scenario where a tight loop doing + * spin_unlock()/spin_lock() on CPU#1 was racing with + * spin_lock() on CPU#0. CPU#0 should have noticed spin_unlock(), but + * apparently the spin_unlock() information did not make it + * through to CPU#0 ... nasty, is this by design, do we have to limit + * 'memory update oscillation frequency' artificially like here? + * + * Such 'high frequency update' races can be avoided by careful design, but + * some of our major constructs like spinlocks use similar techniques, + * it would be nice to clarify this issue. Set this define to 0 if you + * want to check whether your system freezes. I suspect the delay done + * by SYNC_OTHER_CORES() is in correlation with 'snooping latency', but + * i thought that such things are guaranteed by design, since we use + * the 'LOCK' prefix. + */ +#define SUSPECTED_CPU_OR_CHIPSET_BUG_WORKAROUND 1 + +#if SUSPECTED_CPU_OR_CHIPSET_BUG_WORKAROUND +# define SYNC_OTHER_CORES(x) udelay(x+1) +#else +/* + * We have to allow irqs to arrive between __sti and __cli + */ +# define SYNC_OTHER_CORES(x) __asm__ __volatile__ ("nop") +#endif + +static inline void wait_on_irq(int cpu) +{ + int count = MAXCOUNT; + + for (;;) { + + /* + * Wait until all interrupts are gone. Wait + * for bottom half handlers unless we're + * already executing in one.. + */ + if (!atomic_read(&global_irq_count)) { + if (local_bh_count[cpu] || !atomic_read(&global_bh_count)) + break; + } + + /* Duh, we have to loop. Release the lock to avoid deadlocks */ + clear_bit(0,&global_irq_lock); + + for (;;) { + if (!--count) { + show("wait_on_irq"); + count = ~0; + } + __sti(); + SYNC_OTHER_CORES(cpu); + __cli(); + //check_smp_invalidate(cpu); + if (atomic_read(&global_irq_count)) + continue; + if (global_irq_lock) + continue; + if (!local_bh_count[cpu] && atomic_read(&global_bh_count)) + continue; + if (!test_and_set_bit(0,&global_irq_lock)) + break; + } + } +} + +/* + * This is called when we want to synchronize with + * bottom half handlers. We need to wait until + * no other CPU is executing any bottom half handler. + * + * Don't wait if we're already running in an interrupt + * context or are inside a bh handler. + */ +void synchronize_bh(void) +{ + if (atomic_read(&global_bh_count) && !in_interrupt()) + wait_on_bh(); +} + +/* + * This is called when we want to synchronize with + * interrupts. We may for example tell a device to + * stop sending interrupts: but to make sure there + * are no interrupts that are executing on another + * CPU we need to call this function. + */ +void synchronize_irq(void) +{ + if (atomic_read(&global_irq_count)) { + /* Stupid approach */ + cli(); + sti(); + } +} + +static inline void get_irqlock(int cpu) +{ + if (test_and_set_bit(0,&global_irq_lock)) { + /* do we already hold the lock? */ + if ((unsigned char) cpu == global_irq_holder) + return; + /* Uhhuh.. Somebody else got it. Wait.. */ + do { + do { + //check_smp_invalidate(cpu); + } while (test_bit(0,&global_irq_lock)); + } while (test_and_set_bit(0,&global_irq_lock)); + } + /* + * We also to make sure that nobody else is running + * in an interrupt context. + */ + wait_on_irq(cpu); + + /* + * Ok, finally.. + */ + global_irq_holder = cpu; +} + +#define EFLAGS_IF_SHIFT 9 + +/* + * A global "cli()" while in an interrupt context + * turns into just a local cli(). Interrupts + * should use spinlocks for the (very unlikely) + * case that they ever want to protect against + * each other. + * + * If we already have local interrupts disabled, + * this will not turn a local disable into a + * global one (problems with spinlocks: this makes + * save_flags+cli+sti usable inside a spinlock). + */ +void __global_cli(void) +{ + unsigned int flags; + + __save_flags(flags); + if (flags & (1 << EFLAGS_IF_SHIFT)) { + int cpu = smp_processor_id(); + __cli(); + if (!local_irq_count[cpu]) + get_irqlock(cpu); + } +} + +void __global_sti(void) +{ + int cpu = smp_processor_id(); + + if (!local_irq_count[cpu]) + release_irqlock(cpu); + __sti(); +} + +/* + * SMP flags value to restore to: + * 0 - global cli + * 1 - global sti + * 2 - local cli + * 3 - local sti + */ +unsigned long __global_save_flags(void) +{ + int retval; + int local_enabled; + unsigned long flags; + + __save_flags(flags); + local_enabled = (flags >> EFLAGS_IF_SHIFT) & 1; + /* default to local */ + retval = 2 + local_enabled; + + /* check for global flags if we're not in an interrupt */ + if (!local_irq_count[smp_processor_id()]) { + if (local_enabled) + retval = 1; + if (global_irq_holder == (unsigned char) smp_processor_id()) + retval = 0; + } + return retval; +} + +void __global_restore_flags(unsigned long flags) +{ + switch (flags) { + case 0: + __global_cli(); + break; + case 1: + __global_sti(); + break; + case 2: + __cli(); + break; + case 3: + __sti(); + break; + default: + printk("global_restore_flags: %08lx (%08lx)\n", + flags, (&flags)[-1]); + } +} + +#endif + +static void (*old_clock_handler) (); + +void +init_IRQ (void) +{ + char *p; + int latch = (CLKNUM + hz / 2) / hz; + + /* + * Ensure interrupts are disabled. + */ + (void) splhigh (); + + /* + * Program counter 0 of 8253 to interrupt hz times per second. + */ + outb_p (PIT_C0 | PIT_SQUAREMODE | PIT_READMODE, PITCTL_PORT); + outb_p (latch & 0xff, PITCTR0_PORT); + outb (latch >> 8, PITCTR0_PORT); + + /* + * Install our clock interrupt handler. + */ + old_clock_handler = ivect[0]; + ivect[0] = linux_timer_intr; + + reserve_mach_irqs (); + + /* + * Enable interrupts. + */ + (void) spl0 (); + + /* + * Check if the machine has an EISA bus. + */ + p = (char *) 0x0FFFD9; + if (*p++ == 'E' && *p++ == 'I' && *p++ == 'S' && *p == 'A') + EISA_bus = 1; + + /* + * Permanently allocate standard device ports. + */ + request_region (0x00, 0x20, "dma1"); + request_region (0x20, 0x20, "pic1"); + request_region (0x40, 0x20, "timer"); + request_region (0x70, 0x10, "rtc"); + request_region (0x80, 0x20, "dma page reg"); + request_region (0xa0, 0x20, "pic2"); + request_region (0xc0, 0x20, "dma2"); + request_region (0xf0, 0x10, "npu"); +} + +void +restore_IRQ (void) +{ + /* + * Disable interrupts. + */ + (void) splhigh (); + + /* + * Restore clock interrupt handler. + */ + ivect[0] = old_clock_handler; +} + -- cgit v1.2.3