Skip to content

Commit

Permalink
virt: gunyah: Add irqfd interface
Browse files Browse the repository at this point in the history
Enable support for creating irqfds which can raise an interrupt on a
Gunyah virtual machine. irqfds are exposed to userspace as a Gunyah VM
function with the name "irqfd". If the VM devicetree is not configured
to create a doorbell with the corresponding label, userspace will still
be able to assert the eventfd but no interrupt will be raised on the
guest.

Co-developed-by: Prakruthi Deepak Heragu <quic_pheragu@quicinc.com>
Signed-off-by: Prakruthi Deepak Heragu <quic_pheragu@quicinc.com>
Signed-off-by: Elliot Berman <quic_eberman@quicinc.com>
  • Loading branch information
eberman-quic authored and intel-lab-lkp committed Dec 19, 2022
1 parent 88981a6 commit 158304c
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 1 deletion.
22 changes: 22 additions & 0 deletions Documentation/virt/gunyah/vm-manager.rst
Expand Up @@ -142,3 +142,25 @@ The vcpu type will register with the VM Manager to expect to control
vCPU number `vcpu_id`. It returns a file descriptor allowing interaction with
the vCPU. See the Gunyah vCPU API description sections for interacting with
the Gunyah vCPU file descriptors.

Type: "irqfd"
^^^^^^^^^^^^^

::

struct gh_fn_irqfd_arg {
__u32 fd;
__u32 label;
#define GH_IRQFD_LEVEL (1UL << 0)
#define GH_IRQFD_DEASSIGN (1UL << 1)
__u32 flags;
};

Allows setting an eventfd to directly trigger a guest interrupt.
irqfd.fd specifies the file descriptor to use as the eventfd.
irqfd.label corresponds to the doorbell label used in the guest VM's devicetree.
The irqfd is removed using the GH_IRQFD_DEASSIGN flag and specifying at least
the irqfd.label.

GH_IRQFD_LEVEL configures the corresponding doorbell to behave like a level
triggered interrupt.
10 changes: 10 additions & 0 deletions drivers/virt/gunyah/Kconfig
Expand Up @@ -49,5 +49,15 @@ config GUNYAH_VCPU
can schedule the guest VM's vCPUs instead of using Gunyah's scheduler.
VMMs can also handle stage 2 faults of the vCPUs.

Say Y/M here if unsure and you want to support Gunyah VMMs.

config GUNYAH_IRQFD
tristate "Gunyah irqfd interface"
depends on GUNYAH_RESOURCE_MANAGER
depends on GUNYAH_VM_MANAGER
help
Enable kernel support for creating irqfds which can raise an interrupt
on Gunyah virtual machine.

Say Y/M here if unsure and you want to support Gunyah VMMs.
endif
1 change: 1 addition & 0 deletions drivers/virt/gunyah/Makefile
Expand Up @@ -6,3 +6,4 @@ gunyah_rsc_mgr-$(CONFIG_GUNYAH_VM_MANAGER) += vm_mgr.o vm_mgr_mm.o
obj-$(CONFIG_GUNYAH_RESOURCE_MANAGER) += gunyah_rsc_mgr.o

obj-$(CONFIG_GUNYAH_VCPU) += gunyah_vcpu.o
obj-$(CONFIG_GUNYAH_IRQFD) += gunyah_irqfd.o
180 changes: 180 additions & 0 deletions drivers/virt/gunyah/gunyah_irqfd.c
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/

#include <linux/eventfd.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/gunyah.h>
#include <linux/gunyah_vm_mgr.h>
#include <linux/kref.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/printk.h>

#include <uapi/linux/gunyah.h>

struct gunyah_irqfd {
struct gunyah_resource *ghrsc;
struct gunyah_vm_resource_ticket ticket;
struct gunyah_vm_function *f;

struct kref kref;
bool level;

struct eventfd_ctx *ctx;
wait_queue_entry_t wait;
poll_table pt;
struct fd fd;
struct work_struct shutdown_work;
};

static void gh_irqfd_cleanup(struct kref *kref)
{
struct gunyah_irqfd *irqfd = container_of(kref, struct gunyah_irqfd, kref);

kfree(irqfd);
}


static void irqfd_shutdown(struct work_struct *work)
{
struct gunyah_irqfd *irqfd = container_of(work, struct gunyah_irqfd, shutdown_work);
u64 isr;

if (irqfd->ctx) {
eventfd_ctx_remove_wait_queue(irqfd->ctx, &irqfd->wait, &isr);
eventfd_ctx_put(irqfd->ctx);
fdput(irqfd->fd);
irqfd->ctx = NULL;
irqfd->fd.file = NULL;
}

kref_put(&irqfd->kref, gh_irqfd_cleanup);
}

static int irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode, int sync, void *key)
{
struct gunyah_irqfd *irqfd = container_of(wait, struct gunyah_irqfd, wait);
__poll_t flags = key_to_poll(key);
u64 enable_mask = GH_DBL_NONBLOCK;
u64 old_flags;
int ret = 0;

if (flags & EPOLLIN) {
if (irqfd->ghrsc) {
ret = gh_hypercall_dbl_send(irqfd->ghrsc->capid, enable_mask, &old_flags);
if (ret)
pr_err("Failed to assert irq %d\n", irqfd->f->fn.irqfd.label);
}
}

return 0;
}

