/***
 * CopyPolicy: GNU Public License 2 applies
 * Copyright (C) by Heiko Eissfeldt heiko@colossus.escape.de
 *
 * Interface module for cdrom drive access
 *
 * three interfaces are possible.
 *
 * 1. using low level commands via ioctl() at the scsi cdrom device
 *    disadvantages: SCSI only, toshiba & NEC type drives only.
 *
 * 2. using the generic scsi device.
 *
 * 3. if kernel support exists, use 'cooked' ioctls()
 *    disadvantages: available for sbpcd and cdu31a drives only.
 */

/* functions so far:
 *
 * EnableCdda	
 * ReadToc
 * ReadCdRom
 * ReadSubQ
 * SetupInterface
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/cdrom.h>

#include <linux/../../drivers/scsi/sg.h>

#include "cdda2wav.h"
#include "uti.h"
#include "interface.h"

unsigned interface;
unsigned OFF;
unsigned nsectors = NSECTORS;

int trackindex_disp = TRUE;

/* buffer for cdrom audio data sector */
static unsigned char *bufferCddaRaw;
unsigned char *bufferCdda;
static unsigned char *bufferTOC;
static unsigned char *SubQbuffer;
static unsigned char *cmd;

/*******************************************************************
 *
 *	SCSI section
 *
 */

/* process a complete scsi cmd. Use either generic or ioctl interface. */
static int handle_scsi_cmd(unsigned cmd_len, 
				 unsigned in_size, 
				 unsigned char *i_buff, 
				 unsigned out_size, 
				 unsigned char *o_buff )
{
    int status = 0;
    unsigned char *buff_p;
    struct sg_header *sg_hd;

    /* safety checks */
    if (!cmd_len) return -1;
    if (!i_buff) return -1;
#ifdef SG_BIG_BUFF
    if (OFF + cmd_len + in_size > SG_BIG_BUFF) return -1;
    if (OFF + out_size > SG_BIG_BUFF) return -1;
#else
    if (OFF + cmd_len + in_size > 4096) return -1;
    if (OFF + out_size > 4096) return -1;
#endif

    if (!o_buff) out_size = 0;

    if (out_size > in_size + cmd_len) {
	buff_p = o_buff;
	memcpy(o_buff + OFF, i_buff + OFF, in_size + cmd_len);
    } else {
	buff_p = i_buff;
    }

    if (interface == SCSI_IOCTL) {
	/* input buffer i_buff has size in_size + OFF. 
	 * output buffer o_buff has size out_size + OFF. 
	 */
	*(  (int *) buff_p     ) = in_size;	 /* input buffer length */
	*( ((int *) buff_p) +1 ) = out_size;	 /* output buffer length */
    
	status = ioctl( cd_fd, 1 /* SCSI_IOCTL_SEND_COMMAND */, buff_p );
    
	if (status) {
	    if (status == -EPERM) {
		fprintf( stderr, "Please run this program setuid root.\n");
		exit(status);
	    } else {
	       fprintf( stderr, "\nioctl(SCSI_IOCTL_SEND_COMMAND) status = 0x%x, cmd = %x\n", 
	       status, i_buff[OFF] );
	    }
	} else {
	    /* get output back */
	    if (out_size && out_size <= in_size + cmd_len)
		memcpy(o_buff, i_buff, out_size + OFF);
	}
    }
    if (interface == GENERIC_SCSI) {
	/* generic scsi device services */
	sg_hd = (struct sg_header *) buff_p;
	sg_hd->pack_len = OFF + cmd_len + in_size;
	sg_hd->reply_len = OFF + out_size;
	sg_hd->twelve_byte = cmd_len == 12;
#if	0
	sg_hd->pack_id;	/* not used */
	sg_hd->other_flags; 	/* not used */
#endif
	status = write( cd_fd, buff_p, OFF + cmd_len + in_size );
	if ( status < 0 || status != OFF + cmd_len + in_size || sg_hd->result ) {
	    if (status == -EPERM) {
		fprintf( stderr, "Please run this program setuid root.\n");
		exit(status);
	    } else {
	       fprintf( stderr, "\nwrite(generic) status = 0x%x, result = 0x%x cmd = %x\n", 
		       status, sg_hd->result, i_buff[OFF] );
	    }
	    return status;
	}
	
	status = read( cd_fd, buff_p, OFF + out_size);
	if ( status < 0 || status != OFF + out_size || sg_hd->result ) {
	    fprintf( stderr, "\nread(generic) status = 0x%x, result = 0x%x cmd = %x\n", 
		   status, sg_hd->result, i_buff[OFF] );
	    fprintf( stderr, "\nread(generic) sense "
                     "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", 
	     sg_hd->sense_buffer[0],	     sg_hd->sense_buffer[1],
	     sg_hd->sense_buffer[2],	     sg_hd->sense_buffer[3],
	     sg_hd->sense_buffer[4],	     sg_hd->sense_buffer[5],
	     sg_hd->sense_buffer[6],	     sg_hd->sense_buffer[7],
	     sg_hd->sense_buffer[8],	     sg_hd->sense_buffer[9],
	     sg_hd->sense_buffer[10],	     sg_hd->sense_buffer[11],
	     sg_hd->sense_buffer[12],	     sg_hd->sense_buffer[13],
	     sg_hd->sense_buffer[14],	     sg_hd->sense_buffer[15]
		    );
	} else
	    /* get output back */
	    if (out_size && out_size <= in_size + cmd_len)
		memcpy(o_buff, i_buff, out_size + OFF);
	/* Look if we got what we expected to get */
	if (status == OFF + out_size) status = 0; /* got them all */
    }
    return status;
}

