Driver and device communication by _HID method ACPI on the example of the GPIO controller Lynxpoint

Formulation of the problem


Linux has a standard interface for working with GPIO via sysfs. Documentation on it can be found here .

In short, in the folder "/ sys / class / gpio" there are files "export" and "unexport". By writing the number X to the export file, you can open the interface in the user space to control GPIOX

# открыть интерфейс в user space для управления GPIO12
$ echo 12 > /sys/class/gpio/export

After opening the interface, the folder / sys / class / gpio / gpioX / will appear in which there will be such files as “value” or “direction”, and by writing “in” or “out” to the file “direction” and writing 1 or 0 to the file "Value" can be controlled by the output of GPIO directly via the command line.

# настроить GPIO на вывод
$ echo"out" > /sys/class/gpio/gpio12/direction
# установить 1 на выводе GPIO
$ echo 1 > /sys/class/gpio/gpio12/value

In order for the “echo X> / sys / class / gpio / export” command to create the “gpioX” folder, the GPIO controller driver must be registered in the kernel, opening the interface to the GPIO lines.

It so happened that I'm working on porting a coreboot for a custom card based on the Intel Haswell i7 processor [For those who don't know, coreboot is an open source BIOS project with open source ( https://www.coreboot.org/ ) ]. I have a LynxpointLP south bridge embedded in my processor with 94 GPIO lines. And I wanted to open them in sysfs ...

Problem solving (driver and device communication in Linux)


Having performed a small search on the kernel code, I found that this driver was already written, is in the drivers \ gpio \ gpio-lynxpoint.c file and is enabled using Kconfig

config GPIO_LYNXPOINT
	tristate "Intel Lynxpoint GPIO support"
	dependson ACPI && X86
	select GPIOLIB_IRQCHIP
	help
	  driver for GPIO functionality on Intel Lynxpoint PCH chipset
	  Requires ACPI device enumeration code toset up a platform device.

The GPIO_LYNXPOINT option was enabled in the kernel with which I worked, but in the folder "/ sys / class / gpio /" there was not a single folder "gpiochipN" for the GPIO controller (which should be), and even such a script did not result in the export of any lines.

$ for i in {0..255}; doecho$i > /sys/class/gpio/export; done

Looking at the coreboot code or studying the documentation for this south bridge, you can understand that the GPIO controller is not a separate PCI device. It is part of another PCI device: LPC Interface Bridge. Using the PCI configuration space registers of this device, you must enable the GPIO controller and assign it BASE_ADDRESS in the I / O space. This will open a window in I / O space of 1kV size. Writing / reading bytes in this window, you can manage the lines GPIO.

What we can see in the coreboot code:

southbridge \ intel \ lynxpoint \ pch.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)

southbridge \ intel \ lynxpoint \ early_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);

If we look at the LPC device registers in Linux through “lspci -xxx”, we will see that the registers contain information recorded by us. So everything seems to be set up as it should.

Continuing to watch the driver code, I noticed that the Linux driver communicates with the device through the .acpi_match_table field. Since our device cannot be enumerated (it is not located either on the PCI or on the USB bus), then the driver is required for it, and the driver is connected to the device via the ACPI table. The usual case for x86, in the case of ARM, we would have registered our device in DeviceTree, or in the old hardcode in the kernel.

drivers \ gpio \ gpio-lynxpoint.c:

staticconststructacpi_device_idlynxpoint_gpio_acpi_match[] = {
	{ "INT33C7", 0 },
	{ "INT3437", 0 },
	{ }
};
MODULE_DEVICE_TABLE(acpi, lynxpoint_gpio_acpi_match);
staticstructplatform_driverlp_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),
	},
};

It works like this: if the kernel, when parsing the ACPI table, sees in it a device with _HID identifier "INT33C7", it will try to find a platform driver for it with matching identifiers in the ".driver-> acpi_match_table" structure fields.

When a match is found, Linux will execute the .probe driver function.

As it turned out, the ACPI code for this device was presented in the coreboot, I just had it commented out. Commented out for the reason that for this device, Windows could not find the driver and output "Unknown device" in the device manager. More on this will be below.

So, we are interested in information from the
src \ southbridge \ intel \ lynxpoint \ acpi \ serialio.asl file (the code is slightly simplified):

