4.4BSD/usr/src/sys/vax/if/ACC/driver/if_dda.c

Compare this file to the similar file:
Show the results in this format:

/*************************************************************************/
/*									 */
/*									 */
/*	 ________________________________________________________	 */
/*	/							 \	 */
/*     |	  AAA	       CCCCCCCCCCCCCC	 CCCCCCCCCCCCCC	  |	 */
/*     |	 AAAAA	      CCCCCCCCCCCCCCCC	CCCCCCCCCCCCCCCC  |	 */
/*     |	AAAAAAA	      CCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCC |	 */
/*     |       AAAA AAAA      CCCC		CCCC		  |	 */
/*     |      AAAA   AAAA     CCCC		CCCC		  |	 */
/*     |     AAAA     AAAA    CCCC		CCCC		  |	 */
/*     |    AAAA       AAAA   CCCC		CCCC		  |	 */
/*     |   AAAA	 AAAAAAAAAAA  CCCCCCCCCCCCCCCCC CCCCCCCCCCCCCCCCC |	 */
/*     |  AAAA	  AAAAAAAAAAA CCCCCCCCCCCCCCCC	CCCCCCCCCCCCCCCC  |	 */
/*     | AAAA	   AAAAAAAAA   CCCCCCCCCCCCCC	 CCCCCCCCCCCCCC	  |	 */
/*	\________________________________________________________/	 */
/*									 */
/*	Copyright (c) 1986 by Advanced Computer Communications		 */
/*	720 Santa Barbara Street, Santa Barbara, California  93101	 */
/*	(805) 963-9431							 */
/*									 */
/*									 */
/*  File:		if_dda.c					 */
/*									 */
/*  Project:		DDN-X.25 Network Interface Driver for ACP 5250	 */
/*			and ACP 6250					 */
/*									 */
/*  Function:		This is a network interface driver supporting	 */
/*			the ACP5250/6250 under UNIX versions 4.2, 4.3,	 */
/*			4.3-tahoe, Ultrix versions 1.2 and 2.0, and	 */
/*			under VMS,  TWG WIN/VX and TGV Multinet.	 */
/*									 */
/*  Components:		required: if_dda.c, if_ddareg.h, if_ddavar.h,	 */
/*			  and one of: if_dda_uqbus.c if_dda_bibus.c	 */
/*			optional: if_pi.c, if_pivar.h, if_x29.c,	 */
/*				  if_vmsx29.c				 */
/*									 */
/*************************************************************************/


#include "dda.h"

#if NDDA > 0

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%%		       SYSTEM CONFIGURATION			 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

#if !defined(ACC_ULTRIX) && !defined(ACC_BSD) && !defined(ACC_VMS)
	ERROR
		an ACC OS specific option must be defined in your config file
	ERROR
#endif

/*
 *	now define the un-set options to zero
 */
#if !defined(ACC_ULTRIX)
#define	ACC_ULTRIX	00
#endif

#if !defined(ACC_BSD)
#define	ACC_BSD		00
#endif

#if !defined(ACC_VMS)
#define ACC_VMS		00
#endif

/*
 * the define DDA_MSGQ enables the message queue.  this adds 2k to the
 * data size of the driver.  It should only be used during driver development
 */

/*#define DDA_MSGQ		/* uncomment this to enable message queue */

/*
 * The following line disables the use of the histogram facilities.  This
 * value (DDA_HISTOGRAM) is automatically undefined for all 4.2 and ULTRIX
 * 1.2 systems which do not support the histogram facilities.
 */

#define DDA_HISTOGRAM		/* comment this out to disable histogram */

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%%		       INCLUDE FILES				 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

#ifndef SIMULATION		/* real unix system */
#include "../machine/pte.h"	/* page table entries */
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/mbuf.h"
#include "../h/buf.h"
#include "../h/protosw.h"
#include "../h/socket.h"
#include "../h/vmmac.h"
#include "../h/errno.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/kernel.h"
#include "../h/ioctl.h"

#include "../vax/cpu.h"
#include "../vax/mtpr.h"

#include "../net/if.h"
#include "../net/netisr.h"
#include "../net/route.h"
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#  if ACC_BSD > 42 || ACC_ULTRIX > 12
#    include "../netinet/in_var.h"
#  endif
#include "../netinet/ip.h"
#include "../netinet/ip_var.h"

#include "../vaxif/if_ddareg.h"
#include "../vaxif/if_ddavar.h"

#else	SIMULATION
#include "machine/pte.h"	/* page table entries */

#include "h/param.h"
#include "h/systm.h"
#include "h/mbuf.h"
#include "h/buf.h"
#include "h/protosw.h"
#include "h/socket.h"
#include "h/vmmac.h"
#include "h/errno.h"
#include "h/dir.h"
#include "h/user.h"
#include "h/kernel.h"
#include "h/ioctl.h"

#include "vax/cpu.h"
#include "vax/mtpr.h"

#include "net/if.h"
#include "net/netisr.h"
#include "net/route.h"
#include "netinet/in.h"
#include "netinet/in_systm.h"
#  if ACC_BSD > 42 || ACC_ULTRIX > 12
#    include "netinet/in_var.h"
#  endif
#include "netinet/ip.h"
#include "netinet/ip_var.h"
#include "if_ddareg.h"
#include "if_ddavar.h"

#  ifndef	SIOCACPCONFIG
#    define	SIOCACPCONFIG	_IOWR(i,40,struct ifreq)
#  endif
#  ifndef	INET
#    define	INET	1
#  endif

extern	struct	ifqueue ipintrq;	/* IP input queue */
#endif	SIMULATION

#if ACC_VMS > 00
#  ifdef eunice
#    define WINS
#  else
#    define MULTINET
#  endif
#endif

#if ACC_VMS > 00
#  ifdef WINS
#    include <vms/adpdef.h>	/* Define Adapters */
#    include <vms/dcdef.h>	/* Define AT$_UBA, adapter type */
#  else MULTINET
#    include "../vaxif/if_ddaioctl.h"	/* not in ioctl.h */
#  endif
#endif 

/* disable histogram functions for BSD 4.2 and ULTRIX 1.2 */

#if ACC_BSD == 42 || ACC_ULTRIX == 12
#  undef DDA_HISTOGRAM
#endif

/* Ultrix doesn't have syslog, so use printf instead.  Since the two
 * functions take different arg list formats, embed the open paren in
 * the defined symbol; provide DDAELOG to close the call while keeping
 * parentheses matched.	 The argument to DDALOG is ignored for printf;
 * for log(), debugging messages use LOG_DEBUG, all others use LOG_ERR.
 */
#if (ACC_BSD > 42 || ACC_VMS > 00) && !defined(SIMULATION)
#  include "syslog.h"
#  define DDALOG(s)	log( s,
#else
#  define DDALOG(s)	printf(
#endif
#define DDAELOG		)

#ifndef	DDADEBUG
#define	PRIVATE		static		/* hide our internal functions */
#else
#define	PRIVATE				/* let the world see them */
#endif

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%%		       GLOBAL FUNCTIONS				 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

int		ddaprobe();
int		ddaattach();
int		ddareset();
int		ddainit();
int		ddaoutput();
int		ddatimer();
int		ddaioctl();
int		ddainta();	/* service interrupt "a" from front end */
int		ddaintb();	/* service interrupt "b" from front end */


/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%%		       LOCAL  FUNCTIONS				 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void send_config();
PRIVATE struct dda_cb *locate_x25_lcn();
PRIVATE struct dda_cb *find_free_lcn();
PRIVATE boolean	convert_ip_addr();
PRIVATE u_long	convert_x25_addr();
PRIVATE boolean	make_x25_call();
PRIVATE void	dda_start();
PRIVATE void	dda_rrq();
PRIVATE void	dda_wrq();
PRIVATE int	start_chn();
PRIVATE void	dda_data();
PRIVATE void	dda_supr();
PRIVATE void	supr_msg();
PRIVATE boolean	decode_ring();
PRIVATE void	decode_answer();
PRIVATE void	clear_lcn();
PRIVATE void	send_restart();
PRIVATE void	send_supr();
PRIVATE void	start_supr();
PRIVATE void	abort_io();
PRIVATE void	prt_addr();

#ifdef DDA_PAD_OR_RAW
PRIVATE int	dda_decode_type();
#endif

#ifdef DDA_PADOPT
PRIVATE void	x29_data();
PRIVATE void	x29_supr();
PRIVATE void	x29_init();
#endif DDA_PADOPT

#ifdef DDA_RAWOPT
PRIVATE void	pi_data();
PRIVATE void	pi_supr();
PRIVATE void	pi_init();
PRIVATE int	pi_circuit_to_handle_protocol();
#endif DDA_RAWOPT

#ifdef DDADEBUG
PRIVATE void	prt_bytes();
#endif

PRIVATE char    *fmt_x25();

#ifdef DDA_HISTOGRAM
PRIVATE void	hist_init();	/* histogram functions */
PRIVATE void	hist_lcn_state();
PRIVATE void	hist_all_lcns();
PRIVATE void	hist_link_state();
PRIVATE void	hist_read();
PRIVATE int	hist_copyout();

#else DDA_HISTOGRAM		/* make all histogram functions no-op's */
#define hist_init(a,b)
#define hist_lcn_state(a,b,c)
#define hist_all_lcns(a,b)
#define hist_link_state(a,b,c)
#define hist_read(a)
#define hist_copyout(a,b)
#endif DDA_HISTOGRAM


/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%%		       LOCAL  VARIABLES				 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE int	tmo_data_idle = TMO_DATA_IDLE;	/* idle circuit timeout for
						 * all boards */

PRIVATE int	nddach[4] = {	/* number of channels currently in use */
			NDDACH_DEFAULT, NDDACH_DEFAULT,
			NDDACH_DEFAULT, NDDACH_DEFAULT };

PRIVATE char   *dda_product;	/* name of product, like "ACP5250" */
PRIVATE int	dda_hasmaint;	/* do we have a maintenance board? */

/* the message bits are used in the DMESG macros defined in if_ddavar.h */
/* word 1 and 2 (msgs 0   -  63)  are reserved for the IP interface	*/
/* word 3	(msgs 64  -  95)  are reserved for the PI interface	*/
/* word 4	(msgs 96  -  127) are reserved for the X.29 interface	*/
/* word 5 and 6 (msgs 128 -  191) are reserved for debugging main module*/
/* word 7	(msgs 192 -  223) are reserved for debugging the PI     */
/* word 8	(msgs 224 -  255) are reserved for debugging X29	*/
/* word 9	(msgs 256 -  287) are reserved for call logging		*/
#define NDMESGWORDS	9
#define MAXDMSGS	(NDMESGWORDS * 32)
PRIVATE long	ddamsgs[NDDA][NDMESGWORDS];
/*					       |  |  |  |   |   |   |   |   |
 * default:	all informational messages on, /--/--/--/   |   |   |   |   |
 *		all debug messages off,	--------------------/---/---/---/   |
 *		log busys, but not calls or I/O aborts ---------------------/
 */

/* Must be as large as the larger of (trtab, ddactl, dnload): */
char	dda_iobuf[sizeof(struct ddactl)];

struct dda_softc dda_softc[NDDA];	/* per device infomation */

/*  header for building command to be sent to the front end in	 */
/*  response to ACPCONFIG user command				 */

PRIVATE u_char acpconfig_msg[] = {
	LINE_CNTL,		/* set command code */
	0x00,			/* not used */
	0x00,			/* not used */
	0x00,			/* extension length (set at runtime) */
	0x00,			/* cmd space */
	0x00,
	0x00,
	0x00,
	0x00
};

PRIVATE u_char bfr_size_msg[] =
{
 SET_BFR_SIZE,			/* set command code */
 0x00,				/* not used */
 0x00,				/* not used */
 0x01,				/* extension length */
 0x00,				/* cmd space */
 0x00,
 0x00,
 0x00,
 0x00
};

PRIVATE u_char	ddacb_cmnd[4] = { CALL, 0, 0, 0 };
PRIVATE u_char	ddacb_called_addr[16] = {0};
PRIVATE u_char	ddacb_calling_addr[16] = {0};
PRIVATE u_char	ddacb_facilities[64] = {0};
PRIVATE u_char	ddacb_protocol[5] = {0};
PRIVATE u_char	ddacb_user_data[64] = {0};

#ifdef DDADEBUG
u_char		dda_silo_counter;
u_char		dda_debug_silo[256];
#endif

/* Table of baud rate values and the associated parameter for the Set	*/
/* System Parameters message, ddainit_msg.  The 'parameter1' is nonzero */
/* for valid baud rate divisors.  These are nominal baud rates.		*/

PRIVATE struct baud {
    char	    b_value;
    u_char	    parameter1; /* first byte of baud rate setting  */
    u_char	    parameter2; /* second byte of baud rate setting */
}		ddabaud_rate[] = {
    { 1, 0x02, 0x00 },		/* 2.00M */
    { 2, 0x03, 0x00 },		/* 1.33M */
    { 3, 0x04, 0x00 },		/* 1.00M */
    { 4, 0x08, 0x00 },		/* 500K	 */
    { 5, 0x10, 0x00 },		/* 250K	 */
    { 6, 0x28, 0x00 },		/* 100K	 */
    { 7, 0x3e, 0x00 },		/* 64K	 */
    { 8, 0x47, 0x00 },		/* 56K	 */
    { 9, 0x85, 0x00 },		/* 30K	 */
    { 10, 0xd0, 0x00 },		/* 19.2K */
    { 11, 0xa1, 0x01 },		/* 9600	 */
    { 12, 0x41, 0x03 },		/* 4800	 */
    { 13, 0x83, 0x06 },		/* 2400	 */
    { 14, 0x05, 0x0d },		/* 1200	 */
    { 0, 0, 0 }
};

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%% Address Translation Table for Internet <-> X.25 addresses	 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

#define DDANATT 32	/* number of addr translation table entries */
PRIVATE int	dda_num_addr_tr[NDDA] = {0};	/* number of address
						 * translations */

 /* currently stored */
PRIVATE struct dda_addr_tr {	/* X.25 PDN address translation table */
    u_long	    ip_addr;			/* internet address */
    u_char	    x25_addr[MAXADDRLEN];	/* X.25 address */
}		dda_addr_tr[NDDA][DDANATT] = {{ 0L, ""}}; /* null */

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%% Aliasing of IP address for 4.2 ==> 4.3 compatibility	 %%*/
/*%% Note: this union is not required in 4.2, since the s_net	 %%*/
/*%% field and its friends are in an include file.  We use it to %%*/
/*%% minimize the number of #ifdef dependencies in the code.	 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

#ifdef s_net			/* 4.2 */
# undef s_net
# undef s_host
# undef s_lh
# undef s_impno
#endif

union imp_addr {
    struct in_addr  ip;
    struct imp {
	u_char		s_net;
	u_char		s_host;
	u_char		s_lh;
	u_char		s_impno;
    }		    imp;
};

/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%								 %%*/
/*%%		       GLOBAL ROUTINES				 %%*/
/*%%								 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

#ifdef	ACP_BI
#include "if_dda_bibus.c"
#else
#include "if_dda_uqbus.c"
#endif

/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%			  DDAIOCTL()				 %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*								   */
/*  Purpose:							   */
/*								   */
/*   This routine processes device dependent ioctl's.  Supported   */
/*   ioctls set the host's internet address for this network	   */
/*   interface, or send Set System Parameters Message to the ACP.  */
/*   The logic for setting the interface address must remain	   */
/*   compatible with both ifconfig and acpconfig programs.	   */
/*   If the ioctl comes from the acpconfig program, the front end  */
/*   is not initialized because the user will specify explicitly   */
/*   what parameters are desired.  If the ioctl comes from the	   */
/*   ifconfig program, the fron end is initialized with default	   */
/*   parameters in the ddainit_msg array.			   */
/*								   */
/*  Call:	     ddaioctl(ifp, cmd, data)			   */
/*  Argument:	     ifp:   pointer to the network interface data  */
/*				 structure, ifnet		   */
/*		     cmd:   identifies the type of ioctl	   */
/*		     data:  information for the ioctl		   */
/*  Returns:	     0 for success, or the nonzero error value:	   */
/*				  EINVAL invalid ioctl request	   */
/*  Called by:	      network software, address of this routine is */
/*		      defined in af_inet network interface struct  */
/*  Calls to:	      splimp()					   */
/*		      if_rtinit()				   */
/*		      in_netof()				   */
/*		      in_lnaof()				   */
/*		      ddainit()					   */
/*		      send_config()				   */
/*		      DDALOG()					   */
/*		      splx()					   */
/*								   */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

#ifdef MULTINET
volatile int	StatQuery_Completed;	/* Polled for board stat ioctl */
#endif

