diff --git a/.gitignore b/.gitignore index 335428e..7e65c42 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ tmp flash.uf2 flash.bin +built/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2c6b307 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "hidapi"] + path = hidapi + url = ../../signal11/hidapi diff --git a/hidapi b/hidapi new file mode 160000 index 0000000..a6a622f --- /dev/null +++ b/hidapi @@ -0,0 +1 @@ +Subproject commit a6a622ffb680c55da0de787ff93b80280498330f diff --git a/uf2tool/Makefile b/uf2tool/Makefile new file mode 100644 index 0000000..f8672ac --- /dev/null +++ b/uf2tool/Makefile @@ -0,0 +1,3 @@ +all: + mkdir -p ../built + $(CC) -g -Wall tool.c ../hidapi/mac/hid.c -I../hidapi/hidapi -I. -framework IOKit -framework CoreFoundation -o ../built/uf2tool diff --git a/uf2tool/tool.c b/uf2tool/tool.c new file mode 100644 index 0000000..2fc463b --- /dev/null +++ b/uf2tool/tool.c @@ -0,0 +1,257 @@ +#ifdef WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include "hidapi.h" +#include "uf2hid.h" + +#define FLASH_ROW_SIZE 4096 + +typedef struct { + hid_device *dev; + uint16_t size; + uint8_t serial; + uint16_t seqNo; + uint16_t pageSize; + uint8_t buf[FLASH_ROW_SIZE + 64]; +} HID_Dev; + +uint64_t millis() { + struct timeval tv; + gettimeofday(&tv, 0); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +void fatal(const char *msg) { + fprintf(stderr, "Fatal error: %s\n", msg); + exit(1); +} + +void write16(uint8_t *ptr, uint16_t v) { + ptr[0] = v; + ptr[1] = v >> 8; +} + +void write32(uint8_t *ptr, uint32_t v) { + ptr[0] = v; + ptr[1] = v >> 8; + ptr[2] = v >> 16; + ptr[3] = v >> 24; +} + +uint32_t read32(uint8_t *ptr) { return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24); } + +uint32_t read16(uint8_t *ptr) { return ptr[0] | (ptr[1] << 8); } + +int recv_hid(HID_Dev *pkt, int timeout) { + uint8_t buf0[65]; + uint8_t *buf; + + pkt->size = 0; + memset(pkt->buf, 0, sizeof(pkt->buf)); + + for (;;) { + int sz = hid_read_timeout(pkt->dev, buf0, 65, timeout); + if (sz <= 0) { + if (timeout < 0) + fatal("read error"); + return 0; + } + buf = buf0; + if (!*buf) + buf++; // skip report number if passed + + uint8_t tag = buf[0]; + if (pkt->size && !(tag & 0x80)) + fatal("invalid serial transfer"); + uint32_t newsize = pkt->size + (tag & HF2_SIZE_MASK); + if (newsize > sizeof(pkt->buf)) + fatal("too large packet"); + memcpy(pkt->buf + pkt->size, buf + 1, tag & HF2_SIZE_MASK); + pkt->size = newsize; + if (tag & 0x40) { + pkt->serial = (tag & HF2_FLAG_MASK) == HF2_FLAG_SERIAL; + return 1; + } + timeout = -1; // next read is blocking + } +} + +void send_hid(hid_device *dev, const void *data, int size) { + uint8_t buf[65] = {0}; + const uint8_t *ptr = data; + + for (;;) { + int s; + if (size <= 63) { + s = size; + buf[1] = HF2_FLAG_PKT_LAST | size; + } else { + s = 63; + buf[1] = HF2_FLAG_PKT_BODY | 63; + } + memcpy(buf + 2, ptr, s); + int sz = hid_write(dev, buf, 65); + if (sz != 65) + fatal("write error"); + ptr += s; + size -= s; + if (!size) + break; + } +} + +void talk_hid(HID_Dev *pkt, int cmd, const void *data, uint32_t len) { + if (len >= sizeof(pkt->buf) - 4) + fatal("buffer overflow"); + if (data) + memcpy(pkt->buf + 4, data, len); + write16(pkt->buf, cmd); + write16(pkt->buf + 2, ++pkt->seqNo); + uint32_t saved = read32(pkt->buf); + send_hid(pkt->dev, pkt->buf, 4 + len); + recv_hid(pkt, -1); + if (read32(pkt->buf) != saved) + fatal("invalid sequence number"); + if (read32(pkt->buf + 4) != 0) + fatal("invalid status"); +} + +uint8_t flashbuf[64 * 1024]; + +unsigned short add_crc(char ptr, unsigned short crc) { + unsigned short cmpt; + crc = crc ^ (int)ptr << 8; + for (cmpt = 0; cmpt < 8; cmpt++) { + if (crc & 0x8000) + crc = crc << 1 ^ 0x1021; + else + crc = crc << 1; + } + return (crc & 0xFFFF); +} + +void verify(HID_Dev *cmd, uint8_t *buf, int size, int offset) { + int maxSize = (cmd->pageSize / 2 - 8) * cmd->pageSize; + while (size > maxSize) { + verify(cmd, buf, maxSize, offset); + buf += maxSize; + offset += maxSize; + size -= maxSize; + } + int numpages = size / cmd->pageSize; + write32(cmd->buf + 4, offset); + write32(cmd->buf + 8, numpages); + talk_hid(cmd, HF2_CMD_CHKSUM_PAGES, 0, 8); + for (int i = 0; i < numpages; ++i) { + int sum = read16(cmd->buf + 8 + i * 2); + uint16_t crc = 0; + for (int j = 0; j < cmd->pageSize; ++j) { + crc = add_crc(buf[j], crc); + } + if (sum != crc) + fatal("verification failed"); + buf += cmd->pageSize; + } +} + +int stdinHasData() { + struct pollfd fds; + fds.fd = 0; + fds.events = POLLIN; + fds.revents = 0; + return poll(&fds, 1, 0) > 0; +} + +void serial(HID_Dev *cmd) { + uint8_t buf[65]; + for (;;) { + while (stdinHasData()) { + memset(buf, 0, 65); + int sz = read(0, buf + 2, 63); + if (sz > 0) { + buf[1] = HF2_FLAG_SERIAL | sz; + hid_write(cmd->dev, buf, 65); + } + } + if (recv_hid(cmd, 10)) { + if (cmd->serial) + write(1, cmd->buf, cmd->size); + } + } +} + +int main(int argc, char *argv[]) { + int res; + HID_Dev cmd = {0}; + + // Initialize the hidapi library + res = hid_init(); + + struct hid_device_info *devs = hid_enumerate(0, 0); + for (struct hid_device_info *p = devs; p; p = p->next) { + if ((p->release_number & 0xff00) == 0x4200) { + printf("DEV: %04x:%04x %s\n", p->vendor_id, p->product_id, p->path); + cmd.dev = hid_open_path(p->path); + } + } + hid_free_enumeration(devs); + if (!cmd.dev) { + printf("no devices\n"); + return 0; + } + + talk_hid(&cmd, HF2_CMD_INFO, 0, 0); + printf("INFO: %s\n", cmd.buf + 8); + + serial(&cmd); + + talk_hid(&cmd, HF2_CMD_BININFO, 0, 0); + if (read32(cmd.buf + 8) != HF2_MODE_BOOTLOADER) + fatal("not bootloader"); + + cmd.pageSize = read32(cmd.buf + 12); + printf("page size: %d\n", cmd.pageSize); + + srand(millis()); + int i; + for (i = 0; i < sizeof(flashbuf); ++i) + flashbuf[i] = rand(); + + uint64_t start = millis(); + + for (i = 0; i < sizeof(flashbuf); i += cmd.pageSize) { + write32(cmd.buf + 4, i + 0x2000); + memcpy(cmd.buf + 8, flashbuf + i, cmd.pageSize); + talk_hid(&cmd, HF2_CMD_WRITE_FLASH_PAGE, 0, cmd.pageSize + 4); + } + + printf("time: %d\n", (int)(millis() - start)); + start = millis(); + +#if 0 + + for (i = 0; i < sizeof(flashbuf); i += cmd.pageSize) { + write32(cmd.buf + 4, i + 0x2000); + talk_hid(&cmd, HF2_CMD_MEM_READ_PAGE, 0, 4); + if (memcmp(cmd.buf + 8, flashbuf + i, cmd.pageSize)) { + printf("%d,%d,%d != %d?\n", cmd.buf[8], cmd.buf[9], cmd.buf[10], flashbuf[i]); + fatal("verification failed"); + } + } +#else + verify(&cmd, flashbuf, sizeof(flashbuf), 0x2000); +#endif + + printf("verify time: %d\n", (int)(millis() - start)); + + // Finalize the hidapi library + res = hid_exit(); + + return 0; +} diff --git a/uf2tool/uf2hid.h b/uf2tool/uf2hid.h new file mode 100644 index 0000000..10c2c29 --- /dev/null +++ b/uf2tool/uf2hid.h @@ -0,0 +1,29 @@ +#ifndef UF2_HID_H +#define UF2_HID_H 1 + +#define HF2_FLAG_PKT_LAST 0xC0 +#define HF2_FLAG_PKT_BODY 0x80 +#define HF2_FLAG_SERIAL 0x40 +#define HF2_FLAG_MASK 0xC0 +#define HF2_FLAG_RESERVED 0x00 +#define HF2_SIZE_MASK 63 + +#define HF2_CMD_INFO 0x0001 +#define HF2_CMD_RESET_INTO_APP 0x0002 +#define HF2_CMD_RESET_INTO_BOOTLOADER 0x0003 +#define HF2_CMD_WRITE_FLASH_PAGE 0x0004 +#define HF2_CMD_MEM_WRITE_PAGE 0x0005 +#define HF2_CMD_MEM_READ_PAGE 0x0006 +#define HF2_CMD_START_FLASH 0x0007 +#define HF2_CMD_BININFO 0x0008 +#define HF2_CMD_CHKSUM_PAGES 0x0009 + +#define HF2_MODE_BOOTLOADER 0x01 +#define HF2_MODE_USERSPACE 0x02 + +#define HF2_STATUS_OK 0x00000000 +#define HF2_STATUS_INVALID_CMD 0x00000001 +#define HF2_STATUS_WRONG_MODE 0x00000002 + + +#endif