spicetools/acioemu/acioemu.cpp

210 lines
5.5 KiB
C++

#include "acioemu.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace acioemu;
ACIOEmu::ACIOEmu() {
this->devices = new std::vector<ACIODeviceEmu *>();
this->response_buffer = new circular_buffer<uint8_t>(4096);
this->read_buffer = new circular_buffer<uint8_t>(1024);
}
ACIOEmu::~ACIOEmu() {
// delete devices
for (auto device : *this->devices) {
delete device;
}
delete this->devices;
// delete buffers
delete this->response_buffer;
delete this->read_buffer;
}
void ACIOEmu::add_device(ACIODeviceEmu *device) {
this->devices->push_back(device);
}
void ACIOEmu::write(uint8_t byte) {
// insert into buffer
if (!invert) {
if (byte == ACIO_ESCAPE) {
invert = true;
} else {
this->read_buffer->put(byte);
}
} else {
byte = ~byte;
invert = false;
this->read_buffer->put(byte);
}
// clean garbage
while (!this->read_buffer->empty() && this->read_buffer->peek() != 0xAA) {
this->read_buffer->get();
}
while (this->read_buffer->size() > 1 && this->read_buffer->peek(1) == 0xAA) {
this->read_buffer->get();
}
// handshake counter
static unsigned int handshake_counter = 0;
if (byte == 0xAA) {
handshake_counter++;
} else {
handshake_counter = 0;
}
// check for handshake
if (handshake_counter > 1) {
/*
* small hack - BIO2 seems to expect more bytes here - sending two bytes each time fixes it
* TODO replace this handshake code with something better
*/
this->response_buffer->put(ACIO_SOF);
this->response_buffer->put(ACIO_SOF);
handshake_counter--;
return;
}
// parse
if (!this->read_buffer->empty() && this->read_buffer->size() >= 6) {
bool is_complete = false;
// check if broadcast
if (this->read_buffer->peek(1) == ACIO_BROADCAST) {
// check msg data size
auto data_size = this->read_buffer->peek(2);
// check if msg is complete (SOF + checksum + broadcast header + data_size)
is_complete = this->read_buffer->size() >= 2u + 2u + data_size;
} else {
// check msg data size
auto data_size = this->read_buffer->peek(5);
// check if msg is complete (SOF + checksum + command header + data_size)
is_complete = this->read_buffer->size() >= 2u + MSG_HEADER_SIZE + data_size;
}
// parse message if complete
if (is_complete) {
this->msg_parse();
this->read_buffer->reset();
}
}
}
std::optional<uint8_t> ACIOEmu::read() {
if (this->response_buffer->empty()) {
return std::nullopt;
}
return this->response_buffer->get();
}
size_t ACIOEmu::bytes_available() {
return this->response_buffer->size();
}
void ACIOEmu::msg_parse() {
#ifdef ACIOEMU_LOG
log_info("acioemu", "MSG RECV: {}", bin2hex(*this->read_buffer));
#endif
// calculate checksum
uint8_t chk = 0;
size_t max = this->read_buffer->size() - 1;
for (size_t i = 1; i < max; i++) {
chk += this->read_buffer->peek(i);
}
// check checksum
uint8_t chk_receive = this->read_buffer->peek(this->read_buffer->size() - 1);
if (chk != chk_receive) {
#ifdef ACIOEMU_LOG
log_info("acioemu", "detected wrong checksum: {}/{}", chk, chk_receive);
#endif
return;
}
// get message data
auto msg_data = this->read_buffer->peek_all();
auto msg_in = (MessageData *) &msg_data[1];
// correct cmd code endianness if this is not a broadcast
if (msg_in->addr != ACIO_BROADCAST) {
msg_in->cmd.code = acio_u16(msg_in->cmd.code);
}
// pass to applicable device
uint8_t node_offset = 0;
for (auto device : *this->devices) {
if (device->is_applicable(node_offset, msg_in->addr)) {
auto cur_offset = msg_in->addr - node_offset - 1;
if (cur_offset < 0) {
break;
}
if (device->parse_msg(msg_in, this->response_buffer)) {
return;
} else {
break;
}
}
node_offset += device->node_count;
}
// ignore broadcast messages by default
if (msg_in->addr == ACIO_BROADCAST) {
return;
}
/*
* Default Behavior
* If you want to do anything different, just handle the
* commands in your own device implementation.
*/
switch (msg_in->cmd.code) {
// node count report
case ACIO_CMD_ASSIGN_ADDRS: {
if (msg_in->addr == 0x00 && node_offset > 0) {
auto msg = ACIODeviceEmu::create_msg(msg_in, 1, &node_offset);
ACIODeviceEmu::write_msg(msg, this->response_buffer);
delete msg;
return;
}
break;
}
// status 0 defaults
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x80: // KEEPALIVE
case 0xFF: // BROADCAST
{
// send status 0
auto msg = ACIODeviceEmu::create_msg_status(msg_in, 0);
ACIODeviceEmu::write_msg(msg, response_buffer);
delete msg;
return;
}
default:
break;
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "UNHANDLED MSG FOR ADDR: {}, CMD: 0x{:x}), DATA: {}",
msg_in->addr,
msg_in->cmd.code,
bin2hex(*this->read_buffer));
#endif
}