NetBSD-5.0.2/sys/dev/pci/if_lmc.c

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

/* $NetBSD: if_lmc.c,v 1.43 2008/06/27 00:53:41 gmcgarry Exp $ */

/*-
 * Copyright (c) 2002-2006 David Boggs. <boggs@boggs.palo-alto.ca.us>
 * All rights reserved.
 *
 * BSD LICENSE:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * GNU GENERAL PUBLIC LICENSE:
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * DESCRIPTION:
 *
 * This is an open-source Unix device driver for PCI-bus WAN interface cards.
 * It sends and receives packets in HDLC frames over synchronous links.
 * A generic PC plus Unix plus some LMC cards makes an OPEN router.
 * This driver works with FreeBSD, NetBSD, OpenBSD, BSD/OS and Linux.
 * It has been tested on i386 (32-bit little-end), PowerPC (32-bit
 * big-end), Sparc (64-bit big-end), and Alpha (64-bit little-end)
 * architectures.
 *
 * HISTORY AND AUTHORS:
 *
 * Ron Crane had the neat idea to use a Fast Ethernet chip as a PCI
 * interface and add an Ethernet-to-HDLC gate array to make a WAN card.
 * David Boggs designed the Ethernet-to-HDLC gate arrays and PC cards.
 * We did this at our company, LAN Media Corporation (LMC).
 * SBE Corp aquired LMC and continues to make the cards.
 *
 * Since the cards use Tulip Ethernet chips, we started with Matt Thomas'
 * ubiquitous "de" driver.  Michael Graff stripped out the Ethernet stuff
 * and added HSSI stuff.  Basil Gunn ported it to Solaris (lost) and
 * Rob Braun ported it to Linux.  Andrew Stanley-Jones added support
 * for three more cards and wrote the first version of lmcconfig.
 * During 2002-5 David Boggs rewrote it and now feels responsible for it.
 *
 * RESPONSIBLE INDIVIDUAL:
 *
 * Send bug reports and improvements to <boggs@boggs.palo-alto.ca.us>.
 */
#if defined(__FreeBSD__)
# include <sys/param.h>	/* OS version */
# include "opt_inet.h"	/* INET */
# include "opt_inet6.h"	/* INET6 */
# include "opt_netgraph.h" /* NETGRAPH */
# if defined(HAVE_KERNEL_OPTION_HEADERS)
#  include "opt_device_polling.h" /* DEVICE_POLLING */
# else
#  include "opt_global.h" /* ALTQ, DEVICE_POLLING */
# endif
# include "opt_bpf.h"	/* DEV_BPF */
# define NBPFILTER DEV_BPF
# define IOREF_CSR 1	/* 1=IO refs; 0=MEM refs */
# define IFNET 1
# define NETDEV 0
# define NAPI 0
# define SPPP 1
# define P2P 0
# define GEN_HDLC 0
# define SYNC_PPP 0
#
# include <sys/systm.h>
# include <sys/kernel.h>
# include <sys/malloc.h>
# include <sys/mbuf.h>
# include <sys/socket.h>
# include <sys/sockio.h>
# include <sys/mutex.h>
# include <sys/module.h>
# include <sys/bus.h>
# include <net/if.h>
# include <net/if_types.h>
# include <net/if_media.h>
# include <net/netisr.h>
# include <sys/bus.h>
# include <machine/resource.h>
# include <machine/clock.h>
# include <sys/rman.h>
# include <vm/vm.h>
# include <vm/pmap.h>
# include <dev/pci/pcivar.h>
# if NETGRAPH
#  include <netgraph/ng_message.h>
#  include <netgraph/netgraph.h>
# endif
# if INET || INET6
#  include <netinet/in.h>
#  include <netinet/in_var.h>
# endif
# if SPPP
#  include <net/if_sppp.h>
# endif
# if NBPFILTER
#  include <net/bpf.h>
# endif
# if !defined(NETGRAPH)
#  define NETGRAPH 0
# endif
# if !defined(DEVICE_POLLING)
#  define DEVICE_POLLING 0
# endif
# if !defined(ALTQ)
#  define ALTQ 0
# endif
/* and finally... */
# include <dev/lmc/if_lmc.h>
#endif /*__FreeBSD__*/

#if defined(__NetBSD__)
# include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_lmc.c,v 1.43 2008/06/27 00:53:41 gmcgarry Exp $");
# include <sys/param.h>	/* OS version */
/* -DLKM is passed on the compiler command line */
# include "opt_inet.h"	/* INET6, INET */
# include "opt_altq_enabled.h" /* ALTQ */
# include "bpfilter.h"	/* NBPFILTER */
# define IOREF_CSR 1	/* 1=IO refs; 0=MEM refs */
# define IFNET 1
# define NETDEV 0
# define NAPI 0
# define SPPP 1
# define P2P 0
# define GEN_HDLC 0
# define SYNC_PPP 0
# define NETGRAPH 0
# define DEVICE_POLLING 0
#
# include <sys/systm.h>
# include <sys/kernel.h>
# include <sys/lkm.h>
# include <sys/mbuf.h>
# include <sys/socket.h>
# include <sys/sockio.h>
# include <sys/device.h>
# include <sys/reboot.h>
# include <sys/kauth.h>
# include <sys/proc.h>
# include <net/if.h>
# include <net/if_types.h>
# include <net/if_media.h>
# include <net/netisr.h>
# include <sys/bus.h>
# include <sys/intr.h>
# include <machine/lock.h>
# include <machine/types.h>
# include <dev/pci/pcivar.h>
# include <uvm/uvm_extern.h>
# if INET || INET6
#  include <netinet/in.h>
#  include <netinet/in_var.h>
# endif
# if SPPP
#  include <net/if_spppvar.h>
# endif
# if NBPFILTER
#  include <net/bpf.h>
# endif
# if !defined(ALTQ)
#  define ALTQ 0
# endif
/* and finally... */
# include "if_lmc.h"
#endif /*__NetBSD__*/

#if defined(__OpenBSD__)
# include <sys/param.h>	/* OS version */
/* -DINET  is passed on the compiler command line */
/* -DINET6 is passed on the compiler command line */
/* -DALTQ  is passed on the compiler command line */
# include "bpfilter.h"	/* NBPFILTER */
# define IOREF_CSR 1	/* 1=IO refs; 0=MEM refs */
# define IFNET 1
# define NAPI 0
# define SPPP 1
# define P2P 0
# define GEN_HDLC 0
# define SYNC_PPP 0
# define NETGRAPH 0
# define DEVICE_POLLING 0
#
# include <sys/systm.h>
# include <sys/kernel.h>
# include <sys/conf.h>
# include <sys/exec.h>
# include <sys/lkm.h>
# include <sys/mbuf.h>
# include <sys/socket.h>
# include <sys/sockio.h>
# include <sys/device.h>
# include <net/if.h>
# include <net/if_types.h>
# include <net/if_media.h>
# include <net/netisr.h>
# include <sys/bus.h>
# include <sys/intr.h>
# include <machine/lock.h>
# include <uvm/uvm_extern.h>
# include <dev/pci/pcivar.h>
# if INET || INET6
#  include <netinet/in.h>
#  include <netinet/in_var.h>
# endif
# if SPPP
#  include <net/if_sppp.h>
# endif
# if NBPFILTER
#  include <net/bpf.h>
# endif
# if !defined(ALTQ)
#  define ALTQ 0
# endif
# undef   NETDEV
# define  NETDEV 0
/* and finally... */
# include "if_lmc.h"
#endif /*__OpenBSD__*/

#if defined(__bsdi__)
# include <sys/param.h>	/* OS version */
/* -DINET  is passed on the compiler command line */
/* -DINET6 is passed on the compiler command line */
/* -DPPP   is passed on the compiler command line */
/* -DCISCO_HDLC is passed on the compiler command line */
/* -DFR    is passed on the compiler command line */
# if PPP || CISCO_HDLC || FR
#  define P2P 1
# else
#  define P2P 0
# endif
# include "bpfilter.h"	/* NBPFILTER */
# define IOREF_CSR 1	/* 1=IO refs; 0=MEM refs */
# define IFNET 1
# define NETDEV 0
# define NAPI 0
# define ALTQ 0
# define SPPP 0
# define GEN_HDLC 0
# define SYNC_PPP 0
# define NETGRAPH 0
# define DEVICE_POLLING 0
#
# include <sys/kernel.h>
# include <sys/malloc.h>
# include <sys/mbuf.h>
# include <sys/socket.h>
# include <sys/sockio.h>
# include <sys/device.h>
# include <sys/reboot.h>
# include <net/if.h>
# include <net/if_types.h>
# include <net/if_media.h>
# include <net/netisr.h>
# include <net/raw_cb.h>
# include <vm/vm.h>
# include <i386/isa/dma.h>
# include <i386/isa/isavar.h>
# include <i386/include/cpu.h>
# include <i386/pci/pci.h>
# if INET || INET6
#  include <netinet/in.h>
#  include <netinet/in_var.h>
# endif
# if P2P
#  include <net/if_p2p.h>
#  include <sys/ttycom.h>
# endif
# if NBPFILTER
#  include <net/bpf.h>
# endif
/* and finally... */
# include "if_lmc.h"
#endif /*__bsdi__*/

#if defined(__linux__)
# include <linux/version.h> /* OS version */
# include <linux/config.h>
# if CONFIG_LANMEDIA_GENHDLC
#  define GEN_HDLC 1
# else
#  define GEN_HDLC 0
# endif
# if CONFIG_LANMEDIA_SYNCPPP
#  define SYNC_PPP 1
# else
#  define SYNC_PPP 0
# endif
# if CONFIG_LANMEDIA_NAPI
#  define NAPI 1
# else
#  define NAPI 0
# endif
# define IOREF_CSR 1	/* 1=IO refs; 0=MEM refs */
# define BSD 0
# define IFNET 0
# define NETDEV 1
# define ALTQ 0
# define P2P 0
# define SPPP 0
# define NETGRAPH 0
# define NBPFILTER 0
# define DEVICE_POLLING 0
#
# include <asm/byteorder.h>
# include <asm/semaphore.h>
# include <asm/uaccess.h>
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/if_arp.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/netdevice.h>
# include <linux/pci.h>
# include <linux/sched.h>
# include <linux/skbuff.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# if GEN_HDLC
#  include <linux/hdlc.h>
# endif
# if SYNC_PPP
#  include <net/syncppp.h>
# endif
/* and finally... */
# include "if_lmc.h"
#endif /* __linux__ */

