mirror of
https://github.com/afska/gba-link-connection.git
synced 2026-04-17 22:59:36 -05:00
1418 lines
39 KiB
C++
1418 lines
39 KiB
C++
#ifndef LINK_WIRELESS_H
|
|
#define LINK_WIRELESS_H
|
|
|
|
// --------------------------------------------------------------------------
|
|
// A high level driver for the GBA Wireless Adapter.
|
|
// --------------------------------------------------------------------------
|
|
// Usage:
|
|
// - 1) Include this header in your main.cpp file and add:
|
|
// LinkWireless* linkWireless = new LinkWireless();
|
|
// - 2) Add the required interrupt service routines: (*)
|
|
// irq_init(NULL);
|
|
// irq_add(II_VBLANK, LINK_WIRELESS_ISR_VBLANK);
|
|
// irq_add(II_SERIAL, LINK_WIRELESS_ISR_SERIAL);
|
|
// irq_add(II_TIMER3, LINK_WIRELESS_ISR_TIMER);
|
|
// - 3) Initialize the library with:
|
|
// linkWireless->activate();
|
|
// - 4) Start a server:
|
|
// linkWireless->serve();
|
|
//
|
|
// // `getState()` should return SERVING now...
|
|
// // `currentPlayerId()` should return 0
|
|
// // `playerCount()` should return the number of active consoles
|
|
// - 5) Connect to a server:
|
|
// std::vector<LinkWireless::Server> servers;
|
|
// linkWireless->getServers(servers);
|
|
// if (servers.empty()) return;
|
|
//
|
|
// linkWireless->connect(servers[0].id);
|
|
// while (linkWireless->getState() == LinkWireless::State::CONNECTING)
|
|
// linkWireless->keepConnecting();
|
|
//
|
|
// // `getState()` should return CONNECTED now...
|
|
// // `currentPlayerId()` should return 1, 2, 3, or 4 (the host is 0)
|
|
// // `playerCount()` should return the number of active consoles
|
|
// - 6) Send data:
|
|
// linkConnection->send(std::vector<u32>{1, 2, 3});
|
|
// - 7) Receive data:
|
|
// auto messages = std::vector<LinkWireless::Message>{};
|
|
// linkConnection->receive(messages);
|
|
// if (messages.size() > 0) {
|
|
// // ...
|
|
// }
|
|
// - 8) Disconnect:
|
|
// linkWireless->activate();
|
|
// // (resets the adapter)
|
|
// --------------------------------------------------------------------------
|
|
// (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug.
|
|
// That can cause packet loss. You might want to use libugba's instead.
|
|
// (see examples)
|
|
// --------------------------------------------------------------------------
|
|
// `send(...)` restrictions:
|
|
// - servers can send up to 19 words of 32 bits at a time!
|
|
// - clients can send up to 3 words of 32 bits at a time!
|
|
// - if retransmission is on, these limits drop to 14 and 1!
|
|
// - don't send 0xFFFFFFFF, it's reserved for errors!
|
|
// --------------------------------------------------------------------------
|
|
|
|
#include <tonc_core.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "LinkGPIO.h"
|
|
#include "LinkSPI.h"
|
|
|
|
#define LINK_WIRELESS_MAX_PLAYERS 5
|
|
#define LINK_WIRELESS_MIN_PLAYERS 2
|
|
#define LINK_WIRELESS_QUEUE_SIZE 30
|
|
#define LINK_WIRELESS_DEFAULT_TIMEOUT 5
|
|
#define LINK_WIRELESS_DEFAULT_REMOTE_TIMEOUT 25
|
|
#define LINK_WIRELESS_DEFAULT_INTERVAL 50
|
|
#define LINK_WIRELESS_DEFAULT_SEND_TIMER_ID 3
|
|
#define LINK_WIRELESS_BASE_FREQUENCY TM_FREQ_1024
|
|
#define LINK_WIRELESS_MSG_CONFIRMATION 0
|
|
#define LINK_WIRELESS_PING_WAIT 50
|
|
#define LINK_WIRELESS_TRANSFER_WAIT 15
|
|
#define LINK_WIRELESS_BROADCAST_SEARCH_WAIT_FRAMES 60
|
|
#define LINK_WIRELESS_CMD_TIMEOUT 100
|
|
#define LINK_WIRELESS_MAX_GAME_NAME_LENGTH 14
|
|
#define LINK_WIRELESS_MAX_USER_NAME_LENGTH 8
|
|
#define LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH 20
|
|
#define LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH 4
|
|
#define LINK_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH 50
|
|
#define LINK_WIRELESS_LOGIN_STEPS 9
|
|
#define LINK_WIRELESS_COMMAND_HEADER 0x9966
|
|
#define LINK_WIRELESS_RESPONSE_ACK 0x80
|
|
#define LINK_WIRELESS_DATA_REQUEST 0x80000000
|
|
#define LINK_WIRELESS_SETUP_MAGIC 0x003c0420
|
|
#define LINK_WIRELESS_STILL_CONNECTING 0x01000000
|
|
#define LINK_WIRELESS_BROADCAST_LENGTH 6
|
|
#define LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH \
|
|
(1 + LINK_WIRELESS_BROADCAST_LENGTH)
|
|
#define LINK_WIRELESS_COMMAND_HELLO 0x10
|
|
#define LINK_WIRELESS_COMMAND_SETUP 0x17
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST 0x16
|
|
#define LINK_WIRELESS_COMMAND_START_HOST 0x19
|
|
#define LINK_WIRELESS_COMMAND_ACCEPT_CONNECTIONS 0x1a
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST_READ_START 0x1c
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST_READ_POLL 0x1d
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST_READ_END 0x1e
|
|
#define LINK_WIRELESS_COMMAND_CONNECT 0x1f
|
|
#define LINK_WIRELESS_COMMAND_IS_FINISHED_CONNECT 0x20
|
|
#define LINK_WIRELESS_COMMAND_FINISH_CONNECTION 0x21
|
|
#define LINK_WIRELESS_COMMAND_SEND_DATA 0x24
|
|
#define LINK_WIRELESS_COMMAND_RECEIVE_DATA 0x26
|
|
#define LINK_WIRELESS_BARRIER asm volatile("" ::: "memory")
|
|
|
|
#define LINK_WIRELESS_RESET_IF_NEEDED \
|
|
if (!isEnabled) \
|
|
return false; \
|
|
if (state == NEEDS_RESET) \
|
|
if (!reset()) \
|
|
return false;
|
|
|
|
static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v4.3.0";
|
|
|
|
void LINK_WIRELESS_ISR_VBLANK();
|
|
void LINK_WIRELESS_ISR_SERIAL();
|
|
void LINK_WIRELESS_ISR_TIMER();
|
|
const u16 LINK_WIRELESS_LOGIN_PARTS[] = {0x494e, 0x494e, 0x544e, 0x544e, 0x4e45,
|
|
0x4e45, 0x4f44, 0x4f44, 0x8001};
|
|
const u16 LINK_WIRELESS_USER_MAX_SERVER_TRANSFER_LENGTHS[] = {19, 14};
|
|
const u32 LINK_WIRELESS_USER_MAX_CLIENT_TRANSFER_LENGTHS[] = {3, 1};
|
|
const u16 LINK_WIRELESS_TIMER_IRQ_IDS[] = {IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2,
|
|
IRQ_TIMER3};
|
|
|
|
class LinkWireless {
|
|
public:
|
|
enum State {
|
|
NEEDS_RESET,
|
|
AUTHENTICATED,
|
|
SEARCHING,
|
|
SERVING,
|
|
CONNECTING,
|
|
CONNECTED
|
|
};
|
|
|
|
enum Error {
|
|
// User errors
|
|
NONE = 0,
|
|
WRONG_STATE = 1,
|
|
GAME_NAME_TOO_LONG = 2,
|
|
USER_NAME_TOO_LONG = 3,
|
|
INVALID_SEND_SIZE = 4,
|
|
BUFFER_IS_FULL = 5,
|
|
// Communication errors
|
|
COMMAND_FAILED = 6,
|
|
WEIRD_PLAYER_ID = 7,
|
|
SEND_DATA_FAILED = 8,
|
|
RECEIVE_DATA_FAILED = 9,
|
|
BAD_CONFIRMATION = 10,
|
|
BAD_MESSAGE = 11,
|
|
ACKNOWLEDGE_FAILED = 12,
|
|
TIMEOUT = 13,
|
|
REMOTE_TIMEOUT = 14
|
|
};
|
|
|
|
struct Message {
|
|
u32 _packetId = 0;
|
|
|
|
u32 data[LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH];
|
|
u32 dataSize = 0;
|
|
|
|
u8 playerId = 0;
|
|
};
|
|
|
|
struct Server {
|
|
u16 id;
|
|
std::string gameName;
|
|
std::string userName;
|
|
};
|
|
|
|
explicit LinkWireless(
|
|
bool forwarding = true,
|
|
bool retransmission = true,
|
|
u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS,
|
|
u32 timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
|
|
u32 remoteTimeout = LINK_WIRELESS_DEFAULT_REMOTE_TIMEOUT,
|
|
u16 interval = LINK_WIRELESS_DEFAULT_INTERVAL,
|
|
u8 sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID) {
|
|
this->config.forwarding = forwarding;
|
|
this->config.retransmission = retransmission;
|
|
this->config.maxPlayers = maxPlayers;
|
|
this->config.timeout = timeout;
|
|
this->config.remoteTimeout = remoteTimeout;
|
|
this->config.interval = interval;
|
|
this->config.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID;
|
|
}
|
|
|
|
bool isActive() { return isEnabled; }
|
|
|
|
bool activate() {
|
|
lastError = NONE;
|
|
isEnabled = false;
|
|
|
|
LINK_WIRELESS_BARRIER;
|
|
bool success = reset();
|
|
LINK_WIRELESS_BARRIER;
|
|
|
|
isEnabled = true;
|
|
return success;
|
|
}
|
|
|
|
void deactivate() {
|
|
lastError = NONE;
|
|
isEnabled = false;
|
|
resetState();
|
|
stop();
|
|
}
|
|
|
|
bool serve(std::string gameName = "", std::string userName = "") {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != AUTHENTICATED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
if (gameName.length() > LINK_WIRELESS_MAX_GAME_NAME_LENGTH) {
|
|
lastError = GAME_NAME_TOO_LONG;
|
|
return false;
|
|
}
|
|
if (userName.length() > LINK_WIRELESS_MAX_GAME_NAME_LENGTH) {
|
|
lastError = USER_NAME_TOO_LONG;
|
|
return false;
|
|
}
|
|
gameName.append(LINK_WIRELESS_MAX_GAME_NAME_LENGTH - gameName.length(), 0);
|
|
userName.append(LINK_WIRELESS_MAX_USER_NAME_LENGTH - userName.length(), 0);
|
|
|
|
addData(buildU32(buildU16(gameName[1], gameName[0]), buildU16(0x02, 0x02)),
|
|
true);
|
|
addData(buildU32(buildU16(gameName[5], gameName[4]),
|
|
buildU16(gameName[3], gameName[2])));
|
|
addData(buildU32(buildU16(gameName[9], gameName[8]),
|
|
buildU16(gameName[7], gameName[6])));
|
|
addData(buildU32(buildU16(gameName[13], gameName[12]),
|
|
buildU16(gameName[11], gameName[10])));
|
|
addData(buildU32(buildU16(userName[3], userName[2]),
|
|
buildU16(userName[1], userName[0])));
|
|
addData(buildU32(buildU16(userName[7], userName[6]),
|
|
buildU16(userName[5], userName[4])));
|
|
bool success = sendCommand(LINK_WIRELESS_COMMAND_BROADCAST, true).success &&
|
|
sendCommand(LINK_WIRELESS_COMMAND_START_HOST).success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
wait(LINK_WIRELESS_TRANSFER_WAIT);
|
|
state = SERVING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getServers(std::vector<Server>& servers) {
|
|
return getServers(servers, []() {});
|
|
}
|
|
|
|
template <typename F>
|
|
bool getServers(std::vector<Server>& servers, F onWait) {
|
|
if (!getServersAsyncStart())
|
|
return false;
|
|
|
|
waitVBlanks(LINK_WIRELESS_BROADCAST_SEARCH_WAIT_FRAMES, onWait);
|
|
|
|
if (!getServersAsyncEnd(servers))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getServersAsyncStart() {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != AUTHENTICATED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
bool success =
|
|
sendCommand(LINK_WIRELESS_COMMAND_BROADCAST_READ_START).success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
state = SEARCHING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getServersAsyncEnd(std::vector<Server>& servers) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != SEARCHING) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
auto result = sendCommand(LINK_WIRELESS_COMMAND_BROADCAST_READ_POLL);
|
|
bool success1 =
|
|
result.success &&
|
|
result.responsesSize % LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH == 0;
|
|
|
|
if (!success1) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
bool success2 =
|
|
sendCommand(LINK_WIRELESS_COMMAND_BROADCAST_READ_END).success;
|
|
|
|
if (!success2) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
u32 totalBroadcasts =
|
|
result.responsesSize / LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH;
|
|
|
|
for (u32 i = 0; i < totalBroadcasts; i++) {
|
|
u32 start = LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH * i;
|
|
|
|
Server server;
|
|
server.id = (u16)result.responses[start];
|
|
recoverName(server.gameName, result.responses[start + 1], false);
|
|
recoverName(server.gameName, result.responses[start + 2]);
|
|
recoverName(server.gameName, result.responses[start + 3]);
|
|
recoverName(server.gameName, result.responses[start + 4]);
|
|
recoverName(server.userName, result.responses[start + 5]);
|
|
recoverName(server.userName, result.responses[start + 6]);
|
|
|
|
servers.push_back(server);
|
|
}
|
|
|
|
state = AUTHENTICATED;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool connect(u16 serverId) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != AUTHENTICATED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
addData(serverId, true);
|
|
bool success = sendCommand(LINK_WIRELESS_COMMAND_CONNECT, true).success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
state = CONNECTING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool keepConnecting() {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != CONNECTING) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
auto result1 = sendCommand(LINK_WIRELESS_COMMAND_IS_FINISHED_CONNECT);
|
|
if (!result1.success || result1.responsesSize == 0) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
if (result1.responses[0] == LINK_WIRELESS_STILL_CONNECTING)
|
|
return true;
|
|
|
|
u8 assignedPlayerId = 1 + (u8)msB32(result1.responses[0]);
|
|
u16 assignedClientId = (u16)result1.responses[0];
|
|
|
|
if (assignedPlayerId >= LINK_WIRELESS_MAX_PLAYERS) {
|
|
reset();
|
|
lastError = WEIRD_PLAYER_ID;
|
|
return false;
|
|
}
|
|
|
|
auto result2 = sendCommand(LINK_WIRELESS_COMMAND_FINISH_CONNECTION);
|
|
if (!result2.success || result2.responsesSize == 0 ||
|
|
(u16)result2.responses[0] != assignedClientId) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
sessionState.currentPlayerId = assignedPlayerId;
|
|
state = CONNECTED;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool send(std::vector<u32> data) { return send(&data[0], data.size()); }
|
|
|
|
bool receive(std::vector<Message>& messages) {
|
|
if (!isEnabled || state == NEEDS_RESET || !isSessionActive())
|
|
return false;
|
|
|
|
LINK_WIRELESS_BARRIER;
|
|
isReadingMessages = true;
|
|
LINK_WIRELESS_BARRIER;
|
|
|
|
while (!sessionState.incomingMessages.isEmpty()) {
|
|
auto message = sessionState.incomingMessages.pop();
|
|
messages.push_back(message);
|
|
forwardMessageIfNeeded(message);
|
|
}
|
|
|
|
LINK_WIRELESS_BARRIER;
|
|
isReadingMessages = false;
|
|
LINK_WIRELESS_BARRIER;
|
|
|
|
return true;
|
|
}
|
|
|
|
State getState() { return state; }
|
|
bool isConnected() { return sessionState.playerCount > 1; }
|
|
bool isSessionActive() { return state == SERVING || state == CONNECTED; }
|
|
u8 playerCount() { return sessionState.playerCount; }
|
|
u8 currentPlayerId() { return sessionState.currentPlayerId; }
|
|
bool canSend() { return !sessionState.outgoingMessages.isFull(); }
|
|
u32 getPendingCount() { return sessionState.outgoingMessages.size(); }
|
|
Error getLastError() {
|
|
Error error = lastError;
|
|
lastError = NONE;
|
|
return error;
|
|
}
|
|
|
|
u32 _lastPacketId() { return sessionState.lastPacketId; }
|
|
u32 _lastConfirmationFromClient1() {
|
|
return sessionState.lastConfirmationFromClients[1];
|
|
}
|
|
u32 _lastPacketIdFromClient1() {
|
|
return sessionState.lastPacketIdFromClients[1];
|
|
}
|
|
u32 _lastConfirmationFromServer() {
|
|
return sessionState.lastConfirmationFromServer;
|
|
}
|
|
u32 _lastPacketIdFromServer() { return sessionState.lastPacketIdFromServer; }
|
|
u32 _nextPendingPacketId() {
|
|
return sessionState.outgoingMessages.isEmpty()
|
|
? 0
|
|
: sessionState.outgoingMessages.peek()._packetId;
|
|
}
|
|
|
|
~LinkWireless() {
|
|
delete linkSPI;
|
|
delete linkGPIO;
|
|
}
|
|
|
|
void _onVBlank() {
|
|
if (!isEnabled)
|
|
return;
|
|
|
|
if (!isSessionActive()) {
|
|
copyState();
|
|
return;
|
|
}
|
|
|
|
if (isConnected() && sessionState.frameRecvCount == 0)
|
|
sessionState.recvTimeout++;
|
|
|
|
sessionState.frameRecvCount = 0;
|
|
sessionState.acceptCalled = false;
|
|
|
|
copyState();
|
|
}
|
|
|
|
void _onSerial() {
|
|
if (!isEnabled)
|
|
return;
|
|
|
|
linkSPI->_onSerial(true);
|
|
copyOutgoingState();
|
|
|
|
bool hasNewData = linkSPI->getAsyncState() == LinkSPI::AsyncState::READY;
|
|
if (hasNewData)
|
|
if (!acknowledge()) {
|
|
reset();
|
|
lastError = ACKNOWLEDGE_FAILED;
|
|
copyState();
|
|
return;
|
|
}
|
|
u32 newData = linkSPI->getAsyncData();
|
|
|
|
if (!isSessionActive()) {
|
|
copyState();
|
|
return;
|
|
}
|
|
|
|
if (asyncCommand.isActive) {
|
|
if (asyncCommand.state == AsyncCommand::State::PENDING) {
|
|
if (hasNewData)
|
|
updateAsyncCommand(newData);
|
|
else
|
|
asyncCommand.state = AsyncCommand::State::COMPLETED;
|
|
|
|
if (asyncCommand.state == AsyncCommand::State::COMPLETED)
|
|
processAsyncCommand();
|
|
}
|
|
}
|
|
|
|
copyState();
|
|
}
|
|
|
|
void _onTimer() {
|
|
if (!isEnabled)
|
|
return;
|
|
|
|
if (!isSessionActive()) {
|
|
copyState();
|
|
return;
|
|
}
|
|
|
|
if (sessionState.recvTimeout >= config.timeout) {
|
|
reset();
|
|
lastError = TIMEOUT;
|
|
copyState();
|
|
return;
|
|
}
|
|
|
|
if (!asyncCommand.isActive)
|
|
acceptConnectionsOrSendData();
|
|
|
|
copyState();
|
|
}
|
|
|
|
private:
|
|
struct Config {
|
|
bool forwarding;
|
|
bool retransmission;
|
|
u8 maxPlayers;
|
|
u32 timeout;
|
|
u32 remoteTimeout;
|
|
u32 interval;
|
|
u32 sendTimerId;
|
|
};
|
|
|
|
class MessageQueue {
|
|
public:
|
|
void push(Message item) {
|
|
if (isFull())
|
|
return;
|
|
|
|
rear = (rear + 1) % LINK_WIRELESS_QUEUE_SIZE;
|
|
arr[rear] = item;
|
|
count++;
|
|
}
|
|
|
|
Message pop() {
|
|
if (isEmpty())
|
|
return Message{};
|
|
|
|
auto x = arr[front];
|
|
front = (front + 1) % LINK_WIRELESS_QUEUE_SIZE;
|
|
count--;
|
|
|
|
return x;
|
|
}
|
|
|
|
Message peek() {
|
|
if (isEmpty())
|
|
return Message{};
|
|
return arr[front];
|
|
}
|
|
|
|
template <typename F>
|
|
void forEach(F action) {
|
|
int currentFront = front;
|
|
|
|
for (u32 i = 0; i < count; i++) {
|
|
if (!action(arr[front]))
|
|
return;
|
|
currentFront = (currentFront + 1) % LINK_WIRELESS_QUEUE_SIZE;
|
|
}
|
|
}
|
|
|
|
void clear() {
|
|
while (!isEmpty())
|
|
pop();
|
|
}
|
|
|
|
int size() { return count; }
|
|
bool isEmpty() { return size() == 0; }
|
|
bool isFull() { return size() == LINK_WIRELESS_QUEUE_SIZE; }
|
|
|
|
private:
|
|
Message arr[LINK_WIRELESS_QUEUE_SIZE];
|
|
vs32 front = 0;
|
|
vs32 rear = -1;
|
|
vu32 count = 0;
|
|
};
|
|
|
|
struct SessionState {
|
|
MessageQueue incomingMessages; // read by user, write by irq&user
|
|
MessageQueue outgoingMessages; // read and write by irq
|
|
MessageQueue tmpMessagesToReceive; // read and write by irq
|
|
MessageQueue tmpMessagesToSend; // read by irq, write by user&irq
|
|
u32 timeouts[LINK_WIRELESS_MAX_PLAYERS];
|
|
u32 recvTimeout = 0;
|
|
u32 frameRecvCount = 0;
|
|
bool acceptCalled = false;
|
|
|
|
u8 playerCount = 1;
|
|
u8 currentPlayerId = 0;
|
|
|
|
u32 lastPacketId = 0;
|
|
u32 lastPacketIdFromServer = 0;
|
|
u32 lastConfirmationFromServer = 0;
|
|
u32 lastPacketIdFromClients[LINK_WIRELESS_MAX_PLAYERS];
|
|
u32 lastConfirmationFromClients[LINK_WIRELESS_MAX_PLAYERS];
|
|
};
|
|
|
|
struct MessageHeader {
|
|
unsigned int packetId : 22;
|
|
unsigned int size : 5;
|
|
unsigned int playerId : 3;
|
|
unsigned int clientCount : 2;
|
|
};
|
|
|
|
union MessageHeaderSerializer {
|
|
MessageHeader asStruct;
|
|
u32 asInt;
|
|
};
|
|
|
|
struct LoginMemory {
|
|
u16 previousGBAData = 0xffff;
|
|
u16 previousAdapterData = 0xffff;
|
|
};
|
|
|
|
struct CommandResult {
|
|
bool success = false;
|
|
u32 responses[LINK_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH];
|
|
u32 responsesSize = 0;
|
|
};
|
|
|
|
struct AsyncCommand {
|
|
enum State { PENDING, COMPLETED };
|
|
|
|
enum Step {
|
|
COMMAND_HEADER,
|
|
COMMAND_PARAMETERS,
|
|
RESPONSE_REQUEST,
|
|
DATA_REQUEST
|
|
};
|
|
|
|
u8 type;
|
|
u32 parameters[LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH];
|
|
u32 responses[LINK_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH];
|
|
CommandResult result;
|
|
State state;
|
|
Step step;
|
|
u32 sentParameters, totalParameters;
|
|
u32 receivedResponses, totalResponses;
|
|
bool isActive;
|
|
};
|
|
|
|
SessionState sessionState;
|
|
AsyncCommand asyncCommand;
|
|
Config config;
|
|
LinkSPI* linkSPI = new LinkSPI();
|
|
LinkGPIO* linkGPIO = new LinkGPIO();
|
|
State state = NEEDS_RESET;
|
|
u32 data[LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH];
|
|
u32 dataSize = 0;
|
|
volatile bool isReadingMessages = false;
|
|
volatile bool isAddingMessage = false;
|
|
volatile bool isPendingClearActive = false;
|
|
Error lastError = NONE;
|
|
bool isEnabled = false;
|
|
|
|
bool send(u32* data, u32 dataSize, int _author = -1) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (!isSessionActive()) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
u32 maxTransferLength = state == SERVING
|
|
? LINK_WIRELESS_USER_MAX_SERVER_TRANSFER_LENGTHS
|
|
[config.retransmission]
|
|
: LINK_WIRELESS_USER_MAX_CLIENT_TRANSFER_LENGTHS
|
|
[config.retransmission];
|
|
if (dataSize == 0 || dataSize > maxTransferLength) {
|
|
lastError = INVALID_SEND_SIZE;
|
|
return false;
|
|
}
|
|
|
|
if (!canSend()) {
|
|
if (_author < 0)
|
|
lastError = BUFFER_IS_FULL;
|
|
return false;
|
|
}
|
|
|
|
Message message;
|
|
message.playerId = _author > 0 ? _author : sessionState.currentPlayerId;
|
|
for (u32 i = 0; i < dataSize; i++)
|
|
message.data[i] = data[i];
|
|
message.dataSize = dataSize;
|
|
|
|
LINK_WIRELESS_BARRIER;
|
|
isAddingMessage = true;
|
|
LINK_WIRELESS_BARRIER;
|
|
|
|
sessionState.tmpMessagesToSend.push(message);
|
|
|
|
LINK_WIRELESS_BARRIER;
|
|
isAddingMessage = false;
|
|
LINK_WIRELESS_BARRIER;
|
|
|
|
return true;
|
|
}
|
|
|
|
void forwardMessageIfNeeded(Message& message) {
|
|
if (state == SERVING && config.forwarding && sessionState.playerCount > 2)
|
|
send(&message.data[0], message.dataSize, message.playerId);
|
|
}
|
|
|
|
void processAsyncCommand() { // (irq only)
|
|
if (!asyncCommand.result.success) {
|
|
if (asyncCommand.type == LINK_WIRELESS_COMMAND_SEND_DATA)
|
|
lastError = SEND_DATA_FAILED;
|
|
else if (asyncCommand.type == LINK_WIRELESS_COMMAND_RECEIVE_DATA)
|
|
lastError = RECEIVE_DATA_FAILED;
|
|
else
|
|
lastError = COMMAND_FAILED;
|
|
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
asyncCommand.isActive = false;
|
|
|
|
switch (asyncCommand.type) {
|
|
case LINK_WIRELESS_COMMAND_ACCEPT_CONNECTIONS: {
|
|
// Accept connections (end)
|
|
sessionState.playerCount = 1 + asyncCommand.result.responsesSize;
|
|
|
|
break;
|
|
}
|
|
case LINK_WIRELESS_COMMAND_SEND_DATA: {
|
|
// Send data (end)
|
|
// Receive data (start)
|
|
sendCommandAsync(LINK_WIRELESS_COMMAND_RECEIVE_DATA);
|
|
|
|
break;
|
|
}
|
|
case LINK_WIRELESS_COMMAND_RECEIVE_DATA: {
|
|
// Receive data (end)
|
|
if (asyncCommand.result.responsesSize == 0)
|
|
break;
|
|
|
|
sessionState.frameRecvCount++;
|
|
sessionState.recvTimeout = 0;
|
|
|
|
trackRemoteTimeouts();
|
|
|
|
if (!addIncomingMessagesFromData(asyncCommand.result))
|
|
return;
|
|
|
|
if (!checkRemoteTimeouts()) {
|
|
reset();
|
|
lastError = REMOTE_TIMEOUT;
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default: {
|
|
}
|
|
}
|
|
}
|
|
|
|
void acceptConnectionsOrSendData() { // (irq only)
|
|
if (state == SERVING && !sessionState.acceptCalled &&
|
|
sessionState.playerCount < config.maxPlayers) {
|
|
// Accept connections (start)
|
|
sendCommandAsync(LINK_WIRELESS_COMMAND_ACCEPT_CONNECTIONS);
|
|
sessionState.acceptCalled = true;
|
|
} else if (state == CONNECTED || isConnected()) {
|
|
// Send data (start)
|
|
sendPendingData();
|
|
}
|
|
}
|
|
|
|
void sendPendingData() { // (irq only)
|
|
setDataFromOutgoingMessages();
|
|
sendCommandAsync(LINK_WIRELESS_COMMAND_SEND_DATA, true);
|
|
clearOutgoingMessagesIfNeeded();
|
|
}
|
|
|
|
void setDataFromOutgoingMessages() { // (irq only)
|
|
u32 maxTransferLength = getDeviceTransferLength();
|
|
|
|
addData(0, true);
|
|
|
|
if (config.retransmission)
|
|
addConfirmations();
|
|
else
|
|
addPingMessageIfNeeded();
|
|
|
|
sessionState.outgoingMessages.forEach(
|
|
[this, maxTransferLength](Message message) {
|
|
u8 size = message.dataSize;
|
|
u32 header =
|
|
buildMessageHeader(message.playerId, size, message._packetId);
|
|
|
|
if (dataSize /* -1 (wireless header) + 1 (msg header) */ + size >
|
|
maxTransferLength)
|
|
return false;
|
|
|
|
addData(header);
|
|
for (u32 i = 0; i < size; i++)
|
|
addData(message.data[i]);
|
|
|
|
return true;
|
|
});
|
|
|
|
// (add wireless header)
|
|
u32 bytes = (dataSize - 1) * 4;
|
|
data[0] = sessionState.currentPlayerId == 0
|
|
? bytes
|
|
: (1 << (3 + sessionState.currentPlayerId * 5)) * bytes;
|
|
}
|
|
|
|
bool addIncomingMessagesFromData(CommandResult& result) { // (irq only)
|
|
for (u32 i = 1; i < result.responsesSize; i++) {
|
|
MessageHeaderSerializer serializer;
|
|
serializer.asInt = result.responses[i];
|
|
|
|
MessageHeader header = serializer.asStruct;
|
|
u8 remotePlayerCount = LINK_WIRELESS_MIN_PLAYERS + header.clientCount;
|
|
u8 remotePlayerId = header.playerId;
|
|
u8 size = header.size;
|
|
u32 packetId = header.packetId;
|
|
|
|
if (i + size >= result.responsesSize) {
|
|
reset();
|
|
lastError = BAD_MESSAGE;
|
|
return false;
|
|
}
|
|
|
|
sessionState.timeouts[0] = 0;
|
|
sessionState.timeouts[remotePlayerId] = 0;
|
|
|
|
if (state == SERVING) {
|
|
if (config.retransmission &&
|
|
packetId != LINK_WIRELESS_MSG_CONFIRMATION &&
|
|
sessionState.lastPacketIdFromClients[remotePlayerId] > 0 &&
|
|
packetId !=
|
|
sessionState.lastPacketIdFromClients[remotePlayerId] + 1)
|
|
goto skip;
|
|
|
|
if (packetId != LINK_WIRELESS_MSG_CONFIRMATION)
|
|
sessionState.lastPacketIdFromClients[remotePlayerId] = packetId;
|
|
} else {
|
|
if (config.retransmission &&
|
|
packetId != LINK_WIRELESS_MSG_CONFIRMATION &&
|
|
sessionState.lastPacketIdFromServer > 0 &&
|
|
packetId != sessionState.lastPacketIdFromServer + 1)
|
|
goto skip;
|
|
|
|
sessionState.playerCount = remotePlayerCount;
|
|
|
|
if (packetId != LINK_WIRELESS_MSG_CONFIRMATION)
|
|
sessionState.lastPacketIdFromServer = packetId;
|
|
}
|
|
|
|
if (remotePlayerId == sessionState.currentPlayerId) {
|
|
skip:
|
|
i += size;
|
|
continue;
|
|
}
|
|
|
|
if (size > 0) {
|
|
Message message;
|
|
message._packetId = packetId;
|
|
for (u32 j = 0; j < size; j++)
|
|
message.data[j] = result.responses[i + 1 + j];
|
|
message.dataSize = size;
|
|
message.playerId = remotePlayerId;
|
|
|
|
if (config.retransmission &&
|
|
packetId == LINK_WIRELESS_MSG_CONFIRMATION) {
|
|
if (!handleConfirmation(message)) {
|
|
reset();
|
|
lastError = BAD_CONFIRMATION;
|
|
return false;
|
|
}
|
|
} else
|
|
sessionState.tmpMessagesToReceive.push(message);
|
|
|
|
i += size;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void clearOutgoingMessagesIfNeeded() { // (irq only)
|
|
if (!config.retransmission)
|
|
sessionState.outgoingMessages.clear();
|
|
}
|
|
|
|
void addPingMessageIfNeeded() { // (irq only)
|
|
if (sessionState.outgoingMessages.isEmpty()) {
|
|
Message emptyMessage;
|
|
emptyMessage.playerId = sessionState.currentPlayerId;
|
|
emptyMessage._packetId = ++sessionState.lastPacketId;
|
|
sessionState.outgoingMessages.push(emptyMessage);
|
|
}
|
|
}
|
|
|
|
void addConfirmations() { // (irq only)
|
|
if (state == SERVING) {
|
|
addData(buildConfirmationHeader(0));
|
|
for (u32 i = 0; i < (u32)(config.maxPlayers - 1); i++)
|
|
addData(sessionState.lastPacketIdFromClients[1 + i]);
|
|
} else {
|
|
addData(buildConfirmationHeader(sessionState.currentPlayerId));
|
|
addData(sessionState.lastPacketIdFromServer);
|
|
}
|
|
}
|
|
|
|
bool handleConfirmation(Message confirmation) { // (irq only)
|
|
if (confirmation.dataSize == 0)
|
|
return false;
|
|
|
|
bool isServerConfirmation = confirmation.playerId == 0;
|
|
|
|
if (isServerConfirmation) {
|
|
if (state != CONNECTED ||
|
|
confirmation.dataSize != (u32)(config.maxPlayers - 1))
|
|
return false;
|
|
|
|
sessionState.lastConfirmationFromServer =
|
|
confirmation.data[sessionState.currentPlayerId - 1];
|
|
removeConfirmedMessages(sessionState.lastConfirmationFromServer);
|
|
} else {
|
|
if (state != SERVING || confirmation.dataSize != 1)
|
|
return false;
|
|
|
|
u32 confirmationData = confirmation.data[0];
|
|
sessionState.lastConfirmationFromClients[confirmation.playerId] =
|
|
confirmationData;
|
|
|
|
u32 min = 0xffffffff;
|
|
for (u32 i = 0; i < (u32)(config.maxPlayers - 1); i++) {
|
|
u32 confirmationData = sessionState.lastConfirmationFromClients[1 + i];
|
|
if (confirmationData > 0 && confirmationData < min)
|
|
min = confirmationData;
|
|
}
|
|
if (min < 0xffffffff)
|
|
removeConfirmedMessages(min);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void removeConfirmedMessages(u32 confirmation) { // (irq only)
|
|
while (!sessionState.outgoingMessages.isEmpty() &&
|
|
sessionState.outgoingMessages.peek()._packetId <= confirmation)
|
|
sessionState.outgoingMessages.pop();
|
|
}
|
|
|
|
u32 buildConfirmationHeader(u8 playerId) { // (irq only)
|
|
return buildMessageHeader(playerId,
|
|
playerId == 0 ? config.maxPlayers - 1 : 1, 0);
|
|
}
|
|
|
|
u32 buildMessageHeader(u8 playerId, u8 size, u32 packetId) { // (irq only)
|
|
MessageHeader header;
|
|
header.clientCount = sessionState.playerCount - LINK_WIRELESS_MIN_PLAYERS;
|
|
header.playerId = playerId;
|
|
header.size = size;
|
|
header.packetId = packetId;
|
|
|
|
MessageHeaderSerializer serializer;
|
|
serializer.asStruct = header;
|
|
return serializer.asInt;
|
|
}
|
|
|
|
void trackRemoteTimeouts() { // (irq only)
|
|
for (u32 i = 0; i < sessionState.playerCount; i++)
|
|
if (i != sessionState.currentPlayerId)
|
|
sessionState.timeouts[i]++;
|
|
}
|
|
|
|
bool checkRemoteTimeouts() { // (irq only)
|
|
for (u32 i = 0; i < sessionState.playerCount; i++) {
|
|
if ((i == 0 || state == SERVING) &&
|
|
sessionState.timeouts[i] > config.remoteTimeout)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
u32 getDeviceTransferLength() { // (irq only)
|
|
return state == SERVING ? LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH
|
|
: LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH;
|
|
}
|
|
|
|
void recoverName(std::string& name,
|
|
u32 word,
|
|
bool includeFirstTwoBytes = true) {
|
|
u32 character = 0;
|
|
if (includeFirstTwoBytes) {
|
|
character = lsB16(lsB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
character = msB16(lsB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
}
|
|
character = lsB16(msB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
character = msB16(msB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
}
|
|
|
|
void addData(u32 value, bool start = false) {
|
|
if (start)
|
|
dataSize = 0;
|
|
data[dataSize] = value;
|
|
dataSize++;
|
|
}
|
|
|
|
bool reset() {
|
|
resetState();
|
|
stop();
|
|
return start();
|
|
}
|
|
|
|
void resetState() {
|
|
this->state = NEEDS_RESET;
|
|
this->sessionState.playerCount = 1;
|
|
this->sessionState.currentPlayerId = 0;
|
|
this->sessionState.recvTimeout = 0;
|
|
this->sessionState.frameRecvCount = 0;
|
|
this->sessionState.acceptCalled = false;
|
|
this->sessionState.lastPacketId = 0;
|
|
this->sessionState.lastPacketIdFromServer = 0;
|
|
this->sessionState.lastConfirmationFromServer = 0;
|
|
for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++) {
|
|
this->sessionState.timeouts[i] = 0;
|
|
this->sessionState.lastPacketIdFromClients[i] = 0;
|
|
this->sessionState.lastConfirmationFromClients[i] = 0;
|
|
}
|
|
this->asyncCommand.isActive = false;
|
|
this->dataSize = 0;
|
|
|
|
if (!isReadingMessages)
|
|
this->sessionState.incomingMessages.clear();
|
|
|
|
isPendingClearActive = true;
|
|
}
|
|
|
|
void stop() {
|
|
stopTimer();
|
|
|
|
linkSPI->deactivate();
|
|
}
|
|
|
|
bool start() {
|
|
startTimer();
|
|
|
|
pingAdapter();
|
|
linkSPI->activate(LinkSPI::Mode::MASTER_256KBPS);
|
|
|
|
if (!login())
|
|
return false;
|
|
|
|
wait(LINK_WIRELESS_TRANSFER_WAIT);
|
|
|
|
if (!sendCommand(LINK_WIRELESS_COMMAND_HELLO).success)
|
|
return false;
|
|
|
|
addData(LINK_WIRELESS_SETUP_MAGIC, true);
|
|
if (!sendCommand(LINK_WIRELESS_COMMAND_SETUP, true).success)
|
|
return false;
|
|
|
|
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
|
|
state = AUTHENTICATED;
|
|
|
|
return true;
|
|
}
|
|
|
|
void stopTimer() {
|
|
REG_TM[config.sendTimerId].cnt =
|
|
REG_TM[config.sendTimerId].cnt & (~TM_ENABLE);
|
|
}
|
|
|
|
void startTimer() {
|
|
REG_TM[config.sendTimerId].start = -config.interval;
|
|
REG_TM[config.sendTimerId].cnt =
|
|
TM_ENABLE | TM_IRQ | LINK_WIRELESS_BASE_FREQUENCY;
|
|
}
|
|
|
|
void copyState() { // (irq only)
|
|
copyOutgoingState();
|
|
copyIncomingState();
|
|
}
|
|
|
|
void copyOutgoingState() { // (irq only)
|
|
if (!isAddingMessage) {
|
|
while (!sessionState.tmpMessagesToSend.isEmpty()) {
|
|
if (isSessionActive() && !canSend())
|
|
break;
|
|
|
|
auto message = sessionState.tmpMessagesToSend.pop();
|
|
|
|
if (isSessionActive()) {
|
|
message._packetId = ++sessionState.lastPacketId;
|
|
sessionState.outgoingMessages.push(message);
|
|
}
|
|
}
|
|
|
|
if (isPendingClearActive) {
|
|
sessionState.outgoingMessages.clear();
|
|
isPendingClearActive = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void copyIncomingState() { // (irq only)
|
|
if (!isReadingMessages) {
|
|
while (!sessionState.tmpMessagesToReceive.isEmpty()) {
|
|
auto message = sessionState.tmpMessagesToReceive.pop();
|
|
|
|
if (state == SERVING || state == CONNECTED)
|
|
sessionState.incomingMessages.push(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
void pingAdapter() {
|
|
linkGPIO->setMode(LinkGPIO::Pin::SO, LinkGPIO::Direction::OUTPUT);
|
|
linkGPIO->setMode(LinkGPIO::Pin::SD, LinkGPIO::Direction::OUTPUT);
|
|
linkGPIO->writePin(LinkGPIO::SD, true);
|
|
wait(LINK_WIRELESS_PING_WAIT);
|
|
linkGPIO->writePin(LinkGPIO::SD, false);
|
|
}
|
|
|
|
bool login() {
|
|
LoginMemory memory;
|
|
|
|
if (!exchangeLoginPacket(LINK_WIRELESS_LOGIN_PARTS[0], 0, memory))
|
|
return false;
|
|
|
|
for (u32 i = 0; i < LINK_WIRELESS_LOGIN_STEPS; i++) {
|
|
if (!exchangeLoginPacket(LINK_WIRELESS_LOGIN_PARTS[i],
|
|
LINK_WIRELESS_LOGIN_PARTS[i], memory))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool exchangeLoginPacket(u16 data,
|
|
u16 expectedResponse,
|
|
LoginMemory& memory) {
|
|
u32 packet = buildU32(~memory.previousAdapterData, data);
|
|
u32 response = transfer(packet, false);
|
|
|
|
if (msB32(response) != expectedResponse ||
|
|
lsB32(response) != (u16)~memory.previousGBAData)
|
|
return false;
|
|
|
|
memory.previousGBAData = data;
|
|
memory.previousAdapterData = expectedResponse;
|
|
|
|
return true;
|
|
}
|
|
|
|
CommandResult sendCommand(u8 type, bool withData = false) {
|
|
CommandResult result;
|
|
u32 command = buildCommand(type, withData ? (u16)dataSize : 0);
|
|
|
|
if (transfer(command) != LINK_WIRELESS_DATA_REQUEST)
|
|
return result;
|
|
|
|
if (withData) {
|
|
for (u32 i = 0; i < dataSize; i++) {
|
|
if (transfer(data[i]) != LINK_WIRELESS_DATA_REQUEST)
|
|
return result;
|
|
}
|
|
}
|
|
|
|
u32 response = transfer(LINK_WIRELESS_DATA_REQUEST);
|
|
u16 header = msB32(response);
|
|
u16 data = lsB32(response);
|
|
u8 responses = msB16(data);
|
|
u8 ack = lsB16(data);
|
|
|
|
if (header != LINK_WIRELESS_COMMAND_HEADER ||
|
|
ack != type + LINK_WIRELESS_RESPONSE_ACK ||
|
|
responses > LINK_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH)
|
|
return result;
|
|
|
|
for (u32 i = 0; i < responses; i++)
|
|
result.responses[i] = transfer(LINK_WIRELESS_DATA_REQUEST);
|
|
result.responsesSize = responses;
|
|
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
void sendCommandAsync(u8 type, bool withData = false) { // (irq only)
|
|
if (asyncCommand.isActive)
|
|
return;
|
|
|
|
asyncCommand.type = type;
|
|
if (withData) {
|
|
for (u32 i = 0; i < dataSize; i++)
|
|
asyncCommand.parameters[i] = data[i];
|
|
}
|
|
asyncCommand.result.success = false;
|
|
asyncCommand.state = AsyncCommand::State::PENDING;
|
|
asyncCommand.step = AsyncCommand::Step::COMMAND_HEADER;
|
|
asyncCommand.sentParameters = 0;
|
|
asyncCommand.totalParameters = withData ? dataSize : 0;
|
|
asyncCommand.receivedResponses = 0;
|
|
asyncCommand.totalResponses = 0;
|
|
asyncCommand.isActive = true;
|
|
|
|
u32 command = buildCommand(type, asyncCommand.totalParameters);
|
|
transferAsync(command);
|
|
}
|
|
|
|
void updateAsyncCommand(u32 newData) { // (irq only)
|
|
switch (asyncCommand.step) {
|
|
case AsyncCommand::Step::COMMAND_HEADER: {
|
|
if (newData != LINK_WIRELESS_DATA_REQUEST) {
|
|
asyncCommand.state = AsyncCommand::State::COMPLETED;
|
|
return;
|
|
}
|
|
|
|
sendAsyncCommandParameterOrRequestResponse();
|
|
break;
|
|
}
|
|
case AsyncCommand::Step::COMMAND_PARAMETERS: {
|
|
if (newData != LINK_WIRELESS_DATA_REQUEST) {
|
|
asyncCommand.state = AsyncCommand::State::COMPLETED;
|
|
return;
|
|
}
|
|
|
|
sendAsyncCommandParameterOrRequestResponse();
|
|
break;
|
|
}
|
|
case AsyncCommand::Step::RESPONSE_REQUEST: {
|
|
u16 header = msB32(newData);
|
|
u16 data = lsB32(newData);
|
|
u8 responses = msB16(data);
|
|
u8 ack = lsB16(data);
|
|
|
|
if (header != LINK_WIRELESS_COMMAND_HEADER ||
|
|
ack != asyncCommand.type + LINK_WIRELESS_RESPONSE_ACK ||
|
|
responses > LINK_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH) {
|
|
asyncCommand.state = AsyncCommand::State::COMPLETED;
|
|
return;
|
|
}
|
|
|
|
asyncCommand.totalResponses = responses;
|
|
asyncCommand.result.responsesSize = responses;
|
|
|
|
receiveAsyncCommandResponseOrFinish();
|
|
break;
|
|
}
|
|
case AsyncCommand::Step::DATA_REQUEST: {
|
|
asyncCommand.result.responses[asyncCommand.receivedResponses] = newData;
|
|
asyncCommand.receivedResponses++;
|
|
|
|
receiveAsyncCommandResponseOrFinish();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void sendAsyncCommandParameterOrRequestResponse() { // (irq only)
|
|
if (asyncCommand.sentParameters < asyncCommand.totalParameters) {
|
|
asyncCommand.step = AsyncCommand::Step::COMMAND_PARAMETERS;
|
|
transferAsync(asyncCommand.parameters[asyncCommand.sentParameters]);
|
|
asyncCommand.sentParameters++;
|
|
} else {
|
|
asyncCommand.step = AsyncCommand::Step::RESPONSE_REQUEST;
|
|
transferAsync(LINK_WIRELESS_DATA_REQUEST);
|
|
}
|
|
}
|
|
|
|
void receiveAsyncCommandResponseOrFinish() { // (irq only)
|
|
if (asyncCommand.receivedResponses < asyncCommand.totalResponses) {
|
|
asyncCommand.step = AsyncCommand::Step::DATA_REQUEST;
|
|
transferAsync(LINK_WIRELESS_DATA_REQUEST);
|
|
} else {
|
|
asyncCommand.result.success = true;
|
|
asyncCommand.state = AsyncCommand::State::COMPLETED;
|
|
}
|
|
}
|
|
|
|
u32 buildCommand(u8 type, u8 length = 0) {
|
|
return buildU32(LINK_WIRELESS_COMMAND_HEADER, buildU16(length, type));
|
|
}
|
|
|
|
void transferAsync(u32 data) {
|
|
linkSPI->transfer(
|
|
data, []() { return false; }, true, true);
|
|
}
|
|
|
|
u32 transfer(u32 data, bool customAck = true) {
|
|
if (!customAck)
|
|
wait(LINK_WIRELESS_TRANSFER_WAIT);
|
|
|
|
u32 lines = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
u32 receivedData = linkSPI->transfer(
|
|
data, [this, &lines, &vCount]() { return cmdTimeout(lines, vCount); },
|
|
false, customAck);
|
|
|
|
if (customAck && !acknowledge())
|
|
return LINK_SPI_NO_DATA;
|
|
|
|
return receivedData;
|
|
}
|
|
|
|
bool acknowledge() {
|
|
u32 lines = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
|
|
linkSPI->_setSOLow();
|
|
while (!linkSPI->_isSIHigh())
|
|
if (cmdTimeout(lines, vCount))
|
|
return false;
|
|
linkSPI->_setSOHigh();
|
|
while (linkSPI->_isSIHigh())
|
|
if (cmdTimeout(lines, vCount))
|
|
return false;
|
|
linkSPI->_setSOLow();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool cmdTimeout(u32& lines, u32& vCount) {
|
|
return timeout(LINK_WIRELESS_CMD_TIMEOUT, lines, vCount);
|
|
}
|
|
|
|
bool timeout(u32 limit, u32& lines, u32& vCount) {
|
|
if (REG_VCOUNT != vCount) {
|
|
lines += std::max((int)REG_VCOUNT - (int)vCount, 0);
|
|
vCount = REG_VCOUNT;
|
|
}
|
|
|
|
return lines > limit;
|
|
}
|
|
|
|
void wait(u32 verticalLines) {
|
|
u32 count = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
|
|
while (count < verticalLines) {
|
|
if (REG_VCOUNT != vCount) {
|
|
count += std::max((int)REG_VCOUNT - (int)vCount, 0);
|
|
vCount = REG_VCOUNT;
|
|
}
|
|
};
|
|
}
|
|
|
|
template <typename F>
|
|
void waitVBlanks(u32 vBlanks, F onVBlank) {
|
|
u32 count = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
|
|
while (count < vBlanks) {
|
|
if (REG_VCOUNT != vCount) {
|
|
vCount = REG_VCOUNT;
|
|
|
|
if (vCount == 160) {
|
|
onVBlank();
|
|
count++;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
u32 buildU32(u16 msB, u16 lsB) { return (msB << 16) | lsB; }
|
|
u16 buildU16(u8 msB, u8 lsB) { return (msB << 8) | lsB; }
|
|
u16 msB32(u32 value) { return value >> 16; }
|
|
u16 lsB32(u32 value) { return value & 0xffff; }
|
|
u8 msB16(u16 value) { return value >> 8; }
|
|
u8 lsB16(u16 value) { return value & 0xff; }
|
|
};
|
|
|
|
extern LinkWireless* linkWireless;
|
|
|
|
inline void LINK_WIRELESS_ISR_VBLANK() {
|
|
linkWireless->_onVBlank();
|
|
}
|
|
|
|
inline void LINK_WIRELESS_ISR_SERIAL() {
|
|
linkWireless->_onSerial();
|
|
}
|
|
|
|
inline void LINK_WIRELESS_ISR_TIMER() {
|
|
linkWireless->_onTimer();
|
|
}
|
|
|
|
#endif // LINK_WIRELESS_H
|