Files
linux-nv-oot/drivers/virt/tegra/hvc_sysfs.c
Manish Bhardwaj 1fabab5472 hvc_sysfs: supporess hvc trace buffer error log
hvc trace buffers logs are being used for collecting
eventlib by hypervisor. Since this is not needed in
prod binary so suppressing the hvc buffer missing error.

Bug 5150377

Change-Id: I567cb2cd3e8b5e7e10a24a57e09221c6014264fc
Signed-off-by: Manish Bhardwaj <mbhardwaj@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3320466
GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Sumeet Gupta <sumeetg@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
Reviewed-by: svcacv <svcacv@nvidia.com>
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
Reviewed-by: Ashwin Joshi <asjoshi@nvidia.com>
2025-07-24 10:19:16 +00:00

466 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#include <nvidia/conftest.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <soc/tegra/fuse.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/mm.h>
#include <linux/bug.h>
#include <linux/io.h>
#include <linux/overflow.h>
#include <soc/tegra/virt/syscalls.h>
#include <soc/tegra/virt/hv-ivc.h>
#define TEGRA_HV_ERR(...) pr_err("hvc_sysfs: " __VA_ARGS__)
#define TEGRA_HV_INFO(...) pr_info("hvc_sysfs: " __VA_ARGS__)
/*
* This file implements a hypervisor control driver that can be accessed
* from user-space via the sysfs interface. Currently, supported use case are
* retrieval of the HV trace log when it is available and mapping nvlog buffers
* to user space.
*/
#define MAX_NAME_SIZE 50
struct nvlog_shmem_info {
struct bin_attribute attr;
struct bin_attribute region_size_attr;
struct bin_attribute buf_size_attr;
struct bin_attribute buf_count_attr;
struct kobject *kobj;
char node_name[MAX_NAME_SIZE];
uint64_t ipa;
uint64_t region_size;
uint64_t buf_size;
uint64_t buf_count;
};
static struct nvlog_shmem_info nvlog_shmem_attrs[MAX_NVLOG_PRODUCERS];
struct hyp_shared_memory_info {
char node_name[MAX_NAME_SIZE];
struct bin_attribute attr;
uint64_t ipa;
unsigned long size;
};
#define HYP_SHM_ID_NUM (2 * NGUESTS_MAX + 1U) // trace buffers + PCT.
static struct hyp_shared_memory_info hyp_shared_memory_attrs[HYP_SHM_ID_NUM];
static uint64_t EventType = 1;
/* Map the HV trace buffer to the calling user process */
static int nvlog_buffer_mmap(struct file *fp, struct kobject *ko,
#if defined(NV_BIN_ATTRIBUTE_STRUCT_MMAP_HAS_CONST_BIN_ATTRIBUTE_ARG) /* Linux v6.13 */
const struct bin_attribute *attr,
#else
struct bin_attribute *attr,
#endif
struct vm_area_struct *vma)
{
struct nvlog_shmem_info *info = container_of(attr, struct nvlog_shmem_info, attr);
unsigned long result;
if ((info->ipa == 0) || (info->region_size == 0))
return -EINVAL;
if (check_sub_overflow(vma->vm_end, vma->vm_start, &result)) {
pr_err("%s: operation got overflown.\n", __func__);
return -EINVAL;
}
if (result != attr->size)
return -EINVAL;
return remap_pfn_range(
vma, vma->vm_start,
info->ipa >> PAGE_SHIFT,
info->region_size, vma->vm_page_prot);
}
static ssize_t nvlog_region_size_read(struct file *fp, struct kobject *ko,
struct bin_attribute *attr, char *buf, loff_t pos, size_t size)
{
struct nvlog_shmem_info *info = container_of(attr, struct nvlog_shmem_info, region_size_attr);
snprintf(buf, size, "%llu\n", info->region_size);
return size;
}
static ssize_t nvlog_buffer_size_read(struct file *fp, struct kobject *ko,
struct bin_attribute *attr, char *buf, loff_t pos, size_t size)
{
struct nvlog_shmem_info *info = container_of(attr, struct nvlog_shmem_info, buf_size_attr);
snprintf(buf, size, "%llu\n", info->buf_size);
return size;
}
static ssize_t nvlog_buffer_count_read(struct file *fp, struct kobject *ko,
struct bin_attribute *attr, char *buf, loff_t pos, size_t size)
{
struct nvlog_shmem_info *info = container_of(attr, struct nvlog_shmem_info, buf_count_attr);
snprintf(buf, size, "%llu\n", info->buf_count);
return size;
}
static int nvlog_create_sysfs_nodes(struct nvlog_shmem_info *info)
{
int ret = 0;
if (info == NULL)
return -EINVAL;
sysfs_bin_attr_init((struct bin_attribute *)&info->attr);
info->attr.attr.name = info->node_name;
info->attr.attr.mode = 0600;
info->attr.size = info->region_size;
info->attr.mmap = nvlog_buffer_mmap;
ret = sysfs_create_bin_file(info->kobj, &info->attr);
if (ret != 0)
goto fail;
sysfs_bin_attr_init((struct bin_attribute *)&info->region_size_attr);
info->region_size_attr.attr.name = "region_size";
info->region_size_attr.attr.mode = 0444;
info->region_size_attr.size = 16u;
info->region_size_attr.read = nvlog_region_size_read;
ret = sysfs_create_bin_file(info->kobj, &info->region_size_attr);
if (ret != 0)
goto del_attr;
sysfs_bin_attr_init((struct bin_attribute *)&info->buf_size_attr);
info->buf_size_attr.attr.name = "buf_size";
info->buf_size_attr.attr.mode = 0444;
info->buf_size_attr.size = 16u;
info->buf_size_attr.read = nvlog_buffer_size_read;
ret = sysfs_create_bin_file(info->kobj, &info->buf_size_attr);
if (ret != 0)
goto del_region_size;
sysfs_bin_attr_init((struct bin_attribute *)&info->buf_count_attr);
info->buf_count_attr.attr.name = "buf_count";
info->buf_count_attr.attr.mode = 0444;
info->buf_count_attr.size = 16u;
info->buf_count_attr.read = nvlog_buffer_count_read;
ret = sysfs_create_bin_file(info->kobj, &info->buf_count_attr);
if (ret != 0)
goto del_buf_size;
return 0;
del_buf_size:
sysfs_remove_bin_file(info->kobj, &info->buf_size_attr);
del_region_size:
sysfs_remove_bin_file(info->kobj, &info->region_size_attr);
del_attr:
sysfs_remove_bin_file(info->kobj, &info->attr);
fail:
return ret;
}
static int hyp_nvlog_buffer_init(void)
{
struct vm_info_region *info;
struct kobject *parent;
uint64_t ipa, cntr;
char dir_name[MAX_NAME_SIZE];
int ret;
parent = kobject_create_and_add("nvlog", NULL);
if (parent == NULL) {
TEGRA_HV_INFO("failed to add kobject\n");
return -ENOMEM;
}
if (hyp_read_vm_info(&ipa) != 0) {
ret = -EINVAL;
goto fail;
}
info = (__force struct vm_info_region *)ioremap(ipa, sizeof(*info));
if (info == NULL) {
ret = -EFAULT;
goto fail;
}
for (cntr = 0; cntr < MAX_NVLOG_PRODUCERS; cntr++) {
if (info->nvlog_producers[cntr].ipa != 0U
&& info->nvlog_producers[cntr].region_size != 0U) {
nvlog_shmem_attrs[cntr].ipa = info->nvlog_producers[cntr].ipa;
nvlog_shmem_attrs[cntr].region_size = info->nvlog_producers[cntr].region_size;
nvlog_shmem_attrs[cntr].buf_size = info->nvlog_producers[cntr].buf_size;
nvlog_shmem_attrs[cntr].buf_count = info->nvlog_producers[cntr].buf_count;
ret = snprintf(nvlog_shmem_attrs[cntr].node_name, MAX_NAME_SIZE, "%s",
info->nvlog_producers[cntr].name);
if (ret > 0U) {
nvlog_shmem_attrs[cntr].node_name[ret] = '\0';
} else {
TEGRA_HV_INFO("snprintf failure - %s\n",
info->nvlog_producers[cntr].name);
iounmap((void __iomem *)info);
ret = -EFAULT;
goto fail;
}
strscpy(dir_name, nvlog_shmem_attrs[cntr].node_name, MAX_NAME_SIZE);
strlcat(dir_name, "_dir", MAX_NAME_SIZE);
nvlog_shmem_attrs[cntr].kobj = kobject_create_and_add(dir_name, parent);
if (nvlog_shmem_attrs[cntr].kobj == NULL) {
TEGRA_HV_INFO("failed to add kobject\n");
ret = -ENOMEM;
goto fail;
}
ret = nvlog_create_sysfs_nodes(&nvlog_shmem_attrs[cntr]);
if (ret != 0)
goto fail;
}
}
iounmap((void __iomem *)info);
return 0;
fail:
for (cntr = 0; cntr < MAX_NVLOG_PRODUCERS; cntr++) {
if (nvlog_shmem_attrs[cntr].kobj)
kobject_del(nvlog_shmem_attrs[cntr].kobj);
}
if (parent)
kobject_del(parent);
return ret;
}
/* Map the HV trace buffer to the calling user process */
static int hvc_sysfs_mmap(struct file *fp, struct kobject *ko,
#if defined(NV_BIN_ATTRIBUTE_STRUCT_MMAP_HAS_CONST_BIN_ATTRIBUTE_ARG) /* Linux v6.13 */
const struct bin_attribute *attr,
#else
struct bin_attribute *attr,
#endif
struct vm_area_struct *vma)
{
struct hyp_shared_memory_info *hyp_shm_info =
container_of(attr, struct hyp_shared_memory_info, attr);
unsigned long result;
if ((hyp_shm_info->ipa == 0) || (hyp_shm_info->size == 0))
return -EINVAL;
if (check_sub_overflow(vma->vm_end, vma->vm_start, &result)) {
pr_err("%s: operation got overflown.\n", __func__);
return -EINVAL;
}
if (result != attr->size)
return -EINVAL;
return remap_pfn_range(
vma,
vma->vm_start,
hyp_shm_info->ipa >> PAGE_SHIFT,
hyp_shm_info->size,
vma->vm_page_prot);
}
/* Discover availability and placement of the HV trace buffer */
static int hvc_create_sysfs(
struct kobject *kobj,
struct hyp_shared_memory_info *hyp_shm_info)
{
sysfs_bin_attr_init((struct bin_attribute *)&hyp_shm_info->attr);
hyp_shm_info->attr.attr.name = hyp_shm_info->node_name;
hyp_shm_info->attr.attr.mode = 0400;
hyp_shm_info->attr.mmap = hvc_sysfs_mmap;
hyp_shm_info->attr.size = (size_t)hyp_shm_info->size;
if ((hyp_shm_info->ipa == 0) || (hyp_shm_info->size == 0))
return -EINVAL;
return sysfs_create_bin_file(kobj, &hyp_shm_info->attr);
}
static ssize_t log_mask_read(struct file *fp, struct kobject *ko,
struct bin_attribute *attr, char *buf, loff_t pos, size_t size)
{
uint64_t value = 0;
/* Kernel checks for validity of buf, no need to check here. */
value = EventType;
if (size == sizeof(uint64_t)) {
hyp_trace_get_mask(&value);
memcpy(buf, &value, sizeof(value));
}
return size;
}
static ssize_t log_mask_write(struct file *fp, struct kobject *ko,
struct bin_attribute *attr, char *buf, loff_t pos, size_t size)
{
uint64_t type, value;
memcpy(&type, buf, sizeof(uint64_t));
memcpy(&value, buf + sizeof(uint64_t), sizeof(uint64_t));
if (size == 2 * sizeof(uint64_t))
hyp_trace_set_mask(type, value);
else
EventType = *buf;
return size;
}
static struct bin_attribute log_mask_attr;
static int create_log_mask_node(struct kobject *kobj)
{
if (kobj == NULL)
return -EINVAL;
sysfs_bin_attr_init((struct bin_attribute *)&log_mask_attr);
log_mask_attr.attr.name = "log_mask";
log_mask_attr.attr.mode = 0600;
log_mask_attr.read = log_mask_read;
log_mask_attr.write = log_mask_write;
log_mask_attr.size = sizeof(uint64_t) * 2U;
return sysfs_create_bin_file(kobj, &log_mask_attr);
}
static int hyp_trace_buffer_init(void)
{
struct kobject *kobj;
int ret;
uint64_t ipa;
struct hyp_info_page *info;
uint64_t log_mask = 1;
struct trace_buf *buffs;
uint16_t i, count;
kobj = kobject_create_and_add("hvc", NULL);
if (kobj == NULL) {
TEGRA_HV_INFO("failed to add kobject\n");
return -ENOMEM;
}
if (hyp_read_hyp_info(&ipa) != 0) {
ret = -EINVAL;
goto fail;
}
info = (__force struct hyp_info_page *)ioremap(ipa, sizeof(*info));
if (info == NULL) {
ret = -EFAULT;
goto fail;
}
buffs = info->trace_buffs;
count = ARRAY_SIZE(info->trace_buffs);
/* 2 bytes for number, 1 for "_" and 1 for null character.*/
if (MAX_NAME_SIZE < (ARRAY_SIZE(buffs[i].name) + 4U)) {
TEGRA_HV_INFO("hyp_shared_memory_attrs.name size is small\n");
iounmap((void __iomem *)info);
ret = -EFAULT;
goto fail;
}
for (i = 0; i < count; i++) {
if (buffs[i].ipa != 0U && buffs[i].size != 0U) {
hyp_shared_memory_attrs[i].ipa = buffs[i].ipa;
hyp_shared_memory_attrs[i].size = (size_t)buffs[i].size;
ret = snprintf(hyp_shared_memory_attrs[i].node_name, MAX_NAME_SIZE,
"%s", buffs[i].name);
if (ret > 0U) {
hyp_shared_memory_attrs[i].node_name[ret] = '\0';
} else {
TEGRA_HV_INFO("snprintf failure - %s\n", buffs[i].name);
iounmap((void __iomem *)info);
ret = -EFAULT;
goto fail;
}
ret = hvc_create_sysfs(kobj, &hyp_shared_memory_attrs[i]);
if (ret == 0)
TEGRA_HV_INFO(" %s trace is available\n", buffs[i].name);
else
TEGRA_HV_INFO(" %s trace is unavailable\n", buffs[i].name);
}
}
if (hyp_trace_get_mask(&log_mask) == 0) {
ret = create_log_mask_node(kobj);
if (ret == 0)
TEGRA_HV_INFO("access to trace event mask is available\n");
}
hyp_shared_memory_attrs[i].ipa = info->pct_ipa;
hyp_shared_memory_attrs[i].size = (size_t)info->pct_size;
ret = snprintf(hyp_shared_memory_attrs[i].node_name, MAX_NAME_SIZE, "pct");
if (ret > 0U) {
hyp_shared_memory_attrs[i].node_name[ret] = '\0';
} else {
TEGRA_HV_INFO("snprintf failure - pct\n");
iounmap((void __iomem *)info);
ret = -EFAULT;
goto fail;
}
ret = hvc_create_sysfs(kobj, &hyp_shared_memory_attrs[i]);
if (ret == 0)
TEGRA_HV_INFO("pct is available\n");
else
TEGRA_HV_INFO("pct is unavailable\n");
iounmap((void __iomem *)info);
return 0;
fail:
kobject_del(kobj);
return ret;
}
/* Set up all relevant hypervisor control nodes */
static int __init hvc_sysfs_register(void)
{
int ret;
if (is_tegra_hypervisor_mode() == false) {
TEGRA_HV_INFO("hypervisor is not present\n");
/* retunring success in case of native kernel otherwise
* systemd-modules-load service will failed.
*/
return 0;
}
ret = hyp_trace_buffer_init();
if (ret == 0)
TEGRA_HV_INFO("Hypervisor trace buffer initialized successfully\n");
ret = hyp_nvlog_buffer_init();
if (ret != 0)
TEGRA_HV_ERR("Error: Hypervisor nvlog buffer init failed\n");
else
TEGRA_HV_INFO("Hypervisor nvlog buffer initialized successfully\n");
return 0;
}
late_initcall(hvc_sysfs_register);
MODULE_LICENSE("GPL");