|
@@ -24,6 +24,8 @@
|
24
|
24
|
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
25
|
25
|
|
26
|
26
|
#include <errno.h>
|
|
27
|
+#include <byteswap.h>
|
|
28
|
+#include <ipxe/uaccess.h>
|
27
|
29
|
#include <ipxe/acpi.h>
|
28
|
30
|
#include <ipxe/interface.h>
|
29
|
31
|
|
|
@@ -40,6 +42,22 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
40
|
42
|
******************************************************************************
|
41
|
43
|
*/
|
42
|
44
|
|
|
45
|
+/**
|
|
46
|
+ * Transcribe ACPI table signature (for debugging)
|
|
47
|
+ *
|
|
48
|
+ * @v signature ACPI table signature
|
|
49
|
+ * @ret name ACPI table signature name
|
|
50
|
+ */
|
|
51
|
+static const char * acpi_name ( uint32_t signature ) {
|
|
52
|
+ static union {
|
|
53
|
+ uint32_t signature;
|
|
54
|
+ char name[5];
|
|
55
|
+ } u;
|
|
56
|
+
|
|
57
|
+ u.signature = cpu_to_le32 ( signature );
|
|
58
|
+ return u.name;
|
|
59
|
+}
|
|
60
|
+
|
43
|
61
|
/**
|
44
|
62
|
* Fix up ACPI table checksum
|
45
|
63
|
*
|
|
@@ -55,6 +73,262 @@ void acpi_fix_checksum ( struct acpi_description_header *acpi ) {
|
55
|
73
|
acpi->checksum -= sum;
|
56
|
74
|
}
|
57
|
75
|
|
|
76
|
+/**
|
|
77
|
+ * Locate ACPI root system description table within a memory range
|
|
78
|
+ *
|
|
79
|
+ * @v start Start address to search
|
|
80
|
+ * @v len Length to search
|
|
81
|
+ * @ret rsdt ACPI root system description table, or UNULL
|
|
82
|
+ */
|
|
83
|
+static userptr_t acpi_find_rsdt_range ( userptr_t start, size_t len ) {
|
|
84
|
+ static const char signature[8] = RSDP_SIGNATURE;
|
|
85
|
+ struct acpi_rsdp rsdp;
|
|
86
|
+ userptr_t rsdt;
|
|
87
|
+ size_t offset;
|
|
88
|
+ uint8_t sum;
|
|
89
|
+ unsigned int i;
|
|
90
|
+
|
|
91
|
+ /* Search for RSDP */
|
|
92
|
+ for ( offset = 0 ; ( ( offset + sizeof ( rsdp ) ) < len ) ;
|
|
93
|
+ offset += RSDP_STRIDE ) {
|
|
94
|
+
|
|
95
|
+ /* Check signature and checksum */
|
|
96
|
+ copy_from_user ( &rsdp, start, offset, sizeof ( rsdp ) );
|
|
97
|
+ if ( memcmp ( rsdp.signature, signature,
|
|
98
|
+ sizeof ( signature ) ) != 0 )
|
|
99
|
+ continue;
|
|
100
|
+ for ( sum = 0, i = 0 ; i < sizeof ( rsdp ) ; i++ )
|
|
101
|
+ sum += *( ( ( uint8_t * ) &rsdp ) + i );
|
|
102
|
+ if ( sum != 0 )
|
|
103
|
+ continue;
|
|
104
|
+
|
|
105
|
+ /* Extract RSDT */
|
|
106
|
+ rsdt = phys_to_user ( le32_to_cpu ( rsdp.rsdt ) );
|
|
107
|
+ DBGC ( rsdt, "RSDT %#08lx found via RSDP %#08lx\n",
|
|
108
|
+ user_to_phys ( rsdt, 0 ),
|
|
109
|
+ user_to_phys ( start, offset ) );
|
|
110
|
+ return rsdt;
|
|
111
|
+ }
|
|
112
|
+
|
|
113
|
+ return UNULL;
|
|
114
|
+}
|
|
115
|
+
|
|
116
|
+/**
|
|
117
|
+ * Locate ACPI root system description table
|
|
118
|
+ *
|
|
119
|
+ * @v ebda Extended BIOS data area, or UNULL
|
|
120
|
+ * @ret rsdt ACPI root system description table, or UNULL
|
|
121
|
+ */
|
|
122
|
+userptr_t acpi_find_rsdt ( userptr_t ebda ) {
|
|
123
|
+ userptr_t rsdt;
|
|
124
|
+
|
|
125
|
+ /* Search EBDA, if applicable */
|
|
126
|
+ if ( ebda ) {
|
|
127
|
+ rsdt = acpi_find_rsdt_range ( ebda, RSDP_EBDA_LEN );
|
|
128
|
+ if ( rsdt )
|
|
129
|
+ return rsdt;
|
|
130
|
+ }
|
|
131
|
+
|
|
132
|
+ /* Search fixed BIOS area */
|
|
133
|
+ rsdt = acpi_find_rsdt_range ( phys_to_user ( RSDP_BIOS_START ),
|
|
134
|
+ RSDP_BIOS_LEN );
|
|
135
|
+ if ( rsdt )
|
|
136
|
+ return rsdt;
|
|
137
|
+
|
|
138
|
+ return UNULL;
|
|
139
|
+}
|
|
140
|
+
|
|
141
|
+/**
|
|
142
|
+ * Locate ACPI table
|
|
143
|
+ *
|
|
144
|
+ * @v rsdt ACPI root system description table
|
|
145
|
+ * @v signature Requested table signature
|
|
146
|
+ * @v index Requested index of table with this signature
|
|
147
|
+ * @ret table Table, or UNULL if not found
|
|
148
|
+ */
|
|
149
|
+userptr_t acpi_find ( userptr_t rsdt, uint32_t signature, unsigned int index ) {
|
|
150
|
+ struct acpi_description_header acpi;
|
|
151
|
+ struct acpi_rsdt *rsdtab;
|
|
152
|
+ typeof ( rsdtab->entry[0] ) entry;
|
|
153
|
+ userptr_t table;
|
|
154
|
+ size_t len;
|
|
155
|
+ unsigned int count;
|
|
156
|
+ unsigned int i;
|
|
157
|
+
|
|
158
|
+ /* Read RSDT header */
|
|
159
|
+ copy_from_user ( &acpi, rsdt, 0, sizeof ( acpi ) );
|
|
160
|
+ if ( acpi.signature != cpu_to_le32 ( RSDT_SIGNATURE ) ) {
|
|
161
|
+ DBGC ( rsdt, "RSDT %#08lx has invalid signature:\n",
|
|
162
|
+ user_to_phys ( rsdt, 0 ) );
|
|
163
|
+ DBGC_HDA ( rsdt, user_to_phys ( rsdt, 0 ), &acpi,
|
|
164
|
+ sizeof ( acpi ) );
|
|
165
|
+ return UNULL;
|
|
166
|
+ }
|
|
167
|
+ len = le32_to_cpu ( acpi.length );
|
|
168
|
+ if ( len < sizeof ( rsdtab->acpi ) ) {
|
|
169
|
+ DBGC ( rsdt, "RSDT %#08lx has invalid length:\n",
|
|
170
|
+ user_to_phys ( rsdt, 0 ) );
|
|
171
|
+ DBGC_HDA ( rsdt, user_to_phys ( rsdt, 0 ), &acpi,
|
|
172
|
+ sizeof ( acpi ) );
|
|
173
|
+ return UNULL;
|
|
174
|
+ }
|
|
175
|
+
|
|
176
|
+ /* Calculate number of entries */
|
|
177
|
+ count = ( ( len - sizeof ( rsdtab->acpi ) ) / sizeof ( entry ) );
|
|
178
|
+
|
|
179
|
+ /* Search through entries */
|
|
180
|
+ for ( i = 0 ; i < count ; i++ ) {
|
|
181
|
+
|
|
182
|
+ /* Get table address */
|
|
183
|
+ copy_from_user ( &entry, rsdt,
|
|
184
|
+ offsetof ( typeof ( *rsdtab ), entry[i] ),
|
|
185
|
+ sizeof ( entry ) );
|
|
186
|
+
|
|
187
|
+ /* Read table header */
|
|
188
|
+ table = phys_to_user ( entry );
|
|
189
|
+ copy_from_user ( &acpi.signature, table, 0,
|
|
190
|
+ sizeof ( acpi.signature ) );
|
|
191
|
+
|
|
192
|
+ /* Check table signature */
|
|
193
|
+ if ( acpi.signature != cpu_to_le32 ( signature ) )
|
|
194
|
+ continue;
|
|
195
|
+
|
|
196
|
+ /* Check index */
|
|
197
|
+ if ( index-- )
|
|
198
|
+ continue;
|
|
199
|
+
|
|
200
|
+ DBGC ( rsdt, "RSDT %#08lx found %s at %08lx\n",
|
|
201
|
+ user_to_phys ( rsdt, 0 ), acpi_name ( signature ),
|
|
202
|
+ user_to_phys ( table, 0 ) );
|
|
203
|
+ return table;
|
|
204
|
+ }
|
|
205
|
+
|
|
206
|
+ DBGC ( rsdt, "RSDT %#08lx could not find %s\n",
|
|
207
|
+ user_to_phys ( rsdt, 0 ), acpi_name ( signature ) );
|
|
208
|
+ return UNULL;
|
|
209
|
+}
|
|
210
|
+
|
|
211
|
+/**
|
|
212
|
+ * Extract \_Sx value from DSDT/SSDT
|
|
213
|
+ *
|
|
214
|
+ * @v zsdt DSDT or SSDT
|
|
215
|
+ * @v signature Signature (e.g. "_S5_")
|
|
216
|
+ * @ret sx \_Sx value, or negative error
|
|
217
|
+ *
|
|
218
|
+ * In theory, extracting the \_Sx value from the DSDT/SSDT requires a
|
|
219
|
+ * full ACPI parser plus some heuristics to work around the various
|
|
220
|
+ * broken encodings encountered in real ACPI implementations.
|
|
221
|
+ *
|
|
222
|
+ * In practice, we can get the same result by scanning through the
|
|
223
|
+ * DSDT/SSDT for the signature (e.g. "_S5_"), extracting the first
|
|
224
|
+ * four bytes, removing any bytes with bit 3 set, and treating
|
|
225
|
+ * whatever is left as a little-endian value. This is one of the
|
|
226
|
+ * uglier hacks I have ever implemented, but it's still prettier than
|
|
227
|
+ * the ACPI specification itself.
|
|
228
|
+ */
|
|
229
|
+static int acpi_sx_zsdt ( userptr_t zsdt, uint32_t signature ) {
|
|
230
|
+ struct acpi_description_header acpi;
|
|
231
|
+ union {
|
|
232
|
+ uint32_t dword;
|
|
233
|
+ uint8_t byte[4];
|
|
234
|
+ } buf;
|
|
235
|
+ size_t offset;
|
|
236
|
+ size_t len;
|
|
237
|
+ unsigned int sx;
|
|
238
|
+ uint8_t *byte;
|
|
239
|
+
|
|
240
|
+ /* Read table header */
|
|
241
|
+ copy_from_user ( &acpi, zsdt, 0, sizeof ( acpi ) );
|
|
242
|
+ len = le32_to_cpu ( acpi.length );
|
|
243
|
+
|
|
244
|
+ /* Locate signature */
|
|
245
|
+ for ( offset = sizeof ( acpi ) ;
|
|
246
|
+ ( ( offset + sizeof ( buf ) /* signature */ + 3 /* pkg header */
|
|
247
|
+ + sizeof ( buf ) /* value */ ) < len ) ;
|
|
248
|
+ offset++ ) {
|
|
249
|
+
|
|
250
|
+ /* Check signature */
|
|
251
|
+ copy_from_user ( &buf, zsdt, offset, sizeof ( buf ) );
|
|
252
|
+ if ( buf.dword != cpu_to_le32 ( signature ) )
|
|
253
|
+ continue;
|
|
254
|
+ DBGC ( zsdt, "DSDT/SSDT %#08lx found %s at offset %#zx\n",
|
|
255
|
+ user_to_phys ( zsdt, 0 ), acpi_name ( signature ),
|
|
256
|
+ offset );
|
|
257
|
+ offset += sizeof ( buf );
|
|
258
|
+
|
|
259
|
+ /* Read first four bytes of value */
|
|
260
|
+ copy_from_user ( &buf, zsdt, ( offset + 3 /* pkg header */ ),
|
|
261
|
+ sizeof ( buf ) );
|
|
262
|
+ DBGC ( zsdt, "DSDT/SSDT %#08lx found %s containing "
|
|
263
|
+ "%02x:%02x:%02x:%02x\n", user_to_phys ( zsdt, 0 ),
|
|
264
|
+ acpi_name ( signature ), buf.byte[0], buf.byte[1],
|
|
265
|
+ buf.byte[2], buf.byte[3] );
|
|
266
|
+
|
|
267
|
+ /* Extract \Sx value. There are three potential
|
|
268
|
+ * encodings that we might encounter:
|
|
269
|
+ *
|
|
270
|
+ * - SLP_TYPa, SLP_TYPb, rsvd, rsvd
|
|
271
|
+ *
|
|
272
|
+ * - <byteprefix>, SLP_TYPa, <byteprefix>, SLP_TYPb, ...
|
|
273
|
+ *
|
|
274
|
+ * - <dwordprefix>, SLP_TYPa, SLP_TYPb, 0, 0
|
|
275
|
+ *
|
|
276
|
+ * Since <byteprefix> and <dwordprefix> both have bit
|
|
277
|
+ * 3 set, and valid SLP_TYPx must have bit 3 clear
|
|
278
|
+ * (since SLP_TYPx is a 3-bit field), we can just skip
|
|
279
|
+ * any bytes with bit 3 set.
|
|
280
|
+ */
|
|
281
|
+ byte = &buf.byte[0];
|
|
282
|
+ if ( *byte & 0x08 )
|
|
283
|
+ byte++;
|
|
284
|
+ sx = *(byte++);
|
|
285
|
+ if ( *byte & 0x08 )
|
|
286
|
+ byte++;
|
|
287
|
+ sx |= ( *byte << 8 );
|
|
288
|
+ return sx;
|
|
289
|
+ }
|
|
290
|
+
|
|
291
|
+ return -ENOENT;
|
|
292
|
+}
|
|
293
|
+
|
|
294
|
+/**
|
|
295
|
+ * Extract \_Sx value from DSDT/SSDT
|
|
296
|
+ *
|
|
297
|
+ * @v rsdt ACPI root system description table
|
|
298
|
+ * @v signature Signature (e.g. "_S5_")
|
|
299
|
+ * @ret sx \_Sx value, or negative error
|
|
300
|
+ */
|
|
301
|
+int acpi_sx ( userptr_t rsdt, uint32_t signature ) {
|
|
302
|
+ struct acpi_fadt fadtab;
|
|
303
|
+ userptr_t fadt;
|
|
304
|
+ userptr_t dsdt;
|
|
305
|
+ userptr_t ssdt;
|
|
306
|
+ unsigned int i;
|
|
307
|
+ int sx;
|
|
308
|
+
|
|
309
|
+ /* Try DSDT first */
|
|
310
|
+ fadt = acpi_find ( rsdt, FADT_SIGNATURE, 0 );
|
|
311
|
+ if ( fadt ) {
|
|
312
|
+ copy_from_user ( &fadtab, fadt, 0, sizeof ( fadtab ) );
|
|
313
|
+ dsdt = phys_to_user ( fadtab.dsdt );
|
|
314
|
+ if ( ( sx = acpi_sx_zsdt ( dsdt, signature ) ) >= 0 )
|
|
315
|
+ return sx;
|
|
316
|
+ }
|
|
317
|
+
|
|
318
|
+ /* Try all SSDTs */
|
|
319
|
+ for ( i = 0 ; ; i++ ) {
|
|
320
|
+ ssdt = acpi_find ( rsdt, SSDT_SIGNATURE, i );
|
|
321
|
+ if ( ! ssdt )
|
|
322
|
+ break;
|
|
323
|
+ if ( ( sx = acpi_sx_zsdt ( ssdt, signature ) ) >= 0 )
|
|
324
|
+ return sx;
|
|
325
|
+ }
|
|
326
|
+
|
|
327
|
+ DBGC ( rsdt, "RSDT %#08lx could not find \\_Sx \"%s\"\n",
|
|
328
|
+ user_to_phys ( rsdt, 0 ), acpi_name ( signature ) );
|
|
329
|
+ return -ENOENT;
|
|
330
|
+}
|
|
331
|
+
|
58
|
332
|
/******************************************************************************
|
59
|
333
|
*
|
60
|
334
|
* Interface methods
|