mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
As part of profiler show operation triggered through sysfs, profiler data is only seen in the kernel log. Add a change to copy the profiler contents in the profiler buffer as well so that the buffer content is visible at the place where sysfs read is issued. Note that there is a limitation in buffer size used in sysfs read and write operations, which is equal to PAGE_SIZE. And so, checks are added to detect buffer overflow while writing profiler data in the buffer. Jira ASSA-934 Change-Id: Ice3eccdfffbd7734ac7a20a0a8ca52ba00af865e Signed-off-by: Bharat Nihalani <bnihalani@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3154262 (cherry picked from commit b619de032dfd58ffc385b08d26353991e2ae3d9b) Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3158713 GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com> Reviewed-by: svcacv <svcacv@nvidia.com> Tested-by: Bitan Biswas <bbiswas@nvidia.com>
855 lines
23 KiB
C
855 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/io.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/memblock.h>
|
|
#include <asm/page.h>
|
|
#include <linux/types.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/suspend.h>
|
|
|
|
#include "tegra_bootloader_debug.h"
|
|
|
|
static phys_addr_t tegra_bl_debug_data_start;
|
|
static phys_addr_t tegra_bl_debug_data_size;
|
|
static phys_addr_t tegra_bl_prof_start;
|
|
static phys_addr_t tegra_bl_prof_size;
|
|
static phys_addr_t tegra_bl_prof_ro_start;
|
|
static phys_addr_t tegra_bl_prof_ro_size;
|
|
static phys_addr_t tegra_bl_bcp_start;
|
|
static phys_addr_t tegra_bl_bcp_size;
|
|
|
|
static const char *module_name = "tegra_bootloader_debug";
|
|
static const char *dir_name = "tegra_bootloader";
|
|
static void __iomem *usc;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static const char *gr_file_mb1 = "gr_mb1";
|
|
static const char *gr_file_mb2 = "gr_mb2";
|
|
static const char *gr_file_cpu_bl = "gr_cpu_bl";
|
|
static const char *boot_cfg = "boot_cfg";
|
|
#endif
|
|
|
|
static char *bl_debug_data = "0@0x0";
|
|
static char *bl_prof_dataptr = "0@0x0";
|
|
static char *bl_prof_ro_ptr = "0@0x0";
|
|
static char *boot_cfg_dataptr = "0@0x0";
|
|
|
|
struct gr_address_value {
|
|
unsigned int gr_address;
|
|
unsigned int gr_value;
|
|
};
|
|
|
|
struct gr_header {
|
|
uint32_t mb1_offset;
|
|
uint32_t mb1_size;
|
|
uint32_t mb2_offset;
|
|
uint32_t mb2_size;
|
|
uint32_t cpu_bl_offset;
|
|
uint32_t cpu_bl_size;
|
|
};
|
|
|
|
enum gr_stage {
|
|
enum_gr_mb1,
|
|
enum_gr_mb2,
|
|
enum_gr_cpu_bl,
|
|
};
|
|
|
|
struct spi_header {
|
|
uint16_t crc;
|
|
uint16_t crc_ack;
|
|
uint16_t frame_len;
|
|
struct {
|
|
uint8_t id: 3;
|
|
uint8_t version: 3;
|
|
uint8_t reserved: 1;
|
|
uint8_t has_ts: 1;
|
|
} version;
|
|
} __packed;
|
|
|
|
struct spi_boot_header {
|
|
struct spi_header header;
|
|
bool rm_respond_evt: 1;
|
|
uint8_t rm_respond_data: 4;
|
|
uint8_t reserved1: 3;
|
|
};
|
|
|
|
struct spi_boot_rx_frame_full {
|
|
struct spi_boot_header header;
|
|
uint8_t data[8200 - sizeof(struct spi_boot_header)];
|
|
};
|
|
|
|
static spinlock_t tegra_bl_lock;
|
|
static struct kobject *boot_profiler_kobj;
|
|
static void *tegra_bl_mapped_prof_start;
|
|
static void *tegra_bl_mapped_prof_ro_start;
|
|
static bool is_privileged_vm;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static const uint32_t gr_mb1 = enum_gr_mb1;
|
|
static const uint32_t gr_mb2 = enum_gr_mb2;
|
|
static const uint32_t gr_cpu_bl = enum_gr_cpu_bl;
|
|
|
|
static int dbg_golden_register_show(struct seq_file *s, void *unused);
|
|
static int dbg_golden_register_open_mb1(struct inode *inode, struct file *file);
|
|
static int dbg_golden_register_open_mb2(struct inode *inode, struct file *file);
|
|
static int dbg_golden_register_open_cpu_bl(struct inode *inode, struct file *file);
|
|
static struct dentry *bl_debug_node;
|
|
static struct dentry *bl_debug_verify_reg_node;
|
|
static struct dentry *bl_debug_boot_cfg;
|
|
static void *tegra_bl_mapped_debug_data_start;
|
|
static int boot_cfg_show(struct seq_file *s, void *unused);
|
|
static int boot_cfg_open(struct inode *inode, struct file *file);
|
|
static void *tegra_bl_mapped_boot_cfg_start;
|
|
|
|
static const struct file_operations debug_gr_fops_mb1 = {
|
|
.open = dbg_golden_register_open_mb1,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
|
|
static const struct file_operations debug_gr_fops_mb2 = {
|
|
.open = dbg_golden_register_open_mb2,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
|
|
static const struct file_operations debug_gr_fops_cpu_bl = {
|
|
.open = dbg_golden_register_open_cpu_bl,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static const struct file_operations boot_cfg_fops = {
|
|
.open = boot_cfg_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
#define MAX_PROFILE_STRLEN 55
|
|
/* This address corresponds to T234
|
|
* TBD - get this information from DT node
|
|
*/
|
|
#define TEGRA_US_COUNTER_REG 0x0C6B0000
|
|
/* Size is currently hardcoded to 64k
|
|
* as QB is using the same size.
|
|
*/
|
|
#define SIZE_OF_FULL_CARVEOUT (64*1024)
|
|
|
|
struct profiler_record {
|
|
char str[MAX_PROFILE_STRLEN + 1];
|
|
uint64_t timestamp;
|
|
} __packed;
|
|
|
|
#define LOG_PROFILER_DATA(print_format, ...) do { \
|
|
if (!buffer_overflow_detected) { \
|
|
len = snprintf(buf + ret, PAGE_SIZE - ret, print_format, ##__VA_ARGS__); \
|
|
if (len >= PAGE_SIZE - ret) { \
|
|
buffer_overflow_detected = true; \
|
|
} else { \
|
|
ret += len; \
|
|
} \
|
|
} \
|
|
pr_info(print_format, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
static ssize_t profiler_show_entries(void *addr, int size, char *buf)
|
|
{
|
|
struct profiler_record *profiler_data;
|
|
int count = 0;
|
|
int i = 0;
|
|
bool prof_data_section_valid = 0;
|
|
ssize_t ret = 0;
|
|
size_t len = 0;
|
|
bool buffer_overflow_detected = false;
|
|
|
|
profiler_data = (struct profiler_record *)addr;
|
|
count = size / sizeof(struct profiler_record);
|
|
i = -1;
|
|
LOG_PROFILER_DATA("\n");
|
|
while (count--) {
|
|
i++;
|
|
if (!profiler_data[i].timestamp) {
|
|
if (prof_data_section_valid) {
|
|
LOG_PROFILER_DATA("\n");
|
|
prof_data_section_valid = false;
|
|
}
|
|
continue;
|
|
}
|
|
if (i > 0 && profiler_data[i - 1].timestamp) {
|
|
LOG_PROFILER_DATA("%-54s\t%16lld \t%16lld\n",
|
|
profiler_data[i].str, profiler_data[i].timestamp,
|
|
profiler_data[i].timestamp - profiler_data[i - 1].timestamp);
|
|
} else {
|
|
LOG_PROFILER_DATA("%-54s\t%16lld\n",
|
|
profiler_data[i].str, profiler_data[i].timestamp);
|
|
}
|
|
prof_data_section_valid = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t profiler_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
ssize_t ret = 0;
|
|
|
|
if (is_privileged_vm) {
|
|
if (!tegra_bl_mapped_prof_ro_start) {
|
|
pr_err("%s\n", "Error mapping RO profiling data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = profiler_show_entries(tegra_bl_mapped_prof_ro_start, tegra_bl_prof_ro_size,
|
|
buf);
|
|
} else {
|
|
if (!tegra_bl_mapped_prof_start) {
|
|
pr_err("%s\n", "Error mapping RW profiling data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = profiler_show_entries(tegra_bl_mapped_prof_start, tegra_bl_prof_size, buf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct kobj_attribute profiler_attribute =
|
|
__ATTR(profiler, 0400, profiler_show, NULL);
|
|
|
|
/**
|
|
* tegra_bl_add_profiler_entry - add a new profiling point
|
|
* @buf: string to add as a profiling marker
|
|
* @len: length of the string
|
|
*
|
|
* Return: 0 on success or error code in case of failure.
|
|
*/
|
|
size_t tegra_bl_add_profiler_entry(const char *buf, size_t len)
|
|
{
|
|
int count = 0;
|
|
int i = 0;
|
|
struct profiler_record *profiler_data;
|
|
|
|
if (len > MAX_PROFILE_STRLEN) {
|
|
pr_err("%s: Failed to add record, invalid length: %ld\n",
|
|
module_name, len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!tegra_bl_mapped_prof_start || !tegra_bl_prof_size) {
|
|
pr_err("Error mapping profiling data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock(&tegra_bl_lock);
|
|
|
|
profiler_data = (struct profiler_record *)tegra_bl_mapped_prof_start;
|
|
count = tegra_bl_prof_size / sizeof(struct profiler_record);
|
|
while (i < count) {
|
|
if (!profiler_data[i].timestamp)
|
|
break;
|
|
i++;
|
|
}
|
|
|
|
if (i == count) {
|
|
pr_err("Error profiling data buffer full\n");
|
|
spin_unlock(&tegra_bl_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
profiler_data[i].timestamp = readl(usc);
|
|
spin_unlock(&tegra_bl_lock);
|
|
|
|
strncpy(profiler_data[i].str, buf, len);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_bl_add_profiler_entry);
|
|
|
|
static ssize_t add_profiler_record_store(struct kobject *kobj,
|
|
struct kobj_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
if (tegra_bl_add_profiler_entry(buf, len))
|
|
pr_err("Error adding profiler entry failed\n");
|
|
|
|
return len;
|
|
}
|
|
|
|
static struct kobj_attribute add_profiler_record_attribute =
|
|
__ATTR_WO(add_profiler_record);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int dbg_golden_register_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct gr_header *golden_reg_header = (struct gr_header *)tegra_bl_mapped_debug_data_start;
|
|
struct gr_address_value *gr_memory_dump;
|
|
unsigned int gr_entries = 0;
|
|
int i;
|
|
|
|
switch (*(int *)(s->private)){
|
|
case enum_gr_mb1:
|
|
gr_entries = golden_reg_header->mb1_size / sizeof(struct gr_address_value);
|
|
gr_memory_dump = (struct gr_address_value *)(golden_reg_header->mb1_offset +
|
|
tegra_bl_mapped_debug_data_start + sizeof(struct gr_header));
|
|
break;
|
|
case enum_gr_mb2:
|
|
gr_entries = golden_reg_header->mb2_size / sizeof(struct gr_address_value);
|
|
gr_memory_dump = (struct gr_address_value *)(golden_reg_header->mb2_offset +
|
|
tegra_bl_mapped_debug_data_start + sizeof(struct gr_header));
|
|
break;
|
|
case enum_gr_cpu_bl:
|
|
gr_entries = golden_reg_header->cpu_bl_size / sizeof(struct gr_address_value);
|
|
gr_memory_dump = (struct gr_address_value *)(golden_reg_header->cpu_bl_offset +
|
|
tegra_bl_mapped_debug_data_start + sizeof(struct gr_header));
|
|
break;
|
|
default:
|
|
seq_printf(s, "Eiiiirror mapping bootloader debug data%x \n",*(int *)(s->private));
|
|
return 0;
|
|
}
|
|
if (!gr_entries || !tegra_bl_mapped_debug_data_start) {
|
|
seq_puts(s, "Error mapping bootloader debug data\n");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < gr_entries; i++) {
|
|
seq_printf(s, "{Address 0x%08x}, {Value 0x%08x}\n",
|
|
gr_memory_dump->gr_address, gr_memory_dump->gr_value);
|
|
|
|
gr_memory_dump++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dbg_golden_register_open_mb1(__attribute((unused))struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, dbg_golden_register_show, (void *)&gr_mb1);
|
|
}
|
|
|
|
static int dbg_golden_register_open_mb2( __attribute((unused))struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, dbg_golden_register_show, (void *)&gr_mb2);
|
|
}
|
|
|
|
static int dbg_golden_register_open_cpu_bl( __attribute((unused))struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, dbg_golden_register_show, (void *)&gr_cpu_bl);
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
/*
|
|
* Notifier for capturing Linux Resume done stage timestamp.
|
|
* Note that this notifier will get called for both suspend and resume cases. But we currently do
|
|
* not have a way to measure Linux Suspend time because we cannot print the profiler stats anyways
|
|
* when the device goes to suspend. And so there is no check added in the notifier function to check
|
|
* if this is for suspend or resume.
|
|
*/
|
|
static int profiler_resume_notifier(struct notifier_block *nb, unsigned long event, void *data)
|
|
{
|
|
tegra_bl_add_profiler_entry("Guest Linux Resume Notifier", 27);
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block profiler_notifier = {
|
|
.notifier_call = profiler_resume_notifier,
|
|
};
|
|
|
|
/*
|
|
* Handler for SC7 Resume to profile the start of driver resume.
|
|
* Since this driver is loaded after all drivers are loaded, it
|
|
* is expected that this driver's resume handler will get called first.
|
|
*/
|
|
static int profiler_resume_noirq_handler(struct device *dev)
|
|
{
|
|
tegra_bl_add_profiler_entry("Profiler Driver Resume Start", 28);
|
|
return 0;
|
|
}
|
|
|
|
static struct dev_pm_ops profiler_pm_ops = {
|
|
.resume_noirq = profiler_resume_noirq_handler,
|
|
};
|
|
|
|
static int __init tegra_bootloader_debuginit(void)
|
|
{
|
|
void __iomem *ptr_bl_prof_ro_carveout = NULL;
|
|
void __iomem *ptr_bl_prof_carveout = NULL;
|
|
int bl_debug_verify_file_entry;
|
|
int ret;
|
|
#ifdef CONFIG_DEBUG_FS
|
|
void __iomem *ptr_bl_debug_data_start = NULL;
|
|
void __iomem *ptr_bl_boot_cfg_start = NULL;
|
|
#endif
|
|
|
|
if (!tegra_bl_prof_start || !tegra_bl_prof_size) {
|
|
pr_err("%s: command line parameter bl_prof_dataptr not initialized\n",
|
|
module_name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Register SC7 resume notifier to profile SC7 Resume done */
|
|
ret = register_pm_notifier(&profiler_notifier);
|
|
if (ret) {
|
|
pr_err("%s: Failed to register resume notifier: %d\n", __func__, ret);
|
|
/* continue even if this fails */
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
if (debugfs_initialized()) {
|
|
bl_debug_node = debugfs_create_dir(dir_name, NULL);
|
|
|
|
if (IS_ERR_OR_NULL(bl_debug_node)) {
|
|
pr_err("%s: failed to create debugfs entries: %ld\n",
|
|
module_name, PTR_ERR(bl_debug_node));
|
|
goto out_err;
|
|
}
|
|
|
|
pr_info("%s: created %s directory\n", module_name, dir_name);
|
|
|
|
bl_debug_verify_reg_node = debugfs_create_file(gr_file_mb1, S_IRUGO,
|
|
bl_debug_node, NULL, &debug_gr_fops_mb1);
|
|
|
|
if (IS_ERR_OR_NULL(bl_debug_verify_reg_node)) {
|
|
pr_err("%s: failed to create debugfs entries: %ld\n",
|
|
module_name, PTR_ERR(bl_debug_verify_reg_node));
|
|
goto out_err;
|
|
}
|
|
|
|
bl_debug_verify_reg_node = debugfs_create_file(gr_file_mb2, S_IRUGO,
|
|
bl_debug_node, NULL, &debug_gr_fops_mb2);
|
|
|
|
if (IS_ERR_OR_NULL(bl_debug_verify_reg_node)) {
|
|
pr_err("%s: failed to create debugfs entries: %ld\n",
|
|
module_name, PTR_ERR(bl_debug_verify_reg_node));
|
|
goto out_err;
|
|
}
|
|
|
|
bl_debug_verify_reg_node = debugfs_create_file(gr_file_cpu_bl, S_IRUGO,
|
|
bl_debug_node, NULL, &debug_gr_fops_cpu_bl);
|
|
|
|
if (IS_ERR_OR_NULL(bl_debug_verify_reg_node)) {
|
|
pr_err("%s: failed to create debugfs entries: %ld\n",
|
|
module_name, PTR_ERR(bl_debug_verify_reg_node));
|
|
goto out_err;
|
|
}
|
|
|
|
if (tegra_bl_debug_data_start && tegra_bl_debug_data_size) {
|
|
tegra_bl_mapped_debug_data_start =
|
|
phys_to_virt(tegra_bl_debug_data_start);
|
|
if (!pfn_valid(__phys_to_pfn(tegra_bl_debug_data_start))) {
|
|
ptr_bl_debug_data_start = ioremap(tegra_bl_debug_data_start,
|
|
tegra_bl_debug_data_size);
|
|
|
|
WARN_ON(!ptr_bl_debug_data_start);
|
|
if (!ptr_bl_debug_data_start) {
|
|
pr_err("%s: Failed to map tegra_bl_debug_data_start%08x\n",
|
|
__func__, (unsigned int)tegra_bl_debug_data_start);
|
|
goto out_err;
|
|
}
|
|
|
|
pr_info("Remapped tegra_bl_debug_data_start(0x%llx)"
|
|
" to address(0x%llx), size(0x%llx)\n",
|
|
(u64)tegra_bl_debug_data_start,
|
|
(__force u64)ptr_bl_debug_data_start,
|
|
(u64)tegra_bl_debug_data_size);
|
|
tegra_bl_mapped_debug_data_start =
|
|
(__force void *)ptr_bl_debug_data_start;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The BCP can be optional, so ignore creating if variables are not set
|
|
*/
|
|
if (tegra_bl_bcp_start && tegra_bl_bcp_size) {
|
|
bl_debug_boot_cfg = debugfs_create_file(boot_cfg, 0444,
|
|
bl_debug_node, NULL, &boot_cfg_fops);
|
|
if (IS_ERR_OR_NULL(bl_debug_boot_cfg)) {
|
|
pr_err("%s: failed to create debugfs entries: %ld\n",
|
|
__func__, PTR_ERR(bl_debug_boot_cfg));
|
|
goto out_err;
|
|
}
|
|
|
|
tegra_bl_mapped_boot_cfg_start =
|
|
phys_to_virt(tegra_bl_bcp_start);
|
|
if (!pfn_valid(__phys_to_pfn(tegra_bl_bcp_start))) {
|
|
ptr_bl_boot_cfg_start = ioremap(tegra_bl_bcp_start,
|
|
tegra_bl_bcp_size);
|
|
|
|
WARN_ON(!ptr_bl_boot_cfg_start);
|
|
if (!ptr_bl_boot_cfg_start) {
|
|
pr_err("%s: Failed to map tegra_bl_prof_start %08x\n",
|
|
__func__,
|
|
(unsigned int)tegra_bl_bcp_start);
|
|
goto out_err;
|
|
}
|
|
tegra_bl_mapped_boot_cfg_start =
|
|
(__force void *)ptr_bl_boot_cfg_start;
|
|
}
|
|
}
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
boot_profiler_kobj = kobject_create_and_add(dir_name, kernel_kobj);
|
|
if (IS_ERR_OR_NULL(boot_profiler_kobj)) {
|
|
pr_err("%s: failed to create sysfs entries: %ld\n",
|
|
module_name, PTR_ERR(boot_profiler_kobj));
|
|
goto out_err;
|
|
}
|
|
|
|
bl_debug_verify_file_entry = sysfs_create_file(boot_profiler_kobj,
|
|
&profiler_attribute.attr);
|
|
if (bl_debug_verify_file_entry) {
|
|
pr_err("%s: failed to create sysfs file : %d\n",
|
|
module_name, bl_debug_verify_file_entry);
|
|
goto out_err;
|
|
}
|
|
|
|
bl_debug_verify_file_entry = sysfs_create_file(boot_profiler_kobj,
|
|
&add_profiler_record_attribute.attr);
|
|
if (bl_debug_verify_file_entry) {
|
|
pr_err("%s: failed to create sysfs file : %d\n",
|
|
module_name, bl_debug_verify_file_entry);
|
|
goto out_err;
|
|
}
|
|
|
|
if (!pfn_valid(__phys_to_pfn(tegra_bl_prof_start))) {
|
|
ptr_bl_prof_carveout = ioremap(tegra_bl_prof_start, tegra_bl_prof_size);
|
|
if (!ptr_bl_prof_carveout) {
|
|
pr_err("%s: failed to map tegra_bl_prof_start\n", __func__);
|
|
goto out_err;
|
|
}
|
|
|
|
pr_info("Remapped tegra_bl_prof_start(0x%llx) "
|
|
"to address 0x%llx, size(0x%llx)\n",
|
|
(u64)tegra_bl_prof_start,
|
|
(__force u64)ptr_bl_prof_carveout,
|
|
(u64)tegra_bl_prof_size);
|
|
|
|
tegra_bl_mapped_prof_start = (__force void *)ptr_bl_prof_carveout;
|
|
}
|
|
|
|
if (tegra_bl_prof_ro_start != 0 && tegra_bl_prof_ro_size != 0 &&
|
|
!pfn_valid(__phys_to_pfn(tegra_bl_prof_ro_start))) {
|
|
ptr_bl_prof_ro_carveout = ioremap(tegra_bl_prof_ro_start, tegra_bl_prof_ro_size);
|
|
if (!ptr_bl_prof_ro_carveout) {
|
|
pr_err("%s: failed to map tegra_bl_prof_ro_start\n", __func__);
|
|
goto out_err;
|
|
}
|
|
|
|
pr_info("Remapped tegra_bl_prof_ro_start(0x%llx) "
|
|
"to address 0x%llx, size(0x%llx)\n",
|
|
(u64)tegra_bl_prof_ro_start,
|
|
(__force u64)ptr_bl_prof_ro_carveout,
|
|
(u64)tegra_bl_prof_ro_size);
|
|
|
|
tegra_bl_mapped_prof_ro_start = (__force void *)ptr_bl_prof_ro_carveout;
|
|
|
|
is_privileged_vm = true;
|
|
} else {
|
|
is_privileged_vm = false;
|
|
}
|
|
|
|
usc = ioremap(TEGRA_US_COUNTER_REG, 4);
|
|
if (!usc) {
|
|
pr_err("Failed to map TEGRA_US_COUNTER_REG\n");
|
|
goto out_err;
|
|
}
|
|
|
|
spin_lock_init(&tegra_bl_lock);
|
|
|
|
return 0;
|
|
|
|
out_err:
|
|
#ifdef CONFIG_DEBUG_FS
|
|
if (!IS_ERR_OR_NULL(bl_debug_node))
|
|
debugfs_remove_recursive(bl_debug_node);
|
|
if (ptr_bl_debug_data_start)
|
|
iounmap(ptr_bl_debug_data_start);
|
|
if (ptr_bl_boot_cfg_start)
|
|
iounmap(ptr_bl_boot_cfg_start);
|
|
#endif
|
|
if (ptr_bl_prof_carveout)
|
|
iounmap(ptr_bl_prof_carveout);
|
|
if (ptr_bl_prof_ro_carveout)
|
|
iounmap(ptr_bl_prof_ro_carveout);
|
|
|
|
if (boot_profiler_kobj) {
|
|
sysfs_remove_file(boot_profiler_kobj,
|
|
&profiler_attribute.attr);
|
|
sysfs_remove_file(boot_profiler_kobj,
|
|
&add_profiler_record_attribute.attr);
|
|
kobject_put(boot_profiler_kobj);
|
|
boot_profiler_kobj = NULL;
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int boot_cfg_show(struct seq_file *s, void *unused)
|
|
{
|
|
uint8_t *data = tegra_bl_mapped_boot_cfg_start;
|
|
struct spi_boot_rx_frame_full *spi_frame =
|
|
tegra_bl_mapped_boot_cfg_start;
|
|
uint32_t i;
|
|
|
|
seq_puts(s, "\n Dumping Boot Configuration Protocol ");
|
|
seq_printf(s, "0x%08x bytes @ 0x%08x\n",
|
|
(unsigned int)tegra_bl_bcp_size,
|
|
(unsigned int)tegra_bl_bcp_start);
|
|
|
|
seq_puts(s, "\n SPI frame header\n");
|
|
seq_printf(s, " CRC : 0x%02x\n",
|
|
spi_frame->header.header.crc);
|
|
seq_printf(s, " CRC ACK : 0x%02x\n",
|
|
spi_frame->header.header.crc_ack);
|
|
seq_printf(s, " Frame len : 0x%02x (%d)\n",
|
|
spi_frame->header.header.frame_len,
|
|
spi_frame->header.header.frame_len);
|
|
seq_printf(s, " Protocol ID : 0x%01x\n",
|
|
spi_frame->header.header.version.id);
|
|
seq_printf(s, " Version : 0x%01x\n",
|
|
spi_frame->header.header.version.version);
|
|
seq_printf(s, " Has ts : 0x%01x\n",
|
|
spi_frame->header.header.version.has_ts);
|
|
seq_printf(s, " Run mode evt: 0x%01x\n",
|
|
spi_frame->header.rm_respond_evt);
|
|
seq_printf(s, " Run mode : 0x%01x\n",
|
|
spi_frame->header.rm_respond_data);
|
|
|
|
for (i = 0; i < tegra_bl_bcp_size; i++) {
|
|
if (i % 12 == 0)
|
|
seq_printf(s, "\n %05d | ", i);
|
|
|
|
seq_printf(s, "0x%02x ", data[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int boot_cfg_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, boot_cfg_show, &inode->i_private);
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
static int __init tegra_bl_args(char *options, phys_addr_t *tegra_bl_arg_size,
|
|
phys_addr_t *tegra_bl_arg_start)
|
|
{
|
|
char *p = options;
|
|
|
|
*tegra_bl_arg_size = memparse(p, &p);
|
|
|
|
if (!p)
|
|
return -EINVAL;
|
|
if (*p != '@')
|
|
return -EINVAL;
|
|
|
|
*tegra_bl_arg_start = memparse(p + 1, &p);
|
|
|
|
if (!(*tegra_bl_arg_size) || !(*tegra_bl_arg_start)) {
|
|
*tegra_bl_arg_size = 0;
|
|
*tegra_bl_arg_start = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (pfn_valid(__phys_to_pfn(*tegra_bl_arg_start))) {
|
|
pr_err("pfn_valid is true for %08llx@%08llx\n",
|
|
(u64)*tegra_bl_arg_size,
|
|
(u64)*tegra_bl_arg_start);
|
|
*tegra_bl_arg_size = 0;
|
|
*tegra_bl_arg_start = 0;
|
|
return -ENXIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_bl_parse_dt_property(struct device_node *np,
|
|
const char *prop_name, u64 *base, u64 *size)
|
|
{
|
|
int ret;
|
|
|
|
ret = of_property_read_u64(np, prop_name, base);
|
|
if (ret) {
|
|
pr_err("Failed to read '%s' property: %d\n", prop_name, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u64_index(np, prop_name, 1, size);
|
|
if (ret) {
|
|
pr_err("Failed to read '%s' size property: %d\n", prop_name, ret);
|
|
return ret;
|
|
}
|
|
|
|
pr_info("Base address of %s: 0x%llx\n", prop_name, *base);
|
|
pr_info("Size of %s: 0x%llx\n", prop_name, *size);
|
|
|
|
return 0; /* Return 0 for success */
|
|
}
|
|
|
|
static int tegra_bl_debug_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
u64 base, size;
|
|
int ret;
|
|
|
|
pr_info("%s: bl_prof_dataptr=%s bl_prof_ro_ptr=%s\n", __func__, bl_prof_dataptr, bl_prof_ro_ptr);
|
|
|
|
if (strncmp(bl_prof_dataptr, "0@0x0", 5) == 0 ||
|
|
strncmp(bl_prof_ro_ptr, "0@0x0", 5) == 0) {
|
|
|
|
if (!np) {
|
|
pr_err("Device tree node not found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = tegra_bl_parse_dt_property(np, "bl_prof_dataptr", &base, &size);
|
|
if (ret)
|
|
return ret;
|
|
tegra_bl_prof_start = base;
|
|
tegra_bl_prof_size = size;
|
|
|
|
base = size = 0;
|
|
ret = tegra_bl_parse_dt_property(np, "bl_prof_ro_ptr", &base, &size);
|
|
if (ret)
|
|
return ret;
|
|
tegra_bl_prof_ro_start = base;
|
|
tegra_bl_prof_ro_size = size;
|
|
}
|
|
|
|
|
|
return 0; /* Return 0 for success */
|
|
}
|
|
|
|
static int tegra_bl_debug_remove(struct platform_device *pdev)
|
|
{
|
|
/* Device removal code goes here */
|
|
pr_info("%s\n", __func__);
|
|
return 0; /* Return 0 for success */
|
|
}
|
|
|
|
static const struct of_device_id tegra_bl_debug_of_match[] = {
|
|
{ .compatible = "tegra_bl_debug" },
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, tegra_bl_debug_of_match);
|
|
|
|
static struct platform_driver tegra_bl_debug_driver = {
|
|
.probe = tegra_bl_debug_probe,
|
|
.remove = tegra_bl_debug_remove,
|
|
.driver = {
|
|
.name = "tegra_bl_debug",
|
|
.pm = &profiler_pm_ops,
|
|
.of_match_table = tegra_bl_debug_of_match,
|
|
},
|
|
};
|
|
|
|
static int __init tegra_bl_debuginit_module_init(void)
|
|
{
|
|
int err = 0;
|
|
|
|
err = platform_driver_register(&tegra_bl_debug_driver);
|
|
if (err < 0) {
|
|
pr_err("%s: Failed to register platform driver: %d\n", __func__, err);
|
|
return err;
|
|
}
|
|
|
|
err = tegra_bl_args(bl_prof_dataptr,
|
|
&tegra_bl_prof_size,
|
|
&tegra_bl_prof_start);
|
|
|
|
if (err != 0)
|
|
return err;
|
|
|
|
err = tegra_bl_args(bl_prof_ro_ptr,
|
|
&tegra_bl_prof_ro_size,
|
|
&tegra_bl_prof_ro_start);
|
|
|
|
if (err != 0)
|
|
return err;
|
|
|
|
err = tegra_bl_args(bl_debug_data,
|
|
&tegra_bl_debug_data_size,
|
|
&tegra_bl_debug_data_start);
|
|
|
|
if (err != 0)
|
|
return err;
|
|
|
|
err = tegra_bl_args(boot_cfg_dataptr,
|
|
&tegra_bl_bcp_size,
|
|
&tegra_bl_bcp_start);
|
|
|
|
if (err != 0)
|
|
return err;
|
|
|
|
return tegra_bootloader_debuginit();
|
|
}
|
|
|
|
static void __exit tegra_bl_debuginit_module_exit(void)
|
|
{
|
|
#ifdef CONFIG_DEBUG_FS
|
|
if (!IS_ERR_OR_NULL(bl_debug_node))
|
|
debugfs_remove_recursive(bl_debug_node);
|
|
|
|
if (tegra_bl_mapped_debug_data_start)
|
|
iounmap((void __iomem *)tegra_bl_mapped_debug_data_start);
|
|
|
|
if (tegra_bl_mapped_boot_cfg_start)
|
|
iounmap((void __iomem *)tegra_bl_mapped_boot_cfg_start);
|
|
#endif
|
|
if (tegra_bl_mapped_prof_ro_start)
|
|
iounmap((void __iomem *)tegra_bl_mapped_prof_ro_start);
|
|
|
|
if (tegra_bl_mapped_prof_start)
|
|
iounmap((void __iomem *)tegra_bl_mapped_prof_start);
|
|
|
|
if (boot_profiler_kobj) {
|
|
sysfs_remove_file(boot_profiler_kobj,
|
|
&profiler_attribute.attr);
|
|
sysfs_remove_file(boot_profiler_kobj,
|
|
&add_profiler_record_attribute.attr);
|
|
kobject_put(boot_profiler_kobj);
|
|
boot_profiler_kobj = NULL;
|
|
}
|
|
|
|
if (usc)
|
|
iounmap(usc);
|
|
|
|
platform_driver_unregister(&tegra_bl_debug_driver);
|
|
unregister_pm_notifier(&profiler_notifier);
|
|
}
|
|
|
|
module_param(bl_debug_data, charp, 0400);
|
|
module_param(bl_prof_dataptr, charp, 0400);
|
|
module_param(bl_prof_ro_ptr, charp, 0400);
|
|
module_param(boot_cfg_dataptr, charp, 0400);
|
|
|
|
module_init(tegra_bl_debuginit_module_init);
|
|
module_exit(tegra_bl_debuginit_module_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("Driver to enumerate bootloader's debug data");
|
|
MODULE_AUTHOR("Mohit Dhingra <mdhingra@nvidia.com>");
|