Bloqueado (Atómico) Registro de lectura / escritura

8

Estoy codificando algo usando el control directo de GPIO, hay algunos buenos recursos para esto, como http://elinux.org/RPi_Low-level_peripherals#GPIO_hardware_hacking ; el proceso implica abrir ("/ dev / mem") y luego una operación de mmap mapea efectivamente la dirección física deseada en su espacio de dirección virtual. Luego, lea la sección 6 de este http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf para averiguar cómo se controlan las E / S.

Para cambiar a la función de un pin (entrada, salida o varias funciones especiales), modifique estos campos de 3 bits en los registros de E / S GPFSELx (000 = entrada, 001 = instancia de salida de enemigo). Estas operaciones de modificación se compilan en operaciones con carga y almacenamiento ordinarios (por ejemplo, para cambiar GPIO0 a input: * (regptr) & = ~ 7; que se compila en algo como

    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)

El problema es este: si se produce una interrupción entre la carga y el almacenamiento, y otro proceso o ISR modifica el mismo registro de E / S, la operación de almacenamiento (basada en una lectura obsoleta en r2) revertirá los efectos de esa otra operación. Por lo tanto, cambiar estos registros de E / S realmente debe hacerse con una operación atómica (bloqueada) de lectura / modificación / escritura. Los ejemplos que he visto no usan una operación bloqueada.

Dado que estos registros de E / S generalmente se cambian solo cuando se configura algo, es poco probable que ocurran problemas, pero 'nunca' siempre es mejor que 'improbable'. Además, si tiene una aplicación en la que está aplicando bit-bashing para emular una salida de colector abierto, entonces (por lo que puedo decir) esto implica programar la salida a 0 y luego cambiarla entre salida (para baja) o entrada ( para apagado / alto). Entonces, en ese caso, habría modificaciones frecuentes en estos registros de E / S, y las modificaciones inseguras tendrían muchas más probabilidades de causar un problema.

Entonces, probablemente haya una operación ARM 'compare and set' u operación similar que se puede usar aquí para hacer esto, ¿alguien puede señalarme eso y cómo hacer que eso suceda desde el código C?

[Tenga en cuenta que no se necesita nada especial cuando ha programado una E / S como salida y solo la cambia de 0 a 1 o viceversa; dado que hay un registro de E / S en el que escribe, para establecer los bits seleccionados en 1 y otro para borrar los bits seleccionados en 0. No se necesita lectura / escritura para esta operación, por lo que no hay peligro de interrupciones].

Greggo
fuente
Tal vez no entendí esto correctamente, pero desde que lo abres /dev/memparece que tu código es un código de espacio de usuario. No creo que en ningún sistema operativo moderno haya que tener cuidado con las interrupciones que cambian los valores de los registros en el código del espacio de usuario. Creo que esto no sería un problema incluso en el código de espacio del núcleo ya que Linux restaura todos los registros cuando el controlador de interrupciones finaliza su trabajo.
Krzysztof Adamski el
1
Tengo entendido que la carga / almacenamiento va a un registro físico a través de la asignación de VM configurada por mmap (un registro de E / S, no un registro de CPU). En este caso, no hay razón para que otro proceso, o un controlador de dispositivo no pueda estar haciendo lo mismo al mismo tiempo y modificando el mismo registro. (Supongo que está modificando un conjunto diferente de bits en el registro, o claramente tenemos problemas más grandes). No hay guardar / restaurar registros IO como lo hay para los registros del procesador.
greggo
He editado un poco para aclarar 'Registro de E / S' en lugar de r2
etc.Greggo
Puedo ver tu punto ahora. Sin embargo, es más una prioridad que un problema de manejo de interrupciones. El uso de operaciones atómicas ayudaría al menos cuando dos procesos intentan establecer diferentes bits al mismo tiempo.
Krzysztof Adamski
ldrex / strex no funciona en la memoria no almacenada en caché. El monitor exclusivo se basa en los cachés. De hecho, solía ser posible bloquear la CPU con fuerza si lo intentaba en un sistema Cortex-A9 SMP, por ejemplo.
thinkfat

Respuestas:

3

Investigué esto, el ARM tiene instrucciones 'ldrex y' strex ', el strex devolverá un resultado de falla si se pierde la exclusividad (o puede haberse perdido) desde el ldrex, que incluye un cambio de contexto (u otro procesador que modifica el mismo registrarse en un entorno multiprocesador). Entonces se puede hacer usando eso; si el strex falla, realiza un bucle y vuelve a realizar la operación (con un ldrex nuevo).

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

Las rutinas a continuación parecen funcionar en la Raspberry Pi (en el sentido de que generan el ensamblador que esperaba; y que el efecto en los bits cuando los uso es el esperado. No he verificado que protejan contra el problema del cambio de contexto) . Tenga en cuenta que se trata de líneas en lugar de funciones, por lo que deben colocarse en un archivo de encabezado.

[ EDITAR : Esto no funciona para el propósito discutido, parece que no está permitido de alguna manera. Si uso estas rutinas donde * addr es una variable ordinaria, funciona bien. Cuando lo uso donde * addr apunta a un registro GPIO mapeado, el proceso obtiene un error de bus. (Cuando cambio el ldrex / strex a ldr / str y deshabilito el bucle do, entonces funciona). Por lo tanto, parece que el monitor exclusivo ARM no puede o no está configurado para funcionar en registros de E / S mapeados en memoria, y la pregunta permanece abierta.]

//
// 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
fuente
Me parece que este es el tipo de cosas que deberían estar en los archivos .h específicos del procesador, pero ningún archivo .h en / usr / include o / usr / lib / gcc / arm-linux-gnueabihf / contiene la cadena 'ldrex '. ¿Quizás un incorporado , o uno de los encabezados del núcleo?
greggo
1
ldrex / strex están diseñados para compartir múltiples núcleos de recursos (RAM compartida). swp se usa tradicionalmente para el bloqueo de un solo núcleo de un recurso de un solo núcleo. ldrex / strex, funciona como una solución de núcleo único (DEPENDIENDO DEL VENDEDOR DE CHIP), por lo que se utiliza incorrectamente. Sin embargo, parece funcionar en el procesador Raspberry Pi.
old_timer