*BSD News Article 86765


Return to BSD News archive

#! rnews 19424 bsd
Path: euryale.cc.adfa.oz.au!newshost.carno.net.au!harbinger.cc.monash.edu.au!news.mira.net.au!news.netspace.net.au!news.mel.connect.com.au!munnari.OZ.AU!news.ecn.uoknor.edu!feed1.news.erols.com!howland.erols.net!newsxfer3.itd.umich.edu!su-news-hub1.bbnplanet.com!news.bbnplanet.com!cpk-news-hub1.bbnplanet.com!mindspring!psinntp!psinntp!pubxfer.news.psi.net!usenet
From: Luoqi Chen <luoqi@watermarkgroup.com>
Newsgroups: comp.unix.bsd.freebsd.misc
Subject: Re: Parallel Port ZIP Drive
Date: Wed, 22 Jan 1997 19:50:13 -0500
Organization: The Watermark Group
Lines: 748
Message-ID: <32E6B5BC.2278@watermarkgroup.com>
References: <5c5bhn$3kv@news.fe.up.pt>
NNTP-Posting-Host: 38.246.139.33
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="------------48026FF63BDE"
X-Mailer: Mozilla 3.01Gold (Macintosh; I; PPC)
To: Jorge Goncalves <mec204@crazy.fe.up.pt>
Xref: euryale.cc.adfa.oz.au comp.unix.bsd.freebsd.misc:34037

This is a multi-part message in MIME format.

--------------48026FF63BDE
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Jorge Goncalves wrote:
> 
> Is there support for this kind of device in FreeBSD?
> 
> Thanks.
> 
> --
> 
> Jorge Goncalves
> mec204@crazy.fe.up.pt
Sometimes ago there was a posting about this. I didn't save the article,
but I downloaded the driver source code it mentioned. I haven't tried it
myself,
but it has a very detailed description at the top.

-lq

---------------------- ppa3.c ------------------

--------------48026FF63BDE
Content-Type: text/plain; charset=us-ascii; x-mac-type="54455854"; x-mac-creator="74747874"; name="ppa3.c"
Content-Transfer-Encoding: 7bit
Content-Description: SimpleText Document
Content-Disposition: inline; filename="ppa3.c"

