aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorspicyjpeg <thatspicyjpeg@gmail.com>2022-12-18 16:30:02 +0100
committerGitHub <noreply@github.com>2022-12-18 16:30:02 +0100
commitb12b716a9e54c4f1892795a136d6ffeb088ff001 (patch)
tree5888e4a77b207f24ccd9ad148d1c74e9c33400e9
parent77306e187ef1a7ad5d3508ae9acb38edc5b68255 (diff)
parenta3359c0e7d85bf4752cda3b00892ecd5ef691077 (diff)
downloadpsn00bsdk-b12b716a9e54c4f1892795a136d6ffeb088ff001.tar.gz
Merge pull request #68 from spicyjpeg/bugfix
Bugfixes, psxcd rewrite, psxgpu and CMake tweaks (v0.22)
-rw-r--r--CHANGELOG.md31
-rw-r--r--CMakeLists.txt2
-rw-r--r--cpack/setup.cmake12
-rw-r--r--doc/cmake_reference.md46
-rw-r--r--doc/known_bugs.md7
-rw-r--r--examples/cdrom/cdbrowse/main.c1
-rw-r--r--examples/cdrom/cdxa/main.c2
-rw-r--r--examples/demos/n00bdemo/main.c6
-rw-r--r--examples/io/pads/spi.c28
-rw-r--r--examples/io/system573/k573io.c126
-rw-r--r--examples/io/system573/k573io.h274
-rw-r--r--examples/io/system573/main.c80
-rw-r--r--examples/mdec/strvideo/main.c93
-rw-r--r--examples/sound/cdstream/main.c4
-rw-r--r--examples/sound/cdstream/stream.vagbin4646912 -> 4687872 bytes
-rw-r--r--examples/sound/spustream/interleave.py13
-rw-r--r--examples/sound/spustream/main.c51
-rw-r--r--examples/sound/spustream/stream.vagbin1140736 -> 1140736 bytes
-rw-r--r--examples/sound/vagsample/main.c4
-rw-r--r--libpsn00b/cmake/flags.cmake16
-rw-r--r--libpsn00b/cmake/internal_setup.cmake47
-rw-r--r--libpsn00b/include/assert.h13
-rw-r--r--libpsn00b/include/dlfcn.h9
-rw-r--r--libpsn00b/include/hwregs_a.inc66
-rw-r--r--libpsn00b/include/hwregs_c.h61
-rw-r--r--libpsn00b/include/psxapi.h42
-rw-r--r--libpsn00b/include/psxcd.h1004
-rw-r--r--libpsn00b/include/psxgpu.h19
-rw-r--r--libpsn00b/include/stdio.h17
-rw-r--r--libpsn00b/libc/clz.s43
-rw-r--r--libpsn00b/lzp/lzp.c1
-rw-r--r--libpsn00b/lzp/qlp.c1
-rw-r--r--libpsn00b/psxapi/_syscalls.s17
-rw-r--r--libpsn00b/psxcd/_cd_control.s144
-rw-r--r--libpsn00b/psxcd/cdmix.s34
-rw-r--r--libpsn00b/psxcd/cdread.c168
-rw-r--r--libpsn00b/psxcd/common.c435
-rw-r--r--libpsn00b/psxcd/getsector.c51
-rw-r--r--libpsn00b/psxcd/isofs.c72
-rw-r--r--libpsn00b/psxcd/misc.c151
-rw-r--r--libpsn00b/psxcd/psxcd.c371
-rw-r--r--libpsn00b/psxcd/psxcd_asm.s487
-rw-r--r--libpsn00b/psxcd/readme.txt40
-rw-r--r--libpsn00b/psxetc/_dl_resolve_wrapper.s30
-rw-r--r--libpsn00b/psxetc/dl.c143
-rw-r--r--libpsn00b/psxetc/interrupts.c5
-rw-r--r--libpsn00b/psxetc/logging.c50
-rw-r--r--libpsn00b/psxgpu/common.c31
-rw-r--r--libpsn00b/psxgpu/env.c99
-rw-r--r--libpsn00b/psxgpu/image.c44
-rw-r--r--libpsn00b/psxgte/initgeom.s51
-rw-r--r--libpsn00b/psxpress/mdec.c8
-rw-r--r--libpsn00b/psxsio/sio.c67
-rw-r--r--libpsn00b/psxsio/tty.c4
-rw-r--r--libpsn00b/psxspu/common.c17
55 files changed, 2256 insertions, 2382 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6b49e9..74395d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,37 @@ to ensure the changelog can be parsed correctly.
-------------------------------------------------------------------------------
+# 2022-12-18: 0.22
+
+spicyjpeg:
+
+- libc: Fixed `assert()` macro and removed redundant type definitions. Added
+ GTE-accelerated leading zero count intrinsics (`__builtin_clz()`).
+
+- psxcd: Rewritten some parts of the library in pure C. Added `CdCommand()` and
+ `CdCommandF()` for advanced usage of the CD-ROM, as well as `CdReadRetry()`,
+ `CdReadBreak()` and `CdGetRegion()`.
+
+- psxgpu: `PutDrawEnv()` and `DrawOTagEnv()` now properly apply the texture
+ page and window attributes in the `DRAWENV` structure. RECTs passed to
+ `LoadImage()` and `StoreImage()` are now copied into a private buffer. Added
+ `setColor*()` macros.
+
+- psxspu: Fixed setting of bus wait states when using `SpuRead()`.
+
+- psxetc: Fixed several critical bugs in the dynamic linker. GOT relocation and
+ linking of variables and objects are now handled properly.
+
+- psxapi: Added `FastEnterCriticalSection()` and `FastExitCriticalSection()`
+ inline macros for quick enabling and disabling of interrupts.
+
+- Renamed some registers in `hwregs_c.h`, `hwregs_a.inc` and updated examples
+ accordingly.
+
+- All projects are now compiled with `-Og` in debug mode and `-O2` in release
+ mode by default. CD images created using `psn00bsdk_add_cd_image()` are now
+ only rebuilt if necessary (i.e. if any of their dependencies change).
+
# 2022-10-27
spicyjpeg:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3ce6573..19050f8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,7 +12,7 @@ include(ExternalProject)
project(
PSn00bSDK
LANGUAGES NONE
- VERSION 0.21
+ VERSION 0.22
DESCRIPTION "Open source PlayStation 1 SDK"
HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk"
)
diff --git a/cpack/setup.cmake b/cpack/setup.cmake
index d585641..1d66066 100644
--- a/cpack/setup.cmake
+++ b/cpack/setup.cmake
@@ -101,16 +101,16 @@ endif()
## Variables common to all package types
if(NOT DEFINED CPACK_GENERATOR)
- if(WIN32)
- set(CPACK_GENERATOR ZIP NSIS)
- elseif(APPLE)
+ #if(WIN32)
+ #set(CPACK_GENERATOR ZIP NSIS)
+ #elseif(APPLE)
# TODO: add a macOS installer and related options
- set(CPACK_GENERATOR ZIP)
+ #set(CPACK_GENERATOR ZIP)
#elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
#set(CPACK_GENERATOR ZIP DEB RPM)
- else()
+ #else()
set(CPACK_GENERATOR ZIP)
- endif()
+ #endif()
endif()
set(CPACK_VERBATIM_VARIABLES ON)
diff --git a/doc/cmake_reference.md b/doc/cmake_reference.md
index bceaac9..ef3f5dc 100644
--- a/doc/cmake_reference.md
+++ b/doc/cmake_reference.md
@@ -172,33 +172,31 @@ psn00bsdk_add_cd_image(
```
Creates a new virtual target that will build a CD image using `mkpsxiso`. The
-CD image will always be considered out-of-date and built, even if none of its
-dependencies or any other files have been modified.
-
-The first argument is the name of the target to create; next up is the name of
-the generated image file (`<image name>.bin` + `<image name>.cue`). The third
-argument is the path to the XML file (relative to the source directory) passed
-to `mkpsxiso`.
+CD image will be added to the top-level target and rebuilt automatically if any
+of its dependencies have been modified since the last build.
+
+The first argument is the name of the target to create and associate with the
+CD image. This target does not actually build the image but depends on it,
+ensuring CMake will build it if necessary. Note that the target, along with any
+other targets depending on it, are always considered out-of-date by CMake
+*even if the CD image is actually up-to-date*.
+
+The second argument specifies the name of the generated image files
+(`<image name>.bin` + `<image name>.cue`) and is followed to the path to the
+XML file (relative to the source directory) to be passed to `mkpsxiso`. Note
+that the `image_name` and `cue_sheet` fields specified in the `<iso_project>`
+root tag (see `mkpsxiso` documentation) are ignored and overridden by the image
+name provided.
The XML file is "configured" by CMake, i.e. any `${var}` or `@var@` expressions
-are replaced with the values of the respective variables. In particular
-`${CD_IMAGE_NAME}` is replaced with the second argument passed to
-`psn00bsdk_add_cd_image()`; the file must properly set the output file names
-like this:
-
-```xml
-<?xml version="1.0" encoding="utf-8"?>
-<iso_project
- image_name="${CD_IMAGE_NAME}.bin"
- cue_sheet="${CD_IMAGE_NAME}.cue"
->
- <!-- ... -->
-</iso_project>
-```
+are replaced with the values of the respective CMake variables. Paths to source
+files are interpreted as relative to the build directory. In order to include a
+file from the source directory, `${PROJECT_SOURCE_DIR}` shall be prepended to
+the path specified in the XML file (e.g. `${PROJECT_SOURCE_DIR}/system.cnf`).
Any additional argument is passed through to the underlying call to
-`add_custom_target()`, so most of the options supported by
-`add_custom_target()` (including `DEPENDS`) are also supported here.
+`add_custom_command()`, so most of the options supported by
+`add_custom_command()` (including `DEPENDS`) are also supported here.
### `psn00bsdk_target_incbin`
@@ -396,4 +394,4 @@ CMake's `add_custom_command()` and `add_custom_target()` to convert models and
generate LZP archives as part of the build pipeline.
-----------------------------------------
-_Last updated on 2022-10-11 by spicyjpeg_
+_Last updated on 2022-12-18 by spicyjpeg_
diff --git a/doc/known_bugs.md b/doc/known_bugs.md
index 5aabaf1..40b3c32 100644
--- a/doc/known_bugs.md
+++ b/doc/known_bugs.md
@@ -37,9 +37,14 @@ fixed.
due to the SPU status register being emulated incorrectly. They work as
expected on other emulators as well as on real hardware.
+`psxcd`:
+
+- The library seems to get into an unpredictable state when removing discs on
+ pcsx-redux (and possibly real hardware?).
+
## Examples
See [README.md in the examples directory](../examples/README.md#examples-summary).
-----------------------------------------
-_Last updated on 2022-10-30 by spicyjpeg_
+_Last updated on 2022-12-18 by spicyjpeg_
diff --git a/examples/cdrom/cdbrowse/main.c b/examples/cdrom/cdbrowse/main.c
index f614f1d..c554f64 100644
--- a/examples/cdrom/cdbrowse/main.c
+++ b/examples/cdrom/cdbrowse/main.c
@@ -55,6 +55,7 @@
*/
#include <stdint.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/examples/cdrom/cdxa/main.c b/examples/cdrom/cdxa/main.c
index 93cf01a..94e6d45 100644
--- a/examples/cdrom/cdxa/main.c
+++ b/examples/cdrom/cdxa/main.c
@@ -189,7 +189,7 @@ char padbuff[2][34];
char xa_sector_buff[2048];
/* Callback for detecting end of channel (hooked by CdReadyCallback) */
-void xa_callback(int intr, unsigned char *result)
+void xa_callback(CdlIntrResult intr, unsigned char *result)
{
SECTOR_HEAD *sec;
diff --git a/examples/demos/n00bdemo/main.c b/examples/demos/n00bdemo/main.c
index 069b549..6d0be3c 100644
--- a/examples/demos/n00bdemo/main.c
+++ b/examples/demos/n00bdemo/main.c
@@ -76,7 +76,7 @@ volatile int timer_counter = 0, frame_counter = 0, frame_rate = 0;
void sort_overlay(int showlotl);
void lightdemo();
-#define K573_WATCHDOG *((volatile unsigned short *) 0x1f5c0000)
+#define K573_WATCHDOG *((volatile unsigned short *) 0xbf5c0000)
#define K573_EXP1_CFG 0x24173f47
void timerTick() {
@@ -101,8 +101,8 @@ void timerSetup() {
EnterCriticalSection();
#ifdef SYSTEM_573_SUPPORT
- EXP1_ADDR = 0x1f000000;
- EXP1_DELAY_SIZE = K573_EXP1_CFG;
+ BUS_EXP1_ADDR = 0x1f000000;
+ BUS_EXP1_CFG = K573_EXP1_CFG;
#endif
TIMER_CTRL(2) = 0x0258; // CLK/8 input, IRQ on reload
diff --git a/examples/io/pads/spi.c b/examples/io/pads/spi.c
index 292e682..234bdba 100644
--- a/examples/io/pads/spi.c
+++ b/examples/io/pads/spi.c
@@ -93,8 +93,8 @@ static void _spi_next_req(void) {
static void _spi_poll_irq_handler(void) {
// Fetch the last response byte, which wasn't followed by a pulse on /ACK,
// from the RX FIFO.
- if (JOY_STAT & 0x0002)
- _context.rx_buff[_context.rx_len - 1] = (uint8_t) JOY_TXRX;
+ if (SIO_STAT(0) & 0x0002)
+ _context.rx_buff[_context.rx_len - 1] = (uint8_t) SIO_DATA(0);
if (_context.callback)
_context.callback(_context.port, _context.rx_buff, _context.rx_len);
@@ -109,49 +109,49 @@ static void _spi_poll_irq_handler(void) {
// enabling the /ACK IRQ. In order to communicate with controllers, /CS has
// to be driven low again for about 20 us before sending the first byte.
// TODO: these delays can be probably tweaked for better performance
- JOY_CTRL = 0x0010;
+ SIO_CTRL(0) = 0x0010;
for (uint32_t i = 0; i < 1000; i++)
__asm__ volatile("");
- JOY_CTRL = 0x1003 | (_context.port << 13);
+ SIO_CTRL(0) = 0x1003 | (_context.port << 13);
for (uint32_t i = 0; i < 2000; i++)
__asm__ volatile("");
// Send the first byte indicating which device to address. If the matching
// device is connected, it will reply by triggering the /ACK IRQ.
- JOY_TXRX = _context.tx_buff[0];
+ SIO_DATA(0) = _context.tx_buff[0];
}
static void _spi_ack_irq_handler(void) {
// Wait until /ACK is pulled up by the controller before sending the next
// byte. According to nocash docs, this has to be done before resetting the
// IRQ.
- while (JOY_STAT & 0x0080)
+ while (SIO_STAT(0) & 0x0080)
__asm__ volatile("");
// Keep /CS pulled low and acknowledge the IRQ (bit 4) to ensure it can be
// triggered again.
- JOY_CTRL = 0x1013 | (_context.port << 13);
+ SIO_CTRL(0) = 0x1013 | (_context.port << 13);
if (!_context.rx_len) {
// We just sent the first address byte. Obviously the response we
// received was read from an open bus, so the SPI port's internal FIFO
// must be flushed (by performing dummy reads) to ensure we are only
// going to read valid data from now on.
- JOY_TXRX;
+ SIO_DATA(0);
} else if (_context.rx_len <= SPI_BUFF_LEN) {
// If this is not the first byte, put it in the RX buffer.
- _context.rx_buff[_context.rx_len - 1] = (uint8_t) JOY_TXRX;
+ _context.rx_buff[_context.rx_len - 1] = (uint8_t) SIO_DATA(0);
}
// Send the next byte, or a null byte if there is no more data to send and
// we're just reading a response.
_context.rx_len++;
if (_context.rx_len < _context.tx_len)
- JOY_TXRX = (uint32_t) _context.tx_buff[_context.rx_len];
+ SIO_DATA(0) = (uint32_t) _context.tx_buff[_context.rx_len];
else
- JOY_TXRX = 0x00;
+ SIO_DATA(0) = 0x00;
}
/* Public API */
@@ -198,9 +198,9 @@ void SPI_Init(SPI_Callback callback) {
InterruptCallback(7, &_spi_ack_irq_handler);
ExitCriticalSection();
- JOY_CTRL = 0x0040; // Reset all registers
- JOY_MODE = 0x000d; // 1x multiplier, 8 data bits, no parity
- JOY_BAUD = 0x0088; // 250000 bps
+ SIO_CTRL(0) = 0x0040; // Reset all registers
+ SIO_MODE(0) = 0x000d; // 1x multiplier, 8 data bits, no parity
+ SIO_BAUD(0) = 0x0088; // 250000 bps
SPI_SetPollRate(250);
_current_req = 0;
diff --git a/examples/io/system573/k573io.c b/examples/io/system573/k573io.c
index 53c109f..69cc871 100644
--- a/examples/io/system573/k573io.c
+++ b/examples/io/system573/k573io.c
@@ -1,12 +1,6 @@
/*
* PSn00bSDK Konami System 573 example (I/O driver)
* (C) 2022 spicyjpeg - MPL licensed
- *
- * Note that this is far from being a complete driver. It currently lacks:
- * - ATAPI driver
- * - Flash erasing/writing APIs
- * - JVS bus APIs
- * - Functions for accessing the digital I/O board's MP3 decoder
*/
#include <stdint.h>
@@ -15,115 +9,47 @@
#include "k573io.h"
-K573_IOBoardType _board_type = IO_TYPE_ANALOG;
-
-/* I/O board light control */
-
-static void _k573_set_lights_analog(uint32_t lights) {
- uint32_t bits = 0xffffffff;
-
- bits ^= (lights & 0x01010101) << 7; // Lamp n*8+0 -> bit n*8+7
- bits ^= (lights & 0x02020202) << 5; // Lamp n*8+1 -> bit n*8+6
- bits ^= (lights & 0x04040404) >> 1; // Lamp n*8+2 -> bit n*8+1
- bits ^= (lights & 0x08080808) >> 3; // Lamp n*8+3 -> bit n*8+0
- bits ^= (lights & 0x10101010) << 1; // Lamp n*8+4 -> bit n*8+5
- bits ^= (lights & 0x20202020) >> 1; // Lamp n*8+5 -> bit n*8+4
- bits ^= (lights & 0x40404040) >> 3; // Lamp n*8+6 -> bit n*8+3
- bits ^= (lights & 0x80808080) >> 5; // Lamp n*8+7 -> bit n*8+2
-
- K573_IO_BOARD[ANALOG_IO_REG_LIGHTS0] = (bits) & 0xff;
- K573_IO_BOARD[ANALOG_IO_REG_LIGHTS1] = (bits >> 8) & 0xff;
- K573_IO_BOARD[ANALOG_IO_REG_LIGHTS2] = (bits >> 16) & 0xff;
- K573_IO_BOARD[ANALOG_IO_REG_LIGHTS3] = (bits >> 24) & 0xff;
-}
-
-// This function controls light outputs on digital I/O boards (i.e. the ones
-// that also include MP3 playback hardware, used by most Bemani games).
-// TODO: test this on real hardware -- it might not work if lights are handled
-// by the board's FPGA, which requires a binary blob...
-static void _k573_set_lights_digital(uint32_t lights) {
- uint32_t bits = 0xffffffff;
-
- bits ^= (lights & 0x11111111); // Lamp n*4+0 -> bit n*4+0
- bits ^= (lights & 0x22222222) << 1; // Lamp n*4+1 -> bit n*4+2
- bits ^= (lights & 0x44444444) << 1; // Lamp n*4+2 -> bit n*4+3
- bits ^= (lights & 0x88888888) >> 2; // Lamp n*4+3 -> bit n*4+1
-
- K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS0] = ((bits) & 0xf) << 12;
- K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS1] = ((bits >> 4) & 0xf) << 12;
- K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS2] = ((bits >> 8) & 0xf) << 12;
- K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS3] = ((bits >> 12) & 0xf) << 12;
- K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS4] = ((bits >> 16) & 0xf) << 12;
- K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS5] = ((bits >> 20) & 0xf) << 12;
- //K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS6] = ((bits >> 24) & 0xf) << 12;
- K573_IO_BOARD[DIGITAL_IO_REG_LIGHTS7] = ((bits >> 28) & 0xf) << 12;
-}
-
-static const void (*_k573_set_lights[])(uint32_t) = {
- &_k573_set_lights_analog,
- &_k573_set_lights_digital
-};
-
-/* Public API */
-
uint32_t K573_GetJAMMAInputs(void) {
uint32_t inputs;
- inputs = K573_IO_CHIP[IO_REG_IN2];
- inputs |= ((K573_IO_CHIP[IO_REG_IN3_LOW] >> 8) & 0x0f) << 16;
- inputs |= ((K573_IO_CHIP[IO_REG_IN3_HIGH] >> 8) & 0x0f) << 20;
- inputs |= ((K573_IO_CHIP[IO_REG_IN1_HIGH] >> 8) & 0x1f) << 24;
- inputs |= (K573_IO_CHIP[IO_REG_IN1_LOW] & 0x07) << 29;
+ inputs = K573_JAMMA_IN;
+ inputs |= ((K573_EXT_IN_P1 >> 8) & 0x0f) << 16;
+ inputs |= ((K573_EXT_IN_P2 >> 8) & 0x0f) << 20;
+ inputs |= ((K573_MISC_IN >> 8) & 0x1f) << 24;
+ inputs |= (K573_DIP_CART_IN & 0x07) << 29;
return ~inputs;
}
-void K573_SetLights(uint32_t lights) {
- if (_board_type > IO_TYPE_DIGITAL)
- return;
-
- _k573_set_lights[_board_type](lights);
-}
+void K573_SetAnalogLights(uint32_t lights) {
+ uint32_t bits = 0xffffffff;
-void K573_SetBoardType(K573_IOBoardType type) {
- _k573_set_lights[_board_type](0);
- _board_type = type;
+ bits ^= (lights & 0x00010101) << 7; // Bit 0 -> bit 7
+ bits ^= (lights & 0x00020202) >> 1; // Bit 1 -> bit 0
+ bits ^= (lights & 0x00040404) << 4; // Bit 2 -> bit 6
+ bits ^= (lights & 0x00080808) >> 2; // Bit 3 -> bit 1
+ bits ^= (lights & 0x00101010) << 1; // Bit 4 -> bit 5
+ bits ^= (lights & 0x00202020) >> 3; // Bit 5 -> bit 2
+ bits ^= (lights & 0x00404040) >> 2; // Bit 6 -> bit 4
+ bits ^= (lights & 0x00808080) >> 4; // Bit 7 -> bit 3
+ bits ^= (lights & 0x0f000000) >> 24;
+
+ K573_IO_BOARD[ANALOG_IO_REG_LIGHTS_A] = (bits) & 0xff;
+ K573_IO_BOARD[ANALOG_IO_REG_LIGHTS_B] = (bits >> 8) & 0xff;
+ K573_IO_BOARD[ANALOG_IO_REG_LIGHTS_C] = (bits >> 16) & 0xff;
+ K573_IO_BOARD[ANALOG_IO_REG_LIGHTS_D] = (bits >> 24) & 0xff;
}
-/*void K573_DDRStageCommand(uint32_t value, uint32_t length) {
- uint32_t last_bit = 0;
- uint32_t mask = 1;
-
- for (uint32_t i = 0; i < length; i++) {
- uint32_t bit = DDR_LIGHT_P1_MUX_DATA | DDR_LIGHT_P2_MUX_DATA;
- if (value & mask)
- bit = 0;
-
- K573_SetLights(last_bit | DDR_LIGHT_P1_MUX_CLK | DDR_LIGHT_P2_MUX_CLK);
- _k573_delay_hblanks(20);
- K573_SetLights(bit | DDR_LIGHT_P1_MUX_CLK | DDR_LIGHT_P2_MUX_CLK);
- _k573_delay_hblanks(20);
- K573_SetLights(bit);
- _k573_delay_hblanks(20);
-
- last_bit = bit;
- mask <<= 1;
- }
-
- K573_SetLights(0);
-}*/
-
-//K573_DDRStageCommand(0x000c90, 13);
-//K573_DDRStageCommand(0x000001, 22);
-
void K573_Init(void) {
- EXP1_ADDR = 0x1f000000;
- EXP1_DELAY_SIZE = 0x24173f47; // 573 BIOS uses this value
+ BUS_EXP1_ADDR = 0x1f000000;
+ BUS_EXP1_CFG = 0x24173f47; // 573 BIOS uses this value
// Bit 6 of this register controls the audio DAC and must be set, otherwise
// no sound will be output. Most of the other bits are data clocks/strobes
// and should be pulled high when not in use.
- K573_IO_CHIP[IO_REG_OUT0] = 0x01e7;
+ K573_MISC_OUT =
+ MISC_OUT_ADC_MOSI | MISC_OUT_ADC_CS | MISC_OUT_ADC_SCK |
+ MISC_OUT_CDDA_ENABLE | MISC_OUT_SPU_ENABLE | MISC_OUT_MCU_CLOCK;
K573_RESET_WATCHDOG();
}
diff --git a/examples/io/system573/k573io.h b/examples/io/system573/k573io.h
index 8655237..552a93c 100644
--- a/examples/io/system573/k573io.h
+++ b/examples/io/system573/k573io.h
@@ -1,57 +1,49 @@
/*
* PSn00bSDK Konami System 573 example (I/O driver)
* (C) 2022 spicyjpeg - MPL licensed
+ *
+ * Reference: https://psx-spx.consoledev.net/konamisystem573/#register-map
*/
#ifndef __K573IO_H
#define __K573IO_H
#include <stdint.h>
+#include <hwregs_c.h>
/* Register definitions */
-#define K573_BANK_SWITCH *((volatile uint16_t *) 0xbf500000)
-#define K573_IDE_RESET *((volatile uint16_t *) 0xbf560000)
-#define K573_WATCHDOG *((volatile uint16_t *) 0xbf5c0000)
-#define K573_EXT_OUT *((volatile uint16_t *) 0xbf600000)
-#define K573_JVS_INPUT *((volatile uint16_t *) 0xbf680000)
-#define K573_SECURITY_OUT *((volatile uint16_t *) 0xbf6a0000)
-
-#define K573_FLASH ((volatile uint16_t *) 0xbf000000)
-#define K573_IO_CHIP ((volatile uint16_t *) 0xbf400000)
-#define K573_IDE_CS0 ((volatile uint16_t *) 0xbf480000)
-#define K573_IDE_CS1 ((volatile uint16_t *) 0xbf4c0000)
-#define K573_RTC ((volatile uint16_t *) 0xbf620000)
-#define K573_IO_BOARD ((volatile uint16_t *) 0xbf640000)
-
-typedef enum _K573_IOChipRegister {
- IO_REG_OUT0 = 0x0,
- IO_REG_IN0 = 0x0,
- IO_REG_IN1_LOW = 0x2,
- IO_REG_IN1_HIGH = 0x3,
- IO_REG_IN2 = 0x4,
- IO_REG_IN3_LOW = 0x6,
- IO_REG_IN3_HIGH = 0x7
-} K573_IOChipRegister;
+#define K573_MISC_OUT _MMIO16(EXP1BASE | 0x400000)
+#define K573_DIP_CART_IN _MMIO16(EXP1BASE | 0x400004)
+#define K573_MISC_IN _MMIO16(EXP1BASE | 0x400006)
+#define K573_JAMMA_IN _MMIO16(EXP1BASE | 0x400008)
+#define K573_JVS_RX_DATA _MMIO16(EXP1BASE | 0x40000a)
+#define K573_EXT_IN_P1 _MMIO16(EXP1BASE | 0x40000c)
+#define K573_EXT_IN_P2 _MMIO16(EXP1BASE | 0x40000e)
+
+#define K573_BANK_SEL _MMIO16(EXP1BASE | 0x500000)
+#define K573_JVS_RESET _MMIO16(EXP1BASE | 0x520000)
+#define K573_IDE_RESET _MMIO16(EXP1BASE | 0x560000)
+#define K573_WATCHDOG _MMIO16(EXP1BASE | 0x5c0000)
+#define K573_EXT_OUT _MMIO16(EXP1BASE | 0x600000)
+#define K573_JVS_TX_DATA _MMIO16(EXP1BASE | 0x680000)
+#define K573_CART_OUT _MMIO16(EXP1BASE | 0x6a0000)
+
+#define K573_FLASH _ADDR16(EXP1BASE | 0x000000)
+#define K573_IDE_CS0 _ADDR16(EXP1BASE | 0x480000)
+#define K573_IDE_CS1 _ADDR16(EXP1BASE | 0x4c0000)
+#define K573_RTC _ADDR16(EXP1BASE | 0x620000)
+#define K573_IO_BOARD _ADDR16(EXP1BASE | 0x640000)
typedef enum _K573_IOBoardRegister {
- ANALOG_IO_REG_LIGHTS0 = 0x40,
- ANALOG_IO_REG_LIGHTS1 = 0x44,
- ANALOG_IO_REG_LIGHTS2 = 0x48,
- ANALOG_IO_REG_LIGHTS3 = 0x4c,
+ ANALOG_IO_REG_LIGHTS_A = 0x40,
+ ANALOG_IO_REG_LIGHTS_B = 0x44,
+ ANALOG_IO_REG_LIGHTS_C = 0x48,
+ ANALOG_IO_REG_LIGHTS_D = 0x4c,
- // The digital I/O board has a lot more registers than these, but there
- // seems to be no DIGITAL_IO_LIGHTS6 register. WTF
- DIGITAL_IO_REG_LIGHTS1 = 0x70,
- DIGITAL_IO_REG_LIGHTS0 = 0x71,
- DIGITAL_IO_REG_LIGHTS3 = 0x72,
- DIGITAL_IO_REG_LIGHTS7 = 0x73,
- DIGITAL_IO_REG_DS2401 = 0x77,
DIGITAL_IO_REG_FPGA_STATUS = 0x7b,
- DIGITAL_IO_REG_FPGA_UPLOAD = 0x7c,
- DIGITAL_IO_REG_LIGHTS4 = 0x7d,
- DIGITAL_IO_REG_LIGHTS5 = 0x7e,
- DIGITAL_IO_REG_LIGHTS2 = 0x7f,
+ DIGITAL_IO_REG_FPGA_PROGRAM = 0x7c,
+ DIGITAL_IO_REG_FPGA_DATA = 0x7d,
FISHBAIT_IO_REG_UNKNOWN = 0x08,
FISHBAIT_IO_REG_MOTOR = 0x40,
@@ -74,10 +66,22 @@ typedef enum _K573_RTCRegister {
RTC_REG_YEAR = 0x1fff
} K573_RTCRegister;
-/* Inputs and lights bitfields */
-
-typedef enum _K573_JAMMAInputs {
- // IO_REG_IN2 bits 0-15
+/* Bitfields */
+
+typedef enum _K573_MiscOutFlag {
+ MISC_OUT_ADC_MOSI = 1 << 0,
+ MISC_OUT_ADC_CS = 1 << 1,
+ MISC_OUT_ADC_SCK = 1 << 2,
+ MISC_OUT_COIN_COUNTER1 = 1 << 3,
+ MISC_OUT_COIN_COUNTER2 = 1 << 4,
+ MISC_OUT_AMP_ENABLE = 1 << 5,
+ MISC_OUT_CDDA_ENABLE = 1 << 6,
+ MISC_OUT_SPU_ENABLE = 1 << 7,
+ MISC_OUT_MCU_CLOCK = 1 << 8
+} K573_MiscOutFlag;
+
+typedef enum _K573_JAMMAInput {
+ // K573_JAMMA_IN bits 0-15
JAMMA_P2_LEFT = 1 << 0,
JAMMA_P2_RIGHT = 1 << 1,
JAMMA_P2_UP = 1 << 2,
@@ -95,108 +99,114 @@ typedef enum _K573_JAMMAInputs {
JAMMA_P1_BUTTON3 = 1 << 14,
JAMMA_P1_START = 1 << 15,
- // IO_REG_IN3_LOW bits 8-11
+ // K573_EXT_IN_P1 bits 8-11
JAMMA_P1_BUTTON4 = 1 << 16,
JAMMA_P1_BUTTON5 = 1 << 17,
JAMMA_TEST = 1 << 18,
JAMMA_P1_BUTTON6 = 1 << 19,
- // IO_REG_IN3_HIGH bits 8-11
+ // K573_EXT_IN_P2 bits 8-11
JAMMA_P2_BUTTON4 = 1 << 20,
JAMMA_P2_BUTTON5 = 1 << 21,
JAMMA_UNKNOWN = 1 << 22,
JAMMA_P2_BUTTON6 = 1 << 23,
- // IO_REG_IN1_HIGH bits 8-12
+ // K573_MISC_IN bits 8-12
JAMMA_COIN1 = 1 << 24,
JAMMA_COIN2 = 1 << 25,
JAMMA_PCMCIA1 = 1 << 26,
JAMMA_PCMCIA2 = 1 << 27,
JAMMA_SERVICE = 1 << 28,
- // IO_REG_IN1_LOW bits 0-2
+ // K573_DIP_CART_IN bits 0-2
JAMMA_DIP1 = 1 << 29,
JAMMA_DIP2 = 1 << 30,
JAMMA_DIP3 = 1 << 31
-} K573_JAMMAInputs;
+} K573_JAMMAInput;
typedef enum _K573_Light {
+ LIGHT_A0 = 1 << 0,
+ LIGHT_A1 = 1 << 1,
+ LIGHT_A2 = 1 << 2,
+ LIGHT_A3 = 1 << 3,
+ LIGHT_A4 = 1 << 4,
+ LIGHT_A5 = 1 << 5,
+ LIGHT_A6 = 1 << 6,
+ LIGHT_A7 = 1 << 7,
+ LIGHT_B0 = 1 << 8,
+ LIGHT_B1 = 1 << 9,
+ LIGHT_B2 = 1 << 10,
+ LIGHT_B3 = 1 << 11,
+ LIGHT_B4 = 1 << 12,
+ LIGHT_B5 = 1 << 13,
+ LIGHT_B6 = 1 << 14,
+ LIGHT_B7 = 1 << 15,
+ LIGHT_C0 = 1 << 16,
+ LIGHT_C1 = 1 << 17,
+ LIGHT_C2 = 1 << 18,
+ LIGHT_C3 = 1 << 19,
+ LIGHT_C4 = 1 << 20,
+ LIGHT_C5 = 1 << 21,
+ LIGHT_C6 = 1 << 22,
+ LIGHT_C7 = 1 << 23,
+ LIGHT_D0 = 1 << 24,
+ LIGHT_D1 = 1 << 25,
+ LIGHT_D2 = 1 << 26,
+ LIGHT_D3 = 1 << 27,
+
// Dance Dance Revolution (2-player)
- DDR_LIGHT_P1_UP = 1 << 0,
- DDR_LIGHT_P1_LEFT = 1 << 1,
- DDR_LIGHT_P1_RIGHT = 1 << 2,
- DDR_LIGHT_P1_DOWN = 1 << 3,
- DDR_LIGHT_P1_MUX_DATA = 1 << 4, // Used for stage commands
- DDR_LIGHT_P1_MUX_CLK = 1 << 7, // Used for stage commands
- DDR_LIGHT_P2_UP = 1 << 8,
- DDR_LIGHT_P2_LEFT = 1 << 9,
- DDR_LIGHT_P2_RIGHT = 1 << 10,
- DDR_LIGHT_P2_DOWN = 1 << 11,
- DDR_LIGHT_P2_MUX_DATA = 1 << 12, // Used for stage commands
- DDR_LIGHT_P2_MUX_CLK = 1 << 15, // Used for stage commands
- DDR_LIGHT_P1_BUTTONS = 1 << 17,
- DDR_LIGHT_P2_BUTTONS = 1 << 18,
- DDR_LIGHT_MARQUEE_BR = 1 << 20,
- DDR_LIGHT_MARQUEE_BL = 1 << 21,
- DDR_LIGHT_MARQUEE_TL = 1 << 22,
- DDR_LIGHT_MARQUEE_TR = 1 << 23,
- DDR_LIGHT_SPEAKER_DIGITAL = 1 << 28, // Speaker neon on digital I/O boards
- DDR_LIGHT_SPEARKER_ANALOG = 1 << 30, // Speaker neon on analog I/O boards
+ LIGHT_DDR_P1_UP = 1 << 0,
+ LIGHT_DDR_P1_DOWN = 1 << 1,
+ LIGHT_DDR_P1_LEFT = 1 << 2,
+ LIGHT_DDR_P1_RIGHT = 1 << 3,
+ LIGHT_DDR_P1_IO_DATA = 1 << 4,
+ LIGHT_DDR_P1_IO_CLK = 1 << 5,
+ LIGHT_DDR_P2_UP = 1 << 8,
+ LIGHT_DDR_P2_DOWN = 1 << 9,
+ LIGHT_DDR_P2_LEFT = 1 << 10,
+ LIGHT_DDR_P2_RIGHT = 1 << 11,
+ LIGHT_DDR_P2_IO_DATA = 1 << 12,
+ LIGHT_DDR_P2_IO_CLK = 1 << 13,
+ LIGHT_DDR_P1_BUTTONS = 1 << 18,
+ LIGHT_DDR_P2_BUTTONS = 1 << 19,
+ LIGHT_DDR_MARQUEE_BR = 1 << 20,
+ LIGHT_DDR_MARQUEE_TR = 1 << 21,
+ LIGHT_DDR_MARQUEE_BL = 1 << 22,
+ LIGHT_DDR_MARQUEE_TL = 1 << 23,
+ LIGHT_DDR_SPEAKER = 1 << 24,
// Dance Dance Revolution Solo
- DDRSOLO_LIGHT_EXTRA4 = 1 << 8,
- DDRSOLO_LIGHT_EXTRA2 = 1 << 9,
- DDRSOLO_LIGHT_EXTRA1 = 1 << 10,
- DDRSOLO_LIGHT_EXTRA3 = 1 << 11,
- DDRSOLO_LIGHT_SPEAKER = 1 << 16,
- DDRSOLO_LIGHT_BUTTONS = 1 << 20,
- DDRSOLO_LIGHT_BODY_CENTER = 1 << 21,
- DDRSOLO_LIGHT_BODY_RIGHT = 1 << 22,
- DDRSOLO_LIGHT_BODY_LEFT = 1 << 23,
+ LIGHT_SOLO_SPEAKER = 1 << 16,
+ LIGHT_SOLO_BUTTONS = 1 << 20,
+ LIGHT_SOLO_BODY_LEFT = 1 << 21,
+ LIGHT_SOLO_BODY_CENTER = 1 << 22,
+ LIGHT_SOLO_BODY_RIGHT = 1 << 23,
// DrumMania 1st Mix
- DM_LIGHT_HIHAT = 1 << 16,
- DM_LIGHT_HIGH_TOM = 1 << 17,
- DM_LIGHT_LOW_TOM = 1 << 18,
- DM_LIGHT_SNARE = 1 << 19,
- DM_LIGHT_CYMBAL = 1 << 20,
- DM_LIGHT_START_BUTTON = 1 << 21,
- DM_LIGHT_SELECT_BUTTON = 1 << 22,
- DM_LIGHT_NEON_BOTTOM = 1 << 27,
- DM_LIGHT_SPOT = 1 << 30,
- DM_LIGHT_NEON_TOP = 1 << 31,
+ LIGHT_DM_HIHAT = 1 << 16,
+ LIGHT_DM_SNARE = 1 << 17,
+ LIGHT_DM_HIGH_TOM = 1 << 18,
+ LIGHT_DM_LOW_TOM = 1 << 19,
+ LIGHT_DM_CYMBAL = 1 << 20,
+ LIGHT_DM_START_BUTTON = 1 << 22,
+ LIGHT_DM_SELECT_BUTTON = 1 << 23,
+ LIGHT_DM_SPOT = 1 << 24,
+ LIGHT_DM_NEON_TOP = 1 << 25,
+ LIGHT_DM_NEON_BOTTOM = 1 << 27,
// DrumMania 2nd Mix and later
- DM2_LIGHT_HIHAT = 1 << 0,
- DM2_LIGHT_HIGH_TOM = 1 << 1,
- DM2_LIGHT_LOW_TOM = 1 << 2,
- DM2_LIGHT_SNARE = 1 << 3,
- DM2_LIGHT_SPOT = 1 << 8,
- DM2_LIGHT_NEON_TOP = 1 << 9,
- DM2_LIGHT_NEON_BOTTOM = 1 << 11,
- DM2_LIGHT_CYMBAL = 1 << 12,
- DM2_LIGHT_START_BUTTON = 1 << 13,
- DM2_LIGHT_SELECT_BUTTON = 1 << 14
+ LIGHT_DM2_HIHAT = 1 << 0,
+ LIGHT_DM2_SNARE = 1 << 1,
+ LIGHT_DM2_HIGH_TOM = 1 << 2,
+ LIGHT_DM2_LOW_TOM = 1 << 3,
+ LIGHT_DM2_SPOT = 1 << 8,
+ LIGHT_DM2_NEON_BOTTOM = 1 << 9,
+ LIGHT_DM2_NEON_TOP = 1 << 10,
+ LIGHT_DM2_CYMBAL = 1 << 12,
+ LIGHT_DM2_START_BUTTON = 1 << 14,
+ LIGHT_DM2_SELECT_BUTTON = 1 << 15
} K573_Light;
-/* System information structures */
-
-typedef enum _K573_IOBoardType {
- IO_TYPE_ANALOG = 0, // Light control board (early Bemani)
- IO_TYPE_DIGITAL = 1, // Light control + MP3 playback board (late Bemani)
- IO_TYPE_FISHBAIT = 2, // Fishing reel controls interface (Fisherman's Bait)
- IO_TYPE_GUNMANIA = 3, // Gun control board (Gun Mania)
- IO_TYPE_KARAOKE = 4, // Karaoke I/O + video mux board (DDR Karaoke Mix)
- IO_TYPE_SERIAL = 5 // Serial port (debug?) board (Great Bishi Bashi Champ)
- // TODO: does PunchMania have its own board?
-} K573_IOBoardType;
-
-typedef enum _K573_DDRStageType {
- DDR_TYPE_NONE = 0,
- DDR_TYPE_2PLAYER = 1,
- DDR_TYPE_SOLO = 2
-} K573_DDRStageType;
-
/* Public API */
#define K573_RESET_WATCHDOG() { \
@@ -208,43 +218,27 @@ extern "C" {
#endif
/**
- * @brief Returns a bitfield containing the state of all JAMMA inputs and DIP
+ * @brief Returns the state of JAMMA inputs and DIP switches.
+ *
+ * @details Returns a bitfield containing the state of all JAMMA inputs and DIP
* switches. All bits are inverted as they represent the actual signal levels
* on the JAMMA pins (i.e. normally pulled up by resistors, shorted to ground
* when a button is pressed).
*
- * @return Inverted logical OR of K573_JAMMAInputs flags
+ * @return Binary OR of K573_JAMMAInputs flags
*/
uint32_t K573_GetJAMMAInputs(void);
/**
- * @brief Sets the 32 light outputs provided by the the analog and digital I/O
- * boards to match the provided bitfield. K573_SetBoardType(IO_TYPE_ANALOG) or
- * K573_SetBoardType(IO_TYPE_DIGITAL) must be called beforehand to set the I/O
- * board type.
- *
- * @param lights Non-inverted logical OR of K573_Light flags
- */
-void K573_SetLights(uint32_t lights);
-
-/**
- * @brief Sets the installed I/O board type. Currently only IO_TYPE_ANALOG and
- * IO_TYPE_DIGITAL are supported.
+ * @brief Sets light outputs on the analog I/O board.
*
- * @param type
- */
-void K573_SetBoardType(K573_IOBoardType type);
-
-/**
- * @brief Sends a command to the multiplexer PCB embedded into DDR stage units
- * (if the system is a DDR cabinet) by bitbanging it through the light outputs.
- * K573_SetBoardType(IO_TYPE_ANALOG) or K573_SetBoardType(IO_TYPE_DIGITAL) must
- * be called beforehand to set the I/O board type.
+ * @details Sets the 32 light outputs provided by the analog I/O board (if
+ * installed) to match the provided bitfield. No other I/O boards are currently
+ * supported.
*
- * @param value
- * @param length Number of bits to send (1-32)
+ * @param lights Binary OR of K573_Light flags
*/
-//void K573_DDRStageCommand(uint32_t value, uint32_t length);
+void K573_SetAnalogLights(uint32_t lights);
/**
* @brief Initializes the expansion port registers to enable System 573 I/O
diff --git a/examples/io/system573/main.c b/examples/io/system573/main.c
index 39ddb64..2878508 100644
--- a/examples/io/system573/main.c
+++ b/examples/io/system573/main.c
@@ -66,12 +66,7 @@
#include "k573io.h"
-const char *const IO_BOARD_TYPES[] = {
- "ANALOG",
- "DIGITAL"
-};
-
-#define btoi(x) ((((x) >> 4) & 0xf) * 10 + ((x) & 0xf))
+#define _btoi(x) ((((x) >> 4) & 0xf) * 10 + ((x) & 0xf))
/* Display/GPU context utilities */
@@ -137,30 +132,18 @@ void display(RenderContext *ctx) {
static RenderContext ctx;
-#define SHOW_STATUS(...) { FntPrint(-1, __VA_ARGS__); FntFlush(-1); display(&ctx); }
-#define SHOW_ERROR(...) { SHOW_STATUS(__VA_ARGS__); while (1) __asm__("nop"); }
-
int main(int argc, const char* argv[]) {
init_context(&ctx);
K573_Init();
const char *const version = (const char *const) GetSystemInfo(0x02);
- //if (strncmp(version, "Konami OS", 9))
- //SHOW_ERROR("ERROR: NOT RUNNING ON A SYSTEM 573!\n\n[%s]\n", version);
uint32_t counter = 0;
uint32_t inputs = K573_GetJAMMAInputs();
uint32_t last_inputs = 0xff;
uint32_t current_light = 0;
- // DIP switch 1 is used to determine if an analog or digital I/O board is
- // installed.
- K573_IOBoardType io_type = (inputs & JAMMA_DIP1)
- ? IO_TYPE_ANALOG
- : IO_TYPE_DIGITAL;
-
- K573_SetBoardType(io_type);
- K573_SetLights(1);
+ K573_SetAnalogLights(1);
while (1) {
inputs = K573_GetJAMMAInputs();
@@ -168,16 +151,14 @@ int main(int argc, const char* argv[]) {
FntPrint(-1, "COUNTER=%d\n", counter++);
FntPrint(-1, "\nJAMMA INPUTS:\n");
- FntPrint(-1, " IN2 =%016@\n", inputs & 0xffff);
- FntPrint(-1, " IN3_L=%04@\n", (inputs >> 16) & 0x0f);
- FntPrint(-1, " IN3_H=%04@\n", (inputs >> 20) & 0x0f);
- FntPrint(-1, " IN1_H=%05@\n", (inputs >> 24) & 0x1f);
+ FntPrint(-1, " MAIN=%016@\n", inputs & 0xffff);
+ FntPrint(-1, " EXT1=%04@\n", (inputs >> 16) & 0x0f);
+ FntPrint(-1, " EXT2=%04@\n", (inputs >> 20) & 0x0f);
+ FntPrint(-1, " COIN=%05@\n", (inputs >> 24) & 0x1f);
- FntPrint(-1, "\nCABINET LIGHTS:\n");
- FntPrint(-1, " BOARD=%s\n", IO_BOARD_TYPES[io_type]);
- FntPrint(-1, " LIGHT=%d\n", current_light);
- FntPrint(-1, "\n [DIP SW1] CHANGE BOARD TYPE\n");
- FntPrint(-1, "\n [TEST SW] CHANGE ACTIVE LIGHT\n");
+ FntPrint(-1, "\nANALOG IO LIGHT TEST:\n");
+ FntPrint(-1, " LAMP=%d\n", current_light);
+ FntPrint(-1, " PRESS [TEST] TO CHANGE LAMP\n");
// Request the current date/time from the RTC and display it.
K573_RTC[RTC_REG_CTRL] |= 0x40;
@@ -185,18 +166,17 @@ int main(int argc, const char* argv[]) {
FntPrint(
-1,
" %02d-%02d-%02d %02d:%02d:%02d\n",
- btoi(K573_RTC[RTC_REG_YEAR]),
- btoi(K573_RTC[RTC_REG_MONTH]),
- btoi(K573_RTC[RTC_REG_DAY_OF_MONTH] & 0x3f),
- btoi(K573_RTC[RTC_REG_HOURS]),
- btoi(K573_RTC[RTC_REG_MINUTES]),
- btoi(K573_RTC[RTC_REG_SECONDS] & 0x7f)
+ _btoi(K573_RTC[RTC_REG_YEAR]),
+ _btoi(K573_RTC[RTC_REG_MONTH]),
+ _btoi(K573_RTC[RTC_REG_DAY_OF_MONTH] & 0x3f),
+ _btoi(K573_RTC[RTC_REG_HOURS]),
+ _btoi(K573_RTC[RTC_REG_MINUTES]),
+ _btoi(K573_RTC[RTC_REG_SECONDS] & 0x7f)
);
FntPrint(-1, "\nSYSTEM:\n");
FntPrint(-1, " KERNEL=%s\n", version);
FntPrint(-1, " DIP SW=%03@\n", inputs >> 29);
- FntPrint(-1, " PCMCIA=%02@\n", (inputs >> 26) & 0x3);
FntFlush(-1);
display(&ctx);
@@ -208,26 +188,16 @@ int main(int argc, const char* argv[]) {
// Change the currently active light if the test button on the 573's
// front panel is pressed. DDR non-light outputs are skipped.
if (!(last_inputs & JAMMA_TEST) && (inputs & JAMMA_TEST)) {
- current_light++;
- if (
- (current_light == 4) || // DDR_LIGHT_P1_MUX_DATA
- (current_light == 7) || // DDR_LIGHT_P1_MUX_CLK
- (current_light == 12) || // DDR_LIGHT_P2_MUX_DATA
- (current_light == 15) // DDR_LIGHT_P2_MUX_CLK
- ) current_light++;
-
- current_light %= 32;
- K573_SetLights(1 << current_light);
- }
-
- // if DIP switch 1 is toggled, change the I/O board type.
- if ((last_inputs & JAMMA_DIP1) != (inputs & JAMMA_DIP1)) {
- io_type = (inputs & JAMMA_DIP1)
- ? IO_TYPE_ANALOG
- : IO_TYPE_DIGITAL;
-
- K573_SetBoardType(io_type);
- K573_SetLights(1 << current_light);
+ do {
+ current_light = (current_light + 1) % 28;
+ } while (
+ (current_light == 4) || // LIGHT_DDR_P1_IO_DATA
+ (current_light == 5) || // LIGHT_DDR_P1_IO_CLK
+ (current_light == 12) || // LIGHT_DDR_P2_IO_DATA
+ (current_light == 13) // LIGHT_DDR_P2_IO_CLK
+ );
+
+ K573_SetAnalogLights(1 << current_light);
}
last_inputs = inputs;
diff --git a/examples/mdec/strvideo/main.c b/examples/mdec/strvideo/main.c
index 842bbb8..28d39b2 100644
--- a/examples/mdec/strvideo/main.c
+++ b/examples/mdec/strvideo/main.c
@@ -31,10 +31,8 @@
* pipeline must also run in lockstep with each other to prevent frame
* corruption, hence several functions and flag variables are used to stall the
* main loop until a frame is available for decoding and the MDEC is ready.
- * Playback is stopped when a sector with the end-of-file flag set in the XA
- * subheader (added at the end of the file by most .STR encoders) is
- * encountered; in order to access the subheader, this example requests 2340
- * bytes of data for each sector (rather than the usual 2048) from the drive.
+ * Playback is stopped once the .STR header is no longer present in sectors
+ * read.
*
* Note that PSn00bSDK's bitstream decoding API only supports version 1 and 2
* bitstreams currently, so make sure your .STR files are encoded as v2 and not
@@ -42,6 +40,7 @@
*/
#include <stdint.h>
+#include <stdio.h>
#include <string.h>
#include <psxetc.h>
#include <psxapi.h>
@@ -85,15 +84,15 @@ void init_context(RenderContext *ctx) {
SetDefDispEnv(&(db->disp), 0, 0, SCREEN_XRES, SCREEN_YRES);
SetDefDrawEnv(&(db->draw), 0, SCREEN_YRES, SCREEN_XRES, SCREEN_YRES);
setRGB0(&(db->draw), BGCOLOR_R, BGCOLOR_G, BGCOLOR_B);
- db->draw.isbg = 1;
- db->draw.dtd = 1;
+ db->draw.isbg = 1;
+ db->draw.dtd = 1;
db = &(ctx->db[1]);
SetDefDispEnv(&(db->disp), 0, SCREEN_YRES, SCREEN_XRES, SCREEN_YRES);
SetDefDrawEnv(&(db->draw), 0, 0, SCREEN_XRES, SCREEN_YRES);
setRGB0(&(db->draw), BGCOLOR_R, BGCOLOR_G, BGCOLOR_B);
- db->draw.isbg = 1;
- db->draw.dtd = 1;
+ db->draw.isbg = 1;
+ db->draw.dtd = 1;
PutDrawEnv(&(db->draw));
//PutDispEnv(&(db->disp));
@@ -145,22 +144,6 @@ typedef struct {
uint32_t _reserved;
} STR_Header;
-// https://problemkaputt.de/psx-spx.htm#cdromxasubheaderfilechannelinterleave
-typedef struct {
- uint8_t file, channel;
- uint8_t submode, coding_info;
-} XA_Header;
-
-// https://problemkaputt.de/psx-spx.htm#cdromsectorencoding
-typedef struct {
- CdlLOC pos;
- XA_Header xa_header[2];
- STR_Header str_header;
- uint8_t data[2016];
- uint32_t edc;
- uint8_t ecc[276];
-} STR_Sector;
-
typedef struct {
uint16_t width, height;
uint32_t bs_data[0x2000]; // Bitstream data read from the disc
@@ -186,57 +169,52 @@ StreamContext str_ctx;
// read from the CD. Due to DMA limitations it can't be allocated on the stack
// (especially not in the interrupt callbacks' stack, whose size is very
// limited).
-STR_Sector sector_buffer;
+STR_Header sector_header;
void cd_sector_handler(void) {
- // Fetch the .STR header of the sector that has been read and check if the
- // end-of-file bit is set in the XA header.
- CdGetSector(&sector_buffer, sizeof(STR_Sector) / 4);
-
- if (
- (sector_buffer.xa_header[0].submode & (1 << 7)) ||
- (sector_buffer.xa_header[1].submode & (1 << 7))
- ) {
- CdControlF(CdlPause, 0);
+ StreamBuffer *frame = &str_ctx.frames[str_ctx.cur_frame];
+
+ // Fetch the .STR header of the sector that has been read and make sure it
+ // is valid. If not, assume the file has ended and set frame_ready as a
+ // signal for the main loop to stop playback.
+ CdGetSector(&sector_header, sizeof(STR_Header) / 4);
+
+ if (sector_header.magic != 0x0160) {
str_ctx.frame_ready = -1;
return;
}
- STR_Header *header = &sector_buffer.str_header;
- StreamBuffer *frame = &str_ctx.frames[str_ctx.cur_frame];
-
// Ignore any non-MDEC sectors that might be present in the stream.
- if (header->type != 0x8001)
+ if (sector_header.type != 0x8001)
return;
// If this sector is actually part of a new frame, validate the sectors
// that have been read so far and flip the bitstream data buffers.
- if (header->frame_id != str_ctx.frame_id) {
+ if (sector_header.frame_id != str_ctx.frame_id) {
// Do not set the ready flag if any sector has been missed.
if (str_ctx.sector_count)
str_ctx.dropped_frames++;
else
str_ctx.frame_ready = 1;
- str_ctx.frame_id = header->frame_id;
- str_ctx.sector_count = header->sector_count;
+ str_ctx.frame_id = sector_header.frame_id;
+ str_ctx.sector_count = sector_header.sector_count;
str_ctx.cur_frame ^= 1;
frame = &str_ctx.frames[str_ctx.cur_frame];
// Initialize the next frame. Dimensions must be rounded up to the
// nearest multiple of 16 as the MDEC operates on 16x16 pixel blocks.
- frame->width = (header->width + 15) & 0xfff0;
- frame->height = (header->height + 15) & 0xfff0;
+ frame->width = (sector_header.width + 15) & 0xfff0;
+ frame->height = (sector_header.height + 15) & 0xfff0;
}
// Append the payload contained in this sector to the current buffer.
- memcpy(
- &(frame->bs_data[2016 / 4 * header->sector_id]),
- sector_buffer.data,
- 2016
- );
str_ctx.sector_count--;
+ CdGetSector(
+ &(frame->bs_data[2016 / 4 * sector_header.sector_id]),
+ 2016 / 4
+ );
}
void mdec_dma_handler(void) {
@@ -262,7 +240,7 @@ void mdec_dma_handler(void) {
);
}
-void cd_event_handler(int event, uint8_t *payload) {
+void cd_event_handler(CdlIntrResult event, uint8_t *payload) {
// Ignore all events other than a sector being ready.
if (event != CdlDataReady)
return;
@@ -292,9 +270,8 @@ void init_stream(void) {
DecDCTvlcSize(0x8000);
DecDCTvlcCopyTable((DECDCTTAB *) 0x1f800000);
- str_ctx.dropped_frames = 0;
- str_ctx.cur_frame = 0;
- str_ctx.cur_slice = 0;
+ str_ctx.cur_frame = 0;
+ str_ctx.cur_slice = 0;
}
StreamBuffer *get_next_frame(void) {
@@ -310,14 +287,15 @@ StreamBuffer *get_next_frame(void) {
void start_stream(CdlFILE *file) {
str_ctx.frame_id = -1;
+ str_ctx.dropped_frames = 0;
str_ctx.sector_pending = 0;
str_ctx.frame_ready = 0;
CdSync(0, 0);
- // Configure the CD drive to read 2340-byte sectors at 2x speed and to
- // play any XA-ADPCM sectors that might be interleaved with the video data.
- uint8_t mode = CdlModeSize | CdlModeRT | CdlModeSpeed;
+ // Configure the CD drive to read at 2x speed and to play any XA-ADPCM
+ // sectors that might be interleaved with the video data.
+ uint8_t mode = CdlModeRT | CdlModeSpeed;
CdControl(CdlSetmode, (const uint8_t *) &mode, 0);
// Start reading in real-time mode (i.e. without retrying in case of read
@@ -369,6 +347,9 @@ int main(int argc, const char* argv[]) {
// ended, restart playback from the beginning.
StreamBuffer *frame = get_next_frame();
if (!frame) {
+ printf("End of file, looping...\n");
+ CdControlB(CdlPause, 0, 0);
+
start_stream(&file);
continue;
}
@@ -423,7 +404,7 @@ int main(int argc, const char* argv[]) {
int x_offset = (fb_clip->w - frame->width) / 2;
int y_offset = (fb_clip->h - frame->height) / 2;
- str_ctx.slice_pos.x = VRAM_X_COORD(fb_clip->x + x_offset);
+ str_ctx.slice_pos.x = fb_clip->x + VRAM_X_COORD(x_offset);
str_ctx.slice_pos.y = fb_clip->y + y_offset;
str_ctx.slice_pos.w = BLOCK_SIZE;
str_ctx.slice_pos.h = frame->height;
diff --git a/examples/sound/cdstream/main.c b/examples/sound/cdstream/main.c
index 636ef10..324abb2 100644
--- a/examples/sound/cdstream/main.c
+++ b/examples/sound/cdstream/main.c
@@ -308,8 +308,8 @@ void start_stream(void) {
for (int i = 0; i < NUM_CHANNELS; i++) {
SPU_CH_ADDR(i) = getSPUAddr(str_ctx.spu_addr + str_ctx.buffer_size * i);
SPU_CH_FREQ(i) = getSPUSampleRate(str_ctx.sample_rate);
- SPU_CH_ADSR1(i) = 0x80ff;
- SPU_CH_ADSR2(i) = 0x1fee;
+ SPU_CH_ADSR1(i) = 0x00ff;
+ SPU_CH_ADSR2(i) = 0x0000;
}
// Unmute the channels and route them for stereo output. You'll want to
diff --git a/examples/sound/cdstream/stream.vag b/examples/sound/cdstream/stream.vag
index a6faf74..aab82ba 100644
--- a/examples/sound/cdstream/stream.vag
+++ b/examples/sound/cdstream/stream.vag
Binary files differ
diff --git a/examples/sound/spustream/interleave.py b/examples/sound/spustream/interleave.py
index 4e68974..4f4f20f 100644
--- a/examples/sound/spustream/interleave.py
+++ b/examples/sound/spustream/interleave.py
@@ -8,7 +8,7 @@ from struct import Struct
from itertools import zip_longest
from argparse import ArgumentParser, FileType
-VAG_HEADER = Struct("> 4s I 4s 2I 12x 16s")
+VAG_HEADER = Struct("> 4s 4I 10x H 16s")
VAG_MAGIC = b"VAGp"
VAGI_MAGIC = b"VAGi"
VAG_VERSION = 0x20
@@ -17,6 +17,9 @@ CHUNK_ALIGN = 0x800
## Helpers
+def swap_endian(value, size):
+ return int.from_bytes(value.to_bytes(size, "big"), "little")
+
def align(data, size):
chunks = (len(data) + size - 1) // size
@@ -40,7 +43,7 @@ class VAGReader:
magic, _, _,
self.size,
self.sample_rate,
- self.name
+ _, _
) = VAG_HEADER.unpack(header)
if magic == VAGI_MAGIC:
@@ -116,9 +119,6 @@ def main():
size = input_files[0].size
sample_rate = input_files[0].sample_rate
- if (not args.raw) and (len(input_files) != 2):
- warn(RuntimeWarning("interleaved .VAG only supports stereo (2 input files)"))
-
for vag in input_files[1:]:
if vag.size != size:
warn(RuntimeWarning(f"{vag.file.name} has a different file size"))
@@ -135,9 +135,10 @@ def main():
header = VAG_HEADER.pack(
VAGI_MAGIC,
VAG_VERSION,
- args.buffer_size.to_bytes(4, "little"),
+ swap_endian(args.buffer_size, 4),
size,
sample_rate,
+ swap_endian(len(input_files), 2),
os.path.basename(_file.name).encode()[0:16]
)
diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c
index d240433..6bf2c71 100644
--- a/examples/sound/spustream/main.c
+++ b/examples/sound/spustream/main.c
@@ -54,8 +54,6 @@
extern const uint8_t stream_data[];
-#define NUM_CHANNELS 2
-
/* Display/GPU context utilities */
#define SCREEN_XRES 320
@@ -124,7 +122,8 @@ typedef struct {
uint32_t interleave; // Little-endian, size of each channel buffer
uint32_t size; // Big-endian, in bytes
uint32_t sample_rate; // Big-endian, in Hertz
- uint32_t _reserved[3];
+ uint16_t _reserved[5];
+ uint16_t channels; // Little-endian, if 0 the file is mono
char name[16];
} VAG_Header;
@@ -147,17 +146,12 @@ typedef struct {
#define DUMMY_BLOCK_ADDR 0x1000
#define BUFFER_START_ADDR 0x1010
-typedef enum {
- STATE_IDLE,
- STATE_BUFFERING
-} StreamState;
-
typedef struct {
const uint8_t *data;
- int buffer_size, num_chunks, sample_rate;
+ int buffer_size, num_chunks, sample_rate, channels;
volatile int next_chunk, spu_addr;
- volatile int8_t db_active, state;
+ volatile int8_t db_active, buffering;
} StreamContext;
static StreamContext str_ctx;
@@ -166,13 +160,13 @@ void spu_irq_handler(void) {
// Acknowledge the interrupt to ensure it can be triggered again. The only
// way to do this is actually to disable the interrupt entirely; we'll
// enable it again once the chunk is ready.
- SPU_CTRL &= 0xffbf;
+ SPU_CTRL &= ~(1 << 6);
- int chunk_size = str_ctx.buffer_size * NUM_CHANNELS;
+ int chunk_size = str_ctx.buffer_size * str_ctx.channels;
int chunk = (str_ctx.next_chunk + 1) % (uint32_t) str_ctx.num_chunks;
str_ctx.db_active ^= 1;
- str_ctx.state = STATE_BUFFERING;
+ str_ctx.buffering = 1;
str_ctx.next_chunk = chunk;
// Configure to SPU to trigger an IRQ once the chunk that is going to be
@@ -184,7 +178,7 @@ void spu_irq_handler(void) {
str_ctx.spu_addr = addr;
SPU_IRQ_ADDR = getSPUAddr(addr);
- for (int i = 0; i < NUM_CHANNELS; i++)
+ for (int i = 0; i < str_ctx.channels; i++)
SPU_CH_LOOP_ADDR(i) = getSPUAddr(addr + str_ctx.buffer_size * i);
// Start uploading the next chunk to the SPU.
@@ -194,9 +188,9 @@ void spu_irq_handler(void) {
void spu_dma_handler(void) {
// Re-enable the SPU IRQ once the new chunk has been fully uploaded.
- SPU_CTRL |= 0x0040;
+ SPU_CTRL |= 1 << 6;
- str_ctx.state = STATE_IDLE;
+ str_ctx.buffering = 0;
}
/* Helper functions */
@@ -228,6 +222,7 @@ void init_stream(const VAG_Header *vag) {
str_ctx.buffer_size = buf_size;
str_ctx.num_chunks = (SWAP_ENDIAN(vag->size) + buf_size - 1) / buf_size;
str_ctx.sample_rate = SWAP_ENDIAN(vag->sample_rate);
+ str_ctx.channels = vag->channels ? vag->channels : 1;
str_ctx.db_active = 1;
str_ctx.next_chunk = -1;
@@ -235,18 +230,22 @@ void init_stream(const VAG_Header *vag) {
// Ensure at least one chunk is in SPU RAM by invoking the IRQ handler
// manually and blocking until the chunk has loaded.
spu_irq_handler();
- while (str_ctx.state != STATE_IDLE)
+ while (str_ctx.buffering)
__asm__ volatile("");
}
void start_stream(void) {
- int bits = 0x00ffffff >> (24 - NUM_CHANNELS);
+ int bits = 0x00ffffff >> (24 - str_ctx.channels);
+
+ // Disable the IRQ as we're going to call spu_irq_handler() manually (due
+ // to finicky SPU timings).
+ SPU_CTRL &= ~(1 << 6);
- for (int i = 0; i < NUM_CHANNELS; i++) {
+ for (int i = 0; i < str_ctx.channels; i++) {
SPU_CH_ADDR(i) = getSPUAddr(str_ctx.spu_addr + str_ctx.buffer_size * i);
SPU_CH_FREQ(i) = getSPUSampleRate(str_ctx.sample_rate);
- SPU_CH_ADSR1(i) = 0x80ff;
- SPU_CH_ADSR2(i) = 0x1fee;
+ SPU_CH_ADSR1(i) = 0x00ff;
+ SPU_CH_ADSR2(i) = 0x0000;
}
// Unmute the channels and route them for stereo output. You'll want to
@@ -257,19 +256,19 @@ void start_stream(void) {
SPU_CH_VOL_L(1) = 0x0000;
SPU_CH_VOL_R(1) = 0x3fff;
- spu_irq_handler();
SpuSetKey(1, bits);
+ spu_irq_handler();
}
// This is basically a variant of reset_spu_channels() that only resets the
// channels used to play the stream, to (again) prevent them from triggering
// the SPU IRQ while the stream is paused.
void stop_stream(void) {
- int bits = 0x00ffffff >> (24 - NUM_CHANNELS);
+ int bits = 0x00ffffff >> (24 - str_ctx.channels);
SpuSetKey(0, bits);
- for (int i = 0; i < NUM_CHANNELS; i++)
+ for (int i = 0; i < str_ctx.channels; i++)
SPU_CH_ADDR(i) = getSPUAddr(DUMMY_BLOCK_ADDR);
SpuSetKey(1, bits);
@@ -300,7 +299,7 @@ int main(int argc, const char* argv[]) {
while (1) {
FntPrint(-1, "PLAYING SPU STREAM\n\n");
FntPrint(-1, "BUFFER: %d\n", str_ctx.db_active);
- FntPrint(-1, "STATUS: %s\n\n", str_ctx.state ? "BUFFERING" : "IDLE");
+ FntPrint(-1, "STATUS: %s\n\n", str_ctx.buffering ? "BUFFERING" : "IDLE");
FntPrint(-1, "POSITION: %d/%d\n", str_ctx.next_chunk, str_ctx.num_chunks);
FntPrint(-1, "SMP RATE: %5d HZ\n\n", (sample_rate * 44100) >> 12);
@@ -350,7 +349,7 @@ int main(int argc, const char* argv[]) {
// Only set the sample rate registers if necessary.
if (pad->btn != 0xffff) {
- for (int i = 0; i < NUM_CHANNELS; i++)
+ for (int i = 0; i < str_ctx.channels; i++)
SPU_CH_FREQ(i) = sample_rate;
}
diff --git a/examples/sound/spustream/stream.vag b/examples/sound/spustream/stream.vag
index e1cb4f4..d75408b 100644
--- a/examples/sound/spustream/stream.vag
+++ b/examples/sound/spustream/stream.vag
Binary files differ
diff --git a/examples/sound/vagsample/main.c b/examples/sound/vagsample/main.c
index 6a60c19..dffa4cc 100644
--- a/examples/sound/vagsample/main.c
+++ b/examples/sound/vagsample/main.c
@@ -149,8 +149,8 @@ void play_sample(int addr, int sample_rate) {
// dummy values that disable the ADSR envelope entirely).
SPU_CH_VOL_L(ch) = 0x3fff;
SPU_CH_VOL_R(ch) = 0x3fff;
- SPU_CH_ADSR1(ch) = 0x80ff;
- SPU_CH_ADSR2(ch) = 0x1fee;
+ SPU_CH_ADSR1(ch) = 0x00ff;
+ SPU_CH_ADSR2(ch) = 0x0000;
// Start the channel.
SpuSetKey(1, 1 << ch);
diff --git a/libpsn00b/cmake/flags.cmake b/libpsn00b/cmake/flags.cmake
index 1ca25c2..fdc479e 100644
--- a/libpsn00b/cmake/flags.cmake
+++ b/libpsn00b/cmake/flags.cmake
@@ -14,7 +14,6 @@ target_compile_options(
# Options common to all target types
-g
-Wa,--strip-local-absolute
- -O2
-ffreestanding
-fno-builtin
-nostdlib
@@ -29,7 +28,6 @@ target_compile_options(
-mabi=32
-mno-mt
-mno-llsc
- -mdivide-breaks
$<$<COMPILE_LANGUAGE:CXX>:
# Options common to all target types (C++)
-fno-exceptions
@@ -38,6 +36,14 @@ target_compile_options(
-fno-threadsafe-statics
-fno-use-cxa-atexit
>
+ $<IF:$<CONFIG:Debug>,
+ # Options for debug builds
+ -Og
+ -mdivide-breaks
+ ,
+ # Options for release builds
+ -O2
+ >
$<$<STREQUAL:$<UPPER_CASE:$<TARGET_PROPERTY:PSN00BSDK_TARGET_TYPE>>,EXECUTABLE_GPREL>:
# Options for executables with $gp-relative addressing
-G8
@@ -86,5 +92,9 @@ target_link_options(
target_compile_definitions(
psn00bsdk INTERFACE
PSN00BSDK=1
- $<$<CONFIG:Release>:NDEBUG=1>
+ $<IF:$<CONFIG:Debug>,
+ #NDEBUG=0
+ ,
+ NDEBUG=1
+ >
)
diff --git a/libpsn00b/cmake/internal_setup.cmake b/libpsn00b/cmake/internal_setup.cmake
index b21451e..9e7a4d3 100644
--- a/libpsn00b/cmake/internal_setup.cmake
+++ b/libpsn00b/cmake/internal_setup.cmake
@@ -173,6 +173,7 @@ function(psn00bsdk_add_executable name type)
#$<${_repl},${PSN00BSDK_SYMBOL_MAP_SUFFIX}>
${CMAKE_CURRENT_BINARY_DIR}/${name}${PSN00BSDK_EXECUTABLE_SUFFIX}
${CMAKE_CURRENT_BINARY_DIR}/${name}${PSN00BSDK_SYMBOL_MAP_SUFFIX}
+ #VERBATIM
)
endfunction()
@@ -200,6 +201,7 @@ function(psn00bsdk_add_library name type)
$<SHELL_PATH:${CMAKE_CURRENT_BINARY_DIR}/${name}${PSN00BSDK_SHARED_LIBRARY_SUFFIX}>
#BYPRODUCTS $<${_repl},${PSN00BSDK_SHARED_LIBRARY_SUFFIX}>
BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/${name}${PSN00BSDK_SHARED_LIBRARY_SUFFIX}
+ VERBATIM
)
else()
message(FATAL_ERROR "Invalid library type: ${type} (must be STATIC, OBJECT, SHARED or MODULE)")
@@ -209,8 +211,8 @@ endfunction()
## Linking helpers
function(psn00bsdk_target_incbin_a name type symbol_name size_name path section align)
- string(MAKE_C_IDENTIFIER ${symbol_name} _id)
- string(MAKE_C_IDENTIFIER ${size_name} _size)
+ string(MAKE_C_IDENTIFIER "${symbol_name}" _id)
+ string(MAKE_C_IDENTIFIER "${size_name}" _size)
cmake_path(ABSOLUTE_PATH path OUTPUT_VARIABLE _path)
string(SHA1 _hash "${name} ${_id}")
@@ -223,11 +225,12 @@ function(psn00bsdk_target_incbin_a name type symbol_name size_name path section
CONFIGURE
OUTPUT ${_asm_file}
CONTENT [[
-.section ${section}
+.section ${section}.${_id}, "aw"
.balign ${align}
.global ${_id}
.type ${_id}, @object
+.size ${_id}, (${_id}_end - ${_id})
${_id}:
.incbin "${_path}"
@@ -235,34 +238,32 @@ ${_id}:
${_id}_end:
.balign ${align}
+
+.section ${section}.${_size}, "aw"
.balign 4
.global ${_size}
.type ${_size}, @object
+.size ${_size}, 4
${_size}:
.int (${_id}_end - ${_id})
-
-.size ${_id}, (${_id}_end - ${_id})
-.size ${_size}, 4
]]
ESCAPE_QUOTES
NEWLINE_STYLE LF
)
target_sources(${name} ${type} ${_asm_file})
- set_source_files_properties(${_asm_file} PROPERTIES OBJECT_DEPENDS ${_path})
+ set_source_files_properties(${_asm_file} PROPERTIES OBJECT_DEPENDS "${_path}")
endfunction()
function(psn00bsdk_target_incbin name type symbol_name path)
- string(MAKE_C_IDENTIFIER ${symbol_name} _id)
-
psn00bsdk_target_incbin_a(
${name}
${type}
- ${_id}
- ${_id}_size
- ${path}
- .data.${_id}
+ "${symbol_name}"
+ "${symbol_name}_size"
+ "${path}"
+ .data
4
)
endfunction()
@@ -280,14 +281,22 @@ function(psn00bsdk_add_cd_image name image_name config_file)
cmake_path(HASH config_file _hash)
set(CD_IMAGE_NAME ${image_name})
- set(CD_CONFIG_FILE cd_image_${_hash}.xml)
- configure_file(${config_file} ${CD_CONFIG_FILE})
+ set(CD_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/cd_image_${_hash}.xml)
+ configure_file("${config_file}" ${CD_CONFIG_FILE})
+ add_custom_command(
+ OUTPUT ${CD_IMAGE_NAME}.bin ${CD_IMAGE_NAME}.cue
+ COMMAND
+ ${MKPSXISO} -y
+ -o ${CD_IMAGE_NAME}.bin -c ${CD_IMAGE_NAME}.cue ${CD_CONFIG_FILE}
+ COMMENT "Building CD image ${CD_IMAGE_NAME}"
+ VERBATIM
+ ${ARGN}
+ )
add_custom_target(
${name} ALL
- COMMAND ${MKPSXISO} -y ${CD_CONFIG_FILE}
- BYPRODUCTS ${image_name}.bin ${image_name}.cue
- COMMENT "Building CD image ${image_name}"
- ${ARGN}
+ DEPENDS
+ ${CMAKE_CURRENT_BINARY_DIR}/${CD_IMAGE_NAME}.bin
+ ${CMAKE_CURRENT_BINARY_DIR}/${CD_IMAGE_NAME}.cue
)
endfunction()
diff --git a/libpsn00b/include/assert.h b/libpsn00b/include/assert.h
index 12212af..1b2bda2 100644
--- a/libpsn00b/include/assert.h
+++ b/libpsn00b/include/assert.h
@@ -11,8 +11,16 @@
#include <stdio.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
void _assert_abort(const char *file, int line, const char *expr);
+#ifdef __cplusplus
+}
+#endif
+
#ifdef NDEBUG
#define assert(expr)
@@ -20,9 +28,8 @@ void _assert_abort(const char *file, int line, const char *expr);
#else
-#define assert(expr) { \
- if (!(expr)) _assert_abort(__FILE__, __LINE__, #expr); \
-}
+#define assert(expr) \
+ ((expr) ? ((void) 0) : _assert_abort(__FILE__, __LINE__, #expr))
#ifdef SDK_LIBRARY_NAME
#define _sdk_log(fmt, ...) printf(SDK_LIBRARY_NAME ": " fmt, ##__VA_ARGS__)
diff --git a/libpsn00b/include/dlfcn.h b/libpsn00b/include/dlfcn.h
index 3c5260d..6192430 100644
--- a/libpsn00b/include/dlfcn.h
+++ b/libpsn00b/include/dlfcn.h
@@ -24,7 +24,7 @@
* GCC will generate code to set $t9 appropriately.
*/
#define DL_PRE_CALL(func) \
- __asm__ volatile("move $t9, %0;" :: "r"(func) : "$t9");
+ __asm__ volatile("move $t9, %0;" :: "r"(func) : "$t9")
/* Structure and enum definitions */
@@ -37,15 +37,14 @@ typedef enum _DL_ResolveMode {
// Members of this struct should not be accessed directly in most cases, but
// they are intentionally exposed for easier expandability.
typedef struct _DLL {
- void *ptr;
- void *malloc_ptr;
+ void *ptr, *malloc_ptr;
size_t size;
const uint32_t *hash;
uint32_t *got;
Elf32_Sym *symtab;
const char *strtab;
- uint16_t symbol_count;
- uint16_t got_length;
+ uint16_t symbol_count, first_got_symbol;
+ uint16_t got_local_count, got_extern_count;
} DLL;
/* Public API */
diff --git a/libpsn00b/include/hwregs_a.inc b/libpsn00b/include/hwregs_a.inc
index ca38542..02c9a5a 100644
--- a/libpsn00b/include/hwregs_a.inc
+++ b/libpsn00b/include/hwregs_a.inc
@@ -3,7 +3,9 @@
## Constants
-.set IOBASE, 0x1f80
+.set IOBASE, 0xbf80
+.set EXP1BASE, 0xbf00
+
.set F_CPU, 33868800
.set F_GPU, 53222400
@@ -61,34 +63,32 @@
.set SPU_CURRENT_VOL_L, 0x1db8
.set SPU_CURRENT_VOL_R, 0x1dba
-.set SPU_VOICE_VOL_L, 0x00
-.set SPU_VOICE_VOL_R, 0x02
-.set SPU_VOICE_FREQ, 0x04
-.set SPU_VOICE_ADDR, 0x06
-.set SPU_VOICE_ADSR1, 0x08
-.set SPU_VOICE_ADSR2, 0x0a
-.set SPU_VOICE_LOOP, 0x0e
+.set SPU_CH_VOL_L, 0x00
+.set SPU_CH_VOL_R, 0x02
+.set SPU_CH_FREQ, 0x04
+.set SPU_CH_ADDR, 0x06
+.set SPU_CH_ADSR1, 0x08
+.set SPU_CH_ADSR2, 0x0a
+.set SPU_CH_LOOP_ADDR, 0x0e
## MDEC
.set MDEC0, 0x1820
.set MDEC1, 0x1824
-## SPI controller port
-
-.set JOY_TXRX, 0x1040
-.set JOY_STAT, 0x1044
-.set JOY_MODE, 0x1048
-.set JOY_CTRL, 0x104a
-.set JOY_BAUD, 0x104e
+## SPI and serial interfaces
-## Serial port
+.set SIO0_DATA, 0x1040
+.set SIO0_STAT, 0x1044
+.set SIO0_MODE, 0x1048
+.set SIO0_CTRL, 0x104a
+.set SIO0_BAUD, 0x104e
-.set SIO_TXRX, 0x1050
-.set SIO_STAT, 0x1054
-.set SIO_MODE, 0x1058
-.set SIO_CTRL, 0x105a
-.set SIO_BAUD, 0x105e
+.set SIO1_DATA, 0x1050
+.set SIO1_STAT, 0x1054
+.set SIO1_MODE, 0x1058
+.set SIO1_CTRL, 0x105a
+.set SIO1_BAUD, 0x105e
## IRQ controller
@@ -142,15 +142,15 @@
.set TIMER2_CTRL, 0x1124
.set TIMER2_RELOAD, 0x1128
-## Memory control
-
-.set EXP1_ADDR, 0x1000
-.set EXP2_ADDR, 0x1004
-.set EXP1_DELAY_SIZE, 0x1008
-.set EXP3_DELAY_SIZE, 0x100c
-.set BIOS_DELAY_SIZE, 0x1010
-.set SPU_DELAY_SIZE, 0x1014
-.set CD_DELAY_SIZE, 0x1018
-.set EXP2_DELAY_SIZE, 0x101c
-.set COM_DELAY_CFG, 0x1020
-.set RAM_SIZE_CFG, 0x1060
+## Memory/bus control
+
+.set BUS_EXP1_ADDR, 0x1000
+.set BUS_EXP2_ADDR, 0x1004
+.set BUS_EXP1_CFG, 0x1008
+.set BUS_EXP3_CFG, 0x100c
+.set BUS_BIOS_CFG, 0x1010
+.set BUS_SPU_CFG, 0x1014
+.set BUS_CD_CFG, 0x1018
+.set BUS_EXP2_CFG, 0x101c
+.set BUS_COM_DELAY, 0x1020
+.set BUS_RAM_SIZE, 0x1060
diff --git a/libpsn00b/include/hwregs_c.h b/libpsn00b/include/hwregs_c.h
index 0e21922..7015101 100644
--- a/libpsn00b/include/hwregs_c.h
+++ b/libpsn00b/include/hwregs_c.h
@@ -8,15 +8,20 @@
#include <stdint.h>
-#define _MMIO8(addr) *((volatile uint8_t *) (addr))
-#define _MMIO16(addr) *((volatile uint16_t *) (addr))
-#define _MMIO32(addr) *((volatile uint32_t *) (addr))
+#define _ADDR8(addr) ((volatile uint8_t *) (addr))
+#define _ADDR16(addr) ((volatile uint16_t *) (addr))
+#define _ADDR32(addr) ((volatile uint32_t *) (addr))
+#define _MMIO8(addr) (*_ADDR8(addr))
+#define _MMIO16(addr) (*_ADDR16(addr))
+#define _MMIO32(addr) (*_ADDR32(addr))
/* Constants */
#define IOBASE 0xbf800000
-#define F_CPU 33868800UL
-#define F_GPU 53222400UL
+#define EXP1BASE 0xbf000000
+
+#define F_CPU 33868800L
+#define F_GPU 53222400L
/* GPU */
@@ -82,24 +87,16 @@
#define MDEC0 _MMIO32(IOBASE | 0x1820)
#define MDEC1 _MMIO32(IOBASE | 0x1824)
-/* SPI controller port */
+/* SPI and serial interfaces */
-// IMPORTANT: even though JOY_TXRX is a 32-bit register, it should only be
+// IMPORTANT: even though SIO_DATA is a 32-bit register, it should only be
// accessed as 8-bit. Reading it as 16 or 32-bit works fine on real hardware,
// but leads to problems in some emulators.
-#define JOY_TXRX _MMIO8 (IOBASE | 0x1040)
-#define JOY_STAT _MMIO16(IOBASE | 0x1044)
-#define JOY_MODE _MMIO16(IOBASE | 0x1048)
-#define JOY_CTRL _MMIO16(IOBASE | 0x104a)
-#define JOY_BAUD _MMIO16(IOBASE | 0x104e)
-
-/* Serial port */
-
-#define SIO_TXRX _MMIO8 (IOBASE | 0x1050)
-#define SIO_STAT _MMIO16(IOBASE | 0x1054)
-#define SIO_MODE _MMIO16(IOBASE | 0x1058)
-#define SIO_CTRL _MMIO16(IOBASE | 0x105a)
-#define SIO_BAUD _MMIO16(IOBASE | 0x105e)
+#define SIO_DATA(N) _MMIO8 (IOBASE | 0x1040 + 16 * (N))
+#define SIO_STAT(N) _MMIO16(IOBASE | 0x1044 + 16 * (N))
+#define SIO_MODE(N) _MMIO16(IOBASE | 0x1048 + 16 * (N))
+#define SIO_CTRL(N) _MMIO16(IOBASE | 0x104a + 16 * (N))
+#define SIO_BAUD(N) _MMIO16(IOBASE | 0x104e + 16 * (N))
/* IRQ controller */
@@ -121,17 +118,17 @@
#define TIMER_CTRL(N) _MMIO32(IOBASE | 0x1104 + 16 * (N))
#define TIMER_RELOAD(N) _MMIO32(IOBASE | 0x1108 + 16 * (N))
-/* Memory control */
-
-#define EXP1_ADDR _MMIO32(IOBASE | 0x1000)
-#define EXP2_ADDR _MMIO32(IOBASE | 0x1004)
-#define EXP1_DELAY_SIZE _MMIO32(IOBASE | 0x1008)
-#define EXP3_DELAY_SIZE _MMIO32(IOBASE | 0x100c)
-#define BIOS_DELAY_SIZE _MMIO32(IOBASE | 0x1010)
-#define SPU_DELAY_SIZE _MMIO32(IOBASE | 0x1014)
-#define CD_DELAY_SIZE _MMIO32(IOBASE | 0x1018)
-#define EXP2_DELAY_SIZE _MMIO32(IOBASE | 0x101c)
-#define COM_DELAY_CFG _MMIO32(IOBASE | 0x1020)
-#define RAM_SIZE_CFG _MMIO32(IOBASE | 0x1060)
+/* Memory/bus control */
+
+#define BUS_EXP1_ADDR _MMIO32(IOBASE | 0x1000)
+#define BUS_EXP2_ADDR _MMIO32(IOBASE | 0x1004)
+#define BUS_EXP1_CFG _MMIO32(IOBASE | 0x1008)
+#define BUS_EXP3_CFG _MMIO32(IOBASE | 0x100c)
+#define BUS_BIOS_CFG _MMIO32(IOBASE | 0x1010)
+#define BUS_SPU_CFG _MMIO32(IOBASE | 0x1014)
+#define BUS_CD_CFG _MMIO32(IOBASE | 0x1018)
+#define BUS_EXP2_CFG _MMIO32(IOBASE | 0x101c)
+#define BUS_COM_DELAY _MMIO32(IOBASE | 0x1020)
+#define BUS_RAM_SIZE _MMIO32(IOBASE | 0x1060)
#endif
diff --git a/libpsn00b/include/psxapi.h b/libpsn00b/include/psxapi.h
index 5d1097e..7353ed2 100644
--- a/libpsn00b/include/psxapi.h
+++ b/libpsn00b/include/psxapi.h
@@ -8,6 +8,7 @@
#include <stdint.h>
#include <stddef.h>
+#include <hwregs_c.h>
/* Definitions */
@@ -143,6 +144,43 @@ typedef struct {
int _reserved;
} INT_RP;
+/* Fast interrupt disabling macros */
+
+// Clearing the IRQ_MASK register is faster than manipulating cop0r12, even
+// though it requires declaring a "hidden" local variable to save its state to;
+// it's also resilient to race conditions as there's no read-modify-write
+// operation during which an interrupt can occur. Note that interrupt flags in
+// the IRQ_STAT register will get set even if the respective enable bits in
+// IRQ_MASK are cleared, so doing this will properly defer IRQs rather than
+// dropping them.
+#define FastEnterCriticalSection() \
+ uint16_t __saved_irq_mask = IRQ_MASK; (IRQ_MASK = 0)
+#define FastExitCriticalSection() \
+ (IRQ_MASK = __saved_irq_mask)
+
+/*#define FastEnterCriticalSection() { \
+ uint32_t r0, r1; \
+ __asm__ volatile( \
+ "mfc0 %0, $12;" \
+ "li %1, -1026;" \
+ "and %1, %0;" \
+ "mtc0 %1, $12;" \
+ "nop;" \
+ : "=r"(r0), "=r"(r1) :: \
+ ); \
+}
+#define FastExitCriticalSection() { \
+ uint32_t r0; \
+ __asm__ volatile( \
+ "mfc0 %0, $12;" \
+ "nop;" \
+ "ori %0, 0x0401;" \
+ "mtc0 %0, $12;" \
+ "nop;" \
+ : "=r"(r0) :: \
+ ); \
+}*/
+
/* API */
#ifdef __cplusplus
@@ -179,9 +217,9 @@ int DelDev(const char *name);
void ListDev(void);
void AddDummyTty(void);
-void EnterCriticalSection(void);
+int EnterCriticalSection(void);
void ExitCriticalSection(void);
-void SwEnterCriticalSection(void);
+int SwEnterCriticalSection(void);
void SwExitCriticalSection(void);
void _InitCd(void);
diff --git a/libpsn00b/include/psxcd.h b/libpsn00b/include/psxcd.h
index 8150703..503bc83 100644
--- a/libpsn00b/include/psxcd.h
+++ b/libpsn00b/include/psxcd.h
@@ -1,6 +1,6 @@
/*
- * PSn00bSDK CD-ROM drive library
- * (C) 2019-2022 Lameguy64, spicyjpeg - MPL licensed
+ * PSn00bSDK CD-ROM library
+ * (C) 2020-2022 Lameguy64, spicyjpeg - MPL licensed
*/
/**
@@ -54,7 +54,9 @@ typedef enum _CdlCommand {
CdlTest = 0x19,
CdlGetID = 0x1a,
CdlReadS = 0x1b,
- CdlReset = 0x1c
+ CdlReset = 0x1c,
+ CdlGetQ = 0x1d,
+ CdlReadTOC = 0x1e
} CdlCommand;
typedef enum _CdlStatFlag {
@@ -90,6 +92,15 @@ typedef enum _CdlIntrResult {
CdlDiskError = 5
} CdlIntrResult;
+typedef enum _CdlRegionCode {
+ CdlRegionUnknown = 0,
+ CdlRegionSCEI = 1, // Japan
+ CdlRegionSCEA = 2, // North America
+ CdlRegionSCEE = 3, // Europe
+ CdlRegionSCEW = 4, // Wordwide (Net Yaroze)
+ CdlRegionDebug = 5 // DebuggingStation or test console
+} CdlRegionCode;
+
typedef enum _CdlIsoError {
CdlIsoOkay = 0,
CdlIsoSeekError = 1,
@@ -99,51 +110,48 @@ typedef enum _CdlIsoError {
} CdlIsoError;
/**
- * @brief Translates a BCD format value to decimal
+ * @brief Translates a BCD value to decimal.
*
- * @details Translates a specified value in BCD format (ie. 32/0x20 = 20) into
- * a decimal integer, as the CD-ROM controller returns integer values only in
- * BCD format.
+ * @details Translates the specified value in BCD format (in 0-99 range) into
+ * a decimal integer.
*/
-#define btoi(b) ((b)/16*10+(b)%16)
+#define btoi(b) (((b) / 16 * 10) + ((b) % 16))
/**
- * @brief Translates a decimal value to BCD
+ * @brief Translates a decimal value to BCD.
*
- * @details Translates a decimal integer into a BCD format value (ie.
- * 20 = 32/0x20), as the CD-ROM controller only accepts values in BCD format.
+ * @details Translates a decimal integer in 0-99 range into a BCD format value.
*/
-#define itob(i) ((i)/10*16+(i)%10)
+#define itob(i) (((i) / 10 * 16) | ((i) % 10))
/* Structure and type definitions */
/**
- * @brief CD-ROM positional coordinates
+ * @brief CD-ROM MSF positional coordinates.
+ *
+ * @details This structure is used to specify CD-ROM coordinates in
+ * minutes/seconds/frames format for some commands (see CdControl()). It can
+ * be converted from/to a logical logical sector number using CdIntToPos() and
+ * CdPosToInt() respectively.
*
- * @details This structure is used to specify CD-ROM positional coordinates for
- * CdlSetloc, CdlReadN and CdlReadS CD-ROM commands. Use CdIntToPos() to set
- * parameters from a logical sector number.
+ * NOTE: the minute, second and sector fields are in BCD format. The track
+ * field is only returned by CdGetToc() and otherwise ignored by all commands.
*
- * @see CdIntToPos(), CdControl()
+ * @see CdIntToPos(), CdPosToInt(), CdControl()
*/
typedef struct _CdlLOC {
uint8_t minute; // Minutes (BCD)
uint8_t second; // Seconds (BCD)
uint8_t sector; // Sector or frame (BCD)
- uint8_t track; // Track number (not used)
+ uint8_t track; // Track number
} CdlLOC;
/**
- * @brief CD-ROM attenuation parameters
- *
- * @details This structure specifies parameters for the CD-ROM attenuation.
- * Values must be of range 0 to 127.
+ * @brief CD-ROM volume mixing matrix.
*
- * The CD-ROM attenuation can be used to set the CD-ROM audio output to mono
- * (0x40, 0x40, 0x40, 0x40) or reversed stereo (0x00, 0x80, 0x00, 0x80). It can
- * also be used to play one of two stereo channels to both speakers.
- *
- * The CD-ROM attenuation affects CD-DA and CD-XA audio.
+ * @details This structure is used to change the CD-ROM drive's volume levels
+ * using CdMix(). Each field represents a volume level as a value in 0-255
+ * range, with 128 being 100% and values above 128 distorting the output.
*
* @see CdMix()
*/
@@ -155,27 +163,73 @@ typedef struct _CdlATV {
} CdlATV;
/**
- * @brief File entry structure
+ * @brief File entry structure.
*
- * @details Used to store basic information of a file such as logical block
- * location and size. Currently, CdSearchFile() is the only function that uses
- * this struct but it will be used in directory listing functions that may be
- * implemented in the future.
+ * @details This structure is used to store basic metadata of a file such as
+ * its position and size. Currently, CdSearchFile() is the only function that
+ * uses this structure.
*
* @see CdSearchFile()
*/
typedef struct _CdlFILE {
- CdlLOC pos; // CD-ROM position coordinates of file
- uint32_t size; // Size of file in bytes
- char name[16]; // File name
+ CdlLOC pos; // CD-ROM position coordinates of file
+ int size; // Size of file in bytes
+ char name[16]; // File name
} CdlFILE;
/**
- * @brief Structure used to set CD-ROM XA filter
+ * @brief Current logical location information structure.
+ *
+ * @details This structure is returned by the CdlGetlocL command and contains
+ * information about the last data sector read by the drive head, including its
+ * location as well as mode and XA attributes if any.
+ *
+ * CdlGetlocL can only be issued while reading data sectors, as CD-DA data has
+ * no headers. Use CdlGetlocP instead to obtain the current drive position when
+ * playing CD-DA.
+ *
+ * @see CdControl()
+ */
+typedef struct _CdlLOCINFOL {
+ uint8_t minute; // Minutes (BCD)
+ uint8_t second; // Seconds (BCD)
+ uint8_t sector; // Sector or frame (BCD)
+ uint8_t mode; // Sector mode (usually 2)
+ uint8_t file; // XA file number (usually 0 or 1)
+ uint8_t chan; // XA channel number (0-31)
+ uint8_t submode; // XA submode
+ uint8_t coding_info; // XA coding information (ADPCM sectors only)
+} CdlLOCINFOL;
+
+/**
+ * @brief Current physical location information structure.
+ *
+ * @details This structure is returned by the CdlGetlocP command and contains
+ * information about the current location of the drive head relative to the
+ * entire CD as well as to the beginning of the track being played or read.
+ *
+ * This information is obtained by reading subchannel Q, so CdlGetlocP works on
+ * both data and CD-DA tracks (albeit with slightly lower precision on data
+ * tracks).
+ *
+ * @see CdControl()
+ */
+typedef struct _CdlLOCINFOP {
+ uint8_t track; // Track number (BCD)
+ uint8_t index; // Index number (BCD, usually 1)
+ uint8_t track_minute; // Minutes relative to beginning of track (BCD)
+ uint8_t track_second; // Seconds relative to beginning of track (BCD)
+ uint8_t track_sector; // Sector or frame relative to beginning of track (BCD)
+ uint8_t minute; // Minutes (BCD)
+ uint8_t second; // Seconds (BCD)
+ uint8_t sector; // Sector or frame (BCD)
+} CdlLOCINFOP;
+
+/**
+ * @brief CD-ROM XA filter structure.
*
- * @details This structure is used to specify stream filter parameters for
- * CD-ROM XA audio streaming using the CdlSetfilter command. This only affects
- * CD-ROM XA audio streaming.
+ * @details This structure is used with the CdlSetfilter command to specify
+ * sector filter parameters for XA-ADPCM audio playback.
*
* CD-ROM XA audio is normally comprised of up to 8 or more ADPCM compressed
* audio streams interleaved into one continuous stream of data. The data
@@ -191,13 +245,13 @@ typedef struct _CdlFILE {
* @see CdControl()
*/
typedef struct _CdlFILTER {
- uint8_t file; // File number to fetch (usually 1)
- uint8_t chan; // Channel number (0 through 7)
- uint16_t pad; // Padding
+ uint8_t file; // XA file number (usually 1)
+ uint8_t chan; // XA channel number (0-31)
+ uint16_t pad;
} CdlFILTER;
/**
- * @brief CD-ROM directory query context handle
+ * @brief CD-ROM directory query context handle.
*
* @details Used to store a directory context created by CdOpenDir(). An open
* context can then be used with CdReadDir() and closed with CdCloseDir().
@@ -206,7 +260,7 @@ typedef struct _CdlFILTER {
*/
typedef void *CdlDIR;
-typedef void (*CdlCB)(int, uint8_t *);
+typedef void (*CdlCB)(CdlIntrResult, uint8_t *);
/* Public API */
@@ -215,190 +269,230 @@ extern "C" {
#endif
/**
- * @brief Initializes the CD-ROM library
+ * @brief Initializes the CD-ROM library.
*
* @details Initializes the CD-ROM subsystem which includes hooking the
* required IRQ handler, sets up internal variables of the CD-ROM library and
* attempts to initialize the CD-ROM controller. The mode parameter does
* nothing but may be used in future updates of this library.
*
- * This function must be called after ResetGraph and before any other CD-ROM
- * library function that interfaces with the CD-ROM controller. This function
- * may not be called twice as it may cause instability or would just crash.
+ * This function shall only be called once after ResetGraph() or
+ * ResetCallback(), and shall not be called on systems which are known not to
+ * be equipped with a CD drive such as most PS1-based arcade boards.
*
- * @return Always 1. May change in the future.
+ * @return 1 if the drive is ready or 0 if an error occurred
*/
int CdInit(void);
/**
- * @brief Translates a logical sector number to CD-ROM positional coordinates
+ * @brief Translates an LBA to MSF coordinates.
*
- * @details This function translates the logical sector number from i to CD-ROM
- * positional coordinates stored to a CdlLOC struct specified by p. The
- * translation takes the lead-in offset into account so the first logical
- * sector begins at 0 and the result will be offset by 150 sectors.
+ * @details Translates the provided logical sector number to CD-ROM positional
+ * coordinates in minutes/seconds/frames format, which are stored into the
+ * provided CdlLOC structure. The translation takes the lead-in offset into
+ * account, so LBA 0 is correctly translated to 00:02:00 rather than 00:00:00.
*
- * @param i Logical sector number
+ * @param i Logical sector number minus the 150-sector lead-in
* @param p Pointer to a CdlLOC structure
- * @return Pointer to the specified CdlLOC struct plus 150 sectors.
+ * @return Pointer to the specified CdlLOC structure
*/
CdlLOC* CdIntToPos(int i, CdlLOC *p);
/**
- * @brief Translates CD-ROM positional coordinates to a logical sector number
+ * @brief Translates MSF coordinates to an LBA.
*
- * @details Translates the CD-ROM position parameters from a CdlLOC struct
- * specified by p to a logical sector number. The translation takes the lead-in
- * offset of 150 sectors into account so the logical sector number returned
- * would begin at zero.
+ * @details Translates the CD-ROM positional coordinates in
+ * minutes/seconds/frames format from the provided CdlLOC structure to a
+ * logical sector number. The translation takes the lead-in offset into account
+ * so 00:02:00 is correctly translated to LBA 0 rather than 150.
*
- * @param p Pointer to a CdlLOC struct
- * @return Logical sector number minus the 150 sector lead-in.
+ * @param p Pointer to a CdlLOC structure
+ * @return Logical sector number minus the 150-sector lead-in
*/
int CdPosToInt(const CdlLOC *p);
/**
- * @brief Gets CD-ROM TOC information
+ * @brief Issues a command to the CD-ROM controller.
*
- * @details Retrieves the track entries from a CD's table of contents (TOC). The
- * function can return up to 99 track entries, which is the maximum number of
- * audio tracks the CD standard supports.
+ * @details Sends a CD-ROM command specified by com to the CD-ROM controller,
+ * waits for an acknowledge interrupt (very fast) then returns. It will also
+ * issue parameters from param to the CD-ROM controller if the command accepts
+ * parameters. Any response from the controller is stored into the provided
+ * buffer asynchronously.
+ *
+ * Some commands (marked as blocking in the table below) will keep running in
+ * the background after being acknowledged. Use CdSync() to wait for these
+ * commands to finish, or CdSyncCallback() to register a callback to be
+ * executed once the drive is idle.
+ *
+ * This function requires interrupts to be enabled and cannot be used in a
+ * critical section or IRQ callback. Use CdControlF() in callbacks instead.
+ *
+ * The following commands are available:
+ *
+ * | Command | Value | Parameter | Blocking | Description |
+ * | :------------ | ----: | :--------- | :------- | :---------------------------------------------------------------------------------------------------------------- |
+ * | CdlNop | 0x01 | | No | Updates the current CD-ROM status and resets the CdlStatShellOpen flag, without doing anything else. |
+ * | CdlSetloc | 0x02 | CdlLOC | No | Sets the seek target location, but does not seek. Actual seeking begins upon issuing a seek or read command. |
+ * | CdlPlay | 0x03 | (uint8_t) | No | Begins CD-DA playback. Parameter specifies an optional track number to play (some emulators do not support it). |
+ * | CdlForward | 0x04 | | No | Starts fast-forwarding (CD-DA only). Issue CdlPlay to stop fast forwarding. |
+ * | CdlBackward | 0x05 | | No | Starts rewinding (CD-DA only). Issue CdlPlay to stop rewinding. |
+ * | CdlReadN | 0x06 | (CdlLOC) | No | Begins reading data sectors with automatic retry. Used in conjunction with CdReadyCallback(). |
+ * | CdlStandby | 0x07 | | Yes | Starts the spindle motor if it was previously stopped. |
+ * | CdlStop | 0x08 | | Yes | Stops playback or data reading and shuts down the spindle motor. |
+ * | CdlPause | 0x09 | | Yes | Stops playback or data reading without stopping the spindle motor. |
+ * | CdlInit | 0x0a | | Yes | Initializes the CD-ROM controller and aborts any ongoing command. |
+ * | CdlMute | 0x0b | | No | Mutes the drive's audio output (both CD-DA and XA). |
+ * | CdlDemute | 0x0c | | No | Unmutes the drive's audio output (both CD-DA and XA). |
+ * | CdlSetfilter | 0x0d | CdlFILTER | No | Configures the XA ADPCM sector filter. |
+ * | CdlSetmode | 0x0e | uint8_t | No | Sets the CD-ROM mode. |
+ * | CdlGetparam | 0x0f | | No | Returns current CD-ROM mode and file/channel filter settings. |
+ * | CdlGetlocL | 0x10 | | No | Returns current logical CD position, mode and XA filter parameters. |
+ * | CdlGetlocP | 0x11 | | No | Returns current physical CD position (using SubQ location data). |
+ * | CdlSetsession | 0x12 | uint8_t | Yes | Attempts to seek to the specified session on a multi-session disc. |
+ * | CdlGetTN | 0x13 | | No | Returns the number of tracks on the disc. |
+ * | CdlGetTD | 0x14 | uint8_t | No | Returns the starting location of the specified track number. |
+ * | CdlSeekL | 0x15 | (CdlLOC) | Yes | Logical seek (using data sector headers) to target position, set by last CdlSetloc command. |
+ * | CdlSeekP | 0x16 | (CdlLOC) | Yes | Physical seek (using subchannel Q) to target position, set by last CdlSetloc command. |
+ * | CdlTest | 0x19 | (varies) | Yes | Executes a test subcommand (see nocash documentation). Shall be issued using CdCommand() rather than CdControl(). |
+ * | CdlGetID | 0x1a | | Yes | Identifies the disc type and returns its license string if any. |
+ * | CdlReadS | 0x1b | (CdlLOC) | No | Begins reading data sectors in real-time (without retry) mode. Intended for playing XA ADPCM or .STR files. |
+ * | CdlReset | 0x1c | | No | Resets the CD-ROM controller (similar behavior to manually opening and closing the door). |
+ * | CdlGetQ | 0x1d | uint8_t[2] | Yes | Reads up to 10 raw bytes of subchannel Q data directly from the table of contents. |
+ * | CdlReadTOC | 0x1e | | Yes | Forces reinitialization of the disc's table of contents. |
+ *
+ * Most commands return the current CD-ROM status as result (which is
+ * automatically saved by the library and can be retrieved at any time using
+ * CdStatus()). The following commands also return additional data:
+ *
+ * | Command | Return values |
+ * | :---------- | :-------------------------------------- |
+ * | CdlGetparam | uint8_t status, mode, _pad, file, chan |
+ * | CdlGetlocL | CdlLOCINFOL info |
+ * | CdlGetlocP | CdlLOCINFOP info |
+ * | CdlGetTN | uint8_t status, first_track, last_track |
+ * | CdlGetTD | uint8_t status, minutes, seconds |
+ *
+ * NOTE: Values are in BCD format. For some commands (CdlReadN, CdlReadS,
+ * CdlSeekL, CdlSeekP), if a CdlLOC parameter is passed, it will be sent to the
+ * controller as a separate CdlSetloc command.
+ *
+ * @param cmd
+ * @param param Pointer to command parameters
+ * @param result Optional pointer to buffer to store result into
+ * @return 1 if the command was issued successfully, 0 if a previously issued
+ * command has not yet finished processing or -1 if a parameter is required but
+ * was not specified
+ *
+ * @see CdSync(), CdControlF(), CdCommand()
+ */
+int CdControl(CdlCommand cmd, const void *param, uint8_t *result);
+
+/**
+ * @brief Issues a command to the CD-ROM controller and waits for it to
+ * complete.
*
- * This function only retrieve the minutes and seconds of an audio track's
- * position as the CD-ROM controller only returns the minutes and seconds of a
- * track, which may result in the end of the previous track being played
- * instead of the intended track to be played. This can be remedied by having a
- * 2 second pregap on each audio track on your disc.
+ * @details Equivalent to CdControl() followed by CdSync(). If a blocking
+ * command is issued, this function blocks until said command has completed.
*
- * @param toc Pointer to an array of CdlLOC entries
- * @return Number of tracks on the disc, zero on error.
+ * This function requires interrupts to be enabled and cannot be used in a
+ * critical section or IRQ callback. Use CdControlF() in callbacks instead.
*
- * @see CdControl()
+ * @param cmd
+ * @param param Pointer to command parameters
+ * @param result Optional pointer to buffer to store result into
+ * @return 1 if the command was issued successfully, 0 if a previously issued
+ * command has not yet finished processing or -1 if a parameter is required but
+ * was not specified
+ *
+ * @see CdControl(), CdControlF()
*/
-int CdGetToc(CdlLOC *toc);
+int CdControlB(CdlCommand cmd, const void *param, uint8_t *result);
/**
- * @brief Issues a control command to the CD-ROM controller
+ * @brief Issues a command to the CD-ROM controller (asynchronous).
*
- * @details Sends a CD-ROM command specified by com to the CD-ROM controller,
- * waits for an acknowledge interrupt (very fast) then returns. It will also
- * issue parameters from param to the CD-ROM controller if the command accepts
- * parameters. Response data from the CD-ROM controller is stored to result on
- * commands that produce response data.
- *
- * Because this function waits for an acknowledge interrupt from the CD-ROM
- * controller, this function should not be used in a callback. Instead, use
- * CdControlF().
- *
- * Commands that are blocking require the use of CdSync() to wait for the
- * command to fully complete.
- *
- * CD-ROM control commands:
- *
- * | Command | Value | Parameter | Blocking | Description |
- * | :------------ | ----: | :-------- | :------- | :----------------------------------------------------------------------------------------------------------------- |
- * | CdlNop | 0x01 | | No | Also known as Getstat. Normally used to query the CD-ROM status, which is retrieved using CdStatus(). |
- * | CdlSetloc | 0x02 | CdlLOC | No | Sets the seek target location, but does not seek. Actual seeking begins upon issuing a seek or read command. |
- * | CdlPlay | 0x03 | uint8_t | No | Begins CD Audio playback. Parameter specifies an optional track number to play (some emulators do not support it). |
- * | CdlForward | 0x04 | | No | Fast forward (CD Audio only), issue CdlPlay to stop fast forward. |
- * | CdlBackward | 0x05 | | No | Rewind (CD Audio only), issue CdlPlay to stop rewind. |
- * | CdlReadN | 0x06 | CdlLOC | No | Begin reading data sectors. Used in conjunction with CdReadCallback(). |
- * | CdlStandby | 0x07 | | Yes | Also known as MotorOn, starts CD motor and remains idle. |
- * | CdlStop | 0x08 | | Yes | Stops playback and the disc itself. |
- * | CdlPause | 0x09 | | Yes | Stops playback or data reading, but leaves the disc on standby. |
- * | CdlInit | 0x0A | | Yes | Initialize the CD-ROM controller. |
- * | CdlMute | 0x0B | | No | Mutes CD audio (both DA and XA). |
- * | CdlDemute | 0x0C | | No | Unmutes CD audio (both DA and XA). |
- * | CdlSetfilter | 0x0D | CdlFILTER | No | Set XA audio filter. |
- * | CdlSetmode | 0x0E | uint8_t | No | Set CD-ROM mode. |
- * | CdlGetparam | 0x0F | | No | Returns current CD-ROM mode and file/channel filter settings. |
- * | CdlGetlocL | 0x10 | | No | Returns current logical CD position, mode and XA filter parameters. |
- * | CdlGetlocP | 0x11 | | No | Returns current physical CD position (using SubQ location data). |
- * | CdlSetsession | 0x12 | uint8_t | Yes | Seek to specified session on a multi-session disc. |
- * | CdlGetTN | 0x13 | | No | Get CD-ROM track count. |
- * | CdlGetTD | 0x14 | uint8_t | No | Get specified track position. |
- * | CdlSeekL | 0x15 | | Yes | Logical seek to target position, set by last CdlSetloc command. |
- * | CdlSeekP | 0x16 | | Yes | Physical seek to target position, set by last CdlSetloc command. |
- * | CdlTest | 0x19 | (varies) | Yes | Special test command not disclosed to official developers (see nocash documents for more info). |
- * | CdlReadS | 0x1B | CdlLOC | No | Begin reading data sectors without pausing for error correction. |
- *
- * CD-ROM return values:
- *
- * | Command | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
- * | :---------- | :---- | :---- | :----- | :--- | :------ | :------ | :--- | :----- |
- * | CdlGetparam | stat | mode | 0 | file | channel | | | |
- * | CdlGetlocL | amin | asec | aframe | mode | file | channel | sm | ci |
- * | CdlGetlocP | track | index | min | sec | frame | amin | asec | aframe |
- * | CdlGetTN | stat | first | last | | | | | |
- * | CdlGetTD | stat | min | sec | | | | | |
- *
- * NOTE: Values are in BCD format.
- *
- * @param com Command value
- * @param param Command parameters
- * @param result Pointer of buffer to store result
- * @return 1 if the command was issued successfully. Otherwise 0 if a
- * previously issued command has not yet finished processing.
- *
- * @see CdSync(), CdControlF()
- */
-int CdControl(uint8_t com, const void *param, uint8_t *result);
-
-/**
- * @brief Issues a CD-ROM command to the CD-ROM controller (blocking)
- *
- * @details This function works just like CdControl(), but blocks on blocking
- * commands until said blocking command has completed.
- *
- * Because this function waits for an acknowledge interrupt from the CD-ROM
- * controller, this function should not be used in a callback. Use CdControlF()
- * instead.
- *
- * @param com Command value
- * @param param Command parameters
- * @param result Pointer of buffer to store result
- * @return 1 if the command was issued successfully. Otherwise 0 if a
- * previously issued command has not yet finished processing.
+ * @details This function works similarly to CdControl() but does not block for
+ * the acknowledge interrupt from the CD-ROM controller, making it suitable for
+ * usage in an IRQ handler or callback function. Use CdSync() (outside of a
+ * callback) to wait for a command to finish, or CdSyncCallback() to register a
+ * callback to be executed once the drive is idle.
*
- * @see CdControl(), CdControlF()
+ * A maximum of two commands can be issued at once and only the first command
+ * can have parameters, as the parameter buffer is not cleared until the last
+ * command is acknowledged by the controller.
+ *
+ * NOTE: as with CdControl(), some commands (CdlReadN, CdlReadS, CdlSeekL,
+ * CdlSeekP) are sent as two separate commands if a CdlLOC parameter is passed.
+ * In some cases this may overflow the controller's two-command buffer.
+ *
+ * @param cmd
+ * @param param Pointer to command parameters
+ * @return -1 if a parameter is required but was not specified, otherwise 1
+ * (even if sending the command failed)
+ *
+ * @see CdControl(), CdCommand()
*/
-int CdControlB(uint8_t com, const void *param, uint8_t *result);
+int CdControlF(CdlCommand cmd, const void *param);
/**
- * @brief Issues a CD-ROM command to the CD-ROM controller (does not block)
+ * @brief Issues a custom packet to the CD-ROM controller.
*
- * @details This function works more or less the same as CdControl() but it
- * does not block even for the acknowledge interrupt from the CD-ROM
- * controller. Since this function is non-blocking it can be used in a callback
- * function.
+ * @details This is a more advanced variant of CdControl() that allows sending
+ * commands with an arbitrary number of parameters, such as CdlTest commands,
+ * and does not issue any additional CdlSetloc commands automatically. The
+ * number of parameter bytes must be specified manually.
*
- * When using this function in a callback, a maximum of two commands can be
- * issued at once and only the first command can have parameters. This is
- * because the CD-ROM controller can only queue up to two commands and the
- * parameter FIFO is not cleared until the last command is acknowledged. But
- * waiting for acknowledgment in a callback is not possible.
+ * As with CdControl(), this function waits for the drive to acknowledge the
+ * command. Any response from the controller is stored into the provided buffer
+ * asynchronously.
*
- * @param com Command value
- * @param param Command parameters
- * @return 1 if the command was issued successfully. Otherwise 0 if a
- * previously issued command has not yet finished processing.
+ * This function requires interrupts to be enabled and cannot be used in a
+ * critical section or IRQ callback. Use CdCommandF() in callbacks instead.
*
- * @see CdControl()
+ * @param cmd
+ * @param param Pointer to command parameters if any
+ * @param length Number of parameter bytes expected by the command
+ * @param result Optional pointer to buffer to store result into
+ * @return 1 if the command was issued successfully or 0 if a previously issued
+ * command has not yet finished processing
+ *
+ * @see CdSync(), CdCommandF(), CdControl()
*/
-int CdControlF(uint8_t com, const void *param);
+int CdCommand(CdlCommand cmd, const void *param, int length, uint8_t *result);
/**
- * @brief Waits for blocking command or blocking status
+ * @brief Issues a custom packet to the CD-ROM controller (asynchronous).
*
- * @details If mode is zero the function blocks if a blocking command was
- * issued earlier until the command has finished. If mode is non-zero the
- * function returns a command status value.
+ * @details This function works similarly to CdCommand() but does not block for
+ * the acknowledge interrupt from the CD-ROM controller, making it suitable for
+ * usage in an IRQ handler or callback function. Use CdSync() (outside of a
+ * callback) to wait for a command to finish, or CdSyncCallback() to register a
+ * callback to be executed once the drive is idle.
+ *
+ * A maximum of two commands can be issued at once and only the first command
+ * can have parameters, as the parameter buffer is not cleared until the last
+ * command is acknowledged by the controller.
+ *
+ * @param cmd
+ * @param param Pointer to command parameters
+ * @param length Number of parameter bytes expected by the command
+ * @return Always 1 (even if sending the command failed)
+ *
+ * @see CdCommand(), CdControlF()
+ */
+int CdCommandF(CdlCommand cmd, const void *param, int length);
+
+/**
+ * @brief Waits for a blocking command or returns the current status.
*
- * A buffer specified by result will be set with the most recent CD-ROM status
- * value from the last command issued.
+ * @details Waits for the controller to finish processing any blocking command
+ * (if mode = 0) or returns the current command status (if mode = 1). The
+ * buffer specified by result will be populated with the most recent CD-ROM
+ * status value from the last command issued.
*
- * @param mode Mode
- * @param result Pointer to store most recent CD-ROM status
+ * @param mode
+ * @param result Optional pointer to buffer to store result into
* @return Command status is returned as one of the following definitions:
*
* | Definition | Description |
@@ -409,332 +503,489 @@ int CdControlF(uint8_t com, const void *param);
*
* @see CdControl()
*/
-int CdSync(int mode, uint8_t *result);
+CdlIntrResult CdSync(int mode, uint8_t *result);
/**
- * @brief Sets a callback function
+ * @brief Sets a callback for blocking commands.
*
- * @details Sets a callback with the specified function func. The callback is
- * executed whenever a blocking command has completed.
+ * @details Registers a function to be called whenever a blocking command has
+ * completed. The callback will get the interrupt status (CdlComplete or
+ * CdlDiskError) and the pointer to any result buffer previously passed to
+ * CdControl() as arguments.
*
- * status is the CD-ROM status from the command that has completed processing.
- * *result corresponds to the *result parameter on CdControl()/CdControlB() and
- * returns the pointer to the buffer last set with that function.
+ * The callback will run in the exception handler's context, so it should be as
+ * fast as possible and shall not call any function that relies on interrupts
+ * being enabled.
*
- * @param func Callback function
- * @return Pointer to last callback function set, or NULL if none was set.
+ * @param func
+ * @return Previously set callback or NULL
*
- * @see CdControl, CdControlB, CdSync
+ * @see CdControl(), CdControlB(), CdSync()
*/
-uint32_t CdSyncCallback(CdlCB func);
+CdlCB CdSyncCallback(CdlCB func);
/**
- * @brief Sets a callback function
+ * @brief Sets a callback for incoming sector or report data.
+ *
+ * @details Registers a function to be called whenever a data sector or report
+ * packet is available to be read from the drive, or in case of an error. The
+ * callback will get the interrupt status (CdlDataReady or CdlDiskError) and
+ * the pointer to any result buffer previously passed to CdControl() as
+ * arguments.
*
- * @details Sets a callback with the specified function func. The callback is
- * executed whenever there's an incoming data sector from the CD-ROM controller
- * during CdlReadN or CdlReadS. The pending sector data can be retrieved using
- * CdGetSector().
+ * Note that the actual sector data is not retrieved automatically and must
+ * instead be read into memory using CdGetSector() or CdGetSector2() before the
+ * drive overwrites its internal sector buffer.
*
- * status is the CD-ROM status code from the last CD command that has finished
- * processing. *result corresponds to the result pointer that was passed by the
- * last CdControl()/CdControlB() call.
+ * The callback will run in the exception handler's context, so it should be as
+ * fast as possible and shall not call any function that relies on interrupts
+ * being enabled.
*
- * This callback cannot be used in conjunction with CdRead() because it also
- * uses this callback hook for its own internal use. The previously set
- * callback is restored after read completion however.
+ * NOTE: when using CdRead() or CdReadRetry(), any callback set using
+ * CdReadyCallback() is temporarily disabled in order to let the library handle
+ * read sectors internally.
*
- * @param func Callback function
- * @return Pointer to last callback function set, or NULL if none was set.
+ * @param func
+ * @return Previously set callback or NULL
*
* @see CdControl(), CdControlB(), CdGetSector()
*/
-int CdReadyCallback(CdlCB func);
+CdlCB CdReadyCallback(CdlCB func);
+
+/**
+ * @brief Sets a callback for the end of a track.
+ *
+ * @details Registers a function to be called whenever the drive encounters and
+ * pauses at the end of a track (if the CdlModeAP bit is set in the CD-ROM
+ * mode), or whether the end of the disc is reached during data reading or
+ * audio playback. The callback will get the interrupt status (CdlDataEnd) and
+ * the pointer to any result buffer previously passed to CdControl() as
+ * arguments.
+ *
+ * Such a callback can be used along with the CdlModeAP flag to detect when
+ * playback of a CD-DA track ends and automatically loop it or start playing
+ * another track, with no manual polling required.
+ *
+ * @param func
+ * @return Previously set callback or NULL
+ *
+ * @see CdControl()
+ */
+CdlCB CdAutoPauseCallback(CdlCB func);
/**
- * @brief Gets data from the CD-ROM sector buffered
+ * @brief Transfers data from the CD-ROM sector buffer.
*
* @details Reads sector data that is pending in the CD-ROM sector buffer and
- * stores it to *madr. Uses DMA to transfer the sector data and blocks very
- * briefly until said transfer completes.
+ * stores it into the provided buffer (which must be 32-bit aligned). Blocks
+ * until the transfer has finished.
*
* This function is intended to be called within a callback routine set using
* CdReadyCallback() to fetch read data sectors from the CD-ROM sector buffer.
- *
- * @param madr Pointer to memory buffer to store sector data
- * @param size Number of 32-bit words to retrieve
- * @return Always 1.
+ *
+ * @param madr
+ * @param size Number of 32-bit words to read (usually 512 for a 2048-byte
+ * sector)
+ * @return Always 1
*
* @see CdReadyCallback()
*/
int CdGetSector(void *madr, int size);
/**
- * @brief Gets data from the CD-ROM sector buffered (non-blocking)
+ * @brief Transfers data from the CD-ROM sector buffer (non-blocking).
*
* @details Reads sector data that is pending in the CD-ROM sector buffer and
- * stores it to *madr. Uses DMA to transfer the sector data in the background
- * while keeping the CPU running (one word is transferred every 16 CPU cycles).
- * Note this is much slower than the blocking transfer performed by
- * CdGetSector().
+ * stores it into the provided buffer (which must be 32-bit aligned). The
+ * transfer runs in the background while keeping the CPU running; about one
+ * word is transferred every 16 CPU cycles. Note this is slower than the
+ * blocking transfer performed by CdGetSector().
*
* This function is intended to be called within a callback routine set using
* CdReadyCallback() to fetch read data sectors from the CD-ROM sector buffer.
* Since the transfer is asynchronous, CdDataSync() should be used to wait
* until the whole sector has been read.
- *
- * @param madr Pointer to memory buffer to store sector data
- * @param size Number of 32-bit words to retrieve
- * @return Always 1.
+ *
+ * @param madr
+ * @param size Number of 32-bit words to read (usually 512 for a 2048-byte
+ * sector)
+ * @return Always 1
*
* @see CdReadyCallback(), CdDataSync()
*/
int CdGetSector2(void *madr, int size);
/**
- * @brief Waits for sector transfer to finish
+ * @brief Waits for a sector buffer transfer to finish.
*
- * @details If mode is zero the function blocks until any sector DMA transfer
- * initiated by calling CdGetSector2() has finished. If mode is non-zero the
- * function returns a boolean value representing whether a transfer is
- * currently in progress.
+ * @details Blocks until any sector DMA transfer initiated using CdGetSector2()
+ * has finished (if mode = 0) or returns whether a transfer is currently in
+ * progress (if mode = 1).
*
- * @param mode Mode
+ * @param mode
* @return 0 if the transfer has finished, 1 if it is still in progress or -1
- * in case of a timeout.
+ * in case of a timeout
*
* @see CdGetSector2()
*/
int CdDataSync(int mode);
/**
- * @brief Locates a file in the CD-ROM file system
+ * @brief Reads one or more sectors into a buffer.
*
- * @details Searches a file specified by filename by path and name in the
- * CD-ROM file system and returns information of the file if found. The file
- * information acquired will be stored to loc.
+ * @details Starts reading the specified number of sectors, starting from the
+ * location set by the last CdlSetloc command issued, and stores them
+ * contiguously into the provided buffer. The given mode is applied using a
+ * CdlSetmode command prior to starting the read.
*
- * Directories can be separated with slashes (/) or backslashes (\), a leading
- * slash or backslash is optional but paths must be absolute. File version
- * identifier (;1) at the end of the file name is also optional. File and
- * directory names are case insensitive.
+ * Each sector read is 2340 bytes long if the CdlModeSize bit is set in the
+ * mode, 2048 bytes long otherwise. Ideally, the CdlModeSpeed bit shall be set
+ * to enable double speed mode.
*
- * The ISO9660 file system routines of libpsxcd do not support long file names
- * currently. Only MS-DOS style 8.3 file names are supported; extensions such
- * as Joliet and Rock Ridge are ignored.
+ * Reading is done asynchronously. Use CdReadSync() to block until all data has
+ * been read, or CdReadCallback() to register a callback to be executed on
+ * completion or error. In case of errors, reading is aborted immediately and
+ * CdReadSync() will return -1.
*
- * Upon calling this function for the first time, the ISO descriptor of the
- * disc is read and the whole path table is cached into memory. Next the
- * directory descriptor of the particular directory specified is loaded and
- * cached to locate the file specified. The directory descriptor is kept in
- * memory as long as the consecutive files to be searched are stored in the
- * same directory until a file in another directory is to be searched. On which
- * the directory descriptor is unloaded and a new directory descriptor is read
- * from the disc and cached. Therefore, locating files in the same directory is
- * faster as the relevant directory descriptor is already in memory and no disc
- * reads are issued.
+ * This function requires interrupts to be enabled and cannot be used in a
+ * critical section or IRQ callback. Any callback set using CdReadyCallback()
+ * is temporarily disabled and restored once the read operation completes.
*
- * As of Revision 66 of PSn00bSDK, media change is detected by checking the
- * CD-ROM lid open status bit and attempting to acknowledge it with a CdlNop
- * command, to discriminate the status from an open lid or changed disc.
+ * @param sectors
+ * @param buf
+ * @param mode CD-ROM mode to apply prior to reading using CdlSetmode
+ * @return 1 if the read operation started successfully or 0 in case of errors
*
- * @param loc Pointer to a CdlFILE struct to store file information
- * @param filename Path and name of file to locate
- * @return Pointer to the specified CdlFILE struct. Otherwise NULL is returned
- * when the file is not found.
+ * @see CdReadRetry(), CdReadSync(), CdReadCallback()
*/
-CdlFILE* CdSearchFile(CdlFILE *loc, const char *filename);
+int CdRead(int sectors, uint32_t *buf, int mode);
/**
- * @brief Reads sectors from the CD-ROM
+ * @brief Reads one or more sectors into a buffer (performs multiple attempts).
*
- * @details Reads a number sectors specified by sectors from the location set
- * by the last CdlSetloc command, the read sectors are then stored to a buffer
- * specified by buf. mode specifies the CD-ROM mode to use for the read
- * operation.
+ * @details This function works similarly to CdRead(), but retries reading in
+ * case of errors. If reading fails, up to the specified number of attempts
+ * will be done before an error is returned by CdReadSync().
*
- * The size of the sector varies depending on the sector read mode specified by
- * mode. For standard data sectors it is multiples of 2048 bytes. If
- * CdlModeSize0 is specified the sector size is 2328 bytes which includes the
- * whole sector minus sync, adress, mode and sub header bytes. CdlModeSize1
- * makes the sector size 2340 which is the entire sector minus sync bytes.
- * Ideally, CdlModeSpeed must be specified to read data sectors at double
- * CD-ROM speed.
+ * This function requires interrupts to be enabled and cannot be used in a
+ * critical section or IRQ callback. Any callback set using CdReadyCallback()
+ * is temporarily disabled and restored once the read operation completes.
*
- * This function blocks very briefly to issue the necessary commands to start
- * CD-ROM reading. To determine if reading has completed use CdReadSync or
- * CdReadCallback.
+ * IMPORTANT: in order for retries to be correctly processed, CdReadSync(0)
+ * (blocking) shall be called immediately after starting the read, or
+ * CdReadSync(1) (non-blocking) shall be called frequently (e.g. once per
+ * frame) until reading has finished.
*
- * @param sectors Number of sectors to read
- * @param buf Pointer to buffer to store sectors read
- * @param mode CD-ROM mode for reading
- * @return Always returns 0 even on errors. This may change in future versions.
+ * @param sectors
+ * @param buf
+ * @param mode CD-ROM mode to apply prior to reading using CdlSetmode
+ * @param attempts Maximum number of attempts (>= 1)
+ * @return 1 if the read operation started successfully or 0 in case of errors
*
- * @see CdReadSync(), CdReadCallback()
+ * @see CdRead(), CdReadSync(), CdReadCallback()
*/
-int CdRead(int sectors, uint32_t *buf, int mode);
+int CdReadRetry(int sectors, uint32_t *buf, int mode, int attempts);
/**
- * @brief Waits for CD-ROM read completion or returns read status
+ * @brief Cancels reading initiated by CdRead().
*
- * @details This function works more or less like CdSync() but for CdRead(). If
- * mode is zero the function blocks if CdRead() was issued earlier until
- * reading has completed. If mode is non-zero the function completes
- * immediately and returns number of sectors remaining.
+ * @details Aborts any ongoing read operation that was previously started by
+ * calling CdRead() or CdReadRetry(). After aborting, CdReadSync() will return
+ * -2 and any callback registered using CdReadCallback() *not* be called.
*
- * A buffer specified by result will be set with the most recent CD-ROM status
- * value from the last read issued.
+ * NOTE: the CD-ROM controller may take several hundred milliseconds to
+ * actually stop reading. CdReadSync() should be used to make sure the drive is
+ * idle before sending a command or starting another read.
*
- * @param mode Mode
- * @param result Pointer to store most recent CD-ROM status
- * @return Number of sectors remaining. If reading is completed, 0 is returned.
- * On error, -1 is returned.
+ * @see CdReadSync()
+ */
+void CdReadBreak(void);
+
+/**
+ * @brief Waits for reading initiated by CdRead() to finish or returns status.
+ *
+ * @details Waits for any ongoing read operation previously started using
+ * CdRead() or CdReadRetry() to finish (if mode = 0) or returns the number of
+ * sectors pending (if mode = 1). The buffer specified by result will be
+ * populated with the most recent CD-ROM status value from the last command
+ * issued.
+ *
+ * When using CdReadRetry(), this function also handles starting a new read
+ * attempt if the previous one failed. As such, it needs to be called once (if
+ * mode = 0) or periodically (if mode = 1) in order to poll the drive for
+ * failures, even if the return value is ignored.
*
- * @see CdRead()
+ * @param mode
+ * @param result Buffer to store most recent CD-ROM status into
+ * @return Number of sectors remaining, -1 in case of errors or -2 if the read
+ * was aborted
+ *
+ * @see CdRead(), CdReadCallback()
*/
int CdReadSync(int mode, uint8_t *result);
/**
- * @brief Sets a callback function for read completion
+ * @brief Sets a callback for read operations started by CdRead().
+ *
+ * @details Registers a function to be called whenever a read started using
+ * CdRead() or CdReadRetry() has completed. The callback will get the interrupt
+ * status (CdlComplete or CdlDiskError) and the pointer to a result buffer
+ * internal to the library.
+ *
+ * The callback will run in the exception handler's context, so it should be as
+ * fast as possible and shall not call any function that relies on interrupts
+ * being enabled.
+ *
+ * @param func
+ * @return Previously set callback or NULL
*
- * @details Works much the same as CdSyncCallback() but for CdRead(). Sets a
- * callback with the specified function func. The callback is executed whenever
- * a read operation initiated by CdRead() has completed.
+ * @see CdRead(), CdReadSync()
+ */
+CdlCB CdReadCallback(CdlCB func);
+
+/**
+ * @brief Returns the last command issued.
*
- * status is the CD-ROM status from the command that has completed processing.
- * *result points to a read result buffer.
+ * @details Returns the index of the last command sent to the CD-ROM controller
+ * using CdCommand(), CdControl() or any of its variants. The value is stored
+ * in an internal variable, so this function returns instantly and can be used
+ * in an IRQ callback.
*
- * @param func Callback function
- * @return Pointer to last callback function set, or NULL if none was set.
+ * @return Last command issued
*
- * @see CdRead()
+ * @see CdControl()
*/
-uint32_t CdReadCallback(CdlCB func);
+CdlCommand CdLastCom(void);
/**
- * @brief Gets the most recent CD-ROM status
+ * @brief Returns the last CD-ROM position set.
*
- * @details Returns the CD-ROM status since the last command issued. The status
- * value is updated by most CD-ROM commands.
+ * @details Returns the last seek location set using the CdlSetloc command (or
+ * any of the commands that are processed by sending CdlSetloc internally, such
+ * as CdlReadN, CdlReadS, CdlSeekL or CdlSeekP). The value is stored in an
+ * internal variable, so this function returns instantly and can be used in an
+ * IRQ callback.
*
- * To get the current CD-ROM status you can issue CdlNop commands at regular
- * intervals to update the CD-ROM status this function returns.
+ * Note that if CdlReadN, CdlReadS or CdlPlay were issued, this will be the
+ * location the read was started from and *not* the current position of the
+ * drive head, which can instead be retrieved using CdlGetlocL or CdlGetlocP.
*
- * @return CD-ROM status from last comand issued.
+ * @return Pointer to an internal CdlLOC structure containing the last position
*
* @see CdControl()
*/
-int CdStatus(void);
+const CdlLOC *CdLastPos(void);
/**
- * @brief Gets the last CD-ROM mode
+ * @brief Returns the last CD-ROM mode set.
*
- * @details Returns the CD-ROM mode last set when issuing a CdlSetmode command.
- * The function returns instantly as it merely returns a value stored in an
- * internal variable.
+ * @details Returns the CD-ROM mode last set using the CdlSetmode command.
+ * The value is stored in an internal variable, so this function returns
+ * instantly and can be used in an IRQ callback.
*
- * Since the value is simply a copy of what was specified from the last
- * CdlSetmode command, the mode value may become inaccurate if CdlInit or other
- * commands that affect the CD-ROM mode have been issued previously.
+ * WARNING: upon receiving some commands, such as CdlInit, the CD-ROM drive may
+ * change its mode automatically. The value returned by CdMode() will not
+ * reflect those changes; use CdlGetparam instead.
*
- * @return Last CD-ROM mode value.
+ * @return Last CD-ROM mode value set
+ *
+ * @see CdControl()
*/
int CdMode(void);
/**
- * @brief Sets CD-ROM mixer or attenuation
+ * @brief Returns the most recent CD-ROM status.
+ *
+ * @details Returns the CD-ROM status since the last command issued. The value
+ * is stored in an internal variable, so this function returns instantly and
+ * can be used in an IRQ callback.
+ *
+ * The status value is updated by most CD-ROM commands, with the exception of
+ * CdlTest, CdlGetlocL and CdlGetlocP; it can also be updated manually by
+ * issuing the CdlNop command.
+ *
+ * @return CD-ROM status returned by last comand issued
+ *
+ * @see CdControl()
+ */
+int CdStatus(void);
+
+/**
+ * @brief Retrieves the disc's table of contents.
+ *
+ * @details Retrieves the track entries from a CD's table of contents (TOC). The
+ * function can return up to 99 track entries, which is the maximum number of
+ * tracks on a CD. This function shall only be called while the drive is idle
+ * and cannot be used in an IRQ callback or critical section.
+ *
+ * NOTE: the CD-ROM controller only returns the minutes and seconds of each
+ * track's location. This makes it impossible to start playing a track from its
+ * *exact* beginning, and may result in the end of the previous track being
+ * played if there are no silent gaps between tracks. The CD specification
+ * recommends adding a 2-second pregap to each track for this reason.
+ *
+ * @param toc Pointer to an array of CdlLOC entries
+ * @return Number of tracks on the disc, or 0 in case of error
+ *
+ * @see CdControl()
+ */
+int CdGetToc(CdlLOC *toc);
+
+/**
+ * @brief Returns the CD-ROM controller's region code.
+ *
+ * @details Attempts to fetch region information from the drive using a CdlTest
+ * command. This can be used to reliably determine the system's region without
+ * having to resort to workarounds like probing the BIOS ROM.
*
- * @details Sets the CD-ROM attenuation parameters from a CdlATV struct
- * specified by vol. The CD-ROM attenuation settings are different from the SPU
- * CD-ROM volume.
+ * This function may return incorrect results on emulators or consoles equipped
+ * with CD-ROM drive emulation devices such as the PSIO. It is not affected by
+ * modchips.
*
- * Normally used to configure CD and XA audio playback for mono or reverse
- * stereo output, though this was rarely used in practice.
+ * @return Region code or 0 if the region cannot be determined
+ */
+CdlRegionCode CdGetRegion(void);
+
+/**
+ * @brief Sets the CD-ROM volume mixing matrix.
+ *
+ * @details Sets the volume levels of the CD-ROM drive's audio output (used for
+ * both CD-DA and XA playback) to match the values in the provided CdlATV
+ * structure.
+ *
+ * The default setting is { 128, 0, 128, 0 }, i.e. send the left channel to the
+ * left output only and the right channel to the right output only. The output
+ * can be downmixed to mono by setting the values to { 64, 64, 64, 64 }, or the
+ * left and right channels can be swapped using { 0, 128, 0, 128 }.
+ *
+ * NOTE: the SPU has an additional volume control for CD audio. Both must be
+ * set in order for audio output to work.
*
* @param vol CD-ROM attenuation parameters
- * @return Always 1.
+ * @return Always 1
*/
int CdMix(const CdlATV *vol);
/**
- * @brief Opens a directory on the CD-ROM file system
+ * @brief Locates a file in the CD-ROM file system.
+ *
+ * @details Searches the CD-ROM's ISO9660 file system for the specified file
+ * and populates the given CdlFILE structure with information about the file if
+ * found. This function uses dynamic memory allocation.
+ *
+ * Directories can be separated with slashes (/) or backslashes (\), a leading
+ * slash or backslash is optional but paths must be absolute. The device prefix
+ * (cdrom:) must be omitted. A file version identifier (;1) at the end of the
+ * name is also optional. File and directory names are case insensitive; long
+ * names and ISO9660 extensions such as Joliet are currently not supported.
+ *
+ * This function is blocking and may take several seconds to load and
+ * subsequently parse the path table and directory records if none of the file
+ * system functions have yet been called.
+ *
+ * Upon calling this function for the first time, the ISO descriptor of the
+ * disc is read and the whole path table is cached into memory. Next the
+ * directory descriptor of the particular directory specified is loaded and
+ * cached to locate the file specified. The directory descriptor is kept in
+ * memory as long as the consecutive files to be searched are stored in the
+ * same directory until a file in another directory is to be searched. On which
+ * the directory descriptor is unloaded and a new directory descriptor is read
+ * from the disc and cached. Therefore, locating files in the same directory is
+ * faster as the relevant directory descriptor is already in memory and no disc
+ * reads are issued.
+ *
+ * Since file system access is slow, it is recommended to only use
+ * CdSearchFile() sparingly to e.g. find the location of a custom archive file,
+ * and then use the archive's internal table of contents to locate entries
+ * within the archive.
+ *
+ * @param loc Pointer to a CdlFILE structure
+ * @param filename
+ * @return Pointer to the specified CdlFILE structure, or a null pointer if the
+ * file cannot be found or another error occurred; the return value of
+ * CdIsoError() is also updated
+ *
+ * @see CdOpenDir()
+ */
+CdlFILE* CdSearchFile(CdlFILE *loc, const char *filename);
+
+/**
+ * @brief Opens a directory on the CD-ROM file system.
*
- * @details Opens a directory on the CD-ROM file system to read the contents of
- * a directory.
+ * @details Opens a directory on the CD-ROM's ISO9660 file system and reads its
+ * contents, returning a newly allocated CdlDIR structure. This function uses
+ * dynamic memory allocation.
*
- * A path name can use a slash (/) or backslash character (\) as the directory
- * name separator. The path must be absolute and should begin with a slash or
- * backslash. It should also not be prefixed with a device name (ie.
- * \MYDIR1\MYDIR2 will work but not cdrom:\MYDIR1\MYDIR2). The file system
- * routines in libpsxcd can query directory paths of up to 128 characters.
+ * Directories can be separated with slashes (/) or backslashes (\), a leading
+ * slash or backslash is optional but paths must be absolute. The device prefix
+ * (cdrom:) must be omitted. Directory names are case insensitive; long names
+ * and ISO9660 extensions such as Joliet are currently not supported.
*
- * The ISO9660 file system routines of libpsxcd do not support long file names
- * currently. Only MS-DOS style 8.3 file names are supported; extensions such
- * as Joliet and Rock Ridge are ignored.
+ * This function is blocking and may take several seconds to load and
+ * subsequently parse the path table and directory records if none of the file
+ * system functions have yet been called.
*
- * @param path Directory path to open
- * @return Pointer of a CdlDIR context, NULL if an error occurred.
+ * @param path
+ * @return Pointer of a CdlDIR context or a null pointer if an error occurred;
+ * the return value of CdIsoError() is also updated
*
* @see CdReadDir(), CdCloseDir()
*/
-CdlDIR* CdOpenDir(const char* path);
+CdlDIR *CdOpenDir(const char *path);
/**
- * @brief Reads a directory entry from an open directory context
+ * @brief Obtains information about the next file in the directory.
*
- * @details Retrieves a file entry from an open directory context and stores it
- * to a CdlFILE struct specified by file. Repeated calls of this function
- * retrieves the next directory entry available until there are no more
- * directory entries that follow.
+ * @details Retrieves a file entry from an open directory context and stores
+ * information about the file into the provided CdlFILE structure. This
+ * function is meant to be called repeatedly until no more files are available
+ * in the directory, in which case it returns 0.
*
- * @param dir Open directory context (from CdOpenDir())
- * @param file Pointer to a CdlFILE struct
- * @return 1 if there are proceeding directory entries that follow, otherwise 0.
+ * @param dir
+ * @param file Pointer to a CdlFILE structure
+ * @return 1 if there are proceeding directory entries that follow, 0 otherwise
*
* @see CdOpenDir()
*/
-int CdReadDir(CdlDIR* dir, CdlFILE* file);
+int CdReadDir(CdlDIR *dir, CdlFILE *file);
/**
- * @brief Closes a directory context created by CdOpenDir()
+ * @brief Closes a directory opened by CdOpenDir().
*
- * @details Closes a directory query context created by CdOpenDir(). Behavior
- * is undefined when closing a previously closed directory context.
+ * @details Closes and deallocates directory query context returned by
+ * CdOpenDir(). Behavior is undefined when closing a previously closed
+ * directory context.
*
- * @param dir Directory context
+ * @param dir
*
* @see CdOpenDir()
*/
-void CdCloseDir(CdlDIR* dir);
-
-int CdGetVolumeLabel(char* label);
+void CdCloseDir(CdlDIR *dir);
/**
- * @brief Sets a callback function for auto pause
- *
- * @details The callback function specified in *func is executed when an auto
- * pause interrupt occurs when the current CD-ROM mode is set with CdlModeAP.
- * Auto pause interrupt occurs when CD Audio playback reaches the end of the
- * audio track. Specifying 0 disables the callback.
+ * @brief Retrieves the volume label of the CD-ROM file system.
*
- * This can be used to easily loop CD audio automatically without requiring any
- * intervention in your software loop.
+ * @details Reads the volume identifier of the disc's ISO9660 file system and
+ * stores it into the provided string buffer. The volume label can be up to 32
+ * characters long.
*
- * @param func Callback function
- * @return Pointer to the last callback function set. Zero if no callback was
- * set previously.
+ * This function is blocking and may take several seconds to load and
+ * subsequently parse the path table and directory records if none of the file
+ * system functions have yet been called.
*
- * @see CdControl()
+ * @param label
+ * @return Length of the volume label (excluding the null terminator) or -1 in
+ * case of an error; the return value of CdIsoError() is also updated
*/
-int* CdAutoPauseCallback(void(*func)());
+int CdGetVolumeLabel(char *label);
/**
- * @brief Retrieves CD-ROM ISO9660 parser status
+ * @brief Retrieves CD-ROM ISO9660 parser status.
*
* @details Returns the status of the file system parser from the last call of
- * a file system related function, such as CdSearchFile(), CdGetVolumeLabel()
- * and CdOpenDir(). Use this function to retrieve the exact error occurred when
- * either of those functions fail.
+ * a file system related function, such as CdSearchFile(), CdGetVolumeLabel(),
+ * CdOpenDir() and CdLoadSession(). Use this function to retrieve the exact
+ * error occurred when any of those functions fail.
*
* @return CD-ROM ISO9660 parser error code, as listed below:
*
@@ -745,11 +996,13 @@ int* CdAutoPauseCallback(void(*func)());
* | CdlIsoReadError | Read error occurred while reading the CD-ROM file system descriptor. |
* | CdlIsoInvalidFs | Disc does not contain a standard ISO9660 file system. |
* | CdlIsoLidOpen | Lid is open when attempting to parse the CD-ROM file system. |
+ *
+ * @see CdSearchFile(), CdOpenDir()
*/
int CdIsoError(void);
/**
- * @brief Locates and parses the specified disc session
+ * @brief Locates and loads the specified disc session.
*
* @details Loads a session specified by session on a multi-session disc. Uses
* CdlSetsession to seek to the specified disc session, then scans the
@@ -778,16 +1031,15 @@ int CdIsoError(void);
* with the swap trick however, so a chipped or unlockable console is desired
* for reading multi-session discs.
*
- * NOTE: When the lid has been opened, the current CD-ROM session is reset to
+ * NOTE: when the lid has been opened, the current CD-ROM session is reset to
* the first session on the disc. The console may produce an audible click
* sound when executing this function. This is normal, and the click sound is
* no different to the click heard on disc spin-up in older models of the
* console.
*
* @param session Session number (1 = first session)
- * @return 0 on success. On failure due to open lid, bad session number or no
- * volume descriptor found in specified session, returns -1 and return value of
- * CdIsoError() is updated.
+ * @return 0 on success or -1 in case of errors; the return value of
+ * CdIsoError() is also updated
*/
int CdLoadSession(int session);
diff --git a/libpsn00b/include/psxgpu.h b/libpsn00b/include/psxgpu.h
index 68e3bff..26e560f 100644
--- a/libpsn00b/include/psxgpu.h
+++ b/libpsn00b/include/psxgpu.h
@@ -39,6 +39,11 @@ typedef enum _GPU_VideoMode {
#define setTPage(p, tp, abr, x, y) ((p)->tpage = getTPage(tp, abr, x, y))
#define setClut(p, x, y) ((p)->clut = getClut(x, y))
+#define setColor0(p, rgb) (((P_COLOR *) &((p)->r0))->color = (rgb))
+#define setColor1(p, rgb) (((P_COLOR *) &((p)->r1))->color = (rgb))
+#define setColor2(p, rgb) (((P_COLOR *) &((p)->r2))->color = (rgb))
+#define setColor3(p, rgb) (((P_COLOR *) &((p)->r3))->color = (rgb))
+
#define setRGB0(p, r, g, b) ((p)->r0 = (r), (p)->g0 = (g), (p)->b0 = (b))
#define setRGB1(p, r, g, b) ((p)->r1 = (r), (p)->g1 = (g), (p)->b1 = (b))
#define setRGB2(p, r, g, b) ((p)->r2 = (r), (p)->g2 = (g), (p)->b2 = (b))
@@ -198,9 +203,15 @@ typedef enum _GPU_VideoMode {
typedef struct _P_TAG {
uint32_t addr:24;
uint32_t len:8;
- uint8_t r, g, b, code;
+ uint32_t color:24;
+ uint32_t code:8;
} P_TAG;
+typedef struct _P_COLOR {
+ uint32_t color:24;
+ uint32_t pad:8;
+} P_COLOR;
+
typedef struct _POLY_F3 {
uint32_t tag;
uint8_t r0, g0, b0, code;
@@ -402,7 +413,7 @@ typedef struct _SPRT_FIXED SPRT_16;
typedef struct _DR_ENV {
uint32_t tag;
- uint32_t code[15];
+ uint32_t code[8];
} DR_ENV;
typedef struct _DR_AREA {
@@ -525,10 +536,10 @@ void *DrawSyncCallback(void (*func)(void));
int LoadImage(const RECT *rect, const uint32_t *data);
int StoreImage(const RECT *rect, uint32_t *data);
-int MoveImage(const RECT *rect, int x, int y);
+//int MoveImage(const RECT *rect, int x, int y);
void LoadImage2(const RECT *rect, const uint32_t *data);
void StoreImage2(const RECT *rect, uint32_t *data);
-void MoveImage2(const RECT *rect, int x, int y);
+//void MoveImage2(const RECT *rect, int x, int y);
void ClearOTagR(uint32_t *ot, size_t length);
void ClearOTag(uint32_t *ot, size_t length);
diff --git a/libpsn00b/include/stdio.h b/libpsn00b/include/stdio.h
index 97a2f5a..8aaf4c7 100644
--- a/libpsn00b/include/stdio.h
+++ b/libpsn00b/include/stdio.h
@@ -3,18 +3,6 @@
#include <stdarg.h>
-#ifndef NULL
-#define NULL 0
-#endif
-
-#ifndef true
-#define true 1
-#endif
-
-#ifndef false
-#define false 0
-#endif
-
// BIOS seek modes
#ifndef SEEK_SET
#define SEEK_SET 0
@@ -26,11 +14,6 @@
#define SEEK_END 2 /* warning: reportedly buggy */
#endif
-#ifndef _SIZE_T
-#define _SIZE_T
-typedef unsigned int size_t;
-#endif
-
#ifdef __cplusplus
extern "C" {
#endif
diff --git a/libpsn00b/libc/clz.s b/libpsn00b/libc/clz.s
new file mode 100644
index 0000000..28a28eb
--- /dev/null
+++ b/libpsn00b/libc/clz.s
@@ -0,0 +1,43 @@
+# PSn00bSDK leading zero count intrinsics
+# (C) 2022 spicyjpeg - MPL licensed
+#
+# libgcc provides two functions used internally by GCC to count the number of
+# leading zeroes or ones in a value, _clzsi2() (32-bit) and _clzdi2() (64-bit).
+# This file overrides them with faster implementations that make use of the
+# GTE's LZCS/LZCR registers.
+
+.set noreorder
+
+.section .text._clzsi2
+.global _clzsi2
+.type _clzsi2, @function
+_clzsi2:
+ mtc2 $a0, $30
+ nop
+ nop
+ mfc2 $v0, $31
+
+ jr $ra
+ nop
+
+.section .text._clzdi2
+.global _clzdi2
+.type _clzdi2, @function
+_clzdi2:
+ bnez $a1, .Lhas_msb
+ nop
+
+ mtc2 $a0, $30 # if (!msb) return 32 + clz(lsb)
+ b .Lreturn
+ li $v1, 32
+
+.Lhas_msb:
+ mtc2 $a1, $30 # if (msb) return 0 + clz(msb)
+ nop
+ li $v1, 0
+
+.Lreturn:
+ mfc2 $v0, $31
+
+ jr $ra
+ addu $v0, $v1
diff --git a/libpsn00b/lzp/lzp.c b/libpsn00b/lzp/lzp.c
index 9f2da48..3ef5b24 100644
--- a/libpsn00b/lzp/lzp.c
+++ b/libpsn00b/lzp/lzp.c
@@ -1,3 +1,4 @@
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
diff --git a/libpsn00b/lzp/qlp.c b/libpsn00b/lzp/qlp.c
index e54f99f..1154a65 100644
--- a/libpsn00b/lzp/qlp.c
+++ b/libpsn00b/lzp/qlp.c
@@ -1,3 +1,4 @@
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
diff --git a/libpsn00b/psxapi/_syscalls.s b/libpsn00b/psxapi/_syscalls.s
index 24864f3..6eaed72 100644
--- a/libpsn00b/psxapi/_syscalls.s
+++ b/libpsn00b/psxapi/_syscalls.s
@@ -27,20 +27,21 @@ ExitCriticalSection:
.global SwEnterCriticalSection
.type SwEnterCriticalSection, @function
SwEnterCriticalSection:
- mfc0 $a0, $12 # cop0r12 &= ~0x00000401
- li $a1, -1026
- and $a0, $a1
- mtc0 $a0, $12
- nop
+ mfc0 $a0, $12 # cop0r12 &= ~0x401
+ li $a1, -1026
+ and $a1, $a0
+ mtc0 $a1, $12
+ andi $a0, 0x0401 # return ((cop0r12_prev & 0x401) == 0x401)
+ sltiu $v0, $a0, 0x0401
- jr $ra
- nop
+ jr $ra
+ xori $v0, 1
.section .text.SwExitCriticalSection
.global SwExitCriticalSection
.type SwExitCriticalSection, @function
SwExitCriticalSection:
- mfc0 $a0, $12 # cop0r12 |= 0x00000401
+ mfc0 $a0, $12 # cop0r12 |= 0x401
nop
ori $a0, 0x0401
mtc0 $a0, $12
diff --git a/libpsn00b/psxcd/_cd_control.s b/libpsn00b/psxcd/_cd_control.s
deleted file mode 100644
index 5fa336a..0000000
--- a/libpsn00b/psxcd/_cd_control.s
+++ /dev/null
@@ -1,144 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.inc"
-
-.section .text
-
-#
-# Issues command and parameter bytes to CD controller directly
-#
-.global _cd_control
-.type _cd_control, @function
-_cd_control:
-
- # a0 - command value
- # a1 - pointer to parameters
- # a2 - length of parameters
-
- addiu $sp, -16
- sw $ra, 0($sp)
- sw $a0, 4($sp)
- sw $a1, 8($sp)
- sw $a2, 12($sp)
-
-# move $a3, $a2 # Debug
-# move $a2, $a1
-# move $a1, $a0
-# la $a0, _cd_control_msg
-# jal printf
-# addiu $sp, -16
-# addiu $sp, 16
-
- lw $a0, 4($sp)
- lw $a1, 8($sp)
- lw $a2, 12($sp)
-
- li $v0, 1 # Set acknowledge wait flag
- la $v1, _cd_ack_wait
- sb $v0, 0($v1)
-
- # Commands that have a 'completion' interrupt (CDIRQ2)
-
- beq $a0, 0x07, .Lset_complete # CdlStandby
- nop
- beq $a0, 0x08, .Lset_complete # CdlStop
- nop
- beq $a0, 0x09, .Lset_complete # CdlPause
- nop
- beq $a0, 0x0A, .Lset_complete # CdlInit
- nop
- beq $a0, 0x12, .Lset_complete # CdlSetsession
- nop
- beq $a0, 0x15, .Lset_complete # CdlSeekL
- nop
- beq $a0, 0x16, .Lset_complete # CdlSeekP
- nop
- beq $a0, 0x1A, .Lset_complete # GetID
- nop
- beq $a0, 0x1D, .Lset_complete # GetQ
- nop
-
- la $v1, _cd_complt_wait # Set wait complete flag
- sb $0 , 0($v1)
-
- b .Lno_complete
- nop
-
-.Lset_complete:
-
- la $v1, _cd_complt_wait # Set wait complete flag
- sb $v0, 0($v1)
-
-.Lno_complete:
-
- bne $a0, 0x0E, .Lnot_mode
- lbu $v0, 0($a1)
- la $v1, _cd_last_mode
- sb $v0, 0($v1)
-
-.Lnot_mode:
-
- la $v1, _cd_last_int # Clear last IRQ value
- sb $0 , 0($v1)
-
- la $v1, _cd_last_cmd # Save command as last command
- sb $a0, 0($v1)
-
- lui $v1, IOBASE
-
-.Lbusy_wait:
- lbu $v0, CD_REG0($v1)
- nop
- andi $v0, 0x80
- bnez $v0, .Lbusy_wait
- nop
-
- li $v0, 1 # Clear parameter FIFO (in case it wasn't cleared)
- sb $v0, CD_REG0($v1)
- li $v0, 0x40
- sb $v0, CD_REG3($v1)
-
-#.Lflush_response: # Flush response FIFO
-# lbu $v0, CD_REG1($v1)
-# nop
-# lbu $v0, CD_REG0($v1)
-# nop
-# andi $v0, 0x20
-# beqz $v0, .Lflush_response
-# nop
-
-.Lcmd_wait: # Wait for CD to become ready for commands
- lbu $v0, CD_REG0($v1)
- nop
- andi $v0, 0x80
- bnez $v0, .Lcmd_wait
- nop
-
- sb $0 , CD_REG0($v1)
-
- beqz $a2, .Lno_params
- nop
-
-.Lfeed_params: # Feed parameters to parameter FIFO
- lbu $v0, 0($a1)
- addi $a2, -1
- sb $v0, CD_REG2($v1)
- bgtz $a2, .Lfeed_params
- addiu $a1, 1
-
-.Lno_params:
-
- sb $0 , CD_REG0($v1) # Feed command value
- sb $a0, CD_REG1($v1)
-
- lw $ra, 0($sp)
- addiu $sp, 16
- jr $ra
- nop
-
-
-#.section .data
-
-#_cd_control_msg:
-# .asciiz "CdControl(%d, %x, %d)\n"
- \ No newline at end of file
diff --git a/libpsn00b/psxcd/cdmix.s b/libpsn00b/psxcd/cdmix.s
deleted file mode 100644
index 40cd181..0000000
--- a/libpsn00b/psxcd/cdmix.s
+++ /dev/null
@@ -1,34 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.inc"
-
-.section .text
-
-
-.global CdMix
-.type CdMix, @function
-CdMix:
-
- lui $a3, IOBASE
-
- lbu $t0, 0($a0)
- lbu $t1, 1($a0)
- lbu $t2, 2($a0)
- lbu $t3, 3($a0)
-
- li $v0, 2
- sb $v0, CD_REG0($a3)
- sb $t0, CD_REG2($a3)
- sb $t1, CD_REG3($a3)
-
- li $v0, 3
- sb $v0, CD_REG0($a3)
- sb $t2, CD_REG1($a3)
- sb $t3, CD_REG2($a3)
-
- li $v0, 0x20
- sb $v0, CD_REG3($a3)
-
- jr $ra
- li $v0, 1
- \ No newline at end of file
diff --git a/libpsn00b/psxcd/cdread.c b/libpsn00b/psxcd/cdread.c
new file mode 100644
index 0000000..d211a01
--- /dev/null
+++ b/libpsn00b/psxcd/cdread.c
@@ -0,0 +1,168 @@
+/*
+ * PSn00bSDK CD-ROM library (high-level reading API)
+ * (C) 2020-2022 Lameguy64, spicyjpeg - MPL licensed
+ *
+ * CdRead() and its related functions are separate from the "main" psxcd code
+ * since handling retries is fairly complicated. In particular the drive
+ * controller will not process any command properly for some time after a
+ * CdlPause command, so an external timer (the vblank counter) and manual
+ * polling are required to defer the next attempt.
+ */
+
+#include <stdint.h>
+#include <assert.h>
+#include <psxgpu.h>
+#include <psxapi.h>
+#include <psxcd.h>
+
+#define CD_READ_TIMEOUT 180
+#define CD_READ_COOLDOWN 60
+
+/* Internal globals */
+
+static CdlCB _read_callback = (CdlCB) 0;
+
+static int _total_sectors, _sector_size;
+static uint8_t _read_result[4];
+
+static volatile uint32_t *_read_addr;
+static volatile int _read_timeout, _pending_attempts, _pending_sectors;
+
+extern CdlCB _cd_override_callback;
+
+/* Private utilities and sector callback */
+
+static void _sector_callback(CdlIntrResult irq, uint8_t *result) {
+ if (irq == CdlDataReady) {
+ CdGetSector((void *) _read_addr, _sector_size);
+ _read_addr += _sector_size;
+
+ if (--_pending_sectors > 0) {
+ _read_timeout = VSync(-1) + CD_READ_TIMEOUT;
+ return;
+ }
+ }
+
+ // Stop reading if an error occurred or if no more sectors need to be read.
+ CdCommandF(CdlPause, 0, 0);
+
+ _cd_override_callback = (CdlCB) 0;
+ if (!_pending_attempts && _read_callback)
+ _read_callback(irq, result);
+
+ _read_timeout = VSync(-1) + CD_READ_COOLDOWN;
+}
+
+static int _poll_retry(void) {
+ if (!_pending_attempts) {
+ _sdk_log("CdRead() failed, too many attempts\n");
+
+ _pending_sectors = 0;
+ return -1;
+ }
+
+ //CdControlB(CdlPause, 0, 0);
+
+ _sdk_log("CdRead() failed, retrying (%d sectors pending)\n", _pending_sectors);
+ _pending_attempts--;
+
+ // Restart from the first sector that returned an error.
+ CdlLOC pos;
+ CdIntToPos(
+ CdPosToInt(CdLastPos()) + _total_sectors - _pending_sectors,
+ &pos
+ );
+
+ _read_timeout = VSync(-1) + CD_READ_TIMEOUT;
+ _total_sectors = _pending_sectors;
+
+ FastEnterCriticalSection();
+ _cd_override_callback = &_sector_callback;
+ FastExitCriticalSection();
+
+ if (CdCommand(CdlSetloc, (uint8_t *) &pos, 3, _read_result))
+ CdCommand(CdlReadN, 0, 0, _read_result);
+
+ return _pending_sectors;
+}
+
+/* Public API */
+
+int CdReadRetry(int sectors, uint32_t *buf, int mode, int attempts) {
+ if (CdReadSync(1, 0) > 0) {
+ _sdk_log("CdRead() failed, another read in progress (%d sectors pending)\n", _pending_sectors);
+ return 0;
+ }
+
+ _read_addr = buf;
+ _read_timeout = VSync(-1) + CD_READ_TIMEOUT;
+ _pending_attempts = attempts - 1;
+ _pending_sectors = sectors;
+ _total_sectors = sectors;
+ _sector_size = (mode & CdlModeSize) ? 585 : 512;
+
+ FastEnterCriticalSection();
+ _cd_override_callback = &_sector_callback;
+ FastExitCriticalSection();
+
+ uint8_t _mode = mode;
+ if (!CdCommand(CdlSetmode, &_mode, 1, 0))
+ return 0;
+ if (!CdCommand(CdlReadN, 0, 0, _read_result))
+ return 0;
+
+ return 1;
+}
+
+int CdRead(int sectors, uint32_t *buf, int mode) {
+ return CdReadRetry(sectors, buf, mode, 1);
+}
+
+void CdReadBreak(void) {
+ if (_pending_sectors > 0)
+ _pending_sectors = -1;
+}
+
+int CdReadSync(int mode, uint8_t *result) {
+ if (mode) {
+ if (_pending_sectors < 0)
+ return -2;
+ if (!_pending_sectors)
+ return 0;
+
+ if (VSync(-1) > _read_timeout)
+ return _poll_retry();
+ if (CdSync(1, 0) == CdlDiskError)
+ return -1;
+
+ return _pending_sectors;
+ }
+
+ while (_pending_sectors > 0) {
+ if (VSync(-1) > _read_timeout) {
+ if (_poll_retry() < 0)
+ return -1;
+ }
+
+ //if (CdSync(1, 0) == CdlDiskError)
+ //return -1;
+ }
+
+ CdlIntrResult status = CdSync(0, result);
+ if (_pending_sectors < 0)
+ return -2;
+ if (status != CdlComplete)
+ return -1;
+
+ return 0;
+}
+
+CdlCB CdReadCallback(CdlCB func) {
+ FastEnterCriticalSection();
+
+ CdlCB old_callback = _read_callback;
+ _read_callback = func;
+
+ FastExitCriticalSection();
+ return old_callback;
+}
diff --git a/libpsn00b/psxcd/common.c b/libpsn00b/psxcd/common.c
new file mode 100644
index 0000000..8b8030b
--- /dev/null
+++ b/libpsn00b/psxcd/common.c
@@ -0,0 +1,435 @@
+/*
+ * PSn00bSDK CD-ROM library (common functions)
+ * (C) 2020-2022 Lameguy64, spicyjpeg - MPL licensed
+ */
+
+#include <stdint.h>
+#include <assert.h>
+#include <psxetc.h>
+#include <psxapi.h>
+#include <psxcd.h>
+#include <hwregs_c.h>
+
+#define CD_ACK_TIMEOUT 0x100000
+#define CD_SYNC_TIMEOUT 0x100000
+
+/* Internal globals */
+
+static CdlCB _ready_callback = (CdlCB) 0;
+static CdlCB _sync_callback = (CdlCB) 0;
+static CdlCB _pause_callback = (CdlCB) 0;
+
+static uint8_t *_result_ptr;
+static uint8_t _last_command, _last_mode;
+static CdlLOC _last_pos;
+
+static volatile uint8_t _last_status, _last_irq, _last_error;
+static volatile uint8_t _ack_pending, _sync_pending;
+
+// These globals are accessed by other parts of the library.
+CdlCB _cd_override_callback;
+volatile int _cd_media_changed;
+
+/* Command metadata */
+
+// The original implementation of CdControlF() and the IRQ handler used a
+// ridiculous number of branches to account for command-specific behavior.
+// Storing that information as an array of bitfields is far more efficient.
+typedef enum {
+ PARAM_1 = 1,
+ PARAM_2 = 2,
+ PARAM_3 = 3,
+ STATUS = 1 << 2, // First byte of CdlAcknowledge response is status
+ C_STATUS = 1 << 3, // First byte of CdlComplete response is status
+ BLOCKING = 1 << 4, // Command triggers CdlComplete interrupt
+ OPTIONAL = 1 << 5, // Parameter is optional for the command
+ SETLOC = 1 << 6 // Parameter shall be sent as a separate CdlSetloc
+} CommandFlags;
+
+// https://problemkaputt.de/psx-spx.htm#cdromcontrollercommandsummary
+static const uint8_t _command_flags[] = {
+ 0,
+ STATUS, // CdlNop
+ STATUS | PARAM_3, // CdlSetloc
+ STATUS | OPTIONAL | PARAM_1, // CdlPlay
+ //STATUS | SETLOC, // CdlPlay
+ STATUS, // CdlForward
+ STATUS, // CdlBackward
+ STATUS | SETLOC, // CdlReadN
+ STATUS | C_STATUS | BLOCKING, // CdlStandby
+ STATUS | C_STATUS | BLOCKING, // CdlStop
+ STATUS | C_STATUS | BLOCKING, // CdlPause
+ STATUS | C_STATUS | BLOCKING, // CdlInit
+ STATUS, // CdlMute
+ STATUS, // CdlDemute
+ STATUS | PARAM_2, // CdlSetfilter
+ STATUS | PARAM_1, // CdlSetmode
+ STATUS, // CdlGetparam
+ 0, // CdlGetlocL
+ 0, // CdlGetlocP
+ STATUS | C_STATUS | BLOCKING | PARAM_1, // CdlSetsession
+ STATUS, // CdlGetTN
+ STATUS | PARAM_1, // CdlGetTD
+ STATUS | C_STATUS | BLOCKING | SETLOC, // CdlSeekL
+ STATUS | C_STATUS | BLOCKING | SETLOC, // CdlSeekP
+ 0,
+ 0,
+ PARAM_1, // CdlTest
+ STATUS | C_STATUS | BLOCKING, // CdlGetID
+ STATUS | SETLOC, // CdlReadS
+ STATUS, // CdlReset
+ STATUS | BLOCKING | PARAM_2, // CdlGetQ
+ STATUS | C_STATUS | BLOCKING // CdlReadTOC
+};
+
+/* Private interrupt handler */
+
+static void _update_status(uint8_t status) {
+ uint8_t last = _last_status;
+ _last_status = status;
+
+ if (!(last & CdlStatError) && (status & CdlStatError))
+ _sdk_log("drive error, status=0x%02x, code=0x%02x\n", _last_status, _last_error);
+
+ if (!(last & CdlStatShellOpen) && (status & CdlStatShellOpen)) {
+ _sdk_log("shell opened, invalidating cache\n");
+ _cd_media_changed = 1;
+ }
+}
+
+static void _cd_irq_handler(void) {
+ CD_REG(0) = 1;
+ CdlIntrResult irq = CD_REG(3) & 7;
+
+ if (irq == CdlDataReady) {
+ // TODO: are the first 4 accesses really needed, or was this just
+ // Sony's (dumb) way to flush the KUSEG write queue? We definitely
+ // don't need to do that since we're using KSEG1.
+ CD_REG(0) = 0;
+ CD_REG(0);
+ CD_REG(3) = 0;
+ CD_REG(3);
+ CD_REG(0) = 0;
+ CD_REG(3) = 0x80; // Request data
+ }
+
+ CD_REG(0) = 1;
+ CD_REG(3) = 0x1f; // Acknowledge all IRQs
+ CD_REG(3) = 0x40; // Reset parameter buffer
+
+ //while (CD_REG(0) & (1 << 5))
+ //CD_REG(1);
+ for (int i = 0; i < 50; i++)
+ __asm__ volatile("");
+
+ if (!irq || (irq > CdlDiskError))
+ return;
+
+ // Fetch the result from the drive if a buffer was previously set up. The
+ // first byte is always read as it contains the drive status for most
+ // commands.
+ uint8_t first_byte = CD_REG(1);
+ CdlCB callback;
+
+ if (_result_ptr) {
+ _result_ptr[0] = first_byte;
+
+ for (int i = 1; (CD_REG(0) & 0x20) && (i < 8); i++)
+ _result_ptr[i] = CD_REG(1);
+ }
+
+ switch (irq) {
+ case CdlDataReady:
+ // CdRead() can override any callback set using CdReadyCallback()
+ // by setting _cd_override_callback.
+ callback = _cd_override_callback;
+ if (!callback)
+ callback = _ready_callback;
+
+ _update_status(first_byte);
+ break;
+
+ case CdlComplete:
+ _sync_pending = 0;
+ callback = _sync_callback;
+
+ if (_last_command <= CdlReadTOC) {
+ if (_command_flags[_last_command] & C_STATUS)
+ _update_status(first_byte);
+ }
+ break;
+
+ case CdlAcknowledge:
+ _ack_pending = 0;
+ callback = (CdlCB) 0;
+
+ if (_last_command <= CdlReadTOC) {
+ if (_command_flags[_last_command] & STATUS)
+ _update_status(first_byte);
+ }
+ break;
+
+ case CdlDataEnd:
+ callback = _pause_callback;
+
+ _update_status(first_byte);
+ break;
+
+ case CdlDiskError:
+ _last_error = CD_REG(1);
+ callback = _ready_callback;
+
+ if (_ack_pending || _sync_pending) {
+ if (_sync_callback)
+ _sync_callback(irq, _result_ptr);
+
+ _ack_pending = 0;
+ _sync_pending = 0;
+ }
+
+ _update_status(first_byte);
+ break;
+ }
+
+ if (callback)
+ callback(irq, _result_ptr);
+
+ _last_command = 0;
+ _last_irq = irq;
+ _result_ptr = (uint8_t *) 0;
+}
+
+/* Initialization */
+
+int CdInit(void) {
+ EnterCriticalSection();
+ InterruptCallback(IRQ_CD, &_cd_irq_handler);
+ ExitCriticalSection();
+
+ BUS_CD_CFG = 0x00020943;
+
+ CD_REG(0) = 1;
+ CD_REG(3) = 0x1f; // Acknowledge all IRQs
+ CD_REG(2) = 0x1f; // Enable all IRQs
+ CD_REG(0) = 0;
+ CD_REG(3) = 0x00; // Clear any pending request
+
+ CdlATV mix = { 0x80, 0x00, 0x80, 0x00 };
+ CdMix(&mix);
+
+ DMA_DPCR |= 0x0000b000; // Enable DMA3
+ DMA_CHCR(DMA_CD) = 0x00000000; // Stop DMA3
+
+ _last_mode = 0;
+ _ack_pending = 0;
+ _sync_pending = 0;
+
+ _cd_override_callback = (CdlCB) 0;
+ _cd_media_changed = 1;
+
+ // Initialize the drive.
+ CdCommand(CdlNop, 0, 0, 0);
+ CdCommand(CdlInit, 0, 0, 0);
+
+ if (CdSync(0, 0) == CdlDiskError) {
+ _sdk_log("setup error, bad disc/drive or no disc inserted\n");
+ return 0;
+ }
+
+ CdCommand(CdlDemute, 0, 0, 0);
+ _sdk_log("setup done\n");
+ return 1;
+}
+
+/* Low-level command API */
+
+int CdCommandF(CdlCommand cmd, const void *param, int length) {
+ const uint8_t *_param = (const uint8_t *) param;
+
+ _last_command = (uint8_t) cmd;
+ _ack_pending = 1;
+
+ if (cmd <= CdlReadTOC) {
+ if (_command_flags[cmd] & BLOCKING)
+ _sync_pending = 1;
+
+ // Keep track of the last mode and seek location set (so retries can be
+ // attempted).
+
+ if (cmd == CdlSetloc) {
+ _last_pos.minute = _param[0];
+ _last_pos.second = _param[1];
+ _last_pos.sector = _param[2];
+ } else if (cmd == CdlSetmode) {
+ _last_mode = _param[0];
+ }
+ }
+
+ // Request a command FIFO write.
+ while (CD_REG(0) & 0x80)
+ __asm__ volatile("");
+
+ CD_REG(0) = 1;
+ CD_REG(3) = 0x40; // Reset parameter buffer
+
+ //while (CD_REG(0) & (1 << 5))
+ //CD_REG(1);
+ for (int i = 0; i < 50; i++)
+ __asm__ volatile("");
+
+ // Wait for the FIFO to become ready, then send the parameters followed by
+ // the command index.
+ while (CD_REG(0) & (1 << 7))
+ __asm__ volatile("");
+
+ CD_REG(0) = 0;
+ for (; length; length--)
+ CD_REG(2) = *(_param++);
+
+ CD_REG(0) = 0;
+ CD_REG(1) = (uint8_t) cmd;
+ return 1;
+}
+
+int CdCommand(CdlCommand cmd, const void *param, int length, uint8_t *result) {
+ /*if (_ack_pending) {
+ _sdk_log("CdCommand(0x%02x) failed, drive busy\n", cmd);
+ return 0;
+ }*/
+
+ _result_ptr = result;
+ CdCommandF(cmd, param, length);
+
+ // Wait for the command to be acknowledged.
+ for (int i = CD_ACK_TIMEOUT; i; i--) {
+ if (!_ack_pending)
+ return 1;
+ }
+
+ _sdk_log("CdCommand(0x%02x) failed, acknowledge timeout\n", cmd);
+ return 0;
+}
+
+/* High-level command API */
+
+int CdControlF(CdlCommand cmd, const void *param) {
+ // Assume no parameters need to be passed if the command is unknown.
+ uint8_t flags = (cmd <= CdlReadTOC) ? _command_flags[cmd] : 0;
+ int length;
+
+ if (flags & OPTIONAL) {
+ // The command may optionally take a parameter.
+ length = param ? (flags & 3) : 0;
+ } else if (flags & SETLOC) {
+ // The command takes no parameter, but the API allows specifying a
+ // location to be sent to the drive as a separate CdlSetloc command.
+ length = 0;
+ if (param)
+ CdCommandF(CdlSetloc, param, 3);
+ } else {
+ // The command takes a mandatory parameter or no parameter.
+ length = flags & 3;
+ if (length && !param)
+ return -1;
+ }
+
+ return CdCommandF(cmd, param, length);
+}
+
+int CdControl(CdlCommand cmd, const void *param, uint8_t *result) {
+ /*if (_ack_pending) {
+ _sdk_log("CdControl(0x%02x) failed, drive busy\n", cmd);
+ return 0;
+ }*/
+
+ _result_ptr = result;
+ CdControlF(cmd, param);
+
+ // Wait for the command to be acknowledged.
+ for (int i = CD_ACK_TIMEOUT; i; i--) {
+ if (!_ack_pending)
+ return 1;
+ }
+
+ _sdk_log("CdControl(0x%02x) failed, acknowledge timeout\n", cmd);
+ return 0;
+}
+
+int CdControlB(CdlCommand cmd, const void *param, uint8_t *result) {
+ int error = CdControl(cmd, param, result);
+ if (error != 1)
+ return error;
+
+ error = CdSync(0, 0);
+ return (error == CdlDiskError) ? 0 : 1;
+}
+
+/* Other APIs and callback setters */
+
+CdlIntrResult CdSync(int mode, uint8_t *result) {
+ if (mode) {
+ if (_sync_pending)
+ return CdlNoIntr;
+
+ if (result)
+ *result = _last_status;
+ if (_last_irq == CdlAcknowledge)
+ return CdlComplete;
+
+ return _last_irq;
+ }
+
+ for (int i = CD_SYNC_TIMEOUT; i; i--) {
+ if (!_sync_pending)
+ return CdSync(1, result); // :P
+ }
+
+ _sdk_log("CdSync() timeout\n");
+ return -1;
+}
+
+CdlCommand CdLastCom(void) {
+ return _last_command;
+}
+
+const CdlLOC *CdLastPos(void) {
+ return &_last_pos;
+}
+
+int CdMode(void) {
+ return _last_mode;
+}
+
+int CdStatus(void) {
+ return _last_status;
+}
+
+CdlCB CdReadyCallback(CdlCB func) {
+ FastEnterCriticalSection();
+
+ CdlCB old_callback = _ready_callback;
+ _ready_callback = func;
+
+ FastExitCriticalSection();
+ return old_callback;
+}
+
+CdlCB CdSyncCallback(CdlCB func) {
+ FastEnterCriticalSection();
+
+ CdlCB old_callback = _sync_callback;
+ _sync_callback = func;
+
+ FastExitCriticalSection();
+ return old_callback;
+}
+
+CdlCB CdAutoPauseCallback(CdlCB func) {
+ FastEnterCriticalSection();
+
+ CdlCB old_callback = _pause_callback;
+ _pause_callback = func;
+
+ FastExitCriticalSection();
+ return old_callback;
+}
diff --git a/libpsn00b/psxcd/getsector.c b/libpsn00b/psxcd/getsector.c
deleted file mode 100644
index a214d7a..0000000
--- a/libpsn00b/psxcd/getsector.c
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * PSn00bSDK CD drive library (sector DMA API)
- * (C) 2022 spicyjpeg - MPL licensed
- */
-
-#include <stdint.h>
-#include <assert.h>
-#include <psxcd.h>
-#include <hwregs_c.h>
-
-#define DATA_SYNC_TIMEOUT 0x100000
-
-/* DMA transfer functions */
-
-int CdGetSector(void *madr, int size) {
- //while (!(CD_STAT & (1 << 6)))
- //__asm__ volatile("");
-
- DMA_MADR(3) = (uint32_t) madr;
- DMA_BCR(3) = size | (1 << 16);
- DMA_CHCR(3) = 0x11000000;
-
- while (DMA_CHCR(3) & (1 << 24))
- __asm__ volatile("");
-
- return 1;
-}
-
-int CdGetSector2(void *madr, int size) {
- //while (!(CD_STAT & (1 << 6)))
- //__asm__ volatile("");
-
- DMA_MADR(3) = (uint32_t) madr;
- DMA_BCR(3) = size | (1 << 16);
- DMA_CHCR(3) = 0x11400100; // Transfer 1 word every 16 CPU cycles
-
- return 1;
-}
-
-int CdDataSync(int mode) {
- if (mode)
- return (DMA_CHCR(3) >> 24) & 1;
-
- for (int i = DATA_SYNC_TIMEOUT; i; i--) {
- if (!(DMA_CHCR(3) & (1 << 24)))
- return 0;
- }
-
- _sdk_log("CdDataSync() timeout\n");
- return -1;
-}
diff --git a/libpsn00b/psxcd/isofs.c b/libpsn00b/psxcd/isofs.c
index e00ddeb..0ac782b 100644
--- a/libpsn00b/psxcd/isofs.c
+++ b/libpsn00b/psxcd/isofs.c
@@ -11,6 +11,7 @@
#include <psxcd.h>
#include "isofs.h"
+#define CD_READ_ATTEMPTS 3
#define DEFAULT_PATH_SEP '\\'
#define IS_PATH_SEP(ch) (((ch) == '/') || ((ch) == '\\'))
@@ -21,7 +22,7 @@ typedef struct _CdlDIR_INT
uint8_t *_dir;
} CdlDIR_INT;
-extern int _cd_media_changed;
+extern volatile int _cd_media_changed;
static int _cd_iso_last_dir_lba;
@@ -77,7 +78,7 @@ static int _CdReadIsoDescriptor(int session_offs)
_sdk_log("Read sectors.\n");
// Read volume descriptor
- CdRead(1, (uint32_t*)_cd_iso_descriptor_buff, CdlModeSpeed);
+ CdReadRetry(1, (uint32_t*)_cd_iso_descriptor_buff, CdlModeSpeed, CD_READ_ATTEMPTS);
if( CdReadSync(0, 0) )
{
@@ -115,7 +116,7 @@ static int _CdReadIsoDescriptor(int session_offs)
// Read path table
CdIntToPos(descriptor->pathTable1Offs, &loc);
CdControl(CdlSetloc, (uint8_t*)&loc, 0);
- CdRead(i>>11, (uint32_t*)_cd_iso_pathtable_buff, CdlModeSpeed);
+ CdReadRetry(i>>11, (uint32_t*)_cd_iso_pathtable_buff, CdlModeSpeed, CD_READ_ATTEMPTS);
if( CdReadSync(0, 0) )
{
_sdk_log("Error reading ISO path table.\n");
@@ -163,7 +164,7 @@ static int _CdReadIsoDirectory(int lba)
// Read first sector of directory record
_cd_iso_directory_buff = (uint8_t*)malloc(2048);
- CdRead(1, (uint32_t*)_cd_iso_directory_buff, CdlModeSpeed);
+ CdReadRetry(1, (uint32_t*)_cd_iso_directory_buff, CdlModeSpeed, CD_READ_ATTEMPTS);
if( CdReadSync(0, 0) )
{
_sdk_log("Error reading initial directory record.\n");
@@ -194,7 +195,7 @@ static int _CdReadIsoDirectory(int lba)
_sdk_log("Allocated %d bytes for directory record.\n", i);
- CdRead(i>>11, (uint32_t*)_cd_iso_directory_buff, CdlModeSpeed);
+ CdReadRetry(i>>11, (uint32_t*)_cd_iso_directory_buff, CdlModeSpeed, CD_READ_ATTEMPTS);
if( CdReadSync(0, 0) )
{
_sdk_log("Error reading remaining directory record.\n");
@@ -485,13 +486,13 @@ CdlFILE *CdSearchFile(CdlFILE *fp, const char *filename)
}
else
{
- _sdk_log("Longest path: %s|\n", rbuff);
+ _sdk_log("Longest path: %s\n", rbuff);
}
#endif
if( get_pathname(search_path, filename) )
{
- _sdk_log("Search path = %s|\n", search_path);
+ _sdk_log("Search path = %s\n", search_path);
}
// Search the pathtable for a matching path
@@ -499,7 +500,7 @@ CdlFILE *CdSearchFile(CdlFILE *fp, const char *filename)
for(i=1; i<num_dirs; i++)
{
rbuff = resolve_pathtable_path(i, tpath_rbuff+127);
- _sdk_log("Found = %s|\n", rbuff);
+ _sdk_log("Found = %s\n", rbuff);
if( rbuff )
{
@@ -580,7 +581,7 @@ CdlDIR *CdOpenDir(const char* path)
for( i=1; i<num_dirs; i++ )
{
rbuff = resolve_pathtable_path( i, tpath_rbuff+127 );
- _sdk_log( "Found = %s|\n", rbuff );
+ _sdk_log( "Found = %s\n", rbuff );
if( rbuff )
{
@@ -666,11 +667,9 @@ int CdReadDir(CdlDIR *dir, CdlFILE* file)
file->size = dir_entry->entrySize.lsb;
- _sdk_log("dir_entry->entryLength = %d, ", dir_entry->entryLength);
-
d_dir->_pos += dir_entry->entryLength;
- _sdk_log("d_dir->_pos = %d\n", d_dir->_pos);
+ _sdk_log("dir_entry->entryLength = %d, d_dir->_pos = %d\n", dir_entry->entryLength, d_dir->_pos);
// Check if padding is reached (end of record sector)
if( d_dir->_dir[d_dir->_pos] == 0 )
@@ -697,34 +696,29 @@ int CdIsoError()
return _cd_iso_error;
}
-int CdGetVolumeLabel(char* label)
+int CdGetVolumeLabel(char *label)
{
- int i;
+ int i, length = 31;
ISO_DESCRIPTOR* descriptor;
-
+
if( _CdReadIsoDescriptor(0) )
- {
return -1;
- }
-
+
descriptor = (ISO_DESCRIPTOR*)_cd_iso_descriptor_buff;
-
- i = 0;
- for( i=0; (descriptor->volumeID[i]!=0x20)&&(i<32); i++ )
- {
- label[i] = descriptor->volumeID[i];
- }
-
- label[i] = 0x00;
-
- return 0;
+
+ while (descriptor->volumeID[length] == 0x20)
+ length--;
+
+ length++;
+ memcpy(label, descriptor->volumeID, length);
+ label[length] = 0x00;
+
+ return length;
}
// Session load routine
-void _cd_control(unsigned char com, unsigned char *param, int plen);
-
static volatile unsigned int _ready_oldcb;
static volatile int _ses_scanfound;
@@ -733,7 +727,7 @@ static volatile int _ses_scancomplete;
//static volatile char _ses_scan_resultbuff[8];
static volatile char *_ses_scanbuff;
-static void _scan_callback(int status, unsigned char *result)
+static void _scan_callback(CdlIntrResult status, unsigned char *result)
{
if( status == CdlDataReady )
{
@@ -743,7 +737,7 @@ static void _scan_callback(int status, unsigned char *result)
{
if( strncmp((const char*)_ses_scanbuff+1, "CD001", 5) == 0 )
{
- _cd_control(CdlPause, 0, 0);
+ CdControlF(CdlPause, 0);
_ses_scancomplete = 1;
_ses_scanfound = 1;
return;
@@ -752,7 +746,7 @@ static void _scan_callback(int status, unsigned char *result)
_ses_scancount++;
if( _ses_scancount >= 512 )
{
- _cd_control(CdlPause, 0, 0);
+ CdControlF(CdlPause, 0);
_ses_scancomplete = 1;
return;
}
@@ -760,7 +754,7 @@ static void _scan_callback(int status, unsigned char *result)
if( status == CdlDiskError )
{
- _cd_control(CdlPause, 0, 0);
+ CdControlF(CdlPause, 0);
_ses_scancomplete = 1;
}
}
@@ -768,7 +762,7 @@ static void _scan_callback(int status, unsigned char *result)
int CdLoadSession(int session)
{
CdlLOC *loc;
- unsigned int ready_oldcb;
+ CdlCB ready_oldcb;
char scanbuff[2048];
char resultbuff[16];
int i;
@@ -791,9 +785,7 @@ int CdLoadSession(int session)
}
// Set search routine callback
- EnterCriticalSection();
ready_oldcb = CdReadyCallback(_scan_callback);
- ExitCriticalSection();
_ses_scanfound = 0;
_ses_scancount = 0;
@@ -810,26 +802,20 @@ int CdLoadSession(int session)
// Wait until scan complete
while(!_ses_scancomplete);
- EnterCriticalSection();
CdReadyCallback((void*)_ready_oldcb);
- ExitCriticalSection();
if( !_ses_scanfound )
{
_sdk_log("CdLoadSession(): Did not find volume descriptor.\n");
_cd_iso_error = CdlIsoInvalidFs;
- EnterCriticalSection();
CdReadyCallback((CdlCB)ready_oldcb);
- ExitCriticalSection();
return -1;
}
// Restore old callback if any
- EnterCriticalSection();
CdReadyCallback((CdlCB)ready_oldcb);
- ExitCriticalSection();
// Wait until CD-ROM has completely stopped reading, to get a consistent
// fix of the CD-ROM pickup's current location
diff --git a/libpsn00b/psxcd/misc.c b/libpsn00b/psxcd/misc.c
new file mode 100644
index 0000000..8fd2a4d
--- /dev/null
+++ b/libpsn00b/psxcd/misc.c
@@ -0,0 +1,151 @@
+/*
+ * PSn00bSDK CD-ROM library (misc. functions)
+ * (C) 2020-2022 Lameguy64, spicyjpeg - MPL licensed
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <psxetc.h>
+#include <psxcd.h>
+#include <hwregs_c.h>
+
+#define DATA_SYNC_TIMEOUT 0x100000
+
+/* Private types */
+
+typedef struct {
+ uint8_t status, first_track, last_track;
+} TrackInfo;
+
+/* Sector DMA transfer functions */
+
+int CdGetSector(void *madr, int size) {
+ //while (!(CD_REG(0) & (1 << 6)))
+ //__asm__ volatile("");
+
+ DMA_MADR(DMA_CD) = (uint32_t) madr;
+ DMA_BCR(DMA_CD) = size | (1 << 16);
+ DMA_CHCR(DMA_CD) = 0x11000000;
+
+ while (DMA_CHCR(DMA_CD) & (1 << 24))
+ __asm__ volatile("");
+
+ return 1;
+}
+
+int CdGetSector2(void *madr, int size) {
+ //while (!(CD_REG(0) & (1 << 6)))
+ //__asm__ volatile("");
+
+ DMA_MADR(DMA_CD) = (uint32_t) madr;
+ DMA_BCR(DMA_CD) = size | (1 << 16);
+ DMA_CHCR(DMA_CD) = 0x11400100; // Transfer 1 word every 16 CPU cycles
+
+ return 1;
+}
+
+int CdDataSync(int mode) {
+ if (mode)
+ return (DMA_CHCR(DMA_CD) >> 24) & 1;
+
+ for (int i = DATA_SYNC_TIMEOUT; i; i--) {
+ if (!(DMA_CHCR(DMA_CD) & (1 << 24)))
+ return 0;
+ }
+
+ _sdk_log("CdDataSync() timeout\n");
+ return -1;
+}
+
+/* LBA/MSF conversion */
+
+CdlLOC *CdIntToPos(int i, CdlLOC *p) {
+ i += 150;
+
+ p->minute = itob(i / (75 * 60));
+ p->second = itob((i / 75) % 60);
+ p->sector = itob(i % 75);
+ return p;
+}
+
+int CdPosToInt(const CdlLOC *p) {
+ return (
+ (btoi(p->minute) * (75 * 60)) +
+ (btoi(p->second) * 75) +
+ btoi(p->sector)
+ ) - 150;
+}
+
+/* Misc. functions */
+
+int CdGetToc(CdlLOC *toc) {
+ TrackInfo track_info;
+
+ if (!CdCommand(CdlGetTN, 0, 0, (uint8_t *) &track_info))
+ return 0;
+ if (CdSync(1, 0) != CdlComplete)
+ return 0;
+
+ int first = btoi(track_info.first_track);
+ int tracks = btoi(track_info.last_track) + 1 - first;
+ //assert(first == 1);
+
+ for (int i = 0; i < tracks; i++) {
+ uint8_t track = itob(first + i);
+
+ if (!CdCommand(CdlGetTD, &track, 1, (uint8_t *) &toc[i]))
+ return 0;
+ if (CdSync(1, 0) != CdlComplete)
+ return 0;
+
+ toc[i].sector = 0;
+ toc[i].track = track;
+ }
+
+ return tracks;
+}
+
+CdlRegionCode CdGetRegion(void) {
+ uint8_t param = 0x22;
+ uint8_t result[16];
+
+ // Test command 0x22 is unsupported in firmware version C0, which was used
+ // exclusively in the SCPH-1000 Japanese model. It's thus safe to assume
+ // that the console is Japanese if the command returns a valid error.
+ // https://psx-spx.consoledev.net/cdromdrive/#19h22h-int3for-europe
+ memset(result, 0, 16);
+
+ if (!CdCommand(CdlTest, &param, 1, result)) {
+ _sdk_log("failed to probe drive region\n");
+ return (result[1] == 0x10) ? CdlRegionSCEI : CdlRegionUnknown;
+ }
+
+ _sdk_log("drive region: %s\n", result);
+
+ if (!strcmp(result, "for Japan"))
+ return CdlRegionSCEI;
+ if (!strcmp(result, "for U/C"))
+ return CdlRegionSCEA;
+ if (!strcmp(result, "for Europe"))
+ return CdlRegionSCEE;
+ if (!strcmp(result, "for NETNA") || !strcmp(result, "for NETEU"))
+ return CdlRegionSCEW;
+ if (!strcmp(result, "for US/AEP"))
+ return CdlRegionDebug;
+
+ return CdlRegionUnknown;
+}
+
+int CdMix(const CdlATV *vol) {
+ CD_REG(0) = 2;
+ CD_REG(2) = vol->val0;
+ CD_REG(3) = vol->val1;
+
+ CD_REG(0) = 3;
+ CD_REG(1) = vol->val2;
+ CD_REG(2) = vol->val3;
+
+ CD_REG(3) = 0x20; // Unmute XA, apply volume changes
+ return 1;
+}
diff --git a/libpsn00b/psxcd/psxcd.c b/libpsn00b/psxcd/psxcd.c
deleted file mode 100644
index 9392d30..0000000
--- a/libpsn00b/psxcd/psxcd.c
+++ /dev/null
@@ -1,371 +0,0 @@
-#include <stdint.h>
-#include <assert.h>
-#include <psxgpu.h>
-#include <psxapi.h>
-#include <psxcd.h>
-
-#define READ_TIMEOUT 600 // 10 seconds for NTSC
-
-extern volatile char _cd_ack_wait;
-extern volatile uint8_t _cd_last_int;
-extern volatile uint8_t _cd_last_mode;
-extern volatile uint8_t _cd_status;
-extern volatile CdlCB _cd_callback_int1_data;
-
-volatile uint8_t *_cd_result_ptr;
-
-// For read retry
-volatile CdlLOC _cd_last_setloc;
-volatile uint32_t *_cd_last_read_addr;
-volatile int _cd_last_sector_count;
-
-int _cd_media_changed;
-
-void _cd_init(void);
-void _cd_control(unsigned char com, const void *param, int plen);
-void _cd_wait_ack(void);
-void _cd_wait(void);
-
-int CdInit(void) {
- // Sets up CD-ROM hardware and low-level subsystem
- _cd_init();
-
- // So CD ISO file system component will update the ISO descriptor
- _cd_media_changed = 1;
-
- // Issue commands to initialize the CD-ROM hardware
- CdControl(CdlNop, 0, 0);
- CdControl(CdlInit, 0, 0);
-
- if(CdSync(0, 0) != CdlDiskError) {
- CdControl(CdlDemute, 0, 0);
- _sdk_log("setup done\n");
- } else {
- _sdk_log("setup error, bad disc/drive or no disc inserted\n");
- }
-
- return 1;
-}
-
-int CdControl(unsigned char com, const void *param, unsigned char *result)
-{
- // Don't issue command if ack is not received yet
- if( _cd_ack_wait )
- {
- return 0;
- }
-
- _cd_result_ptr = result;
-
- CdControlF(com, param);
- _cd_wait_ack();
-
- // Set media changed flag if lid had been opened
- if( (CdStatus()&0x10) )
- {
- _cd_media_changed = 1;
- }
-
- return 1;
-}
-
-int CdControlB(unsigned char com, const void *param, unsigned char *result)
-{
- if( !CdControl(com, param, result) )
- {
- return 0;
- }
-
- CdSync(0, 0);
- return 1;
-}
-
-int CdControlF(unsigned char com, const void *param)
-{
- int param_len=0;
-
- // Command specific parameters
- switch(com)
- {
- case CdlSetloc:
- param_len = 3;
- _cd_last_setloc = *((CdlLOC*)param);
- break;
- case CdlPlay:
- if( param )
- {
- param_len = 1;
- }
- break;
- case CdlSetfilter:
- param_len = 2;
- break;
- case CdlSetmode:
- case CdlSetsession:
- case CdlTest:
- case CdlGetTD:
- param_len = 1;
- break;
- case CdlReadN:
- case CdlReadS:
- case CdlSeekL:
- case CdlSeekP:
- if( param )
- {
- _cd_control(CdlSetloc, param, 3);
- _cd_last_setloc = *((CdlLOC*)param);
- }
- break;
- }
-
- // Issue CD command
- _cd_control(com, param, param_len);
-
- return 1;
-}
-
-int CdSync(int mode, unsigned char *result)
-{
- int cdirq;
-
- if( mode )
- {
- if( result )
- {
- *result = _cd_status;
- }
-
- cdirq = _cd_last_int;
- if( cdirq == CdlAcknowledge )
- {
- cdirq = CdlComplete;
- }
- return cdirq;
- }
-
- _cd_wait();
-
- if( result )
- {
- *result = _cd_status;
- }
-
- cdirq = _cd_last_int;
- if( cdirq == CdlAcknowledge )
- {
- cdirq = CdlComplete;
- }
-
- return cdirq;
-}
-
-int CdGetToc(CdlLOC *toc)
-{
- uint8_t track_info[8];
- int i,tracks;
-
- // Get number of tracks
- if( !CdControl(CdlGetTN, 0, track_info) )
- {
- return 0;
- }
-
- if( CdSync(1, 0) != CdlComplete )
- {
- return 0;
- }
-
- tracks = 1+(btoi(track_info[2])-btoi(track_info[1]));
-
- // Get track positions
- for(i=0; i<tracks; i++)
- {
- int t = itob(1+i);
- if( !CdControl(CdlGetTD, (uint8_t*)&t, (uint8_t*)&toc[i]) )
- {
- return 0;
- }
- if( CdSync(1, 0) != CdlComplete )
- {
- return 0;
- }
- toc[i].sector = 0;
- toc[i].track = 1+i;
- }
-
- return tracks;
-}
-
-CdlLOC *CdIntToPos(int i, CdlLOC *p) {
-
- i += 150;
-
- p->minute = itob((i/75)/60);
- p->second = itob((i/75)%60);
- p->sector = itob(i%75);
-
- return p;
-
-}
-
-int CdPosToInt(const CdlLOC *p)
-{
- return ((75*(btoi(p->minute)*60))+(75*btoi(p->second))+btoi(p->sector))-150;
-}
-
-int CdStatus(void)
-{
- return _cd_status;
-}
-
-int CdMode(void)
-{
- return _cd_last_mode;
-}
-
-
-// CD data read routines
-volatile int _cd_sector_count = 0;
-volatile uint32_t *_cd_read_addr;
-volatile uint8_t _cd_read_result[8];
-volatile uint32_t _cd_read_oldcb;
-volatile uint32_t _cd_read_sector_sz;
-volatile uint32_t _cd_read_counter;
-
-
-
-volatile CdlCB _cd_read_cb;
-
-// Sector callback
-static void _CdReadReadyCallback(int status, unsigned char *result)
-{
- _cd_read_counter = VSync(-1);
-
- if( status == CdlDataReady )
- {
- // Fetch sector from CD controller
- CdGetSector((void*)_cd_read_addr, _cd_read_sector_sz);
-
- // Increment destination address
- _cd_read_addr += _cd_read_sector_sz;
-
- // Subtract sector count
- _cd_sector_count--;
- }
-
- // End reading with pause command when sector count reaches zero
- // or when an error occurs
- if( ( _cd_sector_count <= 0 ) || ( status == CdlDiskError ) )
- {
- // Stop reading
- _cd_control(CdlPause, 0, 0);
-
- // Revert previous ready callback
- _cd_callback_int1_data = (CdlCB)_cd_read_oldcb;
-
- // Execute read completion callback
- if( _cd_read_cb )
- {
- _cd_read_cb(status, result);
- }
- }
-}
-
-int CdRead(int sectors, uint32_t *buf, int mode)
-{
- // Set sectors to read count
- _cd_sector_count = sectors;
- _cd_last_sector_count = sectors;
- _cd_last_read_addr = buf;
- _cd_read_addr = buf;
-
- // Determine sector based on mode flags
- if( mode & CdlModeSize )
- _cd_read_sector_sz = 2340 / 4;
- else
- _cd_read_sector_sz = 2048 / 4;
-
- _cd_read_counter = VSync(-1);
-
- // Set read callback
- EnterCriticalSection();
- _cd_read_oldcb = CdReadyCallback(_CdReadReadyCallback);
- ExitCriticalSection();
-
- // Set specified mode
- CdControl(CdlSetmode, (uint8_t*)&mode, 0);
-
- // Begin reading sectors
- CdControl(CdlReadN, 0, (uint8_t*)_cd_read_result);
-
- return 0;
-}
-
-static void CdDoRetry()
-{
- int cb;
-
- _sdk_log("retrying read...\n");
-
- // Stop reading
- CdControl(CdlPause, 0, 0);
- CdSync(0, 0);
-
- // Reset parameters for retrying read operation
- _cd_sector_count = _cd_last_sector_count;
- _cd_read_addr = _cd_last_read_addr;
-
- // Reset timeout
- _cd_read_counter = VSync(-1);
-
- EnterCriticalSection();
- CdReadyCallback(_CdReadReadyCallback);
- ExitCriticalSection();
-
- // Retry read
- CdControl(CdlSetloc, (void*)&_cd_last_setloc, 0);
- CdControl(CdlReadN, 0, (uint8_t*)_cd_read_result);
-}
-
-int CdReadSync(int mode, uint8_t *result)
-{
- if( (VSync(-1)-_cd_read_counter) > READ_TIMEOUT )
- {
- CdDoRetry();
- }
-
- if( mode )
- {
- if( CdSync(1, 0) == CdlDiskError )
- {
- return -1;
- }
- return _cd_sector_count;
- }
-
- while(_cd_sector_count > 0)
- {
- if( (VSync(-1)-_cd_read_counter) > READ_TIMEOUT )
- {
- CdDoRetry();
- }
- }
-
- if( CdSync(0, result) != CdlComplete )
- {
- return -1;
- }
-
- return 0;
-}
-
-uint32_t CdReadCallback(CdlCB func)
-{
- unsigned int old_func;
-
- old_func = (unsigned int)_cd_read_cb;
-
- _cd_read_cb = func;
-
- return old_func;
-}
diff --git a/libpsn00b/psxcd/psxcd_asm.s b/libpsn00b/psxcd/psxcd_asm.s
deleted file mode 100644
index c0a5312..0000000
--- a/libpsn00b/psxcd/psxcd_asm.s
+++ /dev/null
@@ -1,487 +0,0 @@
-.set noreorder
-
-.include "hwregs_a.inc"
-
-.section .text
-
-.global _cd_init
-.type _cd_init, @function
-_cd_init:
-
- addiu $sp, -4
- sw $ra, 0($sp)
-
- jal EnterCriticalSection
- nop
-
- lui $a3, IOBASE # Acknowledge all CD IRQs
-
- li $v0, 0x20943 # Set CD-ROM Delay/Size and common delay
- sw $v0, CD_DELAY_SIZE($a3)
- li $v0, 0x1325
- sw $v0, COM_DELAY_CFG($a3)
-
- li $v0, 1
- sb $v0, CD_REG0($a3)
- li $v0, 0x1f
- sb $v0, CD_REG3($a3)
- sb $v0, CD_REG2($a3) # Enable all IRQs
-
- sb $0 , CD_REG0($a3)
- sb $0 , CD_REG3($a3)
-
- la $a1, _cd_handler
- jal InterruptCallback
- li $a0, 2
-
- li $v0, 2 # Set CD left volume
- sb $v0, CD_REG0($a3)
- li $v0, 0x80
- sb $v0, CD_REG2($a3)
-
- li $v0, 3 # Set CD right volume
- sb $v0, CD_REG0($a3)
- li $v0, 0x80
- sb $v0, CD_REG1($a3)
-
- li $v0, 0x20 # Apply volume
- sb $v0, CD_REG3($a3)
-
- # Clear a bunch of stats
- la $v0, _cd_ack_wait
- sb $0 , 0($v0)
- la $v0, _cd_complt_wait
- sb $0 , 0($v0)
- la $v0, _cd_last_cmd
- sb $0 , 0($v0)
- la $v0, _cd_last_mode
- sb $0 , 0($v0)
- la $v0, _cd_last_int
- sb $0 , 0($v0)
-
- la $v0, _cd_result_ptr
- sw $0 , 0($v0)
-
- # Clear callback hooks
- la $v0, _cd_callback_int1_data
- sw $0 , 0($v0)
- la $v0, _cd_callback_int4
- sw $0 , 0($v0)
-
- la $v0, _cd_read_cb
- sw $0 , 0($v0)
-
- lw $v0, DMA_DPCR($a3)
- li $v1, 0xB000
- or $v0, $v1
- sw $v0, DMA_DPCR($a3)
-
- jal ExitCriticalSection
- nop
-
- lw $ra, 0($sp)
- addiu $sp, 4
- jr $ra
- nop
-
-
-.global _cd_wait
-.type _cd_wait, @function
-_cd_wait:
- la $v0, _cd_ack_wait
- lbu $v0, 0($v0)
- nop
- bnez $v0, _cd_wait
- nop
-.Lcomplete:
- la $v0, _cd_complt_wait
- lbu $v0, 0($v0)
- nop
- bnez $v0, .Lcomplete
- nop
- jr $ra
- nop
-
-
-.global _cd_wait_ack
-.type _cd_wait_ack, @function
-_cd_wait_ack:
- la $v0, _cd_ack_wait
- lbu $v0, 0($v0)
- nop
- bnez $v0, _cd_wait_ack
- nop
- jr $ra
- nop
-
-
-.global _cd_wait_complt
-.type _cd_wait_complt, @function
-_cd_wait_complt:
- la $v0, _cd_complt_wait
- lbu $v0, 0($v0)
- nop
- bnez $v0, _cd_wait_complt
- nop
- jr $ra
- nop
-
-
-.type _cd_handler, @function
-_cd_handler:
-
- addiu $sp, -4
- sw $ra, 0($sp)
-
- lui $a3, IOBASE # Get IRQ number
- li $v0, 1
- sb $v0, CD_REG0($a3)
-
- lbu $v0, CD_REG3($a3)
- nop
- andi $v0, 0x7
-
- la $v1, _cd_last_int # Save last IRQ value
- sb $v0, 0($v1)
-
- bne $v0, 0x1, .Lno_data
- nop
-
- sb $0 , CD_REG0($a3)
- lbu $v1, CD_REG0($a3)
- sb $0 , CD_REG3($a3)
- lbu $v1, CD_REG3($a3)
-
- sb $0 , CD_REG0($a3) # Load data FIFO on INT1
- li $v1, 0x80
- sb $v1, CD_REG3($a3)
-
-.Lno_data:
-
- li $v1, 1
- sb $v1, CD_REG0($a3) # Clear CD interrupt and parameter FIFO
- li $v1, 0x5f
- sb $v1, CD_REG3($a3)
- li $v1, 0x40
- sb $v1, CD_REG3($a3)
-
- li $v1, 0 # Delay when clearing parameter FIFO
- sw $v1, 0($0)
- li $v1, 1
- sw $v1, 0($0)
- li $v1, 2
- sw $v1, 0($0)
- li $v1, 3
- sw $v1, 0($0)
-
- la $v1, _cd_last_int
- lbu $v0, 0($v1)
-
- beq $v0, 0x1, .Lirq_1 # Data ready
- nop
- beq $v0, 0x2, .Lirq_2 # Command finish
- nop
- beq $v0, 0x3, .Lirq_3 # Acknowledge
- nop
- beq $v0, 0x4, .Lirq_4 # Data/track end
- nop
- beq $v0, 0x5, .Lirq_5 # Error
- nop
-
- b .Lirq_misc
- nop
-
-.Lirq_1: # Data ready
-
- jal _cd_fetch_result
- nop
-
- la $v0, _cd_callback_int1_data
- lw $v0, 0($v0)
- nop
- beqz $v0, .Lirq_misc
- nop
-
- la $a0, _cd_last_int
- lbu $a0, 0($a0)
- la $a1, _cd_result_ptr
- lw $a1, 0($a1)
-
- jalr $v0
- addiu $sp, -16
- addiu $sp, 16
-
- b .Lirq_misc
- nop
-
-.Lirq_2: # Command complete
-
- jal _cd_fetch_result
- nop
-
- la $v0, _cd_complt_wait
- sb $0 , 0($v0)
-
- la $v0, _cd_sync_cb
- lw $v0, 0($v0)
- nop
- beqz $v0, .Lirq_misc
- nop
-
- la $a0, _cd_last_int
- lbu $a0, 0($a0)
- la $a1, _cd_result_ptr
- lw $a1, 0($a1)
- jalr $v0
- addiu $sp, -16
- addiu $sp, 16
-
- b .Lirq_misc
- nop
-
-.Lirq_3: # Command acknowledge
-
- jal _cd_fetch_result
- nop
-
- la $v0, _cd_ack_wait
- sb $0 , 0($v0)
-
- b .Lirq_misc
- nop
-
-.Lirq_4:
-
- jal _cd_fetch_result
- nop
-
- la $v0, _cd_callback_int4
- lw $v0, 0($v0)
- nop
- beqz $v0, .Lirq_misc
- nop
-
- jalr $v0
- addiu $sp, -16
- addiu $sp, 16
-
- b .Lirq_misc
- nop
-
-.Lirq_5: # Error
-
- jal _cd_fetch_result
- nop
-
- la $v0, _cd_complt_wait
- lbu $v0, 0($v0)
- nop
- beqz $v0, .Lno_complete
- nop
-
- la $v0, _cd_sync_cb
- lw $v0, 0($v0)
- nop
- beqz $v0, .Lno_complete
- nop
-
- li $a0, 0x05 # CdlDiskError
- la $a1, _cd_result_ptr
- lw $a1, 0($a1)
- jalr $v0
- addiu $sp, -16
- addiu $sp, 16
-
-.Lno_complete:
-
- la $v0, _cd_complt_wait
- sb $0 , 0($v0)
- la $v0, _cd_ack_wait
- sb $0 , 0($v0)
-
- la $v0, _cd_callback_int1_data
- lw $v0, 0($v0)
- nop
- beqz $v0, .Lirq_misc
- nop
-
- li $a0, 0x05 # CdlDiskError
- la $a1, _cd_result_ptr
- lw $a1, 0($a1)
-
- jalr $v0
- addiu $sp, -16
- addiu $sp, 16
-
-.Lirq_misc:
-
- lw $ra, 0($sp)
- addiu $sp, 4
- jr $ra
- nop
-
-
-_cd_fetch_result:
-
- lui $a3, IOBASE
-
- la $a0, _cd_status
- lbu $v0, CD_REG1($a3)
-
- la $v1, _cd_last_int
- lbu $v1, 0($v1)
- nop
- beq $v1, 0x2, .Lirq2_checks
- nop
-
- # IRQ3 checks
-
- la $v1, _cd_last_cmd
- lbu $v1, 0($v1)
- nop
- beq $v1, 0x10, .Lskip_status
- nop
- beq $v1, 0x11, .Lskip_status
- nop
-
- b .Lwrite_status
- nop
-
- # IRQ2 checks
-
-.Lirq2_checks:
-
- la $v1, _cd_last_cmd
- lbu $v1, 0($v1)
- nop
- beq $v1, 0x1D, .Lskip_status
- nop
-
-.Lwrite_status:
-
- sb $v0, 0($a0)
-
-.Lskip_status:
-
- la $a0, _cd_result_ptr
- lw $a0, 0($a0)
- nop
- beqz $a0, .Lno_result
- nop
- sb $v0, 0($a0)
- addiu $a0, 1
-
- move $a1, $0
-.Lread_futher_result:
-
- lbu $v0, CD_REG0($a3)
- nop
- andi $v0, 0x20
- beqz $v0, .Lno_result
- addu $a1, 1
- bge $a1, 7, .Lno_result # timeout (locks up here on PSIO)
- nop
- lbu $v0, CD_REG1($a3)
- lbu $v1, CD_REG0($a3)
- sb $v0, 0($a0)
-
- andi $v1, 0x20 # when performing a CD-ROM read
- bnez $v1, .Lread_futher_result
- addiu $a0, 1
-
-.Lno_result:
-
-# lbu $v0, CD_REG0($a3) # Flush response FIFO
-# nop
-# andi $v0, 0x20
-# bnez $v0, .Lno_result
-# lbu $v0, CD_REG1($a3)
-
- jr $ra
- nop
-
-
-.global CdAutoPauseCallback
-.type CdAutoPauseCallback, @function
-CdAutoPauseCallback:
-
- addiu $sp, -8
- sw $ra, 0($sp)
- sw $a0, 4($sp)
-
- la $v1, _cd_callback_int4
- lw $v0, 0($v1)
-
- la $v1, _cd_callback_int4
-
- lw $a0, 4($sp)
- nop
- sw $a0, 0($v1)
-
- lw $ra, 0($sp)
- addiu $sp, 8
- jr $ra
- nop
-
-
-.global CdReadyCallback
-.type CdReadyCallback, @function
-CdReadyCallback:
-
- addiu $sp, -12
- sw $ra, 0($sp)
- sw $a0, 4($sp)
-
- la $v1, _cd_callback_int1_data
- lw $v0, 0($v1)
-
- la $v1, _cd_callback_int1_data
- sw $v0, 8($sp)
-
- lw $a0, 4($sp)
- nop
- sw $a0, 0($v1)
-
- lw $ra, 0($sp)
- lw $v0, 8($sp)
- jr $ra
- addiu $sp, 12
-
-
-.global CdSyncCallback
-.type CdSyncCallback, @function
-CdSyncCallback:
- addiu $sp, -12
- sw $ra, 0($sp)
- sw $a0, 4($sp)
-
- la $v1, _cd_sync_cb
- lw $v0, 0($v1)
-
- la $v1, _cd_sync_cb
- sw $v0, 8($sp)
-
- lw $a0, 4($sp)
- nop
- sw $a0, 0($v1)
-
- lw $ra, 0($sp)
- lw $v0, 8($sp)
- jr $ra
- addiu $sp, 12
-
-
-.section .data
-
-.comm _cd_last_cmd, 1, 1
-.comm _cd_last_mode, 1, 1
-.comm _cd_ack_wait, 1, 1
-.comm _cd_complt_wait, 1, 1
-
-.comm _cd_status, 1, 1
-.comm _cd_last_int, 1, 1
-
-# Callback hooks
-.comm _cd_callback_int1_data, 4, 4 # Data IRQ callback
-.comm _cd_callback_int4, 4, 4 # Autopause callback
-.comm _cd_sync_cb, 4, 4
diff --git a/libpsn00b/psxcd/readme.txt b/libpsn00b/psxcd/readme.txt
index 64643ed..dcb40b6 100644
--- a/libpsn00b/psxcd/readme.txt
+++ b/libpsn00b/psxcd/readme.txt
@@ -1,45 +1,39 @@
PSX CD-ROM library, part of PSn00bSDK
-2020 Lameguy64 / Meido-Tek Productions
+2020-2022 Lameguy64 / Meido-Tek Productions
Licensed under Mozilla Public License
- Open source implementation of the long awaited CD-ROM library that
-provides greater functionality than the BIOS CD-ROM subsystem. Supports
-pretty much all features of the CD-ROM hardware such as CD data read,
-CD Audio and XA audio playback. Data streaming should also be possible
-with the use of CdlReadN/CdlReadS commands and CdReadyCallback().
+Open source implementation of the long awaited CD-ROM library that provides
+greater functionality than the BIOS CD-ROM subsystem. Supports pretty much all
+features of the CD-ROM hardware such as CD data read, CD Audio and XA audio
+playback, with the exception of the St*() APIs for .STR playback (but manual
+streaming using CdReadyCallback() is still supported).
- Library also includes an ISO9660 file system parser for locating
-files within the CD-ROM file system. Unlike the file system parser in
-the official libraries libpsxcd can parse directories containing any
-number of files. Rock-ridge and Joliet extensions are not supported
-however.
-
- Be aware that the CD-ROM library might have some loose ends as it
-is still a work in progress, but should work flawlessly in most use
-cases.
+An ISO9660 file system driver for locating files within the CD-ROM is also
+included. Unlike the ISO9660 parser in the official libraries, libpsxcd can
+parse directories containing any number of files. Currently no ISO9660
+extensions are supported.
+Be aware that the CD-ROM library might have some loose ends as it is still a
+work in progress, but should work flawlessly in most use cases.
Library developer(s):
- Lameguy64
-
+ Lameguy64 (ISO9660 driver, initial implementation of low-level functions)
+ spicyjpeg (C rewrite)
Library header(s):
psxcd.h
-
Todo list:
* Command query mechanism so that more than 2 CD-ROM commands can
easily be issued in callbacks. Official library probably does this.
-
+
* Helper functions for handling disc changes (CdDiskReady and
CdGetDiskType) are not yet implemented.
-
- * CdReadBreak() not yet implemented.
-
+
* Data streaming functions (prefixed with St*) not yet implemented.
Would require devising a PSn00bSDK equivalent of the STR file
- format. \ No newline at end of file
+ format.
diff --git a/libpsn00b/psxetc/_dl_resolve_wrapper.s b/libpsn00b/psxetc/_dl_resolve_wrapper.s
index eedfa10..a0944c6 100644
--- a/libpsn00b/psxetc/_dl_resolve_wrapper.s
+++ b/libpsn00b/psxetc/_dl_resolve_wrapper.s
@@ -1,5 +1,5 @@
# PSn00bSDK dynamic linker
-# (C) 2021 spicyjpeg - MPL licensed
+# (C) 2021-2022 spicyjpeg - MPL licensed
#
# This function is called by the lazy loader stubs generated by GCC in the
# .plt/.MIPS.stubs section when attempting to call a GOT entry whose address
@@ -16,34 +16,32 @@
.global _dl_resolve_wrapper
.type _dl_resolve_wrapper, @function
_dl_resolve_wrapper:
- # Push the registers we're going to use onto the stack.
- addiu $sp, -16
+ # Save the arguments being passed to the function to be resolved.
+ addiu $sp, -20
sw $a0, 0($sp)
sw $a1, 4($sp)
- sw $v0, 8($sp)
- sw $t7, 12($sp) # (will be restored directly to $ra)
+ sw $a2, 8($sp)
+ sw $a3, 12($sp)
+ sw $t7, 16($sp) # (will be restored directly to $ra)
# Figure out where the DLL's struct is. dlinit() places a pointer to the
# struct in the second GOT entry, so it's just a matter of indexing the GOT
# using $gp. Then call _dl_resolve_helper with the struct and $t8 as
# arguments, and store the return value into $t0.
- lw $a0, -0x7fec($gp) # (DLL *) sizeof(uint32_t) - 0x7ff0 [see dll.ld]
+ lw $a0, -0x7fec($gp) # dll = &((uint32_t *) (gp - 0x7ff0))[1]
move $a1, $t8
jal _dl_resolve_helper
addiu $sp, -8
addiu $sp, 8
- move $t0, $v0
-
- # Restore registers from the stack and tail-call the function at the
- # address returned by the resolver. This will cause the resolved function
- # to run and return directly to the code that called the stub.
+ # Restore the arguments from the stack and tail-call the function at the
+ # address returned by the resolver.
lw $a0, 0($sp)
lw $a1, 4($sp)
- lw $v0, 8($sp)
- lw $ra, 12($sp)
- addiu $sp, 16
+ lw $a2, 8($sp)
+ lw $a3, 12($sp)
+ lw $ra, 16($sp)
- jr $t0
- nop
+ jr $v0
+ addiu $sp, 20
diff --git a/libpsn00b/psxetc/dl.c b/libpsn00b/psxetc/dl.c
index ccf7a7c..ff712eb 100644
--- a/libpsn00b/psxetc/dl.c
+++ b/libpsn00b/psxetc/dl.c
@@ -82,14 +82,9 @@ void *_dl_resolve_helper(DLL *dll, uint32_t index) {
abort();
}
- // Patch the GOT entry to "cache" the resolved address. This can probably
- // be implemented in a faster way, but this thing is already too complex.
- for (int i = 0; i < dll->got_length; i++) {
- if (dll->got[2 + i] == (uint32_t) sym->st_value) {
- dll->got[2 + i] = (uint32_t) addr;
- break;
- }
- }
+ // Patch the GOT entry to "cache" the resolved address.
+ int _index = index - dll->first_got_symbol;
+ dll->got[dll->got_local_count + _index] = (uint32_t) addr;
return addr;
}
@@ -125,10 +120,7 @@ int DL_InitSymbolMap(int num_entries) {
_symbol_map.nbucket = num_entries;
_symbol_map.nchain = num_entries;
_symbol_map.index = 0;
- _sdk_log(
- "allocating nbucket = %d, nchain = %d\n",
- _symbol_map.nbucket, num_entries
- );
+ _sdk_log("nbucket = %d, nchain = %d\n", _symbol_map.nbucket, num_entries);
_symbol_map.entries = malloc(sizeof(MapEntry) * num_entries);
_symbol_map.bucket = malloc(sizeof(uint32_t) * num_entries);
@@ -222,10 +214,7 @@ int DL_ParseSymbolMap(const char *ptr, size_t size) {
(_type == 'D') || // .data
(_type == 'B') // .bss
)) {
- //_sdk_log(
- //"map sym: %08x,%08x [%c %s]\n",
- //addr, _size, _type, name
- //);
+ //_sdk_log("%08x, %08x [%c %s]\n", addr, _size, _type, name);
DL_AddMapSymbol(name, addr);
entries++;
@@ -253,7 +242,7 @@ void *DL_GetMapSymbol(const char *name) {
// https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html
uint32_t hash = _elf_hash(name);
- for (int i = _symbol_map.bucket[hash % _symbol_map.nbucket]; i != 0xffffffff;) {
+ for (int i = _symbol_map.bucket[hash % _symbol_map.nbucket]; i >= 0;) {
if (i >= _symbol_map.nchain) {
_sdk_log(
"DL_GetMapSymbol() index out of bounds (%d >= %d)\n",
@@ -295,33 +284,33 @@ DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) {
_sdk_log("initializing DLL at %08x\n", ptr);
// Interpret the key-value pairs in the .dynamic section to obtain info
- // about all the other sections. The pairs are null-terminated, which makes
- // parsing trivial.
- uint32_t local_got_len = 0;
- uint32_t first_got_sym = 0;
-
+ // about all the other sections. The pairs are null-terminated.
for (Elf32_Dyn *dyn = (Elf32_Dyn *) ptr; dyn->d_tag; dyn++) {
- //_sdk_log(".dynamic %08x=%08x ", dyn->d_tag, dyn->d_un.d_val);
+ //_sdk_log("tag %08x = %08x\n", dyn->d_tag, dyn->d_un.d_val);
switch (dyn->d_tag) {
// Offset of .got section
case DT_PLTGOT:
- dll->got = (void *) (ptr + dyn->d_un.d_val);
+ dll->got = (uint32_t *)
+ &((uint8_t *) ptr)[dyn->d_un.d_val];
break;
// Offset of .hash section
case DT_HASH:
- dll->hash = (void *) (ptr + dyn->d_un.d_val);
+ dll->hash = (const uint32_t *)
+ &((uint8_t *) ptr)[dyn->d_un.d_val];
break;
// Offset of .dynstr (NOT .strtab) section
case DT_STRTAB:
- dll->strtab = (void *) (ptr + dyn->d_un.d_val);
+ dll->strtab = (const char *)
+ &((uint8_t *) ptr)[dyn->d_un.d_val];
break;
// Offset of .dynsym (NOT .symtab) section
case DT_SYMTAB:
- dll->symtab = (void *) (ptr + dyn->d_un.d_val);
+ dll->symtab = (Elf32_Sym *)
+ &((uint8_t *) ptr)[dyn->d_un.d_val];
break;
// Length of each .dynsym entry
@@ -335,9 +324,7 @@ DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) {
// MIPS ABI (?) version
case DT_MIPS_RLD_VERSION:
- //_sdk_log("[MIPS_RLD_VERSION]\n");
-
- // Versions other than 1 are unsupported (do they even exist?).
+ // Versions other than 1 are unsupported.
if (dyn->d_un.d_val != 1) {
_sdk_log("invalid DLL version %d\n", dyn->d_un.d_val);
return 0;
@@ -346,7 +333,7 @@ DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) {
// DLL/ABI flags
case DT_MIPS_FLAGS:
- // Shortcut pointers (whatever they are) are not supported.
+ // Shortcut pointers are not supported.
if (dyn->d_un.d_val & RHF_QUICKSTART) {
_sdk_log("invalid DLL flags\n");
return 0;
@@ -355,7 +342,7 @@ DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) {
// Number of local (not to resolve) GOT entries
case DT_MIPS_LOCAL_GOTNO:
- local_got_len = dyn->d_un.d_val;
+ dll->got_local_count = dyn->d_un.d_val;
break;
// Base address DLL was compiled for
@@ -375,78 +362,56 @@ DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) {
// Index of first symbol table entry which has a matching GOT entry
case DT_MIPS_GOTSYM:
- first_got_sym = dyn->d_un.d_val;
+ dll->first_got_symbol = dyn->d_un.d_val;
break;
}
}
- // Calculate the number of GOT entries (and symbols, if MIPS_SYMTABNO was
- // not found in the .dynamic section).
- //dll->symbol_count = \
- ((uint32_t) dll->hash - (uint32_t) dll->symtab) / sizeof(Elf32_Sym);
- //dll->got_length = \
- ((uint32_t) ptr + size - (uint32_t) dll->got) / sizeof(uint32_t) - 2;
-
- dll->got_length = local_got_len + (dll->symbol_count - first_got_sym) - 2;
+ dll->got_extern_count = dll->symbol_count - dll->first_got_symbol;
_sdk_log(
- "%d symbols, %d GOT entries\n",
- dll->symbol_count, dll->got_length
+ "%d symbols, %d GOT locals, %d GOT externs\n",
+ dll->symbol_count, dll->got_local_count - 2, dll->got_extern_count
);
- // Relocate the DLL by adding its base address to all pointers in the GOT
- // except the first two, which are reserved. The first entry in particular
- // is a pointer to the lazy resolver, called by auto-generated stubs when a
- // function is first used. got[1] is normally unused, but here we'll set it
- // to the DLL's metadata struct so we can look that up when resolving
- // functions (see _dl_resolve_wrapper()).
+ // Relocate the DLL by adding its base address to all pointers in the local
+ // section of the GOT except the first two, which are reserved. The first
+ // entry in particular is a pointer to the lazy resolver, called by
+ // auto-generated stubs when a function is first used. got[1] is normally
+ // unused, but here we'll set it to the DLL's metadata struct so we can
+ // look that up when resolving functions (see _dl_resolve_wrapper()).
dll->got[0] = (uint32_t) &_dl_resolve_wrapper;
dll->got[1] = (uint32_t) dll;
- for (int i = 0; i < dll->got_length; i++)
- dll->got[2 + i] += (uint32_t) ptr;
+ for (int i = 2; i < dll->got_local_count; i++)
+ dll->got[i] += (uint32_t) ptr;
- // Fix addresses in the symbol table.
- // TODO: clean this shit up
- uint32_t got_offset = first_got_sym;
+ // Relocate all pointers in the symbol table and populate the global
+ // section of the GOT.
+ uint32_t *_got = &(dll->got[dll->got_local_count - dll->first_got_symbol]);
for (int i = 0; i < dll->symbol_count; i++) {
Elf32_Sym *sym = &(dll->symtab[i]);
const char *_name = &(dll->strtab[sym->st_name]);
- if (!sym->st_value)
- continue;
-
sym->st_value += (uint32_t) ptr;
- //_sdk_log(
- //"DLL sym: %08x,%08x [%s]\n",
- //sym->st_value, sym->st_size, _name
- //);
-
- // If DL_NOW was passed, resolve GOT entries ahead of time by
- // cross-referencing them with the symbol table.
- if (!(mode & DL_NOW))
- continue;
-
- for (int j = got_offset; j < dll->got_length; j++) {
- if (dll->got[2 + j] != (uint32_t) sym->st_value)
- continue;
-
- got_offset = j;
-
- // If the symbol is undefined (st_shndx = 0) and is either a
- // variable or a function, resolve it immediately.
- // TODO: linking of external variables needs more testing
- if (!(sym->st_shndx) && (
- ELF32_ST_TYPE(sym->st_info) == STT_OBJECT ||
- ELF32_ST_TYPE(sym->st_info) == STT_FUNC
- )) {
- dll->got[2 + j] = (uint32_t) _dl_resolve_callback(dll, _name);
+ //_sdk_log("%08x, %08x [%s]\n", sym->st_value, sym->st_size, _name);
- if (!dll->got[2 + j])
- return 0;
- }
+ if (i < dll->first_got_symbol)
+ continue;
- break;
+ // Resolve the GOT entry if the symbol is an imported variable (or a
+ // function in non-lazy mode), otherwise relocate the GOT pointer.
+ if (
+ !(sym->st_shndx) &&
+ ((ELF32_ST_TYPE(sym->st_info) != STT_FUNC) || (mode & DL_NOW))
+ ) {
+ void *sym_ptr = _dl_resolve_callback(dll, _name);
+ if (!sym_ptr)
+ return 0;
+
+ _got[i] = (uint32_t) sym_ptr;
+ } else {
+ _got[i] += (uint32_t) ptr;
}
}
@@ -461,7 +426,8 @@ DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) {
const uint32_t *ctor_list = DL_GetDLLSymbol(dll, "__CTOR_LIST__");
if (ctor_list) {
for (int i = ((int) ctor_list[0]); i >= 1; i--) {
- void (*ctor)(void) = (void (*)(void)) ctor_list[i];
+ void (*ctor)(void) = (void (*)(void))
+ ((uint8_t *) ptr + ctor_list[i + 1]);
DL_PRE_CALL(ctor);
ctor();
}
@@ -479,7 +445,8 @@ void DL_DestroyDLL(DLL *dll) {
const uint32_t *dtor_list = DL_GetDLLSymbol(dll, "__DTOR_LIST__");
if (dtor_list) {
for (int i = 0; i < ((int) dtor_list[0]); i++) {
- void (*dtor)(void) = (void (*)(void)) dtor_list[i + 1];
+ void (*dtor)(void) = (void (*)(void))
+ ((uint8_t *) dll->ptr + dtor_list[i + 1]);
DL_PRE_CALL(dtor);
dtor();
}
@@ -507,7 +474,7 @@ void *DL_GetDLLSymbol(const DLL *dll, const char *name) {
// Go through the hash table's chain until the symbol name matches the one
// provided.
- for (int i = bucket[_elf_hash(name) % nbucket]; i != 0xffffffff;) {
+ for (int i = bucket[_elf_hash(name) % nbucket]; i >= 0;) {
if (i >= nchain) {
_sdk_log("DL_GetDLLSymbol() index out of bounds (%d >= %d)\n", i, nchain);
return 0;
diff --git a/libpsn00b/psxetc/interrupts.c b/libpsn00b/psxetc/interrupts.c
index 0d926c4..f2a273c 100644
--- a/libpsn00b/psxetc/interrupts.c
+++ b/libpsn00b/psxetc/interrupts.c
@@ -137,13 +137,13 @@ void *DMACallback(DMA_Channel dma, void (*func)(void)) {
DMA_DICR |= (0x10000 << dma) | (1 << 23);
if (!(_num_dma_handlers++))
- InterruptCallback(3, &_global_dma_handler);
+ InterruptCallback(IRQ_DMA, &_global_dma_handler);
} else if (old_callback && !func) {
if (--_num_dma_handlers) {
DMA_DICR &= ~(0x10000 << dma);
} else {
DMA_DICR = 0;
- InterruptCallback(3, 0);
+ InterruptCallback(IRQ_DMA, 0);
}
}
@@ -173,6 +173,7 @@ int ResetCallback(void) {
for (int i = 0; i < NUM_DMA_CHANNELS; i++)
_dma_handlers[i] = (void *) 0;
+ BUS_COM_DELAY = 0x00001325;
_96_remove();
RestartCallback();
return 0;
diff --git a/libpsn00b/psxetc/logging.c b/libpsn00b/psxetc/logging.c
deleted file mode 100644
index 5199190..0000000
--- a/libpsn00b/psxetc/logging.c
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * PSn00bSDK internal debug logger
- * (C) 2022 spicyjpeg - MPL licensed
- *
- * This file provides the (admittedly minimal) logging system used by all
- * PSn00bSDK libraries. Log messages and warnings are issued using the
- * _sdk_log() macro and collected into a buffer, whose contents can be flushed
- * by calling _sdk_dump_log() (by default this is done by VSync()). Logging is
- * only enabled in debug builds of libpsn00b.
- */
-
-#include <stddef.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <psxapi.h>
-#include <psxetc.h>
-
-#define LOG_BUFFER_SIZE 256
-
-#ifndef NDEBUG
-
-/* Internal globals */
-
-static char _log_buffer[LOG_BUFFER_SIZE];
-static size_t _log_buffer_length = 0;
-
-/* Internal logging API */
-
-void _sdk_log_inner(const char *fmt, ...) {
- va_list ap;
-
- va_start(ap, fmt);
- _log_buffer_length += vsnprintf(
- &_log_buffer[_log_buffer_length],
- LOG_BUFFER_SIZE - _log_buffer_length,
- fmt,
- ap
- );
- va_end(ap);
-}
-
-void _sdk_dump_log_inner(void) {
- if (!_log_buffer_length)
- return;
-
- write(1, _log_buffer, _log_buffer_length);
- _log_buffer_length = 0;
-}
-
-#endif
diff --git a/libpsn00b/psxgpu/common.c b/libpsn00b/psxgpu/common.c
index 9f45f10..e41bd31 100644
--- a/libpsn00b/psxgpu/common.c
+++ b/libpsn00b/psxgpu/common.c
@@ -38,9 +38,6 @@ static volatile uint16_t _last_hblank;
/* Private interrupt handlers */
-#define _ENTER_CRITICAL() uint16_t mask = IRQ_MASK; IRQ_MASK = 0;
-#define _EXIT_CRITICAL() IRQ_MASK = mask;
-
static void _vblank_handler(void) {
_vblank_counter++;
@@ -74,8 +71,8 @@ void ResetGraph(int mode) {
// the first time.
if (!ResetCallback()) {
EnterCriticalSection();
- InterruptCallback(0, &_vblank_handler);
- DMACallback(2, &_gpu_dma_handler);
+ InterruptCallback(IRQ_VBLANK, &_vblank_handler);
+ DMACallback(DMA_GPU, &_gpu_dma_handler);
_gpu_video_mode = (GPU_GP1 >> 20) & 1;
ExitCriticalSection();
@@ -149,22 +146,22 @@ int VSync(int mode) {
}
void *VSyncHaltFunction(void (*func)(void)) {
- //_ENTER_CRITICAL();
+ //FastEnterCriticalSection();
void *old_callback = _vsync_halt_func;
_vsync_halt_func = func;
- //_EXIT_CRITICAL();
+ //FastExitCriticalSection();
return old_callback;
}
void *VSyncCallback(void (*func)(void)) {
- _ENTER_CRITICAL();
+ FastEnterCriticalSection();
void *old_callback = _vsync_callback;
_vsync_callback = func;
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
return old_callback;
}
@@ -184,18 +181,18 @@ int EnqueueDrawOp(
// race condition where the DMA transfer could end while interrupts are
// being disabled. Interrupts are disabled through the IRQ_MASK register
// rather than via syscalls for performance reasons.
- _ENTER_CRITICAL();
+ FastEnterCriticalSection();
int length = _queue_length;
if (!length) {
_queue_length = 1;
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
func(arg1, arg2, arg3);
return 0;
}
if (length >= QUEUE_LENGTH) {
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
_sdk_log("draw queue overflow, dropping commands\n");
return -1;
@@ -211,7 +208,7 @@ int EnqueueDrawOp(
entry->arg2 = arg2;
entry->arg3 = arg3;
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
return length;
}
@@ -242,12 +239,12 @@ int DrawSync(int mode) {
}
void *DrawSyncCallback(void (*func)(void)) {
- _ENTER_CRITICAL();
+ FastEnterCriticalSection();
void *old_callback = _drawsync_callback;
_drawsync_callback = func;
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
return old_callback;
}
@@ -258,8 +255,8 @@ void ClearOTagR(uint32_t *ot, size_t length) {
DMA_BCR(6) = length & 0xffff;
DMA_CHCR(6) = 0x11000002;
- //while (DMA_CHCR(6) & (1 << 24))
- //__asm__ volatile("");
+ while (DMA_CHCR(6) & (1 << 24))
+ __asm__ volatile("");
}
void ClearOTag(uint32_t *ot, size_t length) {
diff --git a/libpsn00b/psxgpu/env.c b/libpsn00b/psxgpu/env.c
index f513727..8784947 100644
--- a/libpsn00b/psxgpu/env.c
+++ b/libpsn00b/psxgpu/env.c
@@ -11,6 +11,28 @@
extern GPU_VideoMode _gpu_video_mode;
+/* Private utilities */
+
+// Converts a texture window size value (a power of two in 8-128 range) into a
+// bit mask by setting the leading zeroes of the value:
+// 0 = 0b00000000 -> 0b00000
+// 8 = 0b00001000 -> 0b11111
+// 16 = 0b00010000 -> 0b11110
+// 32 = 0b00100000 -> 0b11100
+// 64 = 0b01000000 -> 0b11000
+// 128 = 0b10000000 -> 0b10000
+// The GPU uses the mask to process texture coordinates as follows:
+// x &= ~(mask << 3)
+// x |= (offset << 3) & (mask << 3)
+static inline uint32_t _get_window_mask(int size) {
+ uint32_t mask = size >> 3;
+
+ mask |= mask << 1;
+ mask |= mask << 2;
+ mask |= mask << 4;
+ return mask & 0x1f;
+}
+
/* Drawing API */
DRAWENV *SetDefDrawEnv(DRAWENV *env, int x, int y, int w, int h) {
@@ -24,8 +46,8 @@ DRAWENV *SetDefDrawEnv(DRAWENV *env, int x, int y, int w, int h) {
env->tw.x = 0;
env->tw.y = 0;
- env->tw.w = 0;
- env->tw.h = 0;
+ env->tw.w = 256;
+ env->tw.h = 256;
env->tpage = 0x0a;
env->dtd = 1;
@@ -41,50 +63,53 @@ int DrawOTagEnv(const uint32_t *ot, DRAWENV *env) {
DR_ENV *prim = &(env->dr_env);
// All commands are grouped into a single display list packet for
- // performance reasons (keep in mind that the GPU doesn't care about this
- // as the display list is parsed by the DMA unit in the CPU and only the
- // payload is sent to the GPU).
+ // performance reasons (the GPU does not care about the grouping as the
+ // display list is parsed by the DMA unit in the CPU).
setaddr(prim, ot);
- setlen(prim, 4);
+ setlen(prim, 5);
+
+ // Texture page (reset active page and set dither/mask bits)
+ prim->code[0] = 0xe1000000 | env->tpage;
+ prim->code[0] |= (env->dtd & 1) << 9;
+ prim->code[0] |= (env->dfe & 1) << 10;
+
+ // Texture window
+ prim->code[1] = 0xe2000000;
+ prim->code[1] |= _get_window_mask(env->tw.w);
+ prim->code[1] |= _get_window_mask(env->tw.h) << 5;
+ prim->code[1] |= (env->tw.x & 0xf8) << 7; // ((tw.x / 8) & 0x1f) << 10
+ prim->code[1] |= (env->tw.y & 0xf8) << 12; // ((tw.y / 8) & 0x1f) << 15
// Set drawing area top left
- prim->code[0] = 0xe3000000;
- prim->code[0] |= env->clip.x & 0x3ff;
- prim->code[0] |= (env->clip.y & 0x3ff) << 10;
+ prim->code[2] = 0xe3000000;
+ prim->code[2] |= env->clip.x & 0x3ff;
+ prim->code[2] |= (env->clip.y & 0x3ff) << 10;
// Set drawing area bottom right
- prim->code[1] = 0xe4000000;
- prim->code[1] |= (env->clip.x + (env->clip.w - 1)) & 0x3ff;
- prim->code[1] |= ((env->clip.y + (env->clip.h - 1)) & 0x3ff) << 10;
+ prim->code[3] = 0xe4000000;
+ prim->code[3] |= (env->clip.x + (env->clip.w - 1)) & 0x3ff;
+ prim->code[3] |= ((env->clip.y + (env->clip.h - 1)) & 0x3ff) << 10;
// Set drawing offset
- prim->code[2] = 0xe5000000;
- prim->code[2] |= (env->clip.x + env->ofs[0]) & 0x7ff;
- prim->code[2] |= ((env->clip.y + env->ofs[1]) & 0x7ff) << 11;
-
- // Texture page (reset active page and set dither/mask bits)
- prim->code[3] = 0xe1000000;
- prim->code[3] |= (env->dtd & 1) << 9;
- prim->code[3] |= (env->dfe & 1) << 10;
+ prim->code[4] = 0xe5000000;
+ prim->code[4] |= (env->clip.x + env->ofs[0]) & 0x7ff;
+ prim->code[4] |= ((env->clip.y + env->ofs[1]) & 0x7ff) << 11;
if (env->isbg) {
- setlen(prim, 7);
+ setlen(prim, 8);
// Rectangle fill
// FIXME: reportedly this command doesn't accept height values >511...
- prim->code[4] = 0x02000000;
- //prim->code[4] |= env->r0 | (env->g0 << 8) | (env->b0 << 16);
- prim->code[4] |= *((const uint32_t *) &(env->isbg)) >> 8;
- //prim->code[5] = env->clip.x;
- //prim->code[5] |= env->clip.y << 16;
- prim->code[5] = *((const uint32_t *) &(env->clip.x));
- prim->code[6] = env->clip.w;
- prim->code[6] |= _min(env->clip.h, 0x1ff) << 16;
+ prim->code[5] = 0x02000000;
+ //prim->code[5] |= env->r0 | (env->g0 << 8) | (env->b0 << 16);
+ //prim->code[6] = env->clip.x;
+ //prim->code[6] |= env->clip.y << 16;
+ prim->code[5] |= *((const uint32_t *) &(env->isbg)) >> 8;
+ prim->code[6] = *((const uint32_t *) &(env->clip.x));
+ prim->code[7] = env->clip.w;
+ prim->code[7] |= _min(env->clip.h, 0x1ff) << 16;
}
- //while (!(GPU_GP1 & (1 << 26)))
- //__asm__ volatile("");
-
return EnqueueDrawOp((void *) &DrawOTag2, (uint32_t) prim, 0, 0);
}
@@ -96,12 +121,10 @@ void PutDrawEnv(DRAWENV *env) {
// useful if the DRAWENV structure is never modified (which is the case most of
// the time).
void PutDrawEnvFast(DRAWENV *env) {
- if (!(env->dr_env.tag)) {
+ if (!(env->dr_env.tag))
DrawOTagEnv((const uint32_t *) 0x00ffffff, env);
- return;
- }
-
- DrawOTag((const uint32_t *) &(env->dr_env));
+ else
+ DrawOTag((const uint32_t *) &(env->dr_env));
}
/* Display API */
@@ -132,7 +155,7 @@ void PutDispEnv(const DISPENV *env) {
mode |= (env->isinter & 1) << 5;
mode |= (env->reverse & 1) << 7;
- if (env->disp.h >= 256)
+ if (env->disp.h > 256)
mode |= 1 << 2;
// Calculate the horizontal display range values. The original code was
diff --git a/libpsn00b/psxgpu/image.c b/libpsn00b/psxgpu/image.c
index bbdb7c8..fc018a4 100644
--- a/libpsn00b/psxgpu/image.c
+++ b/libpsn00b/psxgpu/image.c
@@ -1,6 +1,10 @@
/*
* PSn00bSDK GPU library (image and VRAM transfer functions)
* (C) 2022 spicyjpeg - MPL licensed
+ *
+ * TODO: MoveImage() is currently commented out as it won't trigger a DMA IRQ,
+ * making it unusable as a draw queue command. A way around this (perhaps using
+ * the GPU IRQ?) shall be found.
*/
#include <stdint.h>
@@ -8,7 +12,17 @@
#include <psxgpu.h>
#include <hwregs_c.h>
-#define DMA_CHUNK_LENGTH 8
+#define QUEUE_LENGTH 16
+#define DMA_CHUNK_LENGTH 8
+
+/* Internal globals */
+
+// LoadImage() and StoreImage() run asynchronously but may be called with a
+// pointer to a RECT struct in the stack, which might no longer be valid by the
+// time the transfer is actually started. This buffer is used to store a copy
+// of all RECTs passed to LoadImage()/StoreImage() as a workaround.
+static RECT _saved_rects[QUEUE_LENGTH];
+static int _next_saved_rect = 0;
/* Private utilities */
@@ -47,21 +61,33 @@ static void _dma_transfer(const RECT *rect, uint32_t *data, int write) {
/* VRAM transfer API */
int LoadImage(const RECT *rect, const uint32_t *data) {
+ int index = _next_saved_rect;
+
+ _saved_rects[index] = *rect;
+ _next_saved_rect = (index + 1) % QUEUE_LENGTH;
+
return EnqueueDrawOp(
- (void *) &_dma_transfer, (uint32_t) rect, (uint32_t) data, 1
+ (void *) &_dma_transfer,
+ (uint32_t) &_saved_rects[index],
+ (uint32_t) data,
+ 1
);
}
int StoreImage(const RECT *rect, uint32_t *data) {
+ int index = _next_saved_rect;
+
+ _saved_rects[index] = *rect;
+ _next_saved_rect = (index + 1) % QUEUE_LENGTH;
+
return EnqueueDrawOp(
- (void *) &_dma_transfer, (uint32_t) rect, (uint32_t) data, 0
+ (void *) &_dma_transfer,
+ (uint32_t) &_saved_rects[index],
+ (uint32_t) data,
+ 0
);
}
-int MoveImage(const RECT *rect, int x, int y) {
- return EnqueueDrawOp((void *) &MoveImage2, (uint32_t) rect, x, y);
-}
-
void LoadImage2(const RECT *rect, const uint32_t *data) {
_dma_transfer(rect, (uint32_t *) data, 1);
}
@@ -70,14 +96,14 @@ void StoreImage2(const RECT *rect, uint32_t *data) {
_dma_transfer(rect, data, 0);
}
-void MoveImage2(const RECT *rect, int x, int y) {
+/*void MoveImage2(const RECT *rect, int x, int y) {
GPU_GP0 = 0x80000000;
//GPU_GP0 = rect->x | (rect->y << 16);
GPU_GP0 = *((const uint32_t *) &(rect->x));
GPU_GP0 = (x & 0xffff) | (y << 16);
//GPU_GP0 = rect->w | (rect->h << 16);
GPU_GP0 = *((const uint32_t *) &(rect->w));
-}
+}*/
/* .TIM image parsers */
diff --git a/libpsn00b/psxgte/initgeom.s b/libpsn00b/psxgte/initgeom.s
index 6d1e36a..ccda7f1 100644
--- a/libpsn00b/psxgte/initgeom.s
+++ b/libpsn00b/psxgte/initgeom.s
@@ -1,43 +1,44 @@
.set noreorder
+.include "hwregs_a.inc"
.include "gtereg.inc"
.section .text.InitGeom
.global InitGeom
.type InitGeom, @function
InitGeom:
- addiu $sp, -4
- sw $ra, 0($sp)
+ # Disable interrupts and make sure the GTE is enabled in COP0.
+ lui $v0, IOBASE
+ lhu $v1, IRQ_MASK($v0)
+ nop
+ sh $0, IRQ_MASK($v0)
- jal EnterCriticalSection
+ mfc0 $a0, $12
+ lui $a1, 0x4000
+ or $a1, $a0
+ mtc0 $a1, $12
nop
+ #nop
- mfc0 $v0, $12 # Get SR
- lui $v1, 0x4000 # Set bit to enable cop2
- or $v0, $v1
- mtc0 $v0, $12 # Set new SR
+ # Re-enable interrupts, then load default values into some GTE registers.
+ sh $v1, IRQ_MASK($v0)
- jal ExitCriticalSection
+ ctc2 $0, C2_OFX
nop
+ ctc2 $0, C2_OFY
- ctc2 $0 , $24 # Reset GTE offset
- ctc2 $0 , $25
-
- li $v0, 320 # Set default projection plane
- ctc2 $v0, $26
+ li $a0, 320
+ ctc2 $a0, C2_H
- li $v0, 0x155 # Set ZSF3 and ZSF4 defaults
- ctc2 $v0, $29
- li $v0, 0x100
- ctc2 $v0, $30
+ li $a0, 0x155
+ ctc2 $a0, C2_ZSF3
+ li $a0, 0x100
+ ctc2 $a0, C2_ZSF4
- li $v0, 0xef9e # DQA and DQB defaults
- lui $v1, 0x0140
- ctc2 $v0, C2_DQA
- ctc2 $v1, C2_DQB
+ li $a0, 0xef9e
+ ctc2 $a0, C2_DQA
+ lui $a0, 0x0140
+ ctc2 $a0, C2_DQB
- lw $ra, 0($sp)
- addiu $sp, 4
- jr $ra
+ jr $ra
nop
-
diff --git a/libpsn00b/psxpress/mdec.c b/libpsn00b/psxpress/mdec.c
index d55dbbb..d15a4db 100644
--- a/libpsn00b/psxpress/mdec.c
+++ b/libpsn00b/psxpress/mdec.c
@@ -84,7 +84,7 @@ static const DECDCTENV _default_mdec_env = {
/* Public API */
void DecDCTReset(int mode) {
- EnterCriticalSection();
+ FastEnterCriticalSection();
DMA_DPCR |= 0x000000bb; // Enable DMA0 and DMA1
DMA_CHCR(0) = 0x00000201; // Stop DMA0
@@ -92,7 +92,7 @@ void DecDCTReset(int mode) {
MDEC1 = 0x80000000; // Reset MDEC
MDEC1 = 0x60000000; // Enable DMA in/out requests
- ExitCriticalSection();
+ FastExitCriticalSection();
if (!mode)
DecDCTPutEnv(0, 0);
}
@@ -127,7 +127,7 @@ void DecDCTin(const uint32_t *data, int mode) {
// the stream.
void DecDCTinRaw(const uint32_t *data, size_t length) {
if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) {
- _sdk_log("transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH);
+ _sdk_log("input data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH);
length += DMA_CHUNK_LENGTH - 1;
}
@@ -157,7 +157,7 @@ void DecDCTout(uint32_t *data, size_t length) {
DecDCToutSync(0);
if ((length >= DMA_CHUNK_LENGTH) && (length % DMA_CHUNK_LENGTH)) {
- _sdk_log("transfer data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH);
+ _sdk_log("output data length (%d) is not a multiple of %d, rounding\n", length, DMA_CHUNK_LENGTH);
length += DMA_CHUNK_LENGTH - 1;
}
diff --git a/libpsn00b/psxsio/sio.c b/libpsn00b/psxsio/sio.c
index 6b80352..919f0cb 100644
--- a/libpsn00b/psxsio/sio.c
+++ b/libpsn00b/psxsio/sio.c
@@ -34,13 +34,10 @@ static volatile RingBuffer _tx_buffer, _rx_buffer;
/* Private interrupt handler */
-#define _ENTER_CRITICAL() uint16_t mask = IRQ_MASK; IRQ_MASK = 0;
-#define _EXIT_CRITICAL() IRQ_MASK = mask;
-
static void _sio_handler(void) {
// Handle any incoming bytes.
- while (SIO_STAT & SR_RXRDY) {
- uint8_t value = SIO_TXRX;
+ while (SIO_STAT(1) & SR_RXRDY) {
+ uint8_t value = SIO_DATA(1);
// Skip storing this byte into the RX buffer if the callback returns a
// non-zero value.
@@ -66,7 +63,7 @@ static void _sio_handler(void) {
// Send the next byte in the buffer if the TX unit is ready. Note that
// checking for CTS is unnecessary as the serial port is already hardwired
// to do so.
- if (SIO_STAT & (SR_TXRDY | SR_TXU)) {
+ if (SIO_STAT(1) & (SR_TXRDY | SR_TXU)) {
int length = _tx_buffer.length;
if (length) {
@@ -74,30 +71,30 @@ static void _sio_handler(void) {
_tx_buffer.head = (head + 1) % BUFFER_LENGTH;
_tx_buffer.length = length - 1;
- SIO_CTRL |= CR_TXIEN;
- SIO_TXRX = _tx_buffer.data[head];
+ SIO_CTRL(1) |= CR_TXIEN;
+ SIO_DATA(1) = _tx_buffer.data[head];
} else {
- SIO_CTRL &= CR_TXIEN ^ 0xffff;
+ SIO_CTRL(1) &= CR_TXIEN ^ 0xffff;
}
}
// Acknowledge the IRQ and update flow control signals.
if (_rx_buffer.length < BUFFER_LENGTH)
- SIO_CTRL = CR_INTRST | (SIO_CTRL | _ctrl_reg_flag);
+ SIO_CTRL(1) = CR_INTRST | (SIO_CTRL(1) | _ctrl_reg_flag);
else
- SIO_CTRL = CR_INTRST | (SIO_CTRL & (_ctrl_reg_flag ^ 0xffff));
+ SIO_CTRL(1) = CR_INTRST | (SIO_CTRL(1) & (_ctrl_reg_flag ^ 0xffff));
}
/* Serial port initialization API */
void SIO_Init(int baud, uint16_t mode) {
EnterCriticalSection();
- _old_sio_handler = InterruptCallback(8, &_sio_handler);
+ _old_sio_handler = InterruptCallback(IRQ_SIO1, &_sio_handler);
- SIO_CTRL = CR_ERRRST;
- SIO_MODE = (mode & 0xfffc) | MR_BR_16;
- SIO_BAUD = (uint16_t) ((int) 0x1fa400 / baud);
- SIO_CTRL = CR_TXEN | CR_RXEN | CR_RXIEN;
+ SIO_CTRL(1) = CR_ERRRST;
+ SIO_MODE(1) = (mode & 0xfffc) | MR_BR_16;
+ SIO_BAUD(1) = (uint16_t) ((int) 0x1fa400 / baud);
+ SIO_CTRL(1) = CR_TXEN | CR_RXEN | CR_RXIEN;
_tx_buffer.head = 0;
_tx_buffer.tail = 0;
@@ -114,40 +111,40 @@ void SIO_Init(int baud, uint16_t mode) {
void SIO_Quit(void) {
EnterCriticalSection();
- InterruptCallback(8, _old_sio_handler);
+ InterruptCallback(IRQ_SIO1, _old_sio_handler);
- SIO_CTRL = CR_ERRRST;
+ SIO_CTRL(1) = CR_ERRRST;
ExitCriticalSection();
}
void SIO_SetFlowControl(SIO_FlowControl mode) {
- _ENTER_CRITICAL();
+ FastEnterCriticalSection();
switch (mode) {
case SIO_FC_NONE:
_flow_control = SIO_FC_NONE;
_ctrl_reg_flag = 0;
- SIO_CTRL &= 0xffff ^ CR_DSRIEN;
+ SIO_CTRL(1) &= 0xffff ^ CR_DSRIEN;
break;
case SIO_FC_RTS_CTS:
_flow_control = SIO_FC_RTS_CTS;
_ctrl_reg_flag = CR_RTS;
- SIO_CTRL &= 0xffff ^ CR_DSRIEN;
+ SIO_CTRL(1) &= 0xffff ^ CR_DSRIEN;
break;
/*case SIO_FC_DTR_DSR:
_flow_control = SIO_FC_DTR_DSR;
_ctrl_reg_flag = CR_DTR;
- SIO_CTRL |= CR_DSRIEN;
+ SIO_CTRL(1) |= CR_DSRIEN;
break;*/
}
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
}
/* Reading API */
@@ -167,13 +164,13 @@ int SIO_ReadByte2(void) {
if (!_rx_buffer.length)
return -1;
- _ENTER_CRITICAL();
+ FastEnterCriticalSection();
int head = _rx_buffer.head;
_rx_buffer.head = (head + 1) % BUFFER_LENGTH;
_rx_buffer.length--;
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
return _rx_buffer.data[head];
}
@@ -192,12 +189,12 @@ int SIO_ReadSync(int mode) {
}
void *SIO_ReadCallback(int (*func)(uint8_t)) {
- EnterCriticalSection();
+ FastEnterCriticalSection();
void *old_callback = _read_callback;
_read_callback = func;
- ExitCriticalSection();
+ FastExitCriticalSection();
}
/* Writing API */
@@ -219,18 +216,18 @@ int SIO_WriteByte2(uint8_t value) {
// condition where the transfer could end while interrupts are being
// disabled. Interrupts are disabled through the IRQ_MASK register rather
// than via syscalls for performance reasons.
- _ENTER_CRITICAL();
+ FastEnterCriticalSection();
- if (SIO_STAT & (SR_TXRDY | SR_TXU)) {
- SIO_TXRX = value;
- _EXIT_CRITICAL();
+ if (SIO_STAT(1) & (SR_TXRDY | SR_TXU)) {
+ SIO_DATA(1) = value;
+ FastExitCriticalSection();
return 0;
}
int length = _tx_buffer.length;
if (length >= BUFFER_LENGTH) {
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
//_sdk_log("TX overrun, dropping bytes\n");
return -1;
@@ -241,9 +238,9 @@ int SIO_WriteByte2(uint8_t value) {
_tx_buffer.length = length + 1;
_tx_buffer.data[tail] = value;
- SIO_CTRL |= CR_TXIEN;
+ SIO_CTRL(1) |= CR_TXIEN;
- _EXIT_CRITICAL();
+ FastExitCriticalSection();
return length;
}
@@ -259,7 +256,7 @@ int SIO_WriteSync(int mode) {
if (!_tx_buffer.length) {
// Wait for the TX unit to finish sending the last byte.
- while (!(SIO_STAT & (SR_TXRDY | SR_TXU)))
+ while (!(SIO_STAT(1) & (SR_TXRDY | SR_TXU)))
__asm__ volatile("");
} else {
//_sdk_log("SIO_WriteSync() timeout\n");
diff --git a/libpsn00b/psxsio/tty.c b/libpsn00b/psxsio/tty.c
index 4dc9fd1..a1b33c8 100644
--- a/libpsn00b/psxsio/tty.c
+++ b/libpsn00b/psxsio/tty.c
@@ -32,10 +32,10 @@ static int _sio_inout(FCB *fcb, int cmd) {
case 2: // write
for (int i = 0; i < fcb->trns_len; i++) {
- while (!(SIO_STAT & (SR_TXRDY | SR_TXU)))
+ while (!(SIO_STAT(1) & (SR_TXRDY | SR_TXU)))
__asm__ volatile("");
- SIO_TXRX = *(ptr++);
+ SIO_DATA(1) = *(ptr++);
}
return fcb->trns_len;
diff --git a/libpsn00b/psxspu/common.c b/libpsn00b/psxspu/common.c
index d1dabfe..45654ad 100644
--- a/libpsn00b/psxspu/common.c
+++ b/libpsn00b/psxspu/common.c
@@ -14,6 +14,10 @@
#define DMA_CHUNK_LENGTH 16
#define STATUS_TIMEOUT 0x100000
+static const uint32_t _dummy_block[4] = {
+ 0x00000500, 0x00000000, 0x00000000, 0x00000000
+};
+
/* Internal globals */
static SPU_TransferMode _transfer_mode = SPU_TRANSFER_BY_DMA;
@@ -40,6 +44,11 @@ static size_t _dma_transfer(uint32_t *data, size_t length, int write) {
length += DMA_CHUNK_LENGTH - 1;
}
+ // Increase bus delay for DMA reads
+ BUS_SPU_CFG &= ~(0xf << 24);
+ if (!write)
+ BUS_SPU_CFG = 2 << 24;
+
SPU_CTRL &= 0xffcf; // Disable DMA request
_wait_status(0x0030, 0x0000);
@@ -98,6 +107,8 @@ static size_t _manual_write(const uint16_t *data, size_t length) {
/* Public API */
void SpuInit(void) {
+ BUS_SPU_CFG = 0x200931e1;
+
SPU_CTRL = 0x0000; // SPU disabled
_wait_status(0x001f, 0x0000);
@@ -128,10 +139,8 @@ void SpuInit(void) {
// Upload a dummy looping ADPCM block to the first 16 bytes of SPU RAM.
// This may be freely used or overwritten.
- uint32_t block[4] = { 0x0500, 0, 0, 0 };
-
_transfer_addr = WRITABLE_AREA_ADDR;
- _manual_write((const uint16_t *) block, 16);
+ _manual_write((const uint16_t *) _dummy_block, 16);
// "Play" the dummy block on all channels. This will reset the start
// address and ADSR envelope status of each channel.
@@ -183,7 +192,7 @@ uint32_t SpuSetTransferStartAddr(uint32_t addr) {
if (addr > 0x7ffff)
return 0;
- _transfer_addr = (addr + 7) / 8;
+ _transfer_addr = getSPUAddr(addr);
return addr;
}