/*
 * Copyright 2025 Sony Corporation
 */

#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <trace/hooks/printk.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sony");
MODULE_DESCRIPTION("Vendor Hook");

/* ******************************************************************** */
/* Implementation required to execute kernel source code on vendor hook */
/* ******************************************************************** */
#include <linux/console.h>
#include <../kernel/printk/printk_ringbuffer.h>
#include <../kernel/printk/internal.h>

#ifdef CONFIG_PRINTK_CALLER
#define PREFIX_MAX		48
#else
#define PREFIX_MAX		32
#endif

#define trace_android_vh_printk_caller(...)	do { } while(0)
#define trace_console_rcuidle(...)		do { } while(0)

static int console_dropped = 0;
static struct console *exclusive_console = NULL;

/* ******************************************************************** */
/* kernel source code                                                   */
/* ******************************************************************** */
#ifdef CONFIG_PRINTK_CALLER
static size_t print_caller(u32 id, char *buf)
{
	char caller[12];
	int vh_ret = 0;

	trace_android_vh_printk_caller(caller, sizeof(caller), id, &vh_ret);
	if (!vh_ret)
		snprintf(caller, sizeof(caller), "%c%u",
			 id & 0x80000000 ? 'C' : 'T', id & ~0x80000000);
	return sprintf(buf, "[%6s]", caller);
}
#else
#define print_caller(id, buf) 0
#endif

static size_t print_time(u64 ts, char *buf)
{
	unsigned long rem_nsec = do_div(ts, 1000000000);

	return sprintf(buf, "[%5lu.%06lu]",
		       (unsigned long)ts, rem_nsec / 1000);
}

static size_t print_syslog(unsigned int level, char *buf)
{
	return sprintf(buf, "<%u>", level);
}

static size_t info_print_prefix(const struct printk_info  *info, bool syslog,
				bool time, char *buf)
{
	size_t len = 0;

	if (syslog)
		len = print_syslog((info->facility << 3) | info->level, buf);

	if (time)
		len += print_time(info->ts_nsec, buf + len);

	len += print_caller(info->caller_id, buf + len);

	if (IS_ENABLED(CONFIG_PRINTK_CALLER) || time) {
		buf[len++] = ' ';
		buf[len] = '\0';
	}

	return len;
}

static size_t record_print_text(const struct printk_record *r, bool syslog, bool time)
{
	size_t text_len = r->info->text_len;
	size_t buf_size = r->text_buf_size;
	char *text = r->text_buf;
	char prefix[PREFIX_MAX];
	bool truncated = false;
	size_t prefix_len;
	size_t line_len;
	size_t len = 0;
	char *next;

	/*
	 * If the message was truncated because the buffer was not large
	 * enough, treat the available text as if it were the full text.
	 */
	if (text_len > buf_size)
		text_len = buf_size;

	prefix_len = info_print_prefix(r->info, syslog, time, prefix);

	/*
	 * @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 {
			/* Drop truncated line(s). */
			if (truncated)
				break;
			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) {
			/* Drop even the current line if no space. */
			if (len + prefix_len + line_len + 1 + 1 > buf_size)
				break;

			text_len = buf_size - len - prefix_len - 1 - 1;
			truncated = true;
		}

		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_buf[len] = 0;

	return len;
}

static void call_console_drivers(const char *ext_text, size_t ext_len, const char *text, size_t len)
{
	static char dropped_text[64];
	size_t dropped_len = 0;
	struct console *con;

	trace_console_rcuidle(text, len);

	if (!console_drivers)
		return;

	if (console_dropped) {
		dropped_len = snprintf(dropped_text, sizeof(dropped_text),
				       "** %lu printk messages dropped **\n",
				       console_dropped);
		console_dropped = 0;
	}

	for_each_console(con) {
		/* ******************************************************************** */
		/* Only ramoops is executed.                                            */
		/* ******************************************************************** */
		if (strcmp(con->name, "ramoops")) {
			continue;
		}
		if (exclusive_console && con != exclusive_console)
			continue;
		if (!(con->flags & CON_ENABLED))
			continue;
		if (!con->write)
			continue;
		if (!cpu_online(smp_processor_id()) &&
		    !(con->flags & CON_ANYTIME))
			continue;
		if (con->flags & CON_EXTENDED)
			con->write(con, ext_text, ext_len);
		else {
			if (dropped_len)
				con->write(con, dropped_text, dropped_len);
			con->write(con, text, len);
		}
	}
}

/* ******************************************************************** */
/* vendor hook function                                                 */
/* ******************************************************************** */
static DEFINE_SPINLOCK(vh_console_lock);
static void android_vh_printk_console_unlock_cb(void *private_data, const struct printk_record *r)
{
	size_t len;
	unsigned long flags;
	bool syslog = false;
	bool time = IS_ENABLED(CONFIG_PRINTK_TIME);
	const char *text = (const char*)r->text_buf;

	(void)private_data;

	//Ramoops does not use syslog, but it does use timestamps.
	len = record_print_text(r, syslog, time);

	// These functions can not be used in vendor hook.
	//printk_safe_enter_irqsave(flags);
	//console_lock_spinning_enable();

	//Instead of above exclusive processing, use spin_trylock_irqsave.
	spin_trylock_irqsave(&vh_console_lock, flags);

	stop_critical_timings();
	//The extension field is not used.
	call_console_drivers(NULL, 0, text, len);
	start_critical_timings();

	spin_unlock_irqrestore(&vh_console_lock, flags);

	//handover = console_lock_spinning_disable_and_check();
	//printk_safe_exit_irqrestore(flags);
	//if (handover)
	//	return;

	//if (do_cond_resched)
	//	cond_resched();

	return;
}

static int __init vh_ignore_loglevel_init(void)
{
	int ret;

	ret = register_trace_android_vh_printk_console_unlock(android_vh_printk_console_unlock_cb, NULL);
	printk(KERN_INFO"%s ret:%d\n", __func__, ret);

	return 0;
}

static void __exit vh_ignore_loglevel_exit(void)
{
	int ret;

	ret = unregister_trace_android_vh_printk_console_unlock(android_vh_printk_console_unlock_cb, NULL);
	printk(KERN_INFO"%s ret:%d\n", __func__, ret);

	return;
}

module_init(vh_ignore_loglevel_init);
module_exit(vh_ignore_loglevel_exit);
