Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/acpi/apei/erst-dbg.c
26285 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
rc = copy_from_user(&record_id, (void __user *)arg,
64
sizeof(record_id));
65
if (rc)
66
return -EFAULT;
67
return erst_clear(record_id);
68
case APEI_ERST_GET_RECORD_COUNT:
69
rc = erst_get_record_count();
70
if (rc < 0)
71
return rc;
72
record_count = rc;
73
rc = put_user(record_count, (u32 __user *)arg);
74
if (rc)
75
return rc;
76
return 0;
77
default:
78
return -ENOTTY;
79
}
80
}
81
82
static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
83
size_t usize, loff_t *off)
84
{
85
int rc, *pos;
86
ssize_t len = 0;
87
u64 id;
88
89
if (*off)
90
return -EINVAL;
91
92
if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
93
return -EINTR;
94
95
pos = (int *)&filp->private_data;
96
97
retry_next:
98
rc = erst_get_record_id_next(pos, &id);
99
if (rc)
100
goto out;
101
/* no more record */
102
if (id == APEI_ERST_INVALID_RECORD_ID) {
103
/*
104
* If the persistent store is empty initially, the function
105
* 'erst_read' below will return "-ENOENT" value. This causes
106
* 'retry_next' label is entered again. The returned value
107
* should be zero indicating the read operation is EOF.
108
*/
109
len = 0;
110
111
goto out;
112
}
113
retry:
114
rc = len = erst_read_record(id, erst_dbg_buf, erst_dbg_buf_len,
115
erst_dbg_buf_len, NULL);
116
/* The record may be cleared by others, try read next record */
117
if (rc == -ENOENT)
118
goto retry_next;
119
if (rc < 0)
120
goto out;
121
if (len > ERST_DBG_RECORD_LEN_MAX) {
122
pr_warn(ERST_DBG_PFX
123
"Record (ID: 0x%llx) length is too long: %zd\n", id, len);
124
rc = -EIO;
125
goto out;
126
}
127
if (len > erst_dbg_buf_len) {
128
void *p;
129
rc = -ENOMEM;
130
p = kmalloc(len, GFP_KERNEL);
131
if (!p)
132
goto out;
133
kfree(erst_dbg_buf);
134
erst_dbg_buf = p;
135
erst_dbg_buf_len = len;
136
goto retry;
137
}
138
139
rc = -EINVAL;
140
if (len > usize)
141
goto out;
142
143
rc = -EFAULT;
144
if (copy_to_user(ubuf, erst_dbg_buf, len))
145
goto out;
146
rc = 0;
147
out:
148
mutex_unlock(&erst_dbg_mutex);
149
return rc ? rc : len;
150
}
151
152
static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf,
153
size_t usize, loff_t *off)
154
{
155
int rc;
156
struct cper_record_header *rcd;
157
158
if (!capable(CAP_SYS_ADMIN))
159
return -EPERM;
160
161
if (usize > ERST_DBG_RECORD_LEN_MAX) {
162
pr_err(ERST_DBG_PFX "Too long record to be written\n");
163
return -EINVAL;
164
}
165
166
if (mutex_lock_interruptible(&erst_dbg_mutex))
167
return -EINTR;
168
if (usize > erst_dbg_buf_len) {
169
void *p;
170
rc = -ENOMEM;
171
p = kmalloc(usize, GFP_KERNEL);
172
if (!p)
173
goto out;
174
kfree(erst_dbg_buf);
175
erst_dbg_buf = p;
176
erst_dbg_buf_len = usize;
177
}
178
rc = copy_from_user(erst_dbg_buf, ubuf, usize);
179
if (rc) {
180
rc = -EFAULT;
181
goto out;
182
}
183
rcd = erst_dbg_buf;
184
rc = -EINVAL;
185
if (rcd->record_length != usize)
186
goto out;
187
188
rc = erst_write(erst_dbg_buf);
189
190
out:
191
mutex_unlock(&erst_dbg_mutex);
192
return rc < 0 ? rc : usize;
193
}
194
195
static const struct file_operations erst_dbg_ops = {
196
.owner = THIS_MODULE,
197
.open = erst_dbg_open,
198
.release = erst_dbg_release,
199
.read = erst_dbg_read,
200
.write = erst_dbg_write,
201
.unlocked_ioctl = erst_dbg_ioctl,
202
};
203
204
static struct miscdevice erst_dbg_dev = {
205
.minor = MISC_DYNAMIC_MINOR,
206
.name = "erst_dbg",
207
.fops = &erst_dbg_ops,
208
};
209
210
static __init int erst_dbg_init(void)
211
{
212
if (erst_disable) {
213
pr_info(ERST_DBG_PFX "ERST support is disabled.\n");
214
return -ENODEV;
215
}
216
return misc_register(&erst_dbg_dev);
217
}
218
219
static __exit void erst_dbg_exit(void)
220
{
221
misc_deregister(&erst_dbg_dev);
222
kfree(erst_dbg_buf);
223
}
224
225
module_init(erst_dbg_init);
226
module_exit(erst_dbg_exit);
227
228
MODULE_AUTHOR("Huang Ying");
229
MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");
230
MODULE_LICENSE("GPL");
231
232