ld.c revision 1.37.2.5
11.37.2.5Syamt/*	$NetBSD: ld.c,v 1.37.2.5 2007/11/15 11:44:01 yamt Exp $	*/
21.1Sad
31.1Sad/*-
41.1Sad * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc.
51.1Sad * All rights reserved.
61.1Sad *
71.1Sad * This code is derived from software contributed to The NetBSD Foundation
81.1Sad * by Andrew Doran and Charles M. Hannum.
91.1Sad *
101.1Sad * Redistribution and use in source and binary forms, with or without
111.1Sad * modification, are permitted provided that the following conditions
121.1Sad * are met:
131.1Sad * 1. Redistributions of source code must retain the above copyright
141.1Sad *    notice, this list of conditions and the following disclaimer.
151.1Sad * 2. Redistributions in binary form must reproduce the above copyright
161.1Sad *    notice, this list of conditions and the following disclaimer in the
171.1Sad *    documentation and/or other materials provided with the distribution.
181.1Sad * 3. All advertising materials mentioning features or use of this software
191.1Sad *    must display the following acknowledgement:
201.1Sad *        This product includes software developed by the NetBSD
211.1Sad *        Foundation, Inc. and its contributors.
221.1Sad * 4. Neither the name of The NetBSD Foundation nor the names of its
231.1Sad *    contributors may be used to endorse or promote products derived
241.1Sad *    from this software without specific prior written permission.
251.1Sad *
261.1Sad * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
271.1Sad * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
281.1Sad * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
291.1Sad * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
301.1Sad * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
311.1Sad * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
321.1Sad * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
331.1Sad * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
341.1Sad * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
351.1Sad * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
361.1Sad * POSSIBILITY OF SUCH DAMAGE.
371.1Sad */
381.1Sad
391.1Sad/*
401.1Sad * Disk driver for use by RAID controllers.
411.1Sad */
421.12Slukem
431.12Slukem#include <sys/cdefs.h>
441.37.2.5Syamt__KERNEL_RCSID(0, "$NetBSD: ld.c,v 1.37.2.5 2007/11/15 11:44:01 yamt Exp $");
451.1Sad
461.1Sad#include "rnd.h"
471.1Sad
481.1Sad#include <sys/param.h>
491.1Sad#include <sys/systm.h>
501.1Sad#include <sys/kernel.h>
511.1Sad#include <sys/device.h>
521.1Sad#include <sys/queue.h>
531.1Sad#include <sys/proc.h>
541.1Sad#include <sys/buf.h>
551.33Syamt#include <sys/bufq.h>
561.1Sad#include <sys/endian.h>
571.1Sad#include <sys/disklabel.h>
581.1Sad#include <sys/disk.h>
591.1Sad#include <sys/dkio.h>
601.1Sad#include <sys/stat.h>
611.1Sad#include <sys/lock.h>
621.1Sad#include <sys/conf.h>
631.1Sad#include <sys/fcntl.h>
641.2Sad#include <sys/vnode.h>
651.1Sad#include <sys/syslog.h>
661.37.2.2Syamt#include <sys/mutex.h>
671.1Sad#if NRND > 0
681.1Sad#include <sys/rnd.h>
691.1Sad#endif
701.1Sad
711.1Sad#include <dev/ldvar.h>
721.1Sad
731.37.2.2Syamt#include <prop/proplib.h>
741.37.2.2Syamt
751.1Sadstatic void	ldgetdefaultlabel(struct ld_softc *, struct disklabel *);
761.1Sadstatic void	ldgetdisklabel(struct ld_softc *);
771.1Sadstatic void	ldminphys(struct buf *bp);
781.1Sadstatic void	ldshutdown(void *);
791.37.2.2Syamtstatic void	ldstart(struct ld_softc *, struct buf *);
801.37.2.2Syamtstatic void	ld_set_properties(struct ld_softc *);
811.37.2.2Syamtstatic void	ld_config_interrupts (struct device *);
821.1Sad
831.1Sadextern struct	cfdriver ld_cd;
841.1Sad
851.29Sthorpejstatic dev_type_open(ldopen);
861.29Sthorpejstatic dev_type_close(ldclose);
871.29Sthorpejstatic dev_type_read(ldread);
881.29Sthorpejstatic dev_type_write(ldwrite);
891.29Sthorpejstatic dev_type_ioctl(ldioctl);
901.29Sthorpejstatic dev_type_strategy(ldstrategy);
911.29Sthorpejstatic dev_type_dump(lddump);
921.29Sthorpejstatic dev_type_size(ldsize);
931.16Sgehenna
941.16Sgehennaconst struct bdevsw ld_bdevsw = {
951.16Sgehenna	ldopen, ldclose, ldstrategy, ldioctl, lddump, ldsize, D_DISK
961.16Sgehenna};
971.16Sgehenna
981.16Sgehennaconst struct cdevsw ld_cdevsw = {
991.16Sgehenna	ldopen, ldclose, ldread, ldwrite, ldioctl,
1001.17Sjdolecek	nostop, notty, nopoll, nommap, nokqfilter, D_DISK
1011.16Sgehenna};
1021.16Sgehenna
1031.30Sthorpejstatic struct	dkdriver lddkdriver = { ldstrategy, ldminphys };
1041.1Sadstatic void	*ld_sdh;
1051.1Sad
1061.1Sadvoid
1071.1Sadldattach(struct ld_softc *sc)
1081.1Sad{
1091.37Schristos	char tbuf[9];
1101.1Sad
1111.37.2.2Syamt	mutex_init(&sc->sc_mutex, MUTEX_DRIVER, IPL_BIO);
1121.37.2.2Syamt
1131.7Sad	if ((sc->sc_flags & LDF_ENABLED) == 0) {
1141.34Sbriggs		aprint_normal("%s: disabled\n", sc->sc_dv.dv_xname);
1151.7Sad		return;
1161.7Sad	}
1171.7Sad
1181.1Sad	/* Initialise and attach the disk structure. */
1191.37.2.4Syamt	disk_init(&sc->sc_dk, sc->sc_dv.dv_xname, &lddkdriver);
1201.1Sad	disk_attach(&sc->sc_dk);
1211.1Sad
1221.1Sad	if (sc->sc_maxxfer > MAXPHYS)
1231.1Sad		sc->sc_maxxfer = MAXPHYS;
1241.9Sad
1251.19Sthorpej	/* Build synthetic geometry if necessary. */
1261.19Sthorpej	if (sc->sc_nheads == 0 || sc->sc_nsectors == 0 ||
1271.19Sthorpej	    sc->sc_ncylinders == 0) {
1281.28Sdbj		uint64_t ncyl;
1291.28Sdbj
1301.19Sthorpej		if (sc->sc_secperunit <= 528 * 2048)		/* 528MB */
1311.19Sthorpej			sc->sc_nheads = 16;
1321.19Sthorpej		else if (sc->sc_secperunit <= 1024 * 2048)	/* 1GB */
1331.19Sthorpej			sc->sc_nheads = 32;
1341.19Sthorpej		else if (sc->sc_secperunit <= 21504 * 2048)	/* 21GB */
1351.19Sthorpej			sc->sc_nheads = 64;
1361.19Sthorpej		else if (sc->sc_secperunit <= 43008 * 2048)	/* 42GB */
1371.19Sthorpej			sc->sc_nheads = 128;
1381.19Sthorpej		else
1391.19Sthorpej			sc->sc_nheads = 255;
1401.19Sthorpej
1411.19Sthorpej		sc->sc_nsectors = 63;
1421.28Sdbj		sc->sc_ncylinders = INT_MAX;
1431.35Sperry		ncyl = sc->sc_secperunit /
1441.19Sthorpej		    (sc->sc_nheads * sc->sc_nsectors);
1451.28Sdbj		if (ncyl < INT_MAX)
1461.28Sdbj			sc->sc_ncylinders = (int)ncyl;
1471.19Sthorpej	}
1481.1Sad
1491.37Schristos	format_bytes(tbuf, sizeof(tbuf), sc->sc_secperunit *
1501.1Sad	    sc->sc_secsize);
1511.34Sbriggs	aprint_normal("%s: %s, %d cyl, %d head, %d sec, %d bytes/sect x %"PRIu64" sectors\n",
1521.37Schristos	    sc->sc_dv.dv_xname, tbuf, sc->sc_ncylinders, sc->sc_nheads,
1531.1Sad	    sc->sc_nsectors, sc->sc_secsize, sc->sc_secperunit);
1541.1Sad
1551.37.2.2Syamt	ld_set_properties(sc);
1561.37.2.2Syamt
1571.1Sad#if NRND > 0
1581.1Sad	/* Attach the device into the rnd source list. */
1591.1Sad	rnd_attach_source(&sc->sc_rnd_source, sc->sc_dv.dv_xname,
1601.1Sad	    RND_TYPE_DISK, 0);
1611.1Sad#endif
1621.1Sad
1631.1Sad	/* Set the `shutdownhook'. */
1641.1Sad	if (ld_sdh == NULL)
1651.1Sad		ld_sdh = shutdownhook_establish(ldshutdown, NULL);
1661.37.2.1Syamt	bufq_alloc(&sc->sc_bufq, BUFQ_DISK_DEFAULT_STRAT, BUFQ_SORT_RAWBLOCK);
1671.30Sthorpej
1681.30Sthorpej	/* Discover wedges on this disk. */
1691.37.2.2Syamt	config_interrupts(&sc->sc_dv, ld_config_interrupts);
1701.1Sad}
1711.1Sad
1721.3Sadint
1731.37Schristosldadjqparam(struct ld_softc *sc, int xmax)
1741.3Sad{
1751.24Sthorpej	int s;
1761.7Sad
1771.7Sad	s = splbio();
1781.37Schristos	sc->sc_maxqueuecnt = xmax;
1791.7Sad	splx(s);
1801.7Sad
1811.24Sthorpej	return (0);
1821.7Sad}
1831.7Sad
1841.7Sadint
1851.7Sadldbegindetach(struct ld_softc *sc, int flags)
1861.7Sad{
1871.24Sthorpej	int s, rv = 0;
1881.7Sad
1891.7Sad	if ((sc->sc_flags & LDF_ENABLED) == 0)
1901.7Sad		return (0);
1911.3Sad
1921.3Sad	if ((flags & DETACH_FORCE) == 0 && sc->sc_dk.dk_openmask != 0)
1931.3Sad		return (EBUSY);
1941.3Sad
1951.3Sad	s = splbio();
1961.24Sthorpej	sc->sc_maxqueuecnt = 0;
1971.7Sad	sc->sc_flags |= LDF_DETACH;
1981.24Sthorpej	while (sc->sc_queuecnt > 0) {
1991.24Sthorpej		sc->sc_flags |= LDF_DRAIN;
2001.24Sthorpej		rv = tsleep(&sc->sc_queuecnt, PRIBIO, "lddrn", 0);
2011.24Sthorpej		if (rv)
2021.24Sthorpej			break;
2031.24Sthorpej	}
2041.3Sad	splx(s);
2051.7Sad
2061.7Sad	return (rv);
2071.3Sad}
2081.3Sad
2091.2Sadvoid
2101.7Sadldenddetach(struct ld_softc *sc)
2111.2Sad{
2121.13Sdrochner	int s, bmaj, cmaj, i, mn;
2131.2Sad
2141.7Sad	if ((sc->sc_flags & LDF_ENABLED) == 0)
2151.7Sad		return;
2161.7Sad
2171.2Sad	/* Wait for commands queued with the hardware to complete. */
2181.2Sad	if (sc->sc_queuecnt != 0)
2191.7Sad		if (tsleep(&sc->sc_queuecnt, PRIBIO, "lddtch", 30 * hz))
2201.7Sad			printf("%s: not drained\n", sc->sc_dv.dv_xname);
2211.2Sad
2221.2Sad	/* Locate the major numbers. */
2231.16Sgehenna	bmaj = bdevsw_lookup_major(&ld_bdevsw);
2241.16Sgehenna	cmaj = cdevsw_lookup_major(&ld_cdevsw);
2251.2Sad
2261.2Sad	/* Kill off any queued buffers. */
2271.2Sad	s = splbio();
2281.37.2.1Syamt	bufq_drain(sc->sc_bufq);
2291.36Syamt	splx(s);
2301.36Syamt
2311.37.2.1Syamt	bufq_free(sc->sc_bufq);
2321.2Sad
2331.2Sad	/* Nuke the vnodes for any open instances. */
2341.13Sdrochner	for (i = 0; i < MAXPARTITIONS; i++) {
2351.37.2.1Syamt		mn = DISKMINOR(device_unit(&sc->sc_dv), i);
2361.13Sdrochner		vdevgone(bmaj, mn, mn, VBLK);
2371.13Sdrochner		vdevgone(cmaj, mn, mn, VCHR);
2381.13Sdrochner	}
2391.13Sdrochner
2401.30Sthorpej	/* Delete all of our wedges. */
2411.30Sthorpej	dkwedge_delall(&sc->sc_dk);
2421.30Sthorpej
2431.2Sad	/* Detach from the disk list. */
2441.2Sad	disk_detach(&sc->sc_dk);
2451.37.2.4Syamt	disk_destroy(&sc->sc_dk);
2461.2Sad
2471.2Sad#if NRND > 0
2481.2Sad	/* Unhook the entropy source. */
2491.2Sad	rnd_detach_source(&sc->sc_rnd_source);
2501.2Sad#endif
2511.2Sad
2521.24Sthorpej	/*
2531.24Sthorpej	 * XXX We can't really flush the cache here, beceause the
2541.24Sthorpej	 * XXX device may already be non-existent from the controller's
2551.24Sthorpej	 * XXX perspective.
2561.24Sthorpej	 */
2571.24Sthorpej#if 0
2581.2Sad	/* Flush the device's cache. */
2591.2Sad	if (sc->sc_flush != NULL)
2601.2Sad		if ((*sc->sc_flush)(sc) != 0)
2611.2Sad			printf("%s: unable to flush cache\n",
2621.2Sad			    sc->sc_dv.dv_xname);
2631.24Sthorpej#endif
2641.2Sad}
2651.2Sad
2661.8Slukem/* ARGSUSED */
2671.1Sadstatic void
2681.1Sadldshutdown(void *cookie)
2691.1Sad{
2701.1Sad	struct ld_softc *sc;
2711.1Sad	int i;
2721.1Sad
2731.1Sad	for (i = 0; i < ld_cd.cd_ndevs; i++) {
2741.1Sad		if ((sc = device_lookup(&ld_cd, i)) == NULL)
2751.1Sad			continue;
2761.1Sad		if (sc->sc_flush != NULL && (*sc->sc_flush)(sc) != 0)
2771.1Sad			printf("%s: unable to flush cache\n",
2781.1Sad			    sc->sc_dv.dv_xname);
2791.1Sad	}
2801.1Sad}
2811.1Sad
2821.8Slukem/* ARGSUSED */
2831.29Sthorpejstatic int
2841.37.2.1Syamtldopen(dev_t dev, int flags, int fmt, struct lwp *l)
2851.1Sad{
2861.1Sad	struct ld_softc *sc;
2871.30Sthorpej	int error, unit, part;
2881.1Sad
2891.1Sad	unit = DISKUNIT(dev);
2901.30Sthorpej	if ((sc = device_lookup(&ld_cd, unit)) == NULL)
2911.1Sad		return (ENXIO);
2921.1Sad	if ((sc->sc_flags & LDF_ENABLED) == 0)
2931.1Sad		return (ENODEV);
2941.1Sad	part = DISKPART(dev);
2951.30Sthorpej
2961.37.2.3Syamt	mutex_enter(&sc->sc_dk.dk_openlock);
2971.1Sad
2981.22Sthorpej	if (sc->sc_dk.dk_openmask == 0) {
2991.22Sthorpej		/* Load the partition info if not already loaded. */
3001.22Sthorpej		if ((sc->sc_flags & LDF_VLABEL) == 0)
3011.22Sthorpej			ldgetdisklabel(sc);
3021.22Sthorpej	}
3031.1Sad
3041.1Sad	/* Check that the partition exists. */
3051.1Sad	if (part != RAW_PART && (part >= sc->sc_dk.dk_label->d_npartitions ||
3061.1Sad	    sc->sc_dk.dk_label->d_partitions[part].p_fstype == FS_UNUSED)) {
3071.30Sthorpej		error = ENXIO;
3081.30Sthorpej		goto bad1;
3091.1Sad	}
3101.1Sad
3111.1Sad	/* Ensure only one open at a time. */
3121.1Sad	switch (fmt) {
3131.1Sad	case S_IFCHR:
3141.1Sad		sc->sc_dk.dk_copenmask |= (1 << part);
3151.1Sad		break;
3161.1Sad	case S_IFBLK:
3171.1Sad		sc->sc_dk.dk_bopenmask |= (1 << part);
3181.1Sad		break;
3191.1Sad	}
3201.1Sad	sc->sc_dk.dk_openmask =
3211.1Sad	    sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask;
3221.1Sad
3231.37.2.3Syamt	error = 0;
3241.30Sthorpej bad1:
3251.37.2.3Syamt	mutex_exit(&sc->sc_dk.dk_openlock);
3261.30Sthorpej	return (error);
3271.1Sad}
3281.1Sad
3291.8Slukem/* ARGSUSED */
3301.29Sthorpejstatic int
3311.37.2.1Syamtldclose(dev_t dev, int flags, int fmt, struct lwp *l)
3321.1Sad{
3331.1Sad	struct ld_softc *sc;
3341.37.2.3Syamt	int part, unit;
3351.1Sad
3361.1Sad	unit = DISKUNIT(dev);
3371.1Sad	part = DISKPART(dev);
3381.1Sad	sc = device_lookup(&ld_cd, unit);
3391.30Sthorpej
3401.37.2.3Syamt	mutex_enter(&sc->sc_dk.dk_openlock);
3411.1Sad
3421.1Sad	switch (fmt) {
3431.1Sad	case S_IFCHR:
3441.1Sad		sc->sc_dk.dk_copenmask &= ~(1 << part);
3451.1Sad		break;
3461.1Sad	case S_IFBLK:
3471.1Sad		sc->sc_dk.dk_bopenmask &= ~(1 << part);
3481.1Sad		break;
3491.1Sad	}
3501.1Sad	sc->sc_dk.dk_openmask =
3511.1Sad	    sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask;
3521.1Sad
3531.22Sthorpej	if (sc->sc_dk.dk_openmask == 0) {
3541.22Sthorpej		if (sc->sc_flush != NULL && (*sc->sc_flush)(sc) != 0)
3551.1Sad			printf("%s: unable to flush cache\n",
3561.1Sad			    sc->sc_dv.dv_xname);
3571.22Sthorpej		if ((sc->sc_flags & LDF_KLABEL) == 0)
3581.22Sthorpej			sc->sc_flags &= ~LDF_VLABEL;
3591.22Sthorpej	}
3601.1Sad
3611.37.2.3Syamt	mutex_exit(&sc->sc_dk.dk_openlock);
3621.1Sad	return (0);
3631.1Sad}
3641.1Sad
3651.8Slukem/* ARGSUSED */
3661.29Sthorpejstatic int
3671.1Sadldread(dev_t dev, struct uio *uio, int ioflag)
3681.1Sad{
3691.1Sad
3701.1Sad	return (physio(ldstrategy, NULL, dev, B_READ, ldminphys, uio));
3711.1Sad}
3721.1Sad
3731.8Slukem/* ARGSUSED */
3741.29Sthorpejstatic int
3751.1Sadldwrite(dev_t dev, struct uio *uio, int ioflag)
3761.1Sad{
3771.1Sad
3781.1Sad	return (physio(ldstrategy, NULL, dev, B_WRITE, ldminphys, uio));
3791.1Sad}
3801.1Sad
3811.8Slukem/* ARGSUSED */
3821.29Sthorpejstatic int
3831.37.2.3Syamtldioctl(dev_t dev, u_long cmd, void *addr, int32_t flag, struct lwp *l)
3841.1Sad{
3851.1Sad	struct ld_softc *sc;
3861.1Sad	int part, unit, error;
3871.4Sfvdl#ifdef __HAVE_OLD_DISKLABEL
3881.6Sitojun	struct disklabel newlabel;
3891.4Sfvdl#endif
3901.6Sitojun	struct disklabel *lp;
3911.1Sad
3921.1Sad	unit = DISKUNIT(dev);
3931.1Sad	part = DISKPART(dev);
3941.1Sad	sc = device_lookup(&ld_cd, unit);
3951.1Sad
3961.37.2.2Syamt	error = disk_ioctl(&sc->sc_dk, cmd, addr, flag, l);
3971.37.2.2Syamt	if (error != EPASSTHROUGH)
3981.37.2.2Syamt		return (error);
3991.37.2.2Syamt
4001.37.2.3Syamt	error = 0;
4011.1Sad	switch (cmd) {
4021.1Sad	case DIOCGDINFO:
4031.1Sad		memcpy(addr, sc->sc_dk.dk_label, sizeof(struct disklabel));
4041.1Sad		return (0);
4051.1Sad
4061.4Sfvdl#ifdef __HAVE_OLD_DISKLABEL
4071.4Sfvdl	case ODIOCGDINFO:
4081.4Sfvdl		newlabel = *(sc->sc_dk.dk_label);
4091.4Sfvdl		if (newlabel.d_npartitions > OLDMAXPARTITIONS)
4101.5Sfvdl			return ENOTTY;
4111.4Sfvdl		memcpy(addr, &newlabel, sizeof(struct olddisklabel));
4121.4Sfvdl		return (0);
4131.4Sfvdl#endif
4141.4Sfvdl
4151.1Sad	case DIOCGPART:
4161.1Sad		((struct partinfo *)addr)->disklab = sc->sc_dk.dk_label;
4171.1Sad		((struct partinfo *)addr)->part =
4181.1Sad		    &sc->sc_dk.dk_label->d_partitions[part];
4191.1Sad		break;
4201.1Sad
4211.1Sad	case DIOCWDINFO:
4221.1Sad	case DIOCSDINFO:
4231.4Sfvdl#ifdef __HAVE_OLD_DISKLABEL
4241.4Sfvdl	case ODIOCWDINFO:
4251.4Sfvdl	case ODIOCSDINFO:
4261.4Sfvdl
4271.4Sfvdl		if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) {
4281.4Sfvdl			memset(&newlabel, 0, sizeof newlabel);
4291.4Sfvdl			memcpy(&newlabel, addr, sizeof (struct olddisklabel));
4301.4Sfvdl			lp = &newlabel;
4311.4Sfvdl		} else
4321.4Sfvdl#endif
4331.4Sfvdl		lp = (struct disklabel *)addr;
4341.4Sfvdl
4351.1Sad		if ((flag & FWRITE) == 0)
4361.1Sad			return (EBADF);
4371.1Sad
4381.37.2.3Syamt		mutex_enter(&sc->sc_dk.dk_openlock);
4391.1Sad		sc->sc_flags |= LDF_LABELLING;
4401.1Sad
4411.1Sad		error = setdisklabel(sc->sc_dk.dk_label,
4421.4Sfvdl		    lp, /*sc->sc_dk.dk_openmask : */0,
4431.1Sad		    sc->sc_dk.dk_cpulabel);
4441.4Sfvdl		if (error == 0 && (cmd == DIOCWDINFO
4451.4Sfvdl#ifdef __HAVE_OLD_DISKLABEL
4461.4Sfvdl		    || cmd == ODIOCWDINFO
4471.4Sfvdl#endif
4481.4Sfvdl		    ))
4491.1Sad			error = writedisklabel(
4501.35Sperry			    MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART),
4511.35Sperry			    ldstrategy, sc->sc_dk.dk_label,
4521.1Sad			    sc->sc_dk.dk_cpulabel);
4531.1Sad
4541.1Sad		sc->sc_flags &= ~LDF_LABELLING;
4551.37.2.3Syamt		mutex_exit(&sc->sc_dk.dk_openlock);
4561.1Sad		break;
4571.1Sad
4581.22Sthorpej	case DIOCKLABEL:
4591.22Sthorpej		if ((flag & FWRITE) == 0)
4601.22Sthorpej			return (EBADF);
4611.22Sthorpej		if (*(int *)addr)
4621.22Sthorpej			sc->sc_flags |= LDF_KLABEL;
4631.22Sthorpej		else
4641.22Sthorpej			sc->sc_flags &= ~LDF_KLABEL;
4651.22Sthorpej		break;
4661.22Sthorpej
4671.1Sad	case DIOCWLABEL:
4681.1Sad		if ((flag & FWRITE) == 0)
4691.1Sad			return (EBADF);
4701.1Sad		if (*(int *)addr)
4711.1Sad			sc->sc_flags |= LDF_WLABEL;
4721.1Sad		else
4731.1Sad			sc->sc_flags &= ~LDF_WLABEL;
4741.1Sad		break;
4751.1Sad
4761.1Sad	case DIOCGDEFLABEL:
4771.1Sad		ldgetdefaultlabel(sc, (struct disklabel *)addr);
4781.1Sad		break;
4791.4Sfvdl
4801.4Sfvdl#ifdef __HAVE_OLD_DISKLABEL
4811.4Sfvdl	case ODIOCGDEFLABEL:
4821.4Sfvdl		ldgetdefaultlabel(sc, &newlabel);
4831.4Sfvdl		if (newlabel.d_npartitions > OLDMAXPARTITIONS)
4841.5Sfvdl			return ENOTTY;
4851.4Sfvdl		memcpy(addr, &newlabel, sizeof (struct olddisklabel));
4861.4Sfvdl		break;
4871.4Sfvdl#endif
4881.1Sad
4891.32Sthorpej	case DIOCCACHESYNC:
4901.32Sthorpej		/*
4911.32Sthorpej		 * XXX Do we really need to care about having a writable
4921.32Sthorpej		 * file descriptor here?
4931.32Sthorpej		 */
4941.32Sthorpej		if ((flag & FWRITE) == 0)
4951.32Sthorpej			error = EBADF;
4961.32Sthorpej		else if (sc->sc_flush)
4971.32Sthorpej			error = (*sc->sc_flush)(sc);
4981.32Sthorpej		else
4991.32Sthorpej			error = 0;	/* XXX Error out instead? */
5001.32Sthorpej		break;
5011.32Sthorpej
5021.30Sthorpej	case DIOCAWEDGE:
5031.30Sthorpej	    {
5041.30Sthorpej	    	struct dkwedge_info *dkw = (void *) addr;
5051.30Sthorpej
5061.30Sthorpej		if ((flag & FWRITE) == 0)
5071.30Sthorpej			return (EBADF);
5081.30Sthorpej
5091.30Sthorpej		/* If the ioctl happens here, the parent is us. */
5101.30Sthorpej		strcpy(dkw->dkw_parent, sc->sc_dv.dv_xname);
5111.30Sthorpej		return (dkwedge_add(dkw));
5121.30Sthorpej	    }
5131.35Sperry
5141.30Sthorpej	case DIOCDWEDGE:
5151.30Sthorpej	    {
5161.30Sthorpej	    	struct dkwedge_info *dkw = (void *) addr;
5171.30Sthorpej
5181.30Sthorpej		if ((flag & FWRITE) == 0)
5191.30Sthorpej			return (EBADF);
5201.30Sthorpej
5211.30Sthorpej		/* If the ioctl happens here, the parent is us. */
5221.30Sthorpej		strcpy(dkw->dkw_parent, sc->sc_dv.dv_xname);
5231.30Sthorpej		return (dkwedge_del(dkw));
5241.30Sthorpej	    }
5251.35Sperry
5261.30Sthorpej	case DIOCLWEDGES:
5271.30Sthorpej	    {
5281.30Sthorpej	    	struct dkwedge_list *dkwl = (void *) addr;
5291.30Sthorpej
5301.37.2.1Syamt		return (dkwedge_list(&sc->sc_dk, dkwl, l));
5311.30Sthorpej	    }
5321.37.2.5Syamt	case DIOCGSTRATEGY:
5331.37.2.5Syamt	    {
5341.37.2.5Syamt		struct disk_strategy *dks = (void *)addr;
5351.37.2.5Syamt
5361.37.2.5Syamt		mutex_enter(&sc->sc_mutex);
5371.37.2.5Syamt		strlcpy(dks->dks_name, bufq_getstrategyname(sc->sc_bufq),
5381.37.2.5Syamt		    sizeof(dks->dks_name));
5391.37.2.5Syamt		mutex_exit(&sc->sc_mutex);
5401.37.2.5Syamt		dks->dks_paramlen = 0;
5411.37.2.5Syamt
5421.37.2.5Syamt		return 0;
5431.37.2.5Syamt	    }
5441.37.2.5Syamt	case DIOCSSTRATEGY:
5451.37.2.5Syamt	    {
5461.37.2.5Syamt		struct disk_strategy *dks = (void *)addr;
5471.37.2.5Syamt		struct bufq_state *new, *old;
5481.30Sthorpej
5491.37.2.5Syamt		if ((flag & FWRITE) == 0)
5501.37.2.5Syamt			return EPERM;
5511.37.2.5Syamt
5521.37.2.5Syamt		if (dks->dks_param != NULL)
5531.37.2.5Syamt			return EINVAL;
5541.37.2.5Syamt
5551.37.2.5Syamt		dks->dks_name[sizeof(dks->dks_name) - 1] = 0; /* ensure term */
5561.37.2.5Syamt		error = bufq_alloc(&new, dks->dks_name,
5571.37.2.5Syamt		    BUFQ_EXACT|BUFQ_SORT_RAWBLOCK);
5581.37.2.5Syamt		if (error)
5591.37.2.5Syamt			return error;
5601.37.2.5Syamt
5611.37.2.5Syamt		mutex_enter(&sc->sc_mutex);
5621.37.2.5Syamt		old = sc->sc_bufq;
5631.37.2.5Syamt		bufq_move(new, old);
5641.37.2.5Syamt		sc->sc_bufq = new;
5651.37.2.5Syamt		mutex_exit(&sc->sc_mutex);
5661.37.2.5Syamt		bufq_free(old);
5671.37.2.5Syamt
5681.37.2.5Syamt		return 0;
5691.37.2.5Syamt	    }
5701.1Sad	default:
5711.1Sad		error = ENOTTY;
5721.1Sad		break;
5731.1Sad	}
5741.1Sad
5751.1Sad	return (error);
5761.1Sad}
5771.1Sad
5781.29Sthorpejstatic void
5791.1Sadldstrategy(struct buf *bp)
5801.1Sad{
5811.1Sad	struct ld_softc *sc;
5821.23Sthorpej	struct disklabel *lp;
5831.23Sthorpej	daddr_t blkno;
5841.23Sthorpej	int s, part;
5851.1Sad
5861.1Sad	sc = device_lookup(&ld_cd, DISKUNIT(bp->b_dev));
5871.23Sthorpej	part = DISKPART(bp->b_dev);
5881.1Sad
5891.7Sad	if ((sc->sc_flags & LDF_DETACH) != 0) {
5901.2Sad		bp->b_error = EIO;
5911.37.2.3Syamt		goto done;
5921.2Sad	}
5931.2Sad
5941.1Sad	lp = sc->sc_dk.dk_label;
5951.1Sad
5961.1Sad	/*
5971.1Sad	 * The transfer must be a whole number of blocks and the offset must
5981.1Sad	 * not be negative.
5991.1Sad	 */
6001.1Sad	if ((bp->b_bcount % lp->d_secsize) != 0 || bp->b_blkno < 0) {
6011.23Sthorpej		bp->b_error = EINVAL;
6021.37.2.3Syamt		goto done;
6031.1Sad	}
6041.1Sad
6051.23Sthorpej	/* If it's a null transfer, return immediately. */
6061.23Sthorpej	if (bp->b_bcount == 0)
6071.23Sthorpej		goto done;
6081.1Sad
6091.1Sad	/*
6101.1Sad	 * Do bounds checking and adjust the transfer.  If error, process.
6111.1Sad	 * If past the end of partition, just return.
6121.1Sad	 */
6131.1Sad	if (part != RAW_PART &&
6141.20Sthorpej	    bounds_check_with_label(&sc->sc_dk, bp,
6151.1Sad	    (sc->sc_flags & (LDF_WLABEL | LDF_LABELLING)) != 0) <= 0) {
6161.23Sthorpej		goto done;
6171.1Sad	}
6181.1Sad
6191.1Sad	/*
6201.23Sthorpej	 * Convert the block number to absolute and put it in terms
6211.23Sthorpej	 * of the device's logical block size.
6221.1Sad	 */
6231.23Sthorpej	if (lp->d_secsize == DEV_BSIZE)
6241.23Sthorpej		blkno = bp->b_blkno;
6251.23Sthorpej	else if (lp->d_secsize > DEV_BSIZE)
6261.23Sthorpej		blkno = bp->b_blkno / (lp->d_secsize / DEV_BSIZE);
6271.1Sad	else
6281.23Sthorpej		blkno = bp->b_blkno * (DEV_BSIZE / lp->d_secsize);
6291.1Sad
6301.11Sthorpej	if (part != RAW_PART)
6311.23Sthorpej		blkno += lp->d_partitions[part].p_offset;
6321.23Sthorpej
6331.23Sthorpej	bp->b_rawblkno = blkno;
6341.1Sad
6351.1Sad	s = splbio();
6361.37.2.2Syamt	ldstart(sc, bp);
6371.1Sad	splx(s);
6381.23Sthorpej	return;
6391.23Sthorpej
6401.23Sthorpej done:
6411.23Sthorpej	bp->b_resid = bp->b_bcount;
6421.23Sthorpej	biodone(bp);
6431.23Sthorpej}
6441.23Sthorpej
6451.23Sthorpejstatic void
6461.37.2.2Syamtldstart(struct ld_softc *sc, struct buf *bp)
6471.23Sthorpej{
6481.23Sthorpej	int error;
6491.1Sad
6501.37.2.2Syamt	mutex_enter(&sc->sc_mutex);
6511.37.2.2Syamt
6521.37.2.2Syamt	if (bp != NULL)
6531.37.2.2Syamt		BUFQ_PUT(sc->sc_bufq, bp);
6541.37.2.2Syamt
6551.23Sthorpej	while (sc->sc_queuecnt < sc->sc_maxqueuecnt) {
6561.23Sthorpej		/* See if there is work to do. */
6571.37.2.1Syamt		if ((bp = BUFQ_PEEK(sc->sc_bufq)) == NULL)
6581.23Sthorpej			break;
6591.23Sthorpej
6601.23Sthorpej		disk_busy(&sc->sc_dk);
6611.23Sthorpej		sc->sc_queuecnt++;
6621.23Sthorpej
6631.23Sthorpej		if (__predict_true((error = (*sc->sc_start)(sc, bp)) == 0)) {
6641.23Sthorpej			/*
6651.23Sthorpej			 * The back-end is running the job; remove it from
6661.23Sthorpej			 * the queue.
6671.23Sthorpej			 */
6681.37.2.1Syamt			(void) BUFQ_GET(sc->sc_bufq);
6691.23Sthorpej		} else  {
6701.23Sthorpej			disk_unbusy(&sc->sc_dk, 0, (bp->b_flags & B_READ));
6711.23Sthorpej			sc->sc_queuecnt--;
6721.23Sthorpej			if (error == EAGAIN) {
6731.23Sthorpej				/*
6741.23Sthorpej				 * Temporary resource shortage in the
6751.23Sthorpej				 * back-end; just defer the job until
6761.23Sthorpej				 * later.
6771.23Sthorpej				 *
6781.23Sthorpej				 * XXX We might consider a watchdog timer
6791.23Sthorpej				 * XXX to make sure we are kicked into action.
6801.23Sthorpej				 */
6811.23Sthorpej				break;
6821.23Sthorpej			} else {
6831.37.2.1Syamt				(void) BUFQ_GET(sc->sc_bufq);
6841.23Sthorpej				bp->b_error = error;
6851.23Sthorpej				bp->b_resid = bp->b_bcount;
6861.37.2.2Syamt				mutex_exit(&sc->sc_mutex);
6871.23Sthorpej				biodone(bp);
6881.37.2.2Syamt				mutex_enter(&sc->sc_mutex);
6891.23Sthorpej			}
6901.23Sthorpej		}
6911.1Sad	}
6921.37.2.2Syamt
6931.37.2.2Syamt	mutex_exit(&sc->sc_mutex);
6941.1Sad}
6951.1Sad
6961.1Sadvoid
6971.1Sadlddone(struct ld_softc *sc, struct buf *bp)
6981.1Sad{
6991.1Sad
7001.37.2.3Syamt	if (bp->b_error != 0) {
7011.1Sad		diskerr(bp, "ld", "error", LOG_PRINTF, 0, sc->sc_dk.dk_label);
7021.1Sad		printf("\n");
7031.1Sad	}
7041.1Sad
7051.18Smrg	disk_unbusy(&sc->sc_dk, bp->b_bcount - bp->b_resid,
7061.18Smrg	    (bp->b_flags & B_READ));
7071.1Sad#if NRND > 0
7081.1Sad	rnd_add_uint32(&sc->sc_rnd_source, bp->b_rawblkno);
7091.1Sad#endif
7101.1Sad	biodone(bp);
7111.1Sad
7121.37.2.2Syamt	mutex_enter(&sc->sc_mutex);
7131.7Sad	if (--sc->sc_queuecnt <= sc->sc_maxqueuecnt) {
7141.24Sthorpej		if ((sc->sc_flags & LDF_DRAIN) != 0) {
7151.24Sthorpej			sc->sc_flags &= ~LDF_DRAIN;
7161.7Sad			wakeup(&sc->sc_queuecnt);
7171.24Sthorpej		}
7181.37.2.2Syamt		mutex_exit(&sc->sc_mutex);
7191.37.2.2Syamt		ldstart(sc, NULL);
7201.37.2.2Syamt	} else
7211.37.2.2Syamt		mutex_exit(&sc->sc_mutex);
7221.1Sad}
7231.1Sad
7241.29Sthorpejstatic int
7251.1Sadldsize(dev_t dev)
7261.1Sad{
7271.1Sad	struct ld_softc *sc;
7281.1Sad	int part, unit, omask, size;
7291.1Sad
7301.1Sad	unit = DISKUNIT(dev);
7311.1Sad	if ((sc = device_lookup(&ld_cd, unit)) == NULL)
7321.1Sad		return (ENODEV);
7331.1Sad	if ((sc->sc_flags & LDF_ENABLED) == 0)
7341.1Sad		return (ENODEV);
7351.1Sad	part = DISKPART(dev);
7361.1Sad
7371.1Sad	omask = sc->sc_dk.dk_openmask & (1 << part);
7381.1Sad
7391.1Sad	if (omask == 0 && ldopen(dev, 0, S_IFBLK, NULL) != 0)
7401.1Sad		return (-1);
7411.1Sad	else if (sc->sc_dk.dk_label->d_partitions[part].p_fstype != FS_SWAP)
7421.1Sad		size = -1;
7431.1Sad	else
7441.1Sad		size = sc->sc_dk.dk_label->d_partitions[part].p_size *
7451.1Sad		    (sc->sc_dk.dk_label->d_secsize / DEV_BSIZE);
7461.1Sad	if (omask == 0 && ldclose(dev, 0, S_IFBLK, NULL) != 0)
7471.1Sad		return (-1);
7481.1Sad
7491.1Sad	return (size);
7501.1Sad}
7511.1Sad
7521.1Sad/*
7531.1Sad * Load the label information from the specified device.
7541.1Sad */
7551.1Sadstatic void
7561.1Sadldgetdisklabel(struct ld_softc *sc)
7571.1Sad{
7581.1Sad	const char *errstring;
7591.1Sad
7601.1Sad	ldgetdefaultlabel(sc, sc->sc_dk.dk_label);
7611.1Sad
7621.1Sad	/* Call the generic disklabel extraction routine. */
7631.37.2.1Syamt	errstring = readdisklabel(MAKEDISKDEV(0, device_unit(&sc->sc_dv),
7641.37.2.1Syamt	    RAW_PART), ldstrategy, sc->sc_dk.dk_label, sc->sc_dk.dk_cpulabel);
7651.1Sad	if (errstring != NULL)
7661.1Sad		printf("%s: %s\n", sc->sc_dv.dv_xname, errstring);
7671.22Sthorpej
7681.22Sthorpej	/* In-core label now valid. */
7691.22Sthorpej	sc->sc_flags |= LDF_VLABEL;
7701.1Sad}
7711.1Sad
7721.1Sad/*
7731.1Sad * Construct a ficticious label.
7741.1Sad */
7751.1Sadstatic void
7761.1Sadldgetdefaultlabel(struct ld_softc *sc, struct disklabel *lp)
7771.1Sad{
7781.1Sad
7791.1Sad	memset(lp, 0, sizeof(struct disklabel));
7801.1Sad
7811.1Sad	lp->d_secsize = sc->sc_secsize;
7821.1Sad	lp->d_ntracks = sc->sc_nheads;
7831.1Sad	lp->d_nsectors = sc->sc_nsectors;
7841.1Sad	lp->d_ncylinders = sc->sc_ncylinders;
7851.1Sad	lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors;
7861.1Sad	lp->d_type = DTYPE_LD;
7871.21Sitojun	strlcpy(lp->d_typename, "unknown", sizeof(lp->d_typename));
7881.21Sitojun	strlcpy(lp->d_packname, "fictitious", sizeof(lp->d_packname));
7891.1Sad	lp->d_secperunit = sc->sc_secperunit;
7901.1Sad	lp->d_rpm = 7200;
7911.1Sad	lp->d_interleave = 1;
7921.1Sad	lp->d_flags = 0;
7931.1Sad
7941.1Sad	lp->d_partitions[RAW_PART].p_offset = 0;
7951.1Sad	lp->d_partitions[RAW_PART].p_size =
7961.1Sad	    lp->d_secperunit * (lp->d_secsize / DEV_BSIZE);
7971.1Sad	lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED;
7981.1Sad	lp->d_npartitions = RAW_PART + 1;
7991.1Sad
8001.1Sad	lp->d_magic = DISKMAGIC;
8011.1Sad	lp->d_magic2 = DISKMAGIC;
8021.1Sad	lp->d_checksum = dkcksum(lp);
8031.1Sad}
8041.1Sad
8051.1Sad/*
8061.1Sad * Take a dump.
8071.1Sad */
8081.29Sthorpejstatic int
8091.37.2.3Syamtlddump(dev_t dev, daddr_t blkno, void *vav, size_t size)
8101.1Sad{
8111.37.2.3Syamt	char *va = vav;
8121.1Sad	struct ld_softc *sc;
8131.1Sad	struct disklabel *lp;
8141.1Sad	int unit, part, nsects, sectoff, towrt, nblk, maxblkcnt, rv;
8151.1Sad	static int dumping;
8161.1Sad
8171.1Sad	unit = DISKUNIT(dev);
8181.1Sad	if ((sc = device_lookup(&ld_cd, unit)) == NULL)
8191.1Sad		return (ENXIO);
8201.1Sad	if ((sc->sc_flags & LDF_ENABLED) == 0)
8211.1Sad		return (ENODEV);
8221.3Sad	if (sc->sc_dump == NULL)
8231.3Sad		return (ENXIO);
8241.3Sad
8251.3Sad	/* Check if recursive dump; if so, punt. */
8261.3Sad	if (dumping)
8271.3Sad		return (EFAULT);
8281.3Sad	dumping = 1;
8291.1Sad
8301.1Sad	/* Convert to disk sectors.  Request must be a multiple of size. */
8311.3Sad	part = DISKPART(dev);
8321.1Sad	lp = sc->sc_dk.dk_label;
8331.1Sad	if ((size % lp->d_secsize) != 0)
8341.1Sad		return (EFAULT);
8351.1Sad	towrt = size / lp->d_secsize;
8361.1Sad	blkno = dbtob(blkno) / lp->d_secsize;	/* blkno in DEV_BSIZE units */
8371.1Sad
8381.1Sad	nsects = lp->d_partitions[part].p_size;
8391.1Sad	sectoff = lp->d_partitions[part].p_offset;
8401.1Sad
8411.1Sad	/* Check transfer bounds against partition size. */
8421.1Sad	if ((blkno < 0) || ((blkno + towrt) > nsects))
8431.1Sad		return (EINVAL);
8441.1Sad
8451.1Sad	/* Offset block number to start of partition. */
8461.1Sad	blkno += sectoff;
8471.1Sad
8481.1Sad	/* Start dumping and return when done. */
8491.3Sad	maxblkcnt = sc->sc_maxxfer / sc->sc_secsize - 1;
8501.1Sad	while (towrt > 0) {
8511.3Sad		nblk = min(maxblkcnt, towrt);
8521.1Sad
8531.1Sad		if ((rv = (*sc->sc_dump)(sc, va, blkno, nblk)) != 0)
8541.1Sad			return (rv);
8551.1Sad
8561.1Sad		towrt -= nblk;
8571.1Sad		blkno += nblk;
8581.1Sad		va += nblk * sc->sc_secsize;
8591.1Sad	}
8601.1Sad
8611.1Sad	dumping = 0;
8621.1Sad	return (0);
8631.1Sad}
8641.1Sad
8651.1Sad/*
8661.1Sad * Adjust the size of a transfer.
8671.1Sad */
8681.1Sadstatic void
8691.1Sadldminphys(struct buf *bp)
8701.1Sad{
8711.1Sad	struct ld_softc *sc;
8721.1Sad
8731.1Sad	sc = device_lookup(&ld_cd, DISKUNIT(bp->b_dev));
8741.1Sad
8751.1Sad	if (bp->b_bcount > sc->sc_maxxfer)
8761.1Sad		bp->b_bcount = sc->sc_maxxfer;
8771.1Sad	minphys(bp);
8781.1Sad}
8791.37.2.2Syamt
8801.37.2.2Syamtstatic void
8811.37.2.2Syamtld_set_properties(struct ld_softc *ld)
8821.37.2.2Syamt{
8831.37.2.2Syamt	prop_dictionary_t disk_info, odisk_info, geom;
8841.37.2.2Syamt
8851.37.2.2Syamt	disk_info = prop_dictionary_create();
8861.37.2.2Syamt
8871.37.2.2Syamt	geom = prop_dictionary_create();
8881.37.2.2Syamt
8891.37.2.2Syamt	prop_dictionary_set_uint64(geom, "sectors-per-unit",
8901.37.2.2Syamt	    ld->sc_secperunit);
8911.37.2.2Syamt
8921.37.2.2Syamt	prop_dictionary_set_uint32(geom, "sector-size",
8931.37.2.2Syamt	    ld->sc_secsize);
8941.37.2.2Syamt
8951.37.2.2Syamt	prop_dictionary_set_uint16(geom, "sectors-per-track",
8961.37.2.2Syamt	    ld->sc_nsectors);
8971.37.2.2Syamt
8981.37.2.2Syamt	prop_dictionary_set_uint16(geom, "tracks-per-cylinder",
8991.37.2.2Syamt	    ld->sc_nheads);
9001.37.2.2Syamt
9011.37.2.2Syamt	prop_dictionary_set_uint64(geom, "cylinders-per-unit",
9021.37.2.2Syamt	    ld->sc_ncylinders);
9031.37.2.2Syamt
9041.37.2.2Syamt	prop_dictionary_set(disk_info, "geometry", geom);
9051.37.2.2Syamt	prop_object_release(geom);
9061.37.2.2Syamt
9071.37.2.2Syamt	prop_dictionary_set(device_properties(&ld->sc_dv),
9081.37.2.2Syamt	    "disk-info", disk_info);
9091.37.2.2Syamt
9101.37.2.2Syamt	/*
9111.37.2.2Syamt	 * Don't release disk_info here; we keep a reference to it.
9121.37.2.2Syamt	 * disk_detach() will release it when we go away.
9131.37.2.2Syamt	 */
9141.37.2.2Syamt
9151.37.2.2Syamt	odisk_info = ld->sc_dk.dk_info;
9161.37.2.2Syamt	ld->sc_dk.dk_info = disk_info;
9171.37.2.2Syamt	if (odisk_info)
9181.37.2.2Syamt		prop_object_release(odisk_info);
9191.37.2.2Syamt}
9201.37.2.2Syamt
9211.37.2.2Syamtstatic void
9221.37.2.2Syamtld_config_interrupts (struct device *d)
9231.37.2.2Syamt{
9241.37.2.2Syamt	struct ld_softc *sc = (struct ld_softc *)d;
9251.37.2.2Syamt	dkwedge_discover(&sc->sc_dk);
9261.37.2.2Syamt}
927