Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
CONFIG_KUNIT=y
CONFIG_PRINTK=y
CONFIG_PRINTK_RINGBUFFER_KUNIT_TEST=y
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y = printk.o
obj-$(CONFIG_PRINTK) += printk_safe.o nbcon.o
obj-$(CONFIG_A11Y_BRAILLE_CONSOLE) += braille.o
obj-$(CONFIG_PRINTK_INDEX) += index.o
obj-$(CONFIG_PRINTK) += printk_support.o
printk_support-y := printk_ringbuffer.o
printk_support-$(CONFIG_SYSCTL) += sysctl.o
obj-$(CONFIG_PRINTK_RINGBUFFER_KUNIT_TEST) += printk_ringbuffer_kunit_test.o
+58
View File
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/console.h>
#include <linux/errno.h>
#include <linux/string.h>
#include "console_cmdline.h"
#include "braille.h"
int _braille_console_setup(char **str, char **brl_options)
{
size_t len;
len = str_has_prefix(*str, "brl,");
if (len) {
*brl_options = "";
*str += len;
return 0;
}
len = str_has_prefix(*str, "brl=");
if (len) {
*brl_options = *str + len;
*str = strchr(*brl_options, ',');
if (!*str) {
pr_err("need port name after brl=\n");
return -EINVAL;
}
*((*str)++) = 0;
}
return 0;
}
int
_braille_register_console(struct console *console, struct console_cmdline *c)
{
int rtn = 0;
if (c->brl_options) {
console->flags |= CON_BRL;
rtn = braille_register_console(console, c->index, c->options,
c->brl_options);
}
return rtn;
}
int
_braille_unregister_console(struct console *console)
{
if (console->flags & CON_BRL)
return braille_unregister_console(console);
return 0;
}
+56
View File
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PRINTK_BRAILLE_H
#define _PRINTK_BRAILLE_H
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
static inline void
braille_set_options(struct console_cmdline *c, char *brl_options)
{
c->brl_options = brl_options;
}
/*
* Setup console according to braille options.
* Return -EINVAL on syntax error, 0 on success (or no braille option was
* actually given).
* Modifies str to point to the serial options
* Sets brl_options to the parsed braille options.
*/
int
_braille_console_setup(char **str, char **brl_options);
int
_braille_register_console(struct console *console, struct console_cmdline *c);
int
_braille_unregister_console(struct console *console);
#else
static inline void
braille_set_options(struct console_cmdline *c, char *brl_options)
{
}
static inline int
_braille_console_setup(char **str, char **brl_options)
{
return 0;
}
static inline int
_braille_register_console(struct console *console, struct console_cmdline *c)
{
return 0;
}
static inline int
_braille_unregister_console(struct console *console)
{
return 0;
}
#endif
#endif
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _CONSOLE_CMDLINE_H
#define _CONSOLE_CMDLINE_H
struct console_cmdline
{
char name[16]; /* Name of the driver */
int index; /* Minor dev. to use */
char devname[32]; /* DEVNAME:0.0 style device name */
bool user_specified; /* Specified by command line vs. platform */
char *options; /* Options for the driver */
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
char *brl_options; /* Options for braille driver */
#endif
};
#endif
+194
View File
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Userspace indexing of printk formats
*/
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/string_helpers.h>
#include "internal.h"
extern struct pi_entry *__start_printk_index[];
extern struct pi_entry *__stop_printk_index[];
/* The base dir for module formats, typically debugfs/printk/index/ */
static struct dentry *dfs_index;
static struct pi_entry *pi_get_entry(const struct module *mod, loff_t pos)
{
struct pi_entry **entries;
unsigned int nr_entries;
#ifdef CONFIG_MODULES
if (mod) {
entries = mod->printk_index_start;
nr_entries = mod->printk_index_size;
} else
#endif
{
/* vmlinux, comes from linker symbols */
entries = __start_printk_index;
nr_entries = __stop_printk_index - __start_printk_index;
}
if (pos >= nr_entries)
return NULL;
return entries[pos];
}
static void *pi_next(struct seq_file *s, void *v, loff_t *pos)
{
const struct module *mod = s->file->f_inode->i_private;
struct pi_entry *entry = pi_get_entry(mod, *pos);
(*pos)++;
return entry;
}
static void *pi_start(struct seq_file *s, loff_t *pos)
{
/*
* Make show() print the header line. Do not update *pos because
* pi_next() still has to return the entry at index 0 later.
*/
if (*pos == 0)
return SEQ_START_TOKEN;
return pi_next(s, NULL, pos);
}
/*
* We need both ESCAPE_ANY and explicit characters from ESCAPE_SPECIAL in @only
* because otherwise ESCAPE_NAP will cause double quotes and backslashes to be
* ignored for quoting.
*/
#define seq_escape_printf_format(s, src) \
seq_escape_str(s, src, ESCAPE_ANY | ESCAPE_NAP | ESCAPE_APPEND, "\"\\")
static int pi_show(struct seq_file *s, void *v)
{
const struct pi_entry *entry = v;
int level = LOGLEVEL_DEFAULT;
enum printk_info_flags flags = 0;
u16 prefix_len = 0;
if (v == SEQ_START_TOKEN) {
seq_puts(s, "# <level/flags> filename:line function \"format\"\n");
return 0;
}
if (!entry->fmt)
return 0;
if (entry->level)
printk_parse_prefix(entry->level, &level, &flags);
else
prefix_len = printk_parse_prefix(entry->fmt, &level, &flags);
if (flags & LOG_CONT) {
/*
* LOGLEVEL_DEFAULT here means "use the same level as the
* message we're continuing from", not the default message
* loglevel, so don't display it as such.
*/
if (level == LOGLEVEL_DEFAULT)
seq_puts(s, "<c>");
else
seq_printf(s, "<%d,c>", level);
} else
seq_printf(s, "<%d>", level);
seq_printf(s, " %s:%d %s \"", entry->file, entry->line, entry->func);
if (entry->subsys_fmt_prefix)
seq_escape_printf_format(s, entry->subsys_fmt_prefix);
seq_escape_printf_format(s, entry->fmt + prefix_len);
seq_puts(s, "\"\n");
return 0;
}
static void pi_stop(struct seq_file *p, void *v) { }
static const struct seq_operations dfs_index_sops = {
.start = pi_start,
.next = pi_next,
.show = pi_show,
.stop = pi_stop,
};
DEFINE_SEQ_ATTRIBUTE(dfs_index);
#ifdef CONFIG_MODULES
static const char *pi_get_module_name(struct module *mod)
{
return mod ? mod->name : "vmlinux";
}
#else
static const char *pi_get_module_name(struct module *mod)
{
return "vmlinux";
}
#endif
static void pi_create_file(struct module *mod)
{
debugfs_create_file(pi_get_module_name(mod), 0444, dfs_index,
mod, &dfs_index_fops);
}
#ifdef CONFIG_MODULES
static void pi_remove_file(struct module *mod)
{
debugfs_lookup_and_remove(pi_get_module_name(mod), dfs_index);
}
static int pi_module_notify(struct notifier_block *nb, unsigned long op,
void *data)
{
struct module *mod = data;
switch (op) {
case MODULE_STATE_COMING:
pi_create_file(mod);
break;
case MODULE_STATE_GOING:
pi_remove_file(mod);
break;
default: /* we don't care about other module states */
break;
}
return NOTIFY_OK;
}
static struct notifier_block module_printk_fmts_nb = {
.notifier_call = pi_module_notify,
};
static void __init pi_setup_module_notifier(void)
{
register_module_notifier(&module_printk_fmts_nb);
}
#else
static inline void __init pi_setup_module_notifier(void) { }
#endif
static int __init pi_init(void)
{
struct dentry *dfs_root = debugfs_create_dir("printk", NULL);
dfs_index = debugfs_create_dir("index", dfs_root);
pi_setup_module_notifier();
pi_create_file(NULL);
return 0;
}
/* debugfs comes up on core and must be initialised first */
postcore_initcall(pi_init);
+312
View File
@@ -0,0 +1,312 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* internal.h - printk internal definitions
*/
#include <linux/console.h>
#include <linux/types.h>
#include <linux/sysctl.h>
#if defined(CONFIG_PRINTK) && defined(CONFIG_SYSCTL)
void __init printk_sysctl_init(void);
int devkmsg_sysctl_set_loglvl(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos);
#else
#define printk_sysctl_init() do { } while (0)
#endif
#define con_printk(lvl, con, fmt, ...) \
printk(lvl pr_fmt("%s%sconsole [%s%d] " fmt), \
(con->flags & CON_NBCON) ? "" : "legacy ", \
(con->flags & CON_BOOT) ? "boot" : "", \
con->name, con->index, ##__VA_ARGS__)
/*
* Identify if legacy printing is forced in a dedicated kthread. If
* true, all printing via console lock occurs within a dedicated
* legacy printer thread. The only exception is on panic, after the
* nbcon consoles have had their chance to print the panic messages
* first.
*/
#ifdef CONFIG_PREEMPT_RT
# define force_legacy_kthread() (true)
#else
# define force_legacy_kthread() (false)
#endif
#ifdef CONFIG_PRINTK
#ifdef CONFIG_PRINTK_CALLER
#define PRINTK_PREFIX_MAX 48
#else
#define PRINTK_PREFIX_MAX 32
#endif
/*
* the maximum size of a formatted record (i.e. with prefix added
* per line and dropped messages or in extended message format)
*/
#define PRINTK_MESSAGE_MAX 2048
/* the maximum size allowed to be reserved for a record */
#define PRINTKRB_RECORD_MAX 1024
/* Flags for a single printk record. */
enum printk_info_flags {
/* always show on console, ignore console_loglevel */
LOG_FORCE_CON = 1,
LOG_NEWLINE = 2, /* text ended with a newline */
LOG_CONT = 8, /* text is a fragment of a continuation line */
};
struct printk_ringbuffer;
struct dev_printk_info;
extern struct printk_ringbuffer *prb;
extern bool printk_kthreads_running;
extern bool printk_kthreads_ready;
extern bool debug_non_panic_cpus;
__printf(4, 0)
int vprintk_store(int facility, int level,
const struct dev_printk_info *dev_info,
const char *fmt, va_list args);
__printf(1, 0) int vprintk_default(const char *fmt, va_list args);
void __printk_safe_enter(void);
void __printk_safe_exit(void);
bool printk_percpu_data_ready(void);
#define printk_safe_enter_irqsave(flags) \
do { \
local_irq_save(flags); \
__printk_safe_enter(); \
} while (0)
#define printk_safe_exit_irqrestore(flags) \
do { \
__printk_safe_exit(); \
local_irq_restore(flags); \
} while (0)
void defer_console_output(void);
bool is_printk_legacy_deferred(void);
bool is_printk_force_console(void);
u16 printk_parse_prefix(const char *text, int *level,
enum printk_info_flags *flags);
void console_lock_spinning_enable(void);
int console_lock_spinning_disable_and_check(int cookie);
u64 nbcon_seq_read(struct console *con);
void nbcon_seq_force(struct console *con, u64 seq);
bool nbcon_alloc(struct console *con);
void nbcon_free(struct console *con);
enum nbcon_prio nbcon_get_default_prio(void);
void nbcon_atomic_flush_pending(void);
bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
int cookie, bool use_atomic);
bool nbcon_kthread_create(struct console *con);
void nbcon_kthread_stop(struct console *con);
void nbcon_kthreads_wake(void);
/**
* nbcon_kthread_wake - Wake up a console printing thread
* @con: Console to operate on
*/
static inline void nbcon_kthread_wake(struct console *con)
{
/*
* Guarantee any new records can be seen by tasks preparing to wait
* before this context checks if the rcuwait is empty.
*
* The full memory barrier in rcuwait_wake_up() pairs with the full
* memory barrier within set_current_state() of
* ___rcuwait_wait_event(), which is called after prepare_to_rcuwait()
* adds the waiter but before it has checked the wait condition.
*
* This pairs with nbcon_kthread_func:A.
*/
rcuwait_wake_up(&con->rcuwait); /* LMM(nbcon_kthread_wake:A) */
}
#else
#define PRINTK_PREFIX_MAX 0
#define PRINTK_MESSAGE_MAX 0
#define PRINTKRB_RECORD_MAX 0
#define printk_kthreads_running (false)
#define printk_kthreads_ready (false)
/*
* In !PRINTK builds we still export console_sem
* semaphore and some of console functions (console_unlock()/etc.), so
* printk-safe must preserve the existing local IRQ guarantees.
*/
#define printk_safe_enter_irqsave(flags) local_irq_save(flags)
#define printk_safe_exit_irqrestore(flags) local_irq_restore(flags)
static inline bool printk_percpu_data_ready(void) { return false; }
static inline void defer_console_output(void) { }
static inline bool is_printk_legacy_deferred(void) { return false; }
static inline u64 nbcon_seq_read(struct console *con) { return 0; }
static inline void nbcon_seq_force(struct console *con, u64 seq) { }
static inline bool nbcon_alloc(struct console *con) { return false; }
static inline void nbcon_free(struct console *con) { }
static inline enum nbcon_prio nbcon_get_default_prio(void) { return NBCON_PRIO_NONE; }
static inline void nbcon_atomic_flush_pending(void) { }
static inline bool nbcon_legacy_emit_next_record(struct console *con, bool *handover,
int cookie, bool use_atomic) { return false; }
static inline void nbcon_kthread_wake(struct console *con) { }
static inline void nbcon_kthreads_wake(void) { }
#endif /* CONFIG_PRINTK */
extern bool have_boot_console;
extern bool have_nbcon_console;
extern bool have_legacy_console;
extern bool legacy_allow_panic_sync;
/**
* struct console_flush_type - Define available console flush methods
* @nbcon_atomic: Flush directly using nbcon_atomic() callback
* @nbcon_offload: Offload flush to printer thread
* @legacy_direct: Call the legacy loop in this context
* @legacy_offload: Offload the legacy loop into IRQ or legacy thread
*
* Note that the legacy loop also flushes the nbcon consoles.
*/
struct console_flush_type {
bool nbcon_atomic;
bool nbcon_offload;
bool legacy_direct;
bool legacy_offload;
};
extern bool console_irqwork_blocked;
/*
* Identify which console flushing methods should be used in the context of
* the caller.
*/
static inline void printk_get_console_flush_type(struct console_flush_type *ft)
{
memset(ft, 0, sizeof(*ft));
switch (nbcon_get_default_prio()) {
case NBCON_PRIO_NORMAL:
if (have_nbcon_console && !have_boot_console) {
if (printk_kthreads_running && !console_irqwork_blocked)
ft->nbcon_offload = true;
else
ft->nbcon_atomic = true;
}
/* Legacy consoles are flushed directly when possible. */
if (have_legacy_console || have_boot_console) {
if (!is_printk_legacy_deferred())
ft->legacy_direct = true;
else if (!console_irqwork_blocked)
ft->legacy_offload = true;
}
break;
case NBCON_PRIO_EMERGENCY:
if (have_nbcon_console && !have_boot_console)
ft->nbcon_atomic = true;
/* Legacy consoles are flushed directly when possible. */
if (have_legacy_console || have_boot_console) {
if (!is_printk_legacy_deferred())
ft->legacy_direct = true;
else if (!console_irqwork_blocked)
ft->legacy_offload = true;
}
break;
case NBCON_PRIO_PANIC:
/*
* In panic, the nbcon consoles will directly print. But
* only allowed if there are no boot consoles.
*/
if (have_nbcon_console && !have_boot_console)
ft->nbcon_atomic = true;
if (have_legacy_console || have_boot_console) {
/*
* This is the same decision as NBCON_PRIO_NORMAL
* except that offloading never occurs in panic.
*
* Note that console_flush_on_panic() will flush
* legacy consoles anyway, even if unsafe.
*/
if (!is_printk_legacy_deferred())
ft->legacy_direct = true;
/*
* In panic, if nbcon atomic printing occurs,
* the legacy consoles must remain silent until
* explicitly allowed.
*/
if (ft->nbcon_atomic && !legacy_allow_panic_sync)
ft->legacy_direct = false;
}
break;
default:
WARN_ON_ONCE(1);
break;
}
}
extern struct printk_buffers printk_shared_pbufs;
/**
* struct printk_buffers - Buffers to read/format/output printk messages.
* @outbuf: After formatting, contains text to output.
* @scratchbuf: Used as temporary ringbuffer reading and string-print space.
*/
struct printk_buffers {
char outbuf[PRINTK_MESSAGE_MAX];
char scratchbuf[PRINTKRB_RECORD_MAX];
};
/**
* struct printk_message - Container for a prepared printk message.
* @pbufs: printk buffers used to prepare the message.
* @outbuf_len: The length of prepared text in @pbufs->outbuf to output. This
* does not count the terminator. A value of 0 means there is
* nothing to output and this record should be skipped.
* @seq: The sequence number of the record used for @pbufs->outbuf.
* @dropped: The number of dropped records from reading @seq.
* @cpu: CPU on which the message was generated.
* @pid: PID of the task that generated the message
* @comm: Name of the task that generated the message.
*/
struct printk_message {
struct printk_buffers *pbufs;
unsigned int outbuf_len;
u64 seq;
unsigned long dropped;
#ifdef CONFIG_PRINTK_EXECUTION_CTX
int cpu;
pid_t pid;
char comm[TASK_COMM_LEN];
#endif
};
bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
bool is_extended, bool may_supress);
#ifdef CONFIG_PRINTK
void console_prepend_dropped(struct printk_message *pmsg, unsigned long dropped);
void console_prepend_replay(struct printk_message *pmsg);
#endif
#ifdef CONFIG_SMP
bool is_printk_cpu_sync_owner(void);
#else
static inline bool is_printk_cpu_sync_owner(void) { return false; }
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,442 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _KERNEL_PRINTK_RINGBUFFER_H
#define _KERNEL_PRINTK_RINGBUFFER_H
#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/dev_printk.h>
#include <linux/stddef.h>
#include <linux/types.h>
/*
* Meta information about each stored message.
*
* All fields are set by the printk code except for @seq, which is
* set by the ringbuffer code.
*/
struct printk_info {
u64 seq; /* sequence number */
u64 ts_nsec; /* timestamp in nanoseconds */
u16 text_len; /* length of text message */
u8 facility; /* syslog facility */
u8 flags:5; /* internal record flags */
u8 level:3; /* syslog level */
u32 caller_id; /* thread id or processor id */
#ifdef CONFIG_PRINTK_EXECUTION_CTX
u32 caller_id2; /* caller_id complement */
/* name of the task that generated the message */
char comm[TASK_COMM_LEN];
#endif
struct dev_printk_info dev_info;
};
/*
* A structure providing the buffers, used by writers and readers.
*
* Writers:
* Using prb_rec_init_wr(), a writer sets @text_buf_size before calling
* prb_reserve(). On success, prb_reserve() sets @info and @text_buf to
* buffers reserved for that writer.
*
* Readers:
* Using prb_rec_init_rd(), a reader sets all fields before calling
* prb_read_valid(). Note that the reader provides the @info and @text_buf,
* buffers. On success, the struct pointed to by @info will be filled and
* the char array pointed to by @text_buf will be filled with text data.
*/
struct printk_record {
struct printk_info *info;
char *text_buf;
unsigned int text_buf_size;
};
/* Specifies the logical position and span of a data block. */
struct prb_data_blk_lpos {
unsigned long begin;
unsigned long next;
};
/*
* A descriptor: the complete meta-data for a record.
*
* @state_var: A bitwise combination of descriptor ID and descriptor state.
*/
struct prb_desc {
atomic_long_t state_var;
struct prb_data_blk_lpos text_blk_lpos;
};
/* A ringbuffer of "ID + data" elements. */
struct prb_data_ring {
unsigned int size_bits;
char *data;
atomic_long_t head_lpos;
atomic_long_t tail_lpos;
};
/* A ringbuffer of "struct prb_desc" elements. */
struct prb_desc_ring {
unsigned int count_bits;
struct prb_desc *descs;
struct printk_info *infos;
atomic_long_t head_id;
atomic_long_t tail_id;
atomic_long_t last_finalized_seq;
};
/*
* The high level structure representing the printk ringbuffer.
*
* @fail: Count of failed prb_reserve() calls where not even a data-less
* record was created.
*/
struct printk_ringbuffer {
struct prb_desc_ring desc_ring;
struct prb_data_ring text_data_ring;
atomic_long_t fail;
};
/*
* Used by writers as a reserve/commit handle.
*
* @rb: Ringbuffer where the entry is reserved.
* @irqflags: Saved irq flags to restore on entry commit.
* @id: ID of the reserved descriptor.
* @text_space: Total occupied buffer space in the text data ring, including
* ID, alignment padding, and wrapping data blocks.
*
* This structure is an opaque handle for writers. Its contents are only
* to be used by the ringbuffer implementation.
*/
struct prb_reserved_entry {
struct printk_ringbuffer *rb;
unsigned long irqflags;
unsigned long id;
unsigned int text_space;
};
/* 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 _DATA_SIZE(sz_bits) (1UL << (sz_bits))
#define _DESCS_COUNT(ct_bits) (1UL << (ct_bits))
#define DESC_SV_BITS BITS_PER_LONG
#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_SV(id, state) (((unsigned long)state << DESC_FLAGS_SHIFT) | id)
#define DESC_ID_MASK (~DESC_FLAGS_MASK)
#define DESC_ID(sv) ((sv) & DESC_ID_MASK)
/*
* Special data block logical position values (for fields of
* @prb_desc.text_blk_lpos).
*
* - Bit0 is used to identify if the record has no data block. (Implemented in
* the LPOS_DATALESS() macro.)
*
* - Bit1 specifies the reason for not having a data block.
*
* These special values could never be real lpos values because of the
* meta data and alignment padding of data blocks. (See to_blk_size() for
* details.)
*/
#define FAILED_LPOS 0x1
#define EMPTY_LINE_LPOS 0x3
#define FAILED_BLK_LPOS \
{ \
.begin = FAILED_LPOS, \
.next = FAILED_LPOS, \
}
/*
* Descriptor Bootstrap
*
* The descriptor array is minimally initialized to allow immediate usage
* by readers and writers. The requirements that the descriptor array
* initialization must satisfy:
*
* Req1
* The tail must point to an existing (committed or reusable) descriptor.
* This is required by the implementation of prb_first_seq().
*
* Req2
* Readers must see that the ringbuffer is initially empty.
*
* Req3
* The first record reserved by a writer is assigned sequence number 0.
*
* To satisfy Req1, the tail initially points to a descriptor that is
* minimally initialized (having no data block, i.e. data-less with the
* data block's lpos @begin and @next values set to FAILED_LPOS).
*
* To satisfy Req2, the initial tail descriptor is initialized to the
* reusable state. Readers recognize reusable descriptors as existing
* records, but skip over them.
*
* To satisfy Req3, the last descriptor in the array is used as the initial
* head (and tail) descriptor. This allows the first record reserved by a
* writer (head + 1) to be the first descriptor in the array. (Only the first
* descriptor in the array could have a valid sequence number of 0.)
*
* The first time a descriptor is reserved, it is assigned a sequence number
* with the value of the array index. A "first time reserved" descriptor can
* be recognized because it has a sequence number of 0 but does not have an
* index of 0. (Only the first descriptor in the array could have a valid
* sequence number of 0.) After the first reservation, all future reservations
* (recycling) simply involve incrementing the sequence number by the array
* count.
*
* Hack #1
* Only the first descriptor in the array is allowed to have the sequence
* number 0. In this case it is not possible to recognize if it is being
* reserved the first time (set to index value) or has been reserved
* previously (increment by the array count). This is handled by _always_
* incrementing the sequence number by the array count when reserving the
* first descriptor in the array. In order to satisfy Req3, the sequence
* number of the first descriptor in the array is initialized to minus
* the array count. Then, upon the first reservation, it is incremented
* to 0, thus satisfying Req3.
*
* Hack #2
* prb_first_seq() can be called at any time by readers to retrieve the
* sequence number of the tail descriptor. However, due to Req2 and Req3,
* initially there are no records to report the sequence number of
* (sequence numbers are u64 and there is nothing less than 0). To handle
* this, the sequence number of the initial tail descriptor is initialized
* to 0. Technically this is incorrect, because there is no record with
* sequence number 0 (yet) and the tail descriptor is not the first
* descriptor in the array. But it allows prb_read_valid() to correctly
* report the existence of a record for _any_ given sequence number at all
* times. Bootstrapping is complete when the tail is pushed the first
* time, thus finally pointing to the first descriptor reserved by a
* writer, which has the assigned sequence number 0.
*/
/*
* Initiating Logical Value Overflows
*
* Both logical position (lpos) and ID values can be mapped to array indexes
* but may experience overflows during the lifetime of the system. To ensure
* that printk_ringbuffer can handle the overflows for these types, initial
* values are chosen that map to the correct initial array indexes, but will
* result in overflows soon.
*
* BLK0_LPOS
* The initial @head_lpos and @tail_lpos for data rings. It is at index
* 0 and the lpos value is such that it will overflow on the first wrap.
*
* DESC0_ID
* The initial @head_id and @tail_id for the desc ring. It is at the last
* index of the descriptor array (see Req3 above) and the ID value is such
* that it will overflow on the second wrap.
*/
#define BLK0_LPOS(sz_bits) (-(_DATA_SIZE(sz_bits)))
#define DESC0_ID(ct_bits) DESC_ID(-(_DESCS_COUNT(ct_bits) + 1))
#define DESC0_SV(ct_bits) DESC_SV(DESC0_ID(ct_bits), desc_reusable)
/*
* Define a ringbuffer with an external text data buffer. The same as
* DEFINE_PRINTKRB() but requires specifying an external buffer for the
* text data.
*
* Note: The specified external buffer must be of the size:
* 2 ^ (descbits + avgtextbits)
*/
#define _DEFINE_PRINTKRB(name, descbits, avgtextbits, text_buf) \
static struct prb_desc _##name##_descs[_DESCS_COUNT(descbits)] = { \
/* the initial head and tail */ \
[_DESCS_COUNT(descbits) - 1] = { \
/* reusable */ \
.state_var = ATOMIC_INIT(DESC0_SV(descbits)), \
/* no associated data block */ \
.text_blk_lpos = FAILED_BLK_LPOS, \
}, \
}; \
static struct printk_info _##name##_infos[_DESCS_COUNT(descbits)] = { \
/* this will be the first record reserved by a writer */ \
[0] = { \
/* will be incremented to 0 on the first reservation */ \
.seq = -(u64)_DESCS_COUNT(descbits), \
}, \
/* the initial head and tail */ \
[_DESCS_COUNT(descbits) - 1] = { \
/* reports the first seq value during the bootstrap phase */ \
.seq = 0, \
}, \
}; \
static struct printk_ringbuffer name = { \
.desc_ring = { \
.count_bits = descbits, \
.descs = &_##name##_descs[0], \
.infos = &_##name##_infos[0], \
.head_id = ATOMIC_INIT(DESC0_ID(descbits)), \
.tail_id = ATOMIC_INIT(DESC0_ID(descbits)), \
.last_finalized_seq = ATOMIC_INIT(0), \
}, \
.text_data_ring = { \
.size_bits = (avgtextbits) + (descbits), \
.data = text_buf, \
.head_lpos = ATOMIC_LONG_INIT(BLK0_LPOS((avgtextbits) + (descbits))), \
.tail_lpos = ATOMIC_LONG_INIT(BLK0_LPOS((avgtextbits) + (descbits))), \
}, \
.fail = ATOMIC_LONG_INIT(0), \
}
/**
* DEFINE_PRINTKRB() - Define a ringbuffer.
*
* @name: The name of the ringbuffer variable.
* @descbits: The number of descriptors as a power-of-2 value.
* @avgtextbits: The average text data size per record as a power-of-2 value.
*
* This is a macro for defining a ringbuffer and all internal structures
* such that it is ready for immediate use. See _DEFINE_PRINTKRB() for a
* variant where the text data buffer can be specified externally.
*/
#define DEFINE_PRINTKRB(name, descbits, avgtextbits) \
static char _##name##_text[1U << ((avgtextbits) + (descbits))] \
__aligned(__alignof__(unsigned long)); \
_DEFINE_PRINTKRB(name, descbits, avgtextbits, &_##name##_text[0])
/* Writer Interface */
/**
* prb_rec_init_wr() - Initialize a buffer for writing records.
*
* @r: The record to initialize.
* @text_buf_size: The needed text buffer size.
*/
static inline void prb_rec_init_wr(struct printk_record *r,
unsigned int text_buf_size)
{
r->info = NULL;
r->text_buf = NULL;
r->text_buf_size = text_buf_size;
}
bool prb_reserve(struct prb_reserved_entry *e, struct printk_ringbuffer *rb,
struct printk_record *r);
bool prb_reserve_in_last(struct prb_reserved_entry *e, struct printk_ringbuffer *rb,
struct printk_record *r, u32 caller_id, unsigned int max_size);
void prb_commit(struct prb_reserved_entry *e);
void prb_final_commit(struct prb_reserved_entry *e);
void prb_init(struct printk_ringbuffer *rb,
char *text_buf, unsigned int text_buf_size,
struct prb_desc *descs, unsigned int descs_count_bits,
struct printk_info *infos);
unsigned int prb_record_text_space(struct prb_reserved_entry *e);
/* Reader Interface */
/**
* prb_rec_init_rd() - Initialize a buffer for reading records.
*
* @r: The record to initialize.
* @info: A buffer to store record meta-data.
* @text_buf: A buffer to store text data.
* @text_buf_size: The size of @text_buf.
*
* Initialize all the fields that a reader is interested in. All arguments
* (except @r) are optional. Only record data for arguments that are
* non-NULL or non-zero will be read.
*/
static inline void prb_rec_init_rd(struct printk_record *r,
struct printk_info *info,
char *text_buf, unsigned int text_buf_size)
{
r->info = info;
r->text_buf = text_buf;
r->text_buf_size = text_buf_size;
}
/**
* prb_for_each_record() - Iterate over the records of a ringbuffer.
*
* @from: The sequence number to begin with.
* @rb: The ringbuffer to iterate over.
* @s: A u64 to store the sequence number on each iteration.
* @r: A printk_record to store the record on each iteration.
*
* This is a macro for conveniently iterating over a ringbuffer.
* Note that @s may not be the sequence number of the record on each
* iteration. For the sequence number, @r->info->seq should be checked.
*
* Context: Any context.
*/
#define prb_for_each_record(from, rb, s, r) \
for ((s) = from; prb_read_valid(rb, s, r); (s) = (r)->info->seq + 1)
/**
* prb_for_each_info() - Iterate over the meta data of a ringbuffer.
*
* @from: The sequence number to begin with.
* @rb: The ringbuffer to iterate over.
* @s: A u64 to store the sequence number on each iteration.
* @i: A printk_info to store the record meta data on each iteration.
* @lc: An unsigned int to store the text line count of each record.
*
* This is a macro for conveniently iterating over a ringbuffer.
* Note that @s may not be the sequence number of the record on each
* iteration. For the sequence number, @i->seq should be checked.
*
* Context: Any context.
*/
#define prb_for_each_info(from, rb, s, i, lc) \
for ((s) = from; prb_read_valid_info(rb, s, i, lc); (s) = (i)->seq + 1)
bool prb_read_valid(struct printk_ringbuffer *rb, u64 seq,
struct printk_record *r);
bool prb_read_valid_info(struct printk_ringbuffer *rb, u64 seq,
struct printk_info *info, unsigned int *line_count);
u64 prb_first_seq(struct printk_ringbuffer *rb);
u64 prb_first_valid_seq(struct printk_ringbuffer *rb);
u64 prb_next_seq(struct printk_ringbuffer *rb);
u64 prb_next_reserve_seq(struct printk_ringbuffer *rb);
#ifdef CONFIG_64BIT
#define __u64seq_to_ulseq(u64seq) (u64seq)
#define __ulseq_to_u64seq(rb, ulseq) (ulseq)
#define ULSEQ_MAX(rb) (-1)
#else /* CONFIG_64BIT */
#define __u64seq_to_ulseq(u64seq) ((u32)u64seq)
#define ULSEQ_MAX(rb) __u64seq_to_ulseq(prb_first_seq(rb) + 0x80000000UL)
static inline u64 __ulseq_to_u64seq(struct printk_ringbuffer *rb, u32 ulseq)
{
u64 rb_first_seq = prb_first_seq(rb);
u64 seq;
/*
* The provided sequence is only the lower 32 bits of the ringbuffer
* sequence. It needs to be expanded to 64bit. Get the first sequence
* number from the ringbuffer and fold it.
*
* Having a 32bit representation in the console is sufficient.
* If a console ever gets more than 2^31 records behind
* the ringbuffer then this is the least of the problems.
*
* Also the access to the ring buffer is always safe.
*/
seq = rb_first_seq - (s32)((u32)rb_first_seq - ulseq);
return seq;
}
#endif /* CONFIG_64BIT */
#endif /* _KERNEL_PRINTK_RINGBUFFER_H */
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/cpuhplock.h>
#include <linux/cpumask.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <kunit/resource.h>
#include <kunit/test.h>
#include "printk_ringbuffer.h"
/*
* This KUnit tests the data integrity of the lockless printk_ringbuffer.
* From multiple CPUs it writes messages of varying length and content while
* a reader validates the correctness of the messages.
*
* IMPORTANT: The more CPUs you can use for this KUnit, the better!
*
* The test works by starting "num_online_cpus() - 1" writer threads, each
* pinned to their own CPU. Each writer thread loops, writing data of varying
* length into a printk_ringbuffer as fast as possible. The data content is
* an embedded data struct followed by string content repeating the byte:
*
* 'A' + CPUID
*
* The reader is running on the remaining online CPU, or if there is only one
* CPU on the same as the writer.
* It ensures that the embedded struct content is consistent with the string
* and that the string * is terminated and is composed of the same repeating
* byte as its first byte.
*
* Because the threads are running in such tight loops, they will call
* cond_resched() from time to time so the system stays functional.
*
* If the reader encounters an error, the test is aborted and some
* information about the error is reported.
* The runtime of the test can be configured with the runtime_ms module parameter.
*
* Note that the test is performed on a separate printk_ringbuffer instance
* and not the instance used by printk().
*/
static unsigned long runtime_ms = 10 * MSEC_PER_SEC;
module_param(runtime_ms, ulong, 0400);
/* test data structure */
struct prbtest_rbdata {
unsigned int size;
char text[] __counted_by(size);
};
#define MAX_RBDATA_TEXT_SIZE 0x80
#define MAX_PRB_RECORD_SIZE (sizeof(struct prbtest_rbdata) + MAX_RBDATA_TEXT_SIZE)
struct prbtest_data {
struct kunit *test;
struct printk_ringbuffer *ringbuffer;
/* used by writers to signal reader of new records */
wait_queue_head_t new_record_wait;
};
struct prbtest_thread_data {
unsigned long num;
struct prbtest_data *test_data;
};
static void prbtest_fail_record(struct kunit *test, const struct prbtest_rbdata *dat, u64 seq)
{
unsigned int len;
len = dat->size - 1;
KUNIT_FAIL(test, "BAD RECORD: seq=%llu size=%u text=%.*s\n",
seq, dat->size,
len < MAX_RBDATA_TEXT_SIZE ? len : -1,
len < MAX_RBDATA_TEXT_SIZE ? dat->text : "<invalid>");
}
static bool prbtest_check_data(const struct prbtest_rbdata *dat)
{
unsigned int len;
/* Sane size? At least one character + trailing '\0' */
if (dat->size < 2 || dat->size > MAX_RBDATA_TEXT_SIZE)
return false;
len = dat->size - 1;
if (dat->text[len] != '\0')
return false;
/* String repeats with the same character? */
while (len--) {
if (dat->text[len] != dat->text[0])
return false;
}
return true;
}
static int prbtest_writer(void *data)
{
struct prbtest_thread_data *tr = data;
char text_id = 'A' + tr->num;
struct prb_reserved_entry e;
struct prbtest_rbdata *dat;
u32 record_size, text_size;
unsigned long count = 0;
struct printk_record r;
kunit_info(tr->test_data->test, "start thread %03lu (writer)\n", tr->num);
for (;;) {
/* ensure at least 1 character + trailing '\0' */
text_size = get_random_u32_inclusive(2, MAX_RBDATA_TEXT_SIZE);
if (WARN_ON_ONCE(text_size < 2))
text_size = 2;
if (WARN_ON_ONCE(text_size > MAX_RBDATA_TEXT_SIZE))
text_size = MAX_RBDATA_TEXT_SIZE;
record_size = sizeof(struct prbtest_rbdata) + text_size;
WARN_ON_ONCE(record_size > MAX_PRB_RECORD_SIZE);
/* specify the text sizes for reservation */
prb_rec_init_wr(&r, record_size);
/*
* Reservation can fail if:
*
* - No free descriptor is available.
* - The buffer is full, and the oldest record is reserved
* but not yet committed.
*
* It actually happens in this test because all CPUs are trying
* to write an unbounded number of messages in a tight loop.
* These failures are intentionally ignored because this test
* focuses on races, ringbuffer consistency, and pushing system
* usability limits.
*/
if (prb_reserve(&e, tr->test_data->ringbuffer, &r)) {
r.info->text_len = record_size;
dat = (struct prbtest_rbdata *)r.text_buf;
dat->size = text_size;
memset(dat->text, text_id, text_size - 1);
dat->text[text_size - 1] = '\0';
prb_commit(&e);
wake_up_interruptible(&tr->test_data->new_record_wait);
}
if ((count++ & 0x3fff) == 0)
cond_resched();
if (kthread_should_stop())
break;
}
kunit_info(tr->test_data->test, "end thread %03lu: wrote=%lu\n", tr->num, count);
return 0;
}
struct prbtest_wakeup_timer {
struct timer_list timer;
struct task_struct *task;
};
static void prbtest_wakeup_callback(struct timer_list *timer)
{
struct prbtest_wakeup_timer *wakeup = timer_container_of(wakeup, timer, timer);
set_tsk_thread_flag(wakeup->task, TIF_NOTIFY_SIGNAL);
wake_up_process(wakeup->task);
}
static int prbtest_reader(struct prbtest_data *test_data, unsigned long timeout_ms)
{
struct prbtest_wakeup_timer wakeup;
char text_buf[MAX_PRB_RECORD_SIZE];
unsigned long count = 0;
struct printk_info info;
struct printk_record r;
u64 seq = 0;
wakeup.task = current;
timer_setup_on_stack(&wakeup.timer, prbtest_wakeup_callback, 0);
mod_timer(&wakeup.timer, jiffies + msecs_to_jiffies(timeout_ms));
prb_rec_init_rd(&r, &info, text_buf, sizeof(text_buf));
kunit_info(test_data->test, "start reader\n");
while (!wait_event_interruptible(test_data->new_record_wait,
prb_read_valid(test_data->ringbuffer, seq, &r))) {
/* check/track the sequence */
if (info.seq < seq)
KUNIT_FAIL(test_data->test, "BAD SEQ READ: request=%llu read=%llu\n",
seq, info.seq);
if (!prbtest_check_data((struct prbtest_rbdata *)r.text_buf))
prbtest_fail_record(test_data->test,
(struct prbtest_rbdata *)r.text_buf, info.seq);
if ((count++ & 0x3fff) == 0)
cond_resched();
seq = info.seq + 1;
}
timer_delete_sync(&wakeup.timer);
timer_destroy_on_stack(&wakeup.timer);
kunit_info(test_data->test, "end reader: read=%lu seq=%llu\n", count, info.seq);
return 0;
}
KUNIT_DEFINE_ACTION_WRAPPER(prbtest_cpumask_cleanup, free_cpumask_var, struct cpumask *);
KUNIT_DEFINE_ACTION_WRAPPER(prbtest_kthread_cleanup, kthread_stop, struct task_struct *);
static void prbtest_add_cpumask_cleanup(struct kunit *test, cpumask_var_t mask)
{
int err;
err = kunit_add_action_or_reset(test, prbtest_cpumask_cleanup, mask);
KUNIT_ASSERT_EQ(test, err, 0);
}
static void prbtest_add_kthread_cleanup(struct kunit *test, struct task_struct *kthread)
{
int err;
err = kunit_add_action_or_reset(test, prbtest_kthread_cleanup, kthread);
KUNIT_ASSERT_EQ(test, err, 0);
}
static inline void prbtest_prb_reinit(struct printk_ringbuffer *rb)
{
prb_init(rb, rb->text_data_ring.data, rb->text_data_ring.size_bits, rb->desc_ring.descs,
rb->desc_ring.count_bits, rb->desc_ring.infos);
}
static void test_readerwriter(struct kunit *test)
{
/* Equivalent to CONFIG_LOG_BUF_SHIFT=13 */
DEFINE_PRINTKRB(test_rb, 8, 5);
struct prbtest_thread_data *thread_data;
struct prbtest_data *test_data;
struct task_struct *thread;
cpumask_var_t test_cpus;
int cpu, reader_cpu;
KUNIT_ASSERT_TRUE(test, alloc_cpumask_var(&test_cpus, GFP_KERNEL));
prbtest_add_cpumask_cleanup(test, test_cpus);
cpus_read_lock();
/*
* Failure of KUNIT_ASSERT() kills the current task
* so it can not be called while the CPU hotplug lock is held.
* Instead use a snapshot of the online CPUs.
* If they change during test execution it is unfortunate but not a grave error.
*/
cpumask_copy(test_cpus, cpu_online_mask);
cpus_read_unlock();
/* One CPU is for the reader, all others are writers */
reader_cpu = cpumask_first(test_cpus);
if (cpumask_weight(test_cpus) == 1)
kunit_warn(test, "more than one CPU is recommended");
else
cpumask_clear_cpu(reader_cpu, test_cpus);
/* KUnit test can get restarted more times. */
prbtest_prb_reinit(&test_rb);
test_data = kunit_kmalloc(test, sizeof(*test_data), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, test_data);
test_data->test = test;
test_data->ringbuffer = &test_rb;
init_waitqueue_head(&test_data->new_record_wait);
kunit_info(test, "running for %lu ms\n", runtime_ms);
for_each_cpu(cpu, test_cpus) {
thread_data = kunit_kmalloc(test, sizeof(*thread_data), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, thread_data);
thread_data->test_data = test_data;
thread_data->num = cpu;
thread = kthread_run_on_cpu(prbtest_writer, thread_data, cpu,
"prbtest writer %u");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, thread);
prbtest_add_kthread_cleanup(test, thread);
}
kunit_info(test, "starting test\n");
set_cpus_allowed_ptr(current, cpumask_of(reader_cpu));
prbtest_reader(test_data, runtime_ms);
kunit_info(test, "completed test\n");
}
static struct kunit_case prb_test_cases[] = {
KUNIT_CASE_SLOW(test_readerwriter),
{}
};
static struct kunit_suite prb_test_suite = {
.name = "printk-ringbuffer",
.test_cases = prb_test_cases,
};
kunit_test_suite(prb_test_suite);
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
MODULE_DESCRIPTION("printk_ringbuffer KUnit test");
MODULE_LICENSE("GPL");
+84
View File
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* printk_safe.c - Safe printk for printk-deadlock-prone contexts
*/
#include <linux/preempt.h>
#include <linux/kdb.h>
#include <linux/smp.h>
#include <linux/cpumask.h>
#include <linux/printk.h>
#include <linux/kprobes.h>
#include "internal.h"
/* Context where printk messages are never suppressed */
static atomic_t force_con;
void printk_force_console_enter(void)
{
atomic_inc(&force_con);
}
void printk_force_console_exit(void)
{
atomic_dec(&force_con);
}
bool is_printk_force_console(void)
{
return atomic_read(&force_con);
}
static DEFINE_PER_CPU(int, printk_context);
/* Can be preempted by NMI. */
void __printk_safe_enter(void)
{
this_cpu_inc(printk_context);
}
/* Can be preempted by NMI. */
void __printk_safe_exit(void)
{
this_cpu_dec(printk_context);
}
void __printk_deferred_enter(void)
{
cant_migrate();
__printk_safe_enter();
}
void __printk_deferred_exit(void)
{
cant_migrate();
__printk_safe_exit();
}
bool is_printk_legacy_deferred(void)
{
/*
* The per-CPU variable @printk_context can be read safely in any
* context. CPU migration is always disabled when set.
*
* A context holding the printk_cpu_sync must not spin waiting for
* another CPU. For legacy printing, it could be the console_lock
* or the port lock.
*/
return (force_legacy_kthread() ||
this_cpu_read(printk_context) ||
in_nmi() ||
is_printk_cpu_sync_owner());
}
asmlinkage int vprintk(const char *fmt, va_list args)
{
#ifdef CONFIG_KGDB_KDB
/* Allow to pass printk() to kdb but avoid a recursion. */
if (unlikely(kdb_trap_printk && kdb_printf_cpu < 0))
return vkdb_printf(KDB_MSGSRC_PRINTK, fmt, args);
#endif
return vprintk_default(fmt, args);
}
EXPORT_SYMBOL(vprintk);
+83
View File
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sysctl.c: General linux system control interface
*/
#include <linux/printk.h>
#include <linux/capability.h>
#include <linux/ratelimit.h>
#include "internal.h"
static const int ten_thousand = 10000;
static int proc_dointvec_minmax_sysadmin(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
if (write && !capable(CAP_SYS_ADMIN))
return -EPERM;
return proc_dointvec_minmax(table, write, buffer, lenp, ppos);
}
static const struct ctl_table printk_sysctls[] = {
{
.procname = "printk",
.data = &console_loglevel,
.maxlen = 4*sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "printk_ratelimit",
.data = &printk_ratelimit_state.interval,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
{
.procname = "printk_ratelimit_burst",
.data = &printk_ratelimit_state.burst,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "printk_delay",
.data = &printk_delay_msec,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = (void *)&ten_thousand,
},
{
.procname = "printk_devkmsg",
.data = devkmsg_log_str,
.maxlen = DEVKMSG_STR_MAX_SIZE,
.mode = 0644,
.proc_handler = devkmsg_sysctl_set_loglvl,
},
{
.procname = "dmesg_restrict",
.data = &dmesg_restrict,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax_sysadmin,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "kptr_restrict",
.data = &kptr_restrict,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax_sysadmin,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_TWO,
},
};
void __init printk_sysctl_init(void)
{
register_sysctl_init("kernel", printk_sysctls);
}