/*
 * This file has been modified for the cdrkit suite.
 *
 * The behaviour and appearence of the program code below can differ to a major
 * extent from the version distributed by the original author(s).
 *
 * For details, see Changelog file distributed with the cdrkit package. If you
 * received this file from another source then ask the distributing person for
 * a log of modifications.
 *
 */

/* @(#)ioctl.c	1.22 06/02/19 Copyright 1998,1999,2000 Heiko Eissfeldt, Copyright 2006 J. Schilling */
/***
 * CopyPolicy: GNU Public License 2 applies
 * Copyright (C) 1999 Heiko Eissfeldt heiko@colossus.escape.de
 *
 * Ioctl interface module for cdrom drive access
 *
 * Solaris ATAPI cdrom drives are untested!
 *
 */
#include "config.h"
#include <stdio.h>
#include <standard.h>
#include <stdxlib.h>
#include <unixstd.h>
#include <strdefs.h>
#include <errno.h>
#include <signal.h>
#include <fctldefs.h>
#include <assert.h>

#include <sys/ioctl.h>
#include <statdefs.h>
#include <schily.h>
#include <device.h>

/* "major" and "minor" macros will be moved to sysmacros.h
 * silence the warning
 * added by cdrkit-1.1.11-sysmacros.patch */
#include <sys/sysmacros.h>

#include <usal/scsitransp.h>

#include "mycdrom.h"
#include "lowlevel.h"
/* some include file locations have changed with newer kernels */
#if defined (__linux__)
# if LINUX_VERSION_CODE > 0x10300 + 97
#  if LINUX_VERSION_CODE < 0x200ff
#   include <linux/sbpcd.h>
#   include <linux/ucdrom.h>
#  endif
#  if !defined(CDROM_SELECT_SPEED)
#   include <linux/ucdrom.h>
#  endif
# endif
#endif

#include "mytype.h"
#include "byteorder.h"
#include "interface.h"
#include "toc.h"
#include "icedax.h"
#include "ioctl.h"
#include "global.h"
#include "exitcodes.h"

#include <utypes.h>
#include <wodim.h>

#if defined (HAVE_IOCTL_INTERFACE)
#if  !defined(sun) && !defined(__sun) && !(defined(__FreeBSD__) && (__FreeBSD_version >= 501112))
static struct cdrom_read_audio arg;
#endif

#if (defined(__FreeBSD__) && __FreeBSD_version >= 400014) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
static unsigned sector_size;
#endif

static int err;

static void EnableCdda_cooked(SCSI *usalp, int fAudioMode, unsigned uSectorsize);
/* ARGSUSED */
static void EnableCdda_cooked(SCSI *usalp, int fAudioMode, unsigned uSectorsize)
{
#if	(defined(__FreeBSD__) && __FreeBSD_version >= 400014) || defined(__DragonFly__)  || defined(__FreeBSD_kernel__)
	if (usalp && usalp->verbose)
		fprintf(stderr, "EnableCdda_cooked (CDRIOCSETBLOCKSIZE)...\n");

	if (fAudioMode) {
		if (ioctl(global.cooked_fd, CDRIOCGETBLOCKSIZE, &sector_size) ==-1)
			sector_size = CD_FRAMESIZE;
        	ioctl(global.cooked_fd, CDRIOCSETBLOCKSIZE, &uSectorsize);
	} else
        	ioctl(global.cooked_fd, CDRIOCSETBLOCKSIZE, &sector_size);
#else
#if	defined	CDIOCSETCDDA
	if (usalp && usalp->verbose) {
		fprintf(stderr, "EnableCdda_cooked (CDIOCSETCDDA)...\n");
		if (uSectorsize != CD_FRAMESIZE_RAW)
			fprintf(stderr, "non audio sector size is ignored.\n");
	}

	ioctl(global.cooked_fd, CDIOCSETCDDA, &fAudioMode);
#else
	fprintf(stderr, "EnableCdda_cooked (CDIOCSETCDDA) is not available...\n");
#endif
#endif

}


static unsigned ReadToc_cooked(SCSI *x);

