Merge "soc: qcom: Add snapshot of cpu vendor hooks driver"

This commit is contained in:
qctecmdr 2022-04-20 19:12:36 -07:00 committed by Gerrit - the friendly Code Review server
commit 686aa245d9
4 changed files with 627 additions and 0 deletions

View File

@ -236,6 +236,15 @@ config QCOM_STATS
various SoC level low power modes statistics and export to debugfs
interface.
config QCOM_LOGBUF_VENDOR_HOOKS
tristate "QTI Logbuf Vendor Hooks Support"
depends on ARCH_QCOM && ANDROID_VENDOR_HOOKS
help
This enables to keep copy of initial log_buf
of minimum 512KB from bootup. It can help in
debugging issues which are manifestation
of failure during initial bootup.
config QCOM_WCNSS_CTRL
tristate "Qualcomm WCNSS control driver"
depends on ARCH_QCOM || COMPILE_TEST
@ -272,4 +281,16 @@ config MSM_BOOT_TIME_MARKER
An instrumentation for boot time measurement.
To create an entry, call "place_marker" function.
At userspace, write marker name to "/sys/kernel/boot_kpi/kpi_values"
config QCOM_CPU_VENDOR_HOOKS
tristate "QTI Vendor Hooks Support"
depends on ARCH_QCOM && ANDROID_VENDOR_HOOKS
help
CPU vendor hooks driver registers with andriod vendor hooks
provided by core kernel to extend kernel functionality.
Currently these features are not supported by upstream kernel and not
having scope to upstream.
If unsure, say N.
endmenu

View File

@ -32,3 +32,5 @@ obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) += kryo-l2-accessors.o
obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o
obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o
obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o
obj-$(CONFIG_QCOM_CPU_VENDOR_HOOKS) += qcom_cpu_vendor_hooks.o
obj-$(CONFIG_QCOM_LOGBUF_VENDOR_HOOKS) += qcom_logbuf_vh.o

View File

