diff --git a/Makefile b/Makefile index 670bbfe..0714fe8 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,18 @@ +CFLAGS=`pkg-config --cflags --libs gtk+-3.0 gmodule-2.0` + all: mojoloader mojoloader: mojoloader.c - $(CC) -std=gnu99 -o $@ $< + $(CC) -std=c99 -o $@ $< -D _DEFAULT_SOURCE + +mojoloader_gtk: mojoloader_gtk.c + $(CC) -g -o $@ $< $(CFLAGS) clean: - $(RM) mojoloader + $(RM) mojoloader mojoloader_gtk + +.PHONY: install +install: mojoloader + install --mode=0755 --owner root --group root --dir $(DESTDIR)/bin + install --mode=0755 --owner root --group root $^ $(DESTDIR)/bin + diff --git a/mojoloader.c b/mojoloader.c index 02c1e30..b0ed3cd 100644 --- a/mojoloader.c +++ b/mojoloader.c @@ -55,10 +55,11 @@ void restart_mojo(int fd) } } -void wait_for_fd(int fd, int write) +int wait_for_fd(int fd, int is_write) { fd_set fds; struct timeval tv; + int ret; FD_ZERO(&fds); FD_SET(fd, &fds); @@ -66,16 +67,112 @@ void wait_for_fd(int fd, int write) tv.tv_sec = 30; tv.tv_usec = 0; - if (write) { - select(FD_SETSIZE, NULL, &fds, NULL, &tv); + if (is_write) { + ret = select(fd + 1, NULL, &fds, NULL, &tv); } else { - select(FD_SETSIZE, &fds, NULL, NULL, &tv); + ret = select(fd + 1, &fds, NULL, NULL, &tv); } + return (ret > 0) ? 0 : -1; +} + +/* helper to read exactly count bytes from a serial port */ +int read_exact(int fd, void *buf, size_t count) +{ + size_t total = 0; + char *ptr = (char *)buf; + while (total < count) { + if (wait_for_fd(fd, 0) < 0) { + return -1; + } + ssize_t r = read(fd, ptr + total, count - total); + if (r < 0) { + return -1; + } + if (r == 0) { + break; + } + total += r; + } + return (total == count) ? 0 : -1; +} + +/* helper to write exactly count bytes to a serial port */ +int write_exact(int fd, const void *buf, size_t count) +{ + size_t total = 0; + const char *ptr = (const char *)buf; + while (total < count) { + if (wait_for_fd(fd, 1) < 0) { + return -1; + } + ssize_t w = write(fd, ptr + total, count - total); + if (w <= 0) { + return -1; + } + total += w; + } + return 0; +} + +/* check if the file is a valid Xilinx Spartan-6 bitstream */ +int check_bitstream(const char *filepath, off_t filesize) +{ + /* check file size limits */ + if (filesize < 10000) { + fprintf(stderr, "Error: File size is too small (%lld bytes). A valid Mojo bitstream should be at least 10KB.\n", (long long)filesize); + return -1; + } + if (filesize > 500000) { + fprintf(stderr, "Error: File size is too large (%lld bytes). A valid Mojo bitstream should not exceed 500KB.\n", (long long)filesize); + return -1; + } + + int fd = open(filepath, O_RDONLY); + if (fd < 0) { + return -1; + } + + unsigned char header[1024]; + ssize_t n = read(fd, header, sizeof(header)); + close(fd); + + if (n < 4) { + fprintf(stderr, "Error: Unable to read file header.\n"); + return -1; + } + + /* scan for the xilinx sync word: 0xaa995566 or its common byte-swapped variants */ + int found = 0; + for (ssize_t i = 0; i <= n - 4; i++) { + if (header[i] == 0xAA && header[i+1] == 0x99 && header[i+2] == 0x55 && header[i+3] == 0x66) { + found = 1; + break; + } + if (header[i] == 0x66 && header[i+1] == 0x55 && header[i+2] == 0x99 && header[i+3] == 0xAA) { + found = 1; + break; + } + if (header[i] == 0x99 && header[i+1] == 0xAA && header[i+2] == 0x66 && header[i+3] == 0x55) { + found = 1; + break; + } + if (header[i] == 0x55 && header[i+1] == 0x66 && header[i+2] == 0xAA && header[i+3] == 0x99) { + found = 1; + break; + } + } + + if (!found) { + fprintf(stderr, "Error: File does not appear to be a valid Xilinx FPGA bitstream (missing sync word 0xAA995566).\n"); + return -1; + } + + return 0; } int main(int argc, char *argv[]) { - int c, fd, fd2, flash_size = 0; + int c, fd, fd2 = -1, flash_size = 0; ssize_t numRead; char *portname = NULL; char *binpath = NULL; @@ -101,8 +198,8 @@ int main(int argc, char *argv[]) verify = 1; break; case '?': - if (optopt == 'c') - fprintf (stderr, "Option -%c requires path to binary.\n", optopt); + if (optopt == 'd' || optopt == 'f') + fprintf (stderr, "Option -%c requires an argument.\n", optopt); else if (isprint (optopt)) fprintf (stderr, "Unknown option `-%c'.\n", optopt); else @@ -124,42 +221,79 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } + /* early file validation before resetting the hardware state */ + if (!clearflash) { + if (!binpath) { + fprintf(stderr, "Error: No flash file specified\n"); + return EXIT_FAILURE; + } + + fd2 = open(binpath, O_RDONLY); + if (fd2 < 0) { + fprintf(stderr, "Failed opening %s\n", binpath); + return EXIT_FAILURE; + } + + if (stat(binpath, &statbuf) == -1) { + fprintf(stderr, "Failed stat on %s\n", binpath); + close(fd2); + return EXIT_FAILURE; + } + + if (check_bitstream(binpath, statbuf.st_size) != 0) { + close(fd2); + return EXIT_FAILURE; + } + } if( (fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC)) < 0) { fprintf(stderr, "Failed opening %s\n", portname); + if (fd2 >= 0) close(fd2); return EXIT_FAILURE; } if(setup_serial(fd)) { fprintf(stderr, "Failed setting attributes of %s\n", portname); + if (fd2 >= 0) close(fd2); + close(fd); return EXIT_FAILURE; } restart_mojo(fd); + /* allow 500ms for FPGA to clear and stabilize */ + usleep(500 * 1000); if(!ramonly && clearflash) { printf("Erasing flash...\n"); - write(fd, "E", 1); - wait_for_fd(fd, 0); - if(read(fd, buf, 1) == 1 && buf[0] == 'D') + if (write_exact(fd, "E", 1) != 0) { + fprintf(stderr, "Failed writing erase command\n"); + close(fd); + return EXIT_FAILURE; + } + if (read_exact(fd, buf, 1) == 0 && buf[0] == 'D') { printf("Erasing done. Read %c\n", buf[0]); - return EXIT_SUCCESS; + close(fd); + return EXIT_SUCCESS; + } else { + fprintf(stderr, "Erasing failed\n"); + close(fd); + return EXIT_FAILURE; + } } - if(!binpath) { - fprintf(stderr, "Error: No flash file specified\n"); - return EXIT_FAILURE; - } - - if( (fd2 = open(binpath, O_RDONLY)) < 0 || (stat(binpath, &statbuf) == -1)) { - fprintf(stderr, "Failed opening %s\n", binpath); + char cmd = ramonly ? 'R' : (verify ? 'V' : 'F'); + if (write_exact(fd, &cmd, 1) != 0) { + fprintf(stderr, "Failed writing command %c\n", cmd); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } - - write(fd, ramonly ? "R" : (verify ? "V" : "F"), 1); - wait_for_fd(fd, 0); - if(read(fd, buf, 1) != 1 || buf[0] != 'R') { + if (read_exact(fd, buf, 1) != 0 || buf[0] != 'R') { printf("Phase 1: Mojo didn't respond. Read %c\n", buf[0]); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } @@ -167,71 +301,122 @@ int main(int argc, char *argv[]) len[i] = ((intmax_t) statbuf.st_size >> (i * 8) & 0xff); } - write(fd, len, 4); - wait_for_fd(fd, 0); - if(read(fd, buf, 1) != 1 || buf[0] != 'O') { + if (write_exact(fd, len, 4) != 0) { + fprintf(stderr, "Failed writing length\n"); + restart_mojo(fd); + close(fd2); + close(fd); + return EXIT_FAILURE; + } + if (read_exact(fd, buf, 1) != 0 || buf[0] != 'O') { printf("Phase 2: Mojo didn't respond. Read %c\n", buf[0]); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } - while((numRead = read(fd2, buf, BUF_SIZE)) > 0) - if (write(fd, buf, numRead) != numRead) { + while ((numRead = read(fd2, buf, BUF_SIZE)) > 0) { + if (write_exact(fd, buf, numRead) != 0) { fprintf(stderr, "Error loading file %s\n", binpath); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } + } - wait_for_fd(fd, 0); - if(read(fd, buf, 1) != 1 || buf[0] != 'D') { + if (read_exact(fd, buf, 1) != 0 || buf[0] != 'D') { printf("Phase 3: Mojo didn't respond. Read %c\n", buf[0]); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } if (!ramonly && verify) { printf("Verifying..."); - write(fd, "S", 1); - wait_for_fd(fd, 0); - if (read(fd, buf, 5) != 5 || buf[0] != '\xaa') { - printf("Failed. Mojo didn't not send valid header. Read %c\n", buf[0]); + if (write_exact(fd, "S", 1) != 0) { + fprintf(stderr, "Failed writing size request\n"); + restart_mojo(fd); + close(fd2); + close(fd); + return EXIT_FAILURE; + } + if (read_exact(fd, buf, 5) != 0 || buf[0] != '\xaa') { + printf("Failed. Mojo did not send valid header. Read %02x\n", (unsigned char)buf[0]); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } for (int i = 0; i < 4; i++) { flash_size |= ((intmax_t)buf[i+1] & 0xff) << (i * 8); } - // substract 5 due to start byte (\xaa + 4 bytes of prepended length) + /* subtract 5 due to start byte and prepended length */ if ((flash_size - 5) != statbuf.st_size) { - printf("Failed. Size mismatch. %d vs %d\n", flash_size - 5, statbuf.st_size); + printf("Failed. Size mismatch. %d vs %lld\n", flash_size - 5, (long long)statbuf.st_size); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } lseek(fd2, 0, SEEK_SET); int need = flash_size - 5; - int want = (need > BUF_SIZE) ? BUF_SIZE : need; int loc = 1; - wait_for_fd(fd, 0); - while((numRead = read(fd, buf, want)) > 0) { + int error_occurred = 0; + while (need > 0) { + int want = (need > BUF_SIZE) ? BUF_SIZE : need; char tmp[BUF_SIZE]; - need -= numRead; - want = (need > BUF_SIZE) ? BUF_SIZE : need; - wait_for_fd(fd2, 0); - read(fd2, tmp, numRead); - for(int i = 0; i < numRead; i++, loc++) { + if (read_exact(fd, buf, want) != 0) { + error_occurred = 1; + break; + } + if (read(fd2, tmp, want) != want) { + error_occurred = 1; + break; + } + for(int i = 0; i < want; i++, loc++) { if (buf[i] != tmp[i]) { printf("Failed. Data mismatch. Got %02x expected %02x @ offset %d\n", buf[i], tmp[i], loc); + restart_mojo(fd); + close(fd2); + close(fd); return EXIT_FAILURE; } } - wait_for_fd(fd, 0); + need -= want; + } + if (error_occurred || need > 0) { + printf("Failed. Incomplete verification or read error.\n"); + restart_mojo(fd); + close(fd2); + close(fd); + return EXIT_FAILURE; } printf("OK\n"); } - write(fd, "L", 1); - wait_for_fd(fd, 0); - if(read(fd, buf, 1) != 1 || buf[0] != 'D') { - printf("Phase 4: Mojo didn't respond. Read %c\n", buf[0]); - return EXIT_FAILURE; + if (!ramonly) { + if (write_exact(fd, "L", 1) != 0) { + fprintf(stderr, "Failed writing load command\n"); + restart_mojo(fd); + close(fd2); + close(fd); + return EXIT_FAILURE; + } + if (read_exact(fd, buf, 1) != 0 || buf[0] != 'D') { + printf("Phase 4: Mojo didn't respond. Read %c\n", buf[0]); + restart_mojo(fd); + close(fd2); + close(fd); + return EXIT_FAILURE; + } } + close(fd2); + close(fd); return EXIT_SUCCESS; }