zs_hdlc.c revision 89b43686db1fe9681d80a7cf5662730cb9378cae
/*
* 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.
*/
/*
* HDLC protocol handler for Z8530 SCC.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/kmem.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/stat.h>
#include <sys/cred.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/buf.h>
#include <sys/mkdev.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/zsdev.h>
#include <sys/ser_sync.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/dlpi.h>
#define ZSH_TRACING
#ifdef ZSH_TRACING
#include <sys/vtrace.h>
/*
* 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 \
{ register 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);
static void 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 = {
(int (*)())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 <sys/errno.h>
#include <sys/modctl.h>
/*
* 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) \
{ \
register int n = ZSH_MAX_RSTANDBY; \
while (--n >= 0) { \
if (!zss->sl_rstandby[n]) { \
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) \
{ \
register 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) \
{ \
register 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 \
{ \
register mblk_t *tmp; \
for (;;) { \
ZSH_GETQ(tmp); \
if (!(tmp)) \
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)
{
register 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,
NULL, 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, NULL) == 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, NULL) == 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)
{
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:
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)
{
register 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)
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)
{
register struct zscom *zs;
register struct syncline *zss;
register struct ser_str *stp;
register int unit;
register 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 = NULL;
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]) {
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)
{
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)
{
register 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 void
zsh_wput(queue_t *wq, mblk_t *mp)
{
register struct ser_str *stp = (struct ser_str *)wq->q_ptr;
register struct zscom *zs;
register struct syncline *zss = NULL;
register ulong_t prim, error = 0;
register union DL_primitives *dlp;
register int ppa;
register mblk_t *tmp;
register struct copyresp *resp;
/*
* stp->str_com supplied by open or DLPI attach.
*/
if (stp == NULL) {
freemsg(mp);
return;
}
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;
}
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;
}
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;
}
} 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;
}
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) {
register mblk_t *mp1;
mp1 = unlinkb(mp);
freemsg(mp);
mp = mp1;
if (mp == NULL)
return;
}
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;
}
tmp = NULL;
again:
if (!zss->sl_xstandby) {
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;
}
if (!zss->sl_wd_id) {
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;
}
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) {
register 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);
}
/*
* 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)
{
register mblk_t *mp;
register uchar_t *wptr;
register uchar_t *rptr;
register 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)
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)
{
register struct ser_str *stp = (struct ser_str *)wq->q_ptr;
register struct zscom *zs = (struct zscom *)stp->str_com;
register struct syncline *zss = (struct syncline *)&zs->zs_priv_str;
register struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
register struct scc_mode *sm;
register struct sl_stats *st;
register uchar_t *msignals;
register mblk_t *tmp;
register 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)
{
register int error = 0;
register 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)
{
register struct syncline *zss;
register mblk_t *mp;
register int tmp;
register 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)
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)
{
register struct syncline *zss = (struct syncline *)&zs->zs_priv_str;
register 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)
{
register struct syncline *zss = (struct syncline *)&zs->zs_priv_str;
register 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) { /* Beginning of frame */
if (!bp) {
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) {
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)
{
register struct syncline *zss = (struct syncline *)&zs->zs_priv_str;
register uchar_t s1;
register 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)
{
register struct syncline *zss;
register queue_t *q;
register mblk_t *mp, *tmp;
register mblk_t *head = NULL, *tail = NULL;
register 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 || (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)
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 {
register 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)
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) {
allocbcount++;
zss->sl_soft_active = 1;
head = mp;
} else {
if (!tail)
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) {
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) {
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)
{
register struct syncline *zss = (struct syncline *)&zs->zs_priv_str;
register struct zs_prog *zspp;
register ushort_t tconst = 0;
register int wr11 = 0;
register int baud = 0;
register int pll = 0;
register int speed = 0;
register 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)
{
register struct syncline *zss = (struct syncline *)&zs->zs_priv_str;
register struct sl_status *mstat;
register 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);
}