1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright 2023 Red Hat
4 */
5
6#include "logger.h"
7
8#include <asm/current.h>
9#include <linux/delay.h>
10#include <linux/hardirq.h>
11#include <linux/module.h>
12#include <linux/printk.h>
13#include <linux/sched.h>
14
15#include "errors.h"
16#include "thread-device.h"
17#include "thread-utils.h"
18
19int vdo_log_level = VDO_LOG_DEFAULT;
20
21int vdo_get_log_level(void)
22{
23	int log_level_latch = READ_ONCE(vdo_log_level);
24
25	if (unlikely(log_level_latch > VDO_LOG_MAX)) {
26		log_level_latch = VDO_LOG_DEFAULT;
27		WRITE_ONCE(vdo_log_level, log_level_latch);
28	}
29	return log_level_latch;
30}
31
32static const char *get_current_interrupt_type(void)
33{
34	if (in_nmi())
35		return "NMI";
36
37	if (in_irq())
38		return "HI";
39
40	if (in_softirq())
41		return "SI";
42
43	return "INTR";
44}
45
46/**
47 * emit_log_message_to_kernel() - Emit a log message to the kernel at the specified priority.
48 *
49 * @priority: The priority at which to log the message
50 * @fmt: The format string of the message
51 */
52static void emit_log_message_to_kernel(int priority, const char *fmt, ...)
53{
54	va_list args;
55	struct va_format vaf;
56
57	if (priority > vdo_get_log_level())
58		return;
59
60	va_start(args, fmt);
61	vaf.fmt = fmt;
62	vaf.va = &args;
63
64	switch (priority) {
65	case VDO_LOG_EMERG:
66	case VDO_LOG_ALERT:
67	case VDO_LOG_CRIT:
68		pr_crit("%pV", &vaf);
69		break;
70	case VDO_LOG_ERR:
71		pr_err("%pV", &vaf);
72		break;
73	case VDO_LOG_WARNING:
74		pr_warn("%pV", &vaf);
75		break;
76	case VDO_LOG_NOTICE:
77	case VDO_LOG_INFO:
78		pr_info("%pV", &vaf);
79		break;
80	case VDO_LOG_DEBUG:
81		pr_debug("%pV", &vaf);
82		break;
83	default:
84		printk(KERN_DEFAULT "%pV", &vaf);
85		break;
86	}
87
88	va_end(args);
89}
90
91/**
92 * emit_log_message() - Emit a log message to the kernel log in a format suited to the current
93 *                      thread context.
94 *
95 * Context info formats:
96 *
97 * interrupt:           uds[NMI]: blah
98 * kvdo thread:         kvdo12:foobarQ: blah
99 * thread w/device id:  kvdo12:myprog: blah
100 * other thread:        uds: myprog: blah
101 *
102 * Fields: module name, interrupt level, process name, device ID.
103 *
104 * @priority: the priority at which to log the message
105 * @module: The name of the module doing the logging
106 * @prefix: The prefix of the log message
107 * @vaf1: The first message format descriptor
108 * @vaf2: The second message format descriptor
109 */
110static void emit_log_message(int priority, const char *module, const char *prefix,
111			     const struct va_format *vaf1, const struct va_format *vaf2)
112{
113	int device_instance;
114
115	/*
116	 * In interrupt context, identify the interrupt type and module. Ignore the process/thread
117	 * since it could be anything.
118	 */
119	if (in_interrupt()) {
120		const char *type = get_current_interrupt_type();
121
122		emit_log_message_to_kernel(priority, "%s[%s]: %s%pV%pV\n", module, type,
123					   prefix, vaf1, vaf2);
124		return;
125	}
126
127	/* Not at interrupt level; we have a process we can look at, and might have a device ID. */
128	device_instance = vdo_get_thread_device_id();
129	if (device_instance >= 0) {
130		emit_log_message_to_kernel(priority, "%s%u:%s: %s%pV%pV\n", module,
131					   device_instance, current->comm, prefix, vaf1,
132					   vaf2);
133		return;
134	}
135
136	/*
137	 * If it's a kernel thread and the module name is a prefix of its name, assume it is ours
138	 * and only identify the thread.
139	 */
140	if (((current->flags & PF_KTHREAD) != 0) &&
141	    (strncmp(module, current->comm, strlen(module)) == 0)) {
142		emit_log_message_to_kernel(priority, "%s: %s%pV%pV\n", current->comm,
143					   prefix, vaf1, vaf2);
144		return;
145	}
146
147	/* Identify the module and the process. */
148	emit_log_message_to_kernel(priority, "%s: %s: %s%pV%pV\n", module, current->comm,
149				   prefix, vaf1, vaf2);
150}
151
152/*
153 * vdo_log_embedded_message() - Log a message embedded within another message.
154 * @priority: the priority at which to log the message
155 * @module: the name of the module doing the logging
156 * @prefix: optional string prefix to message, may be NULL
157 * @fmt1: format of message first part (required)
158 * @args1: arguments for message first part (required)
159 * @fmt2: format of message second part
160 */
161void vdo_log_embedded_message(int priority, const char *module, const char *prefix,
162			      const char *fmt1, va_list args1, const char *fmt2, ...)
163{
164	va_list args1_copy;
165	va_list args2;
166	struct va_format vaf1, vaf2;
167
168	va_start(args2, fmt2);
169
170	if (module == NULL)
171		module = VDO_LOGGING_MODULE_NAME;
172
173	if (prefix == NULL)
174		prefix = "";
175
176	/*
177	 * It is implementation dependent whether va_list is defined as an array type that decays
178	 * to a pointer when passed as an argument. Copy args1 and args2 with va_copy so that vaf1
179	 * and vaf2 get proper va_list pointers irrespective of how va_list is defined.
180	 */
181	va_copy(args1_copy, args1);
182	vaf1.fmt = fmt1;
183	vaf1.va = &args1_copy;
184
185	vaf2.fmt = fmt2;
186	vaf2.va = &args2;
187
188	emit_log_message(priority, module, prefix, &vaf1, &vaf2);
189
190	va_end(args1_copy);
191	va_end(args2);
192}
193
194int vdo_vlog_strerror(int priority, int errnum, const char *module, const char *format,
195		      va_list args)
196{
197	char errbuf[VDO_MAX_ERROR_MESSAGE_SIZE];
198	const char *message = uds_string_error(errnum, errbuf, sizeof(errbuf));
199
200	vdo_log_embedded_message(priority, module, NULL, format, args, ": %s (%d)",
201				 message, errnum);
202	return errnum;
203}
204
205int __vdo_log_strerror(int priority, int errnum, const char *module, const char *format, ...)
206{
207	va_list args;
208
209	va_start(args, format);
210	vdo_vlog_strerror(priority, errnum, module, format, args);
211	va_end(args);
212	return errnum;
213}
214
215void vdo_log_backtrace(int priority)
216{
217	if (priority > vdo_get_log_level())
218		return;
219
220	dump_stack();
221}
222
223void __vdo_log_message(int priority, const char *module, const char *format, ...)
224{
225	va_list args;
226
227	va_start(args, format);
228	vdo_log_embedded_message(priority, module, NULL, format, args, "%s", "");
229	va_end(args);
230}
231
232/*
233 * Sleep or delay a few milliseconds in an attempt to allow the log buffers to be flushed lest they
234 * be overrun.
235 */
236void vdo_pause_for_logger(void)
237{
238	fsleep(4000);
239}
240