Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

mercoledì 11 ottobre 2023

Ricomincio da DEB
come creare un Debian Package - pt.2

Robertino: Ma mammina dice che io ho i complessi nella testa.
Gaetano: E foss' 'o Ddio! Quali complessi? Tu tieni l'orchestra intera 'ncapa, Robbe'.

E allora, dove eravamo rimasti? Ah, si! Abbiamo ricominciato da tre (anzi, da DEB), con un articolo che invece di descrivere "come sviluppare" (nel nostro amato C) una applicazione, descriveva "come distribuire" la stessa applicazione (nel nostro amato Linux). E noi non faremo come il Robertino qui sopra, che aveva un po' di confusione in testa su quale via intraprendere, ma faremo come gli consigliava l'impagabile Gaetano, e prenderemo una via molto creativa, scriveremo uno shell script per creare automaticamente un Debian Package!

...ma possibile che non hai mai scritto un installer?...

La prima parte dell'articolo l'avevamo conclusa su questa lista di cose da fare (con il grado di difficoltà incluso!):

  • costruire l'albero di installazione (facile!)
  • copiarci dentro eseguibili e servizi (facilissimo!)
  • scrivere il file control (si può fare...)
  • scrivere gli eventuali script di pre/post installazione (spoiler: in questo semplice esempio ci serve solo lo script postinst)

E avevo anticipato che nella seconda parte (e cioè qui) avremmo cercato di fare il tutto con lo spirito del programmatore, e cioè avremmo presentato uno shell script che esegue i punti precedenti in modo automatico, e che diventerà la base (con piccoli adattamenti) per altri script di creazione per qualsiasi applicazione che svilupperemo... un compito troppo ambizioso? In realtà è molto semplice!.

Ok, andiamo per passi: vediamo di descrivere i punti della lista e poi, alla fine, automatizzeremo il tutto. Cominciamo con i primi 2 punti (condensati in 1 solo)!

1) costruire l'albero di installazione + copiarci dentro eseguibili e servizi

Nel nostro caso l'albero di installazione avrà questa forma:

├── DEBIAN
│ ├── control
│ └── postinst
├── etc
│ └── systemd
│ └── system
|── cansetup.service
│ └── canrecv.service
└── usr
└── bin
|── cansetup.sh
└── canrecv

per cui, una volta creato (da qualche parte nella nostra directory di lavoro) un albero della forma descritta, sarà un gioco da ragazzi copiarci dentro i file presentati nella prima parte dell'articolo (a parte i file control e postinst che non abbiamo ancora visto). Notare che questo primo punto lo faremo, automaticamente, attraverso il nostro shell script di creazione del package, quello che vedremo tra poco.

2) scrivere il file control

Il file control  ha, come visto nel precedente articolo, una struttura minima abbastanza semplice. Una struttura che si può complicare, a piacere, aggiungendo altre linee di dettagli che, in alcuni casi, possono anche essere utili, ma direi che per la nostra applicazione canrecv  (che è un semplice CAN Server) è sufficiente un file control come questo:

Package: canrecv
Version: 1.0-amd64
Architecture: amd64
Depends: libc6 (>= 2.34)
Maintainer: A.Abate <artcprogramming@gmail.com>
Description: canrecv DEB package
installa la applicazione canrecv e i relativi servizi systemd

dove si possono cambiare, se necessario, la versione Version (N.B.: deve cominciare sempre con un numero) e l'architettura Architecture (nel''esempio ho messo quella su cui lavoro io). Le dipendenze Depends sono quelle ottenute con il procedimento descritto nello scorso articolo, il responsabile Mantainer  sarà l'autore dell'installer stesso e la descrizione corta Description sarà quella adatta all'installer in oggetto, seguita nella linea successiva dalla descrizione lunga, che può essere anche multi-linea, con linee che cominciano sempre con uno spazio. Facile, no?

3) scrivere gli eventuali script di pre/post installazione.

