Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 18175354 authored by Elliot Berman's avatar Elliot Berman
Browse files

haven: rm: Add haven vIRQ lending library



Add library for client drivers to lend interrupts to each other.

Change-Id: I4c60e0443d178e83c0bb4ec824915e4bdadb9c39
Signed-off-by: default avatarElliot Berman <eberman@codeaurora.org>
parent 479830ad
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+304 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
 *
 */

#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/haven/hh_irq_lend.h>
#include <linux/haven/hh_rm_drv.h>
#include <linux/spinlock.h>

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");
+31 −0
Original line number Diff line number Diff line
/* 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 <linux/types.h>

#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