- PVSM.RU - https://www.pvsm.ru -
В Linux есть стандартный интерфейс для работы с GPIO через sysfs. Документацию на него можно посмотреть тут [1].
Если кратко, то в папке "/sys/class/gpio" есть файлы «export» и «unexport». С помощью записи числа X в файл export можно открыть интерфейс в user space для управления GPIOX
# открыть интерфейс в user space для управления GPIO12
$ echo 12 > /sys/class/gpio/export
После открытия интерфейса появится папка /sys/class/gpio/gpioX/ в которой будут такие файлы как «value» или «direction», и путём записи «in» или «out» в файл «direction» и записи 1 или 0 в файл «value» можно управлять выводом GPIO напрямую через командную строку.
# настроить GPIO на вывод
$ echo "out" > /sys/class/gpio/gpio12/direction
# установить 1 на выводе GPIO
$ echo 1 > /sys/class/gpio/gpio12/value
Чтобы команда «echo X > /sys/class/gpio/export» приводила к созданию папки «gpioX», в ядре должен быть зарегистрирован драйвер контроллера GPIO, открывающий интерфейс к линиям GPIO.
Так получилось, что я работаю над портированием coreboot для кастомной платы на базе процессора Intel Haswell i7 [Для тех, кто не знает, coreboot это open source проект по созданию BIOS с открытым исходным кодом (https://www.coreboot.org/ [2])]. В мой процессор встроен южный мост LynxpointLP в котором есть 94 линии GPIO. И я захотел открыть их в sysfs…
Выполнив небольшой поиск по коду ядра, я обнаружил, что данный драйвер уже написан, находится в файле «driversgpiogpio-lynxpoint.c» и включается с помощью Kconfig
config GPIO_LYNXPOINT
tristate "Intel Lynxpoint GPIO support"
depends on ACPI && X86
select GPIOLIB_IRQCHIP
help
driver for GPIO functionality on Intel Lynxpoint PCH chipset
Requires ACPI device enumeration code to set up a platform device.
Опция GPIO_LYNXPOINT была включена в ядре с которым я работал, однако в папке "/sys/class/gpio/" не было ни одной папки «gpiochipN» для GPIO контроллера (что должно быть), и даже такой скрипт не приводил к экспорту ни одной линии.
$ for i in {0..255}; do echo $i > /sys/class/gpio/export; done
Посмотрев код coreboot или изучив документацию для данного южного моста можно понять, что GPIO контроллер не является отдельным PCI устройством. Он входит в состав другого PCI устройства: LPC Interface Bridge. С помощью регистров конфигурационного пространства PCI этого устройства необходимо включить GPIO controller и назначить ему BASE_ADDRESS в пространстве I/O. Это откроет окно в I/O пространстве размером 1КВ. Записывая/считывая байты в данном окне, можно управлять линиями GPIO.
Что мы и можем увидеть в коде coreboot:
southbridgeintellynxpointpch.h:
#define DEFAULT_GPIOBASE 0x1400
#define DEFAULT_GPIOSIZE 0x400
...
#define GPIO_BASE 0x48 /* LPC GPIO Base Address Register */
#define GPIO_CNTL 0x4C /* LPC GPIO Control Register */
...
/* PCI Configuration Space (D31:F0): LPC */
#define PCH_LPC_DEV PCI_DEV(0, 0x1f, 0)
southbridgeintellynxpointearly_pch.c:
/* Setup GPIO Base Address */
pci_write_config32(PCH_LPC_DEV, GPIO_BASE, DEFAULT_GPIOBASE|1);
/* Enable GPIO functionality. */
pci_write_config8(PCH_LPC_DEV, GPIO_CNTL, 0x10);
Если мы посмотрим регистры устройства LPC в Linux через «lspci -xxx», то увидим что в данных регистрах находится записанная нами информация. Так что вроде бы всё настраивается как надо.
Продолжая смотреть код драйвера я обратил внимание, что связь Linux драйвера с устройством осуществляется через поле .acpi_match_table. Так как наше устройство не поддаётся энумерации (оно ведь не расположено ни на PCI, ни на USB шине), то для него необходим platform driver, а связь этого драйвера с устройством осуществляется через ACPI таблицы. Обычный случай для x86, в случае с ARM мы бы прописывали наше устройство в DeviceTree, или по старому хардкодили в ядро.
driversgpiogpio-lynxpoint.c:
static const struct acpi_device_id lynxpoint_gpio_acpi_match[] = {
{ "INT33C7", 0 },
{ "INT3437", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, lynxpoint_gpio_acpi_match);
static struct platform_driver lp_gpio_driver = {
.probe = lp_gpio_probe,
.remove = lp_gpio_remove,
.driver = {
.name = "lp_gpio",
.pm = &lp_gpio_pm_ops,
.acpi_match_table = ACPI_PTR(lynxpoint_gpio_acpi_match),
},
};
Это работает так: если ядро при парсинге ACPI таблицы увидит в ней устройство с _HID идентификатором «INT33C7», то оно будет пытаться найти platform driver для него с совпадающими идентификаторами в полях структуры ".driver->acpi_match_table".
При найденном совпадении Linux выполнит .probe функцию драйвера.
Как оказалось ACPI код для данного устройства был представлен в coreboot, просто у меня он был закомментирован. Закомментирован по причине того, что для данного устройства Windows не мог найти драйвер и выводил «Неизвестное устройство» в диспетчере устройств. Подробнее об этом будет ниже.
Итак нас интересует информация из файла
srcsouthbridgeintellynxpointacpiserialio.asl (код немного упрощён):
/* Несколько констант из файла
* srcsouthbridgeintellynxpointpch.h
* #define DEFAULT_GPIOBASE 0x1400
* #define DEFAULT_GPIOSIZE 0x400
*/
Scope (_SB) {
Device (PCI0)
{
...
Device (GPIO)
{
// GPIO Controller
Name (_HID, "INT33C7")
Name (_CID, "INT33C7")
Name (_UID, 1)
Name (RBUF, ResourceTemplate()
{
DWordIo (ResourceProducer,
MinFixed, // IsMinFixed
MaxFixed, // IsMaxFixed
PosDecode, // Decode
EntireRange, // ISARanges
0x00000000, // AddressGranularity
0x00000000, // AddressMinimum
0x00000000, // AddressMaximum
0x00000000, // AddressTranslation
0x00000001, // RangeLength
, // ResourceSourceIndex
, // ResourceSource
BAR0)
Interrupt (ResourceConsumer,
Level, ActiveHigh, Shared, , , ) {14}
})
Method (_CRS, 0, NotSerialized)
{
CreateDwordField (^RBUF, ^BAR0._MIN, BMIN)
CreateDwordField (^RBUF, ^BAR0._MAX, BMAX)
CreateDwordField (^RBUF, ^BAR0._LEN, BLEN)
Store (DEFAULT_GPIOSIZE, BLEN)
Store (DEFAULT_GPIOBASE, BMIN)
Store (Subtract (Add (DEFAULT_GPIOBASE,
DEFAULT_GPIOSIZE), 1), BMAX)
Return (RBUF)
}
Method (_STA, 0, NotSerialized)
{
Return (0xF)
}
}
...
}
}
Чтобы подробно разобрать данный код следует ознакомиться с синтаксисом языка ASL в спецификации ACPI [3].
Но если коротко, то данный код создаёт устройство с идентификатором «INT33C7» у которого есть 2 ресурса:
I/O memory: 1400-17ff;
IRQ: 14;
Внутри своей функции .probe Linux драйвер получает вышеобозначенные ресурсы устройства так:
io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
irq_rc = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
На основании этих данных код драйвера заполнит структуру gpio_chip и зарегистрирует gpio controller в системе, что сделает его доступным через интерфейс sysfs.
Вернув ASL код устройства и перекомпилировав образ BIOS, в системе удалось получить доступ к GPIO через sysfs.
Для начала в /sys/class/gpio появилась папка «gpiochip162». В данной папке содержится файл «base» и «ngpio». Файл base отвечает за номер первого GPIO данного контроллера, ngpio за их количество.
$ cat /sys/class/gpio/gpiochip162/base
162
$ cat /sys/class/gpio/gpiochip162/ngpio
94
Таким образом всё экспортировалось как надо. Выполняем скрипт:
$ for i in {162..255}; do echo $i > /sys/class/gpio/export; done
После этого в папке /sys/class/gpio/ появятся подпапки gpioN, внутри которых будут файлы для управления состоянием линии.
Пара замечаний:
Стоит отметить, что устройства «INT33C7» нет в ACPI таблицах 2 проприетарных материнских плат на этом же чипсете (от компаний IBASE и DFI). Правда там скорее всего GPIO линии и не выведены (подробно документацию на этот момент я не смотрел).
После поднятия функционала sysfs у меня возник вопрос, откуда вообще взялся идентификационный номер «INT33C7»?
Посмотрев документацию на метод _HID стало понятно, что стоит смотреть в http://www.uefi.org/PNP_ACPI_Registry [4]
Arguments:
None
Return Value:
An Integer or String containing the HID
A _HID object evaluates to either a numeric 32-bit compressed EISA type ID or a string. If a
string, the format must be an alphanumeric PNP or ACPI ID with no asterisk or other leading
characters.
A valid PNP ID must be of the form «AAA####» where A is an uppercase letter and # is a hex
digit. A valid ACPI ID must be of the form «NNNN####» where N is an uppercase letter or a
digit ('0'-'9') and # is a hex digit. This specification reserves the string «ACPI» for use only
with devices defined herein. It further reserves all strings representing 4 HEX digits for
exclusive use with PCI-assigned Vendor IDs.
*-PNP ID and ACPI ID Registry is at http://www.uefi.org/PNP_ACPI_Registry [4]
По данной ссылке есть 3 пункта:
Не очень понятно почему, но по списку PNP ID можно найти, что идентификаторы «INT» зарезервированы на INTERPHASE CORPORATION:
INTERPHASE CORPORATION INT 11/29/1996
Судя по всему единый список полных идентификаторов устройств (буквенная часть+цифровая) не публикуется. Но с помощью гугла удалось найти списки устройств и их _HID например тут [8] или тут [9].
В них указано:
INT33C7=Intel Serial I/O GPIO Host Controller
И судя по остальным строкам из этого списка все устройства «INTxxxx» — это устройства Intel (теперь это звучит достаточно очевидно, но связь с INTERPHASE CORPORATION так и не понятна; также не очень ясно почему нумерация начинается с таких больших чисел, но это видимо на усмотрение корпорации Intel).
Удовлетворив своё любопытство я решил загрузить на своей плате Windows. Как и ожидалось, система не смогла найти драйвер для устройства. Помощи от драйверов для плат IBASE и DFI не было, что и понятно, ведь в BIOS этих плат это устройство и не обозначено.
Удалось найти драйвер на сайте Microsoft [10]
Однако там данный драйвер представлен только для Windows 8.1 и выше. Я же всё ещё работаю с Windows 7.
Тем не менее я попытался скачать один из драйверов и указать его папку при поиске драйвера для моего неизвестного устройства.
Однако диспетчер не смог сопоставить драйвер устройству. Хотя в inf файле явно содержалась информация об устройстве INT33C7.
[Manufacturer]
%INTEL%=Intel,NTamd64.6.3
[Intel.NTamd64.6.3]
%iaLPSS_GPIO.DeviceDesc_LPT%=iaLPSS_GPIO_Device, ACPIINT33C7
%iaLPSS_GPIO.DeviceDesc_WPT%=iaLPSS_GPIO_Device, ACPIINT3437
В процессе разбора INF файла выяснилось, что в секции [Manufacturer] явно указано, что он предназначен не для моей системы:
Что значит Intel.NTamd64.6.3 можно понять по описанию [11]:
nt[Architecture][.[OSMajorVersion][.[OSMinorVersion]
OSMajorVersion=6 => Windows 7/Windows 8.1/Windows Server 2012 R2/...
OSMinorVersion=3 => Windows 8.1/Windows Server 2012 R2
Попытаться подпихнуть драйвер Windows 7 путём замены Intel.NTamd64.6.3 на Intel.NTamd64.6.1 мягко говоря не удалось, так как это дало мне синий экран смерти и незагружаемую ОС, причём так, что пришлось делать восстановление.
Драйвер для Win7 удалось найти только на непонятном сайте в интернете, и то после этого устройство в диспетчере устройств отображается с восклицательным знаком.
Осознав своё бессилие, решил проверить функциональность на Windows 10. Тут ждал приятный сюрприз. «Intel Chipset Device Software (INF Update Utility)» [12] без каких-либо проблем установило драйвер для моего контроллера.
Как видно, у данного устройства обозначенные нами ресурсы
В теории после установки драйвера с GPIO controller можно будет скорее всего работать через IOCTL функции (типа как в данном документе) [13].
Однако задачи программирования GPIO из Windows не стояло, поэтому поиск аналогичного документа для моего чипсета был отложен.
В данной статье была рассмотрена связь драйвера и устройства по _HID методу ACPI. Такая связь может потребоваться на x86 системе для устройств не поддающихся энумерации.
Автор: Константин Аладышев
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/298682
Ссылки в тексте:
[1] тут: https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
[2] https://www.coreboot.org/: https://www.coreboot.org/
[3] спецификации ACPI: https://www.uefi.org/sites/default/files/resources/ACPI_6_2.pdf
[4] http://www.uefi.org/PNP_ACPI_Registry: http://www.uefi.org/PNP_ACPI_Registry
[5] тут: http://www.uefi.org/pnp_id_list
[6] тут: http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/devids.txt
[7] тут: http://www.uefi.org/acpi_id_list
[8] тут: http://www.kebe.com/~danmcd/webrevs/acpica/usr/src/common/acpica/common/ahids.c.html
[9] тут: https://www.pvsm.ruftp://pp.myftp.info/drivers/SamDrivers_16.6/Tools/UDI/PNPdevs.txt
[10] на сайте Microsoft: http://www.catalog.update.microsoft.com/Search.aspx?q=Intel%20Serial%20IO%20GPIO%20Host%20Controller%20INT33C7
[11] по описанию: https://docs.microsoft.com/en-us/windows-hardware/drivers/install/inf-manufacturer-section
[12] «Intel Chipset Device Software (INF Update Utility)»: https://downloadcenter.intel.com/download/20775/Intel-Chipset-Device-Software-INF-Update-Utility-?product=1145
[13] типа как в данном документе): http://www.ctcom.com.tw/doc/324257.pdf
[14] Источник: https://habr.com/post/429336/?utm_source=habrahabr&utm_medium=rss&utm_campaign=429336
Нажмите здесь для печати.