Per il nostro package è necessario solo uno script di post-installazione, uno script che installa e attiva i nuovi servizi. E vediamolo!

#!/bin/bash

# set uscita immediata su errore
set -e

# abilita e avvia il servizio cansetup
systemctl enable cansetup.service
systemctl start cansetup.service
systemctl daemon-reload

# abilita e avvia il servizio canrecv
systemctl enable canrecv.service
systemctl start canrecv.service
systemctl daemon-reload

Questo script è necessario, in questo caso, perché il nostro package contiene dei servizi systemd, che devono essere opportunamente installati nel sistema. Il procedimento consiste nell'abilitazione del servizio, seguita dal suo avvio e dal riavvio del daemon di systemd (tutto questo ripetuto per i due servizi inclusi nel package). Ovviamente per altri tipi di applicazione uno script di postinst  potrebbe non essere necessario o fare ben altre cose. Comunque i tipi di script disponibili li trovate elencati nella prima parte dell'articolo.

A questo punto abbiamo anche i file che ci mancavano da aggiungere al nostro albero creato qui sopra, quindi cosa ci manca? Ma ci manca il nostro (oramai mitico) shell script per automatizzare il tutto! Vediamolo!

#!/bin/bash

# check degli argomenti
if [ $# -eq 0 ]; then
echo "numero argomenti errato"
echo "uso: $0 nome_release (e.g.: $0 1.0-amd64)"
exit 1
fi

# set della directory di lavoro
mydir=canrecv-$1

# compilo l'ultima versione di canrecv.c
cd ..
gcc canrecv.c -o install/base/canrecv
cd install

# creo l'albero di directory destinazione
mkdir $mydir
mkdir $mydir/DEBIAN
mkdir $mydir/usr
mkdir $mydir/usr/local
mkdir $mydir/usr/local/bin
mkdir $mydir/etc
mkdir $mydir/etc/systemd
mkdir $mydir/etc/systemd/system

# copio i file nella directory destinazione
cp base/control $mydir/DEBIAN
cp base/postinst $mydir/DEBIAN
cp base/cansetup.sh $mydir/usr/local/bin
cp base/canrecv $mydir/usr/local/bin
cp base/cansetup.service $mydir/etc/systemd/system
cp base/canrecv.service $mydir/etc/systemd/system

# costruisco il DEB package
dpkg-deb --build --root-owner-group $mydir/

Avrete notato che è uno script molto compatto che esegue solo le operazioni indispensabili (opportunamente commentate):

  • check degli argomenti
  • set della directory di lavoro
  • compilo l'ultima versione di canrecv.c (nel package deve entrare sempre l'ultima versione, no?)
  • creo l'albero di directory destinazione (usando delle semplici istruzioni mkdir)
  • copio i file nella directory destinazione (usando delle semplici istruzioni cp)
  • costruisco il DEB package (usando il classico comando Linux dpkg-deb)

Semplice, no? E come si può usare lo script?  Io suggerisco suggerisco di creare un ambiente di creazione di questo tipo:

├── base
│   ├── canrecv
│   ├── canrecv.service
│   ├── cansetup.service
│   ├── cansetup.sh
│   ├── control
│   └── postinst
└── buildpkg.sh

con una directory "base"  dove risiede la base di installazione (che è, anche, la destinazione della istruzione di compilazione presente nello script, non so se avete notato). Entrando in questo ambiente di creazione ed eseguendo il comando:

aldo@Linux $ ./buildpkg.sh 1.0-amd64
dpkg-deb: generazione del pacchetto "canrecv" in "canrecv-1.0-amd64.deb".
aldo@Linux $

otterremo che il nostro albero di creazione si espanderà così:

├── base
│   ├── canrecv
│   ├── canrecv.service
│   ├── cansetup.service
│   ├── cansetup.sh
│   ├── control
│   └── postinst
├── buildpkg.sh
├── canrecv-1.0-amd64
│   ├── DEBIAN
│   │   ├── control
│   │   └── postinst
│   ├── etc
│   │   └── systemd
│   │   └── system
│   │   ├── canrecv.service
│   │   └── cansetup.service
│   └── usr
│   └── local
│   └── bin
│   ├── canrecv
│   └── cansetup.sh
└── canrecv-1.0-amd64.deb

