Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

arch_scsi_gen_cdrom.c

/* $Id: arch_scsi_gen_cdrom.c,v 1.164 2009-01-28 12:59:16 potyra Exp $ 
 *
 * Copyright (C) 2007-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#define MAX_DATA_TRACKS 100
#define MAX_CD_AREAS    100

#ifdef STATE

struct {
      /*
       * Config
       */
      unsigned int id;
      char media_name[50];

      /*
       * Process
       */
      struct process process;

      /*
       * Signals
       */
      unsigned int state_atn;

      /*
       * State
       */
      /* Media Description */
      toc_session toc;
      struct storage media[MAX_DATA_TRACKS];
      unsigned int nfiles;
      struct {
            enum {
                  CD_LEADIN,
                  CD_PREGAP,
                  CD_AUDIO,
                  CD_DATA,
                  CD_POSTGAP,
                  CD_LEADOUT,
            } type;
            unsigned int tracknum;
            unsigned int indexnum;
            unsigned int filenum;
            unsigned long long fileoffset;
            unsigned long long size;
      } area[MAX_CD_AREAS];
      unsigned int nareas;

      int open;
      int faum_image;
      char media_inserted;
      uint32_t written_blocks;
      int32_t last_written_lba;
      int is_writing;
      int fd;

      cd_image media_desc;

      /* Controller */
      unsigned int locked;

      unsigned int unit_attention;
      unsigned char changed;
      unsigned char sense_key; /* sense: needed for request_sense */
      unsigned char asc;       /* asc:   needed for request_sense */
      unsigned char ascq;      /* ascq:  needed for request_sense */

      char mode_page_05[0x34];


      /* New state */
      unsigned int selected;
      unsigned int lun;

      uint8_t buf[0x10000];
      unsigned int count;
      unsigned int head;
      unsigned int tail;

      uint8_t msg_buf[2 + 256];
      uint8_t cmd_buf[12];

      unsigned long long m2t_pos;
      unsigned long m2t_count;
} NAME;

#endif /* STATE */

#ifdef BEHAVIOR

static void
NAME_(open_file)(struct cpssp *s)
{
      if (s->NAME.open) {
            return;
      }
      if (s->NAME.faum_image) {
            int ret;
            ret = storage_open(&s->NAME.media[0], 1);
            assert(0 <= ret);
      } else {
            unsigned int file;
            for (file = 0; file < s->NAME.nfiles; file++) {
                  storage_open(&s->NAME.media[file], 0);
            }
      }
      s->NAME.open = 1;
}

static void
NAME_(close_file)(struct cpssp *s)
{
      if (! s->NAME.open) {
            return;
      }
      if (s->NAME.faum_image) {
            storage_close(&s->NAME.media[0]);
      } else {
            unsigned int file;

            for (file = 0; file < s->NAME.nfiles; file++) {
                  storage_close(&s->NAME.media[file]);
            }
      }
      s->NAME.open = 0;
}


static void
NAME_(msf_to_lba)(uint8_t msf[3], int64_t *lba)
{
      /* msf[0] = min; msf[1] = sec; msf[2] = frame */
      if (msf[0] < 90) {
            *lba = (msf[0] * 60 + msf[1]) * 75 + msf[2] - 150;
      } else {
            *lba = (msf[0] * 60 + msf[1]) * 75 + msf[2] - 450150;
      }
}

#if CD_WRITER_SUPPORT /* only used in writer */
static void
NAME_(lba_to_msf)(int32_t lba, uint8_t *msf)
{
      /* msf[0] = min; msf[1] = sec; msf[2] = frame */
      if ( -150 <= lba && lba <= 404849 ) {
            msf[0] = (lba + 150) / ( 60 * 75 );
            msf[1] = (lba + 150 - (msf[0] * 60 * 75)) / 75;
            msf[2] = (lba + 150 - (msf[0] * 60 * 75) - msf[1] * 75);
      } else if ( -45150 <= lba && lba <= -151 ) {
            msf[0] = (lba + 450150) / ( 60 * 75 );
            msf[1] = (lba + 450150 - (msf[0] * 60 * 75)) / 75;
            msf[2] = (lba + 450150 - (msf[0] * 60 * 75) - msf[1] * 75);
      } else {
            fprintf(stderr, "Invalid value for lba! (%i)\n", lba);
            assert(0);
      }
}

#endif

static toc_entry*
NAME_(get_track_at_lba)(struct cpssp *s, int32_t lba) {
      int i;
      int64_t lba_leadout;
      toc_entry *act_toc_line = 0;
      toc_entry *act_session;


      act_session = &(s->NAME.toc.xA2);
      /* first check if we are in the right session */
      while ( 1 ) {
            NAME_(msf_to_lba)(&act_session->pmin, &lba_leadout);
            if ( lba < lba_leadout ) {
                  break;
            } else {
                  /* check for invisible track and return NULL */
                  if ( (act_session + 1)->pmin == 0 ) { /* must point to something > 0 */
                        /* so we check for a lba inside the invisible track */
                        return NULL;
                  }
                  /* or jump to next leadin */
                  fixme();
            }
      }


      /* search track for lba value to get blockcount of track */
      /* start with second track, stop after last track */
      for (i = (act_session - 2)->pmin; i <= (act_session - 1)->pmin; i++) {
            act_toc_line = (act_session - 101) + i;
            int64_t lba_startoftrack;
            NAME_(msf_to_lba)(&act_toc_line->pmin, &lba_startoftrack);

            /*
             * we need to know the first track which starts with a lba
             * bigger than the requested one (or the last one)
             */

            if (lba < lba_startoftrack) {
                  break;
            }
      }

      /* then we can go back one track */
      act_toc_line--;
      return act_toc_line;
}

#if CD_WRITER_SUPPORT
static int32_t
NAME_(get_last_written_block)(struct cpssp *s, int32_t lba) {
      toc_entry *act_toc_line;
      act_toc_line = NAME_(get_track_at_lba)(s, lba);
      /* we need the blocktype of the track */
      /* then, skip pregap */
      /* read header of every block until there is no header */
      /* FIXME implement after packet writing because of handling of link-blocks */
      return 0;
}
#endif

static void
NAME_(check_condition)(
            struct cpssp *s,
            unsigned char sense_key,
            unsigned char asc,
            unsigned char ascq
            )
{
      s->NAME.sense_key = sense_key;
      s->NAME.asc = asc;
      s->NAME.ascq = ascq;
}

/*
 * See:
 *
 * SCSI-3 Primary Commands
 *
 * Page 80 ff (list of sense key codes)
 * Page 146 ff (sorted list of ASC/ASCQ codes)
 */
static void
NAME_(medium_changed)(struct cpssp *s)
{
      NAME_(check_condition)(s, UNIT_ATTENTION, 0x28, 0x00);
}

static void
NAME_(medium_not_present)(struct cpssp *s)
{
      NAME_(check_condition)(s, NOT_READY, 0x3a, 0x00);
}

static void
NAME_(end_of_medium_reached)(struct cpssp *s)
{
      NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x3b, 0x0f);
}

static void
NAME_(_recv)(struct cpssp *cpssp, unsigned int count)
{
      cpssp->NAME.count = count;
      cpssp->NAME.head = 0;
      cpssp->NAME.tail = 0;

      NAME_(want_recv)(cpssp, count);

      while (cpssp->NAME.head < cpssp->NAME.count) {
            sched_sleep();
      }
}

static void
NAME_(_send)(struct cpssp *cpssp, unsigned int count)
{
      cpssp->NAME.count = count;
      cpssp->NAME.head = count;
      cpssp->NAME.tail = 0;

      NAME_(want_send)(cpssp, count);

      while (cpssp->NAME.tail < cpssp->NAME.count) {
            sched_sleep();
      }
}

/*
 * SCSI-3 SPC 206
 *
 * 0xa0, mandatory
 */
static void
NAME_(report_luns)(struct cpssp *s)
{
      uint8_t select_report;
      uint32_t allocation_length;

      assert(s->NAME.cmd_buf[0] == 0xa0);
      /* Byte 1 is reserved. */
      select_report = s->NAME.cmd_buf[2];
      /* Byte 3-5 is reserved. */
      allocation_length = (s->NAME.cmd_buf[6] << 24)
                  | (s->NAME.cmd_buf[7] << 16)
                  | (s->NAME.cmd_buf[8] << 8)
                  | (s->NAME.cmd_buf[9] << 0);
      /* Byte 10 is reserved. */
      /* Control - FIXME */

      if (allocation_length < 16) {
illegal_request:;
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            return;
      }

      switch (select_report) {
      case 0x00:
      case 0x01:
      case 0x02:
            fprintf(stderr, "Select report = %d\n", select_report);

            s->NAME.buf[0] = 0; /* One 8-byte entry */
            s->NAME.buf[1] = 0;
            s->NAME.buf[2] = 0;
            s->NAME.buf[3] = 8;

            s->NAME.buf[4] = 0; /* Reserved */
            s->NAME.buf[5] = 0;
            s->NAME.buf[6] = 0;
            s->NAME.buf[7] = 0;

            s->NAME.buf[8] = 0; /* LUN 0 */
            s->NAME.buf[9] = 0;
            s->NAME.buf[10] = 0;
            s->NAME.buf[11] = 0;
            s->NAME.buf[12] = 0;
            s->NAME.buf[13] = 0;
            s->NAME.buf[14] = 0;
            s->NAME.buf[15] = 0;
            break;
      default:
            goto illegal_request;
      }

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, 16);
}

/*
 * SCSI-3 SBC 41
 *
 * 0x28, mandatory
 */
static void
NAME_(read_10)(struct cpssp *s)
{
      unsigned long start;
      unsigned int area;
      unsigned long ret = 0;

      /*
       * Get parameter.
       */
      assert(s->NAME.cmd_buf[0] == 0x28);
      /* dpo fua and reladr are reserved in atapi */
      /* (s->NAME.cmd_buf[1] >> 5) & 7: Reserved */
      /* dpo = (s->NAME.cmd_buf[1] >> 4) & 1 */
      /* fua = (s->NAME.cmd_buf[1] >> 3) & 1 */
      /* (s->NAME.cmd_buf[1] >> 1) & 3: Reserved */
      /* reladr = (s->NAME.cmd_buf[1] >> 0) & 1 */

      s->NAME.m2t_pos = be32_to_cpu(*((uint32_t *) &s->NAME.cmd_buf[2]));
      /* s->NAME.cmd_buf[6]: Reserved */
      s->NAME.m2t_count = be16_to_cpu(*((uint16_t *) &s->NAME.cmd_buf[7]));
      /* s->NAME.cmd_buf[9]: Control */

      if (s->NAME.media_inserted == 0) {
            /* Media not present. */
            s->NAME.m2t_count = 0;
            NAME_(medium_not_present)(s);
            return;
      }
#if DEBUGPCOM
      fprintf(stderr, "Host wants %li blocks beginning from %lli\n", s->NAME.m2t_count, s->NAME.m2t_pos);
#endif

      if (s->NAME.faum_image) {
            int64_t lba_leadout;
            int64_t lba;
            int64_t lead_in_size;
            toc_entry *act_toc_line;
            if (s->NAME.toc.xA1.pmin == 0) { /* empty disc */
                  s->NAME.m2t_count = 0;
                  /* current program area is empty */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x2c, 0x04);
                  return;
            }

            NAME_(msf_to_lba)(&s->NAME.toc.xA2.pmin, &lba_leadout);

            /* End of medium reached. */
            if (lba_leadout <= s->NAME.m2t_pos) {
                  /* logical block address out of range */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x21, 0x00);
                  return;
            }

            act_toc_line = NAME_(get_track_at_lba)(s, s->NAME.m2t_pos);

            NAME_(msf_to_lba)(&(act_toc_line->pmin),&lba);

            if ((lba + act_toc_line->blkcount) < (s->NAME.m2t_pos + s->NAME.m2t_count)) {
                  s->NAME.m2t_count = lba + act_toc_line->blkcount - s->NAME.m2t_pos;
            }

            s->NAME.m2t_count *= 2048;

            NAME_(msf_to_lba)((uint8_t *)&s->NAME.media_desc.start_of_leadin_m, &lead_in_size);
            lead_in_size *= -1;

            NAME_(phase_data_in)(s);
            /* read each 2048byte data slot separately */
            while (ret < s->NAME.m2t_count) {
                  NAME_(open_file)(s);

                  ret += storage_read_write(IO_READ,
                              &s->NAME.media[0],
                              s->NAME.buf,
                              /* (pos / 2048) * 2532 = Offset im Image */
                              s->NAME.media_desc.offset 
                              + s->NAME.media_desc.toc_size
                              + lead_in_size
                              + 150 * 2352
                              + (s->NAME.m2t_pos * sizeof(mode1)),
                              2048);
                  s->NAME.m2t_pos++;
                  NAME_(_send)(s, 2048);
            }
      } else { /* s->NAME.faum_image */
            s->NAME.m2t_pos *= 2048;
            s->NAME.m2t_count *= 2048;
            if (s->NAME.nareas == 0) {
                  /* empty disk */
                  s->NAME.m2t_count = 0;
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x21, 0x00); /* LBA out of range */
                  return;
            }

            start = 0;
            for (area = 0; ; area++) {
                  if (area == s->NAME.nareas) {
                        /* End of media reached. */
                        s->NAME.m2t_count = 0;
                        NAME_(end_of_medium_reached)(s);
                        return;
                  }

                  if (area == 0) {
                        /* Skip LEADIN. */
                        assert(s->NAME.area[area].type == CD_LEADIN);
                        continue;
                  } else if (area == 1) {
                        /* Skip first PREGAP. */
                        assert(s->NAME.area[area].type == CD_PREGAP);
                        continue;
                  } else if (area == s->NAME.nareas - 1) {
                        /* Skip LEADOUT. */
                        assert(s->NAME.area[area].type == CD_LEADOUT);
                        continue;
                  }

                  if (start <= s->NAME.m2t_pos / 2048
                              && s->NAME.m2t_pos / 2048 < start + s->NAME.area[area].size / 2048) {
                        /* Area found. */
                        break;
                  }

                  start += s->NAME.area[area].size / 2048;
            }
            s->NAME.m2t_pos -= (unsigned long long) start * 2048;

            if (s->NAME.area[area].size < s->NAME.m2t_pos + s->NAME.m2t_count) {
                  /* Don't read beyond area end. */
                  s->NAME.m2t_count = s->NAME.area[area].size - s->NAME.m2t_pos;
            }

            switch (s->NAME.area[area].type) {
            case CD_LEADIN:
            case CD_LEADOUT:
                  assert(0);
            case CD_AUDIO:
                  fixme();
                  ret = 0;
                  break;
            case CD_DATA:
                  NAME_(phase_data_in)(s);
                  while (ret < s->NAME.m2t_count) {
                        NAME_(open_file)(s);

                        ret += storage_read_write(IO_READ,
                                    &s->NAME.media[s->NAME.area[area].filenum],
                                    s->NAME.buf,
                                    s->NAME.area[area].fileoffset + s->NAME.m2t_pos,
                                    2048);
                        s->NAME.m2t_pos += 2048;
                        NAME_(_send)(s, 2048);
                  }
                  break;
            case CD_PREGAP:
            case CD_POSTGAP:
                  memset(s->NAME.buf, 0, sizeof(s->NAME.buf));
                  NAME_(phase_data_in)(s);
                  while(ret < s->NAME.m2t_count) {
                        ret += 2048;
                        NAME_(_send)(s, 2048);
                  }
                  break;
            default:
                  assert(0);
            }
      }
      assert(ret == s->NAME.m2t_count);
#if DEBUGPCOM
      fprintf(stderr, "Gave Host %lu Blocks\n", s->NAME.m2t_count / 2048);
#endif
}

/* Adapted from QEMU */
static void
padstr8(unsigned char *buf, int buf_size, const char *src)
{
      int i;

      for (i = 0; i < buf_size; i++) {
            if (*src) {
                  buf[i] = *src++;

            } else {
                  buf[i] = ' ';
            }
      }
}

/*
 * SCSI-3 SPC, 34
 *
 * 0x12, mandatory
 */
static void
NAME_(inquiry)(struct cpssp *s)
{
      uint8_t reserved0;
      uint8_t cmddt;
      uint8_t evpd;
      uint8_t lun;
      uint8_t page_or_operation_code;
      uint8_t reserved1;
      uint16_t allocation_length;

      assert(s->NAME.cmd_buf[0] == 0x12);
      reserved0 = (s->NAME.cmd_buf[1] >> 2) & 0x03;
      lun = (s->NAME.cmd_buf[1] >> 5);
      cmddt = (s->NAME.cmd_buf[1] >> 1) & 1;
      evpd = (s->NAME.cmd_buf[1] >> 0) & 1;
      page_or_operation_code = s->NAME.cmd_buf[2];
      reserved1 = s->NAME.cmd_buf[3];
      allocation_length = s->NAME.cmd_buf[4];
      /* control = s->NAME.cmd_buf[5]; not used */

      /* Check reserved/unsupported fields. */
      if (reserved0 != 0) {
            faum_log(FAUM_LOG_WARNING, "CDROM", "", "reserved0 != 0\n");
            goto illegal_request;
      }
      if (cmddt != 0) {
            faum_log(FAUM_LOG_WARNING, "CDROM", "", "cmddt != 0\n");
            goto illegal_request;
      }
      if (evpd != 0) {
            faum_log(FAUM_LOG_WARNING, "CDROM", "", "evpd != 0\n");
            goto illegal_request;
      }
      if (page_or_operation_code != 0) {
            faum_log(FAUM_LOG_WARNING, "CDROM", "", "page_or_operation_code != 0\n");
            goto illegal_request;
      }
      if (reserved1 != 0) {
            faum_log(FAUM_LOG_WARNING, "CDROM", "", "reserved1 != 0\n");
illegal_request:;
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            return;
      }

      if (allocation_length == 0) {
            /* only transfer header */
            allocation_length = 5;
      }
      if (96 < allocation_length) {
            allocation_length = 96;
      }

      if (lun == 0 && s->NAME.lun == 0) {
            s->NAME.buf[0] = (0 << 5)    /* Peripheral qualifier */
                  | (5 << 0);     /* CDROM */
            s->NAME.buf[1] = (1 << 7)    /* Removable medium */
                  | (0 << 0);     /* Reserved */
            s->NAME.buf[2] = (0 << 6)    /* ISO/IEC version */
                  | (0 << 3)      /* ECMA version */
                  | (3 << 0);     /* ANSI version (compliant to SCSI-3) */
            s->NAME.buf[3] = (0 << 7)    /* No async event reporting */
                  | (0 << 6)      /* No terminate task function */
                  | (0 << 5)      /* No normal ACA support */
                  | (0 << 4)      /* Reserved */
                  | (2 << 0);     /* Response data format compliant to SCSI-3 */
            s->NAME.buf[4] = 95 - 4;/* Additional transfer length */
            s->NAME.buf[5] = 0;     /* Reserved */
            s->NAME.buf[6] = (0 << 7)    /* Reserved */
                  | (0 << 6)      /* EncServ */
                  | (0 << 5)      /* VS */
                  | (0 << 4)      /* MultiP */
                  | (0 << 3)      /* MChngr */
                  | (0 << 2)      /* ACKREQQ */
                  | (0 << 1)      /* Addr32 */
                  | (0 << 0);     /* Addr16 */
            s->NAME.buf[7] = (0 << 7)    /* RelAdr */
                  | (0 << 6)      /* WBus32 */
                  | (0 << 5)      /* WBus16 */
                  | (0 << 4)      /* Sync */
                  | (0 << 3)      /* Linked */
                  | (0 << 2)      /* TranDis */
                  | (0 << 1)      /* CmdQue */
                  | (0 << 0);     /* VS */
            padstr8(&s->NAME.buf[8], 8, "FAUM"); /* Vendor identification */
#if CD_WRITER_SUPPORT
            padstr8(&s->NAME.buf[16], 16, "CD/DVD-RW"); /*Product identification*/
#else
            padstr8(&s->NAME.buf[16], 16, "CD/DVD-ROM"); /*Product identification*/
#endif
            padstr8(&s->NAME.buf[32], 4, "2.1"); /* Product revision level */
            memset(&s->NAME.buf[36], 56 - 36, 0); /* Vendor-specific */
            memset(&s->NAME.buf[56], 96 - 56, 0); /* Reserved */
      } else {
            s->NAME.buf[0] = 0x7f; /* No device on this lun. */
            memset(&s->NAME.buf[1], 36 - 1, 0); /* not needed */
            memset(&s->NAME.buf[36], 56 - 36, 0); /* Vendor-specific */
            memset(&s->NAME.buf[56], 96 - 56, 0); /* Reserved */
      }

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, allocation_length);
}

