ftdi_transfer: move maple code to separate ftdi_maple source file
This commit is contained in:
parent
cfe9c07dfe
commit
1d25d54bb1
@ -33,8 +33,8 @@ inline void copy(T * dst, const T * src, const int32_t n) noexcept
|
||||
static uint8_t * framebuffer[2];
|
||||
static char textbuffer[2][4 * 8];
|
||||
|
||||
static uint32_t send_buf[1024 / 4] __attribute__((aligned(32)));
|
||||
static uint32_t recv_buf[1024 / 4] __attribute__((aligned(32)));
|
||||
static uint8_t send_buf[1024] __attribute__((aligned(32)));
|
||||
static uint8_t recv_buf[1024] __attribute__((aligned(32)));
|
||||
|
||||
struct serial_error_counter {
|
||||
uint32_t brk;
|
||||
@ -200,15 +200,8 @@ void send_device_request()
|
||||
|
||||
void send_raw(struct serial_load::maple_poll_state& state)
|
||||
{
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
((uint8_t*)&__recv_buf)[i] = 0xee;
|
||||
}
|
||||
maple::dma_start(&__send_buf, state.send_length,
|
||||
&__recv_buf, state.recv_length);
|
||||
/*
|
||||
maple::dma_start((uint32_t*)0xac000020, state.send_length,
|
||||
(uint32_t*)0xac002020, state.recv_length);
|
||||
*/
|
||||
maple::dma_start(reinterpret_cast<uint8_t *>(&__send_buf), state.send_length,
|
||||
reinterpret_cast<uint8_t *>(&__recv_buf), state.recv_length);
|
||||
}
|
||||
|
||||
void handle_maple(struct serial_load::maple_poll_state& state)
|
||||
|
@ -24,7 +24,12 @@ ttf_%: ttf_%.o
|
||||
ftdi_%.o: ftdi_%.cpp
|
||||
$(CXX) $(CFLAGS) $(CXXFLAGS) $(FTDI_CFLAGS) -c $< -o $@
|
||||
|
||||
ftdi_%: ftdi_%.o crc32.o
|
||||
FTDI_OBJ = \
|
||||
ftdi_transfer.o \
|
||||
ftdi_maple.o \
|
||||
crc32.o
|
||||
|
||||
ftdi_transfer: $(FTDI_OBJ)
|
||||
$(CXX) $^ -o $@ $(FTDI_LDFLAGS)
|
||||
|
||||
ttf_outline: ttf_outline.o 2d_pack.o
|
||||
|
480
tools/ftdi_maple.cpp
Normal file
480
tools/ftdi_maple.cpp
Normal file
@ -0,0 +1,480 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <bit>
|
||||
|
||||
#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"
|
||||
|
||||
#include "ftdi_transfer.hpp"
|
||||
#include "ftdi_maple.hpp"
|
||||
|
||||
constexpr uint32_t base_address = 0x0c004020;
|
||||
constexpr uint32_t maple_buffer_size = 16384;
|
||||
|
||||
struct storage_fd {
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
void send_device_request_all_ports(maple::host_command_writer<base_address>& writer)
|
||||
{
|
||||
using command_type = maple::device_request;
|
||||
using response_type = maple::device_status;
|
||||
|
||||
writer.template append_command_all_ports<command_type, response_type>();
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
void do_lm_requests(maple::host_command_writer<base_address>& writer, uint8_t port, uint8_t lm,
|
||||
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) {
|
||||
func(writer, port, bit);
|
||||
}
|
||||
bit <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
int handle_block_read_dump(struct ftdi_context * ftdi,
|
||||
maple::host_command_writer<base_address>& writer,
|
||||
struct storage_fd& storage_fd,
|
||||
FILE * dump)
|
||||
{
|
||||
writer.set_end_flag();
|
||||
int res = do_maple_raw(ftdi,
|
||||
writer.send_buf, writer.send_offset,
|
||||
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_fd.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_fd.bytes_per_read_access(),
|
||||
dump);
|
||||
}
|
||||
|
||||
writer.reset();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int do_maple_block_read_dump(struct ftdi_context * ftdi,
|
||||
int port,
|
||||
int lm,
|
||||
struct storage_fd& storage_fd,
|
||||
struct ft1::get_media_info_data_transfer::data_format& media_info) // copy of media_info on the stack
|
||||
{
|
||||
uint8_t send_buf[maple_buffer_size];
|
||||
uint8_t recv_buf[maple_buffer_size];
|
||||
|
||||
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>(send_buf,
|
||||
recv_buf);
|
||||
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_fd.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_fd.read_accesses(); phase++) {
|
||||
fprintf(stderr, "maple_block_read: block %d,%d\n", block_number, phase);
|
||||
send_block_read(writer, port, lm, recv_trailing, partition, phase, block_number);
|
||||
}
|
||||
|
||||
if (writer.recv_offset + host_response_size > maple_buffer_size) {
|
||||
int res = handle_block_read_dump(ftdi,
|
||||
writer,
|
||||
storage_fd,
|
||||
dump);
|
||||
if (res != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writer.send_offset != 0) {
|
||||
int res = handle_block_read_dump(ftdi,
|
||||
writer,
|
||||
storage_fd,
|
||||
dump);
|
||||
if (res != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(dump);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int handle_get_media_info_data_transfer_response(struct ftdi_context * ftdi,
|
||||
maple::host_command_writer<base_address>& writer,
|
||||
struct storage_fd storage_fd[4][5])
|
||||
{
|
||||
writer.set_end_flag();
|
||||
int res = do_maple_raw(ftdi,
|
||||
writer.send_buf, writer.send_offset,
|
||||
writer.recv_buf, writer.recv_offset);
|
||||
if (res != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const uint32_t recv_offset = writer.reset();
|
||||
|
||||
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 *>(&writer.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);
|
||||
|
||||
do_maple_block_read_dump(ftdi,
|
||||
port,
|
||||
lm_bus,
|
||||
storage_fd[port][ap_lm_bus_int(lm_bus)],
|
||||
data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int handle_device_status_response_extension(struct ftdi_context * ftdi,
|
||||
maple::host_command_writer<base_address>& writer,
|
||||
struct storage_fd storage_fd[4][5])
|
||||
{
|
||||
writer.set_end_flag();
|
||||
int res = do_maple_raw(ftdi,
|
||||
writer.send_buf, writer.send_offset,
|
||||
writer.recv_buf, writer.recv_offset);
|
||||
if (res != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const uint32_t recv_offset = writer.reset();
|
||||
|
||||
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 *>(&writer.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_fd[port][ap_lm_bus_int(lm_bus)].fd = fd;
|
||||
send_get_media_info(writer, port, lm_bus);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int handle_device_status_response(struct ftdi_context * ftdi,
|
||||
maple::host_command_writer<base_address>& writer)
|
||||
{
|
||||
int res = do_maple_raw(ftdi,
|
||||
writer.send_buf, writer.send_offset,
|
||||
writer.recv_buf, writer.recv_offset);
|
||||
if (res != 0) {
|
||||
return -1;
|
||||
}
|
||||
writer.reset();
|
||||
|
||||
using response_type = maple::device_status;
|
||||
using host_response_type = maple::host_response<response_type::data_fields>;
|
||||
|
||||
for (uint8_t port = 0; port < 4; port++) {
|
||||
auto host_response = reinterpret_cast<host_response_type *>(writer.recv_buf);
|
||||
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, &send_extension_device_request);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_maple_storage_dump(struct ftdi_context * ftdi)
|
||||
{
|
||||
uint8_t send_buf[maple_buffer_size];
|
||||
uint8_t recv_buf[maple_buffer_size];
|
||||
|
||||
auto writer = maple::host_command_writer<base_address>(send_buf,
|
||||
recv_buf);
|
||||
|
||||
int res;
|
||||
|
||||
send_device_request_all_ports(writer);
|
||||
|
||||
res = handle_device_status_response(ftdi, writer);
|
||||
if (res != 0)
|
||||
return -1;
|
||||
if (writer.send_offset == 0)
|
||||
return 0;
|
||||
|
||||
struct storage_fd storage_fd[4][5];
|
||||
|
||||
res = handle_device_status_response_extension(ftdi, writer, storage_fd);
|
||||
if (res != 0)
|
||||
return -1;
|
||||
if (writer.send_offset == 0)
|
||||
return 0;
|
||||
|
||||
res = handle_get_media_info_data_transfer_response(ftdi, writer, storage_fd);
|
||||
if (res != 0)
|
||||
return -1;
|
||||
if (writer.send_offset == 0)
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
3
tools/ftdi_maple.hpp
Normal file
3
tools/ftdi_maple.hpp
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int do_maple_storage_dump(struct ftdi_context * ftdi);
|
@ -14,13 +14,8 @@
|
||||
|
||||
#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"
|
||||
#include "ftdi_transfer.hpp"
|
||||
#include "ftdi_maple.hpp"
|
||||
|
||||
extern "C" int convert_baudrate_UT_export(int baudrate, struct ftdi_context *ftdi,
|
||||
unsigned short *value, unsigned short *index);
|
||||
@ -569,12 +564,14 @@ 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) {
|
||||
fprintf(stderr, "ftdi_read_data: %s\n", ftdi_get_error_string(ftdi));
|
||||
}
|
||||
assert(res >= 0);
|
||||
if (res > 0) {
|
||||
fwrite(read_buf, 1, res, stdout);
|
||||
fflush(stdout);
|
||||
@ -582,84 +579,6 @@ void do_console(struct ftdi_context * ftdi)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@ -738,399 +657,6 @@ int do_maple_raw(struct ftdi_context * ftdi,
|
||||
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
|
||||
@ -1150,7 +676,7 @@ struct cli_command commands[] = {
|
||||
{ "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 },
|
||||
{ "maple_storage_dump" , 0, (void *)&do_maple_storage_dump },
|
||||
};
|
||||
|
||||
constexpr int commands_length = (sizeof (commands)) / (sizeof (commands[0]));
|
||||
@ -1236,6 +762,8 @@ int handle_command(int argc, const char * argv[], struct ftdi_context * ftdi)
|
||||
{
|
||||
fprintf(stderr, "handle command: %s ()\n", commands[i].name);
|
||||
func_0_arg func = (func_0_arg)commands[i].func;
|
||||
fprintf(stderr, "%p\n", &do_console);
|
||||
fprintf(stderr, "%p\n", func);
|
||||
func_ret = func(ftdi);
|
||||
}
|
||||
break;
|
||||
|
10
tools/ftdi_transfer.hpp
Normal file
10
tools/ftdi_transfer.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ftdi.h>
|
||||
|
||||
int do_maple_raw(struct ftdi_context * ftdi,
|
||||
uint8_t * send_buf,
|
||||
uint32_t send_size,
|
||||
uint8_t * recv_buf,
|
||||
uint32_t recv_size);
|
@ -13,5 +13,5 @@ set -ex
|
||||
|
||||
${SCRIPT_DIR}/ftdi_transfer \
|
||||
write 0xac010000 "$filename" \
|
||||
jump 0xac010000
|
||||
# console
|
||||
jump 0xac010000 \
|
||||
console
|
||||
|
Loading…
x
Reference in New Issue
Block a user