ddaioctl(ifp, cmd, data)
register struct ifnet *ifp;
int		cmd;
caddr_t		data;
{
    register struct dda_softc *ds = &dda_softc[ifp->if_unit];
    struct ifreq   *ifr = (struct ifreq *) data;

#if defined(DDA_PADOPT) && defined(WINS)
    int		    prealloc_x29();	/* Preallocate UCBs for X29 */
#endif

#if ACC_BSD == 42 || ACC_ULTRIX == 12
    struct sockaddr_in *sin = (struct sockaddr_in *) & ifr->ifr_addr;
#else
    struct ifaddr  *ifa = ds->dda_if.if_addrlist;
#endif

    int		    s;
    int		    error = 0;
    int		    i;
    register struct dda_addr_tr *atp, *btp;
    struct trtab   *tr;
    struct ddactl  *da;
    char	    arg2[MAXADDRLEN], code;

#ifdef DDADEBUG
    if (DDADBCH(4, ifp->if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: ioctl()\n", ifp->if_unit DDAELOG;
    }
#endif DDADEBUG

    /*
     * This may not be necessary here, but under some flavors of BSDish
     * systems (2.0ULTRIX) this routine is apparently called at splimp(). In
     * the case that we are currently processing ioctls issued from acpconfig
     * in /etc/rc, the board may not have come on line yet - so we need to be
     * able to process the B interrupt while in the delay loop below. 
     */
#ifndef MULTINET
    s = spl0();
#endif

    switch (cmd) {
      case SIOCSIFADDR:
	if (!suser())
	    return EACCES;

#if ACC_BSD == 42 || ACC_ULTRIX == 12
	if (ifp->if_flags & IFF_RUNNING)
	    if_rtinit(ifp, -1);	/* delete previous route */
	ifp->if_addr = *(struct sockaddr *) sin;
	ifp->if_net = in_netof(sin->sin_addr);
	ifp->if_host[0] = in_lnaof(sin->sin_addr);
	if (ifp->if_flags & IFF_RUNNING)
	    if_rtinit(ifp, RTF_UP);
	else
	    ddainit(ifp->if_unit);
	ds->dda_ipaddr.s_addr = ((struct sockaddr_in *) & ifp->if_addr)->sin_addr.s_addr;
#else					 /* 4.3 networking */
	if (ifa->ifa_addr.sa_family != AF_INET)
	    return (EINVAL);
	if ((ifp->if_flags & IFF_RUNNING) == 0)
	    ddainit(ifp->if_unit);
	ds->dda_ipaddr = IA_SIN(ifa)->sin_addr;
#endif					/* 4.3 networking */
	break;

      case SIOCACPCONFIG:
	/* process ioctl from acpconfig program */

	code = *(ifr->ifr_data);

	/*********************************************************
	 *							 *
	 *	Commands n, h, q, and r are non-privileged	 *
	 *							 *
	 *********************************************************/

	if (!suser() && code != 'n' && code != 'h' && code != 'q' && code != 'r')
	    return EACCES;

#if ACC_BSD == 42 || ACC_ULTRIX == 12
	sin = (struct sockaddr_in *) & ds->dda_if.if_addr;
	if (in_netof(sin->sin_addr) == 0)
#else 
	if (ds->dda_if.if_addrlist == 0)
#endif 
	{
	    error = EDESTADDRREQ;	/* error, no internet address */
	    goto exit;
	}
	/* for command to set baud rate, look up the value for the  */
	/* baud rate divisor in the ddabaud_rate table, put value   */
	/* in the Set System Parameters message, ddainit_msg        */

	if (code >= 1 && code <= 14) {
	    register struct baud *p;

	    if (error = diags_completed(ds))
		goto exit;
	    for (p = ddabaud_rate; p->b_value; p++) {
		if ((*(ifr->ifr_data) - p->b_value) == 0)
		    break;
	    }
	    /* if internal clock not set, do so */
	    if ((ds->dda_init & DDA_INTCLOCK) == 0) {
		ds->dda_init |= DDA_INTCLOCK;
		acpconfig_msg[MSG_OFFSET] = CLOCK_CNTL;
		acpconfig_msg[MSG_OFFSET + 1] = INTERNAL_CLOCK;
		acpconfig_msg[MSG_OFFSET + 2] = BAUD_CNTL;
		acpconfig_msg[MSG_OFFSET + 3] = p->parameter1;
		acpconfig_msg[MSG_OFFSET + 4] = p->parameter2;
		acpconfig_msg[MSG_LENGTH] = 5;
	    } else {
		acpconfig_msg[MSG_OFFSET] = BAUD_CNTL;
		acpconfig_msg[MSG_OFFSET + 1] = p->parameter1;
		acpconfig_msg[MSG_OFFSET + 2] = p->parameter2;
		acpconfig_msg[MSG_LENGTH] = 3;
	    }

	    if ((p->b_value == 0) || (p->parameter1 == 0))
		error = EINVAL;	/* baud rate value invalid */
	    else
		send_config(ds, acpconfig_msg);	/* send message to front end */
	    goto exit;
	}
	switch (code) {
	  case 'a':		/* Add address translation table entry */
	    if (error = diags_completed(ds))
		goto exit;
	    if (dda_num_addr_tr[ifp->if_unit] >= DDANATT) {	/* table already full */
		error = ENOMEM;
		goto exit;
	    }

	    /*
	     * Copy in user arguments and point "tr" at them.  Then scan the
	     * translation table and either find location to insert or flag
	     * error 
	     */
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct trtab))) {
		error = EFAULT;
		goto exit;
	    }
	    tr = (struct trtab *) dda_iobuf;
	    for (i = 0, atp = &dda_addr_tr[ifp->if_unit][0];
		 i < dda_num_addr_tr[ifp->if_unit]; i++, atp++) {
		if (atp->ip_addr == tr->ipaddr) {
		    if (bcmp((char *) atp->x25_addr,
			     (char *) tr->x25addr, MAXADDRLEN)) {
			error = EADDRINUSE;
			goto exit;
		    } else /* addresses are the same, just ignore ioctl */
			goto exit;
		}
		if (atp->ip_addr > tr->ipaddr)	/* insert entry here */
		    break;
	    }
	    for (btp = &dda_addr_tr[ifp->if_unit][dda_num_addr_tr[ifp->if_unit]];
		 btp > atp; btp--) {	/* open up space for a new entry */
		btp->ip_addr = (btp - 1)->ip_addr;
		bcopy((btp - 1)->x25_addr, btp->x25_addr, MAXADDRLEN);
	    }
	    atp->ip_addr = tr->ipaddr;
	    bcopy(tr->x25addr, atp->x25_addr, MAXADDRLEN);
	    dda_num_addr_tr[ifp->if_unit]++;	/* one more table entry */
	    goto exit;

	  case 'D':
	    if (error = diags_completed(ds))
		goto exit;
	    /* clear table for use by 'r' flag of acpconfig */
	    for (i = 0, atp = &dda_addr_tr[ifp->if_unit][0];
		 i < dda_num_addr_tr[ifp->if_unit]; i++, atp++) {
		atp->ip_addr = 0L;
		atp->x25_addr[0] = 0;
	    }
	    dda_num_addr_tr[ifp->if_unit] = 0;
	    goto exit;

	  case 'd':		/* Delete address translation table entry */
	    if (error = diags_completed(ds))
		goto exit;
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct trtab))) {
		error = EFAULT;
		goto exit;
	    }
	    tr = (struct trtab *) dda_iobuf;
	    error = EFAULT;	/* in case inet address not in table */
	    for (i = 0, atp = &dda_addr_tr[ifp->if_unit][0];
		 i < dda_num_addr_tr[ifp->if_unit]; i++, atp++) {
		if (atp->ip_addr == tr->ipaddr) {
		    error = 0;	/* found it: cancel error */
		    for (; i < dda_num_addr_tr[ifp->if_unit] - 1; i++, atp++) {
			atp->ip_addr = (atp + 1)->ip_addr;
			bcopy((atp + 1)->x25_addr, atp->x25_addr, MAXADDRLEN);
		    }
		    atp->ip_addr = 0L;	/* clear last vacated entry */
		    atp->x25_addr[0] = 0;
		    dda_num_addr_tr[ifp->if_unit]--;	/* one fewer table
							 * entries */
		    break;
		}
	    }
	    goto exit;


	  case 'f':		/* -f facility status */

	    /*
	     * The first byte of the "msg" selects the flow control parameter
	     * and the "drval" field holds the status (on or off). 
	     */
	    if (error = diags_completed(ds))
		goto exit;
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    if (ds->dda_firmrev < 0x21) {	/* need 2.0 or above ROMs */
		error = ENOPROTOOPT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    switch (da->msg[0]) {
	      case 0:		/* packet */
		if (da->drval)
		    ds->dda_init |= DDA_PKTNEG;
		else
		    ds->dda_init &= ~DDA_PKTNEG;
		break;
	      case 1:		/* window */
		if (da->drval)
		    ds->dda_init |= DDA_WNDNEG;
		else
		    ds->dda_init &= ~DDA_WNDNEG;
		break;
	    }
	    goto exit;

	  case 'o':		/* Set options */
	    if (error = diags_completed(ds))
		goto exit;
	    if (ds->dda_firmrev < 0x21) {	/* need 2.1 or above ROMs */
		error = ENOPROTOOPT;
		goto exit;
	    }
	    if (ds->dda_state != S_DISABLED) {	/* must bring link down */
		error = EINPROGRESS;
		goto exit;
	    }
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    acpconfig_msg[MSG_OFFSET] = PKT_OPTIONS;
	    acpconfig_msg[MSG_OFFSET + 1] = da->msg[0];
	    acpconfig_msg[MSG_LENGTH] = 2;
#ifdef DDADEBUG
	    if (DDADBCH(4, ifp->if_unit)) {
		DDALOG(LOG_DEBUG) "dda%d: acpconfig_msg is %x %x %x\n",
		    ifp->if_unit, acpconfig_msg[MSG_LENGTH],
		    acpconfig_msg[MSG_OFFSET], acpconfig_msg[MSG_OFFSET + 1] DDAELOG;
	    }
#endif DDADEBUG

	    send_config(ds, acpconfig_msg);
	    goto exit;

	  case 'N':		/* read network id */
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    ds->dda_net_id = da->drval;
	    goto exit;

	  case 'r':		/* Read address translation table entry */

	    /*
	     * The value stored in "ipaddr" is not an address, but an index
	     * of a translation table entry to read out.  The x25_addr field
	     * in the input structure is not used. 
	     */
	    if (error = diags_completed(ds))
		goto exit;
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct trtab))) {
		error = EFAULT;
		goto exit;
	    }
	    tr = (struct trtab *) dda_iobuf;
	    i = tr->ipaddr;
	    if (i >= DDANATT) {	/* scanned the whole table */
		error = EFAULT;
		goto exit;
	    }
	    tr->ipaddr = dda_addr_tr[ifp->if_unit][i].ip_addr;
	    bcopy(dda_addr_tr[ifp->if_unit][i].x25_addr, tr->x25addr, MAXADDRLEN);
	    if (copyout(tr, ifr->ifr_data, sizeof(struct trtab)))
		error = EFAULT;
	    goto exit;

#ifdef DDA_HISTOGRAM
	  case 'h':		/* read histogram */
	    if (error = diags_completed(ds))
		goto exit;
	    hist_read(ifp->if_unit);
	    if (hist_copyout(ifp->if_unit, ifr->ifr_data))
		error = EFAULT;
	    goto exit;

	  case 'H':		/* read and reset histogram */
	    if (error = diags_completed(ds))
		goto exit;
	    hist_read(ifp->if_unit);
	    if (hist_copyout(ifp->if_unit, ifr->ifr_data))
		error = EFAULT;
	    else
		hist_init(ifp->if_unit, 1);
	    goto exit;
#endif DDA_HISTOGRAM

	  case 'v':		/* -v variable value */

	    /*
	     * There are two "variables" in the driver which can be set via
	     * ioctl: packet size, and window size.  The "drval" field holds
	     * the value and the first byte of the "msg" selects the variable.
	     * Note that the selector is another little undocumented piece of
	     * the interface between here and the acpconfig program. It is
	     * coupled to the ordering of a little string table inside that
	     * program; new parameters should be added at the end, not the
	     * middle! 
	     */
	    /* No check to see if powerup diags are completed */
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    switch (da->msg[0]) {
	      case 0:		/* set logging (obsolete) */
	      case 1:		/* set debug (obsolete) */
	      case 2:		/* set debug unit (obsolete) */
		error = EINVAL;
		break;

		/*
		 * For both packet and window sizes, we check that the link
		 * is currently down.  The new parameters will be sent to the
		 * FEP when the link is next brought up.  See processing for
		 * -u flag. 
		 */
	      case 3:		/* set packetsize */
		if (error = diags_completed(ds))
		    goto exit;
		if (ds->dda_firmrev < 0x21) {	/* need 2.1 or above ROMs */
		    error = ENOPROTOOPT;
		    goto exit;
		}
		if (ds->dda_state != S_DISABLED) {	/* must bring link down */
		    error = EINPROGRESS;
		    goto exit;
		}

		/*
		 * X.25 (1984) section 7.2.2.1.1 says 12 (4096 byte packets)
		 * BBN report 5760 (September 1984) 14.2.1.2 says 10. We just
		 * check for 12. 
		 */
		if (da->drval < 4 || da->drval > 12)
		    error = EINVAL;
		else {
		    int             packetsize = 1 << da->drval;

		    acpconfig_msg[MSG_LENGTH] = 3;
		    acpconfig_msg[MSG_OFFSET] = MAX_PKT_SZ;	/* Max negotiable */
		    /* pkt size */
		    acpconfig_msg[MSG_OFFSET + 1] = packetsize & 0xFF;
		    acpconfig_msg[MSG_OFFSET + 2] = (packetsize >> 8) & 0xFF;
		    send_config(ds, acpconfig_msg);
		}
		break;

	      case 4:		/* set windowsize */
		if (error = diags_completed(ds))
		    goto exit;
		if (ds->dda_firmrev < 0x21) {	/* need 2.0 or above ROMs */
		    error = ENOPROTOOPT;
		    goto exit;
		}
		if (ds->dda_state != S_DISABLED) {	/* must bring link down */
		    error = EINPROGRESS;
		    goto exit;
		}
		if (da->drval < 1 || da->drval > 127)
		    error = EINVAL;
		else {
		    acpconfig_msg[MSG_LENGTH] = 2;
		    acpconfig_msg[MSG_OFFSET] = MAX_PKT_WN;	/* Max negotiable */
		    /* pkt window */
		    acpconfig_msg[MSG_OFFSET + 1] = da->drval;
		    send_config(ds, acpconfig_msg);
		}
		break;
	    }
	    goto exit;

	  case 'm':		/* -m message */
	    if (error = diags_completed(ds))
		goto exit;
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    send_config(ds, da->msg);
	    goto exit;

	  case 'n':		/* -n svc_count */
	    if (error = diags_completed(ds))
		goto exit;
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    if (ds->dda_firmrev < 0x21) {	/* need 2.1 or above ROMs */
		error = ENOPROTOOPT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    i = 0;		/* i holds the return value */
	    if (da->drval == 0)
		i = nddach[ifp->if_unit];
	    else if (ds->dda_state != S_DISABLED) {	/* must bring link down */
		error = EINPROGRESS;
		goto exit;
	    } else {
		if (!suser()) {
		    error = EACCES;
		    goto exit;
		}
		if (da->drval < 1 || da->drval > NDDACH)
		    error = EINVAL;
		else {
		    acpconfig_msg[MSG_LENGTH] = 2;
		    acpconfig_msg[MSG_OFFSET] = SVC_LIMIT;
		    acpconfig_msg[MSG_OFFSET + 1] = da->drval;
		    nddach[ifp->if_unit] = da->drval;
		    send_config(ds, acpconfig_msg);
		}
	    }
	    if (copyout(&i, ifr->ifr_data, sizeof(int)))
		error = EFAULT;
	    goto exit;

	  case 'c':		/* -c msgnum  -- dis/enable driver mesg */
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    if (da->drval < 0 || da->drval >= MAXDMSGS)
		error = EINVAL;
	    else {
		u_char          new_val;

		DMESGTOG(ifp->if_unit, da->drval);
		new_val = DMESGVAL(ifp->if_unit, da->drval) ? 1 : 0;
		/* 1 means disabled, 0 means enabled */
		if (copyout(&new_val, ifr->ifr_data, sizeof(u_char)))
		    error = EFAULT;
	    }
	    goto exit;

	  case 't':		/* -t sec  -- set data idle timeout */
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    if (da->drval < 1)
		error = EINVAL;
	    else
		tmo_data_idle = da->drval / DDA_TIMEOUT;
	    goto exit;

	  case 'q':		/* driver/FE/shm/silo state queries */
	    if (copyin(ifr->ifr_data, dda_iobuf, sizeof(struct ddactl))) {
		error = EFAULT;
		goto exit;
	    }
	    da = (struct ddactl *) dda_iobuf;
	    switch (da->msg[0]) {
	    case 0:		/* front end state query */
		if ((error = diags_completed(ds)) == 0) {
		    int s2 = splimp();

		    /* need 2.0 or above ROMs */
		    if (ds->dda_firmrev < 0x21) {
			error = ENOPROTOOPT;
			splx(s2);	/* We got it and woke up */
			break;
		    }
#ifdef	MULTINET
		    StatQuery_Completed = 0;
		    send_supr(ds, STATQUERY, 0, 0);
		    splx(s2);	/* drop ioctl so we can be scheduled */
		    while (!StatQuery_Completed);
#else	MULTINET
		    send_supr(ds, STATQUERY, 0, 0);
		    sleep(dda_iobuf, PSLEP); /* Interruptible with ^C */
		    splx(s2);	/* We got it and woke up */
#endif	MULTINET

		    if (copyout(dda_iobuf, ifr->ifr_data,
			     sizeof(struct ddactl)))
			error = EFAULT;
		}
		break;
	    case 1:		/* driver state query */
		da->msg[0] = ds->dda_state;
		da->msg[1] = ds->dda_init;
		da->msg[2] = ds->dda_flags;
		da->msg[3] = ds->dda_firmrev;
		if (copyout(dda_iobuf, ifr->ifr_data,
			     sizeof(struct ddactl)))
		    error = EFAULT;
		break;
#ifdef DDADEBUG
	    case 2:		/* debug query */
		if (copyout(dda_debug_silo, ifr->ifr_data, 256))
		    error = EFAULT;
		break;
#endif
#if defined(DDADEBUG) && defined(ACP_BI)
	    case 3:		/* shm/biic query (temporary) */
		{
		    register struct uba_device *ui = ddainfo[ifp->if_unit];
		    dda_dump_shm((SYSGEN_BLOCK *) ds->dda_mapreg);
		    dda_dump_biic_regs((struct biic_regs *) ui->ui_addr);
                }
		break;
#endif
	    default:
		error = EINVAL;
	    }
	    goto exit;

	  case '0':		/* -u 0 */
	    if (error = diags_completed(ds))
		goto exit;
	    acpconfig_msg[MSG_OFFSET] = LINK_DISABLE;
	    acpconfig_msg[MSG_LENGTH] = 1;
	    hist_link_state(ifp->if_unit, ds->dda_state, S_GOING_DOWN);
	    ds->dda_state = S_GOING_DOWN;
	    break;
	  case '1':		/* -u 1 */
	    if (error = diags_completed(ds))
		goto exit;
	    acpconfig_msg[MSG_OFFSET] = LINK_LOOPBACK;
	    acpconfig_msg[MSG_OFFSET + 1] = LOOP_NONE;
	    acpconfig_msg[MSG_OFFSET + 2] = DTE_DCE_MODE;
	    acpconfig_msg[MSG_OFFSET + 3] = DTE;
	    acpconfig_msg[MSG_OFFSET + 4] = LINK_ENABLE;
	    acpconfig_msg[MSG_LENGTH] = 5;
	    ds->dda_state = S_COMING_UP;
	    hist_init(ifp->if_unit, 0);
	    break;
	  case '2':		/* -u 2 */
	    if (error = diags_completed(ds))
		goto exit;
	    acpconfig_msg[MSG_OFFSET] = LINK_LOOPBACK;
	    acpconfig_msg[MSG_OFFSET + 1] = LOOP_NONE;
	    acpconfig_msg[MSG_OFFSET + 2] = DTE_DCE_MODE;
	    acpconfig_msg[MSG_OFFSET + 3] = DCE;
	    acpconfig_msg[MSG_OFFSET + 4] = LINK_ENABLE;
	    acpconfig_msg[MSG_LENGTH] = 5;
	    ds->dda_state = S_COMING_UP;
	    hist_init(ifp->if_unit, 0);
	    break;
	  case '3':		/* -u 3 */
	    if (error = diags_completed(ds))
		goto exit;
	    acpconfig_msg[MSG_OFFSET] = LINK_LOOPBACK;
	    acpconfig_msg[MSG_OFFSET + 1] = LOOP_EXTERNAL;
	    acpconfig_msg[MSG_OFFSET + 2] = LINK_ENABLE;
	    acpconfig_msg[MSG_LENGTH] = 3;
	    ds->dda_state = S_COMING_UP;
	    hist_init(ifp->if_unit, 0);
	    break;
	  case '4':		/* -u 4 */
	    if (error = diags_completed(ds))
		goto exit;
	    acpconfig_msg[MSG_OFFSET] = LINK_LOOPBACK;
	    acpconfig_msg[MSG_OFFSET + 1] = LOOP_INTERNAL;
	    acpconfig_msg[MSG_OFFSET + 2] = LINK_ENABLE;
	    acpconfig_msg[MSG_LENGTH] = 3;
	    ds->dda_state = S_COMING_UP;
	    hist_init(ifp->if_unit, 0);
	    break;
	  case 'b':		/* -b 0 */
	    if (error = diags_completed(ds))
		goto exit;
	    acpconfig_msg[MSG_OFFSET] = CLOCK_CNTL;
	    acpconfig_msg[MSG_OFFSET + 1] = EXTERNAL_CLOCK;
	    acpconfig_msg[MSG_LENGTH] = 2;
	    ds->dda_init &= ~DDA_INTCLOCK;
	    break;
	  case 'S':		/* select DDN standard X.25 service */
	    /* -s 0 or -s standard */
	    if (error = diags_completed(ds))
		goto exit;
	    if (ds->dda_if.if_flags & IFF_UP && ds->dda_init & DDA_PDN) {
		error = EALREADY;
		goto exit;	/* no PDN->DDN mode change if running */
	    }
	    ds->dda_init &= ~(DDA_BASIC | DDA_PDN);
	    ds->dda_init |= DDA_STANDARD;
	    goto exit;
	  case 'T':		/* select DDN basic X.25 service */
	    /* -s 1 or -s basic */
	    if (error = diags_completed(ds))
		goto exit;
	    if (ds->dda_if.if_flags & IFF_UP && ds->dda_init & DDA_PDN) {
		error = EALREADY;
		goto exit;	/* no PDN->DDN mode change if running */
	    }
	    ds->dda_init &= ~(DDA_PDN | DDA_STANDARD);
	    ds->dda_init |= DDA_BASIC;
	    goto exit;
	  case 'U':		/* select X.25 Public Data Network service */
	    /* -s 2 or -s pdn */
	    if (error = diags_completed(ds))
		goto exit;
	    if (ds->dda_if.if_flags & IFF_UP && (ds->dda_init &
					      (DDA_BASIC | DDA_STANDARD))) {
		error = EALREADY;
		goto exit;	/* no DDN->PDN mode change if running */
	    }
	    ds->dda_init &= ~(DDA_BASIC | DDA_STANDARD);
	    ds->dda_init |= DDA_PDN;
	    goto exit;

	  case 'e':		/* set buffer size */
	    /* -e size size is encoded in second byte */
	    if (error = diags_completed(ds))
		goto exit;

	    /*
	     * check to see if we have newer at least version 2.2 roms. 
	     */
	    if (ds->dda_firmrev < 0x22) {
		error = ENOPROTOOPT;
		goto exit;
	    }
	    if (ds->dda_if.if_flags & IFF_UP) {
		error = EALREADY;
		goto exit;	/* no PDN->DDN mode change if running */
	    }
	    bfr_size_msg[MSG_OFFSET] = ifr->ifr_data[1];
	    send_config(ds, bfr_size_msg);
	    bufreset(ifp->if_unit);
	    goto exit;

#ifdef	ACP_BI
	  case 'L':
	    {	struct dda_dnload dl;
		if (copyin(ifr->ifr_data, &dl, sizeof(struct dda_dnload))) {
		    error = EFAULT;
		    goto exit;
		}
		error = dda_dload(ifp->if_unit, &dl);
		goto exit;
	    }
#endif

#if defined(DDA_PADOPT) && defined(WINS)
	  case 'x':		/* Preallocate UCBs for X29 -- VMS only */
	    printf("Preallocated %d PTYs for X29\n", prealloc_x29());
	    goto exit;
#endif

	  case 'z':		/* reset specified front-end device, -z */
/* second parm is supposed to be uban, but ddareset doesn't care about it */
	    ddareset(ifp->if_unit, 0);
	    goto exit;
	  default:
	    error = EINVAL;
	    goto exit;
	}
	if ((*(ifr->ifr_data) != '0') && (ds->dda_init & DDA_PDN) &&
#if ACC_BSD == 42 || ACC_ULTRIX == 12
	    (convert_ip_addr(sin->sin_addr, (u_char *) arg2, ds) == 0)
#else
	    (convert_ip_addr(ds->dda_ipaddr, (u_char *) arg2, ds) == 0)
#endif
	    ) {
	    error = EADDRNOTAVAIL;
	    goto exit;
	}
	send_config(ds, acpconfig_msg);
	break;

      default:
	error = EINVAL;
    }

