diff options
Diffstat (limited to 'Marlin/src/HAL/shared')
31 files changed, 4532 insertions, 0 deletions
diff --git a/Marlin/src/HAL/shared/Delay.h b/Marlin/src/HAL/shared/Delay.h new file mode 100644 index 0000000..a48f3f7 --- /dev/null +++ b/Marlin/src/HAL/shared/Delay.h @@ -0,0 +1,161 @@ +/** + * 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/>. + * + */ +#pragma once + +/** + * Busy wait delay cycles routines: + * + * DELAY_CYCLES(count): Delay execution in cycles + * DELAY_NS(count): Delay execution in nanoseconds + * DELAY_US(count): Delay execution in microseconds + */ + +#include "../../core/macros.h" + +#if defined(__arm__) || defined(__thumb__) + + #if __CORTEX_M == 7 + + // Cortex-M3 through M7 can use the cycle counter of the DWT unit + // https://www.anthonyvh.com/2017/05/18/cortex_m-cycle_counter/ + + FORCE_INLINE static void enableCycleCounter() { + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + + #if __CORTEX_M == 7 + DWT->LAR = 0xC5ACCE55; // Unlock DWT on the M7 + #endif + + DWT->CYCCNT = 0; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + } + + FORCE_INLINE volatile uint32_t getCycleCount() { return DWT->CYCCNT; } + + FORCE_INLINE static void DELAY_CYCLES(const uint32_t x) { + const uint32_t endCycles = getCycleCount() + x; + while (PENDING(getCycleCount(), endCycles)) {} + } + + #else + + // https://blueprints.launchpad.net/gcc-arm-embedded/+spec/delay-cycles + + #define nop() __asm__ __volatile__("nop;\n\t":::) + + FORCE_INLINE static void __delay_4cycles(uint32_t cy) { // +1 cycle + #if ARCH_PIPELINE_RELOAD_CYCLES < 2 + #define EXTRA_NOP_CYCLES A("nop") + #else + #define EXTRA_NOP_CYCLES "" + #endif + + __asm__ __volatile__( + A(".syntax unified") // is to prevent CM0,CM1 non-unified syntax + L("1") + A("subs %[cnt],#1") + EXTRA_NOP_CYCLES + A("bne 1b") + : [cnt]"+r"(cy) // output: +r means input+output + : // input: + : "cc" // clobbers: + ); + } + + // Delay in cycles + FORCE_INLINE static void DELAY_CYCLES(uint32_t x) { + + if (__builtin_constant_p(x)) { + #define MAXNOPS 4 + + if (x <= (MAXNOPS)) { + switch (x) { case 4: nop(); case 3: nop(); case 2: nop(); case 1: nop(); } + } + else { // because of +1 cycle inside delay_4cycles + const uint32_t rem = (x - 1) % (MAXNOPS); + switch (rem) { case 3: nop(); case 2: nop(); case 1: nop(); } + if ((x = (x - 1) / (MAXNOPS))) + __delay_4cycles(x); // if need more then 4 nop loop is more optimal + } + #undef MAXNOPS + } + else if ((x >>= 2)) + __delay_4cycles(x); + } + #undef nop + + #endif + +#elif defined(__AVR__) + + #define nop() __asm__ __volatile__("nop;\n\t":::) + + FORCE_INLINE static void __delay_4cycles(uint8_t cy) { + __asm__ __volatile__( + L("1") + A("dec %[cnt]") + A("nop") + A("brne 1b") + : [cnt] "+r"(cy) // output: +r means input+output + : // input: + : "cc" // clobbers: + ); + } + + // Delay in cycles + FORCE_INLINE static void DELAY_CYCLES(uint16_t x) { + + if (__builtin_constant_p(x)) { + #define MAXNOPS 4 + + if (x <= (MAXNOPS)) { + switch (x) { case 4: nop(); case 3: nop(); case 2: nop(); case 1: nop(); } + } + else { + const uint32_t rem = (x) % (MAXNOPS); + switch (rem) { case 3: nop(); case 2: nop(); case 1: nop(); } + if ((x = (x) / (MAXNOPS))) + __delay_4cycles(x); // if need more then 4 nop loop is more optimal + } + + #undef MAXNOPS + } + else if ((x >>= 2)) + __delay_4cycles(x); + } + #undef nop + +#elif defined(__PLAT_LINUX__) || defined(ESP32) + + // specified inside platform + +#else + + #error "Unsupported MCU architecture" + +#endif + +// Delay in nanoseconds +#define DELAY_NS(x) DELAY_CYCLES((x) * ((F_CPU) / 1000000UL) / 1000UL) + +// Delay in microseconds +#define DELAY_US(x) DELAY_CYCLES((x) * ((F_CPU) / 1000000UL)) diff --git a/Marlin/src/HAL/shared/HAL_SPI.h b/Marlin/src/HAL/shared/HAL_SPI.h new file mode 100644 index 0000000..59af554 --- /dev/null +++ b/Marlin/src/HAL/shared/HAL_SPI.h @@ -0,0 +1,93 @@ +/** + * 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/>. + * + */ +#pragma once + +/** + * HAL/shared/HAL_SPI.h + * Core Marlin definitions for SPI, implemented in the HALs + */ + +#include "Marduino.h" +#include <stdint.h> + +/** + * SPI speed where 0 <= index <= 6 + * + * Approximate rates : + * + * 0 : 8 - 10 MHz + * 1 : 4 - 5 MHz + * 2 : 2 - 2.5 MHz + * 3 : 1 - 1.25 MHz + * 4 : 500 - 625 kHz + * 5 : 250 - 312 kHz + * 6 : 125 - 156 kHz + * + * On AVR, actual speed is F_CPU/2^(1 + index). + * On other platforms, speed should be in range given above where possible. + */ + +#define SPI_FULL_SPEED 0 // Set SCK to max rate +#define SPI_HALF_SPEED 1 // Set SCK rate to half of max rate +#define SPI_QUARTER_SPEED 2 // Set SCK rate to quarter of max rate +#define SPI_EIGHTH_SPEED 3 // Set SCK rate to 1/8 of max rate +#define SPI_SIXTEENTH_SPEED 4 // Set SCK rate to 1/16 of max rate +#define SPI_SPEED_5 5 // Set SCK rate to 1/32 of max rate +#define SPI_SPEED_6 6 // Set SCK rate to 1/64 of max rate + +// +// Standard SPI functions +// + +// Initialize SPI bus +void spiBegin(); + +// Configure SPI for specified SPI speed +void spiInit(uint8_t spiRate); + +// Write single byte to SPI +void spiSend(uint8_t b); + +// Read single byte from SPI +uint8_t spiRec(); + +// Read from SPI into buffer +void spiRead(uint8_t* buf, uint16_t nbyte); + +// Write token and then write from 512 byte buffer to SPI (for SD card) +void spiSendBlock(uint8_t token, const uint8_t* buf); + +// Begin SPI transaction, set clock, bit order, data mode +void spiBeginTransaction(uint32_t spiClock, uint8_t bitOrder, uint8_t dataMode); + +// +// Extended SPI functions taking a channel number (Hardware SPI only) +// + +// Write single byte to specified SPI channel +void spiSend(uint32_t chan, byte b); + +// Write buffer to specified SPI channel +void spiSend(uint32_t chan, const uint8_t* buf, size_t n); + +// Read single byte from specified SPI channel +uint8_t spiRec(uint32_t chan); diff --git a/Marlin/src/HAL/shared/HAL_ST7920.h b/Marlin/src/HAL/shared/HAL_ST7920.h new file mode 100644 index 0000000..4e362f9 --- /dev/null +++ b/Marlin/src/HAL/shared/HAL_ST7920.h @@ -0,0 +1,36 @@ +/** + * 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/>. + * + */ +#pragma once + +/** + * HAL/ST7920.h + * For the HALs that provide direct access to the ST7920 display + * (bypassing U8G), it will allow the LIGHTWEIGHT_UI to operate. + */ + +#if BOTH(HAS_MARLINUI_U8GLIB, LIGHTWEIGHT_UI) + void ST7920_cs(); + void ST7920_ncs(); + void ST7920_set_cmd(); + void ST7920_set_dat(); + void ST7920_write_byte(const uint8_t data); +#endif diff --git a/Marlin/src/HAL/shared/HAL_spi_L6470.cpp b/Marlin/src/HAL/shared/HAL_spi_L6470.cpp new file mode 100644 index 0000000..bd85dbe --- /dev/null +++ b/Marlin/src/HAL/shared/HAL_spi_L6470.cpp @@ -0,0 +1,139 @@ +/** + * 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/>. + * + */ + +/** + * Software L6470 SPI functions originally from Arduino Sd2Card Library + * Copyright (c) 2009 by William Greiman + */ + +#include "../../inc/MarlinConfig.h" + +#if HAS_L64XX + +#include "Delay.h" + +#include "../../core/serial.h" +#include "../../libs/L64XX/L64XX_Marlin.h" + +// Make sure GCC optimizes this file. +// Note that this line triggers a bug in GCC which is fixed by casting. +// See the note below. +#pragma GCC optimize (3) + +// run at ~4Mhz +inline uint8_t L6470_SpiTransfer_Mode_0(uint8_t b) { // using Mode 0 + for (uint8_t bits = 8; bits--;) { + WRITE(L6470_CHAIN_MOSI_PIN, b & 0x80); + b <<= 1; // little setup time + + WRITE(L6470_CHAIN_SCK_PIN, HIGH); + DELAY_NS(125); // 10 cycles @ 84mhz + + b |= (READ(L6470_CHAIN_MISO_PIN) != 0); + + WRITE(L6470_CHAIN_SCK_PIN, LOW); + DELAY_NS(125); // 10 cycles @ 84mhz + } + return b; +} + +inline uint8_t L6470_SpiTransfer_Mode_3(uint8_t b) { // using Mode 3 + for (uint8_t bits = 8; bits--;) { + WRITE(L6470_CHAIN_SCK_PIN, LOW); + WRITE(L6470_CHAIN_MOSI_PIN, b & 0x80); + + DELAY_NS(125); // 10 cycles @ 84mhz + WRITE(L6470_CHAIN_SCK_PIN, HIGH); + DELAY_NS(125); // Need more delay for fast CPUs + + b <<= 1; // little setup time + b |= (READ(L6470_CHAIN_MISO_PIN) != 0); + } + DELAY_NS(125); // 10 cycles @ 84mhz + return b; +} + +/** + * L64XX methods for SPI init and transfer + */ +void L64XX_Marlin::spi_init() { + OUT_WRITE(L6470_CHAIN_SS_PIN, HIGH); + OUT_WRITE(L6470_CHAIN_SCK_PIN, HIGH); + OUT_WRITE(L6470_CHAIN_MOSI_PIN, HIGH); + SET_INPUT(L6470_CHAIN_MISO_PIN); + + #if PIN_EXISTS(L6470_BUSY) + SET_INPUT(L6470_BUSY_PIN); + #endif + + OUT_WRITE(L6470_CHAIN_MOSI_PIN, HIGH); +} + +uint8_t L64XX_Marlin::transfer_single(uint8_t data, int16_t ss_pin) { + // First device in chain has data sent last + extDigitalWrite(ss_pin, LOW); + + DISABLE_ISRS(); // Disable interrupts during SPI transfer (can't allow partial command to chips) + const uint8_t data_out = L6470_SpiTransfer_Mode_3(data); + ENABLE_ISRS(); // Enable interrupts + + extDigitalWrite(ss_pin, HIGH); + return data_out; +} + +uint8_t L64XX_Marlin::transfer_chain(uint8_t data, int16_t ss_pin, uint8_t chain_position) { + uint8_t data_out = 0; + + // first device in chain has data sent last + extDigitalWrite(ss_pin, LOW); + + for (uint8_t i = L64XX::chain[0]; !L64xxManager.spi_abort && i >= 1; i--) { // Send data unless aborted + DISABLE_ISRS(); // Disable interrupts during SPI transfer (can't allow partial command to chips) + const uint8_t temp = L6470_SpiTransfer_Mode_3(uint8_t(i == chain_position ? data : dSPIN_NOP)); + ENABLE_ISRS(); // Enable interrupts + if (i == chain_position) data_out = temp; + } + + extDigitalWrite(ss_pin, HIGH); + return data_out; +} + +/** + * Platform-supplied L6470 buffer transfer method + */ +void L64XX_Marlin::transfer(uint8_t L6470_buf[], const uint8_t length) { + // First device in chain has its data sent last + + if (spi_active) { // Interrupted SPI transfer so need to + WRITE(L6470_CHAIN_SS_PIN, HIGH); // guarantee min high of 650ns + DELAY_US(1); + } + + WRITE(L6470_CHAIN_SS_PIN, LOW); + for (uint8_t i = length; i >= 1; i--) + L6470_SpiTransfer_Mode_3(uint8_t(L6470_buf[i])); + WRITE(L6470_CHAIN_SS_PIN, HIGH); +} + +#pragma GCC reset_options + +#endif // HAS_L64XX diff --git a/Marlin/src/HAL/shared/Marduino.h b/Marlin/src/HAL/shared/Marduino.h new file mode 100644 index 0000000..d0ee6ec --- /dev/null +++ b/Marlin/src/HAL/shared/Marduino.h @@ -0,0 +1,85 @@ +/** + * 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/>. + * + */ +#pragma once + +/** + * HAL/shared/Marduino.h + */ + +#undef DISABLED // Redefined by ESP32 +#undef M_PI // Redefined by all +#undef _BV // Redefined by some +#undef SBI // Redefined by arduino/const_functions.h +#undef CBI // Redefined by arduino/const_functions.h +#undef sq // Redefined by teensy3/wiring.h +#undef UNUSED // Redefined by stm32f4xx_hal_def.h + +#include <Arduino.h> // NOTE: If included earlier then this line is a NOOP + +#undef DISABLED +#define DISABLED(V...) DO(DIS,&&,V) + +#undef _BV +#define _BV(b) (1UL << (b)) +#ifndef SBI + #define SBI(A,B) (A |= _BV(B)) +#endif +#ifndef CBI + #define CBI(A,B) (A &= ~_BV(B)) +#endif + +#undef sq +#define sq(x) ((x)*(x)) + +#ifndef __AVR__ + #ifndef strchr_P // Some platforms define a macro (DUE, teensy35) + inline const char* strchr_P(const char *s, int c) { return strchr(s,c); } + //#define strchr_P(s,c) strchr(s,c) + #endif + + #ifndef snprintf_P + #define snprintf_P snprintf + #endif + #ifndef vsnprintf_P + #define vsnprintf_P vsnprintf + #endif +#endif + +// Restart causes +#define RST_POWER_ON 1 +#define RST_EXTERNAL 2 +#define RST_BROWN_OUT 4 +#define RST_WATCHDOG 8 +#define RST_JTAG 16 +#define RST_SOFTWARE 32 +#define RST_BACKUP 64 + +#ifndef M_PI + #define M_PI 3.14159265358979323846f +#endif + +// Remove compiler warning on an unused variable +#ifndef UNUSED + #define UNUSED(x) ((void)(x)) +#endif + +#include "progmem.h" diff --git a/Marlin/src/HAL/shared/backtrace/backtrace.cpp b/Marlin/src/HAL/shared/backtrace/backtrace.cpp new file mode 100644 index 0000000..6cf5e05 --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/backtrace.cpp @@ -0,0 +1,98 @@ +/** + * 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/>. + * + */ +#if defined(__arm__) || defined(__thumb__) + +#include "backtrace.h" +#include "unwinder.h" +#include "unwmemaccess.h" + +#include "../../../core/serial.h" +#include <stdarg.h> + +// Dump a backtrace entry +static bool UnwReportOut(void* ctx, const UnwReport* bte) { + int *p = (int*)ctx; + + (*p)++; + + SERIAL_CHAR('#'); SERIAL_PRINT(*p, DEC); SERIAL_ECHOPGM(" : "); + SERIAL_ECHOPGM(bte->name ? bte->name : "unknown"); SERIAL_ECHOPGM("@0x"); SERIAL_PRINT(bte->function, HEX); + SERIAL_CHAR('+'); SERIAL_PRINT(bte->address - bte->function,DEC); + SERIAL_ECHOPGM(" PC:"); SERIAL_PRINT(bte->address,HEX); SERIAL_CHAR('\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 +}; + +void backtrace() { + + UnwindFrame btf; + uint32_t sp = 0, lr = 0, pc = 0; + + // Capture the values of the registers to perform the traceback + __asm__ __volatile__ ( + " mov %[lrv],lr\n" + " mov %[spv],sp\n" + " mov %[pcv],pc\n" + : [spv]"+r"( sp ), + [lrv]"+r"( lr ), + [pcv]"+r"( pc ) + :: + ); + + // Fill the traceback structure + btf.sp = sp; + btf.fp = btf.sp; + btf.lr = lr; + btf.pc = pc | 1; // Force Thumb, as CORTEX only support it + + // Perform a backtrace + SERIAL_ERROR_MSG("Backtrace:"); + int ctr = 0; + UnwindStart(&btf, &UnwCallbacks, &ctr); +} + +#else // !__arm__ && !__thumb__ + +void backtrace() {} + +#endif diff --git a/Marlin/src/HAL/shared/backtrace/backtrace.h b/Marlin/src/HAL/shared/backtrace/backtrace.h new file mode 100644 index 0000000..fccaded --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/backtrace.h @@ -0,0 +1,25 @@ +/** + * 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/>. + * + */ +#pragma once + +// Perform a backtrace to the serial port +void backtrace(); diff --git a/Marlin/src/HAL/shared/backtrace/unwarm.cpp b/Marlin/src/HAL/shared/backtrace/unwarm.cpp new file mode 100644 index 0000000..adbcca6 --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarm.cpp @@ -0,0 +1,175 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Utility functions and glue for ARM unwinding sub-modules. + **************************************************************************/ + +#if defined(__arm__) || defined(__thumb__) + +#define MODULE_NAME "UNWARM" + +#include <stdint.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include "unwarm.h" +#include "unwarmmem.h" + +#ifdef UNW_DEBUG + +/** + * Printf wrapper. + * This is used such that alternative outputs for any output can be selected + * by modification of this wrapper function. + */ +void UnwPrintf(const char *format, ...) { + va_list args; + + va_start( args, format ); + vprintf(format, args ); +} +#endif + +/** + * Invalidate all general purpose registers. + */ +void UnwInvalidateRegisterFile(RegData *regFile) { + uint8_t t = 0; + do { + regFile[t].o = REG_VAL_INVALID; + t++; + } while (t < 13); +} + + +/** + * Initialize the data used for unwinding. + */ +void UnwInitState(UnwState * const state, /**< Pointer to structure to fill. */ + const UnwindCallbacks *cb, /**< Callbacks. */ + void *rptData, /**< Data to pass to report function. */ + uint32_t pcValue, /**< PC at which to start unwinding. */ + uint32_t spValue) { /**< SP at which to start unwinding. */ + + UnwInvalidateRegisterFile(state->regData); + + /* Store the pointer to the callbacks */ + state->cb = cb; + state->reportData = rptData; + + /* Setup the SP and PC */ + state->regData[13].v = spValue; + state->regData[13].o = REG_VAL_FROM_CONST; + state->regData[15].v = pcValue; + state->regData[15].o = REG_VAL_FROM_CONST; + + UnwPrintd3("\nInitial: PC=0x%08x SP=0x%08x\n", pcValue, spValue); + + /* Invalidate all memory addresses */ + memset(state->memData.used, 0, sizeof(state->memData.used)); +} + +// Detect if function names are available +static int __attribute__ ((noinline)) has_function_names() { + uint32_t flag_word = ((uint32_t*)(((uint32_t)(&has_function_names)) & (-4))) [-1]; + return ((flag_word & 0xFF000000) == 0xFF000000) ? 1 : 0; +} + +/** + * Call the report function to indicate some return address. + * This returns the value of the report function, which if true + * indicates that unwinding may continue. + */ +bool UnwReportRetAddr(UnwState * const state, uint32_t addr) { + + UnwReport entry; + + // We found two acceptable values. + entry.name = nullptr; + entry.address = addr & 0xFFFFFFFE; // Remove Thumb bit + entry.function = 0; + + // If there are function names, try to solve name + if (has_function_names()) { + + // Lets find the function name, if possible + + // Align address to 4 bytes + uint32_t pf = addr & (-4); + + // Scan backwards until we find the function name + uint32_t v; + while (state->cb->readW(pf-4,&v)) { + + // Check if name descriptor is valid + if ((v & 0xFFFFFF00) == 0xFF000000 && (v & 0xFF) > 1) { + // Assume the name was found! + entry.name = ((const char*)pf) - 4 - (v & 0xFF); + entry.function = pf; + break; + } + + // Go backwards to the previous word + pf -= 4; + } + } + + /* Cast away const from reportData. + * The const is only to prevent the unw module modifying the data. + */ + return state->cb->report((void *)state->reportData, &entry); +} + + +/** + * Write some register to memory. + * This will store some register and meta data onto the virtual stack. + * The address for the write + * \param state [in/out] The unwinding state. + * \param wAddr [in] The address at which to write the data. + * \param reg [in] The register to store. + * \return true if the write was successful, false otherwise. + */ +bool UnwMemWriteRegister(UnwState * const state, const uint32_t addr, const RegData * const reg) { + return UnwMemHashWrite(&state->memData, addr, reg->v, M_IsOriginValid(reg->o)); +} + +/** + * Read a register from memory. + * This will read a register from memory, and setup the meta data. + * If the register has been previously written to memory using + * UnwMemWriteRegister, the local hash will be used to return the + * value while respecting whether the data was valid or not. If the + * register was previously written and was invalid at that point, + * REG_VAL_INVALID will be returned in *reg. + * \param state [in] The unwinding state. + * \param addr [in] The address to read. + * \param reg [out] The result, containing the data value and the origin + * which will be REG_VAL_FROM_MEMORY, or REG_VAL_INVALID. + * \return true if the address could be read and *reg has been filled in. + * false is the data could not be read. + */ +bool UnwMemReadRegister(UnwState * const state, const uint32_t addr, RegData * const reg) { + bool tracked; + + // Check if the value can be found in the hash + if (UnwMemHashRead(&state->memData, addr, ®->v, &tracked)) { + reg->o = tracked ? REG_VAL_FROM_MEMORY : REG_VAL_INVALID; + return true; + } + else if (state->cb->readW(addr, ®->v)) { // Not in the hash, so read from real memory + reg->o = REG_VAL_FROM_MEMORY; + return true; + } + else return false; // Not in the hash, and failed to read from memory +} + +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/backtrace/unwarm.h b/Marlin/src/HAL/shared/backtrace/unwarm.h new file mode 100644 index 0000000..86dc98c --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarm.h @@ -0,0 +1,140 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Internal interface between the ARM unwinding sub-modules. + **************************************************************************/ + +#pragma once + +#include "unwinder.h" + +/** The maximum number of instructions to interpet in a function. + * Unwinding will be unconditionally stopped and UNWIND_EXHAUSTED returned + * if more than this number of instructions are interpreted in a single + * function without unwinding a stack frame. This prevents infinite loops + * or corrupted program memory from preventing unwinding from progressing. + */ +#define UNW_MAX_INSTR_COUNT 500 + +/** The size of the hash used to track reads and writes to memory. + * This should be a prime value for efficiency. + */ +#define MEM_HASH_SIZE 31 + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + +typedef enum { + /** Invalid value. */ + REG_VAL_INVALID = 0x00, + REG_VAL_FROM_STACK = 0x01, + REG_VAL_FROM_MEMORY = 0x02, + REG_VAL_FROM_CONST = 0x04, + REG_VAL_ARITHMETIC = 0x80 +} RegValOrigin; + + +/** Type for tracking information about a register. + * This stores the register value, as well as other data that helps unwinding. + */ +typedef struct { + + /** The value held in the register. */ + uint32_t v; + + /** The origin of the register value. + * This is used to track how the value in the register was loaded. + */ + int o; /* (RegValOrigin) */ +} RegData; + + +/** Structure used to track reads and writes to memory. + * This structure is used as a hash to store a small number of writes + * to memory. + */ +typedef struct { + /** Memory contents. */ + uint32_t v[MEM_HASH_SIZE]; + + /** Address at which v[n] represents. */ + uint32_t a[MEM_HASH_SIZE]; + + /** Indicates whether the data in v[n] and a[n] is occupied. + * Each bit represents one hash value. + */ + uint8_t used[(MEM_HASH_SIZE + 7) / 8]; + + /** Indicates whether the data in v[n] is valid. + * This allows a[n] to be set, but for v[n] to be marked as invalid. + * Specifically this is needed for when an untracked register value + * is written to memory. + */ + uint8_t tracked[(MEM_HASH_SIZE + 7) / 8]; +} MemData; + + +/** Structure that is used to keep track of unwinding meta-data. + * This data is passed between all the unwinding functions. + */ +typedef struct { + /** The register values and meta-data. */ + RegData regData[16]; + + /** Memory tracking data. */ + MemData memData; + + /** Pointer to the callback functions */ + const UnwindCallbacks *cb; + + /** Pointer to pass to the report function. */ + const void *reportData; +} UnwState; + +/*************************************************************************** + * Macros + **************************************************************************/ + +#define M_IsOriginValid(v) !!((v) & 0x7F) +#define M_Origin2Str(v) ((v) ? "VALID" : "INVALID") + +#ifdef UNW_DEBUG +#define UnwPrintd1(a) state->cb->printf(a) +#define UnwPrintd2(a,b) state->cb->printf(a,b) +#define UnwPrintd3(a,b,c) state->cb->printf(a,b,c) +#define UnwPrintd4(a,b,c,d) state->cb->printf(a,b,c,d) +#define UnwPrintd5(a,b,c,d,e) state->cb->printf(a,b,c,d,e) +#define UnwPrintd6(a,b,c,d,e,f) state->cb->printf(a,b,c,d,e,f) +#define UnwPrintd7(a,b,c,d,e,f,g) state->cb->printf(a,b,c,d,e,f,g) +#define UnwPrintd8(a,b,c,d,e,f,g,h) state->cb->printf(a,b,c,d,e,f,g,h) +#else +#define UnwPrintd1(a) +#define UnwPrintd2(a,b) +#define UnwPrintd3(a,b,c) +#define UnwPrintd4(a,b,c,d) +#define UnwPrintd5(a,b,c,d,e) +#define UnwPrintd6(a,b,c,d,e,f) +#define UnwPrintd7(a,b,c,d,e,f,g) +#define UnwPrintd8(a,b,c,d,e,f,g,h) +#endif + +/*************************************************************************** + * Function Prototypes + **************************************************************************/ + +UnwResult UnwStartArm(UnwState * const state); +UnwResult UnwStartThumb(UnwState * const state); +void UnwInvalidateRegisterFile(RegData *regFile); +void UnwInitState(UnwState * const state, const UnwindCallbacks *cb, void *rptData, uint32_t pcValue, uint32_t spValue); +bool UnwReportRetAddr(UnwState * const state, uint32_t addr); +bool UnwMemWriteRegister(UnwState * const state, const uint32_t addr, const RegData * const reg); +bool UnwMemReadRegister(UnwState * const state, const uint32_t addr, RegData * const reg); +void UnwMemHashGC(UnwState * const state); diff --git a/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp b/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp new file mode 100644 index 0000000..decf74e --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarm_arm.cpp @@ -0,0 +1,534 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Abstract interpreter for ARM mode. + **************************************************************************/ + +#if defined(__arm__) || defined(__thumb__) + +#define MODULE_NAME "UNWARM_ARM" + +#include <stdio.h> +#include "unwarm.h" + +/** Check if some instruction is a data-processing instruction. + * Decodes the passed instruction, checks if it is a data-processing and + * verifies that the parameters and operation really indicate a data- + * processing instruction. This is needed because some parts of the + * instruction space under this instruction can be extended or represent + * other operations such as MRS, MSR. + * + * \param[in] inst The instruction word. + * \retval true Further decoding of the instruction indicates that this is + * a valid data-processing instruction. + * \retval false This is not a data-processing instruction, + */ +static bool isDataProc(uint32_t instr) { + uint8_t opcode = (instr & 0x01E00000) >> 21; + if ((instr & 0xFC000000) != 0xE0000000) return false; + + /* TST, TEQ, CMP and CMN all require S to be set */ + bool S = !!(instr & 0x00100000); + if (!S && opcode >= 8 && opcode <= 11) return false; + + return true; +} + +UnwResult UnwStartArm(UnwState * const state) { + uint16_t t = UNW_MAX_INSTR_COUNT; + + for (;;) { + uint32_t instr; + + /* Attempt to read the instruction */ + if (!state->cb->readW(state->regData[15].v, &instr)) + return UNWIND_IREAD_W_FAIL; + + UnwPrintd4("A %x %x %08x:", state->regData[13].v, state->regData[15].v, instr); + + /* Check that the PC is still on Arm alignment */ + if (state->regData[15].v & 0x3) { + UnwPrintd1("\nError: PC misalignment\n"); + return UNWIND_INCONSISTENT; + } + + /* Check that the SP and PC have not been invalidated */ + if (!M_IsOriginValid(state->regData[13].o) || !M_IsOriginValid(state->regData[15].o)) { + UnwPrintd1("\nError: PC or SP invalidated\n"); + return UNWIND_INCONSISTENT; + } + + /* Branch and Exchange (BX) + * This is tested prior to data processing to prevent + * mis-interpretation as an invalid TEQ instruction. + */ + if ((instr & 0xFFFFFFF0) == 0xE12FFF10) { + uint8_t rn = instr & 0xF; + + UnwPrintd4("BX r%d\t ; r%d %s\n", rn, rn, M_Origin2Str(state->regData[rn].o)); + + if (!M_IsOriginValid(state->regData[rn].o)) { + UnwPrintd1("\nUnwind failure: BX to untracked register\n"); + return UNWIND_FAILURE; + } + + /* Set the new PC value */ + state->regData[15].v = state->regData[rn].v; + + /* Check if the return value is from the stack */ + if (state->regData[rn].o == REG_VAL_FROM_STACK) { + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v & (~0x1)); + + /* Report the return address */ + if (!UnwReportRetAddr(state, state->regData[rn].v)) + return UNWIND_TRUNCATED; + } + + /* Determine the return mode */ + if (state->regData[rn].v & 0x1) /* Branching to THUMB */ + return UnwStartThumb(state); + + /* Branch to ARM */ + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 4; + } + /* Branch */ + else if ((instr & 0xFF000000) == 0xEA000000) { + + int32_t offset = (instr & 0x00FFFFFF) << 2; + + /* Sign extend if needed */ + if (offset & 0x02000000) offset |= 0xFC000000; + + UnwPrintd2("B %d\n", offset); + + /* Adjust PC */ + state->regData[15].v += offset; + + /* Account for pre-fetch, where normally the PC is 8 bytes + * ahead of the instruction just executed. + */ + state->regData[15].v += 4; + } + + /* MRS */ + else if ((instr & 0xFFBF0FFF) == 0xE10F0000) { + uint8_t rd = (instr & 0x0000F000) >> 12; + #ifdef UNW_DEBUG + const bool R = !!(instr & 0x00400000); + UnwPrintd4("MRS r%d,%s\t; r%d invalidated", rd, R ? "SPSR" : "CPSR", rd); + #endif + + /* Status registers untracked */ + state->regData[rd].o = REG_VAL_INVALID; + } + /* MSR */ + else if ((instr & 0xFFB0F000) == 0xE120F000) { + #ifdef UNW_DEBUG + UnwPrintd2("MSR %s_?, ???", (instr & 0x00400000) ? "SPSR" : "CPSR"); + #endif + + /* Status registers untracked. + * Potentially this could change processor mode and switch + * banked registers r8-r14. Most likely is that r13 (sp) will + * be banked. However, invalidating r13 will stop unwinding + * when potentially this write is being used to disable/enable + * interrupts (a common case). Therefore no invalidation is + * performed. + */ + } + /* Data processing */ + else if (isDataProc(instr)) { + bool I = !!(instr & 0x02000000); + uint8_t opcode = (instr & 0x01E00000) >> 21; + #ifdef UNW_DEBUG + bool S = !!(instr & 0x00100000); + #endif + uint8_t rn = (instr & 0x000F0000) >> 16; + uint8_t rd = (instr & 0x0000F000) >> 12; + uint16_t operand2 = (instr & 0x00000FFF); + uint32_t op2val; + int op2origin; + + switch (opcode) { + case 0: UnwPrintd4("AND%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 1: UnwPrintd4("EOR%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 2: UnwPrintd4("SUB%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 3: UnwPrintd4("RSB%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 4: UnwPrintd4("ADD%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 5: UnwPrintd4("ADC%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 6: UnwPrintd4("SBC%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 7: UnwPrintd4("RSC%s r%d,r%d,", S ? "S" : "", rd, rn); break; + case 8: UnwPrintd3("TST%s r%d,", S ? "S" : "", rn); break; + case 9: UnwPrintd3("TEQ%s r%d,", S ? "S" : "", rn); break; + case 10: UnwPrintd3("CMP%s r%d,", S ? "S" : "", rn); break; + case 11: UnwPrintd3("CMN%s r%d,", S ? "S" : "", rn); break; + case 12: UnwPrintd3("ORR%s r%d,", S ? "S" : "", rn); break; + case 13: UnwPrintd3("MOV%s r%d,", S ? "S" : "", rd); break; + case 14: UnwPrintd4("BIC%s r%d,r%d", S ? "S" : "", rd, rn); break; + case 15: UnwPrintd3("MVN%s r%d,", S ? "S" : "", rd); break; + } + + /* Decode operand 2 */ + if (I) { + uint8_t shiftDist = (operand2 & 0x0F00) >> 8; + uint8_t shiftConst = (operand2 & 0x00FF); + + /* rotate const right by 2 * shiftDist */ + shiftDist *= 2; + op2val = (shiftConst >> shiftDist) | + (shiftConst << (32 - shiftDist)); + op2origin = REG_VAL_FROM_CONST; + + UnwPrintd2("#0x%x", op2val); + } + else { + + /* Register and shift */ + uint8_t rm = (operand2 & 0x000F); + uint8_t regShift = !!(operand2 & 0x0010); + uint8_t shiftType = (operand2 & 0x0060) >> 5; + uint32_t shiftDist; + #ifdef UNW_DEBUG + const char * const shiftMnu[4] = { "LSL", "LSR", "ASR", "ROR" }; + #endif + UnwPrintd2("r%d ", rm); + + /* Get the shift distance */ + if (regShift) { + uint8_t rs = (operand2 & 0x0F00) >> 8; + + if (operand2 & 0x00800) { + UnwPrintd1("\nError: Bit should be zero\n"); + return UNWIND_ILLEGAL_INSTR; + } + else if (rs == 15) { + UnwPrintd1("\nError: Cannot use R15 with register shift\n"); + return UNWIND_ILLEGAL_INSTR; + } + + /* Get shift distance */ + shiftDist = state->regData[rs].v; + op2origin = state->regData[rs].o; + + UnwPrintd7("%s r%d\t; r%d %s r%d %s", shiftMnu[shiftType], rs, rm, M_Origin2Str(state->regData[rm].o), rs, M_Origin2Str(state->regData[rs].o)); + } + else { + shiftDist = (operand2 & 0x0F80) >> 7; + op2origin = REG_VAL_FROM_CONST; + if (shiftDist) UnwPrintd3("%s #%d", shiftMnu[shiftType], shiftDist); + UnwPrintd3("\t; r%d %s", rm, M_Origin2Str(state->regData[rm].o)); + } + + /* Apply the shift type to the source register */ + switch (shiftType) { + case 0: /* logical left */ + op2val = state->regData[rm].v << shiftDist; + break; + + case 1: /* logical right */ + if (!regShift && shiftDist == 0) shiftDist = 32; + op2val = state->regData[rm].v >> shiftDist; + break; + + case 2: /* arithmetic right */ + if (!regShift && shiftDist == 0) shiftDist = 32; + + if (state->regData[rm].v & 0x80000000) { + /* Register shifts maybe greater than 32 */ + if (shiftDist >= 32) + op2val = 0xFFFFFFFF; + else + op2val = (state->regData[rm].v >> shiftDist) | (0xFFFFFFFF << (32 - shiftDist)); + } + else + op2val = state->regData[rm].v >> shiftDist; + break; + + case 3: /* rotate right */ + + if (!regShift && shiftDist == 0) { + /* Rotate right with extend. + * This uses the carry bit and so always has an + * untracked result. + */ + op2origin = REG_VAL_INVALID; + op2val = 0; + } + else { + /* Limit shift distance to 0-31 incase of register shift */ + shiftDist &= 0x1F; + + op2val = (state->regData[rm].v >> shiftDist) | + (state->regData[rm].v << (32 - shiftDist)); + } + break; + + default: + UnwPrintd2("\nError: Invalid shift type: %d\n", shiftType); + return UNWIND_FAILURE; + } + + /* Decide the data origin */ + if (M_IsOriginValid(op2origin) && M_IsOriginValid(state->regData[rm].o)) + op2origin = REG_VAL_ARITHMETIC | state->regData[rm].o; + else + op2origin = REG_VAL_INVALID; + } + + /* Propagate register validity */ + switch (opcode) { + case 0: /* AND: Rd := Op1 AND Op2 */ + case 1: /* EOR: Rd := Op1 EOR Op2 */ + case 2: /* SUB: Rd:= Op1 - Op2 */ + case 3: /* RSB: Rd:= Op2 - Op1 */ + case 4: /* ADD: Rd:= Op1 + Op2 */ + case 12: /* ORR: Rd:= Op1 OR Op2 */ + case 14: /* BIC: Rd:= Op1 AND NOT Op2 */ + if (!M_IsOriginValid(state->regData[rn].o) || + !M_IsOriginValid(op2origin)) { + state->regData[rd].o = REG_VAL_INVALID; + } + else { + state->regData[rd].o = state->regData[rn].o; + state->regData[rd].o = (RegValOrigin)(state->regData[rd].o | op2origin); + } + break; + + case 5: /* ADC: Rd:= Op1 + Op2 + C */ + case 6: /* SBC: Rd:= Op1 - Op2 + C */ + case 7: /* RSC: Rd:= Op2 - Op1 + C */ + /* CPSR is not tracked */ + state->regData[rd].o = REG_VAL_INVALID; + break; + + case 8: /* TST: set condition codes on Op1 AND Op2 */ + case 9: /* TEQ: set condition codes on Op1 EOR Op2 */ + case 10: /* CMP: set condition codes on Op1 - Op2 */ + case 11: /* CMN: set condition codes on Op1 + Op2 */ + break; + + case 13: /* MOV: Rd:= Op2 */ + case 15: /* MVN: Rd:= NOT Op2 */ + state->regData[rd].o = (RegValOrigin) op2origin; + break; + } + + /* Account for pre-fetch by temporarily adjusting PC */ + if (rn == 15) { + + /* If the shift amount is specified in the instruction, + * the PC will be 8 bytes ahead. If a register is used + * to specify the shift amount the PC will be 12 bytes + * ahead. + */ + state->regData[rn].v += ((!I && (operand2 & 0x0010)) ? 12 : 8); + } + + /* Compute values */ + switch (opcode) { + case 0: /* AND: Rd := Op1 AND Op2 */ + state->regData[rd].v = state->regData[rn].v & op2val; + break; + + case 1: /* EOR: Rd := Op1 EOR Op2 */ + state->regData[rd].v = state->regData[rn].v ^ op2val; + break; + + case 2: /* SUB: Rd:= Op1 - Op2 */ + state->regData[rd].v = state->regData[rn].v - op2val; + break; + case 3: /* RSB: Rd:= Op2 - Op1 */ + state->regData[rd].v = op2val - state->regData[rn].v; + break; + + case 4: /* ADD: Rd:= Op1 + Op2 */ + state->regData[rd].v = state->regData[rn].v + op2val; + break; + + case 5: /* ADC: Rd:= Op1 + Op2 + C */ + case 6: /* SBC: Rd:= Op1 - Op2 + C */ + case 7: /* RSC: Rd:= Op2 - Op1 + C */ + case 8: /* TST: set condition codes on Op1 AND Op2 */ + case 9: /* TEQ: set condition codes on Op1 EOR Op2 */ + case 10: /* CMP: set condition codes on Op1 - Op2 */ + case 11: /* CMN: set condition codes on Op1 + Op2 */ + UnwPrintd1("\t; ????"); + break; + + case 12: /* ORR: Rd:= Op1 OR Op2 */ + state->regData[rd].v = state->regData[rn].v | op2val; + break; + + case 13: /* MOV: Rd:= Op2 */ + state->regData[rd].v = op2val; + break; + + case 14: /* BIC: Rd:= Op1 AND NOT Op2 */ + state->regData[rd].v = state->regData[rn].v & (~op2val); + break; + + case 15: /* MVN: Rd:= NOT Op2 */ + state->regData[rd].v = ~op2val; + break; + } + + /* Remove the prefetch offset from the PC */ + if (rd != 15 && rn == 15) + state->regData[rn].v -= ((!I && (operand2 & 0x0010)) ? 12 : 8); + } + + /* Block Data Transfer + * LDM, STM + */ + else if ((instr & 0xFE000000) == 0xE8000000) { + + bool P = !!(instr & 0x01000000), + U = !!(instr & 0x00800000), + S = !!(instr & 0x00400000), + W = !!(instr & 0x00200000), + L = !!(instr & 0x00100000); + uint16_t baseReg = (instr & 0x000F0000) >> 16; + uint16_t regList = (instr & 0x0000FFFF); + uint32_t addr = state->regData[baseReg].v; + bool addrValid = M_IsOriginValid(state->regData[baseReg].o); + int8_t r; + + #ifdef UNW_DEBUG + /* Display the instruction */ + if (L) + UnwPrintd6("LDM%c%c r%d%s, {reglist}%s\n", P ? 'E' : 'F', U ? 'D' : 'A', baseReg, W ? "!" : "", S ? "^" : ""); + else + UnwPrintd6("STM%c%c r%d%s, {reglist}%s\n", !P ? 'E' : 'F', !U ? 'D' : 'A', baseReg, W ? "!" : "", S ? "^" : ""); + #endif + + /* S indicates that banked registers (untracked) are used, unless + * this is a load including the PC when the S-bit indicates that + * that CPSR is loaded from SPSR (also untracked, but ignored). + */ + if (S && (!L || (regList & (0x01 << 15)) == 0)) { + UnwPrintd1("\nError:S-bit set requiring banked registers\n"); + return UNWIND_FAILURE; + } + else if (baseReg == 15) { + UnwPrintd1("\nError: r15 used as base register\n"); + return UNWIND_FAILURE; + } + else if (regList == 0) { + UnwPrintd1("\nError: Register list empty\n"); + return UNWIND_FAILURE; + } + + /* Check if ascending or descending. + * Registers are loaded/stored in order of address. + * i.e. r0 is at the lowest address, r15 at the highest. + */ + r = U ? 0 : 15; + do { + + /* Check if the register is to be transferred */ + if (regList & (0x01 << r)) { + + if (P) addr += U ? 4 : -4; + + if (L) { + + if (addrValid) { + + if (!UnwMemReadRegister(state, addr, &state->regData[r])) + return UNWIND_DREAD_W_FAIL; + + /* Update the origin if read via the stack pointer */ + if (M_IsOriginValid(state->regData[r].o) && baseReg == 13) + state->regData[r].o = REG_VAL_FROM_STACK; + + UnwPrintd5(" R%d = 0x%08x\t; r%d %s\n",r,state->regData[r].v,r, M_Origin2Str(state->regData[r].o)); + } + else { + /* Invalidate the register as the base reg was invalid */ + state->regData[r].o = REG_VAL_INVALID; + UnwPrintd2(" R%d = ???\n", r); + } + } + else { + if (addrValid && !UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) + return UNWIND_DWRITE_W_FAIL; + + UnwPrintd2(" R%d = 0x%08x\n", r); + } + + if (!P) addr += U ? 4 : -4; + } + + /* Check the next register */ + r += U ? 1 : -1; + + } while (r >= 0 && r <= 15); + + /* Check the writeback bit */ + if (W) state->regData[baseReg].v = addr; + + /* Check if the PC was loaded */ + if (L && (regList & (0x01 << 15))) { + if (!M_IsOriginValid(state->regData[15].o)) { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + else { + /* Store the return address */ + if (!UnwReportRetAddr(state, state->regData[15].v)) + return UNWIND_TRUNCATED; + + UnwPrintd2(" Return PC=0x%x", state->regData[15].v); + + /* Determine the return mode */ + if (state->regData[15].v & 0x1) { + /* Branching to THUMB */ + return UnwStartThumb(state); + } + else { + /* Branch to ARM */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 4; + } + } + } + } + else { + UnwPrintd1("????"); + + /* Unknown/undecoded. May alter some register, so invalidate file */ + UnwInvalidateRegisterFile(state->regData); + } + + UnwPrintd1("\n"); + + /* Should never hit the reset vector */ + if (state->regData[15].v == 0) return UNWIND_RESET; + + /* Check next address */ + state->regData[15].v += 4; + + /* Garbage collect the memory hash (used only for the stack) */ + UnwMemHashGC(state); + + if (--t == 0) return UNWIND_EXHAUSTED; + + } + + return UNWIND_UNSUPPORTED; +} + +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/backtrace/unwarm_thumb.cpp b/Marlin/src/HAL/shared/backtrace/unwarm_thumb.cpp new file mode 100644 index 0000000..0c6a706 --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarm_thumb.cpp @@ -0,0 +1,1066 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Abstract interpretation for Thumb mode. + **************************************************************************/ + +#if defined(__arm__) || defined(__thumb__) + +#define MODULE_NAME "UNWARM_THUMB" + +#include <stdio.h> +#include "unwarm.h" + +/** Sign extend an 11 bit value. + * This function simply inspects bit 11 of the input \a value, and if + * set, the top 5 bits are set to give a 2's compliment signed value. + * \param value The value to sign extend. + * \return The signed-11 bit value stored in a 16bit data type. + */ +static int32_t signExtend11(const uint16_t value) { + return (value & 0x400) ? value | 0xFFFFF800 : value; +} + +UnwResult UnwStartThumb(UnwState * const state) { + uint16_t t = UNW_MAX_INSTR_COUNT; + uint32_t lastJumpAddr = 0; // Last JUMP address, to try to detect infinite loops + bool loopDetected = false; // If a loop was detected + + for (;;) { + uint16_t instr; + + /* Attempt to read the instruction */ + if (!state->cb->readH(state->regData[15].v & (~0x1), &instr)) + return UNWIND_IREAD_H_FAIL; + + UnwPrintd4("T %x %x %04x:", state->regData[13].v, state->regData[15].v, instr); + + /* Check that the PC is still on Thumb alignment */ + if (!(state->regData[15].v & 0x1)) { + UnwPrintd1("\nError: PC misalignment\n"); + return UNWIND_INCONSISTENT; + } + + /* Check that the SP and PC have not been invalidated */ + if (!M_IsOriginValid(state->regData[13].o) || !M_IsOriginValid(state->regData[15].o)) { + UnwPrintd1("\nError: PC or SP invalidated\n"); + return UNWIND_INCONSISTENT; + } + + /* + * Detect 32bit thumb instructions + */ + if ((instr & 0xE000) == 0xE000 && (instr & 0x1800) != 0) { + uint16_t instr2; + + /* Check next address */ + state->regData[15].v += 2; + + /* Attempt to read the 2nd part of the instruction */ + if (!state->cb->readH(state->regData[15].v & (~0x1), &instr2)) + return UNWIND_IREAD_H_FAIL; + + UnwPrintd3(" %x %04x:", state->regData[15].v, instr2); + + /* + * Load/Store multiple: Only interpret + * PUSH and POP + */ + if ((instr & 0xFE6F) == 0xE82D) { + bool L = !!(instr & 0x10); + uint16_t rList = instr2; + + if (L) { + uint8_t r; + + /* Load from memory: POP */ + UnwPrintd1("POP {Rlist}\n"); + + /* Load registers from stack */ + for (r = 0; r < 16; r++) { + if (rList & (0x1 << r)) { + + /* Read the word */ + if (!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) + return UNWIND_DREAD_W_FAIL; + + /* Alter the origin to be from the stack if it was valid */ + if (M_IsOriginValid(state->regData[r].o)) { + + state->regData[r].o = REG_VAL_FROM_STACK; + + /* If restoring the PC */ + if (r == 15) { + + /* The bottom bit should have been set to indicate that + * the caller was from Thumb. This would allow return + * by BX for interworking APCS. + */ + if ((state->regData[15].v & 0x1) == 0) { + UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", state->regData[15].v); + + /* Pop into the PC will not switch mode */ + return UNWIND_INCONSISTENT; + } + + /* Store the return address */ + if (!UnwReportRetAddr(state, state->regData[15].v)) + return UNWIND_TRUNCATED; + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v); + + /* Compensate for the auto-increment, which isn't needed here */ + state->regData[15].v -= 2; + + } + + } else { + + if (r == 15) { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + } + + state->regData[13].v += 4; + + UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); + } + } + } + else { + int8_t r; + + /* Store to memory: PUSH */ + UnwPrintd1("PUSH {Rlist}"); + + for (r = 15; r >= 0; r--) { + if (rList & (0x1 << r)) { + UnwPrintd4("\n r%d = 0x%08x\t; %s", r, state->regData[r].v, M_Origin2Str(state->regData[r].o)); + + state->regData[13].v -= 4; + + if (!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) + return UNWIND_DWRITE_W_FAIL; + } + } + } + } + /* + * PUSH register + */ + else if (instr == 0xF84D && (instr2 & 0x0FFF) == 0x0D04) { + uint8_t r = instr2 >> 12; + + /* Store to memory: PUSH */ + UnwPrintd2("PUSH {R%d}\n", r); + UnwPrintd4("\n r%d = 0x%08x\t; %s", r, state->regData[r].v, M_Origin2Str(state->regData[r].o)); + + state->regData[13].v -= 4; + + if (!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) + return UNWIND_DWRITE_W_FAIL; + } + /* + * POP register + */ + else if (instr == 0xF85D && (instr2 & 0x0FFF) == 0x0B04) { + uint8_t r = instr2 >> 12; + + /* Load from memory: POP */ + UnwPrintd2("POP {R%d}\n", r); + + /* Read the word */ + if (!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) + return UNWIND_DREAD_W_FAIL; + + /* Alter the origin to be from the stack if it was valid */ + if (M_IsOriginValid(state->regData[r].o)) { + + state->regData[r].o = REG_VAL_FROM_STACK; + + /* If restoring the PC */ + if (r == 15) { + + /* The bottom bit should have been set to indicate that + * the caller was from Thumb. This would allow return + * by BX for interworking APCS. + */ + if ((state->regData[15].v & 0x1) == 0) { + UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", state->regData[15].v); + + /* Pop into the PC will not switch mode */ + return UNWIND_INCONSISTENT; + } + + /* Store the return address */ + if (!UnwReportRetAddr(state, state->regData[15].v)) + return UNWIND_TRUNCATED; + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v); + + /* Compensate for the auto-increment, which isn't needed here */ + state->regData[15].v -= 2; + + } + + } else { + + if (r == 15) { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + } + + state->regData[13].v += 4; + + UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); + } + /* + * TBB / TBH + */ + else if ((instr & 0xFFF0) == 0xE8D0 && (instr2 & 0xFFE0) == 0xF000) { + /* We are only interested in + * the forms + * TBB [PC, ...] + * TBH [PC, ..., LSL #1] + * as those are used by the C compiler to implement + * the switch clauses + */ + uint8_t rn = instr & 0xF; + bool H = !!(instr2 & 0x10); + + UnwPrintd5("TB%c [r%d,r%d%s]\n", H ? 'H' : 'B', rn, (instr2 & 0xF), H ? ",LSL #1" : ""); + + // We are only interested if the RN is the PC. Let's choose the 1st destination + if (rn == 15) { + if (H) { + uint16_t rv; + if (!state->cb->readH((state->regData[15].v & (~1)) + 2, &rv)) + return UNWIND_DREAD_H_FAIL; + state->regData[15].v += rv * 2; + } + else { + uint8_t rv; + if (!state->cb->readB((state->regData[15].v & (~1)) + 2, &rv)) + return UNWIND_DREAD_B_FAIL; + state->regData[15].v += rv * 2; + } + } + } + /* + * Unconditional branch + */ + else if ((instr & 0xF800) == 0xF000 && (instr2 & 0xD000) == 0x9000) { + uint32_t v; + + uint8_t S = (instr & 0x400) >> 10; + uint16_t imm10 = (instr & 0x3FF); + uint8_t J1 = (instr2 & 0x2000) >> 13; + uint8_t J2 = (instr2 & 0x0800) >> 11; + uint16_t imm11 = (instr2 & 0x7FF); + + uint8_t I1 = J1 ^ S ^ 1; + uint8_t I2 = J2 ^ S ^ 1; + uint32_t imm32 = (S << 24) | (I1 << 23) | (I2 << 22) |(imm10 << 12) | (imm11 << 1); + if (S) imm32 |= 0xFE000000; + + UnwPrintd2("B %d \n", imm32); + + /* Update PC */ + state->regData[15].v += imm32; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Compute the jump address */ + v = state->regData[15].v + 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", v); + + /* Did we detect an infinite loop ? */ + loopDetected = lastJumpAddr == v; + + /* Remember the last address we jumped to */ + lastJumpAddr = v; + } + + /* + * Branch with link + */ + else if ((instr & 0xF800) == 0xF000 && (instr2 & 0xD000) == 0xD000) { + + uint8_t S = (instr & 0x400) >> 10; + uint16_t imm10 = (instr & 0x3FF); + uint8_t J1 = (instr2 & 0x2000) >> 13; + uint8_t J2 = (instr2 & 0x0800) >> 11; + uint16_t imm11 = (instr2 & 0x7FF); + + uint8_t I1 = J1 ^ S ^ 1; + uint8_t I2 = J2 ^ S ^ 1; + uint32_t imm32 = (S << 24) | (I1 << 23) | (I2 << 22) |(imm10 << 12) | (imm11 << 1); + if (S) imm32 |= 0xFE000000; + + UnwPrintd2("BL %d \n", imm32); + + /* Never taken, as we are unwinding the stack */ + if (0) { + + /* Store return address in LR register */ + state->regData[14].v = state->regData[15].v + 2; + state->regData[14].o = REG_VAL_FROM_CONST; + + /* Update PC */ + state->regData[15].v += imm32; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" Return PC=%x", state->regData[15].v); + + /* Report the return address, including mode bit */ + if (!UnwReportRetAddr(state, state->regData[15].v)) + return UNWIND_TRUNCATED; + + /* Determine the new mode */ + if (state->regData[15].v & 0x1) { + /* Branching to THUMB */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 2; + } + else { + /* Branch to ARM */ + return UnwStartArm(state); + } + } + } + + /* + * Conditional branches. Usually not taken, unless infinite loop is detected + */ + else if ((instr & 0xF800) == 0xF000 && (instr2 & 0xD000) == 0x8000) { + + uint8_t S = (instr & 0x400) >> 10; + uint16_t imm6 = (instr & 0x3F); + uint8_t J1 = (instr2 & 0x2000) >> 13; + uint8_t J2 = (instr2 & 0x0800) >> 11; + uint16_t imm11 = (instr2 & 0x7FF); + + uint8_t I1 = J1 ^ S ^ 1; + uint8_t I2 = J2 ^ S ^ 1; + uint32_t imm32 = (S << 20) | (I1 << 19) | (I2 << 18) |(imm6 << 12) | (imm11 << 1); + if (S) imm32 |= 0xFFE00000; + + UnwPrintd2("Bcond %d\n", imm32); + + /* Take the jump only if a loop is detected */ + if (loopDetected) { + + /* Update PC */ + state->regData[15].v += imm32; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", state->regData[15].v + 2); + } + } + /* + * PC-relative load + * LDR Rd,[PC, #+/-imm] + */ + else if ((instr & 0xFF7F) == 0xF85F) { + uint8_t rt = (instr2 & 0xF000) >> 12; + uint8_t imm12 = (instr2 & 0x0FFF); + bool A = !!(instr & 0x80); + uint32_t address; + + /* Compute load address, adding a word to account for prefetch */ + address = (state->regData[15].v & (~0x3)) + 4; + if (A) address += imm12; + else address -= imm12; + + UnwPrintd4("LDR r%d,[PC #%c0x%08x]", rt, A?'+':'-', address); + + if (!UnwMemReadRegister(state, address, &state->regData[rt])) + return UNWIND_DREAD_W_FAIL; + } + /* + * LDR immediate. + * We are only interested when destination is PC. + * LDR Rt,[Rn , #n] + */ + else if ((instr & 0xFFF0) == 0xF8D0) { + uint8_t rn = (instr & 0xF); + uint8_t rt = (instr2 & 0xF000) >> 12; + uint16_t imm12 = (instr2 & 0xFFF); + + /* If destination is PC and we don't know the source value, then fail */ + if (!M_IsOriginValid(state->regData[rn].o)) { + state->regData[rt].o = state->regData[rn].o; + } + else { + uint32_t address = state->regData[rn].v + imm12; + if (!UnwMemReadRegister(state, address, &state->regData[rt])) + return UNWIND_DREAD_W_FAIL; + } + } + /* + * LDR immediate + * We are only interested when destination is PC. + * LDR Rt,[Rn , #-n] + * LDR Rt,[Rn], #+/-n] + * LDR Rt,[Rn, #+/-n]! + */ + else if ((instr & 0xFFF0) == 0xF850 && (instr2 & 0x0800) == 0x0800) { + uint8_t rn = (instr & 0xF); + uint8_t rt = (instr2 & 0xF000) >> 12; + uint16_t imm8 = (instr2 & 0xFF); + bool P = !!(instr2 & 0x400); + bool U = !!(instr2 & 0x200); + bool W = !!(instr2 & 0x100); + + if (!M_IsOriginValid(state->regData[rn].o)) + state->regData[rt].o = state->regData[rn].o; + else { + uint32_t offaddress = state->regData[rn].v + (U ? imm8 + imm8 : 0), + address = P ? offaddress : state->regData[rn].v; + + if (!UnwMemReadRegister(state, address, &state->regData[rt])) + return UNWIND_DREAD_W_FAIL; + + if (W) state->regData[rn].v = offaddress; + } + } + /* + * LDR (register). + * We are interested in the form + * ldr Rt, [Rn, Rm, lsl #x] + * Where Rt is PC, Rn value is known, Rm is not known or unknown + */ + else if ((instr & 0xFFF0) == 0xF850 && (instr2 & 0x0FC0) == 0x0000) { + const uint8_t rn = (instr & 0xF), + rt = (instr2 & 0xF000) >> 12, + rm = (instr2 & 0xF), + imm2 = (instr2 & 0x30) >> 4; + + if (!M_IsOriginValid(state->regData[rn].o) || !M_IsOriginValid(state->regData[rm].o)) { + + /* If Rt is PC, and Rn is known, then do an exception and assume + Rm equals 0 => This takes the first case in a switch() */ + if (rt == 15 && M_IsOriginValid(state->regData[rn].o)) { + uint32_t address = state->regData[rn].v; + if (!UnwMemReadRegister(state, address, &state->regData[rt])) + return UNWIND_DREAD_W_FAIL; + } + else /* Propagate unknown value */ + state->regData[rt].o = state->regData[rn].o; + + } + else { + uint32_t address = state->regData[rn].v + (state->regData[rm].v << imm2); + if (!UnwMemReadRegister(state, address, &state->regData[rt])) + return UNWIND_DREAD_W_FAIL; + } + } + else { + UnwPrintd1("???? (32)"); + + /* Unknown/undecoded. May alter some register, so invalidate file */ + UnwInvalidateRegisterFile(state->regData); + } + /* End of thumb 32bit code */ + + } + /* Format 1: Move shifted register + * LSL Rd, Rs, #Offset5 + * LSR Rd, Rs, #Offset5 + * ASR Rd, Rs, #Offset5 + */ + else if ((instr & 0xE000) == 0x0000 && (instr & 0x1800) != 0x1800) { + bool signExtend; + const uint8_t op = (instr & 0x1800) >> 11, + offset5 = (instr & 0x07C0) >> 6, + rs = (instr & 0x0038) >> 3, + rd = (instr & 0x0007); + + switch (op) { + case 0: /* LSL */ + UnwPrintd6("LSL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + state->regData[rd].v = state->regData[rs].v << offset5; + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 1: /* LSR */ + UnwPrintd6("LSR r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + state->regData[rd].v = state->regData[rs].v >> offset5; + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 2: /* ASR */ + UnwPrintd6("ASL r%d, r%d, #%d\t; r%d %s", rd, rs, offset5, rs, M_Origin2Str(state->regData[rs].o)); + + signExtend = !!(state->regData[rs].v & 0x8000); + state->regData[rd].v = state->regData[rs].v >> offset5; + if (signExtend) state->regData[rd].v |= 0xFFFFFFFF << (32 - offset5); + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 2: add/subtract + * ADD Rd, Rs, Rn + * ADD Rd, Rs, #Offset3 + * SUB Rd, Rs, Rn + * SUB Rd, Rs, #Offset3 + */ + else if ((instr & 0xF800) == 0x1800) { + bool I = !!(instr & 0x0400); + bool op = !!(instr & 0x0200); + uint8_t rn = (instr & 0x01C0) >> 6; + uint8_t rs = (instr & 0x0038) >> 3; + uint8_t rd = (instr & 0x0007); + + /* Print decoding */ + UnwPrintd6("%s r%d, r%d, %c%d\t;",op ? "SUB" : "ADD",rd, rs,I ? '#' : 'r',rn); + UnwPrintd5("r%d %s, r%d %s",rd, M_Origin2Str(state->regData[rd].o),rs, M_Origin2Str(state->regData[rs].o)); + if (!I) { + + UnwPrintd3(", r%d %s", rn, M_Origin2Str(state->regData[rn].o)); + + /* Perform calculation */ + state->regData[rd].v = state->regData[rs].v + (op ? -state->regData[rn].v : state->regData[rn].v); + + /* Propagate the origin */ + if (M_IsOriginValid(state->regData[rs].o) && M_IsOriginValid(state->regData[rn].o)) { + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + else + state->regData[rd].o = REG_VAL_INVALID; + } + else { + /* Perform calculation */ + state->regData[rd].v = state->regData[rs].v + (op ? -rn : rn); + + /* Propagate the origin */ + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + } + /* Format 3: move/compare/add/subtract immediate + * MOV Rd, #Offset8 + * CMP Rd, #Offset8 + * ADD Rd, #Offset8 + * SUB Rd, #Offset8 + */ + else if ((instr & 0xE000) == 0x2000) { + + uint8_t op = (instr & 0x1800) >> 11; + uint8_t rd = (instr & 0x0700) >> 8; + uint8_t offset8 = (instr & 0x00FF); + + switch (op) { + case 0: /* MOV */ + UnwPrintd3("MOV r%d, #0x%x", rd, offset8); + state->regData[rd].v = offset8; + state->regData[rd].o = REG_VAL_FROM_CONST; + break; + + case 1: /* CMP */ + /* Irrelevant to unwinding */ + UnwPrintd1("CMP ???"); + break; + + case 2: /* ADD */ + UnwPrintd5("ADD r%d, #0x%x\t; r%d %s", rd, offset8, rd, M_Origin2Str(state->regData[rd].o)); + state->regData[rd].v += offset8; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + + case 3: /* SUB */ + UnwPrintd5("SUB r%d, #0x%d\t; r%d %s", rd, offset8, rd, M_Origin2Str(state->regData[rd].o)); + state->regData[rd].v -= offset8; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 4: ALU operations + * AND Rd, Rs + * EOR Rd, Rs + * LSL Rd, Rs + * LSR Rd, Rs + * ASR Rd, Rs + * ADC Rd, Rs + * SBC Rd, Rs + * ROR Rd, Rs + * TST Rd, Rs + * NEG Rd, Rs + * CMP Rd, Rs + * CMN Rd, Rs + * ORR Rd, Rs + * MUL Rd, Rs + * BIC Rd, Rs + * MVN Rd, Rs + */ + else if ((instr & 0xFC00) == 0x4000) { + uint8_t op = (instr & 0x03C0) >> 6; + uint8_t rs = (instr & 0x0038) >> 3; + uint8_t rd = (instr & 0x0007); + +#ifdef UNW_DEBUG + static const char * const mnu[16] = { + "AND", "EOR", "LSL", "LSR", + "ASR", "ADC", "SBC", "ROR", + "TST", "NEG", "CMP", "CMN", + "ORR", "MUL", "BIC", "MVN" }; +#endif + /* Print the mnemonic and registers */ + switch (op) { + case 0: /* AND */ + case 1: /* EOR */ + case 2: /* LSL */ + case 3: /* LSR */ + case 4: /* ASR */ + case 7: /* ROR */ + case 9: /* NEG */ + case 12: /* ORR */ + case 13: /* MUL */ + case 15: /* MVN */ + UnwPrintd8("%s r%d ,r%d\t; r%d %s, r%d %s",mnu[op],rd, rs, rd, M_Origin2Str(state->regData[rd].o), rs, M_Origin2Str(state->regData[rs].o)); + break; + + case 5: /* ADC */ + case 6: /* SBC */ + UnwPrintd4("%s r%d, r%d", mnu[op], rd, rs); + break; + + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + /* Irrelevant to unwinding */ + UnwPrintd2("%s ???", mnu[op]); + break; + + case 14: /* BIC */ + UnwPrintd5("r%d ,r%d\t; r%d %s", rd, rs, rs, M_Origin2Str(state->regData[rs].o)); + break; + } + + /* Perform operation */ + switch (op) { + case 0: /* AND */ + state->regData[rd].v &= state->regData[rs].v; + break; + + case 1: /* EOR */ + state->regData[rd].v ^= state->regData[rs].v; + break; + + case 2: /* LSL */ + state->regData[rd].v <<= state->regData[rs].v; + break; + + case 3: /* LSR */ + state->regData[rd].v >>= state->regData[rs].v; + break; + + case 4: /* ASR */ + if (state->regData[rd].v & 0x80000000) { + state->regData[rd].v >>= state->regData[rs].v; + state->regData[rd].v |= 0xFFFFFFFF << (32 - state->regData[rs].v); + } + else { + state->regData[rd].v >>= state->regData[rs].v; + } + + break; + + case 5: /* ADC */ + case 6: /* SBC */ + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + break; + + case 7: /* ROR */ + state->regData[rd].v = (state->regData[rd].v >> state->regData[rs].v) | + (state->regData[rd].v << (32 - state->regData[rs].v)); + break; + + case 9: /* NEG */ + state->regData[rd].v = -state->regData[rs].v; + break; + + case 12: /* ORR */ + state->regData[rd].v |= state->regData[rs].v; + break; + + case 13: /* MUL */ + state->regData[rd].v *= state->regData[rs].v; + break; + + case 14: /* BIC */ + state->regData[rd].v &= ~state->regData[rs].v; + break; + + case 15: /* MVN */ + state->regData[rd].v = ~state->regData[rs].v; + break; + } + + /* Propagate data origins */ + switch (op) { + case 0: /* AND */ + case 1: /* EOR */ + case 2: /* LSL */ + case 3: /* LSR */ + case 4: /* ASR */ + case 7: /* ROR */ + case 12: /* ORR */ + case 13: /* MUL */ + case 14: /* BIC */ + if (M_IsOriginValid(state->regData[rd].o) && M_IsOriginValid(state->regData[rs].o)) { + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + } + else + state->regData[rd].o = REG_VAL_INVALID; + break; + + case 5: /* ADC */ + case 6: /* SBC */ + /* C-bit not tracked */ + state->regData[rd].o = REG_VAL_INVALID; + break; + + case 8: /* TST */ + case 10: /* CMP */ + case 11: /* CMN */ + /* Nothing propagated */ + break; + + case 9: /* NEG */ + case 15: /* MVN */ + state->regData[rd].o = state->regData[rs].o; + state->regData[rd].o |= REG_VAL_ARITHMETIC; + break; + } + } + /* Format 5: Hi register operations/branch exchange + * ADD Rd, Hs + * CMP Hd, Rs + * MOV Hd, Hs + */ + else if ((instr & 0xFC00) == 0x4400) { + uint8_t op = (instr & 0x0300) >> 8; + bool h1 = (instr & 0x0080) ? true: false; + bool h2 = (instr & 0x0040) ? true: false; + uint8_t rhs = (instr & 0x0038) >> 3; + uint8_t rhd = (instr & 0x0007); + + /* Adjust the register numbers */ + if (h2) rhs += 8; + if (h1) rhd += 8; + + switch (op) { + case 0: /* ADD */ + UnwPrintd5("ADD r%d, r%d\t; r%d %s", rhd, rhs, rhs, M_Origin2Str(state->regData[rhs].o)); + state->regData[rhd].v += state->regData[rhs].v; + state->regData[rhd].o = state->regData[rhs].o; + state->regData[rhd].o |= REG_VAL_ARITHMETIC; + break; + + case 1: /* CMP */ + /* Irrelevant to unwinding */ + UnwPrintd1("CMP ???"); + break; + + case 2: /* MOV */ + UnwPrintd5("MOV r%d, r%d\t; r%d %s", rhd, rhs, rhd, M_Origin2Str(state->regData[rhs].o)); + state->regData[rhd].v = state->regData[rhs].v; + state->regData[rhd].o = state->regData[rhs].o; + break; + + case 3: /* BX */ + UnwPrintd4("BX r%d\t; r%d %s\n", rhs, rhs, M_Origin2Str(state->regData[rhs].o)); + + /* Only follow BX if the data was from the stack or BX LR */ + if (rhs == 14 || state->regData[rhs].o == REG_VAL_FROM_STACK) { + UnwPrintd2(" Return PC=0x%x\n", state->regData[rhs].v & (~0x1)); + + /* Report the return address, including mode bit */ + if (!UnwReportRetAddr(state, state->regData[rhs].v)) + return UNWIND_TRUNCATED; + + /* Update the PC */ + state->regData[15].v = state->regData[rhs].v; + + /* Determine the new mode */ + if (state->regData[rhs].v & 0x1) { + /* Branching to THUMB */ + + /* Account for the auto-increment which isn't needed */ + state->regData[15].v -= 2; + } + else /* Branch to ARM */ + return UnwStartArm(state); + } + else { + UnwPrintd4("\nError: BX to invalid register: r%d = 0x%x (%s)\n", rhs, state->regData[rhs].o, M_Origin2Str(state->regData[rhs].o)); + return UNWIND_FAILURE; + } + } + } + /* Format 9: PC-relative load + * LDR Rd,[PC, #imm] + */ + else if ((instr & 0xF800) == 0x4800) { + uint8_t rd = (instr & 0x0700) >> 8; + uint8_t word8 = (instr & 0x00FF); + uint32_t address; + + /* Compute load address, adding a word to account for prefetch */ + address = (state->regData[15].v & (~0x3)) + 4 + (word8 << 2); + + UnwPrintd3("LDR r%d, 0x%08x", rd, address); + + if (!UnwMemReadRegister(state, address, &state->regData[rd])) + return UNWIND_DREAD_W_FAIL; + } + /* Format 13: add offset to Stack Pointer + * ADD sp,#+imm + * ADD sp,#-imm + */ + else if ((instr & 0xFF00) == 0xB000) { + uint8_t value = (instr & 0x7F) * 4; + + /* Check the negative bit */ + if ((instr & 0x80) != 0) { + UnwPrintd2("SUB sp,#0x%x", value); + state->regData[13].v -= value; + } + else { + UnwPrintd2("ADD sp,#0x%x", value); + state->regData[13].v += value; + } + } + /* Format 14: push/pop registers + * PUSH {Rlist} + * PUSH {Rlist, LR} + * POP {Rlist} + * POP {Rlist, PC} + */ + else if ((instr & 0xF600) == 0xB400) { + bool L = !!(instr & 0x0800); + bool R = !!(instr & 0x0100); + uint8_t rList = (instr & 0x00FF); + + if (L) { + uint8_t r; + + /* Load from memory: POP */ + UnwPrintd2("POP {Rlist%s}\n", R ? ", PC" : ""); + + for (r = 0; r < 8; r++) { + if (rList & (0x1 << r)) { + + /* Read the word */ + if (!UnwMemReadRegister(state, state->regData[13].v, &state->regData[r])) + return UNWIND_DREAD_W_FAIL; + + /* Alter the origin to be from the stack if it was valid */ + if (M_IsOriginValid(state->regData[r].o)) + state->regData[r].o = REG_VAL_FROM_STACK; + + state->regData[13].v += 4; + + UnwPrintd3(" r%d = 0x%08x\n", r, state->regData[r].v); + } + } + + /* Check if the PC is to be popped */ + if (R) { + /* Get the return address */ + if (!UnwMemReadRegister(state, state->regData[13].v, &state->regData[15])) + return UNWIND_DREAD_W_FAIL; + + /* Alter the origin to be from the stack if it was valid */ + if (!M_IsOriginValid(state->regData[15].o)) { + /* Return address is not valid */ + UnwPrintd1("PC popped with invalid address\n"); + return UNWIND_FAILURE; + } + else { + /* The bottom bit should have been set to indicate that + * the caller was from Thumb. This would allow return + * by BX for interworking APCS. + */ + if ((state->regData[15].v & 0x1) == 0) { + UnwPrintd2("Warning: Return address not to Thumb: 0x%08x\n", state->regData[15].v); + + /* Pop into the PC will not switch mode */ + return UNWIND_INCONSISTENT; + } + + /* Store the return address */ + if (!UnwReportRetAddr(state, state->regData[15].v)) + return UNWIND_TRUNCATED; + + /* Now have the return address */ + UnwPrintd2(" Return PC=%x\n", state->regData[15].v); + + /* Update the pc */ + state->regData[13].v += 4; + + /* Compensate for the auto-increment, which isn't needed here */ + state->regData[15].v -= 2; + } + } + } + else { + int8_t r; + + /* Store to memory: PUSH */ + UnwPrintd2("PUSH {Rlist%s}", R ? ", LR" : ""); + + /* Check if the LR is to be pushed */ + if (R) { + UnwPrintd3("\n lr = 0x%08x\t; %s", state->regData[14].v, M_Origin2Str(state->regData[14].o)); + + state->regData[13].v -= 4; + + /* Write the register value to memory */ + if (!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[14])) + return UNWIND_DWRITE_W_FAIL; + } + + for (r = 7; r >= 0; r--) { + if (rList & (0x1 << r)) { + UnwPrintd4("\n r%d = 0x%08x\t; %s", r, state->regData[r].v, M_Origin2Str(state->regData[r].o)); + + state->regData[13].v -= 4; + + if (!UnwMemWriteRegister(state, state->regData[13].v, &state->regData[r])) + return UNWIND_DWRITE_W_FAIL; + } + } + } + } + + /* + * Conditional branches + * Bcond + */ + else if ((instr & 0xF000) == 0xD000) { + int32_t branchValue = (instr & 0xFF); + if (branchValue & 0x80) branchValue |= 0xFFFFFF00; + + /* Branch distance is twice that specified in the instruction. */ + branchValue *= 2; + + UnwPrintd2("Bcond %d \n", branchValue); + + /* Only take the branch if a loop was detected */ + if (loopDetected) { + + /* Update PC */ + state->regData[15].v += branchValue; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", state->regData[15].v + 2); + } + } + + /* Format 18: unconditional branch + * B label + */ + else if ((instr & 0xF800) == 0xE000) { + uint32_t v; + int32_t branchValue = signExtend11(instr & 0x07FF); + + /* Branch distance is twice that specified in the instruction. */ + branchValue *= 2; + + UnwPrintd2("B %d \n", branchValue); + + /* Update PC */ + state->regData[15].v += branchValue; + + /* Need to advance by a word to account for pre-fetch. + * Advance by a half word here, allowing the normal address + * advance to account for the other half word. + */ + state->regData[15].v += 2; + + /* Compute the jump address */ + v = state->regData[15].v + 2; + + /* Display PC of next instruction */ + UnwPrintd2(" New PC=%x", v); + + /* Did we detect an infinite loop ? */ + loopDetected = lastJumpAddr == v; + + /* Remember the last address we jumped to */ + lastJumpAddr = v; + } + else { + UnwPrintd1("????"); + + /* Unknown/undecoded. May alter some register, so invalidate file */ + UnwInvalidateRegisterFile(state->regData); + } + + UnwPrintd1("\n"); + + /* Should never hit the reset vector */ + if (state->regData[15].v == 0) return UNWIND_RESET; + + /* Check next address */ + state->regData[15].v += 2; + + /* Garbage collect the memory hash (used only for the stack) */ + UnwMemHashGC(state); + + if (--t == 0) return UNWIND_EXHAUSTED; + + } + + return UNWIND_SUCCESS; +} + +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/backtrace/unwarmbytab.cpp b/Marlin/src/HAL/shared/backtrace/unwarmbytab.cpp new file mode 100644 index 0000000..f1ee81e --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarmbytab.cpp @@ -0,0 +1,430 @@ +/* + * Libbacktrace + * Copyright 2015 Stephen Street <stephen@redrocketcomputing.com> + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://www.mozilla.org/en-US/MPL/2.0/ + * + * This library was modified, some bugs fixed, stack address validated + * and adapted to be used in Marlin 3D printer firmware as backtracer + * for exceptions for debugging purposes in 2018 by Eduardo José Tagle. + */ + +#if defined(__arm__) || defined(__thumb__) + +#include "unwarmbytab.h" + +#include <stdint.h> +#include <string.h> + +/* These symbols point to the unwind index and should be provide by the linker script */ +extern "C" const UnwTabEntry __exidx_start[]; +extern "C" const UnwTabEntry __exidx_end[]; + +/* This prevents the linking of libgcc unwinder code */ +void __aeabi_unwind_cpp_pr0() {}; +void __aeabi_unwind_cpp_pr1() {}; +void __aeabi_unwind_cpp_pr2() {}; + +static inline __attribute__((always_inline)) uint32_t prel31_to_addr(const uint32_t *prel31) { + uint32_t offset = (((uint32_t)(*prel31)) << 1) >> 1; + return ((uint32_t)prel31 + offset) & 0x7FFFFFFF; +} + +static const UnwTabEntry *UnwTabSearchIndex(const UnwTabEntry *start, const UnwTabEntry *end, uint32_t ip) { + const UnwTabEntry *middle; + + /* Perform a binary search of the unwind index */ + while (start < end - 1) { + middle = start + ((end - start + 1) >> 1); + if (ip < prel31_to_addr(&middle->addr_offset)) + end = middle; + else + start = middle; + } + return start; +} + +/* + * Get the function name or nullptr if not found + */ +static const char *UnwTabGetFunctionName(const UnwindCallbacks *cb, uint32_t address) { + uint32_t flag_word = 0; + if (!cb->readW(address-4,&flag_word)) + return nullptr; + + if ((flag_word & 0xFF000000) == 0xFF000000) { + return (const char *)(address - 4 - (flag_word & 0x00FFFFFF)); + } + return nullptr; +} + +/** + * Get the next frame unwinding instruction + * + * Return either the instruction or -1 to signal no more instructions + * are available + */ +static int UnwTabGetNextInstruction(const UnwindCallbacks *cb, UnwTabState *ucb) { + int instruction; + + /* Are there more instructions */ + if (ucb->remaining == 0) + return -1; + + /* Extract the current instruction */ + uint32_t v = 0; + if (!cb->readW(ucb->current, &v)) + return -1; + instruction = (v >> (ucb->byte << 3)) & 0xFF; + + /* Move the next byte */ + --ucb->byte; + if (ucb->byte < 0) { + ucb->current += 4; + ucb->byte = 3; + } + --ucb->remaining; + + return instruction; +} + +/** + * Initialize the frame unwinding state + */ +static UnwResult UnwTabStateInit(const UnwindCallbacks *cb, UnwTabState *ucb, uint32_t instructions, const UnwindFrame *frame) { + + /* Initialize control block */ + memset(ucb, 0, sizeof(UnwTabState)); + ucb->current = instructions; + + /* Is a short unwind description */ + uint32_t v = 0; + if (!cb->readW(instructions, &v)) + return UNWIND_DREAD_W_FAIL; + + if ((v & 0xFF000000) == 0x80000000) { + ucb->remaining = 3; + ucb->byte = 2; + /* Is a long unwind description */ + } else if ((v & 0xFF000000) == 0x81000000) { + ucb->remaining = ((v & 0x00FF0000) >> 14) + 2; + ucb->byte = 1; + } else + return UNWIND_UNSUPPORTED_DWARF_PERSONALITY; + + /* Initialize the virtual register set */ + ucb->vrs[7] = frame->fp; + ucb->vrs[13] = frame->sp; + ucb->vrs[14] = frame->lr; + ucb->vrs[15] = 0; + + /* All good */ + return UNWIND_SUCCESS; +} + +/* + * Execute unwinding instructions + */ +static UnwResult UnwTabExecuteInstructions(const UnwindCallbacks *cb, UnwTabState *ucb) { + int instruction; + uint32_t mask, reg, vsp; + + /* Consume all instruction byte */ + while ((instruction = UnwTabGetNextInstruction(cb, ucb)) != -1) { + + if ((instruction & 0xC0) == 0x00) { // ARM_EXIDX_CMD_DATA_POP + /* vsp = vsp + (xxxxxx << 2) + 4 */ + ucb->vrs[13] += ((instruction & 0x3F) << 2) + 4; + } + else if ((instruction & 0xC0) == 0x40) { // ARM_EXIDX_CMD_DATA_PUSH + /* vsp = vsp - (xxxxxx << 2) - 4 */ + ucb->vrs[13] -= ((instruction & 0x3F) << 2) - 4; + } + else if ((instruction & 0xF0) == 0x80) { + /* pop under mask {r15-r12},{r11-r4} or refuse to unwind */ + instruction = instruction << 8 | UnwTabGetNextInstruction(cb, ucb); + + /* Check for refuse to unwind */ + if (instruction == 0x8000) // ARM_EXIDX_CMD_REFUSED + return UNWIND_REFUSED; + + /* Pop registers using mask */ // ARM_EXIDX_CMD_REG_POP + vsp = ucb->vrs[13]; + mask = instruction & 0xFFF; + + reg = 4; + while (mask) { + if ((mask & 1) != 0) { + uint32_t v; + if (!cb->readW(vsp,&v)) + return UNWIND_DREAD_W_FAIL; + ucb->vrs[reg] = v; + v += 4; + } + mask >>= 1; + ++reg; + } + + /* Patch up the vrs sp if it was in the mask */ + if (instruction & (1 << (13 - 4))) + ucb->vrs[13] = vsp; + } + else if ((instruction & 0xF0) == 0x90 // ARM_EXIDX_CMD_REG_TO_SP + && instruction != 0x9D + && instruction != 0x9F + ) { + /* vsp = r[nnnn] */ + ucb->vrs[13] = ucb->vrs[instruction & 0x0F]; + } + else if ((instruction & 0xF0) == 0xA0) { // ARM_EXIDX_CMD_REG_POP + /* pop r4-r[4+nnn] or pop r4-r[4+nnn], r14*/ + vsp = ucb->vrs[13]; + + for (reg = 4; reg <= uint32_t((instruction & 0x07) + 4); ++reg) { + uint32_t v; + if (!cb->readW(vsp,&v)) + return UNWIND_DREAD_W_FAIL; + + ucb->vrs[reg] = v; + vsp += 4; + } + + if (instruction & 0x08) { // ARM_EXIDX_CMD_REG_POP + uint32_t v; + if (!cb->readW(vsp,&v)) + return UNWIND_DREAD_W_FAIL; + ucb->vrs[14] = v; + vsp += 4; + } + + ucb->vrs[13] = vsp; + + } + else if (instruction == 0xB0) { // ARM_EXIDX_CMD_FINISH + /* finished */ + if (ucb->vrs[15] == 0) + ucb->vrs[15] = ucb->vrs[14]; + + /* All done unwinding */ + return UNWIND_SUCCESS; + + } + else if (instruction == 0xB1) { // ARM_EXIDX_CMD_REG_POP + /* pop register under mask {r3,r2,r1,r0} */ + vsp = ucb->vrs[13]; + mask = UnwTabGetNextInstruction(cb, ucb); + + reg = 0; + while (mask) { + if ((mask & 1) != 0) { + uint32_t v; + if (!cb->readW(vsp,&v)) + return UNWIND_DREAD_W_FAIL; + + ucb->vrs[reg] = v; + vsp += 4; + } + mask >>= 1; + ++reg; + } + ucb->vrs[13] = (uint32_t)vsp; + + } + else if (instruction == 0xB2) { // ARM_EXIDX_CMD_DATA_POP + /* vps = vsp + 0x204 + (uleb128 << 2) */ + ucb->vrs[13] += 0x204 + (UnwTabGetNextInstruction(cb, ucb) << 2); + } + else if (instruction == 0xB3 // ARM_EXIDX_CMD_VFP_POP + || instruction == 0xC8 + || instruction == 0xC9 + ) { + /* pop VFP double-precision registers */ + vsp = ucb->vrs[13]; + + /* D[ssss]-D[ssss+cccc] */ + uint32_t v; + if (!cb->readW(vsp,&v)) + return UNWIND_DREAD_W_FAIL; + + ucb->vrs[14] = v; + vsp += 4; + + if (instruction == 0xC8) { + /* D[16+sssss]-D[16+ssss+cccc] */ + ucb->vrs[14] |= 1 << 16; + } + + if (instruction != 0xB3) { + /* D[sssss]-D[ssss+cccc] */ + ucb->vrs[14] |= 1 << 17; + } + + ucb->vrs[13] = vsp; + } + else if ((instruction & 0xF8) == 0xB8 || (instruction & 0xF8) == 0xD0) { + /* Pop VFP double precision registers D[8]-D[8+nnn] */ + ucb->vrs[14] = 0x80 | (instruction & 0x07); + if ((instruction & 0xF8) == 0xD0) + ucb->vrs[14] = 1 << 17; + } + else + return UNWIND_UNSUPPORTED_DWARF_INSTR; + } + return UNWIND_SUCCESS; +} + +static inline __attribute__((always_inline)) uint32_t read_psp() { + /* Read the current PSP and return its value as a pointer */ + uint32_t psp; + + __asm__ volatile ( + " mrs %0, psp \n" + : "=r" (psp) : : + ); + + return psp; +} + +/* + * Unwind the specified frame and goto the previous one + */ +static UnwResult UnwTabUnwindFrame(const UnwindCallbacks *cb, UnwindFrame *frame) { + + UnwResult err; + UnwTabState ucb; + const UnwTabEntry *index; + uint32_t instructions; + + /* Search the unwind index for the matching unwind table */ + index = UnwTabSearchIndex(__exidx_start, __exidx_end, frame->pc); + + /* Make sure we can unwind this frame */ + if (index->insn == 0x00000001) + return UNWIND_SUCCESS; + + /* Get the pointer to the first unwind instruction */ + if (index->insn & 0x80000000) + instructions = (uint32_t)&index->insn; + else + instructions = prel31_to_addr(&index->insn); + + /* Initialize the unwind control block */ + if ((err = UnwTabStateInit(cb, &ucb, instructions, frame)) < 0) + return err; + + /* Execute the unwind instructions */ + err = UnwTabExecuteInstructions(cb, &ucb); + if (err < 0) + return err; + + /* Set the virtual pc to the virtual lr if this is the first unwind */ + if (ucb.vrs[15] == 0) + ucb.vrs[15] = ucb.vrs[14]; + + /* Check for exception return */ + /* TODO Test with other ARM processors to verify this method. */ + if ((ucb.vrs[15] & 0xF0000000) == 0xF0000000) { + /* According to the Cortex Programming Manual (p.44), the stack address is always 8-byte aligned (Cortex-M7). + Depending on where the exception came from (MSP or PSP), we need the right SP value to work with. + + ucb.vrs[7] contains the right value, so take it and align it by 8 bytes, store it as the current + SP to work with (ucb.vrs[13]) which is then saved as the current (virtual) frame's SP. + */ + uint32_t stack; + ucb.vrs[13] = (ucb.vrs[7] & ~7); + + /* If we need to start from the MSP, we need to go down X words to find the PC, where: + X=2 if it was a non-floating-point exception + X=20 if it was a floating-point (VFP) exception + + If we need to start from the PSP, we need to go up exactly 6 words to find the PC. + See the ARMv7-M Architecture Reference Manual p.594 and Cortex-M7 Processor Programming Manual p.44/p.45 for details. + */ + if ((ucb.vrs[15] & 0xC) == 0) { + /* Return to Handler Mode: MSP (0xFFFFFF-1) */ + stack = ucb.vrs[13]; + + /* The PC is always 2 words down from the MSP, if it was a non-floating-point exception */ + stack -= 2*4; + + /* If there was a VFP exception (0xFFFFFFE1), the PC is located another 18 words down */ + if ((ucb.vrs[15] & 0xF0) == 0xE0) { + stack -= 18*4; + } + } + else { + /* Return to Thread Mode: PSP (0xFFFFFF-d) */ + stack = read_psp(); + + /* The PC is always 6 words up from the PSP */ + stack += 6*4; + } + + /* Store the PC */ + uint32_t v; + if (!cb->readW(stack,&v)) + return UNWIND_DREAD_W_FAIL; + ucb.vrs[15] = v; + stack -= 4; + + /* Store the LR */ + if (!cb->readW(stack,&v)) + return UNWIND_DREAD_W_FAIL; + ucb.vrs[14] = v; + stack -= 4; + } + + /* We are done if current frame pc is equal to the virtual pc, prevent infinite loop */ + if (frame->pc == ucb.vrs[15]) + return UNWIND_SUCCESS; + + /* Update the frame */ + frame->fp = ucb.vrs[7]; + frame->sp = ucb.vrs[13]; + frame->lr = ucb.vrs[14]; + frame->pc = ucb.vrs[15]; + + /* All good - Continue unwinding */ + return UNWIND_MORE_AVAILABLE; +} + +UnwResult UnwindByTableStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data) { + + UnwResult err = UNWIND_SUCCESS; + UnwReport entry; + + /* Use DWARF unwind information to unwind frames */ + do { + if (frame->pc == 0) { + /* Reached __exidx_end. */ + break; + } + + if (frame->pc == 0x00000001) { + /* Reached .cantunwind instruction. */ + break; + } + + /* Find the unwind index of the current frame pc */ + const UnwTabEntry *index = UnwTabSearchIndex(__exidx_start, __exidx_end, frame->pc); + + /* Clear last bit (Thumb indicator) */ + frame->pc &= 0xFFFFFFFEU; + + /* Generate the backtrace information */ + entry.address = frame->pc; + entry.function = prel31_to_addr(&index->addr_offset); + entry.name = UnwTabGetFunctionName(cb, entry.function); + if (!cb->report(data,&entry)) + break; + + /* Unwind frame and repeat */ + } while ((err = UnwTabUnwindFrame(cb, frame)) == UNWIND_MORE_AVAILABLE); + + /* All done */ + return err; +} + +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/backtrace/unwarmbytab.h b/Marlin/src/HAL/shared/backtrace/unwarmbytab.h new file mode 100644 index 0000000..e2f80db --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarmbytab.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Interface to the memory tracking sub-system. + **************************************************************************/ + +#pragma once + +#include "unwarm.h" + +typedef struct { + uint32_t vrs[16]; + uint32_t current; /* Address of current byte */ + int remaining; + int byte; +} UnwTabState; + +typedef struct { + uint32_t addr_offset; + uint32_t insn; +} UnwTabEntry; + +UnwResult UnwindByTableStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data); diff --git a/Marlin/src/HAL/shared/backtrace/unwarmmem.cpp b/Marlin/src/HAL/shared/backtrace/unwarmmem.cpp new file mode 100644 index 0000000..a40d854 --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarmmem.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Implementation of the memory tracking sub-system. + **************************************************************************/ + +#if defined(__arm__) || defined(__thumb__) +#define MODULE_NAME "UNWARMMEM" + +#include <stdio.h> +#include "unwarmmem.h" +#include "unwarm.h" + +#define M_IsIdxUsed(a, v) !!((a)[v >> 3] & (1 << (v & 0x7))) +#define M_SetIdxUsed(a, v) ((a)[v >> 3] |= (1 << (v & 0x7))) +#define M_ClrIdxUsed(a, v) ((a)[v >> 3] &= ~(1 << (v & 0x7))) + +/** Search the memory hash to see if an entry is stored in the hash already. + * This will search the hash and either return the index where the item is + * stored, or -1 if the item was not found. + */ +static int16_t memHashIndex(MemData * const memData, const uint32_t addr) { + + const uint16_t v = addr % MEM_HASH_SIZE; + uint16_t s = v; + + do { + /* Check if the element is occupied */ + if (M_IsIdxUsed(memData->used, s)) { + /* Check if it is occupied with the sought data */ + if (memData->a[s] == addr) return s; + } + else { + /* Item is free, this is where the item should be stored */ + return s; + } + + /* Search the next entry */ + s++; + if (s > MEM_HASH_SIZE) s = 0; + } while (s != v); + + /* Search failed, hash is full and the address not stored */ + return -1; +} + +bool UnwMemHashRead(MemData * const memData, uint32_t addr,uint32_t * const data, bool * const tracked) { + + const int16_t i = memHashIndex(memData, addr); + + if (i >= 0 && M_IsIdxUsed(memData->used, i) && memData->a[i] == addr) { + *data = memData->v[i]; + *tracked = M_IsIdxUsed(memData->tracked, i); + return true; + } + else { + /* Address not found in the hash */ + return false; + } +} + +bool UnwMemHashWrite(MemData * const memData, uint32_t addr, uint32_t val, bool valValid) { + const int16_t i = memHashIndex(memData, addr); + if (i < 0) return false; /* Hash full */ + + /* Store the item */ + memData->a[i] = addr; + M_SetIdxUsed(memData->used, i); + + if (valValid) { + memData->v[i] = val; + M_SetIdxUsed(memData->tracked, i); + } + else { + #ifdef UNW_DEBUG + memData->v[i] = 0xDEADBEEF; + #endif + M_ClrIdxUsed(memData->tracked, i); + } + + return true; +} + +void UnwMemHashGC(UnwState * const state) { + + const uint32_t minValidAddr = state->regData[13].v; + MemData * const memData = &state->memData; + uint16_t t; + + for (t = 0; t < MEM_HASH_SIZE; t++) { + if (M_IsIdxUsed(memData->used, t) && (memData->a[t] < minValidAddr)) { + UnwPrintd3("MemHashGC: Free elem %d, addr 0x%08x\n", t, memData->a[t]); + M_ClrIdxUsed(memData->used, t); + } + } +} + +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/backtrace/unwarmmem.h b/Marlin/src/HAL/shared/backtrace/unwarmmem.h new file mode 100644 index 0000000..1340bbd --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwarmmem.h @@ -0,0 +1,21 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Interface to the memory tracking sub-system. + **************************************************************************/ + +#pragma once + +#include "unwarm.h" + +bool UnwMemHashRead(MemData * const memData, uint32_t addr, uint32_t * const data, bool * const tracked); +bool UnwMemHashWrite(MemData * const memData, uint32_t addr, uint32_t val, bool valValid); +void UnwMemHashGC(UnwState * const state); diff --git a/Marlin/src/HAL/shared/backtrace/unwinder.cpp b/Marlin/src/HAL/shared/backtrace/unwinder.cpp new file mode 100644 index 0000000..0f88e2a --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwinder.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Implementation of the interface into the ARM unwinder. + **************************************************************************/ + +#if defined(__arm__) || defined(__thumb__) + +#define MODULE_NAME "UNWINDER" + +#include <stdio.h> +#include <string.h> +#include "unwinder.h" +#include "unwarm.h" +#include "unwarmbytab.h" + +/* These symbols point to the unwind index and should be provide by the linker script */ +extern "C" const UnwTabEntry __exidx_start[]; +extern "C" const UnwTabEntry __exidx_end[]; + +// Detect if unwind information is present or not +static int HasUnwindTableInfo() { + // > 16 because there are default entries we can't supress + return ((char*)(&__exidx_end) - (char*)(&__exidx_start)) > 16 ? 1 : 0; +} + +UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data) { + if (HasUnwindTableInfo()) { + /* We have unwind information tables */ + return UnwindByTableStart(frame, cb, data); + } + else { + + /* We don't have unwind information tables */ + UnwState state; + + /* Initialize the unwinding state */ + UnwInitState(&state, cb, data, frame->pc, frame->sp); + + /* Check the Thumb bit */ + return (frame->pc & 0x1) ? UnwStartThumb(&state) : UnwStartArm(&state); + } +} +#endif diff --git a/Marlin/src/HAL/shared/backtrace/unwinder.h b/Marlin/src/HAL/shared/backtrace/unwinder.h new file mode 100644 index 0000000..157808d --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwinder.h @@ -0,0 +1,172 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + **************************************************************************/ +/** \file + * Interface to the ARM stack unwinding module. + **************************************************************************/ + +#pragma once + +#include <stdint.h> + +/** \def UNW_DEBUG + * If this define is set, additional information will be produced while + * unwinding the stack to allow debug of the unwind module itself. + */ +/* #define UNW_DEBUG 1 */ + +/*************************************************************************** + * Type Definitions + **************************************************************************/ + +/** Possible results for UnwindStart to return. + */ +typedef enum { + /** Unwinding was successful and complete. */ + UNWIND_SUCCESS = 0, + + /** Not an error: More frames are available. */ + UNWIND_MORE_AVAILABLE = 1, + + /** Unsupported DWARF unwind personality. */ + UNWIND_UNSUPPORTED_DWARF_PERSONALITY = -1, + + /** Refused to perform unwind. */ + UNWIND_REFUSED = -2, + + /** Reached an invalid SP. */ + UNWIND_INVALID_SP = -3, + + /** Reached an invalid PC */ + UNWIND_INVALID_PC = -4, + + /** Unsupported DWARF instruction */ + UNWIND_UNSUPPORTED_DWARF_INSTR = -5, + + /** More than UNW_MAX_INSTR_COUNT instructions were interpreted. */ + UNWIND_EXHAUSTED = -6, + + /** Unwinding stopped because the reporting func returned false. */ + UNWIND_TRUNCATED = -7, + + /** Read data was found to be inconsistent. */ + UNWIND_INCONSISTENT = -8, + + /** Unsupported instruction or data found. */ + UNWIND_UNSUPPORTED = -9, + + /** General failure. */ + UNWIND_FAILURE = -10, + + /** Illegal instruction. */ + UNWIND_ILLEGAL_INSTR = -11, + + /** Unwinding hit the reset vector. */ + UNWIND_RESET = -12, + + /** Failed read for an instruction word. */ + UNWIND_IREAD_W_FAIL = -13, + + /** Failed read for an instruction half-word. */ + UNWIND_IREAD_H_FAIL = -14, + + /** Failed read for an instruction byte. */ + UNWIND_IREAD_B_FAIL = -15, + + /** Failed read for a data word. */ + UNWIND_DREAD_W_FAIL = -16, + + /** Failed read for a data half-word. */ + UNWIND_DREAD_H_FAIL = -17, + + /** Failed read for a data byte. */ + UNWIND_DREAD_B_FAIL = -18, + + /** Failed write for a data word. */ + UNWIND_DWRITE_W_FAIL = -19 + +} UnwResult; + +/** A backtrace report */ +typedef struct { + uint32_t function; /** Starts address of function */ + const char *name; /** Function name, or null if not available */ + uint32_t address; /** PC on that function */ +} UnwReport; + +/** Type for function pointer for result callback. + * The function is passed two parameters, the first is a void * pointer, + * and the second is the return address of the function. The bottom bit + * of the passed address indicates the execution mode; if it is set, + * the execution mode at the return address is Thumb, otherwise it is + * ARM. + * + * The return value of this function determines whether unwinding should + * continue or not. If true is returned, unwinding will continue and the + * report function maybe called again in future. If false is returned, + * unwinding will stop with UnwindStart() returning UNWIND_TRUNCATED. + */ +typedef bool (*UnwindReportFunc)(void* data, const UnwReport* bte); + +/** Structure that holds memory callback function pointers. + */ +typedef struct { + + /** Report an unwind result. */ + UnwindReportFunc report; + + /** Read a 32 bit word from memory. + * The memory address to be read is passed as \a address, and + * \a *val is expected to be populated with the read value. + * If the address cannot or should not be read, false can be + * returned to indicate that unwinding should stop. If true + * is returned, \a *val is assumed to be valid and unwinding + * will continue. + */ + bool (*readW)(const uint32_t address, uint32_t *val); + + /** Read a 16 bit half-word from memory. + * This function has the same usage as for readW, but is expected + * to read only a 16 bit value. + */ + bool (*readH)(const uint32_t address, uint16_t *val); + + /** Read a byte from memory. + * This function has the same usage as for readW, but is expected + * to read only an 8 bit value. + */ + bool (*readB)(const uint32_t address, uint8_t *val); + + #ifdef UNW_DEBUG + /** Print a formatted line for debug. */ + void (*printf)(const char *format, ...); + #endif +} UnwindCallbacks; + +/* A frame */ +typedef struct { + uint32_t fp; + uint32_t sp; + uint32_t lr; + uint32_t pc; +} UnwindFrame; + +/** Start unwinding the current stack. + * This will unwind the stack starting at the PC value supplied to in the + * link register (i.e. not a normal register) and the stack pointer value + * supplied. + * + * -If the program was compiled with -funwind-tables it will use them to + * perform the traceback. Otherwise, brute force will be employed + * -If the program was compiled with -mpoke-function-name, then you will + * get function names in the traceback. Otherwise, you will not. + */ +UnwResult UnwindStart(UnwindFrame* frame, const UnwindCallbacks *cb, void *data); diff --git a/Marlin/src/HAL/shared/backtrace/unwmemaccess.cpp b/Marlin/src/HAL/shared/backtrace/unwmemaccess.cpp new file mode 100644 index 0000000..5ca46f9 --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwmemaccess.cpp @@ -0,0 +1,184 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commercially or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Utility functions to access memory + **************************************************************************/ + +#if defined(__arm__) || defined(__thumb__) + +#include "unwmemaccess.h" +#include "../../../inc/MarlinConfig.h" + +/* Validate address */ + +#ifdef ARDUINO_ARCH_SAM + + // For DUE, valid address ranges are + // SRAM (0x20070000 - 0x20088000) (96kb) + // FLASH (0x00080000 - 0x00100000) (512kb) + // + #define START_SRAM_ADDR 0x20070000 + #define END_SRAM_ADDR 0x20088000 + #define START_FLASH_ADDR 0x00080000 + #define END_FLASH_ADDR 0x00100000 + +#elif defined(TARGET_LPC1768) + + // For LPC1769: + // SRAM (0x10000000 - 0x10008000) (32kb) + // FLASH (0x00000000 - 0x00080000) (512kb) + // + #define START_SRAM_ADDR 0x10000000 + #define END_SRAM_ADDR 0x10008000 + #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) + // + #define START_SRAM_ADDR 0x20000000 + #define END_SRAM_ADDR 0x20010000 + #define START_FLASH_ADDR 0x00000000 + #define END_FLASH_ADDR 0x00080000 + +#elif defined(STM32F4) || defined(STM32F4xx) + + // For STM32F407VET + // SRAM (0x20000000 - 0x20030000) (192kb) + // FLASH (0x08000000 - 0x08080000) (512kb) + // + #define START_SRAM_ADDR 0x20000000 + #define END_SRAM_ADDR 0x20030000 + #define START_FLASH_ADDR 0x08000000 + #define END_FLASH_ADDR 0x08080000 + +#elif MB(REMRAM_V1, NUCLEO_F767ZI) + + // For STM32F765VI in RemRam v1 + // SRAM (0x20000000 - 0x20080000) (512kb) + // FLASH (0x08000000 - 0x08200000) (2048kb) + // + #define START_SRAM_ADDR 0x20000000 + #define END_SRAM_ADDR 0x20080000 + #define START_FLASH_ADDR 0x08000000 + #define END_FLASH_ADDR 0x08200000 + +#elif defined(__MK20DX256__) + + // For MK20DX256 in TEENSY 3.1 or TEENSY 3.2 + // SRAM (0x1FFF8000 - 0x20008000) (64kb) + // FLASH (0x00000000 - 0x00040000) (256kb) + // + #define START_SRAM_ADDR 0x1FFF8000 + #define END_SRAM_ADDR 0x20008000 + #define START_FLASH_ADDR 0x00000000 + #define END_FLASH_ADDR 0x00040000 + +#elif defined(__MK64FX512__) + + // For MK64FX512 in TEENSY 3.5 + // SRAM (0x1FFF0000 - 0x20020000) (192kb) + // FLASH (0x00000000 - 0x00080000) (512kb) + // + #define START_SRAM_ADDR 0x1FFF0000 + #define END_SRAM_ADDR 0x20020000 + #define START_FLASH_ADDR 0x00000000 + #define END_FLASH_ADDR 0x00080000 + +#elif defined(__MK66FX1M0__) + + // For MK66FX1M0 in TEENSY 3.6 + // SRAM (0x1FFF0000 - 0x20030000) (256kb) + // FLASH (0x00000000 - 0x00140000) (1.25Mb) + // + #define START_SRAM_ADDR 0x1FFF0000 + #define END_SRAM_ADDR 0x20030000 + #define START_FLASH_ADDR 0x00000000 + #define END_FLASH_ADDR 0x00140000 + +#elif defined(__IMXRT1062__) + + // For IMXRT1062 in TEENSY 4.0/4/1 + // ITCM (rwx): ORIGIN = 0x00000000, LENGTH = 512K + // DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 512K + // RAM (rwx): ORIGIN = 0x20200000, LENGTH = 512K + // FLASH (rwx): ORIGIN = 0x60000000, LENGTH = 1984K + // + #define START_SRAM_ADDR 0x00000000 + #define END_SRAM_ADDR 0x20280000 + #define START_FLASH_ADDR 0x60000000 + #define END_FLASH_ADDR 0x601F0000 + +#elif defined(__SAMD51P20A__) + + // For SAMD51x20, valid address ranges are + // SRAM (0x20000000 - 0x20040000) (256kb) + // FLASH (0x00000000 - 0x00100000) (1024kb) + // + #define START_SRAM_ADDR 0x20000000 + #define END_SRAM_ADDR 0x20040000 + #define START_FLASH_ADDR 0x00000000 + #define END_FLASH_ADDR 0x00100000 + +#endif + +static bool validate_addr(uint32_t addr) { + + // 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; + + return false; +} + +bool UnwReadW(const uint32_t a, uint32_t *v) { + if (!validate_addr(a)) + return false; + + *v = *(uint32_t *)a; + return true; +} + +bool UnwReadH(const uint32_t a, uint16_t *v) { + if (!validate_addr(a)) + return false; + + *v = *(uint16_t *)a; + return true; +} + +bool UnwReadB(const uint32_t a, uint8_t *v) { + if (!validate_addr(a)) + return false; + + *v = *(uint8_t *)a; + return true; +} + +#endif // __arm__ || __thumb__ diff --git a/Marlin/src/HAL/shared/backtrace/unwmemaccess.h b/Marlin/src/HAL/shared/backtrace/unwmemaccess.h new file mode 100644 index 0000000..562ab3f --- /dev/null +++ b/Marlin/src/HAL/shared/backtrace/unwmemaccess.h @@ -0,0 +1,22 @@ +/*************************************************************************** + * ARM Stack Unwinder, Michael.McTernan.2001@cs.bris.ac.uk + * Updated, adapted and several bug fixes on 2018 by Eduardo José Tagle + * + * This program is PUBLIC DOMAIN. + * This means that there is no copyright and anyone is able to take a copy + * for free and use it as they wish, with or without modifications, and in + * any context, commerically or otherwise. The only limitation is that I + * don't guarantee that the software is fit for any purpose or accept any + * liability for its use or misuse - this software is without warranty. + *************************************************************************** + * File Description: Utility functions to access memory + **************************************************************************/ + +#pragma once + +#include "unwarm.h" +#include <stdint.h> + +bool UnwReadW(const uint32_t a, uint32_t *v); +bool UnwReadH(const uint32_t a, uint16_t *v); +bool UnwReadB(const uint32_t a, uint8_t *v); diff --git a/Marlin/src/HAL/shared/eeprom_api.cpp b/Marlin/src/HAL/shared/eeprom_api.cpp new file mode 100644 index 0000000..47cfa5a --- /dev/null +++ b/Marlin/src/HAL/shared/eeprom_api.cpp @@ -0,0 +1,30 @@ +/** + * Marlin 3D Printer Firmware + * + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 "../../inc/MarlinConfigPre.h" + +#if EITHER(EEPROM_SETTINGS, SD_FIRMWARE_UPDATE) + + #include "eeprom_api.h" + PersistentStore persistentStore; + +#endif diff --git a/Marlin/src/HAL/shared/eeprom_api.h b/Marlin/src/HAL/shared/eeprom_api.h new file mode 100644 index 0000000..6445f7a --- /dev/null +++ b/Marlin/src/HAL/shared/eeprom_api.h @@ -0,0 +1,71 @@ +/** + * Marlin 3D Printer Firmware + * + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * Copyright (c) 2016 Victor Perez victor_pv@hotmail.com + * + * 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 <stddef.h> +#include <stdint.h> + +#include "../../libs/crc16.h" + +class PersistentStore { +public: + + // Total available persistent storage space (in bytes) + static size_t capacity(); + + // Prepare to read or write + static bool access_start(); + + // Housecleaning after read or write + static bool access_finish(); + + // Write one or more bytes of data and update the CRC + // Return 'true' on write error + static bool write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc); + + // Read one or more bytes of data and update the CRC + // Return 'true' on read error + static bool read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing=true); + + // Write one or more bytes of data + // Return 'true' on write error + static inline bool write_data(const int pos, const uint8_t* value, const size_t size=sizeof(uint8_t)) { + int data_pos = pos; + uint16_t crc = 0; + return write_data(data_pos, value, size, &crc); + } + + // Write a single byte of data + // Return 'true' on write error + static inline bool write_data(const int pos, const uint8_t value) { return write_data(pos, &value); } + + // Read one or more bytes of data + // Return 'true' on read error + static inline bool read_data(const int pos, uint8_t* value, const size_t size=1) { + int data_pos = pos; + uint16_t crc = 0; + return read_data(data_pos, value, size, &crc); + } +}; + +extern PersistentStore persistentStore; diff --git a/Marlin/src/HAL/shared/eeprom_if.h b/Marlin/src/HAL/shared/eeprom_if.h new file mode 100644 index 0000000..e44da80 --- /dev/null +++ b/Marlin/src/HAL/shared/eeprom_if.h @@ -0,0 +1,29 @@ +/** + * Marlin 3D Printer Firmware + * + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2016 Bob Cousins bobcousins42@googlemail.com + * Copyright (c) 2015-2016 Nico Tonnhofer wurstnase.reprap@gmail.com + * + * 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 + +// +// EEPROM +// +void eeprom_init(); +void eeprom_write_byte(uint8_t *pos, unsigned char value); +uint8_t eeprom_read_byte(uint8_t *pos); diff --git a/Marlin/src/HAL/shared/eeprom_if_i2c.cpp b/Marlin/src/HAL/shared/eeprom_if_i2c.cpp new file mode 100644 index 0000000..70bb240 --- /dev/null +++ b/Marlin/src/HAL/shared/eeprom_if_i2c.cpp @@ -0,0 +1,89 @@ +/** + * 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/>. + * + */ + +/** + * Platform-independent Arduino functions for I2C EEPROM. + * Enable USE_SHARED_EEPROM if not supplied by the framework. + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(I2C_EEPROM) + +#include "eeprom_if.h" +#include <Wire.h> + +#ifdef __STM32F1__ + void eeprom_init() { Wire.begin(); } +#else + #ifndef I2C_SDA_PIN + #define I2C_SDA_PIN PB7 + #endif + #ifndef I2C_SCL_PIN + #define I2C_SCL_PIN PB6 + #endif + void eeprom_init() { Wire.begin((uint8_t)I2C_SDA_PIN, (uint8_t)I2C_SCL_PIN); } +#endif + + +#if ENABLED(USE_SHARED_EEPROM) + +#ifndef EEPROM_WRITE_DELAY + #define EEPROM_WRITE_DELAY 5 +#endif +#ifndef EEPROM_DEVICE_ADDRESS + #define EEPROM_DEVICE_ADDRESS 0x50 +#endif + +static constexpr uint8_t eeprom_device_address = I2C_ADDRESS(EEPROM_DEVICE_ADDRESS); + +// ------------------------ +// Public functions +// ------------------------ + +void eeprom_write_byte(uint8_t *pos, unsigned char value) { + const unsigned eeprom_address = (unsigned)pos; + + Wire.beginTransmission(eeprom_device_address); + Wire.write(int(eeprom_address >> 8)); // MSB + Wire.write(int(eeprom_address & 0xFF)); // LSB + Wire.write(value); + Wire.endTransmission(); + + // wait for write cycle to complete + // this could be done more efficiently with "acknowledge polling" + delay(EEPROM_WRITE_DELAY); +} + +uint8_t eeprom_read_byte(uint8_t *pos) { + const unsigned eeprom_address = (unsigned)pos; + + Wire.beginTransmission(eeprom_device_address); + Wire.write(int(eeprom_address >> 8)); // MSB + Wire.write(int(eeprom_address & 0xFF)); // LSB + Wire.endTransmission(); + Wire.requestFrom(eeprom_device_address, (byte)1); + return Wire.available() ? Wire.read() : 0xFF; +} + +#endif // USE_SHARED_EEPROM +#endif // I2C_EEPROM diff --git a/Marlin/src/HAL/shared/eeprom_if_spi.cpp b/Marlin/src/HAL/shared/eeprom_if_spi.cpp new file mode 100644 index 0000000..a341fef --- /dev/null +++ b/Marlin/src/HAL/shared/eeprom_if_spi.cpp @@ -0,0 +1,87 @@ +/** + * 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/>. + * + */ + +/** + * Platform-independent Arduino functions for SPI EEPROM. + * Enable USE_SHARED_EEPROM if not supplied by the framework. + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(SPI_EEPROM) + +#include "eeprom_if.h" + +void eeprom_init() {} + +#if ENABLED(USE_SHARED_EEPROM) + +#define CMD_WREN 6 // WREN +#define CMD_READ 2 // WRITE +#define CMD_WRITE 2 // WRITE + +#ifndef EEPROM_WRITE_DELAY + #define EEPROM_WRITE_DELAY 7 +#endif + +uint8_t eeprom_read_byte(uint8_t* pos) { + uint8_t v; + uint8_t eeprom_temp[3]; + + // set read location + // begin transmission from device + eeprom_temp[0] = CMD_READ; + eeprom_temp[1] = ((unsigned)pos>>8) & 0xFF; // addr High + eeprom_temp[2] = (unsigned)pos& 0xFF; // addr Low + WRITE(SPI_EEPROM1_CS, HIGH); + WRITE(SPI_EEPROM1_CS, LOW); + spiSend(SPI_CHAN_EEPROM1, eeprom_temp, 3); + + v = spiRec(SPI_CHAN_EEPROM1); + WRITE(SPI_EEPROM1_CS, HIGH); + return v; +} + +void eeprom_write_byte(uint8_t* pos, uint8_t value) { + uint8_t eeprom_temp[3]; + + /*write enable*/ + eeprom_temp[0] = CMD_WREN; + WRITE(SPI_EEPROM1_CS, LOW); + spiSend(SPI_CHAN_EEPROM1, eeprom_temp, 1); + WRITE(SPI_EEPROM1_CS, HIGH); + delay(1); + + /*write addr*/ + eeprom_temp[0] = CMD_WRITE; + eeprom_temp[1] = ((unsigned)pos>>8) & 0xFF; //addr High + eeprom_temp[2] = (unsigned)pos & 0xFF; //addr Low + WRITE(SPI_EEPROM1_CS, LOW); + spiSend(SPI_CHAN_EEPROM1, eeprom_temp, 3); + + spiSend(SPI_CHAN_EEPROM1, value); + WRITE(SPI_EEPROM1_CS, HIGH); + delay(EEPROM_WRITE_DELAY); // wait for page write to complete +} + +#endif // USE_SHARED_EEPROM +#endif // I2C_EEPROM diff --git a/Marlin/src/HAL/shared/esp_wifi.cpp b/Marlin/src/HAL/shared/esp_wifi.cpp new file mode 100644 index 0000000..a55f5ca --- /dev/null +++ b/Marlin/src/HAL/shared/esp_wifi.cpp @@ -0,0 +1,43 @@ +/** + * 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/>. + * + */ + +#include "../../inc/MarlinConfig.h" +#include "Delay.h" + +void esp_wifi_init(void) { // init ESP01 WIFI module pins + #if PIN_EXISTS(ESP_WIFI_MODULE_GPIO0) + OUT_WRITE(ESP_WIFI_MODULE_GPIO0_PIN, HIGH); + #endif + #if PIN_EXISTS(ESP_WIFI_MODULE_GPIO2) + OUT_WRITE(ESP_WIFI_MODULE_GPIO2_PIN, HIGH); + #endif + #if PIN_EXISTS(ESP_WIFI_MODULE_RESET) + delay(1); // power up delay (0.1mS minimum) + OUT_WRITE(ESP_WIFI_MODULE_RESET_PIN, LOW); + delay(1); + OUT_WRITE(ESP_WIFI_MODULE_RESET_PIN, HIGH); + #endif + #if PIN_EXISTS(ESP_WIFI_MODULE_ENABLE) + delay(1); // delay after reset released (0.1mS minimum) + OUT_WRITE(ESP_WIFI_MODULE_ENABLE_PIN, HIGH); + #endif +} diff --git a/Marlin/src/HAL/shared/esp_wifi.h b/Marlin/src/HAL/shared/esp_wifi.h new file mode 100644 index 0000000..84a50a9 --- /dev/null +++ b/Marlin/src/HAL/shared/esp_wifi.h @@ -0,0 +1,24 @@ +/** + * 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/>. + * + */ +#pragma once + +void esp_wifi_init(); diff --git a/Marlin/src/HAL/shared/math_32bit.h b/Marlin/src/HAL/shared/math_32bit.h new file mode 100644 index 0000000..87e9e64 --- /dev/null +++ b/Marlin/src/HAL/shared/math_32bit.h @@ -0,0 +1,31 @@ +/** + * 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/>. + * + */ +#pragma once + +#include "../../core/macros.h" + +/** + * Math helper functions for 32 bit CPUs + */ +static FORCE_INLINE uint32_t MultiU32X24toH32(uint32_t longIn1, uint32_t longIn2) { + return ((uint64_t)longIn1 * longIn2 + 0x00800000) >> 24; +} diff --git a/Marlin/src/HAL/shared/progmem.h b/Marlin/src/HAL/shared/progmem.h new file mode 100644 index 0000000..539d027 --- /dev/null +++ b/Marlin/src/HAL/shared/progmem.h @@ -0,0 +1,189 @@ +/** + * 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/>. + * + */ +#pragma once + +#ifndef __AVR__ +#ifndef __PGMSPACE_H_ +// This define should prevent reading the system pgmspace.h if included elsewhere +// This is not normally needed. +#define __PGMSPACE_H_ 1 +#endif + +#ifndef PROGMEM +#define PROGMEM +#endif +#ifndef PGM_P +#define PGM_P const char * +#endif +#ifndef PSTR +#define PSTR(str) (str) +#endif +#ifndef F +#define F(str) (str) +#endif +#ifndef _SFR_BYTE +#define _SFR_BYTE(n) (n) +#endif +#ifndef memchr_P +#define memchr_P(str, c, len) memchr((str), (c), (len)) +#endif +#ifndef memcmp_P +#define memcmp_P(a, b, n) memcmp((a), (b), (n)) +#endif +#ifndef memcpy_P +#define memcpy_P(dest, src, num) memcpy((dest), (src), (num)) +#endif +#ifndef memmem_P +#define memmem_P(a, alen, b, blen) memmem((a), (alen), (b), (blen)) +#endif +#ifndef memrchr_P +#define memrchr_P(str, val, len) memrchr((str), (val), (len)) +#endif +#ifndef strcat_P +#define strcat_P(dest, src) strcat((dest), (src)) +#endif +#ifndef strchr_P +#define strchr_P(str, c) strchr((str), (c)) +#endif +#ifndef strchrnul_P +#define strchrnul_P(str, c) strchrnul((str), (c)) +#endif +#ifndef strcmp_P +#define strcmp_P(a, b) strcmp((a), (b)) +#endif +#ifndef strcpy_P +#define strcpy_P(dest, src) strcpy((dest), (src)) +#endif +#ifndef strcasecmp_P +#define strcasecmp_P(a, b) strcasecmp((a), (b)) +#endif +#ifndef strcasestr_P +#define strcasestr_P(a, b) strcasestr((a), (b)) +#endif +#ifndef strlcat_P +#define strlcat_P(dest, src, len) strlcat((dest), (src), (len)) +#endif +#ifndef strlcpy_P +#define strlcpy_P(dest, src, len) strlcpy((dest), (src), (len)) +#endif +#ifndef strlen_P +#define strlen_P(s) strlen((const char *)(s)) +#endif +#ifndef strnlen_P +#define strnlen_P(str, len) strnlen((str), (len)) +#endif +#ifndef strncmp_P +#define strncmp_P(a, b, n) strncmp((a), (b), (n)) +#endif +#ifndef strncasecmp_P +#define strncasecmp_P(a, b, n) strncasecmp((a), (b), (n)) +#endif +#ifndef strncat_P +#define strncat_P(a, b, n) strncat((a), (b), (n)) +#endif +#ifndef strncpy_P +#define strncpy_P(a, b, n) strncpy((a), (b), (n)) +#endif +#ifndef strpbrk_P +#define strpbrk_P(str, chrs) strpbrk((str), (chrs)) +#endif +#ifndef strrchr_P +#define strrchr_P(str, c) strrchr((str), (c)) +#endif +#ifndef strsep_P +#define strsep_P(strp, delim) strsep((strp), (delim)) +#endif +#ifndef strspn_P +#define strspn_P(str, chrs) strspn((str), (chrs)) +#endif +#ifndef strstr_P +#define strstr_P(a, b) strstr((a), (b)) +#endif +#ifndef sprintf_P +#define sprintf_P(s, ...) sprintf((s), __VA_ARGS__) +#endif +#ifndef vfprintf_P +#define vfprintf_P(s, ...) vfprintf((s), __VA_ARGS__) +#endif +#ifndef printf_P +#define printf_P(...) printf(__VA_ARGS__) +#endif +#ifndef snprintf_P +#define snprintf_P(s, n, ...) snprintf((s), (n), __VA_ARGS__) +#endif +#ifndef vsprintf_P +#define vsprintf_P(s, ...) vsprintf((s),__VA_ARGS__) +#endif +#ifndef vsnprintf_P +#define vsnprintf_P(s, n, ...) vsnprintf((s), (n),__VA_ARGS__) +#endif +#ifndef fprintf_P +#define fprintf_P(s, ...) fprintf((s), __VA_ARGS__) +#endif + +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#endif +#ifndef pgm_read_word +#define pgm_read_word(addr) (*(const unsigned short *)(addr)) +#endif +#ifndef pgm_read_dword +#define pgm_read_dword(addr) (*(const unsigned long *)(addr)) +#endif +#ifndef pgm_read_float +#define pgm_read_float(addr) (*(const float *)(addr)) +#endif + +#ifndef pgm_read_byte_near +#define pgm_read_byte_near(addr) pgm_read_byte(addr) +#endif +#ifndef pgm_read_word_near +#define pgm_read_word_near(addr) pgm_read_word(addr) +#endif +#ifndef pgm_read_dword_near +#define pgm_read_dword_near(addr) pgm_read_dword(addr) +#endif +#ifndef pgm_read_float_near +#define pgm_read_float_near(addr) pgm_read_float(addr) +#endif +#ifndef pgm_read_byte_far +#define pgm_read_byte_far(addr) pgm_read_byte(addr) +#endif +#ifndef pgm_read_word_far +#define pgm_read_word_far(addr) pgm_read_word(addr) +#endif +#ifndef pgm_read_dword_far +#define pgm_read_dword_far(addr) pgm_read_dword(addr) +#endif +#ifndef pgm_read_float_far +#define pgm_read_float_far(addr) pgm_read_float(addr) +#endif + +#ifndef pgm_read_pointer +#define pgm_read_pointer +#endif + +// Fix bug in pgm_read_ptr +#undef pgm_read_ptr +#define pgm_read_ptr(addr) (*((void**)(addr))) + +#endif // __AVR__ diff --git a/Marlin/src/HAL/shared/servo.cpp b/Marlin/src/HAL/shared/servo.cpp new file mode 100644 index 0000000..cfec6f3 --- /dev/null +++ b/Marlin/src/HAL/shared/servo.cpp @@ -0,0 +1,156 @@ +/** + * 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/>. + * + */ + +/** + * servo.cpp - Interrupt driven Servo library for Arduino using 16 bit timers- Version 2 + * Copyright (c) 2009 Michael Margolis. All right reserved. + */ + +/** + * A servo is activated by creating an instance of the Servo class passing the desired pin to the attach() method. + * The servos are pulsed in the background using the value most recently written using the write() method + * + * Note that analogWrite of PWM on pins associated with the timer are disabled when the first servo is attached. + * Timers are seized as needed in groups of 12 servos - 24 servos use two timers, 48 servos will use four. + * + * The methods are: + * + * Servo - Class for manipulating servo motors connected to Arduino pins. + * + * attach(pin) - Attach a servo motor to an i/o pin. + * attach(pin, min, max) - Attach to a pin, setting min and max values in microseconds + * Default min is 544, max is 2400 + * + * write() - Set the servo angle in degrees. (Invalid angles —over MIN_PULSE_WIDTH— are treated as µs.) + * writeMicroseconds() - Set the servo pulse width in microseconds. + * move(pin, angle) - Sequence of attach(pin), write(angle), safe_delay(servo_delay[servoIndex]). + * With DEACTIVATE_SERVOS_AFTER_MOVE it detaches after servo_delay[servoIndex]. + * read() - Get the last-written servo pulse width as an angle between 0 and 180. + * readMicroseconds() - Get the last-written servo pulse width in microseconds. + * attached() - Return true if a servo is attached. + * detach() - Stop an attached servo from pulsing its i/o pin. + */ + +#include "../../inc/MarlinConfig.h" + +#if SHARED_SERVOS + +#include "servo.h" +#include "servo_private.h" + +ServoInfo_t servo_info[MAX_SERVOS]; // static array of servo info structures +uint8_t ServoCount = 0; // the total number of attached servos + +#define SERVO_MIN(v) (MIN_PULSE_WIDTH - (v) * 4) // minimum value in uS for this servo +#define SERVO_MAX(v) (MAX_PULSE_WIDTH - (v) * 4) // maximum value in uS for this servo + +/************ static functions common to all instances ***********************/ + +static boolean isTimerActive(timer16_Sequence_t timer) { + // returns true if any servo is active on this timer + LOOP_L_N(channel, SERVOS_PER_TIMER) { + if (SERVO(timer, channel).Pin.isActive) + return true; + } + return false; +} + +/****************** end of static functions ******************************/ + +Servo::Servo() { + if (ServoCount < MAX_SERVOS) { + servoIndex = ServoCount++; // assign a servo index to this instance + servo_info[servoIndex].ticks = usToTicks(DEFAULT_PULSE_WIDTH); // store default values - 12 Aug 2009 + } + else + servoIndex = INVALID_SERVO; // too many servos +} + +int8_t Servo::attach(const int inPin) { + return attach(inPin, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); +} + +int8_t Servo::attach(const int inPin, const int inMin, const int inMax) { + + if (servoIndex >= MAX_SERVOS) return -1; + + if (inPin > 0) servo_info[servoIndex].Pin.nbr = inPin; + pinMode(servo_info[servoIndex].Pin.nbr, OUTPUT); // set servo pin to output + + // TODO: min/max check: ABS(min - MIN_PULSE_WIDTH) / 4 < 128 + min = (MIN_PULSE_WIDTH - inMin) / 4; //resolution of min/max is 4 uS + max = (MAX_PULSE_WIDTH - inMax) / 4; + + // initialize the timer if it has not already been initialized + timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex); + if (!isTimerActive(timer)) initISR(timer); + servo_info[servoIndex].Pin.isActive = true; // this must be set after the check for isTimerActive + + return servoIndex; +} + +void Servo::detach() { + servo_info[servoIndex].Pin.isActive = false; + timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex); + if (!isTimerActive(timer)) finISR(timer); +} + +void Servo::write(int value) { + if (value < MIN_PULSE_WIDTH) // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds) + value = map(constrain(value, 0, 180), 0, 180, SERVO_MIN(min), SERVO_MAX(max)); + writeMicroseconds(value); +} + +void Servo::writeMicroseconds(int value) { + // calculate and store the values for the given channel + byte channel = servoIndex; + if (channel < MAX_SERVOS) { // ensure channel is valid + // ensure pulse width is valid + value = constrain(value, SERVO_MIN(min), SERVO_MAX(max)) - (TRIM_DURATION); + value = usToTicks(value); // convert to ticks after compensating for interrupt overhead - 12 Aug 2009 + + CRITICAL_SECTION_START(); + servo_info[channel].ticks = value; + CRITICAL_SECTION_END(); + } +} + +// return the value as degrees +int Servo::read() { return map(readMicroseconds() + 1, SERVO_MIN(min), SERVO_MAX(max), 0, 180); } + +int Servo::readMicroseconds() { + return (servoIndex == INVALID_SERVO) ? 0 : ticksToUs(servo_info[servoIndex].ticks) + (TRIM_DURATION); +} + +bool Servo::attached() { return servo_info[servoIndex].Pin.isActive; } + +void Servo::move(const int value) { + constexpr uint16_t servo_delay[] = SERVO_DELAY; + static_assert(COUNT(servo_delay) == NUM_SERVOS, "SERVO_DELAY must be an array NUM_SERVOS long."); + if (attach(0) >= 0) { + write(value); + safe_delay(servo_delay[servoIndex]); + TERN_(DEACTIVATE_SERVOS_AFTER_MOVE, detach()); + } +} + +#endif // SHARED_SERVOS diff --git a/Marlin/src/HAL/shared/servo.h b/Marlin/src/HAL/shared/servo.h new file mode 100644 index 0000000..c2560a8 --- /dev/null +++ b/Marlin/src/HAL/shared/servo.h @@ -0,0 +1,115 @@ +/** + * 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/>. + * + */ +#pragma once + +/** + * servo.h - Interrupt driven Servo library for Arduino using 16 bit timers- Version 2 + * Copyright (c) 2009 Michael Margolis. All right reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * A servo is activated by creating an instance of the Servo class passing the desired pin to the attach() method. + * The servos are pulsed in the background using the value most recently written using the write() method + * + * Note that analogWrite of PWM on pins associated with the timer are disabled when the first servo is attached. + * Timers are seized as needed in groups of 12 servos - 24 servos use two timers, 48 servos will use four. + * The sequence used to seize timers is defined in timers.h + * + * The methods are: + * + * Servo - Class for manipulating servo motors connected to Arduino pins. + * + * attach(pin ) - Attaches a servo motor to an i/o pin. + * attach(pin, min, max ) - Attaches to a pin setting min and max values in microseconds + * default min is 544, max is 2400 + * + * write() - Sets the servo angle in degrees. (invalid angle that is valid as pulse in microseconds is treated as microseconds) + * writeMicroseconds() - Sets the servo pulse width in microseconds + * read() - Gets the last written servo pulse width as an angle between 0 and 180. + * readMicroseconds() - Gets the last written servo pulse width in microseconds. (was read_us() in first release) + * attached() - Returns true if there is a servo attached. + * detach() - Stops an attached servos from pulsing its i/o pin. + * move(angle) - Sequence of attach(0), write(angle), + * With DEACTIVATE_SERVOS_AFTER_MOVE wait SERVO_DELAY and detach. + */ + +#if IS_TEENSY32 + #include "../TEENSY31_32/Servo.h" +#elif IS_TEENSY35 || IS_TEENSY36 + #include "../TEENSY35_36/Servo.h" +#elif IS_TEENSY40 || IS_TEENSY41 + #include "../TEENSY40_41/Servo.h" +#elif defined(TARGET_LPC1768) + #include "../LPC1768/Servo.h" +#elif defined(__STM32F1__) || defined(TARGET_STM32F1) + #include "../STM32F1/Servo.h" +#elif defined(ARDUINO_ARCH_STM32) + #include "../STM32/Servo.h" +#elif defined(ARDUINO_ARCH_ESP32) + #include "../ESP32/Servo.h" +#else + #include <stdint.h> + + #if defined(__AVR__) || defined(ARDUINO_ARCH_SAM) || defined(__SAMD51__) + // we're good to go + #else + #error "This library only supports boards with an AVR, SAM3X or SAMD51 processor." + #endif + + #define Servo_VERSION 2 // software version of this library + + class Servo { + public: + Servo(); + int8_t attach(const int pin); // attach the given pin to the next free channel, set pinMode, return channel number (-1 on fail) + int8_t attach(const int pin, const int min, const int max); // as above but also sets min and max values for writes. + void detach(); + void write(int value); // if value is < 200 it is treated as an angle, otherwise as pulse width in microseconds + void writeMicroseconds(int value); // write pulse width in microseconds + void move(const int value); // attach the servo, then move to value + // if value is < 200 it is treated as an angle, otherwise as pulse width in microseconds + // if DEACTIVATE_SERVOS_AFTER_MOVE wait SERVO_DELAY, then detach + int read(); // returns current pulse width as an angle between 0 and 180 degrees + int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release) + bool attached(); // return true if this servo is attached, otherwise false + + private: + uint8_t servoIndex; // index into the channel data for this servo + int8_t min; // minimum is this value times 4 added to MIN_PULSE_WIDTH + int8_t max; // maximum is this value times 4 added to MAX_PULSE_WIDTH + }; + +#endif diff --git a/Marlin/src/HAL/shared/servo_private.h b/Marlin/src/HAL/shared/servo_private.h new file mode 100644 index 0000000..d85d8da --- /dev/null +++ b/Marlin/src/HAL/shared/servo_private.h @@ -0,0 +1,98 @@ +/** + * 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/>. + * + */ +#pragma once + +/** + * servo_private.h - Interrupt driven Servo library for Arduino using 16 bit timers- Version 2 + * Copyright (c) 2009 Michael Margolis. All right reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdint.h> + +// Architecture specific include +#ifdef __AVR__ + #include "../AVR/ServoTimers.h" +#elif defined(ARDUINO_ARCH_SAM) + #include "../DUE/ServoTimers.h" +#elif defined(__SAMD51__) + #include "../SAMD51/ServoTimers.h" +#else + #error "This library only supports boards with an AVR, SAM3X or SAMD51 processor." +#endif + +// Macros + +#define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo +#define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo +#define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached +#define REFRESH_INTERVAL 20000 // minimum time to refresh servos in microseconds + +#define SERVOS_PER_TIMER 12 // the maximum number of servos controlled by one timer +#define MAX_SERVOS (_Nbr_16timers * SERVOS_PER_TIMER) + +#define INVALID_SERVO 255 // flag indicating an invalid servo index + +// Convert microseconds to ticks and back (PRESCALER depends on architecture) +#define usToTicks(_us) (clockCyclesPerMicrosecond() * (_us) / (SERVO_TIMER_PRESCALER)) +#define ticksToUs(_ticks) (unsigned(_ticks) * (SERVO_TIMER_PRESCALER) / clockCyclesPerMicrosecond()) + +// convenience macros +#define SERVO_INDEX_TO_TIMER(_servo_nbr) ((timer16_Sequence_t)(_servo_nbr / (SERVOS_PER_TIMER))) // returns the timer controlling this servo +#define SERVO_INDEX_TO_CHANNEL(_servo_nbr) (_servo_nbr % (SERVOS_PER_TIMER)) // returns the index of the servo on this timer +#define SERVO_INDEX(_timer,_channel) ((_timer*(SERVOS_PER_TIMER)) + _channel) // macro to access servo index by timer and channel +#define SERVO(_timer,_channel) (servo_info[SERVO_INDEX(_timer,_channel)]) // macro to access servo class by timer and channel + +// Types + +typedef struct { + uint8_t nbr : 7 ; // a pin number from 0 to 127 + uint8_t isActive : 1 ; // true if this channel is enabled, pin not pulsed if false +} ServoPin_t; + +typedef struct { + ServoPin_t Pin; + unsigned int ticks; +} ServoInfo_t; + +// Global variables + +extern uint8_t ServoCount; +extern ServoInfo_t servo_info[MAX_SERVOS]; + +// Public functions + +extern void initISR(timer16_Sequence_t timer); +extern void finISR(timer16_Sequence_t timer); |