openlang

Сброс одного флага через |= сбросил все

|= на регистре состояния со сбросом записью единицы сбрасывает больше, чем названный бит.

#include <stdint.h>

#define UART_SR   (*(volatile uint32_t *)0x40004000u)
#define SR_ORE    (1u << 3) /* overrun, write 1 to clear */

void clear_overrun_wrong(void) { UART_SR |= SR_ORE; }
void clear_overrun(void)       { UART_SR = SR_ORE; }

Флаг переполнения сбрасывается записью 1. Запись 0 ничего не делает. UART_SR |= SR_ORE сначала читает весь регистр. Каждый такой флаг, взведённый в этот момент, читается как 1 и записывается обратно как 1, так что сбрасываются все. Ошибка кадра, до которой обработчик ещё не добрался, пропадает вместе с ними.

На -O2 для Cortex-M неправильный вариант - это ldr, orr, str. Правильный - один str маски. volatile сохраняет оба доступа в программе. Что железо делает с записанными обратно битами, он не говорит.

Разговор продолжается 5

Следуя доводам
  1. Ссылка на ответ

    Со стороны C правило уже, чем его обычно помнят. Про E1 |= E2 стандарт говорит, что от E1 = E1 | E2 оно отличается только тем, что E1 вычисляется один раз. С volatile E1 это всё равно одно чтение и одна запись, два доступа, и стандарт требует, чтобы оба произошли. Он не требует, чтобы это была одна инструкция, и ничего не говорит об атомарности. Так что чтение, изменение и запись здесь - ровно то, чего просит язык, и никакая настройка компилятора не превратит их в простую запись, которая нужна регистру.

  2. Ссылка на ответ

    Да, и одна инструкция тоже не спасёт. Обычная ловушка - бит-бэндинг на Cortex-M3. Запись в псевдоним бита в коде выглядит как одна запись, а шина всё равно читает и пишет слово целиком, так что остальные флаги сбрасываются точно так же. Исправлять надо в исходнике. Пишите маску через =, а для регистров со смешанными типами флагов сначала сверьтесь в справочном руководстве с типом доступа каждого бита.

  3. Ссылка на ответ

    Забавно. Попробовал мысленно сдвинуть время. Приёмная сторона взводит новый флаг ошибки после ldr, но до str. Чтение его не видело, этот бит уходит обратно как 0, а запись 0 ничего не делает, так что новый флаг выживает. Выходит, вариант с |= теряет ровно те флаги, что уже были взведены, и сохраняет пришедшие внутри окна? Почти наоборот тому, что я бы угадал, безвредной оказывается как раз гонка.

  4. Ссылка на ответ

    Для битов со сбросом записью единицы ваше прочтение верно. Новый флаг выживает в обоих вариантах. Но у простой записи есть своя цена, и она зависит от остального регистра. UART_SR = SR_ORE пишет 0 во все остальные биты. Это безвредно, только если каждый из них запись 0 игнорирует. Если в том же регистре есть обычное поле для чтения и записи, например бит включения или режима, простая запись его сбросит. В справочном руководстве для каждого бита указан тип доступа, и запись корректна только после того, как каждый бит регистра сверен с ним.

  5. Ссылка на ответ

    Та же операция чтения, изменения и записи встречается ещё в одной форме, потому что карты регистров часто пишут структурами. Запись в битовое поле volatile-структуры - это доступ к единице хранения, в которой оно лежит, так что на практике компилятор читает эту единицу, меняет биты и пишет её обратно. Все флаги со сбросом записью единицы в этой единице сбрасываются вместе с ним. Кроме того, стандарт оставляет порядок размещения битовых полей внутри единицы на усмотрение реализации, и это вторая причина держать битовые поля подальше от описаний регистров и работать масками по volatile uint32_t.

Другие стороны того же вопроса

  1. Разворот C-указателя через .? - это утверждение

    .? на указателе из C - это утверждение, а не проверка. Оно говорит, что null здесь невозможен. В сборке с проверками безопасн…

    Ответы 5zig
  2. В колбэке из C пропадает арена, которую я настроил

    C-библиотека вызывает колбэк в моём коде на Odin, и колбэк не видит контекст, который я настроил перед регистрацией. У proc "…

    Ответы 8odinc
  3. Как выглядит проверка канарейки в листинге -O2

    Три маленькие функции на C, gcc -O2 -fstack-protector-strong -S -masm=intel, x86-64 Linux. read_line и read_count получают од…

    Ответы 0assemblyc