Lucent/Agere WinModem driver

Vladimir Novoseltsev blacknova at tut.by
Sun Dec 19 08:31:51 PST 2004


Any one interesting can try it out.
Just replace ltmdmsio from the FreeBSD port (comms/ltmdm) with attached
one and build and install kernel module.

WBR,
-- 
Vladimir Novoseltsev <blacknova at xxxxxx>
/*-
 * Copyright (c) 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *  This product includes software developed by the University of
 *  California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 * $FreeBSD$
 *  from: @(#)com.c 7.5 (Berkeley) 5/16/91
 *  from: i386/isa sio.c,v 1.234
 *  from: src/sys/isa/sio.c,v 1.291.2.15 2001/02/26
 */

/*
 *  FreeBSD 4.x/5.0C Lucent Winmodem Driver experimental version.
 *         ( serial driver + wrapper for ltmdmobj.o ver 5.78/5.95/5.99/6.00 )
 * 
 *      rev 0.7  2002.03.10  WATANABE Kiyoshi
 */


/* ltmdmobj.o version: 578/595/599/600 */
#ifndef LTMDMOBJ_VERSION
#define LTMDMOBJ_VERSION 600
#endif


#define COMPAT_43 1

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/dkstat.h>
#include <sys/fcntl.h>
#include <sys/interrupt.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <sys/callout.h>
#ifdef ENABLE_PPS
#include <sys/timepps.h>
#endif

#include <bus/pci/pcireg.h>
#include <bus/pci/pcivar.h>

#include <machine/clock.h>

#include <machine/lock.h>
#include <machine/ipl.h>

#include <machine/resource.h>

#include <dev/serial/sio/sioreg.h>
#include <bus/isa/isareg.h>

#ifndef COMBRD
#define COMBRD(x) (1843200 / (16*(x)))
#endif

#define INTR_TYPE_FAST      INTR_FAST
#define lt_disable_intr()   com_lock()
#define lt_enable_intr()    com_unlock()
#define COM_LOCK()	com_lock()
#define COM_UNLOCK()	com_unlock()
#define UNIT_TO_MINOR(unit)     ((((unit) & ~0x1fU) << (8 + 3)) \
                                 | ((unit) & 0x1f))
#define DEV_TO_UNIT(dev)        (MINOR_TO_UNIT(minor(dev)))
#define MINOR_TO_UNIT(mynor)    ((((mynor) & ~0xffffU) >> (8 + 3)) \
                                 | ((mynor) & 0x1f))

#define LOTS_OF_EVENTS  64  /* helps separate urgent events from input */

#define CALLOUT_MASK        0x80
#define CONTROL_MASK        0x60
#define CONTROL_INIT_STATE  0x20
#define CONTROL_LOCK_STATE  0x40
/*
#define DEV_TO_UNIT(dev)    (MINOR_TO_UNIT(minor(dev)))
#define MINOR_MAGIC_MASK    (CALLOUT_MASK | CONTROL_MASK)
#define MINOR_TO_UNIT(mynor)    ((mynor) & ~MINOR_MAGIC_MASK)
*/
#define COM_NOFIFO(flags)   ((flags) & 0x02)
#define COM_FIFOSIZE(flags) (((flags) & 0xff000000) >> 24)

/* UART (16550A) register */

#define UART_DATA   0   /* data register            (R/W) */
#define UART_DLBL   0   /* divisor latch low          (W) */
#define UART_DLBH   1   /* divisor latch high         (W) */
#define UART_IER    1   /* interrupt enable           (W) */
#define UART_IIR    2   /* interrupt identification (R)   */
#define UART_FIFO   2   /* FIFO control               (W) */
#define UART_LCTL   3   /* line control register    (R/W) */
#define UART_CFCR   3   /* line control register    (R/W) */
#define UART_MCR    4   /* modem control register   (R/W) */
#define UART_LSR    5   /* line status register     (R/W) */
#define UART_MSR    6   /* modem status register    (R/W) */

/*
 * com state bits.
 * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher
 * than the other bits so that they can be tested as a group without masking
 * off the low bits.
 *
 * The following com and tty flags correspond closely:
 *  CS_BUSY         = TS_BUSY (maintained by comstart(), siopoll() and
 *                             comstop())
 *  CS_TTGO         = ~TS_TTSTOP (maintained by comparam() and comstart())
 *  CS_CTS_OFLOW    = CCTS_OFLOW (maintained by comparam())
 *  CS_RTS_IFLOW    = CRTS_IFLOW (maintained by comparam())
 * TS_FLUSH is not used.
 * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON.
 * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state).
 */
#define CS_BUSY         0x80    /* output in progress */
#define CS_TTGO         0x40    /* output not stopped by XOFF */
#define CS_ODEVREADY    0x20    /* external device h/w ready (CTS) */
#define CS_CHECKMSR     1       /* check of MSR scheduled */
#define CS_CTS_OFLOW    2       /* use CTS output flow control */
#define CS_DTR_OFF      0x10    /* DTR held off */
#define CS_ODONE        4       /* output completed */
#define CS_RTS_IFLOW    8       /* use RTS input flow control */
#define CSE_BUSYCHECK   1       /* siobusycheck() scheduled */

static  char const * const  error_desc[] = {
#define CE_OVERRUN                  0
    "silo overflow",
#define CE_INTERRUPT_BUF_OVERFLOW   1
    "interrupt-level buffer overflow",
#define CE_TTY_BUF_OVERFLOW         2
    "tty-level buffer overflow",
};

#define CE_NTYPES               3
#define CE_RECORD(com, errnum)  (++(com)->delta_error_counts[errnum])

/* types.  XXX - should be elsewhere */
typedef u_char  bool_t;     /* boolean */

/* queue of linear buffers */
struct lbq {
    u_char  *l_head;    /* next char to process */
    u_char  *l_tail;    /* one past the last char to process */
    struct lbq *l_next; /* next in queue */
    bool_t  l_queued;   /* nonzero if queued */
};

/* com device structure */
struct com_s {
    u_int   flags;          /* Copy isa device flags */
    u_char  state;          /* miscellaneous flag bits */
    bool_t  active_out;     /* nonzero if the callout device is open */
    u_char  cfcr_image;     /* copy of value written to CFCR */
    u_char  extra_state;    /* more flag bits, separate for order trick */
    u_char  fifo_image;     /* copy of value written to FIFO */
    bool_t  hasfifo;        /* nonzero for 16550 UARTs */
    u_char  mcr_image;      /* copy of value written to MCR */
    bool_t  gone;           /* hardware disappeared */
    int unit;               /* unit number */
    int dtr_wait;           /* time to hold DTR down on close (* 1/hz) */
    u_int   tx_fifo_size;
    u_int   wopeners;       /* # processes waiting for DCD in open() */

    /*
     * The high level of the driver never reads status registers directly
     * because there would be too many side effects to handle conveniently.
     * Instead, it reads copies of the registers stored here by the
     * interrupt handler.
     */
    u_char  last_modem_status;  /* last MSR read by intr handler */
    u_char  prev_modem_status;  /* last MSR handled by high level */

#if __FreeBSD_version < 502119
    u_char  hotchar;        /* ldisc-specific char to be handled ASAP */
#endif
    u_char  *ibuf;          /* start of input buffer */
    u_char  *ibufend;       /* end of input buffer */
    u_char  *ibufold;       /* old input buffer, to be freed */
    u_char  *ihighwater;    /* threshold in input buffer */
    u_char  *iptr;          /* next free spot in input buffer */
    int ibufsize;           /* size of ibuf (not include error bytes) */
    int ierroff;            /* offset of error bytes in ibuf */

    struct lbq  obufq;      /* head of queue of output buffers */
    struct lbq  obufs[2];   /* output buffers */

    struct tty  *tp;        /* cross reference */

    /* Initial state. */
    struct termios  it_in;  /* should be in struct tty */
    struct termios  it_out;

    /* Lock state. */
    struct termios  lt_in;  /* should be in struct tty */
    struct termios  lt_out;

    bool_t  do_timestamp;
    bool_t  do_dcd_timestamp;
    struct timeval  timestamp;
    struct timeval  dcd_timestamp;
#ifdef ENABLE_PPS
    struct  pps_state pps;
#endif

    u_long  bytes_in;       /* statistics */
    u_long  bytes_out;
    u_int   delta_error_counts[CE_NTYPES];
    u_long  error_counts[CE_NTYPES];

    int     iorid[6];
    int     irqrid;
    struct resource *iores[6];
    struct resource *irqres;
    void *cookie;
#if __FreeBSD_version > 502115
    struct cdev *devs[6];
#else
    dev_t devs[6];
#endif

    /*
     * Data area for output buffers.  Someday we should build the output
     * buffer queue without copying data.
     */
    u_char  obuf1[256];
    u_char  obuf2[256];
};


static  timeout_t siobusycheck;
static  timeout_t siodtrwakeup;
static  void    comhardclose    __P((struct com_s *com));
static  void    sioinput        __P((struct com_s *com));
static  void    siointr1        __P((struct com_s *com));
static  void    siointr         __P((void *arg));
static  int     commctl         __P((struct com_s *com, int bits, int how));
static  int     comparam        __P((struct tty *tp, struct termios *t));
#if __FreeBSD_version >= 500000
static  void    siopoll         __P((void *));
#else
static  inthand2_t siopoll;
#endif
static  void    siosettimeout   __P((void));
static  int     siosetwater     __P((struct com_s *com, speed_t speed));
static  void    comstart        __P((struct tty *tp));
static  void    comstop         __P((struct tty *tp, int rw));
static  timeout_t comwakeup;
static  void    disc_optim      __P((struct tty *tp, struct termios *t,
                                     struct com_s *com));

static  void ltmdm_pci_release_resource(device_t dev, struct com_s *com);

static  int ltmdm_pci_probe     __P((device_t dev));
static  int ltmdm_pci_attach    __P((device_t dev));
static  int ltmdm_pci_detach    __P((device_t dev));

static char driver_name[] = "ltmdm";

#ifndef KLD_MODULE
static  int sio_inited = 0;
#endif

#if __FreeBSD_version >= 500000
#if __FreeBSD_version <  500028 /* >= 20011218 */
critical_t  savecrit;
#endif
#endif


/* table and macro for fast conversion from a unit number to its com struct */
static  devclass_t  ltmdm_devclass;
#define com_addr(unit)  ((struct com_s *) \
                         devclass_get_softc(ltmdm_devclass, unit))

static device_method_t ltmdm_pci_methods[] = {
    /* Device interface */
    DEVMETHOD(device_probe,     ltmdm_pci_probe),
    DEVMETHOD(device_attach,    ltmdm_pci_attach),
    DEVMETHOD(device_detach,    ltmdm_pci_detach),
    { 0, 0 }
};

static driver_t ltmdm_pci_driver = {
    driver_name,
    ltmdm_pci_methods,
    sizeof(struct com_s),
};

static  d_open_t    sioopen;
static  d_close_t   sioclose;
static  d_read_t    sioread;
static  d_write_t   siowrite;
static  d_ioctl_t   sioioctl;


/* ${SYSDIR}/conf/majors:  entries from 200-252 are reserved for local use */

#ifndef CDEV_MAJOR
#define CDEV_MAJOR  228
#endif

static struct cdevsw sio_cdevsw = {
    /* name */  driver_name,
    /* maj */   CDEV_MAJOR,
    /* flags */ D_TTY | D_KQFILTER,
    /* port */	NULL,
    /* clone */ NULL,
    /* open */  sioopen,
    /* close */ sioclose,
    /* read */  sioread,
    /* write */ siowrite,
    /* ioctl */ sioioctl,
    /* poll */  ttypoll,
    /* mmap */  nommap,
    /* strategy */  nostrategy,
    /* dump */  nodump,
    /* psize */ nopsize,
    /* kqfilter */  ttykqfilter
};

static  u_int   com_events; /* input chars + weighted output completions */
#if __FreeBSD_version >= 500000
static  void    *sio_slow_ih;
static  void    *sio_fast_ih;
#else
static  bool_t  sio_registered;
#endif
static  int sio_timeout;
static  int sio_timeouts_until_log;
static struct callout sio_timeout_handle;
static struct callout sio_busycheck_handle;
static struct callout sio_dtrwakeup_handle;
static struct callout lt_add_timer_handle;
static void (*lt_timer_func)(unsigned long);
static int sio_numunits;

static  struct speedtab comspeedtab[] = {
    { 0,        0 },
    { 50,       COMBRD(50) },
    { 75,       COMBRD(75) },
    { 110,      COMBRD(110) },
    { 134,      COMBRD(134) },
    { 150,      COMBRD(150) },
    { 200,      COMBRD(200) },
    { 300,      COMBRD(300) },
    { 600,      COMBRD(600) },
    { 1200,     COMBRD(1200) },
    { 1800,     COMBRD(1800) },
    { 2400,     COMBRD(2400) },
    { 4800,     COMBRD(4800) },
    { 9600,     COMBRD(9600) },
    { 19200,    COMBRD(19200) },
    { 28800,    COMBRD(28800) },
    { 38400,    COMBRD(38400) },
    { 57600,    COMBRD(57600) },
    { 115200,   COMBRD(115200) },
    { -1,       -1 }
};


/****************************************************************************/

#if __FreeBSD_version >= 500000
#define DEFAULT_INTR_TYPE INTR_TYPE_NET
#define DEFAULT_SWI_TYPE  SWI_TTY
#else
#define DEFAULT_INTR_TYPE INTR_TYPE_TTY
#define DEFAULT_SWI_TYPE  SWI_TTY
#endif

int  ltmdm_intr_type        = DEFAULT_INTR_TYPE;
int  ltmdm_swi_type         = DEFAULT_SWI_TYPE;

int  ltmdm_pci_vendor_id    = 0;
int  ltmdm_pci_device_id    = 0;
char ltmdm_intr[32]         = "";
char ltmdm_swi[32]          = "";
char ltmdm_device_desc[32]  = "";

#if 1
SYSCTL_NODE(_hw,           OID_AUTO,   ltmdm,           CTLFLAG_RD,
                                            0,  "Lucent Winmodem Driver");
SYSCTL_INT(_hw_ltmdm,      OID_AUTO,   pci_vendor_id,   CTLFLAG_RD,
                    &ltmdm_pci_vendor_id,   0,  "");
SYSCTL_INT(_hw_ltmdm,      OID_AUTO,   pci_device_id,   CTLFLAG_RD,
                    &ltmdm_pci_device_id,   0,  "");
SYSCTL_STRING(_hw_ltmdm,   OID_AUTO,   intr,            CTLFLAG_RD,
                    ltmdm_intr,             sizeof(ltmdm_intr),         "");
SYSCTL_STRING(_hw_ltmdm,   OID_AUTO,   swi,             CTLFLAG_RD,
                    ltmdm_swi,              sizeof(ltmdm_swi),          "");
SYSCTL_STRING(_hw_ltmdm,   OID_AUTO,   device_desc,     CTLFLAG_RD,
                    ltmdm_device_desc,      sizeof(ltmdm_device_desc),  "");
#endif

/****************************************************************************/
 

/*  To enable debug message:               */
/*                                         */
/*    make DEBUG_FLAGS=-DLTMDM_DEBUG_MSG   */

/* #undef  LTMDM_DEBUG_MSG */

#define LTMDM_MAX_UNIT        1

#define LTMDM_PCI_BASE_ADDR0  0x10
#define LTMDM_PCI_BASE_ADDR1  0x14
#define LTMDM_PCI_BASE_ADDR2  0x18
#define LTMDM_PCI_BASE_ADDR3  0x1c
#define LTMDM_PCI_BASE_ADDR4  0x20
#define LTMDM_PCI_BASE_ADDR5  0x24

#if LTMDMOBJ_VERSION >= 595

struct ltmodem_res
{
    u_int16_t   BaseAddress;
    u_int16_t   Irq;
};

#endif

#if LTMDMOBJ_VERSION >= 597

struct lt_pci_dev_info
{
    u_int16_t   irq;
    u_int16_t   vendor;
    u_int16_t   device;
    u_int16_t   subvendor;
    u_int16_t   subdevice;
    u_int16_t   devfn;
    u_int16_t   busnum;
    u_int16_t   filler;
    u_int32_t   BaseAddress[6];
};

#endif

#if LTMDMOBJ_VERSION >= 595
extern u_int32_t lucent_detect_modem(struct ltmodem_res *);
#else
extern u_int32_t lucent_detect_modem(void);
#endif

extern void lucent_init_modem(void);
extern void V16550_Init(void);
extern u_int8_t read_vuart_port(u_int8_t);
extern void write_vuart_port(u_int8_t, u_int8_t);
extern void vxdPortOpen(void);
extern void vxdPortClose(void);
extern void dp_dsp_isr(void);

#ifdef LTMDM_DEBUG_MSG
extern u_int8_t  Irq;
extern u_int16_t BaseAddress;
extern u_int16_t BaseAddress2;
extern u_int16_t BaseAddressData;
extern u_int16_t BaseAddressIndex;
extern u_int8_t  BaseValue;
extern u_int8_t  V16550_IRQ_Number;
extern u_int16_t ComAddress;
extern u_int8_t  ToshibaFlag;
extern u_int8_t  CpqFlag;
extern u_int8_t  IBMBlacktip1Flag;
extern u_int8_t  x_dsp_mars;
extern u_int8_t  x_dsp_mars3;
extern u_int8_t  x_chip_version;
#endif


#if LTMDMOBJ_VERSION >= 595
extern int eeprom_flag;
extern unsigned char eeprom[];
#endif

#if LTMDMOBJ_VERSION >= 595
static  void rs_interrupt(void);
#endif

void      rs_interrupt_single(int irq, void *dev_id, void * regs);
void      lt_init_timer(void);
void      lt_add_timer(void (*timerfunction)(unsigned long));
int       lt_pci_present(void);
void      lt_pcibios_read_config_byte(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int8_t *result);
void      lt_pcibios_read_config_word(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int16_t *result);
void      lt_pcibios_read_config_dword(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int32_t *result);

#if LTMDMOBJ_VERSION >= 597
int       lt_pcibios_write_config_word(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int16_t data);
int       lt_pci_find_device(struct lt_pci_dev_info *dev_info, unsigned int id, unsigned int num);
#else
int       lt_pci_find_device(unsigned int vendor, unsigned int device);
u_int8_t  GetIrqFromDev(void);
u_int8_t  GetBusNumberFromDev(void);
u_int8_t  GetDevfnFromDev(void);
u_int16_t GetDeviceFromDev(void);
u_int16_t GetVendorFromDev(void);
u_int32_t GetBase_addressFromDev(int num);
#endif

u_int8_t  Get_PCI_INTERRUPT_LINE(void);
u_int8_t  Get_PCI_BASE_ADDRESS_1(void);
u_int8_t  Get_PCI_BASE_ADDRESS_2(void);
u_int32_t Get_PCI_BASE_ADDRESS_IO_MASK(void);
u_int8_t  Get_PCI_BASE_ADDRESS_SPACE_IO(void);
u_int32_t VMODEM_Get_System_Time(void);
u_int8_t  inp(u_int16_t addr);
void      outp(u_int16_t addr, u_int8_t value);
u_int16_t inpw(u_int16_t addr);
void      outpw(u_int16_t addr, u_int16_t value);
u_int32_t inpd(u_int16_t addr);
void      outpd(u_int16_t addr, u_int32_t value);

#if LTMDMOBJ_VERSION >= 595
void      lin_kill(void);
void      lin_wake_up(void);
void      lin_interruptible_sleep_on(void);
int       lin_signal_pending(void);
int       function_1(int (*fn)(void *), char *a);
int       function_2(char *p);
#endif

static device_t lt_dev = NULL;

#if LTMDMOBJ_VERSION >= 595
static struct ltmodem_res lt_modem_res;
void (*lt_rs_interrupt)(void) = rs_interrupt;
#endif


#ifdef LTMDM_DEBUG_MSG
static int dbg_msg_level = 1;
#define DPRINTF(lev, msg)  if ((lev) <= dbg_msg_level) printf msg ;
#else
#define DPRINTF(lev, msg)
#endif

#if LTMDMOBJ_VERSION >= 595
static void rs_interrupt(void)
{
    rs_interrupt_single(0, NULL, NULL);
}
#endif

void rs_interrupt_single(int irq, void *dev_id, void *regs)
{
    struct com_s    *com;
    int unit;

    unit = 0;

    com = com_addr(unit);
    if (com == NULL)
        return;

    lt_disable_intr();
    siointr1(com);
    lt_enable_intr();
}

void lt_init_timer(void)
{
    static int did_init=0;
    if(did_init==0){
	did_init=1;
	callout_init(&lt_add_timer_handle);
    }
}

void lt_add_timer(void (*timerfunction)(unsigned long))
{
    lt_timer_func = timerfunction;

    callout_reset(&lt_add_timer_handle,1,(void(*)(void *))timerfunction, NULL);
}

int lt_pci_present(void)
{
    return 1;
}

void lt_pcibios_read_config_byte(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int8_t *result)
{
    *result = (u_int8_t)pci_read_config(lt_dev, reg, 1);
}

void lt_pcibios_read_config_word(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int16_t *result)
{
    *result = (u_int16_t)pci_read_config(lt_dev, reg, 2);
}

void lt_pcibios_read_config_dword(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int32_t *result)
{
    *result = (u_int32_t)pci_read_config(lt_dev, reg, 4);
}

#if LTMDMOBJ_VERSION >= 597

int lt_pcibios_write_config_word(u_int8_t bus, u_int8_t devfn, u_int8_t reg, u_int16_t data)
{
    pci_write_config(lt_dev, reg, data, 2);

    return 0;
}

int lt_pci_find_device(struct lt_pci_dev_info *dev_info, unsigned int id, unsigned int num)
{
    int i, ret;

    DPRINTF(1,("  lt_pci_find_device()\n"));

    dev_info->vendor = (unsigned short)pci_get_vendor(lt_dev);
    dev_info->device = (unsigned short)pci_get_device(lt_dev);

    DPRINTF(1,("    vendor    = 0x%04x\n", (u_int)dev_info->vendor));
    DPRINTF(1,("    device    = 0x%04x\n", (u_int)dev_info->device));
    DPRINTF(1,("    id        = 0x%04x\n", (u_int)id));
    DPRINTF(1,("    num       = 0x%04x\n", (u_int)num));

    if (dev_info->vendor == id &&
        dev_info->device == num) {
        dev_info->irq = (unsigned short)pci_get_irq(lt_dev);
        dev_info->subvendor = (unsigned short)pci_get_subvendor(lt_dev);
        dev_info->subdevice = (unsigned short)pci_get_subdevice(lt_dev);
        dev_info->devfn = (unsigned short)(((pci_get_slot(lt_dev) << 3) & 0xf8)
                                          | (pci_get_function(lt_dev)   & 0x07));
        dev_info->busnum = (unsigned  char)pci_get_bus(lt_dev);

        DPRINTF(1,("    irq       = 0x%02x\n", (u_int)dev_info->irq));
        DPRINTF(1,("    subvendor = 0x%04x\n", (u_int)dev_info->subvendor));
        DPRINTF(1,("    subdevice = 0x%04x\n", (u_int)dev_info->subdevice));
        DPRINTF(1,("    devfn     = 0x%04x\n", (u_int)dev_info->devfn));
        DPRINTF(1,("    busnum    = 0x%04x\n", (u_int)dev_info->busnum));

        for (i = 0; i < 6; i++) {
            dev_info->BaseAddress[i]
                    = pci_read_config(lt_dev, PCIR_MAPS + i * 4, 4);
            DPRINTF(1,("    BaseAddress[%d] = 0x%08lx\n",
                       i, dev_info->BaseAddress[i]));
        }
        ret = 1;
    } else {
        ret = 0;
    }

    return ret;
}

#else

int lt_pci_find_device(unsigned int vendor, unsigned int device)
{
    int ret;

    if (pci_get_vendor(lt_dev) == vendor &&
        pci_get_device(lt_dev) == device)
        ret = 1;
    else
        ret = 0;

    return ret;
}

u_int8_t GetIrqFromDev(void)
{
    u_int8_t irq;

    irq =  pci_get_irq(lt_dev);

    DPRINTF(1,("  GetIrqFromDev()\n"));
    DPRINTF(1,("    irq = 0x%02x\n", irq));

    return irq;
}

u_int8_t GetBusNumberFromDev(void)
{
    u_int8_t bus_number;

    bus_number =  pci_get_bus(lt_dev);

    DPRINTF(1,("  GetBusNumberFromDev()\n"));
    DPRINTF(1,("    bus number = 0x%02x\n", bus_number));

    return bus_number;
}

u_int8_t GetDevfnFromDev(void)
{
    u_int8_t slot, func, devfn;

    slot = pci_get_slot(lt_dev);
    func = pci_get_function(lt_dev);

    devfn = ((slot & 0x1f) << 3) | (func & 0x07);

    DPRINTF(1,("  GetDevfnFromDev()\n"));
    DPRINTF(1,("    devfn = 0x%02x\n", devfn));

    return devfn;
}

u_int16_t GetDeviceFromDev(void)
{
    u_int16_t device;

    device = pci_get_device(lt_dev);

    DPRINTF(1,("  GetDeviceFromDev()\n"));
    DPRINTF(1,("    device = 0x%04x\n", device));

    return device;
}

u_int16_t GetVendorFromDev(void)
{
    u_int16_t vendor;

    vendor = pci_get_vendor(lt_dev);

    DPRINTF(1,("  GetVendorFromDev()\n"));
    DPRINTF(1,("    vendor = 0x%04x\n", vendor));

    return vendor;
}

u_int32_t GetBase_addressFromDev(int num)
{
    u_int32_t base_address;

    base_address = pci_read_config(lt_dev, LTMDM_PCI_BASE_ADDR0 + 4 * num, 4);

    DPRINTF(1,("  GetBase_addressFromDev()\n"));
    DPRINTF(1,("    num          = %d\n", num));
    DPRINTF(1,("    base address = 0x%08x\n", base_address));

    return base_address;
}

#endif

u_int8_t Get_PCI_INTERRUPT_LINE(void)
{
    return 0x3c;
}

u_int8_t Get_PCI_BASE_ADDRESS_1(void)
{
    return 0x14;
}

u_int8_t Get_PCI_BASE_ADDRESS_2(void)
{
    return 0x18;
}

u_int32_t Get_PCI_BASE_ADDRESS_IO_MASK(void)
{
    return 0xfffffffcUL;
}

u_int8_t Get_PCI_BASE_ADDRESS_SPACE_IO(void)
{
    return 0x01;
}

u_int32_t VMODEM_Get_System_Time(void)
{
    struct timeval tv;
    unsigned long t;

    microtime(&tv);
    t = tv.tv_usec/1000 + tv.tv_sec*1000;

    return t;
}

u_int8_t inp(u_int16_t addr)
{
    return inb(addr);
}

void outp(u_int16_t addr, u_int8_t value)
{
    outb(addr, value);
}

u_int16_t inpw(u_int16_t addr)
{
    return inw(addr);
}

void outpw(u_int16_t addr, u_int16_t value)
{
    return outw(addr, value);
}

u_int32_t inpd(u_int16_t addr)
{
    return inl(addr);
}

void outpd(u_int16_t addr, u_int32_t value)
{
    return outl(addr, value);
}

#if LTMDMOBJ_VERSION >= 595

void lin_kill(void)
{
    ;
}

void lin_wake_up(void)
{
    ;
}

void lin_interruptible_sleep_on(void)
{
    ;
}

int  lin_signal_pending(void)
{
    return 0;
}

int  function_1(int (*fn)(void *), char *a)
{
    return 0;
}

int  function_2(char *p)
{
    return 0;
}

#endif


/****************************************************************************/


struct pci_ids {        /* pci device group */
    int vendor;         /* vendor_id        */
    int device_bgn;     /* device_id  from  */
    int device_end;     /*            to    */
    const char  *description;
};

static struct pci_ids pci_ids[] = {
#if LTMDMOBJ_VERSION >= 599
    { 0x115d, 0x0000, 0x00d4, "Xircom Winmodem" },
#else
    { 0x115d, 0x0000, 0x000f, "Xircom Winmodem" },
#endif
    { 0x115d, 0x0440, 0x045c, "Xircom Winmodem" },
    { 0x11c1, 0x0440, 0x045c, "Lucent Winmodem" },
    { 0x0000, 0x0000, 0x0000, NULL }
};


/* XXX */
#if 1
static intrmask_t (*splfunc_p)(void) = spltty;

static void set_splfunc(int type);
static void set_splfunc(int type)
{
    switch (type) {
    case INTR_TYPE_MISC : splfunc_p = splhigh;
    case INTR_TYPE_CAM  : splfunc_p = splcam;
    case INTR_TYPE_NET  : splfunc_p = splimp;
    case INTR_TYPE_BIO  : splfunc_p = splbio;
    case INTR_TYPE_TTY  :
    case ( INTR_TYPE_TTY | INTR_FAST ) :
    default             : splfunc_p = spltty;
    }
}

#define splfunc() (*splfunc_p)()
#else
#define splfunc() spltty()
#endif

#if 1
struct int_str_table { int val; char *str; };

static struct int_str_table intr_type_table[] =
{
    {   ( INTR_TYPE_TTY | INTR_FAST ), "INTR_TYPE_TTY|INTR_FAST" },
    {   INTR_TYPE_TTY   ,   "INTR_TYPE_TTY"     },
    {   INTR_TYPE_BIO   ,   "INTR_TYPE_BIO"     },
    {   INTR_TYPE_NET   ,   "INTR_TYPE_NET"     },
    {   INTR_TYPE_CAM   ,   "INTR_TYPE_CAM"     },
    {   INTR_TYPE_MISC  ,   "INTR_TYPE_MISC"    },
    {   -1              ,   NULL                }
};

static struct int_str_table swi_type_table[] =
{
    {   SWI_TTY     ,   "SWI_TTY"       },
    {   SWI_NET     ,   "SWI_NET"       },
    {   -1          ,   NULL            }
};

static void copy_str(char *dst, char *src, int len);
static void copy_str(char *dst, char *src, int len)
{
    while (len > 1 && (len--, *dst++ = *src++)) ;
    while (len-- > 0) *dst++ = '\0';
}
static int str_to_int(struct int_str_table *table, char *str, int *val);
static int str_to_int(struct int_str_table *table, char *str, int *val)
{
    int i;
    for (i=0; table[i].str != NULL; i++)
        if (!strcmp(table[i].str, str)) {
            *val = table[i].val;
            return 0;
        }
    return -1;
}

static int int_to_str(struct int_str_table *table, int val, char *str, int len);
static int int_to_str(struct int_str_table *table, int val, char *str, int len)
{
    int i;
    for (i=0; table[i].str != NULL; i++)
        if (table[i].val == val) {
            copy_str(str, table[i].str, len);
            return 0;
        }
    return -1;
}

#define int_to_str_intr(v, s, l) \
        int_to_str(intr_type_table, (v), (s), (l))
#define int_to_str_swi(v, s, l) \
        int_to_str(swi_type_table, (v), (s), (l))
#define str_to_int_intr(s, v) \
        str_to_int(intr_type_table, (s), (v))
#define str_to_int_swi(s, v) \
        str_to_int(swi_type_table, (s), (v))

static void ltmdm_read_param(void);
static void ltmdm_read_param(void)
#if 1   /* read from kernel environment */
{
    char *str;

    static int first = 1;

    if (!first)
        return;
    first = 0;

    if (getenv_int("hw.ltmdm.pci_vendor_id", &ltmdm_pci_vendor_id))
        printf("hw.ltmdm.pci_vendor_id: %d\n", ltmdm_pci_vendor_id);

    if (getenv_int("hw.ltmdm.pci_device_id", &ltmdm_pci_device_id))
        printf("hw.ltmdm.pci_device_id: %d\n", ltmdm_pci_device_id);

    int_to_str_intr(DEFAULT_INTR_TYPE, ltmdm_intr, sizeof(ltmdm_intr));
    if ((str = getenv("hw.ltmdm.intr")) != NULL) {
        if (str_to_int_intr(str, &ltmdm_intr_type) == 0) {
            copy_str(ltmdm_intr, str, sizeof(ltmdm_intr));
            printf("hw.ltmdm.intr: %s\n", str);
        } else {
            printf("hw.ltmdm.intr: invalid value: %s\n", str);
            printf("hw.ltmdm.intr <- %s\n", ltmdm_intr);
        }
    }
    set_splfunc(ltmdm_intr_type);

    int_to_str_swi(DEFAULT_SWI_TYPE, ltmdm_swi, sizeof(ltmdm_swi));
    if ((str = getenv("hw.ltmdm.swi")) != NULL) {
        if (str_to_int_swi(str, &ltmdm_swi_type) == 0) {
            copy_str(ltmdm_swi, str, sizeof(ltmdm_swi));
            printf("hw.ltmdm.swi: %s\n", str);
        } else {
            printf("hw.ltmdm.swi: invalid value: %s\n", str);
            printf("hw.ltmdm.swi <- %s\n", ltmdm_swi);
        }
    }
}
#else
{
    if (str_to_int_intr(ltmdm_intr, &ltmdm_intr_type) == 0) {
        printf("hw.ltmdm.intr: %s\n", ltmdm_intr);
    } else {
        int put_msg = (ltmdm_intr[0] != '\0');
        if (put_msg)
            printf("hw.ltmdm.intr: invalid value: %s\n", ltmdm_intr);
        ltmdm_intr_type = DEFAULT_INTR_TYPE;
        int_to_str_intr(ltmdm_intr_type, ltmdm_intr, sizeof(ltmdm_intr));
        if (put_msg)
            printf("hw.ltmdm.intr <- %s\n", ltmdm_intr);
    }
    set_splfunc(ltmdm_intr_type);

    if (str_to_int_swi(ltmdm_swi, &ltmdm_swi_type) == 0) {
        printf("hw.ltmdm.swi: %s\n", ltmdm_swi);
    } else {
        int put_msg = (ltmdm_swi[0] != '\0');
        if (put_msg)
            printf("hw.ltmdm.swi: invalid value: %s\n", ltmdm_swi);
        ltmdm_swi_type = DEFAULT_SWI_TYPE;
        int_to_str_swi(ltmdm_swi_type, ltmdm_swi, sizeof(ltmdm_swi));
        if (put_msg)
            printf("hw.ltmdm.swi <- %s\n", ltmdm_swi);
    }
}
#endif
#endif

static void
ltmdm_pci_release_resource(device_t dev, struct com_s *com)
{
    int i;

    if (com->irqres != NULL) {
        if (com->cookie != NULL) {
            bus_teardown_intr(dev, com->irqres, com->cookie);
            com->cookie = NULL;
        }
        bus_release_resource(dev, SYS_RES_IRQ, com->irqrid, com->irqres);
        com->irqrid = 0;
        com->irqres = NULL;
    }

    for (i = 0; i < 6; i++) {
        if (com->iores[i] != NULL) {
            bus_release_resource(dev, SYS_RES_IOPORT, com->iorid[i], com->iores[i]);
            com->iorid[i] = 0;
            com->iores[i] = NULL;
        }
    }
}


static int
ltmdm_pci_probe(device_t dev)
{
    int match;
    int vendor;
    int device;
    const char *desc;
    struct pci_ids  *id;
    struct com_s    *com;

    
    if (sio_numunits >= LTMDM_MAX_UNIT)
        return ENXIO;

    com = device_get_softc(dev);
    bzero(com, sizeof *com);

    vendor = pci_get_vendor(dev);
    device = pci_get_device(dev);

    desc = NULL;
    match = 0;
    if (ltmdm_pci_vendor_id != 0 &&
        ltmdm_pci_device_id != 0) {
        if (vendor == ltmdm_pci_vendor_id &&
            device == ltmdm_pci_device_id) {
            desc = ltmdm_device_desc;
            lt_dev = dev;
            match = 1;
        }
    }
    if (match == 0) {
        for (id = pci_ids; id->vendor != 0; id++) {
            if (id->vendor == vendor && id->device_bgn <= device
                                     && id->device_end >= device) {
                desc = id->description;
                lt_dev = dev;
                match = 1;
                break;
            }
        }
    }
    if (match == 0)
        return ENXIO;

    device_set_desc(dev, desc);

    return 0;
}

static int
ltmdm_pci_attach(device_t dev)
{
    struct com_s    *com;
    u_int   flags;
    int     unit, ret, intr_type;
    int     i, found, data;
    static int did_init = 0;
    int minorbase;
    unit = device_get_unit(dev);
    com = device_get_softc(dev);
    flags = device_get_flags(dev);

    if (unit >= sio_numunits)
        sio_numunits = unit + 1;

    found = 0;
    for (i = 0; i < 6; i++) {
        com->iorid[i] = PCIR_MAPS + 4 * i;
        data = pci_read_config(dev, com->iorid[i], 4);
        if (((data &  0x01UL) == 0x01) &&
            ((data & ~0x03UL) != 0   )) {
            com->iores[i] = bus_alloc_resource(dev, SYS_RES_IOPORT,
                                               &com->iorid[i],
                                               0, ~0, 1, RF_ACTIVE);
            if (com->iores[i] == NULL) {
                device_printf(dev, "could not map ioport.\n");
                ltmdm_pci_release_resource(dev, com);
                return ENXIO;
            }
            found = 1;
        }
    }
    if (!found) {
        device_printf(dev, "could not find ioport base reg.\n");
        ltmdm_pci_release_resource(dev, com);
        return ENXIO;
    }

#ifndef KLD_MODULE
    if (sio_inited++ == 0) {
        ltmdm_read_param();
    }
#endif

    intr_type = ltmdm_intr_type;

    com->irqrid = 0;
    com->irqres = NULL;
    com->cookie = NULL;

    if (intr_type & INTR_FAST) {
        com->irqres = bus_alloc_resource(dev, SYS_RES_IRQ, &com->irqrid, 0ul, ~0ul, 1,
                                         RF_ACTIVE);
    }
    if (com->irqres == NULL) {
        com->irqres = bus_alloc_resource(dev, SYS_RES_IRQ, &com->irqrid, 0ul, ~0ul, 1,
                                         RF_ACTIVE | RF_SHAREABLE);
        if (com->irqres != NULL) {
            if (intr_type & INTR_FAST) {
                intr_type &= ~INTR_FAST;
                device_printf(dev, "could not map interrupt in non-shareable mode.\n");
            }
        }
    }
    if (com->irqres == NULL) {
        device_printf(dev, "could not map interrupt.\n");
        ltmdm_pci_release_resource(dev, com);
        return ENXIO;
    }
/*
    if (intr_type & INTR_FAST) {
	ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres,
			     intr_type, siointr, com, &com->cookie);
        if (ret != 0)
            intr_type &= ~INTR_FAST;
    } else {
        ret = -1;
    }
    if (ret != 0) {
	ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres,
			     intr_type, siointr, com, &com->cookie);
        if (ret == 0 && (ltmdm_intr_type & INTR_FAST)) {
            device_printf(dev, "could not activate interrupt in fast mode\n");
        }
    }

    if (ret != 0) {
        device_printf(dev, "could not activate interrupt\n");
        ltmdm_pci_release_resource(dev, com);
        return ENXIO;
    }

    ltmdm_intr_type = intr_type;
*/

#if LTMDMOBJ_VERSION >= 597
    eeprom_flag = 0;
    eeprom[0] = 0;
#endif

    DPRINTF(1,("  lucent_detect_modem()\n"));

#if LTMDMOBJ_VERSION >= 595
    ret = lucent_detect_modem(&lt_modem_res);
#else
    ret = lucent_detect_modem();
#endif

    if (ret != 0) {
        device_printf(dev, "could not detect modem resources.\n");
        ltmdm_pci_release_resource(dev, com);
        return ENXIO;
    }

    DPRINTF(1,("  lucent_init_modem()\n"));
    lucent_init_modem();

    DPRINTF(1,("  vxdPortOpen()\n"));
    vxdPortOpen();

    DPRINTF(1,("  BaseValue         = 0x%02x\n", BaseValue));
    DPRINTF(1,("  BaseAddress       = 0x%04x\n", BaseAddress));
    DPRINTF(1,("  BaseAddress2      = 0x%04x\n", BaseAddress2));
    DPRINTF(1,("  BaseAddressIndex  = 0x%04x\n", BaseAddressIndex));
    DPRINTF(1,("  BaseAddressData   = 0x%04x\n", BaseAddressData));
    DPRINTF(1,("  ComAddress        = 0x%04x\n", ComAddress));
    DPRINTF(1,("  V16550_IRQ_Number = %d\n", V16550_IRQ_Number));
    DPRINTF(1,("  Irq               = %d\n", Irq));
    DPRINTF(1,("  ToshibaFlag       = %d\n", ToshibaFlag));
    DPRINTF(1,("  CpqFlag           = %d\n", CpqFlag));
    DPRINTF(1,("  IBMBlacktip1Flag  = %d\n", IBMBlacktip1Flag));
    DPRINTF(1,("  x_dsp_mars        = %d\n", x_dsp_mars));
    DPRINTF(1,("  x_dsp_mars3       = %d\n", x_dsp_mars3));
    DPRINTF(1,("  x_chip_version    = %d\n", x_chip_version));

    com->flags = flags;

#ifdef ENABLE_PPS
    com->pps.ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR;
    pps_init(&com->pps);
#endif

    /*
     * initialize the device registers as follows:
     *  o cfcr = CFCR_8BITS.
     *    It is most important that CFCR_DLAB is off, so that the
     *    data port is not hidden when we enable interrupts.
     *  o ier = 0.
     *    Interrupts are only enabled when the line is open.
     *  o mcr = MCR_IENABLE, or 0 if the port has AST/4 compatible
     *    interrupt control register or the config specifies no irq.
     *    Keeping MCR_DTR and MCR_RTS off might stop the external
     *    device from sending before we are ready.
     */
    com->unit = unit;
    com->cfcr_image = CFCR_8BITS;
    com->dtr_wait = 3 * hz;
    com->tx_fifo_size = 1;
    com->obufs[0].l_head = com->obuf1;
    com->obufs[1].l_head = com->obuf2;

    /*
     * We don't use all the flags from <sys/ttydefaults.h> since they
     * are only relevant for logins.  It's important to have echo off
     * initially so that the line doesn't start blathering before the
     * echo flag can be turned off.
     */
    com->it_in.c_iflag = 0;
    com->it_in.c_oflag = 0;
    com->it_in.c_cflag = TTYDEF_CFLAG;
    com->it_in.c_lflag = 0;
    com->it_in.c_ispeed = com->it_in.c_ospeed = TTYDEF_SPEED;
    siosetwater(com, com->it_in.c_ispeed);
    lt_enable_intr();
    termioschars(&com->it_in);
    com->it_out = com->it_in;

    /* attempt to determine UART type */

    printf("ltmdm%d: type", unit);

    write_vuart_port(UART_FIFO, FIFO_ENABLE | FIFO_RX_HIGH);
    DELAY(100);
    switch (read_vuart_port(UART_IIR) & IIR_FIFO_MASK) {
    case FIFO_RX_LOW:
        printf(" 16450");
        break;
    case FIFO_RX_MEDL:
        printf(" 16450?");
        break;
    case FIFO_RX_MEDH:
        printf(" 16550?");
        break;
    case FIFO_RX_HIGH:
        if (COM_NOFIFO(flags)) {
            printf(" 16550A fifo disabled");
        } else {
            com->hasfifo = TRUE;
#if 0
            com->tx_fifo_size = COM_FIFOSIZE(flags);
#else
            com->tx_fifo_size = 64;
            printf(" Virtual");
#endif
            printf(" 16550A");
        }

        break;
    }
    write_vuart_port(UART_FIFO, 0);
    printf("\n");

    if (!sio_registered) {
        register_swi(ltmdm_swi_type, siopoll, NULL, "tty:ltmdm");
        sio_registered = TRUE;
    }
    
    if (did_init==0){
	did_init = 1;
        callout_init(&sio_timeout_handle);
        callout_init(&sio_busycheck_handle);
        callout_init(&sio_dtrwakeup_handle);
/*        callout_init(&lt_add_timer_handle);*/
    }
    minorbase = UNIT_TO_MINOR(unit);

    cdevsw_add(&sio_cdevsw,UNIT_TO_MINOR(-1),minorbase);
    
    make_dev(&sio_cdevsw, minorbase,
                            UID_ROOT, GID_WHEEL, 0600, "ttyl%r", unit);
    make_dev(&sio_cdevsw, minorbase | CONTROL_INIT_STATE,
                            UID_ROOT, GID_WHEEL, 0600, "ttyil%r", unit);
    make_dev(&sio_cdevsw, minorbase | CONTROL_LOCK_STATE,
                            UID_ROOT, GID_WHEEL, 0600, "ttyll%r", unit);
    make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK,
                            UID_UUCP, GID_DIALER, 0660, "cual%r", unit);
    make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_INIT_STATE,
                            UID_UUCP, GID_DIALER, 0660, "cuail%r", unit);
    make_dev(&sio_cdevsw, minorbase | CALLOUT_MASK | CONTROL_LOCK_STATE,
                            UID_UUCP, GID_DIALER, 0660, "cuall%r", unit);
    com->mcr_image = read_vuart_port(UART_MCR);
    
    if (intr_type & INTR_FAST) {
	ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres,
			     intr_type, siointr, com, &com->cookie);
        if (ret != 0)
            intr_type &= ~INTR_FAST;
    } else {
        ret = -1;
    }
    if (ret != 0) {
	ret = BUS_SETUP_INTR(device_get_parent(dev), dev, com->irqres,
			     intr_type, siointr, com, &com->cookie);
        if (ret == 0 && (ltmdm_intr_type & INTR_FAST)) {
            device_printf(dev, "could not activate interrupt in fast mode\n");
        }
    }

    if (ret != 0) {
        device_printf(dev, "could not activate interrupt\n");
        ltmdm_pci_release_resource(dev, com);
        return ENXIO;
    }

    ltmdm_intr_type = intr_type;

    lt_timer_func = NULL;

    return 0;
}

