Registre verrouillé (atomique) en lecture / écriture

8

Je code quelque chose en utilisant le contrôle direct de GPIO, il existe de bonnes ressources pour cela, comme http://elinux.org/RPi_Low-level_peripherals#GPIO_hardware_hacking ; le processus implique l'ouverture ("/ dev / mem"), puis une opération mmap mappe efficacement l'adresse physique souhaitée dans votre espace d'adressage virtuel. Ensuite, vous lisez la section 6 de ce http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf pour savoir comment les E / S sont contrôlées.

Pour passer à la fonction d'une broche (entrée, sortie ou diverses fonctions spéciales), vous devez modifier ces champs de 3 bits dans les registres d'E / S GPFSELx (000 = entrée, 001 = instance de sortie ennemi). Ces opérations de modification sont compilées en opérations avec une charge et un stockage ordinaires (par exemple, pour changer GPIO0 en entrée: * (regptr) & = ~ 7; qui se compile en quelque chose comme

    ldr     r2, [r3, #0]     ; r = *ptr (load r2 from I/O register)
    bic     r2, r2, #7       ; r2 &= ~7
    str     r2, [r3, #0]     ; *ptr = r2 (store r2 to I/O register)

Le problème est le suivant: si une interruption se produit entre la charge et le stockage, et qu'un autre processus ou ISR modifie le même registre d'E / S, l'opération de stockage (basée sur une lecture périmée dans r2) inversera les effets de cette autre opération. La modification de ces registres d'E / S doit donc être effectuée avec une opération de lecture / modification / écriture atomique (verrouillée). Les exemples que j'ai vus n'utilisent pas une opération verrouillée.

Étant donné que ces registres d'E / S ne sont généralement modifiés que lors de la configuration de quelque chose, il est peu probable que des problèmes se produisent, mais «jamais» vaut toujours mieux que «peu probable». De plus, si vous avez une application où vous dénigrez des bits pour émuler une sortie à collecteur ouvert, cela (pour autant que je sache) implique de programmer la sortie sur 0, puis de la basculer entre la sortie (pour le bas) ou l'entrée ( pour off / high). Donc, dans ce cas, il y aurait des mods fréquents dans ces registres d'E / S, et des modifications dangereuses seraient beaucoup plus susceptibles de causer un problème.

Donc, il y a probablement un ARM `` comparer et définir '' ou une opération similaire qui peut être utilisée ici pour faire cela, quelqu'un peut-il m'indiquer cela et comment y arriver à partir du code C?

[Remarque, rien de spécial n'est nécessaire lorsque vous avez programmé une E / S en sortie et que vous la modifiez simplement de 0 à 1 ou vice versa; car il y a un registre d'E / S sur lequel vous écrivez, pour mettre les bits sélectionnés à 1 et un autre pour effacer les bits sélectionnés à 0. Aucune lecture / écriture n'est nécessaire pour cette opération, donc il n'y a aucun risque d'interruption].

greggo
la source
Peut-être que je n'ai pas bien compris cela, mais depuis que vous l'ouvrez, /dev/memil semble que votre code soit du code de l'espace utilisateur. Je ne pense pas que dans un système d'exploitation moderne, il faut faire attention aux interruptions qui changent les valeurs des registres dans le code de l'espace utilisateur. Je crois que ce ne serait pas un problème, même dans le code de l'espace noyau, car Linux restaure tous les registres lorsque le gestionnaire d'interruption termine son travail.
Krzysztof Adamski
1
Ma compréhension est que le chargement / stockage va à un registre physique via le mappage VM configuré par mmap (un registre d'E / S, pas un registre CPU). Dans ce cas, il n'y a aucune raison qu'un autre processus ou un pilote de périphérique ne puisse pas faire la même chose simultanément et modifier le même registre. (Je suppose que cela modifie un ensemble différent de bits dans le reg, ou clairement nous avons de plus gros problèmes). Il n'y a pas de sauvegarde / restauration des registres d'E / S comme pour les registres de processeur.
greggo
J'ai édité un peu pour clarifier le `` registre d'E / S '' par opposition à
R2,
Je peux voir votre point maintenant. C'est plus une préemption qu'un problème de gestion des interruptions. L'utilisation d'opérations atomiques aiderait au moins lorsque deux processus tentent de définir des bits différents en même temps.
Krzysztof Adamski
ldrex / strex ne fonctionne pas sur la mémoire non mise en cache. Le moniteur exclusif s'appuie sur les caches. En fait, il était auparavant possible de verrouiller le processeur dur si vous tentiez cela sur un système Cortex-A9 SMP, par exemple.
thinkfat

Réponses:

3

J'ai regardé cela, l'ARM a des instructions 'ldrex et' strex ', le strex retournera un résultat d'échec si l'exclusivité est perdue (ou peut avoir été perdue) depuis le ldrex, qui comprend un changement de contexte (ou un autre processeur modifiant le même s'inscrire dans un environnement multiprocesseur). Donc, cela peut être fait en utilisant cela; si le strex échoue, vous bouclez et refaites l'opération (avec un nouveau ldrex).

réf: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ch01s02s01.html

Les routines ci-dessous semblent fonctionner sur le Raspberry Pi (en ce qu'elles génèrent l'assembleur que j'attendais; et que l'effet sur les bits lorsque je les utilise est comme prévu. Je n'ai pas vérifié qu'ils protègent contre le problème de changement de contexte) . Notez qu'il s'agit de fonctions en ligne plutôt que de fonctions, elles doivent donc être placées dans un fichier d'en-tête.

[ EDIT : Cela ne fonctionne pas pour le but discuté, il semble que ce soit interdit d'une manière ou d'une autre. Si j'utilise ces routines où * addr est une variable ordinaire, cela fonctionne très bien. Lorsque je l'utilise où * addr pointe vers un registre GPIO mappé, le processus obtient une erreur de bus. (Quand je change le ldrex / strex en ldr / str et désactive la boucle do, cela fonctionne alors). Il semble donc que le moniteur exclusif ARM ne soit pas en mesure ou ne soit pas configuré pour fonctionner sur les registres d'E / S mappés en mémoire, et la question reste ouverte.]

//
// Routines to atomically modify 32-bit registers using ldrex and strex.
// 
//
//
//  locked_bic_to_reg( volatile unsigned * addr, unsigned val )
//                 *addr &= ~val
//  locked_or_to_reg( volatile unsigned * addr, unsigned val )
//                 *addr |= val
//   locked_insert_to_reg( volatile unsigned * addr, unsigned val, int width, int pos )
//           insert 'width' lsbs of 'val into *addr, with the lsb at bit 'pos'.
//           Caller must ensure 1 <= width <= 32 and 0 <= pos < 32-width
//
//
static inline void
locked_bic_to_reg( volatile unsigned * addr, unsigned val )
{
    int fail;
    do{
        asm volatile ("ldrex r0,[%1]\n"
           "   bic r0,r0,%2\n"
           "   strex %0,r0,[%1]": "=r"(fail) : "r"(addr), "r"(val): "r0" );
    }while(fail!=0);
}
static inline void
locked_or_to_reg( volatile unsigned * addr, unsigned val)
{
    int fail;
    do{
        asm volatile ("ldrex r0,[%1]\n"
           "   orr r0,r0,%2\n"
           "   strex %0,r0,[%1]": "=r"(fail) : "r"(addr), "r"(val): "r0" );
    }while(fail!=0);
}

static inline void
locked_insert_to_reg( volatile unsigned * addr, unsigned val, int width, int pos )
{
    int fail;
    if(width >=32 ) {
        *addr = val;    // assume wid = 32, pos = 0;
    }else{
        unsigned m=(1<<width)-1;
        val = (val&m) << pos;   // mask and position
        m <<= pos;

        do{
            asm volatile ("ldrex r0,[%1]\n"
               "   bic r0,r0,%2\n"   /// bic with mask
               "   orr r0,r0,%3\n"    // or result
               "   strex %0,r0,[%1]": "=r"(fail) : "r"(addr), "r"(m), "r"(val): "r0" );
        }while(fail!=0);
    }
}
greggo
la source
Il me semble que c'est le genre de chose qui devrait être dans les fichiers .h spécifiques au processeur, mais aucun fichier .h sous / usr / include ou / usr / lib / gcc / arm-linux-gnueabihf / contient la chaîne 'ldrex '. Peut-être un intégré , ou l'un des en-têtes du noyau?
greggo
1
ldrex / strex sont destinés au partage multi-core des ressources (ram partagé). swp est traditionnellement utilisé pour le verrouillage à cœur unique d'une ressource à cœur unique. ldrex / strex, se trouve fonctionner comme une solution à cœur unique (EN FONCTION DU VENDEUR DE PUCE) donc il est mal utilisé. il semble cependant fonctionner sur le processeur raspberry pi.
old_timer