/* The SROM is a generic 93C46 serial EEPROM (64 words by 16 bits). */
/* Data is set up before the RISING edge of CLK; CLK is parked low. */
static void  /* context: process */
srom_shift_bits(softc_t *sc, u_int32_t data, u_int32_t len)
  {
  u_int32_t csr = READ_CSR(sc, TLP_SROM_MII);
  for (; len>0; len--)
    {  /* MSB first */
    if (data & (1<<(len-1)))
      csr |=  TLP_SROM_DIN;	/* DIN setup */
    else
      csr &= ~TLP_SROM_DIN;	/* DIN setup */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    csr |=  TLP_SROM_CLK;	/* CLK rising edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    csr &= ~TLP_SROM_CLK;	/* CLK falling edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    }
  }

/* Data is sampled on the RISING edge of CLK; CLK is parked low. */
static u_int16_t  /* context: process */
srom_read(softc_t *sc, u_int8_t addr)
  {
  int i;
  u_int32_t csr;
  u_int16_t data;

  /* Enable SROM access. */
  csr = (TLP_SROM_SEL | TLP_SROM_RD | TLP_MII_MDOE);
  WRITE_CSR(sc, TLP_SROM_MII, csr);
  /* CS rising edge prepares SROM for a new cycle. */
  csr |= TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* assert CS */
  srom_shift_bits(sc,  6,   4);		/* issue read cmd */
  srom_shift_bits(sc, addr, 6);		/* issue address */
  for (data=0, i=16; i>=0; i--)		/* read ->17<- bits of data */
    {  /* MSB first */
    csr = READ_CSR(sc, TLP_SROM_MII);	/* DOUT sampled */
    data = (data<<1) | ((csr & TLP_SROM_DOUT) ? 1:0);
    csr |=  TLP_SROM_CLK;		/* CLK rising edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    csr &= ~TLP_SROM_CLK;		/* CLK falling edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    }
  /* Disable SROM access. */
  WRITE_CSR(sc, TLP_SROM_MII, TLP_MII_MDOE);

  return data;
  }

/* The SROM is formatted by the mfgr and should NOT be written! */
/* But lmcconfig can rewrite it in case it gets overwritten somehow. */
static void  /* context: process */
srom_write(softc_t *sc, u_int8_t addr, u_int16_t data)
  {
  u_int32_t csr;
  int i;

  /* Enable SROM access. */
  csr = (TLP_SROM_SEL | TLP_SROM_RD | TLP_MII_MDOE);
  WRITE_CSR(sc, TLP_SROM_MII, csr);

  /* Issue write-enable command. */
  csr |= TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* assert CS */
  srom_shift_bits(sc,  4, 4);		/* issue write enable cmd */
  srom_shift_bits(sc, 63, 6);		/* issue address */
  csr &= ~TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* deassert CS */

  /* Issue erase command. */
  csr |= TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* assert CS */
  srom_shift_bits(sc, 7, 4);		/* issue erase cmd */
  srom_shift_bits(sc, addr, 6);		/* issue address */
  csr &= ~TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* deassert CS */

  /* Issue write command. */
  csr |= TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* assert CS */
  for (i=0; i<10; i++)  /* 100 ms max wait */
    if ((READ_CSR(sc, TLP_SROM_MII) & TLP_SROM_DOUT)==0) SLEEP(10000);
  srom_shift_bits(sc, 5, 4);		/* issue write cmd */
  srom_shift_bits(sc, addr, 6);		/* issue address */
  srom_shift_bits(sc, data, 16);	/* issue data */
  csr &= ~TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* deassert CS */

  /* Issue write-disable command. */
  csr |= TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* assert CS */
  for (i=0; i<10; i++)  /* 100 ms max wait */
    if ((READ_CSR(sc, TLP_SROM_MII) & TLP_SROM_DOUT)==0) SLEEP(10000);
  srom_shift_bits(sc, 4, 4);		/* issue write disable cmd */
  srom_shift_bits(sc, 0, 6);		/* issue address */
  csr &= ~TLP_SROM_CS;
  WRITE_CSR(sc, TLP_SROM_MII, csr);	/* deassert CS */

  /* Disable SROM access. */
  WRITE_CSR(sc, TLP_SROM_MII, TLP_MII_MDOE);
  }

/* Not all cards have BIOS roms. */
/* The BIOS ROM is an AMD 29F010 1Mbit (128K by 8) EEPROM. */
static u_int8_t  /* context: process */
bios_read(softc_t *sc, u_int32_t addr)
  {
  u_int32_t srom_mii;

  /* Load the BIOS rom address register. */
  WRITE_CSR(sc, TLP_BIOS_ROM, addr);

  /* Enable the BIOS rom. */
  srom_mii = TLP_BIOS_SEL | TLP_BIOS_RD | TLP_MII_MDOE;
  WRITE_CSR(sc, TLP_SROM_MII, srom_mii);

  /* Wait at least 20 PCI cycles. */
  DELAY(20);

  /* Read the BIOS rom data. */
  srom_mii = READ_CSR(sc, TLP_SROM_MII);

  /* Disable the BIOS rom. */
  WRITE_CSR(sc, TLP_SROM_MII, TLP_MII_MDOE);

  return (u_int8_t)srom_mii & 0xFF;
  }

static void  /* context: process */
bios_write_phys(softc_t *sc, u_int32_t addr, u_int8_t data)
  {
  u_int32_t srom_mii;

  /* Load the BIOS rom address register. */
  WRITE_CSR(sc, TLP_BIOS_ROM, addr);

  /* Enable the BIOS rom. */
  srom_mii = TLP_BIOS_SEL | TLP_BIOS_WR | TLP_MII_MDOE;

  /* Load the data into the data register. */
  srom_mii = (srom_mii & 0xFFFFFF00) | (data & 0xFF);
  WRITE_CSR(sc, TLP_SROM_MII, srom_mii);

  /* Wait at least 20 PCI cycles. */
  DELAY(20);

  /* Disable the BIOS rom. */
  WRITE_CSR(sc, TLP_SROM_MII, TLP_MII_MDOE);
  }

static void  /* context: process */
bios_write(softc_t *sc, u_int32_t addr, u_int8_t data)
  {
  u_int8_t read_data;

  /* this sequence enables writing */
  bios_write_phys(sc, 0x5555, 0xAA);
  bios_write_phys(sc, 0x2AAA, 0x55);
  bios_write_phys(sc, 0x5555, 0xA0);
  bios_write_phys(sc, addr,   data);

  /* Wait for the write operation to complete. */
  for (;;)  /* interruptible syscall */
    {
    for (;;)
      {
      read_data = bios_read(sc, addr);
      if ((read_data & 0x80) == (data & 0x80)) break;
      if  (read_data & 0x20)
        {  /* Data sheet says read it again. */
        read_data = bios_read(sc, addr);
        if ((read_data & 0x80) == (data & 0x80)) break;
        if (sc->config.debug)
          printf("%s: bios_write() failed; rom addr=0x%x\n",
           NAME_UNIT, addr);
        return;
        }
      }
    read_data = bios_read(sc, addr);
    if (read_data == data) break;
    }
  }

static void  /* context: process */
bios_erase(softc_t *sc)
  {
  unsigned char read_data;

  /* This sequence enables erasing: */
  bios_write_phys(sc, 0x5555, 0xAA);
  bios_write_phys(sc, 0x2AAA, 0x55);
  bios_write_phys(sc, 0x5555, 0x80);
  bios_write_phys(sc, 0x5555, 0xAA);
  bios_write_phys(sc, 0x2AAA, 0x55);
  bios_write_phys(sc, 0x5555, 0x10);

  /* Wait for the erase operation to complete. */
  for (;;) /* interruptible syscall */
    {
    for (;;)
      {
      read_data = bios_read(sc, 0);
      if (read_data & 0x80) break;
      if (read_data & 0x20)
        {  /* Data sheet says read it again. */
        read_data = bios_read(sc, 0);
        if (read_data & 0x80) break;
        if (sc->config.debug)
          printf("%s: bios_erase() failed\n", NAME_UNIT);
        return;
        }
      }
    read_data = bios_read(sc, 0);
    if (read_data == 0xFF) break;
    }
  }

/* MDIO is 3-stated between tranactions. */
/* MDIO is set up before the RISING edge of MDC; MDC is parked low. */
static void  /* context: process */
mii_shift_bits(softc_t *sc, u_int32_t data, u_int32_t len)
  {
  u_int32_t csr = READ_CSR(sc, TLP_SROM_MII);
  for (; len>0; len--)
    {  /* MSB first */
    if (data & (1<<(len-1)))
      csr |=  TLP_MII_MDOUT; /* MDOUT setup */
    else
      csr &= ~TLP_MII_MDOUT; /* MDOUT setup */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    csr |=  TLP_MII_MDC;     /* MDC rising edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    csr &= ~TLP_MII_MDC;     /* MDC falling edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    }
  }

/* The specification for the MII is IEEE Std 802.3 clause 22. */
/* MDIO is sampled on the RISING edge of MDC; MDC is parked low. */
static u_int16_t  /* context: process */
mii_read(softc_t *sc, u_int8_t regad)
  {
  int i;
  u_int32_t csr;
  u_int16_t data = 0;
 
  WRITE_CSR(sc, TLP_SROM_MII, TLP_MII_MDOUT);

  mii_shift_bits(sc, 0xFFFFF, 20);	/* preamble */
  mii_shift_bits(sc, 0xFFFFF, 20);	/* preamble */
  mii_shift_bits(sc, 1, 2);		/* start symbol */
  mii_shift_bits(sc, 2, 2);		/* read op */
  mii_shift_bits(sc, 0, 5);		/* phyad=0 */
  mii_shift_bits(sc, regad, 5);		/* regad */
  csr = READ_CSR(sc, TLP_SROM_MII);
  csr |= TLP_MII_MDOE;
  WRITE_CSR(sc, TLP_SROM_MII, csr);
  mii_shift_bits(sc, 0, 2);		/* turn-around */
  for (i=15; i>=0; i--)			/* data */
    {  /* MSB first */
    csr = READ_CSR(sc, TLP_SROM_MII);	/* MDIN sampled */
    data = (data<<1) | ((csr & TLP_MII_MDIN) ? 1:0);
    csr |=  TLP_MII_MDC;		/* MDC rising edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    csr &= ~TLP_MII_MDC;		/* MDC falling edge */
    WRITE_CSR(sc, TLP_SROM_MII, csr);
    }
  return data;
  }

static void  /* context: process */
mii_write(softc_t *sc, u_int8_t regad, u_int16_t data)
  {
  WRITE_CSR(sc, TLP_SROM_MII, TLP_MII_MDOUT);
  mii_shift_bits(sc, 0xFFFFF, 20);	/* preamble */
  mii_shift_bits(sc, 0xFFFFF, 20);	/* preamble */
  mii_shift_bits(sc, 1, 2);		/* start symbol */
  mii_shift_bits(sc, 1, 2);		/* write op */
  mii_shift_bits(sc, 0, 5);		/* phyad=0 */
  mii_shift_bits(sc, regad, 5);		/* regad */
  mii_shift_bits(sc, 2, 2);		/* turn-around */
  mii_shift_bits(sc, data, 16);		/* data */
  WRITE_CSR(sc, TLP_SROM_MII, TLP_MII_MDOE);
  if (regad == 16) sc->led_state = data; /* a small optimization */
  }

static void
mii16_set_bits(softc_t *sc, u_int16_t bits)
  {
  u_int16_t mii16 = mii_read(sc, 16);
  mii16 |= bits;
  mii_write(sc, 16, mii16);
  }

static void
mii16_clr_bits(softc_t *sc, u_int16_t bits)
  {
  u_int16_t mii16 = mii_read(sc, 16);
  mii16 &= ~bits;
  mii_write(sc, 16, mii16);
  }

static void
mii17_set_bits(softc_t *sc, u_int16_t bits)
  {
  u_int16_t mii17 = mii_read(sc, 17);
  mii17 |= bits;
  mii_write(sc, 17, mii17);
  }

static void
mii17_clr_bits(softc_t *sc, u_int16_t bits)
  {
  u_int16_t mii17 = mii_read(sc, 17);
  mii17 &= ~bits;
  mii_write(sc, 17, mii17);
  }

/*
 * Watchdog code is more readable if it refreshes LEDs
 *  once a second whether they need it or not.
 * But MII refs take 150 uSecs each, so remember the last value
 *  written to MII16 and avoid LED writes that do nothing.
 */

static void
led_off(softc_t *sc, u_int16_t led)
  {
  if ((led & sc->led_state) == led) return;
  mii16_set_bits(sc, led);
  }

static void
led_on(softc_t *sc, u_int16_t led)
  {
  if ((led & sc->led_state) == 0) return;
  mii16_clr_bits(sc, led);
  }

static void
led_inv(softc_t *sc, u_int16_t led)
  {
  u_int16_t mii16 = mii_read(sc, 16);
  mii16 ^= led;
  mii_write(sc, 16, mii16);
  }

/*
 * T1 & T3 framer registers are accessed through MII regs 17 & 18.
 * Write the address to MII reg 17 then R/W data through MII reg 18.
 * The hardware interface is an Intel-style 8-bit muxed A/D bus.
 */
static void
framer_write(softc_t *sc, u_int16_t addr, u_int8_t data)
  {
  mii_write(sc, 17, addr);
  mii_write(sc, 18, data);
  }

static u_int8_t
framer_read(softc_t *sc, u_int16_t addr)
  {
  mii_write(sc, 17, addr);
  return (u_int8_t)mii_read(sc, 18);
  }

/* Tulip's hardware implementation of General Purpose IO
 *   (GPIO) pins makes life difficult for software.
 * Bits 7-0 in the Tulip GPIO CSR are used for two purposes
 *   depending on the state of bit 8.
 * If bit 8 is 0 then bits 7-0 are "data" bits.
 * If bit 8 is 1 then bits 7-0 are "direction" bits.
 * If a direction bit is one, the data bit is an output.
 * The problem is that the direction bits are WRITE-ONLY.
 * Software must remember the direction bits in a shadow copy.
 * (sc->gpio_dir) in order to change some but not all of the bits.
 * All accesses to the Tulip GPIO register use these five procedures.
 */

static void
gpio_make_input(softc_t *sc, u_int32_t bits)
  {
  sc->gpio_dir &= ~bits;
  WRITE_CSR(sc, TLP_GPIO, TLP_GPIO_DIR | (sc->gpio_dir));
  }

static void
gpio_make_output(softc_t *sc, u_int32_t bits)
  {
  sc->gpio_dir |= bits;
  WRITE_CSR(sc, TLP_GPIO, TLP_GPIO_DIR | (sc->gpio_dir));
  }

static u_int32_t
gpio_read(softc_t *sc)
  {
  return READ_CSR(sc, TLP_GPIO);
  }

static void
gpio_set_bits(softc_t *sc, u_int32_t bits)
  {
  WRITE_CSR(sc, TLP_GPIO, (gpio_read(sc) |  bits) & 0xFF);
  }

static void
gpio_clr_bits(softc_t *sc, u_int32_t bits)
  {
  WRITE_CSR(sc, TLP_GPIO, (gpio_read(sc) & ~bits) & 0xFF);
  }

/* Reset ALL of the flip-flops in the gate array to zero. */
/* This does NOT change the gate array programming. */
/* Called during initialization so it must not sleep. */
static void  /* context: kernel (boot) or process (syscall) */
xilinx_reset(softc_t *sc)
  {
  /* Drive RESET low to force initialization. */
  gpio_clr_bits(sc, GPIO_RESET);
  gpio_make_output(sc, GPIO_RESET);

  /* Hold RESET low for more than 10 uSec. */
  DELAY(50);

  /* Done with RESET; make it an input. */
  gpio_make_input(sc,  GPIO_RESET);
  }

/* Load Xilinx gate array program from on-board rom. */
/* This changes the gate array programming. */
static void  /* context: process */
xilinx_load_from_rom(softc_t *sc)
  {
  int i;

  /* Drive MODE low to load from ROM rather than GPIO. */
  gpio_clr_bits(sc, GPIO_MODE);
  gpio_make_output(sc, GPIO_MODE);

  /* Drive DP & RESET low to force configuration. */
  gpio_clr_bits(sc, GPIO_RESET | GPIO_DP);
  gpio_make_output(sc, GPIO_RESET | GPIO_DP);

  /* Hold RESET & DP low for more than 10 uSec. */
  DELAY(50);

  /* Done with RESET & DP; make them inputs. */
  gpio_make_input(sc, GPIO_DP | GPIO_RESET);

  /* BUSY-WAIT for Xilinx chip to configure itself from ROM bits. */
  for (i=0; i<100; i++) /* 1 sec max delay */
    if ((gpio_read(sc) & GPIO_DP)==0) SLEEP(10000);

  /* Done with MODE; make it an input. */
  gpio_make_input(sc, GPIO_MODE);
  }

/* Load the Xilinx gate array program from userland bits. */
/* This changes the gate array programming. */
static int  /* context: process */
xilinx_load_from_file(softc_t *sc, char *addr, u_int32_t len)
  {
  char *data;
  int i, j, error;

  /* Get some pages to hold the Xilinx bits; biggest file is < 6 KB. */
  if (len > 8192) return EFBIG;  /* too big */
  data = malloc(len, M_TEMP, M_WAITOK);
  if (data == NULL) return ENOMEM;

  /* Copy the Xilinx bits from userland. */
  if ((error = copyin(addr, data, len)))
    {
    free(data, M_TEMP);
    return error;
    }

  /* Drive MODE high to load from GPIO rather than ROM. */
  gpio_set_bits(sc, GPIO_MODE);
  gpio_make_output(sc, GPIO_MODE);

  /* Drive DP & RESET low to force configuration. */
  gpio_clr_bits(sc, GPIO_RESET | GPIO_DP);
  gpio_make_output(sc, GPIO_RESET | GPIO_DP);

  /* Hold RESET & DP low for more than 10 uSec. */
  DELAY(50);
  
  /* Done with RESET & DP; make them inputs. */
  gpio_make_input(sc, GPIO_RESET | GPIO_DP);

  /* BUSY-WAIT for Xilinx chip to clear its config memory. */
  gpio_make_input(sc, GPIO_INIT);
  for (i=0; i<10000; i++) /* 1 sec max delay */
    if ((gpio_read(sc) & GPIO_INIT)==0) SLEEP(10000);

  /* Configure CLK and DATA as outputs. */
  gpio_set_bits(sc, GPIO_CLK);  /* park CLK high */
  gpio_make_output(sc, GPIO_CLK | GPIO_DATA);

  /* Write bits to Xilinx; CLK is parked HIGH. */
  /* DATA is set up before the RISING edge of CLK. */
  for (i=0; i<len; i++)
    for (j=0; j<8; j++)
      {  /* LSB first */
      if (data[i] & (1<<j))
        gpio_set_bits(sc, GPIO_DATA); /* DATA setup */
      else
        gpio_clr_bits(sc, GPIO_DATA); /* DATA setup */
      gpio_clr_bits(sc, GPIO_CLK); /* CLK falling edge */
      gpio_set_bits(sc, GPIO_CLK); /* CLK rising edge */
      }

  /* Stop driving all Xilinx-related signals. */
  /* Pullup and pulldown resistors take over. */
  gpio_make_input(sc, GPIO_CLK | GPIO_DATA | GPIO_MODE);

  free(data, M_TEMP);

  return 0;
  }

/* Write fragments of a command into the synthesized oscillator. */
/* DATA is set up before the RISING edge of CLK.  CLK is parked low. */
static void
synth_shift_bits(softc_t *sc, u_int32_t data, u_int32_t len)
  {
  int i;

  for (i=0; i<len; i++)
    { /* LSB first */
    if (data & (1<<i))
      gpio_set_bits(sc, GPIO_DATA); /* DATA setup */
    else
      gpio_clr_bits(sc, GPIO_DATA); /* DATA setup */
    gpio_set_bits(sc, GPIO_CLK);    /* CLK rising edge */
    gpio_clr_bits(sc, GPIO_CLK);    /* CLK falling edge */
    }
  }

/* Write a command to the synthesized oscillator on SSI and HSSIc. */
static void  /* context: process */
synth_write(softc_t *sc, struct synth *synth)
  {
  /* SSI cards have a programmable prescaler */
  if (sc->status.card_type == CSID_LMC_SSI)
    {
    if (synth->prescale == 9) /* divide by 512 */
      mii17_set_bits(sc, MII17_SSI_PRESCALE);
    else                      /* divide by  32 */
      mii17_clr_bits(sc, MII17_SSI_PRESCALE);
    }

  gpio_clr_bits(sc,    GPIO_DATA | GPIO_CLK);
  gpio_make_output(sc, GPIO_DATA | GPIO_CLK);

  /* SYNTH is a low-true chip enable for the AV9110 chip. */
  gpio_set_bits(sc,    GPIO_SSI_SYNTH);
  gpio_make_output(sc, GPIO_SSI_SYNTH);
  gpio_clr_bits(sc,    GPIO_SSI_SYNTH);

  /* Serially shift the command into the AV9110 chip. */
  synth_shift_bits(sc, synth->n, 7);
  synth_shift_bits(sc, synth->m, 7);
  synth_shift_bits(sc, synth->v, 1);
  synth_shift_bits(sc, synth->x, 2);
  synth_shift_bits(sc, synth->r, 2);
  synth_shift_bits(sc, 0x16, 5); /* enable clk/x output */

  /* SYNTH (chip enable) going high ends the command. */
  gpio_set_bits(sc,   GPIO_SSI_SYNTH);
  gpio_make_input(sc, GPIO_SSI_SYNTH);

  /* Stop driving serial-related signals; pullups/pulldowns take over. */
  gpio_make_input(sc, GPIO_DATA | GPIO_CLK);

  /* remember the new synthesizer parameters */
  if (&sc->config.synth != synth) sc->config.synth = *synth;
  }

/* Write a command to the DAC controlling the VCXO on some T3 adapters. */
/* The DAC is a TI-TLV5636: 12-bit resolution and a serial interface. */
/* DATA is set up before the FALLING edge of CLK.  CLK is parked HIGH. */
static void  /* context: process */
dac_write(softc_t *sc, u_int16_t data)
  {
  int i;

  /* Prepare to use DATA and CLK. */
  gpio_set_bits(sc,    GPIO_DATA | GPIO_CLK);
  gpio_make_output(sc, GPIO_DATA | GPIO_CLK);

  /* High-to-low transition prepares DAC for new value. */
  gpio_set_bits(sc,    GPIO_T3_DAC);
  gpio_make_output(sc, GPIO_T3_DAC);
  gpio_clr_bits(sc,    GPIO_T3_DAC);

  /* Serially shift command bits into DAC. */
  for (i=0; i<16; i++)
    { /* MSB first */
    if (data & (1<<(15-i)))
      gpio_set_bits(sc, GPIO_DATA); /* DATA setup */
    else
      gpio_clr_bits(sc, GPIO_DATA); /* DATA setup */
    gpio_clr_bits(sc, GPIO_CLK);    /* CLK falling edge */
    gpio_set_bits(sc, GPIO_CLK);    /* CLK rising edge */
    }

  /* Done with DAC; make it an input; loads new value into DAC. */
  gpio_set_bits(sc,   GPIO_T3_DAC);
  gpio_make_input(sc, GPIO_T3_DAC);

  /* Stop driving serial-related signals; pullups/pulldowns take over. */
  gpio_make_input(sc, GPIO_DATA | GPIO_CLK);
  }

/* Begin HSSI card code */

static struct card hssi_card =
  {
  .ident    = hssi_ident,
  .watchdog = hssi_watchdog,
  .ioctl    = hssi_ioctl,
  .attach   = hssi_attach,
  .detach   = hssi_detach,
  };

static void
hssi_ident(softc_t *sc)
  {
  printf(", EIA-613");
  }

static void  /* context: softirq */
hssi_watchdog(softc_t *sc)
  {
  u_int16_t mii16 = mii_read(sc, 16) & MII16_HSSI_MODEM;

  sc->status.link_state = STATE_UP;

  led_inv(sc, MII16_HSSI_LED_UL);  /* Software is alive. */
  led_on(sc, MII16_HSSI_LED_LL);  /* always on (SSI cable) */

  /* Check the transmit clock. */
  if (sc->status.tx_speed == 0)
    {
    led_on(sc, MII16_HSSI_LED_UR);
    sc->status.link_state = STATE_DOWN;
    }
  else
    led_off(sc, MII16_HSSI_LED_UR);

  /* Is the modem ready? */
  if ((mii16 & MII16_HSSI_CA)==0)
    {
    led_off(sc, MII16_HSSI_LED_LR);
    sc->status.link_state = STATE_DOWN;
    }
  else
    led_on(sc, MII16_HSSI_LED_LR);

  /* Print the modem control signals if they changed. */
  if ((sc->config.debug) && (mii16 != sc->last_mii16))
    {
    const char *on = "ON ", *off = "OFF";
    printf("%s: TA=%s CA=%s LA=%s LB=%s LC=%s TM=%s\n", NAME_UNIT,
     (mii16 & MII16_HSSI_TA) ? on : off,
     (mii16 & MII16_HSSI_CA) ? on : off,
     (mii16 & MII16_HSSI_LA) ? on : off,
     (mii16 & MII16_HSSI_LB) ? on : off,
     (mii16 & MII16_HSSI_LC) ? on : off,
     (mii16 & MII16_HSSI_TM) ? on : off);
    }

  /* SNMP one-second-report */
  sc->status.snmp.hssi.sigs = mii16 & MII16_HSSI_MODEM;

  /* Remember this state until next time. */
  sc->last_mii16 = mii16;

  /* If a loop back is in effect, link status is UP */
  if (sc->config.loop_back != CFG_LOOP_NONE)
    sc->status.link_state = STATE_UP;
  }

static int  /* context: process */
hssi_ioctl(softc_t *sc, struct ioctl *ioctl)
  {
  int error = 0;

  if (ioctl->cmd == IOCTL_SNMP_SIGS)
    {
    u_int16_t mii16 = mii_read(sc, 16);
    mii16 &= ~MII16_HSSI_MODEM;
    mii16 |= (MII16_HSSI_MODEM & ioctl->data);
    mii_write(sc, 16, mii16);
    }
  else if (ioctl->cmd == IOCTL_SET_STATUS)
    {
    if (ioctl->data)
      mii16_set_bits(sc, MII16_HSSI_TA);
    else
      mii16_clr_bits(sc, MII16_HSSI_TA);
    }
  else
    error = EINVAL;

  return error;
  }

/* Must not sleep. */
static void
hssi_attach(softc_t *sc, struct config *config)
  {
  if (config == NULL) /* startup config */
    {
    sc->status.card_type  = READ_PCI_CFG(sc, TLP_CSID);
    sc->config.crc_len    = CFG_CRC_16;
    sc->config.loop_back  = CFG_LOOP_NONE;
    sc->config.tx_clk_src = CFG_CLKMUX_ST;
    sc->config.dte_dce    = CFG_DTE;
    sc->config.synth.n    = 52; /* 52.000 Mbs */
    sc->config.synth.m    = 5;
    sc->config.synth.v    = 0;
    sc->config.synth.x    = 0;
    sc->config.synth.r    = 0;
    sc->config.synth.prescale = 2;
    }
  else if (config != &sc->config) /* change config */
    {
    u_int32_t *old_synth = (u_int32_t *)&sc->config.synth;
    u_int32_t *new_synth = (u_int32_t *)&config->synth;
    if ((sc->config.crc_len    == config->crc_len)    &&
        (sc->config.loop_back  == config->loop_back)  &&
        (sc->config.tx_clk_src == config->tx_clk_src) &&
        (sc->config.dte_dce    == config->dte_dce)    &&
        (*old_synth            == *new_synth))
      return; /* nothing changed */
    sc->config.crc_len    = config->crc_len;
    sc->config.loop_back  = config->loop_back;
    sc->config.tx_clk_src = config->tx_clk_src;
    sc->config.dte_dce    = config->dte_dce;
    *old_synth            = *new_synth;
    }
  /* If (config == &sc->config) then the FPGA microcode
   * was just initialized and the current config should
   * be reloaded into the card.
   */

  /* set CRC length */
  if (sc->config.crc_len == CFG_CRC_32)
    mii16_set_bits(sc, MII16_HSSI_CRC32);
  else
    mii16_clr_bits(sc, MII16_HSSI_CRC32);

  /* Assert pin LA in HSSI conn: ask modem for local loop. */
  if (sc->config.loop_back == CFG_LOOP_LL)
    mii16_set_bits(sc, MII16_HSSI_LA);
  else
    mii16_clr_bits(sc, MII16_HSSI_LA);

  /* Assert pin LB in HSSI conn: ask modem for remote loop. */
  if (sc->config.loop_back == CFG_LOOP_RL)
    mii16_set_bits(sc, MII16_HSSI_LB);
  else
    mii16_clr_bits(sc, MII16_HSSI_LB);

  if (sc->status.card_type == CSID_LMC_HSSI)
    {
    /* set TXCLK src */
    if (sc->config.tx_clk_src == CFG_CLKMUX_ST)
      gpio_set_bits(sc, GPIO_HSSI_TXCLK);
    else
      gpio_clr_bits(sc, GPIO_HSSI_TXCLK);
    gpio_make_output(sc, GPIO_HSSI_TXCLK);
    }
  else if (sc->status.card_type == CSID_LMC_HSSIc)
    {  /* cPCI HSSI rev C has extra features */
    /* Set TXCLK source. */
    u_int16_t mii16 = mii_read(sc, 16);
    mii16 &= ~MII16_HSSI_CLKMUX;
    mii16 |= (sc->config.tx_clk_src&3)<<13;
    mii_write(sc, 16, mii16);

    /* cPCI HSSI implements loopback towards the net. */
    if (sc->config.loop_back == CFG_LOOP_LINE)
      mii16_set_bits(sc, MII16_HSSI_LOOP);
    else
      mii16_clr_bits(sc, MII16_HSSI_LOOP);

    /* Set DTE/DCE mode. */
    if (sc->config.dte_dce == CFG_DCE)
      gpio_set_bits(sc, GPIO_HSSI_DCE);
    else
      gpio_clr_bits(sc, GPIO_HSSI_DCE);
    gpio_make_output(sc, GPIO_HSSI_DCE);

    /* Program the synthesized oscillator. */
    synth_write(sc, &sc->config.synth);
    }
  }

static void
hssi_detach(softc_t *sc)
  {
  mii16_clr_bits(sc, MII16_HSSI_TA);
  led_on(sc, MII16_LED_ALL);
  }

/* End HSSI card code */

/* Begin DS3 card code */

static struct card t3_card =
  {
  .ident    = t3_ident,
  .watchdog = t3_watchdog,
  .ioctl    = t3_ioctl,
  .attach   = t3_attach,
  .detach   = t3_detach,
  };

static void
t3_ident(softc_t *sc)
  {
  printf(", TXC03401 rev B");
  }

static void  /* context: softirq */
t3_watchdog(softc_t *sc)
  {
  u_int16_t CV;
  u_int8_t CERR, PERR, MERR, FERR, FEBE;
  u_int8_t ctl1, stat16, feac;
  u_int16_t mii16;

  sc->status.link_state = STATE_UP;

  /* Read the alarm registers. */
  ctl1   = framer_read(sc, T3CSR_CTL1);
  stat16 = framer_read(sc, T3CSR_STAT16);
  mii16  = mii_read(sc, 16);

  /* Always ignore the RTLOC alarm bit. */
  stat16 &= ~STAT16_RTLOC;

  /* Software is alive. */
  led_inv(sc, MII16_DS3_LED_GRN);

  /* Receiving Alarm Indication Signal (AIS). */
  if (stat16 & STAT16_RAIS) /* receiving ais */
    led_on(sc, MII16_DS3_LED_BLU);
  else if (ctl1 & CTL1_TXAIS) /* sending ais */
    led_inv(sc, MII16_DS3_LED_BLU);
  else
    led_off(sc, MII16_DS3_LED_BLU);

  /* Receiving Remote Alarm Indication (RAI). */
  if (stat16 & STAT16_XERR) /* receiving rai */
    led_on(sc, MII16_DS3_LED_YEL);
  else if ((ctl1 & CTL1_XTX) == 0) /* sending rai */
    led_inv(sc, MII16_DS3_LED_YEL);
  else
    led_off(sc, MII16_DS3_LED_YEL);

  /* If certain status bits are set then the link is 'down'. */
  /* The bad bits are: rxlos rxoof rxais rxidl xerr. */
  if (stat16 & ~(STAT16_FEAC | STAT16_SEF))
    sc->status.link_state = STATE_DOWN;

  /* Declare local Red Alarm if the link is down. */
  if (sc->status.link_state == STATE_DOWN)
    led_on(sc, MII16_DS3_LED_RED);
  else if (sc->loop_timer) /* loopback is active */
    led_inv(sc, MII16_DS3_LED_RED);
  else
    led_off(sc, MII16_DS3_LED_RED);

  /* Print latched error bits if they changed. */
  if ((sc->config.debug) && ((stat16 & ~STAT16_FEAC) != sc->last_stat16))
    {
    const char *on = "ON ", *off = "OFF";
    printf("%s: RLOS=%s ROOF=%s RAIS=%s RIDL=%s SEF=%s XERR=%s\n",
     NAME_UNIT,
     (stat16 & STAT16_RLOS) ? on : off,
     (stat16 & STAT16_ROOF) ? on : off,
     (stat16 & STAT16_RAIS) ? on : off,
     (stat16 & STAT16_RIDL) ? on : off,
     (stat16 & STAT16_SEF)  ? on : off,
     (stat16 & STAT16_XERR) ? on : off);
    }

  /* Check and print error counters if non-zero. */
  CV   = framer_read(sc, T3CSR_CVHI)<<8;
  CV  += framer_read(sc, T3CSR_CVLO);
  PERR = framer_read(sc, T3CSR_PERR);
  CERR = framer_read(sc, T3CSR_CERR);
  FERR = framer_read(sc, T3CSR_FERR);
  MERR = framer_read(sc, T3CSR_MERR);
  FEBE = framer_read(sc, T3CSR_FEBE);

  /* CV is invalid during LOS. */
  if (stat16 & STAT16_RLOS) CV = 0;
  /* CERR & FEBE are invalid in M13 mode */
  if (sc->config.format == CFG_FORMAT_T3M13) CERR = FEBE = 0;
  /* FEBE is invalid during AIS. */
  if (stat16 & STAT16_RAIS) FEBE = 0;
  if (sc->config.debug && (CV || PERR || CERR || FERR || MERR || FEBE))
    printf("%s: CV=%u PERR=%u CERR=%u FERR=%u MERR=%u FEBE=%u\n",
     NAME_UNIT, CV,   PERR,   CERR,   FERR,   MERR,   FEBE);

  /* Driver keeps crude link-level error counters (SNMP is better). */
  sc->status.cntrs.lcv_errs  += CV;
  sc->status.cntrs.par_errs  += PERR;
  sc->status.cntrs.cpar_errs += CERR;
  sc->status.cntrs.frm_errs  += FERR;
  sc->status.cntrs.mfrm_errs += MERR;
  sc->status.cntrs.febe_errs += FEBE;

  /* Check for FEAC messages (FEAC not defined in M13 mode). */
  if (FORMAT_T3CPAR && (stat16 & STAT16_FEAC)) do
    {
    feac = framer_read(sc, T3CSR_FEAC_STK);
    if ((feac & FEAC_STK_VALID)==0) break;
    /* Ignore RxFEACs while a far end loopback has been requested. */
    if (sc->status.snmp.t3.line & TLOOP_FAR_LINE) continue;
    switch (feac & FEAC_STK_FEAC)
      {
      case T3BOP_LINE_UP:   break;
      case T3BOP_LINE_DOWN: break;
      case T3BOP_LOOP_DS3:
        {
        if (sc->last_FEAC == T3BOP_LINE_DOWN)
          {
          if (sc->config.debug)
            printf("%s: Received a 'line loopback deactivate' FEAC msg\n", NAME_UNIT);
          mii16_clr_bits(sc, MII16_DS3_LNLBK);
          sc->loop_timer = 0;
	  }
        if (sc->last_FEAC == T3BOP_LINE_UP)
          {
          if (sc->config.debug)
            printf("%s: Received a 'line loopback activate' FEAC msg\n", NAME_UNIT);
          mii16_set_bits(sc, MII16_DS3_LNLBK);
          sc->loop_timer = 300;
	  }
        break;
        }
      case T3BOP_OOF:
        {
        if (sc->config.debug)
          printf("%s: Received a 'far end LOF' FEAC msg\n", NAME_UNIT);
        break;
	}
      case T3BOP_IDLE:
        {
        if (sc->config.debug)
          printf("%s: Received a 'far end IDL' FEAC msg\n", NAME_UNIT);
        break;
	}
      case T3BOP_AIS:
        {
        if (sc->config.debug)
          printf("%s: Received a 'far end AIS' FEAC msg\n", NAME_UNIT);
        break;
	}
      case T3BOP_LOS:
        {
        if (sc->config.debug)
          printf("%s: Received a 'far end LOS' FEAC msg\n", NAME_UNIT);
        break;
	}
      default:
        {
        if (sc->config.debug)
          printf("%s: Received a 'type 0x%02X' FEAC msg\n", NAME_UNIT, feac & FEAC_STK_FEAC);
        break;
	}
      }
    sc->last_FEAC = feac & FEAC_STK_FEAC;
    } while (feac & FEAC_STK_MORE);
  stat16 &= ~STAT16_FEAC;

  /* Send Service-Affecting priority FEAC messages */
  if (((sc->last_stat16 ^ stat16) & 0xF0) && (FORMAT_T3CPAR))
    {
    /* Transmit continuous FEACs */
    framer_write(sc, T3CSR_CTL14,
     framer_read(sc, T3CSR_CTL14) & ~CTL14_FEAC10);
    if      (stat16 & STAT16_RLOS)
      framer_write(sc, T3CSR_TX_FEAC, 0xC0 + T3BOP_LOS);
    else if (stat16 & STAT16_ROOF)
      framer_write(sc, T3CSR_TX_FEAC, 0xC0 + T3BOP_OOF);
    else if (stat16 & STAT16_RAIS)
      framer_write(sc, T3CSR_TX_FEAC, 0xC0 + T3BOP_AIS);
    else if (stat16 & STAT16_RIDL)
      framer_write(sc, T3CSR_TX_FEAC, 0xC0 + T3BOP_IDLE);
    else
      framer_write(sc, T3CSR_TX_FEAC, CTL5_EMODE);
    }

  /* Start sending RAI, Remote Alarm Indication. */
  if ((stat16 & STAT16_ROOF) && !(stat16 & STAT16_RLOS) &&
   !(sc->last_stat16 & STAT16_ROOF))
    framer_write(sc, T3CSR_CTL1, ctl1 &= ~CTL1_XTX);
  /* Stop sending RAI, Remote Alarm Indication. */
  else if (!(stat16 & STAT16_ROOF) && (sc->last_stat16 & STAT16_ROOF))
    framer_write(sc, T3CSR_CTL1, ctl1 |=  CTL1_XTX);

  /* Start sending AIS, Alarm Indication Signal */
  if ((stat16 & STAT16_RLOS) && !(sc->last_stat16 & STAT16_RLOS))
    {
    mii16_set_bits(sc, MII16_DS3_FRAME);
    framer_write(sc, T3CSR_CTL1, ctl1 |  CTL1_TXAIS);
    }
  /* Stop sending AIS, Alarm Indication Signal */
  else if (!(stat16 & STAT16_RLOS) && (sc->last_stat16 & STAT16_RLOS))
    {
    mii16_clr_bits(sc, MII16_DS3_FRAME);
    framer_write(sc, T3CSR_CTL1, ctl1 & ~CTL1_TXAIS);
    }

  /* Time out loopback requests. */
  if (sc->loop_timer)
    if (--sc->loop_timer == 0)
      if (mii16 & MII16_DS3_LNLBK)
        {
        if (sc->config.debug)
          printf("%s: Timeout: Loop Down after 300 seconds\n", NAME_UNIT);
        mii16_clr_bits(sc, MII16_DS3_LNLBK); /* line loopback off */
        }

  /* SNMP error counters */
  sc->status.snmp.t3.lcv  = CV;
  sc->status.snmp.t3.pcv  = PERR;
  sc->status.snmp.t3.ccv  = CERR;
  sc->status.snmp.t3.febe = FEBE;

  /* SNMP Line Status */
  sc->status.snmp.t3.line = 0;
  if (!(ctl1 & CTL1_XTX))      sc->status.snmp.t3.line |= TLINE_TX_RAI;
  if (stat16 & STAT16_XERR)    sc->status.snmp.t3.line |= TLINE_RX_RAI;
  if (ctl1   & CTL1_TXAIS)     sc->status.snmp.t3.line |= TLINE_TX_AIS;
  if (stat16 & STAT16_RAIS)    sc->status.snmp.t3.line |= TLINE_RX_AIS;
  if (stat16 & STAT16_ROOF)    sc->status.snmp.t3.line |= TLINE_LOF;
  if (stat16 & STAT16_RLOS)    sc->status.snmp.t3.line |= TLINE_LOS;
  if (stat16 & STAT16_SEF)     sc->status.snmp.t3.line |= T3LINE_SEF;

  /* SNMP Loopback Status */
  sc->status.snmp.t3.loop &= ~TLOOP_FAR_LINE;
  if (sc->config.loop_back == CFG_LOOP_TULIP)
                               sc->status.snmp.t3.loop |= TLOOP_NEAR_OTHER;
  if (ctl1  & CTL1_3LOOP)      sc->status.snmp.t3.loop |= TLOOP_NEAR_INWARD;
  if (mii16 & MII16_DS3_TRLBK) sc->status.snmp.t3.loop |= TLOOP_NEAR_OTHER;
  if (mii16 & MII16_DS3_LNLBK) sc->status.snmp.t3.loop |= TLOOP_NEAR_LINE;
/*if (ctl12 & CTL12_RTPLOOP)   sc->status.snmp.t3.loop |= TLOOP_NEAR_PAYLOAD; */

  /* Remember this state until next time. */
  sc->last_stat16 = stat16;

  /* If an INWARD loopback is in effect, link status is UP */
  if (sc->config.loop_back != CFG_LOOP_NONE) /* XXX INWARD ONLY */
    sc->status.link_state = STATE_UP;
  }

static void  /* context: process */
t3_send_dbl_feac(softc_t *sc, int feac1, int feac2)
  {
  u_int8_t tx_feac;
  int i;

  /* The FEAC transmitter could be sending a continuous */
  /*  FEAC msg when told to send a double FEAC message. */
  /* So save the current state of the FEAC transmitter. */
  tx_feac = framer_read(sc, T3CSR_TX_FEAC);
  /* Load second FEAC code and stop FEAC transmitter. */
  framer_write(sc, T3CSR_TX_FEAC,  CTL5_EMODE + feac2);
  /* FEAC transmitter sends 10 more FEACs and then stops. */
  SLEEP(20000); /* sending one FEAC takes 1700 uSecs */
  /* Load first FEAC code and start FEAC transmitter. */
  framer_write(sc, T3CSR_DBL_FEAC, CTL13_DFEXEC + feac1);
  /* Wait for double FEAC sequence to complete -- about 70 ms. */
  for (i=0; i<10; i++) /* max delay 100 ms */
    if (framer_read(sc, T3CSR_DBL_FEAC) & CTL13_DFEXEC) SLEEP(10000);
  /* Flush received FEACS; do not respond to our own loop cmd! */
  while (framer_read(sc, T3CSR_FEAC_STK) & FEAC_STK_VALID) DELAY(1);
  /* Restore previous state of the FEAC transmitter. */
  /* If it was sending a continous FEAC, it will resume. */
  framer_write(sc, T3CSR_TX_FEAC, tx_feac);
  }

static int  /* context: process */
t3_ioctl(softc_t *sc, struct ioctl *ioctl)
  {
  int error = 0;

  switch (ioctl->cmd)
    {
    case IOCTL_SNMP_SEND:  /* set opstatus? */
      {
      if (sc->config.format != CFG_FORMAT_T3CPAR)
        error = EINVAL;
      else if (ioctl->data == TSEND_LINE)
        {
        sc->status.snmp.t3.loop |= TLOOP_FAR_LINE;
        t3_send_dbl_feac(sc, T3BOP_LINE_UP, T3BOP_LOOP_DS3);
        }
      else if (ioctl->data == TSEND_RESET)
        {
        t3_send_dbl_feac(sc, T3BOP_LINE_DOWN, T3BOP_LOOP_DS3);
        sc->status.snmp.t3.loop &= ~TLOOP_FAR_LINE;
        }
      else
        error = EINVAL;
      break;
      }
    case IOCTL_SNMP_LOOP:  /* set opstatus = test? */
      {
      if (ioctl->data == CFG_LOOP_NONE)
        {
        mii16_clr_bits(sc, MII16_DS3_FRAME);
        mii16_clr_bits(sc, MII16_DS3_TRLBK);
        mii16_clr_bits(sc, MII16_DS3_LNLBK);
        framer_write(sc, T3CSR_CTL1,
         framer_read(sc, T3CSR_CTL1) & ~CTL1_3LOOP);
        framer_write(sc, T3CSR_CTL12,
         framer_read(sc, T3CSR_CTL12) & ~(CTL12_RTPLOOP | CTL12_RTPLLEN));
	}
      else if (ioctl->data == CFG_LOOP_LINE)
        mii16_set_bits(sc, MII16_DS3_LNLBK);
      else if (ioctl->data == CFG_LOOP_OTHER)
        mii16_set_bits(sc, MII16_DS3_TRLBK);
      else if (ioctl->data == CFG_LOOP_INWARD)
        framer_write(sc, T3CSR_CTL1,
         framer_read(sc, T3CSR_CTL1) | CTL1_3LOOP);
      else if (ioctl->data == CFG_LOOP_DUAL)
        {
        mii16_set_bits(sc, MII16_DS3_LNLBK);
        framer_write(sc, T3CSR_CTL1,
         framer_read(sc, T3CSR_CTL1) | CTL1_3LOOP);
	}
      else if (ioctl->data == CFG_LOOP_PAYLOAD)
        {
        mii16_set_bits(sc, MII16_DS3_FRAME);
        framer_write(sc, T3CSR_CTL12,
         framer_read(sc, T3CSR_CTL12) |  CTL12_RTPLOOP);
        framer_write(sc, T3CSR_CTL12,
         framer_read(sc, T3CSR_CTL12) |  CTL12_RTPLLEN);
        DELAY(25); /* at least two frames (22 uS) */
        framer_write(sc, T3CSR_CTL12,
         framer_read(sc, T3CSR_CTL12) & ~CTL12_RTPLLEN);
	}
      else
        error = EINVAL;
      break;
      }
    case IOCTL_SET_STATUS:
      {
#if 0
      if (ioctl->data)
        framer_write(sc, T3CSR_CTL1,
         framer_read(sc, T3CSR_CTL1) & ~CTL1_TXIDL);
      else /* off */
        framer_write(sc, T3CSR_CTL1,
         framer_read(sc, T3CSR_CTL1) |  CTL1_TXIDL);
#endif
      break;
      }
    default:
      error = EINVAL;
      break;
    }

  return error;
  }

/* Must not sleep. */
static void
t3_attach(softc_t *sc, struct config *config)
  {
  int i;
  u_int8_t ctl1;

  if (config == NULL) /* startup config */
    {
    sc->status.card_type  = CSID_LMC_T3;
    sc->config.crc_len    = CFG_CRC_16;
    sc->config.loop_back  = CFG_LOOP_NONE;
    sc->config.format     = CFG_FORMAT_T3CPAR;
    sc->config.cable_len  = 10; /* meters */
    sc->config.scrambler  = CFG_SCRAM_DL_KEN;
    sc->config.tx_clk_src = CFG_CLKMUX_INT;

    /* Center the VCXO -- get within 20 PPM of 44736000. */
    dac_write(sc, 0x9002); /* set Vref = 2.048 volts */
    dac_write(sc, 2048); /* range is 0..4095 */
    }
  else if (config != &sc->config) /* change config */
    {
    if ((sc->config.crc_len    == config->crc_len)   &&
        (sc->config.loop_back  == config->loop_back) &&
        (sc->config.format     == config->format)    &&
        (sc->config.cable_len  == config->cable_len) &&
        (sc->config.scrambler  == config->scrambler) &&
        (sc->config.tx_clk_src == config->tx_clk_src))
      return; /* nothing changed */
    sc->config.crc_len    = config->crc_len;
    sc->config.loop_back  = config->loop_back;
    sc->config.format     = config->format;
    sc->config.cable_len  = config->cable_len;
    sc->config.scrambler  = config->scrambler;
    sc->config.tx_clk_src = config->tx_clk_src;
    }

  /* Set cable length. */
  if (sc->config.cable_len > 30)
    mii16_clr_bits(sc, MII16_DS3_ZERO);
  else
    mii16_set_bits(sc, MII16_DS3_ZERO);

  /* Set payload scrambler polynomial. */
  if (sc->config.scrambler == CFG_SCRAM_LARS)
    mii16_set_bits(sc, MII16_DS3_POLY);
  else
    mii16_clr_bits(sc, MII16_DS3_POLY);

  /* Set payload scrambler on/off. */
  if (sc->config.scrambler == CFG_SCRAM_OFF)
    mii16_clr_bits(sc, MII16_DS3_SCRAM);
  else
    mii16_set_bits(sc, MII16_DS3_SCRAM);

  /* Set CRC length. */
  if (sc->config.crc_len == CFG_CRC_32)
    mii16_set_bits(sc, MII16_DS3_CRC32);
  else
    mii16_clr_bits(sc, MII16_DS3_CRC32);

  /* Loopback towards host thru the line interface. */
  if (sc->config.loop_back == CFG_LOOP_OTHER)
    mii16_set_bits(sc, MII16_DS3_TRLBK);
  else
    mii16_clr_bits(sc, MII16_DS3_TRLBK);

  /* Loopback towards network thru the line interface. */
  if (sc->config.loop_back == CFG_LOOP_LINE)
    mii16_set_bits(sc, MII16_DS3_LNLBK);
  else if (sc->config.loop_back == CFG_LOOP_DUAL)
    mii16_set_bits(sc, MII16_DS3_LNLBK);
  else
    mii16_clr_bits(sc, MII16_DS3_LNLBK);

  /* Configure T3 framer chip; write EVERY writeable register. */
  ctl1 = CTL1_SER | CTL1_XTX;
  if (sc->config.format    == CFG_FORMAT_T3M13) ctl1 |= CTL1_M13MODE;
  if (sc->config.loop_back == CFG_LOOP_INWARD)  ctl1 |= CTL1_3LOOP;
  if (sc->config.loop_back == CFG_LOOP_DUAL)    ctl1 |= CTL1_3LOOP;
  framer_write(sc, T3CSR_CTL1,     ctl1);
  framer_write(sc, T3CSR_TX_FEAC,  CTL5_EMODE);
  framer_write(sc, T3CSR_CTL8,     CTL8_FBEC);
  framer_write(sc, T3CSR_CTL12,    CTL12_DLCB1 | CTL12_C21 | CTL12_MCB1);
  framer_write(sc, T3CSR_DBL_FEAC, 0);
  framer_write(sc, T3CSR_CTL14,    CTL14_RGCEN | CTL14_TGCEN);
  framer_write(sc, T3CSR_INTEN,    0);
  framer_write(sc, T3CSR_CTL20,    CTL20_CVEN);

  /* Clear error counters and latched error bits */
  /*  that may have happened while initializing. */
  for (i=0; i<21; i++) framer_read(sc, i);
  }

static void
t3_detach(softc_t *sc)
  {
  framer_write(sc, T3CSR_CTL1,
   framer_read(sc, T3CSR_CTL1) |  CTL1_TXIDL);
  led_on(sc, MII16_LED_ALL);
  }

/* End DS3 card code */

/* Begin SSI card code */

static struct card ssi_card =
  {
  .ident    = ssi_ident,
  .watchdog = ssi_watchdog,
  .ioctl    = ssi_ioctl,
  .attach   = ssi_attach,
  .detach   = ssi_detach,
  };

static void
ssi_ident(softc_t *sc)
  {
  printf(", LTC1343/44");
  }

static void  /* context: softirq */
ssi_watchdog(softc_t *sc)
  {
  u_int16_t cable;
  u_int16_t mii16 = mii_read(sc, 16) & MII16_SSI_MODEM;

  sc->status.link_state = STATE_UP;

  /* Software is alive. */
  led_inv(sc, MII16_SSI_LED_UL);

  /* Check the transmit clock. */
  if (sc->status.tx_speed == 0)
    {
    led_on(sc, MII16_SSI_LED_UR);
    sc->status.link_state = STATE_DOWN;
    }
  else
    led_off(sc, MII16_SSI_LED_UR);

  /* Check the external cable. */
  cable = mii_read(sc, 17);
  cable = cable &  MII17_SSI_CABLE_MASK;
  cable = cable >> MII17_SSI_CABLE_SHIFT;
  if (cable == 7)
    {
    led_off(sc, MII16_SSI_LED_LL); /* no cable */
    sc->status.link_state = STATE_DOWN;
    }
  else
    led_on(sc, MII16_SSI_LED_LL);

  /* The unit at the other end of the cable is ready if: */
  /*  DTE mode and DCD pin is asserted */
  /*  DCE mode and DSR pin is asserted */
  if (((sc->config.dte_dce == CFG_DTE) && !(mii16 & MII16_SSI_DCD)) ||
      ((sc->config.dte_dce == CFG_DCE) && !(mii16 & MII16_SSI_DSR)))
    {
    led_off(sc, MII16_SSI_LED_LR);
    sc->status.link_state = STATE_DOWN;
    }
  else
    led_on(sc, MII16_SSI_LED_LR);

  if (sc->config.debug && (cable != sc->status.cable_type))
    printf("%s: SSI cable type changed to '%s'\n",
     NAME_UNIT, ssi_cables[cable]);
  sc->status.cable_type = cable;

  /* Print the modem control signals if they changed. */
  if ((sc->config.debug) && (mii16 != sc->last_mii16))
    {
    const char *on = "ON ", *off = "OFF";
    printf("%s: DTR=%s DSR=%s RTS=%s CTS=%s DCD=%s RI=%s LL=%s RL=%s TM=%s\n",
     NAME_UNIT,
     (mii16 & MII16_SSI_DTR) ? on : off,
     (mii16 & MII16_SSI_DSR) ? on : off,
     (mii16 & MII16_SSI_RTS) ? on : off,
     (mii16 & MII16_SSI_CTS) ? on : off,
     (mii16 & MII16_SSI_DCD) ? on : off,
     (mii16 & MII16_SSI_RI)  ? on : off,
     (mii16 & MII16_SSI_LL)  ? on : off,
     (mii16 & MII16_SSI_RL)  ? on : off,
     (mii16 & MII16_SSI_TM)  ? on : off);
    }

  /* SNMP one-second report */
  sc->status.snmp.ssi.sigs = mii16 & MII16_SSI_MODEM;

  /* Remember this state until next time. */
  sc->last_mii16 = mii16;

  /* If a loop back is in effect, link status is UP */
  if (sc->config.loop_back != CFG_LOOP_NONE)
    sc->status.link_state = STATE_UP;
  }

static int  /* context: process */
ssi_ioctl(softc_t *sc, struct ioctl *ioctl)
  {
  int error = 0;

  if (ioctl->cmd == IOCTL_SNMP_SIGS)
    {
    u_int16_t mii16 = mii_read(sc, 16);
    mii16 &= ~MII16_SSI_MODEM;
    mii16 |= (MII16_SSI_MODEM & ioctl->data);
    mii_write(sc, 16, mii16);
    }
  else if (ioctl->cmd == IOCTL_SET_STATUS)
    {
    if (ioctl->data)
      mii16_set_bits(sc, (MII16_SSI_DTR | MII16_SSI_RTS | MII16_SSI_DCD));
    else
      mii16_clr_bits(sc, (MII16_SSI_DTR | MII16_SSI_RTS | MII16_SSI_DCD));
    }
  else
    error = EINVAL;

  return error;
  }

/* Must not sleep. */
static void
ssi_attach(softc_t *sc, struct config *config)
  {
  if (config == NULL) /* startup config */
    {
    sc->status.card_type  = CSID_LMC_SSI;
    sc->config.crc_len    = CFG_CRC_16;
    sc->config.loop_back  = CFG_LOOP_NONE;
    sc->config.tx_clk_src = CFG_CLKMUX_ST;
    sc->config.dte_dce    = CFG_DTE;
    sc->config.synth.n    = 51; /* 1.536 MHz */
    sc->config.synth.m    = 83;
    sc->config.synth.v    =  1;
    sc->config.synth.x    =  1;
    sc->config.synth.r    =  1;
    sc->config.synth.prescale = 4;
    }
  else if (config != &sc->config) /* change config */
    {
    u_int32_t *old_synth = (u_int32_t *)&sc->config.synth;
    u_int32_t *new_synth = (u_int32_t *)&config->synth;
    if ((sc->config.crc_len    == config->crc_len)    &&
        (sc->config.loop_back  == config->loop_back)  &&
        (sc->config.tx_clk_src == config->tx_clk_src) &&
        (sc->config.dte_dce    == config->dte_dce)    &&
        (*old_synth            == *new_synth))
      return; /* nothing changed */
    sc->config.crc_len    = config->crc_len;
    sc->config.loop_back  = config->loop_back;
    sc->config.tx_clk_src = config->tx_clk_src;
    sc->config.dte_dce    = config->dte_dce;
    *old_synth            = *new_synth;
    }

  /* Disable the TX clock driver while programming the oscillator. */
  gpio_clr_bits(sc, GPIO_SSI_DCE);
  gpio_make_output(sc, GPIO_SSI_DCE);

  /* Program the synthesized oscillator. */
  synth_write(sc, &sc->config.synth);

  /* Set DTE/DCE mode. */
  /* If DTE mode then DCD & TXC are received. */
  /* If DCE mode then DCD & TXC are driven. */
  /* Boards with MII rev=4.0 do not drive DCD. */
  if (sc->config.dte_dce == CFG_DCE)
    gpio_set_bits(sc, GPIO_SSI_DCE);
  else
    gpio_clr_bits(sc, GPIO_SSI_DCE);
  gpio_make_output(sc, GPIO_SSI_DCE);

  /* Set CRC length. */
  if (sc->config.crc_len == CFG_CRC_32)
    mii16_set_bits(sc, MII16_SSI_CRC32);
  else
    mii16_clr_bits(sc, MII16_SSI_CRC32);

  /* Loop towards host thru cable drivers and receivers. */
  /* Asserts DCD at the far end of a null modem cable. */
  if (sc->config.loop_back == CFG_LOOP_PINS)
    mii16_set_bits(sc, MII16_SSI_LOOP);
  else
    mii16_clr_bits(sc, MII16_SSI_LOOP);

  /* Assert pin LL in modem conn: ask modem for local loop. */
  /* Asserts TM at the far end of a null modem cable. */
  if (sc->config.loop_back == CFG_LOOP_LL)
    mii16_set_bits(sc, MII16_SSI_LL);
  else
    mii16_clr_bits(sc, MII16_SSI_LL);

  /* Assert pin RL in modem conn: ask modem for remote loop. */
  if (sc->config.loop_back == CFG_LOOP_RL)
    mii16_set_bits(sc, MII16_SSI_RL);
  else
    mii16_clr_bits(sc, MII16_SSI_RL);
  }

static void
ssi_detach(softc_t *sc)
  {
  mii16_clr_bits(sc, (MII16_SSI_DTR | MII16_SSI_RTS | MII16_SSI_DCD));
  led_on(sc, MII16_LED_ALL);
  }

/* End SSI card code */

/* Begin T1E1 card code */

static struct card t1_card =
  {
  .ident    = t1_ident,
  .watchdog = t1_watchdog,
  .ioctl    = t1_ioctl,
  .attach   = t1_attach,
  .detach   = t1_detach,
  };

static void
t1_ident(softc_t *sc)
  {
  printf(", Bt837%x rev %x",
   framer_read(sc, Bt8370_DID)>>4,
   framer_read(sc, Bt8370_DID)&0x0F);
  }

static void  /* context: softirq */
t1_watchdog(softc_t *sc)
  {
  u_int16_t LCV = 0, FERR = 0, CRC = 0, FEBE = 0;
  u_int8_t alm1, alm3, loop, isr0;
  int i;

  sc->status.link_state = STATE_UP;

  /* Read the alarm registers */
  alm1 = framer_read(sc, Bt8370_ALM1);
  alm3 = framer_read(sc, Bt8370_ALM3);
  loop = framer_read(sc, Bt8370_LOOP);
  isr0 = framer_read(sc, Bt8370_ISR0);

  /* Always ignore the SIGFRZ alarm bit, */
  alm1 &= ~ALM1_SIGFRZ;
  if (FORMAT_T1ANY)  /* ignore RYEL in T1 modes */
    alm1 &= ~ALM1_RYEL;
  else if (FORMAT_E1NONE) /* ignore all alarms except LOS */
    alm1 &= ALM1_RLOS;

  /* Software is alive. */
  led_inv(sc, MII16_T1_LED_GRN);

  /* Receiving Alarm Indication Signal (AIS). */
  if      (alm1 & ALM1_RAIS) /* receiving ais */
    led_on(sc, MII16_T1_LED_BLU);
  else if (alm1 & ALM1_RLOS) /* sending ais */
    led_inv(sc, MII16_T1_LED_BLU);
  else
    led_off(sc, MII16_T1_LED_BLU);

  /* Receiving Remote Alarm Indication (RAI). */
  if (alm1 & (ALM1_RMYEL | ALM1_RYEL)) /* receiving rai */
    led_on(sc, MII16_T1_LED_YEL);
  else if (alm1 & ALM1_RLOF) /* sending rai */
    led_inv(sc, MII16_T1_LED_YEL);
  else
    led_off(sc, MII16_T1_LED_YEL);

  /* If any alarm bits are set then the link is 'down'. */
  /* The bad bits are: rmyel ryel rais ralos rlos rlof. */
  /* Some alarm bits have been masked by this point. */
  if (alm1) sc->status.link_state = STATE_DOWN;

  /* Declare local Red Alarm if the link is down. */
  if (sc->status.link_state == STATE_DOWN)
    led_on(sc, MII16_T1_LED_RED);
  else if (sc->loop_timer) /* loopback is active */
    led_inv(sc, MII16_T1_LED_RED);
  else
    led_off(sc, MII16_T1_LED_RED);

  /* Print latched error bits if they changed. */
  if ((sc->config.debug) && (alm1 != sc->last_alm1))
    {
    const char *on = "ON ", *off = "OFF";
    printf("%s: RLOF=%s RLOS=%s RALOS=%s RAIS=%s RYEL=%s RMYEL=%s\n",
     NAME_UNIT,
     (alm1 & ALM1_RLOF)  ? on : off,
     (alm1 & ALM1_RLOS)  ? on : off,
     (alm1 & ALM1_RALOS) ? on : off,
     (alm1 & ALM1_RAIS)  ? on : off,
     (alm1 & ALM1_RYEL)  ? on : off,
     (alm1 & ALM1_RMYEL) ? on : off);
    }

  /* Check and print error counters if non-zero. */
  LCV = framer_read(sc, Bt8370_LCV_LO)  +
        (framer_read(sc, Bt8370_LCV_HI)<<8);
  if (!FORMAT_E1NONE)
    FERR = framer_read(sc, Bt8370_FERR_LO) +
          (framer_read(sc, Bt8370_FERR_HI)<<8);
  if (FORMAT_E1CRC || FORMAT_T1ESF)
    CRC  = framer_read(sc, Bt8370_CRC_LO)  +
          (framer_read(sc, Bt8370_CRC_HI)<<8);
  if (FORMAT_E1CRC)
    FEBE = framer_read(sc, Bt8370_FEBE_LO) +
          (framer_read(sc, Bt8370_FEBE_HI)<<8);
  /* Only LCV is valid if Out-Of-Frame */
  if (FORMAT_E1NONE) FERR = CRC = FEBE = 0;
  if ((sc->config.debug) && (LCV || FERR || CRC || FEBE))
    printf("%s: LCV=%u FERR=%u CRC=%u FEBE=%u\n",
     NAME_UNIT, LCV,   FERR,   CRC,   FEBE);

  /* Driver keeps crude link-level error counters (SNMP is better). */
  sc->status.cntrs.lcv_errs  += LCV;
  sc->status.cntrs.frm_errs  += FERR;
  sc->status.cntrs.crc_errs  += CRC;
  sc->status.cntrs.febe_errs += FEBE;

  /* Check for BOP messages in the ESF Facility Data Link. */
  if ((FORMAT_T1ESF) && (framer_read(sc, Bt8370_ISR1) & 0x80))
    {
    u_int8_t bop_code = framer_read(sc, Bt8370_RBOP) & 0x3F;

    switch (bop_code)
      {
      case T1BOP_OOF:
        {
        if ((sc->config.debug) && !(sc->last_alm1 & ALM1_RMYEL))
          printf("%s: Receiving a 'yellow alarm' BOP msg\n", NAME_UNIT);
        break;
        }
      case T1BOP_LINE_UP:
        {
        if (sc->config.debug)
          printf("%s: Received a 'line loopback activate' BOP msg\n", NAME_UNIT);
        framer_write(sc, Bt8370_LOOP, LOOP_LINE);
        sc->loop_timer = 305;
        break;
        }
      case T1BOP_LINE_DOWN:
        {
        if (sc->config.debug)
          printf("%s: Received a 'line loopback deactivate' BOP msg\n", NAME_UNIT);
        framer_write(sc, Bt8370_LOOP,
         framer_read(sc, Bt8370_LOOP) & ~LOOP_LINE);
        sc->loop_timer = 0;
        break;
        }
      case T1BOP_PAY_UP:
        {
        if (sc->config.debug)
          printf("%s: Received a 'payload loopback activate' BOP msg\n", NAME_UNIT);
        framer_write(sc, Bt8370_LOOP, LOOP_PAYLOAD);
        sc->loop_timer = 305;
        break;
        }
      case T1BOP_PAY_DOWN:
        {
        if (sc->config.debug)
          printf("%s: Received a 'payload loopback deactivate' BOP msg\n", NAME_UNIT);
        framer_write(sc, Bt8370_LOOP,
         framer_read(sc, Bt8370_LOOP) & ~LOOP_PAYLOAD);
        sc->loop_timer = 0;
        break;
        }
      default:
        {
        if (sc->config.debug)
          printf("%s: Received a type 0x%02X BOP msg\n", NAME_UNIT, bop_code);
        break;
        }
      }
    }

  /* Check for HDLC pkts in the ESF Facility Data Link. */
  if ((FORMAT_T1ESF) && (framer_read(sc, Bt8370_ISR2) & 0x70))
    {
    /* while (not fifo-empty && not start-of-msg) flush fifo */
    while ((framer_read(sc, Bt8370_RDL1_STAT) & 0x0C)==0)
      framer_read(sc, Bt8370_RDL1);
    /* If (not fifo-empty), then begin processing fifo contents. */
    if ((framer_read(sc, Bt8370_RDL1_STAT) & 0x0C) == 0x08)
      {
      u_int8_t msg[64];
      u_int8_t stat = framer_read(sc, Bt8370_RDL1);
      sc->status.cntrs.fdl_pkts++;
      for (i=0; i<(stat & 0x3F); i++)
        msg[i] = framer_read(sc, Bt8370_RDL1);
      /* Is this FDL message a T1.403 performance report? */
      if (((stat & 0x3F)==11) &&
          ((msg[0]==0x38) || (msg[0]==0x3A)) &&
           (msg[1]==1)   &&  (msg[2]==3))
        /* Copy 4 PRs from FDL pkt to SNMP struct. */
        memcpy(sc->status.snmp.t1.prm, msg+3, 8);
      }
    }

  /* Check for inband loop up/down commands. */
  if (FORMAT_T1ANY)
    {
    u_int8_t isr6   = framer_read(sc, Bt8370_ISR6);
    u_int8_t alarm2 = framer_read(sc, Bt8370_ALM2);
    u_int8_t tlb    = framer_read(sc, Bt8370_TLB);

    /* Inband Code == Loop Up && On Transition && Inband Tx Inactive */
    if ((isr6 & 0x40) && (alarm2 & 0x40) && !(tlb & 1))
      { /* CSU loop up is 10000 10000 ... */
      if (sc->config.debug)
        printf("%s: Received a 'CSU Loop Up' inband msg\n", NAME_UNIT);
      framer_write(sc, Bt8370_LOOP, LOOP_LINE); /* Loop up */
      sc->loop_timer = 305;
      }
    /* Inband Code == Loop Down && On Transition && Inband Tx Inactive */
    if ((isr6 & 0x80) && (alarm2 & 0x80) && !(tlb & 1))
      { /* CSU loop down is 100 100 100 ... */
      if (sc->config.debug)
        printf("%s: Received a 'CSU Loop Down' inband msg\n", NAME_UNIT);
      framer_write(sc, Bt8370_LOOP,
       framer_read(sc, Bt8370_LOOP) & ~LOOP_LINE); /* loop down */
      sc->loop_timer = 0;
      }
    }

  /* Manually send Yellow Alarm BOP msgs. */
  if (FORMAT_T1ESF)
    {
    u_int8_t isr7 = framer_read(sc, Bt8370_ISR7);

    if ((isr7 & 0x02) && (alm1 & 0x02)) /* RLOF on-transition */
      { /* Start sending continuous Yellow Alarm BOP messages. */
      framer_write(sc, Bt8370_BOP,  RBOP_25 | TBOP_CONT);
      framer_write(sc, Bt8370_TBOP, 0x00); /* send BOP; order matters */
      }
    else if ((isr7 & 0x02) && !(alm1 & 0x02)) /* RLOF off-transition */
      { /* Stop sending continuous Yellow Alarm BOP messages. */
      framer_write(sc, Bt8370_BOP,  RBOP_25 | TBOP_OFF);
      }
    }

  /* Time out loopback requests. */
  if (sc->loop_timer)
    if (--sc->loop_timer == 0)
      if (loop)
        {
        if (sc->config.debug)
          printf("%s: Timeout: Loop Down after 300 seconds\n", NAME_UNIT);
        framer_write(sc, Bt8370_LOOP, loop & ~(LOOP_PAYLOAD | LOOP_LINE));
        }

  /* RX Test Pattern status */
  if ((sc->config.debug) && (isr0 & 0x10))
    printf("%s: RX Test Pattern Sync\n", NAME_UNIT);

  /* SNMP Error Counters */
  sc->status.snmp.t1.lcv  = LCV;
  sc->status.snmp.t1.fe   = FERR;
  sc->status.snmp.t1.crc  = CRC;
  sc->status.snmp.t1.febe = FEBE;

  /* SNMP Line Status */
  sc->status.snmp.t1.line = 0;
  if  (alm1 & ALM1_RMYEL)  sc->status.snmp.t1.line |= TLINE_RX_RAI;
  if  (alm1 & ALM1_RYEL)   sc->status.snmp.t1.line |= TLINE_RX_RAI;
  if  (alm1 & ALM1_RLOF)   sc->status.snmp.t1.line |= TLINE_TX_RAI;
  if  (alm1 & ALM1_RAIS)   sc->status.snmp.t1.line |= TLINE_RX_AIS;
  if  (alm1 & ALM1_RLOS)   sc->status.snmp.t1.line |= TLINE_TX_AIS;
  if  (alm1 & ALM1_RLOF)   sc->status.snmp.t1.line |= TLINE_LOF;
  if  (alm1 & ALM1_RLOS)   sc->status.snmp.t1.line |= TLINE_LOS;
  if  (alm3 & ALM3_RMAIS)  sc->status.snmp.t1.line |= T1LINE_RX_TS16_AIS;
  if  (alm3 & ALM3_SRED)   sc->status.snmp.t1.line |= T1LINE_TX_TS16_LOMF;
  if  (alm3 & ALM3_SEF)    sc->status.snmp.t1.line |= T1LINE_SEF;
  if  (isr0 & 0x10)        sc->status.snmp.t1.line |= T1LINE_RX_TEST;
  if ((alm1 & ALM1_RMYEL) && (FORMAT_E1CAS))
                           sc->status.snmp.t1.line |= T1LINE_RX_TS16_LOMF;

  /* SNMP Loopback Status */
  sc->status.snmp.t1.loop &= ~(TLOOP_FAR_LINE | TLOOP_FAR_PAYLOAD);
  if (sc->config.loop_back == CFG_LOOP_TULIP)
                           sc->status.snmp.t1.loop |= TLOOP_NEAR_OTHER;
  if (loop & LOOP_PAYLOAD) sc->status.snmp.t1.loop |= TLOOP_NEAR_PAYLOAD;
  if (loop & LOOP_LINE)    sc->status.snmp.t1.loop |= TLOOP_NEAR_LINE;
  if (loop & LOOP_ANALOG)  sc->status.snmp.t1.loop |= TLOOP_NEAR_OTHER;
  if (loop & LOOP_FRAMER)  sc->status.snmp.t1.loop |= TLOOP_NEAR_INWARD;

  /* Remember this state until next time. */
  sc->last_alm1 = alm1;

  /* If an INWARD loopback is in effect, link status is UP */
  if (sc->config.loop_back != CFG_LOOP_NONE) /* XXX INWARD ONLY */
    sc->status.link_state = STATE_UP;
  }

static void  /* context: process */
t1_send_bop(softc_t *sc, int bop_code)
  {
  u_int8_t bop;
  int i;

  /* The BOP transmitter could be sending a continuous */
  /*  BOP msg when told to send this BOP_25 message. */
  /* So save and restore the state of the BOP machine. */
  bop = framer_read(sc, Bt8370_BOP);
  framer_write(sc, Bt8370_BOP, RBOP_OFF | TBOP_OFF);
  for (i=0; i<40; i++) /* max delay 400 ms. */
    if (framer_read(sc, Bt8370_BOP_STAT) & 0x80) SLEEP(10000);
  /* send 25 repetitions of bop_code */
  framer_write(sc, Bt8370_BOP, RBOP_OFF | TBOP_25);
  framer_write(sc, Bt8370_TBOP, bop_code); /* order matters */
  /* wait for tx to stop */
  for (i=0; i<40; i++) /* max delay 400 ms. */
    if (framer_read(sc, Bt8370_BOP_STAT) & 0x80) SLEEP(10000);
  /* Restore previous state of the BOP machine. */
  framer_write(sc, Bt8370_BOP, bop);
  }

static int  /* context: process */
t1_ioctl(softc_t *sc, struct ioctl *ioctl)
  {
  int error = 0;

  switch (ioctl->cmd)
    {
    case IOCTL_SNMP_SEND:  /* set opstatus? */
      {
      switch (ioctl->data)
        {
        case TSEND_NORMAL:
          {
          framer_write(sc, Bt8370_TPATT, 0x00); /* tx pattern generator off */
          framer_write(sc, Bt8370_RPATT, 0x00); /* rx pattern detector off */
          framer_write(sc, Bt8370_TLB,   0x00); /* tx inband generator off */
          break;
	  }
        case TSEND_LINE:
          {
          if (FORMAT_T1ESF)
            t1_send_bop(sc, T1BOP_LINE_UP);
          else if (FORMAT_T1SF)
            {
            framer_write(sc, Bt8370_LBP, 0x08); /* 10000 10000 ... */
            framer_write(sc, Bt8370_TLB, 0x05); /* 5 bits, framed, start */
	    }
          sc->status.snmp.t1.loop |= TLOOP_FAR_LINE;
          break;
	  }
        case TSEND_PAYLOAD:
          {
          t1_send_bop(sc, T1BOP_PAY_UP);
          sc->status.snmp.t1.loop |= TLOOP_FAR_PAYLOAD;
          break;
	  }
        case TSEND_RESET:
          {
          if (sc->status.snmp.t1.loop == TLOOP_FAR_LINE)
            {
            if (FORMAT_T1ESF)
              t1_send_bop(sc, T1BOP_LINE_DOWN);
            else if (FORMAT_T1SF)
              {
              framer_write(sc, Bt8370_LBP, 0x24); /* 100100 100100 ... */
              framer_write(sc, Bt8370_TLB, 0x09); /* 6 bits, framed, start */
	      }
            sc->status.snmp.t1.loop &= ~TLOOP_FAR_LINE;
	    }
          if (sc->status.snmp.t1.loop == TLOOP_FAR_PAYLOAD)
            {
            t1_send_bop(sc, T1BOP_PAY_DOWN);
            sc->status.snmp.t1.loop &= ~TLOOP_FAR_PAYLOAD;
	    }
          break;
	  }
        case TSEND_QRS:
          {
          framer_write(sc, Bt8370_TPATT, 0x1E); /* framed QRSS */
          break;
	  }
        default:
          {
          error = EINVAL;
          break;
	  }
	}
      break;
      }
    case IOCTL_SNMP_LOOP:  /* set opstatus = test? */
      {
      u_int8_t new_loop = 0;

      if      (ioctl->data == CFG_LOOP_NONE)
        new_loop = 0;
      else if (ioctl->data == CFG_LOOP_PAYLOAD)
        new_loop = LOOP_PAYLOAD;
      else if (ioctl->data == CFG_LOOP_LINE)
        new_loop = LOOP_LINE;
      else if (ioctl->data == CFG_LOOP_OTHER)
        new_loop = LOOP_ANALOG;
      else if (ioctl->data == CFG_LOOP_INWARD)
        new_loop = LOOP_FRAMER;
      else if (ioctl->data == CFG_LOOP_DUAL)
        new_loop = LOOP_DUAL;
      else
        error = EINVAL;
      if (!error)
        {
        framer_write(sc, Bt8370_LOOP, new_loop);
        sc->config.loop_back = ioctl->data;
	}
      break;
      }
    case IOCTL_SET_STATUS:
      {
#if 0
      if (ioctl->data)
        mii16_set_bits(sc, MII16_T1_XOE);
      else
        mii16_clr_bits(sc, MII16_T1_XOE);
#endif
      break;
      }
    default:
      error = EINVAL;
      break;
    }

  return error;
  }

/* Must not sleep. */
static void
t1_attach(softc_t *sc, struct config *config)
  {
  int i;
  u_int8_t pulse, lbo, gain;

  if (config == NULL) /* startup config */
    {
    /* Disable transmitter output drivers. */
    mii16_clr_bits(sc, MII16_T1_XOE);
    /* Bt8370 occasionally powers up in a loopback mode. */
    /* Data sheet says zero LOOP reg and do a sw-reset. */
    framer_write(sc, Bt8370_LOOP, 0x00); /* no loopback */
    framer_write(sc, Bt8370_CR0,  0x80); /* sw-reset */
    for (i=0; i<10; i++) /* wait for sw-reset to clear; max 10 ms */
      if (framer_read(sc, Bt8370_CR0) & 0x80) DELAY(1000);

    sc->status.card_type   = CSID_LMC_T1E1;
    sc->config.crc_len     = CFG_CRC_16;
    sc->config.loop_back   = CFG_LOOP_NONE;
    sc->config.tx_clk_src  = CFG_CLKMUX_RT; /* loop timed */
#if 1 /* USA */ /* decide using time zone? */
    sc->config.format      = CFG_FORMAT_T1ESF;
#else /* REST OF PLANET */
    sc->config.format      = CFG_FORMAT_E1FASCRC;
#endif
    sc->config.time_slots  = 0xFFFFFFFF;
    sc->config.cable_len   = 10;
    sc->config.tx_pulse    = CFG_PULSE_AUTO;
    sc->config.rx_gain_max = CFG_GAIN_AUTO;
    sc->config.tx_lbo      = CFG_LBO_AUTO;
    }
  else if (config != &sc->config) /* change config */
    {
    if ((sc->config.crc_len     == config->crc_len)     &&
        (sc->config.loop_back   == config->loop_back)   &&
        (sc->config.tx_clk_src  == config->tx_clk_src)  &&
        (sc->config.format      == config->format)      &&
        (sc->config.time_slots  == config->time_slots)  &&
        (sc->config.cable_len   == config->cable_len)   &&
        (sc->config.tx_pulse    == config->tx_pulse)    &&
        (sc->config.rx_gain_max == config->rx_gain_max) &&
        (sc->config.tx_lbo      == config->tx_lbo))
      return; /* nothing changed */
    sc->config.crc_len     = config->crc_len;
    sc->config.loop_back   = config->loop_back;
    sc->config.tx_clk_src  = config->tx_clk_src;
    sc->config.format      = config->format;
    sc->config.cable_len   = config->cable_len;
    sc->config.time_slots  = config->time_slots;
    sc->config.tx_pulse    = config->tx_pulse;
    sc->config.rx_gain_max = config->rx_gain_max;
    sc->config.tx_lbo      = config->tx_lbo;
    }

  /* Set CRC length. */
  if (sc->config.crc_len == CFG_CRC_32)
    mii16_set_bits(sc, MII16_T1_CRC32);
  else
    mii16_clr_bits(sc, MII16_T1_CRC32);

  /* Invert HDLC payload data in SF/AMI mode. */
  /* HDLC stuff bits satisfy T1 pulse density. */
  if (FORMAT_T1SF)
    mii16_set_bits(sc, MII16_T1_INVERT);
  else
    mii16_clr_bits(sc, MII16_T1_INVERT);

  /* Set the transmitter output impedance. */
  if (FORMAT_E1ANY) mii16_set_bits(sc, MII16_T1_Z);

  /* 001:CR0 -- Control Register 0 - T1/E1 and frame format */
  framer_write(sc, Bt8370_CR0, sc->config.format);

  /* 002:JAT_CR -- Jitter Attenuator Control Register */
  if (sc->config.tx_clk_src == CFG_CLKMUX_RT) /* loop timing */
    framer_write(sc, Bt8370_JAT_CR, 0xA3); /* JAT in RX path */
  else
    { /* 64-bit elastic store; free-running JCLK and CLADO */
    framer_write(sc, Bt8370_JAT_CR, 0x4B); /* assert jcenter */
    framer_write(sc, Bt8370_JAT_CR, 0x43); /* release jcenter */
    }

  /* 00C-013:IERn -- Interrupt Enable Registers */
  for (i=Bt8370_IER7; i<=Bt8370_IER0; i++)
    framer_write(sc, i, 0); /* no interrupts; polled */

  /* 014:LOOP -- loopbacks */
  if      (sc->config.loop_back == CFG_LOOP_PAYLOAD)
    framer_write(sc, Bt8370_LOOP, LOOP_PAYLOAD);
  else if (sc->config.loop_back == CFG_LOOP_LINE)
    framer_write(sc, Bt8370_LOOP, LOOP_LINE);
  else if (sc->config.loop_back == CFG_LOOP_OTHER)
    framer_write(sc, Bt8370_LOOP, LOOP_ANALOG);
  else if (sc->config.loop_back == CFG_LOOP_INWARD)
    framer_write(sc, Bt8370_LOOP, LOOP_FRAMER);
  else if (sc->config.loop_back == CFG_LOOP_DUAL)
    framer_write(sc, Bt8370_LOOP, LOOP_DUAL);
  else
    framer_write(sc, Bt8370_LOOP, 0x00); /* no loopback */

  /* 015:DL3_TS -- Data Link 3 */
  framer_write(sc, Bt8370_DL3_TS, 0x00); /* disabled */

  /* 018:PIO -- Programmable I/O */
  framer_write(sc, Bt8370_PIO, 0xFF); /* all pins are outputs */

  /* 019:POE -- Programmable Output Enable */
  framer_write(sc, Bt8370_POE, 0x00); /* all outputs are enabled */

  /* 01A;CMUX -- Clock Input Mux */
  if (sc->config.tx_clk_src == CFG_CLKMUX_EXT)
    framer_write(sc, Bt8370_CMUX, 0x0C); /* external timing */
  else
    framer_write(sc, Bt8370_CMUX, 0x0F); /* internal timing */

  /* 020:LIU_CR -- Line Interface Unit Config Register */
  framer_write(sc, Bt8370_LIU_CR, 0xC1); /* reset LIU, squelch */

  /* 022:RLIU_CR -- RX Line Interface Unit Config Reg */
  /* Errata sheet says do not use freeze-short, but we do anyway! */
  framer_write(sc, Bt8370_RLIU_CR, 0xB1); /* AGC=2048, Long Eye */

  /* Select Rx sensitivity based on cable length. */
  if ((gain = sc->config.rx_gain_max) == CFG_GAIN_AUTO)
    {
    if      (sc->config.cable_len > 2000)
      gain = CFG_GAIN_EXTEND;
    else if (sc->config.cable_len > 1000)
      gain = CFG_GAIN_LONG;
    else if (sc->config.cable_len > 100)
      gain = CFG_GAIN_MEDIUM;
    else
      gain = CFG_GAIN_SHORT;
    }

  /* 024:VGA_MAX -- Variable Gain Amplifier Max gain */
  framer_write(sc, Bt8370_VGA_MAX, gain);

  /* 028:PRE_EQ -- Pre Equalizer */
  if (gain == CFG_GAIN_EXTEND)
    framer_write(sc, Bt8370_PRE_EQ, 0xE6);  /* ON; thresh 6 */
  else
    framer_write(sc, Bt8370_PRE_EQ, 0xA6);  /* OFF; thresh 6 */

  /* 038-03C:GAINn -- RX Equalizer gain thresholds */
  framer_write(sc, Bt8370_GAIN0, 0x24);
  framer_write(sc, Bt8370_GAIN1, 0x28);
  framer_write(sc, Bt8370_GAIN2, 0x2C);
  framer_write(sc, Bt8370_GAIN3, 0x30);
  framer_write(sc, Bt8370_GAIN4, 0x34);

  /* 040:RCR0 -- Receiver Control Register 0 */
  if      (FORMAT_T1ESF)
    framer_write(sc, Bt8370_RCR0, 0x05); /* B8ZS, 2/5 FErrs */
  else if (FORMAT_T1SF)
    framer_write(sc, Bt8370_RCR0, 0x84); /* AMI,  2/5 FErrs */
  else if (FORMAT_E1NONE)
    framer_write(sc, Bt8370_RCR0, 0x41); /* HDB3, rabort */
  else if (FORMAT_E1CRC)
    framer_write(sc, Bt8370_RCR0, 0x09); /* HDB3, 3 FErrs or 915 CErrs */
  else  /* E1 no CRC */
    framer_write(sc, Bt8370_RCR0, 0x19); /* HDB3, 3 FErrs */

  /* 041:RPATT -- Receive Test Pattern configuration */
  framer_write(sc, Bt8370_RPATT, 0x3E); /* looking for framed QRSS */

  /* 042:RLB -- Receive Loop Back code detector config */
  framer_write(sc, Bt8370_RLB, 0x09); /* 6 bits down; 5 bits up */

  /* 043:LBA -- Loop Back Activate code */
  framer_write(sc, Bt8370_LBA, 0x08); /* 10000 10000 10000 ... */

  /* 044:LBD -- Loop Back Deactivate code */
  framer_write(sc, Bt8370_LBD, 0x24); /* 100100 100100 100100 ... */

  /* 045:RALM -- Receive Alarm signal configuration */
  framer_write(sc, Bt8370_RALM, 0x0C); /* yel_intg rlof_intg */

  /* 046:LATCH -- Alarm/Error/Counter Latch register */
  framer_write(sc, Bt8370_LATCH, 0x1F); /* stop_cnt latch_{cnt,err,alm} */

  /* Select Pulse Shape based on cable length (T1 only). */
  if ((pulse = sc->config.tx_pulse) == CFG_PULSE_AUTO)
    {
    if (FORMAT_T1ANY)
      {
      if      (sc->config.cable_len > 200)
        pulse = CFG_PULSE_T1CSU;
      else if (sc->config.cable_len > 160)
        pulse = CFG_PULSE_T1DSX4;
      else if (sc->config.cable_len > 120)
        pulse = CFG_PULSE_T1DSX3;
      else if (sc->config.cable_len > 80)
        pulse = CFG_PULSE_T1DSX2;
      else if (sc->config.cable_len > 40)
        pulse = CFG_PULSE_T1DSX1;
      else
        pulse = CFG_PULSE_T1DSX0;
      }
    else
      pulse = CFG_PULSE_E1TWIST;
    }

  /* Select Line Build Out based on cable length (T1CSU only). */
  if ((lbo = sc->config.tx_lbo) == CFG_LBO_AUTO)
    {
    if (pulse == CFG_PULSE_T1CSU)
      {
      if      (sc->config.cable_len > 1500)
        lbo = CFG_LBO_0DB;
      else if (sc->config.cable_len > 1000)
        lbo = CFG_LBO_7DB;
      else if (sc->config.cable_len >  500)
        lbo = CFG_LBO_15DB;
      else
        lbo = CFG_LBO_22DB;
      }
    else
      lbo = 0;
    }

  /* 068:TLIU_CR -- Transmit LIU Control Register */
  framer_write(sc, Bt8370_TLIU_CR, (0x40 | (lbo & 0x30) | (pulse & 0x0E)));

  /* 070:TCR0 -- Transmit Framer Configuration */
  framer_write(sc, Bt8370_TCR0, sc->config.format>>1);

  /* 071:TCR1 -- Transmitter Configuration */
  if (FORMAT_T1SF)
    framer_write(sc, Bt8370_TCR1, 0x43); /* tabort, AMI PDV enforced */
  else
    framer_write(sc, Bt8370_TCR1, 0x41); /* tabort, B8ZS or HDB3 */

  /* 072:TFRM -- Transmit Frame format       MYEL YEL MF FE CRC FBIT */
  if      (sc->config.format == CFG_FORMAT_T1ESF)
    framer_write(sc, Bt8370_TFRM, 0x0B); /*  -   YEL MF -  CRC FBIT */
  else if (sc->config.format == CFG_FORMAT_T1SF)
    framer_write(sc, Bt8370_TFRM, 0x19); /*  -   YEL MF -   -  FBIT */
  else if (sc->config.format == CFG_FORMAT_E1FAS)
    framer_write(sc, Bt8370_TFRM, 0x11); /*  -   YEL -  -   -  FBIT */
  else if (sc->config.format == CFG_FORMAT_E1FASCRC)
    framer_write(sc, Bt8370_TFRM, 0x1F); /*  -   YEL MF FE CRC FBIT */
  else if (sc->config.format == CFG_FORMAT_E1FASCAS)
    framer_write(sc, Bt8370_TFRM, 0x31); /* MYEL YEL -  -   -  FBIT */
  else if (sc->config.format == CFG_FORMAT_E1FASCRCCAS)
    framer_write(sc, Bt8370_TFRM, 0x3F); /* MYEL YEL MF FE CRC FBIT */
  else if (sc->config.format == CFG_FORMAT_E1NONE)
    framer_write(sc, Bt8370_TFRM, 0x00); /* NO FRAMING BITS AT ALL! */

  /* 073:TERROR -- Transmit Error Insert */
  framer_write(sc, Bt8370_TERROR, 0x00); /* no errors, please! */

  /* 074:TMAN -- Transmit Manual Sa-byte/FEBE configuration */
  framer_write(sc, Bt8370_TMAN, 0x00); /* none */

  /* 075:TALM -- Transmit Alarm Signal Configuration */
  if (FORMAT_E1ANY)
    framer_write(sc, Bt8370_TALM, 0x38); /* auto_myel auto_yel auto_ais */
  else if (FORMAT_T1ANY)
    framer_write(sc, Bt8370_TALM, 0x18); /* auto_yel auto_ais */

  /* 076:TPATT -- Transmit Test Pattern Configuration */
  framer_write(sc, Bt8370_TPATT, 0x00); /* disabled */

  /* 077:TLB -- Transmit Inband Loopback Code Configuration */
  framer_write(sc, Bt8370_TLB, 0x00); /* disabled */

  /* 090:CLAD_CR -- Clack Rate Adapter Configuration */
  if (FORMAT_T1ANY)
    framer_write(sc, Bt8370_CLAD_CR, 0x06); /* loop filter gain 1/2^6 */
  else
    framer_write(sc, Bt8370_CLAD_CR, 0x08); /* loop filter gain 1/2^8 */

  /* 091:CSEL -- CLAD frequency Select */
  if (FORMAT_T1ANY)
    framer_write(sc, Bt8370_CSEL, 0x55); /* 1544 kHz */
  else
    framer_write(sc, Bt8370_CSEL, 0x11); /* 2048 kHz */

  /* 092:CPHASE -- CLAD Phase detector */
  if (FORMAT_T1ANY)
    framer_write(sc, Bt8370_CPHASE, 0x22); /* phase compare @  386 kHz */
  else
    framer_write(sc, Bt8370_CPHASE, 0x00); /* phase compare @ 2048 kHz */

  if (FORMAT_T1ESF) /* BOP & PRM are enabled in T1ESF mode only. */
    {
    /* 0A0:BOP -- Bit Oriented Protocol messages */
    framer_write(sc, Bt8370_BOP, RBOP_25 | TBOP_OFF);
    /* 0A4:DL1_TS -- Data Link 1 Time Slot Enable */
    framer_write(sc, Bt8370_DL1_TS, 0x40); /* FDL bits in odd frames */
    /* 0A6:DL1_CTL -- Data Link 1 Control */
    framer_write(sc, Bt8370_DL1_CTL, 0x03); /* FCS mode, TX on, RX on */
    /* 0A7:RDL1_FFC -- Rx Data Link 1 Fifo Fill Control */
    framer_write(sc, Bt8370_RDL1_FFC, 0x30); /* assert "near full" at 48 */
    /* 0AA:PRM -- Performance Report Messages */
    framer_write(sc, Bt8370_PRM, 0x80);
    }

  /* 0D0:SBI_CR -- System Bus Interface Configuration Register */
  if (FORMAT_T1ANY)
    framer_write(sc, Bt8370_SBI_CR, 0x47); /* 1.544 with 24 TS +Fbits */
  else
    framer_write(sc, Bt8370_SBI_CR, 0x46); /* 2.048 with 32 TS */

  /* 0D1:RSB_CR -- Receive System Bus Configuration Register */
  /* Change RINDO & RFSYNC on falling edge of RSBCLKI. */
  framer_write(sc, Bt8370_RSB_CR, 0x70);

  /* 0D2,0D3:RSYNC_{TS,BIT} -- Receive frame Sync offset */
  framer_write(sc, Bt8370_RSYNC_BIT, 0x00);
  framer_write(sc, Bt8370_RSYNC_TS,  0x00);

  /* 0D4:TSB_CR -- Transmit System Bus Configuration Register */
  /* Change TINDO & TFSYNC on falling edge of TSBCLKI. */
  framer_write(sc, Bt8370_TSB_CR, 0x30);

  /* 0D5,0D6:TSYNC_{TS,BIT} -- Transmit frame Sync offset */
  framer_write(sc, Bt8370_TSYNC_BIT, 0x00);
  framer_write(sc, Bt8370_TSYNC_TS,  0x00);

  /* 0D7:RSIG_CR -- Receive SIGnalling Configuration Register */
  framer_write(sc, Bt8370_RSIG_CR, 0x00);

  /* Assign and configure 64Kb TIME SLOTS. */
  /* TS24..TS1 must be assigned for T1, TS31..TS0 for E1. */
  /* Timeslots with no user data have RINDO and TINDO off. */
  for (sc->status.time_slots = 0, i=0; i<32; i++)
    {
    /* 0E0-0FF:SBCn -- System Bus Per-Channel Control */
    if      (FORMAT_T1ANY && (i==0 || i>24))
      framer_write(sc, Bt8370_SBCn +i, 0x00); /* not assigned in T1 mode */
    else if (FORMAT_E1ANY && (i==0)  && !FORMAT_E1NONE)
      framer_write(sc, Bt8370_SBCn +i, 0x01); /* assigned, TS0  o/h bits */
    else if (FORMAT_E1CAS && (i==16) && !FORMAT_E1NONE)
      framer_write(sc, Bt8370_SBCn +i, 0x01); /* assigned, TS16 o/h bits */
    else if ((sc->status.time_slots |= (sc->config.time_slots & (1<<i))))
      framer_write(sc, Bt8370_SBCn +i, 0x0D); /* assigned, RINDO, TINDO */
    else
      framer_write(sc, Bt8370_SBCn +i, 0x01); /* assigned, idle */

    /* 100-11F:TPCn -- Transmit Per-Channel Control */
    if      (FORMAT_E1CAS && (i==0))
      framer_write(sc, Bt8370_TPCn +i, 0x30); /* tidle, sig=0000 (MAS) */
    else if (FORMAT_E1CAS && (i==16))
      framer_write(sc, Bt8370_TPCn +i, 0x3B); /* tidle, sig=1011 (XYXX) */
    else if ((sc->config.time_slots & (1<<i)) == 0)
      framer_write(sc, Bt8370_TPCn +i, 0x20); /* tidle: use TSLIP_LOn */
    else
      framer_write(sc, Bt8370_TPCn +i, 0x00); /* nothing special */

    /* 140-15F:TSLIP_LOn -- Transmit PCM Slip Buffer */
    framer_write(sc, Bt8370_TSLIP_LOn +i, 0x7F); /* idle chan data */
    /* 180-19F:RPCn -- Receive Per-Channel Control */
    framer_write(sc, Bt8370_RPCn +i, 0x00);   /* nothing special */
    }

  /* Enable transmitter output drivers. */
  mii16_set_bits(sc, MII16_T1_XOE);
  }

static void
t1_detach(softc_t *sc)
  {
  led_on(sc, MII16_LED_ALL);
  }

/* End T1E1 card code */

#if NETGRAPH /* FreeBSD */

static struct stack netgraph_stack =
  {
  .ioctl    = netgraph_ioctl,
  .input    = netgraph_input,
  .output   = netgraph_output,
  .watchdog = netgraph_watchdog,
  .open     = netgraph_open,
  .attach   = netgraph_attach,
  .detach   = netgraph_detach,
  };

static int  /* context: process */
netgraph_ioctl(softc_t *sc, u_long cmd, void *data)
  {
  if (sc->config.debug)
    printf("%s: netgraph_ioctl() was called\n", NAME_UNIT);

  return EINVAL;
  }

static void  /* context: interrupt */
netgraph_input(softc_t *sc, struct mbuf *m)
  {
  int error;  /* ignore error */

  NG_SEND_DATA_ONLY(error, sc->ng_hook, m);
  }

static void  /* context: interrupt */
netgraph_output(softc_t *sc)
  {
  if (!IFQ_IS_EMPTY(&sc->ng_fastq))
    IFQ_DEQUEUE(&sc->ng_fastq, sc->tx_mbuf);
  else
    IFQ_DEQUEUE(&sc->ng_sndq,  sc->tx_mbuf);
  }

static void  /* context: softirq */
netgraph_watchdog(softc_t *sc)
  {
  sc->status.stack = STACK_NETGRAPH;
  sc->status.proto = PROTO_NONE;
  }

static int
netgraph_open(softc_t *sc, struct config *config)
  {
  sc->config.proto = PROTO_NONE;

  return 0;
  }

static int
netgraph_attach(softc_t *sc, struct config *config)
  {
  int error;

  if ((error = ng_attach(sc)))
    return error;

  sc->config.proto = PROTO_NONE;
  sc->stack = &netgraph_stack;

  return 0;
  }

static int  /* never fails */
netgraph_detach(softc_t *sc)
  {
  ng_detach(sc);

  sc->config.stack = STACK_NONE;
  sc->config.proto = PROTO_NONE;
  sc->stack = NULL;

  return 0;
  }

#endif /* NETGRAPH */

#if SYNC_PPP  /* Linux */

static struct stack sync_ppp_stack =
  {
  .ioctl    = sync_ppp_ioctl,
  .type     = sync_ppp_type,
  .mtu      = sync_ppp_mtu,
  .watchdog = sync_ppp_watchdog,
  .open     = sync_ppp_open,
  .attach   = sync_ppp_attach,
  .detach   = sync_ppp_detach,
  };

static int  /* context: process */
sync_ppp_ioctl(softc_t *sc, struct ifreq *ifr, int cmd)
  {
  return sppp_do_ioctl(sc->netdev, ifr, cmd);
  }

static int  /* context: interrupt */
sync_ppp_type(softc_t *sc, struct sk_buff *skb)
  {
  return htons(ETH_P_WAN_PPP);
  }

static int  /* context: process */
sync_ppp_mtu(softc_t *sc, int mtu)
  {
  return ((mtu < 128) || (mtu > PPP_MTU)) ? -EINVAL : 0;
  }

static void  /* context: softirq */
sync_ppp_watchdog(softc_t *sc)
  {
  /* Notice when the link comes up. */
  if ((sc->last_link_state != STATE_UP) &&
    (sc->status.link_state == STATE_UP))
    sppp_reopen(sc->netdev);

  /* Notice when the link goes down. */
  if ((sc->last_link_state == STATE_UP) &&
    (sc->status.link_state != STATE_UP))
    sppp_close(sc->netdev);

  /* Report current line protocol. */
  sc->status.stack = STACK_SYNC_PPP;
  if (sc->sppp->pp_flags & PP_CISCO)
    sc->status.proto = PROTO_C_HDLC;
  else
    sc->status.proto = PROTO_PPP;

  /* Report keep-alive status. */
  sc->status.keep_alive = sc->sppp->pp_flags & PP_KEEPALIVE;
  }

static int  /* never fails */
sync_ppp_open(softc_t *sc, struct config *config)
  {
  /* Refresh the keep_alive flag. */
  if (config->keep_alive)
    sc->sppp->pp_flags |=  PP_KEEPALIVE;
  else
    sc->sppp->pp_flags &= ~PP_KEEPALIVE;
  sc->config.keep_alive = config->keep_alive;

  /* Done if proto is not changing. */
  if (config->proto == sc->config.proto)
    return 0;

  /* Close */
  sppp_close(sc->netdev);

  /* Change line protocol. */
  switch (config->proto)
    {
    case PROTO_PPP:
      sc->sppp->pp_flags  &= ~PP_CISCO;
      sc->netdev->type = ARPHRD_PPP;
      sc->config.proto = PROTO_PPP;
      break;
    default:
    case PROTO_C_HDLC:
      sc->sppp->pp_flags  |=  PP_CISCO;
      sc->netdev->type = ARPHRD_CISCO;
      sc->config.proto = PROTO_C_HDLC;
      break;
    }

  /* Open */
  sppp_open(sc->netdev);

  return 0;
  }

static int  /* never fails */
sync_ppp_attach(softc_t *sc, struct config *config)
  {
  sc->ppd = &sc->ppp_dev;      /* struct ppp_device*  */
  sc->netdev->priv = &sc->ppd; /* struct ppp_device** */
  sc->ppp_dev.dev = sc->netdev;
  sc->sppp = &sc->ppp_dev.sppp;

  sppp_attach(&sc->ppp_dev);
  sc->netdev->do_ioctl = netdev_ioctl;
  config->keep_alive = 1;

  sc->config.stack = STACK_SYNC_PPP;
  sc->stack = &sync_ppp_stack;

  return 0;
  }

static int  /* never fails */
sync_ppp_detach(softc_t *sc)
  {
  sppp_close(sc->netdev);
  sppp_detach(sc->netdev);

  netdev_setup(sc->netdev);
  sc->config.stack = STACK_NONE;
  sc->config.proto = PROTO_NONE;
  sc->stack = NULL;

  return 0;
  }

#endif /* SYNC_PPP */

#if GEN_HDLC  /* Linux only */

static struct stack gen_hdlc_stack =
  {
  .ioctl    = gen_hdlc_ioctl,
  .type     = gen_hdlc_type,
  .mtu      = gen_hdlc_mtu,
  .watchdog = gen_hdlc_watchdog,
  .open     = gen_hdlc_open,
  .attach   = gen_hdlc_attach,
  .detach   = gen_hdlc_detach,
  };

static int  /* context: process */
gen_hdlc_ioctl(softc_t *sc, struct ifreq *ifr, int cmd)
  {
  te1_settings settings;
  int error = 0;

  if (cmd == SIOCWANDEV)
    switch (ifr->ifr_settings.type)
      {
      case IF_GET_IFACE: /* get interface config */
        {
        unsigned int size;

        /* NOTE: This assumes struct sync_serial_settings has the */
        /* same layout as the first part of struct te1_settings. */
        if (sc->status.card_type == CSID_LMC_T1E1)
          {
          if (FORMAT_T1ANY) ifr->ifr_settings.type = IF_IFACE_T1;
          if (FORMAT_E1ANY) ifr->ifr_settings.type = IF_IFACE_E1;
          size = sizeof(te1_settings);
	  }
        else
          {
          ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
          size = sizeof(sync_serial_settings);
	  }
        if (ifr->ifr_settings.size < size)
          {
          ifr->ifr_settings.size = size;
          return -ENOBUFS;
	  }
        ifr->ifr_settings.size = size;

        if (sc->config.tx_clk_src == CFG_CLKMUX_ST)
          settings.clock_type = CLOCK_EXT;
        if (sc->config.tx_clk_src == CFG_CLKMUX_INT)
          settings.clock_type = CLOCK_TXINT;
        if (sc->config.tx_clk_src == CFG_CLKMUX_RT)
          settings.clock_type = CLOCK_TXFROMRX;
        settings.loopback = (sc->config.loop_back != CFG_LOOP_NONE) ? 1:0;
        settings.clock_rate = sc->status.tx_speed;
        if (sc->status.card_type == CSID_LMC_T1E1)
          settings.slot_map = sc->status.time_slots;

        error = copy_to_user(ifr->ifr_settings.ifs_ifsu.te1,
         &settings, size);
        break;
        }
      case IF_IFACE_SYNC_SERIAL: /* set interface config */
      case IF_IFACE_T1:
      case IF_IFACE_E1:
        {
        struct config config = sc->config;

        if (!capable(CAP_NET_ADMIN)) return -EPERM;
        if (ifr->ifr_settings.size > sizeof(te1_settings))
          return -ENOBUFS;
        error = copy_from_user(&settings,
         ifr->ifr_settings.ifs_ifsu.te1, sizeof(te1_settings));

        if      (settings.clock_type == CLOCK_EXT)
          config.tx_clk_src = CFG_CLKMUX_ST;
        else if (settings.clock_type == CLOCK_TXINT)
          config.tx_clk_src = CFG_CLKMUX_INT;
        else if (settings.clock_type == CLOCK_TXFROMRX)
          config.tx_clk_src = CFG_CLKMUX_RT;

        if (settings.loopback)
          config.loop_back = CFG_LOOP_TULIP;
        else
          config.loop_back = CFG_LOOP_NONE;

        tulip_loop(sc, &config);
        sc->card->attach(sc, &config);
        break;
        }
      default:  /* Pass the rest to the line pkg. */
        {
        error = hdlc_ioctl(sc->netdev, ifr, cmd);
        break;
        }
      }
    else
      error = -EINVAL;

  return error;
  }

static int  /* context: interrupt */
gen_hdlc_type(softc_t *sc, struct sk_buff *skb)
  {
  return hdlc_type_trans(skb, sc->netdev);
  }

static int  /* context: process */
gen_hdlc_mtu(softc_t *sc, int mtu)
  {
  return ((mtu < 68) || (mtu > HDLC_MAX_MTU)) ? -EINVAL : 0;
  }

static void  /* context: softirq */
gen_hdlc_watchdog(softc_t *sc)
  {
  /* Notice when the link comes up. */
  if ((sc->last_link_state != STATE_UP) &&
    (sc->status.link_state == STATE_UP))
    hdlc_set_carrier(1, sc->netdev);

  /* Notice when the link goes down. */
  if ((sc->last_link_state == STATE_UP) &&
    (sc->status.link_state != STATE_UP))
    hdlc_set_carrier(0, sc->netdev);

  /* Report current line protocol. */
  sc->status.stack = STACK_GEN_HDLC;
  switch (sc->hdlcdev->proto.id)
    {
    case IF_PROTO_PPP:
      {
      struct sppp* sppp = &sc->hdlcdev->state.ppp.pppdev.sppp;
      sc->status.keep_alive = sppp->pp_flags & PP_KEEPALIVE;
      sc->status.proto = PROTO_PPP;
      break;
      }
    case IF_PROTO_CISCO:
      sc->status.proto = PROTO_C_HDLC;
      break;
    case IF_PROTO_FR:
      sc->status.proto = PROTO_FRM_RLY;
      break;
    case IF_PROTO_HDLC:
      sc->status.proto = PROTO_IP_HDLC;
      break;
    case IF_PROTO_X25:
      sc->status.proto = PROTO_X25;
      break;
    case IF_PROTO_HDLC_ETH:
      sc->status.proto = PROTO_ETH_HDLC;
      break;
    default:
      sc->status.proto = PROTO_NONE;
      break;
    }
  }

static int
gen_hdlc_open(softc_t *sc, struct config *config)
  {
  int error = 0;

  /* Refresh the keep_alive flag. */
  if (sc->hdlcdev->proto.id == IF_PROTO_PPP)
    {
    struct sppp* sppp = &sc->hdlcdev->state.ppp.pppdev.sppp;
    if (config->keep_alive)
      sppp->pp_flags |=  PP_KEEPALIVE;
    else
      sppp->pp_flags &= ~PP_KEEPALIVE;
    sc->config.keep_alive = config->keep_alive;
    }

  /* Done if proto is not changing. */
  if (config->proto == sc->config.proto)
    return 0;

  /* Close */
  hdlc_close(sc->netdev);

  /* Generic-HDLC gets protocol params using copy_from_user().
   * This is a problem for a kernel-resident device driver.
   * Luckily, PPP does not need any params so no copy_from_user().
   */

  /* Change line protocol. */
  if (config->proto == PROTO_PPP)
    {
    struct ifreq ifr;
    ifr.ifr_settings.size = 0;
    ifr.ifr_settings.type = IF_PROTO_PPP;
    hdlc_ioctl(sc->netdev, &ifr, SIOCWANDEV);
    }
  /* Changing to any protocol other than PPP */
  /*  requires using the 'sethdlc' program. */

  /* Open */
  if ((error = hdlc_open(sc->netdev)))
    {
    if (sc->config.debug)
      printk("%s: hdlc_open(): error %d\n", NAME_UNIT, error);
    if (error == -ENOSYS)
      printk("%s: Try 'sethdlc %s hdlc|ppp|cisco|fr'\n",
       NAME_UNIT, NAME_UNIT);
    sc->config.proto = PROTO_NONE;
    }
  else
    sc->config.proto = config->proto;

  return error;
  }

static int  /* never fails */
gen_hdlc_attach(softc_t *sc, struct config *config)
  {
  sc->netdev->priv = sc->hdlcdev;

  /* hdlc_attach(sc->netdev); */
  sc->netdev->mtu = HDLC_MAX_MTU;
  sc->hdlcdev->attach = gen_hdlc_card_params;
  sc->hdlcdev->xmit = netdev_start;
  config->keep_alive = 1;

  sc->config.stack = STACK_GEN_HDLC;
  sc->stack = &gen_hdlc_stack;

  return 0;
  }

static int  /* never fails */
gen_hdlc_detach(softc_t *sc)
  {
  hdlc_close(sc->netdev);
  /* hdlc_detach(sc->netdev); */
  hdlc_proto_detach(sc->hdlcdev);
  memset(&sc->hdlcdev->proto, 0, sizeof sc->hdlcdev->proto);

  netdev_setup(sc->netdev);
  sc->config.stack = STACK_NONE;
  sc->config.proto = PROTO_NONE;
  sc->stack = NULL;

  return 0;
  }

static int
gen_hdlc_card_params(struct net_device *netdev,
 unsigned short encoding, unsigned short parity)
  {
  softc_t *sc = NETDEV2SC(netdev);
  struct config config = sc->config;

  /* Encoding does not seem to apply to synchronous interfaces, */
  /* but Parity seems to be generic-HDLC's name for CRC. */
  if (parity == PARITY_CRC32_PR1_CCITT)
    config.crc_len = CFG_CRC_32;
  if (parity == PARITY_CRC16_PR1_CCITT)
    config.crc_len = CFG_CRC_16;
  sc->card->attach(sc, &config);

  return 0;
  }

#endif /* GEN_HDLC */

#if P2P  /* BSD/OS */

static struct stack p2p_stack =
  {
  .ioctl    = p2p_stack_ioctl,
  .input    = p2p_stack_input,
  .output   = p2p_stack_output,
  .watchdog = p2p_stack_watchdog,
  .open     = p2p_stack_open,
  .attach   = p2p_stack_attach,
  .detach   = p2p_stack_detach,
  };

static int  /* context: process */
p2p_stack_ioctl(softc_t *sc, u_long cmd, void *data)
  {
  return p2p_ioctl(sc->ifp, cmd, data);
  }

static void  /* context: interrupt */
p2p_stack_input(softc_t *sc, struct mbuf *mbuf)
  {
  struct mbuf *new_mbuf = mbuf;

  while (new_mbuf)
    {
    sc->p2p->p2p_hdrinput(sc->p2p, new_mbuf->m_data, new_mbuf->m_len);
    new_mbuf = new_mbuf->m_next;
    }
  sc->p2p->p2p_input(sc->p2p, NULL);
  m_freem(mbuf);
  }

static void  /* context: interrupt */
p2p_stack_output(softc_t *sc)
  {
  if (!IFQ_IS_EMPTY(&sc->p2p->p2p_isnd))
    IFQ_DEQUEUE(&sc->p2p->p2p_isnd, sc->tx_mbuf);
  else
    IFQ_DEQUEUE(&sc->ifp->if_snd,   sc->tx_mbuf);
  }

static void  /* context: softirq */
p2p_stack_watchdog(softc_t *sc)
  {
  /* Notice change in link status. */
  if ((sc->last_link_state != sc->status.link_state) &&
   /* if_slowtimo() can run before raw_init() has inited rawcb. */
   (sc->p2p->p2p_modem != NULL) && (rawcb.rcb_next != NULL))
    (*sc->p2p->p2p_modem)(sc->p2p, sc->status.link_state==STATE_UP);

  /* Report current line protocol. */
  sc->status.stack = STACK_P2P;
  switch (sc->ifp->if_type)
    {
    case IFT_PPP:
      sc->status.proto = PROTO_PPP;
      break;
    case IFT_PTPSERIAL:
      sc->status.proto = PROTO_C_HDLC;
      break;
    case IFT_FRELAY:
      sc->status.proto = PROTO_FRM_RLY;
      break;
    default:
      sc->status.proto = PROTO_NONE;
      break;
    }
  }

static int
p2p_stack_open(softc_t *sc, struct config *config)
  {
  int error = 0;

  /* Done if proto is not changing. */
  if (config->proto == sc->config.proto)
    return 0;

  if (error = p2p_stack_detach(sc))
    return error;

  /* Change line protocol. */
  switch (config->proto)
    {
    case PROTO_PPP:
      sc->ifp->if_type = IFT_PPP;
      sc->config.proto = PROTO_PPP;
      break;
    case PROTO_C_HDLC:
      sc->ifp->if_type = IFT_PTPSERIAL;
      sc->config.proto = PROTO_C_HDLC;
      break;
    case PROTO_FRM_RLY:
      sc->ifp->if_type = IFT_FRELAY;
      sc->config.proto = PROTO_FRM_RLY;
      break;
    default:
    case PROTO_NONE:
      sc->ifp->if_type = IFT_NONE;
      sc->config.proto = PROTO_NONE;
      return 0;
    }

  error = p2p_stack_attach(sc, config);

  return error;
  }

static int
p2p_stack_attach(softc_t *sc, struct config *config)
  {
  int error;

  sc->p2p = &sc->p2pcom;
  sc->p2p->p2p_proto = 0; /* force p2p_attach to re-init */

  if ((error = p2p_attach(sc->p2p))) /* calls bpfattach() */
    {
    if (sc->config.debug)
      printf("%s: p2p_attach(): error %d\n", NAME_UNIT, error);
    if (error == EPFNOSUPPORT)
      printf("%s: Try 'ifconfig %s linktype ppp|frelay|chdlc'\n",
       NAME_UNIT, NAME_UNIT);
    sc->config.stack = STACK_NONE; /* not attached to P2P */
    return error;
    }
  sc->p2p->p2p_mdmctl = p2p_mdmctl;
  sc->p2p->p2p_getmdm = p2p_getmdm;

  sc->config.stack = STACK_P2P;
  sc->stack = &p2p_stack;

  return 0;
  }

static int
p2p_stack_detach(softc_t *sc)
  {
  int error = 0;

  if ((error = p2p_detach(sc->p2p))) /* calls bfpdetach() */
    {
    if (sc->config.debug)
      printf("%s: p2p_detach(): error %d\n",  NAME_UNIT, error);
    if (error == EBUSY)
      printf("%s: Try 'ifconfig %s down -remove'\n",
       NAME_UNIT, NAME_UNIT);
    sc->config.stack = STACK_P2P; /* still attached to P2P */
    return error;
    }

  ifnet_setup(sc->ifp);
  sc->config.stack = STACK_NONE;
  sc->config.proto = PROTO_NONE;
  sc->stack = NULL;

  return error;
  }

/* Callout from P2P: */
/* Get the state of DCD (Data Carrier Detect). */
static int  /* never fails */
p2p_getmdm(struct p2pcom *p2p, void *result)
  {
  softc_t *sc = IFP2SC(&p2p->p2p_if);

  /* Non-zero is not good enough; TIOCM_CAR is 0x40. */
  *(int *)result = (sc->status.link_state==STATE_UP) ? TIOCM_CAR : 0;

  return 0;
  }

/* Callout from P2P: */
/* Set the state of DTR (Data Terminal Ready). */
static int  /* never fails */
p2p_mdmctl(struct p2pcom *p2p, int flag)
  {
  softc_t *sc = IFP2SC(&p2p->p2p_if);

  set_ready(sc, flag);

  return 0;
  }

#endif /* P2P */

#if SPPP  /* FreeBSD, NetBSD, OpenBSD */

static struct stack sppp_stack =
  {
  .ioctl    = sppp_stack_ioctl,
  .input    = sppp_stack_input,
  .output   = sppp_stack_output,
  .watchdog = sppp_stack_watchdog,
  .open     = sppp_stack_open,
  .attach   = sppp_stack_attach,
  .detach   = sppp_stack_detach,
  };

# if !defined(PP_FR)
#  define PP_FR 0
# endif
# if !defined(DLT_C_HDLC)
#  define DLT_C_HDLC DLT_PPP
# endif
# if !defined(DLT_FRELAY)
#  define DLT_FRELAY DLT_PPP
# endif

static int  /* context: process */
sppp_stack_ioctl(softc_t *sc, u_long cmd, void *data)
  {
  return sppp_ioctl(sc->ifp, cmd, data);
  }

static void  /* context: interrupt */
sppp_stack_input(softc_t *sc, struct mbuf *mbuf)
  {
  sppp_input(sc->ifp, mbuf);
  }

static void  /* context: interrupt */
sppp_stack_output(softc_t *sc)
  {
  sc->tx_mbuf = sppp_dequeue(sc->ifp);
  }

static void  /* context: softirq */
sppp_stack_watchdog(softc_t *sc)
  {
  /* Notice when the link comes up. */
  if ((sc->last_link_state != STATE_UP) &&
    (sc->status.link_state == STATE_UP))
    sppp_tls(sc->sppp);

  /* Notice when the link goes down. */
  if ((sc->last_link_state == STATE_UP) &&
    (sc->status.link_state != STATE_UP))
    sppp_tlf(sc->sppp);

  /* Report current line protocol. */
  sc->status.stack = STACK_SPPP;
# if defined(__FreeBSD__)
  if (sc->sppp->pp_flags & PP_FR)
    sc->status.proto = PROTO_FRM_RLY;
  else if (sc->ifp->if_flags  & IFF_LINK2)
# elif defined(__NetBSD__) || defined(__OpenBSD__)
  if (sc->sppp->pp_flags & PP_CISCO)
# endif
    sc->status.proto = PROTO_C_HDLC;
  else
    sc->status.proto = PROTO_PPP;

  /* Report keep-alive status. */
  sc->status.keep_alive = sc->sppp->pp_flags & PP_KEEPALIVE;
  }

static int  /* never fails */
sppp_stack_open(softc_t *sc, struct config *config)
  {
  /* Refresh the keep_alive flag. */
  if (config->keep_alive)
    sc->sppp->pp_flags |=  PP_KEEPALIVE;
  else
    sc->sppp->pp_flags &= ~PP_KEEPALIVE;
  sc->config.keep_alive = config->keep_alive;

  /* Done if proto is not changing. */
  if (config->proto == sc->config.proto)
    return 0;

  /* Close */
  sc->ifp->if_flags &= ~IFF_UP; /* down */
  sppp_ioctl(sc->ifp, SIOCSIFFLAGS, NULL);

  /* Change line protocol. */
  LMC_BPF_DETACH(sc);
  switch (config->proto)
    {
    case PROTO_PPP:
      {
# if defined(__FreeBSD__)
      sc->ifp->if_flags  &= ~IFF_LINK2;
      sc->sppp->pp_flags &= ~PP_FR;
# elif defined(__NetBSD__) || defined(__OpenBSD__)
      sc->sppp->pp_flags &= ~PP_CISCO;
# endif
      LMC_BPF_ATTACH(sc, DLT_PPP, 4);
      sc->config.proto = PROTO_PPP;
      break;
      }

    default:
    case PROTO_C_HDLC:
      {
# if defined(__FreeBSD__)
      sc->ifp->if_flags  |=  IFF_LINK2;
      sc->sppp->pp_flags &= ~PP_FR;
# elif defined(__NetBSD__) || defined(__OpenBSD__)
      sc->sppp->pp_flags |=  PP_CISCO;
# endif
      LMC_BPF_ATTACH(sc, DLT_C_HDLC, 4);
      sc->config.proto = PROTO_C_HDLC;
      break;
      }

# if defined(__FreeBSD__)
    case PROTO_FRM_RLY:
      {
      sc->ifp->if_flags  &= ~IFF_LINK2;
      sc->sppp->pp_flags |= PP_FR;
      sc->sppp->pp_flags |= PP_KEEPALIVE;
      sc->config.keep_alive = 1;
      LMC_BPF_ATTACH(sc, DLT_FRELAY, 4);
      sc->config.proto = PROTO_FRM_RLY;
      break;
      }
# endif
    } /* switch(config->proto) */

  /* Open */
  sc->ifp->if_flags |= IFF_UP; /* up and not running */
  sppp_ioctl(sc->ifp, SIOCSIFFLAGS, NULL);

  return 0;
  }

static int  /* never fails */
sppp_stack_attach(softc_t *sc, struct config *config)
  {
# if defined(__FreeBSD__)
  sc->sppp = sc->ifp->if_l2com;
# else
  sc->sppp = &sc->spppcom;
# endif

  LMC_BPF_ATTACH(sc, DLT_RAW, 0);
  sppp_attach(sc->ifp);
  sc->sppp->pp_tls = sppp_tls;
  sc->sppp->pp_tlf = sppp_tlf;
  config->keep_alive = 1;

  sc->config.stack = STACK_SPPP;
  sc->stack = &sppp_stack;

  return 0;
  }

static int  /* never fails */
sppp_stack_detach(softc_t *sc)
  {
  sc->ifp->if_flags &= ~IFF_UP; /* down */
  sppp_ioctl(sc->ifp, SIOCSIFFLAGS, NULL); /* close() */
  sppp_detach(sc->ifp);
  LMC_BPF_DETACH(sc);

  ifnet_setup(sc->ifp);
  sc->config.stack = STACK_NONE;
  sc->config.proto = PROTO_NONE;
  sc->stack = NULL;

  return 0;
  }

/* Callout from SPPP: */
static void
sppp_tls(struct sppp *sppp)
  {
  /* Calling pp_up/down() required by PPP mode in OpenBSD. */
  /* Calling pp_up/down() panics      PPP mode in NetBSD. */
  /* Calling pp_up/down() breaks    Cisco mode in FreeBSD. */
# if defined(__FreeBSD__)
  if (!(sppp->pp_mode  & IFF_LINK2) && /* not Cisco */
      !(sppp->pp_flags & PP_FR))       /* not FrmRly */
# elif defined(__NetBSD__) || defined(__OpenBSD__)
  if (!(sppp->pp_flags & PP_CISCO))    /* not Cisco */
# endif
    sppp->pp_up(sppp);
  }

/* Callout from SPPP: */
static void
sppp_tlf(struct sppp *sppp)
  {
  /* Calling pp_up/down() required by PPP mode in OpenBSD. */
  /* Calling pp_up/down() panics      PPP mode in NetBSD. */
  /* Calling pp_up/down() breaks    Cisco mode in FreeBSD. */
# if defined(__FreeBSD__)
  if (!(sppp->pp_mode  & IFF_LINK2) && /* not Cisco */
      !(sppp->pp_flags & PP_FR))       /* not FrmRly */
# elif defined(__NetBSD__) || defined(__OpenBSD__)
  if (!(sppp->pp_flags & PP_CISCO))    /* not Cisco */
# endif
    sppp->pp_down(sppp);
  }

#endif /* SPPP */

/* RawIP is built into the driver. */

static struct stack rawip_stack =
  {
#if IFNET
  .ioctl    = rawip_ioctl,
  .input    = rawip_input,
  .output   = rawip_output,
#elif NETDEV
  .ioctl    = rawip_ioctl,
  .type     = rawip_type,
  .mtu      = rawip_mtu,
#endif
  .watchdog = rawip_watchdog,
  .open     = rawip_open,
  .attach   = rawip_attach,
  .detach   = rawip_detach,
  };

#if IFNET

static int  /* context: process */
rawip_ioctl(softc_t *sc, u_long cmd, void *data)
  {
  struct ifreq *ifr = (struct ifreq *) data;
  int error = 0;

  switch (cmd)
    {
    case SIOCADDMULTI:
    case SIOCDELMULTI:
      if (sc->config.debug)
        printf("%s: rawip_ioctl: SIOCADD/DELMULTI\n", NAME_UNIT);
    case SIOCAIFADDR:
    case SIOCSIFFLAGS:
    case SIOCSIFDSTADDR:
      break;
    case SIOCSIFADDR:
      sc->ifp->if_flags |= IFF_UP; /* a Unix tradition */
      break;
    case SIOCSIFMTU:
      if ((ifr->ifr_mtu < 72) || (ifr->ifr_mtu > 65535))
        error = EINVAL;
      else if ((error = ifioctl_common(sc->ifp, cmd, data)) == ENETRESET)
        error = 0;
      break;
    default:
      error = EINVAL;
      break;
    }

  return error;
  }

static void  /* context: interrupt */
rawip_input(softc_t *sc, struct mbuf *mbuf)
  {
  ifnet_input(sc->ifp, mbuf);
  }

static void  /* context: interrupt */
rawip_output(softc_t *sc)
  {
  IFQ_DEQUEUE(&sc->ifp->if_snd, sc->tx_mbuf);
  }

#elif NETDEV

static int  /* context: process */
rawip_ioctl(softc_t *sc, struct ifreq *ifr, int cmd)
  {
  if (sc->config.debug)
    printk("%s: rawip_ioctl; cmd=0x%08x\n", NAME_UNIT, cmd);
  return -EINVAL;
  }

static int  /* context: interrupt */
rawip_type(softc_t *sc, struct sk_buff *skb)
  {
  if (skb->data[0]>>4 == 4)
    return htons(ETH_P_IP);
  else if (skb->data[0]>>4 == 6)
    return htons(ETH_P_IPV6);
  else
    return htons(ETH_P_HDLC);
  }

static int  /* Process Context */
rawip_mtu(softc_t *sc, int mtu)
  {
  return ((mtu < 72) || (mtu > 65535)) ? -EINVAL : 0;
  }

#endif /* IFNET */

static void  /* context: softirq */
rawip_watchdog(softc_t *sc)
  {
#if IFNET
  if ((sc->status.link_state == STATE_UP) &&
      (sc->ifp->if_flags & IFF_UP))
# if defined(__FreeBSD__)
    sc->ifp->if_drv_flags |= IFF_DRV_RUNNING;
# else
    sc->ifp->if_flags |= IFF_RUNNING;
# endif
  if ((sc->status.link_state != STATE_UP) ||
     !(sc->ifp->if_flags & IFF_UP))
# if defined(__FreeBSD__)
    sc->ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
# else
    sc->ifp->if_flags &= ~IFF_RUNNING;
# endif
#endif /* IFNET */

  /* Report current line protocol. */
  sc->status.stack = STACK_RAWIP;
  sc->status.proto = PROTO_IP_HDLC;
  }

static int
rawip_open(softc_t *sc, struct config *config)
  {
  sc->config.proto = PROTO_IP_HDLC;

  return 0;
  }

static int
rawip_attach(softc_t *sc, struct config *config)
  {
#if IFNET
  LMC_BPF_ATTACH(sc, DLT_RAW, 0);
#endif

  sc->config.stack = STACK_RAWIP;
  sc->stack = &rawip_stack;

  return 0;
  }

static int
rawip_detach(softc_t *sc)
  {
#if IFNET
  LMC_BPF_DETACH(sc);
  ifnet_setup(sc->ifp);
#elif NETDEV
  netdev_setup(sc->netdev);
#endif

  sc->config.stack = STACK_NONE;
  sc->config.proto = PROTO_NONE;
  sc->stack = NULL;

  return 0;
  }

#if IFNET

/* Called to give a newly arrived pkt to higher levels. */
/* Called from rxintr_cleanup() with bottom_lock held. */
/* This is only used with rawip_stack on a BSD. */
static void
ifnet_input(struct ifnet *ifp, struct mbuf *mbuf)
  {
  softc_t *sc = IFP2SC(ifp);
  struct ifqueue *intrq;
  int isr = 0;

  intrq = NULL; /* surpress compiler warning */
# if INET
  if (mbuf->m_data[0]>>4 == 4)
    {
    isr = NETISR_IP;
#  if !defined(__FreeBSD__)
    intrq = &ipintrq;
#  endif
    }
# endif /* INET */

# if INET6
  if (mbuf->m_data[0]>>4 == 6)
    {
    isr = NETISR_IPV6;
#  if !defined(__FreeBSD__)
    intrq = &ip6intrq;
#  endif
    }
# endif /* INET6 */

  if (isr)
    {
# if defined(__FreeBSD__)
    netisr_dispatch(isr, mbuf);
# else
    if (!IF_QFULL(intrq))
      {
      /* ifnet_input() ENQUEUES in a hard interrupt. */
      /* ip_input() DEQUEUES in a soft interrupt. */
      /* Some BSD QUEUE routines are not interrupt-safe. */
      DISABLE_INTR; /* noop in FreeBSD */
      IF_ENQUEUE(intrq, mbuf);
      ENABLE_INTR;  /* noop in FreeBSD */
      schednetisr(isr); /* wake up the network code */
      }
    else /* intrq is full */
      {
      m_freem(mbuf);
      sc->status.cntrs.idrops++;
      if (sc->config.debug)
        printf("%s: ifnet_input: rx pkt dropped: intrq full\n", NAME_UNIT);
      }
# endif /* __FreeBSD__ */
    }
  else /* isr is zero */
    {
    m_freem(mbuf);
    sc->status.cntrs.idrops++;
    if (sc->config.debug)
      printf("%s: ifnet_input: rx pkt dropped: not IPv4 or IPv6\n", NAME_UNIT);
    }
  }

/* sppp and p2p replace this with their own proc.
 * This is only used with rawip_stack on a BSD.
 * This procedure is very similar to ng_rcvdata().
 */
static int  /* context: process */
ifnet_output(struct ifnet *ifp, struct mbuf *m,
 const struct sockaddr *dst, struct rtentry *rt)
  {
  softc_t *sc = IFP2SC(ifp);
  int error = 0;

  /* Fail if the link is down. */
  if (sc->status.link_state != STATE_UP)
    {
    m_freem(m);
    sc->status.cntrs.odrops++;
    if (sc->config.debug)
      printf("%s: ifnet_output: tx pkt dropped: link down\n", NAME_UNIT);
    return ENETDOWN;
    }

  /* ifnet_output() ENQUEUEs in a syscall or softirq. */
  /* txintr_setup() DEQUEUEs in a hard interrupt. */
  /* Some BSD QUEUE routines are not interrupt-safe. */
  {
  DISABLE_INTR; /* noop in FreeBSD */
#if defined(__FreeBSD__)
  IFQ_ENQUEUE(&ifp->if_snd, m, error);
#else
  IFQ_ENQUEUE(&ifp->if_snd, m, NULL, error);
#endif
  ENABLE_INTR; /* noop in FreeBSD */
  }

  if (error)
    {
    sc->status.cntrs.odrops++;
    if (sc->config.debug)
      printf("%s: ifnet_output: tx pkt dropped: IFQ_ENQUEUE(): error %d\n",
       NAME_UNIT, error);
    }
  else
    /* Process tx pkts; do not process rx pkts. */
    lmc_interrupt(sc, 0, 0);

  return error;
  }

static int  /* context: process */
ifnet_ioctl(struct ifnet *ifp, u_long cmd, void *data)
  {
  softc_t *sc = IFP2SC(ifp);
  struct ifreq *ifr = (struct ifreq *) data;
  int error = 0;

  /* Aquire ioctl/watchdog interlock. */
  if ((error = TOP_LOCK(sc))) return error;

  switch (cmd)
    {
    /* Catch the IOCTLs used by lmcconfig. */
    case LMCIOCGSTAT:
    case LMCIOCGCFG:
    case LMCIOCSCFG:
    case LMCIOCREAD:
    case LMCIOCWRITE:
    case LMCIOCTL:
      error = lmc_ioctl(sc, cmd, data);
      break;

# if defined(__FreeBSD__) || defined(__NetBSD__)
    case SIOCSIFCAP:
#  if DEVICE_POLLING
      if ((ifr->ifr_reqcap & IFCAP_POLLING) &&
       !(ifp->if_capenable & IFCAP_POLLING) &&
       !(error = ether_poll_register(bsd_poll, ifp)))
        { /* enable polling */
        WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_DISABLE);
        ifp->if_capenable |= IFCAP_POLLING;
        }
      else if (!(ifr->ifr_reqcap & IFCAP_POLLING) &&
              (ifp->if_capenable & IFCAP_POLLING) &&
             !(error = ether_poll_deregister(ifp)))
        { /* disable polling */
        ifp->if_capenable &= ~IFCAP_POLLING;
        WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_TXRX);
        }
      else
        error = EINVAL;
#  endif /* DEVICE_POLLING */
      break;
# endif /* FreeBSD || NetBSD */

    case SIOCSIFMEDIA: /* calls lmc_ifmedia_change() */
    case SIOCGIFMEDIA: /* calls ifmedia_status() */
      error = ifmedia_ioctl(ifp, ifr, &sc->ifm, cmd);
      break;

# if defined(__OpenBSD__)
    case SIOCSIFTIMESLOT:
      if (sc->status.card_type == CSID_LMC_T1E1)
        {
        struct config config = sc->config;
        if ((error = copyin(ifr->ifr_data, &config.time_slots,
         sizeof sc->config.time_slots))) break;
        sc->card->attach(sc, &config);
	}
      else
        error = EINVAL;
      break;
    case SIOCGIFTIMESLOT:
      if (sc->status.card_type == CSID_LMC_T1E1)
        error = copyout(&sc->status.time_slots, ifr->ifr_data,
         sizeof sc->status.time_slots);
      else
        error = EINVAL;
      break;
# endif /* OpenBSD */

    /* Pass the rest to the line protocol. */
    default:
      if (sc->stack)
        error = sc->stack->ioctl(sc, cmd, data);
      else
        error = ENOSYS;
      break;
    }

  /* release ioctl/watchdog interlock */
  TOP_UNLOCK(sc);

  if (error && sc->config.debug)
    printf("%s: ifnet_ioctl: op=IO%s%s len=%3lu grp='%c' num=%3lu err=%d\n",
     NAME_UNIT, cmd&IOC_IN ? "W":"", cmd&IOC_OUT ? "R":"",
     IOCPARM_LEN(cmd), (char)IOCGROUP(cmd), cmd&0xFF, error);

  return error;
  }

static void  /* context: process */
ifnet_start(struct ifnet *ifp)
  {
  softc_t *sc = IFP2SC(ifp);

  /* Process tx pkts; do not process rx pkts. */
  lmc_interrupt(sc, 0, 0);
  }

static void  /* context: softirq */
ifnet_watchdog(struct ifnet *ifp)
  {
  softc_t *sc = IFP2SC(ifp);
  struct cntrs *cntrs = &sc->status.cntrs;

  lmc_watchdog(sc); /* updates link_state */

# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
  if (sc->status.link_state == STATE_UP)
    ifp->if_link_state = LINK_STATE_UP;
  else
    ifp->if_link_state = LINK_STATE_DOWN;
# endif

  /* Copy statistics from sc to ifp. */
  ifp->if_baudrate = sc->status.tx_speed;
  ifp->if_ibytes   = cntrs->ibytes;
  ifp->if_obytes   = cntrs->obytes;
  ifp->if_ipackets = cntrs->ipackets;
  ifp->if_opackets = cntrs->opackets;
  ifp->if_ierrors  = cntrs->ierrors;
  ifp->if_oerrors  = cntrs->oerrors;
  ifp->if_iqdrops  = cntrs->idrops;

  /* If the interface debug flag is set, set the driver debug flag. */
  if (sc->ifp->if_flags & IFF_DEBUG)
    sc->config.debug = 1;

  /* Call this procedure again after one second. */
  ifp->if_timer = 1;
  }

/* This setup is for RawIP; SPPP and P2P change many items. */
/* Note the similarity to linux's netdev_setup(). */
static void
ifnet_setup(struct ifnet *ifp)
  {
  ifp->if_flags    = IFF_POINTOPOINT;
  ifp->if_flags   |= IFF_SIMPLEX;
  ifp->if_flags   |= IFF_NOARP;
# if defined(__FreeBSD__) || defined(__NetBSD__)
  ifp->if_input    = ifnet_input;
# endif
  ifp->if_output   = ifnet_output;
  ifp->if_start    = ifnet_start;
  ifp->if_ioctl    = ifnet_ioctl;
  ifp->if_watchdog = ifnet_watchdog;
  ifp->if_timer    = 1;
  ifp->if_type     = IFT_PTPSERIAL;
  ifp->if_addrlen  = 0;
  ifp->if_hdrlen   = 0;
  ifp->if_mtu      = MAX_DESC_LEN;
  }

/* Attach the ifnet kernel interface. */
/* context: kernel (boot) or process (syscall) */
static int
ifnet_attach(softc_t *sc)
  {
# if defined(__FreeBSD__)
  sc->ifp = if_alloc(SPPP ? IFT_PPP : IFT_OTHER);
  if (sc->ifp == NULL) return ENOMEM;
# elif SPPP
  sc->ifp = &sc->spppcom.pp_if;
# elif P2P
  sc->ifp = &sc->p2pcom.p2p_if;
# else
  sc->ifp = &sc->ifnet;
# endif

  sc->ifp->if_softc = sc;
  ifnet_setup(sc->ifp);

# if defined(__FreeBSD__) || defined(__OpenBSD__)
  sc->ifp->if_capabilities |= IFCAP_JUMBO_MTU;
# endif
# if DEVICE_POLLING
  sc->ifp->if_capabilities |= IFCAP_POLLING;
# endif

  /* Every OS does it differently! */
# if defined(__FreeBSD__)
  sc->ifp->if_dname = device_get_name(sc->dev);
  sc->ifp->if_dunit = device_get_unit(sc->dev);
  strlcpy(sc->ifp->if_xname, device_get_nameunit(sc->dev), IFNAMSIZ);
# elif defined(__NetBSD__)
  strlcpy(sc->ifp->if_xname, device_xname(&sc->dev), IFNAMSIZ);
# elif defined(__OpenBSD__)
  bcopy(sc->dev.dv_xname, sc->ifp->if_xname, IFNAMSIZ);
# elif defined(__bsdi__)
  sc->ifp->if_name  = sc->dev.dv_cfdata->cf_driver->cd_name;
  sc->ifp->if_unit  = sc->dev.dv_unit;
# endif

  IFQ_SET_MAXLEN(&sc->ifp->if_snd, SNDQ_MAXLEN);
  IFQ_SET_READY(&sc->ifp->if_snd);

  if_attach(sc->ifp);

# if defined(__NetBSD__) || defined(__OpenBSD__)
  if_alloc_sadl(sc->ifp);
# endif

  ifmedia_setup(sc);

  return 0;
  }

/* Detach the ifnet kernel interface. */
/* context: kernel (boot) or process (syscall). */
static void
ifnet_detach(softc_t *sc)
  {
# if defined(__FreeBSD__)
  ifmedia_removeall(&sc->ifm);
# elif defined(__NetBSD__) || defined(__OpenBSD__)
  ifmedia_delete_instance(&sc->ifm, IFM_INST_ANY);
# endif

# if DEVICE_POLLING
  if (sc->ifp->if_capenable & IFCAP_POLLING)
    ether_poll_deregister(sc->ifp);
# endif

  IFQ_PURGE(&sc->ifp->if_snd);

# if defined(__NetBSD__) || defined(__OpenBSD__)
  if_free_sadl(sc->ifp);
# endif

  if_detach(sc->ifp);

# if defined(__FreeBSD__)
  if_free_type(sc->ifp, SPPP ? IFT_PPP : IFT_OTHER);
# endif
  }

static void
ifmedia_setup(softc_t *sc)
  {
  /* Initialize ifmedia mechanism. */
  ifmedia_init(&sc->ifm, IFM_OMASK | IFM_GMASK | IFM_IMASK,
   lmc_ifmedia_change, ifmedia_status);

# if defined(__OpenBSD__)
  if (sc->status.card_type == CSID_LMC_T3)
    {
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_TDM_T3, 0, NULL);
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_TDM_T3_M13, 0, NULL);
    ifmedia_set(&sc->ifm, IFM_TDM | IFM_TDM_T3);
    }
  else if  (sc->status.card_type == CSID_LMC_T1E1)
    {
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_TDM_T1, 0, NULL);
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_TDM_T1_AMI, 0, NULL);
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_TDM_E1, 0, NULL);
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_TDM_E1_G704, 0, NULL);
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_TDM_E1_G704_CRC4, 0, NULL);
    ifmedia_set(&sc->ifm, IFM_TDM | IFM_TDM_T1);
    }
  else
    {
    ifmedia_add(&sc->ifm, IFM_TDM | IFM_NONE, 0, NULL);
    ifmedia_set(&sc->ifm, IFM_TDM | IFM_NONE);
    }
