freebsd-skq/sys/i386/isa/ida.c
phk 6a5dc97620 Simplify cdevsw registration.
The cdevsw_add() function now finds the major number(s) in the
struct cdevsw passed to it.  cdevsw_add_generic() is no longer
needed, cdevsw_add() does the same thing.

cdevsw_add() will print an message if the d_maj field looks bogus.

Remove nblkdev and nchrdev variables.  Most places they were used
bogusly.  Instead check a dev_t for validity by seeing if devsw()
or bdevsw() returns NULL.

Move bdevsw() and devsw() functions to kern/kern_conf.c

Bump __FreeBSD_version to 400006

This commit removes:
        72 bogus makedev() calls
        26 bogus SYSINIT functions

if_xe.c bogusly accessed cdevsw[], author/maintainer please fix.

I4b and vinum not changed.  Patches emailed to authors.  LINT
probably broken until they catch up.
1999-05-31 11:29:30 +00:00

1787 lines
45 KiB
C

/*
* Copyright (c) 1996, 1997, 1998, 1999
* Mark Dawson and David James. 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 immediately at the beginning of the file, without modification,
* 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. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*
* $Id: ida.c,v 1.1 1999/05/25 19:45:17 julian Exp $
*
*/
/*
* Compaq SMART disk array controller driver for FreeBSD.
* Supports the Compaq SMART-2 and SMART-3 families of disk
* array controllers.
*
*/
#include "id.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/errno.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <i386/isa/isa.h>
#include <i386/isa/isa_device.h>
#ifdef DEVFS
#include <sys/devfsext.h>
#endif /*DEVFS*/
/*#include <sys/dkbad.h>*/
#include <sys/devicestat.h>
#include <sys/disklabel.h>
#include <sys/diskslice.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/vm_prot.h>
#include <vm/pmap.h>
#include <pci.h>
#include <pci/pcireg.h>
#include <pci/pcivar.h>
#include <sys/reboot.h>
extern u_long bootdev;
#define IDA_VERSION 1
#define PAGESIZ 4096
/* IDA wdc vector stealing (cuckoo) control */
#define IDA_CUCKOO_NEVER 0 /* never steal wdc vectors */
#define IDA_CUCKOO_ROOTWD 1 /* steal iff rootdev is wd device */
#define IDA_CUCKOO_ROOTNOTIDA 2 /* steal if rootdev not ida device */
#define IDA_CUCKOO_ALWAYS 3 /* always steal wdc vectors */
#ifndef IDA_CUCKOO_MODE
#define IDA_CUCKOO_MODE IDA_CUCKOO_ALWAYS
#endif
/* IDA PCI controller */
#define PCI_DEVICE_ID_COMPAQ_SMART2P 0xae100e11ul
#define PCI_CONTROLLER(ctlp) (ctlp->ident == PCI_DEVICE_ID_COMPAQ_SMART2P)
typedef struct ida_pci_reg {
u_long unknown;
u_long initiate_fifo;
#define IDA_PCI_BUSY 1
u_long complete_fifo;
u_long interrupt;
#define IDA_PCI_ENABLE_INTS 1
#define IDA_PCI_DISABLE_INTS 0
u_long status;
#define IDA_PCI_PENDING 1
#define IDA_PCI_READY 2
} ida_pci_reg_t;
/* IDA controller register definitions */
#define R_ID0 0xc80 /* id byte 0 */
#define R_ID1 0xc81 /* id byte 1 */
#define R_ID2 0xc82 /* id byte 2 */
#define R_ID3 0xc83 /* id byte 3 */
#define R_CONF 0xc88 /* global configuration */
#define R_SYSINT 0xc89 /* system interrupt enable/ctrl */
#define R_SEM0 0xc8a /* semaphore port 0 */
#define R_SEM1 0xc8b /* semaphore port 1 */
#define R_LBELL_E 0xc8c /* local doorbell enable */
#define R_LBELL_I 0xc8d /* local doorbell int/status */
#define R_EBELL_E 0xc8e /* EISA doorbell enable */
#define R_EBELL_I 0xc8f /* EISA doorbell int/status */
#define R_SUB_ADDR 0xc90 /* submit address */
#define R_SUB_LEN 0xc94 /* submit cmdlist size */
#define R_COM_ADDR 0xc98 /* completion address */
#define R_COM_OFF 0xc9c /* completion request offset */
#define R_COM_STAT 0xc9e /* completion cmdlist status */
#define R_INTDEF 0xcc0 /* interrupt definition */
/*
* BMIC doorbell status codes
*/
#define BMIC_DATA_READY 0x01 /* data ready bit */
#define BMIC_CHAN_CLEAR 0x02 /* channel clear bit */
/* IDA controller command list return status values */
#define IDA_COMPL_OK 0x01 /* command list completed ok */
#define IDA_NON_FATAL 0x02 /* non-fatal error */
#define IDA_FATAL 0x04 /* fatal error */
#define IDA_ABORTED 0x08 /* command list aborted */
#define IDA_INVAL_REQ 0x10 /* bad request block */
#define IDA_INVAL_LIST 0x20 /* bad command list */
#define IDA_AARGH_LIST 0x40 /* totally disastrous command list */
/* IDA controller command codes */
#define IDA_GET_DRV_INFO 0x10
#define IDA_GET_CTL_INFO 0x11
#define IDA_READ_DATA 0x20
#define IDA_WRITE_DATA 0x30
#define IDA_FLUSH_CACHE 0xc2
/* Interrupt definition codes */
#define IDA_IRQ_MASK 0xfc
#define IDA_IRQ_10 0x20
#define IDA_IRQ_11 0x10
#define IDA_IRQ_14 0x40
#define IDA_IRQ_15 0x80
/* IDA controller hardware command structure definitions */
typedef u_long physaddr_t;
struct ida_hdr {
u_long drive:8; /* logical drive */
u_long priority:8; /* block priority */
u_long flags:16; /* control flags */
};
struct ida_req {
u_long next:16; /* offset of next request */
u_long command:8; /* command */
u_long error:8; /* return error code */
u_long blkno; /* block number */
u_short bcount; /* block count */
u_short sgcount; /* number of scatter gather entries */
/* a struct ida_req is followed physically by an array of struct ida_sgb */
};
struct ida_sgb {
u_long len; /* length of scatter gather segmmentk */
physaddr_t addr; /* physical address of block */
};
/* first some handy definitions as FreeBSD gcc doesn't do #pragma pack() */
#define pack_char(name) u_char name[1]
#define pack_short(name) u_char name[2]
#define pack_int(name) u_char name[4]
#define pack_long(name) u_char name[4]
/* ugly, but not inefficient, as it gets evaluated at compile time by gcc */
#define u_unpack(member) ( \
(sizeof(member) == 1) ? *(u_char *)(member) \
: (sizeof(member) == 2) ? *(u_short *)(member) \
: *(u_int *)(member) \
)
#define s_unpack(member) ( \
(sizeof(member) == 1) ? *(char *)(member) \
: (sizeof(member) == 2) ? *(short *)(member) \
: *(int *)(member) \
)
/* IDA controller hardware returned data structure definitions */
struct ida_ctl_info {
pack_char(num_drvs);
pack_long(signature);
pack_long(firm_rev);
};
struct ida_drv_info {
pack_short(secsize);
pack_long(secperunit);
pack_short(ncylinders);
pack_char(ntracks);
pack_char(signature);
pack_char(psectors);
pack_short(wprecomp);
pack_char(max_acc);
pack_char(control);
pack_short(pcylinders);
pack_char(ptracks);
pack_short(landing_zone);
pack_char(nsectors);
pack_char(checksum);
pack_char(mirror);
};
/* IDA driver queue command block */
#define IDA_MAX_SGLEN 32 /* maximum entries in scatter gather list */
#define IDA_MAX_DRVS_CTLR 8 /* maximum logical drives per controller */
#define IDA_DEF_PRIORITY 16 /* default priority for command list */
#define IDA_SCSI_TARGET_ID 7
#define IDA_QCB_MAX 256
struct ida_qcb {
/* first some hardware specific fields ... */
struct ida_hdr hdr;
struct ida_req req;
struct ida_sgb sglist[IDA_MAX_SGLEN];
/* and then some driver queue managment stuff */
u_int flags; /* qcb type */
#define QCB_FREE 0 /* ready for a new command */
#define QCB_ACTIVE 1 /* waiting to be sent to the controller */
#define QCB_SENT 2 /* waiting for interrupt from the controller */
#define QCB_IMMED 4 /* immediate (non-queued) command */
#define QCB_IMMED_FAIL 8
struct ida_qcb *next; /* next ida command block of this type */
struct ida_qcb *last; /* last ida command block of this type */
struct buf *buf; /* buf associated with this qcb */
physaddr_t paddr; /* physical address of this struct */
struct ida_qcb *nexthash; /* next ida command block with same hash value */
};
typedef struct ida_qcb qcb_t;
/* IDA driver controller and drive information blocks */
#define QCB_HASH_SIZE 257 /* some suitable prime number */
#define QCB_HASH(h) ((h) % QCB_HASH_SIZE)
struct ida_drv {
u_int flags;
#define ID_INIT 0x0001
#define ID_WRITEPROT 0x0002
#define ID_DEV_OPEN 0x0004
u_int ctl_unit; /* which controller is this drive on */
u_int drv_unit; /* number of this virtual disk */
struct ida_drv_info drv_info; /* data from the controller */
struct diskslices *slices; /* new slice code */
struct devstat dk_stats; /* devstat entry */
};
struct ida_ctl {
u_int ident; /* controller identifier */
u_int flags;
u_int iobase;
u_short inside; /* number of qcbs in the controller */
u_short max_inside; /* maximum number simulaneously active */
u_short num_qcbs; /* number of qcbs allocated */
u_char num_drvs;
u_char irq;
u_char com_status; /* status of last completed command list */
physaddr_t com_addr; /* address of last completed command list */
u_short com_offset; /* offset of last completed command list */
qcb_t *freelist; /* linked list of free qcbs */
qcb_t *send_next; /* doubly-linked list of unsent qcbs */
qcb_t *send_last; /* because we must treat all jobs equally */
qcb_t *hashlist[QCB_HASH_SIZE];
};
extern struct ida_ctl *idadata[NIDA];
/* Useful IDA controller IO macro definitions */
#define IDA_DISABLE_INTERRUPT(iobase) outb(iobase + R_SYSINT, 0)
#define IDA_ENABLE_INTERRUPT(ctlp) outb(ctlp->iobase + R_SYSINT, 1)
#define IDA_SET_READY(ctlp) outb(ctlp->iobase + R_EBELL_E, 1)
#define IDA_DATA_READY(ctlp) ((inb(ctlp->iobase + R_EBELL_I) & 1))
#define IDA_CHAN_CLEAR(ctlp) ((inb(ctlp->iobase + R_EBELL_I) & 2))
/* enable/disable interrupts on a change of channel clear status (?) */
#define IDA_ENABLE_CHAN(ctlp) \
outb(ctlp->iobase + R_EBELL_E, inb(ctlp->iobase + R_EBELL_E) | 2)
#define IDA_DISABLE_CHAN(ctlp) \
outb(ctlp->iobase + R_EBELL_E, inb(ctlp->iobase + R_EBELL_E) & ~0x2)
/* acknowledge the completion of a command */
#define IDA_ACK_CMD_COM(ctlp) \
(outb(ctlp->iobase + R_EBELL_I, 1), outb(ctlp->iobase + R_LBELL_I, 2))
/* set submission details for a command list */
#define IDA_SET_SUB_ADDR(ctlp, addr) outl(ctlp->iobase + R_SUB_ADDR, addr)
#define IDA_SET_SUB_LEN(ctlp, size) outw(ctlp->iobase + R_SUB_LEN, size)
/* get completion details for a command list */
#define IDA_GET_COM_ADDR(ctlp) inl(ctlp->iobase + R_COM_ADDR)
#define IDA_GET_COM_OFFSET(ctlp) inw(ctlp->iobase + R_COM_OFF)
#define IDA_GET_COM_STATUS(ctlp) inb(ctlp->iobase + R_COM_STAT)
#define IDA_READ_EBELL_I(ctlp) inb(ctlp->iobase + R_EBELL_I)
#define IDA_READ_EBELL_E(ctlp) inb(ctlp->iobase + R_EBELL_E)
#define IDA_READ_LBELL_I(ctlp) inb(ctlp->iobase + R_LBELL_I)
#define IDA_SET_EBELL_I(ctlp, n) outb(ctlp->iobase + R_EBELL_I, n)
#define IDA_SET_LBELL_I(ctlp, n) outb(ctlp->iobase + R_LBELL_I, n)
#define JOB_SUCCESS 0
#define JOB_FAILURE 1
#define JOB_ABORTED 2
/* debugging aids */
#ifdef IDADEBUG
# define IDA_MAXQCBS (1<<0)
# define IDA_SHOWQCBS (1<<1)
# define IDA_SHOWSUBS (1<<2)
# define IDA_SHOWINTS (1<<3)
# define IDA_SHOWCMDS (1<<4)
# define IDA_SHOWMISC (1<<5)
void ida_print_qcb(qcb_t *qcbp);
void ida_print_active_qcb(int unit);
static int ida_debug = 0;
SYSCTL_INT(_debug, OID_AUTO, ida_debug, CTLFLAG_RW, &ida_debug, 0, "");
#endif
static int ida_soft_errors = 0;
SYSCTL_INT(_debug, OID_AUTO, ida_soft_errors,
CTLFLAG_RW, &ida_soft_errors, 0, "");
/* EISA probe and board identification definitions */
#define MAX_EISA_SLOT 16
#define IDA_EISA_PROD_ID 0x40
union eisa_id {
u_int value;
struct {
u_int rev:8;
u_int prod:8;
u_int mfr2:5;
u_int mfr1:5;
u_int mfr0:5;
} split;
};
/* test the manufacturer ID within an EISA board ID */
#define EISA_MFR_EQ(ident, mfr) ( \
(ident).split.mfr0 + '@' == (mfr)[0] && \
(ident).split.mfr1 + '@' == (mfr)[1] && \
(ident).split.mfr2 + '@' == (mfr)[2] \
)
/* generates a list of EISA board ID values, suitable for a printf */
#define EISA_ID_LIST(ident) \
(ident).split.mfr0 + '@', \
(ident).split.mfr1 + '@', \
(ident).split.mfr2 + '@', \
(ident).split.prod, \
(ident).split.rev
extern void DELAY(int millisecs);
/* FreeBSD IDA driver forward function definitions */
static d_open_t idopen;
static d_read_t idread;
static d_write_t idwrite;
static d_close_t idclose;
static d_strategy_t idstrategy;
static d_ioctl_t idioctl;
static d_dump_t iddump;
static d_psize_t idsize;
static pci_inthand_t idaintr;
static const char *ida_pci_probe(pcici_t tag, pcidi_t type);
static void ida_pci_attach(pcici_t config_id, int unit);
static int ida_eisa_probe __P((struct isa_device *dev));
static int ida_eisa_attach __P((struct isa_device *dev));
static int ida_poll __P((int unit, int wait));
static void idaminphys __P((struct buf *bp));
static void ida_start __P((int unit));
static int ida_get_ctl_info(int unit);
static int ida_attach_drives(int unit);
void ida_done(int cntlr, qcb_t *qcbp, int state);
static void ida_queue_buf(int cntlr, struct buf *bp);
static void ida_newqueue(int cntlr);
static qcb_t *ida_dequeue(int cntlr);
static void ida_enqueue(int cntlr, qcb_t *qcbp);
static int ida_submit(int unit, qcb_t *qcbp, int size);
static int ida_submit_wait(int unit, qcb_t *qcbp, int size);
static qcb_t *ida_get_qcb(int unit);
static void ida_free_qcb(int unit, qcb_t *qcbp);
static qcb_t *ida_qcb_phys_kv(struct ida_ctl *ida, physaddr_t ida_qcb_phys);
static struct cdevsw id_cdevsw;
struct isa_driver idadriver = {
ida_eisa_probe,
ida_eisa_attach,
"ida"
};
static u_long ida_pci_count;
static struct pci_device ida_pci_driver = {
"ida",
ida_pci_probe,
ida_pci_attach,
&ida_pci_count
};
DATA_SET (pcidevice_set, ida_pci_driver);
/* definitions for stealing wd driver's vectors */
#define ID_BDMAJ 29
#define ID_CDMAJ 109
#define WD_BDMAJ 0
#define WD_CDMAJ 3
struct isa_driver wdcdriver;
static int stub_probe __P((struct isa_device *dev));
static int stub_attach __P((struct isa_device *dev));
static struct isa_driver nodriver = {
stub_probe,
stub_attach,
"stub"
};
/* steal the wdc driver's vectors if we have booted off a wd device */
static
void
ida_cuckoo_wdc(void)
{
int cuckoo = IDA_CUCKOO_MODE;
int steal = 0;
char *mode;
int major = B_TYPE(bootdev);
if (cuckoo == IDA_CUCKOO_NEVER) {
mode = "never";
} else if (cuckoo == IDA_CUCKOO_ROOTWD) {
mode = "rootwd";
steal = (major == WD_BDMAJ);
} else if (cuckoo == IDA_CUCKOO_ROOTNOTIDA) {
mode = "notida";
/* check for magic value of 3 rather than ID_BDMAJ as boot code
* pretends we are a wt device (not normally bootable)
*/
steal = (major != 3);
} else {
mode = "always";
steal = 1;
}
printf("ida: wdc vector stealing %s (mode = %s, boot major = %d)\n",
(steal ? "on" : "off"), mode, major);
if (!steal) return;
/* OK - we have a controller, so steal wd driver's vectors */
wdcdriver = nodriver;
bdevsw[WD_BDMAJ]->d_open = cdevsw[WD_CDMAJ]->d_open = idopen;
bdevsw[WD_BDMAJ]->d_close = cdevsw[WD_CDMAJ]->d_close = idclose;
bdevsw[WD_BDMAJ]->d_read = cdevsw[WD_CDMAJ]->d_read = idread;
bdevsw[WD_BDMAJ]->d_write = cdevsw[WD_CDMAJ]->d_write = idwrite;
bdevsw[WD_BDMAJ]->d_strategy = cdevsw[WD_CDMAJ]->d_strategy = idstrategy;
bdevsw[WD_BDMAJ]->d_ioctl = cdevsw[WD_CDMAJ]->d_ioctl = idioctl;
bdevsw[WD_BDMAJ]->d_dump = iddump;
bdevsw[WD_BDMAJ]->d_psize = idsize;
return;
}
static struct ida_ctl *idadata[NIDA]; /* controller structures */
static struct ida_drv *id_drive[NID]; /* table of drives */
static int id_unit = 0; /* number of drives found */
/* general purpose data buffer for 'special' IDA driver commands */
union ida_buf {
char pad[512];
struct ida_ctl_info ctl;
struct ida_drv_info drv;
} ida_buf;
static int
stub_probe(struct isa_device *dev)
{
return 0;
}
static int
stub_attach(struct isa_device *dev)
{
return 0;
}
const char *
ida_pci_probe(pcici_t tag, pcidi_t type)
{
switch (type) {
case PCI_DEVICE_ID_COMPAQ_SMART2P:
return "Compaq SMART-2/P array controller";
break;
default:
break;
}
return NULL;
}
static void
ida_pci_attach(pcici_t config_id, int unit)
{
ida_pci_reg_t *reg;
struct ida_ctl *ctlp;
u_long id;
vm_offset_t paddr, vaddr;
id = pci_conf_read(config_id, PCI_ID_REG);
switch (id) {
case PCI_DEVICE_ID_COMPAQ_SMART2P:
break;
default:
break;
}
if (!pci_map_mem(config_id, 0x14, &vaddr, &paddr)) {
printf("ida: map failed.\n");
return;
}
/* allocate and initialise a storage area for this controller */
if (idadata[unit]) {
printf("ida%d: controller structure already allocated\n", unit);
return;
}
if ((ctlp = malloc(sizeof(struct ida_ctl), M_TEMP, M_NOWAIT)) == NULL) {
printf("ida%d: unable to allocate controller structure\n", unit);
return;
}
idadata[unit] = ctlp;
bzero(ctlp, sizeof(struct ida_ctl));
ctlp->ident = id;
ctlp->iobase = vaddr;
/* Install the interrupt handler. */
if (!pci_map_int (config_id, idaintr, (void *)unit, &bio_imask)) {
printf ("ida%d: failed to assign an interrupt handler\n", unit);
free((caddr_t)ctlp, M_DEVBUF);
idadata[unit] = 0;
return;
}
if (!(ida_get_ctl_info(unit) && ida_attach_drives(unit))) {
return;
}
reg = (ida_pci_reg_t *) vaddr;
reg->interrupt = IDA_PCI_ENABLE_INTS;
ida_cuckoo_wdc();
return;
}
int
ida_eisa_probe(struct isa_device *dev)
{
static u_int ida_used = 0;
u_int slot;
u_int port;
u_char intdef;
u_char irq;
int unit = dev->id_unit;
union eisa_id ident;
struct ida_ctl *ctlp;
if (dev->id_iobase) {
/* check out the configured iobase if given one */
slot = dev->id_iobase / 0x1000;
if (slot == 0 || slot > MAX_EISA_SLOT) {
printf("ida: port address (0x%x) out of range\n", dev->id_iobase);
return 0;
}
} else {
/* otherwise, search from the beginning for an unused slot to check out */
slot = 1;
}
while (1) {
while (ida_used & (1 << slot)) {
if (slot++ == MAX_EISA_SLOT) return 0;
}
ida_used |= (1 << slot);
port = slot * 0x1000;
/* read the EISA identification bytes */
ident.value = inb(port + R_ID0);
ident.value <<= 8;
ident.value |= inb(port + R_ID1);
ident.value <<= 8;
ident.value |= inb(port + R_ID2);
ident.value <<= 8;
ident.value |= inb(port + R_ID3);
/* check that the card is the right type ? */
if (EISA_MFR_EQ(ident, "CPQ") && ident.split.prod == IDA_EISA_PROD_ID) {
break;
}
/* if we were config'ed with an iobase, then don't probe any more slots */
if (dev->id_iobase) return 0;
}
/* disable interrupts and find out what interrupt this controller uses */
IDA_DISABLE_INTERRUPT(port);
intdef = inb(port + R_INTDEF);
switch (intdef & IDA_IRQ_MASK) {
case IDA_IRQ_10:
irq = 10;
break;
case IDA_IRQ_11:
irq = 11;
break;
case IDA_IRQ_14:
irq = 14;
break;
case IDA_IRQ_15:
irq = 15;
break;
default:
printf("ida: slot %d bogus interrupt setting (0x%02x)\n", slot, intdef);
return 0;
}
dev->id_irq = (1 << irq);
dev->id_drq = -1;
/* allocate and initialise a storage area for this controller */
if (idadata[unit]) {
printf("ida%d: controller structure already allocated\n", unit);
return 0;
}
if ((ctlp = malloc(sizeof(struct ida_ctl), M_TEMP, M_NOWAIT)) == NULL) {
printf("ida%d: unable to allocate controller structure\n", unit);
return 0;
}
idadata[unit] = ctlp;
bzero(ctlp, sizeof(struct ida_ctl));
ctlp->iobase = dev->id_iobase = port;
ctlp->ident = ident.value;
ctlp->irq = irq;
if (ida_get_ctl_info(unit) == 0) {
return 0;
}
/* return range of io ports used */
return 0x1000;
}
int
ida_get_ctl_info(int unit)
{
struct ida_ctl *ctlp = idadata[unit];
qcb_t qcb;
qcb_t *qcbp = &qcb;
ida_newqueue(unit);
/* controller capacity statistics */
ctlp->inside = 0;
ctlp->max_inside = 0;
/* ask the controller to tell us about itself with an IDA_GET_CTL_INFO */
bzero(qcbp, sizeof(qcb_t));
qcbp->paddr = vtophys(qcbp);
if (PCI_CONTROLLER(ctlp)) {
qcbp->hdr.priority = 0x00;
qcbp->hdr.flags = 0x24;
} else {
qcbp->hdr.priority = IDA_DEF_PRIORITY;
qcbp->hdr.flags = 0x12;
}
qcbp->req.command = IDA_GET_CTL_INFO;
qcbp->req.bcount = 1;
qcbp->req.sgcount = 1;
qcbp->sglist[0].len = sizeof(ida_buf);
qcbp->sglist[0].addr = vtophys(&ida_buf);
if (ida_submit_wait(unit, qcbp, sizeof(struct ida_qcb))) {
printf("ida%d: idasubmit failed on IDA_GET_CTL_INFO\n", unit);
return 0;
}
if (!PCI_CONTROLLER(ctlp)) {
if (ctlp->com_status != IDA_COMPL_OK) {
printf("ida%d: bad status 0x%02x from IDA_GET_CTL_INFO\n",
unit, ctlp->com_status);
return 0;
}
}
/* got the information at last, print it and note the number of drives */
printf("ida%d: drvs=%d firm_rev=%c%c%c%c\n", unit,
u_unpack(ida_buf.ctl.num_drvs), ida_buf.ctl.firm_rev[0],
ida_buf.ctl.firm_rev[1], ida_buf.ctl.firm_rev[2],
ida_buf.ctl.firm_rev[3]);
ctlp->num_drvs = u_unpack(ida_buf.ctl.num_drvs);
return 1;
}
int
ida_attach_drives(int cntlr)
{
struct ida_ctl *ctlp = idadata[cntlr];
qcb_t qcb;
qcb_t *qcbp = &qcb;
struct ida_drv *drv;
int drive;
int unit;
/* prepare to interrogate the drives */
bzero(qcbp, sizeof(qcb_t));
qcbp->req.command = IDA_GET_DRV_INFO;
qcbp->paddr = vtophys(qcbp);
if (PCI_CONTROLLER(ctlp)) {
qcbp->hdr.priority = 0x00;
qcbp->hdr.flags = 0x24;
} else {
qcbp->hdr.priority = IDA_DEF_PRIORITY;
qcbp->hdr.flags = 0x12;
}
qcbp->req.bcount = 1;
qcbp->req.sgcount = 1;
qcbp->sglist[0].len = sizeof(ida_buf);
qcbp->sglist[0].addr = vtophys(&ida_buf);
for (drive = 0 ; drive < ctlp->num_drvs ; drive++) {
qcbp->hdr.drive = drive;
if (ida_submit_wait(cntlr, qcbp, sizeof(struct ida_qcb))) {
printf("ida%d: ida_submit_wait failed on IDA_GET_DRV_INFO\n", cntlr);
return 0;
}
if (!PCI_CONTROLLER(ctlp)) {
if (ctlp->com_status != IDA_COMPL_OK) {
printf("ida%d: bad status 0x%02x from IDA_GET_DRV_INFO\n",
cntlr, ctlp->com_status);
return 0;
}
}
if ((drv = malloc(sizeof(struct ida_drv), M_TEMP, M_NOWAIT)) == NULL) {
printf("ida%d: unable to allocate drive structure\n", cntlr);
return 0;
}
bzero(drv, sizeof(struct ida_drv));
drv->ctl_unit = cntlr;
drv->drv_unit = drive;
drv->drv_info = ida_buf.drv;
drv->flags |= ID_INIT;
unit = id_unit;
id_unit++; /* XXX unsure if this is the right way to do things */
id_drive[unit] = drv;
printf("ida%d: unit %d (id%d): <%s>\n",
cntlr, drive, unit, "Compaq Logical Drive");
printf("id%d: %luMB (%lu total sec), ",
unit,
(u_long)(u_unpack(drv->drv_info.secperunit) / 2048)
* (u_unpack(drv->drv_info.secsize) / 512),
(u_long)u_unpack(drv->drv_info.secperunit));
printf("%lu cyl, %lu head, %lu sec, bytes/sec %lu\n",
(u_long)u_unpack(drv->drv_info.ncylinders),
(u_long)u_unpack(drv->drv_info.ntracks),
(u_long)u_unpack(drv->drv_info.nsectors),
(u_long)u_unpack(drv->drv_info.secsize));
/*
* Export the drive to the devstat interface.
*/
devstat_add_entry(&drv->dk_stats, "id",
unit, (u_int32_t)drv->drv_info.secsize,
DEVSTAT_NO_ORDERED_TAGS,
DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_OTHER);
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWMISC) {
printf("ida%d: drive %d secsize=%d secperunit=%d ncylinders=%d ntracks=%d\n",
unit,
drive,
u_unpack(ida_buf.drv.secsize),
u_unpack(ida_buf.drv.secperunit),
u_unpack(ida_buf.drv.ncylinders),
u_unpack(ida_buf.drv.ntracks));
printf(" signature=0x%02x psectors=%d wprecomp=%d max_acc=%d control=0x%02x\n",
u_unpack(ida_buf.drv.signature),
u_unpack(ida_buf.drv.psectors),
u_unpack(ida_buf.drv.wprecomp),
u_unpack(ida_buf.drv.max_acc),
u_unpack(ida_buf.drv.control));
printf(" pcylinders=%d ptracks=%d landing_zone=%d nsectors=%d checksum=0x%02x\n",
u_unpack(ida_buf.drv.pcylinders),
u_unpack(ida_buf.drv.ptracks),
u_unpack(ida_buf.drv.landing_zone),
u_unpack(ida_buf.drv.nsectors),
u_unpack(ida_buf.drv.checksum));
}
#endif
}
return 1;
}
/* Attach all the sub-devices we can find. */
int
ida_eisa_attach(struct isa_device *dev)
{
int cntlr = dev->id_unit;
struct ida_ctl *ctlp = idadata[cntlr];
if (ida_attach_drives(cntlr)) {
IDA_ENABLE_INTERRUPT(ctlp);
IDA_SET_READY(ctlp);
ida_cuckoo_wdc();
return 1;
} else {
return 0;
}
}
/*
* Initialize a drive.
*/
int
idopen(dev_t dev, int flags, int fmt, struct proc *p)
{
struct ida_drv *drv;
int part = dkpart(dev);
int unit = dkunit(dev);
struct disklabel label;
int err;
if (unit >= NID || part >= MAXPARTITIONS) /* bounds check */
return(ENXIO);
drv = id_drive[unit];
if (!drv || !(drv->flags & ID_INIT)) /* drive not initialised */
return(ENXIO);
drv->flags |= ID_DEV_OPEN;
/* knock up a label for the whole disk. */
bzero(&label, sizeof label);
label.d_secsize = u_unpack(drv->drv_info.secsize);
label.d_nsectors = u_unpack(drv->drv_info.nsectors);
label.d_ntracks = u_unpack(drv->drv_info.ntracks);
label.d_ncylinders = u_unpack(drv->drv_info.ncylinders);
label.d_secpercyl =
u_unpack(drv->drv_info.ntracks) * u_unpack(drv->drv_info.nsectors);
if (label.d_secpercyl == 0)
label.d_secpercyl = 100; /* prevent accidental division by zero */
label.d_secperunit = u_unpack(drv->drv_info.secperunit);
/* Initialize slice tables. */
if ((err = dsopen("id", dev, fmt, 0, &drv->slices, &label, idstrategy,
(ds_setgeom_t *)NULL, &id_cdevsw)) == NULL) {
return 0;
}
if (!dsisopen(drv->slices)) {
drv->flags &= ~ID_DEV_OPEN;
}
return err;
}
/* ARGSUSED */
int
idclose(dev_t dev, int flags, int fmt, struct proc *p)
{
struct ida_drv *drv;
int part = dkpart(dev);
int unit = dkunit(dev);
if (unit >= NID || part >= MAXPARTITIONS) /* bounds check */
return(ENXIO);
drv = id_drive[unit];
dsclose(dev, fmt, drv->slices);
if (!dsisopen(drv->slices)) {
drv->flags &= ~ID_DEV_OPEN;
}
return 0;
}
int
idioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
{
struct ida_drv *drv;
int part = dkpart(dev);
int unit = dkunit(dev);
int err;
if (unit >= NID || part >= MAXPARTITIONS ||
!(drv = id_drive[unit]) || !(drv->flags & ID_INIT)) /* sanity check */
return(ENXIO);
err = dsioctl("id", dev, cmd, addr, flag, &drv->slices,
idstrategy, (ds_setgeom_t *)NULL);
if (err != -1)
return (err);
if (dkpart(dev) != RAW_PART)
return (ENOTTY);
return (0);
}
static int
idread(dev_t dev, struct uio *uio, int ioflag)
{
return (physio(idstrategy, NULL, dev, 1, minphys, uio));
}
static int
idwrite(dev_t dev, struct uio *uio, int ioflag)
{
return (physio(idstrategy, NULL, dev, 0, minphys, uio));
}
/* Read/write routine for a buffer. Finds the proper unit, range checks
* arguments, and schedules the transfer. Does not wait for the transfer
* to complete. Multi-page transfers are supported. All I/O requests must
* be a multiple of a sector in length.
*/
void
idstrategy(struct buf *bp)
{
int unit = dkunit(bp->b_dev);
struct ida_drv *drv;
int opri;
if (unit >= NID) {
printf("ida: unit out of range\n");
bp->b_error = EINVAL;
goto bad;
}
if (!(drv = id_drive[unit]) || !(drv->flags & ID_INIT)) {
printf("id%d: drive not initialised\n", unit);
bp->b_error = EINVAL;
goto bad;
}
if (bp->b_blkno < 0) {
printf("id%d: negative block requested\n", unit);
bp->b_error = EINVAL;
goto bad;
}
if (bp->b_bcount % DEV_BSIZE != 0) { /* bounds check */
printf("id%d: count (%lu) not a multiple of a block\n",
unit, bp->b_bcount);
bp->b_error = EINVAL;
goto bad;
}
idaminphys(bp); /* adjust the transfer size */
/* "soft" write protect check */
if ((drv->flags & ID_WRITEPROT) && (bp->b_flags & B_READ) == 0) {
bp->b_error = EROFS;
goto bad;
}
/* If it's a null transfer, return immediately */
if (bp->b_bcount == 0) {
goto done;
}
if (dscheck(bp, drv->slices) <= 0) {
goto done;
}
opri = splbio();
ida_queue_buf(unit, bp);
devstat_start_transaction(&drv->dk_stats);
ida_start(drv->ctl_unit); /* hit the appropriate controller */
splx(opri);
return /*0*/;
bad:
bp->b_flags |= B_ERROR;
done:
/* correctly set the buf to indicate a completed xfer */
bp->b_resid = bp->b_bcount;
biodone(bp);
return /*0*/;
}
void
idaminphys(bp)
struct buf *bp;
{
/* assumes each page requires an sgb entry */
int max = (IDA_MAX_SGLEN - 1) * PAGESIZ;
if (bp->b_bcount > max) {
bp->b_bcount = max;
}
}
/* Get a free qcb.
* If there are none, see if we can allocate a new one.
* If so, put it in the hash table too,
* otherwise either return an error or sleep.
*/
static qcb_t *
ida_get_qcb(int unit)
{
struct ida_ctl *ida = idadata[unit];
unsigned opri = 0;
qcb_t *qcbp;
int hashnum;
opri = splbio();
/* if the freelist is empty - create a qcb until limit is reached */
while (!(qcbp = ida->freelist)) {
if (ida->num_qcbs < IDA_QCB_MAX) {
qcbp = (qcb_t *)malloc(sizeof(qcb_t), M_TEMP, M_NOWAIT);
if (qcbp) {
bzero(qcbp, sizeof(qcb_t));
ida->num_qcbs++;
qcbp->flags = QCB_ACTIVE;
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS)
printf("ida_get_qcb%d: qcb %d created\n",
unit, ida->num_qcbs);
#endif
/* Put in the phystokv hash table. */
/* Never gets taken out. */
qcbp->paddr = vtophys(qcbp);
hashnum = QCB_HASH(qcbp->paddr);
qcbp->nexthash = ida->hashlist[hashnum];
ida->hashlist[hashnum] = qcbp;
} else {
printf("ida%d: Can't malloc QCB\n", unit);
} goto gottit;
} else {
/* reached maximum allocation of qcbs - sleep until one is freed */
tsleep((caddr_t)&ida->freelist, PRIBIO, "idaqcb", 0);
}
} if (qcbp) {
/* qet the qcb from from the (non-empty) free list */
ida->freelist = qcbp->next;
qcbp->flags = QCB_ACTIVE;
}
gottit:
splx(opri);
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS)
printf("ida_get_qcb%d: returns 0x%x\n", unit, qcbp);
#endif
return (qcbp);
}
/* Return a qcb to the free list */
static void
ida_free_qcb(int unit, qcb_t *qcbp)
{
unsigned int opri = 0;
struct ida_ctl *ida = idadata[unit];
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS)
printf("ida_free_qcb%d: freeing 0x%x\n", unit, qcbp);
#endif
opri = splbio();
qcbp->next = ida->freelist;
ida->freelist = qcbp;
qcbp->flags = QCB_FREE;
/* if the free list was empty, wakeup anyone sleeping */
if (!qcbp->next) {
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS)
printf("ida_free_qcb%d: about to wakeup 0x%x queue\n",
unit, &ida->freelist);
#endif
wakeup((caddr_t)&ida->freelist);
}
splx(opri);
}
/* Find the ida_qcb having a given physical address */
qcb_t *
ida_qcb_phys_kv(ida, ida_qcb_phys)
struct ida_ctl *ida;
physaddr_t ida_qcb_phys;
{
int hash = QCB_HASH(ida_qcb_phys);
qcb_t *qcbp = ida->hashlist[hash];
while (qcbp) {
if (qcbp->paddr == ida_qcb_phys)
break;
qcbp = qcbp->nexthash;
}
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS)
printf("ida?: ida_qcb_phys_kv(0x%x) = 0x%x\n", ida_qcb_phys, qcbp);
#endif
return qcbp;
}
void
ida_queue_buf(int unit, struct buf *bp)
{
struct ida_drv *drv = id_drive[unit];
int cntlr = drv->ctl_unit;
qcb_t *qcbp = ida_get_qcb(cntlr); /* may cause us to wait */
struct ida_ctl *ida = idadata[cntlr];
unsigned int datalen = bp->b_bcount;
int thiskv = (int)bp->b_data;
physaddr_t thisphys = vtophys(thiskv);
int nsgb = 0; /* number of scatter/gather blocks used */
struct ida_sgb *sg = &(qcbp->sglist[0]);
/* fill in the qcb command header */
if (PCI_CONTROLLER(ida)) {
qcbp->hdr.priority = 0x00;
qcbp->hdr.flags = 0x24;
} else {
qcbp->hdr.priority = IDA_DEF_PRIORITY;
qcbp->hdr.flags = 0x10;
}
qcbp->hdr.drive = drv->drv_unit; /* logical drive number */
qcbp->buf = bp; /* the buf this command came from */
/* set up the scatter-gather list in the qcb */
while ((datalen) && (nsgb < IDA_MAX_SGLEN)) {
int bytes_this_seg = 0;
physaddr_t nextphys;
/* put in the base address */
sg->addr = thisphys;
/* do it at least once */
nextphys = thisphys;
while ((datalen) && (thisphys == nextphys)) {
int bytes_this_page;
/* This page is contiguous (physically) with the the last, */
/* just extend the length */
/* how far to the end of the page ... */
nextphys = (thisphys & (~(PAGESIZ - 1))) + PAGESIZ;
bytes_this_page = nextphys - thisphys;
/* ... or to the end of the data */
bytes_this_page = min(bytes_this_page, datalen);
bytes_this_seg += bytes_this_page;
datalen -= bytes_this_page;
/* get ready for the next page */
thiskv = (thiskv & (~(PAGESIZ - 1))) + PAGESIZ;
if (datalen)
thisphys = vtophys(thiskv);
}
/* next page isn't contiguous, finish the seg */
sg->len = bytes_this_seg;
sg++;
nsgb++;
}
if (datalen) { /* still data => command block too small */
printf("ida_queue_buf%d: more than %d scatter/gather blocks needed\n",
cntlr, IDA_MAX_SGLEN);
bp->b_error = EIO;
bp->b_flags |= B_ERROR;
biodone(bp);
return;
}
/* fill-in the I/O request block */
qcbp->req.error = 0;
qcbp->req.next = 0;
qcbp->req.blkno = bp->b_pblkno;
qcbp->req.bcount = bp->b_bcount >> 9;
qcbp->req.sgcount = nsgb;
qcbp->req.command = (bp->b_flags & B_READ ? IDA_READ_DATA : IDA_WRITE_DATA);
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS) {
printf("ida_rw%d: queuing:\n", cntlr);
ida_print_qcb(qcbp);
}
#endif
/* queue for submission to the controller */
ida_enqueue(cntlr, qcbp);
}
void
ida_start(int cntlr)
{
struct ida_ctl *ida = idadata[cntlr];
qcb_t *qcbp;
int count = 0;
int opri = splbio();
if (!ida->send_next) { /* check there is a job in the queue */
splx(opri);
return;
}
if (PCI_CONTROLLER(ida)) {
ida_pci_reg_t *reg = (ida_pci_reg_t *)ida->iobase;
u_int fifo = reg->initiate_fifo;
if (fifo == 1) {
splx(opri);
return; /* not sent - must try again later */
}
/* submit upto 16 jobs at once into the initiate fifo */
while (count < 16 && (fifo = reg->initiate_fifo) != 1
&& (qcbp = ida_dequeue(cntlr))) {
reg->initiate_fifo = qcbp->paddr;
qcbp->flags = QCB_SENT;
ida->inside++;
count++;
}
} else {
if (!IDA_CHAN_CLEAR(ida)) {
IDA_ENABLE_CHAN(ida);
splx(opri);
return;
}
qcbp = ida_dequeue(cntlr);
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS)
printf("ida%d: ida_start: sending 0x%x\n", cntlr, qcbp);
#endif
IDA_SET_EBELL_I(ida, 2);
IDA_SET_SUB_ADDR(ida, qcbp->paddr); /* physical address of this qcb */
IDA_SET_SUB_LEN(ida, sizeof(qcb_t));
IDA_SET_LBELL_I(ida, 1);
qcbp->flags = QCB_SENT;
ida->inside++;
count++;
}
if (ida->inside > ida->max_inside) {
ida->max_inside = ida->inside; /* new maximum */
splx(opri);
#ifdef IDADEBUG
if (ida_debug & IDA_MAXQCBS)
printf("ida%d: qcbs %d/%d\n", cntlr, ida->inside, ida->num_qcbs);
#endif
} else {
splx(opri);
}
#ifdef IDADEBUG
if ((ida_debug & IDA_SHOWSUBS) && count > 1)
printf("ida%d: %d jobs submitted (queue %s).\n",
cntlr, count, ida->send_next ? "not emptied" : "emptied");
#endif
}
void
ida_newqueue(int cntlr)
{
struct ida_ctl *ida = idadata[cntlr];
ida->send_next = 0;
ida->send_last = 0;
}
qcb_t *
ida_dequeue(int cntlr)
{
struct ida_ctl *ida = idadata[cntlr];
qcb_t *qcbp = ida->send_next; /* who is next? */
if (qcbp) { /* queue is not empty */
qcb_t* nextp = qcbp->next;
if (nextp) { /* more than one element in the queue */
nextp->last = 0; /* we are the first */
ida->send_next = nextp; /* hence first to go */
} else { /* exactly one element in the queue */
ida->send_last = ida->send_next = 0;
}
}
return qcbp;
}
static void
ida_enqueue(int cntlr, qcb_t *qcbp)
{
struct ida_ctl *ida = idadata[cntlr];
qcb_t *lastp = ida->send_last; /* who is last? */
int opri = splbio();
if (lastp) { /* if the queue is not empty */
lastp->next = qcbp; /* then we go after the last */
} else { /* if the queue was empty */
ida->send_next = qcbp; /* then we go next */
}
qcbp->last = lastp; /* we follow the last */
qcbp->next = 0; /* and nothing follows us */
ida->send_last = qcbp; /* we go last */
splx(opri);
}
void
idaintr(void *arg)
{
int cntlr = (int)arg;
qcb_t *qcbp;
struct ida_ctl *ida = idadata[cntlr];
u_char status;
physaddr_t paddr, paddr1; /* physical address of the qcb */
int offset; /* qcb offset */
u_char cstat; /* job status */
if (PCI_CONTROLLER(ida)) { /*pci:*/
ida_pci_reg_t *reg = (ida_pci_reg_t *)ida->iobase;
int status = reg->status;
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWINTS)
printf("ida%d: idaintr: status=%x (before complete)\n"
, cntlr, status);
#endif
while (status & IDA_PCI_PENDING) {
paddr1 = reg->complete_fifo;
paddr = paddr1 & ~3;
qcbp = ida_qcb_phys_kv(ida, paddr);
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS) {
printf("ida%d: idaintr: qcb(%x) completed\n", cntlr, qcbp);
ida_print_qcb(qcbp);
}
#endif
if (qcbp) {
if (qcbp->req.error & 3) ida_soft_errors++;
ida_done(cntlr, qcbp, (qcbp->req.error>>2) ? JOB_FAILURE : JOB_SUCCESS);
} else {
printf("ida%d: idaintr: completion (%x) ignored\n", cntlr, paddr1);
}
status = reg->status;
}
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWINTS)
printf("ida%d: idaintr: status=%x (before initiate)\n"
, cntlr, status);
#endif
if (status & IDA_PCI_READY) {
ida_start(cntlr);
}
} else {
while(1) {
status = IDA_READ_EBELL_I(ida) & IDA_READ_EBELL_E(ida);
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWINTS)
printf("ida%d: idaintr: status = 0x%x\n", cntlr, status);
#endif
if ((status & (BMIC_DATA_READY | BMIC_CHAN_CLEAR)) == 0) break;
if (status & BMIC_DATA_READY) { /* data ready */
int job_status;
if (IDA_READ_LBELL_I(ida) & JOB_ABORTED) {
printf("ida%d: idaintr: status:%x local channel should be busy! ",
cntlr, status);
}
paddr = IDA_GET_COM_ADDR(ida);
offset = IDA_GET_COM_OFFSET(ida);
cstat = IDA_GET_COM_STATUS(ida);
/* acknowledge interrupt */
IDA_ACK_CMD_COM(ida);
/* determine which job completed */
qcbp = ida_qcb_phys_kv(ida, paddr);
/* analyse the job status code */
if (cstat & IDA_COMPL_OK) {
job_status = JOB_SUCCESS;
} else {
printf("ida%d: idaintr: return code %x=", cntlr, cstat);
if (cstat & IDA_NON_FATAL) printf("recoverable error! ");
if (cstat & IDA_FATAL) printf("fatal error! ");
if (cstat & IDA_ABORTED) printf("aborted! ");
if (cstat & IDA_INVAL_REQ) printf("invalid request block! ");
if (cstat & IDA_INVAL_LIST) printf("cmd list error! ");
if (cstat & IDA_AARGH_LIST) printf("really bad cmd list! ");
job_status = JOB_FAILURE;
}
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWQCBS) {
printf("ida%d: idaintr: qcb(%x) returned.\n", cntlr, qcbp);
ida_print_qcb(qcbp);
}
#endif
ida_done(cntlr, qcbp, job_status); /* retire the job */
ida_start(cntlr); /* send the controller another job */
}
if (status & BMIC_CHAN_CLEAR) {
/* channel not clear */
IDA_DISABLE_CHAN(ida);
ida_start(cntlr); /* send the controller another job */
}
} /*eisa*/
}
}
int
ida_poll(cntlr, wait)
int cntlr;
int wait; /* delay in milliseconds */
{
struct ida_ctl *ctlp = idadata[cntlr];
if (PCI_CONTROLLER(ctlp)) {
printf("ida%d: error: ida_poll called on a PCI controller\n", cntlr);
return EIO;
}
while (wait-- > 0) {
if (IDA_DATA_READY(ctlp)) {
ctlp->com_addr = IDA_GET_COM_ADDR(ctlp);
ctlp->com_offset = IDA_GET_COM_OFFSET(ctlp);
ctlp->com_status = IDA_GET_COM_STATUS(ctlp);
IDA_ACK_CMD_COM(ctlp);
if (0) printf("ida_poll: addr=0x%08x off=0x%04x cmdstatus=0x%02x\n",
(u_int)ctlp->com_addr,
ctlp->com_offset,
ctlp->com_status);
return 0;
}
DELAY(1000);
}
printf("ida%d: board not responding\n", cntlr);
return EIO;
}
int
ida_submit(int cntlr, qcb_t *qcbp, int size)
{
struct ida_ctl *ida = idadata[cntlr];
int s = splbio();
if (PCI_CONTROLLER(ida)) {
ida_pci_reg_t *reg = (ida_pci_reg_t *)ida->iobase;
u_int fifo = reg->initiate_fifo;
if (fifo == 1) {
splx(s);
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWSUBS)
printf("ida%d: ida_submit(%x): fifo=1 not submitting\n",
cntlr, qcbp, fifo);
#endif
return(1); /* not sent - must try again later */
}
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWSUBS)
printf("ida%d: ida_submit(%x): fifo=%d submitting\n", cntlr, qcbp, fifo);
#endif
reg->initiate_fifo = qcbp->paddr;
} else {
if (!IDA_CHAN_CLEAR(ida)) {
IDA_ENABLE_CHAN(ida);
splx(s);
return(1); /* not sent - must try again later */
}
IDA_SET_EBELL_I(ida, 2);
IDA_SET_SUB_ADDR(ida, qcbp->paddr); /* physical address of this qcb */
IDA_SET_SUB_LEN(ida, size);
IDA_SET_LBELL_I(ida, 1);
}
splx(s);
return(0); /* sent */
}
static
void
ida_empty_pci_complete_fifo(int cntlr, ida_pci_reg_t *reg) {
u_long paddr;
if (paddr = reg->complete_fifo) {
int count = 200;
while (paddr && count > 0) {
printf("ida%d: command completion discarded (0x%x).\n",
cntlr, (u_int)paddr);
if (paddr = reg->complete_fifo) {
DELAY(100);
count--;
}
}
}
return;
}
static
u_long
ida_complete_pci_command(int cntlr, ida_pci_reg_t *reg) {
int count = 1;
u_long paddr;
while (count < 1000000) {
if (reg->status & IDA_PCI_PENDING) {
if ((paddr = reg->complete_fifo) == 0)
printf("ida%d: ida_complete_pci_command: zero address returned.\n",
cntlr);
else
return paddr;
}
DELAY(10);
}
return 1;
}
static
int
ida_submit_wait(int cntlr, qcb_t *qcbp, int size)
{
struct ida_ctl *ida = idadata[cntlr];
if (PCI_CONTROLLER(ida)) {
ida_pci_reg_t *reg = (ida_pci_reg_t *)ida->iobase;
int i, count = 1000000;
u_long paddr;
ida_empty_pci_complete_fifo(cntlr, reg);
reg->interrupt = IDA_PCI_DISABLE_INTS;
while (count > 0 && (i = reg->initiate_fifo) > 16) {
DELAY(10);
count--;
}
if (count == 0) {
printf("ida%d: ida_pci_submit_wait: fifo failed to clear - controller has failed.\n", cntlr);
return 1;
}
reg->initiate_fifo = qcbp->paddr;
paddr = ida_complete_pci_command(cntlr, reg);
if (paddr == 1) {
printf("ida%d: ida_pci_submit_wait timeout. No command list returned.\n",
cntlr);
return 1;
}
if (paddr != qcbp->paddr) {
printf("ida%d: ida_pci_submit_wait error. Invalid command list returned.\n",
cntlr);
return 1;
}
if (qcbp->req.error != 0xfe && qcbp->req.error == 0x40) {
printf("ida%d: ida_pci_submit_wait: Job error.\n", cntlr);
return 1;
}
} else {
if (ida_submit(cntlr, qcbp, size)) {
return 1;
}
if (ida_poll(cntlr, 10)) {
return 1;
}
}
return 0; /* sent */
}
void
ida_done(int cntlr, qcb_t *qcbp, int state)
{
struct buf *bp = qcbp->buf;
if (idadata[cntlr] > 0)
idadata[cntlr]->inside--; /* one less job inside the controller */
if (state != JOB_SUCCESS) {
#ifdef IDADEBUG
if (ida_debug & IDA_SHOWMISC)
printf("ida%d: ida_done: job failed 0x%x\n", cntlr, state);
#endif
/* we had a problem */
bp->b_error = EIO;
bp->b_flags |= B_ERROR;
} else {
struct ida_drv *drv = id_drive[dkunit(bp->b_dev)];
bp->b_resid = 0;
/* Update device stats */
devstat_end_transaction(&drv->dk_stats,
bp->b_bcount - bp->b_resid,
DEVSTAT_TAG_NONE,
(bp->b_flags & B_READ) ? DEVSTAT_READ : DEVSTAT_WRITE);
}
ida_free_qcb(cntlr, qcbp);
biodone(bp);
}
int
idsize(dev_t dev)
{
int unit = dkunit(dev);
struct ida_drv *drv;
if (unit >= NID)
return (-1);
drv = id_drive[unit];
if (!drv || !(drv->flags & ID_INIT))
return (-1);
return (dssize(dev, &drv->slices, idopen, idclose));
}
/*
* dump all of physical memory into the partition specified, starting
* at offset 'dumplo' into the partition.
*/
int
iddump(dev_t dev)
{ /* dump core after a system crash */
return 0; /* XXX */
}
static id_devsw_installed = 0;
static void
id_drvinit(void *unused)
{
if( ! id_devsw_installed ) {
cdevsw_add(&id_cdevsw);
id_devsw_installed = 1;
}
}
SYSINIT(iddev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+ID_CDMAJ,id_drvinit,NULL)
#ifdef IDADEBUG
void
ida_print_qcb(qcb_t *qcbp)
{
int i;
printf("qcb(%x): drive=%x priority=%x flags=%x sgcount=%d\n"
,qcbp
,qcbp->hdr.drive
,qcbp->hdr.priority
,qcbp->hdr.flags
,qcbp->req.sgcount);
printf("qcb(%x): next=%x command=%x error=%x blkno=%x bcount=%x\n"
,qcbp
,qcbp->req.next
,qcbp->req.command
,qcbp->req.error
,qcbp->req.blkno
,qcbp->req.bcount);
for (i=0; i < qcbp->req.sgcount; i++)
printf("qcb(%x): %x len=%x addr=%x\n"
,qcbp
,i
,qcbp->sglist[i].len
,qcbp->sglist[i].addr);
}
void
ida_print_active_qcb(int cntlr)
{
struct ida_ctl *ida = idadata[cntlr];
int i;
for (i=0; i < QCB_HASH_SIZE; i++) {
qcb_t *qcbp = ida->hashlist[i];
while (qcbp) {
if (qcbp->flags != QCB_FREE) {
ida_print_qcb(qcbp);
}
qcbp = qcbp->nexthash;
}
}
}
#endif /*IDADEBUG */