mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-06-22 13:37:10 +00:00
Modified the copyright year range in code, and corrected "int32_tel" into "Intel" in two "hypervisor/include/debug/profiling.h" and "hypervisor/include/debug/profiling_internal.h". Tracked-On: #7559 Signed-off-by: Ziheng Li <ziheng.li@intel.com>
1653 lines
40 KiB
C
1653 lines
40 KiB
C
/*
|
|
* Copyright (C) 2019-2022 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, ®, 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);
|