Moved remotely
git-svn-id: file:///srv/dev-disk-by-uuid-17e88007-4d0c-45e0-8757-cacfcc458630/repositories/svn/Diplomarbeit@111 9fe90eed-be63-e94b-8204-d34ff4c2ff93
This commit is contained in:
@@ -0,0 +1,620 @@
|
||||
/* ---------------------------------------------------------------------------
|
||||
* IspProtocol.c - v0.1 (c) 2008 Micro-key bv
|
||||
* ---------------------------------------------------------------------------
|
||||
* Micro-key bv
|
||||
* Industrieweg 28, 9804 TG Noordhorn
|
||||
* Postbus 92, 9800 AB Zuidhorn
|
||||
* The Netherlands
|
||||
* Tel: +31 594 503020
|
||||
* Fax: +31 594 505825
|
||||
* Email: support@microkey.nl
|
||||
* Web: www.microkey.nl
|
||||
* ---------------------------------------------------------------------------
|
||||
* Description:
|
||||
* ---------------------------------------------------------------------------
|
||||
* Version(s): 0.1, Feb 13, 2008, FSc
|
||||
* Creation.
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* System include files
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Application include files
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
#include "types.h"
|
||||
#include "IspProtocol.h"
|
||||
#include "InternalFlash.h"
|
||||
#include "Crc.h"
|
||||
#include "Leds.h"
|
||||
#include "appImage.h"
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Local constant and macro definitions
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
#define MAX_ADMINS (4)
|
||||
#define START_BYTE (0xAA)
|
||||
#define MAX_PAYLOAD_SIZE (80)
|
||||
|
||||
#define MSGID_ACKNOWLEDGE (0x01)
|
||||
#define MSGID_UNLOCK (0x02)
|
||||
#define MSGID_ERASEBLOCK (0x03)
|
||||
#define MSGID_PROGRAMFLASH (0x04)
|
||||
#define MSGID_VERIFYFLASH (0x05)
|
||||
#define MSGID_FINISHPROGRAMMING (0x06)
|
||||
#define MSGID_STARTPROGRAM (0x07)
|
||||
|
||||
#define APP_FLASH_START_ADDR (0x00005000)
|
||||
#define APP_FLASH_END_ADDR (0x0007DFFF)
|
||||
#define APP_FLASH_START_SECTOR (5)
|
||||
#define APP_FLASH_END_SECTOR (27)
|
||||
#define APP_IMAGE_LENGTH_OFFS 0x0008
|
||||
#define APP_IMAGE_CRC_OFFS 0x000C
|
||||
|
||||
#define PROGRAM_BLOCK_SIZE (256)
|
||||
#define INVALID_ADDRESS (0xFFFFFFFF)
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Global variable definitions
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Local variable definitions
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
IDLE,
|
||||
MESSAGE_ID,
|
||||
PAYLOAD_SIZE,
|
||||
PAYLOAD,
|
||||
CRC
|
||||
} t_isp_decodestatus;
|
||||
|
||||
typedef struct t_ISP_MESSAGE {
|
||||
UINT8 messageId;
|
||||
UINT8 payloadSize;
|
||||
UINT8 payload[MAX_PAYLOAD_SIZE];
|
||||
UINT16 crc;
|
||||
} t_isp_message;
|
||||
|
||||
typedef struct t_ISP_ADMIN {
|
||||
t_isp_decodestatus status;
|
||||
UINT8 rxIndex;
|
||||
UINT16 rxCrc;
|
||||
t_isp_message rxMessage;
|
||||
t_isp_ack_callback ackCallback;
|
||||
} t_isp_admin;
|
||||
|
||||
static t_isp_admin ispAdmins[MAX_ADMINS];
|
||||
static UINT8 lastReservedAdmin = 0;
|
||||
static UINT32 maxProgrammedAddress = 0;
|
||||
static UINT32 startAddress = INVALID_ADDRESS;
|
||||
static UINT8 verifyBlock[PROGRAM_BLOCK_SIZE] __attribute__((aligned(PROGRAM_BLOCK_SIZE)));
|
||||
static UINT8 programBlock[PROGRAM_BLOCK_SIZE] __attribute__((aligned(PROGRAM_BLOCK_SIZE)));
|
||||
static UINT8 programFirstBlock[PROGRAM_BLOCK_SIZE] __attribute__((aligned(PROGRAM_BLOCK_SIZE)));
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Local function definitions
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
void sendAcknowledge(t_isp_admin *handle, t_isp_responses response);
|
||||
BOOLEAN ValidMessageId( UINT8 messageId);
|
||||
void handleMessage(t_isp_admin *admin);
|
||||
void handleUnlockMessage(t_isp_admin *admin);
|
||||
void handleEraseBlockMessage(t_isp_admin *admin);
|
||||
void handleStartProgram(t_isp_admin *admin);
|
||||
void handleFinishProgramming(t_isp_admin *admin);
|
||||
void handleVerifyFlashMessage(t_isp_admin *admin);
|
||||
void handleProgramFlashMessage(t_isp_admin *admin);
|
||||
iflashresult fillProgramBlock( UINT32 address, UINT8 size, UINT8 *data);
|
||||
iflashresult programFlash();
|
||||
void resetRamBlock( UINT32 address );
|
||||
iflashresult StoreCrcAndLength( UINT32 imageSize );
|
||||
|
||||
|
||||
|
||||
/** \brief Initialises a instance of the ISP-protocol handler.
|
||||
*
|
||||
* For each communication port, one ISP-protocol handler must be initialised.
|
||||
*
|
||||
* \param ackCallback Callback-function used for parent to send acknowledge
|
||||
* \returns handle Handle for this ISP-protocol handler.
|
||||
*/
|
||||
int ispInitProtocol(t_isp_ack_callback ackCallback )
|
||||
{
|
||||
int result;
|
||||
if (lastReservedAdmin < MAX_ADMINS)
|
||||
{
|
||||
ispAdmins[lastReservedAdmin].status = IDLE;
|
||||
ispAdmins[lastReservedAdmin].ackCallback = ackCallback;
|
||||
|
||||
result = lastReservedAdmin;
|
||||
lastReservedAdmin++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** \brief Does the protocol handling by parsing each byte received on the communication port.
|
||||
*
|
||||
* \param handle Handle for the ISP-protocol handler
|
||||
* \param byte The received byte on the communication port
|
||||
*/
|
||||
void ispHandleRxByte( int handle, UINT8 byte )
|
||||
{
|
||||
t_isp_admin *admin = &(ispAdmins[handle]);
|
||||
|
||||
switch( admin->status )
|
||||
{
|
||||
case(IDLE):
|
||||
if (byte == START_BYTE)
|
||||
{
|
||||
admin->status = MESSAGE_ID;
|
||||
}
|
||||
break;
|
||||
case(MESSAGE_ID):
|
||||
if (ValidMessageId(byte) == TRUE )
|
||||
{
|
||||
// Determine Payload-size
|
||||
admin->rxCrc = crcCalc(&byte, 1, 0);
|
||||
admin->rxMessage.messageId = byte;
|
||||
admin->status = PAYLOAD_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
sendAcknowledge( admin, ISP_INVALID_MESSAGE_ID );
|
||||
admin->status = IDLE;
|
||||
}
|
||||
break;
|
||||
case(PAYLOAD_SIZE):
|
||||
// Determine Payload-size
|
||||
if (byte <=MAX_PAYLOAD_SIZE)
|
||||
{
|
||||
admin->rxMessage.payloadSize = byte;
|
||||
admin->rxCrc = crcCalc(&byte, 1, admin->rxCrc);
|
||||
admin->rxIndex = 0;
|
||||
if (byte > 0)
|
||||
{
|
||||
admin->status = PAYLOAD;
|
||||
}
|
||||
else
|
||||
{
|
||||
admin->status = CRC;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sendAcknowledge( admin, ISP_PAYLOAD_TOO_LARGE );
|
||||
admin->status = IDLE;
|
||||
}
|
||||
break;
|
||||
case(PAYLOAD):
|
||||
admin->rxMessage.payload[ admin->rxIndex ] = byte;
|
||||
admin->rxCrc = crcCalc(&byte, 1, admin->rxCrc);
|
||||
admin->rxIndex++;
|
||||
|
||||
if (admin->rxIndex >= admin->rxMessage.payloadSize)
|
||||
{
|
||||
admin->rxIndex = 0;
|
||||
admin->status = CRC;
|
||||
}
|
||||
break;
|
||||
case(CRC):
|
||||
// Receive and check CRC
|
||||
if (admin->rxIndex == 0)
|
||||
{
|
||||
admin->rxMessage.crc = (byte << 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
admin->rxMessage.crc |= (byte & 0x00FF);
|
||||
}
|
||||
|
||||
admin->rxIndex++;
|
||||
|
||||
if (admin->rxIndex >= 2)
|
||||
{
|
||||
if (admin->rxCrc == admin->rxMessage.crc)
|
||||
{
|
||||
/* Crc was succesfully checked */
|
||||
handleMessage(admin);
|
||||
}
|
||||
else
|
||||
{
|
||||
sendAcknowledge( admin, ISP_RECV_BAD_CRC );
|
||||
}
|
||||
admin->status = IDLE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
admin->status = IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void handleMessage(t_isp_admin *admin)
|
||||
{
|
||||
switch( admin->rxMessage.messageId )
|
||||
{
|
||||
case(MSGID_UNLOCK):
|
||||
//sendAcknowledge( admin, ISP_CMD_SUCCESS);
|
||||
handleUnlockMessage( admin );
|
||||
break;
|
||||
case(MSGID_ERASEBLOCK):
|
||||
handleEraseBlockMessage( admin );
|
||||
break;
|
||||
case(MSGID_PROGRAMFLASH):
|
||||
handleProgramFlashMessage( admin );
|
||||
break;
|
||||
case(MSGID_VERIFYFLASH):
|
||||
handleVerifyFlashMessage( admin );
|
||||
break;
|
||||
case(MSGID_FINISHPROGRAMMING):
|
||||
handleFinishProgramming( admin );
|
||||
break;
|
||||
case(MSGID_STARTPROGRAM):
|
||||
handleStartProgram( admin );
|
||||
break;
|
||||
default:
|
||||
sendAcknowledge( admin, ISP_INVALID_MESSAGE_ID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BOOLEAN ValidMessageId( UINT8 messageId)
|
||||
{
|
||||
if ((messageId >= 0x01) && (messageId <= 0x07))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void sendAcknowledge(t_isp_admin *handle, t_isp_responses response)
|
||||
{
|
||||
handle->ackCallback( response );
|
||||
}
|
||||
|
||||
|
||||
void handleUnlockMessage(t_isp_admin *admin)
|
||||
{
|
||||
iflashresult unlockResult = ISP_CMD_SUCCESS;
|
||||
|
||||
maxProgrammedAddress = 0;
|
||||
unlockResult =iflashPrepare( APP_FLASH_START_SECTOR, APP_FLASH_END_SECTOR);
|
||||
|
||||
admin->ackCallback( (t_isp_responses)unlockResult );
|
||||
}
|
||||
|
||||
void handleEraseBlockMessage(t_isp_admin *admin)
|
||||
{
|
||||
iflashresult result;
|
||||
UINT8 blockNr;
|
||||
|
||||
// Extract block nr from message
|
||||
blockNr = admin->rxMessage.payload[0];
|
||||
|
||||
if ( ( blockNr >= APP_FLASH_START_SECTOR)
|
||||
&& ( blockNr <= APP_FLASH_END_SECTOR))
|
||||
{
|
||||
result = iflashErase( blockNr );
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ISP_INVALID_SECTOR;
|
||||
}
|
||||
|
||||
admin->ackCallback( (t_isp_responses)result );
|
||||
}
|
||||
|
||||
void handleProgramFlashMessage(t_isp_admin *admin)
|
||||
{
|
||||
iflashresult result;
|
||||
UINT32 address;
|
||||
UINT8 size;
|
||||
UINT8 *data;
|
||||
UINT8 index = 0;
|
||||
|
||||
// Extract address, datalength & data from message
|
||||
address = ispGet32bit(admin->rxMessage.payload, &index);
|
||||
size = ispGet8bit(admin->rxMessage.payload, &index);
|
||||
data = &(admin->rxMessage.payload[index]);
|
||||
|
||||
if (maxProgrammedAddress < address) maxProgrammedAddress = address;
|
||||
|
||||
if ( (address >= APP_FLASH_START_ADDR)
|
||||
&& (address <= APP_FLASH_END_ADDR)
|
||||
)
|
||||
{
|
||||
result = fillProgramBlock(address, size, data );
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ISP_DST_ADDR_ERROR;
|
||||
}
|
||||
|
||||
admin->ackCallback( (t_isp_responses)result );
|
||||
}
|
||||
|
||||
void handleVerifyFlashMessage(t_isp_admin *admin)
|
||||
{
|
||||
iflashresult result;
|
||||
UINT32 address;
|
||||
UINT8 size;
|
||||
UINT8 index = 0;
|
||||
UINT8 bufferIdx;
|
||||
|
||||
// Extract address, datalength & data from message
|
||||
address = ispGet32bit(admin->rxMessage.payload, &index);
|
||||
size = ispGet8bit(admin->rxMessage.payload, &index);
|
||||
for (bufferIdx = 0; bufferIdx < size; bufferIdx++)
|
||||
{
|
||||
verifyBlock[bufferIdx] = ispGet8bit(admin->rxMessage.payload, &index);
|
||||
}
|
||||
|
||||
// Make size dividable by 4
|
||||
size = size - (size % 4);
|
||||
|
||||
if ((size >= 4) && (address != APP_FLASH_START_ADDR))
|
||||
{
|
||||
result = iflashVerify( address, size, verifyBlock );
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ISP_CMD_SUCCESS;
|
||||
}
|
||||
|
||||
admin->ackCallback( (t_isp_responses)result );
|
||||
}
|
||||
|
||||
void handleFinishProgramming(t_isp_admin *admin)
|
||||
{
|
||||
iflashresult result;
|
||||
UINT32 imageSize;
|
||||
|
||||
// Program remaining block on flash
|
||||
fillProgramBlock(0, 0, 0 );
|
||||
|
||||
// Calculate image length
|
||||
imageSize = maxProgrammedAddress - APP_FLASH_START_ADDR;
|
||||
|
||||
// Calculate and store Crc & imageSize on flash
|
||||
result = StoreCrcAndLength( imageSize );
|
||||
|
||||
admin->ackCallback( (t_isp_responses)result );
|
||||
}
|
||||
|
||||
void handleStartProgram(t_isp_admin *admin)
|
||||
{
|
||||
//if (appiValidAppImageAvail() == TRUE)
|
||||
{
|
||||
ledSet( LED1, 0 );
|
||||
ledSet( LED0, 0 );
|
||||
|
||||
appiJumpToAppImage();
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// admin->ackCallback( ISP_BUSY );
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
void ispAdd16bit(UINT8 *payloadlocation, UINT16 data)
|
||||
{
|
||||
UINT8 index = 0;
|
||||
|
||||
payloadlocation[index] = (UINT8)(data >> 8);
|
||||
index++;
|
||||
payloadlocation[index] = (UINT8)(data & 0x00FF);
|
||||
}
|
||||
|
||||
|
||||
void ispAdd32bit(UINT8 *payloadlocation, UINT32 data)
|
||||
{
|
||||
UINT8 index = 0;
|
||||
|
||||
payloadlocation[index] = (UINT8)(data >> 24);
|
||||
index++;
|
||||
payloadlocation[index] = (UINT8)(data >> 16);
|
||||
index++;
|
||||
payloadlocation[index] = (UINT8)(data >> 8);
|
||||
index++;
|
||||
payloadlocation[index] = (UINT8)(data & 0xFF);
|
||||
|
||||
}
|
||||
|
||||
UINT8 ispGet8bit(UINT8 *payload, UINT8 *payloadIndex)
|
||||
{
|
||||
UINT8 result;
|
||||
|
||||
result = (UINT8)payload[*payloadIndex];
|
||||
(*payloadIndex)++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
UINT16 ispGet16bit(UINT8 *payload, UINT8 *payloadIndex)
|
||||
{
|
||||
UINT16 result;
|
||||
|
||||
result = ((UINT16)payload[*payloadIndex]) << 8;
|
||||
(*payloadIndex)++;
|
||||
result += ((UINT16)payload[*payloadIndex] & 0x00FF);
|
||||
(*payloadIndex)++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
UINT32 ispGet32bit(UINT8 *payload, UINT8 *payloadIndex)
|
||||
{
|
||||
UINT32 result;
|
||||
|
||||
result = ((UINT32)payload[*payloadIndex]) << 24;
|
||||
(*payloadIndex)++;
|
||||
result += ((UINT32)payload[*payloadIndex]) << 16;
|
||||
(*payloadIndex)++;
|
||||
result += ((UINT32)payload[*payloadIndex]) << 8;
|
||||
(*payloadIndex)++;
|
||||
result += ((UINT32)payload[*payloadIndex] & 0x000000FF);
|
||||
(*payloadIndex)++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
iflashresult fillProgramBlock( UINT32 address, UINT8 size, UINT8 *data)
|
||||
{
|
||||
UINT32 programIndex;
|
||||
UINT32 beginIndex;
|
||||
UINT32 dataIndex;
|
||||
UINT32 endIndex;
|
||||
iflashresult result = ISP_CMD_SUCCESS;
|
||||
|
||||
if (data == 0)
|
||||
{
|
||||
// finish sending last block
|
||||
if (startAddress > 0)
|
||||
{
|
||||
result = programFlash();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ISP_CMD_SUCCESS;
|
||||
}
|
||||
startAddress = INVALID_ADDRESS;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Determine startAddress.
|
||||
if (startAddress == INVALID_ADDRESS)
|
||||
{
|
||||
// Make sure address is alligned to 256
|
||||
resetRamBlock(address - (address % PROGRAM_BLOCK_SIZE));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (address > (startAddress + PROGRAM_BLOCK_SIZE))
|
||||
{
|
||||
result = programFlash();
|
||||
resetRamBlock( address );
|
||||
}
|
||||
}
|
||||
|
||||
// Determine endIndex;
|
||||
if ( (startAddress + PROGRAM_BLOCK_SIZE) > (address + size) )
|
||||
{
|
||||
endIndex = (address - startAddress) + size;
|
||||
}
|
||||
else
|
||||
{
|
||||
endIndex = PROGRAM_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
// Copy data into programblock
|
||||
dataIndex = 0;
|
||||
beginIndex = address - startAddress;
|
||||
for (programIndex = beginIndex; programIndex < endIndex; programIndex++)
|
||||
{
|
||||
programBlock[programIndex] = data[dataIndex];
|
||||
dataIndex++;
|
||||
}
|
||||
|
||||
// if program block is full, program it
|
||||
if (dataIndex < size)
|
||||
{
|
||||
result = programFlash();
|
||||
resetRamBlock( startAddress + PROGRAM_BLOCK_SIZE );
|
||||
|
||||
// Copy rest in new block
|
||||
endIndex = size - dataIndex;
|
||||
for (programIndex = 0; programIndex < endIndex; programIndex++)
|
||||
{
|
||||
programBlock[programIndex] = data[dataIndex];
|
||||
dataIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((startAddress + PROGRAM_BLOCK_SIZE) == (address + size))
|
||||
{
|
||||
result = programFlash();
|
||||
resetRamBlock( INVALID_ADDRESS );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void resetRamBlock( UINT32 address )
|
||||
{
|
||||
UINT32 programIndex;
|
||||
|
||||
startAddress = address;
|
||||
for (programIndex = 0; programIndex < PROGRAM_BLOCK_SIZE; programIndex++)
|
||||
{
|
||||
programBlock[programIndex] = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
iflashresult programFlash()
|
||||
{
|
||||
UINT16 index;
|
||||
|
||||
if (startAddress != APP_FLASH_START_ADDR)
|
||||
{
|
||||
iflashPrepare( APP_FLASH_START_SECTOR, APP_FLASH_END_SECTOR);
|
||||
return iflashProgram( startAddress, PROGRAM_BLOCK_SIZE, programBlock );
|
||||
}
|
||||
else
|
||||
{
|
||||
// keep copy of first block, programmed later at FinishedProgramming
|
||||
for (index = 0; index < PROGRAM_BLOCK_SIZE; index++)
|
||||
{
|
||||
programFirstBlock[index] = programBlock[index];
|
||||
}
|
||||
|
||||
return ISP_CMD_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
iflashresult StoreCrcAndLength( UINT32 imageSize )
|
||||
{
|
||||
static UINT16 crc = 0;
|
||||
static UINT8 *image = 0;
|
||||
|
||||
// Calculate Crc over part before Length & CRC
|
||||
crc = crcCalc(programFirstBlock, APP_IMAGE_LENGTH_OFFS, crc);
|
||||
|
||||
// Calculate Crc over part after Length & CRC in first program block
|
||||
image = (UINT8 *)(programFirstBlock + APP_IMAGE_CRC_OFFS + 4);
|
||||
crc = crcCalc(image, PROGRAM_BLOCK_SIZE - (APP_IMAGE_CRC_OFFS + 4), crc);
|
||||
|
||||
// Calculate Crc over rest of image already programmed
|
||||
image = (UINT8 *)(APP_FLASH_START_ADDR + PROGRAM_BLOCK_SIZE);
|
||||
crc = crcCalc(image, imageSize - PROGRAM_BLOCK_SIZE, crc);
|
||||
|
||||
// Fill in Length & CRC
|
||||
ispAdd32bit(programFirstBlock + APP_IMAGE_LENGTH_OFFS, imageSize);
|
||||
ispAdd16bit(programFirstBlock + APP_IMAGE_CRC_OFFS, crc);
|
||||
|
||||
// Program first block on flash
|
||||
iflashPrepare( APP_FLASH_START_SECTOR, APP_FLASH_START_SECTOR);
|
||||
return iflashProgram( APP_FLASH_START_ADDR, PROGRAM_BLOCK_SIZE, programFirstBlock );
|
||||
}
|
||||
Reference in New Issue
Block a user