static void EnableCddaToshiba (int fAudioMode)
{

  /* reserved, Medium type=0, Dev spec Parm = 0, block descriptor len 0 oder 8,
     Density (cd format) 
     (0=YellowBook, XA Mode 2=81h, XA Mode1=83h and raw audio via SCSI=82h),
     # blks msb, #blks, #blks lsb, reserved,
     blocksize, blocklen msb, blocklen lsb,
   */

  /* MODE_SELECT, page = SCSI-2  save page disabled, reserved, reserved, 
     parm list len, flags */
  static unsigned char cmdblk [6 + 12] = { 
                         0x15, /* MODE_SELECT */
			 0x10, /* no save page */
			    0, /* reserved */
			    0, /* reserved */
			    12, /* sizeof(mode) */
			    0, /* reserved */
       /* mode section */
			    0, 
                            0, 0, 
                            8,       /* Block Descriptor Length */
                            0,       /* Density Code */
                            0, 0, 0, /* # of Blocks */
                            0,       /* reserved */
                            0, 0, 0};/* Blocklen */
  unsigned char *mode = cmd + OFF + 6;
  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  if (fAudioMode) {
    mode [4] = 0x82;  			/* cdda density */
    mode [10] = (CD_FRAMESIZE_RAW >> 8);   /* block length "msb" */
    mode [11] = (CD_FRAMESIZE_RAW & 0xFF);
  } else {
    mode [4] = 0x00; 			/* normal density */
    mode [10] = (CD_FRAMESIZE >> 8);  /* block length "msb" */
    mode [11] = (CD_FRAMESIZE & 0xFF); /* block length lsb*/
  }

  if (handle_scsi_cmd (6, 12, cmd, 0, NULL))
        perror ("Audio mode switch failed\n");
}

/* actually reverse. For conversion on low endian machines */
static void *Swap (void *p, int size)
{
  char *pc = p;
  char tmp;

  if (size == 4) {
    tmp = pc [0];	/* reverse 4 bytes */
    pc [0] = pc [3];
    pc [3] = tmp;
    tmp = pc [1];
    pc [1] = pc [2];
    pc [2] = tmp;
  } else {
    tmp = pc [0];	/* reverse 2 bytes */
    pc [0] = pc [1];
    pc [1] = tmp;
  }
  return p;
}