ovvero, troveremo il nostro Debian Package in forma espansa (la directory canrecv-1.0-amd64) e compressa (il file canrecv-1.0-amd64.deb), e quest'ultima ci permetterà di distribuire il nostro installer a chi lo vorrà!

E qui direi che l'articolo si può dire concluso. Abbiamo ricominciato da tre (anzi, da DEB), e per una volta abbiamo parlato di distribuzione invece che di programmazione. Quasi quasi nel prossimo articolo, per cavalcare l'onda, potrei parlare si una argomento toccato di striscio nella prima parte, e cioè la compilazione abbinata alla distribuzione (Autotool). Beh, vedremo, e in ogni caso sarà una sorpresa! (spero lieta...).

Ciao, e al prossimo post!

lunedì 25 settembre 2023

Ricomincio da DEB
come creare un Debian Package - pt.1

Gaetano: Chello ch'è stato è stato, basta! Ricomincio da tre!
Lello: Da zero.
Gaetano: Eh?
Lello: Da zero! Ricominci da zero!
GaetanoNossignore, ricomincio da... cioè, tre cose me so' riuscite ind'a vita, pecchè aggià perdere pure cheste?! Aggià ricominciare da zero? Da tre!

Dopo il tormentone del CAN bus (un articolo in ben 4 parti!) ho deciso di ricominciare da zero, anzi... Ricomincio da tre, come il grande Massimo Troisi nella sua opera prima capolavoro. Ricominciare in che senso? Nel senso che, almeno per questa volta, invece di parlare della mia (nostra) amata programmazione in C parleremo di un argomento collegato e un po' trascurato, che corrisponde alla domanda: "Ma dopo aver sviluppato una applicazione Linux come la distribuisco?". Ecco, ci sono vari metodi, ma oggi ho voglia di parlare del più elementare e facile da usare, e cioè il "pacchetto di distribuzione" e, in particolare, mi soffermerò sul tipo più usato, il Debian Package (DEB Package o Pacchetto Debian per gli amici) che si usa nelle distribuzioni Linux più diffuse (Ubuntu, Mint, Debian, etc.).

...ma perché devo ricominciare da zero? Io ricomincio da DEB!...

Come detto sopra, ci sono vari metodi per distribuire una applicazione Linux e, prima di parlare del Debian Package, bisogna fare alcune precisazioni: indubbiamente, il metodo più rigoroso di distribuzione è basato su autotool: si distribuisce tutto l'ambiente di sviluppo (sorgenti, makefile e file di configurazione), e quindi si compila e installa usando il classico comando "./configure && make install". Questo metodo è rigoroso perché si auto-adatta (grazie ad autotool) alla macchina destinazione su cui può essere in uso una qualsiasi versione di Linux, tanto l'applicazione viene compilata e linkata localmente con le risorse a disposizione.

Ma usare autotool è un metodo molto specialistico (credo sia evidente... è roba da programmatori ah ah ah) mentre gli utenti "normali"  vogliono installare e usare in quattro e quattr’otto, (e se gli utenti hanno un passato Windows non ne parliamo neanche...). E quindi anche nel mondo Linux si usano degli installer che si chiamano "pacchetti di distribuzione", e che sono facilissimi da usare ma sono un po' meno flessibili di autotool: non si adattano automaticamente alla macchina (contengono l' applicazione precompilata) e quindi in alcuni casi potrebbero non essere installabili al primo colpo: ad esempio è possibile che la applicazione da installare usi una libreria di una versione differente di quella già presente nella macchina, e questo genera la segnalazione di un errore. In generale, però, è sempre abbastanza facile risolvere eventuali problemi, se l'installer è ben fatto.

