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:
parent
92a0a399b7
commit
d34b3ebdd0
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue