Gesperrtes (Atomic) Register Lesen / Schreiben

8

Ich codiere etwas mithilfe der direkten Steuerung von GPIO. Dafür gibt es einige gute Ressourcen, z. B. http://elinux.org/RPi_Low-level_peripherals#GPIO_hardware_hacking . Der Prozess beinhaltet open ("/ dev / mem") und dann ordnet eine mmap-Operation die gewünschte physische Adresse effektiv Ihrem virtuellen Adressraum zu. Dann lesen Sie Abschnitt 6 dieser http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf , um herauszufinden, wie die E / A gesteuert werden.

Um zur Funktion eines Pins (Eingabe oder Ausgabe oder verschiedene Sonderfunktionen) zu wechseln, ändern Sie diese 3-Bit-Felder in den GPFSELx-E / A-Registern (000 = Eingabe, 001 = Ausgabe der Feindinstanz). Diese Änderungsoperationen werden zu Operationen mit normalem Laden und Speichern kompiliert (z. B. um GPIO0 in Eingabe zu ändern: * (regptr) & = ~ 7;, was zu so etwas kompiliert wird

    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)

Das Problem ist folgendes: Wenn ein Interrupt zwischen Laden und Speichern auftritt und ein anderer Prozess oder ISR dasselbe E / A-Register ändert, werden durch die Speicheroperation (basierend auf einem veralteten Einlesen in r2) die Auswirkungen dieser anderen Operation zurückgesetzt. Das Ändern dieser E / A-Register muss also wirklich mit einer atomaren (gesperrten) Lese- / Änderungs- / Schreiboperation erfolgen. Die Beispiele, die ich gesehen habe, verwenden keine gesperrte Operation.

Da diese E / A-Register im Allgemeinen nur beim Einrichten geändert werden, ist es unwahrscheinlich, dass Probleme auftreten, aber "nie" ist immer besser als "unwahrscheinlich". Wenn Sie eine Anwendung haben, in der Sie Bit-Bashing durchführen, um einen Open-Collector-Ausgang zu emulieren, müssen Sie (soweit ich das beurteilen kann) den Ausgang auf 0 programmieren und dann zwischen Ausgang (für niedrig) oder Eingang (für niedrig) umschalten. für aus / hoch). In diesem Fall würden diese E / A-Register häufig geändert, und unsichere Änderungen würden mit größerer Wahrscheinlichkeit ein Problem verursachen.

Es gibt also wahrscheinlich eine ARM-Operation zum Vergleichen und Setzen oder eine ähnliche Operation, die hier verwendet werden kann. Kann mich jemand darauf hinweisen und wie kann dies mit C-Code geschehen?

