/*-
* Copyright (c) 1996, 1997 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*
* Copyright (c) 1988 University of Utah.
* Copyright (c) 1982, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* the Systems Programming Group of the University of Utah Computer
* Science Department.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
*
* from: Utah $Hdr: rd.c 1.44 92/12/26$
*
* @(#)rd.c 8.2 (Berkeley) 5/19/94
*/
if (rdident(parent, sc, ha) == 0) {
aprint_error(": didn't respond to describe command!\n");
return;
}
/*
* XXX We use DEV_BSIZE instead of the sector size value pulled
* XXX off the driver because all of this code assumes 512 byte
* XXX blocks. ICK!
*/
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);
/*
* Initialize and attach the disk structure.
*/
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_flags = RDF_ALIVE;
#ifdef DEBUG
/* always report errors */
if ((rddebug & RDB_ERROR) != 0)
rderrthresh = 0;
#endif
/*
* attach the device into the random source list
*/
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;
/* Verify that we have a CS80 device. */
if ((ha->ha_id & 0x200) == 0)
return 0;
/* Is it one of the disks we support? */
for (id = 0; id < numrdidentinfo; id++)
if (ha->ha_id == rdidentinfo[id].ri_hwid)
break;
if (id == numrdidentinfo)
return 0;
/*
* The supported device ID is probed.
* Check if the specified physical unit is actually supported
* by brand-new HP-IB emulator devices like HPDisk and HPDrive etc.
*/
/*
* Reset device and collect description
*/
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) {
/*
* No valid response from the specified punit.
*
* Note it looks HPDisk responds to commands against
* supported but not-configured punits at 1 to 3.
*/
return 0;
}
/*
* If we're just probing for the device, that's all the
* work we need to do.
*/
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;
}
/*
* Take care of a couple of anomalies:
* 1. 7945A, 7946A, and 7941A all return same HW id
* 2. 9122S and 9134D both return same HW id
* 3. 9122D and 9134L both return same HW id
*/
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;
}
/*
* HPDisk can have independent physical units that are not
* corresponding to device IDs.
* To handle this, we have to check names in the drive description
* data for punit >= 1.
*/
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;
/*
* Read or construct a disklabel
*/
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;
/*
* Set some default values to use while reading the label
* or to use if there isn't a label.
*/
memset((void *)lp, 0, sizeof *lp);
rdgetdefaultlabel(sc, lp);
/*
* Now try to read the disklabel
*/
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;
/* XXX reset other info since readdisklabel screws with it */
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;
/*
* Wait for any pending opens/closes to complete
*/
while ((sc->sc_flags & (RDF_OPENING | RDF_CLOSING)) != 0)
(void)tsleep(sc, PRIBIO, "rdopen", 0);
/*
* On first open, get label and partition info.
* We may block reading the label, so be careful
* to stop any other opens.
*/
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;
/* Check that the partition exists. */
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;
/* Ensure only one open at a time. */
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;
/*
* On last close, we wait for all activity to cease since
* the label/partition info will become invalid. Since we
* might sleep, we must block any opens while we are here.
* Note we don't have to about other closes since we know
* we are the last one.
*/
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;
}
/*
* Deal with errors.
* Returns 1 if request should be restarted,
* 0 if we should just quietly give up.
*/
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;
}
/*
* Unit requests release for internal maintenance.
* We just delay awhile and try again later. Use exponentially
* increasing backoff ala ethernet drivers since we don't really
* know how long the maintenance will take. With RDWAITC and
* RDRETRY as defined, the range is 1 to 32 seconds.
*/
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;
}
/*
* Only report error if we have reached the error reporting
* threshold. By default, this will only report after the
* retry limit has been exceeded.
*/
if (sc->sc_errcnt < rderrthresh)
return 1;
/*
* First conjure up the block number at which the error occurred.
* Note that not all errors report a block number, in that case
* we just use b_blkno.
*/
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;
}
/*
* Now output a generic message suitable for badsect.
* Note that we don't use harderr cuz it just prints
* out b_blkno which is just the beginning block number
* of the transfer, not necessary where the error occurred.
*/
printf("%s%c: hard error sn%" PRId64 "\n", device_xname(sc->sc_dev),
'a' + rdpart(bp->b_dev), pbn);
/*
* Now report the status as returned by the hardware with
* attempt at interpretation (unless debugging).
*/
printf("%s %s error:", device_xname(sc->sc_dev),
(bp->b_flags & B_READ) != 0 ? "read" : "write");
#ifdef DEBUG
if (rddebug & RDB_ERROR) {
/* status info */
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]);
/* command */
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)
{
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;
/*
* We get called very early on (via swapconf)
* without the device being open so we may need
* to handle it here.
*/
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; /* simple mutex */
/*
* Non-interrupt driven, non-DMA dump routine.
*/
static int
rddump(dev_t dev, daddr_t blkno, void *va, size_t size)
{
int sectorsize; /* size of a disk sector */
int nsects; /* number of sectors in partition */
int sectoff; /* sector offset of partition */
int totwrt; /* total number of sectors left to write */
int nwrt; /* current number of sectors to write */
int part;
int ctlr, slave;
struct rd_softc *sc;
struct disklabel *lp;
char stat;
/* Check for recursive dump; if so, punt. */
if (rddoingadump)
return EFAULT;
rddoingadump = 1;
/* Decompose unit and partition. */
part = rdpart(dev);
/* Make sure dump device is ok. */
sc = device_lookup_private(&rd_cd, rdunit(dev));
if (sc == NULL)
return ENXIO;
if ((sc->sc_flags & RDF_ALIVE) == 0)
return ENXIO;
/*
* Convert to disk sectors. Request must be a multiple of size.
*/
lp = sc->sc_dkdev.dk_label;
sectorsize = lp->d_secsize;
if ((size % sectorsize) != 0)
return EFAULT;
totwrt = size / sectorsize;
blkno = dbtob(blkno) / sectorsize; /* blkno in DEV_BSIZE units */