OpenSS7
SS7 for the
Common Man

© Copyright 1997-2004,OpenSS7 Corporation, All Rights Reserved.
Last modified:

Home Overview Status News Documentation Resources About
   
 Overview
 Status
 News
 Documentation
 Resources
 About

   
Home Index Prev Next More Download Info FAQ Mail   Home -> Resources -> Browse Source -> strss7/drivers/acb56/sdl_acb56.c


File /code/strss7/drivers/acb56/sdl_acb56.c



#ident "@(#) $RCSfile: sdl_acb56.c,v $ $Name:  $($Revision: 0.8.2.5 $) $Date: 2003/04/14 12:12:13 $"

static char const ident[] =
    "$RCSfile: sdl_acb56.c,v $ $Name:  $($Revision: 0.8.2.5 $) $Date: 2003/04/14 12:12:13 $";

/*
 *  This is an implementation of the Signalling Data Link for the SeaLevel
 *  ACB56[tm] V.35 ISA card.  It provides the driver routines necessary for
 *  interface to the SDL driver kernel module.
 */

#include <linux/config.h>
#include <linux/version.h>
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/module.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/cmn_err.h>

#include <linux/errno.h>
#include <linux/types.h>

#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/dma.h>

#include "../lock.h"
#include "../debug.h"
#include "../bufq.h"

#include <ss7/lmi.h>
#include <ss7/lmi_ioctl.h>
#include <ss7/devi.h>
#include <ss7/devi_ioctl.h>
#include <ss7/sdli.h>
#include <ss7/sdli_ioctl.h>

#include "../lmi/lm.h"
#include "../devi/dev.h"
#include "../sdli/sdl.h"

#define ACB56_DESCRIP	"ACB56: SS7/SDL (Signalling Data Link) STREAMS DRIVER."
#define ACB56_COPYRIGHT	"Copyright (c) 1997-2002 OpenSS7 Corpoation.  All Rights Reserved."
#define ACB56_DEVICES	"Supports the SeaLevel ACB56(tm) V.35 boards."
#define ACB56_CONTACT	"Brian Bidulock <bidulock@openss7.org>"
#define ACB56_LICENSE	"GPL"
#define ACB56_BANNER	ACB56_DESCRIP   "\n" \
			ACB56_COPYRIGHT "\n" \
			ACB56_DEVICES   "\n" \
			ACB56_CONTACT   "\n"

#ifdef MODULE
MODULE_AUTHOR(ACB56_CONTACT);
MODULE_DESCRIPTION(ACB56_DESCRIP);
MODULE_SUPPORTED_DEVICE(ACB56_DEVICES);
#ifdef MODULE_LICENSE
MODULE_LICENSE(ACB56_LICENSE);
#endif
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifdef ACB56_DEBUG
int acb56_debug = ACB56_DEBUG;
#else
int acb56_debug = 2;
#endif

#define DEBUG_LEVEL acb56_debug

// #define ACB56_MOD_ID 0x1111
// #define ACB56_MOD_NAME "acb56"
// #define ACB56_CMAJOR 0 /* FIXME: pick something */
// #define ACB56_NMINOR 256 /* as many as possible */

#ifndef ACB56_CMAJOR
#define ACB56_CMAJOR  248	/* FIXME: pick something */
#endif
#define ACB56_NMINOR  5		/* as many as possible */

#ifndef LSSU_SIB
#define LSSU_SIB 0x5
#endif

static int debug = -1;
static int io[] = { -1, -1, -1, -1, -1 };
static int irq[] = { -1, -1, -1, -1, -1 };
static int dma_rx[] = { -1, -1, -1, -1, -1 };
static int dma_tx[] = { -1, -1, -1, -1, -1 };
static int mode[] = { -1, -1, -1, -1, -1 };
static int clock[] = { -1, -1, -1, -1, -1 };

MODULE_PARM(debug, "i");	/* debug flag */
MODULE_PARM(io, "1-5i");	/* i/o port for the i'th card */
MODULE_PARM(irq, "1-5i");	/* irq for the i'th card */
MODULE_PARM(dma_rx, "1-5i");	/* dma for the i'th card */
MODULE_PARM(dma_tx, "1-5i");	/* dma for the i'th card */
MODULE_PARM(mode, "i-5i");	/* interface mode */
MODULE_PARM(clock, "i-5i");	/* clock source */

enum { ACB56_MODE_DTE, ACB56_MODE_DCE, ACB56_MODE_LOOP,
	ACB56_MODE_JACK, ACB56_MODE_ECHO, ACB56_MODE_BOTH
};

static const int mode_map[] = {
	-1,				/* DEV_MODE_NONE */
	-1,				/* DEV_MODE_DSU */
	-1,				/* DEV_MODE_CSU */
	ACB56_MODE_DTE,			/* DEV_MODE_DTE */
	ACB56_MODE_DCE,			/* DEV_MODE_DCE */
	-1,				/* DEV_MODE_CLIENT */
	-1,				/* DEV_MODE_SERVER */
	-1,				/* DEV_MODE_PEER */
	ACB56_MODE_ECHO,		/* DEV_MODE_ECHO */
	-1,				/* DEV_MODE_REM_LB */
	ACB56_MODE_LOOP,		/* DEV_MODE_LOC_LB */
	ACB56_MODE_BOTH,		/* DEV_MODE_LB_ECHO */
	ACB56_MODE_JACK			/* DEV_MODE_TEST */
};

enum { ACB56_CLOCK_EXT, ACB56_CLOCK_INT, ACB56_CLOCK_DPLL };

static const int clock_map[] = {
	-1,				/* DEV_CLOCK_NONE */
	ACB56_CLOCK_INT,		/* DEV_CLOCK_INT */
	ACB56_CLOCK_EXT,		/* DEV_CLOCK_EXT */
	-1,				/* DEV_CLOCK_LOOP */
	-1,				/* DEV_CLOCK_MASTER */
	-1,				/* DEV_CLOCK_SLAVE */
	ACB56_CLOCK_DPLL,		/* DEV_CLOCK_DPLL */
	-1,				/* DEV_CLOCK_ABR */
	-1				/* DEV_CLOCK_SHAPER */
};

static int acb56_cmajor = ACB56_CMAJOR;

typedef struct acb56_stats {
	dev_ulong repeated_sus;
	dev_ulong compressed_sus;
	dev_ulong recv_fisus;
	dev_ulong recv_lssus;
	dev_ulong recv_msus;
	dev_ulong parity_errors;
	dev_ulong cts_transitions;
	dev_ulong dcd_transitions;
	dev_ulong cha_ext_status;
	dev_ulong cha_rx_char_avail;
	dev_ulong cha_rx_sp_cond;
	dev_ulong cha_tx_buf_empty;
	dev_ulong chb_ext_status;
	dev_ulong chb_rx_char_avail;
	dev_ulong chb_rx_sp_cond;
	dev_ulong chb_tx_buf_empty;
	dev_ulong interrupts;
} acb56_stats_t;

typedef struct acb56 {
	struct dev dev;			/* common device structure */
	int rx_octet_mode;		/* Is octet counting on? */
	int rx_octet_count;		/* bits (not octets) counted */
	unsigned char regs[16];		/* register images */
	unsigned char rr0;		/* register image reg 0 */
	mblk_t *tx_msg;			/* transmit buffer */
	mblk_t *rx_msg;			/* receive buffer */
	mblk_t *cp_msg;			/* compressed su buffer */
	acb56_stats_t stats;		/* device private stats */
	unsigned char *tx_buf;		/* current tx buffer pointer */
	unsigned char *tx_max;		/* end of tx buffer pointer */
	int tx_error;			/* transmission error */
	unsigned char *rx_buf;		/* current rx buffer pointer */
	unsigned char *rx_max;		/* end oft rx buffer pointer */
	int rx_error;			/* reception error */
	bufq_t tinputq;
} acb56_t;

/* 
 *  =========================================================================
 *
 *  BUFFER RESUPPLY
 *
 *  =========================================================================
 */

/* 
 *  It is important not to attempt to free or allocate message blocks within
 *  the Interrupt Service Routine.  The purpose of these functions is to
 *  recognize when the receive buffer supply queue is running too low and
 *  perform a resupply of the queue by allocating buffers.  We also detect
 *  when the buffer supply queue is running too high and free buffers from the
 *  queue.  We use a single resupply queue for all of the ACB56 devices served
 *  by this driver.
 */

#define ACB56_SUPQ_MAXLEN   16

#ifndef SIF_MAX
#define SIF_MAX 272
#endif

static bufq_t acb56_supplyq;
static bufq_t acb56_returnq;

static void acb56_resupply(void *unused)
{
	int i;
	mblk_t *mp;

	(void) unused;
	for (i = bufq_length(&acb56_returnq); i > 0; i--) {
		if ((mp = bufq_dequeue(&acb56_returnq)))
			freemsg(mp);
	}
	for (i = bufq_length(&acb56_supplyq); i < ACB56_SUPQ_MAXLEN; i++) {
		if ((mp = allocb(SIF_MAX + 4, BPRI_HI))) {
			mp->b_datap->db_type = M_DATA;
			bufq_queue(&acb56_supplyq, mp);
		}
	}
}

static struct tq_struct acb56_resupply_tasq = {
	{NULL, NULL},		/* list */
	0,			/* sync */
	(void *) acb56_resupply,	/* routine */
	NULL			/* data */
};

static inline void acb56_do_resupply(void)
{
	queue_task(&acb56_resupply_tasq, &tq_immediate);
	mark_bh(IMMEDIATE_BH);
}

/* 
 *  =========================================================================
 *
 *  INTERRUPT SERVICE ROUTINES
 *
 *  =========================================================================
 */

static inline void acb56_tx_setup_next_frame(acb56_t * p)
{
	p->dev.module->stats.tx_sus++;
	p->dev.module->stats.tx_bytes += 4;
	if (bufq_length(&p->tinputq)) {
		acb56_do_resupply();	/* mark BH for resupply */
		bufq_queue(&acb56_returnq, p->tx_msg);
		p->tx_msg = bufq_dequeue(&p->tinputq);
	} else {
		int len = msgdsize(p->tx_msg);
		if (len > 5 || (len > 3 && p->tx_msg->b_rptr[3] == LSSU_SIB)) {	/* its an MSU or
										   SIB, make FISU
										   out of it */
			p->tx_msg->b_wptr = p->tx_msg->b_rptr + 3;
			p->tx_msg->b_rptr[2] = 0;
			p->stats.repeated_sus++;
		}
	}
	p->tx_buf = p->tx_msg->b_rptr;
	p->tx_max = p->tx_msg->b_wptr;
}

static inline void acb56_tx_underrun_eom(acb56_t * p)
{
	if (p->rr0 & 0x40) {	/* set */
		p->tx_buf = p->tx_msg->b_rptr;
		p->tx_error = 1;
		p->dev.module->stats.tx_aborts++;
		p->dev.module->stats.tx_underruns++;
		p->dev.module->stats.tx_sus_in_error++;
		p->dev.module->stats.tx_bytes += 3;
		p->rr0 &= ~0x04;	/* clear indication */
		return;
	} else {
		return acb56_tx_setup_next_frame(p);
	}
}

static inline void acb56_sync_hunt(acb56_t * p)
{
	int actrl = p->dev.iface.iobase + 1;
	if (p->rx_octet_mode && !p->rr0 & 0x10) {
		p->rx_octet_mode = 0;	/* we synced */
		outb(0x0f, actrl);
		outb(p->regs[0x0f] &= ~0x02, actrl);	/* turn of octet mode */
	} else if ((p->rr0 & 0x10) && (p->dev.iface.ifclock != DEV_CLOCK_DPLL)) {
		p->rx_octet_count = 0;
		p->rx_octet_mode = 1;
		outb(0x0f, actrl);
		outb(p->regs[0x0f] |= 0x02, actrl);	/* turn on octet mode */
	}
	p->dev.module->stats.rx_sync_transitions++;
}
static inline void acb56_break_abort(acb56_t * p)
{
	p->dev.module->stats.rx_aborts++;
}
static inline void acb56_dcd(acb56_t * p)
{
	if ((++p->stats.dcd_transitions) & 0x1)
		p->dev.module->stats.lead_dcd_lost++;
}
static inline void acb56_cts(acb56_t * p)
{
	if ((++p->stats.cts_transitions) & 0x1)
		p->dev.module->stats.lead_cts_lost++;
}
static inline void acb56_zero_count(acb56_t * p)
{
	if (!p->rx_octet_count++ & 0x0000007f || (p->dev.iface.ifmode == DEV_MODE_DTE)) {
		p->dev.module->stats.rx_sus_in_error++;
		p->dev.ucalls->daedr_N_octets(&p->dev);
	}
	p->dev.module->stats.rx_bits_octet_counted++;
}

