1337 lines
41 KiB
C++
1337 lines
41 KiB
C++
#include <inttypes.h>
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
|
|
#include <ftdi.h>
|
|
#include <libusb.h>
|
|
|
|
#include "crc32.h"
|
|
#include "serial_protocol.hpp"
|
|
|
|
#include "maple/maple_bus_bits.hpp"
|
|
#include "maple/maple.hpp"
|
|
#include "maple/maple_host_command_writer.hpp"
|
|
#include "maple/maple_bus_commands.hpp"
|
|
#include "maple/maple_port.hpp"
|
|
#include "maple/maple_bus_ft1.hpp"
|
|
|
|
extern "C" int convert_baudrate_UT_export(int baudrate, struct ftdi_context *ftdi,
|
|
unsigned short *value, unsigned short *index);
|
|
|
|
constexpr int current_cks = 0;
|
|
static int current_scbrr = -1;
|
|
|
|
double dreamcast_rate(int cks, int scbrr)
|
|
{
|
|
assert(cks >= 0 && cks <= 3);
|
|
assert(scbrr >= 0 && scbrr <= 255);
|
|
|
|
double div = 1.0;
|
|
for (; cks > 0; cks--) { div *= 4; };
|
|
|
|
return 1562500.0 / (div * ((double)scbrr + 1.0));
|
|
}
|
|
|
|
int init_ftdi_context(struct ftdi_context * ftdi, uint32_t scbrr)
|
|
{
|
|
ftdi_set_interface(ftdi, INTERFACE_ANY);
|
|
struct ftdi_device_list * devlist;
|
|
int num_devices;
|
|
num_devices = ftdi_usb_find_all(ftdi, &devlist, 0, 0);
|
|
if (num_devices < 0) {
|
|
fprintf(stderr, "ftdi_usb_find_all: %d\n", num_devices);
|
|
return -1;
|
|
} else if (num_devices == 0) {
|
|
fprintf(stderr, "ftdi_usb_find_all: zero matching devices\n");
|
|
return -1;
|
|
}
|
|
|
|
struct libusb_device_descriptor desc;
|
|
struct ftdi_device_list * devlist_item = devlist;
|
|
struct libusb_device * dev = devlist_item->dev;
|
|
int res;
|
|
for (int i = 0; i < num_devices; i++) {
|
|
res = libusb_get_device_descriptor(devlist_item->dev, &desc);
|
|
if (res < 0) {
|
|
fprintf(stderr, "libusb_get_device_descriptor: %d\n", res);
|
|
return -1;
|
|
}
|
|
fprintf(stderr, "[%d]\n", i);
|
|
fprintf(stderr, " idVendor: %04x; idProduct: %04x;\n", desc.idVendor, desc.idProduct);
|
|
|
|
uint8_t port_numbers[7];
|
|
res = libusb_get_port_numbers(devlist_item->dev,
|
|
port_numbers,
|
|
(sizeof (port_numbers)));
|
|
if (res < 0) {
|
|
fprintf(stderr, "libusb_get_port_numbers: %d\n", res);
|
|
return -1;
|
|
}
|
|
fprintf(stderr, " libusb port number: ");
|
|
for (int i = 0; i < res; i++) {
|
|
if (i != 0) fprintf(stderr, ":");
|
|
fprintf(stderr, "%o", port_numbers[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
devlist_item = devlist_item->next;
|
|
}
|
|
|
|
assert(dev != NULL);
|
|
res = ftdi_usb_open_dev(ftdi, devlist->dev);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_usb_open_dev: %s\n", ftdi_get_error_string(ftdi));
|
|
return -1;
|
|
}
|
|
ftdi_list_free(&devlist);
|
|
|
|
res = ftdi_set_baudrate(ftdi, round(dreamcast_rate(current_cks, scbrr)));
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_set_baudrate: %s\n", ftdi_get_error_string(ftdi));
|
|
return -1;
|
|
}
|
|
current_scbrr = scbrr;
|
|
|
|
res = ftdi_set_line_property2(ftdi, BITS_8, STOP_BIT_1, NONE, BREAK_ON);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_set_line_property2: %s\n", ftdi_get_error_string(ftdi));
|
|
return -1;
|
|
}
|
|
|
|
res = ftdi_set_line_property2(ftdi, BITS_8, STOP_BIT_1, NONE, BREAK_OFF);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_set_line_property2: %s\n", ftdi_get_error_string(ftdi));
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
res = ftdi_set_latency_timer(ftdi, 1);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_set_latency_timer %s\n", ftdi_get_error_string(ftdi));
|
|
return -1;
|
|
}
|
|
*/
|
|
|
|
res = ftdi_tciflush(ftdi);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_tciflush: %s\n", ftdi_get_error_string(ftdi));
|
|
return -1;
|
|
}
|
|
|
|
res = ftdi_tcoflush(ftdi);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_tcoflush: %s\n", ftdi_get_error_string(ftdi));
|
|
return -1;
|
|
}
|
|
|
|
uint8_t discard[1024];
|
|
res = ftdi_read_data(ftdi, discard, (sizeof (discard)));
|
|
assert(res >= 0);
|
|
(void)discard;
|
|
|
|
return 0;
|
|
}
|
|
|
|
union data_command {
|
|
struct {
|
|
uint8_t command[4];
|
|
uint32_t size;
|
|
uint32_t dest;
|
|
};
|
|
uint8_t data[4 * 3];
|
|
};
|
|
static_assert((sizeof (union data_command)) == 12);
|
|
|
|
long read_with_timeout(struct ftdi_context * ftdi, uint8_t * read_buf, const long expect_length)
|
|
{
|
|
int res;
|
|
struct timespec tp0;
|
|
res = clock_gettime(CLOCK_MONOTONIC, &tp0);
|
|
assert(res >= 0);
|
|
|
|
long read_length = 0;
|
|
while (true) {
|
|
res = ftdi_read_data(ftdi, read_buf, expect_length - read_length);
|
|
assert(res >= 0);
|
|
|
|
read_length += res;
|
|
if (read_length >= expect_length)
|
|
break;
|
|
|
|
struct timespec tp1;
|
|
res = clock_gettime(CLOCK_MONOTONIC, &tp1);
|
|
assert(res >= 0);
|
|
|
|
if (tp1.tv_sec - tp0.tv_sec > 1) {
|
|
fprintf(stderr, "read timeout: %ld expect: %ld\n", read_length, expect_length);
|
|
break;
|
|
}
|
|
}
|
|
return read_length;
|
|
}
|
|
|
|
long min(long a, long b)
|
|
{
|
|
return a > b ? b : a;
|
|
}
|
|
|
|
long max(long a, long b)
|
|
{
|
|
return a > b ? a : b;
|
|
}
|
|
|
|
double timespec_difference(struct timespec const * const a, struct timespec const * const b)
|
|
{
|
|
return (double)(a->tv_sec - b->tv_sec) + (double)(a->tv_nsec - b->tv_nsec) / 1'000'000'000.0;
|
|
}
|
|
|
|
void dump_command_reply(union serial_load::command_reply& cr)
|
|
{
|
|
for (uint32_t i = 0; i < (sizeof (union serial_load::command_reply)) / (sizeof (uint32_t)); i++) {
|
|
fprintf(stderr, " %08x\n", serial_load::le_bswap(cr.u32[i]));
|
|
}
|
|
/*
|
|
for (uint32_t i = 0; i < (sizeof (union serial_load::command_reply)); i++) {
|
|
fprintf(stderr, "%02x ", cr.u8[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
*/
|
|
}
|
|
|
|
int read_reply(struct ftdi_context * ftdi, uint32_t expected_cmd, union serial_load::command_reply& reply)
|
|
{
|
|
using namespace serial_load;
|
|
|
|
constexpr long read_length = (sizeof (union serial_load::command_reply));
|
|
|
|
long length = read_with_timeout(ftdi, reply.u8, read_length);
|
|
if (length != read_length) {
|
|
fprintf(stderr, "read_reply: short read; want %ld bytes; received: %ld\n", read_length, length);
|
|
return -1;
|
|
}
|
|
|
|
uint32_t crc = crc32(&reply.u8[0], 12);
|
|
if (crc != reply.crc) {
|
|
fprintf(stderr, "reply crc mismatch; remote crc: %08x; local crc: %08x\n", reply.crc, crc);
|
|
dump_command_reply(reply);
|
|
|
|
/*
|
|
uint8_t buf[16] = {0};
|
|
long length = read_with_timeout(ftdi, reply.u8, 16);
|
|
if (length > 0) {
|
|
fprintf(stderr, "trailing data:\n");
|
|
for (int i = 0; i < length; i++) {
|
|
fprintf(stderr, "%02x ", buf[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
*/
|
|
return -2;
|
|
}
|
|
|
|
if (reply.cmd != expected_cmd) {
|
|
fprintf(stderr, "invalid reply; remote cmd %08x; expected cmd: %08x\n", reply.cmd, expected_cmd);
|
|
dump_command_reply(reply);
|
|
return -1;
|
|
}
|
|
//dump_command_reply(reply);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_write(struct ftdi_context * ftdi, const uint32_t dest, const uint8_t * buf, const uint32_t size)
|
|
{
|
|
int res;
|
|
|
|
if (size > 0xffffff) {
|
|
fprintf(stderr, "write: invalid size %d (bytes)\n", size);
|
|
fprintf(stderr, "write size must be less than or equal to 16777215 bytes\n");
|
|
return -1;
|
|
}
|
|
|
|
union serial_load::command_reply command = serial_load::command::write(dest, size);
|
|
res = ftdi_write_data(ftdi, command.u8, (sizeof (command)));
|
|
assert(res == (sizeof (command)));
|
|
union serial_load::command_reply reply;
|
|
res = read_reply(ftdi, serial_load::reply::_write, reply);
|
|
if (res != 0) {
|
|
return -2;
|
|
}
|
|
if (reply.arg[0] != command.arg[0] || reply.arg[1] != command.arg[1]) {
|
|
fprintf(stderr, "write: argument mismatch: (%08x, %08x) != (%08x, %08x)\n",
|
|
reply.arg[0], reply.arg[1],
|
|
command.arg[0], command.arg[1]);
|
|
return -1;
|
|
}
|
|
|
|
int clock_res;
|
|
struct timespec start;
|
|
//struct timespec end1;
|
|
struct timespec end2;
|
|
res = clock_gettime(CLOCK_MONOTONIC, &start);
|
|
assert(res == 0);
|
|
res = ftdi_write_data(ftdi, buf, size);
|
|
//clock_res = clock_gettime(CLOCK_MONOTONIC, &end1);
|
|
//assert(clock_res == 0);
|
|
assert(res >= 0);
|
|
assert((uint32_t)res == size);
|
|
|
|
uint32_t buf_crc = crc32(buf, size);
|
|
|
|
union serial_load::command_reply crc_reply;
|
|
res = read_reply(ftdi, serial_load::reply::_crc, crc_reply);
|
|
clock_res = clock_gettime(CLOCK_MONOTONIC, &end2);
|
|
assert(clock_res == 0);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
fprintf(stderr, "remote crc: %08x; local crc %08x\n", crc_reply.arg[0], buf_crc);
|
|
|
|
// one start bit, one stop bit, 8 data bits: 8/10
|
|
unsigned short value;
|
|
unsigned short index;
|
|
double dreamcast_baud = dreamcast_rate(current_cks, current_scbrr);
|
|
int ftdi_baud = convert_baudrate_UT_export(dreamcast_baud, ftdi, &value, &index);
|
|
double idealized_baud = static_cast<double>(ftdi_baud) * 8.0 / 10.0;
|
|
double idealized_time = static_cast<double>(size) * 8.0 / idealized_baud;
|
|
|
|
//double measured_time1 = timespec_difference(&end1, &start);
|
|
double measured_time2 = timespec_difference(&end2, &start);
|
|
// subtract 128 bit-periods (16 bytes) to account for the time spent waiting for the read reply
|
|
// at 1562500 bits per second, this subtracts an (insignificant) 102 µs from the displayed time
|
|
double time_adjustment = (16.0 * 8.0) / (dreamcast_baud * 8.0 / 10.0);
|
|
measured_time2 -= time_adjustment;
|
|
|
|
fprintf(stderr, "%d bits/sec:\n", ftdi_baud);
|
|
fprintf(stderr, " idealized write time : %.03f seconds\n", idealized_time);
|
|
fprintf(stderr, " measured write time : %.03f seconds\n", measured_time2);
|
|
|
|
if (crc_reply.arg[0] != buf_crc) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_jump(struct ftdi_context * ftdi, const uint32_t dest)
|
|
{
|
|
int res;
|
|
|
|
union serial_load::command_reply command = serial_load::command::jump(dest);
|
|
res = ftdi_write_data(ftdi, command.u8, (sizeof (command)));
|
|
assert(res == (sizeof (command)));
|
|
|
|
union serial_load::command_reply reply;
|
|
res = read_reply(ftdi, serial_load::reply::_jump, reply);
|
|
if (res != 0) {
|
|
return -2;
|
|
}
|
|
if (reply.arg[0] != command.arg[0] || reply.arg[1] != command.arg[1]) {
|
|
fprintf(stderr, "jump: argument mismatch: (%08x, %08x) != (%08x, %08x)\n",
|
|
reply.arg[0], reply.arg[1],
|
|
command.arg[0], command.arg[1]);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_show_baudrate_error(struct ftdi_context * ftdi, uint32_t rows)
|
|
{
|
|
/*
|
|
B = (390625 * 4^(1 - n)) / (N + 1)
|
|
*/
|
|
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, " SH7091 baud | FTDI baud | error \n");
|
|
fprintf(stderr, " ------------|-------------|---------\n");
|
|
unsigned short value;
|
|
unsigned short index;
|
|
rows = min(rows, 256);
|
|
for (uint32_t i = 0; i < rows; i++) {
|
|
int baud = convert_baudrate_UT_export(dreamcast_rate(0, i), ftdi, &value, &index);
|
|
if (baud < 0) {
|
|
fprintf(stderr, "ftdi_convert_baudrate: %d\n", baud);
|
|
return -1;
|
|
}
|
|
double baudf = baud;
|
|
double ratef = dreamcast_rate(0, i);
|
|
double error = (baudf - ratef) / ratef * 100.0;
|
|
fprintf(stderr, " %11.00f %11.00f % 02.03f%%\n", round(ratef), baudf, error);
|
|
}
|
|
|
|
fprintf(stderr, "\n \"\n Note: As far as possible, the setting should be made so that the\n error is within 1%%.\n \"\n");
|
|
fprintf(stderr, " - SH7091 Hardware Manual, 03/02/1999, page 486\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_list_baudrates(struct ftdi_context * ftdi, uint32_t rows)
|
|
{
|
|
(void)ftdi;
|
|
|
|
fprintf(stderr, " scbrr | cks 0 | cks 1 | cks 2 | cks 3\n");
|
|
fprintf(stderr, "---------------------------------------------------------\n");
|
|
rows = min(rows, 256);
|
|
for (uint32_t i = 0; i < rows; i++) {
|
|
fprintf(stderr, " 0x%02x % 11.2f % 11.2f % 11.2f % 11.2f\n",
|
|
i,
|
|
dreamcast_rate(0, i),
|
|
dreamcast_rate(1, i),
|
|
dreamcast_rate(2, i),
|
|
dreamcast_rate(3, i));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int read_file(const char * filename, uint8_t ** buf, uint32_t * size_out)
|
|
{
|
|
FILE * file = fopen(filename, "rb");
|
|
if (file == NULL) {
|
|
fprintf(stderr, "fopen(\"%s\", \"rb\"): %s\n", filename, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
int ret;
|
|
ret = fseek(file, 0L, SEEK_END);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "fseek(SEEK_END)");
|
|
return -1;
|
|
}
|
|
|
|
long offset = ftell(file);
|
|
if (offset < 0) {
|
|
fprintf(stderr, "ftell");
|
|
return -1;
|
|
}
|
|
size_t size = offset;
|
|
|
|
ret = fseek(file, 0L, SEEK_SET);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "fseek(SEEK_SET)");
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr, "read_file: %s size %ld\n", filename, size);
|
|
*buf = (uint8_t *)malloc(size);
|
|
size_t fread_size = fread(*buf, 1, size, file);
|
|
if (fread_size != size) {
|
|
fprintf(stderr, "fread `%s` short read: %" PRIu64 " ; expected: %" PRIu64 "\n", filename, fread_size, size);
|
|
return -1;
|
|
}
|
|
|
|
ret = fclose(file);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "fclose");
|
|
return -1;
|
|
}
|
|
|
|
*size_out = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int write_file(const char * filename, uint8_t * buf, uint32_t size)
|
|
{
|
|
FILE * file = fopen(filename, "wb");
|
|
if (file == NULL) {
|
|
fprintf(stderr, "fopen(\"%s\", \"wb\"): %s\n", filename, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
size_t fwrite_size = fwrite(buf, 1, size, file);
|
|
if (fwrite_size != size) {
|
|
fprintf(stderr, "fwrite `%s` short write: %" PRIu64 " ; expected: %" PRIu32 "\n", filename, fwrite_size, size);
|
|
return -1;
|
|
}
|
|
|
|
int ret = fclose(file);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "fclose");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_read(struct ftdi_context * ftdi, const uint32_t src, uint8_t * buf, const uint32_t size)
|
|
{
|
|
int res;
|
|
|
|
if (size > 0xffffff) {
|
|
fprintf(stderr, "read: invalid size %d (bytes)\n", size);
|
|
fprintf(stderr, "read size must be less than or equal to 16777215 bytes\n");
|
|
return -1;
|
|
}
|
|
|
|
union serial_load::command_reply command = serial_load::command::read(src, size);
|
|
res = ftdi_write_data(ftdi, command.u8, (sizeof (command)));
|
|
assert(res == (sizeof (command)));
|
|
union serial_load::command_reply reply;
|
|
res = read_reply(ftdi, serial_load::reply::_read, reply);
|
|
if (res != 0) {
|
|
return -2;
|
|
}
|
|
if (reply.arg[0] != command.arg[0] || reply.arg[1] != command.arg[1]) {
|
|
fprintf(stderr, "read: argument mismatch: (%08x, %08x) != (%08x, %08x)\n",
|
|
reply.arg[0], reply.arg[1],
|
|
command.arg[0], command.arg[1]);
|
|
return -1;
|
|
}
|
|
|
|
uint32_t read_length = 0;
|
|
while (read_length < size) {
|
|
res = ftdi_read_data(ftdi, (uint8_t *)&buf[read_length], size - read_length);
|
|
assert(res >= 0);
|
|
read_length += res;
|
|
if (read_length < size)
|
|
fprintf(stderr, "read: short read; want %x out of %x\n", size - read_length, size);
|
|
}
|
|
|
|
uint32_t buf_crc = crc32((uint8_t*)buf, size);
|
|
|
|
union serial_load::command_reply crc_reply;
|
|
res = read_reply(ftdi, serial_load::reply::_crc, crc_reply);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr, "remote crc: %08x; local crc %08x\n", crc_reply.arg[0], buf_crc);
|
|
if (crc_reply.arg[0] != buf_crc) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_speed(struct ftdi_context * ftdi, uint32_t scbrr)
|
|
{
|
|
int res;
|
|
|
|
if (scbrr > 255) {
|
|
fprintf(stderr, "speed: invalid speed %d\n", scbrr);
|
|
fprintf(stderr, "speed is expressed as a raw SCBRR value; see `list_baudrates`\n");
|
|
return -1;
|
|
}
|
|
|
|
union serial_load::command_reply command = serial_load::command::speed(scbrr);
|
|
res = ftdi_write_data(ftdi, command.u8, (sizeof (command)));
|
|
assert(res == (sizeof (command)));
|
|
|
|
union serial_load::command_reply reply;
|
|
res = read_reply(ftdi, serial_load::reply::_speed, reply);
|
|
if (res != 0) {
|
|
return -2;
|
|
}
|
|
|
|
if (reply.arg[0] != command.arg[0] || reply.arg[1] != command.arg[1]) {
|
|
fprintf(stderr, "speed: argument mismatch: (%08x, %08x) != (%08x, %08x)\n",
|
|
reply.arg[0], reply.arg[1],
|
|
command.arg[0], command.arg[1]);
|
|
return -1;
|
|
}
|
|
|
|
res = ftdi_set_baudrate(ftdi, round(dreamcast_rate(current_cks, scbrr)));
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_set_baudrate\n");
|
|
return -1;
|
|
}
|
|
current_scbrr = scbrr;
|
|
|
|
res = ftdi_tciflush(ftdi);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_tciflush\n");
|
|
return -1;
|
|
}
|
|
|
|
res = ftdi_tcoflush(ftdi);
|
|
if (res < 0) {
|
|
fprintf(stderr, "ftdi_tcoflush\n");
|
|
return -1;
|
|
}
|
|
|
|
uint8_t discard[1024];
|
|
res = ftdi_read_data(ftdi, discard, (sizeof (discard)));
|
|
assert(res >= 0);
|
|
(void)discard;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void do_console(struct ftdi_context * ftdi)
|
|
{
|
|
int res;
|
|
|
|
ftdi->usb_read_timeout = 1;
|
|
|
|
uint8_t read_buf[ftdi->readbuffer_chunksize];
|
|
|
|
while (1) {
|
|
res = ftdi_read_data(ftdi, read_buf, ftdi->readbuffer_chunksize);
|
|
if (res > 0) {
|
|
fwrite(read_buf, 1, res, stdout);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
}
|
|
|
|
constexpr int count_left_set_bits(uint32_t n, int stop_bit)
|
|
{
|
|
int bit_ix = 31;
|
|
int count = 0;
|
|
while (bit_ix != stop_bit) {
|
|
if (n & (1 << 31)) {
|
|
count += 1;
|
|
}
|
|
n <<= 1;
|
|
bit_ix -= 1;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
static_assert(count_left_set_bits(0xe, 1) == 2);
|
|
static_assert(count_left_set_bits(0x2, 1) == 0);
|
|
|
|
consteval int count_trailing_zeros(uint32_t n)
|
|
{
|
|
int count = 0;
|
|
for (int i = 0; i < 32; i++) {
|
|
if ((n & 1) != 0)
|
|
break;
|
|
count += 1;
|
|
n >>= 1;
|
|
}
|
|
return count;
|
|
}
|
|
static_assert(count_trailing_zeros(0x80) == 7);
|
|
static_assert(count_trailing_zeros(0x2) == 1);
|
|
|
|
void print_storage_function_definition(const uint32_t fd)
|
|
{
|
|
int partitions = ((fd >> 24) & 0xff) + 1;
|
|
int bytes_per_block = (((fd >> 16) & 0xff) + 1) * 32;
|
|
int write_accesses = (fd >> 12) & 0xf;
|
|
int read_accesses = (fd >> 8) & 0xf;
|
|
|
|
fprintf(stderr, " storage function definition:\n");
|
|
fprintf(stderr, " partitions: %d\n", partitions);
|
|
fprintf(stderr, " bytes_per_block: %d\n", bytes_per_block);
|
|
fprintf(stderr, " write_accesses: %d\n", write_accesses);
|
|
fprintf(stderr, " read_accesses: %d\n", read_accesses);
|
|
}
|
|
|
|
template <typename T>
|
|
static inline T be_bswap(const T n)
|
|
{
|
|
if constexpr (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
|
return n;
|
|
else
|
|
return std::byteswap<T>(n);
|
|
}
|
|
|
|
void print_device_id(struct maple::device_id& device_id)
|
|
{
|
|
fprintf(stderr, " ft: %08x\n", be_bswap<uint32_t>(device_id.ft));
|
|
fprintf(stderr, " fd[0]: %08x\n", be_bswap<uint32_t>(device_id.fd[0]));
|
|
fprintf(stderr, " fd[1]: %08x\n", be_bswap<uint32_t>(device_id.fd[1]));
|
|
fprintf(stderr, " fd[2]: %08x\n", be_bswap<uint32_t>(device_id.fd[2]));
|
|
}
|
|
|
|
void print_media_info(struct ft1::get_media_info_data_transfer::data_format& data)
|
|
{
|
|
fprintf(stderr, " media_info:\n");
|
|
fprintf(stderr, " total_size: %04x\n", data.total_size);
|
|
fprintf(stderr, " partition_number: %04x\n", data.partition_number);
|
|
fprintf(stderr, " system_area_block_number: %04x\n", data.system_area_block_number);
|
|
fprintf(stderr, " fat_area_block_number: %04x\n", data.fat_area_block_number);
|
|
fprintf(stderr, " number_of_fat_area_blocks: %04x\n", data.number_of_fat_area_blocks);
|
|
fprintf(stderr, " file_information_block_number: %04x\n", data.file_information_block_number);
|
|
fprintf(stderr, " number_of_file_information_blocks: %04x\n", data.number_of_file_information_blocks);
|
|
fprintf(stderr, " volume_icon: %04x\n", data.volume_icon);
|
|
fprintf(stderr, " save_area_block_number: %04x\n", data.save_area_block_number);
|
|
fprintf(stderr, " number_of_save_area_blocks: %04x\n", data.number_of_save_area_blocks);
|
|
fprintf(stderr, " reserved_for_execution_file: %08x\n", data.reserved_for_execution_file);
|
|
}
|
|
|
|
int do_maple_raw(struct ftdi_context * ftdi,
|
|
uint8_t * send_buf,
|
|
uint32_t send_size,
|
|
uint8_t * recv_buf,
|
|
uint32_t recv_size)
|
|
{
|
|
int res;
|
|
|
|
union serial_load::command_reply command = serial_load::command::maple_raw(send_size, recv_size);
|
|
//dump_command_reply(command);
|
|
res = ftdi_write_data(ftdi, command.u8, (sizeof (command)));
|
|
assert(res == (sizeof (command)));
|
|
union serial_load::command_reply reply;
|
|
fprintf(stderr, "maple_raw: wait maple_raw reply\n");
|
|
res = read_reply(ftdi, serial_load::reply::_maple_raw, reply);
|
|
if (res != 0) {
|
|
return -2;
|
|
}
|
|
if (reply.arg[0] != command.arg[0] || reply.arg[1] != command.arg[1]) {
|
|
fprintf(stderr, "maple_raw: argument mismatch: (%08x, %08x) != (%08x, %08x)\n",
|
|
reply.arg[0], reply.arg[1],
|
|
command.arg[0], command.arg[1]);
|
|
return -1;
|
|
}
|
|
|
|
res = ftdi_write_data(ftdi, send_buf, send_size);
|
|
assert(res >= 0);
|
|
assert((uint32_t)res == send_size);
|
|
|
|
uint32_t send_buf_crc = crc32(send_buf, send_size);
|
|
/*
|
|
fprintf(stderr, "send_size: %d\n", send_size);
|
|
for (uint32_t i = 0; i < send_size; i++) {
|
|
fprintf(stderr, "%02x ", send_buf[i]);
|
|
if (i % 4 == 3)
|
|
fprintf(stderr, "\n");
|
|
}
|
|
fprintf(stderr, "\n");
|
|
*/
|
|
|
|
union serial_load::command_reply send_crc_reply;
|
|
fprintf(stderr, "maple_raw: send: wait crc reply\n");
|
|
res = read_reply(ftdi, serial_load::reply::_crc, send_crc_reply);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
fprintf(stderr, "maple_raw: send: remote crc: %08x; local crc %08x\n", send_crc_reply.arg[0], send_buf_crc);
|
|
if (send_crc_reply.arg[0] != send_buf_crc) {
|
|
dump_command_reply(send_crc_reply);
|
|
return -1;
|
|
}
|
|
|
|
uint32_t read_length = 0;
|
|
while (read_length < recv_size) {
|
|
res = ftdi_read_data(ftdi, &recv_buf[read_length], recv_size - read_length);
|
|
assert(res >= 0);
|
|
read_length += res;
|
|
if (read_length < recv_size)
|
|
fprintf(stderr, "maple raw: short read; want %x out of %x\n", recv_size - read_length, recv_size);
|
|
}
|
|
|
|
uint32_t recv_buf_crc = crc32(recv_buf, recv_size);
|
|
|
|
union serial_load::command_reply recv_crc_reply;
|
|
fprintf(stderr, "maple_raw: recv: wait crc reply\n");
|
|
res = read_reply(ftdi, serial_load::reply::_crc, recv_crc_reply);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr, "maple_raw: recv: remote crc: %08x; local crc %08x\n", recv_crc_reply.arg[0], recv_buf_crc);
|
|
if (recv_crc_reply.arg[0] != recv_buf_crc) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
template <uint32_t base_address>
|
|
void send_extension_device_request(maple::host_command_writer<base_address>& writer, uint8_t port, uint8_t lm)
|
|
{
|
|
uint32_t host_port_select = host_instruction_port_select(port);
|
|
uint32_t destination_ap = ap_port_select(port) | ap::de::expansion_device | lm;
|
|
|
|
using command_type = maple::device_request;
|
|
using response_type = maple::device_status;
|
|
|
|
writer.template append_command<command_type, response_type>(host_port_select,
|
|
destination_ap,
|
|
false); // end_flag
|
|
}
|
|
|
|
template <uint32_t base_address>
|
|
void send_get_media_info(maple::host_command_writer<base_address>& writer, uint8_t port, uint8_t lm)
|
|
{
|
|
uint32_t host_port_select = host_instruction_port_select(port);
|
|
uint32_t destination_ap = ap_port_select(port) | ap::de::expansion_device | lm;
|
|
|
|
using command_type = maple::get_media_info;
|
|
using response_type = maple::data_transfer<ft1::get_media_info_data_transfer::data_format>;
|
|
|
|
auto [host_command, host_response] =
|
|
writer.template append_command<command_type, response_type>(host_port_select,
|
|
destination_ap,
|
|
false); // end_flag
|
|
|
|
host_command->bus_data.data_fields.function_type = be_bswap<uint32_t>(function_type::storage);
|
|
host_command->bus_data.data_fields.pt = 0;
|
|
}
|
|
|
|
struct block_read_response {
|
|
union responses {
|
|
struct maple::file_error::data_fields file_error;
|
|
struct maple::data_transfer<ft1::block_read_data_transfer::data_format>::data_fields data_transfer;
|
|
};
|
|
|
|
using data_fields = union responses;
|
|
};
|
|
|
|
template <uint32_t base_address>
|
|
void send_block_read(maple::host_command_writer<base_address>& writer, uint8_t port, uint8_t lm, uint32_t recv_trailing, int partition, int phase, int block_number)
|
|
{
|
|
uint32_t host_port_select = host_instruction_port_select(port);
|
|
uint32_t destination_ap = ap_port_select(port) | ap::de::expansion_device | lm;
|
|
|
|
using command_type = maple::block_read;
|
|
using response_type = block_read_response;
|
|
|
|
auto [host_command, host_response] =
|
|
writer.template append_command<command_type, response_type>(host_port_select,
|
|
destination_ap,
|
|
false, // end_flag
|
|
0, // send_trailing
|
|
recv_trailing // recv_trailing
|
|
);
|
|
|
|
auto& data_fields = host_command->bus_data.data_fields;
|
|
data_fields.function_type = be_bswap<uint32_t>(function_type::storage);
|
|
data_fields.pt = partition;
|
|
data_fields.phase = phase;
|
|
data_fields.block_number = be_bswap<uint16_t>(block_number);
|
|
}
|
|
|
|
template <uint32_t base_address>
|
|
void do_lm_requests(maple::host_command_writer<base_address>& writer, uint8_t port, uint8_t lm, uint32_t& last_send_offset,
|
|
void (* func)(maple::host_command_writer<base_address>& writer, uint8_t port, uint8_t lm))
|
|
{
|
|
uint32_t bit = ap::lm_bus::_0;
|
|
for (int i = 0; i < 5; i++) {
|
|
if (lm & bit) {
|
|
last_send_offset = writer.send_offset;
|
|
func(writer, port, bit);
|
|
}
|
|
bit <<= 1;
|
|
}
|
|
}
|
|
|
|
struct storage_state {
|
|
uint32_t fd;
|
|
|
|
uint32_t partitions() {
|
|
return ((fd >> 24) & 0xff) + 1;
|
|
}
|
|
uint32_t bytes_per_block() {
|
|
return (((fd >> 16) & 0xff) + 1) * 32;
|
|
}
|
|
uint32_t write_accesses() {
|
|
return (fd >> 12) & 0xf;
|
|
}
|
|
uint32_t read_accesses() {
|
|
return (fd >> 8) & 0xf;
|
|
}
|
|
|
|
uint32_t bytes_per_read_access()
|
|
{
|
|
// divide rounding up
|
|
return (bytes_per_block() + (read_accesses() - 1)) / read_accesses();
|
|
}
|
|
|
|
uint32_t bytes_per_write_access()
|
|
{
|
|
// divide rounding up
|
|
return (bytes_per_block() + (write_accesses() - 1)) / write_accesses();
|
|
}
|
|
|
|
};
|
|
|
|
template <uint32_t base_address>
|
|
int handle_block_read_dump(struct ftdi_context * ftdi,
|
|
maple::host_command_writer<base_address>& writer,
|
|
struct storage_state& storage_state,
|
|
uint32_t last_send_offset,
|
|
FILE * dump)
|
|
{
|
|
using command_type = maple::block_read;
|
|
using host_command_type = maple::host_command<command_type::data_fields>;
|
|
|
|
uint8_t * send_buf = reinterpret_cast<uint8_t *>(writer.send_buf);
|
|
auto host_command = reinterpret_cast<host_command_type *>(&send_buf[last_send_offset]);
|
|
host_command->host_instruction |= host_instruction::end_flag;
|
|
|
|
int res = do_maple_raw(ftdi,
|
|
reinterpret_cast<uint8_t *>(writer.send_buf), writer.send_offset,
|
|
reinterpret_cast<uint8_t *>(writer.recv_buf), writer.recv_offset);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
|
|
using response_type = block_read_response;
|
|
using host_response_type = maple::host_response<response_type::data_fields>;
|
|
const uint32_t recv_trailing = storage_state.bytes_per_read_access();
|
|
const uint32_t host_response_size = (sizeof (host_response_type)) + recv_trailing;
|
|
|
|
uint8_t * recv_buf = reinterpret_cast<uint8_t *>(writer.recv_buf);
|
|
|
|
for (uint32_t offset = 0; offset < writer.recv_offset; offset += host_response_size) {
|
|
auto host_response = reinterpret_cast<host_response_type *>(&recv_buf[offset]);
|
|
auto& bus_data = host_response->bus_data;
|
|
if (bus_data.command_code != maple::data_transfer<uint8_t[0]>::command_code) {
|
|
fprintf(stderr, "lm did not reply to block read: %d\n", bus_data.command_code);
|
|
auto& file_error = bus_data.data_fields.file_error;
|
|
if (bus_data.command_code == maple::file_error::command_code) {
|
|
fprintf(stderr, "function error code: %d\n", file_error.function_error_code);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
fwrite(bus_data.data_fields.data_transfer.data.block_data,
|
|
1,
|
|
storage_state.bytes_per_read_access(),
|
|
dump);
|
|
}
|
|
|
|
writer.send_offset = 0;
|
|
writer.recv_offset = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
template <uint32_t base_address>
|
|
int do_maple_block_read_dump(struct ftdi_context * ftdi,
|
|
int port,
|
|
int lm,
|
|
struct storage_state& storage_state,
|
|
struct ft1::get_media_info_data_transfer::data_format& media_info) // copy of media_info on the stack
|
|
{
|
|
uint8_t send_buf[8192];
|
|
uint8_t recv_buf[8192];
|
|
|
|
char filename[256];
|
|
snprintf(filename, (sizeof (filename)), "dump_p%dl%d.bin", port, ap_lm_bus_int(lm));
|
|
|
|
FILE * dump = fopen(filename, "wb");
|
|
|
|
auto writer = maple::host_command_writer<base_address>(reinterpret_cast<uint32_t *>(send_buf),
|
|
reinterpret_cast<uint32_t *>(recv_buf));
|
|
uint32_t last_send_offset = 0;
|
|
|
|
constexpr int partition = 0;
|
|
|
|
using response_type = maple::data_transfer<ft1::get_media_info_data_transfer::data_format>;
|
|
using host_response_type = maple::host_response<response_type>;
|
|
const uint32_t recv_trailing = storage_state.bytes_per_read_access();
|
|
const uint32_t host_response_size = (sizeof (host_response_type)) + recv_trailing;
|
|
|
|
for (uint16_t block_number = 0; block_number < (media_info.total_size + 1); block_number++) {
|
|
for (uint32_t phase = 0; phase < storage_state.read_accesses(); phase++) {
|
|
fprintf(stderr, "maple_block_read: block %d,%d\n", block_number, phase);
|
|
last_send_offset = writer.send_offset;
|
|
send_block_read(writer, port, lm, recv_trailing, partition, phase, block_number);
|
|
}
|
|
|
|
constexpr uint32_t maple_buffer_size = 8192;
|
|
if (writer.recv_offset + host_response_size > maple_buffer_size) {
|
|
int res = handle_block_read_dump<base_address>(ftdi,
|
|
writer,
|
|
storage_state,
|
|
last_send_offset,
|
|
dump);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (writer.send_offset != 0) {
|
|
int res = handle_block_read_dump<base_address>(ftdi,
|
|
writer,
|
|
storage_state,
|
|
last_send_offset,
|
|
dump);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
fclose(dump);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_maple_list(struct ftdi_context * ftdi)
|
|
{
|
|
constexpr uint32_t base_address = 0xac002020;
|
|
|
|
uint8_t send_buf[8192];
|
|
uint8_t recv_buf[8192];
|
|
|
|
using command_type = maple::device_request;
|
|
using response_type = maple::device_status;
|
|
|
|
auto writer = maple::host_command_writer<base_address>(reinterpret_cast<uint32_t *>(send_buf),
|
|
reinterpret_cast<uint32_t *>(recv_buf));
|
|
auto [host_command, host_response]
|
|
= writer.append_command_all_ports<command_type, response_type>();
|
|
|
|
int res = do_maple_raw(ftdi,
|
|
send_buf, writer.send_offset,
|
|
recv_buf, writer.recv_offset);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
|
|
// reset writer
|
|
writer.send_offset = 0;
|
|
writer.recv_offset = 0;
|
|
uint32_t last_send_offset = 0;
|
|
|
|
for (uint8_t port = 0; port < 4; port++) {
|
|
auto& bus_data = host_response[port].bus_data;
|
|
auto& data_fields = bus_data.data_fields;
|
|
fprintf(stderr, "[device] port: %d\n", port);
|
|
if (bus_data.command_code != response_type::command_code) {
|
|
fprintf(stderr, " disconnected %02x %02x %02x %02x\n",
|
|
bus_data.command_code,
|
|
bus_data.destination_ap,
|
|
bus_data.source_ap,
|
|
bus_data.data_size);
|
|
} else {
|
|
print_device_id(data_fields.device_id);
|
|
uint8_t source_ap__lm_bus = bus_data.source_ap & ap::lm_bus::bit_mask;
|
|
do_lm_requests(writer, port, source_ap__lm_bus, last_send_offset, &send_extension_device_request);
|
|
}
|
|
}
|
|
|
|
if (writer.send_offset == 0) {
|
|
return 0;
|
|
}
|
|
|
|
struct storage_state storage_state[4][5];
|
|
|
|
{
|
|
// rewrite the end flag of the last request
|
|
using host_command_type = maple::host_command<uint8_t[0]>;
|
|
auto host_command = reinterpret_cast<host_command_type *>(&send_buf[last_send_offset]);
|
|
host_command->host_instruction |= host_instruction::end_flag;
|
|
|
|
int res = do_maple_raw(ftdi,
|
|
send_buf, writer.send_offset,
|
|
recv_buf, writer.recv_offset);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
|
|
const uint32_t recv_offset = writer.recv_offset;
|
|
|
|
// reset writer
|
|
writer.send_offset = 0;
|
|
writer.recv_offset = 0;
|
|
last_send_offset = 0;
|
|
|
|
using response_type = maple::device_status;
|
|
using host_response_type = maple::host_response<response_type::data_fields>;
|
|
for (uint32_t offset = 0; offset < recv_offset; offset += (sizeof (host_response_type))) {
|
|
auto host_response = reinterpret_cast<host_response_type *>(&recv_buf[offset]);
|
|
|
|
auto& bus_data = host_response->bus_data;
|
|
auto& data_fields = bus_data.data_fields;
|
|
|
|
if (bus_data.command_code != response_type::command_code) {
|
|
fprintf(stderr, " disconnected %02x %02x %02x %02x\n",
|
|
bus_data.command_code,
|
|
bus_data.destination_ap,
|
|
bus_data.source_ap,
|
|
bus_data.data_size);
|
|
continue;
|
|
}
|
|
|
|
uint32_t port = (bus_data.source_ap & ap::port_select::bit_mask) >> 6;
|
|
uint32_t lm_bus = (bus_data.source_ap & ap::lm_bus::bit_mask) >> 0;
|
|
fprintf(stderr, "[extension] port: %d ; lm: %05b\n", port, lm_bus);
|
|
print_device_id(data_fields.device_id);
|
|
|
|
uint32_t ft = be_bswap<uint32_t>(data_fields.device_id.ft);
|
|
if (ft & function_type::storage) {
|
|
int fd_ix = count_left_set_bits(ft, count_trailing_zeros(function_type::storage));
|
|
uint32_t fd = be_bswap<uint32_t>(data_fields.device_id.fd[fd_ix]);
|
|
print_storage_function_definition(fd);
|
|
|
|
storage_state[port][ap_lm_bus_int(lm_bus)].fd = fd;
|
|
last_send_offset = writer.send_offset;
|
|
send_get_media_info(writer, port, lm_bus);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (writer.send_offset == 0) {
|
|
return 0;
|
|
}
|
|
|
|
{
|
|
// rewrite the end flag of the last request
|
|
using host_command_type = maple::host_command<uint8_t[0]>;
|
|
auto host_command = reinterpret_cast<host_command_type *>(&send_buf[last_send_offset]);
|
|
host_command->host_instruction |= host_instruction::end_flag;
|
|
|
|
int res = do_maple_raw(ftdi,
|
|
send_buf, writer.send_offset,
|
|
recv_buf, writer.recv_offset);
|
|
if (res != 0) {
|
|
return -1;
|
|
}
|
|
|
|
const uint32_t recv_offset = writer.recv_offset;
|
|
// reset writer
|
|
writer.send_offset = 0;
|
|
writer.recv_offset = 0;
|
|
last_send_offset = 0;
|
|
|
|
using response_type = maple::data_transfer<ft1::get_media_info_data_transfer::data_format>;
|
|
using host_response_type = maple::host_response<response_type::data_fields>;
|
|
|
|
for (uint32_t offset = 0; offset < recv_offset; offset += (sizeof (host_response_type))) {
|
|
auto host_response = reinterpret_cast<host_response_type *>(&recv_buf[offset]);
|
|
|
|
auto& bus_data = host_response->bus_data;
|
|
auto& data_fields = bus_data.data_fields;
|
|
auto& data = data_fields.data;
|
|
|
|
if (bus_data.command_code != response_type::command_code) {
|
|
fprintf(stderr, " disconnected %02x %02x %02x %02x\n",
|
|
bus_data.command_code,
|
|
bus_data.destination_ap,
|
|
bus_data.source_ap,
|
|
bus_data.data_size);
|
|
continue;
|
|
}
|
|
|
|
uint32_t port = (bus_data.source_ap & ap::port_select::bit_mask) >> 6;
|
|
uint32_t lm_bus = (bus_data.source_ap & ap::lm_bus::bit_mask) >> 0;
|
|
fprintf(stderr, "[extension] port: %d ; lm: %05b\n", port, lm_bus);
|
|
print_media_info(data);
|
|
|
|
using command_type = maple::block_read;
|
|
using host_command_type = maple::host_command<command_type>;
|
|
using response_type = block_read_response;
|
|
using host_response_type = maple::host_response<response_type::data_fields>;
|
|
fprintf(stderr, "block read command size %ld\n", (sizeof (host_command_type)));
|
|
fprintf(stderr, "block read response size %ld\n", (sizeof (host_response_type)));
|
|
|
|
do_maple_block_read_dump<base_address>(ftdi,
|
|
port,
|
|
lm_bus,
|
|
storage_state[port][ap_lm_bus_int(lm_bus)],
|
|
data);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum struct argument_type {
|
|
string,
|
|
integer
|
|
};
|
|
|
|
struct cli_command {
|
|
const char * name;
|
|
int num_arguments;
|
|
void * func;
|
|
};
|
|
|
|
struct cli_command commands[] = {
|
|
{ "read" , 3, (void *)&do_read },
|
|
{ "write" , 2, (void *)&do_write },
|
|
{ "jump" , 1, (void *)&do_jump },
|
|
{ "speed" , 1, (void *)&do_speed },
|
|
{ "list_baudrates" , 1, (void *)&do_list_baudrates },
|
|
{ "show_baudrate_error", 1, (void *)&do_show_baudrate_error },
|
|
{ "console" , 0, (void *)&do_console },
|
|
{ "maple_list" , 0, (void *)&do_maple_list },
|
|
};
|
|
|
|
constexpr int commands_length = (sizeof (commands)) / (sizeof (commands[0]));
|
|
|
|
typedef int (*func_0_arg)(struct ftdi_context *);
|
|
typedef int (*func_1_arg)(struct ftdi_context *, uint32_t);
|
|
typedef int (*func_2_arg)(struct ftdi_context *, uint32_t, uint8_t *, uint32_t);
|
|
typedef int (*func_3_arg)(struct ftdi_context *, uint32_t, uint32_t, uint8_t *, uint32_t);
|
|
|
|
int parse_integer(const char * s, uint32_t * value)
|
|
{
|
|
if (s[0] == '0' && s[1] == 'x') {
|
|
s = &s[2];
|
|
}
|
|
|
|
uint32_t n = 0;
|
|
while (*s != 0) {
|
|
char c = *s++;
|
|
n = n << 4;
|
|
switch (c) {
|
|
case '0': n += 0; break;
|
|
case '1': n += 1; break;
|
|
case '2': n += 2; break;
|
|
case '3': n += 3; break;
|
|
case '4': n += 4; break;
|
|
case '5': n += 5; break;
|
|
case '6': n += 6; break;
|
|
case '7': n += 7; break;
|
|
case '8': n += 8; break;
|
|
case '9': n += 9; break;
|
|
case 'A': [[fallthrough]];
|
|
case 'a': n += 0xa; break;
|
|
case 'B': [[fallthrough]];
|
|
case 'b': n += 0xb; break;
|
|
case 'C': [[fallthrough]];
|
|
case 'c': n += 0xc; break;
|
|
case 'D': [[fallthrough]];
|
|
case 'd': n += 0xd; break;
|
|
case 'E': [[fallthrough]];
|
|
case 'e': n += 0xe; break;
|
|
case 'F': [[fallthrough]];
|
|
case 'f': n += 0xf; break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
*value = n;
|
|
return 0;
|
|
}
|
|
|
|
#define CHECK_ARGC(__name__) \
|
|
if (arg_index >= argc) { \
|
|
fprintf(stderr, "while processing command `%s` expected argument `%s`\n", name, #__name__); \
|
|
return -1; \
|
|
}
|
|
|
|
#define INTEGER_ARGUMENT(__name__) \
|
|
CHECK_ARGC(__name__); \
|
|
uint32_t __name__; \
|
|
const char * __name__##str = argv[arg_index++]; \
|
|
{ int res = parse_integer(__name__##str, &__name__); \
|
|
if (res < 0) { \
|
|
fprintf(stderr, "while processing command `%s` expected integer at `%s`", name, __name__##str); \
|
|
return -1; \
|
|
} }
|
|
|
|
#define STRING_ARGUMENT(__name__) \
|
|
CHECK_ARGC(__name__); \
|
|
const char * __name__ = argv[arg_index++];
|
|
|
|
int handle_command(int argc, const char * argv[], struct ftdi_context * ftdi)
|
|
{
|
|
assert(argc >= 1);
|
|
int arg_index = 0;
|
|
const char * name = argv[arg_index++];
|
|
int func_ret;
|
|
|
|
for (int i = 0; i < commands_length; i++) {
|
|
if (strcmp(commands[i].name, name) == 0) {
|
|
switch (commands[i].num_arguments) {
|
|
case 0:
|
|
{
|
|
fprintf(stderr, "handle command: %s ()\n", commands[i].name);
|
|
func_0_arg func = (func_0_arg)commands[i].func;
|
|
func_ret = func(ftdi);
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
INTEGER_ARGUMENT(arg0);
|
|
|
|
fprintf(stderr, "handle command: %s (0x%08x)\n", commands[i].name, arg0);
|
|
func_1_arg func = (func_1_arg)commands[i].func;
|
|
func_ret = func(ftdi, arg0);
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
INTEGER_ARGUMENT(dest_addr);
|
|
STRING_ARGUMENT(filename);
|
|
|
|
uint8_t * buf = NULL;
|
|
uint32_t write_size;
|
|
int res = read_file(filename, &buf, &write_size);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
|
|
fprintf(stderr, "handle command: %s (0x%08x, %s)\n", commands[i].name, dest_addr, filename);
|
|
func_2_arg func = (func_2_arg)commands[i].func;
|
|
func_ret = func(ftdi, dest_addr, buf, write_size);
|
|
|
|
assert(buf != NULL);
|
|
free(buf);
|
|
}
|
|
break;
|
|
case 3:
|
|
{
|
|
INTEGER_ARGUMENT(src_addr);
|
|
INTEGER_ARGUMENT(read_size);
|
|
STRING_ARGUMENT(filename);
|
|
|
|
uint8_t * buf = (uint8_t *)malloc(read_size);
|
|
|
|
fprintf(stderr, "handle command %s (0x%08x, 0x%08x) → %s\n", commands[i].name, src_addr, read_size, filename);
|
|
func_2_arg func = (func_2_arg)commands[i].func;
|
|
func_ret = func(ftdi, src_addr, buf, read_size);
|
|
|
|
int res = write_file(filename, buf, read_size);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
|
|
assert(buf != NULL);
|
|
free(buf);
|
|
}
|
|
break;
|
|
default:
|
|
assert(false); // unimplemented
|
|
}
|
|
|
|
if (func_ret < 0)
|
|
return func_ret;
|
|
else
|
|
return arg_index;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "unknown command `%s`\n", name);
|
|
return -1;
|
|
}
|
|
|
|
int main(int argc, const char * argv[])
|
|
{
|
|
struct ftdi_context * ftdi;
|
|
|
|
ftdi = ftdi_new();
|
|
if (ftdi == 0) {
|
|
fprintf(stderr, "ftdi_new\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
int res;
|
|
res = init_ftdi_context(ftdi, 0);
|
|
if (res < 0) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
assert(argc >= 1);
|
|
argc--;
|
|
argv++;
|
|
while (argc > 0) {
|
|
res = handle_command(argc, argv, ftdi);
|
|
if (res < 0) {
|
|
return -1;
|
|
}
|
|
argc -= res;
|
|
argv += res;
|
|
}
|
|
|
|
return 0;
|
|
}
|