static int
ltmdm_pci_detach(device_t dev)
{
    struct com_s *com;
    struct tty  *tp;
    int s;

    com = device_get_softc(dev);
    tp = com->tp;
    if (tp && (tp->t_state & TS_ISOPEN))
        return EBUSY;

    com->gone = 1;
    if (sio_registered) {
        unregister_swi(ltmdm_swi_type, siopoll);
        sio_registered = FALSE;
    }

    s = splfunc();
    if (tp) {
        (*linesw[tp->t_line].l_close)(tp, FNONBLOCK);
        disc_optim(tp, &tp->t_termios, com);
        comstop(tp, FREAD | FWRITE);
        comhardclose(com);
        ttyclose(tp);
    }
    vxdPortClose();
    siosettimeout();
    splx(s);
    /* give chance for timeout routines to run ? */
    s = splfunc();

    callout_stop(&sio_timeout_handle);
    callout_stop(&sio_busycheck_handle);
    callout_stop(&sio_dtrwakeup_handle);
    callout_stop(&lt_add_timer_handle);
    
    if (com->ibuf != NULL) {
        free(com->ibuf, M_DEVBUF);
        com->ibuf = NULL;
    }
    if (tp) {
#if 0                      /* #if 0 -> causes memory leak on "kldunload". */
                           /* #if 1 -> causes error on "pstat -t".        */
        free(tp, M_TTYS);  /* XXX -- until ttyfree() become available */
#endif
        com->tp = NULL;
    }
/*
    for (i = 0 ; i < 6; i++)
        destroy_dev(com->devs[i]);
*/
    ltmdm_pci_release_resource(dev, com);

    splx(s);

    return 0;
}

static int
sioopen(dev_t dev, int flag, int mode, struct thread *td)
{
    struct com_s    *com;
    int     error;
    int     mynor;
    int     s;
    struct tty  *tp;
    int     unit;

    mynor = minor(dev);
    unit = MINOR_TO_UNIT(mynor);
    com = com_addr(unit);
    if (com == NULL)
        return (ENXIO);
    if (com->gone)
        return (ENXIO);
    if (mynor & CONTROL_MASK)
        return (0);
    tp = dev->si_tty = com->tp = ttymalloc(com->tp);
    s = splfunc();
    /*
     * We jump to this label after all non-interrupted sleeps to pick
     * up any changes of the device state.
     */
open_top:
    while (com->state & CS_DTR_OFF) {
/*        error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "ltmdmdtr", 0);*/
	error = tsleep(&com->dtr_wait, PCATCH, "ltmdmdtr", 0);
        if (com_addr(unit) == NULL)
            return (ENXIO);
        if (error != 0 || com->gone)
            goto out;
    }
    if (tp->t_state & TS_ISOPEN) {
        /*
         * The device is open, so everything has been initialized.
         * Handle conflicts.
         */
        if (mynor & CALLOUT_MASK) {
            if (!com->active_out) {
                error = EBUSY;
                goto out;
            }
        } else {
            if (com->active_out) {
                if (flag & O_NONBLOCK) {
                    error = EBUSY;
                    goto out;
                }
                error = tsleep(&com->active_out,
                               PCATCH, "ltmdmbi", 0);
                if (com_addr(unit) == NULL)
                    return (ENXIO);
                if (error != 0 || com->gone)
                    goto out;
                goto open_top;
            }
        }
        if (tp->t_state & TS_XCLUDE &&
            suser(td)) {
            error = EBUSY;
            goto out;
        }
    } else {
        /*
         * The device isn't open, so there are no conflicts.
         * Initialize it.  Initialization is done twice in many
         * cases: to preempt sleeping callin opens if we are
         * callout, and to complete a callin open after DCD rises.
         */
        tp->t_oproc = comstart;
        tp->t_param = comparam;
        tp->t_stop = comstop;
        tp->t_dev = dev;
        tp->t_termios = mynor & CALLOUT_MASK ? com->it_out : com->it_in;
        (void)commctl(com, TIOCM_DTR | TIOCM_RTS, DMSET);
        ++com->wopeners;
        error = comparam(tp, &tp->t_termios);
        --com->wopeners;
        if (error != 0)
            goto out;
        /*
         * XXX we should goto open_top if comparam() slept.
         */
        if (com->hasfifo) {
            /*
             * (Re)enable and drain fifos.
             *
             * Certain SMC chips cause problems if the fifos
             * are enabled while input is ready.  Turn off the
             * fifo if necessary to clear the input.  We test
             * the input ready bit after enabling the fifos
             * since we've already enabled them in comparam()
             * and to handle races between enabling and fresh
             * input.
             */
            while (TRUE) {
                write_vuart_port(UART_FIFO,
                                 FIFO_RCV_RST | FIFO_XMT_RST | com->fifo_image);
                /*
                 * XXX the delays are for superstitious
                 * historical reasons.  It must be less than
                 * the character time at the maximum
                 * supported speed (87 usec at 115200 bps
                 * 8N1).  Otherwise we might loop endlessly
                 * if data is streaming in.  We used to use
                 * delays of 100.  That usually worked
                 * because DELAY(100) used to usually delay
                 * for about 85 usec instead of 100.
                 */
                DELAY(50);
                if (!(read_vuart_port(UART_LSR) & LSR_RXRDY))
                    break;
                write_vuart_port(UART_FIFO, 0);
                DELAY(50);
                (void) read_vuart_port(UART_DATA);
            }
        }

        lt_disable_intr();
        (void) read_vuart_port(UART_LSR);
        (void) read_vuart_port(UART_DATA);
        com->prev_modem_status = com->last_modem_status
                               = read_vuart_port(UART_MSR);
        write_vuart_port(UART_IER,
                         IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC);
        lt_enable_intr();
        /*
         * Handle initial DCD.  Callout devices get a fake initial
         * DCD (trapdoor DCD).  If we are callout, then any sleeping
         * callin opens get woken up and resume sleeping on "ltmdmbi"
         * instead of "ltmdmdcd".
         */
        /*
         * XXX `mynor & CALLOUT_MASK' should be
         * `tp->t_cflag & (SOFT_CARRIER | TRAPDOOR_CARRIER) where
         * TRAPDOOR_CARRIER is the default initial state for callout
         * devices and SOFT_CARRIER is like CLOCAL except it hides
         * the true carrier.
         */
        if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK)
            (*linesw[tp->t_line].l_modem)(tp, 1);
    }
    /*
     * Wait for DCD if necessary.
     */
    if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK)
        && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) {
        ++com->wopeners;
        error = tsleep(TSA_CARR_ON(tp), PCATCH, "ltmdmdcd", 0);
        if (com_addr(unit) == NULL)
            return (ENXIO);
        --com->wopeners;
        if (error != 0 || com->gone)
            goto out;
        goto open_top;
    }
    error = (*linesw[tp->t_line].l_open)(dev, tp);
    disc_optim(tp, &tp->t_termios, com);
    if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK)
        com->active_out = TRUE;
    siosettimeout();
out:
    splx(s);
    if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0)
        comhardclose(com);
    return (error);
}

static int
sioclose(dev_t dev, int flag, int mode, struct thread *td)
{
    struct com_s    *com;
    int     mynor;
    int     s;
    struct tty  *tp;

    mynor = minor(dev);
    if (mynor & CONTROL_MASK)
        return (0);
    com = com_addr(MINOR_TO_UNIT(mynor));
    if (com == NULL)
        return (ENODEV);
    tp = com->tp;
    s = splfunc();
    (*linesw[tp->t_line].l_close)(tp, flag);
    disc_optim(tp, &tp->t_termios, com);
    comstop(tp, FREAD | FWRITE);
    comhardclose(com);
    ttyclose(tp);
    siosettimeout();
    splx(s);
    if (com->gone) {
        printf("ltmdm%d: gone\n", com->unit);
        s = splfunc();
        if (com->ibuf != NULL) {
            free(com->ibuf, M_DEVBUF);
            com->ibuf = NULL;
        }
        bzero(tp, sizeof *tp);
        splx(s);
    }
    return (0);
}

static void
comhardclose(struct com_s *com)
{
    struct tty  *tp;
    int     unit;
    int     s;

    unit = com->unit;
    s = splfunc();
    com->do_timestamp = FALSE;
    com->do_dcd_timestamp = FALSE;
#ifdef ENABLE_PPS
    com->pps.ppsparam.mode = 0;
#endif
    write_vuart_port(UART_CFCR, com->cfcr_image &= ~CFCR_SBREAK);
    {
        write_vuart_port(UART_IER, 0);
        tp = com->tp;
        if (tp->t_cflag & HUPCL
            /*
             * XXX we will miss any carrier drop between here and the
             * next open.  Perhaps we should watch DCD even when the
             * port is closed; it is not sufficient to check it at
             * the next open because it might go up and down while
             * we're not watching.
             */
            || (!com->active_out
                && !(com->prev_modem_status & MSR_DCD)
                && !(com->it_in.c_cflag & CLOCAL))
            || !(tp->t_state & TS_ISOPEN)) {
            (void)commctl(com, TIOCM_DTR, DMBIC);
            if (com->dtr_wait != 0 && !(com->state & CS_DTR_OFF)) {
		callout_reset(&sio_dtrwakeup_handle, com->dtr_wait,
			      siodtrwakeup, com);
                com->state |= CS_DTR_OFF;
            }
        }
    }
    if (com->hasfifo) {
        /*
         * Disable fifos so that they are off after controlled
         * reboots.  Some BIOSes fail to detect 16550s when the
         * fifos are enabled.
         */
        write_vuart_port(UART_FIFO, 0);
    }
    com->active_out = FALSE;
    wakeup(&com->active_out);
    wakeup(TSA_CARR_ON(tp));    /* restart any wopeners */
    splx(s);
}

static int
sioread(dev_t dev, struct uio *uio, int flag)
{
    int     mynor;
    struct com_s    *com;

    mynor = minor(dev);
    if (mynor & CONTROL_MASK)
        return (ENODEV);
    com = com_addr(MINOR_TO_UNIT(mynor));
    if (com == NULL || com->gone)
        return (ENODEV);
    return ((*linesw[com->tp->t_line].l_read)(com->tp, uio, flag));
}

static int
siowrite(dev_t dev, struct uio *uio, int flag)
{
    int     mynor;
    struct com_s    *com;
    int     unit;

    mynor = minor(dev);
    if (mynor & CONTROL_MASK)
        return (ENODEV);

    unit = MINOR_TO_UNIT(mynor);
    com = com_addr(unit);
    if (com == NULL || com->gone)
        return (ENODEV);

    return ((*linesw[com->tp->t_line].l_write)(com->tp, uio, flag));
}

static void
siobusycheck(void *chan)
{
    struct com_s    *com;
    int     s;

    com = (struct com_s *)chan;

    /*
     * Clear TS_BUSY if low-level output is complete.
     * spl locking is sufficient because siointr1() does not set CS_BUSY.
     * If siointr1() clears CS_BUSY after we look at it, then we'll get
     * called again.  Reading the line status port outside of siointr1()
     * is safe because CS_BUSY is clear so there are no output interrupts
     * to lose.
     */
    s = splfunc();
    if (com->state & CS_BUSY)
        com->extra_state &= ~CSE_BUSYCHECK; /* False alarm. */
    else if ((read_vuart_port(UART_LSR) & (LSR_TSRE | LSR_TXRDY))
                                      == (LSR_TSRE | LSR_TXRDY)) {
        com->tp->t_state &= ~TS_BUSY;
        ttwwakeup(com->tp);
        com->extra_state &= ~CSE_BUSYCHECK;
    } else
	callout_reset(&sio_busycheck_handle, hz/100, siobusycheck, com);
    splx(s);
}

static void
siodtrwakeup(void *chan)
{
    struct com_s    *com;

    com = (struct com_s *)chan;
    com->state &= ~CS_DTR_OFF;
    wakeup(&com->dtr_wait);
}

static void
sioinput(struct com_s *com)
{
    u_char      *buf;
    int         incc;
    u_char      line_status;
    int         recv_data;
    struct tty  *tp;

    buf = com->ibuf;
    tp = com->tp;
    if (!(tp->t_state & TS_ISOPEN) || !(tp->t_cflag & CREAD)) {
        com_events -= (com->iptr - com->ibuf);
        com->iptr = com->ibuf;
        return;
    }
    if (tp->t_state & TS_CAN_BYPASS_L_RINT) {
        /*
         * Avoid the grotesquely inefficient lineswitch routine
         * (ttyinput) in "raw" mode.  It usually takes about 450
         * instructions (that's without canonical processing or echo!).
         * slinput is reasonably fast (usually 40 instructions plus
         * call overhead).
         */
        do {
            lt_enable_intr();
            incc = com->iptr - buf;
            if (tp->t_rawq.c_cc + incc > tp->t_ihiwat
                && (com->state & CS_RTS_IFLOW
                || tp->t_iflag & IXOFF)
                && !(tp->t_state & TS_TBLOCK))
                ttyblock(tp);
            com->delta_error_counts[CE_TTY_BUF_OVERFLOW]
                += b_to_q((char *)buf, incc, &tp->t_rawq);
            buf += incc;
            tk_nin += incc;
            tk_rawcc += incc;
            tp->t_rawcc += incc;
            ttwakeup(tp);
            if (tp->t_state & TS_TTSTOP
                && (tp->t_iflag & IXANY
                || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) {
                tp->t_state &= ~TS_TTSTOP;
                tp->t_lflag &= ~FLUSHO;
                comstart(tp);
            }
            lt_disable_intr();
        } while (buf < com->iptr);
    } else {
        do {
            lt_enable_intr();
            line_status = buf[com->ierroff];
            recv_data = *buf++;
            if (line_status
                & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) {
                if (line_status & LSR_BI)
                    recv_data |= TTY_BI;
                if (line_status & LSR_FE)
                    recv_data |= TTY_FE;
                if (line_status & LSR_OE)
                    recv_data |= TTY_OE;
                if (line_status & LSR_PE)
                    recv_data |= TTY_PE;
            }
            (*linesw[tp->t_line].l_rint)(recv_data, tp);
            lt_disable_intr();
        } while (buf < com->iptr);
    }
    com_events -= (com->iptr - com->ibuf);
    com->iptr = com->ibuf;

    /*
     * There is now room for another low-level buffer full of input,
     * so enable RTS if it is now disabled and there is room in the
     * high-level buffer.
     */
    if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) &&
        !(tp->t_state & TS_TBLOCK))
        write_vuart_port(UART_MCR, com->mcr_image |= MCR_RTS);
}

static void
siointr(void *arg)
{
    COM_LOCK();
    dp_dsp_isr();
    COM_UNLOCK();
}

static void
siointr1(struct com_s *com)
{
    u_char  line_status;
    u_char  modem_status;
    u_char  *ioptr;
    u_char  recv_data;
    u_char  int_ctl;
    u_char  int_ctl_new;

#ifdef ENABLE_PPS
    u_int   count;
#endif

    int_ctl = read_vuart_port(UART_IER);
    int_ctl_new = int_ctl;

    while (!com->gone) {
#ifdef ENABLE_PPS
        if (com->pps.ppsparam.mode & PPS_CAPTUREBOTH) {
            modem_status = read_vuart_port(UART_MSR);
            if ((modem_status ^ com->last_modem_status) & MSR_DCD) {
                count = cputimer_count();
                pps_event(&com->pps,count, 
                          (modem_status & MSR_DCD) ? 
                          PPS_CAPTUREASSERT : PPS_CAPTURECLEAR);
            }
        }
#endif
        line_status = read_vuart_port(UART_LSR);

        /* input event? (check first to help avoid overruns) */
        while (line_status & LSR_RCV_MASK) {
            /* break/unnattached error bits or real input? */
            if (!(line_status & LSR_RXRDY))
                recv_data = 0;
            else
                recv_data = read_vuart_port(UART_DATA);
            if (line_status & (LSR_BI | LSR_FE | LSR_PE)) {
                /*
                 * Don't store BI if IGNBRK or FE/PE if IGNPAR.
                 * Otherwise, push the work to a higher level
                 * (to handle PARMRK) if we're bypassing.
                 * Otherwise, convert BI/FE and PE+INPCK to 0.
                 *
                 * This makes bypassing work right in the
                 * usual "raw" case (IGNBRK set, and IGNPAR
                 * and INPCK clear).
                 *
                 * Note: BI together with FE/PE means just BI.
                 */
                if (line_status & LSR_BI) {
                    if (com->tp == NULL
                        || com->tp->t_iflag & IGNBRK)
                        goto cont;
                } else {
                    if (com->tp == NULL
                        || com->tp->t_iflag & IGNPAR)
                        goto cont;
                }
                if (com->tp->t_state & TS_CAN_BYPASS_L_RINT
                    && (line_status & (LSR_BI | LSR_FE)
                    || com->tp->t_iflag & INPCK))
                    recv_data = 0;
            }
            ++com->bytes_in;
            if (com->hotchar != 0 && recv_data == com->hotchar)
                setsofttty();
            ioptr = com->iptr;
            if (ioptr >= com->ibufend)
                CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW);
            else {
                if (com->do_timestamp)
                    microtime(&com->timestamp);
                ++com_events;
                schedsofttty();
                ioptr[0] = recv_data;
                ioptr[com->ierroff] = line_status;
                com->iptr = ++ioptr;
                if (ioptr == com->ihighwater
                    && com->state & CS_RTS_IFLOW)
                    write_vuart_port(UART_MCR, com->mcr_image &= ~MCR_RTS);
                if (line_status & LSR_OE)
                    CE_RECORD(com, CE_OVERRUN);
            }
cont:
            /*
             * "& 0x7F" is to avoid the gcc-1.40 generating a slow
             * jump from the top of the loop to here
             */
            line_status = read_vuart_port(UART_LSR) & 0x7F;
        }

        /* modem status change? (always check before doing output) */
        modem_status = read_vuart_port(UART_MSR);
        if (modem_status != com->last_modem_status) {
            if (com->do_dcd_timestamp
                && !(com->last_modem_status & MSR_DCD)
                && modem_status & MSR_DCD)
                microtime(&com->dcd_timestamp);

            /*
             * Schedule high level to handle DCD changes.  Note
             * that we don't use the delta bits anywhere.  Some
             * UARTs mess them up, and it's easy to remember the
             * previous bits and calculate the delta.
             */
            com->last_modem_status = modem_status;
            if (!(com->state & CS_CHECKMSR)) {
                com_events += LOTS_OF_EVENTS;
                com->state |= CS_CHECKMSR;
                setsofttty();
            }

            /* handle CTS change immediately for crisp flow ctl */
            if (com->state & CS_CTS_OFLOW) {
                if (modem_status & MSR_CTS)
                    com->state |= CS_ODEVREADY;
                else
                    com->state &= ~CS_ODEVREADY;
            }
        }

        /* output queued and everything ready? */
        if (line_status & LSR_TXRDY
            && com->state >= (CS_BUSY | CS_TTGO | CS_ODEVREADY)) {
            ioptr = com->obufq.l_head;
            if (com->tx_fifo_size > 1) {
                u_int   ocount;

                ocount = com->obufq.l_tail - ioptr;
                if (ocount > com->tx_fifo_size)
                    ocount = com->tx_fifo_size;
                com->bytes_out += ocount;
                do
                    write_vuart_port(UART_DATA, *ioptr++);
                while (--ocount != 0);
            } else {
                write_vuart_port(UART_DATA, *ioptr++);
                ++com->bytes_out;
            }
            com->obufq.l_head = ioptr;
            if (ioptr >= com->obufq.l_tail) {
                struct lbq  *qp;

                qp = com->obufq.l_next;
                qp->l_queued = FALSE;
                qp = qp->l_next;
                if (qp != NULL) {
                    com->obufq.l_head = qp->l_head;
                    com->obufq.l_tail = qp->l_tail;
                    com->obufq.l_next = qp;
                } else {
                    /* output just completed */
                    com->state &= ~CS_BUSY;
                }
                if (!(com->state & CS_ODONE)) {
                    com_events += LOTS_OF_EVENTS;
                    com->state |= CS_ODONE;
                }
            }
        }

        /* finished? */
        if ((read_vuart_port(UART_IIR) & IIR_IMASK) == IIR_NOPEND)
            return;
    }
}

