summaryrefslogtreecommitdiff
path: root/upload.c
diff options
context:
space:
mode:
authorThomas Albers <thomas@thomaslabs.org>2023-05-02 08:07:05 +0200
committerThomas Albers <thomas@thomaslabs.org>2023-05-02 08:07:05 +0200
commit2055b0c662b81d353c96e4fc474c2fe1a79cd953 (patch)
treed2df3cf7c0056c82f6b2718e5d6a6ce7aa7d35d3 /upload.c
Initial commit
Diffstat (limited to 'upload.c')
-rw-r--r--upload.c315
1 files changed, 315 insertions, 0 deletions
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);
+}