Files
2025-08-12 22:16:57 +02:00

285 lines
7.7 KiB
C
Executable File

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "gunyah: " fmt
#include <linux/arm-smccc.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/of.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/gunyah.h>
#include <linux/gunyah/gh_errno.h>
#include "hcall_ctrl.h"
#define QC_HYP_SMCCC_CALL_UID \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
ARM_SMCCC_OWNER_VENDOR_HYP, 0x3f01)
#define QC_HYP_SMCCC_REVISION \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
ARM_SMCCC_OWNER_VENDOR_HYP, 0xff03)
#define QC_HYP_UID0 0x19bd54bd
#define QC_HYP_UID1 0x0b37571b
#define QC_HYP_UID2 0x946f609b
#define QC_HYP_UID3 0x54539de6
#define QC_HYP1_UID0 0xbd54bd19
#define QC_HYP1_UID1 0x1b57370b
#define QC_HYP1_UID2 0x9b606f94
#define QC_HYP1_UID3 0xe69d5354
/* Use */
#undef GH_API_INFO_API_VERSION
#undef GH_API_INFO_BIG_ENDIAN
#undef GH_API_INFO_IS_64BIT
#undef GH_API_INFO_VARIANT
#define GH_API_INFO_API_VERSION(x) (((x) >> 0) & 0x3fff)
#define GH_API_INFO_BIG_ENDIAN(x) (((x) >> 14) & 1)
#define GH_API_INFO_IS_64BIT(x) (((x) >> 15) & 1)
#define GH_API_INFO_VARIANT(x) (((x) >> 56) & 0xff)
#define GH_IDENTIFY_PARTITION_CSPACE(x) (((x) >> 0) & 1)
#define GH_IDENTIFY_DOORBELL(x) (((x) >> 1) & 1)
#define GH_IDENTIFY_MSGQUEUE(x) (((x) >> 2) & 1)
#define GH_IDENTIFY_VIC(x) (((x) >> 3) & 1)
#define GH_IDENTIFY_VPM(x) (((x) >> 4) & 1)
#define GH_IDENTIFY_VCPU(x) (((x) >> 5) & 1)
#define GH_IDENTIFY_MEMEXTENT(x) (((x) >> 6) & 1)
#define GH_IDENTIFY_TRACE_CTRL(x) (((x) >> 7) & 1)
#define GH_IDENTIFY_ROOTVM_CHANNEL(x) (((x) >> 16) & 1)
#define GH_IDENTIFY_SCHEDULER(x) (((x) >> 28) & 0xf)
static bool qc_hyp_calls;
static struct gh_hcall_hyp_identify_resp gunyah_api;
static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buffer)
{
return scnprintf(buffer, PAGE_SIZE, "gunyah\n");
}
static struct kobj_attribute type_attr = __ATTR_RO(type);
static ssize_t api_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buffer)
{
return scnprintf(buffer, PAGE_SIZE, "%d\n",
(int)GH_API_INFO_API_VERSION(gunyah_api.api_info));
}
static struct kobj_attribute api_attr = __ATTR_RO(api);
static ssize_t variant_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buffer)
{
return scnprintf(buffer, PAGE_SIZE, "%d\n",
(int)GH_API_INFO_VARIANT(gunyah_api.api_info));
}
static struct kobj_attribute variant_attr = __ATTR_RO(variant);
static struct attribute *version_attrs[] = { &api_attr.attr,
&variant_attr.attr, NULL };
static const struct attribute_group version_group = {
.name = "version",
.attrs = version_attrs,
};
static int __init gh_sysfs_register(void)
{
int ret;
ret = sysfs_create_file(hypervisor_kobj, &type_attr.attr);
if (ret)
return ret;
return sysfs_create_group(hypervisor_kobj, &version_group);
}
static void __exit gh_sysfs_unregister(void)
{
sysfs_remove_file(hypervisor_kobj, &type_attr.attr);
sysfs_remove_group(hypervisor_kobj, &version_group);
}
#if defined(CONFIG_DEBUG_FS)
#define QC_HYP_SMCCC_UART_DISABLE \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
ARM_SMCCC_OWNER_VENDOR_HYP, 0x0)
#define QC_HYP_SMCCC_UART_ENABLE \
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \
ARM_SMCCC_OWNER_VENDOR_HYP, 0xc)
#define ENABLE 1
#define DISABLE 0
static struct dentry *gh_dbgfs_dir;
static int hyp_uart_enable;
static void gh_control_hyp_uart(int val)
{
switch (val) {
case ENABLE:
if (!hyp_uart_enable) {
hyp_uart_enable = val;
pr_info("Gunyah: enabling HYP UART\n");
arm_smccc_1_1_smc(QC_HYP_SMCCC_UART_ENABLE, NULL);
} else {
pr_info("Gunyah: HYP UART already enabled\n");
}
break;
case DISABLE:
if (hyp_uart_enable) {
hyp_uart_enable = val;
pr_info("Gunyah: disabling HYP UART\n");
arm_smccc_1_1_smc(QC_HYP_SMCCC_UART_DISABLE, NULL);
} else {
pr_info("Gunyah: HYP UART already disabled\n");
}
break;
default:
pr_info("Gunyah: supported values disable(0)/enable(1)\n");
}
}
static int gh_dbgfs_trace_class_set(void *data, u64 val)
{
return gh_remap_error(gh_hcall_trace_update_class_flags(val, 0, NULL));
}
static int gh_dbgfs_trace_class_clear(void *data, u64 val)
{
return gh_remap_error(gh_hcall_trace_update_class_flags(0, val, NULL));
}
static int gh_dbgfs_trace_class_get(void *data, u64 *val)
{
*val = 0;
return gh_remap_error(gh_hcall_trace_update_class_flags(0, 0, val));
}
static int gh_dbgfs_hyp_uart_set(void *data, u64 val)
{
gh_control_hyp_uart(val);
return 0;
}
static int gh_dbgfs_hyp_uart_get(void *data, u64 *val)
{
*val = hyp_uart_enable;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(gh_dbgfs_trace_class_set_fops,
gh_dbgfs_trace_class_get,
gh_dbgfs_trace_class_set,
"0x%llx\n");
DEFINE_DEBUGFS_ATTRIBUTE(gh_dbgfs_trace_class_clear_fops,
gh_dbgfs_trace_class_get,
gh_dbgfs_trace_class_clear,
"0x%llx\n");
DEFINE_DEBUGFS_ATTRIBUTE(gh_dbgfs_hyp_uart_ctrl_fops,
gh_dbgfs_hyp_uart_get,
gh_dbgfs_hyp_uart_set,
"0x%llx\n");
static int __init gh_dbgfs_register(void)
{
struct dentry *dentry;
gh_dbgfs_dir = debugfs_create_dir("gunyah", NULL);
if (IS_ERR_OR_NULL(gh_dbgfs_dir))
return PTR_ERR(gh_dbgfs_dir);
if (GH_IDENTIFY_TRACE_CTRL(gunyah_api.flags[0])) {
dentry = debugfs_create_file("trace_set", 0600, gh_dbgfs_dir,
NULL, &gh_dbgfs_trace_class_set_fops);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
dentry = debugfs_create_file("trace_clear", 0600, gh_dbgfs_dir,
NULL, &gh_dbgfs_trace_class_clear_fops);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
dentry = debugfs_create_file("hyp_uart_ctrl", 0600, gh_dbgfs_dir,
NULL, &gh_dbgfs_hyp_uart_ctrl_fops);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
}
return 0;
}
static void __exit gh_dbgfs_unregister(void)
{
debugfs_remove_recursive(gh_dbgfs_dir);
}
#else /* !defined (CONFIG_DEBUG_FS) */
static inline int gh_dbgfs_register(void) { return 0; }
static inline int gh_dbgfs_unregister(void) { return 0; }
#endif
static int __init gh_ctrl_init(void)
{
int ret;
struct device_node *hyp;
struct arm_smccc_res res;
hyp = of_find_node_by_path("/hypervisor");
if (!hyp || (!of_device_is_compatible(hyp, "qcom,gunyah-hypervisor") &&
!of_device_is_compatible(hyp, "qcom,haven-hypervisor"))) {
pr_err("gunyah-hypervisor or haven-hypervisor node not present\n");
return 0;
}
(void)gh_hcall_hyp_identify(&gunyah_api);
if (GH_API_INFO_API_VERSION(gunyah_api.api_info) != 1) {
pr_err("unknown version\n");
return 0;
}
/* Check for ARM SMCCC VENDOR_HYP service calls by UID. */
arm_smccc_1_1_smc(QC_HYP_SMCCC_CALL_UID, &res);
if ((res.a0 == QC_HYP_UID0) && (res.a1 == QC_HYP_UID1) &&
(res.a2 == QC_HYP_UID2) && (res.a3 == QC_HYP_UID3))
qc_hyp_calls = true;
else if ((res.a0 == QC_HYP1_UID0) && (res.a1 == QC_HYP1_UID1) &&
(res.a2 == QC_HYP1_UID2) && (res.a3 == QC_HYP1_UID3))
qc_hyp_calls = true;
if (qc_hyp_calls) {
ret = gh_sysfs_register();
if (ret)
return ret;
ret = gh_dbgfs_register();
if (ret)
pr_warn("failed to register dbgfs: %d\n", ret);
} else {
pr_info("Gunyah: no QC HYP interface detected\n");
}
return 0;
}
module_init(gh_ctrl_init);
static void __exit gh_ctrl_exit(void)
{
gh_sysfs_unregister();
gh_dbgfs_unregister();
}
module_exit(gh_ctrl_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Gunyah Hypervisor Control Driver");