/*
 * /sys/i386/isa/ppa3.c
 *
 *   FreeBSD R2.1.0 driver for PPA3 adapter
 *   embedded in the IOMEGA ZIP 100 drive.
 *   -------------------------------------------------------------------
 *
 *   Release: 0.10
 *   Release: 0.11
 *	- *int32 becomes *int32_t according to 
 *		Subject: Installing parallel port IOMega ZIP drive?
 *		Author:  Nathan Melhorn at TELEBIT_CHELMSFORD_NFS
 *		Date:    96.07.10 18:48
 *	  form the freebsd-questions mailing-list
 *
 *   Release: 0.20
 *	- Interrupt emulation with timeout().
 *
 *   THIS SOFTWARE IS DISTRIBUTED WITH NO WARRANTY.
 *   -------------------------------------------------------------------
 *
 *   The Iomega Zip 100 drive is an external drive with removable 100Mb
 *   disks. It is connected either to the parallel port (with an 
 *   embedded SCSI-2 adapter) or to a SCSI-2 adapter. An IDE version
 *   is also available.
 *
 * | See http://www.iomega.com for details.
 * 
 *   This driver has been developed and tested for FreeBSD R2.1.0 with:
 *	- an AMD486-DX120 processor
 *	- no other SCSI hardware
 *
 *   Add the following lines in your kernel configuration files:
 *   -------------------------------------------------------------------
 *
 *   in /sys/i386/conf/files.i386,
 *
 *   i386/isa/ppa3.c optional ppa device-driver
 *   -------------------------------------------------------------------
 *
 *   in /sys/i386/conf/MACHINE,
 *
 *   controller      scbus0  #base SCSI code
 *   device          sd0     #SCSI disks
 *
 *   controller      isa0
 *   device          ppa0    at isa? port 0x278 bio
 *   -------------------------------------------------------------------
 *
 * | The SCSI_DELAY may not be a good option. The driver was developed
 * | without it.
 *
 * | The base address of your parallel port (here 0x278) may be different.
 * | Printer services are not supported by ppa3.c ; your parallel port 
 * | should not be shared with the lpt driver. Thus you have to disable
 * | the printer driver either in your MACHINE config file or at boot 
 * | with the -c option (see boot(8) manpage). If you have more than one
 * | printer port, configure the base addresses as needed.
 *
 *   (see /sys/i386/conf/LINT and config(8) manpage for details)
 *
 *   You may have to increase timeouts, PPA_MAX_RETRY or PPA_SPEED_LOW.
 *
 *   At startup, the size of the disk is probed and the driver 
 *   should report 196608 512 bytes blocks.
 *   If the size reported is 0 the drive won't be usable, you have to 
 * | reboot to get the right size. Actually, the scsi user interface
 * | should allow to re-probe the drive...
 *   Toplevel scsi driver will report "using ficticious geometry..." 
 *   because it can't sense the drive geometry. This is not a problem.
 *
 * | The probe may take a while... the target of the drive is 6 on the
 * | ZIP scsi bus. Thus 6 devices are probed before the good one.
 *
 *   Use fdisk to be sure the DOS partition of the disk is the 4th one.
 *   Then, mount -t msdos /dev/sd0s4 /mnt, for exemple.
 *
 *   About benchs...
 * | Transfer rate is around 15kB/s.
 *   Writing directly do /dev/sd0s4 without the msdos filesystem
 *   may be faster.
 *
 * | Try the parameter list below includes. With good profiling
 * | you can certainly get more than 15Kb/s with a Pentium or higher.
 *
 * | #define PPA_INTR_TMO	10
 * | This is the timeout duration for each SCSI request. Increase it
 * | to get more CPU time and a worse transfer rate.
 *
 * | #define PPA_SPEED_LOW	3
 * | This is the delay after each port output.
 *
 * | #define PPA_OPENNINGS	2
 * | The size of the device queue. Since all requests are timedout()
 * | the maximum size depends on the timer queue.
 *
 * | #define PPA_SELECT_TMO	5000
 * | #define PPA_SPIN_TMO	100000
 * | #define PPA_MAX_RETRY	10
 * | These parameters are timeouts, you should increase them if
 * | you have errors.
 *
 *   This driver is a port of the Linux driver,
 *   see http://www.torque.net/zip.html for details.
 *   -------------------------------------------------------------------
 *
 *   Send bug report, suggestions to Nicolas.Souchu@prism.uvsq.fr,
 *   see http://www.prism.uvsq.fr/~son/ppa3.html for more info.
 *   -------------------------------------------------------------------
 */

#include <sys/types.h>

#ifdef KERNEL
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <syslog.h>
#include <machine/clock.h>
#include <i386/isa/isa_device.h>
#include <machine/clock.h>
#include <machine/cpu.h>	/* XXX for bootverbose: a funny place */
#endif	/* KERNEL */
#include <scsi/scsi_all.h>
#include <scsi/scsi_disk.h>
#include <scsi/scsiconf.h>
#include <sys/devconf.h>

#ifdef	KERNEL
#include <sys/kernel.h>
#endif /*KERNEL */

#include "ppa.h"		/* ppa.h is generated by 'config' */
#if NPPA > 0

/* --------------------------------------------------------------------
 * HERE ARE THINGS YOU MAY HAVE/WANT TO CHANGE
 */

#define PPA_INTR_TMO	10	/* timeout() duration */
#define PPA_SPEED_LOW	3
#define PPA_OPENNINGS	2	/* ppa queue size */
#define PPA_SELECT_TMO	5000
#define PPA_SPIN_TMO	100000
#define PPA_MAX_RETRY	10

/*
 * DO NOT MODIFY ANYTHING UNDER THIS LINE
 * --------------------------------------------------------------------
 */

#define barrier() __asm__("": : :"memory")

#define PPA_INITIATOR	0x7

#define PPA_SPEED_HIGH	1
#define PPA_SECTOR_SIZE	512
#define PPA_BUFFER_SIZE	0x12000 /* 64k should be enough, but to be sure... */

#define PPA_NIBBLE	0x1

