BlueSCSI_cdrom.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. /* Advanced CD-ROM drive emulation.
  2. * Adds a few capabilities on top of the SCSI2SD CD-ROM emulation:
  3. *
  4. * - bin/cue support for support of multiple tracks
  5. * - on the fly image switching
  6. *
  7. * SCSI2SD V6 - Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
  8. * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
  9. *
  10. * This file is licensed under the GPL version 3 or any later version. 
  11. * It is derived from cdrom.c in SCSI2SD V6
  12. *
  13. * https://www.gnu.org/licenses/gpl-3.0.html
  14. * ----
  15. * This program is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation, either version 3 of the License, or
  18. * (at your option) any later version. 
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23. * GNU General Public License for more details. 
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  27. */
  28. #include <string.h>
  29. #include "BlueSCSI_cdrom.h"
  30. #include "BlueSCSI_log.h"
  31. #include "BlueSCSI_config.h"
  32. #include "BlueSCSI_cdrom.h"
  33. #include <CUEParser.h>
  34. extern "C" {
  35. #include <scsi.h>
  36. }
  37. /******************************************/
  38. /* Basic TOC generation without cue sheet */
  39. /******************************************/
  40. static const uint8_t SimpleTOC[] =
  41. {
  42. 0x00, // toc length, MSB
  43. 0x12, // toc length, LSB
  44. 0x01, // First track number
  45. 0x01, // Last track number,
  46. // TRACK 1 Descriptor
  47. 0x00, // reserved
  48. 0x14, // Q sub-channel encodes current position, Digital track
  49. 0x01, // Track 1,
  50. 0x00, // Reserved
  51. 0x00,0x00,0x00,0x00, // Track start sector (LBA)
  52. 0x00, // reserved
  53. 0x14, // Q sub-channel encodes current position, Digital track
  54. 0xAA, // Leadout Track
  55. 0x00, // Reserved
  56. 0x00,0x00,0x00,0x00, // Track start sector (LBA)
  57. };
  58. static const uint8_t LeadoutTOC[] =
  59. {
  60. 0x00, // toc length, MSB
  61. 0x0A, // toc length, LSB
  62. 0x01, // First track number
  63. 0x01, // Last track number,
  64. 0x00, // reserved
  65. 0x14, // Q sub-channel encodes current position, Digital track
  66. 0xAA, // Leadout Track
  67. 0x00, // Reserved
  68. 0x00,0x00,0x00,0x00, // Track start sector (LBA)
  69. };
  70. static const uint8_t SessionTOC[] =
  71. {
  72. 0x00, // toc length, MSB
  73. 0x0A, // toc length, LSB
  74. 0x01, // First session number
  75. 0x01, // Last session number,
  76. // TRACK 1 Descriptor
  77. 0x00, // reserved
  78. 0x14, // Q sub-channel encodes current position, Digital track
  79. 0x01, // First track number in last complete session
  80. 0x00, // Reserved
  81. 0x00,0x00,0x00,0x00 // LBA of first track in last session
  82. };
  83. static const uint8_t FullTOC[] =
  84. {
  85. 0x00, // 0: toc length, MSB
  86. 0x44, // 1: toc length, LSB
  87. 0x01, // 2: First session number
  88. 0x01, // 3: Last session number,
  89. // A0 Descriptor
  90. 0x01, // 4: session number
  91. 0x14, // 5: ADR/Control
  92. 0x00, // 6: TNO
  93. 0xA0, // 7: POINT
  94. 0x00, // 8: Min
  95. 0x00, // 9: Sec
  96. 0x00, // 10: Frame
  97. 0x00, // 11: Zero
  98. 0x01, // 12: First Track number.
  99. 0x00, // 13: Disc type 00 = Mode 1
  100. 0x00, // 14: PFRAME
  101. // A1
  102. 0x01, // 15: session number
  103. 0x14, // 16: ADR/Control
  104. 0x00, // 17: TNO
  105. 0xA1, // 18: POINT
  106. 0x00, // 19: Min
  107. 0x00, // 20: Sec
  108. 0x00, // 21: Frame
  109. 0x00, // 22: Zero
  110. 0x01, // 23: Last Track number
  111. 0x00, // 24: PSEC
  112. 0x00, // 25: PFRAME
  113. // A2
  114. 0x01, // 26: session number
  115. 0x14, // 27: ADR/Control
  116. 0x00, // 28: TNO
  117. 0xA2, // 29: POINT
  118. 0x00, // 30: Min
  119. 0x00, // 31: Sec
  120. 0x00, // 32: Frame
  121. 0x00, // 33: Zero
  122. 0x79, // 34: LEADOUT position BCD
  123. 0x59, // 35: leadout PSEC BCD
  124. 0x74, // 36: leadout PFRAME BCD
  125. // TRACK 1 Descriptor
  126. 0x01, // 37: session number
  127. 0x14, // 38: ADR/Control
  128. 0x00, // 39: TNO
  129. 0x01, // 40: Point
  130. 0x00, // 41: Min
  131. 0x00, // 42: Sec
  132. 0x00, // 43: Frame
  133. 0x00, // 44: Zero
  134. 0x00, // 45: PMIN
  135. 0x00, // 46: PSEC
  136. 0x00, // 47: PFRAME
  137. // b0
  138. 0x01, // 48: session number
  139. 0x54, // 49: ADR/Control
  140. 0x00, // 50: TNO
  141. 0xB1, // 51: POINT
  142. 0x79, // 52: Min BCD
  143. 0x59, // 53: Sec BCD
  144. 0x74, // 54: Frame BCD
  145. 0x00, // 55: Zero
  146. 0x79, // 56: PMIN BCD
  147. 0x59, // 57: PSEC BCD
  148. 0x74, // 58: PFRAME BCD
  149. // c0
  150. 0x01, // 59: session number
  151. 0x54, // 60: ADR/Control
  152. 0x00, // 61: TNO
  153. 0xC0, // 62: POINT
  154. 0x00, // 63: Min
  155. 0x00, // 64: Sec
  156. 0x00, // 65: Frame
  157. 0x00, // 66: Zero
  158. 0x00, // 67: PMIN
  159. 0x00, // 68: PSEC
  160. 0x00 // 69: PFRAME
  161. };
  162. static void LBA2MSF(uint32_t LBA, uint8_t* MSF)
  163. {
  164. MSF[0] = 0; // reserved.
  165. MSF[3] = LBA % 75; // M
  166. uint32_t rem = LBA / 75;
  167. MSF[2] = rem % 60; // S
  168. MSF[1] = rem / 60;
  169. }
  170. static void doReadTOCSimple(int MSF, uint8_t track, uint16_t allocationLength)
  171. {
  172. if (track == 0xAA)
  173. {
  174. // 0xAA requests only lead-out track information (reports capacity)
  175. uint32_t len = sizeof(LeadoutTOC);
  176. memcpy(scsiDev.data, LeadoutTOC, len);
  177. uint32_t capacity = getScsiCapacity(
  178. scsiDev.target->cfg->sdSectorStart,
  179. scsiDev.target->liveCfg.bytesPerSector,
  180. scsiDev.target->cfg->scsiSectors);
  181. // Replace start of leadout track
  182. if (MSF)
  183. {
  184. LBA2MSF(capacity, scsiDev.data + 8);
  185. }
  186. else
  187. {
  188. scsiDev.data[8] = capacity >> 24;
  189. scsiDev.data[9] = capacity >> 16;
  190. scsiDev.data[10] = capacity >> 8;
  191. scsiDev.data[11] = capacity;
  192. }
  193. if (len > allocationLength)
  194. {
  195. len = allocationLength;
  196. }
  197. scsiDev.dataLen = len;
  198. scsiDev.phase = DATA_IN;
  199. }
  200. else if (track <= 1)
  201. {
  202. // We only support track 1.
  203. // track 0 means "return all tracks"
  204. uint32_t len = sizeof(SimpleTOC);
  205. memcpy(scsiDev.data, SimpleTOC, len);
  206. uint32_t capacity = getScsiCapacity(
  207. scsiDev.target->cfg->sdSectorStart,
  208. scsiDev.target->liveCfg.bytesPerSector,
  209. scsiDev.target->cfg->scsiSectors);
  210. // Replace start of leadout track
  211. if (MSF)
  212. {
  213. LBA2MSF(capacity, scsiDev.data + 0x10);
  214. }
  215. else
  216. {
  217. scsiDev.data[0x10] = capacity >> 24;
  218. scsiDev.data[0x11] = capacity >> 16;
  219. scsiDev.data[0x12] = capacity >> 8;
  220. scsiDev.data[0x13] = capacity;
  221. }
  222. if (len > allocationLength)
  223. {
  224. len = allocationLength;
  225. }
  226. scsiDev.dataLen = len;
  227. scsiDev.phase = DATA_IN;
  228. }
  229. else
  230. {
  231. scsiDev.status = CHECK_CONDITION;
  232. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  233. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  234. scsiDev.phase = STATUS;
  235. }
  236. }
  237. static void doReadSessionInfoSimple(uint8_t session, uint16_t allocationLength)
  238. {
  239. uint32_t len = sizeof(SessionTOC);
  240. memcpy(scsiDev.data, SessionTOC, len);
  241. if (len > allocationLength)
  242. {
  243. len = allocationLength;
  244. }
  245. scsiDev.dataLen = len;
  246. scsiDev.phase = DATA_IN;
  247. }
  248. static inline uint8_t
  249. fromBCD(uint8_t val)
  250. {
  251. return ((val >> 4) * 10) + (val & 0xF);
  252. }
  253. static void doReadFullTOCSimple(int convertBCD, uint8_t session, uint16_t allocationLength)
  254. {
  255. // We only support session 1.
  256. if (session > 1)
  257. {
  258. scsiDev.status = CHECK_CONDITION;
  259. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  260. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  261. scsiDev.phase = STATUS;
  262. }
  263. else
  264. {
  265. uint32_t len = sizeof(FullTOC);
  266. memcpy(scsiDev.data, FullTOC, len);
  267. if (convertBCD)
  268. {
  269. int descriptor = 4;
  270. while (descriptor < len)
  271. {
  272. int i;
  273. for (i = 0; i < 7; ++i)
  274. {
  275. scsiDev.data[descriptor + i] =
  276. fromBCD(scsiDev.data[descriptor + 4 + i]);
  277. }
  278. descriptor += 11;
  279. }
  280. }
  281. if (len > allocationLength)
  282. {
  283. len = allocationLength;
  284. }
  285. scsiDev.dataLen = len;
  286. scsiDev.phase = DATA_IN;
  287. }
  288. }
  289. static uint8_t SimpleHeader[] =
  290. {
  291. 0x01, // 2048byte user data, L-EC in 288 byte aux field.
  292. 0x00, // reserved
  293. 0x00, // reserved
  294. 0x00, // reserved
  295. 0x00,0x00,0x00,0x00 // Track start sector (LBA or MSF)
  296. };
  297. void doReadHeaderSimple(int MSF, uint32_t lba, uint16_t allocationLength)
  298. {
  299. uint32_t len = sizeof(SimpleHeader);
  300. memcpy(scsiDev.data, SimpleHeader, len);
  301. if (len > allocationLength)
  302. {
  303. len = allocationLength;
  304. }
  305. scsiDev.dataLen = len;
  306. scsiDev.phase = DATA_IN;
  307. }
  308. /*********************************/
  309. /* TOC generation from cue sheet */
  310. /*********************************/
  311. // Format track info read from cue sheet into the format used by ReadTOC command.
  312. // Refer to T10/1545-D MMC-4 Revision 5a, "Response Format 0000b: Formatted TOC"
  313. static void formatTrackInfo(const CUETrackInfo *track, uint8_t *dest, bool use_MSF_time)
  314. {
  315. uint8_t control_adr = 0x14; // Digital track
  316. if (track->track_mode == CUETrack_AUDIO)
  317. {
  318. control_adr = 0x10; // Audio track
  319. }
  320. dest[0] = 0; // Reserved
  321. dest[1] = control_adr;
  322. dest[2] = track->track_number;
  323. dest[3] = 0; // Reserved
  324. if (use_MSF_time)
  325. {
  326. // Time in minute-second-frame format
  327. LBA2MSF(track->data_start, &dest[4]);
  328. }
  329. else
  330. {
  331. // Time as logical block address
  332. dest[4] = (track->data_start >> 24) & 0xFF;
  333. dest[5] = (track->data_start >> 16) & 0xFF;
  334. dest[6] = (track->data_start >> 8) & 0xFF;
  335. dest[7] = (track->data_start >> 0) & 0xFF;
  336. }
  337. }
  338. // Load data from CUE sheet for the given device,
  339. // using the second half of scsiDev.data buffer for temporary storage.
  340. // Returns false if no cue sheet or it could not be opened.
  341. static bool loadCueSheet(image_config_t &img, CUEParser &parser)
  342. {
  343. if (!img.cuesheetfile.isOpen())
  344. {
  345. return false;
  346. }
  347. // Use second half of scsiDev.data as the buffer for cue sheet text
  348. char *cuebuf = (char*)&scsiDev.data[sizeof(scsiDev.data) / 2];
  349. img.cuesheetfile.seek(0);
  350. int len = img.cuesheetfile.read(cuebuf, sizeof(cuebuf));
  351. if (len <= 0)
  352. {
  353. return false;
  354. }
  355. cuebuf[len] = '\0';
  356. parser = CUEParser(cuebuf);
  357. return true;
  358. }
  359. static void doReadTOC(int MSF, uint8_t track, uint16_t allocationLength)
  360. {
  361. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  362. CUEParser parser;
  363. if (!loadCueSheet(img, parser))
  364. {
  365. // No CUE sheet, use hardcoded data
  366. return doReadTOCSimple(MSF, track, allocationLength);
  367. }
  368. // Format track info
  369. uint8_t *trackdata = &scsiDev.data[4];
  370. int trackcount = 0;
  371. int firsttrack = -1;
  372. int lasttrack = -1;
  373. const CUETrackInfo *trackinfo;
  374. while ((trackinfo = parser.next_track()) != NULL)
  375. {
  376. if (firsttrack < 0) firsttrack = trackinfo->track_number;
  377. lasttrack = trackinfo->track_number;
  378. if (track == 0 || track == trackinfo->track_number)
  379. {
  380. formatTrackInfo(trackinfo, &trackdata[8 * trackcount], MSF);
  381. trackcount += 1;
  382. }
  383. }
  384. // Format lead-out track info
  385. if (track == 0 || track == 0xAA)
  386. {
  387. CUETrackInfo leadout = {};
  388. leadout.track_number = 0xAA;
  389. leadout.track_mode = CUETrack_MODE1_2048;
  390. leadout.data_start = img.scsiSectors;
  391. formatTrackInfo(&leadout, &trackdata[8 * trackcount], MSF);
  392. trackcount += 1;
  393. }
  394. // Format response header
  395. uint16_t toc_length = 2 + trackcount * 8;
  396. scsiDev.data[0] = toc_length >> 8;
  397. scsiDev.data[1] = toc_length & 0xFF;
  398. scsiDev.data[2] = firsttrack;
  399. scsiDev.data[3] = lasttrack;
  400. if (trackcount == 0)
  401. {
  402. // Unknown track requested
  403. scsiDev.status = CHECK_CONDITION;
  404. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  405. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  406. scsiDev.phase = STATUS;
  407. }
  408. else
  409. {
  410. uint32_t len = 2 + toc_length;
  411. if (len > allocationLength)
  412. {
  413. len = allocationLength;
  414. }
  415. scsiDev.dataLen = len;
  416. scsiDev.phase = DATA_IN;
  417. }
  418. }
  419. static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
  420. {
  421. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  422. CUEParser parser;
  423. if (!loadCueSheet(img, parser))
  424. {
  425. // No CUE sheet, use hardcoded data
  426. return doReadSessionInfoSimple(session, allocationLength);
  427. }
  428. uint32_t len = sizeof(SessionTOC);
  429. memcpy(scsiDev.data, SessionTOC, len);
  430. // Replace first track info in the session table
  431. // based on data from CUE sheet.
  432. const CUETrackInfo *trackinfo = parser.next_track();
  433. if (trackinfo)
  434. {
  435. formatTrackInfo(trackinfo, &scsiDev.data[4], false);
  436. }
  437. if (len > allocationLength)
  438. {
  439. len = allocationLength;
  440. }
  441. scsiDev.dataLen = len;
  442. scsiDev.phase = DATA_IN;
  443. }
  444. static void LBA2MSFRaw(uint32_t LBA, uint8_t* MSF)
  445. {
  446. MSF[2] = LBA % 75; // M
  447. uint32_t rem = LBA / 75;
  448. MSF[1] = rem % 60; // S
  449. MSF[0] = rem / 60;
  450. }
  451. // Format track info read from cue sheet into the format used by ReadFullTOC command.
  452. // Refer to T10/1545-D MMC-4 Revision 5a, "Response Format 0010b: Raw TOC"
  453. static void formatRawTrackInfo(const CUETrackInfo *track, uint8_t *dest)
  454. {
  455. uint8_t control_adr = 0x14; // Digital track
  456. if (track->track_mode == CUETrack_AUDIO)
  457. {
  458. control_adr = 0x10; // Audio track
  459. }
  460. dest[0] = 0x01; // Session always 1
  461. dest[1] = control_adr;
  462. dest[2] = 0x00; // "TNO", always 0?
  463. dest[3] = track->track_number; // "POINT", contains track number
  464. if (track->pregap_start > 0)
  465. {
  466. LBA2MSFRaw(track->pregap_start, &dest[4]);
  467. }
  468. else
  469. {
  470. LBA2MSFRaw(track->data_start, &dest[4]);
  471. }
  472. dest[7] = 0; // HOUR
  473. LBA2MSFRaw(track->data_start, &dest[8]);
  474. }
  475. static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLength)
  476. {
  477. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  478. CUEParser parser;
  479. if (!loadCueSheet(img, parser))
  480. {
  481. // No CUE sheet, use hardcoded data
  482. return doReadFullTOCSimple(convertBCD, session, allocationLength);
  483. }
  484. // We only support session 1.
  485. if (session > 1)
  486. {
  487. scsiDev.status = CHECK_CONDITION;
  488. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  489. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  490. scsiDev.phase = STATUS;
  491. return;
  492. }
  493. // Take the beginning of the hardcoded TOC as base
  494. uint32_t len = 4 + 11 * 3; // Header, A0, A1, A2
  495. memcpy(scsiDev.data, FullTOC, len);
  496. // Add track descriptors
  497. int trackcount = 0;
  498. int firsttrack = -1;
  499. int lasttrack = -1;
  500. const CUETrackInfo *trackinfo;
  501. while ((trackinfo = parser.next_track()) != NULL)
  502. {
  503. if (firsttrack < 0) firsttrack = trackinfo->track_number;
  504. lasttrack = trackinfo->track_number;
  505. formatRawTrackInfo(trackinfo, &scsiDev.data[len]);
  506. trackcount += 1;
  507. len += 11;
  508. }
  509. // First and last track numbers
  510. scsiDev.data[12] = firsttrack;
  511. scsiDev.data[23] = lasttrack;
  512. // Leadout track position
  513. LBA2MSFRaw(img.scsiSectors, &scsiDev.data[34]);
  514. // Append recordable disc records b0 and c0 indicating non-recordable disc
  515. memcpy(scsiDev.data + len, &FullTOC[48], 22);
  516. len += 22;
  517. // Correct the record length in header
  518. uint16_t toclen = len - 2;
  519. scsiDev.data[0] = toclen >> 8;
  520. scsiDev.data[1] = toclen & 0xFF;
  521. if (len > allocationLength)
  522. {
  523. len = allocationLength;
  524. }
  525. scsiDev.dataLen = len;
  526. scsiDev.phase = DATA_IN;
  527. }
  528. // SCSI-3 MMC Read Header command, seems to be deprecated in later standards.
  529. // Refer to ANSI X3.304-1997
  530. void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
  531. {
  532. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  533. CUEParser parser;
  534. if (!loadCueSheet(img, parser))
  535. {
  536. // No CUE sheet, use hardcoded data
  537. return doReadHeaderSimple(MSF, lba, allocationLength);
  538. }
  539. // Take the hardcoded header as base
  540. uint32_t len = sizeof(SimpleHeader);
  541. memcpy(scsiDev.data, SimpleHeader, len);
  542. // Search the track with the requested LBA
  543. const CUETrackInfo *trackinfo;
  544. CUETrackMode trackmode = CUETrack_MODE1_2048;
  545. uint32_t trackstart = 0;
  546. while ((trackinfo = parser.next_track()) != NULL)
  547. {
  548. if (trackinfo->data_start < lba)
  549. {
  550. trackstart = trackinfo->data_start;
  551. trackmode = trackinfo->track_mode;
  552. }
  553. }
  554. // Track mode (audio / data)
  555. if (trackmode == CUETrack_AUDIO)
  556. {
  557. scsiDev.data[0] = 0;
  558. }
  559. // Track start
  560. if (MSF)
  561. {
  562. LBA2MSF(trackstart, &scsiDev.data[4]);
  563. }
  564. else
  565. {
  566. scsiDev.data[4] = (trackstart >> 24) & 0xFF;
  567. scsiDev.data[5] = (trackstart >> 16) & 0xFF;
  568. scsiDev.data[6] = (trackstart >> 8) & 0xFF;
  569. scsiDev.data[7] = (trackstart >> 0) & 0xFF;
  570. }
  571. if (len > allocationLength)
  572. {
  573. len = allocationLength;
  574. }
  575. scsiDev.dataLen = len;
  576. scsiDev.phase = DATA_IN;
  577. }
  578. /****************************************/
  579. /* CUE sheet check at image load time */
  580. /****************************************/
  581. bool cdromValidateCueSheet(image_config_t &img)
  582. {
  583. CUEParser parser;
  584. if (!loadCueSheet(img, parser))
  585. {
  586. return false;
  587. }
  588. const CUETrackInfo *trackinfo;
  589. int trackcount = 0;
  590. while ((trackinfo = parser.next_track()) != NULL)
  591. {
  592. trackcount++;
  593. if (trackinfo->track_mode != CUETrack_AUDIO &&
  594. trackinfo->track_mode != CUETrack_MODE1_2048)
  595. {
  596. log("---- Warning: track ", trackinfo->track_number, " has unsupported mode ", (int)trackinfo->track_mode);
  597. }
  598. if (trackinfo->file_mode != CUEFile_BINARY)
  599. {
  600. log("---- Unsupported CUE data file mode ", (int)trackinfo->file_mode);
  601. }
  602. }
  603. if (trackcount == 0)
  604. {
  605. log("---- Opened cue sheet but no valid tracks found");
  606. return false;
  607. }
  608. log("---- Cue sheet loaded with ", (int)trackcount, " tracks");
  609. return true;
  610. }
  611. /**************************************/
  612. /* Ejection and image switching logic */
  613. /**************************************/
  614. // Reinsert any ejected CDROMs on reboot
  615. void cdromReinsertFirstImage(image_config_t &img)
  616. {
  617. if (img.image_index > 0)
  618. {
  619. // Multiple images for this drive, force restart from first one
  620. debuglog("---- Restarting from first CD-ROM image");
  621. img.image_index = 9;
  622. cdromSwitchNextImage(img);
  623. }
  624. else if (img.ejected)
  625. {
  626. // Reinsert the single image
  627. debuglog("---- Closing CD-ROM tray");
  628. img.ejected = false;
  629. img.cdrom_events = 2; // New media
  630. }
  631. }
  632. // Check if we have multiple CD-ROM images to cycle when drive is ejected.
  633. bool cdromSwitchNextImage(image_config_t &img)
  634. {
  635. // Check if we have a next image to load, so that drive is closed next time the host asks.
  636. img.image_index++;
  637. char filename[MAX_FILE_PATH];
  638. int target_idx = img.scsiId & 7;
  639. if (!scsiDiskGetImageNameFromConfig(img, filename, sizeof(filename)))
  640. {
  641. img.image_index = 0;
  642. scsiDiskGetImageNameFromConfig(img, filename, sizeof(filename));
  643. }
  644. if (filename[0] != '\0')
  645. {
  646. log("Switching to next CD-ROM image for ", target_idx, ": ", filename);
  647. img.file.close();
  648. bool status = scsiDiskOpenHDDImage(target_idx, filename, target_idx, 0, 2048);
  649. if (status)
  650. {
  651. img.ejected = false;
  652. img.cdrom_events = 2; // New media
  653. return true;
  654. }
  655. }
  656. return false;
  657. }
  658. static void doGetEventStatusNotification(bool immed)
  659. {
  660. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  661. if (!immed)
  662. {
  663. // Asynchronous notification not supported
  664. scsiDev.status = CHECK_CONDITION;
  665. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  666. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  667. scsiDev.phase = STATUS;
  668. }
  669. else if (img.cdrom_events)
  670. {
  671. scsiDev.data[0] = 0;
  672. scsiDev.data[1] = 6; // EventDataLength
  673. scsiDev.data[2] = 0x04; // Media status events
  674. scsiDev.data[3] = 0x04; // Supported events
  675. scsiDev.data[4] = img.cdrom_events;
  676. scsiDev.data[5] = 0x01; // Power status
  677. scsiDev.data[6] = 0; // Start slot
  678. scsiDev.data[7] = 0; // End slot
  679. scsiDev.dataLen = 8;
  680. scsiDev.phase = DATA_IN;
  681. img.cdrom_events = 0;
  682. if (img.ejected)
  683. {
  684. // We are now reporting to host that the drive is open.
  685. // Simulate a "close" for next time the host polls.
  686. cdromSwitchNextImage(img);
  687. }
  688. }
  689. else
  690. {
  691. scsiDev.data[0] = 0;
  692. scsiDev.data[1] = 2; // EventDataLength
  693. scsiDev.data[2] = 0x00; // Media status events
  694. scsiDev.data[3] = 0x04; // Supported events
  695. scsiDev.dataLen = 4;
  696. scsiDev.phase = DATA_IN;
  697. }
  698. }
  699. /**************************************/
  700. /* CD-ROM command dispatching */
  701. /**************************************/
  702. // Handle direct-access scsi device commands
  703. extern "C" int scsiCDRomCommand()
  704. {
  705. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  706. int commandHandled = 1;
  707. uint8_t command = scsiDev.cdb[0];
  708. if (command == 0x1B && (scsiDev.cdb[4] & 2))
  709. {
  710. // CD-ROM load & eject
  711. int start = scsiDev.cdb[4] & 1;
  712. if (start)
  713. {
  714. debuglog("------ CDROM close tray");
  715. img.ejected = false;
  716. img.cdrom_events = 2; // New media
  717. }
  718. else
  719. {
  720. debuglog("------ CDROM open tray");
  721. img.ejected = true;
  722. img.cdrom_events = 3; // Media removal
  723. }
  724. }
  725. else if (command == 0x43)
  726. {
  727. // CD-ROM Read TOC
  728. int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
  729. uint8_t track = scsiDev.cdb[6];
  730. uint16_t allocationLength =
  731. (((uint32_t) scsiDev.cdb[7]) << 8) +
  732. scsiDev.cdb[8];
  733. // Reject MMC commands for now, otherwise the TOC data format
  734. // won't be understood.
  735. // The "format" field is reserved for SCSI-2
  736. uint8_t format = scsiDev.cdb[2] & 0x0F;
  737. switch (format)
  738. {
  739. case 0: doReadTOC(MSF, track, allocationLength); break; // SCSI-2
  740. case 1: doReadSessionInfo(MSF, allocationLength); break; // MMC2
  741. case 2: doReadFullTOC(0, track, allocationLength); break; // MMC2
  742. case 3: doReadFullTOC(1, track, allocationLength); break; // MMC2
  743. default:
  744. {
  745. scsiDev.status = CHECK_CONDITION;
  746. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  747. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  748. scsiDev.phase = STATUS;
  749. }
  750. }
  751. }
  752. else if (command == 0x44)
  753. {
  754. // CD-ROM Read Header
  755. int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
  756. uint32_t lba = 0; // IGNORED for now
  757. uint16_t allocationLength =
  758. (((uint32_t) scsiDev.cdb[7]) << 8) +
  759. scsiDev.cdb[8];
  760. doReadHeader(MSF, lba, allocationLength);
  761. }
  762. else if (command == 0x4A)
  763. {
  764. // Get event status notifications (media change notifications)
  765. bool immed = scsiDev.cdb[1] & 1;
  766. doGetEventStatusNotification(immed);
  767. }
  768. else
  769. {
  770. commandHandled = 0;
  771. }
  772. return commandHandled;
  773. }