diff options
| author | spicyjpeg <thatspicyjpeg@gmail.com> | 2022-12-18 16:30:02 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-12-18 16:30:02 +0100 |
| commit | b12b716a9e54c4f1892795a136d6ffeb088ff001 (patch) | |
| tree | 5888e4a77b207f24ccd9ad148d1c74e9c33400e9 | |
| parent | 77306e187ef1a7ad5d3508ae9acb38edc5b68255 (diff) | |
| parent | a3359c0e7d85bf4752cda3b00892ecd5ef691077 (diff) | |
| download | psn00bsdk-b12b716a9e54c4f1892795a136d6ffeb088ff001.tar.gz | |
Merge pull request #68 from spicyjpeg/bugfix
Bugfixes, psxcd rewrite, psxgpu and CMake tweaks (v0.22)
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(§or_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(§or_header, sizeof(STR_Header) / 4); + + if (sector_header.magic != 0x0160) { str_ctx.frame_ready = -1; return; } - STR_Header *header = §or_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 Binary files differindex a6faf74..aab82ba 100644 --- a/examples/sound/cdstream/stream.vag +++ b/examples/sound/cdstream/stream.vag 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 Binary files differindex e1cb4f4..d75408b 100644 --- a/examples/sound/spustream/stream.vag +++ b/examples/sound/spustream/stream.vag 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, ¶m, 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; } |
