freebsd-nq/sys/i386/isa/b004.c
1996-09-06 23:09:20 +00:00

668 lines
17 KiB
C

/*
* FreeBSD device driver for B004-compatible Transputer boards.
*
* based on Linux version Copyright (C) 1993 by Christoph Niemann
*
* Rewritten for FreeBSD by
* Luigi Rizzo (luigi@iet.unipi.it) and
* Lorenzo Vicisano (l.vicisano@iet.unipi.it)
* Dipartimento di Ingegneria dell'Informazione
* Universita` di Pisa
* via Diotisalvi 2, 56126 Pisa, ITALY
* 14 september 1994
*
* 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 Christoph Niemann,
* Luigi Rizzo and Lorenzo Vicisano - Dipartimento di Ingegneria
* dell'Informazione
* 4. The names of these contributors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE 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 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.
*
* NOTE NOTE NOTE
* The assembler version is still under development.
*/
/* #define USE_ASM */
#include "bqu.h"
#if NBQU > 0
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/uio.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#ifdef DEVFS
#include <sys/devfsext.h>
#endif /*DEVFS*/
#include <machine/clock.h>
#include <i386/isa/b004.h>
#include <i386/isa/isa_device.h>
#define IOCTL_OUT(arg, ret) *(int*)arg = ret
#define B004PRI (PZERO+8)
#define B004_CHANCE 8
/*
* Define these symbols if you want to debug the code.
*/
#undef B004_DEBUG
#undef B004_DEBUG_2
#ifdef B004_DEBUG
static u_char d_inb(u_int port);
static void d_outb(u_int port, u_char data);
#define out(port,data) d_outb(port, data)
#define in(a) d_inb(((u_int)a))
#else
#define out(port, data) outb(port,data)
#define in(port) inb(((u_int)port))
#endif B004_DEBUG
#ifdef B004_DEBUG
#define DEB(x) x
#define NO_DEB(x) /* */
#else
#define DEB(x) /* */
#define NO_DEB(x) x
#endif
#ifdef B004_DEBUG_2
#define DEB2(x) x
#else
#define DEB2(x)
#endif
static int bquprobe(struct isa_device *idp);
static int bquattach(struct isa_device *idp);
struct isa_driver bqudriver = {
bquprobe, bquattach, "bqu"
};
static d_open_t bquopen;
static d_close_t bquclose;
static d_read_t bquread;
static d_write_t bquwrite;
static d_ioctl_t bquioctl;
static d_select_t bquselect;
#define CDEV_MAJOR 8
static struct cdevsw bqu_cdevsw =
{ bquopen, bquclose, bquread, bquwrite, /*8*/
bquioctl, nostop, nullreset, nodevtotty,/* tputer */
bquselect, nommap, NULL, "bqu", NULL, -1 };
static int b004_sleep; /* wait address */
static struct b004_struct b004_table[NBQU];
static int first_time=1;
/*
* At these addresses the driver will search for B004-compatible boards
*/
static int
b004_base_addresses[B004_CHANCE] = {
/* 0x150, 0x170, 0x190, 0x200, 0x300, 0x320, 0x340, 0x360 */
0x150, 0x190, 0, 0, 0, 0, 0, 0
};
#ifdef B004_DEBUG
static void
d_outb(u_int port, u_char data)
{
printf("OUT 0x%x TO 0x%x\n",data,port);
outb(port,data);
}
static u_char
d_inb(u_int port)
{
u_char ap;
ap=inb(port);
printf("INPUT 0x%x FROM 0x%x\n",ap,port);
return(ap);
}
#endif
static int
detected(int base)
{
int i;
for(i=0;i<NBQU;i++)
if ((B004_F(i) & B004_EXIST) && (B004_BASE(i)==base)) return 1;
return (0);
}
#define b004_delay(a) DELAY(10000)
/*
* static void bqureset(): reset transputer network.
*
*/
static void
bqureset( const int dev_min )
{
DEB(printf("B004 resetting transputer at link %d.\n", dev_min);)
out(B004_BASE(dev_min)+B004_ANALYSE_OFFSET, B004_DEASSERT_ANALYSE);
b004_delay(dev_min);
out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_DEASSERT_RESET);
b004_delay(dev_min);
out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_ASSERT_RESET);
b004_delay(dev_min);
out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_DEASSERT_RESET);
b004_delay(dev_min);
DEB(printf("B004 reset done.\n");)
}
/*
* static void bquanalyse(): switch transputer network to analyse mode.
*
*/
static void
bquanalyse( const int dev_min )
{
DEB(printf("B004 analysing transputer at link %d.\n", dev_min);)
out(B004_BASE(dev_min) + B004_ANALYSE_OFFSET, B004_DEASSERT_ANALYSE);
b004_delay(dev_min);
out(B004_BASE(dev_min) + B004_ANALYSE_OFFSET, B004_ASSERT_ANALYSE);
b004_delay(dev_min);
out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_ASSERT_RESET);
b004_delay(dev_min);
out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_DEASSERT_RESET);
b004_delay(dev_min);
out(B004_BASE(dev_min) + B004_ANALYSE_OFFSET, B004_DEASSERT_ANALYSE);
b004_delay(dev_min);
DEB(printf("B004 switching to analyse-mode done.\n");)
}
/****************************************************************************
*
* int bquread() - read bytes from the link interface.
*
* At first, the driver checks if the link-interface is ready to send a byte
* to the PC. If not, this check is repeated up to B004_MAXTRY times.
* If the link-interface is not ready after this loop, the driver sleeps for
* an NO=1 ticks and then checks the link-interface again.
* If the interface is still not ready, repeats as above incrementing NO.
* Once almost one byte is read N0 is set to 1.
* If B004_TIMEOUT != 0 and the link-interface is not ready for more than
* B004_TIMEOUT ticks read aborts returnig with the number of bytes read
* or with an error if no byte was read.
*
* By default, B004_TIMEOUT is = 0 (read is blocking)
*
*****************************************************************************/
static int
bquread(dev_t dev, struct uio *uio, int flag)
{
unsigned int dev_min = minor(dev) & 7;
int timeout=B004_TIMEOUT(dev_min);
int Timeout=timeout;
int idr=B004_IDR(dev_min);
int isr=B004_ISR(dev_min);
char buffer[B004_MAX_BYTES];
if ( uio->uio_resid < 0) {
DEB(printf("B004: invalid count for reading = %d.\n", uio->uio_resid);)
return EINVAL;
}
while ( uio->uio_resid ) {
int sleep_ticks=0;
char *p, *last, *lim;
int i, end = min(B004_MAX_BYTES,uio->uio_resid);
lim= &buffer[end];
for (p= buffer; p<lim;) {
last=p;
/*** try to read as much as possible ***/
#ifdef USE_ASM
/* assembly code uses a very tight loop, with
* BX= data port, DX= address port, CX=count, ES:DI=p, AL=data, AH=1
* SI=retry counter
*/
__asm__ (
"movl %1, %%edx\n\t" /* isr */
"movl %2, %%ebx\n\t" /* idr */
"movl %3, %%edi\n" /* p */
"movl %4, %%ecx\n\t" /* lim */
"subl %%edi, %%ecx\n\t"
"push %%es\n\t"
"movw %%ss, %%ax\n\t" /** prepare ES, DF for transfer */
"movw %%ax, %%es\n\t"
"cld\n\t"
"movb $1, %%ah\n\t"
"1:\tinb %%dx, %%al\n\t"
"testb %%ah, %%al\n\t"
"jz 2f\n\t"
"xchgl %%edx, %%ebx\n\t"
"insb\n\t"
"xchgl %%edx, %%ebx\n"
"2:\tloop 1b\n\t"
"pop %%es\n\t"
"movl %%edi, %0\n\t" /* store p */
: /* out */ "=g" (p)
: /* in */ "g" (isr), "g" (idr), "g" (p), "g" (lim)
: /* regs */ "eax", "ebx", "edx", "ecx", "edi");
#else
for (i=lim - p; i-- ;)
if (inb(isr)&B004_READBYTE) *p++ =(char) inb(idr);
#endif
if (last!=p) {
sleep_ticks = 0;
} else {
/*** no new data read, must sleep ***/
sleep_ticks= (sleep_ticks<20 ? sleep_ticks+1 : sleep_ticks);
if (Timeout) {
if (timeout <=0) {
DEB2(printf("Read : TIMEOUT OCCURRED XXXXXXXXXXX\n");)
break;
}
if (timeout < sleep_ticks) sleep_ticks=timeout;
timeout -= sleep_ticks;
}
DEB2(printf("Read: SLEEPING FOR %d TICKS XXXXX\n",sleep_ticks);)
if (tsleep((caddr_t)&b004_sleep, B004PRI | PCATCH,
"b004_rd", sleep_ticks)!=EWOULDBLOCK) return 1;
}
}
if (p != buffer) {
uiomove((caddr_t)buffer, p - buffer, uio);
}
if( (Timeout) && (timeout <= 0) )
break;
}
return 0;
} /* bquread() */
/*
* int bquwrite() - write to the link interface.
*/
static int
bquwrite(dev_t dev, struct uio *uio, int flag)
{
unsigned int dev_min = minor(dev) & 7;
int i, end;
int timeout=B004_TIMEOUT(dev_min);
int Timeout=timeout;
int odr=B004_ODR(dev_min);
int osr=B004_OSR(dev_min);
char buffer[B004_MAX_BYTES];
if ( uio->uio_resid < 0) {
DEB(printf("B004 invalid argument for writing: count = %d.\n", uio->uio_resid);)
return EINVAL;
}
while ( uio->uio_resid ) {
int sleep_ticks=0;
char *p, *last, *lim;
end = min(B004_MAX_BYTES,uio->uio_resid);
uiomove((caddr_t)buffer, end, uio);
lim= &buffer[end];
for (p= &buffer[0]; p<lim;) {
last=p;
#ifdef USE_ASM
/* assembly code uses a very tight loop, with
* BX= data port, DX= address port, CX=count, DS:SI=p, AL=data, AH=1
* DI= retry counter
* Unfortunately, C is almost as fast as this!
*/
__asm__ (
"movl %1, %%edx\n\t" /* osr */
"movl %2, %%ebx\n\t" /* odr */
"movl %3, %%esi\n" /* p */
"movl %4, %%ecx\n\t" /* lim */
"subl %%esi, %%ecx\n\t"
"push %%ds\n\t"
"movw %%ss, %%ax\n\t" /** prepare DS, DF for transfer */
"movw %%ax, %%ds\n\t"
"cld\n\t"
"movb $1, %%ah\n\t"
"movw $100, %%di\n\t"
"1:\tinb %%dx, %%al\n\t"
"testb %%ah, %%al\n\t"
"jz 2f\n\t"
"xchgl %%edx, %%ebx\n\t"
"outsb\n\t"
"xchgl %%edx, %%ebx\n\t"
"loop 1b\n\t"
"jmp 3f\n"
"2:\tdec %%di\n\t"
"jnc 1b\n\t"
"3:\tpop %%ds\n"
"movl %%esi, %0\n\t" /* store p */
: /* out */ "=g" (p)
: /* in */ "g" (osr), "g" (odr), "g" (p), "g" (lim)
: /* regs */ "eax", "ebx", "edx", "ecx", "esi", "edi");
#else
for (i=lim - p; i-- ; ) {
if (inb(osr)&B004_WRITEBYTE) outb(odr, *p++);
}
#endif
if (p != last ) {
sleep_ticks=0;
} else {
sleep_ticks= (sleep_ticks<20 ? sleep_ticks+1 : sleep_ticks);
if (Timeout) {
if (timeout <=0) {
DEB2(printf("Write : TIMEOUT OCCURRED XXXXXXXXXXX\n");)
uio->uio_resid += (lim - p);
break;
}
if (timeout < sleep_ticks) sleep_ticks=timeout;
timeout -= sleep_ticks;
}
DEB2(printf("Write: SLEEPING FOR %d TICKS XXXXXXX\n",sleep_ticks);)
if (tsleep((caddr_t)&b004_sleep, B004PRI | PCATCH,
"b004_rd", sleep_ticks)!=EWOULDBLOCK) return 1;
}
}
if( (Timeout) && (timeout <= 0) )
break;
}
return 0;
} /* bquwrite() */
/*
* int bquopen() -- open the link-device.
*
*/
static int
bquopen(dev_t dev, int flags, int fmt, struct proc *p)
{
unsigned int dev_min = minor(dev) & 7;
if (dev_min >= NBQU) {
DEB(printf("B004 not opened, minor number >= %d.\n", NBQU);)
return ENXIO;
}
if ((B004_F(dev_min) & B004_EXIST) == 0) {
DEB(printf("B004 not opened, board %d does not exist.\n", dev_min);)
return ENXIO;
}
if (B004_F(dev_min) & B004_BUSY) {
DEB(printf("B004 not opened, board busy (minor = %d).\n", dev_min);)
return EBUSY;
}
B004_F(dev_min) |= B004_BUSY;
B004_TIMEOUT(dev_min) = 0;
DEB(printf( "B004 opened, minor = %d.\n", dev_min );)
return 0;
} /* bquopen() */
/*
* int b004close() -- close the link device.
*/
static int
bquclose(dev_t dev, int flags, int fmt, struct proc *p)
{
unsigned int dev_min = minor(dev) & 7;
if (dev_min >= NBQU) {
DEB(printf("B004 not released, minor number >= %d.\n", NBQU);)
return ENXIO;
}
B004_F(dev_min) &= ~B004_BUSY;
DEB(printf("B004(%d) released.\n", dev_min );)
return 0;
}
static int
bquselect(dev_t dev, int rw, struct proc *p)
{
/* still unimplemented */
return(1);
}
/*
* int bquioctl()
*
* Supported functions:
* - reset
* - analyse
* - test error flag
* - set timeout
*/
static int
bquioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p)
{
unsigned int dev_min = minor(dev) & 7;
int result = 0;
if (dev_min >= NBQU) {
DEB(printf("B004 ioctl exit, minor >= %d.\n", NBQU );)
return ENODEV;
}
if ((B004_F(dev_min) & B004_EXIST) == 0) {
DEB(printf("B004 ioctl exit, (B004_F & B004_EXIST) == 0.\n" );)
return ENODEV;
}
switch ( cmd ) {
case B004RESET: /* reset transputer */
bqureset(dev_min);
DEB(printf("B004 ioctl B004RESET, done\n" );)
break;
case B004WRITEABLE: /* can we write a byte to the C012 ? */
IOCTL_OUT (addr, ((in(B004_OSR(dev_min))&B004_WRITEBYTE) != 0 ));
break;
case B004READABLE: /* can we read a byte from C012 ? */
IOCTL_OUT (addr, ((in(B004_ISR(dev_min)) & B004_READBYTE) != 0 ));
break;
case B004ANALYSE: /* switch transputer to analyse mode */
bquanalyse(dev_min);
break;
case B004ERROR: /* test error-flag */
IOCTL_OUT (addr,
((inb(B004_BASE(dev_min)+B004_ERROR_OFFSET) &
B004_TEST_ERROR) ? 0 : 1));
break;
case B004TIMEOUT: /* set, retrieve timeout for writing & reading*/
B004_TIMEOUT(dev_min) = *((int *)addr);
break;
default: result = EINVAL;
}
return result;
} /* bquioctl() */
static int
bquattach(struct isa_device *idp)
{
int unit = idp->id_unit;
struct b004_struct *bp;
int i;
#ifdef DEVFS
#define BQU_UID 66
#define BQU_GID 66
#define BQU_PERM 0600
bp = &b004_table[unit];
for ( i = 0; i < 8; i++) {
#ifdef NOTYET
/* if (we've done all the ports found) break; */
#endif
bp->devfs_token[i][0]=
devfs_add_devswf(&bqu_cdevsw, i, DV_CHR, BQU_UID,
BQU_GID, BQU_PERM, "ttyba%d", i);
bp->devfs_token[i][0]=
devfs_add_devswf(&bqu_cdevsw, i+64, DV_CHR, BQU_UID,
BQU_GID, BQU_PERM, "ttybb%d", i);
bp->devfs_token[i][0]=
devfs_add_devswf(&bqu_cdevsw, i+128, DV_CHR, BQU_UID,
BQU_GID, BQU_PERM, "ttybc%d", i);
bp->devfs_token[i][0]=
devfs_add_devswf(&bqu_cdevsw, i+192, DV_CHR, BQU_UID,
BQU_GID, BQU_PERM, "ttybd%d", unit);
}
#endif
return 1;
}
/*
* int bquprobe
*
* Initializes the driver. It tries to detect the hardware
* and sets up all relevant data-structures.
*/
static int
bquprobe(struct isa_device *idp)
{
unsigned int test;
unsigned int dev_min = idp->id_unit;
int i,found = 0;
/* After a reset it should be possible to write a byte to
the B004. So let'S do a reset and then test the output status
register
*/
#ifdef undef
printf(
"bquprobe::\nIOBASE 0x%x\nIRQ %d\nDRQ %d\nMSIZE %d\nUNIT %d\nFLAGS"
"x0%x\nALIVE %d\n",idp->id_iobase,idp->id_irq,
idp->id_drq,idp->id_msize,idp->id_unit,idp->id_flags,idp->id_alive);
#endif
if(first_time){
for(i=0;i<NBQU;i++) B004_F(i) &= ~B004_EXIST;
first_time=0;
}
if(dev_min >= NBQU) return (0); /* No more descriptors */
if ((idp->id_iobase < 0x100) || (idp->id_iobase >= 0x1000))
idp->id_iobase=0; /* Dangerous isa addres ) */
for (test = 0; (test < B004_CHANCE); test++) {
if((idp->id_iobase==0)&&((!b004_base_addresses[test])||
detected(b004_base_addresses[test])))
continue;
idp->id_iobase=b004_base_addresses[test];
DEB(printf("Probing device %d at address 0x%x\n",dev_min,
idp->id_iobase);
)
b004_delay(test);
B004_F(dev_min) = 0;
B004_TIMEOUT(dev_min) = B004_INIT_TIMEOUT;
B004_BASE(dev_min) = idp->id_iobase;
B004_ODR(dev_min) = B004_BASE(dev_min) + B004_ODR_OFFSET;
B004_ISR(dev_min) = B004_BASE(dev_min) + B004_ISR_OFFSET;
B004_OSR(dev_min) = B004_BASE(dev_min) + B004_OSR_OFFSET;
bqureset(dev_min);
for (i = 0; i < B004_MAXTRY; i++)
if ( in(B004_OSR(dev_min)) == B004_WRITEBYTE) {
B004_F(dev_min) |= B004_EXIST;
out(B004_BASE(dev_min) + B008_INT_OFFSET, 0);
b004_delay(test);
if (in(B004_BASE(dev_min) + B008_INT_OFFSET) & 0x0f == 0)
B004_BOARDTYPE(dev_min) = B008;
else
B004_BOARDTYPE(dev_min) = B004;
printf("bqu%d at 0x0%x (polling) is a B00%s\n",
dev_min,B004_IDR(dev_min),
(B004_BOARDTYPE(dev_min) == B004) ? "4" : "8");
found = 1;
break;
}
if(!found) {
idp->id_iobase=0;
}
else break;
}
if (!found){
DEB(printf("b004probe(): no B004-board found.\n"));
return (0);
}
idp->id_maddr=NULL;
idp->id_irq=0;
if(B004_BOARDTYPE(dev_min) == B004)
return(18);
else
return(20);
} /* bquprobe() */
static bqu_devsw_installed = 0;
static void
bqu_drvinit(void *unused)
{
dev_t dev;
if( ! bqu_devsw_installed ) {
dev = makedev(CDEV_MAJOR, 0);
cdevsw_add(&dev,&bqu_cdevsw, NULL);
bqu_devsw_installed = 1;
}
}
SYSINIT(bqudev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,bqu_drvinit,NULL)
#endif /* NBQU */