Ah, dimenticavo: ovviamente un Debian Package ha un doppio uso: installare e disinstallare (e anche questa seconda attività è importante, no?). E allora, come si crea un pacchetto di questo tipo? Vediamo prima di tutto come è fatto: il Debian Package è un file di tipo archivio compresso che, internamente, ha la seguente struttura tipica:

├── DEBIAN
│ ├── control
│ ├── preinst
│ ├── postinst
│ ├── prerm
│ └── postrm
├── etc
│ └── systemd
│ └── system
│ └── myservice.service
└── usr
└── bin
└── myapp

ossia: ci sono tre directory principali (DEBIAN, etc e usr) che hanno il seguente uso:

  • DEBIAN: contiene i file di controllo e di pre/post installazione/disinstallazione:
    • control: è il file di controllo master, quello che guida l'installazione, infatti è l'unico file obbligatorio.
    • preinst: è uno shell script preparatorio eseguito automaticamente prima dell'installazione vera e propria. È opzionale.
    • postinst: è uno shell script di finalizzazione eseguito automaticamente dopo l'installazione. È opzionale.
    • prerm: è uno shell script preparatorio eseguito automaticamente prima della disinstallazione vera e propria. È opzionale.
    • postrm: è uno shell script di finalizzazione eseguito automaticamente dopo la disinstallazione. È opzionale.
  • lib: contiene l'albero di directory che replica l'albero dei servizi systemd della macchina destino: in questo lib/systemd/system si copieranno i servizi (file con estensione .service) necessari alla nostra applicazione (questa directory lib è opzionale, serve solo se il Package installa anche dei servizi systemd).
  • usr: contiene l'albero di directory che replica l'albero delle applicazioni della macchina destino: in questo usr/bin si copieranno gli eseguibili che compongono la nostra applicazione (nell'esempio sopra è una sola applicazione che si chiama "myapp").

E come è fatto un file control? È un file di testo che contiene alcune linee che descrivono il Package, e le linee che, come minimo, devono essere presenti sono:

  • Package: il nome dell'applicazione da installare.
  • Version: la versione dell'applicazione da installare.
  • Maintainer – il nome e l'indirizzo email del responsabile del Package.
  • Description – una descrizione corta dell'applicazione. Di solito questa è l'ultima linea del control: sotto questa linea si può aggiungere una descrizione più lunga che deve, però, cominciare con uno spazio.

si possono aggiungere molte altre linee con funzioni particolari, in particolare due linee che non dovrebbero mai mancare sono queste (N.B.: in realtà la lista dei campi required/recommended/optional varia un po' tra un manuale e l'altro... diciamo che usando i quattro campi qui sopra più i due qui sotto non si dovrebbero avere problemi):

  • Architecture – la architettura dove può correre la applicazione (i.e.: all, oppure amd64 oppure i386, oppure sparc... e molte altre. all si usa per le installazioni compatibili con qualsiasi macchina).
  • Depends: descrive le librerie (con le rispettive versioni) indispensabili all'installazione.

La linea Depends è un poco particolare: ci sono vari metodi per cercare quali sono le librerie indispensabili alla nostra applicazione, e uno dei metodi più interessanti usa un meccanismo dello stesso ambiente di generazione del Package, però con alcune stranezze (la prima volta che l'ho usato mi è costato un po' farlo funzionare). Ma uno dei miei compiti è svelare i trucchi, no? E vediamoli! Bisogna creare un mini-albero secondario che somiglia a quello principale mostrato sopra, un mini-albero come questo:

├── debian
│ └── control
└── usr
└── bin
└── myapp

sempre supponendo che la applicazione si chiami "myapp". Notare che debian è in minuscolo e che il file control  deve contenere solo questa linea:

source: myapp

dopodiché,  posizionandosi nella root-directory del mini-albero , si può eseguire il comando dpkg-shlibdeps ottenendo un risultato di questo tipo:

aldo@Linux $ dpkg-shlibdeps -O usr/local/bin/myapp
dpkg-shlibdeps: Avviso: binaries to analyze should already be installed in their package's directory
shlibs:Depends=libc6 (>= 2.34)
aldo@Linux $

Il risultato è, quindi, la lista delle librerie indispensabili (nell'esempio sopra c'è solo: libc6 dalla ver.2.34 in su) che potremo aggiungere in DEBIAN/control nella linea Depends. Dopodiché il mini-albero che abbiamo usato possiamo cancellarlo (ha già esaurito il suo compito) e possiamo tornare a usare l'albero principale.

Dopo questa introduzione teorica è l'ora di passare a un caso pratico, no? Vedendo un caso reale molti dubbi vengono cancellati automaticamente (beh, almeno a me succede spesso). Visto che ce l'abbiamo fresco in mente e abbiamo già il codice disponibile negli ultimi articoli pubblicati, cercheremo di creare un Debian Package che installa nel sistema canrecv, che è un CAN Server (e scusate se ritiro fuori un argomento appena visto!).

Ah, fino a qui ho dato per scontato che tutti sappiano cos'è un servizio systemd (e magari ci tornerò in futuro con un articolo) comunque in questo caso è sufficiente sapere che è la maniera più "moderna" di assegnare compiti a Linux, compiti del tipo: "al boot avvia questo", "se si interrompe riavvialo", ecc. Prima si usavano altri metodi (non so se a tutti è familiare il vecchio e sempre valido "init" derivato da "SysVinit"), ma da qualche anno a questa parte si usa quasi sempre systemd per cui lo useremo anche noi.

E torniamo al punto: l'obiettivo è, quindi, che il nostro Debian Package installi queste cose nel sistema:

  • un eseguibile cansetup.sh
  • un eseguibile canrecv
  • un servizio systemd  cansetup.service che produce l'esecuzione del CAN setup al boot  del sistema
  • un servizio systemd  canrecv.service che produce l'avvio del Server al boot  del sistema

Il codice di canrecv (canrecv.c) l'ho già pubblicato e lo possiamo trovare qui: è un Server molto minimale (riceve un messaggio ed esce) però per questo semplice esempio va più che bene (compito a casa: modificare canrecv.c per ottenere un CAN Server che riceva messaggi in loop). Anche lo shell script  cansetup.sh lo abbiamo visto in quest'altro articolo, ed è indispensabile al funzionamento del Server, visto che prepara il dispositivo virtual CAN del sistema. Ci mancano solo i due servizi: questo è cansetup.service:

[Unit]
Description=setup del vcan0 device
After=network.target

[Service]
ExecStart=/usr/local/bin/cansetup.sh

[Install]
WantedBy=default.target
mentre questo è canrecv.service:
[Unit]
Description=avvia il processo canrecv
After=cansetup.service

[Service]
ExecStart=/usr/local/bin/canrecv

[Install]
WantedBy=default.target

Come potete vedere sono abbastanza semplici (oserei dire: auto-esplicativi) e, comunque, spiegare come funzionano non è l'argomento di questo articolo (ripeto la semi-promessa: magari ci tornerò in futuro). L'importante è lo scopo di questi servizi, e quello credo che sia abbastanza chiaro. Quindi non ci resta che:

  • costruire l'albero di installazione (facile!)
  • copiarci dentro eseguibili e servizi (facilissimo!)
  • scrivere il file control (si può fare...)
  • scrivere gli eventuali script di pre/post installazione (spoiler: in questo semplice esempio ci serve solo lo script postinst)

E qui potrei concludere l'articolo aggiungendo le parti appena elencate... ma qui viene fuori la mia (nostra) anima di programmatore: perché fare a mano le operazioni descritte quando si possono automatizzare? Non posso concludere un articolo senza aggiungere una minima parte di programmazione, no?  E allora, nella seconda parte che arriverà prossimamente (giuro), vi presenterò un utile shell script che ho scritto per creare e ricreare (a piacere) il nostro Debian Package: è facilmente adattabile ad ogni uso futuro ed è una buona base per qualsiasi tipo di applicazione che abbisogna di un installer. Quindi per il momento vi saluto e, come sempre, vi consiglio di non trattenere il respiro nell'attesa!

Ciao, e al prossimo post! 

mercoledì 23 agosto 2023

Guardians of the CAN bus
come usare il CAN bus in C - pt.4

Stakar Ogord: Uniformi della OrgoCorp. Dovrete indossarle per muovervi nell'Orgosfera senza attirare l'attenzione.
Drax: Questo non è il mio colore.
Stakar Ogord: Cosa hai detto?
Drax: Stona con i miei occhi.
Ed eccoci alla quarta (e ultima!) parte di Guardians of the CAN bus, un ciclo di articoli ispirati al bellissimo Guardiani della Galassia Vol.3 (ripassatevi la prima, la seconda e la terza parte, please…). Nel dialogo qui sopra c'è, di nuovo, tutta la vena comica del film, che viaggia come al solito in parallelo alla parte seria. Mi ripeterò: è po’ come il CAN bus, che è così ben studiato ed efficace che rende divertente scrivere Software che lo usa, salvo quando devi risolvere un problema molto complicato e allora entri nella fase seria e drammatica... In questa quarta parte scopriremo gli ultimi segreti che ci mancano per diventare dei veri e propri Guardians of the CAN bus!

...ma anche il CAN bus stona coi tuoi occhi?...

Come ho fatto notare nella chiusura dell'ultimo articolo oramai ci manca solo il CAN FD per ultimare il nostro viaggio nei segreti del CAN bus. E allora veniamo subito al dunque: cosa è il CAN FD? Il CAN FD (CAN Flexible Data Rate) è un'espansione del CAN bus "classico" (Classical CAN) ed è definito dalla norma ISO 11898-1:2015. Il CAN FD facilita non solo l'invio di volumi di dati più grandi, visto che il limite di 8 byte del CAN classico è stato aumentato a 64 byte. Inoltre, offre la possibilità di una velocità di trasmissione più elevata nella fase dei dati, raggiungendo agevolmente i 5 Mbit/s. Anche del CAN FD esiste una versione extended, quindi, alla fin fine, abbiamo quattro tipi di frame, quelli mostrati in questa figura riassuntiva:

can bus versions

Si può commentare che, con questa versione FD, il CAN bus si snatura un poco, passando da semplice e affidabilissimo "bus di campo" a un rango superiore, quasi quello dei protocolli di rete sofisticati (tipo Ethernet) che permettono di trasferire notevoli moli di dati: comunque, anche se "snaturato", è assolutamente pronto all'uso e quando è il caso si può/deve usare, perché offre delle grandi prestazioni.

Ok, con ben quattro tipi disponibili sembrerebbe abbastanza complicato scrivere Software CAN "compatibile" (o quasi) passando da un tipo all'altro senza fare troppi cambi... Ma per questo c'è il nostro amico SocketCAN! Come abbiamo già visto nell'articolo precedente, passare da CAN classic a CAN extended è decisamente semplice, e sono felice di comunicarvi che, grazie alla flessibilità di SocketCAN, anche il CAN FD si usa in maniera molto semplificata. Vediamo come: non so se ricordate, ma nel primo articolo del ciclo avevo mostrato come è descritto (nell'header can.h) un frame CAN di SocketCAN, ve lo ripropongo:

