You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
562 lines
14 KiB
562 lines
14 KiB
/* $Id */
|
|
|
|
/* $OpenBSD: cd.c,v 1.54 2000/04/18 06:34:18 csapuntz Exp $ */
|
|
/* $NetBSD: cd.c,v 1.100 1997/04/02 02:29:30 mycroft Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1994, 1995, 1997 Charles M. Hannum. All rights reserved.
|
|
*
|
|
* 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 Charles M. Hannum.
|
|
* 4. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
|
*/
|
|
|
|
/*
|
|
* Originally written by Julian Elischer (julian@tfs.com)
|
|
* for TRW Financial Systems for use under the MACH(2.5) operating system.
|
|
*
|
|
* TRW Financial Systems, in accordance with their agreement with Carnegie
|
|
* Mellon University, makes this software available to CMU to distribute
|
|
* or use in any manner that they see fit as long as this message is kept with
|
|
* the software. For this reason TFS also grants any other persons or
|
|
* organisations permission to use or modify this software.
|
|
*
|
|
* TFS supplies this software to be publicly redistributed
|
|
* on the understanding that TFS is not responsible for the correct
|
|
* functioning of this software in any circumstances.
|
|
*
|
|
* Ported to run under 386BSD by Julian Elischer (julian@tfs.com) Sept 1992
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/file.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/device.h>
|
|
#include <sys/disklabel.h>
|
|
#include <sys/disk.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/scsiio.h>
|
|
#include <sys/vnode.h>
|
|
|
|
#include <scsi/scsi_all.h>
|
|
#include <scsi/cd.h>
|
|
#include <scsi/scsi_disk.h> /* rw_big and start_stop come from there */
|
|
#include <scsi/scsiconf.h>
|
|
|
|
|
|
#define CDRETRIES 4
|
|
#define CDBLKSIZE 2048 /* XXX hardwired in */
|
|
|
|
struct cd_ops;
|
|
|
|
struct cd_softc {
|
|
struct device sc_dev;
|
|
struct disk sc_dk;
|
|
|
|
int flags;
|
|
#define CDF_LOCKED 0x01
|
|
#define CDF_WANTED 0x02
|
|
#define CDF_WLABEL 0x04 /* label is writable */
|
|
#define CDF_LABELLING 0x08 /* writing label */
|
|
struct scsi_link *sc_link; /* contains our targ, lun, etc. */
|
|
struct buf buf_queue;
|
|
char name[16]; /* product name, for default disklabel */
|
|
};
|
|
|
|
#define CDOUTSTANDING 4
|
|
|
|
#define CDUNIT(z) DISKUNIT(z)
|
|
#define CDMINOR(unit, part) DISKMINOR(unit, part)
|
|
#if 0
|
|
#define CDPART(z) DISKPART(z)
|
|
#else
|
|
#define CDPART(z) RAW_PART
|
|
#endif
|
|
#define MAKECDDEV(maj, unit, part) MAKEDISKDEV(maj, unit, part)
|
|
|
|
#define CDLABELDEV(dev) (MAKECDDEV(major(dev), CDUNIT(dev), RAW_PART))
|
|
|
|
int cdmatch __P((struct device *, void *, void *));
|
|
void cdattach __P((struct device *, struct device *, void *));
|
|
int cdopen(dev_t dev, int flag, int fmt, struct proc *p);
|
|
int cdclose(dev_t dev, int flag, int fmt, struct proc *p);
|
|
void cdstrategy(struct buf *bp);
|
|
int cdread(dev_t dev, struct uio *uio, int ioflag);
|
|
int cdwrite(dev_t dev, struct uio *uio, int ioflag);
|
|
|
|
void cdstart __P((void *));
|
|
void cddone __P((struct scsi_xfer *));
|
|
|
|
struct cfattach cd_ca = {
|
|
sizeof(struct cd_softc), cdmatch, cdattach
|
|
};
|
|
|
|
struct cfdriver cd_cd = {
|
|
NULL, "cd", DV_DISK
|
|
};
|
|
|
|
struct scsi_device cd_switch = {
|
|
NULL, /* use default error handler */
|
|
cdstart, /* we have a queue, which is started by this */
|
|
NULL, /* we do not have an async handler */
|
|
cddone, /* deal with stats at interrupt time */
|
|
};
|
|
|
|
struct scsi_inquiry_pattern cd_patterns[] = {
|
|
{T_CDROM, T_REMOV,
|
|
"", "", ""},
|
|
{T_WORM, T_REMOV,
|
|
"", "", ""},
|
|
{T_DIRECT, T_REMOV,
|
|
"NEC CD-ROM DRIVE:260", "", ""},
|
|
#if 0
|
|
{T_CDROM, T_REMOV, /* more luns */
|
|
"PIONEER ", "CD-ROM DRM-600 ", ""},
|
|
#endif
|
|
};
|
|
|
|
#define cdlookup(unit) (struct cd_softc *)device_lookup(&cd_cd, (unit))
|
|
|
|
int
|
|
cdmatch(parent, match, aux)
|
|
struct device *parent;
|
|
void *match, *aux;
|
|
{
|
|
struct scsibus_attach_args *sa = aux;
|
|
int priority;
|
|
|
|
(void)scsi_inqmatch(sa->sa_inqbuf,
|
|
(caddr_t)cd_patterns, sizeof(cd_patterns)/sizeof(cd_patterns[0]),
|
|
sizeof(cd_patterns[0]), &priority);
|
|
return (priority);
|
|
}
|
|
|
|
/*
|
|
* The routine called by the low level scsi routine when it discovers
|
|
* A device suitable for this driver
|
|
*/
|
|
void
|
|
cdattach(parent, self, aux)
|
|
struct device *parent, *self;
|
|
void *aux;
|
|
{
|
|
struct cd_softc *cd = (void *)self;
|
|
struct scsibus_attach_args *sa = aux;
|
|
struct scsi_link *sc_link = sa->sa_sc_link;
|
|
|
|
SC_DEBUG(sc_link, SDEV_DB2, ("cdattach: "));
|
|
|
|
/*
|
|
* Store information needed to contact our base driver
|
|
*/
|
|
cd->sc_link = sc_link;
|
|
sc_link->device = &cd_switch;
|
|
sc_link->device_softc = cd;
|
|
if (sc_link->openings > CDOUTSTANDING)
|
|
sc_link->openings = CDOUTSTANDING;
|
|
|
|
/*
|
|
* Initialize and attach the disk structure.
|
|
*/
|
|
cd->sc_dk.dk_name = cd->sc_dev.dv_xname;
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
/*
|
|
* open the device. Make sure the partition info is a up-to-date as can be.
|
|
*/
|
|
int
|
|
cdopen(dev, flag, fmt, p)
|
|
dev_t dev;
|
|
int flag, fmt;
|
|
struct proc *p;
|
|
{
|
|
struct cd_softc *cd;
|
|
struct scsi_link *sc_link;
|
|
int unit, part;
|
|
int error;
|
|
|
|
unit = CDUNIT(dev);
|
|
cd = cdlookup(unit);
|
|
if (cd == NULL)
|
|
return ENXIO;
|
|
|
|
sc_link = cd->sc_link;
|
|
|
|
SC_DEBUG(sc_link, SDEV_DB1,
|
|
("cdopen: dev=0x%x (unit %d (of %d), partition %d)\n", dev, unit,
|
|
cd_cd.cd_ndevs, CDPART(dev)));
|
|
|
|
if (cd->sc_dk.dk_openmask != 0) {
|
|
/*
|
|
* If any partition is open, but the disk has been invalidated,
|
|
* disallow further opens.
|
|
*/
|
|
if ((sc_link->flags & SDEV_MEDIA_LOADED) == 0) {
|
|
error = EIO;
|
|
goto bad3;
|
|
}
|
|
} else {
|
|
/* Check that it is still responding and ok. */
|
|
error = scsi_test_unit_ready(sc_link,
|
|
SCSI_IGNORE_ILLEGAL_REQUEST |
|
|
SCSI_IGNORE_MEDIA_CHANGE |
|
|
SCSI_IGNORE_NOT_READY);
|
|
if (error)
|
|
goto bad3;
|
|
|
|
/* Start the pack spinning if necessary. */
|
|
error = scsi_start(sc_link, SSS_START,
|
|
SCSI_IGNORE_ILLEGAL_REQUEST |
|
|
SCSI_IGNORE_MEDIA_CHANGE | SCSI_SILENT);
|
|
if (error)
|
|
goto bad3;
|
|
|
|
sc_link->flags |= SDEV_OPEN;
|
|
|
|
/* Lock the pack in. */
|
|
error = scsi_prevent(sc_link, PR_PREVENT,
|
|
SCSI_IGNORE_ILLEGAL_REQUEST |
|
|
SCSI_IGNORE_MEDIA_CHANGE);
|
|
if (error)
|
|
goto bad;
|
|
|
|
if ((sc_link->flags & SDEV_MEDIA_LOADED) == 0) {
|
|
sc_link->flags |= SDEV_MEDIA_LOADED;
|
|
}
|
|
}
|
|
|
|
part = CDPART(dev);
|
|
|
|
/* Fake some label info that we are going to use. If we
|
|
* want to be more advanced in the future, eg not ony RAW
|
|
* this label must probably be filled in some more...
|
|
*/
|
|
cd->sc_dk.dk_label->d_secsize = 2048; /* XXX Yeah... */
|
|
|
|
/* Check that the partition exists. */
|
|
if (part != RAW_PART &&
|
|
(part >= cd->sc_dk.dk_label->d_npartitions ||
|
|
cd->sc_dk.dk_label->d_partitions[part].p_fstype == FS_UNUSED)) {
|
|
error = ENXIO;
|
|
goto bad;
|
|
}
|
|
|
|
/* Insure only one open at a time. */
|
|
switch (fmt) {
|
|
case S_IFCHR:
|
|
cd->sc_dk.dk_copenmask |= (1 << part);
|
|
break;
|
|
case S_IFBLK:
|
|
cd->sc_dk.dk_bopenmask |= (1 << part);
|
|
break;
|
|
}
|
|
cd->sc_dk.dk_openmask = cd->sc_dk.dk_copenmask | cd->sc_dk.dk_bopenmask;
|
|
|
|
SC_DEBUG(sc_link, SDEV_DB3, ("open complete\n"));
|
|
return 0;
|
|
|
|
sc_link->flags &= ~SDEV_MEDIA_LOADED;
|
|
|
|
bad:
|
|
if (cd->sc_dk.dk_openmask == 0) {
|
|
scsi_prevent(sc_link, PR_ALLOW,
|
|
SCSI_IGNORE_ILLEGAL_REQUEST | SCSI_IGNORE_MEDIA_CHANGE);
|
|
sc_link->flags &= ~SDEV_OPEN;
|
|
}
|
|
|
|
bad3:
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* close the device.. only called if we are the LAST
|
|
* occurence of an open device
|
|
*/
|
|
int
|
|
cdclose(dev, flag, fmt, p)
|
|
dev_t dev;
|
|
int flag, fmt;
|
|
struct proc *p;
|
|
{
|
|
struct cd_softc *cd;
|
|
int part = CDPART(dev);
|
|
|
|
cd = cdlookup(CDUNIT(dev));
|
|
if (cd == NULL)
|
|
return ENXIO;
|
|
|
|
switch (fmt) {
|
|
case S_IFCHR:
|
|
cd->sc_dk.dk_copenmask &= ~(1 << part);
|
|
break;
|
|
case S_IFBLK:
|
|
cd->sc_dk.dk_bopenmask &= ~(1 << part);
|
|
break;
|
|
}
|
|
cd->sc_dk.dk_openmask = cd->sc_dk.dk_copenmask | cd->sc_dk.dk_bopenmask;
|
|
|
|
if (cd->sc_dk.dk_openmask == 0) {
|
|
/* XXXX Must wait for I/O to complete! */
|
|
|
|
scsi_prevent(cd->sc_link, PR_ALLOW,
|
|
SCSI_IGNORE_ILLEGAL_REQUEST | SCSI_IGNORE_NOT_READY);
|
|
cd->sc_link->flags &= ~SDEV_OPEN;
|
|
|
|
if (cd->sc_link->flags & SDEV_EJECTING) {
|
|
scsi_start(cd->sc_link, SSS_STOP|SSS_LOEJ, 0);
|
|
|
|
cd->sc_link->flags &= ~SDEV_EJECTING;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Actually translate the requested transfer into one the physical driver can
|
|
* understand. The transfer is described by a buf and will include only one
|
|
* physical transfer.
|
|
*/
|
|
void
|
|
cdstrategy(bp)
|
|
struct buf *bp;
|
|
{
|
|
struct cd_softc *cd;
|
|
int opri;
|
|
|
|
if ((cd = cdlookup(CDUNIT(bp->b_dev))) == NULL) {
|
|
bp->b_error = ENXIO;
|
|
goto bad;
|
|
}
|
|
|
|
SC_DEBUG(cd->sc_link, SDEV_DB2, ("cdstrategy "));
|
|
SC_DEBUG(cd->sc_link, SDEV_DB1,
|
|
("%ld bytes @ blk %d\n", bp->b_bcount, bp->b_blkno));
|
|
/*
|
|
* The transfer must be a whole number of blocks.
|
|
*/
|
|
if ((bp->b_bcount % cd->sc_dk.dk_label->d_secsize) != 0) {
|
|
bp->b_error = EINVAL;
|
|
goto bad;
|
|
}
|
|
/*
|
|
* If the device has been made invalid, error out
|
|
* maybe the media changed
|
|
*/
|
|
if ((cd->sc_link->flags & SDEV_MEDIA_LOADED) == 0) {
|
|
bp->b_error = EIO;
|
|
goto bad;
|
|
}
|
|
/*
|
|
* If it's a null transfer, return immediately
|
|
*/
|
|
if (bp->b_bcount == 0)
|
|
goto done;
|
|
|
|
opri = splbio();
|
|
|
|
/*
|
|
* Place it in the queue of disk activities for this disk
|
|
*/
|
|
disksort(&cd->buf_queue, bp);
|
|
|
|
/*
|
|
* Tell the device to get going on the transfer if it's
|
|
* not doing anything, otherwise just wait for completion
|
|
*/
|
|
cdstart(cd);
|
|
|
|
splx(opri);
|
|
return;
|
|
|
|
bad:
|
|
bp->b_flags |= B_ERROR;
|
|
done:
|
|
/*
|
|
* Correctly set the buf to indicate a completed xfer
|
|
*/
|
|
bp->b_resid = bp->b_bcount;
|
|
biodone(bp);
|
|
}
|
|
|
|
/*
|
|
* cdstart looks to see if there is a buf waiting for the device
|
|
* and that the device is not already busy. If both are true,
|
|
* It deques the buf and creates a scsi command to perform the
|
|
* transfer in the buf. The transfer request will call scsi_done
|
|
* on completion, which will in turn call this routine again
|
|
* so that the next queued transfer is performed.
|
|
* The bufs are queued by the strategy routine (cdstrategy)
|
|
*
|
|
* This routine is also called after other non-queued requests
|
|
* have been made of the scsi driver, to ensure that the queue
|
|
* continues to be drained.
|
|
*
|
|
* must be called at the correct (highish) spl level
|
|
* cdstart() is called at splbio from cdstrategy and scsi_done
|
|
*/
|
|
void
|
|
cdstart(v)
|
|
register void *v;
|
|
{
|
|
register struct cd_softc *cd = v;
|
|
register struct scsi_link *sc_link = cd->sc_link;
|
|
struct buf *bp = 0;
|
|
struct buf *dp;
|
|
struct scsi_rw_big cmd_big;
|
|
struct scsi_rw cmd_small;
|
|
struct scsi_generic *cmdp;
|
|
int blkno, nblks, cmdlen;
|
|
struct partition *p;
|
|
|
|
SC_DEBUG(sc_link, SDEV_DB2, ("cdstart "));
|
|
/*
|
|
* Check if the device has room for another command
|
|
*/
|
|
while (sc_link->openings > 0) {
|
|
/*
|
|
* there is excess capacity, but a special waits
|
|
* It'll need the adapter as soon as we clear out of the
|
|
* way and let it run (user level wait).
|
|
*/
|
|
if (sc_link->flags & SDEV_WAITING) {
|
|
sc_link->flags &= ~SDEV_WAITING;
|
|
wakeup((caddr_t)sc_link);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* See if there is a buf with work for us to do..
|
|
*/
|
|
dp = &cd->buf_queue;
|
|
if ((bp = dp->b_actf) == NULL) /* yes, an assign */
|
|
return;
|
|
dp->b_actf = bp->b_actf;
|
|
|
|
/*
|
|
* If the deivce has become invalid, abort all the
|
|
* reads and writes until all files have been closed and
|
|
* re-opened
|
|
*/
|
|
if ((sc_link->flags & SDEV_MEDIA_LOADED) == 0) {
|
|
bp->b_error = EIO;
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_resid = bp->b_bcount;
|
|
biodone(bp);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We have a buf, now we should make a command
|
|
*
|
|
* First, translate the block to absolute and put it in terms
|
|
* of the logical blocksize of the device.
|
|
*/
|
|
blkno =
|
|
bp->b_blkno / (cd->sc_dk.dk_label->d_secsize / DEV_BSIZE);
|
|
if (CDPART(bp->b_dev) != RAW_PART) {
|
|
p = &cd->sc_dk.dk_label->d_partitions[CDPART(bp->b_dev)];
|
|
blkno += p->p_offset;
|
|
}
|
|
nblks = howmany(bp->b_bcount, cd->sc_dk.dk_label->d_secsize);
|
|
|
|
/*
|
|
* Fill out the scsi command. If the transfer will
|
|
* fit in a "small" cdb, use it.
|
|
*/
|
|
if (!(sc_link->flags & SDEV_ATAPI) &&
|
|
((blkno & 0x1fffff) == blkno) &&
|
|
((nblks & 0xff) == nblks)) {
|
|
/*
|
|
* We can fit in a small cdb.
|
|
*/
|
|
bzero(&cmd_small, sizeof(cmd_small));
|
|
cmd_small.opcode = (bp->b_flags & B_READ) ?
|
|
READ_COMMAND : WRITE_COMMAND;
|
|
_lto3b(blkno, cmd_small.addr);
|
|
cmd_small.length = nblks & 0xff;
|
|
cmdlen = sizeof(cmd_small);
|
|
cmdp = (struct scsi_generic *)&cmd_small;
|
|
} else {
|
|
/*
|
|
* Need a large cdb.
|
|
*/
|
|
bzero(&cmd_big, sizeof(cmd_big));
|
|
cmd_big.opcode = (bp->b_flags & B_READ) ?
|
|
READ_BIG : WRITE_BIG;
|
|
_lto4b(blkno, cmd_big.addr);
|
|
_lto2b(nblks, cmd_big.length);
|
|
cmdlen = sizeof(cmd_big);
|
|
cmdp = (struct scsi_generic *)&cmd_big;
|
|
}
|
|
|
|
/*
|
|
* Call the routine that chats with the adapter.
|
|
* Note: we cannot sleep as we may be an interrupt
|
|
*/
|
|
if (scsi_scsi_cmd(sc_link, cmdp, cmdlen,
|
|
(u_char *) bp->b_data, bp->b_bcount,
|
|
CDRETRIES, 30000, bp, SCSI_NOSLEEP |
|
|
((bp->b_flags & B_READ) ? SCSI_DATA_IN : SCSI_DATA_OUT))) {
|
|
printf("%s: not queued", cd->sc_dev.dv_xname);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
cddone(xs)
|
|
struct scsi_xfer *xs;
|
|
{
|
|
}
|
|
|
|
int
|
|
cdread(dev, uio, ioflag)
|
|
dev_t dev;
|
|
struct uio *uio;
|
|
int ioflag;
|
|
{
|
|
|
|
return (physio(cdstrategy, NULL, dev, B_READ, NULL, uio));
|
|
}
|
|
|
|
int
|
|
cdwrite(dev, uio, ioflag)
|
|
dev_t dev;
|
|
struct uio *uio;
|
|
int ioflag;
|
|
{
|
|
|
|
return (physio(cdstrategy, NULL, dev, B_WRITE, NULL, uio));
|
|
}
|
|
|