struct ppa_data {
    short		ppa_base;
    struct scsi_xfer *	ppa_xfer;
    unsigned char	ppa_buffer[PPA_BUFFER_SIZE];
    unsigned int	ppa_stat;
    unsigned int	ppa_count;
    unsigned int	ppa_flags;
    unsigned char	ppa_port_delay;
    int flags;
    struct scsi_link sc_link;	/* prototype for subdevs */
};

#define ppaio_outb(ppa,port,byte) { \
		    outb ((ppa)->ppa_base + port, byte); \
		    DELAY ((ppa)->ppa_port_delay); \
}

#define ppaio_inb(ppa,port) ((char) inb((ppa)->ppa_base + port))

extern void ppaio_do_reset	__P((struct ppa_data *));
extern int ppaio_init		__P((struct ppa_data *));
extern int ppaio_do_scsi	__P((struct ppa_data *, int, int, char *, int,
				     char *, int, int *, int *));

struct ppa_data * ppadata[NPPA];

int	ppaprobe();
int	ppaattach();
int32_t	ppa_scsi_cmd();
void	ppaminphys();
u_int32_t	ppa_adapter_info();

#ifdef KERNEL
struct scsi_adapter ppa_switch =
{
    ppa_scsi_cmd,
    ppaminphys,
    0,
    0,
    ppa_adapter_info,
    "ppa",
    { 0, 0 }
};

/* 
 * The below structure is so we have a default dev struct
 * for out link struct.
 */
struct scsi_device ppa_dev =
{
    NULL,			/* Use default error handler */
    NULL,			/* have a queue, served by this */
    NULL,			/* have no async handler */
    NULL,			/* Use default 'done' routine */
    "ppa",
    0,
    { 0, 0 }
};

struct isa_driver ppadriver =
{
    ppaprobe,
    ppaattach,
    "ppa"
};

static struct kern_devconf kdc_ppa[NPPA] = { {
    0, 0, 0,		/* filled in by dev_attach */
    "ppa", 0, { MDDT_ISA, 0, "bio" },
    isa_generic_externalize, 0, 0, ISA_EXTERNALLEN,
    &kdc_isa0,		/* parent */
    0,			/* parentdata */
    DC_UNCONFIGURED,	/* always start out here */
    "Iomega PPA3 Parallel Port SCSI host adapter",
    DC_CLS_MISC		/* SCSI host adapters aren't special */
} };

static inline void
ppa_registerdev (struct isa_device * id)
{
    if (id->id_unit)
	kdc_ppa[id->id_unit] = kdc_ppa[0];
    kdc_ppa[id->id_unit].kdc_unit = id->id_unit;
    kdc_ppa[id->id_unit].kdc_parentdata = id;
    dev_attach(&kdc_ppa[id->id_unit]);
}

#endif /* KERNEL */

static int ppaunit = 0;

u_int32_t
ppa_adapter_info (int unit) {

	return 1;
}

static int
ppa_init (int unit) {

    struct ppa_data * ppa = ppadata[unit];
    int rs;

    ppaio_do_reset (ppa);
    if ((rs = ppaio_init (ppa))) {
	printf ("ppa%d: ppa_init() failed (%d)\n", unit, rs);
	return 1;
    }

    return 0;
}

int
ppaprobe (struct isa_device * dev) {

    int unit = ppaunit;
    struct ppa_data * ppa;
    int rv = -1;

    /*
     * find unit and check we have that many defined
     */
    if (unit >= NPPA) {
	printf("ppa%d: unit number too high\n", unit);
	return 0;
    }
    dev->id_unit = unit;

    /*
     * Allocate a storage area for us
     */
    if (ppadata[unit]) {
	printf("ppa%d: memory already allocated\n", unit);
	return 0;
    }
    ppa = malloc (sizeof(struct ppa_data), M_TEMP, M_NOWAIT);
    if (!ppa) {
	printf("ppa%d: cannot malloc!\n", unit);
	return 0;
    }
    bzero(ppa, sizeof(struct ppa_data));
    ppadata[unit] = ppa;
    ppa->ppa_base = dev->id_iobase;
    ppa->ppa_port_delay = PPA_SPEED_LOW;

#ifndef DEV_LKM
    ppa_registerdev(dev);
#endif

    if ((rv = ppa_init(unit)) != 0) {
#ifdef DEBUG_PPA3
	log (LOG_DEBUG, "ppaprobe(): ppa_init() error (%d)\n", rv);
#endif

	ppadata[unit] = NULL;
	free(ppa, M_TEMP);
	return 0;
    }

    ppaunit ++;
    return 4;
}

