]> rtime.felk.cvut.cz Git - jailhouse.git/blob - hypervisor/pci.c
core: Moderate access to PCI capabilities
[jailhouse.git] / hypervisor / pci.c
1 /*
2  * Jailhouse, a Linux-based partitioning hypervisor
3  *
4  * Copyright (c) Siemens AG, 2014
5  *
6  * Authors:
7  *  Ivan Kolchin <ivan.kolchin@siemens.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2.  See
10  * the COPYING file in the top-level directory.
11  */
12
13 #include <jailhouse/acpi.h>
14 #include <jailhouse/mmio.h>
15 #include <jailhouse/pci.h>
16 #include <jailhouse/printk.h>
17 #include <jailhouse/utils.h>
18
19 struct acpi_mcfg_alloc {
20         u64 base_addr;
21         u16 segment_num;
22         u8 start_bus;
23         u8 end_bus;
24         u32 reserved;
25 } __attribute__((packed));
26
27 struct acpi_mcfg_table {
28         struct acpi_table_header header;
29         u8 reserved[8];
30         struct acpi_mcfg_alloc alloc_structs[];
31 } __attribute__((packed));
32
33 /* entry for PCI config space whitelist (granting access) */
34 struct pci_cfg_access {
35         u32 reg_num; /** Register number (4-byte aligned) */
36         u32 mask; /** Bit set: access allowed */
37 };
38
39 /* --- Whilelist for writing to PCI config space registers --- */
40 /* Type 1: Endpoints */
41 static const struct pci_cfg_access endpoint_write_access[] = {
42         { 0x04, 0xffffffff }, /* Command, Status */
43         { 0x0c, 0xff00ffff }, /* BIST, Latency Timer, Cacheline */
44         { 0x3c, 0x000000ff }, /* Int Line */
45 };
46 /* Type 2: Bridges */
47 static const struct pci_cfg_access bridge_write_access[] = {
48         { 0x04, 0xffffffff }, /* Command, Status */
49         { 0x0c, 0xff00ffff }, /* BIST, Latency Timer, Cacheline */
50         { 0x3c, 0xffff00ff }, /* Int Line, Bridge Control */
51 };
52
53 static void *pci_space;
54 static u64 pci_mmcfg_addr;
55 static u32 pci_mmcfg_size;
56
57 /**
58  * pci_get_assigned_device() - Look up device owned by a cell
59  * @cell:       Owning cell
60  * @bdf:        16-bit bus/device/function ID
61  *
62  * Return: Valid pointer - owns, NULL - doesn't own.
63  */
64 const struct jailhouse_pci_device *
65 pci_get_assigned_device(const struct cell *cell, u16 bdf)
66 {
67         const struct jailhouse_pci_device *device =
68                 jailhouse_cell_pci_devices(cell->config);
69         u32 n;
70
71         for (n = 0; n < cell->config->num_pci_devices; n++)
72                 if (((device[n].bus << 8) | device[n].devfn) == bdf)
73                         return &device[n];
74
75         return NULL;
76 }
77
78 /**
79  * pci_find_capability() - Look up capability at given config space address
80  * @cell:       Device owning cell
81  * @device:     The device to be accessed
82  * @address:    Config space access address
83  *
84  * Return: Corresponding capability structure or NULL if none found.
85  */
86 static const struct jailhouse_pci_capability *
87 pci_find_capability(const struct cell *cell,
88                     const struct jailhouse_pci_device *device, u16 address)
89 {
90         const struct jailhouse_pci_capability *cap =
91                 jailhouse_cell_pci_caps(cell->config) + device->caps_start;
92         u32 n;
93
94         for (n = 0; n < device->num_caps; n++, cap++)
95                 if (cap->start <= address && cap->start + cap->len > address)
96                         return cap;
97
98         return NULL;
99 }
100
101 /**
102  * pci_cfg_read_moderate() - Moderate config space read access
103  * @cell:       Request issuing cell
104  * @device:     The device to be accessed; if NULL, access will be emulated,
105  *              returning a value of -1
106  * @reg_num:    Register number (4-byte aligned)
107  * @bias:       Bias from register base address in bytes
108  * @size:       Access size (1, 2 or 4 bytes)
109  * @value:      Pointer to buffer to receive the emulated value if
110  *              PCI_ACCESS_EMULATE is returned
111  *
112  * Return: PCI_ACCESS_PERFORM or PCI_ACCESS_EMULATE.
113  */
114 enum pci_access
115 pci_cfg_read_moderate(const struct cell *cell,
116                       const struct jailhouse_pci_device *device, u8 reg_num,
117                       unsigned int reg_bias, unsigned int size, u32 *value)
118 {
119         const struct jailhouse_pci_capability *cap;
120
121         if (!device) {
122                 *value = -1;
123                 return PCI_ACCESS_EMULATE;
124         }
125
126         if (reg_num < PCI_CONFIG_HEADER_SIZE)
127                 return PCI_ACCESS_PERFORM;
128
129         cap = pci_find_capability(cell, device, reg_num + reg_bias);
130         if (!cap)
131                 return PCI_ACCESS_PERFORM;
132
133         // TODO: Emulate MSI/MSI-X etc.
134
135         return PCI_ACCESS_PERFORM;
136 }
137
138 /**
139  * pci_cfg_write_moderate() - Moderate config space write access
140  * @cell:       Request issuing cell
141  * @device:     The device to be accessed; if NULL, access will be rejected
142  * @reg_num:    Register number (4-byte aligned)
143  * @bias:       Bias from register base address in bytes
144  * @size:       Access size (1, 2 or 4 bytes)
145  * @value:      Pointer to value to be written, initialized with cell value,
146  *              set to the to-be-written hardware value if PCI_ACCESS_EMULATE
147  *              is returned
148  *
149  * Return: PCI_ACCESS_REJECT, PCI_ACCESS_PERFORM or PCI_ACCESS_EMULATE.
150  */
151 enum pci_access
152 pci_cfg_write_moderate(const struct cell *cell,
153                        const struct jailhouse_pci_device *device, u8 reg_num,
154                        unsigned int reg_bias, unsigned int size, u32 *value)
155 {
156         const struct jailhouse_pci_capability *cap;
157         /* initialize list to work around wrong compiler warning */
158         const struct pci_cfg_access *list = NULL;
159         unsigned int n, len = 0;
160
161         if (!device)
162                 return PCI_ACCESS_REJECT;
163
164         if (reg_num < PCI_CONFIG_HEADER_SIZE) {
165                 if (device->type == JAILHOUSE_PCI_TYPE_DEVICE) {
166                         list = endpoint_write_access;
167                         len = ARRAY_SIZE(endpoint_write_access);
168                 } else if (device->type == JAILHOUSE_PCI_TYPE_BRIDGE) {
169                         list = bridge_write_access;
170                         len = ARRAY_SIZE(bridge_write_access);
171                 }
172
173                 for (n = 0; n < len; n++) {
174                         if (list[n].reg_num == reg_num &&
175                             ((list[n].mask >> (reg_bias * 8)) &
176                              BYTE_MASK(size)) == BYTE_MASK(size))
177                                 return PCI_ACCESS_PERFORM;
178                 }
179                 return PCI_ACCESS_REJECT;
180         }
181
182         cap = pci_find_capability(cell, device, reg_num + reg_bias);
183         if (!cap || !(cap->flags & JAILHOUSE_PCICAPS_WRITE))
184                 return PCI_ACCESS_REJECT;
185
186         return PCI_ACCESS_PERFORM;
187 }
188
189 /**
190  * pci_init() - Initialization of PCI module
191  *
192  * Return: 0 - success, error code - if error.
193  */
194 int pci_init(void)
195 {
196         struct acpi_mcfg_table *mcfg;
197
198         mcfg = (struct acpi_mcfg_table *)acpi_find_table("MCFG", NULL);
199         if (!mcfg)
200                 return 0;
201
202         if (mcfg->header.length !=
203             sizeof(struct acpi_mcfg_table) + sizeof(struct acpi_mcfg_alloc))
204                 return -EIO;
205
206         pci_mmcfg_addr = mcfg->alloc_structs[0].base_addr;
207         pci_mmcfg_size = (mcfg->alloc_structs[0].end_bus -
208                           mcfg->alloc_structs[0].start_bus) * 256 * 4096;
209         pci_space = page_alloc(&remap_pool, pci_mmcfg_size / PAGE_SIZE);
210         if (!pci_space)
211                 return -ENOMEM;
212
213         return page_map_create(&hv_paging_structs,
214                                mcfg->alloc_structs[0].base_addr,
215                                pci_mmcfg_size, (unsigned long)pci_space,
216                                PAGE_DEFAULT_FLAGS | PAGE_FLAG_UNCACHED,
217                                PAGE_MAP_NON_COHERENT);
218 }
219
220 /**
221  * pci_mmio_access_handler() - Handler for MMIO-accesses to PCI config space
222  * @cell:       Request issuing cell
223  * @is_write:   True if write access
224  * @addr:       Address accessed
225  * @value:      Pointer to value for reading/writing
226  *
227  * Return: 1 if handled successfully, 0 if unhandled, -1 on access error
228  */
229 int pci_mmio_access_handler(const struct cell *cell, bool is_write,
230                             u64 addr, u32 *value)
231 {
232         u32 mmcfg_offset, val, reg_bias, reg_num;
233         const struct jailhouse_pci_device *device;
234         enum pci_access access;
235
236         if (!pci_space || addr < pci_mmcfg_addr ||
237             addr >= (pci_mmcfg_addr + pci_mmcfg_size - 4))
238                 return 0;
239
240         mmcfg_offset = addr - pci_mmcfg_addr;
241         reg_bias = mmcfg_offset % 4;
242         reg_num = mmcfg_offset & 0xfff;
243         device = pci_get_assigned_device(cell, mmcfg_offset >> 12);
244
245         if (is_write) {
246                 val = *value;
247                 access = pci_cfg_write_moderate(cell, device,
248                                                 reg_num - reg_bias, reg_bias,
249                                                 4, &val);
250                 if (access == PCI_ACCESS_REJECT)
251                         goto invalid_access;
252                 mmio_write32(pci_space + mmcfg_offset, val);
253         } else {
254                 access = pci_cfg_read_moderate(cell, device,
255                                                reg_num - reg_bias, reg_bias,
256                                                4, value);
257                 if (access == PCI_ACCESS_PERFORM)
258                         *value = mmio_read32(pci_space + mmcfg_offset);
259         }
260
261         return 1;
262
263 invalid_access:
264         panic_printk("FATAL: Invalid PCI MMCONFIG write, device %02x:%02x.%x, "
265                      "reg: %\n", mmcfg_offset >> 20, (mmcfg_offset >> 15) & 31,
266                      (mmcfg_offset >> 12) & 7, reg_num);
267         return -1;
268
269 }