# else /* FreeBSD, NetBSD, BSD/OS */
  ifmedia_add(&sc->ifm, IFM_ETHER | IFM_NONE, 0, NULL);
  ifmedia_set(&sc->ifm, IFM_ETHER | IFM_NONE);
# endif  /* OpenBSD */
  }

/* SIOCSIFMEDIA: context: process. */
static int
lmc_ifmedia_change(struct ifnet *ifp)
  {
  softc_t *sc = IFP2SC(ifp);
  struct config config = sc->config;
  int media = sc->ifm.ifm_media;
  int error;

# if defined(__OpenBSD__)
  /* ifconfig lmc0 media t1 */
  if      (sc->status.card_type == CSID_LMC_T3)
    {
    if      ((media & IFM_TMASK) == IFM_TDM_T3)
      config.format = CFG_FORMAT_T3CPAR;
    else if ((media & IFM_TMASK) == IFM_TDM_T3_M13)
      config.format = CFG_FORMAT_T3M13;
    }
  else if (sc->status.card_type == CSID_LMC_T1E1)
    {
    if      ((media & IFM_TMASK) == IFM_TDM_T1)
      config.format = CFG_FORMAT_T1ESF;
    else if ((media & IFM_TMASK) == IFM_TDM_T1_AMI)
      config.format = CFG_FORMAT_T1SF;
    else if ((media & IFM_TMASK) == IFM_TDM_E1)
      config.format = CFG_FORMAT_E1NONE;
    else if ((media & IFM_TMASK) == IFM_TDM_E1_G704)
      config.format = CFG_FORMAT_E1FAS;
    else if ((media & IFM_TMASK) == IFM_TDM_E1_G704_CRC4)
      config.format = CFG_FORMAT_E1FASCRC;
    }

  /* ifconfig lmc0 mediaopt hdlc-crc16 */
  if (media & IFM_TDM_HDLC_CRC16)
    config.proto = PROTO_C_HDLC;

  /* ifconfig lmc0 mediaopt ppp */
  if (media & IFM_TDM_PPP)
    config.proto = PROTO_PPP;

  /* ifconfig lmc0 mediaopt framerelay-ansi */
  if (media & IFM_TDM_FR_ANSI)
    config.proto = PROTO_FRM_RLY;

  /* ifconfig lmc0 mediaopt master */
  if ((media & IFM_TDM_MASTER) &&
   (sc->status.card_type == CSID_LMC_T1E1))
    config.tx_clk_src = CFG_CLKMUX_INT; /* xtal */
  else
    config.tx_clk_src = CFG_CLKMUX_RT; /* loop */
# endif  /* OpenBSD */

  /* ifconfig lmc0 mediaopt loopback */
  if (media & IFM_LOOP)
    config.loop_back = CFG_LOOP_TULIP;
  else
    config.loop_back = CFG_LOOP_NONE;

  error = open_proto(sc, &config);
  tulip_loop(sc, &config);
  sc->card->attach(sc, &config);

  return error;
  }

