Make the repo self-contained (no dependency on the parent firmware-tools/ checkout): copy in the patch/disasm/syx tools and the c2probe RP2040 C2 flash-reader/patcher firmware. - patch_usb1_to_cv.py : byte-level USB-1->CV patch (imported by roundtrip.py) - d8051.py : standalone 8051 disassembler - syx_extract.py : SysEx extractor - c2probe/ : RP2040 C2 flash reader + host scripts (c2probe.c, cdc/dump_flash/patch_c2/reflash_page/verify.py, CMake build) - RECOVERY.md : C2 flash recovery procedure - EFM8UB20_PINOUT.md : reverse-engineered QFP48 pinout + firmware pin usage - roundtrip.py : import local patch_usb1_to_cv (parent as fallback) - .gitignore : exclude c2probe/.venv, c2probe/build Verified: stock + patched round-trips still re-assemble byte-identical.
547 lines
22 KiB
C
547 lines
22 KiB
C
// c2probe -- RP2040 bit-bang programmer for the Silicon Labs C2 interface.
|
|
//
|
|
// Reads code flash on EFM8UB2 (and C8051F) parts. READ-ONLY by design: it
|
|
// implements only the C2 frame primitives, register read/write, PI init, and
|
|
// the FPDAT Block Read (0x06) command. There is no erase/write/lock path and
|
|
// no Device Erase arming, so it cannot damage flash.
|
|
//
|
|
// Wiring: GP2 = C2CK (target RST/C2CK), GP3 = C2D. 3.3V, direct.
|
|
// Protocol reference: Silicon Labs AN127 Rev 1.4.
|
|
//
|
|
// Host command interface over USB CDC (line-based, LF-terminated):
|
|
// hello
|
|
// speed <us> C2CK half-period in us (low=high=<us>), default 1
|
|
// fpdat <hex> set FPDAT register address (default 0xAD EFM8UB2)
|
|
// pins report pin assignment
|
|
// reset C2 device reset (C2CK low >=20us)
|
|
// status Address Read -> status byte (FLBusy/EError/InBusy/OutReady)
|
|
// rdreg <hexaddr> Address Write + Data Read -> register value
|
|
// wrreg <hexaddr> <hex> Address Write + Data Write
|
|
// id read DEVICEID(0x00) and REVID(0x01)
|
|
// piinit full PI init (reset + FPCTL 0x02,0x04,0x01 + 20ms)
|
|
// rawaw <hex> raw Address Write
|
|
// rawar raw Address Read (-> status)
|
|
// rawdw <hex> raw Data Write
|
|
// rawdr raw Data Read
|
|
// read <hexaddr> <len> FPDAT Block Read, len 1..256 (0=256) -> hex bytes
|
|
// dump <hexstart> <hexend> loop read over range, one line per block
|
|
//
|
|
// Replies: "ok ...", "data <addr> <n> <hex>", "err <code>", "stat <hex>", etc.
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "pico/stdlib.h"
|
|
#include "hardware/gpio.h"
|
|
#include "hardware/sync.h"
|
|
#include "pico/bootrom.h"
|
|
|
|
// C2CK / C2D pin numbers are runtime-configurable via the `pins` command so the
|
|
// host can try both wiring orientations without reflashing. Default: GP2=C2CK,
|
|
// GP3=C2D.
|
|
static int C2CK = 2;
|
|
static int C2D = 3;
|
|
|
|
// C2CK low/high time for a bit strobe, in microseconds. Must satisfy
|
|
// 20ns <= tCL < 5000ns (else a reset is triggered). 1us is safe.
|
|
static uint32_t half_us = 1;
|
|
|
|
// FPDAT register address (device-specific). EFM8UB2 = 0xAD. Settable at runtime.
|
|
static uint8_t fpdat_addr = 0xAD;
|
|
|
|
// ----------------------------------------------------------------- low-level
|
|
|
|
static inline void c2ck_set(int v) { gpio_put(C2CK, v); }
|
|
static inline void c2d_drive(int v) {
|
|
gpio_set_dir(C2D, GPIO_OUT);
|
|
gpio_put(C2D, v ? 1 : 0);
|
|
}
|
|
static inline void c2d_release(void) {
|
|
gpio_set_dir(C2D, GPIO_IN); // high-Z, no pull
|
|
}
|
|
static inline int c2d_get(void) { return gpio_get(C2D) ? 1 : 0; }
|
|
|
|
// One C2CK strobe: high->low (tCL)->high (tCH). IRQs disabled across the low
|
|
// window so the low time stays under the 5us reset threshold even if a USB
|
|
// IRQ fires. Used when the master is driving C2D (write bits / start / stop).
|
|
static void strobe_write(void) {
|
|
uint32_t save = save_and_disable_interrupts();
|
|
gpio_put(C2CK, 0);
|
|
busy_wait_us(half_us);
|
|
gpio_put(C2CK, 1);
|
|
restore_interrupts(save);
|
|
busy_wait_us(half_us);
|
|
}
|
|
|
|
// One C2CK strobe that reads a slave-driven bit. C2D is released (input) and
|
|
// sampled after the rising edge + tDV. IRQs disabled across low+sample.
|
|
static int strobe_read(void) {
|
|
c2d_release();
|
|
uint32_t save = save_and_disable_interrupts();
|
|
gpio_put(C2CK, 0);
|
|
busy_wait_us(half_us);
|
|
gpio_put(C2CK, 1);
|
|
busy_wait_us(half_us); // tDV ~20ns; half_us gives margin
|
|
int v = gpio_get(C2D) ? 1 : 0;
|
|
restore_interrupts(save);
|
|
return v;
|
|
}
|
|
|
|
static void c2_start(void) { c2d_drive(1); strobe_write(); }
|
|
static void c2_stop(void) { c2d_drive(1); strobe_write(); c2d_release(); }
|
|
|
|
static void c2_write_bit(int b) {
|
|
c2d_drive(b ? 1 : 0);
|
|
strobe_write();
|
|
}
|
|
|
|
// ----------------------------------------------------------------- frames
|
|
|
|
// Address Write (INS=11b): START, INS(1,1), ADDRESS 8 bits LSB-first, STOP.
|
|
static void c2_addr_write(uint8_t addr) {
|
|
c2_start();
|
|
c2_write_bit(1); c2_write_bit(1); // INS = 11b
|
|
for (int i = 0; i < 8; i++) c2_write_bit((addr >> i) & 1);
|
|
c2_stop();
|
|
}
|
|
|
|
// Address Read (INS=10b): START, INS(0,1), release, read 8 bits = status, STOP.
|
|
static uint8_t c2_addr_read(void) {
|
|
c2_start();
|
|
c2_write_bit(0); c2_write_bit(1); // INS = 10b
|
|
uint8_t v = 0;
|
|
for (int i = 0; i < 8; i++) v |= (strobe_read() << i);
|
|
c2_stop();
|
|
return v;
|
|
}
|
|
|
|
// Data Write (INS=01b): START, INS(1,0), LENGTH(00=1B), DATA 8, WAIT(0s then 1), STOP.
|
|
// Returns 0 on success, -1 on WAIT timeout.
|
|
static int c2_data_write(uint8_t val) {
|
|
c2_start();
|
|
c2_write_bit(1); c2_write_bit(0); // INS = 01b
|
|
c2_write_bit(0); c2_write_bit(0); // LENGTH = 00 (1 byte)
|
|
for (int i = 0; i < 8; i++) c2_write_bit((val >> i) & 1);
|
|
// WAIT: slave releases 0s then a 1; clock and read until 1 seen.
|
|
int waited = 0;
|
|
while (strobe_read() == 0) {
|
|
if (++waited > 8192) { c2_stop(); return -1; }
|
|
}
|
|
c2_stop();
|
|
return 0;
|
|
}
|
|
|
|
// Data Read (INS=00b): START, INS(0,0), LENGTH(00), WAIT(0s then 1), DATA 8, STOP.
|
|
// Returns 0 on success, -1 on WAIT timeout.
|
|
static int c2_data_read(uint8_t *out) {
|
|
c2_start();
|
|
c2_write_bit(0); c2_write_bit(0); // INS = 00b
|
|
c2_write_bit(0); c2_write_bit(0); // LENGTH = 00 (1 byte)
|
|
int waited = 0;
|
|
while (strobe_read() == 0) { // WAIT
|
|
if (++waited > 8192) { c2_stop(); return -1; }
|
|
}
|
|
uint8_t v = 0;
|
|
for (int i = 0; i < 8; i++) v |= (strobe_read() << i); // DATA
|
|
c2_stop();
|
|
*out = v;
|
|
return 0;
|
|
}
|
|
|
|
// ----------------------------------------------------------------- helpers
|
|
|
|
static uint8_t c2_status(void) { return c2_addr_read(); }
|
|
|
|
static uint8_t c2_reg_read(uint8_t addr) { c2_addr_write(addr); uint8_t v = 0; c2_data_read(&v); return v; }
|
|
static int c2_reg_write(uint8_t addr, uint8_t val) { c2_addr_write(addr); return c2_data_write(val); }
|
|
|
|
static int c2_poll_inbusy(void) {
|
|
for (long i = 0; i < 200000L; i++) {
|
|
uint8_t s = c2_status();
|
|
if (!((s >> 1) & 1)) return 0; // InBusy cleared
|
|
}
|
|
return -1;
|
|
}
|
|
static int c2_poll_outready(void) {
|
|
for (long i = 0; i < 200000L; i++) {
|
|
uint8_t s = c2_status();
|
|
if (s & 1) return 0; // OutReady set
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void c2_reset(void) {
|
|
// Device reset: C2CK low >= 20us, high, wait >= 2us. Also leaves C2CK high.
|
|
c2d_release();
|
|
c2ck_set(0);
|
|
busy_wait_us(50); // tRD >= 20us
|
|
c2ck_set(1);
|
|
busy_wait_us(5); // tSD >= 2us
|
|
}
|
|
|
|
static void c2_pi_init(void) {
|
|
c2_reset();
|
|
c2_reg_write(0x02, 0x02); // FPCTL <- 0x02 (enable)
|
|
c2_reg_write(0x02, 0x04); // FPCTL <- 0x04 (halt core)
|
|
c2_reg_write(0x02, 0x01); // FPCTL <- 0x01
|
|
busy_wait_us(20000); // >= 20ms
|
|
}
|
|
|
|
// Block Read: read `len` bytes (1..256, 0 => 256) from flash `addr`.
|
|
// Returns number of bytes read, or negative error code.
|
|
static int c2_block_read(uint16_t addr, uint8_t len, uint8_t *buf) {
|
|
int n = len ? (int)len : 256;
|
|
uint8_t st;
|
|
|
|
c2_addr_write(fpdat_addr);
|
|
if (c2_data_write(0x06)) return -10; // Block Read cmd
|
|
if (c2_poll_inbusy()) return -1;
|
|
if (c2_poll_outready()) return -2;
|
|
if (c2_data_read(&st)) return -3;
|
|
if (st != 0x0D) return -4; // status not OK
|
|
|
|
if (c2_data_write((addr >> 8) & 0xFF)) return -11; // addr high
|
|
if (c2_poll_inbusy()) return -5;
|
|
if (c2_data_write(addr & 0xFF)) return -12; // addr low
|
|
if (c2_poll_inbusy()) return -6;
|
|
|
|
if (c2_data_write(len)) return -13; // length code (0=256)
|
|
if (c2_poll_inbusy()) return -7;
|
|
|
|
// Block-ack status: after addr+len the PI emits a second 0x0D before the
|
|
// data stream (confirmed vs ec2drv: read_port(..., cmd[3]+1) "// +1 for
|
|
// 0x0d"). Consume and discard it, else it lands as buf[0] and the whole
|
|
// block reads shifted by one (off-by-one vs stock).
|
|
if (c2_poll_outready()) return -14;
|
|
if (c2_data_read(&st)) return -15;
|
|
if (st != 0x0D) return -16;
|
|
|
|
for (int k = 0; k < n; k++) {
|
|
if (c2_poll_outready()) return -8 - k;
|
|
uint8_t b;
|
|
if (c2_data_read(&b)) return -200 - k;
|
|
buf[k] = b;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// ----------------------------------------------------------------- write/erase
|
|
//
|
|
// SAFETY: the bootloader lives at 0x0000-0x23FF and the flash lock / reserved
|
|
// area at 0xFA00-0xFFFF. These are UNRECOVERABLE if erased (no image available).
|
|
// Every erase/write below is hard-limited to the application region
|
|
// 0x2400-0xF9FF. There is no Device Erase / mass-erase command anywhere in
|
|
// this firmware. The guards are both compile-time (constants) and runtime.
|
|
#define APP_LO 0x2400 // first application address (inclusive)
|
|
#define APP_HI 0xFA00 // first protected address (exclusive)
|
|
#define PAGE_BYTES 512
|
|
#define APP_PAGE_LO (APP_LO / PAGE_BYTES) // 0x12
|
|
#define APP_PAGE_HI ((APP_HI / PAGE_BYTES) - 1) // 0x7C (0xF800 page is last app page)
|
|
|
|
static int app_addr_ok(uint32_t a, uint32_t len) {
|
|
return a >= APP_LO && (a + len) <= APP_HI && len > 0;
|
|
}
|
|
static int app_page_ok(uint8_t page) {
|
|
return page >= APP_PAGE_LO && page <= APP_PAGE_HI;
|
|
}
|
|
|
|
// Poll until OutReady CLEARS (used by Page Erase step 8).
|
|
static int c2_poll_outready_clear(void) {
|
|
for (long i = 0; i < 400000L; i++) { // erase can take longer
|
|
uint8_t s = c2_status();
|
|
if (!(s & 1)) return 0; // OutReady cleared
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Direct Write (FPDAT 0x0A): write one SFR. Used for the pre-flash VDD-monitor
|
|
// / flash-timing / clock setup. No address guard (SFRs are 0x80-0xFF by
|
|
// definition; this only touches the four documented EFM8UB2 setup registers).
|
|
static int c2_direct_write(uint8_t sfr, uint8_t val) {
|
|
c2_addr_write(fpdat_addr);
|
|
if (c2_data_write(0x0A)) return -10; // Direct Write cmd
|
|
if (c2_poll_inbusy()) return -1;
|
|
if (c2_poll_outready()) return -2;
|
|
uint8_t st;
|
|
if (c2_data_read(&st)) return -3;
|
|
if (st != 0x0D) return -4;
|
|
if (c2_data_write(sfr)) return -11; // SFR address
|
|
if (c2_poll_inbusy()) return -5;
|
|
if (c2_data_write(0x01)) return -12; // length = 1 byte
|
|
if (c2_poll_inbusy()) return -6;
|
|
if (c2_data_write(val)) return -13; // SFR value
|
|
if (c2_poll_inbusy()) return -7;
|
|
return 0;
|
|
}
|
|
|
|
// EFM8UB2 pre-flash setup (AN127 Table 3.6): flash timing, enable VDD monitor,
|
|
// clock, supply-monitor reset source. Must run once after piinit, before any
|
|
// erase/write. Returns 0 on success.
|
|
static int c2_pgm_setup(void) {
|
|
if (c2_direct_write(0xB6, 0x90)) return -1; // FLSCL = 0x90 (flash timing)
|
|
if (c2_direct_write(0xFF, 0x80)) return -2; // VDM0CN = 0x80 (VDD mon enable)
|
|
if (c2_direct_write(0xA9, 0x03)) return -3; // CLKSEL = 0x03
|
|
if (c2_direct_write(0xEF, 0x02)) return -4; // RSTSRC = 0x02 (VDD mon reset src)
|
|
return 0;
|
|
}
|
|
|
|
// Page Erase (FPDAT 0x08). `page` = address / 512. Guarded to app region only.
|
|
static int c2_page_erase(uint8_t page) {
|
|
if (!app_page_ok(page)) return -100; // REFUSED: outside app region
|
|
uint8_t st;
|
|
c2_addr_write(fpdat_addr);
|
|
if (c2_data_write(0x08)) return -10; // Page Erase cmd
|
|
if (c2_poll_inbusy()) return -1;
|
|
if (c2_poll_outready()) return -2;
|
|
if (c2_data_read(&st)) return -3;
|
|
if (st != 0x0D) return -4;
|
|
if (c2_data_write(page)) return -11; // target page number
|
|
if (c2_poll_inbusy()) return -5;
|
|
// Page-number ack: the PI sets OutReady with a 0x0D status after consuming
|
|
// the page number. AN127 step 8 says "poll until clear" but on the EFM8UB2
|
|
// the byte is genuinely pending (OutReady stuck SET), so we poll until SET
|
|
// and read it -- mirroring the command-ack (steps 4-5) and completion-ack
|
|
// (steps 12-13). The 0x0D check below rejects any error status safely.
|
|
if (c2_poll_outready()) return -6; // OutReady -> 1 (ack ready)
|
|
if (c2_data_read(&st)) return -7;
|
|
if (st != 0x0D) return -8;
|
|
if (c2_data_write(0x00)) return -12; // initiate erase
|
|
if (c2_poll_inbusy()) return -9;
|
|
if (c2_poll_outready()) return -13; // OutReady -> 1 (done)
|
|
if (c2_data_read(&st)) return -14;
|
|
if (st != 0x0D) return -15;
|
|
return 0;
|
|
}
|
|
|
|
// Block Write (FPDAT 0x07): program `len` bytes (1..256, 0 => 256) at `addr`.
|
|
// addr..addr+len must lie inside the app region. Flash must be erased (0xFF)
|
|
// at the target first. Returns number of bytes written, or negative error.
|
|
static int c2_block_write(uint16_t addr, uint8_t len, const uint8_t *buf) {
|
|
int n = len ? (int)len : 256;
|
|
if (!app_addr_ok(addr, n)) return -100; // REFUSED: outside app region
|
|
uint8_t st;
|
|
c2_addr_write(fpdat_addr);
|
|
if (c2_data_write(0x07)) return -10; // Block Write cmd
|
|
if (c2_poll_inbusy()) return -1;
|
|
if (c2_poll_outready()) return -2;
|
|
if (c2_data_read(&st)) return -3;
|
|
if (st != 0x0D) return -4;
|
|
if (c2_data_write((addr >> 8) & 0xFF)) return -11; // addr high
|
|
if (c2_poll_inbusy()) return -5;
|
|
if (c2_data_write(addr & 0xFF)) return -12; // addr low
|
|
if (c2_poll_inbusy()) return -6;
|
|
if (c2_data_write(len)) return -13; // length code (0=256)
|
|
if (c2_poll_inbusy()) return -7;
|
|
for (int k = 0; k < n; k++) {
|
|
if (c2_data_write(buf[k])) return -200 - k; // data byte
|
|
if (c2_poll_inbusy()) return -8 - k;
|
|
}
|
|
if (c2_poll_outready()) return -14; // write complete
|
|
if (c2_data_read(&st)) return -15;
|
|
if (st != 0x0D) return -16;
|
|
return n;
|
|
}
|
|
|
|
// ----------------------------------------------------------------- command I/O
|
|
|
|
static int get_line(char *buf, int maxlen) {
|
|
int n = 0;
|
|
while (n < maxlen - 1) {
|
|
int c = getchar_timeout_us(1000);
|
|
if (c == PICO_ERROR_TIMEOUT) continue;
|
|
if (c < 0) continue;
|
|
if (c == '\r') continue;
|
|
if (c == '\n') break;
|
|
buf[n++] = (char)c;
|
|
}
|
|
buf[n] = 0;
|
|
return n;
|
|
}
|
|
|
|
static void print_hex(const uint8_t *p, int n) {
|
|
for (int k = 0; k < n; k++) printf("%02x", p[k]);
|
|
printf("\n");
|
|
}
|
|
|
|
static void do_read(uint16_t addr, uint8_t len) {
|
|
static uint8_t buf[256];
|
|
int rc = c2_block_read(addr, len, buf);
|
|
if (rc < 0) {
|
|
printf("err %d\n", rc);
|
|
return;
|
|
}
|
|
printf("data %04x %d ", addr, rc);
|
|
print_hex(buf, rc);
|
|
}
|
|
|
|
// (Re)configure the C2CK / C2D pins. Idles C2CK high and releases C2D.
|
|
static void c2_setup_pins(int ck, int d) {
|
|
C2CK = ck; C2D = d;
|
|
gpio_init(ck); gpio_set_dir(ck, GPIO_OUT); gpio_put(ck, 1);
|
|
gpio_init(d); gpio_set_dir(d, GPIO_OUT); gpio_put(d, 1);
|
|
c2d_release();
|
|
}
|
|
|
|
int main(void) {
|
|
stdio_init_all();
|
|
|
|
c2_setup_pins(2, 3); // default: GP2=C2CK, GP3=C2D
|
|
|
|
// Must hold the longest command: "bw ffff 0 " (10) + 512 hex chars = 522.
|
|
char line[1024];
|
|
while (true) {
|
|
int n = get_line(line, sizeof(line));
|
|
if (n == 0) { printf("ok\n"); continue; }
|
|
|
|
char cmd[32];
|
|
if (sscanf(line, "%31s", cmd) != 1) { printf("err parse\n"); continue; }
|
|
|
|
if (!strcmp(cmd, "hello")) {
|
|
printf("ok c2probe v2\n");
|
|
} else if (!strcmp(cmd, "bootsel")) {
|
|
printf("ok bootsel\n");
|
|
fflush(stdout);
|
|
busy_wait_us(5000);
|
|
reset_usb_boot(0, 0); // reboot into USB bootloader (BOOTSEL)
|
|
} else if (!strcmp(cmd, "speed")) {
|
|
unsigned us = 1;
|
|
sscanf(line, "%*s %u", &us);
|
|
half_us = (us > 1000) ? 1000 : us;
|
|
printf("ok speed %luus\n", (unsigned long)half_us);
|
|
} else if (!strcmp(cmd, "fpdat")) {
|
|
unsigned a = fpdat_addr;
|
|
sscanf(line, "%*s %x", &a);
|
|
fpdat_addr = a & 0xFF;
|
|
printf("ok fpdat %02x\n", fpdat_addr);
|
|
} else if (!strcmp(cmd, "pins")) {
|
|
int ck = C2CK, d = C2D;
|
|
if (sscanf(line, "%*s %i %i", &ck, &d) == 2) {
|
|
c2_setup_pins(ck, d);
|
|
}
|
|
printf("ok ck=gp%d d=gp%d\n", C2CK, C2D);
|
|
} else if (!strcmp(cmd, "reset")) {
|
|
c2_reset();
|
|
printf("ok reset\n");
|
|
} else if (!strcmp(cmd, "status")) {
|
|
printf("stat %02x\n", c2_status());
|
|
} else if (!strcmp(cmd, "rdreg")) {
|
|
unsigned a = 0;
|
|
sscanf(line, "%*s %x", &a);
|
|
printf("reg %02x\n", c2_reg_read(a & 0xFF));
|
|
} else if (!strcmp(cmd, "wrreg")) {
|
|
unsigned a = 0, v = 0;
|
|
sscanf(line, "%*s %x %x", &a, &v);
|
|
int rc = c2_reg_write(a & 0xFF, v & 0xFF);
|
|
printf("%s\n", rc ? "err wait" : "ok");
|
|
} else if (!strcmp(cmd, "id")) {
|
|
uint8_t d = c2_reg_read(0x00), r = c2_reg_read(0x01);
|
|
printf("id devid=%02x revid=%02x\n", d, r);
|
|
} else if (!strcmp(cmd, "id2")) {
|
|
// atomic: reset then read DEVICEID/REVID with no host round-trip
|
|
c2_reset();
|
|
uint8_t d = c2_reg_read(0x00), r = c2_reg_read(0x01);
|
|
printf("id2 devid=%02x revid=%02x\n", d, r);
|
|
} else if (!strcmp(cmd, "dbg")) {
|
|
// 3-state read of C2D: floating line follows the pull; a driven
|
|
// line ignores it. Reveals whether a slave is driving C2D.
|
|
gpio_set_dir(C2D, GPIO_IN);
|
|
gpio_disable_pulls(C2D); busy_wait_us(20); int none = c2d_get();
|
|
gpio_pull_up(C2D); busy_wait_us(20); int up = c2d_get();
|
|
gpio_disable_pulls(C2D);
|
|
gpio_pull_down(C2D); busy_wait_us(20); int dn = c2d_get();
|
|
gpio_disable_pulls(C2D);
|
|
printf("dbg c2d none=%d up=%d dn=%d c2ck=%d (float if up=1,dn=0)\n",
|
|
none, up, dn, gpio_get(C2CK));
|
|
} else if (!strcmp(cmd, "piinit")) {
|
|
c2_pi_init();
|
|
printf("ok piinit\n");
|
|
} else if (!strcmp(cmd, "rawaw")) {
|
|
unsigned a = 0; sscanf(line, "%*s %x", &a);
|
|
c2_addr_write(a & 0xFF);
|
|
printf("ok\n");
|
|
} else if (!strcmp(cmd, "rawar")) {
|
|
printf("ar %02x\n", c2_addr_read());
|
|
} else if (!strcmp(cmd, "rawdw")) {
|
|
unsigned v = 0; sscanf(line, "%*s %x", &v);
|
|
printf("%s\n", c2_data_write(v & 0xFF) ? "err wait" : "ok");
|
|
} else if (!strcmp(cmd, "rawdr")) {
|
|
uint8_t v = 0;
|
|
int rc = c2_data_read(&v);
|
|
printf("dr %02x %s\n", v, rc ? "err" : "ok");
|
|
} else if (!strcmp(cmd, "read")) {
|
|
unsigned a = 0; int l = 0;
|
|
sscanf(line, "%*s %x %i", &a, &l);
|
|
if (l < 0) l = 0;
|
|
if (l > 256) l = 256;
|
|
do_read(a & 0xFFFF, (uint8_t)l);
|
|
} else if (!strcmp(cmd, "dump")) {
|
|
unsigned s = 0, e = 0;
|
|
sscanf(line, "%*s %x %x", &s, &e);
|
|
if (e > 0x10000) e = 0x10000;
|
|
int blocks = 0;
|
|
for (unsigned a = s; a < e; ) {
|
|
int chunk = e - a;
|
|
if (chunk > 256) chunk = 256;
|
|
do_read(a & 0xFFFF, (uint8_t)chunk);
|
|
a += chunk;
|
|
blocks++;
|
|
}
|
|
printf("done %d\n", blocks);
|
|
} else if (!strcmp(cmd, "sfrw")) {
|
|
// Direct Write one SFR (0x80-0xFF). For the pre-flash setup only.
|
|
unsigned a = 0, v = 0;
|
|
sscanf(line, "%*s %x %x", &a, &v);
|
|
int rc = c2_direct_write(a & 0xFF, v & 0xFF);
|
|
printf("%s\n", rc ? "err" : "ok");
|
|
if (rc) printf("sfrw rc %d\n", rc);
|
|
} else if (!strcmp(cmd, "wsetup")) {
|
|
// EFM8UB2 pre-flash SFR setup (flash timing + VDD monitor + clock).
|
|
int rc = c2_pgm_setup();
|
|
printf("%s\n", rc ? "err" : "ok");
|
|
if (rc) printf("wsetup rc %d\n", rc);
|
|
} else if (!strcmp(cmd, "pe")) {
|
|
// Page Erase. Page number = addr/512. Guarded to app region.
|
|
unsigned pg = 0;
|
|
sscanf(line, "%*s %x", &pg);
|
|
int rc = c2_page_erase(pg & 0xFF);
|
|
if (rc == -100) printf("refused page %02x outside app region\n", pg & 0xFF);
|
|
else printf("%s\n", rc ? "err" : "ok");
|
|
if (rc && rc != -100) printf("pe rc %d\n", rc);
|
|
} else if (!strcmp(cmd, "bw")) {
|
|
// Block Write: bw <addr> <len> <hex...>. Guarded to app region.
|
|
unsigned a = 0; int l = 0;
|
|
char hex[600];
|
|
hex[0] = 0;
|
|
// "bw ADDR LEN HEXSTR"
|
|
int got = sscanf(line, "%*s %x %i %599s", &a, &l, hex);
|
|
if (got < 3) { printf("err bw usage\n"); }
|
|
else {
|
|
if (l < 0) l = 0;
|
|
if (l > 256) l = 256;
|
|
int n = l ? l : 256;
|
|
static uint8_t wbuf[256];
|
|
int hl = (int)strlen(hex);
|
|
if (hl < n * 2) { printf("err bw short hex (%d want %d)\n", hl, n * 2); }
|
|
else {
|
|
int ok = 1;
|
|
for (int k = 0; k < n; k++) {
|
|
unsigned b;
|
|
if (sscanf(hex + k * 2, "%2x", &b) != 1) { ok = 0; break; }
|
|
wbuf[k] = (uint8_t)b;
|
|
}
|
|
if (!ok) printf("err bw hex parse\n");
|
|
else {
|
|
int rc = c2_block_write(a & 0xFFFF, (uint8_t)l, wbuf);
|
|
if (rc == -100) printf("refused addr %04x outside app region\n", a & 0xFFFF);
|
|
else if (rc < 0) { printf("err\n"); printf("bw rc %d\n", rc); }
|
|
else printf("ok bw %04x %d\n", a & 0xFFFF, rc);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
printf("err unknown\n");
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
return 0;
|
|
} |