diff --git a/sys/pc98/cbus/sio.c b/sys/pc98/cbus/sio.c index 1a57098644b0..1a034b230c60 100644 --- a/sys/pc98/cbus/sio.c +++ b/sys/pc98/cbus/sio.c @@ -1,4228 +1,4228 @@ /*- * 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.41 1997/10/27 11:00:31 kato Exp $ + * $Id: sio.c,v 1.42 1997/11/03 02:30:45 kato Exp $ */ #include "opt_comconsole.h" #include "opt_ddb.h" #include "opt_sio.h" #include "sio.h" #include "pnp.h" #ifndef EXTRA_SIO #if NPNP > 0 #define EXTRA_SIO 2 #else #define EXTRA_SIO 0 #endif #endif #define NSIOTOT (NSIO + EXTRA_SIO) /* * 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 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef DEVFS #include #endif #include #ifdef PC98 #include #include #include #include #include #include #else #include #include #include #endif #include #ifdef COM_ESP #include #endif #include #include "card.h" #if NCARD > 0 -#include +#include #include #include #endif #if NPNP > 0 #include #endif #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ #ifdef APIC_IO /* * INTs are masked in the (global) IO APIC, * but the IRR register is in each LOCAL APIC, * so we would have to unmask the INT to be able to "see INT pending". * So instead we just look in the 8259 ICU. */ #define isa_irq_pending icu_irq_pending #endif /* APIC_IO */ #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_CONSOLE(dev) ((dev)->id_flags & 0x10) #define COM_FORCECONSOLE(dev) ((dev)->id_flags & 0x20) #define COM_LLCONSOLE(dev) ((dev)->id_flags & 0x40) #define COM_LOSESOUTINTS(dev) ((dev)->id_flags & 0x08) #define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) #define COM_VERBOSE(dev) ((dev)->id_flags & 0x80) #define COM_NOTST3(dev) ((dev)->id_flags & 0x10000) #define COM_ST16650A(dev) ((dev)->id_flags & 0x20000) #define COM_FIFOSIZE(dev) (((dev)->id_flags & 0xff000000) >> 24) #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 */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ 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 extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t st16650a; /* Is a Startech 16650A or RTS/CTS compat */ 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; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_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 point. */ void siopoll __P((void)); /* Device switch entry points. */ #define sioreset noreset #define siommap nommap #define siostrategy nostrategy #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)); static timeout_t siobusycheck; 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)); static int sioprobe __P((struct isa_device *dev)); 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[NSIOTOT]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { 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, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIOTOT]; #else static struct tty sio_tty[NSIOTOT]; #endif static const int nsio_tty = NSIOTOT; #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 } }; #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 /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate SYSCTL_HANDLER_ARGS { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (!com) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); #if NCARD > 0 /* * PC-Card (PCMCIA) specific code. */ static int sioinit(struct pccard_devinfo *); /* init device */ static void siounload(struct pccard_devinfo *); /* Disable driver */ static int card_intr(struct pccard_devinfo *); /* Interrupt handler */ static struct pccard_device sio_info = { driver_name, sioinit, siounload, card_intr, 0, /* Attributes - presently unused */ &tty_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; DATA_SET(pccarddrv_set, sio_info); /* * Initialize the device - called from Slot manager. */ int sioinit(struct pccard_devinfo *devi) { /* validate unit number. */ if (devi->isahd.id_unit >= (NSIOTOT)) return(ENODEV); /* Make sure it isn't already probed. */ if (com_addr(devi->isahd.id_unit)) return(EBUSY); /* * Probe the device. If a value is returned, the * device was found at the location. */ if (sioprobe(&devi->isahd) == 0) return(ENXIO); if (sioattach(&devi->isahd) == 0) return(ENXIO); 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_devinfo *devi) { struct com_s *com; com = com_addr(devi->isahd.id_unit); if (!com->iobase) { printf("sio%d already unloaded!\n",devi->isahd.id_unit); return; } if (com->tp && (com->tp->t_state & TS_ISOPEN)) { com->gone = 1; printf("sio%d: unload\n", devi->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", devi->isahd.id_unit); } } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_devinfo *devi) { struct com_s *com; COM_LOCK(); com = com_addr(devi->isahd.id_unit); if (com && !com->gone) siointr1(com_addr(devi->isahd.id_unit)); COM_UNLOCK(); return(1); } #endif /* NCARD > 0 */ static int sioprobe(dev) struct isa_device *dev; { static bool_t already_init; bool_t failures[10]; int fn; struct isa_device *idev; Port_t iobase; u_char mcr_image; int result; #ifdef PC98 struct isa_device *xdev; int irqout=0; int ret = 0; int tmp; struct siodev iod; #else struct isa_device *xdev; #endif 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. */ for (xdev = isa_devtab_tty; xdev->id_driver != NULL; xdev++) if (xdev->id_driver == &siodriver && xdev->id_enabled) #ifdef PC98 if (IS_PC98IN(xdev->id_iobase)) outb(xdev->id_iobase + 2, 0xf2); else #endif outb(xdev->id_iobase + com_mcr, 0); 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 = isa_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)) { idev = find_isadev(isa_devtab_tty, &siodriver, 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; if (COM_LLCONSOLE(dev)) { printf("sio%d: reserved for low-level i/o\n", dev->id_unit); return (0); } /* * 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? */ /* * 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. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); outb(iobase + com_dlbl, COMBRD(SIO_TEST_SPEED) & 0xff); outb(iobase + com_dlbh, (u_int) COMBRD(SIO_TEST_SPEED) >> 8); outb(iobase + com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 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 / (SIO_TEST_SPEED / 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(10000); /* Some internal modems need this time */ if (idev->id_irq != 0 && !COM_NOTST3(idev)) failures[3] = isa_irq_pending(idev) ? 0 : 1; failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; DELAY(1000); /* XXX */ if (idev->id_irq != 0) failures[5] = isa_irq_pending(idev) ? 1 : 0; 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 */ if (idev->id_irq != 0) failures[8] = isa_irq_pending(idev) ? 1 : 0; failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; 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) struct isa_device *isdp; { 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 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; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; termioschars(&com->it_in); 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"); 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"); 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)"); break; #ifdef COM_IF_PC9861K case COM_IF_PC9861K: printf(" 8251 (PC9861K)"); break; #endif #ifdef COM_IF_PIO9032B case COM_IF_PIO9032B: printf(" 8251 (PIO9032B)"); break; #endif #ifdef COM_IF_B98_01 case COM_IF_B98_01: printf(" 8251 (B98_01)"); break; #endif } } else { #endif /* PC98 */ outb(iobase + com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); com->st16650a = 0; switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(isdp)) { printf(" 16550A fifo disabled"); } else { com->hasfifo = TRUE; if (COM_ST16650A(isdp)) { com->st16650a = 1; com->tx_fifo_size = 32; printf(" ST16650A"); } else { com->tx_fifo_size = COM_FIFOSIZE(isdp); printf(" 16550A"); } } #ifdef COM_ESP for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(isdp, com, *espp)) { com->tx_fifo_size = 1024; break; } #endif if (!com->st16650a) { if (!com->tx_fifo_size) com->tx_fifo_size = 16; else printf(" lookalike with %d bytes FIFO", com->tx_fifo_size); } break; } #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, 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(")"); com->no_irq = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(isdp))->id_irq == 0; } #endif /* COM_MULTIPORT */ #ifdef PC98 } #endif if (unit == comconsole) printf(", console"); printf("\n"); s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS 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 >= NSIOTOT || (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; } 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); /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(50); (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 = FALSE; com->do_dcd_timestamp = FALSE; #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 && !(com->state & CS_DTR_OFF)) { 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 */ 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 siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ #ifdef PC98 else if (IS_8251(com->pc98_if_type) && (inb(com->sts_port) & (STS8251_TxRDY | STS8251_TxEMP)) == (STS8251_TxRDY | STS8251_TxEMP) || (inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { #else else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { #endif com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { #ifndef COM_MULTIPORT COM_LOCK(); siointr1(com_addr(unit)); COM_UNLOCK(); #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. */ COM_LOCK(); do { possibly_more_intrs = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ #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; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); COM_UNLOCK(); #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 */ 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_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || 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 { if (com->do_timestamp) microtime(&com->timestamp); ++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) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * 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; 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 defined(PC98) if(IS_8251(com->pc98_if_type)) if ( pc98_check_i8251_interrupt(com) & IEN_TxFLAG ) com_int_Tx_disable(com); #endif } 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: com_tiocm_bis(com, TIOCM_DTR | TIOCM_RTS ); break; case TIOCCDTR: com_tiocm_bic(com, TIOCM_DTR); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: com_tiocm_set( com, *(int *)data ); break; case TIOCMBIS: com_tiocm_bis( com, *(int *)data ); break; case TIOCMBIC: com_tiocm_bic( com, *(int *)data ); break; case TIOCMGET: *(int *)data = com_tiocm_get(com); 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; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_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 < NSIOTOT; ++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. */ #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; enable_intr(); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) 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; u_char dlbh; u_char dlbl; 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; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif 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); /* * Only set the divisor registers if they would change, * since on some 16550 incompatibles (UMC8669F), setting * them while input is arriving them loses sync until * data stops arriving. */ dlbl = divisor & 0xFF; if (inb(iobase + com_dlbl) != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (inb(iobase + com_dlbh) != dlbh) outb(iobase + com_dlbh, dlbh); } 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)) if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x40); } com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #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); if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x40); } } /* * 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)) if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x80); } #ifdef PC98 } #endif } else { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x80); } com->state &= ~CS_ODEVREADY; } outb(iobase + com_cfcr, com->cfcr_image); /* 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); comstart(tp); 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 { #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 && com->state & CS_RTS_IFLOW) #else if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) #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) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_XMT_RST | com->fifo_image); 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) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_RCV_RST | com->fifo_image); com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); } 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 >= NSIOTOT) 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_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIOTOT; ++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; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIOTOT; ++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 < NSIOTOT; ++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 struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; static speed_t siocngetspeed __P((Port_t, struct speedtab *)); static void siocnclose __P((struct siocnstate *sp)); static void siocnopen __P((struct siocnstate *sp)); static void siocntxwait __P((void)); static void siocntxwait() { int timo; /* * 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) ; } /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, table) Port_t iobase; struct speedtab *table; { int code; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); code = dlbh << 8 | dlbl; for ( ; table->sp_speed != -1; table++) if (table->sp_code == code) return (table->sp_speed); return 0; /* didn't match anything sane */ } static void siocnopen(sp) struct siocnstate *sp; { int divisor; u_char dlbh; u_char dlbl; 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 | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = ttspeedtab(comdefaultrate, comspeedtab); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); 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 | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) 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; { struct isa_device *dvp; int s; struct siocnstate sp; speed_t boot_speed; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (dvp = isa_devtab_tty; dvp->id_driver != NULL; dvp++) if (dvp->id_driver == &siodriver && dvp->id_enabled && COM_CONSOLE(dvp)) { siocniobase = dvp->id_iobase; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(siocniobase, comspeedtab); if (boot_speed) comdefaultrate = boot_speed; } siocnopen(&sp); splx(s); if (!COM_LLCONSOLE(dvp)) { cp->cn_dev = makedev(CDEV_MAJOR, dvp->id_unit); cp->cn_pri = COM_FORCECONSOLE(dvp) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; } break; } } 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 = -1; 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 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 */ /* * support PnP cards if we are using 'em */ #if NPNP > 0 static struct siopnp_ids { u_long vend_id; char *id_str; } siopnp_ids[] = { { 0x8113b04e, "Supra1381"}, { 0x9012b04e, "Supra1290"}, { 0x11007256, "USR0011"}, { 0 } }; static char *siopnp_probe(u_long csn, u_long vend_id); static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); static u_long nsiopnp = NSIO; static struct pnp_device siopnp = { "siopnp", siopnp_probe, siopnp_attach, &nsiopnp, &tty_imask }; DATA_SET (pnpdevice_set, siopnp); static char * siopnp_probe(u_long csn, u_long vend_id) { struct siopnp_ids *ids; char *s = NULL; for(ids = siopnp_ids; ids->vend_id != 0; ids++) { if (vend_id == ids->vend_id) { s = ids->id_str; break; } } if (s) { struct pnp_cinfo d; read_pnp_parms(&d, 0); if (d.enable == 0 || d.flags & 1) { printf("CSN %d is disabled.\n", csn); return (NULL); } } return (s); } static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev) { struct pnp_cinfo d; struct isa_device *dvp; if (dev->id_unit >= NSIOTOT) return; if (read_pnp_parms(&d, 0) == 0) { printf("failed to read pnp parms\n"); return; } write_pnp_parms(&d, 0); enable_pnp_card(); dev->id_iobase = d.port[0]; dev->id_irq = (1 << d.irq[0]); dev->id_intr = siointr; dev->id_ri_flags = RI_FAST; dev->id_drq = -1; if (dev->id_driver == NULL) { dev->id_driver = &siodriver; dvp = find_isadev(isa_devtab_tty, &siodriver, 0); if (dvp != NULL) dev->id_id = dvp->id_id; } if ((dev->id_alive = sioprobe(dev)) != 0) sioattach(dev); else printf("sio%d: probe failed\n", dev->id_unit); } #endif #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; 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 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/pc98/if_ed.c b/sys/pc98/pc98/if_ed.c index 4e3836ec880f..5264bdb2ce1d 100644 --- a/sys/pc98/pc98/if_ed.c +++ b/sys/pc98/pc98/if_ed.c @@ -1,4198 +1,4198 @@ /* * Copyright (c) 1995, David Greenman * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * - * $Id: if_ed.c,v 1.34 1997/11/03 02:27:36 kato Exp $ + * $Id: if_ed.c,v 1.35 1997/11/07 12:53:55 kato Exp $ */ /* * Device driver for National Semiconductor DS8390/WD83C690 based ethernet * adapters. By David Greenman, 29-April-1993 * * Currently supports the Western Digital/SMC 8003 and 8013 series, * the SMC Elite Ultra (8216), the 3Com 3c503, the NE1000 and NE2000, * and a variety of similar clones. * */ /* * FreeBSD(98) supports: * Allied Telesis CenterCom LA-98-T, SIC-98 * D-Link DE-298P, DE-298 * ELECOM LANEED LD-BDN * ICM DT-ET-25, DT-ET-T5, IF-2766ET, IF_2711ET * IO-DATA PCLA/T, LA/T-98 * MACNICA NE2098 * NEC PC-9801-108 * MELCO LPC-TJ, LPC-TS, LGY-98, LGH-98, IND-SP, IND-SS, EGY-98 * PLANET SMART COM CREDITCARD/2000 PCMCIA, EN-2298 * Contec C-NET(98), C-NET(98)E, C-NET(98)L, C-NET(98)E-A, C-NET(98)L-A * * Modified for FreeBSD(98) 2.2 by KATO T. of Nagoya University. * * LPC-T support routine was contributed by Chikun. * * SIC-98 spport routine was derived from the code by A. Kojima of * Kyoto University Microcomputer Club (KMC). */ #include "ed.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #endif #include #include #include #include #include #ifdef PC98 /* register offsets */ struct pc98_edregister { u_int *port; u_int nic_offset; u_int asic_offset; u_int data; u_int reset; u_int pc_misc; u_int pc_reset; }; #endif /* * ed_softc: per line info and status */ struct ed_softc { struct arpcom arpcom; /* ethernet common */ char *type_str; /* pointer to type string */ u_char vendor; /* interface vendor */ u_char type; /* interface type code */ u_char gone; /* HW missing, presumed having a good time */ u_short asic_addr; /* ASIC I/O bus address */ u_short nic_addr; /* NIC (DS8390) I/O bus address */ /* * The following 'proto' variable is part of a work-around for 8013EBT asics * being write-only. It's sort of a prototype/shadow of the real thing. */ u_char wd_laar_proto; u_char cr_proto; u_char isa16bit; /* width of access to card 0=8 or 1=16 */ int is790; /* set by the probe code if the card is 790 * based */ /* * HP PC LAN PLUS card support. */ u_short hpp_options; /* flags controlling behaviour of the HP card */ u_short hpp_id; /* software revision and other fields */ caddr_t hpp_mem_start; /* Memory-mapped IO register address */ caddr_t mem_start; /* NIC memory start address */ caddr_t mem_end; /* NIC memory end address */ u_long mem_size; /* total NIC memory size */ caddr_t mem_ring; /* start of RX ring-buffer (in NIC mem) */ u_char mem_shared; /* NIC memory is shared with host */ u_char xmit_busy; /* transmitter is busy */ u_char txb_cnt; /* number of transmit buffers */ u_char txb_inuse; /* number of TX buffers currently in-use */ u_char txb_new; /* pointer to where new buffer will be added */ u_char txb_next_tx; /* pointer to next buffer ready to xmit */ u_short txb_len[8]; /* buffered xmit buffer lengths */ u_char tx_page_start; /* first page of TX buffer area */ u_char rec_page_start; /* first page of RX ring-buffer */ u_char rec_page_stop; /* last page of RX ring-buffer */ u_char next_packet; /* pointer to next unread RX packet */ struct ifmib_iso_8802_3 mibdata; /* stuff for network mgmt */ #ifdef PC98 struct pc98_edregister edreg; /* I/O port register offset info */ #endif }; static struct ed_softc ed_softc[NED]; #ifdef PC98 #include #endif static int ed_attach __P((struct ed_softc *, int, int)); static int ed_attach_isa __P((struct isa_device *)); static void ed_init __P((void *)); static int ed_ioctl __P((struct ifnet *, int, caddr_t)); static int ed_probe __P((struct isa_device *)); static void ed_start __P((struct ifnet *)); static void ed_reset __P((struct ifnet *)); static void ed_watchdog __P((struct ifnet *)); static void ed_stop __P((struct ed_softc *)); static int ed_probe_generic8390 __P((struct ed_softc *)); static int ed_probe_WD80x3 __P((struct isa_device *)); static int ed_probe_3Com __P((struct isa_device *)); static int ed_probe_Novell __P((struct isa_device *)); static int ed_probe_Novell_generic __P((struct ed_softc *, int, int, int)); #ifdef PC98 static int ed_probe_SIC98 __P((struct isa_device *)); static int ed_probe_CNET98 __P((struct isa_device *)); static int ed_probe_CNET98EL __P((struct isa_device *)); #endif static int ed_probe_HP_pclanp __P((struct isa_device *)); #include "pci.h" #if NPCI > 0 void *ed_attach_NE2000_pci __P((int, int)); #endif #include "card.h" #if NCARD > 0 static int ed_probe_pccard __P((struct isa_device *, u_char *)); #endif static void ds_getmcaf __P((struct ed_softc *, u_long *)); static void ed_get_packet(struct ed_softc *, char *, /* u_short */ int, int); static void ed_rint __P((struct ed_softc *)); static void ed_xmit __P((struct ed_softc *)); static char * ed_ring_copy __P((struct ed_softc *, char *, char *, /* u_short */ int)); static void ed_hpp_set_physical_link __P((struct ed_softc *)); static void ed_hpp_readmem __P((struct ed_softc *, int, unsigned char *, /* u_short */ int)); static u_short ed_hpp_write_mbufs __P((struct ed_softc *, struct mbuf *, int)); static void ed_pio_readmem __P((struct ed_softc *, int, unsigned char *, /* u_short */ int)); static void ed_pio_writemem __P((struct ed_softc *, char *, /* u_short */ int, /* u_short */ int)); static u_short ed_pio_write_mbufs __P((struct ed_softc *, struct mbuf *, int)); void edintr_sc __P((struct ed_softc *)); static void ed_setrcr(struct ed_softc *); static u_long ds_crc(u_char *ep); #if NCARD > 0 #include #include -#include +#include #include #include /* * PC-Card (PCMCIA) specific code. */ static int edinit(struct pccard_devinfo *); /* init device */ static void edunload(struct pccard_devinfo *); /* Disable driver */ static int card_intr(struct pccard_devinfo *); /* Interrupt handler */ static struct pccard_device ed_info = { "ed", edinit, edunload, card_intr, 0, /* Attributes - presently unused */ &net_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; DATA_SET(pccarddrv_set, ed_info); /* * Initialize the device - called from Slot manager. */ static int edinit(struct pccard_devinfo *devi) { struct ed_softc *sc = &ed_softc[devi->isahd.id_unit]; /* validate unit number. */ if (devi->isahd.id_unit >= NED) return(ENODEV); /* * Probe the device. If a value is returned, the * device was found at the location. */ sc->gone = 0; if (ed_probe_pccard(&devi->isahd, devi->misc) == 0) return(ENXIO); if (ed_attach_isa(&devi->isahd) == 0) return(ENXIO); return(0); } /* * edunload - 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 edunload(struct pccard_devinfo *devi) { struct ed_softc *sc = &ed_softc[devi->isahd.id_unit]; struct ifnet *ifp = &sc->arpcom.ac_if; if (sc->gone) { printf("ed%d: already unloaded\n", devi->isahd.id_unit); return; } ifp->if_flags &= ~IFF_RUNNING; if_down(ifp); sc->gone = 1; printf("ed%d: unload\n", devi->isahd.id_unit); } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_devinfo *devi) { edintr_sc(&ed_softc[devi->isahd.id_unit]); return(1); } #endif /* NCARD > 0 */ struct isa_driver eddriver = { ed_probe, ed_attach_isa, "ed", 1 /* We are ultra sensitive */ }; /* * Interrupt conversion table for WD/SMC ASIC/83C584 * (IRQ* are defined in icu.h) */ static unsigned short ed_intr_mask[] = { IRQ9, IRQ3, IRQ5, IRQ7, IRQ10, IRQ11, IRQ15, IRQ4 }; /* * Interrupt conversion table for 83C790 */ static unsigned short ed_790_intr_mask[] = { 0, IRQ9, IRQ3, IRQ5, IRQ7, IRQ10, IRQ11, IRQ15 }; /* * Interrupt conversion table for the HP PC LAN+ */ static unsigned short ed_hpp_intr_mask[] = { 0, /* 0 */ 0, /* 1 */ 0, /* 2 */ IRQ3, /* 3 */ IRQ4, /* 4 */ IRQ5, /* 5 */ IRQ6, /* 6 */ IRQ7, /* 7 */ 0, /* 8 */ IRQ9, /* 9 */ IRQ10, /* 10 */ IRQ11, /* 11 */ IRQ12, /* 12 */ 0, /* 13 */ 0, /* 14 */ IRQ15 /* 15 */ }; /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * NULL if device not found * or # of i/o addresses used (if found) */ static int ed_probe(isa_dev) struct isa_device *isa_dev; { int nports; #ifdef PC98 int nports98; #define EDNPORTS nports98 #else #define EDNPORTS nports #endif #ifdef PC98 /* * XXX * MELCO LPC-TJ, LPC-TS * PLANET SMART COM CREDITCARD/2000 PCMCIA * IO-DATA PCLA/T */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_LPC) { ed_softc[isa_dev->id_unit].type = ED_TYPE98_LPC; nports98 = pc98_set_register(isa_dev, ED_TYPE98_LPC); nports = ed_probe_Novell(isa_dev); if (nports) return (EDNPORTS); } /* * Generic probe routine * Allied Telesis CenterCom LA-98-T */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_GENERIC; nports98 = pc98_set_register(isa_dev, ED_TYPE98_GENERIC); if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_GENERIC) { #endif nports = ed_probe_WD80x3(isa_dev); if (nports) return (nports); nports = ed_probe_3Com(isa_dev); if (nports) return (nports); nports = ed_probe_Novell(isa_dev); if (nports) return (nports); #ifdef PC98 } /* * Allied Telesis SIC-98 */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_SIC) { ed_softc[isa_dev->id_unit].type = ED_TYPE98_SIC; nports98 = pc98_set_register(isa_dev, ED_TYPE98_SIC); nports = ed_probe_SIC98(isa_dev); if (nports) return (EDNPORTS); } /* * ELECOM LANEED LD-BDN * PLANET SMART COM 98 EN-2298 */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_BDN) { /* LD-BDN */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_BDN; nports98 = pc98_set_register(isa_dev, ED_TYPE98_BDN); nports = ed_probe_Novell(isa_dev); if (nports) return (EDNPORTS); } /* * MELCO LGY-98, IND-SP, IND-SS * MACNICA NE2098 */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_LGY) { /* LGY-98 */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_LGY; nports98 = pc98_set_register(isa_dev, ED_TYPE98_LGY); nports = ed_probe_Novell(isa_dev); if (nports) return (EDNPORTS); } /* * ICM DT-ET-25, DT-ET-T5, IF-2766ET, IF-2771ET * D-Link DE-298P, DE-298 */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_ICM) { /* ICM */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_ICM; nports98 = pc98_set_register(isa_dev, ED_TYPE98_ICM); nports = ed_probe_Novell(isa_dev); if (nports) return (EDNPORTS); } /* * MELCO EGY-98 * Contec C-NET(98)E-A, C-NET(98)L-A */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_EGY) { /* EGY-98 */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_EGY; nports98 = pc98_set_register(isa_dev, ED_TYPE98_EGY); nports = ed_probe_Novell(isa_dev); if (nports) return (EDNPORTS); } /* * IO-DATA LA/T-98 */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_LA98) { /* LA-98 */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_LA98; nports98 = pc98_set_register(isa_dev, ED_TYPE98_LA98); nports = ed_probe_Novell(isa_dev); if (nports) return (EDNPORTS); } /* * NEC PC-9801-108 */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_108) { /* PC-9801-108 */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_108; nports98 = pc98_set_register(isa_dev, ED_TYPE98_108); nports = ed_probe_Novell(isa_dev); if (nports) return (EDNPORTS); } /* * Contec C-NET(98)E/L */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_CNET98EL) { /* C-NET(98)E/L */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_CNET98EL; nports98 = pc98_set_register(isa_dev, ED_TYPE98_CNET98EL); nports = ed_probe_CNET98EL(isa_dev); if (nports) return (EDNPORTS); } /* * Contec C-NET(98) */ if (ED_TYPE98(isa_dev->id_flags) == ED_TYPE98_CNET98) { /* C-NET(98) */ ed_softc[isa_dev->id_unit].type = ED_TYPE98_CNET98; nports98 = pc98_set_register(isa_dev, ED_TYPE98_CNET98); nports = ed_probe_CNET98(isa_dev); if (nports) return (EDNPORTS); } #endif nports = ed_probe_HP_pclanp(isa_dev); if (nports) return (nports); return (0); } /* * Generic probe routine for testing for the existance of a DS8390. * Must be called after the NIC has just been reset. This routine * works by looking at certain register values that are guaranteed * to be initialized a certain way after power-up or reset. Seems * not to currently work on the 83C690. * * Specifically: * * Register reset bits set bits * Command Register (CR) TXP, STA RD2, STP * Interrupt Status (ISR) RST * Interrupt Mask (IMR) All bits * Data Control (DCR) LAS * Transmit Config. (TCR) LB1, LB0 * * We only look at the CR and ISR registers, however, because looking at * the others would require changing register pages (which would be * intrusive if this isn't an 8390). * * Return 1 if 8390 was found, 0 if not. */ static int ed_probe_generic8390(sc) struct ed_softc *sc; { #ifdef PC98 if (sc->type == ED_TYPE98_LPC) { if ((inb(sc->nic_addr + ED_P0_CR) & (ED_CR_RD2 | ED_CR_TXP | ED_CR_STA | ED_CR_STP)) != (ED_CR_RD2 | ED_CR_STP | ED_CR_STA)) return (0); } else { #endif if ((inb(sc->nic_addr + ED_P0_CR) & (ED_CR_RD2 | ED_CR_TXP | ED_CR_STA | ED_CR_STP)) != (ED_CR_RD2 | ED_CR_STP)) return (0); #ifdef PC98 } inb(sc->nic_addr + ED_P0_ISR); #else if ((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) != ED_ISR_RST) return (0); #endif return (1); } /* * Probe and vendor-specific initialization routine for SMC/WD80x3 boards */ static int ed_probe_WD80x3(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int i; u_int memsize, maddr; u_char iptr, isa16bit, sum; sc->asic_addr = isa_dev->id_iobase; sc->nic_addr = sc->asic_addr + ED_WD_NIC_OFFSET; sc->is790 = 0; #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_POW); DELAY(10000); #endif /* * Attempt to do a checksum over the station address PROM. If it * fails, it's probably not a SMC/WD board. There is a problem with * this, though: some clone WD boards don't pass the checksum test. * Danpex boards for one. */ for (sum = 0, i = 0; i < 8; ++i) sum += inb(sc->asic_addr + ED_WD_PROM + i); if (sum != ED_WD_ROM_CHECKSUM_TOTAL) { /* * Checksum is invalid. This often happens with cheap WD8003E * clones. In this case, the checksum byte (the eighth byte) * seems to always be zero. */ if (inb(sc->asic_addr + ED_WD_CARD_ID) != ED_TYPE_WD8003E || inb(sc->asic_addr + ED_WD_PROM + 7) != 0) return (0); } /* reset card to force it into a known state. */ #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST | ED_WD_MSR_POW); #else outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST); #endif DELAY(100); outb(sc->asic_addr + ED_WD_MSR, inb(sc->asic_addr + ED_WD_MSR) & ~ED_WD_MSR_RST); /* wait in the case this card is reading it's EEROM */ DELAY(5000); sc->vendor = ED_VENDOR_WD_SMC; sc->type = inb(sc->asic_addr + ED_WD_CARD_ID); /* * Set initial values for width/size. */ memsize = 8192; isa16bit = 0; switch (sc->type) { case ED_TYPE_WD8003S: sc->type_str = "WD8003S"; break; case ED_TYPE_WD8003E: sc->type_str = "WD8003E"; break; case ED_TYPE_WD8003EB: sc->type_str = "WD8003EB"; break; case ED_TYPE_WD8003W: sc->type_str = "WD8003W"; break; case ED_TYPE_WD8013EBT: sc->type_str = "WD8013EBT"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013W: sc->type_str = "WD8013W"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EP: /* also WD8003EP */ if (inb(sc->asic_addr + ED_WD_ICR) & ED_WD_ICR_16BIT) { isa16bit = 1; memsize = 16384; sc->type_str = "WD8013EP"; } else { sc->type_str = "WD8003EP"; } break; case ED_TYPE_WD8013WC: sc->type_str = "WD8013WC"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EBP: sc->type_str = "WD8013EBP"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_WD8013EPC: sc->type_str = "WD8013EPC"; memsize = 16384; isa16bit = 1; break; case ED_TYPE_SMC8216C: /* 8216 has 16K shared mem -- 8416 has 8K */ case ED_TYPE_SMC8216T: if (sc->type == ED_TYPE_SMC8216C) { sc->type_str = "SMC8216/SMC8216C"; } else { sc->type_str = "SMC8216T"; } outb(sc->asic_addr + ED_WD790_HWR, inb(sc->asic_addr + ED_WD790_HWR) | ED_WD790_HWR_SWH); switch (inb(sc->asic_addr + ED_WD790_RAR) & ED_WD790_RAR_SZ64) { case ED_WD790_RAR_SZ64: memsize = 65536; break; case ED_WD790_RAR_SZ32: memsize = 32768; break; case ED_WD790_RAR_SZ16: memsize = 16384; break; case ED_WD790_RAR_SZ8: /* 8216 has 16K shared mem -- 8416 has 8K */ if (sc->type == ED_TYPE_SMC8216C) { sc->type_str = "SMC8416C/SMC8416BT"; } else { sc->type_str = "SMC8416T"; } memsize = 8192; break; } outb(sc->asic_addr + ED_WD790_HWR, inb(sc->asic_addr + ED_WD790_HWR) & ~ED_WD790_HWR_SWH); isa16bit = 1; sc->is790 = 1; break; #ifdef TOSH_ETHER case ED_TYPE_TOSHIBA1: sc->type_str = "Toshiba1"; memsize = 32768; isa16bit = 1; break; case ED_TYPE_TOSHIBA4: sc->type_str = "Toshiba4"; memsize = 32768; isa16bit = 1; break; #endif default: sc->type_str = ""; break; } /* * Make some adjustments to initial values depending on what is found * in the ICR. */ if (isa16bit && (sc->type != ED_TYPE_WD8013EBT) #ifdef TOSH_ETHER && (sc->type != ED_TYPE_TOSHIBA1) && (sc->type != ED_TYPE_TOSHIBA4) #endif && ((inb(sc->asic_addr + ED_WD_ICR) & ED_WD_ICR_16BIT) == 0)) { isa16bit = 0; memsize = 8192; } #if ED_DEBUG printf("type = %x type_str=%s isa16bit=%d memsize=%d id_msize=%d\n", sc->type, sc->type_str, isa16bit, memsize, isa_dev->id_msize); for (i = 0; i < 8; i++) printf("%x -> %x\n", i, inb(sc->asic_addr + i)); #endif /* * Allow the user to override the autoconfiguration */ if (isa_dev->id_msize) memsize = isa_dev->id_msize; maddr = (u_int) isa_dev->id_maddr & 0xffffff; if (maddr < 0xa0000 || maddr + memsize > 0x1000000) { printf("ed%d: Invalid ISA memory address range configured: 0x%x - 0x%x\n", isa_dev->id_unit, maddr, maddr + memsize); return 0; } /* * (note that if the user specifies both of the following flags that * '8bit' mode intentionally has precedence) */ if (isa_dev->id_flags & ED_FLAGS_FORCE_16BIT_MODE) isa16bit = 1; if (isa_dev->id_flags & ED_FLAGS_FORCE_8BIT_MODE) isa16bit = 0; /* * If possible, get the assigned interrupt number from the card and * use it. */ if ((sc->type & ED_WD_SOFTCONFIG) && (!sc->is790)) { /* * Assemble together the encoded interrupt number. */ iptr = (inb(isa_dev->id_iobase + ED_WD_ICR) & ED_WD_ICR_IR2) | ((inb(isa_dev->id_iobase + ED_WD_IRR) & (ED_WD_IRR_IR0 | ED_WD_IRR_IR1)) >> 5); /* * If no interrupt specified (or "?"), use what the board tells us. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_intr_mask[iptr]; /* * Enable the interrupt. */ outb(isa_dev->id_iobase + ED_WD_IRR, inb(isa_dev->id_iobase + ED_WD_IRR) | ED_WD_IRR_IEN); } if (sc->is790) { outb(isa_dev->id_iobase + ED_WD790_HWR, inb(isa_dev->id_iobase + ED_WD790_HWR) | ED_WD790_HWR_SWH); iptr = (((inb(isa_dev->id_iobase + ED_WD790_GCR) & ED_WD790_GCR_IR2) >> 4) | (inb(isa_dev->id_iobase + ED_WD790_GCR) & (ED_WD790_GCR_IR1 | ED_WD790_GCR_IR0)) >> 2); outb(isa_dev->id_iobase + ED_WD790_HWR, inb(isa_dev->id_iobase + ED_WD790_HWR) & ~ED_WD790_HWR_SWH); /* * If no interrupt specified (or "?"), use what the board tells us. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_790_intr_mask[iptr]; /* * Enable interrupts. */ outb(isa_dev->id_iobase + ED_WD790_ICR, inb(isa_dev->id_iobase + ED_WD790_ICR) | ED_WD790_ICR_EIL); } if (isa_dev->id_irq <= 0) { printf("ed%d: %s cards don't support auto-detected/assigned interrupts.\n", isa_dev->id_unit, sc->type_str); return (0); } sc->isa16bit = isa16bit; sc->mem_shared = 1; isa_dev->id_msize = memsize; sc->mem_start = (caddr_t) isa_dev->id_maddr; /* * allocate one xmit buffer if < 16k, two buffers otherwise */ if ((memsize < 16384) || (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING)) { sc->txb_cnt = 1; } else { sc->txb_cnt = 2; } sc->tx_page_start = ED_WD_PAGE_OFFSET; sc->rec_page_start = ED_WD_PAGE_OFFSET + ED_TXBUF_SIZE * sc->txb_cnt; sc->rec_page_stop = ED_WD_PAGE_OFFSET + memsize / ED_PAGE_SIZE; sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * sc->rec_page_start); sc->mem_size = memsize; sc->mem_end = sc->mem_start + memsize; /* * Get station address from on-board ROM */ for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = inb(sc->asic_addr + ED_WD_PROM + i); /* * Set upper address bits and 8/16 bit access to shared memory. */ if (isa16bit) { if (sc->is790) { sc->wd_laar_proto = inb(sc->asic_addr + ED_WD_LAAR); } else { sc->wd_laar_proto = ED_WD_LAAR_L16EN | ((kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI); } /* * Enable 16bit access */ outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto | ED_WD_LAAR_M16EN); } else { if (((sc->type & ED_WD_SOFTCONFIG) || #ifdef TOSH_ETHER (sc->type == ED_TYPE_TOSHIBA1) || (sc->type == ED_TYPE_TOSHIBA4) || #endif (sc->type == ED_TYPE_WD8013EBT)) && (!sc->is790)) { sc->wd_laar_proto = (kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI; outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto); } } /* * Set address and enable interface shared memory. */ if (!sc->is790) { #ifdef TOSH_ETHER outb(sc->asic_addr + ED_WD_MSR + 1, ((kvtop(sc->mem_start) >> 8) & 0xe0) | 4); outb(sc->asic_addr + ED_WD_MSR + 2, ((kvtop(sc->mem_start) >> 16) & 0x0f)); outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB | ED_WD_MSR_POW); #else outb(sc->asic_addr + ED_WD_MSR, ((kvtop(sc->mem_start) >> 13) & ED_WD_MSR_ADDR) | ED_WD_MSR_MENB); #endif sc->cr_proto = ED_CR_RD2; } else { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); outb(sc->asic_addr + ED_WD790_HWR, (inb(sc->asic_addr + ED_WD790_HWR) | ED_WD790_HWR_SWH)); outb(sc->asic_addr + ED_WD790_RAR, ((kvtop(sc->mem_start) >> 13) & 0x0f) | ((kvtop(sc->mem_start) >> 11) & 0x40) | (inb(sc->asic_addr + ED_WD790_RAR) & 0xb0)); outb(sc->asic_addr + ED_WD790_HWR, (inb(sc->asic_addr + ED_WD790_HWR) & ~ED_WD790_HWR_SWH)); sc->cr_proto = 0; } #if 0 printf("starting memory performance test at 0x%x, size %d...\n", sc->mem_start, memsize*16384); for (i = 0; i < 16384; i++) bzero(sc->mem_start, memsize); printf("***DONE***\n"); #endif /* * Now zero memory and verify that it is clear */ bzero(sc->mem_start, memsize); for (i = 0; i < memsize; ++i) { if (sc->mem_start[i]) { printf("ed%d: failed to clear shared memory at %lx - check configuration\n", isa_dev->id_unit, kvtop(sc->mem_start + i)); /* * Disable 16 bit access to shared memory */ if (isa16bit) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto & ~ED_WD_LAAR_M16EN); } return (0); } } /* * Disable 16bit access to shared memory - we leave it * disabled so that 1) machines reboot properly when the board * is set 16 bit mode and there are conflicting 8bit * devices/ROMS in the same 128k address space as this boards * shared memory. and 2) so that other 8 bit devices with * shared memory can be used in this 128k region, too. */ if (isa16bit) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto & ~ED_WD_LAAR_M16EN); } return (ED_WD_IO_PORTS); } /* * Probe and vendor-specific initialization routine for 3Com 3c503 boards */ static int ed_probe_3Com(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int i; u_int memsize; u_char isa16bit; sc->asic_addr = isa_dev->id_iobase + ED_3COM_ASIC_OFFSET; sc->nic_addr = isa_dev->id_iobase + ED_3COM_NIC_OFFSET; /* * Verify that the kernel configured I/O address matches the board * configured address */ switch (inb(sc->asic_addr + ED_3COM_BCFR)) { case ED_3COM_BCFR_300: if (isa_dev->id_iobase != 0x300) return (0); break; case ED_3COM_BCFR_310: if (isa_dev->id_iobase != 0x310) return (0); break; case ED_3COM_BCFR_330: if (isa_dev->id_iobase != 0x330) return (0); break; case ED_3COM_BCFR_350: if (isa_dev->id_iobase != 0x350) return (0); break; case ED_3COM_BCFR_250: if (isa_dev->id_iobase != 0x250) return (0); break; case ED_3COM_BCFR_280: if (isa_dev->id_iobase != 0x280) return (0); break; case ED_3COM_BCFR_2A0: if (isa_dev->id_iobase != 0x2a0) return (0); break; case ED_3COM_BCFR_2E0: if (isa_dev->id_iobase != 0x2e0) return (0); break; default: return (0); } /* * Verify that the kernel shared memory address matches the board * configured address. */ switch (inb(sc->asic_addr + ED_3COM_PCFR)) { case ED_3COM_PCFR_DC000: if (kvtop(isa_dev->id_maddr) != 0xdc000) return (0); break; case ED_3COM_PCFR_D8000: if (kvtop(isa_dev->id_maddr) != 0xd8000) return (0); break; case ED_3COM_PCFR_CC000: if (kvtop(isa_dev->id_maddr) != 0xcc000) return (0); break; case ED_3COM_PCFR_C8000: if (kvtop(isa_dev->id_maddr) != 0xc8000) return (0); break; default: return (0); } /* * Reset NIC and ASIC. Enable on-board transceiver throughout reset * sequence because it'll lock up if the cable isn't connected if we * don't. */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_RST | ED_3COM_CR_XSEL); /* * Wait for a while, then un-reset it */ DELAY(50); /* * The 3Com ASIC defaults to rather strange settings for the CR after * a reset - it's important to set it again after the following outb * (this is done when we map the PROM below). */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); /* * Wait a bit for the NIC to recover from the reset */ DELAY(5000); sc->vendor = ED_VENDOR_3COM; sc->type_str = "3c503"; sc->mem_shared = 1; sc->cr_proto = ED_CR_RD2; /* * Hmmm...a 16bit 3Com board has 16k of memory, but only an 8k window * to it. */ memsize = 8192; /* * Get station address from on-board ROM */ /* * First, map ethernet address PROM over the top of where the NIC * registers normally appear. */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_EALO | ED_3COM_CR_XSEL); for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = inb(sc->nic_addr + i); /* * Unmap PROM - select NIC registers. The proper setting of the * tranceiver is set in ed_init so that the attach code is given a * chance to set the default based on a compile-time config option */ outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); /* * Determine if this is an 8bit or 16bit board */ /* * select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); /* * Attempt to clear WTS bit. If it doesn't clear, then this is a 16bit * board. */ outb(sc->nic_addr + ED_P0_DCR, 0); /* * select page 2 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_2 | ED_CR_RD2 | ED_CR_STP); /* * The 3c503 forces the WTS bit to a one if this is a 16bit board */ if (inb(sc->nic_addr + ED_P2_DCR) & ED_DCR_WTS) isa16bit = 1; else isa16bit = 0; /* * select page 0 registers */ outb(sc->nic_addr + ED_P2_CR, ED_CR_RD2 | ED_CR_STP); sc->mem_start = (caddr_t) isa_dev->id_maddr; sc->mem_size = memsize; sc->mem_end = sc->mem_start + memsize; /* * We have an entire 8k window to put the transmit buffers on the * 16bit boards. But since the 16bit 3c503's shared memory is only * fast enough to overlap the loading of one full-size packet, trying * to load more than 2 buffers can actually leave the transmitter idle * during the load. So 2 seems the best value. (Although a mix of * variable-sized packets might change this assumption. Nonetheless, * we optimize for linear transfers of same-size packets.) */ if (isa16bit) { if (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING) sc->txb_cnt = 1; else sc->txb_cnt = 2; sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_16BIT; sc->rec_page_start = ED_3COM_RX_PAGE_OFFSET_16BIT; sc->rec_page_stop = memsize / ED_PAGE_SIZE + ED_3COM_RX_PAGE_OFFSET_16BIT; sc->mem_ring = sc->mem_start; } else { sc->txb_cnt = 1; sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_8BIT; sc->rec_page_start = ED_TXBUF_SIZE + ED_3COM_TX_PAGE_OFFSET_8BIT; sc->rec_page_stop = memsize / ED_PAGE_SIZE + ED_3COM_TX_PAGE_OFFSET_8BIT; sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE); } sc->isa16bit = isa16bit; /* * Initialize GA page start/stop registers. Probably only needed if * doing DMA, but what the hell. */ outb(sc->asic_addr + ED_3COM_PSTR, sc->rec_page_start); outb(sc->asic_addr + ED_3COM_PSPR, sc->rec_page_stop); /* * Set IRQ. 3c503 only allows a choice of irq 2-5. */ switch (isa_dev->id_irq) { case IRQ2: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ2); break; case IRQ3: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ3); break; case IRQ4: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ4); break; case IRQ5: outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ5); break; default: printf("ed%d: Invalid irq configuration (%d) must be 3-5,9 for 3c503\n", isa_dev->id_unit, ffs(isa_dev->id_irq) - 1); return (0); } /* * Initialize GA configuration register. Set bank and enable shared * mem. */ outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL | ED_3COM_GACFR_MBS0); /* * Initialize "Vector Pointer" registers. These gawd-awful things are * compared to 20 bits of the address on ISA, and if they match, the * shared memory is disabled. We set them to 0xffff0...allegedly the * reset vector. */ outb(sc->asic_addr + ED_3COM_VPTR2, 0xff); outb(sc->asic_addr + ED_3COM_VPTR1, 0xff); outb(sc->asic_addr + ED_3COM_VPTR0, 0x00); /* * Zero memory and verify that it is clear */ bzero(sc->mem_start, memsize); for (i = 0; i < memsize; ++i) if (sc->mem_start[i]) { printf("ed%d: failed to clear shared memory at %lx - check configuration\n", isa_dev->id_unit, kvtop(sc->mem_start + i)); return (0); } isa_dev->id_msize = memsize; return (ED_3COM_IO_PORTS); } /* * Probe and vendor-specific initialization routine for NE1000/2000 boards */ static int ed_probe_Novell_generic(sc, port, unit, flags) struct ed_softc *sc; int port; int unit; int flags; { u_int memsize, n; #ifdef PC98 u_char romdata[16], tmp, st1d01; #else u_char romdata[16], tmp; #endif static char test_pattern[32] = "THIS is A memory TEST pattern"; char test_buffer[32]; sc->asic_addr = port + ED_NOVELL_ASIC_OFFSET; sc->nic_addr = port + ED_NOVELL_NIC_OFFSET; /* XXX - do Novell-specific probe here */ /* Reset the board */ #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_ON(); #endif #ifdef GWETHER outb(sc->asic_addr + ED_NOVELL_RESET, 0); DELAY(200); #endif /* GWETHER */ #ifdef PC98 switch (sc->type) { case ED_TYPE98_BDN: st1d01 = inb(sc->nic_addr + ED_NOVELL_RESET); outb(sc->asic_addr + 0xc000, st1d01 & 0xf0 | 0x08); outb(sc->nic_addr + 0x4000, st1d01); tmp = inb(sc->asic_addr + 0x8000); outb(sc->asic_addr + 0x8000, st1d01); outb(sc->asic_addr + 0x8000, st1d01 & 0x7f); break; default: tmp = inb(sc->asic_addr + ED_NOVELL_RESET); } #else tmp = inb(sc->asic_addr + ED_NOVELL_RESET); #endif /* * I don't know if this is necessary; probably cruft leftover from * Clarkson packet driver code. Doesn't do a thing on the boards I've * tested. -DG [note that a outb(0x84, 0) seems to work here, and is * non-invasive...but some boards don't seem to reset and I don't have * complete documentation on what the 'right' thing to do is...so we * do the invasive thing for now. Yuck.] */ #ifdef PC98 if (sc->type != ED_TYPE98_BDN) #endif outb(sc->asic_addr + ED_NOVELL_RESET, tmp); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_OFF(); #endif DELAY(5000); /* * This is needed because some NE clones apparently don't reset the * NIC properly (or the NIC chip doesn't reset fully on power-up) XXX * - this makes the probe invasive! ...Done against my better * judgement. -DLG */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); DELAY(5000); /* Make sure that we really have an 8390 based board */ if (!ed_probe_generic8390(sc)) return (0); sc->vendor = ED_VENDOR_NOVELL; sc->mem_shared = 0; sc->cr_proto = ED_CR_RD2; /* * Test the ability to read and write to the NIC memory. This has the * side affect of determining if this is an NE1000 or an NE2000. */ /* * This prevents packets from being stored in the NIC memory when the * readmem routine turns on the start bit in the CR. */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); /* Temporarily initialize DCR for byte operations */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); outb(sc->nic_addr + ED_P0_PSTART, 8192 / ED_PAGE_SIZE); outb(sc->nic_addr + ED_P0_PSTOP, 16384 / ED_PAGE_SIZE); sc->isa16bit = 0; /* * Write a test pattern in byte mode. If this fails, then there * probably isn't any memory at 8k - which likely means that the board * is an NE2000. */ ed_pio_writemem(sc, test_pattern, 8192, sizeof(test_pattern)); ed_pio_readmem(sc, 8192, test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) { /* not an NE1000 - try NE2000 */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_WTS | ED_DCR_FT1 | ED_DCR_LS); outb(sc->nic_addr + ED_P0_PSTART, 16384 / ED_PAGE_SIZE); outb(sc->nic_addr + ED_P0_PSTOP, 32768 / ED_PAGE_SIZE); sc->isa16bit = 1; /* * Write a test pattern in word mode. If this also fails, then * we don't know what this board is. */ ed_pio_writemem(sc, test_pattern, 16384, sizeof(test_pattern)); ed_pio_readmem(sc, 16384, test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) return (0); /* not an NE2000 either */ #ifndef PC98 sc->type = ED_TYPE_NE2000; sc->type_str = "NE2000"; } else { sc->type = ED_TYPE_NE1000; sc->type_str = "NE1000"; #else } switch (sc->type) { case ED_TYPE98_GENERIC: sc->type_str = "NE2000"; break; case ED_TYPE98_LPC: sc->type_str = "LPC-T"; break; case ED_TYPE98_BDN: sc->type_str = "LD-BDN"; break; case ED_TYPE98_EGY: sc->type_str = "EGY-98"; break; case ED_TYPE98_LGY: sc->type_str = "LGY-98"; break; case ED_TYPE98_ICM: sc->type_str = "ICM"; break; case ED_TYPE98_SIC: sc->type_str = "SIC-98"; break; case ED_TYPE98_108: sc->type_str = "PC-9801-108"; break; case ED_TYPE98_LA98: sc->type_str = "LA-98"; break; default: sc->type_str = "Unknown"; break; #endif } /* 8k of memory plus an additional 8k if 16bit */ memsize = 8192 + sc->isa16bit * 8192; #if 0 /* probably not useful - NE boards only come two ways */ /* allow kernel config file overrides */ if (isa_dev->id_msize) memsize = isa_dev->id_msize; #endif sc->mem_size = memsize; /* NIC memory doesn't start at zero on an NE board */ /* The start address is tied to the bus width */ sc->mem_start = (char *) 8192 + sc->isa16bit * 8192; sc->mem_end = sc->mem_start + memsize; sc->tx_page_start = memsize / ED_PAGE_SIZE; #ifdef GWETHER { int x, i, mstart = 0, msize = 0; char pbuf0[ED_PAGE_SIZE], pbuf[ED_PAGE_SIZE], tbuf[ED_PAGE_SIZE]; for (i = 0; i < ED_PAGE_SIZE; i++) pbuf0[i] = 0; /* Clear all the memory. */ for (x = 1; x < 256; x++) ed_pio_writemem(sc, pbuf0, x * 256, ED_PAGE_SIZE); /* Search for the start of RAM. */ for (x = 1; x < 256; x++) { ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { for (i = 0; i < ED_PAGE_SIZE; i++) pbuf[i] = 255 - x; ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) { mstart = x * ED_PAGE_SIZE; msize = ED_PAGE_SIZE; break; } } } if (mstart == 0) { printf("ed%d: Cannot find start of RAM.\n", unit); return 0; } /* Search for the start of RAM. */ for (x = (mstart / ED_PAGE_SIZE) + 1; x < 256; x++) { ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { for (i = 0; i < ED_PAGE_SIZE; i++) pbuf[i] = 255 - x; ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); if (bcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) msize += ED_PAGE_SIZE; else { break; } } else { break; } } if (msize == 0) { printf("ed%d: Cannot find any RAM, start : %d, x = %d.\n", unit, mstart, x); return 0; } printf("ed%d: RAM start at %d, size : %d.\n", unit, mstart, msize); sc->mem_size = msize; sc->mem_start = (char *) mstart; sc->mem_end = (char *) (msize + mstart); sc->tx_page_start = mstart / ED_PAGE_SIZE; } #endif /* GWETHER */ /* * Use one xmit buffer if < 16k, two buffers otherwise (if not told * otherwise). */ if ((memsize < 16384) || (flags & ED_FLAGS_NO_MULTI_BUFFERING)) sc->txb_cnt = 1; else sc->txb_cnt = 2; sc->rec_page_start = sc->tx_page_start + sc->txb_cnt * ED_TXBUF_SIZE; sc->rec_page_stop = sc->tx_page_start + memsize / ED_PAGE_SIZE; sc->mem_ring = sc->mem_start + sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE; ed_pio_readmem(sc, 0, romdata, 16); for (n = 0; n < ETHER_ADDR_LEN; n++) sc->arpcom.ac_enaddr[n] = romdata[n * (sc->isa16bit + 1)]; #ifdef GWETHER if (sc->arpcom.ac_enaddr[2] == 0x86) { sc->type_str = "Gateway AT"; } #endif /* GWETHER */ /* clear any pending interrupts that might have occurred above */ outb(sc->nic_addr + ED_P0_ISR, 0xff); return (ED_NOVELL_IO_PORTS); } static int ed_probe_Novell(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; #ifndef PC98 isa_dev->id_maddr = 0; #endif return ed_probe_Novell_generic(sc, isa_dev->id_iobase, isa_dev->id_unit, isa_dev->id_flags); } #if NCARD > 0 /* * Probe framework for pccards. Replicates the standard framework, * minus the pccard driver registration and ignores the ether address * supplied (from the CIS), relying on the probe to find it instead. */ static int ed_probe_pccard(isa_dev, ether) struct isa_device *isa_dev; u_char *ether; { int nports; nports = ed_probe_WD80x3(isa_dev); if (nports) return (nports); nports = ed_probe_Novell(isa_dev); if (nports) return (nports); return (0); } #endif /* NCARD > 0 */ #define ED_HPP_TEST_SIZE 16 /* * Probe and vendor specific initialization for the HP PC Lan+ Cards. * (HP Part nos: 27247B and 27252A). * * The card has an asic wrapper around a DS8390 core. The asic handles * host accesses and offers both standard register IO and memory mapped * IO. Memory mapped I/O allows better performance at the expense of greater * chance of an incompatibility with existing ISA cards. * * The card has a few caveats: it isn't tolerant of byte wide accesses, only * short (16 bit) or word (32 bit) accesses are allowed. Some card revisions * don't allow 32 bit accesses; these are indicated by a bit in the software * ID register (see if_edreg.h). * * Other caveats are: we should read the MAC address only when the card * is inactive. * * For more information; please consult the CRYNWR packet driver. * * The AUI port is turned on using the "link2" option on the ifconfig * command line. */ static int ed_probe_HP_pclanp(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int n; /* temp var */ int memsize; /* mem on board */ u_char checksum; /* checksum of board address */ u_char irq; /* board configured IRQ */ char test_pattern[ED_HPP_TEST_SIZE]; /* read/write areas for */ char test_buffer[ED_HPP_TEST_SIZE]; /* probing card */ /* Fill in basic information */ sc->asic_addr = isa_dev->id_iobase + ED_HPP_ASIC_OFFSET; sc->nic_addr = isa_dev->id_iobase + ED_HPP_NIC_OFFSET; sc->is790 = 0; sc->isa16bit = 0; /* the 8390 core needs to be in byte mode */ /* * Look for the HP PCLAN+ signature: "0x50,0x48,0x00,0x53" */ if ((inb(sc->asic_addr + ED_HPP_ID) != 0x50) || (inb(sc->asic_addr + ED_HPP_ID + 1) != 0x48) || ((inb(sc->asic_addr + ED_HPP_ID + 2) & 0xF0) != 0) || (inb(sc->asic_addr + ED_HPP_ID + 3) != 0x53)) return 0; /* * Read the MAC address and verify checksum on the address. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_MAC); for (n = 0, checksum = 0; n < ETHER_ADDR_LEN; n++) checksum += (sc->arpcom.ac_enaddr[n] = inb(sc->asic_addr + ED_HPP_MAC_ADDR + n)); checksum += inb(sc->asic_addr + ED_HPP_MAC_ADDR + ETHER_ADDR_LEN); if (checksum != 0xFF) return 0; /* * Verify that the software model number is 0. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_ID); if (((sc->hpp_id = inw(sc->asic_addr + ED_HPP_PAGE_4)) & ED_HPP_ID_SOFT_MODEL_MASK) != 0x0000) return 0; /* * Read in and save the current options configured on card. */ sc->hpp_options = inw(sc->asic_addr + ED_HPP_OPTION); sc->hpp_options |= (ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ); /* * Reset the chip. This requires writing to the option register * so take care to preserve the other bits. */ outw(sc->asic_addr + ED_HPP_OPTION, (sc->hpp_options & ~(ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET))); DELAY(5000); /* wait for chip reset to complete */ outw(sc->asic_addr + ED_HPP_OPTION, (sc->hpp_options | (ED_HPP_OPTION_NIC_RESET | ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ))); DELAY(5000); if (!(inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST)) return 0; /* reset did not complete */ /* * Read out configuration information. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); irq = inb(sc->asic_addr + ED_HPP_HW_IRQ); /* * Check for impossible IRQ. */ if (irq >= (sizeof(ed_hpp_intr_mask) / sizeof(ed_hpp_intr_mask[0]))) return 0; /* * If the kernel IRQ was specified with a '?' use the cards idea * of the IRQ. If the kernel IRQ was explicitly specified, it * should match that of the hardware. */ if (isa_dev->id_irq <= 0) isa_dev->id_irq = ed_hpp_intr_mask[irq]; else if (isa_dev->id_irq != ed_hpp_intr_mask[irq]) return 0; /* * Fill in softconfig info. */ sc->vendor = ED_VENDOR_HP; sc->type = ED_TYPE_HP_PCLANPLUS; sc->type_str = "HP-PCLAN+"; sc->mem_shared = 0; /* we DON'T have dual ported RAM */ sc->mem_start = 0; /* we use offsets inside the card RAM */ sc->hpp_mem_start = NULL;/* no memory mapped I/O by default */ /* * Check if memory mapping of the I/O registers possible. */ if (sc->hpp_options & ED_HPP_OPTION_MEM_ENABLE) { u_long mem_addr; /* * determine the memory address from the board. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); mem_addr = (inw(sc->asic_addr + ED_HPP_HW_MEM_MAP) << 8); /* * Check that the kernel specified start of memory and * hardware's idea of it match. */ if (mem_addr != kvtop(isa_dev->id_maddr)) return 0; sc->hpp_mem_start = isa_dev->id_maddr; } /* * The board has 32KB of memory. Is there a way to determine * this programmatically? */ memsize = 32768; /* * Fill in the rest of the soft config structure. */ /* * The transmit page index. */ sc->tx_page_start = ED_HPP_TX_PAGE_OFFSET; if (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING) sc->txb_cnt = 1; else sc->txb_cnt = 2; /* * Memory description */ sc->mem_size = memsize; sc->mem_ring = sc->mem_start + (sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE); sc->mem_end = sc->mem_start + sc->mem_size; /* * Receive area starts after the transmit area and * continues till the end of memory. */ sc->rec_page_start = sc->tx_page_start + (sc->txb_cnt * ED_TXBUF_SIZE); sc->rec_page_stop = (sc->mem_size / ED_PAGE_SIZE); sc->cr_proto = 0; /* value works */ /* * Set the wrap registers for string I/O reads. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_HW); outw(sc->asic_addr + ED_HPP_HW_WRAP, ((sc->rec_page_start / ED_PAGE_SIZE) | (((sc->rec_page_stop / ED_PAGE_SIZE) - 1) << 8))); /* * Reset the register page to normal operation. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_PERF); /* * Verify that we can read/write from adapter memory. * Create test pattern. */ for (n = 0; n < ED_HPP_TEST_SIZE; n++) { test_pattern[n] = (n*n) ^ ~n; } #undef ED_HPP_TEST_SIZE /* * Check that the memory is accessible thru the I/O ports. * Write out the contents of "test_pattern", read back * into "test_buffer" and compare the two for any * mismatch. */ for (n = 0; n < (32768 / ED_PAGE_SIZE); n ++) { ed_pio_writemem(sc, test_pattern, (n * ED_PAGE_SIZE), sizeof(test_pattern)); ed_pio_readmem(sc, (n * ED_PAGE_SIZE), test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) return 0; } return (ED_HPP_IO_PORTS); } /* * HP PC Lan+ : Set the physical link to use AUI or TP/TL. */ void ed_hpp_set_physical_link(struct ed_softc *sc) { struct ifnet *ifp = &sc->arpcom.ac_if; int lan_page; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); lan_page = inw(sc->asic_addr + ED_HPP_PAGE_0); if (ifp->if_flags & IFF_ALTPHYS) { /* * Use the AUI port. */ lan_page |= ED_HPP_LAN_AUI; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); outw(sc->asic_addr + ED_HPP_PAGE_0, lan_page); } else { /* * Use the ThinLan interface */ lan_page &= ~ED_HPP_LAN_AUI; outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_LAN); outw(sc->asic_addr + ED_HPP_PAGE_0, lan_page); } /* * Wait for the lan card to re-initialize itself */ DELAY(150000); /* wait 150 ms */ /* * Restore normal pages. */ outw(sc->asic_addr + ED_HPP_PAGING, ED_HPP_PAGE_PERF); } #ifdef PC98 static int ed_probe_SIC98(struct isa_device* pc98_dev) { u_int i; struct ed_softc *sc = &ed_softc[pc98_dev->id_unit]; u_char sum; u_int memsize; int unit = pc98_dev->id_unit; if ((pc98_dev->id_maddr == 0) || (pc98_dev->id_msize == 0)) return 0; /* Setup card RAM and I/O address * Kernel Veirtual to segment C0000-DFFFF???? */ sc->asic_addr = pc98_dev->id_iobase + ED_NOVELL_ASIC_OFFSET; sc->nic_addr = pc98_dev->id_iobase + ED_NOVELL_NIC_OFFSET; sc->mem_start = (caddr_t) pc98_dev->id_maddr; memsize = pc98_dev->id_msize; /* reset card to force it into a known state. */ outb(sc->asic_addr, 0x00); DELAY(100); outb(sc->asic_addr, 0x94); DELAY(100); outb(sc->asic_addr, 0x94); DELAY(100); /* Here we check the card ROM, if the checksum passes, and the * type code and ethernet address check out, then we know we have * a SIC card. */ for (sum = 0, i = 0; i < 7; ++i) sum ^= sc->mem_start[i*2]; if (sum != 0) return 0; sc->isa16bit = 1; sc->mem_shared = 1; sc->vendor = ED_VENDOR_MISC; sc->type_str = "SIC98"; sc->cr_proto = 0; sc->txb_cnt = 1; /* * Save board ROM station address */ for (i = 0; i < 6; ++i) sc->arpcom.ac_enaddr[i] = sc->mem_start[i*2]; /* * SIC ram page 0x0000-0x3fff (or 0x7fff) */ outb(sc->asic_addr, 0x90); DELAY(100); sc->mem_size = memsize; sc->mem_end = sc->mem_start + memsize; sc->tx_page_start = 0; sc->rec_page_start = ED_TXBUF_SIZE; sc->rec_page_stop = (memsize / ED_PAGE_SIZE); sc->mem_ring = sc->mem_start + (ED_TXBUF_SIZE * ED_PAGE_SIZE); /* * clear interface memory, then sum to make sure its valid */ bzero(sc->mem_start, memsize); for (i = 0; i < memsize; ++i) if (sc->mem_start[i]) { printf("ed%d: failed to clear shared memory at %lx - check configuration\n", pc98_dev->id_unit, kvtop(sc->mem_start + i)); return (0); } /* * select page 0 regsister */ outb(sc->nic_addr + ED_P2_CR, ED_CR_RD2 | ED_CR_PAGE_0 | ED_CR_STP); return (1); } /* * Probe and vendor-specific initialization routine for CNET98 boards */ static int ed_probe_CNET98(isa_dev) struct isa_device *isa_dev; { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; int i; u_long j; u_char cmd,sum; u_char tmp,tmp_s,tmp_e; if ((isa_dev->id_maddr == 0) || (isa_dev->id_msize == 0)) return 0; sc->vendor = ED_VENDOR_MISC; /* vendor name */ sc->type_str = "CNET98"; /* board name */ sc->isa16bit = 0; /* 16bit mode off = 0 */ sc->cr_proto = ED_CR_RD2; /* */ sc->asic_addr = isa_dev->id_iobase; /* 0xa3d0,0xb3d0,0xc3d0 */ sc->nic_addr = isa_dev->id_iobase; /* 0xd3d0,0xe3d0,0xf3d0 */ sc->is790 = 0; /* special chip */ sc->mem_start = (caddr_t)isa_dev->id_maddr; sc->mem_end = sc->mem_start + isa_dev->id_msize; sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE); sc->mem_size = isa_dev->id_msize; sc->mem_shared = 1; /* shared memory on */ sc->txb_cnt = 1; /* tx buffer counter 1 */ sc->tx_page_start = 0; /* page offset 0 */ sc->rec_page_start = ED_TXBUF_SIZE; /* page offset 6 */ sc->rec_page_stop = isa_dev->id_msize / ED_PAGE_SIZE; /* page offset 40 */ /* * Check i/o address. * 0xa3d0, 0xb3d0, 0xc3d0, 0xd3d0, 0xe3d0, 0xf3d0 */ if ( ((sc->asic_addr & (u_short) 0x0fff) != 0x03d0) && ((sc->asic_addr & (u_short) 0xf000) >= 0xa000) ){ printf("ed%d: Invalid i/o port configuration (0x%x) must be " "0x?3d0 for CNET98\n", isa_dev->id_unit, sc->asic_addr); return (0); } /* * Check window area address. */ tmp_s = kvtop(sc->mem_start) >> 12; if ( tmp_s < 0x80 ) { printf("ed%d: Please change window address(0x%x) \n", isa_dev->id_unit,sc->mem_start); return (0); } tmp = sc->asic_addr >> 12; tmp_s = (tmp_s & (u_char) 0x0f); tmp_e = tmp_s + 4; if ( (tmp_s <= tmp) && (tmp < tmp_e ) ){ printf("ed%d: Please change iobase address(0x%x) or window address(0x%x) \n", isa_dev->id_unit,isa_dev->id_iobase,kvtop(sc->mem_start)); return (0); } /* * Reset card to force it into a known state. */ outb(ED_CNET98_INIT_ADDR, 0x00); /* Request */ DELAY(5000); outb(ED_CNET98_INIT_ADDR, 0x01); /* Cancel */ DELAY(5000); /* * Set i/o address and cpu type * * AAAAIXXC(8bit) * AAAA: A15-A12, I: I/O enable, XX: reserved, C: CPU type */ tmp = (sc->asic_addr & (u_short) 0xf000) >> 8; tmp |= (0x08 | 0x01); #ifdef ED_DEBUG printf("ed%d: Board status %x \n",isa_dev->id_unit, tmp); #endif outb((ED_CNET98_INIT_ADDR + 2), tmp); DELAY(1000); /* * Set window ethernet address area * board memory base 0x480000 data 256byte * FreeBSD address 0xf00xxxxx */ outb((sc->asic_addr + ED_CNET98_MAP_REG0L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG0H),0x48); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG1L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG1H),0x41); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG2L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG2H),0x42); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG3L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG3H),0x43); DELAY(10); /* * Enable window memory(16Kbyte) * bit7:0 disable , 1 enable */ cmd = (kvtop(sc->mem_start) >> 12); #ifdef ED_DEBUG printf("ed%d: Set window start address %x \n",isa_dev->id_unit,cmd); #endif outb((sc->asic_addr + ED_CNET98_WIN_REG),cmd); DELAY(10); /* * CNET98 checksum code * * for (sum = 0, i = 0; i < ETHER_ADDR_LEN; ++i) * sum ^= *((caddr_t)(isa_dev -> id_maddr + i)); * printf(" checkusum = %x \n",sum); */ /* * Get station address from on-board ROM */ for (i = 0; i < ETHER_ADDR_LEN; ++i) sc->arpcom.ac_enaddr[i] = *((caddr_t)(isa_dev -> id_maddr + i)); /* * Disable window memory * bit7:1 enable , 0 disable */ cmd = cmd & 0x7f; outb((sc->asic_addr + ED_CNET98_WIN_REG),cmd); DELAY(10); /* * Set window buffer memory area * board memory base 0x400000 data 16kbyte * FreeBSD address 0xf00xxxxx */ outb((sc->asic_addr + ED_CNET98_MAP_REG0L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG0H),0x40); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG1L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG1H),0x41); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG2L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG2H),0x42); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG3L),0x00); DELAY(10); outb((sc->asic_addr + ED_CNET98_MAP_REG3H),0x43); DELAY(10); /* * Enable window memory * bit7:1 enable , 0 disable */ cmd = cmd | 0x80; outb((sc->asic_addr + ED_CNET98_WIN_REG),cmd); DELAY(10); /* * Clear interface memory, then sum to make sure its valid */ for (j = 0; j < sc->mem_size; ++j) sc->mem_start[j] = 0x0; for (sum = 0, j = 0; j < sc->mem_size; ++j) sum |= sc->mem_start[j]; if (sum != 0x0) { printf("ed%d: CNET98 dual port RAM address error\n", isa_dev->id_unit); return (0); } /* * Set interrupt level */ switch (isa_dev->id_irq) { case IRQ12: outb((sc->asic_addr + ED_CNET98_INT_LEV),ED_CNET98_INT_IRQ12); break; case IRQ3: outb((sc->asic_addr + ED_CNET98_INT_LEV),ED_CNET98_INT_IRQ3); break; case IRQ5: outb((sc->asic_addr + ED_CNET98_INT_LEV),ED_CNET98_INT_IRQ5); break; case IRQ6: outb((sc->asic_addr + ED_CNET98_INT_LEV),ED_CNET98_INT_IRQ6); break; case IRQ9: outb((sc->asic_addr + ED_CNET98_INT_LEV),ED_CNET98_INT_IRQ9); break; case IRQ13: outb((sc->asic_addr + ED_CNET98_INT_LEV),ED_CNET98_INT_IRQ13); break; default: printf("ed%d: Change Interrupt level default value from %d to %d.\n", isa_dev->id_irq,IRQ5); isa_dev->id_irq = IRQ5; outb((sc->asic_addr + ED_CNET98_INT_LEV),ED_CNET98_INT_IRQ5); break; } DELAY(1000); /* * Set interrupt mask. * bit7:1 all interrupt mask * bit1:1 timer interrupt mask * bit0:0 NS controler interrupt enable */ outb((sc->asic_addr + ED_CNET98_INT_MASK),0x7e); DELAY(1000); return (ED_CNET98_IO_PORTS); } static int ed_probe_CNET98EL(struct isa_device* isa_dev) { struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; u_int memsize, n; u_char romdata[ETHER_ADDR_LEN * 2], tmp; static char test_pattern[32] = "THIS is A memory TEST pattern"; char test_buffer[32]; u_short init_addr = ED_CNET98EL_INIT; int unit = isa_dev->id_unit; sc->asic_addr = isa_dev->id_iobase + ED_NOVELL_ASIC_OFFSET; sc->nic_addr = isa_dev->id_iobase + ED_NOVELL_NIC_OFFSET; /* Choose initial register address */ if (ED_TYPE98SUB(isa_dev->id_flags) != 0) { init_addr = ED_CNET98EL_INIT2; } #ifdef ED_DEBUG printf("ed%d: initial register=%x\n", isa_dev->id_unit, init_addr); #endif /* Check i/o address. CNET98E/L only allows ?3d0h */ if ((sc->nic_addr & (u_short) 0x0fff) != 0x03d0) { printf("ed%d: Invalid i/o port configuration (%x) must be " "?3d0h for CNET98E/L\n", isa_dev->id_unit, sc->nic_addr); return (0); } /* * Reset the board to force it into a known state. */ outb(init_addr, 0x00); /* request */ DELAY(5000); outb(init_addr, 0x01); /* cancel */ /* * Set i/o address(A15-12) and cpu type */ tmp = (sc->nic_addr & (u_short) 0xf000) >> 8; tmp |= (0x08 | 0x01); /* * bit0 is 1:80286 or higher, 0:not. * But FreeBSD runs under i386 or higher, thus bit0 must be 1. */ #ifdef ED_DEBUG printf("ed%d: outb(%x, %x)\n", isa_dev->id_unit, init_addr + 2, tmp); #endif outb(init_addr + 2, tmp); /* Make sure that we really have a DL9800 board */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); DELAY(5000); tmp = inb(sc->nic_addr + ED_P0_CR); #ifdef ED_DEBUG printf("ed%d: inb(%x) = %x\n", isa_dev->id_unit, sc->nic_addr + ED_P0_CR, tmp); #endif if ((tmp & ~ED_CR_STA) != (ED_CR_RD2 | ED_CR_STP)) return (0); if ((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) != ED_ISR_RST) return (0); sc->vendor = ED_VENDOR_NOVELL; sc->mem_shared = 0; sc->cr_proto = ED_CR_RD2; isa_dev->id_maddr = 0; /* Test the ability to read and write to the NIC memory. */ /* * This prevents packets from being stored in the NIC memory when the * readmem routine turns on the start bit in the CR. */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); /* initialize DCR for word operations */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_WTS | ED_DCR_FT1 | ED_DCR_LS); sc->isa16bit = 1; /* CNET98E/L board has 16k of memory */ memsize = 16384; /* NIC memory start at zero on a CNET98E/L board */ sc->mem_start = (char *) ED_CNET98EL_PAGE_OFFSET; sc->mem_end = sc->mem_start + memsize; sc->tx_page_start = ED_CNET98EL_PAGE_OFFSET / ED_PAGE_SIZE; /* * Write a test pattern in word mode. If failure page is not 16k, then * we don't know what this board is. */ for (n = ED_CNET98EL_PAGE_OFFSET; n < 65536; n += 1024) { ed_pio_writemem(sc, test_pattern, n, sizeof(test_pattern)); ed_pio_readmem(sc, n, test_buffer, sizeof(test_pattern)); if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) break; } if (n != (ED_CNET98EL_PAGE_OFFSET + memsize)) { #ifdef ED_DEBUG printf("ed%d: CNET98E/L memory failure at %x\n", isa_dev->id_unit, n); #endif return (0); /* not a CNET98E/L */ } /* * Set IRQ. CNET98E/L only allows a choice of irq 3,5,6. */ switch (isa_dev->id_irq) { case IRQ3: outb(sc->asic_addr + ED_CNET98EL_ICR, ED_CNET98EL_ICR_IRQ3); break; case IRQ5: outb(sc->asic_addr + ED_CNET98EL_ICR, ED_CNET98EL_ICR_IRQ5); break; case IRQ6: outb(sc->asic_addr + ED_CNET98EL_ICR, ED_CNET98EL_ICR_IRQ6); break; #if 0 case IRQ12: outb(sc->asic_addr + ED_CNET98EL_ICR, ED_CNET98EL_ICR_IRQ12); break; #endif default: printf( "ed%d: Invalid irq configuration (%d) must be 3,5,6 for CNET98E/L\n", isa_dev->id_unit, ffs(isa_dev->id_irq) - 1); return (0); } outb(sc->asic_addr + ED_CNET98EL_IMR, 0x7e); sc->type_str = "CNET98E/L"; #if 0 /* probably not useful - NE boards only come two ways */ /* allow kernel config file overrides */ if (isa_dev->id_msize) memsize = isa_dev->id_msize; #endif sc->mem_size = memsize; /* * Use one xmit buffer if < 16k, two buffers otherwise (if not told * otherwise). */ if ((memsize < 16384) || (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING)) sc->txb_cnt = 1; else sc->txb_cnt = 2; sc->rec_page_start = sc->tx_page_start + sc->txb_cnt * ED_TXBUF_SIZE; sc->rec_page_stop = sc->tx_page_start + memsize / ED_PAGE_SIZE; sc->mem_ring = sc->mem_start + sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE; /* * Get station address from on-board ROM */ ed_pio_readmem(sc, 16384, romdata, sizeof(romdata)); for (n = 0; n < ETHER_ADDR_LEN; n++) sc->arpcom.ac_enaddr[n] = romdata[n * (sc->isa16bit + 1)]; /* clear any pending interrupts that might have occurred above */ outb(sc->nic_addr + ED_P0_ISR, 0xff); return (ED_CNET98EL_IO_PORTS); } #endif /* * Install interface into kernel networking data structures */ static int ed_attach(sc, unit, flags) struct ed_softc *sc; int unit; int flags; { struct ifnet *ifp = &sc->arpcom.ac_if; /* * Set interface to stopped condition (reset) */ ed_stop(sc); if (!ifp->if_name) { /* * Initialize ifnet structure */ ifp->if_softc = sc; ifp->if_unit = unit; ifp->if_name = "ed"; ifp->if_output = ether_output; ifp->if_start = ed_start; ifp->if_ioctl = ed_ioctl; ifp->if_watchdog = ed_watchdog; ifp->if_init = ed_init; ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; ifp->if_linkmib = &sc->mibdata; ifp->if_linkmiblen = sizeof sc->mibdata; /* * XXX - should do a better job. */ if (sc->is790) sc->mibdata.dot3StatsEtherChipSet = DOT3CHIPSET(dot3VendorWesternDigital, dot3ChipSetWesternDigital83C790); else sc->mibdata.dot3StatsEtherChipSet = DOT3CHIPSET(dot3VendorNational, dot3ChipSetNational8390); sc->mibdata.dot3Compliance = DOT3COMPLIANCE_COLLS; /* * Set default state for ALTPHYS flag (used to disable the * tranceiver for AUI operation), based on compile-time * config option. */ if (flags & ED_FLAGS_DISABLE_TRANCEIVER) ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_ALTPHYS); else ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); /* * Attach the interface */ if_attach(ifp); ether_ifattach(ifp); } /* device attach does transition from UNCONFIGURED to IDLE state */ /* * Print additional info when attached */ printf("%s%d: address %6D, ", ifp->if_name, ifp->if_unit, sc->arpcom.ac_enaddr, ":"); if (sc->type_str && (*sc->type_str != 0)) printf("type %s ", sc->type_str); else printf("type unknown (0x%x) ", sc->type); if (sc->vendor == ED_VENDOR_HP) printf("(%s %s IO)", (sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS) ? "16-bit" : "32-bit", sc->hpp_mem_start ? "memory mapped" : "regular"); else printf("%s ", sc->isa16bit ? "(16 bit)" : "(8 bit)"); printf("%s\n", (((sc->vendor == ED_VENDOR_3COM) || (sc->vendor == ED_VENDOR_HP)) && (ifp->if_flags & IFF_ALTPHYS)) ? " tranceiver disabled" : ""); /* * If BPF is in the kernel, call the attach for it */ #if NBPFILTER > 0 bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); #endif return 1; } static int ed_attach_isa(isa_dev) struct isa_device *isa_dev; { int unit = isa_dev->id_unit; struct ed_softc *sc = &ed_softc[unit]; int flags = isa_dev->id_flags; return ed_attach(sc, unit, flags); } #if NPCI > 0 void * ed_attach_NE2000_pci(unit, port) int unit; int port; { struct ed_softc *sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT); int isa_flags = 0; if (!sc) return sc; bzero(sc, sizeof *sc); if (ed_probe_Novell_generic(sc, port, unit, isa_flags) == 0 || ed_attach(sc, unit, isa_flags) == 0) { free(sc, M_DEVBUF); return NULL; } return sc; } #endif /* * Reset interface. */ static void ed_reset(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; int s; if (sc->gone) return; s = splimp(); /* * Stop interface and re-initialize. */ ed_stop(sc); ed_init(sc); (void) splx(s); } /* * Take interface offline. */ static void ed_stop(sc) struct ed_softc *sc; { int n = 5000; if (sc->gone) return; /* * Stop everything on the interface, and select page 0 registers. */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); /* * Wait for interface to enter stopped state, but limit # of checks to * 'n' (about 5ms). It shouldn't even take 5us on modern DS8390's, but * just in case it's an old one. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) == 0) && --n); } /* * Device timeout/watchdog routine. Entered if the device neglects to * generate an interrupt after a transmit has been started on it. */ static void ed_watchdog(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; if (sc->gone) return; log(LOG_ERR, "ed%d: device timeout\n", ifp->if_unit); ifp->if_oerrors++; ed_reset(ifp); } /* * Initialize device. */ static void ed_init(xsc) void *xsc; { struct ed_softc *sc = xsc; struct ifnet *ifp = &sc->arpcom.ac_if; int i, s; if (sc->gone) return; /* address not known */ if (TAILQ_EMPTY(&ifp->if_addrhead)) /* unlikely? XXX */ return; /* * Initialize the NIC in the exact order outlined in the NS manual. * This init procedure is "mandatory"...don't change what or when * things happen. */ s = splimp(); /* reset transmitter flags */ sc->xmit_busy = 0; ifp->if_timer = 0; sc->txb_inuse = 0; sc->txb_new = 0; sc->txb_next_tx = 0; /* This variable is used below - don't move this assignment */ sc->next_packet = sc->rec_page_start + 1; /* * Set interface for page 0, Remote DMA complete, Stopped */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); if (sc->isa16bit) { /* * Set FIFO threshold to 8, No auto-init Remote DMA, byte * order=80x86, word-wide DMA xfers, */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_WTS | ED_DCR_LS); } else { /* * Same as above, but byte-wide DMA xfers */ outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); } /* * Clear Remote Byte Count Registers */ outb(sc->nic_addr + ED_P0_RBCR0, 0); outb(sc->nic_addr + ED_P0_RBCR1, 0); /* * For the moment, don't store incoming packets in memory. */ outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); /* * Place NIC in internal loopback mode */ outb(sc->nic_addr + ED_P0_TCR, ED_TCR_LB0); /* * Initialize transmit/receive (ring-buffer) Page Start */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start); outb(sc->nic_addr + ED_P0_PSTART, sc->rec_page_start); /* Set lower bits of byte addressable framing to 0 */ if (sc->is790) outb(sc->nic_addr + 0x09, 0); /* * Initialize Receiver (ring-buffer) Page Stop and Boundry */ outb(sc->nic_addr + ED_P0_PSTOP, sc->rec_page_stop); outb(sc->nic_addr + ED_P0_BNRY, sc->rec_page_start); /* * Clear all interrupts. A '1' in each bit position clears the * corresponding flag. */ outb(sc->nic_addr + ED_P0_ISR, 0xff); /* * Enable the following interrupts: receive/transmit complete, * receive/transmit error, and Receiver OverWrite. * * Counter overflow and Remote DMA complete are *not* enabled. */ outb(sc->nic_addr + ED_P0_IMR, ED_IMR_PRXE | ED_IMR_PTXE | ED_IMR_RXEE | ED_IMR_TXEE | ED_IMR_OVWE); /* * Program Command Register for page 1 */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STP); /* * Copy out our station address */ #ifdef PC98 for (i = 0; i < ETHER_ADDR_LEN; ++i) outb(sc->nic_addr + ED_P1_PAR(i), sc->arpcom.ac_enaddr[i]); #else for (i = 0; i < ETHER_ADDR_LEN; ++i) outb(sc->nic_addr + ED_P1_PAR0 + i, sc->arpcom.ac_enaddr[i]); #endif /* * Set Current Page pointer to next_packet (initialized above) */ outb(sc->nic_addr + ED_P1_CURR, sc->next_packet); /* * Program Receiver Configuration Register and multicast filter. CR is * set to page 0 on return. */ ed_setrcr(sc); /* * Take interface out of loopback */ outb(sc->nic_addr + ED_P0_TCR, 0); /* * If this is a 3Com board, the tranceiver must be software enabled * (there is no settable hardware default). */ if (sc->vendor == ED_VENDOR_3COM) { if (ifp->if_flags & IFF_ALTPHYS) { outb(sc->asic_addr + ED_3COM_CR, 0); } else { outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); } } /* * Set 'running' flag, and clear output active flag. */ ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; /* * ...and attempt to start output */ ed_start(ifp); (void) splx(s); } /* * This routine actually starts the transmission on the interface */ static inline void ed_xmit(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; unsigned short len; if (sc->gone) return; len = sc->txb_len[sc->txb_next_tx]; /* * Set NIC for page 0 register access */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * Set TX buffer start page */ outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start + sc->txb_next_tx * ED_TXBUF_SIZE); /* * Set TX length */ outb(sc->nic_addr + ED_P0_TBCR0, len); outb(sc->nic_addr + ED_P0_TBCR1, len >> 8); /* * Set page 0, Remote DMA complete, Transmit Packet, and *Start* */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_TXP | ED_CR_STA); sc->xmit_busy = 1; /* * Point to next transmit buffer slot and wrap if necessary. */ sc->txb_next_tx++; if (sc->txb_next_tx == sc->txb_cnt) sc->txb_next_tx = 0; /* * Set a timer just in case we never hear from the board again */ ifp->if_timer = 2; } /* * Start output on interface. * We make two assumptions here: * 1) that the current priority is set to splimp _before_ this code * is called *and* is returned to the appropriate priority after * return * 2) that the IFF_OACTIVE flag is checked before this code is called * (i.e. that the output part of the interface is idle) */ static void ed_start(ifp) struct ifnet *ifp; { struct ed_softc *sc = ifp->if_softc; struct mbuf *m0, *m; caddr_t buffer; int len; if (sc->gone) { printf("ed_start(%p) GONE\n",ifp); return; } outloop: /* * First, see if there are buffered packets and an idle transmitter - * should never happen at this point. */ if (sc->txb_inuse && (sc->xmit_busy == 0)) { printf("ed: packets buffered, but transmitter idle\n"); ed_xmit(sc); } /* * See if there is room to put another packet in the buffer. */ if (sc->txb_inuse == sc->txb_cnt) { /* * No room. Indicate this to the outside world and exit. */ ifp->if_flags |= IFF_OACTIVE; return; } IF_DEQUEUE(&ifp->if_snd, m); if (m == 0) { /* * We are using the !OACTIVE flag to indicate to the outside * world that we can accept an additional packet rather than * that the transmitter is _actually_ active. Indeed, the * transmitter may be active, but if we haven't filled all the * buffers with data then we still want to accept more. */ ifp->if_flags &= ~IFF_OACTIVE; return; } /* * Copy the mbuf chain into the transmit buffer */ m0 = m; /* txb_new points to next open buffer slot */ buffer = sc->mem_start + (sc->txb_new * ED_TXBUF_SIZE * ED_PAGE_SIZE); if (sc->mem_shared) { /* * Special case setup for 16 bit boards... */ if (sc->isa16bit) { switch (sc->vendor) { /* * For 16bit 3Com boards (which have 16k of * memory), we have the xmit buffers in a * different page of memory ('page 0') - so * change pages. */ case ED_VENDOR_3COM: outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL); break; /* * Enable 16bit access to shared memory on * WD/SMC boards. */ case ED_VENDOR_WD_SMC:{ outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto | ED_WD_LAAR_M16EN); if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); } break; } } } for (len = 0; m != 0; m = m->m_next) { bcopy(mtod(m, caddr_t), buffer, m->m_len); buffer += m->m_len; len += m->m_len; } /* * Restore previous shared memory access */ if (sc->isa16bit) { switch (sc->vendor) { case ED_VENDOR_3COM: outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL | ED_3COM_GACFR_MBS0); break; case ED_VENDOR_WD_SMC:{ if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto & ~ED_WD_LAAR_M16EN); break; } } } } else { len = ed_pio_write_mbufs(sc, m, (int)buffer); if (len == 0) goto outloop; } sc->txb_len[sc->txb_new] = max(len, (ETHER_MIN_LEN-ETHER_CRC_LEN)); sc->txb_inuse++; /* * Point to next buffer slot and wrap if necessary. */ sc->txb_new++; if (sc->txb_new == sc->txb_cnt) sc->txb_new = 0; if (sc->xmit_busy == 0) ed_xmit(sc); /* * Tap off here if there is a bpf listener. */ #if NBPFILTER > 0 if (ifp->if_bpf) { bpf_mtap(ifp, m0); } #endif m_freem(m0); /* * Loop back to the top to possibly buffer more packets */ goto outloop; } /* * Ethernet interface receiver interrupt. */ static inline void ed_rint(sc) struct ed_softc *sc; { struct ifnet *ifp = &sc->arpcom.ac_if; u_char boundry; u_short len; struct ed_ring packet_hdr; char *packet_ptr; if (sc->gone) return; /* * Set NIC to page 1 registers to get 'current' pointer */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STA); /* * 'sc->next_packet' is the logical beginning of the ring-buffer - * i.e. it points to where new data has been buffered. The 'CURR' * (current) register points to the logical end of the ring-buffer - * i.e. it points to where additional new data will be added. We loop * here until the logical beginning equals the logical end (or in * other words, until the ring-buffer is empty). */ while (sc->next_packet != inb(sc->nic_addr + ED_P1_CURR)) { /* get pointer to this buffer's header structure */ packet_ptr = sc->mem_ring + (sc->next_packet - sc->rec_page_start) * ED_PAGE_SIZE; /* * The byte count includes a 4 byte header that was added by * the NIC. */ if (sc->mem_shared) packet_hdr = *(struct ed_ring *) packet_ptr; else ed_pio_readmem(sc, (int)packet_ptr, (char *) &packet_hdr, sizeof(packet_hdr)); len = packet_hdr.count; if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring)) || len < (ETHER_MIN_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring))) { /* * Length is a wild value. There's a good chance that * this was caused by the NIC being old and buggy. * The bug is that the length low byte is duplicated in * the high byte. Try to recalculate the length based on * the pointer to the next packet. */ /* * NOTE: sc->next_packet is pointing at the current packet. */ len &= ED_PAGE_SIZE - 1; /* preserve offset into page */ if (packet_hdr.next_packet >= sc->next_packet) { len += (packet_hdr.next_packet - sc->next_packet) * ED_PAGE_SIZE; } else { len += ((packet_hdr.next_packet - sc->rec_page_start) + (sc->rec_page_stop - sc->next_packet)) * ED_PAGE_SIZE; } if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN + sizeof(struct ed_ring))) sc->mibdata.dot3StatsFrameTooLongs++; } /* * Be fairly liberal about what we allow as a "reasonable" length * so that a [crufty] packet will make it to BPF (and can thus * be analyzed). Note that all that is really important is that * we have a length that will fit into one mbuf cluster or less; * the upper layer protocols can then figure out the length from * their own length field(s). */ if ((len > sizeof(struct ed_ring)) && (len <= MCLBYTES) && (packet_hdr.next_packet >= sc->rec_page_start) && (packet_hdr.next_packet < sc->rec_page_stop)) { /* * Go get packet. */ ed_get_packet(sc, packet_ptr + sizeof(struct ed_ring), len - sizeof(struct ed_ring), packet_hdr.rsr & ED_RSR_PHY); ifp->if_ipackets++; } else { /* * Really BAD. The ring pointers are corrupted. */ log(LOG_ERR, "ed%d: NIC memory corrupt - invalid packet length %d\n", ifp->if_unit, len); ifp->if_ierrors++; ed_reset(ifp); return; } /* * Update next packet pointer */ sc->next_packet = packet_hdr.next_packet; /* * Update NIC boundry pointer - being careful to keep it one * buffer behind. (as recommended by NS databook) */ boundry = sc->next_packet - 1; if (boundry < sc->rec_page_start) boundry = sc->rec_page_stop - 1; /* * Set NIC to page 0 registers to update boundry register */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); outb(sc->nic_addr + ED_P0_BNRY, boundry); /* * Set NIC to page 1 registers before looping to top (prepare * to get 'CURR' current pointer) */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STA); } } /* * Ethernet interface interrupt processor */ void edintr_sc(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; u_char isr; if (sc->gone) return; /* * Set NIC to page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * loop until there are no more new interrupts */ while ((isr = inb(sc->nic_addr + ED_P0_ISR)) != 0) { /* * reset all the bits that we are 'acknowledging' by writing a * '1' to each bit position that was set (writing a '1' * *clears* the bit) */ outb(sc->nic_addr + ED_P0_ISR, isr); /* * Handle transmitter interrupts. Handle these first because * the receiver will reset the board under some conditions. */ if (isr & (ED_ISR_PTX | ED_ISR_TXE)) { u_char collisions = inb(sc->nic_addr + ED_P0_NCR) & 0x0f; /* * Check for transmit error. If a TX completed with an * error, we end up throwing the packet away. Really * the only error that is possible is excessive * collisions, and in this case it is best to allow * the automatic mechanisms of TCP to backoff the * flow. Of course, with UDP we're screwed, but this * is expected when a network is heavily loaded. */ (void) inb(sc->nic_addr + ED_P0_TSR); if (isr & ED_ISR_TXE) { u_char tsr; /* * Excessive collisions (16) */ tsr = inb(sc->nic_addr + ED_P0_TSR); if ((tsr & ED_TSR_ABT) && (collisions == 0)) { /* * When collisions total 16, the * P0_NCR will indicate 0, and the * TSR_ABT is set. */ collisions = 16; sc->mibdata.dot3StatsExcessiveCollisions++; sc->mibdata.dot3StatsCollFrequencies[15]++; } if (tsr & ED_TSR_OWC) sc->mibdata.dot3StatsLateCollisions++; if (tsr & ED_TSR_CDH) sc->mibdata.dot3StatsSQETestErrors++; if (tsr & ED_TSR_CRS) sc->mibdata.dot3StatsCarrierSenseErrors++; if (tsr & ED_TSR_FU) sc->mibdata.dot3StatsInternalMacTransmitErrors++; /* * update output errors counter */ ifp->if_oerrors++; } else { /* * Update total number of successfully * transmitted packets. */ ifp->if_opackets++; } /* * reset tx busy and output active flags */ sc->xmit_busy = 0; ifp->if_flags &= ~IFF_OACTIVE; /* * clear watchdog timer */ ifp->if_timer = 0; /* * Add in total number of collisions on last * transmission. */ ifp->if_collisions += collisions; switch(collisions) { case 0: case 16: break; case 1: sc->mibdata.dot3StatsSingleCollisionFrames++; sc->mibdata.dot3StatsCollFrequencies[0]++; break; default: sc->mibdata.dot3StatsMultipleCollisionFrames++; sc->mibdata. dot3StatsCollFrequencies[collisions-1] ++; break; } /* * Decrement buffer in-use count if not zero (can only * be zero if a transmitter interrupt occured while * not actually transmitting). If data is ready to * transmit, start it transmitting, otherwise defer * until after handling receiver */ if (sc->txb_inuse && --sc->txb_inuse) ed_xmit(sc); } /* * Handle receiver interrupts */ if (isr & (ED_ISR_PRX | ED_ISR_RXE | ED_ISR_OVW)) { /* * Overwrite warning. In order to make sure that a * lockup of the local DMA hasn't occurred, we reset * and re-init the NIC. The NSC manual suggests only a * partial reset/re-init is necessary - but some chips * seem to want more. The DMA lockup has been seen * only with early rev chips - Methinks this bug was * fixed in later revs. -DG */ if (isr & ED_ISR_OVW) { ifp->if_ierrors++; #ifdef DIAGNOSTIC log(LOG_WARNING, "ed%d: warning - receiver ring buffer overrun\n", ifp->if_unit); #endif /* * Stop/reset/re-init NIC */ ed_reset(ifp); } else { /* * Receiver Error. One or more of: CRC error, * frame alignment error FIFO overrun, or * missed packet. */ if (isr & ED_ISR_RXE) { u_char rsr; rsr = inb(sc->nic_addr + ED_P0_RSR); if (rsr & ED_RSR_CRC) sc->mibdata.dot3StatsFCSErrors++; if (rsr & ED_RSR_FAE) sc->mibdata.dot3StatsAlignmentErrors++; if (rsr & ED_RSR_FO) sc->mibdata.dot3StatsInternalMacReceiveErrors++; ifp->if_ierrors++; #ifdef ED_DEBUG printf("ed%d: receive error %x\n", ifp->if_unit, inb(sc->nic_addr + ED_P0_RSR)); #endif } /* * Go get the packet(s) XXX - Doing this on an * error is dubious because there shouldn't be * any data to get (we've configured the * interface to not accept packets with * errors). */ /* * Enable 16bit access to shared memory first * on WD/SMC boards. */ if (sc->isa16bit && (sc->vendor == ED_VENDOR_WD_SMC)) { outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto | ED_WD_LAAR_M16EN); if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); } } ed_rint(sc); /* disable 16bit access */ if (sc->isa16bit && (sc->vendor == ED_VENDOR_WD_SMC)) { if (sc->is790) { outb(sc->asic_addr + ED_WD_MSR, 0x00); } outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto & ~ED_WD_LAAR_M16EN); } } } /* * If it looks like the transmitter can take more data, * attempt to start output on the interface. This is done * after handling the receiver to give the receiver priority. */ if ((ifp->if_flags & IFF_OACTIVE) == 0) ed_start(ifp); /* * return NIC CR to standard state: page 0, remote DMA * complete, start (toggling the TXP bit off, even if was just * set in the transmit routine, is *okay* - it is 'edge' * triggered from low to high) */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* * If the Network Talley Counters overflow, read them to reset * them. It appears that old 8390's won't clear the ISR flag * otherwise - resulting in an infinite loop. */ if (isr & ED_ISR_CNT) { (void) inb(sc->nic_addr + ED_P0_CNTR0); (void) inb(sc->nic_addr + ED_P0_CNTR1); (void) inb(sc->nic_addr + ED_P0_CNTR2); } } } void edintr(unit) int unit; { edintr_sc (&ed_softc[unit]); } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int ed_ioctl(ifp, command, data) register struct ifnet *ifp; int command; caddr_t data; { struct ed_softc *sc = ifp->if_softc; int s, error = 0; if (sc->gone) { ifp->if_flags &= ~IFF_RUNNING; return ENXIO; } s = splimp(); switch (command) { case SIOCSIFADDR: case SIOCGIFADDR: case SIOCSIFMTU: error = ether_ioctl(ifp, command, data); break; case SIOCSIFFLAGS: /* * If the interface is marked up and stopped, then start it. * If it is marked down and running, then stop it. */ if (ifp->if_flags & IFF_UP) { if ((ifp->if_flags & IFF_RUNNING) == 0) ed_init(sc); } else { if (ifp->if_flags & IFF_RUNNING) { ed_stop(sc); ifp->if_flags &= ~IFF_RUNNING; } } #if NBPFILTER > 0 /* * Promiscuous flag may have changed, so reprogram the RCR. */ ed_setrcr(sc); #endif /* * An unfortunate hack to provide the (required) software * control of the tranceiver for 3Com boards. The ALTPHYS flag * disables the tranceiver if set. */ if (sc->vendor == ED_VENDOR_3COM) { if (ifp->if_flags & IFF_ALTPHYS) { outb(sc->asic_addr + ED_3COM_CR, 0); } else { outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); } } else if (sc->vendor == ED_VENDOR_HP) ed_hpp_set_physical_link(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: /* * Multicast list has changed; set the hardware filter * accordingly. */ ed_setrcr(sc); error = 0; break; default: error = EINVAL; } (void) splx(s); return (error); } /* * Given a source and destination address, copy 'amount' of a packet from * the ring buffer into a linear destination buffer. Takes into account * ring-wrap. */ static inline char * ed_ring_copy(sc, src, dst, amount) struct ed_softc *sc; char *src; char *dst; u_short amount; { u_short tmp_amount; /* does copy wrap to lower addr in ring buffer? */ if (src + amount > sc->mem_end) { tmp_amount = sc->mem_end - src; /* copy amount up to end of NIC memory */ if (sc->mem_shared) bcopy(src, dst, tmp_amount); else ed_pio_readmem(sc, (int)src, dst, tmp_amount); amount -= tmp_amount; src = sc->mem_ring; dst += tmp_amount; } if (sc->mem_shared) bcopy(src, dst, amount); else ed_pio_readmem(sc, (int)src, dst, amount); return (src + amount); } /* * Retreive packet from shared memory and send to the next level up via * ether_input(). If there is a BPF listener, give a copy to BPF, too. */ static void ed_get_packet(sc, buf, len, multicast) struct ed_softc *sc; char *buf; u_short len; int multicast; { struct ether_header *eh; struct mbuf *m; /* Allocate a header mbuf */ MGETHDR(m, M_DONTWAIT, MT_DATA); if (m == NULL) return; m->m_pkthdr.rcvif = &sc->arpcom.ac_if; m->m_pkthdr.len = m->m_len = len; /* * We always put the received packet in a single buffer - * either with just an mbuf header or in a cluster attached * to the header. The +2 is to compensate for the alignment * fixup below. */ if ((len + 2) > MHLEN) { /* Attach an mbuf cluster */ MCLGET(m, M_DONTWAIT); /* Insist on getting a cluster */ if ((m->m_flags & M_EXT) == 0) { m_freem(m); return; } } /* * The +2 is to longword align the start of the real packet. * This is important for NFS. */ m->m_data += 2; eh = mtod(m, struct ether_header *); /* * Get packet, including link layer address, from interface. */ ed_ring_copy(sc, buf, (char *)eh, len); #if NBPFILTER > 0 /* * Check if there's a BPF listener on this interface. If so, hand off * the raw packet to bpf. */ if (sc->arpcom.ac_if.if_bpf) { bpf_mtap(&sc->arpcom.ac_if, m); /* * Note that the interface cannot be in promiscuous mode if * there are no BPF listeners. And if we are in promiscuous * mode, we have to check if this packet is really ours. */ if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, sizeof(eh->ether_dhost)) != 0 && multicast == 0) { m_freem(m); return; } } #endif /* * Remove link layer address. */ m->m_pkthdr.len = m->m_len = len - sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); ether_input(&sc->arpcom.ac_if, eh, m); return; } /* * Supporting routines */ /* * Given a NIC memory source address and a host memory destination * address, copy 'amount' from NIC to host using Programmed I/O. * The 'amount' is rounded up to a word - okay as long as mbufs * are word sized. * This routine is currently Novell-specific. */ static void ed_pio_readmem(sc, src, dst, amount) struct ed_softc *sc; int src; unsigned char *dst; unsigned short amount; { /* HP cards need special handling */ if (sc->vendor == ED_VENDOR_HP && sc->type == ED_TYPE_HP_PCLANPLUS) { ed_hpp_readmem(sc, src, dst, amount); return; } /* Regular Novell cards */ /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* round up to a word */ if (amount & 1) ++amount; /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, amount); outb(sc->nic_addr + ED_P0_RBCR1, amount >> 8); /* set up source address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, src); outb(sc->nic_addr + ED_P0_RSAR1, src >> 8); outb(sc->nic_addr + ED_P0_CR, ED_CR_RD0 | ED_CR_STA); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_ON(); #endif if (sc->isa16bit) { insw(sc->asic_addr + ED_NOVELL_DATA, dst, amount / 2); } else insb(sc->asic_addr + ED_NOVELL_DATA, dst, amount); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_OFF(); #endif } /* * Stripped down routine for writing a linear buffer to NIC memory. * Only used in the probe routine to test the memory. 'len' must * be even. */ static void ed_pio_writemem(sc, src, dst, len) struct ed_softc *sc; char *src; unsigned short dst; unsigned short len; { int maxwait = 200; /* about 240us */ if (sc->vendor == ED_VENDOR_NOVELL) { /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, len); outb(sc->nic_addr + ED_P0_RBCR1, len >> 8); /* set up destination address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, dst); outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); /* set remote DMA write */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_ON(); #endif if (sc->isa16bit) outsw(sc->asic_addr + ED_NOVELL_DATA, src, len / 2); else outsb(sc->asic_addr + ED_NOVELL_DATA, src, len); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_OFF(); #endif /* * Wait for remote DMA complete. This is necessary because on the * transmit side, data is handled internally by the NIC in bursts and * we can't start another remote DMA until this one completes. Not * waiting causes really bad things to happen - like the NIC * irrecoverably jamming the ISA bus. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); } else if ((sc->vendor == ED_VENDOR_HP) && (sc->type == ED_TYPE_HP_PCLANPLUS)) { /* HP PCLAN+ */ /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* program the write address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_0, dst); if (sc->hpp_mem_start) { u_short *s = (u_short *) src; volatile u_short *d = (u_short *) sc->hpp_mem_start; u_short *const fence = s + (len >> 1); /* * Enable memory mapped access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); /* * Copy to NIC memory. */ while (s < fence) *d = *s++; /* * Restore Boot ROM access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); } else { /* write data using I/O writes */ outsw(sc->asic_addr + ED_HPP_PAGE_4, src, len / 2); } } } /* * Write an mbuf chain to the destination NIC memory address using * programmed I/O. */ static u_short ed_pio_write_mbufs(sc, m, dst) struct ed_softc *sc; struct mbuf *m; int dst; { struct ifnet *ifp = (struct ifnet *)sc; unsigned short total_len, dma_len; struct mbuf *mp; int maxwait = 200; /* about 240us */ /* HP PC Lan+ cards need special handling */ if ((sc->vendor == ED_VENDOR_HP) && (sc->type == ED_TYPE_HP_PCLANPLUS)) { return ed_hpp_write_mbufs(sc, m, dst); } /* First, count up the total number of bytes to copy */ for (total_len = 0, mp = m; mp; mp = mp->m_next) total_len += mp->m_len; dma_len = total_len; if (sc->isa16bit && (dma_len & 1)) dma_len++; /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* set up DMA byte count */ outb(sc->nic_addr + ED_P0_RBCR0, dma_len); outb(sc->nic_addr + ED_P0_RBCR1, dma_len >> 8); /* set up destination address in NIC mem */ outb(sc->nic_addr + ED_P0_RSAR0, dst); outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); /* set remote DMA write */ outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); /* * Transfer the mbuf chain to the NIC memory. * 16-bit cards require that data be transferred as words, and only words. * So that case requires some extra code to patch over odd-length mbufs. */ if (!sc->isa16bit) { /* NE1000s are easy */ while (m) { if (m->m_len) { #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_ON(); #endif outsb(sc->asic_addr + ED_NOVELL_DATA, m->m_data, m->m_len); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_OFF(); #endif } m = m->m_next; } } else { /* NE2000s are a pain */ unsigned char *data; int len, wantbyte; unsigned char savebyte[2]; wantbyte = 0; while (m) { len = m->m_len; if (len) { data = mtod(m, caddr_t); /* finish the last word */ if (wantbyte) { savebyte[1] = *data; #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_ON(); #endif outw(sc->asic_addr + ED_NOVELL_DATA, *(u_short *)savebyte); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_OFF(); #endif data++; len--; wantbyte = 0; } /* output contiguous words */ if (len > 1) { #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_ON(); #endif outsw(sc->asic_addr + ED_NOVELL_DATA, data, len >> 1); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_OFF(); #endif data += len & ~1; len &= 1; } /* save last byte, if necessary */ if (len == 1) { savebyte[0] = *data; wantbyte = 1; } } m = m->m_next; } /* spit last byte */ if (wantbyte) { #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_ON(); #endif outw(sc->asic_addr + ED_NOVELL_DATA, *(u_short *)savebyte); #ifdef PC98 if (sc->type == ED_TYPE98_LPC) LPCT_1d0_OFF(); #endif } } /* * Wait for remote DMA complete. This is necessary because on the * transmit side, data is handled internally by the NIC in bursts and * we can't start another remote DMA until this one completes. Not * waiting causes really bad things to happen - like the NIC * irrecoverably jamming the ISA bus. */ while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); if (!maxwait) { log(LOG_WARNING, "ed%d: remote transmit DMA failed to complete\n", ifp->if_unit); ed_reset(ifp); return(0); } return (total_len); } /* * Support routines to handle the HP PC Lan+ card. */ /* * HP PC Lan+: Read from NIC memory, using either PIO or memory mapped * IO. */ static void ed_hpp_readmem(sc, src, dst, amount) struct ed_softc *sc; unsigned short src; unsigned char *dst; unsigned short amount; { int use_32bit_access = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); /* Program the source address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_2, src); /* * The HP PC Lan+ card supports word reads as well as * a memory mapped i/o port that is aliased to every * even address on the board. */ if (sc->hpp_mem_start) { /* Enable memory mapped access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); if (use_32bit_access && (amount > 3)) { u_long *dl = (u_long *) dst; volatile u_long *const sl = (u_long *) sc->hpp_mem_start; u_long *const fence = dl + (amount >> 2); /* Copy out NIC data. We could probably write this as a `movsl'. The currently generated code is lousy. */ while (dl < fence) *dl++ = *sl; dst += (amount & ~3); amount &= 3; } /* Finish off any words left, as a series of short reads */ if (amount > 1) { u_short *d = (u_short *) dst; volatile u_short *const s = (u_short *) sc->hpp_mem_start; u_short *const fence = d + (amount >> 1); /* Copy out NIC data. */ while (d < fence) *d++ = *s; dst += (amount & ~1); amount &= 1; } /* * read in a byte; however we need to always read 16 bits * at a time or the hardware gets into a funny state */ if (amount == 1) { /* need to read in a short and copy LSB */ volatile u_short *const s = (volatile u_short *) sc->hpp_mem_start; *dst = (*s) & 0xFF; } /* Restore Boot ROM access. */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); } else { /* Read in data using the I/O port */ if (use_32bit_access && (amount > 3)) { insl(sc->asic_addr + ED_HPP_PAGE_4, dst, amount >> 2); dst += (amount & ~3); amount &= 3; } if (amount > 1) { insw(sc->asic_addr + ED_HPP_PAGE_4, dst, amount >> 1); dst += (amount & ~1); amount &= 1; } if (amount == 1) { /* read in a short and keep the LSB */ *dst = inw(sc->asic_addr + ED_HPP_PAGE_4) & 0xFF; } } } /* * Write to HP PC Lan+ NIC memory. Access to the NIC can be by using * outsw() or via the memory mapped interface to the same register. * Writes have to be in word units; byte accesses won't work and may cause * the NIC to behave wierdly. Long word accesses are permitted if the ASIC * allows it. */ static u_short ed_hpp_write_mbufs(struct ed_softc *sc, struct mbuf *m, int dst) { int len, wantbyte; unsigned short total_len; unsigned char savebyte[2]; volatile u_short * const d = (volatile u_short *) sc->hpp_mem_start; int use_32bit_accesses = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); /* select page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); /* reset remote DMA complete flag */ outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); /* program the write address in RAM */ outw(sc->asic_addr + ED_HPP_PAGE_0, dst); if (sc->hpp_mem_start) /* enable memory mapped I/O */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options & ~(ED_HPP_OPTION_MEM_DISABLE | ED_HPP_OPTION_BOOT_ROM_ENB)); wantbyte = 0; total_len = 0; if (sc->hpp_mem_start) { /* Memory mapped I/O port */ while (m) { total_len += (len = m->m_len); if (len) { caddr_t data = mtod(m, caddr_t); /* finish the last word of the previous mbuf */ if (wantbyte) { savebyte[1] = *data; *d = *((ushort *) savebyte); data++; len--; wantbyte = 0; } /* output contiguous words */ if ((len > 3) && (use_32bit_accesses)) { volatile u_long *const dl = (volatile u_long *) d; u_long *sl = (u_long *) data; u_long *fence = sl + (len >> 2); while (sl < fence) *dl = *sl++; data += (len & ~3); len &= 3; } /* finish off remain 16 bit writes */ if (len > 1) { u_short *s = (u_short *) data; u_short *fence = s + (len >> 1); while (s < fence) *d = *s++; data += (len & ~1); len &= 1; } /* save last byte if needed */ if (wantbyte = (len == 1)) savebyte[0] = *data; } m = m->m_next; /* to next mbuf */ } if (wantbyte) /* write last byte */ *d = *((u_short *) savebyte); } else { /* use programmed I/O */ while (m) { total_len += (len = m->m_len); if (len) { caddr_t data = mtod(m, caddr_t); /* finish the last word of the previous mbuf */ if (wantbyte) { savebyte[1] = *data; outw(sc->asic_addr + ED_HPP_PAGE_4, *((u_short *)savebyte)); data++; len--; wantbyte = 0; } /* output contiguous words */ if ((len > 3) && use_32bit_accesses) { outsl(sc->asic_addr + ED_HPP_PAGE_4, data, len >> 2); data += (len & ~3); len &= 3; } /* finish off remaining 16 bit accesses */ if (len > 1) { outsw(sc->asic_addr + ED_HPP_PAGE_4, data, len >> 1); data += (len & ~1); len &= 1; } if (wantbyte = (len == 1)) savebyte[0] = *data; } /* if len != 0 */ m = m->m_next; } if (wantbyte) /* spit last byte */ outw(sc->asic_addr + ED_HPP_PAGE_4, *(u_short *)savebyte); } if (sc->hpp_mem_start) /* turn off memory mapped i/o */ outw(sc->asic_addr + ED_HPP_OPTION, sc->hpp_options); return (total_len); } static void ed_setrcr(sc) struct ed_softc *sc; { struct ifnet *ifp = (struct ifnet *)sc; int i; /* set page 1 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_PAGE_1 | ED_CR_STP); if (ifp->if_flags & IFF_PROMISC) { /* * Reconfigure the multicast filter. */ #ifdef PC98 for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR(i), 0xff); #else for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR0 + i, 0xff); #endif /* * And turn on promiscuous mode. Also enable reception of * runts and packets with CRC & alignment errors. */ /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_PRO | ED_RCR_AM | ED_RCR_AB | ED_RCR_AR | ED_RCR_SEP); } else { /* set up multicast addresses and filter modes */ if (ifp->if_flags & IFF_MULTICAST) { u_long mcaf[2]; if (ifp->if_flags & IFF_ALLMULTI) { mcaf[0] = 0xffffffff; mcaf[1] = 0xffffffff; } else ds_getmcaf(sc, mcaf); /* * Set multicast filter on chip. */ #ifdef PC98 for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR(i), ((u_char *) mcaf)[i]); #else for (i = 0; i < 8; i++) outb(sc->nic_addr + ED_P1_MAR0 + i, ((u_char *) mcaf)[i]); #endif /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AM | ED_RCR_AB); } else { /* * Initialize multicast address hashing registers to * not accept multicasts. */ #ifdef PC98 for (i = 0; i < 8; ++i) outb(sc->nic_addr + ED_P1_MAR(i), 0x00); #else for (i = 0; i < 8; ++i) outb(sc->nic_addr + ED_P1_MAR0 + i, 0x00); #endif /* Set page 0 registers */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STP); outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AB); } } /* * Start interface. */ outb(sc->nic_addr + ED_P0_CR, sc->cr_proto | ED_CR_STA); } /* * Compute crc for ethernet address */ static u_long ds_crc(ep) u_char *ep; { #define POLYNOMIAL 0x04c11db6 register u_long crc = 0xffffffffL; register int carry, i, j; register u_char b; for (i = 6; --i >= 0;) { b = *ep++; for (j = 8; --j >= 0;) { carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); crc <<= 1; b >>= 1; if (carry) crc = ((crc ^ POLYNOMIAL) | carry); } } return crc; #undef POLYNOMIAL } /* * Compute the multicast address filter from the * list of multicast addresses we need to listen to. */ static void ds_getmcaf(sc, mcaf) struct ed_softc *sc; u_long *mcaf; { register u_int index; register u_char *af = (u_char *) mcaf; struct ifmultiaddr *ifma; mcaf[0] = 0; mcaf[1] = 0; for (ifma = sc->arpcom.ac_if.if_multiaddrs.lh_first; ifma; ifma = ifma->ifma_link.le_next) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; index = ds_crc(LLADDR((struct sockaddr_dl *)ifma->ifma_addr)) >> 26; af[index >> 3] |= 1 << (index & 7); } } diff --git a/sys/pc98/pc98/if_fe.c b/sys/pc98/pc98/if_fe.c index 7bf336bc96f0..fe376cc55e76 100644 --- a/sys/pc98/pc98/if_fe.c +++ b/sys/pc98/pc98/if_fe.c @@ -1,3827 +1,3827 @@ /* * All Rights Reserved, Copyright (C) Fujitsu Limited 1995 * * This software may be used, modified, copied, distributed, and sold, in * both source and binary form provided that the above copyright, these * terms and the following disclaimer are retained. The name of the author * and/or the contributor 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 AND THE CONTRIBUTOR ``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 OR THE CONTRIBUTOR 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. */ /* - * $Id: if_fe.c,v 1.25 1997/11/03 02:28:39 kato Exp $ + * $Id: if_fe.c,v 1.26 1997/11/07 12:53:59 kato Exp $ * * Device driver for Fujitsu MB86960A/MB86965A based Ethernet cards. * To be used with FreeBSD 2.x * Contributed by M. Sekiguchi. * * This version is intended to be a generic template for various * MB86960A/MB86965A based Ethernet cards. It currently supports * Fujitsu FMV-180 series for ISA and Allied-Telesis AT1700/RE2000 * series for ISA, as well as Fujitsu MBH10302 PC card. * There are some currently- * unused hooks embedded, which are primarily intended to support * other types of Ethernet cards, but the author is not sure whether * they are useful. * * This version also includes some alignments for * RE1000/RE1000+/ME1500 support. It is incomplete, however, since the * cards are not for AT-compatibles. (They are for PC98 bus -- a * proprietary bus architecture available only in Japan.) Further * work for PC98 version will be available as a part of FreeBSD(98) * project. * * This software is a derivative work of if_ed.c version 1.56 by David * Greenman available as a part of FreeBSD 2.0 RELEASE source distribution. * * The following lines are retained from the original if_ed.c: * * Copyright (C) 1993, David Greenman. This software may be used, modified, * copied, distributed, and sold, in both source and binary form provided * that the above copyright and these terms are retained. Under no * circumstances is the author responsible for the proper functioning * of this software, nor does the author assume any responsibility * for damages incurred with its use. */ /* * Modified for Allied-Telesis RE1000 series. */ /* * TODO: * o To support MBH10304 PC card. It is another MB8696x based * PCMCIA Ethernet card by Fujitsu, which is not compatible with * MBH10302. * o To merge FreeBSD(98) efforts into a single source file. * o To support ISA PnP auto configuration for FMV-183/184. * o To reconsider mbuf usage. * o To reconsider transmission buffer usage, including * transmission buffer size (currently 4KB x 2) and pros-and- * cons of multiple frame transmission. * o To test IPX codes. */ #include "fe.h" #include "bpfilter.h" #include #include #include #include #include #include #include #include #include #include #ifdef INET #include #include #endif /* IPX code is not tested. FIXME. */ #ifdef IPX #include #include #endif /* To be used with IPv6 package of INRIA. */ #ifdef INET6 /* IPv6 added by shin 96.2.6 */ #include #endif /* XNS code is not tested. FIXME. */ #ifdef NS #include #include #endif #if NBPFILTER > 0 #include #endif #include #include #include /* PCCARD suport */ #include "card.h" #if NCARD > 0 #include -#include +#include #include #include #endif #include #include /* * This version of fe is an ISA device driver. * Override the following macro to adapt it to another bus. * (E.g., PC98.) */ #define DEVICE struct isa_device /* * Default settings for fe driver specific options. * They can be set in config file by "options" statements. */ /* * Debug control. * 0: No debug at all. All debug specific codes are stripped off. * 1: Silent. No debug messages are logged except emergent ones. * 2: Brief. Lair events and/or important information are logged. * 3: Detailed. Logs all information which *may* be useful for debugging. * 4: Trace. All actions in the driver is logged. Super verbose. */ #ifndef FE_DEBUG #define FE_DEBUG 1 #endif /* * Transmit just one packet per a "send" command to 86960. * This option is intended for performance test. An EXPERIMENTAL option. */ #ifndef FE_SINGLE_TRANSMISSION #define FE_SINGLE_TRANSMISSION 0 #endif /* * Device configuration flags. */ /* DLCR6 settings. */ #define FE_FLAGS_DLCR6_VALUE 0x007F /* Force DLCR6 override. */ #define FE_FLAGS_OVERRIDE_DLCR6 0x0080 /* Shouldn't these be defined somewhere else such as isa_device.h? */ #define NO_IOADDR (-1) #define NO_IRQ 0 /* * Data type for a multicast address filter on 8696x. */ struct fe_filter { u_char data [ FE_FILTER_LEN ]; }; /* * Special filter values. */ static struct fe_filter const fe_filter_nothing = { FE_FILTER_NOTHING }; static struct fe_filter const fe_filter_all = { FE_FILTER_ALL }; /* How many registers does an fe-supported adapter have at maximum? */ #define MAXREGISTERS 32 /* * fe_softc: per line info and status */ static struct fe_softc { /* Used by "common" codes. */ struct arpcom arpcom; /* Ethernet common */ /* Used by config codes. */ /* Set by probe() and not modified in later phases. */ char * typestr; /* printable name of the interface. */ u_short iobase; /* base I/O address of the adapter. */ u_short ioaddr [ MAXREGISTERS ]; /* I/O addresses of register. */ u_short txb_size; /* size of TX buffer, in bytes */ u_char proto_dlcr4; /* DLCR4 prototype. */ u_char proto_dlcr5; /* DLCR5 prototype. */ u_char proto_dlcr6; /* DLCR6 prototype. */ u_char proto_dlcr7; /* DLCR7 prototype. */ u_char proto_bmpr13; /* BMPR13 prototype. */ u_char proto_bmpr14; /* BMPR14 prototype. */ /* Vendor specific hooks. */ void ( * init )( struct fe_softc * ); /* Just before fe_init(). */ void ( * stop )( struct fe_softc * ); /* Just after fe_stop(). */ /* Transmission buffer management. */ u_short txb_free; /* free bytes in TX buffer */ u_char txb_count; /* number of packets in TX buffer */ u_char txb_sched; /* number of scheduled packets */ /* Excessive collision counter (see fe_tint() for details. */ u_char tx_excolls; /* # of excessive collisions. */ /* Multicast address filter management. */ u_char filter_change; /* MARs must be changed ASAP. */ struct fe_filter filter;/* new filter value. */ } fe_softc[NFE]; #define sc_if arpcom.ac_if #define sc_unit arpcom.ac_if.if_unit #define sc_enaddr arpcom.ac_enaddr /* Standard driver entry points. These can be static. */ static int fe_probe ( struct isa_device * ); static int fe_attach ( struct isa_device * ); static void fe_init ( int ); static int fe_ioctl ( struct ifnet *, int, caddr_t ); static void fe_start ( struct ifnet * ); static void fe_reset ( int ); static void fe_watchdog ( struct ifnet * ); /* Local functions. Order of declaration is confused. FIXME. */ #ifdef PC98 static int fe_probe_re1000 ( DEVICE *, struct fe_softc * ); static int fe_probe_re1000p( DEVICE *, struct fe_softc * ); static int fe_probe_cnet9ne ( DEVICE *, struct fe_softc * ); static int fe_probe_cnet98p2( DEVICE *, struct fe_softc * ); #else static int fe_probe_fmv ( DEVICE *, struct fe_softc * ); static int fe_probe_ati ( DEVICE *, struct fe_softc * ); static void fe_init_ati ( struct fe_softc * ); #endif /* PC98 */ static int fe_probe_gwy ( DEVICE *, struct fe_softc * ); #if NCARD > 0 static int fe_probe_mbh ( DEVICE *, struct fe_softc * ); static void fe_init_mbh ( struct fe_softc * ); static int fe_probe_tdk ( DEVICE *, struct fe_softc * ); #endif static int fe_get_packet ( struct fe_softc *, u_short ); static void fe_stop ( int ); static void fe_tint ( struct fe_softc *, u_char ); static void fe_rint ( struct fe_softc *, u_char ); static void fe_xmit ( struct fe_softc * ); static void fe_emptybuffer ( struct fe_softc * ); static void fe_write_mbufs ( struct fe_softc *, struct mbuf * ); static struct fe_filter fe_mcaf ( struct fe_softc * ); static int fe_hash ( u_char * ); static void fe_setmode ( struct fe_softc * ); static void fe_loadmar ( struct fe_softc * ); #if FE_DEBUG >= 1 static void fe_dump ( int, struct fe_softc *, char * ); #endif /* Driver struct used in the config code. This must be public (external.) */ struct isa_driver fedriver = { fe_probe, fe_attach, "fe", 1 /* It's safe to mark as "sensitive" */ }; /* * Fe driver specific constants which relate to 86960/86965. */ /* Interrupt masks */ #define FE_TMASK ( FE_D2_COLL16 | FE_D2_TXDONE ) #define FE_RMASK ( FE_D3_OVRFLO | FE_D3_CRCERR \ | FE_D3_ALGERR | FE_D3_SRTPKT | FE_D3_PKTRDY ) /* Maximum number of iterations for a receive interrupt. */ #define FE_MAX_RECV_COUNT ( ( 65536 - 2048 * 2 ) / 64 ) /* * Maximum size of SRAM is 65536, * minimum size of transmission buffer in fe is 2x2KB, * and minimum amount of received packet including headers * added by the chip is 64 bytes. * Hence FE_MAX_RECV_COUNT is the upper limit for number * of packets in the receive buffer. */ /* * Routines to access contiguous I/O ports. */ static void inblk ( struct fe_softc * sc, int offs, u_char * mem, int len ) { while ( --len >= 0 ) { *mem++ = inb( sc->ioaddr[ offs++ ] ); } } static void outblk ( struct fe_softc * sc, int offs, u_char const * mem, int len ) { while ( --len >= 0 ) { outb( sc->ioaddr[ offs++ ], *mem++ ); } } /* PCCARD Support */ #if NCARD > 0 /* * PC-Card (PCMCIA) specific code. */ static int feinit(struct pccard_devinfo *); /* init device */ static void feunload(struct pccard_devinfo *); /* Disable driver */ static int fe_card_intr(struct pccard_devinfo *); /* Interrupt handler */ static struct pccard_device fe_info = { "fe", feinit, feunload, fe_card_intr, 0, /* Attributes - presently unused */ &net_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; DATA_SET(pccarddrv_set, fe_info); /* * Initialize the device - called from Slot manager. */ static int feinit(struct pccard_devinfo *devi) { struct fe_softc *sc; /* validate unit number. */ if (devi->isahd.id_unit >= NFE) return (ENODEV); /* * Probe the device. If a value is returned, * the device was found at the location. */ #if FE_DEBUG >= 2 printf("Start Probe\n"); #endif /* Initialize "minimum" parts of our softc. */ sc = &fe_softc[devi->isahd.id_unit]; sc->sc_unit = devi->isahd.id_unit; sc->iobase = devi->isahd.id_iobase; /* Use Ethernet address got from CIS, if one is available. */ if ((devi->misc[0] & 0x03) == 0x00 && (devi->misc[0] | devi->misc[1] | devi->misc[2]) != 0) { /* Yes, it looks like a valid Ether address. */ bcopy(devi->misc, sc->sc_enaddr, ETHER_ADDR_LEN); } else { /* Indicate we have no Ether address in CIS. */ bzero(sc->sc_enaddr, ETHER_ADDR_LEN); } /* Probe supported PC card models. */ if (fe_probe_tdk(&devi->isahd, sc) == 0 && fe_probe_mbh(&devi->isahd, sc) == 0) return (ENXIO); #if FE_DEBUG >= 2 printf("Start attach\n"); #endif if (fe_attach(&devi->isahd) == 0) return (ENXIO); return (0); } /* * feunload - 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 feunload(struct pccard_devinfo *devi) { struct fe_softc *sc = &fe_softc[devi->isahd.id_unit]; printf("fe%d: unload\n", devi->isahd.id_unit); fe_stop(devi->isahd.id_unit); } /* * fe_card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int fe_card_intr(struct pccard_devinfo *devi) { feintr(devi->isahd.id_unit); return (1); } #endif /* NCARD > 0 */ /* * Hardware probe routines. */ /* How and where to probe; to support automatic I/O address detection. */ struct fe_probe_list { int ( * probe ) ( DEVICE *, struct fe_softc * ); u_short const * addresses; }; /* Lists of possible addresses. */ #ifdef PC98 static u_short const fe_re1000_addr [] = { 0x0D0, 0x0D2, 0x0D4, 0x0D6, 0x0D8, 0x0DA, 0x0DC, 0x0DE, 0x1D0, 0x1D2, 0x1D4, 0x1D6, 0x1D8, 0x1DA, 0x1DC, 0x1DE, 0 }; static u_short const fe_re1000p_addr [] = { 0x0D0, 0x0D2, 0x0D4, 0x0D8, 0x1D4, 0x1D6, 0x1D8, 0x1DA, 0 }; static u_short const fe_cnet9ne_addr [] = { 0x73D0, 0 }; static u_short const fe_cnet98p2_addr [] = { 0x03D0, 0x13D0, 0x23D0, 0x33D0, 0x43D0, 0x53D0, 0x63D0, 0x73D0, 0x83D0, 0x93D0, 0xA3D0, 0xB3D0, 0xC3D0, 0xD3D0, 0 }; #else static u_short const fe_fmv_addr [] = { 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x300, 0x340, 0 }; static u_short const fe_ati_addr [] = { 0x240, 0x260, 0x280, 0x2A0, 0x300, 0x320, 0x340, 0x380, 0 }; #endif static struct fe_probe_list const fe_probe_list [] = { #ifdef PC98 { fe_probe_re1000, fe_re1000_addr }, { fe_probe_re1000p, fe_re1000p_addr }, /* XXX: We must probe C-NET(98)P2 after C-NET(9N)E. */ { fe_probe_cnet9ne, fe_cnet9ne_addr }, { fe_probe_cnet98p2, fe_cnet98p2_addr }, #else { fe_probe_fmv, fe_fmv_addr }, { fe_probe_ati, fe_ati_addr }, #endif { fe_probe_gwy, NULL }, /* GWYs cannot be auto detected. */ { NULL, NULL } }; /* * Determine if the device is present * * on entry: * a pointer to an isa_device struct * on exit: * zero if device not found * or number of i/o addresses used (if found) */ static int fe_probe ( DEVICE * dev ) { struct fe_softc * sc; int u; int nports; struct fe_probe_list const * list; u_short const * addr; u_short single [ 2 ]; /* Initialize "minimum" parts of our softc. */ sc = &fe_softc[ dev->id_unit ]; sc->sc_unit = dev->id_unit; /* TODO: Should be in each probe routines */ sc->proto_bmpr14 = 0; /* Probe each possibility, one at a time. */ for ( list = fe_probe_list; list->probe != NULL; list++ ) { if ( dev->id_iobase != NO_IOADDR ) { /* Probe one specific address. */ single[ 0 ] = dev->id_iobase; single[ 1 ] = 0; addr = single; } else if ( list->addresses != NULL ) { /* Auto detect. */ addr = list->addresses; } else { /* We need a list of addresses to do auto detect. */ continue; } /* Probe all possible addresses for the board. */ while ( *addr != 0 ) { /* See if the address is already in use. */ for ( u = 0; u < NFE; u++ ) { if ( fe_softc[u].iobase == *addr ) break; } #if FE_DEBUG >= 3 if ( u == NFE ) { log( LOG_INFO, "fe%d: probing %d at 0x%x\n", sc->sc_unit, list - fe_probe_list, *addr ); } else if ( u == sc->sc_unit ) { log( LOG_INFO, "fe%d: re-probing %d at 0x%x?\n", sc->sc_unit, list - fe_probe_list, *addr ); } else { log( LOG_INFO, "fe%d: skipping %d at 0x%x\n", sc->sc_unit, list - fe_probe_list, *addr ); } #endif /* Probe the address if it is free. */ if ( u == NFE || u == sc->sc_unit ) { /* Probe an address. */ sc->iobase = *addr; nports = list->probe( dev, sc ); if ( nports > 0 ) { /* Found. */ dev->id_iobase = *addr; return ( nports ); } sc->iobase = 0; } /* Try next. */ addr++; } } /* Probe failed. */ return ( 0 ); } /* * Check for specific bits in specific registers have specific values. */ struct fe_simple_probe_struct { u_char port; /* Offset from the base I/O address. */ u_char mask; /* Bits to be checked. */ u_char bits; /* Values to be compared against. */ }; static int fe_simple_probe ( struct fe_softc const * sc, struct fe_simple_probe_struct const * sp ) { struct fe_simple_probe_struct const * p; for ( p = sp; p->mask != 0; p++ ) { #if FE_DEBUG >=2 printf("Probe Port:%x,Value:%x,Mask:%x.Bits:%x\n", p->port,inb(sc->ioaddr[ p->port]),p->mask,p->bits); #endif if ( ( inb( sc->ioaddr[ p->port ] ) & p->mask ) != p->bits ) { return ( 0 ); } } return ( 1 ); } /* * Routines to read all bytes from the config EEPROM through MB86965A. * I'm not sure what exactly I'm doing here... I was told just to follow * the steps, and it worked. Could someone tell me why the following * code works? (Or, why all similar codes I tried previously doesn't * work.) FIXME. */ static void fe_strobe_eeprom ( u_short bmpr16 ) { /* * We must guarantee 800ns (or more) interval to access slow * EEPROMs. The following redundant code provides enough * delay with ISA timing. (Even if the bus clock is "tuned.") * Some modification will be needed on faster busses. */ outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT | FE_B16_CLOCK ); outb( bmpr16, FE_B16_SELECT | FE_B16_CLOCK ); outb( bmpr16, FE_B16_SELECT ); outb( bmpr16, FE_B16_SELECT ); } static void fe_read_eeprom ( struct fe_softc * sc, u_char * data ) { u_short bmpr16 = sc->ioaddr[ FE_BMPR16 ]; u_short bmpr17 = sc->ioaddr[ FE_BMPR17 ]; u_char n, val, bit; /* Read bytes from EEPROM; two bytes per an iteration. */ for ( n = 0; n < FE_EEPROM_SIZE / 2; n++ ) { /* Reset the EEPROM interface. */ outb( bmpr16, 0x00 ); outb( bmpr17, 0x00 ); /* Start EEPROM access. */ outb( bmpr16, FE_B16_SELECT ); outb( bmpr17, FE_B17_DATA ); fe_strobe_eeprom( bmpr16 ); /* Pass the iteration count to the chip. */ val = 0x80 | n; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { outb( bmpr17, ( val & bit ) ? FE_B17_DATA : 0 ); fe_strobe_eeprom( bmpr16 ); } outb( bmpr17, 0x00 ); /* Read a byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { fe_strobe_eeprom( bmpr16 ); if ( inb( bmpr17 ) & FE_B17_DATA ) { val |= bit; } } *data++ = val; /* Read one more byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { fe_strobe_eeprom( bmpr16 ); if ( inb( bmpr17 ) & FE_B17_DATA ) { val |= bit; } } *data++ = val; } /* Reset the EEPROM interface, again. */ outb( bmpr16, 0x00 ); outb( bmpr17, 0x00 ); #if FE_DEBUG >= 3 /* Report what we got. */ data -= FE_EEPROM_SIZE; log( LOG_INFO, "fe%d: EEPROM:" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x\n", sc->sc_unit, data[ 0], data[ 1], data[ 2], data[ 3], data[ 4], data[ 5], data[ 6], data[ 7], data[ 8], data[ 9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31] ); #endif } /* * Hardware (vendor) specific probe routines. */ #ifdef PC98 /* * Probe and initialization for Allied-Telesis RE1000 series. */ static int fe_probe_re1000 ( DEVICE * isa_dev, struct fe_softc * sc ) { int i, n; int dlcr6, dlcr7; u_char c = 0; static u_short const irqmap [ 4 ] = { IRQ3, IRQ5, IRQ6, IRQ12 }; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: probe (0x%x) for RE1000\n", sc->sc_unit, sc->iobase ); fe_dump( LOG_INFO, sc, NULL ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + (i/2)*0x200 + (i%2); } /* * RE1000 does not use 86965 EEPROM interface. */ c ^= sc->sc_enaddr[0] = inb(sc->ioaddr[FE_RE1000_MAC0]); c ^= sc->sc_enaddr[1] = inb(sc->ioaddr[FE_RE1000_MAC1]); c ^= sc->sc_enaddr[2] = inb(sc->ioaddr[FE_RE1000_MAC2]); c ^= sc->sc_enaddr[3] = inb(sc->ioaddr[FE_RE1000_MAC3]); c ^= sc->sc_enaddr[4] = inb(sc->ioaddr[FE_RE1000_MAC4]); c ^= sc->sc_enaddr[5] = inb(sc->ioaddr[FE_RE1000_MAC5]); c ^= inb(sc->ioaddr[FE_RE1000_MACCHK]); if (c != 0) return 0; if ( sc->sc_enaddr[ 0 ] != 0x00 || sc->sc_enaddr[ 1 ] != 0x00 || sc->sc_enaddr[ 2 ] != 0xF4 ) return 0; /* * check interrupt configure */ for (n=0; n<4; n++) { if (isa_dev->id_irq == irqmap[n]) break; } if (n == 4) return 0; /* * set irq */ c = inb(sc->ioaddr[FE_RE1000_IRQCONF]); c &= (~ FE_RE1000_IRQCONF_IRQ); c |= (1 << (n + FE_RE1000_IRQCONF_IRQSHIFT)); outb(sc->ioaddr[FE_RE1000_IRQCONF], c); sc->typestr = "RE1000"; /* * Program the 86965 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; /* FIXME */ sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "RE1000 found" ); #endif /* Initialize 86965. */ outb( sc->ioaddr[FE_DLCR6], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY(200); /* Disable all interrupts. */ outb( sc->ioaddr[FE_DLCR2], 0 ); outb( sc->ioaddr[FE_DLCR3], 0 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of fe_probe_re1000()" ); #endif /* * That's all. RE1000 occupies 2*16 I/O addresses, by the way. */ return 2; /* ??? */ } /* * Probe and initialization for Allied-Telesis RE1000Plus/ME1500 series. */ static int fe_probe_re1000p ( DEVICE * isa_dev, struct fe_softc * sc ) { int i, n, signature; int dlcr6, dlcr7; u_char eeprom [ FE_EEPROM_SIZE ]; static u_short const irqmap [ 4 ] = { IRQ3, IRQ5, IRQ6, IRQ12 }; static struct fe_simple_probe_struct const probe_signature1 [] = { { FE_DLCR0, 0xBF, 0x00 }, { FE_DLCR2, 0xFF, 0x00 }, { FE_DLCR4, 0x0F, 0x06 }, { FE_DLCR6, 0x0F, 0x06 }, { 0 } }; static struct fe_simple_probe_struct const probe_signature2 [] = { { FE_DLCR1, 0xFF, 0x00 }, { FE_DLCR3, 0xFF, 0x00 }, { FE_DLCR5, 0xFF, 0x41 }, { 0 } }; static struct fe_simple_probe_struct const probe_table [] = { { FE_DLCR2, 0x71, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR5, 0x80, 0x00 }, { 0 } }; static struct fe_simple_probe_struct const vendor_code [] = { { FE_DLCR8, 0xFF, 0x00 }, { FE_DLCR9, 0xFF, 0x00 }, { FE_DLCR10, 0xFF, 0xF4 }, { 0 } }; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: probe (0x%x) for RE1000Plus/ME1500\n", sc->sc_unit, sc->iobase ); fe_dump( LOG_INFO, sc, NULL ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < 16; i++ ) { sc->ioaddr[ i ] = sc->iobase + (i/2)*0x200 + (i%2); } for ( i = 16; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i*0x200 - 0x1000; } /* First, check the "signature" */ signature = 0; if (fe_simple_probe(sc, probe_signature1)) { outb(sc->ioaddr[FE_DLCR6], (inb(sc->ioaddr[FE_DLCR6]) & 0xCF) | 0x16); if (fe_simple_probe(sc, probe_signature2)) signature = 1; } /* * If the "signature" not detected, 86965 *might* be previously * initialized. So, check the Ethernet address here. * * Allied-Telesis uses 00 00 F4 ?? ?? ??. */ if (signature == 0) { /* Simple check */ if (!fe_simple_probe(sc, probe_table)) return 0; /* Disable DLC */ dlcr6 = inb(sc->ioaddr[FE_DLCR6]); outb(sc->ioaddr[FE_DLCR6], dlcr6 | FE_D6_DLC_DISABLE); /* Select register bank for DLCR */ dlcr7 = inb(sc->ioaddr[FE_DLCR7]); outb(sc->ioaddr[FE_DLCR7], dlcr7 & 0xF3 | FE_D7_RBS_DLCR); /* Check the Ethernet address */ if (!fe_simple_probe(sc, vendor_code)) return 0; /* Restore configuration registers */ DELAY(200); outb(sc->ioaddr[FE_DLCR6], dlcr6); outb(sc->ioaddr[FE_DLCR7], dlcr7); } /* * We are now almost sure we have an 86965 at the given * address. So, read EEPROM through 86965. We have to write * into LSI registers to read from EEPROM. I want to avoid it * at this stage, but I cannot test the presense of the chip * any further without reading EEPROM. FIXME. */ fe_read_eeprom( sc, eeprom ); /* Make sure that config info in EEPROM and 86965 agree. */ if ( eeprom[ FE_EEPROM_CONF ] != inb( sc->ioaddr[FE_BMPR19] ) ) { return 0; } /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ bcopy( eeprom + FE_ATI_EEP_ADDR, sc->sc_enaddr, ETHER_ADDR_LEN ); sc->typestr = "RE1000Plus/ME1500"; /* * Read IRQ configuration. */ n = (inb(sc->ioaddr[FE_BMPR19]) & FE_B19_IRQ ) >> FE_B19_IRQ_SHIFT; isa_dev->id_irq = irqmap[n]; /* * Program the 86965 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; /* FIXME */ sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "RE1000Plus/ME1500 found" ); #endif /* Initialize 86965. */ outb( sc->ioaddr[FE_DLCR6], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY(200); /* Disable all interrupts. */ outb( sc->ioaddr[FE_DLCR2], 0 ); outb( sc->ioaddr[FE_DLCR3], 0 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of fe_probe_re1000p()" ); #endif /* * That's all. RE1000Plus/ME1500 occupies 2*16 I/O addresses, by the way. */ return 2; /* ??? */ } /* * Probe and initialization for Contec C-NET(9N)E series. */ /* TODO: Should be in "if_fereg.h" */ #define FE_CNET9NE_INTR 0x10 /* Interrupt Mask? */ #define FE_CNET9NE_MAC0 0x11 /* Station(MAC) address */ #define FE_CNET9NE_MAC1 0x13 #define FE_CNET9NE_MAC2 0x15 #define FE_CNET9NE_MAC3 0x17 #define FE_CNET9NE_MAC4 0x19 #define FE_CNET9NE_MAC5 0x1B /* TODO: Should be in "ic/mb86960.h" */ #define FE_D7_ENDEC 0xC0 /* Encoder/Decoder mode(86960 only) */ #define FE_D7_ENDEC_NORMAL_NICE 0x00 /* Normal NICE */ #define FE_D7_ENDEC_NICE_MONITOR 0x40 /* NICE + Monitor */ #define FE_D7_ENDEC_BYPASS 0x80 /* Encoder/Decoder Bypass */ #define FE_D7_ENDEC_TEST 0xC0 /* Encoder/Decoder Test */ static int fe_probe_cnet9ne ( DEVICE * isa_dev, struct fe_softc * sc ) { int i; u_char c; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: probe (0x%x) for C-NET(9N)E\n", sc->sc_unit, sc->iobase ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < 16; i++ ) { sc->ioaddr[i] = sc->iobase + i; } for ( ; i < MAXREGISTERS; i++ ) { sc->ioaddr[i] = sc->iobase + 0x400 - 16 + i; } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* Get our station address from EEPROM. */ sc->sc_enaddr[0] = inb( sc->ioaddr[FE_CNET9NE_MAC0] ); sc->sc_enaddr[1] = inb( sc->ioaddr[FE_CNET9NE_MAC1] ); sc->sc_enaddr[2] = inb( sc->ioaddr[FE_CNET9NE_MAC2] ); sc->sc_enaddr[3] = inb( sc->ioaddr[FE_CNET9NE_MAC3] ); sc->sc_enaddr[4] = inb( sc->ioaddr[FE_CNET9NE_MAC4] ); sc->sc_enaddr[5] = inb( sc->ioaddr[FE_CNET9NE_MAC5] ); #if 1 /* * Check the Ethernet address here. * * Contec uses 00 80 4C ?? ?? ??. */ if ( sc->sc_enaddr[0] != (u_char)0x00 || sc->sc_enaddr[1] != (u_char)0x80 || sc->sc_enaddr[2] != (u_char)0x4C ) { #else /* * Make sure we got a valid Ethernet address. */ if ( ( sc->sc_enaddr[0] & 0x03 ) != 0x00 /* Multicast or Local address. */ || ( sc->sc_enaddr[0] | sc->sc_enaddr[1] | sc->sc_enaddr[2] ) == 0x00 ) { #endif #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: invalid MAC adrs(%x:%x:%x:%x:%x:%x)\n" , sc->sc_unit , (u_char)sc->sc_enaddr[0], (u_char)sc->sc_enaddr[1] , (u_char)sc->sc_enaddr[2], (u_char)sc->sc_enaddr[3] , (u_char)sc->sc_enaddr[4], (u_char)sc->sc_enaddr[5] ); #endif return 0; } /* See if C-NET(9N)E is on its address. */ if ( inb( sc->ioaddr[FE_DLCR6] ) == (u_char)0xff ) { #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: inb(%x) returns 0xff\n" , sc->sc_unit, sc->ioaddr[FE_DLCR6] ); #endif return 0; } sc->typestr = "C-NET9NE"; /* * Program the 86960 as follows: * SRAM: 64KB, word-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. * Encoder/Decoder mode: Normal NICE. * * 86960 manual says that SRAM access-time can't be configured. * (must be 1) */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = FE_D5_RMTRST; /* reserved bit(must be 1) */ sc->proto_dlcr6 = FE_D6_BUFSIZ_64KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_WORD | FE_D6_SBW_WORD | FE_D6_SRAM; #ifndef CNET9NC sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_ENDEC_NORMAL_NICE; #else sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_ENDEC_BYPASS; #endif sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; sc->proto_bmpr14 = 0; sc->stop = sc->init = NULL; #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "C-NET(9N)E found" ); #endif /* Initialize 86960. */ outb( sc->ioaddr[FE_DLCR6], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); #if 1 /* XXX: Is this really necessary? FIXME. */ c = inb( sc->ioaddr[FE_DLCR1] ); if ( c == (u_char)0xff ) { #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: inb(%x) returns 0xff\n" , sc->sc_unit, sc->ioaddr[FE_DLCR1] ); #endif return 0; } if ( ( c & FE_D1_PKTRDY ) == 0 ) { outb( sc->ioaddr[FE_DLCR1], FE_D1_PKTRDY ); } #endif /* Disable all interrupts. */ outb( sc->ioaddr[FE_DLCR2], 0 ); outb( sc->ioaddr[FE_DLCR3], 0 ); #ifndef CNET9NC /* Enable interrupt? FIXME. */ outb( sc->ioaddr[FE_CNET9NE_INTR], 0x10 ); #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of fe_probe_cnet9ne()" ); #endif /* * XXX: The I/O address range is fragmented in the CNET(9N)E. * "16" is the number of regs at iobase. */ return 16; } /* * Probe and initialization for Contec C-NET(98)P2 series. */ /* * Routines to read all bytes from the config EEPROM through TDK 78Q8377A. * I'm not sure what exactly I'm doing here... I was told just to follow * the steps, and it worked. Could someone tell me why the following * code works? FIXME. */ static void fe_strobe_eeprom_tdk ( u_short bmpr12 ) { outb( bmpr12, 0x10 ); outb( bmpr12, 0x12 ); outb( bmpr12, 0x12 ); outb( bmpr12, 0x16 ); outb( bmpr12, 0x12 | 0x01 ); outb( bmpr12, 0x16 | 0x01 ); outb( bmpr12, 0x12 | 0x01 ); outb( bmpr12, 0x16 | 0x01 ); outb( bmpr12, 0x12 ); outb( bmpr12, 0x16 ); } static void fe_read_eeprom_tdk ( struct fe_softc * sc, u_char * data ) { u_short bmpr12 = sc->ioaddr[FE_DLCR12]; u_char n, val, bit; outb( sc->ioaddr[FE_DLCR6], FE_D6_BBW_WORD | FE_D6_SBW_WORD | FE_D6_DLC_DISABLE ); outb( sc->ioaddr[FE_DLCR7], FE_D7_BYTSWP_LH | FE_D7_RBS_BMPR | FE_D7_RDYPNS | FE_D7_POWER_UP ); /* Read bytes from EEPROM; two bytes per an iteration. */ for ( n = 0; n < FE_EEPROM_SIZE / 2; n++ ) { /* Start EEPROM access. */ fe_strobe_eeprom_tdk( bmpr12 ); /* Pass the iteration count to the chip. */ for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { val = ( n & bit ) ? 0x01 : 0x00; outb( bmpr12, 0x12 | val ); outb( bmpr12, 0x16 | val ); } /* Read a byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { outb( bmpr12, 0x12 ); outb( bmpr12, 0x16 ); if ( inb( bmpr12 ) & 0x01 ) { val |= bit; } } *data++ = val; /* Read one more byte. */ val = 0; for ( bit = 0x80; bit != 0x00; bit >>= 1 ) { outb( bmpr12, 0x12 ); outb( bmpr12, 0x16 ); if ( inb( bmpr12 ) & 0x01 ) { val |= bit; } } *data++ = val; outb( bmpr12, 0x10 ); } /* Reset the EEPROM interface. */ outb( bmpr12, 0x00 ); #if FE_DEBUG >= 3 /* Report what we got. */ data -= FE_EEPROM_SIZE; log( LOG_INFO, "fe%d: EEPROM:" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x -" " %02x%02x%02x%02x %02x%02x%02x%02x\n", sc->sc_unit, data[ 0], data[ 1], data[ 2], data[ 3], data[ 4], data[ 5], data[ 6], data[ 7], data[ 8], data[ 9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31] ); #endif } /* TODO: Should be in "if_fereg.h" */ #define FE_CNET98P2_EEP_IRQ (0x04 * 2 + 1) /* Irq */ #define FE_CNET98P2_EEP_ADDR (0x08 * 2) /* Station(MAC) address */ #define FE_CNET98P2_EEP_DUPLEX (0x0c * 2 + 1) /* Duplex mode */ static int fe_probe_cnet98p2 ( DEVICE * isa_dev, struct fe_softc * sc ) { int i; u_char duplex; u_char eeprom[FE_EEPROM_SIZE]; static u_short const irqmap [] = /* INT0 INT1 INT2 */ { NO_IRQ, NO_IRQ, NO_IRQ, IRQ3, NO_IRQ, IRQ5, IRQ6, NO_IRQ, NO_IRQ, IRQ9, IRQ10, NO_IRQ, IRQ12, IRQ13, NO_IRQ, NO_IRQ }; /* INT3 INT4 INT5 INT6 */ #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: probe (0x%x) for C-NET(98)P2\n", sc->sc_unit, sc->iobase ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < 16; i++ ) { sc->ioaddr[i] = sc->iobase + i; } /* Full unused slots with a safe address. */ for ( ; i < MAXREGISTERS; i++ ) { sc->ioaddr[i] = sc->iobase; } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* See if C-NET(98)P2 is on its address. */ if ( inb( sc->ioaddr[FE_DLCR0] ) == (u_char)0xff ) { #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: inb(%x) returns 0xff\n" , sc->sc_unit, sc->ioaddr[FE_DLCR0] ); #endif return 0; } if ( inb( sc->ioaddr[FE_DLCR6] ) == (u_char)0xff ) { #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: inb(%x) returns 0xff\n" , sc->sc_unit, sc->ioaddr[FE_DLCR6] ); #endif return 0; } /* * We are now almost sure we have a 78Q8377 at the given * address. So, read EEPROM through 78Q8377. We have to write * into LSI registers to read from EEPROM. FIXME. */ fe_read_eeprom_tdk( sc, eeprom ); /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ bcopy( eeprom + FE_CNET98P2_EEP_ADDR, sc->sc_enaddr, ETHER_ADDR_LEN ); #if 1 /* * Check the Ethernet address here. * * Contec uses 00 80 4C ?? ?? ??. */ if ( sc->sc_enaddr[0] != (u_char)0x00 || sc->sc_enaddr[1] != (u_char)0x80 || sc->sc_enaddr[2] != (u_char)0x4C ) { #else /* * Make sure we got a valid Ethernet address. */ if ( ( sc->sc_enaddr[0] & 0x03 ) != 0x00 /* Multicast or Local address. */ || ( sc->sc_enaddr[0] | sc->sc_enaddr[1] | sc->sc_enaddr[2] ) == 0x00 ) { #endif #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: invalid MAC adrs(%x:%x:%x:%x:%x:%x)\n" , sc->sc_unit , (u_char)sc->sc_enaddr[0], (u_char)sc->sc_enaddr[1] , (u_char)sc->sc_enaddr[2], (u_char)sc->sc_enaddr[3] , (u_char)sc->sc_enaddr[4], (u_char)sc->sc_enaddr[5] ); #endif return 0; } /* * Get IRQ configuration from EEPROM. */ isa_dev->id_irq = irqmap[ eeprom[FE_CNET98P2_EEP_IRQ] ]; if ( isa_dev->id_irq == NO_IRQ ) { #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: invalid irq configuration(%d)\n" , sc->sc_unit, eeprom[FE_CNET98P2_EEP_IRQ] ); #endif return 0; } /* * Get Duplex-mode configuration from EEPROM. */ duplex = eeprom[FE_CNET98P2_EEP_DUPLEX] & FE_D4_DSC; sc->typestr = ( duplex ? "CNET98P2(Full duplex)" : "CNET98P2(Half duplex)" ); /* * Program the 78Q8377 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. * XXX: Should we add IDENT_NICE or IDENT_EC to DLCR7? FIXME. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL | duplex; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; sc->proto_bmpr14 = FE_B14_FILTER; sc->stop = sc->init = NULL; #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "C-NET(98)P2 found" ); #endif /* Initialize 78Q8377. */ outb( sc->ioaddr[FE_DLCR6], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[FE_DLCR2], 0 ); outb( sc->ioaddr[FE_DLCR3], 0 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of fe_probe_cnet98p2()" ); #endif /* * That's all. C-NET(98)P2 occupies 16 I/O addresses, as always. */ return 16; } #else /* * Probe and initialization for Fujitsu FMV-180 series boards */ static int fe_probe_fmv ( DEVICE * dev, struct fe_softc * sc ) { int i, n; static u_short const baseaddr [ 8 ] = { 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x300, 0x340 }; static u_short const irqmap [ 4 ] = { IRQ3, IRQ7, IRQ10, IRQ15 }; static struct fe_simple_probe_struct const probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, /* { FE_DLCR5, 0x80, 0x00 }, Doesn't work. */ { FE_FMV0, 0x78, 0x50 }, /* ERRDY+PRRDY */ { FE_FMV1, 0xB0, 0x00 }, /* FMV-183/184 has 0x48 bits. */ { FE_FMV3, 0x7F, 0x00 }, #if 1 /* * Test *vendor* part of the station address for Fujitsu. * The test will gain reliability of probe process, but * it rejects FMV-180 clone boards manufactured by other vendors. * We have to turn the test off when such cards are made available. */ { FE_FMV4, 0xFF, 0x00 }, { FE_FMV5, 0xFF, 0x00 }, { FE_FMV6, 0xFF, 0x0E }, #else /* * We can always verify the *first* 2 bits (in Ethernet * bit order) are "no multicast" and "no local" even for * unknown vendors. */ { FE_FMV4, 0x03, 0x00 }, #endif { 0 } }; /* "Hardware revision ID" */ int revision; /* * See if the specified address is possible for FMV-180 series. */ for ( i = 0; i < 8; i++ ) { if ( baseaddr[ i ] == sc->iobase ) break; } if ( i == 8 ) return 0; /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* Simple probe. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Check if our I/O address matches config info. on EEPROM. */ n = ( inb( sc->ioaddr[ FE_FMV2 ] ) & FE_FMV2_IOS ) >> FE_FMV2_IOS_SHIFT; if ( baseaddr[ n ] != sc->iobase ) { #if 0 /* May not work on some revisions of the cards... FIXME. */ return 0; #else /* Just log the fact and see what happens... FIXME. */ log( LOG_WARNING, "fe%d: strange I/O config?\n", sc->sc_unit ); #endif } /* Find the "hardware revision." */ revision = inb( sc->ioaddr[ FE_FMV1 ] ) & FE_FMV1_REV; /* Determine the card type. */ sc->typestr = NULL; switch ( inb( sc->ioaddr[ FE_FMV0 ] ) & FE_FMV0_MEDIA ) { case 0: /* No interface? This doesn't seem to be an FMV-180... */ return 0; case FE_FMV0_MEDIUM_T: switch ( revision ) { case 8: sc->typestr = "FMV-183"; break; case 12: sc->typestr = "FMV-183 (on-board)"; break; } break; case FE_FMV0_MEDIUM_T | FE_FMV0_MEDIUM_5: switch ( revision ) { case 0: sc->typestr = "FMV-181"; break; case 1: sc->typestr = "FMV-181A"; break; } break; case FE_FMV0_MEDIUM_2: switch ( revision ) { case 8: sc->typestr = "FMV-184 (CSR = 2)"; break; } break; case FE_FMV0_MEDIUM_5: switch ( revision ) { case 8: sc->typestr = "FMV-184 (CSR = 1)"; break; } break; case FE_FMV0_MEDIUM_2 | FE_FMV0_MEDIUM_5: switch ( revision ) { case 0: sc->typestr = "FMV-182"; break; case 1: sc->typestr = "FMV-182A"; break; case 8: sc->typestr = "FMV-184 (CSR = 3)"; break; } break; } if ( sc->typestr == NULL ) { /* Unknown card type... Hope the driver works. */ sc->typestr = "unknown FMV-180 version"; log( LOG_WARNING, "fe%d: %s: %x-%x-%x-%x\n", sc->sc_unit, sc->typestr, inb( sc->ioaddr[ FE_FMV0 ] ), inb( sc->ioaddr[ FE_FMV1 ] ), inb( sc->ioaddr[ FE_FMV2 ] ), inb( sc->ioaddr[ FE_FMV3 ] ) ); } /* * An FMV-180 has been proved. * Determine which IRQ to be used. * * In this version, we give a priority to the kernel config file. * If the EEPROM and config don't match, say it to the user for * an attention. */ n = ( inb( sc->ioaddr[ FE_FMV2 ] ) & FE_FMV2_IRS ) >> FE_FMV2_IRS_SHIFT; if ( dev->id_irq == NO_IRQ ) { /* Just use the probed value. */ dev->id_irq = irqmap[ n ]; } else if ( dev->id_irq != irqmap[ n ] ) { /* Don't match. */ log( LOG_WARNING, "fe%d: check IRQ in config; it may be incorrect\n", sc->sc_unit ); } /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ inblk( sc, FE_FMV4, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; /* * Register values which (may) depend on board design. * * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* * Minimum initialization of the hardware. * We write into registers; hope I/O ports have no * overlap with other boards. */ /* Initialize ASIC. */ outb( sc->ioaddr[ FE_FMV3 ], 0 ); outb( sc->ioaddr[ FE_FMV10 ], 0 ); /* Initialize 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* "Refresh" hardware configuration. FIXME. */ outb( sc->ioaddr[ FE_FMV2 ], inb( sc->ioaddr[ FE_FMV2 ] ) ); /* Turn the "master interrupt control" flag of ASIC on. */ outb( sc->ioaddr[ FE_FMV3 ], FE_FMV3_IRQENB ); /* * That's all. FMV-180 occupies 32 I/O addresses, by the way. */ return 32; } /* * Probe and initialization for Allied-Telesis AT1700/RE2000 series. */ static int fe_probe_ati ( DEVICE * dev, struct fe_softc * sc ) { int i, n; u_char eeprom [ FE_EEPROM_SIZE ]; u_char save16, save17; static u_short const baseaddr [ 8 ] = { 0x260, 0x280, 0x2A0, 0x240, 0x340, 0x320, 0x380, 0x300 }; static u_short const irqmaps [ 4 ][ 4 ] = { { IRQ3, IRQ4, IRQ5, IRQ9 }, { IRQ10, IRQ11, IRQ12, IRQ15 }, { IRQ3, IRQ11, IRQ5, IRQ15 }, { IRQ10, IRQ11, IRQ14, IRQ15 }, }; static struct fe_simple_probe_struct const probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR5, 0x80, 0x00 }, #if 0 { FE_BMPR16, 0x1B, 0x00 }, { FE_BMPR17, 0x7F, 0x00 }, #endif { 0 } }; /* Assume we have 86965 and no need to restore these. */ save16 = 0; save17 = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: probe (0x%x) for ATI\n", sc->sc_unit, sc->iobase ); fe_dump( LOG_INFO, sc, NULL ); #endif /* * See if the specified address is possible for MB86965A JLI mode. */ for ( i = 0; i < 8; i++ ) { if ( baseaddr[ i ] == sc->iobase ) break; } if ( i == 8 ) goto NOTFOUND; /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * We should test if MB86965A is on the base address now. * Unfortunately, it is very hard to probe it reliably, since * we have no way to reset the chip under software control. * On cold boot, we could check the "signature" bit patterns * described in the Fujitsu document. On warm boot, however, * we can predict almost nothing about register values. */ if ( !fe_simple_probe( sc, probe_table ) ) goto NOTFOUND; /* Check if our I/O address matches config info on 86965. */ n = ( inb( sc->ioaddr[ FE_BMPR19 ] ) & FE_B19_ADDR ) >> FE_B19_ADDR_SHIFT; if ( baseaddr[ n ] != sc->iobase ) goto NOTFOUND; /* * We are now almost sure we have an AT1700 at the given * address. So, read EEPROM through 86965. We have to write * into LSI registers to read from EEPROM. I want to avoid it * at this stage, but I cannot test the presence of the chip * any further without reading EEPROM. FIXME. */ save16 = inb( sc->ioaddr[ FE_BMPR16 ] ); save17 = inb( sc->ioaddr[ FE_BMPR17 ] ); fe_read_eeprom( sc, eeprom ); /* Make sure the EEPROM is turned off. */ outb( sc->ioaddr[ FE_BMPR16 ], 0 ); outb( sc->ioaddr[ FE_BMPR17 ], 0 ); /* Make sure that config info in EEPROM and 86965 agree. */ if ( eeprom[ FE_EEPROM_CONF ] != inb( sc->ioaddr[ FE_BMPR19 ] ) ) { goto NOTFOUND; } /* * The following model identification codes are stolen from * from the NetBSD port of the fe driver. My reviewers * suggested minor revision. */ /* Determine the card type. */ switch (eeprom[FE_ATI_EEP_MODEL]) { case FE_ATI_MODEL_AT1700T: sc->typestr = "AT-1700T/RE2001"; break; case FE_ATI_MODEL_AT1700BT: sc->typestr = "AT-1700BT/RE2003"; break; case FE_ATI_MODEL_AT1700FT: sc->typestr = "AT-1700FT/RE2009"; break; case FE_ATI_MODEL_AT1700AT: sc->typestr = "AT-1700AT/RE2005"; break; default: sc->typestr = "unknown AT-1700/RE2000 ?"; break; } /* * Try to determine IRQ settings. * Different models use different ranges of IRQs. */ if ( dev->id_irq == NO_IRQ ) { n = ( inb( sc->ioaddr[ FE_BMPR19 ] ) & FE_B19_IRQ ) >> FE_B19_IRQ_SHIFT; switch ( eeprom[ FE_ATI_EEP_REVISION ] & 0xf0 ) { case 0x30: dev->id_irq = irqmaps[ 3 ][ n ]; break; case 0x10: case 0x50: dev->id_irq = irqmaps[ 2 ][ n ]; break; case 0x40: case 0x60: if ( eeprom[ FE_ATI_EEP_MAGIC ] & 0x04 ) { dev->id_irq = irqmaps[ 1 ][ n ]; } else { dev->id_irq = irqmaps[ 0 ][ n ]; } break; default: dev->id_irq = irqmaps[ 0 ][ n ]; break; } } /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ bcopy( eeprom + FE_ATI_EEP_ADDR, sc->sc_enaddr, ETHER_ADDR_LEN ); #if 1 /* * This test doesn't work well for AT1700 look-alike by * other vendors. */ /* Make sure the vendor part is for Allied-Telesis. */ if ( sc->sc_enaddr[ 0 ] != 0x00 || sc->sc_enaddr[ 1 ] != 0x00 || sc->sc_enaddr[ 2 ] != 0xF4 ) return 0; #else /* Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; #endif /* * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; /* FIXME */ sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_EC; #if 0 /* XXXX Should we use this? FIXME. */ sc->proto_bmpr13 = eeprom[ FE_ATI_EEP_MEDIA ]; #else sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "ATI found" ); #endif /* Setup hooks. This may solves a nasty bug. FIXME. */ sc->init = fe_init_ati; /* Initialize 86965. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of fe_probe_ati()" ); #endif /* * That's all. AT1700 occupies 32 I/O addresses, by the way. */ return 32; NOTFOUND: /* * We have no AT1700 at a given address. * Restore BMPR16 and BMPR17 if we have destroyed them, * hoping that the hardware on the address didn't get * bad side effect. */ if ( save16 != 0 | save17 != 0 ) { outb( sc->ioaddr[ FE_BMPR16 ], save16 ); outb( sc->ioaddr[ FE_BMPR17 ], save17 ); } return ( 0 ); } /* ATI specific initialization routine. */ static void fe_init_ati ( struct fe_softc * sc ) { /* * I've told that the following operation "Resets" the chip. * Hope this solve a bug which hangs up the driver under * heavy load... FIXME. */ /* Minimal initialization of 86965. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* "Reset" by wrting into an undocument register location. */ outb( sc->ioaddr[ 0x1F ], 0 ); /* How long do we have to wait after the reset? FIXME. */ DELAY( 300 ); } #endif /* PC98 */ /* * Probe and initialization for Gateway Communications' old cards. */ static int fe_probe_gwy ( DEVICE * dev, struct fe_softc * sc ) { int i; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR7, 0xC0, 0x00 }, /* * Test *vendor* part of the address for Gateway. * This test is essential to identify Gateway's cards. * We shuld define some symbolic names for the * following offsets. FIXME. */ { 0x18, 0xFF, 0x00 }, { 0x19, 0xFF, 0x00 }, { 0x1A, 0xFF, 0x61 }, { 0 } }; /* * We need explicit IRQ and supported address. * I'm not sure which address and IRQ is possible for Gateway * Ethernet family. The following accepts everything. FIXME. */ if ( dev->id_irq == NO_IRQ || ( sc->iobase & ~0x3E0 ) != 0 ) { return ( 0 ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "top of probe" ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* See if the card is on its address. */ if ( !fe_simple_probe( sc, probe_table ) ) { return 0; } /* Determine the card type. */ sc->typestr = "Gateway Ethernet w/ Fujitsu chipset"; /* Get our station address from EEPROM. */ inblk( sc, 0x18, sc->sc_enaddr, ETHER_ADDR_LEN ); /* * Program the 86960 as follows: * SRAM: 16KB, 100ns, byte-wide access. * Transmission buffer: 2KB x 2. * System bus interface: 16 bits. * Make sure to clear out ID bits in DLCR7 * (They actually are Encoder/Decoder control in NICE.) */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_16KB | FE_D6_TXBSIZ_2x2KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH; sc->proto_bmpr13 = 0; /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* That's all. The card occupies 32 I/O addresses, as always. */ return 32; } #if NCARD > 0 /* * Probe and initialization for Fujitsu MBH10302 PCMCIA Ethernet interface. * Note that this is for 10302 only; MBH10304 is handled by fe_probe_tdk(). */ static int fe_probe_mbh ( DEVICE * dev, struct fe_softc * sc ) { int i,type; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR0, 0x09, 0x00 }, { FE_DLCR2, 0x79, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, { FE_DLCR6, 0xFF, 0xB6 }, /* * The following location has the first byte of the card's * Ethernet (MAC) address. * We can always verify the *first* 2 bits (in Ethernet * bit order) are "global" and "unicast" for any vendors'. */ { FE_MBH10, 0x03, 0x00 }, /* Just a gap? Seems reliable, anyway. */ { 0x12, 0xFF, 0x00 }, { 0x13, 0xFF, 0x00 }, { 0x14, 0xFF, 0x00 }, { 0x15, 0xFF, 0x00 }, { 0x16, 0xFF, 0x00 }, { 0x17, 0xFF, 0x00 }, #if 0 { 0x18, 0xFF, 0xFF }, { 0x19, 0xFF, 0xFF }, #endif { 0 } }; /* * We need explicit IRQ and supported address. */ if ( dev->id_irq == NO_IRQ || ( sc->iobase & ~0x3E0 ) != 0 ) { return ( 0 ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "top of probe" ); #endif /* Setup an I/O address mapping table. */ for ( i = 0; i < MAXREGISTERS; i++ ) { sc->ioaddr[ i ] = sc->iobase + i; } /* * See if MBH10302 is on its address. * I'm not sure the following probe code works. FIXME. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Determine the card type. */ sc->typestr = "MBH10302 (PCMCIA)"; /* * Initialize constants in the per-line structure. */ /* Get our station address from EEPROM. */ inblk( sc, FE_MBH10, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Make sure we got a valid station address. */ if ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) return 0; /* * Program the 86960 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_NICE; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* Setup hooks. We need a special initialization procedure. */ sc->init = fe_init_mbh; /* * Minimum initialization. */ /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); #if 1 /* FIXME. */ /* Initialize system bus interface and encoder/decoder operation. */ outb( sc->ioaddr[ FE_MBH0 ], FE_MBH0_MAGIC | FE_MBH0_INTR_DISABLE ); #endif /* * That's all. MBH10302 occupies 32 I/O addresses, by the way. */ return 32; } /* MBH specific initialization routine. */ static void fe_init_mbh ( struct fe_softc * sc ) { /* Minimal initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* Enable master interrupt flag. */ outb( sc->ioaddr[ FE_MBH0 ], FE_MBH0_MAGIC | FE_MBH0_INTR_ENABLE ); } /* * Probe and initialization for TDK/CONTEC PCMCIA Ethernet interface. * by MASUI Kenji * * (Contec uses TDK Ethenet chip -- hosokawa) * * This version of fe_probe_tdk has been rewrote to handle * *generic* PC card implementation of Fujitsu MB8696x and compatibles. * The name _tdk is just for a historical reason. :-) */ static int fe_probe_tdk ( DEVICE * dev, struct fe_softc * sc ) { int i; static struct fe_simple_probe_struct probe_table [] = { { FE_DLCR2, 0x70, 0x00 }, { FE_DLCR4, 0x08, 0x00 }, /* { FE_DLCR5, 0x80, 0x00 }, Does not work well. */ { 0 } }; /* We need an IRQ. */ if ( dev->id_irq == NO_IRQ ) { return ( 0 ); } /* Generic driver needs Ethernet address taken from CIS. */ if (sc->arpcom.ac_enaddr[0] == 0 && sc->arpcom.ac_enaddr[1] == 0 && sc->arpcom.ac_enaddr[2] == 0) { return 0; } /* Setup an I/O address mapping table; we need only 16 ports. */ for (i = 0; i < 16; i++) { sc->ioaddr[i] = sc->iobase + i; } /* Fill unused slots with a safe address. */ for (i = 16; i < MAXREGISTERS; i++) { sc->ioaddr[i] = sc->iobase; } /* * See if C-NET(PC)C is on its address. */ if ( !fe_simple_probe( sc, probe_table ) ) return 0; /* Determine the card type. */ sc->typestr = "Generic MB8696x Ethernet (PCMCIA)"; /* * Initialize constants in the per-line structure. */ /* The station address *must*be* already in sc_enaddr; Make sure we got a valid station address. */ if ( ( sc->sc_enaddr[ 0 ] & 0x03 ) != 0x00 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) return 0; /* * Program the 86965 as follows: * SRAM: 32KB, 100ns, byte-wide access. * Transmission buffer: 4KB x 2. * System bus interface: 16 bits. * XXX: Should we remove IDENT_NICE from DLCR7? Or, * even add IDENT_EC instead? FIXME. */ sc->proto_dlcr4 = FE_D4_LBC_DISABLE | FE_D4_CNTRL; sc->proto_dlcr5 = 0; sc->proto_dlcr6 = FE_D6_BUFSIZ_32KB | FE_D6_TXBSIZ_2x4KB | FE_D6_BBW_BYTE | FE_D6_SBW_WORD | FE_D6_SRAM_100ns; sc->proto_dlcr7 = FE_D7_BYTSWP_LH | FE_D7_IDENT_NICE; sc->proto_bmpr13 = FE_B13_TPTYPE_UTP | FE_B13_PORT_AUTO; /* Minimul initialization of 86960. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Disable all interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0 ); outb( sc->ioaddr[ FE_DLCR3 ], 0 ); /* * That's all. C-NET(PC)C occupies 16 I/O addresses. * * Some PC cards (e.g., TDK and Contec) have 16 I/O addresses, * while some others (e.g., Fujitsu) have 32. Fortunately, * this generic driver never accesses latter 16 ports in 32 * ports cards. So, we can assume the *generic* PC cards * always have 16 ports. * * Moreover, PC card probe is isolated from ISA probe, and PC * card probe routine doesn't use "# of ports" returned by this * function. 16 v.s. 32 is not important now. */ return 16; } #endif /* NCARD > 0 */ /* * Install interface into kernel networking data structures */ static int fe_attach ( DEVICE * dev ) { #if NCARD > 0 static int already_ifattach[NFE]; #endif struct fe_softc *sc = &fe_softc[dev->id_unit]; /* * Initialize ifnet structure */ sc->sc_if.if_softc = sc; sc->sc_if.if_unit = sc->sc_unit; sc->sc_if.if_name = "fe"; sc->sc_if.if_output = ether_output; sc->sc_if.if_start = fe_start; sc->sc_if.if_ioctl = fe_ioctl; sc->sc_if.if_watchdog = fe_watchdog; /* * Set default interface flags. */ sc->sc_if.if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; /* * Set maximum size of output queue, if it has not been set. * It is done here as this driver may be started after the * system initialization (i.e., the interface is PCMCIA.) * * I'm not sure this is really necessary, but, even if it is, * it should be done somewhere else, e.g., in if_attach(), * since it must be a common workaround for all network drivers. * FIXME. */ if ( sc->sc_if.if_snd.ifq_maxlen == 0 ) { sc->sc_if.if_snd.ifq_maxlen = ifqmaxlen; } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "attach()" ); #endif #if FE_SINGLE_TRANSMISSION /* Override txb config to allocate minimum. */ sc->proto_dlcr6 &= ~FE_D6_TXBSIZ sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB; #endif /* Modify hardware config if it is requested. */ if ( dev->id_flags & FE_FLAGS_OVERRIDE_DLCR6 ) { sc->proto_dlcr6 = dev->id_flags & FE_FLAGS_DLCR6_VALUE; } /* Find TX buffer size, based on the hardware dependent proto. */ switch ( sc->proto_dlcr6 & FE_D6_TXBSIZ ) { case FE_D6_TXBSIZ_2x2KB: sc->txb_size = 2048; break; case FE_D6_TXBSIZ_2x4KB: sc->txb_size = 4096; break; case FE_D6_TXBSIZ_2x8KB: sc->txb_size = 8192; break; default: /* Oops, we can't work with single buffer configuration. */ #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: strange TXBSIZ config; fixing\n", sc->sc_unit ); #endif sc->proto_dlcr6 &= ~FE_D6_TXBSIZ; sc->proto_dlcr6 |= FE_D6_TXBSIZ_2x2KB; sc->txb_size = 2048; break; } /* Attach and stop the interface. */ #if NCARD > 0 if (already_ifattach[dev->id_unit] != 1) { if_attach(&sc->sc_if); already_ifattach[dev->id_unit] = 1; } #else if_attach(&sc->sc_if); #endif fe_stop(sc->sc_unit); /* This changes the state to IDLE. */ ether_ifattach(&sc->sc_if); /* Print additional info when attached. */ printf( "fe%d: address %6D, type %s\n", sc->sc_unit, sc->sc_enaddr, ":" , sc->typestr ); #if FE_DEBUG >= 3 { int buf, txb, bbw, sbw, ram; buf = txb = bbw = sbw = ram = -1; switch ( sc->proto_dlcr6 & FE_D6_BUFSIZ ) { case FE_D6_BUFSIZ_8KB: buf = 8; break; case FE_D6_BUFSIZ_16KB: buf = 16; break; case FE_D6_BUFSIZ_32KB: buf = 32; break; case FE_D6_BUFSIZ_64KB: buf = 64; break; } switch ( sc->proto_dlcr6 & FE_D6_TXBSIZ ) { case FE_D6_TXBSIZ_2x2KB: txb = 2; break; case FE_D6_TXBSIZ_2x4KB: txb = 4; break; case FE_D6_TXBSIZ_2x8KB: txb = 8; break; } switch ( sc->proto_dlcr6 & FE_D6_BBW ) { case FE_D6_BBW_BYTE: bbw = 8; break; case FE_D6_BBW_WORD: bbw = 16; break; } switch ( sc->proto_dlcr6 & FE_D6_SBW ) { case FE_D6_SBW_BYTE: sbw = 8; break; case FE_D6_SBW_WORD: sbw = 16; break; } switch ( sc->proto_dlcr6 & FE_D6_SRAM ) { case FE_D6_SRAM_100ns: ram = 100; break; case FE_D6_SRAM_150ns: ram = 150; break; } printf( "fe%d: SRAM %dKB %dbit %dns, TXB %dKBx2, %dbit I/O\n", sc->sc_unit, buf, bbw, ram, txb, sbw ); } #endif #if NBPFILTER > 0 /* If BPF is in the kernel, call the attach for it. */ bpfattach( &sc->sc_if, DLT_EN10MB, sizeof(struct ether_header)); #endif return 1; } /* * Reset interface. */ static void fe_reset ( int unit ) { /* * Stop interface and re-initialize. */ fe_stop(unit); fe_init(unit); } /* * Stop everything on the interface. * * All buffered packets, both transmitting and receiving, * if any, will be lost by stopping the interface. */ static void fe_stop ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; int s; s = splimp(); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "stop()" ); #endif /* Disable interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR3 ], 0x00 ); /* Stop interface hardware. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Clear all interrupt status. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* Put the chip in stand-by mode. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_POWER_DOWN ); DELAY( 200 ); /* Reset transmitter variables and interface flags. */ sc->sc_if.if_flags &= ~( IFF_OACTIVE | IFF_RUNNING ); sc->sc_if.if_timer = 0; sc->txb_free = sc->txb_size; sc->txb_count = 0; sc->txb_sched = 0; /* MAR loading can be delayed. */ sc->filter_change = 0; /* Update config status also. */ /* Call a hook. */ if ( sc->stop ) sc->stop( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "end of stop()" ); #endif (void) splx(s); } /* * Device timeout/watchdog routine. Entered if the device neglects to * generate an interrupt after a transmit has been started on it. */ static void fe_watchdog ( struct ifnet *ifp ) { struct fe_softc *sc = (struct fe_softc *)ifp; #if FE_DEBUG >= 1 /* A "debug" message. */ log( LOG_ERR, "fe%d: transmission timeout (%d+%d)%s\n", ifp->if_unit, sc->txb_sched, sc->txb_count, ( ifp->if_flags & IFF_UP ) ? "" : " when down" ); if ( sc->sc_if.if_opackets == 0 && sc->sc_if.if_ipackets == 0 ) { log( LOG_WARNING, "fe%d: wrong IRQ setting in config?\n", ifp->if_unit ); } #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* Record how many packets are lost by this accident. */ ifp->if_oerrors += sc->txb_sched + sc->txb_count; /* Put the interface into known initial state. */ if ( ifp->if_flags & IFF_UP ) { fe_reset( ifp->if_unit ); } else { fe_stop( ifp->if_unit ); } } /* * Initialize device. */ static void fe_init ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; int s; #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "init()" ); #endif /* We need an address. */ if (TAILQ_EMPTY(&sc->sc_if.if_addrhead)) { /* XXX unlikely */ #if FE_DEBUG >= 1 log( LOG_ERR, "fe%d: init() without any address\n", sc->sc_unit ); #endif return; } #if FE_DEBUG >= 1 /* * Make sure we have a valid station address. * The following test is applicable for any Ethernet interfaces. * It can be done in somewhere common to all of them. FIXME. */ if ( ( sc->sc_enaddr[ 0 ] & 0x01 ) != 0 || ( sc->sc_enaddr[ 0 ] == 0x00 && sc->sc_enaddr[ 1 ] == 0x00 && sc->sc_enaddr[ 2 ] == 0x00 ) ) { log( LOG_ERR, "fe%d: invalid station address (%6D)\n", sc->sc_unit, sc->sc_enaddr, ":" ); return; } #endif /* Start initializing 86960. */ s = splimp(); /* Call a hook. */ if ( sc->init ) sc->init( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after init hook" ); #endif /* * Make sure to disable the chip, also. * This may also help re-programming the chip after * hot insertion of PCMCIAs. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Power up the chip and select register bank for DLCRs. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_DLCR | FE_D7_POWER_UP ); DELAY( 200 ); /* Feed the station address. */ outblk( sc, FE_DLCR8, sc->sc_enaddr, ETHER_ADDR_LEN ); /* Clear multicast address filter to receive nothing. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_MAR | FE_D7_POWER_UP ); outblk( sc, FE_MAR8, fe_filter_nothing.data, FE_FILTER_LEN ); /* Select the BMPR bank for runtime register access. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP ); /* Initialize registers. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); /* Clear all bits. */ outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* ditto. */ outb( sc->ioaddr[ FE_DLCR2 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR3 ], 0x00 ); outb( sc->ioaddr[ FE_DLCR4 ], sc->proto_dlcr4 ); outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 ); outb( sc->ioaddr[ FE_BMPR10 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR11 ], FE_B11_CTRL_SKIP | FE_B11_MODE1 ); outb( sc->ioaddr[ FE_BMPR12 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR13 ], sc->proto_bmpr13 ); outb( sc->ioaddr[ FE_BMPR14 ], 0x00 ); outb( sc->ioaddr[ FE_BMPR15 ], 0x00 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "just before enabling DLC" ); #endif /* Enable interrupts. */ outb( sc->ioaddr[ FE_DLCR2 ], FE_TMASK ); outb( sc->ioaddr[ FE_DLCR3 ], FE_RMASK ); /* Enable transmitter and receiver. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_ENABLE ); DELAY( 200 ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "just after enabling DLC" ); #endif /* * Make sure to empty the receive buffer. * * This may be redundant, but *if* the receive buffer were full * at this point, then the driver would hang. I have experienced * some strange hang-up just after UP. I hope the following * code solve the problem. * * I have changed the order of hardware initialization. * I think the receive buffer cannot have any packets at this * point in this version. The following code *must* be * redundant now. FIXME. * * I've heard a rumore that on some PC card implementation of * 8696x, the receive buffer can have some data at this point. * The following message helps discovering the fact. FIXME. */ if ( !( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) ) { log( LOG_WARNING, "fe%d: receive buffer has some data after reset\n", sc->sc_unit ); fe_emptybuffer( sc ); } #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after ERB loop" ); #endif /* Do we need this here? Actually, no. I must be paranoia. */ outb( sc->ioaddr[ FE_DLCR0 ], 0xFF ); /* Clear all bits. */ outb( sc->ioaddr[ FE_DLCR1 ], 0xFF ); /* ditto. */ #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after FIXME" ); #endif /* Set 'running' flag, because we are now running. */ sc->sc_if.if_flags |= IFF_RUNNING; /* * At this point, the interface is running properly, * except that it receives *no* packets. we then call * fe_setmode() to tell the chip what packets to be * received, based on the if_flags and multicast group * list. It completes the initialization process. */ fe_setmode( sc ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "after setmode" ); #endif /* ...and attempt to start output queued packets. */ fe_start( &sc->sc_if ); #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, "init() done" ); #endif (void) splx(s); } /* * This routine actually starts the transmission on the interface */ static void fe_xmit ( struct fe_softc * sc ) { /* * Set a timer just in case we never hear from the board again. * We use longer timeout for multiple packet transmission. * I'm not sure this timer value is appropriate. FIXME. */ sc->sc_if.if_timer = 1 + sc->txb_count; /* Update txb variables. */ sc->txb_sched = sc->txb_count; sc->txb_count = 0; sc->txb_free = sc->txb_size; sc->tx_excolls = 0; /* Start transmitter, passing packets in TX buffer. */ outb( sc->ioaddr[ FE_BMPR10 ], sc->txb_sched | FE_B10_START ); } /* * Start output on interface. * We make two assumptions here: * 1) that the current priority is set to splimp _before_ this code * is called *and* is returned to the appropriate priority after * return * 2) that the IFF_OACTIVE flag is checked before this code is called * (i.e. that the output part of the interface is idle) */ void fe_start ( struct ifnet *ifp ) { struct fe_softc *sc = ifp->if_softc; struct mbuf *m; #if FE_DEBUG >= 1 /* Just a sanity check. */ if ( ( sc->txb_count == 0 ) != ( sc->txb_free == sc->txb_size ) ) { /* * Txb_count and txb_free co-works to manage the * transmission buffer. Txb_count keeps track of the * used potion of the buffer, while txb_free does unused * potion. So, as long as the driver runs properly, * txb_count is zero if and only if txb_free is same * as txb_size (which represents whole buffer.) */ log( LOG_ERR, "fe%d: inconsistent txb variables (%d, %d)\n", sc->sc_unit, sc->txb_count, sc->txb_free ); /* * So, what should I do, then? * * We now know txb_count and txb_free contradicts. We * cannot, however, tell which is wrong. More * over, we cannot peek 86960 transmission buffer or * reset the transmission buffer. (In fact, we can * reset the entire interface. I don't want to do it.) * * If txb_count is incorrect, leaving it as-is will cause * sending of garbage after next interrupt. We have to * avoid it. Hence, we reset the txb_count here. If * txb_free was incorrect, resetting txb_count just loose * some packets. We can live with it. */ sc->txb_count = 0; } #endif #if FE_DEBUG >= 1 /* * First, see if there are buffered packets and an idle * transmitter - should never happen at this point. */ if ( ( sc->txb_count > 0 ) && ( sc->txb_sched == 0 ) ) { log( LOG_ERR, "fe%d: transmitter idle with %d buffered packets\n", sc->sc_unit, sc->txb_count ); fe_xmit( sc ); } #endif /* * Stop accepting more transmission packets temporarily, when * a filter change request is delayed. Updating the MARs on * 86960 flushes the transmission buffer, so it is delayed * until all buffered transmission packets have been sent * out. */ if ( sc->filter_change ) { /* * Filter change request is delayed only when the DLC is * working. DLC soon raise an interrupt after finishing * the work. */ goto indicate_active; } for (;;) { /* * See if there is room to put another packet in the buffer. * We *could* do better job by peeking the send queue to * know the length of the next packet. Current version just * tests against the worst case (i.e., longest packet). FIXME. * * When adding the packet-peek feature, don't forget adding a * test on txb_count against QUEUEING_MAX. * There is a little chance the packet count exceeds * the limit. Assume transmission buffer is 8KB (2x8KB * configuration) and an application sends a bunch of small * (i.e., minimum packet sized) packets rapidly. An 8KB * buffer can hold 130 blocks of 62 bytes long... */ if ( sc->txb_free < ETHER_MAX_LEN - ETHER_CRC_LEN + FE_DATA_LEN_LEN ) { /* No room. */ goto indicate_active; } #if FE_SINGLE_TRANSMISSION if ( sc->txb_count > 0 ) { /* Just one packet per a transmission buffer. */ goto indicate_active; } #endif /* * Get the next mbuf chain for a packet to send. */ IF_DEQUEUE( &sc->sc_if.if_snd, m ); if ( m == NULL ) { /* No more packets to send. */ goto indicate_inactive; } /* * Copy the mbuf chain into the transmission buffer. * txb_* variables are updated as necessary. */ fe_write_mbufs( sc, m ); /* Start transmitter if it's idle. */ if ( ( sc->txb_count > 0 ) && ( sc->txb_sched == 0 ) ) { fe_xmit( sc ); } /* * Tap off here if there is a bpf listener, * and the device is *not* in promiscuous mode. * (86960 receives self-generated packets if * and only if it is in "receive everything" * mode.) */ #if NBPFILTER > 0 if ( sc->sc_if.if_bpf && !( sc->sc_if.if_flags & IFF_PROMISC ) ) { bpf_mtap( &sc->sc_if, m ); } #endif m_freem( m ); } indicate_inactive: /* * We are using the !OACTIVE flag to indicate to * the outside world that we can accept an * additional packet rather than that the * transmitter is _actually_ active. Indeed, the * transmitter may be active, but if we haven't * filled all the buffers with data then we still * want to accept more. */ sc->sc_if.if_flags &= ~IFF_OACTIVE; return; indicate_active: /* * The transmitter is active, and there are no room for * more outgoing packets in the transmission buffer. */ sc->sc_if.if_flags |= IFF_OACTIVE; return; } /* * Drop (skip) a packet from receive buffer in 86960 memory. */ static void fe_droppacket ( struct fe_softc * sc, int len ) { int i; /* * 86960 manual says that we have to read 8 bytes from the buffer * before skip the packets and that there must be more than 8 bytes * remaining in the buffer when issue a skip command. * Remember, we have already read 4 bytes before come here. */ if ( len > 12 ) { /* Read 4 more bytes, and skip the rest of the packet. */ ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); outb( sc->ioaddr[ FE_BMPR14 ], sc->proto_bmpr14 | FE_B14_SKIP ); } else { /* We should not come here unless receiving RUNTs. */ for ( i = 0; i < len; i += 2 ) { ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); } } } /* * Empty receiving buffer. */ static void fe_emptybuffer ( struct fe_softc * sc ) { int i; u_char saved_dlcr5; #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: emptying receive buffer\n", sc->sc_unit ); #endif /* * Stop receiving packets, temporarily. */ saved_dlcr5 = inb( sc->ioaddr[ FE_DLCR5 ] ); outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 ); DELAY(1300); /* * When we come here, the receive buffer management should * have been broken. So, we cannot use skip operation. * Just discard everything in the buffer. */ for (i = 0; i < 32768; i++) { if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) break; ( void )inw( sc->ioaddr[ FE_BMPR8 ] ); } /* * Double check. */ if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) { log( LOG_ERR, "fe%d: could not empty receive buffer\n", sc->sc_unit ); /* Hmm. What should I do if this happens? FIXME. */ } /* * Restart receiving packets. */ outb( sc->ioaddr[ FE_DLCR5 ], saved_dlcr5 ); } /* * Transmission interrupt handler * The control flow of this function looks silly. FIXME. */ static void fe_tint ( struct fe_softc * sc, u_char tstat ) { int left; int col; /* * Handle "excessive collision" interrupt. */ if ( tstat & FE_D0_COLL16 ) { /* * Find how many packets (including this collided one) * are left unsent in transmission buffer. */ left = inb( sc->ioaddr[ FE_BMPR10 ] ); #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: excessive collision (%d/%d)\n", sc->sc_unit, left, sc->txb_sched ); #endif #if FE_DEBUG >= 3 fe_dump( LOG_INFO, sc, NULL ); #endif /* * Clear the collision flag (in 86960) here * to avoid confusing statistics. */ outb( sc->ioaddr[ FE_DLCR0 ], FE_D0_COLLID ); /* * Restart transmitter, skipping the * collided packet. * * We *must* skip the packet to keep network running * properly. Excessive collision error is an * indication of the network overload. If we * tried sending the same packet after excessive * collision, the network would be filled with * out-of-time packets. Packets belonging * to reliable transport (such as TCP) are resent * by some upper layer. */ outb( sc->ioaddr[ FE_BMPR11 ], FE_B11_CTRL_SKIP | FE_B11_MODE1 ); /* Update statistics. */ sc->tx_excolls++; } /* * Handle "transmission complete" interrupt. */ if ( tstat & FE_D0_TXDONE ) { /* * Add in total number of collisions on last * transmission. We also clear "collision occurred" flag * here. * * 86960 has a design flaw on collision count on multiple * packet transmission. When we send two or more packets * with one start command (that's what we do when the * transmission queue is crowded), 86960 informs us number * of collisions occurred on the last packet on the * transmission only. Number of collisions on previous * packets are lost. I have told that the fact is clearly * stated in the Fujitsu document. * * I considered not to mind it seriously. Collision * count is not so important, anyway. Any comments? FIXME. */ if ( inb( sc->ioaddr[ FE_DLCR0 ] ) & FE_D0_COLLID ) { /* Clear collision flag. */ outb( sc->ioaddr[ FE_DLCR0 ], FE_D0_COLLID ); /* Extract collision count from 86960. */ col = inb( sc->ioaddr[ FE_DLCR4 ] ); col = ( col & FE_D4_COL ) >> FE_D4_COL_SHIFT; if ( col == 0 ) { /* * Status register indicates collisions, * while the collision count is zero. * This can happen after multiple packet * transmission, indicating that one or more * previous packet(s) had been collided. * * Since the accurate number of collisions * has been lost, we just guess it as 1; * Am I too optimistic? FIXME. */ col = 1; } sc->sc_if.if_collisions += col; #if FE_DEBUG >= 3 log( LOG_WARNING, "fe%d: %d collision(s) (%d)\n", sc->sc_unit, col, sc->txb_sched ); #endif } /* * Update transmission statistics. * Be sure to reflect number of excessive collisions. */ sc->sc_if.if_opackets += sc->txb_sched - sc->tx_excolls; sc->sc_if.if_oerrors += sc->tx_excolls; sc->sc_if.if_collisions += sc->tx_excolls * 16; sc->txb_sched = 0; /* * The transmitter is no more active. * Reset output active flag and watchdog timer. */ sc->sc_if.if_flags &= ~IFF_OACTIVE; sc->sc_if.if_timer = 0; /* * If more data is ready to transmit in the buffer, start * transmitting them. Otherwise keep transmitter idle, * even if more data is queued. This gives receive * process a slight priority. */ if ( sc->txb_count > 0 ) fe_xmit( sc ); } } /* * Ethernet interface receiver interrupt. */ static void fe_rint ( struct fe_softc * sc, u_char rstat ) { u_short len; u_char status; int i; /* * Update statistics if this interrupt is caused by an error. */ if ( rstat & ( FE_D1_OVRFLO | FE_D1_CRCERR | FE_D1_ALGERR | FE_D1_SRTPKT ) ) { #if FE_DEBUG >= 2 log( LOG_WARNING, "fe%d: receive error: %s%s%s%s(%02x)\n", sc->sc_unit, rstat & FE_D1_OVRFLO ? "OVR " : "", rstat & FE_D1_CRCERR ? "CRC " : "", rstat & FE_D1_ALGERR ? "ALG " : "", rstat & FE_D1_SRTPKT ? "LEN " : "", rstat ); #endif sc->sc_if.if_ierrors++; } /* * MB86960 has a flag indicating "receive queue empty." * We just loop, checking the flag, to pull out all received * packets. * * We limit the number of iterations to avoid infinite-loop. * The upper bound is set to unrealistic high value. */ for (i = 0; i < FE_MAX_RECV_COUNT * 2; i++) { /* Stop the iteration if 86960 indicates no packets. */ if ( inb( sc->ioaddr[ FE_DLCR5 ] ) & FE_D5_BUFEMP ) break; /* * Extract A receive status byte. * As our 86960 is in 16 bit bus access mode, we have to * use inw() to get the status byte. The significant * value is returned in lower 8 bits. */ status = ( u_char )inw( sc->ioaddr[ FE_BMPR8 ] ); #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: receive status = %04x\n", sc->sc_unit, status ); #endif /* * Extract the packet length. * It is a sum of a header (14 bytes) and a payload. * CRC has been stripped off by the 86960. */ len = inw( sc->ioaddr[ FE_BMPR8 ] ); #if FE_DEBUG >= 1 /* * If there was an error with the received packet, it * must be an indication of out-of-sync on receive * buffer, because we have programmed the 8696x to * to discard errored packets, even when the interface * is in promiscuous mode. We have to re-synchronize. */ if (!(status & FE_RPH_GOOD)) { log(LOG_ERR, "fe%d: corrupted receive status byte (%02x)\n", sc->arpcom.ac_if.if_unit, status); sc->arpcom.ac_if.if_ierrors++; fe_emptybuffer( sc ); break; } #endif #if FE_DEBUG >= 1 /* * MB86960 checks the packet length and drop big packet * before passing it to us. There are no chance we can * get big packets through it, even if they are actually * sent over a line. Hence, if the length exceeds * the specified limit, it means some serious failure, * such as out-of-sync on receive buffer management. * * Same for short packets, since we have programmed * 86960 to drop short packets. */ if ( len > ETHER_MAX_LEN - ETHER_CRC_LEN || len < ETHER_MIN_LEN - ETHER_CRC_LEN ) { log( LOG_WARNING, "fe%d: received a %s packet? (%u bytes)\n", sc->sc_unit, len < ETHER_MIN_LEN - ETHER_CRC_LEN ? "partial" : "big", len ); sc->sc_if.if_ierrors++; fe_emptybuffer( sc ); break; } #endif /* * Go get a packet. */ if ( fe_get_packet( sc, len ) < 0 ) { #if FE_DEBUG >= 2 log( LOG_WARNING, "%s%d: out of mbuf;" " dropping a packet (%u bytes)\n", sc->sc_unit, len ); #endif /* Skip a packet, updating statistics. */ sc->sc_if.if_ierrors++; fe_droppacket( sc, len ); /* * Try extracting other packets, although they will * cause out-of-mbuf error again. This is required * to keep receiver interrupt comming. * (Earlier versions had a bug on this point.) */ continue; } /* Successfully received a packet. Update stat. */ sc->sc_if.if_ipackets++; } } /* * Ethernet interface interrupt processor */ void feintr ( int unit ) { struct fe_softc *sc = &fe_softc[unit]; u_char tstat, rstat; /* * Loop until there are no more new interrupt conditions. */ for (;;) { #if FE_DEBUG >= 4 fe_dump( LOG_INFO, sc, "intr()" ); #endif /* * Get interrupt conditions, masking unneeded flags. */ tstat = inb( sc->ioaddr[ FE_DLCR0 ] ) & FE_TMASK; rstat = inb( sc->ioaddr[ FE_DLCR1 ] ) & FE_RMASK; #if FE_DEBUG >= 1 /* Test for a "dead-lock" condition. */ if ((rstat & FE_D1_PKTRDY) == 0 && (inb(sc->ioaddr[FE_DLCR5]) & FE_D5_BUFEMP) == 0 && (inb(sc->ioaddr[FE_DLCR1]) & FE_D1_PKTRDY) == 0) { /* * PKTRDY is off, while receive buffer is not empty. * We did a double check to avoid a race condition... * So, we should have missed an interrupt. */ log(LOG_WARNING, "fe%d: missed a receiver interrupt?\n", sc->arpcom.ac_if.if_unit); /* Simulate the missed interrupt condition. */ rstat |= FE_D1_PKTRDY; } #endif /* Stop processing if there are no interrupts to handle. */ if ( tstat == 0 && rstat == 0 ) break; /* * Reset the conditions we are acknowledging. */ outb( sc->ioaddr[ FE_DLCR0 ], tstat ); outb( sc->ioaddr[ FE_DLCR1 ], rstat ); /* * Handle transmitter interrupts. Handle these first because * the receiver will reset the board under some conditions. */ if ( tstat ) { fe_tint( sc, tstat ); } /* * Handle receiver interrupts */ if ( rstat ) { fe_rint( sc, rstat ); } /* * Update the multicast address filter if it is * needed and possible. We do it now, because * we can make sure the transmission buffer is empty, * and there is a good chance that the receive queue * is empty. It will minimize the possibility of * packet loss. */ if ( sc->filter_change && sc->txb_count == 0 && sc->txb_sched == 0 ) { fe_loadmar(sc); sc->sc_if.if_flags &= ~IFF_OACTIVE; } /* * If it looks like the transmitter can take more data, * attempt to start output on the interface. This is done * after handling the receiver interrupt to give the * receive operation priority. * * BTW, I'm not sure in what case the OACTIVE is on at * this point. Is the following test redundant? * * No. This routine polls for both transmitter and * receiver interrupts. 86960 can raise a receiver * interrupt when the transmission buffer is full. */ if ( ( sc->sc_if.if_flags & IFF_OACTIVE ) == 0 ) { fe_start( &sc->sc_if ); } } } /* * Process an ioctl request. This code needs some work - it looks * pretty ugly. */ static int fe_ioctl ( struct ifnet * ifp, int command, caddr_t data ) { struct fe_softc *sc = ifp->if_softc; int s, error = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: ioctl(%x)\n", sc->sc_unit, command ); #endif s = splimp(); switch (command) { case SIOCSIFADDR: { struct ifaddr * ifa = ( struct ifaddr * )data; sc->sc_if.if_flags |= IFF_UP; switch (ifa->ifa_addr->sa_family) { #ifdef INET case AF_INET: fe_init( sc->sc_unit ); /* before arp_ifinit */ arp_ifinit( &sc->arpcom, ifa ); break; #endif #ifdef IPX /* * XXX - This code is probably wrong */ case AF_IPX: { register struct ipx_addr *ina = &(IA_SIPX(ifa)->sipx_addr); if (ipx_nullhost(*ina)) ina->x_host = *(union ipx_host *) (sc->sc_enaddr); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->sc_enaddr, sizeof(sc->sc_enaddr)); } /* * Set new address */ fe_init(sc->sc_unit); break; } #endif #ifdef INET6 case AF_INET6: /* IPV6 added by shin 96.2.6 */ fe_init(sc->sc_unit); ndp6_ifinit(&sc->arpcom, ifa); break; #endif #ifdef NS /* * XXX - This code is probably wrong */ case AF_NS: { register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); if (ns_nullhost(*ina)) ina->x_host = *(union ns_host *) (sc->sc_enaddr); else { bcopy((caddr_t) ina->x_host.c_host, (caddr_t) sc->sc_enaddr, sizeof(sc->sc_enaddr)); } /* * Set new address */ fe_init(sc->sc_unit); break; } #endif default: fe_init( sc->sc_unit ); break; } break; } #ifdef SIOCGIFADDR case SIOCGIFADDR: { struct ifreq * ifr = ( struct ifreq * )data; struct sockaddr * sa = ( struct sockaddr * )&ifr->ifr_data; bcopy((caddr_t)sc->sc_enaddr, (caddr_t)sa->sa_data, ETHER_ADDR_LEN); break; } #endif #ifdef SIOCGIFPHYSADDR case SIOCGIFPHYSADDR: { struct ifreq * ifr = ( struct ifreq * )data; bcopy((caddr_t)sc->sc_enaddr, (caddr_t)&ifr->ifr_data, ETHER_ADDR_LEN); break; } #endif #ifdef notdef #ifdef SIOCSIFPHYSADDR case SIOCSIFPHYSADDR: { /* * Set the physical (Ethernet) address of the interface. * When and by whom is this command used? FIXME. */ struct ifreq * ifr = ( struct ifreq * )data; bcopy((caddr_t)&ifr->ifr_data, (caddr_t)sc->sc_enaddr, ETHER_ADDR_LEN); fe_setlinkaddr( sc ); break; } #endif #endif /* notdef */ #ifdef SIOCSIFFLAGS case SIOCSIFFLAGS: { /* * Switch interface state between "running" and * "stopped", reflecting the UP flag. */ if ( sc->sc_if.if_flags & IFF_UP ) { if ( ( sc->sc_if.if_flags & IFF_RUNNING ) == 0 ) { fe_init( sc->sc_unit ); } } else { if ( ( sc->sc_if.if_flags & IFF_RUNNING ) != 0 ) { fe_stop( sc->sc_unit ); } } /* * Promiscuous and/or multicast flags may have changed, * so reprogram the multicast filter and/or receive mode. */ fe_setmode( sc ); #if FE_DEBUG >= 1 /* "ifconfig fe0 debug" to print register dump. */ if ( sc->sc_if.if_flags & IFF_DEBUG ) { fe_dump( LOG_DEBUG, sc, "SIOCSIFFLAGS(DEBUG)" ); } #endif break; } #endif #ifdef SIOCADDMULTI case SIOCADDMULTI: case SIOCDELMULTI: /* * Multicast list has changed; set the hardware filter * accordingly. */ fe_setmode( sc ); error = 0; break; #endif #ifdef SIOCSIFMTU case SIOCSIFMTU: { /* * Set the interface MTU. */ struct ifreq * ifr = ( struct ifreq * )data; if ( ifr->ifr_mtu > ETHERMTU ) { error = EINVAL; } else { sc->sc_if.if_mtu = ifr->ifr_mtu; } break; } #endif default: error = EINVAL; } (void) splx(s); return (error); } /* * Retrieve packet from receive buffer and send to the next level up via * ether_input(). If there is a BPF listener, give a copy to BPF, too. * Returns 0 if success, -1 if error (i.e., mbuf allocation failure). */ static int fe_get_packet ( struct fe_softc * sc, u_short len ) { struct ether_header *eh; struct mbuf *m; /* * NFS wants the data be aligned to the word (4 byte) * boundary. Ethernet header has 14 bytes. There is a * 2-byte gap. */ #define NFS_MAGIC_OFFSET 2 /* * This function assumes that an Ethernet packet fits in an * mbuf (with a cluster attached when necessary.) On FreeBSD * 2.0 for x86, which is the primary target of this driver, an * mbuf cluster has 4096 bytes, and we are happy. On ancient * BSDs, such as vanilla 4.3 for 386, a cluster size was 1024, * however. If the following #error message were printed upon * compile, you need to rewrite this function. */ #if ( MCLBYTES < ETHER_MAX_LEN - ETHER_CRC_LEN + NFS_MAGIC_OFFSET ) #error "Too small MCLBYTES to use fe driver." #endif /* * Our strategy has one more problem. There is a policy on * mbuf cluster allocation. It says that we must have at * least MINCLSIZE (208 bytes on FreeBSD 2.0 for x86) to * allocate a cluster. For a packet of a size between * (MHLEN - 2) to (MINCLSIZE - 2), our code violates the rule... * On the other hand, the current code is short, simple, * and fast, however. It does no harmful thing, just waists * some memory. Any comments? FIXME. */ /* Allocate an mbuf with packet header info. */ MGETHDR(m, M_DONTWAIT, MT_DATA); if ( m == NULL ) return -1; /* Attach a cluster if this packet doesn't fit in a normal mbuf. */ if ( len > MHLEN - NFS_MAGIC_OFFSET ) { MCLGET( m, M_DONTWAIT ); if ( !( m->m_flags & M_EXT ) ) { m_freem( m ); return -1; } } /* Initialize packet header info. */ m->m_pkthdr.rcvif = &sc->sc_if; m->m_pkthdr.len = len; /* Set the length of this packet. */ m->m_len = len; /* The following silliness is to make NFS happy */ m->m_data += NFS_MAGIC_OFFSET; /* Get a packet. */ insw( sc->ioaddr[ FE_BMPR8 ], m->m_data, ( len + 1 ) >> 1 ); /* Get (actually just point to) the header part. */ eh = mtod( m, struct ether_header *); #define ETHER_ADDR_IS_MULTICAST(A) (*(char *)(A) & 1) #if NBPFILTER > 0 /* * Check if there's a BPF listener on this interface. * If it is, hand off the raw packet to bpf. */ if ( sc->sc_if.if_bpf ) { bpf_mtap( &sc->sc_if, m ); } #endif /* * Make sure this packet is (or may be) directed to us. * That is, the packet is either unicasted to our address, * or broad/multi-casted. If any other packets are * received, it is an indication of an error -- probably * 86960 is in a wrong operation mode. * Promiscuous mode is an exception. Under the mode, all * packets on the media must be received. (We must have * programmed the 86960 so.) */ if ( ( sc->sc_if.if_flags & IFF_PROMISC ) && !ETHER_ADDR_IS_MULTICAST( eh->ether_dhost ) && bcmp( eh->ether_dhost, sc->sc_enaddr, ETHER_ADDR_LEN ) != 0 ) { /* * The packet was not for us. This is normal since * we are now in promiscuous mode. Just drop the packet. */ m_freem( m ); return 0; } #if FE_DEBUG >= 3 if ( !ETHER_ADDR_IS_MULTICAST( eh->ether_dhost ) && bcmp( eh->ether_dhost, sc->sc_enaddr, ETHER_ADDR_LEN ) != 0 ) { /* * This packet was not for us. We can't be in promiscuous * mode since the case was handled by above test. * We found an error (of this driver.) */ log( LOG_WARNING, "fe%d: got an unwanted packet, dst = %6D\n", sc->sc_unit, eh->ether_dhost , ":" ); m_freem( m ); return 0; } #endif /* Strip off the Ethernet header. */ m->m_pkthdr.len -= sizeof ( struct ether_header ); m->m_len -= sizeof ( struct ether_header ); m->m_data += sizeof ( struct ether_header ); /* Feed the packet to upper layer. */ ether_input( &sc->sc_if, eh, m ); return 0; } /* * Write an mbuf chain to the transmission buffer memory using 16 bit PIO. * Returns number of bytes actually written, including length word. * * If an mbuf chain is too long for an Ethernet frame, it is not sent. * Packets shorter than Ethernet minimum are legal, and we pad them * before sending out. An exception is "partial" packets which are * shorter than mandatory Ethernet header. */ static void fe_write_mbufs ( struct fe_softc *sc, struct mbuf *m ) { u_short addr_bmpr8 = sc->ioaddr[ FE_BMPR8 ]; u_short length, len; struct mbuf *mp; u_char *data; u_short savebyte; /* WARNING: Architecture dependent! */ #define NO_PENDING_BYTE 0xFFFF static u_char padding [ ETHER_MIN_LEN - ETHER_CRC_LEN - ETHER_HDR_LEN ]; #if FE_DEBUG >= 1 /* First, count up the total number of bytes to copy */ length = 0; for ( mp = m; mp != NULL; mp = mp->m_next ) { length += mp->m_len; } #else /* Just use the length value in the packet header. */ length = m->m_pkthdr.len; #endif #if FE_DEBUG >= 2 /* Check if this matches the one in the packet header. */ if ( length != m->m_pkthdr.len ) { log( LOG_WARNING, "fe%d: packet length mismatch? (%d/%d)\n", sc->sc_unit, length, m->m_pkthdr.len ); } #endif #if FE_DEBUG >= 1 /* * Should never send big packets. If such a packet is passed, * it should be a bug of upper layer. We just ignore it. * ... Partial (too short) packets, neither. */ if ( length < ETHER_HDR_LEN || length > ETHER_MAX_LEN - ETHER_CRC_LEN ) { log( LOG_ERR, "fe%d: got an out-of-spec packet (%u bytes) to send\n", sc->sc_unit, length ); sc->sc_if.if_oerrors++; return; } #endif /* * Put the length word for this frame. * Does 86960 accept odd length? -- Yes. * Do we need to pad the length to minimum size by ourselves? * -- Generally yes. But for (or will be) the last * packet in the transmission buffer, we can skip the * padding process. It may gain performance slightly. FIXME. */ outw( addr_bmpr8, max( length, ETHER_MIN_LEN - ETHER_CRC_LEN ) ); /* * Update buffer status now. * Truncate the length up to an even number, since we use outw(). */ length = ( length + 1 ) & ~1; sc->txb_free -= FE_DATA_LEN_LEN + max( length, ETHER_MIN_LEN - ETHER_CRC_LEN); sc->txb_count++; /* * Transfer the data from mbuf chain to the transmission buffer. * MB86960 seems to require that data be transferred as words, and * only words. So that we require some extra code to patch * over odd-length mbufs. */ savebyte = NO_PENDING_BYTE; for ( mp = m; mp != 0; mp = mp->m_next ) { /* Ignore empty mbuf. */ len = mp->m_len; if ( len == 0 ) continue; /* Find the actual data to send. */ data = mtod(mp, caddr_t); /* Finish the last byte. */ if ( savebyte != NO_PENDING_BYTE ) { outw( addr_bmpr8, savebyte | ( *data << 8 ) ); data++; len--; savebyte = NO_PENDING_BYTE; } /* output contiguous words */ if (len > 1) { outsw( addr_bmpr8, data, len >> 1); data += len & ~1; len &= 1; } /* Save a remaining byte, if there is one. */ if ( len > 0 ) { savebyte = *data; } } /* Spit the last byte, if the length is odd. */ if ( savebyte != NO_PENDING_BYTE ) { outw( addr_bmpr8, savebyte ); } /* Pad to the Ethernet minimum length, if the packet is too short. */ if ( length < ETHER_MIN_LEN - ETHER_CRC_LEN ) { outsw( addr_bmpr8, padding, ( ETHER_MIN_LEN - ETHER_CRC_LEN - length ) >> 1); } } /* * Compute hash value for an Ethernet address */ static int fe_hash ( u_char * ep ) { #define FE_HASH_MAGIC_NUMBER 0xEDB88320L u_long hash = 0xFFFFFFFFL; int i, j; u_char b; u_long m; for ( i = ETHER_ADDR_LEN; --i >= 0; ) { b = *ep++; for ( j = 8; --j >= 0; ) { m = hash; hash >>= 1; if ( ( m ^ b ) & 1 ) hash ^= FE_HASH_MAGIC_NUMBER; b >>= 1; } } return ( ( int )( hash >> 26 ) ); } /* * Compute the multicast address filter from the * list of multicast addresses we need to listen to. */ static struct fe_filter fe_mcaf ( struct fe_softc *sc ) { int index; struct fe_filter filter; struct ifmultiaddr *ifma; filter = fe_filter_nothing; for (ifma = sc->arpcom.ac_if.if_multiaddrs.lh_first; ifma; ifma = ifma->ifma_link.le_next) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; index = fe_hash(LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: hash(%6D) == %d\n", sc->sc_unit, enm->enm_addrlo , ":", index ); #endif filter.data[index >> 3] |= 1 << (index & 7); } return ( filter ); } /* * Calculate a new "multicast packet filter" and put the 86960 * receiver in appropriate mode. */ static void fe_setmode ( struct fe_softc *sc ) { int flags = sc->sc_if.if_flags; /* * If the interface is not running, we postpone the update * process for receive modes and multicast address filter * until the interface is restarted. It reduces some * complicated job on maintaining chip states. (Earlier versions * of this driver had a bug on that point...) * * To complete the trick, fe_init() calls fe_setmode() after * restarting the interface. */ if ( !( flags & IFF_RUNNING ) ) return; /* * Promiscuous mode is handled separately. */ if ( flags & IFF_PROMISC ) { /* * Program 86960 to receive all packets on the segment * including those directed to other stations. * Multicast filter stored in MARs are ignored * under this setting, so we don't need to update it. * * Promiscuous mode in FreeBSD 2 is used solely by * BPF, and BPF only listens to valid (no error) packets. * So, we ignore erroneous ones even in this mode. * (Older versions of fe driver mistook the point.) */ outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 | FE_D5_AFM0 | FE_D5_AFM1 ); sc->filter_change = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: promiscuous mode\n", sc->sc_unit ); #endif return; } /* * Turn the chip to the normal (non-promiscuous) mode. */ outb( sc->ioaddr[ FE_DLCR5 ], sc->proto_dlcr5 | FE_D5_AFM1 ); /* * Find the new multicast filter value. * I'm not sure we have to handle modes other than MULTICAST. * Who sets ALLMULTI? Who turns MULTICAST off? FIXME. */ if ( flags & IFF_ALLMULTI ) { sc->filter = fe_filter_all; } else if ( flags & IFF_MULTICAST ) { sc->filter = fe_mcaf( sc ); } else { sc->filter = fe_filter_nothing; } sc->filter_change = 1; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: address filter: [%8D]\n", sc->sc_unit, sc->filter.data, " " ); #endif /* * We have to update the multicast filter in the 86960, A.S.A.P. * * Note that the DLC (Data Link Control unit, i.e. transmitter * and receiver) must be stopped when feeding the filter, and * DLC trashes all packets in both transmission and receive * buffers when stopped. * * ... Are the above sentences correct? I have to check the * manual of the MB86960A. FIXME. * * To reduce the packet loss, we delay the filter update * process until buffers are empty. */ if ( sc->txb_sched == 0 && sc->txb_count == 0 && !( inb( sc->ioaddr[ FE_DLCR1 ] ) & FE_D1_PKTRDY ) ) { /* * Buffers are (apparently) empty. Load * the new filter value into MARs now. */ fe_loadmar(sc); } else { /* * Buffers are not empty. Mark that we have to update * the MARs. The new filter will be loaded by feintr() * later. */ #if FE_DEBUG >= 4 log( LOG_INFO, "fe%d: filter change delayed\n", sc->sc_unit ); #endif } } /* * Load a new multicast address filter into MARs. * * The caller must have splimp'ed before fe_loadmar. * This function starts the DLC upon return. So it can be called only * when the chip is working, i.e., from the driver's point of view, when * a device is RUNNING. (I mistook the point in previous versions.) */ static void fe_loadmar ( struct fe_softc * sc ) { /* Stop the DLC (transmitter and receiver). */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_DISABLE ); DELAY( 200 ); /* Select register bank 1 for MARs. */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_MAR | FE_D7_POWER_UP ); /* Copy filter value into the registers. */ outblk( sc, FE_MAR8, sc->filter.data, FE_FILTER_LEN ); /* Restore the bank selection for BMPRs (i.e., runtime registers). */ outb( sc->ioaddr[ FE_DLCR7 ], sc->proto_dlcr7 | FE_D7_RBS_BMPR | FE_D7_POWER_UP ); /* Restart the DLC. */ DELAY( 200 ); outb( sc->ioaddr[ FE_DLCR6 ], sc->proto_dlcr6 | FE_D6_DLC_ENABLE ); DELAY( 200 ); /* We have just updated the filter. */ sc->filter_change = 0; #if FE_DEBUG >= 3 log( LOG_INFO, "fe%d: address filter changed\n", sc->sc_unit ); #endif } #if FE_DEBUG >= 1 static void fe_dump ( int level, struct fe_softc * sc, char * message ) { log( level, "fe%d: %s," " DLCR = %02x %02x %02x %02x %02x %02x %02x %02x," " BMPR = xx xx %02x %02x %02x %02x %02x %02x," " asic = %02x %02x %02x %02x %02x %02x %02x %02x" " + %02x %02x %02x %02x %02x %02x %02x %02x\n", sc->sc_unit, message ? message : "registers", inb( sc->ioaddr[ FE_DLCR0 ] ), inb( sc->ioaddr[ FE_DLCR1 ] ), inb( sc->ioaddr[ FE_DLCR2 ] ), inb( sc->ioaddr[ FE_DLCR3 ] ), inb( sc->ioaddr[ FE_DLCR4 ] ), inb( sc->ioaddr[ FE_DLCR5 ] ), inb( sc->ioaddr[ FE_DLCR6 ] ), inb( sc->ioaddr[ FE_DLCR7 ] ), inb( sc->ioaddr[ FE_BMPR10 ] ), inb( sc->ioaddr[ FE_BMPR11 ] ), inb( sc->ioaddr[ FE_BMPR12 ] ), inb( sc->ioaddr[ FE_BMPR13 ] ), inb( sc->ioaddr[ FE_BMPR14 ] ), inb( sc->ioaddr[ FE_BMPR15 ] ), inb( sc->ioaddr[ 0x10 ] ), inb( sc->ioaddr[ 0x11 ] ), inb( sc->ioaddr[ 0x12 ] ), inb( sc->ioaddr[ 0x13 ] ), inb( sc->ioaddr[ 0x14 ] ), inb( sc->ioaddr[ 0x15 ] ), inb( sc->ioaddr[ 0x16 ] ), inb( sc->ioaddr[ 0x17 ] ), inb( sc->ioaddr[ 0x18 ] ), inb( sc->ioaddr[ 0x19 ] ), inb( sc->ioaddr[ 0x1A ] ), inb( sc->ioaddr[ 0x1B ] ), inb( sc->ioaddr[ 0x1C ] ), inb( sc->ioaddr[ 0x1D ] ), inb( sc->ioaddr[ 0x1E ] ), inb( sc->ioaddr[ 0x1F ] ) ); } #endif diff --git a/sys/pc98/pc98/sio.c b/sys/pc98/pc98/sio.c index 1a57098644b0..1a034b230c60 100644 --- a/sys/pc98/pc98/sio.c +++ b/sys/pc98/pc98/sio.c @@ -1,4228 +1,4228 @@ /*- * 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.41 1997/10/27 11:00:31 kato Exp $ + * $Id: sio.c,v 1.42 1997/11/03 02:30:45 kato Exp $ */ #include "opt_comconsole.h" #include "opt_ddb.h" #include "opt_sio.h" #include "sio.h" #include "pnp.h" #ifndef EXTRA_SIO #if NPNP > 0 #define EXTRA_SIO 2 #else #define EXTRA_SIO 0 #endif #endif #define NSIOTOT (NSIO + EXTRA_SIO) /* * 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 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef DEVFS #include #endif #include #ifdef PC98 #include #include #include #include #include #include #else #include #include #include #endif #include #ifdef COM_ESP #include #endif #include #include "card.h" #if NCARD > 0 -#include +#include #include #include #endif #if NPNP > 0 #include #endif #ifdef SMP #define disable_intr() COM_DISABLE_INTR() #define enable_intr() COM_ENABLE_INTR() #endif /* SMP */ #ifdef APIC_IO /* * INTs are masked in the (global) IO APIC, * but the IRR register is in each LOCAL APIC, * so we would have to unmask the INT to be able to "see INT pending". * So instead we just look in the 8259 ICU. */ #define isa_irq_pending icu_irq_pending #endif /* APIC_IO */ #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_CONSOLE(dev) ((dev)->id_flags & 0x10) #define COM_FORCECONSOLE(dev) ((dev)->id_flags & 0x20) #define COM_LLCONSOLE(dev) ((dev)->id_flags & 0x40) #define COM_LOSESOUTINTS(dev) ((dev)->id_flags & 0x08) #define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) #define COM_VERBOSE(dev) ((dev)->id_flags & 0x80) #define COM_NOTST3(dev) ((dev)->id_flags & 0x10000) #define COM_ST16650A(dev) ((dev)->id_flags & 0x20000) #define COM_FIFOSIZE(dev) (((dev)->id_flags & 0xff000000) >> 24) #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 */ #define CSE_BUSYCHECK 1 /* siobusycheck() scheduled */ 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 extra_state; /* more flag bits, separate for order trick */ u_char fifo_image; /* copy of value written to FIFO */ bool_t hasfifo; /* nonzero for 16550 UARTs */ bool_t st16650a; /* Is a Startech 16650A or RTS/CTS compat */ 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; bool_t do_dcd_timestamp; struct timeval timestamp; struct timeval dcd_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 point. */ void siopoll __P((void)); /* Device switch entry points. */ #define sioreset noreset #define siommap nommap #define siostrategy nostrategy #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)); static timeout_t siobusycheck; 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)); static int sioprobe __P((struct isa_device *dev)); 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[NSIOTOT]; #define com_addr(unit) (p_com_addr[unit]) struct isa_driver siodriver = { 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, ttpoll, nommap, NULL, driver_name, NULL, -1, }; static int comconsole = -1; static volatile speed_t comdefaultrate = CONSPEED; static u_int com_events; /* input chars + weighted output completions */ static Port_t siocniobase; static int sio_timeout; static int sio_timeouts_until_log; static struct callout_handle sio_timeout_handle = CALLOUT_HANDLE_INITIALIZER(&sio_timeout_handle); #if 0 /* XXX */ static struct tty *sio_tty[NSIOTOT]; #else static struct tty sio_tty[NSIOTOT]; #endif static const int nsio_tty = NSIOTOT; #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 } }; #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 /* * handle sysctl read/write requests for console speed * * In addition to setting comdefaultrate for I/O through /dev/console, * also set the initial and lock values for the /dev/ttyXX device * if there is one associated with the console. Finally, if the /dev/tty * device has already been open, change the speed on the open running port * itself. */ static int sysctl_machdep_comdefaultrate SYSCTL_HANDLER_ARGS { int error, s; speed_t newspeed; struct com_s *com; struct tty *tp; newspeed = comdefaultrate; error = sysctl_handle_opaque(oidp, &newspeed, sizeof newspeed, req); if (error || !req->newptr) return (error); comdefaultrate = newspeed; if (comconsole < 0) /* serial console not selected? */ return (0); com = com_addr(comconsole); if (!com) return (ENXIO); /* * set the initial and lock rates for /dev/ttydXX and /dev/cuaXX * (note, the lock rates really are boolean -- if non-zero, disallow * speed changes) */ com->it_in.c_ispeed = com->it_in.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_out.c_ispeed = com->it_out.c_ospeed = com->lt_out.c_ispeed = com->lt_out.c_ospeed = comdefaultrate; /* * if we're open, change the running rate too */ tp = com->tp; if (tp && (tp->t_state & TS_ISOPEN)) { tp->t_termios.c_ispeed = tp->t_termios.c_ospeed = comdefaultrate; s = spltty(); error = comparam(tp, &tp->t_termios); splx(s); } return error; } SYSCTL_PROC(_machdep, OID_AUTO, conspeed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_machdep_comdefaultrate, "I", ""); #if NCARD > 0 /* * PC-Card (PCMCIA) specific code. */ static int sioinit(struct pccard_devinfo *); /* init device */ static void siounload(struct pccard_devinfo *); /* Disable driver */ static int card_intr(struct pccard_devinfo *); /* Interrupt handler */ static struct pccard_device sio_info = { driver_name, sioinit, siounload, card_intr, 0, /* Attributes - presently unused */ &tty_imask /* Interrupt mask for device */ /* XXX - Should this also include net_imask? */ }; DATA_SET(pccarddrv_set, sio_info); /* * Initialize the device - called from Slot manager. */ int sioinit(struct pccard_devinfo *devi) { /* validate unit number. */ if (devi->isahd.id_unit >= (NSIOTOT)) return(ENODEV); /* Make sure it isn't already probed. */ if (com_addr(devi->isahd.id_unit)) return(EBUSY); /* * Probe the device. If a value is returned, the * device was found at the location. */ if (sioprobe(&devi->isahd) == 0) return(ENXIO); if (sioattach(&devi->isahd) == 0) return(ENXIO); 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_devinfo *devi) { struct com_s *com; com = com_addr(devi->isahd.id_unit); if (!com->iobase) { printf("sio%d already unloaded!\n",devi->isahd.id_unit); return; } if (com->tp && (com->tp->t_state & TS_ISOPEN)) { com->gone = 1; printf("sio%d: unload\n", devi->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", devi->isahd.id_unit); } } /* * card_intr - Shared interrupt called from * front end of PC-Card handler. */ static int card_intr(struct pccard_devinfo *devi) { struct com_s *com; COM_LOCK(); com = com_addr(devi->isahd.id_unit); if (com && !com->gone) siointr1(com_addr(devi->isahd.id_unit)); COM_UNLOCK(); return(1); } #endif /* NCARD > 0 */ static int sioprobe(dev) struct isa_device *dev; { static bool_t already_init; bool_t failures[10]; int fn; struct isa_device *idev; Port_t iobase; u_char mcr_image; int result; #ifdef PC98 struct isa_device *xdev; int irqout=0; int ret = 0; int tmp; struct siodev iod; #else struct isa_device *xdev; #endif 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. */ for (xdev = isa_devtab_tty; xdev->id_driver != NULL; xdev++) if (xdev->id_driver == &siodriver && xdev->id_enabled) #ifdef PC98 if (IS_PC98IN(xdev->id_iobase)) outb(xdev->id_iobase + 2, 0xf2); else #endif outb(xdev->id_iobase + com_mcr, 0); 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 = isa_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)) { idev = find_isadev(isa_devtab_tty, &siodriver, 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; if (COM_LLCONSOLE(dev)) { printf("sio%d: reserved for low-level i/o\n", dev->id_unit); return (0); } /* * 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? */ /* * 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. */ if (iobase == siocniobase) DELAY((16 + 1) * 1000000 / (comdefaultrate / 10)); else { outb(iobase + com_cfcr, CFCR_DLAB | CFCR_8BITS); outb(iobase + com_dlbl, COMBRD(SIO_TEST_SPEED) & 0xff); outb(iobase + com_dlbh, (u_int) COMBRD(SIO_TEST_SPEED) >> 8); outb(iobase + com_cfcr, CFCR_8BITS); DELAY((16 + 1) * 1000000 / (SIO_TEST_SPEED / 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 / (SIO_TEST_SPEED / 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(10000); /* Some internal modems need this time */ if (idev->id_irq != 0 && !COM_NOTST3(idev)) failures[3] = isa_irq_pending(idev) ? 0 : 1; failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; DELAY(1000); /* XXX */ if (idev->id_irq != 0) failures[5] = isa_irq_pending(idev) ? 1 : 0; 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 */ if (idev->id_irq != 0) failures[8] = isa_irq_pending(idev) ? 1 : 0; failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; 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) struct isa_device *isdp; { 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 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; com->lt_out.c_ispeed = com->lt_out.c_ospeed = com->lt_in.c_ispeed = com->lt_in.c_ospeed = com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; } else com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED; termioschars(&com->it_in); 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"); 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"); 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)"); break; #ifdef COM_IF_PC9861K case COM_IF_PC9861K: printf(" 8251 (PC9861K)"); break; #endif #ifdef COM_IF_PIO9032B case COM_IF_PIO9032B: printf(" 8251 (PIO9032B)"); break; #endif #ifdef COM_IF_B98_01 case COM_IF_B98_01: printf(" 8251 (B98_01)"); break; #endif } } else { #endif /* PC98 */ outb(iobase + com_fifo, FIFO_ENABLE | FIFO_RX_HIGH); DELAY(100); com->st16650a = 0; switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_RX_LOW: printf(" 16450"); break; case FIFO_RX_MEDL: printf(" 16450?"); break; case FIFO_RX_MEDH: printf(" 16550?"); break; case FIFO_RX_HIGH: if (COM_NOFIFO(isdp)) { printf(" 16550A fifo disabled"); } else { com->hasfifo = TRUE; if (COM_ST16650A(isdp)) { com->st16650a = 1; com->tx_fifo_size = 32; printf(" ST16650A"); } else { com->tx_fifo_size = COM_FIFOSIZE(isdp); printf(" 16550A"); } } #ifdef COM_ESP for (espp = likely_esp_ports; *espp != 0; espp++) if (espattach(isdp, com, *espp)) { com->tx_fifo_size = 1024; break; } #endif if (!com->st16650a) { if (!com->tx_fifo_size) com->tx_fifo_size = 16; else printf(" lookalike with %d bytes FIFO", com->tx_fifo_size); } break; } #ifdef COM_ESP if (com->esp) { /* * Set 16550 compatibility mode. * We don't use the ESP_MODE_SCALE bit to increase the * fifo trigger levels because we can't handle large * bursts of input. * XXX flow control should be set in comparam(), not here. */ outb(com->esp_port + ESP_CMD1, ESP_SETMODE); outb(com->esp_port + ESP_CMD2, 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(")"); com->no_irq = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(isdp))->id_irq == 0; } #endif /* COM_MULTIPORT */ #ifdef PC98 } #endif if (unit == comconsole) printf(", console"); printf("\n"); s = spltty(); com_addr(unit) = com; splx(s); dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &sio_cdevsw, NULL); #ifdef DEVFS 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 >= NSIOTOT || (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; } 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); /* * XXX the delays are for superstitious * historical reasons. It must be less than * the character time at the maximum * supported speed (87 usec at 115200 bps * 8N1). Otherwise we might loop endlessly * if data is streaming in. We used to use * delays of 100. That usually worked * because DELAY(100) used to usually delay * for about 85 usec instead of 100. */ DELAY(50); if (!(inb(com->line_status_port) & LSR_RXRDY)) break; outb(iobase + com_fifo, 0); DELAY(50); (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 = FALSE; com->do_dcd_timestamp = FALSE; #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 && !(com->state & CS_DTR_OFF)) { 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 */ 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 siobusycheck(chan) void *chan; { struct com_s *com; int s; com = (struct com_s *)chan; /* * Clear TS_BUSY if low-level output is complete. * spl locking is sufficient because siointr1() does not set CS_BUSY. * If siointr1() clears CS_BUSY after we look at it, then we'll get * called again. Reading the line status port outside of siointr1() * is safe because CS_BUSY is clear so there are no output interrupts * to lose. */ s = spltty(); if (com->state & CS_BUSY) com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */ #ifdef PC98 else if (IS_8251(com->pc98_if_type) && (inb(com->sts_port) & (STS8251_TxRDY | STS8251_TxEMP)) == (STS8251_TxRDY | STS8251_TxEMP) || (inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { #else else if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) == (LSR_TSRE | LSR_TXRDY)) { #endif com->tp->t_state &= ~TS_BUSY; ttwwakeup(com->tp); com->extra_state &= ~CSE_BUSYCHECK; } else timeout(siobusycheck, com, hz / 100); splx(s); } static void siodtrwakeup(chan) void *chan; { struct com_s *com; com = (struct com_s *)chan; com->state &= ~CS_DTR_OFF; wakeup(&com->dtr_wait); } void siointr(unit) int unit; { #ifndef COM_MULTIPORT COM_LOCK(); siointr1(com_addr(unit)); COM_UNLOCK(); #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. */ COM_LOCK(); do { possibly_more_intrs = FALSE; for (unit = 0; unit < NSIOTOT; ++unit) { com = com_addr(unit); /* * XXX COM_LOCK(); * would it work here, or be counter-productive? */ #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; } /* XXX COM_UNLOCK(); */ } } while (possibly_more_intrs); COM_UNLOCK(); #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 */ 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_BI | LSR_FE | LSR_PE)) { /* * Don't store BI if IGNBRK or FE/PE if IGNPAR. * Otherwise, push the work to a higher level * (to handle PARMRK) if we're bypassing. * Otherwise, convert BI/FE and PE+INPCK to 0. * * This makes bypassing work right in the * usual "raw" case (IGNBRK set, and IGNPAR * and INPCK clear). * * Note: BI together with FE/PE means just BI. */ if (line_status & LSR_BI) { #if defined(DDB) && defined(BREAK_TO_DEBUGGER) if (com->unit == comconsole) { breakpoint(); goto cont; } #endif if (com->tp == NULL || com->tp->t_iflag & IGNBRK) goto cont; } else { if (com->tp == NULL || com->tp->t_iflag & IGNPAR) goto cont; } if (com->tp->t_state & TS_CAN_BYPASS_L_RINT && (line_status & (LSR_BI | LSR_FE) || 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 { if (com->do_timestamp) microtime(&com->timestamp); ++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) { if (com->do_dcd_timestamp && !(com->last_modem_status & MSR_DCD) && modem_status & MSR_DCD) microtime(&com->dcd_timestamp); /* * 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; 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 defined(PC98) if(IS_8251(com->pc98_if_type)) if ( pc98_check_i8251_interrupt(com) & IEN_TxFLAG ) com_int_Tx_disable(com); #endif } 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: com_tiocm_bis(com, TIOCM_DTR | TIOCM_RTS ); break; case TIOCCDTR: com_tiocm_bic(com, TIOCM_DTR); break; /* * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set. The * changes get undone on the next call to comparam(). */ case TIOCMSET: com_tiocm_set( com, *(int *)data ); break; case TIOCMBIS: com_tiocm_bis( com, *(int *)data ); break; case TIOCMBIC: com_tiocm_bic( com, *(int *)data ); break; case TIOCMGET: *(int *)data = com_tiocm_get(com); 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; case TIOCDCDTIMESTAMP: com->do_dcd_timestamp = TRUE; *(struct timeval *)data = com->dcd_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 < NSIOTOT; ++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. */ #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; enable_intr(); if (!(com->state & CS_BUSY) && !(com->extra_state & CSE_BUSYCHECK)) { timeout(siobusycheck, com, hz / 100); com->extra_state |= CSE_BUSYCHECK; } (*linesw[tp->t_line].l_start)(tp); } if (incc <= 0 || !(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) 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; u_char dlbh; u_char dlbl; 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; #ifdef COM_ESP /* * The Hayes ESP card needs the fifo DMA mode bit set * in compatibility mode. If not, it will interrupt * for each character received. */ if (com->esp) com->fifo_image |= FIFO_DMA_MODE; #endif 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); /* * Only set the divisor registers if they would change, * since on some 16550 incompatibles (UMC8669F), setting * them while input is arriving them loses sync until * data stops arriving. */ dlbl = divisor & 0xFF; if (inb(iobase + com_dlbl) != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (inb(iobase + com_dlbh) != dlbh) outb(iobase + com_dlbh, dlbh); } 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)) if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x40); } com->state |= CS_TTGO; if (cflag & CRTS_IFLOW) { com->state |= CS_RTS_IFLOW; /* * If CS_RTS_IFLOW just changed from off to on, the change * needs to be propagated to MCR_RTS. This isn't urgent, * so do it later by calling comstart() instead of repeating * a lot of code from comstart() here. */ } else if (com->state & CS_RTS_IFLOW) { com->state &= ~CS_RTS_IFLOW; /* * CS_RTS_IFLOW just changed from on to off. Force MCR_RTS * on here, since comstart() won't do it later. */ #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); if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x40); } } /* * 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)) if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) | 0x80); } #ifdef PC98 } #endif } else { if (com->st16650a) { outb(iobase + com_cfcr, 0xbf); outb(iobase + com_fifo, inb(iobase + com_fifo) & ~0x80); } com->state &= ~CS_ODEVREADY; } outb(iobase + com_cfcr, com->cfcr_image); /* 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); comstart(tp); 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 { #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 && com->state & CS_RTS_IFLOW) #else if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater && com->state & CS_RTS_IFLOW) #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) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_XMT_RST | com->fifo_image); 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) { if (com->hasfifo) #ifdef COM_ESP /* XXX avoid h/w bug. */ if (!com->esp) #endif /* XXX does this flush everything? */ outb(com->iobase + com_fifo, FIFO_RCV_RST | com->fifo_image); com_events -= (com->iptr - com->ibuf); com->iptr = com->ibuf; } enable_intr(); comstart(tp); } 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 >= NSIOTOT) 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_handle); sio_timeout = hz; someopen = FALSE; for (unit = 0; unit < NSIOTOT; ++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; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); } else { /* Flush error messages, if any. */ sio_timeouts_until_log = 1; comwakeup((void *)NULL); untimeout(comwakeup, (void *)NULL, sio_timeout_handle); } } static void comwakeup(chan) void *chan; { struct com_s *com; int unit; sio_timeout_handle = timeout(comwakeup, (void *)NULL, sio_timeout); /* * Recover from lost output interrupts. * Poll any lines that don't use interrupts. */ for (unit = 0; unit < NSIOTOT; ++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 < NSIOTOT; ++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 struct siocnstate { u_char dlbl; u_char dlbh; u_char ier; u_char cfcr; u_char mcr; }; static speed_t siocngetspeed __P((Port_t, struct speedtab *)); static void siocnclose __P((struct siocnstate *sp)); static void siocnopen __P((struct siocnstate *sp)); static void siocntxwait __P((void)); static void siocntxwait() { int timo; /* * 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) ; } /* * Read the serial port specified and try to figure out what speed * it's currently running at. We're assuming the serial port has * been initialized and is basicly idle. This routine is only intended * to be run at system startup. * * If the value read from the serial port doesn't make sense, return 0. */ static speed_t siocngetspeed(iobase, table) Port_t iobase; struct speedtab *table; { int code; u_char dlbh; u_char dlbl; u_char cfcr; cfcr = inb(iobase + com_cfcr); outb(iobase + com_cfcr, CFCR_DLAB | cfcr); dlbl = inb(iobase + com_dlbl); dlbh = inb(iobase + com_dlbh); outb(iobase + com_cfcr, cfcr); code = dlbh << 8 | dlbl; for ( ; table->sp_speed != -1; table++) if (table->sp_code == code) return (table->sp_speed); return 0; /* didn't match anything sane */ } static void siocnopen(sp) struct siocnstate *sp; { int divisor; u_char dlbh; u_char dlbl; 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 | CFCR_8BITS); sp->dlbl = inb(iobase + com_dlbl); sp->dlbh = inb(iobase + com_dlbh); /* * Only set the divisor registers if they would change, since on * some 16550 incompatibles (Startech), setting them clears the * data input register. This also reduces the effects of the * UMC8669F bug. */ divisor = ttspeedtab(comdefaultrate, comspeedtab); dlbl = divisor & 0xFF; if (sp->dlbl != dlbl) outb(iobase + com_dlbl, dlbl); dlbh = (u_int) divisor >> 8; if (sp->dlbh != dlbh) outb(iobase + com_dlbh, dlbh); 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 | CFCR_8BITS); if (sp->dlbl != inb(iobase + com_dlbl)) outb(iobase + com_dlbl, sp->dlbl); if (sp->dlbh != inb(iobase + com_dlbh)) 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; { struct isa_device *dvp; int s; struct siocnstate sp; speed_t boot_speed; /* * Find our first enabled console, if any. If it is a high-level * console device, then initialize it and return successfully. * If it is a low-level console device, then initialize it and * return unsuccessfully. It must be initialized in both cases * for early use by console drivers and debuggers. Initializing * the hardware is not necessary in all cases, since the i/o * routines initialize it on the fly, but it is necessary if * input might arrive while the hardware is switched back to an * uninitialized state. We can't handle multiple console devices * yet because our low-level routines don't take a device arg. * We trust the user to set the console flags properly so that we * don't need to probe. */ cp->cn_pri = CN_DEAD; for (dvp = isa_devtab_tty; dvp->id_driver != NULL; dvp++) if (dvp->id_driver == &siodriver && dvp->id_enabled && COM_CONSOLE(dvp)) { siocniobase = dvp->id_iobase; s = spltty(); if (boothowto & RB_SERIAL) { boot_speed = siocngetspeed(siocniobase, comspeedtab); if (boot_speed) comdefaultrate = boot_speed; } siocnopen(&sp); splx(s); if (!COM_LLCONSOLE(dvp)) { cp->cn_dev = makedev(CDEV_MAJOR, dvp->id_unit); cp->cn_pri = COM_FORCECONSOLE(dvp) || boothowto & RB_SERIAL ? CN_REMOTE : CN_NORMAL; } break; } } 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 = -1; 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 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 */ /* * support PnP cards if we are using 'em */ #if NPNP > 0 static struct siopnp_ids { u_long vend_id; char *id_str; } siopnp_ids[] = { { 0x8113b04e, "Supra1381"}, { 0x9012b04e, "Supra1290"}, { 0x11007256, "USR0011"}, { 0 } }; static char *siopnp_probe(u_long csn, u_long vend_id); static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev); static u_long nsiopnp = NSIO; static struct pnp_device siopnp = { "siopnp", siopnp_probe, siopnp_attach, &nsiopnp, &tty_imask }; DATA_SET (pnpdevice_set, siopnp); static char * siopnp_probe(u_long csn, u_long vend_id) { struct siopnp_ids *ids; char *s = NULL; for(ids = siopnp_ids; ids->vend_id != 0; ids++) { if (vend_id == ids->vend_id) { s = ids->id_str; break; } } if (s) { struct pnp_cinfo d; read_pnp_parms(&d, 0); if (d.enable == 0 || d.flags & 1) { printf("CSN %d is disabled.\n", csn); return (NULL); } } return (s); } static void siopnp_attach(u_long csn, u_long vend_id, char *name, struct isa_device *dev) { struct pnp_cinfo d; struct isa_device *dvp; if (dev->id_unit >= NSIOTOT) return; if (read_pnp_parms(&d, 0) == 0) { printf("failed to read pnp parms\n"); return; } write_pnp_parms(&d, 0); enable_pnp_card(); dev->id_iobase = d.port[0]; dev->id_irq = (1 << d.irq[0]); dev->id_intr = siointr; dev->id_ri_flags = RI_FAST; dev->id_drq = -1; if (dev->id_driver == NULL) { dev->id_driver = &siodriver; dvp = find_isadev(isa_devtab_tty, &siodriver, 0); if (dvp != NULL) dev->id_id = dvp->id_id; } if ((dev->id_alive = sioprobe(dev)) != 0) sioattach(dev); else printf("sio%d: probe failed\n", dev->id_unit); } #endif #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; 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 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 */