mirror of
https://github.com/afska/gba-link-connection.git
synced 2026-10-03 06:17:07 -05:00
Adding basic packet sending (works!!)
This commit is contained in:
@@ -341,7 +341,7 @@ Name | Return type | Description
|
||||
|
||||
⚠️ only use the 2Mbps mode with custom hardware (very short wires)!
|
||||
|
||||
⚠️ don't send `0xFFFFFFFF` (or `0xFF` in 8-bit mode), it's reserved for errors!
|
||||
⚠️ returns `0xFFFFFFFF` (or `0xFF`) on misuse or cancelled transfers!
|
||||
|
||||
## SPI Configuration
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
// TODO: Remove
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "_link_tonc_mgba.h"
|
||||
|
||||
#define LINK_MOBILE_BARRIER asm volatile("" ::: "memory")
|
||||
|
||||
@@ -41,9 +42,7 @@
|
||||
|
||||
static volatile char LINK_MOBILE_VERSION[] = "LinkMobile/v6.7.0";
|
||||
|
||||
void LINK_MOBILE_ISR_VBLANK();
|
||||
void LINK_MOBILE_ISR_SERIAL();
|
||||
void LINK_MOBILE_ISR_TIMER();
|
||||
#define LINK_MOBILE_MAX_COMMAND_TRANSFER_LENGTH 255
|
||||
|
||||
class LinkMobile {
|
||||
private:
|
||||
@@ -51,12 +50,27 @@ class LinkMobile {
|
||||
using u16 = unsigned short;
|
||||
using u8 = unsigned char;
|
||||
|
||||
static constexpr int FRAME_LINES = 228;
|
||||
static constexpr int PING_WAIT = FRAME_LINES * 7;
|
||||
static constexpr int CMD_TIMEOUT = 100;
|
||||
static constexpr int ADAPTER_WAITING = 0xD2;
|
||||
static constexpr int GBA_WAITING = 0x4B;
|
||||
static constexpr int OR_VALUE = 0x80;
|
||||
static constexpr int LOGIN_STEPS = 8;
|
||||
static constexpr int COMMAND_HEADER_VALUE1 = 0x99;
|
||||
static constexpr int COMMAND_HEADER_VALUE2 = 0x66;
|
||||
static constexpr int DEVICE_GBA = 0x1;
|
||||
static constexpr int DEVICE_ADAPTER_BLUE = 0x8;
|
||||
static constexpr int DEVICE_ADAPTER_YELLOW = 0x9;
|
||||
static constexpr int DEVICE_ADAPTER_GREEN = 0xA;
|
||||
static constexpr int DEVICE_ADAPTER_RED = 0xB;
|
||||
static constexpr int COMMAND_BEGIN_SESSION = 0x10;
|
||||
static constexpr int COMMAND_END_SESSION = 0x11;
|
||||
static constexpr int COMMAND_DIAL_TELEPHONE = 0x12;
|
||||
static constexpr int COMMAND_HANG_UP_TELEPHONE = 0x13;
|
||||
static constexpr int COMMAND_WAIT_FOR_TELEPHONE_CALL = 0x14;
|
||||
static constexpr int COMMAND_TRANSFER_DATA = 0x15;
|
||||
static constexpr int COMMAND_RESET = 0x16;
|
||||
static constexpr int COMMAND_TELEPHONE_STATUS = 0x17;
|
||||
static constexpr int COMMAND_SIO32 = 0x18;
|
||||
static constexpr int COMMAND_READ_CONFIGURATION_DATA = 0x19;
|
||||
@@ -68,6 +82,12 @@ class LinkMobile {
|
||||
static constexpr int COMMAND_CLOSE_UDP_CONNECTION = 0x26;
|
||||
static constexpr int COMMAND_DNS_QUERY = 0x28;
|
||||
static constexpr int COMMAND_ERROR_STATUS = 0x6E;
|
||||
static constexpr u8 LOGIN_PARTS[] = {0x4e, 0x49, 0x4e, 0x54,
|
||||
0x45, 0x4e, 0x44, 0x4f};
|
||||
static constexpr int SUPPORTED_DEVICES_SIZE = 4;
|
||||
static constexpr u8 SUPPORTED_DEVICES[] = {
|
||||
DEVICE_ADAPTER_BLUE, DEVICE_ADAPTER_YELLOW, DEVICE_ADAPTER_GREEN,
|
||||
DEVICE_ADAPTER_RED};
|
||||
|
||||
public:
|
||||
std::function<void(std::string str)> debug; // TODO: REMOVE
|
||||
@@ -152,17 +172,27 @@ class LinkMobile {
|
||||
|
||||
private:
|
||||
struct MagicBytes {
|
||||
u8 magic1 = 0x99;
|
||||
u8 magic2 = 0x66;
|
||||
u8 magic1 = COMMAND_HEADER_VALUE1;
|
||||
u8 magic2 = COMMAND_HEADER_VALUE2;
|
||||
|
||||
bool isValid() {
|
||||
return magic1 == COMMAND_HEADER_VALUE1 && magic2 == COMMAND_HEADER_VALUE2;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PacketData {
|
||||
u8 bytes[LINK_MOBILE_MAX_COMMAND_TRANSFER_LENGTH];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PacketHeader {
|
||||
u8 commandId;
|
||||
u8 _unused_;
|
||||
u8 dataSizeH; // The Mobile Adapter discards any packets bigger than 255
|
||||
// bytes, effectively forcing the high byte of the packet
|
||||
// data length to be 0.
|
||||
u8 _unused_ = 0;
|
||||
u8 _unusedDataSizeH_ = 0; // The Mobile Adapter discards any packets bigger
|
||||
// than 255 bytes, effectively forcing the high
|
||||
// byte of the packet data length to be 0.
|
||||
u8 dataSizeL;
|
||||
|
||||
u16 sum() { return commandId + _unused_ + _unusedDataSizeH_ + dataSizeL; }
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PacketChecksum {
|
||||
@@ -191,16 +221,34 @@ class LinkMobile {
|
||||
struct Command {
|
||||
MagicBytes magicBytes;
|
||||
PacketHeader packetHeader;
|
||||
u8 data[255];
|
||||
PacketData packetData;
|
||||
PacketChecksum packetChecksum;
|
||||
AcknowledgementSignal acknowledgementSignal;
|
||||
};
|
||||
|
||||
struct CommandResponse {
|
||||
bool success = false;
|
||||
Command command;
|
||||
};
|
||||
|
||||
static constexpr u32 HEADER_SIZE = sizeof(MagicBytes) + sizeof(PacketHeader);
|
||||
static constexpr u32 CHECKSUM_SIZE = sizeof(PacketChecksum);
|
||||
|
||||
LinkSPI* linkSPI = new LinkSPI();
|
||||
State state = NEEDS_RESET;
|
||||
PacketData nextCommandData;
|
||||
u32 nextCommandDataSize = 0;
|
||||
volatile bool isSendingSyncCommand = false;
|
||||
Error lastError = NONE;
|
||||
volatile bool isEnabled = false;
|
||||
|
||||
void addData(u8 value, bool start = false) {
|
||||
if (start)
|
||||
nextCommandDataSize = 0;
|
||||
nextCommandData.bytes[nextCommandDataSize] = value;
|
||||
nextCommandDataSize++;
|
||||
}
|
||||
|
||||
void copyName(char* target, const char* source, u32 length) {
|
||||
u32 len = std::strlen(source);
|
||||
|
||||
@@ -230,6 +278,8 @@ class LinkMobile {
|
||||
linkSPI->activate(LinkSPI::Mode::MASTER_256KBPS,
|
||||
LinkSPI::DataSize::SIZE_8BIT);
|
||||
|
||||
pingAdapter();
|
||||
|
||||
if (!login())
|
||||
return false;
|
||||
|
||||
@@ -251,13 +301,167 @@ class LinkMobile {
|
||||
// Link::_TM_ENABLE | Link::_TM_IRQ | BASE_FREQUENCY;
|
||||
}
|
||||
|
||||
void pingAdapter() {
|
||||
transfer(0);
|
||||
wait(PING_WAIT);
|
||||
}
|
||||
|
||||
bool login() {
|
||||
// TODO: IMPLEMENT
|
||||
for (u32 i = 0; i < LOGIN_STEPS; i++)
|
||||
addData(LOGIN_PARTS[i], i == 0);
|
||||
auto command = buildCommand(COMMAND_BEGIN_SESSION, true);
|
||||
|
||||
LINK_MOBILE_BARRIER;
|
||||
isSendingSyncCommand = true;
|
||||
LINK_MOBILE_BARRIER;
|
||||
|
||||
sendCommand(command);
|
||||
|
||||
LINK_MOBILE_BARRIER;
|
||||
isSendingSyncCommand = false;
|
||||
LINK_MOBILE_BARRIER;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandResponse sendCommand(Command command) {
|
||||
CommandResponse response;
|
||||
|
||||
mgbalog("----------");
|
||||
mgbalog("sending command: %d (%d bytes).", command.packetHeader.commandId,
|
||||
command.packetHeader.dataSizeL);
|
||||
|
||||
// COMMAND
|
||||
const u8* commandBytes = (const u8*)&command;
|
||||
for (u32 i = 0; i < HEADER_SIZE + command.packetHeader.dataSizeL; i++) {
|
||||
u8 r;
|
||||
if ((r = transfer(commandBytes[i])) != ADAPTER_WAITING) {
|
||||
mgbalog("! not waiting: %d", r);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
mgbalog("header + data sent");
|
||||
commandBytes += HEADER_SIZE + LINK_MOBILE_MAX_COMMAND_TRANSFER_LENGTH;
|
||||
for (u32 i = 0; i < CHECKSUM_SIZE; i++) {
|
||||
u8 r;
|
||||
if ((r = transfer(commandBytes[i])) != ADAPTER_WAITING) {
|
||||
mgbalog("! not waiting: %d (i = %d)", r, i);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
mgbalog("checksum sent");
|
||||
if (!exchangeACK(0, command.packetHeader.commandId ^ OR_VALUE)) {
|
||||
mgbalog("! invalid ack");
|
||||
return response;
|
||||
}
|
||||
mgbalog("COMMAND SENT!");
|
||||
|
||||
// wait for response????
|
||||
u8 rr;
|
||||
while ((rr = transfer(GBA_WAITING)) == ADAPTER_WAITING) {
|
||||
mgbalog("waiting %d", rr);
|
||||
}
|
||||
mgbalog("AND NOW %d", rr);
|
||||
if (rr != COMMAND_HEADER_VALUE1) {
|
||||
mgbalog("invalid magic response 1! %d", rr);
|
||||
return response;
|
||||
}
|
||||
rr = transfer(GBA_WAITING);
|
||||
if (rr != COMMAND_HEADER_VALUE2) {
|
||||
mgbalog("invalid magic response 2! %d", rr);
|
||||
return response;
|
||||
}
|
||||
|
||||
u8* responseCommandBytes = (u8*)&response.command;
|
||||
for (u32 i = 2; i < HEADER_SIZE; i++)
|
||||
responseCommandBytes[i] = transfer(GBA_WAITING);
|
||||
responseCommandBytes += HEADER_SIZE;
|
||||
// TODO: VALIDATE high bytes = 0?
|
||||
mgbalog("header received, %d bytes",
|
||||
response.command.packetHeader.dataSizeL);
|
||||
for (u32 i = 0; i < response.command.packetHeader.dataSizeL; i++)
|
||||
responseCommandBytes[i] = transfer(GBA_WAITING);
|
||||
responseCommandBytes += LINK_MOBILE_MAX_COMMAND_TRANSFER_LENGTH;
|
||||
mgbalog("bytes received");
|
||||
for (u32 i = 0; i < CHECKSUM_SIZE; i++)
|
||||
responseCommandBytes[i] = transfer(GBA_WAITING);
|
||||
mgbalog("checksum received");
|
||||
|
||||
// VALIDATE CHECKSUM
|
||||
u32 rChecksum = response.command.packetHeader.sum();
|
||||
for (u32 i = 0; i < response.command.packetHeader.dataSizeL; i++)
|
||||
rChecksum += response.command.packetData.bytes[i];
|
||||
if (msB16(rChecksum) != response.command.packetChecksum.checksumH) {
|
||||
mgbalog("wrong checksum H: expected %d but got %d", msB16(rChecksum),
|
||||
response.command.packetChecksum.checksumH);
|
||||
return response;
|
||||
}
|
||||
if (lsB16(rChecksum) != response.command.packetChecksum.checksumL) {
|
||||
mgbalog("wrong checksum L: expected %d but got %d", lsB16(rChecksum),
|
||||
response.command.packetChecksum.checksumL);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!exchangeACK(response.command.packetHeader.commandId ^ OR_VALUE, 0)) {
|
||||
mgbalog("! invalid ack response");
|
||||
return response;
|
||||
}
|
||||
|
||||
mgbalog("I think everything worked? %d",
|
||||
response.command.packetHeader.dataSizeL);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
bool exchangeACK(u8 localCommandAck, u8 remoteCommandAck) {
|
||||
if (!isSupportedAdapter(transfer(DEVICE_GBA | OR_VALUE)))
|
||||
return false;
|
||||
if (transfer(localCommandAck) != remoteCommandAck)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isSupportedAdapter(u8 ack) {
|
||||
for (u32 i = 0; i < SUPPORTED_DEVICES_SIZE; i++) {
|
||||
if ((SUPPORTED_DEVICES[i] | OR_VALUE) == ack)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Command buildCommand(u8 type, bool withData = false) {
|
||||
Command command;
|
||||
command.packetHeader.commandId = type;
|
||||
command.packetHeader._unused_ = 0;
|
||||
command.packetHeader._unusedDataSizeH_ = 0;
|
||||
command.packetHeader.dataSizeL = withData ? (u8)nextCommandDataSize : 0;
|
||||
if (withData)
|
||||
command.packetData = nextCommandData;
|
||||
u16 checksum = command.packetHeader.sum();
|
||||
for (u32 i = 0; i < command.packetHeader.dataSizeL; i++)
|
||||
checksum += command.packetData.bytes[i];
|
||||
command.packetChecksum.checksumH = msB16(checksum);
|
||||
command.packetChecksum.checksumL = lsB16(checksum);
|
||||
command.acknowledgementSignal.deviceId = DEVICE_GBA | OR_VALUE;
|
||||
command.acknowledgementSignal.commandAck = 0;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
u32 transfer(u32 data) {
|
||||
u32 lines = 0;
|
||||
u32 vCount = Link::_REG_VCOUNT;
|
||||
// mgbalog("SENDING %d", data);
|
||||
u32 receivedData = linkSPI->transfer(
|
||||
data, [this, &lines, &vCount]() { return cmdTimeout(lines, vCount); });
|
||||
|
||||
return receivedData;
|
||||
}
|
||||
|
||||
bool cmdTimeout(u32& lines, u32& vCount) {
|
||||
return timeout(100, lines, vCount);
|
||||
return timeout(CMD_TIMEOUT, lines, vCount);
|
||||
}
|
||||
|
||||
bool timeout(u32 limit, u32& lines, u32& vCount) {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
// considerations:
|
||||
// - when using Normal Mode between two GBAs, use a GBC Link Cable!
|
||||
// - only use the 2Mbps mode with custom hardware (very short wires)!
|
||||
// - don't send 0xFFFFFFFF (or 0xFF in 8-bit mode), it's reserved for errors!
|
||||
// - returns 0xFFFFFFFF (or 0xFF) on misuse or cancelled transfers!
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#include "_link_common.hpp"
|
||||
|
||||
45
lib/_link_tonc_mgba.h
Normal file
45
lib/_link_tonc_mgba.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef LINK_TONC_MGBA
|
||||
#define LINK_TONC_MGBA
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include "tonc_types.h"
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// STRUCTS
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
typedef enum LogLevel {
|
||||
LOG_FATAL = 0x100,
|
||||
LOG_ERR = 0x101,
|
||||
LOG_WARN = 0x102,
|
||||
LOG_INFO = 0x103
|
||||
} LogLevel;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// MACROS
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
#define REG_LOG_ENABLE *(vu16*)0x4FFF780
|
||||
#define REG_LOG_LEVEL *(vu16*)0x4FFF700
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// INLINES
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
static inline void mgbalog(const char* fmt, ...) {
|
||||
REG_LOG_ENABLE = 0xC0DE;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
char* const log = (char*)0x4FFF600;
|
||||
vsnprintf(log, 0x100, fmt, args);
|
||||
REG_LOG_LEVEL = LogLevel::LOG_WARN;
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#endif // LINK_TONC_MGBA
|
||||
|
||||
// EOF
|
||||
Reference in New Issue
Block a user