/* SIOCGIFMEDIA: context: process. */
static void
ifmedia_status(struct ifnet *ifp, struct ifmediareq *ifmr)
  {
  softc_t *sc = IFP2SC(ifp);

  ifmr->ifm_status = IFM_AVALID;
  if (sc->status.link_state == STATE_UP)
    ifmr->ifm_status |= IFM_ACTIVE;

  if (sc->config.loop_back != CFG_LOOP_NONE)
    ifmr->ifm_active |= IFM_LOOP;

# if defined(__OpenBSD__)
  if (sc->status.proto == PROTO_C_HDLC)
    ifmr->ifm_active |= IFM_TDM_HDLC_CRC16;

  if (sc->status.proto == PROTO_PPP)
    ifmr->ifm_active |= IFM_TDM_PPP;

  if (sc->status.proto == PROTO_FRM_RLY)
    ifmr->ifm_active |= IFM_TDM_FR_ANSI;

  if (sc->config.tx_clk_src == CFG_CLKMUX_INT)
    ifmr->ifm_active |= IFM_TDM_MASTER;
# endif  /* OpenBSD */
  }

#endif  /* IFNET */

#if NETDEV

/* This net_device method is called when IFF_UP goes true. */
static int  /* context: process */
netdev_open(struct net_device *netdev)
  {
  softc_t *sc = NETDEV2SC(netdev);

  WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_TXRX);
  netif_start_queue(sc->netdev);
  set_ready(sc, 1);

  return 0;
  }

