From da93288b4d978e72a3c6eb08043bdf9fccf4b5be Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Wed, 6 Jan 2021 03:12:16 +0100 Subject: [PATCH] First commit --- .gitignore | 36 ++++++++ .gitmodules | 6 ++ Makefile | 40 +++++++++ libs/CLI11 | 1 + libs/serial | 1 + src/main.cpp | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 332 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Makefile create mode 160000 libs/CLI11 create mode 160000 libs/serial create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d273be5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +### Git ### +*.orig + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +# auto-generated tag files +tags + +# Custom +.clangd/ +.cache +.build +compile_commands.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..33c694a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libs/serial"] + path = libs/serial + url = https://github.com/wjwwood/serial +[submodule "libs/CLI11"] + path = libs/CLI11 + url = https://github.com/CLIUtils/CLI11 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b2424f --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +CXX = g++ +BUILD = .build +TARGET = upload +OPT = g -g + +CFLAGS = -Wall -Wextra -std=c++20 -O$(OPT) -flto -Iinclude -Ilibs/serial/include -Ilibs/CLI11/include +LDFLAGS = -Wall -Wextra -O$(OPT) -flto + +SRC = \ +src/main.cpp \ +libs/serial/src/serial.cc \ +libs/serial/src/impl/unix.cc \ +libs/serial/src/impl/list_ports/list_ports_linux.cc \ + +OBJ_1 = $(notdir $(SRC:.cpp=.o)) +OBJ_2 = $(notdir $(OBJ_1:.cc=.o)) +OBJ = $(addprefix $(BUILD)/, $(OBJ_2)) +# We can't use this as it will use the wrong main +# vpath %.c $(sort $(dir $(SRC))) +vpath %.cpp $(dir $(SRC)) +vpath %.cc $(dir $(SRC)) + +.PHONY: all clean + +all: $(BUILD) $(BUILD)/$(TARGET) + +$(BUILD)/%.o: %.cpp Makefile | $(BUILD) + $(CXX) -c $(CFLAGS) $< -o $@ + +$(BUILD)/%.o: %.cc Makefile | $(BUILD) + $(CXX) -c $(CFLAGS) $< -o $@ + +$(BUILD)/$(TARGET): $(OBJ) Makefile + $(CXX) $(LDFLAGS) $(OBJ) -o $@ + +$(BUILD): + mkdir $@ + +clean: + rm -fr $(BUILD) diff --git a/libs/CLI11 b/libs/CLI11 new file mode 160000 index 0000000..f862849 --- /dev/null +++ b/libs/CLI11 @@ -0,0 +1 @@ +Subproject commit f862849488557eeee0397814a47449ecfdae0383 diff --git a/libs/serial b/libs/serial new file mode 160000 index 0000000..cbcca7c --- /dev/null +++ b/libs/serial @@ -0,0 +1 @@ +Subproject commit cbcca7c83745fedd75afb7a0a27ee5c4112435c2 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..af79e00 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CLI/App.hpp" +#include "CLI/Formatter.hpp" +#include "CLI/Config.hpp" + +#include "serial/serial.h" + +void enumerate_port() { + std::vector devices_found = serial::list_ports(); + + auto iter = devices_found.begin(); + + while (iter != devices_found.end()) { + serial::PortInfo device = *iter++; + + printf("(%s, %s, %s)\n", device.port.c_str(), device.description.c_str(), device.hardware_id.c_str()); + } +} + +// @todo Template length +template +T read_length(std::unique_ptr& ser) { + uint8_t r[sizeof(T)]; + if (ser->read(r, sizeof(r)) != sizeof(r)) { + throw std::runtime_error("Failed to read bytes"); + } + + T value = 0; + for (size_t i = 0; i < sizeof(T); ++i) { + value += r[i] << (8*i); + } + + return value; +} + +void wait_for_ack(std::unique_ptr& ser) { + uint8_t r; + if (ser->read(&r, 1) != 1) { + throw std::runtime_error("Failed to read byte"); + } + + if (r == 0xFE) { + throw std::runtime_error("Received nack"); + } else if (r != 0x01) { + std::cout << "Response: " << r << ", " << std::hex << (uint32_t)r << '\n'; + throw std::runtime_error("Invalid response"); + } +} + +void write_command(std::unique_ptr& ser, uint8_t c) { + if (ser->write(&c, 1) != 1) { + throw std::runtime_error("Failed to write command"); + } + + wait_for_ack(ser); +} + +template +void write_length(std::unique_ptr& ser, T length) { + uint8_t s[sizeof(T)]; + + for (size_t i = 0; i < sizeof(T); ++i) { + s[i] = (length >> (8*i)) & 0xFF; + } + + if (ser->write(s, sizeof(s)) != sizeof(s)) { + throw std::runtime_error("Failed to write length"); + } + + if (length != read_length(ser)) { + throw std::runtime_error("Received back wrong length"); + } +} + +void write_data(std::unique_ptr& ser, uint8_t* data, uint16_t length) { + if (ser->write(data, length) != length) { + throw std::runtime_error("Failed to write data"); + } + + // @todo: As a response we need to send the CRC of the data back and compare it + + wait_for_ack(ser); +} + +void read_data(std::unique_ptr& ser, uint8_t* data, uint16_t length) { + if (ser->read(data, length) != length) { + throw std::runtime_error("Failed to read data"); + } +} + +// @todo We need to check if we are already in the bootloader +void program_stm32(std::unique_ptr& ser, std::string filename) { + write_command(ser, 0xFF); + write_command(ser, 'b'); + + // @todo Can we somehow check if the device is present + std::cout << "Waiting for device to reboot...\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // @todo We need some sort of dfu library because this is terrible + std::string cmd = "dfu-util -a 0 -D " + filename + " -s 0x08000000:leave"; + std::cout << "Running command: " << cmd << '\n'; + system(cmd.c_str()); +} + +// @todo Send over base address +void program_rom(std::unique_ptr& ser, std::string filename, uint16_t address) { + // Get the file size + std::ifstream file(filename, std::ios::in | std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Failed to open the file"); + } + file.seekg(0, std::ios::end); + uint16_t length = file.tellg(); + file.seekg(0, std::ios::beg); + + write_command(ser, 0xFF); + write_command(ser, 'u'); + // We are targeting ROM + write_command(ser, 0x01); + + std::cout << "Uploading data... (size: " << length << ")\n"; + + // Send length of data + write_length(ser, length); + + // Send actual data + uint8_t* data = new uint8_t[length]; + file.read(reinterpret_cast(data), length); + write_data(ser, data, length); + delete[] data; + + // Track progress + std::cout << "Programming... (base address: 0x" << std::hex << address << std::dec << ")\n"; + for (uint16_t progress = 0; progress != length; progress = read_length(ser)) { + std::cout << "Progress: " << progress << '/' << length << '\r'; + } + std::cout << '\n'; + + // Wait for otherside to signal we are done + wait_for_ack(ser); + std::cout << "Done!\n"; +} + +// @todo This shares a lot of code with ROM upload +void program_i2c(std::unique_ptr& ser, std::string filename, uint8_t address) { + // Get the file size + std::ifstream file(filename, std::ios::in | std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Failed to open the file"); + } + file.seekg(0, std::ios::end); + uint16_t length = file.tellg(); + file.seekg(0, std::ios::beg); + + write_command(ser, 0xFF); + write_command(ser, 'u'); + // We are targeting I2C + write_command(ser, 0x02); + + std::cout << "Uploading data... (size: " << length << ")\n"; + + // Send length of data + write_length(ser, length); + + // Send actual data + uint8_t* data = new uint8_t[length]; + file.read(reinterpret_cast(data), length); + write_data(ser, data, length); + delete[] data; + + // Send the I2C address + write_length(ser, address); + + // Checking that the device is present + wait_for_ack(ser); + + std::cout << "Restarting target to bootloader... (target: 0x" << std::hex << address << std::dec << ")\n"; + + wait_for_ack(ser); + + char version_string[16] = {0}; + read_data(ser, reinterpret_cast(version_string), sizeof(version_string)); + std::cout << "Bootloader version: " << version_string << '\n'; + + // Track progress + std::cout << "Programming...\n"; + for (uint16_t progress = 0; progress != length; progress = read_length(ser)) { + std::cout << "Progress: " << progress << '/' << length << '\r'; + } + std::cout << '\n'; + + // Wait for otherside to signal we are done + wait_for_ack(ser); + std::cout << "Done!\n"; +} + +int main(int argc, const char** argv) { + CLI::App app("Upload utility for Z80 computer"); + app.require_subcommand(1); + + std::string filename; + std::string device; + uint16_t address; + std::unique_ptr ser = nullptr; + + app.add_option("filename", filename, "File to upload")->required(); + app.add_option("--dev,-d", device, "Serial device to use")->default_val("/dev/ttyUSB0"); + + app.add_subcommand("stm", "Program STM32 mcu")->callback([&] { + program_stm32(ser, filename); + }); + + CLI::App* rom = app.add_subcommand("rom", "Program ROM/RAM")->callback([&] { + program_rom(ser, filename, address); + }); + rom->add_option("--address,-a", address, "Base address")->default_val(0x0000); + + CLI::App* i2c = app.add_subcommand("i2c", "Program I2C bootloader")->callback([&] { + program_i2c(ser, filename, address); + }); + i2c->add_option("--address,-a", address, "I2C address")->default_val(0x29); + + // @todo Implement upload for bload + // CLI::App* bload = app.add_subcommand("bload", "Upload for bload"); + + app.parse_complete_callback([&] { + std::cout << "Initializing serial\n"; + ser = std::make_unique("/dev/ttyUSB0", 115200, serial::Timeout::simpleTimeout(1000)); + + if (!ser->isOpen()) { + throw std::runtime_error("Port is not open"); + } + + ser->flushInput(); + ser->flushOutput(); + }); + + CLI11_PARSE(app, argc, argv); +}