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", &params, 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