DM: virtio-gpio: emulate GPIO IRQ controller

GPIO IRQ controller emulation is used to handle level trigger and
edge trigger interrupts. Use GPIO IRQ virtqueue to handle IRQ chip
operations and GPIO event virtqueue to indicate IRQ source to UOS.

Tracked-On: #2512
Signed-off-by: Yuan Liu <yuan1.liu@intel.com>
Acked-by: Yu Wang <yu1.wang@intel.com>
This commit is contained in:
Yuan Liu 2019-03-05 11:42:12 +08:00 committed by Eddie Dong
parent 92a0a399b7
commit d34b3ebdd0
1 changed files with 304 additions and 1 deletions

View File

@ -117,6 +117,8 @@ static FILE *dbg_file;
} \
} while (0)
#define BIT(x) (1 << (x))
/* Virtio GPIO supports maximum number of virtual gpio */
#define VIRTIO_GPIO_MAX_VLINES 64
@ -130,6 +132,14 @@ static FILE *dbg_file;
#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;
@ -171,6 +181,16 @@ enum virtio_gpio_request_command {
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;
@ -807,10 +827,255 @@ native_gpio_init(struct virtio_gpio *gpio, char *opts)
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];
if (vq_has_descs(vq) && mask) {
vq_getchain(vq, &idx, iov, 1, NULL);
data = iov[0].iov_base;
assert(sizeof(*data) == iov[0].iov_len);
*data = mask;
/*
* Release this chain and handle more
*/
vq_relchain(vq, idx, sizeof(*data));
/* Generate interrupt if appropriate. */
vq_endchains(vq, 1);
} else
DPRINTF("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 last_level, err;
assert(arg != NULL);
desc = (struct gpio_irq_desc *) arg;
gpio = (struct virtio_gpio *) desc->data;
last_level = desc->level;
/* get pin state */
memset(&data, 0, sizeof(data));
err = read(desc->fd, &data, sizeof(data));
if (err != sizeof(data)) {
DPRINTF("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) && (last_level == 0))
|| (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) && (last_level == 1))
|| (desc->mode & IRQ_TYPE_LEVEL_LOW)) {
gpio_irq_generate_intr(gpio, desc->pin);
}
} else
DPRINTF("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) {
DPRINTF(" 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) {
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");
assert(param != NULL);
desc = (struct gpio_irq_desc *) param;
desc->mask = false;
desc->mode = IRQ_TYPE_NONE;
if (desc->fd > -1) {
close(desc->fd);
desc->fd = -1;
}
}
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) {
DPRINTF("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) {
DPRINTF("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) {
DPRINTF("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;
@ -822,7 +1087,42 @@ virtio_gpio_irq_proc(struct virtio_gpio *gpio, struct iovec *iov, uint16_t flag)
return;
}
/* implement in next patch */
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);
break;
case IRQ_ACTION_DISABLE:
gpio_irq_disable(chip, req->pin);
/* reopen the GPIO */
native_gpio_open_line(desc->gpio, 0, 0);
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:
DPRINTF("virtio gpio, unknown IRQ action %d\n", req->action);
}
}
static void
@ -850,6 +1150,9 @@ virtio_irq_notify(void *vdev, struct virtio_vq_info *vq)
* Release this chain and handle more
*/
vq_relchain(vq, idx, 1);
/* Generate interrupt if appropriate. */
vq_endchains(vq, 1);
}
}