static void
NAME_(get_modepage_defaults)(char *defaults, char page_code)
{
      switch (page_code) {
      case 0x05:
            defaults[0] = (0 << 7) | (0 << 6) | (0x05 << 0);
            defaults[1] = 0x32;
            defaults[2] = (0 << 5) | (0 << 4) | (0x01 << 0);
            defaults[3] = (0 << 6) | (0 << 5) | (0 << 4) | (0x5 << 0);
            defaults[4] = (0 << 4) | (0x8 << 0);
            defaults[5] = 0x00;
            defaults[6] = 0x00;
            defaults[7] = (0 << 6) | (0x00 << 0);
            defaults[8] = 0x00;
            defaults[9] = 0x00;
            defaults[10] = 0x00;
            defaults[11] = 0x00;
            defaults[12] = 0x00;
            defaults[13] = 0x00;
            defaults[14] = 150 / 256;
            defaults[15] = 150 % 256;
            defaults[16] = 0x00;
            defaults[17] = 0x00;
            defaults[18] = 0x00;
            defaults[19] = 0x00;
            defaults[20] = 0x00;
            defaults[21] = 0x00;
            defaults[22] = 0x00;
            defaults[23] = 0x00;
            defaults[24] = 0x00;
            defaults[25] = 0x00;
            defaults[26] = 0x00;
            defaults[27] = 0x00;
            defaults[28] = 0x00;
            defaults[29] = 0x00;
            defaults[30] = 0x00;
            defaults[31] = 0x00;
            defaults[32] = 0x00;
            defaults[33] = 0x00;
            defaults[34] = 0x00;
            defaults[35] = 0x00;
            defaults[36] = 0x00;
            defaults[37] = 0x00;
            defaults[38] = 0x00;
            defaults[39] = 0x00;
            defaults[40] = 0x00;
            defaults[41] = 0x00;
            defaults[42] = 0x00;
            defaults[43] = 0x00;
            defaults[44] = 0x00;
            defaults[45] = 0x00;
            defaults[46] = 0x00;
            defaults[47] = 0x00;
            defaults[48] = 0x00;
            defaults[49] = 0x00;
            defaults[50] = 0x00;
            defaults[51] = 0x00;
            break;
      default:
            fixme();
      }
}

static void
NAME_(get_modepage_changeables)(char *changeables, char page_code)
{
      switch (page_code) {
      case 0x05:
            changeables[0] = 0x05;
            changeables[1] = 0x32;
            changeables[2] = 0x5f;
            changeables[3] = 0xff;
            changeables[4] = 0x0f;
            changeables[5] = 0x00;
            changeables[6] = 0x00;
            changeables[7] = 0x3f;
            changeables[8] = 0xff;
            changeables[9] = 0x00;
            changeables[10] = 0xff;
            changeables[11] = 0xff;
            changeables[12] = 0xff;
            changeables[13] = 0xff;
            changeables[14] = 0xff;
            changeables[15] = 0xff;
            changeables[16] = 0xff;
            changeables[17] = 0xff;
            changeables[18] = 0xff;
            changeables[19] = 0xff;
            changeables[20] = 0xff;
            changeables[21] = 0xff;
            changeables[22] = 0xff;
            changeables[23] = 0xff;
            changeables[24] = 0xff;
            changeables[25] = 0xff;
            changeables[26] = 0xff;
            changeables[27] = 0xff;
            changeables[28] = 0xff;
            changeables[29] = 0xff;
            changeables[30] = 0xff;
            changeables[31] = 0xff;
            changeables[32] = 0xff;
            changeables[33] = 0xff;
            changeables[34] = 0xff;
            changeables[35] = 0xff;
            changeables[36] = 0xff;
            changeables[37] = 0xff;
            changeables[38] = 0xff;
            changeables[39] = 0xff;
            changeables[40] = 0xff;
            changeables[41] = 0xff;
            changeables[42] = 0xff;
            changeables[43] = 0xff;
            changeables[44] = 0xff;
            changeables[45] = 0xff;
            changeables[46] = 0xff;
            changeables[47] = 0xff;
            changeables[48] = 0xff;
            changeables[49] = 0xff;
            changeables[50] = 0xff;
            changeables[51] = 0xff;
            break;
      default:
            fixme();
      }
}

/*
 * SCSI-3 SPC 47
 *
 * 0x55, mandatory
 */
static int
NAME_(modepage_store)(
            struct cpssp *s,
            int cmd_len,
            uint16_t parameter_list_length
            )
{
      unsigned char *pointer;
      unsigned char changeables[100];
      int i;
      int block_descriptor_length;

      pointer = s->NAME.buf;

      if (cmd_len == 10) {
            /* skip 8 bytes header */
            pointer += 8;
            parameter_list_length -= 8;

            /* After 8 bytes header there may be block descriptors */
            block_descriptor_length = (s->NAME.buf[6] << 8) | s->NAME.buf[7];
      } else {
            /* skip 4 bytes header */
            pointer += 4;
            parameter_list_length -= 4;

            /* After 4 bytes header there may be block descriptors */
            block_descriptor_length = s->NAME.buf[3];
      }

      while (0 < block_descriptor_length) {
            /* 8 bytes block descriptors */
            pointer += 8;
            parameter_list_length -= 8;
            block_descriptor_length -= 8;
            /* Do nothing here because block descriptors must not be supported:
             * MMC-5 P.648 A.3 */
      }

      /* finally process mode pages */
      /* FIXME there is no verification whether the parameters are valid */
      while (0 < parameter_list_length) {
            switch (pointer[0] & 0x3F) {
            case 0x05: /* write parameters scsi-3 mmc 89 */
                  memset(changeables, 0, 100);
                  NAME_(get_modepage_changeables)(changeables, 0x05);
                  for (i=2; i<0x34; i++) {
                        s->NAME.mode_page_05[i] &= ~changeables[i];
                        pointer[i] &= changeables[i];
                        s->NAME.mode_page_05[i] |= pointer[i];
                  }
                  break;
            case 0x0e: /* CD Audio Control parameters page SCSI-3 MMC 75 */
                  /* FIXME: do we have to set something here? */
                  break;
            case 0x2a: /* MM Capabilities and Mechanical Status Page (ro) */
                  break;
            default:
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x26, 0x00); /* invalid field in parameter list */
                  return -1;
            }
            /* decrease parameter list length and increase pointer
             * (do not forget the 2 bytes header not included in
             * pointer[1]) */
            parameter_list_length -= (pointer[1] + 2);
            pointer += (pointer[1] + 2);
      }
      return 0;
}

/*
 * SCSI-3 SPC 45
 *
 * 0x15, mandatory
 */
static void
NAME_(mode_select_6)(struct cpssp *s)
{
      int pf;
      int sp;
      uint16_t parameter_list_length;

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0x15);
      /* (s->NAME.cmd_buf[1] >> 5) & 0x7: Reserved */
      pf = (s->NAME.cmd_buf[1] >> 4) & 1;
      /* (s->NAME.cmd_buf[1] >> 1) & 0x7: Reserved */
      sp = (s->NAME.cmd_buf[1] >> 0) & 1;
      /* s->NAME.cmd_buf[2]: Reserved */
      /* s->NAME.cmd_buf[3]: Reserved */
      parameter_list_length = s->NAME.cmd_buf[4];
      /* s->NAME.cmd_buf[5]: Control */

      NAME_(phase_data_out)(s);
      NAME_(_recv)(s, parameter_list_length);
      if (pf == 1) { /* according to standard */
            int ret;
            ret = NAME_(modepage_store)(s, 6, parameter_list_length);
      } else { /* vendor specific */
            fprintf(stderr, "Got vendor specific mode select 10\n");
      }
}

static void
NAME_(mode_select_10)(struct cpssp *s)
{
      int pf;
      int sp;
      uint16_t parameter_list_length;

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0x55);
      /* (s->NAME.cmd_buf[1] >> 5) & 0x7: Reserved */
      pf = (s->NAME.cmd_buf[1] >> 4) & 1;
      /* (s->NAME.cmd_buf[1] >> 1) & 0x7: Reserved */
      sp = (s->NAME.cmd_buf[1] >> 0) & 1;
      /* s->NAME.cmd_buf[2]: Reserved */
      /* s->NAME.cmd_buf[3]: Reserved */
      parameter_list_length = (s->NAME.cmd_buf[7] << 8)
            | s->NAME.cmd_buf[8];
      /* s->NAME.cmd_buf[9]: Control */

      if (sp == 1) {
            /* save is something different than setting
             * a new value! */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            return;
      }

      NAME_(phase_data_out)(s);
      NAME_(_recv)(s, parameter_list_length);
      /* transfer completed */
#if 0
      /* mostly not interesting */
      {
            int i;
            for (i = 0; i < parameter_list_length; i++) {
                  fprintf(stderr, " 0x%02x", s->NAME.buf[i]);
                  if ((i % 8) == 7) {
                        fprintf(stderr, "\n");
                  }
            }
      }
      fprintf(stderr, "\n");
#endif
      if (pf == 1) { /* according to standard */
            int ret;
            ret = NAME_(modepage_store)(s, 10, parameter_list_length);
      } else { /* vendor specific */
            fprintf(stderr, "Got vendor specific mode select 10\n");
      }
}

static void
NAME_(mode_sense_gen)(
            struct cpssp *s,
            int cmd_len,
            int dbd,
            int pc,
            unsigned int page_code,
            uint16_t allocation_length
            )
{
      unsigned char changeables[100];
      unsigned char defaults[100];
      unsigned int offset;
      unsigned int handled = 0;
      int i; /* just for counting */

      if (pc == 3) {
            /* Saved pages not supported. */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
      }

      /*
       * Fill header.
       */
      offset = 0;

      /* Mode data length */
      /* Will be filled later. */
      if (cmd_len == 6) {
            offset += 1;
      } else {
            offset += 2;
      }

      /* Medium Type */
      /* SCSI-3 MMC 73 */
      s->NAME.buf[offset++] = 0x01; /* CD-ROM, data only, 120mm (obsolete?) */

      /* Device specific parameter */
      /* SCSI-3 MMC 73 */
      s->NAME.buf[offset++] = 0x00;

      if (cmd_len == 10) {
            /* Reserved */
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
      }

      /* Block Descriptor Lenght */
      if (cmd_len == 6) {
            s->NAME.buf[offset++] = 0x00;
      } else {
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
      }

      assert((cmd_len ==  6 && offset == 4)
          || (cmd_len == 10 && offset == 8));

      /*
       * Fill block descriptor table.
       */
      /* Nothing to do... No table... */

      /*
       * Fill mode pages.
       */
      if (page_code == 0x01
                  || page_code == 0x3f) {
            /* SCSI-3 MMC 78 */
            /* PS, Reserved, Page Code */
            s->NAME.buf[offset++] = (0 << 7) | (0 << 6) | (0x01 << 0);
            /* Parameter Length */
            s->NAME.buf[offset++] = 6;
            handled = 1; /* no need to generate check condition if handled */
            switch (pc) {
            case 0: /* Read Current */
            case 2: /* Default Values */
                  /* Error Recovery Parameter */
                  s->NAME.buf[offset++] = 0x00;
                  /* Read Retry Count */
                  s->NAME.buf[offset++] = 3;
                  /* Reserved */
                  s->NAME.buf[offset++] = 0;
                  s->NAME.buf[offset++] = 0;
                  s->NAME.buf[offset++] = 0;
                  s->NAME.buf[offset++] = 0;
                  break;
            case 1:
                  /* Read Changeable */
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  break;
            case 3: /* Saved values */
            default:
                  /* Saved pages not supported. */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            }
      }
      if (page_code == 0x05
                  || page_code == 0x3f) {
            /* SCSI-3 MMC 89 - Write Parameters */
            /* size = 0x32 + 2 */
            handled = 1; /* no need to generate check condition if handled */
            switch (pc) {
            case 0: /* Read Current */
                  if (s->NAME.mode_page_05[0] != 0x05) {
                        NAME_(get_modepage_defaults)(defaults, 0x05);
                        memcpy(&s->NAME.mode_page_05, defaults, 0x34);
                        memset(defaults, 0 , 100);
                  }
                  memcpy(&s->NAME.buf[offset], s->NAME.mode_page_05, 0x34);
                  offset += 0x34;
                  break;
            case 2: /* Default Values */
                  NAME_(get_modepage_defaults)(defaults, 0x05);
                  memcpy(&s->NAME.buf[offset], defaults, 0x34);
                  offset += 0x34;
                  memset(defaults, 0 , 100);
                  break;
            case 1: /* Read Changeable */
                  NAME_(get_modepage_changeables)(changeables, 0x05);
                  memcpy(&s->NAME.buf[offset], changeables, 0x34);
                  offset += 0x34;
                  memset(changeables, 0 , 100);
                  break;
            default:
                  /* Saved pages not supported. */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            }
      }
      if (page_code == 0x0d
                  || page_code == 0x3f) {
            /* SCSI-3 MMC 77 */
            /* PS, Reserved, Page Code */
            s->NAME.buf[offset++] = (0 << 7) | (0 << 6) | (0x0d << 0);
            /* Parameter Lenght */
            s->NAME.buf[offset++] = 6;
            handled = 1; /* no need to generate check condition if handled */
            switch (pc) {
            case 0: /* Read Current */
            case 2: /* Default Values */
                  /* Reserved */
                  s->NAME.buf[offset++] = 0x00;
                  /* Reserved, Inactivity Timer Multiplier */
                  s->NAME.buf[offset++] = (0 << 4) | (0x1 << 0);
                  /* Number Of MSF - S Units Per MSF - M Unit */
                  s->NAME.buf[offset++] = 60 / 256;
                  s->NAME.buf[offset++] = 60 % 256;
                  /* Number Of MSF - F Units Per MSF - S Unit */
                  s->NAME.buf[offset++] = 75 / 256;
                  s->NAME.buf[offset++] = 75 % 256;
                  break;
            case 1: /* Read Changeable */
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  break;
            default:
                  /* Saved pages not supported. */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            }
      }
      if (page_code == 0x0e
                  || page_code == 0x3f) {
            /* SCSI-3 MMC 75 */
            /* PS, Reserved, Page Code */
            s->NAME.buf[offset++] = (0 << 7) | (0 << 6) | (0x0e << 0);
            /* Parameter Lenght */
            s->NAME.buf[offset++] = 0x0e;
            handled = 1; /* no need to generate check condition if handled */
            switch (pc) {
            case 0: /* Read Current */
            case 2: /* Default Values */
                  /* Reserved, IMMED, SOTC, Reserved */
                  s->NAME.buf[offset++] = (0 << 3) | (1 << 2) | (0 << 1) | (0 << 0);
                  /* Reserved */
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  /* Obsolete */
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  s->NAME.buf[offset++] = 0x00;
                  /* Reserved, Output Port 0 Channel Selection */
                  s->NAME.buf[offset++] = (0 << 5) | (1 << 0);
                  /* Output Port 0 Volume */
                  s->NAME.buf[offset++] = 0xff;
                  /* Reserved, Output Port 1 Channel Selection */
                  s->NAME.buf[offset++] = (0 << 5) | (2 << 0);
                  /* Output Port 1 Volume */
                  s->NAME.buf[offset++] = 0xff;
                  /* Reserved, Output Port 2 Channel Selection */
                  s->NAME.buf[offset++] = (0 << 5) | (0 << 0);
                  /* Output Port 2 Volume */
                  s->NAME.buf[offset++] = 0x00;
                  /* Reserved, Output Port 3 Channel Selection */
                  s->NAME.buf[offset++] = (0 << 5) | (0 << 0);
                  /* Output Port 3 Volume */
                  s->NAME.buf[offset++] = 0x00;
                  break;
            case 1: /* Read Changeable */
                  for (i=0; i < 14; i++) {
                        s->NAME.buf[offset++] = 0x00;
                  }
                  break;
            default:
                  /* Saved pages not supported. */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            }
      }
      if (page_code == 0x2a
                  || page_code == 0x3f) {

            /* CD capabilities and mechanical status page */
            /* SCSI-3 MMC pp. 79-82 */

            handled = 1; /* no need to generate check condition if handled */

            /* byte 0 */
            s->NAME.buf[offset++] =      (0 << 7) /* PS not supported */
                              |    (0 << 6) /* reserved */
                              | (0x2a << 0) /* Page Code */
                              ;
            /* byte 1 */
            s->NAME.buf[offset++] = 0x14; /* page length */

            switch (pc) {
            case 0: /* Read Current */
            case 2: /* Default Values */
                  /* byte 2 */
                  s->NAME.buf[offset++] =   (0 << 3) /* reserved */
                                    | (0 << 2) /* Method 2 */
                                    | (1 << 1) /* read CD-RW */
                                    | (1 << 0) /* read CD-R */
                                    ;
                  /* byte 3 */
                  s->NAME.buf[offset++] =   (0 << 3)  /* reserved */
#if CD_WRITER_SUPPORT
                  /* test write is mandatory here, otherwise windows will
                   * not recognize the cd-media */
                                    | (1 << 2) /* Test Write */
                                    | (1 << 1) /* write CD-RW */
                                    | (1 << 0) /* write CD-R */
#else
                                    | (0 << 2) /* Test Write */
                                    | (0 << 1) /* write CD-RW */
                                    | (0 << 0) /* write CD-R */
#endif
                                    ;
                  /* byte 4 */
                  s->NAME.buf[offset++] =   (0 << 7) /* reserved */
                                    | (1 << 6) /* Multi Session */
                                    | (0 << 5) /* Mode 2 Form 2 */
                                    | (0 << 4) /* Mode 2 Form 1 */
                                    | (0 << 3) /* Digital Port 2 */
                                    | (0 << 2) /* Digital Port 1 */
#if CD_AUDIO_SUPPORT
                                    | (1 << 1) /* Composite */
                                    | (1 << 0) /* Audio Play */
#else
                                    | (0 << 1) /* Composite */
                                    | (0 << 0) /* Audio Play */
#endif
                                    ;
                  /* byte 5 */
                  s->NAME.buf[offset++] =   (0 << 7)  /* Read Bar Code */
                                    | (1 << 6)  /* UPC */
#if CD_AUDIO_SUPPORT 
                                    | (1 << 5) /* ISRC */
                                    | (1 << 4) /* C2 Pointers */
                                    | (1 << 3) /* R-W De-interleaved & corr. */
                                    | (1 << 2) /* R-W Supported */
                                    | (1 << 1) /* CD-DA Stream Accurate */
                                    | (1 << 0) /* CD-DA Commands Supported */
#else
                                    | (0 << 5) /* ISRC */
                                    | (0 << 4) /* C2 Pointers */
                                    | (0 << 3) /* R-W De-interleaved & corr. */
                                    | (0 << 2) /* R-W Supported */
                                    | (0 << 1) /* CD-DA Stream Accurate */
                                    | (0 << 0) /* CD-DA Commands Supported */
#endif
                                    ;
                  /* byte 6 */
                  s->NAME.buf[offset++] =   (1 << 5) /* Loading Mechanism: tray */
                                    | (0 << 4) /* reserved */
                                    | (1 << 3) /* Eject (via START STOP) */
                                    | (0 << 2) /* Prevent Jumper */
                                    | (s->NAME.locked << 1) /* Lock State */
                                    | (1 << 0) /* Lock possible */
                                    ;
                  /* byte 7 */
                  s->NAME.buf[offset++] =   (0 << 4) /* reserved */
                                    | (0 << 3) /* S/W Slot Selection */
                                    | (0 << 2) /* Changer Supports Disc Present */
#if CD_AUDIO_SUPPORT
                                    | (1 << 1) /* Separate Channel Mute */
                                    | (1 << 0) /* Separate volume levels */
#else
                                    | (0 << 1) /* Separate Channel Mute */
                                    | (0 << 0) /* Separate volume levels */
#endif
                                    ;
                  /* byte 8/9 */
                  s->NAME.buf[offset++] = 0x05; /* max read speed */
                  s->NAME.buf[offset++] = 0x83;

                  /* byte 10/11 */
                  s->NAME.buf[offset++] = 0x01; /* # of volume levels */
                  s->NAME.buf[offset++] = 0x00;

                  /* byte 12/13 */
                  s->NAME.buf[offset++] = 0x00; /* buffer size */
                  s->NAME.buf[offset++] = 0x00;

                  /* byte 14/15 */
                  s->NAME.buf[offset++] = 0x05; /* current read speed */
                  s->NAME.buf[offset++] = 0x83;

                  /* byte 16 */
                  s->NAME.buf[offset++] = 0x00; /* reserved */

                  /* byte 17 */
                  s->NAME.buf[offset++] = 0x00; /* digital out control */
#if CD_WRITER_SUPPORT
                  /* byte 18/19 */
                  s->NAME.buf[offset++] = 0x05; /* max write speed */
                  s->NAME.buf[offset++] = 0x83;

                  /* byte 20/21 */
                  s->NAME.buf[offset++] = 0x05; /* curr write speed */
                  s->NAME.buf[offset++] = 0x83;
#else
                  /* byte 18/19 */
                  s->NAME.buf[offset++] = 0x00; /* max write speed */
                  s->NAME.buf[offset++] = 0x00;

                  /* byte 20/21 */
                  s->NAME.buf[offset++] = 0x00; /* curr write speed */
                  s->NAME.buf[offset++] = 0x00;
#endif
                  break;
            case 1: /* Read Changeable */
                  for(i=0; i<38; i++) {
                        s->NAME.buf[offset++] = 0x00;
                  }
                  break;
            default:
                  /* Saved pages not supported. */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
            }
      }
      if (! handled) {
            /* if it was no implemented mode page */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
      }
      /* Patch data length */
      if (cmd_len == 10) {
            s->NAME.buf[0] = (offset - 2) / 256;
            s->NAME.buf[1] = (offset - 2) % 256;
      } else {
            s->NAME.buf[0] = offset - 2;
      }

      if (offset < allocation_length) {
            allocation_length = offset;
      }

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, allocation_length);
}

/*
 * SCSI-3 SPC 47
 *
 * 0x1a, mandatory
 */
static void
NAME_(mode_sense_6)(struct cpssp *s)
{
      int dbd;
      int pc;
      unsigned int page_code;
      uint8_t allocation_length;

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0x1a);
      /* (s->NAME.cmd_buf[1] >> 4) & 0xf: Reserved */
      dbd = (s->NAME.cmd_buf[1] >> 3) & 0x1;
      /* (s->NAME.cmd_buf[1] >> 0) & 0x7: Reserved */
      pc = (s->NAME.cmd_buf[2] >> 6) & 0x3;
      page_code = (s->NAME.cmd_buf[2] >> 0) & 0x3f;
      /* s->NAME.cmd_buf[3]: Reserved */
      allocation_length = s->NAME.cmd_buf[4];
      /* s->NAME.cmd_buf[5]: Control */

      NAME_(mode_sense_gen)(s, 6, dbd, pc, page_code, allocation_length);
}

/*
 * SCSI-3 SPC 50
 *
 * 0x5a, mandatory
 */
static void
NAME_(mode_sense_10)(struct cpssp *s)
{
      int dbd;
      int pc;
      unsigned int page_code;
      uint16_t allocation_length;

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0x5a);
      /* (s->NAME.cmd_buf[1] >> 4) & 0xf: Reserved */
      dbd = (s->NAME.cmd_buf[1] >> 3) & 0x1;
      /* (s->NAME.cmd_buf[1] >> 0) & 0x7: Reserved */
      pc = (s->NAME.cmd_buf[2] >> 6) & 0x3;
      page_code = (s->NAME.cmd_buf[2] >> 0) & 0x3f;
      /* s->NAME.cmd_buf[3]: Reserved */
      /* s->NAME.cmd_buf[4]: Reserved */
      /* s->NAME.cmd_buf[5]: Reserved */
      /* s->NAME.cmd_buf[6]: Reserved */
      allocation_length = (s->NAME.cmd_buf[7] << 8) | (s->NAME.cmd_buf[8] << 0);
      /* s->NAME.cmd_buf[9]: Control */

      if (pc == 3) {
            /* CHECK CONDITION status
             * ILLEGAL REQUEST sense key
             * SAVING PARAMETERS NOT SUPPORTED additional sense
             */
      } else {
            NAME_(mode_sense_gen)(s, 10, dbd, pc, page_code, allocation_length);
      }
}

/*
 * SCSI-3 SPC ???
 *
 * 0x1e, mandatory
 */
static void
NAME_(prevent_allow_medium_removal)(struct cpssp *s)
{
      s->NAME.locked = s->NAME.cmd_buf[4] & 0x01;

      if (! s->NAME.locked) {
            /* Close storage to allow medium change. */
            NAME_(close_file)(s);
      }
}

/*
 * SCSI-3 SPC ???
 *
 * 0x57, mandatory
 */
static void
NAME_(release_10)(struct cpssp *s)
{
      fixme();
}

/*
 * ATAPI working draft Rev 2.5 Page 171
 *
 * 0x03, mandatory
 */
static void
NAME_(request_sense)(struct cpssp *s)
{
      struct request_sense *sns = (struct request_sense *) s->NAME.buf;
      unsigned int length = s->NAME.cmd_buf[4]; /* Allocation Length */

      memset(sns, 0, length);

      sns->valid = 1;
      sns->error_code = 0x70;
      sns->sense_key = s->NAME.sense_key;
      sns->add_sense_len = 11;
      sns->asc = s->NAME.asc;
      sns->ascq = s->NAME.ascq;

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, length);
}

/*
 * SCSI-3 SPC ???
 *
 * 0x56, mandatory
 */
static void
NAME_(reserve_10)(struct cpssp *s)
{
      fixme();
}

/*
 * SCSI-3 SPC ???
 *
 * 0x0b, mandatory
 */
static void
NAME_(seek_6)(struct cpssp *s)
{
      fixme();
}

/*
 * SCSI-3 SBC P.51
 *
 * 0x2b, mandatory
 */
static void
NAME_(seek_10)(struct cpssp *s)
{
      /* Nothing to do... */
}

/*
 * SCSI-3 SPC ???
 *
 * 0x1d, mandatory
 */
static void
NAME_(send_diagnostic)(struct cpssp *s)
{
      fixme();
}

/*
 * SCSI-3 SBC-3 P.72
 *
 * 0x1b, mandatory
 */
static void
NAME_(start_stop_unit)(struct cpssp *s)
{
      switch (s->NAME.cmd_buf[4] & 0x03) {
      case 0x00: /* Stop the Disc */
      case 0x01: /* Start the Disc and read the TOC */
            /* Not implemented right now */
#if 0
            assert(0);
#endif
            /* FIXME Read the specs and handle not inserted media
             * --tg 15:50 04-07-12 */
            break;

      case 0x02: /* Eject the Disc if possible */
            /* FIXME I don't think this is clean, rework */
            NAME_(close_file)(s);
            s->NAME.locked = 0;
            /*FALLTHROUGH*/

      case 0x03: /* Load the Disc */
            break;

      default:
            /* Never reached */
            assert(0);
      }
}

/*
 * SCSI-3 SPC 95
 */
static void
NAME_(test_unit_ready)(struct cpssp *s) /* 0x00, mandatory */
{
      if (s->NAME.media_inserted == 0) {
            /* Removed Media */
            NAME_(medium_not_present)(s);
            return;
      }
}

/*
   the next function will tell recording software what our drive can do.
   mmc specs define profiles for various device types and features that have to beimplemented for every profile.
   our drive is a cd-r, profile 0009h
   these are the features required:
0000h: Profile list
0001h: Core
0002h: Morphing
0003h: Removable Medium
0010h: Random Readable, PP=1
001Eh: CD Read
0021h: Incremental Streaming Writable
002Dh: CD Track at Once
0100h: Power Management
0105h: Time-out
0107h: Real-Time Streaming
*/

static void
NAME_(get_configuration)(struct cpssp *s) /* 0x46 */
{
      unsigned char RT;
      unsigned short sfn, allocation_length;
      unsigned char buffer[100];
      unsigned char *pointer;
      enum {
            NONE,
            CD_RO,
            CD_WO,
            CD_RW,
      } medium;

      RT = s->NAME.cmd_buf[1] & 0x03;
      sfn = (s->NAME.cmd_buf[2] << 8) | s->NAME.cmd_buf[3];
      allocation_length = (s->NAME.cmd_buf[7] << 8) | s->NAME.cmd_buf[8];

#if DEBUGPCOM
      fprintf(stderr, "gc: RT=%d, sfn=0x%04x, al=%d\n", RT, sfn, allocation_length);
#endif

      memset(s->NAME.buf, 0, allocation_length);

      if (s->NAME.media_inserted == 0) {
            s->NAME.buf[7] = 0x00;
            medium = NONE;
      } else if (s->NAME.media_desc.media_type == 1) {
            s->NAME.buf[7] = 0x0A;
            medium = CD_RW;
      } else {
            s->NAME.buf[7] = 0x09;
            medium = CD_WO;
      }
#if 0
      /* As we only have selfmade images, there is nothing like CD_RO for us */
      /* CD_RO is only for stamped cds */
      else {
            s->NAME.buf[7] = 0x08;
            medium = CD_RO;
      }
#endif

      memset(buffer, 0, 100);
      pointer = &(s->NAME.buf[8]);

      switch (sfn) {
      case 0x0000:
            // Feature 0000h: Profile list
            // 8 bytes
            buffer[1] = 0x00;
            buffer[2] = 0x03; // 00000011b
            buffer[3] = 4*3; // additional length after 4 byte header
            buffer[5] = 0x0A; // cd-rw
            buffer[6] = (medium == CD_RW) ? 1 : 0;
            buffer[9] = 0x09; // cd-r write once
            buffer[10] = (medium == CD_WO) ? 1 : 0;
            buffer[13] = 0x08; // cd-r read only
            buffer[14] = (medium == CD_RO) ? 1 : 0;
            memcpy(pointer, buffer, 16); pointer+=16; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0001:
            // Feature 0001h: Core
            // 8 bytes
            buffer[1] = 0x01;
            buffer[2] = 0x03; // 00000011b
            buffer[3] = 0x04; // additional length after 4 byte header
            buffer[7] = 0x01; // scsi /* ATTENTION! 0x02 for atapi */
            memcpy(pointer, buffer, 8); pointer+=8; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0002:
            // Feature 0002h: Morphing
            // 8 bytes
            buffer[1] = 0x02;
            buffer[2] = 0x03; // 00000011b
            buffer[3] = 0x04; // additional length after 4 byte header
            memcpy(pointer, buffer, 8); pointer+=8; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0003:
            // Feature 0003h: Removable Medium
            // 8 bytes
            buffer[1] = 0x03;
            buffer[2] = 0x03; // 00000011b
            buffer[3] = 0x04; // additional length after 4 byte header
            buffer[4] = 0x29; // 00101001b
            memcpy(pointer, buffer, 8); pointer+=8; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0004 ... 0x000F:
            buffer[0] = (sfn >> 8) % 256;
            buffer[1] = (sfn >> 0) % 256;
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0010:
            // Feature 0010h: Random Readable, PP=1
            // 12 bytes
            buffer[1] = 0x10;
            if (s->NAME.media_inserted == 0 || s->NAME.toc.xA1.pmin == 0) { /* empty or no media */
                  buffer[2] = 0x00;
                  buffer[9] = 0x00;
            } else {
                  buffer[2] = 0x01;
                  buffer[9] = 0x10;
            }
            buffer[3] = 0x08; // additional length after 4 byte header
            buffer[6] = (2048 & 0xff00) >> 8;
            buffer[7] = 2048 & 0xff;
            buffer[10] = 0x01; // PP: Page Present, defined as 1 for this profile
            memcpy(pointer, buffer, 12); pointer+=12; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0011 ... 0x001c:
            if (RT == 2) {
                  buffer[0] = (sfn >> 8) % 256;
                  buffer[1] = (sfn >> 0) % 256;
                  memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
                  break;
            }
      case 0x001d:
            // Feature 001dh: Multi-Read
            // 4 bytes
            buffer[1] = 0x1D;
            buffer[2] = 0x00; // 00000000b
            buffer[3] = 0x00; // additional length after 4 byte header
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x001e:
            // Feature 001Eh: CD Read
            // 4 bytes
            buffer[1] = 0x1E;
            if (s->NAME.media_inserted == 0 || s->NAME.toc.xA1.pmin == 0) { /* empty or no media */
                  buffer[2] = 0x04; // 00000100b
            } else {
                  buffer[2] = 0x05; // 00000101b
            }
            buffer[3] = 0x04; // additional length after 4 byte header
            buffer[4] = 0x00; // 00000000b
            memcpy(pointer, buffer, 8); pointer+=8; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x001f:
            // Feature 001fh: DVD read
            // 4 bytes
            buffer[1] = 0x1F;
            if (s->NAME.media_inserted == 0 || s->NAME.toc.xA1.pmin == 0) { /* empty or no media */
                  buffer[2] = 0x00; // 00000000b
            } else {
                  buffer[2] = 0x01; // 00000001b /* FIXME read dvd structure command missing */
            }
            buffer[3] = 0x00; // additional length after 4 byte header
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0020:
            // Feature 0020h: random writeable
            buffer[1] = 0x20;
            buffer[2] = 0x04; // 00000100b
            buffer[3] = 0x0C; // additional length after 4 byte header
            buffer[10] = (2048 & 0xff00) >> 8;
            buffer[11] = 2048 & 0xff;
            memcpy(pointer, buffer, 16); pointer+=16; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0021:
            // Feature 0021h: Incremental Streaming Writable
            /* mandatory for cd-r and cd-rw */
            // 12 bytes
            buffer[1] = 0x21;
            if (medium == CD_RW || medium == CD_WO) { /* writeable media */
                  buffer[2] = 0x05;
                  buffer[8] = 7;
            } else {
                  buffer[2] = 0x04;
                  buffer[8] = 0;
            }
            buffer[3] = 0x08; // additional length after 4 byte header
            buffer[7] = 0; // number of link sizes, 1 for cd, 2 for dvd
            // 9-11 padding
            memcpy(pointer, buffer, 12); pointer+=12; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0022 ... 0x0025:
            if (RT == 2) {
                  buffer[0] = (sfn >> 8) % 256;
                  buffer[1] = (sfn >> 0) % 256;
                  memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
                  break;
            }
      case 0x0026:
            // Feature 0026h: Restricted Overwrite
            // 4 bytes
            buffer[1] = 0x26;
            if (medium == CD_RW) { /* rewriteable media */
                  buffer[2] = 0x01; // 00000001b
            }
      case 0x0027 ... 0x002c:
            if (RT == 2) {
                  buffer[0] = (sfn >> 8) % 256;
                  buffer[1] = (sfn >> 0) % 256;
                  memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
                  break;
            }
      case 0x002d:
            // Feature 002dh: CD Track at Once
            // 8 bytes
            buffer[1] = 0x2d;
            if (medium == CD_RW || medium == CD_WO) { /* writeable media */
                  buffer[2] = 0x01;
            } else {
                  buffer[2] = 0x00;
            }
            buffer[3] = 0x04; // additional length after 4 byte header
            buffer[4] = 0x04; // 00000100b
#if 0
            /* could be 0x06 with CD-RW Medium - elaborate */:
                  buffer[4] = 0x06; // 00000100b 
#endif
            // 5,6,7: data type supported bitmap
            memcpy(pointer, buffer, 8); pointer+=8; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
#if 0 /* no cd-mastering yet, because it implies send cue sheet */
      case 0x002e:
            // Feature 002eh: CD Mastering
            // 8 bytes
            buffer[1] = 0x2e;
            if (medium == CD_RW || medium == CD_WO) { /* writeable media */
                  buffer[2] = 0x01;
            } else {
                  buffer[2] = 0x00;
            }
            buffer[3] = 0x04; // additional length after 4 byte header
            buffer[4] = 0x3F; // 00111111b
            buffer[6] = 0x0d; // maximum cue sheet length 0xd00
            memcpy(pointer, buffer, 8); pointer+=8; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
#endif
      case 0x002e ... 0x00ff:
            if (RT == 2) {
                  buffer[0] = (sfn >> 8) % 256;
                  buffer[1] = (sfn >> 0) % 256;
                  memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
                  break;
            }
      case 0x0100:
            // Feature 0100h: Power Management
            // 4 bytes
            buffer[0] = 0x01;
            buffer[1] = 0x00;
            buffer[2] = 0x03;
            buffer[3] = 0x00;
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0101 ... 0x0104:
            if (RT == 2) {
                  buffer[0] = (sfn >> 8) % 256;
                  buffer[1] = (sfn >> 0) % 256;
                  memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
                  break;
            }
      case 0x0105:
            // Feature 0105h: Time-out
            // 4 bytes
            buffer[0] = 0x01;
            buffer[1] = 0x05;
            buffer[2] = 0x03;
            buffer[3] = 0x00;
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0106:
            // Feature 0106h: DVD-CSS /* not supported */
            buffer[0] = 0x01;
            buffer[1] = 0x06;
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x0107:
            // Feature 0107h: Real-Time Streaming
            // 4 bytes
            buffer[0] = 0x01;
            buffer[1] = 0x07;
            buffer[2] = 0x03;
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      case 0x010a:
            // Feature 010Ah: Disc Control Blocks (for DVD)
            // 4 bytes
            buffer[0] = 0x01;
            buffer[1] = 0x0A;
            memcpy(pointer, buffer, 4); pointer+=4; memset(buffer, 0, 100);
            if (RT == 2) {
                  break;
            }
      }

      s->NAME.buf[0] = ((pointer - &(s->NAME.buf[8])) >> 24) % 256;
      s->NAME.buf[1] = ((pointer - &(s->NAME.buf[8])) >> 16) % 256;
      s->NAME.buf[2] = ((pointer - &(s->NAME.buf[8])) >> 8) % 256;
      s->NAME.buf[3] = ((pointer - &(s->NAME.buf[8])) >> 0) % 256;

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, allocation_length);
}

#if CD_CHANGER_SUPPORT
/*
 * SCSI-3 MMC 25
 *
 * 0xa6, optional
 */
static void
NAME_(load_unload)(struct cpssp *s)
{
      fixme();
}
#endif

/*
 * SCSI-3 MMC 27
 *
 * 0xbd, mandatory
 */
static void
NAME_(mechanism_status)(struct cpssp *s)
{
      uint16_t allocation_length;

      fixme();

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0xbd);
      /* s->NAME.cmd_buf[1]: Reserved */
      /* s->NAME.cmd_buf[2]: Reserved */
      /* s->NAME.cmd_buf[3]: Reserved */
      /* s->NAME.cmd_buf[4]: Reserved */
      /* s->NAME.cmd_buf[5]: Reserved */
      /* s->NAME.cmd_buf[6]: Reserved */
      /* s->NAME.cmd_buf[7]: Reserved */
      allocation_length = (s->NAME.cmd_buf[8] << 8) | (s->NAME.cmd_buf[9] << 0);
      /* s->NAME.cmd_buf[10]: Reserved */
      /* s->NAME.cmd_buf[11]: Control */

      /* FIXME VOSSI */
}

#if CD_AUDIO_SUPPORT
/*
 * SCSI-3 MMC 30
 *
 * 0x4b, mandatory for audio
 */
static void
NAME_(pause_resume)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_AUDIO_SUPPORT
/*
 * SCSI-3 MMC 31
 *
 * 0x45, mandatory for audio
 */
static void
NAME_(play_audio_10)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_AUDIO_SUPPORT
/*
 * SCSI-3 MMC 32
 *
 * 0xa5, mandatory for audio
 */
static void
NAME_(play_audio_12)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_AUDIO_SUPPORT
/*
 * SCSI-3 MMC 33
 *
 * 0xa7, mandatory for audio
 */
static void
NAME_(play_audio_msf)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_PLAY_CD_SUPPORT
/*
 * SCSI-3 MMC 34
 *
 * 0xbc, optional
 */