/*
 * Attach all the sub-devices we can find.
 */
int
ppaattach (struct isa_device * dev) {

    int unit = dev->id_unit;
    struct ppa_data * ppa = ppadata[unit];
    struct scsibus_data * scbus;

    /*
     * fill in the prototype scsi_link.
     */
    ppa->sc_link.adapter_unit = unit;
    ppa->sc_link.adapter_targ = PPA_INITIATOR;
    ppa->sc_link.adapter = &ppa_switch;
    ppa->sc_link.device = &ppa_dev;
    ppa->sc_link.flags = ppa->flags;
    ppa->sc_link.opennings = PPA_OPENNINGS;

    /*
     * Prepare the scsibus_data area for the upperlevel
     * scsi code.
     */
    scbus = scsi_alloc_bus();
    if(!scbus)
	return 0;
    scbus->adapter_link = &ppa->sc_link;

    /*
     * Ask the adapter what subunits are present.
     */
    kdc_ppa[unit].kdc_state = DC_BUSY; /* host adapters are always busy */

    scsi_attachdevs(scbus);

    return 1;
}

void
ppaminphys(struct buf * bp) {

    if (bp->b_bcount > PPA_BUFFER_SIZE)
	bp->b_bcount = PPA_BUFFER_SIZE;

    return;
}

/*
 * Must be called at splbio() level.
 */
static void
ppaintr (struct ppa_data * ppa, struct scsi_xfer * xs) {

    int timeout, retry;

#ifdef DEBUG_PPA3
    log (LOG_DEBUG, "Entering ppaintr()...\n");
#endif

    if (xs->datalen && !(xs->flags & SCSI_DATA_IN))
      bcopy (xs->data, ppa->ppa_buffer, xs->datalen);
    
    retry = 0;
    do {
	timeout = ppaio_do_scsi (ppa, PPA_INITIATOR, xs->sc_link->target,
				 (char *) xs->cmd, xs->cmdlen, ppa->ppa_buffer,
				 xs->datalen, &ppa->ppa_stat, &ppa->ppa_count);

    } while ((timeout || ppa->ppa_count < xs->datalen) &&
	     retry++ < PPA_MAX_RETRY);

#ifdef DEBUG_PPA3
    log (LOG_DEBUG, "ppa_do_scsi = %d, status = 0x%x, count = %d\n", 
	 timeout, ppa->ppa_stat, ppa->ppa_count);
#endif

    if (timeout || ppa->ppa_count < xs->datalen) {
	xs->error = XS_TIMEOUT;
	goto error;
    }

    if (ppa->ppa_stat) {
	xs->error = XS_SENSE;
	goto error;
    }

    if (xs->datalen && (xs->flags & SCSI_DATA_IN))
        bcopy (ppa->ppa_buffer, xs->data, xs->datalen);
     
    xs->resid = 0;
    xs->error = XS_NOERROR;

error:
    xs->flags |= ITSDONE;
    scsi_done (xs);

    return;
}

static void
ppa_softint (void * arg) {

    struct scsi_xfer * xs = (struct scsi_xfer *) arg;
    int s;

    s = splbio();
    ppaintr (ppadata[xs->sc_link->adapter_unit], xs);
    splx(s);

    return;
}

/*
 * Must be called at splbio() level.
 */
#define PPA_SCHEDINTR(arg) timeout(ppa_softint, (void *) arg, PPA_INTR_TMO);
	
int32_t
ppa_scsi_cmd (struct scsi_xfer * xs) {

    int s;

    if (xs->flags & SCSI_DATA_UIO) {
	log (LOG_INFO, "UIO not supported by ppa_driver !\n");
	xs->error = XS_DRIVER_STUFFUP;
	return COMPLETE;
    }

#ifdef DEBUG_PPA3
    { int i;
	log (LOG_DEBUG, "ppa_scsi_cmd(): xs->flags = 0x%x, "\
	     "xs->data = 0x%x, xs->datalen = %d\n",
	     xs->flags, xs->data, xs->datalen);

	for (i=0; i<xs->cmdlen; i++)
		log (LOG_DEBUG, "%2x", ((u_char *) xs->cmd)[i]);
	log (LOG_DEBUG, "\n");
    }
#endif

    if (xs->flags & SCSI_NOMASK) {
	ppaintr (ppadata[xs->sc_link->adapter_unit], xs);
	return COMPLETE;
    }

    s = splbio ();
    PPA_SCHEDINTR(xs);
    splx (s);

    return SUCCESSFULLY_QUEUED;
}

