diff options
author | Pasha <pasha@member.fsf.org> | 2024-02-20 18:49:50 +0000 |
---|---|---|
committer | Pasha <pasha@member.fsf.org> | 2024-02-20 18:49:50 +0000 |
commit | 5e0b8d508ed51004bd836384293be00950ee62c9 (patch) | |
tree | e3f16b1aa8b7177032ce3ec429fbad2b1d92a876 /linux/dev/arch/i386/kernel | |
download | gnumach-riscv-5e0b8d508ed51004bd836384293be00950ee62c9.tar.gz gnumach-riscv-5e0b8d508ed51004bd836384293be00950ee62c9.tar.bz2 |
init gnumach copy
Diffstat (limited to 'linux/dev/arch/i386/kernel')
-rw-r--r-- | linux/dev/arch/i386/kernel/irq.c | 775 | ||||
-rw-r--r-- | linux/dev/arch/i386/kernel/setup.c | 13 |
2 files changed, 788 insertions, 0 deletions
diff --git a/linux/dev/arch/i386/kernel/irq.c b/linux/dev/arch/i386/kernel/irq.c new file mode 100644 index 0000000..3b349cc --- /dev/null +++ b/linux/dev/arch/i386/kernel/irq.c @@ -0,0 +1,775 @@ +/* + * 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 <sys/types.h> +#include <mach/mach_types.h> +#include <mach/vm_param.h> +#include <kern/assert.h> +#include <kern/cpu_number.h> + +#include <i386/spl.h> +#include <i386/irq.h> +#include <i386/pit.h> + +#define MACH_INCLUDE +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/delay.h> +#include <linux/kernel_stat.h> +#include <linux/malloc.h> +#include <linux/ioport.h> + +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/hardirq.h> + +#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]; +unsigned int local_irq_count[NR_CPUS]; +#else +#define local_bh_count (&intr_count) +#define local_irq_count (&intr_count) +#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; + user_intr_t *user_intr; +}; + +static struct linux_action *irq_action[NINTR] = {0}; + +/* + * 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(&irqtab, 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--; +} + +/* IRQ mask according to Linux drivers */ +static unsigned linux_pic_mask; + +/* These only record that Linux requested to mask IRQs */ +void +disable_irq (unsigned int irq_nr) +{ + unsigned long flags; + unsigned mask = 1U << irq_nr; + + save_flags (flags); + cli (); + if (!(linux_pic_mask & mask)) + { + linux_pic_mask |= mask; + __disable_irq(irq_nr); + } + restore_flags (flags); +} + +void +enable_irq (unsigned int irq_nr) +{ + unsigned long flags; + unsigned mask = 1U << irq_nr; + + save_flags (flags); + cli (); + if (linux_pic_mask & mask) + { + linux_pic_mask &= ~mask; + __enable_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; +} + +int +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 < NINTR); + + /* 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->dst_port == user_intr->dst_port) + { + 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 = SA_SHIRQ; + 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. + */ +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 < NINTR); + + 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; + action->user_intr = NULL; + + 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 >= NINTR) + 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[cpu_number()] == 0); + + /* + * Allocate all available IRQs. + */ + for (i = NINTR - 1; 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 & ~linux_pic_mask); +} + +/* + * Return the result of an irq probe. + */ +int +probe_irq_off (unsigned long irqs) +{ + unsigned int i; + + assert (curr_ipl[cpu_number()] == 0); + + irqs &= linux_pic_mask; + + /* + * Disable unnecessary IRQs. + */ + for (i = NINTR - 1; 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 < NINTR; 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 (); + +#ifndef APIC + /* + * 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; +#endif + + reserve_mach_irqs (); + + /* + * Enable interrupts. + */ + (void) spl0 (); + + /* + * Check if the machine has an EISA bus. + */ + p = (char *) phystokv(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 (); + +#ifndef APIC + /* + * Restore clock interrupt handler. + */ + ivect[0] = old_clock_handler; +#endif +} + diff --git a/linux/dev/arch/i386/kernel/setup.c b/linux/dev/arch/i386/kernel/setup.c new file mode 100644 index 0000000..92b782a --- /dev/null +++ b/linux/dev/arch/i386/kernel/setup.c @@ -0,0 +1,13 @@ +char x86 = +#if defined(CONFIG_M386) +3; +#elif defined(CONFIG_M486) +4; +#elif defined(CONFIG_M586) +5; +#elif defined(CONFIG_M686) +6; +#else +#error "CPU type is undefined!" +#endif + |