static void
NAME_(play_cd)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_READ_CD_SUPPORT
/*
 * SCSI-3 MMC 36
 *
 * 0xbe, optional
 */
static void
NAME_(read_cd)(struct cpssp *s)
{
      assert(/* s->NAME.cmd_buf[1]  == 0 FIXME similang
              && */ s->NAME.cmd_buf[2]  == 0
                  && s->NAME.cmd_buf[3]  == 0
                  && s->NAME.cmd_buf[4]  == 0
                  && s->NAME.cmd_buf[5]  == 0
                  && s->NAME.cmd_buf[6]  == 0
                  && s->NAME.cmd_buf[7]  == 0
                  && s->NAME.cmd_buf[8]  == 0
                  && s->NAME.cmd_buf[9]  == 0
                  && s->NAME.cmd_buf[10] == 0
                  && s->NAME.cmd_buf[11] == 0);
}
#endif

#if CD_READ_CD_SUPPORT
/*
 * SCSI-3 MMC 44
 *
 * 0xb9, optional
 */
static void
NAME_(read_cd_msf)(struct cpssp *s)
{
      fixme();
}
#endif

/*
 * SCSI-3 MMC 45
 *
 * 0x25, mandatory
 */
static void
NAME_(read_cd_recorded_capacity)(struct cpssp *s)
{
      unsigned int area;
      int64_t pos = 0;
      struct cap {
            uint32_t lba;
            uint32_t len;
      } *cap = (struct cap *) s->NAME.buf;


      if (s->NAME.media_inserted == 0) {
            /* Removed Media */
            NAME_(medium_not_present)(s);
            return;
      }

      if (s->NAME.faum_image) {
            /* FIXME
             * if there is a B0 - Pointer, follow it.
             * we need the last completed session
             */
            /* Start position of lead-out minus 1 is the last accessable
             * block.
             */
            NAME_(msf_to_lba)(&(s->NAME.toc.xA2.pmin), &pos);
            pos--;
            if ( pos < 0 ) {
                  pos = 0;
            }
            cap->lba = cpu_to_be32((uint32_t) pos);
      } else {
            for (area = 0; area < s->NAME.nareas; area++) {
                  switch (s->NAME.area[area].type) {
                  case CD_LEADIN:
                        break;
                  case CD_PREGAP:
                  case CD_AUDIO:
                  case CD_DATA:
                  case CD_POSTGAP:
                        pos += s->NAME.area[area].size;
                        break;
                  case CD_LEADOUT:
                        break;
                  }
            }
            cap->lba = cpu_to_be32((uint32_t) (pos / 2048 - 1));
      }

#if DEBUGPCOM
      fprintf(stderr, "Last accessible block is: %"PRIi64"\n", pos);
#endif
      /* FIXME this should be set according to blocksize */
      cap->len = cpu_to_be32(2048);

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, 8);
}

#if 0 /* obsolete */
/*
 * SCSI-3 MMC 47
 *
 * 0x44, mandatory
 */
static void
NAME_(read_header)(struct cpssp *s)
{
      fixme();
}
#endif

/*
 * SCSI-3 MMC 49
 *
 * 0x42, mandatory
 */
static void
NAME_(read_subchannel)(struct cpssp *s)
{
      unsigned int msf;
      unsigned int subq;
      unsigned int parameter_list;
      unsigned int offset = 0;
      uint16_t allocation_length;

      if (s->NAME.media_inserted == 0) {
            /* Removed Media */
            NAME_(medium_not_present)(s);
            return;
      }

      msf = (s->NAME.cmd_buf[1] >> 1) & 1;
      subq = (s->NAME.cmd_buf[2] >> 6) & 1;
      parameter_list = s->NAME.cmd_buf[3];
      allocation_length = (s->NAME.cmd_buf[7] << 8) | (s->NAME.cmd_buf[8] << 0);

      memset(s->NAME.buf, 0, allocation_length);

      /* prepare sub-q channel data header */

      s->NAME.buf[offset++] = 0; /* reserved */
      s->NAME.buf[offset++] = 0; /* audio status */
      s->NAME.buf[offset++] = 0; /* sub-channel data length msb */
      s->NAME.buf[offset++] = 0; /* sub-channel data length lsb */

#if CD_AUDIO_SUPPORT
      if(subq) {
            /* return sub-q data, too
             * (only needed if audio is supported)
             */
            fixme();
      }
#endif

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, allocation_length);
}

/*
 * SCSI-3 MMC 56
 *
 * 0x43, mandatory
 */
static void
NAME_(read_toc_pma_atip)(struct cpssp *s)
{
      unsigned int msf;
      unsigned int format;
      unsigned int first_reported_track;
      unsigned int offset = 0;
      uint16_t allocation_length;
      int64_t lba = 0;
      unsigned int tracks_to_report;
      unsigned int last_reported_track;
      unsigned long blk;
      unsigned int area;
      struct atapi_toc_header *hdr;
      struct atapi_toc_entry *entry;
      struct atapi_toc_q_entry *ttoc_base;
      struct atapi_toc_q_entry *ttoc_current;
      unsigned int session_open=0;
      unsigned int found_track=0;
      unsigned int first_track=0;
      unsigned int sessions=0;
      unsigned int last_session=0;
      unsigned int first_session_track=0;
      unsigned int last_session_track=0;


      if (s->NAME.media_inserted == 0) {
            /* Removed Media */
            NAME_(medium_not_present)(s);
            return;
      }

      /*
       * Get info from the CDB.
       */
      msf = (s->NAME.cmd_buf[1] >> 1) & 1;
      format = (s->NAME.cmd_buf[2] >> 0) & 0xf;
      if(format == 0) {
            /* look for SFF8020, version 1.2 format code */
            format = (s->NAME.cmd_buf[9] >> 6) & 3;
      }
      first_reported_track = s->NAME.cmd_buf[6];
      allocation_length = (s->NAME.cmd_buf[7] << 8) | (s->NAME.cmd_buf[8] << 0);

      if (s->NAME.faum_image) {
            if (s->NAME.toc.xA1.pmin == 0 /* empty disc */
                        && ( format == 0 || format == 4 || format == 8 )) {
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
                  return;
            }

#if DEBUGPCOM
            fprintf(stderr, "msf: %u, format: %u, first: %u\n", msf, format, first_reported_track);
#endif

            if (format == 0x00) {
                  int i;
                  s->NAME.buf[offset++] = 0; /* filled at bottom */
                  s->NAME.buf[offset++] = 0; /* filled at bottom */
                  /* first track No. in first completed session */
                  /* FIXME
                   * if it is a multisession disk, clear the first_reported
                   * track field (if it is not 0xAA)
                   */
                  s->NAME.buf[offset++] = s->NAME.toc.xA0.pmin;

                  /* if first reported track is less then first track on disc, set it to the latter */
                  if (first_reported_track < s->NAME.toc.xA0.pmin) {
                        first_reported_track = s->NAME.toc.xA0.pmin;
                  }
                  /* FIXME
                   * if there is a B0 - Pointer, follow it.
                   * we need the last track of the last completed session
                   */
                  s->NAME.buf[offset++] = s->NAME.toc.xA1.pmin;

                  for (i = first_reported_track - 1; i < s->NAME.toc.xA1.pmin; i++) {
                        toc_entry *act_toc_line = &(s->NAME.toc.x01) + i;
                        s->NAME.buf[offset++] = 0; /* reserved */
                        s->NAME.buf[offset++] = ((act_toc_line->adr << 4) & 0xF0)/* adr */
                              | (act_toc_line->ctrl & 0x0F); /* control */
                        /* FIXME should be in hex */
                        s->NAME.buf[offset++] = act_toc_line->point;
                        s->NAME.buf[offset++] = 0; /* reserved */
                        if (msf) {
                              s->NAME.buf[offset++] = 0; /* reserved */
                              s->NAME.buf[offset++] = act_toc_line->pmin;
                              s->NAME.buf[offset++] = act_toc_line->psec;
                              s->NAME.buf[offset++] = act_toc_line->pframe;
                        } else {
                              NAME_(msf_to_lba)(&(act_toc_line->pmin),&lba);
                              s->NAME.buf[offset++] = (lba >> 24) % 256;
                              s->NAME.buf[offset++] = (lba >> 16) % 256;
                              s->NAME.buf[offset++] = (lba >>  8) % 256;
                              s->NAME.buf[offset++] = (lba >>  0) % 256;
                        }
                  }
            }

      } else { /* s->NAME.faum_image */

            if (s->NAME.nareas == 0 /* empty disc */
                        && (format == 0 || format == 4 || format == 8)) {
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* INVALID FIELD IN CDB */
                  return;
            }

            tracks_to_report = 0;
            last_reported_track = 0;
            for (area = 0; area < s->NAME.nareas; area++) {
                  switch (s->NAME.area[area].type) {
                  case CD_LEADIN:
                        session_open = 1;
                        break;
                  case CD_PREGAP:
                        break;
                  case CD_AUDIO:
                  case CD_DATA:
                        if (s->NAME.area[area].indexnum == 1) {
                              unsigned int tracknum
                                    = s->NAME.area[area].tracknum;

                              if (first_reported_track == 0) {
                                    first_reported_track = tracknum;
                              }
                              if (first_reported_track <= tracknum) {
                                    tracks_to_report++;
                              }
                              if (last_reported_track < tracknum) {
                                    last_reported_track = tracknum;
                              }

                              if(!found_track && session_open) {
                                    found_track = tracknum;
                              }
                        }
                        break;
                  case CD_POSTGAP:
                        break;
                  case CD_LEADOUT:
                        if(session_open) {
                              session_open = 0;
                              if(found_track) {
                                    first_track = found_track;
                                    found_track = 0;
                              }
                              sessions++;
                        }
                        break;
                  }
            }
            tracks_to_report++; /* Leadout */
            last_session = sessions - (session_open ? 1 : 0);

            /*
             * Write TOC header data to buffer.
             */
            hdr = (struct atapi_toc_header *) s->NAME.buf;
            switch(format) {
                  case 0x00:
                        hdr->toc_length = cpu_to_be16(sizeof(*hdr) - sizeof(hdr->toc_length)
                                    + tracks_to_report * sizeof(*entry));
                        hdr->first_track = first_reported_track;
                        hdr->last_track = last_reported_track;
                        break;
                  case 0x01:
                        /* See MMC-11.0 p. 59 */
                        hdr->toc_length = cpu_to_be16(0x0a); /* format 1 is always 10 bytes long */
                        hdr->first_track = 1;              /* first complete session */
                        hdr->last_track = last_session;    /* last complete session */
                        break;
                  case 0x02:
                        /* See MMC-11.0 pp. 60ff. */
                        hdr->first_track = 1;           /* first complete session */
                        hdr->last_track = last_session; /* last complete session */
                        break;
                  case 0x03:
                  case 0x04:
                        hdr->first_track = 0; /* reserved */
                        hdr->last_track = 0;  /* reserved */
                        break;
                  default:
                        assert(0);
            }

            /*
             * Write TOC entry data to buffer.
             */
            entry = (struct atapi_toc_entry *) (hdr + 1);
            ttoc_current = (struct atapi_toc_q_entry *) (hdr + 1);
            ttoc_base = NULL;
            blk = 0;
            sessions = 0;
            for (area = 0; area < s->NAME.nareas; area++) {
                  switch (s->NAME.area[area].type) {
                  case CD_LEADIN:
                        sessions++;

                        if(format == 0x02 && sessions >= first_reported_track) {
                              first_session_track = 0;
                              last_session_track = 0;
                              ttoc_base = ttoc_current;
                              ttoc_current = ttoc_base + 3;
                              break;
                        }
                  case CD_PREGAP:
                  case CD_POSTGAP:
                        goto skip;
                  case CD_AUDIO:
                  case CD_DATA:
                  case CD_LEADOUT:
                        break;
                  default:
                        assert(0);
                  }

                  /* Clear "reserved" fields. */
                  memset(entry, 0, sizeof(*entry));

                  /* Fill info. */
                  switch (format) {
                  case 0x0:
                        entry->track = s->NAME.area[area].tracknum;
                        if (msf) {
                              entry->addr.msf.minute = (blk / 75) / 60;
                              entry->addr.msf.second = (blk / 75) % 60;
                              entry->addr.msf.frame = blk % 75;
                        } else {
                              if(s->NAME.area[area].type != CD_LEADOUT) {
                                    /* LBA 0 must always be at MSF 00:02:00 */
                                    assert(150 <= blk);
                                    entry->addr.lba = cpu_to_be32(blk - 150);
                              } else {
                                    /* SCSI-3 MMC, p. 9 allows the assumption
                                     * that the last post gap before lead out
                                     * has to be skipped, too */
                                    assert(300 <= blk);
                                    entry->addr.lba = cpu_to_be32(blk - 300);
                              }
                        }
                        switch (s->NAME.area[area].type) {
                        case CD_AUDIO:
                              fixme();
                              break;
                        case CD_DATA:
                              entry->control = 4; /* Data track */
                              entry->adr = 1;     /* Cur pos data */
                              break;
                        case CD_LEADOUT:
                              entry->control = 4; /* Data track */
                              entry->adr = 1;     /* Cur pos data */
                              break;
                        default:
                              assert(0);
                        }
                        break;
                  case 0x01:
                        /* assemble session info:
                         * search for last complete session
                         */
                        if(sessions == last_session && s->NAME.area[area].tracknum == first_track) {
                              s->NAME.buf[6] = first_track;
                              if(msf) {
                                    s->NAME.buf[9]  = (blk / 75) / 60;
                                    s->NAME.buf[10] = (blk / 75) % 60;
                                    s->NAME.buf[11] =  blk % 75;
                              } else {
                                    if(s->NAME.area[area].type != CD_LEADOUT) {
                                          assert(blk >= 150);
                                          lba = cpu_to_be32(blk - 150);
                                    } else {
                                          assert(blk >= 300);
                                          lba = cpu_to_be32(blk - 300);
                                    }
                                    s->NAME.buf[8]  = (lba >> 24) % 256;
                                    s->NAME.buf[9]  = (lba >> 16) % 256;
                                    s->NAME.buf[10] = (lba >> 8)  % 256;
                                    s->NAME.buf[11] = (lba >> 0)  % 256;
                              }
                              switch (s->NAME.area[area].type) {
                              case CD_AUDIO:
                                    fixme();
                                    break;
                              case CD_DATA:
                                    s->NAME.buf[5]  = (4 << 0); /* CONTROL: Data track */
                                    s->NAME.buf[5] |= (1 << 4); /* ADR: Cur pos data */
                                    break;
                              case CD_LEADOUT:
                                    s->NAME.buf[5]  = (4 << 0); /* CONTROL: Data track */
                                    s->NAME.buf[5] |= (1 << 4); /* ADR: Cur pos data */
                                    break;
                              default:
                                    assert(0);
                              }
                        }
                        break;
                  case 0x02:
                        if(sessions >= first_reported_track) { 
                              switch (s->NAME.area[area].type) {
                              case CD_LEADIN:
                                    assert(ttoc_base != NULL);

                                    ttoc_base[0].session = sessions;
                                    ttoc_base[0].adr = 1;         /* cur pos data */
                                    ttoc_base[0].control = 4;     /* data */
                                    ttoc_base[0].tno = 0;
                                    ttoc_base[0].point = 0xa0;    /* first track info*/
                                    ttoc_base[0].min = 0;
                                    ttoc_base[0].sec = 0;
                                    ttoc_base[0].frame = 0;
                                    ttoc_base[0].zero = 0;
                                    ttoc_base[0].psec = 0;        /* disc type */
                                    ttoc_base[0].pframe = 0;
                                    break;
                              case CD_AUDIO:
                                    fixme();
                                    break;
                              case CD_DATA:
                                    if(first_session_track == 0) {
                                          first_session_track = s->NAME.area[area].tracknum;
                                    }
                                    if(last_session_track < s->NAME.area[area].tracknum) {
                                          last_session_track = s->NAME.area[area].tracknum;
                                    }

                                    ttoc_current->session = sessions;
                                    ttoc_current->adr = 1;        /* cur pos data */
                                    ttoc_current->control = 4;    /* data */
                                    ttoc_current->tno = 0;
                                    ttoc_current->point = s->NAME.area[area].tracknum;
                                    ttoc_current->min = 0;
                                    ttoc_current->sec = 0;
                                    ttoc_current->frame = 0;
                                    ttoc_current->zero = 0;
                                    ttoc_current->pmin   = (blk / 75) / 60;
                                    ttoc_current->psec   = (blk / 75) % 60;
                                    ttoc_current->pframe =  blk % 75;

                                    ttoc_current++;
                                    break;
                              case CD_LEADOUT:
                                    assert(ttoc_base != NULL);

                                    ttoc_base[0].pmin = first_session_track;

                                    ttoc_base[1].session = sessions;
                                    ttoc_base[1].adr = 1;         /* cur pos data */
                                    ttoc_base[1].control = 4;     /* data */
                                    ttoc_base[1].tno = 0;
                                    ttoc_base[1].point = 0xa1;    /* last track info */
                                    ttoc_base[1].min = 0;
                                    ttoc_base[1].sec = 0;
                                    ttoc_base[1].frame = 0;
                                    ttoc_base[1].zero = 0;
                                    ttoc_base[1].pmin = last_session_track;
                                    ttoc_base[1].psec = 0;
                                    ttoc_base[1].pframe = 0;

                                    ttoc_base[2].session = sessions;
                                    ttoc_base[2].adr = 1;         /* cur pos data */
                                    ttoc_base[2].control = 4;     /* data */
                                    ttoc_base[2].tno = 0;
                                    ttoc_base[2].point = 0xa2;    /* lead out */
                                    ttoc_base[2].min = 0;
                                    ttoc_base[2].sec = 0;
                                    ttoc_base[2].frame = 0;
                                    ttoc_base[2].zero = 0;
                                    ttoc_base[2].pmin   = (blk / 75) / 60;
                                    ttoc_base[2].psec   = (blk / 75) % 60;
                                    ttoc_base[2].pframe =  blk % 75;
                                    break;
                              default:
                                    assert(0);
                              }
                        }
                        break;
                  case 0x03:
                        assert(0);
                  case 0x04:
                        /* do this one time outside the for-loop
                         * it is not neccessary to travel n-times
                         * through the loop FIXME */
                        break;
                  case 0x05:
                        assert(0);
                  case 0x06:
                        assert(0);
                  case 0x07:
                        assert(0);
                  default:
                        assert(0);
                  }
                  entry++;

skip:   ;
      blk += s->NAME.area[area].size / 2048;
            }

            if (format == 0x02) {
                  hdr->toc_length = cpu_to_be16(sizeof(*hdr) - sizeof(hdr->toc_length)
                                    + ((unsigned long) ttoc_current)
                                    - ((unsigned long) (hdr + 1)));
            }

      } /* s->NAME.faum_image */

      if (format == 0x04) {
            /* return absolute time in pregroove */
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x1A; /* 26 bytes payload */
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;

            /*
             * byte 4, bit 6,5,4 indicates write power: (bit 7 must be 1)
             * 000b 5mw
             * ...
             * 111b 12mw
             *
             * bit 2,1,0 indicates reference speed: (bit 3 is reserved)
             * 010b 4x
             * 011b 8x
             */
            s->NAME.buf[offset++] = ((( s->NAME.media_desc.writing_power | 0x80 ) % 256 ) << 4)
                  | (( s->NAME.media_desc.reference_speed & 0x07 ) % 256 );

            /*
             * byte 5, bit 6 unrestricted use bit
             */
            s->NAME.buf[offset++] = 0x40; /* unrestricted */

            /*
             * byte 6, bit 7 must be 1, bit 6 = 0 for CD-R / = 1 for CD-RW
             * bit 5,4,3 = disc-sub-type shall be 000b
             * 2 = A1 - indicates, if bytes 16-18 are valid
             * 1 = A2 - indicates, if bytes 20-22 are valid
             * 0 = A3 - indicates, if bytes 24-26 are valid
             */
            s->NAME.buf[offset++] = 0x80 | (( s->NAME.media_desc.media_type & 0x01 ) << 6);

            s->NAME.buf[offset++] = 0x00;

            /* FIXME change msf values to computed values from LBA */
            /* why??? */

            /* atip start time of lead-in (min) */
            s->NAME.buf[offset++] = s->NAME.media_desc.start_of_leadin_m;

            /* atip start time of lead-in (sec) */
            s->NAME.buf[offset++] = s->NAME.media_desc.start_of_leadin_s;

            /* atip start time of lead-in (frame) */
            s->NAME.buf[offset++] = s->NAME.media_desc.start_of_leadin_f;

            s->NAME.buf[offset++] = 0x00;

            /* atip last possible start time of lead-out (min) */
            s->NAME.buf[offset++] = s->NAME.media_desc.last_possible_start_of_leadout_m;

            /* atip last possible start time of lead-out (sec) */
            s->NAME.buf[offset++] = s->NAME.media_desc.last_possible_start_of_leadout_s;

            /* atip last possible start time of lead-out (frame) */
            s->NAME.buf[offset++] = s->NAME.media_desc.last_possible_start_of_leadout_f;

            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
            s->NAME.buf[offset++] = 0x00;
      }

      /* Patch data length if not set yet */
      if (offset > 0 && (s->NAME.buf[0] | s->NAME.buf[1]) == 0) {
            s->NAME.buf[0] = (offset - 2) / 256;
            s->NAME.buf[1] = (offset - 2) % 256;

            if (offset < allocation_length) {
                  allocation_length = offset;
            }
      }

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, allocation_length);
}

