forked from torvalds/linux
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
88981a6
commit 158304c
Showing
6 changed files
with
228 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters