diff options
author | asami <asami@FreeBSD.org> | 1996-06-14 10:04:54 +0000 |
---|---|---|
committer | asami <asami@FreeBSD.org> | 1996-06-14 10:04:54 +0000 |
commit | 11677d0ab4154036d365fe6697f9d6048dd6bf0e (patch) | |
tree | b16bc29399fcb327f66d0a663ab7e5390be39106 /sys/pc98 | |
parent | 27f43565aafed9c68ce08066a71a43a9c692bcb7 (diff) | |
download | FreeBSD-src-11677d0ab4154036d365fe6697f9d6048dd6bf0e.zip FreeBSD-src-11677d0ab4154036d365fe6697f9d6048dd6bf0e.tar.gz |
The PC98-specific files.
Ok'd by: core
Submitted by: FreeBSD(98) development team
Diffstat (limited to 'sys/pc98')
-rw-r--r-- | sys/pc98/cbus/30line.h | 125 | ||||
-rw-r--r-- | sys/pc98/cbus/cbus.h | 185 | ||||
-rw-r--r-- | sys/pc98/cbus/clock.c | 1129 | ||||
-rw-r--r-- | sys/pc98/cbus/fdc.c | 2489 | ||||
-rw-r--r-- | sys/pc98/cbus/fdcreg.h | 102 | ||||
-rw-r--r-- | sys/pc98/cbus/pcrtc.c | 1129 | ||||
-rw-r--r-- | sys/pc98/cbus/sio.c | 4023 | ||||
-rw-r--r-- | sys/pc98/conf/GENERIC | 164 | ||||
-rw-r--r-- | sys/pc98/pc98/machdep.c | 2068 |
9 files changed, 11414 insertions, 0 deletions
diff --git a/sys/pc98/cbus/30line.h b/sys/pc98/cbus/30line.h new file mode 100644 index 0000000..a6b7b7c --- /dev/null +++ b/sys/pc98/cbus/30line.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 1994, 1995, 1996. FreeBSD(98) porting team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer as + * the first lines of this file unmodified. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __PC98_PC98_30LINE_H__ +#define __PC98_PC98_30LINE_H__ + +#include <pc98/pc98/module.h> + +#ifndef LINE30_ROW +#define LINE30_ROW 30 +#endif + +#define _CR 80 +#ifndef _VS +#define _VS 7 +#endif +#ifndef _HS +#define _HS 6 + 1 +#endif +#ifndef _HFP +#define _HFP 10 + 1 +#endif +#ifndef _HBP +#define _HBP 7 + 1 +#endif +#ifndef _VFP +#define _VFP 7 +#endif +#ifndef _VBP +#define _VBP 25 +#endif + +#define _LF LINE30_ROW*16 + +#define _GDC_RESET 0x00 +#define _GDC_SYNC 0x0e +#define _GDC_MASTER 0x6f +#define _GDC_SLAVE 0x6e +#define _GDC_START 0x0d +#define _GDC_STOP 0x0c +#define _GDC_SCROLL 0x70 +#define _GDC_PITCH 0x47 + +#define GDC_CR 0 +#define GDC_VS 1 +#define GDC_HS 2 +#define GDC_HFP 3 +#define GDC_HBP 4 +#define GDC_VFP 5 +#define GDC_VBP 6 +#define GDC_LF 7 + + +#define _2_5MHZ 0 +#define _5MHZ 1 + +#define _25L 0 +#define _30L 1 + +#define T25_G400 0 +#define T30_G400 1 +#define T30_G480 2 + +static void master_gdc_cmd(unsigned int); +static void master_gdc_prm(unsigned int); +static void master_gdc_word_prm(unsigned int); +static void master_gdc_fifo_empty(void); +static void master_gdc_wait_vsync(void); + +static void gdc_cmd(unsigned int); +static void gdc_prm(unsigned int); +static void gdc_word_prm(unsigned int); +static void gdc_fifo_empty(void); +static void gdc_wait_vsync(void); + +static int check_gdc_clock(void); + +static int gdc_INFO = _25L; +static void initialize_gdc(unsigned int); + +static unsigned int master_param[2][8] = { +{78, 8, 7, 9, 7, 7, 25, 400}, +{_CR-2, _VS, _HS-1, _HFP-1, _HBP-1, _VFP, _VBP, _LF}}; + +static unsigned int slave_param[6][8] = { +{38, 8, 3, 4, 3, 7, 25, 400}, /* normal */ +{78, 8, 7, 9, 7, 7, 25, 400}, +{_CR/2-2, _VS, (_HS)/2-1, (_HFP)/2-1, (_HBP)/2-1, +_VFP+(_LF-400)/2+8, _VBP+(_LF-400)/2-8, 400}, /* 30 & 400 */ +{_CR-2, _VS, _HS-1, _HFP-1, _HBP-1, +_VFP+(_LF-400)/2+8, _VBP+(_LF-400)/2-8, 400}, +{_CR/2-2, _VS, (_HS)/2-1, (_HFP)/2-1, (_HBP)/2-1, +_VFP, _VBP, _LF}, /* 30 & 480 */ +{_CR-2, _VS, _HS-1, _HFP-1, _HBP-1, _VFP, _VBP, _LF}}; + +static int SlavePCH[2] = {40,80}; +static int MasterPCH = 80; +static int SlaveScrlLF[3] = {400,400,_LF}; + +#endif diff --git a/sys/pc98/cbus/cbus.h b/sys/pc98/cbus/cbus.h new file mode 100644 index 0000000..806c840 --- /dev/null +++ b/sys/pc98/cbus/cbus.h @@ -0,0 +1,185 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)isa.h 5.7 (Berkeley) 5/9/91 + * $Id: isa.h,v 1.18 1996/01/30 22:55:57 mpp Exp $ + */ + +#ifndef _PC98_PC98_PC98_H_ +#define _PC98_PC98_PC98_H_ + +/* BEWARE: Included in both assembler and C code */ + +/* + * PC98 Bus conventions + */ +/* + * PC98 Bus conventions + * modified for PC9801 by A.Kojima F.Ukai M.Ishii + * Kyoto University Microcomputer Club (KMC) + */ + +/* + * Input / Output Port Assignments + */ + +#ifndef IO_BEGIN +#ifndef PC98 /* IBM-PC */ +#define IO_ISABEGIN 0x000 /* 0x000 - Beginning of I/O Registers */ +#endif + +/* PC98 IO address ... very dirty (^_^; */ + +#define IO_ICU1 0x000 /* 8259A Interrupt Controller #1 */ +#define IO_ICU2 0x008 /* 8259A Interrupt Controller #2 */ +#define IO_DMA 0x001 /* 8237A DMA Controller */ +#define IO_RTC 0x020 /* 4990A RTC */ +#define IO_DMAPG 0x021 /* DMA Page Registers */ +#define IO_COM1 0x030 /* 8251A RS232C serial I/O (int) */ +#define IO_COM2 0x0b1 /* 8251A RS232C serial I/O (ext) */ +#define IO_COM3 0x0b9 /* 8251A RS232C serial I/O (ext) */ +#define IO_SYSPORT 0x031 /* 8255A System Port */ +#define IO_LPT 0x040 /* 8255A Printer Port */ +#define IO_KBD 0x041 /* 8251A Keyboard */ +#define IO_PPI 0x035 /* Programmable Peripheral Interface */ +#define IO_NMI 0x050 /* NMI Control */ +#define IO_WAIT 0x05F /* WAIT 0.6 us */ +#define IO_GDC1 0x060 /* 7220 GDC Text Control */ +#define IO_TIMER 0x071 /* 8253C Timer */ +#define IO_SASI 0x080 /* SASI Hard Disk Controller */ +#define IO_SOUND 0x188 /* YM2203 FM sound board */ +#define IO_GDC2 0x0a0 /* 7220 GDC Graphic Control */ +#define IO_EGC 0x4a0 /* 7220 GDC Graphic Control */ +#define IO_CGROM 0x0a1 /* Character ROM */ +#define IO_SCSI 0xCC0 /* SCSI Controller */ +#define IO_FD1 0x090 /* 765A 1MB FDC */ +#define IO_FD2 0x0c8 /* 765A 640KB FDC */ +#define IO_FDPORT 0x0be /* FD I/F port (1M<->640K,EMTON) */ +#define IO_BEEPF 0x3fdb /* beep frequency */ +#define IO_MOUSE 0x7fd9 /* mouse */ +#define IO_MOUSETM 0xdfbd /* mouse timer */ +#define IO_REEST 0x0F0 /* CPU FPU reset */ +#define IO_A2OEN 0x0F2 /* A20 enable */ +#define IO_A20CT 0x0F6 /* A20 control enable/disable */ +#define IO_NPX 0x0F8 /* Numeric Coprocessor */ +#define IO_BMS 0x7fd9 /* Bus Mouse */ +#define IO_MSE 0x7fd9 /* Bus Mouse */ +#define IO_SIO1 0x0d0 /* MC16550II ext RS232C */ +#define IO_SIO2 0x8d0 /* MC16550II ext RS232C */ + +/*#ifdef PC98NS*/ +#if 1 +/* Oct 13, ukai */ +#define IO_WD1_NEC 0x640 /* 98note IDE Hard disk controller */ +#define IO_WD1_EPSON 0x80 /* 386note Hard disk controller */ +#define IO_WD1 IO_WD1_NEC /* IDE Hard disk controller */ +#endif + +#ifndef PC98 /* IBM-PC */ +#define IO_ISAEND 0x3FF /* - 0x3FF End of I/O Registers */ +#endif IO_ISABEGIN +#endif + +/* + * Input / Output Port Sizes - these are from several sources, and tend + * to be the larger of what was found, ie COM ports can be 4, but some + * boards do not fully decode the address, thus 8 ports are used. + */ + +#ifndef IO_PC98SIZES +#define IO_PC98SIZES + +#define IO_COMSIZE 8 /* 8250, 16X50 com controllers (4?) */ +#define IO_CGASIZE 16 /* CGA controllers */ +#define IO_DMASIZE 16 /* 8237 DMA controllers */ +#define IO_DPGSIZE 32 /* 74LS612 DMA page registers */ +#define IO_FDCSIZE 8 /* Nec765 floppy controllers */ +#define IO_WDCSIZE 8 /* WD compatible disk controllers */ +#define IO_GAMSIZE 16 /* AT compatible game controllers */ +#define IO_ICUSIZE 16 /* 8259A interrupt controllers */ +#define IO_KBDSIZE 16 /* 8042 Keyboard controllers */ +#define IO_LPTSIZE 8 /* LPT controllers, some use only 4 */ +#define IO_MDASIZE 16 /* Monochrome display controllers */ +#define IO_RTCSIZE 16 /* CMOS real time clock, NMI control */ +#define IO_TMRSIZE 16 /* 8253 programmable timers */ +#define IO_NPXSIZE 16 /* 80387/80487 NPX registers */ +#define IO_VGASIZE 16 /* VGA controllers */ +#define IO_EISASIZE 4096 /* EISA controllers */ +#define IO_PMPSIZE 2 /* 82347 power management peripheral */ + +#endif /* IO_PC98SIZES */ + +/* + * Input / Output Memory Physical Addresses + */ + +#ifndef IOM_BEGIN +#define IOM_BEGIN 0x0a0000 /* Start of I/O Memory "hole" */ +#define IOM_END 0x100000 /* End of I/O Memory "hole" */ +#define IOM_SIZE (IOM_END - IOM_BEGIN) +#endif IOM_BEGIN + +/* + * RAM Physical Address Space (ignoring the above mentioned "hole") + */ + +#ifndef RAM_BEGIN +#define RAM_BEGIN 0x0000000 /* Start of RAM Memory */ +#ifdef EPSON_BOUNCEDMA +#define RAM_END 0x0f00000 /* End of EPSON GR?? RAM Memory */ +#else +#define RAM_END 0x1000000 /* End of RAM Memory */ +#endif +#define RAM_SIZE (RAM_END - RAM_BEGIN) +#endif RAM_BEGIN + +#ifndef PC98 /* IBM-PC */ +/* + * Oddball Physical Memory Addresses + */ +#ifndef COMPAQ_RAMRELOC +#define COMPAQ_RAMRELOC 0x80c00000 /* Compaq RAM relocation/diag */ +#define COMPAQ_RAMSETUP 0x80c00002 /* Compaq RAM setup */ +#define WEITEK_FPU 0xC0000000 /* WTL 2167 */ +#define CYRIX_EMC 0xC0000000 /* Cyrix EMC */ +#endif COMPAQ_RAMRELOC +#endif + +/* + * Obtained from NetBSD/pc98 + */ +#define MADDRUNK -1 + +#endif /* !_PC98_PC98_PC98_H_ */ diff --git a/sys/pc98/cbus/clock.c b/sys/pc98/cbus/clock.c new file mode 100644 index 0000000..b2befbf --- /dev/null +++ b/sys/pc98/cbus/clock.c @@ -0,0 +1,1129 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz and Don Ahn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)clock.c 7.2 (Berkeley) 5/12/91 + * $Id: clock.c,v 1.58 1996/05/01 08:39:02 bde Exp $ + */ + +/* + * inittodr, settodr and support routines written + * by Christoph Robitschko <chmr@edvz.tu-graz.ac.at> + * + * reintroduced and updated by Chris Stenton <chris@gnome.co.uk> 8/10/94 + */ + +/* + * modified for PC98 + * $Id: clock.c,v 1.7 1994/03/26 22:56:13 kakefuda Exp kakefuda $ + */ + +/* + * Primitive clock interrupt routines. + */ +#include "opt_ddb.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> + +#include <machine/clock.h> +#ifdef CLK_CALIBRATION_LOOP +#include <machine/cons.h> +#endif +#include <machine/cpu.h> +#include <machine/frame.h> + +#ifdef PC98 +#include <sys/syslog.h> +#include <pc98/pc98/icu.h> +#include <pc98/pc98/pc98.h> +#include <pc98/pc98/pc98_device.h> +#include <pc98/pc98/timerreg.h> +#else +#include <i386/isa/icu.h> +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/rtc.h> +#include <i386/isa/timerreg.h> +#endif + +/* + * 32-bit time_t's can't reach leap years before 1904 or after 2036, so we + * can use a simple formula for leap years. + */ +#define LEAPYEAR(y) ((u_int)(y) % 4 == 0) +#define DAYSPERYEAR (31+28+31+30+31+30+31+31+30+31+30+31) + +#define TIMER_DIV(x) ((timer_freq + (x) / 2) / (x)) + +/* + * Time in timer cycles that it takes for microtime() to disable interrupts + * and latch the count. microtime() currently uses "cli; outb ..." so it + * normally takes less than 2 timer cycles. Add a few for cache misses. + * Add a few more to allow for latency in bogus calls to microtime() with + * interrupts already disabled. + */ +#define TIMER0_LATCH_COUNT 20 + +/* + * Minimum maximum count that we are willing to program into timer0. + * Must be large enough to guarantee that the timer interrupt handler + * returns before the next timer interrupt. Must be larger than + * TIMER0_LATCH_COUNT so that we don't have to worry about underflow in + * the calculation of timer0_overflow_threshold. + */ +#define TIMER0_MIN_MAX_COUNT TIMER_DIV(20000) + +int adjkerntz; /* local offset from GMT in seconds */ +int disable_rtc_set; /* disable resettodr() if != 0 */ +int wall_cmos_clock; /* wall CMOS clock assumed if != 0 */ + +u_int idelayed; +#if defined(I586_CPU) || defined(I686_CPU) +unsigned i586_ctr_freq; +unsigned i586_ctr_rate; +long long i586_ctr_bias; +long long i586_last_tick; +unsigned long i586_avg_tick; +#endif +int statclock_disable; +u_int stat_imask = SWI_CLOCK_MASK; +int timer0_max_count; +u_int timer0_overflow_threshold; +u_int timer0_prescaler_count; + +static int beeping = 0; +static u_int clk_imask = HWI_MASK | SWI_MASK; +static const u_char daysinmonth[] = {31,28,31,30,31,30,31,31,30,31,30,31}; +static u_int hardclock_max_count; +/* + * XXX new_function and timer_func should not handle clockframes, but + * timer_func currently needs to hold hardclock to handle the + * timer0_state == 0 case. We should use register_intr()/unregister_intr() + * to switch between clkintr() and a slightly different timerintr(). + * This will require locking when acquiring and releasing timer0 - the + * current (nonexistent) locking doesn't seem to be adequate even now. + */ +static void (*new_function) __P((struct clockframe *frame)); +static u_int new_rate; +#ifndef PC98 +static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; +static u_char rtc_statusb = RTCSB_24HR | RTCSB_PINTR; +#endif +#ifdef TIMER_FREQ +static u_int timer_freq = TIMER_FREQ; +#else +#ifdef PC98 +#ifndef AUTO_CLOCK +#ifndef PC98_8M +static u_int timer_freq = 2457600; +#else /* !PC98_8M */ +static u_int timer_freq = 1996800; +#endif /* PC98_8M */ +#else /* AUTO_CLOCK */ +static u_int timer_freq = 2457600; +#endif /* AUTO_CLOCK */ +#else /* IBM-PC */ +static u_int timer_freq = 1193182; +#endif /* PC98 */ +#endif +static char timer0_state = 0; +#ifdef PC98 +static char timer1_state = 0; +#endif +static char timer2_state = 0; +static void (*timer_func) __P((struct clockframe *frame)) = hardclock; +int rtc_inb __P((void)); + +#if 0 +void +clkintr(struct clockframe frame) +{ + hardclock(&frame); + setdelayed(); +} +#else +static void +clkintr(struct clockframe frame) +{ + timer_func(&frame); + switch (timer0_state) { + case 0: + setdelayed(); + break; + case 1: + if ((timer0_prescaler_count += timer0_max_count) + >= hardclock_max_count) { + hardclock(&frame); + setdelayed(); + timer0_prescaler_count -= hardclock_max_count; + } + break; + case 2: + setdelayed(); + timer0_max_count = TIMER_DIV(new_rate); + timer0_overflow_threshold = + timer0_max_count - TIMER0_LATCH_COUNT; + disable_intr(); + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(TIMER_CNTR0, timer0_max_count & 0xff); + outb(TIMER_CNTR0, timer0_max_count >> 8); + enable_intr(); + timer0_prescaler_count = 0; + timer_func = new_function; + timer0_state = 1; + break; + case 3: + if ((timer0_prescaler_count += timer0_max_count) + >= hardclock_max_count) { + hardclock(&frame); + setdelayed(); + timer0_max_count = hardclock_max_count; + timer0_overflow_threshold = + timer0_max_count - TIMER0_LATCH_COUNT; + disable_intr(); + outb(TIMER_MODE, + TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(TIMER_CNTR0, timer0_max_count & 0xff); + outb(TIMER_CNTR0, timer0_max_count >> 8); + enable_intr(); + /* + * See microtime.s for this magic. + */ +#ifdef PC98 +#ifndef AUTO_CLOCK +#ifndef PC98_8M + time.tv_usec += (6667 * + (timer0_prescaler_count - hardclock_max_count)) + >> 14; +#else /* PC98_8M */ + time.tv_usec += (16411 * + (timer0_prescaler_count - hardclock_max_count)) + >> 15; +#endif /* PC98_8M */ +#else /* AUTO_CLOCK */ + if (pc98_machine_type & M_8M) { + /* PC98_8M */ + time.tv_usec += (16411 * + (timer0_prescaler_count - + hardclock_max_count)) >> 15; + } else { + time.tv_usec += (6667 * + (timer0_prescaler_count - + hardclock_max_count)) >> 14; + } +#endif /* AUTO_CLOCK */ +#else /* IBM-PC */ + time.tv_usec += (27645 * + (timer0_prescaler_count - hardclock_max_count)) + >> 15; +#endif /* PC98 */ + if (time.tv_usec >= 1000000) + time.tv_usec -= 1000000; + timer0_prescaler_count = 0; + timer_func = hardclock;; + timer0_state = 0; + } + break; + } +} +#endif + +int +acquire_timer0(int rate, void (*function) __P((struct clockframe *frame))) +{ + if (timer0_state || TIMER_DIV(rate) < TIMER0_MIN_MAX_COUNT || + !function) + return -1; + new_function = function; + new_rate = rate; + timer0_state = 2; + return 0; +} + +#ifdef PC98 +int +acquire_timer1(int mode) +{ + if (timer1_state) + return -1; + timer1_state = 1; + outb(TIMER_MODE, TIMER_SEL1 | (mode &0x3f)); + return 0; +} +#endif + +int +acquire_timer2(int mode) +{ + if (timer2_state) + return -1; + timer2_state = 1; + outb(TIMER_MODE, TIMER_SEL2 | (mode &0x3f)); + return 0; +} + +int +release_timer0() +{ + if (!timer0_state) + return -1; + timer0_state = 3; + return 0; +} + +#ifdef PC98 +int +release_timer1() +{ + if (!timer1_state) + return -1; + timer1_state = 0; + outb(TIMER_MODE, TIMER_SEL1|TIMER_SQWAVE|TIMER_16BIT); + return 0; +} +#endif + +int +release_timer2() +{ + if (!timer2_state) + return -1; + timer2_state = 0; + outb(TIMER_MODE, TIMER_SEL2|TIMER_SQWAVE|TIMER_16BIT); + return 0; +} + +#ifndef PC98 +/* + * This routine receives statistical clock interrupts from the RTC. + * As explained above, these occur at 128 interrupts per second. + * When profiling, we receive interrupts at a rate of 1024 Hz. + * + * This does not actually add as much overhead as it sounds, because + * when the statistical clock is active, the hardclock driver no longer + * needs to keep (inaccurate) statistics on its own. This decouples + * statistics gathering from scheduling interrupts. + * + * The RTC chip requires that we read status register C (RTC_INTR) + * to acknowledge an interrupt, before it will generate the next one. + */ +static void +rtcintr(struct clockframe frame) +{ + u_char stat; + stat = rtcin(RTC_INTR); + if(stat & RTCIR_PERIOD) { + statclock(&frame); + } +} + +#ifdef DDB +static void +DDB_printrtc(void) +{ + printf("%02x/%02x/%02x %02x:%02x:%02x, A = %02x, B = %02x, C = %02x\n", + rtcin(RTC_YEAR), rtcin(RTC_MONTH), rtcin(RTC_DAY), + rtcin(RTC_HRS), rtcin(RTC_MIN), rtcin(RTC_SEC), + rtcin(RTC_STATUSA), rtcin(RTC_STATUSB), rtcin(RTC_INTR)); +} +#endif +#endif /* for PC98 */ + +static int +getit(void) +{ + int high, low; + + disable_intr(); + /* select timer0 and latch counter value */ + outb(TIMER_MODE, TIMER_SEL0); + low = inb(TIMER_CNTR0); + high = inb(TIMER_CNTR0); + enable_intr(); + return ((high << 8) | low); +} + +/* + * Wait "n" microseconds. + * Relies on timer 1 counting down from (timer_freq / hz) + * Note: timer had better have been programmed before this is first used! + */ +void +DELAY(int n) +{ + int prev_tick, tick, ticks_left, sec, usec; + +#ifdef DELAYDEBUG + int getit_calls = 1; + int n1; + static int state = 0; + + if (state == 0) { + state = 1; + for (n1 = 1; n1 <= 10000000; n1 *= 10) + DELAY(n1); + state = 2; + } + if (state == 1) + printf("DELAY(%d)...", n); +#endif + /* + * Read the counter first, so that the rest of the setup overhead is + * counted. Guess the initial overhead is 20 usec (on most systems it + * takes about 1.5 usec for each of the i/o's in getit(). The loop + * takes about 6 usec on a 486/33 and 13 usec on a 386/20. The + * multiplications and divisions to scale the count take a while). + */ + prev_tick = getit(); + n -= 20; + /* + * Calculate (n * (timer_freq / 1e6)) without using floating point + * Calculate (n * (TIMER_FREQ / 1e6)) without using floating point + * and without any avoidable overflows. + */ + sec = n / 1000000; + usec = n - sec * 1000000; + ticks_left = sec * timer_freq + + usec * (timer_freq / 1000000) + + usec * ((timer_freq % 1000000) / 1000) / 1000 + + usec * (timer_freq % 1000) / 1000000; + if (n < 0) + ticks_left = 0; /* XXX timer_freq is unsigned */ + + while (ticks_left > 0) { + tick = getit(); +#ifdef DELAYDEBUG + ++getit_calls; +#endif + if (tick > prev_tick) + ticks_left -= prev_tick - (tick - timer0_max_count); + else + ticks_left -= prev_tick - tick; + prev_tick = tick; + } +#ifdef DELAYDEBUG + if (state == 1) + printf(" %d calls to getit() at %d usec each\n", + getit_calls, (n + 5) / getit_calls); +#endif +} + +static void +sysbeepstop(void *chan) +{ +#ifdef PC98 /* PC98 */ + outb(IO_PPI, inb(IO_PPI)|0x08); /* disable counter1 output to speaker */ + release_timer1(); +#else + outb(IO_PPI, inb(IO_PPI)&0xFC); /* disable counter2 output to speaker */ + release_timer2(); +#endif + beeping = 0; +} + +int +sysbeep(int pitch, int period) +{ +#ifdef PC98 + if (acquire_timer1(TIMER_SQWAVE|TIMER_16BIT)) + return -1; + disable_intr(); + outb(0x3fdb, pitch); + outb(0x3fdb, (pitch>>8)); + enable_intr(); + if (!beeping) { + outb(IO_PPI, (inb(IO_PPI) & 0xf7)); /* enable counter1 output to speaker */ + beeping = period; + timeout(sysbeepstop, (void *)NULL, period); + } +#else + + if (acquire_timer2(TIMER_SQWAVE|TIMER_16BIT)) + return -1; + disable_intr(); + outb(TIMER_CNTR2, pitch); + outb(TIMER_CNTR2, (pitch>>8)); + enable_intr(); + if (!beeping) { + outb(IO_PPI, inb(IO_PPI) | 3); /* enable counter2 output to speaker */ + beeping = period; + timeout(sysbeepstop, (void *)NULL, period); + } +#endif + return 0; +} + +#ifndef PC98 +/* + * RTC support routines + */ + +int +rtcin(reg) + int reg; +{ + u_char val; + + outb(IO_RTC, reg); + inb(0x84); + val = inb(IO_RTC + 1); + inb(0x84); + return (val); +} + +static __inline void +writertc(u_char reg, u_char val) +{ + outb(IO_RTC, reg); + outb(IO_RTC + 1, val); +} + +static __inline int +readrtc(int port) +{ + return(bcd2bin(rtcin(port))); +} +#endif + +#ifdef PC98 +unsigned int delaycount; +#define FIRST_GUESS 0x2000 +static void findcpuspeed(void) +{ + int i; + int remainder; + + /* Put counter in count down mode */ + outb(TIMER_MODE, TIMER_SEL0 | TIMER_16BIT | TIMER_RATEGEN); + outb(TIMER_CNTR0, 0xff); + outb(TIMER_CNTR0, 0xff); + for (i = FIRST_GUESS; i; i--) + ; + remainder = getit(); + delaycount = (FIRST_GUESS * TIMER_DIV(1000)) / (0xffff - remainder); +} +#endif + +#ifndef PC98 +static u_int +calibrate_clocks(void) +{ + u_int count, prev_count, tot_count; + int sec, start_sec, timeout; + + printf("Calibrating clock(s) relative to mc146818A clock ... "); + if (!(rtcin(RTC_STATUSD) & RTCSD_PWR)) + goto fail; + timeout = 100000000; + + /* Read the mc146818A seconds counter. */ + for (;;) { + if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) { + sec = rtcin(RTC_SEC); + break; + } + if (--timeout == 0) + goto fail; + } + + /* Wait for the mC146818A seconds counter to change. */ + start_sec = sec; + for (;;) { + if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) { + sec = rtcin(RTC_SEC); + if (sec != start_sec) + break; + } + if (--timeout == 0) + goto fail; + } + + /* Start keeping track of the i8254 counter. */ + prev_count = getit(); + if (prev_count == 0 || prev_count > timer0_max_count) + goto fail; + tot_count = 0; + +#if defined(I586_CPU) || defined(I686_CPU) + if (cpu_class == CPUCLASS_586 || cpu_class == CPUCLASS_686) + wrmsr(0x10, 0LL); /* XXX 0x10 is the MSR for the TSC */ +#endif + + /* + * Wait for the mc146818A seconds counter to change. Read the i8254 + * counter for each iteration since this is convenient and only + * costs a few usec of inaccuracy. The timing of the final reads + * of the counters almost matches the timing of the initial reads, + * so the main cause of inaccuracy is the varying latency from + * inside getit() or rtcin(RTC_STATUSA) to the beginning of the + * rtcin(RTC_SEC) that returns a changed seconds count. The + * maximum inaccuracy from this cause is < 10 usec on 486's. + */ + start_sec = sec; + for (;;) { + if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) + sec = rtcin(RTC_SEC); + count = getit(); + if (count == 0 || count > timer0_max_count) + goto fail; + if (count > prev_count) + tot_count += prev_count - (count - timer0_max_count); + else + tot_count += prev_count - count; + prev_count = count; + if (sec != start_sec) + break; + if (--timeout == 0) + goto fail; + } + +#if defined(I586_CPU) || defined(I686_CPU) + /* + * Read the cpu cycle counter. The timing considerations are + * similar to those for the i8254 clock. + */ + if (cpu_class == CPUCLASS_586 || cpu_class == CPUCLASS_686) { + unsigned long long i586_count; + + i586_count = rdtsc(); + i586_ctr_freq = i586_count; + i586_ctr_rate = (i586_count << I586_CTR_RATE_SHIFT) / 1000000; + printf("i586 clock: %u Hz, ", i586_ctr_freq); + } +#endif + + printf("i8254 clock: %u Hz\n", tot_count); + return (tot_count); + +fail: + printf("failed, using default i8254 clock of %u Hz\n", timer_freq); + return (timer_freq); +} +#endif /* !PC98 */ + +static void +set_timer_freq(u_int freq, int intr_freq) +{ + u_long ef; + + ef = read_eflags(); + timer_freq = freq; + timer0_max_count = hardclock_max_count = TIMER_DIV(intr_freq); + timer0_overflow_threshold = timer0_max_count - TIMER0_LATCH_COUNT; + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(TIMER_CNTR0, timer0_max_count & 0xff); + outb(TIMER_CNTR0, timer0_max_count >> 8); + write_eflags(ef); +} + +/* + * Initialize 8253 timer 0 early so that it can be used in DELAY(). + * XXX initialization of other timers is unintentionally left blank. + */ +void +startrtclock() +{ + u_int delta, freq; + +#ifdef PC98 + findcpuspeed(); +#ifndef AUTO_CLOCK + if (pc98_machine_type & M_8M) { +#ifndef PC98_8M + log(LOG_EMERG, + "you must reconfig a kernel with \"PC98_8M\" option.\n"); +#endif + } else { +#ifdef PC98_8M + log(LOG_EMERG, + "You must reconfig a kernel without \"PC98_8M\" option.\n"); +#endif + } +#else /* AUTO_CLOCK */ + if (pc98_machine_type & M_8M) + timer_freq = 1996800L; /* 1.9968 MHz */ + else + timer_freq = 2457600L; /* 2.4576 MHz */ +#endif /* AUTO_CLOCK */ +#endif /* PC98 */ + +#ifndef PC98 + writertc(RTC_STATUSA, rtc_statusa); + writertc(RTC_STATUSB, RTCSB_24HR); +#endif + +#ifndef PC98 + /* + * Temporarily calibrate with a high intr_freq to get a low + * timer0_max_count to help detect bogus i8254 counts. + */ + set_timer_freq(timer_freq, 20000); + freq = calibrate_clocks(); +#ifdef CLK_CALIBRATION_LOOP + if (bootverbose) { + printf( + "Press a key on the console to abort clock calibration\n"); + while (!cncheckc()) + calibrate_clocks(); + } +#endif + + /* + * Use the calibrated i8254 frequency if it seems reasonable. + * Otherwise use the default, and don't use the calibrated i586 + * frequency. + */ + delta = freq > timer_freq ? freq - timer_freq : timer_freq - freq; + if (delta < timer_freq / 100) { +#ifndef CLK_USE_I8254_CALIBRATION + printf( +"CLK_USE_I8254_CALIBRATION not specified - using default frequency\n"); + freq = timer_freq; +#endif + timer_freq = freq; + } else { + printf("%d Hz differs from default of %d Hz by more than 1%%\n", + freq, timer_freq); +#if defined(I586_CPU) || defined(I686_CPU) + i586_ctr_freq = 0; + i586_ctr_rate = 0; +#endif + } +#endif + + set_timer_freq(timer_freq, hz); + +#if defined(I586_CPU) || defined(I686_CPU) +#ifndef CLK_USE_I586_CALIBRATION + if (i586_ctr_rate != 0) { + printf( +"CLK_USE_I586_CALIBRATION not specified - using old calibration method\n"); + i586_ctr_freq = 0; + i586_ctr_rate = 0; + } +#endif + if (i586_ctr_rate == 0 && + (cpu_class == CPUCLASS_586 || cpu_class == CPUCLASS_686)) { + /* + * Calibration of the i586 clock relative to the mc146818A + * clock failed. Do a less accurate calibration relative + * to the i8254 clock. + */ + unsigned long long i586_count; + + wrmsr(0x10, 0LL); /* XXX */ + DELAY(1000000); + i586_count = rdtsc(); + i586_ctr_rate = (i586_count << I586_CTR_RATE_SHIFT) / 1000000; + printf("i586 clock: %u Hz\n", i586_ctr_freq); + } +#endif +} + +#ifdef PC98 +void +rtc_serialcombit(int i) +{ + outb(IO_RTC, ((i&0x01)<<5)|0x07); + DELAY(1); + outb(IO_RTC, ((i&0x01)<<5)|0x17); + DELAY(1); + outb(IO_RTC, ((i&0x01)<<5)|0x07); + DELAY(1); +} + +void +rtc_serialcom(int i) +{ + rtc_serialcombit(i&0x01); + rtc_serialcombit((i&0x02)>>1); + rtc_serialcombit((i&0x04)>>2); + rtc_serialcombit((i&0x08)>>3); + outb(IO_RTC, 0x07); + DELAY(1); + outb(IO_RTC, 0x0f); + DELAY(1); + outb(IO_RTC, 0x07); + DELAY(1); +} + +void +rtc_outb(int val) +{ + int s; + int sa = 0; + + for (s=0;s<8;s++) { + sa = ((val >> s) & 0x01) ? 0x27 : 0x07; + outb(IO_RTC, sa); /* set DI & CLK 0 */ + DELAY(1); + outb(IO_RTC, sa | 0x10); /* CLK 1 */ + DELAY(1); + } + outb(IO_RTC, sa & 0xef); /* CLK 0 */ +} + +int +rtc_inb(void) +{ + int s; + int sa = 0; + + for (s=0;s<8;s++) { + sa |= ((inb(0x33) & 0x01) << s); + outb(IO_RTC, 0x17); /* CLK 1 */ + DELAY(1); + outb(IO_RTC, 0x07); /* CLK 0 */ + DELAY(2); + } + return sa; +} +#endif /* PC-98 */ + +/* + * Initialize the time of day register, based on the time base which is, e.g. + * from a filesystem. + */ +void +inittodr(time_t base) +{ + unsigned long sec, days; + int yd; + int year, month; + int y, m, s; +#ifdef PC98 + int second, min, hour; +#endif + + s = splclock(); + time.tv_sec = base; + time.tv_usec = 0; + splx(s); + +#ifdef PC98 + rtc_serialcom(0x03); /* Time Read */ + rtc_serialcom(0x01); /* Register shift command. */ + DELAY(20); + + second = bcd2bin(rtc_inb() & 0xff); /* sec */ + min = bcd2bin(rtc_inb() & 0xff); /* min */ + hour = bcd2bin(rtc_inb() & 0xff); /* hour */ + days = bcd2bin(rtc_inb() & 0xff) - 1; /* date */ + + month = (rtc_inb() >> 4) & 0x0f; /* month */ + for (m = 1; m < month; m++) + days += daysinmonth[m-1]; + year = bcd2bin(rtc_inb() & 0xff) + 1900; /* year */ + /* 2000 year problem */ + if (year < 1995) + year += 100; + if (year < 1970) + goto wrong_time; + for (y = 1970; y < year; y++) + days += DAYSPERYEAR + LEAPYEAR(y); + if ((month > 2) && LEAPYEAR(year)) + days ++; + sec = ((( days * 24 + + hour) * 60 + + min) * 60 + + second); + /* sec now contains the number of seconds, since Jan 1 1970, + in the local time zone */ +#else /* IBM-PC */ + /* Look if we have a RTC present and the time is valid */ + if (!(rtcin(RTC_STATUSD) & RTCSD_PWR)) + goto wrong_time; + + /* wait for time update to complete */ + /* If RTCSA_TUP is zero, we have at least 244us before next update */ + while (rtcin(RTC_STATUSA) & RTCSA_TUP); + + days = 0; +#ifdef USE_RTC_CENTURY + year = readrtc(RTC_YEAR) + readrtc(RTC_CENTURY) * 100; +#else + year = readrtc(RTC_YEAR) + 1900; + if (year < 1970) + year += 100; +#endif + if (year < 1970) + goto wrong_time; + month = readrtc(RTC_MONTH); + for (m = 1; m < month; m++) + days += daysinmonth[m-1]; + if ((month > 2) && LEAPYEAR(year)) + days ++; + days += readrtc(RTC_DAY) - 1; + yd = days; + for (y = 1970; y < year; y++) + days += DAYSPERYEAR + LEAPYEAR(y); + sec = ((( days * 24 + + readrtc(RTC_HRS)) * 60 + + readrtc(RTC_MIN)) * 60 + + readrtc(RTC_SEC)); + /* sec now contains the number of seconds, since Jan 1 1970, + in the local time zone */ +#endif + + sec += tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); + + s = splclock(); + time.tv_sec = sec; + splx(s); + return; + +wrong_time: + printf("Invalid time in real time clock.\n"); + printf("Check and reset the date immediately!\n"); +} + +/* + * Write system time back to RTC + */ +void +resettodr() +{ + unsigned long tm; + int y, m, s; +#ifdef PC98 + int wd; +#endif + + if (disable_rtc_set) + return; + + s = splclock(); + tm = time.tv_sec; + splx(s); + +#ifdef PC98 + rtc_serialcom(0x01); /* Register shift command. */ + + /* Calculate local time to put in RTC */ + + tm -= tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); + + rtc_outb(bin2bcd(tm%60)); tm /= 60; /* Write back Seconds */ + rtc_outb(bin2bcd(tm%60)); tm /= 60; /* Write back Minutes */ + rtc_outb(bin2bcd(tm%24)); tm /= 24; /* Write back Hours */ + + /* We have now the days since 01-01-1970 in tm */ + wd = (tm+4)%7; + for (y = 1970, m = DAYSPERYEAR + LEAPYEAR(y); + tm >= m; + y++, m = DAYSPERYEAR + LEAPYEAR(y)) + tm -= m; + + /* Now we have the years in y and the day-of-the-year in tm */ + for (m = 0; ; m++) { + int ml; + + ml = daysinmonth[m]; + if (m == 1 && LEAPYEAR(y)) + ml++; + if (tm < ml) + break; + tm -= ml; + } + + m++; + rtc_outb(bin2bcd(tm+1)); /* Write back Day */ + rtc_outb((m << 4) | wd); /* Write back Month & Weekday */ + rtc_outb(bin2bcd(y%100)); /* Write back Year */ + + rtc_serialcom(0x02); /* Time set & Counter hold command. */ + rtc_serialcom(0x00); /* Register hold command. */ +#else + /* Disable RTC updates and interrupts. */ + writertc(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR); + + /* Calculate local time to put in RTC */ + + tm -= tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); + + writertc(RTC_SEC, bin2bcd(tm%60)); tm /= 60; /* Write back Seconds */ + writertc(RTC_MIN, bin2bcd(tm%60)); tm /= 60; /* Write back Minutes */ + writertc(RTC_HRS, bin2bcd(tm%24)); tm /= 24; /* Write back Hours */ + + /* We have now the days since 01-01-1970 in tm */ + writertc(RTC_WDAY, (tm+4)%7); /* Write back Weekday */ + for (y = 1970, m = DAYSPERYEAR + LEAPYEAR(y); + tm >= m; + y++, m = DAYSPERYEAR + LEAPYEAR(y)) + tm -= m; + + /* Now we have the years in y and the day-of-the-year in tm */ + writertc(RTC_YEAR, bin2bcd(y%100)); /* Write back Year */ +#ifdef USE_RTC_CENTURY + writertc(RTC_CENTURY, bin2bcd(y/100)); /* ... and Century */ +#endif + for (m = 0; ; m++) { + int ml; + + ml = daysinmonth[m]; + if (m == 1 && LEAPYEAR(y)) + ml++; + if (tm < ml) + break; + tm -= ml; + } + + writertc(RTC_MONTH, bin2bcd(m + 1)); /* Write back Month */ + writertc(RTC_DAY, bin2bcd(tm + 1)); /* Write back Month Day */ + + /* Reenable RTC updates and interrupts. */ + writertc(RTC_STATUSB, rtc_statusb); +#endif +} + +/* + * Start both clocks running. + */ +void +cpu_initclocks() +{ +#ifndef PC98 + int diag; + + if (statclock_disable) { + /* + * The stat interrupt mask is different without the + * statistics clock. Also, don't set the interrupt + * flag which would normally cause the RTC to generate + * interrupts. + */ + stat_imask = HWI_MASK | SWI_MASK; + rtc_statusb = RTCSB_24HR; + } else { + /* Setting stathz to nonzero early helps avoid races. */ + stathz = RTC_NOPROFRATE; + profhz = RTC_PROFRATE; + } +#endif + + /* Finish initializing 8253 timer 0. */ + register_intr(/* irq */ 0, /* XXX id */ 0, /* flags */ 0, + /* XXX */ (inthand2_t *)clkintr, &clk_imask, + /* unit */ 0); + INTREN(IRQ0); +#if defined(I586_CPU) || defined(I686_CPU) + /* + * Finish setting up anti-jitter measures. + */ + if (i586_ctr_rate) { + i586_last_tick = rdtsc(); + i586_ctr_bias = i586_last_tick; + } +#endif + +#ifndef PC98 + /* Initialize RTC. */ + writertc(RTC_STATUSA, rtc_statusa); + writertc(RTC_STATUSB, RTCSB_24HR); + +static int +sysctl_machdep_i8254_freq SYSCTL_HANDLER_ARGS +{ + int error; + u_int freq; + + /* + * Use `i8254' instead of `timer' in external names because `timer' + * is is too generic. Should use it everywhere. + */ + freq = timer_freq; + error = sysctl_handle_opaque(oidp, &freq, sizeof freq, req); + if (error == 0 && freq != timer_freq) { + if (timer0_state != 0) + return (EBUSY); /* too much trouble to handle */ + set_timer_freq(freq, hz); + } + return (error); +} + +SYSCTL_PROC(_machdep, OID_AUTO, i8254_freq, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(u_int), sysctl_machdep_i8254_freq, "I", ""); + +#if defined(I586_CPU) || defined(I686_CPU) +static int +sysctl_machdep_i586_freq SYSCTL_HANDLER_ARGS +{ + int error; + u_int freq; + + if (i586_ctr_rate == 0) + return (EOPNOTSUPP); + freq = i586_ctr_freq; + error = sysctl_handle_opaque(oidp, &freq, sizeof freq, req); + if (error == 0 && freq != i586_ctr_freq) { + i586_ctr_freq = freq; + i586_ctr_rate = ((unsigned long long)freq << + I586_CTR_RATE_SHIFT) / 1000000; + } + return (error); +} + +SYSCTL_PROC(_machdep, OID_AUTO, i586_freq, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(u_int), sysctl_machdep_i586_freq, "I", ""); +#endif /* defined(I586_CPU) || defined(I686_CPU) */ + + /* Don't bother enabling the statistics clock. */ + if (statclock_disable) + return; + diag = rtcin(RTC_DIAG); + if (diag != 0) + printf("RTC BIOS diagnostic error %b\n", diag, RTCDG_BITS); + register_intr(/* irq */ 8, /* XXX id */ 1, /* flags */ 0, + /* XXX */ (inthand2_t *)rtcintr, &stat_imask, + /* unit */ 0); + INTREN(IRQ8); + writertc(RTC_STATUSB, rtc_statusb); +#endif +} + +void +setstatclockrate(int newhz) +{ +#ifndef PC98 + if (newhz == RTC_PROFRATE) + rtc_statusa = RTCSA_DIVIDER | RTCSA_PROF; + else + rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; + writertc(RTC_STATUSA, rtc_statusa); +#endif +} diff --git a/sys/pc98/cbus/fdc.c b/sys/pc98/cbus/fdc.c new file mode 100644 index 0000000..55a8354 --- /dev/null +++ b/sys/pc98/cbus/fdc.c @@ -0,0 +1,2489 @@ +/* + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Don Ahn. + * + * Copyright (c) 1993, 1994 by + * jc@irbs.UUCP (John Capo) + * vak@zebub.msk.su (Serge Vakulenko) + * ache@astral.msk.su (Andrew A. Chernov) + * + * Copyright (c) 1993, 1994, 1995 by + * joerg_wunsch@uriah.sax.de (Joerg Wunsch) + * dufault@hda.com (Peter Dufault) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)fd.c 7.4 (Berkeley) 5/25/91 + * $Id: fd.c,v 1.89 1996/05/03 20:15:11 phk Exp $ + * + */ + +#include "ft.h" +#if NFT < 1 +#undef NFDC +#endif +#include "fd.h" + +#if NFDC > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/ioctl.h> +#include <machine/clock.h> +#include <machine/ioctl_fd.h> +#include <sys/disklabel.h> +#include <sys/diskslice.h> +#include <sys/buf.h> +#include <sys/uio.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/syslog.h> +#include <sys/devconf.h> +#include <sys/dkstat.h> +#ifdef PC98 +#include <pc98/pc98/pc98.h> +#include <pc98/pc98/pc98_device.h> +#include <pc98/pc98/fdreg.h> +#include <pc98/pc98/fdc.h> +#else +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/fdreg.h> +#include <i386/isa/fdc.h> +#include <i386/isa/rtc.h> +#endif +#include <machine/stdarg.h> +#if NFT > 0 +#include <sys/ftape.h> +#ifdef PC98 +#include <pc98/pc98/ftreg.h> +#else +#include <i386/isa/ftreg.h> +#endif +#endif +#ifdef DEVFS +#include <sys/devfsext.h> +#endif + + +static int fd_goaway(struct kern_devconf *, int); +static int fdc_goaway(struct kern_devconf *, int); +static int fd_externalize(struct kern_devconf *, struct sysctl_req *); + +/* + * Templates for the kern_devconf structures used when we attach. + */ +static struct kern_devconf kdc_fd[NFD] = { { + 0, 0, 0, /* filled in by kern_devconf.c */ + "fd", 0, { MDDT_DISK, 0 }, + fd_externalize, 0, fd_goaway, DISK_EXTERNALLEN, + 0, /* parent */ + 0, /* parentdata */ + DC_UNCONFIGURED, /* state */ + "floppy disk", + DC_CLS_DISK /* class */ +} }; + +struct kern_devconf kdc_fdc[NFDC] = { { + 0, 0, 0, /* filled in by kern_devconf.c */ +#ifdef PC98 + "fdc", 0, { MDDT_PC98, 0, "bio" }, + pc98_generic_externalize, 0, fdc_goaway, PC98_EXTERNALLEN, +#else + "fdc", 0, { MDDT_ISA, 0, "bio" }, + isa_generic_externalize, 0, fdc_goaway, ISA_EXTERNALLEN, +#endif + 0, /* parent */ + 0, /* parentdata */ + DC_UNCONFIGURED, /* state */ + "floppy disk/tape controller", + DC_CLS_MISC /* class */ +} }; + +static inline void +fd_registerdev(int ctlr, int unit) +{ + if(unit != 0) + kdc_fd[unit] = kdc_fd[0]; + + kdc_fd[unit].kdc_unit = unit; + kdc_fd[unit].kdc_parent = &kdc_fdc[ctlr]; + kdc_fd[unit].kdc_parentdata = 0; + dev_attach(&kdc_fd[unit]); +} + +static inline void +#ifdef PC98 +fdc_registerdev(struct pc98_device *dvp) +#else +fdc_registerdev(struct isa_device *dvp) +#endif +{ + int unit = dvp->id_unit; + + if(unit != 0) + kdc_fdc[unit] = kdc_fdc[0]; + + kdc_fdc[unit].kdc_unit = unit; +#ifdef PC98 + kdc_fdc[unit].kdc_parent = &kdc_nec0; +#else + kdc_fdc[unit].kdc_parent = &kdc_isa0; +#endif + kdc_fdc[unit].kdc_parentdata = dvp; + dev_attach(&kdc_fdc[unit]); +} + +static int +fdc_goaway(struct kern_devconf *kdc, int force) +{ + if(force) { + dev_detach(kdc); + return 0; + } else { + return EBUSY; /* XXX fix */ + } +} + +static int +fd_goaway(struct kern_devconf *kdc, int force) +{ + dev_detach(kdc); + return 0; +} + +#define b_cylin b_resid /* XXX now spelled b_cylinder elsewhere */ + +/* misuse a flag to identify format operation */ +#define B_FORMAT B_XXX + +/* + * this biotab field doubles as a field for the physical unit number + * on the controller + */ +#define id_physid id_scsiid + +/* error returns for fd_cmd() */ +#define FD_FAILED -1 +#define FD_NOT_VALID -2 +#define FDC_ERRMAX 100 /* do not log more */ + +#ifdef PC98 +#define NUMTYPES 5 +#define NUMDENS NUMTYPES +#else +#define NUMTYPES 14 +#define NUMDENS (NUMTYPES - 6) +#endif + +/* These defines (-1) must match index for fd_types */ +#define F_TAPE_TYPE 0x020 /* bit for fd_types to indicate tape */ +#define NO_TYPE 0 /* must match NO_TYPE in ft.c */ +#ifdef PC98 +#define FDT_NONE 0 /* none present */ +#define FDT_12M 1 /* 1M/640K FDD */ +#define FDT_144M 2 /* 1.44M/1M/640K FDD */ + +#define FD_1200 1 +#define FD_1232 2 +#define FD_720 3 +#define FD_640 4 +#define FD_1440 5 +#else +#define FD_1720 1 +#define FD_1480 2 +#define FD_1440 3 +#define FD_1200 4 +#define FD_820 5 +#define FD_800 6 +#define FD_720 7 +#define FD_360 8 + +#define FD_1480in5_25 9 +#define FD_1440in5_25 10 +#define FD_820in5_25 11 +#define FD_800in5_25 12 +#define FD_720in5_25 13 +#define FD_360in5_25 14 +#endif + + +static struct fd_type fd_types[NUMTYPES] = +{ +#ifdef PC98 +{ 15,2,0xFF,0x1B,80,2400,1,0,2,0x54,1 }, /* 1.2 meg HD floppy */ +{ 8,3,0xFF,0x35,77,1232,1,0,2,0x74,1 }, /* 1.2 meg HD floppy 1024/sec */ +{ 9,2,0xFF,0x20,80,1440,1,1,2,0x50,1 }, /* 720k floppy in 1.2meg drive */ +{ 8,2,0xFF,0x2A,80,1280,1,1,2,0x50,1 }, /* 640k floppy in 1.2meg drive */ +{ 18,2,0xFF,0x1B,80,2880,1,2,2,0x54,1 }, /* 1.44 meg HD 3.5in floppy */ +#else +{ 21,2,0xFF,0x04,82,3444,1,FDC_500KBPS,2,0x0C,2 }, /* 1.72M in HD 3.5in */ +{ 18,2,0xFF,0x1B,82,2952,1,FDC_500KBPS,2,0x6C,1 }, /* 1.48M in HD 3.5in */ +{ 18,2,0xFF,0x1B,80,2880,1,FDC_500KBPS,2,0x6C,1 }, /* 1.44M in HD 3.5in */ +{ 15,2,0xFF,0x1B,80,2400,1,FDC_500KBPS,2,0x54,1 }, /* 1.2M in HD 5.25/3.5 */ +{ 10,2,0xFF,0x10,82,1640,1,FDC_250KBPS,2,0x2E,1 }, /* 820K in HD 3.5in */ +{ 10,2,0xFF,0x10,80,1600,1,FDC_250KBPS,2,0x2E,1 }, /* 800K in HD 3.5in */ +{ 9,2,0xFF,0x20,80,1440,1,FDC_250KBPS,2,0x50,1 }, /* 720K in HD 3.5in */ +{ 9,2,0xFF,0x2A,40, 720,1,FDC_250KBPS,2,0x50,1 }, /* 360K in DD 5.25in */ + +{ 18,2,0xFF,0x02,82,2952,1,FDC_500KBPS,2,0x02,2 }, /* 1.48M in HD 5.25in */ +{ 18,2,0xFF,0x02,80,2880,1,FDC_500KBPS,2,0x02,2 }, /* 1.44M in HD 5.25in */ +{ 10,2,0xFF,0x10,82,1640,1,FDC_300KBPS,2,0x2E,1 }, /* 820K in HD 5.25in */ +{ 10,2,0xFF,0x10,80,1600,1,FDC_300KBPS,2,0x2E,1 }, /* 800K in HD 5.25in */ +{ 9,2,0xFF,0x20,80,1440,1,FDC_300KBPS,2,0x50,1 }, /* 720K in HD 5.25in */ +{ 9,2,0xFF,0x23,40, 720,2,FDC_300KBPS,2,0x50,1 }, /* 360K in HD 5.25in */ +#endif +}; + +#ifdef PC98 +#define DRVS_PER_CTLR 4 /* 4 floppies */ +#else +#define DRVS_PER_CTLR 2 /* 2 floppies */ +#endif + +/***********************************************************************\ +* Per controller structure. * +\***********************************************************************/ +struct fdc_data fdc_data[NFDC]; + +/***********************************************************************\ +* Per drive structure. * +* N per controller (DRVS_PER_CTLR) * +\***********************************************************************/ +static struct fd_data { + struct fdc_data *fdc; /* pointer to controller structure */ + int fdsu; /* this units number on this controller */ + int type; /* Drive type (FD_1440...) */ + struct fd_type *ft; /* pointer to the type descriptor */ + int flags; +#define FD_OPEN 0x01 /* it's open */ +#define FD_ACTIVE 0x02 /* it's active */ +#define FD_MOTOR 0x04 /* motor should be on */ +#define FD_MOTOR_WAIT 0x08 /* motor coming up */ + int skip; + int hddrv; +#define FD_NO_TRACK -2 + int track; /* where we think the head is */ + int options; /* user configurable options, see ioctl_fd.h */ + int dkunit; /* disk stats unit number */ +#ifdef DEVFS + void *bdevs[1 + NUMDENS + MAXPARTITIONS]; + void *cdevs[1 + NUMDENS + MAXPARTITIONS]; +#endif +#ifdef PC98 + int pc98_trans; +#endif +} fd_data[NFD]; + +#ifdef EPSON_NRDISK +typedef unsigned int nrd_t; + +#define P_NRD_ADDRH 0xc24 +#define P_NRD_ADDRM 0xc22 +#define P_NRD_ADDRL 0xc20 +#define P_NRD_CHECK 0xc20 +#define P_NRD_DATA 0xc26 +#define P_NRD_LED 0xc36 +#define B_NRD_CHK 0x80 +#define B_NRD_LED 0x40 +#define A_NRD_INFO 0x2 +#define A_NRD_BASE 0x400 +#define NRD_STATUS 0x0 +#define NRD_ST0_HD 0x04 + +static fdu_t nrdu=-1; +static int nrdsec=0; +static nrd_t nrdblkn=0; +static nrd_t nrdaddr=0x0; + +#define nrd_check_ready() ({ \ + (epson_inb(P_NRD_CHECK) & B_NRD_CHK) ? 0 : 1; \ + }) +#define nrd_LED_on() epson_outb(P_NRD_LED, B_NRD_LED) +#define nrd_LED_off() epson_outb(P_NRD_LED, ~B_NRD_LED) +#define nrd_trac() ((int)(nrd_info(nrdaddr) & 0xff)) +#define nrd_head() ((int)((nrd_info(nrdaddr) >> 8) & 0xff)) +#define nrd_sec() ((int)(nrd_info(nrdaddr + 2) & 0xff)) +#define nrd_secsize() ((int)((nrd_info(A_NRD_INFO) >> 8) & 0xff)) +#define nrd_addrset(p) nrd_addr((nrd_t)((nrd_t)p+A_NRD_BASE)) + +static inline void +nrd_addr(addr) + nrd_t addr; +{ + epson_outb(P_NRD_ADDRH, (u_char)((addr >> 16) & 0x1f)); + epson_outb(P_NRD_ADDRM, (u_char)((addr >> 8) & 0xff)); + epson_outb(P_NRD_ADDRL, (u_char)(addr & 0xff)); +} + +static inline u_short +nrd_info(addr) + nrd_t addr; +{ + u_short tmp; + + nrd_addr(addr); + outb(0x43f, 0x42); + tmp = (short)inw(P_NRD_DATA); + outb(0x43f, 0x40); + return ((u_short)tmp); +} +#endif /* EPSON_NRDISK */ + +/***********************************************************************\ +* Throughout this file the following conventions will be used: * +* fd is a pointer to the fd_data struct for the drive in question * +* fdc is a pointer to the fdc_data struct for the controller * +* fdu is the floppy drive unit number * +* fdcu is the floppy controller unit number * +* fdsu is the floppy drive unit number on that controller. (sub-unit) * +\***********************************************************************/ + +#if NFT > 0 +int ftopen(dev_t, int); +int ftintr(ftu_t ftu); +int ftclose(dev_t, int); +void ftstrategy(struct buf *); +int ftioctl(dev_t, int, caddr_t, int, struct proc *); +int ftdump(dev_t); +int ftsize(dev_t); +#ifdef PC98 +int ftattach(struct pc98_device *, struct pc98_device *, int); +#else +int ftattach(struct isa_device *, struct isa_device *, int); +#endif +#endif + +/* autoconfig functions */ +#ifdef PC98 +static int fdprobe(struct pc98_device *); +static int fdattach(struct pc98_device *); +#else +static int fdprobe(struct isa_device *); +static int fdattach(struct isa_device *); +#endif + +/* needed for ft driver, thus exported */ +int in_fdc(fdcu_t); +int out_fdc(fdcu_t, int); + +/* internal functions */ +static void set_motor(fdcu_t, int, int); +# define TURNON 1 +# define TURNOFF 0 +static timeout_t fd_turnoff; +static timeout_t fd_motor_on; +static void fd_turnon(fdu_t); +static void fdc_reset(fdc_p); +static int fd_in(fdcu_t, int *); +static void fdstart(fdcu_t); +static timeout_t fd_timeout; +static timeout_t fd_pseudointr; +static int fdstate(fdcu_t, fdc_p); +static int retrier(fdcu_t); +static int fdformat(dev_t, struct fd_formb *, struct proc *); + + +#define DEVIDLE 0 +#define FINDWORK 1 +#define DOSEEK 2 +#define SEEKCOMPLETE 3 +#define IOCOMPLETE 4 +#define RECALCOMPLETE 5 +#define STARTRECAL 6 +#define RESETCTLR 7 +#define SEEKWAIT 8 +#define RECALWAIT 9 +#define MOTORWAIT 10 +#define IOTIMEDOUT 11 + +#ifdef DEBUG +char *fdstates[] = +{ +"DEVIDLE", +"FINDWORK", +"DOSEEK", +"SEEKCOMPLETE", +"IOCOMPLETE", +"RECALCOMPLETE", +"STARTRECAL", +"RESETCTLR", +"SEEKWAIT", +"RECALWAIT", +"MOTORWAIT", +"IOTIMEDOUT" +}; + +/* CAUTION: fd_debug causes huge amounts of logging output */ +int fd_debug = 0; +#define TRACE0(arg) if(fd_debug) printf(arg) +#define TRACE1(arg1, arg2) if(fd_debug) printf(arg1, arg2) +#else /* DEBUG */ +#define TRACE0(arg) +#define TRACE1(arg1, arg2) +#endif /* DEBUG */ + +/* autoconfig structure */ +#ifdef PC98 +struct pc98_driver fdcdriver = { +#else +struct isa_driver fdcdriver = { +#endif + fdprobe, fdattach, "fdc", +}; + +static d_open_t Fdopen; /* NOTE, not fdopen */ +static d_close_t fdclose; +static d_ioctl_t fdioctl; +static d_strategy_t fdstrategy; + +#define CDEV_MAJOR 9 +#define BDEV_MAJOR 2 +extern struct cdevsw fd_cdevsw; +static struct bdevsw fd_bdevsw = + { Fdopen, fdclose, fdstrategy, fdioctl, /*2*/ + nodump, nopsize, 0, "fd", &fd_cdevsw, -1 }; + +static struct cdevsw fd_cdevsw = + { Fdopen, fdclose, rawread, rawwrite, /*9*/ + fdioctl, nostop, nullreset, nodevtotty, + seltrue, nommap, fdstrategy, "fd", + &fd_bdevsw, -1 }; + +#ifdef PC98 +static struct pc98_device *fdcdevs[NFDC]; +#else +static struct isa_device *fdcdevs[NFDC]; +#endif + +/* + * Provide hw.devconf information. + */ +static int +fd_externalize(struct kern_devconf *kdc, struct sysctl_req *req) +{ + return disk_externalize(fd_data[kdc->kdc_unit].fdsu, req); +} + +static int +fdc_err(fdcu_t fdcu, const char *s) +{ + fdc_data[fdcu].fdc_errs++; + if(s) { + if(fdc_data[fdcu].fdc_errs < FDC_ERRMAX) + printf("fdc%d: %s", fdcu, s); + else if(fdc_data[fdcu].fdc_errs == FDC_ERRMAX) + printf("fdc%d: too many errors, not logging any more\n", + fdcu); + } + + return FD_FAILED; +} + +/* + * fd_cmd: Send a command to the chip. Takes a varargs with this structure: + * Unit number, + * # of output bytes, output bytes as ints ..., + * # of input bytes, input bytes as ints ... + */ + +static int +fd_cmd(fdcu_t fdcu, int n_out, ...) +{ + u_char cmd; + int n_in; + int n; + va_list ap; + + va_start(ap, n_out); + cmd = (u_char)(va_arg(ap, int)); + va_end(ap); + va_start(ap, n_out); + for (n = 0; n < n_out; n++) + { + if (out_fdc(fdcu, va_arg(ap, int)) < 0) + { + char msg[50]; + sprintf(msg, + "cmd %x failed at out byte %d of %d\n", + cmd, n + 1, n_out); + return fdc_err(fdcu, msg); + } + } + n_in = va_arg(ap, int); + for (n = 0; n < n_in; n++) + { + int *ptr = va_arg(ap, int *); + if (fd_in(fdcu, ptr) < 0) + { + char msg[50]; + sprintf(msg, + "cmd %02x failed at in byte %d of %d\n", + cmd, n + 1, n_in); + return fdc_err(fdcu, msg); + } + } + + return 0; +} + +static int +fd_sense_drive_status(fdc_p fdc, int *st3p) +{ + int st3; + + if (fd_cmd(fdc->fdcu, 2, NE7CMD_SENSED, fdc->fdu, 1, &st3)) + { + return fdc_err(fdc->fdcu, "Sense Drive Status failed\n"); + } + if (st3p) + *st3p = st3; + + return 0; +} + +static int +fd_sense_int(fdc_p fdc, int *st0p, int *cylp) +{ + int st0, cyl; + +#ifdef EPSON_NRDISK + if (fdc->fdu == nrdu) { + if (fdc->fd->track >= 0) nrdaddr = (fdc->fd->track + 1) * 8; + else nrdaddr = 0x0; + *st0p = nrd_head() ? NRD_ST0_HD : NRD_STATUS; + *cylp = nrd_trac(); + } + else { +#endif /* EPSON_NRDISK */ + int ret = fd_cmd(fdc->fdcu, 1, NE7CMD_SENSEI, 1, &st0); + + if (ret) + { + (void)fdc_err(fdc->fdcu, + "sense intr err reading stat reg 0\n"); + return ret; + } + + if (st0p) + *st0p = st0; + + if ((st0 & NE7_ST0_IC) == NE7_ST0_IC_IV) + { + /* + * There doesn't seem to have been an interrupt. + */ + return FD_NOT_VALID; + } + + if (fd_in(fdc->fdcu, &cyl) < 0) + { + return fdc_err(fdc->fdcu, "can't get cyl num\n"); + } + + if (cylp) + *cylp = cyl; + +#ifdef EPSON_NRDISK + } +#endif /* EPSON_NRDISK */ + return 0; +} + + +static int +fd_read_status(fdc_p fdc, int fdsu) +{ + int i, ret; + + for (i = 0; i < 7; i++) + { + /* + * XXX types are poorly chosen. Only bytes can by read + * from the hardware, but fdc_status wants u_longs and + * fd_in() gives ints. + */ + int status; + +#ifdef EPSON_NRDISK + if (fdc->fdu == nrdu) { + switch (i) { + case 0: fdc->status[i] = nrd_head() + ? NRD_ST0_HD : NRD_STATUS; break; + case 1: fdc->status[i] = NRD_STATUS; break; + case 2: fdc->status[i] = NRD_STATUS; break; + case 3: fdc->status[i] = nrd_trac(); break; + case 4: fdc->status[i] = nrd_head(); break; + case 5: fdc->status[i] = nrdsec; break; + case 6: fdc->status[i] = nrd_secsize(); break; + } + ret = 0; + } + else { +#endif /* EPSON_NRDISK */ + ret = fd_in(fdc->fdcu, &status); + fdc->status[i] = status; + if (ret != 0) + break; +#ifdef EPSON_NRDISK + } +#endif /* EPSON_NRDISK */ + } + + if (ret == 0) + fdc->flags |= FDC_STAT_VALID; + else + fdc->flags &= ~FDC_STAT_VALID; + + return ret; +} + +/****************************************************************************/ +/* autoconfiguration stuff */ +/****************************************************************************/ +#ifdef PC98 +static int pc98_trans = 0; /* 0 : HD , 1 : DD , 2 : 1.44 */ +static int pc98_trans_prev = 0; + +static void set_density(fdcu_t, fdu_t); +static int pc98_fd_check_ready(fdu_t); + +static void set_density(fdcu, fdu) + fdcu_t fdcu; + fdu_t fdu; +{ + /* always motor on */ + outb(IO_FDPORT, + (pc98_trans != 1 ? FDP_FDDEXC : 0) | FDP_PORTEXC); + DELAY(100); + outb(fdc_data[fdcu].baseport + FDOUT, FDO_RST | FDO_DMAE); + /* in the case of note W, always inhibit 100ms timer */ +} + +static int pc98_fd_check_ready(fdu) + fdu_t fdu; +{ + fd_p fd = fd_data + fdu; + fdcu_t fdcu = fd->fdc->fdcu; + int retry = 0; + +#ifdef EPSON_NRDISK + if (fdu == nrdu) { + if (nrd_check_ready()) return 0; + else return -1; + } +#endif + while (retry++ < 30000) { + set_motor(fdcu, fd->fdsu, TURNON); + out_fdc(fdcu, NE7CMD_SENSED); /* Sense Drive Status */ + DELAY(100); + out_fdc(fdcu, fdu); /* Drive number */ + DELAY(100); + if ((in_fdc(fdcu) & NE7_ST3_RD)){ + outb(fdc_data[fdcu].baseport + FDOUT, + FDO_DMAE | FDO_MTON); + DELAY(10); + return 0; + } + } + return -1; +} +#endif + + +/* + * probe for existance of controller + */ +static int +#ifdef PC98 +fdprobe(struct pc98_device *dev) +#else +fdprobe(struct isa_device *dev) +#endif +{ + fdcu_t fdcu = dev->id_unit; + if(fdc_data[fdcu].flags & FDC_ATTACHED) + { + printf("fdc%d: unit used multiple times\n", fdcu); + return 0; + } + + fdcdevs[fdcu] = dev; + fdc_data[fdcu].baseport = dev->id_iobase; + +#ifndef DEV_LKM + fdc_registerdev(dev); +#endif + +#ifndef PC98 + /* First - lets reset the floppy controller */ + outb(dev->id_iobase+FDOUT, 0); + DELAY(100); + outb(dev->id_iobase+FDOUT, FDO_FRST); +#endif + + /* see if it can handle a command */ +#ifdef PC98 + if (fd_cmd(fdcu, + 3, NE7CMD_SPECIFY, NE7_SPEC_1(4, 240), NE7_SPEC_2(2, 0), + 0)) +#else + if (fd_cmd(fdcu, + 3, NE7CMD_SPECIFY, NE7_SPEC_1(3, 240), NE7_SPEC_2(2, 0), + 0)) +#endif + { + return(0); + } + kdc_fdc[fdcu].kdc_state = DC_IDLE; + return (IO_FDCSIZE); +} + +/* + * wire controller into system, look for floppy units + */ +static int +#ifdef PC98 +fdattach(struct pc98_device *dev) +#else +fdattach(struct isa_device *dev) +#endif +{ + unsigned fdt; + fdu_t fdu; + fdcu_t fdcu = dev->id_unit; + fdc_p fdc = fdc_data + fdcu; + fd_p fd; + int fdsu, st0, st3, i, unithasfd; +#ifdef PC98 + struct pc98_device *fdup; +#else + struct isa_device *fdup; +#endif + int ic_type = 0; +#ifdef DEVFS + int mynor; + int typemynor; + int typesize; +#endif + + fdc->fdcu = fdcu; + fdc->flags |= FDC_ATTACHED; +#ifdef PC98 + fdc->dmachan = 2; + if (fdc->dmachan != dev->id_drq) { + dev->id_drq = fdc->dmachan; + printf(" [dma is changed to #%d]", fdc->dmachan); + } + /* Acquire the DMA channel forever, The driver will do the rest */ + pc98_dma_acquire(fdc->dmachan); + pc98_dmainit(fdc->dmachan, 128 << 3 /* XXX max secsize */); + fdc->state = DEVIDLE; + fdc_reset(fdc); +#else + fdc->dmachan = dev->id_drq; + /* Acquire the DMA channel forever, The driver will do the rest */ + isa_dma_acquire(fdc->dmachan); + isa_dmainit(fdc->dmachan, 128 << 3 /* XXX max secsize */); + fdc->state = DEVIDLE; + /* reset controller, turn motor off, clear fdout mirror reg */ + outb(fdc->baseport + FDOUT, ((fdc->fdout = 0))); +#endif + TAILQ_INIT(&fdc->head); + + /* check for each floppy drive */ +#ifdef PC98 + for (fdup = pc98_biotab_fdc; fdup->id_driver != 0; fdup++) { +#else + for (fdup = isa_biotab_fdc; fdup->id_driver != 0; fdup++) { +#endif + if (fdup->id_iobase != dev->id_iobase) + continue; + fdu = fdup->id_unit; + fd = &fd_data[fdu]; + if (fdu >= (NFD+NFT)) + continue; + fdsu = fdup->id_physid; + /* look up what bios thinks we have */ + switch (fdu) { +#ifdef PC98 + case 0: case 1: case 2: case 3: + if ((PC98_SYSTEM_PARAMETER(0x5ae) >> fdu) & 0x01) + fdt = FDT_144M; +#ifdef EPSON_NRDISK + else if ((PC98_SYSTEM_PARAMETER(0x55c) >> fdu) & 0x01) { + fdt = FDT_12M; + switch (epson_machine_id) { + case 0x20: case 0x27: + if ((PC98_SYSTEM_PARAMETER(0x488) >> fdu) & 0x01) { + if (nrd_check_ready()) { + nrd_LED_on(); + nrdu = fdu; + } + else fdt = FDT_NONE; + } + } + } +#else /* !EPSON_NRDISK */ + else if ((PC98_SYSTEM_PARAMETER(0x55c) >> fdu) & 0x01) { + fdt = FDT_12M; + switch (epson_machine_id) { + case 0x20: case 0x27: + if ((PC98_SYSTEM_PARAMETER(0x488) >> fdu) & 0x01) + fdt = FDT_NONE; + } + } +#endif /* EPSON_NRDISK */ + else fdt = FDT_NONE; + break; + default: + fdt = FDT_NONE; + break; +#else + case 0: fdt = (rtcin(RTC_FDISKETTE) & 0xf0); + break; + case 1: fdt = ((rtcin(RTC_FDISKETTE) << 4) & 0xf0); + break; + default: fdt = RTCFDT_NONE; + break; +#endif + } + /* is there a unit? */ +#ifdef PC98 + if ((fdt == FDT_NONE) +#else + if ((fdt == RTCFDT_NONE) +#endif +#if NFT > 0 + || (fdsu >= DRVS_PER_CTLR)) { +#else + ) { +#ifdef PC98 + fd->fdc = fdc; +#endif + fd->type = NO_TYPE; +#endif +#if NFT > 0 + /* If BIOS says no floppy, or > 2nd device */ + /* Probe for and attach a floppy tape. */ + /* Tell FT if there was already a disk */ + /* with this unit number found. */ + + unithasfd = 0; + if (fdu < NFD && fd->type != NO_TYPE) + unithasfd = 1; + if (ftattach(dev, fdup, unithasfd)) + continue; + if (fdsu < DRVS_PER_CTLR) + fd->type = NO_TYPE; +#endif + continue; + } + +#ifdef PC98 + kdc_fdc[fdcu].kdc_description = + "NEC 765 floppy disk/tape controller"; +#else + /* select it */ + set_motor(fdcu, fdsu, TURNON); + DELAY(1000000); /* 1 sec */ + + if (ic_type == 0 && + fd_cmd(fdcu, 1, NE7CMD_VERSION, 1, &ic_type) == 0) + { + printf("fdc%d: ", fdcu); + ic_type = (u_char)ic_type; + switch( ic_type ) { + case 0x80: + printf("NEC 765\n"); + fdc->fdct = FDC_NE765; + kdc_fdc[fdcu].kdc_description = + "NEC 765 floppy disk/tape controller"; + break; + case 0x81: + printf("Intel 82077\n"); + fdc->fdct = FDC_I82077; + kdc_fdc[fdcu].kdc_description = + "Intel 82077 floppy disk/tape controller"; + break; + case 0x90: + printf("NEC 72065B\n"); + fdc->fdct = FDC_NE72065; + kdc_fdc[fdcu].kdc_description = + "NEC 72065B floppy disk/tape controller"; + break; + default: + printf("unknown IC type %02x\n", ic_type); + fdc->fdct = FDC_UNKNOWN; + break; + } + } + if ((fd_cmd(fdcu, 2, NE7CMD_SENSED, fdsu, 1, &st3) == 0) && + (st3 & NE7_ST3_T0)) { + /* if at track 0, first seek inwards */ + /* seek some steps: */ + (void)fd_cmd(fdcu, 3, NE7CMD_SEEK, fdsu, 10, 0); + DELAY(300000); /* ...wait a moment... */ + (void)fd_sense_int(fdc, 0, 0); /* make ctrlr happy */ + } + + /* If we're at track 0 first seek inwards. */ + if ((fd_sense_drive_status(fdc, &st3) == 0) && + (st3 & NE7_ST3_T0)) { + /* Seek some steps... */ + if (fd_cmd(fdcu, 3, NE7CMD_SEEK, fdsu, 10, 0) == 0) { + /* ...wait a moment... */ + DELAY(300000); + /* make ctrlr happy: */ + (void)fd_sense_int(fdc, 0, 0); + } + } + + for(i = 0; i < 2; i++) { + /* + * we must recalibrate twice, just in case the + * heads have been beyond cylinder 76, since most + * FDCs still barf when attempting to recalibrate + * more than 77 steps + */ + /* go back to 0: */ + if (fd_cmd(fdcu, 2, NE7CMD_RECAL, fdsu, 0) == 0) { + /* a second being enough for full stroke seek*/ + DELAY(i == 0? 1000000: 300000); + + /* anything responding? */ + if (fd_sense_int(fdc, &st0, 0) == 0 && + (st0 & NE7_ST0_EC) == 0) + break; /* already probed succesfully */ + } + } + + set_motor(fdcu, fdsu, TURNOFF); + + if (st0 & NE7_ST0_EC) /* no track 0 -> no drive present */ + continue; +#endif + + fd->track = FD_NO_TRACK; + fd->fdc = fdc; + fd->fdsu = fdsu; + fd->options = 0; + printf("fd%d: ", fdu); + + fd_registerdev(fdcu, fdu); + switch (fdt) { +#ifdef PC98 + case FDT_12M: +#ifdef EPSON_NRDISK + if (fdu == nrdu) { + printf("EPSON RAM DRIVE\n"); + nrd_LED_off(); + } + else printf("1M/640M FDD\n"); +#else /* !EPSON_NRDISK */ + printf("1M/640K FDD\n"); +#endif /* EPSON_NRDISK */ + fd->type = FD_1200; + fd->pc98_trans = 0; + kdc_fd[fdu].kdc_description = + "1M/640K floppy disk drive"; +#ifdef DEVFS + sprintf(name,"rfd%d.1200",fdu); +#endif /* DEVFS */ + break; + case FDT_144M: + printf("1.44M FDD\n"); + fd->type = FD_1200; + fd->pc98_trans = 0; + outb(0x4be, (fdu << 5) | 0x10); + kdc_fd[fdu].kdc_description = + "1.44MB (1440K) 3.5in floppy disk drive"; +#ifdef DEVFS + sprintf(name,"rfd%d.1440",fdu); +#endif /* DEVFS */ + break; +#else + case RTCFDT_12M: + printf("1.2MB 5.25in\n"); + fd->type = FD_1200; + kdc_fd[fdu].kdc_description = + "1.2MB (1200K) 5.25in floppy disk drive"; + break; + case RTCFDT_144M: + printf("1.44MB 3.5in\n"); + fd->type = FD_1440; + kdc_fd[fdu].kdc_description = + "1.44MB (1440K) 3.5in floppy disk drive"; + break; + case RTCFDT_288M: + case RTCFDT_288M_1: + printf("2.88MB 3.5in - 1.44MB mode\n"); + fd->type = FD_1440; + kdc_fd[fdu].kdc_description = + "2.88MB (2880K) 3.5in floppy disk drive in 1.44 mode"; + break; + case RTCFDT_360K: + printf("360KB 5.25in\n"); + fd->type = FD_360; + kdc_fd[fdu].kdc_description = + "360KB 5.25in floppy disk drive"; + break; + case RTCFDT_720K: + printf("720KB 3.5in\n"); + fd->type = FD_720; + kdc_fd[fdu].kdc_description = + "720KB 3.5in floppy disk drive"; + break; +#endif + default: + printf("unknown\n"); + fd->type = NO_TYPE; + dev_detach(&kdc_fd[fdu]); + continue; + } + kdc_fd[fdu].kdc_state = DC_IDLE; +#ifdef DEVFS + mynor = fdu << 6; + fd->bdevs[0] = devfs_add_devswf(&fd_bdevsw, mynor, DV_BLK, + UID_ROOT, GID_OPERATOR, 0640, + "fd%d", fdu); + fd->cdevs[0] = devfs_add_devswf(&fd_cdevsw, mynor, DV_CHR, + UID_ROOT, GID_OPERATOR, 0640, + "rfd%d", fdu); + for (i = 1; i < 1 + NUMDENS; i++) { + /* + * XXX this and the lookup in Fdopen() should be + * data driven. + */ + switch (fd->type) { + case FD_360: + if (i != FD_360) + continue; + break; + case FD_720: + if (i != FD_720 && i != FD_800 && i != FD_820) + continue; + break; + case FD_1200: + if (i != FD_360 && i != FD_720 && i != FD_800 + && i != FD_820 && i != FD_1200 + && i != FD_1440 && i != FD_1480) + continue; + break; + case FD_1440: + if (i != FD_720 && i != FD_800 && i != FD_820 + && i != FD_1200 && i != FD_1440 + && i != FD_1480 && i != FD_1720) + continue; + break; + } + typemynor = mynor | i; + typesize = fd_types[i - 1].size / 2; + /* + * XXX all these conversions give bloated code and + * confusing names. + */ + if (typesize == 1476) + typesize = 1480; + if (typesize == 1722) + typesize = 1720; + fd->bdevs[i] = + devfs_add_devswf(&fd_bdevsw, typemynor, DV_BLK, + UID_ROOT, GID_OPERATOR, 0640, + "fd%d.%d", fdu, typesize); + fd->cdevs[i] = + devfs_add_devswf(&fd_cdevsw, typemynor, DV_CHR, + UID_ROOT, GID_OPERATOR, 0640, + "rfd%d.%d", fdu, typesize); + } + for (i = 0; i < MAXPARTITIONS; i++) { + fd->bdevs[1 + NUMDENS + i] = + devfs_link(fd->bdevs[0], + "fd%d%c", fdu, 'a' + i); + fd->cdevs[1 + NUMDENS + i] = + devfs_link(fd->cdevs[0], + "rfd%d%c", fdu, 'a' + i); + } +#endif /* DEVFS */ + if (dk_ndrive < DK_NDRIVE) { + sprintf(dk_names[dk_ndrive], "fd%d", fdu); + fd->dkunit = dk_ndrive++; + /* + * XXX assume rate is FDC_500KBPS. + */ + dk_wpms[dk_ndrive] = 500000 / 8 / 2; + } else { + fd->dkunit = -1; + } + } + + return (1); +} + +/****************************************************************************/ +/* motor control stuff */ +/* remember to not deselect the drive we're working on */ +/****************************************************************************/ +static void +set_motor(fdcu_t fdcu, int fdsu, int turnon) +{ + int fdout = fdc_data[fdcu].fdout; + int needspecify = 0; + +#ifdef PC98 + outb(IO_FDPORT, (pc98_trans != 1 ? FDP_FDDEXC : 0)|FDP_PORTEXC); + DELAY(10); + fdout = FDO_DMAE|FDO_MTON; +#else + if(turnon) { + fdout &= ~FDO_FDSEL; + fdout |= (FDO_MOEN0 << fdsu) + fdsu; + } else + fdout &= ~(FDO_MOEN0 << fdsu); + + if(!turnon + && (fdout & (FDO_MOEN0+FDO_MOEN1+FDO_MOEN2+FDO_MOEN3)) == 0) + /* gonna turn off the last drive, put FDC to bed */ + fdout &= ~ (FDO_FRST|FDO_FDMAEN); + else { + /* make sure controller is selected and specified */ + if((fdout & (FDO_FRST|FDO_FDMAEN)) == 0) + needspecify = 1; + fdout |= (FDO_FRST|FDO_FDMAEN); + } +#endif + + outb(fdc_data[fdcu].baseport+FDOUT, fdout); + DELAY(10); + fdc_data[fdcu].fdout = fdout; +#ifndef PC98 + kdc_fdc[fdcu].kdc_state = (fdout & FDO_FRST)? DC_BUSY: DC_IDLE; +#endif + TRACE1("[0x%x->FDOUT]", fdout); + + if(needspecify) { + /* + * XXX + * special case: since we have just woken up the FDC + * from its sleep, we silently assume the command will + * be accepted, and do not test for a timeout + */ +#ifdef PC98 + (void)fd_cmd(fdcu, 3, NE7CMD_SPECIFY, + NE7_SPEC_1(4, 240), NE7_SPEC_2(2, 0), + 0); +#else + (void)fd_cmd(fdcu, 3, NE7CMD_SPECIFY, + NE7_SPEC_1(3, 240), NE7_SPEC_2(2, 0), + 0); +#endif + } +} + +static void +fd_turnoff(void *arg1) +{ + fdu_t fdu = (fdu_t)arg1; + int s; + fd_p fd = fd_data + fdu; + + TRACE1("[fd%d: turnoff]", fdu); + + /* + * Don't turn off the motor yet if the drive is active. + * XXX shouldn't even schedule turnoff until drive is inactive + * and nothing is queued on it. + */ + if (fd->fdc->state != DEVIDLE && fd->fdc->fdu == fdu) { + timeout(fd_turnoff, arg1, 4 * hz); + return; + } + + s = splbio(); + fd->flags &= ~FD_MOTOR; + set_motor(fd->fdc->fdcu, fd->fdsu, TURNOFF); + splx(s); +} + +static void +fd_motor_on(void *arg1) +{ + fdu_t fdu = (fdu_t)arg1; + int s; + + fd_p fd = fd_data + fdu; + s = splbio(); + fd->flags &= ~FD_MOTOR_WAIT; + if((fd->fdc->fd == fd) && (fd->fdc->state == MOTORWAIT)) + { + fdintr(fd->fdc->fdcu); + } + splx(s); +} + +static void +fd_turnon(fdu_t fdu) +{ + fd_p fd = fd_data + fdu; + if(!(fd->flags & FD_MOTOR)) + { + fd->flags |= (FD_MOTOR + FD_MOTOR_WAIT); + set_motor(fd->fdc->fdcu, fd->fdsu, TURNON); + timeout(fd_motor_on, (caddr_t)fdu, hz); /* in 1 sec its ok */ + } +} + +static void +fdc_reset(fdc_p fdc) +{ + fdcu_t fdcu = fdc->fdcu; + + /* Try a reset, keep motor on */ +#ifdef PC98 + set_density(fdcu, 0); + if (pc98_machine_type & M_EPSON_PC98) + outb(fdc->baseport + FDOUT, 0xe8); + else + outb(fdc->baseport + FDOUT, 0xd8); + DELAY(200); + outb(fdc->baseport + FDOUT, 0x18); + DELAY(10); +#else + outb(fdc->baseport + FDOUT, fdc->fdout & ~(FDO_FRST|FDO_FDMAEN)); + TRACE1("[0x%x->FDOUT]", fdc->fdout & ~(FDO_FRST|FDO_FDMAEN)); + DELAY(100); + /* enable FDC, but defer interrupts a moment */ + outb(fdc->baseport + FDOUT, fdc->fdout & ~FDO_FDMAEN); + TRACE1("[0x%x->FDOUT]", fdc->fdout & ~FDO_FDMAEN); + DELAY(100); + outb(fdc->baseport + FDOUT, fdc->fdout); + TRACE1("[0x%x->FDOUT]", fdc->fdout); +#endif + + /* XXX after a reset, silently believe the FDC will accept commands */ +#ifdef PC98 + (void)fd_cmd(fdcu, 3, NE7CMD_SPECIFY, + NE7_SPEC_1(4, 240), NE7_SPEC_2(2, 0), + 0); +#else + (void)fd_cmd(fdcu, 3, NE7CMD_SPECIFY, + NE7_SPEC_1(3, 240), NE7_SPEC_2(2, 0), + 0); +#endif +} + +/****************************************************************************/ +/* fdc in/out */ +/****************************************************************************/ +int +in_fdc(fdcu_t fdcu) +{ + int baseport = fdc_data[fdcu].baseport; + int i, j = 1000000; + while ((i = inb(baseport+FDSTS) & (NE7_DIO|NE7_RQM)) + != (NE7_DIO|NE7_RQM) && j-- > 0) { + if (i == NE7_RQM) + return fdc_err(fdcu, "ready for output in input\n"); + } + if (j <= 0) + return fdc_err(fdcu, bootverbose? "input ready timeout\n": 0); +#ifdef DEBUG + i = inb(baseport+FDDATA); + TRACE1("[FDDATA->0x%x]", (unsigned char)i); + return(i); +#else + return inb(baseport+FDDATA); +#endif +} + +/* + * fd_in: Like in_fdc, but allows you to see if it worked. + */ +static int +fd_in(fdcu_t fdcu, int *ptr) +{ + int baseport = fdc_data[fdcu].baseport; + int i, j = 1000000; + while ((i = inb(baseport+FDSTS) & (NE7_DIO|NE7_RQM)) + != (NE7_DIO|NE7_RQM) && j-- > 0) { + DELAY(10); + if (i == NE7_RQM) + return fdc_err(fdcu, "ready for output in input\n"); + } + if (j <= 0) + return fdc_err(fdcu, bootverbose? "input ready timeout\n": 0); +#ifdef DEBUG + i = inb(baseport+FDDATA); + TRACE1("[FDDATA->0x%x]", (unsigned char)i); + *ptr = i; + return 0; +#else + i = inb(baseport+FDDATA); + if (ptr) + *ptr = i; + return 0; +#endif +} + +int +out_fdc(fdcu_t fdcu, int x) +{ + int baseport = fdc_data[fdcu].baseport; + int i; + + /* Check that the direction bit is set */ + i = 1000000; + while ((inb(baseport+FDSTS) & NE7_DIO) && i-- > 0) + DELAY(10); + if (i <= 0) return fdc_err(fdcu, "direction bit not set\n"); + + /* Check that the floppy controller is ready for a command */ + i = 1000000; + while ((inb(baseport+FDSTS) & NE7_RQM) == 0 && i-- > 0) + DELAY(10); + if (i <= 0) + return fdc_err(fdcu, bootverbose? "output ready timeout\n": 0); + + /* Send the command and return */ + outb(baseport+FDDATA, x); + DELAY(10); + TRACE1("[0x%x->FDDATA]", x); + return (0); +} + +/****************************************************************************/ +/* fdopen/fdclose */ +/****************************************************************************/ +int +Fdopen(dev_t dev, int flags, int mode, struct proc *p) +{ + fdu_t fdu = FDUNIT(minor(dev)); + int type = FDTYPE(minor(dev)); + fdc_p fdc; + +#if NFT > 0 + /* check for a tape open */ + if (type & F_TAPE_TYPE) + return(ftopen(dev, flags)); +#endif + /* check bounds */ + if (fdu >= NFD) + return(ENXIO); + fdc = fd_data[fdu].fdc; + if ((fdc == NULL) || (fd_data[fdu].type == NO_TYPE)) + return(ENXIO); + if (type > NUMDENS) + return(ENXIO); +#ifdef PC98 + if (pc98_fd_check_ready(fdu) == -1) + return(EIO); +#endif + if (type == 0) + type = fd_data[fdu].type; +#ifndef PC98 + else { + if (type != fd_data[fdu].type) { + switch (fd_data[fdu].type) { + case FD_360: + return(ENXIO); + case FD_720: + if ( type != FD_820 + && type != FD_800 + ) + return(ENXIO); + break; + case FD_1200: + switch (type) { + case FD_1480: + type = FD_1480in5_25; + break; + case FD_1440: + type = FD_1440in5_25; + break; + case FD_820: + type = FD_820in5_25; + break; + case FD_800: + type = FD_800in5_25; + break; + case FD_720: + type = FD_720in5_25; + break; + case FD_360: + type = FD_360in5_25; + break; + default: + return(ENXIO); + } + break; + case FD_1440: + if ( type != FD_1720 + && type != FD_1480 + && type != FD_1200 + && type != FD_820 + && type != FD_800 + && type != FD_720 + ) + return(ENXIO); + break; + } + } + } +#endif + fd_data[fdu].ft = fd_types + type - 1; + fd_data[fdu].flags |= FD_OPEN; + kdc_fd[fdu].kdc_state = DC_BUSY; + + return 0; +} + +int +fdclose(dev_t dev, int flags, int mode, struct proc *p) +{ + fdu_t fdu = FDUNIT(minor(dev)); + +#if NFT > 0 + int type = FDTYPE(minor(dev)); + + if (type & F_TAPE_TYPE) + return ftclose(dev, flags); +#endif + fd_data[fdu].flags &= ~FD_OPEN; + fd_data[fdu].options &= ~FDOPT_NORETRY; + kdc_fd[fdu].kdc_state = DC_IDLE; + + return(0); +} + + +/****************************************************************************/ +/* fdstrategy */ +/****************************************************************************/ +void +fdstrategy(struct buf *bp) +{ + long nblocks, blknum; + int s; + fdcu_t fdcu; + fdu_t fdu; + fdc_p fdc; + fd_p fd; + size_t fdblk; + + fdu = FDUNIT(minor(bp->b_dev)); + fd = &fd_data[fdu]; + fdc = fd->fdc; + fdcu = fdc->fdcu; + +#if NFT > 0 + if (FDTYPE(minor(bp->b_dev)) & F_TAPE_TYPE) { + /* ft tapes do not (yet) support strategy i/o */ + bp->b_error = ENODEV; + bp->b_flags |= B_ERROR; + goto bad; + } + /* check for controller already busy with tape */ + if (fdc->flags & FDC_TAPE_BUSY) { + bp->b_error = EBUSY; + bp->b_flags |= B_ERROR; + goto bad; + } +#endif + fdblk = 128 << (fd->ft->secsize); + if (!(bp->b_flags & B_FORMAT)) { + if ((fdu >= NFD) || (bp->b_blkno < 0)) { + printf( + "fd%d: fdstrat: bad request blkno = %lu, bcount = %ld\n", + fdu, (u_long)bp->b_blkno, bp->b_bcount); + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + goto bad; + } + if ((bp->b_bcount % fdblk) != 0) { + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + goto bad; + } + } + + /* + * Set up block calculations. + */ + blknum = (unsigned long) bp->b_blkno * DEV_BSIZE/fdblk; + nblocks = fd->ft->size; +#ifdef PC98 +#define B_XXX2 0x8000000 + if (bp->b_flags & B_XXX2) { + blknum *= 2; + bp->b_blkno *= 2; + bp->b_flags &= ~B_XXX2; + } +#endif + if (blknum + (bp->b_bcount / fdblk) > nblocks) { + if (blknum == nblocks) { + bp->b_resid = bp->b_bcount; + } else { + bp->b_error = ENOSPC; + bp->b_flags |= B_ERROR; + } + goto bad; + } + bp->b_cylin = blknum / (fd->ft->sectrac * fd->ft->heads); + bp->b_pblkno = bp->b_blkno; + s = splbio(); + tqdisksort(&fdc->head, bp); + untimeout(fd_turnoff, (caddr_t)fdu); /* a good idea */ + fdstart(fdcu); + splx(s); + return; + +bad: + biodone(bp); +} + +/***************************************************************\ +* fdstart * +* We have just queued something.. if the controller is not busy * +* then simulate the case where it has just finished a command * +* So that it (the interrupt routine) looks on the queue for more* +* work to do and picks up what we just added. * +* If the controller is already busy, we need do nothing, as it * +* will pick up our work when the present work completes * +\***************************************************************/ +static void +fdstart(fdcu_t fdcu) +{ + int s; + + s = splbio(); + if(fdc_data[fdcu].state == DEVIDLE) + { + fdintr(fdcu); + } + splx(s); +} + +static void +fd_timeout(void *arg1) +{ + fdcu_t fdcu = (fdcu_t)arg1; + fdu_t fdu = fdc_data[fdcu].fdu; + int baseport = fdc_data[fdcu].baseport; + struct buf *bp; + int s; + + bp = TAILQ_FIRST(&fdc_data[fdcu].head); + + /* + * Due to IBM's brain-dead design, the FDC has a faked ready + * signal, hardwired to ready == true. Thus, any command + * issued if there's no diskette in the drive will _never_ + * complete, and must be aborted by resetting the FDC. + * Many thanks, Big Blue! + */ + + s = splbio(); + + TRACE1("fd%d[fd_timeout()]", fdu); + /* See if the controller is still busy (patiently awaiting data) */ + if(((inb(baseport + FDSTS)) & (NE7_CB|NE7_RQM)) == NE7_CB) + { + DELAY(5); + TRACE1("[FDSTS->0x%x]", inb(baseport + FDSTS)); + /* yup, it is; kill it now */ + fdc_reset(&fdc_data[fdcu]); + printf("fd%d: Operation timeout\n", fdu); + } + + if (bp) + { + retrier(fdcu); + fdc_data[fdcu].status[0] = NE7_ST0_IC_RC; + fdc_data[fdcu].state = IOTIMEDOUT; + if( fdc_data[fdcu].retry < 6) + fdc_data[fdcu].retry = 6; + } + else + { + fdc_data[fdcu].fd = (fd_p) 0; + fdc_data[fdcu].fdu = -1; + fdc_data[fdcu].state = DEVIDLE; + } + fdintr(fdcu); + splx(s); +} + +/* just ensure it has the right spl */ +static void +fd_pseudointr(void *arg1) +{ + fdcu_t fdcu = (fdcu_t)arg1; + int s; + + s = splbio(); + fdintr(fdcu); + splx(s); +} + +/***********************************************************************\ +* fdintr * +* keep calling the state machine until it returns a 0 * +* ALWAYS called at SPLBIO * +\***********************************************************************/ +void +fdintr(fdcu_t fdcu) +{ + fdc_p fdc = fdc_data + fdcu; +#if NFT > 0 + fdu_t fdu = fdc->fdu; + + if (fdc->flags & FDC_TAPE_BUSY) + (ftintr(fdu)); + else +#endif + while(fdstate(fdcu, fdc)) + ; +} + +/***********************************************************************\ +* The controller state machine. * +* if it returns a non zero value, it should be called again immediatly * +\***********************************************************************/ +static int +fdstate(fdcu_t fdcu, fdc_p fdc) +{ + int read, format, head, sec = 0, sectrac, st0, cyl, st3; + unsigned long blknum; + fdu_t fdu = fdc->fdu; + fd_p fd; + register struct buf *bp; + struct fd_formb *finfo = NULL; + size_t fdblk; + + bp = TAILQ_FIRST(&fdc->head); + if(!bp) { + /***********************************************\ + * nothing left for this controller to do * + * Force into the IDLE state, * + \***********************************************/ + fdc->state = DEVIDLE; + if(fdc->fd) + { + printf("fd%d: unexpected valid fd pointer\n", + fdc->fdu); + fdc->fd = (fd_p) 0; + fdc->fdu = -1; + } + TRACE1("[fdc%d IDLE]", fdcu); + return(0); + } + fdu = FDUNIT(minor(bp->b_dev)); + fd = fd_data + fdu; + fdblk = 128 << fd->ft->secsize; + if (fdc->fd && (fd != fdc->fd)) + { + printf("fd%d: confused fd pointers\n", fdu); + } + read = bp->b_flags & B_READ; + format = bp->b_flags & B_FORMAT; + if(format) + finfo = (struct fd_formb *)bp->b_un.b_addr; + TRACE1("fd%d", fdu); + TRACE1("[%s]", fdstates[fdc->state]); + TRACE1("(0x%x)", fd->flags); + untimeout(fd_turnoff, (caddr_t)fdu); + timeout(fd_turnoff, (caddr_t)fdu, 4 * hz); + switch (fdc->state) + { + case DEVIDLE: + case FINDWORK: /* we have found new work */ + fdc->retry = 0; + fd->skip = 0; + fdc->fd = fd; + fdc->fdu = fdu; +#ifdef PC98 + pc98_trans = fd->ft->trans; + if (pc98_trans_prev != pc98_trans) { + int i; + set_density(fdcu, fdu); + for (i = 0; i < 10; i++) { + outb(0x5f, 0); + outb(0x5f, 0); + } + pc98_trans_prev = pc98_trans; + } + if (pc98_trans != fd->pc98_trans) { + if (pc98_trans != 1 && + (PC98_SYSTEM_PARAMETER(0x5ae) >> fdu) & 0x01) { + outb(0x4be, (fdu << 5) | 0x10 | (pc98_trans >> 1)); + outb(0x5f, 0); + outb(0x5f, 0); + } + fd->pc98_trans = pc98_trans; + } +#else + outb(fdc->baseport+FDCTL, fd->ft->trans); +#endif + TRACE1("[0x%x->FDCTL]", fd->ft->trans); + /*******************************************************\ + * If the next drive has a motor startup pending, then * + * it will start up in it's own good time * + \*******************************************************/ + if(fd->flags & FD_MOTOR_WAIT) + { + fdc->state = MOTORWAIT; + return(0); /* come back later */ + } + /*******************************************************\ + * Maybe if it's not starting, it SHOULD be starting * + \*******************************************************/ +#ifdef EPSON_NRDISK + if (fdu != nrdu) { + if (!(fd->flags & FD_MOTOR)) + { + fdc->state = MOTORWAIT; + fd_turnon(fdu); + return(0); + } + else /* at least make sure we are selected */ + { + set_motor(fdcu, fd->fdsu, TURNON); + } + } +#else /* !EPSON_NRDISK */ + if (!(fd->flags & FD_MOTOR)) + { + fdc->state = MOTORWAIT; + fd_turnon(fdu); + return(0); + } + else /* at least make sure we are selected */ + { + set_motor(fdcu, fd->fdsu, TURNON); + } +#endif + fdc->state = DOSEEK; + break; + case DOSEEK: + if (bp->b_cylin == fd->track) + { + fdc->state = SEEKCOMPLETE; + break; + } +#ifdef PC98 + pc98_fd_check_ready(fdu); +#endif + if (fd_cmd(fdcu, 3, NE7CMD_SEEK, + fd->fdsu, bp->b_cylin * fd->ft->steptrac, + 0)) + { + /* + * seek command not accepted, looks like + * the FDC went off to the Saints... + */ + fdc->retry = 6; /* try a reset */ + return(retrier(fdcu)); + } + fd->track = FD_NO_TRACK; + fdc->state = SEEKWAIT; + return(0); /* will return later */ + case SEEKWAIT: + /* allow heads to settle */ + timeout(fd_pseudointr, (caddr_t)fdcu, hz / 16); + fdc->state = SEEKCOMPLETE; + return(0); /* will return later */ + case SEEKCOMPLETE : /* SEEK DONE, START DMA */ + /* Make sure seek really happened*/ + if(fd->track == FD_NO_TRACK) + { + int descyl = bp->b_cylin * fd->ft->steptrac; + do { + /* + * This might be a "ready changed" interrupt, + * which cannot really happen since the + * RDY pin is hardwired to + 5 volts. This + * generally indicates a "bouncing" intr + * line, so do one of the following: + * + * When running on an enhanced FDC that is + * known to not go stuck after responding + * with INVALID, fetch all interrupt states + * until seeing either an INVALID or a + * real interrupt condition. + * + * When running on a dumb old NE765, give + * up immediately. The controller will + * provide up to four dummy RC interrupt + * conditions right after reset (for the + * corresponding four drives), so this is + * our only chance to get notice that it + * was not the FDC that caused the interrupt. + */ + if (fd_sense_int(fdc, &st0, &cyl) + == FD_NOT_VALID) + return 0; + if(fdc->fdct == FDC_NE765 + && (st0 & NE7_ST0_IC) == NE7_ST0_IC_RC) + return 0; /* hope for a real intr */ + } while ((st0 & NE7_ST0_IC) == NE7_ST0_IC_RC); + + if (0 == descyl) + { + int failed = 0; + /* + * seek to cyl 0 requested; make sure we are + * really there + */ + if (fd_sense_drive_status(fdc, &st3)) + failed = 1; +#ifdef EPSON_NRDISK + if (fdu == nrdu) st3 = NE7_ST3_T0; +#endif /* EPSON_NRDISK */ + if ((st3 & NE7_ST3_T0) == 0) { + printf( + "fd%d: Seek to cyl 0, but not really there (ST3 = %b)\n", + fdu, st3, NE7_ST3BITS); + failed = 1; + } + + if (failed) + { + if(fdc->retry < 3) + fdc->retry = 3; + return(retrier(fdcu)); + } + } +#ifdef EPSON_NRDISK + if (fdu == nrdu) cyl = descyl; +#endif + + if (cyl != descyl) + { + printf( + "fd%d: Seek to cyl %d failed; am at cyl %d (ST0 = 0x%x)\n", + fdu, descyl, cyl, st0); + return(retrier(fdcu)); + } + } + + fd->track = bp->b_cylin; + if(format) + fd->skip = (char *)&(finfo->fd_formb_cylno(0)) + - (char *)finfo; +#ifdef EPSON_NRDISK + if (fdu != nrdu) { +#endif /* EPSON_NRDISK */ +#ifdef PC98 + pc98_dmastart(bp->b_flags, bp->b_un.b_addr+fd->skip, +#else + isa_dmastart(bp->b_flags, bp->b_un.b_addr+fd->skip, +#endif + format ? bp->b_bcount : fdblk, fdc->dmachan); + blknum = (unsigned long)bp->b_blkno*DEV_BSIZE/fdblk + + fd->skip/fdblk; + sectrac = fd->ft->sectrac; + sec = blknum % (sectrac * fd->ft->heads); + head = sec / sectrac; + sec = sec % sectrac + 1; + fd->hddrv = ((head&1)<<2)+fdu; + if(format || !read) + { + /* make sure the drive is writable */ + if(fd_sense_drive_status(fdc, &st3) != 0) + { + /* stuck controller? */ + fdc->retry = 6; /* reset the beast */ + return(retrier(fdcu)); + } + if(st3 & NE7_ST3_WP) + { + /* + * XXX YES! this is ugly. + * in order to force the current operation + * to fail, we will have to fake an FDC + * error - all error handling is done + * by the retrier() + */ + fdc->status[0] = NE7_ST0_IC_AT; + fdc->status[1] = NE7_ST1_NW; + fdc->status[2] = 0; + fdc->status[3] = fd->track; + fdc->status[4] = head; + fdc->status[5] = sec; + fdc->retry = 8; /* break out immediately */ + fdc->state = IOTIMEDOUT; /* not really... */ + return (1); + } + } + + if(format) + { + /* formatting */ + if(fd_cmd(fdcu, 6, + NE7CMD_FORMAT, + head << 2 | fdu, + finfo->fd_formb_secshift, + finfo->fd_formb_nsecs, + finfo->fd_formb_gaplen, + finfo->fd_formb_fillbyte, + 0)) + { + /* controller fell over */ + fdc->retry = 6; + return(retrier(fdcu)); + } + } + else + { + if (fd_cmd(fdcu, 9, + (read ? NE7CMD_READ : NE7CMD_WRITE), + head << 2 | fdu, /* head & unit */ + fd->track, /* track */ + head, + sec, /* sector + 1 */ + fd->ft->secsize, /* sector size */ + sectrac, /* sectors/track */ + fd->ft->gap, /* gap size */ + fd->ft->datalen, /* data length */ + 0)) + { + /* the beast is sleeping again */ + fdc->retry = 6; + return(retrier(fdcu)); + } + } + fdc->state = IOCOMPLETE; + timeout(fd_timeout, (caddr_t)fdcu, hz); + return(0); /* will return later */ +#ifdef EPSON_NRDISK + } + else { + nrdblkn = (nrd_t)((unsigned long)bp->b_blkno*DEV_BSIZE/fdblk + + fd->skip/fdblk); + nrd_LED_on(); + nrd_addrset(fdblk * nrdblkn); + while (!nrd_check_ready()) DELAY(1); + if (read) epson_insw(P_NRD_DATA, + bp->b_un.b_addr + fd->skip, + fdblk / sizeof(short)); + else epson_outsw(P_NRD_DATA, + bp->b_un.b_addr + fd->skip, + (format ? bp->b_bcount : fdblk) + / sizeof(short)); + + blknum = (unsigned long)bp->b_blkno*DEV_BSIZE/fdblk + + fd->skip/fdblk; + sectrac = fd->ft->sectrac; + sec = blknum % (sectrac * fd->ft->heads); + head = sec / sectrac; + sec = sec % sectrac + 1; + fd->hddrv = ((head&1)<<2)+fdu; + + if (nrdsec++ >= nrd_sec()) + nrdaddr = (nrd_t)(fd->track * 8 + head * 4); + nrdsec = sec; + fdc->state = IOCOMPLETE; + } +#endif + case IOCOMPLETE: /* IO DONE, post-analyze */ +#ifdef EPSON_NRDISK + if (fdu != nrdu) untimeout(fd_timeout, (caddr_t)fdcu); +#else + untimeout(fd_timeout, (caddr_t)fdcu); +#endif + + if (fd_read_status(fdc, fd->fdsu)) + { + if (fdc->retry < 6) + fdc->retry = 6; /* force a reset */ + return retrier(fdcu); + } + + fdc->state = IOTIMEDOUT; + + /* FALLTHROUGH */ + + case IOTIMEDOUT: +#ifdef EPSON_NRDISK + if (fdu != nrdu) { +#endif /* EPSON_NRDISK */ +#ifdef PC98 + pc98_dmadone(bp->b_flags, bp->b_un.b_addr+fd->skip, +#else + isa_dmadone(bp->b_flags, bp->b_un.b_addr+fd->skip, +#endif + format ? bp->b_bcount : fdblk, fdc->dmachan); +#ifdef EPSON_NRDISK + } + else nrd_LED_off(); +#endif /* EPSON_NRDISK */ + if (fdc->status[0] & NE7_ST0_IC) + { + if ((fdc->status[0] & NE7_ST0_IC) == NE7_ST0_IC_AT + && fdc->status[1] & NE7_ST1_OR) { + /* + * DMA overrun. Someone hogged the bus + * and didn't release it in time for the + * next FDC transfer. + * Just restart it, don't increment retry + * count. (vak) + */ + fdc->state = SEEKCOMPLETE; + return (1); + } + else if((fdc->status[0] & NE7_ST0_IC) == NE7_ST0_IC_IV + && fdc->retry < 6) + fdc->retry = 6; /* force a reset */ + else if((fdc->status[0] & NE7_ST0_IC) == NE7_ST0_IC_AT + && fdc->status[2] & NE7_ST2_WC + && fdc->retry < 3) + fdc->retry = 3; /* force recalibrate */ + return(retrier(fdcu)); + } + /* All OK */ + fd->skip += fdblk; + if (!format && fd->skip < bp->b_bcount) + { + /* set up next transfer */ + blknum = (unsigned long)bp->b_blkno*DEV_BSIZE/fdblk + + fd->skip/fdblk; +#ifdef EPSON_NRDISK + nrdblkn = (unsigned long)bp->b_blkno*DEV_BSIZE/fdblk + + fd->skip/fdblk; +#endif + bp->b_cylin = + (blknum / (fd->ft->sectrac * fd->ft->heads)); + fdc->state = DOSEEK; + } + else + { + /* ALL DONE */ + fd->skip = 0; + bp->b_resid = 0; + TAILQ_REMOVE(&fdc->head, bp, b_act); + biodone(bp); + fdc->fd = (fd_p) 0; + fdc->fdu = -1; + fdc->state = FINDWORK; + } + return(1); + case RESETCTLR: + fdc_reset(fdc); + fdc->retry++; + fdc->state = STARTRECAL; + break; + case STARTRECAL: + /* XXX clear the fdc results from the last reset, if any. */ + { + int i; + for (i = 0; i < 4; i++) + (void)fd_sense_int(fdc, &st0, &cyl); + } + +#ifdef PC98 + pc98_fd_check_ready(fdu); +#endif + if(fd_cmd(fdcu, + 2, NE7CMD_RECAL, fdu, + 0)) /* Recalibrate Function */ + { + /* arrgl */ + fdc->retry = 6; + return(retrier(fdcu)); + } + fdc->state = RECALWAIT; + return(0); /* will return later */ + case RECALWAIT: + /* allow heads to settle */ + timeout(fd_pseudointr, (caddr_t)fdcu, hz / 8); + fdc->state = RECALCOMPLETE; + return(0); /* will return later */ + case RECALCOMPLETE: + do { + /* + * See SEEKCOMPLETE for a comment on this: + */ + if (fd_sense_int(fdc, &st0, &cyl) == FD_NOT_VALID) + return 0; + if(fdc->fdct == FDC_NE765 + && (st0 & NE7_ST0_IC) == NE7_ST0_IC_RC) + return 0; /* hope for a real intr */ + } while ((st0 & NE7_ST0_IC) == NE7_ST0_IC_RC); +#ifdef EPSON_NRDISK + if (fdu == nrdu) { + st0 = NE7_ST0_IC_NT; + cyl = 0; + } +#endif + if ((st0 & NE7_ST0_IC) != NE7_ST0_IC_NT || cyl != 0) + { + if(fdc->retry > 3) + /* + * a recalibrate from beyond cylinder 77 + * will "fail" due to the FDC limitations; + * since people used to complain much about + * the failure message, try not logging + * this one if it seems to be the first + * time in a line + */ + printf("fd%d: recal failed ST0 %b cyl %d\n", + fdu, st0, NE7_ST0BITS, cyl); + if(fdc->retry < 3) fdc->retry = 3; + return(retrier(fdcu)); + } + fd->track = 0; + /* Seek (probably) necessary */ + fdc->state = DOSEEK; + return(1); /* will return immediatly */ + case MOTORWAIT: + if(fd->flags & FD_MOTOR_WAIT) + { + return(0); /* time's not up yet */ + } + /* + * since the controller was off, it has lost its + * idea about the current track it were; thus, + * recalibrate the bastard + */ + fdc->state = STARTRECAL; + return(1); /* will return immediatly */ + default: + printf("fdc%d: Unexpected FD int->", fdcu); + if (fd_read_status(fdc, fd->fdsu) == 0) + printf("FDC status :%lx %lx %lx %lx %lx %lx %lx ", + fdc->status[0], + fdc->status[1], + fdc->status[2], + fdc->status[3], + fdc->status[4], + fdc->status[5], + fdc->status[6] ); + else + printf("No status available "); + if (fd_sense_int(fdc, &st0, &cyl) != 0) + { + printf("[controller is dead now]\n"); + return(0); + } + printf("ST0 = %x, PCN = %x\n", st0, cyl); + return(0); + } + /*XXX confusing: some branches return immediately, others end up here*/ + return(1); /* Come back immediatly to new state */ +} + +static int +retrier(fdcu) + fdcu_t fdcu; +{ + fdc_p fdc = fdc_data + fdcu; + register struct buf *bp; + + bp = TAILQ_FIRST(&fdc->head); + + if(fd_data[FDUNIT(minor(bp->b_dev))].options & FDOPT_NORETRY) + goto fail; + switch(fdc->retry) + { + case 0: case 1: case 2: + fdc->state = SEEKCOMPLETE; + break; + case 3: case 4: case 5: + fdc->state = STARTRECAL; + break; + case 6: + fdc->state = RESETCTLR; + break; + case 7: + break; + default: + fail: + { + dev_t sav_b_dev = bp->b_dev; + /* Trick diskerr */ + bp->b_dev = makedev(major(bp->b_dev), + (FDUNIT(minor(bp->b_dev))<<3)|RAW_PART); + diskerr(bp, "fd", "hard error", LOG_PRINTF, + fdc->fd->skip / DEV_BSIZE, + (struct disklabel *)NULL); + bp->b_dev = sav_b_dev; + if (fdc->flags & FDC_STAT_VALID) + { + printf( + " (ST0 %b ST1 %b ST2 %b cyl %ld hd %ld sec %ld)\n", + fdc->status[0], NE7_ST0BITS, + fdc->status[1], NE7_ST1BITS, + fdc->status[2], NE7_ST2BITS, + fdc->status[3], fdc->status[4], + fdc->status[5]); + } + else + printf(" (No status)\n"); + } + bp->b_flags |= B_ERROR; + bp->b_error = EIO; + bp->b_resid = bp->b_bcount - fdc->fd->skip; + TAILQ_REMOVE(&fdc->head, bp, b_act); + fdc->fd->skip = 0; + biodone(bp); + fdc->state = FINDWORK; + fdc->fd = (fd_p) 0; + fdc->fdu = -1; + /* XXX abort current command, if any. */ + return(1); + } + fdc->retry++; + return(1); +} + +static int +fdformat(dev, finfo, p) + dev_t dev; + struct fd_formb *finfo; + struct proc *p; +{ + fdu_t fdu; + fd_p fd; + + struct buf *bp; + int rv = 0, s; + size_t fdblk; + + fdu = FDUNIT(minor(dev)); + fd = &fd_data[fdu]; + fdblk = 128 << fd->ft->secsize; + + /* set up a buffer header for fdstrategy() */ + bp = (struct buf *)malloc(sizeof(struct buf), M_TEMP, M_NOWAIT); + if(bp == 0) + return ENOBUFS; + /* + * keep the process from being swapped + */ + p->p_flag |= P_PHYSIO; + bzero((void *)bp, sizeof(struct buf)); + bp->b_flags = B_BUSY | B_PHYS | B_FORMAT; + bp->b_proc = p; + bp->b_dev = dev; + + /* + * calculate a fake blkno, so fdstrategy() would initiate a + * seek to the requested cylinder + */ + bp->b_blkno = (finfo->cyl * (fd->ft->sectrac * fd->ft->heads) + + finfo->head * fd->ft->sectrac) * fdblk / DEV_BSIZE; + + bp->b_bcount = sizeof(struct fd_idfield_data) * finfo->fd_formb_nsecs; + bp->b_un.b_addr = (caddr_t)finfo; + + /* now do the format */ + fdstrategy(bp); + + /* ...and wait for it to complete */ + s = splbio(); + while(!(bp->b_flags & B_DONE)) + { + rv = tsleep((caddr_t)bp, PRIBIO, "fdform", 20 * hz); + if(rv == EWOULDBLOCK) + break; + } + splx(s); + + if(rv == EWOULDBLOCK) { + /* timed out */ + rv = EIO; + biodone(bp); + } + if(bp->b_flags & B_ERROR) + rv = bp->b_error; + /* + * allow the process to be swapped + */ + p->p_flag &= ~P_PHYSIO; + free(bp, M_TEMP); + return rv; +} + +/* + * TODO: don't allocate buffer on stack. + */ + +int +fdioctl(dev, cmd, addr, flag, p) + dev_t dev; + int cmd; + caddr_t addr; + int flag; + struct proc *p; +{ + fdu_t fdu = FDUNIT(minor(dev)); + fd_p fd = &fd_data[fdu]; + size_t fdblk; + + struct fd_type *fdt; + struct disklabel *dl; + char buffer[DEV_BSIZE]; + int error = 0; + +#if NFT > 0 + int type = FDTYPE(minor(dev)); + + /* check for a tape ioctl */ + if (type & F_TAPE_TYPE) + return ftioctl(dev, cmd, addr, flag, p); +#endif + + fdblk = 128 << fd->ft->secsize; + +#ifdef PC98 + pc98_fd_check_ready(fdu); +#endif + switch (cmd) + { + case DIOCGDINFO: + bzero(buffer, sizeof (buffer)); + dl = (struct disklabel *)buffer; + dl->d_secsize = fdblk; + fdt = fd_data[FDUNIT(minor(dev))].ft; + dl->d_secpercyl = fdt->size / fdt->tracks; + dl->d_type = DTYPE_FLOPPY; + + if (readdisklabel(dkmodpart(dev, RAW_PART), fdstrategy, dl) + == NULL) + error = 0; + else + error = EINVAL; + + *(struct disklabel *)addr = *dl; + break; + + case DIOCSDINFO: + if ((flag & FWRITE) == 0) + error = EBADF; + break; + + case DIOCWLABEL: + if ((flag & FWRITE) == 0) + error = EBADF; + break; + + case DIOCWDINFO: + if ((flag & FWRITE) == 0) + { + error = EBADF; + break; + } + + dl = (struct disklabel *)addr; + + if ((error = setdisklabel((struct disklabel *)buffer, dl, + (u_long)0)) != 0) + break; + + error = writedisklabel(dev, fdstrategy, + (struct disklabel *)buffer); + break; + + case FD_FORM: + if((flag & FWRITE) == 0) + error = EBADF; /* must be opened for writing */ + else if(((struct fd_formb *)addr)->format_version != + FD_FORMAT_VERSION) + error = EINVAL; /* wrong version of formatting prog */ + else + error = fdformat(dev, (struct fd_formb *)addr, p); + break; + + case FD_GTYPE: /* get drive type */ + *(struct fd_type *)addr = *fd_data[FDUNIT(minor(dev))].ft; + break; + + case FD_STYPE: /* set drive type */ + /* this is considered harmful; only allow for superuser */ + if(suser(p->p_ucred, &p->p_acflag) != 0) + return EPERM; + *fd_data[FDUNIT(minor(dev))].ft = *(struct fd_type *)addr; + break; + + case FD_GOPTS: /* get drive options */ + *(int *)addr = fd_data[FDUNIT(minor(dev))].options; + break; + + case FD_SOPTS: /* set drive options */ + fd_data[FDUNIT(minor(dev))].options = *(int *)addr; + break; + + default: + error = ENOTTY; + break; + } + return (error); +} + + +static fd_devsw_installed = 0; + +static void fd_drvinit(void *notused ) +{ + dev_t dev; + + if( ! fd_devsw_installed ) { + dev = makedev(CDEV_MAJOR, 0); + cdevsw_add(&dev,&fd_cdevsw, NULL); + dev = makedev(BDEV_MAJOR, 0); + bdevsw_add(&dev,&fd_bdevsw, NULL); + fd_devsw_installed = 1; + } +} + +SYSINIT(fddev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,fd_drvinit,NULL) + +#endif +/* + * Hello emacs, these are the + * Local Variables: + * c-indent-level: 8 + * c-continued-statement-offset: 8 + * c-continued-brace-offset: 0 + * c-brace-offset: -8 + * c-brace-imaginary-offset: 0 + * c-argdecl-indent: 8 + * c-label-offset: -8 + * c++-hanging-braces: 1 + * c++-access-specifier-offset: -8 + * c++-empty-arglist-indent: 8 + * c++-friend-offset: 0 + * End: + */ diff --git a/sys/pc98/cbus/fdcreg.h b/sys/pc98/cbus/fdcreg.h new file mode 100644 index 0000000..7b10161 --- /dev/null +++ b/sys/pc98/cbus/fdcreg.h @@ -0,0 +1,102 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)fdreg.h 7.1 (Berkeley) 5/9/91 + * $Id: fdreg.h,v 1.8 1994/09/25 23:37:38 phk Exp $ + */ + +/* + * AT floppy controller registers and bitfields + */ + +#ifdef PC98 +/* uses NEC765 controller */ +#include <pc98/pc98/ic/nec765.h> + +#define FDSTS 0 /* NEC 765 Main Status Register (R) */ +#define FDDATA 2 /* NEC 765 Data Register (R/W) */ + +/* registers */ +#define FDOUT 4 /* Digital Output Register (W) */ +#define FDO_RST 0x80 /* FDC RESET */ +#define FDO_FRY 0x40 /* force READY */ +#define FDO_AIE 0x20 /* Attention Interrupt Enable */ +#define FDO_DD 0x20 /* FDD Mode Exchange 0:1M 1:640K */ +#define FDO_DMAE 0x10 /* enable floppy DMA */ +#define FDO_MTON 0x08 /* MOTOR ON (when EMTON=1)*/ +#define FDO_TMSK 0x04 /* TIMER MASK */ +#define FDO_TTRG 0x01 /* TIMER TRIGER */ + +#define FDIN 4 /* Digital Input Register (R) */ +#define FDI_TYP0 0x04 /* FDD #1/#2 TYPE */ +#define FDI_TYP1 0x08 /* FDD #3/#4 TYPE */ +#define FDI_RDY 0x10 /* Ready */ +#define FDI_DMACH 0x20 /* DMA Channel */ +#define FDI_FINT0 0x40 /* Interrupt */ +#define FDI_FINT1 0x80 /* Interrupt */ + +#define FDP_EMTON 0x04 /* enable MTON */ +#define FDP_FDDEXC 0x02 /* FDD Mode Exchange 1:1M 0:640K */ +#define FDP_PORTEXC 0x01 /* PORT Exchane 1:1M 0:640K */ + +#else + +/* uses NEC765 controller */ +#include <i386/isa/ic/nec765.h> + +/* registers */ +#define FDOUT 2 /* Digital Output Register (W) */ +#define FDO_FDSEL 0x03 /* floppy device select */ +#define FDO_FRST 0x04 /* floppy controller reset */ +#define FDO_FDMAEN 0x08 /* enable floppy DMA and Interrupt */ +#define FDO_MOEN0 0x10 /* motor enable drive 0 */ +#define FDO_MOEN1 0x20 /* motor enable drive 1 */ +#define FDO_MOEN2 0x40 /* motor enable drive 2 */ +#define FDO_MOEN3 0x80 /* motor enable drive 3 */ + +#define FDSTS 4 /* NEC 765 Main Status Register (R) */ +#define FDDATA 5 /* NEC 765 Data Register (R/W) */ +#define FDCTL 7 /* Control Register (W) */ + +#ifndef FDC_500KBPS +# define FDC_500KBPS 0x00 /* 500KBPS MFM drive transfer rate */ +# define FDC_300KBPS 0x01 /* 300KBPS MFM drive transfer rate */ +# define FDC_250KBPS 0x02 /* 250KBPS MFM drive transfer rate */ +# define FDC_125KBPS 0x03 /* 125KBPS FM drive transfer rate */ + /* for some controllers 1MPBS instead */ +#endif /* FDC_500KBPS */ + +#define FDIN 7 /* Digital Input Register (R) */ +#define FDI_DCHG 0x80 /* diskette has been changed */ + /* requires drive and motor being selected */ + /* is cleared by any step pulse to drive */ +#endif diff --git a/sys/pc98/cbus/pcrtc.c b/sys/pc98/cbus/pcrtc.c new file mode 100644 index 0000000..b2befbf --- /dev/null +++ b/sys/pc98/cbus/pcrtc.c @@ -0,0 +1,1129 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz and Don Ahn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)clock.c 7.2 (Berkeley) 5/12/91 + * $Id: clock.c,v 1.58 1996/05/01 08:39:02 bde Exp $ + */ + +/* + * inittodr, settodr and support routines written + * by Christoph Robitschko <chmr@edvz.tu-graz.ac.at> + * + * reintroduced and updated by Chris Stenton <chris@gnome.co.uk> 8/10/94 + */ + +/* + * modified for PC98 + * $Id: clock.c,v 1.7 1994/03/26 22:56:13 kakefuda Exp kakefuda $ + */ + +/* + * Primitive clock interrupt routines. + */ +#include "opt_ddb.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> + +#include <machine/clock.h> +#ifdef CLK_CALIBRATION_LOOP +#include <machine/cons.h> +#endif +#include <machine/cpu.h> +#include <machine/frame.h> + +#ifdef PC98 +#include <sys/syslog.h> +#include <pc98/pc98/icu.h> +#include <pc98/pc98/pc98.h> +#include <pc98/pc98/pc98_device.h> +#include <pc98/pc98/timerreg.h> +#else +#include <i386/isa/icu.h> +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/rtc.h> +#include <i386/isa/timerreg.h> +#endif + +/* + * 32-bit time_t's can't reach leap years before 1904 or after 2036, so we + * can use a simple formula for leap years. + */ +#define LEAPYEAR(y) ((u_int)(y) % 4 == 0) +#define DAYSPERYEAR (31+28+31+30+31+30+31+31+30+31+30+31) + +#define TIMER_DIV(x) ((timer_freq + (x) / 2) / (x)) + +/* + * Time in timer cycles that it takes for microtime() to disable interrupts + * and latch the count. microtime() currently uses "cli; outb ..." so it + * normally takes less than 2 timer cycles. Add a few for cache misses. + * Add a few more to allow for latency in bogus calls to microtime() with + * interrupts already disabled. + */ +#define TIMER0_LATCH_COUNT 20 + +/* + * Minimum maximum count that we are willing to program into timer0. + * Must be large enough to guarantee that the timer interrupt handler + * returns before the next timer interrupt. Must be larger than + * TIMER0_LATCH_COUNT so that we don't have to worry about underflow in + * the calculation of timer0_overflow_threshold. + */ +#define TIMER0_MIN_MAX_COUNT TIMER_DIV(20000) + +int adjkerntz; /* local offset from GMT in seconds */ +int disable_rtc_set; /* disable resettodr() if != 0 */ +int wall_cmos_clock; /* wall CMOS clock assumed if != 0 */ + +u_int idelayed; +#if defined(I586_CPU) || defined(I686_CPU) +unsigned i586_ctr_freq; +unsigned i586_ctr_rate; +long long i586_ctr_bias; +long long i586_last_tick; +unsigned long i586_avg_tick; +#endif +int statclock_disable; +u_int stat_imask = SWI_CLOCK_MASK; +int timer0_max_count; +u_int timer0_overflow_threshold; +u_int timer0_prescaler_count; + +static int beeping = 0; +static u_int clk_imask = HWI_MASK | SWI_MASK; +static const u_char daysinmonth[] = {31,28,31,30,31,30,31,31,30,31,30,31}; +static u_int hardclock_max_count; +/* + * XXX new_function and timer_func should not handle clockframes, but + * timer_func currently needs to hold hardclock to handle the + * timer0_state == 0 case. We should use register_intr()/unregister_intr() + * to switch between clkintr() and a slightly different timerintr(). + * This will require locking when acquiring and releasing timer0 - the + * current (nonexistent) locking doesn't seem to be adequate even now. + */ +static void (*new_function) __P((struct clockframe *frame)); +static u_int new_rate; +#ifndef PC98 +static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; +static u_char rtc_statusb = RTCSB_24HR | RTCSB_PINTR; +#endif +#ifdef TIMER_FREQ +static u_int timer_freq = TIMER_FREQ; +#else +#ifdef PC98 +#ifndef AUTO_CLOCK +#ifndef PC98_8M +static u_int timer_freq = 2457600; +#else /* !PC98_8M */ +static u_int timer_freq = 1996800; +#endif /* PC98_8M */ +#else /* AUTO_CLOCK */ +static u_int timer_freq = 2457600; +#endif /* AUTO_CLOCK */ +#else /* IBM-PC */ +static u_int timer_freq = 1193182; +#endif /* PC98 */ +#endif +static char timer0_state = 0; +#ifdef PC98 +static char timer1_state = 0; +#endif +static char timer2_state = 0; +static void (*timer_func) __P((struct clockframe *frame)) = hardclock; +int rtc_inb __P((void)); + +#if 0 +void +clkintr(struct clockframe frame) +{ + hardclock(&frame); + setdelayed(); +} +#else +static void +clkintr(struct clockframe frame) +{ + timer_func(&frame); + switch (timer0_state) { + case 0: + setdelayed(); + break; + case 1: + if ((timer0_prescaler_count += timer0_max_count) + >= hardclock_max_count) { + hardclock(&frame); + setdelayed(); + timer0_prescaler_count -= hardclock_max_count; + } + break; + case 2: + setdelayed(); + timer0_max_count = TIMER_DIV(new_rate); + timer0_overflow_threshold = + timer0_max_count - TIMER0_LATCH_COUNT; + disable_intr(); + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(TIMER_CNTR0, timer0_max_count & 0xff); + outb(TIMER_CNTR0, timer0_max_count >> 8); + enable_intr(); + timer0_prescaler_count = 0; + timer_func = new_function; + timer0_state = 1; + break; + case 3: + if ((timer0_prescaler_count += timer0_max_count) + >= hardclock_max_count) { + hardclock(&frame); + setdelayed(); + timer0_max_count = hardclock_max_count; + timer0_overflow_threshold = + timer0_max_count - TIMER0_LATCH_COUNT; + disable_intr(); + outb(TIMER_MODE, + TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(TIMER_CNTR0, timer0_max_count & 0xff); + outb(TIMER_CNTR0, timer0_max_count >> 8); + enable_intr(); + /* + * See microtime.s for this magic. + */ +#ifdef PC98 +#ifndef AUTO_CLOCK +#ifndef PC98_8M + time.tv_usec += (6667 * + (timer0_prescaler_count - hardclock_max_count)) + >> 14; +#else /* PC98_8M */ + time.tv_usec += (16411 * + (timer0_prescaler_count - hardclock_max_count)) + >> 15; +#endif /* PC98_8M */ +#else /* AUTO_CLOCK */ + if (pc98_machine_type & M_8M) { + /* PC98_8M */ + time.tv_usec += (16411 * + (timer0_prescaler_count - + hardclock_max_count)) >> 15; + } else { + time.tv_usec += (6667 * + (timer0_prescaler_count - + hardclock_max_count)) >> 14; + } +#endif /* AUTO_CLOCK */ +#else /* IBM-PC */ + time.tv_usec += (27645 * + (timer0_prescaler_count - hardclock_max_count)) + >> 15; +#endif /* PC98 */ + if (time.tv_usec >= 1000000) + time.tv_usec -= 1000000; + timer0_prescaler_count = 0; + timer_func = hardclock;; + timer0_state = 0; + } + break; + } +} +#endif + +int +acquire_timer0(int rate, void (*function) __P((struct clockframe *frame))) +{ + if (timer0_state || TIMER_DIV(rate) < TIMER0_MIN_MAX_COUNT || + !function) + return -1; + new_function = function; + new_rate = rate; + timer0_state = 2; + return 0; +} + +#ifdef PC98 +int +acquire_timer1(int mode) +{ + if (timer1_state) + return -1; + timer1_state = 1; + outb(TIMER_MODE, TIMER_SEL1 | (mode &0x3f)); + return 0; +} +#endif + +int +acquire_timer2(int mode) +{ + if (timer2_state) + return -1; + timer2_state = 1; + outb(TIMER_MODE, TIMER_SEL2 | (mode &0x3f)); + return 0; +} + +int +release_timer0() +{ + if (!timer0_state) + return -1; + timer0_state = 3; + return 0; +} + +#ifdef PC98 +int +release_timer1() +{ + if (!timer1_state) + return -1; + timer1_state = 0; + outb(TIMER_MODE, TIMER_SEL1|TIMER_SQWAVE|TIMER_16BIT); + return 0; +} +#endif + +int +release_timer2() +{ + if (!timer2_state) + return -1; + timer2_state = 0; + outb(TIMER_MODE, TIMER_SEL2|TIMER_SQWAVE|TIMER_16BIT); + return 0; +} + +#ifndef PC98 +/* + * This routine receives statistical clock interrupts from the RTC. + * As explained above, these occur at 128 interrupts per second. + * When profiling, we receive interrupts at a rate of 1024 Hz. + * + * This does not actually add as much overhead as it sounds, because + * when the statistical clock is active, the hardclock driver no longer + * needs to keep (inaccurate) statistics on its own. This decouples + * statistics gathering from scheduling interrupts. + * + * The RTC chip requires that we read status register C (RTC_INTR) + * to acknowledge an interrupt, before it will generate the next one. + */ +static void +rtcintr(struct clockframe frame) +{ + u_char stat; + stat = rtcin(RTC_INTR); + if(stat & RTCIR_PERIOD) { + statclock(&frame); + } +} + +#ifdef DDB +static void +DDB_printrtc(void) +{ + printf("%02x/%02x/%02x %02x:%02x:%02x, A = %02x, B = %02x, C = %02x\n", + rtcin(RTC_YEAR), rtcin(RTC_MONTH), rtcin(RTC_DAY), + rtcin(RTC_HRS), rtcin(RTC_MIN), rtcin(RTC_SEC), + rtcin(RTC_STATUSA), rtcin(RTC_STATUSB), rtcin(RTC_INTR)); +} +#endif +#endif /* for PC98 */ + +static int +getit(void) +{ + int high, low; + + disable_intr(); + /* select timer0 and latch counter value */ + outb(TIMER_MODE, TIMER_SEL0); + low = inb(TIMER_CNTR0); + high = inb(TIMER_CNTR0); + enable_intr(); + return ((high << 8) | low); +} + +/* + * Wait "n" microseconds. + * Relies on timer 1 counting down from (timer_freq / hz) + * Note: timer had better have been programmed before this is first used! + */ +void +DELAY(int n) +{ + int prev_tick, tick, ticks_left, sec, usec; + +#ifdef DELAYDEBUG + int getit_calls = 1; + int n1; + static int state = 0; + + if (state == 0) { + state = 1; + for (n1 = 1; n1 <= 10000000; n1 *= 10) + DELAY(n1); + state = 2; + } + if (state == 1) + printf("DELAY(%d)...", n); +#endif + /* + * Read the counter first, so that the rest of the setup overhead is + * counted. Guess the initial overhead is 20 usec (on most systems it + * takes about 1.5 usec for each of the i/o's in getit(). The loop + * takes about 6 usec on a 486/33 and 13 usec on a 386/20. The + * multiplications and divisions to scale the count take a while). + */ + prev_tick = getit(); + n -= 20; + /* + * Calculate (n * (timer_freq / 1e6)) without using floating point + * Calculate (n * (TIMER_FREQ / 1e6)) without using floating point + * and without any avoidable overflows. + */ + sec = n / 1000000; + usec = n - sec * 1000000; + ticks_left = sec * timer_freq + + usec * (timer_freq / 1000000) + + usec * ((timer_freq % 1000000) / 1000) / 1000 + + usec * (timer_freq % 1000) / 1000000; + if (n < 0) + ticks_left = 0; /* XXX timer_freq is unsigned */ + + while (ticks_left > 0) { + tick = getit(); +#ifdef DELAYDEBUG + ++getit_calls; +#endif + if (tick > prev_tick) + ticks_left -= prev_tick - (tick - timer0_max_count); + else + ticks_left -= prev_tick - tick; + prev_tick = tick; + } +#ifdef DELAYDEBUG + if (state == 1) + printf(" %d calls to getit() at %d usec each\n", + getit_calls, (n + 5) / getit_calls); +#endif +} + +static void +sysbeepstop(void *chan) +{ +#ifdef PC98 /* PC98 */ + outb(IO_PPI, inb(IO_PPI)|0x08); /* disable counter1 output to speaker */ + release_timer1(); +#else + outb(IO_PPI, inb(IO_PPI)&0xFC); /* disable counter2 output to speaker */ + release_timer2(); +#endif + beeping = 0; +} + +int +sysbeep(int pitch, int period) +{ +#ifdef PC98 + if (acquire_timer1(TIMER_SQWAVE|TIMER_16BIT)) + return -1; + disable_intr(); + outb(0x3fdb, pitch); + outb(0x3fdb, (pitch>>8)); + enable_intr(); + if (!beeping) { + outb(IO_PPI, (inb(IO_PPI) & 0xf7)); /* enable counter1 output to speaker */ + beeping = period; + timeout(sysbeepstop, (void *)NULL, period); + } +#else + + if (acquire_timer2(TIMER_SQWAVE|TIMER_16BIT)) + return -1; + disable_intr(); + outb(TIMER_CNTR2, pitch); + outb(TIMER_CNTR2, (pitch>>8)); + enable_intr(); + if (!beeping) { + outb(IO_PPI, inb(IO_PPI) | 3); /* enable counter2 output to speaker */ + beeping = period; + timeout(sysbeepstop, (void *)NULL, period); + } +#endif + return 0; +} + +#ifndef PC98 +/* + * RTC support routines + */ + +int +rtcin(reg) + int reg; +{ + u_char val; + + outb(IO_RTC, reg); + inb(0x84); + val = inb(IO_RTC + 1); + inb(0x84); + return (val); +} + +static __inline void +writertc(u_char reg, u_char val) +{ + outb(IO_RTC, reg); + outb(IO_RTC + 1, val); +} + +static __inline int +readrtc(int port) +{ + return(bcd2bin(rtcin(port))); +} +#endif + +#ifdef PC98 +unsigned int delaycount; +#define FIRST_GUESS 0x2000 +static void findcpuspeed(void) +{ + int i; + int remainder; + + /* Put counter in count down mode */ + outb(TIMER_MODE, TIMER_SEL0 | TIMER_16BIT | TIMER_RATEGEN); + outb(TIMER_CNTR0, 0xff); + outb(TIMER_CNTR0, 0xff); + for (i = FIRST_GUESS; i; i--) + ; + remainder = getit(); + delaycount = (FIRST_GUESS * TIMER_DIV(1000)) / (0xffff - remainder); +} +#endif + +#ifndef PC98 +static u_int +calibrate_clocks(void) +{ + u_int count, prev_count, tot_count; + int sec, start_sec, timeout; + + printf("Calibrating clock(s) relative to mc146818A clock ... "); + if (!(rtcin(RTC_STATUSD) & RTCSD_PWR)) + goto fail; + timeout = 100000000; + + /* Read the mc146818A seconds counter. */ + for (;;) { + if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) { + sec = rtcin(RTC_SEC); + break; + } + if (--timeout == 0) + goto fail; + } + + /* Wait for the mC146818A seconds counter to change. */ + start_sec = sec; + for (;;) { + if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) { + sec = rtcin(RTC_SEC); + if (sec != start_sec) + break; + } + if (--timeout == 0) + goto fail; + } + + /* Start keeping track of the i8254 counter. */ + prev_count = getit(); + if (prev_count == 0 || prev_count > timer0_max_count) + goto fail; + tot_count = 0; + +#if defined(I586_CPU) || defined(I686_CPU) + if (cpu_class == CPUCLASS_586 || cpu_class == CPUCLASS_686) + wrmsr(0x10, 0LL); /* XXX 0x10 is the MSR for the TSC */ +#endif + + /* + * Wait for the mc146818A seconds counter to change. Read the i8254 + * counter for each iteration since this is convenient and only + * costs a few usec of inaccuracy. The timing of the final reads + * of the counters almost matches the timing of the initial reads, + * so the main cause of inaccuracy is the varying latency from + * inside getit() or rtcin(RTC_STATUSA) to the beginning of the + * rtcin(RTC_SEC) that returns a changed seconds count. The + * maximum inaccuracy from this cause is < 10 usec on 486's. + */ + start_sec = sec; + for (;;) { + if (!(rtcin(RTC_STATUSA) & RTCSA_TUP)) + sec = rtcin(RTC_SEC); + count = getit(); + if (count == 0 || count > timer0_max_count) + goto fail; + if (count > prev_count) + tot_count += prev_count - (count - timer0_max_count); + else + tot_count += prev_count - count; + prev_count = count; + if (sec != start_sec) + break; + if (--timeout == 0) + goto fail; + } + +#if defined(I586_CPU) || defined(I686_CPU) + /* + * Read the cpu cycle counter. The timing considerations are + * similar to those for the i8254 clock. + */ + if (cpu_class == CPUCLASS_586 || cpu_class == CPUCLASS_686) { + unsigned long long i586_count; + + i586_count = rdtsc(); + i586_ctr_freq = i586_count; + i586_ctr_rate = (i586_count << I586_CTR_RATE_SHIFT) / 1000000; + printf("i586 clock: %u Hz, ", i586_ctr_freq); + } +#endif + + printf("i8254 clock: %u Hz\n", tot_count); + return (tot_count); + +fail: + printf("failed, using default i8254 clock of %u Hz\n", timer_freq); + return (timer_freq); +} +#endif /* !PC98 */ + +static void +set_timer_freq(u_int freq, int intr_freq) +{ + u_long ef; + + ef = read_eflags(); + timer_freq = freq; + timer0_max_count = hardclock_max_count = TIMER_DIV(intr_freq); + timer0_overflow_threshold = timer0_max_count - TIMER0_LATCH_COUNT; + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(TIMER_CNTR0, timer0_max_count & 0xff); + outb(TIMER_CNTR0, timer0_max_count >> 8); + write_eflags(ef); +} + +/* + * Initialize 8253 timer 0 early so that it can be used in DELAY(). + * XXX initialization of other timers is unintentionally left blank. + */ +void +startrtclock() +{ + u_int delta, freq; + +#ifdef PC98 + findcpuspeed(); +#ifndef AUTO_CLOCK + if (pc98_machine_type & M_8M) { +#ifndef PC98_8M + log(LOG_EMERG, + "you must reconfig a kernel with \"PC98_8M\" option.\n"); +#endif + } else { +#ifdef PC98_8M + log(LOG_EMERG, + "You must reconfig a kernel without \"PC98_8M\" option.\n"); +#endif + } +#else /* AUTO_CLOCK */ + if (pc98_machine_type & M_8M) + timer_freq = 1996800L; /* 1.9968 MHz */ + else + timer_freq = 2457600L; /* 2.4576 MHz */ +#endif /* AUTO_CLOCK */ +#endif /* PC98 */ + +#ifndef PC98 + writertc(RTC_STATUSA, rtc_statusa); + writertc(RTC_STATUSB, RTCSB_24HR); +#endif + +#ifndef PC98 + /* + * Temporarily calibrate with a high intr_freq to get a low + * timer0_max_count to help detect bogus i8254 counts. + */ + set_timer_freq(timer_freq, 20000); + freq = calibrate_clocks(); +#ifdef CLK_CALIBRATION_LOOP + if (bootverbose) { + printf( + "Press a key on the console to abort clock calibration\n"); + while (!cncheckc()) + calibrate_clocks(); + } +#endif + + /* + * Use the calibrated i8254 frequency if it seems reasonable. + * Otherwise use the default, and don't use the calibrated i586 + * frequency. + */ + delta = freq > timer_freq ? freq - timer_freq : timer_freq - freq; + if (delta < timer_freq / 100) { +#ifndef CLK_USE_I8254_CALIBRATION + printf( +"CLK_USE_I8254_CALIBRATION not specified - using default frequency\n"); + freq = timer_freq; +#endif + timer_freq = freq; + } else { + printf("%d Hz differs from default of %d Hz by more than 1%%\n", + freq, timer_freq); +#if defined(I586_CPU) || defined(I686_CPU) + i586_ctr_freq = 0; + i586_ctr_rate = 0; +#endif + } +#endif + + set_timer_freq(timer_freq, hz); + +#if defined(I586_CPU) || defined(I686_CPU) +#ifndef CLK_USE_I586_CALIBRATION + if (i586_ctr_rate != 0) { + printf( +"CLK_USE_I586_CALIBRATION not specified - using old calibration method\n"); + i586_ctr_freq = 0; + i586_ctr_rate = 0; + } +#endif + if (i586_ctr_rate == 0 && + (cpu_class == CPUCLASS_586 || cpu_class == CPUCLASS_686)) { + /* + * Calibration of the i586 clock relative to the mc146818A + * clock failed. Do a less accurate calibration relative + * to the i8254 clock. + */ + unsigned long long i586_count; + + wrmsr(0x10, 0LL); /* XXX */ + DELAY(1000000); + i586_count = rdtsc(); + i586_ctr_rate = (i586_count << I586_CTR_RATE_SHIFT) / 1000000; + printf("i586 clock: %u Hz\n", i586_ctr_freq); + } +#endif +} + +#ifdef PC98 +void +rtc_serialcombit(int i) +{ + outb(IO_RTC, ((i&0x01)<<5)|0x07); + DELAY(1); + outb(IO_RTC, ((i&0x01)<<5)|0x17); + DELAY(1); + outb(IO_RTC, ((i&0x01)<<5)|0x07); + DELAY(1); +} + +void +rtc_serialcom(int i) +{ + rtc_serialcombit(i&0x01); + rtc_serialcombit((i&0x02)>>1); + rtc_serialcombit((i&0x04)>>2); + rtc_serialcombit((i&0x08)>>3); + outb(IO_RTC, 0x07); + DELAY(1); + outb(IO_RTC, 0x0f); + DELAY(1); + outb(IO_RTC, 0x07); + DELAY(1); +} + +void +rtc_outb(int val) +{ + int s; + int sa = 0; + + for (s=0;s<8;s++) { + sa = ((val >> s) & 0x01) ? 0x27 : 0x07; + outb(IO_RTC, sa); /* set DI & CLK 0 */ + DELAY(1); + outb(IO_RTC, sa | 0x10); /* CLK 1 */ + DELAY(1); + } + outb(IO_RTC, sa & 0xef); /* CLK 0 */ +} + +int +rtc_inb(void) +{ + int s; + int sa = 0; + + for (s=0;s<8;s++) { + sa |= ((inb(0x33) & 0x01) << s); + outb(IO_RTC, 0x17); /* CLK 1 */ + DELAY(1); + outb(IO_RTC, 0x07); /* CLK 0 */ + DELAY(2); + } + return sa; +} +#endif /* PC-98 */ + +/* + * Initialize the time of day register, based on the time base which is, e.g. + * from a filesystem. + */ +void +inittodr(time_t base) +{ + unsigned long sec, days; + int yd; + int year, month; + int y, m, s; +#ifdef PC98 + int second, min, hour; +#endif + + s = splclock(); + time.tv_sec = base; + time.tv_usec = 0; + splx(s); + +#ifdef PC98 + rtc_serialcom(0x03); /* Time Read */ + rtc_serialcom(0x01); /* Register shift command. */ + DELAY(20); + + second = bcd2bin(rtc_inb() & 0xff); /* sec */ + min = bcd2bin(rtc_inb() & 0xff); /* min */ + hour = bcd2bin(rtc_inb() & 0xff); /* hour */ + days = bcd2bin(rtc_inb() & 0xff) - 1; /* date */ + + month = (rtc_inb() >> 4) & 0x0f; /* month */ + for (m = 1; m < month; m++) + days += daysinmonth[m-1]; + year = bcd2bin(rtc_inb() & 0xff) + 1900; /* year */ + /* 2000 year problem */ + if (year < 1995) + year += 100; + if (year < 1970) + goto wrong_time; + for (y = 1970; y < year; y++) + days += DAYSPERYEAR + LEAPYEAR(y); + if ((month > 2) && LEAPYEAR(year)) + days ++; + sec = ((( days * 24 + + hour) * 60 + + min) * 60 + + second); + /* sec now contains the number of seconds, since Jan 1 1970, + in the local time zone */ +#else /* IBM-PC */ + /* Look if we have a RTC present and the time is valid */ + if (!(rtcin(RTC_STATUSD) & RTCSD_PWR)) + goto wrong_time; + + /* wait for time update to complete */ + /* If RTCSA_TUP is zero, we have at least 244us before next update */ + while (rtcin(RTC_STATUSA) & RTCSA_TUP); + + days = 0; +#ifdef USE_RTC_CENTURY + year = readrtc(RTC_YEAR) + readrtc(RTC_CENTURY) * 100; +#else + year = readrtc(RTC_YEAR) + 1900; + if (year < 1970) + year += 100; +#endif + if (year < 1970) + goto wrong_time; + month = readrtc(RTC_MONTH); + for (m = 1; m < month; m++) + days += daysinmonth[m-1]; + if ((month > 2) && LEAPYEAR(year)) + days ++; + days += readrtc(RTC_DAY) - 1; + yd = days; + for (y = 1970; y < year; y++) + days += DAYSPERYEAR + LEAPYEAR(y); + sec = ((( days * 24 + + readrtc(RTC_HRS)) * 60 + + readrtc(RTC_MIN)) * 60 + + readrtc(RTC_SEC)); + /* sec now contains the number of seconds, since Jan 1 1970, + in the local time zone */ +#endif + + sec += tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); + + s = splclock(); + time.tv_sec = sec; + splx(s); + return; + +wrong_time: + printf("Invalid time in real time clock.\n"); + printf("Check and reset the date immediately!\n"); +} + +/* + * Write system time back to RTC + */ +void +resettodr() +{ + unsigned long tm; + int y, m, s; +#ifdef PC98 + int wd; +#endif + + if (disable_rtc_set) + return; + + s = splclock(); + tm = time.tv_sec; + splx(s); + +#ifdef PC98 + rtc_serialcom(0x01); /* Register shift command. */ + + /* Calculate local time to put in RTC */ + + tm -= tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); + + rtc_outb(bin2bcd(tm%60)); tm /= 60; /* Write back Seconds */ + rtc_outb(bin2bcd(tm%60)); tm /= 60; /* Write back Minutes */ + rtc_outb(bin2bcd(tm%24)); tm /= 24; /* Write back Hours */ + + /* We have now the days since 01-01-1970 in tm */ + wd = (tm+4)%7; + for (y = 1970, m = DAYSPERYEAR + LEAPYEAR(y); + tm >= m; + y++, m = DAYSPERYEAR + LEAPYEAR(y)) + tm -= m; + + /* Now we have the years in y and the day-of-the-year in tm */ + for (m = 0; ; m++) { + int ml; + + ml = daysinmonth[m]; + if (m == 1 && LEAPYEAR(y)) + ml++; + if (tm < ml) + break; + tm -= ml; + } + + m++; + rtc_outb(bin2bcd(tm+1)); /* Write back Day */ + rtc_outb((m << 4) | wd); /* Write back Month & Weekday */ + rtc_outb(bin2bcd(y%100)); /* Write back Year */ + + rtc_serialcom(0x02); /* Time set & Counter hold command. */ + rtc_serialcom(0x00); /* Register hold command. */ +#else + /* Disable RTC updates and interrupts. */ + writertc(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR); + + /* Calculate local time to put in RTC */ + + tm -= tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); + + writertc(RTC_SEC, bin2bcd(tm%60)); tm /= 60; /* Write back Seconds */ + writertc(RTC_MIN, bin2bcd(tm%60)); tm /= 60; /* Write back Minutes */ + writertc(RTC_HRS, bin2bcd(tm%24)); tm /= 24; /* Write back Hours */ + + /* We have now the days since 01-01-1970 in tm */ + writertc(RTC_WDAY, (tm+4)%7); /* Write back Weekday */ + for (y = 1970, m = DAYSPERYEAR + LEAPYEAR(y); + tm >= m; + y++, m = DAYSPERYEAR + LEAPYEAR(y)) + tm -= m; + + /* Now we have the years in y and the day-of-the-year in tm */ + writertc(RTC_YEAR, bin2bcd(y%100)); /* Write back Year */ +#ifdef USE_RTC_CENTURY + writertc(RTC_CENTURY, bin2bcd(y/100)); /* ... and Century */ +#endif + for (m = 0; ; m++) { + int ml; + + ml = daysinmonth[m]; + if (m == 1 && LEAPYEAR(y)) + ml++; + if (tm < ml) + break; + tm -= ml; + } + + writertc(RTC_MONTH, bin2bcd(m + 1)); /* Write back Month */ + writertc(RTC_DAY, bin2bcd(tm + 1)); /* Write back Month Day */ + + /* Reenable RTC updates and interrupts. */ + writertc(RTC_STATUSB, rtc_statusb); +#endif +} + +/* + * Start both clocks running. + */ +void +cpu_initclocks() +{ +#ifndef PC98 + int diag; + + if (statclock_disable) { + /* + * The stat interrupt mask is different without the + * statistics clock. Also, don't set the interrupt + * flag which would normally cause the RTC to generate + * interrupts. + */ + stat_imask = HWI_MASK | SWI_MASK; + rtc_statusb = RTCSB_24HR; + } else { + /* Setting stathz to nonzero early helps avoid races. */ + stathz = RTC_NOPROFRATE; + profhz = RTC_PROFRATE; + } +#endif + + /* Finish initializing 8253 timer 0. */ + register_intr(/* irq */ 0, /* XXX id */ 0, /* flags */ 0, + /* XXX */ (inthand2_t *)clkintr, &clk_imask, + /* unit */ 0); + INTREN(IRQ0); +#if defined(I586_CPU) || defined(I686_CPU) + /* + * Finish setting up anti-jitter measures. + */ + if (i586_ctr_rate) { + i586_last_tick = rdtsc(); + i586_ctr_bias = i586_last_tick; + } +#endif + +#ifndef PC98 + /* Initialize RTC. */ + writertc(RTC_STATUSA, rtc_statusa); + writertc(RTC_STATUSB, RTCSB_24HR); + +static int +sysctl_machdep_i8254_freq SYSCTL_HANDLER_ARGS +{ + int error; + u_int freq; + + /* + * Use `i8254' instead of `timer' in external names because `timer' + * is is too generic. Should use it everywhere. + */ + freq = timer_freq; + error = sysctl_handle_opaque(oidp, &freq, sizeof freq, req); + if (error == 0 && freq != timer_freq) { + if (timer0_state != 0) + return (EBUSY); /* too much trouble to handle */ + set_timer_freq(freq, hz); + } + return (error); +} + +SYSCTL_PROC(_machdep, OID_AUTO, i8254_freq, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(u_int), sysctl_machdep_i8254_freq, "I", ""); + +#if defined(I586_CPU) || defined(I686_CPU) +static int +sysctl_machdep_i586_freq SYSCTL_HANDLER_ARGS +{ + int error; + u_int freq; + + if (i586_ctr_rate == 0) + return (EOPNOTSUPP); + freq = i586_ctr_freq; + error = sysctl_handle_opaque(oidp, &freq, sizeof freq, req); + if (error == 0 && freq != i586_ctr_freq) { + i586_ctr_freq = freq; + i586_ctr_rate = ((unsigned long long)freq << + I586_CTR_RATE_SHIFT) / 1000000; + } + return (error); +} + +SYSCTL_PROC(_machdep, OID_AUTO, i586_freq, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(u_int), sysctl_machdep_i586_freq, "I", ""); +#endif /* defined(I586_CPU) || defined(I686_CPU) */ + + /* Don't bother enabling the statistics clock. */ + if (statclock_disable) + return; + diag = rtcin(RTC_DIAG); + if (diag != 0) + printf("RTC BIOS diagnostic error %b\n", diag, RTCDG_BITS); + register_intr(/* irq */ 8, /* XXX id */ 1, /* flags */ 0, + /* XXX */ (inthand2_t *)rtcintr, &stat_imask, + /* unit */ 0); + INTREN(IRQ8); + writertc(RTC_STATUSB, rtc_statusb); +#endif +} + +void +setstatclockrate(int newhz) +{ +#ifndef PC98 + if (newhz == RTC_PROFRATE) + rtc_statusa = RTCSA_DIVIDER | RTCSA_PROF; + else + rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; + writertc(RTC_STATUSA, rtc_statusa); +#endif +} diff --git a/sys/pc98/cbus/sio.c b/sys/pc98/cbus/sio.c new file mode 100644 index 0000000..b16d153 --- /dev/null +++ b/sys/pc98/cbus/sio.c @@ -0,0 +1,4023 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)com.c 7.5 (Berkeley) 5/16/91 + * $Id: sio.c,v 1.142 1996/05/02 09:34:40 phk Exp $ + */ + +#include "opt_comconsole.h" +#include "opt_ddb.h" +#include "opt_sio.h" +#include "sio.h" + +/* + * Serial driver, based on 386BSD-0.1 com driver. + * Mostly rewritten to use pseudo-DMA. + * Works for National Semiconductor NS8250-NS16550AF UARTs. + * COM driver, based on HP dca driver. + * + * Changes for PC-Card integration: + * - Added PC-Card driver table and handlers + */ +/*=============================================================== + * 386BSD(98),FreeBSD-1.1x(98) com driver. + * ----- + * modified for PC9801 by M.Ishii + * Kyoto University Microcomputer Club (KMC) + * Chou "TEFUTEFU" Hirotomi + * Kyoto Univ. the faculty of medicine + *=============================================================== + * FreeBSD-2.0.1(98) sio driver. + * ----- + * modified for pc98 Internal i8251 and MICRO CORE MC16550II + * T.Koike(hfc01340@niftyserve.or.jp) + * implement kernel device configuration + * aizu@orient.center.nitech.ac.jp + * + * Notes. + * ----- + * PC98 localization based on 386BSD(98) com driver. Using its PC98 local + * functions. + * This driver is under debugging,has bugs. + * + * 1) config + * options COM_MULTIPORT #if using MC16550II + * device sio0 at nec? port 0x30 tty irq 4 vector siointr #internal + * device sio1 at nec? port 0xd2 tty irq 5 flags 0x101 vector siointr #mc1 + * device sio2 at nec? port 0x8d2 tty flags 0x101 vector siointr #mc2 + * # ~~~~~iobase ~~multi port flag + * # ~ master device is sio1 + * 2) device + * cd /dev; MAKEDEV ttyd0 ttyd1 .. + * 3) /etc/rc.serial + * 57600bps is too fast for sio0(internal8251) + * my ex. + * #set default speed 9600 + * modem() + * : + * stty </dev/ttyid$i crtscts 9600 + * : # ~~~~ default speed(can change after init.) + * modem 0 1 2 + * 4) COMCONSOLE + * not changed. + * 5) PC9861K,PIO9032B,B98_01 + * not tested. + */ +/* + * modified for AIWA B98-01 + * by T.Hatanou <hatanou@yasuda.comm.waseda.ac.jp> last update: 15 Sep.1995 + * + * How to configure... + * # options COM_MULTIPORT # support for MICROCORE MC16550II + * ... comment-out this line, which will conflict with B98_01. + * options "B98_01" # support for AIWA B98-01 + * device sio1 at nec? port 0x00d1 tty irq ? vector siointr + * device sio2 at nec? port 0x00d5 tty irq ? vector siointr + * ... you can leave these lines `irq ?', irq will be autodetected. + */ +#ifdef PC98 +#define MC16550 0 +#define COM_IF_INTERNAL 1 +#if 0 +#define COM_IF_PC9861K 2 +#define COM_IF_PIO9032B 3 +#endif +#ifdef B98_01 +#undef COM_MULTIPORT /* COM_MULTIPORT will conflict with B98_01 */ +#define COM_IF_B98_01 4 +#endif /* B98_01 */ +#endif /* PC98 */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/reboot.h> +#include <sys/ioctl.h> +#include <sys/tty.h> +#include <sys/proc.h> +#include <sys/conf.h> +#include <sys/dkstat.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/syslog.h> +#include <sys/devconf.h> +#ifdef DEVFS +#include <sys/devfsext.h> +#endif + +#include <machine/clock.h> + +#ifdef PC98 +#include <pc98/pc98/icu.h> /* XXX just to get at `imen' */ +#include <pc98/pc98/pc98.h> +#include <pc98/pc98/pc98_device.h> +#include <pc98/pc98/sioreg.h> +#include <pc98/pc98/ic/i8251.h> +#include <pc98/pc98/ic/ns16550.h> +#else +#include <i386/isa/icu.h> /* XXX just to get at `imen' */ +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/sioreg.h> + +#ifdef COM_ESP +#include <i386/isa/ic/esp.h> +#endif +#include <i386/isa/ic/ns16550.h> +#endif + +#include "crd.h" +#if NCRD > 0 +#include <pccard/card.h> +#include <pccard/driver.h> +#include <pccard/slot.h> +#endif + +#define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ +#define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) +#define RS_IBUFSIZE 256 + +#define CALLOUT_MASK 0x80 +#define CONTROL_MASK 0x60 +#define CONTROL_INIT_STATE 0x20 +#define CONTROL_LOCK_STATE 0x40 +#define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) +#define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) +#define MINOR_TO_UNIT(mynor) ((mynor) & ~MINOR_MAGIC_MASK) + +#ifdef COM_MULTIPORT +/* checks in flags for multiport and which is multiport "master chip" + * for a given card + */ +#define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01) +#define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff) +#define COM_NOTAST4(dev) ((dev)->id_flags & 0x04) +#endif /* COM_MULTIPORT */ + +#define COM_LOSESOUTINTS(dev) ((dev)->id_flags & 0x08) +#define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) +#define COM_VERBOSE(dev) ((dev)->id_flags & 0x80) + +#ifndef PC98 +#define com_scr 7 /* scratch register for 16450-16550 (R/W) */ +#endif /* !PC98 */ + +/* + * Input buffer watermarks. + * The external device is asked to stop sending when the buffer exactly reaches + * high water, or when the high level requests it. + * The high level is notified immediately (rather than at a later clock tick) + * when this watermark is reached. + * The buffer size is chosen so the watermark should almost never be reached. + * The low watermark is invisibly 0 since the buffer is always emptied all at + * once. + */ +#define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) + +/* + * com state bits. + * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher + * than the other bits so that they can be tested as a group without masking + * off the low bits. + * + * The following com and tty flags correspond closely: + * CS_BUSY = TS_BUSY (maintained by comstart(), siopoll() and + * siostop()) + * CS_TTGO = ~TS_TTSTOP (maintained by comparam() and comstart()) + * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) + * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) + * TS_FLUSH is not used. + * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. + * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). + */ +#define CS_BUSY 0x80 /* output in progress */ +#define CS_TTGO 0x40 /* output not stopped by XOFF */ +#define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ +#define CS_CHECKMSR 1 /* check of MSR scheduled */ +#define CS_CTS_OFLOW 2 /* use CTS output flow control */ +#define CS_DTR_OFF 0x10 /* DTR held off */ +#define CS_ODONE 4 /* output completed */ +#define CS_RTS_IFLOW 8 /* use RTS input flow control */ + +static char const * const error_desc[] = { +#define CE_OVERRUN 0 + "silo overflow", +#define CE_INTERRUPT_BUF_OVERFLOW 1 + "interrupt-level buffer overflow", +#define CE_TTY_BUF_OVERFLOW 2 + "tty-level buffer overflow", +}; + +#define CE_NTYPES 3 +#define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) + +/* types. XXX - should be elsewhere */ +typedef u_int Port_t; /* hardware port */ +typedef u_char bool_t; /* boolean */ + +/* queue of linear buffers */ +struct lbq { + u_char *l_head; /* next char to process */ + u_char *l_tail; /* one past the last char to process */ + struct lbq *l_next; /* next in queue */ + bool_t l_queued; /* nonzero if queued */ +}; + +/* com device structure */ +struct com_s { + u_char state; /* miscellaneous flag bits */ + bool_t active_out; /* nonzero if the callout device is open */ + u_char cfcr_image; /* copy of value written to CFCR */ +#ifdef COM_ESP + bool_t esp; /* is this unit a hayes esp board? */ +#endif + u_char fifo_image; /* copy of value written to FIFO */ + bool_t hasfifo; /* nonzero for 16550 UARTs */ + bool_t loses_outints; /* nonzero if device loses output interrupts */ + u_char mcr_image; /* copy of value written to MCR */ +#ifdef COM_MULTIPORT + bool_t multiport; /* is this unit part of a multiport device? */ +#endif /* COM_MULTIPORT */ + bool_t no_irq; /* nonzero if irq is not attached */ + bool_t gone; /* hardware disappeared */ + bool_t poll; /* nonzero if polling is required */ + bool_t poll_output; /* nonzero if polling for output is required */ + int unit; /* unit number */ + int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ + u_int tx_fifo_size; + u_int wopeners; /* # processes waiting for DCD in open() */ + + /* + * The high level of the driver never reads status registers directly + * because there would be too many side effects to handle conveniently. + * Instead, it reads copies of the registers stored here by the + * interrupt handler. + */ + u_char last_modem_status; /* last MSR read by intr handler */ + u_char prev_modem_status; /* last MSR handled by high level */ + + u_char hotchar; /* ldisc-specific char to be handled ASAP */ + u_char *ibuf; /* start of input buffer */ + u_char *ibufend; /* end of input buffer */ + u_char *ihighwater; /* threshold in input buffer */ + u_char *iptr; /* next free spot in input buffer */ + + struct lbq obufq; /* head of queue of output buffers */ + struct lbq obufs[2]; /* output buffers */ + +#ifdef PC98 + Port_t cmd_port; + Port_t sts_port; + Port_t in_modem_port; + Port_t intr_ctrl_port; + int intr_enable; + int pc98_prev_modem_status; + int pc98_modem_delta; + int modem_car_chg_timer; + int pc98_prev_siocmd; + int pc98_prev_siomod; + int modem_checking; + int pc98_if_type; +#endif /* PC98 */ + Port_t data_port; /* i/o ports */ +#ifdef COM_ESP + Port_t esp_port; +#endif + Port_t int_id_port; + Port_t iobase; + Port_t modem_ctl_port; + Port_t line_status_port; + Port_t modem_status_port; + + struct tty *tp; /* cross reference */ + + /* Initial state. */ + struct termios it_in; /* should be in struct tty */ + struct termios it_out; + + /* Lock state. */ + struct termios lt_in; /* should be in struct tty */ + struct termios lt_out; + + bool_t do_timestamp; + struct timeval timestamp; + + u_long bytes_in; /* statistics */ + u_long bytes_out; + u_int delta_error_counts[CE_NTYPES]; + u_long error_counts[CE_NTYPES]; + + /* + * Ping-pong input buffers. The extra factor of 2 in the sizes is + * to allow for an error byte for each input byte. + */ +#define CE_INPUT_OFFSET RS_IBUFSIZE + u_char ibuf1[2 * RS_IBUFSIZE]; + u_char ibuf2[2 * RS_IBUFSIZE]; + + /* + * Data area for output buffers. Someday we should build the output + * buffer queue without copying data. + */ + u_char obuf1[256]; + u_char obuf2[256]; +#ifdef DEVFS + void *devfs_token_ttyd; + void *devfs_token_ttyl; + void *devfs_token_ttyi; + void *devfs_token_cuaa; + void *devfs_token_cual; + void *devfs_token_cuai; +#endif +}; + +/* + * XXX public functions in drivers should be declared in headers produced + * by `config', not here. + */ + +/* Interrupt handling entry points. */ +inthand2_t siointrts; +void siopoll __P((void)); + +/* Device switch entry points. */ +#define sioreset noreset +#define siommap nommap +#define siostrategy nostrategy + +#ifdef PC98 +#ifdef COM_ESP +static int espattach __P((struct pc98_device *isdp, struct com_s *com, + Port_t esp_port)); +#endif +static int sioattach __P((struct pc98_device *dev)); +#else +#ifdef COM_ESP +static int espattach __P((struct isa_device *isdp, struct com_s *com, + Port_t esp_port)); +#endif +static int sioattach __P((struct isa_device *dev)); +#endif +static timeout_t siodtrwakeup; +static void comhardclose __P((struct com_s *com)); +static void siointr1 __P((struct com_s *com)); +static int commctl __P((struct com_s *com, int bits, int how)); +static int comparam __P((struct tty *tp, struct termios *t)); +#ifdef PC98 +static int sioprobe __P((struct pc98_device *dev)); +static void sioregisterdev __P((struct pc98_device *id)); +#else +static int sioprobe __P((struct isa_device *dev)); +static void sioregisterdev __P((struct isa_device *id)); +#endif +static void siosettimeout __P((void)); +static void comstart __P((struct tty *tp)); +static timeout_t comwakeup; +static int tiocm_xxx2mcr __P((int tiocm_xxx)); +static void disc_optim __P((struct tty *tp, struct termios *t, + struct com_s *com)); + +#ifdef DSI_SOFT_MODEM +static int LoadSoftModem __P((int unit,int base_io, u_long size, u_char *ptr)); +#endif /* DSI_SOFT_MODEM */ + +static char driver_name[] = "sio"; + +/* table and macro for fast conversion from a unit number to its com struct */ +static struct com_s *p_com_addr[NSIO]; +#define com_addr(unit) (p_com_addr[unit]) + +static struct timeval intr_timestamp; + +#ifdef PC98 +struct pc98_driver siodriver = { +#else +struct isa_driver siodriver = { +#endif + sioprobe, sioattach, driver_name +}; + +static d_open_t sioopen; +static d_close_t sioclose; +static d_read_t sioread; +static d_write_t siowrite; +static d_ioctl_t sioioctl; +static d_stop_t siostop; +static d_devtotty_t siodevtotty; + +#define CDEV_MAJOR 28 +static struct cdevsw sio_cdevsw = { + sioopen, sioclose, sioread, siowrite, + sioioctl, siostop, noreset, siodevtotty, + ttselect, nommap, NULL, driver_name, + NULL, -1, +}; + +static int comconsole = -1; +static speed_t comdefaultrate = TTYDEF_SPEED; +static u_int com_events; /* input chars + weighted output completions */ +static int sio_timeout; +static int sio_timeouts_until_log; +#if 0 /* XXX */ +static struct tty *sio_tty[NSIO]; +#else +static struct tty sio_tty[NSIO]; +#endif +static const int nsio_tty = NSIO; + +#ifdef PC98 +struct siodev { + short if_type; + short irq; + Port_t cmd, sts, ctrl, mod; + }; +static int sysclock; +static short port_table[5][3] = { + {0x30, 0xb1, 0xb9}, + {0x32, 0xb3, 0xbb}, + {0x32, 0xb3, 0xbb}, + {0x33, 0xb0, 0xb2}, + {0x35, 0xb0, 0xb2} + }; +#define PC98SIO_data_port(ch) port_table[0][ch] +#define PC98SIO_cmd_port(ch) port_table[1][ch] +#define PC98SIO_sts_port(ch) port_table[2][ch] +#define PC98SIO_in_modem_port(ch) port_table[3][ch] +#define PC98SIO_intr_ctrl_port(ch) port_table[4][ch] +#ifdef COM_IF_PIO9032B +#define IO_COM_PIO9032B_2 0x0b8 +#define IO_COM_PIO9032B_3 0x0ba +#endif /* COM_IF_PIO9032B */ +#ifdef COM_IF_B98_01 +#define IO_COM_B98_01_2 0x0d1 +#define IO_COM_B98_01_3 0x0d5 +#endif /* COM_IF_B98_01 */ +#define COM_INT_DISABLE {int previpri; previpri=spltty(); +#define COM_INT_ENABLE splx(previpri);} +#define IEN_TxFLAG IEN_Tx + +#define COM_CARRIER_DETECT_EMULATE 0 +#define PC98_CHECK_MODEM_INTERVAL (hz/10) +#define DCD_OFF_TOLERANCE 2 +#define DCD_ON_RECOGNITION 2 +#define IS_8251(type) (type != MC16550) +#define IS_PC98IN(adr) (adr == 0x30) + +static void commint __P((dev_t dev)); +static void com_tiocm_set __P((struct com_s *com, int msr)); +static void com_tiocm_bis __P((struct com_s *com, int msr)); +static void com_tiocm_bic __P((struct com_s *com, int msr)); +static int com_tiocm_get __P((struct com_s *com)); +static int com_tiocm_get_delta __P((struct com_s *com)); +static void pc98_msrint_start __P((dev_t dev)); +static void com_cflag_and_speed_set __P((struct com_s *com, int cflag, int speed)); +static int pc98_ttspeedtab __P((struct com_s *com, int speed)); +static int pc98_get_modem_status __P((struct com_s *com)); +static timeout_t pc98_check_msr; +static void pc98_set_baud_rate __P((struct com_s *com, int count)); +static void pc98_i8251_reset __P((struct com_s *com, int mode, int command)); +static void pc98_disable_i8251_interrupt __P((struct com_s *com, int mod)); +static void pc98_enable_i8251_interrupt __P((struct com_s *com, int mod)); +static int pc98_check_i8251_interrupt __P((struct com_s *com)); +static int pc98_i8251_get_cmd __P((struct com_s *com)); +static int pc98_i8251_get_mod __P((struct com_s *com)); +static void pc98_i8251_set_cmd __P((struct com_s *com, int x)); +static void pc98_i8251_or_cmd __P((struct com_s *com, int x)); +static void pc98_i8251_clear_cmd __P((struct com_s *com, int x)); +static void pc98_i8251_clear_or_cmd __P((struct com_s *com, int clr, int x)); +static int pc98_check_if_type __P((int iobase, struct siodev *iod)); +static void pc98_check_sysclock __P((void)); +static int pc98_set_ioport __P((struct com_s *com, int io_base)); + +#define com_int_Tx_disable(com) \ + pc98_disable_i8251_interrupt(com,IEN_Tx|IEN_TxEMP) +#define com_int_Tx_enable(com) \ + pc98_enable_i8251_interrupt(com,IEN_TxFLAG) +#define com_int_Rx_disable(com) \ + pc98_disable_i8251_interrupt(com,IEN_Rx) +#define com_int_Rx_enable(com) \ + pc98_enable_i8251_interrupt(com,IEN_Rx) +#define com_int_TxRx_disable(com) \ + pc98_disable_i8251_interrupt(com,IEN_Tx|IEN_TxEMP|IEN_Rx) +#define com_int_TxRx_enable(com) \ + pc98_enable_i8251_interrupt(com,IEN_TxFLAG|IEN_Rx) +#define com_send_break_on(com) \ + pc98_i8251_or_cmd(com,CMD8251_SBRK) +#define com_send_break_off(com) \ + pc98_i8251_clear_cmd(com,CMD8251_SBRK) + +struct speedtab pc98speedtab[] = { /* internal RS232C interface */ + 0, 0, + 50, 50, + 75, 75, + 150, 150, + 200, 200, + 300, 300, + 600, 600, + 1200, 1200, + 2400, 2400, + 4800, 4800, + 9600, 9600, + 19200, 19200, + 38400, 38400, + 76800, 76800, + 20800, 20800, + 41600, 41600, + 15600, 15600, + 31200, 31200, + 62400, 62400, + -1, -1 +}; +#ifdef COM_IF_PIO9032B +struct speedtab comspeedtab_pio9032b[] = { + 300, 6, + 600, 5, + 1200, 4, + 2400, 3, + 4800, 2, + 9600, 1, + 19200, 0, + 38400, 7, + -1, -1 +}; +#endif + +#ifdef COM_IF_B98_01 +struct speedtab comspeedtab_b98_01[] = { + 0, 0, + 75, 15, + 150, 14, + 300, 13, + 600, 12, + 1200, 11, + 2400, 10, + 4800, 9, + 9600, 8, + 19200, 7, + 38400, 6, + 76800, 5, + 153600, 4, + -1, -1 +}; +#endif +#endif /* PC98 */ + +static struct speedtab comspeedtab[] = { + { 0, 0 }, + { 50, COMBRD(50) }, + { 75, COMBRD(75) }, + { 110, COMBRD(110) }, + { 134, COMBRD(134) }, + { 150, COMBRD(150) }, + { 200, COMBRD(200) }, + { 300, COMBRD(300) }, + { 600, COMBRD(600) }, + { 1200, COMBRD(1200) }, + { 1800, COMBRD(1800) }, + { 2400, COMBRD(2400) }, + { 4800, COMBRD(4800) }, + { 9600, COMBRD(9600) }, + { 19200, COMBRD(19200) }, + { 38400, COMBRD(38400) }, + { 57600, COMBRD(57600) }, + { 115200, COMBRD(115200) }, + { -1, -1 } +}; + +static struct kern_devconf kdc_sio[NSIO] = { { + 0, 0, 0, /* filled in by dev_attach */ +#ifdef PC98 + driver_name, 0, { MDDT_PC98, 0, "tty" }, + pc98_generic_externalize, 0, 0, PC98_EXTERNALLEN, + &kdc_nec0, /* parent */ +#else + driver_name, 0, { MDDT_ISA, 0, "tty" }, + isa_generic_externalize, 0, 0, ISA_EXTERNALLEN, + &kdc_isa0, /* parent */ +#endif + 0, /* parentdata */ + DC_UNCONFIGURED, /* state */ + "Serial port", + DC_CLS_SERIAL /* class */ +} }; + +#ifdef COM_ESP +/* XXX configure this properly. */ +static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; +static Port_t likely_esp_ports[] = { 0x140, 0x180, 0x280, 0 }; +#endif + +#if NCRD > 0 +/* + * PC-Card (PCMCIA) specific code. + */ +static int card_intr(struct pccard_dev *); /* Interrupt handler */ +static void siounload(struct pccard_dev *); /* Disable driver */ +static void siosuspend(struct pccard_dev *); /* Suspend driver */ +static int sioinit(struct pccard_dev *, int); /* init device */ + +static struct pccard_drv sio_info = { + driver_name, + card_intr, + siounload, + siosuspend, + sioinit, + 0, /* Attributes - presently unused */ + &tty_imask /* Interrupt mask for device */ + /* XXX - Should this also include net_imask? */ +}; + +/* + * Called when a power down is requested. Shuts down the + * device and configures the device as unavailable (but + * still loaded...). A resume is done by calling + * sioinit with first=0. This is called when the user suspends + * the system, or the APM code suspends the system. + */ +static void +siosuspend(struct pccard_dev *dp) +{ + printf("sio%d: suspending\n", dp->isahd.id_unit); +} + +/* + * Initialize the device - called from Slot manager. + * If first is set, then check for the device's existence + * before initializing it. Once initialized, the device table may + * be set up. + */ +int +sioinit(struct pccard_dev *dp, int first) +{ + + /* validate unit number. */ + if (first) { + if (dp->isahd.id_unit >= NSIO) + return(ENODEV); + /* Make sure it isn't already probed. */ + if (com_addr(dp->isahd.id_unit)) + return(EBUSY); + /* + * Probe the device. If a value is returned, the + * device was found at the location. + */ + if (sioprobe(&dp->isahd)==0) + return(ENXIO); + if (sioattach(&dp->isahd)==0) + return(ENXIO); + } + /* + * XXX TODO: + * If it was initialized before, the device structure + * should also be initialized. We should + * reset (and possibly restart) the hardware, but + * I am not sure of the best way to do this... + */ + return(0); +} + +/* + * siounload - unload the driver and clear the table. + * XXX TODO: + * This is usually called when the card is ejected, but + * can be caused by a modunload of a controller driver. + * The idea is to reset the driver's view of the device + * and ensure that any driver entry points such as + * read and write do not hang. + */ +static void +siounload(struct pccard_dev *dp) +{ + struct com_s *com; + + com = com_addr(dp->isahd.id_unit); + if (!com->iobase) { + printf("sio%d already unloaded!\n",dp->isahd.id_unit); + return; + } + kdc_sio[com->unit].kdc_state = DC_UNCONFIGURED; + kdc_sio[com->unit].kdc_description = "Serial port"; + if (com->tp && (com->tp->t_state & TS_ISOPEN)) { + com->gone = 1; + printf("sio%d: unload\n", dp->isahd.id_unit); + com->tp->t_gen++; + ttyclose(com->tp); + ttwakeup(com->tp); + ttwwakeup(com->tp); + } else { + com_addr(com->unit) = NULL; + bzero(com, sizeof *com); + free(com,M_TTYS); + printf("sio%d: unload,gone\n", dp->isahd.id_unit); + } +} + +/* + * card_intr - Shared interrupt called from + * front end of PC-Card handler. + */ +static int +card_intr(struct pccard_dev *dp) +{ + struct com_s *com; + com = com_addr(dp->isahd.id_unit); + if (com && !com_addr(dp->isahd.id_unit)->gone) + siointr1(com_addr(dp->isahd.id_unit)); + return(1); +} +#endif /* NCRD > 0 */ + +static void +sioregisterdev(id) +#ifdef PC98 + struct pc98_device *id; +#else + struct isa_device *id; +#endif +{ + int unit; + + unit = id->id_unit; +/* + * If already registered, don't try to re-register. + */ +#ifdef PC98 + if (kdc_sio[unit].kdc_pc98) +#else + if (kdc_sio[unit].kdc_isa) +#endif + return; + if (unit != 0) + kdc_sio[unit] = kdc_sio[0]; + kdc_sio[unit].kdc_state = DC_UNCONFIGURED; + kdc_sio[unit].kdc_description = "Serial port"; + kdc_sio[unit].kdc_unit = unit; +#ifdef PC98 + kdc_sio[unit].kdc_pc98 = id; +#else + kdc_sio[unit].kdc_isa = id; +#endif + dev_attach(&kdc_sio[unit]); +} + +static int +sioprobe(dev) +#ifdef PC98 + struct pc98_device *dev; +#else + struct isa_device *dev; +#endif +{ + static bool_t already_init; + bool_t failures[10]; + int fn; +#ifdef PC98 + struct pc98_device *idev; +#else + struct isa_device *idev; +#endif + Port_t iobase; + u_char mcr_image; + int result; +#ifdef PC98 + struct pc98_device *xdev; + int irqout=0; + int ret = 0; + int tmp; + struct siodev iod; +#else + struct isa_device *xdev; +#endif + + sioregisterdev(dev); + + if (!already_init) { + /* + * Turn off MCR_IENABLE for all likely serial ports. An unused + * port with its MCR_IENABLE gate open will inhibit interrupts + * from any used port that shares the interrupt vector. + * XXX the gate enable is elsewhere for some multiports. + */ +#ifdef PC98 + for (xdev = pc98_devtab_tty; xdev->id_driver != NULL; xdev++) +#else + for (xdev = isa_devtab_tty; xdev->id_driver != NULL; xdev++) +#endif + if (xdev->id_driver == &siodriver && xdev->id_enabled) +#ifdef PC98 + if (IS_PC98IN(xdev->id_iobase)) + outb(xdev->id_iobase + 2, 0xf2); + else +#else + outb(xdev->id_iobase + com_mcr, 0); +#endif +#if NCRD > 0 + /* + * If PC-Card probe required, then register driver with + * slot manager. + */ + pccard_add_driver(&sio_info); +#endif + already_init = TRUE; + } + +#ifdef PC98 + /* + * If the port is i8251 UART (internal, B98_01) + */ + if(pc98_check_if_type(dev->id_iobase, &iod) == -1) + return 0; + if(IS_8251(iod.if_type)){ + if ( iod.irq > 0 ) + dev->id_irq = (1 << iod.irq); + outb(iod.cmd, 0); + DELAY(10); + outb(iod.cmd, 0); + DELAY(10); + outb(iod.cmd, 0); + DELAY(10); + outb(iod.cmd, CMD8251_RESET); + DELAY(1000); /* for a while...*/ + outb(iod.cmd, 0xf2); /* MODE (dummy) */ + DELAY(10); + outb(iod.cmd, 0x01); /* CMD (dummy) */ + DELAY(1000); /* for a while...*/ + if (( inb(iod.sts) & STS8251_TxEMP ) == 0 ) { + ret = 0; + } + switch (iod.if_type) { + case COM_IF_INTERNAL: + COM_INT_DISABLE + tmp = ( inb( iod.ctrl ) & ~(IEN_Rx|IEN_TxEMP|IEN_Tx)); + outb( iod.ctrl, tmp|IEN_TxEMP ); + ret = pc98_irq_pending(dev) ? 4 : 0; + outb( iod.ctrl, tmp ); + COM_INT_ENABLE + break; +#ifdef COM_IF_B98_01 + case COM_IF_B98_01: + /* B98_01 doesn't activate TxEMP interrupt line + when being reset, so we can't check irq pending.*/ + ret = 4; + break; +#endif + } + if (epson_machine_id==0x20) { /* XXX */ + ret = 4; + } + return ret; + } +#endif /* PC98 */ + /* + * If the device is on a multiport card and has an AST/4 + * compatible interrupt control register, initialize this + * register and prepare to leave MCR_IENABLE clear in the mcr. + * Otherwise, prepare to set MCR_IENABLE in the mcr. + * Point idev to the device struct giving the correct id_irq. + * This is the struct for the master device if there is one. + */ + idev = dev; + mcr_image = MCR_IENABLE; +#ifdef COM_MULTIPORT + if (COM_ISMULTIPORT(dev)) { +#ifdef PC98 + idev = find_pc98dev(pc98_devtab_tty, &siodriver, +#else + idev = find_isadev(isa_devtab_tty, &siodriver, +#endif + COM_MPMASTER(dev)); + if (idev == NULL) { + printf("sio%d: master device %d not configured\n", + dev->id_unit, COM_MPMASTER(dev)); + return (0); + } +#ifndef PC98 + if (!COM_NOTAST4(dev)) { + outb(idev->id_iobase + com_scr, + idev->id_irq ? 0x80 : 0); + mcr_image = 0; + } +#endif /* !PC98 */ + } +#endif /* COM_MULTIPORT */ + if (idev->id_irq == 0) + mcr_image = 0; + +#ifdef PC98 + switch(idev->id_irq){ + case IRQ3: irqout = 4; break; + case IRQ5: irqout = 5; break; + case IRQ6: irqout = 6; break; + case IRQ12: irqout = 7; break; + default: + printf("sio%d: irq configuration error\n",dev->id_unit); + return (0); + } + outb(dev->id_iobase+0x1000, irqout); +#endif + bzero(failures, sizeof failures); + iobase = dev->id_iobase; + + /* + * We don't want to get actual interrupts, just masked ones. + * Interrupts from this line should already be masked in the ICU, + * but mask them in the processor as well in case there are some + * (misconfigured) shared interrupts. + */ + disable_intr(); +/* EXTRA DELAY? */ + + /* + * XXX DELAY() reenables CPU interrupts. This is a problem for + * shared interrupts after the first device using one has been + * successfully probed - config_isadev() has enabled the interrupt + * in the ICU. + */ +#ifdef PC98 + outb(IO_ICU1 + 2, 0xff); +#else + outb(IO_ICU1 + 1, 0xff); +#endif + + /* + * Initialize the speed and the word size and wait long enough to + * drain the maximum of 16 bytes of junk in device output queues. + * The speed is undefined after a master reset and must be set + * before relying on anything related to output. There may be + * junk after a (very fast) soft reboot and (apparently) after + * master reset. + * XXX what about the UART bug avoided by waiting in comparam()? + * We don't want to to wait long enough to drain at 2 bps. + */ + outb(iobase + com_cfcr, CFCR_DLAB); + outb(iobase + com_dlbl, COMBRD(9600) & 0xff); + outb(iobase + com_dlbh, (u_int) COMBRD(9600) >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + DELAY((16 + 1) * 1000000 / (9600 / 10)); + + /* + * Enable the interrupt gate and disable device interupts. This + * should leave the device driving the interrupt line low and + * guarantee an edge trigger if an interrupt can be generated. + */ +/* EXTRA DELAY? */ + outb(iobase + com_mcr, mcr_image); + outb(iobase + com_ier, 0); + + /* + * Attempt to set loopback mode so that we can send a null byte + * without annoying any external device. + */ +/* EXTRA DELAY? */ + outb(iobase + com_mcr, mcr_image | MCR_LOOPBACK); + + /* + * Attempt to generate an output interrupt. On 8250's, setting + * IER_ETXRDY generates an interrupt independent of the current + * setting and independent of whether the THR is empty. On 16450's, + * setting IER_ETXRDY generates an interrupt independent of the + * current setting. On 16550A's, setting IER_ETXRDY only + * generates an interrupt when IER_ETXRDY is not already set. + */ + outb(iobase + com_ier, IER_ETXRDY); + + /* + * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate + * an interrupt. They'd better generate one for actually doing + * output. Loopback may be broken on the same incompatibles but + * it's unlikely to do more than allow the null byte out. + */ + outb(iobase + com_data, 0); + DELAY((1 + 2) * 1000000 / (9600 / 10)); + + /* + * Turn off loopback mode so that the interrupt gate works again + * (MCR_IENABLE was hidden). This should leave the device driving + * an interrupt line high. It doesn't matter if the interrupt + * line oscillates while we are not looking at it, since interrupts + * are disabled. + */ +/* EXTRA DELAY? */ + outb(iobase + com_mcr, mcr_image); + + /* + * Check that + * o the CFCR, IER and MCR in UART hold the values written to them + * (the values happen to be all distinct - this is good for + * avoiding false positive tests from bus echoes). + * o an output interrupt is generated and its vector is correct. + * o the interrupt goes away when the IIR in the UART is read. + */ +/* EXTRA DELAY? */ + failures[0] = inb(iobase + com_cfcr) - CFCR_8BITS; + failures[1] = inb(iobase + com_ier) - IER_ETXRDY; + failures[2] = inb(iobase + com_mcr) - mcr_image; + DELAY(1000); /* XXX */ + if (idev->id_irq != 0) +#ifdef PC98 + failures[3] = pc98_irq_pending(idev) ? 0 : 1; +#else + failures[3] = isa_irq_pending(idev) ? 0 : 1; +#endif + failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; + DELAY(1000); /* XXX */ +#ifdef PC98 + if (idev->id_irq != 0) + failures[5] = pc98_irq_pending(idev) ? 1 : 0; +#else + if (idev->id_irq != 0) + failures[5] = isa_irq_pending(idev) ? 1 : 0; +#endif + failures[6] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; + + /* + * Turn off all device interrupts and check that they go off properly. + * Leave MCR_IENABLE alone. For ports without a master port, it gates + * the OUT2 output of the UART to + * the ICU input. Closing the gate would give a floating ICU input + * (unless there is another device driving at) and spurious interrupts. + * (On the system that this was first tested on, the input floats high + * and gives a (masked) interrupt as soon as the gate is closed.) + */ + outb(iobase + com_ier, 0); + outb(iobase + com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ + failures[7] = inb(iobase + com_ier); + DELAY(1000); /* XXX */ +#ifdef PC98 + if (idev->id_irq != 0) + failures[8] = pc98_irq_pending(idev) ? 1 : 0; +#else + if (idev->id_irq != 0) + failures[8] = isa_irq_pending(idev) ? 1 : 0; +#endif + failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; + +#ifdef PC98 + outb(IO_ICU1 + 2, imen); /* XXX */ +#else + outb(IO_ICU1 + 1, imen); /* XXX */ +#endif + enable_intr(); + + result = IO_COMSIZE; + for (fn = 0; fn < sizeof failures; ++fn) + if (failures[fn]) { + outb(iobase + com_mcr, 0); + result = 0; + if (COM_VERBOSE(dev)) + printf("sio%d: probe test %d failed\n", + dev->id_unit, fn); + } + return (result); +} + +#ifdef COM_ESP +static int +espattach(isdp, com, esp_port) + struct isa_device *isdp; + struct com_s *com; + Port_t esp_port; +{ + u_char dips; + u_char val; + + /* + * Check the ESP-specific I/O port to see if we're an ESP + * card. If not, return failure immediately. + */ + if ((inb(esp_port) & 0xf3) == 0) { + printf(" port 0x%x is not an ESP board?\n", esp_port); + return (0); + } + + /* + * We've got something that claims to be a Hayes ESP card. + * Let's hope so. + */ + + /* Get the dip-switch configuration */ + outb(esp_port + ESP_CMD1, ESP_GETDIPS); + dips = inb(esp_port + ESP_STATUS1); + + /* + * Bits 0,1 of dips say which COM port we are. + */ + if (com->iobase == likely_com_ports[dips & 0x03]) + printf(" : ESP"); + else { + printf(" esp_port has com %d\n", dips & 0x03); + return (0); + } + + /* + * Check for ESP version 2.0 or later: bits 4,5,6 = 010. + */ + outb(esp_port + ESP_CMD1, ESP_GETTEST); + val = inb(esp_port + ESP_STATUS1); /* clear reg 1 */ + val = inb(esp_port + ESP_STATUS2); + if ((val & 0x70) < 0x20) { + printf("-old (%o)", val & 0x70); + return (0); + } + + /* + * Check for ability to emulate 16550: bit 7 == 1 + */ + if ((dips & 0x80) == 0) { + printf(" slave"); + return (0); + } + + /* + * Okay, we seem to be a Hayes ESP card. Whee. + */ + com->esp = TRUE; + com->esp_port = esp_port; + return (1); +} +#endif /* COM_ESP */ + +static int +sioattach(isdp) +#ifdef PC98 + struct pc98_device *isdp; +#else + struct isa_device *isdp; +#endif +{ + struct com_s *com; + dev_t dev; +#ifdef COM_ESP + Port_t *espp; +#endif + Port_t iobase; + int s; + int unit; + + isdp->id_ri_flags |= RI_FAST; + iobase = isdp->id_iobase; + unit = isdp->id_unit; + com = malloc(sizeof *com, M_TTYS, M_NOWAIT); + if (com == NULL) + return (0); + + /* + * sioprobe() has initialized the device registers as follows: + * o cfcr = CFCR_8BITS. + * It is most important that CFCR_DLAB is off, so that the + * data port is not hidden when we enable interrupts. + * o ier = 0. + * Interrupts are only enabled when the line is open. + * o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible + * interrupt control register or the config specifies no irq. + * Keeping MCR_DTR and MCR_RTS off might stop the external + * device from sending before we are ready. + */ + bzero(com, sizeof *com); + com->unit = unit; + com->cfcr_image = CFCR_8BITS; + com->dtr_wait = 3 * hz; + com->loses_outints = COM_LOSESOUTINTS(isdp) != 0; + com->no_irq = isdp->id_irq == 0; + com->tx_fifo_size = 1; + com->iptr = com->ibuf = com->ibuf1; + com->ibufend = com->ibuf1 + RS_IBUFSIZE; + com->ihighwater = com->ibuf1 + RS_IHIGHWATER; + com->obufs[0].l_head = com->obuf1; + com->obufs[1].l_head = com->obuf2; + + com->iobase = iobase; +#ifdef PC98 + if(pc98_set_ioport(com, iobase) == -1) + if((iobase & 0x0f0) == 0xd0) { + com->pc98_if_type = MC16550; + com->data_port = iobase + com_data; + com->int_id_port = iobase + com_iir; + com->modem_ctl_port = iobase + com_mcr; + com->mcr_image = inb(com->modem_ctl_port); + com->line_status_port = iobase + com_lsr; + com->modem_status_port = iobase + com_msr; + } +#else /* not PC98 */ + com->data_port = iobase + com_data; + com->int_id_port = iobase + com_iir; + com->modem_ctl_port = iobase + com_mcr; + com->mcr_image = inb(com->modem_ctl_port); + com->line_status_port = iobase + com_lsr; + com->modem_status_port = iobase + com_msr; +#endif + + /* + * We don't use all the flags from <sys/ttydefaults.h> since they + * are only relevant for logins. It's important to have echo off + * initially so that the line doesn't start blathering before the + * echo flag can be turned off. + */ + com->it_in.c_iflag = 0; + com->it_in.c_oflag = 0; + com->it_in.c_cflag = TTYDEF_CFLAG; + com->it_in.c_lflag = 0; + if (unit == comconsole) { +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + DELAY(100000); +#endif + com->it_in.c_iflag = TTYDEF_IFLAG; + com->it_in.c_oflag = TTYDEF_OFLAG; + com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; + com->it_in.c_lflag = TTYDEF_LFLAG; + com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; + } + termioschars(&com->it_in); + com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; + com->it_out = com->it_in; + + /* attempt to determine UART type */ + printf("sio%d: type", unit); + +#ifdef DSI_SOFT_MODEM + if((inb(iobase+7) ^ inb(iobase+7)) & 0x80) { + printf(" Digicom Systems, Inc. SoftModem"); + kdc_sio[unit].kdc_description = + "Serial port: Digicom Systems SoftModem"; + goto determined_type; + } +#endif /* DSI_SOFT_MODEM */ + +#ifndef PC98 +#ifdef COM_MULTIPORT + if (!COM_ISMULTIPORT(isdp)) +#endif + { + u_char scr; + u_char scr1; + u_char scr2; + + scr = inb(iobase + com_scr); + outb(iobase + com_scr, 0xa5); + scr1 = inb(iobase + com_scr); + outb(iobase + com_scr, 0x5a); + scr2 = inb(iobase + com_scr); + outb(iobase + com_scr, scr); + if (scr1 != 0xa5 || scr2 != 0x5a) { + printf(" 8250"); + kdc_sio[unit].kdc_description = + "Serial port: National 8250 or compatible"; + goto determined_type; + } + } +#endif /* !PC98 */ +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + com_int_TxRx_disable( com ); + com_cflag_and_speed_set( com, com->it_in.c_cflag, + comdefaultrate ); + com_tiocm_bic( com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE ); + com_send_break_off( com ); + switch(com->pc98_if_type){ + case COM_IF_INTERNAL: + printf(" 8251 (internal)"); + kdc_sio[unit].kdc_description = + "Serial port: PC-9801 internal"; + break; +#ifdef COM_IF_PC9861K + case COM_IF_PC9861K: + printf(" 8251 (PC9861K)"); + kdc_sio[unit].kdc_description = + "Serial port: PC-9861K"; + break; +#endif +#ifdef COM_IF_PIO9032B + case COM_IF_PIO9032B: + printf(" 8251 (PIO9032B)"); + kdc_sio[unit].kdc_description = + "Serial port: PIO9032B"; + break; +#endif +#ifdef COM_IF_B98_01 + case COM_IF_B98_01: + printf(" 8251 (B98_01)"); + kdc_sio[unit].kdc_description = + "Serial port: B98_01"; + break; +#endif + } + } else { +#endif /* PC98 */ + outb(iobase + com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); + DELAY(100); + switch (inb(com->int_id_port) & IIR_FIFO_MASK) { + case FIFO_RX_LOW: + printf(" 16450"); + kdc_sio[unit].kdc_description = + "Serial port: National 16450 or compatible"; + break; + case FIFO_RX_MEDL: + printf(" 16450?"); + kdc_sio[unit].kdc_description = + "Serial port: maybe National 16450"; + break; + case FIFO_RX_MEDH: + printf(" 16550?"); + kdc_sio[unit].kdc_description = + "Serial port: maybe National 16550"; + break; + case FIFO_RX_HIGH: + printf(" 16550A"); + if (COM_NOFIFO(isdp)) { + printf(" fifo disabled"); + kdc_sio[unit].kdc_description = + "Serial port: National 16550A, FIFO disabled"; + } else { + com->hasfifo = TRUE; + com->tx_fifo_size = 16; + kdc_sio[unit].kdc_description = + "Serial port: National 16550A or compatible"; +#ifdef COM_ESP + for (espp = likely_esp_ports; *espp != 0; espp++) + if (espattach(isdp, com, *espp)) { + com->tx_fifo_size = 1024; + kdc_sio[unit].kdc_description = + "Serial port: Hayes ESP"; + break; + } +#endif + } +#if 0 + /* + * Check for the Startech ST16C650 chip. + * it has a shadow register under the com_iir, + * which can only be accessed when cfcr == 0xff + */ + { + u_char i, j; + + i = inb(iobase + com_iir); + outb(iobase + com_cfcr, 0xff); + outb(iobase + com_iir, 0x0); + outb(iobase + com_cfcr, CFCR_8BITS); + j = inb(iobase + com_iir); + outb(iobase + com_iir, i); + if (i != j) { + printf(" 16550A"); + } else { + com->tx_fifo_size = 32; + printf(" 16650"); + kdc_sio[unit].kdc_description = + "Serial port: Startech 16C650 or similar"; + } + if (!com->tx_fifo_size) + printf(" fifo disabled"); + } +#endif + break; + } +#ifdef COM_ESP + if (com->esp) { + outb(iobase + com_fifo, + FIFO_DMA_MODE | FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST + | FIFO_RX_MEDH); + + /* Set 16550 compatibility mode. */ + outb(com->esp_port + ESP_CMD1, ESP_SETMODE); + outb(com->esp_port + ESP_CMD2, + ESP_MODE_SCALE | ESP_MODE_RTS | ESP_MODE_FIFO); + + /* Set RTS/CTS flow control. */ + outb(com->esp_port + ESP_CMD1, ESP_SETFLOWTYPE); + outb(com->esp_port + ESP_CMD2, ESP_FLOW_RTS); + outb(com->esp_port + ESP_CMD2, ESP_FLOW_CTS); + + /* Set flow-control levels. */ + outb(com->esp_port + ESP_CMD1, ESP_SETRXFLOW); + outb(com->esp_port + ESP_CMD2, HIBYTE(768)); + outb(com->esp_port + ESP_CMD2, LOBYTE(768)); + outb(com->esp_port + ESP_CMD2, HIBYTE(512)); + outb(com->esp_port + ESP_CMD2, LOBYTE(512)); + } +#endif /* COM_ESP */ + outb(iobase + com_fifo, 0); +determined_type: ; + +#ifdef COM_MULTIPORT + if (COM_ISMULTIPORT(isdp)) { + com->multiport = TRUE; + printf(" (multiport"); + if (unit == COM_MPMASTER(isdp)) + printf(" master"); + printf(")"); +#ifdef PC98 + com->no_irq = find_pc98dev(pc98_devtab_tty, &siodriver, +#else + com->no_irq = find_isadev(isa_devtab_tty, &siodriver, +#endif + COM_MPMASTER(isdp))->id_irq == 0; + } +#endif /* COM_MULTIPORT */ +#ifdef PC98 + } +#endif + printf("\n"); + + kdc_sio[unit].kdc_state = (unit == comconsole) ? DC_BUSY : DC_IDLE; + + s = spltty(); + com_addr(unit) = com; + splx(s); + + dev = makedev(CDEV_MAJOR, 0); + cdevsw_add(&dev, &sio_cdevsw, NULL); +#ifdef DEVFS + /* devsw, minor, type, uid, gid, perm, fmt, ... */ + com->devfs_token_ttyd = devfs_add_devswf(&sio_cdevsw, + unit, DV_CHR, + UID_ROOT, GID_WHEEL, 0600, "ttyd%n", unit); + com->devfs_token_ttyi = devfs_add_devswf(&sio_cdevsw, + unit | CONTROL_INIT_STATE, DV_CHR, + UID_ROOT, GID_WHEEL, 0600, "ttyid%n", unit); + com->devfs_token_ttyl = devfs_add_devswf(&sio_cdevsw, + unit | CONTROL_LOCK_STATE, DV_CHR, + UID_ROOT, GID_WHEEL, 0600, "ttyld%n", unit); + com->devfs_token_cuaa = devfs_add_devswf(&sio_cdevsw, + unit | CALLOUT_MASK, DV_CHR, + UID_UUCP, GID_DIALER, 0660, "cuaa%n", unit); + com->devfs_token_cuai = devfs_add_devswf(&sio_cdevsw, + unit | CALLOUT_MASK | CONTROL_INIT_STATE, DV_CHR, + UID_UUCP, GID_DIALER, 0660, "cuaia%n", unit); + com->devfs_token_cual = devfs_add_devswf(&sio_cdevsw, + unit | CALLOUT_MASK | CONTROL_LOCK_STATE, DV_CHR, + UID_UUCP, GID_DIALER, 0660, "cuala%n", unit); +#endif + return (1); +} + +static int +sioopen(dev, flag, mode, p) + dev_t dev; + int flag; + int mode; + struct proc *p; +{ + struct com_s *com; + int error; + Port_t iobase; + int mynor; + int s; + struct tty *tp; + int unit; + + mynor = minor(dev); + unit = MINOR_TO_UNIT(mynor); + if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) + return (ENXIO); + if (com->gone) + return (ENXIO); + if (mynor & CONTROL_MASK) + return (0); +#if 0 /* XXX */ + tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); +#else + tp = com->tp = &sio_tty[unit]; +#endif + s = spltty(); + /* + * We jump to this label after all non-interrupted sleeps to pick + * up any changes of the device state. + */ +open_top: + while (com->state & CS_DTR_OFF) { + error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); + if (com_addr(unit) == NULL) + return (ENXIO); + if (error != 0 || com->gone) + goto out; + } + kdc_sio[unit].kdc_state = DC_BUSY; + if (tp->t_state & TS_ISOPEN) { + /* + * The device is open, so everything has been initialized. + * Handle conflicts. + */ + if (mynor & CALLOUT_MASK) { + if (!com->active_out) { + error = EBUSY; + goto out; + } + } else { + if (com->active_out) { + if (flag & O_NONBLOCK) { + error = EBUSY; + goto out; + } + error = tsleep(&com->active_out, + TTIPRI | PCATCH, "siobi", 0); + if (com_addr(unit) == NULL) + return (ENXIO); + if (error != 0 || com->gone) + goto out; + goto open_top; + } + } + if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { + error = EBUSY; + goto out; + } + } else { + /* + * The device isn't open, so there are no conflicts. + * Initialize it. Initialization is done twice in many + * cases: to preempt sleeping callin opens if we are + * callout, and to complete a callin open after DCD rises. + */ + tp->t_oproc = comstart; + tp->t_param = comparam; + tp->t_dev = dev; + tp->t_termios = mynor & CALLOUT_MASK + ? com->it_out : com->it_in; +#ifdef PC98 + if(!IS_8251(com->pc98_if_type)) +#endif + (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET); + com->poll = com->no_irq; + com->poll_output = com->loses_outints; + ++com->wopeners; + error = comparam(tp, &tp->t_termios); + --com->wopeners; + if (error != 0) + goto out; +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + com_tiocm_bis(com, TIOCM_DTR|TIOCM_RTS); + pc98_msrint_start(dev); + } +#endif + /* + * XXX we should goto open_top if comparam() slept. + */ + ttsetwater(tp); + iobase = com->iobase; + if (com->hasfifo) { + /* + * (Re)enable and drain fifos. + * + * Certain SMC chips cause problems if the fifos + * are enabled while input is ready. Turn off the + * fifo if necessary to clear the input. We test + * the input ready bit after enabling the fifos + * since we've already enabled them in comparam() + * and to handle races between enabling and fresh + * input. + */ + while (TRUE) { + outb(iobase + com_fifo, + FIFO_RCV_RST | FIFO_XMT_RST + | com->fifo_image); + DELAY(100); + if (!(inb(com->line_status_port) & LSR_RXRDY)) + break; + outb(iobase + com_fifo, 0); + DELAY(100); + (void) inb(com->data_port); + } + } + + disable_intr(); +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + com_tiocm_bis(com, TIOCM_LE); + com->pc98_prev_modem_status = + pc98_get_modem_status(com); + com_int_Rx_enable(com); + } else { +#endif + (void) inb(com->line_status_port); + (void) inb(com->data_port); + com->prev_modem_status = com->last_modem_status + = inb(com->modem_status_port); + outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS + | IER_EMSC); +#ifdef PC98 + } +#endif + enable_intr(); + /* + * Handle initial DCD. Callout devices get a fake initial + * DCD (trapdoor DCD). If we are callout, then any sleeping + * callin opens get woken up and resume sleeping on "siobi" + * instead of "siodcd". + */ + /* + * XXX `mynor & CALLOUT_MASK' should be + * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where + * TRAPDOOR_CARRIER is the default initial state for callout + * devices and SOFT_CARRIER is like CLOCAL except it hides + * the true carrier. + */ +#ifdef PC98 + if ((IS_8251(com->pc98_if_type) && + (pc98_get_modem_status(com) & TIOCM_CAR)) || + (!IS_8251(com->pc98_if_type) && + (com->prev_modem_status & MSR_DCD)) || + mynor & CALLOUT_MASK) +#else + if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) +#endif + (*linesw[tp->t_line].l_modem)(tp, 1); + } + /* + * Wait for DCD if necessary. + */ + if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) + && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { + ++com->wopeners; + error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); + if (com_addr(unit) == NULL) + return (ENXIO); + --com->wopeners; + if (error != 0 || com->gone) + goto out; + goto open_top; + } + error = (*linesw[tp->t_line].l_open)(dev, tp); + disc_optim(tp, &tp->t_termios, com); + if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) + com->active_out = TRUE; + siosettimeout(); +out: + splx(s); + if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) + comhardclose(com); + return (error); +} + +static int +sioclose(dev, flag, mode, p) + dev_t dev; + int flag; + int mode; + struct proc *p; +{ + struct com_s *com; + int mynor; + int s; + struct tty *tp; + + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (0); + com = com_addr(MINOR_TO_UNIT(mynor)); + tp = com->tp; + s = spltty(); + (*linesw[tp->t_line].l_close)(tp, flag); +#ifdef PC98 + com->modem_checking = 0; +#endif + disc_optim(tp, &tp->t_termios, com); + siostop(tp, FREAD | FWRITE); + comhardclose(com); + ttyclose(tp); + siosettimeout(); + splx(s); + if (com->gone) { + printf("sio%d: gone\n", com->unit); + s = spltty(); + com_addr(com->unit) = 0; + bzero(tp,sizeof *tp); + bzero(com,sizeof *com); + free(com,M_TTYS); + splx(s); + } + return (0); +} + +static void +comhardclose(com) + struct com_s *com; +{ + Port_t iobase; + int s; + struct tty *tp; + int unit; + + unit = com->unit; + iobase = com->iobase; + s = spltty(); + com->poll = FALSE; + com->poll_output = FALSE; + com->do_timestamp = 0; +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + com_send_break_off(com); + else +#endif + outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); + { +#ifdef PC98 + int tmp; + if(IS_8251(com->pc98_if_type)) + com_int_TxRx_disable(com); + else +#endif + outb(iobase + com_ier, 0); + tp = com->tp; +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + tmp = pc98_get_modem_status(com) & TIOCM_CAR; + else + tmp = com->prev_modem_status & MSR_DCD; +#endif + if (tp->t_cflag & HUPCL + /* + * XXX we will miss any carrier drop between here and the + * next open. Perhaps we should watch DCD even when the + * port is closed; it is not sufficient to check it at + * the next open because it might go up and down while + * we're not watching. + */ + || !com->active_out +#ifdef PC98 + && !(tmp) +#else + && !(com->prev_modem_status & MSR_DCD) +#endif + && !(com->it_in.c_cflag & CLOCAL) + || !(tp->t_state & TS_ISOPEN)) { +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + com_tiocm_bic(com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE); + else +#endif + (void)commctl(com, TIOCM_DTR, DMBIC); + if (com->dtr_wait != 0) { + timeout(siodtrwakeup, com, com->dtr_wait); + com->state |= CS_DTR_OFF; + } + } +#ifdef PC98 + else { + if(IS_8251(com->pc98_if_type)) + com_tiocm_bic(com, TIOCM_LE ); + } +#endif + } + if (com->hasfifo) { + /* + * Disable fifos so that they are off after controlled + * reboots. Some BIOSes fail to detect 16550s when the + * fifos are enabled. + */ + outb(iobase + com_fifo, 0); + } + com->active_out = FALSE; + wakeup(&com->active_out); + wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ + if (!(com->state & CS_DTR_OFF) && unit != comconsole) + kdc_sio[unit].kdc_state = DC_IDLE; + splx(s); +} + +static int +sioread(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + int mynor; + int unit; + struct tty *tp; + + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (ENODEV); + unit = MINOR_TO_UNIT(mynor); + if (com_addr(unit)->gone) + return (ENODEV); + tp = com_addr(unit)->tp; + return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); +} + +static int +siowrite(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + int mynor; + struct tty *tp; + int unit; + + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (ENODEV); + + unit = MINOR_TO_UNIT(mynor); + if (com_addr(unit)->gone) + return (ENODEV); + tp = com_addr(unit)->tp; + /* + * (XXX) We disallow virtual consoles if the physical console is + * a serial port. This is in case there is a display attached that + * is not the console. In that situation we don't need/want the X + * server taking over the console. + */ + if (constty != NULL && unit == comconsole) + constty = NULL; + return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); +} + +static void +siodtrwakeup(chan) + void *chan; +{ + struct com_s *com; + + com = (struct com_s *)chan; + com->state &= ~CS_DTR_OFF; + if (com->unit != comconsole) + kdc_sio[com->unit].kdc_state = DC_IDLE; + wakeup(&com->dtr_wait); +} + +/* Interrupt routine for timekeeping purposes */ +void +siointrts(unit) + int unit; +{ + /* + * XXX microtime() reenables CPU interrupts. We can't afford to + * be interrupted and don't want to slow down microtime(), so lock + * out interrupts in another way. + */ +#ifdef PC98 + outb(IO_ICU1 + 2, 0xff); +#else /* IBM-PC */ + outb(IO_ICU1 + 1, 0xff); +#endif /* PC98 */ + microtime(&intr_timestamp); + disable_intr(); +#ifdef PC98 + outb(IO_ICU1 + 2, imen); +#else /* IBM_PC */ + outb(IO_ICU1 + 1, imen); +#endif /* PC98 */ + + siointr(unit); +} + +void +siointr(unit) + int unit; +{ +#ifndef COM_MULTIPORT + siointr1(com_addr(unit)); +#else /* COM_MULTIPORT */ + struct com_s *com; + bool_t possibly_more_intrs; + + /* + * Loop until there is no activity on any port. This is necessary + * to get an interrupt edge more than to avoid another interrupt. + * If the IRQ signal is just an OR of the IRQ signals from several + * devices, then the edge from one may be lost because another is + * on. + */ + do { + possibly_more_intrs = FALSE; + for (unit = 0; unit < NSIO; ++unit) { + com = com_addr(unit); +#ifdef PC98 + if (com != NULL + && !com->gone + && IS_8251(com->pc98_if_type)){ + siointr1(com); + } else +#endif /* PC98 */ + if (com != NULL + && !com->gone + && (inb(com->int_id_port) & IIR_IMASK) + != IIR_NOPEND) { + siointr1(com); + possibly_more_intrs = TRUE; + } + } + } while (possibly_more_intrs); +#endif /* COM_MULTIPORT */ +} + +static void +siointr1(com) + struct com_s *com; +{ + u_char line_status; + u_char modem_status; + u_char *ioptr; + u_char recv_data; +#ifdef PC98 + u_char tmp=0; +recv_data=0; +#endif /* PC98 */ + + if (com->do_timestamp) + /* XXX a little bloat here... */ + com->timestamp = intr_timestamp; + while (TRUE) { +#ifdef PC98 +status_read:; + if (IS_8251(com->pc98_if_type)) { + tmp = inb(com->sts_port); +more_intr: + line_status = 0; + if (tmp & STS8251_TxRDY) line_status |= LSR_TXRDY; + if (tmp & STS8251_RxRDY) line_status |= LSR_RXRDY; + if (tmp & STS8251_TxEMP) line_status |= LSR_TSRE; + if (tmp & STS8251_PE) line_status |= LSR_PE; + if (tmp & STS8251_OE) line_status |= LSR_OE; + if (tmp & STS8251_FE) line_status |= LSR_FE; + if (tmp & STS8251_BD_SD) line_status |= LSR_BI; + } else +#endif /* PC98 */ + line_status = inb(com->line_status_port); + + /* input event? (check first to help avoid overruns) */ + while (line_status & LSR_RCV_MASK) { + /* break/unnattached error bits or real input? */ +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + recv_data = inb(com->data_port); + if(tmp & 0x78){ + pc98_i8251_or_cmd(com,CMD8251_ER); + recv_data = 0; + } + } else { +#endif /* PC98 */ + if (!(line_status & LSR_RXRDY)) + recv_data = 0; + else + recv_data = inb(com->data_port); +#ifdef PC98 + } +#endif + if (line_status & (LSR_PE|LSR_FE|LSR_BI)) { +#ifdef DDB +#ifdef BREAK_TO_DEBUGGER + if (line_status & LSR_BI + && com->unit == comconsole) { + Debugger("serial console break"); + goto cont; + } +#endif +#endif + /* + Don't store PE if IGNPAR and BI if IGNBRK, + this hack allows "raw" tty optimization + works even if IGN* is set. + */ + if ( com->tp == NULL + || !(com->tp->t_state & TS_ISOPEN) + || (line_status & (LSR_PE|LSR_FE)) + && (com->tp->t_iflag & IGNPAR) + || (line_status & LSR_BI) + && (com->tp->t_iflag & IGNBRK)) + goto cont; + if ( (line_status & (LSR_PE|LSR_FE)) + && (com->tp->t_state & TS_CAN_BYPASS_L_RINT) + && ((line_status & LSR_FE) + || (line_status & LSR_PE) + && (com->tp->t_iflag & INPCK))) + recv_data = 0; + } + + ++com->bytes_in; + if (com->hotchar != 0 && recv_data == com->hotchar) + setsofttty(); + ioptr = com->iptr; + if (ioptr >= com->ibufend) + CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); + else { + ++com_events; + schedsofttty(); +#if 0 /* for testing input latency vs efficiency */ +if (com->iptr - com->ibuf == 8) + setsofttty(); +#endif + ioptr[0] = recv_data; + ioptr[CE_INPUT_OFFSET] = line_status; + com->iptr = ++ioptr; + if (ioptr == com->ihighwater + && com->state & CS_RTS_IFLOW) +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + com_tiocm_bic(com, TIOCM_RTS); + else +#endif + outb(com->modem_ctl_port, + com->mcr_image &= ~MCR_RTS); + if (line_status & LSR_OE) + CE_RECORD(com, CE_OVERRUN); + } +cont: + /* + * "& 0x7F" is to avoid the gcc-1.40 generating a slow + * jump from the top of the loop to here + */ +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + goto status_read; + else +#endif + line_status = inb(com->line_status_port) & 0x7F; + } + + /* modem status change? (always check before doing output) */ +#ifdef PC98 + if(!IS_8251(com->pc98_if_type)){ +#endif + modem_status = inb(com->modem_status_port); + if (modem_status != com->last_modem_status) { + /* + * Schedule high level to handle DCD changes. Note + * that we don't use the delta bits anywhere. Some + * UARTs mess them up, and it's easy to remember the + * previous bits and calculate the delta. + */ + com->last_modem_status = modem_status; + if (!(com->state & CS_CHECKMSR)) { + com_events += LOTS_OF_EVENTS; + com->state |= CS_CHECKMSR; + setsofttty(); + } + + /* handle CTS change immediately for crisp flow ctl */ + if (com->state & CS_CTS_OFLOW) { + if (modem_status & MSR_CTS) + com->state |= CS_ODEVREADY; + else + com->state &= ~CS_ODEVREADY; + } + } +#ifdef PC98 + } +#endif + + /* output queued and everything ready? */ + if (line_status & LSR_TXRDY + && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) { + ioptr = com->obufq.l_head; + if (com->tx_fifo_size > 1) { + u_int ocount; + + ocount = com->obufq.l_tail - ioptr; + if (ocount > com->tx_fifo_size) + ocount = com->tx_fifo_size; + com->bytes_out += ocount; + do + outb(com->data_port, *ioptr++); + while (--ocount != 0); + } else { + outb(com->data_port, *ioptr++); + ++com->bytes_out; + } +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + if ( !(pc98_check_i8251_interrupt(com) & IEN_TxFLAG) ) + com_int_Tx_enable(com); +#endif + com->obufq.l_head = ioptr; + if (ioptr >= com->obufq.l_tail) { + struct lbq *qp; +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + if ( pc98_check_i8251_interrupt(com) & IEN_TxFLAG ) + com_int_Tx_disable(com); +#endif + qp = com->obufq.l_next; + qp->l_queued = FALSE; + qp = qp->l_next; + if (qp != NULL) { + com->obufq.l_head = qp->l_head; + com->obufq.l_tail = qp->l_tail; + com->obufq.l_next = qp; + } else { + /* output just completed */ + com->state &= ~CS_BUSY; + } + if (!(com->state & CS_ODONE)) { + com_events += LOTS_OF_EVENTS; + com->state |= CS_ODONE; + setsofttty(); /* handle at high level ASAP */ + } + } + } +#ifdef PC98 + else if (line_status & LSR_TXRDY) { + if(IS_8251(com->pc98_if_type)) + if ( pc98_check_i8251_interrupt(com) & IEN_TxFLAG ) + com_int_Tx_disable(com); + } + if(IS_8251(com->pc98_if_type)) + if ((tmp = inb(com->sts_port)) & STS8251_RxRDY) + goto more_intr; +#endif + + /* finished? */ +#ifndef COM_MULTIPORT +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + return; +#endif + if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) +#endif /* COM_MULTIPORT */ + return; + } +} + +static int +sioioctl(dev, cmd, data, flag, p) + dev_t dev; + int cmd; + caddr_t data; + int flag; + struct proc *p; +{ + struct com_s *com; + int error; + Port_t iobase; + int mynor; + int s; + struct tty *tp; +#if defined(COMPAT_43) || defined(COMPAT_SUNOS) + int oldcmd; + struct termios term; +#endif + + mynor = minor(dev); + com = com_addr(MINOR_TO_UNIT(mynor)); + if (com->gone) + return (ENODEV); + iobase = com->iobase; + if (mynor & CONTROL_MASK) { + struct termios *ct; + + switch (mynor & CONTROL_MASK) { + case CONTROL_INIT_STATE: + ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; + break; + case CONTROL_LOCK_STATE: + ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; + break; + default: + return (ENODEV); /* /dev/nodev */ + } + switch (cmd) { + case TIOCSETA: + error = suser(p->p_ucred, &p->p_acflag); + if (error != 0) + return (error); + *ct = *(struct termios *)data; + return (0); + case TIOCGETA: + *(struct termios *)data = *ct; + return (0); + case TIOCGETD: + *(int *)data = TTYDISC; + return (0); + case TIOCGWINSZ: + bzero(data, sizeof(struct winsize)); + return (0); +#ifdef DSI_SOFT_MODEM + /* + * Download micro-code to Digicom modem. + */ + case TIOCDSIMICROCODE: + { + u_long l; + u_char *p,*pi; + + pi = (u_char*)(*(caddr_t*)data); + error = copyin(pi,&l,sizeof l); + if(error) + {return error;}; + pi += sizeof l; + + p = malloc(l,M_TEMP,M_NOWAIT); + if(!p) + {return ENOBUFS;} + error = copyin(pi,p,l); + if(error) + {free(p,M_TEMP); return error;}; + if(error = LoadSoftModem( + MINOR_TO_UNIT(mynor),iobase,l,p)) + {free(p,M_TEMP); return error;} + free(p,M_TEMP); + return(0); + } +#endif /* DSI_SOFT_MODEM */ + default: + return (ENOTTY); + } + } + tp = com->tp; +#if defined(COMPAT_43) || defined(COMPAT_SUNOS) + term = tp->t_termios; + oldcmd = cmd; + error = ttsetcompat(tp, &cmd, data, &term); + if (error != 0) + return (error); + if (cmd != oldcmd) + data = (caddr_t)&term; +#endif + if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { + int cc; + struct termios *dt = (struct termios *)data; + struct termios *lt = mynor & CALLOUT_MASK + ? &com->lt_out : &com->lt_in; + + dt->c_iflag = (tp->t_iflag & lt->c_iflag) + | (dt->c_iflag & ~lt->c_iflag); + dt->c_oflag = (tp->t_oflag & lt->c_oflag) + | (dt->c_oflag & ~lt->c_oflag); + dt->c_cflag = (tp->t_cflag & lt->c_cflag) + | (dt->c_cflag & ~lt->c_cflag); + dt->c_lflag = (tp->t_lflag & lt->c_lflag) + | (dt->c_lflag & ~lt->c_lflag); + for (cc = 0; cc < NCCS; ++cc) + if (lt->c_cc[cc] != 0) + dt->c_cc[cc] = tp->t_cc[cc]; + if (lt->c_ispeed != 0) + dt->c_ispeed = tp->t_ispeed; + if (lt->c_ospeed != 0) + dt->c_ospeed = tp->t_ospeed; + } + error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); + if (error >= 0) + return (error); + s = spltty(); + error = ttioctl(tp, cmd, data, flag); + disc_optim(tp, &tp->t_termios, com); + if (error >= 0) { + splx(s); + return (error); + } +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + switch (cmd) { + case TIOCSBRK: + com_send_break_on( com ); + break; + case TIOCCBRK: + com_send_break_off( com ); + break; + case TIOCSDTR: + (void)commctl(com, TIOCM_DTR, DMBIS); + break; + case TIOCCDTR: + (void)commctl(com, TIOCM_DTR, DMBIC); + break; + case TIOCMSET: + (void)commctl(com, *(int *)data, DMSET); + break; + case TIOCMBIS: + (void)commctl(com, *(int *)data, DMBIS); + break; + case TIOCMBIC: + (void)commctl(com, *(int *)data, DMBIC); + break; + case TIOCMGET: + *(int *)data = commctl(com, 0, DMGET); + break; + case TIOCMSDTRWAIT: + /* must be root since the wait applies to following logins */ + error = suser(p->p_ucred, &p->p_acflag); + if (error != 0) { + splx(s); + return (error); + } + com->dtr_wait = *(int *)data * hz / 100; + break; + case TIOCMGDTRWAIT: + *(int *)data = com->dtr_wait * 100 / hz; + break; + case TIOCTIMESTAMP: + com->do_timestamp = TRUE; + *(struct timeval *)data = com->timestamp; + break; + default: + splx(s); + return (ENOTTY); + } + } else { +#endif + switch (cmd) { + case TIOCSBRK: + outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); + break; + case TIOCCBRK: + outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); + break; + case TIOCSDTR: + (void)commctl(com, TIOCM_DTR, DMBIS); + break; + case TIOCCDTR: + (void)commctl(com, TIOCM_DTR, DMBIC); + break; + case TIOCMSET: + (void)commctl(com, *(int *)data, DMSET); + break; + case TIOCMBIS: + (void)commctl(com, *(int *)data, DMBIS); + break; + case TIOCMBIC: + (void)commctl(com, *(int *)data, DMBIC); + break; + case TIOCMGET: + *(int *)data = commctl(com, 0, DMGET); + break; + case TIOCMSDTRWAIT: + /* must be root since the wait applies to following logins */ + error = suser(p->p_ucred, &p->p_acflag); + if (error != 0) { + splx(s); + return (error); + } + com->dtr_wait = *(int *)data * hz / 100; + break; + case TIOCMGDTRWAIT: + *(int *)data = com->dtr_wait * 100 / hz; + break; + case TIOCTIMESTAMP: + com->do_timestamp = TRUE; + *(struct timeval *)data = com->timestamp; + break; + default: + splx(s); + return (ENOTTY); + } +#ifdef PC98 + } +#endif + splx(s); + return (0); +} + +void +siopoll() +{ + int unit; + + if (com_events == 0) + return; +repeat: + for (unit = 0; unit < NSIO; ++unit) { + u_char *buf; + struct com_s *com; + u_char *ibuf; + int incc; + struct tty *tp; +#ifdef PC98 + int tmp; +#endif + + com = com_addr(unit); + if (com == NULL) + continue; + if (com->gone) + continue; + tp = com->tp; + if (tp == NULL) { + /* + * XXX forget any events related to closed devices + * (actually never opened devices) so that we don't + * loop. + */ + disable_intr(); + incc = com->iptr - com->ibuf; + com->iptr = com->ibuf; + if (com->state & CS_CHECKMSR) { + incc += LOTS_OF_EVENTS; + com->state &= ~CS_CHECKMSR; + } + com_events -= incc; + enable_intr(); + if (incc != 0) + log(LOG_DEBUG, + "sio%d: %d events for device with no tp\n", + unit, incc); + continue; + } + + /* switch the role of the low-level input buffers */ + if (com->iptr == (ibuf = com->ibuf)) { + buf = NULL; /* not used, but compiler can't tell */ + incc = 0; + } else { + buf = ibuf; + disable_intr(); + incc = com->iptr - buf; + com_events -= incc; + if (ibuf == com->ibuf1) + ibuf = com->ibuf2; + else + ibuf = com->ibuf1; + com->ibufend = ibuf + RS_IBUFSIZE; + com->ihighwater = ibuf + RS_IHIGHWATER; + com->iptr = ibuf; + + /* + * There is now room for another low-level buffer full + * of input, so enable RTS if it is now disabled and + * there is room in the high-level buffer. + */ + /* + * XXX this used not to look at CS_RTS_IFLOW. The + * change is to allow full control of MCR_RTS via + * ioctls after turning CS_RTS_IFLOW off. Check + * for races. We shouldn't allow the ioctls while + * CS_RTS_IFLOW is on. + */ +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + tmp = com_tiocm_get(com) & TIOCM_RTS; + else + tmp = com->mcr_image & MCR_RTS; +#endif + if ((com->state & CS_RTS_IFLOW) +#ifdef PC98 + && !(tmp) +#else + && !(com->mcr_image & MCR_RTS) +#endif + && !(tp->t_state & TS_TBLOCK)) +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + com_tiocm_bis(com, TIOCM_RTS); + else +#endif + outb(com->modem_ctl_port, + com->mcr_image |= MCR_RTS); + enable_intr(); + com->ibuf = ibuf; + } + + if (com->state & CS_CHECKMSR) { + u_char delta_modem_status; + +#ifdef PC98 + if(!IS_8251(com->pc98_if_type)){ +#endif + disable_intr(); + delta_modem_status = com->last_modem_status + ^ com->prev_modem_status; + com->prev_modem_status = com->last_modem_status; + com_events -= LOTS_OF_EVENTS; + com->state &= ~CS_CHECKMSR; + enable_intr(); + if (delta_modem_status & MSR_DCD) + (*linesw[tp->t_line].l_modem) + (tp, com->prev_modem_status & MSR_DCD); +#ifdef PC98 + } +#endif + } + if (com->state & CS_ODONE) { + disable_intr(); + com_events -= LOTS_OF_EVENTS; + com->state &= ~CS_ODONE; + if (!(com->state & CS_BUSY)) + com->tp->t_state &= ~TS_BUSY; + enable_intr(); + (*linesw[tp->t_line].l_start)(tp); + } + if (incc <= 0 || !(tp->t_state & TS_ISOPEN)) + continue; + /* + * Avoid the grotesquely inefficient lineswitch routine + * (ttyinput) in "raw" mode. It usually takes about 450 + * instructions (that's without canonical processing or echo!). + * slinput is reasonably fast (usually 40 instructions plus + * call overhead). + */ + if (tp->t_state & TS_CAN_BYPASS_L_RINT) { + if (tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER + && (com->state & CS_RTS_IFLOW + || tp->t_iflag & IXOFF) + && !(tp->t_state & TS_TBLOCK)) + ttyblock(tp); + tk_nin += incc; + tk_rawcc += incc; + tp->t_rawcc += incc; + com->delta_error_counts[CE_TTY_BUF_OVERFLOW] + += b_to_q((char *)buf, incc, &tp->t_rawq); + ttwakeup(tp); + if (tp->t_state & TS_TTSTOP + && (tp->t_iflag & IXANY + || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { + tp->t_state &= ~TS_TTSTOP; + tp->t_lflag &= ~FLUSHO; + comstart(tp); + } + } else { + do { + u_char line_status; + int recv_data; + + line_status = (u_char) buf[CE_INPUT_OFFSET]; + recv_data = (u_char) *buf++; + if (line_status + & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { + if (line_status & LSR_BI) + recv_data |= TTY_BI; + if (line_status & LSR_FE) + recv_data |= TTY_FE; + if (line_status & LSR_OE) + recv_data |= TTY_OE; + if (line_status & LSR_PE) + recv_data |= TTY_PE; + } + (*linesw[tp->t_line].l_rint)(recv_data, tp); + } while (--incc > 0); + } + if (com_events == 0) + break; + } + if (com_events >= LOTS_OF_EVENTS) + goto repeat; +} + +static int +comparam(tp, t) + struct tty *tp; + struct termios *t; +{ + u_int cfcr; + int cflag; + struct com_s *com; + int divisor; + int error; + Port_t iobase; + int s; + int unit; + int txtimeout; +#ifdef PC98 + Port_t tmp_port; + int tmp_flg; +#endif + +#ifdef PC98 + cfcr = 0; + unit = DEV_TO_UNIT(tp->t_dev); + com = com_addr(unit); + iobase = com->iobase; + if(IS_8251(com->pc98_if_type)) { + divisor = pc98_ttspeedtab(com, t->c_ospeed); + } else +#endif + /* do historical conversions */ + if (t->c_ispeed == 0) + t->c_ispeed = t->c_ospeed; + + /* check requested parameters */ + divisor = ttspeedtab(t->c_ospeed, comspeedtab); + if (divisor < 0 || divisor > 0 && t->c_ispeed != t->c_ospeed) + return (EINVAL); + + /* parameters are OK, convert them to the com struct and the device */ +#ifndef PC98 + unit = DEV_TO_UNIT(tp->t_dev); + com = com_addr(unit); + iobase = com->iobase; +#endif + s = spltty(); +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + if(divisor == 0){ + com_int_TxRx_disable( com ); + com_tiocm_bic( com, TIOCM_DTR|TIOCM_RTS|TIOCM_LE ); + } + } else { +#endif + if (divisor == 0) + (void)commctl(com, TIOCM_DTR, DMBIC); /* hang up line */ + else + (void)commctl(com, TIOCM_DTR, DMBIS); +#ifdef PC98 + } +#endif + cflag = t->c_cflag; +#ifdef PC98 + if(!IS_8251(com->pc98_if_type)){ +#endif + switch (cflag & CSIZE) { + case CS5: + cfcr = CFCR_5BITS; + break; + case CS6: + cfcr = CFCR_6BITS; + break; + case CS7: + cfcr = CFCR_7BITS; + break; + default: + cfcr = CFCR_8BITS; + break; + } + if (cflag & PARENB) { + cfcr |= CFCR_PENAB; + if (!(cflag & PARODD)) + cfcr |= CFCR_PEVEN; + } + if (cflag & CSTOPB) + cfcr |= CFCR_STOPB; + + if (com->hasfifo && divisor != 0) { + /* + * Use a fifo trigger level low enough so that the input + * latency from the fifo is less than about 16 msec and + * the total latency is less than about 30 msec. These + * latencies are reasonable for humans. Serial comms + * protocols shouldn't expect anything better since modem + * latencies are larger. + */ + com->fifo_image = t->c_ospeed <= 4800 + ? FIFO_ENABLE : FIFO_ENABLE | FIFO_RX_HIGH; + outb(iobase + com_fifo, com->fifo_image); + } + + /* + * Some UARTs lock up if the divisor latch registers are selected + * while the UART is doing output (they refuse to transmit anything + * more until given a hard reset). Fix this by stopping filling + * the device buffers and waiting for them to drain. Reading the + * line status port outside of siointr1() might lose some receiver + * error bits, but that is acceptable here. + */ +#ifdef PC98 + } +#endif + disable_intr(); +retry: + com->state &= ~CS_TTGO; + txtimeout = tp->t_timeout; + enable_intr(); +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + tmp_port = com->sts_port; + tmp_flg = (STS8251_TxRDY|STS8251_TxEMP); + } else { + tmp_port = com->line_status_port; + tmp_flg = (LSR_TSRE|LSR_TXRDY); + } + while ((inb(tmp_port) & tmp_flg) != tmp_flg) { +#else + while ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY)) { +#endif + tp->t_state |= TS_SO_OCOMPLETE; + error = ttysleep(tp, TSA_OCOMPLETE(tp), TTIPRI | PCATCH, + "siotx", hz / 100); + if ( txtimeout != 0 + && (!error || error == EAGAIN) + && (txtimeout -= hz / 100) <= 0 + ) + error = EIO; + if (com->gone) + error = ENODEV; + if (error != 0 && error != EAGAIN) { + if (!(tp->t_state & TS_TTSTOP)) { + disable_intr(); + com->state |= CS_TTGO; + enable_intr(); + } + splx(s); + return (error); + } + } + + disable_intr(); /* very important while com_data is hidden */ + + /* + * XXX - clearing CS_TTGO is not sufficient to stop further output, + * because siopoll() calls comstart() which usually sets it again + * because TS_TTSTOP is clear. Setting TS_TTSTOP would not be + * sufficient, for similar reasons. + */ +#ifdef PC98 + if ((inb(tmp_port) & tmp_flg) != tmp_flg) +#else + if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY)) +#endif + goto retry; + +#ifdef PC98 + if(!IS_8251(com->pc98_if_type)){ +#endif + if (divisor != 0) { + outb(iobase + com_cfcr, cfcr | CFCR_DLAB); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + } + outb(iobase + com_cfcr, com->cfcr_image = cfcr); +#ifdef PC98 + } else + com_cflag_and_speed_set(com, cflag, t->c_ospeed); +#endif + if (!(tp->t_state & TS_TTSTOP)) + com->state |= CS_TTGO; + if (cflag & CRTS_IFLOW) + com->state |= CS_RTS_IFLOW; /* XXX - secondary changes? */ + else + com->state &= ~CS_RTS_IFLOW; + + /* + * Set up state to handle output flow control. + * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? + * Now has 10+ msec latency, while CTS flow has 50- usec latency. + */ + com->state |= CS_ODEVREADY; + com->state &= ~CS_CTS_OFLOW; + if (cflag & CCTS_OFLOW) { + com->state |= CS_CTS_OFLOW; +#ifdef PC98 + if(IS_8251(com->pc98_if_type)){ + if (!(pc98_get_modem_status(com) & TIOCM_CTS)) + com->state &= ~CS_ODEVREADY; + } else { +#endif + if (!(com->last_modem_status & MSR_CTS)) + com->state &= ~CS_ODEVREADY; +#ifdef PC98 + } +#endif + } + /* XXX shouldn't call functions while intrs are disabled. */ + disc_optim(tp, t, com); + /* + * Recover from fiddling with CS_TTGO. We used to call siointr1() + * unconditionally, but that defeated the careful discarding of + * stale input in sioopen(). + */ + if (com->state >= (CS_BUSY | CS_TTGO)) + siointr1(com); + + enable_intr(); + splx(s); + return (0); +} + +static void +comstart(tp) + struct tty *tp; +{ + struct com_s *com; + int s; + int unit; +#ifdef PC98 + int tmp; +#endif + + unit = DEV_TO_UNIT(tp->t_dev); + com = com_addr(unit); + s = spltty(); + disable_intr(); + if (tp->t_state & TS_TTSTOP) + com->state &= ~CS_TTGO; + else + com->state |= CS_TTGO; + if (tp->t_state & TS_TBLOCK) { +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + tmp = com_tiocm_get(com) & TIOCM_RTS; + else + tmp = com->mcr_image & MCR_RTS; + if (tmp && (com->state & CS_RTS_IFLOW)) +#else + if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) +#endif +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + com_tiocm_bic(com, TIOCM_RTS); + else +#endif + outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); + } else { + /* + * XXX don't raise MCR_RTS if CTS_RTS_IFLOW is off. Set it + * appropriately in comparam() if RTS-flow is being changed. + * Check for races. + */ +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + tmp = com_tiocm_get(com) & TIOCM_RTS; + else + tmp = com->mcr_image & MCR_RTS; + if (!(tmp) && com->iptr < com->ihighwater) +#else + if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater) +#endif +#ifdef PC98 + if(IS_8251(com->pc98_if_type)) + com_tiocm_bis(com, TIOCM_RTS); + else +#endif + outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); + } + enable_intr(); + if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) { +#ifdef PC98 +/* if(IS_8251(com->pc98_if_type)) + com_int_Tx_enable(com); */ +#endif + splx(s); + return; + } + if (tp->t_outq.c_cc != 0) { + struct lbq *qp; + struct lbq *next; + + if (!com->obufs[0].l_queued) { + com->obufs[0].l_tail + = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1, + sizeof com->obuf1); + com->obufs[0].l_next = NULL; + com->obufs[0].l_queued = TRUE; + disable_intr(); + if (com->state & CS_BUSY) { + qp = com->obufq.l_next; + while ((next = qp->l_next) != NULL) + qp = next; + qp->l_next = &com->obufs[0]; + } else { + com->obufq.l_head = com->obufs[0].l_head; + com->obufq.l_tail = com->obufs[0].l_tail; + com->obufq.l_next = &com->obufs[0]; + com->state |= CS_BUSY; + } + enable_intr(); + } + if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) { + com->obufs[1].l_tail + = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2, + sizeof com->obuf2); + com->obufs[1].l_next = NULL; + com->obufs[1].l_queued = TRUE; + disable_intr(); + if (com->state & CS_BUSY) { + qp = com->obufq.l_next; + while ((next = qp->l_next) != NULL) + qp = next; + qp->l_next = &com->obufs[1]; + } else { + com->obufq.l_head = com->obufs[1].l_head; + com->obufq.l_tail = com->obufs[1].l_tail; + com->obufq.l_next = &com->obufs[1]; + com->state |= CS_BUSY; + } + enable_intr(); + } + tp->t_state |= TS_BUSY; + } + disable_intr(); + if (com->state >= (CS_BUSY | CS_TTGO)) + siointr1(com); /* fake interrupt to start output */ + enable_intr(); +#ifdef PC98 +/* if(IS_8251(com->pc98_if_type)) + com_int_Tx_enable(com); */ +#endif + ttwwakeup(tp); + splx(s); +} + +static void +siostop(tp, rw) + struct tty *tp; + int rw; +{ + struct com_s *com; + + com = com_addr(DEV_TO_UNIT(tp->t_dev)); + if (com->gone) + return; + disable_intr(); + if (rw & FWRITE) { + com->obufs[0].l_queued = FALSE; + com->obufs[1].l_queued = FALSE; + if (com->state & CS_ODONE) + com_events -= LOTS_OF_EVENTS; + com->state &= ~(CS_ODONE | CS_BUSY); + com->tp->t_state &= ~TS_BUSY; + } + if (rw & FREAD) { + com_events -= (com->iptr - com->ibuf); + com->iptr = com->ibuf; + } + enable_intr(); + comstart(tp); + + /* XXX should clear h/w fifos too. */ +} + +static struct tty * +siodevtotty(dev) + dev_t dev; +{ + int mynor; + int unit; + + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (NULL); + unit = MINOR_TO_UNIT(mynor); + if ((u_int) unit >= NSIO) + return (NULL); + return (&sio_tty[unit]); +} + +static int +commctl(com, bits, how) + struct com_s *com; + int bits; + int how; +{ + int mcr; + int msr; + + if (how == DMGET) { + bits = TIOCM_LE; /* XXX - always enabled while open */ + mcr = com->mcr_image; + if (mcr & MCR_DTR) + bits |= TIOCM_DTR; + if (mcr & MCR_RTS) + bits |= TIOCM_RTS; + msr = com->prev_modem_status; + if (msr & MSR_CTS) + bits |= TIOCM_CTS; + if (msr & MSR_DCD) + bits |= TIOCM_CD; + if (msr & MSR_DSR) + bits |= TIOCM_DSR; + /* + * XXX - MSR_RI is naturally volatile, and we make MSR_TERI + * more volatile by reading the modem status a lot. Perhaps + * we should latch both bits until the status is read here. + */ + if (msr & (MSR_RI | MSR_TERI)) + bits |= TIOCM_RI; + return (bits); + } + mcr = 0; + if (bits & TIOCM_DTR) + mcr |= MCR_DTR; + if (bits & TIOCM_RTS) + mcr |= MCR_RTS; + if (com->gone) + return(0); + disable_intr(); + switch (how) { + case DMSET: + outb(com->modem_ctl_port, + com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE)); + break; + case DMBIS: + outb(com->modem_ctl_port, com->mcr_image |= mcr); + break; + case DMBIC: + outb(com->modem_ctl_port, com->mcr_image &= ~mcr); + break; + } + enable_intr(); + return (0); +} + +static void +siosettimeout() +{ + struct com_s *com; + bool_t someopen; + int unit; + + /* + * Set our timeout period to 1 second if no polled devices are open. + * Otherwise set it to max(1/200, 1/hz). + * Enable timeouts iff some device is open. + */ + untimeout(comwakeup, (void *)NULL); + sio_timeout = hz; + someopen = FALSE; + for (unit = 0; unit < NSIO; ++unit) { + com = com_addr(unit); + if (com != NULL && com->tp != NULL + && com->tp->t_state & TS_ISOPEN && !com->gone) { + someopen = TRUE; + if (com->poll || com->poll_output) { + sio_timeout = hz > 200 ? hz / 200 : 1; + break; + } + } + } + if (someopen) { + sio_timeouts_until_log = hz / sio_timeout; + timeout(comwakeup, (void *)NULL, sio_timeout); + } else { + /* Flush error messages, if any. */ + sio_timeouts_until_log = 1; + comwakeup((void *)NULL); + untimeout(comwakeup, (void *)NULL); + } +} + +static void +comwakeup(chan) + void *chan; +{ + struct com_s *com; + int unit; + + timeout(comwakeup, (void *)NULL, sio_timeout); + + /* + * Recover from lost output interrupts. + * Poll any lines that don't use interrupts. + */ + for (unit = 0; unit < NSIO; ++unit) { + com = com_addr(unit); + if (com != NULL && !com->gone + && (com->state >= (CS_BUSY | CS_TTGO) || com->poll)) { + disable_intr(); + siointr1(com); + enable_intr(); + } + } + + /* + * Check for and log errors, but not too often. + */ + if (--sio_timeouts_until_log > 0) + return; + sio_timeouts_until_log = hz / sio_timeout; + for (unit = 0; unit < NSIO; ++unit) { + int errnum; + + com = com_addr(unit); + if (com == NULL) + continue; + if (com->gone) + continue; + for (errnum = 0; errnum < CE_NTYPES; ++errnum) { + u_int delta; + u_long total; + + disable_intr(); + delta = com->delta_error_counts[errnum]; + com->delta_error_counts[errnum] = 0; + enable_intr(); + if (delta == 0) + continue; + total = com->error_counts[errnum] += delta; + log(LOG_ERR, "sio%d: %u more %s%s (total %lu)\n", + unit, delta, error_desc[errnum], + delta == 1 ? "" : "s", total); + } + } +} + +#ifdef PC98 +/* commint is called when modem control line changes */ +static void +commint(dev_t dev) +{ + register struct tty *tp; + int stat,delta; + struct com_s *com; + int mynor,unit; + + mynor = minor(dev); + unit = MINOR_TO_UNIT(mynor); + com = com_addr(unit); + tp = com->tp; + + stat = com_tiocm_get(com); + delta = com_tiocm_get_delta(com); + + if (com->state & CS_CTS_OFLOW) { + if (stat & TIOCM_CTS) + com->state |= CS_ODEVREADY; + else + com->state &= ~CS_ODEVREADY; + } + if ((delta & TIOCM_CAR) && (mynor & CALLOUT_MASK) == 0) { + if (stat & TIOCM_CAR ) + (void)(*linesw[tp->t_line].l_modem)(tp, 1); + else if ((*linesw[tp->t_line].l_modem)(tp, 0) == 0) { + /* negate DTR, RTS */ + com_tiocm_bic(com, (tp->t_cflag & HUPCL) ? + TIOCM_DTR|TIOCM_RTS|TIOCM_LE : TIOCM_LE ); + /* disable IENABLE */ + com_int_TxRx_disable( com ); + } + } +} +#endif + +static void +disc_optim(tp, t, com) + struct tty *tp; + struct termios *t; + struct com_s *com; +{ + if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) + && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) + && (!(t->c_iflag & PARMRK) + || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) + && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) + && linesw[tp->t_line].l_rint == ttyinput) + tp->t_state |= TS_CAN_BYPASS_L_RINT; + else + tp->t_state &= ~TS_CAN_BYPASS_L_RINT; + /* + * Prepare to reduce input latency for packet + * discplines with a end of packet character. + */ + if (tp->t_line == SLIPDISC) + com->hotchar = 0xc0; + else if (tp->t_line == PPPDISC) + com->hotchar = 0x7e; + else + com->hotchar = 0; +} + +/* + * Following are all routines needed for SIO to act as console + */ +#include <machine/cons.h> + +struct siocnstate { + u_char dlbl; + u_char dlbh; + u_char ier; + u_char cfcr; + u_char mcr; +}; + +static Port_t siocniobase; + +static void siocnclose __P((struct siocnstate *sp)); +static void siocnopen __P((struct siocnstate *sp)); +static void siocntxwait __P((void)); + +static void +siocntxwait() +{ + int timo; +#ifdef PC98 + int tmp; +#endif + + /* + * Wait for any pending transmission to finish. Required to avoid + * the UART lockup bug when the speed is changed, and for normal + * transmits. + */ + timo = 100000; + while ((inb(siocniobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY) && --timo != 0) + ; +} + +static void +siocnopen(sp) + struct siocnstate *sp; +{ + int divisor; + Port_t iobase; + + /* + * Save all the device control registers except the fifo register + * and set our default ones (cs8 -parenb speed=comdefaultrate). + * We can't save the fifo register since it is read-only. + */ + iobase = siocniobase; + sp->ier = inb(iobase + com_ier); + outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ + siocntxwait(); + sp->cfcr = inb(iobase + com_cfcr); + outb(iobase + com_cfcr, CFCR_DLAB); + sp->dlbl = inb(iobase + com_dlbl); + sp->dlbh = inb(iobase + com_dlbh); + divisor = ttspeedtab(comdefaultrate, comspeedtab); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + sp->mcr = inb(iobase + com_mcr); + /* + * We don't want interrupts, but must be careful not to "disable" + * them by clearing the MCR_IENABLE bit, since that might cause + * an interrupt by floating the IRQ line. + */ + outb(iobase + com_mcr, (sp->mcr & MCR_IENABLE) | MCR_DTR | MCR_RTS); +} + +static void +siocnclose(sp) + struct siocnstate *sp; +{ + Port_t iobase; + + /* + * Restore the device control registers. + */ + siocntxwait(); + iobase = siocniobase; + outb(iobase + com_cfcr, CFCR_DLAB); + outb(iobase + com_dlbl, sp->dlbl); + outb(iobase + com_dlbh, sp->dlbh); + outb(iobase + com_cfcr, sp->cfcr); + /* + * XXX damp oscillations of MCR_DTR and MCR_RTS by not restoring them. + */ + outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); + outb(iobase + com_ier, sp->ier); +} + +void +siocnprobe(cp) + struct consdev *cp; +{ + int unit; + + /* XXX: ick */ + unit = DEV_TO_UNIT(CONUNIT); + siocniobase = CONADDR; + + /* make sure hardware exists? XXX */ + + /* initialize required fields */ + cp->cn_dev = makedev(CDEV_MAJOR, unit); +#ifdef COMCONSOLE + cp->cn_pri = CN_REMOTE; /* Force a serial port console */ +#else + cp->cn_pri = (boothowto & RB_SERIAL) ? CN_REMOTE : CN_NORMAL; +#endif +} + +void +siocninit(cp) + struct consdev *cp; +{ + comconsole = DEV_TO_UNIT(cp->cn_dev); +} + +int +siocncheckc(dev) + dev_t dev; +{ + int c; + Port_t iobase; + int s; + struct siocnstate sp; + + iobase = siocniobase; + s = spltty(); + siocnopen(&sp); + if (inb(iobase + com_lsr) & LSR_RXRDY) + c = inb(iobase + com_data); + else + c = 0; + siocnclose(&sp); + splx(s); + return (c); +} + + +int +siocngetc(dev) + dev_t dev; +{ + int c; + Port_t iobase; + int s; + struct siocnstate sp; + + iobase = siocniobase; + s = spltty(); + siocnopen(&sp); + while (!(inb(iobase + com_lsr) & LSR_RXRDY)) + ; + c = inb(iobase + com_data); + siocnclose(&sp); + splx(s); + return (c); +} + +void +siocnputc(dev, c) + dev_t dev; + int c; +{ + int s; + struct siocnstate sp; + + s = spltty(); + siocnopen(&sp); + siocntxwait(); + outb(siocniobase + com_data, c); + siocnclose(&sp); + splx(s); +} + +#ifdef DSI_SOFT_MODEM +/* + * The magic code to download microcode to a "Connection 14.4+Fax" + * modem from Digicom Systems Inc. Very magic. + */ + +#define DSI_ERROR(str) { ptr = str; goto error; } +static int +LoadSoftModem(int unit, int base_io, u_long size, u_char *ptr) +{ + int int_c,int_k; + int data_0188, data_0187; + + /* + * First see if it is a DSI SoftModem + */ + if(!((inb(base_io+7) ^ inb(base_io+7) & 0x80))) + return ENODEV; + + data_0188 = inb(base_io+4); + data_0187 = inb(base_io+3); + outb(base_io+3,0x80); + outb(base_io+4,0x0C); + outb(base_io+0,0x31); + outb(base_io+1,0x8C); + outb(base_io+7,0x10); + outb(base_io+7,0x19); + + if(0x18 != (inb(base_io+7) & 0x1A)) + DSI_ERROR("dsp bus not granted"); + + if(0x01 != (inb(base_io+7) & 0x01)) { + outb(base_io+7,0x18); + outb(base_io+7,0x19); + if(0x01 != (inb(base_io+7) & 0x01)) + DSI_ERROR("program mem not granted"); + } + + int_c = 0; + + while(1) { + if(int_c >= 7 || size <= 0x1800) + break; + + for(int_k = 0 ; int_k < 0x800; int_k++) { + outb(base_io+0,*ptr++); + outb(base_io+1,*ptr++); + outb(base_io+2,*ptr++); + } + + size -= 0x1800; + int_c++; + } + + if(size > 0x1800) { + outb(base_io+7,0x18); + outb(base_io+7,0x19); + if(0x00 != (inb(base_io+7) & 0x01)) + DSI_ERROR("program data not granted"); + + for(int_k = 0 ; int_k < 0x800; int_k++) { + outb(base_io+1,*ptr++); + outb(base_io+2,0); + outb(base_io+1,*ptr++); + outb(base_io+2,*ptr++); + } + + size -= 0x1800; + + while(size > 0x1800) { + for(int_k = 0 ; int_k < 0xC00; int_k++) { + outb(base_io+1,*ptr++); + outb(base_io+2,*ptr++); + } + size -= 0x1800; + } + + if(size < 0x1800) { + for(int_k=0;int_k<size/2;int_k++) { + outb(base_io+1,*ptr++); + outb(base_io+2,*ptr++); + } + } + + } else if (size > 0) { + if(int_c == 7) { + outb(base_io+7,0x18); + outb(base_io+7,0x19); + if(0x00 != (inb(base_io+7) & 0x01)) + DSI_ERROR("program data not granted"); + for(int_k = 0 ; int_k < size/3; int_k++) { + outb(base_io+1,*ptr++); + outb(base_io+2,0); + outb(base_io+1,*ptr++); + outb(base_io+2,*ptr++); + } + } else { + for(int_k = 0 ; int_k < size/3; int_k++) { + outb(base_io+0,*ptr++); + outb(base_io+1,*ptr++); + outb(base_io+2,*ptr++); + } + } + } + outb(base_io+7,0x11); + outb(base_io+7,3); + + outb(base_io+4,data_0188 & 0xfb); + + outb(base_io+3,data_0187); + + return 0; +error: + printf("sio%d: DSI SoftModem microcode load failed: <%s>\n",unit,ptr); + outb(base_io+7,0x00); \ + outb(base_io+3,data_0187); \ + outb(base_io+4,data_0188); \ + return EIO; +} +#endif /* DSI_SOFT_MODEM */ +#ifdef PC98 +/* + * pc98 local function + */ + +static void +com_tiocm_set(struct com_s *com, int msr) +{ + int s; + int tmp = 0; + int mask = CMD8251_TxEN|CMD8251_RxEN|CMD8251_DTR|CMD8251_RTS; + + s=spltty(); + com->pc98_prev_modem_status = ( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ) + | ( com->pc98_prev_modem_status & ~(TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); + tmp |= (CMD8251_TxEN|CMD8251_RxEN); + if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; + if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; + pc98_i8251_clear_or_cmd( com, mask, tmp ); + splx(s); +} + +static void +com_tiocm_bis(struct com_s *com, int msr) +{ + int s; + int tmp = 0; + + s=spltty(); + com->pc98_prev_modem_status |= ( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); + tmp |= CMD8251_TxEN|CMD8251_RxEN; + if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; + if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; + + pc98_i8251_or_cmd( com, tmp ); + splx(s); +} + +static void +com_tiocm_bic(struct com_s *com, int msr) +{ + int s; + int tmp = msr; + + s=spltty(); + com->pc98_prev_modem_status &= ~( msr & (TIOCM_LE|TIOCM_DTR|TIOCM_RTS) ); + if ( msr & TIOCM_DTR ) tmp |= CMD8251_DTR; + if ( msr & TIOCM_RTS ) tmp |= CMD8251_RTS; + + pc98_i8251_clear_cmd( com, tmp ); + splx(s); +} + +static int +com_tiocm_get(struct com_s *com) +{ + return( com->pc98_prev_modem_status ); +} + +static int +com_tiocm_get_delta(struct com_s *com) +{ + int tmp; + + tmp = com->pc98_modem_delta; + com->pc98_modem_delta = 0; + return( tmp ); +} + +/* convert to TIOCM_?? ( ioctl.h ) */ +static int +pc98_get_modem_status(struct com_s *com) +{ + int stat, stat2; + register int msr; + + int ret; + + stat = inb(com->sts_port); + stat2 = inb(com->in_modem_port); + msr = com->pc98_prev_modem_status + & ~(TIOCM_CAR|TIOCM_RI|TIOCM_DSR|TIOCM_CTS); + if ( !(stat2 & CICSCD_CD) ) msr |= TIOCM_CAR; + if ( !(stat2 & CICSCD_CI) ) msr |= TIOCM_RI; + if ( stat & STS8251_DSR ) msr |= TIOCM_DSR; + if ( !(stat2 & CICSCD_CS) ) msr |= TIOCM_CTS; +#if COM_CARRIER_DETECT_EMULATE + if ( msr & (TIOCM_DSR|TIOCM_CTS) ) { + msr |= TIOCM_CAR; + } +#endif + return(msr); +} + +static void +pc98_check_msr(void* chan) +{ + int msr, delta; + int s; + register struct tty *tp; + struct com_s *com; + int mynor; + int unit; + dev_t dev; + + dev=(dev_t)chan; + mynor = minor(dev); + unit = MINOR_TO_UNIT(mynor); + com = com_addr(unit); + tp = com->tp; + + s = spltty(); + msr = pc98_get_modem_status(com); + /* make change flag */ + delta = msr ^ com->pc98_prev_modem_status; + if ( delta & TIOCM_CAR ) { + if ( com->modem_car_chg_timer ) { + if ( -- com->modem_car_chg_timer ) + msr ^= TIOCM_CAR; + } else { + if ( com->modem_car_chg_timer = ( msr & TIOCM_CAR ) ? + DCD_ON_RECOGNITION : DCD_OFF_TOLERANCE ) + msr ^= TIOCM_CAR; + } + } else + com->modem_car_chg_timer = 0; + delta = ( msr ^ com->pc98_prev_modem_status ) & + (TIOCM_CAR|TIOCM_RI|TIOCM_DSR|TIOCM_CTS); + com->pc98_prev_modem_status = msr; + delta = ( com->pc98_modem_delta |= delta ); + splx(s); + if ( com->modem_checking || (tp->t_state & (TS_ISOPEN)) ) { + if ( delta ) { + commint(dev); + } + timeout(pc98_check_msr, (caddr_t)dev, + PC98_CHECK_MODEM_INTERVAL); + } else { + com->modem_checking = 0; + } +} + +static void +pc98_msrint_start(dev_t dev) +{ + struct com_s *com; + int mynor; + int unit; + int s = spltty(); + + mynor = minor(dev); + unit = MINOR_TO_UNIT(mynor); + com = com_addr(unit); + /* modem control line check routine envoke interval is 1/10 sec */ + if ( com->modem_checking == 0 ) { + com->pc98_prev_modem_status = pc98_get_modem_status(com); + com->pc98_modem_delta = 0; + timeout(pc98_check_msr, (caddr_t)dev, + PC98_CHECK_MODEM_INTERVAL); + com->modem_checking = 1; + } + splx(s); +} + +static void +pc98_disable_i8251_interrupt(struct com_s *com, int mod) +{ + /* disable interrupt */ + register int tmp; + + mod |= ~(IEN_Tx|IEN_TxEMP|IEN_Rx); + COM_INT_DISABLE + tmp = inb( com->intr_ctrl_port ) & ~(IEN_Tx|IEN_TxEMP|IEN_Rx); + outb( com->intr_ctrl_port, (com->intr_enable&=~mod) | tmp ); + COM_INT_ENABLE +} + +static void +pc98_enable_i8251_interrupt(struct com_s *com, int mod) +{ + register int tmp; + + COM_INT_DISABLE + tmp = inb( com->intr_ctrl_port ) & ~(IEN_Tx|IEN_TxEMP|IEN_Rx); + outb( com->intr_ctrl_port, (com->intr_enable|=mod) | tmp ); + COM_INT_ENABLE +} + +static int +pc98_check_i8251_interrupt(struct com_s *com) +{ + return ( com->intr_enable & 0x07 ); +} + +static void +pc98_i8251_clear_cmd(struct com_s *com, int x) +{ + int tmp; + + COM_INT_DISABLE + tmp = com->pc98_prev_siocmd & ~(x); + outb(com->cmd_port, tmp); + com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); + COM_INT_ENABLE +} + +static void +pc98_i8251_or_cmd(struct com_s *com, int x) +{ + int tmp; + + COM_INT_DISABLE + tmp = com->pc98_prev_siocmd | (x); + outb(com->cmd_port, tmp); + com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); + COM_INT_ENABLE +} + +static void +pc98_i8251_set_cmd(struct com_s *com, int x) +{ + int tmp; + + COM_INT_DISABLE + tmp = (x); + outb(com->cmd_port, tmp); + com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); + COM_INT_ENABLE +} + +static void +pc98_i8251_clear_or_cmd(struct com_s *com, int clr, int x) +{ + int tmp; + COM_INT_DISABLE + tmp = com->pc98_prev_siocmd & ~(clr); + tmp |= (x); + outb(com->cmd_port, tmp); + com->pc98_prev_siocmd = tmp & ~(CMD8251_ER|CMD8251_RESET|CMD8251_EH); + COM_INT_ENABLE +} + +static int +pc98_i8251_get_cmd(struct com_s *com) +{ + return com->pc98_prev_siocmd; +} + +static int +pc98_i8251_get_mod(struct com_s *com) +{ + return com->pc98_prev_siomod; +} + +static void +pc98_i8251_reset(struct com_s *com, int mode, int command) +{ + outb(com->cmd_port, 0); /* dummy */ + DELAY(2); + outb(com->cmd_port, 0); /* dummy */ + DELAY(2); + outb(com->cmd_port, 0); /* dummy */ + DELAY(2); + outb(com->cmd_port, CMD8251_RESET); /* internal reset */ + DELAY(2); + outb(com->cmd_port, mode ); /* mode register */ + com->pc98_prev_siomod = mode; + DELAY(2); + pc98_i8251_set_cmd( com, (command|CMD8251_ER) ); +} + +static void +pc98_check_sysclock(void) +{ + /* get system clock from port */ + if ( pc98_machine_type & M_8M ) { + /* 8 MHz system & H98 */ + sysclock = 8; + } else { + /* 5 MHz system */ + sysclock = 5; + } +} + +static void +com_cflag_and_speed_set( struct com_s *com, int cflag, int speed) +{ + int cfcr=0, count; + int s, previnterrupt; + + count = pc98_ttspeedtab( com, speed ); + if ( count < 0 ) return; + + previnterrupt = pc98_check_i8251_interrupt(com); + pc98_disable_i8251_interrupt( com, IEN_Tx|IEN_TxEMP|IEN_Rx ); + + switch ( cflag&CSIZE ) { + case CS5: + cfcr = MOD8251_5BITS; break; + case CS6: + cfcr = MOD8251_6BITS; break; + case CS7: + cfcr = MOD8251_7BITS; break; + case CS8: + cfcr = MOD8251_8BITS; break; + } + if ( cflag&PARENB ) { + if ( cflag&PARODD ) + cfcr |= MOD8251_PODD; + else + cfcr |= MOD8251_PEVEN; + } else + cfcr |= MOD8251_PDISAB; + + if ( cflag&CSTOPB ) + cfcr |= MOD8251_STOP2; + else + cfcr |= MOD8251_STOP1; + + if ( count & 0x10000 ) + cfcr |= MOD8251_CLKX1; + else + cfcr |= MOD8251_CLKX16; + + if (epson_machine_id != 0x20) { /* XXX */ + { + int tmp; + while (!((tmp = inb(com->sts_port)) & STS8251_TxEMP)) + ; + } + } + /* set baud rate from ospeed */ + pc98_set_baud_rate( com, count ); + + if ( cfcr != pc98_i8251_get_mod(com) ) + pc98_i8251_reset(com, cfcr, pc98_i8251_get_cmd(com) ); + + pc98_enable_i8251_interrupt( com, previnterrupt ); +} + +static int +pc98_ttspeedtab(struct com_s *com, int speed) +{ + int effect_sp, count=-1, mod; + + switch ( com->pc98_if_type ) { + case COM_IF_INTERNAL: + /* for *1CLK asynchronous! mode , TEFUTEFU */ + effect_sp = ttspeedtab( speed, pc98speedtab ); + if ( effect_sp < 0 ) + effect_sp = ttspeedtab( (speed-1), pc98speedtab ); + if ( effect_sp <= 0 ) + return effect_sp; + mod = (sysclock == 5 ? 2457600 : 1996800); + if ( effect_sp == speed ) + mod /= 16; + count = mod / effect_sp; + if ( count > 65535 ) + return(-1); + if ( effect_sp >= 2400 ) + if ( !(sysclock != 5 && + (effect_sp == 19200 || effect_sp == 38400)) ) + if ( ( mod % effect_sp ) != 0 ) + return(-1); + if ( effect_sp != speed ) + count |= 0x10000; + break; +#ifdef COM_IF_PC9861K + case COM_IF_PC9861K: + effect_sp = speed; + count = 1; + break; +#endif +#ifdef COM_IF_PIO9032B + case COM_IF_PIO9032B: + if ( speed == 0 ) return 0; + count = ttspeedtab( speed, comspeedtab_pio9032b ); + if ( count < 0 ) return count; + effect_sp = speed; + break; +#endif +#ifdef COM_IF_B98_01 + case COM_IF_B98_01: + effect_sp=speed; + count = ttspeedtab( speed, comspeedtab_b98_01 ); + if ( count <= 3 ) + return -1; /* invalid speed/count */ + if ( count <= 5 ) + count |= 0x10000; /* x1 mode for 76800 and 153600 */ + else + count -= 4; /* x16 mode for slower */ + break; +#endif + } + return count; +} + +static void +pc98_set_baud_rate( struct com_s *com, int count) +{ + int s; + + switch ( com->pc98_if_type ) { + case COM_IF_INTERNAL: + if ( count < 0 ) { + printf( "[ Illegal count : %d ]", count ); + return; + } else if ( count == 0) + return; + /* set i8253 */ + s = splclock(); + outb( 0x77, 0xb6 ); + outb( 0x5f, 0); + outb( 0x75, count & 0xff ); + outb( 0x5f, 0); + outb( 0x75, (count >> 8) & 0xff ); + splx(s); + break; +#if 0 +#ifdef COM_IF_PC9861K + case COM_IF_PC9861K: + break; + /* ext. RS232C board: speed is determined by DIP switch */ +#endif +#endif /* 0 */ +#ifdef COM_IF_PIO9032B + case COM_IF_PIO9032B: + outb( com_addr[unit], count & 0x07 ); + break; +#endif +#ifdef COM_IF_B98_01 + case COM_IF_B98_01: + outb( com->iobase, count & 0x0f ); +#ifdef B98_01_OLD + /* some old board should be controlled in different way, + but this hasn't been tested yet.*/ + outb( com->iobase+2, ( count & 0x10000 ) ? 0xf0 : 0xf2 ); +#endif + break; +#endif + } +} +static int +pc98_check_if_type( int iobase, struct siodev *iod) +{ + int irr = 0, tmp = 0; + int ret = 0; + static short irq_tab[2][8] = { + { 3, 5, 6, 9, 10, 12, 13, -1}, + { 3, 10, 12, 13, 5, 6, 9, -1} + }; + iod->irq = 0; + switch ( iobase & 0xff ) { + case IO_COM1: + iod->if_type = COM_IF_INTERNAL; + ret = 0; iod->irq = 4; break; +#ifdef COM_IF_PC9861K + case IO_COM2: + iod->if_type = COM_IF_PC9861K; + ret = 1; irr = 0; tmp = 3; break; + case IO_COM3: + iod->if_type = COM_IF_PC9861K; + ret = 2; irr = 1; tmp = 3; break; +#endif +#ifdef COM_IF_PIO9032B + case IO_COM_PIO9032B_2: + iod->if_type = COM_IF_PIO9032B; + ret = 1; irr = 0; tmp = 7; break; + case IO_COM_PIO9032B_3: + iod->if_type = COM_IF_PIO9032B; + ret = 2; irr = 1; tmp = 7; break; +#endif +#ifdef COM_IF_B98_01 + case IO_COM_B98_01_2: + iod->if_type = COM_IF_B98_01; + ret = 1; irr = 0; tmp = 7; + outb(iobase + 2, 0xf2); + outb(iobase, 4); + break; + case IO_COM_B98_01_3: + iod->if_type = COM_IF_B98_01; + ret = 2; irr = 1; tmp = 7; + outb(iobase + 2, 0xf2); + outb(iobase , 4); + break; +#endif + default: + if((iobase & 0x0f0) == 0xd0){ + iod->if_type = MC16550; + return 0; + } + return -1; + } + + iod->cmd = ( iobase & 0xff00 )|PC98SIO_cmd_port(ret); + iod->sts = ( iobase & 0xff00 )|PC98SIO_sts_port(ret); + iod->mod = ( iobase & 0xff00 )|PC98SIO_in_modem_port(ret); + iod->ctrl = ( iobase & 0xff00 )|PC98SIO_intr_ctrl_port(ret); + + if ( iod->irq == 0 ) { + tmp &= inb( iod->mod ); + iod->irq = irq_tab[irr][tmp]; + if ( iod->irq == -1 ) return -1; + } + return 0; +} +static int +pc98_set_ioport( struct com_s *com, int io_base ) +{ + int a, io, type; + + switch ( io_base & 0xff ) { + case IO_COM1: a = 0; io = 0; type = COM_IF_INTERNAL; + pc98_check_sysclock(); break; +#ifdef COM_IF_PC9861K + case IO_COM2: a = 1; io = 0; type = COM_IF_PC9861K; break; + case IO_COM3: a = 2; io = 0; type = COM_IF_PC9861K; break; +#endif /* COM_IF_PC9861K */ +#ifdef COM_IF_PIO9032B + /* PIO9032B : I/O address is changeable */ + case IO_COM_PIO9032B_2: + a = 1; io = io_base & 0xff00; + type = COM_IF_PIO9032B; break; + case IO_COM_PIO9032B_3: + a = 2; io = io_base & 0xff00; + type = COM_IF_PIO9032B; break; +#endif /* COM_IF_PIO9032B */ +#ifdef COM_IF_B98_01 + case IO_COM_B98_01_2: + a = 1; io = 0; type = COM_IF_B98_01; break; + case IO_COM_B98_01_3: + a = 2; io = 0; type = COM_IF_B98_01; break; +#endif /* COM_IF_B98_01*/ + default: /* i/o address not match */ + return -1; + } + + com->pc98_if_type = type; + com->data_port = io | PC98SIO_data_port(a); + com->cmd_port = io | PC98SIO_cmd_port(a); + com->sts_port = io | PC98SIO_sts_port(a); + com->in_modem_port = io | PC98SIO_in_modem_port(a); + com->intr_ctrl_port = io | PC98SIO_intr_ctrl_port(a); + return 0; +} +#endif /* PC98 defined */ diff --git a/sys/pc98/conf/GENERIC b/sys/pc98/conf/GENERIC new file mode 100644 index 0000000..3df81d4 --- /dev/null +++ b/sys/pc98/conf/GENERIC @@ -0,0 +1,164 @@ +# +# GENERIC -- Generic machine with WD/AHx/NCR/BTx family disks +# +# $Id: GENERIC,v 1.70 1996/05/13 04:29:13 nate Exp $ +# + +# GENERIC98 -- Generic PC98 machine with WD/SBIC55 disks + +machine "pc98" +cpu "I386_CPU" +cpu "I486_CPU" +cpu "I586_CPU" +cpu "I686_CPU" +ident "GENERIC98" +maxusers 10 + +options "PC98" #PC98 +options MATH_EMULATE #Support for x87 emulation +#options GPL_MATH_EMULATE #GPL-licensed emulator +options INET #InterNETworking +options FFS #Berkeley Fast Filesystem +options NFS #Network Filesystem +options MSDOSFS #MSDOS Filesystem +options "CD9660" #ISO 9660 Filesystem +options PROCFS #Process filesystem +options "COMPAT_43" #Compatible with BSD 4.3 [KEEP THIS!] +options SYSVSHM +options SYSVSEM +options SYSVMSG +options UCONSOLE #Allow users to grab the console +options FAILSAFE #Be conservative +options "MAXCONS=4" #4 virtual consoles +options BOUNCE_BUFFERS #include support for DMA bounce buffers +options EPSON_BOUNCEDMA #use bounce buufer for 15-16M +#options EPSON_MEMWIN #EPSON memory window support +options "LINE30" +options AUTO_CLOCK +options COM_MULTIPORT + +# +# non-Intel CPU support +# +#options "IBM_486SLC" # IBM486SLC/SLC2 support +#options "CYRIX_486DLC" # Cyrix 486DLC/SLC/DLC2/SLC2 support +#option "CYRIX_5X86" # Cyrix 5x86 support +#options SUSP_HLT # CPU enters suspend mode when HALT +#options "DISABLE_5X86_LSSER" # Load-Store reordering enable + +# +# sbic55.c.new +# +#options SCSI_SYNC # synchronous transfer mode +#options FORCE_BUSMASTER +#options "HA55BS_ID=0" + +# +# IBM-PC HDD support +#options COMPAT_ATDISK + +# +# FreeBSD(98)-current is a *TEST VERSION*. +# It is highly recomended to compile with following options, and to +# record the panic messages and the result of trace command brefore +# reporting a problem. +# +# If you need more information for the kernel, KTRACE option may help +# you. +# +options DDB +options DIAGNOSTIC +#options KTRACE + + +config kernel root on wd0 + +controller nec0 +controller pci0 + +controller fdc0 at nec? port "IO_FD1" bio irq 11 drq 2 vector fdintr +disk fd0 at fdc0 drive 0 +disk fd1 at fdc0 drive 1 +disk fd2 at fdc0 drive 2 +disk fd3 at fdc0 drive 3 +tape ft0 at fdc0 drive 4 + +controller wdc0 at nec? port "IO_WD1" bio irq 9 vector wdintr +disk wd0 at wdc0 drive 0 +#disk wd1 at wdc0 drive 1 +#disk wd2 at wdc0 drive 2 +#disk wd3 at wdc0 drive 3 + +options ATAPI # Enable ATAPI support for IDE bus +options ATAPI_STATIC #Don't do it as an LKM +device wcd #IDE CD-ROM + +controller sbic0 at nec? port "IO_SCSI" bio irq 5 drq 3 vector sbicintr +#controller sbic0 at nec? port "IO_SCSI" bio irq 5 drq 3 flags 0xff vector sbicintr +controller aic0 at nec? port 0x1840 bio irq 5 vector aicintr +controller ahc0 + +controller scbus0 + +device sd0 + +device st0 + +device cd0 #Only need one of these, the code dynamically grows + +device od0 + +controller matcd0 at nec? port? bio + +# syscons is the default console driver, resembling an SCO console +device sc0 at nec? port "IO_KBD" tty irq 1 vector scintr +#options XSERVER # include code for XFree86 + +# Mandatory, don't remove +device npx0 at nec? port "IO_NPX" irq 8 vector npxintr + +# +# Laptop support (see LINT for more options) +# +device apm0 at nec? disable # Advanced Power Management +options APM_BROKEN_STATCLOCK # Workaround some buggy APM BIOS +# PCCARD (PCMCIA) support +#controller crd0 +#device pcic0 at crd? +#device pcic1 at crd? + +device lpt0 at nec? port "IO_LPT" tty +device mse0 at nec? port "IO_MSE" tty irq 13 vector mseintr + +device sio0 at nec? port "IO_COM1" tty irq 4 vector siointr +device sio1 at nec? port 0xd2 tty irq 5 flags 0x101 vector siointr +device sio2 at nec? port 0x8d2 tty flags 0x101 vector siointr + + +device ed0 at nec? port 0x00d0 net irq 6 vector edintr +device ed1 at nec? port 0x56d0 net irq 5 vector edintr +device ed2 at nec? port 0x00d0 net irq 6 iomem 0xd0000 iosiz 16384 vector edintr +device fe0 at nec? prot 0x00d0 net irq 3 vector feintr +device zp0 at nec? port 0x0300 net irq 10 iomem 0xe0000 vector zpintr +device ep0 at nec? port 0x00d0 net irq 6 vector epintr + +#controller snd0 +#device sb0 at nec? port 0x20d2 irq 10 conflicts drq 3 vector sbintr +#device sbxvi0 at nec? drq 3 +#device sbmidi0 at nec? port 0x80d2 +#device opl0 at nec? port 0x28d2 + +#device pcm0 at nec? port 0xa460 irq 12 vector pcmintr + +#device mss0 at nec? port 0xf40 irq12 drq 1 vectro adintr + +pseudo-device loop +pseudo-device ether +pseudo-device log +pseudo-device sl 2 +# ijppp uses tun instead of ppp device +#pseudo-device ppp 1 +pseudo-device tun 1 +pseudo-device pty 16 +# keep this if you want to be able to continue to use /stand/sysinstall +pseudo-device gzip # Exec gzipped a.out's diff --git a/sys/pc98/pc98/machdep.c b/sys/pc98/pc98/machdep.c new file mode 100644 index 0000000..029bbf5 --- /dev/null +++ b/sys/pc98/pc98/machdep.c @@ -0,0 +1,2068 @@ +/*- + * Copyright (c) 1992 Terrence R. Lambert. + * Copyright (c) 1982, 1987, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)machdep.c 7.4 (Berkeley) 6/3/91 + * $Id: machdep.c,v 1.192 1996/06/08 11:03:01 bde Exp $ + */ + +#include "npx.h" +#ifndef PC98 +#include "isa.h" +#endif +#include "opt_sysvipc.h" +#include "opt_ddb.h" +#include "opt_bounce.h" +#include "opt_machdep.h" +#include "opt_perfmon.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sysproto.h> +#include <sys/signalvar.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/buf.h> +#include <sys/reboot.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/mount.h> +#include <sys/msgbuf.h> +#include <sys/ioctl.h> +#include <sys/sysent.h> +#include <sys/tty.h> +#include <sys/sysctl.h> +#include <sys/devconf.h> +#include <sys/vmmeter.h> + +#ifdef SYSVSHM +#include <sys/shm.h> +#endif + +#ifdef SYSVMSG +#include <sys/msg.h> +#endif + +#ifdef SYSVSEM +#include <sys/sem.h> +#endif + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/vm_prot.h> +#include <vm/lock.h> +#include <vm/vm_kern.h> +#include <vm/vm_object.h> +#include <vm/vm_page.h> +#include <vm/vm_map.h> +#include <vm/vm_pager.h> +#include <vm/vm_extern.h> + +#include <sys/user.h> +#include <sys/exec.h> +#include <sys/vnode.h> + +#include <ddb/ddb.h> + +#include <net/netisr.h> + +#include <machine/cpu.h> +#include <machine/npx.h> +#include <machine/reg.h> +#include <machine/psl.h> +#include <machine/clock.h> +#include <machine/specialreg.h> +#include <machine/sysarch.h> +#include <machine/cons.h> +#include <machine/devconf.h> +#include <machine/bootinfo.h> +#include <machine/md_var.h> +#ifdef PERFMON +#include <machine/perfmon.h> +#endif + +#ifdef PC98 +#include <pc98/pc98/pc98.h> +#include <pc98/pc98/pc98_device.h> +#else +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/rtc.h> +#endif +#include <machine/random.h> + +extern void init386 __P((int first)); +extern int ptrace_set_pc __P((struct proc *p, unsigned int addr)); +extern int ptrace_single_step __P((struct proc *p)); +extern int ptrace_write_u __P((struct proc *p, vm_offset_t off, int data)); +extern void dblfault_handler __P((void)); + +extern void i486_bzero __P((void *, size_t)); +extern void i586_bzero __P((void *, size_t)); +extern void i686_bzero __P((void *, size_t)); + +static void cpu_startup __P((void *)); +SYSINIT(cpu, SI_SUB_CPU, SI_ORDER_FIRST, cpu_startup, NULL) + +static void identifycpu(void); + +char machine[] = "i386"; +SYSCTL_STRING(_hw, HW_MACHINE, machine, CTLFLAG_RD, machine, 0, ""); + +static char cpu_model[128]; +SYSCTL_STRING(_hw, HW_MODEL, model, CTLFLAG_RD, cpu_model, 0, ""); + +struct kern_devconf kdc_cpu0 = { + 0, 0, 0, /* filled in by dev_attach */ + "cpu", 0, { MDDT_CPU }, + 0, 0, 0, CPU_EXTERNALLEN, + 0, /* CPU has no parent */ + 0, /* no parentdata */ + DC_BUSY, /* the CPU is always busy */ + cpu_model, /* no sense in duplication */ + DC_CLS_CPU /* class */ +}; + +#ifndef PANIC_REBOOT_WAIT_TIME +#define PANIC_REBOOT_WAIT_TIME 15 /* default to 15 seconds */ +#endif + +#ifdef BOUNCE_BUFFERS +extern char *bouncememory; +extern int maxbkva; +#ifdef BOUNCEPAGES +int bouncepages = BOUNCEPAGES; +#else +int bouncepages = 0; +#endif +#endif /* BOUNCE_BUFFERS */ + +extern int freebufspace; +int msgbufmapped = 0; /* set when safe to use msgbuf */ +int _udatasel, _ucodesel; +u_int atdevbase; + + +int physmem = 0; +int cold = 1; + +static int +sysctl_hw_physmem SYSCTL_HANDLER_ARGS +{ + int error = sysctl_handle_int(oidp, 0, ctob(physmem), req); + return (error); +} + +SYSCTL_PROC(_hw, HW_PHYSMEM, physmem, CTLTYPE_INT|CTLFLAG_RD, + 0, 0, sysctl_hw_physmem, "I", ""); + +static int +sysctl_hw_usermem SYSCTL_HANDLER_ARGS +{ + int error = sysctl_handle_int(oidp, 0, + ctob(physmem - cnt.v_wire_count), req); + return (error); +} + +SYSCTL_PROC(_hw, HW_USERMEM, usermem, CTLTYPE_INT|CTLFLAG_RD, + 0, 0, sysctl_hw_usermem, "I", ""); + +int boothowto = 0, bootverbose = 0, Maxmem = 0; +static int badpages = 0; +#ifdef PC98 +int Maxmem_under16M = 0; +extern pt_entry_t *panic_kwin_pte; +extern caddr_t panic_kwin; +#endif +long dumplo; +extern int bootdev; + +vm_offset_t phys_avail[10]; + +/* must be 2 less so 0 0 can signal end of chunks */ +#define PHYS_AVAIL_ARRAY_END ((sizeof(phys_avail) / sizeof(vm_offset_t)) - 2) + +int cpu_class = CPUCLASS_386; /* smallest common denominator */ + +static void dumpsys __P((void)); +static void setup_netisrs __P((struct linker_set *)); /* XXX declare elsewhere */ + +static vm_offset_t buffer_sva, buffer_eva; +vm_offset_t clean_sva, clean_eva; +static vm_offset_t pager_sva, pager_eva; +extern struct linker_set netisr_set; + +#define offsetof(type, member) ((size_t)(&((type *)0)->member)) + +static void +cpu_startup(dummy) + void *dummy; +{ + register unsigned i; + register caddr_t v; + vm_offset_t maxaddr; + vm_size_t size = 0; + int firstaddr; + vm_offset_t minaddr; + + if (boothowto & RB_VERBOSE) + bootverbose++; + + /* + * Initialize error message buffer (at end of core). + */ + + /* avail_end was pre-decremented in init386() to compensate */ + for (i = 0; i < btoc(sizeof (struct msgbuf)); i++) + pmap_enter(pmap_kernel(), (vm_offset_t)msgbufp, + avail_end + i * PAGE_SIZE, + VM_PROT_ALL, TRUE); + msgbufmapped = 1; + + /* + * Good {morning,afternoon,evening,night}. + */ + printf(version); + cpu_class = i386_cpus[cpu].cpu_class; + startrtclock(); + identifycpu(); + printf("real memory = %d (%dK bytes)\n", ptoa(Maxmem), ptoa(Maxmem) / 1024); + /* + * Display any holes after the first chunk of extended memory. + */ + if (badpages != 0) { + int indx = 1; + + /* + * XXX skip reporting ISA hole & unmanaged kernel memory + */ + if (phys_avail[0] == PAGE_SIZE) + indx += 2; + + printf("Physical memory hole(s):\n"); + for (; phys_avail[indx + 1] != 0; indx += 2) { + int size = phys_avail[indx + 1] - phys_avail[indx]; + + printf("0x%08lx - 0x%08lx, %d bytes (%d pages)\n", phys_avail[indx], + phys_avail[indx + 1] - 1, size, size / PAGE_SIZE); + } + } + + /* + * Quickly wire in netisrs. + */ + setup_netisrs(&netisr_set); + +/* +#ifdef ISDN + DONET(isdnintr, NETISR_ISDN); +#endif +*/ + + /* + * Allocate space for system data structures. + * The first available kernel virtual address is in "v". + * As pages of kernel virtual memory are allocated, "v" is incremented. + * As pages of memory are allocated and cleared, + * "firstaddr" is incremented. + * An index into the kernel page table corresponding to the + * virtual memory address maintained in "v" is kept in "mapaddr". + */ + + /* + * Make two passes. The first pass calculates how much memory is + * needed and allocates it. The second pass assigns virtual + * addresses to the various data structures. + */ + firstaddr = 0; +again: + v = (caddr_t)firstaddr; + +#define valloc(name, type, num) \ + (name) = (type *)v; v = (caddr_t)((name)+(num)) +#define valloclim(name, type, num, lim) \ + (name) = (type *)v; v = (caddr_t)((lim) = ((name)+(num))) + valloc(callout, struct callout, ncallout); +#ifdef SYSVSHM + valloc(shmsegs, struct shmid_ds, shminfo.shmmni); +#endif +#ifdef SYSVSEM + valloc(sema, struct semid_ds, seminfo.semmni); + valloc(sem, struct sem, seminfo.semmns); + /* This is pretty disgusting! */ + valloc(semu, int, (seminfo.semmnu * seminfo.semusz) / sizeof(int)); +#endif +#ifdef SYSVMSG + valloc(msgpool, char, msginfo.msgmax); + valloc(msgmaps, struct msgmap, msginfo.msgseg); + valloc(msghdrs, struct msg, msginfo.msgtql); + valloc(msqids, struct msqid_ds, msginfo.msgmni); +#endif + + if (nbuf == 0) { + nbuf = 30; + if( physmem > 1024) + nbuf += min((physmem - 1024) / 12, 1024); + } + nswbuf = min(nbuf, 128); + + valloc(swbuf, struct buf, nswbuf); + valloc(buf, struct buf, nbuf); + +#ifdef BOUNCE_BUFFERS + /* + * If there is more than 16MB of memory, allocate some bounce buffers + */ + if (Maxmem > 4096) { + if (bouncepages == 0) { + bouncepages = 64; + bouncepages += ((Maxmem - 4096) / 2048) * 32; + } + v = (caddr_t)((vm_offset_t)round_page(v)); + valloc(bouncememory, char, bouncepages * PAGE_SIZE); + } +#endif + + /* + * End of first pass, size has been calculated so allocate memory + */ + if (firstaddr == 0) { + size = (vm_size_t)(v - firstaddr); + firstaddr = (int)kmem_alloc(kernel_map, round_page(size)); + if (firstaddr == 0) + panic("startup: no room for tables"); + goto again; + } + + /* + * End of second pass, addresses have been assigned + */ + if ((vm_size_t)(v - firstaddr) != size) + panic("startup: table size inconsistency"); + +#ifdef BOUNCE_BUFFERS + clean_map = kmem_suballoc(kernel_map, &clean_sva, &clean_eva, + (nbuf*MAXBSIZE) + (nswbuf*MAXPHYS) + + maxbkva + pager_map_size, TRUE); + io_map = kmem_suballoc(clean_map, &minaddr, &maxaddr, maxbkva, FALSE); +#else + clean_map = kmem_suballoc(kernel_map, &clean_sva, &clean_eva, + (nbuf*MAXBSIZE) + (nswbuf*MAXPHYS) + pager_map_size, TRUE); +#endif + buffer_map = kmem_suballoc(clean_map, &buffer_sva, &buffer_eva, + (nbuf*MAXBSIZE), TRUE); + pager_map = kmem_suballoc(clean_map, &pager_sva, &pager_eva, + (nswbuf*MAXPHYS) + pager_map_size, TRUE); + exec_map = kmem_suballoc(kernel_map, &minaddr, &maxaddr, + (16*ARG_MAX), TRUE); + exech_map = kmem_suballoc(kernel_map, &minaddr, &maxaddr, + (32*ARG_MAX), TRUE); + u_map = kmem_suballoc(kernel_map, &minaddr, &maxaddr, + (maxproc*UPAGES*PAGE_SIZE), FALSE); + + /* + * Finally, allocate mbuf pool. Since mclrefcnt is an off-size + * we use the more space efficient malloc in place of kmem_alloc. + */ + mclrefcnt = (char *)malloc(nmbclusters+PAGE_SIZE/MCLBYTES, + M_MBUF, M_NOWAIT); + bzero(mclrefcnt, nmbclusters+PAGE_SIZE/MCLBYTES); + mcl_map = kmem_suballoc(kmem_map, (vm_offset_t *)&mbutl, &maxaddr, + nmbclusters * MCLBYTES, FALSE); + { + vm_size_t mb_map_size; + mb_map_size = nmbufs * MSIZE; + mb_map = kmem_suballoc(kmem_map, &minaddr, &maxaddr, + round_page(mb_map_size), FALSE); + } + + /* + * Initialize callouts + */ + callfree = callout; + for (i = 1; i < ncallout; i++) + callout[i-1].c_next = &callout[i]; + + if (boothowto & RB_CONFIG) { + userconfig(); + cninit(); /* the preferred console may have changed */ + } + +#ifdef BOUNCE_BUFFERS + /* + * init bounce buffers + */ + vm_bounce_init(); +#endif + + printf("avail memory = %d (%dK bytes)\n", ptoa(cnt.v_free_count), + ptoa(cnt.v_free_count) / 1024); + + /* + * Set up buffers, so they can be used to read disk labels. + */ + bufinit(); + vm_pager_bufferinit(); + + /* + * In verbose mode, print out the BIOS's idea of the disk geometries. + */ + if (bootverbose) { + printf("BIOS Geometries:\n"); + for (i = 0; i < N_BIOS_GEOM; i++) { + unsigned long bios_geom; + int max_cylinder, max_head, max_sector; + + bios_geom = bootinfo.bi_bios_geom[i]; + + /* + * XXX the bootstrap punts a 1200K floppy geometry + * when the get-disk-geometry interrupt fails. Skip + * drives that have this geometry. + */ + if (bios_geom == 0x4f010f) + continue; + + printf(" %x:%08lx ", i, bios_geom); + max_cylinder = bios_geom >> 16; + max_head = (bios_geom >> 8) & 0xff; + max_sector = bios_geom & 0xff; + printf( + "0..%d=%d cylinders, 0..%d=%d heads, 1..%d=%d sectors\n", + max_cylinder, max_cylinder + 1, + max_head, max_head + 1, + max_sector, max_sector); + } + printf(" %d accounted for\n", bootinfo.bi_n_bios_used); + } +} + +int +register_netisr(num, handler) + int num; + netisr_t *handler; +{ + + if (num < 0 || num >= (sizeof(netisrs)/sizeof(*netisrs)) ) { + printf("register_netisr: bad isr number: %d\n", num); + return (EINVAL); + } + netisrs[num] = handler; + return (0); +} + +static void +setup_netisrs(ls) + struct linker_set *ls; +{ + int i; + const struct netisrtab *nit; + + for(i = 0; ls->ls_items[i]; i++) { + nit = (const struct netisrtab *)ls->ls_items[i]; + register_netisr(nit->nit_num, nit->nit_isr); + } +} + +static struct cpu_nameclass i386_cpus[] = { + { "Intel 80286", CPUCLASS_286 }, /* CPU_286 */ + { "i386SX", CPUCLASS_386 }, /* CPU_386SX */ + { "i386DX", CPUCLASS_386 }, /* CPU_386 */ + { "i486SX", CPUCLASS_486 }, /* CPU_486SX */ + { "i486DX", CPUCLASS_486 }, /* CPU_486 */ + { "Pentium", CPUCLASS_586 }, /* CPU_586 */ + { "Cy486DLC", CPUCLASS_486 }, /* CPU_486DLC */ + { "Pentium Pro", CPUCLASS_686 }, /* CPU_686 */ +}; + +static void +identifycpu() +{ + printf("CPU: "); + strncpy(cpu_model, i386_cpus[cpu].cpu_name, sizeof cpu_model); + +#if defined(I486_CPU) || defined(I586_CPU) || defined(I686_CPU) + if (!strcmp(cpu_vendor,"GenuineIntel")) { + if ((cpu_id & 0xf00) > 3) { + cpu_model[0] = '\0'; + + switch (cpu_id & 0x3000) { + case 0x1000: + strcpy(cpu_model, "Overdrive "); + break; + case 0x2000: + strcpy(cpu_model, "Dual "); + break; + } + + switch (cpu_id & 0xf00) { + case 0x400: + strcat(cpu_model, "i486 "); + break; + case 0x500: + strcat(cpu_model, "Pentium"); /* nb no space */ + break; + case 0x600: + strcat(cpu_model, "Pentium Pro"); + break; + default: + strcat(cpu_model, "unknown"); + break; + } + + switch (cpu_id & 0xff0) { + case 0x400: + strcat(cpu_model, "DX"); break; + case 0x410: + strcat(cpu_model, "DX"); break; + case 0x420: + strcat(cpu_model, "SX"); break; + case 0x430: + strcat(cpu_model, "DX2"); break; + case 0x440: + strcat(cpu_model, "SL"); break; + case 0x450: + strcat(cpu_model, "SX2"); break; + case 0x470: + strcat(cpu_model, "DX2 Write-Back Enhanced"); + break; + case 0x480: + strcat(cpu_model, "DX4"); break; + break; + } + } + } +#endif + printf("%s (", cpu_model); + switch(cpu_class) { + case CPUCLASS_286: + printf("286"); + break; +#if defined(I386_CPU) + case CPUCLASS_386: + printf("386"); + break; +#endif +#if defined(I486_CPU) + case CPUCLASS_486: + printf("486"); + bzero = i486_bzero; + break; +#endif +#if defined(I586_CPU) + case CPUCLASS_586: + printf("%d.%02d-MHz ", + ((100 * i586_ctr_rate) >> I586_CTR_RATE_SHIFT) / 100, + ((100 * i586_ctr_rate) >> I586_CTR_RATE_SHIFT) % 100); + printf("586"); + break; +#endif +#if defined(I686_CPU) + case CPUCLASS_686: + printf("%d.%02d-MHz ", + ((100 * i586_ctr_rate) >> I586_CTR_RATE_SHIFT) / 100, + ((100 * i586_ctr_rate) >> I586_CTR_RATE_SHIFT) % 100); + printf("686"); + break; +#endif + default: + printf("unknown"); /* will panic below... */ + } + printf("-class CPU)\n"); +#if defined(I486_CPU) || defined(I586_CPU) || defined(I686_CPU) + if(*cpu_vendor) + printf(" Origin = \"%s\"",cpu_vendor); + if(cpu_id) + printf(" Id = 0x%lx",cpu_id); + + if (!strcmp(cpu_vendor, "GenuineIntel")) { + printf(" Stepping=%ld", cpu_id & 0xf); + if (cpu_high > 0) { + printf("\n Features=0x%b", cpu_feature, + "\020" + "\001FPU" + "\002VME" + "\003DE" + "\004PSE" + "\005TSC" + "\006MSR" + "\007PAE" + "\010MCE" + "\011CX8" + "\012APIC" + "\013<b10>" + "\014<b11>" + "\015MTRR" + "\016PGE" + "\017MCA" + "\020CMOV" + ); + } + } + /* Avoid ugly blank lines: only print newline when we have to. */ + if (*cpu_vendor || cpu_id) + printf("\n"); +#endif + /* + * Now that we have told the user what they have, + * let them know if that machine type isn't configured. + */ + switch (cpu_class) { + case CPUCLASS_286: /* a 286 should not make it this far, anyway */ +#if !defined(I386_CPU) && !defined(I486_CPU) && !defined(I586_CPU) && !defined(I686_CPU) +#error This kernel is not configured for one of the supported CPUs +#endif +#if !defined(I386_CPU) + case CPUCLASS_386: +#endif +#if !defined(I486_CPU) + case CPUCLASS_486: +#endif +#if !defined(I586_CPU) + case CPUCLASS_586: +#endif +#if !defined(I686_CPU) + case CPUCLASS_686: +#endif + panic("CPU class not configured"); + default: + break; + } +#ifdef PERFMON + perfmon_init(); +#endif + dev_attach(&kdc_cpu0); +} + +/* + * Send an interrupt to process. + * + * Stack is set up to allow sigcode stored + * at top to call routine, followed by kcall + * to sigreturn routine below. After sigreturn + * resets the signal mask, the stack, and the + * frame pointer, it returns to the user + * specified pc, psl. + */ +void +sendsig(catcher, sig, mask, code) + sig_t catcher; + int sig, mask; + u_long code; +{ + register struct proc *p = curproc; + register int *regs; + register struct sigframe *fp; + struct sigframe sf; + struct sigacts *psp = p->p_sigacts; + int oonstack; + + regs = p->p_md.md_regs; + oonstack = psp->ps_sigstk.ss_flags & SS_ONSTACK; + /* + * Allocate and validate space for the signal handler context. + */ + if ((psp->ps_flags & SAS_ALTSTACK) && !oonstack && + (psp->ps_sigonstack & sigmask(sig))) { + fp = (struct sigframe *)(psp->ps_sigstk.ss_sp + + psp->ps_sigstk.ss_size - sizeof(struct sigframe)); + psp->ps_sigstk.ss_flags |= SS_ONSTACK; + } else { + fp = (struct sigframe *)regs[tESP] - 1; + } + + /* + * grow() will return FALSE if the fp will not fit inside the stack + * and the stack can not be grown. useracc will return FALSE + * if access is denied. + */ + if ((grow(p, (int)fp) == FALSE) || + (useracc((caddr_t)fp, sizeof (struct sigframe), B_WRITE) == FALSE)) { + /* + * Process has trashed its stack; give it an illegal + * instruction to halt it in its tracks. + */ + SIGACTION(p, SIGILL) = SIG_DFL; + sig = sigmask(SIGILL); + p->p_sigignore &= ~sig; + p->p_sigcatch &= ~sig; + p->p_sigmask &= ~sig; + psignal(p, SIGILL); + return; + } + + /* + * Build the argument list for the signal handler. + */ + if (p->p_sysent->sv_sigtbl) { + if (sig < p->p_sysent->sv_sigsize) + sig = p->p_sysent->sv_sigtbl[sig]; + else + sig = p->p_sysent->sv_sigsize + 1; + } + sf.sf_signum = sig; + sf.sf_code = code; + sf.sf_scp = &fp->sf_sc; + sf.sf_addr = (char *) regs[tERR]; + sf.sf_handler = catcher; + + /* save scratch registers */ + sf.sf_sc.sc_eax = regs[tEAX]; + sf.sf_sc.sc_ebx = regs[tEBX]; + sf.sf_sc.sc_ecx = regs[tECX]; + sf.sf_sc.sc_edx = regs[tEDX]; + sf.sf_sc.sc_esi = regs[tESI]; + sf.sf_sc.sc_edi = regs[tEDI]; + sf.sf_sc.sc_cs = regs[tCS]; + sf.sf_sc.sc_ds = regs[tDS]; + sf.sf_sc.sc_ss = regs[tSS]; + sf.sf_sc.sc_es = regs[tES]; + sf.sf_sc.sc_isp = regs[tISP]; + + /* + * Build the signal context to be used by sigreturn. + */ + sf.sf_sc.sc_onstack = oonstack; + sf.sf_sc.sc_mask = mask; + sf.sf_sc.sc_sp = regs[tESP]; + sf.sf_sc.sc_fp = regs[tEBP]; + sf.sf_sc.sc_pc = regs[tEIP]; + sf.sf_sc.sc_ps = regs[tEFLAGS]; + + /* + * Copy the sigframe out to the user's stack. + */ + if (copyout(&sf, fp, sizeof(struct sigframe)) != 0) { + /* + * Something is wrong with the stack pointer. + * ...Kill the process. + */ + sigexit(p, SIGILL); + }; + + regs[tESP] = (int)fp; + regs[tEIP] = (int)(((char *)PS_STRINGS) - *(p->p_sysent->sv_szsigcode)); + regs[tEFLAGS] &= ~PSL_VM; + regs[tCS] = _ucodesel; + regs[tDS] = _udatasel; + regs[tES] = _udatasel; + regs[tSS] = _udatasel; +} + +/* + * System call to cleanup state after a signal + * has been taken. Reset signal mask and + * stack state from context left by sendsig (above). + * Return to previous pc and psl as specified by + * context left by sendsig. Check carefully to + * make sure that the user has not modified the + * state to gain improper privileges. + */ +int +sigreturn(p, uap, retval) + struct proc *p; + struct sigreturn_args /* { + struct sigcontext *sigcntxp; + } */ *uap; + int *retval; +{ + register struct sigcontext *scp; + register struct sigframe *fp; + register int *regs = p->p_md.md_regs; + int eflags; + + /* + * (XXX old comment) regs[tESP] points to the return address. + * The user scp pointer is above that. + * The return address is faked in the signal trampoline code + * for consistency. + */ + scp = uap->sigcntxp; + fp = (struct sigframe *) + ((caddr_t)scp - offsetof(struct sigframe, sf_sc)); + + if (useracc((caddr_t)fp, sizeof (*fp), 0) == 0) + return(EINVAL); + + /* + * Don't allow users to change privileged or reserved flags. + */ +#define EFLAGS_SECURE(ef, oef) ((((ef) ^ (oef)) & ~PSL_USERCHANGE) == 0) + eflags = scp->sc_ps; + /* + * XXX do allow users to change the privileged flag PSL_RF. The + * cpu sets PSL_RF in tf_eflags for faults. Debuggers should + * sometimes set it there too. tf_eflags is kept in the signal + * context during signal handling and there is no other place + * to remember it, so the PSL_RF bit may be corrupted by the + * signal handler without us knowing. Corruption of the PSL_RF + * bit at worst causes one more or one less debugger trap, so + * allowing it is fairly harmless. + */ + if (!EFLAGS_SECURE(eflags & ~PSL_RF, regs[tEFLAGS] & ~PSL_RF)) { +#ifdef DEBUG + printf("sigreturn: eflags = 0x%x\n", eflags); +#endif + return(EINVAL); + } + + /* + * Don't allow users to load a valid privileged %cs. Let the + * hardware check for invalid selectors, excess privilege in + * other selectors, invalid %eip's and invalid %esp's. + */ +#define CS_SECURE(cs) (ISPL(cs) == SEL_UPL) + if (!CS_SECURE(scp->sc_cs)) { +#ifdef DEBUG + printf("sigreturn: cs = 0x%x\n", scp->sc_cs); +#endif + trapsignal(p, SIGBUS, T_PROTFLT); + return(EINVAL); + } + + /* restore scratch registers */ + regs[tEAX] = scp->sc_eax; + regs[tEBX] = scp->sc_ebx; + regs[tECX] = scp->sc_ecx; + regs[tEDX] = scp->sc_edx; + regs[tESI] = scp->sc_esi; + regs[tEDI] = scp->sc_edi; + regs[tCS] = scp->sc_cs; + regs[tDS] = scp->sc_ds; + regs[tES] = scp->sc_es; + regs[tSS] = scp->sc_ss; + regs[tISP] = scp->sc_isp; + + if (useracc((caddr_t)scp, sizeof (*scp), 0) == 0) + return(EINVAL); + + if (scp->sc_onstack & 01) + p->p_sigacts->ps_sigstk.ss_flags |= SS_ONSTACK; + else + p->p_sigacts->ps_sigstk.ss_flags &= ~SS_ONSTACK; + p->p_sigmask = scp->sc_mask &~ + (sigmask(SIGKILL)|sigmask(SIGCONT)|sigmask(SIGSTOP)); + regs[tEBP] = scp->sc_fp; + regs[tESP] = scp->sc_sp; + regs[tEIP] = scp->sc_pc; + regs[tEFLAGS] = eflags; + return(EJUSTRETURN); +} + +#ifdef PC98 +/* + * disable screen saver + */ +extern int scrn_blanked; +extern void (*current_saver)(int blank); + +static void pc98_disable_screen_saver(void) +{ + if (scrn_blanked) + (*current_saver)(FALSE); +} + + +/* + * change ralay on video card + */ +static void pc98_change_relay(void) +{ + /* mode register 2 */ + outb(0x6a, 0x07); /* enable to change FF */ + outb(0x6a, 0x8e); + outb(0x6a, 0x06); /* disable to change FF */ + outb(0x7c, 0); + outb(0x68, 0x0f); /* display */ + + /* PWLB */ + *(int *)panic_kwin_pte = (0xf0c00000 & PG_FRAME) | PG_V | PG_RW ; + pmap_update(); + *(long *)panic_kwin = 0; + /* PowerWindow(C-Bus) */ + outb(0x0dc | 0x600, 0); /* XXX */ + /* PCHKB & PCSKB4 */ + outb(0x6e68, 0); + /* PCSKB */ + outb(0x6ee8, 0); + /* NEC-S3, Cirrus (local bus) */ + outb(0xfaa, 3); + outb(0xfab, 1); + outb(0xfaa, 6); + outb(0xfab, 0xff); + outb(0xfaa, 7); + outb(0xfab, 0); + /* NEC-S3 (C-bus) */ + outb(0xfa2, 3); + outb(0xfa3, 0); + /* GA-NB */ + outb(0x40e1, 0xc2); + /* WAB-S & WAP */ + outb(0x40e1, 0xfa); + + /* stop G-GDC */ + outb(0xa2, 0x0c); + /* XXX start T-GDC (which is true?)*/ + outb(0x62, 0x69); + outb(0x62, 0x0d); +} +#endif + + +static int waittime = -1; +struct pcb dumppcb; + +__dead void +boot(howto) + int howto; +{ + if (!cold && (howto & RB_NOSYNC) == 0 && waittime < 0) { + register struct buf *bp; + int iter, nbusy; + +#ifdef PC98 + pc98_change_relay(); + pc98_disable_screen_saver(); +#endif + waittime = 0; + printf("\nsyncing disks... "); + + sync(&proc0, NULL, NULL); + + for (iter = 0; iter < 20; iter++) { + nbusy = 0; + for (bp = &buf[nbuf]; --bp >= buf; ) { + if ((bp->b_flags & (B_BUSY | B_INVAL)) == B_BUSY) { + nbusy++; + } + } + if (nbusy == 0) + break; + printf("%d ", nbusy); + DELAY(40000 * iter); + } + if (nbusy) { + /* + * Failed to sync all blocks. Indicate this and don't + * unmount filesystems (thus forcing an fsck on reboot). + */ + printf("giving up\n"); +#ifdef SHOW_BUSYBUFS + nbusy = 0; + for (bp = &buf[nbuf]; --bp >= buf; ) { + if ((bp->b_flags & (B_BUSY | B_INVAL)) == B_BUSY) { + nbusy++; + printf("%d: dev:%08x, flags:%08x, blkno:%d, lblkno:%d\n", nbusy, bp->b_dev, bp->b_flags, bp->b_blkno, bp->b_lblkno); + } + } + DELAY(5000000); /* 5 seconds */ +#endif + } else { + printf("done\n"); + /* + * Unmount filesystems + */ + if (panicstr == 0) + vfs_unmountall(); + } + DELAY(100000); /* wait for console output to finish */ + dev_shutdownall(FALSE); + } + splhigh(); + if (howto & RB_HALT) { + printf("\n"); + printf("The operating system has halted.\n"); + printf("Please press any key to reboot.\n\n"); + cngetc(); + } else { + if (howto & RB_DUMP) { + if (!cold) { + savectx(&dumppcb); + dumppcb.pcb_cr3 = rcr3(); + dumpsys(); + } + + if (PANIC_REBOOT_WAIT_TIME != 0) { + if (PANIC_REBOOT_WAIT_TIME != -1) { + int loop; + printf("Automatic reboot in %d seconds - press a key on the console to abort\n", + PANIC_REBOOT_WAIT_TIME); + for (loop = PANIC_REBOOT_WAIT_TIME * 10; loop > 0; --loop) { + DELAY(1000 * 100); /* 1/10th second */ + if (cncheckc()) /* Did user type a key? */ + break; + } + if (!loop) + goto die; + } + } else { /* zero time specified - reboot NOW */ + goto die; + } + printf("--> Press a key on the console to reboot <--\n"); + cngetc(); + } + } +die: + printf("Rebooting...\n"); + DELAY(1000000); /* wait 1 sec for printf's to complete and be read */ + cpu_reset(); + for(;;) ; + /* NOTREACHED */ +} + +/* + * Magic number for savecore + * + * exported (symorder) and used at least by savecore(8) + * + */ +u_long dumpmag = 0x8fca0101UL; + +static int dumpsize = 0; /* also for savecore */ + +static int dodump = 1; +SYSCTL_INT(_machdep, OID_AUTO, do_dump, CTLFLAG_RW, &dodump, 0, ""); + +/* + * Doadump comes here after turning off memory management and + * getting on the dump stack, either when called above, or by + * the auto-restart code. + */ +static void +dumpsys() +{ + + if (!dodump) + return; + if (dumpdev == NODEV) + return; + if ((minor(dumpdev)&07) != 1) + return; + if (!(bdevsw[major(dumpdev)])) + return; + if (!(bdevsw[major(dumpdev)]->d_dump)) + return; + dumpsize = Maxmem; + printf("\ndumping to dev %lx, offset %ld\n", dumpdev, dumplo); + printf("dump "); + switch ((*bdevsw[major(dumpdev)]->d_dump)(dumpdev)) { + + case ENXIO: + printf("device bad\n"); + break; + + case EFAULT: + printf("device not ready\n"); + break; + + case EINVAL: + printf("area improper\n"); + break; + + case EIO: + printf("i/o error\n"); + break; + + case EINTR: + printf("aborted from console\n"); + break; + + default: + printf("succeeded\n"); + break; + } +} + +/* + * Clear registers on exec + */ +void +setregs(p, entry, stack) + struct proc *p; + u_long entry; + u_long stack; +{ + int *regs = p->p_md.md_regs; + +#ifdef USER_LDT + struct pcb *pcb = &p->p_addr->u_pcb; + + /* was i386_user_cleanup() in NetBSD */ + if (pcb->pcb_ldt) { + if (pcb == curpcb) + lldt(GSEL(GUSERLDT_SEL, SEL_KPL)); + kmem_free(kernel_map, (vm_offset_t)pcb->pcb_ldt, + pcb->pcb_ldt_len * sizeof(union descriptor)); + pcb->pcb_ldt_len = (int)pcb->pcb_ldt = 0; + } +#endif + + bzero(regs, sizeof(struct trapframe)); + regs[tEIP] = entry; + regs[tESP] = stack; + regs[tEFLAGS] = PSL_USER | (regs[tEFLAGS] & PSL_T); + regs[tSS] = _udatasel; + regs[tDS] = _udatasel; + regs[tES] = _udatasel; + regs[tCS] = _ucodesel; + + p->p_addr->u_pcb.pcb_flags = 0; /* no fp at all */ + load_cr0(rcr0() | CR0_TS); /* start emulating */ +#if NNPX > 0 + npxinit(__INITIAL_NPXCW__); +#endif /* NNPX > 0 */ +} + +static int +sysctl_machdep_adjkerntz SYSCTL_HANDLER_ARGS +{ + int error; + error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, + req); + if (!error && req->newptr) + resettodr(); + return (error); +} + +SYSCTL_PROC(_machdep, CPU_ADJKERNTZ, adjkerntz, CTLTYPE_INT|CTLFLAG_RW, + &adjkerntz, 0, sysctl_machdep_adjkerntz, "I", ""); + +SYSCTL_INT(_machdep, CPU_DISRTCSET, disable_rtc_set, + CTLFLAG_RW, &disable_rtc_set, 0, ""); + +SYSCTL_STRUCT(_machdep, CPU_BOOTINFO, bootinfo, + CTLFLAG_RD, &bootinfo, bootinfo, ""); + +SYSCTL_INT(_machdep, CPU_WALLCLOCK, wall_cmos_clock, + CTLFLAG_RW, &wall_cmos_clock, 0, ""); + +/* + * Initialize 386 and configure to run kernel + */ + +/* + * Initialize segments & interrupt table + */ + +int currentldt; +int _default_ldt; +union descriptor gdt[NGDT]; /* global descriptor table */ +struct gate_descriptor idt[NIDT]; /* interrupt descriptor table */ +union descriptor ldt[NLDT]; /* local descriptor table */ + +static struct i386tss dblfault_tss; +static char dblfault_stack[PAGE_SIZE]; + +extern struct user *proc0paddr; + +/* software prototypes -- in more palatable form */ +struct soft_segment_descriptor gdt_segs[] = { +/* GNULL_SEL 0 Null Descriptor */ +{ 0x0, /* segment base address */ + 0x0, /* length */ + 0, /* segment type */ + 0, /* segment descriptor priority level */ + 0, /* segment descriptor present */ + 0, 0, + 0, /* default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, +/* GCODE_SEL 1 Code Descriptor for kernel */ +{ 0x0, /* segment base address */ + 0xfffff, /* length - all address space */ + SDT_MEMERA, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 1, /* default 32 vs 16 bit size */ + 1 /* limit granularity (byte/page units)*/ }, +/* GDATA_SEL 2 Data Descriptor for kernel */ +{ 0x0, /* segment base address */ + 0xfffff, /* length - all address space */ + SDT_MEMRWA, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 1, /* default 32 vs 16 bit size */ + 1 /* limit granularity (byte/page units)*/ }, +/* GLDT_SEL 3 LDT Descriptor */ +{ (int) ldt, /* segment base address */ + sizeof(ldt)-1, /* length - all address space */ + SDT_SYSLDT, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 0, /* unused - default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, +/* GTGATE_SEL 4 Null Descriptor - Placeholder */ +{ 0x0, /* segment base address */ + 0x0, /* length - all address space */ + 0, /* segment type */ + 0, /* segment descriptor priority level */ + 0, /* segment descriptor present */ + 0, 0, + 0, /* default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, +/* GPANIC_SEL 5 Panic Tss Descriptor */ +{ (int) &dblfault_tss, /* segment base address */ + sizeof(struct i386tss)-1,/* length - all address space */ + SDT_SYS386TSS, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 0, /* unused - default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, +/* GPROC0_SEL 6 Proc 0 Tss Descriptor */ +{ (int) kstack, /* segment base address */ + sizeof(struct i386tss)-1,/* length - all address space */ + SDT_SYS386TSS, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 0, /* unused - default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, +/* GUSERLDT_SEL 7 User LDT Descriptor per process */ +{ (int) ldt, /* segment base address */ + (512 * sizeof(union descriptor)-1), /* length */ + SDT_SYSLDT, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 0, /* unused - default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, +/* GAPMCODE32_SEL 8 APM BIOS 32-bit interface (32bit Code) */ +{ 0, /* segment base address (overwritten by APM) */ + 0xfffff, /* length */ + SDT_MEMERA, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 1, /* default 32 vs 16 bit size */ + 1 /* limit granularity (byte/page units)*/ }, +/* GAPMCODE16_SEL 9 APM BIOS 32-bit interface (16bit Code) */ +{ 0, /* segment base address (overwritten by APM) */ + 0xfffff, /* length */ + SDT_MEMERA, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 0, /* default 32 vs 16 bit size */ + 1 /* limit granularity (byte/page units)*/ }, +/* GAPMDATA_SEL 10 APM BIOS 32-bit interface (Data) */ +{ 0, /* segment base address (overwritten by APM) */ + 0xfffff, /* length */ + SDT_MEMRWA, /* segment type */ + 0, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 1, /* default 32 vs 16 bit size */ + 1 /* limit granularity (byte/page units)*/ }, +}; + +static struct soft_segment_descriptor ldt_segs[] = { + /* Null Descriptor - overwritten by call gate */ +{ 0x0, /* segment base address */ + 0x0, /* length - all address space */ + 0, /* segment type */ + 0, /* segment descriptor priority level */ + 0, /* segment descriptor present */ + 0, 0, + 0, /* default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, + /* Null Descriptor - overwritten by call gate */ +{ 0x0, /* segment base address */ + 0x0, /* length - all address space */ + 0, /* segment type */ + 0, /* segment descriptor priority level */ + 0, /* segment descriptor present */ + 0, 0, + 0, /* default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, + /* Null Descriptor - overwritten by call gate */ +{ 0x0, /* segment base address */ + 0x0, /* length - all address space */ + 0, /* segment type */ + 0, /* segment descriptor priority level */ + 0, /* segment descriptor present */ + 0, 0, + 0, /* default 32 vs 16 bit size */ + 0 /* limit granularity (byte/page units)*/ }, + /* Code Descriptor for user */ +{ 0x0, /* segment base address */ + 0xfffff, /* length - all address space */ + SDT_MEMERA, /* segment type */ + SEL_UPL, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 1, /* default 32 vs 16 bit size */ + 1 /* limit granularity (byte/page units)*/ }, + /* Data Descriptor for user */ +{ 0x0, /* segment base address */ + 0xfffff, /* length - all address space */ + SDT_MEMRWA, /* segment type */ + SEL_UPL, /* segment descriptor priority level */ + 1, /* segment descriptor present */ + 0, 0, + 1, /* default 32 vs 16 bit size */ + 1 /* limit granularity (byte/page units)*/ }, +}; + +void +setidt(idx, func, typ, dpl, selec) + int idx; + inthand_t *func; + int typ; + int dpl; + int selec; +{ + struct gate_descriptor *ip = idt + idx; + + ip->gd_looffset = (int)func; + ip->gd_selector = selec; + ip->gd_stkcpy = 0; + ip->gd_xx = 0; + ip->gd_type = typ; + ip->gd_dpl = dpl; + ip->gd_p = 1; + ip->gd_hioffset = ((int)func)>>16 ; +} + +#define IDTVEC(name) __CONCAT(X,name) + +extern inthand_t + IDTVEC(div), IDTVEC(dbg), IDTVEC(nmi), IDTVEC(bpt), IDTVEC(ofl), + IDTVEC(bnd), IDTVEC(ill), IDTVEC(dna), IDTVEC(fpusegm), + IDTVEC(tss), IDTVEC(missing), IDTVEC(stk), IDTVEC(prot), + IDTVEC(page), IDTVEC(rsvd), IDTVEC(fpu), IDTVEC(align), + IDTVEC(syscall), IDTVEC(int0x80_syscall); + +void +sdtossd(sd, ssd) + struct segment_descriptor *sd; + struct soft_segment_descriptor *ssd; +{ + ssd->ssd_base = (sd->sd_hibase << 24) | sd->sd_lobase; + ssd->ssd_limit = (sd->sd_hilimit << 16) | sd->sd_lolimit; + ssd->ssd_type = sd->sd_type; + ssd->ssd_dpl = sd->sd_dpl; + ssd->ssd_p = sd->sd_p; + ssd->ssd_def32 = sd->sd_def32; + ssd->ssd_gran = sd->sd_gran; +} + +void +init386(first) + int first; +{ + int x; + unsigned biosbasemem, biosextmem; + struct gate_descriptor *gdp; + int gsel_tss; + /* table descriptors - used to load tables by microp */ + struct region_descriptor r_gdt, r_idt; + int pagesinbase, pagesinext; + int target_page, pa_indx; + + proc0.p_addr = proc0paddr; + + atdevbase = ISA_HOLE_START + KERNBASE; + + /* + * Initialize the console before we print anything out. + */ + cninit(); + + /* + * make gdt memory segments, the code segment goes up to end of the + * page with etext in it, the data segment goes to the end of + * the address space + */ + /* + * XXX text protection is temporarily (?) disabled. The limit was + * i386_btop(round_page(etext)) - 1. + */ + gdt_segs[GCODE_SEL].ssd_limit = i386_btop(0) - 1; + gdt_segs[GDATA_SEL].ssd_limit = i386_btop(0) - 1; + for (x = 0; x < NGDT; x++) + ssdtosd(&gdt_segs[x], &gdt[x].sd); + + /* make ldt memory segments */ + /* + * The data segment limit must not cover the user area because we + * don't want the user area to be writable in copyout() etc. (page + * level protection is lost in kernel mode on 386's). Also, we + * don't want the user area to be writable directly (page level + * protection of the user area is not available on 486's with + * CR0_WP set, because there is no user-read/kernel-write mode). + * + * XXX - VM_MAXUSER_ADDRESS is an end address, not a max. And it + * should be spelled ...MAX_USER... + */ +#define VM_END_USER_RW_ADDRESS VM_MAXUSER_ADDRESS + /* + * The code segment limit has to cover the user area until we move + * the signal trampoline out of the user area. This is safe because + * the code segment cannot be written to directly. + */ +#define VM_END_USER_R_ADDRESS (VM_END_USER_RW_ADDRESS + UPAGES * PAGE_SIZE) + ldt_segs[LUCODE_SEL].ssd_limit = i386_btop(VM_END_USER_R_ADDRESS) - 1; + ldt_segs[LUDATA_SEL].ssd_limit = i386_btop(VM_END_USER_RW_ADDRESS) - 1; + /* Note. eventually want private ldts per process */ + for (x = 0; x < NLDT; x++) + ssdtosd(&ldt_segs[x], &ldt[x].sd); + + /* exceptions */ + for (x = 0; x < NIDT; x++) + setidt(x, &IDTVEC(rsvd), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(0, &IDTVEC(div), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(1, &IDTVEC(dbg), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(2, &IDTVEC(nmi), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(3, &IDTVEC(bpt), SDT_SYS386TGT, SEL_UPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(4, &IDTVEC(ofl), SDT_SYS386TGT, SEL_UPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(5, &IDTVEC(bnd), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(6, &IDTVEC(ill), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(7, &IDTVEC(dna), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(8, 0, SDT_SYSTASKGT, SEL_KPL, GSEL(GPANIC_SEL, SEL_KPL)); + setidt(9, &IDTVEC(fpusegm), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(10, &IDTVEC(tss), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(11, &IDTVEC(missing), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(12, &IDTVEC(stk), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(13, &IDTVEC(prot), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); +#ifdef CYRIX_486DLC + setidt(14, &IDTVEC(page), SDT_SYS386IGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); +#else + setidt(14, &IDTVEC(page), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); +#endif + setidt(15, &IDTVEC(rsvd), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(16, &IDTVEC(fpu), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(17, &IDTVEC(align), SDT_SYS386TGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL)); + setidt(0x80, &IDTVEC(int0x80_syscall), + SDT_SYS386TGT, SEL_UPL, GSEL(GCODE_SEL, SEL_KPL)); + +#ifdef PC98 +#include "nec.h" +#include "epson.h" +#if NNEC > 0 || NEPSON > 0 + pc98_defaultirq(); +#endif +#else /* IBM-PC */ +#include "isa.h" +#if NISA >0 + isa_defaultirq(); +#endif +#endif + rand_initialize(); + + r_gdt.rd_limit = sizeof(gdt) - 1; + r_gdt.rd_base = (int) gdt; + lgdt(&r_gdt); + + r_idt.rd_limit = sizeof(idt) - 1; + r_idt.rd_base = (int) idt; + lidt(&r_idt); + + _default_ldt = GSEL(GLDT_SEL, SEL_KPL); + lldt(_default_ldt); + currentldt = _default_ldt; + +#ifdef DDB + kdb_init(); + if (boothowto & RB_KDB) + Debugger("Boot flags requested debugger"); +#endif + +#ifdef PC98 +#ifdef EPSON_MEMWIN + if (pc98_machine_type & M_EPSON_PC98) { + if (Maxmem > 3840) { + if (Maxmem == Maxmem_under16M) { + Maxmem = 3840; + Maxmem_under16M = 3840; + } else if (Maxmem_under16M > 3840) { + Maxmem_under16M = 3840; + } + } + + /* Disable 15MB-16MB caching */ + switch (epson_machine_id) { + case 0x34: /* PC486HX */ + case 0x35: /* PC486HG */ + case 0x3B: /* PC486HA */ + /* Cache control start */ + outb(0x43f, 0x42); + outw(0xc40, 0x0033); + + /* Disable 0xF00000-0xFFFFFF */ + outb(0xc48, 0x49); outb(0xc4c, 0x00); + outb(0xc48, 0x48); outb(0xc4c, 0xf0); + outb(0xc48, 0x4d); outb(0xc4c, 0x00); + outb(0xc48, 0x4c); outb(0xc4c, 0xff); + outb(0xc48, 0x4f); outb(0xc4c, 0x00); + + /* Cache control end */ + outb(0x43f, 0x40); + break; + + case 0x2B: /* PC486GR/GF */ + case 0x30: /* PC486P */ + case 0x31: /* PC486GRSuper */ + case 0x32: /* PC486GR+ */ + case 0x37: /* PC486SE */ + case 0x38: /* PC486SR */ + /* Disable 0xF00000-0xFFFFFF */ + outb(0x43f, 0x42); + outb(0x467, 0xe0); + outb(0x567, 0xd8); + + outb(0x43f, 0x40); + outb(0x467, 0xe0); + outb(0x567, 0xe0); + break; + } + + /* Disable 15MB-16MB RAM and enable memory window */ + outb(0x43b, inb(0x43b) & 0xfd); /* clear bit1 */ + } +#endif + biosbasemem = 640; /* 640KB */ + biosextmem = (Maxmem * PAGE_SIZE - 0x100000)/1024; /* extent memory */ +#else /* IBM-PC */ + /* Use BIOS values stored in RTC CMOS RAM, since probing + * breaks certain 386 AT relics. + */ + biosbasemem = rtcin(RTC_BASELO)+ (rtcin(RTC_BASEHI)<<8); + biosextmem = rtcin(RTC_EXTLO)+ (rtcin(RTC_EXTHI)<<8); + + /* + * Print a warning if the official BIOS interface disagrees + * with the hackish interface used above. Eventually only + * the official interface should be used. + */ + if (bootinfo.bi_memsizes_valid) { + if (bootinfo.bi_basemem != biosbasemem) + printf("BIOS basemem (%ldK) != RTC basemem (%dK)\n", + bootinfo.bi_basemem, biosbasemem); + if (bootinfo.bi_extmem != biosextmem) + printf("BIOS extmem (%ldK) != RTC extmem (%dK)\n", + bootinfo.bi_extmem, biosextmem); + } +#endif + + /* + * If BIOS tells us that it has more than 640k in the basemem, + * don't believe it - set it to 640k. + */ + if (biosbasemem > 640) + biosbasemem = 640; + + /* + * Some 386 machines might give us a bogus number for extended + * mem. If this happens, stop now. + */ +#ifndef PC98 +#ifndef LARGEMEM + if (biosextmem > 65536) { + panic("extended memory beyond limit of 64MB"); + /* NOTREACHED */ + } +#endif +#endif + + pagesinbase = biosbasemem * 1024 / PAGE_SIZE; + pagesinext = biosextmem * 1024 / PAGE_SIZE; + + /* + * Special hack for chipsets that still remap the 384k hole when + * there's 16MB of memory - this really confuses people that + * are trying to use bus mastering ISA controllers with the + * "16MB limit"; they only have 16MB, but the remapping puts + * them beyond the limit. + */ +#ifndef PC98 + /* + * If extended memory is between 15-16MB (16-17MB phys address range), + * chop it to 15MB. + */ + if ((pagesinext > 3840) && (pagesinext < 4096)) + pagesinext = 3840; +#endif + + /* + * Maxmem isn't the "maximum memory", it's one larger than the + * highest page of of the physical address space. It + */ + Maxmem = pagesinext + 0x100000/PAGE_SIZE; + +#ifdef MAXMEM + Maxmem = MAXMEM/4; +#endif + + /* call pmap initialization to make new kernel address space */ + pmap_bootstrap (first, 0); + + /* + * Size up each available chunk of physical memory. + */ + + /* + * We currently don't bother testing base memory. + * XXX ...but we probably should. + */ + pa_indx = 0; + badpages = 0; + if (pagesinbase > 1) { + phys_avail[pa_indx++] = PAGE_SIZE; /* skip first page of memory */ + phys_avail[pa_indx] = ptoa(pagesinbase);/* memory up to the ISA hole */ + physmem = pagesinbase - 1; + } else { + /* point at first chunk end */ + pa_indx++; + } + +#ifdef PC98 + /* + * Certain 'CPU accelerator' supports over 16MB memory on + * the machines whose BIOS doesn't store true size. + * To support this, we don't trust BIOS values if Maxmem < 4096. + */ + if (Maxmem < 4096) { + for (target_page = ptoa(4096); /* 16MB */ + target_page < ptoa(32768); /* 128MB */ + target_page += 256 * PAGE_SIZE /* 1MB step */) { + int tmp, page_bad = FALSE, OrigMaxmem = Maxmem; + + *(int *)CMAP1 = PG_V | PG_RW | PG_N | target_page; + pmap_update(); + + tmp = *(int *)CADDR1; + /* + * Test for alternating 1's and 0's + */ + *(volatile int *)CADDR1 = 0xaaaaaaaa; + if (*(volatile int *)CADDR1 != 0xaaaaaaaa) { + page_bad = TRUE; + } + /* + * Test for alternating 0's and 1's + */ + *(volatile int *)CADDR1 = 0x55555555; + if (*(volatile int *)CADDR1 != 0x55555555) { + page_bad = TRUE; + } + /* + * Test for all 1's + */ + *(volatile int *)CADDR1 = 0xffffffff; + if (*(volatile int *)CADDR1 != 0xffffffff) { + page_bad = TRUE; + } + /* + * Test for all 0's + */ + *(volatile int *)CADDR1 = 0x0; + if (*(volatile int *)CADDR1 != 0x0) { + /* + * test of page failed + */ + page_bad = TRUE; + } + /* + * Restore original value. + */ + *(int *)CADDR1 = tmp; + if (page_bad == TRUE) { + if (target_page > ptoa(4096)) + Maxmem = atop(target_page); + else + Maxmem = OrigMaxmem; + + break; + } + } + *(int *)CMAP1 = 0; + pmap_update(); + + /* XXX */ + if (Maxmem > 3840) { + Maxmem_under16M = 3840; + if (Maxmem < 4096) { + Maxmem = 3840; + } + } + } +#endif + + for (target_page = avail_start; target_page < ptoa(Maxmem); target_page += PAGE_SIZE) { + int tmp, page_bad = FALSE; + +#ifdef PC98 + /* skip system area */ + if (target_page>=ptoa(Maxmem_under16M) && + target_page < ptoa(4096)) + page_bad = TRUE; +#endif + /* + * map page into kernel: valid, read/write, non-cacheable + */ + *(int *)CMAP1 = PG_V | PG_RW | PG_N | target_page; + pmap_update(); + + tmp = *(int *)CADDR1; + /* + * Test for alternating 1's and 0's + */ + *(volatile int *)CADDR1 = 0xaaaaaaaa; + if (*(volatile int *)CADDR1 != 0xaaaaaaaa) { + page_bad = TRUE; + } + /* + * Test for alternating 0's and 1's + */ + *(volatile int *)CADDR1 = 0x55555555; + if (*(volatile int *)CADDR1 != 0x55555555) { + page_bad = TRUE; + } + /* + * Test for all 1's + */ + *(volatile int *)CADDR1 = 0xffffffff; + if (*(volatile int *)CADDR1 != 0xffffffff) { + page_bad = TRUE; + } + /* + * Test for all 0's + */ + *(volatile int *)CADDR1 = 0x0; + if (*(volatile int *)CADDR1 != 0x0) { + /* + * test of page failed + */ + page_bad = TRUE; + } + /* + * Restore original value. + */ + *(int *)CADDR1 = tmp; + + /* + * Adjust array of valid/good pages. + */ + if (page_bad == FALSE) { + /* + * If this good page is a continuation of the + * previous set of good pages, then just increase + * the end pointer. Otherwise start a new chunk. + * Note that "end" points one higher than end, + * making the range >= start and < end. + */ + if (phys_avail[pa_indx] == target_page) { + phys_avail[pa_indx] += PAGE_SIZE; + } else { + pa_indx++; + if (pa_indx == PHYS_AVAIL_ARRAY_END) { + printf("Too many holes in the physical address space, giving up\n"); + pa_indx--; + break; + } + phys_avail[pa_indx++] = target_page; /* start */ + phys_avail[pa_indx] = target_page + PAGE_SIZE; /* end */ + } + physmem++; + } else { + badpages++; + page_bad = FALSE; + } + } + + *(int *)CMAP1 = 0; + pmap_update(); + + /* + * XXX + * The last chunk must contain at least one page plus the message + * buffer to avoid complicating other code (message buffer address + * calculation, etc.). + */ + while (phys_avail[pa_indx - 1] + PAGE_SIZE + + round_page(sizeof(struct msgbuf)) >= phys_avail[pa_indx]) { + physmem -= atop(phys_avail[pa_indx] - phys_avail[pa_indx - 1]); + phys_avail[pa_indx--] = 0; + phys_avail[pa_indx--] = 0; + } + + Maxmem = atop(phys_avail[pa_indx]); + + /* Trim off space for the message buffer. */ + phys_avail[pa_indx] -= round_page(sizeof(struct msgbuf)); + + avail_end = phys_avail[pa_indx]; + + /* now running on new page tables, configured,and u/iom is accessible */ + + /* make a initial tss so microp can get interrupt stack on syscall! */ + proc0.p_addr->u_pcb.pcb_tss.tss_esp0 = (int) kstack + UPAGES*PAGE_SIZE; + proc0.p_addr->u_pcb.pcb_tss.tss_ss0 = GSEL(GDATA_SEL, SEL_KPL) ; + gsel_tss = GSEL(GPROC0_SEL, SEL_KPL); + + dblfault_tss.tss_esp = dblfault_tss.tss_esp0 = dblfault_tss.tss_esp1 = + dblfault_tss.tss_esp2 = (int) &dblfault_stack[sizeof(dblfault_stack)]; + dblfault_tss.tss_ss = dblfault_tss.tss_ss0 = dblfault_tss.tss_ss1 = + dblfault_tss.tss_ss2 = GSEL(GDATA_SEL, SEL_KPL); + dblfault_tss.tss_cr3 = IdlePTD; + dblfault_tss.tss_eip = (int) dblfault_handler; + dblfault_tss.tss_eflags = PSL_KERNEL; + dblfault_tss.tss_ds = dblfault_tss.tss_es = dblfault_tss.tss_fs = dblfault_tss.tss_gs = + GSEL(GDATA_SEL, SEL_KPL); + dblfault_tss.tss_cs = GSEL(GCODE_SEL, SEL_KPL); + dblfault_tss.tss_ldt = GSEL(GLDT_SEL, SEL_KPL); + + ((struct i386tss *)gdt_segs[GPROC0_SEL].ssd_base)->tss_ioopt = + (sizeof(struct i386tss))<<16; + + ltr(gsel_tss); + + /* make a call gate to reenter kernel with */ + gdp = &ldt[LSYS5CALLS_SEL].gd; + + x = (int) &IDTVEC(syscall); + gdp->gd_looffset = x++; + gdp->gd_selector = GSEL(GCODE_SEL,SEL_KPL); + gdp->gd_stkcpy = 1; + gdp->gd_type = SDT_SYS386CGT; + gdp->gd_dpl = SEL_UPL; + gdp->gd_p = 1; + gdp->gd_hioffset = ((int) &IDTVEC(syscall)) >>16; + + /* transfer to user mode */ + + _ucodesel = LSEL(LUCODE_SEL, SEL_UPL); + _udatasel = LSEL(LUDATA_SEL, SEL_UPL); + + /* setup proc 0's pcb */ + proc0.p_addr->u_pcb.pcb_flags = 0; + proc0.p_addr->u_pcb.pcb_cr3 = IdlePTD; +} + +/* + * The registers are in the frame; the frame is in the user area of + * the process in question; when the process is active, the registers + * are in "the kernel stack"; when it's not, they're still there, but + * things get flipped around. So, since p->p_md.md_regs is the whole address + * of the register set, take its offset from the kernel stack, and + * index into the user block. Don't you just *love* virtual memory? + * (I'm starting to think seymour is right...) + */ +#define TF_REGP(p) ((struct trapframe *) \ + ((char *)(p)->p_addr \ + + ((char *)(p)->p_md.md_regs - kstack))) + +int +ptrace_set_pc(p, addr) + struct proc *p; + unsigned int addr; +{ + TF_REGP(p)->tf_eip = addr; + return (0); +} + +int +ptrace_single_step(p) + struct proc *p; +{ + TF_REGP(p)->tf_eflags |= PSL_T; + return (0); +} + +int ptrace_write_u(p, off, data) + struct proc *p; + vm_offset_t off; + int data; +{ + struct trapframe frame_copy; + vm_offset_t min; + struct trapframe *tp; + + /* + * Privileged kernel state is scattered all over the user area. + * Only allow write access to parts of regs and to fpregs. + */ + min = (char *)p->p_md.md_regs - kstack; + if (off >= min && off <= min + sizeof(struct trapframe) - sizeof(int)) { + tp = TF_REGP(p); + frame_copy = *tp; + *(int *)((char *)&frame_copy + (off - min)) = data; + if (!EFLAGS_SECURE(frame_copy.tf_eflags, tp->tf_eflags) || + !CS_SECURE(frame_copy.tf_cs)) + return (EINVAL); + *(int*)((char *)p->p_addr + off) = data; + return (0); + } + min = offsetof(struct user, u_pcb) + offsetof(struct pcb, pcb_savefpu); + if (off >= min && off <= min + sizeof(struct save87) - sizeof(int)) { + *(int*)((char *)p->p_addr + off) = data; + return (0); + } + return (EFAULT); +} + +int +fill_regs(p, regs) + struct proc *p; + struct reg *regs; +{ + struct trapframe *tp; + + tp = TF_REGP(p); + regs->r_es = tp->tf_es; + regs->r_ds = tp->tf_ds; + regs->r_edi = tp->tf_edi; + regs->r_esi = tp->tf_esi; + regs->r_ebp = tp->tf_ebp; + regs->r_ebx = tp->tf_ebx; + regs->r_edx = tp->tf_edx; + regs->r_ecx = tp->tf_ecx; + regs->r_eax = tp->tf_eax; + regs->r_eip = tp->tf_eip; + regs->r_cs = tp->tf_cs; + regs->r_eflags = tp->tf_eflags; + regs->r_esp = tp->tf_esp; + regs->r_ss = tp->tf_ss; + return (0); +} + +int +set_regs(p, regs) + struct proc *p; + struct reg *regs; +{ + struct trapframe *tp; + + tp = TF_REGP(p); + if (!EFLAGS_SECURE(regs->r_eflags, tp->tf_eflags) || + !CS_SECURE(regs->r_cs)) + return (EINVAL); + tp->tf_es = regs->r_es; + tp->tf_ds = regs->r_ds; + tp->tf_edi = regs->r_edi; + tp->tf_esi = regs->r_esi; + tp->tf_ebp = regs->r_ebp; + tp->tf_ebx = regs->r_ebx; + tp->tf_edx = regs->r_edx; + tp->tf_ecx = regs->r_ecx; + tp->tf_eax = regs->r_eax; + tp->tf_eip = regs->r_eip; + tp->tf_cs = regs->r_cs; + tp->tf_eflags = regs->r_eflags; + tp->tf_esp = regs->r_esp; + tp->tf_ss = regs->r_ss; + return (0); +} + +#ifndef DDB +void +Debugger(const char *msg) +{ + printf("Debugger(\"%s\") called.\n", msg); +} +#endif /* no DDB */ + +#include <sys/disklabel.h> +#define b_cylin b_resid +/* + * Determine the size of the transfer, and make sure it is + * within the boundaries of the partition. Adjust transfer + * if needed, and signal errors or early completion. + */ +int +bounds_check_with_label(struct buf *bp, struct disklabel *lp, int wlabel) +{ + struct partition *p = lp->d_partitions + dkpart(bp->b_dev); + int labelsect = lp->d_partitions[0].p_offset; + int maxsz = p->p_size, + sz = (bp->b_bcount + DEV_BSIZE - 1) >> DEV_BSHIFT; + + /* overwriting disk label ? */ + /* XXX should also protect bootstrap in first 8K */ + if (bp->b_blkno + p->p_offset <= LABELSECTOR + labelsect && +#if LABELSECTOR != 0 + bp->b_blkno + p->p_offset + sz > LABELSECTOR + labelsect && +#endif + (bp->b_flags & B_READ) == 0 && wlabel == 0) { + bp->b_error = EROFS; + goto bad; + } + +#if defined(DOSBBSECTOR) && defined(notyet) + /* overwriting master boot record? */ + if (bp->b_blkno + p->p_offset <= DOSBBSECTOR && + (bp->b_flags & B_READ) == 0 && wlabel == 0) { + bp->b_error = EROFS; + goto bad; + } +#endif + + /* beyond partition? */ + if (bp->b_blkno < 0 || bp->b_blkno + sz > maxsz) { + /* if exactly at end of disk, return an EOF */ + if (bp->b_blkno == maxsz) { + bp->b_resid = bp->b_bcount; + return(0); + } + /* or truncate if part of it fits */ + sz = maxsz - bp->b_blkno; + if (sz <= 0) { + bp->b_error = EINVAL; + goto bad; + } + bp->b_bcount = sz << DEV_BSHIFT; + } + + /* calculate cylinder for disksort to order transfers with */ + bp->b_pblkno = bp->b_blkno + p->p_offset; + bp->b_cylin = bp->b_pblkno / lp->d_secpercyl; + return(1); + +bad: + bp->b_flags |= B_ERROR; + return(-1); +} + +int +disk_externalize(int drive, struct sysctl_req *req) +{ + return SYSCTL_OUT(req, &drive, sizeof drive); +} |