
It's hard to underestimate the role of
GPIO , especially in the world of embedded ARM systems. In addition to being an extremely popular material for all beginner manuals,
GPIO provides a way to manage many peripherals, act as a source of valuable interruptions, or may even be the only available way to communicate with the world for SOC.
Based on my own humble experience, I can say that interruption is not the most consecrated topic in the Linux community. Because of its features, as well as a strong binding to the hardware, all training materials on interrupts are devoid of a real and easily reproducible example. This fact prevents understanding that very often interrupts and GPIO are inseparable, especially in the field of embedded Linux. Many people start to believe that GPIO is a very simple and boring thing (which, by the way, has become such thanks to the sysfs subsystem).
')
Even in the example given in LDD3 (snull driver), interrupts are emitted by explicitly calling the pair device function. There are also examples in USFCA courses (
http://cs.usfca.edu/~cruse/cs686s08/ ), but they use someone else's interrupt, are closely related to x86 architecture and are very outdated.
The proposed solution can solve these problems. From the point of view of user space and, in many respects, in the internal implementation, the driver is indistinguishable from most of the “real” ones that provide interrupts for general purpose I / O ports. At the moment, the driver supports interrupts on the leading or trailing edge and can be used as a source of interruptions for other devices.
ivshmem - Inter-VM shared memory
Designed for sharing shared memory (allocated on the host platform through the POSIX shared memory API mechanism) by multiple QEMU processes with different guest platforms. In order for all guest platforms to have access to the shared memory area, ivshmem models a PCI device by providing memory access as a PCI BAR.

From the point of view of the virtual machine, the ivshmem PCI device contains three basic address registers (BAR).
- BAR0 is a MMIO domain that supports registers and interrupts in case MSI is not used, one kilobyte in size.
- BAR1 is used for MSI-X if MSI support is enabled.
- BAR2 to access the shared memory object.
This mechanism was introduced by Cam Macdonnel in the original report
“Nahanni - a shared memory interface for KVM” (later became known as ivshmem), in which he put forward the following theses:
- zero-copy data access
- interrupt mechanism
- guest / guest and host / guest interaction
and analyzed the overall performance.
At the moment, officially, nobody escorts ivshmem, however, Red Hat employees are making a big contribution to the development of ivshmem.
purpose
ivshmem can serve as the basis for simulating and debugging many classes of devices.
In this article, we look at the general-purpose input / output (GPIO) virtual pci, which is also a source of interrupts, and the corresponding driver with access and control through the sysfs mechanism.
Prerequisites:
- Qemu 2.5.1.1 source code (not recommended for a lower version)
- Linux-kernel 4.1 source code
For development and testing, the virtual board qemu versatilepb (system ARM) was used.
Optional:
- arm-cross-toolchain
- nairobi-embedded - Guest-side ivshmem PCI device test sources
Legend:
g >> - commands or output executed on the guest system.h >> on the main one.Sample and original code
To begin with, we will demonstrate the original code based on the original code (
https://github.com/henning-schild/ivshmem-guest-code ), and modified, subsequently, Siro Mugabi.
h>> qemu: += -device ivshmem,shm=ivshmem,size=1 g>>
In principle, this is quite enough to emulate GPIO already in this form. And in many cases they did this when a simple input or output state is enough, using sysfs and interrupts implies a small add-on to I / O mem.
Implementation
Note that
/ dev / ivshmem0 and
ne_ivshmem_shm_guest_usr.c are no longer needed, all work from the guest machine from the user space (user-space) will be handled using the
sysfs interface.
Before marking our device in memory, I would like to note that we simply duplicate the scheme used in most gpio drivers.
Firstly, all gpio inputs / outputs are divided into ports, usually 8, 16, 32 inputs. Each port has at least an input status register (
GPIO_DATA ), a direction register if in / out switching is supported (
GPIO_OUTPUT ). Further (if there is support in the device itself), the interrupt status register, the interrupt registers on the rising edge (rising) and the falling edge and on the level (high and low). The hardware interrupt supplied by the main interrupt controller is usually one for the entire port and is shared between all the inputs of the port.
Examples of existing implementations with comments
Sitara am335x
better known in the beaglebone boardDeveloper: Texas Instruments
Documentation: AM335x Sitara Processors Technical Reference Manual (page 4865)
Its corresponding gpio driver is: linux / drivers / gpio / gpio-omap.cCorresponding title: linux / include / linux / platform_data / gpio-omap.hNumber of inputs / outputs: 128 (4 gpio ports - 32 contacts each)
am335x Sitara gpio register table - port ARegister Name | Bias | Driver Name | Comment |
---|
GPIO_IRQSTATUS_0 | 0x02 | OMAP4_GPIO_IRQSTATUS_0 | Interrupt status for specified input |
GPIO_IRQSTATUS_1 | 0x030 | OMAP4_GPIO_IRQSTATUS_1 | Interrupt status for specified input |
GPIO_IRQSTATUS_SET_0 | 0x034 | OMAP4_GPIO_IRQSTATUS_SET_0 | Enable interrupts on a given input. |
GPIO_IRQSTATUS_SET_1 | 0x038 | OMAP4_GPIO_IRQSTATUS_SET_1 | Enable interrupts on a given input. |
GPIO_IRQSTATUS_CLR_0 | 0x03 | OMAP4_GPIO_IRQSTATUS_CLR_0 | Turns off interrupts on a given input. |
GPIO_IRQSTATUS_CLR_1 | 0x040 | OMAP4_GPIO_IRQSTATUS_CLR_1 | Turns off interrupts on a given input. |
GPIO_OE | 0x134 | OMAP4_GPIO_OE | Monitors the input / output status (in / out) |
GPIO_DATAIN | 0x138 | OMAP4_GPIO_DATAIN | I / O status |
GPIO_DATAOUT | 0x13C | OMAP4_GPIO_DATAOUT | Setting the status for the outputs (low / high) |
GPIO_LEVELDETECT0 | 0x140 | OMAP4_GPIO_LEVELDETECT0 | Enable / disable interrupts for low signal input |
GPIO_LEVELDETECT1 | 0x144 | OMAP4_GPIO_LEVELDETECT1 | Enable / disable interrupts for high signal input |
GPIO_RISINGDETECT | 0x148 | OMAP4_GPIO_RISINGDETECT | Enable / Disable Interrupts for Front-End Entry |
GPIO_FALLINGDETECT | 0x14 | OMAP4_GPIO_FALLINGDETECT | Enable / disable interrupts for trailing edge input |
GPIO_CLEARDATAOUT | 0x190 | OMAP4_GPIO_CLEARDATAOUT | Toggles the corresponding input to low |
GPIO_SETDATAOUT | 0x194 | OMAP4_GPIO_SETDATAOUT | Switches the corresponding input to the high state. |
Note: GPIO_IRQSTATUS_N is also used for IRQ ACK. Managing bounce, as well as nutrition is beyond the scope of this article.The presence of the GPIO_CLEARDATAOUT and GPIO_SETDATAOUT registers in addition to the GPIO_DATAOUT register, as well as GPIO_IRQSTATUS_SET_N and GPIO_IRQSTATUS_CLR_N in addition to GPIO_IRQSTATUS_N, is explained by two ways of recording the output status:
- Standard: Read register entry completely at main address
- Task and cleaning (recommended by the manufacturer): Two corresponding registers are used to set and clear the corresponding contact as an output, the same applies to interrupt control.
ep9301
Developer: Cirrus Logic
Documentation: EP9301 User's Guide (page 523)
Its corresponding gpio driver is: linux / drivers / gpio / gpio-ep93xx.cCorresponding header: linux / arch / arm / mach-ep93xx / include / mach / gpio-ep93xx.hNumber of inputs / outputs: 56 (7 gpio ports - 8 contacts each)
ep9301 gpio register table - port ARegister Name | Bias | Driver Name | Description |
---|
PADR | 0x00 | EP93XX_GPIO_REG (0x0) | The I / O status register is readable. |
Paddr | 0x10 | EP93XX_GPIO_REG (0x10) | Monitors the input / output status (in / out) |
GPIOAIntEn | 0x9C | int_en_register_offset [0] | Enable interrupts on a given input. |
GPIOAIntType1 | 0x90 | int_type1_register_offset [0] | Sets the level / edge interrupt type |
GPIOAIntType2 | 0x94 | int_type2_register_offset [0] | Sets high / rising or low / falling depending on the type of interrupt selected |
GPIOAEOI | 0x98 | eoi_register_offset [0] | Register for notification of a processed interrupt |
IntStsA | 0xA0 | EP93XX_GPIO_A_INT_STATUS | Register interrupt status |
Note:Of these, 7 ports are available for 8, 8, 1, 2, 3, 2, 4 inputs / outputs, with only the first, second and fifth ports having interrupt registers.The table only considers port A.One of the features of ep9301, is that the type of interrupts both at the hardware level is not supported; the driver switches to the moment the interrupt is triggered. Another interesting feature is that on port F each contact has its own interruption.Bt848
Last example: pci board Bt848, with gpio.Developer: Intel
Documentation: Bt848 / 848A / 849A (page 68)
The corresponding gpio driver is: linux / drivers / gpio / gpio-bt8xx.cCorresponding header: linux / drivers / media / pci / bt8xx / bt848.hNumber of inputs / outputs: 24
Bt848 is a video capture card.Bt848 gpio register tableRegister Name | Bias | Driver Name | Description |
---|
BT848_GPIO_OUT_EN | 0x118 | BT848_GPIO_OUT_EN | The status of the inputs / outputs is readable and writeable. |
BT848_GPIO_DATA | 0x200 | BT848_GPIO_DATA | Monitors the input / output status (in / out) |
There is no support for interrupts. Only two registers - the state and setting in / out.We mark our device in memory
To begin, select a place for data and state management.
Let the device have 8 general purpose inputs / outputs, then:
Register Name | Bias | Driver Name | Description |
---|
DATA | 0x00 | VIRTUAL_GPIO_DATA | The status of the inputs / outputs is readable and writeable. |
OUTPUTEN | 0x01 | VIRTUAL_GPIO_OUT_EN | Monitors the input / output status (in / out) |
Gpio interface quick reference
struct gpio_chip { const char *label; int (*direction_input)(struct gpio_chip *chip, unsigned offset); int (*get)(struct gpio_chip *chip, unsigned offset); int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value); void (*set)(struct gpio_chip *chip, unsigned offset, int value); int base; u16 ngpio; };
Documentation:
https://www.kernel.org/doc/Documentation/gpio/sysfs.txtLink to source code:
linux-kernel 4.1Output state when switching
It is necessary to note the
int value parameter in the
direction_output function, which serves the file / sys / class / gpio / gpioN / direction, which takes a value not only “in” / ”out”, but also “high” / “low”, the values ​​of which are transmitted as the
value parameter (
this simple fact, for some reason, is rarely mentioned in the beginner's guides ).
g>> /sys/class/gpio
Dynamic assignment int base and legacy ARCH_NR_GPIOS
Historically, the number of GPIOs in the kernel was limited to the
ARCH_NR_GPIOS parameter,
defaulting to 256 and subsequently increased to 512 (
version 3.18 ).
Its meaning is quite simple, in the kernel there can be no more GPIO than the parameter value, if the planned quantity was greater than the default value, it was redefined in the corresponding platform header file.
The reason for this behavior was the definition of the GPIO description table as static and the maximum offset for each port was limited:
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
GPIO ports and their offsets were rigidly defined in files describing the hardware of a particular SOC, for example:
EP93XX_GPIO_BANK/source/arch/arm/mach-ep93xx/gpio.c #define EP93XX_GPIO_BANK(name, dr, ddr, base_gpio) \ { \ .chip = { \ .label = name, \ .direction_input = ep93xx_gpio_direction_input, \ .direction_output = ep93xx_gpio_direction_output,\ .get = ep93xx_gpio_get, \ .set = ep93xx_gpio_set, \ .dbg_show = ep93xx_gpio_dbg_show, \ .base = base_gpio, \ .ngpio = 8, \ }, \ .data_reg = EP93XX_GPIO_REG(dr), \ .data_dir_reg = EP93XX_GPIO_REG(ddr), \ } static struct ep93xx_gpio_chip ep93xx_gpio_banks[] = { EP93XX_GPIO_BANK("A", 0x00, 0x10, 0), EP93XX_GPIO_BANK("B", 0x04, 0x14, 8), EP93XX_GPIO_BANK("C", 0x08, 0x18, 40), EP93XX_GPIO_BANK("D", 0x0c, 0x1c, 24), EP93XX_GPIO_BANK("E", 0x20, 0x24, 32), EP93XX_GPIO_BANK("F", 0x30, 0x34, 16), EP93XX_GPIO_BANK("G", 0x38, 0x3c, 48), EP93XX_GPIO_BANK("H", 0x40, 0x44, 56), };
Starting from
version 3.19, the static array was replaced with a dynamic array for each GPIO port allocated in the
gpiochip_add () function .
However,
ARCH_NR_GPIOS is still here (at the time of version 4.7) and is used to search for an offset when dynamically assigning a base.
static int gpiochip_find_base(int ngpio);
The
base parameter of the
gpio_chip structure can be defined as -1, then the offset will be defined as the first free range starting from the end, that is, if the port has the number of contacts equal to 8, the offset will be 248 with the
ARCH_NR_GPIOS parameter equal to 256 (
ARCH_NR_GPIOS - ngpio) if the port is logged in first.
Define the following features of our driver
Set the appropriate contact as input:
static int virtual_gpio_direction_input (struct gpio_chip * gpio, unsigned nr) static int virtual_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 outen, data; spin_lock_irqsave(&vg->lock, flags); data = vgread(VIRTUAL_GPIO_DATA); data &= ~(1 << nr); vgwrite(data, VIRTUAL_GPIO_DATA); outen = vgread(VIRTUAL_GPIO_OUT_EN); outen &= ~(1 << nr); vgwrite(outen, VIRTUAL_GPIO_OUT_EN); spin_unlock_irqrestore(&vg->lock, flags); return 0; }
Reading current contact status:
static int virtual_gpio_get (struct gpio_chip * gpio, unsigned nr) static int virtual_gpio_get(struct gpio_chip *gpio, unsigned nr) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 data; spin_lock_irqsave(&vg->lock, flags); data= vgread(VIRTUAL_GPIO_DATA); spin_unlock_irqrestore(&vg->lock, flags); return !!(data & (1 << nr)); }
Set corresponding contact as output:
static int virtual_gpio_direction_output (struct gpio_chip * gpio, unsigned nr, int val) static int virtual_gpio_direction_output(struct gpio_chip *gpio, unsigned nr, int val) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 outen, data; spin_lock_irqsave(&vg->lock, flags); outen = vgread(VIRTUAL_GPIO_OUT_EN); outen |= (1 << nr); vgwrite(outen, VIRTUAL_GPIO_OUT_EN); data = vgread(VIRTUAL_GPIO_DATA); if (val) data |= (1 << nr); else data &= ~(1 << nr); vgwrite(data, VIRTUAL_GPIO_DATA); spin_unlock_irqrestore(&vg->lock, flags); return 0; }
Set exit status:
static void virtual_gpio_set (struct gpio_chip * gpio, unsigned nr, int val) static void virtual_gpio_set(struct gpio_chip *gpio, unsigned nr, int val) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 data; spin_lock_irqsave(&vg->lock, flags); data = vgread(VIRTUAL_GPIO_DATA); if (val) data |= (1 << nr); else data &= ~(1 << nr); vgwrite(data, VIRTUAL_GPIO_DATA); spin_unlock_irqrestore(&vg->lock, flags); }
The function of registering our driver as a gpio_chip device:
static void virtual_gpio_setup (struct virtual_gpio * gpio) static void virtual_gpio_setup(struct virtual_gpio *gpio) { struct gpio_chip *chip = &gpio->chip; chip->label = dev_name(&gpio->pdev->dev); chip->owner = THIS_MODULE; chip->direction_input = virtual_gpio_direction_input; chip->get = virtual_gpio_get; chip->direction_output = virtual_gpio_direction_output; chip->set = virtual_gpio_set; chip->dbg_show = NULL; chip->base = modparam_gpiobase; chip->ngpio = VIRTUAL_GPIO_NR_GPIOS; chip->can_sleep = 0;
vgread and vgwrite are just wrappers for the iowrite8 and ioread8 functions: #define vgwrite(dat, adr) iowrite8((dat), vg->data_base_addr+(adr)) #define vgread(adr) ioread8(vg->data_base_addr+(adr))
Passing the gpiobase value as a parameter when dynamically loading a module
Note: Starting from version 4.2, this is the recommended way to register a GPIO port. static int modparam_gpiobase = -1; module_param_named(gpiobase, modparam_gpiobase, int, 0444); MODULE_PARM_DESC(gpiobase, "The GPIO base number. -1 means dynamic, which is the default.");
Loading and testing module
h>> $ rm /dev/shm/ivshmem h>> Adding parameters to qemu launch command line += -device ivshmem,shm=ivshmem,size=1 g>>
Simple check:
h>> $ xxd -b -l 2 -c 2 /dev/shm/ivshmem 0000000: 00000001 00000001 ..
DATA is set, OUTPUTEN is set.
Add interrupts
Tagging interrupt registers and basic interrupt handling
Note: The virtual driver only considers EDGEDETECT_RISE and EDGEDETECT_FALL.Note: Please use only qemu versions older than 2.5.0 or qemu-linaro. Idshmem interrupt support is broken in 2.5.0 or simply does not work in some versions earlier than 2.5.0. If using 2.5.0 you must use a patch for 2.5.0 ( http://lists.gnu.org/archive/html/qemu-stable/2015-12/msg00034.html ).Add the following registers:
Register Name | Bias | Driver Name | Description |
---|
INTERRUPT_EN | 0x01 | VIRTUAL_GPIO_INT_EN | Enable interrupts on a given input. |
INTERRUPT_ST | 0x02 | VIRTUAL_GPIO_INT_ST | Interrupt Status Register |
INTERRUPT_EOI | 0x03 | VIRTUAL_GPIO_INT_EOI | Register for notification of a processed interrupt |
EDGEDETECT_RISE | 0x04 | VIRTUAL_GPIO_RISING | Enable / Disable Interrupts for Front-End Entry |
EDGEDETECT_FALL | 0x05 | VIRTUAL_GPIO_FALLING | Enable / disable interrupts for trailing edge input |
LEVELDETECT_HIGH | | NC | NOT CONNECTED |
LEVELDETECT_LOW | | NC | NOT CONNECTED |
The following function is responsible for handling the pci bus interrupt, at the moment its role is only in the notification of the processed interrupt:
static irqreturn_t virtual_gpio_interrupt (int irq, void * data) static irqreturn_t virtual_gpio_interrupt(int irq, void *data) { u32 status; struct virtual_gpio *vg = (struct virtual_gpio *)data; status = readl(vg->regs_base_addr + IntrStatus); if (!status || (status == 0xFFFFFFFF)) return IRQ_NONE; printk(KERN_INFO "VGPIO: interrupt (status = 0x%04x)\n", status); return IRQ_HANDLED; }
This stage will require an external daemon, which is included in the standard qemu-ivshmem-server delivery. The -chardev path to the UNIX socket is added to the qemu startup line, messages exchanged between running qemu, ivshmem-server and ivshmem-client exchanges are implemented using the eventfd mechanism.
h>> $ ivshmem-server -v -F -p ivshmem.pid -l 1M
irq_chip and chained_interrupt concept
We will not go into details, this topic is well
covered in the
first patch presented by irq_chip , the kernel documentation and the book “Professional Linux Kernel Architecture” (currently it is outdated, but irq_chip is also not a new thing).
At the moment, the main thing for us is the fact that GPIO ports that provide interrupts are a common practice in the days of Linux that interrupt cascading from the parent interrupt controller.
This is why part of the GPIO driver responsible for interrupts uses
irq_chip . In other words, such a driver uses two subsystems simultaneously:
gpio_chip and
irq_chip .
A quick look at the irq subsystem gives us the following picture:

High-Level Interrupt Service Routines (ISRs) - Performs all necessary interrupt service work on the device driver. For example, if an interrupt is used to indicate readable new data, the work of the ISR will be to copy the data to the appropriate place.
Interrupt Flow Handling - This subsystem is responsible for features in the implementation of interrupt processing, such as triggering on a signal level (level) or on a front (edge).
Edge triggering occurs when determining that a potential change has occurred on the line. Triggering by level (Level-triggering), is defined as a specific value of the potential, and the change in potential does not matter.
From the point of view of the kernel, the level triggering is a more complicated case, since, at the beginning of each interrupt, it must be masked.
Chip-Level Hardware Encapsulation - Used to encapsulate the features of the implementation of working with the hardware. This subsystem can be considered as a kind of “device driver” for interrupt controllers.
As we can see, the kernel takes control of the processing of the interrupt chain and the difference in the implementation of types (by front and level), if we provide the appropriate infrastructure.
IRQ Domains
The
IRQ Domain subsystem, which appeared in the
irq: add irq_domain translation infrastructure patch, allowed separating the interrupt controller numbers from the interrupt numbers in the kernel, providing a common array of interrupt numbers. Quoting official documentation:
“Today the IRQ number is just a number .
”Prior to this update, hardware numbers were mapped to kernel numbers as 1: 1, and cascading was not supported. By hardware numbers, we mean local interrupt numbers for the controller, which in our case coincide with local GPIO numbers.
The following types of mapping exist in the IRQ Domain:
- Linear
- Tree view
- And type "No map" (No display)
Since our interrupt vector is rather small, and we definitely have no interest in the "No map" map, our map is linear, in fact the numbers are matched 1: 1 with the offset, the difference with the old approach is that the assignment of irq numbers and the calculation of the offset core, while ensuring continuity of the allocated range.
In each function of the irq_chip interface, a pointer is passed to the
struct irq_data structure, where
irq_data-> irq is the interrupt number in the linux kernel, and
irq_data-> hwirq is our local interrupt number within the driver. Also, a pointer to our
struct virtual_gpio structure is passed to
struct irq_data , which is not surprising.
Binding irq_chip and gpio_chip
If we were guided by younger versions of the kernel, we would have to use the
irq_domain_add_simple function to display our number, but from version 3.15 in the
gpio patch
: add IRQ chip helpers in gpiolib patch there is no need to directly use the IRQ Domain interface.
Therefore, instead of using the IRQ Domain interface directly and providing the infrastructure for mapping local numbers to global (
.map () ops), we will use the functions
gpiochip_irqchip_add and
gpiochip_set_chained_irqchip (depend on the GPIOLIB_IRQCHIP parameter Kconfig).
A great example of use and ease of use is the
gpio-pl061 driver .
Bind our
irq_chip to an existing
gpio_chip :
gpiochip_irqchip_add(&vg->chip, &virtual_gpio_irq_chip, 0, handle_edge_irq, IRQ_TYPE_NONE)
handle_edge_irq is one of the built-in stream handlers that takes control of the chain of interrupts on fronts.
Note: Frontal interrupts are the most common. The main difference from level interrupts lies in the management of the chain, the level interruption is masked in the kernel immediately upon receipt. gpiochip_set_chained_irqchip(&vg->chip, &virtual_gpio_irq_chip, pdev->irq, NULL)
By calling the gpiochip_set_chained_irqchip function
, we tell the kernel that our
irq_chip uses a PCI bus interrupt and our interrupts are cascaded from
pdev-> irq .
We
’ll modify our handler to generate interrupts depending on the state of
VIRTUAL_GPIO_INT_ST :
pending = vgread(VIRTUAL_GPIO_INT_ST); /* check if irq is really raised */ if(pending) { for_each_set_bit(i, &pending, VIRTUAL_GPIO_NR_GPIOS) generic_handle_irq(irq_find_mapping(vg->chip.irqdomain, i)); }
irq_find_mapping is an auxiliary function for translating the local input number into the global interrupt number.
Putting it all together
First of all, we note that the irq_chip interface of our driver looks like this:
static struct irq_chip virtual_gpio_irq_chip = { .name = "GPIO", .irq_ack = virtual_gpio_irq_ack, .irq_mask = virtual_gpio_irq_mask, .irq_unmask = virtual_gpio_irq_unmask, .irq_set_type = virtual_gpio_irq_type, };
The ack () function is always closely related to the hardware specificity of the controller. Some devices, for example, require confirmation of the processing of an interruption request before subsequent requests can be served.static void virtual_gpio_irq_ack (struct irq_data * d) static void virtual_gpio_irq_ack(struct irq_data *d) { unsigned long flags; u8 nr = d->hwirq; u8 mask = 1 << nr; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); spin_lock_irqsave(&vg->lock, flags); vgwrite(mask, VIRTUAL_GPIO_INT_EOI); spin_unlock_irqrestore(&vg->lock, flags); }
In our case, the program vg_get_set - rather rough emulation of the eoi register is used. After setting the interrupt status flag, the eoi register is constantly polled in the loop. When the interrupt notification input bit is set by the driver, the eoi register is cleared and the interrupt status bit is cleared at the input.Masking and unmasking is done by writing the corresponding value to the INTERRUPT_EN register .Interrupt masking:static void virtual_gpio_irq_mask (struct irq_data * d) static void virtual_gpio_irq_mask(struct irq_data *d) { u8 mask; unsigned long flags; u8 nr = d->hwirq; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); spin_lock_irqsave(&vg->lock, flags); mask = vgread(VIRTUAL_GPIO_INT_EN); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_INT_EN); spin_unlock_irqrestore(&vg->lock, flags); }
:
static void virtual_gpio_irq_unmask(struct irq_data *d) static void virtual_gpio_irq_unmask(struct irq_data *d) { u8 mask; unsigned long flags; u8 nr = d->hwirq; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); spin_lock_irqsave(&vg->lock, flags); mask = vgread(VIRTUAL_GPIO_INT_EN); mask |= (1 << nr); vgwrite(mask, VIRTUAL_GPIO_INT_EN); spin_unlock_irqrestore(&vg->lock, flags); }
irq_type — :
IRQ_TYPE_NONE —
IRQ_TYPE_EDGE_RISING —
IRQ_TYPE_EDGE_FALLING —
IRQ_TYPE_EDGE_BOTH —
IRQ_TYPE_LEVEL_HIGH —
IRQ_TYPE_LEVEL_LOW —
static int virtual_gpio_irq_type(struct irq_data *d, unsigned int type) static int virtual_gpio_irq_type(struct irq_data *d, unsigned int type) { unsigned long flags; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); u8 mask; u8 nr = d->hwirq; spin_lock_irqsave(&vg->lock, flags); switch (type) { case IRQ_TYPE_EDGE_RISING: mask = vgread(VIRTUAL_GPIO_RISING); mask |= (1 << nr); vgwrite(mask, VIRTUAL_GPIO_RISING); mask = vgread(VIRTUAL_GPIO_FALLING); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_FALLING); break; case IRQ_TYPE_EDGE_FALLING: mask = vgread(VIRTUAL_GPIO_FALLING); mask |= (1 << nr); vgwrite(mask, VIRTUAL_GPIO_FALLING); mask = vgread(VIRTUAL_GPIO_RISING); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_RISING); break; default: retval = -EINVAL; goto end; } mask = vgread(VIRTUAL_GPIO_INT_EN); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_INT_EN); end: spin_unlock_irqrestore(&vg->lock, flags); return retval; }
user space, vg_guest_client. gpio_sysfs,
“ select , () exceptfds” .
:
FD_ZERO(&efds); maxfd = 0; for(i = 0; i < gpio_size; i++) { FD_SET(gpios[i].fd, &efds); maxfd = (maxfd < gpios[i].fd) ? gpios[i].fd : maxfd; } ready = pselect(maxfd + 1, NULL, NULL, &efds, NULL, NULL); if(ready > 0) for(i = 0; i < gpio_size; i++) if(FD_ISSET(gpios[i].fd, &efds)) { read(gpios[i].fd, &value, 1); if(lseek(gpios[i].fd, 0, SEEK_SET) == -1) perror("lseek"); printf("gpio number=%d interrupt caught\n", gpios[i].number); }
sysfs :
g>>
: gpio .
pselect:
static irqreturn_t virtual_gpio_interrupt (int irq, void *data) int generic_handle_irq(unsigned int irq); ... static irqreturn_t gpio_sysfs_irq(int irq, void *priv); static inline void sysfs_notify_dirent(struct kernfs_node *kn); void kernfs_notify(struct kernfs_node *kn); static void kernfs_notify_workfn(struct work_struct *work);
Conclusion
, , , , - . Qemu ivshmem . .
gpio sysfs sysfs, GPIO , . .
, , .
generic-gpio , , , mmio gpio , , , . , .
, , :
- Device Tree
- generic-gpio mmio gpio
- , GPIO
- gpio — , ,
It is also impossible to lose sight of the latest changes in gpiolib - sysfs gpio is now outdated. A new ioctl- based interface for gpiolib on the way to becoming a new standard for communicating with GPIO. But the younger versions will be used for a long time, besides, no one is going to remove the old interface from the kernel at the moment. For example, I still have devices that successfully work on the 2.6.34 kernel version.List of materials:- http://nairobi-embedded.org/category/device-drivers.html [Siro Mugabi]
- http://lxr.free-electrons.com/source
- Professional Linux Kernel Architecture [Wolfgang Mauerer]
- LDD3 [Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman]
:
- http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/ ( )
- https://developer.ridgerun.com/wiki/index.php?title=Gpio-int-test.c
- http://www.assert.cc/2015/01/03/selects-exceptional-conditions.html
, Makefile README:
https://github.com/maquefel/virtual_gpio_basic