Настройка оборудования на раннем этапе загрузки средствами ACPI (на примере FreeBSD)

    Несколько лет назад, когда CardBus и FireWire (IEEE 1394) еще были относительно «в ходу», многие производители ноутбуков в своей продукции использовали контроллеры семейства PCIXX21 и PCIXX11 фирмы Texas Instruments: один небольшой чип обеспечивал поддержку не только упомянутых интерфейсов, но и многих популярных стандартов сменных карт памяти.

    Такой чип (а именно, PCI7411) стоит и в моей NEC Versa S950. Этот малоизвестный ноутбук я в свое время предпочел даже ThinkPad-серии практически исключительно из-за более лучшей поддержки FreeBSD (оборудования в целом и спящего режима в частности) — специально тестировал в новосибирском Техносити перед покупкой. Долгое время я не пользовался встроенным кард-ридером, по привычке обходясь USB'шными «свистками». Но недавно я обнаружил, что FreeBSD до сих пор его не поддерживает. И если лет пять-шесть назад это можно было объяснить отсутствием нормального драйвера для этих контроллеров (нужно было что-то скачивать и собирать самому), то теперь я точно знал, что они «из коробки» поддерживаются во FreeBSD драйвером sdhci(4), о чем прямо сказано на странице руководства (и позже подтвердилось чтением исходников).

    Я начал неспешно гуглить на эту тему, и картина стала вырисовываться невеселая. Оказалось, что таких «счастливчиков», как я, немало. Многие постили в рассылки и форумы «портянки» dmesg и pciconf -lv, заводили баги в трекерах (например, OpenBSD PR i386/5843), но решения никто не предлагал. Более того, фактически поставив точку в вопросе, Александр Мотин, автор драйвера sdhci(4), в 2010 г. написал на форуме, что-де TI документацию на чип не дают, а значит, если производитель сконфигурировал чип неверно, а его настройка через BIOS не предусмотрена, сделать что-либо затруднительно. В свою очередь, Theo de Raadt закрыл i386/5843 со словами: «We do what we can. This vendor, amongst other, have their sdhc controllers locked out and hidden behind little undocumented bits. We've strugged before to find this information, and failed. If you can find this information on some other operating system, or in some vendor documentation, we would be thrilled.»

    В отчаянии я загрузился с Ubuntu LiveCD. И очень удивился тому, что в Linux кард-ридер работает. Значит…

    Плохо гуглил


    Оказывается, еще в 2006 г. Алекс Дубов написал Linux-драйвер для TI FlashMedia ридеров. Я скачал исходники и принялся их изучать, надеясь впоследствии доработать sdhci(4) или даже спортировать драйвер целиком. В первую очередь я посмотрел список поддерживаемых PCI vendor/device ids, чтобы сравнить с «нашим» драйвером. Он оказался небольшим:

    $ cat linux/pci_ids.h
    #define PCI_VENDOR_ID_TI		0x104c
    #define PCI_DEVICE_ID_TI_XX21_XX11_FM	0x8033
    #define PCI_DEVICE_ID_TI_XX12_FM	0x803b
    #define PCI_DEVICE_ID_TI_XX20_FM	0xac8f

    Число 0x8033 мне уже знакомо по выводу pciconf -lv на моем ноуте (chip=0x8033104c):

    none3@pci0:6:7:3:	class=0x018000 card=0x83191033 chip=0x8033104c rev=0x00 hdr=0x00
        vendor     = 'Texas Instruments (TI)'
        device     = 'PCIxx11/21 Integrated FlashMedia Controller'
        class      = mass storage

    Это тот самый кард-ридер, который не работает во FreeBSD, но работает в Linux. А вот кусок кода из sdhci.c (FreeBSD):

    static const struct sdhci_device {
    	uint32_t	model;
    	uint16_t	subvendor;
    	char	*desc;
    	u_int	quirks;
    } sdhci_devices[] = {
    	{ 0x08221180, 	0xffff,	"RICOH R5C822 SD",
    	    SDHCI_QUIRK_FORCE_DMA },
    	{ 0xe8221180, 	0xffff,	"RICOH SD",
    	    SDHCI_QUIRK_FORCE_DMA },
    	{ 0xe8231180, 	0xffff,	"RICOH R5CE823 SD",
    	    SDHCI_QUIRK_LOWER_FREQUENCY },
    	{ 0x8034104c, 	0xffff, "TI XX21/XX11 SD",
    	    SDHCI_QUIRK_FORCE_DMA },

    Можно заметить, что идентификатор устройства TI XX21/XX11 SD (0x8034104c) похож на мой (0x8033104c) с точностью до одной цифры. Кроме того, я обратил внимание, что контроллеры CardBus (0x8031104c) и FireWire (0x8032104c) не просто имеют схожие id'шки, но и PCI-селекторы всех устройств отличаются только номером функции, а устройство у них у всех одно и то же:

    none1@pci0:6:7:0:	class=0x060700 card=0x83191033 chip=0x8031104c rev=0x00 hdr=0x02
        vendor     = 'Texas Instruments (TI)'
        device     = 'PCIxx21/x515 Cardbus Controller'
        class      = bridge
        subclass   = PCI-CardBus
    none2@pci0:6:7:2:	class=0x0c0010 card=0x83191033 chip=0x8032104c rev=0x00 hdr=0x00
        vendor     = 'Texas Instruments (TI)'
        device     = 'OHCI Compliant IEEE-1394 FireWire Controller'
        class      = serial bus
        subclass   = FireWire

    Вспомнив слова Саши Мотина о том, что чип на самом деле реализует оба контроллера (SDHCI и FlashMedia), я стал искать более целенаправленно, и вскоре наткнулся на еще один пост, а затем на сообщение в рассылке freebsd-mobile@ о похожей (но немного другой) проблеме на HP NC6220. Рабочее решение нигде не предлагалось, но, в отличие от большинства дискуссий, которые сводились к дурацким советам типа «попробуйте последнюю версию драйвера» или банальным «сожалею, но, похоже, вы в пролете», теперь, по крайней мере, стало понятно, что конфигурация чипа как-то отображается в дампе PCI function (а значит, возможно, ее получится поменять), а главное, что таки-доступна документация: PCIXXX21/PCIXXX11 Implementation Guide. И вот тут мне стало по-настоящему интересно.

    Забегая вперед, скажу, что удивительнее всего то, что люди, раскопав практически datasheet на «капризный» чип, останавливались в шаге от решения проблемы. Я так и не нашел ни у кого рецепта, как правильно воспользоваться документацией (что и побудило меня написать этот пост). Но обо всем по порядку.

    PCIXXX21/PCIXXX11 Implementation Guide — документ о 117 страницах для проектировщиков аппаратуры на базе этих контроллеров. Подробно его разбирать смысла не имеет; самое важное, что я из него почерпнул: контроллер действительно реализует пять функций: CardBus, 1394, FlashMedia, SD Host и SmartCard; начальная конфигурация обычно берется из EEPROM. Главный регистр конфигурации — General Control Register (раздел 12.4.28, с. 65) — находится по адресам 1Eh-1Fh в ROM (нас интересует только нулевой байт, т.к. именно там маскируются функции чипа) и соотвествует PCI offset 86h нулевой функции устройства. Теперь —

    За дело


    Для начала посмотрим, что нам скажет утилита pciconf(8) про PCI configuration space «головной» (нулевой) функции чипа, т.е., в терминологии FreeBSD, селектора pci0:6:7:0. Ради краткости я не буду приводить дамп всех 256 байт, а ограничусь лишь интересующим нас, по смещению 86h:

    # pciconf -rb pci0:6:7:0 0x86
    d3

    Интересно. Смотрим в табличку на 65-й странице pdf'ки, видим, что тройка в нижнем нибле (полубайте) равна типичному значению бит, отвечающих за top level arbitration, SmartCard socket power control и OHCI 1394, это нас мало интересует. А вот верхний нибл как раз маскирует (включает-выключает) логику остальных контроллеров (таблицу целиком не привожу опять же для экономии места):

    scpu022a.pdf section 12.4.28


    0xD это 1101, т.е. установлены биты DISABLE_SC, DISABLE_SD и DISABLE_SKTB, а бит DISABLE_FM сброшен. Следовательно, чтобы «оживить» контроллер SD Host, нам, по логике, надо сбросить DISABLE_SD (разрешить), а DISABLE_FM, напротив, выставить (запретить). Маске 1011 соответсвует значение 0xB, т.е. по сути, нам надо поменять байт 0xD3 на 0xB3. Проблема, однако, в том, что сделать это нужно сильно заранее, до того, как чип будет проинициализирован, вернее, до того, как он определит, какие контроллеры включать. После того, как система загрузилась, менять конфигурацию бесполезно: все устройства уже «в строю». И вот тут нам на помощь приходит

    ACPI


    Что такое ACPI и для чего оно нужно, я объяснять не стану: это выходит за рамки топика, к тому же, на Хабре уже был хороший пост на эту тему. В данном случае нам важен вопрос: можно ли пропатчить DSDT до инициализации чипа так, чтобы он включил нужный контроллер (SD Host) и выключил ненужный, для которого у нас нет драйвера (FlashMedia).

    Посмотрим, какие у нас есть средства отладки и вывода информации в рамках интерпретатора («виртуальной машины») ACPI. В спецификации ACPI (параграф 19.5.25, с. 733) упоминается специальный объект Debug, который операционная система должна «донести» до пользователя. Во FreeBSD за это отвечает системная переменная debug.acpi.enable_debug_objects, которую нужно выставить в единицу:

    # sysctl debug.acpi.enable_debug_objects=1
    debug.acpi.enable_debug_objects: 0 -> 1

    Теперь мы можем писать произвольные строчки в Debug, а ядро FreeBSD будет выводить их на консоль. Осталось придумать, как заставить ACPI выдавать интересующую нас информацию по требованию. Для начала сдампим и дизассемблируем DSDT ноута, и поизучаем его:

    # acpidump -dt > s950.asl

    Я решил найти метод, который вызывается через какое-либо внешнее воздействие (или внутреннее, но периодическое, типа опроса батарейки), при этом практически не затрагивая работу «железа». Изучая код DSDT, я наткнулся на любопытный кусок:

    Method (_Q0C, 0, NotSerialized)
    {
        If (\_SB.PCI0.PEGA)
        {
            \_SB.PCI0.PEGP.VGA.SWIH ()
        }
        Else
        {
            Store (0x01, TLST)
            HKDS (0x0A)
        }
    }

    Больше нигде метод \_SB.PCI0.PEGP.VGA.SWIH не вызывается, а его название намекает, что это некое переключение дисплея. На клавиатуре многих ноутбуков одна из функциональных клавиш в сочетании с Fn-модификатором переключает видео-вывод с внутреннего дисплея на внешний. На моей «версе» это F3. Попробуем модифицировать код метода следующим образом:

                         Method (_Q0C, 0, NotSerialized)
                         {
    +                        Store ("Fn-F3 pressed", Debug)
                             If (\_SB.PCI0.PEGA)
                             {

    Пересоберем ASL:

    # iasl s950-patched.asl
    
    Intel ACPI Component Architecture
    ASL Optimizing Compiler version 20101013-32
    Copyright (c) 2000 - 2010 Intel Corporation
    
    ASL Input:  s950-patched.asl - 7749 lines, 280987 bytes, 2840 keywords
    AML Output: /tmp/acpidump.aml - 24863 bytes, 640 named objects, 2200 executable opcodes
    
    Compilation complete. 0 Errors, 0 Warnings, 0 Remarks, 958 Optimizations
    # cp /tmp/acpidump.aml /root/s950-patched.aml

    Чтобы FreeBSD использовала нашу таблицу при загрузке, добавим в /boot/loader.conf следующие строчки:

    acpi_dsdt_load="YES"
    acpi_dsdt_name="/root/s950-patched.aml"

    Если все сделано правильно, и наш расчет оправдался, то при нажатии на Fn-F3 мы будем видеть на консоли сообщения ядра (повышенной яркости) о том, что клавиша Fn-F3 была нажата. Теперь, когда мы более-менее умеем взаимодействовать с ACPI, самое время попробовать

    Достучаться до регистра 86h


    Физические адресные пространства всевозможных устройств (оперативная память, порты ввода-вывода, платы расширения, CMOS, IPMI и пр.) отображаются в пространство имен ACPI в виде т.н. операционных регионов (OperationRegion), внутри которых обычно выделяются битовые поля (Field), состоящие из одного или нескольких поименованных «виртуальных регистров», или field units (параграф 19.5.96, с. 782 спецификации). OperationRegion для нашего контроллера может выглядеть, например, так:

    OperationRegion (PCIC, PCI_Config, 0x00, 256)
    Field (PCIC, AnyAcc, NoLock, Preserve)
    {
    	Offset	(0x86),		// General Control Register по смещению 86h
    	TLA,	2,		// Top level arbitration
    	SCSP,	1,		// SmartCard socket power
    	OHCI,	1,		// Disable OHCI 1394 controller function
    	SKTB,	1,		// Disable CardBus socket B
    	FM,		1,		// Disable FlashMedia function
    	SD,		1,		// Disable SD host controller function
    	SC,		1,		// Disable SmartCard function
    }

    Или даже проще, если в OperationRegion объявить не все 256 байт, а только интересующий нас, и не выделять отдельные биты в конфигурационном регистре:

    OperationRegion (PCIC, PCI_Config, 0x86, 0x01)
    Field (PCIC, AnyAcc, NoLock, Preserve)
    {
    	GCR0,	8,		// General Control Register (Byte 0)
    }

    Нетрудно заметить, что само по себе такое определение бесполезно: оно не привязано к какому-либо устройству, являясь по сути просто структурой из одного байта по известному смещению. Устройства в DSDT задаются через ключевое слово Device (параграф 19.5.30, с. 735); совокупность всех устройств представляет собой этакое дерево. Так, все PCI-устройства находятся чаще всего внутри пространства устройства \_SB.PCI0, которое соответствует корневой шине PCI (обычно такая шина одна, но теоретически их может быть до 256 в одном PCI-домене).

    Для идентификации устройства на шине нужно задать его адрес в виде (device << 16) | function. В нашем случае (помните вывод pciconf -lv?) function = 0, device = 7, bus = 6. То есть устройство, видимо, должно выглядеть как-то так:

    Device (XX11)
    {
    	Name (_ADR, 0x00070000)		// pci0:6:7:0
    }

    Хорошо, но откуда взялась шестая шина? И где она в DSDT? Посмотрим лог загрузки ядра (dmesg):

    $ dmesg | grep pci6
    pci6: <ACPI PCI bus> on pcib4
    pci6: <bridge, PCI-CardBus> at device 7.0 (no driver attached)
    pci6: <serial bus, FireWire> at device 7.2 (no driver attached)
    pci6: <mass storage> at device 7.3 (no driver attached)
    pci6: <network> at device 8.0 (no driver attached)
    $ dmesg | grep pcib4
    pcib4: <ACPI PCI-PCI bridge> at device 30.0 on pci0
    pci6: <ACPI PCI bus> on pcib4

    Получается, pci6 — это дополнительная, «виртуальная» шина на мосту PCI-PCI. Номер 6 (как и 4 для моста) ей достался потому, что FreeBSD так распределила устройства. Внутри DSDT никаких шести шин и четырех мостов, конечно, нет. Мост — Device (PCIB) — там ровно один, как и ожидалось. Полностью наше описание должно выглядеть так (привожу краткий вариант, не раскладывая регистр на отдельные биты):

    Scope (\_SB.PCI0.PCIB) {
    	Device (XX11)
    	{
    		Name (_ADR, 0x00070000)		// pci0:6:7:0
    		OperationRegion (PCIC, PCI_Config, 0x86, 0x01)
    		Field (PCIC, AnyAcc, NoLock, Preserve) { GCR0, 8, }
    	}
    }

    Теперь мы можем заменить наш отладочный код в методе _Q0C на что-то более осмысленное:

                         Method (_Q0C, 0, NotSerialized)
                         {
    +                        Store (Concatenate("GCR0 = 0x", \_SB.PCI0.PCIB.XX11.GCR0), Debug)
                             If (\_SB.PCI0.PEGA)
                             {

    Пересобираем ASL, перезагружаемся, жмем Fn-F3. Если мы все сделали верно, то должны увидеть то же самое значение, которое мы ранее читали через pciconf(8):

    vanilla pciconf -lv


    (Реализация функции для записи значения регистра напрямую в видеопамять оставляется читателю в качестве легкого упражнения.)

    Нам остается ответить на самый главный вопрос: получится ли изменить значение регистра и заставить чип сконфигурировать себя так, как нам нужно?

    Стандарт ACPI определяет специальный метод для инициализации устройств, _INI (параграф 6.5.1, с. 349). Добавим в наше устройство следующий код:

    Method (_INI)
    {
    	Store (0xB3, GCR0)
    	/* Альтернативный вариант для побитовой конфигурации:
    	Store (0x01, FM)
    	Store (0x00, SD) */
    }

    Компилируем ASL, копируем получившийся файл AML в /root/s950-patched.aml, снова перезагружаемся. Смотрим на

    Результат


    pciconf -lv after patch


    Прежде всего, обратим внимание, что контроллер 0x8033104c (FlashMedia) исчез из вывода pciconf -lv, зато появился 0x8034104c (SD Host). Загружаем нужные модули ядра, вставляем карточку и пробуем примонтировать ее:

    # kldload sdhci mmc mmcsd
    # ls /dev/mmcsd0*
    /dev/mmcsd0     /dev/mmcsd0s1
    # mount -t msdosfs /dev/mmcsd0s1 /mnt/tmp
    # ls /mnt/tmp/DCIM/100CANON
    IMG_0403.JPG    IMG_0424.JPG    IMG_0450.JPG    IMG_0494.JPG    IMG_0515.JPG
    IMG_0406.JPG    IMG_0425.JPG    IMG_0451.JPG    IMG_0498.JPG    IMG_0517.JPG
    IMG_0407.JPG    IMG_0427.JPG    IMG_0452.JPG    IMG_0499.JPG    IMG_0518.JPG
    IMG_0409.JPG    IMG_0429.JPG    IMG_0453.JPG    IMG_0500.JPG    IMG_0520.JPG
    IMG_0410.JPG    IMG_0430.JPG    IMG_0467.JPG    IMG_0501.JPG    IMG_0522.JPG
    IMG_0412.JPG    IMG_0439.JPG    IMG_0473.JPG    IMG_0506.JPG    IMG_0525.JPG
    IMG_0413.JPG    IMG_0440.JPG    IMG_0474.JPG    IMG_0507.JPG    IMG_0526.JPG
    IMG_0414.JPG    IMG_0445.JPG    IMG_0475.JPG    IMG_0508.JPG    IMG_0534.JPG
    IMG_0415.JPG    IMG_0447.JPG    IMG_0478.JPG    IMG_0510.JPG    IMG_0535.JPG
    IMG_0421.JPG    IMG_0448.JPG    IMG_0492.JPG    IMG_0512.JPG    IMG_0537.JPG
    IMG_0423.JPG    IMG_0449.JPG    IMG_0493.JPG    IMG_0514.JPG    IMG_0538.JPG
    # tar cf /dev/null /mnt/tmp/DCIM/100CANON ; echo $?
    tar: Removing leading '/' from member names
    0

    Вроде все работает, ну и славно. Можно убирать отладочный код из DSDT и наслаждаться жизнью пользоваться кард-ридером.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      +1
      Пользуясь случаем прошу совета у знающих. Пишу экспромтом, поэтому точных подробностей описать не могу. Есть у знакомого нетбук Asus eeePC 1001-что-тотам, после обновления BIOS в системе напрочь пропала WebCam, опция в биосе тоже пропала. В Ubuntu 12.04 в /proc/acpi/*** никаких упоминаний о WebCam не нашлось. Пробовал найти acpi драйвер для eeepc, но к сожалению он был для старых моделей (до 1000-х). Понижение на предыдущие версии BIOS не помогло. Была мысль отредактировать ami BIOS, но под рукой не было 32-х битной windows, которая требовалась для запуска редактора. Кто направит на путь истинный?
        +2
        Больше похоже, что у вас шлейф камеры перетерся, а биос — совпадение
          +1
          У меня аналогично «пропадал» встроенный кард-ридер как раз после апгрейда BIOS на ASUS EEE PC 901.
          Помог сброс настроек BIOS к дефолтным, есть там такой пункт.
          +3
          Спасибо за статью! Давно хотел хоть сколько-нибудь смутно понять, что есть DSDT и ACPI в целом.
          Завеса открывается… (:
            +1
            Можно водрузить на свой комп Хакинтош и вы познакомитесь с таблицами ACPI в полной мере, т.к. правки оных основной метод заставить работать основные устройства :)
              0
              Это конечно так, но у меня всегда всё заводилось «из коробки» — подходили те конфиги, которые предусмотрели авторы сборки Hackintosh-а.
            +3
            Вы молодец!
              +5
              Отлично.

              Вот в чём польза, кстати, драйвера в апстриме в линуксе — дальнейшее портирование в другие ОС — вопрос лишь желания, но никак не долгих нудных уговоров вендора.

              Сравните с out-of-tree блобами и дровами под проприентарные ОС.
                +3
                Отличная статья. Спокойная, с нужными подробностями (и увлекательно написанная!), и тема близка. Как и любому пользователю систем с отсутствующими драйверами.
                Обязательно пишите еще!
                P.S. Тоже удивляют ответы в форумах, подобные «вендор не способствует разработке открытых драйверов, тема закрыта». Вот и вы доказали, что можно справиться и без явной поддержки производителя. |:)
                • UFO just landed and posted this here
                    +2
                    Я там специально выделил эту фразу курсивом. Мне показалось, что такая ирония по поводу общего качества поддержки железа фрюниксами будет уместной.
                    • UFO just landed and posted this here
                    +5
                    Какой молодец! Давненько не попадались тут такие грамотные статьи, спасибо вам большое!
                      0
                      А в соотвествующую рассылку FreeBSD не писали этот рецепт?
                        +1
                        Вы знаете, отчасти вы правы: по-хорошему, надо бы прошерстить интернет (форумы, архивы рассылок, и т.п.), где люди сталкивались с подобной проблемой (она, как понимаете, не только FreeBSD-specific), и написать ответ или хотя бы дать ссылку. Возможно, я так и поступлю.

                        Постить же (предварительно переведя на английский) рецепт целиком (или даже урезав его) мне кажется несколько не подходящим по формату тех же рассылок, где общение ведется в виде «вопрос-ответ(ы)»; ведь все-таки рассылка — это не блог.
                        +1
                        Вот таких материалов и не хватает на хабре

                        Only users with full accounts can post comments. Log in, please.