From 18175354e0c3d5a1c054bb056da511cdcf7215d3 Mon Sep 17 00:00:00 2001 From: Elliot Berman Date: Tue, 10 Mar 2020 11:52:22 -0700 Subject: [PATCH] haven: rm: Add haven vIRQ lending library Add library for client drivers to lend interrupts to each other. Change-Id: I4c60e0443d178e83c0bb4ec824915e4bdadb9c39 Signed-off-by: Elliot Berman --- drivers/virt/haven/Kconfig | 9 + drivers/virt/haven/Makefile | 1 + drivers/virt/haven/hh_irq_lend.c | 304 ++++++++++++++++++++++++++++++ include/linux/haven/hh_irq_lend.h | 31 +++ 4 files changed, 345 insertions(+) create mode 100644 drivers/virt/haven/hh_irq_lend.c create mode 100644 include/linux/haven/hh_irq_lend.h diff --git a/drivers/virt/haven/Kconfig b/drivers/virt/haven/Kconfig index 29370773efa9..ce00fd916a17 100644 --- a/drivers/virt/haven/Kconfig +++ b/drivers/virt/haven/Kconfig @@ -47,4 +47,13 @@ config HH_DBL these doorbells by calling send and/or a receive primitives exposed by driver and trigger an interrupt to each other and exchange the data. +config HH_IRQ_LEND + tristate "Haven IRQ Lending Framework" + depends on HH_RM_DRV + help + Haven Resource Manager permits interrupts to be shared between + virtual machines. This config enables a framework which + supports sharing these interrupts. The follows RM recommended + protocol. + endif diff --git a/drivers/virt/haven/Makefile b/drivers/virt/haven/Makefile index a1f9b15a438f..4aea5ea755e0 100644 --- a/drivers/virt/haven/Makefile +++ b/drivers/virt/haven/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_HH_MSGQ) += hh_msgq.o obj-$(CONFIG_HH_RM_DRV) += hh_rm_drv.o hh_rm_drv-y := hh_rm_core.o hh_rm_iface.o obj-$(CONFIG_HH_DBL) += hh_dbl.o +obj-$(CONFIG_HH_IRQ_LEND) += hh_irq_lend.o diff --git a/drivers/virt/haven/hh_irq_lend.c b/drivers/virt/haven/hh_irq_lend.c new file mode 100644 index 000000000000..312c6b3a2488 --- /dev/null +++ b/drivers/virt/haven/hh_irq_lend.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include +#include + +struct hh_irq_entry { + hh_vmid_t vmid; + hh_irq_handle_fn handle; + void *data; + + enum { + /* start state */ + HH_IRQ_STATE_NONE, + /* NONE -> WAIT_RELEASE by hh_irq_lend */ + HH_IRQ_STATE_WAIT_RELEASE, + /* NONE -> WAIT_LEND by hh_irq_wait_lend */ + HH_IRQ_STATE_WAIT_LEND, + /* WAIT_RELEASE -> RELEASED by notifier */ + /* RELEASED -> NONE by hh_irq_reclaim */ + HH_IRQ_STATE_RELEASED, + /* WAIT_LEND -> LENT by notifier */ + /* LENT -> NONE by hh_irq_release */ + HH_IRQ_STATE_LENT, + } state; + hh_virq_handle_t virq_handle; +}; + +static struct hh_irq_entry hh_irq_entries[HH_IRQ_LABEL_MAX]; +static DEFINE_SPINLOCK(hh_irq_lend_lock); + +static int hh_irq_released_nb_handler(struct notifier_block *this, + unsigned long cmd, void *data) +{ + unsigned long flags; + enum hh_irq_label label; + struct hh_irq_entry *entry; + struct hh_rm_notif_vm_irq_released_payload *released = data; + + if (cmd != HH_RM_NOTIF_VM_IRQ_RELEASED) + return NOTIFY_DONE; + + spin_lock_irqsave(&hh_irq_lend_lock, flags); + for (label = 0; label < HH_IRQ_LABEL_MAX; label++) { + entry = &hh_irq_entries[label]; + if (entry->state != HH_IRQ_STATE_WAIT_RELEASE) + continue; + + if (released->virq_handle == entry->virq_handle) { + entry->state = HH_IRQ_STATE_RELEASED; + spin_unlock_irqrestore(&hh_irq_lend_lock, + flags); + + entry->handle(entry->data, label); + + return NOTIFY_OK; + } + } + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + + return NOTIFY_DONE; +} + +static struct notifier_block hh_irq_released_nb = { + .notifier_call = hh_irq_released_nb_handler, +}; + +static int hh_irq_lent_nb_handler(struct notifier_block *this, + unsigned long cmd, void *data) +{ + unsigned long flags; + enum hh_irq_label label; + struct hh_irq_entry *entry; + struct hh_rm_notif_vm_irq_lent_payload *lent = data; + + if (cmd != HH_RM_NOTIF_VM_IRQ_LENT) + return NOTIFY_DONE; + + spin_lock_irqsave(&hh_irq_lend_lock, flags); + for (label = 0; label < HH_IRQ_LABEL_MAX; label++) { + entry = &hh_irq_entries[label]; + if (entry->state != HH_IRQ_STATE_WAIT_LEND) + continue; + + if (label == lent->virq_label && + (entry->vmid == HH_VM_MAX || + entry->vmid == lent->owner_vmid)) { + entry->vmid = lent->owner_vmid; + entry->virq_handle = lent->virq_handle; + + entry->state = HH_IRQ_STATE_LENT; + spin_unlock_irqrestore(&hh_irq_lend_lock, + flags); + + entry->handle(entry->data, label); + + return NOTIFY_OK; + } + } + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + + return NOTIFY_DONE; +} + +static struct notifier_block hh_irq_lent_nb = { + .notifier_call = hh_irq_lent_nb_handler, +}; + +/** + * hh_irq_lend: Lend a hardware interrupt to another VM + * @label: vIRQ high-level label + * @name: VM name to send interrupt to + * @hw_irq: Hardware IRQ number to lend + * @on_release: callback to invoke when other VM returns the + * interrupt + * @data: Argument to pass to on_release + */ +int hh_irq_lend(enum hh_irq_label label, enum hh_vm_names name, + int hw_irq, hh_irq_handle_fn on_release, void *data) +{ + int ret; + unsigned long flags; + struct hh_irq_entry *entry; + + if (label >= HH_IRQ_LABEL_MAX || !on_release) + return -EINVAL; + + entry = &hh_irq_entries[label]; + + spin_lock_irqsave(&hh_irq_lend_lock, flags); + if (entry->state != HH_IRQ_STATE_NONE) { + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + return -EINVAL; + } + + ret = hh_rm_get_vmid(name, &entry->vmid); + if (ret) { + entry->state = HH_IRQ_STATE_NONE; + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + return ret; + } + + entry->handle = on_release; + entry->data = data; + entry->state = HH_IRQ_STATE_WAIT_RELEASE; + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + + return hh_rm_vm_irq_lend_notify(entry->vmid, hw_irq, label, + &entry->virq_handle); +} +EXPORT_SYMBOL(hh_irq_lend); + +/** + * hh_irq_reclaim: Reclaim a hardware interrupt after other VM + * has released. + * @label: vIRQ high-level label + * + * This function should be called inside or after on_release() + * callback from hh_irq_lend. + * This function is not thread-safe. Do not race with another hh_irq_reclaim + * with same label + */ +int hh_irq_reclaim(enum hh_irq_label label) +{ + int ret; + struct hh_irq_entry *entry; + + if (label >= HH_IRQ_LABEL_MAX) + return -EINVAL; + + entry = &hh_irq_entries[label]; + + if (entry->state != HH_IRQ_STATE_RELEASED) + return -EINVAL; + + ret = hh_rm_vm_irq_reclaim(entry->virq_handle); + if (!ret) + entry->state = HH_IRQ_STATE_NONE; + return ret; +} +EXPORT_SYMBOL(hh_irq_reclaim); + +/** + * hh_irq_wait_lend: Register to claim a lent interrupt from another + * VM + * @label: vIRQ high-level label + * @name: Lender's VM name. If don't care, then use HH_VM_MAX + * @on_lend: callback to invoke when other VM lends the interrupt + * @data: Argument to pass to on_lend + */ +int hh_irq_wait_for_lend(enum hh_irq_label label, enum hh_vm_names name, + hh_irq_handle_fn on_lend, void *data) +{ + int ret; + unsigned long flags; + struct hh_irq_entry *entry; + + if (label >= HH_IRQ_LABEL_MAX || !on_lend) + return -EINVAL; + + entry = &hh_irq_entries[label]; + + spin_lock_irqsave(&hh_irq_lend_lock, flags); + if (entry->state != HH_IRQ_STATE_NONE) { + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + return -EINVAL; + } + + ret = hh_rm_get_vmid(name, &entry->vmid); + if (ret) { + entry->state = HH_IRQ_STATE_NONE; + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + return ret; + } + + entry->handle = on_lend; + entry->data = data; + entry->state = HH_IRQ_STATE_WAIT_LEND; + spin_unlock_irqrestore(&hh_irq_lend_lock, flags); + + return ret; +} +EXPORT_SYMBOL(hh_irq_wait_for_lend); + +/** + * hh_irq_accept: Register to receive interrupts with a lent vIRQ + * @label: vIRQ high-level label + * @hw_irq: HWIRQ# to associate vIRQ with. If don't care, use -1 + * + * If hw_irq is not -1, then returns 0 on success, <0 otherwise + * If hw_irq is -1, then returns the HWIRQ# that vIRQ was registered + * to or <0 for error. + * This function is not thread-safe w.r.t. IRQ lend state. Do not race with + * with hh_irq_release or another hh_irq_accept with same label. + */ +int hh_irq_accept(enum hh_irq_label label, int hw_irq) +{ + struct hh_irq_entry *entry; + + if (label >= HH_IRQ_LABEL_MAX) + return -EINVAL; + + entry = &hh_irq_entries[label]; + + if (entry->state != HH_IRQ_STATE_LENT) + return -EINVAL; + + return hh_rm_vm_irq_accept(entry->virq_handle, hw_irq); +} +EXPORT_SYMBOL(hh_irq_accept); + +/** + * hh_irq_release: Release a lent interrupt + * @label: vIRQ high-level label + * This function is not thread-safe w.r.t. IRQ lend state. Do not race with + * with hh_irq_accept or another hh_irq_release with same label. + */ +int hh_irq_release(enum hh_irq_label label) +{ + int ret; + struct hh_irq_entry *entry; + + if (label >= HH_IRQ_LABEL_MAX) + return -EINVAL; + + entry = &hh_irq_entries[label]; + + if (entry->state != HH_IRQ_STATE_LENT) + return -EINVAL; + + ret = hh_rm_vm_irq_release_notify(entry->vmid, + entry->virq_handle); + if (!ret) + entry->state = HH_IRQ_STATE_NONE; + return ret; +} +EXPORT_SYMBOL(hh_irq_release); + +static int __init hh_irq_lend_init(void) +{ + int ret; + + ret = hh_rm_register_notifier(&hh_irq_lent_nb); + if (ret) + return ret; + return hh_rm_register_notifier(&hh_irq_released_nb); +} +module_init(hh_irq_lend_init); + +static void hh_irq_lend_exit(void) +{ + hh_rm_unregister_notifier(&hh_irq_lent_nb); + hh_rm_unregister_notifier(&hh_irq_released_nb); +} +module_exit(hh_irq_lend_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Haven IRQ Lending Library"); diff --git a/include/linux/haven/hh_irq_lend.h b/include/linux/haven/hh_irq_lend.h new file mode 100644 index 000000000000..febc6cc703a2 --- /dev/null +++ b/include/linux/haven/hh_irq_lend.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +#ifndef __HH_IRQ_LEND_H +#define __HH_IRQ_LEND_H + +#include + +#include "hh_common.h" +#include "hh_rm_drv.h" + +enum hh_irq_label { + HH_IRQ_LABEL_DUMMY, /* Remove me when you add a label! */ + HH_IRQ_LABEL_MAX +}; + +typedef void (*hh_irq_handle_fn)(void *req, enum hh_irq_label label); + +int hh_irq_lend(enum hh_irq_label label, enum hh_vm_names name, + int hw_irq, hh_irq_handle_fn on_release, void *data); +int hh_irq_reclaim(enum hh_irq_label label); + +int hh_irq_wait_for_lend(enum hh_irq_label label, enum hh_vm_names name, + hh_irq_handle_fn on_lend, void *data); +int hh_irq_accept(enum hh_irq_label label, int hw_irq); +int hh_irq_release(enum hh_irq_label label); + +#endif