1 /*  cdrdao - write audio CD-Rs in disc-at-once mode
2  *
3  *  Copyright (C) 1998-2002 Andreas Mueller <andreas@daneb.de>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 
20 #include <config.h>
21 
22 #include <string.h>
23 #include <assert.h>
24 
25 #include "GenericMMCraw.h"
26 #include "PQSubChannel16.h"
27 #include "PWSubChannel96.h"
28 #include "CdTextEncoder.h"
29 
30 #include "Toc.h"
31 #include "log.h"
32 #include "port.h"
33 
GenericMMCraw(ScsiIf * scsiIf,unsigned long options)34 GenericMMCraw::GenericMMCraw(ScsiIf *scsiIf, unsigned long options)
35   : GenericMMC(scsiIf, options), PQChannelEncoder()
36 {
37   driverName_ = "Generic SCSI-3/MMC (raw writing) - Version 2.0";
38 
39   encodingMode_ = 0;
40 
41   subChannelMode_ = 0;
42 
43   leadInLen_ = leadOutLen_ = 0;
44   subChannel_ = NULL;
45   encSubChannel_ = NULL;
46   encodeBuffer_ = NULL;
47 
48   // CD-TEXT dynamic data
49   cdTextStartLba_ = 0;
50   cdTextEndLba_ = 0;
51   cdTextSubChannels_ = NULL;
52   cdTextSubChannelCount_ = 0;
53   cdTextSubChannelAct_ = 0;
54 }
55 
~GenericMMCraw()56 GenericMMCraw::~GenericMMCraw()
57 {
58   delete subChannel_, subChannel_ = NULL;
59   delete[] encodeBuffer_, encodeBuffer_ = NULL;
60 
61   delete[] encSubChannel_;
62   encSubChannel_ = NULL;
63 
64   cdTextStartLba_ = 0;
65   cdTextEndLba_ = 0;
66   cdTextSubChannels_ = NULL;
67   cdTextSubChannelCount_ = 0;
68   cdTextSubChannelAct_ = 0;
69 }
70 
multiSession(bool m)71 int GenericMMCraw::multiSession(bool m)
72 {
73     if (m) {
74 	// multi session mode is currently not support for raw writing
75 	return 1;
76     }
77 
78   return 0;
79 }
80 
81 // static constructor
instance(ScsiIf * scsiIf,unsigned long options)82 CdrDriver *GenericMMCraw::instance(ScsiIf *scsiIf, unsigned long options)
83 {
84   return new GenericMMCraw(scsiIf, options);
85 }
86 
subChannelEncodingMode(TrackData::SubChannelMode sm) const87 int GenericMMCraw::subChannelEncodingMode(TrackData::SubChannelMode sm) const
88 {
89   int ret = 0;
90 
91 
92   if (subChannelMode_ == 0) {
93     // The supported sub-channel writing mode has not been determined, yet,
94     // so just return the plain mode here. 'initDao' will finally check if
95     // writing of the sub-channel data defined in 'toc_' is supported by the
96     // drive.
97     return 0;
98   }
99 
100   switch (sm) {
101   case TrackData::SUBCHAN_NONE:
102     ret = 0;
103     break;
104 
105   case TrackData::SUBCHAN_RW:
106     switch (subChannelMode_) {
107     case 2:
108       ret = 0; // plain
109       break;
110     case 3:
111       ret = -1; // currently not supported
112       //ret = 1; // have to create parity and perform interleaving
113       break;
114     default:
115       ret = -1; // not supported
116       break;
117     }
118     break;
119 
120   case TrackData::SUBCHAN_RW_RAW:
121     if (subChannelMode_ == 3)
122       ret = 1;
123     else
124       ret = -1;
125     break;
126   }
127 
128   return ret;
129 }
130 
131 // Sets write parameters via mode page 0x05.
132 // return: 0: OK
133 //         1: scsi command failed
setWriteParameters(int dataBlockType)134 int GenericMMCraw::setWriteParameters(int dataBlockType)
135 {
136   unsigned char mp[0x38];
137 
138   if (getModePage(5/*write parameters mode page*/, mp, 0x38,
139 		  NULL, NULL, 1) != 0) {
140     log_message(-2, "Cannot retrieve write parameters mode page.");
141     return 1;
142   }
143 
144   mp[0] &= 0x7f; // clear PS flag
145 
146   mp[2] &= 0xe0;
147   mp[2] |= 0x03; // write type: raw
148   if (simulate_) {
149     mp[2] |= 1 << 4; // test write
150   }
151 
152   const DriveInfo *di;
153   if ((di = driveInfo(1)) != NULL) {
154     if (di->burnProof) {
155       // This drive has BURN-Proof function.
156       // Enable it unless explicitly disabled.
157       if (bufferUnderRunProtection()) {
158 	log_message(2, "Turning BURN-Proof on");
159 	mp[2] |= 0x40;
160       }
161       else {
162 	log_message(2, "Turning BURN-Proof off");
163 	mp[2] &= ~0x40;
164       }
165     }
166 
167     RicohSetWriteOptions(di);
168   }
169 
170   mp[3] &= 0x3f; // Multi-session: No B0 pointer, next session not allowed
171   mp[3] = 0;
172 
173   mp[4] &= 0xf0;
174   mp[4] = dataBlockType & 0x0f;  // Data Block Type:
175                                  // 1: raw data, block size: 2368 PQ sub chan
176                                  // 2: raw data, block size: 2448
177                                  // 3: raw data, block size: 2448
178 
179   mp[8] = 0; // session format: CD-DA or CD-ROM
180 
181   if (setModePage(mp, NULL, NULL, 0) != 0) {
182     //log_message(-2, "Cannot set write parameters mode page.");
183     return 1;
184   }
185 
186   return 0;
187 }
188 
getMultiSessionInfo(int sessionNr,int multi,SessionInfo * info)189 int GenericMMCraw::getMultiSessionInfo(int sessionNr, int multi,
190 				       SessionInfo *info)
191 {
192   int err = 0;
193 
194   memset(info, 0, sizeof(SessionInfo));
195 
196   info->sessionNr = 1;
197 
198   if (getSessionInfo() != 0)
199     return 1;
200 
201   info->leadInStart = leadInStart_.lba() - 150;
202 
203   if (leadInStart_.min() >= 80) {
204     info->leadInStart = leadInStart_.lba() - 450000;
205   }
206 
207   info->leadInLen = leadInLen_;
208   info->leadOutLen = leadOutLen_;
209 
210   if (multi) {
211     unsigned char cmd[10];
212     unsigned char data[4];
213     unsigned char *buf = NULL;
214     long dataLen;
215 
216     // read ATIP data
217     memset(cmd, 0, 10);
218     memset(data, 0, 4);
219 
220     cmd[0] = 0x43; // READ TOC/PMA/ATIP
221     cmd[1] = 0x00;
222     cmd[2] = 4; // get ATIP
223     cmd[7] = 0;
224     cmd[8] = 4; // data length
225 
226     if (sendCmd(cmd, 10, NULL, 0, data, 4, 0) != 0) {
227       log_message(-2, "Cannot read ATIP data.");
228       return 1;
229     }
230 
231     dataLen = (data[0] << 8) | data[1];
232     dataLen += 2;
233 
234     log_message(4, "ATIP data len: %ld", dataLen);
235 
236     if (sessionNr == 1) {
237       if (dataLen < 19) {
238 	log_message(-2, "Cannot read ATIP data.");
239 	return 1;
240       }
241     }
242     else {
243       if (dataLen < 15) {
244 	log_message(-2, "Cannot read ATIP data.");
245 	return 1;
246       }
247     }
248 
249     buf = new unsigned char[dataLen];
250     memset(buf, 0, dataLen);
251 
252     cmd[7] = dataLen >> 8;
253     cmd[8] = dataLen;
254 
255     if (sendCmd(cmd, 10, NULL, 0, buf, dataLen, 0) != 0) {
256       log_message(-2, "Cannot read ATIP data.");
257       delete[] buf;
258       return 1;
259     }
260 
261     info->lastLeadoutStart = Msf(buf[12], buf[13], buf[14]);
262 
263     if (sessionNr == 1) {
264       info->optimumRecordingPower = buf[4];
265 
266       if (buf[8] >= 80 && buf[8] <= 99) {
267 	info->atipLeadinStart = Msf(buf[8], buf[9], buf[10]);
268       }
269       else {
270 	log_message(-2, "Invalid start time of lead-in in ATIP.");
271 	err = 1;
272       }
273 
274       info->cdrw = (buf[6] & 0x40) ? 1 : 0;
275 
276       if (info->cdrw) {
277 	if (buf[6] & 0x04) {
278 	  info->atipA1[0] = buf[16];
279 	  info->atipA1[1] = buf[17];
280 	  info->atipA1[2] = buf[18];
281 	}
282 	else {
283 	  log_message(-2, "ATIP data does not contain point A1 data.");
284 	  err = 1;
285 	}
286       }
287     }
288 
289     delete[] buf;
290     buf = NULL;
291   }
292 
293   log_message(4, "SI: session nr: %d", info->sessionNr);
294   log_message(4, "SI: lead-in start: %ld", info->leadInStart);
295   log_message(4, "SI: lead-in len: %ld", info->leadInLen);
296   log_message(4, "SI: lead-out len: %ld", info->leadOutLen);
297   log_message(4, "SI: last lead-out start: %d %d %d", info->lastLeadoutStart.min(),
298 	  info->lastLeadoutStart.sec(), info->lastLeadoutStart.frac());
299   log_message(4, "SI: cdrw: %d", info->cdrw);
300   log_message(4, "SI: atip lead-in start: %d %d %d", info->atipLeadinStart.min(),
301 	  info->atipLeadinStart.sec(), info->atipLeadinStart.frac());
302   log_message(4, "SI: optimum recording power: %u", info->optimumRecordingPower);
303   log_message(4, "SI: atip A1: %u %u %u", info->atipA1[0], info->atipA1[1],
304 	  info->atipA1[2]);
305 
306   return err;
307 }
308 
getSubChannelModeFromToc()309 int GenericMMCraw::getSubChannelModeFromToc()
310 {
311   TrackIterator itr(CdrDriver::toc_);
312   const Track *tr;
313   int mode = 0;
314 
315   for (tr = itr.first(); tr != NULL; tr = itr.next()) {
316     switch (tr->subChannelType()) {
317     case TrackData::SUBCHAN_NONE:
318       break;
319 
320     case TrackData::SUBCHAN_RW:
321       // we need at least packed RW writing
322       if (mode < 2)
323 	mode = 2;
324       break;
325 
326     case TrackData::SUBCHAN_RW_RAW:
327       // we need raw PW writing
328       mode = 3;
329       break;
330     }
331   }
332 
333   log_message(5, "Sub-channel mode requested by toc: %d", mode);
334 
335   return mode;
336 }
337 
setSubChannelMode()338 int GenericMMCraw::setSubChannelMode()
339 {
340   delete subChannel_;
341   subChannel_ = NULL;
342 
343   subChannelMode_ = 0;
344 
345 #if 1
346   if (cdTextEncoder_ != NULL) {
347     if (setWriteParameters(3) == 0) {
348       subChannel_ = new PWSubChannel96;
349       subChannelMode_ = 3;
350     }
351     else {
352       delete cdTextEncoder_;
353       cdTextEncoder_ = NULL;
354 
355       log_message(force() ? -1 : -2,
356 	      "Cannot write CD-TEXT data because the 96 byte raw P-W sub-channel data mode is not supported.");
357 
358       if (force()) {
359 	log_message(-1, "Ignored because of --force option.");
360       }
361       else {
362 	log_message(-2, "Use option --force to ignore this error.");
363 	return 1;
364       }
365     }
366   }
367 
368   // check if the toc requires a certain sub-channel mode and try to set it
369   if (subChannel_ == NULL) {
370     int tocMode = getSubChannelModeFromToc();
371 
372     if (tocMode > 0) {
373       for (; tocMode <= 3 && subChannel_ == NULL; tocMode++) {
374 	if (setWriteParameters(tocMode) == 0) {
375 	  if (tocMode == 1)
376 	    subChannel_ = new PQSubChannel16;
377 	  else
378 	    subChannel_ = new PWSubChannel96;
379 
380 	  subChannelMode_ = tocMode;
381 	}
382       }
383 
384       if (subChannel_ == NULL) {
385 	log_message(-2, "Cannot setup sub-channel writing mode for sub-channel data defined in the toc-file.");
386 	return 1;
387       }
388     }
389   }
390 
391   // select any available sub-channel mode
392   if (subChannel_ == NULL) {
393     if (setWriteParameters(1) == 0) {
394       subChannel_ = new PQSubChannel16;
395       subChannelMode_ = 1;
396     }
397     else if (setWriteParameters(3) == 0) {
398       subChannel_ = new PWSubChannel96;
399       subChannelMode_ = 3;
400     }
401     else if (setWriteParameters(2) == 0) {
402       subChannel_ = new PWSubChannel96;
403       subChannelMode_ = 2;
404     }
405     else {
406       log_message(-2, "Cannot setup disk-at-once writing for this drive.");
407       return 1;
408     }
409   }
410 
411 #else
412 
413   //subChannel_ = new PWSubChannel96;
414   subChannel_ = new PQSubChannel16;
415   subChannelMode_ = 1;
416 #endif
417 
418   switch (subChannelMode_) {
419   case 1:
420     log_message(2, "Using 16 byte P-Q sub-channel data mode.");
421     break;
422   case 2:
423     log_message(2, "Using 96 byte packed P-W sub-channel data mode.");
424     break;
425   case 3:
426     if (cdTextEncoder_ != NULL)
427       log_message(2, "Using 96 byte raw P-W sub-channel data mode for CD-TEXT.");
428     else
429       log_message(2, "Using 96 byte raw P-W sub-channel data mode.");
430     break;
431   }
432 
433   return 0;
434 }
435 
initDao(const Toc * toc)436 int GenericMMCraw::initDao(const Toc *toc)
437 {
438   long n;
439 
440   CdrDriver::toc_ = toc;
441 
442   if (selectSpeed() != 0 ||
443       getSessionInfo() != 0) {
444     return 1;
445   }
446 
447   delete cdTextEncoder_;
448   cdTextEncoder_ = new CdTextEncoder(toc);
449   if (cdTextEncoder_->encode() != 0) {
450     log_message(-2, "CD-TEXT encoding failed.");
451     return 1;
452   }
453 
454   if (cdTextEncoder_->getSubChannels(&n) == NULL || n == 0) {
455     delete cdTextEncoder_;
456     cdTextEncoder_ = NULL;
457   }
458 
459   if (setSubChannelMode() != 0)
460     return 1;
461 
462   blockLength_ = AUDIO_BLOCK_LEN + subChannel_->dataLength();
463   blocksPerWrite_ = scsiIf_->maxDataLen() / blockLength_;
464   assert(blocksPerWrite_ > 0);
465   log_message(4, "Block length: %ld", blockLength_);
466 
467   long cueSheetLen;
468   unsigned char *cueSheet = createCueSheet(0, &cueSheetLen);
469 
470   if (cueSheet == NULL) {
471     return 1;
472   }
473 
474   if (setCueSheet(subChannel_, sessionFormat(), cueSheet, cueSheetLen,
475 		  leadInStart_) != 0) {
476     return 1;
477   }
478 
479   if (cdTextEncoder_ != NULL) {
480     cdTextStartLba_ = leadInStart_.lba() - 450150;
481     cdTextEndLba_ = cdTextStartLba_ + leadInLen_;
482     cdTextSubChannels_ = cdTextEncoder_->getSubChannels(&cdTextSubChannelCount_);
483     cdTextSubChannelAct_ = 0;
484   }
485   else {
486     cdTextStartLba_ = 0;
487     cdTextEndLba_ = 0;
488     cdTextSubChannels_ = NULL;
489     cdTextSubChannelCount_ = 0;
490     cdTextSubChannelAct_ = 0;
491   }
492 
493   // allocate buffer for write zeros
494   n = blocksPerWrite_ * (AUDIO_BLOCK_LEN + subChannel_->dataLength());
495   delete[] zeroBuffer_;
496   zeroBuffer_ = new char[n];
497   memset(zeroBuffer_, 0, n);
498 
499   // allocate buffer for sub-channel encoding
500   n = blocksPerWrite_ * blockLength_;
501   delete[] encodeBuffer_;
502   encodeBuffer_ = new unsigned char[n];
503 
504   delete[] encSubChannel_;
505   encSubChannel_ = new unsigned char[blocksPerWrite_ * subChannel_->dataLength()];
506 
507   /*
508   SessionInfo sessInfo;
509 
510   getMultiSessionInfo(1, 1, &sessInfo);
511 
512   return 1;
513   */
514 
515   if (!simulate_) {
516     if (performPowerCalibration() != 0) {
517       if (!force()) {
518 	log_message(-2, "Use option --force to ignore this error.");
519 	return 1;
520       }
521       else {
522 	log_message(-2, "Ignored because of option --force.");
523       }
524     }
525   }
526 
527   return 0;
528 }
529 
startDao()530 int GenericMMCraw::startDao()
531 {
532   log_message(2, "Writing lead-in and gap...");
533 
534   long lba = leadInStart_.lba() - 450150;
535 
536   if (writeZeros(CdrDriver::toc_->leadInMode(), TrackData::SUBCHAN_NONE,
537 		 lba, 0, leadInLen_) != 0) {
538     return 1;
539   }
540 
541   TrackData::SubChannelMode subChanMode = TrackData::SUBCHAN_NONE;
542   TrackIterator itr(CdrDriver::toc_);
543   const Track *tr;
544 
545   if ((tr = itr.first()) != NULL) {
546     subChanMode = tr->subChannelType();
547   }
548 
549   if (writeZeros(CdrDriver::toc_->leadInMode(), subChanMode, lba, 0, 150)
550       != 0) {
551     return 1;
552   }
553 
554   return 0;
555 }
556 
finishDao()557 int GenericMMCraw::finishDao()
558 {
559   int ret;
560 
561   log_message(2, "Writing lead-out...");
562 
563   long lba = CdrDriver::toc_->length().lba();
564 
565   writeZeros(CdrDriver::toc_->leadOutMode(), TrackData::SUBCHAN_NONE,
566 	     lba, lba + 150, leadOutLen_);
567 
568   log_message(2, "\nFlushing cache...");
569 
570   if (flushCache() != 0) {
571     return 1;
572   }
573 
574   while ((ret = checkDriveReady()) == 2)
575     mSleep(2000);
576 
577   if (ret != 0)
578     log_message(-1, "TEST UNIT READY failed after recording.");
579 
580   delete cdTextEncoder_, cdTextEncoder_ = NULL;
581   delete[] zeroBuffer_, zeroBuffer_ = NULL;
582   delete[] encodeBuffer_, encodeBuffer_ = NULL;
583 
584   return 0;
585 }
586 
nextWritableAddress()587 long GenericMMCraw::nextWritableAddress()
588 {
589   unsigned char cmd[10];
590   unsigned char data[28];
591   long lba = 0xffffffff;
592 
593   memset(cmd, 0, 10);
594   memset(data, 0, 28);
595 
596   cmd[0] = 0x52; // READ TRACK INFORMATION
597   cmd[1] = 0;
598   cmd[2] = lba >> 24;
599   cmd[3] = lba >> 16;
600   cmd[4] = lba >> 8;
601   cmd[5] = lba;
602   cmd[8] = 28;
603 
604   if (sendCmd(cmd, 10, NULL, 0, data, 28) != 0) {
605     log_message(-2, "Cannt get track information.");
606     return 0;
607   }
608 
609   long adr = (data[12] << 24) | (data[13] << 16) | (data[14] << 8) |
610     data[15];
611 
612   return adr;
613 
614 }
615 
616 // Writes data to target. The encoded sub-channel data is appended to each
617 // block.
618 // return: 0: OK
619 //         1: scsi command failed
writeData(TrackData::Mode mode,TrackData::SubChannelMode sm,long & lba,const char * buf,long len)620 int GenericMMCraw::writeData(TrackData::Mode mode,
621 			     TrackData::SubChannelMode sm,
622 			     long &lba, const char *buf, long len)
623 {
624   assert(blockLength_ > 0);
625   assert(blocksPerWrite_ > 0);
626   assert(mode == TrackData::AUDIO);
627   int writeLen = 0;
628   unsigned char cmd[10];
629   int i, j;
630 
631   long iblen = blockSize(mode, sm);
632   long slen = subChannel_->dataLength();
633 
634   /*
635   log_message(0, "lba: %ld, len: %ld, bpc: %d, bl: %d ", lba, len, blocksPerCmd,
636 	 blockLength_);
637    */
638 
639   memset(cmd, 0, 10);
640   cmd[0] = 0x2a; // WRITE1
641 
642   while (len > 0) {
643     writeLen = (len > blocksPerWrite_ ? blocksPerWrite_ : len);
644 
645     cmd[2] = lba >> 24;
646     cmd[3] = lba >> 16;
647     cmd[4] = lba >> 8;
648     cmd[5] = lba;
649 
650     cmd[7] = writeLen >> 8;
651     cmd[8] = writeLen;
652 
653     // encode the PQ sub-channel data
654     encode(lba, encSubChannel_, writeLen);
655 
656     for (i = 0; i < writeLen; i++) {
657       memcpy(encodeBuffer_ + i * blockLength_, buf + i * iblen,
658 	     AUDIO_BLOCK_LEN);
659 
660       memcpy(encodeBuffer_ + i * blockLength_ + AUDIO_BLOCK_LEN,
661 	     encSubChannel_ + i * slen, slen);
662 
663       if (cdTextSubChannels_ != NULL && lba >= cdTextStartLba_ &&
664 	  lba + i < cdTextEndLba_) {
665 
666 	const unsigned char *data = cdTextSubChannels_[cdTextSubChannelAct_]->data();
667 	long dataLen = cdTextSubChannels_[cdTextSubChannelAct_]->dataLength();
668 
669 	unsigned char *actBuf = encodeBuffer_ + i * blockLength_ + AUDIO_BLOCK_LEN;
670 
671 	//log_message(0, "Adding CD-TEXT channel %ld for LBA %ld", cdTextSubChannelAct_, lba + i);
672 	for (j = 0; j < dataLen; j++) {
673 	  *actBuf |= (*data & 0x3f);
674 	  actBuf++;
675 	  data++;
676 	}
677 
678 	cdTextSubChannelAct_++;
679 	if (cdTextSubChannelAct_ >= cdTextSubChannelCount_)
680 	  cdTextSubChannelAct_ = 0;
681       }
682       else {
683 	switch (sm) {
684 	case TrackData::SUBCHAN_NONE:
685 	  break;
686 
687 	case TrackData::SUBCHAN_RW:
688 	case TrackData::SUBCHAN_RW_RAW:
689 	  {
690 	    unsigned char *oBuf = encodeBuffer_ + i * blockLength_ + AUDIO_BLOCK_LEN;
691 	    const char *iBuf = buf + i * iblen + AUDIO_BLOCK_LEN;
692 
693 	    for (j = 0; j < PW_SUBCHANNEL_LEN; j++) {
694 	      *oBuf |= (*iBuf & 0x3f);
695 	      oBuf++;
696 	      iBuf++;
697 	    }
698 	  }
699 	  break;
700 	}
701       }
702     }
703 
704 
705 #if 0
706     // consistency checks
707     long sum1, sum2;
708     int n;
709     const char *p;
710     for (i = 0; i < writeLen; i++) {
711       log_message(0, "%ld: ", lba + i);
712       SubChannel *chan = subChannel_->makeSubChannel(encodeBuffer_ + i * blockLength_ + AUDIO_BLOCK_LEN);
713       chan->print();
714       delete chan;
715 
716       sum1 = 0;
717       for (p = buf + i * iblen, n = 0;
718 	   n < AUDIO_BLOCK_LEN; n++, p++) {
719 	sum1 += *p;
720       }
721 
722       sum2 = 0;
723       for (n = 0; n < AUDIO_BLOCK_LEN; n++) {
724 	sum2 += *(char *)(encodeBuffer_ + i * blockLength_ + n);
725       }
726 
727       //log_message(0, "%ld - %ld", sum1, sum2);
728       assert(sum1 == sum2);
729     }
730 #endif
731 
732 #if 1
733     if (sendCmd(cmd, 10, encodeBuffer_, writeLen * blockLength_,
734 		NULL, 0) != 0) {
735       log_message(-2, "Write data failed.");
736       return 1;
737     }
738 #endif
739     //log_message(0, ". ");
740 
741     lba += writeLen;
742     len -= writeLen;
743     buf += writeLen * iblen;
744   }
745 
746   //log_message(0, "");
747 
748   return 0;
749 }
750