#if CD_SCAN_CD_SUPPORT
/*
 * SCSI-3 MMC 66
 *
 * 0xba, optional
 */
static void
NAME_(scan)(struct cpssp *s)
{
      fixme();
}
#endif

/*
 * SCSI-3 MMC 69
 *
 * 0xbb, mandatory for CD-R/RW
 */
static void
NAME_(set_cd_speed)(struct cpssp *s)
{
      unsigned short read_speed, write_speed;

      read_speed = (s->NAME.cmd_buf[2] << 8) | s->NAME.cmd_buf[3];
      write_speed = (s->NAME.cmd_buf[4] << 8) | s->NAME.cmd_buf[5];

#if DEBUGPCOM
      fprintf(stderr, "%s: read_speed=%d, write_speed=%d\n",
                  __FUNCTION__, read_speed, write_speed);
#endif
}

#if CD_WRITER_SUPPORT

/*
 * SCSI-3 MMC 96
 *
 * 0x5b; close session
 */
static void
NAME_(close_track)(struct cpssp *s)
{

      uint8_t track;
      uint8_t session;
      uint8_t track_no;
      track = s->NAME.cmd_buf[2] % 2;
      session = (s->NAME.cmd_buf[2] >> 1) % 2;
      track_no = s->NAME.cmd_buf[5];

      if (! s->NAME.faum_image) {
            /* medium is write protected */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x27, 0x00);
            return;
      }

      /* check if disc is closed */
      if ( s->NAME.media_desc.disc_status == 2 ) {
            /* medium is write protected */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x27, 0x00);
            return;
      }

      if (track != session) { /* proceed only if one of both is 1 */
            if (track == 1) {
                  /* FIXME pad only to the minimum length of 4 seconds and only if incomplete */
                  /* FIXME if partially recorded or empty reserved, the device shall pad the track */
                  /* FIXME in case of  an empty track, the device shall write the track according
                   * to the write parameter page */
                  fixme();
                  /* FIXME close the track */
            } else if (session == 1) {
                  uint8_t msf[3];
                  int64_t startlba;

                  /* get last entry in toc/pma */
                  toc_entry *last_track = &(s->NAME.toc.x01) + s->NAME.toc.xA1.pmin - 1;

                  /* its easier to transform msf to lba, accumulate and transform back */
                  NAME_(msf_to_lba)(&last_track->pmin, &startlba);
                  /* FIXME maybe write direct to s->NAME.toc.xA2.pmin ??? */
                  NAME_(lba_to_msf)(last_track->blkcount + startlba, (uint8_t *)msf);

                  s->NAME.toc.xA2.ctrl = 4;
                  s->NAME.toc.xA2.adr = 1;
                  s->NAME.toc.xA2.tno = 0;
                  s->NAME.toc.xA2.point = 0xa2;
                  s->NAME.toc.xA2.min = 0;
                  s->NAME.toc.xA2.sec = 0;
                  s->NAME.toc.xA2.frame = 0;
                  s->NAME.toc.xA2.zero = 0;
                  /* start position of lead-out */
                  s->NAME.toc.xA2.pmin = msf[0];
                  s->NAME.toc.xA2.psec = msf[1];
                  s->NAME.toc.xA2.pframe = msf[2];

                  /* open file before the readonly variables are set! */
                  NAME_(open_file)(s);

                  /* FIXME change this for multisession support */
                  /* mark disk as closed and append leadout */
                  s->NAME.media_desc.disc_status = 2;
                  s->NAME.media_desc.last_session_status = 3;

                  storage_read_write(IO_WRITE,
                              &s->NAME.media[0],
                              (char *)&s->NAME.toc,
                              s->NAME.media_desc.offset,
                              sizeof(toc_session));
                  storage_read_write(IO_WRITE,
                              &s->NAME.media[0],
                              (char *)&s->NAME.media_desc,
                              0,
                              sizeof(cd_image));
            }
      }


      NAME_(phase_data_in)(s);
      NAME_(_send)(s, 0);
}
#endif

#if CD_WRITER_RW_SUPPORT
/*
 * SCSI-3 MMC 94
 *
 * 0xa1, mandatory for CD-R/RW
 */
static void
NAME_(blank)(struct cpssp *s)
{
      uint8_t immed;
      uint8_t blank_type;
      uint32_t track;
      uint32_t i;
      int64_t lead_in_size;
      toc_entry *act_toc_line;
      char null[2352];
      memset(null, 0, sizeof(null));

      immed = (s->NAME.cmd_buf[1] >> 4) & 1;
      blank_type = (s->NAME.cmd_buf[1] ) & 7;
      track = (s->NAME.cmd_buf[2] << 24)
            | (s->NAME.cmd_buf[3] << 16)
            | (s->NAME.cmd_buf[4] << 8)
            | (s->NAME.cmd_buf[5] << 0);

      if (! s->NAME.faum_image) {
            /* medium is write protected */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x27, 0x00);
            return;
      }

      if (s->NAME.media_desc.media_type != 1) {
            /* cannot format medium - incompatible medium */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x30, 0x06);
            return;
      }

      NAME_(msf_to_lba)((uint8_t *)&s->NAME.media_desc.start_of_leadin_m, &lead_in_size);
      lead_in_size *= -1;

      switch(blank_type) {
            case 6: /* erase session */
                  /* FIXME with multisession-support, this should be implemented separately */
            case 0: /* blank the disc */
                  memset(&s->NAME.toc, 0, sizeof(s->NAME.toc));

                  s->NAME.media_desc.disc_status = 0;
                  s->NAME.media_desc.last_session_status = 0;

                  for (i = 0; i < s->NAME.media_desc.total_blocks; i++) {
                        NAME_(open_file)(s);
                        storage_read_write(IO_WRITE,
                                    &s->NAME.media[0],
                                    (char *)&null,
                                    /* (pos / blocksize) * 2532 = Offset im Image */
                                    s->NAME.media_desc.offset
                                    + s->NAME.media_desc.toc_size
                                    + lead_in_size
                                    + 150 * 2352
                                    + (i * sizeof(null)),
                                    sizeof(null));
                  }

                  break;

            case 1: /* minimally blank the disc */
                  memset(&s->NAME.toc, 0, sizeof(s->NAME.toc));

                  s->NAME.media_desc.disc_status = 0;
                  s->NAME.media_desc.last_session_status = 0;

                  break;
            case 2: /* blank a track */
                  act_toc_line = &(s->NAME.toc.x01) + track - 1;

                  for (i = 0; i < act_toc_line->blkcount; i++) {
                        NAME_(open_file)(s);
                        storage_read_write(IO_WRITE,
                                    &s->NAME.media[0],
                                    (char *)&null,
                                    /* (pos / blocksize) * 2532 = Offset im Image */
                                    s->NAME.media_desc.offset
                                    + s->NAME.media_desc.toc_size
                                    + lead_in_size
                                    + 150 * 2352
                                    + (i * sizeof(null)),
                                    sizeof(null));
                  }

                  break;

            case 3: /* unreserve a track */
                  /* implement with reserve_track() */
                  fixme();
            case 4: /* blank a track tail */
                  /* only valid for packet recording */
                  fixme();
            case 5: /* unclose the last session */
                  /* erase lead-in of last session */
                  /* FIXME B0-Pointer */
                  memset(&s->NAME.toc, 0, sizeof(s->NAME.toc));
                  s->NAME.media_desc.disc_status = 1;
                  s->NAME.media_desc.last_session_status = 1;

                  break;

            case 7: /* reserved */
                  fixme();
      }

      NAME_(open_file)(s);
      storage_read_write(IO_WRITE,
                  &s->NAME.media[0],
                  (char *)&s->NAME.toc,
                  s->NAME.media_desc.offset,
                  sizeof(toc_session));
      storage_read_write(IO_WRITE,
                  &s->NAME.media[0],
                  (char *)&s->NAME.media_desc,
                  0,
                  sizeof(cd_image));

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, 0);
}

/*
 * SCSI-3 MMC 98
 *
 * 0x04, optional
 */
static void
NAME_(format_unit)(struct cpssp *s)
{
      fixme();
}
#endif

#if 0 /* obsolete */
/*
 * SCSI-3 MMC 101
 *
 * 0x5c, optional
 */
static void
NAME_(read_buffer_capacity)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_WRITER_SUPPORT
/*
 * SCSI-3 MMC 103
 *
 * 0x51, mandatory
 */
static void
NAME_(read_disc_info)(struct cpssp *s)
{
      uint16_t allocation_length;

      /*
       * Get parameter.
       */
      assert(s->NAME.cmd_buf[0] == 0x51);
      /* s->NAME.cmd_buf[1]: Reserved */
      /* s->NAME.cmd_buf[2]: Reserved */
      /* s->NAME.cmd_buf[3]: Reserved */
      /* s->NAME.cmd_buf[4]: Reserved */
      /* s->NAME.cmd_buf[5]: Reserved */
      /* s->NAME.cmd_buf[6]: Reserved */
      allocation_length = (s->NAME.cmd_buf[7] << 8) | (s->NAME.cmd_buf[8] << 0);
      /* s->NAME.cmd_buf[9]: Control Byte */

      if (34 + 1 * 8 < allocation_length) {
            allocation_length = 34 + 1 * 8;
      }

      /*
       * Fill info.
       */
      /* Data Length */
      s->NAME.buf[0] = (34 + 1 * 8 - 2) / 256;
      s->NAME.buf[1] = (34 + 1 * 8 - 2) % 256;

      /* Reserved, Erasable, State of last Session, Disc Status */
      s->NAME.buf[2] = (0 << 5)
            | (s->NAME.media_desc.media_type & 0x01 << 4)
            | (s->NAME.media_desc.last_session_status & 0x03 << 2)
            | (s->NAME.media_desc.disc_status & 0x03 << 0);

      /* Number of First Track on Disc */
      s->NAME.buf[3] = s->NAME.toc.xA0.pmin;

      /* Number of Sessions */
      s->NAME.buf[4] = 1; /* FIXME This must be computed */

      /* First Track Number in Last Session */
      s->NAME.buf[5] = s->NAME.toc.xA0.pmin; /* FIXME This must be computed */

      /* Last Track Number in Last Session */
      s->NAME.buf[6] = s->NAME.toc.xA1.pmin; /* FIXME This must be computed */

      /* DID_V, DBC_V, URU, Reserved */
      s->NAME.buf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 0);

      /* Disc Type */
      s->NAME.buf[8] = 0; /* CD-DA or CD-ROM Disc */

      /* Reserved */
      s->NAME.buf[9] = 0;
      s->NAME.buf[10] = 0;
      s->NAME.buf[11] = 0;

      /* Disc Identification */
      /* Not set; see Disc-ID-Valid bit (DID_V) above. */
      s->NAME.buf[12] = 0;
      s->NAME.buf[13] = 0;
      s->NAME.buf[14] = 0;
      s->NAME.buf[15] = 0;

      /* Last Session lead-in Start Time (MSF) */
      s->NAME.buf[16] = 0; /* reserved */
      s->NAME.buf[17] = s->NAME.media_desc.start_of_leadin_m;
      s->NAME.buf[18] = s->NAME.media_desc.start_of_leadin_s;
      s->NAME.buf[19] = s->NAME.media_desc.start_of_leadin_f;

      /* Last Possible Start Time for Start of lead-out (MSF) */
      s->NAME.buf[20] = 0; /* reserved */
      s->NAME.buf[21] = s->NAME.media_desc.last_possible_start_of_leadout_m;
      s->NAME.buf[22] = s->NAME.media_desc.last_possible_start_of_leadout_s;
      s->NAME.buf[23] = s->NAME.media_desc.last_possible_start_of_leadout_f;

      /* Disc Bar Code */
      /* Not set; see Disc-Bar-Code-Valid bit (DBC_V) above. */
      s->NAME.buf[24] = 0;
      s->NAME.buf[25] = 0;
      s->NAME.buf[26] = 0;
      s->NAME.buf[27] = 0;
      s->NAME.buf[28] = 0;
      s->NAME.buf[29] = 0;
      s->NAME.buf[30] = 0;
      s->NAME.buf[31] = 0;

      /* Reserved */
      s->NAME.buf[32] = 0;

      /* Number of OPC Table Entries */
      s->NAME.buf[33] = 1;

      /* OPC Table Entry 0 */
      /* Speed (kBytes per second); 44100*4 1X */
      s->NAME.buf[34] = (176 * 1) / 256;
      s->NAME.buf[35] = (176 * 1) % 256;

      /* OPC Values */
      s->NAME.buf[36] = 1; /* Vendor specific */
      s->NAME.buf[37] = 2;
      s->NAME.buf[38] = 3;
      s->NAME.buf[39] = 4;
      s->NAME.buf[40] = 5;
      s->NAME.buf[41] = 6;

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, allocation_length);
}
#endif /* CD_WRITER_SUPPORT */

#if 0 /* obsolete */
/*
 * SCSI-3 MMC 108
 *
 * 0x59, optional
 */
static void
NAME_(read_master_cue)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_WRITER_SUPPORT
/*
 * SCSI-3 MMC-2 235
 *
 * 0x52, mandatory for CD-R/RW
 */
static void
NAME_(read_track_information)(struct cpssp *s)
{
      uint8_t gather_more_info = 0;

      uint8_t track;
      uint32_t lba_track_number;
      uint16_t allocation_length;
      uint16_t track_number = 1;
      uint8_t track_mode = 0;
      uint8_t data_mode = 0xF;
      uint16_t session_number = 1;
      uint8_t nwa_v = 0;
      uint8_t lra_v = 0;
      uint8_t damage = 0;
      uint8_t copy = 0;
      uint8_t rt = 0;
      uint8_t packet = 0;
      uint8_t fp = 0;
      uint8_t blank = 0;
      int64_t track_start_address = 0;
      int32_t next_empty_block = 0;
      int32_t next_writable_address = 0;
      int32_t last_recorded_address = 0;
      uint32_t free_blocks = 0;
      uint32_t fixed_packet_size = 0;
      uint32_t track_size = 0;
      toc_entry *act_toc_line = NULL;

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0x52);
      /* (s->NAME.cmd_buf[1] >> 1) & 0x7f: Reserved */
      track = (s->NAME.cmd_buf[1] >> 0) & 3;
      lba_track_number = (s->NAME.cmd_buf[2] << 24)
            | (s->NAME.cmd_buf[3] << 16)
            | (s->NAME.cmd_buf[4] << 8)
            | (s->NAME.cmd_buf[5] << 0);
      /* s->NAME.cmd_buf[6]: Reserved */
      allocation_length = (s->NAME.cmd_buf[7] << 8)
            | (s->NAME.cmd_buf[8] << 0);
      /* s->NAME.cmd_buf[9]: Control Byte */

      if ( ! s->NAME.faum_image) {
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x30, 0x02); /* ILLEGAL MODE FOR THIS TRACK */
            return;
      }

      /* EVERYTHING HERE IS PER TRACK! */
      /* a track can be blank while a cd is not */

      /*
       * Modify parameters.
       */
      if (track == 1 && lba_track_number == 0x00) {
            int64_t lba = 0;
            int64_t lead_in_size = 0;
            /* Get info for toc. */
            track_number = 0x00;
            session_number = 0x00;
            rt = 0; /* only tracks can be reserved, the toc is not a track */
            /*
             * Lookup info.
             */
            /* start of toc is time of 1st entry */
            NAME_(msf_to_lba)(&s->NAME.toc.x01.min, &lba);

            if (&s->NAME.media_desc.disc_status == 0 && lba != 0) { /* disc is empty */
                  /* no toc -> no such track -> check condition */
                  /* FIXME hope this is correct this way here */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* invalid field in cdb */
#if 1 /* check when winxp is available again */
                  return;
#else
                  blank = 1;
#endif
            } else {
                  NAME_(msf_to_lba)((uint8_t *)&s->NAME.media_desc.start_of_leadin_m, &lead_in_size);
                  lead_in_size *= -1;
                  track_start_address = lba;
                  track_size = lead_in_size;
                  /* FIXME get this directly from block */
                  /* FIXME before this, write this in every block ;) */
                  track_mode = 0x0; /* 0 for leadin mmc2 page 276 table 350 */
                  data_mode = 0x1; /* 1 for leadin mmc2 page 276 table 350 */
            }

      } else if (track == 1 && lba_track_number == 0xff) {
            /* Get info for invisible (or incomplete (for dvd)) track. */

            track_number = 0xFF;
            rt = 0; /* an invisible track is never reserved */

            if (s->NAME.media_desc.disc_status != 2) { /* empty or appendable */
                  /*
                   * Lookup info.
                   */
                  if (s->NAME.media_desc.disc_status == 0) { /* empty */
                        /* use values for complete disc */
                        track_start_address = 0;
                        next_empty_block = 0;
                        track_size = s->NAME.media_desc.total_blocks;
                        data_mode = 0;
                        track_mode = 0;
                        nwa_v = 0x1;
                        blank = 1;
                  } else { /* appendable */
                        int64_t lba_startoftrack;
                        act_toc_line = &(s->NAME.toc.x01);
                        if (s->NAME.toc.xA2.pmin != 0) { /* there is a leadout */
                              /* FIXME read B0 pointer and check for next leadout */
                        }
                        /* change to last written track on media */
                        act_toc_line += s->NAME.toc.xA1.pmin - 1;
                        /* get start of last written track */
                        NAME_(msf_to_lba)(&act_toc_line->pmin, &lba_startoftrack);
                        /* get start of unwritten area beyond last track */
                        track_start_address = lba_startoftrack + act_toc_line->blkcount;
                        if ( s->NAME.is_writing ) {
                              next_empty_block = (s->NAME.last_written_lba)++;
                              blank = 0;
                              lra_v = 0x1;
                        } else {
                              next_empty_block = track_start_address;
                              blank = 1;
                        }
                        /* compute tracksize of unwritten area */
                        track_size = s->NAME.media_desc.total_blocks - track_start_address;
                        session_number = 1; /* FIXME count up when reading B0 Pointer */
                        track_mode = 0x4; /* FIXME read from tdb? */
                        data_mode = 0xF;
                        nwa_v = 0x1;
                  }
            } else {
                  /*
                   * disc not appendable, no writeable invisible track
                   */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* invalid field in cdb */
                  return;
            }
      } else if (track == 1) {
            track_number = lba_track_number;
            act_toc_line = &(s->NAME.toc.x01) + track_number - 1;
            gather_more_info = 1;
      } else if (track == 0) {
            /* get track at LBA 'lba_track_number'. */
            act_toc_line = NAME_(get_track_at_lba)(s, s->NAME.m2t_pos);
            track_number = act_toc_line->point;
            gather_more_info = 1;
      } else {
            fprintf(stderr, "CD: read_track_info: track not qualified: %i\n", track);
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* invalid field in cdb */
            return;
      }
      if (36 < allocation_length) {
            allocation_length = 36;
      }

      if ( gather_more_info ) { 

            NAME_(msf_to_lba)(&(act_toc_line->pmin), &track_start_address);
            track_size = act_toc_line->blkcount;
            session_number = 1; /* FIXME B0-Pointer etc. */
            track_mode = act_toc_line->ctrl;
            data_mode = act_toc_line->datablocktype;

            rt = act_toc_line->reserved;
            copy = 0;
            free_blocks = 358400;
            fixed_packet_size = 0;
            if ( rt ) { /* if reserved track */
                  free_blocks = 0;
                  nwa_v = 0x1;
                  if ( act_toc_line->trackdescblock.tracktype ) {
                        lra_v = 0x1;
                        blank = 0;
                        last_recorded_address = NAME_(get_last_written_block)(s, track_start_address);
                        switch (act_toc_line->trackdescblock.tracktype) {
                        case 0x04: /* fixed packet */
                              fixed_packet_size = act_toc_line->trackdescblock.fixed_packet_size;
                              fp = 1;
                              /* FIXME fill next_empty_block */
                        case 0x03: /* variable packet */
                              packet = 1;
                              /* FIXME fill next_empty_block (link_blocks) */
                        default:
                              break;
                        }
                  } else {
                        lra_v = 0x0;
                        blank = 1;
                  }
            } else { /* else it is definetly not blank and not writable */
                  /* ergo: last_recorded_address = last block of track */
                  last_recorded_address = track_start_address + act_toc_line->blkcount - 1;
            }

      }

      damage = 0; /* damage is ALWAYS 0 for us, can only be caused if checksum error occurs */

      /* if the track is blank, then there could be no last recorded address */
      if ( blank ) {
            lra_v = 0;
      }

      if (nwa_v == 1) {
            next_writable_address = next_empty_block;
      } else {
            next_writable_address = 0;
      }

      if (lra_v == 1) {
            if ( s->NAME.is_writing ) {
                  last_recorded_address = s->NAME.last_written_lba;
            } else { /* scan track for last recorded block */
                  /* better do this above */
            }
      } else {
            last_recorded_address = 0;
      }

      /*
       * Fill track information block.
       */
      /* Data Length */
      s->NAME.buf[0] = (36 - 2) / 256;
      s->NAME.buf[1] = (36 - 2) % 256;

      /* Track Number (LSB) */
      s->NAME.buf[2] = track_number % 256;

      /* Session Number (LSB) */
      s->NAME.buf[3] = session_number % 256;

      /* Reserved */
      s->NAME.buf[4] = 0;

      /* Damage, Copy, Track Mode */
      s->NAME.buf[5] = ((damage & 0x1)  << 5) | ((copy & 0x1) << 4)
            | ((track_mode & 0x0F) << 0);

      /* RT, Blank, Packet, FP, Data Mode */
      s->NAME.buf[6] = ((rt & 0x1) << 7) | ((blank & 0x1) << 6)
            | ((packet & 0x1) << 5) | ((fp & 0x1) << 4)
            | ((data_mode & 0x0F) << 0);

      /* LRA_V, NWA_V */
      s->NAME.buf[7] = ((lra_v & 0x1) << 1) | ((nwa_v & 0x1) << 0);

      /* Track Start Address */
      s->NAME.buf[8] = ( track_start_address >> 24 ) % 256;
      s->NAME.buf[9] = ( track_start_address >> 16 ) % 256;
      s->NAME.buf[10] = ( track_start_address >> 8 ) % 256;
      s->NAME.buf[11] = ( track_start_address >> 0 ) % 256;

      /* Next Writable Address */
      s->NAME.buf[12] = ( next_writable_address >> 24 ) % 256;
      s->NAME.buf[13] = ( next_writable_address >> 16 ) % 256;
      s->NAME.buf[14] = ( next_writable_address >> 8 ) % 256;
      s->NAME.buf[15] = ( next_writable_address >> 0 ) % 256;

      /* Free Blocks */
      s->NAME.buf[16] = ( free_blocks >> 24 ) % 256;
      s->NAME.buf[17] = ( free_blocks >> 16 ) % 256;
      s->NAME.buf[18] = ( free_blocks >> 8 ) % 256;
      s->NAME.buf[19] = ( free_blocks >> 0 ) % 256;

      /* Fixed Packet Size */
      s->NAME.buf[20] = ( fixed_packet_size >> 24 ) % 256;
      s->NAME.buf[21] = ( fixed_packet_size >> 16 ) % 256;
      s->NAME.buf[22] = ( fixed_packet_size >> 8 ) % 256;
      s->NAME.buf[23] = ( fixed_packet_size >> 0 ) % 256;

      /* Track Size */
      s->NAME.buf[24] = ( track_size >> 24 ) % 256;
      s->NAME.buf[25] = ( track_size >> 16 ) % 256;
      s->NAME.buf[26] = ( track_size >> 8 ) % 256;
      s->NAME.buf[27] = ( track_size >> 0 ) % 256;

      /* Last Recorded Address */
      s->NAME.buf[28] = ( last_recorded_address >> 24 ) % 256;
      s->NAME.buf[29] = ( last_recorded_address >> 16 ) % 256;
      s->NAME.buf[30] = ( last_recorded_address >> 8 ) % 256;
      s->NAME.buf[31] = ( last_recorded_address >> 0 ) % 256;

      /* Track Number (MSB) */
      s->NAME.buf[32] = track_number / 256;

      /* Session Number (MSB) */
      s->NAME.buf[33] = session_number / 256;

      /* reserved */
      s->NAME.buf[34] = 0;
      s->NAME.buf[35] = 0;

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, allocation_length);
}

