/* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2011 Bayard G. Bell. All rights reserved. * Copyright (c) 2019 Peter Tribble. */ /* * HDLC protocol handler for Z8530 SCC. */ #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 #define ZSH_TRACING #ifdef ZSH_TRACING #include /* * Temp tracepoint definitions */ #define TR_ZSH 50 #define TR_ZSH_TXINT 1 #define TR_ZSH_XSINT 2 #define TR_ZSH_RXINT 3 #define TR_ZSH_SRINT 4 #define TR_ZSH_WPUT_START 5 #define TR_ZSH_WPUT_END 6 #define TR_ZSH_START_START 7 #define TR_ZSH_START_END 8 #define TR_ZSH_SOFT_START 9 #define TR_ZSH_SOFT_END 10 #define TR_ZSH_OPEN 11 #define TR_ZSH_CLOSE 12 #endif /* ZSH_TRACING */ /* * Logging definitions */ /* * #define ZSH_DEBUG */ #ifdef ZSH_DEBUG #ifdef ZS_DEBUG_ALL extern char zs_h_log[]; extern int zs_h_log_n; #define zsh_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'; \ } #define zsh_h_log_clear #else #define ZSH_H_LOG_MAX 0x8000 char zsh_h_log[2][ZSH_H_LOG_MAX +10]; int zsh_h_log_n[2]; #define zsh_h_log_add(c) \ { \ if (zsh_h_log_n[zs->zs_unit] >= ZSH_H_LOG_MAX) \ zsh_h_log_n[zs->zs_unit] = 0; \ zsh_h_log[zs->zs_unit][zsh_h_log_n[zs->zs_unit]++] = c; \ zsh_h_log[zs->zs_unit][zsh_h_log_n[zs->zs_unit]] = '\0'; \ } #define zsh_h_log_clear \ { char *p; \ for (p = &zsh_h_log[zs->zs_unit][ZSH_H_LOG_MAX]; \ p >= &zsh_h_log[zs->zs_unit][0]; p--) \ *p = '\0'; \ zsh_h_log_n[zs->zs_unit] = 0; \ } #endif #define ZSH_R0_LOG(r0) { \ if (r0 & ZSRR0_RX_READY) zsh_h_log_add('R'); \ if (r0 & ZSRR0_TIMER) zsh_h_log_add('Z'); \ if (r0 & ZSRR0_TX_READY) zsh_h_log_add('T'); \ if (r0 & ZSRR0_CD) zsh_h_log_add('D'); \ if (r0 & ZSRR0_SYNC) zsh_h_log_add('S'); \ if (r0 & ZSRR0_CTS) zsh_h_log_add('C'); \ if (r0 & ZSRR0_TXUNDER) zsh_h_log_add('U'); \ if (r0 & ZSRR0_BREAK) zsh_h_log_add('B'); \ } #endif #ifndef MAXZSH #define MAXZSH 2 #define MAXZSHCLONES (80) /* three clone opens per instance */ #endif /* MAXZSH */ int maxzsh = MAXZSH; int zsh_timer_count = 10; int zsh_default_mru = 1024; struct ser_str *zsh_str = NULL; unsigned char zsh_usedminor[MAXZSHCLONES]; /* * The HDLC protocol */ int zsh_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int zsh_probe(dev_info_t *dev); static int zsh_attach(dev_info_t *dev, ddi_attach_cmd_t cmd); static int zsh_detach(dev_info_t *dev, ddi_detach_cmd_t cmd); static int zsh_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr); static int zsh_close(queue_t *rq, int flag, cred_t *cr); static int zsh_wput(queue_t *wq, mblk_t *mp); static int zsh_start(struct zscom *zs, struct syncline *zss); static void zsh_ioctl(queue_t *wq, mblk_t *mp); static struct module_info hdlc_minfo = { 0x5a48, /* module ID number: "ZH" */ "zsh", /* module name */ 0, /* minimum packet size accepted */ INFPSZ, /* maximum packet size accepted */ 12 * 1024, /* queue high water mark (bytes) */ 4 * 1024 /* queue low water mark (bytes) */ }; static struct qinit hdlc_rinit = { putq, /* input put procedure */ NULL, /* input service procedure */ zsh_open, /* open procedure */ zsh_close, /* close procedure */ NULL, /* reserved */ &hdlc_minfo, /* module info */ NULL /* reserved */ }; static struct qinit hdlc_winit = { zsh_wput, /* output put procedure */ NULL, /* output service procedure */ NULL, /* open procedure */ NULL, /* close procedure */ NULL, /* reserved */ &hdlc_minfo, /* module info */ NULL /* reserved */ }; struct streamtab hdlctab = { &hdlc_rinit, /* initialize read queue */ &hdlc_winit, /* initialize write queue */ NULL, /* mux read qinit */ NULL /* mux write qinit */ }; DDI_DEFINE_STREAM_OPS(zsh_ops, nulldev, zsh_probe, zsh_attach, zsh_detach, nodev, zsh_info, D_MP, &hdlctab, ddi_quiesce_not_supported); /* * This is the loadable module wrapper. */ #include #include /* * Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ "Z8530 serial HDLC drv", &zsh_ops, /* our own ops for this module */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; int _init(void) { return (mod_install(&modlinkage)); } int _fini(void) { return (mod_remove(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * The HDLC interrupt entry points. */ static void zsh_txint(struct zscom *zs); static void zsh_xsint(struct zscom *zs); static void zsh_rxint(struct zscom *zs); static void zsh_srint(struct zscom *zs); static int zsh_softint(struct zscom *zs); struct zsops zsops_hdlc = { zsh_txint, zsh_xsint, zsh_rxint, zsh_srint, zsh_softint, NULL, NULL }; static int zsh_program(struct zscom *zs, struct scc_mode *sm); static void zsh_setmstat(struct zscom *zs, int event); static void zsh_rxbad(struct zscom *zs, struct syncline *zss); static void zsh_txbad(struct zscom *zs, struct syncline *zss); static void zsh_watchdog(void *); static void zsh_callback(void *); static int zsh_hdp_ok_or_rts_state(struct zscom *zs, struct syncline *zss); static void zsh_init_port(struct zscom *zs, struct syncline *zss); static int zsh_setmode(struct zscom *zs, struct syncline *zss, struct scc_mode *sm); /* * The HDLC Driver. */ /* * 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 ZSH_GETBLOCK(zs, allocbcount) \ { \ int n = ZSH_MAX_RSTANDBY; \ while (--n >= 0) { \ if (zss->sl_rstandby[n] == NULL) { \ if ((zss->sl_rstandby[n] = \ allocb(zss->sl_mru, BPRI_MED)) == NULL) { \ if (zss->sl_bufcid == 0) { \ mutex_enter(zs->zs_excl_hi); \ if (zss->sl_txstate != TX_OFF) { \ mutex_exit(zs->zs_excl_hi); \ zss->sl_bufcid = bufcall(zss->sl_mru, \ BPRI_MED, zsh_callback, zs); \ break; \ } else \ mutex_exit(zs->zs_excl_hi); \ } \ } \ allocbcount--; \ } \ } \ } /* * Should be called holding the spin (zs_excl_hi) mutex. */ #define ZSH_ALLOCB(mp) \ { \ int n = ZSH_MAX_RSTANDBY; \ mp = NULL; \ while (--n >= 0) { \ if ((mp = zss->sl_rstandby[n]) != NULL) { \ zss->sl_rstandby[n] = NULL; \ break; \ } \ } \ } #define ZSH_PUTQ(mp) \ { \ int wptr, rptr; \ wptr = zss->sl_rdone_wptr; \ rptr = zss->sl_rdone_rptr; \ zss->sl_rdone[wptr] = mp; \ if ((wptr) + 1 == ZSH_RDONE_MAX) \ zss->sl_rdone_wptr = wptr = 0; \ else \ zss->sl_rdone_wptr = ++wptr; \ if (wptr == rptr) { /* Should never occur */ \ SCC_BIC(1, ZSWR1_INIT); \ zss->sl_m_error = ENOSR; \ ZSSETSOFT(zs); \ } \ } #define ZSH_FREEMSG(mp) \ { \ ZSH_PUTQ(mp); \ } /* * Should be called holding only the adaptive (zs_excl) mutex. */ #define ZSH_GETQ(mp) \ { \ if (zss->sl_rdone_rptr != zss->sl_rdone_wptr) { \ mp = zss->sl_rdone[zss->sl_rdone_rptr++]; \ if (zss->sl_rdone_rptr == ZSH_RDONE_MAX) \ zss->sl_rdone_rptr = 0; \ } else \ mp = NULL; \ } #define ZSH_FLUSHQ \ { \ mblk_t *tmp; \ for (;;) { \ ZSH_GETQ(tmp); \ if (tmp == NULL) \ break; \ freemsg(tmp); \ } \ } /*ARGSUSED*/ static int zsh_probe(dev_info_t *dev) { return (DDI_PROBE_DONTCARE); } /*ARGSUSED*/ static int zsh_attach(dev_info_t *dev, ddi_attach_cmd_t cmd) { int unit; char name[3] = { '\0', '\0', '\0' }; /* * Since zsh is a child of the "pseudo" nexus, we can expect the * attach routine to be called only once. We need to create all * necessary devices in one shot. There is never more than one * SCC chip that supports zsh devices. */ if (cmd != DDI_ATTACH) return (DDI_FAILURE); if (zscom == NULL) return (DDI_FAILURE); /* zsattach not done */ unit = 2 * ddi_get_instance(dev); if (unit > 1) return (DDI_FAILURE); /* only use cpu ports */ if (ddi_create_minor_node(dev, "zsh", S_IFCHR, 0, DDI_PSEUDO, CLONE_DEV) == DDI_FAILURE) { ddi_remove_minor_node(dev, NULL); cmn_err(CE_WARN, "zsh clone device creation failed."); return (DDI_FAILURE); } for (; unit < maxzsh / 2; unit++) { zscom[unit].zs_hdlc_dip = dev; (void) sprintf(name, "%d", unit); if (ddi_create_minor_node(dev, name, S_IFCHR, 2 * unit, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_remove_minor_node(dev, NULL); return (DDI_FAILURE); } unit++; (void) sprintf(name, "%d", unit); if (ddi_create_minor_node(dev, name, S_IFCHR, 2 * (unit - 1) + 1, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_remove_minor_node(dev, NULL); return (DDI_FAILURE); } } return (DDI_SUCCESS); } /* ARGSUSED */ int zsh_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { dev_t dev = (dev_t)arg; int unit, error; struct zscom *zs; if ((unit = UNIT(dev)) >= nzs) return (DDI_FAILURE); switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if (zscom == NULL) { error = DDI_FAILURE; } else { zs = &zscom[unit]; *result = zs->zs_hdlc_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); } static int zsh_detach(dev_info_t *dev, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); ddi_remove_minor_node(dev, NULL); return (DDI_SUCCESS); } static void zsh_init_port(struct zscom *zs, struct syncline *zss) { uchar_t s0; SCC_WRITE(3, (ZSWR3_RX_ENABLE | ZSWR3_RXCRC_ENABLE | ZSWR3_RX_8)); SCC_WRITE(5, (ZSWR5_TX_8 | ZSWR5_DTR | ZSWR5_TXCRC_ENABLE)); zss->sl_rr0 = SCC_READ0(); if (zss->sl_flags & SF_FDXPTP) { SCC_BIS(5, ZSWR5_TX_ENABLE); SCC_BIS(5, ZSWR5_RTS); s0 = SCC_READ0(); if ((s0 & ZSRR0_CTS) || !(zss->sl_mode.sm_config & (CONN_SIGNAL | CONN_IBM))) { /* * send msg that CTS is up */ zss->sl_rr0 |= ZSRR0_CTS; zss->sl_txstate = TX_IDLE; } else { zss->sl_flags |= SF_XMT_INPROG; zss->sl_txstate = TX_RTS; zss->sl_rr0 &= ~ZSRR0_CTS; zss->sl_wd_count = zsh_timer_count; if (zss->sl_wd_id == NULL) zss->sl_wd_id = timeout(zsh_watchdog, zs, SIO_WATCHDOG_TICK); } } else { SCC_BIC(15, ZSR15_CTS); SCC_BIC(5, ZSWR5_TX_ENABLE); SCC_BIC(5, ZSWR5_RTS); zss->sl_flags &= ~SF_FLUSH_WQ; } } /* * Open routine. */ /*ARGSUSED*/ static int zsh_open(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr) { struct zscom *zs; struct syncline *zss; struct ser_str *stp; int unit; int tmp; if (sflag != CLONEOPEN) { if (rq->q_ptr) return (EBUSY); /* We got a stream that is in use */ unit = UNIT(*dev); if (unit >= maxzsh) return (ENXIO); /* unit not configured */ if (zscom == NULL) return (ENXIO); /* device not found by autoconfig */ zs = &zscom[unit]; if (zs->zs_ops == NULL) { return (ENXIO); /* device not found by autoconfig */ } TRACE_1(TR_ZSH, TR_ZSH_OPEN, "zsh_open:unit = %d", unit); mutex_enter(zs->zs_excl); if ((zs->zs_ops != &zsops_null) && (zs->zs_ops != &zsops_hdlc)) { mutex_exit(zs->zs_excl); return (EBUSY); /* another protocol got here first */ } /* Mark device as busy (for power management) */ (void) pm_busy_component(zs->zs_dip, unit%2+1); (void) ddi_dev_is_needed(zs->zs_dip, unit%2+1, 1); zsopinit(zs, &zsops_hdlc); zss = (struct syncline *)&zscom[unit].zs_priv_str; stp = &zss->sl_stream; stp->str_state = 0; stp->str_com = (caddr_t)zs; zss->sl_xhead = NULL; zss->sl_xactb = NULL; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; zss->sl_rhead = NULL; zss->sl_ractb = NULL; zs->zs_rd_cur = NULL; zs->zs_rd_lim = NULL; zss->sl_mstat = NULL; zss->sl_xstandby = NULL; zss->sl_wd_id = 0; zss->sl_soft_active = 0; zss->sl_stream.str_rq = NULL; zs->zs_priv = (caddr_t)zss; zss->sl_mru = zsh_default_mru; tmp = ZSH_MAX_RSTANDBY; ZSH_GETBLOCK(zs, tmp); if (zss->sl_rstandby[0] == NULL) { cmn_err(CE_WARN, "zsh_open: can't alloc message block"); mutex_exit(zs->zs_excl); return (ENOSR); } mutex_enter(zs->zs_excl_hi); ZSH_ALLOCB(zss->sl_ractb); zss->sl_txstate = TX_OFF; zss->sl_rr0 = SCC_READ0(); zss->sl_flags &= (SF_INITIALIZED | SF_FDXPTP); if (zss->sl_flags & SF_INITIALIZED) zsh_init_port(zs, zss); mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); } else { /* CLONEOPEN */ mutex_enter(&zs_curr_lock); for (unit = maxzsh; unit < MAXZSHCLONES; unit++) if (zsh_usedminor[unit] == '\0') { zsh_usedminor[unit] = (unsigned char)unit; break; } mutex_exit(&zs_curr_lock); if (unit >= MAXZSHCLONES) /* no slots available */ return (ENODEV); *dev = makedevice(getmajor(*dev), unit); stp = kmem_zalloc(sizeof (struct ser_str), KM_NOSLEEP); if (stp == NULL) { cmn_err(CE_WARN, "zsh clone open failed, no memory, rq=%p\n", (void *)rq); return (ENOMEM); } stp->str_state = STR_CLONE; stp->str_com = NULL; /* can't determine without ppa */ } stp->str_rq = rq; stp->str_inst = unit; rq->q_ptr = WR(rq)->q_ptr = (caddr_t)stp; qprocson(rq); return (0); } /* * Close routine. */ int zsh_tx_enable_in_close = 0; /*ARGSUSED*/ static int zsh_close(queue_t *rq, int flag, cred_t *cr __unused) { struct ser_str *stp; struct zscom *zs; struct syncline *zss; mblk_t *mp; int i; timeout_id_t sl_wd_id; bufcall_id_t sl_bufcid; /* * Note that a close is only called on the last close of a * particular stream. Assume that we need to do it all. */ qprocsoff(rq); /* no new business after this */ stp = (struct ser_str *)rq->q_ptr; if (stp == NULL) return (0); /* already been closed once */ if (stp->str_state == STR_CLONE) { zsh_usedminor[stp->str_inst] = 0; } else { zs = (struct zscom *)stp->str_com; if (zs == NULL) goto out; TRACE_1(TR_ZSH, TR_ZSH_CLOSE, "zs = %p", zs); zss = (struct syncline *)zs->zs_priv; mutex_enter(zs->zs_excl); flushq(WR(rq), FLUSHALL); mutex_enter(zs->zs_excl_hi); if (zss->sl_xstandby) { zss->sl_xstandby->b_wptr = zss->sl_xstandby->b_rptr; ZSH_FREEMSG(zss->sl_xstandby); zss->sl_xstandby = NULL; } mutex_exit(zs->zs_excl_hi); ZSH_FLUSHQ; /* * Stop the Watchdog Timer. */ if ((sl_wd_id = zss->sl_wd_id) != 0) zss->sl_wd_id = 0; /* * Cancel outstanding "bufcall" request. */ if ((sl_bufcid = zss->sl_bufcid) != 0) zss->sl_bufcid = 0; mutex_enter(zs->zs_excl_hi); if (zs->zs_wr_cur) { zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; SCC_WRITE0(ZSWR0_SEND_ABORT); ZSDELAY(); ZSDELAY(); } zss->sl_txstate = TX_OFF; /* so it can't rearm in close */ zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; SCC_BIC(15, (ZSR15_TX_UNDER | ZSR15_BREAK | ZSR15_SYNC | ZSR15_CTS)); SCC_WRITE(3, 0); /* Quiesce receiver */ if (zsh_tx_enable_in_close && !(zss->sl_flags & SF_FDXPTP)) { SCC_BIS(5, ZSWR5_TX_ENABLE); } else SCC_BIC(5, ZSWR5_TX_ENABLE); SCC_BIC(5, (ZSWR5_DTR | ZSWR5_RTS | ZSWR5_TXCRC_ENABLE)); SCC_WRITE0(ZSWR0_RESET_TXINT); /* reset TX */ SCC_WRITE0(ZSWR0_RESET_STATUS); /* reset XS */ SCC_WRITE0(ZSWR0_RESET_ERRORS); (void) SCC_READDATA(); /* reset RX */ ZSDELAY(); (void) SCC_READDATA(); ZSDELAY(); (void) SCC_READDATA(); ZSDELAY(); /* * Free up everything we ever allocated. */ if ((mp = zss->sl_rhead) != NULL) { zss->sl_ractb = NULL; /* already freed */ zs->zs_rd_cur = NULL; zs->zs_rd_lim = NULL; zss->sl_rhead = NULL; } mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); mutex_enter(zs->zs_excl_hi); if ((mp = zss->sl_ractb) != NULL) { zs->zs_rd_cur = NULL; zs->zs_rd_lim = NULL; zss->sl_ractb = NULL; } mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); for (i = 0; i < ZSH_MAX_RSTANDBY; i++) { mutex_enter(zs->zs_excl_hi); mp = zss->sl_rstandby[i]; zss->sl_rstandby[i] = NULL; mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); } mutex_enter(zs->zs_excl_hi); if ((mp = zss->sl_xhead) != NULL) { zss->sl_xhead = NULL; zss->sl_xactb = NULL; } mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); ZSH_FLUSHQ; mutex_enter(zs->zs_excl_hi); if ((mp = zss->sl_xstandby) != NULL) zss->sl_xstandby = NULL; mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); mutex_enter(zs->zs_excl_hi); if ((mp = zss->sl_mstat) != NULL) zss->sl_mstat = NULL; zss->sl_txstate = TX_OFF; /* so it can't rearm in close */ mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); zss->sl_stream.str_rq = NULL; zsopinit(zs, &zsops_null); mutex_exit(zs->zs_excl); if (sl_wd_id) (void) untimeout(sl_wd_id); if (sl_bufcid) unbufcall(sl_bufcid); while (zss->sl_soft_active) drv_usecwait(1); /* Mark device as available for power management */ (void) pm_idle_component(zs->zs_dip, zs->zs_unit%2+1); } if (stp->str_state == STR_CLONE) kmem_free(stp, sizeof (struct ser_str)); out: rq->q_ptr = WR(rq)->q_ptr = NULL; return (0); } static int zsh_hdp_ok_or_rts_state(struct zscom *zs, struct syncline *zss) { uchar_t s0; SCC_BIS(15, ZSR15_CTS); SCC_BIS(5, ZSWR5_RTS); s0 = SCC_READ0(); if (s0 & ZSRR0_CTS) { SCC_BIS(5, ZSWR5_TX_ENABLE); zss->sl_rr0 |= ZSRR0_CTS; return (1); } zss->sl_flags |= SF_XMT_INPROG; zss->sl_txstate = TX_RTS; zss->sl_rr0 &= ~ZSRR0_CTS; zss->sl_wd_count = zsh_timer_count; return (0); } /* * Put procedure for write queue. */ static int zsh_wput(queue_t *wq, mblk_t *mp) { struct ser_str *stp = (struct ser_str *)wq->q_ptr; struct zscom *zs; struct syncline *zss = NULL; ulong_t prim, error = 0; union DL_primitives *dlp; int ppa; mblk_t *tmp; struct copyresp *resp; /* * stp->str_com supplied by open or DLPI attach. */ if (stp == NULL) { freemsg(mp); return (0); } zs = (struct zscom *)stp->str_com; TRACE_0(TR_ZSH, TR_ZSH_WPUT_START, "zsh_wput start"); if ((mp->b_datap->db_type == M_FLUSH) && (stp->str_state == STR_CLONE)) { if (*mp->b_rptr & FLUSHW) { flushq(wq, FLUSHDATA); *mp->b_rptr &= ~FLUSHW; } if (*mp->b_rptr & FLUSHR) qreply(wq, mp); /* let the read queues have at it */ else freemsg(mp); return (0); } if ((zs == NULL) && (mp->b_datap->db_type != M_PROTO)) { freemsg(mp); cmn_err(CE_WARN, "zsh: clone device %d must be attached before use!", stp->str_inst); (void) putnextctl1(RD(wq), M_ERROR, EPROTO); return (0); } if (stp->str_state == STR_CLONE) { /* Clone opened, limited. */ if ((mp->b_datap->db_type != M_PROTO) && (mp->b_datap->db_type != M_IOCTL) && (mp->b_datap->db_type != M_IOCDATA)) { freemsg(mp); cmn_err(CE_WARN, "zsh%x: invalid operation for clone dev.\n", stp->str_inst); (void) putnextctl1(RD(wq), M_ERROR, EPROTO); return (0); } } else { zss = (struct syncline *)zs->zs_priv; } switch (mp->b_datap->db_type) { case M_DATA: /* * Queue the message up to be transmitted. * Set "in progress" flag and call the start routine. */ mutex_enter(zs->zs_excl_hi); if (!(zss->sl_flags & SF_INITIALIZED)) { mutex_exit(zs->zs_excl_hi); cmn_err(CE_WARN, "zsh%x not initialized, can't send message", zs->zs_unit); freemsg(mp); (void) putnextctl1(RD(wq), M_ERROR, ECOMM); return (0); } mutex_exit(zs->zs_excl_hi); if (zs->zs_flags & ZS_NEEDSOFT) { zs->zs_flags &= ~ZS_NEEDSOFT; (void) zsh_softint(zs); } while (mp->b_wptr == mp->b_rptr) { mblk_t *mp1; mp1 = unlinkb(mp); freemsg(mp); mp = mp1; if (mp == NULL) return (0); } mutex_enter(zs->zs_excl); (void) putq(wq, mp); mutex_enter(zs->zs_excl_hi); if (zss->sl_flags & SF_FLUSH_WQ) { mutex_exit(zs->zs_excl_hi); flushq(wq, FLUSHDATA); mutex_exit(zs->zs_excl); TRACE_1(TR_ZSH, TR_ZSH_WPUT_END, "zsh_wput end: zs = %p", zs); return (0); } tmp = NULL; again: if (zss->sl_xstandby == NULL) { if (tmp) zss->sl_xstandby = tmp; else { mutex_exit(zs->zs_excl_hi); tmp = getq(wq); mutex_enter(zs->zs_excl_hi); if (tmp) goto again; } } else if (tmp) { mutex_exit(zs->zs_excl_hi); (void) putbq(wq, tmp); mutex_enter(zs->zs_excl_hi); } if (zss->sl_flags & SF_XMT_INPROG) { mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); TRACE_1(TR_ZSH, TR_ZSH_WPUT_END, "zsh_wput end: zs = %p", zs); return (0); } if (zss->sl_wd_id == NULL) { zss->sl_wd_count = zsh_timer_count; zss->sl_txstate = TX_IDLE; mutex_exit(zs->zs_excl_hi); zss->sl_wd_id = timeout(zsh_watchdog, zs, SIO_WATCHDOG_TICK); mutex_enter(zs->zs_excl_hi); } zss->sl_flags |= SF_XMT_INPROG; if ((zss->sl_flags & SF_FDXPTP) || zsh_hdp_ok_or_rts_state(zs, zss)) (void) zsh_start(zs, zss); mutex_exit(zs->zs_excl_hi); mutex_exit(zs->zs_excl); break; case M_PROTO: /* * Here is where a clone device finds out about the * hardware it is going to attach to. The request is * validated and a ppa is extracted from it and validated. * This number is used to index the hardware data structure * and the protocol data structure, in case the latter * was not provided by a data-path open before this. */ if (stp->str_state != STR_CLONE) { freemsg(mp); return (0); } if (MBLKL(mp) < DL_ATTACH_REQ_SIZE) { prim = DL_ATTACH_REQ; error = DL_BADPRIM; goto end_proto; } dlp = (union DL_primitives *)mp->b_rptr; prim = dlp->dl_primitive; if (prim != DL_ATTACH_REQ) { error = DL_BADPRIM; goto end_proto; } ppa = dlp->attach_req.dl_ppa; ppa = (ppa%2) ? ((ppa-1)*2 +1) : (ppa*2); if (ppa >= maxzsh) { error = DL_BADPPA; goto end_proto; } zs = &zscom[ppa]; if (zs->zs_ops == NULL) { error = ENXIO; goto end_proto; } mutex_enter(zs->zs_excl); if ((zs->zs_ops != &zsops_null) && (zs->zs_ops != &zsops_hdlc)) { /* * another protocol got here first */ error = (EBUSY); mutex_exit(zs->zs_excl); goto end_proto; } stp->str_com = (caddr_t)zs; mutex_exit(zs->zs_excl); end_proto: if (error) dlerrorack(wq, mp, prim, error, 0); else dlokack(wq, mp, DL_ATTACH_REQ); break; case M_IOCTL: zsh_ioctl(wq, mp); break; case M_IOCDATA: resp = (struct copyresp *)mp->b_rptr; if (resp->cp_rval) { /* * Just free message on failure. */ freemsg(mp); break; } switch (resp->cp_cmd) { case S_IOCGETMODE: case S_IOCGETSTATS: case S_IOCGETSPEED: case S_IOCGETMCTL: case S_IOCGETMRU: mioc2ack(mp, NULL, 0, 0); qreply(wq, mp); break; case S_IOCSETMODE: zss = (struct syncline *)&zs->zs_priv_str; mutex_enter(zs->zs_excl); error = zsh_setmode(zs, zss, (struct scc_mode *)mp->b_cont->b_rptr); if (error) { struct iocblk *iocp = (struct iocblk *)mp->b_rptr; mp->b_datap->db_type = M_IOCNAK; iocp->ioc_error = error; } else mioc2ack(mp, NULL, 0, 0); mutex_exit(zs->zs_excl); qreply(wq, mp); break; case S_IOCSETMRU: zss = (struct syncline *)&zs->zs_priv_str; mutex_enter(zs->zs_excl); zss->sl_mru = *(int *)mp->b_cont->b_rptr; mutex_exit(zs->zs_excl); mioc2ack(mp, NULL, 0, 0); qreply(wq, mp); break; default: freemsg(mp); } break; /* * We're at the bottom of the food chain, so we flush our * write queue, clear the FLUSHW bit so it doesn't go round * and round forever, then flush our read queue (since there's * no read put procedure down here) and pass it up for any * higher modules to deal with in their own way. */ case M_FLUSH: if (*mp->b_rptr & FLUSHW) { mutex_enter(zs->zs_excl); flushq(wq, FLUSHDATA); mutex_enter(zs->zs_excl_hi); tmp = zss->sl_xstandby; zss->sl_xstandby = NULL; mutex_exit(zs->zs_excl_hi); if (tmp) freemsg(tmp); mutex_exit(zs->zs_excl); *mp->b_rptr &= ~FLUSHW; } if (*mp->b_rptr & FLUSHR) { mutex_enter(zs->zs_excl); ZSH_FLUSHQ; mutex_exit(zs->zs_excl); qreply(wq, mp); /* let the read queues have at it */ } else freemsg(mp); break; default: /* * "No, I don't want a subscription to Chain Store Age, * thank you anyway." */ freemsg(mp); break; } TRACE_1(TR_ZSH, TR_ZSH_WPUT_END, "zsh_wput end: zs = %p", zs); return (0); } /* * Get the next message from the write queue, set up the necessary pointers, * state info, etc., and start the transmit "engine" by sending the first * character. We'll then rotate through txint until done, then get an xsint. */ static int zsh_start(struct zscom *zs, struct syncline *zss) { mblk_t *mp; uchar_t *wptr; uchar_t *rptr; uchar_t sl_flags = zss->sl_flags; /* * Attempt to grab the next M_DATA message off the queue (that's * all that will be left after wput) and begin transmission. * This routine is normally called after completion of a previous * frame, or when zsh_wput gets a new message. If we are in a * mode that put us in the TX_RTS state, waiting for CTS, and CTS * is not up yet, we have no business here. Ditto if we're in * either the TX_ACTIVE or TX_CRC states. In these cases we * don't clear SF_CALLSTART, so we don't forget there's work to do. */ TRACE_1(TR_ZSH, TR_ZSH_START_START, "zsh_start start: zs = %p", zs); if (sl_flags & SF_PHONY) { sl_flags &= ~SF_PHONY; SCC_BIC(15, ZSR15_CTS); SCC_BIC(5, ZSWR5_RTS); SCC_WRITE0(ZSWR0_RESET_TXINT); SCC_BIC(5, ZSWR5_TX_ENABLE); zss->sl_rr0 &= ~ZSRR0_CTS; zss->sl_txstate = TX_IDLE; /* * if we get another msg by chance zsh_watchog will start */ sl_flags &= ~SF_XMT_INPROG; zss->sl_flags = sl_flags; TRACE_1(TR_ZSH, TR_ZSH_START_END, "zsh_start end: zs = %d", zs); return (0); } mp = zss->sl_xstandby; if (mp == NULL) { if (!(sl_flags & SF_FDXPTP)) { sl_flags |= SF_PHONY; ZSH_ALLOCB(mp); if (mp == NULL) return (0); mp->b_datap->db_type = M_RSE; mp->b_wptr = mp->b_rptr + 1; goto transmit; } sl_flags &= ~SF_XMT_INPROG; zss->sl_flags = sl_flags; TRACE_1(TR_ZSH, TR_ZSH_START_END, "zsh_start end: zs = %p", zs); return (0); } transmit: zss->sl_xstandby = NULL; rptr = mp->b_rptr; wptr = mp->b_wptr; ZSSETSOFT(zs); #ifdef ZSH_DEBUG if (zss->sl_xhead || zss->sl_xactb) { debug_enter("xhead1"); } #endif zss->sl_xhead = mp; zss->sl_xactb = mp; zss->sl_wd_count = zsh_timer_count; zss->sl_txstate = TX_ACTIVE; zss->sl_ocnt = 0; SCC_BIS(10, ZSWR10_UNDERRUN_ABORT); /* abort on underrun */ SCC_WRITE0(ZSWR0_RESET_TXCRC); /* reset transmit CRC */ zss->sl_ocnt = wptr - rptr; mp->b_wptr = rptr; /* to tell soft to free this msg */ SCC_WRITEDATA(*rptr++); /* resets TXINT */ zs->zs_wr_cur = rptr; zs->zs_wr_lim = wptr; SCC_WRITE0(ZSWR0_RESET_EOM); TRACE_1(TR_ZSH, TR_ZSH_START_END, "zsh_start end: zs = %p", zs); zss->sl_flags = sl_flags; return (1); } /* * Process an "ioctl" message sent down to us. */ static void zsh_ioctl(queue_t *wq, mblk_t *mp) { struct ser_str *stp = (struct ser_str *)wq->q_ptr; struct zscom *zs = (struct zscom *)stp->str_com; struct syncline *zss = (struct syncline *)&zs->zs_priv_str; struct iocblk *iocp = (struct iocblk *)mp->b_rptr; struct scc_mode *sm; struct sl_stats *st; uchar_t *msignals; mblk_t *tmp; int error = 0; mutex_enter(zs->zs_excl); if ((zs->zs_ops != &zsops_null) && (zs->zs_ops != &zsops_hdlc)) { /* * another protocol got here first */ error = (EBUSY); goto end_zsh_ioctl; } switch (iocp->ioc_cmd) { case S_IOCGETMODE: tmp = allocb(sizeof (struct scc_mode), BPRI_MED); if (tmp == NULL) { error = EAGAIN; break; } if (iocp->ioc_count != TRANSPARENT) mioc2ack(mp, tmp, sizeof (struct scc_mode), 0); else mcopyout(mp, NULL, sizeof (struct scc_mode), NULL, tmp); sm = (struct scc_mode *)mp->b_cont->b_rptr; bcopy(&zss->sl_mode, sm, sizeof (struct scc_mode)); break; case S_IOCGETSTATS: tmp = allocb(sizeof (struct sl_stats), BPRI_MED); if (tmp == NULL) { error = EAGAIN; break; } if (iocp->ioc_count != TRANSPARENT) mioc2ack(mp, tmp, sizeof (struct sl_stats), 0); else mcopyout(mp, NULL, sizeof (struct sl_stats), NULL, tmp); st = (struct sl_stats *)mp->b_cont->b_rptr; bcopy(&zss->sl_st, st, sizeof (struct sl_stats)); break; case S_IOCGETSPEED: 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); *(int *)mp->b_cont->b_rptr = zss->sl_mode.sm_baudrate; break; case S_IOCGETMCTL: tmp = allocb(sizeof (char), BPRI_MED); if (tmp == NULL) { error = EAGAIN; break; } if (iocp->ioc_count != TRANSPARENT) mioc2ack(mp, tmp, sizeof (char), 0); else mcopyout(mp, NULL, sizeof (char), NULL, tmp); msignals = (uchar_t *)mp->b_cont->b_rptr; *msignals = zss->sl_rr0 & (ZSRR0_CD | ZSRR0_CTS); break; case S_IOCGETMRU: 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); *(int *)mp->b_cont->b_rptr = zss->sl_mru; break; case S_IOCSETMODE: if (iocp->ioc_count != TRANSPARENT) { error = miocpullup(mp, sizeof (struct scc_mode)); if (error != 0) break; error = zsh_setmode(zs, zss, (struct scc_mode *)mp->b_cont->b_rptr); if (error == 0) mioc2ack(mp, NULL, 0, 0); } else mcopyin(mp, NULL, sizeof (struct scc_mode), NULL); break; case S_IOCCLRSTATS: mutex_enter(zs->zs_excl_hi); bzero(&zss->sl_st, sizeof (struct sl_stats)); mutex_exit(zs->zs_excl_hi); mioc2ack(mp, NULL, 0, 0); break; case S_IOCSETMRU: if (iocp->ioc_count != TRANSPARENT) { error = miocpullup(mp, sizeof (int)); if (error != 0) break; zss->sl_mru = *(int *)mp->b_cont->b_rptr; mioc2ack(mp, NULL, 0, 0); } else mcopyin(mp, NULL, sizeof (int), NULL); break; case S_IOCSETDTR: /* * The first integer of the M_DATA block that should * follow indicate if DTR must be set or reset */ error = miocpullup(mp, sizeof (int)); if (error != 0) break; mutex_enter(zs->zs_excl_hi); if (*(int *)mp->b_cont->b_rptr != 0) (void) zsmctl(zs, ZSWR5_DTR, DMBIS); else (void) zsmctl(zs, ZSWR5_DTR, DMBIC); mutex_exit(zs->zs_excl_hi); break; default: error = EINVAL; } end_zsh_ioctl: iocp->ioc_error = error; mp->b_datap->db_type = (error) ? M_IOCNAK : M_IOCACK; mutex_exit(zs->zs_excl); qreply(wq, mp); } /* * Set the mode of the zsh port */ int zsh_setmode(struct zscom *zs, struct syncline *zss, struct scc_mode *sm) { int error = 0; mblk_t *mp; mutex_enter(zs->zs_excl_hi); if (sm->sm_rxclock == RXC_IS_PLL) { zss->sl_mode.sm_retval = SMERR_RXC; mutex_exit(zs->zs_excl_hi); return (EINVAL); /* not supported */ } else { if (((zss->sl_mode.sm_config ^ sm->sm_config) & CONN_SIGNAL) != 0) { /* Changing, going... */ if (sm->sm_config & CONN_SIGNAL) { /* ...up. */ if (zss->sl_mstat == NULL) { mutex_exit(zs->zs_excl_hi); mp = allocb( sizeof (struct sl_status), BPRI_MED); mutex_enter(zs->zs_excl_hi); zss->sl_mstat = mp; } } else { /* ...down. */ if ((mp = zss->sl_mstat) != NULL) zss->sl_mstat = NULL; mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); mutex_enter(zs->zs_excl_hi); } } if (!(sm->sm_config & CONN_IBM)) { if (sm->sm_config & CONN_HDX) { zss->sl_mode.sm_retval = SMERR_HDX; mutex_exit(zs->zs_excl_hi); return (EINVAL); } if (sm->sm_config & CONN_MPT) { zss->sl_mode.sm_retval = SMERR_MPT; mutex_exit(zs->zs_excl_hi); return (EINVAL); } } zss->sl_flags &= ~SF_FDXPTP; /* "conmode" */ if ((sm->sm_config & (CONN_HDX | CONN_MPT)) == 0) zss->sl_flags |= SF_FDXPTP; error = zsh_program(zs, sm); if (!error && (zs->zs_ops != &zsops_null)) zsh_init_port(zs, zss); } mutex_exit(zs->zs_excl_hi); return (error); } /* * Transmit interrupt service procedure */ static void zsh_txint(struct zscom *zs) { struct syncline *zss; mblk_t *mp; int tmp; uchar_t *wr_cur; TRACE_1(TR_ZSH, TR_ZSH_TXINT, "zsh_txint: zs = %p", zs); if ((wr_cur = zs->zs_wr_cur) != NULL && (wr_cur < zs->zs_wr_lim)) { SCC_WRITEDATA(*wr_cur++); zs->zs_wr_cur = wr_cur; return; } zss = (struct syncline *)&zs->zs_priv_str; switch (zss->sl_txstate) { /* * we here because end of message block lim = cur */ case TX_ACTIVE: mp = zss->sl_xactb; again_txint: mp = mp->b_cont; if (mp) { zss->sl_xactb = mp; zss->sl_ocnt += tmp = mp->b_wptr - mp->b_rptr; if (tmp == 0) goto again_txint; zs->zs_wr_cur = mp->b_rptr; zs->zs_wr_lim = mp->b_wptr; SCC_WRITEDATA(*zs->zs_wr_cur++); return; } /* * This is where the fun starts. At this point the * last character in the frame has been sent. We * issue a RESET_TXINT so we won't get another txint * until the CRC has been completely sent. Also we * reset the Abort-On-Underrun bit so that CRC is * sent at EOM, rather than an Abort. */ zs->zs_wr_cur = zs->zs_wr_lim = NULL; zss->sl_txstate = TX_CRC; SCC_WRITE0(ZSWR0_RESET_TXINT); if (!(zss->sl_flags & SF_PHONY)) { SCC_BIC(10, ZSWR10_UNDERRUN_ABORT); zss->sl_st.opack++; zss->sl_st.ochar += zss->sl_ocnt; } zss->sl_ocnt = 0; ZSH_FREEMSG(zss->sl_xhead); zss->sl_xhead = zss->sl_xactb = NULL; ZSSETSOFT(zs); break; /* * This txint means we have sent the CRC bytes at EOF. * The next txint will mean we are sending or have sent the * flag character at EOF, but we handle that differently, and * enter different states,depending on whether we're IBM or not. */ case TX_CRC: if (!(zss->sl_flags & SF_FDXPTP)) { zss->sl_txstate = TX_FLAG; /* HDX path */ } else { /* FDX path */ if (!zsh_start(zs, zss)) { zss->sl_txstate = TX_IDLE; SCC_WRITE0(ZSWR0_RESET_TXINT); } } break; /* * This txint means the closing flag byte is going out the door. * We use this state to allow this to complete before dropping RTS. */ case TX_FLAG: zss->sl_txstate = TX_LAST; (void) zsh_start(zs, zss); break; /* * Arriving here means the flag should be out and it's finally * time to close the barn door. */ case TX_LAST: zss->sl_txstate = TX_IDLE; SCC_WRITE0(ZSWR0_RESET_TXINT); break; /* * If transmit was aborted, do nothing - watchdog will recover. */ case TX_ABORTED: SCC_WRITE0(ZSWR0_RESET_TXINT); break; default: SCC_WRITE0(ZSWR0_RESET_TXINT); break; } } /* * External Status Change interrupt service procedure */ static void zsh_xsint(struct zscom *zs) { struct syncline *zss = (struct syncline *)&zs->zs_priv_str; uchar_t s0, x0; TRACE_1(TR_ZSH, TR_ZSH_XSINT, "zsh_xsint: zs = %p", zs); s0 = SCC_READ0(); x0 = s0 ^ zss->sl_rr0; zss->sl_rr0 = s0; SCC_WRITE0(ZSWR0_RESET_STATUS); if (s0 & ZSRR0_TXUNDER) { switch (zss->sl_txstate) { /* * A transmitter underrun has occurred. If we are not * here as the result of an abort sent by the watchdog * timeout routine, we need to send an abort to flush * the transmitter. Otherwise there is a danger of * trashing the next frame but still sending a good crc. * The TX_ABORTED flag is set so that the watchdog * routine can initiate recovery. */ case TX_ACTIVE: SCC_WRITE0(ZSWR0_SEND_ABORT); SCC_WRITE0(ZSWR0_RESET_TXINT); zss->sl_st.underrun++; zsh_txbad(zs, zss); zss->sl_txstate = TX_ABORTED; zss->sl_wd_count = 0; break; case TX_CRC: break; case TX_FLAG: break; case TX_ABORTED: break; case TX_OFF: break; case TX_LAST: break; default: break; } } if ((x0 & ZSRR0_BREAK) && (s0 & ZSRR0_BREAK) && zs->zs_rd_cur) { zss->sl_st.abort++; zsh_rxbad(zs, zss); } else if ((s0 & ZSRR0_SYNC) && (zs->zs_rd_cur)) { /* * Tricky code to avoid disaster in the case where * an abort was detected while receiving a packet, * but the abort did not last long enough to be * detected by zsh_xsint - this can happen since * the ZSRR0_BREAK is not latched. Since an abort * will automatically cause the SCC to enter * hunt mode, hopefully, the sync/hunt bit will be * set in this case (although if the interrupt is * sufficiently delayed, the SCC may have sync'ed * in again if it has detected a flag). */ zss->sl_st.abort++; zsh_rxbad(zs, zss); } if (x0 & s0 & ZSRR0_CTS) { if (zss->sl_txstate == TX_RTS) { if (!(zss->sl_flags & SF_FDXPTP)) { SCC_BIS(5, ZSWR5_TX_ENABLE); } (void) zsh_start(zs, zss); } else if ((zss->sl_mode.sm_config & (CONN_IBM | CONN_SIGNAL))) { zss->sl_flags &= ~SF_FLUSH_WQ; zsh_setmstat(zs, CS_CTS_UP); } } /* * We don't care about CTS transitions unless we are in either * IBM or SIGNAL mode, or both. So, if we see CTS drop, and we * care, and we are not idle, send up a report message. */ if ((x0 & ZSRR0_CTS) && ((s0 & ZSRR0_CTS) == 0) && (zss->sl_txstate != TX_OFF) && (zss->sl_mode.sm_config & (CONN_IBM | CONN_SIGNAL))) { SCC_BIC(15, ZSR15_CTS); zsh_setmstat(zs, CS_CTS_DOWN); zss->sl_flags &= ~SF_XMT_INPROG; zss->sl_flags |= SF_FLUSH_WQ; zss->sl_st.cts++; if (zss->sl_txstate != TX_IDLE) SCC_WRITE0(ZSWR0_SEND_ABORT); SCC_WRITE0(ZSWR0_RESET_ERRORS); SCC_WRITE0(ZSWR0_RESET_TXINT); zss->sl_wd_count = 0; zsh_txbad(zs, zss); } } /* * Receive interrupt service procedure */ static void zsh_rxint(struct zscom *zs) { struct syncline *zss = (struct syncline *)&zs->zs_priv_str; mblk_t *bp = zss->sl_ractb; unsigned char *rd_cur; TRACE_1(TR_ZSH, TR_ZSH_RXINT, "zsh_rxint: zs = %p", zs); if (((rd_cur = zs->zs_rd_cur) != NULL) && rd_cur < zs->zs_rd_lim) { *rd_cur++ = SCC_READDATA(); zs->zs_rd_cur = rd_cur; return; } if (rd_cur == NULL) { /* Beginning of frame */ if (bp == NULL) { ZSH_ALLOCB(bp); zss->sl_ractb = bp; } zss->sl_rhead = bp; } else { /* end of data block should be cur==lim */ bp->b_wptr = zs->zs_rd_cur; ZSH_ALLOCB(bp->b_cont); bp = zss->sl_ractb = bp->b_cont; } if (bp == NULL) { zss->sl_st.nobuffers++; zsh_rxbad(zs, zss); return; } zs->zs_rd_cur = bp->b_wptr; zs->zs_rd_lim = bp->b_datap->db_lim; *zs->zs_rd_cur++ = SCC_READDATA(); /* Also resets interrupt */ } /* * Special Receive Condition Interrupt routine */ static void zsh_srint(struct zscom *zs) { struct syncline *zss = (struct syncline *)&zs->zs_priv_str; uchar_t s1; uchar_t *rd_cur; TRACE_1(TR_ZSH, TR_ZSH_SRINT, "zsh_srint: zs = %p", zs); SCC_READ(1, s1); if (s1 & ZSRR1_RXEOF) { /* end of frame */ (void) SCC_READDATA(); SCC_WRITE0(ZSWR0_RESET_ERRORS); if (s1 & ZSRR1_FE) { /* bad CRC */ zss->sl_st.crc++; zsh_rxbad(zs, zss); return; } if ((rd_cur = zs->zs_rd_cur) == NULL) return; /* * Drop one CRC byte from length because it came in * before the special interrupt got here. */ zss->sl_ractb->b_wptr = rd_cur - 1; /* * put on done queue */ ZSH_PUTQ(zss->sl_rhead); zss->sl_rhead = NULL; zss->sl_ractb = NULL; zs->zs_rd_cur = NULL; zs->zs_rd_lim = NULL; ZSSETSOFT(zs); } else if (s1 & ZSRR1_DO) { (void) SCC_READDATA(); SCC_WRITE0(ZSWR0_RESET_ERRORS); zss->sl_st.overrun++; zsh_rxbad(zs, zss); } else SCC_WRITE0(ZSWR0_RESET_ERRORS); } /* * Handle a second stage interrupt. * Does mostly lower priority buffer management stuff. */ static int zsh_softint(struct zscom *zs) { struct syncline *zss; queue_t *q; mblk_t *mp, *tmp; mblk_t *head = NULL, *tail = NULL; int allocbcount = 0; int m_error; TRACE_1(TR_ZSH, TR_ZSH_SOFT_START, "zsh_soft start: zs = %p", zs); mutex_enter(zs->zs_excl); zss = (struct syncline *)zs->zs_priv; if (zss == NULL || (q = zss->sl_stream.str_rq) == NULL) { mutex_exit(zs->zs_excl); return (0); } m_error = zss->sl_m_error; zss->sl_m_error = 0; if (zss->sl_mstat == NULL) zss->sl_mstat = allocb(sizeof (struct sl_status), BPRI_MED); mutex_enter(zs->zs_excl_hi); if (zss->sl_flags & SF_FLUSH_WQ) { if (!(zss->sl_flags & SF_FDXPTP)) { zss->sl_flags &= ~SF_FLUSH_WQ; } else { uchar_t s0; s0 = SCC_READ0(); if (s0 & ZSRR0_CTS) { zss->sl_rr0 |= ZSRR0_CTS; SCC_BIS(15, ZSR15_CTS); zss->sl_flags &= ~SF_FLUSH_WQ; zsh_setmstat(zs, CS_CTS_UP); } if (zss->sl_flags & SF_FLUSH_WQ) { mutex_exit(zs->zs_excl_hi); flushq(WR(q), FLUSHDATA); goto next; } } } mutex_exit(zs->zs_excl_hi); next: for (;;) { ZSH_GETQ(mp); if (mp == NULL) break; if (mp->b_rptr == mp->b_wptr) { if (mp->b_datap->db_type == M_RSE) { allocbcount++; } freemsg(mp); continue; } if (mp->b_datap->db_type == M_DATA) { zss->sl_st.ichar += msgdsize(mp); zss->sl_st.ipack++; if (!(canputnext(q))) { zss->sl_st.ierror++; allocbcount++; freemsg(mp); continue; } } else if (mp->b_datap->db_type == M_PROTO) { if (!(canputnext(q))) { freemsg(mp); continue; } } if (head == NULL) { allocbcount++; zss->sl_soft_active = 1; head = mp; } else { if (tail == NULL) tail = head; tail->b_next = mp; tail = mp; } } if (allocbcount) ZSH_GETBLOCK(zs, allocbcount); tmp = NULL; again: mutex_enter(zs->zs_excl_hi); if (zss->sl_xstandby == NULL) { if (tmp) { zss->sl_xstandby = tmp; mutex_exit(zs->zs_excl_hi); } else { mutex_exit(zs->zs_excl_hi); if (tmp = getq(WR(q))) goto again; } } else { mutex_exit(zs->zs_excl_hi); if (tmp) (void) putbq(WR(q), tmp); } mutex_exit(zs->zs_excl); while (head) { if (tail == NULL) { putnext(q, head); break; } mp = head; head = head->b_next; mp->b_next = NULL; putnext(q, mp); } if (m_error) (void) putnextctl1(q, M_ERROR, m_error); zss->sl_soft_active = 0; TRACE_1(TR_ZSH, TR_ZSH_SOFT_END, "zsh_soft end: zs = %p", zs); return (0); } /* * Initialization routine. * Sets Clock sources, baud rate, modes and miscellaneous parameters. */ static int zsh_program(struct zscom *zs, struct scc_mode *sm) { struct syncline *zss = (struct syncline *)&zs->zs_priv_str; struct zs_prog *zspp; ushort_t tconst = 0; int wr11 = 0; int baud = 0; int pll = 0; int speed = 0; int flags = ZSP_SYNC; int err = 0; ZSSETSOFT(zs); /* get our house in order */ switch (sm->sm_txclock) { case TXC_IS_TXC: wr11 |= ZSWR11_TXCLK_TRXC; break; case TXC_IS_RXC: wr11 |= ZSWR11_TXCLK_RTXC; break; case TXC_IS_BAUD: wr11 |= ZSWR11_TXCLK_BAUD; wr11 |= ZSWR11_TRXC_OUT_ENA + ZSWR11_TRXC_XMIT; baud++; break; case TXC_IS_PLL: wr11 |= ZSWR11_TXCLK_DPLL; pll++; break; default: zss->sl_mode.sm_retval = SMERR_TXC; err = EINVAL; goto out; } switch (sm->sm_rxclock) { case RXC_IS_RXC: wr11 |= ZSWR11_RXCLK_RTXC; break; case RXC_IS_TXC: wr11 |= ZSWR11_RXCLK_TRXC; break; case RXC_IS_BAUD: wr11 |= ZSWR11_RXCLK_BAUD; baud++; break; case RXC_IS_PLL: wr11 |= ZSWR11_RXCLK_DPLL; pll++; break; default: zss->sl_mode.sm_retval = SMERR_RXC; err = EINVAL; goto out; } if (baud && pll) { zss->sl_mode.sm_retval = SMERR_PLL; err = EINVAL; goto out; } if (pll && !(sm->sm_config & CONN_NRZI)) { zss->sl_mode.sm_retval = SMERR_PLL; err = EINVAL; goto out; } /* * If we're going to use the BRG and the speed we want is != 0... */ if (baud && (speed = sm->sm_baudrate)) { tconst = (PCLK + speed) / (2 * speed) - 2; if (tconst == 0) { zss->sl_mode.sm_retval = SMERR_BAUDRATE; err = EINVAL; goto out; } sm->sm_baudrate = PCLK / (2 * ((int)tconst + 2)); } else { tconst = 0; /* Stop BRG. Also quiesces pin 24. */ } if (pll) { if ((speed = sm->sm_baudrate * 32) != 0) tconst = (PCLK + speed) / (2 * speed) - 2; else tconst = 0; if (tconst == 0) { zss->sl_mode.sm_retval = SMERR_BAUDRATE; err = EINVAL; goto out; } speed = PCLK / (2 * ((int)tconst + 2)); sm->sm_baudrate = speed / 32; flags |= ZSP_PLL; } if ((sm->sm_config & (CONN_LPBK|CONN_ECHO)) == (CONN_LPBK|CONN_ECHO)) { zss->sl_mode.sm_retval = SMERR_LPBKS; err = EINVAL; goto out; } if (sm->sm_config & CONN_LPBK) flags |= ZSP_LOOP; if (sm->sm_config & CONN_NRZI) flags |= ZSP_NRZI; if (sm->sm_config & CONN_ECHO) flags |= ZSP_ECHO; zspp = &zs_prog[zs->zs_unit]; zspp->zs = zs; zspp->flags = (uchar_t)flags; zspp->wr4 = ZSWR4_SDLC; zspp->wr11 = (uchar_t)wr11; zspp->wr12 = (uchar_t)(tconst & 0xff); zspp->wr13 = (uchar_t)((tconst >> 8) & 0xff); zspp->wr3 = (uchar_t)(ZSWR3_RX_ENABLE | ZSWR3_RXCRC_ENABLE | ZSWR3_RX_8); zspp->wr5 = (uchar_t)(ZSWR5_TX_8 | ZSWR5_DTR | ZSWR5_TXCRC_ENABLE); if (zss->sl_flags & SF_FDXPTP) { zspp->wr5 |= ZSWR5_RTS; zss->sl_rr0 |= ZSRR0_CTS; /* Assume CTS is high */ } if (sm->sm_config & CONN_IBM) { zspp->wr15 = (uchar_t) (ZSR15_TX_UNDER | ZSR15_BREAK | ZSR15_SYNC | ZSR15_CTS); if (!(zss->sl_flags & SF_FDXPTP)) zspp->wr15 &= ~ZSR15_CTS; } else { zspp->wr5 |= ZSWR5_TX_ENABLE; zspp->wr15 = (uchar_t) (ZSR15_TX_UNDER | ZSR15_BREAK | ZSR15_SYNC); if (sm->sm_config & CONN_SIGNAL) zspp->wr15 |= ZSR15_CTS; } zs_program(zspp); SCC_WRITE0(ZSWR0_RESET_STATUS); /* reset XS */ SCC_WRITE0(ZSWR0_RESET_STATUS); /* reset XS */ zss->sl_flags |= SF_INITIALIZED; bzero(&zss->sl_st, sizeof (struct sl_stats)); bcopy(sm, &zss->sl_mode, sizeof (struct scc_mode)); zss->sl_mode.sm_retval = 0; /* successful */ out: return (err); } /* * Function to store modem signal changes in sl_mstat field. * Note that these events are supposed to be so far apart in time that * we should always be able to send up the event and allocate a message * block before another one happens. If not, we'll overwrite this one. */ static void zsh_setmstat(struct zscom *zs, int event) { struct syncline *zss = (struct syncline *)&zs->zs_priv_str; struct sl_status *mstat; mblk_t *mp; if (((mp = zss->sl_mstat) != NULL) && (zss->sl_mode.sm_config & (CONN_SIGNAL))) { mstat = (struct sl_status *)mp->b_wptr; mstat->type = (zss->sl_mode.sm_config & CONN_IBM) ? SLS_LINKERR : SLS_MDMSTAT; mstat->status = event; gethrestime(&mstat->tstamp); mp->b_wptr += sizeof (struct sl_status); mp->b_datap->db_type = M_PROTO; ZSH_PUTQ(mp); zss->sl_mstat = NULL; ZSSETSOFT(zs); } } /* * Received Bad Frame procedure */ static void zsh_rxbad(struct zscom *zs, struct syncline *zss) { /* * swallow bad characters */ (void) SCC_READDATA(); (void) SCC_READDATA(); (void) SCC_READDATA(); SCC_BIS(3, ZSWR3_HUNT); /* enter hunt mode - ignores rest of frame */ zss->sl_st.ierror++; /* * Free active receive message. */ if (zss->sl_rhead) { zss->sl_rhead->b_wptr = zss->sl_rhead->b_rptr; zss->sl_rhead->b_datap->db_type = M_RSE; ZSH_FREEMSG(zss->sl_rhead); zss->sl_ractb = NULL; zs->zs_rd_cur = NULL; zs->zs_rd_lim = NULL; } if (zss->sl_rhead) { zss->sl_rhead = NULL; ZSH_ALLOCB(zss->sl_ractb); zs->zs_rd_cur = NULL; zs->zs_rd_lim = NULL; } ZSSETSOFT(zs); } /* * Transmit error procedure */ static void zsh_txbad(struct zscom *zs, struct syncline *zss) { if (zss->sl_xhead) { /* free the message we were sending */ zss->sl_xhead->b_wptr = zss->sl_xhead->b_rptr; ZSH_FREEMSG(zss->sl_xhead); zss->sl_xactb = NULL; zs->zs_wr_cur = NULL; zs->zs_wr_lim = NULL; } zss->sl_xhead = NULL; if (!(zss->sl_flags & SF_FDXPTP)) { /* * drop RTS and our notion of CTS */ SCC_BIC(5, ZSWR5_RTS); SCC_BIC(5, ZSWR5_TX_ENABLE); zss->sl_rr0 &= ~ZSRR0_CTS; } zss->sl_txstate = TX_IDLE; if (!(zss->sl_flags & SF_PHONY)) zss->sl_st.oerror++; } /* * Transmitter watchdog timeout routine */ static void zsh_watchdog(void *arg) { struct zscom *zs = arg; struct syncline *zss = (struct syncline *)&zs->zs_priv_str; queue_t *wq; mblk_t *mp; int warning = 0; uchar_t s0; int do_flushwq = 0; /* * The main reason for this routine is because, under some * circumstances, a transmit interrupt may get lost (ie., if * underrun occurs after the last character has been sent, and * the tx interrupt following the abort gets scheduled before * the current tx interrupt has been serviced). Transmit can * also get hung if the cable is pulled out and the clock was * coming in from the modem. */ mutex_enter(zs->zs_excl); if (zss->sl_stream.str_rq) wq = WR(zss->sl_stream.str_rq); else { mutex_exit(zs->zs_excl); return; /* guard against close/callback race */ } mutex_enter(zs->zs_excl_hi); if (!(zss->sl_flags & SF_XMT_INPROG) && wq->q_first) { zss->sl_flags |= SF_XMT_INPROG; if ((zss->sl_flags & SF_FDXPTP) || zsh_hdp_ok_or_rts_state(zs, zss)) (void) zsh_start(zs, zss); goto end_watchdog; } if (zss->sl_wd_count-- > 0) goto end_watchdog; if (zss->sl_flags & SF_FLUSH_WQ) { if (!(zss->sl_flags & SF_FDXPTP)) zss->sl_flags &= ~SF_FLUSH_WQ; else { s0 = SCC_READ0(); if (s0 & ZSRR0_CTS) { zss->sl_rr0 |= ZSRR0_CTS; SCC_BIS(15, ZSR15_CTS); zss->sl_flags &= ~SF_FLUSH_WQ; zsh_setmstat(zs, CS_CTS_UP); } } } switch (zss->sl_txstate) { case TX_ABORTED: /* * Transmitter was hung ... try restarting it. */ if (zss->sl_flags & SF_FDXPTP) { zss->sl_flags |= SF_XMT_INPROG; (void) zsh_start(zs, zss); } else do_flushwq = 1; break; case TX_ACTIVE: case TX_CRC: /* * Transmit is hung for some reason. Reset tx interrupt. * Flush transmit fifo by sending an abort command * which also sets the Underrun/EOM latch in WR0 and in * turn generates an External Status interrupt that * will reset the necessary message buffer pointers. * The watchdog timer will cycle again to allow the SCC * to settle down after the abort command. The next * time through we'll see that the state is now TX_ABORTED * and call zsh_start to grab a new message. */ if (--zss->sl_wd_count <= 0) { SCC_WRITE0(ZSWR0_SEND_ABORT); SCC_WRITE0(ZSWR0_RESET_ERRORS); SCC_WRITE0(ZSWR0_RESET_TXINT); zsh_txbad(zs, zss); zss->sl_txstate = TX_ABORTED; /* must be after txbad */ warning = 1; } break; case TX_RTS: /* * Timer expired after we raised RTS. CTS never came up. */ zss->sl_st.cts++; zsh_setmstat(zs, CS_CTS_TO); zss->sl_flags &= ~SF_XMT_INPROG; zss->sl_flags |= SF_FLUSH_WQ; ZSSETSOFT(zs); break; default: /* * If we time out in an inactive state we set a soft * interrupt. This will call zsh_start which will * clear SF_XMT_INPROG if the queue is empty. */ break; } end_watchdog: if (zss->sl_txstate != TX_OFF) { mutex_exit(zs->zs_excl_hi); zss->sl_wd_id = timeout(zsh_watchdog, zs, SIO_WATCHDOG_TICK); } else { zss->sl_wd_id = 0; /* safety */ mutex_exit(zs->zs_excl_hi); } if (warning || do_flushwq) { flushq(wq, FLUSHDATA); mutex_enter(zs->zs_excl_hi); if ((mp = zss->sl_xstandby) != NULL) zss->sl_xstandby = NULL; mutex_exit(zs->zs_excl_hi); if (mp) freemsg(mp); } mutex_exit(zs->zs_excl); if (warning) cmn_err(CE_WARN, "zsh%x: transmit hung", zs->zs_unit); } static void zsh_callback(void *arg) { struct zscom *zs = arg; struct syncline *zss = (struct syncline *)&zs->zs_priv_str; int tmp = ZSH_MAX_RSTANDBY; mutex_enter(zs->zs_excl); if (zss->sl_bufcid) { zss->sl_bufcid = 0; ZSH_GETBLOCK(zs, tmp); } mutex_exit(zs->zs_excl); }