From ab8f810343cb4afc9332945a1e2e517029c9088b Mon Sep 17 00:00:00 2001 From: Prakruthi Deepak Heragu Date: Wed, 18 Dec 2019 10:59:57 -0800 Subject: [PATCH 1/6] soc: qcom: Add Guest VM PIL loader Add loader to load Guest VM images, based on the input from device tree. Based on the name of the guest vm as mentioned in the device tree, this loader loads all the images belonging to the guest vm into the memory specified in the devicetree file. Once the loading is complete, it makes necessary calls to the resource manager driver to start the guest VM. Change-Id: I8b276236dab5e5efa0d08bcd4a7fe2d1c7f7b4e6 Acked-by: Tom Kuo Signed-off-by: Prakruthi Deepak Heragu Signed-off-by: Elliot Berman --- drivers/soc/qcom/Kconfig | 9 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/guestvm_loader.c | 197 ++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 drivers/soc/qcom/guestvm_loader.c diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 27044fee2aab..03064ff8a0a9 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -706,4 +706,13 @@ config QCOM_SMCINVOKE communication between QTI Secure Execution Environment (QSEE) and high level operating system. It exposes APIs for both userspace and kernel clients. + +config QCOM_GUESTVM + tristate "Enable Guest VM to be loaded by PIL" + help + This driver invokes Peripheral Image Loader to load images of + any guest Virtual Machine (VM). It also communicates with the + Resource Manager driver to start the boot of VMs once it has + successfully loaded the VM images in the designated memory. + endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 9e2c9bc5c59e..bbe723efa0cf 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -66,3 +66,4 @@ obj-$(CONFIG_QCOM_WATCHDOG) += qcom_watchdog.o obj-$(CONFIG_MSM_SPCOM) += spcom.o obj-$(CONFIG_QCOM_FSA4480_I2C) += fsa4480-i2c.o obj-$(CONFIG_QCOM_EUD) += eud.o +obj-$(CONFIG_QCOM_GUESTVM) += guestvm_loader.o diff --git a/drivers/soc/qcom/guestvm_loader.c b/drivers/soc/qcom/guestvm_loader.c new file mode 100644 index 000000000000..2a09fcc30cba --- /dev/null +++ b/drivers/soc/qcom/guestvm_loader.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MAX_LEN 256 + +const static struct { + enum hh_vm_names val; + const char *str; +} conversion[] = { + {HH_PRIMARY_VM, "pvm"}, + {HH_TRUSTED_VM, "trustedvm"}, +}; + +static struct kobj_type guestvm_kobj_type = { + .sysfs_ops = &kobj_sysfs_ops, +}; + +struct guestvm_loader_private { + struct work_struct vm_loader_work; + struct kobject vm_loader_kobj; + struct device *dev; + char vm_name[MAX_LEN]; + void *vm_loaded; + int vmid; +}; + +static inline enum hh_vm_names get_hh_vm_name(const char *str) +{ + int vmid; + + for (vmid = 0; vmid < ARRAY_SIZE(conversion); ++vmid) { + if (!strcmp(str, conversion[vmid].str)) + return conversion[vmid].val; + } + return HH_VM_MAX; +} + +static void guestvm_loader_rm_notifier(struct work_struct *vm_loader_work) +{ + struct guestvm_loader_private *priv; + int ret = 0; + + priv = container_of(vm_loader_work, struct guestvm_loader_private, + vm_loader_work); + priv->vmid = hh_rm_vm_alloc_vmid(get_hh_vm_name(priv->vm_name)); + if (priv->vmid == HH_VM_MAX) { + dev_err(priv->dev, "Couldn't get vmid.\n"); + return; + } + ret = hh_rm_vm_start(priv->vmid); + if (ret) + dev_err(priv->dev, "VM start has failed with %d.\n", ret); +} + +static ssize_t guestvm_load_start(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + struct guestvm_loader_private *priv; + int ret = 0; + bool boot = false; + + ret = kstrtobool(buf, &boot); + if (ret) + return -EINVAL; + + priv = container_of(kobj, struct guestvm_loader_private, + vm_loader_kobj); + if (ret != 1 || priv->vm_loaded) { + dev_err(priv->dev, "invalid arguments for guestvm_loader.\n"); + return -EINVAL; + } + + if (boot) { + priv->vm_loaded = subsystem_get(priv->vm_name); + if (IS_ERR(priv->vm_loaded)) { + ret = (int)(PTR_ERR(priv->vm_loaded)); + dev_err(priv->dev, + "subsystem_get failed with error %d\n", ret); + priv->vm_loaded = NULL; + return ret; + } + schedule_work(&priv->vm_loader_work); + } + + return ret; +} +static struct kobj_attribute guestvm_loader_attribute = +__ATTR(boot_guestvm, 0220, NULL, guestvm_load_start); + +static struct attribute *attrs[] = { + &guestvm_loader_attribute.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static int guestvm_loader_probe(struct platform_device *pdev) +{ + struct guestvm_loader_private *priv = NULL; + const char *sub_sys; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + priv->dev = &pdev->dev; + + ret = of_property_read_string(pdev->dev.of_node, "image_to_be_loaded", + &sub_sys); + if (ret) + return -EINVAL; + strlcpy(priv->vm_name, sub_sys, sizeof(priv->vm_name)); + + INIT_WORK(&priv->vm_loader_work, guestvm_loader_rm_notifier); + + ret = kobject_init_and_add(&priv->vm_loader_kobj, &guestvm_kobj_type, + kernel_kobj, "load_guestvm"); + if (ret) { + dev_err(&pdev->dev, "sysfs create and add failed\n"); + ret = -ENOMEM; + goto error_return; + } + + ret = sysfs_create_group(&priv->vm_loader_kobj, &attr_group); + if (ret) { + dev_err(&pdev->dev, "sysfs create group failed %d\n", ret); + goto error_return; + } + + return 0; + +error_return: + if (kobject_name(&priv->vm_loader_kobj) != NULL) { + kobject_del(&priv->vm_loader_kobj); + kobject_put(&priv->vm_loader_kobj); + + memset(&priv->vm_loader_kobj, 0, sizeof(priv->vm_loader_kobj)); + } + + return ret; +} + +static int guestvm_loader_remove(struct platform_device *pdev) +{ + struct guestvm_loader_private *priv = platform_get_drvdata(pdev); + + if (priv->vm_loaded) + subsystem_put(priv->vm_loaded); + + if (kobject_name(&priv->vm_loader_kobj) != NULL) { + kobject_del(&priv->vm_loader_kobj); + kobject_put(&priv->vm_loader_kobj); + + memset(&priv->vm_loader_kobj, 0, sizeof(priv->vm_loader_kobj)); + } + + return 0; +} + +static const struct of_device_id guestvm_loader_match_table[] = { + { .compatible = "qcom,guestvm-loader" }, + {}, +}; + +static struct platform_driver guestvm_loader_driver = { + .probe = guestvm_loader_probe, + .remove = guestvm_loader_remove, + .driver = { + .name = "qcom_guestvm_loader", + .of_match_table = guestvm_loader_match_table, + }, +}; + +module_platform_driver(guestvm_loader_driver); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. GuestVM loader"); +MODULE_LICENSE("GPL v2"); From f4271725967dc47fd38c71b8ec9597d838573283 Mon Sep 17 00:00:00 2001 From: Prakruthi Deepak Heragu Date: Thu, 19 Dec 2019 11:19:18 -0800 Subject: [PATCH 2/6] arm64: defconfig: lahaina: Enable GuestVM PIL support Enable PIL to load guestvm image Change-Id: I183b3f077c0d84be959ba0e4b840b59775564ef0 Signed-off-by: Prakruthi Deepak Heragu --- arch/arm64/configs/vendor/lahaina_QGKI.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/vendor/lahaina_QGKI.config b/arch/arm64/configs/vendor/lahaina_QGKI.config index c100ff72d0b9..2df6afbcaa6c 100644 --- a/arch/arm64/configs/vendor/lahaina_QGKI.config +++ b/arch/arm64/configs/vendor/lahaina_QGKI.config @@ -72,6 +72,7 @@ CONFIG_EDAC_QCOM=y CONFIG_EDAC_QCOM_LLCC_PANIC_ON_UE=y CONFIG_QCOM_DEVFREQ_ICC=y CONFIG_QCOM_LLCC_PMU=y +CONFIG_QCOM_GUESTVM=y CONFIG_QTI_TZ_LOG=y CONFIG_QCOM_MEMORY_DUMP_V2=y CONFIG_MSM_REMOTEQDSS=y From 3415eaf8cb8852091d34bbbef663bc26dcf8edb6 Mon Sep 17 00:00:00 2001 From: Elliot Berman Date: Tue, 28 Jan 2020 15:12:22 -0800 Subject: [PATCH 3/6] haven: rm: Add IRQ Management APIs for IRQ lending RM allows VMs to lend interrupts to one another. This patch implements the lender API. Change-Id: I7671a2b346d2425f963631da4b8529fc9b10dc40 Signed-off-by: Elliot Berman --- drivers/virt/haven/hh_rm_drv_private.h | 30 ++++++ drivers/virt/haven/hh_rm_iface.c | 129 +++++++++++++++++++++++++ include/linux/haven/hh_rm_drv.h | 3 + 3 files changed, 162 insertions(+) diff --git a/drivers/virt/haven/hh_rm_drv_private.h b/drivers/virt/haven/hh_rm_drv_private.h index cbfc02f7484a..0a1a39d85ab2 100644 --- a/drivers/virt/haven/hh_rm_drv_private.h +++ b/drivers/virt/haven/hh_rm_drv_private.h @@ -151,6 +151,36 @@ struct hh_vm_irq_accept_resp_payload { s32 virq; } __packed; +/* Call: VM_IRQ_LEND */ +struct hh_vm_irq_lend_req_payload { + hh_vmid_t vmid; + s32 virq; + s32 label; +} __packed; + +struct hh_vm_irq_lend_resp_payload { + hh_virq_handle_t virq; +} __packed; + +/* Call: VM_IRQ_NOTIFY */ +#define HH_VM_IRQ_NOTIFY_FLAGS_LENT BIT(0) +#define HH_VM_IRQ_NOTIFY_FLAGS_RELEASED BIT(1) + +struct hh_vm_irq_notify_req_payload { + hh_virq_handle_t virq; + u16 flags; + u16 reserved0; + u32 reserved1; + struct { + u32 num_vmids; + u64 vmids[0]; + } optional[0]; +} __packed; + +struct hh_vm_irq_notify_resp_payload { + hh_virq_handle_t virq; +} __packed; + /* End Message ID headers */ /* Common function declerations */ diff --git a/drivers/virt/haven/hh_rm_iface.c b/drivers/virt/haven/hh_rm_iface.c index b0b4d6d712bf..23f54ccf86aa 100644 --- a/drivers/virt/haven/hh_rm_iface.c +++ b/drivers/virt/haven/hh_rm_iface.c @@ -185,6 +185,135 @@ out: return ret; } +/** + * hh_rm_vm_irq_lend: Lend an IRQ to another VM + * @vmid: VM to lend the interrupt to + * @virq: Virtual IRQ number to lend + * @label: Label to give to VM so it may know how to associate the interrupt + * @virq_handle: Response handle which RM will accept from the other VM to take + * the lent interrupt + */ +static int hh_rm_vm_irq_lend(hh_vmid_t vmid, int virq, int label, + hh_virq_handle_t *virq_handle) +{ + struct hh_vm_irq_lend_resp_payload *resp_payload; + struct hh_vm_irq_lend_req_payload req_payload; + size_t resp_payload_size; + int ret = 0, reply_err_code; + + req_payload.vmid = vmid; + req_payload.virq = virq; + req_payload.label = label; + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_IRQ_LEND, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s: VM_IRQ_LEND failed with err: %d\n", + __func__, ret); + return ret; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for VM_IRQ_LEND: %u\n", + __func__, resp_payload_size); + ret = -EINVAL; + goto out; + } + + if (virq_handle) + *virq_handle = resp_payload->virq; +out: + kfree(resp_payload); + return ret; +} + +/** + * hh_rm_vm_irq_notify: Notify an IRQ to another VM + * @vmids: VMs to notify the handle about + * @num_vmids: number of VMs to notify the handle about + * @flags: notification reason + * @virq_handle: Response handle which RM will accept from the other VM to take + * the lent interrupt + */ +static int hh_rm_vm_irq_notify(const hh_vmid_t *vmids, unsigned int num_vmids, + u16 flags, hh_virq_handle_t virq_handle) +{ + struct hh_vm_irq_notify_resp_payload *resp_payload; + struct hh_vm_irq_notify_req_payload *req_payload; + size_t resp_payload_size, req_payload_size; + int ret = 0, reply_err_code; + unsigned int i; + + + if (!(flags & HH_VM_IRQ_NOTIFY_FLAGS_LENT) && num_vmids) + return -EINVAL; + + if (num_vmids > U32_MAX) + return -EINVAL; + + req_payload_size = sizeof(*req_payload); + if (flags & HH_VM_IRQ_NOTIFY_FLAGS_LENT) + req_payload_size += sizeof(req_payload->optional) + + (sizeof(req_payload->optional->vmids[0]) * num_vmids); + req_payload = kzalloc(req_payload_size, GFP_KERNEL); + + if (!req_payload) + return -ENOMEM; + + req_payload->virq = virq_handle; + req_payload->flags = flags; + if (flags & HH_VM_IRQ_NOTIFY_FLAGS_LENT) { + req_payload->optional[0].num_vmids = num_vmids; + for (i = 0; i < num_vmids; i++) + req_payload->optional[0].vmids[i] = vmids[i]; + } + + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_IRQ_NOTIFY, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + kfree(req_payload); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + ret = PTR_ERR(resp_payload); + pr_err("%s: VM_IRQ_NOTIFY failed with err: %d\n", + __func__, ret); + return ret; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for VM_IRQ_NOTIFY: %u\n", + __func__, resp_payload_size); + ret = -EINVAL; + } + + kfree(resp_payload); + return ret; +} + +/** + * hh_rm_vm_irq_lend_notify: Lend an IRQ to a VM and notify the VM about it + * @vmid: VM to lend interrupt to + * @virq: Virtual IRQ number to lend + * @label: Label to give to VM so it may know how to associate the interrupt + * + * This function performs interrupt sharing flow for "HLOS" described in + * Resource Manager High Level Design Sec. 3.3.3. + */ +int hh_rm_vm_irq_lend_notify(hh_vmid_t vmid, int virq, int label) +{ + hh_virq_handle_t virq_handle; + int ret; + + ret = hh_rm_vm_irq_lend(vmid, virq, label, &virq_handle); + if (ret) + return ret; + + return hh_rm_vm_irq_notify(&vmid, 1, HH_VM_IRQ_NOTIFY_FLAGS_LENT, + virq_handle); +} + /** * hh_rm_vm_alloc_vmid: Return a vmid associated with the vm loaded into * memory. This call should be called only during diff --git a/include/linux/haven/hh_rm_drv.h b/include/linux/haven/hh_rm_drv.h index 1b74fa2905a1..6dacbb758dd0 100644 --- a/include/linux/haven/hh_rm_drv.h +++ b/include/linux/haven/hh_rm_drv.h @@ -114,7 +114,10 @@ struct hh_rm_notif_vm_console_chars { int hh_rm_register_notifier(struct notifier_block *nb); int hh_rm_unregister_notifier(struct notifier_block *nb); + +/* Client APIs for IRQ management */ int hh_rm_vm_irq_accept(hh_virq_handle_t virq_handle, int virq); +int hh_rm_vm_irq_lend_notify(hh_vmid_t vmid, int virq, int label); /* Client APIs for VM management */ int hh_rm_vm_alloc_vmid(enum hh_vm_names vm_name); From ad9c03a8b039d11daec66a5d155b8ed6133148bc Mon Sep 17 00:00:00 2001 From: Elliot Berman Date: Mon, 27 Jan 2020 16:58:27 -0800 Subject: [PATCH 4/6] haven: rm: Add VM Services - Console APIs Add console APIs to emulate virtual console mechanism between SVM and PVM to get the SVM messages to PVM for debug purpose. Change-Id: I24ec2c427460195c83f5975de70f4ec5c4f2f763 Signed-off-by: Elliot Berman --- drivers/virt/haven/hh_rm_drv_private.h | 21 ++++ drivers/virt/haven/hh_rm_iface.c | 159 +++++++++++++++++++++++++ include/linux/haven/hh_rm_drv.h | 6 + 3 files changed, 186 insertions(+) diff --git a/drivers/virt/haven/hh_rm_drv_private.h b/drivers/virt/haven/hh_rm_drv_private.h index 0a1a39d85ab2..21c3d10e2a45 100644 --- a/drivers/virt/haven/hh_rm_drv_private.h +++ b/drivers/virt/haven/hh_rm_drv_private.h @@ -112,6 +112,27 @@ struct hh_vm_start_resp_payload { u32 response; } __packed; +/* Call: CONSOLE_OPEN, CONSOLE_CLOSE, CONSOLE_FLUSH */ +struct hh_vm_console_common_req_payload { + u32 vmid; + u32 reserved0; +} __packed; + +struct hh_vm_console_common_resp_payload { + u32 response; +} __packed; + +/* Call: CONSOLE_WRITE */ +struct hh_vm_console_write_req_payload { + u32 vmid; + u32 num_bytes; + u8 data[0]; +} __packed; + +struct hh_vm_console_write_resp_payload { + u32 response; +} __packed; + /* Message ID headers */ /* Call: VM_GET_HYP_RESOURCES */ #define HH_RM_RES_TYPE_DB_TX 0 diff --git a/drivers/virt/haven/hh_rm_iface.c b/drivers/virt/haven/hh_rm_iface.c index 23f54ccf86aa..9f72464fa40c 100644 --- a/drivers/virt/haven/hh_rm_iface.c +++ b/drivers/virt/haven/hh_rm_iface.c @@ -5,6 +5,7 @@ */ #include +#include #include #include @@ -407,3 +408,161 @@ int hh_rm_vm_start(int vmid) return resp_payload->response; } EXPORT_SYMBOL(hh_rm_vm_start); + +/** + * hh_rm_console_open: Open a console with a VM + * @vmid: The vmid of the vm to be started. + */ +int hh_rm_console_open(hh_vmid_t vmid) +{ + struct hh_vm_console_common_resp_payload *resp_payload; + struct hh_vm_console_common_req_payload req_payload = {0}; + size_t resp_payload_size; + int err = 0, reply_err_code = 0; + + req_payload.vmid = vmid; + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_OPEN, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + err = PTR_ERR(resp_payload); + pr_err("%s: CONSOLE_OPEN failed with err: %d\n", + __func__, err); + goto out; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for CONSOLE_OPEN: %u\n", + __func__, resp_payload_size); + err = -EINVAL; + } + + err = err ? : resp_payload->response; + kfree(resp_payload); +out: + return err; +} +EXPORT_SYMBOL(hh_rm_console_open); + +/** + * hh_rm_console_close: Close a console with a VM + * @vmid: The vmid of the vm whose console to close. + */ +int hh_rm_console_close(hh_vmid_t vmid) +{ + struct hh_vm_console_common_resp_payload *resp_payload; + struct hh_vm_console_common_req_payload req_payload = {0}; + size_t resp_payload_size; + int err = 0, reply_err_code = 0; + + req_payload.vmid = vmid; + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_CLOSE, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + err = PTR_ERR(resp_payload); + pr_err("%s: CONSOLE_CLOSE failed with err: %d\n", + __func__, err); + goto out; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for CONSOLE_CLOSE: %u\n", + __func__, resp_payload_size); + err = -EINVAL; + } + + err = err ? : resp_payload->response; + kfree(resp_payload); +out: + return err; +} +EXPORT_SYMBOL(hh_rm_console_close); + +/** + * hh_rm_console_write: Write to a VM's console + * @vmid: The vmid of the vm whose console to write to. + * @buf: Buffer to write to the VM's console + * @size: Size of the buffer + */ +int hh_rm_console_write(hh_vmid_t vmid, const char *buf, size_t size) +{ + struct hh_vm_console_write_resp_payload *resp_payload; + struct hh_vm_console_write_req_payload *req_payload; + size_t resp_payload_size; + int err = 0, reply_err_code = 0; + size_t req_payload_size = sizeof(*req_payload) + size; + + if (size < 1 || size > U32_MAX) + return -EINVAL; + + req_payload = kzalloc(req_payload_size, GFP_KERNEL); + + if (!req_payload) + return -ENOMEM; + + req_payload->vmid = vmid; + req_payload->num_bytes = size; + memcpy(req_payload->data, buf, size); + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_WRITE, + req_payload, req_payload_size, + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + err = PTR_ERR(resp_payload); + pr_err("%s: CONSOLE_WRITE failed with err: %d\n", + __func__, err); + goto out; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for CONSOLE_WRITE: %u\n", + __func__, resp_payload_size); + err = -EINVAL; + } + + err = err ? : resp_payload->response; + kfree(resp_payload); +out: + kfree(req_payload); + return err; +} +EXPORT_SYMBOL(hh_rm_console_write); + +/** + * hh_rm_console_flush: Flush a console with a VM + * @vmid: The vmid of the vm whose console to flush + */ +int hh_rm_console_flush(hh_vmid_t vmid) +{ + struct hh_vm_console_common_resp_payload *resp_payload; + struct hh_vm_console_common_req_payload req_payload = {0}; + size_t resp_payload_size; + int err = 0, reply_err_code = 0; + + req_payload.vmid = vmid; + + resp_payload = hh_rm_call(HH_RM_RPC_MSG_ID_CALL_VM_CONSOLE_FLUSH, + &req_payload, sizeof(req_payload), + &resp_payload_size, &reply_err_code); + if (reply_err_code || IS_ERR_OR_NULL(resp_payload)) { + err = PTR_ERR(resp_payload); + pr_err("%s: CONSOLE_FLUSH failed with err: %d\n", + __func__, err); + goto out; + } + + if (resp_payload_size != sizeof(*resp_payload)) { + pr_err("%s: Invalid size received for CONSOLE_FLUSH: %u\n", + __func__, resp_payload_size); + err = -EINVAL; + } + + err = err ? : resp_payload->response; + kfree(resp_payload); +out: + return err; +} +EXPORT_SYMBOL(hh_rm_console_flush); diff --git a/include/linux/haven/hh_rm_drv.h b/include/linux/haven/hh_rm_drv.h index 6dacbb758dd0..0f9368231a92 100644 --- a/include/linux/haven/hh_rm_drv.h +++ b/include/linux/haven/hh_rm_drv.h @@ -125,4 +125,10 @@ int hh_rm_get_vmid(enum hh_vm_names vm_name, hh_vmid_t *vmid); int hh_rm_get_vm_name(hh_vmid_t vmid, enum hh_vm_names *vm_name); int hh_rm_vm_start(int vmid); +/* Client APIs for VM Services */ +int hh_rm_console_open(hh_vmid_t vmid); +int hh_rm_console_close(hh_vmid_t vmid); +int hh_rm_console_write(hh_vmid_t vmid, const char *buf, size_t size); +int hh_rm_console_flush(hh_vmid_t vmid); + #endif From 94e8159e75c4b23d923f7c9f9d5349f9de120eee Mon Sep 17 00:00:00 2001 From: Elliot Berman Date: Tue, 28 Jan 2020 09:52:02 -0800 Subject: [PATCH 5/6] tty: Add Haven RM Console driver Haven Resource Manager API defines a simple console for VM log sharing. This patch adds support for this console with the HVC console framework. It is possible to use this driver as the "primary VM" to listen to other VM console logs or as the "secondary VM" to send logs to the "primary VM". For the latter, CONFIG_HVC_HAVEN_CONSOLE must be set to y. Change-Id: I3606b2d373535d0f347f66e5b729a303a9513fa8 Signed-off-by: Elliot Berman --- drivers/tty/hvc/Kconfig | 19 ++++ drivers/tty/hvc/Makefile | 1 + drivers/tty/hvc/hvc_haven.c | 215 ++++++++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 drivers/tty/hvc/hvc_haven.c diff --git a/drivers/tty/hvc/Kconfig b/drivers/tty/hvc/Kconfig index 95eceeaf540e..289b9c7a1801 100644 --- a/drivers/tty/hvc/Kconfig +++ b/drivers/tty/hvc/Kconfig @@ -87,6 +87,25 @@ config HVC_DCC driver. This console is used through a JTAG only on ARM. If you don't have a JTAG then you probably don't want this option. +config HVC_HAVEN + tristate "Haven tty support" + depends on HH_RM_DRV + select HVC_DRIVER + help + This console exposes communication with other + virtual machines in the Haven hypervisor. This + option may also be used as an early console + to another VM. + +config HVC_HAVEN_CONSOLE + bool "Haven console support" + depends on HVC_HAVEN + help + Select this option to allow Haven tty + as an boot console communicating with + the primary VM. Still need to specify + earlycon and console parameters. + config HVC_RISCV_SBI bool "RISC-V SBI console support" depends on RISCV diff --git a/drivers/tty/hvc/Makefile b/drivers/tty/hvc/Makefile index 98880e357941..876e4d2bae4c 100644 --- a/drivers/tty/hvc/Makefile +++ b/drivers/tty/hvc/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_HVC_OPAL) += hvc_opal.o hvsi_lib.o obj-$(CONFIG_HVC_OLD_HVSI) += hvsi.o obj-$(CONFIG_HVC_RTAS) += hvc_rtas.o obj-$(CONFIG_HVC_DCC) += hvc_dcc.o +obj-$(CONFIG_HVC_HAVEN) += hvc_haven.o obj-$(CONFIG_HVC_DRIVER) += hvc_console.o obj-$(CONFIG_HVC_IRQ) += hvc_irq.o obj-$(CONFIG_HVC_XEN) += hvc_xen.o diff --git a/drivers/tty/hvc/hvc_haven.c b/drivers/tty/hvc/hvc_haven.c new file mode 100644 index 000000000000..2bf640cab02b --- /dev/null +++ b/drivers/tty/hvc/hvc_haven.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "hvc_haven: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "hvc_console.h" + +#define HVC_HH_VTERM_COOKIE 0x474E5948 + +struct hh_hvc_prv { + struct hvc_struct *hvc; + DECLARE_KFIFO(fifo, char, 1024); +}; + +static DEFINE_SPINLOCK(fifo_lock); +static struct hh_hvc_prv hh_hvc_data[2]; + +static inline int hh_vm_name_to_vtermno(enum hh_vm_names vmname) +{ + return vmname + HVC_HH_VTERM_COOKIE; +} + +static inline int vtermno_to_hh_vm_name(int vtermno) +{ + return vtermno - HVC_HH_VTERM_COOKIE; +} + +static int hh_hvc_notify_console_chars(struct notifier_block *this, + unsigned long cmd, void *data) +{ + struct hh_rm_notif_vm_console_chars *msg = data; + enum hh_vm_names vm_name; + int ret; + + if (cmd != HH_RM_NOTIF_VM_CONSOLE_CHARS) + return NOTIFY_DONE; + + ret = hh_rm_get_vm_name(msg->vmid, &vm_name); + if (ret) { + pr_warn_ratelimited("don't know VMID %d\n", vm_name); + return NOTIFY_OK; + } + + ret = kfifo_in_spinlocked(&hh_hvc_data[vm_name].fifo, + msg->bytes, msg->num_bytes, + &fifo_lock); + + if (ret < 0) + pr_warn_ratelimited("dropped %d bytes from VM%d - error %d\n", + msg->num_bytes, vm_name, ret); + else if (ret < msg->num_bytes) + pr_warn_ratelimited("dropped %d bytes from VM%d - full fifo\n", + msg->num_bytes - ret, vm_name); + + hvc_kick(); + return NOTIFY_OK; +} + +static int hh_hvc_get_chars(uint32_t vtermno, char *buf, int count) +{ + int vm_name = vtermno_to_hh_vm_name(vtermno); + + if (vm_name < 0 || vm_name >= HH_VM_MAX) + return -EINVAL; + + return kfifo_out_spinlocked(&hh_hvc_data[vm_name].fifo, + buf, count, &fifo_lock); +} + +static int hh_hvc_put_chars(uint32_t vtermno, const char *buf, int count) +{ + int ret, vm_name = vtermno_to_hh_vm_name(vtermno); + hh_vmid_t vmid; + + if (vm_name < 0 || vm_name >= HH_VM_MAX) + return -EINVAL; + + ret = hh_rm_get_vmid(vm_name, &vmid); + if (ret) + return ret; + + + return hh_rm_console_write(vmid, buf, count); +} + +static int hh_hvc_flush(uint32_t vtermno, bool wait) +{ + int ret, vm_name = vtermno_to_hh_vm_name(vtermno); + hh_vmid_t vmid; + + if (vm_name < 0 || vm_name >= HH_VM_MAX) + return -EINVAL; + + ret = hh_rm_get_vmid(vm_name, &vmid); + if (ret) + return ret; + + return hh_rm_console_flush(vmid); +} + +static int hh_hvc_notify_add(struct hvc_struct *hp, int vm_name) +{ + int ret; + hh_vmid_t vmid; + + ret = hh_rm_get_vmid(vm_name, &vmid); + if (ret) + return ret; + + return hh_rm_console_open(vmid); +} + +static void hh_hvc_notify_del(struct hvc_struct *hp, int vm_name) +{ + int ret; + hh_vmid_t vmid; + + if (vm_name < 0 || vm_name >= HH_VM_MAX) + return; + + ret = hh_rm_get_vmid(vm_name, &vmid); + if (ret) + return; + + ret = hh_rm_console_close(vmid); + + if (ret) + pr_err("Failed close VM%d console - %d\n", vm_name, ret); + + kfifo_reset(&hh_hvc_data[vm_name].fifo); +} + +static struct notifier_block hh_hvc_nb = { + .notifier_call = hh_hvc_notify_console_chars, +}; + +static const struct hv_ops hh_hv_ops = { + .get_chars = hh_hvc_get_chars, + .put_chars = hh_hvc_put_chars, + .flush = hh_hvc_flush, + .notifier_add = hh_hvc_notify_add, + .notifier_del = hh_hvc_notify_del, +}; + +#ifdef CONFIG_HVC_HAVEN_CONSOLE +static int __init hvc_hh_console_init(void) +{ + int ret; + + ret = hvc_instantiate(hh_vm_name_to_vtermno(HH_PRIMARY_VM), 0, + &hh_hv_ops); + + return ret < 0 ? -ENODEV : 0; +} +console_initcall(hvc_hh_console_init); +#endif /* CONFIG_HVC_HAVEN_CONSOLE */ + +static int __init hvc_hh_init(void) +{ + int i, ret = 0; + struct hh_hvc_prv *prv; + + for (i = 0; i < HH_VM_MAX; i++) { + prv = &hh_hvc_data[i]; + INIT_KFIFO(prv->fifo); + prv->hvc = hvc_alloc(hh_vm_name_to_vtermno(i), i, &hh_hv_ops, + 256); + ret = PTR_ERR_OR_ZERO(prv->hvc); + if (ret) + goto bail; + } + + ret = hh_rm_register_notifier(&hh_hvc_nb); + if (ret) + goto bail; + + return 0; +bail: + for (; i >= 0; i--) { + hvc_remove(hh_hvc_data[i].hvc); + hh_hvc_data[i].hvc = NULL; + } + return ret; +} +device_initcall(hvc_hh_init); + +static __exit void hvc_hh_exit(void) +{ + int i; + + for (i = 0; i < HH_VM_MAX; i++) + if (hh_hvc_data[i].hvc) { + hvc_remove(hh_hvc_data[i].hvc); + hh_hvc_data[i].hvc = NULL; + } + + hh_rm_unregister_notifier(&hh_hvc_nb); +} +module_exit(hvc_hh_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Haven Hypervisor Console Driver"); From 82e1a7c6722fab1784e3f62652ead9c1e7685271 Mon Sep 17 00:00:00 2001 From: Elliot Berman Date: Tue, 28 Jan 2020 10:11:31 -0800 Subject: [PATCH 6/6] arm64: defconfig: Enable Haven HVC Driver Enable Haven HVC driver for Lahiana platform. Change-Id: I17fa7ab4f875407e1cd5f14a0ea14a366a8815e5 Signed-off-by: Elliot Berman --- arch/arm64/configs/vendor/lahaina_GKI.config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index c1fac404b3ee..2fb99bd6a11a 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -123,6 +123,8 @@ CONFIG_HAVEN_DRIVERS=y CONFIG_HH_MSGQ=m CONFIG_HH_RM_DRV=m CONFIG_HH_DBL=m +CONFIG_HVC_HAVEN=m +# CONFIG_HVC_HAVEN_CONSOLE is not set CONFIG_CFI_PERMISSIVE=y # CONFIG_SND_SOC_WCD9335 is not set # CONFIG_SLIM_QCOM_CTRL is not set