static inline void acb56_frame_error(acb56_t * p)
{
	p->rx_error = 0;
	p->rx_buf = p->rx_msg->b_rptr;
	if (p->rx_octet_mode)
		return;		/* only a good frame counts in octet mode */
	p->cp_msg->b_wptr = p->cp_msg->b_rptr;
	p->dev.module->stats.rx_sus++;
	p->dev.module->stats.rx_bytes += 2;
	p->dev.module->stats.rx_sus_in_error++;
	p->dev.ucalls->daedr_error_frame(&p->dev);
}
static inline void acb56_parity_error(acb56_t * p)
{
	p->stats.parity_errors++;
	p->rx_error = 1;
}
static inline void acb56_rx_overrun(acb56_t * p)
{
	p->dev.module->stats.rx_overruns++;
	p->rx_error = 1;
}
static inline void acb56_crc_error(acb56_t * p)
{
	if (p->rx_octet_mode)
		return;		/* only a good frame counts in octet mode */
	p->dev.module->stats.rx_crc_errors++;
	acb56_frame_error(p);
}
static inline void acb56_buffer_error(acb56_t * p)
{
	if (p->rx_octet_mode)
		return;		/* only a good frame counts in octet mode */
	p->dev.module->stats.rx_buffer_overflows++;
	acb56_frame_error(p);
}
static inline void acb56_short_error(acb56_t * p)
{
	if (p->rx_octet_mode)
		return;		/* only a good frame counts in octet mode */
	p->dev.module->stats.rx_frame_too_short++;
	acb56_frame_error(p);
}
static inline void acb56_long_error(acb56_t * p)
{
	if (p->rx_octet_mode)
		return;		/* only a good frame counts in octet mode */
	p->dev.module->stats.rx_frame_too_long++;
	acb56_frame_error(p);
}
static inline void acb56_length_error(acb56_t * p)
{
	if (p->rx_octet_mode)
		return;		/* only a good frame counts in octet mode */
	p->dev.module->stats.rx_length_error++;
	acb56_frame_error(p);
}
static inline void acb56_rx_setup_next_frame(acb56_t * p)
{
	int len = p->rx_buf - p->rx_msg->b_rptr;
	p->rx_buf = p->rx_msg->b_rptr;
	p->dev.module->stats.rx_sus++;
	if (len == 3)
		p->stats.recv_fisus++;
	if (len >= 6)
		p->stats.recv_msus++;
	if (len >= 4)
		p->stats.recv_lssus++;
	p->dev.module->stats.rx_bytes += 2;
}
static inline void acb56_residue_error(acb56_t * p)
{
	p->dev.module->stats.rx_residue_errors++;
	acb56_frame_error(p);
}
static inline void acb56_end_of_frame(acb56_t * p)
{
	if (p->rx_error)
		return acb56_frame_error(p);
	{
		mblk_t *mp = p->rx_msg, *mc = p->cp_msg;
		unsigned int len = (unsigned int) (p->rx_buf - mp->b_rptr);
		unsigned char li = mp->b_rptr[2] & 0x3f;
		if (len < 3)
			return acb56_short_error(p);
		if (len > p->dev.module->config.m + 4)
			return acb56_long_error(p);
		if (li != (len > 63 + 3 ? 63 + 3 : len))
			return acb56_length_error(p);
		mp->b_wptr = mp->b_rptr + len;
		if (len < 6) {
			int clen = mc->b_wptr - mc->b_rptr;
			if (len == clen && !memcmp(mc->b_rptr, mp->b_rptr, len)) {
				p->stats.compressed_sus++;
				acb56_rx_setup_next_frame(p);
				p->dev.ucalls->daedr_compr_frame(&p->dev, 1);
				return;
			} else {
				memcpy(mc->b_rptr, mp->b_rptr, len);
				mc->b_wptr = mc->b_rptr + len;
			}
		} else
			mc->b_wptr = mc->b_rptr;
		p->rx_octet_mode = 0;
		acb56_rx_setup_next_frame(p);
		p->dev.ucalls->daedr_recvd_frame(&p->dev, mp);
		acb56_do_resupply();	/* mark BH for resupply */
		if (!(p->rx_msg = bufq_dequeue(&acb56_supplyq)) &&
		    !(p->rx_msg = bufq_dequeue(&acb56_returnq)))
			return acb56_buffer_error(p);
		p->rx_buf = p->rx_msg->b_wptr = p->rx_msg->b_rptr;
		p->rx_max = p->rx_msg->b_datap->db_lim;
		return;
	}
}
static inline void acb56_frame_overflow_check(acb56_t * p)
{
	int actrl = p->dev.iface.iobase + 1;
	/* check for frame overflow */
	if (p->rx_buf > p->rx_max) {
		/* FIGURE 11/Q.703 (sheet 1 of 2) "m+7 octets without flags" */
		if (!p->rx_octet_mode && p->dev.iface.ifclock != DEV_CLOCK_DPLL) {	/* can't
											   octet
											   count on 
											   DPLL! */
			p->rx_octet_count = 0;
			p->rx_octet_mode = 1;
			outb(0x0f, actrl);
			outb(p->regs[0x0f] |= 0x02, actrl);	/* octet counting isr */
			outb(0x03, actrl);
			outb(p->regs[0x03] |= 0x10, actrl);	/* force flag hunt */
			acb56_rx_setup_next_frame(p);
			p->dev.ucalls->daedr_loss_of_sync(&p->dev);
		}
	}
}

static void acb56_isr_cha_tx_buf_empty(acb56_t * p)
{
	p->stats.cha_tx_buf_empty++;
}

static void acb56_isr_cha_ext_status(acb56_t * p)
{
	unsigned char rr0;
	register int actrl = p->dev.iface.iobase + 1;

	rr0 = p->rr0 & ~0x02;
	outb(0x00, actrl);
	p->rr0 = inb(actrl);
	outb(0x00, actrl);
	outb(0x10, actrl);
	outb(0x00, actrl);
	outb(0x10, actrl);	/* debounce */
	rr0 ^= p->rr0 & 0xfa;
	if (rr0) {
		if (rr0 & 0x40)
			acb56_tx_underrun_eom(p);
		if (rr0 & 0x10)
			acb56_sync_hunt(p);
		if (rr0 & 0x80)
			acb56_break_abort(p);
		if (rr0 & 0x08)
			acb56_dcd(p);
		if (rr0 & 0x20)
			acb56_cts(p);
	} else
		acb56_zero_count(p);
	p->stats.cha_ext_status++;
}

