xref: /openbsd-src/usr.sbin/vmd/pci.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: pci.c,v 1.9 2016/09/01 16:40:06 mlarkin Exp $	*/
2 
3 /*
4  * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <dev/pci/pcireg.h>
22 #include <dev/pci/pcidevs.h>
23 #include <dev/pci/virtioreg.h>
24 #include <machine/vmmvar.h>
25 
26 #include <string.h>
27 #include "vmd.h"
28 #include "pci.h"
29 
30 struct pci pci;
31 
32 extern char *__progname;
33 
34 /* PIC IRQs, assigned to devices in order */
35 const uint8_t pci_pic_irqs[PCI_MAX_PIC_IRQS] = {3, 5, 9, 10, 11};
36 
37 /*
38  * pci_add_bar
39  *
40  * Adds a BAR for the PCI device 'id'. On access, 'barfn' will be
41  * called, and passed 'cookie' as an identifier.
42  *
43  * BARs are fixed size, meaning all I/O BARs requested have the
44  * same size and all MMIO BARs have the same size.
45  *
46  * Parameters:
47  *  id: PCI device to add the BAR to (local count, eg if id == 4,
48  *      this BAR is to be added to the VM's 5th PCI device)
49  *  type: type of the BAR to add (PCI_MAPREG_TYPE_xxx)
50  *  barfn: callback function invoked on BAR access
51  *  cookie: cookie passed to barfn on access
52  *
53  * Returns 0 if the BAR was added successfully, 1 otherwise.
54  */
55 int
56 pci_add_bar(uint8_t id, uint32_t type, void *barfn, void *cookie)
57 {
58 	uint8_t bar_reg_idx, bar_ct;
59 
60 	/* Check id */
61 	if (id >= pci.pci_dev_ct)
62 		return (1);
63 
64 	/* Can only add PCI_MAX_BARS BARs to any device */
65 	bar_ct = pci.pci_devices[id].pd_bar_ct;
66 	if (bar_ct >= PCI_MAX_BARS)
67 		return (1);
68 
69 	/* Compute BAR address and add */
70 	bar_reg_idx = (PCI_MAPREG_START + (bar_ct * 4)) / 4;
71 	if (type == PCI_MAPREG_TYPE_MEM) {
72 		if (pci.pci_next_mmio_bar >= VMM_PCI_MMIO_BAR_END)
73 			return (1);
74 
75 		pci.pci_devices[id].pd_cfg_space[bar_reg_idx] =
76 		    PCI_MAPREG_MEM_ADDR(pci.pci_next_mmio_bar);
77 		pci.pci_next_mmio_bar += VMM_PCI_MMIO_BAR_SIZE;
78 		pci.pci_devices[id].pd_barfunc[bar_ct] = barfn;
79 		pci.pci_devices[id].pd_bar_cookie[bar_ct] = cookie;
80 		pci.pci_devices[id].pd_bartype[bar_ct] = PCI_BAR_TYPE_MMIO;
81 		pci.pci_devices[id].pd_barsize[bar_ct] = VMM_PCI_MMIO_BAR_SIZE;
82 		pci.pci_devices[id].pd_bar_ct++;
83 	} else if (type == PCI_MAPREG_TYPE_IO) {
84 		if (pci.pci_next_io_bar >= VMM_PCI_IO_BAR_END)
85 			return (1);
86 
87 		pci.pci_devices[id].pd_cfg_space[bar_reg_idx] =
88 		     PCI_MAPREG_IO_ADDR(pci.pci_next_io_bar) |
89 		     PCI_MAPREG_TYPE_IO;
90 		pci.pci_next_io_bar += VMM_PCI_IO_BAR_SIZE;
91 		pci.pci_devices[id].pd_barfunc[bar_ct] = barfn;
92 		pci.pci_devices[id].pd_bar_cookie[bar_ct] = cookie;
93 		dprintf("%s: adding pci bar cookie for dev %d bar %d = %p",
94 		    __progname, id, bar_ct, cookie);
95 		pci.pci_devices[id].pd_bartype[bar_ct] = PCI_BAR_TYPE_IO;
96 		pci.pci_devices[id].pd_barsize[bar_ct] = VMM_PCI_IO_BAR_SIZE;
97 		pci.pci_devices[id].pd_bar_ct++;
98 	}
99 
100 	return (0);
101 }
102 
103 /*
104  * pci_add_device
105  *
106  * Adds a PCI device to the guest VM defined by the supplied parameters.
107  *
108  * Parameters:
109  *  id: the new PCI device ID (0 .. PCI_CONFIG_MAX_DEV)
110  *  vid: PCI VID of the new device
111  *  pid: PCI PID of the new device
112  *  class: PCI 'class' of the new device
113  *  subclass: PCI 'subclass' of the new device
114  *  subsys_vid: subsystem VID of the new device
115  *  subsys_id: subsystem ID of the new device
116  *  irq_needed: 1 if an IRQ should be assigned to this PCI device, 0 otherwise
117  *  csfunc: PCI config space callback function when the guest VM accesses
118  *      CS of this PCI device
119  *
120  * Return values:
121  *  0: the PCI device was added successfully. The PCI device ID is in 'id'.
122  *  1: the PCI device addition failed.
123  */
124 int
125 pci_add_device(uint8_t *id, uint16_t vid, uint16_t pid, uint8_t class,
126     uint8_t subclass, uint16_t subsys_vid, uint16_t subsys_id,
127     uint8_t irq_needed, pci_cs_fn_t csfunc)
128 {
129 	/* Exceeded max devices? */
130 	if (pci.pci_dev_ct >= PCI_CONFIG_MAX_DEV)
131 		return (1);
132 
133 	/* Exceeded max IRQs? */
134 	/* XXX we could share IRQs ... */
135 	if (pci.pci_next_pic_irq >= PCI_MAX_PIC_IRQS && irq_needed)
136 		return (1);
137 
138 	*id = pci.pci_dev_ct;
139 
140 	pci.pci_devices[*id].pd_vid = vid;
141 	pci.pci_devices[*id].pd_did = pid;
142 	pci.pci_devices[*id].pd_class = class;
143 	pci.pci_devices[*id].pd_subclass = subclass;
144 	pci.pci_devices[*id].pd_subsys_vid = subsys_vid;
145 	pci.pci_devices[*id].pd_subsys_id = subsys_id;
146 
147 	pci.pci_devices[*id].pd_csfunc = csfunc;
148 
149 	if (irq_needed) {
150 		pci.pci_devices[*id].pd_irq =
151 		    pci_pic_irqs[pci.pci_next_pic_irq];
152 		pci.pci_devices[*id].pd_int = 1;
153 		pci.pci_next_pic_irq++;
154 		dprintf("assigned irq %d to pci dev %d",
155 		    pci.pci_devices[*id].pd_irq, *id);
156 	}
157 
158 	pci.pci_dev_ct ++;
159 
160 	return (0);
161 }
162 
163 /*
164  * pci_init
165  *
166  * Initializes the PCI subsystem for the VM by adding a PCI host bridge
167  * as the first PCI device.
168  */
169 void
170 pci_init(void)
171 {
172 	uint8_t id;
173 
174 	memset(&pci, 0, sizeof(pci));
175 	pci.pci_next_mmio_bar = VMM_PCI_MMIO_BAR_BASE;
176 	pci.pci_next_io_bar = VMM_PCI_IO_BAR_BASE;
177 
178 	if (pci_add_device(&id, PCI_VENDOR_OPENBSD, PCI_PRODUCT_OPENBSD_PCHB,
179 	    PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_HOST,
180 	    PCI_VENDOR_OPENBSD, 0, 0, NULL)) {
181 		log_warnx("%s: can't add PCI host bridge", __progname);
182 		return;
183 	}
184 }
185 
186 void
187 pci_handle_address_reg(struct vm_run_params *vrp)
188 {
189 	union vm_exit *vei = vrp->vrp_exit;
190 
191 	/*
192 	 * vei_dir == VEI_DIR_OUT : out instruction
193 	 *
194 	 * The guest wrote to the address register.
195 	 */
196 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
197 		pci.pci_addr_reg = vei->vei.vei_data;
198 	} else {
199 		/*
200 		 * vei_dir == VEI_DIR_IN : in instruction
201 		 *
202 		 * The guest read the address register
203 		 */
204 		vei->vei.vei_data = pci.pci_addr_reg;
205 	}
206 }
207 
208 uint8_t
209 pci_handle_io(struct vm_run_params *vrp)
210 {
211 	int i, j, k, l;
212 	uint16_t reg, b_hi, b_lo;
213 	pci_iobar_fn_t fn;
214 	union vm_exit *vei = vrp->vrp_exit;
215 	uint8_t intr, dir;
216 
217 	k = -1;
218 	l = -1;
219 	reg = vei->vei.vei_port;
220 	dir = vei->vei.vei_dir;
221 	intr = 0xFF;
222 
223 	for (i = 0 ; i < pci.pci_dev_ct ; i++) {
224 		for (j = 0 ; j < pci.pci_devices[i].pd_bar_ct; j++) {
225 			b_lo = PCI_MAPREG_IO_ADDR(pci.pci_devices[i].pd_bar[j]);
226 			b_hi = b_lo + VMM_PCI_IO_BAR_SIZE;
227 			if (reg >= b_lo && reg < b_hi) {
228 				if (pci.pci_devices[i].pd_barfunc[j]) {
229 					k = j;
230 					l = i;
231 				}
232 			}
233 		}
234 	}
235 
236 	if (k >= 0 && l >= 0) {
237 		fn = (pci_iobar_fn_t)pci.pci_devices[l].pd_barfunc[k];
238 		if (fn(vei->vei.vei_dir, reg -
239 		    PCI_MAPREG_IO_ADDR(pci.pci_devices[l].pd_bar[k]),
240 		    &vei->vei.vei_data, &intr,
241 		    pci.pci_devices[l].pd_bar_cookie[k])) {
242 			log_warnx("%s: pci i/o access function failed",
243 			    __progname);
244 		}
245 	} else {
246 		log_warnx("%s: no pci i/o function for reg 0x%llx",
247 		    __progname, (uint64_t)reg);
248 		/* Reads from undefined ports return 0xFF */
249 		if (dir == 1)
250 			vei->vei.vei_data = 0xFFFFFFFF;
251 	}
252 
253 	if (intr != 0xFF) {
254 		intr = pci.pci_devices[l].pd_irq;
255 	}
256 
257 	return (intr);
258 }
259 
260 void
261 pci_handle_data_reg(struct vm_run_params *vrp)
262 {
263 	union vm_exit *vei = vrp->vrp_exit;
264 	uint8_t b, d, f, o, baridx;
265 	int ret;
266 	pci_cs_fn_t csfunc;
267 
268 	/* abort if the address register is wack */
269 	if (!(pci.pci_addr_reg & PCI_MODE1_ENABLE)) {
270 		/* if read, return FFs */
271 		if (vei->vei.vei_dir == VEI_DIR_IN)
272 			vei->vei.vei_data = 0xffffffff;
273 		log_warnx("invalid address register during pci read: "
274 		    "0x%llx", (uint64_t)pci.pci_addr_reg);
275 		return;
276 	}
277 
278 	b = (pci.pci_addr_reg >> 16) & 0xff;
279 	d = (pci.pci_addr_reg >> 11) & 0x1f;
280 	f = (pci.pci_addr_reg >> 8) & 0x7;
281 	o = (pci.pci_addr_reg & 0xfc);
282 
283 	csfunc = pci.pci_devices[d].pd_csfunc;
284 	if (csfunc != NULL) {
285 		ret = csfunc(vei->vei.vei_dir, (o / 4), &vei->vei.vei_data);
286 		if (ret)
287 			log_warnx("cfg space access function failed for "
288 			    "pci device %d", d);
289 		return;
290 	}
291 
292 	/* No config space function, fallback to default simple r/w impl. */
293 
294 	/*
295 	 * vei_dir == VEI_DIR_OUT : out instruction
296 	 *
297 	 * The guest wrote to the config space location denoted by the current
298 	 * value in the address register.
299 	 */
300 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
301 		if ((o >= 0x10 && o <= 0x24) &&
302 		    vei->vei.vei_data == 0xffffffff) {
303 			/*
304 			 * Compute BAR index:
305 			 * o = 0x10 -> baridx = 1
306 			 * o = 0x14 -> baridx = 2
307 			 * o = 0x18 -> baridx = 3
308 			 * o = 0x1c -> baridx = 4
309 			 * o = 0x20 -> baridx = 5
310 			 * o = 0x24 -> baridx = 6
311 			 */
312 			baridx = (o / 4) - 3;
313 			if (pci.pci_devices[d].pd_bar_ct >= baridx)
314 				vei->vei.vei_data = 0xfffff000;
315 			else
316 				vei->vei.vei_data = 0;
317 		}
318 		pci.pci_devices[d].pd_cfg_space[o / 4] = vei->vei.vei_data;
319 	} else {
320 		/*
321 		 * vei_dir == VEI_DIR_IN : in instruction
322 		 *
323 		 * The guest read from the config space location determined by
324 		 * the current value in the address register.
325 		 */
326 		vei->vei.vei_data = pci.pci_devices[d].pd_cfg_space[o / 4];
327 	}
328 }
329