/**
* struct can_frame - basic CAN frame structure
* @can_id: the CAN ID of the frame and CAN_*_FLAG flags, see above.
* @can_dlc: the data length field of the CAN frame
* @data: the CAN frame payload.
*/
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* data length code: 0 .. 8 */
__u8 data[8] __attribute__((aligned(8)));
};

Ecco, questa sopra è una versione abbastanza datata (Linux 2.6) che non trattava ancora CAN FD. In Linux 3.7 troviamo la prima versione "moderna" che include il CAN FD:

/**
* struct can_frame - basic CAN frame structure
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @can_dlc: frame payload length in byte (0 .. 8) aka data length code
* N.B. the DLC field from ISO 11898-1 Chapter 8.4.2.3 has a 1:1
* mapping of the 'data length code' to the real payload length
* @data: CAN frame payload (up to 8 byte)
*/
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
__u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));
};

/**
* struct canfd_frame - CAN flexible data rate frame structure
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @len: frame payload length in byte (0 .. CANFD_MAX_DLEN)
* @flags: additional flags for CAN FD
* @__res0: reserved / padding
* @__res1: reserved / padding
* @data: CAN FD frame payload (up to CANFD_MAX_DLEN byte)
*/
struct canfd_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 len; /* frame payload length in byte */
__u8 flags; /* additional flags for CAN FD */
__u8 __res0; /* reserved / padding */
__u8 __res1; /* reserved / padding */
__u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
};

