diff --git a/lib/LinkMobile.hpp b/lib/LinkMobile.hpp index 787efb7..f0f04ec 100644 --- a/lib/LinkMobile.hpp +++ b/lib/LinkMobile.hpp @@ -22,16 +22,9 @@ #include "_link_common.hpp" -#include // TODO: for std::strlen #include "LinkGPIO.hpp" #include "LinkSPI.hpp" -/** - * @brief ... - * - */ -#define LINK_MOBILE_TODO_KNOB 10 - static volatile char LINK_MOBILE_VERSION[] = "LinkMobile/v7.0.0"; #define LINK_MOBILE_MAX_COMMAND_TRANSFER_LENGTH 255 @@ -54,6 +47,7 @@ class LinkMobile { static constexpr auto BASE_FREQUENCY = Link::_TM_FREQ_1024; static constexpr int PING_WAIT_FRAMES = 7; + static constexpr int TIMEOUT_INIT_FRAMES = 30; static constexpr int ADAPTER_WAITING = 0xD2; static constexpr u32 ADAPTER_WAITING_32BIT = 0xD2D2D2D2; static constexpr int GBA_WAITING = 0x4B; @@ -111,14 +105,35 @@ class LinkMobile { SESSION_ACTIVE_ISP }; - enum Error { - // User errors - NONE, - WRONG_STATE, - // Communication errors - NOT_CONNECTED, - FAILURE_ACTIVATING_SIO32, - FAILURE_READING_CONFIGURATION, + enum CommandResult { + PENDING, + SUCCESS, + NOT_WAITING, + INVALID_DEVICE_ID, + INVALID_COMMAND_ACK, + INVALID_MAGIC_BYTES, + WEIRD_DATA_SIZE, + WRONG_CHECKSUM, + ERROR, + WEIRD_ERROR, + TIMEOUT + }; + + struct Error { + enum Type { + NONE, + ADAPTER_NOT_CONNECTED, + UNEXPECTED_FAILURE, + WEIRD_RESPONSE, + BAD_CONFIGURATION_CHECKSUM + }; + + Error::Type type = Error::Type::NONE; + State state = State::NEEDS_RESET; + u8 cmdId = 0; + CommandResult cmdResult = CommandResult::PENDING; + u8 cmdErrorCode = 0; + bool cmdIsSending = false; }; struct ConfigurationData { @@ -151,7 +166,7 @@ class LinkMobile { * @brief ... */ void activate() { - lastError = NONE; + lastError = {}; LINK_MOBILE_BARRIER; isEnabled = false; @@ -168,7 +183,7 @@ class LinkMobile { } void deactivate() { - lastError = NONE; + lastError = {}; isEnabled = false; resetState(); stop(); @@ -189,9 +204,9 @@ class LinkMobile { } Error getLastError(bool clear = true) { - Error error = lastError; + auto error = lastError; if (clear) - lastError = NONE; + lastError = {}; return error; } @@ -201,6 +216,12 @@ class LinkMobile { if (!isEnabled) return; + if (state > NEEDS_RESET && state < SESSION_ACTIVE) { + timeoutFrames++; + if (timeoutFrames >= TIMEOUT_INIT_FRAMES) + return abort(Error::Type::ADAPTER_NOT_CONNECTED); + } + processNewFrame(); } @@ -303,20 +324,6 @@ class LinkMobile { PacketChecksum checksum; }; - enum CommandResult { - PENDING, - SUCCESS, - NOT_WAITING, - INVALID_DEVICE_ID, - INVALID_COMMAND_ACK, - INVALID_MAGIC_BYTES, - WEIRD_DATA_SIZE, - WRONG_CHECKSUM, - ERROR, - UNEXPECTED_RESPONSE, - TIMEOUT - }; - struct CommandResponse { CommandResult result = CommandResult::PENDING; Command command; @@ -333,6 +340,7 @@ class LinkMobile { Direction direction; u16 expectedChecksum; bool expectsResponse; + u8 errorCode; bool isActive = false; void reset() { @@ -343,7 +351,8 @@ class LinkMobile { direction = AsyncCommand::Direction::SENDING; expectedChecksum = 0; expectsResponse = false; - isActive = true; + errorCode = 0; + isActive = false; } bool respondsTo(u8 commandId) { @@ -351,10 +360,24 @@ class LinkMobile { cmd.header.commandId == (commandId | OR_VALUE); } + void finish() { + if (cmd.header.commandId == COMMAND_ERROR_STATUS) { + if (cmd.header.size != 2) { + result = CommandResult::WEIRD_ERROR; + errorCode = cmd.data.bytes[1]; + } else { + result = CommandResult::ERROR; + } + } else { + result = CommandResult::SUCCESS; + } + + state = AsyncCommand::State::COMPLETED; + } + void fail(CommandResult _result) { result = _result; state = AsyncCommand::State::COMPLETED; - // TODO: Command fail log } }; @@ -365,13 +388,14 @@ class LinkMobile { AdapterConfiguration adapterConfiguration; AsyncCommand asyncCommand; u32 waitFrames = 0; + u32 timeoutFrames = 0; LinkSPI* linkSPI = new LinkSPI(); State state = NEEDS_RESET; PacketData nextCommandData; u32 nextCommandDataSize = 0; bool hasPendingTransfer = false; u32 pendingTransfer = 0; - Error lastError = NONE; + Error lastError = {}; volatile bool isEnabled = false; void processNewFrame() { @@ -402,36 +426,23 @@ class LinkMobile { } void processAsyncCommand() { - // TODO: PARSE ERRORS - // TODO: ADAPTER NOT CONNECTED ERROR - - /* - auto remoteCommand = response.command.header.commandId; - if (remoteCommand == COMMAND_ERROR_STATUS) { - if (response.command.header.size != 2 || - response.command.data.bytes[0] != command.header.commandId) { - response.result = CommandResult::UNEXPECTED_RESPONSE; - return response; - } else { - response.result = CommandResult::ERROR; - return response; - } + if (asyncCommand.result != CommandResult::SUCCESS) { + if (state >= STARTING_SESSION && state < SESSION_ACTIVE) + return abort(Error::Type::UNEXPECTED_FAILURE); } - */ + _LMLOG_("%s $%X [%d]", asyncCommand.direction == AsyncCommand::Direction::SENDING ? ">!" : " %d", oldState, newState); (void)oldState; } + void abort(Error::Type errorType) { + lastError = + Error{.type = errorType, + .state = state, + .cmdId = (u8)(asyncCommand.cmd.header.commandId & (~OR_VALUE)), + .cmdResult = asyncCommand.result, + .cmdErrorCode = asyncCommand.errorCode, + .cmdIsSending = + asyncCommand.direction == AsyncCommand::Direction::SENDING}; + + _LMLOG_( + "!! aborted:\n error:%d\n cmdId=%s%d\n cmdResult=%d\n " + "cmdErrorCode=%d", + lastError.type, lastError.cmdIsSending ? ">" : "<", lastError.cmdId, + lastError.cmdResult, lastError.cmdErrorCode); + + resetState(); + stop(); + } + void reset() { resetState(); stop(); @@ -583,8 +597,10 @@ class LinkMobile { setState(NEEDS_RESET); this->adapterConfiguration = AdapterConfiguration{}; - this->asyncCommand.isActive = false; + this->asyncCommand.reset(); this->waitFrames = 0; + this->timeoutFrames = 0; + this->nextCommandDataSize = 0; this->hasPendingTransfer = false; this->pendingTransfer = 0; } @@ -619,6 +635,7 @@ class LinkMobile { asyncCommand.reset(); asyncCommand.cmd = command; asyncCommand.expectsResponse = expectsResponse; + asyncCommand.isActive = true; if (isSIO32Mode()) // Magic+Header advance32(buildU32(command.magicBytes.magic1, command.magicBytes.magic2, @@ -631,6 +648,7 @@ class LinkMobile { _LMLOG_("<< ..."); asyncCommand.reset(); asyncCommand.direction = AsyncCommand::Direction::RECEIVING; + asyncCommand.isActive = true; if (isSIO32Mode()) transferAsync(GBA_WAITING_32BIT); @@ -666,8 +684,7 @@ class LinkMobile { // Acknowledgement Signal (3) if (newData != (asyncCommand.cmd.header.commandId ^ OR_VALUE)) return asyncCommand.fail(CommandResult::INVALID_COMMAND_ACK); - asyncCommand.result = CommandResult::SUCCESS; - asyncCommand.state = AsyncCommand::State::COMPLETED; + asyncCommand.finish(); } } @@ -717,8 +734,7 @@ class LinkMobile { return asyncCommand.fail(CommandResult::INVALID_DEVICE_ID); if (lsB16(ackData) != (asyncCommand.cmd.header.commandId ^ OR_VALUE)) return asyncCommand.fail(CommandResult::INVALID_COMMAND_ACK); - asyncCommand.result = CommandResult::SUCCESS; - asyncCommand.state = AsyncCommand::State::COMPLETED; + asyncCommand.finish(); } } @@ -770,8 +786,7 @@ class LinkMobile { // Acknowledgement Signal (2) if (newData != ACK_SENDER) return asyncCommand.fail(CommandResult::INVALID_COMMAND_ACK); - asyncCommand.result = CommandResult::SUCCESS; - asyncCommand.state = AsyncCommand::State::COMPLETED; + asyncCommand.finish(); } } @@ -849,8 +864,7 @@ class LinkMobile { u32 ackData = msB32(newData); if (!isSupportedAdapter(msB16(ackData)) || lsB16(ackData) != ACK_SENDER) return asyncCommand.fail(CommandResult::INVALID_DEVICE_ID); - asyncCommand.result = CommandResult::SUCCESS; - asyncCommand.state = AsyncCommand::State::COMPLETED; + asyncCommand.finish(); } } @@ -928,5 +942,3 @@ inline void LINK_MOBILE_ISR_TIMER() { #undef _LMLOG_ #endif // LINK_MOBILE_H - -// TODO: Implement state timeouts (e.g. PINGING)