123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- /*
- * VFIO platform devices interrupt handling
- *
- * Copyright (C) 2013 - Virtual Open Systems
- * Author: Antonios Motakis <a.motakis@virtualopensystems.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2, as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
- #include <linux/eventfd.h>
- #include <linux/interrupt.h>
- #include <linux/slab.h>
- #include <linux/types.h>
- #include <linux/vfio.h>
- #include <linux/irq.h>
- #include "vfio_platform_private.h"
- static void vfio_platform_mask(struct vfio_platform_irq *irq_ctx)
- {
- unsigned long flags;
- spin_lock_irqsave(&irq_ctx->lock, flags);
- if (!irq_ctx->masked) {
- disable_irq_nosync(irq_ctx->hwirq);
- irq_ctx->masked = true;
- }
- spin_unlock_irqrestore(&irq_ctx->lock, flags);
- }
- static int vfio_platform_mask_handler(void *opaque, void *unused)
- {
- struct vfio_platform_irq *irq_ctx = opaque;
- vfio_platform_mask(irq_ctx);
- return 0;
- }
- static int vfio_platform_set_irq_mask(struct vfio_platform_device *vdev,
- unsigned index, unsigned start,
- unsigned count, uint32_t flags,
- void *data)
- {
- if (start != 0 || count != 1)
- return -EINVAL;
- if (!(vdev->irqs[index].flags & VFIO_IRQ_INFO_MASKABLE))
- return -EINVAL;
- if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
- int32_t fd = *(int32_t *)data;
- if (fd >= 0)
- return vfio_virqfd_enable((void *) &vdev->irqs[index],
- vfio_platform_mask_handler,
- NULL, NULL,
- &vdev->irqs[index].mask, fd);
- vfio_virqfd_disable(&vdev->irqs[index].mask);
- return 0;
- }
- if (flags & VFIO_IRQ_SET_DATA_NONE) {
- vfio_platform_mask(&vdev->irqs[index]);
- } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
- uint8_t mask = *(uint8_t *)data;
- if (mask)
- vfio_platform_mask(&vdev->irqs[index]);
- }
- return 0;
- }
- static void vfio_platform_unmask(struct vfio_platform_irq *irq_ctx)
- {
- unsigned long flags;
- spin_lock_irqsave(&irq_ctx->lock, flags);
- if (irq_ctx->masked) {
- enable_irq(irq_ctx->hwirq);
- irq_ctx->masked = false;
- }
- spin_unlock_irqrestore(&irq_ctx->lock, flags);
- }
- static int vfio_platform_unmask_handler(void *opaque, void *unused)
- {
- struct vfio_platform_irq *irq_ctx = opaque;
- vfio_platform_unmask(irq_ctx);
- return 0;
- }
- static int vfio_platform_set_irq_unmask(struct vfio_platform_device *vdev,
- unsigned index, unsigned start,
- unsigned count, uint32_t flags,
- void *data)
- {
- if (start != 0 || count != 1)
- return -EINVAL;
- if (!(vdev->irqs[index].flags & VFIO_IRQ_INFO_MASKABLE))
- return -EINVAL;
- if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
- int32_t fd = *(int32_t *)data;
- if (fd >= 0)
- return vfio_virqfd_enable((void *) &vdev->irqs[index],
- vfio_platform_unmask_handler,
- NULL, NULL,
- &vdev->irqs[index].unmask,
- fd);
- vfio_virqfd_disable(&vdev->irqs[index].unmask);
- return 0;
- }
- if (flags & VFIO_IRQ_SET_DATA_NONE) {
- vfio_platform_unmask(&vdev->irqs[index]);
- } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
- uint8_t unmask = *(uint8_t *)data;
- if (unmask)
- vfio_platform_unmask(&vdev->irqs[index]);
- }
- return 0;
- }
- static irqreturn_t vfio_automasked_irq_handler(int irq, void *dev_id)
- {
- struct vfio_platform_irq *irq_ctx = dev_id;
- unsigned long flags;
- int ret = IRQ_NONE;
- spin_lock_irqsave(&irq_ctx->lock, flags);
- if (!irq_ctx->masked) {
- ret = IRQ_HANDLED;
- /* automask maskable interrupts */
- disable_irq_nosync(irq_ctx->hwirq);
- irq_ctx->masked = true;
- }
- spin_unlock_irqrestore(&irq_ctx->lock, flags);
- if (ret == IRQ_HANDLED)
- eventfd_signal(irq_ctx->trigger, 1);
- return ret;
- }
- static irqreturn_t vfio_irq_handler(int irq, void *dev_id)
- {
- struct vfio_platform_irq *irq_ctx = dev_id;
- eventfd_signal(irq_ctx->trigger, 1);
- return IRQ_HANDLED;
- }
- static int vfio_set_trigger(struct vfio_platform_device *vdev, int index,
- int fd, irq_handler_t handler)
- {
- struct vfio_platform_irq *irq = &vdev->irqs[index];
- struct eventfd_ctx *trigger;
- int ret;
- if (irq->trigger) {
- irq_clear_status_flags(irq->hwirq, IRQ_NOAUTOEN);
- free_irq(irq->hwirq, irq);
- kfree(irq->name);
- eventfd_ctx_put(irq->trigger);
- irq->trigger = NULL;
- }
- if (fd < 0) /* Disable only */
- return 0;
- irq->name = kasprintf(GFP_KERNEL, "vfio-irq[%d](%s)",
- irq->hwirq, vdev->name);
- if (!irq->name)
- return -ENOMEM;
- trigger = eventfd_ctx_fdget(fd);
- if (IS_ERR(trigger)) {
- kfree(irq->name);
- return PTR_ERR(trigger);
- }
- irq->trigger = trigger;
- irq_set_status_flags(irq->hwirq, IRQ_NOAUTOEN);
- ret = request_irq(irq->hwirq, handler, 0, irq->name, irq);
- if (ret) {
- kfree(irq->name);
- eventfd_ctx_put(trigger);
- irq->trigger = NULL;
- return ret;
- }
- if (!irq->masked)
- enable_irq(irq->hwirq);
- return 0;
- }
- static int vfio_platform_set_irq_trigger(struct vfio_platform_device *vdev,
- unsigned index, unsigned start,
- unsigned count, uint32_t flags,
- void *data)
- {
- struct vfio_platform_irq *irq = &vdev->irqs[index];
- irq_handler_t handler;
- if (vdev->irqs[index].flags & VFIO_IRQ_INFO_AUTOMASKED)
- handler = vfio_automasked_irq_handler;
- else
- handler = vfio_irq_handler;
- if (!count && (flags & VFIO_IRQ_SET_DATA_NONE))
- return vfio_set_trigger(vdev, index, -1, handler);
- if (start != 0 || count != 1)
- return -EINVAL;
- if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
- int32_t fd = *(int32_t *)data;
- return vfio_set_trigger(vdev, index, fd, handler);
- }
- if (flags & VFIO_IRQ_SET_DATA_NONE) {
- handler(irq->hwirq, irq);
- } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
- uint8_t trigger = *(uint8_t *)data;
- if (trigger)
- handler(irq->hwirq, irq);
- }
- return 0;
- }
- int vfio_platform_set_irqs_ioctl(struct vfio_platform_device *vdev,
- uint32_t flags, unsigned index, unsigned start,
- unsigned count, void *data)
- {
- int (*func)(struct vfio_platform_device *vdev, unsigned index,
- unsigned start, unsigned count, uint32_t flags,
- void *data) = NULL;
- switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
- case VFIO_IRQ_SET_ACTION_MASK:
- func = vfio_platform_set_irq_mask;
- break;
- case VFIO_IRQ_SET_ACTION_UNMASK:
- func = vfio_platform_set_irq_unmask;
- break;
- case VFIO_IRQ_SET_ACTION_TRIGGER:
- func = vfio_platform_set_irq_trigger;
- break;
- }
- if (!func)
- return -ENOTTY;
- return func(vdev, index, start, count, flags, data);
- }
- int vfio_platform_irq_init(struct vfio_platform_device *vdev)
- {
- int cnt = 0, i;
- while (vdev->get_irq(vdev, cnt) >= 0)
- cnt++;
- vdev->irqs = kcalloc(cnt, sizeof(struct vfio_platform_irq), GFP_KERNEL);
- if (!vdev->irqs)
- return -ENOMEM;
- for (i = 0; i < cnt; i++) {
- int hwirq = vdev->get_irq(vdev, i);
- if (hwirq < 0)
- goto err;
- spin_lock_init(&vdev->irqs[i].lock);
- vdev->irqs[i].flags = VFIO_IRQ_INFO_EVENTFD;
- if (irq_get_trigger_type(hwirq) & IRQ_TYPE_LEVEL_MASK)
- vdev->irqs[i].flags |= VFIO_IRQ_INFO_MASKABLE
- | VFIO_IRQ_INFO_AUTOMASKED;
- vdev->irqs[i].count = 1;
- vdev->irqs[i].hwirq = hwirq;
- vdev->irqs[i].masked = false;
- }
- vdev->num_irqs = cnt;
- return 0;
- err:
- kfree(vdev->irqs);
- return -EINVAL;
- }
- void vfio_platform_irq_cleanup(struct vfio_platform_device *vdev)
- {
- int i;
- for (i = 0; i < vdev->num_irqs; i++)
- vfio_set_trigger(vdev, i, -1, NULL);
- vdev->num_irqs = 0;
- kfree(vdev->irqs);
- }
|