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,
<mdm_pci_vendor_id, 0, "");
SYSCTL_INT(_hw_ltmdm, OID_AUTO, pci_device_id, CTLFLAG_RD,
<mdm_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(<_add_timer_handle);
}
}
void lt_add_timer(void (*timerfunction)(unsigned long))
{
lt_timer_func = timerfunction;
callout_reset(<_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", <mdm_pci_vendor_id))
printf("hw.ltmdm.pci_vendor_id: %d\n", ltmdm_pci_vendor_id);
if (getenv_int("hw.ltmdm.pci_device_id", <mdm_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, <mdm_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, <mdm_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, <mdm_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, <mdm_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(<_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(<_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(<_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