/* cdrdao - write audio CD-Rs in disc-at-once mode * * Copyright (C) 1998-2002 Andreas Mueller * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include "GenericMMC.h" #include "port.h" #include "Toc.h" #include "util.h" #include "PQSubChannel16.h" #include "PWSubChannel96.h" #include "CdTextEncoder.h" // Variants for cue sheet generation // do not set ctl flags for ISRC cue sheet entries #define CUE_VAR_ISRC_NO_CTL 0x1 // do not set start of lead-in time in lead-in cue sheet entry #define CUE_VAR_CDTEXT_NO_TIME 0x2 #define CUE_VAR_MAX 0x3 // Variants for write parameters mode page //do not set data block type to block size 2448 if a CD-TEXT lead-in is written #define WMP_VAR_CDTEXT_NO_DATA_BLOCK_TYPE 0x1 #define WMP_VAR_MAX 0x1 GenericMMC::GenericMMC(ScsiIf *scsiIf, unsigned long options) : CdrDriver(scsiIf, options) { int i; driverName_ = "Generic SCSI-3/MMC - Version 2.0"; speed_ = 0; rspeed_ = 0; simulate_ = 1; encodingMode_ = 1; scsiTimeout_ = 0; cdTextEncoder_ = NULL; driveInfo_ = NULL; memset(&diskInfo_, 0, sizeof(DiskInfo)); for (i = 0; i < maxScannedSubChannels_; i++) { if (options_ & OPT_MMC_USE_PQ) scannedSubChannels_[i] = new PQSubChannel16; else scannedSubChannels_[i] = new PWSubChannel96; } // MMC drives usually return little endian samples audioDataByteOrder_ = 0; } GenericMMC::~GenericMMC() { int i; for (i = 0; i < maxScannedSubChannels_; i++) { delete scannedSubChannels_[i]; scannedSubChannels_[i] = NULL; } delete cdTextEncoder_; cdTextEncoder_ = NULL; delete driveInfo_; driveInfo_ = NULL; } // static constructor CdrDriver *GenericMMC::instance(ScsiIf *scsiIf, unsigned long options) { return new GenericMMC(scsiIf, options); } int GenericMMC::checkToc(const Toc *toc) { int err = CdrDriver::checkToc(toc); int e; if (options_ & OPT_MMC_CD_TEXT) { if ((e = toc->checkCdTextData()) > err) err = e; } return err; } int GenericMMC::subChannelEncodingMode(TrackData::SubChannelMode sm) const { int ret = 0; switch (sm) { case TrackData::SUBCHAN_NONE: ret = 0; break; case TrackData::SUBCHAN_RW: #if 0 if (options_ & OPT_MMC_NO_RW_PACKED) ret = 1; // have to encode the R-W sub-channel data else ret = 0; #endif ret = 0; break; case TrackData::SUBCHAN_RW_RAW: // raw R-W sub-channel writing is assumed to be always supported ret = 1; break; } return ret; } // sets speed // return: 0: OK // 1: illegal speed int GenericMMC::speed(int s) { speed_ = s; if (selectSpeed() != 0) return 1; return 0; } int GenericMMC::speed() { const DriveInfo *di; delete driveInfo_; driveInfo_ = NULL; if ((di = driveInfo(true)) == NULL) { return 0; } return speed2Mult(di->currentWriteSpeed); } // sets fspeed // return: true: OK // false: illegal speed bool GenericMMC::rspeed(int s) { rspeed_ = s; if (selectSpeed() != 0) return false; return true; } int GenericMMC::rspeed() { const DriveInfo *di; delete driveInfo_; driveInfo_ = NULL; if ((di = driveInfo(true)) == NULL) { return 0; } return speed2Mult(di->currentReadSpeed); } // loads ('unload' == 0) or ejects ('unload' == 1) tray // return: 0: OK // 1: scsi command failed int GenericMMC::loadUnload(int unload) const { unsigned char cmd[6]; memset(cmd, 0, 6); cmd[0] = 0x1b; // START/STOP UNIT if (unload) { cmd[4] = 0x02; // LoUnlo=1, Start=0 } else { cmd[4] = 0x03; // LoUnlo=1, Start=1 } if (sendCmd(cmd, 6, NULL, 0, NULL, 0) != 0) { message(-2, "Cannot load/unload medium."); return 1; } return 0; } // Checks for ready status of the drive after a write operation // Return: 0: drive ready // 1: error occured // 2: drive not ready int GenericMMC::checkDriveReady() const { unsigned char cmd[10]; unsigned char data[4]; int ret; ret = testUnitReady(0); if (ret == 0) { // testUnitReady reports ready status but this might actually not // be the truth -> additionally check the READ DISK INFO command memset(cmd, 0, 10); cmd[0] = 0x51; // READ DISK INFORMATION cmd[8] = 4; ret = sendCmd(cmd, 10, NULL, 0, data, 4, 0); if (ret == 2) { const unsigned char *sense; int senseLen; ret = 0; // indicates ready status sense = scsiIf_->getSense(senseLen); if (senseLen >= 14 && (sense[2] & 0x0f) == 0x2 && sense[7] >= 6 && sense[12] == 0x4 && (sense[13] == 0x8 || sense[13] == 0x7)) { // Not Ready, long write in progress ret = 2; // indicates not ready status } } } return ret; } // Performs complete blanking of a CD-RW. // return: 0: OK // 1: scsi command failed int GenericMMC::blankDisk(BlankingMode mode) { unsigned char cmd[12]; int ret, progress; time_t startTime, endTime; setSimulationMode(0); memset(cmd, 0, 12); cmd[0] = 0xa1; // BLANK switch (mode) { case BLANK_FULL: cmd[1] = 0x0; // erase complete disk break; case BLANK_MINIMAL: cmd[1] = 0x1; // erase PMA, lead-in and 1st track's pre-gap break; } cmd[1] |= 1 << 4; // immediate return sendBlankCdProgressMsg(0); if (sendCmd(cmd, 12, NULL, 0, NULL, 0, 1) != 0) { message(-2, "Cannot erase CD-RW."); return 1; } time(&startTime); progress = 0; do { mSleep(2000); ret = checkDriveReady(); if (ret == 1) { message(-2, "Test Unit Ready command failed."); } progress += 10; sendBlankCdProgressMsg(progress); if (progress >= 1000) progress = 0; } while (ret == 2); if (ret == 0) sendBlankCdProgressMsg(1000); time(&endTime); message(2, "Blanking time: %ld seconds", endTime - startTime); return ret; } // sets read/write speed and simulation mode // return: 0: OK // 1: scsi command failed int GenericMMC::selectSpeed() { unsigned char cmd[12]; int spd; memset(cmd, 0, 12); cmd[0] = 0xbb; // SET CD SPEED // select maximum read speed if (rspeed_ == 0) { cmd[2] = 0xff; cmd[3] = 0xff; } else { spd = mult2Speed(rspeed_); cmd[2] = spd >> 8; cmd[3] = spd; } // select maximum write speed if (speed_ == 0) { cmd[4] = 0xff; cmd[5] = 0xff; } else { spd = mult2Speed(speed_); cmd[4] = spd >> 8; cmd[5] = spd; } if ((options_ & OPT_MMC_YAMAHA_FORCE_SPEED) != 0 && writeSpeedControl()) cmd[11] = 0x80; // enable Yamaha's force speed if (sendCmd(cmd, 12, NULL, 0, NULL, 0) != 0) { message(-2, "Cannot set cd speed."); return 1; } return 0; } // Determins start and length of lead-in and length of lead-out. // return: 0: OK // 1: SCSI command failed int GenericMMC::getSessionInfo() { unsigned char cmd[10]; unsigned long dataLen = 34; unsigned char data[34]; memset(cmd, 0, 10); memset(data, 0, dataLen); cmd[0] = 0x51; // READ DISK INFORMATION cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-2, "Cannot retrieve disk information."); return 1; } leadInStart_ = Msf(data[17], data[18], data[19]); if (leadInStart_.lba() >= Msf(80, 0, 0).lba()) { leadInLen_ = 450000 - leadInStart_.lba(); if (fullBurn_) { leadOutLen_ = (userCapacity_ ? Msf(userCapacity_, 0, 0).lba() : diskInfo_.capacity) + Msf(1, 30, 0).lba() - toc_->length().lba() - diskInfo_.thisSessionLba - 150; // Fill all rest space if (leadOutLen_ < Msf(1, 30, 0).lba()) { leadOutLen_ = Msf(1, 30, 0).lba(); // 90 seconds lead-out } } else { leadOutLen_ = Msf(1, 30, 0).lba(); // 90 seconds lead-out } } else { leadInLen_ = Msf(1, 0, 0).lba(); leadOutLen_ = Msf(0, 30, 0).lba(); } message(4, "Lead-in start: %s length: %ld", leadInStart_.str(), leadInLen_); message(4, "Lead-out length: %ld", leadOutLen_); return 0; } bool GenericMMC::readBufferCapacity(long *capacity, long *available) { unsigned char cmd[10]; unsigned char data[12]; long bufsize; memset(cmd, 0, 10); memset(data, 0, 12); cmd[0] = 0x5c; // READ BUFFER CAPACITY cmd[8] = 12; if (sendCmd(cmd, 10, NULL, 0, data, 12) != 0) { message(-2, "Read buffer capacity failed."); return false; } *capacity = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7]; *available = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11]; return true; } int GenericMMC::performPowerCalibration() { unsigned char cmd[10]; int ret; memset(cmd, 0, 10); cmd[0] = 0x54; // SEND OPC INFORMATION cmd[1] = 1; message(2, "Executing power calibration..."); if ((ret = sendCmd(cmd, 10, NULL, 0, NULL, 0)) != 0) { if (ret == 2) { const unsigned char *sense; int senseLen; sense = scsiIf_->getSense(senseLen); if(senseLen >= 14 && (sense[2] & 0x0f) == 0x5 && sense[7] >= 6 && sense[12] == 0x20 && sense[13] == 0x0) { message(2, "Power calibration not supported."); return 0; } else { message(-2, "Power calibration failed."); } } else { message(-2, "Power calibration failed."); } return 1; } message(2, "Power calibration successful."); return 0; } // Sets write parameters via mode page 0x05. // return: 0: OK // 1: scsi command failed int GenericMMC::setWriteParameters(unsigned long variant) { unsigned char mp[0x38]; unsigned char mpHeader[8]; unsigned char blockDesc[8]; if (getModePage(5/*write parameters mode page*/, mp, 0x38, mpHeader, blockDesc, 1) != 0) { message(-2, "Cannot retrieve write parameters mode page."); return 1; } mp[0] &= 0x7f; // clear PS flag mp[2] &= 0xe0; mp[2] |= 0x02; // write type: Session-at-once if (simulate_) { mp[2] |= 1 << 4; // test write } const DriveInfo *di; if ((di = driveInfo(true)) != NULL) { if (di->burnProof) { // This drive has BURN-Proof function. // Enable it unless explicitly disabled. if (bufferUnderRunProtection()) { message(2, "Turning BURN-Proof on"); mp[2] |= 0x40; } else { message(2, "Turning BURN-Proof off"); mp[2] &= ~0x40; } } RicohSetWriteOptions(di); } mp[3] &= 0x3f; // Multi-session: No B0 pointer, next session not allowed if (multiSession_ != 0) mp[3] |= 0x03 << 6; // open next session else if (!diskInfo_.empty) mp[3] |= 0x01 << 6; // use B0=FF:FF:FF when closing last session of a // multi session CD-R message(4, "Multi session mode: %d", mp[3] >> 6); mp[4] &= 0xf0; // Data Block Type: raw data, block size: 2352 (I think not // used for session at once writing) if (cdTextEncoder_ != NULL) { if ((variant & WMP_VAR_CDTEXT_NO_DATA_BLOCK_TYPE) == 0) mp[4] |= 3; /* Data Block Type: raw data with raw P-W sub-channel data, block size 2448, required for CD-TEXT lead-in writing according to the SCSI/MMC-3 manual */ } message(4, "Data block type: %u", mp[4] & 0x0f); mp[8] = sessionFormat(); if (!diskInfo_.empty) { // use the toc type of the already recorded sessions switch (diskInfo_.diskTocType) { case 0x00: case 0x10: case 0x20: mp[8] = diskInfo_.diskTocType; break; } } message(4, "Toc type: 0x%x", mp[8]); if (setModePage(mp, mpHeader, NULL, 0) != 0) { message(-2, "Cannot set write parameters mode page."); return 1; } return 0; } // Sets simulation mode via mode page 0x05. // return: 0: OK // 1: scsi command failed int GenericMMC::setSimulationMode(int showMessage) { unsigned char mp[0x38]; unsigned char mpHeader[8]; if (getModePage(5/*write parameters mode page*/, mp, 0x38, mpHeader, NULL, showMessage) != 0) { if (showMessage) message(-2, "Cannot retrieve write parameters mode page."); return 1; } mp[0] &= 0x7f; // clear PS flag if (simulate_) mp[2] |= 1 << 4; // test write else mp[2] &= ~(1 << 4); if (setModePage(mp, mpHeader, NULL, showMessage) != 0) { if (showMessage) message(-2, "Cannot set write parameters mode page."); return 1; } return 0; } int GenericMMC::getNWA(long *nwa) { unsigned char cmd[10]; int infoblocklen = 16; unsigned char info[16]; long lba = 0; cmd[0] = 0x52; // READ TRACK INFORMATION cmd[1] = 0x01; // track instead of lba designation cmd[2] = 0x00; cmd[3] = 0x00; cmd[4] = 0x00; cmd[5] = 0xff; // invisible track cmd[6] = 0x00; // reserved cmd[7] = infoblocklen << 8; cmd[8] = infoblocklen; // alloc length cmd[9] = 0x00; // Control Byte if (sendCmd(cmd, 10, NULL, 0, info, infoblocklen) != 0) { message(-2, "Cannot get Track Information Block."); return 1; } #if 0 message(3,"Track Information Block"); for (int i=0;icatalogValid()) { len += 2; } for (t = itr.first(start, end), trackNr = 1; t != NULL; t = itr.next(start, end), trackNr++) { len += 1; // entry for track if (t->start().lba() != 0 && trackNr > 1) { len += 1; // entry for pre-gap } if (t->type() == TrackData::AUDIO && t->isrcValid()) { len += 2; // entries for ISRC code } len += t->nofIndices(); // entry for each index increment } cueSheet = new unsigned char[len * 8]; n = 0; if (toc_->leadInMode() == TrackData::AUDIO) { ctl = 0; } else { ctl = 0x40; } if (toc_->catalogValid()) { // fill catalog number entry cueSheet[n*8] = 0x02 | ctl; cueSheet[(n+1)*8] = 0x02 | ctl; for (i = 1; i <= 13; i++) { if (i < 8) { cueSheet[n*8 + i] = toc_->catalog(i-1) + '0'; } else { cueSheet[(n+1)*8 + i - 7] = toc_->catalog(i-1) + '0'; } } cueSheet[(n+1)*8+7] = 0; n += 2; } // entry for lead-in cueSheet[n*8] = 0x01 | ctl; // CTL/ADR cueSheet[n*8+1] = 0; // Track number cueSheet[n*8+2] = 0; // Index if (cdTextEncoder_ != NULL) { cueSheet[n*8+3] = 0x41; // Data Form: CD-DA with P-W sub-channels, // main channel data generated by device } else { cueSheet[n*8+3] = leadInOutDataMode(toc_->leadInMode()); } cueSheet[n*8+4] = 0; // Serial Copy Management System if (cdTextEncoder_ != NULL && (variant & CUE_VAR_CDTEXT_NO_TIME) == 0) { cueSheet[n*8+5] = leadInStart_.min(); cueSheet[n*8+6] = leadInStart_.sec(); cueSheet[n*8+7] = leadInStart_.frac(); } else { cueSheet[n*8+5] = 0; // MIN cueSheet[n*8+6] = 0; // SEC cueSheet[n*8+7] = 0; // FRAME } n++; int firstTrack = 1; for (t = itr.first(start, end), trackNr = trackOffset + 1; t != NULL; t = itr.next(start, end), trackNr++) { if (encodingMode_ == 0) { // just used for some experiments with raw writing dataMode = 0; } else { switch (t->type()) { case TrackData::AUDIO: dataMode = 0; break; case TrackData::MODE1: case TrackData::MODE1_RAW: dataMode = 0x10; break; case TrackData::MODE2: dataMode = 0x30; break; case TrackData::MODE2_RAW: // assume it contains XA sectors case TrackData::MODE2_FORM1: case TrackData::MODE2_FORM2: case TrackData::MODE2_FORM_MIX: dataMode = 0x20; break; default: dataMode = 0; break; } } // add mode for sub-channel writing dataMode |= subChannelDataForm(t->subChannelType(), subChannelEncodingMode(t->subChannelType())); ctl = 0; if (t->copyPermitted()) { ctl |= 0x20; } if (t->type() == TrackData::AUDIO) { // audio track if (t->preEmphasis()) { ctl |= 0x10; } if (t->audioType() == 1) { ctl |= 0x80; } if (t->isrcValid()) { if ((variant & CUE_VAR_ISRC_NO_CTL) == 0) cueSheet[n*8] = ctl | 0x03; else cueSheet[n*8] = 0x03; cueSheet[n*8+1] = trackNr; cueSheet[n*8+2] = t->isrcCountry(0); cueSheet[n*8+3] = t->isrcCountry(1); cueSheet[n*8+4] = t->isrcOwner(0); cueSheet[n*8+5] = t->isrcOwner(1); cueSheet[n*8+6] = t->isrcOwner(2); cueSheet[n*8+7] = t->isrcYear(0) + '0'; n++; if ((variant & CUE_VAR_ISRC_NO_CTL) == 0) cueSheet[n*8] = ctl | 0x03; else cueSheet[n*8] = 0x03; cueSheet[n*8+1] = trackNr; cueSheet[n*8+2] = t->isrcYear(1) + '0'; cueSheet[n*8+3] = t->isrcSerial(0) + '0'; cueSheet[n*8+4] = t->isrcSerial(1) + '0'; cueSheet[n*8+5] = t->isrcSerial(2) + '0'; cueSheet[n*8+6] = t->isrcSerial(3) + '0'; cueSheet[n*8+7] = t->isrcSerial(4) + '0'; n++; } } else { // data track ctl |= 0x40; } if (firstTrack) { Msf sessionStart(lbaOffset); // entry for gap before first track cueSheet[n*8] = ctl | 0x01; cueSheet[n*8+1] = trackNr; cueSheet[n*8+2] = 0; // Index 0 cueSheet[n*8+3] = dataMode; // Data Form cueSheet[n*8+4] = 0; // Serial Copy Management System cueSheet[n*8+5] = sessionStart.min(); cueSheet[n*8+6] = sessionStart.sec(); cueSheet[n*8+7] = sessionStart.frac(); n++; } else if (t->start().lba() != 0) { // entry for pre-gap Msf pstart(lbaOffset + start.lba() - t->start().lba() + 150); cueSheet[n*8] = ctl | 0x01; cueSheet[n*8+1] = trackNr; cueSheet[n*8+2] = 0; // Index 0 indicates pre-gap cueSheet[n*8+3] = dataMode; // Data Form cueSheet[n*8+4] = 0; // no alternate copy bit cueSheet[n*8+5] = pstart.min(); cueSheet[n*8+6] = pstart.sec(); cueSheet[n*8+7] = pstart.frac(); n++; } Msf tstart(lbaOffset + start.lba() + 150); cueSheet[n*8] = ctl | 0x01; cueSheet[n*8+1] = trackNr; cueSheet[n*8+2] = 1; // Index 1 cueSheet[n*8+3] = dataMode; // Data Form cueSheet[n*8+4] = 0; // no alternate copy bit cueSheet[n*8+5] = tstart.min(); cueSheet[n*8+6] = tstart.sec(); cueSheet[n*8+7] = tstart.frac(); n++; for (i = 0; i < t->nofIndices(); i++) { index = tstart + t->getIndex(i); cueSheet[n*8] = ctl | 0x01; cueSheet[n*8+1] = trackNr; cueSheet[n*8+2] = i+2; // Index cueSheet[n*8+3] = dataMode; // Data Form cueSheet[n*8+4] = 0; // no alternate copy bit cueSheet[n*8+5] = index.min(); cueSheet[n*8+6] = index.sec(); cueSheet[n*8+7] = index.frac(); n++; } firstTrack = 0; } assert(n == len - 1); // entry for lead out Msf lostart(lbaOffset + toc_->length().lba() + 150); ctl = toc_->leadOutMode() == TrackData::AUDIO ? 0 : 0x40; cueSheet[n*8] = ctl | 0x01; cueSheet[n*8+1] = 0xaa; cueSheet[n*8+2] = 1; // Index 1 cueSheet[n*8+3] = leadInOutDataMode(toc_->leadOutMode()); cueSheet[n*8+4] = 0; // no alternate copy bit cueSheet[n*8+5] = lostart.min(); cueSheet[n*8+6] = lostart.sec(); cueSheet[n*8+7] = lostart.frac(); message(3, "\nCue Sheet (variant %lx):", variant); message(3, "CTL/ TNO INDEX DATA SCMS MIN SEC FRAME"); message(3, "ADR FORM"); for (n = 0; n < len; n++) { message(3, "%02x %02x %02x %02x %02x %02d %02d %02d", cueSheet[n*8], cueSheet[n*8+1], cueSheet[n*8+2], cueSheet[n*8+3], cueSheet[n*8+4], cueSheet[n*8+5], cueSheet[n*8+6], cueSheet[n*8+7]); } *cueSheetLen = len * 8; return cueSheet; } int GenericMMC::sendCueSheet() { unsigned char cmd[10]; long cueSheetLen; unsigned long variant; unsigned char *cueSheet; int cueSheetSent = 0; for (variant = 0; variant <= CUE_VAR_MAX; variant++) { if (cdTextEncoder_ == NULL && (variant & CUE_VAR_CDTEXT_NO_TIME) != 0) { // skip CD-TEXT variants if no CD-TEXT has to be written continue; } cueSheet = createCueSheet(variant, &cueSheetLen); if (cueSheet != NULL) { memset(cmd, 0, 10); cmd[0] = 0x5d; // SEND CUE SHEET cmd[6] = cueSheetLen >> 16; cmd[7] = cueSheetLen >> 8; cmd[8] = cueSheetLen; if (sendCmd(cmd, 10, cueSheet, cueSheetLen, NULL, 0, 0) != 0) { delete[] cueSheet; } else { message(3, "Drive accepted cue sheet variant %lx.", variant); delete[] cueSheet; cueSheetSent = 1; break; } } } if (cueSheetSent) { return 0; } else { message(-2, "Drive does not accept any cue sheet variant - please report."); return 1; } } int GenericMMC::initDao(const Toc *toc) { long n; blockLength_ = AUDIO_BLOCK_LEN + MAX_SUBCHANNEL_LEN; blocksPerWrite_ = scsiIf_->maxDataLen() / blockLength_; assert(blocksPerWrite_ > 0); toc_ = toc; if (options_ & OPT_MMC_CD_TEXT) { delete cdTextEncoder_; cdTextEncoder_ = new CdTextEncoder(toc_); if (cdTextEncoder_->encode() != 0) { message(-2, "CD-TEXT encoding failed."); return 1; } if (cdTextEncoder_->getSubChannels(&n) == NULL || n == 0) { delete cdTextEncoder_; cdTextEncoder_ = NULL; } //return 1; } diskInfo(); if (!diskInfo_.valid.empty || !diskInfo_.valid.append) { message(-2, "Cannot determine status of inserted medium."); return 1; } if (!diskInfo_.append) { message(-2, "Inserted medium is not appendable."); return 1; } if (selectSpeed() != 0 || getSessionInfo() != 0) { return 1; } // allocate buffer for writing zeros n = blocksPerWrite_ * blockLength_; delete[] zeroBuffer_; zeroBuffer_ = new char[n]; memset(zeroBuffer_, 0, n); return 0; } int GenericMMC::startDao() { unsigned long variant; int writeParametersSet = 0; scsiTimeout_ = scsiIf_->timeout(3 * 60); for (variant = 0; variant <= WMP_VAR_MAX; variant++) { if (cdTextEncoder_ == NULL && (variant & WMP_VAR_CDTEXT_NO_DATA_BLOCK_TYPE) != 0) { // skip CD-TEXT variants if no CD-TEXT has to be written continue; } if (setWriteParameters(variant) == 0) { message(3, "Drive accepted write parameter mode page variant %lx.", variant); writeParametersSet = 1; break; } } if (!writeParametersSet) { message(-2, "Cannot setup write parameters for session-at-once mode."); message(-2, "Please try to use the 'generic-mmc-raw' driver."); return 1; } if (!simulate_) { if (performPowerCalibration() != 0) { if (!force()) { message(-2, "Use option --force to ignore this error."); return 1; } else { message(-2, "Ignored because of option --force."); } } } // It does not hurt if the following command fails. // The Panasonic CW-7502 needs it, unfortunately it returns the wrong // data so we ignore the returned data and start writing always with // LBA -150. getNWA(NULL); if (sendCueSheet() != 0) return 1; //message(2, "Writing lead-in and gap..."); if (writeCdTextLeadIn() != 0) { return 1; } long lba = diskInfo_.thisSessionLba - 150; TrackData::Mode mode = TrackData::AUDIO; TrackData::SubChannelMode subChanMode = TrackData::SUBCHAN_NONE; TrackIterator itr(toc_); const Track *tr; if ((tr = itr.first()) != NULL) { mode = tr->type(); subChanMode = tr->subChannelType(); } if (writeZeros(mode, subChanMode, lba, lba + 150, 150) != 0) { return 1; } return 0; } int GenericMMC::finishDao() { int ret; flushCache(); /* Some drives never return to a ready state after writing * the lead-out. This is a try to solve this problem. */ while ((ret = checkDriveReady()) == 2) { mSleep(2000); } if (ret != 0) message(-1, "TEST UNIT READY failed after recording."); message(2, "Flushing cache..."); if (flushCache() != 0) { return 1; } scsiIf_->timeout(scsiTimeout_); delete cdTextEncoder_; cdTextEncoder_ = NULL; delete[] zeroBuffer_, zeroBuffer_ = NULL; return 0; } void GenericMMC::abortDao() { flushCache(); delete cdTextEncoder_; cdTextEncoder_ = NULL; } // Writes data to target, the block length depends on the actual writing // 'mode'. 'len' is number of blocks to write. // 'lba' specifies the next logical block address for writing and is updated // by this function. // return: 0: OK // 1: scsi command failed int GenericMMC::writeData(TrackData::Mode mode, TrackData::SubChannelMode sm, long &lba, const char *buf, long len) { assert(blocksPerWrite_ > 0); int writeLen = 0; unsigned char cmd[10]; long blockLength = blockSize(mode, sm); int retry; int ret; #if 0 long bufferCapacity; int waitForBuffer; int speedFrac; if (speed_ > 0) speedFrac = 75 * speed_; else speedFrac = 75 * 10; // adjust this value when the first >10x burner is out #endif #if 0 long sum, i; sum = 0; for (i = 0; i < len * blockLength; i++) { sum += buf[i]; } message(0, "W: %ld: %ld, %ld, %ld", lba, blockLength, len, sum); #endif memset(cmd, 0, 10); cmd[0] = 0x2a; // WRITE1 while (len > 0) { writeLen = (len > blocksPerWrite_ ? blocksPerWrite_ : len); cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; cmd[7] = writeLen >> 8; cmd[8] = writeLen & 0xff; #if 0 do { long available waitForBuffer = 0; if (readBufferCapacity(&bufferCapacity, &available) == 0) { //message(0, "Buffer Capacity: %ld", bufferCapacity); if (bufferCapacity < writeLen * blockLength) { long t = 1000 * writeLen; t /= speedFrac; if (t <= 0) t = 1; message(0, "Waiting for %ld msec at lba %ld", t, lba); mSleep(t); waitForBuffer = 1; } } } while (waitForBuffer); #endif do { retry = 0; ret = sendCmd(cmd, 10, (unsigned char *)buf, writeLen * blockLength, NULL, 0, 0); if(ret == 2) { const unsigned char *sense; int senseLen; sense = scsiIf_->getSense(senseLen); // check if drive rejected the command because the internal buffer // is filled if(senseLen >= 14 && (sense[2] & 0x0f) == 0x2 && sense[7] >= 6 && sense[12] == 0x4 && sense[13] == 0x8) { // Not Ready, long write in progress mSleep(40); retry = 1; } else { scsiIf_->printError(); } } } while (retry); if (ret != 0) { message(-2, "Write data failed."); return 1; } buf += writeLen * blockLength; lba += writeLen; len -= writeLen; } return 0; } int GenericMMC::writeCdTextLeadIn() { unsigned char cmd[10]; const PWSubChannel96 **cdTextSubChannels; long cdTextSubChannelCount; long channelsPerCmd; long scp = 0; long lba = -150 - leadInLen_; long len = leadInLen_; long n; long i; int retry; int ret; unsigned char *p; if (cdTextEncoder_ == NULL) return 0; channelsPerCmd = scsiIf_->maxDataLen() / 96; cdTextSubChannels = cdTextEncoder_->getSubChannels(&cdTextSubChannelCount); assert(channelsPerCmd > 0); assert(cdTextSubChannels != NULL); assert(cdTextSubChannelCount > 0); message(2, "Writing CD-TEXT lead-in..."); message(4, "Start LBA: %ld, length: %ld", lba, len); memset(cmd, 0, 10); cmd[0] = 0x2a; // WRITE1 while (len > 0) { n = (len > channelsPerCmd) ? channelsPerCmd : len; cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; cmd[7] = n >> 8; cmd[8] = n; p = transferBuffer_; for (i = 0; i < n; i++) { memcpy(p, cdTextSubChannels[scp]->data(), 96); p += 96; scp++; if (scp >= cdTextSubChannelCount) scp = 0; } message(5, "Writing %ld CD-TEXT sub-channels at LBA %ld.", n, lba); do { retry = 0; ret = sendCmd(cmd, 10, transferBuffer_, n * 96, NULL, 0, 0); if(ret == 2) { const unsigned char *sense; int senseLen; sense = scsiIf_->getSense(senseLen); // check if drive rejected the command because the internal buffer // is filled if(senseLen >= 14 && (sense[2] & 0x0f) == 0x2 && sense[7] >= 6 && sense[12] == 0x4 && sense[13] == 0x8) { // Not Ready, long write in progress mSleep(40); retry = 1; } else { scsiIf_->printError(); } } } while (retry); if (ret != 0) { message(-2, "Writing of CD-TEXT data failed."); return 1; } len -= n; lba += n; } return 0; } DiskInfo *GenericMMC::diskInfo() { unsigned char cmd[10]; unsigned long dataLen = 34; unsigned char data[34]; char spd; memset(&diskInfo_, 0, sizeof(DiskInfo)); // perform READ DISK INFORMATION memset(cmd, 0, 10); memset(data, 0, dataLen); cmd[0] = 0x51; // READ DISK INFORMATION cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen, 0) == 0) { diskInfo_.cdrw = (data[2] & 0x10) ? 1 : 0; diskInfo_.valid.cdrw = 1; switch (data[2] & 0x03) { case 0: // disc is empty diskInfo_.empty = 1; diskInfo_.append = 1; diskInfo_.manufacturerId = Msf(data[17], data[18], data[19]); diskInfo_.valid.manufacturerId = 1; break; case 1: // disc is not empty but appendable diskInfo_.sessionCnt = data[4]; diskInfo_.lastTrackNr = data[6]; diskInfo_.diskTocType = data[8]; switch ((data[2] >> 2) & 0x03) { case 0: // last session is empty diskInfo_.append = 1; // don't count the empty session and invisible track diskInfo_.sessionCnt -= 1; diskInfo_.lastTrackNr -= 1; if (getStartOfSession(&(diskInfo_.thisSessionLba)) == 0) { // reserve space for pre-gap after lead-in diskInfo_.thisSessionLba += 150; } else { // try to guess start of data area from start of lead-in // reserve space for 4500 lead-in and 150 pre-gap sectors diskInfo_.thisSessionLba = Msf(data[17], data[18], data[19]).lba() - 150 + 4650; } break; case 1: // last session is incomplete (not fixated) // we cannot append in DAO mode, just update the statistic data diskInfo_.diskTocType = data[8]; // don't count the invisible track diskInfo_.lastTrackNr -= 1; break; } break; case 2: // disk is complete diskInfo_.sessionCnt = data[4]; diskInfo_.lastTrackNr = data[6]; diskInfo_.diskTocType = data[8]; break; } diskInfo_.valid.empty = 1; diskInfo_.valid.append = 1; if (data[21] != 0xff || data[22] != 0xff || data[23] != 0xff) { diskInfo_.valid.capacity = 1; diskInfo_.capacity = Msf(data[21], data[22], data[23]).lba() - 150; } } // perform READ TOC to get session info memset(cmd, 0, 10); dataLen = 12; memset(data, 0, dataLen); cmd[0] = 0x43; // READ TOC cmd[2] = 1; // get session info cmd[8] = dataLen; // allocation length if (sendCmd(cmd, 10, NULL, 0, data, dataLen, 0) == 0) { diskInfo_.lastSessionLba = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11]; if (!diskInfo_.valid.empty) { diskInfo_.valid.empty = 1; diskInfo_.empty = (data[3] == 0) ? 1 : 0; diskInfo_.sessionCnt = data[3]; } } // read ATIP data dataLen = 28; memset(cmd, 0, 10); memset(data, 0, dataLen); cmd[0] = 0x43; // READ TOC/PMA/ATIP cmd[1] = 0x00; cmd[2] = 4; // get ATIP cmd[7] = 0; cmd[8] = dataLen; // data length if (sendCmd(cmd, 10, NULL, 0, data, dataLen, 0) == 0) { if (data[6] & 0x04) { diskInfo_.valid.recSpeed = 1; spd = (data[16] >> 4) & 0x07; diskInfo_.recSpeedLow = spd == 1 ? 2 : 0; spd = (data[16] & 0x0f); diskInfo_.recSpeedHigh = spd >= 1 && spd <= 4 ? spd * 2 : 0; } if (data[8] >= 80 && data[8] <= 99) { diskInfo_.manufacturerId = Msf(data[8], data[9], data[10]); diskInfo_.valid.manufacturerId = 1; } } return &diskInfo_; } // tries to read catalog number from disk and adds it to 'toc' // return: 1 if valid catalog number was found, else 0 int GenericMMC::readCatalog(Toc *toc, long startLba, long endLba) { unsigned char cmd[10]; unsigned char data[24]; char catalog[14]; int i; if (options_ & OPT_MMC_SCAN_MCN) { if (readCatalogScan(catalog, startLba, endLba) == 0) { if (catalog[0] != 0) { if (toc->catalog(catalog) == 0) return 1; else message(-1, "Found illegal MCN data: %s", catalog); } } } else { memset(cmd, 0, 10); memset(data, 0, 24); cmd[0] = 0x42; // READ SUB-CHANNEL cmd[2] = 1 << 6; cmd[3] = 0x02; // get media catalog number cmd[8] = 24; // transfer length if (sendCmd(cmd, 10, NULL, 0, data, 24) != 0) { message(-2, "Cannot get catalog number."); return 0; } if (data[8] & 0x80) { for (i = 0; i < 13; i++) { catalog[i] = data[0x09 + i]; } catalog[13] = 0; if (toc->catalog(catalog) == 0) { return 1; } } } return 0; } int GenericMMC::readIsrc(int trackNr, char *buf) { unsigned char cmd[10]; unsigned char data[24]; int i; buf[0] = 0; memset(cmd, 0, 10); memset(data, 0, 24); cmd[0] = 0x42; // READ SUB-CHANNEL cmd[2] = 1 << 6; cmd[3] = 0x03; // get ISRC cmd[6] = trackNr; cmd[8] = 24; // transfer length if (sendCmd(cmd, 10, NULL, 0, data, 24) != 0) { message(-2, "Cannot get ISRC code."); return 0; } if (data[8] & 0x80) { for (i = 0; i < 12; i++) { buf[i] = data[0x09 + i]; } buf[12] = 0; } return 0; } int GenericMMC::analyzeTrack(TrackData::Mode mode, int trackNr, long startLba, long endLba, Msf *indexIncrements, int *indexIncrementCnt, long *pregap, char *isrcCode, unsigned char *ctl) { int ret; int noScan = 0; selectSpeed(); if ((readCapabilities_ & CDR_AUDIO_SCAN_CAP) == 0) { ret = analyzeTrackSearch(mode, trackNr, startLba, endLba, indexIncrements, indexIncrementCnt, pregap, isrcCode, ctl); noScan = 1; } else { ret = analyzeTrackScan(mode, trackNr, startLba, endLba, indexIncrements, indexIncrementCnt, pregap, isrcCode, ctl); } if (noScan || (options_ & OPT_MMC_READ_ISRC) != 0 || (readCapabilities_ & CDR_READ_CAP_AUDIO_PQ_HEX) != 0) { // The ISRC code is usually not usable if the PQ channel data is // converted to hex numbers by the drive. Read them with the // appropriate command in this case *isrcCode = 0; if (mode == TrackData::AUDIO) readIsrc(trackNr, isrcCode); } return ret; } int GenericMMC::readSubChannels(TrackData::SubChannelMode sm, long lba, long len, SubChannel ***chans, Sample *audioData) { int retries = 5; unsigned char cmd[12]; int i; long blockLen = 0; unsigned long subChanMode = 0; cmd[0] = 0xbe; // READ CD cmd[1] = 0; cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; cmd[6] = len >> 16; cmd[7] = len >> 8; cmd[8] = len; cmd[9] = 0xf8; cmd[11] = 0; switch (sm) { case TrackData::SUBCHAN_NONE: // no sub-channel data selected choose what is available if ((readCapabilities_ & CDR_READ_CAP_AUDIO_PW_RAW) != 0) { // reading of raw P-W sub-channel data is supported blockLen = AUDIO_BLOCK_LEN + 96; cmd[10] = 0x01; // raw P-W sub-channel data subChanMode = CDR_READ_CAP_AUDIO_PW_RAW; } else if ((readCapabilities_ & (CDR_READ_CAP_AUDIO_PQ_BCD|CDR_READ_CAP_AUDIO_PQ_HEX)) != 0) { // reading of PQ sub-channel data is supported blockLen = AUDIO_BLOCK_LEN + 16; cmd[10] = 0x02; // PQ sub-channel data if ((readCapabilities_ & CDR_READ_CAP_AUDIO_PQ_BCD) != 0) subChanMode = CDR_READ_CAP_AUDIO_PQ_BCD; else subChanMode = CDR_READ_CAP_AUDIO_PQ_HEX; } else { // no usable sub-channel reading mode is supported blockLen = AUDIO_BLOCK_LEN; cmd[10] = 0; subChanMode = 0; } break; case TrackData::SUBCHAN_RW: blockLen = AUDIO_BLOCK_LEN + 96; cmd[10] = 0x04; break; case TrackData::SUBCHAN_RW_RAW: blockLen = AUDIO_BLOCK_LEN + 96; cmd[10] = 0x01; break; } while (1) { if (sendCmd(cmd, 12, NULL, 0, transferBuffer_, len * blockLen, retries == 0 ? 1 : 0) != 0) { if (retries == 0) return 1; } else { break; } retries--; } #if 0 if (lba > 5000) { char fname[200]; sprintf(fname, "testout_%ld", lba); FILE *fp = fopen(fname, "w"); fwrite(transferBuffer_, blockLen, len, fp); fclose(fp); } #endif if (subChanMode != 0) { unsigned char *buf = transferBuffer_ + AUDIO_BLOCK_LEN; for (i = 0; i < len; i++) { switch (subChanMode) { case CDR_READ_CAP_AUDIO_PQ_HEX: // All numbers in sub-channel data are hex conforming to the // MMC standard. We have to convert them back to BCD for the // 'SubChannel' class. buf[1] = SubChannel::bcd(buf[1]); buf[2] = SubChannel::bcd(buf[2]); buf[3] = SubChannel::bcd(buf[3]); buf[4] = SubChannel::bcd(buf[4]); buf[5] = SubChannel::bcd(buf[5]); buf[6] = SubChannel::bcd(buf[6]); buf[7] = SubChannel::bcd(buf[7]); buf[8] = SubChannel::bcd(buf[8]); buf[9] = SubChannel::bcd(buf[9]); // fall through case CDR_READ_CAP_AUDIO_PQ_BCD: ((PQSubChannel16*)scannedSubChannels_[i])->init(buf); if (scannedSubChannels_[i]->type() != SubChannel::QMODE_ILLEGAL) { // the CRC of the sub-channel data is usually invalid -> mark the // sub-channel object that it should not try to verify the CRC scannedSubChannels_[i]->crcInvalid(); } break; case CDR_READ_CAP_AUDIO_PW_RAW: ((PWSubChannel96*)scannedSubChannels_[i])->init(buf); break; } #if 0 if (subChanMode == CDR_READ_CAP_AUDIO_PW_RAW) { // xxam! int j, k; message(0, ""); for (j = 0; j < 4; j++) { for (k = 0; k < 24; k++) { unsigned char data = buf[j * 24 + k]; message(0, "%02x ", data&0x3f); } message(0, ""); } } #endif buf += blockLen; } } if (audioData != NULL) { unsigned char *p = transferBuffer_; for (i = 0; i < len; i++) { memcpy(audioData, p, AUDIO_BLOCK_LEN); audioData += SAMPLES_PER_BLOCK; switch (sm) { case TrackData::SUBCHAN_NONE: break; case TrackData::SUBCHAN_RW: case TrackData::SUBCHAN_RW_RAW: memcpy(audioData, p + AUDIO_BLOCK_LEN, PW_SUBCHANNEL_LEN); audioData += PW_SUBCHANNEL_LEN / SAMPLE_LEN; break; } p += blockLen; } } if (subChanMode == 0) *chans = NULL; else *chans = scannedSubChannels_; return 0; } // Tries to retrieve configuration feature 'feature' and fills data to // provided buffer 'buf' with maximum length 'bufLen'. // Return: 0: OK // 1: feature not available // 2: SCSI error int GenericMMC::getFeature(unsigned int feature, unsigned char *buf, unsigned long bufLen, int showMsg) { unsigned char header[8]; unsigned char *data; unsigned char cmd[10]; unsigned long len; memset(cmd, 0, 10); memset(header, 0, 8); cmd[0] = 0x46; // GET CONFIGURATION cmd[1] = 0x02; // return single feature descriptor cmd[2] = feature >> 8; cmd[3] = feature; cmd[8] = 8; // allocation length if (sendCmd(cmd, 10, NULL, 0, header, 8, showMsg) != 0) { if (showMsg) message(-2, "Cannot get feature 0x%x.", feature); return 2; } len = (header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3]; message(4, "getFeature: data len: %lu", len); if (len < 8) return 1; // feature not defined if (bufLen == 0) return 0; len -= 4; if (len > bufLen) len = bufLen; data = new unsigned char[len + 8]; cmd[7] = (len + 8) >> 8; cmd[8] = (len + 8); if (sendCmd(cmd, 10, NULL, 0, data, len + 8, showMsg) != 0) { if (showMsg) message(-2, "Cannot get data for feature 0x%x.", feature); delete[] data; return 2; } len = (header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3]; message(4, "getFeature: data len: %lu", len); if (len < 8) { delete[] data; return 1; // feature not defined } len -= 4; if (len > bufLen) len = bufLen; memcpy(buf, data + 8, len); delete[] data; return 0; } const DriveInfo *GenericMMC::driveInfo(bool showErrorMsg) { unsigned char mp[32]; if (driveInfo_ != NULL) return driveInfo_; driveInfo_ = new DriveInfo; if (getModePage(0x2a, mp, 32, NULL, NULL, showErrorMsg) != 0) { if (showErrorMsg) { message(-2, "Cannot retrieve drive capabilities mode page."); } delete driveInfo_; driveInfo_ = NULL; return NULL; } driveInfo_->burnProof = (mp[4] & 0x80) ? 1 : 0; driveInfo_->accurateAudioStream = mp[5] & 0x02 ? 1 : 0; driveInfo_->maxReadSpeed = (mp[8] << 8) | mp[9]; driveInfo_->currentReadSpeed = (mp[14] << 8) | mp[15]; driveInfo_->maxWriteSpeed = (mp[18] << 8) | mp[19]; driveInfo_->currentWriteSpeed = (mp[20] << 8) | mp[21]; #if 0 unsigned char cdMasteringFeature[8]; if (getFeature(0x2e, cdMasteringFeature, 8, 1) == 0) { message(0, "Feature: %x %x %x %x %x %x %x %x", cdMasteringFeature[0], cdMasteringFeature[1], cdMasteringFeature[2], cdMasteringFeature[3], cdMasteringFeature[4], cdMasteringFeature[5], cdMasteringFeature[6], cdMasteringFeature[7]); } #endif RicohGetWriteOptions(); return driveInfo_; } TrackData::Mode GenericMMC::getTrackMode(int, long trackStartLba) { unsigned char cmd[12]; unsigned char data[AUDIO_BLOCK_LEN]; memset(cmd, 0, 12); cmd[0] = 0xbe; // READ CD cmd[2] = trackStartLba >> 24; cmd[3] = trackStartLba >> 16; cmd[4] = trackStartLba >> 8; cmd[5] = trackStartLba; cmd[8] = 1; cmd[9] = 0xf8; if (sendCmd(cmd, 12, NULL, 0, data, AUDIO_BLOCK_LEN) != 0) { message(-2, "Cannot read sector of track."); return TrackData::MODE0; } if (memcmp(CdrDriver::syncPattern, data, 12) != 0) { // cannot be a data sector return TrackData::MODE0; } TrackData::Mode mode = determineSectorMode(data + 12); if (mode == TrackData::MODE0) { // illegal message(-2, "Found illegal mode in sector %ld.", trackStartLba); } return mode; } CdRawToc *GenericMMC::getRawToc(int sessionNr, int *len) { unsigned char cmd[10]; unsigned short dataLen; unsigned char *data = NULL;; unsigned char reqData[4]; // buffer for requestion the actual length unsigned char *p; int i, entries; CdRawToc *rawToc; assert(sessionNr >= 1); // read disk toc length memset(cmd, 0, 10); cmd[0] = 0x43; // READ TOC cmd[2] = 2; cmd[6] = sessionNr; cmd[8] = 4; if (sendCmd(cmd, 10, NULL, 0, reqData, 4) != 0) { message(-2, "Cannot read disk toc."); return NULL; } dataLen = ((reqData[0] << 8) | reqData[1]) + 2; message(4, "Raw toc data len: %d", dataLen); data = new unsigned char[dataLen]; // read disk toc cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-2, "Cannot read disk toc."); delete[] data; return NULL; } entries = (((data[0] << 8) | data[1]) - 2) / 11; rawToc = new CdRawToc[entries]; for (i = 0, p = data + 4; i < entries; i++, p += 11 ) { #if 0 message(5, "%d %02x %02d %2x %02d:%02d:%02d %02d %02d:%02d:%02d", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10]); #endif rawToc[i].sessionNr = p[0]; rawToc[i].adrCtl = p[1]; rawToc[i].point = p[3]; rawToc[i].pmin = p[8]; rawToc[i].psec = p[9]; rawToc[i].pframe = p[10]; } delete[] data; *len = entries; return rawToc; } long GenericMMC::readTrackData(TrackData::Mode mode, TrackData::SubChannelMode sm, long lba, long len, unsigned char *buf) { long i; long inBlockLen = AUDIO_BLOCK_LEN; unsigned char cmd[12]; const unsigned char *sense; int senseLen; memset(cmd, 0, 12); cmd[0] = 0xbe; // READ CD cmd[1] = 0; cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; cmd[6] = len >> 16; cmd[7] = len >> 8; cmd[8] = len; cmd[9] = 0xf8; switch (sm) { case TrackData::SUBCHAN_NONE: cmd[10] = 0; // no sub-channel reading break; case TrackData::SUBCHAN_RW: cmd[10] = 0x4; inBlockLen += PW_SUBCHANNEL_LEN; break; case TrackData::SUBCHAN_RW_RAW: cmd[10] = 0x1; inBlockLen += PW_SUBCHANNEL_LEN; break; } switch (sendCmd(cmd, 12, NULL, 0, transferBuffer_, len * inBlockLen, 0)) { case 0: break; case 2: sense = scsiIf_->getSense(senseLen); if (senseLen > 0x0c) { if ((sense[2] & 0x0f) == 5) { // Illegal request switch (sense[12]) { case 0x63: // End of user area encountered on this track case 0x64: // Illegal mode for this track return -2; break; case 0x20: // INVALID COMMAND OPERATION CODE case 0x24: // INVALID FIELD IN CDB case 0x26: // INVALID FIELD IN PARAMETER LIST /* These error codes mean that something was wrong with the * command we are sending. Report them as hard errors to the * upper level. */ scsiIf_->printError(); return -1; break; } } else if ((sense[2] & 0x0f) == 4) { // Hardware Error switch (sense[12]) { case 0x9: // focus servo failure return -2; break; } } else if ((sense[2] & 0x0f) == 3) { // Medium error switch (sense[12]) { case 0x02: // No seek complete, sector not found case 0x06: // no reference position found case 0x11: // L-EC error case 0x15: // random positioning error return -2; break; } } } /* All other errors are unexpected. They will be treated like L-EC errors * by the upper layer. Just print the error code so that we can decice * later to add the errors to the known possible error list. */ scsiIf_->printError(); return -2; break; default: message(-2, "Read error at LBA %ld, len %ld", lba, len); return -2; break; } unsigned char *sector = transferBuffer_; for (i = 0; i < len; i++) { if (buf != NULL) { switch (mode) { case TrackData::MODE1: memcpy(buf, sector + 16, MODE1_BLOCK_LEN); buf += MODE1_BLOCK_LEN; break; case TrackData::MODE1_RAW: memcpy(buf, sector, AUDIO_BLOCK_LEN); buf += AUDIO_BLOCK_LEN; break; case TrackData::MODE2: case TrackData::MODE2_FORM_MIX: memcpy(buf, sector + 16, MODE2_BLOCK_LEN); buf += MODE2_BLOCK_LEN; break; case TrackData::MODE2_FORM1: memcpy(buf, sector + 24, MODE2_FORM1_DATA_LEN); buf += MODE2_FORM1_DATA_LEN; break; case TrackData::MODE2_FORM2: memcpy(buf, sector + 24, MODE2_FORM2_DATA_LEN); buf += MODE2_FORM2_DATA_LEN; break; case TrackData::MODE2_RAW: memcpy(buf, sector, AUDIO_BLOCK_LEN); buf += AUDIO_BLOCK_LEN; break; case TrackData::MODE0: case TrackData::AUDIO: message(-3, "GenericMMC::readTrackData: Illegal mode."); return 0; break; } // copy sub-channel data switch (sm) { case TrackData::SUBCHAN_NONE: break; case TrackData::SUBCHAN_RW: case TrackData::SUBCHAN_RW_RAW: memcpy(buf, sector + AUDIO_BLOCK_LEN, PW_SUBCHANNEL_LEN); buf += PW_SUBCHANNEL_LEN; break; } } #if 0 // xxam! int j, k; message(0, ""); for (j = 0; j < 4; j++) { for (k = 0; k < 24; k++) { unsigned char data = sector[AUDIO_BLOCK_LEN + j * 24 + k]; message(0, "%02x ", data&0x3f); } message(0, ""); } #endif sector += inBlockLen; } return len; } int GenericMMC::readAudioRange(ReadDiskInfo *rinfo, int fd, long start, long end, int startTrack, int endTrack, TrackInfo *info) { if (!onTheFly_) { if (((readCapabilities_ & CDR_READ_CAP_AUDIO_PQ_BCD) == 0 && (readCapabilities_ & CDR_READ_CAP_AUDIO_PW_RAW) == 0) || (options_ & OPT_MMC_READ_ISRC) != 0) { int t; long pregap = 0; // The ISRC code is usually not usable if the PQ channel data is // converted to hex numbers by the drive. Read them with the // appropriate command in this case message(1, "Analyzing..."); for (t = startTrack; t <= endTrack; t++) { long totalProgress; message(1, "Track %d...", t + 1); totalProgress = t * 1000; totalProgress /= rinfo->tracks; sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 0, totalProgress); if ((readCapabilities_ & CDR_AUDIO_SCAN_CAP) == 0) { // we have to use the binary search method to find pre-gap and // index marks if the drive cannot read sub-channel data if (!fastTocReading_) { long slba, elba; int i, indexCnt; Msf index[98]; unsigned char ctl; if (pregap > 0) message(2, "Found pre-gap: %s", Msf(pregap).str()); slba = info[t].start; if (info[t].mode == info[t + 1].mode) elba = info[t + 1].start; else elba = info[t + 1].start - 150; pregap = 0; if (analyzeTrackSearch(TrackData::AUDIO, t + 1, slba, elba, index, &indexCnt, &pregap, info[t].isrcCode, &ctl) != 0) return 1; for (i = 0; i < indexCnt; i++) info[t].index[i] = index[i].lba(); info[t].indexCnt = indexCnt; if (t < endTrack) info[t + 1].pregap = pregap; } else { info[t].indexCnt = 0; info[t + 1].pregap = 0; } } info[t].isrcCode[0] = 0; readIsrc(t + 1, info[t].isrcCode); if (info[t].isrcCode[0] != 0) message(2, "Found ISRC code."); totalProgress = (t + 1) * 1000; totalProgress /= rinfo->tracks; sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 1000, totalProgress); } message(1, "Reading..."); } } if (subChanReadMode_ == TrackData::SUBCHAN_NONE) { return CdrDriver::readAudioRangeParanoia(rinfo, fd, start, end, startTrack, endTrack, info); } else { return CdrDriver::readAudioRangeStream(rinfo, fd, start, end, startTrack, endTrack, info); } } int GenericMMC::getTrackIndex(long lba, int *trackNr, int *indexNr, unsigned char *ctl) { unsigned char cmd[12]; unsigned short dataLen = 0x30; unsigned char data[0x30]; int waitLoops = 10; int waitFailed = 0; // play one audio block memset(cmd, 0, 10); cmd[0] = 0x45; // PLAY AUDIO cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; cmd[7] = 0; cmd[8] = 1; if (sendCmd(cmd, 10, NULL, 0, NULL, 0) != 0) { message(-2, "Cannot play audio block."); return 1; } // wait until the play command finished memset(cmd, 0, 12); cmd[0] = 0xbd; // MECHANISM STATUS cmd[9] = 8; while (waitLoops > 0) { if (sendCmd(cmd, 12, NULL, 0, data, 8, 0) == 0) { //message(0, "%d, %x", waitLoops, data[1]); if ((data[1] >> 5) == 1) // still playing? waitLoops--; else waitLoops = 0; } else { waitFailed = 1; waitLoops = 0; } } if (waitFailed) { // The play operation immediately returns success status and the waiting // loop above failed. Wait here for a while until the desired block is // played. It takes ~13 msecs to play a block but access time is in the // order of several 100 msecs mSleep(300); } // read sub channel information memset(cmd, 0, 10); cmd[0] = 0x42; // READ SUB CHANNEL cmd[2] = 0x40; // get sub channel data cmd[3] = 0x01; // get sub Q channel data cmd[6] = 0; cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-2, "Cannot read sub Q channel data."); return 1; } *trackNr = data[6]; *indexNr = data[7]; if (ctl != NULL) { *ctl = data[5] & 0x0f; } //message(0, "%d %d", *trackNr, *indexNr); return 0; } /* * Checks if a certain sub-channel reading mode is supported. * lba: start address for reading * len: maximum number of sectors available for testing * subChanMode: 1: read PQ sub-channels * 2: read raw P-W sub-channels * 3: read cooked R-W sub-channels * Return: 0 sub-channel read mode not supported * 1 sub-channel read mode supported (BCD for PQ) * 2 sub-channel read mode supported (HEX for PQ) * 3 sub-channel read mode PQ supported but cannot determine data * format */ int GenericMMC::readCdTest(long lba, long len, int subChanMode) const { unsigned char cmd[12]; long blockLen; int ret; int successRead = 0; int pqSubChanBcdOk = 0; int pqSubChanHexOk = 0; //message(0, "readCdTest: %ld %ld %d", lba, len, subChanMode); if (len <= 0) return 0; cmd[0] = 0xbe; // READ CD cmd[1] = 0; cmd[6] = 0; cmd[7] = 0; cmd[8] = 1; // transfer length: 1 cmd[9] = 0xf8; cmd[10] = 0; cmd[11] = 0; blockLen = AUDIO_BLOCK_LEN; switch (subChanMode) { case 1: // PQ blockLen += PQ_SUBCHANNEL_LEN; cmd[10] = 0x02; if (len > 300) len = 300; /* we have to check many sub-channels here to determine the * data mode (BCD or HEX) */ break; case 2: // PW_RAW cmd[10] = 0x01; blockLen += PW_SUBCHANNEL_LEN; if (len > 10) len = 10; break; case 3: // RW_COOKED cmd[10] = 0x04; blockLen += PW_SUBCHANNEL_LEN; if (len > 10) len = 10; break; } while (len > 0) { cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; if ((ret = sendCmd(cmd, 12, NULL, 0, transferBuffer_, blockLen, 0)) == 0) { successRead++; if (subChanMode == 1) { unsigned char *buf = transferBuffer_ + AUDIO_BLOCK_LEN; #if 0 { PQSubChannel16 chan; chan.init(buf); chan.print(); } #endif // check if Q sub-channel values are in BCD or HEX format if (SubChannel::isBcd(buf[1]) && SubChannel::isBcd(buf[2]) && SubChannel::isBcd(buf[3]) && SubChannel::isBcd(buf[4]) && SubChannel::isBcd(buf[5]) && SubChannel::isBcd(buf[6]) && SubChannel::isBcd(buf[7]) && SubChannel::isBcd(buf[8]) && SubChannel::isBcd(buf[9])) { PQSubChannel16 chan; chan.init(buf); chan.type(SubChannel::QMODE1DATA); int min = chan.amin(); int sec = chan.asec(); int frac = chan.aframe(); if ((frac >= 0 && frac < 75) && (sec >= 0 && sec < 60) && (min >= 0)) { long pqlba = Msf(min, sec, frac).lba() - 150; long diff = pqlba - lba; if (diff < 0) diff = -diff; if (diff < 20) { pqSubChanBcdOk++; } } } if (buf[7] < 100 && buf[8] < 60 && buf[9] < 75) { long pqlba = Msf(buf[7], buf[8], buf[9]).lba() - 150; //message(0, "readCdTest: pqlba: %ld", pqlba); long diff = pqlba - lba; if (diff < 0) diff = -diff; if (diff < 20) { pqSubChanHexOk++; } } } } len--; lba++; } if (successRead) { if (subChanMode == 1) { if (pqSubChanBcdOk > pqSubChanHexOk) return 1; else if (pqSubChanHexOk > pqSubChanBcdOk) return 2; else return 3; } else { return 1; } } return 0; } unsigned long GenericMMC::getReadCapabilities(const CdToc *toc, int nofTracks) const { unsigned long caps = 0; int audioRawPWChecked = 0; int audioPQChecked = 0; int audioCookedRWChecked = 0; int dataRawPWChecked = 0; int dataPQChecked = 0; int dataCookedRWChecked = 0; int t; if ((options_ & OPT_MMC_NO_SUBCHAN) != 0) { // driver options indicate that PQ and raw RW sub-channel reading for // audio tracks is not supported so skip all corresponding tests audioPQChecked = 1; audioRawPWChecked = 1; } else if ((options_ & OPT_MMC_USE_PQ) != 0) { // driver options indicated that PQ sub-channel reading is supported for // audio/data tracks and RW sub-channel reading is not supported, skip // the corresponding checks and set the capabilities appropriately audioPQChecked = 1; audioRawPWChecked = 1; dataPQChecked = 1; if ((options_ & OPT_MMC_PQ_BCD) != 0) caps |= CDR_READ_CAP_AUDIO_PQ_BCD | CDR_READ_CAP_DATA_PQ_BCD; else caps |= CDR_READ_CAP_AUDIO_PQ_HEX | CDR_READ_CAP_DATA_PQ_HEX; } else if ((options_ & OPT_MMC_USE_RAW_RW) != 0) { // driver options indicated that raw PW sub-channel reading is supported // audio tracks and raw PW sub-channel reading is not supported, skip // the corresponding checks and set the capabilities appropriately audioPQChecked = 1; audioRawPWChecked = 1; caps |= CDR_READ_CAP_DATA_PW_RAW; } for (t = 0; t < nofTracks; t++) { long tlen = toc[t+1].start - toc[t].start; if ((toc[t].adrCtl & 0x04) != 0) { // data track if (!dataPQChecked) { dataPQChecked = 1; message(3, "Checking for PQ sub-channel reading support (data track)..."); switch (readCdTest(toc[t].start, tlen, 1)) { case 0: message(3, "PQ sub-channel reading (data track) not supported."); break; case 1: message(2, "PQ sub-channel reading (data track) is supported, data format is BCD."); caps |= CDR_READ_CAP_DATA_PQ_BCD; break; case 2: message(2, "PQ sub-channel reading (data track) is supported, data format is HEX."); caps |= CDR_READ_CAP_DATA_PQ_HEX; break; case 3: message(2, "PQ sub-channel reading (data track) seems to be supported but cannot determine data format."); message(2, "Please use driver option '--driver generic-mmc:0x1' or '--driver generic-mmc:0x3' to set the data format explicitly."); break; } } if (!dataRawPWChecked) { dataRawPWChecked = 1; message(3, "Checking for raw P-W sub-channel reading support (data track)..."); if (readCdTest(toc[t].start, tlen, 2)) { message(2, "Raw P-W sub-channel reading (data track) is supported."); caps |= CDR_READ_CAP_DATA_PW_RAW; } else { message(3, "Raw P-W sub-channel reading (data track) is not supported."); } } if (!dataCookedRWChecked) { dataCookedRWChecked = 1; message(3, "Checking for cooked R-W sub-channel reading support (data track)..."); if (readCdTest(toc[t].start, tlen, 3)) { message(2, "Cooked R-W sub-channel reading (data track) is supported."); caps |= CDR_READ_CAP_DATA_RW_COOKED; } else { message(3, "Cooked R-W sub-channel reading (data track) is not supported."); } } } else { // audio track if (!audioPQChecked) { audioPQChecked = 1; message(3, "Checking for PQ sub-channel reading support (audio track)..."); switch (readCdTest(toc[t].start, tlen, 1)) { case 0: message(3, "PQ sub-channel reading (audio track) is not supported."); break; case 1: message(2, "PQ sub-channel reading (audio track) is supported, data format is BCD."); caps |= CDR_READ_CAP_AUDIO_PQ_BCD; break; case 2: message(2, "PQ sub-channel reading (audio track) is supported, data format is HEX."); caps |= CDR_READ_CAP_AUDIO_PQ_HEX; break; case 3: message(2, "PQ sub-channel reading (audio track) seems to be supported but cannot determine data format."); message(2, "Please use driver option '--driver generic-mmc:0x1' or '--driver generic-mmc:0x3' to set the data format explicitly."); break; } } if (!audioRawPWChecked) { audioRawPWChecked = 1; message(3, "Checking for raw P-W sub-channel reading support (audio track)..."); if (readCdTest(toc[t].start, tlen, 2)) { message(2, "Raw P-W sub-channel reading (audio track) is supported."); caps |= CDR_READ_CAP_AUDIO_PW_RAW; } else { message(3, "Raw P-W sub-channel reading (audio track) is not supported."); } } if (!audioCookedRWChecked) { audioCookedRWChecked = 1; message(3, "Checking for cooked R-W sub-channel reading support (audio track)..."); if (readCdTest(toc[t].start, tlen, 3)) { message(2, "Cooked R-W sub-channel reading (audio track) is supported."); caps |= CDR_READ_CAP_AUDIO_RW_COOKED; } else { message(3, "Raw R-W sub-channel reading (audio track) is not supported."); } } } } return caps; } int GenericMMC::RicohGetWriteOptions() { unsigned char mp[14]; driveInfo_->ricohJustLink = 0; driveInfo_->ricohJustSpeed = 0; if (getModePage(0x30, mp, 14, NULL, NULL, 0) != 0) { return 1; } if (mp[1] != 14) return 1; if (mp[2] & (1 << 5)) driveInfo_->ricohJustSpeed = 1; if (mp[2] & 0x01) driveInfo_->ricohJustLink = 1; return 0; } int GenericMMC::RicohSetWriteOptions(const DriveInfo *di) { unsigned char mp[14]; if (di->ricohJustLink == 0 && di->ricohJustSpeed == 0) return 0; if (getModePage(0x30, mp, 14, NULL, NULL, 1) != 0) { message(-2, "Cannot retrieve Ricoh mode page 30."); return 1; } if (di->ricohJustLink) { if (bufferUnderRunProtection()) { message(2, "Enabling JustLink."); mp[3] |= 0x1; } else { message(2, "Disabling JustLink."); mp[3] &= ~0x1; } } if (di->ricohJustSpeed) { if (writeSpeedControl()) { message(2, "Enabling JustSpeed."); mp[3] &= ~(1 << 5); // clear bit to enable write speed control } else { message(2, "Disabling JustSpeed."); mp[3] |= (1 << 5); // set bit to disable write speed control } } if (setModePage(mp, NULL, NULL, 1) != 0) { message(-2, "Cannot set Ricoh mode page 30."); return 1; } return 0; }