/*
* 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
* 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/sysmacros.h>
#include <sys/ser_sync.h>
#define ZSH_TRACING
#ifdef ZSH_TRACING
/*
* Temp tracepoint definitions
*/
#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++] = c; \
}
#define zsh_h_log_clear
#else
#define zsh_h_log_add(c) \
{ \
}
#define zsh_h_log_clear \
{ register char *p; \
*p = '\0'; \
}
#endif
}
#endif
#ifndef MAXZSH
#endif /* MAXZSH */
/*
* The HDLC protocol
*/
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) */
};
putq, /* input put procedure */
NULL, /* input service procedure */
zsh_open, /* open procedure */
zsh_close, /* close procedure */
NULL, /* reserved */
&hdlc_minfo, /* module info */
NULL /* reserved */
};
(int (*)())zsh_wput, /* output put procedure */
NULL, /* output service procedure */
NULL, /* open procedure */
NULL, /* close procedure */
NULL, /* reserved */
&hdlc_minfo, /* module info */
NULL /* reserved */
};
&hdlc_rinit, /* initialize read queue */
&hdlc_winit, /* initialize write queue */
NULL, /* mux read qinit */
NULL /* mux write qinit */
};
/*
* This is the loadable module wrapper.
*/
/*
* Module linkage information for the kernel.
*/
&mod_driverops, /* Type of module. This one is a driver */
"Z8530 serial HDLC drv",
&zsh_ops, /* our own ops for this module */
};
(void *)&modldrv,
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
{
}
/*
* The HDLC interrupt entry points.
*/
NULL,
};
static void zsh_watchdog(void *);
static void zsh_callback(void *);
/*
* 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.
*/
{ \
register int n = ZSH_MAX_RSTANDBY; \
while (--n >= 0) { \
if (!zss->sl_rstandby[n]) { \
if ((zss->sl_rstandby[n] = \
break; \
} else \
} \
} \
allocbcount--; \
} \
} \
}
/*
* Should be called holding the spin (zs_excl_hi) mutex.
*/
{ \
register int n = ZSH_MAX_RSTANDBY; \
while (--n >= 0) { \
break; \
} \
} \
}
{ \
else \
} \
}
{ \
}
/*
* Should be called holding only the adaptive (zs_excl) mutex.
*/
{ \
zss->sl_rdone_rptr = 0; \
} else \
}
#define ZSH_FLUSHQ \
{ \
for (;;) { \
if (!(tmp)) \
break; \
} \
}
/*ARGSUSED*/
static int
{
return (DDI_PROBE_DONTCARE);
}
/*ARGSUSED*/
static int
{
register int unit;
'\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);
return (DDI_FAILURE); /* zsattach not done */
if (unit > 1)
return (DDI_FAILURE); /* only use cpu ports */
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
unit++;
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/* ARGSUSED */
int
void **result)
{
return (DDI_FAILURE);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_FAILURE;
} else {
error = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
}
return (error);
}
static int
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
static void
{
/*
* send msg that CTS is up
*/
} else {
}
} else {
}
}
/*
* Open routine.
*/
/*ARGSUSED*/
static int
{
register int unit;
register int tmp;
return (EBUSY); /* We got a stream that is in use */
return (ENXIO); /* unit not configured */
return (ENXIO); /* device not found by autoconfig */
return (ENXIO); /* device not found by autoconfig */
}
return (EBUSY); /* another protocol got here first */
}
/* Mark device as busy (for power management) */
zss->sl_soft_active = 0;
return (ENOSR);
}
} else { /* CLONEOPEN */
if (!zsh_usedminor[unit]) {
break;
}
return (ENODEV);
"zsh clone open failed, no memory, rq=%p\n",
(void *)rq);
return (ENOMEM);
}
}
return (0);
}
/*
* Close routine.
*/
int zsh_tx_enable_in_close = 0;
/*ARGSUSED*/
static int
{
int i;
/*
* Note that a close is only called on the last close of a
* particular stream. Assume that we need to do it all.
*/
return (0); /* already been closed once */
} else {
goto out;
if (zss->sl_xstandby) {
}
/*
* Stop the Watchdog Timer.
*/
/*
* Cancel outstanding "bufcall" request.
*/
ZSDELAY();
ZSDELAY();
}
SCC_BIC(15,
} else
(void) SCC_READDATA(); /* reset RX */
ZSDELAY();
(void) SCC_READDATA();
ZSDELAY();
(void) SCC_READDATA();
ZSDELAY();
/*
* Free up everything we ever allocated.
*/
}
if (mp)
}
if (mp)
for (i = 0; i < ZSH_MAX_RSTANDBY; i++) {
if (mp)
}
}
if (mp)
if (mp)
if (mp)
if (sl_wd_id)
if (sl_bufcid)
while (zss->sl_soft_active)
drv_usecwait(1);
/* Mark device as available for power management */
}
out:
return (0);
}
static int
{
return (1);
}
return (0);
}
/*
* Put procedure for write queue.
*/
static void
{
register int ppa;
/*
* stp->str_com supplied by open or DLPI attach.
*/
return;
}
}
else
return;
}
"zsh: clone device %d must be attached before use!",
return;
}
"zsh%x: invalid operation for clone dev.\n",
return;
}
} else {
}
case M_DATA:
/*
* Queue the message up to be transmitted.
* Set "in progress" flag and call the start routine.
*/
"zsh%x not initialized, can't send message",
return;
}
(void) zsh_softint(zs);
}
return;
}
"zsh_wput end: zs = %p", zs);
return;
}
if (!zss->sl_xstandby) {
if (tmp)
else {
if (tmp)
goto again;
}
} else if (tmp) {
}
"zsh_wput end: zs = %p", zs);
return;
}
}
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.
*/
return;
}
error = DL_BADPRIM;
goto end_proto;
}
if (prim != DL_ATTACH_REQ) {
error = DL_BADPRIM;
goto end_proto;
}
goto end_proto;
}
goto end_proto;
}
/*
* another protocol got here first
*/
goto end_proto;
}
if (error)
else
break;
case M_IOCTL:
break;
case M_IOCDATA:
/*
* Just free message on failure.
*/
break;
}
case S_IOCGETMODE:
case S_IOCGETSTATS:
case S_IOCGETSPEED:
case S_IOCGETMCTL:
case S_IOCGETMRU:
break;
case S_IOCSETMODE:
if (error) {
} else
break;
case S_IOCSETMRU:
break;
default:
}
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 (tmp)
}
} else
break;
default:
/*
* "No, I don't want a subscription to Chain Store Age,
* thank you anyway."
*/
break;
}
}
/*
* 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
{
/*
* 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.
*/
"zsh_start start: zs = %p", zs);
/*
* if we get another msg by chance zsh_watchog will start
*/
sl_flags &= ~SF_XMT_INPROG;
"zsh_start end: zs = %d", zs);
return (0);
}
ZSH_ALLOCB(mp);
if (!mp)
return (0);
goto transmit;
}
sl_flags &= ~SF_XMT_INPROG;
"zsh_start end: zs = %p", zs);
return (0);
}
#ifdef ZSH_DEBUG
debug_enter("xhead1");
}
#endif
"zsh_start end: zs = %p", zs);
return (1);
}
/*
* Process an "ioctl" message sent down to us.
*/
static void
{
register int error = 0;
/*
* another protocol got here first
*/
goto end_zsh_ioctl;
}
case S_IOCGETMODE:
break;
}
else
break;
case S_IOCGETSTATS:
break;
}
else
break;
case S_IOCGETSPEED:
break;
}
else
break;
case S_IOCGETMCTL:
break;
}
else
break;
case S_IOCGETMRU:
break;
}
else
break;
case S_IOCSETMODE:
if (error != 0)
break;
if (error == 0)
} else
break;
case S_IOCCLRSTATS:
break;
case S_IOCSETMRU:
if (error != 0)
break;
} else
break;
case S_IOCSETDTR:
/*
* The first integer of the M_DATA block that should
* follow indicate if DTR must be set or reset
*/
if (error != 0)
break;
else
break;
default:
}
}
/*
* Set the mode of the zsh port
*/
int
{
register int error = 0;
return (EINVAL); /* not supported */
} else {
CONN_SIGNAL) != 0) { /* Changing, going... */
sizeof (struct sl_status),
BPRI_MED);
}
} else { /* ...down. */
if (mp)
}
}
return (EINVAL);
}
return (EINVAL);
}
}
}
return (error);
}
/*
* Transmit interrupt service procedure
*/
static void
{
register int tmp;
SCC_WRITEDATA(*wr_cur++);
return;
}
switch (zss->sl_txstate) {
/*
* we here because end of message block lim = cur
*/
case TX_ACTIVE:
if (mp) {
if (!tmp)
goto again_txint;
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.
*/
}
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:
} else { /* FDX path */
}
}
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:
break;
/*
* Arriving here means the flag should be out and it's finally
* time to close the barn door.
*/
case TX_LAST:
break;
/*
* If transmit was aborted, do nothing - watchdog will recover.
*/
case TX_ABORTED:
break;
default:
break;
}
}
/*
* External Status Change interrupt service procedure
*/
static void
{
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:
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;
}
}
/*
* 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
* 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).
*/
}
}
(CONN_IBM | CONN_SIGNAL))) {
}
}
/*
* 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.
*/
zss->sl_wd_count = 0;
}
}
/*
* Receive interrupt service procedure
*/
static void
{
unsigned char *rd_cur;
*rd_cur++ = SCC_READDATA();
return;
}
if (!rd_cur) { /* Beginning of frame */
if (!bp) {
ZSH_ALLOCB(bp);
}
} else { /* end of data block should be cur==lim */
}
if (!bp) {
return;
}
}
/*
* Special Receive Condition Interrupt routine
*/
static void
{
(void) SCC_READDATA();
return;
}
return;
/*
* Drop one CRC byte from length because it came in
* before the special interrupt got here.
*/
/*
* put on done queue
*/
(void) SCC_READDATA();
} else
}
/*
* Handle a second stage interrupt.
* Does mostly lower priority buffer management stuff.
*/
static int
{
register queue_t *q;
register int allocbcount = 0;
int m_error;
return (0);
}
zss->sl_m_error = 0;
} else {
}
goto next;
}
}
}
next:
for (;;) {
if (!mp)
break;
allocbcount++;
}
continue;
}
if (!(canputnext(q))) {
allocbcount++;
continue;
}
if (!(canputnext(q))) {
continue;
}
}
if (!head) {
allocbcount++;
} else {
if (!tail)
}
}
if (allocbcount)
if (!zss->sl_xstandby) {
if (tmp) {
} else {
goto again;
}
} else {
if (tmp)
}
while (head) {
if (!tail) {
break;
}
}
if (m_error)
zss->sl_soft_active = 0;
return (0);
}
/*
* Initialization routine.
* Sets Clock sources, baud rate, modes and miscellaneous parameters.
*/
static int
{
register int wr11 = 0;
register int baud = 0;
register int pll = 0;
register int speed = 0;
int err = 0;
switch (sm->sm_txclock) {
case TXC_IS_TXC:
break;
case TXC_IS_RXC:
break;
case TXC_IS_BAUD:
baud++;
break;
case TXC_IS_PLL:
pll++;
break;
default:
goto out;
}
switch (sm->sm_rxclock) {
case RXC_IS_RXC:
break;
case RXC_IS_TXC:
break;
case RXC_IS_BAUD:
baud++;
break;
case RXC_IS_PLL:
pll++;
break;
default:
goto out;
}
goto out;
}
goto out;
}
/*
* If we're going to use the BRG and the speed we want is != 0...
*/
if (tconst == 0) {
goto out;
}
} else {
tconst = 0; /* Stop BRG. Also quiesces pin 24. */
}
if (pll) {
else
tconst = 0;
if (tconst == 0) {
goto out;
}
}
goto out;
}
}
} else {
}
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
{
}
}
/*
* Received Bad Frame procedure
*/
static void
{
/*
* swallow bad characters
*/
(void) SCC_READDATA();
(void) SCC_READDATA();
(void) SCC_READDATA();
/*
* Free active receive message.
*/
}
}
}
/*
* Transmit error procedure
*/
static void
{
}
/*
* drop RTS and our notion of CTS
*/
}
}
/*
* Transmitter watchdog timeout routine
*/
static void
{
int warning = 0;
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.
*/
else {
}
goto end_watchdog;
}
if (zss->sl_wd_count-- > 0)
goto end_watchdog;
else {
}
}
}
switch (zss->sl_txstate) {
case TX_ABORTED:
/*
* Transmitter was hung ... try restarting it.
*/
} 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
* 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) {
warning = 1;
}
break;
case TX_RTS:
/*
* Timer expired after we raised RTS. CTS never came up.
*/
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;
}
} else {
}
if (warning || do_flushwq) {
if (mp)
}
if (warning)
}
static void
{
}
}