mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 11:37:06 +02:00
Merge "soc: qcom: Add snapshot of cpu vendor hooks driver"
This commit is contained in:
commit
686aa245d9
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
214
drivers/soc/qcom/qcom_cpu_vendor_hooks.c
Normal file
214
drivers/soc/qcom/qcom_cpu_vendor_hooks.c
Normal 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");
|
||||
390
drivers/soc/qcom/qcom_logbuf_vh.c
Normal file
390
drivers/soc/qcom/qcom_logbuf_vh.c
Normal 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");
|
||||
Loading…
Reference in New Issue
Block a user