Poi, in Linux 5.11, il can_frame è diventato così (mentre il canfd_frame è rimasto invariato):

/**
* struct can_frame - Classical CAN frame structure (aka CAN 2.0B)
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @len: CAN frame payload length in byte (0 .. 8)
* @can_dlc: deprecated name for CAN frame payload length in byte (0 .. 8)
* @__pad: padding
* @__res0: reserved / padding
* @len8_dlc: optional DLC value (9 .. 15) at 8 byte payload length
* len8_dlc contains values from 9 .. 15 when the payload length is
* 8 bytes but the DLC value (see ISO 11898-1) is greater then 8.
* CAN_CTRLMODE_CC_LEN8_DLC flag has to be enabled in CAN driver.
* @data: CAN frame payload (up to 8 byte)
*/
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
union {
/* CAN frame payload length in byte (0 .. CAN_MAX_DLEN)
* was previously named can_dlc so we need to carry that
* name for legacy support
*/
__u8 len;
__u8 can_dlc; /* deprecated */
};
__u8 __pad; /* padding */
__u8 __res0; /* reserved / padding */
__u8 len8_dlc; /* optional DLC for 8 byte payload length (9 .. 15) */
__u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));
};

Come si nota il campo can_dlc è stato deprecato (ma non cancellato) e il nome è stato allineato a quello usato nel CAN FD, proprio per permettere la scrittura semplificata di Software per entrambi i tipi senza troppi cambi!. Insomma, alla fine della fiera, ora è possibile riscrivere gli esempi del primo articolo (cansend.c e canrecv.c) per usare "Classic" o FD definendo una semplice variabile di compilazione (che ho chiamato CAN_FD). Cominciamo con canfdsend.c, vai col codice!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