static void acb56_isr_cha_rx_char_avail(acb56_t * p)
{
	register int adata = p->dev.iface.iobase;
	register int i = 0;
	if (p->rx_buf) {
		/* collect bytes */
		for (i = 0; i < 4; i++)
			*(p->rx_buf++) = inb(adata);
		acb56_frame_overflow_check(p);
	}
	p->dev.module->stats.rx_bytes += 4;
	p->stats.cha_rx_char_avail++;

}
static void acb56_isr_cha_rx_sp_cond(acb56_t * p)
{
	unsigned char rr1 = 0;
	register int adata = p->dev.iface.iobase;
	register int actrl = p->dev.iface.iobase + 1;
	register int i = 0;
	p->stats.cha_rx_sp_cond++;
	/* collect bytes */
	outb(0x00, actrl);
	for (i = 0; i < 4 && (inb(actrl) & 0x01); i++) {
		*(p->rx_buf++) = inb(adata);
		p->dev.module->stats.rx_bytes++;
		outb(0x00, actrl);
	}
	acb56_frame_overflow_check(p);
	/* check for special conditions */
	outb(0x07, actrl);
	if (inb(actrl) & 0x40) {
		outb(0x01, actrl);
		if ((rr1 = inb(actrl)) & 0xf0) {
			outb(0x00, actrl);
			outb(0x30, actrl);	/* reset error */
			if (rr1 & 0x10) {
				return acb56_parity_error(p);
			}
			if (rr1 & 0x20) {
				return acb56_rx_overrun(p);
			}
			if (rr1 & 0x80) {
				if (rr1 & 0x40) {
					return acb56_crc_error(p);
				} else {
					if ((rr1 & 0xe) ^ 0x6) {
						return acb56_residue_error(p);
					} else {
						return acb56_end_of_frame(p);
					}
				}
			}
		}
	}
}

static void acb56_isr_donothing(acb56_t * p)
{
	(void) p;
};

static void acb56_isr_chb_tx_buf_empty(acb56_t * p)
{
	p->stats.chb_tx_buf_empty++;
}
static void acb56_isr_chb_ext_status(acb56_t * p)
{
	p->stats.chb_ext_status++;
}
static void acb56_isr_chb_rx_char_avail(acb56_t * p)
{
	p->stats.chb_rx_char_avail++;
}
static void acb56_isr_chb_rx_sp_cond(acb56_t * p)
{
	p->stats.chb_rx_sp_cond++;
}

static void acb56_isr(int irq, void *dev_id, struct pt_regs *regs)
{
	static void (*vector_map[]) (acb56_t *) = {
	acb56_isr_chb_tx_buf_empty,
		    acb56_isr_chb_ext_status,
		    acb56_isr_chb_rx_char_avail,
		    acb56_isr_chb_rx_sp_cond,
		    acb56_isr_cha_tx_buf_empty,
		    acb56_isr_cha_ext_status, acb56_isr_cha_rx_char_avail,
		    acb56_isr_cha_rx_sp_cond};
	unsigned char rr3;
	register int i;
	register int actrl = ((acb56_t *) dev_id)->dev.iface.iobase + 1;
	for (i = 0, outb(0x03, actrl), rr3 = inb(actrl); i < 4 && rr3;
	     i++, outb(0x03, actrl), rr3 = inb(actrl)) {
		outb(0x02, actrl + 2);
		(*vector_map[inb(actrl + 2) >> 1]) (dev_id);
		/* reset highest interrupt under service */
		outb(0x00, actrl);
		outb(0x38, actrl);
	}
	((acb56_t *) dev_id)->stats.interrupts++;
};

/* 
 *  =========================================================================
 *
 *  DRIVER SERVICE CALLS
 *
 *  =========================================================================
 */

static dev_device_t dev_acbdev_default = {
	SPIN_LOCK_UNLOCKED,		/* iflock */
	0,				/* ifflags */
	DEV_TYPE_V35,			/* iftype */
	DEV_GTYPE_NONE,			/* ifgtype */
	DEV_MODE_DTE,			/* ifmode */
	56000,				/* ifrate */
	DEV_CLOCK_DPLL,			/* ifclock */
	DEV_CODING_NRZI,		/* ifcoding */
	0,				/* ifleads */
	0,				/* ifindex */
	0,				/* irq */
	0,				/* dma_rx */
	0				/* dma_tx */
};

static const unsigned char preamble[] = {
	0x09, 0xC0, 0x0F, 0x01, 0x07, 0x6B, 0x0F, 0x00, 0x00, 0x00, 0x04, 0x20,
	0x01, 0x00, 0x02, 0x00, 0x03, 0xCA, 0x05, 0x63, 0x06, 0x00, 0x07, 0x7e,
	0x09, 0x00, 0x0A, 0x00, 0x0B, 0x16, 0x0C, 0x40, 0x0D, 0x00, 0x0E, 0x02,
	0x0E, 0x02, 0x0E, 0x02, 0x0E, 0x03, 0x03, 0xCB, 0x05, 0x6B, 0x00, 0x80,
	0x00, 0x30, 0x01, 0x00, 0x0F, 0x00, 0x00, 0x10, 0x00, 0x10, 0x01, 0x00,
	0x09, 0x00
};
static const unsigned char preset[] = {
	0x09, 0xc0, 0x00, 0x00, 0x04, 0x20, 0x03, 0xca, 0x05, 0xe3, 0x07, 0x7e,
	0x06, 0x00, 0x0F, 0x01, 0x07, 0x6b, 0x0F, 0x00, 0x01, 0x00, 0x02, 0x00,
	0x09, 0x00, 0x0A, 0x80
};
static const unsigned char mode_clock[6][3] = {
	{0x08, 0x05, 0x7f}, {0x08, 0x56, 0x7f}, {0x50, 0x50, 0x78},
	{0x16, 0x50, 0x1f}, {0x50, 0x50, 0x78}, {0x50, 0x50, 0x78}
};

static unsigned char irqprobe[] = {
	0x01, 0x19, 0x0F, 0xFA, 0x00, 0x10, 0x00, 0x10, 0x09, 0x08, 0x0E, 0x03
};

static void dummy_isr(int irq, void *dev_id, struct pt_regs *regs)
{
	volatile int *p;
	(void) irq;
	(void) dev_id;
	(void) regs;
	p = NULL;
	p++;
}

static int board = 0;
static int ports[] = { 0x238, 0x280, 0x2A0, 0x300, 0x328, 0 };

/* 
 *  DEVICE-ATTACH: This is the device attach.  It should probe for the device
 *  as specified by the minor device number and determine whether the device
 *  is there and all of the resources for the device can be acquired.  All
 *  resources associated with the device (except the allocated structure,
 *  which is freed by the caller) should be freed at the end of this routine
 *  so that the device may be used by some other module if required.
 */
