initial check in based on SVN revision 575
This commit is contained in:
200
source/Loader/bootloader.c
Normal file
200
source/Loader/bootloader.c
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* bootloader.c
|
||||
*
|
||||
* Created on: Oct 22, 2023
|
||||
* Author: Brian.Bailey
|
||||
*/
|
||||
|
||||
#include <Loader/bootloader.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lcd.h"
|
||||
#include "Fonts/fontLibrary.h"
|
||||
#include "timer.h"
|
||||
#include "utils.h"
|
||||
#include "keys.h"
|
||||
#include "init.h"
|
||||
#include "usbComms.h"
|
||||
|
||||
#include "fsl_power.h"
|
||||
#include "virtual_com.h"
|
||||
#include "sha256.h"
|
||||
|
||||
// #include "bootloader.h"
|
||||
#include "Loader/iap_jump.h"
|
||||
#include "eeprom.h"
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Definitions
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Variables
|
||||
******************************************************************************/
|
||||
|
||||
BL_DATA_t blData;
|
||||
|
||||
extern uint8_t tempString[40];
|
||||
/*******************************************************************************
|
||||
* Static Function Declarations
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Static Functions
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Public Functions
|
||||
******************************************************************************/
|
||||
|
||||
void BL_Run(void)
|
||||
{
|
||||
Power_ON_OFF(ON); // Hold power on.
|
||||
Select_Estop(ON); // Ensure output is ISOLATED from connections
|
||||
SPI0_Chip_Select(CS_HIGH,E2PROM); // Set EEPROM as SPI CS
|
||||
KEY_Init();
|
||||
SPI_Init();
|
||||
|
||||
//Read bootloader EEPROM data to the blData structure
|
||||
//Read the EEPROM
|
||||
// blData.version = 1;
|
||||
// blData.loaderRunRequest = 0;
|
||||
// blData.transmitterProgrammed = 1;
|
||||
#if 0 //for testing blank EEPROM
|
||||
blData.version = 0xffffffff;
|
||||
blData.loaderRunRequest = 0xffffffff;
|
||||
blData.transmitterProgrammed = 0xffffffff;
|
||||
M95512_WriteMemoryUINT32(BL_VERSION_EEPROM_ADDR, &blData.version);
|
||||
M95512_WriteMemoryUINT32(BL_LOADERRUNREQUEST_EEPROM_ADDR, &blData.loaderRunRequest);
|
||||
M95512_WriteMemoryUINT32(BL_PROGRAMMED_EEPROM_ADDR, &blData.transmitterProgrammed);
|
||||
|
||||
#else
|
||||
EE_ReadMemoryUINT32(BL_VERSION_EEPROM_ADDR, &blData.version);
|
||||
EE_ReadMemoryUINT32(BL_LOADERRUNREQUEST_EEPROM_ADDR, &blData.loaderRunRequest);
|
||||
EE_ReadMemoryUINT32(BL_PROGRAMMED_EEPROM_ADDR, &blData.transmitterProgrammed);
|
||||
|
||||
|
||||
if(blData.version > BL_MAX_VERSION) //Check for bootloader FLASH uninitialized. if uninitialized, this will be 0xffff
|
||||
{
|
||||
//Zero out Bootloader EEPROM data
|
||||
blData.loaderRunRequest = 0;
|
||||
blData.transmitterProgrammed = 0;
|
||||
EE_WriteMemoryUINT32(BL_LOADERRUNREQUEST_EEPROM_ADDR, &blData.loaderRunRequest);
|
||||
EE_WriteMemoryUINT32(BL_PROGRAMMED_EEPROM_ADDR, &blData.transmitterProgrammed);
|
||||
//Set correct BL VERSION in EEPROM
|
||||
blData.version = BL_VERSION;
|
||||
EE_WriteMemoryUINT32(BL_VERSION_EEPROM_ADDR, &blData.version);
|
||||
|
||||
//TODO: Maybe need to read back,verify, and add error information
|
||||
EE_ReadMemoryUINT32(BL_VERSION_EEPROM_ADDR, &blData.version);
|
||||
EE_ReadMemoryUINT32(BL_LOADERRUNREQUEST_EEPROM_ADDR, &blData.loaderRunRequest);
|
||||
EE_ReadMemoryUINT32(BL_PROGRAMMED_EEPROM_ADDR, &blData.transmitterProgrammed);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!blData.transmitterProgrammed) //If NOT programmed, return to run loader
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(blData.loaderRunRequest) //If loaderRunRequest, return to run loader
|
||||
{
|
||||
//Clear the loader run request
|
||||
blData.loaderRunRequest = 0;
|
||||
EE_WriteMemoryUINT32(BL_LOADERRUNREQUEST_EEPROM_ADDR, &blData.loaderRunRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//if FREQ and UP keys are held, return to run the loader screen
|
||||
if(KEY_GetUpKeyHeld() && KEY_GetFrequencyKeyHeld())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Jump to the application at BL_APP_IMAGE_ADDR
|
||||
IAP_JumpToApp(BL_APP_IMAGE_ADDR);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void BL_ShowScreen(void)
|
||||
{
|
||||
float32_t timer = 0.0;
|
||||
|
||||
//Init some things
|
||||
timer_init(); //Needed for Delay_ticks
|
||||
Init_Ports(); //make sure port expander outputs are in a safe state
|
||||
LCD_Init();
|
||||
|
||||
|
||||
//Update screen @ 10Hz and listen for USB data
|
||||
while(1)
|
||||
{
|
||||
uint32_t xText = 20;
|
||||
uint32_t yText = 30;
|
||||
uint32_t yInc = 10;
|
||||
|
||||
LCD_Clear();
|
||||
|
||||
//Draw Text to LCD
|
||||
sprintf(tempString, "TX Loader v%d", BL_VERSION);
|
||||
FL_DrawString(tempString, LCD_X_MID, 0, font12Bold, LCD_DRAW_SET, FL_ALIGN_CENTER);
|
||||
|
||||
|
||||
|
||||
|
||||
//TX Programmed
|
||||
if(true == blData.transmitterProgrammed)
|
||||
{
|
||||
FL_DrawString("TX Programmed", xText, yText, font12Bold, LCD_DRAW_SET, FL_ALIGN_LEFT);
|
||||
}
|
||||
else
|
||||
{
|
||||
FL_DrawString("TX NOT Programmed", xText, yText, font12Bold, LCD_DRAW_SET, FL_ALIGN_LEFT);
|
||||
}
|
||||
yText += yInc;
|
||||
yText += yInc;
|
||||
|
||||
FL_DrawString("Ready for Programming...", xText, yText, font12Bold, LCD_DRAW_SET, FL_ALIGN_LEFT);
|
||||
yText += yInc;
|
||||
|
||||
//show time
|
||||
timer += 0.1;
|
||||
sprintf(tempString, "%0.1fs", timer);
|
||||
FL_DrawString(tempString, 0, LCD_Y_MAX - FL_GetFontHeight(font12Bold), font12Bold, LCD_DRAW_SET, FL_ALIGN_LEFT);
|
||||
|
||||
LCD_Update();
|
||||
|
||||
//Power off if user presses power key
|
||||
if(KEY_GetPowerKeyHeld())
|
||||
{
|
||||
Power_ON_OFF(OFF);
|
||||
while(1); //wait for power off
|
||||
}
|
||||
|
||||
Delay_Ticks(10); //100mS delay
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read bootloader data from EEPROM
|
||||
* Have seen issue where first EEPROM read is garbage. throw the first read away.
|
||||
*/
|
||||
void BL_ReadInfo(void)
|
||||
{
|
||||
EE_ReadMemoryUINT32(BL_VERSION_EEPROM_ADDR, &blData.version);
|
||||
EE_ReadMemoryUINT32(BL_LOADERRUNREQUEST_EEPROM_ADDR, &blData.loaderRunRequest);
|
||||
EE_ReadMemoryUINT32(BL_PROGRAMMED_EEPROM_ADDR, &blData.transmitterProgrammed);
|
||||
}
|
||||
|
||||
|
||||
|
||||
44
source/Loader/bootloader.h
Normal file
44
source/Loader/bootloader.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* bootloader.h
|
||||
*
|
||||
* Created on: Oct 22, 2023
|
||||
* Author: Brian.Bailey
|
||||
*/
|
||||
|
||||
#ifndef LOADER_BOOTLOADER_H_
|
||||
#define LOADER_BOOTLOADER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "bootloader.h"
|
||||
|
||||
|
||||
#define BL_VERSION 1 //Bootloader Version (integer)
|
||||
#define BL_MAX_VERSION 255 //Max version - otherwise FLASH reads as uninitialized
|
||||
|
||||
//Locations for bootable image components
|
||||
//Update these for transmitter.
|
||||
#define BL_BOOTLOADER_IMAGE_ADDR 0x00000000 //base address for the bootloader image in FLASH
|
||||
#define BL_APP_IMAGE_ADDR 0x00010000 //base address for the application image in FLASH
|
||||
|
||||
//Bootloader EEPROM Addresses
|
||||
#define BL_RESERVED 0x0000 //Don't use address 0x0
|
||||
#define BL_VERSION_EEPROM_ADDR 0x0004
|
||||
#define BL_LOADERRUNREQUEST_EEPROM_ADDR 0x0008
|
||||
#define BL_PROGRAMMED_EEPROM_ADDR 0x000C
|
||||
//First 128 addresses (32 entries) in EEPROM are reserved for the bootloader
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t version; //bootloader version (for application to read)
|
||||
uint32_t loaderRunRequest; //loader run request for program update
|
||||
uint32_t transmitterProgrammed; //Transmitter has been programmed
|
||||
}BL_DATA_t;
|
||||
|
||||
void BL_Run(void);
|
||||
void BL_ShowScreen(void);
|
||||
|
||||
void BL_ReadInfo(void); //for application
|
||||
|
||||
#endif /* LOADER_BOOTLOADER_H_ */
|
||||
343
source/Loader/flashUpdate.c
Normal file
343
source/Loader/flashUpdate.c
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* flashUpdate.c
|
||||
*
|
||||
* Created on: Oct 23, 2023
|
||||
* Author: Brian.Bailey
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "usbComms.h"
|
||||
#include "sha256.h"
|
||||
|
||||
#include "flashUpdate.h"
|
||||
|
||||
#include "fsl_common.h"
|
||||
#include "fsl_iap.h"
|
||||
|
||||
#include "bootloader.h"
|
||||
#include "eeprom.h"
|
||||
/*******************************************************************************
|
||||
* Definitions
|
||||
******************************************************************************/
|
||||
|
||||
#define null 0 //NULL is defined at (void *)0 so it causes errors
|
||||
|
||||
//sector0~2 for bootloader, sector3~7 for APP
|
||||
#define IAP_FLASH_SECTOR_START (3)
|
||||
#define IAP_FLASH_SECTOR_END (7)
|
||||
// #define IAP_FLASH_SECTOR_END (3) //for test
|
||||
|
||||
/*******************************************************************************
|
||||
* Variables
|
||||
******************************************************************************/
|
||||
FLASH_UPDATE_t fu;
|
||||
extern USB_t usb;
|
||||
extern BL_DATA_t blData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Static Function Declarations
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Static Functions
|
||||
******************************************************************************/
|
||||
|
||||
/*
|
||||
* Convert the source string to bytes & store in *dst
|
||||
* Converts 32 ASCII chars to 16 bytes
|
||||
*/
|
||||
static void ConvertStringToHalfHash(uint8_t *src, uint8_t *dst)
|
||||
{
|
||||
//32 ASCII chars to 16 bytes
|
||||
for(uint32_t i = 0; i < SHA256_BLOCK_SIZE/2; i++)
|
||||
{
|
||||
//sscanf(&src[i*2], "%02hhX", &dst[i]); //This works in the RX but has problems when ASCII is > 127?
|
||||
|
||||
uint8_t temp[3];
|
||||
temp[0] = src[i*2];
|
||||
temp[1] = src[i*2+1];
|
||||
temp[3] = 0;
|
||||
|
||||
dst[i] = strtol(temp, NULL, 16);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Convert uint8 to uint32.
|
||||
*
|
||||
* @param len length of arr8.
|
||||
*/
|
||||
static void ConvertUint8ToUint32(uint8_t *arr8, uint32_t *arr32, uint32_t len) {
|
||||
|
||||
uint32_t temp;
|
||||
for (uint32_t i = 0; i < FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES; i++)
|
||||
{
|
||||
*(((uint8_t *)arr32) + i) = 0xFF;
|
||||
}
|
||||
//get the length of arr32
|
||||
if(len % 4){
|
||||
len /= 4;
|
||||
len += 1;
|
||||
}
|
||||
else{
|
||||
len /= 4;
|
||||
}
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
temp = (uint32_t)arr8[3] << 24;
|
||||
temp |= (uint32_t)arr8[2] << 16;
|
||||
temp |= (uint32_t)arr8[1] << 8;
|
||||
temp |= (uint32_t)arr8[0];
|
||||
arr8 += 4;
|
||||
arr32[i] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Public Functions
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/* Setup for FLASH programming
|
||||
* /arg *data: csv string: pProgramSetup,<numBytes>,<hash>
|
||||
* Use program length to calculate number of FLASH sectors to erase
|
||||
* Save hash, program size, and calculated parameters
|
||||
* Set usb.programMode
|
||||
* Send ACK / NAK
|
||||
*/
|
||||
void FU_ProgramSetup(uint8_t *data)
|
||||
{
|
||||
//reset variables
|
||||
fu.imageReceived = false;
|
||||
fu.flashHashMatch = false;
|
||||
fu.numBytesToProgram = 0;
|
||||
fu.numBytesProgrammed = 0;
|
||||
|
||||
bool error = false;
|
||||
|
||||
//Parse Program message
|
||||
static uint8_t *p; //pointer for strtok - static so it doesn't get optimized out.
|
||||
p = strtok(data, ","); //points to "pProgramSetup"
|
||||
p = strtok(null, ","); //number of bytes in the image
|
||||
fu.numBytesToProgram = atoi(p);
|
||||
|
||||
p = strtok(null, ","); //hash - sent as string
|
||||
ConvertStringToHalfHash(p, fu.hostHash);
|
||||
|
||||
//clear programmed flag in EEPROM
|
||||
blData.transmitterProgrammed = 0;
|
||||
EE_WriteMemoryUINT32(BL_PROGRAMMED_EEPROM_ADDR, &blData.transmitterProgrammed);
|
||||
|
||||
//Erase APP flash
|
||||
//USB_SendString("Erasing flash..."); //or to LCD
|
||||
IAP_PrepareSectorForWrite(IAP_FLASH_SECTOR_START, IAP_FLASH_SECTOR_END);
|
||||
IAP_EraseSector(IAP_FLASH_SECTOR_START, IAP_FLASH_SECTOR_END, SystemCoreClock);
|
||||
//USB_SendString("Erase flash completed"); //OR TO LCD
|
||||
status_t status;
|
||||
status = IAP_BlankCheckSector(IAP_FLASH_SECTOR_START, IAP_FLASH_SECTOR_END);
|
||||
if (status != kStatus_IAP_Success)
|
||||
{
|
||||
error = 1;
|
||||
// PRINTF("\r\nSector erase failed\r\n"); //to LCD
|
||||
}
|
||||
|
||||
//Initialize fu
|
||||
memset(fu.rxPageData, 0xff, sizeof(fu.rxPageData));
|
||||
fu.flashProgramTargetAddress = BL_APP_IMAGE_ADDR;
|
||||
|
||||
|
||||
if(error)
|
||||
{
|
||||
USB_SendString(NAK);
|
||||
}
|
||||
else
|
||||
{
|
||||
usb.programMode = true;
|
||||
USB_SendString(ACK);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write received data to FLASH using fu struct information
|
||||
* This function is called repeatedly to write program FLASH
|
||||
* When all data received, function sends ACK or NACK via USB
|
||||
*/
|
||||
void FU_WriteProgramDataToFLASH(void)
|
||||
{
|
||||
bool error = false;
|
||||
|
||||
//Verify packet: check header and check that size matches
|
||||
if (USB_PACKET_START_BYTE != usb.rxDataBuffer[USB_PACKET_HEADER_OFFSET])
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
uint32_t packetSize = usb.rxDataBuffer[USB_PACKET_LENGTH_H_OFFSET] * 256; //high byte
|
||||
packetSize += usb.rxDataBuffer[USB_PACKET_LENGTH_L_OFFSET]; //low byte
|
||||
|
||||
if(packetSize != usb.rxDataIndex) //check that computed packet size matches number of bytes received
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
|
||||
//Write data to FLASH
|
||||
//you can use packetSize. It is the size of the data in bytes
|
||||
//Best to wait for a whole page or sector of data before writing to FLASH.
|
||||
//faster this way
|
||||
uint32_t rxData[64];
|
||||
|
||||
if(!error){
|
||||
//Judge whether it is the last packet
|
||||
if((packetSize - USB_PACKET_HEADER_SIZE + fu.numBytesProgrammed) == fu.numBytesToProgram)
|
||||
{
|
||||
//Judge whether the page is going to be full
|
||||
if((fu.numBytesInPageData + packetSize - USB_PACKET_HEADER_SIZE) >= FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES)
|
||||
{
|
||||
uint16_t numBytesCurrentPage;
|
||||
numBytesCurrentPage = FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES - fu.numBytesInPageData;
|
||||
memcpy(&fu.rxPageData[fu.numBytesInPageData], &usb.rxDataBuffer[USB_PACKET_PAYLOAD_OFFSET], numBytesCurrentPage);
|
||||
|
||||
ConvertUint8ToUint32(fu.rxPageData, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES);
|
||||
IAP_PrepareSectorForWrite(IAP_FLASH_SECTOR_START, IAP_FLASH_SECTOR_END);
|
||||
IAP_CopyRamToFlash(fu.flashProgramTargetAddress, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES, SystemCoreClock);
|
||||
fu.flashProgramTargetAddress += FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES;
|
||||
|
||||
//start the next FLASH page
|
||||
memset(fu.rxPageData, 0xff, sizeof(fu.rxPageData));
|
||||
fu.numBytesInPageData = 0;
|
||||
memcpy(&fu.rxPageData[fu.numBytesInPageData], &usb.rxDataBuffer[USB_PACKET_PAYLOAD_OFFSET + numBytesCurrentPage], (packetSize - USB_PACKET_HEADER_SIZE - numBytesCurrentPage));
|
||||
fu.numBytesInPageData += (packetSize - USB_PACKET_HEADER_SIZE - numBytesCurrentPage);
|
||||
|
||||
//write to FLASH
|
||||
ConvertUint8ToUint32(fu.rxPageData, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES);
|
||||
IAP_PrepareSectorForWrite(IAP_FLASH_SECTOR_START, IAP_FLASH_SECTOR_END);
|
||||
IAP_CopyRamToFlash(fu.flashProgramTargetAddress, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES, SystemCoreClock);
|
||||
fu.flashProgramTargetAddress += FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES;
|
||||
}
|
||||
else
|
||||
{
|
||||
//write the final page to FLASH
|
||||
memcpy(&fu.rxPageData[fu.numBytesInPageData], &usb.rxDataBuffer[USB_PACKET_PAYLOAD_OFFSET], (packetSize - USB_PACKET_HEADER_SIZE)); //copy packet to fu.rxPageData
|
||||
fu.numBytesInPageData += (packetSize - USB_PACKET_HEADER_SIZE);
|
||||
|
||||
ConvertUint8ToUint32(fu.rxPageData, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES);
|
||||
IAP_PrepareSectorForWrite(IAP_FLASH_SECTOR_START, IAP_FLASH_SECTOR_END);
|
||||
IAP_CopyRamToFlash(fu.flashProgramTargetAddress, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES, SystemCoreClock);
|
||||
fu.flashProgramTargetAddress += FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES;
|
||||
}
|
||||
}
|
||||
else{
|
||||
//Judge whether the page is going to be full
|
||||
if((fu.numBytesInPageData + packetSize - USB_PACKET_HEADER_SIZE) >= FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES){
|
||||
uint16_t numBytesCurrentPage;
|
||||
numBytesCurrentPage = FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES - fu.numBytesInPageData;
|
||||
memcpy(&fu.rxPageData[fu.numBytesInPageData], &usb.rxDataBuffer[USB_PACKET_PAYLOAD_OFFSET], numBytesCurrentPage);
|
||||
|
||||
ConvertUint8ToUint32(fu.rxPageData, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES);
|
||||
IAP_PrepareSectorForWrite(IAP_FLASH_SECTOR_START, IAP_FLASH_SECTOR_END);
|
||||
IAP_CopyRamToFlash(fu.flashProgramTargetAddress, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES, SystemCoreClock);
|
||||
fu.flashProgramTargetAddress += FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES;
|
||||
|
||||
//start the next FLASH page
|
||||
memset(fu.rxPageData, 0xff, sizeof(fu.rxPageData));
|
||||
fu.numBytesInPageData = 0;
|
||||
memcpy(&fu.rxPageData[fu.numBytesInPageData], &usb.rxDataBuffer[USB_PACKET_PAYLOAD_OFFSET + numBytesCurrentPage], (packetSize - USB_PACKET_HEADER_SIZE - numBytesCurrentPage));
|
||||
fu.numBytesInPageData += (packetSize - USB_PACKET_HEADER_SIZE - numBytesCurrentPage);
|
||||
}
|
||||
else{
|
||||
memcpy(&fu.rxPageData[fu.numBytesInPageData], &usb.rxDataBuffer[USB_PACKET_PAYLOAD_OFFSET], (packetSize - USB_PACKET_HEADER_SIZE));
|
||||
fu.numBytesInPageData += (packetSize - USB_PACKET_HEADER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
fu.numBytesProgrammed += packetSize - USB_PACKET_HEADER_SIZE;
|
||||
}
|
||||
|
||||
//Test for all data transferred
|
||||
if(fu.numBytesProgrammed >= fu.numBytesToProgram)
|
||||
{
|
||||
usb.programMode = false;
|
||||
fu.imageReceived = true;
|
||||
}
|
||||
|
||||
if(error)
|
||||
{
|
||||
USB_SendString(NAK);
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SendString(ACK);
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify FLASH image using sha256 hash
|
||||
* Compute sha256 hash of program image
|
||||
* Compare to fu.hostHash
|
||||
*/
|
||||
void FU_VerifyImage()
|
||||
{
|
||||
//TODO: Compute sha256 hash of program image from FLASH
|
||||
static uint32_t dstAddr = BL_APP_IMAGE_ADDR;
|
||||
uint8_t rxData[FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES];
|
||||
uint32_t i = 0;
|
||||
SHA256_CTX ctx;
|
||||
|
||||
/* EMAMPLE: (Also see sha256_test())
|
||||
* SHA256_CTX ctx;
|
||||
* sha256_init(&ctx); //initialize
|
||||
* sha256_update(&ctx, data, dataLength); //call this for each block of data
|
||||
* sha256_final(&ctx, fu.flashHash); //finish
|
||||
*/
|
||||
|
||||
sha256_init(&ctx);
|
||||
for(i = 0; i < (fu.numBytesToProgram / FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES); i++){
|
||||
//read flash
|
||||
for(uint32_t j = 0; j < FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES; j++) //read 256 bytes from FLASH to rxData
|
||||
{
|
||||
rxData[j] = *(uint8_t *)(dstAddr + FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES * i + j);
|
||||
}
|
||||
//calculate sha256
|
||||
// sha256_update(&ctx, rxData, strlen((char *)rxData));
|
||||
sha256_update(&ctx, rxData, FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES);
|
||||
}
|
||||
for(uint32_t j = 0; j < (fu.numBytesToProgram % FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES); j++){
|
||||
rxData[j] = *(uint8_t *)(dstAddr + FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES * i + j);
|
||||
}
|
||||
sha256_update(&ctx, rxData, (fu.numBytesToProgram % FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES));
|
||||
sha256_final(&ctx, fu.flashHash);
|
||||
|
||||
//Compare fu.flashHash with fu.hostHash - FIRST HALF ONLY!
|
||||
if(0 == memcmp(fu.hostHash, fu.flashHash, SHA256_BLOCK_SIZE/2)) //Failed, I don't know the reason.
|
||||
// if(1) //for test, bypass verification and it works
|
||||
{
|
||||
//set programmed flag in EEPROM
|
||||
blData.transmitterProgrammed = true;
|
||||
EE_WriteMemoryUINT32(BL_PROGRAMMED_EEPROM_ADDR, &blData.transmitterProgrammed);
|
||||
USB_SendString(ACK);
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SendString(NAK);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Reset the processor
|
||||
* Works even if in ISR
|
||||
*/
|
||||
void FU_ResetProcessor(void)
|
||||
{
|
||||
//Disable interrupts.
|
||||
__disable_irq();
|
||||
|
||||
// Memory barriers for good measure.
|
||||
__ISB();
|
||||
__DSB();
|
||||
|
||||
//Reset
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
33
source/Loader/flashUpdate.h
Normal file
33
source/Loader/flashUpdate.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* flashUpdate.h
|
||||
*
|
||||
* Created on: Oct 23, 2023
|
||||
* Author: Brian.Bailey
|
||||
*/
|
||||
|
||||
#ifndef LOADER_FLASHUPDATE_H_
|
||||
#define LOADER_FLASHUPDATE_H_
|
||||
|
||||
#include "sha256.h"
|
||||
#include "LPC54114_cm4_features.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t numBytesToProgram; //number of bytes to burn to FLASH
|
||||
uint32_t flashProgramTargetAddress; //Target address in FLASH to write program
|
||||
uint32_t numBytesProgrammed; //number of bytes programmed to FLASH
|
||||
uint8_t hostHash[SHA256_BLOCK_SIZE]; //SHA256 hash of program data from host
|
||||
uint8_t flashHash[SHA256_BLOCK_SIZE]; //SHA256 hash computed from data in FLASH
|
||||
bool imageReceived; //Entire program image received (got the number of bytes)
|
||||
bool flashHashMatch; //flashHash matches hostHash
|
||||
uint8_t rxPageData[FSL_FEATURE_SYSCON_FLASH_PAGE_SIZE_BYTES]; //save packets data when the number is less then one page
|
||||
uint16_t numBytesInPageData; //number of bytes saved in rxPageData
|
||||
bool programCompleted; //Entire program completed
|
||||
}FLASH_UPDATE_t;
|
||||
|
||||
|
||||
void FU_ProgramSetup(uint8_t *data);
|
||||
void FU_WriteProgramDataToFLASH(void);
|
||||
void FU_VerifyImage();
|
||||
void FU_ResetProcessor();
|
||||
|
||||
#endif /* LOADER_FLASHUPDATE_H_ */
|
||||
85
source/Loader/iap_jump.c
Normal file
85
source/Loader/iap_jump.c
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* iap_jump.c
|
||||
*
|
||||
* Created on: Oct 23, 2023
|
||||
* Author: Warner
|
||||
*/
|
||||
|
||||
#include <Loader/iap_jump.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "LPC54114_cm4.h"
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Definitions
|
||||
******************************************************************************/
|
||||
typedef void (*IAP_JumpFun)(void);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Variables
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Static Function Declarations
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Static Functions
|
||||
******************************************************************************/
|
||||
|
||||
/*******************************************************************************
|
||||
* Public Functions
|
||||
******************************************************************************/
|
||||
#if 0
|
||||
void IAP_JumpToAPP(uint32_t applicationAddress, uint32_t stackPointer){
|
||||
/* Static variables are needed as we need to ensure the values we are using are not stored on the
|
||||
previous stack */
|
||||
static uint32_t s_stackPointer = 0;
|
||||
s_stackPointer = stackPointer;
|
||||
static void (*farewellBootloader)(void) = 0;
|
||||
farewellBootloader = (void (*)(void))applicationAddress;
|
||||
|
||||
/* Set the VTOR to the application vector table address */
|
||||
SCB->VTOR = applicationAddress;
|
||||
|
||||
/* Set stack pointers to the application stack pointer */
|
||||
__set_MSP(s_stackPointer);
|
||||
__set_PSP(s_stackPointer);
|
||||
|
||||
/* Jump to the application */
|
||||
farewellBootloader();
|
||||
}
|
||||
#endif
|
||||
|
||||
void IAP_JumpToApp(uint32_t app_addr){
|
||||
/* Static variables are needed as we need to ensure the values we are using are not stored on the
|
||||
previous stack */
|
||||
static uint32_t s_stackPointer = 0;
|
||||
s_stackPointer = app_addr;
|
||||
|
||||
IAP_JumpFun JumpToApp;
|
||||
JumpToApp=(IAP_JumpFun)*(uint32_t*)(app_addr+4);
|
||||
|
||||
/* Set the VTOR to the application vector table address */
|
||||
SCB->VTOR = app_addr;
|
||||
|
||||
/* Set stack pointers to the application stack pointer */
|
||||
__set_MSP(*(uint32_t*)s_stackPointer);
|
||||
__set_PSP(*(uint32_t*)s_stackPointer);
|
||||
|
||||
/* Jump to the application */
|
||||
JumpToApp();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
17
source/Loader/iap_jump.h
Normal file
17
source/Loader/iap_jump.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* iap_jump.h
|
||||
*
|
||||
* Created on: Oct 23, 2023
|
||||
* Author: Warner
|
||||
*/
|
||||
|
||||
#ifndef IAP_JUMP_H_
|
||||
#define IAP_JUMP_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// void IAP_JumpToAPP(uint32_t applicationAddress, uint32_t stackPointer);
|
||||
void IAP_JumpToApp(uint32_t app_addr);
|
||||
|
||||
#endif /* IAP_JUMP_H_ */
|
||||
204
source/Loader/sha256.c
Normal file
204
source/Loader/sha256.c
Normal file
@@ -0,0 +1,204 @@
|
||||
/*********************************************************************
|
||||
* Filename: sha256.c
|
||||
* Author: Brad Conte (brad AT bradconte.com)
|
||||
* Copyright:
|
||||
* Disclaimer: This code is presented "as is" without any guarantees.
|
||||
* Details: Implementation of the SHA-256 hashing algorithm.
|
||||
SHA-256 is one of the three algorithms in the SHA2
|
||||
specification. The others, SHA-384 and SHA-512, are not
|
||||
offered in this implementation.
|
||||
Algorithm specification can be found here:
|
||||
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
|
||||
This implementation uses little endian byte order.
|
||||
|
||||
* github: https://github.com/B-Con/crypto-algorithms
|
||||
*********************************************************************/
|
||||
|
||||
/*************************** HEADER FILES ***************************/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "sha256.h"
|
||||
|
||||
/****************************** MACROS ******************************/
|
||||
#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
|
||||
#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
|
||||
|
||||
#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
|
||||
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||
#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
|
||||
#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
|
||||
#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
|
||||
#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
|
||||
|
||||
/**************************** VARIABLES *****************************/
|
||||
static const WORD k[64] = {
|
||||
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
|
||||
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
|
||||
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
|
||||
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
|
||||
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
|
||||
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
|
||||
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
|
||||
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
|
||||
};
|
||||
|
||||
/*********************** FUNCTION DEFINITIONS ***********************/
|
||||
void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
|
||||
{
|
||||
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
|
||||
|
||||
for (i = 0, j = 0; i < 16; ++i, j += 4)
|
||||
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
|
||||
for ( ; i < 64; ++i)
|
||||
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
|
||||
|
||||
a = ctx->state[0];
|
||||
b = ctx->state[1];
|
||||
c = ctx->state[2];
|
||||
d = ctx->state[3];
|
||||
e = ctx->state[4];
|
||||
f = ctx->state[5];
|
||||
g = ctx->state[6];
|
||||
h = ctx->state[7];
|
||||
|
||||
for (i = 0; i < 64; ++i) {
|
||||
t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
|
||||
t2 = EP0(a) + MAJ(a,b,c);
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + t1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = t1 + t2;
|
||||
}
|
||||
|
||||
ctx->state[0] += a;
|
||||
ctx->state[1] += b;
|
||||
ctx->state[2] += c;
|
||||
ctx->state[3] += d;
|
||||
ctx->state[4] += e;
|
||||
ctx->state[5] += f;
|
||||
ctx->state[6] += g;
|
||||
ctx->state[7] += h;
|
||||
}
|
||||
|
||||
void sha256_init(SHA256_CTX *ctx)
|
||||
{
|
||||
ctx->datalen = 0;
|
||||
ctx->bitlen = 0;
|
||||
ctx->state[0] = 0x6a09e667;
|
||||
ctx->state[1] = 0xbb67ae85;
|
||||
ctx->state[2] = 0x3c6ef372;
|
||||
ctx->state[3] = 0xa54ff53a;
|
||||
ctx->state[4] = 0x510e527f;
|
||||
ctx->state[5] = 0x9b05688c;
|
||||
ctx->state[6] = 0x1f83d9ab;
|
||||
ctx->state[7] = 0x5be0cd19;
|
||||
}
|
||||
|
||||
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
|
||||
{
|
||||
WORD i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
ctx->data[ctx->datalen] = data[i];
|
||||
ctx->datalen++;
|
||||
if (ctx->datalen == 64) {
|
||||
sha256_transform(ctx, ctx->data);
|
||||
ctx->bitlen += 512;
|
||||
ctx->datalen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sha256_final(SHA256_CTX *ctx, BYTE hash[])
|
||||
{
|
||||
WORD i;
|
||||
|
||||
i = ctx->datalen;
|
||||
|
||||
// Pad whatever data is left in the buffer.
|
||||
if (ctx->datalen < 56) {
|
||||
ctx->data[i++] = 0x80;
|
||||
while (i < 56)
|
||||
ctx->data[i++] = 0x00;
|
||||
}
|
||||
else {
|
||||
ctx->data[i++] = 0x80;
|
||||
while (i < 64)
|
||||
ctx->data[i++] = 0x00;
|
||||
sha256_transform(ctx, ctx->data);
|
||||
memset(ctx->data, 0, 56);
|
||||
}
|
||||
|
||||
// Append to the padding the total message's length in bits and transform.
|
||||
ctx->bitlen += ctx->datalen * 8;
|
||||
ctx->data[63] = ctx->bitlen;
|
||||
ctx->data[62] = ctx->bitlen >> 8;
|
||||
ctx->data[61] = ctx->bitlen >> 16;
|
||||
ctx->data[60] = ctx->bitlen >> 24;
|
||||
ctx->data[59] = ctx->bitlen >> 32;
|
||||
ctx->data[58] = ctx->bitlen >> 40;
|
||||
ctx->data[57] = ctx->bitlen >> 48;
|
||||
ctx->data[56] = ctx->bitlen >> 56;
|
||||
sha256_transform(ctx, ctx->data);
|
||||
|
||||
// Since this implementation uses little endian byte ordering and SHA uses big endian,
|
||||
// reverse all the bytes when copying the final state to the output hash.
|
||||
for (i = 0; i < 4; ++i) {
|
||||
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
|
||||
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
|
||||
}
|
||||
}
|
||||
|
||||
void sha256_ComputeHash(BYTE *data, WORD numBytes, BYTE *hash)
|
||||
{
|
||||
SHA256_CTX ctx;
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, data, numBytes);
|
||||
sha256_final(&ctx, hash);
|
||||
}
|
||||
|
||||
int sha256_test()
|
||||
{
|
||||
BYTE text1[] = {"abc"};
|
||||
BYTE text2[] = {"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"};
|
||||
BYTE text3[] = {"aaaaaaaaaa"};
|
||||
BYTE hash1[SHA256_BLOCK_SIZE] = {0xba,0x78,0x16,0xbf,0x8f,0x01,0xcf,0xea,0x41,0x41,0x40,0xde,0x5d,0xae,0x22,0x23,
|
||||
0xb0,0x03,0x61,0xa3,0x96,0x17,0x7a,0x9c,0xb4,0x10,0xff,0x61,0xf2,0x00,0x15,0xad};
|
||||
BYTE hash2[SHA256_BLOCK_SIZE] = {0x24,0x8d,0x6a,0x61,0xd2,0x06,0x38,0xb8,0xe5,0xc0,0x26,0x93,0x0c,0x3e,0x60,0x39,
|
||||
0xa3,0x3c,0xe4,0x59,0x64,0xff,0x21,0x67,0xf6,0xec,0xed,0xd4,0x19,0xdb,0x06,0xc1};
|
||||
BYTE hash3[SHA256_BLOCK_SIZE] = {0xcd,0xc7,0x6e,0x5c,0x99,0x14,0xfb,0x92,0x81,0xa1,0xc7,0xe2,0x84,0xd7,0x3e,0x67,
|
||||
0xf1,0x80,0x9a,0x48,0xa4,0x97,0x20,0x0e,0x04,0x6d,0x39,0xcc,0xc7,0x11,0x2c,0xd0};
|
||||
BYTE buf[SHA256_BLOCK_SIZE];
|
||||
SHA256_CTX ctx;
|
||||
int idx;
|
||||
int pass = 1;
|
||||
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, text1, strlen((char *)text1));
|
||||
sha256_final(&ctx, buf);
|
||||
pass = pass && !memcmp(hash1, buf, SHA256_BLOCK_SIZE);
|
||||
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx, text2, strlen((char *)text2));
|
||||
sha256_final(&ctx, buf);
|
||||
pass = pass && !memcmp(hash2, buf, SHA256_BLOCK_SIZE);
|
||||
|
||||
sha256_init(&ctx);
|
||||
for (idx = 0; idx < 100000; ++idx)
|
||||
sha256_update(&ctx, text3, strlen((char *)text3));
|
||||
sha256_final(&ctx, buf);
|
||||
pass = pass && !memcmp(hash3, buf, SHA256_BLOCK_SIZE);
|
||||
|
||||
return(pass);
|
||||
}
|
||||
|
||||
37
source/Loader/sha256.h
Normal file
37
source/Loader/sha256.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*********************************************************************
|
||||
* Filename: sha256.h
|
||||
* Author: Brad Conte (brad AT bradconte.com)
|
||||
* Copyright:
|
||||
* Disclaimer: This code is presented "as is" without any guarantees.
|
||||
* Details: Defines the API for the corresponding SHA1 implementation.
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef SHA256_H
|
||||
#define SHA256_H
|
||||
|
||||
/*************************** HEADER FILES ***************************/
|
||||
#include <stddef.h>
|
||||
|
||||
/****************************** MACROS ******************************/
|
||||
#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
|
||||
#define SHA256_DIGEST_SIZE 32 // Added for rsa.c
|
||||
|
||||
/**************************** DATA TYPES ****************************/
|
||||
typedef unsigned char BYTE; // 8-bit byte
|
||||
typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines
|
||||
|
||||
typedef struct {
|
||||
BYTE data[64];
|
||||
WORD datalen;
|
||||
unsigned long long bitlen;
|
||||
WORD state[8];
|
||||
} SHA256_CTX;
|
||||
|
||||
/*********************** FUNCTION DECLARATIONS **********************/
|
||||
void sha256_init(SHA256_CTX *ctx);
|
||||
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
|
||||
void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
|
||||
void sha256_ComputeHash(BYTE *data, WORD numBytes, BYTE *hash);
|
||||
int sha256_test();
|
||||
|
||||
#endif // SHA256_H
|
||||
Reference in New Issue
Block a user