// definizione del tipo CanFrame
#ifdef CAN_FD
typedef struct canfd_frame CanFrame; // CAN FD
#else
typedef struct can_frame CanFrame; // CAN classic
#endif

// canfdsend - funzione main()
int main(int argc, char *argv[])
{
// creo il socket
int sockfd;
if ((sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) {
// errore socket()
printf("%s: errore socket() (%s)\n", argv[0], strerror(errno));
return EXIT_FAILURE;
}

// set degli attributi di i/o
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", "vcan0");
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1) {
// errore ioctl()
printf("%s: errore ioctl() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}

#ifdef CAN_FD
// set del modo CAN_FD
int enable_canfd = 1;
if ( setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
&enable_canfd, sizeof(enable_canfd)) == -1) {

// errore setsockopt()
printf("%s: errore setsockopt() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}
#endif

// set attributi dell'indirizzo
struct sockaddr_can addr;
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;

// assegna l'indirizzo al socket
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
// errore bind()
printf("%s: errore bind() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}

// compongo il frame
CanFrame frame;
memset(&frame, 0, sizeof(CanFrame));
frame.can_id = 0x100U;
frame.len = 8;
snprintf(frame.data, sizeof(frame.data), "0123456");

// invio il frame
if (write(sockfd, &frame, sizeof(CanFrame)) != sizeof(CanFrame) == -1) {
// errore write()
printf("%s: errore write() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}

close(sockfd);
return EXIT_SUCCESS;
}

ed ora passiamo a canfdrecv.c!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

// definizione del tipo CanFrame
#ifdef CAN_FD
typedef struct canfd_frame CanFrame; // CAN FD
#else
typedef struct can_frame CanFrame; // CAN classic
#endif

// canfdrecv - funzione main()
int main(int argc, char *argv[])
{
// creo il socket
int sockfd;
if ((sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) {
// errore socket()
printf("%s: errore socket() (%s)\n", argv[0], strerror(errno));
return EXIT_FAILURE;
}

// set degli attributi di i/o
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", "vcan0");
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1) {
// errore ioctl()
printf("%s: errore ioctl() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}

#ifdef CAN_FD
// set del modo CAN_FD
int enable_canfd = 1;
if ( setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
&enable_canfd, sizeof(enable_canfd)) == -1) {

// errore setsockopt()
printf("%s: errore setsockopt() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}
#endif

// set attributi dell'indirizzo
struct sockaddr_can addr;
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;

// assegna l'indirizzo al socket
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
// errore bind()
printf("%s: errore bind() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}

// ricevo il frame
CanFrame frame;
if (read(sockfd, &frame, sizeof(CanFrame)) == -1) {
// errore read()
printf("%s: errore read() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}

// mostro il frame ricevuto
printf("id:0x%x dlc:%d data: ", frame.can_id, frame.len);
for (int i = 0; i < frame.len; i++)
printf("%c ", frame.data[i]);

printf("\n");

close(sockfd);
return EXIT_SUCCESS;
}

Visto come è facile? Nel codice ci sono gli appositi "#ifdef CAN_FD" che decidono che parte di codice usare. Quindi per ottenere la versione "Classic" si può compilare con:

aldo@Linux $ gcc canfdsend.c -o canfdsend
aldo@Linux $ gcc canfdrecv.c -o canfdrecv

Mentre, per ottenere la versione FD si deve compilare con:

aldo@Linux $ gcc canfdsend.c -o canfdsend -DCAN_FD
aldo@Linux $ gcc canfdrecv.c -o canfdrecv -DCAN_FD

E i cambi nel codice sono veramente limitatissimi, grazie alla flessibilità dell'interfaccia SocketCAN: basta definire un nuovo tipo (che ho chiamato CanFrame) da usare al posto della struct can_frame (o della struct canfd_frame), così i cambi nel codice sono minimi, anche grazie al fatto che il campo can_dlc ora si chiama len in entrambe le strutture, e quindi, in pratica, l'unico vero cambio nel codice è questo:

#ifdef CAN_FD
// set del modo CAN_FD
int enable_canfd = 1;
if ( setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
&enable_canfd, sizeof(enable_canfd)) == -1) {

// errore setsockopt()
printf("%s: errore setsockopt() (%s)\n", argv[0], strerror(errno));
close(sockfd);
return EXIT_FAILURE;
}
#endif

che forza l'uso, nel socket aperto precedentemente, dei frame di tipo FD. Insomma, come avevo promesso è veramente semplice scrivere codice "compatibile" con entrambi i tipi "Classic" e FD. E il CAN FD extended? Vabbé, ve lo lascio come utile esercizio, basti sapere che è sufficiente modificare il codice per FD con le minime modifiche che avevo mostrato nello scorso articolo al riguardo del CAN extended.

Ok, penso che con questo possa bastare. Il nostro viaggio nei misteri del CAN bus e del suo uso attraverso SocketCAN è terminato, ora possiamo qualificarci come Guardians of the CAN bus! Giuro che non ci sarà (per il momento...) una quinta parte, quindi nel prossimo articolo parleremo d'altro. E non trattenete il fiato nell'attesa, mi raccomando!

Ciao, e al prossimo post!