xenpci.c revision 188685
1/*
2 * Copyright (c) 2008 Citrix Systems, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: user/dfr/xenhvm/7/sys/dev/xen/xenpci/xenpci.c 186767 2009-01-05 10:43:48Z dfr $");
29
30#include <sys/param.h>
31#include <sys/bus.h>
32#include <sys/kernel.h>
33#include <sys/malloc.h>
34#include <sys/module.h>
35#include <sys/proc.h>
36#include <sys/systm.h>
37#include <sys/time.h>
38
39#include <machine/bus.h>
40#include <machine/resource.h>
41#include <sys/rman.h>
42
43#include <machine/stdarg.h>
44#include <machine/xen/xen-os.h>
45#include <xen/features.h>
46#include <xen/hypervisor.h>
47#include <xen/gnttab.h>
48#include <xen/xen_intr.h>
49#include <xen/interface/memory.h>
50#include <xen/interface/hvm/params.h>
51
52#include <dev/pci/pcireg.h>
53#include <dev/pci/pcivar.h>
54
55#include <vm/vm.h>
56#include <vm/vm_extern.h>
57#include <vm/vm_kern.h>
58#include <vm/pmap.h>
59
60#include <dev/xen/xenpci/xenpcivar.h>
61
62/*
63 * These variables are used by the rest of the kernel to access the
64 * hypervisor.
65 */
66char *hypercall_stubs;
67shared_info_t *HYPERVISOR_shared_info;
68static vm_paddr_t shared_info_pa;
69
70/*
71 * This is used to find our platform device instance.
72 */
73static devclass_t xenpci_devclass;
74
75/*
76 * Return the CPUID base address for Xen functions.
77 */
78static uint32_t
79xenpci_cpuid_base(void)
80{
81	uint32_t base, regs[4];
82
83	for (base = 0x40000000; base < 0x40001000; base += 0x100) {
84		do_cpuid(base, regs);
85		if (!memcmp("XenVMMXenVMM", &regs[1], 12)
86		    && (regs[0] - base) >= 2)
87			return (base);
88	}
89	return (0);
90}
91
92/*
93 * Allocate and fill in the hypcall page.
94 */
95static int
96xenpci_init_hypercall_stubs(device_t dev, struct xenpci_softc * scp)
97{
98	uint32_t base, regs[4];
99	int i;
100
101	base = xenpci_cpuid_base();
102	if (!base) {
103		device_printf(dev, "Xen platform device but not Xen VMM\n");
104		return (EINVAL);
105	}
106
107	if (bootverbose) {
108		do_cpuid(base + 1, regs);
109		device_printf(dev, "Xen version %d.%d.\n",
110		    regs[0] >> 16, regs[0] & 0xffff);
111	}
112
113	/*
114	 * Find the hypercall pages.
115	 */
116	do_cpuid(base + 2, regs);
117
118	hypercall_stubs = malloc(regs[0] * PAGE_SIZE, M_TEMP, M_WAITOK);
119
120	for (i = 0; i < regs[0]; i++) {
121		wrmsr(regs[1], vtophys(hypercall_stubs + i * PAGE_SIZE) + i);
122	}
123
124	return (0);
125}
126
127/*
128 * After a resume, re-initialise the hypercall page.
129 */
130static void
131xenpci_resume_hypercall_stubs(device_t dev, struct xenpci_softc * scp)
132{
133	uint32_t base, regs[4];
134	int i;
135
136	base = xenpci_cpuid_base();
137
138	do_cpuid(base + 2, regs);
139	for (i = 0; i < regs[0]; i++) {
140		wrmsr(regs[1], vtophys(hypercall_stubs + i * PAGE_SIZE) + i);
141	}
142}
143
144/*
145 * Tell the hypervisor how to contact us for event channel callbacks.
146 */
147static void
148xenpci_set_callback(device_t dev)
149{
150	int irq;
151	uint64_t callback;
152	struct xen_hvm_param xhp;
153
154	irq = pci_get_irq(dev);
155	if (irq < 16) {
156		callback = irq;
157	} else {
158		callback = (pci_get_intpin(dev) - 1) & 3;
159		callback |= pci_get_slot(dev) << 11;
160		callback |= 1ull << 56;
161	}
162
163	xhp.domid = DOMID_SELF;
164	xhp.index = HVM_PARAM_CALLBACK_IRQ;
165	xhp.value = callback;
166	if (HYPERVISOR_hvm_op(HVMOP_set_param, &xhp))
167		panic("Can't set evtchn callback");
168}
169
170
171/*
172 * Deallocate anything allocated by xenpci_allocate_resources.
173 */
174static int
175xenpci_deallocate_resources(device_t dev)
176{
177	struct xenpci_softc *scp = device_get_softc(dev);
178
179	if (scp->res_irq != 0) {
180		bus_deactivate_resource(dev, SYS_RES_IRQ,
181			scp->rid_irq, scp->res_irq);
182		bus_release_resource(dev, SYS_RES_IRQ,
183			scp->rid_irq, scp->res_irq);
184		scp->res_irq = 0;
185	}
186	if (scp->res_memory != 0) {
187		bus_deactivate_resource(dev, SYS_RES_MEMORY,
188			scp->rid_memory, scp->res_memory);
189		bus_release_resource(dev, SYS_RES_MEMORY,
190			scp->rid_memory, scp->res_memory);
191		scp->res_memory = 0;
192	}
193
194	return (0);
195}
196
197/*
198 * Allocate irq and memory resources.
199 */
200static int
201xenpci_allocate_resources(device_t dev)
202{
203	struct xenpci_softc *scp = device_get_softc(dev);
204
205	scp->res_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ,
206			&scp->rid_irq, RF_SHAREABLE|RF_ACTIVE);
207	if (scp->res_irq == NULL)
208		goto errexit;
209
210	scp->rid_memory = PCIR_BAR(1);
211	scp->res_memory = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
212			&scp->rid_memory, RF_ACTIVE);
213	if (scp->res_memory == NULL)
214		goto errexit;
215	return (0);
216
217errexit:
218	/* Cleanup anything we may have assigned. */
219	xenpci_deallocate_resources(dev);
220	return (ENXIO); /* For want of a better idea. */
221}
222
223/*
224 * Allocate a physical address range from our mmio region.
225 */
226static int
227xenpci_alloc_space_int(struct xenpci_softc *scp, size_t sz,
228    vm_paddr_t *pa)
229{
230
231	if (scp->phys_next + sz > rman_get_end(scp->res_memory)) {
232		return (ENOMEM);
233	}
234
235	*pa = scp->phys_next;
236	scp->phys_next += sz;
237
238	return (0);
239}
240
241/*
242 * Allocate a physical address range from our mmio region.
243 */
244int
245xenpci_alloc_space(size_t sz, vm_paddr_t *pa)
246{
247	device_t dev = devclass_get_device(xenpci_devclass, 0);
248
249	if (dev) {
250		return (xenpci_alloc_space_int(device_get_softc(dev),
251			sz, pa));
252	} else {
253		return (ENOMEM);
254	}
255}
256
257/*
258 * Called very early in the resume sequence - reinitialise the various
259 * bits of Xen machinery including the hypercall page and the shared
260 * info page.
261 */
262void
263xenpci_resume()
264{
265	device_t dev = devclass_get_device(xenpci_devclass, 0);
266	struct xenpci_softc *scp = device_get_softc(dev);
267	struct xen_add_to_physmap xatp;
268
269	xenpci_resume_hypercall_stubs(dev, scp);
270
271	xatp.domid = DOMID_SELF;
272	xatp.idx = 0;
273	xatp.space = XENMAPSPACE_shared_info;
274	xatp.gpfn = shared_info_pa >> PAGE_SHIFT;
275	if (HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xatp))
276		panic("HYPERVISOR_memory_op failed");
277
278	pmap_kenter((vm_offset_t) HYPERVISOR_shared_info, shared_info_pa);
279
280	xenpci_set_callback(dev);
281
282	gnttab_resume();
283	irq_resume();
284}
285
286/*
287 * Probe - just check device ID.
288 */
289static int
290xenpci_probe(device_t dev)
291{
292
293	if (pci_get_devid(dev) != 0x00015853)
294		return (ENXIO);
295
296	device_set_desc(dev, "Xen Platform Device");
297	return (bus_generic_probe(dev));
298}
299
300/*
301 * Attach - find resources and talk to Xen.
302 */
303static int
304xenpci_attach(device_t dev)
305{
306        int error;
307	struct xenpci_softc *scp = device_get_softc(dev);
308	struct xen_add_to_physmap xatp;
309	vm_offset_t shared_va;
310
311	error = xenpci_allocate_resources(dev);
312	if (error)
313		goto errexit;
314
315	scp->phys_next = rman_get_start(scp->res_memory);
316
317	error = xenpci_init_hypercall_stubs(dev, scp);
318	if (error)
319		goto errexit;
320
321	setup_xen_features();
322
323	xenpci_alloc_space_int(scp, PAGE_SIZE, &shared_info_pa);
324
325	xatp.domid = DOMID_SELF;
326	xatp.idx = 0;
327	xatp.space = XENMAPSPACE_shared_info;
328	xatp.gpfn = shared_info_pa >> PAGE_SHIFT;
329	if (HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xatp))
330		panic("HYPERVISOR_memory_op failed");
331
332	shared_va = kmem_alloc_nofault(kernel_map, PAGE_SIZE);
333	pmap_kenter(shared_va, shared_info_pa);
334	HYPERVISOR_shared_info = (void *) shared_va;
335
336	/*
337	 * Hook the irq up to evtchn
338	 */
339	xenpci_irq_init(dev, scp);
340	xenpci_set_callback(dev);
341
342	return (bus_generic_attach(dev));
343
344errexit:
345	/*
346	 * Undo anything we may have done.
347	 */
348	xenpci_deallocate_resources(dev);
349        return (error);
350}
351
352/*
353 * Detach - reverse anything done by attach.
354 */
355static int
356xenpci_detach(device_t dev)
357{
358	struct xenpci_softc *scp = device_get_softc(dev);
359	device_t parent = device_get_parent(dev);
360
361	/*
362	 * Take our interrupt handler out of the list of handlers
363	 * that can handle this irq.
364	 */
365	if (scp->intr_cookie != NULL) {
366		if (BUS_TEARDOWN_INTR(parent, dev,
367			scp->res_irq, scp->intr_cookie) != 0)
368				printf("intr teardown failed.. continuing\n");
369		scp->intr_cookie = NULL;
370	}
371
372	/*
373	 * Deallocate any system resources we may have
374	 * allocated on behalf of this driver.
375	 */
376	return (xenpci_deallocate_resources(dev));
377}
378
379static device_method_t xenpci_methods[] = {
380	/* Device interface */
381	DEVMETHOD(device_probe,		xenpci_probe),
382	DEVMETHOD(device_attach,	xenpci_attach),
383	DEVMETHOD(device_detach,	xenpci_detach),
384	DEVMETHOD(device_suspend,	bus_generic_suspend),
385	DEVMETHOD(device_resume,	bus_generic_resume),
386
387	/* Bus interface */
388	DEVMETHOD(bus_add_child,	bus_generic_add_child),
389
390	{ 0, 0 }
391};
392
393static driver_t xenpci_driver = {
394	"xenpci",
395	xenpci_methods,
396	sizeof(struct xenpci_softc),
397};
398
399DRIVER_MODULE(xenpci, pci, xenpci_driver, xenpci_devclass, 0, 0);
400