ftp.nice.ch/pub/next/tools/cdrom/mCD.971026.s.tar.gz#/mCD/scsi_cd.subproj/cd_commands.c

This is cd_commands.c in view mode; [Download] [Up]

/*
 * cd_commands.c: CD-ROM drive specific commands
 *
 * This is based on the file that NeXT included in
 *      /NextDeveloper/Examples/UNIX/SCSI_CD,
 *      done by James C. Lee at NeXT, Sep 1991.
 * It has been changed "just a bit" by Garance Alistair Drosehn/March 1994.
 *
 */

#define CD_DEBUG2

#import <libc.h>
#import <objc/objc.h>
#import <c.h>
#import "cd_commands.h"
#import "cd_cmdsint.h"		/* structs internal to cd_commands */
#import "scsi_commands.h"

/**************************************************************************
 *   do_eject_1b
 *
 */
int
do_eject_1b(int fd, struct timeval * tvp, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct start_stop_1B_cmd *sscp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    sscp = (struct start_stop_1B_cmd *) & sr.sr_cdb;
    sscp->ssc_opcode = C6OP_STARTSTOP;
    sscp->ssc_imm = 0;		/* status will be returned after the
				 * operation is completed */
    sscp->ssc_loej = 1;		/* eject when spin down (scc_start=0) */
    sscp->ssc_start = 0;	/* spin down */

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;		/* don't really do I/O to memory */
    sr.sr_ioto = 10;		/* time out in 10 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    *tvp = sr.sr_exec_time;

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_spinup_1b
 *
 *	same command as do_eject, except that sscp->ssc_start=1 and
 *	sscp->ssc_loej=0
 */
int
do_spinup_1b(int fd, struct timeval * tvp, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct start_stop_1B_cmd *sscp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    sscp = (struct start_stop_1B_cmd *) & sr.sr_cdb;
    sscp->ssc_opcode = C6OP_STARTSTOP;
    sscp->ssc_imm = 0;		/* status will be returned after the
				 * operation is completed */
    sscp->ssc_loej = 0;
    sscp->ssc_start = 1;	/* spin up */

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;		/* don't really do I/O to memory */
    sr.sr_ioto = 10;		/* time out in 10 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    *tvp = sr.sr_exec_time;

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_stopunit_1b
 *
 *	same command as do_eject_1b, except that sscp->ssc_loej=0
 *	because the caddy should not be ejected.
 */
int
do_stopunit_1b(int fd, struct timeval * tvp, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct start_stop_1B_cmd *sscp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    sscp = (struct start_stop_1B_cmd *) & sr.sr_cdb;
    sscp->ssc_opcode = C6OP_STARTSTOP;
    sscp->ssc_imm = 0;		/* status will be returned after the
				 * operation is completed */
    sscp->ssc_loej = 0;		/* do not eject when spin down (scc_start=0) */
    sscp->ssc_start = 0;	/* spin down */

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;		/* don't really do I/O to memory */
    sr.sr_ioto = 10;		/* time out in 10 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    *tvp = sr.sr_exec_time;

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_pauseaudio_4b
 */
int
do_pauseaudio_4b(int fd, int pause, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct pause_4b_cmd *pp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    pp = (struct pause_4b_cmd *) & sr.sr_cdb;
    pp->p_op_code = C10OP_PAUSE_4B;
    pp->p_resume = !pause;	/* resume = not(pause) */

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_pauseaudio_c5
 *
 *	this command implements the same capability as do_pauseaudio_4b,
 *	but the x'c5' opcode is the the standardized one.  This works on
 *	the CD-ROM drive that NeXT sold, but not on other CD-ROM drives.
 */
int
do_pauseaudio_c5(int fd, int pause, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct pause_c5_cmd *pp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    pp = (struct pause_c5_cmd *) & sr.sr_cdb;
    pp->p_op_code = C10OP_PAUSE_C5;
    pp->p_pause = pause;

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_preventremoval_1e
 *
 *	prevent (or allow) medium removal.  ie, disable (enable) the
 *	button on the front of the CD-ROM drive.
 */
int
do_preventremoval_1e(int fd, int prevent, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct prevent_removal_1e_cmd *pp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    pp = (struct prevent_removal_1e_cmd *) & sr.sr_cdb;
    pp->par_op_code = C6OP_PREVENT_REMOVAL_1E;
    pp->par_prevent = prevent;	/* 0 = allow, 1 = prevent */

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_rezerounit_01
 */
int
do_rezerounit_01(int fd, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct rezero_unit_01_cmd *rz;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    rz = (struct rezero_unit_01_cmd *) & sr.sr_cdb;
    rz->rzu_opcode = C6OP_REZEROUNIT_01;

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_playaudio_c8
 */
int
do_playaudio_c8(int fd, int lba, int length, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct playaudio_c8_cmd *pap;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    pap = (struct playaudio_c8_cmd *) & sr.sr_cdb;
    pap->pa_op_code = C6OP_PLAYAUDIO_C8;
    SET_4BYTE_UINT(pap->pa_lba, lba);	/* block to play audio from */
    SET_2BYTE_UINT(pap->pa_length, length);	/* length to play */

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;
    sr.sr_ioto = 10;		/* time out in 10 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_playaudio_msf_47
 */
int
do_playaudio_msf_47(int fd, struct pa_msf startmsf, struct pa_msf endmsf,
		    struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct playaudio_msf_47_cmd *pap;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    pap = (struct playaudio_msf_47_cmd *) & sr.sr_cdb;
    pap->pam_op_code = C10OP_PLAYAUDIO_MSF_47;
    pap->pam_start_min = startmsf.min;
    pap->pam_start_sec = startmsf.sec;
    pap->pam_start_frame = startmsf.frame;
    pap->pam_end_min = endmsf.min;
    pap->pam_end_sec = endmsf.sec;
    pap->pam_end_frame = endmsf.frame;

    sr.sr_addr = NULL;
    sr.sr_dma_max = 0;
    sr.sr_ioto = 10;		/* time out in 10 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);

    if (sr.sr_dma_xfr != 0)
	fatal("scsi driver did transfer: %d", sr.sr_dma_xfr);

    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_readtoc_43
 *
 *	read in the entire table-of-contents of an audio CD, and return
 *	to the user in a platform-independent format.
 */
int
do_readtoc_43(int fd, struct cd_toc * toc_all, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct readtoc_43_cmd *rt10cp;
    struct readtoc_43_reply tocr;
    int                 err;
    char               *toc_reply;	/* toc reply from scsi bus, need to
					 * be allocated */
    u_int               toc_data_length,
			toc_temp_length,
                        lastsec,
                        currsec;
    struct rtr_desc    *desc;	/* pointer to traverse through *toc_reply */
    int                 nrecords,	/* number of desc blocks info in toc */
			nrectemp,
                        i,
                        track,
                        lasttrack;

 /*
  * First zero most everything in the cd_toc parameter, except the times.
  * Note the time variables are really unsigned integers.. 
  */
    bzero((char *)toc_all, sizeof(struct cd_toc));
    for (i = 0; i <= 100; i++) {
	toc_all->info[i].hour = -1;
	toc_all->info[i].min = -1;
	toc_all->info[i].sec = -1;
	toc_all->info[i].frame = -1;
    }

    bzero((char *)&sr, sizeof(sr));
    toc_reply = NULL;

    rt10cp = (struct readtoc_43_cmd *) & sr.sr_cdb;
    rt10cp->rt_op_code = C10OP_READTOC_43;
    rt10cp->rt_msf = 0;		/* don't use msf format */
    rt10cp->rt_starttrack = 0;
 /* read only info on track 0 to find out TOC data length */
    toc_data_length = sizeof(struct readtoc_43_reply);
    SET_2BYTE_UINT(rt10cp->rt_length, toc_data_length);

    sr.sr_addr = (char *)&tocr;
    sr.sr_dma_max = sizeof(tocr);
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;	/* read &sr.sr_addr from device */

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;
    if (err | sr.sr_io_status) {/* problem reading TOC, return */
	return err | sr.sr_io_status;
    }
 /* record first & last track, and toc data length */
    toc_all->firstCDtrack = tocr.h.rtr_firsttrack;
    toc_all->lastCDtrack = tocr.h.rtr_lasttrack;
    nrecords = tocr.h.rtr_lasttrack - tocr.h.rtr_firsttrack + 2;
    toc_data_length = nrecords * sizeof(struct rtr_desc);
    toc_temp_length = GET_2BYTE_UINT(tocr.h.rtr_datalen) - 2;
    nrectemp = toc_data_length / sizeof(struct rtr_desc);
    if (toc_temp_length > toc_data_length) {
#ifdef CD_DEBUG2
        printf("mCD: first = %d, last = %d, nrec=%d,   toc_dl = %d\n",
                tocr.h.rtr_firsttrack, tocr.h.rtr_lasttrack,
	        nrecords, toc_data_length);
        printf("mCD:          switch to --> nrec=%d, rtr_dl-2 = %d\n",
                nrectemp,  toc_temp_length);
#endif CD_DEBUG2
	toc_data_length = toc_temp_length;
	nrecords = nrectemp;
    } else if (toc_temp_length < toc_data_length) {
        printf("mCD: first = %d, last = %d, nrec=%d,   toc_dl = %d\n",
                tocr.h.rtr_firsttrack, tocr.h.rtr_lasttrack,
	        nrecords, toc_data_length);
        printf("mCD:                but nrectemp=%d, rtr_dl-2 = %d !!\n",
                nrectemp,  toc_temp_length);
    }

 /* prepare for second read toc for the whole thing */
    bzero((char *)&sr, sizeof(sr));

    rt10cp = (struct readtoc_43_cmd *) & sr.sr_cdb;
    rt10cp->rt_op_code = C10OP_READTOC_43;
    rt10cp->rt_msf = 1;		/* want msf format */
    rt10cp->rt_starttrack = 0;
    toc_data_length += sizeof(struct rtr_header);
    SET_2BYTE_UINT(rt10cp->rt_length, toc_data_length);

    toc_reply = (char *)malloc(toc_data_length);
    bzero((char *)toc_reply, toc_data_length);
    sr.sr_addr = toc_reply;
    sr.sr_dma_max = toc_data_length;
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;	/* read &sr.sr_addr from device */

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;
    if (err | sr.sr_io_status) {/* problem reading TOC, return */
	return err | sr.sr_io_status;
    }
 /* successfully read the whole thing, now process it. */
    desc = (struct rtr_desc *) & (toc_reply[4]);	/* skip the header */
    lastsec = 0;
    lasttrack = -1;
    toc_all->firstAtrack = 255;
    toc_all->lastAtrack = 0;
    for (i = 0; i < nrecords; i++) {
	track = desc->rtrd_track;
	if (track == 0xaa)
	    track = 100;	/* lead-out area */
	if (desc->rtrd_control & DATA_TRACK)
	    toc_all->ndata++;
	else if (track != 100) {
	    toc_all->naudio++;
	    if (toc_all->firstAtrack > track) toc_all->firstAtrack = track;
	    toc_all->lastAtrack = track;
	}
	toc_all->info[track].control = desc->rtrd_control;
	toc_all->info[track].lblock = (desc->rtrd_addr.msf.hour * 3600)
	  + (desc->rtrd_addr.msf.min * 60)
	  + desc->rtrd_addr.msf.sec;
	toc_all->info[track].lblock = (toc_all->info[track].lblock * 75)
	  + desc->rtrd_addr.msf.frame;
	toc_all->info[track].hour = desc->rtrd_addr.msf.hour;
	toc_all->info[track].min = desc->rtrd_addr.msf.min;
	toc_all->info[track].sec = desc->rtrd_addr.msf.sec;
	toc_all->info[track].frame = desc->rtrd_addr.msf.frame;
	currsec = (desc->rtrd_addr.msf.hour * 3600)
	  + (desc->rtrd_addr.msf.min * 60)
	  + desc->rtrd_addr.msf.sec;
	if (lasttrack > 0)
	    toc_all->info[lasttrack].elapsedSec = currsec - lastsec;
	lasttrack = track;
	lastsec = currsec;
	desc++;
    }
    toc_all->info[100].elapsedSec = (toc_all->info[100].hour * 3600)
      + (toc_all->info[100].min * 60)
      + toc_all->info[100].sec;

#ifdef CD_DEBUG
 /* print the table of contents to stderr */
    printf("\n           start time    time elapsed\n");
    for (i = toc_all->firsttrack; i <= toc_all->lasttrack; i++) {
	printf("Track %2d:  %d:%02d:%02d-%02d %c  %02d:%02d\n", i,
	       toc_all->info[i].hour, toc_all->info[i].min,
	       toc_all->info[i].sec,
	       toc_all->info[i].frame,
	       toc_all->info[i].control & DATA_TRACK ? 'D' : 'A',
	       toc_all->info[i].elapsedSec / 60,
	       toc_all->info[i].elapsedSec % 60);
    }
    printf("Track LO:  %d:%02d:%02d-%02d %c\n",
	   toc_all->info[100].hour, toc_all->info[100].min,
	   toc_all->info[100].sec,
	   toc_all->info[100].frame,
	   toc_all->info[100].control & DATA_TRACK ? 'D' : 'A');
#endif

    if (toc_reply != NULL)
	free(toc_reply);
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_readsubchannel_42
 *
 *	This is only here for reference purposes.  Almost all programs
 *      interested in the information from readsubchannel should be using
 *	routines like do_readcurrentposition_42 and do_readmediacatnum_42.
 */
int
do_readsubchannel_42(int fd, int msf, int subq, int page, int track,
		     struct sc_reply * scrp, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct read_subchannel_42_cmd *rscp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    rscp = (struct read_subchannel_42_cmd *) & sr.sr_cdb;
    rscp->rsc_op_code = C10OP_READSUBCHANNEL_42;
    rscp->rsc_msf = msf;
    rscp->rsc_subq = subq;
    rscp->rsc_dformat = page;
    rscp->rsc_track = track;
    SET_2BYTE_UINT(rscp->rsc_length, sizeof(struct sc_reply));

    sr.sr_addr = (char *)scrp;
    sr.sr_dma_max = sizeof(struct sc_reply);
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_readcurrentposition_42
 *
 *	This is really the "read sub-channel data" command with a few of
 *      the fields automatically filled in.
 */
int
do_readcurrentposition_42(int fd, struct rsc_cur_pos_reply * curpos,
			  struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct sc_reply     scrp;
    struct read_subchannel_42_cmd *rscp;
    u_int               tempUint;
    int                 err;

    bzero((char *)&sr, sizeof(sr));
    bzero((char *)&scrp, sizeof(scrp));

    rscp = (struct read_subchannel_42_cmd *) & sr.sr_cdb;
    rscp->rsc_op_code = C10OP_READSUBCHANNEL_42;
    rscp->rsc_msf = 1;		/* want MSF (not LBA) format for times */
    rscp->rsc_subq = 1;		/* want to get Q subchannel data */
    rscp->rsc_dformat = 1;	/* want page 1 of Q subchannel data */
    rscp->rsc_track = 0;	/* track is irrelevent for page 1 req */
    SET_2BYTE_UINT(rscp->rsc_length, sizeof(struct sc_reply));

    sr.sr_addr = (char *)&scrp;
    sr.sr_dma_max = sizeof(struct sc_reply);
    sr.sr_ioto = 5;		/* time out in 5 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;
    curpos->track = scrp.u.u_scr_cur_pos.sc1_track;
    curpos->index = scrp.u.u_scr_cur_pos.sc1_index;
    curpos->rsc_control = scrp.u.u_scr_cur_pos.sc1_control;
    curpos->rsc_audio_status = scrp.scr_header.sch_astatus;

    if (rscp->rsc_msf == 1) {	/* ie, msf formatted times */
	curpos->abs_logical_block = scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.sec
	  + (scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.min * 60)
	  + (scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.hour * 3600);
	curpos->abs_logical_block = (curpos->abs_logical_block * 75)
	  + scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.frame;
	curpos->abs_hour = scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.hour;
	curpos->abs_min = scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.min;
	curpos->abs_sec = scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.sec;
	curpos->abs_frame = scrp.u.u_scr_cur_pos.sc1_abs_addr.msf.frame;
	if (curpos->abs_min >= 60) {
	    curpos->abs_hour++;
	    curpos->abs_min -= 60;
	}
	curpos->rel_logical_block = scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.sec
	  + (scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.min * 60)
	  + (scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.hour * 3600);
	curpos->rel_logical_block = (curpos->rel_logical_block * 75)
	  + scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.frame;
	curpos->rel_hour = scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.hour;
	curpos->rel_min = scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.min;
	curpos->rel_sec = scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.sec;
	curpos->rel_frame = scrp.u.u_scr_cur_pos.sc1_rel_addr.msf.frame;
	if (curpos->rel_min >= 60) {
	    curpos->rel_hour++;
	    curpos->rel_min -= 60;
	}
    } else {			/* ie, lba formatted times */
	tempUint = GET_4BYTE_UINT(scrp.u.u_scr_cur_pos.sc1_abs_addr.lba.lblock);
	curpos->abs_logical_block = tempUint;
	curpos->abs_frame = tempUint % 75;
	curpos->abs_sec = tempUint / 75;
	curpos->abs_min = curpos->abs_sec / 60;
	curpos->abs_sec = curpos->abs_sec % 60;
	if (curpos->abs_min >= 60) {
	    curpos->abs_hour++;
	    curpos->abs_min -= 60;
	}
	tempUint = GET_4BYTE_UINT(scrp.u.u_scr_cur_pos.sc1_rel_addr.lba.lblock);
	curpos->rel_logical_block = tempUint;
	curpos->rel_frame = tempUint % 75;
	curpos->rel_sec = tempUint / 75;
	curpos->rel_min = curpos->rel_sec / 60;
	curpos->rel_sec = curpos->rel_sec % 60;
	if (curpos->rel_min >= 60) {
	    curpos->rel_hour++;
	    curpos->rel_min -= 60;
	}
    }

    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_readmediacatnum_42
 *
 *	This is really the "read sub-channel data" command with a few of
 *      the fields automatically filled in.
 */
int
do_readmediacatnum_42(int fd, struct rsc_media_catnum_reply * catnum,
		      struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct sc_reply     scrp;
    struct read_subchannel_42_cmd *rscp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));
    bzero((char *)&scrp, sizeof(scrp));

    rscp = (struct read_subchannel_42_cmd *) & sr.sr_cdb;
    rscp->rsc_op_code = C10OP_READSUBCHANNEL_42;
    rscp->rsc_msf = 0;		/* MSF format is irrelevent for this */
    rscp->rsc_subq = 1;		/* want to get Q subchannel data */
    rscp->rsc_dformat = 2;	/* want page 2 of Q subchannel data */
    rscp->rsc_track = 0;	/* track is irrelevent for page 2 req */
    SET_2BYTE_UINT(rscp->rsc_length, sizeof(struct sc_reply));

    sr.sr_addr = (char *)&scrp;
    sr.sr_dma_max = sizeof(struct sc_reply);
    sr.sr_ioto = 10;		/* time out in 10 seconds */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;
    catnum->catnum_isSet = scrp.u.u_scr_med_cat.sc2_mcval;
    memcpy(catnum->media_catnum, scrp.u.u_scr_med_cat.sc2_med_cat, 15);

 /*
  * note that many CDs have mcval set on, but the media-cat# is zero... Also
  * note that different drives return the media-cat# in different formats...  
  */
    bzero((char *)&scrp.u.u_scr_med_cat.sc2_med_cat, 15);	/* = no catnum */
    if (!memcmp(catnum->media_catnum, scrp.u.u_scr_med_cat.sc2_med_cat, 15))
	catnum->catnum_isSet = 0;
    if (!memcmp(catnum->media_catnum, "000000000000000", 15))
	catnum->catnum_isSet = 0;

    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_playbackstatus_c4
 *
 *	Find out the position and status (playing, paused, whatever) of
 *      the CD drive.  Also picks up the current volume settings.  Note
 *      that this is probably Sony-specific.
 */
int
do_playbackstatus_c4(int fd, struct pb_status_reply * pbstatus,
		     struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct playback_statuscontrol_cmd *pbcp;
    struct playback_c4c9_data pbdata;
    u_int               tempUint;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    pbcp = (struct playback_statuscontrol_cmd *) & sr.sr_cdb;
    pbcp->pb_opcode = C10OP_PLAYBACKSTATUS_C4;
    SET_2BYTE_UINT(pbcp->pb_length, sizeof(struct playback_c4c9_data));

    bzero((char *)&pbdata, sizeof(pbdata));
    sr.sr_addr = (char *)&pbdata;
    sr.sr_dma_max = sizeof(struct playback_c4c9_data);
    sr.sr_ioto = 1;		/* time out in 1 second */
    sr.sr_dma_dir = SR_DMA_RD;

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;

 /*
  * copy information from platform-specific (bigendian) layout to one that
  * matches the current platform 
  */
    bzero((char *)pbstatus, sizeof(struct pb_status_reply));
    pbstatus->pbs_audio_status = pbdata.pbd_audio_status;
    pbstatus->pbs_ch0_sel = pbdata.pbd_ch0_sel;
    pbstatus->pbs_ch0_vol = pbdata.pbd_ch0_vol;
    pbstatus->pbs_ch1_sel = pbdata.pbd_ch1_sel;
    pbstatus->pbs_ch1_vol = pbdata.pbd_ch1_vol;
    pbstatus->pbs_ch2_sel = pbdata.pbd_ch2_sel;
    pbstatus->pbs_ch2_vol = pbdata.pbd_ch2_vol;
    pbstatus->pbs_ch3_sel = pbdata.pbd_ch3_sel;
    pbstatus->pbs_ch3_vol = pbdata.pbd_ch3_vol;

 /* the cd-address is returned in both addressing formats */
    if (pbdata.pbd_lbamsf == 1) {	/* ie, msf formatted times */
	if (pbdata.pbd_cd_addr.msf.min == 255) {
	/* if min = 255, then we're really before the */
	/* start of the first track... */
	    pbdata.pbd_cd_addr.msf.min = 0;
	    pbdata.pbd_cd_addr.msf.sec = 0;
	    pbdata.pbd_cd_addr.msf.frame = 75 - pbdata.pbd_cd_addr.msf.frame;
	}
	pbstatus->pbs_logical_block = pbdata.pbd_cd_addr.msf.sec
	  + (pbdata.pbd_cd_addr.msf.min * 60)
	  + (pbdata.pbd_cd_addr.msf.hour * 3600);
	pbstatus->pbs_logical_block = (pbstatus->pbs_logical_block * 75)
	  + pbdata.pbd_cd_addr.msf.frame;
	pbstatus->pbs_hour = pbdata.pbd_cd_addr.msf.hour;
	pbstatus->pbs_min = pbdata.pbd_cd_addr.msf.min;
	pbstatus->pbs_sec = pbdata.pbd_cd_addr.msf.sec;
	pbstatus->pbs_frame = pbdata.pbd_cd_addr.msf.frame;
	if (pbstatus->pbs_min >= 60) {
	    pbstatus->pbs_hour++;
	    pbstatus->pbs_min -= 60;
	}
    } else {			/* ie, lba formatted times */
	tempUint = GET_4BYTE_UINT(pbdata.pbd_cd_addr.lba.lblock);
	pbstatus->pbs_logical_block = tempUint;
	pbstatus->pbs_frame = tempUint % 75;
	pbstatus->pbs_sec = tempUint / 75;
	pbstatus->pbs_min = pbstatus->pbs_sec / 60;
	pbstatus->pbs_sec = pbstatus->pbs_sec % 60;
	if (pbstatus->pbs_min == 255) {
	/* if min = 255, then we're really before the */
	/* start of the first track... */
	    pbstatus->pbs_min = 0;
	    pbstatus->pbs_sec = 0;
	    pbstatus->pbs_frame = 75 - pbstatus->pbs_frame;
	    pbstatus->pbs_logical_block = pbstatus->pbs_frame;
	}
	if (pbstatus->pbs_min >= 60) {
	    pbstatus->pbs_hour++;
	    pbstatus->pbs_min -= 60;
	}
    }

    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_playbackvolume_c9
 *
 *	Change the current volume settings, using the playback control
 *      command (which is probably Sony-specific).
 */
int
do_playbackvolume_c9(int fd, int leftVolume, int rightVolume,
		     struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct playback_statuscontrol_cmd *pbcp;
    struct playback_c4c9_data pbdata;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    pbcp = (struct playback_statuscontrol_cmd *) & sr.sr_cdb;
    pbcp->pb_opcode = C10OP_PLAYBACKCONTROL_C9;
    SET_2BYTE_UINT(pbcp->pb_length, sizeof(struct playback_c4c9_data));

    bzero((char *)&pbdata, sizeof(pbdata));
    pbdata.pbd_ch0_sel = 1;
    pbdata.pbd_ch0_vol = leftVolume;
    pbdata.pbd_ch1_sel = 2;
    pbdata.pbd_ch1_vol = rightVolume;

    sr.sr_addr = (char *)&pbdata;
    sr.sr_dma_max = sizeof(struct playback_c4c9_data);
    sr.sr_ioto = 1;		/* time out in 1 second */
    sr.sr_dma_dir = SR_DMA_WR;

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;
    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_modesense_pc_E
 *
 *	This is just the do_modesense command from scsi_commands, except
 *	that it's setup to return page-code E in particular.  Page-code E
 *	is the one for CD-ROM Audio Control parameters.  Declarations of
 *	some structs are also redone (and in cd_cmdsint.h) to make them
 *	more platform-independent.
 */
int
do_modesense_pc_E(int fd, struct cd_volset_reply * volset, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct mode_sense_select_cmd *mscp;
    struct mode_sense_select_reply msrp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));
    bzero((char *)&msrp, sizeof(msrp));

    mscp = (struct mode_sense_select_cmd *) & sr.sr_cdb;
    mscp->msc_opcode = C6OP_MODESENSE;
    mscp->msc_lun = 0;
    mscp->msc_pcf = 0;		/* report current values */
    mscp->msc_page = 0x0E;
    mscp->msc_len = sizeof(msrp);

    sr.sr_addr = (char *)&msrp;
    sr.sr_dma_max = sizeof(msrp);
    sr.sr_ioto = 50;		/* using an extended timeout */
    sr.sr_dma_dir = SR_DMA_RD;	/* read &sr.sr_addr from device */

    err = ioctl(fd, SDIOCSRQ, &sr);
    *erp = sr.sr_esense;

 /*
  * copy information from platform-specific (bigendian) layout to one that
  * matches the current platform 
  */
    bzero((char *)volset, sizeof(struct cd_volset_reply));
    volset->ch0_sel = msrp.u.u_msr_pcE.pce_ch0_sel;
    volset->ch0_vol = msrp.u.u_msr_pcE.pce_ch0_vol;
    volset->ch1_sel = msrp.u.u_msr_pcE.pce_ch1_sel;
    volset->ch1_vol = msrp.u.u_msr_pcE.pce_ch1_vol;
    volset->ch2_sel = msrp.u.u_msr_pcE.pce_ch2_sel;
    volset->ch2_vol = msrp.u.u_msr_pcE.pce_ch2_vol;
    volset->ch3_sel = msrp.u.u_msr_pcE.pce_ch3_sel;
    volset->ch3_vol = msrp.u.u_msr_pcE.pce_ch3_vol;

    return err | sr.sr_io_status;
}


/**************************************************************************
 *   do_modeselect_pc_E
 *
 *	This is basically just the do_modesense_pc_E command reversed,
 *	such that CD volume settings are being set instead of sensed.
 */
int
do_modeselect_pc_E(int fd, int leftVolume, int rightVolume, struct esense_reply * erp)
{
    struct scsi_req     sr;
    struct mode_sense_select_cmd *mscp;
    struct mode_select_alt_reply msarp;
    int                 err;

    bzero((char *)&sr, sizeof(sr));

    mscp = (struct mode_sense_select_cmd *) & sr.sr_cdb;
    mscp->msc_opcode = C6OP_MODESELECT;
    mscp->msc_lun = 0;
    mscp->msc_len = sizeof(msarp);
    mscp->msc_pf = 1;

    sr.sr_addr = (char *)&msarp;
    sr.sr_dma_max = sizeof(msarp);
    sr.sr_ioto = 50;		/* using an extended timeout */
    sr.sr_dma_dir = SR_DMA_WR;	/* write &sr.sr_addr to device */

    bzero((char *)&msarp, sizeof(msarp));
    msarp.msar_plh.plh_blkdesclen = 0;	/* using alternate form,  with no
					 * block descriptor */
    msarp.u.u_msar_pcE.pce_pagecode = 0x0E;
    msarp.u.u_msar_pcE.pce_parlen = 0x0E;
    msarp.u.u_msar_pcE.pce_immd = YES;
    msarp.u.u_msar_pcE.pce_ch0_sel = 1;
    msarp.u.u_msar_pcE.pce_ch0_vol = leftVolume;
    msarp.u.u_msar_pcE.pce_ch1_sel = 2;
    msarp.u.u_msar_pcE.pce_ch1_vol = rightVolume;

    err = ioctl(fd, SDIOCSRQ, &sr);

    *erp = sr.sr_esense;

    return err | sr.sr_io_status;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.