From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Yuan Liu Date: Wed, 27 Feb 2019 15:54:46 +0800 Subject: [PATCH] add an IRQ chip to gpio-virtio GPIO chip. add an IRQ chip to support GPIO IRQ function, the IRQ chip operations and generating IRQ sources are based on virtio. v2: replace spin_lock_irqsave with spin_lock. v3: 1) asynchronouse free irq request instead synchronous way. 2) refine the commit message. Change-Id: Id079ade05ce7b65fe02be353c42064b404e6228c Tracked-On: projectacrn/acrn-hypervisor#2512 Signed-off-by: Yuan Liu Reviewed-by: Zhao Yakui Reviewed-by: Yu Wang Tracked-On: PKT-1852 --- drivers/gpio/gpio-virtio.c | 214 +++++++++++++++++++++++++++++++++++-- 1 file changed, 206 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-virtio.c b/drivers/gpio/gpio-virtio.c index 4cb45737e639..befcea14a2e8 100644 --- a/drivers/gpio/gpio-virtio.c +++ b/drivers/gpio/gpio-virtio.c @@ -33,6 +33,17 @@ enum gpio_virtio_request_command { GPIO_REQ_MAX }; +enum gpio_virtio_irq_action { + GPIO_IRQ_ACTION_ENABLE = 0, + GPIO_IRQ_ACTION_DISABLE, + GPIO_IRQ_ACTION_ACK, + GPIO_IRQ_ACTION_MASK, + GPIO_IRQ_ACTION_UNMASK, + + GPIO_IRQ_MAX +}; + + struct gpio_virtio_request { uint8_t cmd; uint8_t offset; @@ -58,14 +69,26 @@ struct gpio_virtio_data { char name[32]; } __packed; +struct gpio_virtio_irq_request { + uint8_t action; + uint8_t pin; + uint8_t mode; +} __packed; + + struct gpio_virtio { struct device *dev; struct virtio_device *vdev; struct virtqueue *gpio_vq; + struct virtqueue *irq_vq; + struct virtqueue *evt_vq; struct gpio_chip chip; struct gpio_virtio_data *data; const char **names; + uint64_t *evts; struct mutex gpio_lock; + spinlock_t irq_lock; + spinlock_t evt_lock; }; static unsigned int features[] = {GPIO_VIRTIO_F_CHIP}; @@ -285,21 +308,180 @@ static int gpio_virtio_register_chip(struct gpio_virtio *vgpio, return err; } +static void gpio_virtio_irq_handler(struct virtqueue *vq) +{ + struct gpio_virtio *vgpio = vq->vdev->priv; + struct gpio_virtio_irq_request *req; + unsigned int len; + + spin_lock(&vgpio->irq_lock); + while ((req = virtqueue_get_buf(vgpio->irq_vq, &len)) != NULL) + kfree(req); + spin_unlock(&vgpio->irq_lock); +} + +static void gpio_virtio_event_handler(struct virtqueue *vq) +{ + struct gpio_virtio *vgpio = vq->vdev->priv; + struct scatterlist sg; + uint64_t *evt; + int len, bit, irq; + + spin_lock(&vgpio->evt_lock); + while ((evt = virtqueue_get_buf(vgpio->evt_vq, &len)) != NULL) { + spin_unlock(&vgpio->evt_lock); + for_each_set_bit(bit, (unsigned long *)evt, 64) { + irq = irq_find_mapping(vgpio->chip.irq.domain, bit); + generic_handle_irq(irq); + } + spin_lock(&vgpio->evt_lock); + sg_init_one(&sg, evt, sizeof(*evt)); + virtqueue_add_inbuf(vgpio->evt_vq, &sg, 1, evt, GFP_ATOMIC); + } + spin_unlock(&vgpio->evt_lock); +} + static int init_vqs(struct gpio_virtio *vgpio) { - struct virtqueue *vqs[1]; - vq_callback_t *callbacks[1] = {NULL}; - const char * const names[1] = {"gpio"}; + struct virtqueue *vqs[3]; + vq_callback_t *callbacks[3] = {NULL, gpio_virtio_irq_handler, + gpio_virtio_event_handler}; + const char * const names[3] = {"gpio", "gpio-irq", "gpio-irq-evt"}; int err; - err = virtio_find_vqs(vgpio->vdev, 1, vqs, callbacks, names, NULL); + err = virtio_find_vqs(vgpio->vdev, 3, vqs, callbacks, names, NULL); if (err) return err; vgpio->gpio_vq = vqs[0]; + vgpio->irq_vq = vqs[1]; + vgpio->evt_vq = vqs[2]; + return 0; } +static int gpio_virtio_alloc_event_buffer(struct gpio_virtio *vgpio) +{ + struct scatterlist sg; + uint64_t *evt; + int i, n, err; + + n = virtqueue_get_vring_size(vgpio->evt_vq); + if (n <= 0) { + dev_err(&vgpio->vdev->dev, "failed to get irq vring size\n"); + return -EINVAL; + } + evt = kcalloc(n, sizeof(*evt), GFP_KERNEL); + if (!evt) + return -ENOMEM; + + /* Pre-allocating the buffer for interrupt events */ + for (i = 0; i < n; i++) { + sg_init_one(&sg, evt + i, sizeof(*evt)); + err = virtqueue_add_inbuf(vgpio->evt_vq, &sg, 1, evt + i, + GFP_ATOMIC); + if (err) { + dev_err(&vgpio->vdev->dev, + "failed to add inbuf for irq events buffer\n"); + kfree(evt); + return err; + } + } + vgpio->evts = evt; + return 0; +} + +static void gpio_virtio_irq_update(struct irq_data *d, unsigned int action) +{ + struct gpio_virtio *vgpio; + struct gpio_chip *chip; + struct gpio_virtio_irq_request *req; + struct scatterlist sg; + unsigned long timeout; + int err, len; + + chip = irq_data_get_irq_chip_data(d); + vgpio = gpiochip_get_data(chip); + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) { + dev_err(&vgpio->vdev->dev, + "failed to alloc buffer for irq, ignore pin %d, action %u\n", + d->hwirq, action); + return; + } + + req->action = action; + req->pin = d->hwirq; + if (action == GPIO_IRQ_ACTION_ENABLE) + req->mode = irq_get_trigger_type(d->irq); + + sg_init_one(&sg, req, sizeof(*req)); + spin_lock(&vgpio->irq_lock); + err = virtqueue_add_outbuf(vgpio->irq_vq, &sg, 1, req, GFP_ATOMIC); + if (err) { + dev_err(&vgpio->vdev->dev, + "failed to add outbuf for irq, ignore pin %d, action %u\n", + d->hwirq, action); + spin_unlock(&vgpio->irq_lock); + goto out; + } + virtqueue_kick(vgpio->irq_vq); + spin_unlock(&vgpio->irq_lock); + + return; +out: + kfree(req); +} + +static void gpio_virtio_irq_mask(struct irq_data *d) +{ + gpio_virtio_irq_update(d, GPIO_IRQ_ACTION_MASK); +} + +static void gpio_virtio_irq_unmask(struct irq_data *d) +{ + gpio_virtio_irq_update(d, GPIO_IRQ_ACTION_UNMASK); +} + +static void gpio_virtio_irq_ack(struct irq_data *d) +{ + gpio_virtio_irq_update(d, GPIO_IRQ_ACTION_ACK); +} + +static void gpio_virtio_irq_enable(struct irq_data *d) +{ + /* TODO: need to handle the failure of GPIO_IRQ_ACTION_ENABLE */ + gpio_virtio_irq_update(d, GPIO_IRQ_ACTION_ENABLE); +} + +static void gpio_virtio_irq_disable(struct irq_data *d) +{ + gpio_virtio_irq_update(d, GPIO_IRQ_ACTION_DISABLE); +} + +static int gpio_virtio_irq_type(struct irq_data *d, unsigned int type) +{ + if (type & ~IRQ_TYPE_SENSE_MASK) + return -EINVAL; + + if (type & IRQ_TYPE_EDGE_BOTH) + irq_set_handler_locked(d, handle_edge_irq); + else if (type & IRQ_TYPE_LEVEL_MASK) + irq_set_handler_locked(d, handle_level_irq); + + return 0; +} + +static struct irq_chip gpio_virtio_irqchip = { + .name = "virtio-gpio-irq", + .irq_mask = gpio_virtio_irq_mask, + .irq_unmask = gpio_virtio_irq_unmask, + .irq_set_type = gpio_virtio_irq_type, + .irq_ack = gpio_virtio_irq_ack, + .irq_enable = gpio_virtio_irq_enable, + .irq_disable = gpio_virtio_irq_disable, +}; + static int gpio_virtio_probe(struct virtio_device *vdev) { struct gpio_virtio *vgpio; @@ -314,17 +496,32 @@ static int gpio_virtio_probe(struct virtio_device *vdev) vdev->priv = vgpio; vgpio->vdev = vdev; mutex_init(&vgpio->gpio_lock); + spin_lock_init(&vgpio->irq_lock); + spin_lock_init(&vgpio->evt_lock); err = init_vqs(vgpio); if (err) - goto out; + goto init_err; err = gpio_virtio_register_chip(vgpio, pdev); if (err) - goto out; + goto init_err; + + err = gpiochip_irqchip_add(&vgpio->chip, &gpio_virtio_irqchip, 0, + handle_bad_irq, IRQ_TYPE_NONE); + if (err) + goto irq_err; + + err = gpio_virtio_alloc_event_buffer(vgpio); + if (err) + goto irq_err; return 0; -out: - dev_err(&vgpio->vdev->dev, "failed to initialize gpio virtio\n"); + +irq_err: + gpiochip_remove(&vgpio->chip); + kfree(vgpio->data); + kfree(vgpio->names); +init_err: kfree(vgpio); return err; } @@ -343,6 +540,7 @@ static void gpio_virtio_remove(struct virtio_device *vdev) kfree(gpio->data); kfree(gpio->names); + kfree(gpio->evts); kfree(gpio); } -- https://clearlinux.org