static int
sioioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td)
{
    struct com_s    *com;
    int     error;
    int     mynor;
    int     s;
    struct tty  *tp;
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
    u_long      oldcmd;
    struct termios  term;
#endif

    mynor = minor(dev);
    com = com_addr(MINOR_TO_UNIT(mynor));
    if (com == NULL || com->gone)
        return (ENODEV);
    if (mynor & CONTROL_MASK) {
        struct termios  *ct;

        switch (mynor & CONTROL_MASK) {
        case CONTROL_INIT_STATE:
            ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in;
            break;
        case CONTROL_LOCK_STATE:
            ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in;
            break;
        default:
            return (ENODEV);    /* /dev/nodev */
        }
        switch (cmd) {
        case TIOCSETA:
            error = suser(td);
            if (error != 0)
                return (error);
            *ct = *(struct termios *)data;
            return (0);
        case TIOCGETA:
            *(struct termios *)data = *ct;
            return (0);
        case TIOCGETD:
            *(int *)data = TTYDISC;
            return (0);
        case TIOCGWINSZ:
            bzero(data, sizeof(struct winsize));
            return (0);
        default:
            return (ENOTTY);
        }
    }
    tp = com->tp;
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
    term = tp->t_termios;
    oldcmd = cmd;
    error = ttsetcompat(tp, &cmd, data, &term);
    if (error != 0)
        return (error);
    if (cmd != oldcmd)
        data = (caddr_t)&term;
#endif
    if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) {
        int cc;
        struct termios *dt = (struct termios *)data;
        struct termios *lt = mynor & CALLOUT_MASK
                     ? &com->lt_out : &com->lt_in;

        dt->c_iflag = (tp->t_iflag & lt->c_iflag)
                    | (dt->c_iflag & ~lt->c_iflag);
        dt->c_oflag = (tp->t_oflag & lt->c_oflag)
                    | (dt->c_oflag & ~lt->c_oflag);
        dt->c_cflag = (tp->t_cflag & lt->c_cflag)
                    | (dt->c_cflag & ~lt->c_cflag);
        dt->c_lflag = (tp->t_lflag & lt->c_lflag)
                    | (dt->c_lflag & ~lt->c_lflag);
        for (cc = 0; cc < NCCS; ++cc)
            if (lt->c_cc[cc] != 0)
                dt->c_cc[cc] = tp->t_cc[cc];
        if (lt->c_ispeed != 0)
            dt->c_ispeed = tp->t_ispeed;
        if (lt->c_ospeed != 0)
            dt->c_ospeed = tp->t_ospeed;
    }
    error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, td);
    if (error != ENOIOCTL)
        return (error);
    s = splfunc();
    error = ttioctl(tp, cmd, data, flag);
    disc_optim(tp, &tp->t_termios, com);
    if (error != ENOIOCTL) {
        splx(s);
        return (error);
    }
    switch (cmd) {
    case TIOCSBRK:
        write_vuart_port(UART_CFCR, com->cfcr_image |= CFCR_SBREAK);
        break;
    case TIOCCBRK:
        write_vuart_port(UART_CFCR, com->cfcr_image &= ~CFCR_SBREAK);
        break;
    case TIOCSDTR:
        (void)commctl(com, TIOCM_DTR, DMBIS);
        break;
    case TIOCCDTR:
        (void)commctl(com, TIOCM_DTR, DMBIC);
        break;
    /*
     * XXX should disallow changing MCR_RTS if CS_RTS_IFLOW is set.  The
     * changes get undone on the next call to comparam().
     */
    case TIOCMSET:
        (void)commctl(com, *(int *)data, DMSET);
        break;
    case TIOCMBIS:
        (void)commctl(com, *(int *)data, DMBIS);
        break;
    case TIOCMBIC:
        (void)commctl(com, *(int *)data, DMBIC);
        break;
    case TIOCMGET:
        *(int *)data = commctl(com, 0, DMGET);
        break;
    case TIOCMSDTRWAIT:
        /* must be root since the wait applies to following logins */
        error = suser(td);
        if (error != 0) {
            splx(s);
            return (error);
        }
        com->dtr_wait = *(int *)data * hz / 100;
        break;
    case TIOCMGDTRWAIT:
        *(int *)data = com->dtr_wait * 100 / hz;
        break;
    case TIOCTIMESTAMP:
        com->do_timestamp = TRUE;
        *(struct timeval *)data = com->timestamp;
        break;
    case TIOCDCDTIMESTAMP:
        com->do_dcd_timestamp = TRUE;
        *(struct timeval *)data = com->dcd_timestamp;
        break;
    default:
        splx(s);
#ifdef ENABLE_PPS
        error = pps_ioctl(cmd, data, &com->pps);
#endif
        if (error == ENODEV)
            error = ENOTTY;
        return (error);
    }
    splx(s);
    return (0);
}

static void
siopoll(void *dummy)
{
    int     unit;

    if (com_events == 0)
        return;
repeat:
    for (unit = 0; unit < sio_numunits; ++unit) {
        struct com_s    *com;
        int             incc;
        struct tty      *tp;

        com = com_addr(unit);
        if (com == NULL)
            continue;
        tp = com->tp;
        if (tp == NULL || com->gone) {
            /*
             * Discard any events related to never-opened or
             * going-away devices.
             */
            lt_disable_intr();
            incc = com->iptr - com->ibuf;
            com->iptr = com->ibuf;
            if (com->state & CS_CHECKMSR) {
                incc += LOTS_OF_EVENTS;
                com->state &= ~CS_CHECKMSR;
            }
            com_events -= incc;
            lt_enable_intr();
            continue;
        }
        if (com->iptr != com->ibuf) {
            lt_disable_intr();
            sioinput(com);
            lt_enable_intr();
        }
        if (com->state & CS_CHECKMSR) {
            u_char  delta_modem_status;

            lt_disable_intr();
            delta_modem_status = com->last_modem_status
                         ^ com->prev_modem_status;
            com->prev_modem_status = com->last_modem_status;
            com_events -= LOTS_OF_EVENTS;
            com->state &= ~CS_CHECKMSR;
            lt_enable_intr();
            if (delta_modem_status & MSR_DCD)
                (*linesw[tp->t_line].l_modem)
                    (tp, com->prev_modem_status & MSR_DCD);
        }
        if (com->state & CS_ODONE) {
            lt_disable_intr();
            com_events -= LOTS_OF_EVENTS;
            com->state &= ~CS_ODONE;
            lt_enable_intr();
            if (!(com->state & CS_BUSY)
                && !(com->extra_state & CSE_BUSYCHECK)) {
		callout_reset(&sio_busycheck_handle, hz/100, siobusycheck, com);
                com->extra_state |= CSE_BUSYCHECK;
            }
            (*linesw[tp->t_line].l_start)(tp);
        }
        if (com_events == 0)
            break;
    }
    if (com_events >= LOTS_OF_EVENTS)
        goto repeat;
}

static int
comparam(struct tty *tp, struct termios *t)
{
    struct com_s    *com;
    u_int       cfcr;
    int         cflag;
    int         divisor;
    u_char      dlbh;
    u_char      dlbl;
    int         s;
    int         unit;

    /* do historical conversions */
    if (t->c_ispeed == 0)
        t->c_ispeed = t->c_ospeed;

    /* check requested parameters */
    divisor = ttspeedtab(t->c_ospeed, comspeedtab);
    if (divisor < 0 || (divisor > 0 && t->c_ispeed != t->c_ospeed))
        return (EINVAL);

    /* parameters are OK, convert them to the com struct and the device */
    unit = DEV_TO_UNIT(tp->t_dev);
    com = com_addr(unit);
    if (com == NULL)
        return (ENODEV);
    s = splfunc();
    if (divisor == 0)
        (void)commctl(com, TIOCM_DTR, DMBIC);   /* hang up line */
    else
        (void)commctl(com, TIOCM_DTR, DMBIS);
    cflag = t->c_cflag;
    switch (cflag & CSIZE) {
    case CS5:
        cfcr = CFCR_5BITS;
        break;
    case CS6:
        cfcr = CFCR_6BITS;
        break;
    case CS7:
        cfcr = CFCR_7BITS;
        break;
    default:
        cfcr = CFCR_8BITS;
        break;
    }
    if (cflag & PARENB) {
        cfcr |= CFCR_PENAB;
        if (!(cflag & PARODD))
            cfcr |= CFCR_PEVEN;
    }
    if (cflag & CSTOPB)
        cfcr |= CFCR_STOPB;

    if (com->hasfifo && divisor != 0) {
        /*
         * Use a fifo trigger level low enough so that the input
         * latency from the fifo is less than about 16 msec and
         * the total latency is less than about 30 msec.  These
         * latencies are reasonable for humans.  Serial comms
         * protocols shouldn't expect anything better since modem
         * latencies are larger.
         *
         * The fifo trigger level cannot be set at RX_HIGH for high
         * speed connections without further work on reducing 
         * interrupt disablement times in other parts of the system,
         * without producing silo overflow errors.
         */
        com->fifo_image = t->c_ospeed <= 4800 ? FIFO_ENABLE
                                              : FIFO_ENABLE | FIFO_RX_MEDH;
        write_vuart_port(UART_FIFO, com->fifo_image);
    }

    /*
     * This returns with interrupts disabled so that we can complete
     * the speed change atomically.  Keeping interrupts disabled is
     * especially important while UART_DATA is hidden.
     */
    (void) siosetwater(com, t->c_ispeed);

    if (divisor != 0) {
        write_vuart_port(UART_CFCR, cfcr | CFCR_DLAB);
        /*
         * Only set the divisor registers if they would change,
         * since on some 16550 incompatibles (UMC8669F), setting
         * them while input is arriving them loses sync until
         * data stops arriving.
         */
        dlbl = divisor & 0xFF;
        if (read_vuart_port(UART_DLBL) != dlbl)
            write_vuart_port(UART_DLBL, dlbl);
        dlbh = (u_int) divisor >> 8;
        if (read_vuart_port(UART_DLBH) != dlbh)
            write_vuart_port(UART_DLBH, dlbh);
    }

    write_vuart_port(UART_CFCR, com->cfcr_image = cfcr);

    if (!(tp->t_state & TS_TTSTOP))
        com->state |= CS_TTGO;

    if (cflag & CRTS_IFLOW) {
        com->state |= CS_RTS_IFLOW;
        /*
         * If CS_RTS_IFLOW just changed from off to on, the change
         * needs to be propagated to MCR_RTS.  This isn't urgent,
         * so do it later by calling comstart() instead of repeating
         * a lot of code from comstart() here.
         */
    } else if (com->state & CS_RTS_IFLOW) {
        com->state &= ~CS_RTS_IFLOW;
        /*
         * CS_RTS_IFLOW just changed from on to off.  Force MCR_RTS
         * on here, since comstart() won't do it later.
         */
        write_vuart_port(UART_MCR, com->mcr_image |= MCR_RTS);
    }


    /*
     * Set up state to handle output flow control.
     * XXX - worth handling MDMBUF (DCD) flow control at the lowest level?
     * Now has 10+ msec latency, while CTS flow has 50- usec latency.
     */
    com->state |= CS_ODEVREADY;
    com->state &= ~CS_CTS_OFLOW;
    if (cflag & CCTS_OFLOW) {
        com->state |= CS_CTS_OFLOW;
        if (!(com->last_modem_status & MSR_CTS))
            com->state &= ~CS_ODEVREADY;
    }

    write_vuart_port(UART_CFCR, com->cfcr_image);

    /* XXX shouldn't call functions while intrs are disabled. */
    disc_optim(tp, t, com);
    /*
     * Recover from fiddling with CS_TTGO.  We used to call siointr1()
     * unconditionally, but that defeated the careful discarding of
     * stale input in sioopen().
     */
    if (com->state >= (CS_BUSY | CS_TTGO))
        siointr1(com);

    lt_enable_intr();
    splx(s);
    comstart(tp);
    if (com->ibufold != NULL) {
        free(com->ibufold, M_DEVBUF);
        com->ibufold = NULL;
    }
    return (0);
}

