// erase_size = 64
// write_size = 32
// program_memory = 65536
// Bootloader con XTEA elaborado a partir de ejemplos y otras implementaciones.
#include<18f26k20.h>
#fuses INTRC_IO, NOWDT, BROWNOUT, PUT, NOLVP, NOMCLR, PBADEN,
#use delay(clock=64000000)
#use rs232(baud=115200, xmit=PIN_C6, rcv=PIN_C7,STREAM=TX) // Comunicación con el cargador (Coolterm, realterm, etc).
#use rs232(baud=9600,parity=N,xmit=PIN_C7,rcv=PIN_C6,bits=8,FORCE_SW,STREAM=LCD) // Comunicacion rs232 con la pantalla lcd ascii.
#define PUSH_BUTTON PIN_B4 // pulsador de modo bootloader
#byte OSCCON=getenv("SFR:OSCCON") // registo de configuración del oscilador.
#bit PLLEN=getenv("BIT:PLLEN") // registo de configuración del oscilador. PLL
#byte OSCTUNE=getenv("SFR:OSCTUNE") // registo de configuración del oscilador.
#define CLK_16 0x70 // configuración para oscilador a 16 Mhz.
#BIT CREN = 0xfab.4 // control recepción modulo usart
#define XON 0x11 // control de flujo datos desde el cargador por sotware
#define XOFF 0x13 // control de flujo datos desde el cargador por sotware
#org 0xc84,0xfffe {} // a partir de la posicion 0xc80 hasta el final de la flash es para el programa a cargar
// pero permitimos a bootloader escribir hasta 0xc84 para que sobreescriba la función del vector de arranque.
int8 checksum, line_type; // Control de checksum y almacen del tipo de linea hex respectivamente.
int16 l_addr,h_addr=0;
int32 addr; // almacena el addres donde hay que cargar el buffer data en la flash
int8 dataidx, i, count; // indice buffer de data, i = control usos multiples, count = almacena numero de bytes de una linea relevante hex.
int8 data[32]; // buffer de datos enteros de 8 bits ya convertidos listos para enviar a la flash,
int16 key [4] = {0xface,0xdead,0xbabe,0xd00d}; // clave XTEA
char encripted_buffer[8]; // buffer de 8 bytes para recibir los primeros 8 caracteres encriptados.
char line [46]; // buffer para construir una linea hex completa, desde ":" hasta el "CR"
int buffidx = 0; // indice para buffer primario (desencriptado))
int lineidx = 0; // indice para buffer constructor de lineas
int1 lineconst = 0; // bool para control de linea en contrucción o linea concluida.
unsigned int atoi_b16(char *s); // conversor de dos caracteres hex en un entero de 8 bits decimal
void buffering(); // Obtiene un paquete de 8 bytes desencriptados.
void decipher(int32 v[2]); // recibe 8 bytes encriptados por xtea y devuelve esos 8 bytes desencriptados.
void load_program (); // constructor de lineas hex
void graba(); // procesador de lineas hex y grabación en la flash.
#org 0xc80,0xc82 // vector de arranque de la aplicación a cargar. Esto será sobreescrito por el "goto" inicial de la app cargada
void main_program (void) {
while(1);
} // fin vector de arranque
void main(void) {
OSCCON=CLK_16; // configuracion oscilador
OSCTUNE=0b11000000; // fonfig oscilador.
PLLEN=TRUE; // config oscilador 64 Mhz.
set_tris_a(0b00111111); // definidos los puertos segun la configuración de la placa y persiféricos conectados en la cpu donde se va a cargar este bootloader
set_tris_b(0b10111111);
set_tris_c(0b01111101);
CREN = 0; // Deshabilitada la recepción por puerto rs232. ( Si algo envía datos, no nos llena el fifo))
delay_ms(1000); // pausa de 1 segundo para estabilizar arranque, alimentación, osciladores.
if(!input(PUSH_BUTTON)) // si está pulsado entramos en modo bootloader.
{
fprintf(LCD
,"TTWaiting for download"); // mensaje en el lcd. fputc(0,LCD
); // caracter null para el lcd (requerido por el lcd);) delay_ms(100); // espera de tráfico concluido hacia el lcd.
fputc (XON
,TX
); // purgamos cualquier dato que pueda haber retenido en el cargador coolterm, realterm, etc..
load_program(); // recepcion, procesado y grabación del programa encriptado.
}
main_program(); // si no hemos pulsado saltamos al vector de arranque de la app.
}
#int_global
void isr(void) {
jump_to_isr(0x0c88); // redirige el vector de interrupción.
}
void load_program () { // recepcion, procesado y grabación del programa encriptado.
lineidx = 0; // indice buffer de linea hex a cero.
while(1) { // bucle secundario
while (1) { // bucle primario
buffering(); // llenamos un buffer de 8 bytes de caracteres desencriptados.
for (i = 0; i <= 7; i++) { // recorremos el buffer en busca del inicio de linea (:)
if ((encripted_buffer[i] == ':') || (lineconst == 1)) {
lineconst = 1; // si lo encontramos indicamos que hay una linea en construcción
line[lineidx++] = encripted_buffer[i]; // recorremos el buffer ya desencriptado y lo volcamos sobre el buffer line.
if (line[lineidx - 1] == 0x0d) lineconst = 0; // si en algun momento encontramos el caracter 0x0d cerramos la linea.
}
if (lineconst == 0) break; // si se ha completado una linea salimos del bucle for y dejamos el buffer actual intacto..
}
if (lineconst == 0) break; // si se ha completado una linea salimos del bucle while primario.
}
graba(); // mandamos a procesar y a grabar la linea en la flash.
// borrado de linea
for (i=0; i<=45;i++) line[i] = 255; // vaciamos completamente la linea a 0xFF.
// el ultimo buffer de 8 bytes, donde habiamos encontrado el 0x0d retorno de carro (fin de linea hex) aun contiene o puede contener mas caracteres
// que tenemos que utilizar.
// Por ello, habiéndolo dejado intacto volvemos a escanear el ultimo buffer donde se obtuvo el CR ya que seguramente contendrá el inicio de la linea siguiente..
lineidx = 0; // ponemos indice idx de la linea en cero para que empiece a sobreescribirla.
for (i = 0; i <= 7; i++) { // recorremos el buffer en busca del inicio de la siguiente linea (:)
if ((encripted_buffer[i] == ':') || (lineconst == 1)) { // si lo encontramos abrimos nueva linea.
lineconst = 1;
line[lineidx++] = encripted_buffer[i]; // volcamos el resto del buffer desde donde se ha encontrado ":" .
}
}
} // y volvemos al bucle secundario .
// este proceso se repetirá indefinidamente hasta que se haya completado la descarga.
// en la función graba();, es donde se detecta el fin de la carga cuando se recibe un tipo de linea = 0.)
}
void graba() {
h_addr = 0; // iniciamos h_addr a cero (16 bits superiores de la dirección de la flash)
int p = 0; // control local de la función.
if (line[0] == ':') { // comprobamos que el primer caracter sea el inicio de linea (:)
count = atoi_b16 (&line[1]); // Cargamos en Count, el numero de bytes que hay que grabar en la flash. (Atoi nos convierte dos caracteres hex en un entero de 8 bits)
l_addr = make16(atoi_b16(&line[3]),atoi_b16(&line[5])); // cargamos la direccion de la memoria flash donde hay que grabar estos datos.
line_type = atoi_b16 (&line[7]); // cargamos el tipo de linea al que se refiere.
addr = make32(h_addr,l_addr); // construimos addres de 32 bits para direccionar la flash.
int left = (count * 2) + 9; // Count contiene el numero de enteros de 8 bits que hay que grabar, son dos caracteres por entero, por eso multiplicamos su valor por dos.
//, le sumamos 9 por que son los caracteres del principio de la linea ":",numero de bytes "00", address, "0000", tipo de linea "00", son 9 caracteres
// Aqui lo usamos para saber el numero de bytes que hay que meter en checksum, mas abajo también es utilizado por el buffer Data
// ahora Left contiene la posición del primer caracter de checksum.
int checksum = 0; // valor inicial a checksum.
for (i=1; i<left; i+=2) // el checksum se verifica con los caracteres desde posicion 1 de la linea, hasta el final excepto los dos ultimos valores (que son el propio checksum). tampoco se tiene en cuenta el LF ni el CR.
checksum += atoi_b16 (&line[i]); // concatenamos y sumamos todos los valores arriba indicados.
checksum = 0xFF - checksum + 1; // restamos a 255 el valor que nos da la concatenación y le sumamos 1. El resultado ha de coincidir con el entero
// que suministran los dos ultimos caracteres.
if (checksum != atoi_b16 (&line[left])){ // comparamos
fprintf(LCD
,"TTChecksum ERROR!\r\n"); // si no coincide cancelamos la operación. delay_ms(10);
for(;;); // fin de programa por error de checksum.
}
// si checksum es ok continuamos.....
if (line_type == 1) { // si el tipo de linea es "1" significa que hemos concluido la carga completa del programa.
delay_ms(100);
fprintf(LCD
,"TTSuccessfull update!\r\n"); // indicamos fin de carga con éxito. delay_ms(1000);
reset_cpu(); // y reiniciamos para que arranque el nuevo programa.
}
else if (line_type == 4) // si el tipo de linea es "4" es por que tenemos que usar una dirección de más de 16 bits de datos, obtenemos de esta linea los otros 16 bits, y saltamos al final del bucle para pedir la siguiente linea.
h_addr = make16(atoi_b16(&line[9]), atoi_b16(&line[11])); // ya tenemos en h_addr los otros 16 bits.
else if (line_type == 0) { // si el tipo de linea es "0", se trata de una linea relevante de datos para la flash.
if ((addr
>= 0xc80) && (addr
< getenv("PROGRAM_MEMORY"))) { // Solo si la posicion de memoria está comprendida entre la zona reservada grabamos la flash
for (i = 9,dataidx=0; i < left; i += 2) // a partir del caracter 9 de una linea, hasta la posicion "left" calculada segun "count" (numero de bytes)
data[dataidx++]=atoi_b16(&line[i]); // cargamos en el bucle DATA los enteros ya convertidos de los caracteres. Aunque no está lleno, data es un array de 32 bytes
// debido a que la función write_program_memory en el caso de este cpu, trabaja por bloques de 32 bytes
write_program_memory(addr, data, count); // Grabamos en la flash el bloque completo de 32 bytes.
} // notese que hemos elegido que el programa ocupa a partir de la dirección 0xc80. Es el multiplo de 64 (flash_erase_size" mas cercano)
// que tengo por encima de lo minimo que ocupa este bootloader + una reserva de espacio por si el dia de mañana tuviese que actualizar el propio bootloader.
} // de modo que esta función trabajará de forma mas optima por que borrará todo el bloque de 64 bytes en cada operación multiplo
} // y eso garantiza que no queden datos residuales de anteriores versiones del programa cargado.
} // Si esto no se cumple, sería prudente efectuar un borrado completo de la zona reservada en flash antes de cargar un programa.
void buffering() { // XTEA trabaja por bloques de 8 bytes, recibimos el firmware de 8 en 8 bytes,
fputc (XON
,TX
); // indicamos al software externo que proceda a enviar datos. // llenamos el buffer inicial con 8 caracteres desencriptados...
buffidx = 0; // indice del buffer de 8 bytes para el XTEA
CREN = 1; // habilito la recepción de datos en el modulo usart de la cpu.
do { // recibimos el primer bloque de 8 bytes.
encripted_buffer
[buffidx
] = fgetc(TX
); // con este bucle recibiremos 7 bytes, el octavo lo leeremos justo después del XOFF ya que siempre le da tiempo de enviar un byte mas. } while (buffidx++ <= 5); // ya que al solicitar XOFF a coolterm, siempre le ha dado tiempo a enviar un ultimo byte.
fputc (XOFF
,TX
); // enviamos solicitud a software externo para que pause el envío de caracteres. encripted_buffer
[7] = fgetc(TX
); // cogemos el octavo byte que siempre se envía tras la peticion de parada
CREN = 0; // detenemos el puerto de recepcion del microcontrolador.
decipher(encripted_buffer); // desencriptamos por Xtea de 128 bits. Ahora tenemos en el mismo buffer 8 bytes desencriptados.
}
void decipher(int32 v[2]) { // tiny XTEA 128 bits.
int32 v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * 32; // 32 rondas
for (i = 0; i < 32; i++)
{
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0; v[1] = v1; // asignamos para cargar en el buffer.
}
unsigned int atoi_b16(char *s) { // obtiene un numero entero de 8 bits a partir de dos caracter headecimales.-
unsigned int result = 0;
int i;
for (i=0; i<2; i++,s++) {
if (*s >= 'A')
result = 16*result + (*s) - 'A' + 10;
else
result = 16*result + (*s) - '0';
}
return(result);
}