/* read the table of contents (toc) via the ioctl interface */
static unsigned ReadToc_cooked(SCSI *x)
{
    unsigned i;
    unsigned tracks;
    struct cdrom_tochdr hdr;
    struct cdrom_tocentry entry[100];
    struct cdrom_tocentry entryMSF[100];

    if (x && x->verbose) {
	fprintf(stderr, "ReadToc_cooked (CDROMREADTOCHDR)...\n");
    }

    /* get TocHeader to find out how many entries there are */
    err = ioctl( global.cooked_fd, CDROMREADTOCHDR, &hdr );
    if ( err != 0 ) {
	/* error handling */
	if (err == -1) {
	    if (errno == EPERM)
		fprintf( stderr, "Please run this program setuid root.\n");
	    perror("cooked: Read TOC ");
	    exit( DEVICE_ERROR );
	} else {
	    fprintf( stderr, "can't get TocHeader (error %d).\n", err );
	    exit( MEDIA_ERROR );
	}
    }
    /* get all TocEntries */
    for ( i = 0; i < hdr.cdth_trk1; i++ ) {
	entryMSF[i].cdte_track = 1+i;
	entryMSF[i].cdte_format = CDROM_MSF;
	err = ioctl( global.cooked_fd, CDROMREADTOCENTRY, &entryMSF[i] );
	if ( err != 0 ) {
	    /* error handling */
	    fprintf( stderr, "can't get TocEntry #%d msf (error %d).\n", i+1, err );
	    exit( MEDIA_ERROR );
	}
    }
    entryMSF[i].cdte_track = CDROM_LEADOUT;
    entryMSF[i].cdte_format = CDROM_MSF;
    err = ioctl( global.cooked_fd, CDROMREADTOCENTRY, &entryMSF[i] );
    if ( err != 0 ) {
	/* error handling */
	fprintf( stderr, "can't get TocEntry LEADOUT msf (error %d).\n", err );
	exit( MEDIA_ERROR );
    }
    tracks = hdr.cdth_trk1+1;
/*
    for (i = 0; i < tracks; i++) {
        toc[i].bFlags = (entry[i].cdte_adr << 4) | (entry[i].cdte_ctrl & 0x0f);
        toc[i].bTrack = entry[i].cdte_track;
	toc[i].mins = entry[i].cdte_addr.msf.minute;
	toc[i].secs = entry[i].cdte_addr.msf.second;
	toc[i].frms = entry[i].cdte_addr.msf.frame;
    }
*/
    /* get all TocEntries now in lba format */
    for ( i = 0; i < hdr.cdth_trk1; i++ ) {
	entry[i].cdte_track = 1+i;
	entry[i].cdte_format = CDROM_LBA;
	err = ioctl( global.cooked_fd, CDROMREADTOCENTRY, &entry[i] );
	if ( err != 0 ) {
	    /* error handling */
	    fprintf( stderr, "can't get TocEntry #%d lba (error %d).\n", i+1, err );
	    exit( MEDIA_ERROR );
	}
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
	entry[i].cdte_addr.lba = be32_to_cpu(entry[i].cdte_addr.lba);
#endif
    }
    entry[i].cdte_track = CDROM_LEADOUT;
    entry[i].cdte_format = CDROM_LBA;
    err = ioctl( global.cooked_fd, CDROMREADTOCENTRY, &entry[i] );
    if ( err != 0 ) {
	/* error handling */
	fprintf( stderr, "can't get TocEntry LEADOUT lba (error %d).\n", err );
	exit( MEDIA_ERROR );
    }
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
    entry[i].cdte_addr.lba = be32_to_cpu(entry[i].cdte_addr.lba);
#endif

    for (i = 0; i < tracks; i++) {
        toc_entry(i+1,
		  (entry[i].cdte_adr << 4) | (entry[i].cdte_ctrl & 0x0f),
		  entry[i].cdte_track,
		  NULL /* ISRC */,
		  entry[i].cdte_addr.lba,
		  entryMSF[i].cdte_addr.msf.minute,
		  entryMSF[i].cdte_addr.msf.second,
		  entryMSF[i].cdte_addr.msf.frame);
    }
    bufferTOC[0] = '\0';
    bufferTOC[1] = '\0';
    return --tracks;           /* without lead-out */
}

static void trash_cache_cooked(UINT4 *p, unsigned lSector, 
										 unsigned SectorBurstVal);

