1 | #include <uacpi/internal/tables.h> |
2 | #include <uacpi/internal/utilities.h> |
3 | #include <uacpi/internal/stdlib.h> |
4 | #include <uacpi/internal/interpreter.h> |
5 | |
6 | #ifndef UACPI_STATIC_TABLE_ARRAY_LEN |
7 | #define UACPI_STATIC_TABLE_ARRAY_LEN 16 |
8 | #endif |
9 | |
10 | DYNAMIC_ARRAY_WITH_INLINE_STORAGE( |
11 | table_array, struct uacpi_installed_table, UACPI_STATIC_TABLE_ARRAY_LEN |
12 | ) |
13 | DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL( |
14 | table_array, struct uacpi_installed_table, |
15 | ) |
16 | |
17 | static struct table_array tables; |
18 | static uacpi_table_installation_handler installation_handler; |
19 | static uacpi_handle table_mutex; |
20 | |
21 | uacpi_status uacpi_initialize_tables(void) |
22 | { |
23 | table_mutex = uacpi_kernel_create_mutex(); |
24 | if (uacpi_unlikely(table_mutex == UACPI_NULL)) |
25 | return UACPI_STATUS_OUT_OF_MEMORY; |
26 | |
27 | return UACPI_STATUS_OK; |
28 | } |
29 | |
30 | uacpi_status uacpi_set_table_installation_handler( |
31 | uacpi_table_installation_handler handler |
32 | ) |
33 | { |
34 | uacpi_status ret = UACPI_STATUS_OK; |
35 | |
36 | /* |
37 | * The mutex might not exist yet because uacpi_initialize_tables might not |
38 | * have been called at this point, allow that possibility since the user |
39 | * might want to install this handler early. |
40 | */ |
41 | if (table_mutex != UACPI_NULL) |
42 | UACPI_MUTEX_ACQUIRE(table_mutex); |
43 | |
44 | if (installation_handler != UACPI_NULL && handler != UACPI_NULL) |
45 | goto out; |
46 | |
47 | installation_handler = handler; |
48 | |
49 | out: |
50 | if (table_mutex != UACPI_NULL) |
51 | UACPI_MUTEX_RELEASE(table_mutex); |
52 | return ret; |
53 | } |
54 | |
55 | static uacpi_status initialize_fadt(struct acpi_sdt_hdr*); |
56 | |
57 | static uacpi_object_name fadt_signature = { |
58 | .text = { ACPI_FADT_SIGNATURE }, |
59 | }; |
60 | |
61 | static uacpi_object_name dsdt_signature = { |
62 | .text = { ACPI_DSDT_SIGNATURE }, |
63 | }; |
64 | |
65 | static uacpi_object_name facs_signature = { |
66 | .text = { ACPI_FACS_SIGNATURE }, |
67 | }; |
68 | |
69 | static uacpi_u8 table_checksum(void *table, uacpi_size size) |
70 | { |
71 | uacpi_u8 *bytes = table; |
72 | uacpi_u8 csum = 0; |
73 | uacpi_size i; |
74 | |
75 | for (i = 0; i < size; ++i) |
76 | csum += bytes[i]; |
77 | |
78 | return csum; |
79 | } |
80 | |
81 | uacpi_status uacpi_verify_table_checksum(void *table, uacpi_size size) |
82 | { |
83 | uacpi_status ret = UACPI_STATUS_OK; |
84 | uacpi_u8 csum; |
85 | |
86 | csum = table_checksum(table, size); |
87 | |
88 | if (uacpi_unlikely(csum != 0)) { |
89 | enum uacpi_log_level lvl = UACPI_LOG_WARN; |
90 | struct acpi_sdt_hdr *hdr = table; |
91 | |
92 | if (uacpi_check_flag(UACPI_FLAG_BAD_CSUM_FATAL)) { |
93 | ret = UACPI_STATUS_BAD_CHECKSUM; |
94 | lvl = UACPI_LOG_ERROR; |
95 | } |
96 | |
97 | uacpi_log_lvl( |
98 | lvl, "invalid table " UACPI_PRI_TBL_HDR" checksum %d!\n" , |
99 | UACPI_FMT_TBL_HDR(hdr), csum |
100 | ); |
101 | } |
102 | |
103 | return ret; |
104 | } |
105 | |
106 | uacpi_status uacpi_check_table_signature(void *table, const uacpi_char *expect) |
107 | { |
108 | uacpi_status ret = UACPI_STATUS_OK; |
109 | |
110 | if (uacpi_memcmp(lhs: table, rhs: expect, count: 4) != 0) { |
111 | enum uacpi_log_level lvl = UACPI_LOG_WARN; |
112 | struct acpi_sdt_hdr *hdr = table; |
113 | |
114 | if (uacpi_check_flag(UACPI_FLAG_BAD_TBL_SIGNATURE_FATAL)) { |
115 | ret = UACPI_STATUS_INVALID_SIGNATURE; |
116 | lvl = UACPI_LOG_ERROR; |
117 | } |
118 | |
119 | uacpi_log_lvl( |
120 | lvl, |
121 | "invalid table " UACPI_PRI_TBL_HDR" signature (expected '%.4s')\n" , |
122 | UACPI_FMT_TBL_HDR(hdr), expect |
123 | ); |
124 | } |
125 | |
126 | return ret; |
127 | } |
128 | |
129 | static uacpi_status table_alloc( |
130 | struct uacpi_installed_table **out_tbl, uacpi_size *out_idx |
131 | ) |
132 | { |
133 | struct uacpi_installed_table *tbl; |
134 | |
135 | tbl = table_array_alloc(arr: &tables); |
136 | if (uacpi_unlikely(tbl == UACPI_NULL)) |
137 | return UACPI_STATUS_OUT_OF_MEMORY; |
138 | |
139 | *out_tbl = tbl; |
140 | *out_idx = table_array_size(arr: &tables) - 1; |
141 | return UACPI_STATUS_OK; |
142 | } |
143 | |
144 | static uacpi_status |
145 | get_external_table_signature_and_length(uacpi_phys_addr phys_addr, |
146 | uacpi_object_name *out_sign, |
147 | uacpi_u32 *out_len) |
148 | { |
149 | struct acpi_sdt_hdr *hdr; |
150 | |
151 | hdr = uacpi_kernel_map(addr: phys_addr, len: sizeof(struct acpi_sdt_hdr)); |
152 | if (uacpi_unlikely(!hdr)) |
153 | return UACPI_STATUS_MAPPING_FAILED; |
154 | |
155 | uacpi_memcpy(dest: out_sign, src: hdr->signature, count: sizeof(hdr->signature)); |
156 | *out_len = hdr->length; |
157 | |
158 | uacpi_kernel_unmap(addr: hdr, len: sizeof(struct acpi_sdt_hdr)); |
159 | return UACPI_STATUS_OK; |
160 | } |
161 | |
162 | static uacpi_status verify_and_install_table( |
163 | uacpi_object_name signature, uacpi_u32 length, |
164 | uacpi_phys_addr phys_addr, void *virt_addr, enum uacpi_table_origin origin, |
165 | uacpi_table *out_table |
166 | ) |
167 | { |
168 | uacpi_status ret; |
169 | struct uacpi_installed_table *table; |
170 | uacpi_size idx; |
171 | |
172 | /* |
173 | * FACS is the only(?) table without a checksum because it has OSPM |
174 | * writable fields. Don't try to validate it here. |
175 | */ |
176 | if (signature.id != facs_signature.id) { |
177 | ret = uacpi_verify_table_checksum(table: virt_addr, size: length); |
178 | if (uacpi_unlikely_error(ret)) |
179 | return ret; |
180 | } |
181 | |
182 | if (signature.id == dsdt_signature.id) { |
183 | struct acpi_sdt_hdr *hdr = virt_addr; |
184 | g_uacpi_rt_ctx.is_rev1 = hdr->revision < 2; |
185 | } |
186 | |
187 | if (signature.id == fadt_signature.id) { |
188 | ret = initialize_fadt(virt_addr); |
189 | if (uacpi_unlikely_error(ret)) |
190 | return ret; |
191 | } |
192 | |
193 | ret = table_alloc(out_tbl: &table, out_idx: &idx); |
194 | if (uacpi_unlikely_error(ret)) |
195 | return ret; |
196 | |
197 | table->signature = signature; |
198 | table->phys_addr = phys_addr; |
199 | table->ptr = virt_addr; |
200 | table->length = length; |
201 | table->flags = 0; |
202 | table->origin = origin; |
203 | |
204 | if (out_table != UACPI_NULL) { |
205 | out_table->ptr = virt_addr; |
206 | out_table->index = idx; |
207 | } |
208 | |
209 | return UACPI_STATUS_OK; |
210 | } |
211 | |
212 | static uacpi_status table_install_physical_with_origin_unlocked( |
213 | uacpi_phys_addr phys, enum uacpi_table_origin origin, |
214 | const uacpi_char *expected_signature, uacpi_table *out_table |
215 | ); |
216 | static uacpi_status table_install_with_origin_unlocked( |
217 | void *virt, enum uacpi_table_origin origin, uacpi_table *out_table |
218 | ); |
219 | |
220 | static uacpi_status handle_table_override( |
221 | uacpi_table_installation_disposition disposition, uacpi_u64 address, |
222 | uacpi_table *out_table |
223 | ) |
224 | { |
225 | uacpi_status ret; |
226 | |
227 | switch (disposition) { |
228 | case UACPI_TABLE_INSTALLATION_DISPOSITON_VIRTUAL_OVERRIDE: |
229 | ret = table_install_with_origin_unlocked( |
230 | UACPI_VIRT_ADDR_TO_PTR((uacpi_virt_addr)address), |
231 | origin: UACPI_TABLE_ORIGIN_HOST_VIRTUAL, |
232 | out_table |
233 | ); |
234 | return ret; |
235 | case UACPI_TABLE_INSTALLATION_DISPOSITON_PHYSICAL_OVERRIDE: |
236 | return table_install_physical_with_origin_unlocked( |
237 | phys: (uacpi_phys_addr)address, |
238 | origin: UACPI_TABLE_ORIGIN_HOST_PHYSICAL, |
239 | UACPI_NULL, |
240 | out_table |
241 | ); |
242 | default: |
243 | uacpi_error("invalid table installation disposition %d\n" , disposition); |
244 | return UACPI_STATUS_INTERNAL_ERROR; |
245 | } |
246 | } |
247 | |
248 | static uacpi_status table_install_physical_with_origin_unlocked( |
249 | uacpi_phys_addr phys, enum uacpi_table_origin origin, |
250 | const uacpi_char *expected_signature, uacpi_table *out_table |
251 | ) |
252 | { |
253 | uacpi_object_name signature; |
254 | uacpi_u32 length; |
255 | void *virt; |
256 | uacpi_status ret; |
257 | |
258 | ret = get_external_table_signature_and_length(phys_addr: phys, out_sign: &signature, out_len: &length); |
259 | if (uacpi_unlikely_error(ret)) |
260 | return ret; |
261 | |
262 | if (uacpi_unlikely(length < sizeof(struct acpi_sdt_hdr))) { |
263 | uacpi_error("invalid table '%.4s' (0x016%" UACPI_PRIX64") size: %u\n" , |
264 | signature.text, UACPI_FMT64(phys), length); |
265 | return UACPI_STATUS_INVALID_TABLE_LENGTH; |
266 | } |
267 | |
268 | virt = uacpi_kernel_map(addr: phys, len: length); |
269 | if (uacpi_unlikely(!virt)) |
270 | return UACPI_STATUS_MAPPING_FAILED; |
271 | |
272 | if (expected_signature != UACPI_NULL) { |
273 | ret = uacpi_check_table_signature(table: virt, expect: expected_signature); |
274 | if (uacpi_unlikely_error(ret)) |
275 | goto out; |
276 | } |
277 | |
278 | if (origin == UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL && |
279 | installation_handler != UACPI_NULL) { |
280 | uacpi_u64 override; |
281 | uacpi_table_installation_disposition disposition; |
282 | |
283 | disposition = installation_handler(virt, &override); |
284 | |
285 | switch (disposition) { |
286 | case UACPI_TABLE_INSTALLATION_DISPOSITON_ALLOW: |
287 | break; |
288 | case UACPI_TABLE_INSTALLATION_DISPOSITON_DENY: |
289 | uacpi_info( |
290 | "table '%.4s' (0x016%" UACPI_PRIX64") installation denied " |
291 | "by host\n" , signature.text, UACPI_FMT64(phys) |
292 | ); |
293 | ret = UACPI_STATUS_DENIED; |
294 | goto out; |
295 | |
296 | default: |
297 | uacpi_info( |
298 | "table '%.4s' (0x016%" UACPI_PRIX64") installation " |
299 | "overriden by host\n" , signature.text, UACPI_FMT64(phys) |
300 | ); |
301 | |
302 | ret = handle_table_override(disposition, address: override, out_table); |
303 | if (uacpi_likely_success(ret)) |
304 | ret = UACPI_STATUS_OVERRIDEN; |
305 | |
306 | goto out; |
307 | } |
308 | } |
309 | |
310 | ret = verify_and_install_table(signature, length, phys_addr: phys, virt_addr: virt, origin, |
311 | out_table); |
312 | out: |
313 | if (uacpi_unlikely_error(ret)) { |
314 | uacpi_kernel_unmap(addr: virt, len: length); |
315 | return ret; |
316 | } |
317 | |
318 | return UACPI_STATUS_OK; |
319 | } |
320 | |
321 | uacpi_status uacpi_table_install_physical_with_origin( |
322 | uacpi_phys_addr phys, enum uacpi_table_origin origin, uacpi_table *out_table |
323 | ) |
324 | { |
325 | uacpi_status ret; |
326 | |
327 | UACPI_MUTEX_ACQUIRE(table_mutex); |
328 | ret = table_install_physical_with_origin_unlocked( |
329 | phys, origin, UACPI_NULL, out_table |
330 | ); |
331 | UACPI_MUTEX_RELEASE(table_mutex); |
332 | |
333 | return ret; |
334 | } |
335 | |
336 | static uacpi_status table_install_with_origin_unlocked( |
337 | void *virt, enum uacpi_table_origin origin, uacpi_table *out_table |
338 | ) |
339 | { |
340 | uacpi_object_name signature; |
341 | struct acpi_sdt_hdr *hdr = virt; |
342 | uacpi_u32 length; |
343 | |
344 | uacpi_memcpy(dest: &signature, src: hdr->signature, count: sizeof(hdr->signature)); |
345 | length = hdr->length; |
346 | |
347 | if (uacpi_unlikely(length < sizeof(struct acpi_sdt_hdr))) { |
348 | uacpi_error("invalid table '%.4s' (%p) size: %u\n" , |
349 | signature.text, virt, length); |
350 | return UACPI_STATUS_INVALID_TABLE_LENGTH; |
351 | } |
352 | |
353 | if (origin == UACPI_TABLE_ORIGIN_FIRMWARE_VIRTUAL && |
354 | installation_handler != UACPI_NULL) { |
355 | uacpi_u64 override; |
356 | uacpi_table_installation_disposition disposition; |
357 | |
358 | disposition = installation_handler(virt, &override); |
359 | |
360 | switch (disposition) { |
361 | case UACPI_TABLE_INSTALLATION_DISPOSITON_ALLOW: |
362 | break; |
363 | case UACPI_TABLE_INSTALLATION_DISPOSITON_DENY: |
364 | uacpi_info( |
365 | "table " UACPI_PRI_TBL_HDR" installation denied by host\n" , |
366 | UACPI_FMT_TBL_HDR(hdr) |
367 | ); |
368 | return UACPI_STATUS_DENIED; |
369 | |
370 | default: { |
371 | uacpi_status ret; |
372 | uacpi_info( |
373 | "table " UACPI_PRI_TBL_HDR" installation overriden by host\n" , |
374 | UACPI_FMT_TBL_HDR(hdr) |
375 | ); |
376 | |
377 | ret = handle_table_override(disposition, address: override, out_table); |
378 | if (uacpi_likely_success(ret)) |
379 | ret = UACPI_STATUS_OVERRIDEN; |
380 | |
381 | return ret; |
382 | } |
383 | } |
384 | } |
385 | |
386 | return verify_and_install_table( |
387 | signature, length, phys_addr: 0, virt_addr: virt, origin, out_table |
388 | ); |
389 | } |
390 | |
391 | uacpi_status uacpi_table_install_with_origin( |
392 | void *virt, enum uacpi_table_origin origin, uacpi_table *out_table |
393 | ) |
394 | { |
395 | uacpi_status ret; |
396 | |
397 | UACPI_MUTEX_ACQUIRE(table_mutex); |
398 | ret = table_install_with_origin_unlocked(virt, origin, out_table); |
399 | UACPI_MUTEX_RELEASE(table_mutex); |
400 | |
401 | return ret; |
402 | } |
403 | |
404 | uacpi_status uacpi_table_install(void *virt, uacpi_table *out_table) |
405 | { |
406 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
407 | |
408 | return uacpi_table_install_with_origin( |
409 | virt, origin: UACPI_TABLE_ORIGIN_HOST_VIRTUAL, out_table |
410 | ); |
411 | } |
412 | |
413 | uacpi_status uacpi_table_install_physical( |
414 | uacpi_phys_addr addr, uacpi_table *out_table |
415 | ) |
416 | { |
417 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
418 | |
419 | return uacpi_table_install_physical_with_origin( |
420 | phys: addr, origin: UACPI_TABLE_ORIGIN_HOST_PHYSICAL, out_table |
421 | ); |
422 | } |
423 | |
424 | uacpi_status uacpi_for_each_table( |
425 | uacpi_size base_idx, uacpi_table_iteration_callback cb, void *user |
426 | ) |
427 | { |
428 | uacpi_size idx; |
429 | struct uacpi_installed_table *tbl; |
430 | enum uacpi_table_iteration_decision ret; |
431 | |
432 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
433 | |
434 | UACPI_MUTEX_ACQUIRE(table_mutex); |
435 | |
436 | for (idx = base_idx; idx < table_array_size(arr: &tables); ++idx) { |
437 | tbl = table_array_at(arr: &tables, idx); |
438 | |
439 | ret = cb(user, tbl, idx); |
440 | if (ret == UACPI_TABLE_ITERATION_DECISION_BREAK) |
441 | break; |
442 | } |
443 | |
444 | UACPI_MUTEX_RELEASE(table_mutex); |
445 | return UACPI_STATUS_OK; |
446 | } |
447 | |
448 | enum search_type { |
449 | SEARCH_TYPE_BY_ID, |
450 | SEARCH_TYPE_MATCH, |
451 | }; |
452 | |
453 | struct table_search_ctx { |
454 | union { |
455 | const uacpi_table_identifiers *id; |
456 | uacpi_table_match_callback match_cb; |
457 | }; |
458 | |
459 | uacpi_table *out_table; |
460 | uacpi_u8 search_type; |
461 | uacpi_bool found; |
462 | }; |
463 | |
464 | static enum uacpi_table_iteration_decision do_search_tables( |
465 | void *user, struct uacpi_installed_table *tbl, uacpi_size idx |
466 | ) |
467 | { |
468 | struct table_search_ctx *ctx = user; |
469 | uacpi_table *out_table; |
470 | |
471 | switch (ctx->search_type) { |
472 | case SEARCH_TYPE_BY_ID: { |
473 | const uacpi_table_identifiers *id = ctx->id; |
474 | |
475 | if (id->signature.id != tbl->signature.id) |
476 | return UACPI_TABLE_ITERATION_DECISION_CONTINUE; |
477 | |
478 | if (id->oemid[0] != '\0' && |
479 | uacpi_memcmp(lhs: id->oemid, rhs: tbl->hdr->oemid, count: sizeof(id->oemid)) != 0) |
480 | return UACPI_TABLE_ITERATION_DECISION_CONTINUE; |
481 | |
482 | if (id->oem_table_id[0] != '\0' && |
483 | uacpi_memcmp(lhs: id->oem_table_id, rhs: tbl->hdr->oem_table_id, |
484 | count: sizeof(id->oem_table_id)) != 0) |
485 | return UACPI_TABLE_ITERATION_DECISION_CONTINUE; |
486 | |
487 | break; |
488 | } |
489 | |
490 | case SEARCH_TYPE_MATCH: |
491 | if (!ctx->match_cb(tbl)) |
492 | return UACPI_TABLE_ITERATION_DECISION_CONTINUE; |
493 | |
494 | } |
495 | |
496 | out_table = ctx->out_table; |
497 | out_table->ptr = tbl->ptr; |
498 | out_table->index = idx; |
499 | ctx->found = UACPI_TRUE; |
500 | |
501 | return UACPI_TABLE_ITERATION_DECISION_BREAK; |
502 | } |
503 | |
504 | uacpi_status uacpi_table_match( |
505 | uacpi_size base_idx, uacpi_table_match_callback cb, uacpi_table *out_table |
506 | ) |
507 | { |
508 | struct table_search_ctx ctx = { |
509 | .match_cb = cb, |
510 | .search_type = SEARCH_TYPE_MATCH, |
511 | .out_table = out_table, |
512 | }; |
513 | |
514 | uacpi_for_each_table(base_idx, cb: do_search_tables, user: &ctx); |
515 | if (!ctx.found) |
516 | return UACPI_STATUS_NOT_FOUND; |
517 | |
518 | return UACPI_STATUS_OK; |
519 | } |
520 | |
521 | |
522 | static uacpi_status find_table( |
523 | uacpi_size base_idx, const uacpi_table_identifiers *id, |
524 | uacpi_table *out_table |
525 | ) |
526 | { |
527 | struct table_search_ctx ctx = { |
528 | .id = id, |
529 | .out_table = out_table, |
530 | .search_type = SEARCH_TYPE_BY_ID, |
531 | }; |
532 | |
533 | uacpi_for_each_table(base_idx, cb: do_search_tables, user: &ctx); |
534 | if (!ctx.found) |
535 | return UACPI_STATUS_NOT_FOUND; |
536 | |
537 | return UACPI_STATUS_OK; |
538 | } |
539 | |
540 | uacpi_status uacpi_table_find_by_signature( |
541 | const uacpi_char *signature_string, struct uacpi_table *out_table |
542 | ) |
543 | { |
544 | struct uacpi_table_identifiers id = { |
545 | .signature = { |
546 | .text = { |
547 | signature_string[0], |
548 | signature_string[1], |
549 | signature_string[2], |
550 | signature_string[3] |
551 | } |
552 | } |
553 | }; |
554 | |
555 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
556 | return find_table(base_idx: 0, id: &id, out_table); |
557 | } |
558 | |
559 | uacpi_status |
560 | uacpi_table_find_next_with_same_signature(uacpi_table *in_out_table) |
561 | { |
562 | struct uacpi_table_identifiers id = { 0 }; |
563 | |
564 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
565 | |
566 | uacpi_memcpy(dest: &id.signature, src: in_out_table->hdr->signature, |
567 | count: sizeof(id.signature)); |
568 | |
569 | return find_table(base_idx: in_out_table->index + 1, id: &id, out_table: in_out_table); |
570 | } |
571 | |
572 | uacpi_status uacpi_table_find( |
573 | const uacpi_table_identifiers *id, uacpi_table *out_table |
574 | ) |
575 | { |
576 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
577 | return find_table(base_idx: 0, id, out_table); |
578 | } |
579 | |
580 | static uacpi_status table_ctl( |
581 | uacpi_size idx, uacpi_u8 expect_set, uacpi_u8 expect_clear, |
582 | uacpi_u8 set, uacpi_u8 clear, void **out_tbl |
583 | ) |
584 | { |
585 | uacpi_status ret = UACPI_STATUS_OK; |
586 | struct uacpi_installed_table *tbl; |
587 | |
588 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
589 | |
590 | UACPI_MUTEX_ACQUIRE(table_mutex); |
591 | if (uacpi_unlikely(table_array_size(&tables) <= idx)) { |
592 | uacpi_error( |
593 | "requested invalid table index %zu (%zu tables installed)\n" , |
594 | idx, table_array_size(&tables) |
595 | ); |
596 | ret = UACPI_STATUS_INVALID_ARGUMENT; |
597 | goto out; |
598 | } |
599 | |
600 | tbl = table_array_at(arr: &tables, idx); |
601 | if (uacpi_unlikely((tbl->flags & expect_set) != expect_set)) { |
602 | uacpi_error( |
603 | "unexpected table '%.4s' flags %02X, expected %02X to be set\n" , |
604 | tbl->signature.text, tbl->flags, expect_set |
605 | ); |
606 | ret = UACPI_STATUS_INVALID_ARGUMENT; |
607 | goto out; |
608 | } |
609 | |
610 | if (uacpi_unlikely((tbl->flags & expect_clear) != 0)) { |
611 | uacpi_error( |
612 | "unexpected table '%.4s' flags %02X, expected %02X to be clear\n" , |
613 | tbl->signature.text, tbl->flags, expect_clear |
614 | ); |
615 | ret = UACPI_STATUS_ALREADY_EXISTS; |
616 | goto out; |
617 | } |
618 | |
619 | if (out_tbl != UACPI_NULL) |
620 | *out_tbl = tbl->ptr; |
621 | |
622 | tbl->flags |= set; |
623 | tbl->flags &= ~clear; |
624 | |
625 | out: |
626 | UACPI_MUTEX_RELEASE(table_mutex); |
627 | return ret; |
628 | } |
629 | |
630 | uacpi_status uacpi_table_load_with_cause( |
631 | uacpi_size idx, enum uacpi_table_load_cause cause |
632 | ) |
633 | { |
634 | uacpi_status ret; |
635 | void *tbl; |
636 | uacpi_u8 set = UACPI_TABLE_LOADED; |
637 | uacpi_u8 expect_clear = set; |
638 | |
639 | ret = table_ctl(idx, expect_set: 0, expect_clear, set, clear: 0, out_tbl: &tbl); |
640 | if (uacpi_unlikely_error(ret)) |
641 | return ret; |
642 | |
643 | return uacpi_execute_table(tbl, cause); |
644 | } |
645 | |
646 | uacpi_status uacpi_table_load(uacpi_size idx) |
647 | { |
648 | return uacpi_table_load_with_cause(idx, cause: UACPI_TABLE_LOAD_CAUSE_HOST); |
649 | } |
650 | |
651 | void uacpi_table_mark_as_loaded(uacpi_size idx) |
652 | { |
653 | table_ctl(idx, expect_set: 0, expect_clear: 0, UACPI_TABLE_LOADED, clear: 0, UACPI_NULL); |
654 | } |
655 | |
656 | uacpi_u16 fadt_version_sizes[] = { |
657 | 116, 132, 244, 244, 268, 276 |
658 | }; |
659 | |
660 | static void fadt_ensure_correct_revision(struct acpi_fadt *fadt) |
661 | { |
662 | uacpi_size current_rev, rev; |
663 | |
664 | current_rev = fadt->hdr.revision; |
665 | |
666 | for (rev = 0; rev < UACPI_ARRAY_SIZE(fadt_version_sizes); ++rev) { |
667 | if (fadt->hdr.length <= fadt_version_sizes[rev]) |
668 | break; |
669 | } |
670 | |
671 | if (rev == UACPI_ARRAY_SIZE(fadt_version_sizes)) { |
672 | uacpi_trace( |
673 | "FADT revision (%zu) is likely greater than the last " |
674 | "supported, reducing to %zu\n" , current_rev, rev |
675 | ); |
676 | fadt->hdr.revision = rev; |
677 | return; |
678 | } |
679 | |
680 | rev++; |
681 | |
682 | if (current_rev != rev && !(rev == 3 && current_rev == 4)) { |
683 | uacpi_warn( |
684 | "FADT length %u doesn't match expected for revision %zu, " |
685 | "assuming version %zu\n" , fadt->hdr.length, current_rev, |
686 | rev |
687 | ); |
688 | fadt->hdr.revision = rev; |
689 | } |
690 | } |
691 | |
692 | static void gas_init_system_io( |
693 | struct acpi_gas *gas, uacpi_u64 address, uacpi_u8 byte_size |
694 | ) |
695 | { |
696 | gas->address = address; |
697 | gas->address_space_id = UACPI_ADDRESS_SPACE_SYSTEM_IO; |
698 | gas->register_bit_width = UACPI_MIN(255, byte_size * 8); |
699 | gas->register_bit_offset = 0; |
700 | gas->access_size = 0; |
701 | } |
702 | |
703 | |
704 | struct register_description { |
705 | uacpi_size offset, xoffset; |
706 | uacpi_size length_offset; |
707 | }; |
708 | |
709 | #define fadt_offset(field) uacpi_offsetof(struct acpi_fadt, field) |
710 | |
711 | /* |
712 | * We convert all the legacy registers into GAS format and write them into |
713 | * the x_* fields for convenience and faster access at runtime. |
714 | */ |
715 | static struct register_description fadt_registers[] = { |
716 | { |
717 | .offset = fadt_offset(pm1a_evt_blk), |
718 | .xoffset = fadt_offset(x_pm1a_evt_blk), |
719 | .length_offset = fadt_offset(pm1_evt_len), |
720 | }, |
721 | { |
722 | .offset = fadt_offset(pm1b_evt_blk), |
723 | .xoffset = fadt_offset(x_pm1b_evt_blk), |
724 | .length_offset = fadt_offset(pm1_evt_len), |
725 | }, |
726 | { |
727 | .offset = fadt_offset(pm1a_cnt_blk), |
728 | .xoffset = fadt_offset(x_pm1a_cnt_blk), |
729 | .length_offset = fadt_offset(pm1_cnt_len), |
730 | }, |
731 | { |
732 | .offset = fadt_offset(pm1b_cnt_blk), |
733 | .xoffset = fadt_offset(x_pm1b_cnt_blk), |
734 | .length_offset = fadt_offset(pm1_cnt_len), |
735 | }, |
736 | { |
737 | .offset = fadt_offset(pm2_cnt_blk), |
738 | .xoffset = fadt_offset(x_pm2_cnt_blk), |
739 | .length_offset = fadt_offset(pm2_cnt_len), |
740 | }, |
741 | { |
742 | .offset = fadt_offset(pm_tmr_blk), |
743 | .xoffset = fadt_offset(x_pm_tmr_blk), |
744 | .length_offset = fadt_offset(pm_tmr_len), |
745 | }, |
746 | { |
747 | .offset = fadt_offset(gpe0_blk), |
748 | .xoffset = fadt_offset(x_gpe0_blk), |
749 | .length_offset = fadt_offset(gpe0_blk_len), |
750 | }, |
751 | { |
752 | .offset = fadt_offset(gpe1_blk), |
753 | .xoffset = fadt_offset(x_gpe1_blk), |
754 | .length_offset = fadt_offset(gpe1_blk_len), |
755 | }, |
756 | }; |
757 | |
758 | static void *fadt_relative(uacpi_size offset) |
759 | { |
760 | return ((uacpi_u8*)&g_uacpi_rt_ctx.fadt) + offset; |
761 | } |
762 | |
763 | static void convert_registers_to_gas(void) |
764 | { |
765 | uacpi_size i; |
766 | struct register_description *desc; |
767 | struct acpi_gas *gas; |
768 | uacpi_u32 legacy_addr; |
769 | uacpi_u8 length; |
770 | |
771 | for (i = 0; i < UACPI_ARRAY_SIZE(fadt_registers); ++i) { |
772 | desc = &fadt_registers[i]; |
773 | |
774 | legacy_addr = *(uacpi_u32*)fadt_relative(offset: desc->offset); |
775 | length = *(uacpi_u8*)fadt_relative(offset: desc->length_offset); |
776 | gas = fadt_relative(offset: desc->xoffset); |
777 | |
778 | if (gas->address) |
779 | continue; |
780 | |
781 | gas_init_system_io(gas, address: legacy_addr, byte_size: length); |
782 | } |
783 | } |
784 | |
785 | static void split_one_block( |
786 | struct acpi_gas *src, struct acpi_gas *dst0, struct acpi_gas *dst1 |
787 | ) |
788 | { |
789 | uacpi_size byte_length; |
790 | |
791 | if (src->address == 0) |
792 | return; |
793 | |
794 | byte_length = src->register_bit_width / 8; |
795 | byte_length /= 2; |
796 | |
797 | gas_init_system_io(gas: dst0, address: src->address, byte_size: byte_length); |
798 | gas_init_system_io(gas: dst1, address: src->address + byte_length, byte_size: byte_length); |
799 | } |
800 | |
801 | static void split_event_blocks(void) |
802 | { |
803 | split_one_block( |
804 | src: &g_uacpi_rt_ctx.fadt.x_pm1a_evt_blk, |
805 | dst0: &g_uacpi_rt_ctx.pm1a_status_blk, |
806 | dst1: &g_uacpi_rt_ctx.pm1a_enable_blk |
807 | ); |
808 | split_one_block( |
809 | src: &g_uacpi_rt_ctx.fadt.x_pm1b_evt_blk, |
810 | dst0: &g_uacpi_rt_ctx.pm1b_status_blk, |
811 | dst1: &g_uacpi_rt_ctx.pm1b_enable_blk |
812 | ); |
813 | } |
814 | |
815 | static uacpi_status initialize_fadt(struct acpi_sdt_hdr *hdr) |
816 | { |
817 | uacpi_status ret; |
818 | uacpi_table tbl; |
819 | struct acpi_fadt *fadt = &g_uacpi_rt_ctx.fadt; |
820 | |
821 | /* |
822 | * Here we (roughly) follow ACPICA initialization sequence to make sure we |
823 | * handle potential BIOS quirks with garbage inside FADT correctly. |
824 | */ |
825 | |
826 | uacpi_memcpy(dest: fadt, src: hdr, UACPI_MIN(sizeof(*fadt), hdr->length)); |
827 | |
828 | #ifndef UACPI_REDUCED_HARDWARE |
829 | g_uacpi_rt_ctx.is_hardware_reduced = fadt->flags & ACPI_HW_REDUCED_ACPI; |
830 | #endif |
831 | |
832 | fadt_ensure_correct_revision(fadt); |
833 | |
834 | /* |
835 | * These are reserved prior to version 3, so zero them out to work around |
836 | * BIOS implementations that might dirty these. |
837 | */ |
838 | if (fadt->hdr.revision <= 2) { |
839 | fadt->preferred_pm_profile = 0; |
840 | fadt->pstate_cnt = 0; |
841 | fadt->cst_cnt = 0; |
842 | fadt->iapc_boot_arch = 0; |
843 | } |
844 | |
845 | if (!fadt->x_dsdt) |
846 | fadt->x_dsdt = fadt->dsdt; |
847 | |
848 | if (fadt->x_dsdt) { |
849 | ret = table_install_physical_with_origin_unlocked( |
850 | phys: fadt->x_dsdt, origin: UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL, |
851 | ACPI_DSDT_SIGNATURE, out_table: &tbl |
852 | ); |
853 | if (uacpi_unlikely_error(ret)) |
854 | return ret; |
855 | } |
856 | |
857 | if (!uacpi_is_hardware_reduced()) { |
858 | convert_registers_to_gas(); |
859 | split_event_blocks(); |
860 | |
861 | /* |
862 | * Unconditionally use 32 bit FACS if it exists, as 64 bit FACS is known |
863 | * to cause issues on some firmware: |
864 | * https://bugzilla.kernel.org/show_bug.cgi?id=74021 |
865 | */ |
866 | if (fadt->firmware_ctrl) |
867 | fadt->x_firmware_ctrl = fadt->firmware_ctrl; |
868 | |
869 | if (fadt->x_firmware_ctrl) { |
870 | ret = table_install_physical_with_origin_unlocked( |
871 | phys: fadt->x_firmware_ctrl, origin: UACPI_TABLE_ORIGIN_FIRMWARE_PHYSICAL, |
872 | ACPI_FACS_SIGNATURE, out_table: &tbl |
873 | ); |
874 | if (uacpi_unlikely_error(ret)) |
875 | return ret; |
876 | |
877 | g_uacpi_rt_ctx.facs = tbl.ptr; |
878 | } |
879 | } |
880 | |
881 | return UACPI_STATUS_OK; |
882 | } |
883 | |
884 | uacpi_status uacpi_table_fadt(struct acpi_fadt **out_fadt) |
885 | { |
886 | UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_TABLES_LOADED); |
887 | |
888 | *out_fadt = &g_uacpi_rt_ctx.fadt; |
889 | return UACPI_STATUS_OK; |
890 | } |
891 | |