xref: /linux/drivers/md/dm-vdo/logger.c (revision da5b2ad1c2f18834cb1ce429e2e5a5cf5cbdf21b)
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 
19 int vdo_log_level = VDO_LOG_DEFAULT;
20 
21 int 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 
32 static 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  */
52 static 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  */
110 static 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  */
161 void 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 
194 int 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 
205 int __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 
215 void vdo_log_backtrace(int priority)
216 {
217 	if (priority > vdo_get_log_level())
218 		return;
219 
220 	dump_stack();
221 }
222 
223 void __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  */
236 void vdo_pause_for_logger(void)
237 {
238 	fsleep(4000);
239 }
240