Postmortem Debugging to serial port (#20492)
This commit is contained in:
parent
61753bb5e3
commit
dc11874abe
@ -3732,3 +3732,10 @@
|
||||
|
||||
// Enable Marlin dev mode which adds some special commands
|
||||
//#define MARLIN_DEV_MODE
|
||||
|
||||
/**
|
||||
* Postmortem Debugging captures misbehavior and outputs the CPU status and backtrace to serial.
|
||||
* When running in the debugger it will break for debugging. This is useful to help understand
|
||||
* a crash from a remote location. Requires ~400 bytes of SRAM and 5Kb of flash.
|
||||
*/
|
||||
//#define POSTMORTEM_DEBUGGING
|
||||
|
@ -56,3 +56,10 @@
|
||||
#if BOTH(HAS_TMC_SW_SERIAL, MONITOR_DRIVER_STATUS)
|
||||
#error "MONITOR_DRIVER_STATUS causes performance issues when used with SoftwareSerial-connected drivers. Disable MONITOR_DRIVER_STATUS or use hardware serial to continue."
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Postmortem debugging
|
||||
*/
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
#error "POSTMORTEM_DEBUGGING is not supported on AVR boards."
|
||||
#endif
|
||||
|
@ -1,342 +0,0 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_SAM
|
||||
|
||||
#include "../../core/macros.h"
|
||||
#include "../../core/serial.h"
|
||||
|
||||
#include "../shared/backtrace/unwinder.h"
|
||||
#include "../shared/backtrace/unwmemaccess.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
// Debug monitor that dumps to the Programming port all status when
|
||||
// an exception or WDT timeout happens - And then resets the board
|
||||
|
||||
// All the Monitor routines must run with interrupts disabled and
|
||||
// under an ISR execution context. That is why we cannot reuse the
|
||||
// Serial interrupt routines or any C runtime, as we don't know the
|
||||
// state we are when running them
|
||||
|
||||
// A SW memory barrier, to ensure GCC does not overoptimize loops
|
||||
#define sw_barrier() __asm__ volatile("": : :"memory");
|
||||
|
||||
// (re)initialize UART0 as a monitor output to 250000,n,8,1
|
||||
static void TXBegin() {
|
||||
|
||||
// Disable UART interrupt in NVIC
|
||||
NVIC_DisableIRQ( UART_IRQn );
|
||||
|
||||
// We NEED memory barriers to ensure Interrupts are actually disabled!
|
||||
// ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the )
|
||||
__DSB();
|
||||
__ISB();
|
||||
|
||||
// Disable clock
|
||||
pmc_disable_periph_clk( ID_UART );
|
||||
|
||||
// Configure PMC
|
||||
pmc_enable_periph_clk( ID_UART );
|
||||
|
||||
// Disable PDC channel
|
||||
UART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
|
||||
|
||||
// Reset and disable receiver and transmitter
|
||||
UART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS;
|
||||
|
||||
// Configure mode: 8bit, No parity, 1 bit stop
|
||||
UART->UART_MR = UART_MR_CHMODE_NORMAL | US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO;
|
||||
|
||||
// Configure baudrate (asynchronous, no oversampling) to BAUDRATE bauds
|
||||
UART->UART_BRGR = (SystemCoreClock / (BAUDRATE << 4));
|
||||
|
||||
// Enable receiver and transmitter
|
||||
UART->UART_CR = UART_CR_RXEN | UART_CR_TXEN;
|
||||
}
|
||||
|
||||
// Send character through UART with no interrupts
|
||||
static void TX(char c) {
|
||||
while (!(UART->UART_SR & UART_SR_TXRDY)) { WDT_Restart(WDT); sw_barrier(); };
|
||||
UART->UART_THR = c;
|
||||
}
|
||||
|
||||
// Send String through UART
|
||||
static void TX(const char* s) {
|
||||
while (*s) TX(*s++);
|
||||
}
|
||||
|
||||
static void TXDigit(uint32_t d) {
|
||||
if (d < 10) TX((char)(d+'0'));
|
||||
else if (d < 16) TX((char)(d+'A'-10));
|
||||
else TX('?');
|
||||
}
|
||||
|
||||
// Send Hex number thru UART
|
||||
static void TXHex(uint32_t v) {
|
||||
TX("0x");
|
||||
for (uint8_t i = 0; i < 8; i++, v <<= 4)
|
||||
TXDigit((v >> 28) & 0xF);
|
||||
}
|
||||
|
||||
// Send Decimal number thru UART
|
||||
static void TXDec(uint32_t v) {
|
||||
if (!v) {
|
||||
TX('0');
|
||||
return;
|
||||
}
|
||||
|
||||
char nbrs[14];
|
||||
char *p = &nbrs[0];
|
||||
while (v != 0) {
|
||||
*p++ = '0' + (v % 10);
|
||||
v /= 10;
|
||||
}
|
||||
do {
|
||||
p--;
|
||||
TX(*p);
|
||||
} while (p != &nbrs[0]);
|
||||
}
|
||||
|
||||
// Dump a backtrace entry
|
||||
static bool UnwReportOut(void* ctx, const UnwReport* bte) {
|
||||
int* p = (int*)ctx;
|
||||
|
||||
(*p)++;
|
||||
TX('#'); TXDec(*p); TX(" : ");
|
||||
TX(bte->name?bte->name:"unknown"); TX('@'); TXHex(bte->function);
|
||||
TX('+'); TXDec(bte->address - bte->function);
|
||||
TX(" PC:");TXHex(bte->address); TX('\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef UNW_DEBUG
|
||||
void UnwPrintf(const char* format, ...) {
|
||||
char dest[256];
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vsprintf(dest, format, argptr);
|
||||
va_end(argptr);
|
||||
TX(&dest[0]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Table of function pointers for passing to the unwinder */
|
||||
static const UnwindCallbacks UnwCallbacks = {
|
||||
UnwReportOut,
|
||||
UnwReadW,
|
||||
UnwReadH,
|
||||
UnwReadB
|
||||
#ifdef UNW_DEBUG
|
||||
, UnwPrintf
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* HardFaultHandler_C:
|
||||
* This is called from the HardFault_HandlerAsm with a pointer the Fault stack
|
||||
* as the parameter. We can then read the values from the stack and place them
|
||||
* into local variables for ease of reading.
|
||||
* We then read the various Fault Status and Address Registers to help decode
|
||||
* cause of the fault.
|
||||
* The function ends with a BKPT instruction to force control back into the debugger
|
||||
*/
|
||||
extern "C"
|
||||
void HardFault_HandlerC(unsigned long *sp, unsigned long lr, unsigned long cause) {
|
||||
|
||||
static const char* causestr[] = {
|
||||
"NMI","Hard","Mem","Bus","Usage","Debug","WDT","RSTC"
|
||||
};
|
||||
|
||||
UnwindFrame btf;
|
||||
|
||||
// Dump report to the Programming port (interrupts are DISABLED)
|
||||
TXBegin();
|
||||
TX("\n\n## Software Fault detected ##\n");
|
||||
TX("Cause: "); TX(causestr[cause]); TX('\n');
|
||||
|
||||
TX("R0 : "); TXHex(((unsigned long)sp[0])); TX('\n');
|
||||
TX("R1 : "); TXHex(((unsigned long)sp[1])); TX('\n');
|
||||
TX("R2 : "); TXHex(((unsigned long)sp[2])); TX('\n');
|
||||
TX("R3 : "); TXHex(((unsigned long)sp[3])); TX('\n');
|
||||
TX("R12 : "); TXHex(((unsigned long)sp[4])); TX('\n');
|
||||
TX("LR : "); TXHex(((unsigned long)sp[5])); TX('\n');
|
||||
TX("PC : "); TXHex(((unsigned long)sp[6])); TX('\n');
|
||||
TX("PSR : "); TXHex(((unsigned long)sp[7])); TX('\n');
|
||||
|
||||
// Configurable Fault Status Register
|
||||
// Consists of MMSR, BFSR and UFSR
|
||||
TX("CFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED28)))); TX('\n');
|
||||
|
||||
// Hard Fault Status Register
|
||||
TX("HFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED2C)))); TX('\n');
|
||||
|
||||
// Debug Fault Status Register
|
||||
TX("DFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED30)))); TX('\n');
|
||||
|
||||
// Auxiliary Fault Status Register
|
||||
TX("AFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED3C)))); TX('\n');
|
||||
|
||||
// Read the Fault Address Registers. These may not contain valid values.
|
||||
// Check BFARVALID/MMARVALID to see if they are valid values
|
||||
// MemManage Fault Address Register
|
||||
TX("MMAR : "); TXHex((*((volatile unsigned long *)(0xE000ED34)))); TX('\n');
|
||||
|
||||
// Bus Fault Address Register
|
||||
TX("BFAR : "); TXHex((*((volatile unsigned long *)(0xE000ED38)))); TX('\n');
|
||||
|
||||
TX("ExcLR: "); TXHex(lr); TX('\n');
|
||||
TX("ExcSP: "); TXHex((unsigned long)sp); TX('\n');
|
||||
|
||||
btf.sp = ((unsigned long)sp) + 8*4; // The original stack pointer
|
||||
btf.fp = btf.sp;
|
||||
btf.lr = ((unsigned long)sp[5]);
|
||||
btf.pc = ((unsigned long)sp[6]) | 1; // Force Thumb, as CORTEX only support it
|
||||
|
||||
// Perform a backtrace
|
||||
TX("\nBacktrace:\n\n");
|
||||
int ctr = 0;
|
||||
UnwindStart(&btf, &UnwCallbacks, &ctr);
|
||||
|
||||
// Disable all NVIC interrupts
|
||||
NVIC->ICER[0] = 0xFFFFFFFF;
|
||||
NVIC->ICER[1] = 0xFFFFFFFF;
|
||||
|
||||
// Relocate VTOR table to default position
|
||||
SCB->VTOR = 0;
|
||||
|
||||
// Disable USB
|
||||
otg_disable();
|
||||
|
||||
// Restart watchdog
|
||||
WDT_Restart(WDT);
|
||||
|
||||
// Reset controller
|
||||
NVIC_SystemReset();
|
||||
for (;;) WDT_Restart(WDT);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void NMI_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#0")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void HardFault_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#1")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void MemManage_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#2")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void BusFault_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#3")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void UsageFault_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#4")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void DebugMon_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#5")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
/* This is NOT an exception, it is an interrupt handler - Nevertheless, the framing is the same */
|
||||
__attribute__((naked)) void WDT_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#6")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void RSTC_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#7")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_SAM
|
@ -40,6 +40,8 @@ uint16_t HAL_adc_result;
|
||||
// Public functions
|
||||
// ------------------------
|
||||
|
||||
TERN_(POSTMORTEM_DEBUGGING, extern void install_min_serial());
|
||||
|
||||
// HAL initialization task
|
||||
void HAL_init() {
|
||||
// Initialize the USB stack
|
||||
@ -47,6 +49,7 @@ void HAL_init() {
|
||||
OUT_WRITE(SDSS, HIGH); // Try to set SDSS inactive before any other SPI users start up
|
||||
#endif
|
||||
usb_task_init();
|
||||
TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the min serial handler
|
||||
}
|
||||
|
||||
// HAL idle task
|
||||
|
91
Marlin/src/HAL/DUE/HAL_MinSerial.cpp
Normal file
91
Marlin/src/HAL/DUE/HAL_MinSerial.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_SAM
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
|
||||
#include "../shared/HAL_MinSerial.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
static void TXBegin() {
|
||||
// Disable UART interrupt in NVIC
|
||||
NVIC_DisableIRQ( UART_IRQn );
|
||||
|
||||
// We NEED memory barriers to ensure Interrupts are actually disabled!
|
||||
// ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the )
|
||||
__DSB();
|
||||
__ISB();
|
||||
|
||||
// Disable clock
|
||||
pmc_disable_periph_clk( ID_UART );
|
||||
|
||||
// Configure PMC
|
||||
pmc_enable_periph_clk( ID_UART );
|
||||
|
||||
// Disable PDC channel
|
||||
UART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
|
||||
|
||||
// Reset and disable receiver and transmitter
|
||||
UART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS;
|
||||
|
||||
// Configure mode: 8bit, No parity, 1 bit stop
|
||||
UART->UART_MR = UART_MR_CHMODE_NORMAL | US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO;
|
||||
|
||||
// Configure baudrate (asynchronous, no oversampling) to BAUDRATE bauds
|
||||
UART->UART_BRGR = (SystemCoreClock / (BAUDRATE << 4));
|
||||
|
||||
// Enable receiver and transmitter
|
||||
UART->UART_CR = UART_CR_RXEN | UART_CR_TXEN;
|
||||
}
|
||||
|
||||
// A SW memory barrier, to ensure GCC does not overoptimize loops
|
||||
#define sw_barrier() __asm__ volatile("": : :"memory");
|
||||
static void TX(char c) {
|
||||
while (!(UART->UART_SR & UART_SR_TXRDY)) { WDT_Restart(WDT); sw_barrier(); };
|
||||
UART->UART_THR = c;
|
||||
}
|
||||
|
||||
void install_min_serial() {
|
||||
HAL_min_serial_init = &TXBegin;
|
||||
HAL_min_serial_out = &TX;
|
||||
}
|
||||
|
||||
#if DISABLED(DYNAMIC_VECTORTABLE)
|
||||
extern "C" {
|
||||
__attribute__((naked)) void JumpHandler_ASM() {
|
||||
__asm__ __volatile__ (
|
||||
"b CommonHandler_ASM\n"
|
||||
);
|
||||
}
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) HardFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) BusFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) UsageFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) MemManage_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) NMI_Handler();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // POSTMORTEM_DEBUGGING
|
||||
#endif // ARDUINO_ARCH_SAM
|
@ -57,5 +57,5 @@
|
||||
#endif
|
||||
|
||||
#if HAS_TMC_SW_SERIAL
|
||||
#error "TMC220x Software Serial is not supported on this platform."
|
||||
#error "TMC220x Software Serial is not supported on the DUE platform."
|
||||
#endif
|
||||
|
@ -30,9 +30,13 @@
|
||||
#endif
|
||||
|
||||
#if HAS_TMC_SW_SERIAL
|
||||
#error "TMC220x Software Serial is not supported on this platform."
|
||||
#error "TMC220x Software Serial is not supported on ESP32."
|
||||
#endif
|
||||
|
||||
#if BOTH(WIFISUPPORT, ESP3D_WIFISUPPORT)
|
||||
#error "Only enable one WiFi option, either WIFISUPPORT or ESP3D_WIFISUPPORT."
|
||||
#endif
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
#error "POSTMORTEM_DEBUGGING is not yet supported on ESP32."
|
||||
#endif
|
||||
|
@ -35,5 +35,9 @@
|
||||
#endif
|
||||
|
||||
#if HAS_TMC_SW_SERIAL
|
||||
#error "TMC220x Software Serial is not supported on this platform."
|
||||
#error "TMC220x Software Serial is not supported on LINUX."
|
||||
#endif
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
#error "POSTMORTEM_DEBUGGING is not yet supported on LINUX."
|
||||
#endif
|
||||
|
@ -1,322 +0,0 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifdef TARGET_LPC1768
|
||||
|
||||
#include "../../core/macros.h"
|
||||
#include "../../core/serial.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "../shared/backtrace/unwinder.h"
|
||||
#include "../shared/backtrace/unwmemaccess.h"
|
||||
#include "watchdog.h"
|
||||
#include <debug_frmwrk.h>
|
||||
|
||||
|
||||
// Debug monitor that dumps to the Programming port all status when
|
||||
// an exception or WDT timeout happens - And then resets the board
|
||||
|
||||
// All the Monitor routines must run with interrupts disabled and
|
||||
// under an ISR execution context. That is why we cannot reuse the
|
||||
// Serial interrupt routines or any C runtime, as we don't know the
|
||||
// state we are when running them
|
||||
|
||||
// A SW memory barrier, to ensure GCC does not overoptimize loops
|
||||
#define sw_barrier() __asm__ volatile("": : :"memory");
|
||||
|
||||
// (re)initialize UART0 as a monitor output to 250000,n,8,1
|
||||
static void TXBegin() {
|
||||
}
|
||||
|
||||
// Send character through UART with no interrupts
|
||||
static void TX(char c) {
|
||||
_DBC(c);
|
||||
}
|
||||
|
||||
// Send String through UART
|
||||
static void TX(const char* s) {
|
||||
while (*s) TX(*s++);
|
||||
}
|
||||
|
||||
static void TXDigit(uint32_t d) {
|
||||
if (d < 10) TX((char)(d+'0'));
|
||||
else if (d < 16) TX((char)(d+'A'-10));
|
||||
else TX('?');
|
||||
}
|
||||
|
||||
// Send Hex number thru UART
|
||||
static void TXHex(uint32_t v) {
|
||||
TX("0x");
|
||||
for (uint8_t i = 0; i < 8; i++, v <<= 4)
|
||||
TXDigit((v >> 28) & 0xF);
|
||||
}
|
||||
|
||||
// Send Decimal number thru UART
|
||||
static void TXDec(uint32_t v) {
|
||||
if (!v) {
|
||||
TX('0');
|
||||
return;
|
||||
}
|
||||
|
||||
char nbrs[14];
|
||||
char *p = &nbrs[0];
|
||||
while (v != 0) {
|
||||
*p++ = '0' + (v % 10);
|
||||
v /= 10;
|
||||
}
|
||||
do {
|
||||
p--;
|
||||
TX(*p);
|
||||
} while (p != &nbrs[0]);
|
||||
}
|
||||
|
||||
// Dump a backtrace entry
|
||||
static bool UnwReportOut(void* ctx, const UnwReport* bte) {
|
||||
int* p = (int*)ctx;
|
||||
|
||||
(*p)++;
|
||||
TX('#'); TXDec(*p); TX(" : ");
|
||||
TX(bte->name?bte->name:"unknown"); TX('@'); TXHex(bte->function);
|
||||
TX('+'); TXDec(bte->address - bte->function);
|
||||
TX(" PC:");TXHex(bte->address); TX('\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef UNW_DEBUG
|
||||
void UnwPrintf(const char* format, ...) {
|
||||
char dest[256];
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vsprintf(dest, format, argptr);
|
||||
va_end(argptr);
|
||||
TX(&dest[0]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Table of function pointers for passing to the unwinder */
|
||||
static const UnwindCallbacks UnwCallbacks = {
|
||||
UnwReportOut,
|
||||
UnwReadW,
|
||||
UnwReadH,
|
||||
UnwReadB
|
||||
#ifdef UNW_DEBUG
|
||||
,UnwPrintf
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* HardFaultHandler_C:
|
||||
* This is called from the HardFault_HandlerAsm with a pointer the Fault stack
|
||||
* as the parameter. We can then read the values from the stack and place them
|
||||
* into local variables for ease of reading.
|
||||
* We then read the various Fault Status and Address Registers to help decode
|
||||
* cause of the fault.
|
||||
* The function ends with a BKPT instruction to force control back into the debugger
|
||||
*/
|
||||
extern "C"
|
||||
void HardFault_HandlerC(unsigned long *sp, unsigned long lr, unsigned long cause) {
|
||||
|
||||
static const char* causestr[] = {
|
||||
"NMI","Hard","Mem","Bus","Usage","Debug","WDT","RSTC"
|
||||
};
|
||||
|
||||
UnwindFrame btf;
|
||||
|
||||
// Dump report to the Programming port (interrupts are DISABLED)
|
||||
TXBegin();
|
||||
TX("\n\n## Software Fault detected ##\n");
|
||||
TX("Cause: "); TX(causestr[cause]); TX('\n');
|
||||
|
||||
TX("R0 : "); TXHex(((unsigned long)sp[0])); TX('\n');
|
||||
TX("R1 : "); TXHex(((unsigned long)sp[1])); TX('\n');
|
||||
TX("R2 : "); TXHex(((unsigned long)sp[2])); TX('\n');
|
||||
TX("R3 : "); TXHex(((unsigned long)sp[3])); TX('\n');
|
||||
TX("R12 : "); TXHex(((unsigned long)sp[4])); TX('\n');
|
||||
TX("LR : "); TXHex(((unsigned long)sp[5])); TX('\n');
|
||||
TX("PC : "); TXHex(((unsigned long)sp[6])); TX('\n');
|
||||
TX("PSR : "); TXHex(((unsigned long)sp[7])); TX('\n');
|
||||
|
||||
// Configurable Fault Status Register
|
||||
// Consists of MMSR, BFSR and UFSR
|
||||
TX("CFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED28)))); TX('\n');
|
||||
|
||||
// Hard Fault Status Register
|
||||
TX("HFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED2C)))); TX('\n');
|
||||
|
||||
// Debug Fault Status Register
|
||||
TX("DFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED30)))); TX('\n');
|
||||
|
||||
// Auxiliary Fault Status Register
|
||||
TX("AFSR : "); TXHex((*((volatile unsigned long *)(0xE000ED3C)))); TX('\n');
|
||||
|
||||
// Read the Fault Address Registers. These may not contain valid values.
|
||||
// Check BFARVALID/MMARVALID to see if they are valid values
|
||||
// MemManage Fault Address Register
|
||||
TX("MMAR : "); TXHex((*((volatile unsigned long *)(0xE000ED34)))); TX('\n');
|
||||
|
||||
// Bus Fault Address Register
|
||||
TX("BFAR : "); TXHex((*((volatile unsigned long *)(0xE000ED38)))); TX('\n');
|
||||
|
||||
TX("ExcLR: "); TXHex(lr); TX('\n');
|
||||
TX("ExcSP: "); TXHex((unsigned long)sp); TX('\n');
|
||||
|
||||
btf.sp = ((unsigned long)sp) + 8*4; // The original stack pointer
|
||||
btf.fp = btf.sp;
|
||||
btf.lr = ((unsigned long)sp[5]);
|
||||
btf.pc = ((unsigned long)sp[6]) | 1; // Force Thumb, as CORTEX only support it
|
||||
|
||||
// Perform a backtrace
|
||||
TX("\nBacktrace:\n\n");
|
||||
int ctr = 0;
|
||||
UnwindStart(&btf, &UnwCallbacks, &ctr);
|
||||
|
||||
// Disable all NVIC interrupts
|
||||
NVIC->ICER[0] = 0xFFFFFFFF;
|
||||
NVIC->ICER[1] = 0xFFFFFFFF;
|
||||
|
||||
// Relocate VTOR table to default position
|
||||
SCB->VTOR = 0;
|
||||
|
||||
// Clear cause of reset to prevent entering smoothie bootstrap
|
||||
HAL_clear_reset_source();
|
||||
|
||||
// Restart watchdog
|
||||
#if ENABLED(USE_WATCHDOG)
|
||||
//WDT_Restart(WDT);
|
||||
watchdog_init();
|
||||
#endif
|
||||
|
||||
// Reset controller
|
||||
NVIC_SystemReset();
|
||||
|
||||
// Nothing below here is compiled because NVIC_SystemReset loops forever
|
||||
|
||||
for (;;) { TERN_(USE_WATCHDOG, watchdog_init()); }
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
__attribute__((naked)) void NMI_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#0")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void HardFault_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#1")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void MemManage_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#2")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void BusFault_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#3")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void UsageFault_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#4")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void DebugMon_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#5")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
/* This is NOT an exception, it is an interrupt handler - Nevertheless, the framing is the same */
|
||||
__attribute__((naked)) void WDT_IRQHandler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#6")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
|
||||
__attribute__((naked)) void RSTC_Handler() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified" "\n\t"
|
||||
A("tst lr, #4")
|
||||
A("ite eq")
|
||||
A("mrseq r0, msp")
|
||||
A("mrsne r0, psp")
|
||||
A("mov r1,lr")
|
||||
A("mov r2,#7")
|
||||
A("b HardFault_HandlerC")
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif // TARGET_LPC1768
|
50
Marlin/src/HAL/LPC1768/HAL_MinSerial.cpp
Normal file
50
Marlin/src/HAL/LPC1768/HAL_MinSerial.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifdef TARGET_LPC1768
|
||||
|
||||
#include "HAL.h"
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
|
||||
#include "../shared/HAL_MinSerial.h"
|
||||
#include <debug_frmwrk.h>
|
||||
|
||||
static void TX(char c) { _DBC(c); }
|
||||
void install_min_serial() { HAL_min_serial_out = &TX; }
|
||||
|
||||
#if DISABLED(DYNAMIC_VECTORTABLE)
|
||||
extern "C" {
|
||||
__attribute__((naked)) void JumpHandler_ASM() {
|
||||
__asm__ __volatile__ (
|
||||
"b CommonHandler_ASM\n"
|
||||
);
|
||||
}
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) HardFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) BusFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) UsageFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) MemManage_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"))) NMI_Handler();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // POSTMORTEM_DEBUGGING
|
||||
#endif // TARGET_LPC1768
|
@ -270,7 +270,7 @@ static_assert(DISABLED(BAUD_RATE_GCODE), "BAUD_RATE_GCODE is not yet supported o
|
||||
#endif
|
||||
|
||||
#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
|
||||
#error "SERIAL_STATS_MAX_RX_QUEUED is not supported on this platform."
|
||||
#error "SERIAL_STATS_MAX_RX_QUEUED is not supported on LPC176x."
|
||||
#elif ENABLED(SERIAL_STATS_DROPPED_RX)
|
||||
#error "SERIAL_STATS_DROPPED_RX is not supported on this platform."
|
||||
#error "SERIAL_STATS_DROPPED_RX is not supported on LPX176x."
|
||||
#endif
|
||||
|
@ -46,6 +46,8 @@ extern "C" {
|
||||
|
||||
void SysTick_Callback() { disk_timerproc(); }
|
||||
|
||||
TERN_(POSTMORTEM_DEBUGGING, extern void install_min_serial());
|
||||
|
||||
void HAL_init() {
|
||||
|
||||
// Init LEDs
|
||||
@ -123,9 +125,7 @@ void HAL_init() {
|
||||
delay(1000); // Give OS time to notice
|
||||
USB_Connect(true);
|
||||
|
||||
#if HAS_SD_HOST_DRIVE
|
||||
MSC_SD_Init(0); // Enable USB SD card access
|
||||
#endif
|
||||
TERN_(HAS_SD_HOST_DRIVE, MSC_SD_Init(0)); // Enable USB SD card access
|
||||
|
||||
const millis_t usb_timeout = millis() + 2000;
|
||||
while (!USB_Configuration && PENDING(millis(), usb_timeout)) {
|
||||
@ -137,6 +137,8 @@ void HAL_init() {
|
||||
}
|
||||
|
||||
HAL_timer_init();
|
||||
|
||||
TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the min serial handler
|
||||
}
|
||||
|
||||
// HAL idle task
|
||||
|
@ -57,6 +57,8 @@ uint16_t HAL_adc_result;
|
||||
// Public functions
|
||||
// ------------------------
|
||||
|
||||
TERN_(POSTMORTEM_DEBUGGING, extern void install_min_serial());
|
||||
|
||||
// HAL initialization task
|
||||
void HAL_init() {
|
||||
FastIO_init();
|
||||
@ -83,6 +85,8 @@ void HAL_init() {
|
||||
USB_Hook_init();
|
||||
#endif
|
||||
|
||||
TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the min serial handler
|
||||
|
||||
#if HAS_SD_HOST_DRIVE
|
||||
MSC_SD_init(); // Enable USB SD card access
|
||||
#endif
|
||||
|
152
Marlin/src/HAL/STM32/HAL_MinSerial.cpp
Normal file
152
Marlin/src/HAL/STM32/HAL_MinSerial.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
* Copyright (c) 2017 Victor Perez
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_STM32) && !defined(STM32GENERIC)
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
|
||||
#include "../shared/HAL_MinSerial.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
/* Instruction Synchronization Barrier */
|
||||
#define isb() __asm__ __volatile__ ("isb" : : : "memory")
|
||||
|
||||
/* Data Synchronization Barrier */
|
||||
#define dsb() __asm__ __volatile__ ("dsb" : : : "memory")
|
||||
|
||||
// Dumb mapping over the registers of a USART device on STM32
|
||||
struct USARTMin {
|
||||
volatile uint32_t SR;
|
||||
volatile uint32_t DR;
|
||||
volatile uint32_t BRR;
|
||||
volatile uint32_t CR1;
|
||||
volatile uint32_t CR2;
|
||||
};
|
||||
|
||||
#if WITHIN(SERIAL_PORT, 1, 6)
|
||||
// Depending on the CPU, the serial port is different for USART1
|
||||
static const uintptr_t regsAddr[] = {
|
||||
TERN(STM32F1xx, 0x40013800, 0x40011000), // USART1
|
||||
0x40004400, // USART2
|
||||
0x40004800, // USART3
|
||||
0x40004C00, // UART4_BASE
|
||||
0x40005000, // UART5_BASE
|
||||
0x40011400 // USART6
|
||||
};
|
||||
static USARTMin * regs = (USARTMin*)regsAddr[SERIAL_PORT - 1];
|
||||
#endif
|
||||
|
||||
static void TXBegin() {
|
||||
#if !WITHIN(SERIAL_PORT, 1, 6)
|
||||
#warning "Using POSTMORTEM_DEBUGGING requires a physical U(S)ART hardware in case of severe error."
|
||||
#warning "Disabling the severe error reporting feature currently because the used serial port is not a HW port."
|
||||
#else
|
||||
// This is common between STM32F1/STM32F2 and STM32F4
|
||||
const int nvicUART[] = { /* NVIC_USART1 */ 37, /* NVIC_USART2 */ 38, /* NVIC_USART3 */ 39, /* NVIC_UART4 */ 52, /* NVIC_UART5 */ 53, /* NVIC_USART6 */ 71 };
|
||||
int nvicIndex = nvicUART[SERIAL_PORT - 1];
|
||||
|
||||
struct NVICMin {
|
||||
volatile uint32_t ISER[32];
|
||||
volatile uint32_t ICER[32];
|
||||
};
|
||||
|
||||
NVICMin * nvicBase = (NVICMin*)0xE000E100;
|
||||
nvicBase->ICER[nvicIndex / 32] |= _BV32(nvicIndex % 32);
|
||||
|
||||
// We NEED memory barriers to ensure Interrupts are actually disabled!
|
||||
// ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the )
|
||||
dsb();
|
||||
isb();
|
||||
|
||||
// Example for USART1 disable: (RCC->APB2ENR &= ~(RCC_APB2ENR_USART1EN))
|
||||
// Too difficult to reimplement here, let's query the STM32duino macro here
|
||||
#if SERIAL_PORT == 1
|
||||
__HAL_RCC_USART1_CLK_DISABLE();
|
||||
__HAL_RCC_USART1_CLK_ENABLE();
|
||||
#elif SERIAL_PORT == 2
|
||||
__HAL_RCC_USART2_CLK_DISABLE();
|
||||
__HAL_RCC_USART2_CLK_ENABLE();
|
||||
#elif SERIAL_PORT == 3
|
||||
__HAL_RCC_USART3_CLK_DISABLE();
|
||||
__HAL_RCC_USART3_CLK_ENABLE();
|
||||
#elif SERIAL_PORT == 4
|
||||
__HAL_RCC_UART4_CLK_DISABLE(); // BEWARE: UART4 and not USART4 here
|
||||
__HAL_RCC_UART4_CLK_ENABLE();
|
||||
#elif SERIAL_PORT == 5
|
||||
__HAL_RCC_UART5_CLK_DISABLE(); // BEWARE: UART5 and not USART5 here
|
||||
__HAL_RCC_UART5_CLK_ENABLE();
|
||||
#elif SERIAL_PORT == 6
|
||||
__HAL_RCC_USART6_CLK_DISABLE();
|
||||
__HAL_RCC_USART6_CLK_ENABLE();
|
||||
#endif
|
||||
|
||||
uint32_t brr = regs->BRR;
|
||||
regs->CR1 = 0; // Reset the USART
|
||||
regs->CR2 = 0; // 1 stop bit
|
||||
|
||||
// If we don't touch the BRR (baudrate register), we don't need to recompute.
|
||||
regs->BRR = brr;
|
||||
|
||||
regs->CR1 = _BV(3) | _BV(13); // 8 bits, no parity, 1 stop bit (TE | UE)
|
||||
#endif
|
||||
}
|
||||
|
||||
// A SW memory barrier, to ensure GCC does not overoptimize loops
|
||||
#define sw_barrier() __asm__ volatile("": : :"memory");
|
||||
static void TX(char c) {
|
||||
#if WITHIN(SERIAL_PORT, 1, 6)
|
||||
constexpr uint32_t usart_sr_txe = _BV(7);
|
||||
while (!(regs->SR & usart_sr_txe)) {
|
||||
TERN_(USE_WATCHDOG, HAL_watchdog_refresh());
|
||||
sw_barrier();
|
||||
}
|
||||
regs->DR = c;
|
||||
#else
|
||||
// Let's hope a mystical guru will fix this, one day by writting interrupt-free USB CDC ACM code (or, at least, by polling the registers since interrupt will be queued but will never trigger)
|
||||
// For now, it's completely lost to oblivion.
|
||||
#endif
|
||||
}
|
||||
|
||||
void install_min_serial() {
|
||||
HAL_min_serial_init = &TXBegin;
|
||||
HAL_min_serial_out = &TX;
|
||||
}
|
||||
|
||||
#if DISABLED(DYNAMIC_VECTORTABLE) && DISABLED(STM32F0xx) // Cortex M0 can't jump to a symbol that's too far from the current function, so we work around this in exception_arm.cpp
|
||||
extern "C" {
|
||||
__attribute__((naked)) void JumpHandler_ASM() {
|
||||
__asm__ __volatile__ (
|
||||
"b CommonHandler_ASM\n"
|
||||
);
|
||||
}
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) HardFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) BusFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) UsageFault_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) MemManage_Handler();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) NMI_Handler();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // POSTMORTEM_DEBUGGING
|
||||
#endif // ARDUINO_ARCH_STM32
|
@ -47,9 +47,9 @@
|
||||
#endif
|
||||
|
||||
#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
|
||||
#error "SERIAL_STATS_MAX_RX_QUEUED is not supported on this platform."
|
||||
#error "SERIAL_STATS_MAX_RX_QUEUED is not supported on STM32."
|
||||
#elif ENABLED(SERIAL_STATS_DROPPED_RX)
|
||||
#error "SERIAL_STATS_DROPPED_RX is not supported on this platform."
|
||||
#error "SERIAL_STATS_DROPPED_RX is not supported on STM32."
|
||||
#endif
|
||||
|
||||
#if ANY(TFT_COLOR_UI, TFT_LVGL_UI, TFT_CLASSIC_UI) && NOT_TARGET(STM32F4xx, STM32F1xx)
|
||||
|
@ -272,6 +272,8 @@ static void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) {
|
||||
} }
|
||||
#endif
|
||||
|
||||
TERN_(POSTMORTEM_DEBUGGING, extern void install_min_serial());
|
||||
|
||||
void HAL_init() {
|
||||
NVIC_SetPriorityGrouping(0x3);
|
||||
#if PIN_EXISTS(LED)
|
||||
@ -287,6 +289,7 @@ void HAL_init() {
|
||||
delay(1000); // Give OS time to notice
|
||||
OUT_WRITE(USB_CONNECT_PIN, USB_CONNECT_INVERTING);
|
||||
#endif
|
||||
TERN_(POSTMORTEM_DEBUGGING, install_min_serial()); // Install the minimal serial handler
|
||||
}
|
||||
|
||||
// HAL idle task
|
||||
|
118
Marlin/src/HAL/STM32F1/HAL_MinSerial.cpp
Normal file
118
Marlin/src/HAL/STM32F1/HAL_MinSerial.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
* Copyright (c) 2017 Victor Perez
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#ifdef __STM32F1__
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
|
||||
#include "../shared/HAL_MinSerial.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
#include <libmaple/usart.h>
|
||||
#include <libmaple/rcc.h>
|
||||
#include <libmaple/nvic.h>
|
||||
|
||||
/* Instruction Synchronization Barrier */
|
||||
#define isb() __asm__ __volatile__ ("isb" : : : "memory")
|
||||
|
||||
/* Data Synchronization Barrier */
|
||||
#define dsb() __asm__ __volatile__ ("dsb" : : : "memory")
|
||||
|
||||
static void TXBegin() {
|
||||
#if !WITHIN(SERIAL_PORT, 1, 6)
|
||||
#warning "Using POSTMORTEM_DEBUGGING requires a physical U(S)ART hardware in case of severe error."
|
||||
#warning "Disabling the severe error reporting feature currently because the used serial port is not a HW port."
|
||||
#else
|
||||
// We use MYSERIAL0 here, so we need to figure out how to get the linked register
|
||||
struct usart_dev* dev = MYSERIAL0.c_dev();
|
||||
|
||||
// Or use this if removing libmaple
|
||||
// int irq = dev->irq_num;
|
||||
// int nvicUART[] = { NVIC_USART1 /* = 37 */, NVIC_USART2 /* = 38 */, NVIC_USART3 /* = 39 */, NVIC_UART4 /* = 52 */, NVIC_UART5 /* = 53 */ };
|
||||
// Disabling irq means setting the bit in the NVIC ICER register located at
|
||||
// Disable UART interrupt in NVIC
|
||||
nvic_irq_disable(dev->irq_num);
|
||||
|
||||
// Use this if removing libmaple
|
||||
//NVIC_BASE->ICER[1] |= _BV(irq - 32);
|
||||
|
||||
// We NEED memory barriers to ensure Interrupts are actually disabled!
|
||||
// ( https://dzone.com/articles/nvic-disabling-interrupts-on-arm-cortex-m-and-the )
|
||||
dsb();
|
||||
isb();
|
||||
|
||||
rcc_clk_disable(dev->clk_id);
|
||||
rcc_clk_enable(dev->clk_id);
|
||||
|
||||
usart_reg_map *regs = dev->regs;
|
||||
regs->CR1 = 0; // Reset the USART
|
||||
regs->CR2 = 0; // 1 stop bit
|
||||
|
||||
// If we don't touch the BRR (baudrate register), we don't need to recompute. Else we would need to call
|
||||
usart_set_baud_rate(dev, 0, BAUDRATE);
|
||||
|
||||
regs->CR1 = (USART_CR1_TE | USART_CR1_UE); // 8 bits, no parity, 1 stop bit
|
||||
#endif
|
||||
}
|
||||
|
||||
// A SW memory barrier, to ensure GCC does not overoptimize loops
|
||||
#define sw_barrier() __asm__ volatile("": : :"memory");
|
||||
static void TX(char c) {
|
||||
#if WITHIN(SERIAL_PORT, 1, 6)
|
||||
struct usart_dev* dev = MYSERIAL0.c_dev();
|
||||
while (!(dev->regs->SR & USART_SR_TXE)) {
|
||||
TERN_(USE_WATCHDOG, HAL_watchdog_refresh());
|
||||
sw_barrier();
|
||||
}
|
||||
dev->regs->DR = c;
|
||||
#endif
|
||||
}
|
||||
|
||||
void install_min_serial() {
|
||||
HAL_min_serial_init = &TXBegin;
|
||||
HAL_min_serial_out = &TX;
|
||||
}
|
||||
|
||||
#if DISABLED(DYNAMIC_VECTORTABLE) && DISABLED(STM32F0xx) // Cortex M0 can't branch to a symbol that's too far, so we have a specific hack for them
|
||||
extern "C" {
|
||||
__attribute__((naked)) void JumpHandler_ASM() {
|
||||
__asm__ __volatile__ (
|
||||
"b CommonHandler_ASM\n"
|
||||
);
|
||||
}
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_hardfault();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_busfault();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_usagefault();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_memmanage();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __exc_nmi();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception7();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception8();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception9();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception10();
|
||||
void __attribute__((naked, alias("JumpHandler_ASM"), nothrow)) __stm32reservedexception13();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // POSTMORTEM_DEBUGGING
|
||||
#endif // __STM32F1__
|
@ -34,9 +34,9 @@
|
||||
#endif
|
||||
|
||||
#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
|
||||
#error "SERIAL_STATS_MAX_RX_QUEUED is not supported on this platform."
|
||||
#error "SERIAL_STATS_MAX_RX_QUEUED is not supported on the STM32F1 platform."
|
||||
#elif ENABLED(SERIAL_STATS_DROPPED_RX)
|
||||
#error "SERIAL_STATS_DROPPED_RX is not supported on this platform."
|
||||
#error "SERIAL_STATS_DROPPED_RX is not supported on the STM32F1 platform."
|
||||
#endif
|
||||
|
||||
#if ENABLED(NEOPIXEL_LED)
|
||||
|
@ -34,5 +34,9 @@
|
||||
#endif
|
||||
|
||||
#if HAS_TMC_SW_SERIAL
|
||||
#error "TMC220x Software Serial is not supported on this platform."
|
||||
#error "TMC220x Software Serial is not supported on Teensy 3.1/3.2."
|
||||
#endif
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
#error "POSTMORTEM_DEBUGGING is not yet supported on Teensy 3.1/3.2."
|
||||
#endif
|
||||
|
@ -34,5 +34,9 @@
|
||||
#endif
|
||||
|
||||
#if HAS_TMC_SW_SERIAL
|
||||
#error "TMC220x Software Serial is not supported on this platform."
|
||||
#error "TMC220x Software Serial is not supported on Teensy 3.5/3.6."
|
||||
#endif
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
#error "POSTMORTEM_DEBUGGING is not yet supported on Teensy 3.5/3.6."
|
||||
#endif
|
||||
|
@ -34,5 +34,9 @@
|
||||
#endif
|
||||
|
||||
#if HAS_TMC_SW_SERIAL
|
||||
#error "TMC220x Software Serial is not supported on this platform."
|
||||
#error "TMC220x Software Serial is not supported on Teensy 4.0/4.1."
|
||||
#endif
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
#error "POSTMORTEM_DEBUGGING is not yet supported on Teensy 4.0/4.1."
|
||||
#endif
|
||||
|
33
Marlin/src/HAL/shared/HAL_MinSerial.cpp
Normal file
33
Marlin/src/HAL/shared/HAL_MinSerial.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include "HAL_MinSerial.h"
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
|
||||
void HAL_min_serial_init_default() {}
|
||||
void HAL_min_serial_out_default(char ch) { SERIAL_CHAR(ch); }
|
||||
void (*HAL_min_serial_init)() = &HAL_min_serial_init_default;
|
||||
void (*HAL_min_serial_out)(char) = &HAL_min_serial_out_default;
|
||||
|
||||
bool MinSerial::force_using_default_output = false;
|
||||
|
||||
#endif
|
79
Marlin/src/HAL/shared/HAL_MinSerial.h
Normal file
79
Marlin/src/HAL/shared/HAL_MinSerial.h
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../../core/serial.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// Serial stuff here
|
||||
// Inside an exception handler, the CPU state is not safe, we can't expect the handler to resume
|
||||
// and the software to continue. UART communication can't rely on later callback/interrupt as it might never happen.
|
||||
// So, you need to provide some method to send one byte to the usual UART with the interrupts disabled
|
||||
// By default, the method uses SERIAL_CHAR but it's 100% guaranteed to break (couldn't be worse than nothing...)7
|
||||
extern void (*HAL_min_serial_init)();
|
||||
extern void (*HAL_min_serial_out)(char ch);
|
||||
|
||||
struct MinSerial {
|
||||
static bool force_using_default_output;
|
||||
// Serial output
|
||||
static void TX(char ch) {
|
||||
if (force_using_default_output)
|
||||
SERIAL_CHAR(ch);
|
||||
else
|
||||
HAL_min_serial_out(ch);
|
||||
}
|
||||
// Send String through UART
|
||||
static void TX(const char* s) { while (*s) TX(*s++); }
|
||||
// Send a digit through UART
|
||||
static void TXDigit(uint32_t d) {
|
||||
if (d < 10) TX((char)(d+'0'));
|
||||
else if (d < 16) TX((char)(d+'A'-10));
|
||||
else TX('?');
|
||||
}
|
||||
|
||||
// Send Hex number through UART
|
||||
static void TXHex(uint32_t v) {
|
||||
TX("0x");
|
||||
for (uint8_t i = 0; i < 8; i++, v <<= 4)
|
||||
TXDigit((v >> 28) & 0xF);
|
||||
}
|
||||
|
||||
// Send Decimal number through UART
|
||||
static void TXDec(uint32_t v) {
|
||||
if (!v) {
|
||||
TX('0');
|
||||
return;
|
||||
}
|
||||
|
||||
char nbrs[14];
|
||||
char *p = &nbrs[0];
|
||||
while (v != 0) {
|
||||
*p++ = '0' + (v % 10);
|
||||
v /= 10;
|
||||
}
|
||||
do {
|
||||
p--;
|
||||
TX(*p);
|
||||
} while (p != &nbrs[0]);
|
||||
}
|
||||
static void init() { if (!force_using_default_output) HAL_min_serial_init(); }
|
||||
};
|
@ -25,7 +25,7 @@
|
||||
#include "unwinder.h"
|
||||
#include "unwmemaccess.h"
|
||||
|
||||
#include "../../../core/serial.h"
|
||||
#include "../HAL_MinSerial.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
// Dump a backtrace entry
|
||||
@ -34,10 +34,12 @@ static bool UnwReportOut(void* ctx, const UnwReport* bte) {
|
||||
|
||||
(*p)++;
|
||||
|
||||
SERIAL_CHAR('#'); SERIAL_ECHO(*p); SERIAL_ECHOPGM(" : ");
|
||||
SERIAL_ECHOPGM(bte->name ? bte->name : "unknown"); SERIAL_ECHOPGM("@0x"); SERIAL_PRINT(bte->function, PrintBase::Hex);
|
||||
SERIAL_CHAR('+'); SERIAL_ECHO(bte->address - bte->function);
|
||||
SERIAL_ECHOPGM(" PC:"); SERIAL_PRINT(bte->address, PrintBase::Hex); SERIAL_CHAR('\n');
|
||||
const uint32_t a = bte->address, f = bte->function;
|
||||
MinSerial::TX('#'); MinSerial::TXDec(*p); MinSerial::TX(" : ");
|
||||
MinSerial::TX(bte->name?:"unknown"); MinSerial::TX('@'); MinSerial::TXHex(f);
|
||||
MinSerial::TX('+'); MinSerial::TXDec(a - f);
|
||||
MinSerial::TX(" PC:"); MinSerial::TXHex(a);
|
||||
MinSerial::TX('\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -48,7 +50,7 @@ static bool UnwReportOut(void* ctx, const UnwReport* bte) {
|
||||
va_start(argptr, format);
|
||||
vsprintf(dest, format, argptr);
|
||||
va_end(argptr);
|
||||
TX(&dest[0]);
|
||||
MinSerial::TX(&dest[0]);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -63,10 +65,10 @@ static const UnwindCallbacks UnwCallbacks = {
|
||||
#endif
|
||||
};
|
||||
|
||||
// Perform a backtrace to the serial port
|
||||
void backtrace() {
|
||||
|
||||
UnwindFrame btf;
|
||||
uint32_t sp = 0, lr = 0, pc = 0;
|
||||
unsigned long sp = 0, lr = 0, pc = 0;
|
||||
|
||||
// Capture the values of the registers to perform the traceback
|
||||
__asm__ __volatile__ (
|
||||
@ -79,6 +81,12 @@ void backtrace() {
|
||||
::
|
||||
);
|
||||
|
||||
backtrace_ex(sp, lr, pc);
|
||||
}
|
||||
|
||||
void backtrace_ex(unsigned long sp, unsigned long lr, unsigned long pc) {
|
||||
UnwindFrame btf;
|
||||
|
||||
// Fill the traceback structure
|
||||
btf.sp = sp;
|
||||
btf.fp = btf.sp;
|
||||
@ -86,7 +94,7 @@ void backtrace() {
|
||||
btf.pc = pc | 1; // Force Thumb, as CORTEX only support it
|
||||
|
||||
// Perform a backtrace
|
||||
SERIAL_ERROR_MSG("Backtrace:");
|
||||
MinSerial::TX("Backtrace:");
|
||||
int ctr = 0;
|
||||
UnwindStart(&btf, &UnwCallbacks, &ctr);
|
||||
}
|
||||
@ -95,4 +103,4 @@ void backtrace() {
|
||||
|
||||
void backtrace() {}
|
||||
|
||||
#endif
|
||||
#endif // __arm__ || __thumb__
|
||||
|
@ -23,3 +23,6 @@
|
||||
|
||||
// Perform a backtrace to the serial port
|
||||
void backtrace();
|
||||
|
||||
// Perform a backtrace to the serial port
|
||||
void backtrace_ex(unsigned long sp, unsigned long lr, unsigned long pc);
|
||||
|
@ -41,27 +41,16 @@
|
||||
#define START_FLASH_ADDR 0x00000000
|
||||
#define END_FLASH_ADDR 0x00080000
|
||||
|
||||
#elif 0
|
||||
|
||||
// For STM32F103CBT6
|
||||
// SRAM (0x20000000 - 0x20005000) (20kb)
|
||||
// FLASH (0x00000000 - 0x00020000) (128kb)
|
||||
//
|
||||
#define START_SRAM_ADDR 0x20000000
|
||||
#define END_SRAM_ADDR 0x20005000
|
||||
#define START_FLASH_ADDR 0x00000000
|
||||
#define END_FLASH_ADDR 0x00020000
|
||||
|
||||
#elif defined(__STM32F1__) || defined(STM32F1xx) || defined(STM32F0xx)
|
||||
|
||||
// For STM32F103ZET6/STM32F103VET6/STM32F0xx
|
||||
// SRAM (0x20000000 - 0x20010000) (64kb)
|
||||
// FLASH (0x00000000 - 0x00080000) (512kb)
|
||||
// FLASH (0x08000000 - 0x08080000) (512kb)
|
||||
//
|
||||
#define START_SRAM_ADDR 0x20000000
|
||||
#define END_SRAM_ADDR 0x20010000
|
||||
#define START_FLASH_ADDR 0x00000000
|
||||
#define END_FLASH_ADDR 0x00080000
|
||||
#define START_FLASH_ADDR 0x08000000
|
||||
#define END_FLASH_ADDR 0x08080000
|
||||
|
||||
#elif defined(STM32F4) || defined(STM32F4xx)
|
||||
|
||||
@ -142,20 +131,57 @@
|
||||
#define START_FLASH_ADDR 0x00000000
|
||||
#define END_FLASH_ADDR 0x00100000
|
||||
|
||||
#else
|
||||
// Generic ARM code, that's testing if an access to the given address would cause a fault or not
|
||||
// It can't guarantee an address is in RAM or Flash only, but we usually don't care
|
||||
|
||||
#define NVIC_FAULT_STAT 0xE000ED28 // Configurable Fault Status Reg.
|
||||
#define NVIC_CFG_CTRL 0xE000ED14 // Configuration Control Register
|
||||
#define NVIC_FAULT_STAT_BFARV 0x00008000 // BFAR is valid
|
||||
#define NVIC_CFG_CTRL_BFHFNMIGN 0x00000100 // Ignore bus fault in NMI/fault
|
||||
#define HW_REG(X) (*((volatile unsigned long *)(X)))
|
||||
|
||||
static bool validate_addr(uint32_t read_address) {
|
||||
bool works = true;
|
||||
uint32_t intDisabled = 0;
|
||||
// Read current interrupt state
|
||||
__asm__ __volatile__ ("MRS %[result], PRIMASK\n\t" : [result]"=r"(intDisabled) :: ); // 0 is int enabled, 1 for disabled
|
||||
|
||||
// Clear bus fault indicator first (write 1 to clear)
|
||||
HW_REG(NVIC_FAULT_STAT) |= NVIC_FAULT_STAT_BFARV;
|
||||
// Ignore bus fault interrupt
|
||||
HW_REG(NVIC_CFG_CTRL) |= NVIC_CFG_CTRL_BFHFNMIGN;
|
||||
// Disable interrupts if not disabled previously
|
||||
if (!intDisabled) __asm__ __volatile__ ("CPSID f");
|
||||
// Probe address
|
||||
*(volatile uint32_t*)read_address;
|
||||
// Check if a fault happened
|
||||
if ((HW_REG(NVIC_FAULT_STAT) & NVIC_FAULT_STAT_BFARV) != 0)
|
||||
works = false;
|
||||
// Enable interrupts again if previously disabled
|
||||
if (!intDisabled) __asm__ __volatile__ ("CPSIE f");
|
||||
// Enable fault interrupt flag
|
||||
HW_REG(NVIC_CFG_CTRL) &= ~NVIC_CFG_CTRL_BFHFNMIGN;
|
||||
|
||||
return works;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static bool validate_addr(uint32_t addr) {
|
||||
#ifdef START_SRAM_ADDR
|
||||
static bool validate_addr(uint32_t addr) {
|
||||
|
||||
// Address must be in SRAM range
|
||||
if (addr >= START_SRAM_ADDR && addr < END_SRAM_ADDR)
|
||||
return true;
|
||||
// Address must be in SRAM range
|
||||
if (addr >= START_SRAM_ADDR && addr < END_SRAM_ADDR)
|
||||
return true;
|
||||
|
||||
// Or in FLASH range
|
||||
if (addr >= START_FLASH_ADDR && addr < END_FLASH_ADDR)
|
||||
return true;
|
||||
// Or in FLASH range
|
||||
if (addr >= START_FLASH_ADDR && addr < END_FLASH_ADDR)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool UnwReadW(const uint32_t a, uint32_t *v) {
|
||||
if (!validate_addr(a))
|
||||
|
379
Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp
Normal file
379
Marlin/src/HAL/shared/cpu_exception/exception_arm.cpp
Normal file
@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
* Copyright (c) 2020 Cyril Russo
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************
|
||||
* ARM CPU Exception handler
|
||||
***************************************************************************/
|
||||
|
||||
#if defined(__arm__) || defined(__thumb__)
|
||||
|
||||
|
||||
/*
|
||||
On ARM CPUs exception handling is quite powerful.
|
||||
|
||||
By default, upon a crash, the CPU enters the handlers that have a higher priority than any other interrupts,
|
||||
so, in effect, no (real) interrupt can "interrupt" the handler (it's acting like if interrupts were disabled).
|
||||
|
||||
If the handler is not called as re-entrant (that is, if the crash is not happening inside an interrupt or an handler),
|
||||
then it'll patch the return address to a dumping function (resume_from_fault) and save the crash state.
|
||||
The CPU will exit the handler and, as such, re-allow the other interrupts, and jump to the dumping function.
|
||||
In this function, the usual serial port (USB / HW) will be used to dump the crash (no special configuration required).
|
||||
|
||||
The only case where it requires hardware UART is when it's crashing in an interrupt or a crash handler.
|
||||
In that case, instead of returning to the resume_from_fault function (and thus, re-enabling interrupts),
|
||||
it jumps to this function directly (so with interrupts disabled), after changing the behavior of the serial output
|
||||
wrapper to use the HW uart (and in effect, calling MinSerial::init which triggers a warning if you are using
|
||||
a USB serial port).
|
||||
|
||||
In the case you have a USB serial port, this part will be disabled, and only that part (so that's the reason for
|
||||
the warning).
|
||||
This means that you can't have a crash report if the crash happens in an interrupt or an handler if you are using
|
||||
a USB serial port since it's physically impossible.
|
||||
You will get a crash report in all other cases.
|
||||
*/
|
||||
|
||||
#include "exception_hook.h"
|
||||
#include "../backtrace/backtrace.h"
|
||||
#include "../HAL_MinSerial.h"
|
||||
|
||||
#define HW_REG(X) (*((volatile unsigned long *)(X)))
|
||||
|
||||
// Default function use the CPU VTOR register to get the vector table.
|
||||
// Accessing the CPU VTOR register is done in assembly since it's the only way that's common to all current tool
|
||||
unsigned long get_vtor() { return HW_REG(0xE000ED08); } // Even if it looks like an error, it is not an error
|
||||
void * hook_get_hardfault_vector_address(unsigned vtor) { return (void*)(vtor + 0x03); }
|
||||
void * hook_get_memfault_vector_address(unsigned vtor) { return (void*)(vtor + 0x04); }
|
||||
void * hook_get_busfault_vector_address(unsigned vtor) { return (void*)(vtor + 0x05); }
|
||||
void * hook_get_usagefault_vector_address(unsigned vtor) { return (void*)(vtor + 0x06); }
|
||||
void * hook_get_reserved_vector_address(unsigned vtor) { return (void*)(vtor + 0x07); }
|
||||
|
||||
// Common exception frame for ARM, should work for all ARM CPU
|
||||
// Described here (modified for convenience): https://interrupt.memfault.com/blog/cortex-m-fault-debug
|
||||
struct __attribute__((packed)) ContextStateFrame {
|
||||
uint32_t r0;
|
||||
uint32_t r1;
|
||||
uint32_t r2;
|
||||
uint32_t r3;
|
||||
uint32_t r12;
|
||||
uint32_t lr;
|
||||
uint32_t pc;
|
||||
uint32_t xpsr;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) ContextSavedFrame {
|
||||
uint32_t R0;
|
||||
uint32_t R1;
|
||||
uint32_t R2;
|
||||
uint32_t R3;
|
||||
uint32_t R12;
|
||||
uint32_t LR;
|
||||
uint32_t PC;
|
||||
uint32_t XPSR;
|
||||
|
||||
uint32_t CFSR;
|
||||
uint32_t HFSR;
|
||||
uint32_t DFSR;
|
||||
uint32_t AFSR;
|
||||
uint32_t MMAR;
|
||||
uint32_t BFAR;
|
||||
|
||||
uint32_t ESP;
|
||||
uint32_t ELR;
|
||||
};
|
||||
|
||||
#if DISABLED(STM32F0xx)
|
||||
extern "C"
|
||||
__attribute__((naked)) void CommonHandler_ASM() {
|
||||
__asm__ __volatile__ (
|
||||
// Bit 2 of LR tells which stack pointer to use (either main or process, only main should be used anyway)
|
||||
"tst lr, #4\n"
|
||||
"ite eq\n"
|
||||
"mrseq r0, msp\n"
|
||||
"mrsne r0, psp\n"
|
||||
// Save the LR in use when being interrupted
|
||||
"mov r1, lr\n"
|
||||
// Get the exception number from the ICSR register
|
||||
"ldr r2, =0xE000ED00\n"
|
||||
"ldr r2, [r2, #4]\n"
|
||||
"b CommonHandler_C\n"
|
||||
);
|
||||
}
|
||||
#else // Cortex M0 does not support conditional mov and testing with a constant, so let's have a specific handler for it
|
||||
extern "C"
|
||||
__attribute__((naked)) void CommonHandler_ASM() {
|
||||
__asm__ __volatile__ (
|
||||
".syntax unified\n"
|
||||
// Save the LR in use when being interrupted
|
||||
"mov r1, lr\n"
|
||||
// Get the exception number from the ICSR register
|
||||
"ldr r2, =0xE000ED00\n"
|
||||
"ldr r2, [r2, #4]\n"
|
||||
"movs r0, #4\n"
|
||||
"tst r1, r0\n"
|
||||
"beq _MSP\n"
|
||||
"mrs r0, psp\n"
|
||||
"b CommonHandler_C\n"
|
||||
"_MSP:\n"
|
||||
"mrs r0, msp\n"
|
||||
"b CommonHandler_C\n"
|
||||
);
|
||||
}
|
||||
|
||||
#if DISABLED(DYNAMIC_VECTORTABLE) // Cortex M0 requires the handler's address to be within 32kB to the actual function to be able to branch to it
|
||||
extern "C" {
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_hardfault();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_busfault();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_usagefault();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_memmanage();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __exc_nmi();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception7();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception8();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception9();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception10();
|
||||
void __attribute__((naked, alias("CommonHandler_ASM"), nothrow)) __stm32reservedexception13();
|
||||
}
|
||||
//TODO When going off from libmaple, you'll need to replace those by the one from STM32/HAL_MinSerial.cpp
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Must be a macro to avoid creating a function frame
|
||||
#define HALT_IF_DEBUGGING() \
|
||||
do { \
|
||||
if (HW_REG(0xE000EDF0) & _BV(0)) { \
|
||||
__asm("bkpt 1"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Resume from a fault (if possible) so we can still use interrupt based code for serial output
|
||||
// In that case, we will not jump back to the faulty code, but instead to a dumping code and then a
|
||||
// basic loop with watchdog calling or manual resetting
|
||||
static ContextSavedFrame savedFrame;
|
||||
static uint8_t lastCause;
|
||||
bool resume_from_fault() {
|
||||
static const char* causestr[] = { "Thread", "Rsvd", "NMI", "Hard", "Mem", "Bus", "Usage", "7", "8", "9", "10", "SVC", "Dbg", "13", "PendSV", "SysTk", "IRQ" };
|
||||
// Reinit the serial link (might only work if implemented in each of your boards)
|
||||
MinSerial::init();
|
||||
|
||||
MinSerial::TX("\n\n## Software Fault detected ##\n");
|
||||
MinSerial::TX("Cause: "); MinSerial::TX(causestr[min(lastCause, (uint8_t)16)]); MinSerial::TX('\n');
|
||||
|
||||
MinSerial::TX("R0 : "); MinSerial::TXHex(savedFrame.R0); MinSerial::TX('\n');
|
||||
MinSerial::TX("R1 : "); MinSerial::TXHex(savedFrame.R1); MinSerial::TX('\n');
|
||||
MinSerial::TX("R2 : "); MinSerial::TXHex(savedFrame.R2); MinSerial::TX('\n');
|
||||
MinSerial::TX("R3 : "); MinSerial::TXHex(savedFrame.R3); MinSerial::TX('\n');
|
||||
MinSerial::TX("R12 : "); MinSerial::TXHex(savedFrame.R12); MinSerial::TX('\n');
|
||||
MinSerial::TX("LR : "); MinSerial::TXHex(savedFrame.LR); MinSerial::TX('\n');
|
||||
MinSerial::TX("PC : "); MinSerial::TXHex(savedFrame.PC); MinSerial::TX('\n');
|
||||
MinSerial::TX("PSR : "); MinSerial::TXHex(savedFrame.XPSR); MinSerial::TX('\n');
|
||||
|
||||
// Configurable Fault Status Register
|
||||
// Consists of MMSR, BFSR and UFSR
|
||||
MinSerial::TX("CFSR : "); MinSerial::TXHex(savedFrame.CFSR); MinSerial::TX('\n');
|
||||
|
||||
// Hard Fault Status Register
|
||||
MinSerial::TX("HFSR : "); MinSerial::TXHex(savedFrame.HFSR); MinSerial::TX('\n');
|
||||
|
||||
// Debug Fault Status Register
|
||||
MinSerial::TX("DFSR : "); MinSerial::TXHex(savedFrame.DFSR); MinSerial::TX('\n');
|
||||
|
||||
// Auxiliary Fault Status Register
|
||||
MinSerial::TX("AFSR : "); MinSerial::TXHex(savedFrame.AFSR); MinSerial::TX('\n');
|
||||
|
||||
// Read the Fault Address Registers. These may not contain valid values.
|
||||
// Check BFARVALID/MMARVALID to see if they are valid values
|
||||
// MemManage Fault Address Register
|
||||
MinSerial::TX("MMAR : "); MinSerial::TXHex(savedFrame.MMAR); MinSerial::TX('\n');
|
||||
|
||||
// Bus Fault Address Register
|
||||
MinSerial::TX("BFAR : "); MinSerial::TXHex(savedFrame.BFAR); MinSerial::TX('\n');
|
||||
|
||||
MinSerial::TX("ExcLR: "); MinSerial::TXHex(savedFrame.ELR); MinSerial::TX('\n');
|
||||
MinSerial::TX("ExcSP: "); MinSerial::TXHex(savedFrame.ESP); MinSerial::TX('\n');
|
||||
|
||||
// The stack pointer is pushed by 8 words upon entering an exception, so we need to revert this
|
||||
backtrace_ex(savedFrame.ESP + 8*4, savedFrame.LR, savedFrame.PC);
|
||||
|
||||
// Call the last resort function here
|
||||
hook_last_resort_func();
|
||||
|
||||
const uint32_t start = millis(), end = start + 100; // 100ms should be enough
|
||||
// We need to wait for the serial buffers to be output but we don't know for how long
|
||||
// So we'll just need to refresh the watchdog for a while and then stop for the system to reboot
|
||||
uint32_t last = start;
|
||||
while (PENDING(last, end)) {
|
||||
watchdog_refresh();
|
||||
while (millis() == last) { /* nada */ }
|
||||
last = millis();
|
||||
MinSerial::TX('.');
|
||||
}
|
||||
|
||||
// Reset now by reinstantiating the bootloader's vector table
|
||||
HW_REG(0xE000ED08) = 0;
|
||||
// Restart watchdog
|
||||
#if DISABLED(USE_WATCHDOG)
|
||||
// No watchdog, let's perform ARMv7 reset instead by writing to AIRCR register with VECTKEY set to SYSRESETREQ
|
||||
HW_REG(0xE000ED0C) = (HW_REG(0xE000ED0C) & 0x0000FFFF) | 0x05FA0004;
|
||||
#endif
|
||||
|
||||
while(1) {} // Bad luck, nothing worked
|
||||
}
|
||||
|
||||
// Make sure the compiler does not optimize the frame argument away
|
||||
extern "C"
|
||||
__attribute__((optimize("O0")))
|
||||
void CommonHandler_C(ContextStateFrame * frame, unsigned long lr, unsigned long cause) {
|
||||
|
||||
// If you are using it'll stop here
|
||||
HALT_IF_DEBUGGING();
|
||||
|
||||
// Save the state to backtrace later on (don't call memcpy here since the stack can be corrupted)
|
||||
savedFrame.R0 = frame->r0;
|
||||
savedFrame.R1 = frame->r1;
|
||||
savedFrame.R2 = frame->r2;
|
||||
savedFrame.R3 = frame->r3;
|
||||
savedFrame.R12 = frame->r12;
|
||||
savedFrame.LR = frame->lr;
|
||||
savedFrame.PC = frame->pc;
|
||||
savedFrame.XPSR= frame->xpsr;
|
||||
lastCause = cause & 0x1FF;
|
||||
|
||||
volatile uint32_t &CFSR = HW_REG(0xE000ED28);
|
||||
savedFrame.CFSR = CFSR;
|
||||
savedFrame.HFSR = HW_REG(0xE000ED2C);
|
||||
savedFrame.DFSR = HW_REG(0xE000ED30);
|
||||
savedFrame.AFSR = HW_REG(0xE000ED3C);
|
||||
savedFrame.MMAR = HW_REG(0xE000ED34);
|
||||
savedFrame.BFAR = HW_REG(0xE000ED38);
|
||||
savedFrame.ESP = (unsigned long)frame; // Even on return, this should not be overwritten by the CPU
|
||||
savedFrame.ELR = lr;
|
||||
|
||||
// First check if we can resume from this exception to our own handler safely
|
||||
// If we can, then we don't need to disable interrupts and the usual serial code
|
||||
// can be used
|
||||
|
||||
//const uint32_t non_usage_fault_mask = 0x0000FFFF;
|
||||
//const bool non_usage_fault_occurred = (CFSR & non_usage_fault_mask) != 0;
|
||||
// the bottom 8 bits of the xpsr hold the exception number of the
|
||||
// executing exception or 0 if the processor is in Thread mode
|
||||
const bool faulted_from_exception = ((frame->xpsr & 0xFF) != 0);
|
||||
if (!faulted_from_exception) { // Not sure about the non_usage_fault, we want to try anyway, don't we ? && !non_usage_fault_occurred)
|
||||
// Try to resume to our handler here
|
||||
CFSR |= CFSR; // The ARM programmer manual says you must write to 1 all fault bits to clear them so this instruction is correct
|
||||
// The frame will not be valid when returning anymore, let's clean it
|
||||
savedFrame.CFSR = 0;
|
||||
|
||||
frame->pc = (uint32_t)resume_from_fault; // Patch where to return to
|
||||
frame->lr = 0xdeadbeef; // If our handler returns (it shouldn't), let's make it trigger an exception immediately
|
||||
frame->xpsr = _BV(24); // Need to clean the PSR register to thumb II only
|
||||
MinSerial::force_using_default_output = true;
|
||||
return; // The CPU will resume in our handler hopefully, and we'll try to use default serial output
|
||||
}
|
||||
|
||||
// Sorry, we need to emergency code here since the fault is too dangerous to recover from
|
||||
MinSerial::force_using_default_output = false;
|
||||
resume_from_fault();
|
||||
}
|
||||
|
||||
void hook_cpu_exceptions() {
|
||||
#if ENABLED(DYNAMIC_VECTORTABLE)
|
||||
// On ARM 32bits CPU, the vector table is like this:
|
||||
// 0x0C => Hardfault
|
||||
// 0x10 => MemFault
|
||||
// 0x14 => BusFault
|
||||
// 0x18 => UsageFault
|
||||
|
||||
// Unfortunately, it's usually run from flash, and we can't write to flash here directly to hook our instruction
|
||||
// We could set an hardware breakpoint, and hook on the fly when it's being called, but this
|
||||
// is hard to get right and would probably break debugger when attached
|
||||
|
||||
// So instead, we'll allocate a new vector table filled with the previous value except
|
||||
// for the fault we are interested in.
|
||||
// Now, comes the issue to figure out what is the current vector table size
|
||||
// There is nothing telling us what is the vector table as it's per-cpu vendor specific.
|
||||
// BUT: we are being called at the end of the setup, so we assume the setup is done
|
||||
// Thus, we can read the current vector table until we find an address that's not in flash, and it would mark the
|
||||
// end of the vector table (skipping the fist entry obviously)
|
||||
// The position of the program in flash is expected to be at 0x08xxx xxxx on all known platform for ARM and the
|
||||
// flash size is available via register 0x1FFFF7E0 on STM32 family, but it's not the case for all ARM boards
|
||||
// (accessing this register might trigger a fault if it's not implemented).
|
||||
|
||||
// So we'll simply mask the top 8 bits of the first handler as an hint of being in the flash or not -that's poor and will
|
||||
// probably break if the flash happens to be more than 128MB, but in this case, we are not magician, we need help from outside.
|
||||
|
||||
unsigned long * vecAddr = (unsigned long*)get_vtor();
|
||||
SERIAL_ECHO("Vector table addr: ");
|
||||
SERIAL_PRINTLN(get_vtor(), HEX);
|
||||
|
||||
#ifdef VECTOR_TABLE_SIZE
|
||||
uint32_t vec_size = VECTOR_TABLE_SIZE;
|
||||
alignas(128) static unsigned long vectable[VECTOR_TABLE_SIZE] ;
|
||||
#else
|
||||
#ifndef IS_IN_FLASH
|
||||
#define IS_IN_FLASH(X) (((unsigned long)X & 0xFF000000) == 0x08000000)
|
||||
#endif
|
||||
|
||||
// When searching for the end of the vector table, this acts as a limit not to overcome
|
||||
#ifndef VECTOR_TABLE_SENTINEL
|
||||
#define VECTOR_TABLE_SENTINEL 80
|
||||
#endif
|
||||
|
||||
// Find the vector table size
|
||||
uint32_t vec_size = 1;
|
||||
while (IS_IN_FLASH(vecAddr[vec_size]) && vec_size < VECTOR_TABLE_SENTINEL)
|
||||
vec_size++;
|
||||
|
||||
// We failed to find a valid vector table size, let's abort hooking up
|
||||
if (vec_size == VECTOR_TABLE_SENTINEL) return;
|
||||
// Poor method that's wasting RAM here, but allocating with malloc and alignment would be worst
|
||||
// 128 bytes alignement is required for writing the VTOR register
|
||||
alignas(128) static unsigned long vectable[VECTOR_TABLE_SENTINEL];
|
||||
|
||||
SERIAL_ECHO("Detected vector table size: ");
|
||||
SERIAL_PRINTLN(vec_size, HEX);
|
||||
#endif
|
||||
|
||||
uint32_t defaultFaultHandler = vecAddr[(unsigned)7];
|
||||
// Copy the current vector table into the new table
|
||||
for (uint32_t i = 0; i < vec_size; i++) {
|
||||
vectable[i] = vecAddr[i];
|
||||
// Replace all default handler by our own handler
|
||||
if (vectable[i] == defaultFaultHandler)
|
||||
vectable[i] = (unsigned long)&CommonHandler_ASM;
|
||||
}
|
||||
|
||||
// Let's hook now with our functions
|
||||
vectable[(unsigned long)hook_get_hardfault_vector_address(0)] = (unsigned long)&CommonHandler_ASM;
|
||||
vectable[(unsigned long)hook_get_memfault_vector_address(0)] = (unsigned long)&CommonHandler_ASM;
|
||||
vectable[(unsigned long)hook_get_busfault_vector_address(0)] = (unsigned long)&CommonHandler_ASM;
|
||||
vectable[(unsigned long)hook_get_usagefault_vector_address(0)] = (unsigned long)&CommonHandler_ASM;
|
||||
|
||||
// Finally swap with our own vector table
|
||||
// This is supposed to be atomic, but let's do that with interrupt disabled
|
||||
|
||||
HW_REG(0xE000ED08) = (unsigned long)vectable | _BV32(29); // 29th bit is for telling the CPU the table is now in SRAM (should be present already)
|
||||
|
||||
SERIAL_ECHOLN("Installed fault handlers");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // __arm__ || __thumb__
|
28
Marlin/src/HAL/shared/cpu_exception/exception_hook.cpp
Normal file
28
Marlin/src/HAL/shared/cpu_exception/exception_hook.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include "exception_hook.h"
|
||||
|
||||
void * __attribute__((weak)) hook_get_hardfault_vector_address(unsigned) { return 0; }
|
||||
void * __attribute__((weak)) hook_get_memfault_vector_address(unsigned) { return 0; }
|
||||
void * __attribute__((weak)) hook_get_busfault_vector_address(unsigned) { return 0; }
|
||||
void * __attribute__((weak)) hook_get_usagefault_vector_address(unsigned) { return 0; }
|
||||
void __attribute__((weak)) hook_last_resort_func() {}
|
54
Marlin/src/HAL/shared/cpu_exception/exception_hook.h
Normal file
54
Marlin/src/HAL/shared/cpu_exception/exception_hook.h
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/* Here is the expected behavior of a system producing a CPU exception with this hook installed:
|
||||
1. Before the system is crashed
|
||||
1.1 Upon validation (not done yet in this code, but we could be using DEBUG flags here to allow/disallow hooking)
|
||||
1.2 Install the hook by overwriting the vector table exception handler with the hooked function
|
||||
2. Upon system crash (for example, by a dereference of a NULL pointer or anything else)
|
||||
2.1 The CPU triggers its exception and jump into the vector table for the exception type
|
||||
2.2 Instead of finding the default handler, it finds the updated pointer to our hook
|
||||
2.3 The CPU jumps into our hook function (likely a naked function to keep all information about crash point intact)
|
||||
2.4 The hook (naked) function saves the important registers (stack pointer, program counter, current mode) and jumps to a common exception handler (in C)
|
||||
2.5 The common exception handler dumps the registers on the serial link and perform a backtrace around the crashing point
|
||||
2.6 Once the backtrace is performed the last resort function is called (platform specific).
|
||||
On some platform with a LCD screen, this might display the crash information as a QR code or as text for the
|
||||
user to capture by taking a picture
|
||||
2.7 The CPU is reset and/or halted by triggering a debug breakpoint if a debugger is attached */
|
||||
|
||||
// Hook into CPU exception interrupt table to call the backtracing code upon an exception
|
||||
// Most platform will simply do nothing here, but those who can will install/overwrite the default exception handler
|
||||
// with a more performant exception handler
|
||||
void hook_cpu_exceptions();
|
||||
|
||||
// Some platform might deal without a hard fault handler, in that case, return 0 in your platform here or skip implementing it
|
||||
void * __attribute__((weak)) hook_get_hardfault_vector_address(unsigned base_address);
|
||||
// Some platform might deal without a memory management fault handler, in that case, return 0 in your platform here or skip implementing it
|
||||
void * __attribute__((weak)) hook_get_memfault_vector_address(unsigned base_address);
|
||||
// Some platform might deal without a bus fault handler, in that case, return 0 in your platform here or skip implementing it
|
||||
void * __attribute__((weak)) hook_get_busfault_vector_address(unsigned base_address);
|
||||
// Some platform might deal without a usage fault handler, in that case, return 0 in your platform here or skip implementing it
|
||||
void * __attribute__((weak)) hook_get_usagefault_vector_address(unsigned base_address);
|
||||
|
||||
// Last resort function that can be called after the exception handler was called.
|
||||
void __attribute__((weak)) hook_last_resort_func();
|
@ -36,6 +36,7 @@
|
||||
|
||||
#include "HAL/shared/Delay.h"
|
||||
#include "HAL/shared/esp_wifi.h"
|
||||
#include "HAL/shared/cpu_exception/exception_hook.h"
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <pins_arduino.h>
|
||||
@ -935,6 +936,8 @@ void setup() {
|
||||
while (/*!WIFISERIAL && */PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
|
||||
#endif
|
||||
|
||||
TERN_(DYNAMIC_VECTORTABLE, hook_cpu_exceptions()); // If supported, install Marlin exception handlers at runtime
|
||||
|
||||
SETUP_RUN(HAL_init());
|
||||
|
||||
// Init and disable SPI thermocouples; this is still needed
|
||||
|
@ -152,8 +152,7 @@
|
||||
NOMORE(len, FLASH_SIZE - addr);
|
||||
if (parser.seenval('X')) {
|
||||
// TODO: Write the hex bytes after the X
|
||||
//while (len--) {
|
||||
//}
|
||||
//while (len--) {}
|
||||
}
|
||||
else {
|
||||
// while (len--) {
|
||||
@ -180,7 +179,25 @@
|
||||
for (int i = 10000; i--;) DELAY_US(1000UL);
|
||||
ENABLE_ISRS();
|
||||
SERIAL_ECHOLNPGM("FAILURE: Watchdog did not trigger board reset.");
|
||||
} break;
|
||||
|
||||
#if ENABLED(POSTMORTEM_DEBUGGING)
|
||||
case 451: { // Trigger all kind of faults to test exception catcher
|
||||
SERIAL_ECHOLNPGM("Disabling heaters");
|
||||
thermalManager.disable_all_heaters();
|
||||
delay(1000); // Allow time to print
|
||||
volatile uint8_t type[5] = { parser.byteval('T', 1) };
|
||||
|
||||
// The code below is obviously wrong and it's full of quirks to fool the compiler from optimizing away the code
|
||||
switch (type[0]) {
|
||||
case 1: default: *(int*)0 = 451; break; // Write at bad address
|
||||
case 2: { volatile int a = 0; volatile int b = 452 / a; *(int*)&a = b; } break; // Divide by zero (some CPUs accept this, like ARM)
|
||||
case 3: { *(uint32_t*)&type[1] = 453; volatile int a = *(int*)&type[1]; type[0] = a / 255; } break; // Unaligned access (some CPUs accept this)
|
||||
case 4: { volatile void (*func)() = (volatile void (*)()) 0xE0000000; func(); } break; // Invalid instruction
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
104
buildroot/share/PlatformIO/scripts/exc.S
Normal file
104
buildroot/share/PlatformIO/scripts/exc.S
Normal file
@ -0,0 +1,104 @@
|
||||
/* *****************************************************************************
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010 Perry Hung.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
* ****************************************************************************/
|
||||
|
||||
# On an exception, push a fake stack thread mode stack frame and redirect
|
||||
# thread execution to a thread mode error handler
|
||||
|
||||
# From RM008:
|
||||
# The SP is decremented by eight words by the completion of the stack push.
|
||||
# Figure 5-1 shows the contents of the stack after an exception pre-empts the
|
||||
# current program flow.
|
||||
#
|
||||
# Old SP--> <previous>
|
||||
# xPSR
|
||||
# PC
|
||||
# LR
|
||||
# r12
|
||||
# r3
|
||||
# r2
|
||||
# r1
|
||||
# SP--> r0
|
||||
|
||||
.text
|
||||
.globl __exc_nmi
|
||||
.weak __exc_nmi
|
||||
.globl __exc_hardfault
|
||||
.weak __exc_hardfault
|
||||
.globl __exc_memmanage
|
||||
.weak __exc_memmanage
|
||||
.globl __exc_busfault
|
||||
.weak __exc_busfault
|
||||
.globl __exc_usagefault
|
||||
.weak __exc_usagefault
|
||||
|
||||
.code 16
|
||||
.thumb_func
|
||||
__exc_nmi:
|
||||
mov r0, #1
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_hardfault:
|
||||
mov r0, #2
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_memmanage:
|
||||
mov r0, #3
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_busfault:
|
||||
mov r0, #4
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__exc_usagefault:
|
||||
mov r0, #5
|
||||
b __default_exc
|
||||
|
||||
.thumb_func
|
||||
__default_exc:
|
||||
ldr r2, NVIC_CCR @ Enable returning to thread mode even if there are
|
||||
mov r1 ,#1 @ pending exceptions. See flag NONEBASETHRDENA.
|
||||
str r1, [r2]
|
||||
cpsid i @ Disable global interrupts
|
||||
ldr r2, SYSTICK_CSR @ Disable systick handler
|
||||
mov r1, #0
|
||||
str r1, [r2]
|
||||
ldr r1, CPSR_MASK @ Set default CPSR
|
||||
push {r1}
|
||||
ldr r1, TARGET_PC @ Set target pc
|
||||
push {r1}
|
||||
sub sp, sp, #24 @ Don't care
|
||||
ldr r1, EXC_RETURN @ Return to thread mode
|
||||
mov lr, r1
|
||||
bx lr @ Exception exit
|
||||
|
||||
.align 4
|
||||
CPSR_MASK: .word 0x61000000
|
||||
EXC_RETURN: .word 0xFFFFFFF9
|
||||
TARGET_PC: .word __error
|
||||
NVIC_CCR: .word 0xE000ED14 @ NVIC configuration control register
|
||||
SYSTICK_CSR: .word 0xE000E010 @ Systick control register
|
29
buildroot/share/PlatformIO/scripts/fix_framework_weakness.py
Normal file
29
buildroot/share/PlatformIO/scripts/fix_framework_weakness.py
Normal file
@ -0,0 +1,29 @@
|
||||
from os.path import join, isfile
|
||||
import shutil
|
||||
from pprint import pprint
|
||||
|
||||
Import("env")
|
||||
|
||||
if env.MarlinFeatureIsEnabled("POSTMORTEM_DEBUGGING"):
|
||||
FRAMEWORK_DIR = env.PioPlatform().get_package_dir("framework-arduinoststm32-maple")
|
||||
patchflag_path = join(FRAMEWORK_DIR, ".exc-patching-done")
|
||||
|
||||
# patch file only if we didn't do it before
|
||||
if not isfile(patchflag_path):
|
||||
print("Patching libmaple exception handlers")
|
||||
original_file = join(FRAMEWORK_DIR, "STM32F1", "cores", "maple", "libmaple", "exc.S")
|
||||
backup_file = join(FRAMEWORK_DIR, "STM32F1", "cores", "maple", "libmaple", "exc.S.bak")
|
||||
src_file = join("buildroot", "share", "PlatformIO", "scripts", "exc.S")
|
||||
|
||||
assert isfile(original_file) and isfile(src_file)
|
||||
shutil.copyfile(original_file, backup_file)
|
||||
shutil.copyfile(src_file, original_file);
|
||||
|
||||
def _touch(path):
|
||||
with open(path, "w") as fp:
|
||||
fp.write("")
|
||||
|
||||
env.Execute(lambda *args, **kwargs: _touch(patchflag_path))
|
||||
print("Done patching exception handler")
|
||||
|
||||
print("Libmaple modified and ready for post mortem debugging")
|
@ -64,6 +64,7 @@ default_src_filter = +<src/*> -<src/config> -<src/HAL> +<src/HAL/shared>
|
||||
-<src/sd/usb_flashdrive/Sd2Card_FlashDrive.cpp>
|
||||
-<src/sd/cardreader.cpp> -<src/sd/Sd2Card.cpp> -<src/sd/SdBaseFile.cpp> -<src/sd/SdFatUtil.cpp> -<src/sd/SdFile.cpp> -<src/sd/SdVolume.cpp> -<src/gcode/sd>
|
||||
-<src/HAL/shared/backtrace>
|
||||
-<src/HAL/shared/cpu_exception>
|
||||
-<src/HAL/shared/eeprom_if_i2c.cpp>
|
||||
-<src/HAL/shared/eeprom_if_spi.cpp>
|
||||
-<src/feature/babystep.cpp>
|
||||
@ -223,6 +224,8 @@ YHCB2004 = red-scorp/LiquidCrystal_AIP31068@^1.0.4, red-scorp/Sof
|
||||
HAS_TFT_LVGL_UI = lvgl=https://github.com/makerbase-mks/LVGL-6.1.1-MKS/archive/master.zip
|
||||
src_filter=+<src/lcd/extui/lib/mks_ui>
|
||||
extra_scripts=download_mks_assets.py
|
||||
POSTMORTEM_DEBUGGING = src_filter=+<src/HAL/shared/cpu_exception> +<src/HAL/shared/backtrace>
|
||||
build_flags=-funwind-tables
|
||||
MKS_WIFI_MODULE = QRCode=https://github.com/makerbase-mks/QRCode/archive/master.zip
|
||||
HAS_TRINAMIC_CONFIG = TMCStepper@~0.7.1
|
||||
src_filter=+<src/feature/tmc_util.cpp> +<src/module/stepper/trinamic.cpp> +<src/gcode/feature/trinamic/M122.cpp> +<src/gcode/feature/trinamic/M906.cpp> +<src/gcode/feature/trinamic/M911-M914.cpp>
|
||||
@ -637,14 +640,6 @@ platform = atmelsam
|
||||
extends = env:DUE
|
||||
board = dueUSB
|
||||
|
||||
[env:DUE_debug]
|
||||
# Used when WATCHDOG_RESET_MANUAL is enabled
|
||||
platform = atmelsam
|
||||
extends = env:DUE
|
||||
build_flags = ${common.build_flags}
|
||||
-funwind-tables
|
||||
-mpoke-function-name
|
||||
|
||||
#
|
||||
# Archim SAM
|
||||
#
|
||||
@ -662,12 +657,6 @@ extra_scripts = ${common.extra_scripts}
|
||||
platform = ${common_DUE_archim.platform}
|
||||
extends = common_DUE_archim
|
||||
|
||||
# Used when WATCHDOG_RESET_MANUAL is enabled
|
||||
[env:DUE_archim_debug]
|
||||
platform = ${common_DUE_archim.platform}
|
||||
extends = common_DUE_archim
|
||||
build_flags = ${common_DUE_archim.build_flags} -funwind-tables -mpoke-function-name
|
||||
|
||||
#################################
|
||||
# #
|
||||
# SAMD51 Architecture #
|
||||
@ -763,6 +752,8 @@ lib_ignore = SPI, FreeRTOS701, FreeRTOS821
|
||||
lib_deps = ${common.lib_deps}
|
||||
SoftwareSerialM
|
||||
platform_packages = tool-stm32duino
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/fix_framework_weakness.py
|
||||
|
||||
#
|
||||
# STM32F103RC
|
||||
@ -788,7 +779,7 @@ build_flags = ${common_stm32f1.build_flags}
|
||||
-DUSE_USB_COMPOSITE
|
||||
-DVECT_TAB_OFFSET=0x2000
|
||||
-DGENERIC_BOOTLOADER
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
pre:buildroot/share/PlatformIO/scripts/STM32F1_create_variant.py
|
||||
buildroot/share/PlatformIO/scripts/STM32F103RC_MEEB_3DP.py
|
||||
lib_deps = ${common.lib_deps}
|
||||
@ -934,7 +925,7 @@ upload_protocol = serial
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103VE
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/STM32F103VE_longer.py
|
||||
build_flags = ${common_stm32f1.build_flags}
|
||||
-DMCU_STM32F103VE -DSTM32F1xx -USERIAL_USB -DU20 -DTS_V12
|
||||
@ -948,7 +939,7 @@ build_unflags = ${common_stm32f1.build_unflags}
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103VE
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/mks_robin_mini.py
|
||||
build_flags = ${common_stm32f1.build_flags}
|
||||
-DMCU_STM32F103VE
|
||||
@ -974,7 +965,7 @@ upload_protocol = jlink
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103ZE
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/mks_robin.py
|
||||
build_flags = ${common_stm32f1.build_flags}
|
||||
-DSS_TIMER=4 -DSTM32_XL_DENSITY
|
||||
@ -1008,7 +999,7 @@ lib_deps =
|
||||
[env:mks_robin_pro]
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = env:mks_robin
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/mks_robin_pro.py
|
||||
|
||||
#
|
||||
@ -1017,7 +1008,7 @@ extra_scripts = ${common.extra_scripts}
|
||||
[env:trigorilla_pro]
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = env:mks_robin
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
|
||||
#
|
||||
# MKS Robin E3D (STM32F103RCT6) and
|
||||
@ -1041,7 +1032,7 @@ build_flags = ${common_stm32f1.build_flags}
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103VE
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/mks_robin_e3p.py
|
||||
build_flags = ${common_stm32f1.build_flags}
|
||||
-DMCU_STM32F103VE -DSS_TIMER=4
|
||||
@ -1055,7 +1046,7 @@ upload_protocol = jlink
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103RC
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/mks_robin_lite.py
|
||||
|
||||
#
|
||||
@ -1065,7 +1056,7 @@ extra_scripts = ${common.extra_scripts}
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103RC
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/mks_robin_lite3.py
|
||||
|
||||
#
|
||||
@ -1075,7 +1066,7 @@ extra_scripts = ${common.extra_scripts}
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103ZE
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/jgaurora_a5s_a1_with_bootloader.py
|
||||
build_flags = ${common_stm32f1.build_flags}
|
||||
-DSTM32F1xx -DSTM32_XL_DENSITY
|
||||
@ -1230,7 +1221,7 @@ extra_scripts = ${common.extra_scripts}
|
||||
platform = ${common_stm32f1.platform}
|
||||
extends = common_stm32f1
|
||||
board = genericSTM32F103RC
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts = ${common_stm32f1.extra_scripts}
|
||||
buildroot/share/PlatformIO/scripts/fly_mini.py
|
||||
build_flags = ${common_stm32f1.build_flags}
|
||||
-DDEBUG_LEVEL=0 -DSS_TIMER=4
|
||||
@ -1557,7 +1548,6 @@ build_flags = ${common_stm32.build_flags} -DENABLE_HWSERIAL3 -DTIMER_SE
|
||||
build_unflags = ${common_stm32.build_unflags} -DUSBCON -DUSBD_USE_CDC
|
||||
extra_scripts = ${common.extra_scripts} pre:buildroot/share/PlatformIO/scripts/generic_create_variant.py buildroot/share/PlatformIO/scripts/stm32_bootloader.py
|
||||
|
||||
|
||||
#################################
|
||||
# #
|
||||
# Other Architectures #
|
||||
|
Loading…
Reference in New Issue
Block a user