182 lines
12 KiB
C
182 lines
12 KiB
C
/* Shared by the application and the separate Flash bootloader. */
|
||
/* Аппаратный слой: общая RAM между reset, watchdog и SPI EEPROM.
|
||
* Один исходник компилируется и в приложение, и в отдельный Flash-загрузчик.
|
||
* В отличие от store.c этот файл требует настоящих регистров F28335. */
|
||
#include "DSP2833x_Device.h"
|
||
#include "f28335_boot_platform.h"
|
||
#include "f28335_can_boot.h"
|
||
|
||
/* Must be NOINIT in BOTH linkers, outside ROM scratch and application payload. */
|
||
#pragma DATA_SECTION(boot_handoff,".boot_shared")
|
||
/* 8 слов NOINIT по адресу 0x50, одинаково зарезервированных обоими linker-файлами.
|
||
* [0..1] magic; [2] запрошенная session; [3] запущенный слот; [4..6] служебные
|
||
* поля; [7] XOR-контроль. Это кратковременная передача контекста через watchdog
|
||
* reset, а не замена энергонезависимого журнала EEPROM. */
|
||
static volatile unsigned int boot_handoff[8];
|
||
/* Проверяем magic и XOR прежде, чем трактовать RAM как запрос входа в boot.
|
||
* После включения питания содержимое NOINIT может быть произвольным. */
|
||
static unsigned int handoff_valid(void)
|
||
{
|
||
unsigned int i,sum=0;
|
||
for(i=0;i<8;++i) sum^=boot_handoff[i];
|
||
return boot_handoff[0]==0xB283 && boot_handoff[1]==0x4D7C && sum==0xA95B;
|
||
}
|
||
/* Сначала снимаем признак действительности, затем заполняем поля и контроль.
|
||
* Первый magic записываем последним; volatile сохраняет обращения к общей RAM.
|
||
* request=0 означает обычный запуск, ненулевая session — явный ENTER_BOOT. */
|
||
static void handoff_set(unsigned int request,unsigned int running)
|
||
{
|
||
unsigned int i,sum=0;
|
||
boot_handoff[0]=0; boot_handoff[1]=0x4D7C;
|
||
boot_handoff[2]=request; boot_handoff[3]=running;
|
||
boot_handoff[4]=0xF335; boot_handoff[5]=0x0CCA; boot_handoff[6]=1;
|
||
sum=0xB283;
|
||
for(i=1;i<7;++i) sum^=boot_handoff[i];
|
||
boot_handoff[7]=sum^0xA95B;
|
||
boot_handoff[0]=0xB283; /* publish last */
|
||
}
|
||
/* Приложение узнаёт свой слот из handoff, а не предполагает, что оно active:
|
||
* во время пробного запуска выполняется pending, active ещё указывает на старый образ. */
|
||
unsigned int F28335Boot_RunningSlot(void)
|
||
{ return handoff_valid()?boot_handoff[3]:F28335_AB_NONE; }
|
||
unsigned int F28335Boot_RequestedSession(void)
|
||
{ return handoff_valid() && boot_handoff[2]<256?boot_handoff[2]:0; }
|
||
/* Одновременно публикуем выбранный слот и потребляем запрос ENTER_BOOT:
|
||
* он не должен повторяться при следующем обычном reset. */
|
||
void F28335Boot_SetRunning(unsigned int slot) { handoff_set(0,slot); }
|
||
/* Timer1 должен давать 1 мс на InterruptCount и в приложении, и в загрузчике.
|
||
* Тайм-ауты используют разность unsigned-счётчиков для переноса через wrap. */
|
||
uint32_t F28335Boot_Now(void) { return CpuTimer1.InterruptCount; }
|
||
/* Вызвать сразу после InitSysCtrl, который штатно отключает watchdog.
|
||
* Сбрасываем разрешение кормления из ISR до полной инициализации CAN-сервиса.
|
||
* Для приложения, запущенного нашим загрузчиком, включаем watchdog с делителем. */
|
||
void F28335Boot_StartupWatchdog(void)
|
||
{
|
||
F28335CanBoot_EarlyInit();
|
||
if(F28335Boot_RunningSlot()<2) {
|
||
EALLOW; SysCtrlRegs.WDKEY=0x55; SysCtrlRegs.WDKEY=0xAA;
|
||
SysCtrlRegs.WDCR=0x002F; EDIS;
|
||
}
|
||
}
|
||
/* Платформенный Quiesce останавливает управление выходами перед reset.
|
||
* Session сохраняется до включения watchdog; ненулевую session загрузчик
|
||
* использует для отложенного ACK ENTER_BOOT. В бесконечном цикле watchdog
|
||
* намеренно не обслуживается — выход из функции должен произойти через reset. */
|
||
void F28335Boot_Reset(unsigned int session)
|
||
{
|
||
F28335Boot_Quiesce();
|
||
DINT;
|
||
handoff_set(session,F28335_AB_NONE);
|
||
EALLOW; SysCtrlRegs.WDCR=0x0028; EDIS;
|
||
for(;;) { }
|
||
}
|
||
|
||
/* Main context only. Legacy Timer2 EEPROM driver must be idle/stopped. Save
|
||
* its SPI configuration: it changes the character width during transactions. */
|
||
typedef struct { unsigned int ccr,ctl,brr,tx,rx,ct,pri; } boot_spi_saved;
|
||
static void spi_cs(unsigned int high)
|
||
{ if(high) GpioDataRegs.GPASET.bit.GPIO19=1; else GpioDataRegs.GPACLEAR.bit.GPIO19=1; }
|
||
/* Доступ разрешён только из main при остановленном Timer2 старого SPI-драйвера.
|
||
* Это проверка занятости в однопоточном приложении, не универсальный mutex.
|
||
* Сохраняем режим SPI, поскольку штатный драйвер меняет разрядность передачи.
|
||
* CS сначала поднимаем, затем переключаем SPI на 8-битные транзакции. */
|
||
static bool spi_take(boot_spi_saved *s)
|
||
{
|
||
volatile unsigned int discard;
|
||
if(!CpuTimer2Regs.TCR.bit.TSS) return false;
|
||
s->ccr=SpiaRegs.SPICCR.all; s->ctl=SpiaRegs.SPICTL.all; s->brr=SpiaRegs.SPIBRR;
|
||
s->tx=SpiaRegs.SPIFFTX.all; s->rx=SpiaRegs.SPIFFRX.all;
|
||
s->ct=SpiaRegs.SPIFFCT.all; s->pri=SpiaRegs.SPIPRI.all;
|
||
EALLOW;
|
||
GpioCtrlRegs.GPAMUX2.bit.GPIO16=1; GpioCtrlRegs.GPAMUX2.bit.GPIO17=1;
|
||
GpioCtrlRegs.GPAMUX2.bit.GPIO18=1; GpioCtrlRegs.GPAMUX2.bit.GPIO19=0;
|
||
GpioDataRegs.GPASET.bit.GPIO19=1; GpioCtrlRegs.GPADIR.bit.GPIO19=1;
|
||
GpioCtrlRegs.GPAQSEL2.bit.GPIO17=3;
|
||
EDIS;
|
||
SpiaRegs.SPICCR.all=7; SpiaRegs.SPICTL.all=0x0E; /* master, phase=1, IRQ off */
|
||
SpiaRegs.SPIBRR=CLKMULT*6+1;
|
||
SpiaRegs.SPIFFTX.all=0x8000; SpiaRegs.SPIFFRX.all=0; SpiaRegs.SPIFFCT.all=0;
|
||
SpiaRegs.SPIPRI.all=0x10; SpiaRegs.SPICCR.all=0x87;
|
||
/* Снимаем оставшийся RX/INT_FLAG от прежней операции, чтобы первый обмен
|
||
* не принял старое содержимое RXBUF за ответ EEPROM. */
|
||
discard=SpiaRegs.SPIRXBUF; (void)discard;
|
||
return true;
|
||
}
|
||
/* Выход и при успехе, и при ошибке: CS не должен остаться активным.
|
||
* Настройки возвращаются при удержании SPI в reset; исходный SPISWRESET
|
||
* восстанавливается последним, после остальных регистров. */
|
||
static void spi_release(const boot_spi_saved *s)
|
||
{
|
||
volatile unsigned int discard;
|
||
spi_cs(1); discard=SpiaRegs.SPIRXBUF; (void)discard;
|
||
SpiaRegs.SPICCR.all=s->ccr&0x7FU; SpiaRegs.SPICTL.all=s->ctl;
|
||
SpiaRegs.SPIBRR=s->brr; SpiaRegs.SPIFFTX.all=s->tx; SpiaRegs.SPIFFRX.all=s->rx;
|
||
SpiaRegs.SPIFFCT.all=s->ct; SpiaRegs.SPIPRI.all=s->pri; SpiaRegs.SPICCR.all=s->ccr;
|
||
}
|
||
/* У F28335 8-битный символ для передачи выравнивается в старшие биты TXBUF.
|
||
* Полученный октет читается из младших битов RXBUF. Счётчик budget ограничивает
|
||
* ожидание даже при неработающем Timer1; это число итераций, а не миллисекунды. */
|
||
static bool spi_octet(unsigned int tx,unsigned int *rx)
|
||
{
|
||
uint32_t budget=100000UL;
|
||
SpiaRegs.SPITXBUF=(tx&255U)<<8;
|
||
while(!SpiaRegs.SPISTS.bit.INT_FLAG) if(--budget==0) return false;
|
||
*rx=SpiaRegs.SPIRXBUF&255U; return true;
|
||
}
|
||
/* Команда EEPROM и 24-битный байтовый адрес, старший октет адреса первым.
|
||
* CS удерживается вызывающей функцией на всю команду и полезные данные. */
|
||
static bool spi_prefix(unsigned int command,uint32_t address)
|
||
{
|
||
unsigned int discard;
|
||
return spi_octet(command,&discard) && spi_octet((unsigned int)(address>>16),&discard) &&
|
||
spi_octet((unsigned int)(address>>8),&discard) && spi_octet((unsigned int)address,&discard);
|
||
}
|
||
/* RDSR (05h): WIP в бите 0 означает незавершённую внутреннюю запись EEPROM.
|
||
* Ожидание ограничено и 50 мс, и числом опросов на случай остановки tick.
|
||
* После каждого чтения статуса CS снимается, в том числе при ошибке SPI. */
|
||
static bool spi_ready(void)
|
||
{
|
||
unsigned int status,discard; uint32_t started=F28335Boot_Now(),budget=20000UL;
|
||
do {
|
||
spi_cs(0);
|
||
if(!spi_octet(5,&discard) || !spi_octet(255,&status)) { spi_cs(1); return false; }
|
||
spi_cs(1);
|
||
if(!(status&1U)) return true;
|
||
} while(--budget && (uint32_t)(F28335Boot_Now()-started)<50UL);
|
||
return false;
|
||
}
|
||
/* Читать разрешено всю физическую EEPROM, включая параметры. Проверка
|
||
* через вычитание защищает от переполнения адреса. spi_release обязателен
|
||
* после успешного захвата SPI независимо от результата последующего обмена. */
|
||
bool F28335Boot_Read(void *u,uint32_t address,pcan_boot_byte_t *p,unsigned int n)
|
||
{
|
||
boot_spi_saved saved; unsigned int i,value; bool ok; (void)u;
|
||
if(address>0x40000UL || n>0x40000UL-address || !spi_take(&saved)) return false;
|
||
ok=spi_ready();
|
||
spi_cs(0); if(ok) ok=spi_prefix(3,address);
|
||
for(i=0;ok && i<n;++i) { ok=spi_octet(255,&value); p[i]=value; }
|
||
spi_release(&saved); return ok;
|
||
}
|
||
/* Нижний аппаратный барьер записи: разрешены журнал и слоты до 0x3FE00.
|
||
* Резерв, параметры и последнее слово нельзя изменить даже ошибочным вызовом
|
||
* этой функции. Защита активного слота дополнительно выполняется в store.c. */
|
||
bool F28335Boot_Write(void *u,uint32_t address,const pcan_boot_byte_t *p,unsigned int n)
|
||
{
|
||
boot_spi_saved saved; unsigned int i,chunk,discard; bool ok; (void)u;
|
||
/* This driver cannot overwrite parameters, even if a caller is defective. */
|
||
if(address>0x3FE00UL || n>0x3FE00UL-address || !spi_take(&saved)) return false;
|
||
ok=spi_ready();
|
||
while(ok && n) {
|
||
/* Нельзя пересекать страницу одной командой WRITE: EEPROM может завернуть
|
||
* адрес внутри страницы. Для каждого фрагмента заново выдаём WREN (06h),
|
||
* WRITE (02h) и ждём снятия WIP. Сравнение readback выполняет верхний слой. */
|
||
chunk=256U-(unsigned int)(address&255U); if(chunk>n) chunk=n;
|
||
spi_cs(0); ok=spi_octet(6,&discard); spi_cs(1); /* WREN */
|
||
spi_cs(0); if(ok) ok=spi_prefix(2,address);
|
||
for(i=0;ok && i<chunk;++i) ok=spi_octet(p[i],&discard);
|
||
spi_cs(1); if(ok) ok=spi_ready();
|
||
n-=chunk; p+=chunk; address+=chunk;
|
||
}
|
||
spi_release(&saved); return ok;
|
||
}
|