summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Albers <thomas@thomaslabs.org>2023-08-06 10:01:18 +0200
committerThomas Albers <thomas@thomaslabs.org>2023-08-06 10:01:18 +0200
commitb50e1f74202e18ea10f1f2cd46e9e02a9d4f0e65 (patch)
tree3b408615595bf82c2ac4f6ec62009ab2f05a6bd5
parent56157d2cc9385e5df65ba310abef2873429c8af5 (diff)
Rewrite for new serial protocol
-rw-r--r--.gitignore1
-rw-r--r--Makefile16
-rw-r--r--README.org27
-rw-r--r--cmd.c201
-rw-r--r--crc16.c48
-rw-r--r--manifest.scm5
-rw-r--r--repl.c263
-rw-r--r--serial.c394
-rw-r--r--upload.c315
-rw-r--r--zup.c244
-rw-r--r--zup.h128
11 files changed, 1324 insertions, 318 deletions
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 <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;
+}
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 <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"))
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 <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);
-}
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 <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, &param);
+ verbose = param.verbose;
+
+ int fd = open_tty(param.port, param.baud);
+
+ if (ind == argc || param.repl)
+ repl(fd, &param);
+ else
+ suc = run_commands(fd, &param, 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 <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