acrn-hypervisor/devicemodel/hw/pci/virtio/virtio_gpio.c
Geoffroy Van Cutsem 8b16be9185 Remove "All rights reserved" string headers
Many of the license and Intel copyright headers include the "All rights
reserved" string. It is not relevant in the context of the BSD-3-Clause
license that the code is released under. This patch removes those strings
throughout the code (hypervisor, devicemodel and misc).

Tracked-On: #7254
Signed-off-by: Geoffroy Van Cutsem <geoffroy.vancutsem@intel.com>
2022-04-06 13:21:02 +08:00

1653 lines
40 KiB
C

/*
* Copyright (C) 2019 Intel Corporation.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <ctype.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include "acpi.h"
#include "dm.h"
#include "pci_core.h"
#include "mevent.h"
#include "virtio.h"
#include "gpio_dm.h"
/*
* GPIO virtualization architecture
*
* +--------------------------+
* |ACRN DM |
* | +--------------------+ |
* | | | | virtqueue
* | | GPIO mediator |<-+-----------+
* | | | | |
* | +-+-----+--------+---+ | |
* User space +----|-----|--------|------+ |
* +---------+ | | |
* v v v |
* +----------------+ +-----+ +----------------+ | +---------------+
* -+ /dev/gpiochip0 +---+ ... +---+ /dev/gpiochipN +-----+ User VM +-
* + + + + + + | +/dev/gpiochip0 +
* +------------+---+ +--+--+ +-------------+--+ | +------+--------+
* Kernel space | +--------------+ | | |
* +--------------------+ | | | |
* v v v | v
* +---------------------+ +---------------------+ | +--------------+
* | | | | | |User VM Virtio|
* | pinctrl subsystem |<---+ gpiolib subsystem | +->+GPIO Driver |
* | | | | | |
* +--------+------------+ +----------+----------+ +--------------+
* | +------------------------+
* | |
* ----------|---|----------------------------------------------------------
* Hardware | |
* v v
* +------------------+
* | |
* | GPIO controllers |
* | |
* +------------------+
*/
/*
* GPIO IRQ virtualization architecture
*
* Service VM User VM
* +-------------------------------+
* | virtio GPIO mediator |
* | +-------------------------+ |
* | | GPIO IRQ chip | | IRQ chip
* | | +-------------------+ | | virtqueue
* | | |Enable, Disable +<--|---|-----------+
* | | |Mask, Unmask, Ack | | | |
* | | +-------------------+ | | IRQ event |
* | | +--------------+ | | virtqueue |
* | | | Generate IRQ +--------|---|--------+ |
* | | +--------------+ | | | |
* | +-------------------------+ | | |
* | ^ ^ ^ ^ | | |
* +---|----|----|----|------------+ | |
* -----|----|----|----|---------------------+--+-------------------------------
* +-+----+----+----+-+ | | +------------+ +------------+
* | gpiolib framework| | | |IRQ consumer| |IRQ consumer|
* +------------------+ | | +------------+ +------------+
* | | +----------------------------+
* | | | User VM gpiolib framework|
* | | +----------------------------+
* | | +----------------------+
* | +-+ User VM virtio GPIO|
* +--->| IRQ chip |
* +----------------------+
*/
static int gpio_debug;
#define DPRINTF(params) do { if (gpio_debug) pr_dbg params; } while (0)
#define WPRINTF(params) (pr_err params)
#define BIT(x) (1 << (x))
/* Virtio GPIO supports maximum number of virtual gpio */
#define VIRTIO_GPIO_MAX_VLINES 64
/* Virtio GPIO supports maximum native gpio chips */
#define VIRTIO_GPIO_MAX_CHIPS 8
/* Virtio GPIO virtqueue numbers*/
#define VIRTIO_GPIO_MAXQ 3
/* Virtio GPIO capabilities */
#define VIRTIO_GPIO_F_CHIP 1
#define VIRTIO_GPIO_S_HOSTCAPS VIRTIO_GPIO_F_CHIP
#define IRQ_TYPE_NONE 0
#define IRQ_TYPE_EDGE_RISING (1 << 0)
#define IRQ_TYPE_EDGE_FALLING (1 << 1)
#define IRQ_TYPE_LEVEL_HIGH (1 << 2)
#define IRQ_TYPE_LEVEL_LOW (1 << 3)
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_MASK (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)
/* make virtio gpio mediator a singleton mode */
static bool virtio_gpio_is_active;
/* GPIO PIO space */
#define GPIO_PIO_SIZE (VIRTIO_GPIO_MAX_VLINES * 4)
static uint64_t gpio_pio_start;
/* Uses the same packed config format as generic pinconf. */
#define PIN_CONF_UNPACKED(p) ((unsigned long) p & 0xffUL)
enum pin_config_param {
PIN_CONFIG_BIAS_BUS_HOLD,
PIN_CONFIG_BIAS_DISABLE,
PIN_CONFIG_BIAS_HIGH_IMPEDANCE,
PIN_CONFIG_BIAS_PULL_DOWN,
PIN_CONFIG_BIAS_PULL_PIN_DEFAULT,
PIN_CONFIG_BIAS_PULL_UP,
PIN_CONFIG_DRIVE_OPEN_DRAIN,
PIN_CONFIG_DRIVE_OPEN_SOURCE,
PIN_CONFIG_DRIVE_PUSH_PULL,
PIN_CONFIG_DRIVE_STRENGTH,
PIN_CONFIG_INPUT_DEBOUNCE,
PIN_CONFIG_INPUT_ENABLE,
PIN_CONFIG_INPUT_SCHMITT,
PIN_CONFIG_INPUT_SCHMITT_ENABLE,
PIN_CONFIG_LOW_POWER_MODE,
PIN_CONFIG_OUTPUT_ENABLE,
PIN_CONFIG_OUTPUT,
PIN_CONFIG_POWER_SOURCE,
PIN_CONFIG_SLEEP_HARDWARE_STATE,
PIN_CONFIG_SLEW_RATE,
PIN_CONFIG_END = 0x7F,
PIN_CONFIG_MAX = 0xFF,
};
enum virtio_gpio_request_command {
GPIO_REQ_SET_VALUE = 0,
GPIO_REQ_GET_VALUE = 1,
GPIO_REQ_INPUT_DIRECTION = 2,
GPIO_REQ_OUTPUT_DIRECTION = 3,
GPIO_REQ_GET_DIRECTION = 4,
GPIO_REQ_SET_CONFIG = 5,
GPIO_REQ_MAX
};
enum gpio_irq_action {
IRQ_ACTION_ENABLE = 0,
IRQ_ACTION_DISABLE,
IRQ_ACTION_ACK,
IRQ_ACTION_MASK,
IRQ_ACTION_UNMASK,
IRQ_ACTION_MAX
};
struct virtio_gpio_request {
uint8_t cmd;
uint8_t offset;
uint64_t data;
} __attribute__((packed));
struct virtio_gpio_response {
int8_t err;
uint8_t data;
} __attribute__((packed));
struct virtio_gpio_info {
struct virtio_gpio_request req;
struct virtio_gpio_response rsp;
} __attribute__((packed));
struct virtio_gpio_data {
char name[32];
} __attribute__((packed));
struct virtio_gpio_config {
uint16_t base; /* base number */
uint16_t ngpio; /* number of gpios */
} __attribute__((packed));
struct virtio_gpio_irq_request {
uint8_t action;
uint8_t pin;
uint8_t mode;
} __attribute__((packed));
struct gpio_line {
char name[32]; /* native gpio name */
char vname[32]; /* virtual gpio name */
int offset; /* offset in real chip */
int voffset; /* offset in virtual chip */
int fd; /* native gpio line fd */
int dir; /* gpio direction */
bool busy; /* gpio line request by kernel */
int value; /* gpio value */
uint64_t config; /* gpio configuration */
struct native_gpio_chip *chip; /* parent gpio chip */
struct gpio_irq_desc *irq; /* connect to irq descriptor */
};
struct native_gpio_chip {
char name[32]; /* gpio chip name */
char label[32]; /* gpio chip label name */
char dev_name[32]; /* device node name */
int fd; /* native gpio chip fd */
uint32_t ngpio; /* gpio line numbers */
struct gpio_line *lines; /* gpio lines in the chip */
};
struct gpio_irq_desc {
struct gpio_line *gpio; /* connect to gpio line */
struct mevent *mevt; /* mevent for event report */
int fd; /* read event */
int pin; /* pin number */
bool mask; /* mask and unmask */
bool deinit; /* deinit state */
uint8_t level; /* level value */
uint64_t mode; /* interrupt trigger mode */
void *data; /* virtio gpio instance */
uint64_t intr_stat; /* interrupts count */
};
struct gpio_irq_chip {
pthread_mutex_t intr_mtx;
struct gpio_irq_desc descs[VIRTIO_GPIO_MAX_VLINES];
uint64_t intr_pending; /* pending interrupts */
uint64_t intr_service; /* service interrupts */
uint64_t intr_stat; /* all interrupts count */
};
struct virtio_gpio {
struct virtio_base base;
pthread_mutex_t mtx;
struct virtio_vq_info queues[VIRTIO_GPIO_MAXQ];
struct native_gpio_chip chips[VIRTIO_GPIO_MAX_CHIPS];
uint32_t nchip;
struct gpio_line *vlines[VIRTIO_GPIO_MAX_VLINES];
uint32_t nvline;
struct virtio_gpio_config config;
struct gpio_irq_chip irq_chip;
};
static void print_gpio_info(struct virtio_gpio *gpio);
static void print_virtio_gpio_info(struct virtio_gpio_request *req,
struct virtio_gpio_response *rsp, bool in);
static void print_intr_statistics(struct gpio_irq_chip *chip);
static void record_intr_statistics(struct gpio_irq_chip *chip, uint64_t mask);
static void gpio_pio_write(struct virtio_gpio *gpio, int n, uint64_t reg);
static uint32_t gpio_pio_read(struct virtio_gpio *gpio, int n);
static void native_gpio_close_line(struct gpio_line *line);
static void
virtio_gpio_abort(struct virtio_vq_info *vq, uint16_t idx)
{
if (idx < vq->qsize) {
vq_relchain(vq, idx, 1);
vq_endchains(vq, 0);
}
}
static void
native_gpio_update_line_info(struct gpio_line *line)
{
struct gpioline_info info;
int rc;
memset(&info, 0, sizeof(info));
info.line_offset = line->offset;
rc = ioctl(line->chip->fd, GPIO_GET_LINEINFO_IOCTL, &info);
if (rc) {
WPRINTF(("ioctl GPIO_GET_LINEINFO_IOCTL error %s\n",
strerror(errno)));
return;
}
line->busy = info.flags & GPIOLINE_FLAG_KERNEL;
/*
* if it is already used by virtio gpio model,
* it is not set to busy state
*/
if (line->fd > 0)
line->busy = false;
/* 0 means output, 1 means input */
line->dir = info.flags & GPIOLINE_FLAG_IS_OUT ? 0 : 1;
strncpy(line->name, info.name, sizeof(line->name) - 1);
}
static void
native_gpio_close_chip(struct native_gpio_chip *chip)
{
int i;
if (chip) {
memset(chip->name, 0, sizeof(chip->name));
memset(chip->label, 0, sizeof(chip->label));
memset(chip->dev_name, 0, sizeof(chip->dev_name));
for (i = 0; i < chip->ngpio; i++) {
native_gpio_close_line(&chip->lines[i]);
}
if (chip->fd > 0) {
close(chip->fd);
chip->fd = -1;
}
if (chip->lines) {
free(chip->lines);
chip->lines = NULL;
}
chip->ngpio = 0;
}
}
static void
native_gpio_close_line(struct gpio_line *line)
{
if (line->fd > 0) {
close(line->fd);
line->fd = -1;
}
}
static int
native_gpio_open_line(struct gpio_line *line, unsigned int flags,
unsigned int value)
{
struct gpiohandle_request req;
int rc;
memset(&req, 0, sizeof(req));
req.lineoffsets[0] = line->offset;
req.lines = 1;
strncpy(req.consumer_label, "acrn_dm", sizeof(req.consumer_label) - 1);
if (flags) {
/*
* before setting a new flag, it needs to try to close the fd
* that has been opened first, otherwise it will not be ioctl
* successfully.
*/
native_gpio_close_line(line);
req.flags = flags;
if (flags & GPIOHANDLE_REQUEST_OUTPUT)
req.default_values[0] = value;
}
rc = ioctl(line->chip->fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
if (rc < 0) {
WPRINTF(("ioctl GPIO_GET_LINEHANDLE_IOCTL error %s\n",
strerror(errno)));
return -1;
}
if (flags) {
line->config = flags;
if (flags & GPIOHANDLE_REQUEST_OUTPUT) {
line->dir = 0;
line->value = value;
} else if (flags & GPIOHANDLE_REQUEST_INPUT)
line->dir = 1;
}
line->fd = req.fd;
return rc;
}
static int
gpio_set_value(struct virtio_gpio *gpio, unsigned int offset,
unsigned int value)
{
struct gpio_line *line;
struct gpiohandle_data data;
int rc;
line = gpio->vlines[offset];
if (line->busy || line->fd < 0) {
WPRINTF(("failed to set gpio%d value, busy:%d, fd:%d\n",
offset, line->busy, line->fd));
return -1;
}
memset(&data, 0, sizeof(data));
data.values[0] = value;
rc = ioctl(line->fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
if (rc < 0) {
WPRINTF(("ioctl GPIOHANDLE_SET_LINE_VALUES_IOCTL error %s\n",
strerror(errno)));
return -1;
}
line->value = value;
return 0;
}
static int
gpio_get_value(struct virtio_gpio *gpio, unsigned int offset)
{
struct gpio_line *line;
struct gpiohandle_data data;
int rc, fd;
line = gpio->vlines[offset];
if (line->busy) {
WPRINTF(("failed to get gpio %d value, it is busy\n", offset));
return -1;
}
fd = line->fd;
if (fd < 0) {
/*
* if the GPIO line has configured as IRQ mode, then can't use
* gpio line fd to get its value, instead, use IRQ fd to get
* the value.
*/
if (line->irq->fd < 0) {
WPRINTF(("failed to get gpio %d value, fd is invalid\n",
offset));
return -1;
}
fd = line->irq->fd;
}
memset(&data, 0, sizeof(data));
rc = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
if (rc < 0) {
WPRINTF(("ioctl GPIOHANDLE_GET_LINE_VALUES_IOCTL error %s\n",
strerror(errno)));
return -1;
}
return data.values[0];
}
static int
gpio_set_direction_input(struct virtio_gpio *gpio, unsigned int offset)
{
struct gpio_line *line;
line = gpio->vlines[offset];
return native_gpio_open_line(line, GPIOHANDLE_REQUEST_INPUT, 0);
}
static int
gpio_set_direction_output(struct virtio_gpio *gpio, unsigned int offset,
int value)
{
struct gpio_line *line;
line = gpio->vlines[offset];
return native_gpio_open_line(line, GPIOHANDLE_REQUEST_OUTPUT, value);
}
static int
gpio_get_direction(struct virtio_gpio *gpio, unsigned int offset)
{
struct gpio_line *line;
line = gpio->vlines[offset];
native_gpio_update_line_info(line);
return line->dir;
}
static int
gpio_set_config(struct virtio_gpio *gpio, unsigned int offset,
unsigned long config)
{
struct gpio_line *line;
line = gpio->vlines[offset];
/*
* gpio userspace API can only provide GPIOLINE_FLAG_OPEN_DRAIN
* and GPIOLINE_FLAG_OPEN_SOURCE settings currenlty, other
* settings will return an success.
*/
config = PIN_CONF_UNPACKED(config);
switch (config) {
case PIN_CONFIG_DRIVE_OPEN_DRAIN:
config = GPIOLINE_FLAG_OPEN_DRAIN;
break;
case PIN_CONFIG_DRIVE_OPEN_SOURCE:
config = GPIOLINE_FLAG_OPEN_SOURCE;
break;
default:
return 0;
}
return native_gpio_open_line(line, config, 0);
}
static void
gpio_request_handler(struct virtio_gpio *gpio, struct virtio_gpio_request *req,
struct virtio_gpio_response *rsp)
{
int rc;
if (req->cmd >= GPIO_REQ_MAX || req->offset >= gpio->nvline) {
WPRINTF(("discards the gpio request, command:%u, offset:%u\n",
req->cmd, req->offset));
rsp->err = -1;
return;
}
print_virtio_gpio_info(req, rsp, true);
switch (req->cmd) {
case GPIO_REQ_SET_VALUE:
rc = gpio_set_value(gpio, req->offset, req->data);
break;
case GPIO_REQ_GET_VALUE:
rc = gpio_get_value(gpio, req->offset);
if (rc >= 0)
rsp->data = rc;
break;
case GPIO_REQ_OUTPUT_DIRECTION:
rc = gpio_set_direction_output(gpio, req->offset,
req->data);
break;
case GPIO_REQ_INPUT_DIRECTION:
rc = gpio_set_direction_input(gpio, req->offset);
break;
case GPIO_REQ_GET_DIRECTION:
rc = gpio_get_direction(gpio, req->offset);
if (rc >= 0)
rsp->data = rc;
break;
case GPIO_REQ_SET_CONFIG:
rc = gpio_set_config(gpio, req->offset, req->data);
break;
default:
WPRINTF(("invalid gpio request command:%d\n", req->cmd));
rc = -1;
break;
}
rsp->err = rc < 0 ? -1 : 0;
print_virtio_gpio_info(req, rsp, false);
}
static void virtio_gpio_reset(void *vdev)
{
struct virtio_gpio *gpio;
gpio = vdev;
DPRINTF(("%s", "virtio_gpio: device reset requested!\n"));
virtio_reset_dev(&gpio->base);
}
static int
virtio_gpio_cfgwrite(void *vdev, int offset, int size, uint32_t value)
{
int cfg_size;
cfg_size = sizeof(struct virtio_gpio_config);
offset -= cfg_size;
if (offset < 0 || offset >= GPIO_PIO_SIZE) {
WPRINTF(("virtio_gpio: write to invalid reg %d\n", offset));
return -1;
}
gpio_pio_write((struct virtio_gpio *)vdev, offset >> 2, value);
return 0;
}
static int
virtio_gpio_cfgread(void *vdev, int offset, int size, uint32_t *retval)
{
struct virtio_gpio *gpio = vdev;
void *ptr;
int cfg_size;
uint32_t reg;
cfg_size = sizeof(struct virtio_gpio_config);
if (offset < 0 || offset >= cfg_size + GPIO_PIO_SIZE) {
WPRINTF(("virtio_gpio: read from invalid reg %d\n", offset));
return -1;
} else if (offset < cfg_size) {
ptr = (uint8_t *)&gpio->config + offset;
memcpy(retval, ptr, size);
} else {
reg = gpio_pio_read(gpio, (offset - cfg_size) >> 2);
memcpy(retval, &reg, size);
}
return 0;
}
static struct virtio_ops virtio_gpio_ops = {
"virtio_gpio", /* our name */
VIRTIO_GPIO_MAXQ, /* we support VTGPIO_MAXQ virtqueues */
0, /* config reg size */
virtio_gpio_reset, /* reset */
NULL, /* device-wide qnotify */
virtio_gpio_cfgread, /* read virtio config */
virtio_gpio_cfgwrite, /* write virtio config */
NULL, /* apply negotiated features */
NULL, /* called on guest set status */
};
static int
virtio_gpio_proc(struct virtio_gpio *gpio, struct iovec *iov, int n)
{
struct virtio_gpio_data *data;
struct virtio_gpio_request *req;
struct virtio_gpio_response *rsp;
struct gpio_line *line;
int i, len, rc;
if (n == 1) { /* provide gpio names for front-end driver */
data = iov[0].iov_base;
len = iov[0].iov_len;
if (len != gpio->nvline * sizeof(*data)) {
WPRINTF(("virtio gpio, invalid virtual gpio %d\n", len));
return 0;
}
for (i = 0; i < gpio->nvline; i++) {
line = gpio->vlines[i];
/*
* if the user provides the name of gpios in the
* command line paremeter, then provide it to User VM,
* otherwise provide the physical name of gpio to User VM.
*/
if (strnlen(line->vname, sizeof(line->vname)))
strncpy(data[i].name, line->vname,
sizeof(data[0].name) - 1);
else if (strnlen(line->name, sizeof(line->name)))
strncpy(data[i].name, line->name,
sizeof(data[0].name) - 1);
}
rc = gpio->nvline;
} else if (n == 2) { /* handle gpio operations requests */
req = iov[0].iov_base;
len = iov[0].iov_len;
if (len != sizeof(*req)) {
WPRINTF(("virtio gpio, invalid req size %d\n", len));
return 0;
}
rsp = iov[1].iov_base;
len = iov[1].iov_len;
if (len != sizeof(*rsp)) {
WPRINTF(("virtio gpio, invalid rsp size %d\n", len));
return 0;
}
gpio_request_handler(gpio, req, rsp);
rc = sizeof(*rsp);
} else {
WPRINTF(("virtio gpio: number of buffer error %d\n", n));
rc = 0;
}
return rc;
}
static void
virtio_gpio_notify(void *vdev, struct virtio_vq_info *vq)
{
struct iovec iov[2];
struct virtio_gpio *gpio;
uint16_t idx;
int n, len;
idx = vq->qsize;
gpio = (struct virtio_gpio *)vdev;
if (vq_has_descs(vq)) {
n = vq_getchain(vq, &idx, iov, 2, NULL);
if (n < 1 || n >= 3) {
WPRINTF(("virtio gpio, invalid chain number %d\n", n));
virtio_gpio_abort(vq, idx);
return;
}
len = virtio_gpio_proc(gpio, iov, n);
/*
* Release this chain and handle more
*/
vq_relchain(vq, idx, len);
}
}
static int
native_gpio_open_chip(struct native_gpio_chip *chip, const char *name)
{
struct gpiochip_info info;
struct gpio_line *line;
char path[64] = {0};
int fd, rc, i;
snprintf(path, sizeof(path), "/dev/%s", name);
fd = open(path, O_RDWR);
if (fd < 0) {
WPRINTF(("Can't open gpio device: %s, error %s\n",
path, strerror(errno)));
return -1;
}
memset(&info, 0, sizeof(info));
rc = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
if (rc < 0) {
WPRINTF(("Can't ioctl gpio device: %s, error %s\n",
path, strerror(errno)));
goto fail;
}
chip->lines = calloc(1, info.lines * sizeof(*chip->lines));
if (!chip->lines) {
WPRINTF(("Alloc chip lines error, %s:%d, error %s\n",
path, chip->ngpio, strerror(errno)));
goto fail;
}
chip->fd = fd;
chip->ngpio = info.lines;
strncpy(chip->name, info.name, sizeof(chip->name) - 1);
strncpy(chip->label, info.label, sizeof(chip->label) - 1);
strncpy(chip->dev_name, name, sizeof(chip->dev_name) - 1);
/* initialize all lines of the chip */
for (i = 0; i < chip->ngpio; i++) {
line = &chip->lines[i];
line->offset = i;
line->chip = chip;
/*
* The line's fd and voffset will be initialized
* when virtual gpio line connects to the real line.
*/
line->fd = -1;
line->voffset = -1;
/* Set line state and name via ioctl*/
native_gpio_update_line_info(line);
}
return 0;
fail:
if (fd > 0)
close(fd);
chip->fd = -1;
chip->ngpio = 0;
return -1;
}
static int
native_gpio_get_offset(struct native_gpio_chip *chip, char *name)
{
int rc;
int i;
/* try to find a gpio index by offset or name */
if (isalpha(name[0])) {
for (i = 0; i < chip->ngpio; i++) {
if (!strcmp(chip->lines[i].name, name))
return i;
}
} else if (isdigit(name[0])) {
rc = dm_strtoi(name, NULL, 10, &i);
if (rc == 0 && i < chip->ngpio)
return i;
}
return -1;
}
static struct gpio_line *
native_gpio_find_line(struct native_gpio_chip *chip, const char *name)
{
int offset;
char *b, *o, *c;
struct gpio_line *line = NULL;
b = o = strdup(name);
c = strsep(&o, "=");
/* find the line's offset in the chip by name or number */
offset = native_gpio_get_offset(chip, c);
if (offset < 0) {
WPRINTF(("the %s line has not been found in %s chip\n",
c, chip->dev_name));
goto out;
}
line = &chip->lines[offset];
if (line->busy || native_gpio_open_line(line, 0, 0) < 0) {
line = NULL;
goto out;
}
/* If the user sets the name of the GPIO, copy it to vname */
if (o)
strncpy(line->vname, o, sizeof(line->vname) - 1);
out:
free(b);
return line;
}
static int
native_gpio_init(struct virtio_gpio *gpio, char *opts)
{
struct gpio_line *line;
char *cstr, *lstr, *tmp, *b, *o;
int rc;
int cn = 0;
int ln = 0;
/*
* -s <slot>,virtio-gpio,<gpio resources>
* <gpio resources> format
* <@chip_name0{offset|name[=vname]:offset|name[=vname]:...}
* [@chip_name1{offset|name[=vname]:offset|name[=vname]:...}]
* [@chip_name2{offset|name[=vname]:offset|name[=vname]:...}]
* ...>
*/
b = o = strdup(opts);
while ((tmp = strsep(&o, "@")) != NULL) {
/* discard subsequent chips */
if (cn >= VIRTIO_GPIO_MAX_CHIPS ||
ln >= VIRTIO_GPIO_MAX_VLINES) {
WPRINTF(("gpio chips or lines reach max, cn %d, ln %d\n",
cn, ln));
break;
}
/* ignore the null string */
if (tmp[0] == '\0')
continue;
/*
* parse gpio chip name
* if there is no gpiochip information, like "@{...}"
* ignore all of the lines.
*/
cstr = strsep(&tmp, "{");
if (!tmp || !cstr || cstr[0] == '\0')
continue;
/* get chip information with its name */
rc = native_gpio_open_chip(&gpio->chips[cn], cstr);
if (rc < 0)
continue;
/* parse all gpio lines in one chip */
cstr = strsep(&tmp, "}");
while ((lstr = strsep(&cstr, ":")) != NULL) {
/* safety check, to avoid "@gpiochip0{::0:1...}" */
if (lstr[0] == '\0')
continue;
/* discard subsequent lines */
if (ln >= VIRTIO_GPIO_MAX_VLINES) {
WPRINTF(("Virtual gpio lines reach max:%d\n",
ln));
break;
}
/*
* If the line provided by gpio command line is found
* assign one virtual gpio offset for it.
*/
line = native_gpio_find_line(&gpio->chips[cn], lstr);
if (line) {
gpio->vlines[ln] = line;
line->voffset = ln;
ln++;
}
}
cn++;
}
gpio->nchip = cn;
gpio->nvline = ln;
free(b);
return ln == 0 ? -1 : 0;
}
static void
gpio_irq_deliver_intr(struct virtio_gpio *gpio, uint64_t mask)
{
struct virtio_vq_info *vq;
struct iovec iov[1];
uint16_t idx;
uint64_t *data;
vq = &gpio->queues[2];
idx = vq->qsize;
if (vq_has_descs(vq) && mask) {
vq_getchain(vq, &idx, iov, 1, NULL);
data = iov[0].iov_base;
if (sizeof(*data) != iov[0].iov_len) {
WPRINTF(("virtio gpio, invalid gpio data size %lu\n",
iov[0].iov_len));
virtio_gpio_abort(vq, idx);
return;
}
*data = mask;
/*
* Release this chain and handle more
*/
vq_relchain(vq, idx, sizeof(*data));
/* Generate interrupt if appropriate. */
vq_endchains(vq, 1);
/* interrupt statistics */
record_intr_statistics(&gpio->irq_chip, mask);
} else
WPRINTF(("virtio gpio failed to send an IRQ, mask %lu", mask));
}
static void
gpio_irq_generate_intr(struct virtio_gpio *gpio, int pin)
{
struct gpio_irq_chip *chip;
struct gpio_irq_desc *desc;
chip = &gpio->irq_chip;
desc = &chip->descs[pin];
/* Ignore interrupt until it is unmasked */
if (desc->mask)
return;
pthread_mutex_lock(&chip->intr_mtx);
/* set it to pending mask */
chip->intr_pending |= BIT(pin);
/*
* if all interrupts in service are acknowledged, then send pending
* interrupts.
*/
if (!chip->intr_service) {
chip->intr_service = chip->intr_pending;
chip->intr_pending = 0;
/* deliver interrupt */
gpio_irq_deliver_intr(gpio, chip->intr_service);
}
pthread_mutex_unlock(&chip->intr_mtx);
}
static void
gpio_irq_set_pin_state(int fd __attribute__((unused)),
enum ev_type t __attribute__((unused)),
void *arg)
{
struct gpioevent_data data;
struct virtio_gpio *gpio;
struct gpio_irq_desc *desc;
int err;
desc = (struct gpio_irq_desc *) arg;
gpio = (struct virtio_gpio *) desc->data;
/* get pin state */
memset(&data, 0, sizeof(data));
err = read(desc->fd, &data, sizeof(data));
if (err != sizeof(data)) {
WPRINTF(("virtio gpio, gpio mevent read error %s, len %d\n",
strerror(errno), err));
return;
}
if (data.id == GPIOEVENT_EVENT_RISING_EDGE) {
/* pin level is high */
desc->level = 1;
/* jitter protection */
if ((desc->mode & IRQ_TYPE_EDGE_RISING)
|| (desc->mode & IRQ_TYPE_LEVEL_HIGH)) {
gpio_irq_generate_intr(gpio, desc->pin);
}
} else if (data.id == GPIOEVENT_EVENT_FALLING_EDGE) {
/* pin level is low */
desc->level = 0;
/* jitter protection */
if ((desc->mode & IRQ_TYPE_EDGE_FALLING)
|| (desc->mode & IRQ_TYPE_LEVEL_LOW)) {
gpio_irq_generate_intr(gpio, desc->pin);
}
} else
WPRINTF(("virtio gpio, undefined GPIO event id %d\n", data.id));
}
static void
gpio_irq_disable(struct gpio_irq_chip *chip, unsigned int pin)
{
struct gpio_irq_desc *desc;
if (pin >= VIRTIO_GPIO_MAX_VLINES) {
WPRINTF((" gpio irq disable pin %d is invalid\n", pin));
return;
}
desc = &chip->descs[pin];
DPRINTF(("disable IRQ pin %d <-> native chip %s, GPIO %d\n",
pin, desc->gpio->chip->dev_name, desc->gpio->offset));
/* Release the mevent, mevent teardown handles IRQ desc reset */
if (desc->mevt) {
desc->deinit = false;
mevent_delete(desc->mevt);
desc->mevt = NULL;
}
}
static void
gpio_irq_teardown(void *param)
{
struct gpio_irq_desc *desc;
DPRINTF(("%s", "virtio gpio tear down\n"));
desc = (struct gpio_irq_desc *) param;
desc->mask = false;
desc->mode = IRQ_TYPE_NONE;
if (desc->fd > -1) {
close(desc->fd);
desc->fd = -1;
}
/* if deinit is not set, switch the pin to GPIO mode */
if (!desc->deinit)
native_gpio_open_line(desc->gpio, 0, 0);
}
static void
gpio_irq_enable(struct virtio_gpio *gpio, unsigned int pin,
uint64_t mode)
{
struct gpioevent_request req;
struct gpio_line *line;
struct gpio_irq_chip *chip;
struct gpio_irq_desc *desc;
int err;
chip = &gpio->irq_chip;
desc = &chip->descs[pin];
line = desc->gpio;
DPRINTF(("enable IRQ pin %d, mode %lu <-> chip %s, GPIO %d\n",
pin, mode, desc->gpio->chip->dev_name, desc->gpio->offset));
/*
* Front-end should set the gpio direction to input before
* enable one gpio to irq, so get the gpio value directly
* no need to set it to input direction.
*/
desc->level = gpio_get_value(gpio, pin);
/* Release the GPIO line before enable it for IRQ */
native_gpio_close_line(line);
memset(&req, 0, sizeof(req));
if (mode & IRQ_TYPE_EDGE_RISING)
req.eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
if (mode & IRQ_TYPE_EDGE_FALLING)
req.eventflags |= GPIOEVENT_REQUEST_FALLING_EDGE;
/*
* For level tigger, detect rising and fallling edges to
* update the IRQ level value, the value is used to check
* the level IRQ is active.
*/
if (mode & IRQ_TYPE_LEVEL_MASK)
req.eventflags |= GPIOEVENT_REQUEST_BOTH_EDGES;
if (!req.eventflags) {
WPRINTF(("failed to enable pin %d to IRQ with invalid flags\n",
pin));
return;
}
desc->mode = mode;
req.lineoffset = line->offset;
strncpy(req.consumer_label, "acrn_dm_irq",
sizeof(req.consumer_label) - 1);
err = ioctl(line->chip->fd, GPIO_GET_LINEEVENT_IOCTL, &req);
if (err < 0) {
WPRINTF(("ioctl GPIO_GET_LINEEVENT_IOCTL error %s\n",
strerror(errno)));
goto error;
}
desc->fd = req.fd;
desc->mevt = mevent_add(desc->fd, EVF_READ,
gpio_irq_set_pin_state, desc,
gpio_irq_teardown, desc);
if (!desc->mevt) {
WPRINTF(("failed to enable IRQ pin %d, mevent add error\n",
pin));
goto error;
}
return;
error:
gpio_irq_disable(chip, pin);
}
static bool
gpio_irq_has_pending_intr(struct gpio_irq_desc *desc)
{
bool level_high, level_low;
/*
* For level trigger mode, check the pin mode and level value
* to resend an interrupt.
*/
if (desc->mode & IRQ_TYPE_LEVEL_MASK) {
level_high = desc->mode & IRQ_TYPE_LEVEL_HIGH;
level_low = desc->mode & IRQ_TYPE_LEVEL_LOW;
if ((level_high && desc->level == 1) ||
(level_low && desc->level == 0))
return true;
}
return false;
}
static void
gpio_irq_clear_intr(struct gpio_irq_chip *chip, int pin)
{
pthread_mutex_lock(&chip->intr_mtx);
chip->intr_service &= ~BIT(pin);
pthread_mutex_unlock(&chip->intr_mtx);
}
static void
virtio_gpio_irq_proc(struct virtio_gpio *gpio, struct iovec *iov, uint16_t flag)
{
struct virtio_gpio_irq_request *req;
struct gpio_irq_chip *chip;
struct gpio_irq_desc *desc;
int len;
req = iov[0].iov_base;
len = iov[0].iov_len;
if (len != sizeof(*req)) {
WPRINTF(("virtio gpio, invalid req size %d\n", len));
return;
}
if (req->pin >= gpio->nvline) {
WPRINTF(("virtio gpio, invalid IRQ pin %d, ignore action %d\n",
req->pin, req->action));
return;
}
chip = &gpio->irq_chip;
desc = &chip->descs[req->pin];
switch (req->action) {
case IRQ_ACTION_ENABLE:
/*
* TODO: need to notify the FE driver
* if gpio_irq_enable failure.
*/
gpio_irq_enable(gpio, req->pin, req->mode);
/* print IRQ statistics */
print_intr_statistics(chip);
break;
case IRQ_ACTION_DISABLE:
gpio_irq_disable(chip, req->pin);
/* print IRQ statistics */
print_intr_statistics(chip);
break;
case IRQ_ACTION_ACK:
/*
* For level trigger, we need to check the level value
* for next interrupt.
*/
gpio_irq_clear_intr(chip, req->pin);
if (gpio_irq_has_pending_intr(desc))
gpio_irq_generate_intr(gpio, req->pin);
break;
case IRQ_ACTION_MASK:
desc->mask = true;
break;
case IRQ_ACTION_UNMASK:
desc->mask = false;
if (gpio_irq_has_pending_intr(desc))
gpio_irq_generate_intr(gpio, req->pin);
break;
default:
WPRINTF(("virtio gpio, unknown IRQ action %d\n", req->action));
}
}
static void
virtio_irq_evt_notify(void *vdev, struct virtio_vq_info *vq)
{
/* The front-end driver does not make a kick, just avoid warning */
DPRINTF(("%s", "virtio gpio irq_evt_notify\n"));
}
static void
virtio_irq_notify(void *vdev, struct virtio_vq_info *vq)
{
struct iovec iov[1];
struct virtio_gpio *gpio;
uint16_t idx, flag;
int n;
idx = vq->qsize;
gpio = (struct virtio_gpio *)vdev;
if (vq_has_descs(vq)) {
n = vq_getchain(vq, &idx, iov, 1, &flag);
if (n != 1) {
WPRINTF(("virtio gpio, invalid irq chain %d\n", n));
virtio_gpio_abort(vq, idx);
return;
}
virtio_gpio_irq_proc(gpio, iov, flag);
/*
* Release this chain and handle more
*/
vq_relchain(vq, idx, 1);
/* Generate interrupt if appropriate. */
vq_endchains(vq, 1);
}
}
static void
gpio_irq_deinit(struct virtio_gpio *gpio)
{
struct gpio_irq_chip *chip;
struct gpio_irq_desc *desc;
int i;
chip = &gpio->irq_chip;
pthread_mutex_destroy(&chip->intr_mtx);
for (i = 0; i < gpio->nvline; i++) {
desc = &chip->descs[i];
if (desc->mevt) {
desc->deinit = true;
mevent_delete(desc->mevt);
desc->mevt = NULL;
/* desc fd will be closed by mevent teardown */
}
}
}
static int
gpio_irq_init(struct virtio_gpio *gpio)
{
struct gpio_irq_chip *chip;
struct gpio_irq_desc *desc;
struct gpio_line *line;
int i, rc;
chip = &gpio->irq_chip;
rc = pthread_mutex_init(&chip->intr_mtx, NULL);
if (rc) {
WPRINTF(("IRQ pthread_mutex_init failed with error %d!\n", rc));
return -1;
}
for (i = 0; i < gpio->nvline; i++) {
desc = &chip->descs[i];
line = gpio->vlines[i];
desc->pin = i;
desc->fd = -1;
desc->mevt = NULL;
desc->data = gpio;
desc->gpio = line;
line->irq = desc;
}
return 0;
}
static int
virtio_gpio_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
struct virtio_gpio *gpio;
pthread_mutexattr_t attr;
int rc, i;
/* Just support one bdf */
if (virtio_gpio_is_active)
return -1;
if (!opts) {
WPRINTF(("%s", "virtio gpio: needs gpio information\n"));
rc = -EINVAL;
goto init_fail;
}
gpio = calloc(1, sizeof(struct virtio_gpio));
if (!gpio) {
WPRINTF(("%s", "virtio gpio: failed to calloc virtio_gpio\n"));
rc = -ENOMEM;
goto init_fail;
}
rc = native_gpio_init(gpio, opts);
if (rc) {
WPRINTF(("%s", "virtio gpio: failed to initialize gpio\n"));
goto gpio_fail;
}
rc = gpio_irq_init(gpio);
if (rc) {
WPRINTF(("%s", "virtio gpio: failed to initialize gpio irq\n"));
goto irq_fail;
}
/* init mutex attribute properly to avoid deadlock */
rc = pthread_mutexattr_init(&attr);
if (rc) {
WPRINTF(("mutexattr init failed with error %d!\n", rc));
goto mtx_fail;
}
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
if (rc) {
WPRINTF(("mutexattr_settype failed with error %d!\n", rc));
goto fail;
}
rc = pthread_mutex_init(&gpio->mtx, &attr);
if (rc) {
WPRINTF(("pthread_mutex_init failed with error %d!\n", rc));
goto fail;
}
virtio_linkup(&gpio->base, &virtio_gpio_ops, gpio, dev, gpio->queues,
BACKEND_VBSU);
/* gpio base for frontend gpio chip */
gpio->config.base = 0;
/* gpio numbers for frontend gpio chip */
gpio->config.ngpio = gpio->nvline;
gpio->base.device_caps = VIRTIO_GPIO_S_HOSTCAPS;
gpio->base.mtx = &gpio->mtx;
gpio->queues[0].qsize = 64;
gpio->queues[0].notify = virtio_gpio_notify;
gpio->queues[1].qsize = 64;
gpio->queues[1].notify = virtio_irq_notify;
gpio->queues[2].qsize = 64;
gpio->queues[2].notify = virtio_irq_evt_notify;
/* initialize config space */
pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_GPIO);
pci_set_cfgdata16(dev, PCIR_VENDOR, INTEL_VENDOR_ID);
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_BASEPERIPH);
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_GPIO);
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, INTEL_VENDOR_ID);
/* use BAR 1 to map MSI-X table and PBA, if we're using MSI-X */
if (virtio_interrupt_init(&gpio->base, virtio_uses_msix())) {
rc = -1;
goto fail;
}
/* Allocate PIO space for GPIO */
virtio_gpio_ops.cfgsize = sizeof(struct virtio_gpio_config) + GPIO_PIO_SIZE;
/* use BAR 0 to map config regs in IO space */
virtio_set_io_bar(&gpio->base, 0);
gpio_pio_start = dev->bar[0].addr + VIRTIO_PCI_CONFIG_OFF(1) +
sizeof(struct virtio_gpio_config);
virtio_gpio_is_active = true;
/* dump gpio information */
print_gpio_info(gpio);
return 0;
fail:
pthread_mutex_destroy(&gpio->mtx);
mtx_fail:
gpio_irq_deinit(gpio);
irq_fail:
for (i = 0; i < gpio->nchip; i++)
native_gpio_close_chip(&gpio->chips[i]);
gpio_fail:
free(gpio);
dev->arg = NULL;
init_fail:
return rc;
}
static void
virtio_gpio_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
struct virtio_gpio *gpio;
int i;
DPRINTF(("%s", "virtio gpio: pci_gpio_deinit\r\n"));
virtio_gpio_is_active = false;
gpio = (struct virtio_gpio *)dev->arg;
if (gpio) {
pthread_mutex_destroy(&gpio->mtx);
gpio_irq_deinit(gpio);
for (i = 0; i < gpio->nchip; i++)
native_gpio_close_chip(&gpio->chips[i]);
virtio_gpio_reset(gpio);
free(gpio);
dev->arg = NULL;
}
}
static void
virtio_gpio_write_dsdt(struct pci_vdev *dev)
{
dsdt_line("");
dsdt_line("Device (AGPI)");
dsdt_line("{");
dsdt_line(" Name (_ADR, 0x%04X%04X)", dev->slot, dev->func);
dsdt_line(" Name (_DDN, \"Virtio GPIO Controller \")");
dsdt_line(" Name (_UID, One)");
dsdt_line(" Name (LINK, \"\\\\_SB_.PCI0.AGPI\")");
dsdt_line(" Method (_CRS, 0, NotSerialized)");
dsdt_line(" {");
dsdt_line(" }");
dsdt_line("}");
dsdt_line("");
dsdt_line("Scope (_SB)");
dsdt_line("{");
dsdt_line(" Method (%s, 2, Serialized)", PIO_GPIO_CM_SET);
dsdt_line(" {");
dsdt_line(" Local0 = (0x%08x + (Arg0 << 2))", gpio_pio_start);
dsdt_line(" OperationRegion (GPOR, SystemIO, Local0, 0x04)");
dsdt_line(" Field (GPOR, DWordAcc, NoLock, Preserve)");
dsdt_line(" {");
dsdt_line(" TEMP, 2");
dsdt_line(" }");
dsdt_line(" TEMP = Arg1");
dsdt_line(" }");
dsdt_line("");
dsdt_line(" Method (%s, 1, Serialized)", PIO_GPIO_CM_GET);
dsdt_line(" {");
dsdt_line(" Local0 = (0x%08x + (Arg0 << 2))", gpio_pio_start);
dsdt_line(" OperationRegion (GPOR, SystemIO, Local0, 0x04)");
dsdt_line(" Field (GPOR, DWordAcc, NoLock, Preserve)");
dsdt_line(" {");
dsdt_line(" TEMP, 1,");
dsdt_line(" }");
dsdt_line(" Return (TEMP)");
dsdt_line(" }");
dsdt_line("}");
}
static void
gpio_pio_write(struct virtio_gpio *gpio, int n, uint64_t reg)
{
struct gpio_line *line;
int value, dir, mode;
uint16_t config;
if (n >= gpio->nvline) {
WPRINTF(("pio write is invalid, n %d, nvline %d\n",
n, gpio->nvline));
return;
}
line = gpio->vlines[n];
value = reg & PIO_GPIO_VALUE_MASK;
dir = (reg & PIO_GPIO_DIR_MASK) >> PIO_GPIO_DIR_OFFSET;
mode = (reg & PIO_GPIO_MODE_MASK) >> PIO_GPIO_MODE_OFFSET;
config = (reg & PIO_GPIO_CONFIG_MASK) >> PIO_GPIO_CONFIG_OFFSET;
/* 0 means GPIO, 1 means IRQ */
if (mode == 1) {
WPRINTF(("pio write failure, gpio %d is in IRQ mode\n", n));
return;
}
DPRINTF(("pio write n %d, reg 0x%lX, val %d, dir %d, mod %d, cfg 0x%x\n",
n, reg, value, dir, mode, config));
if (config != line->config)
gpio_set_config(gpio, n, config);
/* 0 means output, 1 means input */
if (dir != line->dir || ((dir == 0) && (value != line->value)))
dir == 0 ? gpio_set_direction_output(gpio, n, value) :
gpio_set_direction_input(gpio, n);
}
static uint32_t
gpio_pio_read(struct virtio_gpio *gpio, int n)
{
struct gpio_line *line;
uint32_t reg = 0;
if (n >= gpio->nvline) {
WPRINTF(("pio read is invalid, n %d, nvline %d\n",
n, gpio->nvline));
return 0xFFFFFFFF;
}
line = gpio->vlines[n];
reg = line->value;
reg |= ((line->dir << PIO_GPIO_DIR_OFFSET) & PIO_GPIO_DIR_MASK);
reg |= ((line->config << PIO_GPIO_CONFIG_OFFSET) &
PIO_GPIO_CONFIG_MASK);
if (line->irq->fd > 0)
reg |= 1 << PIO_GPIO_MODE_OFFSET;
DPRINTF(("pio read n %d, reg 0x%X\n", n, reg));
return reg;
}
struct pci_vdev_ops pci_ops_virtio_gpio = {
.class_name = "virtio-gpio",
.vdev_init = virtio_gpio_init,
.vdev_deinit = virtio_gpio_deinit,
.vdev_barwrite = virtio_pci_write,
.vdev_barread = virtio_pci_read,
.vdev_write_dsdt = virtio_gpio_write_dsdt,
};
static void
print_gpio_info(struct virtio_gpio *gpio)
{
struct native_gpio_chip *chip;
struct gpio_line *line;
int i;
DPRINTF(("=== virtual lines(%u) mapping ===\n", gpio->nvline));
for (i = 0; i < gpio->nvline; i++) {
line = gpio->vlines[i];
DPRINTF(("%d: (vname:%8s, name:%8s) <=> %s, gpio=%d\n",
i,
line->vname,
line->name,
line->chip->dev_name,
line->offset));
}
DPRINTF(("=== native gpio chips(%u) info ===\n", gpio->nchip));
for (i = 0; i < gpio->nchip; i++) {
chip = &gpio->chips[i];
DPRINTF(("index:%d, name:%s, dev_name:%s, label:%s, ngpio:%u\n",
i,
chip->name,
chip->dev_name,
chip->label,
chip->ngpio));
}
}
static void
print_virtio_gpio_info(struct virtio_gpio_request *req,
struct virtio_gpio_response *rsp, bool in)
{
const char *item;
const char *const cmd_map[GPIO_REQ_MAX + 1] = {
"GPIO_REQ_SET_VALUE",
"GPIO_REQ_GET_VALUE",
"GPIO_REQ_INPUT_DIRECTION",
"GPIO_REQ_OUTPUT_DIRECTION",
"GPIO_REQ_GET_DIRECTION",
"GPIO_REQ_SET_CONFIG",
"GPIO_REQ_MAX",
};
if (req->cmd == GPIO_REQ_SET_VALUE || req->cmd == GPIO_REQ_GET_VALUE)
item = "value";
else if (req->cmd == GPIO_REQ_SET_CONFIG)
item = "config";
else if (req->cmd == GPIO_REQ_GET_DIRECTION)
item = "direction";
else
item = "data";
if (in)
DPRINTF(("<<<< gpio=%u, %s, %s=%lu\n",
req->offset,
cmd_map[req->cmd],
item,
req->data));
else
DPRINTF((">>>> gpio=%u, err=%d, %s=%d\n",
req->offset,
rsp->err,
item,
rsp->data));
}
static void
record_intr_statistics(struct gpio_irq_chip *chip, uint64_t mask)
{
struct gpio_irq_desc *desc;
int i;
for (i = 0; i < VIRTIO_GPIO_MAX_VLINES; i++) {
desc = &chip->descs[i];
if (mask & BIT(desc->pin))
desc->intr_stat++;
}
chip->intr_stat++;
}
static void
print_intr_statistics(struct gpio_irq_chip *chip)
{
struct gpio_irq_desc *desc;
int i;
DPRINTF(("virtio gpio generated interrupts %lu\n", chip->intr_stat));
for (i = 0; i < VIRTIO_GPIO_MAX_VLINES; i++) {
desc = &chip->descs[i];
if (!desc->gpio || desc->intr_stat == 0)
continue;
DPRINTF(("Chip %s GPIO %d generated interrupts %lu\n",
desc->gpio->chip->dev_name, desc->gpio->offset,
desc->intr_stat));
}
}
DEFINE_PCI_DEVTYPE(pci_ops_virtio_gpio);