1/**
2 * \file acpi_parse_dmar.c
3 * \brief ACPI DMA REMAPPING STRUCTURE
4 *
5 * Intel Virtualization Technology for Directed I/O Architecture Specification,
6 * Rev. 2.5 November 2017, Chapter 8 BIOS Considerations
7 * https://software.intel.com/sites/default/files/managed/c5/15/vt-directed-io-spec.pdf
8 */
9
10/*
11 * Copyright (c) 2017 ETH Zurich.
12 * All rights reserved.
13 *
14 * This file is distributed under the terms in the attached LICENSE file.
15 * If you do not find this file, copies can be found by writing to:
16 * ETH Zurich D-INFK, Universitaetsstrasse 6, CH-8092 Zurich. Attn: Systems Group.
17 */
18
19#include <barrelfish/barrelfish.h>
20
21#include <skb/skb.h>
22#include <octopus/getset.h>
23
24#include <hw_records.h>
25
26#include "acpi_debug.h"
27#include "acpi_shared.h"
28
29
30#define SKB_SCHEMA_DMAR \
31    "dmar(%" PRIu8 ", %" PRIu8 ")."
32
33#define SKB_SCHEMA_DMAR_HW_UNIT \
34    "dmar_drhd(%" PRIu32 ", %" PRIu8 ", %" PRIu16 ", %" PRIu64 ")."
35
36#define SKB_SCHEMA_IOMMU \
37    "iommu(%" PRIu32 ", %" PRIu32 ", %" PRIu8 ", %" PRIu16 ")."
38
39#define SKB_SCHEMA_DMAR_RESERVED_MEMORY \
40    "dmar_rmem(%" PRIu16 ", %" PRIu64 ", %" PRIu64 ")."
41
42#define SKB_SCHEMA_DMAR_ATSR \
43    "dmar_atsr(%" PRIu8 ", %" PRIu16 ")."
44
45#define SKB_SCHEMA_DMAR_RHSA \
46    "dmar_rhsa(%" PRIu64 ", %" PRIu32 ")."
47
48#define SKB_SCHEMA_DMAR_ANDD \
49     "dmar_andd(%" PRIu8 ", %s)."
50
51#define SKB_SCHEMA_DMAR_DEVSC \
52    "dmar_devsc(%" PRIu32 ", %" PRIu8 ", %" PRIu8 ", "\
53                "addr(%" PRIu16 ", %" PRIu8 ", %" PRIu8 ", %" PRIu8 "), "\
54                "%" PRIu8  ")."
55
56
57/*
58 * The Device Scope Structure is made up of Device Scope Entries. Each Device
59 * Scope Entry may be used to indicate a PCI endpoint device, a PCI sub-hierarchy,
60 * or devices such as I/OxAPICs or HPET (High Precision Event Timer).
61 *
62 * A PCI sub-hierarchy is defined as the collection of PCI controllers that are
63 * downstream to a specific PCI-PCI bridge. To identify a PCI sub-hierarchy,
64 * the Device Scope Entry needs to identify only the parent PCI-PCI bridge of
65 * the sub-hierarchy.
66 */
67static errval_t parse_device_scope(ACPI_DMAR_DEVICE_SCOPE *dsc, void *end,
68                                   uint16_t segment, enum AcpiDmarType type,
69                                   bool include_all_flag, uint32_t unit_idx)
70{
71    errval_t err;
72    ACPI_DMAR_PCI_PATH *pcip;
73
74    while((void *)dsc < end) {
75        if ((dsc->Length - sizeof(ACPI_DMAR_DEVICE_SCOPE))
76                > sizeof(ACPI_DMAR_PCI_PATH)) {
77            debug_printf("  > [dmar] [dscp] Too deep in the hierarchy, we curently "
78                         "only handle path length of 1.\n");
79            dsc = (ACPI_DMAR_DEVICE_SCOPE *) ((uint8_t *) dsc + dsc->Length);
80            continue;
81        }
82        assert((dsc->Length - sizeof(ACPI_DMAR_DEVICE_SCOPE)) == sizeof(ACPI_DMAR_PCI_PATH));
83        pcip = (ACPI_DMAR_PCI_PATH *)((uint8_t *)dsc + sizeof(ACPI_DMAR_DEVICE_SCOPE));
84
85        ACPI_DEBUG(SKB_SCHEMA_DMAR_DEVSC "\n", unit_idx, type, dsc->EntryType,
86                           segment, dsc->Bus, pcip->Device, pcip->Function,
87                           dsc->EnumerationId);
88
89        /* we put the raw entry into the SKB */
90        err = skb_add_fact(SKB_SCHEMA_DMAR_DEVSC, unit_idx, type, dsc->EntryType,
91                           segment, dsc->Bus, pcip->Device, pcip->Function,
92                           dsc->EnumerationId);
93        if (err_is_fail(err)) {
94            DEBUG_ERR(err, "Failed to insert fact into the SKB"
95                    SKB_SCHEMA_DMAR_DEVSC "\n", unit_idx, type, dsc->EntryType,
96                      segment, dsc->Bus, pcip->Device, pcip->Function,
97                      dsc->EnumerationId);
98        }
99
100        switch(dsc->EntryType) {
101            case ACPI_DMAR_SCOPE_TYPE_ENDPOINT:
102                /*
103                 * 0x01: PCI Endpoint Device - The device identified by the 'Path'
104                 * field is a PCI endpoint device. This type must not be used in
105                 * Device Scope of DRHD structures with INCLUDE_PCI_ALL flag Set.
106                 */
107                debug_printf("  > [dmar] [dscp] PCI Endpoint Device. Enumeration ID=%u,"
108                                     "Start Bus: %u.%u.%u Path Length: %u\n",
109                             dsc->EnumerationId, dsc->Bus, pcip->Device, pcip->Function,
110                             (dsc->Length - 6) >> 1);
111                assert(dsc->EnumerationId == 0);
112                assert(!(type == ACPI_DMAR_TYPE_HARDWARE_UNIT && include_all_flag));
113
114                break;
115            case ACPI_DMAR_SCOPE_TYPE_BRIDGE:
116                /*
117                 * 0x02: PCI Sub-hierarchy - The device identified by the 'Path'
118                 * field is a PCI-PCI bridge. In this case, the specified bridge
119                 * device and all its downstream devices are included in the scope.
120                 * This type must not be in Device Scope of DRHD structures with
121                 * INCLUDE_PCI_ALL flag Set.
122                 */
123                debug_printf("  > [dmar] [dscp] PCI-PCI Bridge. Enumeration ID=%u,"
124                                     "Start Bus: %u.%u.%u, Path Length: %u\n",
125                             dsc->EnumerationId, dsc->Bus, pcip->Device, pcip->Function,
126                             (dsc->Length - 6) >> 1);
127                assert(dsc->EnumerationId == 0);
128                assert(!(type == ACPI_DMAR_TYPE_HARDWARE_UNIT && include_all_flag));
129                break;
130            case ACPI_DMAR_SCOPE_TYPE_IOAPIC:
131                /*
132                 * 0x03: IOAPIC - The device identified by the 'Path' field is
133                 * an I/O APIC (or I/O SAPIC) device, enumerated through the
134                 * ACPI MADT I/O APIC (or I/O SAPIC) structure.
135                 *
136                 * Enumeration ID: the IOAPIC ID as provided in ACPI MADT
137                 */
138
139                debug_printf("  > [dmar] [dscp] IOAPIC. Enumeration ID=%u,"
140                                     "Start Bus: %u.%u.%u, Path Length: %u\n",
141                             dsc->EnumerationId, dsc->Bus,pcip->Device, pcip->Function,
142                             (dsc->Length - 6) >> 1);
143
144                break;
145            case ACPI_DMAR_SCOPE_TYPE_HPET:
146                /* 0x04: MSI_CAPABLE_HPET1 - The device identified by the 'Path'
147                 * field is an HPET Timer Block capable of generating MSI (Message
148                 * Signaled interrupts). HPET hardware is reported through ACPI
149                 * HPET structure.
150                 *
151                 * Enumeration ID: HPET Number corresponding to the APCI HPET block
152                 */
153                debug_printf("  > [dmar] [dscp] MSI HPET Device. Enumeration ID=%u,"
154                                     "Start Bus: %u.%u.%u, Path Length: %u\n",
155                             dsc->EnumerationId, dsc->Bus, pcip->Device, pcip->Function,
156                             (dsc->Length - 6) >> 1);
157                break;
158            case ACPI_DMAR_SCOPE_TYPE_NAMESPACE:
159                /*
160                 * 0x05: ACPI_NAMESPACE_DEVICE - The device identified by the
161                 * 'Path' field is an ACPI namespace enumerated device capable
162                 * of generating DMA requests.
163                 *
164                 * Enumeration ID is the ACPI device number as in  ANDD structure
165                 */
166                debug_printf("  > [dmar] [dscp] ACPI Namespace device. Enumeration ID=%u,"
167                                     "Start Bus: %u.%u.%u, Path Length: %u\n",
168                             dsc->EnumerationId, dsc->Bus, pcip->Device, pcip->Function,
169                             (dsc->Length - 6) >> 1);
170            default:
171                return ACPI_ERR_INVALID_HANDLE;
172        }
173
174       dsc = (ACPI_DMAR_DEVICE_SCOPE *) ((uint8_t *) dsc + dsc->Length);
175    }
176
177    return SYS_ERR_OK;
178}
179
180/**
181 * @brief parses the DMA remapping hardware unit structure
182 *
183 * @param drhd  pointer to the ACPI DRHD sub table
184 * @param end   pointer to the end of the sub table
185 * @param idx   hardware unit index
186 *
187 * @return SYS_ERR_OK on success, errval on failure
188 *
189 * Each remapping hardware unit is reported by such a structure. there is
190 * at least one structure per segment.
191 */
192static errval_t parse_hardware_unit(ACPI_DMAR_HARDWARE_UNIT *drhd, void *end,
193                                    uint32_t idx)
194{
195    errval_t err;
196
197    ACPI_DEBUG("[dmar] [drhd] " SKB_SCHEMA_DMAR_HW_UNIT "\n",
198                 idx, drhd->Flags, drhd->Segment, drhd->Address);
199
200    err = skb_add_fact(SKB_SCHEMA_DMAR_HW_UNIT, idx, drhd->Flags, drhd->Segment,
201                       drhd->Address);
202    if (err_is_fail(err)) {
203        DEBUG_ERR(err, "Failed to insert into SKB: " SKB_SCHEMA_DMAR_HW_UNIT "\n",
204                  idx, drhd->Flags, drhd->Segment, drhd->Address);
205    }
206
207    err = skb_add_fact(SKB_SCHEMA_IOMMU, HW_PCI_IOMMU_INTEL, idx,
208                       drhd->Flags & ACPI_DMAR_INCLUDE_ALL, drhd->Segment);
209    if (err_is_fail(err)) {
210        DEBUG_ERR(err, "Failed to insert into SKB: " SKB_SCHEMA_DMAR_HW_UNIT "\n",
211                  idx, drhd->Flags, drhd->Segment, drhd->Address);
212    }
213
214    // Remove from memory that can be used for PCI bars
215    err = skb_add_fact("fixed_memory(%"PRIu64", %"PRIu64")",
216                       drhd->Address, drhd->Address + BASE_PAGE_SIZE);
217    if (err_is_fail(err)) {
218        DEBUG_ERR(err, "Failed to insert into SKB: fixed memory region %"PRIx64" \n",
219                  drhd->Address);
220    }
221
222    debug_printf("[dmar] [drhd] set fixed_memory(%"PRIx64", %"PRIx64")\n",
223                 drhd->Address, drhd->Address + BASE_PAGE_SIZE);
224    /*
225     * If Set, this remapping hardware unit has under its scope all PCI
226     * compatible devices in the specified Segment, except devices reported
227     * under the scope of other remapping hardware units for the same Segment.
228     * If a DRHD structure with INCLUDE_PCI_ALL flag Set is reported for a
229     * Segment, it must be enumerated by BIOS after all other DRHD structures
230     * for the same Segment1. A DRHD structure with INCLUDE_PCI_ALL flag Set
231     * may use the 'DeviceScope' field to enumerate I/OxAPIC and HPET
232     * devices under its scope.
233     */
234    if (drhd->Flags & ACPI_DMAR_INCLUDE_ALL) {
235        debug_printf("[dmar] [drhd] ACPI_DMAR_INCLUDE_ALL set for segment %u\n",
236                     drhd->Segment);
237    }
238
239    /*
240     * The Device Scope structure contains zero or more Device Scope Entries
241     * that identify devices in the specified segment and under the scope of
242     * this remapping hardware unit.
243     */
244    void *sub = ((uint8_t *)drhd) + sizeof(ACPI_DMAR_HARDWARE_UNIT);
245    err = parse_device_scope(sub, end, drhd->Segment, ACPI_DMAR_TYPE_HARDWARE_UNIT,
246                             drhd->Flags & ACPI_DMAR_INCLUDE_ALL, idx);
247    if (err_is_fail(err)) {
248        DEBUG_ERR(err, "Failed to parse device scope: " SKB_SCHEMA_DMAR_HW_UNIT "\n",
249                  idx, drhd->Flags, drhd->Segment, drhd->Address);
250    }
251
252    debug_printf("[dmar] [drhd] set " HW_PCI_IOMMU_RECORD_FORMAT "\n",
253                 idx, HW_PCI_IOMMU_INTEL, drhd->Flags, drhd->Segment, drhd->Address);
254
255    return oct_mset(SET_SEQUENTIAL, HW_PCI_IOMMU_RECORD_FORMAT, idx,
256                    HW_PCI_IOMMU_INTEL, drhd->Flags, drhd->Segment,
257                    drhd->Address);
258}
259
260
261/**
262 * @brief parses the reserved memory region (RMRR) structures
263 *
264 * @param rmem  pointer ot the ACIP RMRR sub table
265 * @param end   pointer to the end of the table
266 *
267 * @return SYS_ERR_OK on success, errval on failure
268 *
269 * The RMRR regions are expected to be used for legacy usages
270 * (such as USB, UMA Graphics, etc.) requiring reserved memory.
271 *
272 * The BIOS reports each memory region through a RMRR structure and a list of
273 * devices that require access to the specified reserved memory region.
274 */
275static errval_t parse_reserved_memory(ACPI_DMAR_RESERVED_MEMORY *rmem, void *end)
276{
277    errval_t err;
278
279    ACPI_DEBUG("[dmar] [rmem] " SKB_SCHEMA_DMAR_RESERVED_MEMORY "\n",
280                 rmem->Segment, rmem->BaseAddress, rmem->EndAddress);
281
282    debug_printf("[dmar] [rmem] [0x%lx..0x%lx]\n", rmem->BaseAddress,
283                 rmem->EndAddress);
284
285    err = skb_add_fact(SKB_SCHEMA_DMAR_RESERVED_MEMORY, rmem->Segment,
286                       rmem->BaseAddress, rmem->EndAddress);
287    if (err_is_fail(err)) {
288        DEBUG_ERR(err, "Failed to insert into SKB: "
289                  SKB_SCHEMA_DMAR_RESERVED_MEMORY "\n",
290                  rmem->Segment, rmem->BaseAddress, rmem->EndAddress);
291    }
292
293    void *sub = ((uint8_t *)rmem) + sizeof(ACPI_DMAR_RESERVED_MEMORY);
294    return parse_device_scope(sub, end, rmem->Segment,
295                              ACPI_DMAR_TYPE_RESERVED_MEMORY, false, 0);
296}
297
298
299/**
300 * @brief parses the root-port address translation services (ATSR) structure
301 *
302 * @param atsr      pointer to the ACPI sub table
303 * @param end       end address of the table
304 *
305 * @return SYS_ERR_OK on success, errval on failure
306 *
307 * This structure is only for platforms supporting device TLBs. For each
308 * PCI segment there shall be one ATSR structure. The structure identifies
309 * which PCI Express root ports supporting ATS transactions
310 */
311static errval_t parse_root_ats_capabilities(ACPI_DMAR_ATSR *atsr, void *end)
312{
313    errval_t err;
314
315    ACPI_DEBUG("[dmar] [atsr] " SKB_SCHEMA_DMAR_ATSR "\n",
316                 atsr->Flags, atsr->Segment);
317
318    err = skb_add_fact(SKB_SCHEMA_DMAR_ATSR, atsr->Flags, atsr->Segment);
319    if (err_is_fail(err)) {
320        DEBUG_ERR(err, "Failed to insert into the SKB: " SKB_SCHEMA_DMAR_ATSR "\n",
321                  atsr->Flags, atsr->Segment);
322    }
323
324    /*
325     *  Bit 0: ALL_PORTS:
326     *      If Set, all PCI-Express Root Ports in the segment support ATS transactions.
327     *      If Clear, only root-ports indicated by the device scope fields support
328     *                ATS transactions
329     */
330    if (atsr->Flags & ACPI_DMAR_ALL_PORTS) {
331        debug_printf("[dmar] [atsr] ACPI_DMAR_ALL_PORTS flag is set. "
332                      "Omitting device scope structures\n");
333        return SYS_ERR_OK;
334    }
335
336    /*
337     * The Device Scope structure is described in Section 8.3.1. All Device Scope
338     * Entries in this structure must have a Device Scope Entry Type of
339     * 02h
340     */
341    void *sub = ((uint8_t *)atsr) + sizeof(ACPI_DMAR_ATSR);
342    return parse_device_scope(sub, end, atsr->Segment, ACPI_DMAR_TYPE_ROOT_ATS,
343                              false, 0);
344}
345
346
347/**
348 * @brief parses the Remapping Hardware Static Affinity Structure
349 *
350 * @param rhsa  pointer to the ACPI sub table
351 *
352 * @return SYS_ERR_OK on success, errval on failure
353 *
354 * On systems with NUMA nodes, it may be better peformance wise to allocate
355 * translation structures on the NUMA node close by the hardware unit.
356 * This RHSA structure provides proximity information similar to the SRAT
357 * table.
358 */
359static errval_t parse_hardware_resource_affinity(ACPI_DMAR_RHSA *rhsa)
360{
361    ACPI_DEBUG("[dmar] [rhsa] " SKB_SCHEMA_DMAR_RHSA "\n",
362                 rhsa->BaseAddress, rhsa->ProximityDomain);
363
364    return skb_add_fact(SKB_SCHEMA_DMAR_RHSA, rhsa->BaseAddress,
365                        rhsa->ProximityDomain);
366}
367
368/**
369 * @brief parses ACPI name-space device declarations
370 *
371 * @param andd  pointer to the ACPI namespace table
372 *
373 * @return SYS_ERR_OK on success, errval on failure
374 *
375 * NOTE: This is not yet implemented.
376 */
377static errval_t parse_namespace_device_declaration(ACPI_DMAR_ANDD *andd)
378{
379    ACPI_DEBUG("[dmar] [andd] NYI! " SKB_SCHEMA_DMAR_ANDD "\n",
380                 andd->DeviceNumber, andd->DeviceName);
381
382    return skb_add_fact(SKB_SCHEMA_DMAR_ANDD, andd->DeviceNumber,
383                        andd->DeviceName);
384}
385
386
387/**
388 * @brief  Parses the DMA Remapping Reporting Table (DMAR)
389 *
390 * @return SYS_ERR_OK on success, error value on failure
391 */
392errval_t acpi_parse_dmar(void)
393{
394    errval_t err;
395
396    ACPI_STATUS         as;
397    ACPI_TABLE_DMAR     *dmar;
398    ACPI_TABLE_HEADER   *ath;
399
400    /* Get the ACPI DMAR table (the DMAR) */
401    as = AcpiGetTable(ACPI_SIG_DMAR, 1, (ACPI_TABLE_HEADER **)&ath);
402
403    if(ACPI_FAILURE(as)) {
404        debug_printf("No DMAR found in ACPI! Cannot initialize IO MMUs.\n");
405
406        return oct_mset(SET_SEQUENTIAL, HW_PCI_IOMMU_RECORD_FORMAT, HW_PCI_IOMMU_INTEL, 0,
407                        0, 0);
408
409        return ACPI_ERR_OBJECT_NOT_FOUND;
410    }
411    else {
412        dmar = (ACPI_TABLE_DMAR*)ath;
413    }
414
415    /*
416     * Width: Number of bits that can be addressed by the platform in DMA.
417     *        the field is stored as W = Width + 1.
418     *
419     * Flags:
420     * Bit 0: INTR_REMAP
421     * Bit 1: X2APIC_OPT_OUT
422     * Bit 2: DMA_CTRL_PLATFORM_OPT_IN_FLAG
423     */
424    skb_add_fact(SKB_SCHEMA_DMAR, dmar->Width + 1, dmar->Flags);
425
426
427    debug_printf("DMAR Revision: %u, Size=%u, OEM=%s, HAW=%u, flags=%x\n",
428                 dmar->Header.Revision, dmar->Header.Length, dmar->Header.OemId,
429                 dmar->Width + 1, dmar->Flags);
430
431
432    void *p = (void *)dmar + sizeof(ACPI_TABLE_DMAR);
433    void *table_end = (void *)dmar + dmar->Header.Length;
434    uint32_t vtd_unit_idx = 0;
435    while(p < table_end) {
436        ACPI_DMAR_HEADER *sh = (ACPI_DMAR_HEADER *)p;
437        assert(sh->Length);
438        void *p_end = p + sh->Length;
439
440        switch (sh->Type) {
441            case ACPI_DMAR_TYPE_HARDWARE_UNIT:
442                err = parse_hardware_unit(p, p_end, vtd_unit_idx);
443                if (err_is_fail(err)) {
444                    DEBUG_ERR(err, "parsing hardware unit failed. Continuing...\n");
445                }
446                vtd_unit_idx++;
447                break;
448            case ACPI_DMAR_TYPE_RESERVED_MEMORY:
449                err = parse_reserved_memory(p, p_end);
450                if (err_is_fail(err)) {
451                    DEBUG_ERR(err, "reserved memory failed. Continuing...\n");
452                }
453                break;
454            case ACPI_DMAR_TYPE_ROOT_ATS:
455                err = parse_root_ats_capabilities(p, p_end);
456                if (err_is_fail(err)) {
457                    DEBUG_ERR(err, "parsing root ats caps failed. Continuing...\n");
458                }
459                break;
460            case ACPI_DMAR_TYPE_HARDWARE_AFFINITY:
461                err = parse_hardware_resource_affinity(p);
462                if (err_is_fail(err)) {
463                    DEBUG_ERR(err, "parsing resource affinity failed. "
464                                   "Continuing...\n");
465                }
466                break;
467            case ACPI_DMAR_TYPE_NAMESPACE:
468                err = parse_namespace_device_declaration(p);
469                if (err_is_fail(err)) {
470                    DEBUG_ERR(err, "parsing namespace declarations failed. "
471                                   "Continuing...\n");
472                }
473                break;
474            default:
475                USER_PANIC("Discovered unknown subtable %u. Consider updating"
476                           "ACPI. Skipping\n", sh->Type);
477        }
478        p = p_end;
479    }
480
481    return SYS_ERR_OK;
482}
483