Что такое использование __iomem в linux при написании драйверов устройств?

Я видел, что __iomem используется для хранения возвращаемого типа ioremap(), но я использовал u32 в архитектуре ARM для него, и он работает хорошо.

Итак, какая разница делает __iomem здесь? И в каких обстоятельствах я должен использовать его точно?

Ответы

Ответ 1

Множество отливок типа просто "хорошо работают". Однако это не очень строго. Ничто не мешает вам отбрасывать u32 в u32 * и разыгрывать его, но это не соответствует API ядра и подвержено ошибкам.

__iomem - это файл cookie, используемый Sparse, инструментом, используемым для поиска возможных ошибок кодирования в ядре. Если вы не скомпилируете код ядра с включенным Sparse, __iomem все равно будет проигнорирован.

Используйте Sparse, предварительно установив его, а затем добавив C=1 в ваш вызов. Например, при создании модуля используйте:

make -C $KPATH M=$PWD C=1 modules

__iomem определяется следующим образом:

# define __iomem        __attribute__((noderef, address_space(2)))

Добавление (и требование) cookie типа __iomem для всех доступов ввода/вывода - это способ быть более строгим и избегать ошибок программирования. Вы не хотите читать/записывать из/в области памяти ввода/вывода с абсолютными адресами, потому что вы обычно используете виртуальную память. Таким образом,

void __iomem *ioremap(phys_addr_t offset, unsigned long size);

обычно вызывается для получения виртуального адреса физического адреса ввода-вывода offset для указанной длины size в байтах. ioremap() возвращает указатель с cookie __iomem, поэтому теперь его можно использовать с встроенными функциями, такими как readl()/writel() (хотя теперь предпочтительнее использовать более явные макросы ioread32()/iowrite32(), для пример), которые принимают адреса __iomem.

Кроме того, атрибут noderef используется Sparse, чтобы убедиться, что вы не разыгрываете указатель __iomem. Разделение должно работать над некоторой архитектурой, где ввод-вывод действительно отображен в памяти, но другие архитектуры используют специальные инструкции для доступа к входам и выводам, и в этом случае разыменование не будет работать.

Посмотрим на пример:

void *io = ioremap(42, 4);

Разреженный не нравится:

warning: incorrect type in initializer (different address spaces)
    expected void *io
    got void [noderef] <asn:2>*

Или:

u32 __iomem* io = ioremap(42, 4);
pr_info("%x\n", *io);

Разреженный тоже не рад:

warning: dereference of noderef expression

В последнем примере первая строка верна, потому что ioremap() возвращает свое значение переменной __iomem. Но тогда мы это уважаем, и мы не должны этого делать.

Это облегчает работу Sparse:

void __iomem* io = ioremap(42, 4);
pr_info("%x\n", ioread32(io));

Нижняя строка: всегда используйте __iomem, где это необходимо (как тип возврата или как тип параметра), и используйте Sparse, чтобы убедиться, что вы это сделали. Также: не разыщите указатель __iomem.

Изменить. Здесь отличная статья LWN о начале __iomem и ее использовании.