@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "VendorHooks: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/sched/debug.h>
#include <linux/io.h>
#include <soc/qcom/watchdog.h>
#include <trace/hooks/debug.h>
#include <trace/hooks/printk.h>
#include <trace/hooks/timer.h>
static DEFINE_PER_CPU(struct pt_regs, regs_before_stop);
static DEFINE_RAW_SPINLOCK(stop_lock);
static void printk_hotplug(void *unused, int *flag)
{
*flag = 1;
}
static void trace_ipi_stop(void *unused, struct pt_regs *regs)
{
unsigned int cpu = smp_processor_id();
unsigned long flags;
per_cpu(regs_before_stop, cpu) = *regs;
raw_spin_lock_irqsave(&stop_lock, flags);
pr_crit("CPU%u: stopping\n", cpu);
show_regs(regs);
raw_spin_unlock_irqrestore(&stop_lock, flags);
}
static void timer_recalc_index(void *unused,
unsigned int lvl, unsigned long *expires)
{
*expires -= 1;
}
#if IS_ENABLED(CONFIG_DEBUG_SPINLOCK) && \
(IS_ENABLED(CONFIG_DEBUG_SPINLOCK_BITE_ON_BUG) || IS_ENABLED(CONFIG_DEBUG_SPINLOCK_PANIC_ON_BUG))
static int entry_spin_bug(struct kretprobe_instance *ri, struct pt_regs *regs)
{
raw_spinlock_t *lock = (raw_spinlock_t *)regs->regs[0];
const char *msg = (const char *)regs->regs[1];
struct task_struct *owner = READ_ONCE(lock->owner);
if (!debug_locks_off())
return 0;
/* Dup of spin_bug in kernel/locking/spinlock_debug.c */
if (owner == SPINLOCK_OWNER_INIT)
owner = NULL;
printk(KERN_EMERG "BUG: spinlock %s on CPU#%d, %s/%d\n",
msg, raw_smp_processor_id(),
current->comm, task_pid_nr(current));
printk(KERN_EMERG " lock: %pS, .magic: %08x, .owner: %s/%d, "
".owner_cpu: %d\n",
lock, READ_ONCE(lock->magic),
owner ? owner->comm : "<none>",
owner ? task_pid_nr(owner) : -1,
READ_ONCE(lock->owner_cpu));
#if IS_ENABLED(CONFIG_DEBUG_SPINLOCK_BITE_ON_BUG)
qcom_wdt_trigger_bite();
#elif IS_ENABLED(CONFIG_DEBUG_SPINLOCK_PANIC_ON_BUG)
BUG();
#else
# error "Neither CONFIG_DEBUG_SPINLOCK_BITE_ON_BUG nor CONFIG_DEBUG_SPINLOCK_PANIC_ON_BUG is enabled yet trying to enable spin_bug hook"
#endif
return 0;
}
struct kretprobe spin_bug_probe = {
.entry_handler = entry_spin_bug,
.maxactive = 1,
.kp.symbol_name = "spin_bug",
};
static void register_spinlock_bug_hook(struct platform_device *pdev)
{
int ret;
ret = register_kretprobe(&spin_bug_probe);
if (ret)
dev_err(&pdev->dev, "Failed to register spin_bug_probe: %x\n", ret);
}
#else
static inline void register_spinlock_bug_hook(struct platform_device *pdev) { }
#endif
#ifdef CONFIG_RANDOMIZE_BASE
#define KASLR_OFFSET_MASK 0x00000000FFFFFFFF
static void __iomem *map_prop_mem(const char *propname)
{
struct device_node *np = of_find_compatible_node(NULL, NULL, propname);
void __iomem *addr;
if (!np) {
pr_err("Unable to find DT property: %s\n", propname);
return NULL;
}
addr = of_iomap(np, 0);
if (!addr)
pr_err("Unable to map memory for DT property: %s\n", propname);
return addr;
}
static void store_kaslr_offset(void)
{
void __iomem *mem = map_prop_mem("qcom,msm-imem-kaslr_offset");
if (!mem)
return;
__raw_writel(0xdead4ead, mem);
__raw_writel((kimage_vaddr - KIMAGE_VADDR) & KASLR_OFFSET_MASK,
mem + 4);
__raw_writel(((kimage_vaddr - KIMAGE_VADDR) >> 32) & KASLR_OFFSET_MASK,
mem + 8);
iounmap(mem);
}
#else
static void store_kaslr_offset(void) {}
#endif /* CONFIG_RANDOMIZE_BASE */
static int cpu_vendor_hooks_driver_probe(struct platform_device *pdev)
{
int ret;
store_kaslr_offset();
ret = register_trace_android_vh_ipi_stop(trace_ipi_stop, NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to register android_vh_ipi_stop hook\n");
return ret;
}
ret = register_trace_android_vh_printk_hotplug(printk_hotplug, NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to android_vh_printk_hotplug hook\n");
unregister_trace_android_vh_ipi_stop(trace_ipi_stop, NULL);
return ret;
}
ret = register_trace_android_vh_timer_calc_index(timer_recalc_index, NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to android_vh_timer_calc_index hook\n");
unregister_trace_android_vh_ipi_stop(trace_ipi_stop, NULL);
unregister_trace_android_vh_printk_hotplug(printk_hotplug, NULL);
return ret;
}
register_spinlock_bug_hook(pdev);
return ret;
}
static int cpu_vendor_hooks_driver_remove(struct platform_device *pdev)
{
/* Reset all initialized global variables and unregister callbacks. */
unregister_trace_android_vh_ipi_stop(trace_ipi_stop, NULL);
unregister_trace_android_vh_printk_hotplug(printk_hotplug, NULL);
unregister_trace_android_vh_timer_calc_index(timer_recalc_index, NULL);
return 0;
}
static const struct of_device_id cpu_vendor_hooks_of_match[] = {
{ .compatible = "qcom,cpu-vendor-hooks" },
{ }
};
MODULE_DEVICE_TABLE(of, cpu_vendor_hooks_of_match);
static struct platform_driver cpu_vendor_hooks_driver = {
.driver = {
.name = "qcom-cpu-vendor-hooks",
.of_match_table = cpu_vendor_hooks_of_match,
},
.probe = cpu_vendor_hooks_driver_probe,
.remove = cpu_vendor_hooks_driver_remove,
};
static int __init qcom_vendor_hook_driver_init(void)
{
return platform_driver_register(&cpu_vendor_hooks_driver);
}
#if IS_MODULE(CONFIG_QCOM_CPU_VENDOR_HOOKS)
module_init(qcom_vendor_hook_driver_init);
#else
pure_initcall(qcom_vendor_hook_driver_init);
#endif
static void __exit qcom_vendor_hook_driver_exit(void)
{
return platform_driver_unregister(&cpu_vendor_hooks_driver);
}
module_exit(qcom_vendor_hook_driver_exit);
MODULE_DESCRIPTION("QCOM CPU Vendor Hooks Driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,390 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "logbuf_vh: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/atomic.h>
#include <linux/slab.h>
#include <soc/qcom/minidump.h>
#include <trace/hooks/logbuf.h>
#include "../../../kernel/printk/printk_ringbuffer.h"
#define BOOT_LOG_SIZE SZ_512K
#define LOG_NEWLINE 2
static char *boot_log_buf;
static unsigned int boot_log_buf_size;
static bool copy_early_boot_log = true;
static unsigned int off;
static size_t print_time(u64 ts, char *buf, size_t buf_sz)
{
unsigned long rem_nsec = do_div(ts, 1000000000);
return scnprintf(buf, buf_sz, "[%5lu.%06lu]",
(unsigned long)ts, rem_nsec / 1000);
}
#ifdef CONFIG_PRINTK_CALLER
#define PREFIX_MAX 48
static size_t print_caller(u32 id, char *buf, size_t buf_sz)
{
char caller[12];
snprintf(caller, sizeof(caller), "%c%u",
id & 0x80000000 ? 'C' : 'T', id & ~0x80000000);
return scnprintf(buf, buf_sz, "[%6s]", caller);
}
#else
#define PREFIX_MAX 32
#define print_caller(id, buf) 0
#endif
static size_t info_print_prefix(const struct printk_info *info, char *buf,
size_t buf_sz)
{
size_t len = 0;
len = print_time(info->ts_nsec, buf + len, buf_sz);
len += print_caller(info->caller_id, buf + len, buf_sz - len);
buf[len++] = ' ';
buf[len] = '\0';
return len;
}
static size_t record_print_text(struct printk_info *pinfo, char *r_text, size_t buf_size)
{
size_t text_len = pinfo->text_len;
char prefix[PREFIX_MAX];
size_t prefix_len;
char *text = r_text;
size_t line_len;
size_t len = 0;
char *next;
prefix_len = info_print_prefix(pinfo, prefix, PREFIX_MAX);
/*
* @text_len: bytes of unprocessed text
* @line_len: bytes of current line _without_ newline
* @text: pointer to beginning of current line
* @len: number of bytes prepared in r->text_buf
*/
for (;;) {
next = memchr(text, '\n', text_len);
if (next)
line_len = next - text;
else
line_len = text_len;
/*
* Truncate the text if there is not enough space to add the
* prefix and a trailing newline and a terminator.
*/
if ((len + prefix_len + text_len + 1 + 1) > buf_size)
break;
memmove(text + prefix_len, text, text_len);
memcpy(text, prefix, prefix_len);
/*
* Increment the prepared length to include the text and
* prefix that were just moved+copied. Also increment for the
* newline at the end of this line. If this is the last line,
* there is no newline, but it will be added immediately below.
*/
len += prefix_len + line_len + 1;
if (text_len == line_len) {
/*
* This is the last line. Add the trailing newline
* removed in vprintk_store().
*/
text[prefix_len + line_len] = '\n';
break;
}
/*
* Advance beyond the added prefix and the related line with
* its newline.
*/
text += prefix_len + line_len + 1;
/*
* The remaining text has only decreased by the line with its
* newline.
*
* Note that @text_len can become zero. It happens when @text
* ended with a newline (either due to truncation or the
* original string ending with "\n\n"). The loop is correctly
* repeated and (if not truncated) an empty line with a prefix
* will be prepared.
*/
text_len -= line_len + 1;
}
/*
* If a buffer was provided, it will be terminated. Space for the
* string terminator is guaranteed to be available. The terminator is
* not counted in the return value.
*/
if (buf_size > 0)
r_text[len] = 0;
return len;
}
static void register_log_minidump(struct printk_ringbuffer *prb)
{
struct prb_desc_ring descring = prb->desc_ring;
struct prb_data_ring textdata_ring = prb->text_data_ring;
struct prb_desc *descaddr = descring.descs;
struct printk_info *p_infos = descring.infos;
struct md_region md_entry;
int ret;
strscpy(md_entry.name, "KLOGBUF", sizeof(md_entry.name));
md_entry.virt_addr = (uintptr_t)textdata_ring.data;
md_entry.phys_addr = virt_to_phys(textdata_ring.data);
md_entry.size = _DATA_SIZE(textdata_ring.size_bits);
ret = msm_minidump_add_region(&md_entry);
if (ret < 0)
pr_err("Failed to add log_text entry in minidump table\n");
strscpy(md_entry.name, "LOG_DESC", sizeof(md_entry.name));
md_entry.virt_addr = (uintptr_t)descaddr;
md_entry.phys_addr = virt_to_phys(descaddr);
md_entry.size = sizeof(struct prb_desc) * _DESCS_COUNT(descring.count_bits);
ret = msm_minidump_add_region(&md_entry);
if (ret < 0)
pr_err("Failed to add log_desc entry in minidump table\n");
strscpy(md_entry.name, "LOG_INFO", sizeof(md_entry.name));
md_entry.virt_addr = (uintptr_t)p_infos;
md_entry.phys_addr = virt_to_phys(p_infos);
md_entry.size = sizeof(struct printk_info) * _DESCS_COUNT(descring.count_bits);
ret = msm_minidump_add_region(&md_entry);
if (ret < 0)
pr_err("Failed to add log_info entry in minidump table\n");
}
static void copy_boot_log_pr_cont(void *unused, struct printk_record *r, size_t text_len)
{
if (copy_early_boot_log)
return;
if (!r->info->text_len || !off ||
((off + r->info->text_len + 1 + 1) > boot_log_buf_size))
return;
if (boot_log_buf[off - 1] == '\n')
off = off - 1;
memcpy(&boot_log_buf[off], &r->text_buf[r->info->text_len - text_len], text_len);
off += text_len;
if (r->info->flags & LOG_NEWLINE) {
boot_log_buf[off] = '\n';
boot_log_buf[off + 1] = 0;
off += 1;
}
}
static void copy_boot_log(void *unused, struct printk_ringbuffer *prb,
struct printk_record *r)
{
struct prb_desc_ring descring = prb->desc_ring;
struct prb_data_ring textdata_ring = prb->text_data_ring;
struct prb_desc *descaddr = descring.descs;
struct printk_info *p_infos = descring.infos;
atomic_long_t headid, tailid;
unsigned long did, ind, sv;
unsigned int textdata_size = _DATA_SIZE(textdata_ring.size_bits);
unsigned long begin, end;
enum desc_state state;
size_t rem_buf_sz;
tailid = descring.tail_id;
headid = descring.head_id;
if (!copy_early_boot_log) {
if (!r->info->text_len)
return;
/*
* Check whether remaining buffer has enough space
* for record meta data size + newline + terminator
* if not, let's reject the record.
*/
if ((off + r->info->text_len + PREFIX_MAX + 1 + 1) > boot_log_buf_size)
return;
rem_buf_sz = boot_log_buf_size - off;
if (!rem_buf_sz)
return;
memcpy(&boot_log_buf[off], &r->text_buf[0], r->info->text_len);
off += record_print_text(r->info, &boot_log_buf[off], rem_buf_sz);
return;
}
copy_early_boot_log = false;
register_log_minidump(prb);
did = atomic_long_read(&tailid);
while (true) {
ind = did % _DESCS_COUNT(descring.count_bits);
sv = atomic_long_read(&descaddr[ind].state_var);
state = DESC_STATE(sv);
/* skip non-committed record */
if ((state != desc_committed) && (state != desc_finalized)) {
if (did == atomic_long_read(&headid))
break;
did = DESC_ID(did + 1);
continue;
}
begin = (descaddr[ind].text_blk_lpos.begin) % textdata_size;
end = (descaddr[ind].text_blk_lpos.next) % textdata_size;
if ((begin & 1) != 1) {
unsigned long text_start;
u16 textlen;
if (begin > end)
begin = 0;
text_start = begin + sizeof(unsigned long);
textlen = p_infos[ind].text_len;
if (end - text_start < textlen)
textlen = end - text_start;
/*
* Check whether remaining buffer has enough space
* for record meta data size + newline + terminator
* if not, let's reject the record.
*/
if ((off + textlen + PREFIX_MAX + 1 + 1) > boot_log_buf_size)
break;
rem_buf_sz = boot_log_buf_size - off;
memcpy(&boot_log_buf[off], &textdata_ring.data[text_start],
textlen);
off += record_print_text(&p_infos[ind],
&boot_log_buf[off], rem_buf_sz);
}
if (did == atomic_long_read(&headid))
break;
did = DESC_ID(did + 1);
}
}
static int boot_log_init(void)
{
void *start;
int ret = 0;
unsigned int size = BOOT_LOG_SIZE;
struct md_region md_entry;
start = kzalloc(size, GFP_KERNEL);
if (!start) {
ret = -ENOMEM;
goto out;
}
strscpy(md_entry.name, "KBOOT_LOG", sizeof(md_entry.name));
md_entry.virt_addr = (uintptr_t)start;
md_entry.phys_addr = virt_to_phys(start);
md_entry.size = size;
ret = msm_minidump_add_region(&md_entry);
if (ret < 0) {
pr_err("Failed to add boot_log entry in minidump table\n");
kfree(start);
goto out;
}
boot_log_buf_size = size;
boot_log_buf = start;
/*
* Ensure boot_log_buf and boot_log_buf initialization
* is visible to other CPU's
*/
smp_mb();
out:
return ret;
}
static void release_boot_log_buf(void)
{
if (!boot_log_buf)
return;
kfree(boot_log_buf);
}
static int logbuf_vh_driver_probe(struct platform_device *pdev)
{
int ret;
ret = boot_log_init();
if (ret < 0)
return ret;
ret = register_trace_android_vh_logbuf(copy_boot_log, NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to register android_vh_logbuf hook\n");
kfree(boot_log_buf);
return ret;
}
ret = register_trace_android_vh_logbuf_pr_cont(copy_boot_log_pr_cont, NULL);
if (ret) {
dev_err(&pdev->dev, "Failed to register android_vh_logbuf_pr_cont hook\n");
unregister_trace_android_vh_logbuf(copy_boot_log, NULL);
kfree(boot_log_buf);
}
return ret;
}
static int logbuf_vh_driver_remove(struct platform_device *pdev)
{
unregister_trace_android_vh_logbuf_pr_cont(copy_boot_log_pr_cont, NULL);
unregister_trace_android_vh_logbuf(copy_boot_log, NULL);
release_boot_log_buf();
return 0;
}
static const struct of_device_id logbuf_vh_of_match[] = {
{ .compatible = "qcom,logbuf-vendor-hooks" },
{ }
};
MODULE_DEVICE_TABLE(of, logbuf_vh_of_match);
static struct platform_driver logbuf_vh_driver = {
.driver = {
.name = "qcom-logbuf-vh",
.of_match_table = logbuf_vh_of_match,
},
.probe = logbuf_vh_driver_probe,
.remove = logbuf_vh_driver_remove,
};
module_platform_driver(logbuf_vh_driver);
MODULE_DESCRIPTION("QCOM Logbuf Vendor Hooks Driver");
MODULE_LICENSE("GPL v2");