static void trash_cache_cooked(UINT4 *p, unsigned lSector, 
                               unsigned SectorBurstVal)
{
      /* trash the cache */

#if	defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
#if	defined(__FreeBSD__) && __FreeBSD_version >= 501112
      pread(global.cooked_fd, (void *) &p[0], 3*CD_FRAMESIZE_RAW,
          find_an_off_sector(lSector, SectorBurstVal)*CD_FRAMESIZE_RAW);
#else
      static struct cdrom_read_audio arg2;

      arg2.address.lba = find_an_off_sector(lSector, SectorBurstVal);
      arg2.addr_format = CDROM_LBA;
      arg2.nframes = 3;
      arg2.buffer = (unsigned char *) &p[0];

      ioctl(global.cooked_fd, CDROMREADAUDIO, &arg2);
#endif
#endif
#if	defined __linux__
      static struct cdrom_read_audio arg2;

      arg2.addr.lba = find_an_off_sector(lSector, SectorBurstVal);
      arg2.addr_format = CDROM_LBA;
      arg2.nframes = 3;
      arg2.buf = (unsigned char *) &p[0];

      ioctl(global.cooked_fd, CDROMREADAUDIO, &arg2);
#endif
#if	defined __sun || (defined HAVE_SYS_CDIO_H && defined CDROM_DA_NO_SUBCODE)
      struct cdrom_cdda suncdda;

      suncdda.cdda_addr = lSector;
      suncdda.cdda_length = SectorBurstVal*CD_FRAMESIZE_RAW;
      suncdda.cdda_data = (char *) &p[0];
      suncdda.cdda_subcode = CDROM_DA_NO_SUBCODE;
 
      ioctl(global.cooked_fd, CDROMCDDA, &suncdda);
#endif
}

static void ReadCdRomData_cooked(SCSI *x, UINT4 *p, unsigned lSector, 
											unsigned SectorBurstVal);
/* read 'SectorBurst' adjacent sectors of data sectors 
 * to Buffer '*p' beginning at sector 'lSector'
 */
static void ReadCdRomData_cooked(SCSI *x, UINT4 *p, unsigned lSector, 
                                 unsigned SectorBurstVal)
{
	int	retval;

	if (x && x->verbose) {
		fprintf(stderr, "ReadCdRomData_cooked (lseek & read)...\n");
	}

	if ((retval = lseek(global.cooked_fd, lSector*CD_FRAMESIZE, SEEK_SET))
		!= (int)lSector*CD_FRAMESIZE) { perror("cannot seek sector"); }
	if ((retval = read(global.cooked_fd, p, SectorBurstVal*CD_FRAMESIZE))
		!= (int)SectorBurstVal*CD_FRAMESIZE) { perror("cannot read sector"); }

	return;
}

static int ReadCdRom_cooked(SCSI *x, UINT4 *p, unsigned lSector, 
									 unsigned SectorBurstVal);
/* read 'SectorBurst' adjacent sectors of audio sectors 
 * to Buffer '*p' beginning at sector 'lSector'
 */
