]>
Commit | Line | Data |
---|---|---|
f7e26ffa ED |
1 | /* |
2 | * ACPI Error Record Serialization Table, ERST, Implementation | |
3 | * | |
4 | * ACPI ERST introduced in ACPI 4.0, June 16, 2009. | |
5 | * ACPI Platform Error Interfaces : Error Serialization | |
6 | * | |
7 | * Copyright (c) 2021 Oracle and/or its affiliates. | |
8 | * | |
9 | * SPDX-License-Identifier: GPL-2.0-or-later | |
10 | */ | |
11 | ||
12 | #include "qemu/osdep.h" | |
13 | #include "qapi/error.h" | |
14 | #include "hw/qdev-core.h" | |
15 | #include "exec/memory.h" | |
16 | #include "qom/object.h" | |
edf5ca5d | 17 | #include "hw/pci/pci_device.h" |
f7e26ffa ED |
18 | #include "qom/object_interfaces.h" |
19 | #include "qemu/error-report.h" | |
20 | #include "migration/vmstate.h" | |
21 | #include "hw/qdev-properties.h" | |
22 | #include "hw/acpi/acpi.h" | |
23 | #include "hw/acpi/acpi-defs.h" | |
24 | #include "hw/acpi/aml-build.h" | |
25 | #include "hw/acpi/bios-linker-loader.h" | |
26 | #include "exec/address-spaces.h" | |
27 | #include "sysemu/hostmem.h" | |
28 | #include "hw/acpi/erst.h" | |
29 | #include "trace.h" | |
30 | ||
31 | /* ACPI 4.0: Table 17-16 Serialization Actions */ | |
32 | #define ACTION_BEGIN_WRITE_OPERATION 0x0 | |
33 | #define ACTION_BEGIN_READ_OPERATION 0x1 | |
34 | #define ACTION_BEGIN_CLEAR_OPERATION 0x2 | |
35 | #define ACTION_END_OPERATION 0x3 | |
36 | #define ACTION_SET_RECORD_OFFSET 0x4 | |
37 | #define ACTION_EXECUTE_OPERATION 0x5 | |
38 | #define ACTION_CHECK_BUSY_STATUS 0x6 | |
39 | #define ACTION_GET_COMMAND_STATUS 0x7 | |
40 | #define ACTION_GET_RECORD_IDENTIFIER 0x8 | |
41 | #define ACTION_SET_RECORD_IDENTIFIER 0x9 | |
42 | #define ACTION_GET_RECORD_COUNT 0xA | |
43 | #define ACTION_BEGIN_DUMMY_WRITE_OPERATION 0xB | |
44 | #define ACTION_RESERVED 0xC | |
45 | #define ACTION_GET_ERROR_LOG_ADDRESS_RANGE 0xD | |
46 | #define ACTION_GET_ERROR_LOG_ADDRESS_LENGTH 0xE | |
47 | #define ACTION_GET_ERROR_LOG_ADDRESS_RANGE_ATTRIBUTES 0xF | |
48 | #define ACTION_GET_EXECUTE_OPERATION_TIMINGS 0x10 /* ACPI 6.3 */ | |
49 | ||
50 | /* ACPI 4.0: Table 17-17 Command Status Definitions */ | |
51 | #define STATUS_SUCCESS 0x00 | |
52 | #define STATUS_NOT_ENOUGH_SPACE 0x01 | |
53 | #define STATUS_HARDWARE_NOT_AVAILABLE 0x02 | |
54 | #define STATUS_FAILED 0x03 | |
55 | #define STATUS_RECORD_STORE_EMPTY 0x04 | |
56 | #define STATUS_RECORD_NOT_FOUND 0x05 | |
57 | ||
c9cd06ca ED |
58 | /* ACPI 4.0: Table 17-19 Serialization Instructions */ |
59 | #define INST_READ_REGISTER 0x00 | |
60 | #define INST_READ_REGISTER_VALUE 0x01 | |
61 | #define INST_WRITE_REGISTER 0x02 | |
62 | #define INST_WRITE_REGISTER_VALUE 0x03 | |
63 | #define INST_NOOP 0x04 | |
64 | #define INST_LOAD_VAR1 0x05 | |
65 | #define INST_LOAD_VAR2 0x06 | |
66 | #define INST_STORE_VAR1 0x07 | |
67 | #define INST_ADD 0x08 | |
68 | #define INST_SUBTRACT 0x09 | |
69 | #define INST_ADD_VALUE 0x0A | |
70 | #define INST_SUBTRACT_VALUE 0x0B | |
71 | #define INST_STALL 0x0C | |
72 | #define INST_STALL_WHILE_TRUE 0x0D | |
73 | #define INST_SKIP_NEXT_INSTRUCTION_IF_TRUE 0x0E | |
74 | #define INST_GOTO 0x0F | |
75 | #define INST_SET_SRC_ADDRESS_BASE 0x10 | |
76 | #define INST_SET_DST_ADDRESS_BASE 0x11 | |
77 | #define INST_MOVE_DATA 0x12 | |
78 | ||
f7e26ffa ED |
79 | /* UEFI 2.1: Appendix N Common Platform Error Record */ |
80 | #define UEFI_CPER_RECORD_MIN_SIZE 128U | |
81 | #define UEFI_CPER_RECORD_LENGTH_OFFSET 20U | |
82 | #define UEFI_CPER_RECORD_ID_OFFSET 96U | |
f7e26ffa ED |
83 | |
84 | /* | |
85 | * NOTE that when accessing CPER fields within a record, memcpy() | |
86 | * is utilized to avoid a possible misaligned access on the host. | |
87 | */ | |
88 | ||
89 | /* | |
90 | * This implementation is an ACTION (cmd) and VALUE (data) | |
91 | * interface consisting of just two 64-bit registers. | |
92 | */ | |
93 | #define ERST_REG_SIZE (16UL) | |
94 | #define ERST_ACTION_OFFSET (0UL) /* action (cmd) */ | |
95 | #define ERST_VALUE_OFFSET (8UL) /* argument/value (data) */ | |
96 | ||
97 | /* | |
98 | * ERST_RECORD_SIZE is the buffer size for exchanging ERST | |
99 | * record contents. Thus, it defines the maximum record size. | |
100 | * As this is mapped through a PCI BAR, it must be a power of | |
101 | * two and larger than UEFI_CPER_RECORD_MIN_SIZE. | |
102 | * The backing storage is divided into fixed size "slots", | |
103 | * each ERST_RECORD_SIZE in length, and each "slot" | |
104 | * storing a single record. No attempt at optimizing storage | |
105 | * through compression, compaction, etc is attempted. | |
106 | * NOTE that slot 0 is reserved for the backing storage header. | |
107 | * Depending upon the size of the backing storage, additional | |
108 | * slots will be part of the slot 0 header in order to account | |
109 | * for a record_id for each available remaining slot. | |
110 | */ | |
111 | /* 8KiB records, not too small, not too big */ | |
112 | #define ERST_RECORD_SIZE (8192UL) | |
113 | ||
114 | #define ACPI_ERST_MEMDEV_PROP "memdev" | |
115 | #define ACPI_ERST_RECORD_SIZE_PROP "record_size" | |
116 | ||
117 | /* | |
118 | * From the ACPI ERST spec sections: | |
119 | * A record id of all 0s is used to indicate 'unspecified' record id. | |
120 | * A record id of all 1s is used to indicate empty or end. | |
121 | */ | |
122 | #define ERST_UNSPECIFIED_RECORD_ID (0UL) | |
123 | #define ERST_EMPTY_END_RECORD_ID (~0UL) | |
124 | ||
125 | #define ERST_IS_VALID_RECORD_ID(rid) \ | |
126 | ((rid != ERST_UNSPECIFIED_RECORD_ID) && \ | |
127 | (rid != ERST_EMPTY_END_RECORD_ID)) | |
128 | ||
129 | /* | |
130 | * Implementation-specific definitions and types. | |
131 | * Values are arbitrary and chosen for this implementation. | |
132 | * See erst.rst documentation for details. | |
133 | */ | |
134 | #define ERST_EXECUTE_OPERATION_MAGIC 0x9CUL | |
135 | #define ERST_STORE_MAGIC 0x524F545354535245UL /* ERSTSTOR */ | |
136 | typedef struct { | |
137 | uint64_t magic; | |
138 | uint32_t record_size; | |
139 | uint32_t storage_offset; /* offset to record storage beyond header */ | |
140 | uint16_t version; | |
141 | uint16_t reserved; | |
142 | uint32_t record_count; | |
143 | uint64_t map[]; /* contains record_ids, and position indicates index */ | |
144 | } __attribute__((packed)) ERSTStorageHeader; | |
145 | ||
146 | /* | |
147 | * Object cast macro | |
148 | */ | |
149 | #define ACPIERST(obj) \ | |
150 | OBJECT_CHECK(ERSTDeviceState, (obj), TYPE_ACPI_ERST) | |
151 | ||
152 | /* | |
153 | * Main ERST device state structure | |
154 | */ | |
155 | typedef struct { | |
156 | PCIDevice parent_obj; | |
157 | ||
158 | /* Backend storage */ | |
159 | HostMemoryBackend *hostmem; | |
160 | MemoryRegion *hostmem_mr; | |
161 | uint32_t storage_size; | |
162 | uint32_t default_record_size; | |
163 | ||
164 | /* Programming registers */ | |
165 | MemoryRegion iomem_mr; | |
166 | ||
167 | /* Exchange buffer */ | |
168 | MemoryRegion exchange_mr; | |
169 | ||
170 | /* Interface state */ | |
171 | uint8_t operation; | |
172 | uint8_t busy_status; | |
173 | uint8_t command_status; | |
174 | uint32_t record_offset; | |
175 | uint64_t reg_action; | |
176 | uint64_t reg_value; | |
177 | uint64_t record_identifier; | |
178 | ERSTStorageHeader *header; | |
179 | unsigned first_record_index; | |
180 | unsigned last_record_index; | |
181 | unsigned next_record_index; | |
182 | ||
183 | } ERSTDeviceState; | |
184 | ||
c9cd06ca ED |
185 | /*******************************************************************/ |
186 | /*******************************************************************/ | |
187 | typedef struct { | |
188 | GArray *table_data; | |
189 | pcibus_t bar; | |
190 | uint8_t instruction; | |
191 | uint8_t flags; | |
192 | uint8_t register_bit_width; | |
193 | pcibus_t register_offset; | |
194 | } BuildSerializationInstructionEntry; | |
195 | ||
196 | /* ACPI 4.0: 17.4.1.2 Serialization Instruction Entries */ | |
197 | static void build_serialization_instruction( | |
198 | BuildSerializationInstructionEntry *e, | |
199 | uint8_t serialization_action, | |
200 | uint64_t value) | |
201 | { | |
202 | /* ACPI 4.0: Table 17-18 Serialization Instruction Entry */ | |
203 | struct AcpiGenericAddress gas; | |
204 | uint64_t mask; | |
205 | ||
206 | /* Serialization Action */ | |
207 | build_append_int_noprefix(e->table_data, serialization_action, 1); | |
208 | /* Instruction */ | |
209 | build_append_int_noprefix(e->table_data, e->instruction, 1); | |
210 | /* Flags */ | |
211 | build_append_int_noprefix(e->table_data, e->flags, 1); | |
212 | /* Reserved */ | |
213 | build_append_int_noprefix(e->table_data, 0, 1); | |
214 | /* Register Region */ | |
215 | gas.space_id = AML_SYSTEM_MEMORY; | |
216 | gas.bit_width = e->register_bit_width; | |
217 | gas.bit_offset = 0; | |
218 | gas.access_width = (uint8_t)ctz32(e->register_bit_width) - 2; | |
219 | gas.address = (uint64_t)(e->bar + e->register_offset); | |
220 | build_append_gas_from_struct(e->table_data, &gas); | |
221 | /* Value */ | |
222 | build_append_int_noprefix(e->table_data, value, 8); | |
223 | /* Mask */ | |
224 | mask = (1ULL << (e->register_bit_width - 1) << 1) - 1; | |
225 | build_append_int_noprefix(e->table_data, mask, 8); | |
226 | } | |
227 | ||
228 | /* ACPI 4.0: 17.4.1 Serialization Action Table */ | |
229 | void build_erst(GArray *table_data, BIOSLinker *linker, Object *erst_dev, | |
230 | const char *oem_id, const char *oem_table_id) | |
231 | { | |
232 | /* | |
233 | * Serialization Action Table | |
234 | * The serialization action table must be generated first | |
235 | * so that its size can be known in order to populate the | |
236 | * Instruction Entry Count field. | |
237 | */ | |
238 | unsigned action; | |
239 | GArray *table_instruction_data = g_array_new(FALSE, FALSE, sizeof(char)); | |
240 | pcibus_t bar0 = pci_get_bar_addr(PCI_DEVICE(erst_dev), 0); | |
241 | AcpiTable table = { .sig = "ERST", .rev = 1, .oem_id = oem_id, | |
242 | .oem_table_id = oem_table_id }; | |
243 | /* Contexts for the different ways ACTION and VALUE are accessed */ | |
244 | BuildSerializationInstructionEntry rd_value_32_val = { | |
245 | .table_data = table_instruction_data, .bar = bar0, .flags = 0, | |
246 | .instruction = INST_READ_REGISTER_VALUE, | |
247 | .register_bit_width = 32, | |
248 | .register_offset = ERST_VALUE_OFFSET, | |
249 | }; | |
250 | BuildSerializationInstructionEntry rd_value_32 = { | |
251 | .table_data = table_instruction_data, .bar = bar0, .flags = 0, | |
252 | .instruction = INST_READ_REGISTER, | |
253 | .register_bit_width = 32, | |
254 | .register_offset = ERST_VALUE_OFFSET, | |
255 | }; | |
256 | BuildSerializationInstructionEntry rd_value_64 = { | |
257 | .table_data = table_instruction_data, .bar = bar0, .flags = 0, | |
258 | .instruction = INST_READ_REGISTER, | |
259 | .register_bit_width = 64, | |
260 | .register_offset = ERST_VALUE_OFFSET, | |
261 | }; | |
262 | BuildSerializationInstructionEntry wr_value_32_val = { | |
263 | .table_data = table_instruction_data, .bar = bar0, .flags = 0, | |
264 | .instruction = INST_WRITE_REGISTER_VALUE, | |
265 | .register_bit_width = 32, | |
266 | .register_offset = ERST_VALUE_OFFSET, | |
267 | }; | |
268 | BuildSerializationInstructionEntry wr_value_32 = { | |
269 | .table_data = table_instruction_data, .bar = bar0, .flags = 0, | |
270 | .instruction = INST_WRITE_REGISTER, | |
271 | .register_bit_width = 32, | |
272 | .register_offset = ERST_VALUE_OFFSET, | |
273 | }; | |
274 | BuildSerializationInstructionEntry wr_value_64 = { | |
275 | .table_data = table_instruction_data, .bar = bar0, .flags = 0, | |
276 | .instruction = INST_WRITE_REGISTER, | |
277 | .register_bit_width = 64, | |
278 | .register_offset = ERST_VALUE_OFFSET, | |
279 | }; | |
280 | BuildSerializationInstructionEntry wr_action = { | |
281 | .table_data = table_instruction_data, .bar = bar0, .flags = 0, | |
282 | .instruction = INST_WRITE_REGISTER_VALUE, | |
283 | .register_bit_width = 32, | |
284 | .register_offset = ERST_ACTION_OFFSET, | |
285 | }; | |
286 | ||
287 | trace_acpi_erst_pci_bar_0(bar0); | |
288 | ||
289 | /* Serialization Instruction Entries */ | |
290 | action = ACTION_BEGIN_WRITE_OPERATION; | |
291 | build_serialization_instruction(&wr_action, action, action); | |
292 | ||
293 | action = ACTION_BEGIN_READ_OPERATION; | |
294 | build_serialization_instruction(&wr_action, action, action); | |
295 | ||
296 | action = ACTION_BEGIN_CLEAR_OPERATION; | |
297 | build_serialization_instruction(&wr_action, action, action); | |
298 | ||
299 | action = ACTION_END_OPERATION; | |
300 | build_serialization_instruction(&wr_action, action, action); | |
301 | ||
302 | action = ACTION_SET_RECORD_OFFSET; | |
303 | build_serialization_instruction(&wr_value_32, action, 0); | |
304 | build_serialization_instruction(&wr_action, action, action); | |
305 | ||
306 | action = ACTION_EXECUTE_OPERATION; | |
307 | build_serialization_instruction(&wr_value_32_val, action, | |
308 | ERST_EXECUTE_OPERATION_MAGIC); | |
309 | build_serialization_instruction(&wr_action, action, action); | |
310 | ||
311 | action = ACTION_CHECK_BUSY_STATUS; | |
312 | build_serialization_instruction(&wr_action, action, action); | |
313 | build_serialization_instruction(&rd_value_32_val, action, 0x01); | |
314 | ||
315 | action = ACTION_GET_COMMAND_STATUS; | |
316 | build_serialization_instruction(&wr_action, action, action); | |
317 | build_serialization_instruction(&rd_value_32, action, 0); | |
318 | ||
319 | action = ACTION_GET_RECORD_IDENTIFIER; | |
320 | build_serialization_instruction(&wr_action, action, action); | |
321 | build_serialization_instruction(&rd_value_64, action, 0); | |
322 | ||
323 | action = ACTION_SET_RECORD_IDENTIFIER; | |
324 | build_serialization_instruction(&wr_value_64, action, 0); | |
325 | build_serialization_instruction(&wr_action, action, action); | |
326 | ||
327 | action = ACTION_GET_RECORD_COUNT; | |
328 | build_serialization_instruction(&wr_action, action, action); | |
329 | build_serialization_instruction(&rd_value_32, action, 0); | |
330 | ||
331 | action = ACTION_BEGIN_DUMMY_WRITE_OPERATION; | |
332 | build_serialization_instruction(&wr_action, action, action); | |
333 | ||
334 | action = ACTION_GET_ERROR_LOG_ADDRESS_RANGE; | |
335 | build_serialization_instruction(&wr_action, action, action); | |
336 | build_serialization_instruction(&rd_value_64, action, 0); | |
337 | ||
338 | action = ACTION_GET_ERROR_LOG_ADDRESS_LENGTH; | |
339 | build_serialization_instruction(&wr_action, action, action); | |
340 | build_serialization_instruction(&rd_value_64, action, 0); | |
341 | ||
342 | action = ACTION_GET_ERROR_LOG_ADDRESS_RANGE_ATTRIBUTES; | |
343 | build_serialization_instruction(&wr_action, action, action); | |
344 | build_serialization_instruction(&rd_value_32, action, 0); | |
345 | ||
346 | action = ACTION_GET_EXECUTE_OPERATION_TIMINGS; | |
347 | build_serialization_instruction(&wr_action, action, action); | |
348 | build_serialization_instruction(&rd_value_64, action, 0); | |
349 | ||
350 | /* Serialization Header */ | |
351 | acpi_table_begin(&table, table_data); | |
352 | ||
353 | /* Serialization Header Size */ | |
354 | build_append_int_noprefix(table_data, 48, 4); | |
355 | ||
356 | /* Reserved */ | |
357 | build_append_int_noprefix(table_data, 0, 4); | |
358 | ||
359 | /* | |
360 | * Instruction Entry Count | |
361 | * Each instruction entry is 32 bytes | |
362 | */ | |
363 | g_assert((table_instruction_data->len) % 32 == 0); | |
364 | build_append_int_noprefix(table_data, | |
365 | (table_instruction_data->len / 32), 4); | |
366 | ||
367 | /* Serialization Instruction Entries */ | |
368 | g_array_append_vals(table_data, table_instruction_data->data, | |
369 | table_instruction_data->len); | |
370 | g_array_free(table_instruction_data, TRUE); | |
371 | ||
372 | acpi_table_end(linker, &table); | |
373 | } | |
374 | ||
f7e26ffa ED |
375 | /*******************************************************************/ |
376 | /*******************************************************************/ | |
377 | static uint8_t *get_nvram_ptr_by_index(ERSTDeviceState *s, unsigned index) | |
378 | { | |
379 | uint8_t *rc = NULL; | |
380 | off_t offset = (index * le32_to_cpu(s->header->record_size)); | |
381 | ||
382 | g_assert(offset < s->storage_size); | |
383 | ||
384 | rc = memory_region_get_ram_ptr(s->hostmem_mr); | |
385 | rc += offset; | |
386 | ||
387 | return rc; | |
388 | } | |
389 | ||
390 | static void make_erst_storage_header(ERSTDeviceState *s) | |
391 | { | |
392 | ERSTStorageHeader *header = s->header; | |
393 | unsigned mapsz, headersz; | |
394 | ||
395 | header->magic = cpu_to_le64(ERST_STORE_MAGIC); | |
396 | header->record_size = cpu_to_le32(s->default_record_size); | |
397 | header->version = cpu_to_le16(0x0100); | |
398 | header->reserved = cpu_to_le16(0x0000); | |
399 | ||
400 | /* Compute mapsize */ | |
401 | mapsz = s->storage_size / s->default_record_size; | |
402 | mapsz *= sizeof(uint64_t); | |
403 | /* Compute header+map size */ | |
404 | headersz = sizeof(ERSTStorageHeader) + mapsz; | |
405 | /* Round up to nearest integer multiple of ERST_RECORD_SIZE */ | |
406 | headersz = QEMU_ALIGN_UP(headersz, s->default_record_size); | |
407 | header->storage_offset = cpu_to_le32(headersz); | |
408 | ||
409 | /* | |
410 | * The HostMemoryBackend initializes contents to zero, | |
411 | * so all record_ids stashed in the map are zero'd. | |
412 | * As well the record_count is zero. Properly initialized. | |
413 | */ | |
414 | } | |
415 | ||
416 | static void check_erst_backend_storage(ERSTDeviceState *s, Error **errp) | |
417 | { | |
418 | ERSTStorageHeader *header; | |
419 | uint32_t record_size; | |
420 | ||
421 | header = memory_region_get_ram_ptr(s->hostmem_mr); | |
422 | s->header = header; | |
423 | ||
424 | /* Ensure pointer to header is 64-bit aligned */ | |
425 | g_assert(QEMU_PTR_IS_ALIGNED(header, sizeof(uint64_t))); | |
426 | ||
427 | /* | |
428 | * Check if header is uninitialized; HostMemoryBackend inits to 0 | |
429 | */ | |
430 | if (le64_to_cpu(header->magic) == 0UL) { | |
431 | make_erst_storage_header(s); | |
432 | } | |
433 | ||
434 | /* Validity check record_size */ | |
435 | record_size = le32_to_cpu(header->record_size); | |
436 | if (!( | |
437 | (record_size) && /* non zero */ | |
438 | (record_size >= UEFI_CPER_RECORD_MIN_SIZE) && | |
439 | (((record_size - 1) & record_size) == 0) && /* is power of 2 */ | |
440 | (record_size >= 4096) /* PAGE_SIZE */ | |
441 | )) { | |
442 | error_setg(errp, "ERST record_size %u is invalid", record_size); | |
8c97e4de | 443 | return; |
f7e26ffa ED |
444 | } |
445 | ||
446 | /* Validity check header */ | |
447 | if (!( | |
448 | (le64_to_cpu(header->magic) == ERST_STORE_MAGIC) && | |
449 | ((le32_to_cpu(header->storage_offset) % record_size) == 0) && | |
450 | (le16_to_cpu(header->version) == 0x0100) && | |
451 | (le16_to_cpu(header->reserved) == 0) | |
452 | )) { | |
453 | error_setg(errp, "ERST backend storage header is invalid"); | |
8c97e4de | 454 | return; |
f7e26ffa ED |
455 | } |
456 | ||
457 | /* Check storage_size against record_size */ | |
458 | if (((s->storage_size % record_size) != 0) || | |
459 | (record_size > s->storage_size)) { | |
460 | error_setg(errp, "ACPI ERST requires storage size be multiple of " | |
461 | "record size (%uKiB)", record_size); | |
8c97e4de | 462 | return; |
f7e26ffa ED |
463 | } |
464 | ||
465 | /* Compute offset of first and last record storage slot */ | |
466 | s->first_record_index = le32_to_cpu(header->storage_offset) | |
467 | / record_size; | |
468 | s->last_record_index = (s->storage_size / record_size); | |
469 | } | |
470 | ||
471 | static void update_map_entry(ERSTDeviceState *s, unsigned index, | |
472 | uint64_t record_id) | |
473 | { | |
474 | if (index < s->last_record_index) { | |
475 | s->header->map[index] = cpu_to_le64(record_id); | |
476 | } | |
477 | } | |
478 | ||
479 | static unsigned find_next_empty_record_index(ERSTDeviceState *s) | |
480 | { | |
481 | unsigned rc = 0; /* 0 not a valid index */ | |
482 | unsigned index = s->first_record_index; | |
483 | ||
484 | for (; index < s->last_record_index; ++index) { | |
485 | if (le64_to_cpu(s->header->map[index]) == ERST_UNSPECIFIED_RECORD_ID) { | |
486 | rc = index; | |
487 | break; | |
488 | } | |
489 | } | |
490 | ||
491 | return rc; | |
492 | } | |
493 | ||
494 | static unsigned lookup_erst_record(ERSTDeviceState *s, | |
495 | uint64_t record_identifier) | |
496 | { | |
497 | unsigned rc = 0; /* 0 not a valid index */ | |
498 | ||
499 | /* Find the record_identifier in the map */ | |
500 | if (record_identifier != ERST_UNSPECIFIED_RECORD_ID) { | |
501 | /* | |
502 | * Count number of valid records encountered, and | |
503 | * short-circuit the loop if identifier not found | |
504 | */ | |
505 | uint32_t record_count = le32_to_cpu(s->header->record_count); | |
506 | unsigned count = 0; | |
507 | unsigned index; | |
508 | for (index = s->first_record_index; index < s->last_record_index && | |
509 | count < record_count; ++index) { | |
510 | if (le64_to_cpu(s->header->map[index]) == record_identifier) { | |
511 | rc = index; | |
512 | break; | |
513 | } | |
514 | if (le64_to_cpu(s->header->map[index]) != | |
515 | ERST_UNSPECIFIED_RECORD_ID) { | |
516 | ++count; | |
517 | } | |
518 | } | |
519 | } | |
520 | ||
521 | return rc; | |
522 | } | |
523 | ||
524 | /* | |
525 | * ACPI 4.0: 17.4.1.1 Serialization Actions, also see | |
526 | * ACPI 4.0: 17.4.2.2 Operations - Reading 6.c and 2.c | |
527 | */ | |
528 | static unsigned get_next_record_identifier(ERSTDeviceState *s, | |
529 | uint64_t *record_identifier, bool first) | |
530 | { | |
531 | unsigned found = 0; | |
532 | unsigned index; | |
533 | ||
534 | /* For operations needing to return 'first' record identifier */ | |
535 | if (first) { | |
536 | /* Reset initial index to beginning */ | |
537 | s->next_record_index = s->first_record_index; | |
538 | } | |
539 | index = s->next_record_index; | |
540 | ||
541 | *record_identifier = ERST_EMPTY_END_RECORD_ID; | |
542 | ||
543 | if (le32_to_cpu(s->header->record_count)) { | |
544 | for (; index < s->last_record_index; ++index) { | |
545 | if (le64_to_cpu(s->header->map[index]) != | |
546 | ERST_UNSPECIFIED_RECORD_ID) { | |
547 | /* where to start next time */ | |
548 | s->next_record_index = index + 1; | |
549 | *record_identifier = le64_to_cpu(s->header->map[index]); | |
550 | found = 1; | |
551 | break; | |
552 | } | |
553 | } | |
554 | } | |
555 | if (!found) { | |
556 | /* at end (ie scan complete), reset */ | |
557 | s->next_record_index = s->first_record_index; | |
558 | } | |
559 | ||
560 | return STATUS_SUCCESS; | |
561 | } | |
562 | ||
563 | /* ACPI 4.0: 17.4.2.3 Operations - Clearing */ | |
564 | static unsigned clear_erst_record(ERSTDeviceState *s) | |
565 | { | |
566 | unsigned rc = STATUS_RECORD_NOT_FOUND; | |
567 | unsigned index; | |
568 | ||
569 | /* Check for valid record identifier */ | |
570 | if (!ERST_IS_VALID_RECORD_ID(s->record_identifier)) { | |
571 | return STATUS_FAILED; | |
572 | } | |
573 | ||
574 | index = lookup_erst_record(s, s->record_identifier); | |
575 | if (index) { | |
576 | /* No need to wipe record, just invalidate its map entry */ | |
577 | uint32_t record_count; | |
578 | update_map_entry(s, index, ERST_UNSPECIFIED_RECORD_ID); | |
579 | record_count = le32_to_cpu(s->header->record_count); | |
580 | record_count -= 1; | |
581 | s->header->record_count = cpu_to_le32(record_count); | |
582 | rc = STATUS_SUCCESS; | |
583 | } | |
584 | ||
585 | return rc; | |
586 | } | |
587 | ||
588 | /* ACPI 4.0: 17.4.2.2 Operations - Reading */ | |
589 | static unsigned read_erst_record(ERSTDeviceState *s) | |
590 | { | |
591 | unsigned rc = STATUS_RECORD_NOT_FOUND; | |
592 | unsigned exchange_length; | |
593 | unsigned index; | |
594 | ||
595 | /* Check if backend storage is empty */ | |
596 | if (le32_to_cpu(s->header->record_count) == 0) { | |
597 | return STATUS_RECORD_STORE_EMPTY; | |
598 | } | |
599 | ||
600 | exchange_length = memory_region_size(&s->exchange_mr); | |
601 | ||
602 | /* Check for record identifier of all 0s */ | |
603 | if (s->record_identifier == ERST_UNSPECIFIED_RECORD_ID) { | |
604 | /* Set to 'first' record in storage */ | |
605 | get_next_record_identifier(s, &s->record_identifier, true); | |
606 | /* record_identifier is now a valid id, or all 1s */ | |
607 | } | |
608 | ||
609 | /* Check for record identifier of all 1s */ | |
610 | if (s->record_identifier == ERST_EMPTY_END_RECORD_ID) { | |
611 | return STATUS_FAILED; | |
612 | } | |
613 | ||
614 | /* Validate record_offset */ | |
615 | if (s->record_offset > (exchange_length - UEFI_CPER_RECORD_MIN_SIZE)) { | |
616 | return STATUS_FAILED; | |
617 | } | |
618 | ||
619 | index = lookup_erst_record(s, s->record_identifier); | |
620 | if (index) { | |
621 | uint8_t *nvram; | |
622 | uint8_t *exchange; | |
623 | uint32_t record_length; | |
624 | ||
625 | /* Obtain pointer to the exchange buffer */ | |
626 | exchange = memory_region_get_ram_ptr(&s->exchange_mr); | |
627 | exchange += s->record_offset; | |
628 | /* Obtain pointer to slot in storage */ | |
629 | nvram = get_nvram_ptr_by_index(s, index); | |
630 | /* Validate CPER record_length */ | |
631 | memcpy((uint8_t *)&record_length, | |
632 | &nvram[UEFI_CPER_RECORD_LENGTH_OFFSET], | |
633 | sizeof(uint32_t)); | |
634 | record_length = le32_to_cpu(record_length); | |
635 | if (record_length < UEFI_CPER_RECORD_MIN_SIZE) { | |
636 | rc = STATUS_FAILED; | |
637 | } | |
defb7098 | 638 | if (record_length > exchange_length - s->record_offset) { |
f7e26ffa ED |
639 | rc = STATUS_FAILED; |
640 | } | |
641 | /* If all is ok, copy the record to the exchange buffer */ | |
642 | if (rc != STATUS_FAILED) { | |
643 | memcpy(exchange, nvram, record_length); | |
644 | rc = STATUS_SUCCESS; | |
645 | } | |
646 | } else { | |
647 | /* | |
648 | * See "Reading : 'The steps performed by the platform ...' 2.c" | |
649 | * Set to 'first' record in storage | |
650 | */ | |
651 | get_next_record_identifier(s, &s->record_identifier, true); | |
652 | } | |
653 | ||
654 | return rc; | |
655 | } | |
656 | ||
657 | /* ACPI 4.0: 17.4.2.1 Operations - Writing */ | |
658 | static unsigned write_erst_record(ERSTDeviceState *s) | |
659 | { | |
660 | unsigned rc = STATUS_FAILED; | |
661 | unsigned exchange_length; | |
662 | unsigned index; | |
663 | uint64_t record_identifier; | |
664 | uint32_t record_length; | |
665 | uint8_t *exchange; | |
666 | uint8_t *nvram = NULL; | |
667 | bool record_found = false; | |
668 | ||
669 | exchange_length = memory_region_size(&s->exchange_mr); | |
670 | ||
671 | /* Validate record_offset */ | |
672 | if (s->record_offset > (exchange_length - UEFI_CPER_RECORD_MIN_SIZE)) { | |
673 | return STATUS_FAILED; | |
674 | } | |
675 | ||
676 | /* Obtain pointer to record in the exchange buffer */ | |
677 | exchange = memory_region_get_ram_ptr(&s->exchange_mr); | |
678 | exchange += s->record_offset; | |
679 | ||
680 | /* Validate CPER record_length */ | |
681 | memcpy((uint8_t *)&record_length, &exchange[UEFI_CPER_RECORD_LENGTH_OFFSET], | |
682 | sizeof(uint32_t)); | |
683 | record_length = le32_to_cpu(record_length); | |
684 | if (record_length < UEFI_CPER_RECORD_MIN_SIZE) { | |
685 | return STATUS_FAILED; | |
686 | } | |
defb7098 | 687 | if (record_length > exchange_length - s->record_offset) { |
f7e26ffa ED |
688 | return STATUS_FAILED; |
689 | } | |
690 | ||
691 | /* Extract record identifier */ | |
692 | memcpy((uint8_t *)&record_identifier, &exchange[UEFI_CPER_RECORD_ID_OFFSET], | |
693 | sizeof(uint64_t)); | |
694 | record_identifier = le64_to_cpu(record_identifier); | |
695 | ||
696 | /* Check for valid record identifier */ | |
697 | if (!ERST_IS_VALID_RECORD_ID(record_identifier)) { | |
698 | return STATUS_FAILED; | |
699 | } | |
700 | ||
701 | index = lookup_erst_record(s, record_identifier); | |
702 | if (index) { | |
703 | /* Record found, overwrite existing record */ | |
704 | nvram = get_nvram_ptr_by_index(s, index); | |
705 | record_found = true; | |
706 | } else { | |
707 | /* Record not found, not an overwrite, allocate for write */ | |
708 | index = find_next_empty_record_index(s); | |
709 | if (index) { | |
710 | nvram = get_nvram_ptr_by_index(s, index); | |
711 | } else { | |
712 | /* All slots are occupied */ | |
713 | rc = STATUS_NOT_ENOUGH_SPACE; | |
714 | } | |
715 | } | |
716 | if (nvram) { | |
717 | /* Write the record into the slot */ | |
718 | memcpy(nvram, exchange, record_length); | |
defb7098 | 719 | memset(nvram + record_length, 0xFF, exchange_length - record_length); |
f7e26ffa ED |
720 | /* If a new record, increment the record_count */ |
721 | if (!record_found) { | |
722 | uint32_t record_count; | |
723 | record_count = le32_to_cpu(s->header->record_count); | |
724 | record_count += 1; /* writing new record */ | |
725 | s->header->record_count = cpu_to_le32(record_count); | |
726 | } | |
727 | update_map_entry(s, index, record_identifier); | |
728 | rc = STATUS_SUCCESS; | |
729 | } | |
730 | ||
731 | return rc; | |
732 | } | |
733 | ||
734 | /*******************************************************************/ | |
735 | ||
736 | static uint64_t erst_rd_reg64(hwaddr addr, | |
737 | uint64_t reg, unsigned size) | |
738 | { | |
739 | uint64_t rdval; | |
740 | uint64_t mask; | |
741 | unsigned shift; | |
742 | ||
743 | if (size == sizeof(uint64_t)) { | |
744 | /* 64b access */ | |
745 | mask = 0xFFFFFFFFFFFFFFFFUL; | |
746 | shift = 0; | |
747 | } else { | |
748 | /* 32b access */ | |
749 | mask = 0x00000000FFFFFFFFUL; | |
750 | shift = ((addr & 0x4) == 0x4) ? 32 : 0; | |
751 | } | |
752 | ||
753 | rdval = reg; | |
754 | rdval >>= shift; | |
755 | rdval &= mask; | |
756 | ||
757 | return rdval; | |
758 | } | |
759 | ||
760 | static uint64_t erst_wr_reg64(hwaddr addr, | |
761 | uint64_t reg, uint64_t val, unsigned size) | |
762 | { | |
763 | uint64_t wrval; | |
764 | uint64_t mask; | |
765 | unsigned shift; | |
766 | ||
767 | if (size == sizeof(uint64_t)) { | |
768 | /* 64b access */ | |
769 | mask = 0xFFFFFFFFFFFFFFFFUL; | |
770 | shift = 0; | |
771 | } else { | |
772 | /* 32b access */ | |
773 | mask = 0x00000000FFFFFFFFUL; | |
774 | shift = ((addr & 0x4) == 0x4) ? 32 : 0; | |
775 | } | |
776 | ||
777 | val &= mask; | |
778 | val <<= shift; | |
779 | mask <<= shift; | |
780 | wrval = reg; | |
781 | wrval &= ~mask; | |
782 | wrval |= val; | |
783 | ||
784 | return wrval; | |
785 | } | |
786 | ||
787 | static void erst_reg_write(void *opaque, hwaddr addr, | |
788 | uint64_t val, unsigned size) | |
789 | { | |
790 | ERSTDeviceState *s = (ERSTDeviceState *)opaque; | |
791 | ||
792 | /* | |
793 | * NOTE: All actions/operations/side effects happen on the WRITE, | |
794 | * by this implementation's design. The READs simply return the | |
795 | * reg_value contents. | |
796 | */ | |
797 | trace_acpi_erst_reg_write(addr, val, size); | |
798 | ||
799 | switch (addr) { | |
800 | case ERST_VALUE_OFFSET + 0: | |
801 | case ERST_VALUE_OFFSET + 4: | |
802 | s->reg_value = erst_wr_reg64(addr, s->reg_value, val, size); | |
803 | break; | |
804 | case ERST_ACTION_OFFSET + 0: | |
805 | /* | |
806 | * NOTE: all valid values written to this register are of the | |
807 | * ACTION_* variety. Thus there is no need to make this a 64-bit | |
808 | * register, 32-bits is appropriate. As such ERST_ACTION_OFFSET+4 | |
809 | * is not needed. | |
810 | */ | |
811 | switch (val) { | |
812 | case ACTION_BEGIN_WRITE_OPERATION: | |
813 | case ACTION_BEGIN_READ_OPERATION: | |
814 | case ACTION_BEGIN_CLEAR_OPERATION: | |
815 | case ACTION_BEGIN_DUMMY_WRITE_OPERATION: | |
816 | case ACTION_END_OPERATION: | |
817 | s->operation = val; | |
818 | break; | |
819 | case ACTION_SET_RECORD_OFFSET: | |
820 | s->record_offset = s->reg_value; | |
821 | break; | |
822 | case ACTION_EXECUTE_OPERATION: | |
823 | if ((uint8_t)s->reg_value == ERST_EXECUTE_OPERATION_MAGIC) { | |
824 | s->busy_status = 1; | |
825 | switch (s->operation) { | |
826 | case ACTION_BEGIN_WRITE_OPERATION: | |
827 | s->command_status = write_erst_record(s); | |
828 | break; | |
829 | case ACTION_BEGIN_READ_OPERATION: | |
830 | s->command_status = read_erst_record(s); | |
831 | break; | |
832 | case ACTION_BEGIN_CLEAR_OPERATION: | |
833 | s->command_status = clear_erst_record(s); | |
834 | break; | |
835 | case ACTION_BEGIN_DUMMY_WRITE_OPERATION: | |
836 | s->command_status = STATUS_SUCCESS; | |
837 | break; | |
838 | case ACTION_END_OPERATION: | |
839 | s->command_status = STATUS_SUCCESS; | |
840 | break; | |
841 | default: | |
842 | s->command_status = STATUS_FAILED; | |
843 | break; | |
844 | } | |
845 | s->busy_status = 0; | |
846 | } | |
847 | break; | |
848 | case ACTION_CHECK_BUSY_STATUS: | |
849 | s->reg_value = s->busy_status; | |
850 | break; | |
851 | case ACTION_GET_COMMAND_STATUS: | |
852 | s->reg_value = s->command_status; | |
853 | break; | |
854 | case ACTION_GET_RECORD_IDENTIFIER: | |
855 | s->command_status = get_next_record_identifier(s, | |
856 | &s->reg_value, false); | |
857 | break; | |
858 | case ACTION_SET_RECORD_IDENTIFIER: | |
859 | s->record_identifier = s->reg_value; | |
860 | break; | |
861 | case ACTION_GET_RECORD_COUNT: | |
862 | s->reg_value = le32_to_cpu(s->header->record_count); | |
863 | break; | |
864 | case ACTION_GET_ERROR_LOG_ADDRESS_RANGE: | |
865 | s->reg_value = (hwaddr)pci_get_bar_addr(PCI_DEVICE(s), 1); | |
866 | break; | |
867 | case ACTION_GET_ERROR_LOG_ADDRESS_LENGTH: | |
868 | s->reg_value = le32_to_cpu(s->header->record_size); | |
869 | break; | |
870 | case ACTION_GET_ERROR_LOG_ADDRESS_RANGE_ATTRIBUTES: | |
871 | s->reg_value = 0x0; /* intentional, not NVRAM mode */ | |
872 | break; | |
873 | case ACTION_GET_EXECUTE_OPERATION_TIMINGS: | |
874 | s->reg_value = | |
875 | (100ULL << 32) | /* 100us max time */ | |
876 | (10ULL << 0) ; /* 10us min time */ | |
877 | break; | |
878 | default: | |
879 | /* Unknown action/command, NOP */ | |
880 | break; | |
881 | } | |
882 | break; | |
883 | default: | |
884 | /* This should not happen, but if it does, NOP */ | |
885 | break; | |
886 | } | |
887 | } | |
888 | ||
889 | static uint64_t erst_reg_read(void *opaque, hwaddr addr, | |
890 | unsigned size) | |
891 | { | |
892 | ERSTDeviceState *s = (ERSTDeviceState *)opaque; | |
893 | uint64_t val = 0; | |
894 | ||
895 | switch (addr) { | |
896 | case ERST_ACTION_OFFSET + 0: | |
897 | case ERST_ACTION_OFFSET + 4: | |
898 | val = erst_rd_reg64(addr, s->reg_action, size); | |
899 | break; | |
900 | case ERST_VALUE_OFFSET + 0: | |
901 | case ERST_VALUE_OFFSET + 4: | |
902 | val = erst_rd_reg64(addr, s->reg_value, size); | |
903 | break; | |
904 | default: | |
905 | break; | |
906 | } | |
907 | trace_acpi_erst_reg_read(addr, val, size); | |
908 | return val; | |
909 | } | |
910 | ||
911 | static const MemoryRegionOps erst_reg_ops = { | |
912 | .read = erst_reg_read, | |
913 | .write = erst_reg_write, | |
914 | .endianness = DEVICE_NATIVE_ENDIAN, | |
915 | }; | |
916 | ||
917 | /*******************************************************************/ | |
918 | /*******************************************************************/ | |
919 | static int erst_post_load(void *opaque, int version_id) | |
920 | { | |
921 | ERSTDeviceState *s = opaque; | |
922 | ||
923 | /* Recompute pointer to header */ | |
924 | s->header = (ERSTStorageHeader *)get_nvram_ptr_by_index(s, 0); | |
925 | trace_acpi_erst_post_load(s->header, le32_to_cpu(s->header->record_size)); | |
926 | ||
927 | return 0; | |
928 | } | |
929 | ||
930 | static const VMStateDescription erst_vmstate = { | |
931 | .name = "acpi-erst", | |
932 | .version_id = 1, | |
933 | .minimum_version_id = 1, | |
934 | .post_load = erst_post_load, | |
c559ba57 | 935 | .fields = (const VMStateField[]) { |
f7e26ffa ED |
936 | VMSTATE_UINT8(operation, ERSTDeviceState), |
937 | VMSTATE_UINT8(busy_status, ERSTDeviceState), | |
938 | VMSTATE_UINT8(command_status, ERSTDeviceState), | |
939 | VMSTATE_UINT32(record_offset, ERSTDeviceState), | |
940 | VMSTATE_UINT64(reg_action, ERSTDeviceState), | |
941 | VMSTATE_UINT64(reg_value, ERSTDeviceState), | |
942 | VMSTATE_UINT64(record_identifier, ERSTDeviceState), | |
943 | VMSTATE_UINT32(next_record_index, ERSTDeviceState), | |
944 | VMSTATE_END_OF_LIST() | |
945 | } | |
946 | }; | |
947 | ||
948 | static void erst_realizefn(PCIDevice *pci_dev, Error **errp) | |
949 | { | |
20bc5013 | 950 | ERRP_GUARD(); |
f7e26ffa ED |
951 | ERSTDeviceState *s = ACPIERST(pci_dev); |
952 | ||
953 | trace_acpi_erst_realizefn_in(); | |
954 | ||
955 | if (!s->hostmem) { | |
956 | error_setg(errp, "'" ACPI_ERST_MEMDEV_PROP "' property is not set"); | |
957 | return; | |
958 | } else if (host_memory_backend_is_mapped(s->hostmem)) { | |
959 | error_setg(errp, "can't use already busy memdev: %s", | |
960 | object_get_canonical_path_component(OBJECT(s->hostmem))); | |
961 | return; | |
962 | } | |
963 | ||
964 | s->hostmem_mr = host_memory_backend_get_memory(s->hostmem); | |
965 | ||
966 | /* HostMemoryBackend size will be multiple of PAGE_SIZE */ | |
967 | s->storage_size = object_property_get_int(OBJECT(s->hostmem), "size", errp); | |
20bc5013 PMD |
968 | if (*errp) { |
969 | return; | |
970 | } | |
f7e26ffa ED |
971 | |
972 | /* Initialize backend storage and record_count */ | |
973 | check_erst_backend_storage(s, errp); | |
20bc5013 PMD |
974 | if (*errp) { |
975 | return; | |
976 | } | |
f7e26ffa ED |
977 | |
978 | /* BAR 0: Programming registers */ | |
979 | memory_region_init_io(&s->iomem_mr, OBJECT(pci_dev), &erst_reg_ops, s, | |
980 | TYPE_ACPI_ERST, ERST_REG_SIZE); | |
981 | pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->iomem_mr); | |
982 | ||
983 | /* BAR 1: Exchange buffer memory */ | |
984 | memory_region_init_ram(&s->exchange_mr, OBJECT(pci_dev), | |
985 | "erst.exchange", | |
986 | le32_to_cpu(s->header->record_size), errp); | |
20bc5013 PMD |
987 | if (*errp) { |
988 | return; | |
989 | } | |
f7e26ffa ED |
990 | pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, |
991 | &s->exchange_mr); | |
992 | ||
993 | /* Include the backend storage in the migration stream */ | |
994 | vmstate_register_ram_global(s->hostmem_mr); | |
995 | ||
996 | trace_acpi_erst_realizefn_out(s->storage_size); | |
997 | } | |
998 | ||
999 | static void erst_reset(DeviceState *dev) | |
1000 | { | |
1001 | ERSTDeviceState *s = ACPIERST(dev); | |
1002 | ||
1003 | trace_acpi_erst_reset_in(le32_to_cpu(s->header->record_count)); | |
1004 | s->operation = 0; | |
1005 | s->busy_status = 0; | |
1006 | s->command_status = STATUS_SUCCESS; | |
1007 | s->record_identifier = ERST_UNSPECIFIED_RECORD_ID; | |
1008 | s->record_offset = 0; | |
1009 | s->next_record_index = s->first_record_index; | |
1010 | /* NOTE: first/last_record_index are computed only once */ | |
1011 | trace_acpi_erst_reset_out(le32_to_cpu(s->header->record_count)); | |
1012 | } | |
1013 | ||
1014 | static Property erst_properties[] = { | |
1015 | DEFINE_PROP_LINK(ACPI_ERST_MEMDEV_PROP, ERSTDeviceState, hostmem, | |
1016 | TYPE_MEMORY_BACKEND, HostMemoryBackend *), | |
1017 | DEFINE_PROP_UINT32(ACPI_ERST_RECORD_SIZE_PROP, ERSTDeviceState, | |
1018 | default_record_size, ERST_RECORD_SIZE), | |
1019 | DEFINE_PROP_END_OF_LIST(), | |
1020 | }; | |
1021 | ||
1022 | static void erst_class_init(ObjectClass *klass, void *data) | |
1023 | { | |
1024 | DeviceClass *dc = DEVICE_CLASS(klass); | |
1025 | PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); | |
1026 | ||
1027 | trace_acpi_erst_class_init_in(); | |
1028 | k->realize = erst_realizefn; | |
1029 | k->vendor_id = PCI_VENDOR_ID_REDHAT; | |
1030 | k->device_id = PCI_DEVICE_ID_REDHAT_ACPI_ERST; | |
1031 | k->revision = 0x00; | |
1032 | k->class_id = PCI_CLASS_OTHERS; | |
1033 | dc->reset = erst_reset; | |
1034 | dc->vmsd = &erst_vmstate; | |
1035 | dc->user_creatable = true; | |
1036 | dc->hotpluggable = false; | |
1037 | device_class_set_props(dc, erst_properties); | |
1038 | dc->desc = "ACPI Error Record Serialization Table (ERST) device"; | |
1039 | set_bit(DEVICE_CATEGORY_MISC, dc->categories); | |
1040 | trace_acpi_erst_class_init_out(); | |
1041 | } | |
1042 | ||
1043 | static const TypeInfo erst_type_info = { | |
1044 | .name = TYPE_ACPI_ERST, | |
1045 | .parent = TYPE_PCI_DEVICE, | |
1046 | .class_init = erst_class_init, | |
1047 | .instance_size = sizeof(ERSTDeviceState), | |
1048 | .interfaces = (InterfaceInfo[]) { | |
1049 | { INTERFACE_CONVENTIONAL_PCI_DEVICE }, | |
1050 | { } | |
1051 | } | |
1052 | }; | |
1053 | ||
1054 | static void erst_register_types(void) | |
1055 | { | |
1056 | type_register_static(&erst_type_info); | |
1057 | } | |
1058 | ||
1059 | type_init(erst_register_types) |