Bloqueado (Atômico) Registre leitura / gravação

8

Estou codificando algo usando o controle direto do GPIO, existem alguns bons recursos para isso, como http://elinux.org/RPi_Low-level_peripherals#GPIO_hardware_hacking ; o processo envolve aberto ("/ dev / mem") e, em seguida, uma operação mmap efetivamente mapeia o endereço físico desejado em seu espaço de endereço virtual. Em seguida, leia a seção 6 deste http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf para descobrir como as E / S são controladas.

Para mudar para a função de um pino (entrada, saída ou várias funções especiais), você modifica esses campos de 3 bits nos registros de E / S GPFSELx (000 = entrada, 001 = instância de saída). Essas operações de modificação são compiladas para operações com carga e armazenamento comuns (por exemplo, para alterar GPIO0 para entrada: * (regptr) & = ~ 7; que compila para 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)

O problema é o seguinte: se ocorrer uma interrupção entre a carga e a loja, e outro processo ou ISR modificar o mesmo registro de E / S, a operação de armazenamento (com base em uma leitura obsoleta em r2) reverterá os efeitos dessa outra operação. Portanto, a alteração desses registros de E / S realmente precisa ser feita com uma operação de leitura / modificação / gravação atômica (bloqueada). Os exemplos que eu vi não usam uma operação bloqueada.

Como esses registros de E / S geralmente são alterados apenas ao configurar algo, é improvável que ocorram problemas, mas 'nunca' é sempre melhor do que 'improvável'. Além disso, se você tiver um aplicativo no qual você está fazendo um bit-bashing para emular uma saída de coletor aberto, (tanto quanto eu sei), isso envolve programar a saída para 0 e alterná-la entre saída (baixa) ou entrada ( para off / high). Portanto, nesse caso, haveria modificações frequentes nesses registros de E / S, e as modificações inseguras teriam muito mais probabilidade de causar um problema.

Portanto, provavelmente existe uma operação de comparação e configuração do ARM ou similar que pode ser usada aqui para fazer isso, alguém pode me indicar isso e como fazer isso acontecer com o código C?

[Observe que nada de especial é necessário quando você programa uma E / S como saída e apenas a altera de 0 para 1 ou vice-versa; como existe um registro de E / S no qual você escreve, para definir os bits selecionados como 1 e outro para limpar os bits selecionados como 0. Nenhuma leitura / gravação é necessária para esta operação, portanto, não há risco de interrupções].

Greggo
fonte
Talvez eu não tenha entendido isso corretamente, mas, como você abre /dev/mem, parece que seu código é código do espaço do usuário. Eu não acho que em qualquer sistema operacional moderno seja necessário ter cuidado com as interrupções na alteração dos valores dos registros no código do espaço do usuário. Eu acredito que isso não seria um problema, mesmo no código de espaço do kernel, pois o Linux restaura todos os registros quando o manipulador de interrupções termina seu trabalho.
precisa saber é o seguinte
11
Meu entendimento é que o carregamento / armazenamento vai para um registro físico por meio do mapeamento da VM configurado pelo mmap (um registro de E / S, não um registro da CPU). Nesse caso, não há razão para que outro processo ou um driver de dispositivo não possa fazer a mesma coisa simultaneamente e modificar o mesmo registro. (Presumo que esteja modificando um conjunto diferente de bits no registro, ou claramente temos problemas maiores). Não há salvar / restaurar registros de E / S como existem para registros de processador.
Greggo
Eu editei um pouco para esclarecer 'I / O registo' em oposição a r2 etc.
Greggo
Eu posso ver o seu ponto agora. É mais uma preempção do que um problema de manipulação de interrupção. Usar operações atômicas ajudaria pelo menos quando dois processos estão tentando definir bits diferentes ao mesmo tempo.
precisa saber é o seguinte
O ldrex / strex não funciona na memória não armazenada em cache. O monitor exclusivo depende dos caches. De fato, costumava ser possível travar a CPU com força, se você tentasse isso em um sistema SMP Cortex-A9, por exemplo.
thinkfat

Respostas:

3

Analisei isso, o ARM possui instruções 'ldrex e' strex ', o strex retornará um resultado de falha se a exclusividade for perdida (ou pode ter sido perdida) desde o ldrex, que inclui uma opção de contexto (ou outro processador que modifica o mesmo registrar em um ambiente com vários processadores). Então, isso pode ser feito usando isso; se o strex falhar, você executa um loop e refaça a operação (com um novo ldrex).

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

As rotinas abaixo parecem funcionar no Raspberry Pi (na medida em que geram o assembler que eu esperava; e que o efeito nos bits quando os uso são os esperados. Não verifiquei se eles protegem contra o problema da troca de contexto) . Observe que essas linhas são inline, e não funções, portanto devem ser colocadas em um arquivo de cabeçalho.

[ EDIT : Isso não funciona para o propósito discutido, parece que é proibido de alguma forma. Se eu usar essas rotinas em que * addr é uma variável comum, funciona bem. Quando o uso onde * addr é apontado para um registro GPIO mapeado, o processo recebe um erro de barramento. (Quando altero o ldrex / strex para ldr / str e desativo o loop do, ele funciona). Portanto, parece que o monitor exclusivo do ARM não pode ou não está configurado para funcionar em registros de E / S mapeados na memória, e a questão permanece em aberto.]

//
// 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
fonte
Parece-me que esse é o tipo de coisa que deve estar nos arquivos .h específicos do processador, mas nenhum arquivo .h em / usr / include ou / usr / lib / gcc / arm-linux-gnueabihf / contém a string 'ldrex ' Talvez um builtin , ou um dos cabeçalhos do kernel?
Greggo
11
O ldrex / strex é destinado ao compartilhamento de recursos em vários núcleos (ram compartilhado). O swp é tradicionalmente usado para bloqueio de núcleo único de um recurso de núcleo único. O ldrex / strex funciona por acaso como uma solução de núcleo único (DEPENDENDO DO VENDEDOR DE CHIP), por isso é mal utilizado. parece funcionar no processador raspberry pi.
old_timer