#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rd.c,v 1.126 2023/04/21 23:01:59 tsutsui Exp $");
#include "opt_useleds.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#include <sys/stat.h>
#include <sys/rndsource.h>
#include <hp300/dev/hpibvar.h>
#include <hp300/dev/rdreg.h>
#include <hp300/dev/rdvar.h>
#ifdef USELEDS
#include <hp300/hp300/leds.h>
#endif
#include "ioconf.h"
int rderrthresh = RDRETRY - 1;
#ifdef DEBUG
static const char *err_reject[] = {
0, 0,
"channel parity error",
0, 0,
"illegal opcode",
"module addressing",
"address bounds",
"parameter bounds",
"illegal parameter",
"message sequence",
0,
"message length",
0, 0, 0
};
static const char *err_fault[] = {
0,
"cross unit",
0,
"controller fault",
0, 0,
"unit fault",
0,
"diagnostic result",
0,
"operator release request",
"diagnostic release request",
"internal maintenance release request",
0,
"power fail",
"retransmit"
};
static const char *err_access[] = {
"illegal parallel operation",
"uninitialized media",
"no spares available",
"not ready",
"write protect",
"no data found",
0, 0,
"unrecoverable data overflow",
"unrecoverable data",
0,
"end of file",
"end of volume",
0, 0, 0
};
static const char *err_info[] = {
"operator release request",
"diagnostic release request",
"internal maintenance release request",
"media wear",
"latency induced",
0, 0,
"auto sparing invoked",
0,
"recoverable data overflow",
"marginal data",
"recoverable data",
0,
"maintenance track overflow",
0, 0
};
#define RDB_FOLLOW 0x01
#define RDB_STATUS 0x02
#define RDB_IDENT 0x04
#define RDB_IO 0x08
#define RDB_ASYNC 0x10
#define RDB_ERROR 0x80
int rddebug = RDB_ERROR | RDB_IDENT;
#endif
static const struct rdidentinfo rdidentinfo[] = {
[RD7945A] = {
.ri_hwid = RD7946AID,
.ri_desc = "7945A",
.ri_nbpt = NRD7945ABPT,
.ri_ntpc = NRD7945ATRK,
.ri_ncyl = NRD7945ACYL,
.ri_nblocks = NRD7945ABLK
},
[RD9134D] = {
.ri_hwid = RD9134DID,
.ri_desc = "9134D",
.ri_nbpt = NRD9134DBPT,
.ri_ntpc = NRD9134DTRK,
.ri_ncyl = NRD9134DCYL,
.ri_nblocks = NRD9134DBLK
},
[RD9122S] = {
.ri_hwid = RD9134LID,
.ri_desc = "9122S",
.ri_nbpt = NRD9122SBPT,
.ri_ntpc = NRD9122STRK,
.ri_ncyl = NRD9122SCYL,
.ri_nblocks = NRD9122SBLK
},
[RD7912P] = {
.ri_hwid = RD7912PID,
.ri_desc = "7912P",
.ri_nbpt = NRD7912PBPT,
.ri_ntpc = NRD7912PTRK,
.ri_ncyl = NRD7912PCYL,
.ri_nblocks = NRD7912PBLK
},
[RD7914P] = {
.ri_hwid = RD7914PID,
.ri_desc = "7914P",
.ri_nbpt = NRD7914PBPT,
.ri_ntpc = NRD7914PTRK,
.ri_ncyl = NRD7914PCYL,
.ri_nblocks = NRD7914PBLK
},
[RD7958A] = {
.ri_hwid = RD7958AID,
.ri_desc = "7958A",
.ri_nbpt = NRD7958ABPT,
.ri_ntpc = NRD7958ATRK,
.ri_ncyl = NRD7958ACYL,
.ri_nblocks = NRD7958ABLK
},
[RD7957A] = {
.ri_hwid = RD7957AID,
.ri_desc = "7957A",
.ri_nbpt = NRD7957ABPT,
.ri_ntpc = NRD7957ATRK,
.ri_ncyl = NRD7957ACYL,
.ri_nblocks = NRD7957ABLK
},
[RD7933H] = {
.ri_hwid = RD7933HID,
.ri_desc = "7933H",
.ri_nbpt = NRD7933HBPT,
.ri_ntpc = NRD7933HTRK,
.ri_ncyl = NRD7933HCYL,
.ri_nblocks = NRD7933HBLK
},
[RD9134L] = {
.ri_hwid = RD9134LID,
.ri_desc = "9134L",
.ri_nbpt = NRD9134LBPT,
.ri_ntpc = NRD9134LTRK,
.ri_ncyl = NRD9134LCYL,
.ri_nblocks = NRD9134LBLK
},
[RD7936H] = {
.ri_hwid = RD7936HID,
.ri_desc = "7936H",
.ri_nbpt = NRD7936HBPT,
.ri_ntpc = NRD7936HTRK,
.ri_ncyl = NRD7936HCYL,
.ri_nblocks = NRD7936HBLK
},
[RD7937H] = {
.ri_hwid = RD7937HID,
.ri_desc = "7937H",
.ri_nbpt = NRD7937HBPT,
.ri_ntpc = NRD7937HTRK,
.ri_ncyl = NRD7937HCYL,
.ri_nblocks = NRD7937HBLK
},
[RD7914CT] = {
.ri_hwid = RD7914CTID,
.ri_desc = "7914CT",
.ri_nbpt = NRD7914PBPT,
.ri_ntpc = NRD7914PTRK,
.ri_ncyl = NRD7914PCYL,
.ri_nblocks = NRD7914PBLK
},
[RD7946A] = {
.ri_hwid = RD7946AID,
.ri_desc = "7946A",
.ri_nbpt = NRD7945ABPT,
.ri_ntpc = NRD7945ATRK,
.ri_ncyl = NRD7945ACYL,
.ri_nblocks = NRD7945ABLK
},
[RD9122D] = {
.ri_hwid = RD9134LID,
.ri_desc = "9122D",
.ri_nbpt = NRD9122SBPT,
.ri_ntpc = NRD9122STRK,
.ri_ncyl = NRD9122SCYL,
.ri_nblocks = NRD9122SBLK
},
[RD7957B] = {
.ri_hwid = RD7957BID,
.ri_desc = "7957B",
.ri_nbpt = NRD7957BBPT,
.ri_ntpc = NRD7957BTRK,
.ri_ncyl = NRD7957BCYL,
.ri_nblocks = NRD7957BBLK
},
[RD7958B] = {
.ri_hwid = RD7958BID,
.ri_desc = "7958B",
.ri_nbpt = NRD7958BBPT,
.ri_ntpc = NRD7958BTRK,
.ri_ncyl = NRD7958BCYL,
.ri_nblocks = NRD7958BBLK
},
[RD7959B] = {
.ri_hwid = RD7959BID,
.ri_desc = "7959B",
.ri_nbpt = NRD7959BBPT,
.ri_ntpc = NRD7959BTRK,
.ri_ncyl = NRD7959BCYL,
.ri_nblocks = NRD7959BBLK
},
[RD2200A] = {
.ri_hwid = RD2200AID,
.ri_desc = "2200A",
.ri_nbpt = NRD2200ABPT,
.ri_ntpc = NRD2200ATRK,
.ri_ncyl = NRD2200ACYL,
.ri_nblocks = NRD2200ABLK
},
[RD2203A] = {
.ri_hwid = RD2203AID,
.ri_desc = "2203A",
.ri_nbpt = NRD2203ABPT,
.ri_ntpc = NRD2203ATRK,
.ri_ncyl = NRD2203ACYL,
.ri_nblocks = NRD2203ABLK
},
[RD2202A] = {
.ri_hwid = RD2202AID,
.ri_desc = "2202A",
.ri_nbpt = NRD2202ABPT,
.ri_ntpc = NRD2202ATRK,
.ri_ncyl = NRD2202ACYL,
.ri_nblocks = NRD2202ABLK
},
[RD7908A] = {
.ri_hwid = RD7908AID,
.ri_desc = "7908A",
.ri_nbpt = NRD7908ABPT,
.ri_ntpc = NRD7908ATRK,
.ri_ncyl = NRD7908ACYL,
.ri_nblocks = NRD7908ABLK
},
[RD7911A] = {
.ri_hwid = RD7911AID,
.ri_desc = "7911A",
.ri_nbpt = NRD7911ABPT,
.ri_ntpc = NRD7911ATRK,
.ri_ncyl = NRD7911ACYL,
.ri_nblocks = NRD7911ABLK
},
[RD7941A] = {
.ri_hwid = RD7946AID,
.ri_desc = "7941A",
.ri_nbpt = NRD7941ABPT,
.ri_ntpc = NRD7941ATRK,
.ri_ncyl = NRD7941ACYL,
.ri_nblocks = NRD7941ABLK
}
};
static const int numrdidentinfo = __arraycount(rdidentinfo);
struct rdname2id {
const char *rn_name;
int rn_id;
};
static const struct rdname2id rdname2id[] = {
{ RD7945ANAME, RD7945A },
{ RD9134DNAME, RD9134D },
{ RD7912PNAME, RD7912P },
{ RD7914PNAME, RD7914P },
{ RD7958ANAME, RD7958A },
{ RD7957ANAME, RD7957A },
{ RD7933HNAME, RD7933H },
{ RD9134LNAME, RD9134L },
{ RD7936HNAME, RD7936H },
{ RD7937HNAME, RD7937H },
{ RD7914CTNAME, RD7914CT },
{ RD9122DNAME, RD9122D },
{ RD7957BNAME, RD7957B },
{ RD7958BNAME, RD7958B },
{ RD7959BNAME, RD7959B },
{ RD2200ANAME, RD2200A },
{ RD2203ANAME, RD2203A },
{ RD2202ANAME, RD2202A },
{ RD7908ANAME, RD7908A },
{ RD7911ANAME, RD7911A },
{ RD7941ANAME, RD7941A }
};
static const int numrdname2id = __arraycount(rdname2id);
static int rdident(device_t, struct rd_softc *,
struct hpibbus_attach_args *);
static void rdreset(struct rd_softc *);
static void rdreset_unit(int, int, int);
static void rd_set_geom(struct rd_softc *);
static int rdgetinfo(dev_t);
static void rdgetdefaultlabel(struct rd_softc *, struct disklabel *);
static void rdrestart(void *);
static void rdustart(struct rd_softc *);
static struct buf *rdfinish(struct rd_softc *, struct buf *);
static void rdstart(void *);
static void rdgo(void *);
static void rdintr(void *);
static int rdstatus(struct rd_softc *);
static int rderror(int);
#ifdef DEBUG
static void rdprinterr(const char *, short, const char **);
#endif
static int rdmatch(device_t, cfdata_t, void *);
static void rdattach(device_t, device_t, void *);
CFATTACH_DECL_NEW(rd, sizeof(struct rd_softc),
rdmatch, rdattach, NULL, NULL);
static dev_type_open(rdopen);
static dev_type_close(rdclose);
static dev_type_read(rdread);
static dev_type_write(rdwrite);
static dev_type_ioctl(rdioctl);
static dev_type_strategy(rdstrategy);
static dev_type_dump(rddump);
static dev_type_size(rdsize);
const struct bdevsw rd_bdevsw = {
.d_open = rdopen,
.d_close = rdclose,
.d_strategy = rdstrategy,
.d_ioctl = rdioctl,
.d_dump = rddump,
.d_psize = rdsize,
.d_discard = nodiscard,
.d_flag = D_DISK
};
const struct cdevsw rd_cdevsw = {
.d_open = rdopen,
.d_close = rdclose,
.d_read = rdread,
.d_write = rdwrite,
.d_ioctl = rdioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_DISK
};
static int
rdmatch(device_t parent, cfdata_t cf, void *aux)
{
struct hpibbus_attach_args *ha = aux;
return rdident(parent, NULL, ha);
}
static void
rdattach(device_t parent, device_t self, void *aux)
{
struct rd_softc *sc = device_private(self);
struct hpibbus_attach_args *ha = aux;
int id;
char pbuf[9];
sc->sc_dev = self;
bufq_alloc(&sc->sc_tab, "disksort", BUFQ_SORT_RAWBLOCK);
if (rdident(parent, sc, ha) == 0) {
aprint_error(": didn't respond to describe command!\n");
return;
}
id = sc->sc_type;
aprint_normal(": %s\n", rdidentinfo[id].ri_desc);
format_bytes(pbuf, sizeof(pbuf),
rdidentinfo[id].ri_nblocks * DEV_BSIZE);
aprint_normal_dev(sc->sc_dev, "%s, %d cyl, %d head, %d sec,"
" %d bytes/block x %u blocks\n",
pbuf, rdidentinfo[id].ri_ncyl, rdidentinfo[id].ri_ntpc,
rdidentinfo[id].ri_nbpt,
DEV_BSIZE, rdidentinfo[id].ri_nblocks);
memset(&sc->sc_dkdev, 0, sizeof(sc->sc_dkdev));
disk_init(&sc->sc_dkdev, device_xname(sc->sc_dev), NULL);
disk_attach(&sc->sc_dkdev);
rd_set_geom(sc);
sc->sc_slave = ha->ha_slave;
sc->sc_punit = ha->ha_punit;
callout_init(&sc->sc_restart_ch, 0);
sc->sc_hq.hq_softc = sc;
sc->sc_hq.hq_slave = sc->sc_slave;
sc->sc_hq.hq_start = rdstart;
sc->sc_hq.hq_go = rdgo;
sc->sc_hq.hq_intr = rdintr;
sc->sc_flags = RDF_ALIVE;
#ifdef DEBUG
if ((rddebug & RDB_ERROR) != 0)
rderrthresh = 0;
#endif
rnd_attach_source(&sc->rnd_source, device_xname(sc->sc_dev),
RND_TYPE_DISK, RND_FLAG_DEFAULT);
}
static int
rdident(device_t parent, struct rd_softc *sc, struct hpibbus_attach_args *ha)
{
struct cs80_describe desc;
uint8_t stat, cmd[3];
char name[7];
int i, id, n, ctlr, slave;
ctlr = device_unit(parent);
slave = ha->ha_slave;
if ((ha->ha_id & 0x200) == 0)
return 0;
for (id = 0; id < numrdidentinfo; id++)
if (ha->ha_id == rdidentinfo[id].ri_hwid)
break;
if (id == numrdidentinfo)
return 0;
memset(&desc, 0, sizeof(desc));
stat = 0;
rdreset_unit(ctlr, slave, ha->ha_punit);
cmd[0] = C_SUNIT(ha->ha_punit);
cmd[1] = C_SVOL(0);
cmd[2] = C_DESC;
hpibsend(ctlr, slave, C_CMD, cmd, sizeof(cmd));
hpibrecv(ctlr, slave, C_EXEC, &desc, sizeof(desc));
hpibrecv(ctlr, slave, C_QSTAT, &stat, sizeof(stat));
if (stat != 0 || desc.d_name == 0) {
return 0;
}
if (sc == NULL)
return 1;
memset(name, 0, sizeof(name));
n = desc.d_name;
for (i = 5; i >= 0; i--) {
name[i] = (n & 0xf) + '0';
n >>= 4;
}
#ifdef DEBUG
if (rddebug & RDB_IDENT) {
aprint_normal("\n");
aprint_normal_dev(sc->sc_dev, "id: 0x%04x, name: %x ('%s')\n",
ha->ha_id, desc.d_name, name);
aprint_normal(" iuw %x, maxxfr %d, ctype %d\n",
desc.d_iuw, desc.d_cmaxxfr, desc.d_ctype);
aprint_normal(" utype %d, bps %d, blkbuf %d, burst %d,"
" blktime %d\n",
desc.d_utype, desc.d_sectsize,
desc.d_blkbuf, desc.d_burstsize, desc.d_blocktime);
aprint_normal(" avxfr %d, ort %d, atp %d, maxint %d, fv %x"
", rv %x\n",
desc.d_uavexfr, desc.d_retry, desc.d_access,
desc.d_maxint, desc.d_fvbyte, desc.d_rvbyte);
aprint_normal(" maxcyl/head/sect %d/%d/%d, maxvsect %d,"
" inter %d\n",
desc.d_maxcyl, desc.d_maxhead, desc.d_maxsect,
desc.d_maxvsectl, desc.d_interleave);
aprint_normal("%s", device_xname(sc->sc_dev));
}
#endif
switch (ha->ha_id) {
case RD7946AID:
if (memcmp(name, RD7945ANAME, RDNAMELEN) == 0)
id = RD7945A;
else if (memcmp(name, RD7941ANAME, RDNAMELEN) == 0)
id = RD7941A;
else
id = RD7946A;
break;
case RD9134LID:
if (memcmp(name, RD9134LNAME, RDNAMELEN) == 0)
id = RD9134L;
else
id = RD9122D;
break;
case RD9134DID:
if (memcmp(name, RD9122SNAME, RDNAMELEN) == 0)
id = RD9122S;
else
id = RD9134D;
break;
}
if (ha->ha_punit >= 1) {
for (i = 0; i < numrdname2id; i++) {
if (memcmp(name, rdname2id[i].rn_name,
RDNAMELEN) == 0) {
id = rdname2id[i].rn_id;
break;
}
}
}
sc->sc_type = id;
return 1;
}
static void
rdreset(struct rd_softc *sc)
{
int ctlr, slave, punit;
ctlr = device_unit(device_parent(sc->sc_dev));
slave = sc->sc_slave;
punit = sc->sc_punit;
rdreset_unit(ctlr, slave, punit);
#ifdef DEBUG
sc->sc_stats.rdresets++;
#endif
}
static void
rdreset_unit(int ctlr, int slave, int punit)
{
struct rd_ssmcmd ssmc;
struct rd_srcmd src;
struct rd_clearcmd clear;
uint8_t stat;
clear.c_unit = C_SUNIT(punit);
clear.c_cmd = C_CLEAR;
hpibsend(ctlr, slave, C_TCMD, &clear, sizeof(clear));
hpibswait(ctlr, slave);
hpibrecv(ctlr, slave, C_QSTAT, &stat, sizeof(stat));
src.c_unit = C_SUNIT(RDCTLR);
src.c_nop = C_NOP;
src.c_cmd = C_SREL;
src.c_param = C_REL;
hpibsend(ctlr, slave, C_CMD, &src, sizeof(src));
hpibswait(ctlr, slave);
hpibrecv(ctlr, slave, C_QSTAT, &stat, sizeof(stat));
ssmc.c_unit = C_SUNIT(punit);
ssmc.c_cmd = C_SSM;
ssmc.c_refm = REF_MASK;
ssmc.c_fefm = FEF_MASK;
ssmc.c_aefm = AEF_MASK;
ssmc.c_iefm = IEF_MASK;
hpibsend(ctlr, slave, C_CMD, &ssmc, sizeof(ssmc));
hpibswait(ctlr, slave);
hpibrecv(ctlr, slave, C_QSTAT, &stat, sizeof(stat));
}
static void
rd_set_geom(struct rd_softc *sc)
{
struct disk_geom *dg = &sc->sc_dkdev.dk_geom;
const struct rdidentinfo *ri = &rdidentinfo[sc->sc_type];
memset(dg, 0, sizeof(*dg));
dg->dg_secsize = DEV_BSIZE;
dg->dg_nsectors = ri->ri_nbpt;
dg->dg_ntracks = ri->ri_ntpc;
dg->dg_ncylinders = ri->ri_ncyl;
dg->dg_secperunit = ri->ri_nblocks;
disk_set_info(sc->sc_dev, &sc->sc_dkdev, ri->ri_desc);
}
static int
rdgetinfo(dev_t dev)
{
struct rd_softc *sc = device_lookup_private(&rd_cd, rdunit(dev));
struct disklabel *lp = sc->sc_dkdev.dk_label;
struct partition *pi;
const char *msg;
memset((void *)lp, 0, sizeof *lp);
rdgetdefaultlabel(sc, lp);
msg = readdisklabel(rdlabdev(dev), rdstrategy, lp, NULL);
if (msg == NULL)
return 0;
pi = lp->d_partitions;
printf("%s: WARNING: %s\n", device_xname(sc->sc_dev), msg);
pi[RAW_PART].p_size = rdidentinfo[sc->sc_type].ri_nblocks;
lp->d_npartitions = 3;
pi[0].p_size = 0;
return 0;
}
static int
rdopen(dev_t dev, int flags, int mode, struct lwp *l)
{
struct rd_softc *sc;
int error, mask, part;
sc = device_lookup_private(&rd_cd, rdunit(dev));
if (sc == NULL)
return ENXIO;
if ((sc->sc_flags & RDF_ALIVE) == 0)
return ENXIO;
while ((sc->sc_flags & (RDF_OPENING | RDF_CLOSING)) != 0)
(void)tsleep(sc, PRIBIO, "rdopen", 0);
if (sc->sc_dkdev.dk_openmask == 0) {
sc->sc_flags |= RDF_OPENING;
error = rdgetinfo(dev);
sc->sc_flags &= ~RDF_OPENING;
wakeup((void *)sc);
if (error)
return error;
}
part = rdpart(dev);
mask = 1 << part;
if (part != RAW_PART &&
(part > sc->sc_dkdev.dk_label->d_npartitions ||
sc->sc_dkdev.dk_label->d_partitions[part].p_fstype == FS_UNUSED))
return ENXIO;
switch (mode) {
case S_IFCHR:
sc->sc_dkdev.dk_copenmask |= mask;
break;
case S_IFBLK:
sc->sc_dkdev.dk_bopenmask |= mask;
break;
}
sc->sc_dkdev.dk_openmask =
sc->sc_dkdev.dk_copenmask | sc->sc_dkdev.dk_bopenmask;
return 0;
}
static int
rdclose(dev_t dev, int flag, int mode, struct lwp *l)
{
struct rd_softc *sc = device_lookup_private(&rd_cd, rdunit(dev));
struct disk *dk = &sc->sc_dkdev;
int mask, s;
mask = 1 << rdpart(dev);
if (mode == S_IFCHR)
dk->dk_copenmask &= ~mask;
else
dk->dk_bopenmask &= ~mask;
dk->dk_openmask = dk->dk_copenmask | dk->dk_bopenmask;
if (dk->dk_openmask == 0) {
sc->sc_flags |= RDF_CLOSING;
s = splbio();
while (sc->sc_active) {
sc->sc_flags |= RDF_WANTED;
(void)tsleep(&sc->sc_tab, PRIBIO, "rdclose", 0);
}
splx(s);
sc->sc_flags &= ~(RDF_CLOSING | RDF_WLABEL);
wakeup((void *)sc);
}
return 0;
}
static void
rdstrategy(struct buf *bp)
{
struct rd_softc *sc = device_lookup_private(&rd_cd, rdunit(bp->b_dev));
struct partition *pinfo;
daddr_t bn;
int s;
int offset;
#ifdef DEBUG
if ((rddebug & RDB_FOLLOW) != 0)
printf("rdstrategy(%p): dev %" PRIx64
", bn %llx, bcount %x, %c\n",
bp, bp->b_dev, bp->b_blkno, bp->b_bcount,
(bp->b_flags & B_READ) != 0 ? 'R' : 'W');
#endif
bn = bp->b_blkno;
pinfo = &sc->sc_dkdev.dk_label->d_partitions[rdpart(bp->b_dev)];
offset = (rdpart(bp->b_dev) == RAW_PART) ? 0 : pinfo->p_offset;
if (rdpart(bp->b_dev) == RAW_PART) {
if (bounds_check_with_mediasize(bp, DEV_BSIZE,
rdidentinfo[sc->sc_type].ri_nblocks) <= 0)
goto done;
} else {
if (bounds_check_with_label(&sc->sc_dkdev, bp,
(sc->sc_flags & RDF_WLABEL) != 0) <= 0)
goto done;
}
bp->b_rawblkno = bn + offset;
s = splbio();
bufq_put(sc->sc_tab, bp);
if (sc->sc_active == 0) {
sc->sc_active = 1;
rdustart(sc);
}
splx(s);
return;
done:
biodone(bp);
}
static void
rdrestart(void *arg)
{
struct rd_softc *sc = arg;
int s;
s = splbio();
rdustart(sc);
splx(s);
}
static void
rdustart(struct rd_softc *sc)
{
struct buf *bp;
bp = bufq_peek(sc->sc_tab);
sc->sc_addr = bp->b_data;
sc->sc_resid = bp->b_bcount;
if (hpibreq(device_parent(sc->sc_dev), &sc->sc_hq))
rdstart(sc);
}
static struct buf *
rdfinish(struct rd_softc *sc, struct buf *bp)
{
sc->sc_errcnt = 0;
(void)bufq_get(sc->sc_tab);
bp->b_resid = 0;
biodone(bp);
hpibfree(device_parent(sc->sc_dev), &sc->sc_hq);
if ((bp = bufq_peek(sc->sc_tab)) != NULL)
return bp;
sc->sc_active = 0;
if ((sc->sc_flags & RDF_WANTED) != 0) {
sc->sc_flags &= ~RDF_WANTED;
wakeup((void *)&sc->sc_tab);
}
return NULL;
}
static void
rdstart(void *arg)
{
struct rd_softc *sc = arg;
struct buf *bp = bufq_peek(sc->sc_tab);
int ctlr, slave;
ctlr = device_unit(device_parent(sc->sc_dev));
slave = sc->sc_slave;
again:
#ifdef DEBUG
if (rddebug & RDB_FOLLOW)
printf("rdstart(%s): bp %p, %c\n", device_xname(sc->sc_dev), bp,
(bp->b_flags & B_READ) ? 'R' : 'W');
#endif
sc->sc_flags |= RDF_SEEK;
sc->sc_ioc.c_unit = C_SUNIT(sc->sc_punit);
sc->sc_ioc.c_volume = C_SVOL(0);
sc->sc_ioc.c_saddr = C_SADDR;
sc->sc_ioc.c_hiaddr = 0;
sc->sc_ioc.c_addr = RDBTOS(bp->b_rawblkno);
sc->sc_ioc.c_nop2 = C_NOP;
sc->sc_ioc.c_slen = C_SLEN;
sc->sc_ioc.c_len = sc->sc_resid;
sc->sc_ioc.c_cmd = (bp->b_flags & B_READ) != 0 ? C_READ : C_WRITE;
#ifdef DEBUG
if ((rddebug & RDB_IO) != 0)
printf("rdstart: hpibsend(%x, %x, %x, %p, %x)\n",
ctlr, slave, C_CMD,
&sc->sc_ioc.c_unit, sizeof(sc->sc_ioc) - 2);
#endif
if (hpibsend(ctlr, slave, C_CMD, &sc->sc_ioc.c_unit,
sizeof(sc->sc_ioc) - 2) == sizeof(sc->sc_ioc) - 2) {
disk_busy(&sc->sc_dkdev);
iostat_seek(sc->sc_dkdev.dk_stats);
#ifdef DEBUG
if ((rddebug & RDB_IO) != 0)
printf("rdstart: hpibawait(%x)\n", ctlr);
#endif
hpibawait(ctlr);
return;
}
#ifdef DEBUG
if ((rddebug & RDB_ERROR) != 0)
printf("%s: rdstart: cmd %x adr %x blk %lld len %d ecnt %d\n",
device_xname(sc->sc_dev),
sc->sc_ioc.c_cmd, sc->sc_ioc.c_addr,
bp->b_blkno, sc->sc_resid, sc->sc_errcnt);
sc->sc_stats.rdretries++;
#endif
sc->sc_flags &= ~RDF_SEEK;
rdreset(sc);
if (sc->sc_errcnt++ < RDRETRY)
goto again;
printf("%s: rdstart err: cmd 0x%x sect %u blk %" PRId64 " len %d\n",
device_xname(sc->sc_dev), sc->sc_ioc.c_cmd, sc->sc_ioc.c_addr,
bp->b_blkno, sc->sc_resid);
bp->b_error = EIO;
bp = rdfinish(sc, bp);
if (bp != NULL) {
sc->sc_addr = bp->b_data;
sc->sc_resid = bp->b_bcount;
if (hpibreq(device_parent(sc->sc_dev), &sc->sc_hq))
goto again;
}
}
static void
rdgo(void *arg)
{
struct rd_softc *sc = arg;
struct buf *bp = bufq_peek(sc->sc_tab);
int rw, ctlr, slave;
ctlr = device_unit(device_parent(sc->sc_dev));
slave = sc->sc_slave;
rw = bp->b_flags & B_READ;
disk_busy(&sc->sc_dkdev);
#ifdef USELEDS
ledcontrol(0, 0, LED_DISK);
#endif
hpibgo(ctlr, slave, C_EXEC, sc->sc_addr, sc->sc_resid, rw, rw != 0);
}
static void
rdintr(void *arg)
{
struct rd_softc *sc = arg;
int unit = device_unit(sc->sc_dev);
struct buf *bp = bufq_peek(sc->sc_tab);
uint8_t stat = 13;
int rv, restart, ctlr, slave;
ctlr = device_unit(device_parent(sc->sc_dev));
slave = sc->sc_slave;
#ifdef DEBUG
if ((rddebug & RDB_FOLLOW) != 0)
printf("rdintr(%d): bp %p, %c, flags %x\n", unit, bp,
(bp->b_flags & B_READ) ? 'R' : 'W', sc->sc_flags);
if (bp == NULL) {
printf("%s: bp == NULL\n", device_xname(sc->sc_dev));
return;
}
#endif
disk_unbusy(&sc->sc_dkdev, (bp->b_bcount - bp->b_resid),
(bp->b_flags & B_READ));
if ((sc->sc_flags & RDF_SEEK) != 0) {
sc->sc_flags &= ~RDF_SEEK;
if (hpibustart(ctlr))
rdgo(sc);
return;
}
if ((sc->sc_flags & RDF_SWAIT) == 0) {
#ifdef DEBUG
sc->sc_stats.rdpolltries++;
#endif
if (hpibpptest(ctlr, slave) == 0) {
#ifdef DEBUG
sc->sc_stats.rdpollwaits++;
#endif
disk_busy(&sc->sc_dkdev);
sc->sc_flags |= RDF_SWAIT;
hpibawait(ctlr);
return;
}
} else
sc->sc_flags &= ~RDF_SWAIT;
rv = hpibrecv(ctlr, slave, C_QSTAT, &stat, 1);
if (rv != 1 || stat != 0) {
#ifdef DEBUG
if (rddebug & RDB_ERROR)
printf("rdintr: recv failed or bad stat %d\n", stat);
#endif
restart = rderror(unit);
#ifdef DEBUG
sc->sc_stats.rdretries++;
#endif
if (sc->sc_errcnt++ < RDRETRY) {
if (restart)
rdstart(sc);
return;
}
bp->b_error = EIO;
}
if (rdfinish(sc, bp))
rdustart(sc);
rnd_add_uint32(&sc->rnd_source, bp->b_blkno);
}
static int
rdstatus(struct rd_softc *sc)
{
int c, s;
uint8_t stat;
int rv;
c = device_unit(device_parent(sc->sc_dev));
s = sc->sc_slave;
sc->sc_rsc.c_unit = C_SUNIT(sc->sc_punit);
sc->sc_rsc.c_sram = C_SRAM;
sc->sc_rsc.c_ram = C_RAM;
sc->sc_rsc.c_cmd = C_STATUS;
memset((void *)&sc->sc_stat, 0, sizeof(sc->sc_stat));
rv = hpibsend(c, s, C_CMD, &sc->sc_rsc, sizeof(sc->sc_rsc));
if (rv != sizeof(sc->sc_rsc)) {
#ifdef DEBUG
if ((rddebug & RDB_STATUS) != 0)
printf("rdstatus: send C_CMD failed %d != %d\n",
rv, sizeof(sc->sc_rsc));
#endif
return 1;
}
rv = hpibrecv(c, s, C_EXEC, &sc->sc_stat, sizeof(sc->sc_stat));
if (rv != sizeof(sc->sc_stat)) {
#ifdef DEBUG
if ((rddebug & RDB_STATUS) != 0)
printf("rdstatus: send C_EXEC failed %d != %d\n",
rv, sizeof(sc->sc_stat));
#endif
return 1;
}
rv = hpibrecv(c, s, C_QSTAT, &stat, 1);
if (rv != 1 || stat != 0) {
#ifdef DEBUG
if ((rddebug & RDB_STATUS) != 0)
printf("rdstatus: recv failed %d or bad stat %d\n",
rv, stat);
#endif
return 1;
}
return 0;
}
static int
rderror(int unit)
{
struct rd_softc *sc = device_lookup_private(&rd_cd, unit);
struct rd_stat *sp;
struct buf *bp;
daddr_t hwbn, pbn;
if (rdstatus(sc) != 0) {
#ifdef DEBUG
printf("%s: couldn't get status\n", device_xname(sc->sc_dev));
#endif
rdreset(sc);
return 1;
}
sp = &sc->sc_stat;
if ((sp->c_fef & FEF_REXMT) != 0)
return 1;
if ((sp->c_fef & FEF_PF) != 0) {
rdreset(sc);
return 1;
}
if ((sp->c_fef & FEF_IMR) != 0) {
int rdtimo = RDWAITC << sc->sc_errcnt;
#ifdef DEBUG
printf("%s: internal maintenance, %d second timeout\n",
device_xname(sc->sc_dev), rdtimo);
sc->sc_stats.rdtimeouts++;
#endif
hpibfree(device_parent(sc->sc_dev), &sc->sc_hq);
callout_reset(&sc->sc_restart_ch, rdtimo * hz, rdrestart, sc);
return 0;
}
if (sc->sc_errcnt < rderrthresh)
return 1;
bp = bufq_peek(sc->sc_tab);
pbn = sc->sc_dkdev.dk_label->d_partitions[rdpart(bp->b_dev)].p_offset;
if ((sp->c_fef & FEF_CU) != 0 || (sp->c_fef & FEF_DR) != 0 ||
(sp->c_ief & IEF_RRMASK) != 0) {
hwbn = RDBTOS(pbn + bp->b_blkno);
pbn = bp->b_blkno;
} else {
hwbn = sp->c_blk;
pbn = RDSTOB(hwbn) - pbn;
}
printf("%s%c: hard error sn%" PRId64 "\n", device_xname(sc->sc_dev),
'a' + rdpart(bp->b_dev), pbn);
printf("%s %s error:", device_xname(sc->sc_dev),
(bp->b_flags & B_READ) != 0 ? "read" : "write");
#ifdef DEBUG
if (rddebug & RDB_ERROR) {
printf("\n volume: %d, unit: %d\n",
(sp->c_vu >> 4) & 0xF, sp->c_vu & 0xF);
rdprinterr("reject", sp->c_ref, err_reject);
rdprinterr("fault", sp->c_fef, err_fault);
rdprinterr("access", sp->c_aef, err_access);
rdprinterr("info", sp->c_ief, err_info);
printf(" block: %lld, P1-P10: ", hwbn);
printf("0x%x", *(uint32_t *)&sp->c_raw[0]);
printf("0x%x", *(uint32_t *)&sp->c_raw[4]);
printf("0x%x\n", *(uint16_t *)&sp->c_raw[8]);
printf(" ioc: ");
printf("0x%x", *(uint32_t *)&sc->sc_ioc.c_pad);
printf("0x%x", *(uint16_t *)&sc->sc_ioc.c_hiaddr);
printf("0x%x", *(uint32_t *)&sc->sc_ioc.c_addr);
printf("0x%x", *(uint16_t *)&sc->sc_ioc.c_nop2);
printf("0x%x", *(uint32_t *)&sc->sc_ioc.c_len);
printf("0x%x\n", *(uint16_t *)&sc->sc_ioc.c_cmd);
return 1;
}
#endif
printf(" v%d u%d, R0x%x F0x%x A0x%x I0x%x\n",
(sp->c_vu >> 4) & 0xF, sp->c_vu & 0xF,
sp->c_ref, sp->c_fef, sp->c_aef, sp->c_ief);
printf("P1-P10: ");
printf("0x%x", *(uint32_t *)&sp->c_raw[0]);
printf("0x%x", *(uint32_t *)&sp->c_raw[4]);
printf("0x%x\n", *(uint16_t *)&sp->c_raw[8]);
return 1;
}
static int
rdread(dev_t dev, struct uio *uio, int flags)
{
return physio(rdstrategy, NULL, dev, B_READ, minphys, uio);
}
static int
rdwrite(dev_t dev, struct uio *uio, int flags)
{
return physio(rdstrategy, NULL, dev, B_WRITE, minphys, uio);
}
static int
rdioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
struct rd_softc *sc = device_lookup_private(&rd_cd, rdunit(dev));
struct disklabel *lp = sc->sc_dkdev.dk_label;
int error, flags;
error = disk_ioctl(&sc->sc_dkdev, dev, cmd, data, flag, l);
if (error != EPASSTHROUGH)
return error;
switch (cmd) {
case DIOCWLABEL:
if ((flag & FWRITE) == 0)
return EBADF;
if (*(int *)data)
sc->sc_flags |= RDF_WLABEL;
else
sc->sc_flags &= ~RDF_WLABEL;
return 0;
case DIOCSDINFO:
if ((flag & FWRITE) == 0)
return EBADF;
return setdisklabel(lp, (struct disklabel *)data,
(sc->sc_flags & RDF_WLABEL) ? 0 : sc->sc_dkdev.dk_openmask,
NULL);
case DIOCWDINFO:
if ((flag & FWRITE) == 0)
return EBADF;
error = setdisklabel(lp, (struct disklabel *)data,
(sc->sc_flags & RDF_WLABEL) ? 0 : sc->sc_dkdev.dk_openmask,
NULL);
if (error != 0)
return error;
flags = sc->sc_flags;
sc->sc_flags = RDF_ALIVE | RDF_WLABEL;
error = writedisklabel(rdlabdev(dev), rdstrategy, lp, NULL);
sc->sc_flags = flags;
return error;
case DIOCGDEFLABEL:
rdgetdefaultlabel(sc, (struct disklabel *)data);
return 0;
case DIOCCACHESYNC:
return 0;
}
return EINVAL;
}
static void
rdgetdefaultlabel(struct rd_softc *sc, struct disklabel *lp)
{
int type = sc->sc_type;
memset((void *)lp, 0, sizeof(struct disklabel));
lp->d_type = DKTYPE_HPIB;
lp->d_secsize = DEV_BSIZE;
lp->d_nsectors = rdidentinfo[type].ri_nbpt;
lp->d_ntracks = rdidentinfo[type].ri_ntpc;
lp->d_ncylinders = rdidentinfo[type].ri_ncyl;
lp->d_secperunit = rdidentinfo[type].ri_nblocks;
lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors;
strlcpy(lp->d_typename, rdidentinfo[type].ri_desc,
sizeof(lp->d_typename));
strlcpy(lp->d_packname, "fictitious", sizeof(lp->d_packname));
lp->d_rpm = 3000;
lp->d_interleave = 1;
lp->d_flags = 0;
lp->d_partitions[RAW_PART].p_offset = 0;
lp->d_partitions[RAW_PART].p_size =
lp->d_secperunit * (lp->d_secsize / DEV_BSIZE);
lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED;
lp->d_npartitions = RAW_PART + 1;
lp->d_magic = DISKMAGIC;
lp->d_magic2 = DISKMAGIC;
lp->d_checksum = dkcksum(lp);
}
static int
rdsize(dev_t dev)
{
struct rd_softc *sc;
int psize, didopen = 0;
sc = device_lookup_private(&rd_cd, rdunit(dev));
if (sc == NULL)
return ENXIO;
if ((sc->sc_flags & RDF_ALIVE) == 0)
return ENXIO;
if (sc->sc_dkdev.dk_openmask == 0) {
if (rdopen(dev, FREAD | FWRITE, S_IFBLK, NULL))
return -1;
didopen = 1;
}
psize = sc->sc_dkdev.dk_label->d_partitions[rdpart(dev)].p_size *
(sc->sc_dkdev.dk_label->d_secsize / DEV_BSIZE);
if (didopen)
(void)rdclose(dev, FREAD | FWRITE, S_IFBLK, NULL);
return psize;
}
#ifdef DEBUG
static void
rdprinterr(const char *str, short err, const char **tab)
{
int i;
int printed;
if (err == 0)
return;
printf(" %s error %d field:", str, err);
printed = 0;
for (i = 0; i < 16; i++)
if ((err & (0x8000 >> i)) != 0)
printf("%s%s", printed++ ? " + " : " ", tab[i]);
printf("\n");
}
#endif
static int rddoingadump;
static int
rddump(dev_t dev, daddr_t blkno, void *va, size_t size)
{
int sectorsize;
int nsects;
int sectoff;
int totwrt;
int nwrt;
int part;
int ctlr, slave;
struct rd_softc *sc;
struct disklabel *lp;
char stat;
if (rddoingadump)
return EFAULT;
rddoingadump = 1;
part = rdpart(dev);
sc = device_lookup_private(&rd_cd, rdunit(dev));
if (sc == NULL)
return ENXIO;
if ((sc->sc_flags & RDF_ALIVE) == 0)
return ENXIO;
ctlr = device_unit(device_parent(sc->sc_dev));
slave = sc->sc_slave;
lp = sc->sc_dkdev.dk_label;
sectorsize = lp->d_secsize;
if ((size % sectorsize) != 0)
return EFAULT;
totwrt = size / sectorsize;
blkno = dbtob(blkno) / sectorsize;
nsects = lp->d_partitions[part].p_size;
sectoff = lp->d_partitions[part].p_offset;
if ((blkno < 0) || (blkno + totwrt) > nsects)
return EINVAL;
blkno += sectoff;
while (totwrt > 0) {
nwrt = totwrt;
#ifndef RD_DUMP_NOT_TRUSTED
sc->sc_ioc.c_unit = C_SUNIT(sc->sc_punit);
sc->sc_ioc.c_volume = C_SVOL(0);
sc->sc_ioc.c_saddr = C_SADDR;
sc->sc_ioc.c_hiaddr = 0;
sc->sc_ioc.c_addr = RDBTOS(blkno);
sc->sc_ioc.c_nop2 = C_NOP;
sc->sc_ioc.c_slen = C_SLEN;
sc->sc_ioc.c_len = nwrt * sectorsize;
sc->sc_ioc.c_cmd = C_WRITE;
hpibsend(ctlr, slave, C_CMD, &sc->sc_ioc.c_unit,
sizeof(sc->sc_ioc) - 2);
if (hpibswait(ctlr, slave))
return EIO;
hpibsend(ctlr, slave, C_EXEC, va, nwrt * sectorsize);
(void)hpibswait(ctlr, slave);
hpibrecv(ctlr, slave, C_QSTAT, &stat, 1);
if (stat)
return EIO;
#else
printf("%s: dump addr %p, blk %d\n", device_xname(sc->sc_dev),
va, blkno);
delay(500 * 1000);
#endif
totwrt -= nwrt;
blkno += nwrt;
va = (uint8_t *)va + sectorsize * nwrt;
}
rddoingadump = 0;
return 0;
}