static unsigned ReadTocSCSI ( TOC *g_toc )
{
  /* READTOC, MSF format flag, res, res, res, res, Start track, len msb,
     len lsb, flags */
  static unsigned char cmdblk [] = { 
    0x43, 0, 0, 0, 0,   0, 1, CD_FRAMESIZE >> 8, CD_FRAMESIZE & 0xFF, 0
  };
  int i;
  unsigned tracks;

  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  if (handle_scsi_cmd ( sizeof(cmdblk), 0, cmd, CD_FRAMESIZE, bufferTOC))
      FatalError ("Read TOC failed.\n");

  tracks = ((bufferTOC [OFF + 0] << 8) + bufferTOC [OFF + 1] - 2) / 8;
  if (tracks > MAXTRK) return 0;
  memcpy (g_toc, bufferTOC + OFF + 4, 8 * tracks);
  for (i = 0; i < tracks; i++) {
      Swap (&g_toc [i].dwStartSector, 4);
  }
  return --tracks;           /* without lead-out */
}


/* Read max. SectorBurst of cdda sectors to bufferCdda+OFF */
static void Read6 (long lSector, unsigned long SectorBurst )
{
  /* READ6, block1 msb, block2, block3, block4 lsb, 
     transfer len, block addressing mode */
  static unsigned char cmdblk [6] = {0x08, 0, 0, 0, 0, 0};

  unsigned char *cmd0 = cmd + OFF;
  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  cmd0 [1] = (unsigned char)((lSector >> 16) & 0xFF);
  cmd0 [2] = (unsigned char)((lSector >> 8) & 0xFF);
  cmd0 [3] = (unsigned char)(lSector & 0xFF);
  cmd0 [4] = (unsigned char)SectorBurst;

  if (handle_scsi_cmd(sizeof(cmdblk), 0, cmd, 
	SectorBurst * CD_FRAMESIZE_RAW, bufferCddaRaw ))
    FatalError ("Read CD-ROM6 failed\n");
}


