/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright (c) 2019 Peter Tribble. */ /* * Asynchronous protocol handler for Z8530 chips * Handles normal UNIX support for terminals & modems */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * PPS (Pulse Per Second) support. */ extern void ddi_hardpps(struct timeval *, int); static struct ppsclockev ppsclockev; #ifdef PPSCLOCKLED /* XXX Use these to observe PPS latencies and jitter on a scope */ #define LED_ON #define LED_OFF #else #define LED_ON #define LED_OFF #endif #define ZSA_RCV_SIZE 64 #define ZA_KICK_RCV_COUNT 3 #define ZSA_GRACE_MIN_FLOW_CONTROL 5 #define ZSA_GRACE_MAX_FLOW_CONTROL 20 int zsasoftdtr = 0; /* if nonzero, softcarrier raises dtr at attach */ int zsb134_weird = 0; /* if set, old weird B134 behavior */ int g_zsticks = 0; /* if set, becomes the global zsticks value */ int g_nocluster = 0; /* if set, disables clustering of received data */ unsigned int zsa_rstandby = ZSA_MIN_RSTANDBY; unsigned int zsa_rdone = ZSA_RDONE_MIN; unsigned int zsa_grace_flow_control = ZSA_GRACE_MIN_FLOW_CONTROL; #define NSPEED 18 /* max # of speeds */ ushort_t zs_speeds[NSPEED] = { 0, ZSPEED(50), /* 1 */ ZSPEED(75), /* 2 */ ZSPEED(110), /* 3 */ #ifdef lint ZSPEED(134), /* 4 */ #else ZSPEED(269/2), /* XXX - This is sleazy */ #endif ZSPEED(150), /* 5 */ ZSPEED(200), /* 6 */ ZSPEED(300), /* 7 */ ZSPEED(600), /* 8 */ ZSPEED(1200), /* 9 */ ZSPEED(1800), /* 10 */ ZSPEED(2400), /* 11 */ ZSPEED(4800), /* 12 */ ZSPEED(9600), /* 13 */ ZSPEED(19200), /* 14 */ ZSPEED(38400), /* 15 */ ZSPEED(57680), /* 16 */ ZSPEED(76800) /* 17 */ }; ushort_t zsticks[NSPEED] = { 3, /* 0 */ 3, /* 1 */ 3, /* 2 */ 3, /* 3 */ 3, /* 4 */ 3, /* 5 */ 3, /* 6 */ 3, /* 7 */ 3, /* 8 */ 3, /* 9 */ 3, /* 10 */ 3, /* 11 */ 3, /* 12 */ 3, /* 13 */ 2, /* 14 */ 1, /* 15 */ 1, /* 16 */ 1 /* 17 */ }; #define ztdelay(nsp) (zsdelay[(nsp)]*(hz/100)) ushort_t zsdelay[NSPEED] = { 0, ZDELAY(50), /* 1 */ ZDELAY(75), /* 2 */ ZDELAY(110), /* 3 */ #ifdef lint ZDELAY(134), /* 4 */ #else ZDELAY(269/2), #endif ZDELAY(150), /* 5 */ ZDELAY(200), /* 6 */ ZDELAY(300), /* 7 */ ZDELAY(600), /* 8 */ ZDELAY(1200), /* 9 */ ZDELAY(1800), /* 10 */ ZDELAY(2400), /* 11 */ ZDELAY(4800), /* 12 */ ZDELAY(9600), /* 13 */ ZDELAY(19200), /* 14 */ ZDELAY(38400), /* 15 */ ZDELAY(57600), /* 16 */ ZDELAY(76800) /* 17 */ }; ushort_t zslowat[NSPEED] = { 3, /* 0 */ 3, /* 1 */ 3, /* 2 */ 3, /* 3 */ 3, /* 4 */ 3, /* 5 */ 3, /* 6 */ 2, /* 7 */ 2, /* 8 */ 2, /* 9 */ 2, /* 10 */ 1, /* 11 */ 1, /* 12 */ 1, /* 13 */ 1, /* 14 */ 1, /* 15 */ 1, /* 16 */ 1 /* 17 */ }; ushort_t zshiwat[NSPEED] = { 0, /* 0 */ 1, /* 1 */ 1, /* 2 */ 1, /* 3 */ 1, /* 4 */ 1, /* 5 */ 1, /* 6 */ 1, /* 7 */ 1, /* 8 */ 1, /* 9 */ 1, /* 10 */ 1, /* 11 */ 1, /* 12 */ 3, /* 13 */ 3, /* 14 */ 4, /* 15 */ 4, /* 16 */ 4 /* 17 */ }; #define SLAVIO_BUG /* this workaround required to fix bug 1102778 */ #define SPEED(cflag) \ ((cflag) & CBAUDEXT) ? \ (((cflag) & 0x1) + CBAUD + 1) : ((cflag) & CBAUD) /* * Special macros to handle STREAMS operations. * These are required to address memory leakage problems. * WARNING : the macro do NOT call ZSSETSOFT */ /* * Should be called holding only the adaptive (zs_excl) mutex. */ #define ZSA_GETBLOCK(zs, allocbcount) \ { \ register int n = zsa_rstandby; \ while (--n >= 0 && allocbcount > 0) { \ if (!za->za_rstandby[n]) { \ if ((za->za_rstandby[n] = allocb(ZSA_RCV_SIZE, \ BPRI_MED)) == NULL) { \ if (za->za_bufcid == 0) { \ za->za_bufcid = bufcall(ZSA_RCV_SIZE, \ BPRI_MED, \ zsa_callback, zs); \ break; \ } \ } \ allocbcount--; \ } \ } \ if (za->za_ttycommon.t_cflag & CRTSXOFF) { \ mutex_enter(zs->zs_excl_hi); \ if (!(zs->zs_wreg[5] & ZSWR5_RTS)) { \ register int usedcnt = 0; \ for (n = 0; n < zsa_rstandby; n++) \ if (!za->za_rstandby[n]) \ usedcnt++; \ if ((ushort_t)usedcnt <= \ zslowat[SPEED(za->za_ttycommon.t_cflag)]) \ SCC_BIS(5, ZSWR5_RTS); \ } \ mutex_exit(zs->zs_excl_hi); \ } \ } /* * Should be called holding the spin (zs_excl_hi) mutex. */ #define ZSA_ALLOCB(mp) \ { \ register int n = zsa_rstandby; \ while (--n >= 0) { \ if ((mp = za->za_rstandby[n]) != NULL) { \ za->za_rstandby[n] = NULL; \ break; \ } \ } \ if (za->za_ttycommon.t_cflag & CRTSXOFF) { \ if (!mp) { \ if (zs->zs_wreg[5] & ZSWR5_RTS) \ SCC_BIC(5, ZSWR5_RTS); \ cmn_err(CE_WARN, "zs%d: message lost\n", \ UNIT(za->za_dev)); \ } else if (zs->zs_wreg[5] & ZSWR5_RTS) { \ register int usedcnt = 0; \ for (n = 0; n < zsa_rstandby; n++) \ if (!za->za_rstandby[n]) \ usedcnt++; \ if ((ushort_t)usedcnt >= (zsa_rstandby - \ zshiwat[SPEED(za->za_ttycommon.t_cflag)])) \ SCC_BIC(5, ZSWR5_RTS); \ } \ } \ } /* * Should get the spin (zs_excl_hi) mutex. */ #define ZSA_QREPLY(q, mp) \ { \ mutex_enter(zs->zs_excl_hi); \ ZSA_PUTQ(mp); \ ZSSETSOFT(zs); \ mutex_exit(zs->zs_excl_hi); \ } /* * Should be called holding the spin (zs_excl_hi) mutex. */ #define ZSA_PUTQ(mp) \ { \ register int wptr, rptr; \ wptr = za->za_rdone_wptr; \ rptr = za->za_rdone_rptr; \ za->za_rdone[wptr] = mp; \ if ((wptr)+1 == zsa_rdone) { \ za->za_rdone_wptr = wptr = 0; \ } else \ za->za_rdone_wptr = ++wptr; \ if (wptr == rptr) { \ SCC_BIC(1, ZSWR1_INIT); \ cmn_err(CE_WARN, "zs%d disabled: input buffer overflow", \ UNIT(za->za_dev)); \ } \ } /* * Should be called holding the spin (zs_excl_hi) mutex. */ #define ZSA_KICK_RCV \ { \ register mblk_t *mp = za->za_rcvblk; \ if (mp) { \ if (zs->zs_rd_cur) { /* M_DATA */ \ mp->b_wptr = zs->zs_rd_cur; \ zs->zs_rd_cur = NULL; \ zs->zs_rd_lim = NULL; \ } \ za->za_rcvblk = NULL; \ ZSA_PUTQ(mp); \ ZSSETSOFT(zs); \ } \ } #define ZSA_SEEQ(mp) \ { \ if (za->za_rdone_rptr != za->za_rdone_wptr) { \ mp = za->za_rdone[za->za_rdone_rptr]; \ } else { \ mp = NULL; \ } \ } /* * Should be called holding only the adaptive (zs_excl) mutex. */ #define ZSA_GETQ(mp) \ { \ if (za->za_rdone_rptr != za->za_rdone_wptr) { \ mp = za->za_rdone[za->za_rdone_rptr]; \ za->za_rdone[za->za_rdone_rptr++] = NULL; \ if (za->za_rdone_rptr == zsa_rdone) \ za->za_rdone_rptr = 0; \ } else { \ mp = NULL; \ } \ } /* * Should be called holding only the adaptive (zs_excl) mutex. */ #define ZSA_FLUSHQ \ { \ register mblk_t *tmp; \ for (;;) { \ ZSA_GETQ(tmp); \ if (!(tmp)) \ break; \ freemsg(tmp); \ } \ } /* * Logging definitions */ #ifdef ZSA_DEBUG #ifdef ZS_DEBUG_ALL extern char zs_h_log[]; extern int zs_h_log_n; #define zsa_h_log_clear #define zsa_h_log_add(c) \ { \ if (zs_h_log_n >= ZS_H_LOG_MAX) \ zs_h_log_n = 0; \ zs_h_log[zs_h_log_n++] = 'A' + zs->zs_unit; \ zs_h_log[zs_h_log_n++] = c; \ zs_h_log[zs_h_log_n] = '\0'; \ } #else /* ZS_DEBUG_ALL */ #define ZSA_H_LOG_MAX 0x4000 char zsa_h_log[40][ZSA_H_LOG_MAX +10]; int zsa_h_log_n[40]; #define zsa_h_log_add(c) \ { \ if (zsa_h_log_n[zs->zs_unit] >= ZSA_H_LOG_MAX) \ zsa_h_log_n[zs->zs_unit] = 0; \ zsa_h_log[zs->zs_unit][zsa_h_log_n[zs->zs_unit]++] = c; \ zsa_h_log[zs->zs_unit][zsa_h_log_n[zs->zs_unit]] = '\0'; \ } #define zsa_h_log_clear \ { \ register char *p; \ for (p = &zsa_h_log[zs->zs_unit][ZSA_H_LOG_MAX]; \ p >= &zsa_h_log[zs->zs_unit][0]; /* null */) \ *p-- = '\0'; \ zsa_h_log_n[zs->zs_unit] = 0; \ } #endif /* ZS_DEBUG_ALL */ #define ZSA_R0_LOG(r0) \ { \ if (r0 & ZSRR0_RX_READY) zsa_h_log_add('R'); \ if (r0 & ZSRR0_TIMER) zsa_h_log_add('Z'); \ if (r0 & ZSRR0_TX_READY) zsa_h_log_add('T'); \ if (r0 & ZSRR0_CD) zsa_h_log_add('D'); \ if (r0 & ZSRR0_SYNC) zsa_h_log_add('S'); \ if (r0 & ZSRR0_CTS) zsa_h_log_add('C'); \ if (r0 & ZSRR0_TXUNDER) zsa_h_log_add('U'); \ if (r0 & ZSRR0_BREAK) zsa_h_log_add('B'); \ } #else /* ZSA_DEBUG */ #define zsa_h_log_clear #define zsa_h_log_add(c) #define ZSA_R0_LOG(r0) #endif /* ZSA_DEBUG */ static int zsa_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr); static int zsa_close(queue_t *q, int flag, cred_t *cr); static void zsa_wput(queue_t *q, mblk_t *mp); static void zsa_rsrv(queue_t *q); static struct module_info asyncm_info = { 0, "zs", 0, INFPSZ, 2048, 128 }; static struct qinit async_rinit = { putq, (int (*)())zsa_rsrv, zsa_open, zsa_close, NULL, &asyncm_info, NULL }; static struct qinit async_winit = { (int (*)())zsa_wput, NULL, NULL, NULL, NULL, &asyncm_info, NULL }; struct streamtab asynctab = { &async_rinit, &async_winit, NULL, NULL, }; /* * The async interrupt entry points. */ static void zsa_txint(struct zscom *zs); static void zsa_xsint(struct zscom *zs); static void zsa_rxint(struct zscom *zs); static void zsa_srint(struct zscom *zs); static int zsa_softint(struct zscom *zs); static int zsa_suspend(struct zscom *zs); static int zsa_resume(struct zscom *zs); static void zsa_null(struct zscom *zs) { /* LINTED */ register short c; SCC_WRITE0(ZSWR0_RESET_TXINT); SCC_WRITE0(ZSWR0_RESET_STATUS); c = SCC_READDATA(); ZSDELAY(); SCC_WRITE0(ZSWR0_RESET_ERRORS); } /*ARGSUSED*/ static int zsa_null_int(struct zscom *zs) { return (0); } struct zsops zsops_null_async = { zsa_null, zsa_null, zsa_null, zsa_null, zsa_null_int, zsa_null_int, zsa_null_int }; struct zsops zsops_async = { zsa_txint, zsa_xsint, zsa_rxint, zsa_srint, zsa_softint, zsa_suspend, zsa_resume }; static int dmtozs(int bits); static int zstodm(int bits); static void zsa_restart(void *); static void zsa_reioctl(void *); static void zsa_ioctl(struct asyncline *za, queue_t *q, mblk_t *mp); static void zsa_program(struct asyncline *za, int setibaud); static void zsa_start(struct zscom *zs); static void zsa_kick_rcv(void *); static void zsa_callback(void *); static void zsa_set_za_rcv_flags_mask(struct asyncline *za); int zsgetspeed(dev_t dev); static boolean_t abort_charseq_recognize(uchar_t ch); /* ARGSUSED */ int zsc_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { register dev_t dev = (dev_t)arg; register int unit, error; register struct zscom *zs; if ((unit = UNIT(dev)) >= nzs) return (DDI_FAILURE); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: zs = &zscom[unit]; *result = zs->zs_dip; error = DDI_SUCCESS; break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)(uintptr_t)(unit / 2); error = DDI_SUCCESS; break; default: error = DDI_FAILURE; } return (error); } /* * The Asynchronous Driver. */ /* * Determine if the zsminor device is in use as either a stdin or stdout * device, so we can be careful about how we initialize the DUART, if * it is, in fact, in use. * * Since this is expensive, we do it once and store away the answers, * since this gets called a number of times per phyical zs device. * Perhaps, this should be in a loadable module, so it can get thrown * away after all the zs devices are attached? */ /* * To determine if a given unit is being used by the PROM, * we need to map stdin/stdout devices as known to the PROM * to zs internal minor device numbers: * * PROM (real device) zs minor device * * "zs", 0, "a" 0 ttya * "zs", 0, "b" 1 ttyb * "zs", 1, "a" 2 keyboard * "zs", 1, "b" 3 mouse * "zs", 2, "a" 4 ttyc * "zs", 2, "b" 5 ttyd * * The following value mapping lines assume that insource * and outsink map as "screen, a, b, c, d, ...", and that * zs minors are "a, b, kbd, mouse, c, d, ...". */ static int zsa_inuse; /* Strictly for debugging */ int zsa_channel_is_active_in_rom(dev_info_t *dev, int zsminor) { char pathname[OBP_MAXPATHLEN]; char default_pathname[OBP_MAXPATHLEN]; char *stdioname; char minordata[3]; /* * Basically, get my name and compare it to stdio devnames * and if we get a match, then the device is in use as either * stdin or stdout device (console tip line or keyboard device). * * We get two forms of the pathname, one complete with the * the channel number, and if the channel is 'a', then * we also deal with the user's ability to default to * channel 'a', by omitting the channel number option. * We then compare these pathnames to both the stdin and * stdout pathnames. If any of these match, then the channel * is in use. */ (void) ddi_pathname(dev, pathname); /* device pathname */ default_pathname[0] = (char)0; /* default pathname if channel 'a' */ if ((zsminor & 1) == 0) (void) strcpy(default_pathname, pathname); minordata[0] = ':'; minordata[1] = (char)('a' + (zsminor & 1)); minordata[2] = (char)0; (void) strcat(pathname, minordata); stdioname = prom_stdinpath(); if (strcmp(pathname, stdioname) == 0) { zsa_inuse |= (1 << zsminor); return (1); } if (strcmp(default_pathname, stdioname) == 0) { zsa_inuse |= (1 << zsminor); return (1); } stdioname = prom_stdoutpath(); if (strcmp(pathname, stdioname) == 0) { zsa_inuse |= (1 << zsminor); return (1); } if (strcmp(default_pathname, stdioname) == 0) { zsa_inuse |= (1 << zsminor); return (1); } return (0); } /* * Initialize zs */ void zsa_init(struct zscom *zs) { /* * This routine is called near the end of the zs module's attach * process. It initializes the TTY protocol-private data for this * channel that needs to be in place before interrupts are enabled. */ mutex_enter(zs->zs_excl); mutex_enter(zs->zs_excl_hi); /* * Raise modem control lines on serial ports associated * with the console and (optionally) softcarrier lines. * Drop modem control lines on all others so that modems * will not answer and portselectors will skip these * lines until they are opened by a getty. */ if (zsa_channel_is_active_in_rom(zs->zs_dip, zs->zs_unit)) (void) zsmctl(zs, ZS_ON, DMSET); /* raise dtr */ else if (zsasoftdtr && (zssoftCAR[zs->zs_unit])) (void) zsmctl(zs, ZS_ON, DMSET); /* raise dtr */ else (void) zsmctl(zs, ZS_OFF, DMSET); /* drop dtr */ if (zsa_rstandby > ZSA_MAX_RSTANDBY) zsa_rstandby = ZSA_MAX_RSTANDBY; if (zsa_rdone > ZSA_RDONE_MAX) zsa_rdone = ZSA_RDONE_MAX; if (zsa_grace_flow_control > ZSA_GRACE_MAX_FLOW_CONTROL) zsa_grace_flow_control = ZSA_GRACE_MAX_FLOW_CONTROL; mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); } /* * Open routine. */ /*ARGSUSED*/ static int zsa_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr) { register struct zscom *zs; register struct asyncline *za; register int speed, unit; struct termios *termiosp; int len; register int allocbcount = zsa_rstandby; boolean_t set_zsoptinit = B_FALSE; unit = UNIT(*dev); if (unit >= nzs) return (ENXIO); /* unit not configured */ /* zscom is allocated by zsattach, and thus cannot be NULL here */ zs = &zscom[unit]; if (zs->zs_ops == NULL) { return (ENXIO); /* device not found by autoconfig */ } mutex_enter(zs->zs_ocexcl); mutex_enter(zs->zs_excl); again: if ((zs->zs_ops != &zsops_null) && (zs->zs_ops != &zsops_async)) { mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); return (EBUSY); /* another protocol got here first */ } za = (struct asyncline *)&zs->zs_priv_str; if (zs->zs_suspended) { mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); (void) ddi_dev_is_needed(zs->zs_dip, 0, 1); mutex_enter(zs->zs_ocexcl); mutex_enter(zs->zs_excl); } /* Mark device as busy (for power management) */ (void) pm_busy_component(zs->zs_dip, unit%2+1); if (zs->zs_ops == &zsops_null) { bzero(za, sizeof (zs->zs_priv_str)); za->za_common = zs; if (zssoftCAR[zs->zs_unit]) za->za_ttycommon.t_flags |= TS_SOFTCAR; zsopinit(zs, &zsops_async); set_zsoptinit = B_TRUE; za->za_rdone_wptr = 0; za->za_rdone_rptr = 0; } zs->zs_priv = (caddr_t)za; /* * Block waiting for carrier to come up, * unless this is a no-delay open. */ mutex_enter(zs->zs_excl_hi); if (!(za->za_flags & ZAS_ISOPEN)) { /* * Get the default termios settings (cflag). * These are stored as a property in the * "options" node. */ mutex_exit(zs->zs_excl_hi); if (ddi_getlongprop(DDI_DEV_T_ANY, ddi_root_node(), 0, "ttymodes", (caddr_t)&termiosp, &len) == DDI_PROP_SUCCESS && len == sizeof (struct termios)) { za->za_ttycommon.t_cflag = termiosp->c_cflag; kmem_free(termiosp, len); } else { /* * Gack! Whine about it. */ cmn_err(CE_WARN, "zs: Couldn't get ttymodes property!"); } mutex_enter(zs->zs_excl_hi); if ((*dev == rconsdev) || (*dev == kbddev) || (*dev == stdindev)) { speed = zsgetspeed(*dev); za->za_ttycommon.t_cflag &= ~(CBAUD); if (speed > CBAUD) { za->za_ttycommon.t_cflag |= CBAUDEXT; za->za_ttycommon.t_cflag |= ((speed - CBAUD - 1) & CBAUD); } else { za->za_ttycommon.t_cflag &= ~CBAUDEXT; za->za_ttycommon.t_cflag |= (speed & CBAUD); } } za->za_overrun = 0; za->za_ttycommon.t_iflag = 0; za->za_ttycommon.t_iocpending = NULL; za->za_ttycommon.t_size.ws_row = 0; za->za_ttycommon.t_size.ws_col = 0; za->za_ttycommon.t_size.ws_xpixel = 0; za->za_ttycommon.t_size.ws_ypixel = 0; za->za_dev = *dev; za->za_wbufcid = 0; zsa_program(za, za->za_ttycommon.t_cflag & (CIBAUDEXT|CIBAUD)); zsa_set_za_rcv_flags_mask(za); } else if ((za->za_ttycommon.t_flags & TS_XCLUDE) && secpolicy_excl_open(cr) != 0) { mutex_exit(zs->zs_excl_hi); if (set_zsoptinit && !(za->za_flags & ISOPEN)) zsopinit(zs, &zsops_null); mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); return (EBUSY); } else if ((*dev & OUTLINE) && !(za->za_flags & ZAS_OUT)) { mutex_exit(zs->zs_excl_hi); if (set_zsoptinit && !(za->za_flags & ISOPEN)) zsopinit(zs, &zsops_null); mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); return (EBUSY); } if (*dev & OUTLINE) za->za_flags |= ZAS_OUT; (void) zsmctl(zs, ZS_ON, DMSET); /* * Check carrier. */ if ((za->za_ttycommon.t_flags & TS_SOFTCAR) || (zsmctl(zs, 0, DMGET) & ZSRR0_CD)) za->za_flags |= ZAS_CARR_ON; mutex_exit(zs->zs_excl_hi); /* * If FNDELAY and FNONBLOCK are clear, block until carrier up. * Quit on interrupt. */ if (!(flag & (FNDELAY|FNONBLOCK)) && !(za->za_ttycommon.t_cflag & CLOCAL)) { if (!(za->za_flags & (ZAS_CARR_ON|ZAS_OUT)) || ((za->za_flags & ZAS_OUT) && !(*dev & OUTLINE))) { za->za_flags |= ZAS_WOPEN; mutex_exit(zs->zs_excl); if (cv_wait_sig(&zs->zs_flags_cv, zs->zs_ocexcl) == 0) { mutex_enter(zs->zs_excl); if (zs->zs_suspended) { mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); (void) ddi_dev_is_needed(zs->zs_dip, 0, 1); mutex_enter(zs->zs_ocexcl); mutex_enter(zs->zs_excl); } za->za_flags &= ~ZAS_WOPEN; if (set_zsoptinit && !(za->za_flags & ISOPEN)) zsopinit(zs, &zsops_null); mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); return (EINTR); } mutex_enter(zs->zs_excl); za->za_flags &= ~ZAS_WOPEN; if ((zs->zs_ops == &zsops_null) || (zs->zs_ops == &zsops_async)) goto again; else { if (set_zsoptinit && !(za->za_flags & ISOPEN)) zsopinit(zs, &zsops_null); mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); return (EBUSY); } } } else if ((za->za_flags & ZAS_OUT) && !(*dev & OUTLINE)) { if (set_zsoptinit && !(za->za_flags & ISOPEN)) zsopinit(zs, &zsops_null); mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); return (EBUSY); } za->za_ttycommon.t_readq = rq; za->za_ttycommon.t_writeq = WR(rq); rq->q_ptr = WR(rq)->q_ptr = (caddr_t)za; za->za_flags |= ZAS_ISOPEN; ZSA_GETBLOCK(zs, allocbcount); qprocson(rq); mutex_exit(zs->zs_excl); mutex_exit(zs->zs_ocexcl); return (0); } static void zs_progress_check(void *arg) { struct asyncline *za = arg; struct zscom *zs = za->za_common; mblk_t *bp; /* * We define "progress" as either waiting on a timed break or delay, or * having had at least one transmitter interrupt. If none of these are * true, then just terminate the output and wake up that close thread. */ mutex_enter(zs->zs_excl); if (!(zs->zs_flags & ZS_PROGRESS) && !(za->za_flags & (ZAS_BREAK|ZAS_DELAY))) { za->za_flags &= ~ZAS_BUSY; mutex_enter(zs->zs_excl_hi); za->za_rcv_flags_mask &= ~DO_RETRANSMIT; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; bp = za->za_xmitblk; za->za_xmitblk = NULL; mutex_exit(zs->zs_excl_hi); zs->zs_timer = 0; mutex_exit(zs->zs_excl); if (bp != NULL) freeb(bp); /* * Since this timer is running, we know that we're in exit(2). * That means that the user can't possibly be waiting on any * valid ioctl(2) completion anymore, and we should just flush * everything. */ flushq(za->za_ttycommon.t_writeq, FLUSHALL); cv_broadcast(&zs->zs_flags_cv); } else { zs->zs_flags &= ~ZS_PROGRESS; zs->zs_timer = timeout(zs_progress_check, za, drv_usectohz(zs_drain_check)); mutex_exit(zs->zs_excl); } } /* * Close routine. * * Important locking note: the zs_ocexcl lock is not held at all in this * routine. This is intentional. That lock is used to coordinate multiple * simultaneous opens on a stream, and there's no such thing as multiple * simultaneous closes on a stream. */ /*ARGSUSED*/ static int zsa_close(queue_t *q, int flag, cred_t *cr __unused) { struct asyncline *za; struct zscom *zs; int i; mblk_t *bp; timeout_id_t za_zsa_restart_id, za_kick_rcv_id; bufcall_id_t za_bufcid, za_wbufcid; int tmp; za = q->q_ptr; ASSERT(za != NULL); zs = za->za_common; mutex_enter(zs->zs_excl); zs->zs_flags |= ZS_CLOSING; /* * There are two flavors of break -- timed (M_BREAK or TCSBRK) and * untimed (TIOCSBRK). For the timed case, these are enqueued on our * write queue and there's a timer running, so we don't have to worry * about them. For the untimed case, though, the user obviously made a * mistake, because these are handled immediately. We'll terminate the * break now and honor their implicit request by discarding the rest of * the data. */ if (!(za->za_flags & ZAS_BREAK) && (zs->zs_wreg[5] & ZSWR5_BREAK)) goto nodrain; /* * If the user told us not to delay the close ("non-blocking"), then * don't bother trying to drain. * * If the user did M_STOP (ASYNC_STOPPED), there's no hope of ever * getting an M_START (since these messages aren't enqueued), and the * only other way to clear the stop condition is by loss of DCD, which * would discard the queue data. Thus, we drop the output data if * ASYNC_STOPPED is set. */ if ((flag & (FNDELAY|FNONBLOCK)) || (za->za_flags & ZAS_STOPPED)) goto nodrain; /* * If there's any pending output, then we have to try to drain it. * There are two main cases to be handled: * - called by close(2): need to drain until done or until * a signal is received. No timeout. * - called by exit(2): need to drain while making progress * or until a timeout occurs. No signals. * * If we can't rely on receiving a signal to get us out of a hung * session, then we have to use a timer. In this case, we set a timer * to check for progress in sending the output data -- all that we ask * (at each interval) is that there's been some progress made. Since * the interrupt routine grabs buffers from the write queue, we can't * trust changes in zs_wr_cur. Instead, we use a progress flag. * * Note that loss of carrier will cause the output queue to be flushed, * and we'll wake up again and finish normally. */ if (!ddi_can_receive_sig() && zs_drain_check != 0) { zs->zs_flags &= ~ZS_PROGRESS; zs->zs_timer = timeout(zs_progress_check, za, drv_usectohz(zs_drain_check)); } while (zs->zs_wr_cur != NULL || za->za_ttycommon.t_writeq->q_first != NULL || (za->za_flags & (ZAS_BUSY|ZAS_DELAY|ZAS_BREAK))) { if (cv_wait_sig(&zs->zs_flags_cv, zs->zs_excl) == 0) break; } if (zs->zs_timer != 0) { (void) untimeout(zs->zs_timer); zs->zs_timer = 0; } nodrain: /* * If break is in progress, stop it. */ mutex_enter(zs->zs_excl_hi); if (zs->zs_wreg[5] & ZSWR5_BREAK) { SCC_BIC(5, ZSWR5_BREAK); za->za_flags &= ~ZAS_BREAK; } za_wbufcid = za->za_wbufcid; za_bufcid = za->za_bufcid; za_zsa_restart_id = za->za_zsa_restart_id; za_kick_rcv_id = za->za_kick_rcv_id; za->za_wbufcid = za->za_bufcid = 0; za->za_zsa_restart_id = za->za_kick_rcv_id = 0; /* * If line has HUPCL set or is incompletely opened, * and it is not the console or the keyboard, * fix up the modem lines. */ zsopinit(zs, &zsops_null_async); /* * Nobody, zsh or zs can now open this port until * zsopinit(zs, &zsops_null); * */ if ((za->za_dev != rconsdev) && (za->za_dev != kbddev) && (za->za_dev != stdindev) && (((za->za_flags & (ZAS_WOPEN|ZAS_ISOPEN)) != ZAS_ISOPEN) || (za->za_ttycommon.t_cflag & HUPCL))) { /* * If DTR is being held high by softcarrier, * set up the ZS_ON set; if not, hang up. */ if (zsasoftdtr && (za->za_ttycommon.t_flags & TS_SOFTCAR)) (void) zsmctl(zs, ZS_ON, DMSET); else (void) zsmctl(zs, ZS_OFF, DMSET); mutex_exit(zs->zs_excl_hi); /* * Don't let an interrupt in the middle of close * bounce us back to the top; just continue * closing as if nothing had happened. */ tmp = cv_reltimedwait_sig(&zs->zs_flags_cv, zs->zs_excl, drv_usectohz(10000), TR_CLOCK_TICK); if (zs->zs_suspended) { mutex_exit(zs->zs_excl); (void) ddi_dev_is_needed(zs->zs_dip, 0, 1); mutex_enter(zs->zs_excl); } if (tmp == 0) goto out; mutex_enter(zs->zs_excl_hi); } /* * If nobody's now using it, turn off receiver interrupts. */ if ((za->za_flags & (ZAS_ISOPEN|ZAS_WOPEN)) == 0) SCC_BIC(1, ZSWR1_RIE); mutex_exit(zs->zs_excl_hi); out: /* * Clear out device state. */ ttycommon_close(&za->za_ttycommon); za->za_ttycommon.t_readq = NULL; za->za_ttycommon.t_writeq = NULL; mutex_enter(zs->zs_excl_hi); za->za_rcv_flags_mask &= ~DO_RETRANSMIT; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; bp = za->za_xmitblk; za->za_xmitblk = NULL; mutex_exit(zs->zs_excl_hi); if (bp) freemsg(bp); mutex_enter(zs->zs_excl_hi); zs->zs_rd_cur = NULL; zs->zs_rd_lim = NULL; bp = za->za_rcvblk; za->za_rcvblk = NULL; mutex_exit(zs->zs_excl_hi); if (bp) freemsg(bp); for (i = 0; i < zsa_rstandby; i++) { mutex_enter(zs->zs_excl_hi); bp = za->za_rstandby[i]; za->za_rstandby[i] = NULL; mutex_exit(zs->zs_excl_hi); if (bp) freemsg(bp); } if (za->za_soft_active || za->za_kick_active) { zs->zs_flags |= ZS_CLOSED; while (za->za_soft_active || za->za_kick_active) cv_wait(&zs->zs_flags_cv, zs->zs_excl); } if (zs->zs_suspended) { mutex_exit(zs->zs_excl); (void) ddi_dev_is_needed(zs->zs_dip, 0, 1); mutex_enter(zs->zs_excl); } ZSA_FLUSHQ; bzero(za, sizeof (struct asyncline)); qprocsoff(q); mutex_exit(zs->zs_excl); /* * Cancel outstanding "bufcall" request. */ if (za_wbufcid) unbufcall(za_wbufcid); if (za_bufcid) unbufcall(za_bufcid); /* * Cancel outstanding timeout. */ if (za_zsa_restart_id) (void) untimeout(za_zsa_restart_id); if (za_kick_rcv_id) (void) untimeout(za_kick_rcv_id); q->q_ptr = WR(q)->q_ptr = NULL; zsopinit(zs, &zsops_null); cv_broadcast(&zs->zs_flags_cv); /* Mark device as available for power management */ (void) pm_idle_component(zs->zs_dip, zs->zs_unit%2+1); return (0); } /* * Put procedure for write queue. * Respond to M_STOP, M_START, M_IOCTL, and M_FLUSH messages here; * set the flow control character for M_STOPI and M_STARTI messages; * queue up M_BREAK, M_DELAY, and M_DATA messages for processing * by the start routine, and then call the start routine; discard * everything else. Note that this driver does not incorporate any * mechanism to negotiate to handle the canonicalization process. * It expects that these functions are handled in upper module(s), * as we do in ldterm. */ static void zsa_wput(queue_t *q, mblk_t *mp) { register struct asyncline *za; register struct zscom *zs; register struct copyresp *resp; register mblk_t *bp = NULL; int error; struct iocblk *iocp; za = (struct asyncline *)q->q_ptr; zs = za->za_common; if (zs->zs_flags & ZS_NEEDSOFT) { zs->zs_flags &= ~ZS_NEEDSOFT; (void) zsa_softint(zs); } switch (mp->b_datap->db_type) { case M_STOP: /* * Since we don't do real DMA, we can just let the * chip coast to a stop after applying the brakes. */ mutex_enter(zs->zs_excl); mutex_enter(zs->zs_excl_hi); za->za_flags |= ZAS_STOPPED; if ((zs->zs_wr_cur) != NULL) { za->za_flags &= ~ZAS_BUSY; za->za_rcv_flags_mask &= ~DO_RETRANSMIT; bp = za->za_xmitblk; bp->b_rptr = zs->zs_wr_cur; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; za->za_xmitblk = NULL; } mutex_exit(zs->zs_excl_hi); if (bp) (void) putbq(q, bp); freemsg(mp); mutex_exit(zs->zs_excl); break; case M_START: mutex_enter(zs->zs_excl); if (za->za_flags & ZAS_STOPPED) { za->za_flags &= ~ZAS_STOPPED; /* * If an output operation is in progress, * resume it. Otherwise, prod the start * routine. */ zsa_start(zs); } freemsg(mp); mutex_exit(zs->zs_excl); break; case M_IOCTL: mutex_enter(zs->zs_excl); iocp = (struct iocblk *)mp->b_rptr; switch (iocp->ioc_cmd) { case TIOCGPPS: /* * Get PPS state. */ if (mp->b_cont != NULL) freemsg(mp->b_cont); mp->b_cont = allocb(sizeof (int), BPRI_HI); if (mp->b_cont == NULL) { mp->b_datap->db_type = M_IOCNAK; iocp->ioc_error = ENOMEM; ZSA_QREPLY(q, mp); break; } if (za->za_pps) *(int *)mp->b_cont->b_wptr = 1; else *(int *)mp->b_cont->b_wptr = 0; mp->b_cont->b_wptr += sizeof (int); mp->b_datap->db_type = M_IOCACK; iocp->ioc_count = sizeof (int); ZSA_QREPLY(q, mp); break; case TIOCSPPS: /* * Set PPS state. */ error = miocpullup(mp, sizeof (int)); if (error != 0) { mp->b_datap->db_type = M_IOCNAK; iocp->ioc_error = error; ZSA_QREPLY(q, mp); break; } za->za_pps = (*(int *)mp->b_cont->b_rptr != 0); mp->b_datap->db_type = M_IOCACK; ZSA_QREPLY(q, mp); break; case TIOCGPPSEV: { /* * Get PPS event data. */ void *buf; #ifdef _SYSCALL32_IMPL struct ppsclockev32 p32; #endif if (mp->b_cont != NULL) { freemsg(mp->b_cont); mp->b_cont = NULL; } if (za->za_pps == NULL) { mp->b_datap->db_type = M_IOCNAK; iocp->ioc_error = ENXIO; ZSA_QREPLY(q, mp); break; } #ifdef _SYSCALL32_IMPL if ((iocp->ioc_flag & IOC_MODELS) != IOC_NATIVE) { TIMEVAL_TO_TIMEVAL32(&p32.tv, &ppsclockev.tv); p32.serial = ppsclockev.serial; buf = &p32; iocp->ioc_count = sizeof (struct ppsclockev32); } else #endif { buf = &ppsclockev; iocp->ioc_count = sizeof (struct ppsclockev); } if ((bp = allocb(iocp->ioc_count, BPRI_HI)) == NULL) { mp->b_datap->db_type = M_IOCNAK; iocp->ioc_error = ENOMEM; ZSA_QREPLY(q, mp); break; } mp->b_cont = bp; bcopy(buf, bp->b_wptr, iocp->ioc_count); bp->b_wptr += iocp->ioc_count; mp->b_datap->db_type = M_IOCACK; ZSA_QREPLY(q, mp); break; } case TCSETSW: case TCSETSF: case TCSETAW: case TCSETAF: case TCSBRK: /* * The changes do not take effect until all * output queued before them is drained. * Put this message on the queue, so that * "zsa_start" will see it when it's done * with the output before it. Poke the * start routine, just in case. */ (void) putq(q, mp); zsa_start(zs); break; default: /* * Do it now. */ zsa_ioctl(za, q, mp); break; } mutex_exit(zs->zs_excl); break; case M_IOCDATA: mutex_enter(zs->zs_excl); resp = (struct copyresp *)mp->b_rptr; if (resp->cp_rval) { /* * Just free message on failure. */ freemsg(mp); mutex_exit(zs->zs_excl); break; } switch (resp->cp_cmd) { case TIOCMSET: mutex_enter(zs->zs_excl_hi); (void) zsmctl(zs, dmtozs(*(int *)mp->b_cont->b_rptr), DMSET); mutex_exit(zs->zs_excl_hi); mioc2ack(mp, NULL, 0, 0); ZSA_QREPLY(q, mp); break; case TIOCMBIS: mutex_enter(zs->zs_excl_hi); (void) zsmctl(zs, dmtozs(*(int *)mp->b_cont->b_rptr), DMBIS); mutex_exit(zs->zs_excl_hi); mioc2ack(mp, NULL, 0, 0); ZSA_QREPLY(q, mp); break; case TIOCMBIC: mutex_enter(zs->zs_excl_hi); (void) zsmctl(zs, dmtozs(*(int *)mp->b_cont->b_rptr), DMBIC); mutex_exit(zs->zs_excl_hi); mioc2ack(mp, NULL, 0, 0); ZSA_QREPLY(q, mp); break; case TIOCMGET: mioc2ack(mp, NULL, 0, 0); ZSA_QREPLY(q, mp); break; default: freemsg(mp); } mutex_exit(zs->zs_excl); break; case M_FLUSH: mutex_enter(zs->zs_excl); if (*mp->b_rptr & FLUSHW) { /* * Abort any output in progress. */ if (za->za_flags & ZAS_BUSY) { za->za_flags &= ~ZAS_BUSY; mutex_enter(zs->zs_excl_hi); za->za_rcv_flags_mask &= ~DO_RETRANSMIT; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; bp = za->za_xmitblk; za->za_xmitblk = NULL; mutex_exit(zs->zs_excl_hi); if (bp) freemsg(bp); } /* * Flush our write queue. */ flushq(q, FLUSHDATA); /* XXX doesn't flush M_DELAY */ *mp->b_rptr &= ~FLUSHW; /* it has been flushed */ } if (*mp->b_rptr & FLUSHR) { /* * Flush any data in the temporary receive buffer */ mutex_enter(zs->zs_excl_hi); if ((za->za_ttycommon.t_flags & TS_SOFTCAR) || (SCC_READ0() & ZSRR0_CD)) { ZSA_KICK_RCV; } else { ZSA_KICK_RCV; if (!(SCC_READ0() & ZSRR0_RX_READY)) { /* * settle time for 1 character shift */ mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); delay(ztdelay(SPEED( za->za_ttycommon.t_cflag))/3 + 1); mutex_enter(zs->zs_excl); mutex_enter(zs->zs_excl_hi); if (!(SCC_READ0() & ZSRR0_CD)) ZSA_KICK_RCV; } while ((SCC_READ0() & (ZSRR0_CD | ZSRR0_RX_READY)) == ZSRR0_RX_READY) { /* * Empty Receiver */ (void) SCC_READDATA(); } } mutex_exit(zs->zs_excl_hi); flushq(RD(q), FLUSHDATA); ZSA_QREPLY(q, mp); /* * give the read queues a crack at it */ } else freemsg(mp); /* * We must make sure we process messages that survive the * write-side flush. Without this call, the close protocol * with ldterm can hang forever. (ldterm will have sent us a * TCSBRK ioctl that it expects a response to.) */ zsa_start(zs); mutex_exit(zs->zs_excl); break; case M_BREAK: case M_DELAY: case M_DATA: mutex_enter(zs->zs_excl); /* * Queue the message up to be transmitted, * and poke the start routine. */ (void) putq(q, mp); zsa_start(zs); mutex_exit(zs->zs_excl); break; case M_STOPI: mutex_enter(zs->zs_excl); mutex_enter(zs->zs_excl_hi); za->za_flowc = za->za_ttycommon.t_stopc; if ((zs->zs_wr_cur) != NULL) { za->za_rcv_flags_mask &= ~DO_RETRANSMIT; bp = za->za_xmitblk; bp->b_rptr = zs->zs_wr_cur; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; za->za_xmitblk = NULL; } mutex_exit(zs->zs_excl_hi); if (bp) (void) putbq(q, bp); else zsa_start(zs); /* poke the start routine */ freemsg(mp); mutex_exit(zs->zs_excl); break; case M_STARTI: mutex_enter(zs->zs_excl); mutex_enter(zs->zs_excl_hi); za->za_flowc = za->za_ttycommon.t_startc; if ((zs->zs_wr_cur) != NULL) { za->za_rcv_flags_mask &= ~DO_RETRANSMIT; bp = za->za_xmitblk; bp->b_rptr = zs->zs_wr_cur; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; za->za_xmitblk = NULL; } mutex_exit(zs->zs_excl_hi); if (bp) (void) putbq(q, bp); else zsa_start(zs); /* poke the start routine */ freemsg(mp); mutex_exit(zs->zs_excl); break; case M_CTL: if (MBLKL(mp) >= sizeof (struct iocblk) && ((struct iocblk *)mp->b_rptr)->ioc_cmd == MC_POSIXQUERY) { ((struct iocblk *)mp->b_rptr)->ioc_cmd = MC_HAS_POSIX; qreply(q, mp); } else { /* * These MC_SERVICE type messages are used by upper * modules to tell this driver to send input up * immediately, or that it can wait for normal * processing that may or may not be done. Sun * requires these for the mouse module. */ mutex_enter(zs->zs_excl); switch (*mp->b_rptr) { case MC_SERVICEIMM: mutex_enter(zs->zs_excl_hi); za->za_flags |= ZAS_SERVICEIMM; mutex_exit(zs->zs_excl_hi); break; case MC_SERVICEDEF: mutex_enter(zs->zs_excl_hi); za->za_flags &= ~ZAS_SERVICEIMM; mutex_exit(zs->zs_excl_hi); break; } freemsg(mp); mutex_exit(zs->zs_excl); } break; default: /* * "No, I don't want a subscription to Chain Store Age, * thank you anyway." */ freemsg(mp); break; } } /* * zs read service procedure */ static void zsa_rsrv(queue_t *q) { struct asyncline *za; struct zscom *zs; if (((za = (struct asyncline *)q->q_ptr) != NULL) && (za->za_ttycommon.t_cflag & CRTSXOFF)) { zs = za->za_common; mutex_enter(zs->zs_excl_hi); ZSSETSOFT(zs); mutex_exit(zs->zs_excl_hi); } } /* * Transmitter interrupt service routine. * If there's more data to transmit in the current pseudo-DMA block, * and the transmitter is ready, send the next character if output * is not stopped or draining. * Otherwise, queue up a soft interrupt. */ static void zsa_txint(struct zscom *zs) { register struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; register uchar_t *wr_cur; register uchar_t s0; s0 = SCC_READ0(); if ((wr_cur = zs->zs_wr_cur) != NULL) { if (wr_cur < zs->zs_wr_lim) { if ((za->za_ttycommon.t_cflag & CRTSCTS) && !(s0 & ZSRR0_CTS)) { SCC_WRITE0(ZSWR0_RESET_TXINT); za->za_rcv_flags_mask |= DO_RETRANSMIT; return; } SCC_WRITEDATA(*wr_cur++); #ifdef ZSA_DEBUG za->za_wr++; #endif zs->zs_wr_cur = wr_cur; zs->zs_flags |= ZS_PROGRESS; return; } else { zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; /* * Use the rcv_flags_mask as it is set and * test while holding the zs_excl_hi mutex */ za->za_rcv_flags_mask |= DO_TRANSMIT; SCC_WRITE0(ZSWR0_RESET_TXINT); ZSSETSOFT(zs); return; } } if (za->za_flowc != '\0' && (!(za->za_flags & ZAS_DRAINING))) { if ((za->za_ttycommon.t_cflag & CRTSCTS) && !(s0 & ZSRR0_CTS)) { SCC_WRITE0(ZSWR0_RESET_TXINT); return; } SCC_WRITEDATA(za->za_flowc); za->za_flowc = '\0'; return; } SCC_WRITE0(ZSWR0_RESET_TXINT); /* * Set DO_TRANSMIT bit so that the soft interrupt can * test it and unset the ZAS_BUSY in za_flags while holding * the mutex zs_excl and zs_excl_hi */ za->za_rcv_flags_mask |= DO_TRANSMIT; ZSSETSOFT(zs); } /* * External/Status interrupt. */ static void zsa_xsint(struct zscom *zs) { register struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; register uchar_t s0, x0; s0 = SCC_READ0(); ZSA_R0_LOG(s0); x0 = s0 ^ za->za_rr0; za->za_rr0 = s0; SCC_WRITE0(ZSWR0_RESET_STATUS); /* * PPS (Pulse Per Second) support. */ if (za->za_pps && (x0 & ZSRR0_CD) && (s0 & ZSRR0_CD)) { /* * This code captures a timestamp at the designated * transition of the PPS signal (CD asserted). The * code provides a pointer to the timestamp, as well * as the hardware counter value at the capture. * * Note: the kernel has nano based time values while * NTP requires micro based, an in-line fast algorithm * to convert nsec to usec is used here -- see hrt2ts() * in common/os/timers.c for a full description. */ struct timeval *tvp = &ppsclockev.tv; timespec_t ts; int nsec, usec; LED_OFF; gethrestime(&ts); LED_ON; nsec = ts.tv_nsec; usec = nsec + (nsec >> 2); usec = nsec + (usec >> 1); usec = nsec + (usec >> 2); usec = nsec + (usec >> 4); usec = nsec - (usec >> 3); usec = nsec + (usec >> 2); usec = nsec + (usec >> 3); usec = nsec + (usec >> 4); usec = nsec + (usec >> 1); usec = nsec + (usec >> 6); tvp->tv_usec = usec >> 10; tvp->tv_sec = ts.tv_sec; ++ppsclockev.serial; /* * Because the kernel keeps a high-resolution time, pass the * current highres timestamp in tvp and zero in usec. */ ddi_hardpps(tvp, 0); } ZSA_KICK_RCV; if ((x0 & ZSRR0_BREAK) && (s0 & ZSRR0_BREAK) == 0) { #ifdef SLAVIO_BUG /* * ZSRR0_BREAK turned off. This means that the break sequence * has completed (i.e., the stop bit finally arrived). */ if ((s0 & ZSRR0_RX_READY) == 0) { /* * SLAVIO will generate a separate STATUS change * interrupt when the break sequence completes. * SCC will combine both, taking the higher priority * one, the receive. Should still see the ext/stat. * bit in REG3 on SCC. If no ext/stat, it must be * a SLAVIO. */ za->za_breakoff = 1; } else { /* * The NUL character in the receiver is part of the * break sequence; it is discarded. */ (void) SCC_READDATA(); /* swallow null */ } #else /* SLAVIO_BUG */ /* * ZSRR0_BREAK turned off. This means that the break sequence * has completed (i.e., the stop bit finally arrived). The NUL * character in the receiver is part of the break sequence; * it is discarded. */ (void) SCC_READDATA(); /* swallow null */ #endif /* SLAVIO_BUG */ SCC_WRITE0(ZSWR0_RESET_ERRORS); /* * Note: this will cause an abort if a break occurs on * the "keyboard device", regardless of whether the * "keyboard device" is a real keyboard or just a * terminal on a serial line. This permits you to * abort a workstation by unplugging the keyboard, * even if the normal abort key sequence isn't working. */ if ((za->za_dev == kbddev) || ((za->za_dev == rconsdev) || (za->za_dev == stdindev)) && (abort_enable != KIOCABORTALTERNATE)) { abort_sequence_enter((char *)NULL); /* * We just broke into the monitor or debugger, * ignore the break in this case so whatever * random program that was running doesn't get * a SIGINT. */ return; } za->za_break = 1; } /* * If hardware flow control is enabled, (re)start output * when CTS is reasserted. */ if ((za->za_ttycommon.t_cflag & CRTSCTS) && (x0 & ZSRR0_CTS) && (s0 & ZSRR0_CTS) && (za->za_rcv_flags_mask & DO_RETRANSMIT)) za->za_rcv_flags_mask |= DO_TRANSMIT; za->za_ext = 1; ZSSETSOFT(zs); } /* * Receive Interrupt */ static void zsa_rxint(struct zscom *zs) { register struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; register uchar_t c; register uchar_t *rd_cur = zs->zs_rd_cur; register uchar_t *rd_lim = zs->zs_rd_lim; register mblk_t *bp; register uint_t fm = za->za_rcv_flags_mask; #ifdef ZSA_DEBUG za->za_rd++; #endif c = (fm >> 16) & (SCC_READDATA()); /* * Check for character break sequence */ if ((abort_enable == KIOCABORTALTERNATE) && (za->za_dev == rconsdev)) { if (abort_charseq_recognize(c)) abort_sequence_enter((char *)NULL); } if (!rd_cur) { #ifdef SLAVIO_BUG /* * SLAVIO generates FE for the start of break and * during break when parity is set. End of break is * detected when the first character is received. * This character is always garbage and is thrown away. */ if (za->za_slav_break) { za->za_slav_break = 0; za->za_rr0 |= ZSRR0_BREAK; zsa_xsint(zs); return; } #endif /* SLAVIO_BUG */ if (c == 0 && (za->za_rr0 & ZSRR0_BREAK)) { /* * A break sequence was under way, and a NUL character * was received. Discard the NUL character, as it is * part of the break sequence; if ZSRR0_BREAK turned * off, indicating that the break sequence has com- * pleted, call "zsa_xsint" to properly handle the * error. It would appear that External/Status * interrupts get lost occasionally, so this ensures * that one is delivered. */ c = SCC_READ0(); if (!(c & ZSRR0_BREAK)) zsa_xsint(zs); return; } #ifdef SLAVIO_BUG if (c == 0 && za->za_breakoff) { /* * A break sequence completed, but SLAVIO generates * the NULL character interrupt late, so we throw the * NULL away now. */ return; } /* * make sure it gets cleared. */ za->za_breakoff = 0; #endif /* SLAVIO_BUG */ ZSA_KICK_RCV; /* We can have M_BREAK msg */ ZSA_ALLOCB(bp); if (!bp) { za->za_sw_overrun++; ZSSETSOFT(zs); return; } za->za_rcvblk = bp; zs->zs_rd_cur = rd_cur = bp->b_wptr; zs->zs_rd_lim = rd_lim = bp->b_datap->db_lim; if (za->za_kick_rcv_id == 0) ZSSETSOFT(zs); } if (c == 0377 && (fm & DO_ESC)) { if (rd_lim < rd_cur + 2) { ZSA_ALLOCB(bp); ZSA_KICK_RCV; if (!bp) { za->za_sw_overrun++; return; } za->za_rcvblk = bp; zs->zs_rd_cur = rd_cur = bp->b_wptr; zs->zs_rd_lim = rd_lim = bp->b_datap->db_lim; } *rd_cur++ = c; } *rd_cur++ = c; zs->zs_rd_cur = rd_cur; if (rd_cur == rd_lim) { ZSA_KICK_RCV; } else if ((fm & DO_STOPC) && (c == (fm & 0xff))) { za->za_do_kick_rcv_in_softint = 1; ZSSETSOFT(zs); } if ((za->za_flags & ZAS_SERVICEIMM) || g_nocluster) { /* * Send the data up immediately */ ZSA_KICK_RCV; } } /* * Special receive condition interrupt handler. */ static void zsa_srint(struct zscom *zs) { register struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; register short s1; register uchar_t c; register uchar_t c1; register mblk_t *bp = za->za_rcvblk; register uchar_t *rd_cur = zs->zs_rd_cur; SCC_READ(1, s1); if (s1 & (ZSRR1_FE | ZSRR1_PE | ZSRR1_DO)) { c = SCC_READDATA(); /* swallow bad character */ } #ifdef SLAVIO_BUG /* * SLAVIO does not handle breaks properly when parity is enabled. * * In general, if a null character is received when a framing * error occurs then it is a break condition and not a real * framing error. The null character must be limited to the * number of bits including the parity bit. For example, a 6 * bit character with parity would be null if the lower 7 bits * read from the receive fifo were 0. (The higher order bits are * padded with 1 and/or the stop bits.) The only exception to this * general rule would be an 8 bit null character with parity being * a 1 in the parity bit and a framing error. This exception * can be determined by examining the parity error bit in RREG 1. * * A null character, even parity, 8 bits, no parity error, * (0 0000 0000) with framing error is a break condition. * * A null character, even parity, 8 bits, parity error, * (1 0000 0000) with framing error is a framing error. * * A null character, odd parity, 8 bits, parity error * (0 0000 0000) with framing error is a break condition. * * A null character, odd parity, 8 bits, no parity error, * (1 0000 0000) with framing error is a framing error. */ if (za->za_ttycommon.t_cflag & PARENB) { switch (za->za_ttycommon.t_cflag & CSIZE) { case CS5: c1 = c & 0x3f; break; case CS6: c1 = c & 0x7f; break; case CS7: c1 = c & 0xff; break; case CS8: if ((za->za_ttycommon.t_cflag & PARODD) && !(s1 & ZSRR1_PE)) c1 = 0xff; else if (!(za->za_ttycommon.t_cflag & PARODD) && (s1 & ZSRR1_PE)) c1 = 0xff; else c1 = c; break; } /* * We fake start of break condition. */ if ((s1 & ZSRR1_FE) && c1 == 0) { za->za_slav_break = 1; return; } } #endif /* SLAVIO_BUG */ if (s1 & ZSRR1_PE) { /* * Mark the parity error so zsa_process will * notice it and send it up in an M_BREAK * message; ldterm will do the actual parity error * processing */ if (bp && zs->zs_rd_cur) { /* M_DATA msg */ ZSA_KICK_RCV; bp = NULL; } if (!bp) ZSA_ALLOCB(bp); if (!bp) { za->za_sw_overrun++; ZSSETSOFT(zs); } else { za->za_rcvblk = bp; zs->zs_rd_cur = rd_cur = bp->b_wptr; zs->zs_rd_lim = bp->b_datap->db_lim; *rd_cur++ = c; zs->zs_rd_cur = rd_cur; bp->b_datap->db_type = M_BREAK; if (bp->b_datap->db_lim <= rd_cur) ZSA_KICK_RCV; za->za_do_kick_rcv_in_softint = 1; ZSSETSOFT(zs); } } SCC_WRITE0(ZSWR0_RESET_ERRORS); if (s1 & ZSRR1_DO) { za->za_hw_overrun++; ZSSETSOFT(zs); } } /* * Process software interrupts (or poll) * Crucial points: * 3. BUG - breaks are handled "out-of-band" - their relative position * among input events is lost, as well as multiple breaks together. * This is probably not a problem in practice. */ static int zsa_softint(struct zscom *zs) { register struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; register uchar_t r0; register uchar_t za_kick_active; register int m_error; register int allocbcount = 0; register int do_ttycommon_qfull = 0; boolean_t hangup = B_FALSE, unhangup = B_FALSE; boolean_t m_break = B_FALSE, wakeup = B_FALSE; register queue_t *q; register mblk_t *bp; register mblk_t *head = NULL, *tail = NULL; mutex_enter(zs->zs_excl); if (zs->zs_suspended || (zs->zs_flags & ZS_CLOSED)) { mutex_exit(zs->zs_excl); return (0); } q = za->za_ttycommon.t_readq; if (za->za_flags & ZAS_WOPEN && !q) { if (za->za_ext) { mutex_enter(zs->zs_excl_hi); r0 = SCC_READ0(); za->za_ext = 0; mutex_exit(zs->zs_excl_hi); /* * carrier up? */ if ((r0 & ZSRR0_CD) || (za->za_ttycommon.t_flags & TS_SOFTCAR)) { /* * carrier present */ if ((za->za_flags & ZAS_CARR_ON) == 0) { za->za_flags |= ZAS_CARR_ON; mutex_exit(zs->zs_excl); cv_broadcast(&zs->zs_flags_cv); return (0); } } } mutex_exit(zs->zs_excl); return (0); } q = za->za_ttycommon.t_readq; if (!q) { mutex_exit(zs->zs_excl); return (0); } m_error = za->za_m_error; za->za_m_error = 0; if (za->za_do_kick_rcv_in_softint) { mutex_enter(zs->zs_excl_hi); ZSA_KICK_RCV; za->za_do_kick_rcv_in_softint = 0; mutex_exit(zs->zs_excl_hi); } za_kick_active = za->za_kick_active; while (!za_kick_active) { ZSA_SEEQ(bp); if (!bp) break; allocbcount++; if (bp->b_datap->db_type <= QPCTL) { if (!(canputnext(q))) { if (za->za_grace_flow_control >= zsa_grace_flow_control) { if (za->za_ttycommon.t_cflag & CRTSXOFF) { allocbcount--; break; } ZSA_GETQ(bp); freemsg(bp); do_ttycommon_qfull = 1; continue; } else za->za_grace_flow_control++; } else za->za_grace_flow_control = 0; } ZSA_GETQ(bp); if (!head) { head = bp; } else { if (!tail) tail = head; tail->b_next = bp; tail = bp; } } if (allocbcount) ZSA_GETBLOCK(zs, allocbcount); if (za->za_ext) { mutex_enter(zs->zs_excl_hi); r0 = SCC_READ0(); za->za_ext = 0; /* * carrier up? */ if ((r0 & ZSRR0_CD) || (za->za_ttycommon.t_flags & TS_SOFTCAR)) { /* * carrier present */ if ((za->za_flags & ZAS_CARR_ON) == 0) { za->za_flags |= ZAS_CARR_ON; unhangup = B_TRUE; wakeup = B_TRUE; } } else { if ((za->za_flags & ZAS_CARR_ON) && !(za->za_ttycommon.t_cflag & CLOCAL)) { /* * Carrier went away. * Drop DTR, abort any output in progress, * indicate that output is not stopped, and * send a hangup notification upstream. */ (void) zsmctl(zs, ZSWR5_DTR, DMBIC); if ((za->za_flags & ZAS_BUSY) && (zs->zs_wr_cur != NULL)) { zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; } hangup = B_TRUE; wakeup = B_TRUE; za->za_flags &= ~(ZAS_STOPPED | ZAS_CARR_ON | ZAS_BUSY); za->za_rcv_flags_mask &= ~(DO_TRANSMIT | DO_RETRANSMIT); } } mutex_exit(zs->zs_excl_hi); if (hangup && (bp = za->za_xmitblk) != NULL) { za->za_xmitblk = NULL; freeb(bp); } } if (za->za_break != 0) { mutex_enter(zs->zs_excl_hi); r0 = SCC_READ0(); mutex_exit(zs->zs_excl_hi); if ((r0 & ZSRR0_BREAK) == 0) { za->za_break = 0; m_break = B_TRUE; } } /* * If a transmission has finished, indicate that it's * finished, and start that line up again. */ mutex_enter(zs->zs_excl_hi); if (za->za_rcv_flags_mask & DO_TRANSMIT) { za->za_rcv_flags_mask &= ~DO_TRANSMIT; za->za_flags &= ~ZAS_BUSY; if ((za->za_ttycommon.t_cflag & CRTSCTS) && (za->za_rcv_flags_mask & DO_RETRANSMIT) && zs->zs_wr_cur) bp = NULL; else { za->za_rcv_flags_mask &= ~DO_RETRANSMIT; bp = za->za_xmitblk; za->za_xmitblk = 0; } mutex_exit(zs->zs_excl_hi); if (bp) freemsg(bp); zsa_start(zs); /* if we didn't start anything, then notify waiters */ if (!(za->za_flags & ZAS_BUSY)) wakeup = B_TRUE; } else { mutex_exit(zs->zs_excl_hi); } /* * A note about these overrun bits: all they do is *tell* someone * about an error- They do not track multiple errors. In fact, * you could consider them latched register bits if you like. * We are only interested in printing the error message once for * any cluster of overrun errors. */ if ((!za->za_kick_rcv_id) && (zs->zs_rd_cur || za_kick_active)) { if (g_zsticks) za->za_kick_rcv_id = timeout(zsa_kick_rcv, zs, g_zsticks); else za->za_kick_rcv_id = timeout(zsa_kick_rcv, zs, zsticks[SPEED(za->za_ttycommon.t_cflag)]); za->za_kick_rcv_count = ZA_KICK_RCV_COUNT; } za->za_soft_active = 1; mutex_exit(zs->zs_excl); if (!hangup && do_ttycommon_qfull) { ttycommon_qfull(&za->za_ttycommon, q); mutex_enter(zs->zs_excl); zsa_start(zs); mutex_exit(zs->zs_excl); } if (za->za_hw_overrun > 10) { cmn_err(CE_NOTE, "zs%d: silo overflow\n", UNIT(za->za_dev)); za->za_hw_overrun = 0; } if (za->za_sw_overrun > 10) { cmn_err(CE_NOTE, "zs%d:ring buffer overflow\n", UNIT(za->za_dev)); za->za_sw_overrun = 0; } if (unhangup) (void) putnextctl(q, M_UNHANGUP); if (m_break) (void) putnextctl(q, M_BREAK); while (head) { if (!tail) { putnext(q, head); break; } bp = head; head = head->b_next; bp->b_next = NULL; putnext(q, bp); } if (hangup) { int flushflag; /* * If we're in the midst of close, then flush everything. Don't * leave stale ioctls lying about. */ flushflag = (zs->zs_flags & ZS_CLOSING) ? FLUSHALL : FLUSHDATA; flushq(za->za_ttycommon.t_writeq, flushflag); (void) putnextctl(q, M_HANGUP); } if (m_error) (void) putnextctl1(q, M_ERROR, m_error); za->za_soft_active = 0; if (wakeup || (zs->zs_flags & ZS_CLOSED)) cv_broadcast(&zs->zs_flags_cv); return (0); } /* * Start output on a line, unless it's busy, frozen, or otherwise. */ static void zsa_start(struct zscom *zs) { register struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; register int cc; register queue_t *q; register mblk_t *bp; uchar_t *rptr, *wptr; /* * If the chip is busy (i.e., we're waiting for a break timeout * to expire, or for the current transmission to finish, or for * output to finish draining from chip), don't grab anything new. */ if ((za->za_flags & (ZAS_BREAK|ZAS_BUSY|ZAS_DRAINING)) || zs->zs_suspended) return; if (za->za_ttycommon.t_cflag & CRTSCTS) { mutex_enter(zs->zs_excl_hi); if (za->za_rcv_flags_mask & DO_RETRANSMIT) { rptr = zs->zs_wr_cur; wptr = zs->zs_wr_lim; goto zsa_start_retransmit; } mutex_exit(zs->zs_excl_hi); } /* * If we have a flow-control character to transmit, do it now. */ if (za->za_flowc != '\0') { mutex_enter(zs->zs_excl_hi); if (za->za_ttycommon.t_cflag & CRTSCTS) { if ((SCC_READ0() & (ZSRR0_CTS|ZSRR0_TX_READY)) != (ZSRR0_CTS|ZSRR0_TX_READY)) { mutex_exit(zs->zs_excl_hi); return; } } else if (!(SCC_READ0() & ZSRR0_TX_READY)) { mutex_exit(zs->zs_excl_hi); return; } ZSDELAY(); SCC_WRITEDATA(za->za_flowc); za->za_flowc = '\0'; mutex_exit(zs->zs_excl_hi); return; } /* * If we're waiting for a delay timeout to expire, don't grab * anything new. */ if (za->za_flags & ZAS_DELAY) return; if ((q = za->za_ttycommon.t_writeq) == NULL) return; /* not attached to a stream */ zsa_start_again: for (;;) { if ((bp = getq(q)) == NULL) return; /* no data to transmit */ /* * We have a message block to work on. * Check whether it's a break, a delay, or an ioctl (the latter * occurs if the ioctl in question was waiting for the output * to drain). If it's one of those, process it immediately. */ switch (bp->b_datap->db_type) { case M_BREAK: /* * Set the break bit, and arrange for "zsa_restart" * to be called in 1/4 second; it will turn the * break bit off, and call "zsa_start" to grab * the next message. */ mutex_enter(zs->zs_excl_hi); SCC_BIS(5, ZSWR5_BREAK); mutex_exit(zs->zs_excl_hi); if (!za->za_zsa_restart_id) { za->za_zsa_restart_id = timeout(zsa_restart, zs, hz/4); } za->za_flags |= ZAS_BREAK; freemsg(bp); return; /* wait for this to finish */ case M_DELAY: /* * Arrange for "zsa_restart" to be called when the * delay expires; it will turn MTS_DELAY off, * and call "zsa_start" to grab the next message. */ if (! za->za_zsa_restart_id) { za->za_zsa_restart_id = timeout(zsa_restart, zs, (int)(*(unsigned char *)bp->b_rptr + 6)); } za->za_flags |= ZAS_DELAY; freemsg(bp); return; /* wait for this to finish */ case M_IOCTL: /* * This ioctl was waiting for the output ahead of * it to drain; obviously, it has. Do it, and * then grab the next message after it. */ zsa_ioctl(za, q, bp); continue; default: /* M_DATA */ goto zsa_start_transmit; } } zsa_start_transmit: /* * We have data to transmit. If output is stopped, put * it back and try again later. */ if (za->za_flags & ZAS_STOPPED) { (void) putbq(q, bp); return; } za->za_xmitblk = bp; rptr = bp->b_rptr; wptr = bp->b_wptr; cc = wptr - rptr; bp = bp->b_cont; if (bp != NULL) { za->za_xmitblk->b_cont = NULL; (void) putbq(q, bp); /* not done with this message yet */ } if (rptr >= wptr) { freeb(za->za_xmitblk); za->za_xmitblk = NULL; goto zsa_start_again; } /* * In 5-bit mode, the high order bits are used * to indicate character sizes less than five, * so we need to explicitly mask before transmitting */ if ((za->za_ttycommon.t_cflag & CSIZE) == CS5) { register unsigned char *p = rptr; register int cnt = cc; while (cnt--) *p++ &= (unsigned char) 0x1f; } /* * Set up this block for pseudo-DMA. */ mutex_enter(zs->zs_excl_hi); zs->zs_wr_cur = rptr; zs->zs_wr_lim = wptr; zsa_start_retransmit: za->za_rcv_flags_mask &= ~DO_TRANSMIT; if (za->za_ttycommon.t_cflag & CRTSCTS) { if ((SCC_READ0() & (ZSRR0_CTS|ZSRR0_TX_READY)) != (ZSRR0_CTS|ZSRR0_TX_READY)) { za->za_rcv_flags_mask |= DO_RETRANSMIT; za->za_flags |= ZAS_BUSY; mutex_exit(zs->zs_excl_hi); return; } za->za_rcv_flags_mask &= ~DO_RETRANSMIT; } else { if (!(SCC_READ0() & ZSRR0_TX_READY)) { za->za_flags |= ZAS_BUSY; mutex_exit(zs->zs_excl_hi); return; } } /* * If the transmitter is ready, shove the first * character out. */ ZSDELAY(); SCC_WRITEDATA(*rptr++); #ifdef ZSA_DEBUG za->za_wr++; #endif zs->zs_wr_cur = rptr; za->za_flags |= ZAS_BUSY; zs->zs_flags |= ZS_PROGRESS; mutex_exit(zs->zs_excl_hi); } /* * Restart output on a line after a delay or break timer expired. */ static void zsa_restart(void *arg) { struct zscom *zs = arg; struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; /* * If break timer expired, turn off the break bit. */ mutex_enter(zs->zs_excl); if (!za->za_zsa_restart_id) { mutex_exit(zs->zs_excl); return; } za->za_zsa_restart_id = 0; if (za->za_flags & ZAS_BREAK) { mutex_enter(zs->zs_excl_hi); SCC_BIC(5, ZSWR5_BREAK); mutex_exit(zs->zs_excl_hi); } za->za_flags &= ~(ZAS_DELAY|ZAS_BREAK); if (za->za_ttycommon.t_writeq != NULL) zsa_start(zs); mutex_exit(zs->zs_excl); cv_broadcast(&zs->zs_flags_cv); } /* * See if the receiver has any data after zs_tick delay */ static void zsa_kick_rcv(void *arg) { struct zscom *zs = arg; struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; queue_t *q; int tmp; mblk_t *mp; uchar_t za_soft_active, za_kick_active; int allocbcount = 0; int do_ttycommon_qfull = 0; mblk_t *head = NULL, *tail = NULL; mutex_enter(zs->zs_excl); if (za->za_kick_rcv_id == 0 || (zs->zs_flags & ZS_CLOSED)) { mutex_exit(zs->zs_excl); return; } za_soft_active = za->za_soft_active; za_kick_active = za->za_kick_active; q = za->za_ttycommon.t_readq; if (!q) { mutex_exit(zs->zs_excl); return; } mutex_enter(zs->zs_excl_hi); if (zs->zs_rd_cur) { ZSA_KICK_RCV; za->za_kick_rcv_count = tmp = ZA_KICK_RCV_COUNT; } else tmp = --za->za_kick_rcv_count; if (tmp > 0 || za_soft_active || za_kick_active) { mutex_exit(zs->zs_excl_hi); if (g_zsticks) za->za_kick_rcv_id = timeout(zsa_kick_rcv, zs, g_zsticks); else za->za_kick_rcv_id = timeout(zsa_kick_rcv, zs, zsticks[SPEED(za->za_ttycommon.t_cflag)]); if (za_soft_active || za_kick_active) { mutex_exit(zs->zs_excl); return; } } else { za->za_kick_rcv_id = 0; mutex_exit(zs->zs_excl_hi); } for (;;) { ZSA_SEEQ(mp); if (!mp) break; allocbcount++; if (mp->b_datap->db_type <= QPCTL) { if (!(canputnext(q))) { if (za->za_grace_flow_control >= zsa_grace_flow_control) { if (za->za_ttycommon.t_cflag & CRTSXOFF) { allocbcount--; break; } ZSA_GETQ(mp); freemsg(mp); do_ttycommon_qfull = 1; continue; } else za->za_grace_flow_control++; } else za->za_grace_flow_control = 0; } ZSA_GETQ(mp); if (!head) { head = mp; } else { if (!tail) tail = head; tail->b_next = mp; tail = mp; } } if (allocbcount) ZSA_GETBLOCK(zs, allocbcount); za->za_kick_active = 1; mutex_exit(zs->zs_excl); if (do_ttycommon_qfull) { ttycommon_qfull(&za->za_ttycommon, q); mutex_enter(zs->zs_excl); zsa_start(zs); mutex_exit(zs->zs_excl); } while (head) { if (!tail) { putnext(q, head); break; } mp = head; head = head->b_next; mp->b_next = NULL; putnext(q, mp); } za->za_kick_active = 0; if (zs->zs_flags & ZS_CLOSED) cv_broadcast(&zs->zs_flags_cv); } /* * Retry an "ioctl", now that "bufcall" claims we may be able to allocate * the buffer we need. */ static void zsa_reioctl(void *arg) { struct asyncline *za = arg; struct zscom *zs = za->za_common; queue_t *q; mblk_t *mp; /* * The bufcall is no longer pending. */ mutex_enter(zs->zs_excl); if (!za->za_wbufcid) { mutex_exit(zs->zs_excl); return; } za->za_wbufcid = 0; if ((q = za->za_ttycommon.t_writeq) == NULL) { mutex_exit(zs->zs_excl); return; } if ((mp = za->za_ttycommon.t_iocpending) != NULL) { /* * not pending any more */ za->za_ttycommon.t_iocpending = NULL; zsa_ioctl(za, q, mp); } mutex_exit(zs->zs_excl); } /* * Process an "ioctl" message sent down to us. * Note that we don't need to get any locks until we are ready to access * the hardware. Nothing we access until then is going to be altered * outside of the STREAMS framework, so we should be safe. */ static void zsa_ioctl(struct asyncline *za, queue_t *wq, mblk_t *mp) { register struct zscom *zs = za->za_common; register struct iocblk *iocp; register unsigned datasize; int error; register mblk_t *tmp; if (za->za_ttycommon.t_iocpending != NULL) { /* * We were holding an "ioctl" response pending the * availability of an "mblk" to hold data to be passed up; * another "ioctl" came through, which means that "ioctl" * must have timed out or been aborted. */ freemsg(za->za_ttycommon.t_iocpending); za->za_ttycommon.t_iocpending = NULL; } iocp = (struct iocblk *)mp->b_rptr; /* * The only way in which "ttycommon_ioctl" can fail is if the "ioctl" * requires a response containing data to be returned to the user, * and no mblk could be allocated for the data. * No such "ioctl" alters our state. Thus, we always go ahead and * do any state-changes the "ioctl" calls for. If we couldn't allocate * the data, "ttycommon_ioctl" has stashed the "ioctl" away safely, so * we just call "bufcall" to request that we be called back when we * stand a better chance of allocating the data. */ mutex_exit(zs->zs_excl); datasize = ttycommon_ioctl(&za->za_ttycommon, wq, mp, &error); mutex_enter(zs->zs_excl); if (za->za_ttycommon.t_flags & TS_SOFTCAR) zssoftCAR[zs->zs_unit] = 1; else zssoftCAR[zs->zs_unit] = 0; if (datasize != 0) { if (za->za_wbufcid) unbufcall(za->za_wbufcid); za->za_wbufcid = bufcall(datasize, BPRI_HI, zsa_reioctl, za); return; } if (error == 0) { /* * "ttycommon_ioctl" did most of the work; we just use the * data it set up. */ switch (iocp->ioc_cmd) { case TCSETS: case TCSETSW: case TCSETSF: case TCSETA: case TCSETAW: case TCSETAF: mutex_enter(zs->zs_excl_hi); zsa_program(za, 1); zsa_set_za_rcv_flags_mask(za); mutex_exit(zs->zs_excl_hi); break; } } else if (error < 0) { /* * "ttycommon_ioctl" didn't do anything; we process it here. */ error = 0; switch (iocp->ioc_cmd) { case TCSBRK: error = miocpullup(mp, sizeof (int)); if (error != 0) break; if (*(int *)mp->b_cont->b_rptr == 0) { /* * The delay ensures that a 3 byte transmit * fifo is empty. */ mutex_exit(zs->zs_excl); delay(ztdelay(SPEED(za->za_ttycommon.t_cflag))); mutex_enter(zs->zs_excl); /* * Set the break bit, and arrange for * "zsa_restart" to be called in 1/4 second; * it will turn the break bit off, and call * "zsa_start" to grab the next message. */ mutex_enter(zs->zs_excl_hi); SCC_BIS(5, ZSWR5_BREAK); if (!za->za_zsa_restart_id) { mutex_exit(zs->zs_excl_hi); za->za_zsa_restart_id = timeout(zsa_restart, zs, hz / 4); mutex_enter(zs->zs_excl_hi); } za->za_flags |= ZAS_BREAK; mutex_exit(zs->zs_excl_hi); } break; case TIOCSBRK: mutex_enter(zs->zs_excl_hi); SCC_BIS(5, ZSWR5_BREAK); mutex_exit(zs->zs_excl_hi); mioc2ack(mp, NULL, 0, 0); break; case TIOCCBRK: mutex_enter(zs->zs_excl_hi); SCC_BIC(5, ZSWR5_BREAK); mutex_exit(zs->zs_excl_hi); mioc2ack(mp, NULL, 0, 0); break; case TIOCMSET: case TIOCMBIS: case TIOCMBIC: { int mlines; if (iocp->ioc_count == TRANSPARENT) { mcopyin(mp, NULL, sizeof (int), NULL); break; } error = miocpullup(mp, sizeof (int)); if (error != 0) break; mlines = *(int *)mp->b_cont->b_rptr; mutex_enter(zs->zs_excl_hi); switch (iocp->ioc_cmd) { case TIOCMSET: (void) zsmctl(zs, dmtozs(mlines), DMSET); break; case TIOCMBIS: (void) zsmctl(zs, dmtozs(mlines), DMBIS); break; case TIOCMBIC: (void) zsmctl(zs, dmtozs(mlines), DMBIC); break; } mutex_exit(zs->zs_excl_hi); mioc2ack(mp, NULL, 0, 0); break; } case TIOCMGET: tmp = allocb(sizeof (int), BPRI_MED); if (tmp == NULL) { error = EAGAIN; break; } if (iocp->ioc_count != TRANSPARENT) mioc2ack(mp, tmp, sizeof (int), 0); else mcopyout(mp, NULL, sizeof (int), NULL, tmp); mutex_enter(zs->zs_excl_hi); *(int *)mp->b_cont->b_rptr = zstodm(zsmctl(zs, 0, DMGET)); mutex_exit(zs->zs_excl_hi); /* * qreply done below */ break; default: /* * If we don't understand it, it's an error. NAK it. */ error = EINVAL; break; } } if (error != 0) { iocp->ioc_error = error; mp->b_datap->db_type = M_IOCNAK; } ZSA_QREPLY(wq, mp); } static int dmtozs(int bits) { register int b = 0; if (bits & TIOCM_CAR) b |= ZSRR0_CD; if (bits & TIOCM_CTS) b |= ZSRR0_CTS; if (bits & TIOCM_RTS) b |= ZSWR5_RTS; if (bits & TIOCM_DTR) b |= ZSWR5_DTR; return (b); } static int zstodm(int bits) { register int b; b = 0; if (bits & ZSRR0_CD) b |= TIOCM_CAR; if (bits & ZSRR0_CTS) b |= TIOCM_CTS; if (bits & ZSWR5_RTS) b |= TIOCM_RTS; if (bits & ZSWR5_DTR) b |= TIOCM_DTR; return (b); } /* * Assemble registers and flags necessary to program the port to our liking. * For async operation, most of this is based on the values of * the "c_iflag" and "c_cflag" fields supplied to us. */ static void zsa_program(struct asyncline *za, int setibaud) { register struct zscom *zs = za->za_common; register struct zs_prog *zspp; register int wr3, wr4, wr5, wr15, speed, baudrate, flags = 0; if ((baudrate = SPEED(za->za_ttycommon.t_cflag)) == 0) { /* * Hang up line. */ (void) zsmctl(zs, ZS_OFF, DMSET); return; } /* * set input speed same as output, as split speed not supported */ if (setibaud) { za->za_ttycommon.t_cflag &= ~(CIBAUD); if (baudrate > CBAUD) { za->za_ttycommon.t_cflag |= CIBAUDEXT; za->za_ttycommon.t_cflag |= (((baudrate - CBAUD - 1) << IBSHIFT) & CIBAUD); } else { za->za_ttycommon.t_cflag &= ~CIBAUDEXT; za->za_ttycommon.t_cflag |= ((baudrate << IBSHIFT) & CIBAUD); } } /* * Do not allow the console/keyboard device to have its receiver * disabled; doing that would mean you couldn't type an abort * sequence. */ if ((za->za_dev == rconsdev) || (za->za_dev == kbddev) || (za->za_dev == stdindev) || (za->za_ttycommon.t_cflag & CREAD)) wr3 = ZSWR3_RX_ENABLE; else wr3 = 0; wr4 = ZSWR4_X16_CLK; wr5 = (zs->zs_wreg[5] & (ZSWR5_RTS|ZSWR5_DTR)) | ZSWR5_TX_ENABLE; if (zsb134_weird && baudrate == B134) { /* what a joke! */ /* * XXX - should B134 set all this crap in the compatibility * module, leaving this stuff fairly clean? */ flags |= ZSP_PARITY_SPECIAL; wr3 |= ZSWR3_RX_6; wr4 |= ZSWR4_PARITY_ENABLE | ZSWR4_PARITY_EVEN; wr4 |= ZSWR4_1_5_STOP; wr5 |= ZSWR5_TX_6; } else { switch (za->za_ttycommon.t_cflag & CSIZE) { case CS5: wr3 |= ZSWR3_RX_5; wr5 |= ZSWR5_TX_5; break; case CS6: wr3 |= ZSWR3_RX_6; wr5 |= ZSWR5_TX_6; break; case CS7: wr3 |= ZSWR3_RX_7; wr5 |= ZSWR5_TX_7; break; case CS8: wr3 |= ZSWR3_RX_8; wr5 |= ZSWR5_TX_8; break; } if (za->za_ttycommon.t_cflag & PARENB) { /* * The PARITY_SPECIAL bit causes a special rx * interrupt on parity errors. Turn it on if * we're checking the parity of characters. */ if (za->za_ttycommon.t_iflag & INPCK) flags |= ZSP_PARITY_SPECIAL; wr4 |= ZSWR4_PARITY_ENABLE; if (!(za->za_ttycommon.t_cflag & PARODD)) wr4 |= ZSWR4_PARITY_EVEN; } wr4 |= (za->za_ttycommon.t_cflag & CSTOPB) ? ZSWR4_2_STOP : ZSWR4_1_STOP; } #if 0 /* * The AUTO_CD_CTS flag enables the hardware flow control feature of * the 8530, which allows the state of CTS and DCD to control the * enabling of the transmitter and receiver, respectively. The * receiver and transmitter still must have their enable bits set in * WR3 and WR5, respectively, for CTS and DCD to be monitored this way. * Hardware flow control can thus be implemented with no help from * software. */ if (za->za_ttycommon.t_cflag & CRTSCTS) wr3 |= ZSWR3_AUTO_CD_CTS; #endif if (za->za_ttycommon.t_cflag & CRTSCTS) wr15 = ZSR15_BREAK | ZSR15_TX_UNDER | ZSR15_CD | ZSR15_CTS; else wr15 = ZSR15_BREAK | ZSR15_TX_UNDER | ZSR15_CD; speed = zs->zs_wreg[12] + (zs->zs_wreg[13] << 8); /* * Here we assemble a set of changes to be passed to zs_program. * Note: Write Register 15 must be set to enable BREAK and UNDERrun * interrupts. It must also enable CD interrupts which, although * not processed by the hardware interrupt handler, will be processed * by zsa_process, indirectly resulting in a SIGHUP being delivered * to the controlling process if CD drops. CTS interrupts must NOT * be enabled. We don't use them at all, and they will hang IPC/IPX * systems at boot time if synchronous modems that supply transmit * clock are attached to any of their serial ports. */ if (((zs->zs_wreg[1] & ZSWR1_PARITY_SPECIAL) && !(flags & ZSP_PARITY_SPECIAL)) || (!(zs->zs_wreg[1] & ZSWR1_PARITY_SPECIAL) && (flags & ZSP_PARITY_SPECIAL)) || wr3 != zs->zs_wreg[3] || wr4 != zs->zs_wreg[4] || wr5 != zs->zs_wreg[5] || wr15 != zs->zs_wreg[15] || speed != zs_speeds[baudrate]) { za->za_flags |= ZAS_DRAINING; zspp = &zs_prog[zs->zs_unit]; zspp->zs = zs; zspp->flags = (uchar_t)flags; zspp->wr4 = (uchar_t)wr4; zspp->wr11 = (uchar_t)(ZSWR11_TXCLK_BAUD | ZSWR11_RXCLK_BAUD); speed = zs_speeds[baudrate]; zspp->wr12 = (uchar_t)(speed & 0xff); zspp->wr13 = (uchar_t)((speed >> 8) & 0xff); zspp->wr3 = (uchar_t)wr3; zspp->wr5 = (uchar_t)wr5; zspp->wr15 = (uchar_t)wr15; zs_program(zspp); za->za_flags &= ~ZAS_DRAINING; } } /* * Get the current speed of the console and turn it into something * UNIX knows about - used to preserve console speed when UNIX comes up. */ int zsgetspeed(dev_t dev) { register struct zscom *zs; register int uspeed, zspeed; register uchar_t rr; zs = &zscom[UNIT(dev)]; SCC_READ(12, zspeed); SCC_READ(13, rr); zspeed |= rr << 8; for (uspeed = 0; uspeed < NSPEED; uspeed++) if (zs_speeds[uspeed] == zspeed) return (uspeed); /* * 9600 baud if we can't figure it out */ return (ISPEED); } /* * callback routine when enough memory is available. */ static void zsa_callback(void *arg) { struct zscom *zs = arg; struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; int allocbcount = zsa_rstandby; mutex_enter(zs->zs_excl); if (za->za_bufcid) { za->za_bufcid = 0; ZSA_GETBLOCK(zs, allocbcount); } mutex_exit(zs->zs_excl); } /* * Set the receiver flags */ static void zsa_set_za_rcv_flags_mask(struct asyncline *za) { register uint_t mask; za->za_rcv_flags_mask &= ~0xFF; switch (za->za_ttycommon.t_cflag & CSIZE) { case CS5: mask = 0x1f; break; case CS6: mask = 0x3f; break; case CS7: mask = 0x7f; break; default: mask = 0xff; } za->za_rcv_flags_mask &= ~(0xFF << 16); za->za_rcv_flags_mask |= mask << 16; if ((za->za_ttycommon.t_iflag & PARMRK) && !(za->za_ttycommon.t_iflag & (IGNPAR|ISTRIP))) { za->za_rcv_flags_mask |= DO_ESC; } else za->za_rcv_flags_mask &= ~DO_ESC; if (za->za_ttycommon.t_iflag & IXON) { za->za_rcv_flags_mask |= DO_STOPC; za->za_rcv_flags_mask &= ~0xFF; za->za_rcv_flags_mask |= za->za_ttycommon.t_stopc; } else za->za_rcv_flags_mask &= ~DO_STOPC; } static int zsa_suspend(struct zscom *zs) { struct asyncline *za; queue_t *q; mblk_t *bp = NULL; timeout_id_t restart_id, kick_rcv_id; struct zs_prog *zspp; za = (struct asyncline *)&zs->zs_priv_str; mutex_enter(zs->zs_excl); if (zs->zs_suspended) { mutex_exit(zs->zs_excl); return (DDI_SUCCESS); } zs->zs_suspended = 1; /* * Turn off interrupts and get any bytes in receiver */ mutex_enter(zs->zs_excl_hi); SCC_BIC(1, ZSWR1_INIT); ZSA_KICK_RCV; restart_id = za->za_zsa_restart_id; za->za_zsa_restart_id = 0; kick_rcv_id = za->za_kick_rcv_id; za->za_kick_rcv_id = 0; mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); /* * Cancel any timeouts */ if (restart_id) (void) untimeout(restart_id); if (kick_rcv_id) (void) untimeout(kick_rcv_id); /* * Since we have turned off interrupts, zsa_txint will not be called * and no new chars will given to the chip. We just wait for the * current character(s) to drain. */ delay(ztdelay(za->za_ttycommon.t_cflag & CBAUD)); /* * Return remains of partially sent message to queue */ mutex_enter(zs->zs_excl); if ((q = za->za_ttycommon.t_writeq) != NULL) { mutex_enter(zs->zs_excl_hi); if ((zs->zs_wr_cur) != NULL) { za->za_flags &= ~ZAS_BUSY; za->za_rcv_flags_mask &= ~DO_RETRANSMIT; bp = za->za_xmitblk; bp->b_rptr = zs->zs_wr_cur; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; za->za_xmitblk = NULL; } mutex_exit(zs->zs_excl_hi); if (bp) (void) putbq(q, bp); } /* * Stop any breaks in progress. */ mutex_enter(zs->zs_excl_hi); if (zs->zs_wreg[5] & ZSWR5_BREAK) { SCC_BIC(5, ZSWR5_BREAK); za->za_flags &= ~ZAS_BREAK; } /* * Now get a copy of current registers setting. */ zspp = &zs_prog[zs->zs_unit]; zspp->zs = zs; zspp->flags = 0; zspp->wr3 = zs->zs_wreg[3]; zspp->wr4 = zs->zs_wreg[4]; zspp->wr5 = zs->zs_wreg[5]; zspp->wr11 = zs->zs_wreg[11]; zspp->wr12 = zs->zs_wreg[12]; zspp->wr13 = zs->zs_wreg[13]; zspp->wr15 = zs->zs_wreg[15]; mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); /* * We do this on the off chance that zsa_close is waiting on a timed * break to complete and nothing else. */ cv_broadcast(&zs->zs_flags_cv); return (DDI_SUCCESS); } static int zsa_resume(struct zscom *zs) { register struct asyncline *za; struct zs_prog *zspp; za = (struct asyncline *)&zs->zs_priv_str; mutex_enter(zs->zs_excl); if (!(zs->zs_suspended)) { mutex_exit(zs->zs_excl); return (DDI_SUCCESS); } /* * Restore H/W state */ mutex_enter(zs->zs_excl_hi); zspp = &zs_prog[zs->zs_unit]; zs_program(zspp); /* * Enable all interrupts for this chip and delay to let chip settle */ SCC_WRITE(9, ZSWR9_MASTER_IE | ZSWR9_VECTOR_INCL_STAT); DELAY(4000); /* * Restart receiving and transmitting */ zs->zs_suspended = 0; za->za_rcv_flags_mask |= DO_TRANSMIT; za->za_ext = 1; ZSSETSOFT(zs); mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); return (DDI_SUCCESS); } #ifdef ZSA_DEBUG static void zsa_print_info(struct zscom *zs) { register struct asyncline *za = (struct asyncline *)&zs->zs_priv_str; register queue_t *q = za->za_ttycommon.t_writeq; printf(" next q=%s\n", (RD(q))->q_next->q_qinfo->qi_minfo->mi_idname); printf("unit=%d\n", zs->zs_unit); printf("tflag:\n"); if (za->za_ttycommon.t_flags & TS_SOFTCAR) printf(" t_fl:TS_SOFTCAR"); if (za->za_ttycommon.t_flags & TS_XCLUDE) printf(" t_fl:TS_XCLUDE"); if (za->za_ttycommon.t_iflag & IGNBRK) printf(" t_ifl:IGNBRK"); if (za->za_ttycommon.t_iflag & BRKINT) printf(" t_ifl:BRKINT"); if (za->za_ttycommon.t_iflag & IGNPAR) printf(" t_ifl:IGNPAR"); if (za->za_ttycommon.t_iflag & PARMRK) printf(" t_ifl:PARMRK"); if (za->za_ttycommon.t_iflag & INPCK) printf(" t_ifl:INPCK"); if (za->za_ttycommon.t_iflag & ISTRIP) printf(" t_ifl:ISTRIP"); if (za->za_ttycommon.t_iflag & INLCR) printf(" t_ifl:INLCR"); if (za->za_ttycommon.t_iflag & IGNCR) printf(" t_ifl:IGNCR"); if (za->za_ttycommon.t_iflag & ICRNL) printf(" t_ifl:ICRNL"); if (za->za_ttycommon.t_iflag & IUCLC) printf(" t_ifl:IUCLC"); if (za->za_ttycommon.t_iflag & IXON) printf(" t_ifl:IXON"); if (za->za_ttycommon.t_iflag & IXOFF) printf(" t_ifl:IXOFF"); printf("\n"); if (za->za_ttycommon.t_cflag & CSIZE == CS5) printf(" t_cfl:CS5"); if (za->za_ttycommon.t_cflag & CSIZE == CS6) printf(" t_cfl:CS6"); if (za->za_ttycommon.t_cflag & CSIZE == CS7) printf(" t_cfl:CS7"); if (za->za_ttycommon.t_cflag & CSIZE == CS8) printf(" t_cfl:CS8"); if (za->za_ttycommon.t_cflag & CSTOPB) printf(" t_cfl:CSTOPB"); if (za->za_ttycommon.t_cflag & CREAD) printf(" t_cfl:CREAD"); if (za->za_ttycommon.t_cflag & PARENB) printf(" t_cfl:PARENB"); if (za->za_ttycommon.t_cflag & PARODD) printf(" t_cfl:PARODD"); if (za->za_ttycommon.t_cflag & HUPCL) printf(" t_cfl:HUPCL"); if (za->za_ttycommon.t_cflag & CLOCAL) printf(" t_cfl:CLOCAL"); printf(" t_stopc=%x", za->za_ttycommon.t_stopc); printf("\n"); } #endif /* * Check for abort character sequence */ static boolean_t abort_charseq_recognize(uchar_t ch) { static int state = 0; #define CNTRL(c) ((c)&037) static char sequence[] = { '\r', '~', CNTRL('b') }; if (ch == sequence[state]) { if (++state >= sizeof (sequence)) { state = 0; return (B_TRUE); } } else { state = (ch == sequence[0]) ? 1 : 0; } return (B_FALSE); }