summaryrefslogtreecommitdiff
path: root/lib/sys_info.c
blob: 5bf503fd7ec14b31ffba0738a0e907140a5c0eed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/sched/debug.h>
#include <linux/console.h>
#include <linux/kernel.h>
#include <linux/ftrace.h>
#include <linux/sysctl.h>
#include <linux/nmi.h>

#include <linux/sys_info.h>

struct sys_info_name {
	unsigned long bit;
	const char *name;
};

/*
 * When 'si_names' gets updated,  please make sure the 'sys_info_avail'
 * below is updated accordingly.
 */
static const struct sys_info_name  si_names[] = {
	{ SYS_INFO_TASKS,		"tasks" },
	{ SYS_INFO_MEM,			"mem" },
	{ SYS_INFO_TIMERS,		"timers" },
	{ SYS_INFO_LOCKS,		"locks" },
	{ SYS_INFO_FTRACE,		"ftrace" },
	{ SYS_INFO_ALL_CPU_BT,		"all_bt" },
	{ SYS_INFO_BLOCKED_TASKS,	"blocked_tasks" },
};

/* Expecting string like "xxx_sys_info=tasks,mem,timers,locks,ftrace,..." */
unsigned long sys_info_parse_param(char *str)
{
	unsigned long si_bits = 0;
	char *s, *name;
	int i;

	s = str;
	while ((name = strsep(&s, ",")) && *name) {
		for (i = 0; i < ARRAY_SIZE(si_names); i++) {
			if (!strcmp(name, si_names[i].name)) {
				si_bits |= si_names[i].bit;
				break;
			}
		}
	}

	return si_bits;
}

#ifdef CONFIG_SYSCTL

static const char sys_info_avail[] __maybe_unused = "tasks,mem,timers,locks,ftrace,all_bt,blocked_tasks";

int sysctl_sys_info_handler(const struct ctl_table *ro_table, int write,
					  void *buffer, size_t *lenp,
					  loff_t *ppos)
{
	char names[sizeof(sys_info_avail) + 1];
	struct ctl_table table;
	unsigned long *si_bits_global;

	si_bits_global = ro_table->data;

	if (write) {
		unsigned long si_bits;
		int ret;

		table = *ro_table;
		table.data = names;
		table.maxlen = sizeof(names);
		ret = proc_dostring(&table, write, buffer, lenp, ppos);
		if (ret)
			return ret;

		si_bits = sys_info_parse_param(names);
		/* The access to the global value is not synchronized. */
		WRITE_ONCE(*si_bits_global, si_bits);
		return 0;
	} else {
		/* for 'read' operation */
		char *delim = "";
		int i, len = 0;

		for (i = 0; i < ARRAY_SIZE(si_names); i++) {
			if (*si_bits_global & si_names[i].bit) {
				len += scnprintf(names + len, sizeof(names) - len,
					"%s%s", delim, si_names[i].name);
				delim = ",";
			}
		}

		table = *ro_table;
		table.data = names;
		table.maxlen = sizeof(names);
		return proc_dostring(&table, write, buffer, lenp, ppos);
	}
}
#endif

void sys_info(unsigned long si_mask)
{
	if (si_mask & SYS_INFO_TASKS)
		show_state();

	if (si_mask & SYS_INFO_MEM)
		show_mem();

	if (si_mask & SYS_INFO_TIMERS)
		sysrq_timer_list_show();

	if (si_mask & SYS_INFO_LOCKS)
		debug_show_all_locks();

	if (si_mask & SYS_INFO_FTRACE)
		ftrace_dump(DUMP_ALL);

	if (si_mask & SYS_INFO_ALL_CPU_BT)
		trigger_all_cpu_backtrace();

	if (si_mask & SYS_INFO_BLOCKED_TASKS)
		show_state_filter(TASK_UNINTERRUPTIBLE);
}