static void
ppaio_d_pulse (struct ppa_data * ppa, char b) {

    ppaio_outb (ppa, 0, b);
    ppaio_outb (ppa, 2, 0xc);
    ppaio_outb (ppa, 2, 0xe);
    ppaio_outb (ppa, 2, 0xc);
    ppaio_outb (ppa, 2, 0x4);
    ppaio_outb (ppa, 2, 0xc);
}

static void
ppaio_disconnect (struct ppa_data * ppa) {

    ppaio_d_pulse(ppa, 0);
    ppaio_d_pulse(ppa, 0x3c);
    ppaio_d_pulse(ppa, 0x20);
    ppaio_d_pulse(ppa, 0xf);
}

static void
ppaio_c_pulse (struct ppa_data * ppa, char b) {

    ppaio_outb (ppa, 0, b);
    ppaio_outb (ppa, 2, 0x4);
    ppaio_outb (ppa, 2, 0x6);
    ppaio_outb (ppa, 2, 0x4);
    ppaio_outb (ppa, 2, 0xc);
}

static void
ppaio_connect (struct ppa_data * ppa) {

    ppaio_c_pulse (ppa, 0);
    ppaio_c_pulse (ppa, 0x3c);
    ppaio_c_pulse (ppa, 0x20);
    ppaio_c_pulse (ppa, 0x8f);
}

void ppaio_do_reset (struct ppa_data * ppa) {
    ppaio_outb (ppa, 2, 0);             /* This is really just a guess */
    DELAY (100);
}


/* This is based on a trace of what the Iomega DOS 'guest' driver does.
 * I've tried several different kinds of parallel ports with guest and
 * coded this to react in the same ways that it does.
 *
 * The return value from this function is just a hint about where the
 * handshaking failed.
 *
 *							Grant R. Guenther 
 */
int
ppaio_init (struct ppa_data * ppa) {

    char r, s;

    ppaio_outb (ppa, 0, 0xaa); 
    if (ppaio_inb (ppa, 0) != (char) 0xaa) return 1; 

    ppaio_disconnect (ppa);
    ppaio_connect (ppa);
    ppaio_outb (ppa, 2, 0x6); 
    if ((ppaio_inb (ppa, 1) & 0xf0) != 0xf0) return 2; 

    ppaio_outb (ppa, 2, 0x4);
    if ((ppaio_inb (ppa, 1) & 0xf0) != 0x80) return 3; 

    ppaio_disconnect (ppa);
    s = ppaio_inb (ppa, 2);
    ppaio_outb (ppa, 2, 0xec);
    ppaio_outb (ppa, 0, 0x55); 
    r = ppaio_inb (ppa, 0);

    if (r != (char) 0xff) { 
	ppa->ppa_flags |= PPA_NIBBLE;
	if (r != (char) 0x55) return 4; 

	ppaio_outb (ppa, 0, 0xaa); 
	if (ppaio_inb (ppa, 0) != (char) 0xaa) return 5; 
    }

    ppaio_outb (ppa, 2, s);
    ppaio_connect (ppa);
    ppaio_outb (ppa, 0, 0x40);
    ppaio_outb (ppa, 2, 0x8);
    ppaio_outb (ppa, 2, 0xc);
    ppaio_disconnect (ppa);

    return 0;
}

char
ppaio_select (struct ppa_data * ppa, int initiator, int target) {

    char    r;
    int     k;

    r = ppaio_inb (ppa, 1);
    ppaio_outb (ppa, 0, (1<<target));
    ppaio_outb (ppa, 2, 0xe);
    ppaio_outb (ppa, 2, 0xc);
    ppaio_outb (ppa, 0, (1<<initiator));
    ppaio_outb (ppa, 2, 0x8);

    k = 0;
    while (!((r = ppaio_inb (ppa, 1)) & 0xf0) && (k++ < PPA_SELECT_TMO))
        barrier();

    return r;
}

