diff options
author | Thomas Albers <thomas@thomaslabs.org> | 2023-05-02 08:07:05 +0200 |
---|---|---|
committer | Thomas Albers <thomas@thomaslabs.org> | 2023-05-02 08:07:05 +0200 |
commit | 2055b0c662b81d353c96e4fc474c2fe1a79cd953 (patch) | |
tree | d2df3cf7c0056c82f6b2718e5d6a6ce7aa7d35d3 |
Initial commit
-rw-r--r-- | .gitignore | 0 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | upload.c | 315 | ||||
-rwxr-xr-x | z80up | bin | 0 -> 17680 bytes |
4 files changed, 331 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/.gitignore diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1ddf091 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +TARGET = zup +PREFIX = /usr + +$(TARGET) : upload.c + gcc -o $@ $< + +all : $(TARGET) + +.PHONY : install +install : + mkdir -p $(PREFIX)/bin + install $(TARGET) $(PREFIX)/bin/$(TARGET) + +.PHONY : clean +clean : + rm $(TARGET) diff --git a/upload.c b/upload.c new file mode 100644 index 0000000..36434ac --- /dev/null +++ b/upload.c @@ -0,0 +1,315 @@ +#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); +} Binary files differ |