diff --git a/README.md b/README.md index 8070db2..6d0964d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port. - [🔌](#-LinkGPIO) [LinkGPIO.hpp](lib/LinkGPIO.hpp): Use the Link Port however you want to control **any device** (like LEDs, rumble motors, and that kind of stuff)! - [🔗](#-LinkSPI) [LinkSPI.hpp](lib/LinkSPI.hpp): Connect with a PC (like a **Raspberry Pi**) or another GBA (with a GBC Link Cable) using this mode. Transfer up to 2Mbit/s! - [⏱️](#%EF%B8%8F-LinkUART) [LinkUART.hpp](lib/LinkUART.hpp): Easily connect to **any PC** using a USB to UART cable! +- [🟪](#-LinkCube) [LinkCube.hpp](lib/LinkCube.hpp): Exchange data with a *Wii* or a *GameCube* using the classic **Joybus** protocol! - [📱](#-LinkMobile) [LinkMobile.hpp](lib/LinkMobile.hpp): Connect to **the internet** using the *Mobile Adapter GB*, brought back to life thanks to the [REON](https://github.com/REONTeam) project! - [🖱️](#%EF%B8%8F-LinkPS2Mouse) [LinkPS2Mouse.hpp](lib/LinkPS2Mouse.hpp): Connect a **PS/2 mouse** to the GBA for extended controls! - [⌨️](#%EF%B8%8F-LinkPS2Keyboard) [LinkPS2Keyboard.hpp](lib/LinkPS2Keyboard.hpp): Connect a **PS/2 keyboard** to the GBA for extended controls! @@ -81,8 +82,8 @@ You can update these values at any time without creating a new instance: You can also change these compile-time constants: - `LINK_CABLE_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max **per player**). The default value is `15`, which seems fine for most games. - - This affects how much memory is allocated. With the default value, it's `390` bytes. There's a double-buffered pending queue (to avoid data races), `1` incoming queue and `1` outgoing queue. - - You can calculate the memory usage with: + - This affects how much memory is allocated. With the default value, it's around `390` bytes. There's a double-buffered pending queue (to avoid data races), `1` incoming queue and `1` outgoing queue. + - You can approximate the memory usage with: - `(LINK_CABLE_QUEUE_SIZE * sizeof(u16) * LINK_CABLE_MAX_PLAYERS) * 3 + LINK_CABLE_QUEUE_SIZE * sizeof(u16)` <=> `LINK_CABLE_QUEUE_SIZE * 26` ## Methods @@ -186,8 +187,8 @@ You can update these values at any time without creating a new instance: You can also change these compile-time constants: - `LINK_WIRELESS_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max). The default value is `30`, which seems fine for most games. - - This affects how much memory is allocated. With the default value, it's `960` bytes. There's a double-buffered incoming queue and a double-buffered outgoing queue (to avoid data races). - - You can calculate the memory usage with: + - This affects how much memory is allocated. With the default value, it's around `960` bytes. There's a double-buffered incoming queue and a double-buffered outgoing queue (to avoid data races). + - You can approximate the memory usage with: - `LINK_WIRELESS_QUEUE_SIZE * sizeof(Message) * 4` <=> `LINK_WIRELESS_QUEUE_SIZE * 32` - `LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH` and `LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH`: to set the biggest allowed transfer per timer tick. Transfers contain retransmission headers and multiple user messages. These values must be in the range `[6;20]` for servers and `[2;4]` for clients. The default values are `20` and `4`, but you might want to set them a bit lower to reduce CPU usage. - `LINK_WIRELESS_PUT_ISR_IN_IWRAM`: to put critical functions in IWRAM, which can significantly improve performance due to its faster access. This is disabled by default to conserve IWRAM space, which is limited, but it's enabled in demos to showcase its performance benefits. @@ -421,6 +422,36 @@ The GBA operates using `1` stop bit, but everything else can be configured. By d - Green wire (`TX`) -> GBA `SI`. - White wire (`RX`) -> GBA `SO`. +# 🟪 LinkCube + +*(aka JOYBUS Mode)* + +This is the GBA's implementation of JOYBUS, in which users connect the console to a *GameCube* (or *Wii* with GC ports) using an official adapter. The library can be tested using *Dolphin/mGBA* and [gba-joybus-tester](https://github.com/afska/gba-joybus-tester). + +![screenshot](https://github.com/user-attachments/assets/93c11c9a-bdbf-4726-a070-895465739789) + +You can change these compile-time constants: +- `LINK_CUBE_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing values the queues can store at max). The default value is `10`, which seems fine for most games. + - This affects how much memory is allocated. With the default value, it's around `120` bytes. There's a double-buffered pending queue (to avoid data races), and 1 outgoing queue. + - You can approximate the memory usage with: + - `LINK_CUBE_QUEUE_SIZE * sizeof(u32) * 3` <=> `LINK_CUBE_QUEUE_SIZE * 12` + +## Methods + +Name | Return type | Description +--- | --- | --- +`isActive()` | **bool** | Returns whether the library is active or not. +`activate()` | - | Activates the library. +`deactivate()` | - | Deactivates the library. +`wait()` | **bool** | Waits for data. Returns `true` on success, or `false` on JOYBUS reset. +`wait(cancel)` | **bool** | Like `wait()` but accepts a `cancel()` function. The library will continuously invoke it, and abort the wait if it returns `true`. +`canRead()` | **bool** | Returns `true` if there are pending received values to read. +`read()` | **u32** | Dequeues and returns the next received value. If there's no received data, a `0` will be returned. +`peek()` | **u32** | Returns the next received value without dequeuing it. If there's no received data, a `0` will be returned. +`send(data)` | - | Sends 32-bit `data`. If the other end asks for data at the same time you call this method, a `0x00000000` will be sent. +`pendingCount()` | **u32** | Returns the number of pending outgoing transfers. +`didReset([clear])` | **bool** | Returns whether a JOYBUS reset was requested or not. After this call, the reset flag is cleared if `clear` is `true` (default behavior). + # 📱 LinkMobile *(aka Mobile Adapter GB)* diff --git a/docs/img/link-cube.gif b/docs/img/link-cube.gif new file mode 100644 index 0000000..8915791 Binary files /dev/null and b/docs/img/link-cube.gif differ diff --git a/examples/LinkCube_demo/Makefile b/examples/LinkCube_demo/Makefile new file mode 100644 index 0000000..dbc85d2 --- /dev/null +++ b/examples/LinkCube_demo/Makefile @@ -0,0 +1,284 @@ +# +# Template tonc makefile +# +# Yoinked mostly from DKP's template +# + +# === SETUP =========================================================== + +# --- No implicit rules --- +.SUFFIXES: + +# --- Paths --- +export TONCLIB := ${DEVKITPRO}/libtonc + +# === TONC RULES ====================================================== +# +# Yes, this is almost, but not quite, completely like to +# DKP's base_rules and gba_rules +# + +export PATH := $(DEVKITARM)/bin:$(PATH) + + +# --- Executable names --- + +PREFIX ?= arm-none-eabi- + +export CC := $(PREFIX)gcc +export CXX := $(PREFIX)g++ +export AS := $(PREFIX)as +export AR := $(PREFIX)ar +export NM := $(PREFIX)nm +export OBJCOPY := $(PREFIX)objcopy + +# LD defined in Makefile + + +# === LINK / TRANSLATE ================================================ + +%.gba : %.elf + @$(OBJCOPY) -O binary $< $@ + @echo built ... $(notdir $@) + @gbafix $@ -t$(TITLE) + +#---------------------------------------------------------------------- + +%.mb.elf : + @echo Linking multiboot + $(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(NM) -Sn $@ > $(basename $(notdir $@)).map + +#---------------------------------------------------------------------- + +%.elf : + @echo Linking cartridge + $(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(NM) -Sn $@ > $(basename $(notdir $@)).map + +#---------------------------------------------------------------------- + +%.a : + @echo $(notdir $@) + @rm -f $@ + $(AR) -crs $@ $^ + + +# === OBJECTIFY ======================================================= + +%.iwram.o : %.iwram.cpp + @echo $(notdir $<) + $(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@ + +#---------------------------------------------------------------------- +%.iwram.o : %.iwram.c + @echo $(notdir $<) + $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(IARCH) -c $< -o $@ + +#---------------------------------------------------------------------- + +%.o : %.cpp + @echo $(notdir $<) + $(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(RARCH) -c $< -o $@ + +#---------------------------------------------------------------------- + +%.o : %.c + @echo $(notdir $<) + $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(RARCH) -c $< -o $@ + +#---------------------------------------------------------------------- + +%.o : %.s + @echo $(notdir $<) + $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ + +#---------------------------------------------------------------------- + +%.o : %.S + @echo $(notdir $<) + $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ + + +#---------------------------------------------------------------------- +# canned command sequence for binary data +#---------------------------------------------------------------------- + +define bin2o + bin2s $< | $(AS) -o $(@) + echo "extern const u8" `(echo $( `(echo $(> `(echo $(> `(echo $( $(BUILD)/$(TARGET).map + +all : $(BUILD) + +clean: + @echo clean ... + @rm -rf $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav + + +else # If we're here, we should be in the BUILD dir + +DEPENDS := $(OFILES:.o=.d) + +# --- Main targets ---- + +$(OUTPUT).gba : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +-include $(DEPENDS) + + +endif # End BUILD switch + +# --- More targets ---------------------------------------------------- + +.PHONY: clean rebuild start + +rebuild: clean $(BUILD) + +start: + start "$(TARGET).gba" + +restart: rebuild start + +# EOF diff --git a/examples/LinkCube_demo/src/main.cpp b/examples/LinkCube_demo/src/main.cpp new file mode 100644 index 0000000..0ad996e --- /dev/null +++ b/examples/LinkCube_demo/src/main.cpp @@ -0,0 +1,120 @@ +// (0) Include the header +#include "../../../lib/LinkCube.hpp" + +#include +#include +#include "../../_lib/interrupt.h" + +void log(std::string text); +void wait(u32 verticalLines); +bool didPress(unsigned short key, bool& pressed); +inline void VBLANK() {} + +bool a = false, b = false, l = false; + +// (1) Create a LinkCube instance +LinkCube* linkCube = new LinkCube(); + +void init() { + REG_DISPCNT = DCNT_MODE0 | DCNT_BG0; + tte_init_se_default(0, BG_CBB(0) | BG_SBB(31)); + + // (2) Add the interrupt service routines + interrupt_init(); + interrupt_set_handler(INTR_VBLANK, VBLANK); + interrupt_enable(INTR_VBLANK); + interrupt_set_handler(INTR_SERIAL, LINK_CUBE_ISR_SERIAL); + interrupt_enable(INTR_SERIAL); +} + +int main() { + init(); + + // (3) Initialize the library + linkCube->activate(); + + u32 counter = 0; + std::string received = ""; + bool reset = false; + u32 vblanks = 0; + + while (true) { + // Title + std::string output = + "LinkCube_demo (v7.0.0)" + std::string(reset ? " !RESET!" : "") + + "\n\nPress A to send\nPress B to clear\n (L = " + "+1024)\n\nLast sent: " + + std::to_string(counter) + + "\n(pending = " + std::to_string(linkCube->pendingCount()) + + ")\n\nReceived:\n" + received; + + // (4) Send 32-bit values + if (didPress(KEY_A, a)) { + counter++; + linkCube->send(counter); + } + + // +1024 + if (didPress(KEY_L, l)) { + counter += 1024; + linkCube->send(counter); + } + + // (5) Read 32-bit values + while (linkCube->canRead()) { + received += std::to_string(linkCube->read()) + ", "; + } + + // Clear + if (didPress(KEY_B, b)) + received = ""; + + // Reset warning + if (linkCube->didReset()) { + counter = 0; + reset = true; + vblanks = 0; + } + if (reset) { + vblanks++; + if (vblanks > 60) + reset = false; + } + + // Print + VBlankIntrWait(); + log(output); + } + + return 0; +} + +void log(std::string text) { + tte_erase_screen(); + tte_write("#{P:0,0}"); + tte_write(text.c_str()); +} + +void wait(u32 verticalLines) { + u32 count = 0; + u32 vCount = REG_VCOUNT; + + while (count < verticalLines) { + if (REG_VCOUNT != vCount) { + count++; + vCount = REG_VCOUNT; + } + }; +} + +bool didPress(u16 key, bool& pressed) { + u16 keys = ~REG_KEYS & KEY_ANY; + bool isPressedNow = false; + if ((keys & key) && !pressed) { + pressed = true; + isPressedNow = true; + } + if (pressed && !(keys & key)) + pressed = false; + return isPressedNow; +} diff --git a/lib/LinkCable.hpp b/lib/LinkCable.hpp index 1fb73d8..4e7a067 100644 --- a/lib/LinkCable.hpp +++ b/lib/LinkCable.hpp @@ -45,13 +45,13 @@ #include "_link_common.hpp" /** - * @brief Buffer size (how many incoming and outgoing messages - * the queues can store at max **per player**). The default value is `15`, which - * seems fine for most games. + * @brief Buffer size (how many incoming and outgoing messages the queues can + * store at max **per player**). The default value is `15`, which seems fine for + * most games. * \warning This affects how much memory is allocated. With the default value, - * it's `390` bytes. There's a double-buffered pending queue (to avoid data - * races), 1 incoming queue and 1 outgoing queue. - * \warning You can calculate the usage with `LINK_CABLE_QUEUE_SIZE * 26`. + * it's around `390` bytes. There's a double-buffered pending queue (to avoid + * data races), 1 incoming queue and 1 outgoing queue. \warning You can + * approximate the usage with `LINK_CABLE_QUEUE_SIZE * 26`. */ #define LINK_CABLE_QUEUE_SIZE 15 diff --git a/lib/LinkCube.hpp b/lib/LinkCube.hpp new file mode 100644 index 0000000..f07194d --- /dev/null +++ b/lib/LinkCube.hpp @@ -0,0 +1,274 @@ +#ifndef LINK_CUBE_H +#define LINK_CUBE_H + +// -------------------------------------------------------------------------- +// A JOYBUS handler for the Link Port. +// -------------------------------------------------------------------------- +// Usage: +// - 1) Include this header in your main.cpp file and add: +// LinkCube* linkCube = new LinkCube(); +// - 2) Add the required interrupt service routines: (*) +// irq_init(NULL); +// irq_add(II_SERIAL, LINK_CUBE_ISR_SERIAL); +// - 3) Initialize the library with: +// linkCube->activate(); +// - 4) Send 32-bit values: +// linkCube->send(0x12345678); +// // (now linkCube->pendingCount() will be 1 until it's actually sent) +// - 5) Read 32-bit values: +// if (linkCube->canRead()) { +// u32 value = linkCube->read(); +// // ... +// } +// -------------------------------------------------------------------------- +// (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug. +// That causes packet loss. You REALLY want to use libugba's instead. +// (see examples) +// -------------------------------------------------------------------------- + +#include "_link_common.hpp" + +/** + * @brief Buffer size (how many incoming and outgoing values the queues can + * store at max). The default value is `10`, which seems fine for most games. + * \warning This affects how much memory is allocated. With the default value, + * it's around `120` bytes. There's a double-buffered pending queue (to avoid + * data races), and 1 outgoing queue. + * \warning You can approximate the usage with `LINK_CUBE_QUEUE_SIZE * 12`. + */ +#define LINK_CUBE_QUEUE_SIZE 10 + +static volatile char LINK_CUBE_VERSION[] = "LinkCube/v7.0.0"; + +#define LINK_CUBE_BARRIER asm volatile("" ::: "memory") + +/** + * @brief A JOYBUS handler for the Link Port. + */ +class LinkCube { + private: + using u32 = unsigned int; + using u16 = unsigned short; + using u8 = unsigned char; + using U32Queue = Link::Queue; + + static constexpr int BIT_CMD_RESET = 0; + static constexpr int BIT_CMD_RECEIVE = 1; + static constexpr int BIT_CMD_SEND = 2; + static constexpr int BIT_IRQ = 6; + static constexpr int BIT_JOYBUS_HIGH = 14; + static constexpr int BIT_GENERAL_PURPOSE_LOW = 14; + static constexpr int BIT_GENERAL_PURPOSE_HIGH = 15; + + public: + /** + * @brief Returns whether the library is active or not. + */ + [[nodiscard]] bool isActive() { return isEnabled; } + + /** + * @brief Activates the library. + */ + void activate() { + LINK_CUBE_BARRIER; + isEnabled = false; + LINK_CUBE_BARRIER; + + resetState(); + stop(); + + LINK_CUBE_BARRIER; + isEnabled = true; + LINK_CUBE_BARRIER; + + start(); + } + + /** + * @brief Deactivates the library. + */ + void deactivate() { + isEnabled = false; + resetState(); + stop(); + } + + /** + * @brief Waits for data. Returns `true` on success, or `false` on + * JOYBUS reset. + */ + bool wait() { + return wait([]() { return false; }); + } + + /** + * @brief Waits for data. Returns `true` on success, or `false` on + * JOYBUS reset or cancellation. + * @param cancel A function that will be continuously invoked. If it returns + * `true`, the wait be aborted. + */ + template + bool wait(F cancel) { + resetFlag = false; + + while (!resetFlag && !canRead() && !cancel()) + Link::_IntrWait(1, Link::_IRQ_SERIAL); + + return canRead(); + } + + /** + * @brief Returns `true` if there are pending received values to read. + */ + [[nodiscard]] bool canRead() { return !incomingQueue.isEmpty(); } + + /** + * @brief Dequeues and returns the next received value. + * \warning If there's no received data, a `0` will be returned. + */ + u32 read() { return incomingQueue.syncPop(); } + + /** + * @brief Returns the next received value without dequeuing it. + * \warning If there's no received data, a `0` will be returned. + */ + [[nodiscard]] u32 peek() { return incomingQueue.peek(); } + + /** + * @brief Sends 32-bit `data`. + * @param data The value to be sent. + * \warning If the other end asks for data at the same time you call this + * method, a `0x00000000` will be sent. + */ + void send(u32 data) { outgoingQueue.syncPush(data); } + + /** + * @brief Returns the number of pending outgoing transfers. + */ + [[nodiscard]] u32 pendingCount() { return outgoingQueue.size(); } + + /** + * @brief Returns whether a JOYBUS reset was requested or not. After this + * call, the reset flag is cleared if `clear` is `true` (default behavior). + * @param clear Whether it should clear the reset flag or not. + */ + bool didReset(bool clear = true) { + bool reset = resetFlag; + if (clear) + resetFlag = false; + return reset; + } + + /** + * @brief This method is called by the SERIAL interrupt handler. + * \warning This is internal API! + */ + void _onSerial() { + if (!isEnabled) + return; + + if (isBitHigh(BIT_CMD_RESET)) { + resetState(); + resetFlag = true; + setBitHigh(BIT_CMD_RESET); + } + + if (isBitHigh(BIT_CMD_RECEIVE)) { + newIncomingQueue.push(getData()); + setBitHigh(BIT_CMD_RECEIVE); + } + + if (isBitHigh(BIT_CMD_SEND)) { + setPendingData(); + setBitHigh(BIT_CMD_SEND); + } + + copyState(); + } + + private: + U32Queue newIncomingQueue; + U32Queue incomingQueue; + U32Queue outgoingQueue; + volatile bool resetFlag = false; + volatile bool needsClear = false; + volatile bool isEnabled = false; + + void copyState() { + if (incomingQueue.isReading()) + return; + + if (needsClear) { + incomingQueue.clear(); + needsClear = false; + } + + while (!newIncomingQueue.isEmpty()) + incomingQueue.push(newIncomingQueue.pop()); + } + + void resetState() { + needsClear = false; + newIncomingQueue.clear(); + if (incomingQueue.isReading()) + needsClear = true; + else + incomingQueue.clear(); + outgoingQueue.syncClear(); + resetFlag = false; + } + + void setPendingData() { + setData(outgoingQueue.isWriting() ? 0 : outgoingQueue.pop()); + } + + void setData(u32 data) { + Link::_REG_JOY_TRANS_H = msB32(data); + Link::_REG_JOY_TRANS_L = lsB32(data); + } + + u32 getData() { + return buildU32(Link::_REG_JOY_RECV_H, Link::_REG_JOY_RECV_L); + } + + void stop() { + setInterruptsOff(); + setGeneralPurposeMode(); + } + + void start() { + setJoybusMode(); + setInterruptsOn(); + } + + void setJoybusMode() { + Link::_REG_RCNT = Link::_REG_RCNT | (1 << BIT_JOYBUS_HIGH) | + (1 << BIT_GENERAL_PURPOSE_HIGH); + } + + void setGeneralPurposeMode() { + Link::_REG_RCNT = (Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_LOW)) | + (1 << BIT_GENERAL_PURPOSE_HIGH); + } + + void setInterruptsOn() { setBitHigh(BIT_IRQ); } + void setInterruptsOff() { setBitLow(BIT_IRQ); } + + u32 buildU32(u16 msB, u16 lsB) { return (msB << 16) | lsB; } + u16 msB32(u32 value) { return value >> 16; } + u16 lsB32(u32 value) { return value & 0xffff; } + bool isBitHigh(u8 bit) { return (Link::_REG_JOYCNT >> bit) & 1; } + void setBitHigh(u8 bit) { Link::_REG_JOYCNT |= 1 << bit; } + void setBitLow(u8 bit) { Link::_REG_JOYCNT &= ~(1 << bit); } +}; + +extern LinkCube* linkCube; + +/** + * @brief SERIAL interrupt handler. + */ +inline void LINK_CUBE_ISR_SERIAL() { + linkCube->_onSerial(); +} + +#endif // LINK_CUBE_H diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp index 4d9e18c..e139119 100644 --- a/lib/LinkUART.hpp +++ b/lib/LinkUART.hpp @@ -256,19 +256,7 @@ class LinkUART { /** * @brief Reads a byte. Returns 0 if nothing is found. */ - u8 read() { - LINK_UART_BARRIER; - incomingQueue.startReading(); - LINK_UART_BARRIER; - - u8 data = incomingQueue.pop(); - - LINK_UART_BARRIER; - incomingQueue.stopReading(); - LINK_UART_BARRIER; - - return data; - } + u8 read() { return incomingQueue.syncPop(); } /** * @brief Sends a `data` byte. diff --git a/lib/LinkWireless.hpp b/lib/LinkWireless.hpp index 009d95e..cc2ea48 100644 --- a/lib/LinkWireless.hpp +++ b/lib/LinkWireless.hpp @@ -67,9 +67,9 @@ * @brief Buffer size (how many incoming and outgoing messages the queues can * store at max). The default value is `30`, which seems fine for most games. * \warning This affects how much memory is allocated. With the default value, - * it's `960` bytes. There's a double-buffered incoming queue and a + * it's around `960` bytes. There's a double-buffered incoming queue and a * double-buffered outgoing queue (to avoid data races). - * \warning You can calculate the usage with `LINK_WIRELESS_QUEUE_SIZE * 32`. + * \warning You can approximate the usage with `LINK_WIRELESS_QUEUE_SIZE * 32`. */ #define LINK_WIRELESS_QUEUE_SIZE 30 diff --git a/lib/_link_common.hpp b/lib/_link_common.hpp index 1dffb72..464d215 100644 --- a/lib/_link_common.hpp +++ b/lib/_link_common.hpp @@ -76,6 +76,12 @@ inline vu32& _REG_SIODATA32 = *reinterpret_cast(_REG_BASE + 0x0120); inline vu16& _REG_SIODATA8 = *reinterpret_cast(_REG_BASE + 0x012A); inline vu16& _REG_SIOMLT_SEND = *reinterpret_cast(_REG_BASE + 0x012A); inline vu16* const _REG_SIOMULTI = reinterpret_cast(_REG_BASE + 0x0120); +inline vu16& _REG_JOYCNT = *reinterpret_cast(_REG_BASE + 0x0140); +inline vu16& _REG_JOY_RECV_L = *reinterpret_cast(_REG_BASE + 0x0150); +inline vu16& _REG_JOY_RECV_H = *reinterpret_cast(_REG_BASE + 0x0152); +inline vu16& _REG_JOY_TRANS_L = *reinterpret_cast(_REG_BASE + 0x0154); +inline vu16& _REG_JOY_TRANS_H = *reinterpret_cast(_REG_BASE + 0x0156); +inline vu16& _REG_JOYSTAT = *reinterpret_cast(_REG_BASE + 0x0158); inline vu16& _REG_VCOUNT = *reinterpret_cast(_REG_BASE + 0x0006); inline vu16& _REG_KEYS = *reinterpret_cast(_REG_BASE + 0x0130); inline vu16& _REG_TM1CNT_L = *reinterpret_cast(_REG_BASE + 0x0104); @@ -213,6 +219,19 @@ class Queue { } } + T syncPop() { + _isReading = true; + asm volatile("" ::: "memory"); + + auto value = pop(); + + asm volatile("" ::: "memory"); + _isReading = false; + asm volatile("" ::: "memory"); + + return value; + } + void syncClear() { if (_isReading) return; // (it will be cleared later anyway) @@ -224,8 +243,8 @@ class Queue { } u32 size() { return count; } - bool isEmpty() { return size() == 0; } - bool isFull() { return size() == Size; } + bool isEmpty() { return count == 0; } + bool isFull() { return count == Size; } bool isReading() { return _isReading; } bool isWriting() { return _isWriting; } bool canMutate() { return !_isReading && !_isWriting; }