static void irqfd_ptable_queue_proc(struct file *file, wait_queue_head_t *wqh, poll_table *pt)
{
struct gunyah_irqfd *irq_ctx = container_of(pt, struct gunyah_irqfd, pt);

add_wait_queue(wqh, &irq_ctx->wait);
}

static int gunyah_irqfd_populate(struct gunyah_vm_resource_ticket *ticket,
struct gunyah_resource *ghrsc)
{
struct gunyah_irqfd *irqfd = container_of(ticket, struct gunyah_irqfd, ticket);
u64 enable_mask = GH_DBL_NONBLOCK;
u64 ack_mask = ~0;
int ret = 0;

irqfd->ghrsc = ghrsc;
if (irqfd->level) {
ret = gh_hypercall_dbl_set_mask(irqfd->ghrsc->capid, enable_mask, ack_mask);
if (ret)
pr_warn("irq %d couldn't be set as level triggered. Might cause IRQ storm if asserted\n",
irqfd->f->fn.irqfd.label);
}
kref_get(&irqfd->kref);

return 0;
}

static void gunyah_irqfd_unpopulate(struct gunyah_vm_resource_ticket *ticket,
struct gunyah_resource *ghrsc)
{
struct gunyah_irqfd *irqfd = container_of(ticket, struct gunyah_irqfd, ticket);

queue_work(system_wq, &irqfd->shutdown_work);
irqfd->ghrsc = NULL;
kref_put(&irqfd->kref, gh_irqfd_cleanup);
}

static long gunyah_irqfd_bind(struct gunyah_vm_function *f)
{
__poll_t events;
struct gunyah_irqfd *irqfd;
long r;

irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);
if (!irqfd)
return -ENOMEM;

irqfd->f = f;
f->data = irqfd;

irqfd->fd = fdget(f->fn.irqfd.fd);
if (!irqfd->fd.file) {
r = -EBADF;
goto err_free;
}

irqfd->ctx = eventfd_ctx_fileget(irqfd->fd.file);
if (IS_ERR(irqfd->ctx)) {
r = PTR_ERR(irqfd->ctx);
goto err_fdput;
}

if (f->fn.irqfd.flags & GH_IRQFD_LEVEL)
irqfd->level = true;

irqfd->ticket.resource_type = GUNYAH_RESOURCE_TYPE_BELL_TX;
irqfd->ticket.label = f->fn.irqfd.label;
irqfd->ticket.owner = THIS_MODULE;
irqfd->ticket.populate = gunyah_irqfd_populate;
irqfd->ticket.unpopulate = gunyah_irqfd_unpopulate;

r = ghvm_add_resource_ticket(f->ghvm, &irqfd->ticket);
if (r)
goto err_ctx;

init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
INIT_WORK(&irqfd->shutdown_work, irqfd_shutdown);
init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);
kref_init(&irqfd->kref);

events = vfs_poll(irqfd->fd.file, &irqfd->pt);
if (events & EPOLLIN)
pr_warn("Premature injection of interrupt\n");

return 0;
err_ctx:
eventfd_ctx_put(irqfd->ctx);
err_fdput:
fdput(irqfd->fd);
err_free:
kfree(irqfd);
return r;
}

static void gunyah_irqfd_release(struct gunyah_vm_function *f)
{
struct gunyah_irqfd *irqfd = f->data;

/* unpopulate will trigger clean up of the eventfd */
ghvm_remove_resource_ticket(irqfd->f->ghvm, &irqfd->ticket);
}

DECLARE_GUNYAH_VM_FUNCTION_INIT(irqfd, gunyah_irqfd_bind, gunyah_irqfd_release);
MODULE_DESCRIPTION("Gunyah irqfds");
MODULE_LICENSE("GPL");
5 changes: 5 additions & 0 deletions include/linux/gunyah.h
Expand Up @@ -32,6 +32,11 @@ struct gunyah_resource {
u32 rm_label;
};

/**
* Gunyah Doorbells
*/
#define GH_DBL_NONBLOCK BIT(32)

/**
* Gunyah Message Queues
*/
Expand Down
11 changes: 10 additions & 1 deletion include/uapi/linux/gunyah.h
Expand Up @@ -57,10 +57,19 @@ struct gh_fn_vcpu_arg {
__u32 vcpu_id;
};

struct gh_fn_irqfd_arg {
__u32 fd;
__u32 label;
#define GH_IRQFD_LEVEL (1UL << 0)
#define GH_IRQFD_DEASSIGN (1UL << 1)
__u32 flags;
};

struct gh_vm_function {
char name[GUNYAH_FUNCTION_NAME_SIZE];
union {
struct gh_device_vcpu_arg vcpu;
struct gh_fn_vcpu_arg vcpu;
struct gh_fn_irqfd_arg irqfd;
char data[GUNYAH_FUNCTION_MAX_ARG_SIZE];
};
};
Expand Down

0 comments on commit 158304c

Please sign in to comment.