From 4149df9005f2cdd2ecf70058dfe7d72f48c3a68c Mon Sep 17 00:00:00 2001
From: John Ogness <john.ogness@linutronix.de>
Date: Wed, 25 Nov 2020 23:26:59 +0106
Subject: [PATCH] printk: add support for lockless ringbuffer

Linux 5.10 moved to a new lockless ringbuffer. The new ringbuffer
is structured completely different to the previous iterations.
Add support for retrieving the ringbuffer using vmcoreinfo. The
new ringbuffer is detected based on the availability of the
"prb" symbol.

Signed-off-by: John Ogness <john.ogness@linutronix.de>
Signed-off-by: Simon Horman <horms@verge.net.au>
---
 util_lib/elf_info.c | 438 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 437 insertions(+), 1 deletion(-)

diff --git a/util_lib/elf_info.c b/util_lib/elf_info.c
index 7803a94..2f23a44 100644
--- a/util_lib/elf_info.c
+++ b/util_lib/elf_info.c
@@ -27,6 +27,32 @@ static int num_pt_loads;
 
 static char osrelease[4096];
 
+/* VMCOREINFO symbols for lockless printk ringbuffer */
+static loff_t prb_vaddr;
+static size_t printk_ringbuffer_sz;
+static size_t prb_desc_sz;
+static size_t printk_info_sz;
+static uint64_t printk_ringbuffer_desc_ring_offset;
+static uint64_t printk_ringbuffer_text_data_ring_offset;
+static uint64_t prb_desc_ring_count_bits_offset;
+static uint64_t prb_desc_ring_descs_offset;
+static uint64_t prb_desc_ring_infos_offset;
+static uint64_t prb_data_ring_size_bits_offset;
+static uint64_t prb_data_ring_data_offset;
+static uint64_t prb_desc_ring_head_id_offset;
+static uint64_t prb_desc_ring_tail_id_offset;
+static uint64_t atomic_long_t_counter_offset;
+static uint64_t prb_desc_state_var_offset;
+static uint64_t prb_desc_info_offset;
+static uint64_t prb_desc_text_blk_lpos_offset;
+static uint64_t prb_data_blk_lpos_begin_offset;
+static uint64_t prb_data_blk_lpos_next_offset;
+static uint64_t printk_info_seq_offset;
+static uint64_t printk_info_caller_id_offset;
+static uint64_t printk_info_ts_nsec_offset;
+static uint64_t printk_info_level_offset;
+static uint64_t printk_info_text_len_offset;
+
 static loff_t log_buf_vaddr;
 static loff_t log_end_vaddr;
 static loff_t log_buf_len_vaddr;
@@ -304,6 +330,7 @@ void scan_vmcoreinfo(char *start, size_t size)
 		size_t len;
 		loff_t *vaddr;
 	} symbol[] = {
+		SYMBOL(prb),
 		SYMBOL(log_buf),
 		SYMBOL(log_end),
 		SYMBOL(log_buf_len),
@@ -361,6 +388,119 @@ void scan_vmcoreinfo(char *start, size_t size)
 			*symbol[i].vaddr = vaddr;
 		}
 
+		str = "SIZE(printk_ringbuffer)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_ringbuffer_sz = strtoull(pos + strlen(str),
+							NULL, 10);
+
+		str = "SIZE(prb_desc)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_sz = strtoull(pos + strlen(str), NULL, 10);
+
+		str = "SIZE(printk_info)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_info_sz = strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(printk_ringbuffer.desc_ring)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_ringbuffer_desc_ring_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(printk_ringbuffer.text_data_ring)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_ringbuffer_text_data_ring_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc_ring.count_bits)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_ring_count_bits_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc_ring.descs)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_ring_descs_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc_ring.infos)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_ring_infos_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_data_ring.size_bits)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_data_ring_size_bits_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_data_ring.data)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_data_ring_data_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc_ring.head_id)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_ring_head_id_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc_ring.tail_id)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_ring_tail_id_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(atomic_long_t.counter)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			atomic_long_t_counter_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc.state_var)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_state_var_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc.info)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_info_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_desc.text_blk_lpos)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_desc_text_blk_lpos_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_data_blk_lpos.begin)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_data_blk_lpos_begin_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(prb_data_blk_lpos.next)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			prb_data_blk_lpos_next_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(printk_info.seq)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_info_seq_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(printk_info.caller_id)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_info_caller_id_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(printk_info.ts_nsec)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_info_ts_nsec_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(printk_info.level)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_info_level_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
+		str = "OFFSET(printk_info.text_len)=";
+		if (memcmp(str, pos, strlen(str)) == 0)
+			printk_info_text_len_offset =
+				strtoull(pos + strlen(str), NULL, 10);
+
 		/* Check for "SIZE(printk_log)" or older "SIZE(log)=" */
 		str = "SIZE(log)=";
 		if (memcmp(str, pos, strlen(str)) == 0)
