1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * sysfs.c - ACPI sysfs interface to userspace. |
4 | */ |
5 | |
6 | #define pr_fmt(fmt) "ACPI: " fmt |
7 | |
8 | #include <linux/acpi.h> |
9 | #include <linux/bitmap.h> |
10 | #include <linux/init.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/kstrtox.h> |
13 | #include <linux/moduleparam.h> |
14 | |
15 | #include "internal.h" |
16 | |
17 | #ifdef CONFIG_ACPI_DEBUG |
18 | /* |
19 | * ACPI debug sysfs I/F, including: |
20 | * /sys/modules/acpi/parameters/debug_layer |
21 | * /sys/modules/acpi/parameters/debug_level |
22 | * /sys/modules/acpi/parameters/trace_method_name |
23 | * /sys/modules/acpi/parameters/trace_state |
24 | * /sys/modules/acpi/parameters/trace_debug_layer |
25 | * /sys/modules/acpi/parameters/trace_debug_level |
26 | */ |
27 | |
28 | struct acpi_dlayer { |
29 | const char *name; |
30 | unsigned long value; |
31 | }; |
32 | struct acpi_dlevel { |
33 | const char *name; |
34 | unsigned long value; |
35 | }; |
36 | #define ACPI_DEBUG_INIT(v) { .name = #v, .value = v } |
37 | |
38 | static const struct acpi_dlayer acpi_debug_layers[] = { |
39 | ACPI_DEBUG_INIT(ACPI_UTILITIES), |
40 | ACPI_DEBUG_INIT(ACPI_HARDWARE), |
41 | ACPI_DEBUG_INIT(ACPI_EVENTS), |
42 | ACPI_DEBUG_INIT(ACPI_TABLES), |
43 | ACPI_DEBUG_INIT(ACPI_NAMESPACE), |
44 | ACPI_DEBUG_INIT(ACPI_PARSER), |
45 | ACPI_DEBUG_INIT(ACPI_DISPATCHER), |
46 | ACPI_DEBUG_INIT(ACPI_EXECUTER), |
47 | ACPI_DEBUG_INIT(ACPI_RESOURCES), |
48 | ACPI_DEBUG_INIT(ACPI_CA_DEBUGGER), |
49 | ACPI_DEBUG_INIT(ACPI_OS_SERVICES), |
50 | ACPI_DEBUG_INIT(ACPI_CA_DISASSEMBLER), |
51 | ACPI_DEBUG_INIT(ACPI_COMPILER), |
52 | ACPI_DEBUG_INIT(ACPI_TOOLS), |
53 | }; |
54 | |
55 | static const struct acpi_dlevel acpi_debug_levels[] = { |
56 | ACPI_DEBUG_INIT(ACPI_LV_INIT), |
57 | ACPI_DEBUG_INIT(ACPI_LV_DEBUG_OBJECT), |
58 | ACPI_DEBUG_INIT(ACPI_LV_INFO), |
59 | ACPI_DEBUG_INIT(ACPI_LV_REPAIR), |
60 | ACPI_DEBUG_INIT(ACPI_LV_TRACE_POINT), |
61 | |
62 | ACPI_DEBUG_INIT(ACPI_LV_INIT_NAMES), |
63 | ACPI_DEBUG_INIT(ACPI_LV_PARSE), |
64 | ACPI_DEBUG_INIT(ACPI_LV_LOAD), |
65 | ACPI_DEBUG_INIT(ACPI_LV_DISPATCH), |
66 | ACPI_DEBUG_INIT(ACPI_LV_EXEC), |
67 | ACPI_DEBUG_INIT(ACPI_LV_NAMES), |
68 | ACPI_DEBUG_INIT(ACPI_LV_OPREGION), |
69 | ACPI_DEBUG_INIT(ACPI_LV_BFIELD), |
70 | ACPI_DEBUG_INIT(ACPI_LV_TABLES), |
71 | ACPI_DEBUG_INIT(ACPI_LV_VALUES), |
72 | ACPI_DEBUG_INIT(ACPI_LV_OBJECTS), |
73 | ACPI_DEBUG_INIT(ACPI_LV_RESOURCES), |
74 | ACPI_DEBUG_INIT(ACPI_LV_USER_REQUESTS), |
75 | ACPI_DEBUG_INIT(ACPI_LV_PACKAGE), |
76 | |
77 | ACPI_DEBUG_INIT(ACPI_LV_ALLOCATIONS), |
78 | ACPI_DEBUG_INIT(ACPI_LV_FUNCTIONS), |
79 | ACPI_DEBUG_INIT(ACPI_LV_OPTIMIZATIONS), |
80 | |
81 | ACPI_DEBUG_INIT(ACPI_LV_MUTEX), |
82 | ACPI_DEBUG_INIT(ACPI_LV_THREADS), |
83 | ACPI_DEBUG_INIT(ACPI_LV_IO), |
84 | ACPI_DEBUG_INIT(ACPI_LV_INTERRUPTS), |
85 | |
86 | ACPI_DEBUG_INIT(ACPI_LV_AML_DISASSEMBLE), |
87 | ACPI_DEBUG_INIT(ACPI_LV_VERBOSE_INFO), |
88 | ACPI_DEBUG_INIT(ACPI_LV_FULL_TABLES), |
89 | ACPI_DEBUG_INIT(ACPI_LV_EVENTS), |
90 | }; |
91 | |
92 | static int param_get_debug_layer(char *buffer, const struct kernel_param *kp) |
93 | { |
94 | int result = 0; |
95 | int i; |
96 | |
97 | result = sprintf(buf: buffer, fmt: "%-25s\tHex SET\n" , "Description" ); |
98 | |
99 | for (i = 0; i < ARRAY_SIZE(acpi_debug_layers); i++) { |
100 | result += sprintf(buf: buffer + result, fmt: "%-25s\t0x%08lX [%c]\n" , |
101 | acpi_debug_layers[i].name, |
102 | acpi_debug_layers[i].value, |
103 | (acpi_dbg_layer & acpi_debug_layers[i].value) |
104 | ? '*' : ' '); |
105 | } |
106 | result += |
107 | sprintf(buf: buffer + result, fmt: "%-25s\t0x%08X [%c]\n" , "ACPI_ALL_DRIVERS" , |
108 | ACPI_ALL_DRIVERS, |
109 | (acpi_dbg_layer & ACPI_ALL_DRIVERS) == |
110 | ACPI_ALL_DRIVERS ? '*' : (acpi_dbg_layer & ACPI_ALL_DRIVERS) |
111 | == 0 ? ' ' : '-'); |
112 | result += |
113 | sprintf(buf: buffer + result, |
114 | fmt: "--\ndebug_layer = 0x%08X ( * = enabled)\n" , |
115 | acpi_dbg_layer); |
116 | |
117 | return result; |
118 | } |
119 | |
120 | static int param_get_debug_level(char *buffer, const struct kernel_param *kp) |
121 | { |
122 | int result = 0; |
123 | int i; |
124 | |
125 | result = sprintf(buf: buffer, fmt: "%-25s\tHex SET\n" , "Description" ); |
126 | |
127 | for (i = 0; i < ARRAY_SIZE(acpi_debug_levels); i++) { |
128 | result += sprintf(buf: buffer + result, fmt: "%-25s\t0x%08lX [%c]\n" , |
129 | acpi_debug_levels[i].name, |
130 | acpi_debug_levels[i].value, |
131 | (acpi_dbg_level & acpi_debug_levels[i].value) |
132 | ? '*' : ' '); |
133 | } |
134 | result += |
135 | sprintf(buf: buffer + result, fmt: "--\ndebug_level = 0x%08X (* = enabled)\n" , |
136 | acpi_dbg_level); |
137 | |
138 | return result; |
139 | } |
140 | |
141 | static const struct kernel_param_ops param_ops_debug_layer = { |
142 | .set = param_set_uint, |
143 | .get = param_get_debug_layer, |
144 | }; |
145 | |
146 | static const struct kernel_param_ops param_ops_debug_level = { |
147 | .set = param_set_uint, |
148 | .get = param_get_debug_level, |
149 | }; |
150 | |
151 | module_param_cb(debug_layer, ¶m_ops_debug_layer, &acpi_dbg_layer, 0644); |
152 | module_param_cb(debug_level, ¶m_ops_debug_level, &acpi_dbg_level, 0644); |
153 | |
154 | static char trace_method_name[1024]; |
155 | |
156 | static int param_set_trace_method_name(const char *val, |
157 | const struct kernel_param *kp) |
158 | { |
159 | u32 saved_flags = 0; |
160 | bool is_abs_path = true; |
161 | |
162 | if (*val != '\\') |
163 | is_abs_path = false; |
164 | |
165 | if ((is_abs_path && strlen(val) > 1023) || |
166 | (!is_abs_path && strlen(val) > 1022)) { |
167 | pr_err("%s: string parameter too long\n" , kp->name); |
168 | return -ENOSPC; |
169 | } |
170 | |
171 | /* |
172 | * It's not safe to update acpi_gbl_trace_method_name without |
173 | * having the tracer stopped, so we save the original tracer |
174 | * state and disable it. |
175 | */ |
176 | saved_flags = acpi_gbl_trace_flags; |
177 | (void)acpi_debug_trace(NULL, |
178 | debug_level: acpi_gbl_trace_dbg_level, |
179 | debug_layer: acpi_gbl_trace_dbg_layer, |
180 | flags: 0); |
181 | |
182 | /* This is a hack. We can't kmalloc in early boot. */ |
183 | if (is_abs_path) |
184 | strcpy(p: trace_method_name, q: val); |
185 | else { |
186 | trace_method_name[0] = '\\'; |
187 | strcpy(p: trace_method_name+1, q: val); |
188 | } |
189 | |
190 | /* Restore the original tracer state */ |
191 | (void)acpi_debug_trace(name: trace_method_name, |
192 | debug_level: acpi_gbl_trace_dbg_level, |
193 | debug_layer: acpi_gbl_trace_dbg_layer, |
194 | flags: saved_flags); |
195 | |
196 | return 0; |
197 | } |
198 | |
199 | static int param_get_trace_method_name(char *buffer, const struct kernel_param *kp) |
200 | { |
201 | return sysfs_emit(buf: buffer, fmt: "%s\n" , acpi_gbl_trace_method_name); |
202 | } |
203 | |
204 | static const struct kernel_param_ops param_ops_trace_method = { |
205 | .set = param_set_trace_method_name, |
206 | .get = param_get_trace_method_name, |
207 | }; |
208 | |
209 | static const struct kernel_param_ops param_ops_trace_attrib = { |
210 | .set = param_set_uint, |
211 | .get = param_get_uint, |
212 | }; |
213 | |
214 | module_param_cb(trace_method_name, ¶m_ops_trace_method, &trace_method_name, 0644); |
215 | module_param_cb(trace_debug_layer, ¶m_ops_trace_attrib, &acpi_gbl_trace_dbg_layer, 0644); |
216 | module_param_cb(trace_debug_level, ¶m_ops_trace_attrib, &acpi_gbl_trace_dbg_level, 0644); |
217 | |
218 | static int param_set_trace_state(const char *val, |
219 | const struct kernel_param *kp) |
220 | { |
221 | acpi_status status; |
222 | const char *method = trace_method_name; |
223 | u32 flags = 0; |
224 | |
225 | /* So "xxx-once" comparison should go prior than "xxx" comparison */ |
226 | #define acpi_compare_param(val, key) \ |
227 | strncmp((val), (key), sizeof(key) - 1) |
228 | |
229 | if (!acpi_compare_param(val, "enable" )) { |
230 | method = NULL; |
231 | flags = ACPI_TRACE_ENABLED; |
232 | } else if (!acpi_compare_param(val, "disable" )) |
233 | method = NULL; |
234 | else if (!acpi_compare_param(val, "method-once" )) |
235 | flags = ACPI_TRACE_ENABLED | ACPI_TRACE_ONESHOT; |
236 | else if (!acpi_compare_param(val, "method" )) |
237 | flags = ACPI_TRACE_ENABLED; |
238 | else if (!acpi_compare_param(val, "opcode-once" )) |
239 | flags = ACPI_TRACE_ENABLED | ACPI_TRACE_ONESHOT | ACPI_TRACE_OPCODE; |
240 | else if (!acpi_compare_param(val, "opcode" )) |
241 | flags = ACPI_TRACE_ENABLED | ACPI_TRACE_OPCODE; |
242 | else |
243 | return -EINVAL; |
244 | |
245 | status = acpi_debug_trace(name: method, |
246 | debug_level: acpi_gbl_trace_dbg_level, |
247 | debug_layer: acpi_gbl_trace_dbg_layer, |
248 | flags); |
249 | if (ACPI_FAILURE(status)) |
250 | return -EBUSY; |
251 | |
252 | return 0; |
253 | } |
254 | |
255 | static int param_get_trace_state(char *buffer, const struct kernel_param *kp) |
256 | { |
257 | if (!(acpi_gbl_trace_flags & ACPI_TRACE_ENABLED)) |
258 | return sprintf(buf: buffer, fmt: "disable\n" ); |
259 | if (!acpi_gbl_trace_method_name) |
260 | return sprintf(buf: buffer, fmt: "enable\n" ); |
261 | if (acpi_gbl_trace_flags & ACPI_TRACE_ONESHOT) |
262 | return sprintf(buf: buffer, fmt: "method-once\n" ); |
263 | else |
264 | return sprintf(buf: buffer, fmt: "method\n" ); |
265 | } |
266 | |
267 | module_param_call(trace_state, param_set_trace_state, param_get_trace_state, |
268 | NULL, 0644); |
269 | #endif /* CONFIG_ACPI_DEBUG */ |
270 | |
271 | |
272 | /* /sys/modules/acpi/parameters/aml_debug_output */ |
273 | |
274 | module_param_named(aml_debug_output, acpi_gbl_enable_aml_debug_object, |
275 | byte, 0644); |
276 | MODULE_PARM_DESC(aml_debug_output, |
277 | "To enable/disable the ACPI Debug Object output." ); |
278 | |
279 | /* /sys/module/acpi/parameters/acpica_version */ |
280 | static int param_get_acpica_version(char *buffer, |
281 | const struct kernel_param *kp) |
282 | { |
283 | int result; |
284 | |
285 | result = sprintf(buf: buffer, fmt: "%x\n" , ACPI_CA_VERSION); |
286 | |
287 | return result; |
288 | } |
289 | |
290 | module_param_call(acpica_version, NULL, param_get_acpica_version, NULL, 0444); |
291 | |
292 | /* |
293 | * ACPI table sysfs I/F: |
294 | * /sys/firmware/acpi/tables/ |
295 | * /sys/firmware/acpi/tables/data/ |
296 | * /sys/firmware/acpi/tables/dynamic/ |
297 | */ |
298 | |
299 | static LIST_HEAD(acpi_table_attr_list); |
300 | static struct kobject *tables_kobj; |
301 | static struct kobject *tables_data_kobj; |
302 | static struct kobject *dynamic_tables_kobj; |
303 | static struct kobject *hotplug_kobj; |
304 | |
305 | #define ACPI_MAX_TABLE_INSTANCES 999 |
306 | #define ACPI_INST_SIZE 4 /* including trailing 0 */ |
307 | |
308 | struct acpi_table_attr { |
309 | struct bin_attribute attr; |
310 | char name[ACPI_NAMESEG_SIZE]; |
311 | int instance; |
312 | char filename[ACPI_NAMESEG_SIZE+ACPI_INST_SIZE]; |
313 | struct list_head node; |
314 | }; |
315 | |
316 | struct acpi_data_attr { |
317 | struct bin_attribute attr; |
318 | u64 addr; |
319 | }; |
320 | |
321 | static ssize_t acpi_table_show(struct file *filp, struct kobject *kobj, |
322 | struct bin_attribute *bin_attr, char *buf, |
323 | loff_t offset, size_t count) |
324 | { |
325 | struct acpi_table_attr *table_attr = |
326 | container_of(bin_attr, struct acpi_table_attr, attr); |
327 | struct acpi_table_header * = NULL; |
328 | acpi_status status; |
329 | ssize_t rc; |
330 | |
331 | status = acpi_get_table(signature: table_attr->name, instance: table_attr->instance, |
332 | out_table: &table_header); |
333 | if (ACPI_FAILURE(status)) |
334 | return -ENODEV; |
335 | |
336 | rc = memory_read_from_buffer(to: buf, count, ppos: &offset, from: table_header, |
337 | available: table_header->length); |
338 | acpi_put_table(table: table_header); |
339 | return rc; |
340 | } |
341 | |
342 | static int acpi_table_attr_init(struct kobject *tables_obj, |
343 | struct acpi_table_attr *table_attr, |
344 | struct acpi_table_header *) |
345 | { |
346 | struct acpi_table_header * = NULL; |
347 | struct acpi_table_attr *attr = NULL; |
348 | char instance_str[ACPI_INST_SIZE]; |
349 | |
350 | sysfs_attr_init(&table_attr->attr.attr); |
351 | ACPI_COPY_NAMESEG(table_attr->name, table_header->signature); |
352 | |
353 | list_for_each_entry(attr, &acpi_table_attr_list, node) { |
354 | if (ACPI_COMPARE_NAMESEG(table_attr->name, attr->name)) |
355 | if (table_attr->instance < attr->instance) |
356 | table_attr->instance = attr->instance; |
357 | } |
358 | table_attr->instance++; |
359 | if (table_attr->instance > ACPI_MAX_TABLE_INSTANCES) { |
360 | pr_warn("%4.4s: too many table instances\n" , table_attr->name); |
361 | return -ERANGE; |
362 | } |
363 | |
364 | ACPI_COPY_NAMESEG(table_attr->filename, table_header->signature); |
365 | table_attr->filename[ACPI_NAMESEG_SIZE] = '\0'; |
366 | if (table_attr->instance > 1 || (table_attr->instance == 1 && |
367 | !acpi_get_table |
368 | (signature: table_header->signature, instance: 2, out_table: &header))) { |
369 | snprintf(buf: instance_str, size: sizeof(instance_str), fmt: "%u" , |
370 | table_attr->instance); |
371 | strcat(p: table_attr->filename, q: instance_str); |
372 | } |
373 | |
374 | table_attr->attr.size = table_header->length; |
375 | table_attr->attr.read = acpi_table_show; |
376 | table_attr->attr.attr.name = table_attr->filename; |
377 | table_attr->attr.attr.mode = 0400; |
378 | |
379 | return sysfs_create_bin_file(kobj: tables_obj, attr: &table_attr->attr); |
380 | } |
381 | |
382 | acpi_status acpi_sysfs_table_handler(u32 event, void *table, void *context) |
383 | { |
384 | struct acpi_table_attr *table_attr; |
385 | |
386 | switch (event) { |
387 | case ACPI_TABLE_EVENT_INSTALL: |
388 | table_attr = kzalloc(size: sizeof(*table_attr), GFP_KERNEL); |
389 | if (!table_attr) |
390 | return AE_NO_MEMORY; |
391 | |
392 | if (acpi_table_attr_init(tables_obj: dynamic_tables_kobj, |
393 | table_attr, table_header: table)) { |
394 | kfree(objp: table_attr); |
395 | return AE_ERROR; |
396 | } |
397 | list_add_tail(new: &table_attr->node, head: &acpi_table_attr_list); |
398 | break; |
399 | case ACPI_TABLE_EVENT_LOAD: |
400 | case ACPI_TABLE_EVENT_UNLOAD: |
401 | case ACPI_TABLE_EVENT_UNINSTALL: |
402 | /* |
403 | * we do not need to do anything right now |
404 | * because the table is not deleted from the |
405 | * global table list when unloading it. |
406 | */ |
407 | break; |
408 | default: |
409 | return AE_BAD_PARAMETER; |
410 | } |
411 | return AE_OK; |
412 | } |
413 | |
414 | static ssize_t acpi_data_show(struct file *filp, struct kobject *kobj, |
415 | struct bin_attribute *bin_attr, char *buf, |
416 | loff_t offset, size_t count) |
417 | { |
418 | struct acpi_data_attr *data_attr; |
419 | void __iomem *base; |
420 | ssize_t size; |
421 | |
422 | data_attr = container_of(bin_attr, struct acpi_data_attr, attr); |
423 | size = data_attr->attr.size; |
424 | |
425 | if (offset < 0) |
426 | return -EINVAL; |
427 | |
428 | if (offset >= size) |
429 | return 0; |
430 | |
431 | if (count > size - offset) |
432 | count = size - offset; |
433 | |
434 | base = acpi_os_map_iomem(phys: data_attr->addr, size); |
435 | if (!base) |
436 | return -ENOMEM; |
437 | |
438 | memcpy_fromio(buf, base + offset, count); |
439 | |
440 | acpi_os_unmap_iomem(virt: base, size); |
441 | |
442 | return count; |
443 | } |
444 | |
445 | static int acpi_bert_data_init(void *th, struct acpi_data_attr *data_attr) |
446 | { |
447 | struct acpi_table_bert *bert = th; |
448 | |
449 | if (bert->header.length < sizeof(struct acpi_table_bert) || |
450 | bert->region_length < sizeof(struct acpi_hest_generic_status)) { |
451 | kfree(objp: data_attr); |
452 | return -EINVAL; |
453 | } |
454 | data_attr->addr = bert->address; |
455 | data_attr->attr.size = bert->region_length; |
456 | data_attr->attr.attr.name = "BERT" ; |
457 | |
458 | return sysfs_create_bin_file(kobj: tables_data_kobj, attr: &data_attr->attr); |
459 | } |
460 | |
461 | static int acpi_ccel_data_init(void *th, struct acpi_data_attr *data_attr) |
462 | { |
463 | struct acpi_table_ccel *ccel = th; |
464 | |
465 | if (ccel->header.length < sizeof(struct acpi_table_ccel) || |
466 | !ccel->log_area_start_address || !ccel->log_area_minimum_length) { |
467 | kfree(objp: data_attr); |
468 | return -EINVAL; |
469 | } |
470 | data_attr->addr = ccel->log_area_start_address; |
471 | data_attr->attr.size = ccel->log_area_minimum_length; |
472 | data_attr->attr.attr.name = "CCEL" ; |
473 | |
474 | return sysfs_create_bin_file(kobj: tables_data_kobj, attr: &data_attr->attr); |
475 | } |
476 | |
477 | static struct acpi_data_obj { |
478 | char *name; |
479 | int (*fn)(void *, struct acpi_data_attr *); |
480 | } acpi_data_objs[] = { |
481 | { ACPI_SIG_BERT, acpi_bert_data_init }, |
482 | { ACPI_SIG_CCEL, acpi_ccel_data_init }, |
483 | }; |
484 | |
485 | #define NUM_ACPI_DATA_OBJS ARRAY_SIZE(acpi_data_objs) |
486 | |
487 | static int acpi_table_data_init(struct acpi_table_header *th) |
488 | { |
489 | struct acpi_data_attr *data_attr; |
490 | int i; |
491 | |
492 | for (i = 0; i < NUM_ACPI_DATA_OBJS; i++) { |
493 | if (ACPI_COMPARE_NAMESEG(th->signature, acpi_data_objs[i].name)) { |
494 | data_attr = kzalloc(size: sizeof(*data_attr), GFP_KERNEL); |
495 | if (!data_attr) |
496 | return -ENOMEM; |
497 | sysfs_attr_init(&data_attr->attr.attr); |
498 | data_attr->attr.read = acpi_data_show; |
499 | data_attr->attr.attr.mode = 0400; |
500 | return acpi_data_objs[i].fn(th, data_attr); |
501 | } |
502 | } |
503 | return 0; |
504 | } |
505 | |
506 | static int acpi_tables_sysfs_init(void) |
507 | { |
508 | struct acpi_table_attr *table_attr; |
509 | struct acpi_table_header * = NULL; |
510 | int table_index; |
511 | acpi_status status; |
512 | int ret; |
513 | |
514 | tables_kobj = kobject_create_and_add(name: "tables" , parent: acpi_kobj); |
515 | if (!tables_kobj) |
516 | goto err; |
517 | |
518 | tables_data_kobj = kobject_create_and_add(name: "data" , parent: tables_kobj); |
519 | if (!tables_data_kobj) |
520 | goto err_tables_data; |
521 | |
522 | dynamic_tables_kobj = kobject_create_and_add(name: "dynamic" , parent: tables_kobj); |
523 | if (!dynamic_tables_kobj) |
524 | goto err_dynamic_tables; |
525 | |
526 | for (table_index = 0;; table_index++) { |
527 | status = acpi_get_table_by_index(table_index, out_table: &table_header); |
528 | |
529 | if (status == AE_BAD_PARAMETER) |
530 | break; |
531 | |
532 | if (ACPI_FAILURE(status)) |
533 | continue; |
534 | |
535 | table_attr = kzalloc(size: sizeof(*table_attr), GFP_KERNEL); |
536 | if (!table_attr) |
537 | return -ENOMEM; |
538 | |
539 | ret = acpi_table_attr_init(tables_obj: tables_kobj, |
540 | table_attr, table_header); |
541 | if (ret) { |
542 | kfree(objp: table_attr); |
543 | return ret; |
544 | } |
545 | list_add_tail(new: &table_attr->node, head: &acpi_table_attr_list); |
546 | acpi_table_data_init(th: table_header); |
547 | } |
548 | |
549 | kobject_uevent(kobj: tables_kobj, action: KOBJ_ADD); |
550 | kobject_uevent(kobj: tables_data_kobj, action: KOBJ_ADD); |
551 | kobject_uevent(kobj: dynamic_tables_kobj, action: KOBJ_ADD); |
552 | |
553 | return 0; |
554 | err_dynamic_tables: |
555 | kobject_put(kobj: tables_data_kobj); |
556 | err_tables_data: |
557 | kobject_put(kobj: tables_kobj); |
558 | err: |
559 | return -ENOMEM; |
560 | } |
561 | |
562 | /* |
563 | * Detailed ACPI IRQ counters: |
564 | * /sys/firmware/acpi/interrupts/ |
565 | */ |
566 | |
567 | u32 acpi_irq_handled; |
568 | u32 acpi_irq_not_handled; |
569 | |
570 | #define COUNT_GPE 0 |
571 | #define COUNT_SCI 1 /* acpi_irq_handled */ |
572 | #define COUNT_SCI_NOT 2 /* acpi_irq_not_handled */ |
573 | #define COUNT_ERROR 3 /* other */ |
574 | #define 4 |
575 | |
576 | struct event_counter { |
577 | u32 count; |
578 | u32 flags; |
579 | }; |
580 | |
581 | static struct event_counter *all_counters; |
582 | static u32 num_gpes; |
583 | static u32 num_counters; |
584 | static struct attribute **all_attrs; |
585 | static u32 acpi_gpe_count; |
586 | |
587 | static struct attribute_group interrupt_stats_attr_group = { |
588 | .name = "interrupts" , |
589 | }; |
590 | |
591 | static struct kobj_attribute *counter_attrs; |
592 | |
593 | static void delete_gpe_attr_array(void) |
594 | { |
595 | struct event_counter *tmp = all_counters; |
596 | |
597 | all_counters = NULL; |
598 | kfree(objp: tmp); |
599 | |
600 | if (counter_attrs) { |
601 | int i; |
602 | |
603 | for (i = 0; i < num_gpes; i++) |
604 | kfree(objp: counter_attrs[i].attr.name); |
605 | |
606 | kfree(objp: counter_attrs); |
607 | } |
608 | kfree(objp: all_attrs); |
609 | } |
610 | |
611 | static void gpe_count(u32 gpe_number) |
612 | { |
613 | acpi_gpe_count++; |
614 | |
615 | if (!all_counters) |
616 | return; |
617 | |
618 | if (gpe_number < num_gpes) |
619 | all_counters[gpe_number].count++; |
620 | else |
621 | all_counters[num_gpes + ACPI_NUM_FIXED_EVENTS + |
622 | COUNT_ERROR].count++; |
623 | } |
624 | |
625 | static void fixed_event_count(u32 event_number) |
626 | { |
627 | if (!all_counters) |
628 | return; |
629 | |
630 | if (event_number < ACPI_NUM_FIXED_EVENTS) |
631 | all_counters[num_gpes + event_number].count++; |
632 | else |
633 | all_counters[num_gpes + ACPI_NUM_FIXED_EVENTS + |
634 | COUNT_ERROR].count++; |
635 | } |
636 | |
637 | static void acpi_global_event_handler(u32 event_type, acpi_handle device, |
638 | u32 event_number, void *context) |
639 | { |
640 | if (event_type == ACPI_EVENT_TYPE_GPE) { |
641 | gpe_count(gpe_number: event_number); |
642 | pr_debug("GPE event 0x%02x\n" , event_number); |
643 | } else if (event_type == ACPI_EVENT_TYPE_FIXED) { |
644 | fixed_event_count(event_number); |
645 | pr_debug("Fixed event 0x%02x\n" , event_number); |
646 | } else { |
647 | pr_debug("Other event 0x%02x\n" , event_number); |
648 | } |
649 | } |
650 | |
651 | static int get_status(u32 index, acpi_event_status *ret, |
652 | acpi_handle *handle) |
653 | { |
654 | acpi_status status; |
655 | |
656 | if (index >= num_gpes + ACPI_NUM_FIXED_EVENTS) |
657 | return -EINVAL; |
658 | |
659 | if (index < num_gpes) { |
660 | status = acpi_get_gpe_device(gpe_index: index, gpe_device: handle); |
661 | if (ACPI_FAILURE(status)) { |
662 | pr_warn("Invalid GPE 0x%x" , index); |
663 | return -ENXIO; |
664 | } |
665 | status = acpi_get_gpe_status(gpe_device: *handle, gpe_number: index, event_status: ret); |
666 | } else { |
667 | status = acpi_get_event_status(event: index - num_gpes, event_status: ret); |
668 | } |
669 | if (ACPI_FAILURE(status)) |
670 | return -EIO; |
671 | |
672 | return 0; |
673 | } |
674 | |
675 | static ssize_t counter_show(struct kobject *kobj, |
676 | struct kobj_attribute *attr, char *buf) |
677 | { |
678 | int index = attr - counter_attrs; |
679 | int size; |
680 | acpi_handle handle; |
681 | acpi_event_status status; |
682 | int result = 0; |
683 | |
684 | all_counters[num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_SCI].count = |
685 | acpi_irq_handled; |
686 | all_counters[num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_SCI_NOT].count = |
687 | acpi_irq_not_handled; |
688 | all_counters[num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_GPE].count = |
689 | acpi_gpe_count; |
690 | size = sprintf(buf, fmt: "%8u" , all_counters[index].count); |
691 | |
692 | /* "gpe_all" or "sci" */ |
693 | if (index >= num_gpes + ACPI_NUM_FIXED_EVENTS) |
694 | goto end; |
695 | |
696 | result = get_status(index, ret: &status, handle: &handle); |
697 | if (result) |
698 | goto end; |
699 | |
700 | if (status & ACPI_EVENT_FLAG_ENABLE_SET) |
701 | size += sprintf(buf: buf + size, fmt: " EN" ); |
702 | else |
703 | size += sprintf(buf: buf + size, fmt: " " ); |
704 | if (status & ACPI_EVENT_FLAG_STATUS_SET) |
705 | size += sprintf(buf: buf + size, fmt: " STS" ); |
706 | else |
707 | size += sprintf(buf: buf + size, fmt: " " ); |
708 | |
709 | if (!(status & ACPI_EVENT_FLAG_HAS_HANDLER)) |
710 | size += sprintf(buf: buf + size, fmt: " invalid " ); |
711 | else if (status & ACPI_EVENT_FLAG_ENABLED) |
712 | size += sprintf(buf: buf + size, fmt: " enabled " ); |
713 | else if (status & ACPI_EVENT_FLAG_WAKE_ENABLED) |
714 | size += sprintf(buf: buf + size, fmt: " wake_enabled" ); |
715 | else |
716 | size += sprintf(buf: buf + size, fmt: " disabled " ); |
717 | if (status & ACPI_EVENT_FLAG_MASKED) |
718 | size += sprintf(buf: buf + size, fmt: " masked " ); |
719 | else |
720 | size += sprintf(buf: buf + size, fmt: " unmasked" ); |
721 | |
722 | end: |
723 | size += sprintf(buf: buf + size, fmt: "\n" ); |
724 | return result ? result : size; |
725 | } |
726 | |
727 | /* |
728 | * counter_set() sets the specified counter. |
729 | * setting the total "sci" file to any value clears all counters. |
730 | * enable/disable/clear a gpe/fixed event in user space. |
731 | */ |
732 | static ssize_t counter_set(struct kobject *kobj, |
733 | struct kobj_attribute *attr, const char *buf, |
734 | size_t size) |
735 | { |
736 | int index = attr - counter_attrs; |
737 | acpi_event_status status; |
738 | acpi_handle handle; |
739 | int result = 0; |
740 | unsigned long tmp; |
741 | |
742 | if (index == num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_SCI) { |
743 | int i; |
744 | for (i = 0; i < num_counters; ++i) |
745 | all_counters[i].count = 0; |
746 | acpi_gpe_count = 0; |
747 | acpi_irq_handled = 0; |
748 | acpi_irq_not_handled = 0; |
749 | goto end; |
750 | } |
751 | |
752 | /* show the event status for both GPEs and Fixed Events */ |
753 | result = get_status(index, ret: &status, handle: &handle); |
754 | if (result) |
755 | goto end; |
756 | |
757 | if (!(status & ACPI_EVENT_FLAG_HAS_HANDLER)) { |
758 | pr_warn("Can not change Invalid GPE/Fixed Event status\n" ); |
759 | return -EINVAL; |
760 | } |
761 | |
762 | if (index < num_gpes) { |
763 | if (!strcmp(buf, "disable\n" ) && |
764 | (status & ACPI_EVENT_FLAG_ENABLED)) |
765 | result = acpi_disable_gpe(gpe_device: handle, gpe_number: index); |
766 | else if (!strcmp(buf, "enable\n" ) && |
767 | !(status & ACPI_EVENT_FLAG_ENABLED)) |
768 | result = acpi_enable_gpe(gpe_device: handle, gpe_number: index); |
769 | else if (!strcmp(buf, "clear\n" ) && |
770 | (status & ACPI_EVENT_FLAG_STATUS_SET)) |
771 | result = acpi_clear_gpe(gpe_device: handle, gpe_number: index); |
772 | else if (!strcmp(buf, "mask\n" )) |
773 | result = acpi_mask_gpe(gpe_device: handle, gpe_number: index, TRUE); |
774 | else if (!strcmp(buf, "unmask\n" )) |
775 | result = acpi_mask_gpe(gpe_device: handle, gpe_number: index, FALSE); |
776 | else if (!kstrtoul(s: buf, base: 0, res: &tmp)) |
777 | all_counters[index].count = tmp; |
778 | else |
779 | result = -EINVAL; |
780 | } else if (index < num_gpes + ACPI_NUM_FIXED_EVENTS) { |
781 | int event = index - num_gpes; |
782 | if (!strcmp(buf, "disable\n" ) && |
783 | (status & ACPI_EVENT_FLAG_ENABLE_SET)) |
784 | result = acpi_disable_event(event, ACPI_NOT_ISR); |
785 | else if (!strcmp(buf, "enable\n" ) && |
786 | !(status & ACPI_EVENT_FLAG_ENABLE_SET)) |
787 | result = acpi_enable_event(event, ACPI_NOT_ISR); |
788 | else if (!strcmp(buf, "clear\n" ) && |
789 | (status & ACPI_EVENT_FLAG_STATUS_SET)) |
790 | result = acpi_clear_event(event); |
791 | else if (!kstrtoul(s: buf, base: 0, res: &tmp)) |
792 | all_counters[index].count = tmp; |
793 | else |
794 | result = -EINVAL; |
795 | } else |
796 | all_counters[index].count = strtoul(buf, NULL, 0); |
797 | |
798 | if (ACPI_FAILURE(result)) |
799 | result = -EINVAL; |
800 | end: |
801 | return result ? result : size; |
802 | } |
803 | |
804 | /* |
805 | * A Quirk Mechanism for GPE Flooding Prevention: |
806 | * |
807 | * Quirks may be needed to prevent GPE flooding on a specific GPE. The |
808 | * flooding typically cannot be detected and automatically prevented by |
809 | * ACPI_GPE_DISPATCH_NONE check because there is a _Lxx/_Exx prepared in |
810 | * the AML tables. This normally indicates a feature gap in Linux, thus |
811 | * instead of providing endless quirk tables, we provide a boot parameter |
812 | * for those who want this quirk. For example, if the users want to prevent |
813 | * the GPE flooding for GPE 00, they need to specify the following boot |
814 | * parameter: |
815 | * acpi_mask_gpe=0x00 |
816 | * Note, the parameter can be a list (see bitmap_parselist() for the details). |
817 | * The masking status can be modified by the following runtime controlling |
818 | * interface: |
819 | * echo unmask > /sys/firmware/acpi/interrupts/gpe00 |
820 | */ |
821 | #define ACPI_MASKABLE_GPE_MAX 0x100 |
822 | static DECLARE_BITMAP(acpi_masked_gpes_map, ACPI_MASKABLE_GPE_MAX) __initdata; |
823 | |
824 | static int __init acpi_gpe_set_masked_gpes(char *val) |
825 | { |
826 | int ret; |
827 | u8 gpe; |
828 | |
829 | ret = kstrtou8(s: val, base: 0, res: &gpe); |
830 | if (ret) { |
831 | ret = bitmap_parselist(buf: val, maskp: acpi_masked_gpes_map, ACPI_MASKABLE_GPE_MAX); |
832 | if (ret) |
833 | return ret; |
834 | } else |
835 | set_bit(nr: gpe, addr: acpi_masked_gpes_map); |
836 | |
837 | return 1; |
838 | } |
839 | __setup("acpi_mask_gpe=" , acpi_gpe_set_masked_gpes); |
840 | |
841 | void __init acpi_gpe_apply_masked_gpes(void) |
842 | { |
843 | acpi_handle handle; |
844 | acpi_status status; |
845 | u16 gpe; |
846 | |
847 | for_each_set_bit(gpe, acpi_masked_gpes_map, ACPI_MASKABLE_GPE_MAX) { |
848 | status = acpi_get_gpe_device(gpe_index: gpe, gpe_device: &handle); |
849 | if (ACPI_SUCCESS(status)) { |
850 | pr_info("Masking GPE 0x%x.\n" , gpe); |
851 | (void)acpi_mask_gpe(gpe_device: handle, gpe_number: gpe, TRUE); |
852 | } |
853 | } |
854 | } |
855 | |
856 | void acpi_irq_stats_init(void) |
857 | { |
858 | acpi_status status; |
859 | int i; |
860 | |
861 | if (all_counters) |
862 | return; |
863 | |
864 | num_gpes = acpi_current_gpe_count; |
865 | num_counters = num_gpes + ACPI_NUM_FIXED_EVENTS + NUM_COUNTERS_EXTRA; |
866 | |
867 | all_attrs = kcalloc(n: num_counters + 1, size: sizeof(*all_attrs), GFP_KERNEL); |
868 | if (all_attrs == NULL) |
869 | return; |
870 | |
871 | all_counters = kcalloc(n: num_counters, size: sizeof(*all_counters), GFP_KERNEL); |
872 | if (all_counters == NULL) |
873 | goto fail; |
874 | |
875 | status = acpi_install_global_event_handler(handler: acpi_global_event_handler, NULL); |
876 | if (ACPI_FAILURE(status)) |
877 | goto fail; |
878 | |
879 | counter_attrs = kcalloc(n: num_counters, size: sizeof(*counter_attrs), GFP_KERNEL); |
880 | if (counter_attrs == NULL) |
881 | goto fail; |
882 | |
883 | for (i = 0; i < num_counters; ++i) { |
884 | char buffer[12]; |
885 | char *name; |
886 | |
887 | if (i < num_gpes) |
888 | sprintf(buf: buffer, fmt: "gpe%02X" , i); |
889 | else if (i == num_gpes + ACPI_EVENT_PMTIMER) |
890 | sprintf(buf: buffer, fmt: "ff_pmtimer" ); |
891 | else if (i == num_gpes + ACPI_EVENT_GLOBAL) |
892 | sprintf(buf: buffer, fmt: "ff_gbl_lock" ); |
893 | else if (i == num_gpes + ACPI_EVENT_POWER_BUTTON) |
894 | sprintf(buf: buffer, fmt: "ff_pwr_btn" ); |
895 | else if (i == num_gpes + ACPI_EVENT_SLEEP_BUTTON) |
896 | sprintf(buf: buffer, fmt: "ff_slp_btn" ); |
897 | else if (i == num_gpes + ACPI_EVENT_RTC) |
898 | sprintf(buf: buffer, fmt: "ff_rt_clk" ); |
899 | else if (i == num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_GPE) |
900 | sprintf(buf: buffer, fmt: "gpe_all" ); |
901 | else if (i == num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_SCI) |
902 | sprintf(buf: buffer, fmt: "sci" ); |
903 | else if (i == num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_SCI_NOT) |
904 | sprintf(buf: buffer, fmt: "sci_not" ); |
905 | else if (i == num_gpes + ACPI_NUM_FIXED_EVENTS + COUNT_ERROR) |
906 | sprintf(buf: buffer, fmt: "error" ); |
907 | else |
908 | sprintf(buf: buffer, fmt: "bug%02X" , i); |
909 | |
910 | name = kstrdup(s: buffer, GFP_KERNEL); |
911 | if (name == NULL) |
912 | goto fail; |
913 | |
914 | sysfs_attr_init(&counter_attrs[i].attr); |
915 | counter_attrs[i].attr.name = name; |
916 | counter_attrs[i].attr.mode = 0644; |
917 | counter_attrs[i].show = counter_show; |
918 | counter_attrs[i].store = counter_set; |
919 | |
920 | all_attrs[i] = &counter_attrs[i].attr; |
921 | } |
922 | |
923 | interrupt_stats_attr_group.attrs = all_attrs; |
924 | if (!sysfs_create_group(kobj: acpi_kobj, grp: &interrupt_stats_attr_group)) |
925 | return; |
926 | |
927 | fail: |
928 | delete_gpe_attr_array(); |
929 | } |
930 | |
931 | static void __exit interrupt_stats_exit(void) |
932 | { |
933 | sysfs_remove_group(kobj: acpi_kobj, grp: &interrupt_stats_attr_group); |
934 | |
935 | delete_gpe_attr_array(); |
936 | } |
937 | |
938 | static ssize_t pm_profile_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
939 | { |
940 | return sprintf(buf, fmt: "%d\n" , acpi_gbl_FADT.preferred_profile); |
941 | } |
942 | |
943 | static const struct kobj_attribute pm_profile_attr = __ATTR_RO(pm_profile); |
944 | |
945 | static ssize_t enabled_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
946 | { |
947 | struct acpi_hotplug_profile *hotplug = to_acpi_hotplug_profile(kobj); |
948 | |
949 | return sprintf(buf, fmt: "%d\n" , hotplug->enabled); |
950 | } |
951 | |
952 | static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr, |
953 | const char *buf, size_t size) |
954 | { |
955 | struct acpi_hotplug_profile *hotplug = to_acpi_hotplug_profile(kobj); |
956 | unsigned int val; |
957 | |
958 | if (kstrtouint(s: buf, base: 10, res: &val) || val > 1) |
959 | return -EINVAL; |
960 | |
961 | acpi_scan_hotplug_enabled(hotplug, val); |
962 | return size; |
963 | } |
964 | |
965 | static struct kobj_attribute hotplug_enabled_attr = __ATTR_RW(enabled); |
966 | |
967 | static struct attribute *hotplug_profile_attrs[] = { |
968 | &hotplug_enabled_attr.attr, |
969 | NULL |
970 | }; |
971 | ATTRIBUTE_GROUPS(hotplug_profile); |
972 | |
973 | static const struct kobj_type acpi_hotplug_profile_ktype = { |
974 | .sysfs_ops = &kobj_sysfs_ops, |
975 | .default_groups = hotplug_profile_groups, |
976 | }; |
977 | |
978 | void acpi_sysfs_add_hotplug_profile(struct acpi_hotplug_profile *hotplug, |
979 | const char *name) |
980 | { |
981 | int error; |
982 | |
983 | if (!hotplug_kobj) |
984 | goto err_out; |
985 | |
986 | error = kobject_init_and_add(kobj: &hotplug->kobj, |
987 | ktype: &acpi_hotplug_profile_ktype, parent: hotplug_kobj, fmt: "%s" , name); |
988 | if (error) { |
989 | kobject_put(kobj: &hotplug->kobj); |
990 | goto err_out; |
991 | } |
992 | |
993 | kobject_uevent(kobj: &hotplug->kobj, action: KOBJ_ADD); |
994 | return; |
995 | |
996 | err_out: |
997 | pr_err("Unable to add hotplug profile '%s'\n" , name); |
998 | } |
999 | |
1000 | static ssize_t force_remove_show(struct kobject *kobj, |
1001 | struct kobj_attribute *attr, char *buf) |
1002 | { |
1003 | return sprintf(buf, fmt: "%d\n" , 0); |
1004 | } |
1005 | |
1006 | static ssize_t force_remove_store(struct kobject *kobj, |
1007 | struct kobj_attribute *attr, |
1008 | const char *buf, size_t size) |
1009 | { |
1010 | bool val; |
1011 | int ret; |
1012 | |
1013 | ret = kstrtobool(s: buf, res: &val); |
1014 | if (ret < 0) |
1015 | return ret; |
1016 | |
1017 | if (val) { |
1018 | pr_err("Enabling force_remove is not supported anymore. Please report to linux-acpi@vger.kernel.org if you depend on this functionality\n" ); |
1019 | return -EINVAL; |
1020 | } |
1021 | return size; |
1022 | } |
1023 | |
1024 | static const struct kobj_attribute force_remove_attr = __ATTR_RW(force_remove); |
1025 | |
1026 | int __init acpi_sysfs_init(void) |
1027 | { |
1028 | int result; |
1029 | |
1030 | result = acpi_tables_sysfs_init(); |
1031 | if (result) |
1032 | return result; |
1033 | |
1034 | hotplug_kobj = kobject_create_and_add(name: "hotplug" , parent: acpi_kobj); |
1035 | if (!hotplug_kobj) |
1036 | return -ENOMEM; |
1037 | |
1038 | result = sysfs_create_file(kobj: hotplug_kobj, attr: &force_remove_attr.attr); |
1039 | if (result) |
1040 | return result; |
1041 | |
1042 | result = sysfs_create_file(kobj: acpi_kobj, attr: &pm_profile_attr.attr); |
1043 | return result; |
1044 | } |
1045 | |