NetBSD/sys/kern/subr_disk.c
thorpej 5b39541e48 New generic disk framework. Highlights:
- New metrics handling.  Metrics are now kept in the new
	  `struct disk'.  Busy time is now stored as a timeval, and
	  transfer count in bytes.

	- Storage for disklabels is now dynamically allocated, so that
	  the size of the disk structure is not machine-dependent.

	- Several new functions for attaching and detaching disks, and
	  handling metrics calculation.

Old-style instrumentation is still supported in drivers that did it before.
However, old-style instrumentation is being deprecated, and will go away
once the userland utilities are updated for the new framework.

For usage and architectural details, see the forthcoming disk(9) manual
page.
1996-01-07 22:01:38 +00:00

412 lines
11 KiB
C

/* $NetBSD: subr_disk.c,v 1.15 1996/01/07 22:03:49 thorpej Exp $ */
/*
* Copyright (c) 1995 Jason R. Thorpe. All rights reserved.
* Copyright (c) 1982, 1986, 1988, 1993
* The Regents of the University of California. All rights reserved.
* (c) UNIX System Laboratories, Inc.
* All or some portions of this file are derived from material licensed
* to the University of California by American Telephone and Telegraph
* Co. or Unix System Laboratories, Inc. and are reproduced herein with
* the permission of UNIX System Laboratories, Inc.
*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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.
*
* @(#)ufs_disksubr.c 8.5 (Berkeley) 1/21/94
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/syslog.h>
#include <sys/time.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
#include <sys/dkstat.h> /* XXX */
/*
* A global list of all disks attached to the system. May grow or
* shrink over time.
*/
struct disklist_head disklist; /* TAILQ_HEAD */
int disk_count; /* number of drives in global disklist */
/*
* Old-style disk instrumentation structures. These will go away
* someday.
*/
long dk_seek[DK_NDRIVE];
long dk_time[DK_NDRIVE];
long dk_wds[DK_NDRIVE];
long dk_wpms[DK_NDRIVE];
long dk_xfer[DK_NDRIVE];
int dk_busy;
int dk_ndrive;
int dkn; /* number of slots filled so far */
/*
* Seek sort for disks. We depend on the driver which calls us using b_resid
* as the current cylinder number.
*
* The argument ap structure holds a b_actf activity chain pointer on which we
* keep two queues, sorted in ascending cylinder order. The first queue holds
* those requests which are positioned after the current cylinder (in the first
* request); the second holds requests which came in after their cylinder number
* was passed. Thus we implement a one way scan, retracting after reaching the
* end of the drive to the first request on the second queue, at which time it
* becomes the first queue.
*
* A one-way scan is natural because of the way UNIX read-ahead blocks are
* allocated.
*/
void
disksort(ap, bp)
register struct buf *ap, *bp;
{
register struct buf *bq;
/* If the queue is empty, then it's easy. */
if (ap->b_actf == NULL) {
bp->b_actf = NULL;
ap->b_actf = bp;
return;
}
/*
* If we lie after the first (currently active) request, then we
* must locate the second request list and add ourselves to it.
*/
bq = ap->b_actf;
if (bp->b_cylinder < bq->b_cylinder) {
while (bq->b_actf) {
/*
* Check for an ``inversion'' in the normally ascending
* cylinder numbers, indicating the start of the second
* request list.
*/
if (bq->b_actf->b_cylinder < bq->b_cylinder) {
/*
* Search the second request list for the first
* request at a larger cylinder number. We go
* before that; if there is no such request, we
* go at end.
*/
do {
if (bp->b_cylinder <
bq->b_actf->b_cylinder)
goto insert;
if (bp->b_cylinder ==
bq->b_actf->b_cylinder &&
bp->b_blkno < bq->b_actf->b_blkno)
goto insert;
bq = bq->b_actf;
} while (bq->b_actf);
goto insert; /* after last */
}
bq = bq->b_actf;
}
/*
* No inversions... we will go after the last, and
* be the first request in the second request list.
*/
goto insert;
}
/*
* Request is at/after the current request...
* sort in the first request list.
*/
while (bq->b_actf) {
/*
* We want to go after the current request if there is an
* inversion after it (i.e. it is the end of the first
* request list), or if the next request is a larger cylinder
* than our request.
*/
if (bq->b_actf->b_cylinder < bq->b_cylinder ||
bp->b_cylinder < bq->b_actf->b_cylinder ||
(bp->b_cylinder == bq->b_actf->b_cylinder &&
bp->b_blkno < bq->b_actf->b_blkno))
goto insert;
bq = bq->b_actf;
}
/*
* Neither a second list nor a larger request... we go at the end of
* the first list, which is the same as the end of the whole schebang.
*/
insert: bp->b_actf = bq->b_actf;
bq->b_actf = bp;
}
/* encoding of disk minor numbers, should be elsewhere... */
#define dkunit(dev) (minor(dev) >> 3)
#define dkpart(dev) (minor(dev) & 07)
#define dkminor(unit, part) (((unit) << 3) | (part))
/*
* Compute checksum for disk label.
*/
u_int
dkcksum(lp)
register struct disklabel *lp;
{
register u_short *start, *end;
register u_short sum = 0;
start = (u_short *)lp;
end = (u_short *)&lp->d_partitions[lp->d_npartitions];
while (start < end)
sum ^= *start++;
return (sum);
}
/*
* Disk error is the preface to plaintive error messages
* about failing disk transfers. It prints messages of the form
hp0g: hard error reading fsbn 12345 of 12344-12347 (hp0 bn %d cn %d tn %d sn %d)
* if the offset of the error in the transfer and a disk label
* are both available. blkdone should be -1 if the position of the error
* is unknown; the disklabel pointer may be null from drivers that have not
* been converted to use them. The message is printed with printf
* if pri is LOG_PRINTF, otherwise it uses log at the specified priority.
* The message should be completed (with at least a newline) with printf
* or addlog, respectively. There is no trailing space.
*/
void
diskerr(bp, dname, what, pri, blkdone, lp)
register struct buf *bp;
char *dname, *what;
int pri, blkdone;
register struct disklabel *lp;
{
int unit = dkunit(bp->b_dev), part = dkpart(bp->b_dev);
register void (*pr) __P((const char *, ...));
char partname = 'a' + part;
int sn;
if (pri != LOG_PRINTF) {
log(pri, "");
pr = addlog;
} else
pr = printf;
(*pr)("%s%d%c: %s %sing fsbn ", dname, unit, partname, what,
bp->b_flags & B_READ ? "read" : "writ");
sn = bp->b_blkno;
if (bp->b_bcount <= DEV_BSIZE)
(*pr)("%d", sn);
else {
if (blkdone >= 0) {
sn += blkdone;
(*pr)("%d of ", sn);
}
(*pr)("%d-%d", bp->b_blkno,
bp->b_blkno + (bp->b_bcount - 1) / DEV_BSIZE);
}
if (lp && (blkdone >= 0 || bp->b_bcount <= lp->d_secsize)) {
#ifdef tahoe
sn *= DEV_BSIZE / lp->d_secsize; /* XXX */
#endif
sn += lp->d_partitions[part].p_offset;
(*pr)(" (%s%d bn %d; cn %d", dname, unit, sn,
sn / lp->d_secpercyl);
sn %= lp->d_secpercyl;
(*pr)(" tn %d sn %d)", sn / lp->d_nsectors, sn % lp->d_nsectors);
}
}
/*
* Initialize the disklist. Called by main() before autoconfiguration.
*/
void
disk_init()
{
TAILQ_INIT(&disklist);
disk_count = 0;
dk_ndrive = DK_NDRIVE; /* XXX */
}
/*
* Searches the disklist for the disk corresponding to the
* name provided.
*/
struct disk *
disk_find(name)
char *name;
{
struct disk *diskp;
if ((name == NULL) || (disk_count <= 0))
return (NULL);
for (diskp = disklist.tqh_first; diskp != NULL;
diskp = diskp->dk_link.tqe_next)
if (strcmp(diskp->dk_name, name) == 0)
return (diskp);
return (NULL);
}
/*
* Attach a disk.
*/
void
disk_attach(diskp)
struct disk *diskp;
{
int s;
/*
* Allocate and initialize the disklabel structures. Note that
* it's not safe to sleep here, since we're probably going to be
* called during autoconfiguration.
*/
diskp->dk_label = malloc(sizeof(struct disklabel), M_DEVBUF, M_NOWAIT);
diskp->dk_cpulabel = malloc(sizeof(struct cpu_disklabel), M_DEVBUF,
M_NOWAIT);
if ((diskp->dk_label == NULL) || (diskp->dk_cpulabel == NULL))
panic("disk_attach: can't allocate storage for disklabel");
bzero(diskp->dk_label, sizeof(struct disklabel));
bzero(diskp->dk_cpulabel, sizeof(struct cpu_disklabel));
/*
* Set the attached timestamp.
*/
s = splclock();
diskp->dk_attachtime = mono_time;
splx(s);
/*
* Link into the disklist.
*/
TAILQ_INSERT_TAIL(&disklist, diskp, dk_link);
++disk_count;
}
/*
* Detatch a disk.
*/
void
disk_detatch(diskp)
struct disk *diskp;
{
/*
* Free the space used by the disklabel structures.
*/
free(diskp->dk_label, M_DEVBUF);
free(diskp->dk_cpulabel, M_DEVBUF);
/*
* Remove from the disklist.
*/
TAILQ_REMOVE(&disklist, diskp, dk_link);
if (--disk_count < 0)
panic("disk_detatch: disk_count < 0");
}
/*
* Increment a disk's busy counter. If the counter is going from
* 0 to 1, set the timestamp.
*/
void
disk_busy(diskp)
struct disk *diskp;
{
int s;
/*
* XXX We'd like to use something as accurate as microtime(),
* but that doesn't depend on the system TOD clock.
*/
if (diskp->dk_busy++ == 0) {
s = splclock();
diskp->dk_timestamp = mono_time;
splx(s);
}
}
/*
* Decrement a disk's busy counter, increment the byte count, total busy
* time, and reset the timestamp.
*/
void
disk_unbusy(diskp, bcount)
struct disk *diskp;
long bcount;
{
int s;
struct timeval dv_time, diff_time;
if (diskp->dk_busy-- == 0)
panic("disk_unbusy: %s: dk_busy < 0", diskp->dk_name);
s = splclock();
dv_time = mono_time;
splx(s);
timersub(&dv_time, &diskp->dk_timestamp, &diff_time);
timeradd(&diskp->dk_time, &diff_time, &diskp->dk_time);
diskp->dk_timestamp = dv_time;
if (bcount > 0) {
diskp->dk_bytes += bcount;
diskp->dk_xfer++;
}
}
/*
* Reset the metrics counters on the given disk. Note that we cannot
* reset the busy counter, as it may case a panic in disk_unbusy().
* We also must avoid playing with the timestamp information, as it
* may skew any pending transfer results.
*/
void
disk_resetstat(diskp)
struct disk *diskp;
{
int s = splbio(), t;
diskp->dk_xfer = 0;
diskp->dk_bytes = 0;
t = splclock();
diskp->dk_attachtime = mono_time;
splx(t);
timerclear(&diskp->dk_time);
splx(s);
}