/*
 * SCSI-3 MMC 117
 *
 * 0x53, mandatory for CD-R/RW
 */
static void
NAME_(reserve_track)(struct cpssp *s)
{
      fixme();
      /* check if last track is already a reserved one */
      /* get last track number */

      /* create toc-entry and mark it as reserved */

      /* compute reservation size depending on write mode */
      switch(s->NAME.mode_page_05[2] & 0x0F) {
            case 0: /* packet */
                  if (s->NAME.mode_page_05[3] & 0x20) { /* fixed packet */
                        /* FIXME start + (packet size  + 7 ) * reservation size / packet size - 5 */
                        break;
                  } /* variable packet shall behave as tao */
            case 1: /* track at once */
                  /* start of track + (pre-gap) + reservationsize + 2 */
                  break;
            case 2: /* session at once */
                  /* check condition, illegal request, command sequence error */
                  NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x2c, 0x00);
                  return;
            case 3: /* raw */
                  break;
            default:
                  fprintf(stderr, "CD: write type has reserved value.\n");
                  fixme();
      }
      /* write to toc */
}

#if CD_WRITER_DAO_SUPPORT
/*
 * SCSI-3 MMC 119
 *
 * 0x5d, optional
 */
static void
NAME_(send_cue_sheet)(struct cpssp *s)
{
      uint32_t cue_sheet_size;
      uint8_t *line;
      int x;

      cue_sheet_size = (s->NAME.cmd_buf[6] << 16)
            | (s->NAME.cmd_buf[7] << 8)
            | (s->NAME.cmd_buf[8] << 0);


      NAME_(phase_data_out)(s);
      NAME_(_recv)(s, cue_sheet_size);

      /* parse cue sheet */

      for (x = 0; x < cue_sheet_size; x+=8) {
            line = (uint8_t *)&(s->NAME.buf[x]);
#if 1
            /* mostly not interesting */
            {
                  int i;
                  for (i = 0; i < 8; i++) {
                        fprintf(stderr, " %#02X", line[i]);
                  }
            }
            fprintf(stderr, "\n");
#endif
            if (0 < line[1] && line[1] < 100) {
                  int64_t startlba;
                  int64_t nextstartlba;
                  int64_t blkcount;
                  startlba = nextstartlba;
                  NAME_(msf_to_lba)((uint8_t *)((&line + 8) + 5),&nextstartlba);
                  blkcount = nextstartlba - startlba;
                  /* if its not lead-in or lead-out */
                  if ( ! s->NAME.toc.xA0.pmin ) {
                        /* first track number */
                        s->NAME.toc.xA0.pmin = line[1];
                  }
                  /* last track number */
                  s->NAME.toc.xA1.pmin = line[1];
                  if ( line[2] == 1 ) {
                        (&(s->NAME.toc.x01) + line[1] - 1)->ctrl = line[0] >> 4;
                        (&(s->NAME.toc.x01) + line[1] - 1)->adr = line[0] & 0x0F;
                        (&(s->NAME.toc.x01) + line[1] - 1)->pmin = line[5];
                        (&(s->NAME.toc.x01) + line[1] - 1)->psec = line[6];
                        (&(s->NAME.toc.x01) + line[1] - 1)->pframe = line[7];
                  }
            } else if ( line[1] == 0xAA ) {
                  s->NAME.toc.xA2.ctrl = line[0] >> 4;
                  s->NAME.toc.xA2.adr = line[0] & 0x0F;
                  s->NAME.toc.xA2.pmin = line[5];
                  s->NAME.toc.xA2.psec = line[6];
                  s->NAME.toc.xA2.pframe = line[7];
            }
      }

      NAME_(open_file)(s);

      storage_read_write(IO_WRITE,
                  &s->NAME.media[0],
                  (char *)&s->NAME.toc,
                  s->NAME.media_desc.offset,
                  sizeof(toc_session));

      NAME_(phase_data_in)(s);
      NAME_(_send)(s, 0);
}
#endif

/*
 * SCSI-3 MMC 128
 *
 * 0x54, optional
 */
static void
NAME_(send_opc_information)(struct cpssp *s)
{
      uint8_t doopc;
      uint16_t parameter_list_length;

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0x54);
      doopc = (s->NAME.cmd_buf[1] >> 0) & 1;
      parameter_list_length = (s->NAME.cmd_buf[7] << 8)
            | (s->NAME.cmd_buf[8] << 0);
      /* s->NAME.cmd_buf[9]: Control Byte */

      if ( parameter_list_length ) {
            NAME_(phase_data_in)(s);
            NAME_(_recv)(s, parameter_list_length);
      }
      /* FIXME shall I do something with this information? */
}
#endif /* CD_WRITER_SUPPORT */

#if CD_AUDIO_SUPPORT
/*
 * SCSI-3 MMC ???
 *
 * 0x4e, mandatory for audio
 */
static void
NAME_(stop_play_scan)(struct cpssp *s)
{
      fixme();
}
#endif

#if CD_WRITER_SUPPORT
/*
 * SCSI-3 MMC 130
 *
 * 0x35, mandatory for CD-R/RW
 */
static void
NAME_(synchronize_cache)(struct cpssp *s)
{
      if ( ! s->NAME.faum_image ) {
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x24, 0x00); /* invalid field in cdb */
            return;
      }
      if ( s->NAME.is_writing ) {
            toc_entry *act_toc_line = 0;
            uint8_t msf[3];

            s->NAME.is_writing = 0;
            switch (s->NAME.mode_page_05[2] & 0x0F) { /* mmc-2 page 305 */
            case 0: /* pkt */
                  /* fixed packet: pad the packet */
                  /*
                   * variable packet: For CD, if insufficient space exists for
                   * another variable packet within a reserved track, pad the
                   * packet such that it fills the track. Otherwise, write
                   * run-out and link blocks. For DVD perform linking
                   */
                  break;
            case 1: /* tao */
                  /* FIXME B0 Pointer! */
                  s->NAME.toc.xA0.pmin = 1;

                  /* pad the track (if reserved or not minimum length) and update pma */
                  /* FIXME check if s->NAME.last_written_lba belongs to invisible track */

                  /* increment last track number after locating the right toc line */
                  act_toc_line = &(s->NAME.toc.x01) + s->NAME.toc.xA1.pmin;
                  s->NAME.toc.xA1.pmin++;

                  act_toc_line->ctrl = 0x6; /* data, recorded uninterrupted */
                  act_toc_line->adr = 1;
                  act_toc_line->tno = 0;
                  act_toc_line->point = s->NAME.toc.xA1.pmin;
                  act_toc_line->min = 0;
                  act_toc_line->sec = 0;
                  act_toc_line->frame = 0;
                  act_toc_line->zero = 0;
                  act_toc_line->datablocktype = s->NAME.mode_page_05[4] & 0x0F;

                  /* get start address of track */
                  NAME_(lba_to_msf)(s->NAME.last_written_lba - (s->NAME.written_blocks - 1),
                              (uint8_t *)msf);
                  act_toc_line->pmin = msf[0];
                  act_toc_line->psec = msf[1];
                  act_toc_line->pframe = msf[2];

                  act_toc_line->blkcount = s->NAME.written_blocks;
                  s->NAME.written_blocks = 0;

                  s->NAME.media_desc.disc_status = 1; /* incomplete/appendable */
                  s->NAME.media_desc.last_session_status = 1; /* incomplete */

                  break;
            case 2: /* sao */
                  /* generate lead-out */
                  s->NAME.media_desc.disc_status = 2; /* incomplete/appendable */
                  s->NAME.media_desc.last_session_status = 3; /* incomplete */
                  break;
            case 3: /* raw */
                  /* 
                   * write run-out and link blocks. read the TOC and track information
                   * from the session just written and update the PMA. assume that the
                   * the Lead-out was written
                   */

                  break;
            }
            NAME_(open_file)(s);
            storage_read_write(IO_WRITE,
                        &s->NAME.media[0],
                        (char *)&s->NAME.toc,
                        s->NAME.media_desc.offset,
                        sizeof(toc_session));
            storage_read_write(IO_WRITE,
                        &s->NAME.media[0],
                        (char *)&s->NAME.media_desc,
                        0,
                        sizeof(cd_image));

      } else {
            /* 
             * if there was no writing before, there is nothing to do
             * for us, because close_track is performed immediately 
             */
      }
}

/*
 * SCSI-3 MMC 131
 *
 * 0x2a, mandatory
 */
static void
NAME_(write_10)(struct cpssp *s)
{
      int dpo;
      int fua;
      int reladr;
      int ret = 0;
      int32_t lba;
      uint16_t transfer_length;

      uint16_t blocksize = 0;
      char write_type[4];
      char block_type[10];

      int64_t lead_in_size;

      if (! s->NAME.faum_image) {
            /* medium is write protected */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x27, 0x00);
            return;
      }

      /* check if disc is closed */
      if ( s->NAME.media_desc.disc_status == 2 ) {
            /* medium is write protected */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x27, 0x00);
            return;
      }

      /*
       * Get parameters.
       */
      assert(s->NAME.cmd_buf[0] == 0x2a);

      /* (s->NAME.cmd_buf[1] >> 5) & 0x7: Reserved */
      dpo = (s->NAME.cmd_buf[1] >> 4) & 1; /* reserved later */
      fua = (s->NAME.cmd_buf[1] >> 3) & 1;
      /* (s->NAME.cmd_buf[1] >> 1) & 0x3: Reserved */
      reladr = (s->NAME.cmd_buf[1] >> 0) & 1; /* shall be set to zero anyway */
      lba = (s->NAME.cmd_buf[2] << 24)
            | (s->NAME.cmd_buf[3] << 16)
            | (s->NAME.cmd_buf[4] << 8)
            | (s->NAME.cmd_buf[5] << 0);
      /* s->NAME.cmd_buf[6]: Reserved */
      transfer_length = (s->NAME.cmd_buf[7] << 8)
            | (s->NAME.cmd_buf[8] << 0);
      /* s->NAME.cmd_buf[9]: Control */

      switch (s->NAME.mode_page_05[2] & 0x0F) {
      case 0:
            sprintf(write_type,"pkt");
            break;
      case 1:
            sprintf(write_type,"tao");
            break;
      case 2:
            sprintf(write_type,"sao");
            break;
      case 3:
            sprintf(write_type,"raw");
            break;
      default:
            fprintf(stderr, "CD: write type has reserved value.\n");
            fixme();
      }

      switch (s->NAME.mode_page_05[4] & 0x0F) {
      case 0: /* optional */
            sprintf(block_type,"raw");
            blocksize = 2352;
            break;
      case 1: /* optional */
            sprintf(block_type,"raw+pq");
            blocksize = 2368;
            break;
      case 2: /* optional */
            sprintf(block_type,"raw+p-w");
            blocksize = 2448;
            break;
      case 3: /* optional */
            sprintf(block_type,"raw+rp-w");
            blocksize = 2448;
            break;
      case 8: /* mandatory */
            sprintf(block_type,"mode1");
            blocksize = 2048;
            break;
      case 9: /* optional */
            sprintf(block_type,"mode2");
            blocksize = 2336;
            break;
      case 10: /* mandatory */
            sprintf(block_type,"mode2-xa1");
            blocksize = 2048;
            break;
      case 11: /* optional */
            sprintf(block_type,"mode2-xa1");
            blocksize = 2056;
            break;
      case 12: /* optional */
            sprintf(block_type,"mode2-xa2");
            blocksize = 2324;
            break;
      case 13: /* mandatory */
            sprintf(block_type,"mode2-xa");
            blocksize = 2332;
            break;
      default:
            fprintf(stderr, "CD: block type has reserved or vendor value.\n");
            fixme();
      }

#if DEBUGPCOM
      fprintf(stderr, "write: lba=%d tl=%d ", lba, transfer_length);
      fprintf(stderr, "block_type: %s ", block_type);
      fprintf(stderr, "write_type: %s\n", write_type);
#endif

      /* save amount of written blocks for toc */
      if ( strncmp(write_type, "tao", 3) == 0 || strncmp(write_type, "raw", 3) == 0 ) {
            if ( ! s->NAME.written_blocks ) {
                  s->NAME.written_blocks = 0;
            }
            s->NAME.written_blocks += transfer_length;
      }

      NAME_(open_file)(s);
      NAME_(phase_data_out)(s);
      if ( ! s->NAME.is_writing && strncmp(write_type, "tao", 3)) {
            /* write tdb if it is the first block in tao mode in a reserved track */
            toc_entry *act_toc_line = NAME_(get_track_at_lba)(s, s->NAME.m2t_pos);
            if ( act_toc_line != NULL ) {
                  if (act_toc_line->reserved) {
                        /* write tdb */
                        act_toc_line->trackdescblock.tracktype = 0x02;
                  }
            }
      }

      NAME_(msf_to_lba)((uint8_t *)&s->NAME.media_desc.start_of_leadin_m, &lead_in_size);
      lead_in_size *= -1;

      s->NAME.is_writing = 1;
      s->NAME.media_desc.disc_status = 1; /* incomplete/appendable */
      s->NAME.media_desc.last_session_status = 1; /* incomplete */
      while ( ret < transfer_length ) {
            mode1 cd_block;

            NAME_(_recv)(s, blocksize);

            switch ( blocksize ) {
            case 2048:
                  cd_block.typeoftrack = 0x6; /* data, recorded uninterrupted */
                  cd_block.datablocktype = s->NAME.mode_page_05[4] & 0x0F;

                  /* read blocksize byte from source and write 2352 to imagefile */
                  memcpy(cd_block.data, s->NAME.buf, sizeof(cd_block.data));
                  /* FIXME make sure we do not write outside of our file */
                  storage_read_write(IO_WRITE,
                              &s->NAME.media[0],
                              (char *)&cd_block,
                              /* (pos / blocksize) * 2532 = Offset im Image */
                              s->NAME.media_desc.offset
                              + s->NAME.media_desc.toc_size
                              + lead_in_size
                              + 150 * 2352
                              + (lba * sizeof(cd_block)),
                              sizeof(cd_block));
                  ret++;
                  lba++;
                  break;
            case 2352:
                  /* FIXME make sure we do not write outside of our file */
                  storage_read_write(IO_WRITE,
                              &s->NAME.media[0],
                              s->NAME.buf,
                              /* (pos / blocksize) * 2532 = Offset im Image */
                              s->NAME.media_desc.offset
                              + s->NAME.media_desc.toc_size
                              + lead_in_size
                              + 150 * 2352
                              + (lba * 2352),
                              2352);
                  lba++;
                  ret++;
                  break;
            default:
                  fprintf(stderr, "blocksize: %i ", blocksize);
                  fixme();
                  break;
            }
            s->NAME.last_written_lba = lba - 1;
      }
}
#endif /* CD_WRITER_SUPPORT */

enum token {
      PARSE_ERROR,