static int ReadCdRom_cooked(SCSI *x, UINT4 *p, unsigned lSector, 
                            unsigned SectorBurstVal)
{
  int retry_count=0;
  static int nothing_read = 1;

/* read 2352 bytes audio data */
#if	defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
#if	defined(__FreeBSD__) && __FreeBSD_version >= 501112
    if (x && x->verbose) {
	fprintf(stderr, "ReadCdRom_cooked (pread)...\n");
    }

  do {
    err = 0;
    if (pread(global.cooked_fd, (void *) &p[0], SectorBurstVal*CD_FRAMESIZE_RAW,
	lSector*CD_FRAMESIZE_RAW) == -1)
		err = -1;
#else
  arg.address.lba = lSector;
  arg.addr_format = CDROM_LBA;
  arg.nframes = SectorBurstVal;
  arg.buffer = (unsigned char *) &p[0];

    if (x && x->verbose) {
	fprintf(stderr, "ReadCdRom_cooked (CDROMREADAUDIO)...\n");
    }

  do {
    err = ioctl(global.cooked_fd, CDROMREADAUDIO, &arg);
#endif
#endif
#if	defined __linux__
  arg.addr.lba = lSector;
  arg.addr_format = CDROM_LBA;
  arg.nframes = SectorBurstVal;
  arg.buf = (unsigned char *) &p[0];

    if (x && x->verbose) {
	fprintf(stderr, "ReadCdRom_cooked (CDROMREADAUDIO)...\n");
    }

  do {
    err = ioctl(global.cooked_fd, CDROMREADAUDIO, &arg);
#endif
#if	defined __sun || (defined HAVE_SYS_CDIO_H && defined CDROM_DA_NO_SUBCODE)
  struct cdrom_cdda suncdda;

  suncdda.cdda_addr = lSector;
  suncdda.cdda_length = SectorBurstVal*CD_FRAMESIZE_RAW;
  suncdda.cdda_data = (char *) &p[0];
  suncdda.cdda_subcode = CDROM_DA_NO_SUBCODE;
 
    if (x && x->verbose) {
	fprintf(stderr, "ReadCdRom_cooked (CDROMCDDA)...\n");
    }

  do {
    err = ioctl(global.cooked_fd, CDROMCDDA, &suncdda);
#endif
    retry_count++;

    if (err) { 
      trash_cache_cooked(p, lSector, SectorBurstVal);
    }

  } while ((err) && (retry_count < 30));
  if (err != 0) {
	if (x->silent == 0) {
		/* error handling */
		if (err == -1) {
			if (nothing_read && (errno == EINVAL || errno == EIO))
				fprintf( stderr, "Sorry, this driver and/or drive does not support cdda reading.\n");
			perror("cooked: Read cdda ");
			fprintf(stderr, " sector %u + %u, buffer %p + %x\n", lSector, SectorBurstVal, p, global.shmsize);
		} else {
			fprintf(stderr, "can't read frame #%u (error %d).\n", 
				lSector, err);
		}
	}
	return SectorBurstVal - 1;
  } else {
    nothing_read = 0;
  }

  return SectorBurstVal;
}

static int StopPlay_cooked(SCSI *x);
static int StopPlay_cooked(SCSI *x)
{
    if (x && x->verbose) {
	fprintf(stderr, "StopPlay_cooked (CDROMSTOP)...\n");
    }

	return ioctl( global.cooked_fd, CDROMSTOP, 0 ) ? 0 : -1; 
}

static int Play_at_cooked(SCSI *x, unsigned int from_sector, 
								  unsigned int sectors);
static int Play_at_cooked(SCSI *x, unsigned int from_sector, 
                          unsigned int sectors)
{
	struct cdrom_msf cmsf;
	int retval;

    if (x && x->verbose) {
	fprintf(stderr, "Play_at_cooked (CDROMSTART & CDROMPLAYMSF)... (%u-%u)",
		from_sector, from_sector+sectors-1);
	
	fprintf(stderr, "\n");
    }

	cmsf.cdmsf_min0 = (from_sector + 150) / (60*75);
	cmsf.cdmsf_sec0 = ((from_sector + 150) / 75) % 60;
	cmsf.cdmsf_frame0 = (from_sector + 150) % 75;
	cmsf.cdmsf_min1 = (from_sector + 150 + sectors) / (60*75);
	cmsf.cdmsf_sec1 = ((from_sector + 150 + sectors) / 75) % 60;
	cmsf.cdmsf_frame1 = (from_sector + 150 + sectors) % 75;

#if	0
/* makes index scanning under FreeBSD too slow */
	if (( retval = ioctl( global.cooked_fd, CDROMSTART, 0 )) != 0){
		perror("");
	}
#endif
	if (( retval = ioctl( global.cooked_fd, CDROMPLAYMSF, &cmsf )) != 0){
		perror("");
	}
	return retval;
}

/* request sub-q-channel information. This function may cause confusion
 * for a drive, when called in the sampling process.
 */
static subq_chnl *ReadSubQ_cooked(SCSI *x, unsigned char sq_format, 
											 unsigned char track)
{
    struct cdrom_subchnl sub_ch;

#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
    struct cd_sub_channel_info sub_ch_info;

    if (x && x->verbose) {
	fprintf(stderr, "ReadSubQ_cooked (CDROM_GET_MCN or CDROMSUBCHNL)...\n");
    }

    sub_ch.address_format = CD_MSF_FORMAT;
    sub_ch.track = track;
    sub_ch.data_len = sizeof(struct cd_sub_channel_info);
    sub_ch.data = &sub_ch_info;

    switch (sq_format) {
      case GET_CATALOGNUMBER:
      sub_ch.data_format = CD_MEDIA_CATALOG;
#else
    if (x && x->verbose) {
	fprintf(stderr, "ReadSubQ_cooked (CDROM_GET_MCN or CDROMSUBCHNL)...\n");
    }

    switch (sq_format) {
      case GET_CATALOGNUMBER:
#endif
#if	defined CDROM_GET_MCN
      if (!(err = ioctl(global.cooked_fd, CDROM_GET_MCN, (struct cdrom_mcn *) SubQbuffer))) {
          subq_chnl *SQp = (subq_chnl *) SubQbuffer;
	  subq_catalog *SQPp = (subq_catalog *) &SQp->data;

          memmove(SQPp->media_catalog_number, SQp, sizeof (SQPp->media_catalog_number));
          SQPp->zero = 0;
          SQPp->mc_valid = 0x80;
          break;
      } else
#endif
      {
          return NULL;
      }
      case GET_POSITIONDATA:
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
      sub_ch.data_format = CD_CURRENT_POSITION;
#endif
#if defined (__linux__)
      sub_ch.cdsc_format = CDROM_MSF;
#endif
      if (!(err = ioctl(global.cooked_fd, CDROMSUBCHNL, &sub_ch))) {
	  /* copy to SubQbuffer */
	  subq_chnl *SQp = (subq_chnl *) (SubQbuffer);
	  subq_position *SQPp = (subq_position *) SQp->data;
	  SQp->audio_status 	= sub_ch.cdsc_audiostatus;
	  SQp->format 		= sub_ch.cdsc_format;
	  SQp->control_adr	= (sub_ch.cdsc_adr << 4) | (sub_ch.cdsc_ctrl & 0x0f);
	  SQp->track 		= sub_ch.cdsc_trk;
	  SQp->index 		= sub_ch.cdsc_ind;
	  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 == -1) {
	      if (errno == EPERM)
		  fprintf( stderr, "Please run this program setuid root.\n");
	      perror("cooked: Read subq ");
	      exit( DEVICE_ERROR );
	  } else {
	      fprintf(stderr, "can't read sub q channel (error %d).\n", err);
	      exit( DEVICE_ERROR );
	  }
      }
      break;
      default:
          return NULL;
    } /* switch */
  return (subq_chnl *)(SubQbuffer);
}

/* Speed control */
static void SpeedSelect_cooked(SCSI *x, unsigned speed);
/* ARGSUSED */
static void SpeedSelect_cooked(SCSI *x, unsigned speed)
{
	if (x && x->verbose) {
		fprintf(stderr, "SpeedSelect_cooked (CDROM_SELECT_SPEED)...\n");
	}

#ifdef CDROM_SELECT_SPEED
	/* CAUTION!!!!! Non standard ioctl parameter types here!!!! */
	if ((err = ioctl(global.cooked_fd, CDROM_SELECT_SPEED, speed))) {
		if (err == -1) {
			if (errno == EPERM)
				fprintf( stderr, "Please run this program setuid root.\n");
			perror("cooked: Speed select ");
			/*exit( err ); */
		} else {
			fprintf(stderr, "can't set speed %d (error %d).\n", speed, err);
			exit( DEVICE_ERROR );
		}
	}
#endif
}

/* set function pointers to use the ioctl routines */
void SetupCookedIoctl(char *pdev_name)
{
#if (HAVE_ST_RDEV == 1)
    struct stat statstruct;

    if (fstat(global.cooked_fd, &statstruct)) {
      fprintf(stderr, "cannot stat cd %d (%s)\n",global.cooked_fd, pdev_name);
      exit(STAT_ERROR);
    }
#if	defined __linux__
    switch (major(statstruct.st_rdev)) {
    case CDU31A_CDROM_MAJOR:	/* sony cdu-31a/33a */
        global.nsectors = 13;
        if (global.nsectors >= 14) {
	  global.overlap = 10;
	}
        break;
    case MATSUSHITA_CDROM_MAJOR:	/* sbpcd 1 */
    case MATSUSHITA_CDROM2_MAJOR:	/* sbpcd 2 */
    case MATSUSHITA_CDROM3_MAJOR:	/* sbpcd 3 */
    case MATSUSHITA_CDROM4_MAJOR:	/* sbpcd 4 */
        /* some are more compatible than others */
        global.nsectors = 13;
	break;
    default:
        global.nsectors = 8;
	break;
    }
    err = ioctl(global.cooked_fd, CDROMAUDIOBUFSIZ, global.nsectors);

    switch (major(statstruct.st_rdev)) {
    case MATSUSHITA_CDROM_MAJOR:	/* sbpcd 1 */
    case MATSUSHITA_CDROM2_MAJOR:	/* sbpcd 2 */
    case MATSUSHITA_CDROM3_MAJOR:	/* sbpcd 3 */
    case MATSUSHITA_CDROM4_MAJOR:	/* sbpcd 4 */
      if (err == -1) {
        perror("ioctl(CDROMAUDIOBUFSIZ)");
      }
    }
#endif
#endif
    EnableCdda = EnableCdda_cooked;
    ReadCdRom = ReadCdRom_cooked;
    ReadCdRomData = (int (*)(SCSI *, unsigned char *, unsigned, unsigned)) ReadCdRomData_cooked;
    doReadToc = ReadToc_cooked;
    ReadTocText = NULL;
    ReadSubQ = ReadSubQ_cooked;
    ReadSubChannels = NULL;
    SelectSpeed = SpeedSelect_cooked;
    Play_at = Play_at_cooked;
    StopPlay = StopPlay_cooked;
    trash_cache = trash_cache_cooked;
    ReadLastAudio = NULL;
}
#endif