static lmi_t *acb56_devatt(dev_t dev)
{
	int iobase, _irq, actrl, _dma_rx, _dma_tx, i, err;
	unsigned long time, cookie;
	acb56_t *p = NULL;

	board = getminor(dev) - 1;

	if ((iobase = io[board]) == -1)
		iobase = ports[board];
	if ((err = check_region(iobase, 8)))
		return NULL;
	actrl = iobase + 1;
	outb(0x02, actrl);
	outb(0x55, actrl);	/* write to unallocated 8530 bit in WR2 */
	outb(0x02, actrl);
	if (inb(actrl) != 0x55)
		return NULL;	/* probably an 8530 */
	outb(0x09, actrl);
	outb(0xc0, actrl);	/* force hardware reset */
	outb(0x0f, actrl);
	outb(0x01, actrl);	/* Access W7P register */
	outb(0x0f, actrl);
	if (!inb(actrl) & 0x01)
		return NULL;	/* probably an 8530 */
	outb(0x0f, actrl);
	outb(0x00, actrl);	/* Remove accss to W7P register */
	outb(0x0f, actrl);
	if (inb(actrl) & 0x01)
		return NULL;	/* probably an 8530 */
	/* check assigned irq */
	if ((_irq = irq[board]) != -1) {
		if ((err = request_irq(_irq, dummy_isr, SA_SHIRQ, "acb56_dummy", NULL)))
			return NULL;
		else
			goto acb_probe_dma;
	}
	for (i = 0; i < sizeof(preamble);) {	/* setup chip */
		outb(preamble[i], actrl);
		i++;
		outb(preamble[i], actrl);
		i++;
	}
	cookie = probe_irq_on();
	for (i = 0; i < sizeof(irqprobe);) {	/* setup for guaranteed interrupt */
		outb(irqprobe[i], actrl);
		i++;
		outb(irqprobe[i], actrl);
		i++;
	}
	/* fill tx fifo to get an interrupt */
	outb(0x55, iobase);
	outb(0x55, iobase);
	outb(0x55, iobase);
	outb(0x55, iobase);
	outb(0x55, iobase);
	outb(0x55, iobase);
	outb(0x55, iobase);
	outb(0x55, iobase);
	outb(0x55, iobase);
	for (time = jiffies; jiffies - time < 100; i++);
	if (!(_irq = probe_irq_off(cookie)))
		return NULL;	/* no irq! */
	outb(0x03, actrl);
	if (!inb(actrl))
		return NULL;	/* it wasn't us */
	outb(0x09, actrl);
	outb(0x00, actrl);
	outb(0x09, actrl);
	outb(0xc0, actrl);	/* force hardware reset */
	if ((err = request_irq(_irq, dummy_isr, SA_SHIRQ, "acb56_dummy", NULL)))
		return NULL;
      acb_probe_dma:
	free_irq(_irq, NULL);
	/* check for dma */
	if ((_dma_rx = dma_rx[board]) && _dma_rx != -1 && !(request_dma(_dma_rx, "acb56_probe")))
		free_dma(_dma_rx);
	else
		_dma_rx = 0;
	if ((_dma_tx = dma_tx[board]) && _dma_tx != -1 && !(request_dma(_dma_tx, "acb56_probe")))
		free_dma(_dma_tx);
	else
		_dma_tx = 0;

	if (!(p = kmalloc(sizeof(*p), GFP_KERNEL)))
		return NULL;
	bzero(p, sizeof(*p));
	bcopy(&dev_acbdev_default, &p->dev.iface, sizeof(p->dev.iface));

	p->dev.iface.ifindex = board;
	p->dev.iface.irq = _irq;
	p->dev.iface.iobase = iobase;
	p->dev.iface.dma_tx = _dma_tx;
	p->dev.iface.dma_rx = _dma_rx;

	if (mode[board] != -1)
		p->dev.iface.ifmode = mode[board];
	if (clock[board] != -1)
		p->dev.iface.ifclock = clock[board];

	ptrace(("sucessful:\n"));
	ptrace(("  ifindex = %lu\n", p->dev.iface.ifindex));
	ptrace(("  irq     = %lu\n", p->dev.iface.irq));
	ptrace(("  iobase  = 0x%lx\n", p->dev.iface.iobase));
	ptrace(("  dma_tx  = %lu\n", p->dev.iface.dma_tx));
	ptrace(("  dma_rx  = %lu\n", p->dev.iface.dma_rx));

	return ((lmi_t *) p);
}

/* 
 *  DEVICE-OPEN: Using the information contained in the device structure
 *  created at device attach, this function should reacquire all of the
 *  resources associated with the device (e.g., irqs).  The device is left in
 *  the quiescent state.
 */
static int acb56_open(lmi_t * lmi)
{
	acb56_t *p = (acb56_t *) lmi;
	dev_device_t *dev = &p->dev.iface;
	int err = 0;
	unsigned long flags;

	spin_lock_irqsave(&dev->iflock, flags);
	MOD_INC_USE_COUNT;
	/* get io region */
	if ((err = check_region(dev->iobase, 8))) {
		MOD_DEC_USE_COUNT;
		spin_unlock_irqrestore(&dev->iflock, flags);
		return err;
	}
	request_region(dev->iobase, 8, "acb56");
	/* get dma channels */
	if (dev->dma_rx && request_dma(dev->dma_rx, "acb56"))
		dev->dma_rx = 0;
	if (dev->dma_tx && request_dma(dev->dma_tx, "acb56"))
		dev->dma_tx = 0;
	/* get interrupt */
	if ((err = request_irq(dev->irq, acb56_isr, SA_SHIRQ, "acb56", p))) {
		if (dev->dma_rx)
			free_dma(dev->dma_rx);
		if (dev->dma_tx)
			free_dma(dev->dma_tx);
		MOD_DEC_USE_COUNT;
		spin_unlock_irqrestore(&dev->iflock, flags);
		return err;
	}
	spin_unlock_irqrestore(&dev->iflock, flags);
	return (0);
}

/* 
 *  DEVICE-CLOSE: Using the information contained in the device structure
 *  created at device attach, this function should deallocate all of the
 *  driver resources associated with the device with the exception of the
 *  device structure itself.  The device will be in the quiescent state when
 *  the procedure is called.
 */
static int acb56_close(lmi_t * lmi)
{
	acb56_t *p = (acb56_t *) lmi;
	dev_device_t *dev = &p->dev.iface;
	unsigned long flags;

	spin_lock_irqsave(&dev->iflock, flags);
	free_irq(dev->irq, p);
	if (dev->dma_tx)
		free_dma(dev->dma_tx);
	if (dev->dma_rx)
		free_dma(dev->dma_rx);
	release_region(dev->iobase, 8);

	MOD_DEC_USE_COUNT;
	spin_unlock_irqrestore(&dev->iflock, flags);
	return (0);
}