/* This net_device method is called when IFF_UP goes false. */
static int  /* context: process */
netdev_stop(struct net_device *netdev)
  {
  softc_t *sc = NETDEV2SC(netdev);

  set_ready(sc, 0);
  netif_stop_queue(sc->netdev);
  WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_DISABLE);

  return 0;
  }

/* This net_device method hands outgoing packets to the transmitter. */
/* With txintr_setup(), it implements output flow control. */
static int  /* context: netdev->xmit_lock held; BHs disabled */
netdev_start(struct sk_buff *skb, struct net_device *netdev)
  {
  softc_t *sc = NETDEV2SC(netdev);

  if (sc->tx_skb == NULL)
    {
    /* Put this skb where the transmitter will see it. */
    sc->tx_skb = skb;

    /* Process tx pkts; do not process rx pkts. */
    lmc_interrupt(sc, 0, 0);

    return NETDEV_TX_OK;
    }
  else
    {
    /* txintr_setup() calls netif_wake_queue(). */
    netif_stop_queue(netdev);
    return NETDEV_TX_BUSY;
    }
  }

# if NAPI

/* This net_device method services the card without interrupts. */
/* With rxintr_cleanup(), it implements input flow control. */
static int  /* context: softirq */
netdev_poll(struct net_device *netdev, int *budget)
  {
  softc_t *sc = NETDEV2SC(netdev);
  int received;

  /* Handle the card interrupt with kernel ints enabled. */
  /* Allow processing up to netdev->quota incoming packets. */
  /* This is the ONLY time lmc_interrupt() may process rx pkts. */
  /* Otherwise (sc->quota == 0) and rxintr_cleanup() is a NOOP. */
  lmc_interrupt(sc, min(netdev->quota, *budget), 0);

  /* Report number of rx packets processed. */
  received = netdev->quota - sc->quota;
  netdev->quota -= received;
  *budget       -= received;

  /* If quota prevented processing all rx pkts, leave rx ints disabled. */
  if (sc->quota == 0)  /* this is off by one...but harmless */
    {
    WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_TX);
    return 1; /* more pkts to handle -- reschedule */
    }

  /* Remove self from poll list. */
  netif_rx_complete(netdev);

  /* Enable card interrupts. */
  WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_TXRX);

  return 0; /* all pkts handled -- success */
  }

# endif /* NAPI */

/* This net_device method handles IOCTL syscalls. */
static int  /* context: process */
netdev_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
  {
  softc_t *sc = NETDEV2SC(netdev);
  int error = 0;

  /* Aquire ioctl/watchdog interlock. */
  if ((error = TOP_LOCK(sc))) return error;

  if ((cmd >= SIOCDEVPRIVATE) && (cmd <= SIOCDEVPRIVATE+15))
    {
    struct iohdr *iohdr = (struct iohdr *)ifr;
    u_int16_t direction = iohdr->direction;
    u_int16_t length = iohdr->length;
    char *user_addr = (char *)iohdr->iohdr;
    char *kern_addr = NULL;

    if (iohdr->cookie != NGM_LMC_COOKIE)
      error = -EINVAL;

    /* Emulate a BSD-style IOCTL syscall. */
    if (!error)
      error = (kern_addr = kmalloc(length, GFP_KERNEL)) ? 0: -ENOMEM;
    if (!error && (direction & DIR_IOW))
      error = copy_from_user(kern_addr, user_addr, length);
    if (!error)
      error = -lmc_ioctl(sc, (unsigned long)cmd, kern_addr);
    if (!error && (direction & DIR_IOR))
      error = copy_to_user(user_addr, kern_addr, length);
    kfree(kern_addr);
    }
  else if (sc->stack)
    error = sc->stack->ioctl(sc, ifr, cmd);
  else
    error = -ENOSYS;
# if GEN_HDLC
  /* If generic-HDLC is present but not the currently-attached
   * stack, call hdlc_ioctl() anyway because proto must
   * be set using SIOCWANDEV or hdlc_open() will fail.
   */
  if (sc->stack != &gen_hdlc_stack)
    hdlc_ioctl(sc->netdev, ifr, cmd); /* ignore error result */
# endif

  /* Release ioctl/watchdog interlock. */
  TOP_UNLOCK(sc);

  if (error && sc->config.debug)
    printk("%s: netdev_ioctl; cmd=0x%08x error=%d\n",
     NAME_UNIT, cmd, error);

  return error;
  }

/* This net_device method sets the Maximum Tranmit Unit. */
/* This driver does not limit MTU; stacks and protos do. */
static int
netdev_mtu(struct net_device *netdev, int mtu)
  {
  softc_t *sc = NETDEV2SC(netdev);
  int error = 0;

  if (sc->stack)
    error = sc->stack->mtu(sc, mtu);
  else
    error = -ENOSYS;

  if (!error)
     netdev->mtu = mtu;

  return error;
  }

/* This net_device method restarts the transmitter if it hangs. */
static void  /* BHs disabled */
netdev_timeout(struct net_device *netdev)
  {
  softc_t *sc = NETDEV2SC(netdev);

  /* Process tx pkts; do not process rx pkts. */
  lmc_interrupt(sc, 0, 0);
  }

/* This net_device method returns a pointer to device statistics. */
static struct net_device_stats *  /* context: process */
netdev_stats(struct net_device *netdev)
  {
  softc_t *sc = NETDEV2SC(netdev);

# if GEN_HDLC
  return &sc->hdlcdev->stats;
# else
  return &sc->netdev_stats;
# endif
  }

static void  /* context: softirq */
netdev_watchdog(unsigned long softc)
  {
  softc_t *sc = (softc_t *)softc;
  struct cntrs *cntrs = &sc->status.cntrs;
  struct net_device_stats *stats = netdev_stats(sc->netdev);

  lmc_watchdog(sc); /* updates link_state */

  /* Notice when the link comes up. */
  if ((sc->last_link_state != STATE_UP) &&
    (sc->status.link_state == STATE_UP))
    {
    netif_wake_queue(sc->netdev);
    netif_carrier_on(sc->netdev);
    }

  /* Notice when the link goes down. */
  if ((sc->last_link_state == STATE_UP) &&
    (sc->status.link_state != STATE_UP))
    {
    netif_tx_disable(sc->netdev);
    netif_carrier_off(sc->netdev);
    }

  /* Copy statistics from sc to netdev. */
  stats->rx_bytes         = cntrs->ibytes;
  stats->tx_bytes         = cntrs->obytes;
  stats->rx_packets       = cntrs->ipackets;
  stats->tx_packets       = cntrs->opackets;
  stats->rx_errors        = cntrs->ierrors;
  stats->tx_errors        = cntrs->oerrors;
  stats->rx_dropped       = cntrs->idrops;
  stats->rx_missed_errors = cntrs->missed;
  stats->tx_dropped       = cntrs->odrops;
  stats->rx_fifo_errors   = cntrs->fifo_over;
  stats->rx_over_errors   = cntrs->overruns;
  stats->tx_fifo_errors   = cntrs->fifo_under;
/*stats->tx_under_errors  = cntrs=>underruns; */

  /* If the interface debug flag is set, set the driver debug flag. */
  if (sc->netdev->flags & IFF_DEBUG)
    sc->config.debug = 1;

  /* Call this procedure again after one second. */
  sc->wd_timer.expires = jiffies + HZ -8; /* -8 is a FUDGE factor */
  add_timer(&sc->wd_timer);
  }

/* This setup is for RawIP; Generic-HDLC changes many items. */
/* Note the similarity to BSD's ifnet_setup(). */
static void
netdev_setup(struct net_device *netdev)
  {
  netdev->flags           = IFF_POINTOPOINT;
  netdev->flags          |= IFF_NOARP;
  netdev->open            = netdev_open;
  netdev->stop            = netdev_stop;
  netdev->hard_start_xmit = netdev_start;
# if NAPI
  netdev->poll            = netdev_poll;
  netdev->weight          = 32; /* sc->rxring.num_descs; */
# endif
  netdev->rebuild_header  = NULL; /* no arp */
  netdev->hard_header     = NULL; /* no arp */
  netdev->do_ioctl        = netdev_ioctl;
  netdev->change_mtu      = netdev_mtu;
  netdev->tx_timeout      = netdev_timeout;
  netdev->get_stats       = netdev_stats;
  netdev->watchdog_timeo  = 1 * HZ;
  netdev->mtu             = MAX_DESC_LEN;
  netdev->type            = ARPHRD_HDLC;
  netdev->hard_header_len = 16;
  netdev->addr_len        = 0;
  netdev->tx_queue_len    = SNDQ_MAXLEN;
/* The receiver generates frag-lists for packets >4032 bytes.   */
/* The transmitter accepts scatter/gather lists and frag-lists. */
/* However Linux linearizes outgoing packets since our hardware */
/*  does not compute soft checksums.  All that work for nothing! */
/*netdev->features       |= NETIF_F_SG; */
/*netdev->features       |= NETIF_F_FRAGLIST; */
  }

/* Attach the netdevice kernel interface. */
/* context: kernel (boot) or process (syscall). */
static int
netdev_attach(softc_t *sc)
  {
  int error;

# if GEN_HDLC /* generic-hdlc line protocol pkg configured */

  /* Allocate space for the HDLC network device struct. */
  /* Allocating a netdev and attaching to generic-HDLC should be separate. */
  if ((sc->netdev = alloc_hdlcdev(sc)) == NULL)
    {
    printk("%s: netdev_attach: alloc_hdlcdev() failed\n", DEVICE_NAME);
    return -ENOMEM;
    }

  /* Initialize the basic network device struct. */
  /* This clobbers some netdev stuff set by alloc_hdlcdev(). */
  /* Our get_stats() and change_mtu() do the right thing. */
  netdev_setup(sc->netdev);

  /* HACK: make the private eco-net pointer -> struct softc. */
  sc->netdev->ec_ptr = sc;

  /* Cross-link pcidev and netdev. */
  SET_NETDEV_DEV(sc->netdev, &sc->pcidev->dev);
  sc->netdev->mem_end   = pci_resource_end(sc->pcidev, 1);
  sc->netdev->mem_start = pci_resource_start(sc->pcidev, 1);
  sc->netdev->base_addr = pci_resource_start(sc->pcidev, 0);
  sc->netdev->irq       = sc->pcidev->irq;

  /* Initialize the HDLC extension to the network device. */
  sc->hdlcdev         = sc->netdev->priv;
  sc->hdlcdev->attach = gen_hdlc_card_params;
  sc->hdlcdev->xmit   = netdev_start; /* the REAL hard_start_xmit() */

  if ((error = register_hdlc_device(sc->netdev)))
    {
    printk("%s: register_hdlc_device(): error %d\n", DEVICE_NAME, error);
    free_netdev(sc->netdev);
    return error;
    }

# else

  /* Allocate and initialize the basic network device struct. */
  if ((sc->netdev = alloc_netdev(0, DEVICE_NAME"%d", netdev_setup)) == NULL)
    {
    printk("%s: netdev_attach: alloc_netdev() failed\n", DEVICE_NAME);
    return -ENOMEM;
    }

  /* HACK: make the private eco-net pointer -> struct softc. */
  sc->netdev->ec_ptr = sc;

  /* Cross-link pcidev and netdev. */
  SET_NETDEV_DEV(sc->netdev, &sc->pcidev->dev);
  sc->netdev->mem_end   = pci_resource_end(sc->pcidev, 1);
  sc->netdev->mem_start = pci_resource_start(sc->pcidev, 1);
  sc->netdev->base_addr = pci_resource_start(sc->pcidev, 0);
  sc->netdev->irq       = sc->pcidev->irq;

  if ((error = register_netdev(sc->netdev)))
    {
    printk("%s: register_netdev(): error %d\n", DEVICE_NAME, error);
    free_netdev(sc->netdev);
    return error;
    }

# endif /* GEN_HDLC */

  /* Arrange to call netdev_watchdog() once a second. */
  init_timer(&sc->wd_timer);
  sc->wd_timer.expires  = jiffies + HZ; /* now plus one second */
  sc->wd_timer.function = &netdev_watchdog;
  sc->wd_timer.data     = (unsigned long) sc;
  add_timer(&sc->wd_timer);

  return 0; /* success */
  }

/* Detach the netdevice kernel interface. */
/* context: kernel (boot) or process (syscall). */
static void
netdev_detach(softc_t *sc)
  {
  if (sc->pcidev == NULL) return;
  if (sc->netdev == NULL) return;

  netdev_stop(sc->netdev); /* check for not inited */
  del_timer(&sc->wd_timer);

# if GEN_HDLC
  unregister_hdlc_device(sc->netdev);
# else
  unregister_netdev(sc->netdev);
# endif

  free_netdev(sc->netdev);
  }

#endif /* NETDEV */

#if NETGRAPH

/* These next two macros should be added to netgraph */
#  define NG_TYPE_REF(type) atomic_add_int(&(type)->refs, 1)
#  define NG_TYPE_UNREF(type)	\
do {				\
  if ((type)->refs == 1)	\
    ng_rmtype(type);		\
  else				\
    atomic_subtract_int(&(type)->refs, 1); \
   } while (0)

/* It is an error to construct new copies of this Netgraph node. */
/* All instances are constructed by ng_attach and are persistent. */
static int ng_constructor(node_p  node)
  { return EINVAL; }

/* Incoming Netgraph control message. */
static int
ng_rcvmsg(node_p node, item_p item, hook_p lasthook)
  {
  struct ng_mesg *msg;
  struct ng_mesg *resp = NULL;
  softc_t *sc = NG_NODE_PRIVATE(node);
  int error = 0;

  if ((error = TOP_LOCK(sc))) return error;

  NGI_GET_MSG(item, msg);
  if (msg->header.typecookie == NGM_LMC_COOKIE)
    {
    switch (msg->header.cmd)
      {
      case LMCIOCGSTAT:
      case LMCIOCGCFG:
      case LMCIOCSCFG:
      case LMCIOCREAD:
      case LMCIOCWRITE:
      case LMCIOCTL:
        {
        /* Call the core ioctl procedure. */
        error = lmc_ioctl(sc, msg->header.cmd, msg->data);
        if (msg->header.cmd & IOC_OUT)
          { /* synchronous response */
          NG_MKRESPONSE(resp, msg, sizeof(struct ng_mesg) +
           IOCPARM_LEN(msg->header.cmd), M_NOWAIT);
          if (resp == NULL)
            error = ENOMEM;
          else
            memcpy(resp->data, msg->data, IOCPARM_LEN(msg->header.cmd));
          }
        break;
        }
      default:
        error = EINVAL;
        break;
      }
    }
  else if ((msg->header.typecookie == NGM_GENERIC_COOKIE) &&
           (msg->header.cmd == NGM_TEXT_STATUS))
    {  /* synchronous response */
    NG_MKRESPONSE(resp, msg, sizeof(struct ng_mesg) +
     NG_TEXTRESPONSE, M_NOWAIT);
    if (resp == NULL)
      error = ENOMEM;
    else
      {
      char *s = resp->data;
      sprintf(s, "Card type = <%s>\n"
       "This driver considers the link to be %s.\n"
       "Use lmcconfig to configure this interface.\n",
       sc->dev_desc, (sc->status.link_state==STATE_UP) ? "UP" : "DOWN");
      resp->header.arglen = strlen(s) +1;
      }
    }
  else
    error = EINVAL;
/* Netgraph should be able to read and write these
 *  parameters with text-format control messages:
 *  SSI	     HSSI     T1E1     T3
 *  crc	     crc      crc      crc      
 *  loop     loop     loop     loop
 *           clksrc   clksrc
 *  dte	     dte      format   format
 *  synth    synth    cablen   cablen
 *  cable             timeslot scram
 *                    gain
 *                    pulse
 *                    lbo
 * Someday I'll implement this...
 */

  /* Handle synchronous response. */
  NG_RESPOND_MSG(error, node, item, resp);
  NG_FREE_MSG(msg);

  TOP_UNLOCK(sc);

  return error;
  }

/* This is a persistent netgraph node. */
static int
ng_shutdown(node_p node)
  {
  /* unless told to really die, bounce back to life */
  if ((node->nd_flags & NG_REALLY_DIE)==0)
    node->nd_flags &= ~NG_INVALID; /* bounce back to life */

  return 0;
  }

/* ng_disconnect is the opposite of this procedure. */
static int
ng_newhook(node_p node, hook_p hook, const char *name)
  {
  softc_t *sc = NG_NODE_PRIVATE(node);

  /* Hook name must be 'rawdata'. */
  if (strncmp(name, "rawdata", 7)) return EINVAL;

  /* Is netgraph the current package? */
  if (sc->stack != &netgraph_stack) return EBUSY;

  /* Is our hook connected? */
  if (sc->ng_hook) return EBUSY;

  /* Accept the hook. */
  sc->ng_hook = hook;

  return 0;
  }

/* Both ends have accepted their hooks and the links have been made. */
/* This is the last chance to reject the connection request. */
static int
ng_connect(hook_p hook)
  {
  /* Probably not at splnet, force outward queueing. */
  NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));

  return 0; /* always accept */
  }

/* Receive data in mbufs from another Netgraph node.
 * Transmit an mbuf-chain on the communication link.
 * This procedure is very similar to ifnet_output().
 */
static int  /* context: process */
ng_rcvdata(hook_p hook, item_p item)
  {
  softc_t *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
  int error = 0;
  struct mbuf *m;
  meta_p meta = NULL;

  NGI_GET_M(item, m);
  NGI_GET_META(item, meta);
  NG_FREE_ITEM(item);
  NG_FREE_META(meta);

  /* Fail if the link is down. */
  if (sc->status.link_state != STATE_UP)
    {
    m_freem(m);
    sc->status.cntrs.odrops++;
    if (sc->config.debug)
      printf("%s: ng_rcvdata: tx pkt dropped: link down\n", NAME_UNIT);
    return ENETDOWN;
    }

  /* ng_rcvdata() ENQUEUEs in a syscall or softirq. */
  /* txintr_setup() DEQUEUEs in a hard interrupt. */
  /* Some BSD QUEUE routines are not interrupt-safe. */
  {
  DISABLE_INTR; /* noop in FreeBSD */
# if defined(__FreeBSD__)
  if (meta==NULL)
    IFQ_ENQUEUE(&sc->ng_sndq, m, error);
  else
    IFQ_ENQUEUE(&sc->ng_fastq, m, error);
# else
  if (meta==NULL)
    IFQ_ENQUEUE(&sc->ng_sndq, m, NULL, error);
  else
    IFQ_ENQUEUE(&sc->ng_fastq, m, NULL, error);
# endif
  ENABLE_INTR;  /* noop in FreeBSD */
  }

  if (error)
    {
    sc->status.cntrs.odrops++;
    if (sc->config.debug)
      printf("%s: ng_rcvdata: tx pkt dropped: IFQ_ENQUEUE(): error %d\n",
       NAME_UNIT, error);
    }
  else
    /* Process tx pkts; do not process rx pkts. */
    lmc_interrupt(sc, 0, 0);

  return error;
  }

/* ng_newhook is the opposite of this procedure, not */
/*  ng_connect, as you might expect from the names. */
static int
ng_disconnect(hook_p hook)
  {
  softc_t *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));

  /* Disconnect the hook. */
  sc->ng_hook = NULL;

  return 0;
  }

static
struct ng_type ng_type =
  {
  .version	= NG_ABI_VERSION,
  .name		= NG_LMC_NODE_TYPE,
  .mod_event	= NULL,
  .constructor	= ng_constructor,
  .rcvmsg	= ng_rcvmsg,
  .close	= NULL,
  .shutdown	= ng_shutdown,
  .newhook	= ng_newhook,
  .findhook	= NULL,
  .connect	= ng_connect,
  .rcvdata	= ng_rcvdata,
  .disconnect	= ng_disconnect,
  };

static void  /* context: softirq */
ng_watchdog(void *arg)
  {
  softc_t *sc = arg;

  /* Call the core watchdog procedure. */
  lmc_watchdog(sc);

  /* Call this procedure again after one second. */
  callout_reset(&sc->ng_callout, hz, ng_watchdog, sc);
  }

/* Attach the Netgraph kernel interface. */
/* This is effectively ng_constructor. */
/* context: kernel (boot) or process (syscall) */
static int
ng_attach(softc_t *sc)
  {
  int error;

  /* If this node type is not known to Netgraph then register it. */
  if (ng_findtype(ng_type.name) == NULL)
    {
    if ((error = ng_newtype(&ng_type)))
      {
      printf("%s: ng_newtype(): error %d\n", NAME_UNIT, error);
      return error;
      }
    }
  else
    NG_TYPE_REF(&ng_type);

  /* Call the superclass node constructor. */
  if ((error = ng_make_node_common(&ng_type, &sc->ng_node)))
    {
    NG_TYPE_UNREF(&ng_type);
    printf("%s: ng_make_node_common(): error %d\n", NAME_UNIT, error);
    return error;
    }

  /* Associate a name with this netgraph node. */
  if ((error = ng_name_node(sc->ng_node, NAME_UNIT)))
    {
    NG_NODE_UNREF(sc->ng_node);
    NG_TYPE_UNREF(&ng_type);
    printf("%s: ng_name_node(): error %d\n", NAME_UNIT, error);
    return error;
    }

  /* Initialize the send queue mutexes. */
  mtx_init(&sc->ng_sndq.ifq_mtx,  NAME_UNIT, "ng sndq",  MTX_DEF);
  mtx_init(&sc->ng_fastq.ifq_mtx, NAME_UNIT, "ng fastq", MTX_DEF);

  /* Put a backpointer to softc in the netgraph node. */
  NG_NODE_SET_PRIVATE(sc->ng_node, sc);

  /* ALTQ output queue initialization. */
  IFQ_SET_MAXLEN(&sc->ng_fastq, SNDQ_MAXLEN);
  IFQ_SET_READY(&sc->ng_fastq);
  IFQ_SET_MAXLEN(&sc->ng_sndq,  SNDQ_MAXLEN);
  IFQ_SET_READY(&sc->ng_sndq);

  /* If ifnet is present, it will call watchdog. */
  /* Otherwise, arrange to call watchdog here. */
  if (IFNET==0)
    {
    /* Arrange to call ng_watchdog() once a second. */
    callout_init(&sc->ng_callout, 0);
    callout_reset(&sc->ng_callout, hz, ng_watchdog, sc);
    }

  return 0;
  }

/* Detach the Netgraph kernel interface. */
/* context: kernel (boot) or process (syscall) */
static void
ng_detach(softc_t *sc)
  {
  if (IFNET==0)
    callout_stop(&sc->ng_callout);
  IFQ_PURGE(&sc->ng_fastq);
  IFQ_PURGE(&sc->ng_sndq);
  mtx_destroy(&sc->ng_sndq.ifq_mtx);
  mtx_destroy(&sc->ng_fastq.ifq_mtx);
  ng_rmnode_self(sc->ng_node); /* free hook */
  NG_NODE_UNREF(sc->ng_node);  /* free node */
  NG_TYPE_UNREF(&ng_type);
  }

#endif /* NETGRAPH */

