/* * Mach Operating System * Copyright (c) 1994,1993,1991,1990 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ #if NCOM > 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void comparam(); static vm_offset_t com_std[NCOM] = { 0 }; struct bus_device *cominfo[NCOM]; struct bus_driver comdriver = { comprobe, 0, comattach, 0, com_std, "com", cominfo, 0, 0, 0}; struct tty com_tty[NCOM]; int commodem[NCOM]; int comcarrier[NCOM] = {0, 0,}; boolean_t comfifo[NCOM]; boolean_t comtimer_active; int comtimer_state[NCOM]; #define RCBAUD B9600 static int rcline = -1; static struct bus_device *comcndev; /* XX */ extern char *kernel_cmdline; #ifndef PORTSELECTOR #define ISPEED B9600 #define IFLAGS (EVENP|ODDP|ECHO|CRMOD) #else #define ISPEED B4800 #define IFLAGS (EVENP|ODDP) #endif u_short divisorreg[] = { 0, 2304, 1536, 1047, /* 0, 50, 75, 110*/ 857, 768, 576, 384, 192, /* 134.5, 150, 200, 300, 600*/ 96, 64, 48, /* 1200, 1800, 2000, 2400 */ 24, 12, /* 3600, 4800, 7200, 9600 */ 6, 3, 2, 1}; /* 19200, 38400, 56000,115200 */ /* * * Probes are called during kernel boot: return 1 to mean that * the relevant device is present today. * */ int comprobe_general(struct bus_device *dev, int noisy) { u_short addr = dev->address; int unit = dev->unit; int oldctl, oldmsb; char *type = "8250"; int i; if ((unit < 0) || (unit > NCOM)) { printf("com %d out of range\n", unit); return(0); } oldctl = inb(LINE_CTL(addr)); /* Save old value of LINE_CTL */ oldmsb = inb(BAUD_MSB(addr)); /* Save old value of BAUD_MSB */ outb(LINE_CTL(addr), 0); /* Select INTR_ENAB */ outb(BAUD_MSB(addr), 0); if (inb(BAUD_MSB(addr)) != 0) { outb(LINE_CTL(addr), oldctl); outb(BAUD_MSB(addr), oldmsb); return 0; } outb(LINE_CTL(addr), iDLAB); /* Select BAUD_MSB */ outb(BAUD_MSB(addr), 255); if (inb(BAUD_MSB(addr)) != 255) { outb(LINE_CTL(addr), oldctl); outb(BAUD_MSB(addr), oldmsb); return 0; } outb(LINE_CTL(addr), 0); /* Select INTR_ENAB */ if (inb(BAUD_MSB(addr)) != 0) /* Check that it has kept its value*/ { outb(LINE_CTL(addr), oldctl); outb(BAUD_MSB(addr), oldmsb); return 0; } /* Com port found, now check what chip it has */ for(i = 0; i < 256; i++) /* Is there Scratch register */ { outb(SCR(addr), i); if (inb(SCR(addr)) != i) break; } if (i == 256) { /* Yes == 450 or 460 */ outb(SCR(addr), 0); type = "82450 or 16450"; outb(FIFO_CTL(addr), iFIFOENA | iFIFO14CH); /* Enable fifo */ if ((inb(FIFO_CTL(addr)) & iFIFO14CH) != 0) { /* Was it successful */ /* if both bits are not set then broken xx550 */ if ((inb(FIFO_CTL(addr)) & iFIFO14CH) == iFIFO14CH) { type = "82550 or 16550"; comfifo[unit] = TRUE; } else { type = "82550 or 16550 with non-working FIFO"; } outb(INTR_ID(addr), 0x00); /* Disable fifos */ } } if (noisy) printf("com%d: %s chip.\n", unit, type); return 1; } /* * Probe routine for use during kernel startup when it is probing * all of bus_device_init */ int comprobe(vm_offset_t port, struct bus_ctlr *dev) { return comprobe_general((struct bus_device *)dev, /*noisy*/ 0); } /* * Probe routine for use by the console */ int comcnprobe(struct consdev *cp) { struct bus_device *b; int maj, unit, pri; #define CONSOLE_PARAMETER " console=com" u_char *console = (u_char *) strstr(kernel_cmdline, CONSOLE_PARAMETER); if (console) mach_atoi(console + strlen(CONSOLE_PARAMETER), &rcline); maj = 0; unit = -1; pri = CN_DEAD; for (b = bus_device_init; b->driver; b++) if (strcmp(b->name, "com") == 0 && b->unit == rcline && comprobe_general(b, /*quiet*/ 0)) { /* Found one */ comcndev = b; unit = b->unit; pri = CN_REMOTE; break; } cp->cn_dev = makedev(maj, unit); cp->cn_pri = pri; return 0; } /* * * Device Attach's are called during kernel boot, but only if the matching * device Probe returned a 1. * */ void comattach(struct bus_device *dev) { u_char unit = dev->unit; u_short addr = dev->address; take_dev_irq(dev); printf(", port = %lx, spl = %ld, pic = %d. (DOS COM%d)", dev->address, dev->sysdep, dev->sysdep1, unit+1); /* comcarrier[unit] = addr->flags;*/ commodem[unit] = 0; outb(INTR_ENAB(addr), 0); outb(MODEM_CTL(addr), 0); while (!(inb(INTR_ID(addr))&1)) { (void) inb(LINE_STAT (addr)); /* reset overrun error etc */ (void) inb(TXRX (addr)); /* reset data-ready */ (void) inb(MODEM_STAT(addr)); /* reset modem status reg */ } } /* * Attach/init routine for console. This isn't called by * configure_bus_device which sets the alive, adaptor, and minfo * fields of the bus_device struct (comattach is), therefore we do * that by hand. */ int comcninit(struct consdev *cp) { u_char unit = comcndev->unit; u_short addr = comcndev->address; take_dev_irq(comcndev); comcndev->alive = 1; comcndev->adaptor = 0; cominfo[minor(cp->cn_dev)] = comcndev; outb(LINE_CTL(addr), iDLAB); outb(BAUD_LSB(addr), divisorreg[RCBAUD] & 0xff); outb(BAUD_MSB(addr), divisorreg[RCBAUD] >>8); outb(LINE_CTL(addr), i8BITS); outb(INTR_ENAB(addr), 0); outb(MODEM_CTL(addr), iDTR|iRTS|iOUT2); { char msg[128]; volatile unsigned char *p = (volatile unsigned char *)0xb8000; int i; sprintf(msg, " **** using COM port %d for console ****", unit+1); for (i = 0; msg[i]; i++) { p[2*i] = msg[i]; p[2*i+1] = (0<<7) /* blink */ | (0x0<<4) /* bg */ | (1<<3) /* hi-intensity */ | 0x4; /* fg */ } } return 0; } /* * Probe for COM after autoconfiguration. * Used to handle PCMCIA modems, which may appear * at any time. */ boolean_t com_reprobe( int unit) { struct bus_device *device; /* * Look for COM device in the device * initialization list. It must not be alive * (otherwise we would have opened it already). */ for (device = bus_device_init; device->driver; device++) { if (device->driver == &comdriver && device->unit == unit && !device->alive && device->ctlr == (char)-1) { /* * Found an entry for com port . * Probe it. */ if (configure_bus_device(device->name, device->address, device->phys_address, 0, "atbus")) return TRUE; } } return FALSE; } io_return_t comopen( dev_t dev, int flag, io_req_t ior) { int unit = minor(dev); u_short addr; struct bus_device *isai; struct tty *tp; spl_t s; io_return_t result; if (unit >= NCOM) return D_NO_SUCH_DEVICE; /* no such device */ if ((isai = cominfo[unit]) == 0 || isai->alive == 0) { /* * Try to probe it again */ if (!com_reprobe(unit)) return D_NO_SUCH_DEVICE; if ((isai = cominfo[unit]) == 0 || isai->alive == 0) return D_NO_SUCH_DEVICE; } tp = &com_tty[unit]; if ((tp->t_state & (TS_ISOPEN|TS_WOPEN)) == 0) { ttychars(tp); tp->t_addr = (char *)isai->address; tp->t_dev = dev; tp->t_oproc = comstart; tp->t_stop = comstop; tp->t_mctl = commctl; tp->t_getstat = comgetstat; tp->t_setstat = comsetstat; #ifndef PORTSELECTOR if (tp->t_ispeed == 0) { #else tp->t_state |= TS_HUPCLS; #endif /* PORTSELECTOR */ tp->t_ispeed = ISPEED; tp->t_ospeed = ISPEED; tp->t_flags = IFLAGS; tp->t_state &= ~TS_BUSY; #ifndef PORTSELECTOR } #endif /* PORTSELECTOR */ } /*rvb tp->t_state |= TS_WOPEN; */ if ((tp->t_state & TS_ISOPEN) == 0) comparam(unit); addr = (int)tp->t_addr; s = spltty(); if (!comcarrier[unit]) /* not originating */ tp->t_state |= TS_CARR_ON; else { int modem_stat = inb(MODEM_STAT(addr)); if (modem_stat & iRLSD) tp->t_state |= TS_CARR_ON; else tp->t_state &= ~TS_CARR_ON; fix_modem_state(unit, modem_stat); } splx(s); result = char_open(dev, tp, flag, ior); if (!comtimer_active) { comtimer_active = TRUE; comtimer(NULL); } s = spltty(); while(!(inb(INTR_ID(addr))&1)) { /* while pending interrupts */ (void) inb(LINE_STAT (addr)); /* reset overrun error */ (void) inb(TXRX (addr)); /* reset data-ready */ (void) inb(MODEM_STAT(addr)); /* reset modem status */ } splx(s); return result; } void comclose(dev, flag) dev_t dev; int flag; { struct tty *tp = &com_tty[minor(dev)]; u_short addr = (int)tp->t_addr; ttyclose(tp); if (tp->t_state&TS_HUPCLS || (tp->t_state&TS_ISOPEN)==0) { outb(INTR_ENAB(addr), 0); outb(MODEM_CTL(addr), 0); tp->t_state &= ~TS_BUSY; commodem[minor(dev)] = 0; if (comfifo[minor(dev)] != 0) outb(INTR_ID(addr), 0x00); /* Disable fifos */ } return; } io_return_t comread(dev, ior) dev_t dev; io_req_t ior; { return char_read(&com_tty[minor(dev)], ior); } io_return_t comwrite(dev, ior) dev_t dev; io_req_t ior; { return char_write(&com_tty[minor(dev)], ior); } io_return_t comportdeath(dev, port) dev_t dev; mach_port_t port; { return (tty_portdeath(&com_tty[minor(dev)], (ipc_port_t)port)); } io_return_t comgetstat(dev, flavor, data, count) dev_t dev; dev_flavor_t flavor; dev_status_t data; /* pointer to OUT array */ mach_msg_type_number_t *count; /* out */ { io_return_t result = D_SUCCESS; int unit = minor(dev); switch (flavor) { case TTY_MODEM: fix_modem_state(unit, inb(MODEM_STAT(cominfo[unit]->address))); *data = commodem[unit]; *count = 1; break; default: result = tty_get_status(&com_tty[unit], flavor, data, count); break; } return (result); } io_return_t comsetstat( dev_t dev, dev_flavor_t flavor, dev_status_t data, mach_msg_type_number_t count) { io_return_t result = D_SUCCESS; int unit = minor(dev); struct tty *tp = &com_tty[unit]; switch (flavor) { case TTY_SET_BREAK: commctl(tp, TM_BRK, DMBIS); break; case TTY_CLEAR_BREAK: commctl(tp, TM_BRK, DMBIC); break; case TTY_MODEM: commctl(tp, *data, DMSET); break; default: result = tty_set_status(&com_tty[unit], flavor, data, count); if (result == D_SUCCESS && flavor == TTY_STATUS) comparam(unit); return (result); } return (D_SUCCESS); } void comintr(int unit) { struct tty *tp = &com_tty[unit]; u_short addr = cominfo[unit]->address; static char comoverrun = 0; char c, line, intr_id; int line_stat; while (! ((intr_id=(inb(INTR_ID(addr))&MASKi)) & 1)) switch (intr_id) { case MODi: /* modem change */ commodem_intr(unit, inb(MODEM_STAT(addr))); break; case TRAi: comtimer_state[unit] = 0; tp->t_state &= ~(TS_BUSY|TS_FLUSH); tt_write_wakeup(tp); (void) comstart(tp); break; case RECi: case CTIi: /* Character timeout indication */ if (tp->t_state&TS_ISOPEN) { int escape = 0; while ((line = inb(LINE_STAT(addr))) & iDR) { c = inb(TXRX(addr)); if (c == 0x1b) { escape = 1; continue; } #if MACH_KDB if (escape && c == 'D'-('A'-1)) /* ctrl-alt-d pressed, invoke debugger */ kdb_kintr(); else #endif /* MACH_KDB */ if (escape) { ttyinput(0x1b, tp); ttyinput(c, tp); } else ttyinput(c, tp); escape = 0; } if (escape) /* just escape */ ttyinput(0x1b, tp); } else tt_open_wakeup(tp); break; case LINi: line_stat = inb(LINE_STAT(addr)); if ((line_stat & iPE) && ((tp->t_flags&(EVENP|ODDP)) == EVENP || (tp->t_flags&(EVENP|ODDP)) == ODDP)) { /* parity error */; } else if (line_stat&iOR && !comoverrun) { printf("com%d: overrun\n", unit); comoverrun = 1; } else if (line_stat & (iFE | iBRKINTR)) { /* framing error or break */ ttyinput(tp->t_breakc, tp); } break; } } static void comparam(int unit) { struct tty *tp = &com_tty[unit]; u_short addr = (int)tp->t_addr; spl_t s = spltty(); int mode; if (tp->t_ispeed == B0) { tp->t_state |= TS_HUPCLS; outb(MODEM_CTL(addr), iOUT2); commodem[unit] = 0; splx(s); return; } /* Do input buffering */ if (tp->t_ispeed >= B300) tp->t_state |= TS_MIN; outb(LINE_CTL(addr), iDLAB); outb(BAUD_LSB(addr), divisorreg[tp->t_ispeed] & 0xff); outb(BAUD_MSB(addr), divisorreg[tp->t_ispeed] >> 8); if (tp->t_flags & (RAW|LITOUT|PASS8)) mode = i8BITS; else mode = i7BITS | iPEN; if (tp->t_flags & EVENP) mode |= iEPS; if (tp->t_ispeed == B110) /* * 110 baud uses two stop bits - * all other speeds use one */ mode |= iSTB; outb(LINE_CTL(addr), mode); outb(INTR_ENAB(addr), iTX_ENAB|iRX_ENAB|iMODEM_ENAB|iERROR_ENAB); if (comfifo[unit]) outb(FIFO_CTL(addr), iFIFOENA|iFIFO14CH); outb(MODEM_CTL(addr), iDTR|iRTS|iOUT2); commodem[unit] |= (TM_DTR|TM_RTS); splx(s); } void comparm(int unit, int baud, int intr, int mode, int modem) { u_short addr = (u_short)(cominfo[unit]->address); spl_t s = spltty(); if (unit != 0 && unit != 1) { printf("comparm(unit, baud, mode, intr, modem)\n"); splx(s); return; } outb(LINE_CTL(addr), iDLAB); outb(BAUD_LSB(addr), divisorreg[baud] & 0xff); outb(BAUD_MSB(addr), divisorreg[baud] >> 8); outb(LINE_CTL(addr), mode); outb(INTR_ENAB(addr), intr); outb(MODEM_CTL(addr), modem); splx(s); } int comst_1, comst_2, comst_3, comst_4, comst_5 = 14; void comstart(struct tty *tp) { int nch; #if 0 int i; #endif if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP|TS_BUSY)) { comst_1++; return; } if ((!queue_empty(&tp->t_delayed_write)) && (tp->t_outq.c_cc <= TTLOWAT(tp))) { comst_2++; tt_write_wakeup(tp); } if (!tp->t_outq.c_cc) { comst_3++; return; } #if 0 i = (comfifo[minor(tp->t_dev)]) ? /*14*/comst_5 : 1; tp->t_state |= TS_BUSY; while (i-- > 0) { nch = getc(&tp->t_outq); if (nch == -1) break; if ((nch & 0200) && ((tp->t_flags & LITOUT) == 0)) { timeout(ttrstrt, (char *)tp, (nch & 0x7f) + 6); tp->t_state |= TS_TIMEOUT; comst_4++; return(0); } outb(TXRX((int)tp->t_addr), nch); } #else nch = getc(&tp->t_outq); if (nch == -1) return; if ((nch & 0200) && ((tp->t_flags & LITOUT) == 0)) { timeout((timer_func_t *)ttrstrt, (char *)tp, (nch & 0x7f) + 6); tp->t_state |= TS_TIMEOUT; comst_4++; return; } outb(TXRX((int)tp->t_addr), nch); tp->t_state |= TS_BUSY; #endif } /* Check for stuck xmitters */ int comtimer_interval = 5; void comtimer(void * param) { spl_t s = spltty(); struct tty *tp = com_tty; int i, nch; for (i = 0; i < NCOM; i++, tp++) { if ((tp->t_state & TS_ISOPEN) == 0) continue; if (!tp->t_outq.c_cc) continue; if (++comtimer_state[i] < 2) continue; /* Its stuck */ printf("Tty %p was stuck\n", tp); nch = getc(&tp->t_outq); outb(TXRX((int)tp->t_addr), nch); } splx(s); timeout(comtimer, 0, comtimer_interval*hz); } /* * Set receive modem state from modem status register. */ void fix_modem_state( int unit, int modem_stat) { int stat = 0; if (modem_stat & iCTS) stat |= TM_CTS; /* clear to send */ if (modem_stat & iDSR) stat |= TM_DSR; /* data set ready */ if (modem_stat & iRI) stat |= TM_RNG; /* ring indicator */ if (modem_stat & iRLSD) stat |= TM_CAR; /* carrier? */ commodem[unit] = (commodem[unit] & ~(TM_CTS|TM_DSR|TM_RNG|TM_CAR)) | stat; } /* * Modem change (input signals) */ void commodem_intr( int unit, int stat) { int changed; changed = commodem[unit]; fix_modem_state(unit, stat); stat = commodem[unit]; /* Assumption: if the other party can handle modem signals then it should handle all the necessary ones. Else fix the cable. */ changed ^= stat; /* what changed ? */ if (changed & TM_CTS) tty_cts( &com_tty[unit], stat & TM_CTS ); #if 0 if (changed & TM_CAR) ttymodem( &com_tty[unit], stat & TM_CAR ); #endif } /* * Set/get modem bits */ int commctl( struct tty *tp, int bits, int how) { spl_t s; int unit; vm_offset_t dev_addr; int b = 0; /* Suppress gcc warning */ unit = minor(tp->t_dev); if (bits == TM_HUP) { /* close line (internal) */ bits = TM_DTR | TM_RTS; how = DMBIC; } if (how == DMGET) return commodem[unit]; dev_addr = cominfo[unit]->address; s = spltty(); switch (how) { case DMSET: b = bits; break; case DMBIS: b = commodem[unit] | bits; break; case DMBIC: b = commodem[unit] & ~bits; break; } commodem[unit] = b; if (bits & TM_BRK) { if (b & TM_BRK) { outb(LINE_CTL(dev_addr), inb(LINE_CTL(dev_addr)) | iSETBREAK); } else { outb(LINE_CTL(dev_addr), inb(LINE_CTL(dev_addr)) & ~iSETBREAK); } } #if 0 /* do I need to do something on this ? */ if (bits & TM_LE) { /* line enable */ } #endif #if 0 /* Unsupported */ if (bits & TM_ST) { /* secondary transmit */ } if (bits & TM_SR) { /* secondary receive */ } #endif if (bits & (TM_DTR|TM_RTS)) { /* data terminal ready, request to send */ how = iOUT2; if (b & TM_DTR) how |= iDTR; if (b & TM_RTS) how |= iRTS; outb(MODEM_CTL(dev_addr), how); } splx(s); /* the rest are inputs */ return commodem[unit]; } void comstop( struct tty *tp, int flags) { if ((tp->t_state & TS_BUSY) && (tp->t_state & TS_TTSTOP) == 0) tp->t_state |= TS_FLUSH; } /* * * Code to be called from debugger. * */ void compr_addr(vm_offset_t addr) { /* The two line_stat prints may show different values, since * touching some of the registers constitutes changing them. */ printf("LINE_STAT(%lu) %x\n", LINE_STAT(addr), inb(LINE_STAT(addr))); printf("TXRX(%lu) %x, INTR_ENAB(%lu) %x, INTR_ID(%lu) %x, LINE_CTL(%lu) %x,\n\ MODEM_CTL(%lu) %x, LINE_STAT(%lu) %x, MODEM_STAT(%lu) %x\n", TXRX(addr), inb(TXRX(addr)), INTR_ENAB(addr), inb(INTR_ENAB(addr)), INTR_ID(addr), inb(INTR_ID(addr)), LINE_CTL(addr), inb(LINE_CTL(addr)), MODEM_CTL(addr), inb(MODEM_CTL(addr)), LINE_STAT(addr), inb(LINE_STAT(addr)), MODEM_STAT(addr),inb(MODEM_STAT(addr))); } int compr(int unit) { compr_addr(cominfo[unit]->address); return(0); } int comgetc(int unit) { u_short addr = (u_short)(cominfo[unit]->address); spl_t s = spltty(); int c; while((inb(LINE_STAT(addr)) & iDR) == 0) ; c = inb(TXRX(addr)); splx(s); return c; } /* * Routines for the console */ int comcnputc(dev_t dev, int c) { u_short addr = (u_short)(cominfo[minor(dev)]->address); /* Wait for transmitter to empty */ while((inb(LINE_STAT(addr)) & iTHRE) == 0) continue; /* send the char */ if (c == '\n') comcnputc(dev, '\r'); outb(addr, c); return 0; } int comcngetc(dev_t dev, int wait) { u_short addr = (u_short)(cominfo[minor(dev)]->address); int c; while((inb(LINE_STAT(addr)) & iDR) == 0) if (! wait) return 0; c = inb(TXRX(addr)); return c & 0x7f; } #endif /* NCOM */