/*-
* Copyright (c) 1996, 1997, 1998, 1999, 2002, 2008 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe and Roland C. Dowdeswell.
*
* 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.
*/
/*
* If there are wedges, and this is not RAW_PART, then we
* need to fail.
*/
if (dk->dk_nwedges != 0 && part != RAW_PART) {
ret = EBUSY;
goto done;
}
/* If no dkdriver attached, bail */
if (dkd == NULL) {
ret = ENXIO;
goto done;
}
/*
* initialize driver for the first opener
*/
if (dk->dk_openmask == 0 && dkd->d_firstopen != NULL) {
ret = (*dkd->d_firstopen)(dksc->sc_dev, dev, flags, fmt);
if (ret)
goto done;
}
/*
* If we're init'ed and there are no other open partitions then
* update the in-core disklabel.
*/
if ((dksc->sc_flags & DKF_INITED)) {
if ((dksc->sc_flags & DKF_VLABEL) == 0) {
dksc->sc_flags |= DKF_VLABEL;
dk_getdisklabel(dksc, dev);
}
}
/* Fail if we can't find the partition. */
if (part != RAW_PART &&
((dksc->sc_flags & DKF_VLABEL) == 0 ||
part >= lp->d_npartitions ||
lp->d_partitions[part].p_fstype == FS_UNUSED)) {
ret = ENXIO;
goto done;
}
/* Mark our unit as open. */
switch (fmt) {
case S_IFCHR:
dk->dk_copenmask |= pmask;
break;
case S_IFBLK:
dk->dk_bopenmask |= pmask;
break;
}
part = DISKPART(bp->b_dev);
numsecs = dk->dk_geom.dg_secperunit;
secsize = dk->dk_geom.dg_secsize;
/*
* The transfer must be a whole number of blocks and the offset must
* not be negative.
*/
if ((bp->b_bcount % secsize) != 0 || bp->b_blkno < 0) {
bp->b_error = EINVAL;
goto done;
}
/* If there is nothing to do, then we are done */
if (bp->b_bcount == 0)
goto done;
/*
* Convert the block number to absolute and put it in terms
* of the device's logical block size.
*/
if (secsize >= DEV_BSIZE)
blkno = bp->b_blkno / (secsize / DEV_BSIZE);
else
blkno = bp->b_blkno * (DEV_BSIZE / secsize);
if (part != RAW_PART)
blkno += lp->d_partitions[DISKPART(bp->b_dev)].p_offset;
bp->b_rawblkno = blkno;
/*
* If another thread is running the queue, increment
* busy counter to 2 so that the queue is retried,
* because the driver may now accept additional
* requests.
*/
if (dksc->sc_busy < 2)
dksc->sc_busy++;
if (dksc->sc_busy > 1)
goto done;
/*
* Peeking at the buffer queue and committing the operation
* only after success isn't atomic.
*
* So when a diskstart fails, the buffer is saved
* and tried again before the next buffer is fetched.
* dk_drain() handles flushing of a saved buffer.
*
* This keeps order of I/O operations, unlike bufq_put.
*/
while (dksc->sc_busy > 0) {
bp = dksc->sc_deferred;
dksc->sc_deferred = NULL;
if (bp == NULL)
bp = bufq_get(dksc->sc_bufq);
while (bp != NULL) {
disk_busy(&dksc->sc_dkdev);
mutex_exit(&dksc->sc_iolock);
error = dkd->d_diskstart(dksc->sc_dev, bp);
mutex_enter(&dksc->sc_iolock);
if (error == EAGAIN || error == ENOMEM) {
/*
* Not a disk error. Retry later.
*/
KASSERT(dksc->sc_deferred == NULL);
dksc->sc_deferred = bp;
disk_unbusy(&dksc->sc_dkdev, 0, (bp->b_flags & B_READ));
disk_wait(&dksc->sc_dkdev);
break;
}
/* ensure that the pseudo disk is open for writes for these commands */
switch (cmd) {
case DIOCSDINFO:
case DIOCWDINFO:
#ifdef __HAVE_OLD_DISKLABEL
case ODIOCSDINFO:
case ODIOCWDINFO:
#endif
case DIOCKLABEL:
case DIOCWLABEL:
case DIOCAWEDGE:
case DIOCDWEDGE:
case DIOCSSTRATEGY:
if ((flag & FWRITE) == 0)
return EBADF;
}
/* ensure that the pseudo-disk is initialized for these */
switch (cmd) {
case DIOCGDINFO:
case DIOCSDINFO:
case DIOCWDINFO:
case DIOCGPARTINFO:
case DIOCKLABEL:
case DIOCWLABEL:
case DIOCGDEFLABEL:
case DIOCAWEDGE:
case DIOCDWEDGE:
case DIOCLWEDGES:
case DIOCMWEDGES:
case DIOCRMWEDGES:
case DIOCCACHESYNC:
#ifdef __HAVE_OLD_DISKLABEL
case ODIOCGDINFO:
case ODIOCSDINFO:
case ODIOCWDINFO:
case ODIOCGDEFLABEL:
#endif
if ((dksc->sc_flags & DKF_INITED) == 0)
return ENXIO;
}
if (dks->dks_param != NULL) {
return EINVAL;
}
dks->dks_name[sizeof(dks->dks_name) - 1] = 0; /* ensure term */
error = bufq_alloc(&new, dks->dks_name,
BUFQ_EXACT|BUFQ_SORT_RAWBLOCK);
if (error) {
return error;
}
mutex_enter(&dksc->sc_iolock);
old = dksc->sc_bufq;
if (old)
bufq_move(new, old);
dksc->sc_bufq = new;
mutex_exit(&dksc->sc_iolock);
if (old)
bufq_free(old);
break;
}
default:
error = ENOTTY;
}
return error;
}
/*
* dk_dump dumps all of physical memory into the partition specified.
* This requires substantially more framework than {s,w}ddump, and hence
* is probably much more fragile.
*
*/
/*
* ensure that we consider this device to be safe for dumping,
* and that the device is configured.
*/
if (!DKFF_READYFORDUMP(dksc->sc_flags)) {
DPRINTF(DKDB_DUMP, ("%s: bad dump flags 0x%x\n", __func__,
dksc->sc_flags));
return ENXIO;
}
/* ensure that we are not already dumping */
if (dk_dumping)
return EFAULT;
if ((flags & DK_DUMP_RECURSIVE) == 0)
dk_dumping = 1;
if (dkd->d_dumpblocks == NULL) {
DPRINTF(DKDB_DUMP, ("%s: no dumpblocks\n", __func__));
return ENXIO;
}
/* device specific max transfer size */
maxxfer = MAXPHYS;
if (dkd->d_iosize != NULL)
(*dkd->d_iosize)(dksc->sc_dev, &maxxfer);
/* Convert to disk sectors. Request must be a multiple of size. */
part = DISKPART(dev);
lp = dksc->sc_dkdev.dk_label;
if ((size % lp->d_secsize) != 0) {
DPRINTF(DKDB_DUMP, ("%s: odd size %zu\n", __func__, size));
return EFAULT;
}
towrt = size / lp->d_secsize;
blkno = dbtob(blkno) / lp->d_secsize; /* blkno in secsize units */
p = &lp->d_partitions[part];
if (part == RAW_PART) {
if (p->p_fstype != FS_UNUSED) {
DPRINTF(DKDB_DUMP, ("%s: bad fstype %d\n", __func__,
p->p_fstype));
return ENXIO;
}
/* Check whether dump goes to a wedge */
if (dksc->sc_dkdev.dk_nwedges == 0) {
DPRINTF(DKDB_DUMP, ("%s: dump to raw\n", __func__));
return ENXIO;
}
/* Check transfer bounds against media size */
if (blkno < 0 || (blkno + towrt) > dg->dg_secperunit) {
DPRINTF(DKDB_DUMP, ("%s: out of bounds blkno=%jd, towrt=%d, "
"nsects=%jd\n", __func__, (intmax_t)blkno, towrt, dg->dg_secperunit));
return EINVAL;
}
} else {
int nsects, sectoff;
if ((dksc->sc_flags & DKF_LABELSANITY) == 0)
return;
/* Convert sector counts to multiple of DEV_BSIZE for comparison */
lpratio = dgratio = 1;
if (lp->d_secsize > DEV_BSIZE)
lpratio = lp->d_secsize / DEV_BSIZE;
if (dg->dg_secsize > DEV_BSIZE)
dgratio = dg->dg_secsize / DEV_BSIZE;
/* Sanity check */
if ((uint64_t)lp->d_secperunit * lpratio > dg->dg_secperunit * dgratio)
printf("WARNING: %s: "
"total unit size in disklabel (%" PRIu64 ") "
"!= the size of %s (%" PRIu64 ")\n", dksc->sc_xname,
(uint64_t)lp->d_secperunit * lpratio, dksc->sc_xname,
dg->dg_secperunit * dgratio);
else if (lp->d_secperunit < UINT32_MAX &&
(uint64_t)lp->d_secperunit * lpratio < dg->dg_secperunit * dgratio)
printf("%s: %" PRIu64 " trailing sectors not covered"
" by disklabel\n", dksc->sc_xname,
(dg->dg_secperunit * dgratio)
- (lp->d_secperunit * lpratio));
for (i=0; i < lp->d_npartitions; i++) {
uint64_t pend;
pp = &lp->d_partitions[i];
pend = pp->p_offset + pp->p_size;
if (pend * lpratio > dg->dg_secperunit * dgratio)
printf("WARNING: %s: end of partition `%c' exceeds "
"the size of %s (%" PRIu64 ")\n", dksc->sc_xname,
'a' + i, dksc->sc_xname,
dg->dg_secperunit * dgratio);
}
}
/*
* Heuristic to conjure a disklabel if reading a disklabel failed.
*
* This is to allow the raw partition to be used for a filesystem
* without caring about the write protected label sector.
*
* If the driver provides it's own callback, use that instead.
*/
/* ARGSUSED */
static void
dk_makedisklabel(struct dk_softc *dksc)
{
const struct dkdriver *dkd = dksc->sc_dkdev.dk_driver;
struct disklabel *lp = dksc->sc_dkdev.dk_label;
if (dkd->d_label)
dkd->d_label(dksc->sc_dev, lp);
else
lp->d_partitions[RAW_PART].p_fstype = FS_BSDFFS;
lp->d_checksum = dkcksum(lp);
}
MODULE(MODULE_CLASS_MISC, dk_subr, NULL);
static int
dk_subr_modcmd(modcmd_t cmd, void *arg)
{
switch (cmd) {
case MODULE_CMD_INIT:
case MODULE_CMD_FINI:
return 0;
case MODULE_CMD_STAT:
case MODULE_CMD_AUTOUNLOAD:
default:
return ENOTTY;
}
}