      PARSE_EOF,
      PARSE_FILE,
      PARSE_TRACK,
      PARSE_PREGAP,
      PARSE_INDEX,
      PARSE_POSTGAP,
      PARSE_STRING,
      PARSE_PARAM,
};

static enum token
parse(const char **cfp, char *buf)
{
      const char *cf;
      enum token ret;

      cf = *cfp;

      ret = PARSE_ERROR;

      for (;;) {
            if (*cf == '\0') {
                  /* EOF */
                  ret = PARSE_EOF;
                  break;

            } else if (*cf == '\t'
                        || *cf == '\n'
                        || *cf == '\r'
                        || *cf == ' ') {
                  /* White Space */
                  cf++;
                  continue;

            } else if (strncmp(cf, "REM", 3) == 0
                        || strncmp(cf, "rem", 3) == 0) {
                  /* REMark Line */
                  cf += strlen("REM");
                  while (*cf != '\0'
                              && *cf != '\n') {
                        cf++;
                  }
                  continue;

            } else if (strncmp(cf, "FILE", 4) == 0
                        || strncmp(cf, "file", 4) == 0) {
                  /* FILE Token */
                  cf += 4;
                  ret = PARSE_FILE;
                  break;

            } else if (strncmp(cf, "TRACK", 5) == 0
                        || strncmp(cf, "track", 5) == 0) {
                  /* TRACK Token */
                  cf += 5;
                  ret = PARSE_TRACK;
                  break;

            } else if (strncmp(cf, "PREGAP", 6) == 0
                        || strncmp(cf, "pregap", 6) == 0) {
                  /* PREGAP Token */
                  cf += 6;
                  ret = PARSE_PREGAP;
                  break;

            } else if (strncmp(cf, "INDEX", 5) == 0
                        || strncmp(cf, "index", 5) == 0) {
                  /* TRACK Token */
                  cf += 5;
                  ret = PARSE_INDEX;
                  break;

            } else if (strncmp(cf, "POSTGAP", 7) == 0
                        || strncmp(cf, "postgap", 7) == 0) {
                  /* POSTGAP Token */
                  cf += 7;
                  ret = PARSE_POSTGAP;
                  break;

            } else if (*cf == '"') {
                  /* String Token */
                  cf++;
                  while (*cf != '"'
                              && *cf != '\n'
                              && *cf != '\0') {
                        *buf++ = *cf++;
                  }
                  if (*cf != '"') break;
                  cf++;
                  *buf = '\0';
                  ret = PARSE_STRING;
                  break;

            } else {
                  /* Parameter Token */
                  /* E.g.: 01, 00:02:00, BINARY, MODE1/2048, ... */
                  while (*cf != '\0'
                              && *cf != '\t'
                              && *cf != '\n'
                              && *cf != '\r'
                              && *cf != ' ') {
                        *buf++ = *cf++;
                  }
                  *buf = '\0';
                  ret = PARSE_PARAM;
                  break;
            }
      }

      *cfp = cf;
      return ret;
}

static int
NAME_(import_cue_sheet)(struct cpssp *s, const char *cf, const char *path)
{
      enum token token;
      char param[256];
      int filenum;
      int tracknum;
      int indexnum;
      int fileoffset;
      int lastarea;
      unsigned long long size;
      int ret;

      /*
       * Parse cue sheet (simple version). FIXME VOSSI
       */
      filenum = -1;
      tracknum = -1;
      indexnum = -1;
      fileoffset = -1;
      lastarea = -1;
      size = 0;

      s->NAME.nareas = 0;

      /* Add leadin. */
      s->NAME.area[s->NAME.nareas].type = CD_LEADIN;
      s->NAME.area[s->NAME.nareas].tracknum = 0;
      s->NAME.area[s->NAME.nareas].indexnum = 0;
      s->NAME.area[s->NAME.nareas].filenum = 0;
      s->NAME.area[s->NAME.nareas].fileoffset = 0;
      s->NAME.area[s->NAME.nareas].size = 0;
      s->NAME.nareas++;

      for (;;) {
            char filename[1024];
            unsigned int arg;

            token = parse(&cf, param);
            if (token == PARSE_ERROR) {
syntax:;
                   fprintf(stderr, "%s: WARNING: parse error in cue sheet\n",
                               path);
                   return -1;
            }
            if (token == PARSE_EOF) {
                  break;
            }
            switch (token) {
            case PARSE_FILE:
                  /*
                   * FILE "<name>" <type>
                   */
                  /* Read filename. */
                  token = parse(&cf, param);
                  if (token != PARSE_STRING) goto syntax;

                  if (param[0] == '/') {
                        strcpy(filename, param);
                  } else if (strchr(path, '/')) {
                        strcpy(filename, path);
                        strcpy(strrchr(filename, '/') + 1, param);
                  } else {
                        strcpy(filename, param);
                  }

                  /* Read type. */
                  token = parse(&cf, param);
                  if (token != PARSE_PARAM) goto syntax;

                  /* Ignore it... */ /* FIXME */

                  if (0 <= lastarea) {
                        /* Patch last index area. */
                        s->NAME.area[lastarea].size = size;
                  }

                  storage_change(&s->NAME.media[s->NAME.nfiles], filename);
                  ret = storage_open(&s->NAME.media[s->NAME.nfiles], 0);
                  assert(0 <= ret);
                  lastarea = -1;
                  size = storage_size(&s->NAME.media[s->NAME.nfiles]);

                  filenum = s->NAME.nfiles;

                  s->NAME.nfiles++;
                  break;

            case PARSE_TRACK:
                  /*
                   * TRACK <no> <mode>
                   */
                  if (filenum < 0) goto syntax;

                  /* Read track number. */
                  token = parse(&cf, param);
                  tracknum = atoi(param);
                  if (tracknum <= 0 || 100 <= tracknum) goto syntax;

                  /* Read mode. */
                  token = parse(&cf, param);
                  /* Ignoring mode... FIXME */
                  break;

            case PARSE_PREGAP:
                  /*
                   * PREGAP <min>:<sec>:<frame>
                   */
                  /* Read <min>:<sec>:<frame>. */
                  token = parse(&cf, param);
                  if (strlen(param) != 8) goto syntax;
                  if (param[2] != ':') goto syntax;
                  if (param[5] != ':') goto syntax;
                  arg = atoi(&param[0]);
                  arg *= 60;
                  arg += atoi(&param[3]);
                  arg *= 75;
                  arg += atoi(&param[6]);
                  arg *= 2048;

                  if (s->NAME.nareas == 1
                              && arg != 2 * 75 * 2048) {
                        fprintf(stderr, "WARNING: PREGAP of first trackhas bad size!\n");
                        arg = 2 * 75 * 2048;
                  }

                  s->NAME.area[s->NAME.nareas].type = CD_PREGAP;
                  s->NAME.area[s->NAME.nareas].tracknum = tracknum;
                  s->NAME.area[s->NAME.nareas].indexnum = 0;
                  s->NAME.area[s->NAME.nareas].filenum = 0;
                  s->NAME.area[s->NAME.nareas].fileoffset = 0;
                  s->NAME.area[s->NAME.nareas].size = arg;
                  s->NAME.nareas++;
                  break;

            case PARSE_INDEX:
                  /*
                   * INDEX <no> <min>:<sec>:<frame>
                   */
                  if (s->NAME.nareas == 1) {
                        /* Insert standard pregap 00:02:00 */
                        s->NAME.area[s->NAME.nareas].type = CD_PREGAP;
                        s->NAME.area[s->NAME.nareas].tracknum = tracknum;
                        s->NAME.area[s->NAME.nareas].indexnum = 0;
                        s->NAME.area[s->NAME.nareas].filenum = 0;
                        s->NAME.area[s->NAME.nareas].fileoffset = 0;
                        s->NAME.area[s->NAME.nareas].size = 2 * 75 * 2048;
                        s->NAME.nareas++;
                  }

                  /* Read <no>. */
                  token = parse(&cf, param);
                  if (strlen(param) != 2) goto syntax;
                  indexnum = atoi(param);
                  if (indexnum < 0 || 100 <= indexnum) goto syntax;

                  /* Read <min>:<sec>:<frame>. */
                  token = parse(&cf, param);
                  if (strlen(param) != 8) goto syntax;
                  if (param[2] != ':') goto syntax;
                  if (param[5] != ':') goto syntax;
                  arg = atoi(&param[0]);
                  arg *= 60;
                  arg += atoi(&param[3]);
                  arg *= 75;
                  arg += atoi(&param[6]);
                  arg *= 2048;

                  if (0 <= lastarea) {
                        /* Patch last index area. */
                        s->NAME.area[lastarea].size
                              = arg - s->NAME.area[lastarea].fileoffset;
                        size -= s->NAME.area[lastarea].size;
                  }
                  lastarea = s->NAME.nareas;

                  s->NAME.area[s->NAME.nareas].type = CD_DATA;
                  s->NAME.area[s->NAME.nareas].tracknum = tracknum;
                  s->NAME.area[s->NAME.nareas].indexnum = indexnum;
                  s->NAME.area[s->NAME.nareas].filenum = filenum;
                  s->NAME.area[s->NAME.nareas].fileoffset = arg;
                  s->NAME.area[s->NAME.nareas].size = 0; /* will be patched later */
                  s->NAME.nareas++;
                  break;

            case PARSE_POSTGAP:
                  /*
                   * POSTGAP <min>:<sec>:<frame>
                   */
                  /* Read <min>:<sec>:<frame>. */
                  token = parse(&cf, param);
                  if (strlen(param) != 8) goto syntax;
                  if (param[2] != ':') goto syntax;
                  if (param[5] != ':') goto syntax;
                  arg = atoi(&param[0]);
                  arg *= 60;
                  arg += atoi(&param[3]);
                  arg *= 75;
                  arg += atoi(&param[6]);
                  arg *= 2048;

                  s->NAME.area[s->NAME.nareas].type = CD_POSTGAP;
                  s->NAME.area[s->NAME.nareas].tracknum = tracknum;
                  s->NAME.area[s->NAME.nareas].indexnum = 0;
                  s->NAME.area[s->NAME.nareas].filenum = 0;
                  s->NAME.area[s->NAME.nareas].fileoffset = 0;
                  s->NAME.area[s->NAME.nareas].size = arg;
                  s->NAME.nareas++;
                  break;

            default:
                  assert(0);
            }
      }
      if (0 <= lastarea) {
            /* Patch last index area. */
            s->NAME.area[lastarea].size = size;
      }
      s->NAME.open = 1;

      s->NAME.area[s->NAME.nareas].type = CD_LEADOUT;
      s->NAME.area[s->NAME.nareas].tracknum = 0xaa;
      s->NAME.area[s->NAME.nareas].indexnum = 0;
      s->NAME.area[s->NAME.nareas].filenum = 0;
      s->NAME.area[s->NAME.nareas].fileoffset = 0;
      s->NAME.area[s->NAME.nareas].size = 0;
      s->NAME.nareas++;

      return 0;
}

static int
NAME_(insert_media_cue)(struct cpssp *s, const char *path)
{
      unsigned int len;
      unsigned int size;
      int fd;
      char buffer[16 * 1024];

      /*
       * Check for ".cue" suffix.
       */
      len = strlen(path);
      if (len < 4
                  || (strcmp(path + len - 4, ".CUE") != 0
                        && strcmp(path + len - 4, ".cue") != 0)) {
            return -1;
      }

      /*
       * Check for size.
       */
      fd = open(path, O_RDONLY, 0);
      size = read(fd, buffer, sizeof(buffer));
      close(fd);

      if (sizeof(buffer) <= size) {
            /* This size constraint is basically an educated guess,
             * but it should do */
            fprintf(stderr, "WARNING: no cue sheet\n");
            return -1;
      }

      if (size == 0) {
            /* empty cue sheet should not considered as error */
            return 0;
      }

      /*
       * Read cue sheet.
       */
      fd = open(path, O_RDONLY, 0);
      read(fd, buffer, size);
      close(fd);
      buffer[size + 1]='\0';

      /*
       * Parse cue sheet.
       */
      return NAME_(import_cue_sheet)(s, buffer, path);
}

static int
NAME_(insert_media_iso)(struct cpssp *s, const char *path)
{
      char buffer[16 * 1024];

      sprintf(buffer,
                  "FILE \"%s\" BINARY\n"
                  "  TRACK 01 MODE1/2048\n"
                  "    PREGAP 00:02:00\n"
                  "    INDEX 01 00:00:00\n"
                  "    POSTGAP 00:02:00\n",
                  path);

      return NAME_(import_cue_sheet)(s, buffer, ".");
}

static int
NAME_(insert_media_faum_image)(struct cpssp *s, const char *path)
{
      int fd;
      char buffer[16 * 1024];
      cd_image media_desc;
      char tmppath[1024];


      /*
       * Try to read cd_image struct from the beginning of the image
       */
      fd = open(path, O_RDONLY, 0);
      read(fd, buffer, sizeof(cd_image));
      close(fd);
      memcpy(&media_desc, &buffer, sizeof(cd_image));

      /* is there our magic where it should be? */
      if (0 == strncmp(media_desc.magic,"\211FAUM-CDIMAGE",13)) {
            if (FAUM_IMG_VERSION != media_desc.version) {
                  fprintf(stderr, "CD-Image has wrong version!\n");
                  fprintf(stderr, "Image: %i FAUmachine: %i\n",
                              media_desc.version, FAUM_IMG_VERSION);
                  return -1;
            }
#if DEBUGPCOM
            fprintf(stderr, "FAUM-CDimage recognised\n");
#endif
            s->NAME.faum_image = 1;
            memcpy(&s->NAME.media_desc,&media_desc, sizeof(cd_image));
            strncpy((char *)tmppath, path, sizeof(tmppath));
            storage_change(&s->NAME.media[0], (char *)tmppath);
            NAME_(open_file)(s);
            storage_read_write(IO_READ,
                        &s->NAME.media[0],
                        (char *)&s->NAME.toc,
                        s->NAME.media_desc.offset,
                        sizeof(toc_session));
            return 0;
      } else {
            s->NAME.faum_image = 0;
#if DEBUGPCOM
            fprintf(stderr, "FAUM-CDimage _NOT_ recognised, Magic:\"%s\"\n", media_desc.magic);
#endif
            return -1;
      }
}

static void
NAME_(remove_media)(struct cpssp *cpssp)
{
      NAME_(close_file)(cpssp);

      if ( ! cpssp->NAME.faum_image ) {
            cpssp->NAME.nfiles = 0;
            cpssp->NAME.nareas = 0;
      }
      cpssp->NAME.media_inserted = 0;
      cpssp->NAME.faum_image = 0;
      memset(&(cpssp->NAME.media_desc), 0, sizeof(cd_image));
}

static void
NAME_(change)(struct cpssp *cpssp, const char *path)
{
      if (cpssp->NAME.locked) {
            fprintf(stderr, "%s is locked (maybe mounted).\n", cpssp->NAME.media_name);
            return;
      }

      /*
       * Remove old media (if any).
       */
      NAME_(remove_media)(cpssp);

      if (*path != '\0') {
            /*
             * Insert new media.
             */
#if DEBUGPCOM
            fprintf(stderr, "CD: Image to load into drive: %s\n", path);
#endif
            /* Try to read media */
            if (NAME_(insert_media_faum_image)(cpssp, path) < 0
             && NAME_(insert_media_cue)(cpssp, path) < 0
             && NAME_(insert_media_iso)(cpssp, path) < 0) {
                  fprintf(stderr, "%s: Unknown format.\n", path);
                  /* FIXME: unset config if failed! */
            } else {
                  if (! cpssp->NAME.faum_image) {
                        cpssp->NAME.media_desc.disc_status = 2;
                  }
                  assert(cpssp->NAME.open);
                  cpssp->NAME.media_inserted = 1;
            }
      }

      cpssp->NAME.unit_attention = 1;
      cpssp->NAME.changed = 1;
}

static void
NAME_(check_activity)(void *_cpssp)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;
      /*
       * Timeout
       */
      /*
       * No activity within the last 5 secs.
       * If drive not locked close medium to allow
       * medium change.
       */
      if (! cpssp->NAME.locked) {
            NAME_(close_file)(cpssp);
      }
      time_call_at(time_virt() + 5 * TIME_HZ,
                  NAME_(check_activity), cpssp);
      return;
}

static void
scsi_gen_cdrom_reset(struct cpssp *cpssp)
{
      cpssp->NAME.locked = 0;
      cpssp->NAME.unit_attention = 1;
      cpssp->NAME.sense_key = 0;
      cpssp->NAME.asc = 0;
      cpssp->NAME.ascq = 0;
      /* FIXME reset modepages */
}

static void
NAME_(power_set)(struct cpssp *cpssp, unsigned int val)
{
      if (! val) {
            /* Power-off Event */
            cpssp->NAME.locked = 0;
      }
}

static int
NAME_(phase_select)(struct cpssp *cpssp, uint32_t id)
{
      int retval;

      if (cpssp->NAME.id == id) {
            cpssp->NAME.selected = 1;
            sched_wakeup(&cpssp->NAME.process);
            retval = 1;
      } else {
            retval = 0;
      }

      return retval;
}

#if ! defined(ATAPI) && ! defined(USB)
static int
NAME_(phase_reselect)(struct cpssp *cpssp, uint32_t id)
{
      return 0;
}
#endif

static void
NAME_(atn_set)(struct cpssp *cpssp, unsigned int val)
{
      cpssp->NAME.state_atn = val;
}

static int
NAME_(send)(struct cpssp *cpssp, const uint8_t *buf, unsigned int bufsize)
{
      if (! cpssp->NAME.selected) {
            return 0;
      }

      assert(cpssp->NAME.head + bufsize <= cpssp->NAME.count);

      memcpy(&cpssp->NAME.buf[cpssp->NAME.head], buf, bufsize);
      cpssp->NAME.head += bufsize;

      if (cpssp->NAME.head == cpssp->NAME.count) {
            sched_wakeup(&cpssp->NAME.process);
      }

      return cpssp->NAME.count - cpssp->NAME.head;
}

static int
NAME_(recv)(struct cpssp *cpssp, uint8_t *buf, unsigned int bufsize)
{
      unsigned int count;

      if (! cpssp->NAME.selected) {
            return 0;
      }

      if(cpssp->NAME.tail + bufsize <= cpssp->NAME.count) {
            count = bufsize;
      } else {
            /* some (very few) OSes seem to read data words
             * which are bigger than the available rest of
             * the input buffer (e.g. do an inw when there's
             * only one byte left). -> Read as many bytes
             * as possible and fill the rest with zeros.
             */
            assert(cpssp->NAME.count >= cpssp->NAME.tail);
            faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
                        "cannot copy requested %u byte(s)\n", bufsize);
            count = cpssp->NAME.count - cpssp->NAME.tail;
            faum_log(FAUM_LOG_WARNING, __FUNCTION__, "",
                        "-> copying %u byte(s) instead\n", count);
      }

      memcpy(buf, &cpssp->NAME.buf[cpssp->NAME.tail], count);
      memset(&buf[count], 0, bufsize-count);
      cpssp->NAME.tail += count;

      if (cpssp->NAME.tail == cpssp->NAME.count) {
            sched_wakeup(&cpssp->NAME.process);
      }

      return cpssp->NAME.count - cpssp->NAME.tail;
}

