/* * lpc111x-isp/lpc111x.c - LPC111x/LPC11Cxx ISP programmer * * Written 2012 by Werner Almesberger * Copyright 2012 Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #define _GNU_SOURCE /* for vasprintf */ #include #include #include #include #include #include #include #include #include #include #define TGT_nRESET UBB_CMD #define TGT_nISP UBB_DAT1 #define TGT_TX UBB_DAT3 #define TGT_RX UBB_DAT2 #define HOST_RX TGT_TX #define HOST_TX TGT_RX #define BPS 115200 #define MAX_BUF 10000 /* receive buffer */ #define AUTOBAUD_TRIES 10 #define SYNC "Synchronized" #define MAX_RECORD 45 /* data bytes per uuencoded record */ /* * UM 26.4.8 pg 416 says 0x1000017C to 0x1000025B and the last 256+32 bytes * of the RAM are taken. * * UM 26.5.4 pg 418 hints 0x10000300 is a good place. */ #define RAM_BUFFER 0x10000300 #define SECTOR 4096 #define PAGE 256 static int verbose = 0; static int quiet = 0; /* ----- Debugging and tracing --------------------------------------------- */ static void trace(const char *label, const uint8_t *s, int len) { const uint8_t *end = s+len; if (label) fprintf(stderr, "%s ", label); for (end = s+len; s != end; s++) { if (*s >= ' ' && *s <= '~') fprintf(stderr, "%c", *s); else if (*s == 10) fprintf(stderr, "\\n"); else if (*s == 13) fprintf(stderr, "\\r"); else fprintf(stderr, "\\%02o", *s); } fprintf(stderr, "\n"); } static void trace_out(const void *s, int len) { trace(">>>", s, len); } static void trace_in(const void *s, int len) { trace("<<<", s, len); } /* ----- Dialog functions -------------------------------------------------- */ static const char *dialog_fixed(int idle, const char *msg) { static char *res = NULL; int msg_len = strlen(msg); char *tx_buf = alloca(msg_len+3); char *rx_buf = alloca(MAX_BUF); char *s, *t; int got; memcpy(tx_buf, msg, msg_len); memcpy(tx_buf+msg_len, "\r\n", 2); if (verbose) trace_out(tx_buf, msg_len+2); got = swuart_trx(tx_buf, msg_len+2, rx_buf, MAX_BUF, idle, idle); if (verbose) trace_in(rx_buf, got); if (got < msg_len+2) { fprintf(stderr, "response too short for echo\n"); exit(1); } if (memcmp(rx_buf, msg, msg_len) || rx_buf[msg_len] != '\r' || rx_buf[msg_len+1] != '\n') { fprintf(stderr, "echo mismatch\n"); exit(1); } if (memchr(rx_buf, 0, got)) { fprintf(stderr, "NUL in response\n"); exit(1); } rx_buf += msg_len+2; got -= msg_len+2; res = realloc(res, got+1); if (!res) { perror("realloc"); exit(1); } t = res; for (s = rx_buf; s != rx_buf+got; s++) { if (*s == '\r') { if (s+1 != rx_buf+got && s[1] == '\n') continue; fprintf(stderr, "\\r without \\n\n"); exit(1); } if (*s == '\n' && s+1 == rx_buf+got) break; *t++ = *s; } *t = 0; return res; } static const char *vdialog(int idle, const char *cmd, va_list ap) { char *msg; vasprintf(&msg, cmd, ap); return dialog_fixed(idle, msg); } static const char *dialog(int idle, const char *cmd, ...) { va_list ap; const char *res; va_start(ap, cmd); res = vdialog(idle, cmd, ap); va_end(ap); return res; } static const char *dialog_rc(int idle, const char *cmd, ...) { va_list ap; const char *res, *p; unsigned rc; va_start(ap, cmd); res = vdialog(idle, cmd, ap); va_end(ap); p = strchr(res, '\n'); if (!p) p = strchr(res, 0); if (sscanf(res, "%u", &rc) != 1) { fprintf(stderr, "invalid status\n"); exit(1); } if (rc != 0) { fprintf(stderr, "rc %d\n", rc); exit(1); } return p+1; } static int autobaud(void) { uint8_t reply[100]; int i, got; const char *res; for (i = 0; i != AUTOBAUD_TRIES; i++) { CLR(TGT_nRESET); usleep(10); /* DS Table 9 pg 29 says min 50 ns */ SET(TGT_nRESET); usleep(5*1000); /* UM 26.3.1 pg 408 says max 3 ms */ got = swuart_trx("?", 1, reply, sizeof(reply), 1000, 100); if (got != strlen(SYNC)+2 || memcmp(reply, SYNC "\r\n", got)) continue; res = dialog(100, SYNC); if (!strcmp(res, "OK")) return 1; } return 0; } /* ----- Devices database -------------------------------------------------- */ static const struct device { const char *name; unsigned id; int flash_kb; } devices[] = { { "LPC1112FHxxx/202", 0x2524902b, 16 }, { NULL, } }, *device = NULL; static void identify(void) { const char *res; unsigned id, serial[4]; res = dialog_rc(100, "J"); if (sscanf(res, "%u", &id) != 1) { fprintf(stderr, "J: cannot parse ID \"%s\"\n", res); exit(1); } res = dialog_rc(100, "N"); if (sscanf(res, "%u %u %u %u", serial, serial+1, serial+2, serial+3) != 4) { fprintf(stderr, "N: cannot parse serial number\"%s\"\n", res); exit(1); } for (device = devices; device->name; device++) if (device->id == id) { if (!quiet) fprintf(stderr, "%s (0x%04x %04x) %d kB " "serial %08x.%08x.%08x.%08x\n", device->name, id >> 16, id & 0xffff, device->flash_kb, serial[0], serial[1], serial[2], serial[3]); return; } fprintf(stderr, "unknown device 0x%04x %04x\n", id >> 16, id & 0xffff); exit(1); } /* ----- Flash programming ------------------------------------------------- */ static int ms_to_bits(int ms) { return (BPS*ms+999)/1000; } static char uuechar(uint8_t b) { b &= 0x3f; return b ? b+32 : 0x60; } static const char *uuencode(const uint8_t *p, int len) { static char buf[80]; char *t = buf; const uint8_t *end = p+len; unsigned tmp = 0; /* initialize to prevent compiler complaints */ int i; *t++ = len+32; while (p != end) { for (i = 0; i != 3; i++) if (p == end) tmp <<= 8; else tmp = tmp << 8 | *p++; *t++ = uuechar(tmp >> 18); *t++ = uuechar(tmp >> 12); *t++ = uuechar(tmp >> 6); *t++ = uuechar(tmp); } *t = 0; return buf; } static void flash_erase_sectors(unsigned addr, unsigned len) { unsigned from = addr/SECTOR; unsigned to = (addr+len-1)/SECTOR; assert(!(addr & (SECTOR-1))); assert(!(len & (SECTOR-1))); dialog_rc(100, "P %d %d", from, to); /* DS 10.2 pg 77: t_er(max) = 105 ms */ dialog_rc(ms_to_bits(200), "E %d %d", from, to); } static void flash_write_page(unsigned addr, const uint8_t *buf) { unsigned sector = addr/SECTOR; unsigned sum; int chunk, i; const char *res; assert(!(addr & (PAGE-1))); dialog_rc(100, "W %u %u", RAM_BUFFER, PAGE); for (i = 0; i != PAGE; i += chunk) { chunk = PAGE-i > MAX_RECORD ? MAX_RECORD : PAGE-i; dialog(100, "%s", uuencode(buf+i, chunk)); } sum = 0; for (i = 0; i != PAGE; i++) sum += buf[i]; res = dialog(100, "%u", sum); if (strcmp(res, "OK")) { fprintf(stderr, "non-OK response: \"%s\"\n", res); exit(1); } dialog_rc(100, "P %d %d", sector, sector); /* DS 10.2 pg 77: t_prog(max) = 1.05 ms */ dialog_rc(ms_to_bits(2), "C %d %u %d", addr, RAM_BUFFER, PAGE); } static void flash_write(unsigned addr, const uint8_t *p, int len, int erase_all) { uint8_t page[PAGE]; assert(!(addr & (PAGE-1))); dialog_rc(100, "U 23130"); if (erase_all) flash_erase_sectors(0, device->flash_kb << 10); else flash_erase_sectors(addr & ~(SECTOR-1), (addr+len+SECTOR-1) & ~(SECTOR-1)); while (len > 0) { if (len < PAGE) { memcpy(page, p, len); memset(page+len, 0xff, PAGE-len); } else { memcpy(page, p, PAGE); p += PAGE; } flash_write_page(addr, page); addr += PAGE; len -= PAGE; } } static void flash_file(FILE *file) { uint8_t buf[(device->flash_kb << 10)+1]; size_t got; got = fread(buf, 1, sizeof(buf), file); if (!got) { if (ferror(file)) perror("fread"); else fprintf(stderr, "file is empty\n"); exit(1); } if (got > device->flash_kb << 10) { fprintf(stderr, "file is larger than flash (%d kB)\n", device->flash_kb); exit(1); } flash_write(0, buf, got, 1); } /* ----- Flash dump -------------------------------------------------------- */ /* * The uuencoding algorithm is described in AN11229. * It's the same as used by uuencode. */ static uint8_t uudchar(char c) { if (c <= 0x20 && c > 0x60) { fprintf(stderr, "invalid UU character '%c'\n", c); exit(1); } return c == 0x60 ? 0 : c-32; } static int uudecode(const char *s, uint8_t *buf) { int len, uu_len; const char *nl; const uint8_t *end; len = *s-32; if (len < 0 || len > MAX_RECORD) { fprintf(stderr, "invalid UU length (%d)\n", len); exit(1); } nl = strchr(++s, '\n'); if (!nl) { fprintf(stderr, "no \\n at end of UU record\n"); exit(1); } uu_len = nl-s; if (uu_len & 3) { fprintf(stderr, "UU length %d not a multiple of 4\n", uu_len); exit(1); } if ((len+2)/3 != uu_len/4) { fprintf(stderr, "UU length %d vs. %d bytes (\"%.*s\")\n", uu_len, len, nl-s+1, s-1); exit(1); } end = buf+len; while (buf != end) { unsigned tmp = uudchar(s[0]) << 18 | uudchar(s[1]) << 12 | uudchar(s[2]) << 6 | uudchar(s[3]); *buf++ = tmp >> 16; if (buf != end) *buf++ = tmp >> 8; if (buf != end) *buf++ = tmp; s += 4; } return len; } static void dump(void) { int addr = 0, end; unsigned sum = 0, check; const char *res, *nl; int got, wrote, i; end = device->flash_kb << 10; res = dialog_rc(100, "R 0 %u", end); while (addr != end) { while (1) { uint8_t buf[MAX_RECORD]; nl = strchr(res, '\n'); if (!nl) break; got = uudecode(res, buf); for (i = 0; i != got; i++) sum += buf[i]; wrote = fwrite(buf, 1, got, stdout); if (wrote != got) { perror("fwrite"); exit(1); } addr += got; res = nl+1; } if (sscanf(res, "%u", &check) != 1) { fprintf(stderr, "can't parse checksum \"%s\"\n", res); exit(1); } if (check != sum) { fprintf(stderr, "checksum error: got %u received %u\n", sum, check); exit(1); } res = dialog(100, "OK"); sum = 0; } } /* ----- ISP session ------------------------------------------------------- */ static void at_exit(void) { ubb_close(0); } static void start_isp(int power) { const char *res; if (ubb_open(0) < 0) { perror("ubb_open"); exit(1); } atexit(at_exit); if (power) ubb_power(1); usleep(100*1000); SET(TGT_nRESET); OUT(TGT_nRESET); CLR(TGT_nISP); OUT(TGT_nISP); if (swuart_open(HOST_TX, HOST_RX, BPS) < 0) { perror("swuart_open"); exit(1); } if (!autobaud()) { fprintf(stderr, "target is not responding\n"); exit(1); } res = dialog(100, "12000"); if (strcmp(res, "OK")) { fprintf(stderr, "cannot set clock rate\n"); exit(1); } } /* ----- Reset and run target ---------------------------------------------- */ static void run_target(int power) { if (ubb_open(0) < 0) { perror("ubb_open"); exit(1); } if (power) ubb_power(1); SET(TGT_nRESET); OUT(TGT_nRESET); IN(TGT_nISP); CLR(TGT_nRESET); usleep(10); /* DS Table 9 pg 29 says min 50 ns */ SET(TGT_nRESET); ubb_close(UBB_nPWR | TGT_nRESET | TGT_nISP); } /* ----- Command-line processing ------------------------------------------- */ static void usage(const char *name) { fprintf(stderr, "usage: %s [-n] [-q] [-v ...] [file.bin]\n" " %s [-n] -r\n\n" " -n don't power the device\n" " -q suppress basic progress messages\n" " -r reset the target and let it run\n" " -v increase verbosity level\n" , name, name); exit(1); } int main(int argc, char **argv) { FILE *file = NULL; int power = 1; int run = 0; int c; while ((c = getopt(argc, argv, "nqrv")) != EOF) switch (c) { case 'n': power = 0; break; case 'q': quiet = 1; break; case 'r': run = 1; break; case 'v': verbose++; break; default: usage(*argv); } if (run) { if (quiet || verbose || argc != optind) usage(*argv); run_target(power); return 0; } switch (argc-optind) { case 0: break; case 1: if (!strcmp(argv[optind], "-")) file = stdin; else { file = fopen(argv[optind], "r"); if (!file) { perror(argv[optind]); exit(1); } } break; default: usage(*argv); } start_isp(power); identify(); if (file) flash_file(file); else dump(); return 0; }