1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <assert.h>
6 #include <ddk/debug.h>
7 #include <limits.h>
8 #include <stdbool.h>
9 #include <stdio.h>
10 #include <zircon/syscalls/iommu.h>
11
12 #include "dev.h"
13 #include "init.h"
14 #include "iommu.h"
15
16 #define ACPI_MAX_INIT_TABLES 32
17
18 /* @brief Switch interrupts to APIC model (controls IRQ routing) */
set_apic_irq_mode(void)19 static ACPI_STATUS set_apic_irq_mode(void) {
20 ACPI_OBJECT selector = {
21 .Integer.Type = ACPI_TYPE_INTEGER,
22 .Integer.Value = 1, // 1 means APIC mode according to ACPI v5 5.8.1
23 };
24 ACPI_OBJECT_LIST params = {
25 .Count = 1,
26 .Pointer = &selector,
27 };
28 return AcpiEvaluateObject(NULL, (char*)"\\_PIC", ¶ms, NULL);
29 }
30
is_gpe_device(ACPI_HANDLE object)31 static int is_gpe_device(ACPI_HANDLE object) {
32 ACPI_DEVICE_INFO* info = NULL;
33 ACPI_STATUS acpi_status = AcpiGetObjectInfo(object, &info);
34 if (acpi_status == AE_OK) {
35 // These length fields count the trailing NUL.
36 if ((info->Valid & ACPI_VALID_HID) && info->HardwareId.Length <= HID_LENGTH + 1) {
37 if (!strncmp(info->HardwareId.String, GPE_HID_STRING, HID_LENGTH)) {
38 return 1;
39 }
40 }
41 if ((info->Valid & ACPI_VALID_CID) && info->CompatibleIdList.Count > 0) {
42 ACPI_PNP_DEVICE_ID* id = &info->CompatibleIdList.Ids[0];
43 if (!strncmp(id->String, GPE_CID_STRING, CID_LENGTH)) {
44 return 1;
45 }
46 }
47 ACPI_FREE(info);
48 }
49 return 0;
50 }
51
acpi_prw_walk(ACPI_HANDLE obj,UINT32 level,void * context,void ** out_value)52 static ACPI_STATUS acpi_prw_walk(ACPI_HANDLE obj, UINT32 level, void* context, void** out_value) {
53 ACPI_BUFFER buffer = {
54 // Request that the ACPI subsystem allocate the buffer
55 .Length = ACPI_ALLOCATE_BUFFER,
56 .Pointer = NULL,
57 };
58 ACPI_STATUS status = AcpiEvaluateObject(obj, (char*) "_PRW", NULL, &buffer);
59 if (status != AE_OK) {
60 return AE_OK; // Keep walking the tree
61 }
62 ACPI_OBJECT* prw_res = buffer.Pointer;
63
64 // _PRW returns a package with >= 2 entries. The first entry indicates what type of
65 // event it is. If it's a GPE event, the first entry is either an integer indicating
66 // the bit within the FADT GPE enable register or it is a package containing a handle
67 // to a GPE block device and the bit index on that device. There are other event
68 // types with (handle, int) packages, so check that the handle is a GPE device by
69 // checking against the CID/HID required by the ACPI spec.
70 if (prw_res->Type != ACPI_TYPE_PACKAGE || prw_res->Package.Count < 2) {
71 return AE_OK; // Keep walking the tree
72 }
73
74 ACPI_HANDLE gpe_block;
75 UINT32 gpe_bit;
76 ACPI_OBJECT* event_info = &prw_res->Package.Elements[0];
77 if (event_info->Type == ACPI_TYPE_INTEGER) {
78 gpe_block = NULL;
79 gpe_bit = prw_res->Package.Elements[0].Integer.Value;
80 } else if (event_info->Type == ACPI_TYPE_PACKAGE) {
81 if (event_info->Package.Count != 2) {
82 goto bailout;
83 }
84 ACPI_OBJECT* handle_obj = &event_info->Package.Elements[0];
85 ACPI_OBJECT* gpe_num_obj = &event_info->Package.Elements[1];
86 if (handle_obj->Type != ACPI_TYPE_LOCAL_REFERENCE
87 || !is_gpe_device(handle_obj->Reference.Handle)) {
88 goto bailout;
89 }
90 if (gpe_num_obj->Type != ACPI_TYPE_INTEGER) {
91 goto bailout;
92 }
93 gpe_block = handle_obj->Reference.Handle;
94 gpe_bit = gpe_num_obj->Integer.Value;
95 } else {
96 goto bailout;
97 }
98 if (AcpiSetupGpeForWake(obj, gpe_block, gpe_bit) != AE_OK) {
99 printf("INFO: Acpi failed to setup wake GPE\n");
100 }
101
102 bailout:
103 ACPI_FREE(buffer.Pointer);
104
105 return AE_OK; // We want to keep going even if we bailed out
106 }
107
init(void)108 ACPI_STATUS init(void) {
109 // This sequence is described in section 10.1.2.1 (Full ACPICA Initialization)
110 // of the ACPICA developer's reference.
111 ACPI_STATUS status = AcpiInitializeSubsystem();
112 if (status != AE_OK) {
113 printf("WARNING: could not initialize ACPI\n");
114 return status;
115 }
116
117 status = AcpiInitializeTables(NULL, ACPI_MAX_INIT_TABLES, FALSE);
118 if (status == AE_NOT_FOUND) {
119 printf("WARNING: could not find ACPI tables\n");
120 return status;
121 } else if (status == AE_NO_MEMORY) {
122 printf("WARNING: could not initialize ACPI tables\n");
123 return status;
124 } else if (status != AE_OK) {
125 printf("WARNING: could not initialize ACPI tables for unknown reason\n");
126 return status;
127 }
128
129 status = AcpiLoadTables();
130 if (status != AE_OK) {
131 printf("WARNING: could not load ACPI tables: %d\n", status);
132 return status;
133 }
134
135 status = AcpiEnableSubsystem(ACPI_FULL_INITIALIZATION);
136 if (status != AE_OK) {
137 printf("WARNING: could not enable ACPI\n");
138 return status;
139 }
140
141 status = AcpiInitializeObjects(ACPI_FULL_INITIALIZATION);
142 if (status != AE_OK) {
143 printf("WARNING: could not initialize ACPI objects\n");
144 return status;
145 }
146
147 zx_status_t zx_status = iommu_manager_init();
148 if (zx_status != ZX_OK) {
149 zxlogf(INFO, "acpi: Failed to initialize IOMMU manager\n");
150 }
151
152 status = set_apic_irq_mode();
153 if (status == AE_NOT_FOUND) {
154 printf("WARNING: Could not find ACPI IRQ mode switch\n");
155 } else if (status != AE_OK) {
156 printf("Failed to set APIC IRQ mode\n");
157 return status;
158 }
159
160 AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, INT_MAX,
161 acpi_prw_walk, NULL, NULL, NULL);
162
163 status = AcpiUpdateAllGpes();
164 if (status != AE_OK) {
165 printf("WARNING: could not initialize ACPI GPEs\n");
166 return status;
167 }
168
169 // TODO(teisenbe): Maybe back out of ACPI mode on failure, but we rely on
170 // ACPI for some critical things right now, so failure will likely prevent
171 // successful boot anyway.
172 return AE_OK;
173 }
174