FROMLIST: virt: gunyah: Add irqfd interface

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.

Bug: 338347082
Link: https://lore.kernel.org/all/20240222-gunyah-v17-32-1e9da6763d38@quicinc.com/
Change-Id: I642ff862db1268cc5195346f79ed96573a9ba492
Acked-by: Alex Elder <elder@linaro.org>
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>
[Elliot: s/fd.file/fd_file(fd)/]
Signed-off-by: Elliot Berman <elliot.berman@oss.qualcomm.com>
This commit is contained in:
Elliot Berman
2024-02-22 15:16:55 -08:00
committed by Treehugger Robot
parent 35eb2b9248
commit bacbc71692
4 changed files with 232 additions and 0 deletions
+9
View File
@@ -27,3 +27,12 @@ config GUNYAH_QCOM_PLATFORM
extra platform-specific support.
Say Y/M here to use Gunyah on Qualcomm platforms.
config GUNYAH_IRQFD
tristate "Gunyah irqfd interface"
depends on GUNYAH
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.
+1
View File
@@ -5,3 +5,4 @@ gunyah_rsc_mgr-y += rsc_mgr.o rsc_mgr_rpc.o vm_mgr.o vm_mgr_mem.o guest_memfd.o
obj-$(CONFIG_GUNYAH) += gunyah.o gunyah_rsc_mgr.o gunyah_vcpu.o
obj-$(CONFIG_GUNYAH_PLATFORM_HOOKS) += gunyah_platform_hooks.o
obj-$(CONFIG_GUNYAH_QCOM_PLATFORM) += gunyah_qcom.o
obj-$(CONFIG_GUNYAH_IRQFD) += gunyah_irqfd.o
+187
View File
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/eventfd.h>
#include <linux/device/driver.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/gunyah.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_instance *f;
bool level;
struct eventfd_ctx *ctx;
wait_queue_entry_t wait;
poll_table pt;
};
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);
int ret = 0;
if (flags & EPOLLIN) {
if (irqfd->ghrsc) {
ret = gunyah_hypercall_bell_send(irqfd->ghrsc->capid, 1,
NULL);
if (ret)
pr_err_ratelimited(
"Failed to inject interrupt %d: %d\n",
irqfd->ticket.label, ret);
} else
pr_err_ratelimited(
"Premature injection of interrupt\n");
}
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 bool gunyah_irqfd_populate(struct gunyah_vm_resource_ticket *ticket,
struct gunyah_resource *ghrsc)
{
struct gunyah_irqfd *irqfd =
container_of(ticket, struct gunyah_irqfd, ticket);
int ret;
if (irqfd->ghrsc) {
pr_warn("irqfd%d already got a Gunyah resource. Check if multiple resources with same label were configured.\n",
irqfd->ticket.label);
return false;
}
irqfd->ghrsc = ghrsc;
if (irqfd->level) {
/* Configure the bell to trigger when bit 0 is asserted (see
* irq_wakeup) and for bell to automatically clear bit 0 once
* received by the VM (ack_mask). need to make sure bit 0 is cleared right away,
* otherwise the line will never be deasserted. Emulating edge
* trigger interrupt does not need to set either mask
* because irq is listed only once per gunyah_hypercall_bell_send
*/
ret = gunyah_hypercall_bell_set_mask(irqfd->ghrsc->capid, 1, 1);
if (ret)
pr_warn("irq %d couldn't be set as level triggered. Might cause IRQ storm if asserted\n",
irqfd->ticket.label);
}
return true;
}
static void gunyah_irqfd_unpopulate(struct gunyah_vm_resource_ticket *ticket,
struct gunyah_resource *ghrsc)
{
}
static long gunyah_irqfd_bind(struct gunyah_vm_function_instance *f)
{
struct gunyah_fn_irqfd_arg *args = f->argp;
struct gunyah_irqfd *irqfd;
__poll_t events;
struct fd fd;
long r;
if (f->arg_size != sizeof(*args))
return -EINVAL;
/* All other flag bits are reserved for future use */
if (args->flags & ~GUNYAH_IRQFD_FLAGS_LEVEL)
return -EINVAL;
irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);
if (!irqfd)
return -ENOMEM;
irqfd->f = f;
f->data = irqfd;
fd = fdget(args->fd);
if (!fd_file(fd)) {
kfree(irqfd);
return -EBADF;
}
irqfd->ctx = eventfd_ctx_fileget(fd_file(fd));
if (IS_ERR(irqfd->ctx)) {
r = PTR_ERR(irqfd->ctx);
goto err_fdput;
}
if (args->flags & GUNYAH_IRQFD_FLAGS_LEVEL)
irqfd->level = true;
init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);
irqfd->ticket.resource_type = GUNYAH_RESOURCE_TYPE_BELL_TX;
irqfd->ticket.label = args->label;
irqfd->ticket.owner = THIS_MODULE;
irqfd->ticket.populate = gunyah_irqfd_populate;
irqfd->ticket.unpopulate = gunyah_irqfd_unpopulate;
r = gunyah_vm_add_resource_ticket(f->ghvm, &irqfd->ticket);
if (r)
goto err_ctx;
events = vfs_poll(fd_file(fd), &irqfd->pt);
if (events & EPOLLIN)
pr_warn("Premature injection of interrupt\n");
fdput(fd);
return 0;
err_ctx:
eventfd_ctx_put(irqfd->ctx);
err_fdput:
fdput(fd);
kfree(irqfd);
return r;
}
static void gunyah_irqfd_unbind(struct gunyah_vm_function_instance *f)
{
struct gunyah_irqfd *irqfd = f->data;
u64 cnt;
eventfd_ctx_remove_wait_queue(irqfd->ctx, &irqfd->wait, &cnt);
gunyah_vm_remove_resource_ticket(irqfd->f->ghvm, &irqfd->ticket);
eventfd_ctx_put(irqfd->ctx);
kfree(irqfd);
}
static bool gunyah_irqfd_compare(const struct gunyah_vm_function_instance *f,
const void *arg, size_t size)
{
const struct gunyah_fn_irqfd_arg *instance = f->argp, *other = arg;
if (sizeof(*other) != size)
return false;
return instance->label == other->label;
}
DECLARE_GUNYAH_VM_FUNCTION_INIT(irqfd, GUNYAH_FN_IRQFD, 2, gunyah_irqfd_bind,
gunyah_irqfd_unbind, gunyah_irqfd_compare);
MODULE_DESCRIPTION("Gunyah irqfd VM Function");
MODULE_LICENSE("GPL");
+35
View File
@@ -63,9 +63,12 @@ struct gunyah_vm_dtb_config {
* @GUNYAH_FN_VCPU: create a vCPU instance to control a vCPU
* &struct gunyah_fn_desc.arg is a pointer to &struct gunyah_fn_vcpu_arg
* Return: file descriptor to manipulate the vcpu.
* @GUNYAH_FN_IRQFD: register eventfd to assert a Gunyah doorbell
* &struct gunyah_fn_desc.arg is a pointer to &struct gunyah_fn_irqfd_arg
*/
enum gunyah_fn_type {
GUNYAH_FN_VCPU = 1,
GUNYAH_FN_IRQFD,
};
#define GUNYAH_FN_MAX_ARG_SIZE 256
@@ -85,6 +88,38 @@ struct gunyah_fn_vcpu_arg {
__u32 id;
};
/**
* enum gunyah_irqfd_flags - flags for use in gunyah_fn_irqfd_arg
* @GUNYAH_IRQFD_FLAGS_LEVEL: make the interrupt operate like a level triggered
* interrupt on guest side. Triggering IRQFD before
* guest handles the interrupt causes interrupt to
* stay asserted.
*/
enum gunyah_irqfd_flags {
GUNYAH_IRQFD_FLAGS_LEVEL = 1UL << 0,
};
/**
* struct gunyah_fn_irqfd_arg - Arguments to create an irqfd function.
*
* Create this function with &GUNYAH_VM_ADD_FUNCTION using type &GUNYAH_FN_IRQFD.
*
* 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.
*
* @fd: an eventfd which when written to will raise a doorbell
* @label: Label of the doorbell created on the guest VM
* @flags: see &enum gunyah_irqfd_flags
* @padding: padding bytes
*/
struct gunyah_fn_irqfd_arg {
__u32 fd;
__u32 label;
__u32 flags;
__u32 padding;
};
/**
* struct gunyah_fn_desc - Arguments to create a VM function
* @type: Type of the function. See &enum gunyah_fn_type.