[Beachten Sie, dass nichts Besonderes erforderlich ist, wenn Sie eine E / A als Ausgabe programmiert haben und sie nur von 0 auf 1 oder umgekehrt ändern. Da es ein E / A-Register gibt, in das Sie schreiben, um ausgewählte Bits auf 1 und ein anderes, um ausgewählte Bits auf 0 zu setzen, ist für diesen Vorgang kein Lesen / Schreiben erforderlich, daher besteht keine Gefahr durch Interrupts.

Greggo
quelle
Vielleicht habe ich das nicht richtig verstanden, aber seit Sie es öffnen /dev/mem, scheint es, dass Ihr Code Userspace-Code ist. Ich denke nicht, dass man in einem modernen Betriebssystem vorsichtig sein muss, wenn Interrupts die Registerwerte im Userspace-Code ändern. Ich glaube, dass dies selbst im Kernel-Space-Code kein Problem wäre, da Linux alle Register wiederherstellt, wenn der Interrupt-Handler seinen Job beendet.
Krzysztof Adamski
1
Nach meinem Verständnis wird das Laden / Speichern über die von mmap eingerichtete VM-Zuordnung (ein E / A-Register, kein CPU-Register) in ein physisches Register verschoben. In diesem Fall gibt es keinen Grund, warum ein anderer Prozess oder ein Gerätetreiber nicht gleichzeitig dasselbe tun und dasselbe Register ändern kann. (Ich gehe davon aus, dass ein anderer Satz von Bits in der Registrierung geändert wird, oder wir haben eindeutig größere Probleme). Es gibt kein Speichern / Wiederherstellen von E / A-Registern wie bei Prozessorregistern.
Greggo
Ich habe ein wenig bearbeitet, um 'E / A-Register' im Gegensatz zu r2 usw. zu verdeutlichen
greggo
Ich kann Ihren Standpunkt jetzt sehen. Es ist jedoch eher eine Vorabentscheidung als ein Problem bei der Interrupt-Behandlung. Die Verwendung atomarer Operationen würde zumindest dann helfen, wenn zwei Prozesse gleichzeitig versuchen, unterschiedliche Bits zu setzen.
Krzysztof Adamski
ldrex / strex funktioniert nicht mit nicht zwischengespeichertem Speicher. Der exklusive Monitor ist auf die Caches angewiesen. Tatsächlich war es früher möglich, die CPU hart zu sperren, wenn Sie dies beispielsweise auf einem Cortex-A9-SMP-System versuchten.
Thinkfat

Antworten:

3

Ich habe dies untersucht. Der ARM verfügt über die Anweisungen 'ldrex' und 'strex'. Der strex gibt ein Fehlerergebnis zurück, wenn die Exklusivität verloren geht (oder möglicherweise verloren gegangen ist), da der ldrex einen Kontextwechsel enthält (oder einen anderen Prozessor, der diesen modifiziert) in einer Multiprozessor-Umgebung registrieren). So kann es gemacht werden; Wenn der Strex ausfällt, führen Sie eine Schleife durch und wiederholen den Vorgang (mit einem neuen ldrex).

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

Die folgenden Routinen scheinen auf dem Raspberry Pi zu funktionieren (da sie den von mir erwarteten Assembler generieren und die Auswirkungen auf die Bits, wenn ich sie verwende, wie erwartet sind. Ich habe nicht überprüft, ob sie vor dem Problem des Kontextwechsels schützen). . Beachten Sie, dass dies eher Inlines als Funktionen sind. Sie sollten daher in eine Header-Datei eingefügt werden.

[ EDIT : Dies funktioniert nicht für den besprochenen Zweck, es scheint, dass es irgendwie nicht erlaubt ist . Wenn ich diese Routinen verwende, bei denen * addr eine gewöhnliche Variable ist, funktioniert es einwandfrei. Wenn ich es verwende, wobei * addr auf ein zugeordnetes GPIO-Register verweist, wird beim Prozess ein Busfehler angezeigt. (Wenn ich ldrex / strex in ldr / str ändere und die do-Schleife deaktiviere, funktioniert es). Es scheint also, dass der exklusive ARM-Monitor nicht in der Lage oder nicht eingerichtet ist, auf speicherabgebildeten E / A-Registern zu funktionieren, und die Frage bleibt offen.]

//
// 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
quelle
Mir scheint, dies sollte in den prozessorspezifischen .h-Dateien enthalten sein, aber keine .h-Datei unter / usr / include oder / usr / lib / gcc / arm-linux-gnueabihf / enthält die Zeichenfolge 'ldrex '. Vielleicht ein eingebauter oder einer der Kernel-Header?
Greggo
1
ldrex / strex sind für die Multi-Core-gemeinsame Nutzung von Ressourcen (Shared RAM) vorgesehen. swp wird traditionell zum Single-Core-Locking einer Single-Core-Ressource verwendet. ldrex / strex funktioniert zufällig als Single-Core-Lösung (ABHÄNGIG VOM CHIP-VERKÄUFER) und wird daher missbraucht. es scheint jedoch auf dem Himbeer-Pi-Prozessor zu funktionieren.
old_timer