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--