240 lines
5.8 KiB
C
240 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2023 Red Hat
|
|
*/
|
|
|
|
#include "logger.h"
|
|
|
|
#include <asm/current.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/module.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/sched.h>
|
|
|
|
#include "errors.h"
|
|
#include "thread-device.h"
|
|
#include "thread-utils.h"
|
|
|
|
int vdo_log_level = VDO_LOG_DEFAULT;
|
|
|
|
int vdo_get_log_level(void)
|
|
{
|
|
int log_level_latch = READ_ONCE(vdo_log_level);
|
|
|
|
if (unlikely(log_level_latch > VDO_LOG_MAX)) {
|
|
log_level_latch = VDO_LOG_DEFAULT;
|
|
WRITE_ONCE(vdo_log_level, log_level_latch);
|
|
}
|
|
return log_level_latch;
|
|
}
|
|
|
|
static const char *get_current_interrupt_type(void)
|
|
{
|
|
if (in_nmi())
|
|
return "NMI";
|
|
|
|
if (in_irq())
|
|
return "HI";
|
|
|
|
if (in_softirq())
|
|
return "SI";
|
|
|
|
return "INTR";
|
|
}
|
|
|
|
/**
|
|
* emit_log_message_to_kernel() - Emit a log message to the kernel at the specified priority.
|
|
*
|
|
* @priority: The priority at which to log the message
|
|
* @fmt: The format string of the message
|
|
*/
|
|
static void emit_log_message_to_kernel(int priority, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
struct va_format vaf;
|
|
|
|
if (priority > vdo_get_log_level())
|
|
return;
|
|
|
|
va_start(args, fmt);
|
|
vaf.fmt = fmt;
|
|
vaf.va = &args;
|
|
|
|
switch (priority) {
|
|
case VDO_LOG_EMERG:
|
|
case VDO_LOG_ALERT:
|
|
case VDO_LOG_CRIT:
|
|
pr_crit("%pV", &vaf);
|
|
break;
|
|
case VDO_LOG_ERR:
|
|
pr_err("%pV", &vaf);
|
|
break;
|
|
case VDO_LOG_WARNING:
|
|
pr_warn("%pV", &vaf);
|
|
break;
|
|
case VDO_LOG_NOTICE:
|
|
case VDO_LOG_INFO:
|
|
pr_info("%pV", &vaf);
|
|
break;
|
|
case VDO_LOG_DEBUG:
|
|
pr_debug("%pV", &vaf);
|
|
break;
|
|
default:
|
|
printk(KERN_DEFAULT "%pV", &vaf);
|
|
break;
|
|
}
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
/**
|
|
* emit_log_message() - Emit a log message to the kernel log in a format suited to the current
|
|
* thread context.
|
|
*
|
|
* Context info formats:
|
|
*
|
|
* interrupt: uds[NMI]: blah
|
|
* kvdo thread: kvdo12:foobarQ: blah
|
|
* thread w/device id: kvdo12:myprog: blah
|
|
* other thread: uds: myprog: blah
|
|
*
|
|
* Fields: module name, interrupt level, process name, device ID.
|
|
*
|
|
* @priority: the priority at which to log the message
|
|
* @module: The name of the module doing the logging
|
|
* @prefix: The prefix of the log message
|
|
* @vaf1: The first message format descriptor
|
|
* @vaf2: The second message format descriptor
|
|
*/
|
|
static void emit_log_message(int priority, const char *module, const char *prefix,
|
|
const struct va_format *vaf1, const struct va_format *vaf2)
|
|
{
|
|
int device_instance;
|
|
|
|
/*
|
|
* In interrupt context, identify the interrupt type and module. Ignore the process/thread
|
|
* since it could be anything.
|
|
*/
|
|
if (in_interrupt()) {
|
|
const char *type = get_current_interrupt_type();
|
|
|
|
emit_log_message_to_kernel(priority, "%s[%s]: %s%pV%pV\n", module, type,
|
|
prefix, vaf1, vaf2);
|
|
return;
|
|
}
|
|
|
|
/* Not at interrupt level; we have a process we can look at, and might have a device ID. */
|
|
device_instance = vdo_get_thread_device_id();
|
|
if (device_instance >= 0) {
|
|
emit_log_message_to_kernel(priority, "%s%u:%s: %s%pV%pV\n", module,
|
|
device_instance, current->comm, prefix, vaf1,
|
|
vaf2);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If it's a kernel thread and the module name is a prefix of its name, assume it is ours
|
|
* and only identify the thread.
|
|
*/
|
|
if (((current->flags & PF_KTHREAD) != 0) &&
|
|
(strncmp(module, current->comm, strlen(module)) == 0)) {
|
|
emit_log_message_to_kernel(priority, "%s: %s%pV%pV\n", current->comm,
|
|
prefix, vaf1, vaf2);
|
|
return;
|
|
}
|
|
|
|
/* Identify the module and the process. */
|
|
emit_log_message_to_kernel(priority, "%s: %s: %s%pV%pV\n", module, current->comm,
|
|
prefix, vaf1, vaf2);
|
|
}
|
|
|
|
/*
|
|
* vdo_log_embedded_message() - Log a message embedded within another message.
|
|
* @priority: the priority at which to log the message
|
|
* @module: the name of the module doing the logging
|
|
* @prefix: optional string prefix to message, may be NULL
|
|
* @fmt1: format of message first part (required)
|
|
* @args1: arguments for message first part (required)
|
|
* @fmt2: format of message second part
|
|
*/
|
|
void vdo_log_embedded_message(int priority, const char *module, const char *prefix,
|
|
const char *fmt1, va_list args1, const char *fmt2, ...)
|
|
{
|
|
va_list args1_copy;
|
|
va_list args2;
|
|
struct va_format vaf1, vaf2;
|
|
|
|
va_start(args2, fmt2);
|
|
|
|
if (module == NULL)
|
|
module = VDO_LOGGING_MODULE_NAME;
|
|
|
|
if (prefix == NULL)
|
|
prefix = "";
|
|
|
|
/*
|
|
* It is implementation dependent whether va_list is defined as an array type that decays
|
|
* to a pointer when passed as an argument. Copy args1 and args2 with va_copy so that vaf1
|
|
* and vaf2 get proper va_list pointers irrespective of how va_list is defined.
|
|
*/
|
|
va_copy(args1_copy, args1);
|
|
vaf1.fmt = fmt1;
|
|
vaf1.va = &args1_copy;
|
|
|
|
vaf2.fmt = fmt2;
|
|
vaf2.va = &args2;
|
|
|
|
emit_log_message(priority, module, prefix, &vaf1, &vaf2);
|
|
|
|
va_end(args1_copy);
|
|
va_end(args2);
|
|
}
|
|
|
|
int vdo_vlog_strerror(int priority, int errnum, const char *module, const char *format,
|
|
va_list args)
|
|
{
|
|
char errbuf[VDO_MAX_ERROR_MESSAGE_SIZE];
|
|
const char *message = uds_string_error(errnum, errbuf, sizeof(errbuf));
|
|
|
|
vdo_log_embedded_message(priority, module, NULL, format, args, ": %s (%d)",
|
|
message, errnum);
|
|
return errnum;
|
|
}
|
|
|
|
int __vdo_log_strerror(int priority, int errnum, const char *module, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
vdo_vlog_strerror(priority, errnum, module, format, args);
|
|
va_end(args);
|
|
return errnum;
|
|
}
|
|
|
|
void vdo_log_backtrace(int priority)
|
|
{
|
|
if (priority > vdo_get_log_level())
|
|
return;
|
|
|
|
dump_stack();
|
|
}
|
|
|
|
void __vdo_log_message(int priority, const char *module, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
vdo_log_embedded_message(priority, module, NULL, format, args, "%s", "");
|
|
va_end(args);
|
|
}
|
|
|
|
/*
|
|
* Sleep or delay a few milliseconds in an attempt to allow the log buffers to be flushed lest they
|
|
* be overrun.
|
|
*/
|
|
void vdo_pause_for_logger(void)
|
|
{
|
|
fsleep(4000);
|
|
}
|