From b50e1f74202e18ea10f1f2cd46e9e02a9d4f0e65 Mon Sep 17 00:00:00 2001 From: Thomas Albers Date: Sun, 6 Aug 2023 10:01:18 +0200 Subject: Rewrite for new serial protocol --- .gitignore | 1 + Makefile | 16 ++- README.org | 27 ++++ cmd.c | 201 ++++++++++++++++++++++++++++++ crc16.c | 48 ++++++++ manifest.scm | 5 + repl.c | 263 +++++++++++++++++++++++++++++++++++++++ serial.c | 394 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ upload.c | 315 ----------------------------------------------- zup.c | 244 ++++++++++++++++++++++++++++++++++++ zup.h | 128 +++++++++++++++++++ 11 files changed, 1324 insertions(+), 318 deletions(-) create mode 100644 README.org create mode 100644 cmd.c create mode 100644 crc16.c create mode 100644 manifest.scm create mode 100644 repl.c create mode 100644 serial.c delete mode 100644 upload.c create mode 100644 zup.c create mode 100644 zup.h diff --git a/.gitignore b/.gitignore index fe51d38..df28c59 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ zup +*.o diff --git a/Makefile b/Makefile index 1ddf091..8e1c0dd 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,18 @@ TARGET = zup PREFIX = /usr +SRC = crc16.c serial.c repl.c cmd.c zup.c +OBJ = $(SRC:%.c=%.o) -$(TARGET) : upload.c - gcc -o $@ $< +CFLAGS = -Werror -Wpedantic -I. -g -DUSE_READLINE +LDFLAGS = -lreadline + +$(TARGET) : zup.h $(OBJ) + @echo ' (LD)' $(OBJ) + @gcc $(LDFLAGS) -o $@ $(OBJ) + +%.o : %.c zup.h + @echo ' (CC)' $< + @gcc $(CFLAGS) -c -o $@ $< all : $(TARGET) @@ -13,4 +23,4 @@ install : .PHONY : clean clean : - rm $(TARGET) + rm $(TARGET) $(OBJ) diff --git a/README.org b/README.org new file mode 100644 index 0000000..7b439ef --- /dev/null +++ b/README.org @@ -0,0 +1,27 @@ +#+title: zup + +zup is a CLI program for reading and writing from and to memory addresses of a +computer running zbootloader. While designed for my Z80 based computer, one +should be able to be adapt it to other platforms + +* Design goals +- Stable communication: The serial port of my Z80 computer is known to have + some random bit flips. As such, my tools are designed with error correction. + Right now Hamming-(7,4) codes are used for each byte plus a crc16 checksum at + the end of each unit. +- Speed: Error corrections were also needed to speed up communications + without fear of errors. + +* Dependencies +- [ ] (optional) GNU Readline + +* Features yet to be implemented +- [ ] ping +- [ ] info +- [ ] io_read and io_write + +* Wishlist +- [ ] read intel hex files + +* Known bugs +- [ ] write command can only handle up to ~8 byte sized packets diff --git a/cmd.c b/cmd.c new file mode 100644 index 0000000..c4a7d20 --- /dev/null +++ b/cmd.c @@ -0,0 +1,201 @@ +#include + +#include +#include +#include +#include + +typedef int (*cmd_command)(int, const struct param*, int, char**); + +static int +cmd_boot(int fd, const struct param *param, int argc, char **args) +{ + int err = 0; + uint8_t bank; + uint16_t address; + + if (argc != 2) { + fputs("BOOT expects 2 arguments\n", stderr); + return 1; + } + + errno = 0; + bank = strtoul(args[0], NULL, 16); + if (errno) + goto error; + + address = strtoul(args[1], NULL, 16); + if (errno) + goto error; + + return z_boot(fd, bank, address); + +error: + perror("BOOT: Invalid arguments\n"); + return -1; +} + +static int +cmd_read(int fd, const struct param *param, int argc, char **args) +{ + int err = 0; + + uint8_t bank; + uint16_t address; + uint16_t length; + const char *pathname; + + if (!((argc == 4 && !param->human_readable) + || (argc == 3 && param->human_readable))) { + fputs("READ expects 4 arguments\n", stderr); + return 1; + } + + errno = 0; + bank = strtoul(args[0], NULL, 16); + err |= errno; + + errno = 0; + address = strtoul(args[1], NULL, 16); + err |= errno; + + errno = 0; + length = strtoul(args[2], NULL, 16); + err |= errno; + + pathname = args[3]; + + if (err) { + fputs("READ: Invalid arguments\n", stderr); + return -1; + } + + uint8_t buf[MAX_PACKET_SIZE]; + if ((err = z_read(fd, bank, address, length, buf))) + return err; + + if (param->human_readable) { + hexdump(address, length, buf); + } else { + FILE *file; + if (!(file = fopen(pathname, "w"))) { + perror("File could not be opened"); + return -1; + } + fwrite(buf, sizeof(uint8_t), length, file); + fclose(file); + } + + return 0; +} + +// TODO: Handle length 0 at protcol level +static int +cmd_write(int fd, const struct param *param, int argc, char **args) +{ + int err = 0; + + uint8_t bank; + uint16_t address; + uint16_t length; + const char *pathname; + + if (argc != 3) { + fputs("WRITE expects 3 arguments\n", stderr); + return -1; + } + + errno = 0; + bank = strtoul(args[0], NULL, 16); + err |= errno; + + errno = 0; + address = strtoul(args[1], NULL, 16); + err |= errno; + + pathname = args[2]; + + if (err) { + fputs("WRITE: Invalid arguments\n", stderr); + return -1; + } + + uint8_t buf[MAX_PACKET_SIZE]; + FILE *fp; + + if (!(fp = fopen(pathname, "r"))) { + perror("File could not be opened"); + return -1; + } + + // Split file into packets + while ((length = fread(buf, sizeof(uint8_t), 8, fp))) { + if ((err = z_write(fd, bank, address, length, buf))) + break; + address += length; + } + + fclose(fp); + + return err; +} + +static int +cmd_io_read(int fd, const struct param *param, int argc, char **args) +{ + return 0; +} + +static int +cmd_io_write(int fd, const struct param *param, int argc, char **args) +{ + return 0; +} + +int +run_commands(int fd, const struct param *param, int ncmd, char **cmds) +{ + struct command { + const char *name; + cmd_command fptr; + }; + + const struct command cmd_table[] = { + {"b", cmd_boot}, + {"r", cmd_read}, + {"w", cmd_write}, + {"i", cmd_io_read}, + {"o", cmd_io_write} + }; + + for (int i = 0; i < ncmd; ++i) { + int err; + int argc = 1; + char *args[MAX_ARGS] = { NULL }; + + // Parse command + args[0] = strtok(cmds[i], ":"); + while(argc < MAX_ARGS && (args[argc] = strtok(NULL, ":"))) + ++argc; + + // Match action + int cmdi; + for (cmdi = 0; cmdi < LEN(cmd_table); ++cmdi) { + if (!strcmp(args[0], cmd_table[cmdi].name)) + break; + } + + // Call action + if (cmdi == LEN(cmd_table)) { + fprintf(stderr, "Unknown command %s\n", args[0]); + return 1; + } + + if ((err = cmd_table[cmdi].fptr(fd, param, argc - 1, &args[1]))) { + print_error(err); + return err; + } + } + + return 0; +} diff --git a/crc16.c b/crc16.c new file mode 100644 index 0000000..b271f98 --- /dev/null +++ b/crc16.c @@ -0,0 +1,48 @@ +#include + +static const uint16_t crc_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 +}; + +uint16_t +crc16(const void *buf, size_t len) +{ + const uint8_t *p = buf; + + uint16_t crc = 0; + while (len--) + crc = crc_table[(crc >> 8) ^ (*p++)] ^ (crc << 8); + + return crc; +} diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 0000000..c22be4b --- /dev/null +++ b/manifest.scm @@ -0,0 +1,5 @@ +(specifications->manifest + '("make" + "gcc-toolchain" + "gdb" + "readline")) diff --git a/repl.c b/repl.c new file mode 100644 index 0000000..32f2bee --- /dev/null +++ b/repl.c @@ -0,0 +1,263 @@ +#include + +#include +#include +#include +#include +#include + +#ifdef USE_READLINE +#include +#include +#endif // USE_READLINE + +typedef int (*repl_command)(int, const struct param*, int, char**); + +static int +repl_read(int fd, const struct param *param, int argc, char **args) +{ + int err = 0; + uint8_t buf[MAX_PACKET_SIZE]; + + uint8_t bank; + uint16_t address; + uint16_t length; + const char *pathname = NULL; + + if (argc != 3 && argc != 4) + goto syntax_error; + + errno = 0; + bank = strtoul(args[0], NULL, 16); + err |= errno; + + errno = 0; + address = strtoul(args[1], NULL, 16); + err |= errno; + + errno = 0; + length = strtoul(args[2], NULL, 16); + err |= errno; + + if (argc == 4) + pathname = args[3]; + + if (err) + goto syntax_error; + + if ((err = z_read(fd, bank, address, length, buf))) { + print_error(err); + return err; + } + + hexdump(address, length, buf); + + if (pathname) { + FILE *file; + if (!(file = fopen(pathname, "w"))) { + perror("File could not be opened"); + return -1; + } + fwrite(buf, sizeof(uint8_t), length, file); + fclose(file); + } + + return err; + +syntax_error: + fputs("Usage: read
[pathname]\n", stderr); + return 1; +} + +/* +static int +parse_args(const char *fname, const char *types, int argc, char **args, ...) +{ +} +*/ + +static int +repl_write(int fd, const struct param *param, int argc, char **args) +{ + int err; + char line[512]; + uint8_t buf[MAX_PACKET_SIZE]; + + char *p; + + uint8_t bank; + uint16_t address; + uint16_t length = 0; + + do { + // TODO: Handle fgets error + fgets(line, LEN(line), stdin); + if (line && !strcmp("\n", line)) + break; + + p = strtok(line, " \n"); + while (p && length < 10) { + errno = 0; + buf[length++] = strtoul(p, NULL, 16); + p = strtok(NULL, " \n"); + } + + if (p) { + fputs("WRITE: Input can't be longer than 256 bytes\n", stderr); + return -1; + } + } while (1); + + if ((err = z_write(fd, bank, address, length, buf))) { + print_error(err); + return err; + } + + return 0; +} + +static int +repl_echo(int fd, const struct param *param, int argc, char **args) +{ + int err; + char line[512]; + char buf[MAX_PACKET_SIZE]; + + memset(line, 0, sizeof(line)); + memset(buf, 0, sizeof(buf)); + + size_t length = 0; + + do { + // TODO: Handle fgets error + fgets(line, LEN(line), stdin); + if (line && !strcmp("\n", line)) + break; + + size_t len = strlen(line); + if (line[len - 1] == '\n') + line[--len] = '\0'; + + if (length + len >= MAX_PACKET_SIZE) { + fputs("ECHO: Input can't be longer than 256 bytes\n", stderr); + return -1; + } + + strcat(buf, line); + length += len; + } while (1); + + if ((err = z_echo(fd, length, (uint8_t *)buf))) { + print_error(err); + return err; + } + + fwrite(buf, sizeof(uint8_t), length, stdout); + putchar('\n'); + fflush(stdout); + + return 0; +} + +static int +repl_io_read(int fd, const struct param *param, int argc, char **args) +{ + return 0; +} + +static int +repl_io_write(int fd, const struct param *param, int argc, char **args) +{ + return 0; +} + +/* +static volatile sig_atomic_t quit = 0; + +void +interrupt_handler(int signal) +{ + puts("Bye!"); + quit = 1; + // fclose(stdin); +} +*/ + +// TODO: Handle signals (CTRL-C) +// TODO: Print errors when in repl and crash if from terminal +void +repl(int fd, struct param *param) +{ + struct command { + const char *name; + repl_command fptr; + int quit; + }; + + const struct command cmd_table[] = { + {"read", repl_read, 0}, + {"write", repl_write, 0}, + {"echo", repl_echo, 0}, + {"io_read", repl_io_read, 0}, + {"io_write", repl_io_write, 0}, + {"quit", NULL, 1} + }; + + int quit = 0; + +#ifdef USE_READLINE + using_history(); +#endif + + while (!quit) { +#ifdef USE_READLINE + char *line = readline("* "); + + if (!line) + break; + + add_history(line); +#else + size_t len = 0; + char line[512]; + fputs("* ", stdout); + + if (!fgets(line, sizeof(line), stdin)) + break; + + len = strlen(line); + if (line[len - 1] == '\n') + line[len - 1] = '\0'; +#endif + int argc = 1; + char *args[MAX_ARGS] = { NULL }; + + // Parse command + args[0] = strtok(line, " "); + if (!args[0]) + goto skip; + while(argc < MAX_ARGS && (args[argc] = strtok(NULL, " "))) + ++argc; + + // Match action + int cmdi; + for (cmdi = 0; cmdi < LEN(cmd_table); ++cmdi) { + if (!strcmp(args[0], cmd_table[cmdi].name)) + break; + } + + // Call action + if (cmdi == LEN(cmd_table)) { + fprintf(stdout, "Unknown command %s\n", args[0]); + } else { + repl_command fptr = cmd_table[cmdi].fptr; + if (fptr) + fptr(fd, param, argc - 1, &args[1]); + quit = cmd_table[cmdi].quit; + } + skip: +#ifdef USE_READLINE + free(line); +#endif + } +} diff --git a/serial.c b/serial.c new file mode 100644 index 0000000..f6270ba --- /dev/null +++ b/serial.c @@ -0,0 +1,394 @@ +#include + +#include +#include +#include +#include + +#include +#include + + +// Hamming(7,4) encoding +static unsigned char +encode(unsigned char x) +{ + unsigned char y = 0; + const unsigned char c[4] = {0x61, 0x52, 0x34, 0x78}; + + for (int i = 0; i < 4; ++i) + y ^= ((x >> i) & 1) * c[i]; + + return y; +} + +// Hamming(7,4) decoding +static unsigned char +decode(unsigned char x) +{ + unsigned char p = 0; + const unsigned char r[7] = {6, 5, 3, 7, 1, 2, 4}; + + for (int i = 0; i < 7; ++i) + p ^= ((x >> i) & 1) * r[i]; + + // Attempt correcting simple error + if (p) { + size_t n = 0; + for (size_t i = 0; i < n; ++i) { + if (r[i] == x) + n = i; + } + x ^= (1 << n); + } + + return x & 0x0F; +} + +static void +encode_buf(size_t slen, const void *_src, size_t dlen, void *_dest) +{ + const uint8_t *src = _src; + uint8_t *dest = _dest; + + for (size_t i = 0; i < slen && i * 2 < dlen; ++i) { + dest[2 * i] = encode(src[i] & 0x0F); + dest[2 * i + 1] = encode((src[i] >> 4) & 0x0F); + } +} + +static void +decode_buf(size_t slen, const void *_src, size_t dlen, void *_dest) +{ + const uint8_t *src = _src; + uint8_t *dest = _dest; + + for (size_t i = 0; i < dlen && i * 2 < slen; ++i) { + dest[i] = decode(src[2 * i]); + dest[i] |= (decode(src[2 * i + 1]) << 4); + } +} + +// Ensure count bytes are read unless read times out MAX_ATTEMPTS times +static int +e_read(int fd, void *buf, size_t count) +{ + ssize_t ret; + ssize_t nread = 0; + uint8_t *p = buf; + + for (int n = 0; n < MAX_ATTEMPTS && nread < count; ++n) { + while ((ret = read(fd, &p[nread], count - nread)) != 0) { + if (ret < 0) + goto error; + else + nread += ret; + } + } + + return (count == nread) ? 0 : ERR_TIMEOUT; +error: + perror("e_read failed"); + return -1; +} + +// Ensure count bytes are written +static int +e_write(int fd, const void *buf, size_t count) +{ + ssize_t ret = 0; + ssize_t nwrite = 0; + const uint8_t *p = buf; + + while (nwrite < count) { + ret = write(fd, &p[nwrite], count - nwrite); + if (ret < 0) + goto error; + nwrite += ret; + } + + return 0; +error: + perror("e_write failed!"); + return ERR_WRITE; +} + +static int +read_ack(int fd) +{ + int err; + uint8_t ack; + uint8_t buf[2]; + if ((err = e_read(fd, buf, sizeof(buf)))) { + msg(1, "read_ack failed\n"); + return err; + } + decode_buf(sizeof(buf), buf, sizeof(ack), &ack); + msg(1, "read_ack success\n"); + return ack; +} + +static int +write_ack(int fd, uint8_t ack) +{ + int err; + uint8_t buf[2]; + encode_buf(sizeof(ack), &ack, sizeof(buf), buf); + if ((err = e_write(fd, buf, sizeof(buf)))) { + msg(1, "write_ack failed\n"); + return err; + } + tcdrain(fd); + msg(1, "write_ack success\n"); + return 0; +} + +static int +write_header(int fd, const struct header *header) +{ + int err; + uint8_t buf[2 * sizeof(*header)]; + encode_buf(sizeof(*header), header, sizeof(buf), buf); + + msg(1, "write_header\n"); + + for (int i = 0; i < MAX_TRANS_ATTEMPTS; ++i) { + msg(1, " attempt [%d/%d]\n", i + 1, MAX_TRANS_ATTEMPTS); + + if ((err = e_write(fd, buf, sizeof(buf)))) + goto error; + tcdrain(fd); + + err = read_ack(fd); + + if (err == ACK) { + msg(1, " write_header success\n"); + return 0; + } + + if (err != NACK) + goto error; + } + + err = ERR_NACK; + +error: + msg(1, " write_header failed\n"); + return err; +} + +static int +read_buf(int fd, size_t len, void *_buf) +{ + int err; + uint8_t *buf = _buf; + uint8_t ebuf[2 * MAX_PACKET_SIZE]; + + uint16_t checksum = 0; + uint8_t echecksum[4]; + + msg(1, "read_buf\n"); + + for (int i = 0; i < MAX_TRANS_ATTEMPTS; ++i) { + msg(1, " attempt [%d/%d]\n", i + 1, MAX_TRANS_ATTEMPTS); + + if ((err = e_read(fd, echecksum, sizeof(echecksum))) + || (err = e_read(fd, ebuf, 2 * len))) + goto error; + + decode_buf(sizeof(echecksum), echecksum, sizeof(checksum), &checksum); + decode_buf(2 * len, ebuf, len, buf); + + if (checksum == crc16(buf, len)) { + write_ack(fd, ACK); + msg(1, " read_buf success\n"); + return 0; + } else { + write_ack(fd, NACK); + } + } + + err = ERR_NACK; + +error: + msg(1, " read_buf failed\n"); + return err; +} + +static int +write_buf(int fd, size_t len, const void *_buf) +{ + int err; + const uint8_t *buf = _buf; + uint8_t ebuf[2 * MAX_PACKET_SIZE]; + + uint16_t checksum = crc16(buf, len); + uint8_t echecksum[4]; + + encode_buf(sizeof(checksum), &checksum, LEN(echecksum), echecksum); + encode_buf(len, buf, sizeof(ebuf), ebuf); + + msg(1, "write_buf\n"); + + int ack; + for (int i = 0; i < MAX_TRANS_ATTEMPTS; ++i) { + msg(1, " attempt [%d/%d]\n", i + 1, MAX_TRANS_ATTEMPTS); + + if ((err = e_write(fd, echecksum, LEN(echecksum))) + || (err = e_write(fd, ebuf, 2 * len))) + goto error; + + tcdrain(fd); + + err = read_ack(fd); + + if (err == ACK) { + msg(1, " write_buf success\n"); + return 0; + } + + if (err != NACK) + goto error; + } + +error: + msg(1, " write_buf failed\n"); + return ack < 0 ? ack : ERR_NACK; +} + +// Sends a boot command +// returns 0 on success +int +z_boot(int fd, uint8_t bank, uint16_t address) +{ + struct header header = { + .type = CMD_BOOT, + .bank = bank, + .address = address, + .length = 0, + .checksum = 0 + }; + + header.checksum = crc16(&header, sizeof(header)); + + int err; + tcflush(fd, TCIOFLUSH); + if ((err = write_header(fd, &header))) + tcflush(fd, TCIOFLUSH); + + return err; +} + +// Sends a single CMD_READ header and reads its response +// Does no splitting +// returns 0 on success +int +z_read(int fd, uint8_t bank, uint16_t address, uint16_t length, uint8_t *buf) +{ + assert(length <= MAX_PACKET_SIZE); + + struct header header = { + .type = CMD_READ, + .bank = bank, + .address = address, + .length = length, + .checksum = 0 + }; + + header.checksum = crc16(&header, sizeof(header)); + + int err; + tcflush(fd, TCIOFLUSH); + if ((err = write_header(fd, &header)) + || (err = read_buf(fd, length, buf))) { + tcflush(fd, TCIOFLUSH); + return err; + } + + return 0; +} + +// Sends a single CMD_WRITE header and packet +// Does no splitting +// returns 0 on success +int +z_write(int fd, uint8_t bank, uint16_t address, uint16_t length, + const uint8_t *buf) +{ + assert(length <= MAX_PACKET_SIZE); + + struct header header = { + .type = CMD_WRITE, + .bank = bank, + .address = address, + .length = length, + .checksum = 0 + }; + + header.checksum = crc16(&header, sizeof(header)); + + int err; + tcflush(fd, TCIOFLUSH); + if ((err = write_header(fd, &header)) + || (err = write_buf(fd, length, buf))) { + tcflush(fd, TCIOFLUSH); + return err; + } + + return 0; +} + +// Sends a single CMD_ECHO header, packet and reads the response +// Does no splitting +// returns 0 on success +int +z_echo(int fd, uint16_t length, uint8_t *buf) +{ + assert(length <= MAX_PACKET_SIZE); + + struct header header = { + .type = CMD_ECHO, + .bank = 0, + .address = 0, + .length = length, + .checksum = 0 + }; + + header.checksum = crc16(&header, sizeof(header)); + + int err = 0; + tcflush(fd, TCIOFLUSH); + if ((err = write_header(fd, &header)) + || (err = write_buf(fd, length, buf)) + || (err = read_buf(fd, length, buf))) { + tcflush(fd, TCIOFLUSH); + return err; + } + + return 0; +} + +int +open_tty(const char *port, int baud) +{ + int fd = open(port, O_RDWR); + + if (fd < 0) { + fprintf(stderr, "Error: File '%s' could not be opened\n", port); + exit(EXIT_FAILURE); + } + + struct termios term; + tcgetattr(fd, &term); + + cfmakeraw(&term); + // read with 1s timeout + term.c_cc[VTIME] = 5; + term.c_cc[VMIN] = 0; + cfsetspeed(&term, baud); + + tcsetattr(fd, TCSANOW, &term); + tcflush(fd, TCIOFLUSH); + + return fd; +} diff --git a/upload.c b/upload.c deleted file mode 100644 index 36434ac..0000000 --- a/upload.c +++ /dev/null @@ -1,315 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#define PACKET_SIZE 8 - -#define LEN(x) (sizeof(x) / sizeof(x[0])) - -typedef unsigned char uchar; -typedef unsigned short ushort; -typedef unsigned int uint; -typedef unsigned long ulong; -typedef unsigned long long ulonglong; - -enum verbose_level { - NONE, - DEBUG, - WARNING, - ERROR -}; - -// in bootloader spam \n or anything? until some buffer is full - -speed_t baud = 0; -char port[32] = {0}; -int serial_fd = -1; -FILE *file = NULL; -const char *flush = "aaaaaaaaaaaaa\n"; -int verbose = 0; - -void help() -{ - puts("help"); - exit(EXIT_SUCCESS); -} - -void die() -{ - if (file) - fclose(file); - if (serial_fd < 0) - close(serial_fd); - exit(EXIT_FAILURE); -} - -int log_msg(int lvl, const char *fmt, ...) -{ - int ret = 0; - va_list args; - if (verbose >= lvl) { - va_start(args, fmt); - ret = vprintf(fmt, args); - fflush(stdout); - va_end(args); - } - return ret; -} - -struct baud_pair { - const char *b; - speed_t c; -}; - -// Conversion table for baud rates -const struct baud_pair baud_rates[] = { - {"1200", B1200}, - {"2400", B2400}, - {"4800", B4800}, - {"9600", B9600}, - {"19200", B19200}, -}; - -int parse_argv(int argc, char *const argv[]) -{ - struct option long_opts[] = { - { "baud", required_argument, 0, 'b'}, - { "verbose", no_argument, 0, 'v'}, - { "port", required_argument, 0, 'p'}, - { "help", no_argument, 0, 'h'}, - { 0, 0, 0, 0 } - }; - - int long_index = 0; - int c; - - while ((c = getopt_long(argc, argv, "b:p:h", - long_opts, &long_index)) != -1) { - switch (c) { - case 'b': - for (int i = 0; i < LEN(baud_rates); i++) { - if (!strcmp(baud_rates[i].b, optarg)) { - baud = baud_rates[i].c; - break; - } - } - - if (baud == 0) { - fprintf(stderr, "Error: Invalid baud rate '%s'\n", optarg); - exit(EXIT_FAILURE); - } - break; - - case 'v': - verbose = 1; - break; - - case 'p': - strncpy(port, optarg, LEN(port)); - break; - - case 'h': - help(); - break; - - case '?': - help(); - break; - - default: - break; - } - } - - if (argc <= optind) { - fprintf(stderr, "Error: No file was provided to upload\n\n"); - help(); - } - - return optind; -} - -int open_tty(const char *port) -{ - int fd = open(port, O_RDWR); - - if (fd < 0) { - fprintf(stderr, "Error: Port '%s' could not be opened\n", port); - exit(EXIT_FAILURE); - } - - struct termios term; - tcgetattr(fd, &term); - - cfmakeraw(&term); - term.c_cc[VTIME] = 1; - term.c_cc[VMIN] = 0; - term.c_lflag &= ~OCRNL; - cfsetspeed(&term, baud); - - tcsetattr(fd, TCSANOW, &term); - - return fd; -} - -ssize_t read_line(int fd, uchar *buf, size_t len) -{ - int ret = 0; - ssize_t bytes_read = 0; - - // The Serial port runs at ~9600 baud. This loop, as inefficient as it is, - // won' t ever be a bottleneck - for (int i = 0; bytes_read < len; i++) { - ssize_t rd = read(fd, &buf[bytes_read], 1); - if (rd < 0) { - fprintf(stderr, "Error: read from port failed\n%s\n", - strerror(errno)); - die(); - } - - if (rd == 0) - return -1; - - bytes_read += rd; - if (buf[bytes_read - 1] == '\n') - break; - } - - buf[bytes_read] = 0; - return bytes_read; -} - -void flush_bootloader(int serial_fd) -{ - /* Clean up */ - // tcflush(serial_fd, TCIOFLUSH); - /* Send empty line to get a ready prompt from bootloader */ - write(serial_fd, flush, strlen(flush)); - tcdrain(serial_fd); -} - -/* - * Get the bootloader in a ready state, cleaning up any previous errors - */ -void begin_command(int serial_fd) -{ - char buf[32]; - int timeout = 1; - - buf[0] = 0; - - while (timeout) { - /* Comparing should stop after \n even if buf is undefined */ - while (strcmp("ready\r\n", buf) != 0) { - if (read_line(serial_fd, buf, LEN(buf)) < 0) { - timeout = 1; - log_msg(DEBUG, "timeout\n%32s\n", buf); - flush_bootloader(serial_fd); - break; - } else { - timeout = 0; - } - } - } -} - -void send_packet(int index, uint address, int plen, const uchar *packet) -{ - char obuf[128]; - int len = sprintf(obuf, "W %04X %02X ", address, plen); - - for (int i = 0; i < plen; i++) - len += sprintf(&obuf[len], "%02X", packet[i]); - - obuf[len++] = '\r'; - obuf[len++] = '\n'; - obuf[len] = '\0'; - - int cmp = 1; - char ibuf[128]; - char ebuf[128]; - - for (int attempt = 1; cmp != 0; attempt++) { - log_msg(DEBUG, "Sending packet %d, attempt %d\n", index, attempt); - - begin_command(serial_fd); - write(serial_fd, obuf, len); - - if((read_line(serial_fd, ibuf, LEN(ibuf)) == -1) - || (read_line(serial_fd, ebuf, LEN(ebuf)) == -1)) { - continue; - } - - log_msg(DEBUG, "sent: %s", obuf); - log_msg(DEBUG, "recv: %s", ibuf); - log_msg(DEBUG, "ebuf: %s", ebuf); - - cmp = strncmp(ibuf, obuf, LEN(ibuf)); - if (cmp != 0) { - usleep(100); - } - } -} - -int upload_file(FILE *file, int serial_fd) -{ - int file_size = 0; - fseek(file, 0, SEEK_END); - file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - int total = (file_size / PACKET_SIZE - + ((file_size % PACKET_SIZE) == 0 ? 0 : 1)); - - printf("Sending %d bytes\n", file_size); - printf("Progress: [ %3d%% ]", 0); - fflush(stdout); - - uint address = 0x4000; - uchar packet[PACKET_SIZE]; - - for (int i = 1; !feof(file); i++) { - size_t n = fread(packet, sizeof(uchar), LEN(packet), file); - if (n > 0) { - send_packet(i, address, n, packet); - address += n; - } - printf("\rProgress: [ %3d%% ]", 100 * i / total); - fflush(stdout); - } - return 0; -} - -int main(int argc, char *argv[]) -{ - int ind = parse_argv(argc, argv); - serial_fd = open_tty(port); - FILE *file = fopen(argv[ind], "r"); - - if (!file) { - fprintf(stderr, "Error: File '%s' could not be opened\n", argv[ind]); - close(serial_fd); - exit(EXIT_FAILURE); - } - - tcflush(serial_fd, TCIOFLUSH); - flush_bootloader(serial_fd); - - upload_file(file, serial_fd); - puts("\ndone"); - - fclose(file); - close(serial_fd); - - exit(EXIT_SUCCESS); -} diff --git a/zup.c b/zup.c new file mode 100644 index 0000000..84d1cc1 --- /dev/null +++ b/zup.c @@ -0,0 +1,244 @@ +#include + +#include +#include +#include +#include + +#include +#include + +static void +help() +{ + const char *msg = + "Usage: zup [options] command...\n" + "Send commands to a computer running zbootloader. If no commands are\n" + "provided jump into REPL instead.\n" + "\n" + "Options:\n" + " -b, --baud=BAUD use BAUD as baud rate for serial port\n" + " -v, --verbose \n" + " -V, --version \n" + " -H, --human-readable \n" + " -r, --raw \n" + " -R, --repl ignore commands and go into REPL instead\n" + " -p, --port=FILE use FILE for serial comunication\n" + " -h, --help display this text and exit\n" + "\n" + "Available commands:\n" + " r:BANK:ADDR:LEN:FILE read LEN bytes from BANK and ADDR into FILE\n" + " w:BANK:ADDR:FILE write FILE into BANK and ADDR\n" + " b:ADDR exit zbootloader and boot from ADDR\n" + "\n" + "Report bugs to: thomas@thomaslabs.org\n" + "Home page: "; + puts(msg); + exit(EXIT_SUCCESS); +} + +static void +version() +{ + const char *fmt = + "zup %s\n" + "Copyright (C) 2022 Thomas Albers Raviola\n\n"; + printf(fmt, VERSION); + exit(EXIT_SUCCESS); +} + +static speed_t +parse_baud(const char *str) +{ + struct baud_pair { + const char *b; + speed_t c; + }; + + // Conversion table for baud rates + const struct baud_pair baud_rates[] = { + {"50", B50} + {"75", B75} + {"110", B110} + {"134", B134} + {"150", B150} + {"200", B200} + {"300", B300} + {"600", B600} + {"1200", B1200} + {"1800", B1800} + {"2400", B2400} + {"4800", B4800} + {"9600", B9600} + {"19200", B19200} + {"38400", B38400} + {"57600", B57600} + {"115200", B115200} + {"230400", B230400} + {"460800", B460800} + {"500000", B500000} + {"576000", B576000} + {"921600", B921600} + {"1000000", B1000000} + {"1152000", B1152000} + {"1500000", B1500000} + {"2000000", B2000000} + }; + + for (int i = 0; i < LEN(baud_rates); i++) { + if (!strcmp(baud_rates[i].b, str)) + return baud_rates[i].c; + } + + return 0; +} + + +static int +parse_options(int argc, char *const argv[], struct param *param) +{ + const char *sopts = "b:p:hHVRrv"; + const struct option lopts[] = { + { "baud", required_argument, 0, 'b'}, + { "verbose", no_argument, 0, 'v'}, + { "version", no_argument, 0, 'V'}, + { "raw", no_argument, 0, 'r'}, + { "repl", no_argument, 0, 'R'}, + { "port", required_argument, 0, 'p'}, + { "human-readable", no_argument, 0, 'H'}, + { "help", no_argument, 0, 'h'}, + { 0, 0, 0, 0 } + }; + + int c; + int i = 0; + + while ((c = getopt_long(argc, argv, sopts, lopts, &i)) != -1) { + switch (c) { + case 'b': + if (!(param->baud = parse_baud(optarg))) { + fprintf(stderr, "Error: Invalid baud rate '%s'\n", optarg); + exit(EXIT_FAILURE); + } + break; + + case 'v': + param->verbose = 1; + break; + + case 'p': + strncpy(param->port, optarg, LEN(param->port)); + break; + + case 'R': + param->repl = 1; + break; + + case 'H': + param->human_readable = 1; + break; + + case 'r': + param->human_readable = 0; + break; + + case 'V': + version(); + break; + + case 'h': + case '?': + help(); + break; + + default: + break; + } + } + + return optind; +} + +void +print_error(int error) +{ + int i = -error - 1; + const char *msgs[] = { + "ERR_TIMEOUT", + "ERR_READ", + "ERR_WRITE", + "ERR_NACK", + "ERR_ARGS" + }; + + if (0 <= i && i < LEN(msgs)) + fprintf(stderr, "%s\n", msgs[i]); + else + fprintf(stderr, "Unknown error. Code %d\n", error); +} + +void +hexdump(size_t start_address, size_t len, const uint8_t *buf) +{ + long offset = start_address - (start_address & ~0xF); + printf(" "); + for (int i = 0; i < 16; ++i) + printf(" %01X%01X", i, i); + puts("\n------------------------------------------------------" + "|-----------------|"); + for (long i = 0; i < (len + 15) / 16; ++i) { + printf("%04X:", (start_address & ~0xF) + 16 * i); + for (long j = 0; j < 16; ++j) { + long k = 16 * i + j - offset; + if (k < 0 || k >= len) + printf(" "); + else + printf(" %02X", buf[k]); + } + printf(" | "); + for (long j = 0; j < min(16, len - 16 * i); ++j) { + long k = 16 * i + j - offset; + if (k < 0 || k >= len) { + putchar(' '); + } else { + int c = buf[16 * i + j - offset]; + putchar(isprint(c) ? c : '.'); + } + } + putchar('|'); + putchar('\n'); + } +} + +int verbose = 0; + +// TODO: Support stdin/stdout + +int +main(int argc, char *argv[]) +{ + int suc = EXIT_SUCCESS; + + // Init defaults + struct param param = { + .port = "/dev/ttyS1", + .baud = B9600, + .verbose = 0, + .human_readable = 0, + .repl = 0 + + }; + + int ind = parse_options(argc, argv, ¶m); + verbose = param.verbose; + + int fd = open_tty(param.port, param.baud); + + if (ind == argc || param.repl) + repl(fd, ¶m); + else + suc = run_commands(fd, ¶m, argc - ind, &argv[ind]); + + close(fd); + exit(EXIT_SUCCESS); +} diff --git a/zup.h b/zup.h new file mode 100644 index 0000000..cce84ed --- /dev/null +++ b/zup.h @@ -0,0 +1,128 @@ +#ifndef ZUP_H +#define ZUP_H + +#include +#include +#include +#include +#include + +#include + +#define LEN(x) (sizeof(x) / sizeof(x[0])) + +enum error { + ERR_TIMEOUT = -1, + ERR_READ = -2, + ERR_WRITE = -3, + ERR_NACK = -4, + ERR_ARGS = -5 +}; + +struct param { + char port[32]; + speed_t baud; + unsigned int repl : 1; + unsigned int human_readable : 1; + unsigned int verbose : 2; +}; + +#ifdef __GNUC__ +#define PACKED __attribute__((packed)) +#else +#define PACKED +#endif + +enum header_type { + CMD_PING, + CMD_INFO, + CMD_BOOT, + CMD_READ, + CMD_WRITE, + CMD_IO_READ, + CMD_IO_WRITE, + CMD_ECHO +}; + +struct header { + uint8_t type; + uint8_t bank; + uint16_t address; + uint16_t length; + uint16_t checksum; +} PACKED; + +static_assert(sizeof(struct header) == 8, "struct header is not PACKED"); + +enum ack { + ACK = 0x00, + NACK = 0xFF +}; + +static inline int +max(int x, int y) +{ + return x > y ? x : y; +} + +static inline int +min(int x, int y) +{ + return x > y ? y : x; +} + +extern int verbose; + +static inline void +msg(int level, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if (verbose >= level) { + fprintf(stderr, "DEBUG [%d]: ", level); + vfprintf(stderr, fmt, ap); + } + va_end(ap); +} + +uint16_t +crc16(const void *buf, size_t len); + +int +z_boot(int fd, uint8_t bank, uint16_t address); + +int +z_read(int fd, uint8_t bank, uint16_t address, uint16_t length, uint8_t *buf); + +int +z_write(int fd, uint8_t bank, uint16_t address, uint16_t length, + const uint8_t *buf); + +int +z_echo(int fd, uint16_t length, uint8_t *buf); + +int +open_tty(const char *port, int baud); + +int +run_commands(int fd, const struct param *param, int ncmd, char **cmds); + +void +repl(int fd, struct param *param); + +void +print_error(int error); + +void +hexdump(size_t start_address, size_t len, const uint8_t *buf); + +#define MAX_ARGS 5 +#define MAX_PACKET_SIZE 256 +#define MAX_ATTEMPTS 3 +#define MAX_TRANS_ATTEMPTS 5 + +#define VERSION "1.0.0" + +#define DEBUG_MODE 1 + +#endif // ZUP_H -- cgit v1.2.3