exit:
#ifndef	MULTINET
    splx(s);
#endif
    return (error);
}


/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      DDAOUTPUT()                            %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*   This routine is called by the network software when it has an */
/*   IP datagram to send out this interface.  An attempt is made   */
/*   to find a LCN which has a virtual circuit open to the         */
/*   indicated host.  If an LCN is found the packet is queued for  */
/*   output on that LCN.                                           */
/*                                                                 */
/*  Call:            ddaoutput(ifp, m0, dst)                       */
/*  Arguments:       ifp:  locates the network interface, ifnet    */
/*                   m0:   locates an mbuf buffer                  */
/*                   dst:  is the socket destination address       */
/*  Returns:         0 for success, or one of following nonzero    */
/*                        error indications:                       */
/*                               ENETDOWN                          */
/*                               EAFNOSUPPORT                      */
/*                               ENOBUFS                           */
/*  Called by:     network software, address of this routine is    */
/*                 defined in the dda_if network interface struct  */
/*  Calls to:      DDALOG()                                        */
/*                 m_freem()                                       */
/*                 splimp()                                        */
/*                 locate_x25_lcn()                                */
/*                 IF_QFULL()                                      */
/*                 IF_DROP()                                       */
/*                 splx()                                          */
/*                 IF_ENQUEUE()                                    */
/*                 dda_start()                                     */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

ddaoutput(ifp, m0, dst)
struct ifnet   *ifp;
struct mbuf    *m0;
struct sockaddr_in *dst;
{
    register struct mbuf *m = m0;
    register struct dda_softc *ds = &dda_softc[ifp->if_unit];
    register struct dda_cb *dc;
    register struct ifqueue *oq;
    struct mbuf    *prev;
    int             s;
    union imp_addr  imp_addr;

#ifdef DDADEBUG
    if (DDADBCH(1, ifp->if_unit)) {
	imp_addr.ip = dst->sin_addr;
	DDALOG(LOG_DEBUG) "dda%d: ddaoutput: dst = %d.%d.%d.%d\n",
	    ifp->if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
	    imp_addr.imp.s_lh, imp_addr.imp.s_impno DDAELOG;
    }
#endif DDADEBUG

    if ((ds->dda_if.if_flags & IFF_UP) == 0)
	return (ENETDOWN);

    switch (dst->sin_family) {

#ifdef INET
      case AF_INET:
	break;
#endif INET

      default:
	DMESG(ifp->if_unit, 2,
	      (DDALOG(LOG_ERR) "dda%d: can't handle af%d\n", ifp->if_unit,
	       dst->sin_family DDAELOG));
	m_freem(m0);
	return (EAFNOSUPPORT);
    }

#ifdef DDADEBUG
    if (DDADBCH(2, ifp->if_unit)) {
	imp_addr.ip = dst->sin_addr;
	DDALOG(LOG_DEBUG) "dda%d: ddaoutput: dst = %d.%d.%d.%d\n",
	    ifp->if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
	    imp_addr.imp.s_lh, imp_addr.imp.s_impno DDAELOG;
    }
#endif DDADEBUG

    /* Mod to V1.5b ==> V1.5b1 */
    /* In 4.3, the IP code may pass mbuf chains with 0-length mbufs */
    /* This causes "transfer count = 0" messages and might even     */
    /* cause actual garbage data transmission if the mbuf is at the */
    /* end of the chain (we don't think it ever will be, but one    */
    /* can't be too sure...so we scan the chain first).		    */
    /* WE DO ASSUME that there is at least one nonempty mbuf!	    */
    /* (ULTRIX: we don't know, but the code is at worst harmless)   */

    while (m0->m_len == 0) {
	m = m0;
	m0 = m0->m_next;
	m->m_next = 0;
	m_freem(m);
    }
    /* Now we know the first mbuf (at m0)  is not zero length	    */
    prev = m0;
    m = m0->m_next;
    while (m) {
	if (m->m_len == 0) {
	    prev->m_next = m->m_next;
	    m->m_next = 0;
	    m_freem(m);
	    m = prev->m_next;
	} else {
	    prev = m;
	    m = m->m_next;
	}
    }
    m = m0;			/* reset m to beginning of modified chain */

    s = splimp();		/* disable interrrupts */

    /* try to find an LCN */

    if (dc = locate_x25_lcn(ds, dst->sin_addr)) {	/* if found */
#ifdef DDADEBUG
	if (DDADBCH(2, ifp->if_unit)) {
	    DDALOG(LOG_DEBUG) "dda%d: ddaoutput: lcn found = %d\n", ifp->if_unit,
		dc->dc_lcn DDAELOG;
	}
#endif DDADEBUG
	oq = &(dc->dc_oq);	/* point to output queue */
	if (IF_QFULL(oq)) {	/* if q full */
	    IF_DROP(oq);	/* drop the data */
	    m_freem(m);
	    ds->dda_if.if_collisions++;	/* for netstat display */
	    splx(s);
	    return (ENOBUFS);
	}
	IF_ENQUEUE(oq, m);	/* otherwise queue it */
	dda_start(ds, dc);	/* and try to output */
	splx(s);
	return (0);
    } else {			/* if no circuit available */
	IF_DROP(&ifp->if_snd);	/* drop the data */
	m_freem(m);
	splx(s);
	return (EHOSTUNREACH);
    }

}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      DDATIMER()                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*  This routine is entered to perform timer management. The       */
/*  LCN table is scanned for active timers (nonzero) which are     */
/*  decremented.  If a timer expires (becomes zero), the proper    */
/*  action is taken.                                               */
/*                                                                 */
/*                                                                 */
/*  Call:              ddatimer(unit)                              */
/*  Arguments:         unit:  ACP device unit number               */
/*  Returns:           nothing                                     */
/*  Called by:         ddainit()                                   */
/*  Calls to:          splimp()                                    */
/*                     send_restart()                              */
/*                     clear_lcn()                                 */
/*                     splx()                                      */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

int
ddatimer(unit)
int             unit;
{
    register struct dda_softc *ds = &dda_softc[unit];
    register struct dda_cb *dc;
    register int    s, lcn;

#ifdef DDADEBUG
    if (DDADBCH(3, unit)) {
	DDALOG(LOG_DEBUG) "dda%d: ddatimer()\n", unit DDAELOG;
    }
#endif DDADEBUG

    ds->dda_if.if_timer = DDA_TIMEOUT;	/* restart timer */

    dc = ds->dda_cb;

    s = splimp();

    /* LCNLINK */
    for (lcn = 0; lcn <= nddach[unit]; lcn++) {	/* scan all LCN's */
#ifdef DDADEBUG
	if (dc->dc_out_t && lcn > 0 && (--(dc->dc_out_t) == 0)) {
	    DDALOG(LOG_DEBUG) "dda%d: write completion timeout lcn %d\n",
		unit, lcn DDAELOG;
	}
#endif
	if (dc->dc_timer && (--(dc->dc_timer) == 0)) {	/* if a timer expired */
	    if (dc->dc_state == LC_RESTART) {	/* if a restart was out */
		send_restart(ds);	/* send another one */
		break;
	    } else {		/* otherwise */
#ifdef DDA_PAD_OR_RAW
		/* if it is not an X.29 connection */
		if ((dc->dc_flags & (DC_X29W | DC_X29 | DC_RAW)) == 0)
#endif
		    clear_lcn(ds, dc);	/* clear the LCN */
	    }
	}
	dc++;
    }
    splx(s);
}


/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                     DIAGS_COMPLETED()                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine checks to see that power up diagnostics have completed*/
/*    It waits for a while if necessary.                                 */
/*  Call:          diags_completed(ds)                                   */
/*  Argument:      ds - pointer to softc structure;                      */
/*  Returns:       0 if board is up, EINTR if it never came on line.     */
/*  Called by:     ddaioctl()                                            */
/*  Calls to:                                                            */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

diags_completed(ds)
struct dda_softc *ds;
{
    int             nretries = 10;

    /*
     * It's overkill to check this on EVERY ioctl, because it only matters if
     * we are going to touch the board.  But the driver has had repeated
     * problems with not checking it when it should have - overkill is
     * preferred.  The delays are here rather then in the acpconfig program
     * due to a bug in acpconfig.  They will only be executed during
     * /etc/rc.local when the board has not had a chance to do the "B"
     * interrupt yet.  At that time the machine will be running essentially
     * single thread so it won't really matter where the delays are.  (ie, if
     * we put the delay into acpconfig and kept calling here 10 times, the
     * machine would not do anything else useful in the meantime - might as
     * well loop here). 
     */
    while (((ds->dda_flags & DDAF_OK) == 0) && nretries-- > 0)
	DELAY(3000000);
    if ((ds->dda_flags & DDAF_OK) == 0)	/* never came on line... */
	return (EINTR);
    else
	return (0);
}