/* 
 *  INFO: This is for Style 2 device drivers for returning PPAs.  Since this
 *  is only a single-port card, the Signalling Data Link is determined at open
 *  time and we simply return a zero-length PPA.
 */
static int acb56_info(lmi_t * lmi, void **ppap, int *lenp)
{
	acb56_t *p = (acb56_t *) lmi;
	dev_device_t *dev = &p->dev.iface;
	unsigned long flags;

	(void) dev;
	spin_lock_irqsave(&dev->iflock, flags);
	*lenp = 0;
	*ppap = NULL;
	spin_unlock_irqrestore(&dev->iflock, flags);
	return (0);
}

/* 
 *  ATTACH: This is for Style 2 device drivers.  Since this is only a
 *  single-port card, the Signalling Data Link is determined at open time and
 *  we simply agree with any attach command.
 */
static int acb56_attach(lmi_t * lmi, void *ppa, int len)
{
	acb56_t *p = (acb56_t *) lmi;
	dev_device_t *dev = &p->dev.iface;
	unsigned long flags;
	(void) dev;
	(void) ppa;
	(void) len;
	spin_lock_irqsave(&dev->iflock, flags);
	spin_unlock_irqrestore(&dev->iflock, flags);
	return (0);
}

/* 
 *  DETACH: This is for Style 2 device drivers.  Since this is only a
 *  single-port card, the Signalling Data LInk was determined at open time and
 *  cannot be detached, so, we simply agree with any detach command.
 */
static int acb56_detach(lmi_t * lmi)
{
	acb56_t *p = (acb56_t *) lmi;
	dev_device_t *dev = &p->dev.iface;
	unsigned long flags;
	(void) dev;
	spin_lock_irqsave(&dev->iflock, flags);
	spin_unlock_irqrestore(&dev->iflock, flags);
	return (0);
}

#ifndef abs
#define abs(x) ((x)<0 ? -(x):(x))
#endif

/* 
 *  ENABLE: This should set up the hardware for the device using the resources
 *  which were reserved during the open.  The device should be activated and
 *  made ready for operation.  Interrupt service routines should be enabled.
 */
static int acb56_enable(lmi_t * lmi)
{
	acb56_t *p = (acb56_t *) lmi;
	dev_device_t *dev = &p->dev.iface;
	int i, actrl;
	unsigned long flags;

	spin_lock_irqsave(&dev->iflock, flags);

	actrl = dev->iobase + 1;
	for (i = 0; i < 16; i++)
		p->regs[i] = 0;	/* register images */
	/* setup chip */
	for (i = 0; i < sizeof(preset);) {
		outb(preset[i], actrl);
		i++;
		outb(p->regs[i >> 1] = preset[i], actrl);
		i++;
	}
	/* setup interface and clock modes */
	outb(0x0b, actrl);
	outb(p->regs[0x0b] = mode_clock[mode_map[dev->ifmode]][clock_map[dev->ifclock]], actrl);
	/* setup baud rate generator */
	if (dev->ifmode == DEV_MODE_DTE) {
		outb(0x0c, actrl);
		outb(p->regs[0xc] = 0xca, actrl);
		outb(0x0d, actrl);
		outb(p->regs[0xd] = 0x1c, actrl);
	} else if (dev->ifmode == DEV_MODE_LOC_LB) {
		outb(0x0c, actrl);
		outb(p->regs[0xc] = 0x00, actrl);
		outb(0x0d, actrl);
		outb(p->regs[0xd] = 0x00, actrl);
	} else {
		outb(0x0c, actrl);
		outb(p->regs[0xc] = 0x40, actrl);
		outb(0x0d, actrl);
		outb(p->regs[0xd] = 0x00, actrl);
	}
	/* special DPLL modes */
	if (dev->ifclock == DEV_CLOCK_DPLL) {
		outb(0x0e, actrl);
		outb(0x60, actrl);
		outb(0x0e, actrl);
		outb(0xe0, actrl);
		if (dev->ifmode == DEV_MODE_DTE) {
			outb(0x0e, actrl);
			outb(0xa0, actrl);
		} else {
			outb(0x0e, actrl);
			outb(0x80, actrl);
		}
		outb(0x0e, actrl);
		outb(0x20, actrl);
	}
	outb(0x0e, actrl);
	outb(p->regs[0x0e] = 0x02, actrl);
	/* setup loopback and echo modes */
	switch (dev->ifmode) {
	case DEV_MODE_LOC_LB:
		outb(0x0e, actrl);
		outb(p->regs[0x0e] |= 0x10, actrl);
		break;
	case DEV_MODE_ECHO:
		outb(0x0e, actrl);
		outb(p->regs[0x0e] |= 0x08, actrl);
		break;
	case DEV_MODE_LB_ECHO:
		outb(0x0e, actrl);
		outb(p->regs[0x0e] |= 0x18, actrl);
		break;
	}
	/* set up dma registers */
	if (dev->dma_rx || dev->dma_tx) {
		outb(0x0e, actrl);
		outb(p->regs[0x0e] |= 0x04, actrl);
		if (dev->dma_rx && dev->dma_tx) {
			outb(0x01, actrl);
			outb(p->regs[0x01] |= 0xf9, actrl);
		}
		if (dev->dma_tx) {
			outb(0x01, actrl);
			outb(p->regs[0x01] |= 0xc1, actrl);
		}
		if (dev->dma_tx) {
			outb(0x01, actrl);
			outb(p->regs[0x01] |= 0xfb, actrl);
		}
		outb(0x80, actrl + 3);
		if (dev->dma_rx)
			enable_dma(dev->dma_rx);
		if (dev->dma_tx)
			enable_dma(dev->dma_tx);
	} else {
		outb(0x0e, actrl);
		outb(p->regs[0x0e] &= ~0x04, actrl);
		outb(0x01, actrl);
		outb(p->regs[0x01] |= 0x13, actrl);
		outb(0x00, actrl + 3);
	}
	/* disable status fifo */
	outb(0x0f, actrl);
	outb(0xfc, actrl);
	outb(0x09, actrl);
	outb(0x02, actrl);
	/* reset and enable transmitters and receivers */
	outb(0x0E, actrl);
	outb(p->regs[0x0e] |= 0x01, actrl);
	outb(0x03, actrl);
	outb(p->regs[0x0e] |= 0x01, actrl);
	outb(0x00, actrl);
	outb(0x30, actrl);
	outb(0x05, actrl);
	outb(p->regs[0x0e] |= 0x08, actrl);
	outb(0x00, actrl);
	outb(0x80, actrl);
	outb(0x00, actrl);
	outb(0xC0, actrl);
	outb(0x00, actrl);
	outb(0x10, actrl);
	outb(0x00, actrl);
	outb(0x10, actrl);

	dev->ifflags |= DEV_IF_TX_RUNNING;
	dev->ifflags |= DEV_IF_RX_RUNNING;

	bufq_init(&p->tinputq);

	acb56_resupply(NULL);
	p->tx_msg = bufq_dequeue(&acb56_supplyq);
	acb56_resupply(NULL);
	p->rx_msg = bufq_dequeue(&acb56_supplyq);
	acb56_resupply(NULL);
	p->cp_msg = bufq_dequeue(&acb56_supplyq);
	acb56_resupply(NULL);

	if (!p->tx_msg || !p->rx_msg || !p->cp_msg) {
		if (p->tx_msg)
			freemsg(p->tx_msg);
		if (p->rx_msg)
			freemsg(p->rx_msg);
		if (p->cp_msg)
			freemsg(p->cp_msg);
		spin_unlock_irqrestore(&dev->iflock, flags);
		return ENOBUFS;
	}

	*(p->tx_msg->b_wptr)++ = 0x80;	/* Initial SIOS */
	*(p->tx_msg->b_wptr)++ = 0x80;	/* Initial SIOS */
	*(p->tx_msg->b_wptr)++ = 0x01;	/* Initial SIOS */
	*(p->tx_msg->b_wptr)++ = 0x00;	/* Initial SIOS */

	/* enable master interrupt bit */
	outb(0x09, actrl);
	outb(p->regs[0x09] |= 0x08, actrl);

	/* we're running! phew! */
	spin_unlock_irqrestore(&dev->iflock, flags);
	return (0);
}

