cdrom: always error out on shell open
[pcsx_rearmed.git] / libpcsxcore / cdrom.c
... / ...
CommitLineData
1/***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18 ***************************************************************************/
19
20/*
21* Handles all CD-ROM registers and functions.
22*/
23
24#include <assert.h>
25#include "cdrom.h"
26#include "misc.h"
27#include "ppf.h"
28#include "psxdma.h"
29#include "psxevents.h"
30#include "arm_features.h"
31
32/* logging */
33#if 0
34#define CDR_LOG SysPrintf
35#else
36#define CDR_LOG(...)
37#endif
38#if 0
39#define CDR_LOG_I SysPrintf
40#else
41#define CDR_LOG_I(fmt, ...) \
42 log_unhandled("%u cdrom: " fmt, psxRegs.cycle, ##__VA_ARGS__)
43#endif
44#if 0
45#define CDR_LOG_IO SysPrintf
46#else
47#define CDR_LOG_IO(...)
48#endif
49//#define CDR_LOG_CMD_IRQ
50
51static struct {
52 // unused members maintain savesate compatibility
53 unsigned char unused0;
54 unsigned char unused1;
55 unsigned char IrqMask;
56 unsigned char unused2;
57 unsigned char Ctrl;
58 unsigned char IrqStat;
59
60 unsigned char StatP;
61
62 unsigned char Transfer[DATA_SIZE];
63 struct {
64 unsigned char Track;
65 unsigned char Index;
66 unsigned char Relative[3];
67 unsigned char Absolute[3];
68 } subq;
69 unsigned char TrackChanged;
70 unsigned char ReportDelay;
71 unsigned char unused3;
72 unsigned short sectorsRead;
73 unsigned int freeze_ver;
74
75 unsigned char Prev[4];
76 unsigned char Param[8];
77 unsigned char Result[16];
78
79 unsigned char ParamC;
80 unsigned char ParamP;
81 unsigned char ResultC;
82 unsigned char ResultP;
83 unsigned char ResultReady;
84 unsigned char Cmd;
85 unsigned char SubqForwardSectors;
86 unsigned char SetlocPending;
87 u32 Reading;
88
89 unsigned char ResultTN[6];
90 unsigned char ResultTD[4];
91 unsigned char SetSectorPlay[4];
92 unsigned char SetSectorEnd[4];
93 unsigned char SetSector[4];
94 unsigned char Track;
95 boolean Play, Muted;
96 int CurTrack;
97 unsigned char Mode;
98 unsigned char FileChannelSelected;
99 unsigned char CurFile, CurChannel;
100 int FilterFile, FilterChannel;
101 unsigned char LocL[8];
102 int unused4;
103
104 xa_decode_t Xa;
105
106 u16 FifoOffset;
107 u16 FifoSize;
108
109 u16 CmdInProgress;
110 u8 Irq1Pending;
111 u8 AdpcmActive;
112 u32 LastReadSeekCycles;
113
114 u8 unused7;
115
116 u8 DriveState; // enum drive_state
117 u8 FastForward;
118 u8 FastBackward;
119 u8 errorRetryhack;
120
121 u8 AttenuatorLeftToLeft, AttenuatorLeftToRight;
122 u8 AttenuatorRightToRight, AttenuatorRightToLeft;
123 u8 AttenuatorLeftToLeftT, AttenuatorLeftToRightT;
124 u8 AttenuatorRightToRightT, AttenuatorRightToLeftT;
125} cdr;
126static s16 read_buf[CD_FRAMESIZE_RAW/2];
127
128/* CD-ROM magic numbers */
129#define CdlSync 0 /* nocash documentation : "Uh, actually, returns error code 40h = Invalid Command...?" */
130#define CdlNop 1
131#define CdlSetloc 2
132#define CdlPlay 3
133#define CdlForward 4
134#define CdlBackward 5
135#define CdlReadN 6
136#define CdlStandby 7
137#define CdlStop 8
138#define CdlPause 9
139#define CdlReset 10
140#define CdlMute 11
141#define CdlDemute 12
142#define CdlSetfilter 13
143#define CdlSetmode 14
144#define CdlGetparam 15
145#define CdlGetlocL 16
146#define CdlGetlocP 17
147#define CdlReadT 18
148#define CdlGetTN 19
149#define CdlGetTD 20
150#define CdlSeekL 21
151#define CdlSeekP 22
152#define CdlSetclock 23
153#define CdlGetclock 24
154#define CdlTest 25
155#define CdlID 26
156#define CdlReadS 27
157#define CdlInit 28
158#define CdlGetQ 29
159#define CdlReadToc 30
160
161#ifdef CDR_LOG_CMD_IRQ
162static const char * const CmdName[0x100] = {
163 "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay",
164 "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby",
165 "CdlStop", "CdlPause", "CdlReset", "CdlMute",
166 "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetparam",
167 "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN",
168 "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock",
169 "CdlGetclock", "CdlTest", "CdlID", "CdlReadS",
170 "CdlInit", NULL, "CDlReadToc", NULL
171};
172#endif
173
174unsigned char Test04[] = { 0 };
175unsigned char Test05[] = { 0 };
176unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 };
177unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F };
178unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 };
179
180// cdr.IrqStat:
181#define NoIntr 0
182#define DataReady 1
183#define Complete 2
184#define Acknowledge 3
185#define DataEnd 4
186#define DiskError 5
187
188/* Modes flags */
189#define MODE_SPEED (1<<7) // 0x80
190#define MODE_STRSND (1<<6) // 0x40 ADPCM on/off
191#define MODE_SIZE_2340 (1<<5) // 0x20
192#define MODE_SIZE_2328 (1<<4) // 0x10
193#define MODE_SIZE_2048 (0<<4) // 0x00
194#define MODE_SF (1<<3) // 0x08 channel on/off
195#define MODE_REPORT (1<<2) // 0x04
196#define MODE_AUTOPAUSE (1<<1) // 0x02
197#define MODE_CDDA (1<<0) // 0x01
198
199/* Status flags */
200#define STATUS_PLAY (1<<7) // 0x80
201#define STATUS_SEEK (1<<6) // 0x40
202#define STATUS_READ (1<<5) // 0x20
203#define STATUS_SHELLOPEN (1<<4) // 0x10
204#define STATUS_UNKNOWN3 (1<<3) // 0x08
205#define STATUS_SEEKERROR (1<<2) // 0x04
206#define STATUS_ROTATING (1<<1) // 0x02
207#define STATUS_ERROR (1<<0) // 0x01
208
209/* Errors */
210#define ERROR_NOTREADY (1<<7) // 0x80
211#define ERROR_INVALIDCMD (1<<6) // 0x40
212#define ERROR_BAD_ARGNUM (1<<5) // 0x20
213#define ERROR_BAD_ARGVAL (1<<4) // 0x10
214#define ERROR_SHELLOPEN (1<<3) // 0x08
215
216// 1x = 75 sectors per second
217// PSXCLK = 1 sec in the ps
218// so (PSXCLK / 75) = cdr read time (linuzappz)
219#define cdReadTime (PSXCLK / 75)
220
221#define LOCL_INVALID 0xff
222#define SUBQ_FORWARD_SECTORS 2u
223
224enum drive_state {
225 DRIVESTATE_STANDBY = 0, // different from paused
226 DRIVESTATE_LID_OPEN,
227 DRIVESTATE_RESCAN_CD,
228 DRIVESTATE_PREPARE_CD,
229 DRIVESTATE_STOPPED,
230 DRIVESTATE_PAUSED,
231 DRIVESTATE_PLAY_READ,
232 DRIVESTATE_SEEK,
233};
234
235static struct CdrStat stat;
236
237static unsigned int msf2sec(const u8 *msf) {
238 return ((msf[0] * 60 + msf[1]) * 75) + msf[2];
239}
240
241// for that weird psemu API..
242static unsigned int fsm2sec(const u8 *msf) {
243 return ((msf[2] * 60 + msf[1]) * 75) + msf[0];
244}
245
246static void sec2msf(unsigned int s, u8 *msf) {
247 msf[0] = s / 75 / 60;
248 s = s - msf[0] * 75 * 60;
249 msf[1] = s / 75;
250 s = s - msf[1] * 75;
251 msf[2] = s;
252}
253
254// cdrPlayReadInterrupt
255#define CDRPLAYREAD_INT(eCycle, isFirst) { \
256 u32 e_ = eCycle; \
257 psxRegs.interrupt |= (1 << PSXINT_CDREAD); \
258 if (isFirst) \
259 psxRegs.intCycle[PSXINT_CDREAD].sCycle = psxRegs.cycle; \
260 else \
261 psxRegs.intCycle[PSXINT_CDREAD].sCycle += psxRegs.intCycle[PSXINT_CDREAD].cycle; \
262 psxRegs.intCycle[PSXINT_CDREAD].cycle = e_; \
263 set_event_raw_abs(PSXINT_CDREAD, psxRegs.intCycle[PSXINT_CDREAD].sCycle + e_); \
264}
265
266#define StopReading() { \
267 cdr.Reading = 0; \
268 psxRegs.interrupt &= ~(1 << PSXINT_CDREAD); \
269}
270
271#define StopCdda() { \
272 if (cdr.Play && !Config.Cdda) CDR_stop(); \
273 cdr.Play = FALSE; \
274 cdr.FastForward = 0; \
275 cdr.FastBackward = 0; \
276}
277
278#define SetPlaySeekRead(x, f) { \
279 x &= ~(STATUS_PLAY | STATUS_SEEK | STATUS_READ); \
280 x |= f; \
281}
282
283#define SetResultSize_(size) { \
284 cdr.ResultP = 0; \
285 cdr.ResultC = size; \
286 cdr.ResultReady = 1; \
287}
288
289#define SetResultSize(size) { \
290 if (cdr.ResultP < cdr.ResultC) \
291 CDR_LOG_I("overwriting result, len=%u\n", cdr.ResultC); \
292 SetResultSize_(size); \
293}
294
295static void setIrq(u8 irq, int log_cmd)
296{
297 u8 old = cdr.IrqStat & cdr.IrqMask ? 1 : 0;
298 u8 new_ = irq & cdr.IrqMask ? 1 : 0;
299
300 cdr.IrqStat = irq;
301 if ((old ^ new_) & new_)
302 psxHu32ref(0x1070) |= SWAP32((u32)0x4);
303
304#ifdef CDR_LOG_CMD_IRQ
305 if (cdr.IrqStat)
306 {
307 int i;
308 CDR_LOG_I("CDR IRQ=%d cmd %02x irqstat %02x: ",
309 !!(cdr.IrqStat & cdr.IrqMask), log_cmd, cdr.IrqStat);
310 for (i = 0; i < cdr.ResultC; i++)
311 SysPrintf("%02x ", cdr.Result[i]);
312 SysPrintf("\n");
313 }
314#endif
315}
316
317// timing used in this function was taken from tests on real hardware
318// (yes it's slow, but you probably don't want to modify it)
319void cdrLidSeekInterrupt(void)
320{
321 CDR_LOG_I("%s cdr.DriveState=%d\n", __func__, cdr.DriveState);
322
323 switch (cdr.DriveState) {
324 default:
325 case DRIVESTATE_STANDBY:
326 StopCdda();
327 //StopReading();
328 SetPlaySeekRead(cdr.StatP, 0);
329
330 if (CDR_getStatus(&stat) == -1)
331 return;
332
333 if (stat.Status & STATUS_SHELLOPEN)
334 {
335 memset(cdr.Prev, 0xff, sizeof(cdr.Prev));
336 cdr.DriveState = DRIVESTATE_LID_OPEN;
337 set_event(PSXINT_CDRLID, 0x800);
338 }
339 break;
340
341 case DRIVESTATE_LID_OPEN:
342 if (CDR_getStatus(&stat) == -1)
343 stat.Status &= ~STATUS_SHELLOPEN;
344
345 // 02, 12, 10
346 if (!(cdr.StatP & STATUS_SHELLOPEN)) {
347 StopReading();
348 SetPlaySeekRead(cdr.StatP, 0);
349 cdr.StatP |= STATUS_SHELLOPEN;
350
351 // IIRC this sometimes doesn't happen on real hw
352 // (when lots of commands are sent?)
353 SetResultSize(2);
354 cdr.Result[0] = cdr.StatP | STATUS_SEEKERROR;
355 cdr.Result[1] = ERROR_SHELLOPEN;
356 if (cdr.CmdInProgress) {
357 psxRegs.interrupt &= ~(1 << PSXINT_CDR);
358 cdr.CmdInProgress = 0;
359 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
360 cdr.Result[1] = ERROR_NOTREADY;
361 }
362 setIrq(DiskError, 0x1006);
363
364 set_event(PSXINT_CDRLID, cdReadTime * 30);
365 break;
366 }
367 else if (cdr.StatP & STATUS_ROTATING) {
368 cdr.StatP &= ~STATUS_ROTATING;
369 }
370 else if (!(stat.Status & STATUS_SHELLOPEN)) {
371 // closed now
372 CheckCdrom();
373
374 // cdr.StatP STATUS_SHELLOPEN is "sticky"
375 // and is only cleared by CdlNop
376
377 cdr.DriveState = DRIVESTATE_RESCAN_CD;
378 set_event(PSXINT_CDRLID, cdReadTime * 105);
379 break;
380 }
381
382 // recheck for close
383 set_event(PSXINT_CDRLID, cdReadTime * 3);
384 break;
385
386 case DRIVESTATE_RESCAN_CD:
387 cdr.StatP |= STATUS_ROTATING;
388 cdr.DriveState = DRIVESTATE_PREPARE_CD;
389
390 // this is very long on real hardware, over 6 seconds
391 // make it a bit faster here...
392 set_event(PSXINT_CDRLID, cdReadTime * 150);
393 break;
394
395 case DRIVESTATE_PREPARE_CD:
396 if (cdr.StatP & STATUS_SEEK) {
397 SetPlaySeekRead(cdr.StatP, 0);
398 cdr.DriveState = DRIVESTATE_STANDBY;
399 }
400 else {
401 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
402 set_event(PSXINT_CDRLID, cdReadTime * 26);
403 }
404 break;
405 }
406}
407
408static void Find_CurTrack(const u8 *time)
409{
410 int current, sect;
411
412 current = msf2sec(time);
413
414 for (cdr.CurTrack = 1; cdr.CurTrack < cdr.ResultTN[1]; cdr.CurTrack++) {
415 CDR_getTD(cdr.CurTrack + 1, cdr.ResultTD);
416 sect = fsm2sec(cdr.ResultTD);
417 if (sect - current >= 150)
418 break;
419 }
420}
421
422static void generate_subq(const u8 *time)
423{
424 unsigned char start[3], next[3];
425 unsigned int this_s, start_s, next_s, pregap;
426 int relative_s;
427
428 CDR_getTD(cdr.CurTrack, start);
429 if (cdr.CurTrack + 1 <= cdr.ResultTN[1]) {
430 pregap = 150;
431 CDR_getTD(cdr.CurTrack + 1, next);
432 }
433 else {
434 // last track - cd size
435 pregap = 0;
436 next[0] = cdr.SetSectorEnd[2];
437 next[1] = cdr.SetSectorEnd[1];
438 next[2] = cdr.SetSectorEnd[0];
439 }
440
441 this_s = msf2sec(time);
442 start_s = fsm2sec(start);
443 next_s = fsm2sec(next);
444
445 cdr.TrackChanged = FALSE;
446
447 if (next_s - this_s < pregap) {
448 cdr.TrackChanged = TRUE;
449 cdr.CurTrack++;
450 start_s = next_s;
451 }
452
453 cdr.subq.Index = 1;
454
455 relative_s = this_s - start_s;
456 if (relative_s < 0) {
457 cdr.subq.Index = 0;
458 relative_s = -relative_s;
459 }
460 sec2msf(relative_s, cdr.subq.Relative);
461
462 cdr.subq.Track = itob(cdr.CurTrack);
463 cdr.subq.Relative[0] = itob(cdr.subq.Relative[0]);
464 cdr.subq.Relative[1] = itob(cdr.subq.Relative[1]);
465 cdr.subq.Relative[2] = itob(cdr.subq.Relative[2]);
466 cdr.subq.Absolute[0] = itob(time[0]);
467 cdr.subq.Absolute[1] = itob(time[1]);
468 cdr.subq.Absolute[2] = itob(time[2]);
469}
470
471static int ReadTrack(const u8 *time)
472{
473 unsigned char tmp[3];
474 int read_ok;
475
476 tmp[0] = itob(time[0]);
477 tmp[1] = itob(time[1]);
478 tmp[2] = itob(time[2]);
479
480 CDR_LOG("ReadTrack *** %02x:%02x:%02x\n", tmp[0], tmp[1], tmp[2]);
481
482 if (memcmp(cdr.Prev, tmp, 3) == 0)
483 return 1;
484
485 read_ok = CDR_readTrack(tmp);
486 if (read_ok)
487 memcpy(cdr.Prev, tmp, 3);
488 return read_ok;
489}
490
491static void UpdateSubq(const u8 *time)
492{
493 const struct SubQ *subq;
494 int s = MSF2SECT(time[0], time[1], time[2]);
495 u16 crc;
496
497 if (CheckSBI(s))
498 return;
499
500 subq = (struct SubQ *)CDR_getBufferSub(s);
501 if (subq != NULL && cdr.CurTrack == 1) {
502 crc = calcCrc((u8 *)subq + 12, 10);
503 if (crc == (((u16)subq->CRC[0] << 8) | subq->CRC[1])) {
504 cdr.subq.Track = subq->TrackNumber;
505 cdr.subq.Index = subq->IndexNumber;
506 memcpy(cdr.subq.Relative, subq->TrackRelativeAddress, 3);
507 memcpy(cdr.subq.Absolute, subq->AbsoluteAddress, 3);
508 }
509 else {
510 CDR_LOG_I("subq bad crc @%02d:%02d:%02d\n",
511 time[0], time[1], time[2]);
512 }
513 }
514 else {
515 generate_subq(time);
516 }
517
518 CDR_LOG(" -> %02x,%02x %02x:%02x:%02x %02x:%02x:%02x\n",
519 cdr.subq.Track, cdr.subq.Index,
520 cdr.subq.Relative[0], cdr.subq.Relative[1], cdr.subq.Relative[2],
521 cdr.subq.Absolute[0], cdr.subq.Absolute[1], cdr.subq.Absolute[2]);
522}
523
524static void cdrPlayInterrupt_Autopause()
525{
526 u32 abs_lev_max = 0;
527 boolean abs_lev_chselect;
528 u32 i;
529
530 if ((cdr.Mode & MODE_AUTOPAUSE) && cdr.TrackChanged) {
531 CDR_LOG_I("autopause\n");
532
533 SetResultSize(1);
534 cdr.Result[0] = cdr.StatP;
535 setIrq(DataEnd, 0x1000); // 0x1000 just for logging purposes
536
537 StopCdda();
538 SetPlaySeekRead(cdr.StatP, 0);
539 cdr.DriveState = DRIVESTATE_PAUSED;
540 }
541 else if ((cdr.Mode & MODE_REPORT) && !cdr.ReportDelay &&
542 ((cdr.subq.Absolute[2] & 0x0f) == 0 || cdr.FastForward || cdr.FastBackward))
543 {
544 SetResultSize(8);
545 cdr.Result[0] = cdr.StatP;
546 cdr.Result[1] = cdr.subq.Track;
547 cdr.Result[2] = cdr.subq.Index;
548
549 abs_lev_chselect = cdr.subq.Absolute[1] & 0x01;
550
551 /* 8 is a hack. For accuracy, it should be 588. */
552 for (i = 0; i < 8; i++)
553 {
554 abs_lev_max = MAX_VALUE(abs_lev_max, abs(read_buf[i * 2 + abs_lev_chselect]));
555 }
556 abs_lev_max = MIN_VALUE(abs_lev_max, 32767);
557 abs_lev_max |= abs_lev_chselect << 15;
558
559 if (cdr.subq.Absolute[2] & 0x10) {
560 cdr.Result[3] = cdr.subq.Relative[0];
561 cdr.Result[4] = cdr.subq.Relative[1] | 0x80;
562 cdr.Result[5] = cdr.subq.Relative[2];
563 }
564 else {
565 cdr.Result[3] = cdr.subq.Absolute[0];
566 cdr.Result[4] = cdr.subq.Absolute[1];
567 cdr.Result[5] = cdr.subq.Absolute[2];
568 }
569 cdr.Result[6] = abs_lev_max >> 0;
570 cdr.Result[7] = abs_lev_max >> 8;
571
572 setIrq(DataReady, 0x1001);
573 }
574
575 if (cdr.ReportDelay)
576 cdr.ReportDelay--;
577}
578
579static int cdrSeekTime(unsigned char *target)
580{
581 int diff = msf2sec(cdr.SetSectorPlay) - msf2sec(target);
582 int seekTime = abs(diff) * (cdReadTime / 2000);
583 int cyclesSinceRS = psxRegs.cycle - cdr.LastReadSeekCycles;
584 seekTime = MAX_VALUE(seekTime, 20000);
585
586 // need this stupidly long penalty or else Spyro2 intro desyncs
587 // note: if misapplied this breaks MGS cutscenes among other things
588 if (cdr.DriveState == DRIVESTATE_PAUSED && cyclesSinceRS > cdReadTime * 50)
589 seekTime += cdReadTime * 25;
590 // Transformers Beast Wars Transmetals does Setloc(x),SeekL,Setloc(x),ReadN
591 // and then wants some slack time
592 else if (cdr.DriveState == DRIVESTATE_PAUSED || cyclesSinceRS < cdReadTime *3/2)
593 seekTime += cdReadTime;
594
595 seekTime = MIN_VALUE(seekTime, PSXCLK * 2 / 3);
596 CDR_LOG("seek: %.2f %.2f (%.2f) st %d\n", (float)seekTime / PSXCLK,
597 (float)seekTime / cdReadTime, (float)cyclesSinceRS / cdReadTime,
598 cdr.DriveState);
599 return seekTime;
600}
601
602static u32 cdrAlignTimingHack(u32 cycles)
603{
604 /*
605 * timing hack for T'ai Fu - Wrath of the Tiger:
606 * The game has a bug where it issues some cdc commands from a low priority
607 * vint handler, however there is a higher priority default bios handler
608 * that acks the vint irq and returns, so game's handler is not reached
609 * (see bios irq handler chains at e004 and the game's irq handling func
610 * at 80036810). For the game to work, vint has to arrive after the bios
611 * vint handler rejects some other irq (of which only cd and rcnt2 are
612 * active), but before the game's handler loop reads I_STAT. The time
613 * window for this is quite small (~1k cycles of so). Apparently this
614 * somehow happens naturally on the real hardware.
615 *
616 * Note: always enforcing this breaks other games like Crash PAL version
617 * (inputs get dropped because bios handler doesn't see interrupts).
618 */
619 u32 vint_rel;
620 if (psxRegs.cycle - rcnts[3].cycleStart > 250000)
621 return cycles;
622 vint_rel = rcnts[3].cycleStart + 63000 - psxRegs.cycle;
623 vint_rel += PSXCLK / 60;
624 while ((s32)(vint_rel - cycles) < 0)
625 vint_rel += PSXCLK / 60;
626 return vint_rel;
627}
628
629static void cdrUpdateTransferBuf(const u8 *buf);
630static void cdrReadInterrupt(void);
631static void cdrPrepCdda(s16 *buf, int samples);
632
633static void msfiAdd(u8 *msfi, u32 count)
634{
635 assert(count < 75);
636 msfi[2] += count;
637 if (msfi[2] >= 75) {
638 msfi[2] -= 75;
639 msfi[1]++;
640 if (msfi[1] == 60) {
641 msfi[1] = 0;
642 msfi[0]++;
643 }
644 }
645}
646
647static void msfiSub(u8 *msfi, u32 count)
648{
649 assert(count < 75);
650 msfi[2] -= count;
651 if ((s8)msfi[2] < 0) {
652 msfi[2] += 75;
653 msfi[1]--;
654 if ((s8)msfi[1] < 0) {
655 msfi[1] = 60;
656 msfi[0]--;
657 }
658 }
659}
660
661void cdrPlayReadInterrupt(void)
662{
663 cdr.LastReadSeekCycles = psxRegs.cycle;
664
665 if (cdr.Reading) {
666 cdrReadInterrupt();
667 return;
668 }
669
670 if (!cdr.Play) return;
671
672 CDR_LOG("CDDA - %02d:%02d:%02d m %02x\n",
673 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], cdr.Mode);
674
675 cdr.DriveState = DRIVESTATE_PLAY_READ;
676 SetPlaySeekRead(cdr.StatP, STATUS_PLAY);
677 if (memcmp(cdr.SetSectorPlay, cdr.SetSectorEnd, 3) == 0) {
678 CDR_LOG_I("end stop\n");
679 StopCdda();
680 SetPlaySeekRead(cdr.StatP, 0);
681 cdr.TrackChanged = TRUE;
682 cdr.DriveState = DRIVESTATE_PAUSED;
683 }
684 else {
685 CDR_readCDDA(cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2], (u8 *)read_buf);
686 }
687
688 if (!cdr.IrqStat && (cdr.Mode & (MODE_AUTOPAUSE|MODE_REPORT)))
689 cdrPlayInterrupt_Autopause();
690
691 if (cdr.Play && !Config.Cdda) {
692 cdrPrepCdda(read_buf, CD_FRAMESIZE_RAW / 4);
693 SPU_playCDDAchannel(read_buf, CD_FRAMESIZE_RAW, psxRegs.cycle, 0);
694 }
695
696 msfiAdd(cdr.SetSectorPlay, 1);
697
698 // update for CdlGetlocP/autopause
699 generate_subq(cdr.SetSectorPlay);
700
701 CDRPLAYREAD_INT(cdReadTime, 0);
702}
703
704#define CMD_PART2 0x100
705#define CMD_WHILE_NOT_READY 0x200
706
707void cdrInterrupt(void) {
708 int start_rotating = 0;
709 int error = 0;
710 u32 cycles, seekTime = 0;
711 u32 second_resp_time = 0;
712 const void *buf;
713 u8 ParamC;
714 u8 set_loc[3];
715 int read_ok;
716 u16 not_ready = 0;
717 u8 IrqStat = Acknowledge;
718 u16 Cmd;
719 int i;
720
721 if (cdr.IrqStat) {
722 CDR_LOG_I("cmd %02x with irqstat %x\n",
723 cdr.CmdInProgress, cdr.IrqStat);
724 return;
725 }
726 if (cdr.Irq1Pending) {
727 // hand out the "newest" sector, according to nocash
728 cdrUpdateTransferBuf(CDR_getBuffer());
729 CDR_LOG_I("%x:%02x:%02x loaded on ack, cmd=%02x res=%02x\n",
730 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2],
731 cdr.CmdInProgress, cdr.Irq1Pending);
732 SetResultSize(1);
733 cdr.Result[0] = cdr.Irq1Pending;
734 cdr.Irq1Pending = 0;
735 setIrq((cdr.Irq1Pending & STATUS_ERROR) ? DiskError : DataReady, 0x1003);
736 return;
737 }
738
739 // default response
740 SetResultSize(1);
741 cdr.Result[0] = cdr.StatP;
742
743 Cmd = cdr.CmdInProgress;
744 cdr.CmdInProgress = 0;
745 ParamC = cdr.ParamC;
746
747 if (Cmd < 0x100) {
748 cdr.Ctrl &= ~0x80;
749 cdr.ParamC = 0;
750 cdr.Cmd = 0;
751 }
752
753 switch (cdr.DriveState) {
754 case DRIVESTATE_PREPARE_CD:
755 if (Cmd > 2) {
756 // Syphon filter 2 expects commands to work shortly after it sees
757 // STATUS_ROTATING, so give up trying to emulate the startup seq
758 cdr.DriveState = DRIVESTATE_STANDBY;
759 cdr.StatP &= ~STATUS_SEEK;
760 psxRegs.interrupt &= ~(1 << PSXINT_CDRLID);
761 break;
762 }
763 // fallthrough
764 case DRIVESTATE_LID_OPEN:
765 case DRIVESTATE_RESCAN_CD:
766 // no disk or busy with the initial scan, allowed cmds are limited
767 not_ready = CMD_WHILE_NOT_READY;
768 break;
769 }
770
771 switch (Cmd | not_ready) {
772 case CdlNop:
773 case CdlNop + CMD_WHILE_NOT_READY:
774 if (cdr.DriveState != DRIVESTATE_LID_OPEN)
775 cdr.StatP &= ~STATUS_SHELLOPEN;
776 break;
777
778 case CdlSetloc:
779 // case CdlSetloc + CMD_WHILE_NOT_READY: // or is it?
780 CDR_LOG("CDROM setloc command (%02X, %02X, %02X)\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
781
782 // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
783 if (((cdr.Param[0] & 0x0F) > 0x09) || (cdr.Param[0] > 0x99) || ((cdr.Param[1] & 0x0F) > 0x09) || (cdr.Param[1] >= 0x60) || ((cdr.Param[2] & 0x0F) > 0x09) || (cdr.Param[2] >= 0x75))
784 {
785 CDR_LOG_I("Invalid/out of range seek to %02X:%02X:%02X\n", cdr.Param[0], cdr.Param[1], cdr.Param[2]);
786 if (++cdr.errorRetryhack > 100)
787 break;
788 error = ERROR_BAD_ARGNUM;
789 goto set_error;
790 }
791 else
792 {
793 for (i = 0; i < 3; i++)
794 set_loc[i] = btoi(cdr.Param[i]);
795 memcpy(cdr.SetSector, set_loc, 3);
796 cdr.SetSector[3] = 0;
797 cdr.SetlocPending = 1;
798 cdr.errorRetryhack = 0;
799 }
800 break;
801
802 do_CdlPlay:
803 case CdlPlay:
804 StopCdda();
805 StopReading();
806
807 cdr.FastBackward = 0;
808 cdr.FastForward = 0;
809
810 // BIOS CD Player
811 // - Pause player, hit Track 01/02/../xx (Setloc issued!!)
812
813 if (ParamC != 0 && cdr.Param[0] != 0) {
814 int track = btoi( cdr.Param[0] );
815
816 if (track <= cdr.ResultTN[1])
817 cdr.CurTrack = track;
818
819 CDR_LOG("PLAY track %d\n", cdr.CurTrack);
820
821 if (CDR_getTD((u8)cdr.CurTrack, cdr.ResultTD) != -1) {
822 for (i = 0; i < 3; i++)
823 set_loc[i] = cdr.ResultTD[2 - i];
824 seekTime = cdrSeekTime(set_loc);
825 memcpy(cdr.SetSectorPlay, set_loc, 3);
826 }
827 }
828 else if (cdr.SetlocPending) {
829 seekTime = cdrSeekTime(cdr.SetSector);
830 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
831 }
832 else {
833 CDR_LOG("PLAY Resume @ %d:%d:%d\n",
834 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2]);
835 }
836 cdr.SetlocPending = 0;
837
838 /*
839 Rayman: detect track changes
840 - fixes logo freeze
841
842 Twisted Metal 2: skip PREGAP + starting accurate SubQ
843 - plays tracks without retry play
844
845 Wild 9: skip PREGAP + starting accurate SubQ
846 - plays tracks without retry play
847 */
848 Find_CurTrack(cdr.SetSectorPlay);
849 generate_subq(cdr.SetSectorPlay);
850 cdr.LocL[0] = LOCL_INVALID;
851 cdr.SubqForwardSectors = 1;
852 cdr.TrackChanged = FALSE;
853 cdr.FileChannelSelected = 0;
854 cdr.AdpcmActive = 0;
855 cdr.ReportDelay = 60;
856 cdr.sectorsRead = 0;
857
858 if (!Config.Cdda)
859 CDR_play(cdr.SetSectorPlay);
860
861 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
862
863 // BIOS player - set flag again
864 cdr.Play = TRUE;
865 cdr.DriveState = DRIVESTATE_PLAY_READ;
866
867 CDRPLAYREAD_INT(cdReadTime + seekTime, 1);
868 start_rotating = 1;
869 break;
870
871 case CdlForward:
872 // TODO: error 80 if stopped
873 IrqStat = Complete;
874
875 // GameShark CD Player: Calls 2x + Play 2x
876 cdr.FastForward = 1;
877 cdr.FastBackward = 0;
878 break;
879
880 case CdlBackward:
881 IrqStat = Complete;
882
883 // GameShark CD Player: Calls 2x + Play 2x
884 cdr.FastBackward = 1;
885 cdr.FastForward = 0;
886 break;
887
888 case CdlStandby:
889 if (cdr.DriveState != DRIVESTATE_STOPPED) {
890 error = ERROR_BAD_ARGNUM;
891 goto set_error;
892 }
893 cdr.DriveState = DRIVESTATE_STANDBY;
894 second_resp_time = cdReadTime * 125 / 2;
895 start_rotating = 1;
896 break;
897
898 case CdlStandby + CMD_PART2:
899 IrqStat = Complete;
900 break;
901
902 case CdlStop:
903 if (cdr.Play) {
904 // grab time for current track
905 CDR_getTD((u8)(cdr.CurTrack), cdr.ResultTD);
906
907 cdr.SetSectorPlay[0] = cdr.ResultTD[2];
908 cdr.SetSectorPlay[1] = cdr.ResultTD[1];
909 cdr.SetSectorPlay[2] = cdr.ResultTD[0];
910 }
911
912 StopCdda();
913 StopReading();
914 SetPlaySeekRead(cdr.StatP, 0);
915 cdr.StatP &= ~STATUS_ROTATING;
916 cdr.LocL[0] = LOCL_INVALID;
917
918 second_resp_time = 0x800;
919 if (cdr.DriveState != DRIVESTATE_STOPPED)
920 second_resp_time = cdReadTime * 30 / 2;
921
922 cdr.DriveState = DRIVESTATE_STOPPED;
923 break;
924
925 case CdlStop + CMD_PART2:
926 IrqStat = Complete;
927 break;
928
929 case CdlPause:
930 if (cdr.AdpcmActive) {
931 cdr.AdpcmActive = 0;
932 cdr.Xa.nsamples = 0;
933 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, 1); // flush adpcm
934 }
935 StopCdda();
936 StopReading();
937
938 // how the drive maintains the position while paused is quite
939 // complicated, this is the minimum to make "Bedlam" happy
940 msfiSub(cdr.SetSectorPlay, MIN_VALUE(cdr.sectorsRead, 4));
941 cdr.sectorsRead = 0;
942
943 /*
944 Gundam Battle Assault 2: much slower (*)
945 - Fixes boot, gameplay
946
947 Hokuto no Ken 2: slower
948 - Fixes intro + subtitles
949
950 InuYasha - Feudal Fairy Tale: slower
951 - Fixes battles
952 */
953 /* Gameblabla - Tightening the timings (as taken from Duckstation).
954 * The timings from Duckstation are based upon hardware tests.
955 * Mednafen's timing don't work for Gundam Battle Assault 2 in PAL/50hz mode,
956 * seems to be timing sensitive as it can depend on the CPU's clock speed.
957 * */
958 if (!(cdr.StatP & (STATUS_PLAY | STATUS_READ)))
959 {
960 second_resp_time = 7000;
961 }
962 else
963 {
964 second_resp_time = (((cdr.Mode & MODE_SPEED) ? 1 : 2) * 1097107);
965 }
966 SetPlaySeekRead(cdr.StatP, 0);
967 cdr.DriveState = DRIVESTATE_PAUSED;
968 break;
969
970 case CdlPause + CMD_PART2:
971 IrqStat = Complete;
972 break;
973
974 case CdlReset:
975 case CdlReset + CMD_WHILE_NOT_READY:
976 StopCdda();
977 StopReading();
978 SetPlaySeekRead(cdr.StatP, 0);
979 cdr.LocL[0] = LOCL_INVALID;
980 cdr.Mode = MODE_SIZE_2340; /* This fixes This is Football 2, Pooh's Party lockups */
981 cdr.DriveState = DRIVESTATE_PAUSED;
982 cdr.Muted = FALSE;
983 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
984 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
985 second_resp_time = not_ready ? 70000 : 4100000;
986 start_rotating = 1;
987 break;
988
989 case CdlReset + CMD_PART2:
990 case CdlReset + CMD_PART2 + CMD_WHILE_NOT_READY:
991 IrqStat = Complete;
992 break;
993
994 case CdlMute:
995 cdr.Muted = TRUE;
996 SPU_setCDvol(0, 0, 0, 0, psxRegs.cycle);
997 break;
998
999 case CdlDemute:
1000 cdr.Muted = FALSE;
1001 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1002 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1003 break;
1004
1005 case CdlSetfilter:
1006 cdr.FilterFile = cdr.Param[0];
1007 cdr.FilterChannel = cdr.Param[1];
1008 cdr.FileChannelSelected = 0;
1009 break;
1010
1011 case CdlSetmode:
1012 case CdlSetmode + CMD_WHILE_NOT_READY:
1013 CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]);
1014 cdr.Mode = cdr.Param[0];
1015 break;
1016
1017 case CdlGetparam:
1018 case CdlGetparam + CMD_WHILE_NOT_READY:
1019 /* Gameblabla : According to mednafen, Result size should be 5 and done this way. */
1020 SetResultSize_(5);
1021 cdr.Result[1] = cdr.Mode;
1022 cdr.Result[2] = 0;
1023 cdr.Result[3] = cdr.FilterFile;
1024 cdr.Result[4] = cdr.FilterChannel;
1025 break;
1026
1027 case CdlGetlocL:
1028 if (cdr.LocL[0] == LOCL_INVALID) {
1029 error = 0x80;
1030 goto set_error;
1031 }
1032 SetResultSize_(8);
1033 memcpy(cdr.Result, cdr.LocL, 8);
1034 break;
1035
1036 case CdlGetlocP:
1037 SetResultSize_(8);
1038 memcpy(&cdr.Result, &cdr.subq, 8);
1039 break;
1040
1041 case CdlReadT: // SetSession?
1042 // really long
1043 second_resp_time = cdReadTime * 290 / 4;
1044 start_rotating = 1;
1045 break;
1046
1047 case CdlReadT + CMD_PART2:
1048 IrqStat = Complete;
1049 break;
1050
1051 case CdlGetTN:
1052 if (CDR_getTN(cdr.ResultTN) == -1) {
1053 assert(0);
1054 }
1055 SetResultSize_(3);
1056 cdr.Result[1] = itob(cdr.ResultTN[0]);
1057 cdr.Result[2] = itob(cdr.ResultTN[1]);
1058 break;
1059
1060 case CdlGetTD:
1061 cdr.Track = btoi(cdr.Param[0]);
1062 if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) {
1063 error = ERROR_BAD_ARGVAL;
1064 goto set_error;
1065 }
1066 SetResultSize_(3);
1067 cdr.Result[1] = itob(cdr.ResultTD[2]);
1068 cdr.Result[2] = itob(cdr.ResultTD[1]);
1069 // no sector number
1070 //cdr.Result[3] = itob(cdr.ResultTD[0]);
1071 break;
1072
1073 case CdlSeekL:
1074 case CdlSeekP:
1075 StopCdda();
1076 StopReading();
1077 SetPlaySeekRead(cdr.StatP, STATUS_SEEK | STATUS_ROTATING);
1078
1079 seekTime = cdrSeekTime(cdr.SetSector);
1080 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1081 cdr.DriveState = DRIVESTATE_SEEK;
1082 /*
1083 Crusaders of Might and Magic = 0.5x-4x
1084 - fix cutscene speech start
1085
1086 Eggs of Steel = 2x-?
1087 - fix new game
1088
1089 Medievil = ?-4x
1090 - fix cutscene speech
1091
1092 Rockman X5 = 0.5-4x
1093 - fix capcom logo
1094 */
1095 second_resp_time = cdReadTime + seekTime;
1096 start_rotating = 1;
1097 break;
1098
1099 case CdlSeekL + CMD_PART2:
1100 case CdlSeekP + CMD_PART2:
1101 SetPlaySeekRead(cdr.StatP, 0);
1102 cdr.Result[0] = cdr.StatP;
1103 IrqStat = Complete;
1104
1105 Find_CurTrack(cdr.SetSectorPlay);
1106 read_ok = ReadTrack(cdr.SetSectorPlay);
1107 if (read_ok && (buf = CDR_getBuffer()))
1108 memcpy(cdr.LocL, buf, 8);
1109 UpdateSubq(cdr.SetSectorPlay);
1110 cdr.DriveState = DRIVESTATE_STANDBY;
1111 cdr.TrackChanged = FALSE;
1112 cdr.LastReadSeekCycles = psxRegs.cycle;
1113 break;
1114
1115 case CdlTest:
1116 case CdlTest + CMD_WHILE_NOT_READY:
1117 switch (cdr.Param[0]) {
1118 case 0x20: // System Controller ROM Version
1119 SetResultSize_(4);
1120 memcpy(cdr.Result, Test20, 4);
1121 break;
1122 case 0x22:
1123 SetResultSize_(8);
1124 memcpy(cdr.Result, Test22, 4);
1125 break;
1126 case 0x23: case 0x24:
1127 SetResultSize_(8);
1128 memcpy(cdr.Result, Test23, 4);
1129 break;
1130 }
1131 break;
1132
1133 case CdlID:
1134 second_resp_time = 20480;
1135 break;
1136
1137 case CdlID + CMD_PART2:
1138 SetResultSize_(8);
1139 cdr.Result[0] = cdr.StatP;
1140 cdr.Result[1] = 0;
1141 cdr.Result[2] = 0;
1142 cdr.Result[3] = 0;
1143
1144 // 0x10 - audio | 0x40 - disk missing | 0x80 - unlicensed
1145 if (CDR_getStatus(&stat) == -1 || stat.Type == 0 || stat.Type == 0xff) {
1146 cdr.Result[1] = 0xc0;
1147 }
1148 else {
1149 if (stat.Type == 2)
1150 cdr.Result[1] |= 0x10;
1151 if (CdromId[0] == '\0')
1152 cdr.Result[1] |= 0x80;
1153 }
1154 cdr.Result[0] |= (cdr.Result[1] >> 4) & 0x08;
1155 CDR_LOG_I("CdlID: %02x %02x %02x %02x\n", cdr.Result[0],
1156 cdr.Result[1], cdr.Result[2], cdr.Result[3]);
1157
1158 /* This adds the string "PCSX" in Playstation bios boot screen */
1159 memcpy((char *)&cdr.Result[4], "PCSX", 4);
1160 IrqStat = Complete;
1161 break;
1162
1163 case CdlInit:
1164 case CdlInit + CMD_WHILE_NOT_READY:
1165 StopCdda();
1166 StopReading();
1167 SetPlaySeekRead(cdr.StatP, 0);
1168 // yes, it really sets STATUS_SHELLOPEN
1169 cdr.StatP |= STATUS_SHELLOPEN;
1170 cdr.DriveState = DRIVESTATE_RESCAN_CD;
1171 set_event(PSXINT_CDRLID, 20480);
1172 start_rotating = 1;
1173 break;
1174
1175 case CdlGetQ:
1176 case CdlGetQ + CMD_WHILE_NOT_READY:
1177 break;
1178
1179 case CdlReadToc:
1180 case CdlReadToc + CMD_WHILE_NOT_READY:
1181 cdr.LocL[0] = LOCL_INVALID;
1182 second_resp_time = cdReadTime * 180 / 4;
1183 start_rotating = 1;
1184 break;
1185
1186 case CdlReadToc + CMD_PART2:
1187 case CdlReadToc + CMD_PART2 + CMD_WHILE_NOT_READY:
1188 IrqStat = Complete;
1189 break;
1190
1191 case CdlReadN:
1192 case CdlReadS:
1193 if (cdr.Reading && !cdr.SetlocPending)
1194 break;
1195
1196 Find_CurTrack(cdr.SetlocPending ? cdr.SetSector : cdr.SetSectorPlay);
1197
1198 if ((cdr.Mode & MODE_CDDA) && cdr.CurTrack > 1)
1199 // Read* acts as play for cdda tracks in cdda mode
1200 goto do_CdlPlay;
1201
1202 StopCdda();
1203 if (cdr.SetlocPending) {
1204 seekTime = cdrSeekTime(cdr.SetSector);
1205 memcpy(cdr.SetSectorPlay, cdr.SetSector, 4);
1206 cdr.SetlocPending = 0;
1207 }
1208 cdr.Reading = 1;
1209 cdr.FileChannelSelected = 0;
1210 cdr.AdpcmActive = 0;
1211
1212 // Fighting Force 2 - update subq time immediately
1213 // - fixes new game
1214 UpdateSubq(cdr.SetSectorPlay);
1215 cdr.LocL[0] = LOCL_INVALID;
1216 cdr.SubqForwardSectors = 1;
1217 cdr.sectorsRead = 0;
1218 cdr.DriveState = DRIVESTATE_SEEK;
1219
1220 cycles = (cdr.Mode & MODE_SPEED) ? cdReadTime : cdReadTime * 2;
1221 cycles += seekTime;
1222 if (Config.hacks.cdr_read_timing)
1223 cycles = cdrAlignTimingHack(cycles);
1224 CDRPLAYREAD_INT(cycles, 1);
1225
1226 SetPlaySeekRead(cdr.StatP, STATUS_SEEK);
1227 start_rotating = 1;
1228 break;
1229
1230 case CdlSync:
1231 default:
1232 error = ERROR_INVALIDCMD;
1233 // FALLTHROUGH
1234
1235 set_error:
1236 SetResultSize_(2);
1237 cdr.Result[0] = cdr.StatP | STATUS_ERROR;
1238 cdr.Result[1] = not_ready ? ERROR_NOTREADY : error;
1239 IrqStat = DiskError;
1240 CDR_LOG_I("cmd %02x error %02x\n", Cmd, cdr.Result[1]);
1241 break;
1242 }
1243
1244 if (cdr.DriveState == DRIVESTATE_STOPPED && start_rotating) {
1245 cdr.DriveState = DRIVESTATE_STANDBY;
1246 cdr.StatP |= STATUS_ROTATING;
1247 }
1248
1249 if (second_resp_time) {
1250 cdr.CmdInProgress = Cmd | 0x100;
1251 set_event(PSXINT_CDR, second_resp_time);
1252 }
1253 else if (cdr.Cmd && cdr.Cmd != (Cmd & 0xff)) {
1254 cdr.CmdInProgress = cdr.Cmd;
1255 CDR_LOG_I("cmd %02x came before %02x finished\n", cdr.Cmd, Cmd);
1256 }
1257
1258 setIrq(IrqStat, Cmd);
1259}
1260
1261#ifdef HAVE_ARMV7
1262 #define ssat32_to_16(v) \
1263 asm("ssat %0,#16,%1" : "=r" (v) : "r" (v))
1264#else
1265 #define ssat32_to_16(v) do { \
1266 if (v < -32768) v = -32768; \
1267 else if (v > 32767) v = 32767; \
1268 } while (0)
1269#endif
1270
1271static void cdrPrepCdda(s16 *buf, int samples)
1272{
1273#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1274 int i;
1275 for (i = 0; i < samples; i++) {
1276 buf[i * 2 + 0] = SWAP16(buf[i * 2 + 0]);
1277 buf[i * 2 + 1] = SWAP16(buf[i * 2 + 1]);
1278 }
1279#endif
1280}
1281
1282static void cdrReadInterruptSetResult(unsigned char result)
1283{
1284 if (cdr.IrqStat) {
1285 CDR_LOG_I("%d:%02d:%02d irq miss, cmd=%02x irqstat=%02x\n",
1286 cdr.SetSectorPlay[0], cdr.SetSectorPlay[1], cdr.SetSectorPlay[2],
1287 cdr.CmdInProgress, cdr.IrqStat);
1288 cdr.Irq1Pending = result;
1289 return;
1290 }
1291 SetResultSize(1);
1292 cdr.Result[0] = result;
1293 setIrq((result & STATUS_ERROR) ? DiskError : DataReady, 0x1004);
1294}
1295
1296static void cdrUpdateTransferBuf(const u8 *buf)
1297{
1298 if (!buf)
1299 return;
1300 memcpy(cdr.Transfer, buf, DATA_SIZE);
1301 CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]);
1302 CDR_LOG("cdr.Transfer %02x:%02x:%02x\n",
1303 cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]);
1304 if (cdr.FifoOffset < 2048 + 12)
1305 CDR_LOG("FifoOffset(1) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1306}
1307
1308static void cdrReadInterrupt(void)
1309{
1310 const struct { u8 file, chan, mode, coding; } *subhdr;
1311 const u8 *buf = NULL;
1312 int deliver_data = 1;
1313 u8 subqPos[3];
1314 int read_ok;
1315 int is_start;
1316
1317 memcpy(subqPos, cdr.SetSectorPlay, sizeof(subqPos));
1318 msfiAdd(subqPos, cdr.SubqForwardSectors);
1319 UpdateSubq(subqPos);
1320 if (cdr.SubqForwardSectors < SUBQ_FORWARD_SECTORS) {
1321 cdr.SubqForwardSectors++;
1322 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1323 return;
1324 }
1325
1326 // note: CdlGetlocL should work as soon as STATUS_READ is indicated
1327 SetPlaySeekRead(cdr.StatP, STATUS_READ | STATUS_ROTATING);
1328 cdr.DriveState = DRIVESTATE_PLAY_READ;
1329 cdr.sectorsRead++;
1330
1331 read_ok = ReadTrack(cdr.SetSectorPlay);
1332 if (read_ok)
1333 buf = CDR_getBuffer();
1334 if (buf == NULL)
1335 read_ok = 0;
1336
1337 if (!read_ok) {
1338 CDR_LOG_I("cdrReadInterrupt() Log: err\n");
1339 cdrReadInterruptSetResult(cdr.StatP | STATUS_ERROR);
1340 cdr.DriveState = DRIVESTATE_PAUSED; // ?
1341 return;
1342 }
1343 memcpy(cdr.LocL, buf, 8);
1344
1345 if (!cdr.IrqStat && !cdr.Irq1Pending)
1346 cdrUpdateTransferBuf(buf);
1347
1348 subhdr = (void *)(buf + 4);
1349 do {
1350 // try to process as adpcm
1351 if (!(cdr.Mode & MODE_STRSND))
1352 break;
1353 if (buf[3] != 2 || (subhdr->mode & 0x44) != 0x44) // or 0x64?
1354 break;
1355 CDR_LOG("f=%d m=%d %d,%3d | %d,%2d | %d,%2d\n", !!(cdr.Mode & MODE_SF), cdr.Muted,
1356 subhdr->file, subhdr->chan, cdr.CurFile, cdr.CurChannel, cdr.FilterFile, cdr.FilterChannel);
1357 if ((cdr.Mode & MODE_SF) && (subhdr->file != cdr.FilterFile || subhdr->chan != cdr.FilterChannel))
1358 break;
1359 if (subhdr->chan & 0xe0) { // ?
1360 if (subhdr->chan != 0xff)
1361 log_unhandled("adpcm %d:%d\n", subhdr->file, subhdr->chan);
1362 break;
1363 }
1364 if (!cdr.FileChannelSelected) {
1365 cdr.CurFile = subhdr->file;
1366 cdr.CurChannel = subhdr->chan;
1367 cdr.FileChannelSelected = 1;
1368 }
1369 else if (subhdr->file != cdr.CurFile || subhdr->chan != cdr.CurChannel)
1370 break;
1371
1372 // accepted as adpcm
1373 deliver_data = 0;
1374
1375 if (Config.Xa)
1376 break;
1377 is_start = !cdr.AdpcmActive;
1378 cdr.AdpcmActive = !xa_decode_sector(&cdr.Xa, buf + 4, is_start);
1379 if (cdr.AdpcmActive)
1380 SPU_playADPCMchannel(&cdr.Xa, psxRegs.cycle, is_start);
1381 } while (0);
1382
1383 if ((cdr.Mode & MODE_SF) && (subhdr->mode & 0x44) == 0x44) // according to nocash
1384 deliver_data = 0;
1385
1386 /*
1387 Croc 2: $40 - only FORM1 (*)
1388 Judge Dredd: $C8 - only FORM1 (*)
1389 Sim Theme Park - no adpcm at all (zero)
1390 */
1391
1392 if (deliver_data)
1393 cdrReadInterruptSetResult(cdr.StatP);
1394
1395 msfiAdd(cdr.SetSectorPlay, 1);
1396
1397 CDRPLAYREAD_INT((cdr.Mode & MODE_SPEED) ? (cdReadTime / 2) : cdReadTime, 0);
1398}
1399
1400/*
1401cdrRead0:
1402 bit 0,1 - reg index
1403 bit 2 - adpcm active
1404 bit 5 - 1 result ready
1405 bit 6 - 1 dma ready
1406 bit 7 - 1 command being processed
1407*/
1408
1409unsigned char cdrRead0(void) {
1410 cdr.Ctrl &= ~0x24;
1411 cdr.Ctrl |= cdr.AdpcmActive << 2;
1412 cdr.Ctrl |= cdr.ResultReady << 5;
1413
1414 cdr.Ctrl |= 0x40; // data fifo not empty
1415
1416 // What means the 0x10 and the 0x08 bits? I only saw it used by the bios
1417 cdr.Ctrl |= 0x18;
1418
1419 CDR_LOG_IO("cdr r0.sta: %02x\n", cdr.Ctrl);
1420
1421 return psxHu8(0x1800) = cdr.Ctrl;
1422}
1423
1424void cdrWrite0(unsigned char rt) {
1425 CDR_LOG_IO("cdr w0.idx: %02x\n", rt);
1426
1427 cdr.Ctrl = (rt & 3) | (cdr.Ctrl & ~3);
1428}
1429
1430unsigned char cdrRead1(void) {
1431 if ((cdr.ResultP & 0xf) < cdr.ResultC)
1432 psxHu8(0x1801) = cdr.Result[cdr.ResultP & 0xf];
1433 else
1434 psxHu8(0x1801) = 0;
1435 cdr.ResultP++;
1436 if (cdr.ResultP == cdr.ResultC)
1437 cdr.ResultReady = 0;
1438
1439 CDR_LOG_IO("cdr r1.rsp: %02x #%u\n", psxHu8(0x1801), cdr.ResultP - 1);
1440
1441 return psxHu8(0x1801);
1442}
1443
1444void cdrWrite1(unsigned char rt) {
1445 const char *rnames[] = { "cmd", "smd", "smc", "arr" }; (void)rnames;
1446 CDR_LOG_IO("cdr w1.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1447
1448 switch (cdr.Ctrl & 3) {
1449 case 0:
1450 break;
1451 case 3:
1452 cdr.AttenuatorRightToRightT = rt;
1453 return;
1454 default:
1455 return;
1456 }
1457
1458#ifdef CDR_LOG_CMD_IRQ
1459 CDR_LOG_I("CD1 write: %x (%s)", rt, CmdName[rt]);
1460 if (cdr.ParamC) {
1461 int i;
1462 SysPrintf(" Param[%d] = {", cdr.ParamC);
1463 for (i = 0; i < cdr.ParamC; i++)
1464 SysPrintf(" %x,", cdr.Param[i]);
1465 SysPrintf("}\n");
1466 } else {
1467 SysPrintf("\n");
1468 }
1469#endif
1470
1471 cdr.ResultReady = 0;
1472 cdr.Ctrl |= 0x80;
1473
1474 if (!cdr.CmdInProgress) {
1475 cdr.CmdInProgress = rt;
1476 // should be something like 12k + controller delays
1477 set_event(PSXINT_CDR, 5000);
1478 }
1479 else {
1480 CDR_LOG_I("cmd while busy: %02x, prev %02x, busy %02x\n",
1481 rt, cdr.Cmd, cdr.CmdInProgress);
1482 if (cdr.CmdInProgress < 0x100) // no pending 2nd response
1483 cdr.CmdInProgress = rt;
1484 }
1485
1486 cdr.Cmd = rt;
1487}
1488
1489unsigned char cdrRead2(void) {
1490 unsigned char ret = cdr.Transfer[0x920];
1491
1492 if (cdr.FifoOffset < cdr.FifoSize)
1493 ret = cdr.Transfer[cdr.FifoOffset++];
1494 else
1495 CDR_LOG_I("read empty fifo (%d)\n", cdr.FifoSize);
1496
1497 CDR_LOG_IO("cdr r2.dat: %02x\n", ret);
1498 return ret;
1499}
1500
1501void cdrWrite2(unsigned char rt) {
1502 const char *rnames[] = { "prm", "ien", "all", "arl" }; (void)rnames;
1503 CDR_LOG_IO("cdr w2.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1504
1505 switch (cdr.Ctrl & 3) {
1506 case 0:
1507 if (cdr.ParamC < 8) // FIXME: size and wrapping
1508 cdr.Param[cdr.ParamC++] = rt;
1509 return;
1510 case 1:
1511 cdr.IrqMask = rt;
1512 setIrq(cdr.IrqStat, 0x1005);
1513 return;
1514 case 2:
1515 cdr.AttenuatorLeftToLeftT = rt;
1516 return;
1517 case 3:
1518 cdr.AttenuatorRightToLeftT = rt;
1519 return;
1520 }
1521}
1522
1523unsigned char cdrRead3(void) {
1524 if (cdr.Ctrl & 0x1)
1525 psxHu8(0x1803) = cdr.IrqStat | 0xE0;
1526 else
1527 psxHu8(0x1803) = cdr.IrqMask | 0xE0;
1528
1529 CDR_LOG_IO("cdr r3.%s: %02x\n", (cdr.Ctrl & 1) ? "ifl" : "ien", psxHu8(0x1803));
1530 return psxHu8(0x1803);
1531}
1532
1533void cdrWrite3(unsigned char rt) {
1534 const char *rnames[] = { "req", "ifl", "alr", "ava" }; (void)rnames;
1535 u8 ll, lr, rl, rr;
1536 CDR_LOG_IO("cdr w3.%s: %02x\n", rnames[cdr.Ctrl & 3], rt);
1537
1538 switch (cdr.Ctrl & 3) {
1539 case 0:
1540 break; // transfer
1541 case 1:
1542 if (cdr.IrqStat & rt) {
1543 u32 nextCycle = psxRegs.intCycle[PSXINT_CDR].sCycle
1544 + psxRegs.intCycle[PSXINT_CDR].cycle;
1545 int pending = psxRegs.interrupt & (1 << PSXINT_CDR);
1546#ifdef CDR_LOG_CMD_IRQ
1547 CDR_LOG_I("ack %02x (w=%02x p=%d,%x,%x,%d)\n",
1548 cdr.IrqStat & rt, rt, !!pending, cdr.CmdInProgress,
1549 cdr.Irq1Pending, nextCycle - psxRegs.cycle);
1550#endif
1551 // note: Croc, Shadow Tower (more) vs Discworld Noir (<993)
1552 if (!pending && (cdr.CmdInProgress || cdr.Irq1Pending))
1553 {
1554 s32 c = 2048;
1555 if (cdr.CmdInProgress) {
1556 c = 2048 - (psxRegs.cycle - nextCycle);
1557 c = MAX_VALUE(c, 512);
1558 }
1559 set_event(PSXINT_CDR, c);
1560 }
1561 }
1562 cdr.IrqStat &= ~rt;
1563
1564 if (rt & 0x40)
1565 cdr.ParamC = 0;
1566 return;
1567 case 2:
1568 cdr.AttenuatorLeftToRightT = rt;
1569 return;
1570 case 3:
1571 if (rt & 0x01)
1572 log_unhandled("Mute ADPCM?\n");
1573 if (rt & 0x20) {
1574 ll = cdr.AttenuatorLeftToLeftT; lr = cdr.AttenuatorLeftToRightT;
1575 rl = cdr.AttenuatorRightToLeftT; rr = cdr.AttenuatorRightToRightT;
1576 if (ll == cdr.AttenuatorLeftToLeft &&
1577 lr == cdr.AttenuatorLeftToRight &&
1578 rl == cdr.AttenuatorRightToLeft &&
1579 rr == cdr.AttenuatorRightToRight)
1580 return;
1581 cdr.AttenuatorLeftToLeftT = ll; cdr.AttenuatorLeftToRightT = lr;
1582 cdr.AttenuatorRightToLeftT = rl; cdr.AttenuatorRightToRightT = rr;
1583 CDR_LOG_I("CD-XA Volume: %02x %02x | %02x %02x\n", ll, lr, rl, rr);
1584 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1585 }
1586 return;
1587 }
1588
1589 // test: Viewpoint
1590 if ((rt & 0x80) && cdr.FifoOffset < cdr.FifoSize) {
1591 CDR_LOG("cdrom: FifoOffset(2) %d/%d\n", cdr.FifoOffset, cdr.FifoSize);
1592 }
1593 else if (rt & 0x80) {
1594 switch (cdr.Mode & (MODE_SIZE_2328|MODE_SIZE_2340)) {
1595 case MODE_SIZE_2328:
1596 case 0x00:
1597 cdr.FifoOffset = 12;
1598 cdr.FifoSize = 2048 + 12;
1599 break;
1600
1601 case MODE_SIZE_2340:
1602 default:
1603 cdr.FifoOffset = 0;
1604 cdr.FifoSize = 2340;
1605 break;
1606 }
1607 }
1608 else if (!(rt & 0xc0))
1609 cdr.FifoOffset = DATA_SIZE; // fifo empty
1610}
1611
1612void psxDma3(u32 madr, u32 bcr, u32 chcr) {
1613 u32 cdsize, max_words;
1614 int size;
1615 u8 *ptr;
1616
1617#if 0
1618 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x", chcr, madr, bcr);
1619 if (cdr.FifoOffset == 0) {
1620 ptr = cdr.Transfer;
1621 SysPrintf(" %02x:%02x:%02x", ptr[0], ptr[1], ptr[2]);
1622 }
1623 SysPrintf("\n");
1624#endif
1625
1626 switch (chcr & 0x71000000) {
1627 case 0x11000000:
1628 ptr = getDmaRam(madr, &max_words);
1629 if (ptr == INVALID_PTR) {
1630 CDR_LOG_I("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n");
1631 break;
1632 }
1633
1634 cdsize = (((bcr - 1) & 0xffff) + 1) * 4;
1635
1636 /*
1637 GS CDX: Enhancement CD crash
1638 - Setloc 0:0:0
1639 - CdlPlay
1640 - Spams DMA3 and gets buffer overrun
1641 */
1642 size = DATA_SIZE - cdr.FifoOffset;
1643 if (size > cdsize)
1644 size = cdsize;
1645 if (size > max_words * 4)
1646 size = max_words * 4;
1647 if (size > 0)
1648 {
1649 memcpy(ptr, cdr.Transfer + cdr.FifoOffset, size);
1650 cdr.FifoOffset += size;
1651 }
1652 if (size < cdsize) {
1653 CDR_LOG_I("cdrom: dma3 %d/%d\n", size, cdsize);
1654 memset(ptr + size, cdr.Transfer[0x920], cdsize - size);
1655 }
1656 psxCpu->Clear(madr, cdsize / 4);
1657
1658 set_event(PSXINT_CDRDMA, (cdsize / 4) * 24);
1659
1660 HW_DMA3_CHCR &= SWAPu32(~0x10000000);
1661 if (chcr & 0x100) {
1662 HW_DMA3_MADR = SWAPu32(madr + cdsize);
1663 HW_DMA3_BCR &= SWAPu32(0xffff0000);
1664 }
1665 else {
1666 // halted
1667 psxRegs.cycle += (cdsize/4) * 24 - 20;
1668 }
1669 return;
1670
1671 default:
1672 CDR_LOG_I("psxDma3() Log: Unknown cddma %x\n", chcr);
1673 break;
1674 }
1675
1676 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1677 DMA_INTERRUPT(3);
1678}
1679
1680void cdrDmaInterrupt(void)
1681{
1682 if (HW_DMA3_CHCR & SWAP32(0x01000000))
1683 {
1684 HW_DMA3_CHCR &= SWAP32(~0x01000000);
1685 DMA_INTERRUPT(3);
1686 }
1687}
1688
1689static void getCdInfo(void)
1690{
1691 u8 tmp;
1692
1693 CDR_getTN(cdr.ResultTN);
1694 CDR_getTD(0, cdr.SetSectorEnd);
1695 tmp = cdr.SetSectorEnd[0];
1696 cdr.SetSectorEnd[0] = cdr.SetSectorEnd[2];
1697 cdr.SetSectorEnd[2] = tmp;
1698}
1699
1700void cdrReset() {
1701 memset(&cdr, 0, sizeof(cdr));
1702 cdr.CurTrack = 1;
1703 cdr.FilterFile = 0;
1704 cdr.FilterChannel = 0;
1705 cdr.IrqMask = 0x1f;
1706 cdr.IrqStat = NoIntr;
1707 cdr.FifoOffset = DATA_SIZE; // fifo empty
1708
1709 CDR_getStatus(&stat);
1710 if (stat.Status & STATUS_SHELLOPEN) {
1711 cdr.DriveState = DRIVESTATE_LID_OPEN;
1712 cdr.StatP = STATUS_SHELLOPEN;
1713 }
1714 else if (CdromId[0] == '\0') {
1715 cdr.DriveState = DRIVESTATE_STOPPED;
1716 cdr.StatP = 0;
1717 }
1718 else {
1719 cdr.DriveState = DRIVESTATE_STANDBY;
1720 cdr.StatP = STATUS_ROTATING;
1721 }
1722
1723 // BIOS player - default values
1724 cdr.AttenuatorLeftToLeft = 0x80;
1725 cdr.AttenuatorLeftToRight = 0x00;
1726 cdr.AttenuatorRightToLeft = 0x00;
1727 cdr.AttenuatorRightToRight = 0x80;
1728 SPU_setCDvol(cdr.AttenuatorLeftToLeft, cdr.AttenuatorLeftToRight,
1729 cdr.AttenuatorRightToLeft, cdr.AttenuatorRightToRight, psxRegs.cycle);
1730
1731 getCdInfo();
1732}
1733
1734int cdrFreeze(void *f, int Mode) {
1735 u32 tmp;
1736 u8 tmpp[3];
1737
1738 if (Mode == 0 && !Config.Cdda)
1739 CDR_stop();
1740
1741 cdr.freeze_ver = 0x63647202;
1742 gzfreeze(&cdr, sizeof(cdr));
1743
1744 if (Mode == 1) {
1745 cdr.ParamP = cdr.ParamC;
1746 tmp = cdr.FifoOffset;
1747 }
1748
1749 gzfreeze(&tmp, sizeof(tmp));
1750
1751 if (Mode == 0) {
1752 u8 ll = 0, lr = 0, rl = 0, rr = 0;
1753 getCdInfo();
1754
1755 cdr.FifoOffset = tmp < DATA_SIZE ? tmp : DATA_SIZE;
1756 cdr.FifoSize = (cdr.Mode & MODE_SIZE_2340) ? 2340 : 2048 + 12;
1757 if (cdr.SubqForwardSectors > SUBQ_FORWARD_SECTORS)
1758 cdr.SubqForwardSectors = SUBQ_FORWARD_SECTORS;
1759
1760 // read right sub data
1761 tmpp[0] = btoi(cdr.Prev[0]);
1762 tmpp[1] = btoi(cdr.Prev[1]);
1763 tmpp[2] = btoi(cdr.Prev[2]);
1764 cdr.Prev[0]++;
1765 ReadTrack(tmpp);
1766
1767 if (cdr.Play) {
1768 if (cdr.freeze_ver < 0x63647202)
1769 memcpy(cdr.SetSectorPlay, cdr.SetSector, 3);
1770
1771 Find_CurTrack(cdr.SetSectorPlay);
1772 if (!Config.Cdda)
1773 CDR_play(cdr.SetSectorPlay);
1774 }
1775 if (!cdr.Muted)
1776 ll = cdr.AttenuatorLeftToLeft, lr = cdr.AttenuatorLeftToLeft,
1777 rl = cdr.AttenuatorRightToLeft, rr = cdr.AttenuatorRightToRight;
1778 SPU_setCDvol(ll, lr, rl, rr, psxRegs.cycle);
1779 }
1780
1781 return 0;
1782}
1783
1784void LidInterrupt(void) {
1785 getCdInfo();
1786 cdrLidSeekInterrupt();
1787}