/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                                                             %%*/
/*%%                   LOCAL  FUNCTIONS                          %%*/
/*%%                                                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      SEND_CONFIG()                          %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*   Send a Set System Parameters Message to the front end in      */
/*   response to an ACPCONFIG command from the user.               */
/*                                                                 */
/*  Call:          send_config(ds, p)                              */
/*  Argument:      ds:  pointer to ACP device control structure    */
/*		   p: pointer to the message			   */
/*  Returns:           nothing                                     */
/*  Called by:         ddaioctl()                                  */
/*  Calls to:          MGET()                                      */
/*                     DDALOG()                                    */
/*                     mtod()                                      */
/*                     bcopy()                                     */
/*                     sizeof()                                    */
/*                     start_supr()                                 */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
send_config(ds, p)
struct dda_softc *ds;
u_char		 *p;
{
    struct mbuf    *m;
    register u_char *bp;
    int             length;

#ifdef DDADEBUG
    if (DDADBCH(7, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: send_config()\n", ds->dda_if.if_unit DDAELOG;
    }
#endif DDADEBUG

    MGET(m, M_DONTWAIT, MT_DATA);
    if (m == 0) {
	DMESG(ds->dda_if.if_unit, 18,
	      (DDALOG(LOG_ERR) "dda%d: can't get bfr for acpconfig msg\n",
	       ds->dda_if.if_unit DDAELOG));
	return;
    }
    bp = mtod(m, u_char *);	/* point to data section of mbuf */

    length = p[MSG_LENGTH] + MSG_OFFSET;	/* msg length */
    if (length > MLEN - 1) {

	/*
	 * Supervisory messages have to fit in a small mbuf.  The driver
	 * itself is careful never to get in trouble this way, but now that
	 * we have "-m" the user could.  Dropping such a message is not
	 * likely to cause any big problems, and the user can rephrase the
	 * request. 
	 */
	DMESG(ds->dda_if.if_unit, 19,
	      (DDALOG(LOG_ERR) "dda%d: supervisor message too long\n",
	       ds->dda_if.if_unit DDAELOG));
	m->m_next = 0;
	m_freem(m);
	return;
    }
    bcopy(p, bp, length);
    m->m_len = length;		/* set msg length */

#ifdef DDADEBUG
    if (DDADBCH(8, ds->dda_if.if_unit)) {
	prt_bytes(ds->dda_if.if_unit, "send_config", bp, length);
    }
#endif DDADEBUG

    start_supr(ds, m);
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      LOCATE_X25_LCN()                       %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine tries to locate an X25 LCN associated with a    */
/*    remote internet address.  A linear search of the LCN table   */
/*    is made for a matching address.  If the search succeeds, the */
/*    LCN is returned.  If the search fails, the LCN table is      */
/*    searched for an unused table entry.  If an unused table      */
/*    entry is found, an X25 call is generated to the host         */
/*    specified in the destination internet address.  If no LCN is */
/*    available, zero is returned.                                 */
/*                                                                 */
/*  Call:              locate_x25_lcn(ds, ip_addr)                 */
/*  Argument:          ds:   pointer to dev control block struct   */
/*                     ip_addr:  IP address                        */
/*  Returns:           pointer to the dda control block which      */
/*                     contains LCN, else zero for failure         */
/*  Called by:         ddaoutput()                                 */
/*  Calls to:          convert_ip_addr()                           */
/*                     make_x25_call()                             */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE struct dda_cb *
locate_x25_lcn(ds, ip_addr)
struct dda_softc *ds;
struct in_addr  ip_addr;
{
    register int    lcn, maxlcn;
    register struct dda_cb *dc;
    union imp_addr  imp_addr;

#ifdef DDADEBUG
    if (DDADBCH(9, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: locate_x25_lcn()\n",
	    ds->dda_if.if_unit DDAELOG;
    }
#endif DDADEBUG

    imp_addr.ip = ip_addr;	/* DDN X.25 doesn't know net number */

    if (!(ds->dda_init & DDA_PDN)) {
	if ((imp_addr.imp.s_net & 0x80) == 0x00) {	/* class A */
	    imp_addr.imp.s_net = 0;
	    imp_addr.imp.s_lh = 0;
	} else if ((imp_addr.imp.s_net & 0xc0) == 0x80) {	/* class B */
	    imp_addr.imp.s_net = 0;
	    imp_addr.imp.s_host = 0;
	} else {		/* class C, should check for class C */
	    imp_addr.imp.s_net = 0;
	    imp_addr.imp.s_host = 0;
	    imp_addr.imp.s_lh = 0;
	}
    }
    /* LCNLINK */
    maxlcn = nddach[ds->dda_if.if_unit];
    dc = &(ds->dda_cb[1]);	/* scan LCN table for addr match */
    for (lcn = 1; lcn <= maxlcn; lcn++) {
	if ((dc->dc_key.key_addr.s_addr == imp_addr.ip.s_addr)
	&& (dc->dc_state == LC_CALL_PENDING || dc->dc_state == LC_DATA_IDLE))
	    return (dc);	/* return LCN */
	dc++;
    }

    if ((dc = find_free_lcn(ds)) == 0)
	return (0);

    ddacb_user_data[0] = (u_char) 0;		/* we have no user data */

    if (convert_ip_addr(ip_addr, ddacb_called_addr, ds)
	&& make_x25_call(ds, dc, ip_addr, X25_PROTO_IP)) {
						/* addr can be converted */
	dc->dc_inaddr = ip_addr;		/* store dest ip addr */
	dc->dc_key.key_addr.s_addr = imp_addr.ip.s_addr;
						/* store match key */
#ifdef DDADEBUG
	if (DDADBCH(9, ds->dda_if.if_unit)) {
	    imp_addr.ip = ip_addr;
	    DDALOG(LOG_DEBUG)
		"dda%d: locate_x25_lcn: made call to %d.%d.%d.%d\n",
		ds->dda_if.if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
		imp_addr.imp.s_lh, imp_addr.imp.s_impno DDAELOG;

	}
#endif DDADEBUG
	return (dc);		/* and return the LCN */
    } else {
	return (0);		/* give up */
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      FIND_FREE_LCN()                        %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine tries to locate a free X25 LCN.                 */
/*    The LCN table is searched for an unused entry.               */
/*    If no LCN is available, zero is returned.                    */
/*                                                                 */
/*  Call:              find_free_lcn(ds)                           */
/*  Argument:          ds:   pointer to dev control block struct   */
/*  Returns:           pointer to the dda control block which      */
/*                     contains LCN, else zero for failure         */
/*  Called by:         locate_x25_lcn()                            */
/*		       supr_msg()				   */
/*		       xxcntl()					   */
/*  Calls to:          DDALOG()                                    */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE struct dda_cb *
find_free_lcn(ds)
struct dda_softc *ds;
{
    struct dda_cb  *dc;
    register int    lcn, maxlcn, dwnflg = 0;

    /* LCNLINK */
    dc = &(ds->dda_cb[1]);	/* scan LCN table for free entry */
    maxlcn = nddach[ds->dda_if.if_unit];
    for (lcn = 1; lcn <= maxlcn; lcn++) {
#ifdef DDA_PAD_OR_RAW
	if (dc->dc_state == LC_IDLE && (dc->dc_flags & (DC_X29W | DC_X29 | DC_RAW)) == 0)
#else
	if (dc->dc_state == LC_IDLE)
#endif DDA_PAD_OR_RAW
	    break;
	else if (dc->dc_state == LC_RESTART || dc->dc_state == LC_DOWN)
	    dwnflg = 1;
	dc++;
    }

    /* LCNLINK */
    if (lcn > maxlcn) {		/* if we didn't find a free entry */
	if (LOG_BUSY) {
	    if (dwnflg)
		DDALOG(LOG_ERR) "dda%d: no circuits available (link not up)\n",
		    ds->dda_if.if_unit DDAELOG;
	    else
		DDALOG(LOG_ERR) "dda%d: all circuits in use\n",
		    ds->dda_if.if_unit DDAELOG;
	}
	return (0);		/* return empty handed */
    }
    return (dc);
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      CONVERT_IP_ADDR()                      %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    Based on the type of X.25 service, this routine performs     */
/*    one of two functions.  For PDN X.25 service, the address     */
/*    translation table is searched for presence of local X.25     */
/*    address (which was entered by the user via acpconfig).       */
/*                                                                 */
/*    For DDN X.25 service, this routine accepts an internet       */
/*    address and attempts to translate to an equivalent X25       */
/*    address.  This follows the guidelines in the DDN X25         */
/*    interface spec.  The resultant X25 address is stored in the  */
/*    X25 called addr buffer.                                      */
/*                                                                 */
/*    NOTE: Although front end was designed to accept ASCII coded  */
/*    digits for the address fields, we only supply the binary     */
/*    values.  The front end only uses low four bits to extract    */
/*    the binary value from the ASCII digits, so this works out.   */
/*                                                                 */
/*                                                                 */
/*                                                                 */
/*                                                                 */
/*                                                                 */
/*                                                                 */
/*  Call:              convert_ip_addr(ip_addr, x25addr, ds)       */
/*  Argument:          ip_addr:  IP address                        */
/*                     x25addr:  X.25 address                      */
/*                     ds:       &dda_softc[unit]                  */
/*  Returns:           1 for success                               */
/*  Called by:         locate_x25_lcn()                            */
/*                     make_x25_call()                             */
/*                     ddaioctl()                                  */
/*  Calls to:          bcopy()                                     */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE boolean
convert_ip_addr(ip_addr, x25addr, ds)
struct in_addr  ip_addr;
u_char          x25addr[];
struct dda_softc *ds;

{
    register struct dda_addr_tr *atp, *hip, *lop;
    register int    temp;
    union imp_addr  imp_addr;

/****************************************************************/
/* processing for Public Data Network (PDN) X.25 service        */
/* search address translation table for local address           */
/****************************************************************/

    if (ds->dda_init & DDA_PDN) {
	x25addr[0] = 0;		/* clear result X.25 address length */
	lop = &dda_addr_tr[ds->dda_if.if_unit][0];	/* set up for binary
							 * search */
	hip = &dda_addr_tr[ds->dda_if.if_unit][dda_num_addr_tr[ds->dda_if.if_unit]];
	while (lop < hip - 1) {	/* binary search loop */
	    atp = lop + (hip - lop) / 2;
	    if (atp->ip_addr > ip_addr.s_addr)
		hip = atp;
	    else
		lop = atp;
	}
	if (lop->ip_addr == ip_addr.s_addr)
	    bcopy(lop->x25_addr, x25addr, MAXADDRLEN);
    }
/****************************************************************/
/* processing for DDN Standard or Basic X.25 service            */
/* convert IP address to X.25 address                           */
/****************************************************************/

    else {
	int             imp_no, imp_port;


	imp_addr.ip = ip_addr;
	if ((imp_addr.imp.s_net & 0x80) == 0x00) {	/* class A */
	    imp_no = imp_addr.imp.s_impno;
	    imp_port = imp_addr.imp.s_host;
	} else if ((imp_addr.imp.s_net & 0xc0) == 0x80) {	/* class B */
	    imp_no = imp_addr.imp.s_impno;
	    imp_port = imp_addr.imp.s_lh;
	} else {		/* class C */
	    imp_no = imp_addr.imp.s_impno / 32;
	    imp_port = imp_addr.imp.s_impno % 32;
	}

	x25addr[0] = 12;	/* set addr length */
	x25addr[1] = 0;		/* clear DNIC */
	x25addr[2] = 0;
	x25addr[3] = 0;
	x25addr[4] = 0;

	if (imp_port < 64) {	/* Physical:  0000 0 IIIHH00 [SS] *//* s_impno
				 *  -> III, s_host -> HH */
	    x25addr[5] = 0;	/* set flag bit */
	    x25addr[6] = imp_no / 100;
	    x25addr[7] = (imp_no % 100) / 10;
	    x25addr[8] = imp_no % 10;
	    x25addr[9] = imp_port / 10;
	    x25addr[10] = imp_port % 10;
	} else {		/* Logical:   0000 1 RRRRR00 [SS]	 *//* s
				 * _host * 256 + s_impno -> RRRRR */
	    temp = (imp_port << 8) + imp_no;
	    x25addr[5] = 1;
	    x25addr[6] = temp / 10000;
	    x25addr[7] = (temp % 10000) / 1000;
	    x25addr[8] = (temp % 1000) / 100;
	    x25addr[9] = (temp % 100) / 10;
	    x25addr[10] = temp % 10;
	}

	x25addr[11] = 0;	/* clear rest of addr */
	x25addr[12] = 0;
    }

#ifdef DDADEBUG
    if (DDADBCH(11, ds->dda_if.if_unit)) {
	imp_addr.ip = ip_addr;
	DDALOG(LOG_DEBUG) "dda%d: convert_ip_addr: %d.%d.%d.%d ==> %s\n",
	    ds->dda_if.if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
	    imp_addr.imp.s_lh, imp_addr.imp.s_impno,
	    fmt_x25(&x25addr[1], (int) x25addr[0]) DDAELOG;
    }
#endif DDADEBUG

    return (x25addr[0] ? 1 : 0);
}




/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      CONVERT_X25_ADDR()                     %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine accepts an X25 address and attempts to          */
/*    translate to an equivalent internet address.  For DDA this   */
/*    follows the guidelines in the DDA X25 interface spec.  The   */
/*    resultant internet address is returned to the caller.        */
/*                                                                 */
/*  Call:              convert_x25_addr(x25addr, ds)               */
/*  Argument:          x25addr:  X.25 address                      */
/*                     ds:       &dda_softc[unit]                  */
/*                     dc: pointer to allocated dda_cb structure   */
/*  Returns:           IP address for success, else zero for fail  */
/*  Called by:         supr_msg()                                  */
/*  Calls to:          DDALOG()                                    */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE          u_long
convert_x25_addr(x25addr, ds, dc)
u_char          x25addr[];
struct dda_softc *ds;
struct dda_cb  *dc;

{
    register int    cnt, temp;
    union imp_addr  imp_addr;
    register struct dda_addr_tr *atp;

    dc->dc_inaddr.s_addr = imp_addr.ip.s_addr = 0L;
    if (ds->dda_init & DDA_PDN) {
	for (atp = &dda_addr_tr[ds->dda_if.if_unit][0];
	     atp < &dda_addr_tr[ds->dda_if.if_unit][dda_num_addr_tr[ds->dda_if.if_unit]]; atp++) {
	    if (bcmp((char *) atp->x25_addr, (char *) x25addr, x25addr[0] + 1) == 0) {
		/* set key address and print address up */
		dc->dc_inaddr.s_addr = imp_addr.ip.s_addr = atp->ip_addr;
		break;
	    }
	}
    } else {
	int             imp_no, imp_port;
	struct in_addr  my_addr;

	my_addr = ds->dda_ipaddr;
	if (((cnt = x25addr[0]) < MINADDRLEN - 1) || (cnt > MAXADDRLEN - 1)) {
	    DMESG(ds->dda_if.if_unit, 20,
		  (DDALOG(LOG_ERR) "dda%d: illegal X25 address length!\n",
		   ds->dda_if.if_unit DDAELOG));
	    return (0L);
	}
	switch (x25addr[5] & 0x0f) {
	  case 0:		/* Physical:  0000 0 IIIHH00 [SS]	 */
	    imp_no =
		((int) (x25addr[6] & 0x0f) * 100) +
		((int) (x25addr[7] & 0x0f) * 10) +
		((int) (x25addr[8] & 0x0f));


	    imp_port =
		((int) (x25addr[9] & 0x0f) * 10) +
		((int) (x25addr[10] & 0x0f));
	    break;
	  case 1:		/* Logical:   0000 1 RRRRR00 [SS]	 */
	    temp = ((int) (x25addr[6] & 0x0f) * 10000)
		+ ((int) (x25addr[7] & 0x0f) * 1000)
		+ ((int) (x25addr[8] & 0x0f) * 100)
		+ ((int) (x25addr[9] & 0x0f) * 10)
		+ ((int) (x25addr[10] & 0x0f));

	    imp_port = temp >> 8;
	    imp_no = temp & 0xff;
	    break;
	  default:
	    DMESG(ds->dda_if.if_unit, 21,
		  (DDALOG(LOG_ERR) "dda%d: illegal X25 address format!\n",
		   ds->dda_if.if_unit DDAELOG));
	    return (0L);
	}

	dc->dc_inaddr = imp_addr.ip = my_addr;	/* use local net number */
	if ((imp_addr.imp.s_net & 0x80) == 0x00) {	/* class A */
	    imp_addr.imp.s_net = 0;	/* mask net number */
	    imp_addr.imp.s_lh = 0;	/* mask logical host */
	    imp_addr.imp.s_host = imp_port;
	    ((union imp_addr *) & dc->dc_inaddr.s_addr)->imp.s_host = imp_port;
	    imp_addr.imp.s_impno = imp_no;
	    ((union imp_addr *) & dc->dc_inaddr.s_addr)->imp.s_impno = imp_no;
	} else if ((imp_addr.imp.s_net & 0xc0) == 0x80) {	/* class B */
	    imp_addr.imp.s_net = 0;
	    imp_addr.imp.s_lh = imp_port;
	    ((union imp_addr *) & dc->dc_inaddr.s_addr)->imp.s_lh = imp_port;
	    imp_addr.imp.s_host = 0;
	    imp_addr.imp.s_impno = imp_no;
	    ((union imp_addr *) & dc->dc_inaddr.s_addr)->imp.s_impno = imp_no;
	} else {		/* class C */
	    imp_addr.imp.s_impno = (imp_no << 5) + imp_port;
	    ((union imp_addr *) & dc->dc_inaddr.s_addr)->imp.s_impno = imp_addr.imp.s_impno;
	    imp_addr.imp.s_lh = 0;
	    imp_addr.imp.s_host = 0;
	    imp_addr.imp.s_net = 0;
	}
    }

#ifdef DDADEBUG
    if (DDADBCH(12, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: convert_x25_addr: %s ==> %d.%d.%d.%d\n",
	    ds->dda_if.if_unit, fmt_x25(&x25addr[1], (int) x25addr[0]),
	    imp_addr.imp.s_net, imp_addr.imp.s_host, imp_addr.imp.s_lh,
	    imp_addr.imp.s_impno DDAELOG;
    }
#endif DDADEBUG

    return (imp_addr.ip.s_addr);
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      MAKE_X25_CALL()                        %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine places an X25 call using the X25 Call Msg       */
/*    buffer.  The calling LCN is placed in the appropriate state  */
/*    and a timer is started.  Based on dda_init flag, implement   */
/*    DDN standard or basic service.  (If PDN mode is set, then    */
/*    the logic for basic service is followed.)                    */
/*                                                                 */
/*  Call:              make_x25_call(ds, dc, ip_addr, proto	   */
/*				     udlen, ud) 		   */
/*  Arguments:         ds:  pointer to device control structure    */
/*                     dc:  pointer to the Logical Channel control */
/*                            block structure                      */
/*                     ip_addr: callee's ip address                */
/*                     proto: protocol identifier byte             */
/*		       udlen: user data length			   */
/*		       ud:    user data				   */
/*  Returns:           one for success, zero for failure           */
/*  Called by:         locate_x25_lcn()                            */
/*  Calls to:          MGET()                                      */
/*                     mtod()                                      */
/*                     convert_ip_addr()                           */
/*                     bcopy()                                     */
/*                     IF_ENQUEUE()                                */
/*                     start_supr()                                */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE boolean
make_x25_call(ds, dc, ip_addr, proto, udlen, ud)
register struct dda_softc *ds;
register struct dda_cb *dc;
struct in_addr  ip_addr;
u_char          proto;
u_char		udlen;
u_char		*ud;
{
    register struct mbuf *m_callbfr;
    register u_char *cb;
    union imp_addr  imp_addr;

#if ACC_BSD == 42 || ACC_ULTRIX == 12
    struct sockaddr_in *our_addr;
#endif 

#ifdef DDADEBUG
    if (DDADBCH(13, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: make_x25_call: lcn used = %d\n",
	    ds->dda_if.if_unit, dc->dc_lcn DDAELOG;
    }
#endif DDADEBUG

    MGET(m_callbfr, M_DONTWAIT, MT_DATA);	/* try to get call cmnd
						 * buffer */
    if (m_callbfr == 0) {
	DMESG(ds->dda_if.if_unit, 22,
	      (DDALOG(LOG_ERR) "dda%d: couldn't get mbuf for call command\n",
	       ds->dda_if.if_unit DDAELOG));
	return (0);
    }
    cb = mtod(m_callbfr, u_char *);

    if (ds->dda_net_id == TRANSPAC) {
	ddacb_calling_addr[0] = 0;	/* send a 0 length calling address */
    } else {
#if ACC_BSD == 42 || ACC_ULTRIX == 12
	our_addr = (struct sockaddr_in *) & (ds->dda_if.if_addr);
	(void) convert_ip_addr(our_addr->sin_addr, ddacb_calling_addr, ds);
#else
	(void) convert_ip_addr(ds->dda_ipaddr, ddacb_calling_addr, ds);
#endif
    }

    ddacb_protocol[0] = 4;
    ddacb_protocol[1] = proto;	/* protocol type */
    ddacb_protocol[2] = 0;
    ddacb_protocol[3] = 0;
    ddacb_protocol[4] = 0;

    /*
     * CCITT standard facilities must precede DDN specific facilities See BBN
     * report 5476 section 2.1.2.  Firmware preceding rev 0x20 does not
     * support packet size / window negotiation. 
     */
    ddacb_facilities[0] = 0;	/* initialize facilities length */
    if (ds->dda_firmrev >= 0x21) {
	ddacb_facilities[0] = 0;
	if (ds->dda_init & DDA_PKTNEG) {
	    int             n = ddacb_facilities[0];	/* length so far */

	    ddacb_facilities[n + 1] = X25_FACIL_PKTSIZE;
	    ddacb_facilities[n + 2] = PKTSIZE_LARGE;
	    ddacb_facilities[n + 3] = PKTSIZE_LARGE;
	    ddacb_facilities[0] += 3;
	}
	if (ds->dda_init & DDA_WNDNEG) {
	    int             n = ddacb_facilities[0];	/* length so far */

	    ddacb_facilities[n + 1] = X25_FACIL_WINSIZE;
	    ddacb_facilities[n + 2] = WINSIZE_LARGE;
	    ddacb_facilities[n + 3] = WINSIZE_LARGE;
	    ddacb_facilities[0] += 3;
	}
    }
    if ((ds->dda_init & (DDA_BASIC | DDA_PDN)) == 0) {	/* DDN standard mode,
							 * tell callee */
	int             n = ddacb_facilities[0];	/* length so far */

	ddacb_facilities[0] += 4;	/* additional facility bytes */
	ddacb_facilities[n + 1] = DDN_FACIL_MARKER; /* end of CCITT stuff, */
	ddacb_facilities[n + 2] = DDN_FACIL_MARKER; /* and start DDN local */
	ddacb_facilities[n + 3] = X25_FACIL_DDA;    /* DDA standard mode */
	ddacb_facilities[n + 4] = FAC_DDASTD;
    }

    ddacb_cmnd[0] = CALL;	/* set command code */
    ddacb_cmnd[1] = dc->dc_lcn << 1;	/* set channel id */
    ddacb_cmnd[2] = 0;
    ddacb_cmnd[3] = (ddacb_called_addr[0] + 1) +	/* tally cmnd ext len */
	(ddacb_calling_addr[0] + 1) +
	(ddacb_protocol[0] + 1) +
	(ddacb_facilities[0] + 1) +
	(ddacb_user_data[0] + 1);

    if ((unsigned) ddacb_cmnd[3] + 4 > MLEN) {
	DMESG(ds->dda_if.if_unit, 38, (DDALOG(LOG_ERR)
	    "dda%d: make_x25_call message too large for mbuf (%d bytes)\n",
	    ds->dda_if.if_unit, (unsigned) ddacb_cmnd[3] + 4 DDAELOG));
	return 0;	/* failure */
    }

    m_callbfr->m_len = ddacb_cmnd[3] + 4;

    /* copy command header */
    bcopy(ddacb_cmnd, cb, 4);
    cb += 4;

    /* copy called address */
    bcopy(ddacb_called_addr, cb, ddacb_called_addr[0] + 1);
    cb += (ddacb_called_addr[0] + 1);

    /* copy calling address */
    bcopy(ddacb_calling_addr, cb, ddacb_calling_addr[0] + 1);
    cb += (ddacb_calling_addr[0] + 1);

    /* copy protocol */
    bcopy(ddacb_protocol, cb, ddacb_protocol[0] + 1);
    cb += (ddacb_protocol[0] + 1);

    /* copy facilities */
    bcopy(ddacb_facilities, cb, ddacb_facilities[0] + 1);
    cb += (ddacb_facilities[0] + 1);

    /* copy user data */
    bcopy(ddacb_user_data, cb, ddacb_user_data[0] + 1);
    cb += (ddacb_user_data[0] + 1);

    hist_lcn_state(ds->dda_if.if_unit, dc->dc_state, LC_CALL_PENDING);
    dc->dc_state = LC_CALL_PENDING;	/* set state */
    dc->dc_timer = TMO_CALL_PENDING;	/* start call timeout */

#ifdef DDADEBUG
    if (DDADBCH(13, ds->dda_if.if_unit)) {
	prt_bytes(ds->dda_if.if_unit, "make_x25_call: call_bfr",
		  mtod(m_callbfr, u_char *), m_callbfr->m_len);
    }
#endif DDADEBUG
    if (LOG_CALLS) {
	imp_addr.ip = ip_addr;
	DDALOG(LOG_ERR) "dda%d: Calling %d.%d.%d.%d (%s) on lcn %d\n",
	    ds->dda_if.if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
	    imp_addr.imp.s_lh, imp_addr.imp.s_impno,
	    fmt_x25(&ddacb_called_addr[1], (int) ddacb_called_addr[0]),
	    dc->dc_lcn
	    DDAELOG;
    }
    start_supr(ds, m_callbfr);
    return (1);
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      DDA_START()                            %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine attempts to start output of data queued on a    */
/*    specific LCN.  If the LCN was not already busy and data is   */
/*    available for output, the data is copied into the LCN's I/O  */
/*    buffer and a write request queued to the ACP device.  Data   */
/*    is passed in mbuf(s) from IP to ddaoutput(), ddaoutput()     */
/*    queues the data, and the data is dequeued here.              */
/*                                                                 */
/*  Call:              dda_start(ds, dc)                           */
/*  Arguments:         ds:  pointer to device control structure    */
/*                     dc:  pointer to the Logical Channel control */
/*                            block structure                      */
/*  Returns:           nothing                                     */
/*  Called by:         ddaoutput()                                 */
/*                     x25_init()                                  */
/*                     make_x25_call()                             */
/*                     supr_msg()                                  */
/*                     send_supr()                                 */
/*                     dda_data()                                  */
/*                     dda_supr()                                  */
/*  Calls to:          IF_DEQUEUE()                                */
/*                     dda_wrq()                                   */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
dda_start(ds, dc)
register struct dda_softc *ds;
register struct dda_cb *dc;
{
    register struct mbuf *m;
    register struct hdx_chan *hc = &dc->dc_wchan;
    register int    s;

#ifdef DDADEBUG
    if (DDADBCH(14, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: dda_start()\n", ds->dda_if.if_unit DDAELOG;
    }
#endif DDADEBUG

    /*
     * If output isn't active, attempt to start sending a new packet. 
     */

    if ((dc->dc_flags & DC_OBUSY) || (dc->dc_oq.ifq_len == 0) ||
	((dc->dc_lcn != 0) && (dc->dc_state != LC_DATA_IDLE))) {
	return;
    }
    if (dc->dc_lcn != 0)
	dc->dc_timer = tmo_data_idle;
/*
 *  Raise priority whenever touching dc_oq because
 *  the mbufs on this queue may be asynchronously
 *  freed upon receipt of a line status msg, restart,
 *  clear, or reset.
 */
    s = splimp();
    IF_DEQUEUE(&dc->dc_oq, m);
    splx(s);
    if (m == 0) {		/* XXX this is a bug catcher XXX */

	DMESG(ds->dda_if.if_unit, 24,
	 (DDALOG(LOG_ERR) "dda%d: dequeued NULL mbuf in IP output chain!\n",
	  ds->dda_if.if_unit DDAELOG));
	DMESG(ds->dda_if.if_unit, 24,
	      (DDALOG(LOG_ERR) "RESET dda%d MANUALLY: use /etc/acpconfig dda%d -z\n",
	       ds->dda_if.if_unit, ds->dda_if.if_unit DDAELOG));

	ds->dda_if.if_flags &= ~(IFF_RUNNING | IFF_UP);
	hist_link_state(ds->dda_if.if_unit, ds->dda_state, S_DISABLED);
	ds->dda_state = S_DISABLED;
	dda_disable(ds->dda_if.if_unit);
	return;
    }
    s = splimp();
    hc->hc_mbuf = m;
    hc->hc_curr = m;
#ifdef DDA_PAD_OR_RAW		/* crufty kludge to get the Qbit */
    if (dc->dc_flags & (DC_X29 | DC_X29W | DC_RAW)) {	/* raw or x29? */
	if (m->m_len < (MLEN - 1))	/* small mbuf? */
	    hc->hc_sbfc = m->m_dat[MLEN - 1];	/* ok, get the subfunc byte */
	else
	    hc->hc_sbfc = 0;	/* subfunc must be zero for large buffers */
    } else
	hc->hc_sbfc = 0;	/* subfunc must be zero for ip buffers */
#else
    hc->hc_sbfc = 0;
#endif
    splx(s);
    dc->dc_flags |= DC_OBUSY;
    dda_wrq(ds, hc, 0);		/* write request to ACP */
}

/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      DDA_DATA()                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine is called when a data channel I/O completes.    */
/*    If the completion was for a write, an attempt is made to     */
/*    start output on the next packet waiting for output on that   */
/*    LCN.  If the completion was for a read, the received packet  */
/*    is sent to the IP input queue (if no error) and another read */
/*    is started on the LCN.                                       */
/*                                                                 */
/*  Call:              dda_data(ds, hc, cc, cnt)                   */
/*  Argument:          ds:  device control block                   */
/*                     hc:  half duplex channel control block      */
/*                     cc:   Mailbox I/O completion status         */
/*                     cnt:  byte count                            */
/*  Returns:           nothing                                     */
/*  Called by:         ddainta()                                   */
/*  Calls to:          m_freem()                                   */
/*                     dda_start()                                 */
/*                     IF_QFULL()                                  */
/*                     IF_DROP()                                   */
/*                     IF_ENQUEUE()                                */
/*                     schednetisr()                               */
/*                     dda_rrq()                                   */
/*                     dda_wrq()                                   */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
dda_data(ds, hc, cc, cnt)
register struct dda_softc *ds;
register struct hdx_chan *hc;
int             cc, cnt;
{
    register struct dda_cb *dc = &(ds->dda_cb[hc->hc_chan / 2]);
    struct ifqueue *inq = &ipintrq;	/* IP input queue */

/* note that this routine is a weird case in which Ultrix 2.0 behaves like
 * a 4.2 system rather than a 4.3 system.  This is reflected in the structure
 * of conditional compilation segments.
 */
#if ACC_BSD > 42			/* 4.3bsd or newer */
    register struct mbuf *m, *mb;
    struct ifnet   *ifp;
#else					/* 4.2, or all flavors of Ultrix */
    register struct mbuf *m;
#endif

#ifdef DDADEBUG
    if (DDADBCH(18, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: dda_data: chan=%d cc=%x cnt=%x\n",
	    ds->dda_if.if_unit, hc->hc_chan, cc, cnt DDAELOG;
    }
#endif DDADEBUG

#if ACC_BSD > 42
    ifp = &ds->dda_if;
#endif

    if (hc->hc_chan & 0x01) {	/* was it read or write? *//* write, fire up
				 * next output */
#ifdef DDADEBUG
	dc->dc_out_t = TMO_OFF;	/* turn off output completion timer */
#endif
	hc = &dc->dc_wchan;
	if ((hc->hc_func != DDAABT) && (hc->hc_curr = hc->hc_curr->m_next))
	    dda_wrq(ds, hc, 0);
	else {
	    m_freem(hc->hc_mbuf);
	    hc->hc_mbuf = hc->hc_curr = (struct mbuf *) 0;
	    if (hc->hc_func == DDAABT) {
		hc->hc_func &= ~DDAABT;
		hc->hc_inv &= ~INVALID_MBUF;
	    } else
		ds->dda_if.if_opackets++;
	    dc->dc_flags &= ~DC_OBUSY;
	    dda_start(ds, dc);
	}
    } else {			/* read, process rcvd packet */
	hc = &dc->dc_rchan;

#ifdef DDADEBUG
	if (DDADBCH(19, ds->dda_if.if_unit)) {
	    u_char         *p;

	    p = mtod(hc->hc_curr, u_char *);
	    prt_bytes(ds->dda_if.if_unit, "received data", p, (cnt < 64 ? cnt : 64));
	}
#endif DDADEBUG

	if (cc == DDAIOCOK) {	/* Queue good packet for input */
#ifdef DDADEBUG
	    if (DDADBCH(19, ds->dda_if.if_unit)) {
		DDALOG(LOG_DEBUG) "dda%d: dda_data: chan=%d DDAIOCOK\n",
		    ds->dda_if.if_unit, hc->hc_chan DDAELOG;
	    }
#endif DDADEBUG
	    ds->dda_if.if_ipackets++;
	    hist_lcn_state(ds->dda_if.if_unit, dc->dc_state, LC_DATA_IDLE);
	    if (dc->dc_state == LC_DATA_IDLE)
		dc->dc_timer = tmo_data_idle;
	    hc->hc_curr->m_len += cnt;	/* update byte count */
	    m = hc->hc_mbuf;	/* que mbuf chain */

#if ACC_BSD > 42
	    /* Prepend ifp pointer for 4.3 */
	    MGET(mb, M_DONTWAIT, MT_DATA);
	    if (mb == 0) {
		DMESG(ds->dda_if.if_unit, 26,
		(DDALOG(LOG_ERR) "dda%d: couldn't get mbuf for ifp header\n",
		 ds->dda_if.if_unit DDAELOG));
		m_freem(m);
		return;
	    }
	    *(mtod(mb, struct ifnet **)) = ifp;
	    mb->m_len = sizeof(ifp);
	    mb->m_next = m;

	    if (IF_QFULL(inq)) {
		IF_DROP(inq);
		m_freem(mb);
	    } else {
		IF_ENQUEUE(inq, mb);
		schednetisr(NETISR_IP);
	    }
#else
	    if (IF_QFULL(inq)) {
		IF_DROP(inq);
		m_freem(m);
	    } else {
		IF_ENQUEUE(inq, m);
		schednetisr(NETISR_IP);
	    }
#endif
	    hc->hc_mbuf = hc->hc_curr = (struct mbuf *) 0;
	} else if (cc == DDAIOCOKP) {	/* good completion, more data pending */
	    hc->hc_curr->m_len += cnt;
	} else {
	    m_freem(hc->hc_mbuf);
	    hc->hc_mbuf = hc->hc_curr = (struct mbuf *) 0;
	}
	/* hang a new data read */
	dda_rrq(ds, hc);
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      DDA_SUPR()                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine is called when a supervisor I/O completes.      */
/*    If the completion was for a write, an attempt is made to     */
/*    start output on the next supervisor command waiting for      */
/*    output.  If the completion was for a read, the received      */
/*    supervisor message is processed and another read is started. */
/*                                                                 */
/*  Call:              dda_supr(ds, hc, cc)                        */
/*  Argument:          ds:  device control block                   */
/*                     hc:  half duplex channel control block      */
/*                     cc:   Mailbox I/O completion status         */
/*  Returns:           nothing                                     */
/*  Called by:         ddainta()                                   */
/*  Calls to:          dda_start()                                 */
/*                     mtod()                                      */
/*                     supr_msg()                                  */
/*                     m_free()                                    */
/*                     dda_rrq()                                   */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
dda_supr(ds, hc, cc, cnt)
register struct dda_softc *ds;
struct hdx_chan *hc;
int             cc;
int             cnt;
{
    register struct dda_cb *dc = &(ds->dda_cb[hc->hc_chan / 2]);
    u_char         *p;

#ifdef DDADEBUG
    if (DDADBCH(20, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: dda_supr: chan=%d cc=%x\n",
	    ds->dda_if.if_unit, hc->hc_chan, cc DDAELOG;
    }
#endif DDADEBUG

    /* an odd-numbered channel indicates a write */
    /* the supr msg is assumed to be in 1 mbuf   */

    if (hc->hc_chan & 0x01) {
	m_freem(hc->hc_mbuf);
	hc->hc_mbuf = hc->hc_curr = (struct mbuf *) 0;
	dc->dc_flags &= ~DC_OBUSY;
	dda_start(ds, dc);
    }
    /* otherwise, process the read */

    else {
	if (cc == DDAIOCOK) {
	    p = mtod(hc->hc_curr, u_char *);	/* point to data in mbuf */
#ifdef DDA_PAD_OR_RAW
	    switch (dda_decode_type(ds, p)) {
#  ifdef DDA_PADOPT
	      case 1:
#    ifdef DDADEBUG
		if (DDADBCH(20, ds->dda_if.if_unit)) {
		    printf("dda%d: dda_supr(): case 1: chan = %x, p = %x\n",
			   ds->dda_if.if_unit, hc->hc_chan, *p);
		}
#    endif DDADEBUG
		x29_supr(ds, p);
		break;
#  endif
#  ifdef DDA_RAWOPT
	      case 2:
#    ifdef DDADEBUG
		if (DDADBCH(20, ds->dda_if.if_unit)) {
		    printf("dda%d: dda_supr(): case 2: chan = %x, p = %x\n",
			   ds->dda_if.if_unit, hc->hc_chan, *p);
		}
#    endif DDADEBUG
		hc->hc_curr->m_len += cnt;
		pi_supr(ds, hc->hc_curr);
		/* don't free mbuf here */
		hc->hc_mbuf = hc->hc_curr = (struct mbuf *) 0;
		dda_rrq(ds, hc);/* hang a new supr read */
		return;
#  endif
	      default:
		supr_msg(ds, p);/* process supervisor message */
		break;
	    }
#else DDA_PAD_OR_RAW
	    supr_msg(ds, p);	/* process supervisor message */
#endif DDA_PAD_OR_RAW
	} else if (cc == DDAIOCOKP) {
	    DMESG(ds->dda_if.if_unit, 28,
		  (DDALOG(LOG_ERR) "dda%d: truncated supervisor message\n",
		   ds->dda_if.if_unit DDAELOG));
	}
	m_freem(hc->hc_mbuf);
	hc->hc_mbuf = hc->hc_curr = (struct mbuf *) 0;
	dda_rrq(ds, hc);	/* hang a new supr read */
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      SUPR_MSG()                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*       This routine processes received supervisor messages.      */
/*       Depending on the message type, the appropriate action is  */
/*       taken.                                                    */
/*                                                                 */
/*  Call:              supr_msg(ds, p)                             */
/*  Arguments:         ds:  pointer to dev control block struct    */
/*                     p:   pointer to a character array           */
/*                              containing the supervisor message  */
/*  Returns:           nothing                                     */
/*  Called by:         dda_supr()                                  */
/*  Calls to:          DDALOG()                                    */
/*                     IF_DEQUEUE()                                */
/*                     m_freem()                                   */
/*                     send_restart()                              */
/*                     send_supr()                                 */
/*                     dda_start()                                 */
/*                     decode_ring()                               */
/*                     decode_answer()                             */
/*                     convert_x25_addr()                          */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
supr_msg(ds, p)
struct dda_softc *ds;
u_char          p[];

{
    register struct dda_cb *dc;
    register int    lcn;
    register int    maxlcn;
    union imp_addr  imp_addr;

#ifdef DDADEBUG
    if (DDADBCH(21, ds->dda_if.if_unit)) {
	prt_bytes(ds->dda_if.if_unit, "supr_msg", p, (int) (4 + p[3]));
    }
#endif DDADEBUG

    maxlcn = nddach[ds->dda_if.if_unit];	/* obtain SVC limit */
    switch (p[0]) {
      case LINE_STATUS:	/* link status msg */
	if (p[2] == LINK_UP) {	/* if link came up */
#ifdef DDADEBUG
	    if (DDADBCH(21, ds->dda_if.if_unit)) {
		DDALOG(LOG_DEBUG) "dda%d: supr_msg: HDLC link up\n",
		    ds->dda_if.if_unit DDAELOG;
	    }
#endif DDADEBUG
	    send_restart(ds);	/* send restart msg */
	    ds->dda_state = S_COMING_UP;
	} else {		/* if link went down */
	    ds->dda_if.if_flags &= ~IFF_UP;	/* ? should call if_down() ? */
	    hist_link_state(ds->dda_if.if_unit, ds->dda_state, S_DISABLED);
	    ds->dda_state = S_DISABLED;
	    dc = ds->dda_cb;
	    /* LCNLINK */
	    for (lcn = 0; lcn <= maxlcn; lcn++) {	/* for all LCN's */
		dc->dc_inaddr.s_addr = 0;	/* forget dest address */
		dc->dc_key.key_addr.s_addr = 0;
		dc->dc_wsizein = dc->dc_wsizeout = 0;
		dc->dc_pktsizein = dc->dc_pktsizeout = 0;
		dc->dc_state = LC_DOWN;	/* set state */
		dc->dc_timer = TMO_OFF;	/* stop timer */
		dc++;
	    }
	    hist_all_lcns(ds->dda_if.if_unit, LC_DOWN);
	    abort_io(ds->dda_if.if_unit, ALL_CHANS);
#ifdef DDA_PADOPT
	    x29_init(ds->dda_if.if_unit, 1);
#endif
	    if (p[2] == LINK_DISABLED)	/* link disabled */
		DMESG(ds->dda_if.if_unit, 29,
		      (DDALOG(LOG_ERR) "dda%d:  link disabled\n",
		       ds->dda_if.if_unit DDAELOG));
	    else
		DMESG(ds->dda_if.if_unit, 30,
		      (DDALOG(LOG_ERR) "dda%d:  link down\n", ds->dda_if.if_unit DDAELOG));
	}
	break;

      case RESTART:		/* restart received */
	if (ds->dda_cb[0].dc_state != LC_RESTART) {	/* if not restarting */

#ifdef DDADEBUG
	    if (DDADBCH(21, ds->dda_if.if_unit)) {
		DDALOG(LOG_DEBUG)
		    "dda%d: supr_msg: RESTART rcvd, no RESTART pending\n",
		    ds->dda_if.if_unit DDAELOG;
	    }
#endif DDADEBUG
	    send_supr(ds, RSTRT_ACK, 0, 0);	/* send restart ack */
	}
	/* fall thru */
      case RSTRT_ACK:		/* restart ack */
	if ((ds->dda_state == S_COMING_UP) || (ds->dda_state == S_LINK_UP)) {
	    if (p[0] == RSTRT_ACK) {
		DMESG(ds->dda_if.if_unit, 31,
		      (DDALOG(LOG_ERR) "dda%d: Restart Ack received\n",
		       ds->dda_if.if_unit DDAELOG));
	    } else {		/* restart. print cause and diagnostic. */
		DMESG(ds->dda_if.if_unit, 31,
		      (DDALOG(LOG_ERR) "dda%d: Restart (%x %x) received\n",
		       ds->dda_if.if_unit, p[2], p[3] ? p[4] : 0 DDAELOG));
	    }

	    ds->dda_if.if_flags |= IFF_UP;
	    hist_link_state(ds->dda_if.if_unit, ds->dda_state, S_LINK_UP);
	    ds->dda_state = S_LINK_UP;
	    dc = ds->dda_cb;
	    /* LCNLINK */
	    for (lcn = 0; lcn <= maxlcn; lcn++) {	/* for all LCN's */
		dc->dc_state = LC_IDLE;	/* set state */
		dc->dc_timer = TMO_OFF;	/* stop timer */
		dc->dc_inaddr.s_addr = 0;	/* forget address */
		dc->dc_key.key_addr.s_addr = 0;
		dc->dc_wsizein = dc->dc_wsizeout = 0;
		dc->dc_pktsizein = dc->dc_pktsizeout = 0;
		dc++;
	    }
	    hist_all_lcns(ds->dda_if.if_unit, LC_IDLE);
	    abort_io(ds->dda_if.if_unit, ALL_CHANS);
	    DMESG(ds->dda_if.if_unit, 32,
		  (DDALOG(LOG_ERR) "dda%d: (%s rev %d.%d) link up\n",
		   ds->dda_if.if_unit, dda_product,
	      (ds->dda_firmrev >> 4) & 0xF, ds->dda_firmrev & 0xF DDAELOG));
#ifdef DDA_PAD_OR_RAW
	    x29_init(ds->dda_if.if_unit, 1);

	    /*
	     * wake up all processes that tried to open a x29 device but
	     * slept because the board was not up 
	     */
	    wakeup(&ds->dda_state);
#endif DDA_PAD_OR_RAW
	} else
#ifdef DDADEBUG
	if (DDADBCH(21, ds->dda_if.if_unit)) {
	    DDALOG(LOG_ERR) "dda%d:  Unexpected RESTART in state %x\n",
		ds->dda_if.if_unit, ds->dda_state DDAELOG;
	}
#endif DDADEBUG
	break;

      case ANSWER:		/* call answered */
	lcn = p[1] / 2;
	dc = &(ds->dda_cb[lcn]);
	if (dc->dc_state == LC_CALL_PENDING) {	/* if a call pending */
	    hist_lcn_state(ds->dda_if.if_unit, dc->dc_state, LC_DATA_IDLE);
	    decode_answer(p, dc);
	    dc->dc_state = LC_DATA_IDLE;	/* set state */
	    dc->dc_timer = tmo_data_idle;	/* start timer */
	    dda_start(ds, dc);	/* try to send data */
	}
	if (LOG_CALLS) {
	    DDALOG(LOG_ERR) "dda%d: lcn %d connected\n",
		ds->dda_if.if_unit, lcn DDAELOG;
	}
	break;

      case RING:		/* incoming call */
	/* if ring looks ok, and we find a free LCN to assign */
	if (decode_ring(p) && (dc = find_free_lcn(ds))) {
	    dc->dc_key.key_addr.s_addr =
		convert_x25_addr(ddacb_calling_addr, ds, dc);
#ifdef DDADEBUG
	    if (DDADBCH(21, ds->dda_if.if_unit)) {
		imp_addr.ip = dc->dc_inaddr;
		DDALOG(LOG_DEBUG)
		    "dda%d: supr_msg: got call from %d.%d.%d.%d\n",
		    ds->dda_if.if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
		    imp_addr.imp.s_lh, imp_addr.imp.s_impno DDAELOG;
	    }
#endif DDADEBUG
	    hist_lcn_state(ds->dda_if.if_unit, dc->dc_state, LC_DATA_IDLE);
	    dc->dc_state = LC_DATA_IDLE;	/* set state */
	    dc->dc_timer = tmo_data_idle;	/* start timer */
	    dc->dc_pktsizein = 0;
	    dc->dc_pktsizeout = 0;
	    dc->dc_wsizein = 0;
	    dc->dc_wsizeout = 0;
	    send_supr(ds, ANSWER, (int) dc->dc_lcn * 2,
		      (int) p[2]);		/* send answer */
	    if (LOG_CALLS) {
		imp_addr.ip = dc->dc_inaddr;
		DDALOG(LOG_ERR)
		    "dda%d: Accepting call from %d.%d.%d.%d (%s) on lcn %d\n",
		    ds->dda_if.if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
		    imp_addr.imp.s_lh, imp_addr.imp.s_impno,
		    fmt_x25(&ddacb_calling_addr[1],
		    (int) ddacb_calling_addr[0]), dc->dc_lcn DDAELOG;
	    }
	} else {		/* if no free LCN's */
	    send_supr(ds, CLEARVC, p[2], 0);	/* clear call */
	    if (LOG_CALLS) {
		DDALOG(LOG_ERR) "dda%d: Rejecting call from %s on VC 0x%x\n",
		    ds->dda_if.if_unit,
		    fmt_x25(&ddacb_calling_addr[1], ddacb_calling_addr[0]),
		    p[1] DDAELOG;
	    }
	}
	break;

      case CLEARLC:		/* clear by LCN */

	/*
	 * This could mean one of three things: If we have a call request
	 * outstanding, this message means the call has failed. If we have a
	 * clear request outstanding, this message completes the cleanup; the
	 * channel is now available for reuse. If we have a call active, this
	 * message means the other side is closing the circuit. 
	 */
	lcn = p[1] / 2;		/* get LCN */
	dc = &(ds->dda_cb[lcn]);
	if (dc->dc_state != LC_CLR_PENDING) {	/* if no clear pending */
	    send_supr(ds, CLEARLC, p[1], 0);	/* ack the clear */
	}
	if (dc->dc_state == LC_CALL_PENDING	/* if call is cleared */
	    && (LOG_CALLS || DMESGVAL(ds->dda_if.if_unit, 33) == 0)) {
	    imp_addr.ip = dc->dc_inaddr;
	    DDALOG(LOG_ERR)
		"dda%d: Call to %d.%d.%d.%d on lcn %d failed (%x %x)\n",
		ds->dda_if.if_unit, imp_addr.imp.s_net, imp_addr.imp.s_host,
		imp_addr.imp.s_lh, imp_addr.imp.s_impno, dc->dc_lcn, p[2], p[4]
		DDAELOG;

	} else if (LOG_CALLS) {
	    if (dc->dc_state == LC_CLR_PENDING) {	/* did we clear it? *//* y
							 * es, IP address is
							 * already gone.  Say
							 * channel is free.  */
		DDALOG(LOG_ERR) "dda%d: Cleared lcn %d\n",
		    ds->dda_if.if_unit, dc->dc_lcn DDAELOG;
	    } else {		/* cleared by net, print more info */
		imp_addr.ip = dc->dc_inaddr;
		DDALOG(LOG_ERR)
		    "dda%d: Cleared lcn %d to %d.%d.%d.%d (%x %x)\n",
		    ds->dda_if.if_unit, dc->dc_lcn, imp_addr.imp.s_net,
		    imp_addr.imp.s_host, imp_addr.imp.s_lh, imp_addr.imp.s_impno,
		    p[2], p[4] DDAELOG;
	    }
	}
	hist_lcn_state(ds->dda_if.if_unit, dc->dc_state, LC_IDLE);
	/* LCNLINK delete */
	dc->dc_state = LC_IDLE;	/* set state */
	dc->dc_timer = TMO_OFF;	/* stop timer */
	dc->dc_inaddr.s_addr = 0;	/* forget address */
	dc->dc_key.key_addr.s_addr = 0;
	dc->dc_wsizein = dc->dc_wsizeout = 0;
	dc->dc_pktsizein = dc->dc_pktsizeout = 0;
	abort_io(ds->dda_if.if_unit, lcn);
	if (LOG_CALLS) {
	    printf("dda%d: Cleared LCN %d cause code %x diag code %x\n",
		   ds->dda_if.if_unit, dc->dc_lcn, p[2], p[4]);
	}
	break;

      case CLEARVC:		/* clear by VCN */

	send_supr(ds, CLEARVC, p[1], 0);	/* send clear ack */
	if (LOG_CALLS) {
	    DDALOG(LOG_ERR) "dda%d: Network cleared VC %x (%x %x)\n",
		ds->dda_if.if_unit, p[1], p[2], p[4] DDAELOG;
	}
#ifdef DDADEBUG
	else if (DDADBCH(21, ds->dda_if.if_unit)) {
	    DDALOG(LOG_DEBUG) "dda%d: supr_msg: CLEARVC VCN=%x\n",
		ds->dda_if.if_unit, p[1] DDAELOG;
	}
#endif DDADEBUG
	break;

      case RESET:		/* X25 reset */
	lcn = p[1] / 2;		/* get LCN */
	dc = &(ds->dda_cb[lcn]);
	send_supr(ds, RESET_ACK, p[1], 0);	/* send reset ack */
	abort_io(ds->dda_if.if_unit, lcn);
	imp_addr.ip = dc->dc_inaddr;
	DMESG(ds->dda_if.if_unit, 34,
	      (DDALOG(LOG_ERR)
	       "dda%d: X25 RESET (%x %x) on lcn %d: %d.%d.%d.%d\n",
	       ds->dda_if.if_unit, p[2], p[4], lcn, imp_addr.imp.s_net,
	       imp_addr.imp.s_host, imp_addr.imp.s_lh, imp_addr.imp.s_impno
	       DDAELOG));
	break;

      case INTERRUPT:		/* X25 interrupt */
	lcn = p[1] / 2;		/* get LCN */
	dc = &(ds->dda_cb[lcn]);
	imp_addr.ip = dc->dc_inaddr;
	DMESG(ds->dda_if.if_unit, 35,
	      (DDALOG(LOG_ERR)
	       "dda%d: X25 INTERRUPT (%x) on lcn %d: %d.%d.%d.%d\n",
	       ds->dda_if.if_unit, p[2], lcn, imp_addr.imp.s_net,
	       imp_addr.imp.s_host, imp_addr.imp.s_lh, imp_addr.imp.s_impno
	       DDAELOG));
	break;

      case STATRESP:		/* Statistics Response from FEP */

	/*
	 * Copy the whole message into a static buffer, dda_iobuf. The buffer
	 * is viewed as a (struct ddactl).  Wake up the ioctl thread which
	 * will copy the message out for acpconfig. 
	 */
	{
	    struct ddactl  *da = (struct ddactl *) dda_iobuf;

	    bcopy(p, da->msg, max(4 + p[3], sizeof(da->msg)));
#ifdef MULTINET
	    StatQuery_Completed = 1;
#else
	    wakeup(dda_iobuf);
#endif
	    break;
	}

      default:
	DMESG(ds->dda_if.if_unit, 36,
	      (DDALOG(LOG_ERR) "dda%d: supervisor error (%x %x %x %x)\n",
	       ds->dda_if.if_unit, p[0], p[1], p[2], p[3] DDAELOG));
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                   DECODE_ANSWER()                           %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*        This routine looks at the answer message from the FE     */
/*  and decodes it to find the negtiated packet and window sizes   */
/*  if they are present.                                           */
/*                                                                 */
/*  Call:              decode_answer(p, dc)	                   */
/*  Argument:          p: pointer to mbuf data for ANSWER message  */
/*                     dc: pointer to relavant lcn structure       */
/*  Returns:           nothing                                     */
/*  Called by:         supr_msg()                                  */
/*  Calls to:                                                      */
/*                     DDALOG()                                    */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
decode_answer(p, dc)
u_char         *p;
struct dda_cb  *dc;
{
    register u_char *cp;
    int             i, faclen;

    dc->dc_pktsizein = 0;
    dc->dc_pktsizeout = 0;
    dc->dc_wsizein = 0;
    dc->dc_wsizeout = 0;
    cp = p + 4;			/* skip over code, lcn, vcn and count in
				 * answer message */
    /* cp now points to length of called address */
    cp += *cp + 1;		/* skip over called address and length byte */
    /* cp now points to length of calling address */
    cp += *cp + 1;		/* skip over calling address and length byte */
    /* cp now points to length of protocol */
    cp += *cp + 1;		/* skip over protocol and protocol length
				 * byte */
    /* cp now points to the facilities length */

    faclen = *cp++;
    /* cp now points to start of facilities */
    for (i = 0; i < faclen;) {
	switch (*cp & 0xc0) {
	  case 0x00:		/* single octet parameter field */
	    i += 2;
	    cp += 2;
	    break;
	  case 0x40:		/* double octet parameter field */
	    switch (*cp) {
	      case X25_FACIL_PKTSIZE:	/* 0x42, packet size */
		dc->dc_pktsizein = *(cp + 1);
		dc->dc_pktsizeout = *(cp + 2);
		break;
	      case X25_FACIL_WINSIZE:	/* 0x43, window size */
		dc->dc_wsizein = *(cp + 1);
		dc->dc_wsizeout = *(cp + 2);
		break;
	    }
	    i += 3;
	    cp += 3;
	    break;
	  case 0x80:		/* triple octet parameter field */
	    i += 4;
	    cp += 4;
	    break;
	  case 0xc0:		/* variable-length parameter field */
	    cp++;
	    i += 2 + *cp;
	    cp += 1 + *cp;
	    break;
	    /* Note: No other cases (i.e., default) possible */
	}
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      DECODE_RING()                          %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine parses and validates the incoming call message. */
/*                                                                 */
/*  Call:              decode_ring(p)                              */
/*  Argument:          p:   pointer to the message                 */
/*  Returns:           1 for success, else 0 for failure           */
/*  Called by:         supr_msg()                                  */
/*  Calls to:          bcopy()                                     */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE boolean
decode_ring(p)
register u_char *p;
{
    register int    cnt;

#ifdef DDADEBUG
    if (DDADBCH(22, 0)) {	/* no easy access to unit, assume unit 0 */
	DDALOG(LOG_DEBUG) "dda: decode_ring()\n" DDAELOG;
    }
#endif DDADEBUG

    p += 3;			/* skip to cmnd ext length */
    if (*p++ < 5)		/* is count appropriate */
	return (0);		/* return false if not */

    /* called address */
    if ((cnt = *p + 1) > 16)	/* is called addr len legal? */
	return (0);		/* return false if not */
    bcopy(p, ddacb_called_addr, cnt);	/* copy field */
    p += cnt;

    /* calling address */
    if ((cnt = *p + 1) > 16)	/* is calling addr len legal? */
	return (0);		/* return false if not */
    bcopy(p, ddacb_calling_addr, cnt);	/* copy field */
    p += cnt;

    /* protocol part of user data */
    if ((cnt = *p + 1) > 5)	/* is protocol len legal? */
	return (0);		/* return false if not */
    bcopy(p, ddacb_protocol, cnt);	/* copy field */
    p += cnt;

    /* facilities */
    if ((cnt = *p + 1) > 64)	/* is facilities len legal? */
	return (0);		/* return false if not */
    bcopy(p, ddacb_facilities, cnt);	/* copy field */
    p += cnt;

    /* ignore rest of user data for now */

#ifdef	DDA_PAD_OR_RAW
    if (ddacb_protocol[0] == 0)
	return (0);
#else DDA_PAD_OR_RAW
    if ((ddacb_protocol[0] == 0) || (ddacb_protocol[1] != X25_PROTO_IP))
	return (0);		/* bad if not IP */
#endif DDA_PAD_OR_RAW

#ifndef DDA_PAD_OR_RAW
    return (1);			/* looks ok */
#else
#  ifdef DDA_RAWOPT
    return (1);			/* anything is ok if we're PI interface */
#  else
    if (ddacb_protocol[1] == X25_PROTO_IP || ddacb_protocol[1] == X25_PROTO_X29)
	return (1);		/* looks ok */
    else
	return (0);		/* bad if not IP or X29 */
#  endif
#endif DDA_PAD_OR_RAW
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      CLEAR_LCN()                            %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine clears an X25 circuit and releases any buffers  */
/*    queued for transmission.                                     */
/*                                                                 */
/*  Call:              clear_lcn(ds, dc)                           */
/*  Argument:          ds:   pointer to dev control block struct   */
/*                     dc:  pointer to the Logical Channel control */
/*                            block structure                      */
/*  Returns:           nothing                                     */
/*  Called by:         ddatimer()                                  */
/*  Calls to:          IF_DEQUEUE()                                */
/*                     m_freem()                                   */
/*                     send_supr()                                 */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
clear_lcn(ds, dc)
struct dda_softc *ds;
struct dda_cb  *dc;
{
    register struct mbuf *m;
    register int    s;

#ifdef DDADEBUG
    if (DDADBCH(23, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: clear_lcn(%d)\n", ds->dda_if.if_unit,
	    dc->dc_lcn DDAELOG;
    }
#endif DDADEBUG

    if (dc->dc_state == LC_CLR_PENDING) {	/* Unfortunately, we can't
						 * display the destination's
						 * IP address, as we cleared
						 * it when we entered
						 * clear-pending state (to
						 * prevent new data from
						 * being queued to this
						 * channel). */
	DMESG(ds->dda_if.if_unit, 37,
	      (DDALOG(LOG_ERR) "dda%d: Clear request lost -- lcn %d\n",
	       ds->dda_if.if_unit, dc->dc_lcn DDAELOG));
	return;
    }
    hist_lcn_state(ds->dda_if.if_unit, dc->dc_state, LC_CLR_PENDING);
    dc->dc_state = LC_CLR_PENDING;	/* set state */
    dc->dc_timer = TMO_CLR_PENDING;	/* start clear timer */
    dc->dc_inaddr.s_addr = 0;	/* clear associated address */
    dc->dc_key.key_addr.s_addr = 0;
    dc->dc_wsizein = dc->dc_wsizeout = 0;
    dc->dc_pktsizein = dc->dc_pktsizeout = 0;
/*
 *  Raise priority whenever dc_oq is touched.
 */
    s = splimp();
    while (dc->dc_oq.ifq_len) {	/* drop any pending data */
	IF_DEQUEUE(&dc->dc_oq, m);
	m_freem(m);
    }
    splx(s);
    send_supr(ds, CLEARLC, (int) dc->dc_lcn * 2, 0);	/* send clear msg */
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      SEND_RESTART()                         %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine marks all LCNs as being in a restarting state   */
/*    and sends a restart command to X25.                          */
/*                                                                 */
/*  Call:              send_restart(ds)                            */
/*  Argument:          ds:   pointer to dev control block struct   */
/*  Returns:           nothing                                     */
/*  Called by:         ddatimer()                                  */
/*                     supr_msg()                                  */
/*  Calls to:          IF_DEQUEUE()                                */
/*                     m_freem()                                   */
/*                     send_supr()                                 */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
send_restart(ds)
struct dda_softc *ds;
{
    register struct dda_cb *dc;
    register int    lcn;
    register int    maxlcn;

#ifdef DDADEBUG
    if (DDADBCH(24, ds->dda_if.if_unit)) {
	DDALOG(LOG_DEBUG) "dda%d: send_restart()\n", ds->dda_if.if_unit DDAELOG;
    }
#endif DDADEBUG

    dc = ds->dda_cb;
    /* LCNLINK */
    maxlcn = nddach[ds->dda_if.if_unit];
    for (lcn = 0; lcn <= maxlcn; lcn++) {	/* for all LCN's */
	dc->dc_state = LC_RESTART;	/* set state */
	dc->dc_timer = TMO_RESTART;	/* start restart timeout */
	dc->dc_inaddr.s_addr = 0;	/* forget address */
	dc->dc_key.key_addr.s_addr = 0;
	dc->dc_wsizein = dc->dc_wsizeout = 0;
	dc->dc_pktsizein = dc->dc_pktsizeout = 0;
	dc++;
    }
    hist_all_lcns(ds->dda_if.if_unit, LC_RESTART);
    abort_io(ds->dda_if.if_unit, ALL_CHANS);
    send_supr(ds, RESTART, 0, 0);	/* send restart msg */
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                      SEND_SUPR()                            %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                 */
/*  Purpose:                                                       */
/*                                                                 */
/*    This routine is used to send short (4 bytes only) supervisor */
/*    commands, except that longer ANSWER messages may be sent.    */
/*                                                                 */
/*  Call:              send_supr(ds, cmd, p1, p2)                  */
/*  Argument:          ds:   pointer to dev control block struct   */
/*                     cmd:  type of command                       */
/*                     p1:   2nd byte of supervisor message        */
/*                     p2:   3rd byte of supervisor message        */
/*  Returns:           nothing                                     */
/*  Called by:         supr_msg()                                  */
/*                     clear_lcn()                                 */
/*                     send_restart()                              */
/*  Calls to:          MGET()                                      */
/*                     DDALOG()                                    */
/*                     mtod()                                      */
/*                     IF_ENQUEUE()                                */
/*                     dda_start()                                 */
/*                                                                 */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
send_supr(ds, cmd, p1, p2)
struct dda_softc *ds;
int             cmd, p1, p2;
{
    struct mbuf    *m;
    register u_char *cp;
    u_char         *savcp, *fp, *svcp;
    int             i, faclen;

    MGET(m, M_DONTWAIT, MT_DATA);

    if (m == 0) {
	DMESG(ds->dda_if.if_unit, 23,
	      (DDALOG(LOG_ERR) "dda%d: failed to get supr msg bfr!\n",
	       ds->dda_if.if_unit DDAELOG));
	return;
    }
    cp = savcp = mtod(m, u_char *);

    /* build supervisor message */

    *cp++ = (u_char) cmd;
    *cp++ = (u_char) p1;
    *cp++ = (u_char) p2;
    *cp++ = 0;

    m->m_len = 4;

    if (cmd == ANSWER) {
	register struct dda_cb *dc;

	/* for answer messages p1 is (lcn * 2) */
	dc = &(ds->dda_cb[p1 / 2]);
	*cp++ = 0;		/* zero length called address */
	*cp++ = 0;		/* zero length calling address */
	*cp++ = 0;		/* zero length protocol */

	/* check and copy facilities */
	faclen = 0;
	svcp = cp++;
	for (i = 0, fp = &ddacb_facilities[1]; i < ddacb_facilities[0];) {
	    switch (*fp & 0xc0) {
	      case 0x00:	/* single octet parameter field */
		i += 2;
		fp += 2;
		break;
	      case 0x40:	/* double octet parameter field */

		/*
		 * Note that this code can in some cases attempt to negotiate
		 * the packet size or window away from the default, which
		 * appears to violate the X.25 spec. In fact, the FEP
		 * examines these values and bounds them between the
		 * requested value and the default value thus satisfying X.25 
		 */
		switch (*fp) {
		  case X25_FACIL_PKTSIZE:	/* 0x42, packet size */
		    *cp++ = X25_FACIL_PKTSIZE;
		    if (ds->dda_firmrev < 0x21) {
			*cp++ = PKTSIZE_DEF;	/* Set incoming and outgoing */
			*cp++ = PKTSIZE_DEF;	/* packet size to default */
			dc->dc_pktsizein = dc->dc_pktsizeout = PKTSIZE_DEF;
		    } else {
			*cp++ = *(fp + 1);	/* Answer with requested */
			*cp++ = *(fp + 2);	/* facilities */
			dc->dc_pktsizeout = *(fp + 1);
			dc->dc_pktsizein = *(fp + 2);
		    }
		    faclen += 3;
		    break;
		  case X25_FACIL_WINSIZE:	/* 0x43, window size */
		    *cp++ = X25_FACIL_WINSIZE;
		    if (ds->dda_firmrev < 0x21) {
			*cp++ = WINSIZE_DEF;	/* Set incoming and outgoing */
			*cp++ = WINSIZE_DEF;	/* window size to default */
			dc->dc_wsizein = dc->dc_wsizeout = WINSIZE_DEF;
		    } else {
			*cp++ = *(fp + 1);	/* Answer with requested */
			*cp++ = *(fp + 2);	/* facilities */
			dc->dc_wsizeout = *(fp + 1);
			dc->dc_wsizein = *(fp + 2);
		    }
		    faclen += 3;
		    break;
		}
		i += 3;
		fp += 3;
		break;
	      case 0x80:	/* triple octet parameter field */
		i += 4;
		fp += 4;
		break;
	      case 0xc0:	/* variable-length parameter field */
		fp++;
		i += 2 + *fp;
		fp += 1 + *fp;
		break;
		/* Note: No other cases (i.e., default) possible */
	    }
	}

	if (faclen) {		/* Found facilities to negotiate! */
	    *svcp = faclen;	/* facility length <- faclen */
	    *cp++ = 0;		/* user data length <- 0 */
	    *(savcp + 3) = cp - savcp - 4;	/* set supv message length */
	    m->m_len = cp - savcp;	/* set mbuf message length */
	}
    }				/* (end of answer message case) */
# ifdef DDADEBUG
    if (DDADBCH(25, ds->dda_if.if_unit)) {
	prt_bytes(ds->dda_if.if_unit, "send_supr", savcp, m->m_len);
    }
#endif DDADEBUG
    start_supr(ds, m);
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%				START_SUPR()			       %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*	Start i/o on the supervisor channel, checking for queue full.	 */
/*	Added to revision 2.0 so that "queue full" checking would be	 */
/*	applied uniformly to all supervisory channel output.		 */
/*                                                                       */
/*  Call:          start_supr(ds, m)                                     */
/*  Argument:      ds:  softc structure for board			 */
/*		   m:	mbuf holding message				 */
/*  Returns:       nothing                                               */
/*  Called by:     send_supr(), send_config(), make_x25_call()		 */
/*  Calls to:      DDALOG(), dda_start(), IF_ENQUEUE()			 */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
start_supr(ds, m)
struct dda_softc *ds;
struct mbuf    *m;
{
    register int    s;


#ifdef DDADEBUG
    if (DDADBCH(27, ds->dda_if.if_unit))
	DDALOG(LOG_DEBUG) "dda%d: start_supr\n", ds->dda_if.if_unit DDAELOG;
#endif DDADEBUG

    if (IF_QFULL(&(ds->dda_cb[0].dc_oq))) {
	DMESG(ds->dda_if.if_unit, 27,
	(DDALOG(LOG_ERR) "dda%d: supervisory channel overflow (maxlen=%d)\n",
	 ds->dda_if.if_unit, ds->dda_cb[0].dc_oq.ifq_maxlen DDAELOG));
	ds->dda_cb[0].dc_oq.ifq_maxlen += ds->dda_cb[0].dc_oq.ifq_maxlen;
    }
/*
 *  Raise priority whenever you touch dc_oq.  
 *  We do not want to be interrupted in the middle of adding
 *  an mbuf to the output queue because the interrupt may indicate
 *  a condition that will cause the mbuf to be freed.
 *  (The mbufs are freed on receipt of a line status msg, restart,
 *  clear, or reset.)
 */
    s = splimp();
#ifdef DDA_PAD_OR_RAW
    m->m_dat[MLEN - 1] = 0;
#endif
    IF_ENQUEUE(&(ds->dda_cb[0].dc_oq), m);
    splx(s);
    dda_start(ds, &(ds->dda_cb[0]));
}


/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%				ABORT_IO()   		               %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*	Abort outstanding I/O upon receipt of a line status message, 	 */
/*	restart, clear, or reset.                                        */
/*	The contents of the output queue (dc_oq) is cleared for each     */
/*	lcn;  all I/O queued on either the read or write queue           */
/*	(dc_rchan and dc_wchan) is marked invalid; all I/O queued on     */
/*	the sioq is marked invalid;                                      */
/*                                                                       */
/*  Call:          abort_io()        			                 */
/*  Argument:      none						         */
/*  Returns:       nothing                                               */
/*  Called by:                                                           */
/*  Calls to:      IF_DEQUEUE()                                          */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
PRIVATE void
abort_io(unit, lcn)
int             unit, lcn;
{
    register struct dda_cb *dc;
    register struct dda_softc *ds = &dda_softc[unit];
    register struct hdx_chan *hc;
    register struct mbuf *m;
    register int    lchan;
    register int    s;
    register struct hdx_chan *ptr;
    int             start, end;

    /* set up range of lcns affected */
    if (lcn == ALL_CHANS) {
	start = 1;
	end = nddach[unit];
    } else
	start = end = lcn;
#ifdef DDADEBUG
    if (DDADBCH(28, unit))
	DDALOG(LOG_DEBUG) "dda%d: abort_io on lcn's %d - %d\n",
	    unit, start, end DDAELOG;
#endif DDADEBUG
    s = splimp();
/*
 * Invalidate writes on the sioq for specified channel(s)
 */
    if (ptr = ds->dda_sioq.sq_head)
	for (; ptr; ptr = ptr->hc_next)	/* scan sioq */
	    if ((ptr->hc_chan & 0x01) &&
		((lcn == ALL_CHANS) || (lcn == ptr->hc_chan >> 1))
		&& (ptr->hc_chan != 1)) {
#ifdef DDADEBUG
		if (DDADBCH(28, unit))
		    DDALOG(LOG_DEBUG)
			"dda%d: abort_io--invalidating sioq lcn %d\n",
			unit, ptr->hc_chan >> 1 DDAELOG;
#endif DDADEBUG
		ptr->hc_inv |= INVALID_MBUF;
	    }
/*
 * For each selected lcn, clear the output queue and
 * add an hdx struct to the sioq that will generate an
 * abort.
 */
    for (lchan = start; lchan <= end; lchan++) {	/* for selected LCNs */
	dc = &dda_softc[unit].dda_cb[lchan];
	hc = &dc->dc_wchan;
	while (dc->dc_oq.ifq_len) {
	    IF_DEQUEUE(&dc->dc_oq, m);
	    m_freem(m);
	}

	if (hc->hc_mbuf && !(hc->hc_inv & INVALID_MBUF)) {
	    if (dc->dc_flags & DC_OBUSY) {	/* output pending */
#ifdef DDADEBUG
		if (DDADBCH(28, unit))
		    DDALOG(LOG_DEBUG)
			"dda%d: abort_io--queueing abort: lcn %d\n",
			unit, lchan DDAELOG;
#endif DDADEBUG

		hc->hc_inv |= INVALID_MBUF;
		hc->hc_func = DDAABT;
/*
 * Add to the sioq
 */
		dda_wrq(ds, hc, DDAABT);
	    }
	}
    }
    splx(s);
}

#ifdef DDADEBUG


/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                            PRT_BYTES()                            %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine is used to print a label, followed by the contents of */
/*	a buffer in hex, 16 bytes per line.  Each line is preceded by	 */
/*	the device name and unit number.				 */
/*                                                                       */
/*  Call:          prt_bytes(unit, label, bp, cnt)			 */
/*  Argument:      unit: dda unit number to be displayed		 */
/*		   label: pointer to string to be displayed		 */
/*		   bp:  pointer to the buffer to be dumped		 */
/*                 cnt: number of bytes in buffer			 */
/*  Returns:       nothing                                               */
/*  Called by:     dda_data()                                            */
/*                 dda_supr()                                            */
/*                 supr_msg()                                            */
/*  Calls to:      DDALOG()                                              */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
PRIVATE void
prt_bytes(unit, label, bp, cnt)
int             unit;
char           *label;
u_char         *bp;
int             cnt;
{
    char            hexbuf[50];	/* (worst case: 3 * 16 + 1 = 49 bytes) */
    char           *p;
    int             i;
    static char     hex[] = "0123456789abcdef";

    DDALOG(LOG_DEBUG) "dda%d: %s\n", unit, label DDAELOG;
    while (cnt > 0) {
	i = (cnt > 16) ? 16 : cnt;
	cnt -= i;
	p = hexbuf;
	while (--i >= 0) {
	    *p++ = ' ';
	    *p++ = hex[*bp >> 4];
	    *p++ = hex[*bp++ & 0x0f];
	}
	*p++ = '\0';
	DDALOG(LOG_DEBUG) "dda%d: %s\n", unit, hexbuf DDAELOG;
    }
}

#endif



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                            FMT_X25()                              %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine is used to format an X.25 address for inclusion in	 */
/*    an error message.  The previous return value is invalidated each	 */
/*    time the function is called, as it is stored in a static buffer	 */
/*  Note:          The X.25 address is apparently sometimes stored in    */
/*                 BCD, and other times (PDN mode) in ASCII.  So we mask */
/*                 off the high order bits to make ourselves immune.	 */
/*  Call:          fmt_x25(bp, cnt)                                      */
/*  Argument:      bp:  pointer to the string                            */
/*                 cnt: number of bytes (usually from address[0])        */
/*  Returns:       pointer to an internal buffer containing the string;	 */
/*		   string is 1 to 15 digits, null-terminated.		 */
/*  Called by:     make_x25_call()                                       */
/*                 supr_msg()                                            */
/*                 convert_x25_addr()					 */
/*  Calls to:      none							 */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
PRIVATE char *
fmt_x25(bp, cnt)
register u_char *bp;
register int    cnt;
{
    char           *p;
    static char     x25buf[20];	/* worst case is 15 digits plus trailing null */

    /* (Don't put this on the stack!) */
    p = x25buf;
    if (cnt >= sizeof(x25buf))
	cnt = sizeof(x25buf) - 1;	/* (oops!) */
    while (cnt--)
	*p++ = (*bp++ & 0x0f) + '0';
    *p++ = '\0';
    return (x25buf);
}

#ifdef DDA_HISTOGRAM
/*----------------------- HISTOGRAM SUPPORT ---------------------------------*/


/* the histogram array */
struct timeval  histogram[NDDA][HISTSIZE];

/* these two structures save the time of the last change in the state of the
 * lcn table or the board status.
 */

struct timeval  last_lcn_time[NDDA] = {0L, 0L};
struct timeval  last_brd_time[NDDA] = {0L, 0L};

/* h_lcn_level: the current number of active lcns */
int             h_lcn_level[NDDA] = {0};

/*#define DDA_HIST_DEBUG 1     /* set this to debug history features */


/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                           HIST_INIT()                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine initializes the histogram facility when coming up or  */
/*    after a reset.                                                     */
/*  Call:          hist_init(unit,reset)                                 */
/*  Argument:      unit - board number to initialize.                    */
/*                 reset - set to 1 to force an init.                    */
/*  Returns:       nothing.                                              */
/*  Called by:     ddaioctl()                                            */
/*  Calls to:      microtime()                                           */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
hist_init(unit, reset)
int             unit;
int             reset;
{
    int             s;
    register int    i;
    struct dda_cb  *dc;

    if (last_lcn_time[unit].tv_sec != 0L && !reset)
	return;			/* histogram for this unit already enabled */
    bzero(histogram[unit], sizeof(struct timeval) * HISTSIZE);
    h_lcn_level[unit] = 0;
    dc = dda_softc[unit].dda_cb;
    s = splimp();
    for (i = 0; i < NDDACH + 1; i++) {
	if (dc++->dc_state == LC_DATA_IDLE)
	    h_lcn_level[unit]++;
    }
    splx(s);
    microtime(&histogram[unit][H_START]);
#ifdef DDA_HIST_DEBUG
    DDALOG(LOG_DEBUG) "hist_init: starting at level %d\n",
	h_lcn_level[unit] DDAELOG;
#endif
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                     HIST_LCN_STATE()                              %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine changes the histogram depending on how the state of   */
/*    a channel has changed.                                             */
/*  Call:          hist_lcn_state(unit, old_state, new_state)            */
/*  Argument:      old_state: the old state of the lcn.                  */
/*                 new_state: the state the lcn is changing to.          */
/*                 unit: unit this applies to                            */
/*  Returns:       nothing.                                              */
/*  Called by:                                                           */
/*  Calls to:      timevalsub(), timevaladd(), microtime()               */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
hist_lcn_state(unit, old_state, new_state)
int             unit;
u_char          old_state;
u_char          new_state;
{
    struct timeval  tv, tmpv;

    /*
     * this structure for determining state transitions is much more general
     * than is necessary right now.  However it allows easy changes to the
     * state transition table for the histogram so I will leave it in until
     * it settles down 
     */
    switch (old_state) {
      case LC_DATA_IDLE:
	switch (new_state) {
	  case LC_DATA_IDLE:
	    break;
	  default:		/* all other states */
	    microtime(&tv);
	    tmpv = tv;
	    timevalsub(&tv, &last_lcn_time[unit]);
#ifdef DDA_HIST_DEBUG
	    DDALOG(LOG_DEBUG) "hist_lcn_state: adding %ld.%ld to level %d--\n",
		tv.tv_sec, tv.tv_usec, h_lcn_level[unit] DDAELOG;
#endif
	    timevaladd(&histogram[unit][h_lcn_level[unit]], &tv);
	    last_lcn_time[unit] = tmpv;
	    if (--h_lcn_level[unit] < 0)	/* safety net for driver
						 * errors */
		h_lcn_level[unit] = 0;
	    break;
	}
	break;
      default:
	switch (new_state) {
	  case LC_DATA_IDLE:
	    microtime(&tv);
	    tmpv = tv;
	    timevalsub(&tv, &last_lcn_time[unit]);
#ifdef DDA_HIST_DEBUG
	    DDALOG(LOG_DEBUG) "hist_lcn_state: adding %ld.%ld to level %d++\n",
		tv.tv_sec, tv.tv_usec, h_lcn_level[unit] DDAELOG;
#endif
	    timevaladd(&histogram[unit][h_lcn_level[unit]], &tv);
	    last_lcn_time[unit] = tmpv;
	    if (++h_lcn_level[unit] > NDDACH)	/* safety net for driver
						 * errors */
		h_lcn_level[unit] = NDDACH;
	    break;
	  default:		/* all other states */
	    break;
	}
	break;
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                     HIST_ALL_LCNS()                               %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine changes the histogram when the state of all the lcns  */
/*    are changed as a group.                                            */
/*  Call:          hist_lcn_state(unit, state)                           */
/*  Argument:      state: state that all lcn are going to.  Currently not*/
/*                        used.                                          */
/*                 unit: unit this applies to                            */
/*  Returns:       nothing.                                              */
/*  Called by:                                                           */
/*  Calls to:      timevalsub(), timevaladd(), microtime()               */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
hist_all_lcns(unit, state)
int             unit, state;
{
    struct timeval  tmpv, tv;

#ifdef lint
    state = state;
#endif
    if (last_brd_time[unit].tv_sec == 0L
	|| last_lcn_time[unit].tv_sec == 0L)
	return;			/* see if we have initialized yet */
    microtime(&tv);
    tmpv = tv;
    timevalsub(&tv, &last_lcn_time[unit]);
#ifdef DDA_HIST_DEBUG
    DDALOG(LOG_DEBUG) "hist_all_lcns: adding %ld.%ld to level %d\n",
	tv.tv_sec, tv.tv_usec, h_lcn_level[unit] DDAELOG;
#endif
    timevaladd(&histogram[unit][h_lcn_level[unit]], &tv);
    last_lcn_time[unit] = tmpv;
    h_lcn_level[unit] = 0;
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                     HIST_LINK_STATE()                             %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine changes the histogram depending on how the state of   */
/*    the link has changed.                                              */
/*  Call:          hist_link_state(old_state, new_state)                 */
/*  Argument:      old_state: the old state of the link.                 */
/*                 new_state: the state the link is changing to.         */
/*                 unit: unit this applies to                            */
/*  Returns:       nothing.                                              */
/*  Called by:                                                           */
/*  Calls to:      timevalsub(), timevaladd(), microtime()               */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
hist_link_state(unit, old_state, new_state)
int             unit;
u_char          old_state;
u_char          new_state;
{
    struct timeval  tv, tmpv;

    /*
     * this structure for determining state transitions is much more general
     * than is necessary right now.  However it allows easy changes to the
     * state transition table for the histogram so I will leave it in until
     * it settles down 
     */
    switch (old_state) {
      case S_LINK_UP:
	switch (new_state) {
	  case S_LINK_UP:
	    break;
	  default:		/* all other states */
#ifdef DDA_HIST_DEBUG
	    DDALOG(LOG_DEBUG) "hist_link_state: link down\n" DDAELOG;
#endif
	    microtime(&tv);
	    tmpv = tv;
	    timevalsub(&tv, &last_lcn_time[unit]);
	    timevaladd(&histogram[unit][h_lcn_level[unit]], &tv);
	    tv = tmpv;
	    timevalsub(&tv, &last_brd_time[unit]);
	    timevaladd(&histogram[unit][H_LINK_UP], &tv);
	    last_brd_time[unit].tv_sec = 0L;
	    break;
	}
	break;
      default:			/* all other states */
	switch (new_state) {
	  case S_LINK_UP:
#ifdef DDA_HIST_DEBUG
	    DDALOG(LOG_DEBUG) "hist_link_state: link up\n" DDAELOG;
#endif
	    microtime(&last_brd_time[unit]);

	    /*
	     * reset last_lcn_time so 0 entry will not accumulate the time
	     * that we were down 
	     */
	    last_lcn_time[unit] = last_brd_time[unit];
	    break;
	  default:
	    break;
	}
	break;
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                         HIST_READ()                               %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine prepares the histogram table for reading by making    */
/*    all entries current.                                               */
/*  Call:          hist_read(unit)                                       */
/*  Argument:      unit : board to use.                                  */
/*  Returns:       nothing                                               */
/*  Called by:     ddaioctl()                                            */
/*  Calls to:      timevalsub(), timevaladd(), microtime()               */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE void
hist_read(unit)
int             unit;
{
    struct timeval  tmpv, tv;

    microtime(&tv);
    tmpv = tv;
    histogram[unit][H_END] = tmpv;
    histogram[unit][H_TMO].tv_sec = tmo_data_idle * DDA_TIMEOUT;
    histogram[unit][H_TMO].tv_usec = 0L;
    if (last_brd_time[unit].tv_sec) {
	timevalsub(&tv, &last_lcn_time[unit]);
#ifdef DDA_HIST_DEBUG
	DDALOG(LOG_DEBUG) "hist_read: adding %ld.%ld to level %d\n",
	    tv.tv_sec, tv.tv_usec, h_lcn_level[unit] DDAELOG;
#endif
	timevaladd(&histogram[unit][h_lcn_level[unit]], &tv);
	last_lcn_time[unit] = tmpv;
	tv = tmpv;
	timevalsub(&tv, &last_brd_time[unit]);
	timevaladd(&histogram[unit][H_LINK_UP], &tv);
	last_brd_time[unit] = tmpv;
    }
}



/*@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*%%                         HIST_COPYOUT()                            %%*/
/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/*                                                                       */
/*  Purpose:                                                             */
/*                                                                       */
/*    This routine prepares the histogram table for reading by making    */
/*    all entries current.                                               */
/*  Call:          hist_copyout(unit, to)                                */
/*  Argument:      unit : board to use.                                  */
/*                 to   : address in user space to copy to.              */
/*  Returns:       return value from copyout                             */
/*  Called by:     ddaioctl()                                            */
/*  Calls to:      copyout()                                             */
/*                                                                       */
/*##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

PRIVATE int
hist_copyout(unit, to)
int             unit;
caddr_t         to;
{
    return ((copyout(histogram[unit], to, sizeof(struct timeval) * HISTSIZE)));
}

#endif DDA_HISTOGRAM

#ifdef DDA_PAD_OR_RAW

#if ACC_BSD > 42
#  include "uba.h"
#  include "bk.h"
#  include "conf.h"
#  include "proc.h"
#  include "tty.h"
#  include "map.h"
#  include "vm.h"
#  include "bkmac.h"
#  include "clist.h"
#  include "file.h"
#  include "uio.h"
#endif

#if ACC_BSD == 42 || ACC_ULTRIX > 00
#  include "bk.h"
#  include "../h/conf.h"
#  include "../h/proc.h"
#  include "../h/tty.h"
#  include "../h/map.h"
#  include "../h/vm.h"
#  if ACC_ULTRIX > 12
#    include "uba.h"
#  endif
#  include "../h/bk.h"
#  ifdef SIMULATION
#    include "Clist.h"
#  else
#    include "../h/clist.h"
#  endif
#  include "../h/file.h"
#  include "../h/uio.h"
#endif

PRIVATE int
dda_decode_type(ds, p)
struct dda_softc *ds;
u_char         *p;
{
    register u_char *cp;
    int             i, usrlen;

#ifdef DDADEBUG
    if (DDADBCH(20, ds->dda_if.if_unit)) {
	printf(" dda_decode_type():  p[0]= %x ", *p);
    }
#endif DDADEBUG

    switch (p[0]) {
      case LINE_STATUS:	/* link status msg */
      case RESTART:		/* restart received */
      case RSTRT_ACK:		/* restart ack */
      case STATRESP:		/* Statistics Response from FEP */
      case CLEARVC:		/* clear by VCN */
	return (0);
      case RESET:		/* X25 reset */
	return (1);
      case ANSWER:
      case CLEARLC:
      case INTERRUPT:
      case INTR_ACK:
	i = p[1] / 2;		/* get lcn */
	if (ds->dda_cb[i].dc_flags & (DC_X29 | DC_X29W))
	    return (1);
	else if (ds->dda_cb[i].dc_flags & (DC_RAW))
	    return (2);
	else
	    return (0);
    }
    if (p[0] != RING) {		/* let standard dda handle it */
	return (0);
    }
    cp = p + 4;			/* skip over code, lcn, vcn and count in
				 * (ring?) answer message */
    /* cp now points to length of called address */
    cp += *cp + 1;		/* skip over called address and length byte */
    /* cp now points to length of calling address */
    cp += *cp + 1;		/* skip over calling address and length byte */
    /* cp now points to length of protocol */
    if (*cp == 0)
	return (0);

    usrlen = *cp++;
    if (usrlen) {
#ifdef DDA_RAWOPT
	if (pi_circuit_to_handle_protocol(*cp))
	    return (2);
#endif
#ifdef DDADEBUG
	if (DDADBCH(20, ds->dda_if.if_unit)) {
	    printf(" dda_decode_type():  return value = %x ", *cp);
	}
#endif DDADEBUG
	switch (*cp) {
	  case X25_PROTO_IP:
	    return (0);
	  case X25_PROTO_X29:
	    return (1);
	  default:
	    return (2);
	}
    } else
	return (0);
}
#endif DDA_PAD_OR_RAW

#ifdef SIMULATION
#  ifdef DDA_PADOPT
#    include "if_x29.c"
#  endif
#  ifdef DDA_RAWOPT
#    include "if_pi.c"
#  endif
#else
#  ifdef DDA_PADOPT
#    if ACC_VMS > 00
#      include "../vaxif/if_vmsx29.c"
#    else
#      include "../vaxif/if_x29.c"
#    endif
#  endif
#  ifdef DDA_RAWOPT
#    include "../vaxif/if_pi.c"
#  endif
#endif

#ifdef DDA_MSGQ
u_char          ddamsgq[MSGQSIZE];
PRIVATE u_char  *mqptr = 0;

#define MSGQEND	(ddamsgq+MSGQSIZE)

dda_mqstr(s)
char           *s;
{
    if (mqptr == 0)
	mqptr = ddamsgq;
    while (*s) {
	*mqptr++ = *s++;
	if (mqptr >= MSGQEND)
	    mqptr = ddamsgq;
    }
    *mqptr = '\0';
}

dda_mqnum(num, type)
int             num, type;
{
    if (mqptr == 0)
	mqptr = ddamsgq;
    if ((mqptr + sizeof(int) + 2) >= MSGQEND)
	mqptr = ddamsgq;
    *mqptr++ = type;
    *((int *) mqptr) = num;
    mqptr += sizeof(int);
    *mqptr = '\0';
}

#endif DDA_MSGQ

/* link in support for steve's test-jig */
#ifdef	SIMULATION
#include "if_dda_sim.c"
#endif

/*
		Revision History:

18-Dec-87: V3.0 - Brad Engstrom
	Added the -t flag to acpconfig and the 't' case in ddaioctl to allow
	setting of the idle circuit timeout.
	The constant TMO_DATA_IDLE was changed to a variable called
	tmo_data_idle.
11-Mar-88: V3.0 - Brad Engstrom
	Modified the history routine to return the current value of the
	timeout. Also fixed bug so that level 0 records amount of time 0
	circuits were in use only when link is up.
11-Mar-88: V3.0 - Brad Engstrom
	Changed handling of supervisor channel overflows to double the max q
	length each time it overflows.  This Will prevent a flood of console
	messages while still notifying the user that there has been an
	overflow.
21-Mar-88: V3.0 - Brad Engstrom
	Fixed bug in writing the facilities field for packet and window size
	negotiation.  This was in the routine make X.25 call.  Previously
	constants were used to index into the facilities buffer now offsets
	from the current facilities length are used.
12-Apr-88: V3.0 - Brad Engstrom
	Added ability to handle class b and class c addressing.  The changes
	affect locate_x25_lcn, convert_x25_addr, and convert_ip_addr.  The
	modifications came from fixes sent to Wollongong by Lars Poulson.
12-Apr-88: V3.0 - Brad Engstrom
	Made modifications so the driver will work under Ultrix or BSD. In
	cases where there are differences between 4.3 and 4.2 bsd (shown by
	#ifdef BSD4_3) Ultrix 1.2 is exactly like a 4.2 system. Ultrix 2.0 is
	like 4.3 in most cases. New macros were added to distinquish between
	systems.  These are BSD4_2 and BSD43_OR_ULTRIX20.
13-Apr-88: V3.0 - Brad Engstrom
	ddareset() was called from ddaintb without arguments.  This could
	cause ddareset to return without doing anything. Proper arguments were
	inserted.  In ddaioctl the priority level s may be used without being
	set.  This was fixed.
18-Apr-88: V3.0 - Brad Engstrom
	Added the use of a key field in the dda_cb structure.  Previously the
	dc_inaddr field was used both for printing the ip address (-l command)
	and for searching for circuits that were open to a destination.  Using
	this for a cicuit matching address means that the network and local
	host fields needed to be masked off, thus making this field less
	usefull for printing.  Now two fields are used dc_inaddr is used for
	printing.  dc_key is used for circuit matching.  In PDN mode the
	full ip address is used as the key.  In DDN mode just the imp number
	and host(port) number are used.
18-Apr-88: V3.0 - Brad Engstrom
	Made histogram facilities a compile time option.  The histogram is
	enabled if DDA_HISTOGRAM is defined.  The facilities are always
	disabled when using 4.2 or ULTRIX 1.2 as the kernel does not have the
	proper support routines available.
22-Apr-88: V3.0 - Brad Engstrom
	Added new option to -v command to set the dda_db_unit variable.
22-Apr-88: V3.0 - Brad Engstrom
	Added the DMESG macro and the msgbits array to allow selective
	disabling of driver error messages.  To enable or disable an error
	message the -c command of acpconfig is used. The msgbits array holds
	info about whether each message is enabled or disabled.  Setting a bit
	to 1 disables a message.  Clearing a bit to 0 enables a message.
	All messages start as enabled.
22-Apr-88: V3.0 - Brad Engstrom
	Added check for DDAMAINT_BRD in probe routine.  If DDAMAINT_BRD is
	defined then assume we are using a maintenence board so don't try to
	find the firmware id because it won't be there. Fake info that was
	supposed to be contained in the firmware id.
25-Apr-88: V3.0 - Brad Engstrom
	Added check in locate_x25_lcn to see if state of lc is LC_CALL_PENDING
	or LC_DATA_IDLE in the loop that looks for an already open lc.  This
	will prevent an address of 0.0.0.0 from matching a circuit that is not
	in use.  If the address is invalid then the imp will kick it out.
26-Apr-88: V3.0 - Brad Engstrom
	Changed the -n command case so that a command of the form "-n 0" will
	return the number of channels currently available.  This will be used
	by the -l command and possible by the -h command to determine the
	number of available circuits.
10-May-88: V3.0 - Brad Engstrom
	Made all occurences of the length of and X.25 address refer to the
	constants MAXADDRLEN and MINADDRLEN defined in if_ddavar.h.  These
	constants include the 1 byte  for encoding the length.
02-Jun-88: V3.0 - Brad Engstrom
	Change the check for the firmware revision level to 2.2 for the -e
	command.  This command will crash [56]250s that don't have at least
	v2.2 firmware.
12-Jul-88: V3.0 - Brad Engstrom
	Deleted case for class_b_c addressing.
20-Jul-88: V3.0 - Brad Engstrom
	Fixed bug in parsing facilities that would cause the kernel to hang.
	The bug was not incrmenting pointers when an urecognized 2 octet
	facility was seen.  Fixes were applied to send_supr() and
	decode_answer()
30-Aug-88: V4.0 - Brad Engstrom
	Modified driver to support X.29 and a programmers interface.  Includes
	files if_x29.c, if_pi.c, and if_pivar.h
30-Aug-88: V4.0 - Brad Engstrom
	Added support for debug logging under the control of the DDA_MSGQ
	define.   Information is extracted using the new -p command of
	acpconfig.
30-Aug-88: V4.0 - Brad Engstrom
	Modified start_chan to check the ready bit before touching the
	comregs.  Also modified dda_rrq and dda_wrq to raise ipl before
	touching the sioq.  These changes fixed a bug where the FE was losing
	I/O requests.
20-Oct-88: V4.0 - Steve Johnson
	Added SIMULATION #ifdef for simulation support
08-Jan-89: V4.1 - Steve Johnson
	MERGE 4.0 and 3.1
10-Oct-88: V3.1 - Charles Carvalho
	Replace prt_x25 with fmt_x25, which returns a pointer to a formatted
	message instead of printing its data; this allows error messages to be
	output with a single call to DDALOG (or syslog).  Move prt_addr
	inline, for same reason.  Add IP address to some error messages;
	trim excess text from some error messages.  Allocate channels
	for incoming calls from lowest channel up; we do linear searches of
	the lcn table, so it's to our advantage to use the lowest numbers for
	all active circuits. (The lcn is not related to the virtual circuit
	number, so there is no need to allocate incoming channels from the
	top down.)  Modify prt_bytes to take unit number and descriptive
	string to be printed along with the buffer and byte count; it now
	formats up to 16 bytes at a time and prints a full line with each call
	to DDALOG rather than calling DDALOG for each byte.
17-Oct-88: V3.1 - Charles Carvalho
	Add definitions for DDALOG and DDAELOG, which translate into a call to
	DDALOG() or log().
26-Oct-88: V3.1 - Charles Carvalho
	Change index for 'v' ioctl to preserve compatibility with previous 
	versions.  Restrict maximum window size to 127, not 128.
7-Nov-88: V3.2 - Charles Carvalho
	Fix check for no free circuits when processing RING
17-Feb-89: V4.3.0 - Paul Traina
	Added TGV changes for Multinet.
8-Mar-89: V4.3.1 - Steve Johnson
	Installed 'Q' ioctl to support obtaining an internal trace log used
	for debugging only -- not documented for general user.  acpconfig
	dda0 -q 2 dumps 256 bytes from the dda_debug_silo[] array
13-Mar-89: V4.3.2 - Paul Traina
	Updated Multinet support.
17-Apr-89: V4.3.3 - Steve Johnson
	Split bus and simulation related code out to included files for first
	shot at 7000 and tahoe design.  Don't reset timeout counter in
	dda_data() unless link really is in idle state.
28-Apr-89: V4.3.4 - Paul Traina
	Modified changes of 17-Apr-89, added minimal tahoe support until
	driver modified to use 4.3uba transfers.
	Fixed timeout fix of 17-Apr-89 to do what was intended.
	Fixed code dealing with maintenance board, reformatted with indent
	to repair readablility.
09-May-89: V4.3.5 - Paul Traina
	Minimal tahoe support completed,  based on BSD4_3TAHOE define which
	must be uncommented manually.  Finalizing for ECO.
24-May-89: V4.3.6 - Paul Traina
	Ultrix 3.0 support added.  Revised 4.3 tahoe support for automatic
	invocation.
	*** NOTE: one of the three OS defines (ACC_BSD, ACC_ULTRIX, ACC_VMS)
	    in if_dda.c must be set to a non-zero value for the driver to
	    compile.
	Attempting multiple-os support based upon weird variables from include
	files is not acceptable with the latest proliferation of OS versions.
20-Jun-89: V4.3.7 - Paul Traina
	Removed crufty old debug stuff and integrated it with the log-message
	code.  Now X29 and PI modules can be debuged properly (no #if 0's!).
22-Jun-89:	  - Paul Traina
	Diddled ring-decode logic to check for proper ring packet decoding
	before attempting to find a free lcn.  This will make it easier to deal
	with the race condition with find_free_lcn().
	Modified ACC os specific equates to be set as options in the config
	file.  This way, most users won't ever edit if_dda.c.
18-Jul-89:	  - Paul Traina
	Driver will no longer return errors if duplicate address-translation
	entries are made.  Errors will only happen if a redefiniton is
	attempted.
	Moved dc_key.ttyline out of union, creating dc_line.
26-Jul-89:	  - Paul Traina f/Brad Engstrom
	Added support for called user-data field (two new params to
	make_x25_call) to support extended pad mode in the X.29 module.
01-Aug-89:	  - Paul Traina
	Made ddamsgs uninitialized -- it gets inited in ddaattach now.
03-Aug-89:	  - Paul Traina
	Changed hist_copyout definition to PRIVATE.
15-Aug-89:	  - Paul Traina
	Made dda_softc and dda_iobuf non-private.
18-Aug-89:	  - Paul Traina
	Somehow, ddareset was removed from the 'z' ioctl.
28-Aug-89:	  - Paul Traina
	Changed make_x25_call so that it checks length of data to be stuffed
	into the mbuf before actually copying data in.  Removed udlen and
	ud parameters to the routine, as the public areas will be plugged
	with data before being called.  (May need to splimp()).
22-Sep-89:	  - Paul Traina
	The order of the 'v' ioctl parameters was screwed up.  This caused
	window and packet size setting to fail.
23-Oct-89:	  - Paul Traina
	Added further support for Steve's yetchy simulation.  Updated main
	module to work with BI version of dda board.
29-Oct-89:	  - Paul Traina
	Acpconfig inconsistancy (again): removed the 'p', and 'Q' ioctls.
	Since all of these are queries, I placed them under the 'q' ioctl
	with a new switch.  Some day we should just scrap the whole mess
	and design a proper ioctl interface.
11-Nov-89:	  - Paul Traina
	Moved rrq/wrq routines into bus modules because we can do several
	queue reads and writes when working with the BI.
*/
#endif NDDA > 0