diff options
| author | spicyjpeg <88942473+spicyjpeg@users.noreply.github.com> | 2021-11-07 18:57:48 +0100 |
|---|---|---|
| committer | spicyjpeg <88942473+spicyjpeg@users.noreply.github.com> | 2021-11-07 18:57:48 +0100 |
| commit | b3ef4c5874f579c968f2d26843d0db420e9ed40e (patch) | |
| tree | 85f60b460901bfe14b1cb6b30eeedadd6398eee3 | |
| parent | c377ec612422ac82afe034d92eedb45fb8a17a39 (diff) | |
| download | psn00bsdk-b3ef4c5874f579c968f2d26843d0db420e9ed40e.tar.gz | |
Added io/pads example, psxpad.h definitions, bugfixes
| -rw-r--r-- | doc/dev notes.txt | 38 | ||||
| -rw-r--r-- | examples/demos/n00bdemo/main.c | 8 | ||||
| -rw-r--r-- | examples/io/pads/CMakeLists.txt | 22 | ||||
| -rw-r--r-- | examples/io/pads/main.c | 259 | ||||
| -rw-r--r-- | examples/io/pads/spi.c | 216 | ||||
| -rw-r--r-- | examples/io/pads/spi.h | 60 | ||||
| -rw-r--r-- | indev/README.md | 4 | ||||
| -rw-r--r-- | libpsn00b/include/psxpad.h | 203 | ||||
| -rw-r--r-- | libpsn00b/libc/start.c | 6 |
9 files changed, 759 insertions, 57 deletions
diff --git a/doc/dev notes.txt b/doc/dev notes.txt index 1bc9ccd..d825ee7 100644 --- a/doc/dev notes.txt +++ b/doc/dev notes.txt @@ -53,6 +53,44 @@ IRQs are only triggered on transfer completion. Additional notes by spicyjpeg: +* The controller/memory card SPI interface is poorly implemented in most +emulators, making custom controller polling code insanely hard to write and +debug. The only emulator that comes close to real hardware is no$psx, which +seems to correctly implement all features of the SPI port (even those not used +by the BIOS pad driver, such as TX/RX interrupts). DuckStation only emulates +the bare minimum required by the BIOS and Sony libraries, and pcsx-redux has +major bugs that break most custom polling implementations. This pretty much +means TX/RX IRQs and many flags in the JOY_* registers should not be used +unless you are willing to break compatibility with emulators. + +* As if communicating with controllers wasn't difficult enough already, +DualShock pads also have a built-in watchdog timer that gets enabled when first +putting them in configuration mode (and is NOT disabled after exiting config +mode). If no polling commands are sent to the controller for about 1 second, +vibration motors are switched off and analog mode is disabled; the same happens +if the analog button is pressed while in analog mode. In order to always keep +the pad in analog mode you must: + + * Poll both controller ports at least once per frame by sending command 42h. + Polling at a higher rate might be desirable in some cases (such as rhythm + games) to increase timing accuracy. + * If a digital pad response (type = 4) is received from a port that hasn't + previously been flagged as digital-only, attempt to put the pad into config + mode using command 43h. + * If the pad doesn't recognize the config command, flag it as digital-only + and treat all further digital pad responses from it as valid. + * If the pad recognizes the command, it will reply by identifying as an + analog pad in config mode (type = 15). Send command 44h immediately + (without sending 42h first, otherwise the pad will exit config mode) to + enable analog mode and turn on the LED. + * Pressing the analog button will result in the controller identifying as + digital even though it is not flagged as such, thus re-triggering the + configuration process and putting it back into analog mode. + * All analog pad responses (type = 7) can be treated as valid, as they will + come from controllers that have already been configured. + * If no valid response is received, assume no controller is connected and + reset the port's digital-only flag. + * The SDK provides no support yet for replacing the BIOS exception handler with a custom one, however it can be done (if you are ok with losing all BIOS controller, memory card and file functionality). In order not to break anything diff --git a/examples/demos/n00bdemo/main.c b/examples/demos/n00bdemo/main.c index ba21d88..e9dd336 100644 --- a/examples/demos/n00bdemo/main.c +++ b/examples/demos/n00bdemo/main.c @@ -617,7 +617,13 @@ void transition() { if( comp >= 16 ) break; - + + // FIXME: for some reason this loop glitches out and hangs indefinitely + // in no$psx, *unless* there's a function somewhere that gets called + // with a pointer/string as first argument... wtf. It works fine in + // other emulators. If you are reading this, please help and enlighten + // me. -- spicyjpeg + puts("."); } DrawSync(0); diff --git a/examples/io/pads/CMakeLists.txt b/examples/io/pads/CMakeLists.txt new file mode 100644 index 0000000..124d62b --- /dev/null +++ b/examples/io/pads/CMakeLists.txt @@ -0,0 +1,22 @@ +# PSn00bSDK example CMake script +# (C) 2021 spicyjpeg - MPL licensed + +cmake_minimum_required(VERSION 3.21) + +if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{PSN00BSDK_LIBS}) + set(CMAKE_TOOLCHAIN_FILE $ENV{PSN00BSDK_LIBS}/cmake/sdk.cmake) +endif() + +project( + pads + LANGUAGES C ASM + VERSION 1.0.0 + DESCRIPTION "PSn00bSDK controller polling example" + HOMEPAGE_URL "http://lameguy64.net/?page=psn00bsdk" +) + +file(GLOB _sources *.c *.s) +psn00bsdk_add_executable(pads STATIC ${_sources}) +#psn00bsdk_add_cd_image(pads_iso pads iso.xml DEPENDS pads) + +install(FILES ${PROJECT_BINARY_DIR}/pads.exe DESTINATION .) diff --git a/examples/io/pads/main.c b/examples/io/pads/main.c new file mode 100644 index 0000000..bb56a27 --- /dev/null +++ b/examples/io/pads/main.c @@ -0,0 +1,259 @@ +/* + * PSn00bSDK controller polling example + * (C) 2021 spicyjpeg - MPL licensed + * + * This example shows how to poll controllers at high speeds (250 Hz) by using + * timer interrupts and communicating with devices on the SPI controller bus + * manually rather than relying on the BIOS pad driver, which is limited to + * 50/60 Hz and does not support custom commands. The example also demonstrates + * using configuration mode commands to force DualShock pads into analog mode + * and enable button pressure sensing on DualShock 2 (PS2) controllers. + * + * See spi.c for details on how the low-level SPI communication driver works. + * The DualShock handshaking logic is implemented here (see poll_cb() and + * dualshock_init_cb()). There is no support for memory cards in this example, + * but the code in spi.c can be used to read/write sectors on a memory card and + * combined with a higher-level filesystem driver for full support. + * + * IMPORTANT: this example doesn't work on pcsx-redux, and hasn't yet been + * tested on real hardware and/or with unofficial controllers. It works on + * DuckStation and no$psx though. + */ + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <psxetc.h> +#include <psxgpu.h> +#include <psxpad.h> + +#include "spi.h" + +static const char *const PAD_TYPEIDS[] = { + "[UNKNOWN]", + "MOUSE", + "NEGCON", + "IRQ10_GUN", + "DIGITAL", + "ANALOG_STICK", + "GUNCON", + "ANALOG", + "MULTITAP", + "[UNKNOWN]", + "[UNKNOWN]", + "[UNKNOWN]", + "[UNKNOWN]", + "[UNKNOWN]", + "JOGCON", + "CONFIG_MODE" +}; + +/* Display/GPU context utilities */ + +#define SCREEN_XRES 320 +#define SCREEN_YRES 240 + +typedef struct { + DISPENV disp; + DRAWENV draw; +} DB; + +typedef struct { + DB db[2]; + uint32_t db_active; +} CONTEXT; + +void init_context(CONTEXT *ctx) { + DB *db; + + ResetGraph(0); + ctx->db_active = 0; + + db = &(ctx->db[0]); + SetDefDispEnv(&(db->disp), 0, 0, SCREEN_XRES, SCREEN_YRES); + SetDefDrawEnv(&(db->draw), SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES); + setRGB0(&(db->draw), 63, 0, 127); + db->draw.isbg = 1; + db->draw.dtd = 1; + + db = &(ctx->db[1]); + SetDefDispEnv(&(db->disp), SCREEN_XRES, 0, SCREEN_XRES, SCREEN_YRES); + SetDefDrawEnv(&(db->draw), 0, 0, SCREEN_XRES, SCREEN_YRES); + setRGB0(&(db->draw), 63, 0, 127); + db->draw.isbg = 1; + db->draw.dtd = 1; + + PutDrawEnv(&(db->draw)); + //PutDispEnv(&(db->disp)); + + // Create a text stream at the top of the screen. + FntLoad(960, 0); + FntOpen(8, 16, 304, 208, 2, 512); +} + +void display(CONTEXT *ctx) { + DB *db; + + DrawSync(0); + VSync(0); + ctx->db_active ^= 1; + + db = &(ctx->db[ctx->db_active]); + PutDrawEnv(&(db->draw)); + PutDispEnv(&(db->disp)); + SetDispMask(1); +} + +/* Pad buffers and callback */ + +static volatile uint8_t pad_buff[2][34]; +static volatile size_t pad_buff_len[2]; +static volatile uint32_t pad_digital_only[2] = { 0, 0 }; + +// Just a wrapper around spi_new_request(). This does not send the command +// immediately but adds it to the driver's request queue. +void send_pad_cmd( + uint32_t port, + PAD_COMMAND cmd, + uint8_t arg1, + uint8_t arg2, + SPICALLBACK callback +) { + SPIREQUEST *req = spi_new_request(); + + req->len = 9; + req->port = port; + req->callback = callback; + req->pad_req.addr = 0x01; + req->pad_req.cmd = cmd; + req->pad_req.tap_mode = 0x00; + req->pad_req.motor_r = arg1; + req->pad_req.motor_l = arg2; + + // The padding bytes must be 0xff when unlocking vibration motors. + memset( + req->pad_req.dummy, + (cmd == PAD_CMD_REQUEST_CONFIG) ? 0xff : 0x00, + 4 + ); +} + +// This callback determines whether a pad that identifies as digital is +// actually a DualShock in digital mode by checking how it replied to the +// CONFIG_MODE command. +void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) { + // Flag the pad as digital-only if it didn't reply. + // FIXME: rx_len should be 8 for digital pads, but sometimes it's only 4 + if (rx_len < 4) { + printf("no, pad is digital-only (len = %d)\n", rx_len); + + pad_digital_only[port] = 1; + return; + } + + printf("yes, forcing analog mode (len = %d)\n", rx_len); + + // Issue further commands to finish configuring the controller. + // https://gist.github.com/scanlime/5042071 + send_pad_cmd(port, PAD_CMD_SET_ANALOG, 0x01, 0x02, 0); // 0x03 disables analog button...? + send_pad_cmd(port, PAD_CMD_INIT_PRESSURE, 0x00, 0x00, 0); // Ignored by DualShock 1 + send_pad_cmd(port, PAD_CMD_REQUEST_CONFIG, 0x00, 0x01, 0); + send_pad_cmd(port, PAD_CMD_RESPONSE_CONFIG, 0xff, 0xff, 0); // Ignored by DualShock 1 + send_pad_cmd(port, PAD_CMD_CONFIG_MODE, 0x00, 0x00, 0); +} + +// This function is called by the pad timer ISR each time a pad is polled and a +// response (even an invalid/incomplete one) is received. +void poll_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) { + // Copy the response to a persistent buffer so it can be accessed from the + // main loop and displayed on screen. + pad_buff_len[port] = rx_len; + if (rx_len) + memcpy((void *) pad_buff[port], (void *) buff, rx_len); + + PADTYPE *pad = (PADTYPE *) buff; + + // If this pad identifies as a digital pad and hasn't been flagged as a + // digital-only pad already, attempt to put it into analog mode by entering + // configuration mode. It this fails, it will be flagged as digital-only. + // The digital-only flag is reset when the controller is unplugged or stops + // returning digital pad responses. + if ((pad->raw.prefix == 0x5a) && (pad->raw.type == PAD_ID_DIGITAL)) { + if (!pad_digital_only[port]) { + printf("Detecting if pad %d supports config mode... ", port + 1); + + send_pad_cmd( + port, + PAD_CMD_CONFIG_MODE, + 0x01, + 0x00, + &dualshock_init_cb + ); + } + + } else if (pad_digital_only[port]) { + printf("Clearing digital-only flag for pad %d\n", port + 1); + + pad_digital_only[port] = 0; + } +} + +/* Main */ + +static CONTEXT ctx; + +int main(int argc, const char* argv[]) { + init_context(&ctx); + spi_init(&poll_cb); + + uint32_t counter = 0; + + while (1) { + FntPrint(-1, "COUNTER=%d", counter++); + + for (uint32_t port = 0; port < 2; port++) { + PADTYPE *pad = (PADTYPE *) pad_buff[port]; + + // According to nocash docs, there is a hardware bug in DualShock + // controllers that causes the prefix byte (normally 0x5a) to turn + // into 0x00 if the analog button is pressed after configuration + // commands have been used. + //if (!pad->raw.prefix != 0x5a) { + if (!pad_buff_len[port]) { + FntPrint(-1, "\n\nPORT %d: NO DEVICE FOUND\n", port + 1); + if ((counter % 64) < 32) + FntPrint(-1, " CONNECT PAD NOW..."); + + continue; + } + + uint32_t type = pad->raw.type; + uint32_t len = pad->raw.len * 2; + if (type == PAD_ID_MULTITAP) + len = 32; + + FntPrint( + -1, + "\n\nPORT %d: %s\n TYPE=%d LEN=%d", + port + 1, + PAD_TYPEIDS[type], + type, + len + ); + + // Print a hexdump of the payload returned by the pad. + for (uint32_t i = 0; i < len; i++) + FntPrint( + -1, + (i % 8) ? " %02x" : "\n %02x", + pad_buff[port][i + 2] + ); + } + + FntFlush(-1); + display(&ctx); + } + + return 0; +} diff --git a/examples/io/pads/spi.c b/examples/io/pads/spi.c new file mode 100644 index 0000000..4728626 --- /dev/null +++ b/examples/io/pads/spi.c @@ -0,0 +1,216 @@ +/* + * PSn00bSDK controller polling example (SPI driver) + * (C) 2021 spicyjpeg - MPL licensed + * + * This is a fairly complete timer driven high-speed SPI driver, with support + * for sending custom commands (including memory card reads/writes), in about + * 200 lines of code. Feel free to copypaste and adapt it. + * + * The way this works is by maintaining a queue of requests to send, each with + * its own payload and callback. Timer 2 is configured to trigger an IRQ at + * regular intervals. On each tick, the next request in the queue (or a poll + * command if no request is pending) is prepared and the first byte is + * sent; if the controller asks for more data by pulling /ACK low, the next + * byte is sent and the received byte is placed into a buffer. This goes on + * until the last byte is exchanged and the controller stops asserting /ACK. + * + * On the next tick, the response buffer is passed to the request's callback + * and reset, and the next request in the queue is sent. This blindly assumes + * it only takes one tick for a request/response to be sent, which is the case + * for controllers' very small packets but not for memory cards. It is + * advisable to call spi_set_poll_rate() to temporarily reduce poll rate while + * accessing memory cards. + * + * Note that this driver completely takes over the SPI bus, so you won't be + * able to use any BIOS functions that rely on SPI access (i.e. pad and memory + * card APIs) alongside it. + */ + +#include <sys/types.h> +#include <string.h> +#include <malloc.h> +#include <psxetc.h> +#include <psxapi.h> +#include <psxpad.h> + +#include "spi.h" + +/* Register definitions */ + +#define F_CPU 33868800UL + +#define TIM_VALUE(N) *((volatile uint32_t *) 0x1f801100 + 4 * (N)) +#define TIM_CTRL(N) *((volatile uint32_t *) 0x1f801104 + 4 * (N)) +#define TIM_RELOAD(N) *((volatile uint32_t *) 0x1f801108 + 4 * (N)) +#define JOY_TXRX *((volatile uint32_t *) 0x1f801040) +#define JOY_STAT *((volatile uint32_t *) 0x1f801044) +#define JOY_MODE *((volatile uint16_t *) 0x1f801048) +#define JOY_CTRL *((volatile uint16_t *) 0x1f80104a) +#define JOY_BAUD *((volatile uint16_t *) 0x1f80104e) + +/* Internal structures and globals */ + +typedef struct _SPICONTEXT { + uint8_t tx_buff[SPI_BUFF_LEN]; + uint8_t rx_buff[SPI_BUFF_LEN]; + uint32_t tx_len, rx_len, port; + SPICALLBACK callback; +} SPICONTEXT; + +static volatile SPICONTEXT ctx; +static volatile SPIREQUEST volatile *current_req; +static SPICALLBACK default_cb; + +/* Request queue management */ + +static void prepare_poll_req(void) { + PADREQUEST *req = (PADREQUEST *) ctx.tx_buff; + + req->addr = 0x01; + req->cmd = PAD_CMD_READ; + req->tap_mode = 0x00; // 0x01 to enable extended multitap response + req->motor_l = 0x00; + req->motor_r = 0x00; + + ctx.tx_len = 4; + ctx.rx_len = 0; + ctx.port ^= 1; + ctx.callback = default_cb; +} + +static void prepare_next_req(void) { + // Copy the contents of the first request in the queue into the TX buffer. + memcpy((void *) ctx.tx_buff, (void *) current_req->data, current_req->len); + + ctx.tx_len = current_req->len; + ctx.rx_len = 0; + ctx.port = current_req->port; + ctx.callback = current_req->callback; + + // Pop the first request from the queue by deallocating it and adjusting + // the pointer to the first queue item. + SPIREQUEST *next = current_req->next; + + free((void *) current_req); + current_req = next; +} + +/* Interrupt handlers */ + +static void poll_timer_tick(void) { + // Fetch the last response byte, which wasn't followed by a pulse on /ACK, + // from the RX FIFO. + if (JOY_STAT & 0x0002) + ctx.rx_buff[ctx.rx_len - 1] = (uint8_t) JOY_TXRX; + + if (ctx.callback) + ctx.callback(ctx.port, ctx.rx_buff, ctx.rx_len); + + // If the request queue is empty, create a pad polling request. + if (current_req) + prepare_next_req(); + else + prepare_poll_req(); + + // Prepare the SPI port by clearing any pending IRQ, pulling /CS high and + // 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. + JOY_CTRL = 0x0010; + for (uint32_t i = 0; i < 50; i++) + __asm__("nop"); + + JOY_CTRL = 0x1003 | (ctx.port << 13); + for (uint32_t i = 0; i < 500; i++) + __asm__("nop"); + + // 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 = ctx.tx_buff[0]; +} + +static void spi_ack_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) + __asm__("nop"); + + // Keep /CS pulled low and acknowledge the IRQ (bit 4) to ensure it can be + // triggered again. + JOY_CTRL = 0x1013 | (ctx.port << 13); + + if (!ctx.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 to ensure we are only going to read valid data from + // now on. + volatile uint32_t dummy; + while (JOY_STAT & 0x0002) + dummy = JOY_TXRX; + + } else if (ctx.rx_len <= SPI_BUFF_LEN) { + // If this is not the first byte, put it in the RX buffer. + ctx.rx_buff[ctx.rx_len - 1] = (uint8_t) JOY_TXRX; + } + + // Send the next byte, or a null byte if there is no more data to send and + // we're just reading a response. + ctx.rx_len++; + if (ctx.rx_len < ctx.tx_len) + JOY_TXRX = (uint32_t) ctx.tx_buff[ctx.rx_len]; + else + JOY_TXRX = 0x00; +} + +/* Public API */ + +SPIREQUEST *spi_new_request(void) { + SPIREQUEST *req = malloc(sizeof(SPIREQUEST)); + + req->len = 0; + req->port = 0; + req->callback = 0; + req->next = 0; + + // Find the last queued request by traversing the linked list and append a + // pointer to the new request. + if (!current_req) { + current_req = req; + } else { + volatile SPIREQUEST *volatile last = current_req; + while (last->next) + last = last->next; + + last->next = req; + } + + return req; +} + +void spi_set_poll_rate(uint32_t value) { + TIM_CTRL(2) = 0x0258; // CLK/8 input, IRQ on reload, disable one-shot IRQ + + if (value < 65) + TIM_RELOAD(2) = 0xffff; + else + TIM_RELOAD(2) = (F_CPU / 8) / value; +} + +void spi_init(SPICALLBACK callback) { + // Disable the BIOS timer handler (which for some stupid reason is enabled + // by default, even though it does nothing) and set up custom interrupt + // handlers. + EnterCriticalSection(); + ChangeClearRCnt(2, 0); + InterruptCallback(6, &poll_timer_tick); + InterruptCallback(7, &spi_ack_handler); + ExitCriticalSection(); + + JOY_CTRL = 0x0040; // Reset all registers + JOY_MODE = 0x000d; // 1x multiplier, 8 data bits, no parity + JOY_BAUD = 0x0088; // 250000 bps + + spi_set_poll_rate(250); + current_req = 0; + default_cb = callback; +} diff --git a/examples/io/pads/spi.h b/examples/io/pads/spi.h new file mode 100644 index 0000000..09223e2 --- /dev/null +++ b/examples/io/pads/spi.h @@ -0,0 +1,60 @@ +/* + * PSn00bSDK controller polling example (SPI driver) + * (C) 2021 spicyjpeg - MPL licensed + */ + +#ifndef __SPI_H +#define __SPI_H + +#include <psxpad.h> + +//#define SPI_BUFF_LEN 34 +#define SPI_BUFF_LEN 140 + +/* Request structures */ + +typedef void (*SPICALLBACK)(uint32_t port, const volatile uint8_t *buff, size_t rx_len); + +typedef struct _SPIREQUEST { + union { + uint8_t data[SPI_BUFF_LEN]; + PADREQUEST pad_req; + MCDREQUEST mcd_req; + }; + uint32_t len, port; + SPICALLBACK callback; + struct _SPIREQUEST *next; +} SPIREQUEST; + +/* Public API */ + +/** + * @brief Allocates a new request object and adds it to the request queue. The + * object must be populated afterwards by setting the length, callback and + * filling in the TX data buffer. + */ +SPIREQUEST *spi_new_request(void); + +/** + * @brief Changes the controller polling rate. The lowest supported rate is 65 + * Hz (requests sent every 1/65th of a second, each port polled at 32.5 Hz when + * no request is pending). + * + * @param value + */ +void spi_set_poll_rate(uint32_t value); + +/** + * @brief Installs the SPI and timer 2 interrupt handlers and starts the poll + * timer. By default the polling rate is set to 250 Hz (125 Hz per port), + * however it can be changed at any time by calling spi_set_poll_rate(). + * + * The provided callback (if any) is called to report the result of poll + * requests, which are issued automatically when no other request is in the + * queue. Passing NULL as callback does not disable auto-polling. + * + * @param callback + */ +void spi_init(SPICALLBACK callback); + +#endif diff --git a/indev/README.md b/indev/README.md index 2064a36..8263487 100644 --- a/indev/README.md +++ b/indev/README.md @@ -23,6 +23,10 @@ also go into this directory. The Github release of this work-in-progress component includes delay
corrections for PAL consoles.
+ **NOTE**: the `io/pads` example also shows how to poll controllers manually
+ in a slightly different way (using a timer), and includes a reusable
+ low-level pad driver.
+
Work-in-progress components such as psxcd, interlace-exp, xptest and partest
are not included, as the former was completed while the remaining latter are
merely scrap test programs.
\ No newline at end of file diff --git a/libpsn00b/include/psxpad.h b/libpsn00b/include/psxpad.h index 01aff06..d152896 100644 --- a/libpsn00b/include/psxpad.h +++ b/libpsn00b/include/psxpad.h @@ -3,10 +3,12 @@ * 2019 Lameguy64 / Meido-Tek Productions * * Currently only provides a bunch of definitions and a few structs but no - * handling functions yet. Use the code in pad.s in one of the sample - * programs for the meantime instead. + * handling functions yet. See the io/pads example for a simple manual pollling + * implementation (with support for DualShock 2 controllers). * * Work in progress, subject to change significantly in future releases. + * + * Reference: https://gist.github.com/scanlime/5042071 */ #ifndef _PSXPAD_H @@ -50,58 +52,153 @@ #define GCON_TRIGGER 8192 #define GCON_B 16384 -// Struct for digital, joystick, dual analog and Dualshock controllers -typedef struct { - unsigned char stat; // Status - unsigned char len:4; // Data length (in halfwords) - unsigned char type:4; // Device type: - // 0x4 - digital pad - // 0x5 - analog joystick - // 0x7 - dual analog & Dualshock - unsigned short btn; // Button states - unsigned char rs_x,rs_y; // Right stick coordinates - unsigned char ls_x,ls_y; // Left stick coordinates +typedef enum { + PAD_ID_MOUSE = 0x1, // Sony PS1 mouse + PAD_ID_NEGCON = 0x2, // Namco neGcon + PAD_ID_IRQ10_GUN = 0x3, // "Konami" lightgun without composite video passthrough + PAD_ID_DIGITAL = 0x4, // Digital pad or Dual Analog/DualShock in digital mode + PAD_ID_ANALOG_STICK = 0x5, // Flight stick or Dual Analog in green LED mode + PAD_ID_GUNCON = 0x6, // Namco Guncon (lightgun with composite video passthrough) + PAD_ID_ANALOG = 0x7, // Dual Analog/DualShock in analog (red LED) mode + PAD_ID_MULTITAP = 0x8, // Multitap adapter (when tap_mode == 1) + PAD_ID_JOGCON = 0xe, // Namco Jogcon + PAD_ID_CONFIG_MODE = 0xf, // Dual Analog/DualShock in config mode (if len == 0x3) + PAD_ID_NONE = 0xf // No pad connected (if len == 0xf) +} PAD_TYPEID; + +// Controller command definitions +typedef enum { + PAD_CMD_INIT_PRESSURE = '@', // Initialize DS2 button pressure sensors (in config mode) + PAD_CMD_READ = 'B', // Read pad state and set rumble + PAD_CMD_CONFIG_MODE = 'C', // Toggle DualShock configuration mode + PAD_CMD_SET_ANALOG = 'D', // Set analog mode/LED state (in config mode) + PAD_CMD_GET_ANALOG = 'E', // Get analog mode/LED state (in config mode) + PAD_CMD_REQUEST_CONFIG = 'M', // Configure request/unlock vibration (in config mode) + PAD_CMD_RESPONSE_CONFIG = 'O' // Configure response/unlock DS2 pressure (in config mode) +} PAD_COMMAND; + +// Memory card command/response definitions +typedef enum { + MCD_CMD_READ = 'R', // Read sector + MCD_CMD_IDENTIFY = 'S', // Retrieve ID and card size information + MCD_CMD_WRITE = 'W' // Write sector +} MCD_COMMAND; + +typedef enum { + MCD_STAT_OK = 'G', + MCD_STAT_BAD_CHECKSUM = 'N', + MCD_STAT_BAD_SECTOR = 0xff +} MCD_STATUS; + +#define MCD_CMD_READ_LEN 139 +#define MCD_CMD_IDENTIFY_LEN 9 +#define MCD_CMD_WRITE_LEN 137 + +// Memory card status flags +#define MCD_FLAG_WRITE_ERROR 4 // Last write command failed +#define MCD_FLAG_NOT_WRITTEN 8 // No writes have been issued yet +#define MCD_FLAG_UNKNOWN 16 // Might be set on third-party cards + +// Struct for data returned by controllers +typedef struct _PADTYPE { + union { // Header: + struct __attribute__((packed)) { // When parsing data returned by BIOS: + unsigned char stat; // Status + unsigned char len:4; // Payload length / 2, 0 for multitap + unsigned char type:4; // Device type (PAD_TYPEID) + }; + struct __attribute__((packed)) { // When parsing raw controller response: + unsigned char len:4; // Payload length / 2, 0 for multitap + unsigned char type:4; // Device type (PAD_TYPEID) + unsigned char prefix; // Must be 0x5a + } raw; + }; + struct { // Payload: + unsigned short btn; // Button states + union { + struct { // Analog controller: + unsigned char rs_x,rs_y; // Right stick coordinates + unsigned char ls_x,ls_y; // Left stick coordinates + unsigned char press[12]; // Button pressure (DualShock 2 only) + }; + struct { // Mouse: + char x_mov; // X movement of mouse + char y_mov; // Y movement of mouse + }; + struct { // neGcon: + unsigned char twist; // Controller twist + unsigned char btn_i; // I button value + unsigned char btn_ii; // II button value + unsigned char trg_l; // L trigger value + }; + struct { // Jogcon: + unsigned short jog_rot; // Jog rotation + }; + struct { // Guncon: + unsigned short gun_x; // Gun X position in dotclocks + unsigned short gun_y; // Gun Y position in scanlines + }; + }; + }; } PADTYPE; -// Struct for a mouse controller -typedef struct { - unsigned char stat; - unsigned char len:4; - unsigned char type:4; // Device type (0x1) - unsigned short btn; - char x_mov; // X movement of mouse - char y_mov; // Y movement of mouse -} MOUSETYPE; - -// Struct for a neGcon controller (for Namco neGcon) -typedef struct { - unsigned char stat; - unsigned char len:4; - unsigned char type:4; // (0x2) - unsigned short btn; - unsigned char twist; // Controller twist - unsigned char btn_i; // I button value - unsigned char btn_ii; // II button value - unsigned char trg_l; // L trigger value -} NCONTYPE; - -// Struct for a Jogcon controller (for Namco Jogcon) -typedef struct { - unsigned char stat; - unsigned char len:4; - unsigned char type:4; // (0xE) - unsigned short btn; - unsigned short jog_rot; // Jog rotation -} JCONTYPE; - -// Struct for a Gun-Con controller (for Namco Gun-Con) -typedef struct { - unsigned char status; - unsigned char len:4; - unsigned char type:4; // (0x6) - unsigned short btn; - unsigned short gun_x; // Gun X position in dotclocks - unsigned short gun_y; // Gun Y position in scanlines -} GCONTYPE; +typedef struct _MCDRESPONSE { + unsigned char flags; // Status flags + unsigned char type1; // Must be 0x5a + unsigned char type2; // Must be 0x5d + union { + struct { // MCD_CMD_READ response: + unsigned char dummy[2]; + unsigned char ack1; // Must be 0x5c + unsigned char ack2; // Must be 0x5d + unsigned char lba_h; + unsigned char lba_l; + unsigned char data[128]; + unsigned char checksum; // = lba_h ^ lba_l ^ data + unsigned char stat; // Status (MCD_STATUS) + } read; + struct { // MCD_CMD_IDENTIFY response: + unsigned char ack1; // Must be 0x5c + unsigned char ack2; // Must be 0x5d + unsigned char size_h; // Card capacity bits 8-15 (0x04 = 128KB) + unsigned char size_l; // Card capacity bits 0-7 (0x00 = 128KB) + unsigned char blksize_h; // Sector size bits 8-15 (must be 0x00) + unsigned char blksize_l; // Sector size bits 0-7 (must be 0x80) + } identify; + struct { // MCD_CMD_WRITE response: + unsigned char dummy[131]; + unsigned char ack1; // Must be 0x5c + unsigned char ack2; // Must be 0x5d + unsigned char stat; // Status (MCD_STATUS) + } write; + }; +} MCDRESPONSE; + +//typedef PADTYPE MOUSETYPE; +//typedef PADTYPE NCONTYPE; +//typedef PADTYPE JCONTYPE; +//typedef PADTYPE GCONTYPE; + +// Structs for raw controller request +typedef struct _PADREQUEST { + unsigned char addr; // Must be 0x01 (or 02/03/04 for multitap pads) + unsigned char cmd; // Command (PAD_COMMAND) + unsigned char tap_mode; // 0x01 to enable multitap response + unsigned char motor_r; // Right motor control (on/off) + unsigned char motor_l; // Left motor control (PWM) + unsigned char dummy[4]; +} PADREQUEST; + +// Structs for raw memory card request +typedef struct _MCDREQUEST { + unsigned char addr; // Must be 0x81 (or 02/03/04 for multitap cards) + unsigned char cmd; // Command (MCD_COMMAND) + unsigned char dummy[2]; + unsigned char lba_h; // Sector address bits 8-15 (dummy for CMD_IDENTIFY) + unsigned char lba_l; // Sector address bits 0-7 (dummy for CMD_IDENTIFY) + unsigned char data[128]; // Sector payload (dummy for CMD_READ/CMD_IDENTIFY) + unsigned char checksum; // = lba_h ^ lba_l ^ data (CMD_WRITE only) + unsigned char dummy2[3]; +} MCDREQUEST; #endif
\ No newline at end of file diff --git a/libpsn00b/libc/start.c b/libpsn00b/libc/start.c index c5872df..c7dbfe5 100644 --- a/libpsn00b/libc/start.c +++ b/libpsn00b/libc/start.c @@ -88,9 +88,9 @@ void _start(int32_t override_argc, const char **override_argv) { // Calculate how much RAM is available after the loaded executable and // initialize heap accordingly. - void *exe_end = _end + 4; - unsigned int exe_size = (unsigned int) exe_end - (unsigned int) __text_start; - InitHeap(exe_end, 0x200000 - (exe_size + STACK_MAX_SIZE)); + void *exe_end = _end + 4; + size_t exe_size = (size_t) exe_end - (size_t) __text_start; + InitHeap(exe_end, 0x1f0000 - (exe_size + STACK_MAX_SIZE) & 0xfffffffc); if (override_argv) { __argc = override_argc; |