@@ -746,9 +886,305 @@ static void dump_dmesg_structured(int fd, void (*handler)(char*, unsigned int))
 		handler(out_buf, len);
 }
 
+/* convenience struct for passing many values to helper functions */
+struct prb_map {
+	char		*prb;
+
+	char		*desc_ring;
+	unsigned long	desc_ring_count;
+	char		*descs;
+
+	char		*infos;
+
+	char		*text_data_ring;
+	unsigned long	text_data_ring_size;
+	char		*text_data;
+};
+
+/*
+ * desc_state and DESC_* definitions taken from kernel source:
+ *
+ * kernel/printk/printk_ringbuffer.h
+ *
+ * DESC_* definitions modified to provide 32-bit and 64-bit variants.
+ */
+
+/* The possible responses of a descriptor state-query. */
+enum desc_state {
+	desc_miss	=  -1,	/* ID mismatch (pseudo state) */
+	desc_reserved	= 0x0,	/* reserved, in use by writer */
+	desc_committed	= 0x1,	/* committed by writer, could get reopened */
+	desc_finalized	= 0x2,	/* committed, no further modification allowed */
+	desc_reusable	= 0x3,	/* free, not yet used by any writer */
+};
+
+#define DESC_SV_BITS		(sizeof(uint64_t) * 8)
+#define DESC_FLAGS_SHIFT	(DESC_SV_BITS - 2)
+#define DESC_FLAGS_MASK		(3UL << DESC_FLAGS_SHIFT)
+#define DESC_STATE(sv)		(3UL & (sv >> DESC_FLAGS_SHIFT))
+#define DESC_ID_MASK		(~DESC_FLAGS_MASK)
+#define DESC_ID(sv)		((sv) & DESC_ID_MASK)
+
+#define DESC32_SV_BITS		(sizeof(uint32_t) * 8)
+#define DESC32_FLAGS_SHIFT	(DESC32_SV_BITS - 2)
+#define DESC32_FLAGS_MASK	(3UL << DESC32_FLAGS_SHIFT)
+#define DESC32_STATE(sv)	(3UL & (sv >> DESC32_FLAGS_SHIFT))
+#define DESC32_ID_MASK		(~DESC32_FLAGS_MASK)
+#define DESC32_ID(sv)		((sv) & DESC32_ID_MASK)
+
+/*
+ * get_desc_state() taken from kernel source:
+ *
+ * kernel/printk/printk_ringbuffer.c
+ *
+ * get_desc32_state() added as 32-bit variant.
+ */
+
+/* Query the state of a descriptor. */
+static enum desc_state get_desc_state(unsigned long id,
+				      uint64_t state_val)
+{
+	if (id != DESC_ID(state_val))
+		return desc_miss;
+
+	return DESC_STATE(state_val);
+}
+
+static enum desc_state get_desc32_state(unsigned long id,
+					uint64_t state_val)
+{
+	if (id != DESC32_ID(state_val))
+		return desc_miss;
+
+	return DESC32_STATE(state_val);
+}
+
+static bool record_committed(unsigned long id, uint64_t state_var)
+{
+	enum desc_state state;
+
+	if (machine_pointer_bits() == 32)
+		state = get_desc32_state(id, state_var);
+	else
+		state = get_desc_state(id, state_var);
+
+	return (state == desc_committed || state == desc_finalized);
+}
+
+static uint64_t id_inc(uint64_t id)
+{
+	id++;
+
+	if (machine_pointer_bits() == 32)
+		return (id & DESC32_ID_MASK);
+
+	return (id & DESC_ID_MASK);
+}
+
+static uint64_t get_ulong(char *addr)
+{
+	if (machine_pointer_bits() == 32)
+		return struct_val_u32(addr, 0);
+	return struct_val_u64(addr, 0);
+}
+
+static uint64_t sizeof_ulong(void)
+{
+	return (machine_pointer_bits() >> 3);
+}
+
+static void dump_record(struct prb_map *m, unsigned long id,
+			void (*handler)(char*, unsigned int))
+{
+#define OUT_BUF_SIZE	4096
+	char out_buf[OUT_BUF_SIZE];
+	imaxdiv_t imaxdiv_usec;
+	imaxdiv_t imaxdiv_sec;
+	uint32_t offset = 0;
+	unsigned short len;
+	uint64_t state_var;
+	uint64_t ts_nsec;
+	uint64_t begin;
+	uint64_t next;
+	char *info;
+	char *text;
+	char *desc;
+	int i;
+
+	desc = m->descs + ((id % m->desc_ring_count) * prb_desc_sz);
+	info = m->infos + ((id % m->desc_ring_count) * printk_info_sz);
+
+	/* skip non-committed record */
+	state_var = get_ulong(desc + prb_desc_state_var_offset +
+					atomic_long_t_counter_offset);
+	if (!record_committed(id, state_var))
+		return;
+
+	begin = get_ulong(desc + prb_desc_text_blk_lpos_offset +
+			  prb_data_blk_lpos_begin_offset) %
+		m->text_data_ring_size;
+	next = get_ulong(desc + prb_desc_text_blk_lpos_offset +
+			 prb_data_blk_lpos_next_offset) %
+	       m->text_data_ring_size;
+
+	ts_nsec = struct_val_u64(info, printk_info_ts_nsec_offset);
+	imaxdiv_sec = imaxdiv(ts_nsec, 1000000000);
+	imaxdiv_usec = imaxdiv(imaxdiv_sec.rem, 1000);
+
+	offset += sprintf(out_buf + offset, "[%5llu.%06llu] ",
+		(long long unsigned int)imaxdiv_sec.quot,
+		(long long unsigned int)imaxdiv_usec.quot);
+
+	/* skip data-less text blocks */
+	if (begin == next)
+		goto out;
+
+	len = struct_val_u16(info, printk_info_text_len_offset);
+
+	/* handle wrapping data block */
+	if (begin > next)
+		begin = 0;
+
+	/* skip over descriptor ID */
+	begin += sizeof_ulong();
+
+	/* handle truncated messages */
+	if (next - begin < len)
+		len = next - begin;
+
+	text = m->text_data + begin;
+
+	/* escape non-printable characters */
+	for (i = 0; i < len; i++) {
+		unsigned char c = text[i];
+
+		if (!isprint(c) && !isspace(c))
+			offset += sprintf(out_buf + offset, "\\x%02x", c);
+		else
+			out_buf[offset++] = c;
+
+		if (offset >= OUT_BUF_SIZE - 64) {
+			if (handler)
+				handler(out_buf, offset);
+			offset = 0;
+		}
+	}
+out:
+	out_buf[offset++] = '\n';
+
+	if (offset && handler)
+		handler(out_buf, offset);
+}
+
+/*
+ *  Handle the lockless printk_ringbuffer.
+ */
+static void dump_dmesg_lockless(int fd, void (*handler)(char*, unsigned int))
+{
+	struct prb_map m;
+	uint64_t head_id;
+	uint64_t tail_id;
+	uint64_t kaddr;
+	uint64_t id;
+	int ret;
+
+	/* setup printk_ringbuffer */
+	kaddr = read_file_pointer(fd, vaddr_to_offset(prb_vaddr));
+	m.prb = calloc(1, printk_ringbuffer_sz);
+	if (!m.prb) {
+		fprintf(stderr, "Failed to malloc %lu bytes for prb: %s\n",
+			printk_ringbuffer_sz, strerror(errno));
+		exit(64);
+	}
+	ret = pread(fd, m.prb, printk_ringbuffer_sz, vaddr_to_offset(kaddr));
+	if (ret != printk_ringbuffer_sz) {
+		fprintf(stderr, "Failed to read prb of size %lu bytes: %s\n",
+			printk_ringbuffer_sz, strerror(errno));
+		exit(65);
+	}
+
+	/* setup descriptor ring */
+	m.desc_ring = m.prb + printk_ringbuffer_desc_ring_offset;
+	m.desc_ring_count = 1 << struct_val_u32(m.desc_ring,
+					prb_desc_ring_count_bits_offset);
+	kaddr = get_ulong(m.desc_ring + prb_desc_ring_descs_offset);
+	m.descs = calloc(1, prb_desc_sz * m.desc_ring_count);
+	if (!m.descs) {
+		fprintf(stderr, "Failed to malloc %lu bytes for descs: %s\n",
+			prb_desc_sz * m.desc_ring_count, strerror(errno));
+		exit(64);
+	}
+	ret = pread(fd, m.descs, prb_desc_sz * m.desc_ring_count,
+		    vaddr_to_offset(kaddr));
+	if (ret != prb_desc_sz * m.desc_ring_count) {
+		fprintf(stderr,
+			"Failed to read descs of size %lu bytes: %s\n",
+			prb_desc_sz * m.desc_ring_count, strerror(errno));
+		exit(65);
+	}
+
+	/* setup info ring */
+	kaddr = get_ulong(m.prb + prb_desc_ring_infos_offset);
+	m.infos = calloc(1, printk_info_sz * m.desc_ring_count);
+	if (!m.infos) {
+		fprintf(stderr, "Failed to malloc %lu bytes for infos: %s\n",
+			printk_info_sz * m.desc_ring_count, strerror(errno));
+		exit(64);
+	}
+	ret = pread(fd, m.infos, printk_info_sz * m.desc_ring_count,
+		    vaddr_to_offset(kaddr));
+	if (ret != printk_info_sz * m.desc_ring_count) {
+		fprintf(stderr,
+			"Failed to read infos of size %lu bytes: %s\n",
+			printk_info_sz * m.desc_ring_count, strerror(errno));
+		exit(65);
+	}
+
+	/* setup text data ring */
+	m.text_data_ring = m.prb + printk_ringbuffer_text_data_ring_offset;
+	m.text_data_ring_size = 1 << struct_val_u32(m.text_data_ring,
+					prb_data_ring_size_bits_offset);
+	kaddr = get_ulong(m.text_data_ring + prb_data_ring_data_offset);
+	m.text_data = calloc(1, m.text_data_ring_size);
+	if (!m.text_data) {
+		fprintf(stderr,
+			"Failed to malloc %lu bytes for text_data: %s\n",
+			m.text_data_ring_size, strerror(errno));
+		exit(64);
+	}
+	ret = pread(fd, m.text_data, m.text_data_ring_size,
+		    vaddr_to_offset(kaddr));
+	if (ret != m.text_data_ring_size) {
+		fprintf(stderr,
+			"Failed to read text_data of size %lu bytes: %s\n",
+			m.text_data_ring_size, strerror(errno));
+		exit(65);
+	}
+
+	/* ready to go */
+
+	tail_id = get_ulong(m.desc_ring + prb_desc_ring_tail_id_offset +
+						atomic_long_t_counter_offset);
+	head_id = get_ulong(m.desc_ring + prb_desc_ring_head_id_offset +
+						atomic_long_t_counter_offset);
+
+	for (id = tail_id; id != head_id; id = id_inc(id))
+		dump_record(&m, id, handler);
+
+	/* dump head record */
+	dump_record(&m, id, handler);
+
+	free(m.text_data);
+	free(m.infos);
+	free(m.descs);
+	free(m.prb);
+}
+
 void dump_dmesg(int fd, void (*handler)(char*, unsigned int))
 {
-	if (log_first_idx_vaddr)
+	if (prb_vaddr)
+		dump_dmesg_lockless(fd, handler);
+	else if (log_first_idx_vaddr)
 		dump_dmesg_structured(fd, handler);
 	else
 		dump_dmesg_legacy(fd, handler);
-- 
2.43.0

