Type de pointeur en écriture seule

J’écris des logiciels pour un système embarqué.

Nous utilisons des pointeurs pour accéder aux registres d’un périphérique FPGA.
Certains des registres sont en lecture seule, tandis que d’autres sont en écriture seule.

Les registres en écriture seule produiront des valeurs indéfinies en lecture.

Je veux définir un type de pointeur qui permettra au compilateur de détecter lors de la lecture des valeurs à partir d’un registre en écriture seule (aka dereferencing).

Un pointeur en écriture seule peut-il être créé en utilisant uniquement la syntaxe du langage C?
(Nous développons le premier prototype en C, mais passons au C ++ en 2ème génération.)

Comment créer un pointeur efficace en écriture seule en C ++? (N’oubliez pas qu’il ne s’agit pas de suivre les éléments de la mémoire dynamic, mais d’accéder aux adresses matérielles.)

Ce code est utilisé sur un système embarqué où la sécurité et la qualité sont des préoccupations majeures.

J’écrirais probablement une petite classe de wrapper pour chacun:

 template  class read_only { T volatile *addr; public: read_only(int address) : addr((T *)address) {} operator T() volatile const { return *addr; } }; template  class write_only { T volatile *addr; public: write_only(int address) : addr ((T *)address) {} // chaining not allowed since it's write only. void operator=(T const &t) volatile { *addr = t; } }; 

Au moins en supposant que votre système dispose d’un compilateur raisonnable, je m’attendrais à ce que les deux soient optimisés afin que le code généré ne se distingue pas de l’utilisation d’un pointeur brut. Usage:

 read_only x(0x1234); write_only y(0x1235); y = x + 1; // No problem x = y; // won't comstack 

J’ai travaillé avec beaucoup de matériel, dont certains ont des registres “en lecture seule” ou “écriture seule” (ou des fonctions différentes selon que vous lisez ou que vous écrivez dans le registre, ce qui rend amusant de décider) reg | = 4; “au lieu de se souvenir de la valeur, définissez le bit 2 et écrivez la nouvelle valeur, comme vous le feriez. Rien de tel que d’essayer de déboguer du matériel sur lequel des bits aléatoires apparaissent et disparaissent des registres non lisibles! ) Jusqu’à présent, je n’ai vu aucune tentative de blocage des lectures dans un registre en écriture seule ou dans des registres en lecture seule.

A propos, ai-je dit qu’avoir des registres qui sont “en écriture seulement” est vraiment une mauvaise idée, parce que vous ne pouvez pas lire pour vérifier si le logiciel a correctement défini le registre, ce qui rend le débogage vraiment difficile – n’aime pas le débogage de problèmes concrets qui pourraient être vraiment facilités par deux lignes de code VHDL ou Verilog.

Si vous avez un certain contrôle sur la disposition du registre, je vous suggère de placer les registres “readonly” à une adresse alignée sur 4 Ko, et “writeonly” s’enregistre dans une autre adresse alignée sur 4 Ko [plus de 4 Ko sont corrects]. Ensuite, vous pouvez programmer le contrôleur de mémoire du matériel pour empêcher l’access.

Ou, laissez le matériel produire une interruption si des registres qui ne sont pas censés être lus sont en cours de lecture ou si des registres qui ne sont pas censés être écrits sont écrits. Je présume que le matériel produit des interruptions à d’autres fins?

Les autres suggestions faites en utilisant différentes solutions C ++ sont correctes, mais cela n’empêche pas vraiment une personne qui souhaite utiliser directement les registres, donc si c’est vraiment un problème de sécurité (plutôt que de le rendre difficile), alors vous devriez avoir matériel pour protéger contre le mauvais usage du matériel.

J’utiliserais une combinaison de structures pour représenter le registre et une paire de fonctions pour les gérer.

Dans un fpga_register.h vous auriez quelque chose comme

 #define FPGA_READ = 1; #define FPGA_WRITE = 2; typedef struct register_t { char permissions; } FPGARegister; FPGARegister* fpga_init(void* address, char permissions); int fpga_write(FPGARegister* register, void* value); int fpga_read(FPGARegister* register, void* value); 

avec READ et WRITE dans xor pour exprimer des permissions.

Que dans le fpga_register.c vous définiriez une nouvelle structure

 typedef struct register_t2 { char permissions; void * address; } FPGARegisterReal; 

de sorte que vous lui retournez un pointeur au lieu d’un pointeur vers FPGARegister sur fpga_init .

Ensuite, sur fpga_read et fpga_write vous vérifiez les permissions et

  • si l’opération est autorisée, restaurez le FPGARegister de l’argument à un FPGARegisterReal , exécutez l’action souhaitée (définissez ou lisez la valeur) et renvoyez un code de réussite
  • si l’opération n’est pas autorisée, retournez simplement un code d’erreur

De cette façon, personne, y compris le fichier d’en-tête, ne pourra accéder à la structure FPGARegisterReal et n’aura donc pas access direct à l’adresse du registre. De toute évidence, on pourrait le pirater, mais je suis tout à fait sûr que de tels piratages intentionnels ne sont pas vos préoccupations réelles.

En C, vous pouvez utiliser des pointeurs sur des types incomplets pour empêcher tout déréférencement:


 /* writeonly.h */ typedef struct writeonly *wo_ptr_t; 

 /* writeonly.c */ #include "writeonly.h" struct writeonly { int value }; /*...*/ FOO_REGISTER->value = 42; 

 /* someother.c */ #include "writeonly.h" /*...*/ int x = FOO_REGISTER->value; /* error: deref'ing pointer to incomplete type */ 

Seul writeonly.c , ou en général tout code qui a une définition struct writeonly , peut déréférencer le pointeur. Ce code, bien sûr, peut aussi lire accidentellement la valeur, mais au moins tous les autres codes ne peuvent pas déréférencer les pointeurs tous ensemble, tout en pouvant faire passer ces pointeurs et les stocker dans des variables, des tableaux et des structures.

writeonly.[ch] pourrait fournir une fonction pour écrire une valeur.

Je ne vois aucune manière élégante de le faire en C. Cependant, je vois un moyen de le faire:

 #define DEREF_PTR(type, ptr) type ptr; \ typedef char ptr ## _DEREF_PTR; #define NO_DEREF_PTR(type, ptr) type ptr; \ #define DEREFERENCE(ptr) \ *ptr; \ {ptr ## _DEREF_PTR \ attempt_to_dereference_pointer_ ## ptr;} int main(int argc, char *argv[]) { DEREF_PTR(int*, x) NO_DEREF_PTR(int*, y); DEREFERENCE(x); DEREFERENCE(y); // will throw an error } 

Cela a l’avantage de vous donner une vérification d’erreur statique. Bien sûr, en utilisant cette méthode, vous devrez modifier toutes vos déclarations de pointeurs pour utiliser des macros, ce qui n’est probablement pas très amusant.

Edit: Comme décrit dans les commentaires.

 #define READABLE_PTR(type, ptr) type ptr; \ typedef char ptr ## _READABLE_PTR; #define NON_READABLE_PTR(type, ptr) type ptr; \ #define GET(ptr) \ *ptr; \ {ptr ## _READABLE_PTR \ attempt_to_dereference_non_readable_pointer_ ## ptr;} #define SET(ptr, value) \ *ptr = value; int main(int argc, char *argv[]) { READABLE_PTR(int*, x) NON_READABLE_PTR(int*, y); SET(x, 1); SET(y, 1); int foo = GET(x); int bar = GET(y); // error }