BlueSCSI_cdrom.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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. extern "C" {
  33. #include <scsi.h>
  34. }
  35. /******************************************/
  36. /* Basic TOC generation without cue sheet */
  37. /******************************************/
  38. static const uint8_t SimpleTOC[] =
  39. {
  40. 0x00, // toc length, MSB
  41. 0x12, // toc length, LSB
  42. 0x01, // First track number
  43. 0x01, // Last track number,
  44. // TRACK 1 Descriptor
  45. 0x00, // reserved
  46. 0x14, // Q sub-channel encodes current position, Digital track
  47. 0x01, // Track 1,
  48. 0x00, // Reserved
  49. 0x00,0x00,0x00,0x00, // Track start sector (LBA)
  50. 0x00, // reserved
  51. 0x14, // Q sub-channel encodes current position, Digital track
  52. 0xAA, // Leadout Track
  53. 0x00, // Reserved
  54. 0x00,0x00,0x00,0x00, // Track start sector (LBA)
  55. };
  56. static const uint8_t LeadoutTOC[] =
  57. {
  58. 0x00, // toc length, MSB
  59. 0x0A, // toc length, LSB
  60. 0x01, // First track number
  61. 0x01, // Last track number,
  62. 0x00, // reserved
  63. 0x14, // Q sub-channel encodes current position, Digital track
  64. 0xAA, // Leadout Track
  65. 0x00, // Reserved
  66. 0x00,0x00,0x00,0x00, // Track start sector (LBA)
  67. };
  68. static const uint8_t SessionTOC[] =
  69. {
  70. 0x00, // toc length, MSB
  71. 0x0A, // toc length, LSB
  72. 0x01, // First session number
  73. 0x01, // Last session number,
  74. // TRACK 1 Descriptor
  75. 0x00, // reserved
  76. 0x14, // Q sub-channel encodes current position, Digital track
  77. 0x01, // First track number in last complete session
  78. 0x00, // Reserved
  79. 0x00,0x00,0x00,0x00 // LBA of first track in last session
  80. };
  81. static const uint8_t FullTOC[] =
  82. {
  83. 0x00, // toc length, MSB
  84. 0x44, // toc length, LSB
  85. 0x01, // First session number
  86. 0x01, // Last session number,
  87. // A0 Descriptor
  88. 0x01, // session number
  89. 0x14, // ADR/Control
  90. 0x00, // TNO
  91. 0xA0, // POINT
  92. 0x00, // Min
  93. 0x00, // Sec
  94. 0x00, // Frame
  95. 0x00, // Zero
  96. 0x01, // First Track number.
  97. 0x00, // Disc type 00 = Mode 1
  98. 0x00, // PFRAME
  99. // A1
  100. 0x01, // session number
  101. 0x14, // ADR/Control
  102. 0x00, // TNO
  103. 0xA1, // POINT
  104. 0x00, // Min
  105. 0x00, // Sec
  106. 0x00, // Frame
  107. 0x00, // Zero
  108. 0x01, // Last Track number
  109. 0x00, // PSEC
  110. 0x00, // PFRAME
  111. // A2
  112. 0x01, // session number
  113. 0x14, // ADR/Control
  114. 0x00, // TNO
  115. 0xA2, // POINT
  116. 0x00, // Min
  117. 0x00, // Sec
  118. 0x00, // Frame
  119. 0x00, // Zero
  120. 0x79, // LEADOUT position BCD
  121. 0x59, // leadout PSEC BCD
  122. 0x74, // leadout PFRAME BCD
  123. // TRACK 1 Descriptor
  124. 0x01, // session number
  125. 0x14, // ADR/Control
  126. 0x00, // TNO
  127. 0x01, // Point
  128. 0x00, // Min
  129. 0x00, // Sec
  130. 0x00, // Frame
  131. 0x00, // Zero
  132. 0x00, // PMIN
  133. 0x00, // PSEC
  134. 0x00, // PFRAME
  135. // b0
  136. 0x01, // session number
  137. 0x54, // ADR/Control
  138. 0x00, // TNO
  139. 0xB1, // POINT
  140. 0x79, // Min BCD
  141. 0x59, // Sec BCD
  142. 0x74, // Frame BCD
  143. 0x00, // Zero
  144. 0x79, // PMIN BCD
  145. 0x59, // PSEC BCD
  146. 0x74, // PFRAME BCD
  147. // c0
  148. 0x01, // session number
  149. 0x54, // ADR/Control
  150. 0x00, // TNO
  151. 0xC0, // POINT
  152. 0x00, // Min
  153. 0x00, // Sec
  154. 0x00, // Frame
  155. 0x00, // Zero
  156. 0x00, // PMIN
  157. 0x00, // PSEC
  158. 0x00 // PFRAME
  159. };
  160. static void LBA2MSF(uint32_t LBA, uint8_t* MSF)
  161. {
  162. MSF[0] = 0; // reserved.
  163. MSF[3] = LBA % 75; // M
  164. uint32_t rem = LBA / 75;
  165. MSF[2] = rem % 60; // S
  166. MSF[1] = rem / 60;
  167. }
  168. static void doReadTOC(int MSF, uint8_t track, uint16_t allocationLength)
  169. {
  170. if (track == 0xAA)
  171. {
  172. // 0xAA requests only lead-out track information (reports capacity)
  173. uint32_t len = sizeof(LeadoutTOC);
  174. memcpy(scsiDev.data, LeadoutTOC, len);
  175. uint32_t capacity = getScsiCapacity(
  176. scsiDev.target->cfg->sdSectorStart,
  177. scsiDev.target->liveCfg.bytesPerSector,
  178. scsiDev.target->cfg->scsiSectors);
  179. // Replace start of leadout track
  180. if (MSF)
  181. {
  182. LBA2MSF(capacity, scsiDev.data + 8);
  183. }
  184. else
  185. {
  186. scsiDev.data[8] = capacity >> 24;
  187. scsiDev.data[9] = capacity >> 16;
  188. scsiDev.data[10] = capacity >> 8;
  189. scsiDev.data[11] = capacity;
  190. }
  191. if (len > allocationLength)
  192. {
  193. len = allocationLength;
  194. }
  195. scsiDev.dataLen = len;
  196. scsiDev.phase = DATA_IN;
  197. }
  198. else if (track <= 1)
  199. {
  200. // We only support track 1.
  201. // track 0 means "return all tracks"
  202. uint32_t len = sizeof(SimpleTOC);
  203. memcpy(scsiDev.data, SimpleTOC, len);
  204. uint32_t capacity = getScsiCapacity(
  205. scsiDev.target->cfg->sdSectorStart,
  206. scsiDev.target->liveCfg.bytesPerSector,
  207. scsiDev.target->cfg->scsiSectors);
  208. // Replace start of leadout track
  209. if (MSF)
  210. {
  211. LBA2MSF(capacity, scsiDev.data + 0x10);
  212. }
  213. else
  214. {
  215. scsiDev.data[0x10] = capacity >> 24;
  216. scsiDev.data[0x11] = capacity >> 16;
  217. scsiDev.data[0x12] = capacity >> 8;
  218. scsiDev.data[0x13] = capacity;
  219. }
  220. if (len > allocationLength)
  221. {
  222. len = allocationLength;
  223. }
  224. scsiDev.dataLen = len;
  225. scsiDev.phase = DATA_IN;
  226. }
  227. else
  228. {
  229. scsiDev.status = CHECK_CONDITION;
  230. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  231. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  232. scsiDev.phase = STATUS;
  233. }
  234. }
  235. static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
  236. {
  237. uint32_t len = sizeof(SessionTOC);
  238. memcpy(scsiDev.data, SessionTOC, len);
  239. if (len > allocationLength)
  240. {
  241. len = allocationLength;
  242. }
  243. scsiDev.dataLen = len;
  244. scsiDev.phase = DATA_IN;
  245. }
  246. static inline uint8_t
  247. fromBCD(uint8_t val)
  248. {
  249. return ((val >> 4) * 10) + (val & 0xF);
  250. }
  251. static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLength)
  252. {
  253. // We only support session 1.
  254. if (session > 1)
  255. {
  256. scsiDev.status = CHECK_CONDITION;
  257. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  258. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  259. scsiDev.phase = STATUS;
  260. }
  261. else
  262. {
  263. uint32_t len = sizeof(FullTOC);
  264. memcpy(scsiDev.data, FullTOC, len);
  265. if (convertBCD)
  266. {
  267. int descriptor = 4;
  268. while (descriptor < len)
  269. {
  270. int i;
  271. for (i = 0; i < 7; ++i)
  272. {
  273. scsiDev.data[descriptor + i] =
  274. fromBCD(scsiDev.data[descriptor + 4 + i]);
  275. }
  276. descriptor += 11;
  277. }
  278. }
  279. if (len > allocationLength)
  280. {
  281. len = allocationLength;
  282. }
  283. scsiDev.dataLen = len;
  284. scsiDev.phase = DATA_IN;
  285. }
  286. }
  287. static uint8_t SimpleHeader[] =
  288. {
  289. 0x01, // 2048byte user data, L-EC in 288 byte aux field.
  290. 0x00, // reserved
  291. 0x00, // reserved
  292. 0x00, // reserved
  293. 0x00,0x00,0x00,0x00 // Track start sector (LBA or MSF)
  294. };
  295. void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
  296. {
  297. uint32_t len = sizeof(SimpleHeader);
  298. memcpy(scsiDev.data, SimpleHeader, len);
  299. if (len > allocationLength)
  300. {
  301. len = allocationLength;
  302. }
  303. scsiDev.dataLen = len;
  304. scsiDev.phase = DATA_IN;
  305. }
  306. /**************************************/
  307. /* Ejection and image switching logic */
  308. /**************************************/
  309. // Reinsert any ejected CDROMs on reboot
  310. void cdromReinsertFirstImage(image_config_t &img)
  311. {
  312. if (img.image_index > 0)
  313. {
  314. // Multiple images for this drive, force restart from first one
  315. debuglog("---- Restarting from first CD-ROM image");
  316. img.image_index = 9;
  317. cdromSwitchNextImage(img);
  318. }
  319. else if (img.ejected)
  320. {
  321. // Reinsert the single image
  322. debuglog("---- Closing CD-ROM tray");
  323. img.ejected = false;
  324. img.cdrom_events = 2; // New media
  325. }
  326. }
  327. // Check if we have multiple CD-ROM images to cycle when drive is ejected.
  328. bool cdromSwitchNextImage(image_config_t &img)
  329. {
  330. // Check if we have a next image to load, so that drive is closed next time the host asks.
  331. img.image_index++;
  332. char filename[MAX_FILE_PATH];
  333. int target_idx = img.scsiId & 7;
  334. if (!scsiDiskGetImageNameFromConfig(img, filename, sizeof(filename)))
  335. {
  336. img.image_index = 0;
  337. scsiDiskGetImageNameFromConfig(img, filename, sizeof(filename));
  338. }
  339. if (filename[0] != '\0')
  340. {
  341. log("Switching to next CD-ROM image for ", target_idx, ": ", filename);
  342. img.file.close();
  343. bool status = scsiDiskOpenHDDImage(target_idx, filename, target_idx, 0, 2048);
  344. if (status)
  345. {
  346. img.ejected = false;
  347. img.cdrom_events = 2; // New media
  348. return true;
  349. }
  350. }
  351. return false;
  352. }
  353. static void doGetEventStatusNotification(bool immed)
  354. {
  355. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  356. if (!immed)
  357. {
  358. // Asynchronous notification not supported
  359. scsiDev.status = CHECK_CONDITION;
  360. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  361. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  362. scsiDev.phase = STATUS;
  363. }
  364. else if (img.cdrom_events)
  365. {
  366. scsiDev.data[0] = 0;
  367. scsiDev.data[1] = 6; // EventDataLength
  368. scsiDev.data[2] = 0x04; // Media status events
  369. scsiDev.data[3] = 0x04; // Supported events
  370. scsiDev.data[4] = img.cdrom_events;
  371. scsiDev.data[5] = 0x01; // Power status
  372. scsiDev.data[6] = 0; // Start slot
  373. scsiDev.data[7] = 0; // End slot
  374. scsiDev.dataLen = 8;
  375. scsiDev.phase = DATA_IN;
  376. img.cdrom_events = 0;
  377. if (img.ejected)
  378. {
  379. // We are now reporting to host that the drive is open.
  380. // Simulate a "close" for next time the host polls.
  381. cdromSwitchNextImage(img);
  382. }
  383. }
  384. else
  385. {
  386. scsiDev.data[0] = 0;
  387. scsiDev.data[1] = 2; // EventDataLength
  388. scsiDev.data[2] = 0x00; // Media status events
  389. scsiDev.data[3] = 0x04; // Supported events
  390. scsiDev.dataLen = 4;
  391. scsiDev.phase = DATA_IN;
  392. }
  393. }
  394. /**************************************/
  395. /* CD-ROM command dispatching */
  396. /**************************************/
  397. // Handle direct-access scsi device commands
  398. extern "C" int scsiCDRomCommand()
  399. {
  400. image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
  401. int commandHandled = 1;
  402. uint8_t command = scsiDev.cdb[0];
  403. if (command == 0x1B && (scsiDev.cdb[4] & 2))
  404. {
  405. // CD-ROM load & eject
  406. int start = scsiDev.cdb[4] & 1;
  407. if (start)
  408. {
  409. debuglog("------ CDROM close tray");
  410. img.ejected = false;
  411. img.cdrom_events = 2; // New media
  412. }
  413. else
  414. {
  415. debuglog("------ CDROM open tray");
  416. img.ejected = true;
  417. img.cdrom_events = 3; // Media removal
  418. }
  419. }
  420. else if (command == 0x43)
  421. {
  422. // CD-ROM Read TOC
  423. int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
  424. uint8_t track = scsiDev.cdb[6];
  425. uint16_t allocationLength =
  426. (((uint32_t) scsiDev.cdb[7]) << 8) +
  427. scsiDev.cdb[8];
  428. // Reject MMC commands for now, otherwise the TOC data format
  429. // won't be understood.
  430. // The "format" field is reserved for SCSI-2
  431. uint8_t format = scsiDev.cdb[2] & 0x0F;
  432. switch (format)
  433. {
  434. case 0: doReadTOC(MSF, track, allocationLength); break; // SCSI-2
  435. case 1: doReadSessionInfo(MSF, allocationLength); break; // MMC2
  436. case 2: doReadFullTOC(0, track, allocationLength); break; // MMC2
  437. case 3: doReadFullTOC(1, track, allocationLength); break; // MMC2
  438. default:
  439. {
  440. scsiDev.status = CHECK_CONDITION;
  441. scsiDev.target->sense.code = ILLEGAL_REQUEST;
  442. scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
  443. scsiDev.phase = STATUS;
  444. }
  445. }
  446. }
  447. else if (command == 0x44)
  448. {
  449. // CD-ROM Read Header
  450. int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
  451. uint32_t lba = 0; // IGNORED for now
  452. uint16_t allocationLength =
  453. (((uint32_t) scsiDev.cdb[7]) << 8) +
  454. scsiDev.cdb[8];
  455. doReadHeader(MSF, lba, allocationLength);
  456. }
  457. else if (command == 0x4A)
  458. {
  459. // Get event status notifications (media change notifications)
  460. bool immed = scsiDev.cdb[1] & 1;
  461. doGetEventStatusNotification(immed);
  462. }
  463. else
  464. {
  465. commandHandled = 0;
  466. }
  467. return commandHandled;
  468. }