Solo filosofia o necessità? In effetti, esistono differenti strumenti per seguire una sessione di test utilizzando l’ambiente di lavoro GNU con Linux: si parte da una banale comunicazione seriale fino ad arrivare ad una connessione di tipo JTAG e, grazie a questi accorgimenti, è pensabile condurre sessioni di debug sul kernel di Linux.
Esistono, come molti sviluppatori software e hardware hanno posto in evidenza, differenti sistemi di test che rispondono a precise esigenze di tipo commerciale con prerogative legate all’aspetto tecnico. Non solo, l’offerta open source è notevolmente variegata e offre tutto il necessario per condurre un lavoro d’integrazione e di test, anche se poi le aziende, nella maggior parte dei casi, preferiscono utilizzare sistemi di tipo commerciale per via del possibile supporto e di una certificazione finale del loro prodotto.
Una delle ragioni fondamentali che spingono le aziende a preferire soluzioni di tipo commerciali è la possibilità di utilizzare degli strumenti cosiddetti affidabili che permettono di verificare il codice sviluppato anche a livello kernel. Storicamente Linux nasce su un’architettura Intel x86 dove l’ambiente host e target di norma coincidono, ma solo in seguito, grazie alla sua diffusione in ogni ambiente e applicazione, sorse anche l’esigenza di disporre di strumenti più flessibili, tanto che il principale ispiratore di Linux, Torvalds, decise di inserire una patch KGDB (debugger del kernel di Linux) nella struttura principale di Linux fino a quando, dalla versione 2.6.26 venne presentata una variante più snella del KGDB. Per inciso possiamo ricordare che con la versione 2.6.35 del kernel il KGDB poteva essere usato come front-end del gdb sullo stesso computer in luogo dei due originariamente previsti. In effetti, la versione originaria del KGDB prevedeva la presenza di due macchine tra loro connesse attraverso una linea seriale con due istanze di GDB su ogni macchina. La connessione seriale si basava su una banale RS232 utilizzando una configurazione null modem o ricorrendo ad un’interfaccia di rete con UDP/IP (a volte chiamata anche come KGDB over Ethernet o KGDBoE). Di certo, una decisione del genere deriva dal fatto che, per un sistema embedded, diventa pressoché necessario disporre di uno strumento flessibile che consenta di fare il debugging del kernel anche se poi, con l’introduzione del JTAG, l’approccio è sostanzialmente mutato per via della maggiore flessibilità offerta e di una minore invasività delle patch del kernel come ci si era abituati con KGDB.
JTAG, lo standard
Il JTAG (Joint Test Action Group) è stato inizialmente sviluppato come un modo per testare la connessione elettrica dei circuiti, anche oggi è più comunemente utilizzato per le attività di debug dei sistemi embedded. Un adattatore JTAG, a volte indicato come incircuit emulator (ICE), è utilizzato per accedere ai moduli di debug on-chip all’interno della CPU di destinazione. Il proposito di realizzare uno standard per il test dei circuiti è un’idea che viene da lontano; in effetti, il boundaryscan JTAG fu pubblicato per la prima volta nel 1990 con il riferimento di IEEE 1149.1 noto anche come TAP (Test Access Port) o anche BSA (Boundary Scan Architecture). Questo particolare standard definisce la logica di test da includere nei circuiti integrati che può essere utilizzata a livello di scheda per effettuare collaudi strutturali precisi e la programmazione dei componenti direttamente su target. Nel tempo, seppur siano stati inseriti nuovi aggiornamenti, le sue funzioni messe a punto nella versione iniziale non sono state modificate ma, al contrario, sono state introdotte nuove funzioni di tipo opzionale. L’idea dello standard, in sostanza, è molto semplice: si intende finalmente standardizzare il controllo dell’interfaccia utilizzando, a questo scopo, un sottoinsieme di pin fisici (quattro fondamentali e uno opzionale) e, aspetto molto interessante, è anche possibile verificare le connessioni sul circuito stampato prima della programmazione per accertarsi che non vi siano dei corti indesiderati che potrebbero danneggiare il dispositivo in modo irreparabile. Un sistema di questo tipo utilizza i pin TDI (Test Data In), TDO (Test Data Out), TCK (Test Clock), TMS (Test Mode Select), TRST (Test Reset). I primi due sono utilizzati per il transito dei dati, mentre gli altri tre sono segnali di controllo con il segnale TRST definito opzionale nello standard IEEE 1149.1. Non solo, nel caso in cui in uno schema elettrico siano presenti più dispositivi, l’utilizzo del sistema JTAG consente di collegarli in modo seriale: l‘ingresso per l’intero schema elettrico rimane uno e coincide con il segnale TDI del primo dispositivo della catena, mentre l’uscita diventa il segnale TDO dell’ultimo. Il collegamento tra un dispositivo e il successivo è effettuato cortocircuitando il segnale TDO di un dispositivo con il segnale TDI di quello successivo. I segnali di controllo (TCK, TMS, TRST) sono connessi in parallelo su tutti i dispositivi della catena ed in questo modo il numero di pin rimane costante, indipendentemente dal numero di dispositivi presenti nella catena JTAG. Utilizzare la tecnologia JTAG per verificare il codice è un’attività interessante e consente anche di ottenere enormi flessibilità: in passato si utilizzavano anche altri sistemi quali il BDM di Motorola, ora Freescale utilizzato in ambiente ColdFire e68k like. In questo modo si poteva disporre di un debug particolarmente economico e perfettamente comparabile con i costosi emulatori. In effetti, il BDM offre il vantaggio che non vengono richiesti ulteriori circuiti da integrare con il microcontrollore di cui si vuole realizzare il debug: in sostanza si utilizza uno speciale hardware all’interno del microcontrollore stesso. In particolare nella tabella 1 si mostrano i comandi disponibili del BDM per un eventuale sistema di test. Dalla tabella possiamo vedere che il BDM offre le capability di un tipico debug monitor: scrittura e lettura di registri insieme alla capacità di poter interagire con la memoria. Attraverso il Jtag possiamo interrompere la CPU, ispezionare i suoi registri e la memoria e definire punti di interruzione. In effetti, però, per apprezzare un sistema di questo tipo è anche necessario utilizzare anche un debugger software. Alcuni fornitori di adattatori JTAG offrono anche strumenti software (6), mentre altri si basano su pacchetti open source. Anche se la maggior parte debugger JTAG al giorno d’oggi supportano Linux, è necessario al momento dell’acquisto di un dispositivo a basso costo chiedere l’eventuale compatibilità con Linux, la MMU, il formato binario dei file di Linux ed il supporto dei moduli caricabili. Se supporta a distanza GDB (GNU debugger) e protocollo, si può essere sicuri che si tratta di andare a lavorare. Il PEEDI, ad esempio, è un tipico debugger hardware su JTAG con la piena disponibilità di lavorare con GDB e Linux. In effetti, per le sue caratteristiche, PEEDI è la soluzione ottimale per il debugging hardware di schede con interfaccia JTAG. È molto veloce, economico e con buoni possibilità prestazionali, tanto che può rappresentare l’abbinamento ideale per un sistema linux embedded. Non solo, PEEDI fornisce anche funzionalità di Flash programmer (anche su memorie NAND), figura 1.
Il prodotto di casa Ronetix offre interessanti caratteristiche che permettono un suo utilizzo per un ampia possibilità di processori con un buon supporto di prodotti open-source. Ronetix (2), il costruttore del sistema di test, garantisce anche la possibilità di poter fare il download di immagini firmware da TFTP, FTP, HTTP server o MMC/SDcard e supporta l’interfaccia Telnet a linea di comando e l’interfaccia pannello frontale dei due tasti e del display a 7-segmenti. Non solo, si garantisce anche il funzionamento standalone del FLASH programmer senza PC: i file da programmare sono memorizzati su una schedina MMC/SD ed il controllo si stabilisce attraverso il pannello frontale. PEEDI è però un sistema commerciale e, in quanto tale, deve essere acquistato. In realtà, è anche possibile utilizzare sistemi più di basso profilo o, addirittura, costruirci qualcosa di analogo, ma non è questo lo scopo dell’articolo.
Configurare l’emulatore
Una delle attività da fare è quella, una volta compilato il kernel, di configurare in modo appropriato il nostro JTAG. Di solito il costruttore di un adattatore fornisce il file di configurazione. Per scrivere una nostra configurazione, o per modificarne una già esistente, sarà necessario leggere la documentazione offerta dal produttore. Nel nostro caso, il listato 1 presenta un esempio per configurare il PEEDI.
Minimal debugging Arm Linux
Questa è una soluzione minimale a basso costo; in effetti, non si ha la necessità di utilizzare una strumentazione esterna e complessa ma il tutto si basa su una classica interfaccia con costi estremamente contenuti. Esistono diversi strumenti disponibili in rete ma, in base all’esperienza personale, si potrebbe optare per la proposta di casa Macraigor su porta parallela o ricorrendo alla semplice interfaccia hardware di Olimex (2) utilizzando OCD attraverso una porta USB o seriale, fino ad una versione semiprofessionale con J-Link da Sagger (3) utilizzate in ambito didattico. Di certo, qualsiasi progettista hardware/firmware potrebbe realizzare la semplice schedina di interfaccia, anche se a costi estremamente contenuti si potrebbe utilizzare la versione Olimex. Nella soluzione approntata da Segger, l’openOCD (4) non risulta interamente compatibile con il lato GDB server realizzato dal costruttore e, viceversa, lo stesso openOCD non supporta l’interfaccia J-Link di Segger. Per i nostri scopi esclusivamente didattici è possibile, senza dubbio, utilizzare l’interfaccia Olimex; in effetti, l’ARM-USB-OCD è un unico dispositivo hardware che riesce a coniugare il debugging JTAG su una porta seriale virtuale RS232, unito ad un adattatore di alimentazione. In effetti, la maggior parte dei computer disponibili oggi in commercio non hanno né porte parallele seriali ma solo USB. In questo caso, con ARM-USB-OCD è possibile eseguire il debug delle schede ARM, hanno descritto completo di porta RS232 virtuale con tutti i segnali modem presenti, con la presa di alimentazione che può fornire tre valori di tensione 5V - 9V - 12V intervenendo sui jumper di selezione. In alternativa è anche possibile ricorrere a ARM-JTAG, una soluzione inferiore, con interfaccia JTAG/Parallela. In questo caso tutto quello di cui abbiamo bisogno è, oltre all’interfaccia realizzata da Olimex, l’openOCD (4) e Putty (5). Il tutto si basa su GDB, il debugger GNU, accluso in qualsiasi distribuzione: a questo riguardo possimo utilizzare la sua modalità testuale o grafica, magari ricorrendo al tool DDD (Data Display Debugger) o a Insight. Una volta stabilite correttamente le con nessioni è necessario inizializzare la nostra sessione di lavoro così come riportato nel listato 2. La figura 2 mostra l’esito positivo con il nostro target, mentre la figura 3 mostra l’inizio della nostra sessione di debug.
Una tipica sessione di lavoro con openOCD inizia con il mount dell’usbfs con il comando
sudo mount -t usbfs none /proc/bus/usb
e con
sudo bin/openocd -f flyswatter.cfg -f lpc3250.cfg
si lancia il nostro programma per la gestione dell’interfaccia JTAG. A questo punto possiamo mettere in esecuzione il debugger con il comando
arm-unknown-linux-gnueabi-insight vmlinux
all’interno del quale è necessario impostare i parametri di comunicazione con il target, quali target, hostname e port. In questo caso stiamo utilizzando la variante grafica di GBD di Insight (figura e e figura 4).
Oppure possiamo pensare di lavorare con STM32/ARM Cortex-M3 in ambiente Ubuntu, seguendo la procedura seguente ricorrendo a openOCD.
cd ~/stm32/stm32-example/
/projects/stm_h103/ make clean
make
wget http://fun-tech.se/stm32/
/OpenOCD/openocd.cfg
A questo punto ci colleghiamo al nostro target a facciamo partire il nostro server OpenOCD
xterm -e “openocd -f openocd.cfg” & Attraverso una connessione Telnet …. xterm -e “telnet localhost 4444” &
… possiamo anche mettere in flash il nostro sistema di test, così:
reset halt
flash probe 0
stm32x mass_erase 0
flash write_bank 0 main.bin 0 reset halt
A questo punto dobbiamo far partire il nostro debugger scegliendo la modalità testuale o grafica.
In effetti, con:
arm-none-eabi-gdbtui —eval-
-command=”target remote local-
-host:3333” main.out
si fa partire la sessione testuale, mentre con:
arm-none-eabi-insight —eval-com-
mand=”target remote localhost:3333” main.out
si fa partire la scelta grafica basata su Insight. Per utilizzare openOCD è necessario scrivere un file di configurazione, uno script file che si prende in carico l’inizializzazione dell’hardware e la definizione dell’ambiente di lavoro da utilizzare. A questo proposito è necessario seguire le indicazioni contenute nel manuale di riferimento (7). Con la prima parte si intende configurare l’OpenOCD server negli aspetti di ricezione e invio dei comandi verso il debug residente su host. Il primo comando “telnet_port 4444” specifica la porta sulla quale si intende ascoltare le connessioni telnet in entrata. Con gdb_port si instaurano le comunicazioni a livello GDB con il sistema sotto test. Nella sezione Interface si definiscono i parametri utilizzati dall’interfaccia per il test: in questo caso si utilizza il driver ft2232. Questa parte è utilizzata per inizializzare il driver prima di utilizzare il JTAG. È anche possibile definire la memoria target attraverso il comando memory space che identifica l’indirizzo di partenza della porzione di memoria e la sua dimensione. Il lavoro per configurare openOCD deve essere fatto con particolare cura perché deve rispecchiare l’esatta configurazione fisica del nostro target.
Debugging Arm Linux Kernel
La crescente popolarità di microcontrollori ad alte prestazioni disponibili ad un costo ridotto, incluso l’ARM a 32-bit, ha permesso a Linux di entrare nel mondo dei dispositivi embedded: ecco perché si ha la necessità di approntare sistemi di debug delle sue applicazioni e del kernel. Dal momento che Linux è un vero sistema operativo multi-processo, offre la possibilità di sfruttare una Memory Management Unit (MMU) per dare ad ogni processo uno spazio di memoria separato. Non solo: la MMU è anche responsabile della protezione dello spazio di memoria di ogni processo rispetto agli altri. Per questa ragione diventa necessario offrire e sfruttare sistemi di verifica, leggi debugger, per lavorare correttamente e senza problemi con il kernel e le applicazioni di Linux senza interferire sui processi in esecuzione. Possiamo, oltre all’open source, sfruttare a questo proposito la Ronetix PM9261 con le toolchain GNU ARM-ELF e ARM-LINUX: la scheda Ronetix è fornita con Linux installato e la procedura utilizzata, nei suoi tratti fondamentali, è perfettamente integrabile con qualsiasi board di lavoro. Per prima cosa diventa necessario costruire il nostro kernel e installare la nostra toolchain GNU: alla fine di questa fase si ottiene il file image/vmeImage. A questo punto è necessario installare i diversi moduli del kernel e caricare su target i file ottenuti, vmImage e rootfs.jffs2 utilizzando il bootloader che il costruttore offre sul suo sito, in accordo con le indicazioni presenti nel file di configurazione (listato 1, disponibile per il download gratuito all’indirizzo www.fwonline.it). Il tutto è fatto con l’interfaccia Ethernet via TFTP o FTP:
telnet 192.168.3.100 ; si assume l’IP del PEEDI pari a 192.168.3.100
peedi> flash set 0 ; select the first flash profile (U-BOOT)
peedi> flash erase
peedi> flash program
peedi> flash set 1 ; select the second flash profile (Kernel)
peedi> flash program
peedi> flash set 2 ; selectthe ROOTFS flash profile
peedi> flash erase
peedi> flash program
È possible però ricorrere anche ad uno script file, come:
peedi> run $prog
Il progettista, a questo punto, può iniziare la sua sessione di test con:
arm-elf-insight vmlinux
Nella console gdb:
(gdb) target remote 192.168.3.100:2000 (gdb) set $pc = 0x10000040 ; si assume che u-boot è posizionato all’indirizzo 0x10000000
(gdb) c
Il nostro target è pronto per iniziare questa avventura e attraverso una console seriale è possibile vedere il comportamento di Linux. L’utilizzatore può intervenire sul processo in esecuzione o impostare punti di interruzione nel codice. In caso si volesse inserire un breakpoint prima dell’esecuzione del kernel, non è opportuno utilizzare il comando di halt perché potrebbe sospendere l’esecuzione di un processo utente. Si consiglia, a questo proposito, di impostare un breakpoint all’interno del kernel come nella funzione start_kernel() o alla funzione main(). Per ottenere gli indirizzi necessari può essere utile utilizzare l’utility nm.
(gdb) target remote 192.168.3.100:2000
(gdb) set $pc = 0x10000040 ; si assume che u-boot è posizionato all’inidirizzo 0x10000000
(gdb) hbreak start_kernel ; hardware breakpoint
(gdb) c
L’articolo è molto interessante, perchè didatticamente parlando l’utilizzo del JTAG permette di acquisire molte informazioni circa il funzionamento di Linux; sarebbe interessante avere, in futuro, anche un piccolo esempio pratico basato su una scheda Raspberry Pi, cosìcchè possa essere utilizzato come punto di partenza per un approfondimento personale (insieme alla consultazione di questo articolo).
Grazie!