2019-04-16 20:20:53 +00:00
|
|
|
//! @file
|
2018-06-14 13:13:21 +00:00
|
|
|
// Based on the OptiBoot project
|
|
|
|
// https://github.com/Optiboot/optiboot
|
|
|
|
// Licence GLP 2 or later.
|
|
|
|
|
|
|
|
#include "Marlin.h"
|
2021-03-05 07:51:23 +00:00
|
|
|
#include "xflash.h"
|
2018-06-14 13:13:21 +00:00
|
|
|
#include "stk500.h"
|
2018-06-14 21:07:54 +00:00
|
|
|
#include "bootapp.h"
|
2021-02-17 14:59:52 +00:00
|
|
|
#include <avr/wdt.h>
|
2022-04-15 10:37:51 +00:00
|
|
|
#include "lcd.h"
|
2018-06-14 13:13:21 +00:00
|
|
|
|
|
|
|
#define OPTIBOOT_MAJVER 6
|
|
|
|
#define OPTIBOOT_CUSTOMVER 0
|
|
|
|
#define OPTIBOOT_MINVER 2
|
|
|
|
static unsigned const int __attribute__((section(".version")))
|
|
|
|
optiboot_version = 256*(OPTIBOOT_MAJVER + OPTIBOOT_CUSTOMVER) + OPTIBOOT_MINVER;
|
|
|
|
|
|
|
|
#if 0
|
2021-03-05 07:51:23 +00:00
|
|
|
#define XFLASH_SIGNATURE_0 9
|
|
|
|
#define XFLASH_SIGNATURE_1 8
|
|
|
|
#define XFLASH_SIGNATURE_2 7
|
2018-06-14 13:13:21 +00:00
|
|
|
#else
|
|
|
|
//FIXME this is a signature of ATmega2560!
|
2021-03-05 07:51:23 +00:00
|
|
|
#define XFLASH_SIGNATURE_0 0x1E
|
|
|
|
#define XFLASH_SIGNATURE_1 0x98
|
|
|
|
#define XFLASH_SIGNATURE_2 0x01
|
2018-06-14 13:13:21 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#define RECV_READY ((UCSR0A & _BV(RXC0)) != 0)
|
|
|
|
|
|
|
|
static uint8_t getch(void) {
|
|
|
|
uint8_t ch;
|
|
|
|
while(! RECV_READY) ;
|
|
|
|
if (!(UCSR0A & _BV(FE0))) {
|
|
|
|
/*
|
|
|
|
* A Framing Error indicates (probably) that something is talking
|
|
|
|
* to us at the wrong bit rate. Assume that this is because it
|
|
|
|
* expects to be talking to the application, and DON'T reset the
|
|
|
|
* watchdog. This should cause the bootloader to abort and run
|
|
|
|
* the application "soon", if it keeps happening. (Note that we
|
|
|
|
* don't care that an invalid char is returned...)
|
|
|
|
*/
|
2021-02-17 14:59:52 +00:00
|
|
|
wdt_reset();
|
2018-06-14 13:13:21 +00:00
|
|
|
}
|
|
|
|
ch = UDR0;
|
|
|
|
return ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void putch(char ch) {
|
|
|
|
while (!(UCSR0A & _BV(UDRE0)));
|
|
|
|
UDR0 = ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void verifySpace() {
|
|
|
|
if (getch() != CRC_EOP) {
|
|
|
|
putch(STK_FAILED);
|
2021-03-03 14:31:06 +00:00
|
|
|
wdt_enable(WDTO_15MS); // shorten WD timeout
|
2018-06-14 13:13:21 +00:00
|
|
|
while (1) // and busy-loop so that WD causes
|
|
|
|
; // a reset and app start.
|
|
|
|
}
|
|
|
|
putch(STK_INSYNC);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void getNch(uint8_t count) {
|
|
|
|
do getch(); while (--count);
|
|
|
|
verifySpace();
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef uint16_t pagelen_t;
|
|
|
|
|
2021-05-28 07:09:30 +00:00
|
|
|
//Thou shalt not change these messages, else the avrdude-slicer xflash implementation will no longer work and the language upload will fail.
|
|
|
|
//Right now we support 2 xflash chips - the original w25x20cl and a new one GD25Q20C
|
2018-06-14 13:13:21 +00:00
|
|
|
static const char entry_magic_send [] PROGMEM = "start\n";
|
|
|
|
static const char entry_magic_receive[] PROGMEM = "w25x20cl_enter\n";
|
|
|
|
static const char entry_magic_cfm [] PROGMEM = "w25x20cl_cfm\n";
|
|
|
|
|
|
|
|
struct block_t;
|
|
|
|
extern struct block_t *block_buffer;
|
|
|
|
|
2019-04-16 20:20:53 +00:00
|
|
|
//! @brief Enter an STK500 compatible Optiboot boot loader waiting for flashing the languages to an external flash memory.
|
2020-05-27 07:16:44 +00:00
|
|
|
//! @return 1 if "start\n" was not sent. Optiboot was skipped
|
|
|
|
//! @return 0 if "start\n" was sent. Optiboot ran normally. No need to send "start\n" in setup()
|
2021-03-05 07:51:23 +00:00
|
|
|
uint8_t optiboot_xflash_enter()
|
2018-06-14 13:13:21 +00:00
|
|
|
{
|
2021-03-03 14:31:55 +00:00
|
|
|
// Make sure to check boot_app_magic as well. Since these bootapp flags are located right in the middle of the stack,
|
|
|
|
// they can be unintentionally changed. As a workaround to the language upload problem, do not only check for one bit if it's set,
|
|
|
|
// but rather test 33 bits for the correct value before exiting optiboot early.
|
|
|
|
if ((boot_app_magic == BOOT_APP_MAGIC) && (boot_app_flags & BOOT_APP_FLG_USER0)) return 1;
|
2018-06-14 13:13:21 +00:00
|
|
|
uint8_t ch;
|
|
|
|
uint8_t rampz = 0;
|
2022-07-02 17:28:13 +00:00
|
|
|
uint16_t address = 0;
|
|
|
|
pagelen_t length;
|
2018-06-14 13:13:21 +00:00
|
|
|
// Use the planner's queue for the receive / transmit buffers.
|
|
|
|
// uint8_t *buff = (uint8_t*)block_buffer;
|
|
|
|
uint8_t buff[260];
|
|
|
|
// bitmap of pages to be written. Bit is set to 1 if the page has already been erased.
|
|
|
|
uint8_t pages_erased = 0;
|
|
|
|
|
|
|
|
// Handshake sequence: Initialize the serial line, flush serial line, send magic, receive magic.
|
|
|
|
// If the magic is not received on time, or it is not received correctly, continue to the application.
|
|
|
|
{
|
2021-02-17 14:59:52 +00:00
|
|
|
wdt_reset();
|
2018-06-14 13:13:21 +00:00
|
|
|
const char *ptr = entry_magic_send;
|
|
|
|
const char *end = strlen_P(entry_magic_send) + ptr;
|
2020-05-27 14:40:14 +00:00
|
|
|
const uint8_t selectedSerialPort_bak = selectedSerialPort;
|
2018-06-14 13:13:21 +00:00
|
|
|
// Flush the serial line.
|
|
|
|
while (RECV_READY) {
|
2021-02-17 14:59:52 +00:00
|
|
|
wdt_reset();
|
2018-06-14 13:13:21 +00:00
|
|
|
// Dummy register read (discard)
|
|
|
|
(void)(*(char *)UDR0);
|
|
|
|
}
|
2020-05-27 14:40:14 +00:00
|
|
|
selectedSerialPort = 0; //switch to Serial0
|
2020-05-27 10:50:02 +00:00
|
|
|
MYSERIAL.flush(); //clear RX buffer
|
2021-03-03 12:35:37 +00:00
|
|
|
int SerialHead = rx_buffer.head;
|
2018-06-14 13:13:21 +00:00
|
|
|
// Send the initial magic string.
|
|
|
|
while (ptr != end)
|
2018-11-09 09:45:16 +00:00
|
|
|
putch(pgm_read_byte(ptr ++));
|
2021-02-17 14:59:52 +00:00
|
|
|
wdt_reset();
|
2020-05-27 10:50:02 +00:00
|
|
|
// Wait for two seconds until a magic string (constant entry_magic) is received
|
2018-06-14 13:13:21 +00:00
|
|
|
// from the serial line.
|
|
|
|
ptr = entry_magic_receive;
|
|
|
|
end = strlen_P(entry_magic_receive) + ptr;
|
|
|
|
while (ptr != end) {
|
2021-03-03 10:19:39 +00:00
|
|
|
unsigned long boot_timer = 2000000;
|
|
|
|
// Beware of this volatile pointer - it is important since the while-cycle below
|
|
|
|
// doesn't contain any obvious references to rx_buffer.head
|
|
|
|
// thus the compiler is allowed to remove the check from the cycle
|
|
|
|
// i.e. rx_buffer.head == SerialHead would not be checked at all!
|
|
|
|
// With the volatile keyword the compiler generates exactly the same code as without it with only one difference:
|
|
|
|
// the last brne instruction jumps onto the (*rx_head == SerialHead) check and NOT onto the wdr instruction bypassing the check.
|
|
|
|
volatile int *rx_head = &rx_buffer.head;
|
|
|
|
while (*rx_head == SerialHead) {
|
2021-02-17 14:59:52 +00:00
|
|
|
wdt_reset();
|
2021-03-03 10:19:39 +00:00
|
|
|
if ( --boot_timer == 0) {
|
2018-06-14 13:13:21 +00:00
|
|
|
// Timeout expired, continue with the application.
|
2020-05-27 14:40:14 +00:00
|
|
|
selectedSerialPort = selectedSerialPort_bak; //revert Serial setting
|
2020-05-27 06:44:28 +00:00
|
|
|
return 0;
|
2020-05-27 14:40:14 +00:00
|
|
|
}
|
2018-06-14 13:13:21 +00:00
|
|
|
}
|
2020-05-27 10:50:02 +00:00
|
|
|
ch = rx_buffer.buffer[SerialHead];
|
|
|
|
SerialHead = (unsigned int)(SerialHead + 1) % RX_BUFFER_SIZE;
|
2018-11-09 09:45:16 +00:00
|
|
|
if (pgm_read_byte(ptr ++) != ch)
|
2020-05-27 14:40:14 +00:00
|
|
|
{
|
2018-06-14 13:13:21 +00:00
|
|
|
// Magic was not received correctly, continue with the application
|
2020-05-27 14:40:14 +00:00
|
|
|
selectedSerialPort = selectedSerialPort_bak; //revert Serial setting
|
2020-05-27 06:44:28 +00:00
|
|
|
return 0;
|
2020-05-27 14:40:14 +00:00
|
|
|
}
|
2021-02-17 14:59:52 +00:00
|
|
|
wdt_reset();
|
2018-06-14 13:13:21 +00:00
|
|
|
}
|
2020-05-27 10:50:02 +00:00
|
|
|
cbi(UCSR0B, RXCIE0); //disable the MarlinSerial0 interrupt
|
2018-06-14 13:13:21 +00:00
|
|
|
// Send the cfm magic string.
|
|
|
|
ptr = entry_magic_cfm;
|
2021-03-03 10:31:14 +00:00
|
|
|
end = strlen_P(entry_magic_cfm) + ptr;
|
2018-06-14 13:13:21 +00:00
|
|
|
while (ptr != end)
|
2018-11-09 09:45:16 +00:00
|
|
|
putch(pgm_read_byte(ptr ++));
|
2018-06-14 13:13:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
spi_init();
|
2021-03-05 07:51:23 +00:00
|
|
|
xflash_init();
|
2021-03-03 14:31:06 +00:00
|
|
|
wdt_disable();
|
2018-06-14 13:13:21 +00:00
|
|
|
|
2022-04-15 10:37:51 +00:00
|
|
|
lcd_clear();
|
|
|
|
lcd_puts_at_P(0, 1, PSTR(" Upgrading xflash\n Do not disconnect!"));
|
|
|
|
|
2018-06-14 13:13:21 +00:00
|
|
|
/* Forever loop: exits by causing WDT reset */
|
|
|
|
for (;;) {
|
|
|
|
/* get character from UART */
|
|
|
|
ch = getch();
|
|
|
|
|
|
|
|
if(ch == STK_GET_PARAMETER) {
|
|
|
|
unsigned char which = getch();
|
|
|
|
verifySpace();
|
|
|
|
/*
|
|
|
|
* Send optiboot version as "SW version"
|
|
|
|
* Note that the references to memory are optimized away.
|
|
|
|
*/
|
|
|
|
if (which == STK_SW_MINOR) {
|
|
|
|
putch(optiboot_version & 0xFF);
|
|
|
|
} else if (which == STK_SW_MAJOR) {
|
|
|
|
putch(optiboot_version >> 8);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* GET PARAMETER returns a generic 0x03 reply for
|
|
|
|
* other parameters - enough to keep Avrdude happy
|
|
|
|
*/
|
|
|
|
putch(0x03);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(ch == STK_SET_DEVICE) {
|
|
|
|
// SET DEVICE is ignored
|
|
|
|
getNch(20);
|
|
|
|
}
|
|
|
|
else if(ch == STK_SET_DEVICE_EXT) {
|
|
|
|
// SET DEVICE EXT is ignored
|
|
|
|
getNch(5);
|
|
|
|
}
|
|
|
|
else if(ch == STK_LOAD_ADDRESS) {
|
|
|
|
// LOAD ADDRESS
|
|
|
|
uint16_t newAddress;
|
|
|
|
// Workaround for the infamous ';' bug in the Prusa3D usb to serial converter.
|
|
|
|
// Send the binary data by nibbles to avoid transmitting the ';' character.
|
|
|
|
newAddress = getch();
|
|
|
|
newAddress |= getch();
|
|
|
|
newAddress |= (((uint16_t)getch()) << 8);
|
|
|
|
newAddress |= (((uint16_t)getch()) << 8);
|
|
|
|
// Transfer top bit to LSB in rampz
|
|
|
|
if (newAddress & 0x8000)
|
|
|
|
rampz |= 0x01;
|
|
|
|
else
|
|
|
|
rampz &= 0xFE;
|
|
|
|
newAddress += newAddress; // Convert from word address to byte address
|
|
|
|
address = newAddress;
|
|
|
|
verifySpace();
|
|
|
|
}
|
|
|
|
else if(ch == STK_UNIVERSAL) {
|
|
|
|
// LOAD_EXTENDED_ADDRESS is needed in STK_UNIVERSAL for addressing more than 128kB
|
|
|
|
if ( AVR_OP_LOAD_EXT_ADDR == getch() ) {
|
|
|
|
// get address
|
|
|
|
getch(); // get '0'
|
|
|
|
rampz = (rampz & 0x01) | ((getch() << 1) & 0xff); // get address and put it in rampz
|
|
|
|
getNch(1); // get last '0'
|
|
|
|
// response
|
|
|
|
putch(0x00);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// everything else is ignored
|
|
|
|
getNch(3);
|
|
|
|
putch(0x00);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Write memory, length is big endian and is in bytes */
|
|
|
|
else if(ch == STK_PROG_PAGE) {
|
|
|
|
// PROGRAM PAGE - we support flash programming only, not EEPROM
|
|
|
|
uint8_t desttype;
|
|
|
|
uint8_t *bufPtr;
|
|
|
|
pagelen_t savelength;
|
|
|
|
// Read the page length, with the length transferred each nibble separately to work around
|
|
|
|
// the Prusa's USB to serial infamous semicolon issue.
|
|
|
|
length = ((pagelen_t)getch()) << 8;
|
|
|
|
length |= ((pagelen_t)getch()) << 8;
|
|
|
|
length |= getch();
|
|
|
|
length |= getch();
|
|
|
|
|
|
|
|
savelength = length;
|
|
|
|
// Read the destination type. It should always be 'F' as flash.
|
|
|
|
desttype = getch();
|
|
|
|
|
|
|
|
// read a page worth of contents
|
|
|
|
bufPtr = buff;
|
|
|
|
do *bufPtr++ = getch();
|
|
|
|
while (--length);
|
|
|
|
|
|
|
|
// Read command terminator, start reply
|
|
|
|
verifySpace();
|
|
|
|
if (desttype == 'E') {
|
|
|
|
while (1) ; // Error: wait for WDT
|
|
|
|
} else {
|
|
|
|
uint32_t addr = (((uint32_t)rampz) << 16) | address;
|
|
|
|
// During a single bootloader run, only erase a 64kB block once.
|
|
|
|
// An 8bit bitmask 'pages_erased' covers 512kB of FLASH memory.
|
2019-12-08 06:43:43 +00:00
|
|
|
if ((address == 0) && (pages_erased & (1 << (addr >> 16))) == 0) {
|
2021-03-05 07:51:23 +00:00
|
|
|
xflash_wait_busy();
|
|
|
|
xflash_enable_wr();
|
|
|
|
xflash_block64_erase(addr);
|
2019-12-08 06:43:43 +00:00
|
|
|
pages_erased |= (1 << (addr >> 16));
|
2018-06-14 13:13:21 +00:00
|
|
|
}
|
2021-03-05 07:51:23 +00:00
|
|
|
xflash_wait_busy();
|
|
|
|
xflash_enable_wr();
|
|
|
|
xflash_page_program(addr, buff, savelength);
|
|
|
|
xflash_wait_busy();
|
|
|
|
xflash_disable_wr();
|
2018-06-14 13:13:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Read memory block mode, length is big endian. */
|
|
|
|
else if(ch == STK_READ_PAGE) {
|
|
|
|
uint32_t addr = (((uint32_t)rampz) << 16) | address;
|
2022-07-02 17:28:13 +00:00
|
|
|
pagelen_t i;
|
2018-06-14 13:13:21 +00:00
|
|
|
// Read the page length, with the length transferred each nibble separately to work around
|
|
|
|
// the Prusa's USB to serial infamous semicolon issue.
|
|
|
|
length = ((pagelen_t)getch()) << 8;
|
|
|
|
length |= ((pagelen_t)getch()) << 8;
|
|
|
|
length |= getch();
|
|
|
|
length |= getch();
|
2018-08-02 12:55:38 +00:00
|
|
|
// Read the destination type. It should always be 'F' as flash. It is not checked.
|
|
|
|
(void)getch();
|
2018-06-14 13:13:21 +00:00
|
|
|
verifySpace();
|
2021-03-05 07:51:23 +00:00
|
|
|
xflash_wait_busy();
|
|
|
|
xflash_rd_data(addr, buff, length);
|
2018-06-14 13:13:21 +00:00
|
|
|
for (i = 0; i < length; ++ i)
|
|
|
|
putch(buff[i]);
|
|
|
|
}
|
|
|
|
/* Get device signature bytes */
|
|
|
|
else if(ch == STK_READ_SIGN) {
|
|
|
|
// READ SIGN - return what Avrdude wants to hear
|
|
|
|
verifySpace();
|
2021-03-05 07:51:23 +00:00
|
|
|
putch(XFLASH_SIGNATURE_0);
|
|
|
|
putch(XFLASH_SIGNATURE_1);
|
|
|
|
putch(XFLASH_SIGNATURE_2);
|
2018-06-14 13:13:21 +00:00
|
|
|
}
|
|
|
|
else if (ch == STK_LEAVE_PROGMODE) { /* 'Q' */
|
|
|
|
// Adaboot no-wait mod
|
2021-03-03 14:31:06 +00:00
|
|
|
wdt_enable(WDTO_15MS);
|
2018-06-14 13:13:21 +00:00
|
|
|
verifySpace();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// This covers the response to commands like STK_ENTER_PROGMODE
|
|
|
|
verifySpace();
|
|
|
|
}
|
|
|
|
putch(STK_OK);
|
|
|
|
}
|
|
|
|
}
|