/* 
 *  DISABLE: This should shut down the hardware and deactivate ISRs and other
 *  routines, flush buffers to the point that kernel resource which were
 *  allocated can be deallocated in the close.
 */
static int acb56_disable(lmi_t * lmi)
{
	acb56_t *p = (acb56_t *) lmi;
	dev_device_t *dev = &p->dev.iface;
	int actrl = dev->iobase + 1;
	unsigned long flags;

	spin_lock_irqsave(&dev->iflock, flags);

	dev->ifflags &= ~DEV_IF_TX_RUNNING;
	dev->ifflags &= ~DEV_IF_RX_RUNNING;
	dev->ifflags &= ~DEV_IF_SU_COMPRESS;

	outb(0x09, actrl);
	outb(0xc0, actrl);	/* force hw reset */
	outb(0x09, actrl);
	outb(p->regs[0x09] &= ~0x08, actrl);	/* stop interrupts */
	if (dev->dma_tx) {
		outb(0x0e, actrl);
		outb(p->regs[0x0e] &= ~0x04, actrl);	/* disable dma */
		disable_dma(dev->dma_tx);
	}
	if (dev->dma_rx) {
		outb(0x01, actrl);
		outb(p->regs[0x01] &= ~0xc0, actrl);	/* disable dma */
		disable_dma(dev->dma_rx);
	}
	outb(0x09, actrl);
	outb(0xc0, actrl);	/* force hw reset */

	bufq_purge(&p->tinputq);

	spin_unlock_irqrestore(&dev->iflock, flags);
	return (0);
}

/* 
 *  IOCTLS: These are ioctls which are general for SDL and which are specific
 *  to device interfaces and which are privdte for the ACB56 drivers.  They
 *  are invoked at the stream head.
 */
static int acb56_ioctl(lmi_t * sdl, int cmd, void *arg)
{
	size_t size = _IOC_SIZE(cmd);
	acb56_t *p = (acb56_t *) sdl->device;
	dev_device_t *dev = &p->dev.iface;
	sdl_config_t *ureq = NULL;
	sdl_ulong uarg = 0;

	if (_IOC_TYPE(cmd) == SDL_IOC_MAGIC)
		switch (cmd) {
		case SDL_IOCTCONFIG:
		case SDL_IOCSCONFIG:
			if (!arg || size < sizeof(dev_device_t))
				return EINVAL;
			ureq = arg;
			break;
		case SDL_IOCCCONFIG:
		case SDL_IOCCMRESET:
			break;
		default:
			return EOPNOTSUPP;
		}
	if (_IOC_TYPE(cmd) == DEV_IOC_MAGIC)
		switch (cmd) {
		case DEV_IOCGIFFLAGS:
		case DEV_IOCGIFTYPE:
		case DEV_IOCGGRPTYPE:
		case DEV_IOCGIFMODE:
		case DEV_IOCGIFRATE:
		case DEV_IOCGIFCLOCK:
		case DEV_IOCGIFCODING:
		case DEV_IOCGIFLEADS:
		case DEV_IOCSIFFLAGS:
		case DEV_IOCSIFTYPE:
		case DEV_IOCSGRPTYPE:
		case DEV_IOCSIFMODE:
		case DEV_IOCSIFRATE:
		case DEV_IOCSIFCLOCK:
		case DEV_IOCSIFCODING:
		case DEV_IOCSIFLEADS:
		case DEV_IOCCIFLEADS:
			if (!arg || size < sizeof(dev_ulong))
				return EINVAL;
			uarg = *(dev_ulong *) arg;
			break;
		case DEV_IOCCIFRESET:
		case DEV_IOCCDISCTX:
		case DEV_IOCCCONNTX:
			break;
		default:
			return EOPNOTSUPP;
		}
	if (_IOC_TYPE(cmd) == SDL_IOC_MAGIC)
		switch (cmd) {
		case SDL_IOCTCONFIG:
		case SDL_IOCSCONFIG:
		case SDL_IOCCCONFIG:
		case SDL_IOCCMRESET:
			break;
		default:
			return EOPNOTSUPP;
		}
	if (_IOC_TYPE(cmd) == DEV_IOC_MAGIC)
		switch (cmd) {
		case DEV_IOCCIFRESET:
			break;
			/* gets */
		case DEV_IOCGIFFLAGS:
			*(dev_ulong *) arg = dev->ifflags;
			return (0);
		case DEV_IOCGIFTYPE:
			*(dev_ulong *) arg = dev->iftype;
			return (0);
		case DEV_IOCGGRPTYPE:
			*(dev_ulong *) arg = dev->ifgtype;
			return (0);
		case DEV_IOCGIFMODE:
			*(dev_ulong *) arg = dev->ifmode;
			return (0);
		case DEV_IOCGIFRATE:
			*(dev_ulong *) arg = dev->ifrate;
			return (0);
		case DEV_IOCGIFCLOCK:
			*(dev_ulong *) arg = dev->ifclock;
			return (0);
		case DEV_IOCGIFCODING:
			*(dev_ulong *) arg = dev->ifcoding;
			return (0);
		case DEV_IOCGIFLEADS:
			*(dev_ulong *) arg = dev->ifleads;
			return (0);
			/* sets */
		case DEV_IOCSIFFLAGS:
			if (sdl->state == LMI_ENABLED)
				return EBUSY;
			dev->ifflags = uarg;
			return (0);
		case DEV_IOCSIFTYPE:
			if (sdl->state == LMI_ENABLED)
				return EBUSY;
			if (uarg != DEV_TYPE_V35)
				return EINVAL;
			return (0);
		case DEV_IOCSGRPTYPE:
			if (sdl->state == LMI_ENABLED)
				return EBUSY;
			if (uarg != DEV_GTYPE_NONE)
				return EINVAL;
			return (0);
		case DEV_IOCSIFMODE:
			ptrace(("uarg = %lu\n", uarg));
			if (sdl->state == LMI_ENABLED)
				return EBUSY;
			switch (uarg) {
			case DEV_MODE_DTE:
			case DEV_MODE_DCE:
			case DEV_MODE_REM_LB:
			case DEV_MODE_LOC_LB:
			case DEV_MODE_LB_ECHO:
			case DEV_MODE_TEST:
				if (mode_map[uarg] != -1) {
					dev->ifmode = uarg;
					return (0);
				}
			default:
				return EINVAL;
			}
			return (0);
		case DEV_IOCSIFRATE:
			if (sdl->state == LMI_ENABLED)
				return EBUSY;
			if (uarg != 56000)
				return EINVAL;
			return (0);
		case DEV_IOCSIFCLOCK:
			ptrace(("uarg = %lu\n", uarg));
			if (sdl->state == LMI_ENABLED)
				return EBUSY;
			switch (uarg) {
			case DEV_CLOCK_INT:
			case DEV_CLOCK_EXT:
			case DEV_CLOCK_DPLL:
				if (clock_map[uarg] != -1) {
					dev->ifclock = uarg;
					return (0);
				}
			default:
				return EINVAL;
			}
			return (0);
		case DEV_IOCSIFCODING:
			if (sdl->state == LMI_ENABLED)
				return EBUSY;
			if (uarg != DEV_CODING_NRZI)
				return EINVAL;
			return (0);
		case DEV_IOCSIFLEADS:
		case DEV_IOCCIFLEADS:
			/* FIXME: control the leads */
			if (uarg)
				return EINVAL;
			return (0);
		case DEV_IOCCDISCTX:
			dev->ifflags &= ~DEV_IF_TX_RUNNING;
			return (0);
		case DEV_IOCCCONNTX:
			dev->ifflags |= DEV_IF_TX_RUNNING;
			return (0);
		}
	return EOPNOTSUPP;
}

