Заблокированный (атомарный) регистр для чтения / записи

8

Я кодирую что-то, используя прямой контроль над GPIO, для этого есть несколько хороших ресурсов, таких как http://elinux.org/RPi_Low-level_peripherals#GPIO_hardware_hacking ; процесс включает open ("/ dev / mem"), а затем операция mmap эффективно отображает нужный физический адрес в ваше виртуальное адресное пространство. Затем вы читаете раздел 6 этого http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf, чтобы узнать о том, как контролируется ввод-вывод.

Чтобы изменить функцию вывода (вход, выход или другие специальные функции), вы изменяете эти 3-битные поля в регистрах ввода-вывода GPFSELx (000 = вход, 001 = экземпляр выходного объекта). Эти операции модификации скомпилированы для операций с обычной загрузкой и сохранением (например, чтобы изменить GPIO0 на input: * (regptr) & = ~ 7; который компилируется в нечто вроде

    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)

Проблема заключается в следующем: если между загрузкой и сохранением происходит прерывание, и другой процесс или ISR изменяет тот же регистр ввода-вывода, операция сохранения (на основе устаревшего чтения в r2) будет возвращать эффекты этой другой операции. Таким образом, изменение этих регистров ввода / вывода действительно необходимо выполнить с помощью атомарной (заблокированной) операции чтения / изменения / записи. Примеры, которые я видел, не используют заблокированную операцию.

Поскольку эти регистры ввода-вывода обычно меняются только при настройке чего-либо, маловероятно, что возникнут проблемы, но «никогда» всегда лучше, чем «маловероятно». Кроме того, если у вас есть приложение, в котором вы обрабатываете биты для эмуляции вывода с открытым коллектором, то (насколько я могу судить) это включает в себя программирование выхода на 0, а затем переключение его между выходом (для низкого уровня) или входом ( для выключенного / высокого). Таким образом, в этом случае будут частые моды для этих регистров ввода / вывода, и небезопасные модификации будут с гораздо большей вероятностью вызывать проблемы.

Итак, возможно, для этого можно использовать ARM «сравнить и установить» или аналогичную операцию, может ли кто-нибудь указать мне на это, и как это сделать из кода C?

[Примечание: ничего особенного не требуется, если вы запрограммировали ввод / вывод как выход и просто изменили его с 0 на 1 или наоборот; поскольку существует регистр ввода / вывода, в который вы записываете, для установки выбранных битов на 1, а другой для сброса выбранных битов на 0. Для этой операции не требуется чтение / запись, поэтому нет опасности от прерываний].

greggo
источник
Может быть, я не правильно понял это, но, так как вы открываете, /dev/memкажется, что ваш код - это код пользовательского пространства. Я не думаю, что в любой современной ОС нужно быть осторожным с прерываниями, меняющими значения регистров в коде пользовательского пространства. Я считаю, что это не будет проблемой даже в коде пространства ядра, поскольку Linux восстанавливает все регистры, когда обработчик прерываний завершает свою работу.
Кшиштоф Адамски
1
Насколько я понимаю, загрузка / сохранение идет в физический регистр через отображение виртуальной машины, установленное mmap (регистр ввода-вывода, а не регистр процессора). В этом случае нет причин, по которым другой процесс или драйвер устройства не могут одновременно выполнять одно и то же и изменять один и тот же регистр. (Я предполагаю, что это изменяет другой набор битов в регистре, или, очевидно, у нас есть большие проблемы). Сохранение / восстановление регистров ввода-вывода отсутствует, как и для регистров процессора.
Грегго,
Я немного отредактировал, чтобы уточнить «регистр ввода / вывода», в отличие от r2 и т. Д.
greggo
Теперь я понимаю вашу точку зрения. Однако это скорее упреждение, чем проблема обработки прерываний. Использование атомарных операций поможет, по крайней мере, когда два процесса пытаются установить разные биты одновременно.
Кшиштоф Адамски,
ldrex / strex не работает с не кэшированной памятью. Эксклюзивный монитор опирается на кеши. Фактически, раньше было возможно жестко заблокировать процессор, если вы пытались сделать это, например, в системе Cortex-A9 SMP.
Thinkfat

Ответы:

3

Я смотрел на это, ARM имеет инструкции 'ldrex и' strex ', strex будет возвращать результат сбоя, если исключительность потеряна (или, возможно, была потеряна) после ldrex, который включает переключение контекста (или другой процессор, модифицирующий тот же самый). зарегистрироваться в многопроцессорной среде). Так что это можно сделать с помощью этого; если strex не удается, вы зацикливаетесь и снова делаете операцию (с новым ldrex).

ссылка: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ch01s02s01.html

Представленные ниже подпрограммы, кажется, работают на Raspberry Pi (поскольку они генерируют ассемблер, который я ожидал; и что влияние на биты, когда я их использую, является ожидаемым. Я не проверял, что они защищают от проблемы переключения контекста) , Обратите внимание, что это встроенные функции, а не функции, поэтому они должны быть помещены в заголовочный файл.

[ РЕДАКТИРОВАТЬ : Это не работает для обсуждаемой цели, кажется, это как-то запрещено. Если я использую эти подпрограммы, где * addr - обычная переменная, она работает нормально. Когда я использую его, где * addr указывает на сопоставленный регистр GPIO, процесс получает ошибку шины. (Когда я изменяю ldrex / strex на ldr / str и отключаю цикл do, он тогда работает). Таким образом, кажется, что эксклюзивный монитор ARM не может или не настроен на работу с регистрами ввода-вывода с отображением в памяти, и вопрос остается открытым.]

//
// 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
источник
Мне кажется, это то, что должно быть в специфичных для процессора файлах .h, но нет файла .h в каталоге / usr / include или / usr / lib / gcc / arm-linux-gnueabihf /, содержащем строку 'ldrex ». Может быть встроенный или один из заголовков ядра?
Грегго
1
ldrex / strex предназначены для многоядерного совместного использования ресурсов (shared ram). swp традиционно используется для одноядерной блокировки одноядерного ресурса. ldrex / strex, работает как одноядерное решение (в зависимости от поставщика чипов), поэтому он используется не по назначению. похоже, он работает на процессоре Raspberry Pi.
old_timer