#if BSD

/* There are TWO VERSIONS of interrupt/DMA code: Linux & BSD.
 * Handling Linux and the BSDs with CPP directives would
 *  make the code unreadable, so there are two versions.
 * Conceptually, the two versions do the same thing and
 *  lmc_interrupt() does not know they are different.
 *
 * We are "standing on the head of a pin" in these routines.
 * Tulip CSRs can be accessed, but nothing else is interrupt-safe!
 * Do NOT access: MII, GPIO, SROM, BIOSROM, XILINX, SYNTH, or DAC.
 */

/* Initialize a DMA descriptor ring. */
/* context: kernel (boot) or process (syscall) */
static int  /* BSD version */
create_ring(softc_t *sc, struct desc_ring *ring, int num_descs)
  {
  struct dma_desc *descs;
  int size_descs = sizeof(struct dma_desc)*num_descs;
  int i, error = 0;

  /* The DMA descriptor array must not cross a page boundary. */
  if (size_descs > PAGE_SIZE)
    {
    printf("%s: DMA descriptor array > PAGE_SIZE (%d)\n", NAME_UNIT,
     (u_int)PAGE_SIZE);
    return EINVAL;
    }

# if defined(__FreeBSD__)

  /* Create a DMA tag for descriptors and buffers. */
  if ((error = bus_dma_tag_create(NULL, 4, 0, BUS_SPACE_MAXADDR_32BIT,
   BUS_SPACE_MAXADDR, NULL, NULL, PAGE_SIZE, 2, PAGE_SIZE, BUS_DMA_ALLOCNOW,
   NULL, NULL, &ring->tag)))
    {
    printf("%s: bus_dma_tag_create(): error %d\n", NAME_UNIT, error);
    return error;
    }

  /* Allocate wired physical memory for DMA descriptor array */
  /*  and map physical address to kernel virtual address. */
  if ((error = bus_dmamem_alloc(ring->tag, (void**)&ring->first,
   BUS_DMA_NOWAIT | BUS_DMA_COHERENT | BUS_DMA_ZERO, &ring->map)))
    {
    printf("%s: bus_dmamem_alloc(): error %d\n", NAME_UNIT, error);
    return error;
    }
  descs = ring->first;

  /* Map kernel virt addr to PCI bus addr for DMA descriptor array. */
  if ((error = bus_dmamap_load(ring->tag, ring->map, descs, size_descs,
   fbsd_dmamap_load, ring, 0)))
    {
    printf("%s: bus_dmamap_load(): error %d\n", NAME_UNIT, error);
    return error;
    }
  ring->dma_addr = ring->segs[0].ds_addr;

  /* Allocate dmamaps for each DMA descriptor. */
  for (i=0; i<num_descs; i++)
    if ((error = bus_dmamap_create(ring->tag, 0, &descs[i].map)))
      {
      printf("%s: bus_dmamap_create(): error %d\n", NAME_UNIT, error);
      return error;
      }

# elif defined(__NetBSD__) || defined(__OpenBSD__)

  /* Use the DMA tag passed to attach() for descriptors and buffers. */
  ring->tag = sc->pa_dmat;

  /* Allocate wired physical memory for DMA descriptor array. */
  if ((error = bus_dmamem_alloc(ring->tag, size_descs, PAGE_SIZE, 0,
   ring->segs, 1, &ring->nsegs, BUS_DMA_NOWAIT)))
    {
    printf("%s: bus_dmamem_alloc(): error %d\n", NAME_UNIT, error);
    return error;
    }

  /* Map physical address to kernel virtual address. */
  if ((error = bus_dmamem_map(ring->tag, ring->segs, ring->nsegs,
   size_descs, (void **)&ring->first, BUS_DMA_NOWAIT | BUS_DMA_COHERENT)))
    {
    printf("%s: bus_dmamem_map(): error %d\n", NAME_UNIT, error);
    return error;
    }
  descs = ring->first; /* suppress compiler warning about aliasing */
  memset(descs, 0, size_descs);

  /* Allocate dmamap for PCI access to DMA descriptor array. */
  if ((error = bus_dmamap_create(ring->tag, size_descs, 1,
   size_descs, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &ring->map)))
    {
    printf("%s: bus_dmamap_create(): error %d\n", NAME_UNIT, error);
    return error;
    }

  /* Map kernel virt addr to PCI bus addr for DMA descriptor array. */
  if ((error = bus_dmamap_load(ring->tag, ring->map, descs, size_descs,
   0, BUS_DMA_NOWAIT)))
    {
    printf("%s: bus_dmamap_load(): error %d\n", NAME_UNIT, error);
    return error;
    }
  ring->dma_addr = ring->map->dm_segs[0].ds_addr;

  /* Allocate dmamaps for each DMA descriptor. */
  for (i=0; i<num_descs; i++)
    if ((error = bus_dmamap_create(ring->tag, MAX_DESC_LEN, 2,
     MAX_CHUNK_LEN, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &descs[i].map)))
      {
      printf("%s: bus_dmamap_create(): error %d\n", NAME_UNIT, error);
      return error;
      }

# elif defined(__bsdi__)

  /* Allocate wired physical memory for DMA descriptor array. */
  if ((ring->first = malloc(size_descs, M_DEVBUF, M_NOWAIT)) == NULL)
    {
    printf("%s: malloc() failed for DMA descriptor array\n", NAME_UNIT);
    return ENOMEM;
    }
  descs = ring->first;
  memset(descs, 0, size_descs);

  /* Map kernel virt addr to PCI bus addr for DMA descriptor array. */
  ring->dma_addr = vtophys(descs); /* Relax! BSD/OS only. */

# endif

  ring->read  = descs;
  ring->write = descs;
  ring->first = descs;
  ring->last  = descs + num_descs -1;
  ring->last->control = TLP_DCTL_END_RING;
  ring->num_descs = num_descs;
  ring->size_descs = size_descs;
  ring->head = NULL;
  ring->tail = NULL;

  return 0;
  }

/* Destroy a DMA descriptor ring */
/* context: kernel (boot) or process (syscall) */
static void  /* BSD version */
destroy_ring(softc_t *sc, struct desc_ring *ring)
  {
  struct dma_desc *desc;
  struct mbuf *m;

  /* Free queued mbufs. */
  while ((m = mbuf_dequeue(ring)))
    m_freem(m);

  /* TX may have one pkt that is not on any queue. */
  if (sc->tx_mbuf)
    {
    m_freem(sc->tx_mbuf);
    sc->tx_mbuf = NULL;
    }

  /* Unmap active DMA descriptors. */
  while (ring->read != ring->write)
    {
    bus_dmamap_unload(ring->tag, ring->read->map);
    if (ring->read++ == ring->last) ring->read = ring->first;
    }

# if defined(__FreeBSD__)

  /* Free the dmamaps of all DMA descriptors. */
  for (desc=ring->first; desc!=ring->last+1; desc++)
    if (desc->map)
      bus_dmamap_destroy(ring->tag, desc->map);
  /* Unmap PCI address for DMA descriptor array. */
  if (ring->dma_addr)
    bus_dmamap_unload(ring->tag, ring->map);
  /* Free kernel memory for DMA descriptor array. */
  if (ring->first)
    bus_dmamem_free(ring->tag, ring->first, ring->map);
  /* Free the DMA tag created for this ring. */
  if (ring->tag)
    bus_dma_tag_destroy(ring->tag);

# elif defined(__NetBSD__) || defined(__OpenBSD__)

  /* Free the dmamaps of all DMA descriptors. */
  for (desc=ring->first; desc!=ring->last+1; desc++)
    if (desc->map)
      bus_dmamap_destroy(ring->tag, desc->map);
  /* Unmap PCI address for DMA descriptor array. */
  if (ring->dma_addr)
    bus_dmamap_unload(ring->tag, ring->map);
  /* Free dmamap for DMA descriptor array. */
  if (ring->map)
    bus_dmamap_destroy(ring->tag, ring->map);
  /* Unmap kernel address for DMA descriptor array. */
  if (ring->first)
    bus_dmamem_unmap(ring->tag, (void *)ring->first, ring->size_descs);
  /* Free kernel memory for DMA descriptor array. */
  if (ring->segs[0].ds_addr)
    bus_dmamem_free(ring->tag, ring->segs, ring->nsegs);

# elif defined(__bsdi__)

  /* Free kernel memory for DMA descriptor array. */
  if (ring->first)
    free(ring->first, M_DEVBUF);

# endif
  }

/* Singly-linked tail-queues hold mbufs with active DMA.
 * For RX, single mbuf clusters; for TX, mbuf chains are queued.
 * NB: mbufs are linked through their m_nextpkt field.
 * Callers must hold sc->bottom_lock; not otherwise locked.
 */

/* Put an mbuf (chain) on the tail of the descriptor ring queue. */
static void  /* BSD version */
mbuf_enqueue(struct desc_ring *ring, struct mbuf *m)
  {
  m->m_nextpkt = NULL;
  if (ring->tail == NULL)
    ring->head = m;
  else
    ring->tail->m_nextpkt = m;
  ring->tail = m;
  }

/* Get an mbuf (chain) from the head of the descriptor ring queue. */
static struct mbuf*  /* BSD version */
mbuf_dequeue(struct desc_ring *ring)
  {
  struct mbuf *m = ring->head;
  if (m)
    if ((ring->head = m->m_nextpkt) == NULL)
      ring->tail = NULL;
  return m;
  }

/* Clean up after a packet has been received. */
static int  /* BSD version */
rxintr_cleanup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->rxring;
  struct dma_desc *first_desc, *last_desc;
  struct mbuf *first_mbuf=NULL, *last_mbuf=NULL;
  struct mbuf *new_mbuf;
  int pkt_len, desc_len;

  /* Input packet flow control (livelock prevention): */
  /* Give pkts to higher levels only if quota is > 0. */
  if (sc->quota <= 0) return 0;

  /* This looks complicated, but remember: typically packets up */
  /*  to 2048 bytes long fit in one mbuf and use one descriptor. */

  first_desc = last_desc = ring->read;

  /* ASSERTION: If there is a descriptor in the ring and the hardware has */
  /*  finished with it, then that descriptor will have RX_FIRST_DESC set. */
  if ((ring->read != ring->write) && /* descriptor ring not empty */
     !(ring->read->status & TLP_DSTS_OWNER) && /* hardware done */
     !(ring->read->status & TLP_DSTS_RX_FIRST_DESC)) /* should be set */
    panic("%s: rxintr_cleanup: rx-first-descriptor not set.\n", NAME_UNIT);

  /* First decide if a complete packet has arrived. */
  /* Run down DMA descriptors looking for one marked "last". */
  /* Bail out if an active descriptor is encountered. */
  /* Accumulate most significant bits of packet length. */
  pkt_len = 0;
  for (;;)
    {
    if (last_desc == ring->write) return 0;  /* no more descs */
    if (last_desc->status & TLP_DSTS_OWNER) return 0; /* still active */
    if (last_desc->status & TLP_DSTS_RX_LAST_DESC) break; /* end of packet */
    pkt_len += last_desc->length1 + last_desc->length2; /* entire desc filled */
    if (last_desc++->control & TLP_DCTL_END_RING) last_desc = ring->first; /* ring wrap */
    }

  /* A complete packet has arrived; how long is it? */
  /* H/w ref man shows RX pkt length as a 14-bit field. */
  /* An experiment found that only the 12 LSBs work. */
  if (((last_desc->status>>16)&0xFFF) == 0) pkt_len += 4096; /* carry-bit */
  pkt_len = (pkt_len & 0xF000) + ((last_desc->status>>16) & 0x0FFF);
  /* Subtract the CRC length unless doing so would underflow. */
  if (pkt_len >= sc->config.crc_len) pkt_len -= sc->config.crc_len;

  /* Run down DMA descriptors again doing the following:
   *  1) put pkt info in pkthdr of first mbuf,
   *  2) link mbufs,
   *  3) set mbuf lengths.
   */
  first_desc = ring->read;
  do
    {
    /* Read a DMA descriptor from the ring. */
    last_desc = ring->read;
    /* Advance the ring read pointer. */
    if (ring->read++ == ring->last) ring->read = ring->first;

    /* Dequeue the corresponding cluster mbuf. */
    new_mbuf = mbuf_dequeue(ring);
    if (new_mbuf == NULL)
      panic("%s: rxintr_cleanup: expected an mbuf\n", NAME_UNIT);

    desc_len = last_desc->length1 + last_desc->length2;
    /* If bouncing, copy bounce buf to mbuf. */
    DMA_SYNC(last_desc->map, desc_len, BUS_DMASYNC_POSTREAD);
    /* Unmap kernel virtual address to PCI bus address. */
    bus_dmamap_unload(ring->tag, last_desc->map);

    /* 1) Put pkt info in pkthdr of first mbuf. */
    if (last_desc == first_desc)
      {
      first_mbuf = new_mbuf;
      first_mbuf->m_pkthdr.len   = pkt_len; /* total pkt length */
# if IFNET
      first_mbuf->m_pkthdr.rcvif = sc->ifp; /* how it got here */
# else
      first_mbuf->m_pkthdr.rcvif = NULL;
# endif
      }
    else /* 2) link mbufs. */
      {
      KASSERT(last_mbuf != NULL);
      last_mbuf->m_next = new_mbuf;
      /* M_PKTHDR should be set in the first mbuf only. */
      new_mbuf->m_flags &= ~M_PKTHDR;
      }
    last_mbuf = new_mbuf;

    /* 3) Set mbuf lengths. */
    new_mbuf->m_len = (pkt_len >= desc_len) ? desc_len : pkt_len;
    pkt_len -= new_mbuf->m_len;
    } while ((last_desc->status & TLP_DSTS_RX_LAST_DESC)==0);

  /* Decide whether to accept or to drop this packet. */
  /* RxHDLC sets MIIERR for bad CRC, abort and partial byte at pkt end. */
  if (!(last_desc->status & TLP_DSTS_RX_BAD) &&
   (sc->status.link_state == STATE_UP) &&
   (first_mbuf->m_pkthdr.len > 0))
    {
    /* Optimization: copy a small pkt into a small mbuf. */
    if (first_mbuf->m_pkthdr.len <= COPY_BREAK)
      {
      MGETHDR(new_mbuf, M_DONTWAIT, MT_DATA);
      if (new_mbuf)
        {
        new_mbuf->m_pkthdr.rcvif = first_mbuf->m_pkthdr.rcvif;
        new_mbuf->m_pkthdr.len   = first_mbuf->m_pkthdr.len;
        new_mbuf->m_len          = first_mbuf->m_len;
        memcpy(new_mbuf->m_data,   first_mbuf->m_data,
         first_mbuf->m_pkthdr.len);
        m_freem(first_mbuf);
        first_mbuf = new_mbuf;
        }
      }

    /* Include CRC and one flag byte in input byte count. */
    sc->status.cntrs.ibytes += first_mbuf->m_pkthdr.len + sc->config.crc_len +1;
    sc->status.cntrs.ipackets++;

    /* Berkeley Packet Filter */
    LMC_BPF_MTAP(sc, first_mbuf);

    /* Give this good packet to the network stacks. */
    sc->quota--;
    if (sc->stack)
      sc->stack->input(sc, first_mbuf);
    else
      {
      m_freem(first_mbuf);
      sc->status.cntrs.idrops++;
      }
    }
  else if (sc->status.link_state != STATE_UP)
    {
    /* If the link is down, this packet is probably noise. */
    m_freem(first_mbuf);
    sc->status.cntrs.idrops++;
    if (sc->config.debug)
      printf("%s: rxintr_cleanup: rx pkt dropped: link down\n", NAME_UNIT);
    }
  else /* Log and drop this bad packet. */
    {
    if (sc->config.debug)
      printf("%s: RX bad pkt; len=%d %s%s%s%s\n",
       NAME_UNIT, first_mbuf->m_pkthdr.len,
       (last_desc->status & TLP_DSTS_RX_MII_ERR)  ? " miierr"  : "",
       (last_desc->status & TLP_DSTS_RX_DRIBBLE)  ? " dribble" : "",
       (last_desc->status & TLP_DSTS_RX_DESC_ERR) ? " descerr" : "",
       (last_desc->status & TLP_DSTS_RX_OVERRUN)  ? " overrun" : "");
    if (last_desc->status & TLP_DSTS_RX_OVERRUN)
      sc->status.cntrs.fifo_over++;
    else
      sc->status.cntrs.ierrors++;
    m_freem(first_mbuf);
    }

  return 1; /* did something */
  }

/* Setup (prepare) to receive a packet. */
/* Try to keep the RX descriptor ring full of empty buffers. */
static int  /* BSD version */
rxintr_setup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->rxring;
  struct dma_desc *desc;
  struct mbuf *m;
  int desc_len;
  int error;

  /* Ring is full if (wrap(write+1)==read) */
  if (((ring->write == ring->last) ? ring->first : ring->write+1) == ring->read)
    return 0;  /* ring is full; nothing to do */

  /* Allocate a small mbuf and attach an mbuf cluster. */
  MGETHDR(m, M_DONTWAIT, MT_DATA);
  if (m == NULL)
    {
    sc->status.cntrs.rxbuf++;
    if (sc->config.debug)
      printf("%s: rxintr_setup: MGETHDR() failed\n", NAME_UNIT);
    return 0;
    }
  MCLGET(m, M_DONTWAIT);
  if ((m->m_flags & M_EXT)==0)
    {
    m_freem(m);
    sc->status.cntrs.rxbuf++;
    if (sc->config.debug)
      printf("%s: rxintr_setup: MCLGET() failed\n", NAME_UNIT);
    return 0;
    }

  /* Queue the mbuf for later processing by rxintr_cleanup. */
  mbuf_enqueue(ring, m);

  /* Write a DMA descriptor into the ring. */
  /* Hardware will not see it until the OWNER bit is set. */
  desc = ring->write;
  /* Advance the ring write pointer. */
  if (ring->write++ == ring->last) ring->write = ring->first;

  desc_len = (MCLBYTES < MAX_DESC_LEN) ? MCLBYTES : MAX_DESC_LEN;
  /* Map kernel virt addr to PCI bus addr. */
  if ((error = DMA_LOAD(desc->map, m->m_data, desc_len)))
    printf("%s: bus_dmamap_load(rx): error %d\n", NAME_UNIT, error);
  /* Invalidate the cache for this mbuf. */
  DMA_SYNC(desc->map, desc_len, BUS_DMASYNC_PREREAD);

  /* Set up the DMA descriptor. */
# if defined(__FreeBSD__)
  desc->address1 = ring->segs[0].ds_addr;
# elif defined(__NetBSD__) || defined(__OpenBSD__)
  desc->address1 = desc->map->dm_segs[0].ds_addr;
# elif defined(__bsdi__)
  desc->address1 = vtophys(m->m_data); /* Relax! BSD/OS only. */
# endif
  desc->length1  = desc_len>>1;
  desc->address2 = desc->address1 + desc->length1;
  desc->length2  = desc_len>>1;

  /* Before setting the OWNER bit, flush cache backing DMA descriptors. */
  DMA_SYNC(ring->map, ring->size_descs, BUS_DMASYNC_PREWRITE);

  /* Commit the DMA descriptor to the hardware. */
  desc->status = TLP_DSTS_OWNER;

  /* Notify the receiver that there is another buffer available. */
  WRITE_CSR(sc, TLP_RX_POLL, 1);

  return 1; /* did something */
  }

/* Clean up after a packet has been transmitted. */
/* Free the mbuf chain and update the DMA descriptor ring. */
static int  /* BSD version */
txintr_cleanup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->txring;
  struct dma_desc *desc;

  while ((ring->read != ring->write) && /* while ring is not empty */
        !(ring->read->status & TLP_DSTS_OWNER))
    {
    /* Read a DMA descriptor from the ring. */
    desc = ring->read;
    /* Advance the ring read pointer. */
    if (ring->read++ == ring->last) ring->read = ring->first;

    /* This is a no-op on most architectures. */
    DMA_SYNC(desc->map, desc->length1 + desc->length2, BUS_DMASYNC_POSTWRITE);
    /* Unmap kernel virtual address to PCI bus address. */
    bus_dmamap_unload(ring->tag, desc->map);

    /* If this descriptor is the last segment of a packet, */
    /*  then dequeue and free the corresponding mbuf chain. */
    if (desc->control & TLP_DCTL_TX_LAST_SEG)
      {
      struct mbuf *m;

      if ((m = mbuf_dequeue(ring)) == NULL)
        panic("%s: txintr_cleanup: expected an mbuf\n", NAME_UNIT);

      /* The only bad TX status is fifo underrun. */
      if (desc->status & TLP_DSTS_TX_UNDERRUN)
        {
        if (sc->config.debug)
          printf("%s: txintr_cleanup: tx fifo underrun\n", NAME_UNIT);
        sc->status.cntrs.fifo_under++;
        sc->status.cntrs.oerrors++;
	}
      else
        {
        /* Include CRC and one flag byte in output byte count. */
        sc->status.cntrs.obytes += m->m_pkthdr.len + sc->config.crc_len +1;
        sc->status.cntrs.opackets++;

        /* Berkeley Packet Filter */
        LMC_BPF_MTAP(sc, m);
	}

      m_freem(m);
      return 1;  /* did something */
      }
    }

  return 0;
  }

/* Build DMA descriptors for a transmit packet mbuf chain. */
static int  /* 0=success; 1=error */ /* BSD version */
txintr_setup_mbuf(softc_t *sc, struct mbuf *m)
  {
  struct desc_ring *ring = &sc->txring;
  struct dma_desc *desc;
  unsigned int desc_len;

  /* build DMA descriptors for a chain of mbufs. */
  while (m)
    {
    char *data = m->m_data;
    int length = m->m_len; /* zero length mbufs happen! */

    /* Build DMA descriptors for one mbuf. */
    while (length > 0)
      {
      int error;

      /* Ring is full if (wrap(write+1)==read) */
      if (((ring->temp==ring->last) ? ring->first : ring->temp+1) == ring->read)
        { /* Not enough DMA descriptors; try later. */
        for (; ring->temp!=ring->write;
         ring->temp = (ring->temp==ring->first)? ring->last : ring->temp-1)
          bus_dmamap_unload(ring->tag, ring->temp->map);
        sc->status.cntrs.txdma++; /* IFF_OACTIVE? */
        return 1;
	}

      /* Provisionally, write a descriptor into the ring. */
      /* But do not change the REAL ring write pointer. */
      /* Hardware will not see it until the OWNER bit is set. */
      desc = ring->temp;
      /* Advance the temporary ring write pointer. */
      if (ring->temp++ == ring->last) ring->temp = ring->first;

      /* Clear all control bits except the END_RING bit. */
      desc->control &= TLP_DCTL_END_RING;
      /* Do not pad short packets up to 64 bytes. */
      desc->control |= TLP_DCTL_TX_NO_PAD;
      /* Use Tulip's CRC-32 generator, if appropriate. */
      if (sc->config.crc_len != CFG_CRC_32)
        desc->control |= TLP_DCTL_TX_NO_CRC;
      /* Set the OWNER bit, except in the first descriptor. */
      if (desc != ring->write)
        desc->status = TLP_DSTS_OWNER;

      desc_len = (length > MAX_CHUNK_LEN) ? MAX_CHUNK_LEN : length;
      /* Map kernel virt addr to PCI bus addr. */
      if ((error = DMA_LOAD(desc->map, data, desc_len)))
        printf("%s: bus_dmamap_load(tx): error %d\n", NAME_UNIT, error);
      /* Flush the cache and if bouncing, copy mbuf to bounce buf. */
      DMA_SYNC(desc->map, desc_len, BUS_DMASYNC_PREWRITE);

      /* Prevent wild fetches if mapping fails (nsegs==0). */
      desc->length1  = desc->length2  = 0;
      desc->address1 = desc->address2 = 0;
# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
        {
#  if defined(__FreeBSD__)
        bus_dma_segment_t *segs = ring->segs;
        int nsegs = ring->nsegs;
#  elif defined(__NetBSD__) || defined(__OpenBSD__)
        bus_dma_segment_t *segs = desc->map->dm_segs;
        int nsegs = desc->map->dm_nsegs;
#  endif
        if (nsegs >= 1)
          {
          desc->address1 = segs[0].ds_addr;
          desc->length1  = segs[0].ds_len;
          }
        if (nsegs == 2)
          {
          desc->address2 = segs[1].ds_addr;
          desc->length2  = segs[1].ds_len;
          }
        }
# elif defined(__bsdi__)
      desc->address1 = vtophys(data); /* Relax! BSD/OS only. */
      desc->length1  = desc_len;
# endif

      data   += desc_len;
      length -= desc_len;
      } /* while (length > 0) */

    m = m->m_next;
    } /* while (m) */

  return 0; /* success */
  }

/* Setup (prepare) to transmit a packet. */
/* Select a packet, build DMA descriptors and give packet to hardware. */
/* If DMA descriptors run out, abandon the attempt and return 0. */
static int  /* BSD version */
txintr_setup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->txring;
  struct dma_desc *first_desc, *last_desc;

  /* Protect against half-up links: Do not transmit */
  /*  if the receiver can not hear the far end. */
  if (sc->status.link_state != STATE_UP) return 0;

  /* Pick a packet to transmit. */
  if ((sc->tx_mbuf == NULL) && sc->stack)
    sc->stack->output(sc);
  if  (sc->tx_mbuf == NULL) return 0;  /* no pkt to transmit */

  /* Build DMA descriptors for an outgoing mbuf chain. */
  ring->temp = ring->write; /* temporary ring write pointer */
  if (txintr_setup_mbuf(sc, sc->tx_mbuf)) return 0;

  /* Enqueue the mbuf; txintr_cleanup will free it. */
  mbuf_enqueue(ring, sc->tx_mbuf);

  /* The transmitter has room for another packet. */
  sc->tx_mbuf = NULL;

  /* Set first & last segment bits. */
  /* last_desc is the desc BEFORE the one pointed to by ring->temp. */
  first_desc = ring->write;
  first_desc->control |= TLP_DCTL_TX_FIRST_SEG;
  last_desc = (ring->temp==ring->first)? ring->last : ring->temp-1;
   last_desc->control |= TLP_DCTL_TX_LAST_SEG;
  /* Interrupt at end-of-transmission?  Why bother the poor computer! */
/* last_desc->control |= TLP_DCTL_TX_INTERRUPT; */

  /* Make sure the OWNER bit is not set in the next descriptor. */
  /* The OWNER bit may have been set if a previous call aborted. */
  ring->temp->status = 0;

  /* Commit the DMA descriptors to the software. */
  ring->write = ring->temp;

  /* Before setting the OWNER bit, flush cache backing DMA descriptors. */
  DMA_SYNC(ring->map, ring->size_descs, BUS_DMASYNC_PREWRITE);

  /* Commit the DMA descriptors to the hardware. */
  first_desc->status = TLP_DSTS_OWNER;

  /* Notify the transmitter that there is another packet to send. */
  WRITE_CSR(sc, TLP_TX_POLL, 1);

  return 1; /* did something */
  }

/* BSD kernels call this when a hardware interrupt happens. */
static intr_return_t  /* context: interrupt */
bsd_interrupt(void *arg)
  {
  softc_t *sc = arg;

# if DEVICE_POLLING
  if (sc->ifp->if_capenable & IFCAP_POLLING)
    return IRQ_NONE;
# endif

  /* Cut losses early if this is not our interrupt. */
  if ((READ_CSR(sc, TLP_STATUS) & TLP_INT_TXRX)==0)
    return IRQ_NONE;

  /* Process tx and rx pkts. */
  lmc_interrupt(sc, sc->rxring.num_descs, 0);

  return IRQ_HANDLED;
  }

#endif /* BSD */

# if DEVICE_POLLING

/* This procedure services the card without interrupts. */
/* With rxintr_cleanup(), it implements input flow control. */
static void  /* context: softirq */
bsd_poll(struct ifnet *ifp, enum poll_cmd cmd, int quota)
  {
  softc_t *sc = IFP2SC(ifp);

  /* Cut losses early if this is not our interrupt. */
  if ((READ_CSR(sc, TLP_STATUS) & TLP_INT_TXRX)==0)
    return;

  /* Process all tx pkts and up to quota rx pkts. */
  lmc_interrupt(sc, quota, (cmd==POLL_AND_CHECK_STATUS));
  }

# endif /* DEVICE_POLLING */

#if defined(__linux__)

/* There are TWO VERSIONS of interrupt/DMA code: Linux & BSD.
 * Handling Linux and the BSDs with CPP directives would
 *  make the code unreadable, so there are two versions.
 * Conceptually, the two versions do the same thing and
 *  lmc_interrupt() does not know they are different.
 *
 * We are "standing on the head of a pin" in these routines.
 * Tulip CSRs can be accessed, but nothing else is interrupt-safe!
 * Do NOT access: MII, GPIO, SROM, BIOSROM, XILINX, SYNTH, or DAC.
 */

/* Initialize a DMA descriptor ring. */
/* context: kernel (boot) or process (syscall) */
static int  /* Linux version */
create_ring(softc_t *sc, struct desc_ring *ring, int num_descs)
  {
  struct dma_desc *descs;
  int size_descs = sizeof(struct dma_desc)*num_descs;

  /* Allocate and map CACHE COHERENT memory for DMA descriptor array. */
  if ((descs = pci_alloc_consistent(sc->pcidev, size_descs,
   &ring->dma_addr)) == NULL)
    {
    printk("%s: pci_alloc_consistent() failed\n", NAME_UNIT);
    return -ENOMEM;
    }
  memset(descs, 0, size_descs);

  ring->read  = descs;
  ring->write = descs;
  ring->first = descs;
  ring->last  = descs + num_descs -1;
  ring->last->control = TLP_DCTL_END_RING;
  ring->num_descs = num_descs;
  ring->size_descs = size_descs;
  ring->head = NULL;
  ring->tail = NULL;

  return 0;
  }

/* Destroy a DMA descriptor ring */
/* context: kernel (boot) or process (syscall) */
static void  /* Linux version */
destroy_ring(softc_t *sc, struct desc_ring *ring)
  {
  struct sk_buff *skb;

  /* Free queued skbuffs. */
  while ((skb = skbuff_dequeue(ring)))
    dev_kfree_skb(skb);

  /* TX may have one pkt that is not on any queue. */
  if (sc->tx_skb)
    {
    dev_kfree_skb(sc->tx_skb);
    sc->tx_skb = NULL;
    }

  if (ring->first)
    {
    /* Unmap active DMA descriptors. */
    while (ring->read != ring->write)
      {
      pci_unmap_single(sc->pcidev, ring->read->address1,
       ring->read->length1 + ring->read->length2, PCI_DMA_BIDIRECTIONAL);
      if (ring->read++ == ring->last) ring->read = ring->first;
      }

    /* Unmap and free memory for DMA descriptor array. */
    pci_free_consistent(sc->pcidev, ring->size_descs, ring->first,
     ring->dma_addr);
    }
  }

/* Singly-linked tail-queues hold sk_buffs with active DMA.
 * skbuffs are linked through their sk_buff.next field.
 * Callers must hold sc->bottom_lock; not otherwise locked.
 */

/* Put an skbuff on the tail of the descriptor ring queue. */
static void  /* Linux version */
skbuff_enqueue(struct desc_ring *ring, struct sk_buff *skb)
  {
  skb->next = NULL;
  if (ring->tail == NULL)
    ring->head = skb;
  else
    ring->tail->next = skb;
  ring->tail = skb;
  }

/* Get an skbuff from the head of the descriptor ring queue. */
static struct sk_buff*  /* Linux version */
skbuff_dequeue(struct desc_ring *ring)
  {
  struct sk_buff *skb = ring->head;
  if (skb)
    if ((ring->head = skb->next) == NULL)
      ring->tail = NULL;
  return skb;
  }

static int  /* Linux version */
rxintr_cleanup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->rxring;
  struct dma_desc *first_desc, *last_desc;
  struct sk_buff *first_skb=NULL, *last_skb=NULL;
  struct sk_buff *new_skb;
  int pkt_len, desc_len;

  /* Input packet flow control (livelock prevention): */
  /* Give pkts to higher levels only if quota is > 0. */
  if (sc->quota <= 0) return 0;

  /* This looks complicated, but remember: packets up to 4032 */
  /*  bytes long fit in one skbuff and use one DMA descriptor. */

  first_desc = last_desc = ring->read;

  /* ASSERTION: If there is a descriptor in the ring and the hardware has */
  /*  finished with it, then that descriptor will have RX_FIRST_DESC set. */
  if ((ring->read != ring->write) && /* descriptor ring not empty */
     !(ring->read->status & TLP_DSTS_OWNER) && /* hardware done */
     !(ring->read->status & TLP_DSTS_RX_FIRST_DESC)) /* should be set */
    panic("%s: rxintr_cleanup: rx-first-descriptor not set.\n", NAME_UNIT);

  /* First decide if a complete packet has arrived. */
  /* Run down DMA descriptors looking for one marked "last". */
  /* Bail out if an active descriptor is encountered. */
  /* Accumulate most significant bits of packet length. */
  pkt_len = 0;
  for (;;)
    {
    if (last_desc == ring->write) return 0;  /* no more descs */
    if (last_desc->status & TLP_DSTS_OWNER) return 0; /* still active */
    if (last_desc->status & TLP_DSTS_RX_LAST_DESC) break; /* end of packet */
    pkt_len += last_desc->length1 + last_desc->length2; /* entire desc filled */
    if (last_desc++->control & TLP_DCTL_END_RING) last_desc = ring->first; /* ring wrap */
    }
  sc->netdev->last_rx = jiffies; /* timestamp arrival of new packet */

  /* A complete packet has arrived; how long is it? */
  /* H/w ref man shows RX pkt length as a 14-bit field. */
  /* An experiment found that only the 12 LSBs work. */
  if (((last_desc->status>>16)&0xFFF) == 0) pkt_len += 4096; /* carry-bit */
  pkt_len = (pkt_len & 0xF000) + ((last_desc->status>>16) & 0x0FFF);
  /* Subtract the CRC length unless doing so would underflow. */
  if (pkt_len >= sc->config.crc_len) pkt_len -= sc->config.crc_len;

  /* Run down DMA descriptors again doing the following:
   *  1) put pkt info in hdr of first skbuff.
   *  2) put additional skbuffs on frag_list.
   *  3) set skbuff lengths.
   */
  first_desc = ring->read;
  do
    {
    /* Read a DMA descriptor from the ring. */
    last_desc = ring->read;
    /* Advance the ring read pointer. */
    if (ring->read++ == ring->last) ring->read = ring->first;

    /* Dequeue the corresponding skbuff. */
    new_skb = skbuff_dequeue(ring);
    if (new_skb == NULL)
      panic("%s: rxintr_cleanup: expected an skbuff\n", NAME_UNIT);

    desc_len = last_desc->length1 + last_desc->length2;
    /* Unmap kernel virtual addresss to PCI bus address. */
    pci_unmap_single(sc->pcidev, last_desc->address1,
     desc_len, PCI_DMA_FROMDEVICE);

    /* Set skbuff length. */
    skb_put(new_skb, (pkt_len >= desc_len) ? desc_len : pkt_len);
    pkt_len -= new_skb->len;

    /* 1) Put pkt info in hdr of first skbuff. */
    if (last_desc == first_desc)
      {
      first_skb = new_skb;
      if (sc->stack)
        first_skb->protocol = sc->stack->type(sc, first_skb);
      else
        first_skb->protocol = htons(ETH_P_HDLC);
      first_skb->pkt_type   = PACKET_HOST;
      first_skb->mac.raw    = first_skb->data;
      first_skb->ip_summed  = CHECKSUM_NONE;
      first_skb->dev        = sc->netdev;
      }
    else /* 2) link skbuffs. */
      {
      /* Put this skbuff on the frag_list of the first skbuff. */
      new_skb->next = NULL;
      if (skb_shinfo(first_skb)->frag_list == NULL)
        skb_shinfo(first_skb)->frag_list = new_skb;
      else
        /* ASSERT(last_skb != NULL); */
        last_skb->next = new_skb;
      /* 3) set skbuff lengths. */
      first_skb->len      += new_skb->len;
      first_skb->data_len += new_skb->len;
      }
    last_skb = new_skb;
    } while ((last_desc->status & TLP_DSTS_RX_LAST_DESC)==0);

  /* Decide whether to accept or to drop this packet. */
  /* RxHDLC sets MIIERR for bad CRC, abort and partial byte at pkt end. */
  if (!(last_desc->status & TLP_DSTS_RX_BAD) &&
   (sc->status.link_state == STATE_UP) &&
   (first_skb->len > 0))
    {
    /* Optimization: copy a small pkt into a small skbuff. */
    if (first_skb->len <= COPY_BREAK)
      if ((new_skb = skb_copy(first_skb, GFP_ATOMIC)))
        {
        dev_kfree_skb_any(first_skb);
        first_skb = new_skb;
	}

    /* Include CRC and one flag byte in input byte count. */
    sc->status.cntrs.ibytes += first_skb->len + sc->config.crc_len +1;
    sc->status.cntrs.ipackets++;

    /* Give this good packet to the network stacks. */
    sc->quota--;
# if NAPI
    netif_receive_skb(first_skb);
# else
    if (netif_rx(first_skb) == NET_RX_DROP)
      {
      sc->status.cntrs.idrops++;
      if (sc->config.debug && printk_ratelimit())
        printk("%s: rxintr_cleanup: rx pkt dropped by kernel\n", NAME_UNIT);
      }
# endif
    }
  else if (sc->status.link_state != STATE_UP)
    {
    /* If the link is down, this packet is probably noise. */
    dev_kfree_skb_any(first_skb);
    sc->status.cntrs.idrops++;
    if (sc->config.debug && printk_ratelimit())
      printk("%s: rxintr_cleanup: rx pkt dropped: link down\n", NAME_UNIT);
    }
  else /* Log and drop this bad packet. */
    {
    if (sc->config.debug && printk_ratelimit())
      printk("%s: RX bad pkt; len=%d %s%s%s%s\n",
       NAME_UNIT, first_skb->len,
       (last_desc->status & TLP_DSTS_RX_MII_ERR)  ? " miierr"  : "",
       (last_desc->status & TLP_DSTS_RX_DRIBBLE)  ? " dribble" : "",
       (last_desc->status & TLP_DSTS_RX_DESC_ERR) ? " descerr" : "",
       (last_desc->status & TLP_DSTS_RX_OVERRUN)  ? " overrun" : "");
    if (last_desc->status & TLP_DSTS_RX_OVERRUN)
      sc->status.cntrs.fifo_over++;
    else
      sc->status.cntrs.ierrors++;
    dev_kfree_skb_any(first_skb);
    }

  return 1; /* did something */
  }