static struct lmi_ops acb56_lmi_ops = {
	{
	 acb56_devatt,		/* dev.attach */
	 acb56_open,		/* dev.open */
	 acb56_close,		/* dev.close */
	 },
	{
	 acb56_info,		/* lmi.info */
	 acb56_attach,		/* lmi.attach */
	 acb56_detach,		/* lmi.detach */
	 acb56_enable,		/* lmi.enable */
	 acb56_disable,		/* lmi.disable */
	 acb56_ioctl		/* lmi.ioctl */
	 }
};

/* 
 *  =========================================================================
 *
 *  SERVICE ROUTINES
 *
 *  =========================================================================
 */

static void acb56_xmit(struct dev *dev, mblk_t * mp)
{
	acb56_t *p = (acb56_t *) dev;
	unsigned long flags;

	spin_lock_irqsave(&dev->iface.iflock, flags);
	if (!(dev->iface.ifflags & DEV_IF_TX_RUNNING))
		freemsg(mp);
	else
		bufq_queue(&p->tinputq, mp);
	spin_unlock_irqrestore(&dev->iface.iflock, flags);
}

static void acb56_tx_start(struct dev *dev)
{
	dev->iface.ifflags |= DEV_IF_TX_RUNNING;
}

static void acb56_rx_start(struct dev *dev)
{
	dev->iface.ifflags |= DEV_IF_RX_RUNNING;
}

static dev_dcalls_t acb56_dcalls = {
	acb56_xmit,			/* daedt_xmit */
	acb56_tx_start,			/* daedt_start */
	acb56_rx_start			/* daedr_start */
};

/* 
 *  =======================================================================
 *
 *  LiS Module Initialization   (For registered driver.)
 *
 *  =======================================================================
 */

static int acb56_initialized = 0;

void acb56_init(void)
{
	if (acb56_initialized > 0)
		return;
	printk(KERN_INFO ACB56_BANNER);	/* console splash */
	ptrace(("registering sdl_acb: cmajor %d, nminors %d\n", ACB56_CMAJOR, ACB56_NMINOR));
	acb56_initialized =
	    sdl_register_driver(ACB56_CMAJOR, ACB56_NMINOR, "sdl_acb", &acb56_lmi_ops,
				&acb56_dcalls);
	ptrace(("return (device major) = %d\n", acb56_initialized));
	if (acb56_initialized > 0)
		acb56_cmajor = acb56_initialized;
	if (acb56_initialized == 0)
		acb56_initialized = acb56_cmajor;
	if (debug != -1)
		acb56_debug = debug;
	bufq_init(&acb56_supplyq);
	bufq_init(&acb56_returnq);
}

void acb56_terminate(void)
{
	if (acb56_initialized <= 0)
		return;
	ptrace(("unregistering sdl_acb cmajor %d\n", acb56_cmajor));
	acb56_initialized = sdl_unregister_driver(acb56_cmajor);
	ptrace(("return = %d\n", acb56_initialized));
	bufq_purge(&acb56_supplyq);
	bufq_purge(&acb56_returnq);
}

/* 
 *  =======================================================================
 *
 *  Kernel Module Initialization   (For unregistered driver.)
 *
 *  =======================================================================
 */

#ifdef MODULE

int init_module(void)
{
	(void) acb56_debug;
	acb56_init();
	if (acb56_initialized < 0)
		return acb56_initialized;
	return (0);
}

void cleanup_module(void)
{
	acb56_terminate();
}

#endif


Home Index Prev Next More Download Info FAQ Mail   Home -> Resources -> Browse Source -> strss7/drivers/acb56/sdl_acb56.c

OpenSS7
SS7 for the
Common Man
Home Overview Status News Documentation Resources About

© Copyright 1997-2004,OpenSS7 Corporation, All Rights Reserved.
Last modified: