-
Notifications
You must be signed in to change notification settings - Fork 0
/
sconsole.c
141 lines (115 loc) · 3.2 KB
/
sconsole.c
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Author: Marcos Paulo de Souza <marcos@mpdesouza.com>
*
* Simple console implementation for experimenting legacy and NBCON variants
* (TODO).
*
* For testing, load the sconsole module, and read from file /dev/sconsole after
* generating information o /dev/kmsg (dmesg). Take a look into your current
* loglevel otherwise you might end up not recording the information.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/console.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/slab.h>
struct scon_msg {
struct list_head list;
size_t len;
char msg[] __counted_by(len);
};
/* List of all collected messages from printk */
static LIST_HEAD(msgs);
static ssize_t scon_read(struct file *f, char __user *buf, size_t count,
loff_t *fpos)
{
struct scon_msg *smsg;
if (list_empty(&msgs))
return 0;
/* Get the first scon_msg from the list */
smsg = list_first_entry(&msgs, struct scon_msg, list);
/* only read the first message */
if (count > smsg->len)
count = smsg->len;
if (copy_to_user(buf, smsg->msg, count))
return -ERESTARTSYS;
/* Only remove the record if it was copied to userspace correctly */
list_del(&smsg->list);
kfree(smsg);
return count;
}
static void write_msg(struct console *con, const char *msg, unsigned int len)
{
/*
* We need to allocate memory enough for the message, and use
* GFP_ATOMIC since we can't sleep when begin called by the console
* write callpath.
*/
struct scon_msg *smsg = kzalloc(sizeof(struct scon_msg) + sizeof(char) + len + 1,
GFP_ATOMIC);
if (!smsg) {
pr_err("failed to allocate message!\n");
return;
}
smsg->len = len + 1;
strscpy(smsg->msg, msg, len);
/*
* FIXME: To make cat command to print messages in different lines... maybe
* unnecessary?
*/
smsg->msg[len - 1] = '\n';
/* Always add new messages to the end of the messages list */
list_add_tail(&smsg->list, &msgs);
}
static struct file_operations scon_fops = {
.read = scon_read,
};
static struct miscdevice scon_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "sconsole",
.mode = S_IRUGO,
.fops = &scon_fops,
};
static struct console scon = {
.name = "simple_console",
/* Print all messages since boot */
.flags = CON_ENABLED | CON_PRINTBUFFER,
.write = write_msg,
};
static int sconsole_init(void)
{
int ret;
/*
* Register the miscdevice that will be responsible for reading the
* console messages.
*/
ret = misc_register(&scon_misc);
if (ret)
return ret;
/* Registers simple console so kernel can start writing messages to it. */
register_console(&scon);
return 0;
}
static void sconsole_exit(void)
{
struct scon_msg *cur, *tmp;
/* Stop receiving new messages */
unregister_console(&scon);
/*
* Stop receiving new userspace requests to read and delete entries from
* msgs list.
*/
misc_deregister(&scon_misc);
/* Remove all memory allocated to the entries not read */
list_for_each_entry_safe(cur, tmp, &msgs, list) {
list_del(&cur->list);
kfree(cur);
}
}
module_init(sconsole_init);
module_exit(sconsole_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marcos Paulo de Souza <marcos@mpdesouza.com>");
MODULE_DESCRIPTION("Simple console module");