#if DEBUGPCOM
static void
printcmd_buf(struct cpssp *cpssp)
{
      unsigned int i;

      switch (cpssp->NAME.cmd_buf[0]) {
      /* winxp executes this every second... */
      case 0x00: fprintf(stderr, "TEST_UNIT_READY"); break;
      case 0x01: fprintf(stderr, "REZERO_UNIT"); break;
               /* winxp executes this way to often... */
      case 0x03: fprintf(stderr, "REQUEST_SENSE"); break;
      case 0x12: fprintf(stderr, "INQUIRY"); break;
      case 0x1a: fprintf(stderr, "MODE_SENSE_6"); break;
      case 0x1b: fprintf(stderr, "START_STOP_UNIT"); break;
      case 0x1e: fprintf(stderr, "PREVENT_ALLOW_MEDIUM_REMOVAL"); break;
      case 0x25: fprintf(stderr, "READ_CD_RECORDED_CAPACITY"); break;
      case 0x28: fprintf(stderr, "READ_10"); break;
      case 0x2a: fprintf(stderr, "WRITE_10"); break;
      case 0x2b: fprintf(stderr, "SEEK_10"); break;
      case 0x35: fprintf(stderr, "SYNCHRONIZE_CACHE"); break;
      case 0x3c: fprintf(stderr, "READ_BUFFER"); break;
      case 0x42: fprintf(stderr, "READ_SUBCHANNEL"); break;
      case 0x43: fprintf(stderr, "READ_TOC_PMA_ATIP"); break;
      case 0x46: fprintf(stderr, "GET_CONFIGURATION"); break;
      case 0x51: fprintf(stderr, "READ_DISC_INFO"); break;
      case 0x52: fprintf(stderr, "GPCMD_READ_TRACK_INFORMATION"); break;
      case 0x54: fprintf(stderr, "SEND_OPC"); break;
      case 0x55: fprintf(stderr, "MODE_SELECT_10"); break;
      case 0x5a: fprintf(stderr, "MODE_SENSE_10"); break;
      case 0x5b: fprintf(stderr, "CLOSE_TRACK"); break;
      case 0x5c: fprintf(stderr, "READ_BUFFER_CAPACITY"); break;
      case 0x5d: fprintf(stderr, "SEND_CUE_SHEET"); break;
      case 0xa0: fprintf(stderr, "REPORT_LUN"); break;
      case 0xbb: fprintf(stderr, "SET_CD_SPEED"); break;
      default: fprintf(stderr, "0x%02x", cpssp->NAME.cmd_buf[0]); break;
      }
      for (i = 1; i < 12; i++) {
            fprintf(stderr, " 0x%02x", cpssp->NAME.cmd_buf[i]);
      }
      fprintf(stderr, "\n");
}
#endif

static void
NAME_(cmd)(struct cpssp *s)
{
      unsigned int lun;

#if DEBUGPCOM
      fprintf(stderr, "%s: ", __FUNCTION__);
      printcmd_buf(s);
#endif

      lun = (s->NAME.cmd_buf[1] >> 5) & 0x7;
      if (lun != 0 && s->NAME.cmd_buf[0] != GPCMD_INQUIRY) {
            fprintf(stderr, "CD: Error: No INQUIRY and not our lun: %i\n", lun);
            /* prepare sense commands */
            /* Correct error code? FIXME */
            s->NAME.sense_key = NOT_READY;
            s->NAME.asc       = 0x3a;
            s->NAME.ascq      = 0x00; /* FIXME */
            return;
      }

      if (s->NAME.cmd_buf[0] != GPCMD_INQUIRY && s->NAME.unit_attention) {
            if (s->NAME.changed == 1) {
                  NAME_(medium_changed)(s);
                  s->NAME.changed = 0;
            } else {
                  /* power on, reset or bus device reset occured */
                  NAME_(check_condition)(s, UNIT_ATTENTION, 0x29, 0x00);
            }
            s->NAME.unit_attention = 0;
            return;
      }

      if (s->NAME.cmd_buf[0] != GPCMD_REQUEST_SENSE) {
            /* Clear error info *before* executing command. */
            s->NAME.sense_key = 0;
            s->NAME.asc = 0;
            s->NAME.ascq = 0;
      }

      switch (s->NAME.cmd_buf[0]) {
      /*
       * Commands for all devices.
       */
      case GPCMD_REPORT_LUNS: /* 0xa0, mandatory(?) */
            /* SCSI-3 SPC, 6.21, 206 */
            NAME_(report_luns)(s);
            break;
      /*
       * SCSI-3 MMC 11.0, page 24
       * Commands for CD devices
       */
      case GPCMD_INQUIRY: /* 0x12, mandatory */
            NAME_(inquiry)(s);
            break;
#if CD_CHANGER_SUPPORT
      case GPCMD_LOAD_UNLOAD: /* 0xa6, optional */
            NAME_(load_unload)(s);
            break;
#endif
      case GPCMD_MECHANISM_STATUS: /* 0xbd, mandatory */
            NAME_(mechanism_status)(s);
            break;

      case GPCMD_MODE_SELECT_6: /* 0x15, mandatory */
            NAME_(mode_select_6)(s);
            break;

      case GPCMD_MODE_SENSE_6: /* 0x1a, mandatory */
            NAME_(mode_sense_6)(s);
            break;

      case GPCMD_MODE_SENSE_10: /* 0x5a, mandatory */
            NAME_(mode_sense_10)(s);
            break;
#if CD_AUDIO_SUPPORT
      case GPCMD_PAUSE_RESUME: /* 0x4b, mandatory for audio */
            NAME_(pause_resume)(s);
            break;

      case GPCMD_PLAY_AUDIO_10: /* 0x45, mandatory for audio */
            NAME_(play_audio_10)(s);
            break;

      case GPCMD_PLAY_AUDIO_12: /* 0xa5, mandatory for audio */
            NAME_(play_audio_12)(s);
            break;

      case GPCMD_PLAY_AUDIO_MSF: /* 0xa7, mandatory for audio */
            NAME_(play_audio_msf)(s);
            break;
#endif
#if CD_PLAY_CD_SUPPORT
      case GPCMD_PLAY_CD: /* 0xbc, optional */
            NAME_(play_cd)(s);
            break;
#endif
      case GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL: /* 0x1e, mandatory */
            NAME_(prevent_allow_medium_removal)(s);
            break;

      case GPCMD_READ_10: /* 0x28, mandatory */
            NAME_(read_10)(s);
            break;

#if CD_READ_CD_SUPPORT
      case GPCMD_READ_CD: /* 0xbe, optional */
            NAME_(read_cd)(s);
            break;
#endif
#if CD_READ_CD_SUPPORT
      case GPCMD_READ_CD_MSF: /* 0xb9, optional */
            NAME_(read_cd_msf)(s);
            break;
#endif
      case GPCMD_READ_CD_RECORDED_CAPACITY: /* 0x25, mandatory */
            NAME_(read_cd_recorded_capacity)(s);
            break;

#if 0 /* obsolete */
      case GPCMD_READ_HEADER: /* 0x44, mandatory */
            NAME_(read_header)(s);
            break;
#endif

      case GPCMD_READ_SUBCHANNEL: /* 42, mandatory */
            NAME_(read_subchannel)(s);
            break;

      case GPCMD_READ_TOC_PMA_ATIP: /* 0x43, mandatory */
            NAME_(read_toc_pma_atip)(s);
            break;

      case GPCMD_RELEASE_10: /* 0x57, mandatory */
            NAME_(release_10)(s);
            break;

      case GPCMD_REQUEST_SENSE: /* 0x03, mandatory */
            NAME_(request_sense)(s);
            break;

      case GPCMD_RESERVE_10: /* 0x56, mandatory */
            NAME_(reserve_10)(s);
            break;
#if CD_SCAN_CD_SUPPORT
      case GPCMD_SCAN: /* 0xba, optional */
            NAME_(scan)(s);
            break;
#endif
      case GPCMD_SEEK_6: /* 0x0b, mandatory */
            NAME_(seek_6)(s);
            break;

      case GPCMD_SEEK_10: /* 0x2b, mandatory */
            NAME_(seek_10)(s);
            break;

      case GPCMD_SEND_DIAGNOSTIC: /* 0x1d, mandatory */
            NAME_(send_diagnostic)(s);
            break;

      case GPCMD_SET_CD_SPEED: /* 0xbb, mandatory for CD-R/RW */
            NAME_(set_cd_speed)(s);
            break;

      case GPCMD_START_STOP_UNIT: /* 0x1b, mandatory */
            NAME_(start_stop_unit)(s);
            break;
#if CD_AUDIO_SUPPORT
      case GPCMD_STOP_PLAY_SCAN: /* 0x4e, mandatory for audio */
            NAME_(stop_play_scan)(s);
            break;
#endif
      case GPCMD_TEST_UNIT_READY: /* 0x00, mandatory */
            NAME_(test_unit_ready)(s);
            break;

#if CD_WRITER_SUPPORT
            /*
             * SCSI-3 MMC 11.0, page 93
             * Commands for CD-R/RW devices
             */
      case GPCMD_CLOSE_TRACK: /* 0x5b, mandatory for CD-R/RW */
            NAME_(close_track)(s);
            break;
#if CD_WRITER_RW_SUPPORT
      case GPCMD_BLANK: /* 0xa1, mandatory for CD-R/RW */
            NAME_(blank)(s);
            break;

      case GPCMD_FORMAT_UNIT: /* 0x04, optional */
            NAME_(format_unit)(s);
            break;
#endif
#if 0 /* obsolete */
      case GPCMD_READ_BUFFER_CAPACITY: /* 0x5c, optional */
            NAME_(read_buffer_capacity)(s);
            break;
#endif
      case GPCMD_READ_DISC_INFO: /* 0x51, mandatory */
            NAME_(read_disc_info)(s);
            break;
#if 0 /* obsolete */
      case GPCMD_READ_MASTER_CUE: /* 0x59, optional */
            NAME_(read_master_cue)(s);
            break;
#endif
      case GPCMD_READ_TRACK_INFORMATION: /* 0x52, mandatory */
            NAME_(read_track_information)(s);
            break;
      case GPCMD_RESERVE_TRACK: /* 0x53, mandatory */
            NAME_(reserve_track)(s);
            break;
#if CD_WRITER_DAO_SUPPORT
      case GPCMD_SEND_CUE_SHEET: /* 0x5d, optional */
            NAME_(send_cue_sheet)(s);
            break;
#endif
      case GPCMD_SEND_OPC: /* 0x54, optional */
            NAME_(send_opc_information)(s);
            break;
      case GPCMD_SYNCHRONIZE_CACHE: /* 0x35, mandatory */
            NAME_(synchronize_cache)(s);
            break;

      case GPCMD_WRITE_10: /* 0x2a, mandatory */
            NAME_(write_10)(s);
            break;
#endif

      case GPCMD_GET_CONFIGURATION: /* 0x46 */
            NAME_(get_configuration)(s);
            break;
      case GPCMD_MODE_SELECT_10: /* 0x55 */
            NAME_(mode_select_10)(s);
            break;

      default:
#if DEBUGPCOM
            fprintf(stderr, "unknown cmd_buf: 0x%02x\n", s->NAME.cmd_buf[0]);
#endif
            /* prepare sense commands */
            NAME_(check_condition)(s, ILLEGAL_REQUEST, 0x20, 0x00); /* invalid command operation code */
      }

      if (s->NAME.cmd_buf[0] == GPCMD_REQUEST_SENSE) {
            /* Clear error info *after* executing command. */
            s->NAME.sense_key = 0;
            s->NAME.asc = 0;
            s->NAME.ascq = 0;
      }
}

static void __attribute__((__noreturn__))
NAME_(process)(void *_cpssp)
{
      uint8_t skip_remaining = 0;
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      for (;;) {
            NAME_(phase_free)(cpssp);
            cpssp->NAME.selected = 0;
            sched_sleep();

            /* Message Out Phase */
            /* Message system description s2-r10l.pdf P.91 */
            NAME_(phase_msg_out)(cpssp);
            do {
                  /* Receive Message */
                  NAME_(_recv)(cpssp, 1);
                  cpssp->NAME.msg_buf[0] = cpssp->NAME.buf[0];
                  switch (cpssp->NAME.msg_buf[0]) {
                  case 0x00:
                  case 0x02 ... 0x1f:
                  case 0x80 ... 0xff:
                        /* One Byte Message */
                        break;

                  case 0x20 ... 0x2f:
                        /* Two Byte Message */
                        NAME_(_recv)(cpssp, 1);
                        cpssp->NAME.msg_buf[1] = cpssp->NAME.buf[0];
                        break;

                  case 0x01:
                        /* Extended Message */
                        cpssp->NAME.msg_buf[1] = cpssp->NAME.buf[0];
                        NAME_(_recv)(cpssp, cpssp->NAME.msg_buf[1]);
                        memcpy(&cpssp->NAME.msg_buf[2], cpssp->NAME.buf,
                                    cpssp->NAME.msg_buf[1]);
                        break;

                  default:
                        assert(0);
                  }

                  /* Process message... - FIXME */
                  if (cpssp->NAME.msg_buf[0] == 0) {
                        /* ??? Message */
                        /* FIXME */

                        /* see s2-r10l.pdf table 10 p.92ff */
                  } else if (0x80 <= cpssp->NAME.msg_buf[0]) {
                        /* IDENTIFY Message */
                        cpssp->NAME.lun = cpssp->NAME.msg_buf[0] & 0x7;

                  } else if (cpssp->NAME.msg_buf[0] == 0x01
                              && cpssp->NAME.msg_buf[1] == 0x01
                              && cpssp->NAME.msg_buf[2] == 0x00) {
                        /* ??? Message */

                  } else if (cpssp->NAME.msg_buf[0] == 0x01
                              && cpssp->NAME.msg_buf[1] == 0x01
                              && cpssp->NAME.msg_buf[2] == 0x03) {
                        /* ??? Message */
                        /* FIXME */

                  } else if (cpssp->NAME.msg_buf[0] == 0x01
                              && cpssp->NAME.msg_buf[1] == 0x01
                              && cpssp->NAME.msg_buf[2] == 0x09) {
                        /* ??? Message */

                  } else if (cpssp->NAME.msg_buf[0] == 0x01
                              && cpssp->NAME.msg_buf[1] == 0x01
                              && cpssp->NAME.msg_buf[2] == 0x19) {
                        /* ??? Message */

                  } else if (cpssp->NAME.msg_buf[0] == 0x01
                              && cpssp->NAME.msg_buf[1] == 0x06
                              && cpssp->NAME.msg_buf[2] == 0x04) {
                        /* Parallel Negotiation Message */
                        /* FIXME */

                  } else if (cpssp->NAME.msg_buf[0] == 0x01
                              && cpssp->NAME.msg_buf[1] == 0x03
                              && cpssp->NAME.msg_buf[2] == 0x01) {
                        /* Synchron Mode Negotiation Message */
                        /* FIXME */

                  } else if (cpssp->NAME.msg_buf[0] == 0x06) {
#if DEBUGPCOM
                        fprintf(stderr, "Received abort\n");
#endif
                        /* Abort */
                        skip_remaining = 1;
                        break;

                  } else if (cpssp->NAME.msg_buf[0] == 0x07) {
#if DEBUGPCOM
                        fprintf(stderr, "Received message rejected\n");
#endif
                        /* Message Rejected */
                        fixme();

                  } else if (cpssp->NAME.msg_buf[0] == 0x0c) {
#if DEBUGPCOM
                        fprintf(stderr, "Received bus device reset message\n");
#endif
                        /* Bus Device Reset Message */
                        skip_remaining = 1;
                        scsi_gen_cdrom_reset(cpssp); /* include unit_attention */
                        break;

                  } else {
                        fprintf(stderr, "%s: message type 0x%02x/0x%02x/0x%02x unknown.\n",
                                    __FUNCTION__,
                                    cpssp->NAME.msg_buf[0],
                                    cpssp->NAME.msg_buf[1],
                                    cpssp->NAME.msg_buf[2]);
                  }
            } while (cpssp->NAME.state_atn);

            if (skip_remaining == 1) {
                  skip_remaining = 0;
                  continue;
            }

            /* Command Phase */
            NAME_(phase_cmd)(cpssp);
#ifdef ATAPI
            NAME_(_recv)(cpssp, 12);
            memcpy(cpssp->NAME.cmd_buf, cpssp->NAME.buf, 12);
#else
            NAME_(_recv)(cpssp, 1);
            cpssp->NAME.cmd_buf[0] = cpssp->NAME.buf[0];
            switch (cpssp->NAME.cmd_buf[0]) {
            case 0x00 ... 0x1f: /* Group 0: 6 Bytes Command */
                  NAME_(_recv)(cpssp, 6 - 1);
                  memcpy(&cpssp->NAME.cmd_buf[1], cpssp->NAME.buf, 6 - 1);
                  break;
            case 0x20 ... 0x3f: /* Group 1: 10 Bytes Command */
                  NAME_(_recv)(cpssp, 10 - 1);
                  memcpy(&cpssp->NAME.cmd_buf[1], cpssp->NAME.buf, 10 - 1);
                  break;
            case 0x40 ... 0x5f: /* Group 2: 10 Bytes Command */
                  NAME_(_recv)(cpssp, 10 - 1);
                  memcpy(&cpssp->NAME.cmd_buf[1], cpssp->NAME.buf, 10 - 1);
                  break;
            case 0x80 ... 0x9f: /* Group 4: 16 Bytes Command */
                  NAME_(_recv)(cpssp, 16 - 1);
                  memcpy(&cpssp->NAME.cmd_buf[1], cpssp->NAME.buf, 16 - 1);
                  break;
            case 0xa0 ... 0xbf: /* Group 5: 12-bytes-cmd */
                  NAME_(_recv)(cpssp, 12 - 1);
                  memcpy(&cpssp->NAME.cmd_buf[1], cpssp->NAME.buf, 12 - 1);
                  break;
            default:
                  /* FIXME there could be vendor specific commands */
                  fprintf(stderr, "%s: command 0x%02x unknown.\n",
                              __FUNCTION__,
                              cpssp->NAME.cmd_buf[0]);
            }
#endif
#if 0
            fprintf(stderr, "%s %d 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", __FUNCTION__, __LINE__,
                        cpssp->NAME.cmd_buf[0], cpssp->NAME.cmd_buf[1],
                        cpssp->NAME.cmd_buf[2], cpssp->NAME.cmd_buf[3],
                        cpssp->NAME.cmd_buf[4], cpssp->NAME.cmd_buf[5],
                        cpssp->NAME.cmd_buf[6], cpssp->NAME.cmd_buf[7],
                        cpssp->NAME.cmd_buf[8], cpssp->NAME.cmd_buf[9],
                        cpssp->NAME.cmd_buf[10], cpssp->NAME.cmd_buf[11],
                        cpssp->NAME.cmd_buf[12], cpssp->NAME.cmd_buf[13],
                        cpssp->NAME.cmd_buf[14], cpssp->NAME.cmd_buf[15]);
#endif

            /* Execute Command */
            NAME_(cmd)(cpssp);

            /* Status Phase */
            NAME_(phase_status)(cpssp);
            if ( cpssp->NAME.sense_key == 0 ) {
                  cpssp->NAME.buf[0] = 0;
            } else {
                  cpssp->NAME.buf[0] = 2;
            }
            NAME_(_send)(cpssp, 1);

            NAME_(phase_msg_in)(cpssp);
            switch (cpssp->NAME.cmd_buf[0]) {
            /* any not implemented command should go here */
            case 0xa0: /* REPORT_LUNS */
                  /* reject command */
                  /* FIXME should be 0x07 (waiting for helmi) */
                  cpssp->NAME.buf[0] = 0x00;
                  break;
            default: /* Completion Byte */
                  /* send 0 here, because command completed and status sent */
                  cpssp->NAME.buf[0] = 0x00;
            }
            NAME_(_send)(cpssp, 1);
      }
}

static void
NAME_(init)(struct cpssp *cpssp)
{
      unsigned int i;

      for (i = 0; i < sizeof(cpssp->NAME.media) / sizeof(cpssp->NAME.media[0]); i++) {
            storage_init(&cpssp->NAME.media[i]);
      }
      time_call_at(time_virt() + 5 * TIME_HZ,
                  NAME_(check_activity), cpssp);

      sched_process_init(&cpssp->NAME.process, NAME_(process), cpssp);
}

static void
NAME_(create)(struct cpssp *cpssp, const char *path, unsigned int id)
{
      unsigned int i;

      for (i = 0; i < MAX_DATA_TRACKS; i++) {
            storage_create(&cpssp->NAME.media[i], path,
                        1, /* writeable */
                        "", 0,
                        0, /* Blocksize */
                        0, 0, 0, 0);
      }
      cpssp->NAME.id = id;
      cpssp->NAME.open = 0;
      cpssp->NAME.media_inserted = 0;
}

static void
NAME_(destroy)(struct cpssp *cpssp)
{
      /* Nothing to do... */
}

#endif /* BEHAVIOR */

#undef MAX_DATA_TRACKS
#undef MAX_CD_AREAS

Generated by  Doxygen 1.6.0   Back to index