diff options
author | Thomas Albers <thomas@thomaslabs.org> | 2023-08-06 10:01:18 +0200 |
---|---|---|
committer | Thomas Albers <thomas@thomaslabs.org> | 2023-08-06 10:01:18 +0200 |
commit | b50e1f74202e18ea10f1f2cd46e9e02a9d4f0e65 (patch) | |
tree | 3b408615595bf82c2ac4f6ec62009ab2f05a6bd5 | |
parent | 56157d2cc9385e5df65ba310abef2873429c8af5 (diff) |
Rewrite for new serial protocol
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | README.org | 27 | ||||
-rw-r--r-- | cmd.c | 201 | ||||
-rw-r--r-- | crc16.c | 48 | ||||
-rw-r--r-- | manifest.scm | 5 | ||||
-rw-r--r-- | repl.c | 263 | ||||
-rw-r--r-- | serial.c | 394 | ||||
-rw-r--r-- | upload.c | 315 | ||||
-rw-r--r-- | zup.c | 244 | ||||
-rw-r--r-- | zup.h | 128 |
11 files changed, 1324 insertions, 318 deletions
@@ -1 +1,2 @@ zup +*.o @@ -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 @@ -0,0 +1,201 @@ +#include <zup.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +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; +} @@ -0,0 +1,48 @@ +#include <zup.h> + +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")) @@ -0,0 +1,263 @@ +#include <zup.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <errno.h> + +#ifdef USE_READLINE +#include <readline/readline.h> +#include <readline/history.h> +#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 <bank> <address> <length> [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 <zup.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <fcntl.h> +#include <unistd.h> + + +// 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 <stdio.h> -#include <stdlib.h> -#include <stddef.h> -#include <string.h> -#include <stdarg.h> -#include <time.h> - -#include <getopt.h> -#include <termios.h> -#include <unistd.h> -#include <fcntl.h> -#include <search.h> -#include <errno.h> - -#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); -} @@ -0,0 +1,244 @@ +#include <zup.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <getopt.h> +#include <unistd.h> + +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: <https://thomaslabs.org>"; + 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); +} @@ -0,0 +1,128 @@ +#ifndef ZUP_H +#define ZUP_H + +#include <stdio.h> +#include <stdarg.h> +#include <stdint.h> +#include <stddef.h> +#include <assert.h> + +#include <termios.h> + +#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 |