/*      Wait for the high bit to be set.
 *
 *      In principle, this could be tied to an interrupt, but the adapter
 *      doesn't appear to be designed to support interrupts.  We spin on
 *      the 0x80 ready bit, but we call the scheduler to allow other
 *      processes to run while we are waiting.  (The LP driver does this
 *      in polling mode.)
 *
 *							Grant R. Guenther 
 */
char
ppaio_wait (struct ppa_data * ppa) {

    int     k;
    char    r;

    k = 0;
    while (!((r = ppaio_inb (ppa, 1)) & 0x80) && (k++ < PPA_SPIN_TMO))
	barrier ();

    if (k < PPA_SPIN_TMO)
      return (r & 0xf0);

    ppaio_disconnect (ppa);
    return 0;               /* command timed out */    
}

int 
ppaio_do_scsi (struct ppa_data * ppa, int host, int target,
	       char * command,  int clen,
	       char * buffer, int blen,
	       int * result, int * count) {

    char r, l, h;
    int	dir, cnt, fast;
    int k, error = 0;

    ppaio_connect(ppa);
    r = ppaio_select(ppa,host,target);
    if (r == 0)  {
	ppaio_disconnect (ppa);
	return 1;  			/* select timeout */
    }
    ppaio_outb(ppa,2,0xc);

    if (!ppaio_wait(ppa)) return 2;	       	/* not ready for command */

    for (k=0;k<clen;k++) {			/* send the command */
	ppaio_outb(ppa,0,command[k]);
	ppaio_outb(ppa,2,0xe);
	ppaio_outb(ppa,2,0xc);
	if (!( r = ppaio_wait(ppa))) return 3;
    }

    /* 
     * Completion ... 
     */
    fast = ((command[0] == READ_COMMAND) || (command[0] == READ_BIG) ||
	    (command[0] == WRITE_COMMAND) || (command[0] == WRITE_BIG));
    cnt = 0;  dir = 0;

    dir = (r == (char) 0xc0);

    ppa->ppa_port_delay = PPA_SPEED_HIGH;

    while (r != (char) 0xf0) {
	if (dir) do {
	    ppaio_outb(ppa,0,buffer[cnt]);
	    ppaio_outb(ppa,2,0xe);
	    ppaio_outb(ppa,2,0xc);
	    cnt++;
	} while (fast && (cnt % PPA_SECTOR_SIZE));
	else {
	    if (ppa->ppa_flags & PPA_NIBBLE) do { 
		ppaio_outb(ppa,2,0x4); h = ppaio_inb(ppa,1);
		ppaio_outb(ppa,2,0x6); l = ppaio_inb(ppa,1);
		buffer[cnt] = ((l >> 4) & 0x0f) + (h & 0xf0);
		cnt ++;
	    } while (fast && (cnt % PPA_SECTOR_SIZE));
	    else do {
		ppaio_outb (ppa, 2, 0x25);
		buffer[cnt] = ppaio_inb (ppa, 0);
		ppaio_outb (ppa, 2, 0x27);
		cnt++;
	    } while (fast && (cnt % PPA_SECTOR_SIZE));
	    if ((ppa->ppa_flags & PPA_NIBBLE) == 0) {
		ppaio_outb (ppa, 2, 0x5);
		ppaio_outb (ppa, 2, 0x4);
	    }
	    ppaio_outb(ppa,2,0xc);
	}
	if (!(r = ppaio_wait(ppa))) {
	    error = 6; goto error;
	}
    }

    ppa->ppa_port_delay = PPA_SPEED_LOW;
    *count = cnt;

    ppaio_outb(ppa,2,0x4);		/* now read status byte */
    h = ppaio_inb(ppa,1);
    ppaio_outb(ppa,2,0x6);
    l = ppaio_inb(ppa,1);
    ppaio_outb(ppa,2,0xc);
    *result = (l >> 4) + (h & 0xf0);

    ppaio_outb(ppa,2,0xe); ppaio_outb(ppa,2,0xc);
    ppaio_disconnect(ppa);

    return 0;

error:
    ppa->ppa_port_delay = PPA_SPEED_LOW;
    ppaio_disconnect (ppa);
    return error;
}

#endif

--------------48026FF63BDE--