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