1/*
2 * Copyright 2014, General Dynamics C4 Systems
3 *
4 * This software may be distributed and modified according to the terms of
5 * the GNU General Public License version 2. Note that NO WARRANTY is provided.
6 * See "LICENSE_GPLv2.txt" for details.
7 *
8 * @TAG(GD_GPL)
9 */
10
11#include <config.h>
12#include <util.h>
13#include <assert.h>
14#include <machine/io.h>
15#include <linker.h>
16#include <plat/machine.h>
17#include <plat/machine/acpi.h>
18#include <plat/machine/devices.h>
19
20enum acpi_type {
21    ACPI_RSDP,
22    ACPI_RSDT
23};
24
25/* DMA Remapping Reporting Table */
26typedef struct acpi_dmar {
27    acpi_header_t header;
28    uint8_t       host_addr_width;
29    uint8_t       flags;
30    uint8_t       reserved[10];
31} acpi_dmar_t;
32compile_assert(acpi_dmar_packed,
33               sizeof(acpi_dmar_t) == sizeof(acpi_header_t) + 12)
34
35/* DMA Remapping Structure Header */
36typedef struct acpi_dmar_header {
37    uint16_t type;
38    uint16_t length;
39} acpi_dmar_header_t;
40compile_assert(acpi_dmar_header_packed, sizeof(acpi_dmar_header_t) == 4)
41
42/* DMA Remapping Structure Types */
43enum acpi_table_dmar_struct_type {
44    DMAR_DRHD = 0,
45    DMAR_RMRR = 1,
46    DMAR_ATSR = 2,
47};
48
49/* DMA Remapping Hardware unit Definition */
50typedef struct acpi_dmar_drhd {
51    acpi_dmar_header_t header;
52    uint8_t            flags;
53    uint8_t            reserved;
54    uint16_t           segment;
55    uint32_t           reg_base[2];
56} acpi_dmar_drhd_t;
57compile_assert(acpi_dmar_drhd_packed,
58               sizeof(acpi_dmar_drhd_t) == sizeof(acpi_dmar_header_t) + 12)
59
60/* Reserved Memory Region Reporting structure Definition */
61typedef struct acpi_dmar_devscope {
62    uint8_t  type;
63    uint8_t  length;
64    uint16_t reserved;
65    uint8_t  enum_id;
66    uint8_t  start_bus;
67    struct {
68        uint8_t dev;
69        uint8_t fun;
70    } path_0;
71} acpi_dmar_devscope_t;
72compile_assert(acpi_dmar_devscope_packed, sizeof(acpi_dmar_devscope_t) == 8)
73
74/* Reserved Memory Region Reporting structure Definition */
75typedef struct acpi_dmar_rmrr {
76    acpi_dmar_header_t   header;
77    uint16_t             reserved;
78    uint16_t             segment;
79    uint32_t             reg_base[2];
80    uint32_t             reg_limit[2];
81    acpi_dmar_devscope_t devscope_0;
82} acpi_dmar_rmrr_t;
83compile_assert(acpi_dmar_rmrr_packed, sizeof(acpi_dmar_rmrr_t) ==
84               sizeof(acpi_dmar_header_t) + 20 + sizeof(acpi_dmar_devscope_t))
85
86/* Fixed ACPI Description Table (FADT), partial as we only need flags */
87typedef struct acpi_fadt {
88    acpi_header_t  header;
89    uint8_t        reserved[76];
90    uint32_t       flags;
91} acpi_fadt_t;
92compile_assert(acpi_fadt_packed,
93               sizeof(acpi_fadt_t) == sizeof(acpi_header_t) + 80)
94
95/* Multiple APIC Description Table (MADT) */
96typedef struct acpi_madt {
97    acpi_header_t header;
98    uint32_t      apic_addr;
99    uint32_t      flags;
100} acpi_madt_t;
101compile_assert(acpi_madt_packed,
102               sizeof(acpi_madt_t) == sizeof(acpi_header_t) + 8)
103
104typedef struct acpi_madt_header {
105    uint8_t type;
106    uint8_t length;
107} acpi_madt_header_t;
108compile_assert(acpi_madt_header_packed, sizeof(acpi_madt_header_t) == 2)
109
110enum acpi_table_madt_struct_type {
111    MADT_APIC   = 0,
112    MADT_IOAPIC = 1,
113    MADT_ISO    = 2,
114    MADT_x2APIC = 9
115};
116
117typedef struct acpi_madt_apic {
118    acpi_madt_header_t header;
119    uint8_t            cpu_id;
120    uint8_t            apic_id;
121    uint32_t           flags;
122} acpi_madt_apic_t;
123compile_assert(acpi_madt_apic_packed,
124               sizeof(acpi_madt_apic_t) == sizeof(acpi_madt_header_t) + 6)
125
126typedef struct acpi_madt_x2apic {
127    acpi_madt_header_t  header;
128    uint16_t            reserved;
129    uint32_t            x2apic_id;
130    uint32_t            flags;
131    uint32_t            acpi_processor_uid;
132} acpi_madt_x2apic_t;
133compile_assert(acpi_madt_x2apic_packed,
134               sizeof(acpi_madt_x2apic_t) == sizeof(acpi_madt_header_t) + 14)
135
136typedef struct acpi_madt_ioapic {
137    acpi_madt_header_t header;
138    uint8_t            ioapic_id;
139    uint8_t            reserved[1];
140    uint32_t           ioapic_addr;
141    uint32_t           gsib;
142} acpi_madt_ioapic_t;
143compile_assert(acpi_madt_ioapic_packed,
144               sizeof(acpi_madt_ioapic_t) == sizeof(acpi_madt_header_t) + 10)
145
146typedef struct acpi_madt_iso {
147    acpi_madt_header_t header;
148    uint8_t            bus; /* always 0 (ISA) */
149    uint8_t            source;
150    uint32_t           gsi;
151    uint16_t           flags;
152} acpi_madt_iso_t;
153/* We can't assert on the sizeof acpi_madt_iso because it contains trailing
154 * padding.
155 */
156unverified_compile_assert(acpi_madt_iso_packed,
157                          OFFSETOF(acpi_madt_iso_t, flags) == sizeof(acpi_madt_header_t) + 6)
158
159/* workaround because string literals are not supported by C parser */
160const char acpi_str_rsd[]  = {'R', 'S', 'D', ' ', 'P', 'T', 'R', ' ', 0};
161const char acpi_str_fadt[] = {'F', 'A', 'C', 'P', 0};
162const char acpi_str_apic[] = {'A', 'P', 'I', 'C', 0};
163const char acpi_str_dmar[] = {'D', 'M', 'A', 'R', 0};
164
165BOOT_CODE static uint8_t
166acpi_calc_checksum(char* start, uint32_t length)
167{
168    uint8_t checksum = 0;
169
170    while (length > 0) {
171        checksum += *start;
172        start++;
173        length--;
174    }
175    return checksum;
176}
177
178BOOT_CODE static acpi_rsdp_t*
179acpi_get_rsdp(void)
180{
181    char* addr;
182
183    for (addr = (char*)BIOS_PADDR_START; addr < (char*)BIOS_PADDR_END; addr += 16) {
184        if (strncmp(addr, acpi_str_rsd, 8) == 0) {
185            if (acpi_calc_checksum(addr, ACPI_V1_SIZE) == 0) {
186                return (acpi_rsdp_t*)addr;
187            }
188        }
189    }
190    return NULL;
191}
192
193BOOT_CODE static void*
194acpi_table_init(void* entry, enum acpi_type table_type)
195{
196    void* acpi_table;
197    unsigned int pages_for_table;
198    unsigned int pages_for_header = 1;
199
200    /* if we need to map another page to read header */
201    unsigned long offset_in_page = (unsigned long)entry & MASK(LARGE_PAGE_BITS);
202    if (MASK(LARGE_PAGE_BITS) - offset_in_page < sizeof(acpi_rsdp_t)) {
203        pages_for_header++;
204    }
205
206    /* map in table's header */
207    acpi_table = map_temp_boot_page(entry, pages_for_header);
208
209    switch (table_type) {
210    case ACPI_RSDP: {
211        acpi_rsdp_t *rsdp_entry = (acpi_rsdp_t*)entry;
212        pages_for_table = (rsdp_entry->length + offset_in_page) / MASK(LARGE_PAGE_BITS) + 1;
213        break;
214    }
215    case ACPI_RSDT: { // RSDT, MADT, DMAR etc.
216        acpi_rsdt_t *rsdt_entry = (acpi_rsdt_t*)entry;
217        pages_for_table = (rsdt_entry->header.length + offset_in_page) / MASK(LARGE_PAGE_BITS) + 1;
218        break;
219    }
220    default:
221        printf("Error: Mapping unknown ACPI table type\n");
222        assert(false);
223        return NULL;
224    }
225
226    /* map in full table */
227    acpi_table = map_temp_boot_page(entry, pages_for_table);
228
229    return acpi_table;
230}
231
232BOOT_CODE bool_t
233acpi_init(acpi_rsdp_t *rsdp_data)
234{
235    acpi_rsdp_t* acpi_rsdp = acpi_get_rsdp();
236
237    if (acpi_rsdp == NULL) {
238        printf("BIOS: No ACPI support detected\n");
239        return false;
240    }
241    printf("ACPI: RSDP paddr=%p\n", acpi_rsdp);
242    acpi_rsdp = acpi_table_init(acpi_rsdp, ACPI_RSDP);
243    printf("ACPI: RSDP vaddr=%p\n", acpi_rsdp);
244
245    /* create a copy of the rsdp data */
246    *rsdp_data = *acpi_rsdp;
247
248    /* perform final validation */
249    return acpi_validate_rsdp(rsdp_data);
250}
251
252BOOT_CODE bool_t
253acpi_validate_rsdp(acpi_rsdp_t *acpi_rsdp)
254{
255    acpi_rsdt_t* acpi_rsdt;
256    acpi_rsdt_t* acpi_rsdt_mapped;
257
258    if (acpi_calc_checksum((char*)acpi_rsdp, ACPI_V1_SIZE) != 0) {
259        printf("BIOS: ACPIv1 information corrupt\n");
260        return false;
261    }
262
263    if (acpi_rsdp->revision > 0 && acpi_calc_checksum((char*)acpi_rsdp, sizeof(*acpi_rsdp)) != 0) {
264        printf("BIOS: ACPIv2 information corrupt\n");
265        return false;
266    }
267
268    /* verify the rsdt, even though we do not actually make use of the mapping right now */
269    acpi_rsdt = (acpi_rsdt_t*)(word_t)acpi_rsdp->rsdt_address;
270    printf("ACPI: RSDT paddr=%p\n", acpi_rsdt);
271    acpi_rsdt_mapped = (acpi_rsdt_t*)acpi_table_init(acpi_rsdt, ACPI_RSDT);
272    printf("ACPI: RSDT vaddr=%p\n", acpi_rsdt_mapped);
273
274    assert(acpi_rsdt_mapped->header.length > 0);
275    if (acpi_calc_checksum((char*)acpi_rsdt_mapped, acpi_rsdt_mapped->header.length) != 0) {
276        printf("ACPI: RSDT checksum failure\n");
277        return false;
278    }
279
280    return true;
281}
282
283BOOT_CODE uint32_t
284acpi_madt_scan(
285    acpi_rsdp_t* acpi_rsdp,
286    cpu_id_t*    cpu_list,
287    uint32_t*    num_ioapic,
288    paddr_t*     ioapic_paddrs
289)
290{
291    unsigned int entries;
292    uint32_t            num_cpu;
293    uint32_t            count;
294    acpi_madt_t*        acpi_madt;
295    acpi_madt_header_t* acpi_madt_header;
296
297    acpi_rsdt_t* acpi_rsdt_mapped;
298    acpi_madt_t* acpi_madt_mapped;
299    acpi_rsdt_mapped = (acpi_rsdt_t*)acpi_table_init((acpi_rsdt_t*)(word_t)acpi_rsdp->rsdt_address, ACPI_RSDT);
300
301    num_cpu = 0;
302    *num_ioapic = 0;
303
304    assert(acpi_rsdt_mapped->header.length >= sizeof(acpi_header_t));
305    /* Divide by uint32_t explicitly as this is the size as mandated by the ACPI standard */
306    entries = (acpi_rsdt_mapped->header.length - sizeof(acpi_header_t)) / sizeof(uint32_t);
307    for (count = 0; count < entries; count++) {
308        acpi_madt = (acpi_madt_t*)(word_t)acpi_rsdt_mapped->entry[count];
309        acpi_madt_mapped = (acpi_madt_t*)acpi_table_init(acpi_madt, ACPI_RSDT);
310
311        if (strncmp(acpi_str_apic, acpi_madt_mapped->header.signature, 4) == 0) {
312            printf("ACPI: MADT paddr=%p\n", acpi_madt);
313            printf("ACPI: MADT vaddr=%p\n", acpi_madt_mapped);
314            printf("ACPI: MADT apic_addr=0x%x\n", acpi_madt_mapped->apic_addr);
315            printf("ACPI: MADT flags=0x%x\n", acpi_madt_mapped->flags);
316
317            acpi_madt_header = (acpi_madt_header_t*)(acpi_madt_mapped + 1);
318
319            while ((char*)acpi_madt_header < (char*)acpi_madt_mapped + acpi_madt_mapped->header.length) {
320                switch (acpi_madt_header->type) {
321                /* ACPI specifies the following rules when listing APIC IDs:
322                 *  - Boot processor is listed first
323                 *  - For multi-threaded processors, BIOS should list the first logical
324                 *    processor of each of the individual multi-threaded processors in MADT
325                 *    before listing any of the second logical processors.
326                 *  - APIC IDs < 0xFF should be listed in APIC subtable, APIC IDs >= 0xFF
327                 *    should be listed in X2APIC subtable */
328                case MADT_APIC: {
329                    /* what Intel calls apic_id is what is called cpu_id in seL4! */
330                    uint8_t  cpu_id = ((acpi_madt_apic_t*)acpi_madt_header)->apic_id;
331                    uint32_t flags  = ((acpi_madt_apic_t*)acpi_madt_header)->flags;
332                    if (flags == 1) {
333                        printf("ACPI: MADT_APIC apic_id=0x%x\n", cpu_id);
334                        if (num_cpu == CONFIG_MAX_NUM_NODES) {
335                            printf("ACPI: Not recording this APIC, only support %d\n", CONFIG_MAX_NUM_NODES);
336                        } else {
337                            cpu_list[num_cpu] = cpu_id;
338                            num_cpu++;
339                        }
340                    }
341                    break;
342                }
343                case MADT_x2APIC: {
344                    uint32_t cpu_id = ((acpi_madt_x2apic_t*)acpi_madt_header)->x2apic_id;
345                    uint32_t flags  = ((acpi_madt_x2apic_t*)acpi_madt_header)->flags;
346                    if (flags == 1) {
347                        printf("ACPI: MADT_x2APIC apic_id=0x%x\n", cpu_id);
348                        if (num_cpu == CONFIG_MAX_NUM_NODES) {
349                            printf("ACPI: Not recording this APIC, only support %d\n", CONFIG_MAX_NUM_NODES);
350                        } else {
351                            cpu_list[num_cpu] = cpu_id;
352                            num_cpu++;
353                        }
354                    }
355                    break;
356                }
357                case MADT_IOAPIC:
358                    printf(
359                        "ACPI: MADT_IOAPIC ioapic_id=%d ioapic_addr=0x%x gsib=%d\n",
360                        ((acpi_madt_ioapic_t*)acpi_madt_header)->ioapic_id,
361                        ((acpi_madt_ioapic_t*)acpi_madt_header)->ioapic_addr,
362                        ((acpi_madt_ioapic_t*)acpi_madt_header)->gsib
363                    );
364                    if (*num_ioapic == CONFIG_MAX_NUM_IOAPIC) {
365                        printf("ACPI: Not recording this IOAPIC, only support %d\n", CONFIG_MAX_NUM_IOAPIC);
366                    } else {
367                        ioapic_paddrs[*num_ioapic] = ((acpi_madt_ioapic_t*)acpi_madt_header)->ioapic_addr;
368                        (*num_ioapic)++;
369                    }
370                    break;
371                case MADT_ISO:
372                    printf("ACPI: MADT_ISO bus=%d source=%d gsi=%d flags=0x%x\n",
373                           ((acpi_madt_iso_t*)acpi_madt_header)->bus,
374                           ((acpi_madt_iso_t*)acpi_madt_header)->source,
375                           ((acpi_madt_iso_t*)acpi_madt_header)->gsi,
376                           ((acpi_madt_iso_t*)acpi_madt_header)->flags);
377                    break;
378                default:
379                    break;
380                }
381                acpi_madt_header = (acpi_madt_header_t*)((char*)acpi_madt_header + acpi_madt_header->length);
382            }
383        }
384    }
385
386    printf("ACPI: %d CPU(s) detected\n", num_cpu);
387
388    return num_cpu;
389}
390
391BOOT_CODE bool_t
392acpi_fadt_scan(
393    acpi_rsdp_t* acpi_rsdp
394)
395{
396    unsigned int entries;
397    uint32_t            count;
398    acpi_fadt_t*        acpi_fadt;
399
400    acpi_rsdt_t* acpi_rsdt_mapped;
401    acpi_fadt_t* acpi_fadt_mapped;
402    acpi_rsdt_mapped = (acpi_rsdt_t*)acpi_table_init((acpi_rsdt_t*)(word_t)acpi_rsdp->rsdt_address, ACPI_RSDT);
403
404    assert(acpi_rsdt_mapped->header.length >= sizeof(acpi_header_t));
405    /* Divide by uint32_t explicitly as this is the size as mandated by the ACPI standard */
406    entries = (acpi_rsdt_mapped->header.length - sizeof(acpi_header_t)) / sizeof(uint32_t);
407    for (count = 0; count < entries; count++) {
408        acpi_fadt = (acpi_fadt_t*)(word_t)acpi_rsdt_mapped->entry[count];
409        acpi_fadt_mapped = (acpi_fadt_t*)acpi_table_init(acpi_fadt, ACPI_RSDT);
410
411        if (strncmp(acpi_str_fadt, acpi_fadt_mapped->header.signature, 4) == 0) {
412            printf("ACPI: FADT paddr=%p\n", acpi_fadt);
413            printf("ACPI: FADT vaddr=%p\n", acpi_fadt_mapped);
414            printf("ACPI: FADT flags=0x%x\n", acpi_fadt_mapped->flags);
415
416            if (config_set(CONFIG_USE_LOGICAL_IDS) &&
417                    acpi_fadt_mapped->flags & BIT(19)) {
418                printf("system requires apic physical mode\n");
419                return false;
420            }
421        }
422    }
423
424    return true;
425}
426
427BOOT_CODE void
428acpi_dmar_scan(
429    acpi_rsdp_t* acpi_rsdp,
430    paddr_t*     drhu_list,
431    uint32_t*    num_drhu,
432    uint32_t     max_drhu_list_len,
433    acpi_rmrr_list_t *rmrr_list
434)
435{
436    word_t i;
437    unsigned int entries;
438    uint32_t count;
439    uint32_t reg_basel, reg_baseh;
440    int rmrr_count;
441    dev_id_t dev_id;
442
443    acpi_dmar_t*          acpi_dmar;
444    acpi_dmar_header_t*   acpi_dmar_header;
445    acpi_dmar_rmrr_t*     acpi_dmar_rmrr;
446    acpi_dmar_devscope_t* acpi_dmar_devscope;
447
448    acpi_rsdt_t* acpi_rsdt_mapped;
449    acpi_dmar_t* acpi_dmar_mapped;
450
451    acpi_rsdt_mapped = (acpi_rsdt_t*)acpi_table_init((acpi_rsdt_t*)(word_t)acpi_rsdp->rsdt_address, ACPI_RSDT);
452
453    *num_drhu = 0;
454    rmrr_count = 0;
455
456    assert(acpi_rsdt_mapped->header.length >= sizeof(acpi_header_t));
457    entries = (acpi_rsdt_mapped->header.length - sizeof(acpi_header_t)) / sizeof(uint32_t);
458    for (count = 0; count < entries; count++) {
459        acpi_dmar = (acpi_dmar_t*)(word_t)acpi_rsdt_mapped->entry[count];
460        acpi_dmar_mapped = (acpi_dmar_t*)acpi_table_init(acpi_dmar, ACPI_RSDT);
461
462        if (strncmp(acpi_str_dmar, acpi_dmar_mapped->header.signature, 4) == 0) {
463            printf("ACPI: DMAR paddr=%p\n", acpi_dmar);
464            printf("ACPI: DMAR vaddr=%p\n", acpi_dmar_mapped);
465            printf("ACPI: IOMMU host address width: %d\n", acpi_dmar_mapped->host_addr_width + 1);
466            acpi_dmar_header = (acpi_dmar_header_t*)(acpi_dmar_mapped + 1);
467
468            while ((char*)acpi_dmar_header < (char*)acpi_dmar_mapped + acpi_dmar_mapped->header.length) {
469                switch (acpi_dmar_header->type) {
470
471                case DMAR_DRHD:
472                    if (*num_drhu == max_drhu_list_len) {
473                        printf("ACPI: too many IOMMUs, disabling IOMMU support\n");
474                        /* try to increase MAX_NUM_DRHU in config.h */
475                        *num_drhu = 0; /* report zero IOMMUs */
476                        return;
477                    }
478                    reg_basel = ((acpi_dmar_drhd_t*)acpi_dmar_header)->reg_base[0];
479                    reg_baseh = ((acpi_dmar_drhd_t*)acpi_dmar_header)->reg_base[1];
480                    /* check if value fits into uint32_t */
481                    if (reg_baseh != 0) {
482                        printf("ACPI: DMAR_DRHD reg_base exceeds 32 bit, disabling IOMMU support\n");
483                        /* try to make BIOS map it below 4G */
484                        *num_drhu = 0; /* report zero IOMMUs */
485                        return;
486                    }
487                    drhu_list[*num_drhu] = (paddr_t)reg_basel;
488                    (*num_drhu)++;
489                    break;
490
491                case DMAR_RMRR:
492                    /* loop through all device scopes of this RMRR */
493                    acpi_dmar_rmrr = (acpi_dmar_rmrr_t*)acpi_dmar_header;
494                    if (acpi_dmar_rmrr->reg_base[1] != 0 ||
495                            acpi_dmar_rmrr->reg_limit[1] != 0) {
496                        printf("ACPI: RMRR device above 4GiB, disabling IOMMU support\n");
497                        *num_drhu = 0;
498                        return ;
499                    }
500
501                    for (i = 0; i <= (acpi_dmar_header->length - sizeof(acpi_dmar_rmrr_t)) / sizeof(acpi_dmar_devscope_t); i++) {
502                        acpi_dmar_devscope = &acpi_dmar_rmrr->devscope_0 + i;
503
504                        if (acpi_dmar_devscope->type != 1) {
505                            /* FIXME - bugzilla bug 170 */
506                            printf("ACPI: RMRR device scope: non-PCI-Endpoint-Devices not supported yet, disabling IOMMU support\n");
507                            *num_drhu = 0; /* report zero IOMMUs */
508                            return;
509                        }
510
511                        if (acpi_dmar_devscope->length > sizeof(acpi_dmar_devscope_t)) {
512                            /* FIXME - bugzilla bug 170 */
513                            printf("ACPI: RMRR device scope: devices behind bridges not supported yet, disabling IOMMU support\n");
514                            *num_drhu = 0; /* report zero IOMMUs */
515                            return;
516                        }
517
518                        dev_id =
519                            get_dev_id(
520                                acpi_dmar_devscope->start_bus,
521                                acpi_dmar_devscope->path_0.dev,
522                                acpi_dmar_devscope->path_0.fun
523                            );
524
525                        if (rmrr_count == CONFIG_MAX_RMRR_ENTRIES) {
526                            printf("ACPI: Too many RMRR entries, disabling IOMMU support\n");
527                            *num_drhu = 0;
528                            return;
529                        }
530                        printf("\tACPI: registering RMRR entry for region for device: bus=0x%x dev=0x%x fun=0x%x\n",
531                               acpi_dmar_devscope->start_bus,
532                               acpi_dmar_devscope->path_0.dev,
533                               acpi_dmar_devscope->path_0.fun
534                              );
535
536                        rmrr_list->entries[rmrr_count].device = dev_id;
537                        rmrr_list->entries[rmrr_count].base = acpi_dmar_rmrr->reg_base[0];
538                        rmrr_list->entries[rmrr_count].limit = acpi_dmar_rmrr->reg_limit[0];
539                        rmrr_count++;
540                    }
541                    break;
542
543                case DMAR_ATSR:
544                    /* not implemented yet */
545                    break;
546
547                default:
548                    printf("ACPI: Unknown DMA remapping structure type: %x\n", acpi_dmar_header->type);
549                }
550                acpi_dmar_header = (acpi_dmar_header_t*)((char*)acpi_dmar_header + acpi_dmar_header->length);
551            }
552        }
553    }
554    rmrr_list->num = rmrr_count;
555    printf("ACPI: %d IOMMUs detected\n", *num_drhu);
556}
557