/* Read max. SectorBurst of cdda sectors to bufferCdda+OFF */
static void ReadCdda10 (long lSector, unsigned long SectorBurst )
{
  /* READ10, flags, block1 msb, block2, block3, block4 lsb, reserved, 
     transfer len msb, transfer len lsb, block addressing mode */
  static unsigned char cmdblk [10] = {0xd4, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  unsigned char *cmd0 = cmd + OFF;
  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  cmd0 [3] = (unsigned char)((lSector >> 16) & 0xFF);
  cmd0 [4] = (unsigned char)((lSector >> 8) & 0xFF);
  cmd0 [5] = (unsigned char)(lSector & 0xFF);

  cmd0 [8] = (unsigned char)SectorBurst;

  if (handle_scsi_cmd(sizeof(cmdblk), 0, cmd, 
			    SectorBurst * CD_FRAMESIZE_RAW, bufferCddaRaw ))
    FatalError ("Read CD-ROM10 failed\n");
}

/* Read max. SectorBurst of cdda sectors to bufferCdda+OFF */
static void ReadCdda12 (long lSector, unsigned long SectorBurst )
{
  static unsigned char cmdblk [12] = {0xd8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  unsigned char *cmd0 = cmd + OFF;
  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  cmd0 [3] = (unsigned char)((lSector >> 16) & 0xFF);
  cmd0 [4] = (unsigned char)((lSector >> 8) & 0xFF);
  cmd0 [5] = (unsigned char)(lSector & 0xFF);
  cmd0 [9] = (unsigned char)SectorBurst;

  if (handle_scsi_cmd(sizeof(cmdblk), 0, cmd, 
			    SectorBurst * CD_FRAMESIZE_RAW, bufferCddaRaw ))
    FatalError ("Read CD-ROM12 failed\n");
}

/* Read the Sub-Q-Channel to SubQbuffer+OFF */
static subq_chnl *ReadSubQSCSI ( unsigned char format, unsigned char track )
{
  static unsigned char cmdblk [10] = {0x42, 0x02, 0x40, 0, 0, 0, 0, 0, 48, 0};

  unsigned char *cmd0 = cmd + OFF;
  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  cmd0 [3] = format;
  cmd0 [6] = track;

  if (handle_scsi_cmd(sizeof(cmdblk), 0, cmd, 48, SubQbuffer ))
    FatalError ("Read SubQ failed\n");
  return (subq_chnl *)(SubQbuffer + OFF);
}

/* request vendor brand and model */
unsigned char *Inquiry ( void )
{
  static unsigned char Inqbuffer[sizeof(struct sg_header) + 56];
  static unsigned char cmdblk [6] = {0x12, 0, 0, 0, 56, 0};

  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  if (handle_scsi_cmd(sizeof(cmdblk), 0, cmd, sizeof(Inqbuffer) - OFF, 
			    Inqbuffer )) {
      FatalError ("Inquiry failed\n");
  }
  return (Inqbuffer + OFF);
}

#define TESTUNITREADY_CMD 0
#define TESTUNITREADY_CMDLEN 6

#define ADD_SENSECODE 12
#define ADD_SC_QUALIFIER 13
#define NO_MEDIA_SC 0x3a
#define NO_MEDIA_SCQ 0x00
int TestForMedium ( void )
{
  /* request READY status */
  static unsigned char cmdblk [TESTUNITREADY_CMDLEN] = {
      TESTUNITREADY_CMD, /* command */
                      0, /* reserved */
                      0, /* reserved */
                      0, /* reserved */
                      0, /* reserved */
                      0};/* reserved */

  if (interface != GENERIC_SCSI) {
    return 1;
  }

  memcpy( cmd + OFF, cmdblk, sizeof(cmdblk) );

  if (handle_scsi_cmd(sizeof(cmdblk), 0, cmd, 
			    0, NULL)) {
      FatalError ("Test unit ready failed\n");
      return 0;
  }

  return 
   *(((struct sg_header*)cmd)->sense_buffer +ADD_SENSECODE) != NO_MEDIA_SC ||
   *(((struct sg_header*)cmd)->sense_buffer +ADD_SC_QUALIFIER) != NO_MEDIA_SCQ;
}

static void Check_interface_for_device( unsigned char * int_name )
{
    /* check for the correct device number */
    struct stat statstruct;
    if (stat(dev_name, &statstruct)) {
	fprintf(stderr, "cannot stat device %s\n",dev_name);
	exit(1);
    }
    if (!S_ISCHR(statstruct.st_mode) && 
	!S_ISBLK(statstruct.st_mode)) {
	fprintf(stderr, "%s is not a device\n",dev_name);
	exit(1);
    }

    switch (statstruct.st_rdev >> 8) {
    case 11:	/* scsi cd */
	if (interface != SCSI_IOCTL) {
	    fprintf(stderr, "wrong interface (%s) for this device (%s)\n"
		    "set to scsi_ioctl\n",int_name, dev_name);
	    interface = SCSI_IOCTL;
	}
	nsectors = NSECTORS;
	if (nsectors * CD_FRAMESIZE_RAW + OFF > 4096) {
	    fprintf(stderr, "setting nsectors to 1, so that transfer size < 4096\n");
	    nsectors = 1;
	}
	break;
    case 21:	/* generic */
	if (interface != GENERIC_SCSI) {
	    fprintf(stderr, "wrong interface (%s) for this device (%s)\n"
		    "set to generic_scsi\n",int_name, dev_name);
	    interface = GENERIC_SCSI;
	}
	nsectors = NSECTORS;
#ifdef SG_BIG_BUFF
	if (nsectors * CD_FRAMESIZE_RAW + OFF > SG_BIG_BUFF) {
	    nsectors = (SG_BIG_BUFF - OFF)/CD_FRAMESIZE_RAW;
	    fprintf(stderr, 
                    "setting nsectors to %d, so that transfer size < %d\n",
                     nsectors, SG_BIG_BUFF);
#else
	if (nsectors * CD_FRAMESIZE_RAW + OFF > 4096) {
	    nsectors = 1;
	    fprintf(stderr, 
                    "setting nsectors to %d, so that transfer size < %d\n",
                    nsectors, 4096);
#endif
	}
	break;
    case 25:	/* sbpcd */
	if (interface != COOKED_IOCTL) {
	    fprintf(stderr, "wrong interface (%s) for sbpcd device (%s)\n"
		    "set to cooked_ioctl\n",int_name,dev_name);
	    interface = COOKED_IOCTL;
	}
	nsectors = 4; /* current limit */
	break;
    case 15:	/* sony cdu 31a */
	nsectors = 75; /* no limit */
	if (interface != COOKED_IOCTL) {
	    fprintf(stderr, "cdrom device (%s). "
		    "Setting interface to cooked_ioctl.\n", dev_name);
	    interface = COOKED_IOCTL;
	}
	break;
    case 23:	/* mcd (Mitsumi) */
    case 24:	/* sony cdu 535, lms/Phillips */
	        /* idecd ??? */
	if (interface != COOKED_IOCTL) {
	    fprintf(stderr, "cdrom device (%s). "
		    "Setting interface to cooked_ioctl.\n", dev_name);
	    interface = COOKED_IOCTL;
	}
	nsectors = NSECTORS;
	break;
    default:
	fprintf(stderr, "unsupported device (%s)\n", dev_name);
	exit(1);
    }

}

/* open the cdrom device */
int OpenCdRom ( char *dev_name )
{
  int cd_fd;

  if (interface == GENERIC_SCSI)
      cd_fd = open(dev_name,O_RDWR);
  else
      cd_fd = open(dev_name,O_RDONLY);
  if (cd_fd < 0)
    FatalError ("No access to %s.\n",dev_name);
  return cd_fd;
}

/* hook */
void Dummy (int Switch)
{
}

void SetupSCSI( void )
{
    unsigned char *p = Inquiry();
    if (*p != 0x05) {
	fprintf(stderr, "this is no scsi cdrom\n");
	exit(1);
    }
    if (!memcmp(p+8,"TOSHIBA", 7)) {
         /* setup pointer */
	EnableCdda = EnableCddaToshiba;
	ReadCdRom = Read6;
    } else if (!memcmp(p+8,"HITACHI",7) ||
	       !memcmp(p+8,"SONY",4) ||
	       !memcmp(p+8,"PLEXTOR",7)) {
        if (interface != GENERIC_SCSI) {
	  /* unfortunately we have the wrong interface and are
	   * not able to change on the fly */
	  fprintf(stderr, "your cdrom requires the generic scsi interface\n");
	  exit(1);
	}
	EnableCdda = Dummy;
	ReadCdRom = ReadCdda12;
    } else if (!memcmp(p+8,"NEC",3)) {
	EnableCdda = Dummy;
	ReadCdRom = ReadCdda10;
	trackindex_disp = FALSE;
    } else {
	/* thats all i know */
	EnableCdda = Dummy;
	ReadCdRom = ReadCdda12;
    }

    ReadToc = ReadTocSCSI;
    ReadSubQ = ReadSubQSCSI;

    /* look if caddy is loaded */
    while (!TestForMedium()) {
	printf("load cdrom please and press enter");
	getchar();
    }
}

/*******************************************************************
 *
 *	cooked ioctl section
 *
 */
static struct cdrom_tochdr hdr;
static struct cdrom_tocentry entry[100];
static struct cdrom_read_audio arg;
static int err;

unsigned ReadToc_cooked ( TOC *g_toc )
{
    int i;
    unsigned tracks;
    /* get TocHeader */
    err = ioctl( cd_fd, CDROMREADTOCHDR, &hdr );
    if ( err != 0 ) {
	if (err == -EPERM) {
	    fprintf( stderr, "Please run this program setuid root.\n");
	    exit( err );
	} else {
	    fprintf( stderr, "can't get TocHeader (error %d).\n", err );
	    exit( -1 );
	}
    }
    /* get all TocEntries */
    for ( i = 0; i < hdr.cdth_trk1; i++ ) {
	entry[i].cdte_track = 1+i;
	entry[i].cdte_format = CDROM_LBA;
	err = ioctl( cd_fd, CDROMREADTOCENTRY, &entry[i] );
	if ( err != 0 ) {
	    fprintf( stderr, "can't get TocEntry #%d (error %d).\n", i+1, err );
	    exit( -1 );
	}
    }
    entry[i].cdte_track = CDROM_LEADOUT;
    entry[i].cdte_format = CDROM_LBA;
    err = ioctl( cd_fd, CDROMREADTOCENTRY, &entry[i] );
    if ( err != 0 ) {
	fprintf( stderr, "can't get TocEntry LEADOUT (error %d).\n", err );
	exit( -1 );
    }
    tracks = hdr.cdth_trk1+1;
    for (i = 0; i < tracks; i++) {
        g_toc[i].bFlags = (entry[i].cdte_adr << 4) | (entry[i].cdte_ctrl & 0x0f);
        g_toc[i].bTrack = entry[i].cdte_track;
        g_toc[i].dwStartSector = entry[i].cdte_addr.lba;
    }
    return --tracks;           /* without lead-out */
}

void ReadCdRom_cooked (long lSector, unsigned long SectorBurst )
{
  int retry_count=0;

/* read 2352 bytes audio data */
  arg.addr.lba = lSector;
  arg.addr_format = CDROM_LBA;
  arg.nframes = SectorBurst;
  arg.buf = &bufferCdda[0];

  do {
    err = ioctl(cd_fd, CDROMREADAUDIO, &arg);
    retry_count++;
  } while ((err) && (retry_count < 3));
  if (err != 0) {
      if (err == -EPERM) {
	  fprintf( stderr, "Please run this program setuid root.\n");
	  exit( err );
      } else {
	  fprintf(stderr, "can't read frame #%d (error %d).\n", 
		  arg.addr.lba, err);
	  exit( -1 );
      }
  }
}

static subq_chnl *ReadSubQ_cooked ( unsigned char format, unsigned char track )
{
    struct cdrom_subchnl sub_ch;

    if ( format != GET_POSITIONDATA ) return NULL;  /* not supported by kernel */

    if (!(err = ioctl(cd_fd, CDROMSUBCHNL, &sub_ch))) {
	/* copy to SubQbuffer */
	subq_chnl *SQp = (subq_chnl *) (SubQbuffer + OFF);
	subq_position *SQPp = (subq_position *) &SQp->data;
	SQp->audio_status 	= sub_ch.cdsc_audiostatus;
	SQp->format 		= sub_ch.cdsc_format;
	SQp->control 		= sub_ch.cdsc_ctrl;
	SQp->adr 		= sub_ch.cdsc_adr;
	SQp->track 		= sub_ch.cdsc_trk;
	SQp->index 		= sub_ch.cdsc_ind;
	SQp->control 		= sub_ch.cdsc_ctrl;
	SQPp->abs_min 		= sub_ch.cdsc_absaddr.msf.minute;
	SQPp->abs_sec 		= sub_ch.cdsc_absaddr.msf.second;
	SQPp->abs_frame 	= sub_ch.cdsc_absaddr.msf.frame;
	SQPp->trel_min 		= sub_ch.cdsc_reladdr.msf.minute;
	SQPp->trel_sec 		= sub_ch.cdsc_reladdr.msf.second;
	SQPp->trel_frame 	= sub_ch.cdsc_reladdr.msf.frame;
    } else {
	if (err == -EPERM) {
	    fprintf( stderr, "Please run this program setuid root.\n");
	    exit( err );
	} else {
	    fprintf(stderr, "can't read sub q channel (error %d).\n", err);
	    exit( -1 );
	}
    }
  return (subq_chnl *)(SubQbuffer + OFF);
}

void SetupCookedIoctl( void )
{
    EnableCdda = Dummy;
    ReadCdRom = ReadCdRom_cooked;
    ReadToc = ReadToc_cooked;
    ReadSubQ = ReadSubQ_cooked;
}

void SetupInterface( unsigned char *int_name )
{
    /* ensure interface is setup correct */
    Check_interface_for_device( int_name );	
    switch (interface) {
    case GENERIC_SCSI:	OFF = sizeof(struct sg_header); 
	break;
    case SCSI_IOCTL:	OFF = 2*sizeof(int); 
	break;
    case COOKED_IOCTL:	OFF = 0;
	break;
    }
    /* cdda sector audio data */
    bufferCddaRaw = malloc (OFF + nsectors * CD_FRAMESIZE_RAW);
    bufferCdda = bufferCddaRaw + OFF;
    /* one sector for table of contents */
    bufferTOC = malloc( OFF + CD_FRAMESIZE );
    /* SubQchannel buffer */
    SubQbuffer = malloc( OFF + 48 );
    cmd = malloc( OFF + 18 );

    cd_fd = OpenCdRom ( dev_name );

    /* if scsi drive get vendor name */
    if (interface == GENERIC_SCSI || interface == SCSI_IOCTL)
	SetupSCSI();
    else
	SetupCookedIoctl();
}
