freebsd-dev/sys/scsi/cd.c
Nate Williams d97875d4b0 Removed PATCHKIT headers and updated our sources to Julian's with
our patches back in.  Major changes to the Bustek driver and the
Tape driver, the rest are minor.
1993-07-29 11:49:13 +00:00

1716 lines
46 KiB
C

/*
* 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.
*
*/
static char rev[] = "$Revision: 1.3 $";
/*
* Ported to run under 386BSD by Julian Elischer (julian@tfs.com) Sept 1992
*
*/
#define SPLCD splbio
#define ESUCCESS 0
#include <cd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/dkbad.h>
#include <sys/systm.h>
#include <sys/conf.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/cdio.h>
#include <sys/errno.h>
#include <sys/disklabel.h>
#include <scsi/scsi_all.h>
#include <scsi/scsi_cd.h>
#include <scsi/scsi_disk.h> /* rw_big and start_stop come from there */
#include <scsi/scsiconf.h>
long int cdstrats,cdqueues;
#include <ddb.h>
#if NDDB > 0
int Debugger();
#else NDDB > 0
#define Debugger()
#endif NDDB > 0
#define PAGESIZ 4096
#define SECSIZE 2048 /* XXX */ /* default only */
#define CDOUTSTANDING 2
#define CDQSIZE 4
#define CD_RETRIES 4
#define UNITSHIFT 3
#define PARTITION(z) (minor(z) & 0x07)
#define RAW_PART 3
#define UNIT(z) ( (minor(z) >> UNITSHIFT) )
extern int hz;
int cd_done();
int cdstrategy();
int cd_debug = 0;
struct buf cd_buf_queue[NCD];
struct scsi_xfer cd_scsi_xfer[NCD][CDOUTSTANDING]; /* XXX */
struct scsi_xfer *cd_free_xfer[NCD];
int cd_xfer_block_wait[NCD];
struct cd_data
{
int flags;
#define CDVALID 0x02 /* PARAMS LOADED */
#define CDINIT 0x04 /* device has been init'd */
#define CDWAIT 0x08 /* device has someone waiting */
#define CDHAVELABEL 0x10 /* have read the label */
struct scsi_switch *sc_sw; /* address of scsi low level switch */
int ctlr; /* so they know which one we want */
int targ; /* our scsi target ID */
int lu; /* out scsi lu */
int cmdscount; /* cmds allowed outstanding by board*/
struct cd_parms
{
int blksize;
u_long disksize; /* total number sectors */
}params;
struct disklabel disklabel;
int partflags[MAXPARTITIONS]; /* per partition flags */
#define CDOPEN 0x01
int openparts; /* one bit for each open partition */
}cd_data[NCD];
#define CD_STOP 0
#define CD_START 1
#define CD_EJECT -2
static int next_cd_unit = 0;
/***********************************************************************\
* The routine called by the low level scsi routine when it discovers *
* A device suitable for this driver *
\***********************************************************************/
int cdattach(ctlr,targ,lu,scsi_switch)
struct scsi_switch *scsi_switch;
{
int unit,i;
unsigned char *tbl;
struct cd_data *cd;
struct cd_parms *dp;
unit = next_cd_unit++;
cd = cd_data + unit;
dp = &(cd->params);
if(scsi_debug & PRINTROUTINES) printf("cdattach: ");
/*******************************************************\
* Check we have the resources for another drive *
\*******************************************************/
if( unit >= NCD)
{
printf("Too many scsi CDs..(%d > %d) reconfigure kernel",(unit + 1),NCD);
return(0);
}
/*******************************************************\
* Store information needed to contact our base driver *
\*******************************************************/
cd->sc_sw = scsi_switch;
cd->ctlr = ctlr;
cd->targ = targ;
cd->lu = lu;
cd->cmdscount = CDOUTSTANDING; /* XXX (ask the board) */
i = cd->cmdscount;
while(i-- )
{
cd_scsi_xfer[unit][i].next = cd_free_xfer[unit];
cd_free_xfer[unit] = &cd_scsi_xfer[unit][i];
}
/*******************************************************\
* Use the subdriver to request information regarding *
* the drive. We cannot use interrupts yet, so the *
* request must specify this. *
\*******************************************************/
cd_get_parms(unit, SCSI_NOSLEEP | SCSI_NOMASK);
if(dp->disksize)
{
printf("cd present\n");
}
else
{
printf("drive empty\n");
}
cd->flags |= CDINIT;
return;
}
/*******************************************************\
* open the device. Make sure the partition info *
* is a up-to-date as can be. *
\*******************************************************/
cdopen(dev)
{
int errcode = 0;
int unit, part;
struct cd_parms cd_parms;
struct cd_data *cd ;
unit = UNIT(dev);
part = PARTITION(dev);
cd = cd_data + unit;
if(scsi_debug & (PRINTROUTINES | TRACEOPENS))
printf("cdopen: dev=0x%x (unit %d (of %d),partition %d)\n"
, dev, unit, NCD, part);
/*******************************************************\
* Check the unit is legal *
\*******************************************************/
if ( unit >= NCD )
{
return(ENXIO);
}
/*******************************************************\
* Make sure the disk has been initialised *
* At some point in the future, get the scsi driver *
* to look for a new device if we are not initted *
\*******************************************************/
if (! (cd->flags & CDINIT))
return(ENXIO);
/*******************************************************\
* If it's been invalidated, and not everybody has *
* closed it then forbid re-entry. *
* (may have changed media) *
\*******************************************************/
if ((! (cd->flags & CDVALID))
&& ( cd->openparts))
return(ENXIO);
/*******************************************************\
* Check that it is still responding and ok. *
* if the media has been changed this will result in a *
* "unit attention" error which the error code will *
* disregard because the CDVALID flag is not yet set *
\*******************************************************/
if (cd_req_sense(unit, SCSI_SILENT) != 0) {
if(scsi_debug & TRACEOPENS)
printf("not reponding\n");
return(ENXIO);
}
if(scsi_debug & TRACEOPENS)
printf("Device present\n");
/*******************************************************\
* In case it is a funny one, tell it to start *
* not needed for hard drives *
\*******************************************************/
cd_start_unit(unit,part,CD_START);
cd_prevent_unit(unit,PR_PREVENT,SCSI_SILENT);
if(scsi_debug & TRACEOPENS)
printf("started ");
/*******************************************************\
* Load the physical device parameters *
\*******************************************************/
cd_get_parms(unit, 0);
if(scsi_debug & TRACEOPENS)
printf("Params loaded ");
/*******************************************************\
* Load the partition info if not already loaded *
\*******************************************************/
cdgetdisklabel(unit);
if(scsi_debug & TRACEOPENS)
printf("Disklabel fabricated ");
/*******************************************************\
* Check the partition is legal *
\*******************************************************/
if (( part >= cd->disklabel.d_npartitions )
&& (part != RAW_PART))
{
if(scsi_debug & TRACEOPENS)
printf("partition %d > %d\n",part
,cd->disklabel.d_npartitions);
cd_prevent_unit(unit,PR_ALLOW,SCSI_SILENT);
return(ENXIO);
}
/*******************************************************\
* Check that the partition exists *
\*******************************************************/
if (( cd->disklabel.d_partitions[part].p_fstype != FS_UNUSED )
|| (part == RAW_PART))
{
cd->partflags[part] |= CDOPEN;
cd->openparts |= (1 << part);
if(scsi_debug & TRACEOPENS)
printf("open complete\n");
cd->flags |= CDVALID;
}
else
{
if(scsi_debug & TRACEOPENS)
printf("part %d type UNUSED\n",part);
cd_prevent_unit(unit,PR_ALLOW,SCSI_SILENT);
return(ENXIO);
}
return(0);
}
/*******************************************************\
* Get ownership of a scsi_xfer structure *
* If need be, sleep on it, until it comes free *
\*******************************************************/
struct scsi_xfer *cd_get_xs(unit,flags)
int flags;
int unit;
{
struct scsi_xfer *xs;
int s;
if(flags & (SCSI_NOSLEEP | SCSI_NOMASK))
{
if (xs = cd_free_xfer[unit])
{
cd_free_xfer[unit] = xs->next;
xs->flags = 0;
}
}
else
{
s = SPLCD();
while (!(xs = cd_free_xfer[unit]))
{
cd_xfer_block_wait[unit]++; /* someone waiting! */
sleep((caddr_t)&cd_free_xfer[unit], PRIBIO+1);
cd_xfer_block_wait[unit]--;
}
cd_free_xfer[unit] = xs->next;
splx(s);
xs->flags = 0;
}
return(xs);
}
/*******************************************************\
* Free a scsi_xfer, wake processes waiting for it *
\*******************************************************/
cd_free_xs(unit,xs,flags)
struct scsi_xfer *xs;
int unit;
int flags;
{
int s;
if(flags & SCSI_NOMASK)
{
if (cd_xfer_block_wait[unit])
{
printf("doing a wakeup from NOMASK mode\n");
wakeup((caddr_t)&cd_free_xfer[unit]);
}
xs->next = cd_free_xfer[unit];
cd_free_xfer[unit] = xs;
}
else
{
s = SPLCD();
if (cd_xfer_block_wait[unit])
wakeup((caddr_t)&cd_free_xfer[unit]);
xs->next = cd_free_xfer[unit];
cd_free_xfer[unit] = xs;
splx(s);
}
}
/*******************************************************\
* trim the size of the transfer if needed, *
* called by physio *
* basically the smaller of our max and the scsi driver's*
* minphys (note we have no max ourselves) *
\*******************************************************/
/* Trim buffer length if buffer-size is bigger than page size */
void cdminphys(bp)
struct buf *bp;
{
(*(cd_data[UNIT(bp->b_dev)].sc_sw->scsi_minphys))(bp);
}
/*******************************************************\
* 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. *
\*******************************************************/
int cdstrategy(bp)
struct buf *bp;
{
struct buf *dp;
unsigned int opri;
struct cd_data *cd ;
int unit;
cdstrats++;
unit = UNIT((bp->b_dev));
cd = cd_data + unit;
if(scsi_debug & PRINTROUTINES) printf("\ncdstrategy ");
if(scsi_debug & SHOWREQUESTS) printf("cd%d: %d bytes @ blk%d\n",
unit,bp->b_bcount,bp->b_blkno);
cdminphys(bp);
/*******************************************************\
* If the device has been made invalid, error out *
* maybe the media changed *
\*******************************************************/
if(!(cd->flags & CDVALID))
{
bp->b_error = EIO;
goto bad;
}
/*******************************************************\
* can't ever write to a CD *
\*******************************************************/
if ((bp->b_flags & B_READ) == 0) {
bp->b_error = EROFS;
goto bad;
}
/*******************************************************\
* If it's a null transfer, return immediatly *
\*******************************************************/
if (bp->b_bcount == 0) {
goto done;
}
/*******************************************************\
* Decide which unit and partition we are talking about *
\*******************************************************/
if(PARTITION(bp->b_dev) != RAW_PART)
{
if (!(cd->flags & CDHAVELABEL))
{
bp->b_error = EIO;
goto bad;
}
/*
* do bounds checking, adjust transfer. if error, process.
* if end of partition, just return
*/
if (bounds_check_with_label(bp,&cd->disklabel,1) <= 0)
goto done;
/* otherwise, process transfer request */
}
opri = SPLCD();
dp = &cd_buf_queue[unit];
/*******************************************************\
* Place it in the queue of disk activities for this disk*
\*******************************************************/
disksort(dp, bp);
/*******************************************************\
* Tell the device to get going on the transfer if it's *
* not doing anything, otherwise just wait for completion*
\*******************************************************/
cdstart(unit);
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);
return;
}
/***************************************************************\
* 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 cd_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 SPLCD from cdstrategy and cd_done*/
cdstart(unit)
int unit;
{
register struct buf *bp = 0;
register struct buf *dp;
struct scsi_xfer *xs;
struct scsi_rw_big cmd;
int blkno, nblk;
struct cd_data *cd = cd_data + unit;
struct partition *p ;
if(scsi_debug & PRINTROUTINES) printf("cdstart%d ",unit);
/*******************************************************\
* See if there is a buf to do and we are not already *
* doing one *
\*******************************************************/
if(!cd_free_xfer[unit])
{
return; /* none for us, unit already underway */
}
if(cd_xfer_block_wait[unit]) /* there is one, but a special waits */
{
return; /* give the special that's waiting a chance to run */
}
dp = &cd_buf_queue[unit];
if ((bp = dp->b_actf) != NULL) /* yes, an assign */
{
dp->b_actf = bp->av_forw;
}
else
{
return;
}
xs=cd_get_xs(unit,0); /* ok we can grab it */
xs->flags = INUSE; /* Now ours */
/***************************************************************\
* Should reject all queued entries if CDVALID is not true *
\***************************************************************/
if(!(cd->flags & CDVALID))
{
goto bad; /* no I/O.. media changed or something */
}
/*******************************************************\
* We have a buf, now we should move the data into *
* a scsi_xfer definition and try start it *
\*******************************************************/
/*******************************************************\
* First, translate the block to absolute *
* and put it in terms of the logical blocksize of the *
* device.. *
\*******************************************************/
p = cd->disklabel.d_partitions + PARTITION(bp->b_dev);
blkno = ((bp->b_blkno / (cd->params.blksize/512)) + p->p_offset);
nblk = (bp->b_bcount + (cd->params.blksize - 1)) / (cd->params.blksize);
/*******************************************************\
* Fill out the scsi command *
\*******************************************************/
bzero(&cmd, sizeof(cmd));
cmd.op_code = READ_BIG;
cmd.addr_3 = (blkno & 0xff000000) >> 24;
cmd.addr_2 = (blkno & 0xff0000) >> 16;
cmd.addr_1 = (blkno & 0xff00) >> 8;
cmd.addr_0 = blkno & 0xff;
cmd.length2 = (nblk & 0xff00) >> 8;
cmd.length1 = (nblk & 0xff);
/*******************************************************\
* Fill out the scsi_xfer structure *
* Note: we cannot sleep as we may be an interrupt *
\*******************************************************/
xs->flags |= SCSI_NOSLEEP;
xs->adapter = cd->ctlr;
xs->targ = cd->targ;
xs->lu = cd->lu;
xs->retries = CD_RETRIES;
xs->timeout = 10000;/* 10000 millisecs for a disk !*/
xs->cmd = (struct scsi_generic *)&cmd;
xs->cmdlen = sizeof(cmd);
xs->resid = bp->b_bcount;
xs->when_done = cd_done;
xs->done_arg = unit;
xs->done_arg2 = (int)xs;
xs->error = XS_NOERROR;
xs->bp = bp;
xs->data = (u_char *)bp->b_un.b_addr;
xs->datalen = bp->b_bcount;
/*******************************************************\
* Pass all this info to the scsi driver. *
\*******************************************************/
if ( (*(cd->sc_sw->scsi_cmd))(xs) != SUCCESSFULLY_QUEUED)
{
printf("cd%d: oops not queued",unit);
goto bad;
}
cdqueues++;
return;
bad: xs->error = XS_DRIVER_STUFFUP;
cd_done(unit,xs);
}
/*******************************************************\
* This routine is called by the scsi interrupt when *
* the transfer is complete. (or failed) *
\*******************************************************/
int cd_done(unit,xs)
int unit;
struct scsi_xfer *xs;
{
struct buf *bp;
int retval;
if(scsi_debug & PRINTROUTINES) printf("cd_done%d ",unit);
if (! (xs->flags & INUSE)) /* paranoia always pays off */
panic("scsi_xfer not in use!");
if(bp = xs->bp)
{
switch(xs->error)
{
case XS_NOERROR:
bp->b_error = 0;
bp->b_resid = 0;
break;
case XS_SENSE:
retval = (cd_interpret_sense(unit,xs));
if(retval)
{
bp->b_flags |= B_ERROR;
bp->b_error = retval;
}
break;
case XS_TIMEOUT:
printf("cd%d timeout\n",unit);
case XS_BUSY:
/***********************************\
* Just resubmit it straight back to *
* the SCSI driver to try it again *
\***********************************/
if(xs->retries--)
{
xs->error = XS_NOERROR;
xs->flags &= ~ITSDONE;
if ( (*(cd_data[unit].sc_sw->scsi_cmd))(xs)
== SUCCESSFULLY_QUEUED)
{ /* shhh! don't wake the job, ok? */
/* don't tell cdstart either, */
return;
}
/* xs->error is set by the scsi driver */
} /* Fall through */
case XS_DRIVER_STUFFUP:
bp->b_flags |= B_ERROR;
bp->b_error = EIO;
break;
default:
printf("cd%d: unknown error category from scsi driver\n"
,unit);
}
biodone(bp);
cd_free_xs(unit,xs,0);
cdstart(unit); /* If there's anything waiting.. do it */
}
else /* special has finished */
{
wakeup(xs);
}
}
/*******************************************************\
* Perform special action on behalf of the user *
* Knows about the internals of this device *
\*******************************************************/
cdioctl(dev_t dev, int cmd, caddr_t addr, int flag)
{
int error = 0;
unsigned int opri;
unsigned char unit, part;
register struct cd_data *cd;
/*******************************************************\
* Find the device that the user is talking about *
\*******************************************************/
unit = UNIT(dev);
part = PARTITION(dev);
cd = &cd_data[unit];
if(scsi_debug & PRINTROUTINES) printf("cdioctl%d ",unit);
/*******************************************************\
* If the device is not valid.. abandon ship *
\*******************************************************/
if (!(cd_data[unit].flags & CDVALID))
return(EIO);
switch(cmd)
{
case DIOCSBAD:
error = EINVAL;
break;
case DIOCGDINFO:
*(struct disklabel *)addr = cd->disklabel;
break;
case DIOCGPART:
((struct partinfo *)addr)->disklab = &cd->disklabel;
((struct partinfo *)addr)->part =
&cd->disklabel.d_partitions[PARTITION(dev)];
break;
case DIOCWDINFO:
case DIOCSDINFO:
if ((flag & FWRITE) == 0)
error = EBADF;
else
error = setdisklabel(&cd->disklabel,
(struct disklabel *)addr,
/*(cd->flags & DKFL_BSDLABEL) ? cd->openparts : */0,
0);
if (error == 0) {
cd->flags |= CDHAVELABEL;
}
break;
case DIOCWLABEL:
error = EBADF;
break;
case CDIOCPLAYTRACKS:
{
struct ioc_play_track *args
= (struct ioc_play_track *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.sotc = 0;
data.page.audio.immed = 1;
if(error = cd_set_mode(unit,&data))
break;
return(cd_play_tracks(unit
,args->start_track
,args->start_index
,args->end_track
,args->end_index
));
}
break;
case CDIOCPLAYBLOCKS:
{
struct ioc_play_blocks *args
= (struct ioc_play_blocks *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.sotc = 0;
data.page.audio.immed = 1;
if(error = cd_set_mode(unit,&data))
break;
return(cd_play(unit,args->blk,args->len));
}
break;
case CDIOCREADSUBCHANNEL:
{
struct ioc_read_subchannel *args
= (struct ioc_read_subchannel *)addr;
struct cd_sub_channel_info data;
int len=args->data_len;
if(len>sizeof(data)||
len<sizeof(struct cd_sub_channel_header)) {
error=EINVAL;
break;
}
if(error = cd_read_subchannel(unit,args->address_format,
args->data_format,args->track,&data,len)) {
break;
}
len=MIN(len,((data.header.data_len[0]<<8)+data.header.data_len[1]+
sizeof(struct cd_sub_channel_header)));
if(copyout(&data,args->data,len)!=0) {
error=EFAULT;
}
}
break;
case CDIOREADTOCHEADER:
{
struct ioc_toc_header th;
if( error = cd_read_toc(unit,0,0,&th,sizeof(th)))
break;
th.len=(th.len&0xff)<<8+((th.len>>8)&0xff);
bcopy(&th,addr,sizeof(th));
}
break;
case CDIOREADTOCENTRYS:
{
struct ioc_read_toc_entry *te=
(struct ioc_read_toc_entry *)addr;
struct cd_toc_entry data[65];
struct ioc_toc_header *th;
int len=te->data_len;
th=(struct ioc_toc_header *)data;
if(len>sizeof(data) || len<sizeof(struct cd_toc_entry)) {
error=EINVAL;
break;
}
if(error = cd_read_toc(unit,te->address_format,
te->starting_track,
data,
len))
break;
len=MIN(len,((((th->len&0xff)<<8)+((th->len>>8)))+
sizeof(*th)));
if(copyout(th,te->data,len)!=0) {
error=EFAULT;
}
}
break;
case CDIOCSETPATCH:
{
struct ioc_patch *arg = (struct ioc_patch *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.port[LEFT_PORT].channels = arg->patch[0];
data.page.audio.port[RIGHT_PORT].channels = arg->patch[1];
data.page.audio.port[2].channels = arg->patch[2];
data.page.audio.port[3].channels = arg->patch[3];
if(error = cd_set_mode(unit,&data))
break;
}
break;
case CDIOCGETVOL:
{
struct ioc_vol *arg = (struct ioc_vol *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
arg->vol[LEFT_PORT] = data.page.audio.port[LEFT_PORT].volume;
arg->vol[RIGHT_PORT] = data.page.audio.port[RIGHT_PORT].volume;
arg->vol[2] = data.page.audio.port[2].volume;
arg->vol[3] = data.page.audio.port[3].volume;
}
break;
case CDIOCSETVOL:
{
struct ioc_vol *arg = (struct ioc_vol *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.port[LEFT_PORT].volume = arg->vol[LEFT_PORT];
data.page.audio.port[RIGHT_PORT].volume = arg->vol[RIGHT_PORT];
data.page.audio.port[2].volume = arg->vol[2];
data.page.audio.port[3].volume = arg->vol[3];
if(error = cd_set_mode(unit,&data))
break;
}
break;
case CDIOCSETMONO:
{
struct ioc_vol *arg = (struct ioc_vol *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.port[LEFT_PORT].channels = LEFT_CHANNEL|RIGHT_CHANNEL|4|8;
data.page.audio.port[RIGHT_PORT].channels = LEFT_CHANNEL|RIGHT_CHANNEL;
data.page.audio.port[2].channels = 0;
data.page.audio.port[3].channels = 0;
if(error = cd_set_mode(unit,&data))
break;
}
break;
case CDIOCSETSTERIO:
{
struct ioc_vol *arg = (struct ioc_vol *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.port[LEFT_PORT].channels = LEFT_CHANNEL;
data.page.audio.port[RIGHT_PORT].channels = RIGHT_CHANNEL;
data.page.audio.port[2].channels = 0;
data.page.audio.port[3].channels = 0;
if(error = cd_set_mode(unit,&data))
break;
}
break;
case CDIOCSETMUTE:
{
struct ioc_vol *arg = (struct ioc_vol *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.port[LEFT_PORT].channels = 0;
data.page.audio.port[RIGHT_PORT].channels = 0;
data.page.audio.port[2].channels = 0;
data.page.audio.port[3].channels = 0;
if(error = cd_set_mode(unit,&data))
break;
}
break;
case CDIOCSETLEFT:
{
struct ioc_vol *arg = (struct ioc_vol *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.port[LEFT_PORT].channels = 15;
data.page.audio.port[RIGHT_PORT].channels = 15;
data.page.audio.port[2].channels = 15;
data.page.audio.port[3].channels = 15;
if(error = cd_set_mode(unit,&data))
break;
}
break;
case CDIOCSETRIGHT:
{
struct ioc_vol *arg = (struct ioc_vol *)addr;
struct cd_mode_data data;
if(error = cd_get_mode(unit,&data,AUDIO_PAGE))
break;
data.page.audio.port[LEFT_PORT].channels = RIGHT_CHANNEL;
data.page.audio.port[RIGHT_PORT].channels = RIGHT_CHANNEL;
data.page.audio.port[2].channels = 0;
data.page.audio.port[3].channels = 0;
if(error = cd_set_mode(unit,&data))
break;
}
break;
case CDIOCRESUME:
error = cd_pause(unit,1);
break;
case CDIOCPAUSE:
error = cd_pause(unit,0);
break;
case CDIOCSTART:
error = cd_start_unit(unit,part,CD_START);
break;
case CDIOCSTOP:
error = cd_start_unit(unit,part,CD_STOP);
break;
case CDIOCEJECT:
error = cd_start_unit(unit,part,CD_EJECT);
break;
case CDIOCSETDEBUG:
scsi_debug = 0xfff; cd_debug = 0xfff;
break;
case CDIOCCLRDEBUG:
scsi_debug = 0; cd_debug = 0;
break;
case CDIOCRESET:
return(cd_reset(unit));
break;
default:
error = ENOTTY;
break;
}
return (error);
}
/*******************************************************\
* Load the label information on the named device *
* *
* EVENTUALLY take information about different *
* data tracks from the TOC and put it in the disklabel *
\*******************************************************/
int cdgetdisklabel(unit)
unsigned char unit;
{
/*unsigned int n, m;*/
char *errstring;
struct dos_partition *dos_partition_p;
struct cd_data *cd = cd_data + unit;
/*******************************************************\
* If the inflo is already loaded, use it *
\*******************************************************/
if(cd->flags & CDHAVELABEL) return;
bzero(&cd->disklabel,sizeof(struct disklabel));
/*******************************************************\
* make partition 3 the whole disk in case of failure *
* then get pdinfo *
\*******************************************************/
strncpy(cd->disklabel.d_typename,"scsi cd_rom",16);
strncpy(cd->disklabel.d_packname,"ficticious",16);
cd->disklabel.d_secsize = cd->params.blksize; /* as long as it's not 0 */
cd->disklabel.d_nsectors = 100;
cd->disklabel.d_ntracks = 1;
cd->disklabel.d_ncylinders = (cd->params.disksize / 100) + 1;
cd->disklabel.d_secpercyl = 100;
cd->disklabel.d_secperunit = cd->params.disksize;
cd->disklabel.d_rpm = 300;
cd->disklabel.d_interleave = 1;
cd->disklabel.d_flags = D_REMOVABLE;
cd->disklabel.d_npartitions = 1;
cd->disklabel.d_partitions[0].p_offset = 0;
cd->disklabel.d_partitions[0].p_size = cd->params.disksize;
cd->disklabel.d_partitions[0].p_fstype = 9;
cd->disklabel.d_magic = DISKMAGIC;
cd->disklabel.d_magic2 = DISKMAGIC;
cd->disklabel.d_checksum = dkcksum(&(cd->disklabel));
/*******************************************************\
* Signal to other users and routines that we now have a *
* disklabel that represents the media (maybe) *
\*******************************************************/
cd->flags |= CDHAVELABEL;
return(ESUCCESS);
}
/*******************************************************\
* Find out form the device what it's capacity is *
\*******************************************************/
cd_size(unit, flags)
{
struct scsi_read_cd_cap_data rdcap;
struct scsi_read_cd_capacity scsi_cmd;
int size;
int blksize;
/*******************************************************\
* make up a scsi command and ask the scsi driver to do *
* it for you. *
\*******************************************************/
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = READ_CD_CAPACITY;
/*******************************************************\
* If the command works, interpret the result as a 4 byte*
* number of blocks *
\*******************************************************/
if (cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
&rdcap,
sizeof(rdcap),
2000,
flags) != 0)
{
printf("could not get size of unit %d\n", unit);
return(0);
} else {
size = rdcap.addr_0 + 1 ;
size += rdcap.addr_1 << 8;
size += rdcap.addr_2 << 16;
size += rdcap.addr_3 << 24;
blksize = rdcap.length_0 ;
blksize += rdcap.length_1 << 8;
blksize += rdcap.length_2 << 16;
blksize += rdcap.length_3 << 24;
}
if(cd_debug)printf("cd%d: %d %d byte blocks\n",unit,size,blksize);
cd_data[unit].params.disksize = size;
cd_data[unit].params.blksize = blksize;
return(size);
}
/*******************************************************\
* Check with the device that it is ok, (via scsi driver)*
\*******************************************************/
cd_req_sense(unit, flags)
{
struct scsi_sense_data sense_data;
struct scsi_sense scsi_cmd;
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = REQUEST_SENSE;
scsi_cmd.length = sizeof(sense_data);
if (cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
&sense_data,
sizeof(sense_data),
2000,
flags) != 0)
{
return(ENXIO);
}
else
return(0);
}
/*******************************************************\
* Get the requested page into the buffer given *
\*******************************************************/
cd_get_mode(unit,data,page)
int unit;
struct cd_mode_data *data;
int page;
{
struct scsi_mode_sense scsi_cmd;
int retval;
bzero(&scsi_cmd, sizeof(scsi_cmd));
bzero(data,sizeof(*data));
scsi_cmd.op_code = MODE_SENSE;
scsi_cmd.page_code = page;
scsi_cmd.length = sizeof(*data) & 0xff;
retval = cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
data,
sizeof(*data),
20000, /* should be immed */
0);
return (retval);
}
/*******************************************************\
* Get the requested page into the buffer given *
\*******************************************************/
cd_set_mode(unit,data)
int unit;
struct cd_mode_data *data;
{
struct scsi_mode_select scsi_cmd;
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = MODE_SELECT;
scsi_cmd.pf = 1;
scsi_cmd.length = sizeof(*data) & 0xff;
data->header.data_length = 0;
/*show_mem(data,sizeof(*data));/**/
return (cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
data,
sizeof(*data),
20000, /* should be immed */
0)
);
}
/*******************************************************\
* Get scsi driver to send a "start playing" command *
\*******************************************************/
cd_play(unit,blk,len)
int unit,blk,len;
{
struct scsi_play scsi_cmd;
int retval;
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = PLAY;
scsi_cmd.blk_addr[0] = (blk >> 24) & 0xff;
scsi_cmd.blk_addr[1] = (blk >> 16) & 0xff;
scsi_cmd.blk_addr[2] = (blk >> 8) & 0xff;
scsi_cmd.blk_addr[3] = blk & 0xff;
scsi_cmd.xfer_len[0] = (len >> 8) & 0xff;
scsi_cmd.xfer_len[1] = len & 0xff;
retval = cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
0,
0,
200000, /* should be immed */
0);
return(retval);
}
/*******************************************************\
* Get scsi driver to send a "start playing" command *
\*******************************************************/
cd_play_big(unit,blk,len)
int unit,blk,len;
{
struct scsi_play_big scsi_cmd;
int retval;
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = PLAY_BIG;
scsi_cmd.blk_addr[0] = (blk >> 24) & 0xff;
scsi_cmd.blk_addr[1] = (blk >> 16) & 0xff;
scsi_cmd.blk_addr[2] = (blk >> 8) & 0xff;
scsi_cmd.blk_addr[3] = blk & 0xff;
scsi_cmd.xfer_len[0] = (len >> 24) & 0xff;
scsi_cmd.xfer_len[1] = (len >> 16) & 0xff;
scsi_cmd.xfer_len[2] = (len >> 8) & 0xff;
scsi_cmd.xfer_len[3] = len & 0xff;
retval = cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
0,
0,
20000, /* should be immed */
0);
return(retval);
}
/*******************************************************\
* Get scsi driver to send a "start playing" command *
\*******************************************************/
cd_play_tracks(unit,strack,sindex,etrack,eindex)
int unit,strack,sindex,etrack,eindex;
{
struct scsi_play_track scsi_cmd;
int retval;
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = PLAY_TRACK;
scsi_cmd.start_track = strack;
scsi_cmd.start_index = sindex;
scsi_cmd.end_track = etrack;
scsi_cmd.end_index = eindex;
retval = cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
0,
0,
20000, /* should be immed */
0);
return(retval);
}
/*******************************************************\
* Get scsi driver to send a "start up" command *
\*******************************************************/
cd_pause(unit,go)
int unit,go;
{
struct scsi_pause scsi_cmd;
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = PAUSE;
scsi_cmd.resume = go;
return (cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
0,
0,
2000,
0));
}
/*******************************************************\
* Get scsi driver to send a "start up" command *
\*******************************************************/
cd_reset(unit)
int unit;
{
return(cd_scsi_cmd(unit,0,0,0,0,2000,SCSI_RESET));
}
/*******************************************************\
* Get scsi driver to send a "start up" command *
\*******************************************************/
cd_start_unit(unit,part,type)
{
struct scsi_start_stop scsi_cmd;
if(type==CD_EJECT && (cd_data[unit].openparts&~(1<<part)) == 0 ) {
cd_prevent_unit(unit,CD_EJECT,0);
}
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = START_STOP;
scsi_cmd.start = type==CD_START?1:0;
scsi_cmd.loej = type==CD_EJECT?1:0;
if (cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(scsi_cmd),
0,
0,
2000,
0) != 0) {
return(ENXIO);
} else
return(0);
}
/*******************************************************\
* Prevent or allow the user to remove the disk *
\*******************************************************/
cd_prevent_unit(unit,type,flags)
int unit,type,flags;
{
struct scsi_prevent scsi_cmd;
if(type==CD_EJECT || type==PR_PREVENT || cd_data[unit].openparts == 0 ) {
bzero(&scsi_cmd, sizeof(scsi_cmd));
scsi_cmd.op_code = PREVENT_ALLOW;
scsi_cmd.prevent=type==CD_EJECT?PR_ALLOW:type;
if (cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(struct scsi_prevent),
0,
0,
5000,
0) != 0)
{
if(!(flags & SCSI_SILENT))
printf("cannot prevent/allow on cd%d\n", unit);
return(0);
}
}
return(1);
}
/******************************************************\
* Read Subchannel *
\******************************************************/
cd_read_subchannel(unit,mode,format,track,data,len)
int unit,mode,format,len;
struct cd_sub_channel_info *data;
{
struct scsi_read_subchannel scsi_cmd;
int error;
bzero(&scsi_cmd,sizeof(scsi_cmd));
scsi_cmd.op_code=READ_SUBCHANNEL;
if(mode==CD_MSF_FORMAT)
scsi_cmd.msf=1;
scsi_cmd.subQ=1;
scsi_cmd.subchan_format=format;
scsi_cmd.track=track;
scsi_cmd.data_len[0]=(len)>>8;
scsi_cmd.data_len[1]=(len)&0xff;
return cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(struct scsi_read_subchannel),
data,
len,
5000,
0);
}
/*******************************************************\
* Read Table of contents *
\*******************************************************/
cd_read_toc(unit,mode,start,data,len)
int unit,mode,start,len;
struct cd_toc_entry *data;
{
struct scsi_read_toc scsi_cmd;
int error;
int ntoc;
bzero(&scsi_cmd,sizeof(scsi_cmd));
/*if(len!=sizeof(struct ioc_toc_header))
ntoc=((len)-sizeof(struct ioc_toc_header))/sizeof(struct cd_toc_entry);
else*/
ntoc=len;
scsi_cmd.op_code=READ_TOC;
if(mode==CD_MSF_FORMAT)
scsi_cmd.msf=1;
scsi_cmd.from_track=start;
scsi_cmd.data_len[0]=(ntoc)>>8;
scsi_cmd.data_len[1]=(ntoc)&0xff;
return cd_scsi_cmd(unit,
&scsi_cmd,
sizeof(struct scsi_read_toc),
data,
len,
5000,
0);
}
#define b2tol(a) (((unsigned)(a##_1) << 8) + (unsigned)a##_0 )
/*******************************************************\
* Get the scsi driver to send a full inquiry to the *
* device and use the results to fill out the disk *
* parameter structure. *
\*******************************************************/
int cd_get_parms(unit, flags)
{
struct cd_data *cd = cd_data + unit;
/*******************************************************\
* First check if we have it all loaded *
\*******************************************************/
if(cd->flags & CDVALID) return(0);
/*******************************************************\
* give a number of sectors so that sec * trks * cyls *
* is <= disk_size *
\*******************************************************/
if(cd_size(unit, flags))
{
cd->flags |= CDVALID;
return(0);
}
else
{
return(ENXIO);
}
}
/*******************************************************\
* close the device.. only called if we are the LAST *
* occurence of an open device *
\*******************************************************/
cdclose(dev)
dev_t dev;
{
unsigned char unit, part;
unsigned int old_priority;
unit = UNIT(dev);
part = PARTITION(dev);
if(scsi_debug & TRACEOPENS)
printf("closing cd%d part %d\n",unit,part);
cd_data[unit].partflags[part] &= ~CDOPEN;
cd_data[unit].openparts &= ~(1 << part);
cd_prevent_unit(unit,PR_ALLOW,SCSI_SILENT);
return(0);
}
/*******************************************************\
* ask the scsi driver to perform a command for us. *
* Call it through the switch table, and tell it which *
* sub-unit we want, and what target and lu we wish to *
* talk to. Also tell it where to find the command *
* how long int is. *
* Also tell it where to read/write the data, and how *
* long the data is supposed to be *
\*******************************************************/
int cd_scsi_cmd(unit,scsi_cmd,cmdlen,data_addr,datalen,timeout,flags)
int unit,flags;
struct scsi_generic *scsi_cmd;
int cmdlen;
int timeout;
u_char *data_addr;
int datalen;
{
struct scsi_xfer *xs;
int retval;
int s;
struct cd_data *cd = cd_data + unit;
if(scsi_debug & PRINTROUTINES) printf("\ncd_scsi_cmd%d ",unit);
if(cd->sc_sw) /* If we have a scsi driver */
{
xs = cd_get_xs(unit,flags); /* should wait unless booting */
if(!xs)
{
printf("cd_scsi_cmd%d: controller busy"
" (this should never happen)\n",unit);
return(EBUSY);
}
xs->flags |= INUSE;
/*******************************************************\
* Fill out the scsi_xfer structure *
\*******************************************************/
xs->flags |= flags;
xs->adapter = cd->ctlr;
xs->targ = cd->targ;
xs->lu = cd->lu;
xs->retries = CD_RETRIES;
xs->timeout = timeout;
xs->cmd = scsi_cmd;
xs->cmdlen = cmdlen;
xs->data = data_addr;
xs->datalen = datalen;
xs->resid = datalen;
xs->when_done = (flags & SCSI_NOMASK)
?(int (*)())0
:cd_done;
xs->done_arg = unit;
xs->done_arg2 = (int)xs;
retry: xs->error = XS_NOERROR;
xs->bp = 0;
retval = (*(cd->sc_sw->scsi_cmd))(xs);
switch(retval)
{
case SUCCESSFULLY_QUEUED:
s = splbio();
while(!(xs->flags & ITSDONE))
sleep(xs,PRIBIO+1);
splx(s);
case HAD_ERROR:
/*printf("err = %d ",xs->error);*/
switch(xs->error)
{
case XS_NOERROR:
retval = ESUCCESS;
break;
case XS_SENSE:
retval = (cd_interpret_sense(unit,xs));
break;
case XS_DRIVER_STUFFUP:
retval = EIO;
break;
case XS_BUSY:
case XS_TIMEOUT:
if(xs->retries-- )
{
xs->flags &= ~ITSDONE;
goto retry;
}
retval = EIO;
break;
default:
retval = EIO;
printf("cd%d: unknown error category from scsi driver\n"
,unit);
}
break;
case COMPLETE:
retval = ESUCCESS;
break;
case TRY_AGAIN_LATER:
if(xs->retries-- )
{
if(tsleep( 0,PRIBIO + 2,"retry",hz * 2))
{
xs->flags &= ~ITSDONE;
goto retry;
}
}
retval = EIO;
break;
default:
retval = EIO;
}
cd_free_xs(unit,xs,flags);
cdstart(unit); /* check if anything is waiting fr the xs */
}
else
{
printf("cd%d: not set up\n",unit);
return(EINVAL);
}
return(retval);
}
/***************************************************************\
* Look at the returned sense and act on the error and detirmine *
* The unix error number to pass back... (0 = report no error) *
\***************************************************************/
int cd_interpret_sense(unit,xs)
int unit;
struct scsi_xfer *xs;
{
struct scsi_sense_data *sense;
int key;
int silent;
/***************************************************************\
* If the flags say errs are ok, then always return ok. *
\***************************************************************/
if (xs->flags & SCSI_ERR_OK) return(ESUCCESS);
silent = (xs->flags & SCSI_SILENT);
sense = &(xs->sense);
switch(sense->error_class)
{
case 7:
{
key=sense->ext.extended.sense_key;
switch(key)
{
case 0x0:
return(ESUCCESS);
case 0x1:
if(!silent)
{
printf("cd%d: soft error(corrected) ", unit);
if(sense->valid)
{
printf("block no. %d (decimal)",
(sense->ext.extended.info[0] <<24)|
(sense->ext.extended.info[1] <<16)|
(sense->ext.extended.info[2] <<8)|
(sense->ext.extended.info[3] ));
}
printf("\n");
}
return(ESUCCESS);
case 0x2:
if(!silent)printf("cd%d: not ready\n ",
unit);
return(ENODEV);
case 0x3:
if(!silent)
{
printf("cd%d: medium error ", unit);
if(sense->valid)
{
printf("block no. %d (decimal)",
(sense->ext.extended.info[0] <<24)|
(sense->ext.extended.info[1] <<16)|
(sense->ext.extended.info[2] <<8)|
(sense->ext.extended.info[3] ));
}
printf("\n");
}
return(EIO);
case 0x4:
if(!silent)printf("cd%d: non-media hardware failure\n ",
unit);
return(EIO);
case 0x5:
if(!silent)printf("cd%d: illegal request\n ",
unit);
return(EINVAL);
case 0x6:
if(!silent)printf("cd%d: Unit attention.\n ", unit);
if (cd_data[unit].openparts)
cd_data[unit].flags &= ~(CDVALID | CDHAVELABEL);
{
return(EIO);
}
return(ESUCCESS);
case 0x7:
if(!silent)
{
printf("cd%d: attempted protection violation ",
unit);
if(sense->valid)
{
printf("block no. %d (decimal)\n",
(sense->ext.extended.info[0] <<24)|
(sense->ext.extended.info[1] <<16)|
(sense->ext.extended.info[2] <<8)|
(sense->ext.extended.info[3] ));
}
printf("\n");
}
return(EACCES);
case 0x8:
if(!silent)
{
printf("cd%d: block wrong state (worm)\n ",
unit);
if(sense->valid)
{
printf("block no. %d (decimal)\n",
(sense->ext.extended.info[0] <<24)|
(sense->ext.extended.info[1] <<16)|
(sense->ext.extended.info[2] <<8)|
(sense->ext.extended.info[3] ));
}
printf("\n");
}
return(EIO);
case 0x9:
if(!silent)printf("cd%d: vendor unique\n",
unit);
return(EIO);
case 0xa:
if(!silent)printf("cd%d: copy aborted\n ",
unit);
return(EIO);
case 0xb:
if(!silent)printf("cd%d: command aborted\n ",
unit);
return(EIO);
case 0xc:
if(!silent)
{
printf("cd%d: search returned\n ",
unit);
if(sense->valid)
{
printf("block no. %d (decimal)\n",
(sense->ext.extended.info[0] <<24)|
(sense->ext.extended.info[1] <<16)|
(sense->ext.extended.info[2] <<8)|
(sense->ext.extended.info[3] ));
}
printf("\n");
}
return(ESUCCESS);
case 0xd:
if(!silent)printf("cd%d: volume overflow\n ",
unit);
return(ENOSPC);
case 0xe:
if(!silent)
{
printf("cd%d: verify miscompare\n ",
unit);
if(sense->valid)
{
printf("block no. %d (decimal)\n",
(sense->ext.extended.info[0] <<24)|
(sense->ext.extended.info[1] <<16)|
(sense->ext.extended.info[2] <<8)|
(sense->ext.extended.info[3] ));
}
printf("\n");
}
return(EIO);
case 0xf:
if(!silent)printf("cd%d: unknown error key\n ",
unit);
return(EIO);
}
break;
}
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
{
if(!silent)printf("cd%d: error class %d code %d\n",
unit,
sense->error_class,
sense->error_code);
if(sense->valid)
if(!silent)printf("block no. %d (decimal)\n",
(sense->ext.unextended.blockhi <<16)
+ (sense->ext.unextended.blockmed <<8)
+ (sense->ext.unextended.blocklow ));
}
return(EIO);
}
}
int
cdsize(dev_t dev)
{
return (-1);
}
show_mem(address,num)
unsigned char *address;
int num;
{
int x,y;
printf("------------------------------");
for (y = 0; y<num; y += 1)
{
if(!(y % 16))
printf("\n%03d: ",y);
printf("%02x ",*address++);
}
printf("\n------------------------------\n");
}