/* Несколько констант из файла
 * src\southbridge\intel\lynxpoint\pch.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, // ISARanges0x00000000,  // AddressGranularity0x00000000,  // AddressMinimum0x00000000,  // AddressMaximum0x00000000,  // AddressTranslation0x00000001,  // 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)
			}
		}
		...
	}
}

To parse this code in detail, you should familiarize yourself with the ASL language syntax in the ACPI specification .

But in short, this code creates a device with the identifier "INT33C7" which has 2 resources:

I/O memory: 1400-17ff;
IRQ: 14;

Inside its .probe Linux function, the driver obtains the above-indicated device resources as follows:


io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
irq_rc = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

Based on this data, the driver code will fill in the gpio_chip structure and register the gpio controller in the system, making it available through the sysfs interface.

Having returned the device's ASL code and recompiled the BIOS image, the system managed to access the GPIO via sysfs.

For a start, the folder "gpiochip162" appeared in / sys / class / gpio. This folder contains the file "base" and "ngpio". The base file is responsible for the number of the first GPIO of this controller, ngpio for their number.

$ cat /sys/class/gpio/gpiochip162/base
162
$ cat /sys/class/gpio/gpiochip162/ngpio
94

So everything was exported as it should. Run the script:

$ for i in {162..255}; doecho$i > /sys/class/gpio/export; done

After that, gpioN subfolders appear in the / sys / class / gpio / folder, inside which there will be files for managing the state of the line.

A couple of notes:

  • The folder / sys / class / gpio162 / is responsible for managing GPIO0, the folder / sys / class / gpio163 / is responsible for GPIO1, etc. This shift was due to the fact that the driver, during the initialization of the “struct gpio_chip” control structure, assigned “gc-> base = -1;”. That is, provided the kernel to choose the numbers themselves. This is generally not critical, but it is worth remembering that.
  • Access is opened only to GPIO lines that are configured as GPIO, and not as any native functions of the south bridge. For such lines, the driver displays information in dmesg: "gpio% d reserved for ACPI". In the case of coreboot, the GPIO setting is made in the “gpio.h” file in the folder with the motherboard.
  • Comparison of the device and the driver can also go according to the _CID (Compatible ID) method, and the documentation on our topic in the kernel is presented in the document “ACPI based device enumeration”

It should be noted that the “INT33C7” device is not in ACPI tables of 2 proprietary motherboards on the same chipset (from IBASE and DFI). The truth is, there most likely the GPIO lines are not displayed (I did not watch the documentation in detail at this point).

ID "INT33C7"


After raising the sysfs functionality, I had a question, where did the identification number “INT33C7” come from?

Looking at the documentation for the _HID method, it became clear that it is worth looking at http://www.uefi.org/PNP_ACPI_Registry

_HID (Hardware ID)
_HID (Hardware ID)
This object is used to supply OSPM with the device’s PNP ID or ACPI ID*
When describing a platform, use of any _HID objects is optional. However, a _HID object must be
used to describe any device that will be enumerated by OSPM. OSPM only enumerates a device
when no bus enumerator can detect the device ID. For example, devices on an ISA bus are
enumerated by OSPM. Use the _ADR object to describe devices enumerated by bus enumerators
other than OSPM.

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

This link has 3 points:

  • All kinds of 3 letter identifiers (PNP ID) are indicated here.
  • PNP IDs that begin with “PNP” are reserved by Microsoft here.
  • All kinds of 4 letter identifiers (ACPI ID) are indicated here.

It is not very clear why, but according to the PNP ID list, it can be found that the “INT” identifiers are reserved at INTERPHASE CORPORATION:

INTERPHASE CORPORATION	INT11/29/1996

Apparently, a single list of full device identifiers (letter part + numeric) is not published. But with the help of Google it was possible to find lists of devices and their _HID for example, here or here .

They indicate:

INT33C7=Intel Serial I/O GPIO Host Controller

And judging by the remaining lines from this list, all INTxxxx devices are Intel devices (now it sounds quite obvious, but the connection with INTERPHASE CORPORATION is not clear; it is also not very clear why the numbering starts with such large numbers, but it seems to be Intel discretion).

Communication driver and device in Windows


Satisfying my curiosity, I decided to download Windows on my board. As expected, the system could not find a driver for the device. There was no help from the drivers for the IBASE and DFI boards, which is understandable, because this device is not indicated in the BIOS of these boards.

It was possible to find the driver on the Microsoft website.

However, there this driver is presented only for Windows 8.1 and higher. I'm still working with Windows 7.

Nevertheless, I tried to download one of the drivers and specify its folder when searching for a driver for my unknown device.

However, the dispatcher could not match the driver to the device. Although the inf file clearly contained information about the device INT33C7.

[Manufacturer]
%INTEL%=Intel,NTamd64.6.3
[Intel.NTamd64.6.3]
%iaLPSS_GPIO.DeviceDesc_LPT%=iaLPSS_GPIO_Device, ACPI\INT33C7
%iaLPSS_GPIO.DeviceDesc_WPT%=iaLPSS_GPIO_Device, ACPI\INT3437

In the process of parsing the INF file, it turned out that in the [Manufacturer] section it was clearly stated that it was not intended for my system:

What Intel.NTamd64.6.3 means can be understood by the description :

nt[Architecture][.[OSMajorVersion][.[OSMinorVersion]
OSMajorVersion=6 => Windows 7/Windows 8.1/Windows Server 2012 R2/...
OSMinorVersion=3 => Windows 8.1/Windows Server 2012 R2

I did not succeed in trying to push the Windows 7 driver by replacing Intel.NTamd64.6.3 with Intel.NTamd64.6.1, as it gave me a blue screen of death and an unbootable OS, and I had to do a repair.

The driver for Win7 was found only on an incomprehensible website on the Internet, and then the device is displayed in the device manager with an exclamation mark.

Realizing his impotence, I decided to test the functionality on Windows 10. There was a pleasant surprise. “Intel Chipset Device Software (INF Update Utility)” installed the driver for my controller without any problems.



As you can see, the resources we have designated for this device



In theory, after installing the driver with the GPIO controller, it will most likely be possible to work through the IOCTL functions (type as in this document) .

However, there were no GPIO programming tasks from Windows, so the search for a similar document for my chipset was postponed.



Conclusion:


This article has reviewed the driver and device communication using the _HID method of ACPI. Such a connection may be required on an x86 system for devices that cannot be enumerated.

  • In the case of Linux, communication with the driver is done through .acpi_match_table
  • In the case of Windows, communication with the driver is carried out through the INF file.

Also popular now: