Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/acpi/apei/erst-dbg.c
29278 views
1
// SPDX-License-Identifier: GPL-2.0-only
2
/*
3
* APEI Error Record Serialization Table debug support
4
*
5
* ERST is a way provided by APEI to save and retrieve hardware error
6
* information to and from a persistent store. This file provide the
7
* debugging/testing support for ERST kernel support and firmware
8
* implementation.
9
*
10
* Copyright 2010 Intel Corp.
11
* Author: Huang Ying <[email protected]>
12
*/
13
14
#include <linux/kernel.h>
15
#include <linux/module.h>
16
#include <linux/uaccess.h>
17
#include <acpi/apei.h>
18
#include <linux/miscdevice.h>
19
20
#include "apei-internal.h"
21
22
#define ERST_DBG_PFX "ERST DBG: "
23
24
#define ERST_DBG_RECORD_LEN_MAX 0x4000
25
26
static void *erst_dbg_buf;
27
static unsigned int erst_dbg_buf_len;
28
29
/* Prevent erst_dbg_read/write from being invoked concurrently */
30
static DEFINE_MUTEX(erst_dbg_mutex);
31
32
static int erst_dbg_open(struct inode *inode, struct file *file)
33
{
34
int rc, *pos;
35
36
if (erst_disable)
37
return -ENODEV;
38
39
pos = (int *)&file->private_data;
40
41
rc = erst_get_record_id_begin(pos);
42
if (rc)
43
return rc;
44
45
return nonseekable_open(inode, file);
46
}
47
48
static int erst_dbg_release(struct inode *inode, struct file *file)
49
{
50
erst_get_record_id_end();
51
52
return 0;
53
}
54
55
static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
56
{
57
int rc;
58
u64 record_id;
59
u32 record_count;
60
61
switch (cmd) {
62
case APEI_ERST_CLEAR_RECORD:
63
if (copy_from_user(&record_id, (void __user *)arg,
64
sizeof(record_id)))
65
return -EFAULT;
66
return erst_clear(record_id);
67
case APEI_ERST_GET_RECORD_COUNT:
68
rc = erst_get_record_count();
69
if (rc < 0)
70
return rc;
71
record_count = rc;
72
rc = put_user(record_count, (u32 __user *)arg);
73
if (rc)
74
return rc;
75
return 0;
76
default:
77
return -ENOTTY;
78
}
79
}
80
81
static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
82
size_t usize, loff_t *off)
83
{
84
int rc, *pos;
85
ssize_t len = 0;
86
u64 id;
87
88
if (*off)
89
return -EINVAL;
90
91
if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
92
return -EINTR;
93
94
pos = (int *)&filp->private_data;
95
96
retry_next:
97
rc = erst_get_record_id_next(pos, &id);
98
if (rc)
99
goto out;
100
/* no more record */
101
if (id == APEI_ERST_INVALID_RECORD_ID) {
102
/*
103
* If the persistent store is empty initially, the function
104
* 'erst_read' below will return "-ENOENT" value. This causes
105
* 'retry_next' label is entered again. The returned value
106
* should be zero indicating the read operation is EOF.
107
*/
108
len = 0;
109
110
goto out;
111
}
112
retry:
113
rc = len = erst_read_record(id, erst_dbg_buf, erst_dbg_buf_len,
114
erst_dbg_buf_len, NULL);
115
/* The record may be cleared by others, try read next record */
116
if (rc == -ENOENT)
117
goto retry_next;
118
if (rc < 0)
119
goto out;
120
if (len > ERST_DBG_RECORD_LEN_MAX) {
121
pr_warn(ERST_DBG_PFX
122
"Record (ID: 0x%llx) length is too long: %zd\n", id, len);
123
rc = -EIO;
124
goto out;
125
}
126
if (len > erst_dbg_buf_len) {
127
void *p;
128
rc = -ENOMEM;
129
p = kmalloc(len, GFP_KERNEL);
130
if (!p)
131
goto out;
132
kfree(erst_dbg_buf);
133
erst_dbg_buf = p;
134
erst_dbg_buf_len = len;
135
goto retry;
136
}
137
138
rc = -EINVAL;
139
if (len > usize)
140
goto out;
141
142
rc = -EFAULT;
143
if (copy_to_user(ubuf, erst_dbg_buf, len))
144
goto out;
145
rc = 0;
146
out:
147
mutex_unlock(&erst_dbg_mutex);
148
return rc ? rc : len;
149
}
150
151
static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf,
152
size_t usize, loff_t *off)
153
{
154
int rc;
155
struct cper_record_header *rcd;
156
157
if (!capable(CAP_SYS_ADMIN))
158
return -EPERM;
159
160
if (usize > ERST_DBG_RECORD_LEN_MAX) {
161
pr_err(ERST_DBG_PFX "Too long record to be written\n");
162
return -EINVAL;
163
}
164
165
if (mutex_lock_interruptible(&erst_dbg_mutex))
166
return -EINTR;
167
if (usize > erst_dbg_buf_len) {
168
void *p;
169
rc = -ENOMEM;
170
p = kmalloc(usize, GFP_KERNEL);
171
if (!p)
172
goto out;
173
kfree(erst_dbg_buf);
174
erst_dbg_buf = p;
175
erst_dbg_buf_len = usize;
176
}
177
if (copy_from_user(erst_dbg_buf, ubuf, usize)) {
178
rc = -EFAULT;
179
goto out;
180
}
181
rcd = erst_dbg_buf;
182
rc = -EINVAL;
183
if (rcd->record_length != usize)
184
goto out;
185
186
rc = erst_write(erst_dbg_buf);
187
188
out:
189
mutex_unlock(&erst_dbg_mutex);
190
return rc < 0 ? rc : usize;
191
}
192
193
static const struct file_operations erst_dbg_ops = {
194
.owner = THIS_MODULE,
195
.open = erst_dbg_open,
196
.release = erst_dbg_release,
197
.read = erst_dbg_read,
198
.write = erst_dbg_write,
199
.unlocked_ioctl = erst_dbg_ioctl,
200
};
201
202
static struct miscdevice erst_dbg_dev = {
203
.minor = MISC_DYNAMIC_MINOR,
204
.name = "erst_dbg",
205
.fops = &erst_dbg_ops,
206
};
207
208
static __init int erst_dbg_init(void)
209
{
210
if (erst_disable) {
211
pr_info(ERST_DBG_PFX "ERST support is disabled.\n");
212
return -ENODEV;
213
}
214
return misc_register(&erst_dbg_dev);
215
}
216
217
static __exit void erst_dbg_exit(void)
218
{
219
misc_deregister(&erst_dbg_dev);
220
kfree(erst_dbg_buf);
221
}
222
223
module_init(erst_dbg_init);
224
module_exit(erst_dbg_exit);
225
226
MODULE_AUTHOR("Huang Ying");
227
MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");
228
MODULE_LICENSE("GPL");
229
230