/* Setup (prepare) to receive a packet. */
/* Try to keep the RX descriptor ring full of empty buffers. */
static int  /* Linux version */
rxintr_setup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->rxring;
  struct dma_desc *desc;
  struct sk_buff *skb;
  u_int32_t dma_addr;

  /* Ring is full if (wrap(write+1)==read) */
  if (((ring->write == ring->last) ? ring->first : ring->write+1) == ring->read)
    return 0;  /* ring is full; nothing to do */

  /* Allocate an skbuff. */
  if ((skb = dev_alloc_skb(MAX_DESC_LEN)) == NULL)
    {
    sc->status.cntrs.rxbuf++;
    if (sc->config.debug && printk_ratelimit())
      printk("%s: rxintr_setup: dev_alloc_skb() failed\n", NAME_UNIT);
    return 0;
    }
  skb->dev = sc->netdev;

  /* Queue the skbuff for later processing by rxintr_cleanup. */
  skbuff_enqueue(ring, skb);

  /* Write a DMA descriptor into the ring. */
  /* Hardware will not see it until the OWNER bit is set. */
  desc = ring->write;
  /* Advance the ring write pointer. */
  if (ring->write++ == ring->last) ring->write = ring->first;

  /* Map kernel virt addr to PCI bus addr; invalidate cache. */
  dma_addr = pci_map_single(sc->pcidev, skb->data,
   MAX_DESC_LEN, PCI_DMA_FROMDEVICE);

  /* Set up the DMA descriptor. */
  desc->address1 = dma_addr;
  desc->length1  = MAX_CHUNK_LEN;
  desc->address2 = desc->address1 + desc->length1;
  desc->length2  = MAX_CHUNK_LEN;

  /* Before setting the OWNER bit, force descriptor writes to complete. */
  wmb(); /* write memory barrier */

  /* Commit the DMA descriptor to the hardware. */
  desc->status = TLP_DSTS_OWNER;

  /* Notify the receiver that there is another buffer available. */
  WRITE_CSR(sc, TLP_RX_POLL, 1);

  return 1; /* did something */
  }

/* Clean up after a packet has been transmitted. */
/* Free the sk_buff and update the DMA descriptor ring. */
static int  /* Linux version */
txintr_cleanup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->txring;
  struct dma_desc *desc;

  while ((ring->read != ring->write) && /* ring is not empty */
        !(ring->read->status & TLP_DSTS_OWNER))
    {
    /* Read a DMA descriptor from the ring. */
    desc = ring->read;
    /* Advance the ring read pointer. */
    if (ring->read++ == ring->last) ring->read = ring->first;

    /* Unmap kernel virtual address to PCI bus address. */
    pci_unmap_single(sc->pcidev, desc->address1,
     desc->length1 + desc->length2, PCI_DMA_TODEVICE);

    /* If this descriptor is the last segment of a packet, */
    /*  then dequeue and free the corresponding skbuff. */
    if (desc->control & TLP_DCTL_TX_LAST_SEG)
      {
      struct sk_buff *skb;

      if ((skb = skbuff_dequeue(ring)) == NULL)
        panic("%s: txintr_cleanup: expected an sk_buff\n", NAME_UNIT);

      /* The only bad TX status is fifo underrun. */
      if (desc->status & TLP_DSTS_TX_UNDERRUN)
        {
        if (sc->config.debug)
          printk("%s: txintr_cleanup: tx fifo underrun\n", NAME_UNIT);
        sc->status.cntrs.fifo_under++;
        sc->status.cntrs.oerrors++;
	}
      else
        {
        /* Include CRC and one flag byte in output byte count. */
        sc->status.cntrs.obytes += skb->len + sc->config.crc_len +1;
        sc->status.cntrs.opackets++;
	}

      dev_kfree_skb_any(skb);
      return 1;  /* did something */
      }
    }

  return 0;
  }

/* Build DMA descriptors for a tranmit packet fragment, */
/* Assertion: fragment is contiguous in physical memory. */
static int  /* 0=success; 1=error */ /* linux version */
txintr_setup_frag(softc_t *sc, char *data, int length)
  {
  struct desc_ring *ring = &sc->txring;
  struct dma_desc *desc;
  unsigned int desc_len;
  u_int32_t dma_addr;

  while (length > 0)
    {
    /* Ring is full if (wrap(write+1)==read) */
    if (((ring->temp==ring->last) ? ring->first : ring->temp+1) == ring->read)
      { /* Not enough DMA descriptors; try later. */
      for (; ring->temp!=ring->write;
       ring->temp = (ring->temp==ring->first)? ring->last : ring->temp-1)
        pci_unmap_single(sc->pcidev, ring->temp->address1,
         ring->temp->length1 + ring->temp->length2, PCI_DMA_FROMDEVICE);
      sc->status.cntrs.txdma++;
      return 1;
      }

    /* Provisionally, write a DMA descriptor into the ring. */
    /* But do not change the REAL ring write pointer. */
    /* Hardware will not see it until the OWNER bit is set. */
    desc = ring->temp;
    /* Advance the temporary ring write pointer. */
    if (ring->temp++ == ring->last) ring->temp = ring->first;

    /* Clear all control bits except the END_RING bit. */
    desc->control &= TLP_DCTL_END_RING;
    /* Do not pad short packets up to 64 bytes */
    desc->control |= TLP_DCTL_TX_NO_PAD;
    /* Use Tulip's CRC-32 generator, if appropriate. */
    if (sc->config.crc_len != CFG_CRC_32)
      desc->control |= TLP_DCTL_TX_NO_CRC;
    /* Set the OWNER bit, except in the first descriptor. */
    if (desc != ring->write)
      desc->status = TLP_DSTS_OWNER;

    desc_len = (length >= MAX_DESC_LEN) ? MAX_DESC_LEN : length;
    /* Map kernel virt addr to PCI bus addr; flush cache. */
    dma_addr = pci_map_single(sc->pcidev, data, desc_len, PCI_DMA_TODEVICE);
    /* If it will fit in one chunk, do so, otherwise split it. */
    if (desc_len <= MAX_CHUNK_LEN)
      {
      desc->address1 = dma_addr;
      desc->length1  = desc_len;
      desc->address2 = 0;
      desc->length2  = 0;
      }
    else
      {
      desc->address1 = dma_addr;
      desc->length1  = desc_len>>1;
      desc->address2 = desc->address1 + desc->length1;
      desc->length2  = desc_len>>1;
      if (desc_len & 1) desc->length2++;
      }

    data   += desc_len;
    length -= desc_len;
    } /* while (length > 0) */

  return 0; /* success */
  }

/* NB: this procedure is recursive! */
static int  /* 0=success; 1=error */
txintr_setup_skb(softc_t *sc, struct sk_buff *skb)
  {
  struct sk_buff *list;
  int i;

  /* First, handle the data in the skbuff itself. */
  if (txintr_setup_frag(sc, skb->data, skb_headlen(skb)))
    return 1;

  /* Next, handle the VM pages in the Scatter/Gather list. */
  if (skb_shinfo(skb)->nr_frags)
    for (i=0; i<skb_shinfo(skb)->nr_frags; i++)
      {
      skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
      if (txintr_setup_frag(sc, page_address(frag->page) +
       frag->page_offset, frag->size))
        return 1;
      }

  /* Finally, handle the skbuffs in the frag_list. */
  if ((list = skb_shinfo(skb)->frag_list))
    for (; list; list=list->next)
      if (txintr_setup_skb(sc, list)) /* recursive! */
        return 1;

  return 0;
  }

/* Setup (prepare) to transmit a packet. */
/* Select a packet, build DMA descriptors and give packet to hardware. */
/* If DMA descriptors run out, abandon the attempt and return 0. */
static int  /* Linux version */
txintr_setup(softc_t *sc)
  {
  struct desc_ring *ring = &sc->txring;
  struct dma_desc *first_desc, *last_desc;

  /* Protect against half-up links: Do not transmit */
  /*  if the receiver can not hear the far end. */
  if (sc->status.link_state != STATE_UP) return 0;

  /* Pick a packet to transmit. */
  /* netdev_start() puts packets in sc->tx_skb. */
  if (sc->tx_skb == NULL)
    {
    /* netdev_start() calls netif_stop_queue() sometimes. */
    if (netif_queue_stopped(sc->netdev))
      netif_wake_queue(sc->netdev);
    return 0; /* no pkt to transmit */
    }

  /* Build DMA descriptors for an outgoing skbuff. */
  ring->temp = ring->write; /* temporary ring write pointer */
  if (txintr_setup_skb(sc, sc->tx_skb)) return 0;

  /* Enqueue the skbuff; txintr_cleanup will free it. */
  skbuff_enqueue(ring, sc->tx_skb);

  /* The transmitter has room for another packet. */
  sc->tx_skb = NULL;

  /* Set first & last segment bits. */
  /* last_desc is the desc BEFORE the one pointed to by ring->temp. */
  first_desc = ring->write;
  first_desc->control |= TLP_DCTL_TX_FIRST_SEG;
  last_desc = (ring->temp==ring->first)? ring->last : ring->temp-1;
   last_desc->control |= TLP_DCTL_TX_LAST_SEG;
  /* Interrupt at end-of-transmission?  Why bother the poor computer! */
/* last_desc->control |= TLP_DCTL_TX_INTERRUPT; */

  /* Make sure the OWNER bit is not set in the next descriptor. */
  /* The OWNER bit may have been set if a previous call aborted. */
  ring->temp->status = 0;

  /* Commit the DMA descriptors to the software. */
  ring->write = ring->temp;

  /* Before setting the OWNER bit, force descriptor writes to complete. */
  wmb(); /* write memory barrier */

  /* Commit the DMA descriptors to the hardware. */
  first_desc->status = TLP_DSTS_OWNER;

  /* Notify the transmitter that there is another packet to send. */
  WRITE_CSR(sc, TLP_TX_POLL, 1);

  sc->netdev->trans_start = jiffies;

  return 1; /* did something */
  }

/* Linux kernels call this when a hardware interrupt happens. */
static irqreturn_t  /* context: interrupt */
linux_interrupt(int irq, void *arg, struct pt_regs *regs)
  {
  softc_t *sc = arg;

  /* Cut losses early if this is not our interrupt. */
  if ((READ_CSR(sc, TLP_STATUS) & TLP_INT_TXRX)==0)
    return IRQ_NONE;

# if NAPI

  /* Disable card interrupts. */
  WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_DISABLE);

  /* Add self to poll list. */
  if (netif_rx_schedule_prep(sc->netdev))
    __netif_rx_schedule(sc->netdev);
  else
    printk("%s: interrupt while on poll list\n", NAME_UNIT);

# else /* not NAPI */

  /* Process tx and rx pkts. */
  lmc_interrupt(sc, sc->rxring,num_descs, 0);

# endif /* NAPI */

  return IRQ_HANDLED;
  }

#endif /* __linux__ */

/* Open a line protocol. */
/* context: kernel (boot) or process (syscall) */
static int
open_proto(softc_t *sc, struct config *config)
  {
  int error = 0;

  if (sc->stack)
    error = sc->stack->open(sc, config);
  else
    error = BSD ? ENOSYS : -ENOSYS;

  return error;
  }

/* Attach a line protocol stack. */
/* context: kernel (boot) or process (syscall) */
static int
attach_stack(softc_t *sc, struct config *config)
  {
  int error = 0;
  struct stack *stack = NULL;

  /* Done if stack is not changing. */
  if (sc->config.stack == config->stack)
    return 0;

  /* Detach the current stack. */
  if (sc->stack && ((error = sc->stack->detach(sc))))
    return error;

  switch (config->stack)
    {
    case STACK_RAWIP: /* built-in */
      stack = &rawip_stack;
      break;

#if SPPP
    case STACK_SPPP:
      stack = &sppp_stack;
      break;
#endif

#if P2P
    case STACK_P2P:
      stack = &p2p_stack;
      break;
#endif

#if GEN_HDLC
    case STACK_GEN_HDLC:
      stack = &gen_hdlc_stack;
      break;
#endif

#if SYNC_PPP
    case STACK_SYNC_PPP:
      stack = &sync_ppp_stack;
      break;
#endif

#if NETGRAPH
    case STACK_NETGRAPH:
      stack = &netgraph_stack;
      break;
#endif

    default:
      stack = NULL;
      break;
    }

  if (stack)
    error = stack->attach(sc, config);
  else
    error = BSD ? ENOSYS : -ENOSYS;

  return error;
  }


/*
 * This handles IOCTLs from lmcconfig(8).
 * Must not run when card watchdogs run.
 * Always called with top_lock held.
 */
static int  /* context: process */
lmc_ioctl(softc_t *sc, u_long cmd, void *data)
  {
  struct iohdr  *iohdr  = (struct iohdr  *) data;
  struct ioctl  *ioctl  = (struct ioctl  *) data;
  struct status *status = (struct status *) data;
  struct config *config = (struct config *) data;
  int error = 0;

  /* All structs start with a string and a cookie. */
  if (iohdr->cookie != NGM_LMC_COOKIE)
    return EINVAL;

  switch (cmd)
    {
    case LMCIOCGSTAT:
      {
      *status = sc->status;
      iohdr->cookie = NGM_LMC_COOKIE;
      break;
      }
    case LMCIOCGCFG:
      {
      *config = sc->config;
      iohdr->cookie = NGM_LMC_COOKIE;
      break;
      }
    case LMCIOCSCFG:
      {
      if ((error = CHECK_CAP)) break;
      if ((error = attach_stack(sc, config)));
      else error = open_proto(sc, config);
      tulip_loop(sc, config);
      sc->card->attach(sc, config);
      sc->config.debug = config->debug;
      break;
      }
    case LMCIOCREAD:
      {
      if (ioctl->cmd == IOCTL_RW_PCI)
        {
        if (ioctl->address > 252) { error = EFAULT; break; }
        ioctl->data = READ_PCI_CFG(sc, ioctl->address);
	}
      else if (ioctl->cmd == IOCTL_RW_CSR)
        {
        if (ioctl->address >  15) { error = EFAULT; break; }
        ioctl->data = READ_CSR(sc, ioctl->address*TLP_CSR_STRIDE);
	}
      else if (ioctl->cmd == IOCTL_RW_SROM)
        {
        if (ioctl->address >  63) { error = EFAULT; break; }
        ioctl->data = srom_read(sc, ioctl->address);
	}
      else if (ioctl->cmd == IOCTL_RW_BIOS)
        ioctl->data = bios_read(sc, ioctl->address);
      else if (ioctl->cmd == IOCTL_RW_MII)
        ioctl->data = mii_read(sc, ioctl->address);
      else if (ioctl->cmd == IOCTL_RW_FRAME)
        ioctl->data = framer_read(sc, ioctl->address);
      else
        error = EINVAL;
      break;
      }
    case LMCIOCWRITE:
      {
      if ((error = CHECK_CAP)) break;
      if (ioctl->cmd == IOCTL_RW_PCI)
        {
        if (ioctl->address > 252) { error = EFAULT; break; }
        WRITE_PCI_CFG(sc, ioctl->address, ioctl->data);
	}
      else if (ioctl->cmd == IOCTL_RW_CSR)
        {
        if (ioctl->address >  15) { error = EFAULT; break; }
        WRITE_CSR(sc, ioctl->address*TLP_CSR_STRIDE, ioctl->data);
	}
      else if (ioctl->cmd == IOCTL_RW_SROM)
        {
        if (ioctl->address >  63) { error = EFAULT; break; }
        srom_write(sc, ioctl->address, ioctl->data); /* can sleep */
	}
      else if (ioctl->cmd == IOCTL_RW_BIOS)
        {
        if (ioctl->address == 0) bios_erase(sc);
        bios_write(sc, ioctl->address, ioctl->data); /* can sleep */
	}
      else if (ioctl->cmd == IOCTL_RW_MII)
        mii_write(sc, ioctl->address, ioctl->data);
      else if (ioctl->cmd == IOCTL_RW_FRAME)
        framer_write(sc, ioctl->address, ioctl->data);
      else if (ioctl->cmd == IOCTL_WO_SYNTH)
        synth_write(sc, (struct synth *)&ioctl->data);
      else if (ioctl->cmd == IOCTL_WO_DAC)
        {
        dac_write(sc, 0x9002); /* set Vref = 2.048 volts */
        dac_write(sc, ioctl->data & 0xFFF);
	}
      else
        error = EINVAL;
      break;
      }
    case LMCIOCTL:
      {
      if ((error = CHECK_CAP)) break;
      if (ioctl->cmd == IOCTL_XILINX_RESET)
        {
        xilinx_reset(sc);
        sc->card->attach(sc, &sc->config);
	}
      else if (ioctl->cmd == IOCTL_XILINX_ROM)
        {
        xilinx_load_from_rom(sc);
        sc->card->attach(sc, &sc->config);
	}
      else if (ioctl->cmd == IOCTL_XILINX_FILE)
        {
        error = xilinx_load_from_file(sc, ioctl->ucode, ioctl->data);
        if (error) xilinx_load_from_rom(sc); /* try the rom */
        sc->card->attach(sc, &sc->config);
	}
      else if (ioctl->cmd == IOCTL_RESET_CNTRS)
        reset_cntrs(sc);
      else
        error = sc->card->ioctl(sc, ioctl);
      break;
      }
    default:
      error = EINVAL;
      break;
    }

  return error;
  }

/* This is the core watchdog procedure.
 * ioctl syscalls and card watchdog routines must be interlocked.
 * Called by ng_watchdog(), ifnet_watchdog() and netdev_watchdog().
 */
static void  /* context: softirq */
lmc_watchdog(softc_t *sc)
  {
  /* Read and restart the Tulip timer. */
  u_int32_t tx_speed = READ_CSR(sc, TLP_TIMER);
  WRITE_CSR(sc, TLP_TIMER, 0xFFFF);

  /* Measure MII clock using a timer in the Tulip chip.
   * This timer counts transmitter bits divided by 4096.
   * Since this is called once a second the math is easy.
   * This is only correct when the link is NOT sending pkts.
   * On a fully-loaded link, answer will be HALF actual rate.
   * Clock rate during pkt is HALF clk rate between pkts.
   * Measuring clock rate really measures link utilization!
   */
  sc->status.tx_speed = (0xFFFF - (tx_speed & 0xFFFF)) << 12;

  /* Call the card-specific watchdog routine. */
  if (TOP_TRYLOCK(sc))
    {
    /* Remember link_state before updating it. */
    sc->last_link_state = sc->status.link_state;
    /* Update status.link_state. */
    sc->card->watchdog(sc);

    /* Increment a counter which tells user-land */
    /*  observers that SNMP state has been updated. */
    sc->status.ticks++;

    TOP_UNLOCK(sc);
    }
  else
    sc->status.cntrs.lck_watch++;

  /* Kernel date/time can take up to 5 seconds to start running. */
  if ((sc->status.ticks > 3) && /* h/w should be stable by now */
      (sc->status.cntrs.reset_time.tv_sec < 1000))
    {
    microtime(&sc->status.cntrs.reset_time);
    if (sc->status.cntrs.reset_time.tv_sec > 1000)
      reset_cntrs(sc);
    }

  /* Call the stack-specific watchdog routine. */
  if (sc->stack)
    sc->stack->watchdog(sc);

  /* In case an interrupt gets lost, process tx and rx pkts */
  lmc_interrupt(sc, sc->rxring.num_descs, 1);
  }

/* Administrative status of the driver (UP or DOWN) has changed.
 * A card-specific action is taken:
 *  HSSI: drop TA.
 * (T3:   send T3 idle ckt signal. )
 *  SSI:  drop RTS, DTR and DCD
 * (T1:   disable line interface tx; )
 */
static void
set_ready(softc_t *sc, int status)
  {
  struct ioctl ioctl;

  ioctl.cmd = IOCTL_SET_STATUS;
  ioctl.data = status;

  sc->card->ioctl(sc, &ioctl);
  }

static void
reset_cntrs(softc_t *sc)
  {
  memset(&sc->status.cntrs, 0, sizeof(struct cntrs));
  microtime(&sc->status.cntrs.reset_time);
  }

static void  /* context: process, softirq, interrupt! */
lmc_interrupt(void *arg, int quota, int check_status)
  {
  softc_t *sc = arg;
  int activity;

  /* Do this FIRST!  Otherwise UPs deadlock and MPs spin. */
  WRITE_CSR(sc, TLP_STATUS, READ_CSR(sc, TLP_STATUS));

  /* If any CPU is inside this critical section, then */
  /*  other CPUs should go away without doing anything. */
  if (BOTTOM_TRYLOCK(sc) == 0)
    {
    sc->status.cntrs.lck_intr++;
    return;
    }

  /* In Linux, pci_alloc_consistent() means DMA */
  /*  descriptors do not need explicit syncing? */
#if BSD
  {
  struct desc_ring *ring = &sc->txring;
  DMA_SYNC(sc->txring.map, sc->txring.size_descs,
   BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
  ring = &sc->rxring;
  DMA_SYNC(sc->rxring.map, sc->rxring.size_descs,
   BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
  }
#endif

  /* This is the main loop for interrupt processing. */
  sc->quota = quota;
  do
    {
    activity  = txintr_cleanup(sc);
    activity += txintr_setup(sc);
    activity += rxintr_cleanup(sc);
    activity += rxintr_setup(sc);
    } while (activity);

#if BSD
  {
  struct desc_ring *ring = &sc->txring;
  DMA_SYNC(sc->txring.map, sc->txring.size_descs,
   BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
  ring = &sc->rxring;
  DMA_SYNC(sc->rxring.map, sc->rxring.size_descs,
   BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
  }
#endif

  /* As the interrupt is dismissed, check for four unusual events. */
  if (check_status) check_intr_status(sc);

  BOTTOM_UNLOCK(sc);
  }

/* Check for four unusual events:
 *  1) fatal PCI bus errors       - some are recoverable
 *  2) transmitter FIFO underruns - increase fifo threshold
 *  3) receiver FIFO overruns     - clear potential hangup
 *  4) no receive descs or bufs   - count missed packets
 */
static void
check_intr_status(softc_t *sc)
  {
  u_int32_t status, cfcs, op_mode;
  u_int32_t missed, overruns;

  /* 1) A fatal bus error causes a Tulip to stop initiating bus cycles.
   * Module unload/load or boot are the only fixes for Parity Errors.
   * Master and Target Aborts can be cleared and life may continue.
   */
  status = READ_CSR(sc, TLP_STATUS);
  if (status & TLP_STAT_FATAL_ERROR)
    {
    u_int32_t fatal = (status & TLP_STAT_FATAL_BITS)>>TLP_STAT_FATAL_SHIFT;
    printf("%s: FATAL PCI BUS ERROR: %s%s%s%s\n", NAME_UNIT,
     (fatal == 0) ? "PARITY ERROR" : "",
     (fatal == 1) ? "MASTER ABORT" : "",
     (fatal == 2) ? "TARGET ABORT" : "",
     (fatal >= 3) ? "RESERVED (?)" : "");
    cfcs = READ_PCI_CFG(sc, TLP_CFCS);  /* try to clear it */
    cfcs &= ~(TLP_CFCS_MSTR_ABORT | TLP_CFCS_TARG_ABORT);
    WRITE_PCI_CFG(sc, TLP_CFCS, cfcs);
    }

  /* 2) If the transmitter fifo underruns, increase the transmit fifo
   *  threshold: the number of bytes required to be in the fifo
   *  before starting the transmitter (cost: increased tx delay).
   * The TX_FSM must be stopped to change this parameter.
   */
  if (status & TLP_STAT_TX_UNDERRUN)
    {
    op_mode = READ_CSR(sc, TLP_OP_MODE);
    /* enable store-and-forward mode if tx_threshold tops out? */
    if ((op_mode & TLP_OP_TX_THRESH) < TLP_OP_TX_THRESH)
      {
      op_mode += 0x4000;  /* increment TX_THRESH field; can not overflow */
      WRITE_CSR(sc, TLP_OP_MODE, op_mode & ~TLP_OP_TX_RUN);
      /* Wait for the TX FSM to stop; it might be processing a pkt. */
      while (READ_CSR(sc, TLP_STATUS) & TLP_STAT_TX_FSM); /* XXX HANG */
      WRITE_CSR(sc, TLP_OP_MODE, op_mode); /* restart tx */

      if (sc->config.debug)
        printf("%s: tx fifo underrun; threshold now %d bytes\n",
         NAME_UNIT, 128<<((op_mode>>TLP_OP_TR_SHIFT)&3));
      sc->status.cntrs.underruns++;
      }
    }

  /* 3) Errata memo from Digital Equipment Corp warns that 21140A
   *  receivers through rev 2.2 can hang if the fifo overruns.
   * Recommended fix: stop and start the RX FSM after an overrun.
   */
  missed = READ_CSR(sc, TLP_MISSED);
  if ((overruns = ((missed & TLP_MISS_OVERRUN)>>TLP_OVERRUN_SHIFT)))
    {
    if ((READ_PCI_CFG(sc, TLP_CFRV) & 0xFF) <= 0x22)
      {
      op_mode = READ_CSR(sc, TLP_OP_MODE);
      WRITE_CSR(sc, TLP_OP_MODE, op_mode & ~TLP_OP_RX_RUN);
      /* Wait for the RX FSM to stop; it might be processing a pkt. */
      while (READ_CSR(sc, TLP_STATUS) & TLP_STAT_RX_FSM); /* XXX HANG */
      WRITE_CSR(sc, TLP_OP_MODE, op_mode);  /* restart rx */
      }
    if (sc->config.debug)
      printf("%s: rx fifo overruns=%d\n", NAME_UNIT, overruns);
    sc->status.cntrs.overruns += overruns;
    }

  /* 4) When the receiver is enabled and a packet arrives, but no DMA
   *  descriptor is available, the packet is counted as 'missed'.
   * The receiver should never miss packets; warn if it happens.
   */
  if ((missed = (missed & TLP_MISS_MISSED)))
    {
    if (sc->config.debug)
      printf("%s: rx missed %d pkts\n", NAME_UNIT, missed);
    sc->status.cntrs.missed += missed;
    }
  }

/* Initialize the driver. */
/* context: kernel (boot) or process (syscall) */
static int
lmc_attach(softc_t *sc)
  {
  int error = 0;
  struct config config;

  /* Attach the Tulip PCI bus interface. */
  if ((error = tulip_attach(sc))) return error;

  /* Reset the Xilinx Field Programmable Gate Array. */
  xilinx_reset(sc); /* side effect: turns on all four LEDs */

  /* Attach card-specific stuff. */
  sc->card->attach(sc, NULL); /* changes sc->config */

  /* Reset the FIFOs between Gate array and Tulip chip. */
  mii16_set_bits(sc, MII16_FIFO);
  mii16_clr_bits(sc, MII16_FIFO);

#if IFNET
  /* Attach the ifnet kernel interface. */
  if ((error = ifnet_attach(sc))) return error;
#endif

#if NETDEV
  /* Attach the netdevice kernel interface. */
  if ((error = netdev_attach(sc))) return error;
#endif

  /* Attach a protocol stack and open a line protocol. */
  config = sc->config;
  config.stack = STACK_RAWIP;
  attach_stack(sc, &config);
  config.proto = PROTO_IP_HDLC;
  open_proto(sc, &config);

  /* Print obscure card information. */
  if (BOOT_VERBOSE)
    {
    u_int32_t cfrv = READ_PCI_CFG(sc, TLP_CFRV);
    u_int16_t mii3 = mii_read(sc, 3);
    u_int16_t srom[3];
    u_int8_t  *ieee = (u_int8_t *)srom;
    int i;

    printf("%s", NAME_UNIT);
    printf(": PCI rev %d.%d", (cfrv>>4) & 0xF, cfrv & 0xF);
    printf(", MII rev %d.%d", (mii3>>4) & 0xF, mii3 & 0xF);
    for (i=0; i<3; i++) srom[i] = srom_read(sc, 10+i);
    printf(", IEEE addr %02x:%02x:%02x:%02x:%02x:%02x",
     ieee[0], ieee[1], ieee[2], ieee[3], ieee[4], ieee[5]);
    sc->card->ident(sc);
    }

/* BSDs enable card interrupts and appear "ready" here. */
/* Linux does this in netdev_open(). */
#if BSD
  set_ready(sc, 1);
  WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_TXRX);
#endif

  return 0;
  }

/* context: kernel (boot) or process (syscall) */
static void
lmc_detach(softc_t *sc)
  {
  /* Disable card interrupts and appear "not ready". */
  set_ready(sc, 0);
  WRITE_CSR(sc, TLP_INT_ENBL, TLP_INT_DISABLE);

  /* Detach the line protocol package. */
  if (sc->stack)
    sc->stack->detach(sc);

#if IFNET
  /* Detach the ifnet kernel interface. */
  ifnet_detach(sc);
#endif

#if NETDEV
  /* Detach the netdevice kernel interface. */
  netdev_detach(sc);
#endif

  /* Detach framers, line interfaces, etc. on the card. */
  sc->card->detach(sc);

  /* Detach the Tulip PCI bus interface. */
  tulip_detach(sc);
  }

/* Loop back through the TULIP Ethernet chip; (no CRC).
 * Data sheet says stop DMA before changing OPMODE register.
 * But that's not as simple as it sounds; works anyway.
 */
static void
tulip_loop(softc_t *sc, struct config *config)
  {
  /* Check for enabling loopback thru Tulip chip. */
  if ((sc->config.loop_back != CFG_LOOP_TULIP) &&
         (config->loop_back == CFG_LOOP_TULIP))
    {
    u_int32_t op_mode = READ_CSR(sc, TLP_OP_MODE);
    op_mode |= TLP_OP_INT_LOOP;
    WRITE_CSR(sc, TLP_OP_MODE, op_mode);
    config->crc_len = CFG_CRC_0;
    }

  /* Check for disabling loopback thru Tulip chip. */
  if ((sc->config.loop_back == CFG_LOOP_TULIP) &&
         (config->loop_back != CFG_LOOP_TULIP))
    {
    u_int32_t op_mode = READ_CSR(sc, TLP_OP_MODE);
    op_mode &= ~TLP_OP_LOOP_MODE;
    WRITE_CSR(sc, TLP_OP_MODE, op_mode);
    config->crc_len = CFG_CRC_16;
    }

  sc->config.loop_back = config->loop_back;
  }

/* Attach the Tulip PCI bus interface.
 * Allocate DMA descriptors and enable DMA.
 * Returns 0 on success; error code on failure.
 * context: kernel (boot) or process (syscall)
 */
static int
tulip_attach(softc_t *sc)
  {
  int num_rx_descs, error = 0;
  u_int32_t bus_pbl, bus_cal, op_tr;
  u_int32_t cfdd, cfcs, cflt, csid, cfit;

  /* Make sure the COMMAND bits are reasonable. */
  cfcs = READ_PCI_CFG(sc, TLP_CFCS);
  cfcs &= ~TLP_CFCS_MWI_ENABLE;
  cfcs |=  TLP_CFCS_BUS_MASTER;
  cfcs |=  TLP_CFCS_MEM_ENABLE;
  cfcs |=  TLP_CFCS_IO_ENABLE;
  cfcs |=  TLP_CFCS_PAR_ERROR;
  cfcs |=  TLP_CFCS_SYS_ERROR;
  WRITE_PCI_CFG(sc, TLP_CFCS, cfcs);

  /* Set the LATENCY TIMER to the recommended value, */
  /*  and make sure the CACHE LINE SIZE is reasonable. */
  cfit = READ_PCI_CFG(sc, TLP_CFIT);
  cflt = READ_PCI_CFG(sc, TLP_CFLT);
  cflt &= ~TLP_CFLT_LATENCY;
  cflt |= (cfit & TLP_CFIT_MAX_LAT)>>16;
  /* "prgmbl burst length" and "cache alignment" used below. */
  switch(cflt & TLP_CFLT_CACHE)
    {
    case 8: /* 8 bytes per cache line */
      { bus_pbl = 32; bus_cal = 1; break; }
    case 16:
      { bus_pbl = 32; bus_cal = 2; break; }
    case 32:
      { bus_pbl = 32; bus_cal = 3; break; }
    default:
      {
      bus_pbl = 32; bus_cal = 1;
      cflt &= ~TLP_CFLT_CACHE;
      cflt |= 8;
      break;
      }
    }
  WRITE_PCI_CFG(sc, TLP_CFLT, cflt);

  /* Make sure SNOOZE and SLEEP modes are disabled. */
  cfdd = READ_PCI_CFG(sc, TLP_CFDD);
  cfdd &= ~TLP_CFDD_SLEEP;
  cfdd &= ~TLP_CFDD_SNOOZE;
  WRITE_PCI_CFG(sc, TLP_CFDD, cfdd);
  DELAY(11*1000); /* Tulip wakes up in 10 ms max */

  /* Software Reset the Tulip chip; stops DMA and Interrupts. */
  /* This does not change the PCI config regs just set above. */
  WRITE_CSR(sc, TLP_BUS_MODE, TLP_BUS_RESET); /* self-clearing */
  DELAY(5);  /* Tulip is dead for 50 PCI cycles after reset. */

  /* Initialize the PCI busmode register. */
  /* The PCI bus cycle type "Memory Write and Invalidate" does NOT */
  /*  work cleanly in any version of the 21140A, so do not enable it! */
  WRITE_CSR(sc, TLP_BUS_MODE,
        (bus_cal ? TLP_BUS_READ_LINE : 0) |
        (bus_cal ? TLP_BUS_READ_MULT : 0) |
        (bus_pbl<<TLP_BUS_PBL_SHIFT) |
        (bus_cal<<TLP_BUS_CAL_SHIFT) |
   ((BYTE_ORDER == BIG_ENDIAN) ? TLP_BUS_DESC_BIGEND : 0) |
   ((BYTE_ORDER == BIG_ENDIAN) ? TLP_BUS_DATA_BIGEND : 0) |
                TLP_BUS_DSL_VAL |
                TLP_BUS_ARB);

  /* Pick number of RX descriptors and TX fifo threshold. */
  /* tx_threshold in bytes: 0=128, 1=256, 2=512, 3=1024 */
  csid = READ_PCI_CFG(sc, TLP_CSID);
  switch(csid)
    {
    case CSID_LMC_HSSI:		/* 52 Mb/s */
    case CSID_LMC_HSSIc:	/* 52 Mb/s */
    case CSID_LMC_T3:		/* 45 Mb/s */
      { num_rx_descs = 48; op_tr = 2; break; }
    case CSID_LMC_SSI:		/* 10 Mb/s */
      { num_rx_descs = 32; op_tr = 1; break; }
    case CSID_LMC_T1E1:		/*  2 Mb/s */
      { num_rx_descs = 16; op_tr = 0; break; }
    default:
      { num_rx_descs = 16; op_tr = 0; break; }
    }

  /* Create DMA descriptors and initialize list head registers. */
  if ((error = create_ring(sc, &sc->txring, NUM_TX_DESCS))) return error;
  WRITE_CSR(sc, TLP_TX_LIST, sc->txring.dma_addr);
  if ((error = create_ring(sc, &sc->rxring, num_rx_descs))) return error;
  WRITE_CSR(sc, TLP_RX_LIST, sc->rxring.dma_addr);

  /* Initialize the operating mode register. */
  WRITE_CSR(sc, TLP_OP_MODE, TLP_OP_INIT | (op_tr<<TLP_OP_TR_SHIFT));

  /* Read the missed frame register (result ignored) to zero it. */
  error = READ_CSR(sc, TLP_MISSED); /* error is used as a bit-dump */

  /* Disable rx watchdog and tx jabber features. */
  WRITE_CSR(sc, TLP_WDOG, TLP_WDOG_INIT);

  return 0;
  }

/* Detach the Tulip PCI bus interface. */
/* Disable DMA and free DMA descriptors. */
/* context: kernel (boot) or process (syscall) */
static void
tulip_detach(void *arg)
  {
  softc_t *sc = arg;

  /* Software reset the Tulip chip; stops DMA and Interrupts. */
  WRITE_CSR(sc, TLP_BUS_MODE, TLP_BUS_RESET); /* self-clearing */
  DELAY(5);  /* Tulip is dead for 50 PCI cycles after reset. */

  /* Disconnect from the PCI bus except for config cycles. */
  /* Hmmm; Linux syslogs a warning that IO and MEM are disabled. */
  WRITE_PCI_CFG(sc, TLP_CFCS, TLP_CFCS_MEM_ENABLE | TLP_CFCS_IO_ENABLE);

  /* Free the DMA descriptor rings. */
  destroy_ring(sc, &sc->txring);
  destroy_ring(sc, &sc->rxring);
  }

/* Called during config probing -- softc does not yet exist. */
static void
print_driver_info(void)
  {
  /* Print driver information once only. */
  if (driver_announced++ == 0)
    {
    printf("LMC driver version %d/%d/%d; options",
     VER_YEAR, VER_MONTH, VER_DAY);
    if (ALTQ)           printf(" ALTQ");
    if (NBPFILTER)      printf(" BPF");
    if (NAPI)           printf(" NAPI");
    if (DEVICE_POLLING) printf(" POLL");
    if (P2P)            printf(" P2P");
    if (SPPP)           printf(" SPPP");
    if (GEN_HDLC)       printf(" GEN_HDLC");
    if (SYNC_PPP)       printf(" SYNC_PPP");
    if (NETGRAPH)       printf(" NETGRAPH");
    printf(".\n");
    }
  }

#if defined(__FreeBSD__)

/* This is the I/O configuration interface for FreeBSD */

/* Looking for a DEC 21140A chip on any Lan Media Corp card. */
/* context: kernel (boot) or process (syscall) */
static int
fbsd_probe(device_t dev)
  {
  u_int32_t cfid = pci_read_config(dev, TLP_CFID, 4);
  u_int32_t csid = pci_read_config(dev, TLP_CSID, 4);

  if (cfid != TLP_CFID_TULIP) return ENXIO;
  switch (csid)
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
      device_set_desc(dev, HSSI_DESC);
      break;
    case CSID_LMC_T3:
      device_set_desc(dev,   T3_DESC);
      break;
    case CSID_LMC_SSI:
      device_set_desc(dev,  SSI_DESC);
      break;
    case CSID_LMC_T1E1:
      device_set_desc(dev, T1E1_DESC);
      break;
    default:
      return ENXIO;
    }

  print_driver_info();
  return 0;
  }

/* FreeBSD bottom-half initialization. */
/* context: kernel (boot) or process (syscall) */
static int
fbsd_attach(device_t dev)
  {
  softc_t *sc = device_get_softc(dev);
  int error;

  /* for READ/WRITE_PCI_CFG() */
  sc->dev = dev;

  /* What kind of card are we driving? */
  switch (READ_PCI_CFG(sc, TLP_CSID))
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
      sc->dev_desc =  HSSI_DESC;
      sc->card     = &hssi_card;
      break;
    case CSID_LMC_T3:
      sc->dev_desc =    T3_DESC;
      sc->card     =   &t3_card;
      break;
    case CSID_LMC_SSI:
      sc->dev_desc =   SSI_DESC;
      sc->card     =  &ssi_card;
      break;
    case CSID_LMC_T1E1:
      sc->dev_desc =  T1E1_DESC;
      sc->card     =   &t1_card;
      break;
    }

  /* Allocate PCI memory or IO resources to access the Tulip chip CSRs. */
# if IOREF_CSR
  sc->csr_res_id   = TLP_CBIO;
  sc->csr_res_type = SYS_RES_IOPORT;
# else
  sc->csr_res_id   = TLP_CBMA;
  sc->csr_res_type = SYS_RES_MEMORY;
# endif
  sc->csr_res = bus_alloc_resource(dev, sc->csr_res_type, &sc->csr_res_id,
   0, ~0, 1, RF_ACTIVE);
  if (sc->csr_res == NULL)
    {
    printf("%s: bus_alloc_resource(csr) failed\n", NAME_UNIT);
    return ENXIO;
    }
  sc->csr_tag    = rman_get_bustag(sc->csr_res);
  sc->csr_handle = rman_get_bushandle(sc->csr_res); 

  /* Allocate PCI interrupt resources for the card. */
  sc->irq_res_id = 0;
  sc->irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irq_res_id,
   0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
  if (sc->irq_res == NULL)
    {
    printf("%s: bus_alloc_resource(irq) failed\n", NAME_UNIT);
    fbsd_detach(dev);
    return ENXIO;
    }
  if ((error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET | INTR_MPSAFE,
   bsd_interrupt, sc, &sc->irq_cookie)))
    {
    printf("%s: bus_setup_intr(): error %d\n", NAME_UNIT, error);
    fbsd_detach(dev);
    return error;
    }

  /* Initialize the top-half and bottom-half locks. */
  mtx_init(&sc->top_lock, NAME_UNIT, "top-half lock", MTX_DEF);
  sc->bottom_lock = 0;

  /* Initialize the driver. */
  if ((error = lmc_attach(sc))) fbsd_detach(dev);

  return error;
  }

