#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 invoked after every SERIAL interrupt. * If it returns `true`, the wait be aborted. * \warning Blocks the system until the next SERIAL interrupt! */ 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