xref: /qemu/hw/acpi/viot.c (revision 68f14a87)
1 /*
2  * ACPI Virtual I/O Translation table implementation
3  *
4  * SPDX-License-Identifier: GPL-2.0-or-later
5  */
6 #include "qemu/osdep.h"
7 #include "hw/acpi/acpi.h"
8 #include "hw/acpi/aml-build.h"
9 #include "hw/acpi/viot.h"
10 #include "hw/pci/pci.h"
11 #include "hw/pci/pci_host.h"
12 
13 struct viot_pci_host_range {
14     int min_bus;
15     int max_bus;
16 };
17 
build_pci_host_range(GArray * table_data,int min_bus,int max_bus,uint16_t output_node)18 static void build_pci_host_range(GArray *table_data, int min_bus, int max_bus,
19                                  uint16_t output_node)
20 {
21     /* Type */
22     build_append_int_noprefix(table_data, 1 /* PCI range */, 1);
23     /* Reserved */
24     build_append_int_noprefix(table_data, 0, 1);
25     /* Length */
26     build_append_int_noprefix(table_data, 24, 2);
27     /* Endpoint start */
28     build_append_int_noprefix(table_data, PCI_BUILD_BDF(min_bus, 0), 4);
29     /* PCI Segment start */
30     build_append_int_noprefix(table_data, 0, 2);
31     /* PCI Segment end */
32     build_append_int_noprefix(table_data, 0, 2);
33     /* PCI BDF start */
34     build_append_int_noprefix(table_data, PCI_BUILD_BDF(min_bus, 0), 2);
35     /* PCI BDF end */
36     build_append_int_noprefix(table_data, PCI_BUILD_BDF(max_bus, 0xff), 2);
37     /* Output node */
38     build_append_int_noprefix(table_data, output_node, 2);
39     /* Reserved */
40     build_append_int_noprefix(table_data, 0, 6);
41 }
42 
43 /* Build PCI range for a given PCI host bridge */
enumerate_pci_host_bridges(Object * obj,void * opaque)44 static int enumerate_pci_host_bridges(Object *obj, void *opaque)
45 {
46     GArray *pci_host_ranges = opaque;
47 
48     if (object_dynamic_cast(obj, TYPE_PCI_HOST_BRIDGE)) {
49         PCIBus *bus = PCI_HOST_BRIDGE(obj)->bus;
50 
51         if (bus && !pci_bus_bypass_iommu(bus)) {
52             int min_bus, max_bus;
53 
54             pci_bus_range(bus, &min_bus, &max_bus);
55 
56             const struct viot_pci_host_range pci_host_range = {
57                 .min_bus = min_bus,
58                 .max_bus = max_bus,
59             };
60             g_array_append_val(pci_host_ranges, pci_host_range);
61         }
62     }
63 
64     return 0;
65 }
66 
pci_host_range_compare(gconstpointer a,gconstpointer b)67 static gint pci_host_range_compare(gconstpointer a, gconstpointer b)
68 {
69     struct viot_pci_host_range *range_a = (struct viot_pci_host_range *)a;
70     struct viot_pci_host_range *range_b = (struct viot_pci_host_range *)b;
71 
72     if (range_a->min_bus < range_b->min_bus) {
73         return -1;
74     } else if (range_a->min_bus > range_b->min_bus) {
75         return 1;
76     } else {
77         return 0;
78     }
79 }
80 
81 /*
82  * Generate a VIOT table with one PCI-based virtio-iommu that manages PCI
83  * endpoints.
84  *
85  * Defined in the ACPI Specification (Version TBD)
86  */
build_viot(MachineState * ms,GArray * table_data,BIOSLinker * linker,uint16_t virtio_iommu_bdf,const char * oem_id,const char * oem_table_id)87 void build_viot(MachineState *ms, GArray *table_data, BIOSLinker *linker,
88                 uint16_t virtio_iommu_bdf, const char *oem_id,
89                 const char *oem_table_id)
90 {
91     /* The virtio-iommu node follows the 48-bytes header */
92     int viommu_off = 48;
93     AcpiTable table = { .sig = "VIOT", .rev = 0,
94                         .oem_id = oem_id, .oem_table_id = oem_table_id };
95     GArray *pci_host_ranges =  g_array_new(false, true,
96                                            sizeof(struct viot_pci_host_range));
97     struct viot_pci_host_range *pci_host_range;
98     int i;
99 
100     /* Build the list of PCI ranges that this viommu manages */
101     object_child_foreach_recursive(OBJECT(ms), enumerate_pci_host_bridges,
102                                    pci_host_ranges);
103 
104     /* Sort the pci host ranges by min_bus */
105     g_array_sort(pci_host_ranges, pci_host_range_compare);
106 
107     /* ACPI table header */
108     acpi_table_begin(&table, table_data);
109     /* Node count */
110     build_append_int_noprefix(table_data, pci_host_ranges->len + 1, 2);
111     /* Node offset */
112     build_append_int_noprefix(table_data, viommu_off, 2);
113     /* Reserved */
114     build_append_int_noprefix(table_data, 0, 8);
115 
116     /* Virtio-iommu node */
117     /* Type */
118     build_append_int_noprefix(table_data, 3 /* virtio-pci IOMMU */, 1);
119     /* Reserved */
120     build_append_int_noprefix(table_data, 0, 1);
121     /* Length */
122     build_append_int_noprefix(table_data, 16, 2);
123     /* PCI Segment */
124     build_append_int_noprefix(table_data, 0, 2);
125     /* PCI BDF number */
126     build_append_int_noprefix(table_data, virtio_iommu_bdf, 2);
127     /* Reserved */
128     build_append_int_noprefix(table_data, 0, 8);
129 
130     /* PCI ranges found above */
131     for (i = 0; i < pci_host_ranges->len; i++) {
132         pci_host_range = &g_array_index(pci_host_ranges,
133                                         struct viot_pci_host_range, i);
134 
135         build_pci_host_range(table_data, pci_host_range->min_bus,
136                              pci_host_range->max_bus, viommu_off);
137     }
138 
139     g_array_free(pci_host_ranges, true);
140 
141     acpi_table_end(linker, &table);
142 }
143 
144