/* context: kernel (boot) or process (syscall) */
static int
fbsd_detach(device_t dev)
  {
  softc_t *sc = device_get_softc(dev);

  /* Detach from the bus and the kernel. */
  lmc_detach(sc);

  /* Release resources. */
  if (sc->irq_cookie)
    bus_teardown_intr(dev, sc->irq_res, sc->irq_cookie);
  if (sc->irq_res)
    bus_release_resource(dev, SYS_RES_IRQ, sc->irq_res_id, sc->irq_res);
  if (sc->csr_res)
    bus_release_resource(dev, sc->csr_res_type, sc->csr_res_id, sc->csr_res);

  /* Destroy locks. */
  mtx_destroy(&sc->top_lock);

  return 0;
  }

static void
fbsd_shutdown(device_t dev)
  {
  tulip_detach(device_get_softc(dev));
  }

static void  /* Callout from bus_dmamap_load() */
fbsd_dmamap_load(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
  {
  struct desc_ring *ring = arg;
  ring->nsegs = error ? 0 : nsegs;
  ring->segs[0] = segs[0];
  ring->segs[1] = segs[1];
  }

static device_method_t methods[] =
  {
  DEVMETHOD(device_probe,    fbsd_probe),
  DEVMETHOD(device_attach,   fbsd_attach),
  DEVMETHOD(device_detach,   fbsd_detach),
  DEVMETHOD(device_shutdown, fbsd_shutdown),
  /* This driver does not suspend and resume. */
  { 0, 0 }
  };

static driver_t driver =
  {
  .name    = DEVICE_NAME,
  .methods = methods,
  .size    = sizeof(softc_t),
  };

static devclass_t devclass;

DRIVER_MODULE(if_lmc, pci, driver, devclass, 0, 0);
MODULE_VERSION(if_lmc, 2);
MODULE_DEPEND(if_lmc, pci, 1, 1, 1);
# if NETGRAPH
MODULE_DEPEND(if_lmc, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION);
# endif
# if SPPP
MODULE_DEPEND(if_lmc, sppp, 1, 1, 1);
# endif

#endif  /* __FreeBSD__ */

#if defined(__NetBSD__)

/* This is the I/O configuration interface for NetBSD. */

/* Looking for a DEC 21140A chip on any Lan Media Corp card. */
/* context: kernel (boot) or process (syscall) */
static int
nbsd_match(struct device *parent, struct cfdata *match,
    void *aux)
  {
  struct pci_attach_args *pa = aux;
  u_int32_t cfid = pci_conf_read(pa->pa_pc, pa->pa_tag, TLP_CFID);
  u_int32_t csid = pci_conf_read(pa->pa_pc, pa->pa_tag, TLP_CSID);	

  if (cfid != TLP_CFID_TULIP) return 0;
  switch (csid)
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
    case CSID_LMC_T3:
    case CSID_LMC_SSI:
    case CSID_LMC_T1E1:
      print_driver_info();
      return 100;
    default:
      return 0;
    }
  }

/* NetBSD bottom-half initialization. */
/* context: kernel (boot) or process (syscall) */
static void
nbsd_attach(struct device *parent, struct device *self, void *aux)
  {
  softc_t *sc = (softc_t *)self; /* device is first in softc */
  struct pci_attach_args *pa = aux;
  const char *intrstr;
  bus_addr_t csr_addr;
  int error;

  /* for READ/WRITE_PCI_CFG() */
  sc->pa_pc   = pa->pa_pc;
  sc->pa_tag  = pa->pa_tag;
  sc->pa_dmat = pa->pa_dmat;

  /* What kind of card are we driving? */
  switch (READ_PCI_CFG(sc, TLP_CSID))
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
      sc->dev_desc =  HSSI_DESC;
      sc->card     = &hssi_card;
      break;
    case CSID_LMC_T3:
      sc->dev_desc =    T3_DESC;
      sc->card     =   &t3_card;
      break;
    case CSID_LMC_SSI:
      sc->dev_desc =   SSI_DESC;
      sc->card     =  &ssi_card;
      break;
    case CSID_LMC_T1E1:
      sc->dev_desc =  T1E1_DESC;
      sc->card     =   &t1_card;
      break;
    default:
      return;
    }

  /* Allocate PCI resources to access the Tulip chip CSRs. */
# if IOREF_CSR
  csr_addr = (bus_addr_t)READ_PCI_CFG(sc, TLP_CBIO) & -2;
  sc->csr_tag = pa->pa_iot;	/* bus_space tag for IO refs */
# else
  csr_addr = (bus_addr_t)READ_PCI_CFG(sc, TLP_CBMA);
  sc->csr_tag = pa->pa_memt;	/* bus_space tag for MEM refs */
# endif
  if ((error = bus_space_map(sc->csr_tag, csr_addr,
   TLP_CSR_SIZE, 0, &sc->csr_handle)))
    {
    aprint_error("%s: bus_space_map(): error %d\n", NAME_UNIT, error);
    return;
    }

  /* Allocate PCI interrupt resources. */
  if (pci_intr_map(pa, &sc->intr_handle))
    {
    aprint_error("%s: pci_intr_map() failed\n", NAME_UNIT);
    nbsd_detach(self, 0);
    return;
    }
  if ((sc->irq_cookie = pci_intr_establish(pa->pa_pc, sc->intr_handle,
   IPL_NET, bsd_interrupt, sc)) == NULL)
    {
    aprint_error("%s: pci_intr_establish() failed\n", NAME_UNIT);
    nbsd_detach(self, 0);
    return;
    }
  intrstr = pci_intr_string(pa->pa_pc, sc->intr_handle);
  aprint_normal(" %s: %s\n", intrstr, sc->dev_desc);
  aprint_naive(": %s\n", sc->dev_desc);

  /* Install a shutdown hook. */
  if ((sc->sdh_cookie = shutdownhook_establish(tulip_detach, sc)) == NULL)
    {
    aprint_error("%s: shutdown_hook_establish() failed\n", NAME_UNIT);
    nbsd_detach(self, 0);
    return;
    }

  /* Initialize the top-half and bottom-half locks. */
  mutex_init(&sc->top_lock, MUTEX_DEFAULT, IPL_VM);
  __cpu_simple_lock_init(&sc->bottom_lock);

  /* Initialize the driver. */
  if ((error = lmc_attach(sc))) nbsd_detach(self, 0);
  }

/* context: kernel (boot) or process (syscall) */
static int
nbsd_detach(struct device *self, int flags)
  {
  softc_t *sc = (softc_t *)self; /* device is first in softc */

  /* Detach from the bus and the kernel. */
  lmc_detach(sc);

  /* Release resources. */
  if (sc->sdh_cookie)
    shutdownhook_disestablish(sc->sdh_cookie);
  if (sc->irq_cookie)
    pci_intr_disestablish(sc->pa_pc, sc->irq_cookie);
  if (sc->csr_handle)
    bus_space_unmap(sc->csr_tag, sc->csr_handle, TLP_CSR_SIZE);

  /* Destroy locks. */
  mutex_destroy(&sc->top_lock);

  return 0;
  }

CFATTACH_DECL(lmc, sizeof(softc_t),		/* lmc_ca */
 nbsd_match, nbsd_attach, nbsd_detach, NULL);

# if defined(LKM)

static struct cfattach *cfattach[] = { &lmc_ca, NULL };
static const struct cfattachlkminit cfattachs[] =
  { { DEVICE_NAME, cfattach }, { NULL, NULL } };

static CFDRIVER_DECL(lmc, DV_IFNET, NULL);	/* lmc_cd */
static struct cfdriver *cfdrivers[] = { &lmc_cd, NULL };

static int pci_locators[] = { -1, 0 }; /* device, function */
static const struct cfparent pci_parent = { "pci", "pci", DVUNIT_ANY };
static struct cfdata cfdatas[] =
  { { DEVICE_NAME, DEVICE_NAME, 0, FSTATE_STAR,
      pci_locators, 0, &pci_parent },
    { NULL, NULL, 0, 0, NULL, 0, NULL } };

MOD_DRV("if_"DEVICE_NAME, cfdrivers, cfattachs, cfdatas);

int if_lmc_lkmentry(struct lkm_table *lkmtp, int cmd, int ver)
  { LKM_DISPATCH(lkmtp, cmd, ver, lkm_nofunc, lkm_nofunc, lkm_nofunc); }

# endif /* LKM */

#endif  /* __NetBSD__ */

#if defined(__OpenBSD__)

/* This is the I/O configuration interface for OpenBSD. */

/* Looking for a DEC 21140A chip on any Lan Media Corp card. */
/* context: kernel (boot) or process (syscall) */
static int
obsd_match(struct device *parent, void *match, void *aux)
  {
  struct pci_attach_args *pa = aux;
  u_int32_t cfid = pci_conf_read(pa->pa_pc, pa->pa_tag, TLP_CFID);
  u_int32_t csid = pci_conf_read(pa->pa_pc, pa->pa_tag, TLP_CSID);	

  if (cfid != TLP_CFID_TULIP) return 0;
  switch (csid)
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
    case CSID_LMC_T3:
    case CSID_LMC_SSI:
    case CSID_LMC_T1E1:
      print_driver_info();
      return 100; /* match better than other 21140 drivers */
    default:
      return 0;
    }
  }

/* OpenBSD bottom-half initialization. */
/* context: kernel (boot) or process (syscall) */
static void
obsd_attach(struct device *parent, struct device *self, void *aux)
  {
  softc_t *sc = (softc_t *)self; /* device is first in softc */
  struct pci_attach_args *pa = aux;
  const char *intrstr;
  bus_addr_t csr_addr;
  int error;

  /* for READ/WRITE_PCI_CFG() */
  sc->pa_pc   = pa->pa_pc;
  sc->pa_tag  = pa->pa_tag;
  sc->pa_dmat = pa->pa_dmat;

  /* What kind of card are we driving? */
  switch (READ_PCI_CFG(sc, TLP_CSID))
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
      sc->dev_desc =  HSSI_DESC;
      sc->card     = &hssi_card;
      break;
    case CSID_LMC_T3:
      sc->dev_desc =    T3_DESC;
      sc->card     =   &t3_card;
      break;
    case CSID_LMC_SSI:
      sc->dev_desc =   SSI_DESC;
      sc->card     =  &ssi_card;
      break;
    case CSID_LMC_T1E1:
      sc->dev_desc =  T1E1_DESC;
      sc->card     =   &t1_card;
      break;
    default:
      return;
    }

  /* Allocate PCI resources to access the Tulip chip CSRs. */
# if IOREF_CSR
  csr_addr = (bus_addr_t)READ_PCI_CFG(sc, TLP_CBIO) & -2;
  sc->csr_tag = pa->pa_iot;	/* bus_space tag for IO refs */
# else
  csr_addr = (bus_addr_t)READ_PCI_CFG(sc, TLP_CBMA);
  sc->csr_tag = pa->pa_memt;	/* bus_space tag for MEM refs */
# endif
  if ((error = bus_space_map(sc->csr_tag, csr_addr,
   TLP_CSR_SIZE, 0, &sc->csr_handle)))
    {
    printf("%s: bus_space_map(): error %d\n", NAME_UNIT, error);
    return;
    }

  /* Allocate PCI interrupt resources. */
  if (pci_intr_map(pa, &sc->intr_handle))
    {
    printf("%s: pci_intr_map() failed\n", NAME_UNIT);
    obsd_detach(self, 0);
    return;
    }
  if ((sc->irq_cookie = pci_intr_establish(pa->pa_pc, sc->intr_handle,
   IPL_NET, bsd_interrupt, sc, device_xname(self))) == NULL)
    {
    printf("%s: pci_intr_establish() failed\n", NAME_UNIT);
    obsd_detach(self, 0);
    return;
    }
  intrstr = pci_intr_string(pa->pa_pc, sc->intr_handle);
  printf(" %s: %s\n", intrstr, sc->dev_desc);

  /* Install a shutdown hook. */
  if ((sc->sdh_cookie = shutdownhook_establish(tulip_detach, sc)) == NULL)
    {
    printf("%s: shutdown_hook_establish() failed\n", NAME_UNIT);
    obsd_detach(self, 0);
    return;
    }

  /* Initialize the top-half and bottom-half locks. */
  __cpu_simple_lock_init(&sc->top_lock);
  __cpu_simple_lock_init(&sc->bottom_lock);

  /* Initialize the driver. */
  if ((error = lmc_attach(sc))) obsd_detach(self, 0);
  }

/* context: kernel (boot) or process (syscall) */
static int
obsd_detach(struct device *self, int flags)
  {
  softc_t *sc = (softc_t *)self; /* device is first in softc */

  /* Detach from the bus and the kernel. */
  lmc_detach(sc);

  /* Release resources. */
  if (sc->sdh_cookie)
    shutdownhook_disestablish(sc->sdh_cookie);
  if (sc->irq_cookie)
    pci_intr_disestablish(sc->pa_pc, sc->irq_cookie);
  if (sc->csr_handle)
    bus_space_unmap(sc->csr_tag, sc->csr_handle, TLP_CSR_SIZE);

  return 0;
  }

struct cfattach lmc_ca =
  {
  .ca_devsize	= sizeof(softc_t),
  .ca_match	= obsd_match,
  .ca_attach	= obsd_attach,
  .ca_detach	= obsd_detach,
  .ca_activate	= NULL,
  };

# if defined(LKM)

struct cfdriver lmc_cd =
  {
  .cd_name	= DEVICE_NAME,
  .cd_devs	= NULL,
  .cd_class	= DV_IFNET,
  .cd_indirect	= 0,
  .cd_ndevs	= 0,
  };

/* cfdata is declared static, unseen outside this module. */
/* It is used for LKM; config builds its own in ioconf.c. */
static struct cfdata lmc_cfdata =
  {
  .cf_attach	= &lmc_ca,
  .cf_driver	= &lmc_cd,
  .cf_unit	= 0,
  .cf_fstate	= FSTATE_STAR,
  };

static struct lkm_any _module =
  {
  .lkm_name	= "if_"DEVICE_NAME,
  .lkm_type	= LM_MISC,
  .lkm_offset	= 0,
  .lkm_ver	= LKM_VERSION,
  };

/* From /sys/dev/pci/pci.c (no public prototype). */
int pciprint(void *, const char *);

/* struct cfdriver for pci bus; lmc cards are all pci devices. */
extern struct cfdriver pci_cd;

/* XXX This has only been tested on the i386 architecture.  */
/* XXX To do this right, need struct pcibus_attach_args.    */
/* XXX struct pcibus_attach_args exists only while booting. */
/* LKM finds this by appending "_lkmentry" to filename "if_lmc"(.o). */
int if_lmc_lkmentry(struct lkm_table *lkmtp, int cmd, int ver)
  {
  int i, error = 0;

  if (ver != LKM_VERSION) return EINVAL;
  switch (cmd)
    {
    case LKM_E_LOAD:
      {
      lkmtp->private.lkm_any = &_module;
      for (i=0; i<pci_cd.cd_ndevs; i++)
        {  /* for each pci bus... */
        int devnum, maxdevs;
        struct pci_attach_args pa;
        device_t parent = device_lookup(&pci_cd, i);
        /* This is ugly: only way to get pci_chipset_tag. */
        struct pci_sc { struct device dev; pci_chipset_tag_t pc; };
        struct pci_sc *pci_sc = device_lookup_private(&pci_cd, i);

        if (parent == NULL) continue; /* no pci bus */
        pa.pa_pc   = pci_sc->pc;
        pa.pa_iot  = 0;	/* XXX arch_BUS_SPACE_IO  */
        pa.pa_memt = 1;	/* XXX arch_BUS_SPACE_MEM; */
        pa.pa_dmat = &pci_bus_dma_tag;
        pa.pa_bus  = parent->dv_unit;
        pa.pa_function = 0; /* DEC-21140A has function 0 only */
        maxdevs = pci_bus_maxdevs(pa.pa_pc, pa.pa_bus);
        for (devnum=0; devnum<maxdevs; devnum++)
          { /* for each pci device... */
          pcireg_t intr;
          pa.pa_device = devnum;
          pa.pa_tag   = pci_make_tag (pa.pa_pc, pa.pa_bus, pa.pa_device, 0);
          pa.pa_id    = pci_conf_read(pa.pa_pc, pa.pa_tag, PCI_ID_REG);
          if (PCI_VENDOR(pa.pa_id) == 0xFFFF) continue;
          if (PCI_VENDOR(pa.pa_id) == 0) continue;
          pa.pa_class = pci_conf_read(pa.pa_pc, pa.pa_tag, PCI_CLASS_REG);
          /* XXX i386 only; need struct pcibus_attach_args to swizzle */
          pa.pa_intrswiz = 0;
          pa.pa_intrtag = pa.pa_tag;
          intr = pci_conf_read(pa.pa_pc, pa.pa_tag, PCI_INTERRUPT_REG);
          pa.pa_intrpin = ((PCI_INTERRUPT_PIN(intr) -1) % 4) +1;
          pa.pa_intrline =  PCI_INTERRUPT_LINE(intr);
          if (obsd_match(parent, &lmc_cfdata, &pa))
            config_attach(parent, &lmc_cfdata, &pa, pciprint);
          /* config_attach does not return on failure; it calls panic. */
          }
	}
      break;
      }
    case LKM_E_UNLOAD:
      {
      for (i=lmc_cd.cd_ndevs-1; i>=0; i--)
        {
        device_t dev = device_lookup(&lmc_cd, i);
        if (dev == NULL) continue;
        if ((error = config_detach(dev, 0)))
          printf("%s: config_detach(): error %d\n",
           device_xname(dev), error);
        }
      break;
      }
    case LKM_E_STAT:
      break;
    }

  return error;
  }

# endif /* LKM */

#endif  /* __OpenBSD__ */

#if defined(__bsdi__)

/* This is the I/O configuration interface for BSD/OS. */

/* Looking for a DEC 21140A chip on any Lan Media Corp card. */
/* context: kernel (boot) or process (syscall) */
static int
bsdi_match(pci_devaddr_t *pa)
  {
  u_int32_t cfid = pci_inl(pa, TLP_CFID);
  u_int32_t csid = pci_inl(pa, TLP_CSID);

  if (cfid != TLP_CFID_TULIP) return 0;
  switch (csid)
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
    case CSID_LMC_T3:
    case CSID_LMC_SSI:
    case CSID_LMC_T1E1:
      return 1;
    default:
      return 0;
    }
  }

static int  /* context: kernel (boot) */
bsdi_probe(struct device *parent, struct cfdata *cf, void *aux)
  {
  struct isa_attach_args *ia = aux;
  pci_devaddr_t *pa = NULL;
  pci_devres_t res;

  /* This must be a PCI bus. */
  if (ia->ia_bustype != BUS_PCI) return 0;

  /* Scan PCI bus for our cards. */
  if ((pa = pci_scan(bsdi_match)) == NULL) return 0;
  print_driver_info(); /* found a card */

  /* Scan config space for IO and MEM base registers and IRQ info. */
  pci_getres(pa, &res, 1, ia);

  /* Pass matching PCI addr (bus+device+function) in ia_aux. */
  ia->ia_aux = (void *)pa;

  return 1;
  }

/* BSD/OS bottom-half initialization. */
static void  /* context: kernel (boot) */
bsdi_attach(struct device *parent, struct device *self, void *aux)
  {
  softc_t *sc = (softc_t *)self; /* device is first in softc */
  struct isa_attach_args *ia = aux;
  pci_devaddr_t *pa = ia->ia_aux;
  int error;

  /* for READ/WRITE_PCI_CFG() */
  sc->cfgbase = *pa;  /* bus+device+function */

  /* What kind of card are we driving? */
  switch (READ_PCI_CFG(sc, TLP_CSID))
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
      sc->dev_desc =  HSSI_DESC;
      sc->card     = &hssi_card;
      break;
    case CSID_LMC_T3:
      sc->dev_desc =    T3_DESC;
      sc->card     =   &t3_card;
      break;
    case CSID_LMC_SSI:
      sc->dev_desc =   SSI_DESC;
      sc->card     =  &ssi_card;
      break;
    case CSID_LMC_T1E1:
      sc->dev_desc =  T1E1_DESC;
      sc->card     =   &t1_card;
      break;
    default:
      return;
    }
  aprint_naive(": %s\n", sc->dev_desc);
  aprint_normal(": %s\n", sc->dev_desc);

  /* Allocate PCI memory or IO resources to access the Tulip chip CSRs. */
  sc->csr_iobase  = ia->ia_iobase;
  sc->csr_membase = (u_int32_t *)mapphys((vm_offset_t)ia->ia_maddr, TLP_CSR_SIZE);

  /* Attach to the PCI bus. */
  isa_establish(&sc->id, &sc->dev);

  /* Allocate PCI interrupt resources for the card. */
  sc->ih.ih_fun = bsd_interrupt;
  sc->ih.ih_arg = sc;
  intr_establish(ia->ia_irq, &sc->ih, DV_NET);

  /* Install a shutdown hook. */
  sc->ats.func = tulip_detach;
  sc->ats.arg = sc;
  atshutdown(&sc->ats, ATSH_ADD);

  /* Initialize the top-half and bottom-half locks. */
  simple_lock_init(&sc->top_lock);
  simple_lock_init(&sc->bottom_lock);

  /* Initialize the driver. */
  if ((error = lmc_attach(sc))) lmc_detach(sc);
  }

struct cfdriver lmccd =
  {
  .cd_devs	= NULL,
  .cd_name	= DEVICE_NAME,
  .cd_match	= bsdi_probe,
  .cd_attach	= bsdi_attach,
  .cd_class	= DV_IFNET,
  .cd_devsize	= sizeof(softc_t),
  };
#endif  /* __bsdi__ */

#if defined(__linux__)

/* This is the I/O configuration interface for Linux. */

/* This pci_driver method is called during boot or module-load. */
/* Looking for a DEC 21140A chip on any Lan Media Corp card. */
/* context: kernel (boot) or process (syscall) */
static int __init
linux_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
  {
  u_int32_t cfid;
  u_int32_t csid;

  pci_read_config_dword(pcidev, TLP_CFID, &cfid);
  pci_read_config_dword(pcidev, TLP_CSID, &csid);

  if (cfid != TLP_CFID_TULIP) return -ENXIO;
  switch (csid)
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
    case CSID_LMC_T3:
    case CSID_LMC_SSI:
    case CSID_LMC_T1E1:
      print_driver_info();
      break;
    default:
      return -ENXIO;
    }

  /* Factor this task the way BSDs do. */
  return linux_attach(pcidev);
  }

/* Linux bottom-half initialization. */
/* context: kernel (boot) or process (syscall) */
static int __init
linux_attach(struct pci_dev *pcidev)
  {
  softc_t *sc;
  int error;

  /* Allocate the software context, softc, also known as sc. */
  if ((sc = kzalloc(sizeof(softc_t), GFP_KERNEL)) == NULL)
    {
    printk("%s: kzalloc() failed\n", DEVICE_NAME);
    return -ENOMEM;
    }
  /* for linux_remove() */
  pci_set_drvdata(pcidev, sc);

  /* for READ/WRITE_PCI_CFG() */
  sc->pcidev = pcidev;

  /* What kind of card are we driving? */
  switch (READ_PCI_CFG(sc, TLP_CSID))
    {
    case CSID_LMC_HSSI:
    case CSID_LMC_HSSIc:
      sc->dev_desc =  HSSI_DESC;
      sc->card     = &hssi_card;
      break;
    case CSID_LMC_T3:
      sc->dev_desc =    T3_DESC;
      sc->card     =   &t3_card;
      break;
    case CSID_LMC_SSI:
      sc->dev_desc =   SSI_DESC;
      sc->card     =  &ssi_card;
      break;
    case CSID_LMC_T1E1:
      sc->dev_desc =  T1E1_DESC;
      sc->card     =   &t1_card;
      break;
    default: /* should not happen! */
      kfree(sc);
      return -ENXIO;
    }

  /* Declare that these cards use 32-bit single-address PCI cycles. */
  if ((error = pci_set_dma_mask(pcidev, DMA_32BIT_MASK)))
    {
    printk("%s: pci_set_dma_mask(): error %d\n", DEVICE_NAME, error);
    linux_remove(pcidev);
    return error;
    }
  pci_set_consistent_dma_mask(pcidev, DMA_32BIT_MASK); /* can not fail */

  /* Allocate PCI resources to access the Tulip chip CSRs. */
  /* Since PCI resources will not change, request them now. */
  if ((error = pci_request_regions(pcidev, DEVICE_NAME)))
    {
    printk("%s: pci_request_regions(): error %d\n", DEVICE_NAME, error);
    linux_remove(pcidev);
    return error;
    }
  if ((sc->csr_cookie = pci_iomap(pcidev, (IOREF_CSR ? 0:1),
   TLP_CSR_SIZE)) == NULL)
    {
    printk("%s: pci_iomap() failed\n", DEVICE_NAME);
    linux_remove(pcidev);
    return -EFAULT;
    }

  /* Set cfcs.master, enabling busmaster DMA; check bus latency value. */
  pci_set_master(pcidev);
  /* Set cfcs.io and cfcs.mem; set pcidev->irq based on cfit.int. */
  if ((error = pci_enable_device(pcidev)))
    {
    printk("%s: pci_enable_device(): error %d\n", DEVICE_NAME, error);
    linux_remove(pcidev);
    return error;
    }

  /* Allocate PCI interrupt resources for the card. */
  /* Since the interrupt is shared, request it now. */
  if ((error = request_irq(pcidev->irq, &linux_interrupt, SA_SHIRQ,
   DEVICE_NAME, sc)))
    {
    printk("%s: request_irq(): error %d\n", DEVICE_NAME, error);
    linux_remove(pcidev);
    return error;
    }

  /* Initialize the top-half and bottom-half locks. */
  /* Top_lock must be initialized before lmc_attach() is called. */
  init_MUTEX(&sc->top_lock);
  sc->bottom_lock = 0;

  /* Install module parameter. */
  sc->config.debug = debug;

  /* Initialize the driver. */
  /* The NAME_UNIT macro works after lmc_attach. */
  /* Use DEVICE_NAME before this. */
  if ((error = lmc_attach(sc)))
    linux_remove(pcidev);
  else
    {
    /* Describe the hardware. */
    printk("%s: <%s> at pci %s irq %d\n", NAME_UNIT,
     sc->dev_desc, pci_name(pcidev), pcidev->irq);
    if (BOOT_VERBOSE)
      printk("%s: io 0x%04lx/9 mem 0x%08lx/25 rom 0x%08lx/14\n", NAME_UNIT,
       pci_resource_start(pcidev, 0), pci_resource_start(pcidev, 1),
       pci_resource_start(pcidev, 6));
    }

  return error;
  }

/* This pci_driver method is called during shutdown or module-unload. */
/* context: kernel (boot) or process (syscall) */
static void __exit  /* This is detach() in BSD parlance. */
linux_remove(struct pci_dev *pcidev)
  {
  softc_t *sc = pci_get_drvdata(pcidev);

  /* Detach from the bus and the kernel. */
  lmc_detach(sc);

  /* Release resources. */
  free_irq(pcidev->irq, sc);
  if (sc->csr_cookie)
    pci_iounmap(pcidev, sc->csr_cookie);
  if (pci_resource_start(pcidev, 0))
    pci_release_regions(pcidev);
  pci_disable_device(pcidev);

  kfree(sc);
  }

static u_int32_t
READ_PCI_CFG(softc_t *sc, u_int32_t addr)
  {
  u_int32_t data;
  pci_read_config_dword(sc->pcidev, addr, &data);
  return data;
  }

/* This pci driver knows how to drive these devices: */
static __initdata struct pci_device_id pci_device_id_tbl[] =
  {
  /* Looking for a DEC 21140A chip on any Lan Media Corp card. */
    { 0x1011, 0x0009, 0x1376, PCI_ANY_ID, 0, 0, 0 },
    { 0, }
  };
MODULE_DEVICE_TABLE(pci, pci_device_id_tbl);

static struct pci_driver pci_driver =
  {
  .name	    = DEVICE_NAME,
  .id_table = pci_device_id_tbl,
  .probe    = linux_probe,
  .remove   = __devexit_p(linux_remove),
  /* This driver does not suspend and resume. */
  };

/* This ultimately calls our pci_driver.probe() method. */
static int  __init linux_modload(void)
  { return pci_module_init(&pci_driver); }
module_init(linux_modload);

/* This ultimately calls our pci_driver.remove() method. */
static void __exit linux_modunload(void)
  { pci_unregister_driver(&pci_driver); }
module_exit(linux_modunload);

module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Extra messages during operation");
module_param(verbose, int, 0);
MODULE_PARM_DESC(verbose, "Extra messages during startup");

MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Device driver for LMC Wide-Area Network cards");
MODULE_AUTHOR("David Boggs <boggs@boggs.palo-alto.ca.us>");

#endif /* __linux__ */