mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
The driver remove function is a function pointer and therefore, it does not make sense to define the function as an 'inline'. Update the coccinelle script and drivers to remove the inline statement. Bug 4749580 Change-Id: Ia03691b75c4edffe609f27468b911a92a5ddbd68 Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3233980 Tested-by: mobile promotions <svcmobile_promotions@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
902 lines
24 KiB
C
902 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#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
|
|
|
|
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,
|
|
};
|
|
|
|
/*
|
|
* Read the Tegra Microsecond Timer register address.
|
|
* In particular, we need to read address of reg USEC_CNTR_USECCVR_0.
|
|
*/
|
|
static int read_usec_timer_reg_base(u32 *usec_timer_reg_phy_addr)
|
|
{
|
|
struct device_node *node;
|
|
int ret;
|
|
|
|
/* Find the device node by compatible string */
|
|
node = of_find_compatible_node(NULL, NULL, "nvidia,tegra_bl_debug");
|
|
if (!node) {
|
|
pr_err("Could not find device node for tegra_bl_debug\n");
|
|
ret = -ENXIO;
|
|
goto exit_on_err;
|
|
}
|
|
|
|
/* Read the usec_timer_reg_base property */
|
|
ret = of_property_read_u32(node, "usec_timer_reg_base", usec_timer_reg_phy_addr);
|
|
if (ret) {
|
|
pr_err("Failed to read usec_timer_reg_base property\n");
|
|
goto exit_on_err;
|
|
}
|
|
|
|
pr_debug("usec_timer_reg_base: 0x%x\n", *usec_timer_reg_phy_addr);
|
|
|
|
/* Clean-up */
|
|
of_node_put(node);
|
|
|
|
exit_on_err:
|
|
return ret;
|
|
|
|
}
|
|
|
|
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;
|
|
u32 usec_timer_reg_phy_addr;
|
|
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;
|
|
}
|
|
|
|
ret = read_usec_timer_reg_base(&usec_timer_reg_phy_addr);
|
|
if (ret) {
|
|
pr_err("Failed to read Microsecond Timer base address ret %d\n", ret);
|
|
goto out_err;
|
|
}
|
|
|
|
usc = ioremap(usec_timer_reg_phy_addr, 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);
|
|
|
|
#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
|
|
static void tegra_bl_debug_remove_wrapper(struct platform_device *pdev)
|
|
{
|
|
tegra_bl_debug_remove(pdev);
|
|
}
|
|
#else
|
|
static int tegra_bl_debug_remove_wrapper(struct platform_device *pdev)
|
|
{
|
|
return tegra_bl_debug_remove(pdev);
|
|
}
|
|
#endif
|
|
|
|
static struct platform_driver tegra_bl_debug_driver = {
|
|
.probe = tegra_bl_debug_probe,
|
|
.remove = tegra_bl_debug_remove_wrapper,
|
|
.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>");
|