static int
siosetwater(struct com_s *com, speed_t speed)
{
    int         cp4ticks;
    u_char      *ibuf;
    int         ibufsize;
    struct tty  *tp;

    /*
     * Make the buffer size large enough to handle a softtty interrupt
     * latency of about 2 ticks without loss of throughput or data
     * (about 3 ticks if input flow control is not used or not honoured,
     * but a bit less for CS5-CS7 modes).
     */
    cp4ticks = speed / 10 / hz * 4;
#if 0
    for (ibufsize = 128; ibufsize < cp4ticks;)
        ibufsize <<= 1;
#else
    ibufsize = 2048;   /* XXX -- fixed size  for slow interrupt */
#endif
    if (ibufsize == com->ibufsize) {
        lt_disable_intr();
        return (0);
    }

    /*
     * Allocate input buffer.  The extra factor of 2 in the size is
     * to allow for an error byte for each input byte.
     */
    ibuf = malloc(2 * ibufsize, M_DEVBUF, M_NOWAIT);
    if (ibuf == NULL) {
        lt_disable_intr();
        return (ENOMEM);
    }

    /* Initialize non-critical variables. */
    com->ibufold = com->ibuf;
    com->ibufsize = ibufsize;
    tp = com->tp;
    if (tp != NULL) {
        tp->t_ififosize = 2 * ibufsize;
        tp->t_ispeedwat = (speed_t)-1;
        tp->t_ospeedwat = (speed_t)-1;
    }

    /*
     * Read current input buffer, if any.  Continue with interrupts
     * disabled.
     */
    lt_disable_intr();
    if (com->iptr != com->ibuf)
        sioinput(com);

    /*-
     * Initialize critical variables, including input buffer watermarks.
     * The external device is asked to stop sending when the buffer
     * exactly reaches high water, or when the high level requests it.
     * The high level is notified immediately (rather than at a later
     * clock tick) when this watermark is reached.
     * The buffer size is chosen so the watermark should almost never
     * be reached.
     * The low watermark is invisibly 0 since the buffer is always
     * emptied all at once.
     */
    com->iptr = com->ibuf = ibuf;
    com->ibufend = ibuf + ibufsize;
    com->ierroff = ibufsize;
    com->ihighwater = ibuf + 3 * ibufsize / 4;
    return (0);
}

static void
comstart(struct tty *tp)
{
    struct com_s    *com;
    int     s;
    int     unit;

    unit = DEV_TO_UNIT(tp->t_dev);
    com = com_addr(unit);
    if (com == NULL)
        return;
    s = splfunc();
    lt_disable_intr();
    if (tp->t_state & TS_TTSTOP)
        com->state &= ~CS_TTGO;
    else
        com->state |= CS_TTGO;
    if (tp->t_state & TS_TBLOCK) {
        if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW)
            write_vuart_port(UART_MCR, com->mcr_image &= ~MCR_RTS);
    } else {
        if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater
            && com->state & CS_RTS_IFLOW)
            write_vuart_port(UART_MCR, com->mcr_image |= MCR_RTS);
    }
    lt_enable_intr();
    if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) {
        ttwwakeup(tp);
        splx(s);
        return;
    }
    if (tp->t_outq.c_cc != 0) {
        struct lbq  *qp;
        struct lbq  *next;

        if (!com->obufs[0].l_queued) {
            com->obufs[0].l_tail
                = com->obuf1 + q_to_b(&tp->t_outq, com->obuf1,
                          sizeof com->obuf1);
            com->obufs[0].l_next = NULL;
            com->obufs[0].l_queued = TRUE;
            lt_disable_intr();
            if (com->state & CS_BUSY) {
                qp = com->obufq.l_next;
                while ((next = qp->l_next) != NULL)
                    qp = next;
                qp->l_next = &com->obufs[0];
            } else {
                com->obufq.l_head = com->obufs[0].l_head;
                com->obufq.l_tail = com->obufs[0].l_tail;
                com->obufq.l_next = &com->obufs[0];
                com->state |= CS_BUSY;
            }
            lt_enable_intr();
        }
        if (tp->t_outq.c_cc != 0 && !com->obufs[1].l_queued) {
            com->obufs[1].l_tail
                = com->obuf2 + q_to_b(&tp->t_outq, com->obuf2,
                          sizeof com->obuf2);
            com->obufs[1].l_next = NULL;
            com->obufs[1].l_queued = TRUE;
            lt_disable_intr();
            if (com->state & CS_BUSY) {
                qp = com->obufq.l_next;
                while ((next = qp->l_next) != NULL)
                    qp = next;
                qp->l_next = &com->obufs[1];
            } else {
                com->obufq.l_head = com->obufs[1].l_head;
                com->obufq.l_tail = com->obufs[1].l_tail;
                com->obufq.l_next = &com->obufs[1];
                com->state |= CS_BUSY;
            }
            lt_enable_intr();
        }
        tp->t_state |= TS_BUSY;
    }
    lt_disable_intr();
    if (com->state >= (CS_BUSY | CS_TTGO))
        siointr1(com);  /* fake interrupt to start output */
    lt_enable_intr();
    ttwwakeup(tp);
    splx(s);
}

static void
comstop(struct tty *tp, int rw)
{
    struct com_s    *com;

    com = com_addr(DEV_TO_UNIT(tp->t_dev));
    if (com == NULL || com->gone)
        return;
    lt_disable_intr();
    if (rw & FWRITE) {
        if (com->hasfifo)
            write_vuart_port(UART_FIFO, FIFO_XMT_RST | com->fifo_image);
        com->obufs[0].l_queued = FALSE;
        com->obufs[1].l_queued = FALSE;
        if (com->state & CS_ODONE)
            com_events -= LOTS_OF_EVENTS;
        com->state &= ~(CS_ODONE | CS_BUSY);
        com->tp->t_state &= ~TS_BUSY;
    }
    if (rw & FREAD) {
        if (com->hasfifo)
            write_vuart_port(UART_FIFO, FIFO_RCV_RST | com->fifo_image);
        com_events -= (com->iptr - com->ibuf);
        com->iptr = com->ibuf;
    }
    lt_enable_intr();
    comstart(tp);
}

static int
commctl(struct com_s *com, int bits, int how)
{
    int mcr;
    int msr;

    if (how == DMGET) {
        bits = TIOCM_LE;    /* XXX - always enabled while open */
        mcr = com->mcr_image;
        if (mcr & MCR_DTR)
            bits |= TIOCM_DTR;
        if (mcr & MCR_RTS)
            bits |= TIOCM_RTS;
        msr = com->prev_modem_status;
        if (msr & MSR_CTS)
            bits |= TIOCM_CTS;
        if (msr & MSR_DCD)
            bits |= TIOCM_CD;
        if (msr & MSR_DSR)
            bits |= TIOCM_DSR;
        /*
         * XXX - MSR_RI is naturally volatile, and we make MSR_TERI
         * more volatile by reading the modem status a lot.  Perhaps
         * we should latch both bits until the status is read here.
         */
        if (msr & (MSR_RI | MSR_TERI))
            bits |= TIOCM_RI;
        return (bits);
    }
    mcr = 0;
    if (bits & TIOCM_DTR)
        mcr |= MCR_DTR;
    if (bits & TIOCM_RTS)
        mcr |= MCR_RTS;
    if (com->gone)
        return(0);
    lt_disable_intr();
    switch (how) {
    case DMSET:
        write_vuart_port(UART_MCR,
                         com->mcr_image = mcr | (com->mcr_image & MCR_IENABLE));
        break;
    case DMBIS:
        write_vuart_port(UART_MCR, com->mcr_image |= mcr);
        break;
    case DMBIC:
        write_vuart_port(UART_MCR, com->mcr_image &= ~mcr);
        break;
    }
    lt_enable_intr();
    return (0);
}

static void
siosettimeout(void)
{
    struct com_s    *com;
    bool_t      someopen;
    int         unit;

    /*
     * Set our timeout period to 1 second if no polled devices are open.
     * Otherwise set it to max(1/200, 1/hz).
     * Enable timeouts iff some device is open.
     */

    callout_stop(&sio_timeout_handle);
    sio_timeout = hz;
    someopen = FALSE;
    for (unit = 0; unit < sio_numunits; ++unit) {
        com = com_addr(unit);
        if (com != NULL && com->tp != NULL
            && com->tp->t_state & TS_ISOPEN && !com->gone) {
            someopen = TRUE;
        }
    }
    if (someopen) {
        sio_timeouts_until_log = hz / sio_timeout;
	callout_reset(&sio_timeout_handle,sio_timeout, comwakeup, NULL);
    } else {
        /* Flush error messages, if any. */
        sio_timeouts_until_log = 1;
        comwakeup((void *)NULL);
        callout_stop(&sio_timeout_handle);
    }
}

static void
comwakeup(void *chan)
{
    struct com_s    *com;
    int     unit;

    callout_reset(&sio_timeout_handle, sio_timeout, comwakeup, NULL);
    /*
     * Recover from lost output interrupts.
     * Poll any lines that don't use interrupts.
     */
    for (unit = 0; unit < sio_numunits; ++unit) {
        com = com_addr(unit);
        if (com != NULL && !com->gone
            && (com->state >= (CS_BUSY | CS_TTGO))) {
            lt_disable_intr();
            siointr1(com);
            lt_enable_intr();
        }
    }

    /*
     * Check for and log errors, but not too often.
     */
    if (--sio_timeouts_until_log > 0)
        return;
    sio_timeouts_until_log = hz / sio_timeout;
    for (unit = 0; unit < sio_numunits; ++unit) {
        int errnum;

        com = com_addr(unit);
        if (com == NULL)
            continue;
        if (com->gone)
            continue;
        for (errnum = 0; errnum < CE_NTYPES; ++errnum) {
            u_int   delta;
            u_long  total;

            lt_disable_intr();
            delta = com->delta_error_counts[errnum];
            com->delta_error_counts[errnum] = 0;
            lt_enable_intr();
            if (delta == 0)
                continue;
            total = com->error_counts[errnum] += delta;
            log(LOG_ERR, "ltmdm%d: %u more %s%s (total %lu)\n",
                unit, delta, error_desc[errnum],
                delta == 1 ? "" : "s", total);
        }
    }
}

static void
disc_optim(struct tty *tp, struct termios *t, struct com_s *com)
{
    if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON))
        && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK))
        && (!(t->c_iflag & PARMRK)
        || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK))
        && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN))
        && linesw[tp->t_line].l_rint == ttyinput)
            tp->t_state |= TS_CAN_BYPASS_L_RINT;
    else
        tp->t_state &= ~TS_CAN_BYPASS_L_RINT;
        com->hotchar = linesw[tp->t_line].l_hotchar;
}

#ifdef KLD_MODULE
static int
ltmdm_event(struct module *mod, int reason, void *dummy)
{
    switch (reason) {
    case MOD_LOAD:
        ltmdm_read_param();
        break;
    case MOD_UNLOAD:
        break;
    }
    return 0;
}
#else
#define ltmdm_event NULL
#endif

DRIVER_MODULE(ltmdm, pci, ltmdm_pci_driver, ltmdm_devclass, ltmdm_event, 0);




More information about the Submit mailing list