picoboot.c (6296B)
1 // Copyright 2020, Brian Swetland <swetland@frotz.net> 2 // Licensed under the Apache License, Version 2.0. 3 4 #include <fcntl.h> 5 #include <stdio.h> 6 #include <stdint.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <unistd.h> 10 11 #include <libusb-1.0/libusb.h> 12 13 #define ERASE_MASK (4096 - 1) 14 #define BLOCK_MASK (256 - 1) 15 16 void *load_file(const char *fn, size_t *_sz) { 17 int fd; 18 off_t sz; 19 void *data = NULL; 20 fd = open(fn, O_RDONLY); 21 if (fd < 0) goto fail; 22 sz = lseek(fd, 0, SEEK_END); 23 if (sz < 0) goto fail; 24 if (lseek(fd, 0, SEEK_SET)) goto fail; 25 if ((data = malloc(sz)) == NULL) goto fail; 26 if (read(fd, data, sz) != sz) goto fail; 27 *_sz = sz; 28 return data; 29 fail: 30 if (data) free(data); 31 if (fd >= 0) close(fd); 32 return NULL; 33 } 34 35 #define PB_VID 0x2e8a 36 #define PB_PID 0x0003 37 38 #define PB_IFC 1 39 #define PB_EP_IN 0x84 40 #define PB_EP_OUT 0x03 41 42 #define PB_MAGIC 0x431fd10b 43 44 typedef struct { 45 uint32_t magic; 46 uint32_t token; 47 uint8_t cmdid; 48 uint8_t argslen; 49 uint16_t reserved; 50 uint32_t xferlen; 51 union { 52 uint8_t u8[16]; 53 uint32_t u32[4]; 54 }; 55 } pbcmd_t; 56 57 typedef struct { 58 uint32_t token; 59 uint32_t status; 60 uint8_t cmd; 61 uint8_t busy; 62 uint8_t reserved[6]; 63 } pbstatus_t; 64 65 #define CMD_EXCLUSIVE_ACCESS 0x01 // excl:u8 66 #define CMD_REBOOT 0x02 // pc:u32 sp:u32 delay:u32 67 #define CMD_FLASH_ERASE 0x03 // addr:u32 size:u32 68 #define CMD_READ 0x04 // addr:u32 size:u32 69 #define CMD_WRITE 0x05 // addr:u32 size:u32 70 #define CMD_EXIT_XIP 0x06 71 #define CMD_ENTER_XIP 0x07 72 #define CMD_EXEC 0x08 // addr:u32 73 #define CMD_VECTORIZE_FLASH 0x09 // addr:u32 74 75 #define EA_NOT 0 76 #define EA_EXCLUSIVE 1 77 #define EA_EXCLUSIVE_AND_EJECT 2 78 79 static libusb_context* usbctx = NULL; 80 static libusb_device_handle* usbdev = NULL; 81 static uint32_t token = 0; 82 83 int pb_status(void) { 84 pbstatus_t pbs; 85 int r = libusb_control_transfer(usbdev, 86 LIBUSB_REQUEST_TYPE_VENDOR | 87 LIBUSB_RECIPIENT_INTERFACE | 88 LIBUSB_ENDPOINT_IN, 89 0x42, 0, 1, (void*) &pbs, sizeof(pbs), 1000); 90 if (r != sizeof(pbs)) { 91 fprintf(stderr, "picoboot: cannot read status %d\n", r); 92 return -1; 93 } 94 fprintf(stderr, "picoboot: status: tok=%08x sts=%08x cmd=%02x busy=%02x\n", 95 pbs.token, pbs.status, pbs.cmd, pbs.busy); 96 return pbs.status; 97 } 98 99 int TRACE = 0; 100 101 int pb_txn(pbcmd_t* cmd, void* data, int tx) { 102 cmd->magic = PB_MAGIC; 103 cmd->token = token++; 104 int xfer; 105 int r; 106 uint8_t tmp[64]; 107 108 if (TRACE) fprintf(stderr, "picoboot: txn cmd=%02x len=%u\n", cmd->cmdid, cmd->xferlen); 109 if ((r = libusb_bulk_transfer(usbdev, PB_EP_OUT, (void*) cmd, sizeof(pbcmd_t), &xfer, 0)) < 0) { 110 fprintf(stderr, "picoboot: usb error sending command %d\n", r); 111 pb_status(); 112 return r; 113 } 114 if (TRACE) pb_status(); 115 116 if (tx && (cmd->xferlen > 0)) { 117 xfer = 0; 118 if ((r = libusb_bulk_transfer(usbdev, PB_EP_OUT, data, cmd->xferlen, &xfer, 5000)) < 0) { 119 fprintf(stderr, "xfer %u\n", xfer); 120 fprintf(stderr, "picoboot: usb error sending payload %d\n", r); 121 pb_status(); 122 return r; 123 } 124 } 125 126 xfer = 0; 127 if ((r = libusb_bulk_transfer(usbdev, PB_EP_IN, tmp, 64, &xfer, 0)) < 0) { 128 fprintf(stderr, "xfer %u\n", xfer); 129 fprintf(stderr, "picoboot: usb error receiving ack %d\n", r); 130 pb_status(); 131 return -1; 132 } 133 134 if (TRACE) pb_status(); 135 return 0; 136 } 137 138 void usage(void) { 139 fprintf(stderr, 140 "usage: picoboot ( cmd )*\n" 141 "\n" 142 "commands: -flash <filename> ( @hex ) write file to flash\n" 143 " -reboot reboot device\n" 144 " -help show this\n" 145 ); 146 } 147 148 int pb_io(uint8_t _cmd, uint32_t addr, uint32_t size, void* data) { 149 pbcmd_t cmd; 150 memset(&cmd, 0, sizeof(cmd)); 151 cmd.cmdid = _cmd; 152 cmd.argslen = 8; 153 cmd.u32[0] = addr; 154 cmd.u32[1] = size; 155 if (data) cmd.xferlen = size; 156 return pb_txn(&cmd, data, 1); 157 } 158 159 int pb_cmd(uint8_t _cmd) { 160 pbcmd_t cmd; 161 memset(&cmd, 0, sizeof(cmd)); 162 cmd.cmdid = _cmd; 163 switch (_cmd) { 164 case CMD_EXCLUSIVE_ACCESS: 165 cmd.argslen = 1; 166 cmd.u8[0] = EA_EXCLUSIVE; 167 break; 168 case CMD_REBOOT: 169 cmd.argslen = 12; 170 cmd.u32[2] = 100; // ms 171 break; 172 } 173 return pb_txn(&cmd, NULL, 0); 174 } 175 176 int main(int argc, char** argv) { 177 if (argc == 1) { 178 usage(); 179 return 0; 180 } 181 182 if (libusb_init(&usbctx) < 0) return -1; 183 if ((usbdev = libusb_open_device_with_vid_pid(usbctx, PB_VID, PB_PID)) == NULL) { 184 fprintf(stderr, "picoboot: cannot find compatible device\n"); 185 return -1; 186 } 187 if ((libusb_claim_interface(usbdev, PB_IFC)) < 0) { 188 fprintf(stderr, "picoboot: cannot claim interface\n"); 189 return -1; 190 } 191 libusb_clear_halt(usbdev, PB_EP_IN); 192 libusb_clear_halt(usbdev, PB_EP_OUT); 193 194 size_t sz; 195 void* data; 196 for (argc--, argv++; argc > 0; argc--, argv++) { 197 if (!strcmp(argv[0], "-reboot")) { 198 if (pb_cmd(CMD_REBOOT)) { 199 fprintf(stderr, "picoboot: REBOOT failed\n"); 200 return -1; 201 } 202 } else if (!strcmp(argv[0], "-flash")) { 203 const char *fn = argv[1]; 204 if (argc < 2) { 205 fprintf(stderr, "picoboot: missing argument\n"); 206 return -1; 207 } 208 if ((data = load_file(fn, &sz)) == NULL) { 209 fprintf(stderr, "picoboot: failed to load '%s'\n", fn); 210 return -1; 211 } 212 argc--; argv++; 213 uint32_t addr = 0x10000000; 214 if ((argc > 1) && (argv[1][0] == '@')) { 215 addr = strtoul(argv[1] + 1, NULL, 16); 216 argc--; argv++; 217 } 218 if ((addr < 0x10000000) || (addr >= 0x11000000) || (addr & ERASE_MASK)) { 219 fprintf(stderr, "picoboot: bad flash start address 0x%08x\n", addr); 220 return -1; 221 } 222 if (pb_cmd(CMD_EXCLUSIVE_ACCESS)) { 223 fprintf(stderr, "picoboot: EXCLUSIVE ACCESS failed\n"); 224 return -1; 225 } 226 if (pb_cmd(CMD_EXIT_XIP)) { 227 fprintf(stderr, "picoboot: EXIT XIP failed\n"); 228 return -1; 229 } 230 fprintf(stderr, "picoboot: erase flash @ 0x%08x\n", addr); 231 if (pb_io(CMD_FLASH_ERASE, addr, (sz + ERASE_MASK) & (~ERASE_MASK), NULL)) { 232 fprintf(stderr, "picoboot: ERASE failed\n"); 233 return -1; 234 } 235 fprintf(stderr, "picoboot: write '%s' to flash @ 0x%08x\n", fn, addr); 236 if (pb_io(CMD_WRITE, addr, sz, data)) { 237 fprintf(stderr, "picoboot: WRITE failed\n"); 238 return -1; 239 } 240 } else if (!strcmp(argv[0], "-help")) { 241 usage(); 242 return 0; 243 } else { 244 fprintf(stderr, "picoboot: unknown command '%s'\n", argv[0]); 245 usage(); 246 return -1; 247 } 248 } 249 250 return 0; 251 }