|
|
Sample Programs from NeXSTEP 3.3
/* Copyright (c) 1993 NeXT Computer, Inc. All rights reserved.
*
* SCSITapeKern.m -- implementation of scsi tape driver entry point routines
*
* HISTORY
* 31-Mar-93 Phillip Dibner at NeXT
* Created. Adapted from st.c, created by Doug Mitchell at NeXT.
*
*/
/*
* Four different devices are implemented here:
*
* rst - generic SCSI tape, rewind on close
* nrst - generic SCSI tape, no rewind on close
* rxt - Exabyte SCSI tape, rewind on close
* nrxt - Exabyte SCSI tape, no rewind on close
*
* All 4 devices have the same major number. Bit 0 of the minor number
* selects "rewind on close" (0) or "no rewind" (1). Bit 1 of the
* minor number select generic (0) or Exabyte (1).
*
* The Exabyte drive currently requires these actions on open:
*
* -- enable Buffered Write mode
* -- Inhibit Illegal Length errors
* -- Disable Disconnect During Data Transfer
*/
#import <sys/errno.h>
#import <sys/types.h>
#import <sys/buf.h>
#import <sys/conf.h>
#import <sys/uio.h>
#import <sys/mtio.h>
#import <bsd/dev/scsireg.h>
#import <driverkit/scsiTypes.h>
#import <driverkit/align.h>
#import <driverkit/kernelDriver.h>
#import <driverkit/scsiTypes.h>
#import <driverkit/return.h>
#import <driverkit/devsw.h>
#import <kernserv/prototypes.h>
#import "SCSITape.h"
#define USE_EBD 1 /* use "even byte diconnect" rather than
* "no disconnect during data xfer" for exabyte
*/
/*
* Unix-style entry points
*/
int stopen (dev_t dev);
int stclose (dev_t dev);
int stread (dev_t dev, struct uio *uiop);
int stwrite (dev_t dev, struct uio *uiop);
int stioctl (dev_t dev, int cmd, caddr_t data, int flag);
/*
* Subsidiary functions used by the kernel "glue" layer
*/
static int st_rw (dev_t dev, struct uio *uiop, int rw_flag);
static int st_doiocsrq (id scsiTape, scsi_req_t *srp);
/*
* Functions to take care of byte-ordering issues
*/
extern void assign_cdb_c6s_len();
extern void assign_msbd_numblocks();
extern void assign_msbd_blocklength();
unsigned int read_er_info_low_24();
extern id stIdMap[];
/*
* Add ourself to cdevsw. Called from SCSIGeneric layer at probe time.
*/
extern int nulldev();
extern int nodev();
static int stMajor = -1;
int
st_devsw_init()
{
int rtn;
/*
* We get called once for each IOSCSIController in the system; we
* only have to call IOAddToCdevsw() once.
*/
if(stMajor >= 0) {
return stMajor;
}
rtn = IOAddToCdevsw ((IOSwitchFunc) stopen,
(IOSwitchFunc) stclose,
(IOSwitchFunc) stread,
(IOSwitchFunc) stwrite,
(IOSwitchFunc) stioctl,
(IOSwitchFunc) nodev,
(IOSwitchFunc) nulldev, // reset
(IOSwitchFunc) nulldev,
(IOSwitchFunc) nodev, // mmap
(IOSwitchFunc) nodev, // getc
(IOSwitchFunc) nodev); // putc
if(rtn < 0) {
IOLog("st: Can't find space in devsw\n");
}
else {
IOLog("st: major number %d\n", rtn);
stMajor = rtn;
}
return rtn;
}
int
stopen(dev_t dev)
{
int unit = ST_UNIT(dev);
id scsiTape = stIdMap[unit];
if([scsiTape acquireDevice] == IO_R_BUSY)
return(EBUSY); /* already open */
if ((unit >= NST) || /* illegal device */
([scsiTape isInitialized] == NO)) { /* hasn't been init'd */
[scsiTape releaseDevice];
return(ENXIO); /* FIXME - try to init here */
}
/*
* We send this once, and ignore result, to clear check condition
* due to media change, etc.
*/
[scsiTape setIgnoreCheckCondition: YES];
[scsiTape stTestReady];
[scsiTape setIgnoreCheckCondition: NO];
if(ST_EXABYTE(dev)) {
struct modesel_parms *mspp;
struct exabyte_vudata *evudp;
struct mode_sel_hdr *mshp;
mspp = IOMalloc (sizeof (struct modesel_parms));
evudp = (struct exabyte_vudata *) &mspp->msp_data.msd_vudata;
/*
* Exabyte "custom" setup
*/
/* Set variable block size */
if([scsiTape setBlockSize: 0] != IO_R_SUCCESS) {
IOFree (mspp, sizeof (struct modesel_parms));
[scsiTape releaseDevice];
#ifdef DEBUG
IOLog ("stopen: cannot set block size variable\n");
#endif DEBUG
return(EIO);
}
/* Suppress illegal length errors */
[scsiTape setSuppressIllegalLength: YES];
/* Do a mode sense */
mspp->msp_bcount = sizeof(struct mode_sel_hdr) +
sizeof(struct mode_sel_bd) + MSP_VU_EXABYTE;
if([scsiTape stModeSense: mspp] != SR_IOST_GOOD) {
IOFree (mspp, sizeof (struct modesel_parms));
[scsiTape releaseDevice];
#ifdef DEBUG
IOLog ("stopen: Mode Sense failed\n");
#endif DEBUG
return(EIO);
}
/* some fields we have to zero as a matter of course */
mshp = &mspp->msp_data.msd_header;
mshp->msh_sd_length_0 = 0;
mshp->msh_med_type = 0;
mshp->msh_wp = 0;
mshp->msh_bd_length = sizeof(struct mode_sel_bd);
assign_msbd_blocklength (&mspp->msp_data.msd_blockdescript, 0);
assign_msbd_numblocks (&mspp->msp_data.msd_blockdescript, 0);
/*
* set up buffered mode, #blocks = 0, even byte disconnect,
* enable parity; do mode selsect
*/
mspp->msp_data.msd_header.msh_bufmode = 1;
#ifdef USE_EBD
/* clear NDD and set EBD; enable parity */
evudp->nd = 0; /* disconnects OK */
evudp->ebd = 1; /* but only on word boundaries */
evudp->pe = 1; /* parity enabled */
evudp->nbe = 1; /* Busy status disabled */
#else USE_EBD
evudp->nd = 1;
#endif USE_EBD
if([scsiTape stModeSelect: mspp] != SR_IOST_GOOD) {
IOFree (mspp, sizeof (struct modesel_parms));
[scsiTape releaseDevice];
#ifdef DEBUG
IOLog ("stopen: Mode Select failed\n");
#endif DEBUG
return(EIO);
}
IOFree (mspp, sizeof (struct modesel_parms));
}
return(0);
}
int
stclose(dev_t dev)
{
int unit = ST_UNIT(dev);
id scsiTape = stIdMap[unit];
int rtn = 0;
if ([scsiTape didWrite] == YES) {
/* we must write a file mark to close the file */
if ([scsiTape stCloseFile] != SR_IOST_GOOD) {
rtn = EIO;
}
}
if(ST_RETURN(dev) == 0) { /* returning device? */
if ([scsiTape stRewind] != SR_IOST_GOOD) {
rtn = EIO;
}
}
[scsiTape releaseDevice];
return(rtn);
}
int
stread(dev_t dev, struct uio *uiop)
{
return(st_rw(dev,uiop,SR_DMA_RD));
}
int
stwrite(dev_t dev, struct uio *uiop)
{
return(st_rw(dev,uiop,SR_DMA_WR));
}
static int
st_rw(dev_t dev, struct uio *uiop, int rw_flag) {
int unit = ST_UNIT(dev);
id scsiTape = stIdMap[unit];
IOSCSIRequest scsiReq;
struct cdb_6s *cdbp = &scsiReq.cdb.cdb_c6s;
void *freePtr;
int freeCnt;
unsigned char *alignedBuf;
IODMAAlignment dmaAlign;
int length;
int rtn = 0;
sc_status_t scRet = -1;
if (unit >= NST)
return(ENXIO);
if(uiop->uio_iovcnt != 1) /* single requests only */
return(EINVAL);
if(uiop->uio_iov->iov_len == 0)
return(0); /* nothing to do */
#ifdef DEBUG
// if(rw_flag == SR_DMA_RD) {
// XCDBG(("st: READ; count = %xH\n", uiop->uio_iov->iov_len));
// }
// else {
// XCDBG(("st: WRITE; count = %xH\n", uiop->uio_iov->iov_len));
// }
#endif DEBUG
/*
* FIXME: should wire user's memory and DMA from there, avoiding
* a copyin() or copyout().
*/
alignedBuf = [[scsiTape controller]
allocateBufferOfLength: uiop->uio_iov->iov_len
actualStart: &freePtr
actualLength: &freeCnt];
bzero(&scsiReq, sizeof(IOSCSIRequest));
scsiReq.target = [scsiTape target];
scsiReq.lun = [scsiTape lun];
[[scsiTape controller] getDMAAlignment:&dmaAlign];
if(dmaAlign.readLength > 1) {
scsiReq.maxTransfer = IOAlign(int, uiop->uio_iov->iov_len,
dmaAlign.readLength);
} else {
scsiReq.maxTransfer = uiop->uio_iov->iov_len;
}
scsiReq.timeoutLength = ST_IOTO_NORM;
scsiReq.disconnect = 1;
cdbp->c6s_lun = [scsiTape lun];
if ([scsiTape isFixedBlock]) {
/* c6s_len is BLOCK COUNT */
length = howmany(uiop->uio_iov->iov_len, [scsiTape blockSize]);
cdbp->c6s_opt = C6OPT_FIXED;
#ifdef DEBUG
IOLog ("SCSI Tape read/write: set up for fixed block transfer\n");
#endif DEBUG
} else {
length = uiop->uio_iov->iov_len;
if(rw_flag == SR_DMA_RD)
if ([scsiTape suppressIllegalLength]) {
cdbp->c6s_opt |= C6OPT_SIL;
#ifdef DEBUG
IOLog ("SCSI Tape read: variable block read, suppress illegal len errs\n");
#endif DEBUG
}
else {
#ifdef DEBUG
IOLog ("SCSI Tape read: variable block read, allow illegal len errs\n");
#endif DEBUG
}
}
assign_cdb_c6s_len (cdbp, length);
#ifdef DEBUG
IOLog ("Transfer Length is %d\n", length);
#endif DEBUG
if(length > C6S_MAXLEN) {
rtn = EINVAL;
goto out;
}
if(rw_flag == SR_DMA_RD) {
cdbp->c6s_opcode = C6OP_READ;
scsiReq.read = YES;
}
else {
cdbp->c6s_opcode = C6OP_WRITE;
scsiReq.read = NO;
}
scsiReq.bytesTransferred = 0;
/* Copy user data to kernel space if write. */
if(rw_flag == SR_DMA_WR)
if((rtn = copyin(uiop->uio_iov->iov_base, alignedBuf,
uiop->uio_iov->iov_len)))
goto out;
if ((scRet = [scsiTape executeRequest: &scsiReq
buffer: alignedBuf
client: IOVmTaskSelf()
senseBuf: [scsiTape senseDataPtr]]) != SR_IOST_GOOD) {
rtn = EIO;
#ifdef DEBUG
IOLog ("st_rw: returned on failure from executeRequest\n");
IOLog ("st_rw: ---- returned %d\n", scRet);
#endif DEBUG
goto out;
}
/* It worked. Copy data to user space if read. */
if(scsiReq.bytesTransferred && (rw_flag == SR_DMA_RD)) {
rtn = copyout(alignedBuf, uiop->uio_iov->iov_base,
scsiReq.bytesTransferred);
#ifdef DEBUG
IOLog ("return value from copyout is %d\n", rtn);
#endif DEBUG
}
if(scsiReq.driverStatus != SR_IOST_GOOD) { // XXX Can this happen?
rtn = EIO;
}
out:
#ifdef DEBUG
IOLog ("SCSI st_rw transferred %d bytes out of %d\n",
scsiReq.bytesTransferred, uiop->uio_iov->iov_len);
#endif DEBUG
uiop->uio_resid = uiop->uio_iov->iov_len - scsiReq.bytesTransferred;
IOFree (freePtr, freeCnt);
IOSetUNIXError (rtn);
return rtn;
} /* st_rw() */
/*
* ioctl for SCSI Tape.
* XXX sc_return_t to errno conversions could use more review.
*/
int
stioctl(dev_t dev,
int cmd, /* MTIOCTOP, etc */
caddr_t data, /* actually a ptr to mt_op or mtget, if used */
int flag) /* for historical reasons. Not used. */
{
int error = 0;
int unit = ST_UNIT(dev);
id scsiTape = stIdMap[unit];
struct mtget *mgp = (struct mtget *)data;
struct esense_reply *erp;
sc_status_t scsi_err;
if (unit >= NST)
return(ENXIO);
switch (cmd) {
case MTIOCTOP: /* do tape op */
if ((scsi_err =
[scsiTape executeMTOperation: (struct mtop *) data]) !=
SR_IOST_GOOD) {
if (scsi_err == SR_IOST_CMDREJ) {
error = EINVAL;
} else {
error = EIO;
}
}
break;
case MTIOCGET: /* get status */
erp = [scsiTape senseDataPtr];
/*
* If we just did a request sense command as part of
* error recovery, avoid doing another one and
* thus blowing away possible volatile status info.
*/
if([scsiTape senseDataValid] == NO) {
if((scsi_err = [scsiTape requestSense: erp]) != SR_IOST_GOOD) {
error = EIO;
break;
}
}
/*
* [scsiTape senseDataPtr] now definitely contains valid
* sense data.
*/
if(ST_EXABYTE(dev))
mgp->mt_type = MT_ISEXB;
else
mgp->mt_type = MT_ISGS;
mgp->mt_dsreg = ((u_char *)erp)[2];
mgp->mt_erreg = erp->er_addsensecode;
mgp->mt_ext_err0 = (((u_short)erp->er_stat_13) << 8) |
((u_short)erp->er_stat_14);
mgp->mt_ext_err1 = (((u_short)erp->er_stat_15) << 8) |
((u_short)erp->er_rsvd_16);
#if __BIG_ENDIAN__
mgp->mt_resid = (u_int) erp->er_info;
#elif __LITTLE_ENDIAN__
mgp->mt_resid = read_er_info_low_24();
mgp->mt_resid |= (u_int) erp->er_info3;
#endif
/* force actual request sense next time */
[scsiTape forceSenseDataInvalid];
break;
case MTIOCFIXBLK: /* set fixed block mode */
error = [scsiTape
errnoFromReturn: [scsiTape setBlockSize: *(int *)data]];
break;
case MTIOCVARBLK: /* set variable block mode */
error = [scsiTape
errnoFromReturn: [scsiTape setBlockSize: 0]];
break;
case MTIOCINILL: /* inhibit illegal length
* errors on Read */
[scsiTape setSuppressIllegalLength: YES];
break;
case MTIOCALILL: /* allow illegal length
* errors on Read */
[scsiTape setSuppressIllegalLength: NO];
break;
case MTIOCMODSEL: /* mode select */
error = 0;
if ([scsiTape stModeSelect: (struct modesel_parms *)data] !=
SR_IOST_GOOD) {
error = EIO;
break;
}
case MTIOCMODSEN: /* mode sense */
error = 0;
if ([scsiTape stModeSense: (struct modesel_parms *)data] !=
SR_IOST_GOOD) {
error = EIO;
break;
}
case MTIOCSRQ: /* I/O via scsi_req */
error = st_doiocsrq(scsiTape, (struct scsi_req *) data);
break;
default:
error = EINVAL; /* invalid argument */
break;
}
IOSetUNIXError (error); /* XXX Probably not necessary */
return error;
} /* stioctl() */
/*
* Lifted directly from sg driver.
*
* Execute one scsi_req. Called from client's task context. Returns an errno.
*/
/*
* FIXME - DMA to non-page-aligned user memory doesn't work. There
* is data corruption on read operations; the corruption occurs on page
* boundaries.
*/
#define FORCE_PAGE_ALIGN 1
#if FORCE_PAGE_ALIGN
int stForcePageAlign = 1;
#endif FORCE_PAGE_ALIGN
static int st_doiocsrq(id scsiTape, scsi_req_t *srp)
{
void *alignedPtr = NULL;
unsigned alignedLen = 0;
void *freePtr;
unsigned freeLen;
BOOL didAlign = NO;
vm_task_t client = NULL;
int rtn = 0;
IOSCSIRequest scsiReq;
sc_status_t srtn;
if(srp->sr_dma_max > [[scsiTape controller] maxTransfer]) {
return EINVAL;
}
/* Get some well-aligned memory if necessary. By using
* allocateBufferOfLength we guarantee that there is enough space
* in the buffer we pass to the controller to handle
* end-of-buffer alignment, although we won't copy more
* than sr_dma_max to or from the caller.
*/
if(srp->sr_dma_max != 0) {
IODMAAlignment dmaAlign;
id controller = [scsiTape controller];
unsigned alignLength;
unsigned alignStart;
/*
* Get appropriate alignment from controller.
*/
[[scsiTape controller] getDMAAlignment:&dmaAlign];
if(srp->sr_dma_dir == SR_DMA_WR) {
alignLength = dmaAlign.writeLength;
alignStart = dmaAlign.writeStart;
}
else {
alignLength = dmaAlign.readLength;
alignStart = dmaAlign.readStart;
}
#if FORCE_PAGE_ALIGN
if(stForcePageAlign) {
alignStart = PAGE_SIZE;
}
#endif FORCE_PAGE_ALIGN
if( ( (alignStart > 1) &&
!IOIsAligned(srp->sr_addr, alignStart)
) ||
( (alignLength > 1) &&
!IOIsAligned(srp->sr_dma_max, alignLength)
) ||
/*
` * XXX Prevent DMA from user space for now, even if the
* buffer is well-aligned. We need to wire down the user
* memory if we are going to DMA from it.
*/
YES
) {
/*
* DMA from kernel memory, we allocate and copy.
*/
didAlign = YES;
client = IOVmTaskSelf();
if(alignLength > 1) {
alignedLen = IOAlign(unsigned,
srp->sr_dma_max,
alignLength);
}
else {
alignedLen = srp->sr_dma_max;
}
alignedPtr = [controller allocateBufferOfLength:
srp->sr_dma_max
actualStart:&freePtr
actualLength:&freeLen];
if(srp->sr_dma_dir == SR_DMA_WR) {
rtn = copyin(srp->sr_addr, alignedPtr,
srp->sr_dma_max);
if(rtn) {
rtn = EFAULT;
goto err_exit;
}
}
}
else {
/*
* Well-aligned buffer, DMA directly to/from user
* space.
*/
alignedLen = srp->sr_dma_max;
alignedPtr = srp->sr_addr;
client = IOVmTaskCurrent();
didAlign = NO;
}
}
/*
* Generate a contemporary version of scsi_req.
*/
bzero(&scsiReq, sizeof(scsiReq));
scsiReq.target = [scsiTape target];
scsiReq.lun = [scsiTape lun];
/*
* Careful. this assumes that the old and new cdb structs are
* equivalent...
*/
scsiReq.cdb = srp->sr_cdb;
scsiReq.read = (srp->sr_dma_dir == SR_DMA_RD) ? YES : NO;
scsiReq.maxTransfer = alignedLen;
scsiReq.timeoutLength = srp->sr_ioto;
scsiReq.disconnect = 1;
/*
* Go for it.
*
* XXX Should use the SCSITape object's sense buffer, because
* that's where MTIOCGET looks for valid sense data, and then
* copy back the sense data to the old-style scsi_req's sense
* buffer.
*/
srtn = [scsiTape executeRequest:&scsiReq
buffer : alignedPtr
client : client
senseBuf : &srp->sr_esense];
/*
* Copy status back to user. Note that if we got this far, we
* return good status from the function; errors are in
* srp->sr_io_status.
*/
srp->sr_io_status = srtn;
srp->sr_scsi_status = scsiReq.scsiStatus;
srp->sr_dma_xfr = scsiReq.bytesTransferred;
if(srp->sr_dma_xfr > srp->sr_dma_max) {
srp->sr_dma_xfr = srp->sr_dma_max;
}
ns_time_to_timeval(scsiReq.totalTime, &srp->sr_exec_time);
/*
* Copy read data back to user if appropriate.
*/
if((srp->sr_dma_dir == SR_DMA_RD) &&
(scsiReq.bytesTransferred != 0) && didAlign) {
rtn = copyout(alignedPtr,
srp->sr_addr,
srp->sr_dma_xfr);
}
err_exit:
if(didAlign) {
IOFree(freePtr, freeLen);
}
return rtn;
}
/*
* Supporting function for managing byte-order swapping.
*/
unsigned int
read_er_info_low_24(struct esense_reply *erp)
{
#if __BIG_ENDIAN__
return ((unsigned int) erp->er_info);
#elif __LITTLE_ENDIAN__
return (unsigned int)
(erp->er_info2 << 16) + (erp->er_info1 << 8) + erp->er_info0;
#endif
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.