Сделаем код чище: работа с 64-битными регистрами оборудования в Linux

    Нередко у программистов, пишущих драйверы, возникают некоторые трудности с обменом данными в 64-битном формате. Давайте разберём некоторые ситуации.

    Вместо вступления


    На самом деле под ширмой работы с 64-битными регистрами скрыто несколько проблем.

    Во-первых, обслуживание оборудования, в котором применяются 64-битные регистры, должно быть доступно как на 64-битных ядрах, так и на 32-битных.

    Во-вторых, не всякие 64-битные регистры можно записывать за одну команду, так как связано это с нюансами реализации в железе.

    В-третьих, иногда приходится сравнивать 64-битное число как два 32-битных, снова же в связи с установленными стандартами и протоколами.

    Посмотрим, как избежать изобретения велосипеда, и сделать код чище, лаконичнее и красивее.

    Обмен 64-битными данными


    Конечно же многие сталкивались с известными командами записи и чтения регистров оборудования, такими как writel(), readl().

    Они охватывают традиционные байтовые, двухбайтовые и четырёхбайтовые операции с регистрами. Что же делать, когда необходимо записать или считать сразу восемь байт?

    На некоторых 64-битных архитектурах на помощь приходят команды writeq() и readq().

    Традиционно программист для охватывания 32-битных и 64-битных платформ сочиняет нечто подобное в своём коде:
    static inline void writeq(u64 val, void __iomem *addr)
    {       
            writel(val, addr);
            writel(val >> 32, addr + 4);
    }
    

    И соответственно для чтения.
    static inline u64 readq(void __iomem *addr)
    {       
            u32 low, high;
    
            low = readl(addr);
            high = readl(addr + 4);
    
            return low + ((u64)high << 32);
    }
    


    И вот представьте, что таких копий десятки, если не сотни. Во избежание изобретений велосипеда в ядро внесли специальные файлы include/linux/io-64-nonatomic-hi-lo.h и include/linux/io-64-nonatomic-lo-hi.h.

    Какие особенности у этих файлов:
    1. Определённые функции выполняют обращения не атомарно.
    2. В связи с вышесказанным в ядре два файла: для записи младший-старший и наоборот — старший-младший.
    3. Оба файла объявляют writeq() и readq() по принципу «кто первый встал, того и тапки». Вначале проверяется не определены ли они уже? Если нет, тогда переопределяем. Соответственно порядок включения заголовочных файлов имеет значение.
    4. Обращения, что очевидно, выполнены по формуле 8 = 4 + 4.

    Соответственно, если у нас оборудование, которое понимает только обращения вида 4 + 4, то мы применяем lo_hi_writeq() и lo_hi_readq() или hi_lo_writeq() и hi_lo_readq(). При идеальном случае просто writeq() и readq().

    Сравнение 64-битных чисел


    В некоторых случаях возникает необходимость сравнить 64-битное число в варианте 8 с числом в варианте 4 + 4.

    Решение прямо в лоб:
    u32 hi = Y, lo = Z;
    u64 value = X, tmp;
    
    tmp = (Y << 32) | X;
    return value == tmp;
    

    Ну вы поняли, как много будет кода на 32-битной архитектуре.

    Можно разбить это дело на такие два сравнения:
    return (value >> 32) == hi && (u32)value == lo;
    

    Вроде легче, но… есть одна проблема. В ядре встречаются типы, которые непосредственно зависят от конкретной платформы, а именно: phys_addr_t, resource_size_t, dma_addr_t и им подобные.

    Как вы думаете, что будет, если мы напишем такой код:
    u32 hi = Y, lo = Z;
    resource_size_t value = X;
    
    return (value >> 32) == hi && (u32)value == lo;
    

    На 64-битной архитектуре, понятное дело, всё будет прекрасно. А вот на 32-битной нам пожалуется компилятор на сдвиг value >> 32.

    Для того, чтобы и компилятор был счастлив, и пользователь особо себя не утруждал, в ядро добавили следующие макросы: lower_32_bits() и
    upper_32_bits() для соответственно младших и старших 4 байт.

    В результате сравнение будет выглядеть так:
    return upper_32_bits(value) == hi && lower_32_bits(value) == lo;
    


    Не забывайте, что данные операции не атомарны!
    • +10
    • 7,7k
    • 1
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 1

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.