1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2017 The FreeBSD Foundation
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. The name of the company nor the name of the author may be used to
15 * endorse or promote products derived from this software without specific
16 * prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/cdefs.h>
32 #include <sys/types.h>
33 #include <sys/systm.h>
34 #include <sys/bus.h>
35 #include <sys/kernel.h>
36 #include <sys/module.h>
37 #include <sys/mutex.h>
38 #include <sys/rman.h>
39 #include <sys/sysctl.h>
40 #include <sys/time.h>
41 #include <sys/timeet.h>
42 #include <sys/timetc.h>
43
44 #include <machine/bus.h>
45 #include <machine/machdep.h>
46 #include <machine/vmm.h>
47
48 #include <arm64/vmm/arm64.h>
49
50 #include <dev/vmm/vmm_vm.h>
51
52 #include "vgic.h"
53 #include "vtimer.h"
54
55 #define RES1 0xffffffffffffffffUL
56
57 #define timer_enabled(ctl) \
58 (!((ctl) & CNTP_CTL_IMASK) && ((ctl) & CNTP_CTL_ENABLE))
59
60 static uint32_t tmr_frq;
61
62 #define timer_condition_met(ctl) ((ctl) & CNTP_CTL_ISTATUS)
63
64 SYSCTL_DECL(_hw_vmm);
65 SYSCTL_NODE(_hw_vmm, OID_AUTO, vtimer, CTLFLAG_RW, NULL, NULL);
66
67 static bool allow_ecv_phys = false;
68 SYSCTL_BOOL(_hw_vmm_vtimer, OID_AUTO, allow_ecv_phys, CTLFLAG_RW,
69 &allow_ecv_phys, 0,
70 "Enable hardware access to the physical timer if FEAT_ECV_POFF is supported");
71
72 static void vtimer_schedule_irq(struct hypctx *hypctx, bool phys);
73
74 static int
vtimer_virtual_timer_intr(void * arg)75 vtimer_virtual_timer_intr(void *arg)
76 {
77 struct hypctx *hypctx;
78 uint64_t cntpct_el0;
79 uint32_t cntv_ctl;
80
81 hypctx = arm64_get_active_vcpu();
82 cntv_ctl = READ_SPECIALREG(cntv_ctl_el0);
83
84 if (!hypctx) {
85 /* vm_destroy() was called. */
86 eprintf("No active vcpu\n");
87 cntv_ctl = READ_SPECIALREG(cntv_ctl_el0);
88 goto out;
89 }
90 if (!timer_enabled(cntv_ctl)) {
91 eprintf("Timer not enabled\n");
92 goto out;
93 }
94 if (!timer_condition_met(cntv_ctl)) {
95 eprintf("Timer condition not met\n");
96 goto out;
97 }
98
99 cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
100 hypctx_read_sys_reg(hypctx, HOST_CNTVOFF_EL2);
101 if (hypctx->vtimer_cpu.virt_timer.cntx_cval_el0 < cntpct_el0)
102 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
103 GT_VIRT_IRQ, true);
104
105 cntv_ctl = hypctx->vtimer_cpu.virt_timer.cntx_ctl_el0;
106
107 out:
108 /*
109 * Disable the timer interrupt. This will prevent the interrupt from
110 * being reasserted as soon as we exit the handler and getting stuck
111 * in an infinite loop.
112 *
113 * This is safe to do because the guest disabled the timer, and then
114 * enables it as part of the interrupt handling routine.
115 */
116 cntv_ctl &= ~CNTP_CTL_ENABLE;
117 WRITE_SPECIALREG(cntv_ctl_el0, cntv_ctl);
118
119 return (FILTER_HANDLED);
120 }
121
122 int
vtimer_init(void)123 vtimer_init(void)
124 {
125 /*
126 * The guest *MUST* use the same timer frequency as the host. The
127 * register CNTFRQ_EL0 is accessible to the guest and a different value
128 * in the guest dts file might have unforseen consequences.
129 */
130 tmr_frq = READ_SPECIALREG(cntfrq_el0);
131
132 return (0);
133 }
134
135 void
vtimer_cpuinit(struct hypctx * hypctx)136 vtimer_cpuinit(struct hypctx *hypctx)
137 {
138 struct vtimer_cpu *vtimer_cpu;
139 bool ecv_poff;
140
141 vtimer_cpu = &hypctx->vtimer_cpu;
142 /*
143 * Configure physical timer interrupts for the VCPU.
144 *
145 * CNTP_CTL_IMASK: mask interrupts
146 * ~CNTP_CTL_ENABLE: disable the timer
147 */
148 vtimer_cpu->phys_timer.cntx_ctl_el0 = CNTP_CTL_IMASK & ~CNTP_CTL_ENABLE;
149
150 mtx_init(&vtimer_cpu->phys_timer.mtx, "vtimer phys callout mutex", NULL,
151 MTX_DEF);
152 callout_init_mtx(&vtimer_cpu->phys_timer.callout,
153 &vtimer_cpu->phys_timer.mtx, 0);
154 vtimer_cpu->phys_timer.irqid = GT_PHYS_NS_IRQ;
155
156 mtx_init(&vtimer_cpu->virt_timer.mtx, "vtimer virt callout mutex", NULL,
157 MTX_DEF);
158 callout_init_mtx(&vtimer_cpu->virt_timer.callout,
159 &vtimer_cpu->virt_timer.mtx, 0);
160 vtimer_cpu->virt_timer.irqid = GT_VIRT_IRQ;
161
162 ecv_poff = false;
163
164 if (allow_ecv_phys && (hypctx->hyp->feats & HYP_FEAT_ECV_POFF) != 0)
165 ecv_poff = true;
166
167 /*
168 * Configure the Counter-timer Hypervisor Control Register for the VM.
169 */
170 if (in_vhe()) {
171 /*
172 * CNTHCTL_E2H_EL0PCTEN: trap EL0 access to CNTP{CT,CTSS}_EL0
173 * CNTHCTL_E2H_EL0VCTEN: don't trap EL0 access to
174 * CNTV{CT,CTXX}_EL0
175 * CNTHCTL_E2H_EL0VTEN: don't trap EL0 access to
176 * CNTV_{CTL,CVAL,TVAL}_EL0
177 * CNTHCTL_E2H_EL0PTEN: trap EL0 access to
178 * CNTP_{CTL,CVAL,TVAL}_EL0
179 * CNTHCTL_E2H_EL1PCTEN: trap access to CNTPCT_EL0
180 * CNTHCTL_E2H_EL1PTEN: trap access to
181 * CNTP_{CTL,CVAL,TVAL}_EL0
182 * CNTHCTL_E2H_EL1VCTEN: don't trap EL0 access to
183 * CNTV{CT,CTSS}_EL0
184 * CNTHCTL_E2H_EL1PCEN: trap EL1 access to
185 * CNTP_{CTL,CVAL,TVAL}_EL0
186 *
187 * TODO: Don't trap when FEAT_ECV is present
188 */
189 hypctx_write_sys_reg(hypctx, HOST_CNTHCTL_EL2,
190 CNTHCTL_E2H_EL0VCTEN_NOTRAP |
191 CNTHCTL_E2H_EL0VTEN_NOTRAP);
192 if (ecv_poff) {
193 *hypctx_sys_reg(hypctx, HOST_CNTHCTL_EL2) |=
194 CNTHCTL_E2H_EL0PCTEN_NOTRAP |
195 CNTHCTL_E2H_EL0PTEN_NOTRAP |
196 CNTHCTL_E2H_EL1PCTEN_NOTRAP |
197 CNTHCTL_E2H_EL1PTEN_NOTRAP;
198 } else {
199 *hypctx_sys_reg(hypctx, HOST_CNTHCTL_EL2) |=
200 CNTHCTL_E2H_EL0PCTEN_TRAP |
201 CNTHCTL_E2H_EL0PTEN_TRAP |
202 CNTHCTL_E2H_EL1PCTEN_TRAP |
203 CNTHCTL_E2H_EL1PTEN_TRAP;
204 }
205 } else {
206 /*
207 * CNTHCTL_EL1PCEN: trap access to CNTP_{CTL, CVAL, TVAL}_EL0
208 * from EL1
209 * CNTHCTL_EL1PCTEN: trap access to CNTPCT_EL0
210 */
211 if (ecv_poff) {
212 hypctx_write_sys_reg(hypctx, HOST_CNTHCTL_EL2,
213 CNTHCTL_EL1PCTEN_NOTRAP |
214 CNTHCTL_EL1PCEN_NOTRAP);
215 } else {
216 hypctx_write_sys_reg(hypctx, HOST_CNTHCTL_EL2,
217 CNTHCTL_EL1PCTEN_TRAP |
218 CNTHCTL_EL1PCEN_TRAP);
219 }
220 }
221
222 if (ecv_poff)
223 *hypctx_sys_reg(hypctx, HOST_CNTHCTL_EL2) |= CNTHCTL_ECV_EN;
224
225 hypctx_write_sys_reg(hypctx, HOST_CNTVOFF_EL2, hypctx->hyp->cntvoff_el2);
226 }
227
228 void
vtimer_cpucleanup(struct hypctx * hypctx)229 vtimer_cpucleanup(struct hypctx *hypctx)
230 {
231 struct vtimer_cpu *vtimer_cpu;
232
233 vtimer_cpu = &hypctx->vtimer_cpu;
234 callout_drain(&vtimer_cpu->phys_timer.callout);
235 callout_drain(&vtimer_cpu->virt_timer.callout);
236 mtx_destroy(&vtimer_cpu->phys_timer.mtx);
237 mtx_destroy(&vtimer_cpu->virt_timer.mtx);
238 }
239
240 void
vtimer_vmcleanup(struct hyp * hyp)241 vtimer_vmcleanup(struct hyp *hyp)
242 {
243 struct hypctx *hypctx;
244 uint32_t cntv_ctl;
245
246 hypctx = arm64_get_active_vcpu();
247 if (!hypctx) {
248 /* The active VM was destroyed, stop the timer. */
249 cntv_ctl = READ_SPECIALREG(cntv_ctl_el0);
250 cntv_ctl &= ~CNTP_CTL_ENABLE;
251 WRITE_SPECIALREG(cntv_ctl_el0, cntv_ctl);
252 }
253 }
254
255 void
vtimer_cleanup(void)256 vtimer_cleanup(void)
257 {
258 }
259
260 static void
vtime_sync_timer(struct hypctx * hypctx,struct vtimer_timer * timer,uint64_t cntpct_el0)261 vtime_sync_timer(struct hypctx *hypctx, struct vtimer_timer *timer,
262 uint64_t cntpct_el0)
263 {
264 if (!timer_enabled(timer->cntx_ctl_el0)) {
265 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
266 timer->irqid, false);
267 } else if (timer->cntx_cval_el0 < cntpct_el0) {
268 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
269 timer->irqid, true);
270 } else {
271 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
272 timer->irqid, false);
273 vtimer_schedule_irq(hypctx, false);
274 }
275 }
276
277 void
vtimer_sync_hwstate(struct hypctx * hypctx)278 vtimer_sync_hwstate(struct hypctx *hypctx)
279 {
280 uint64_t cntpct_el0;
281
282 cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
283 hypctx_read_sys_reg(hypctx, HOST_CNTVOFF_EL2);
284 vtime_sync_timer(hypctx, &hypctx->vtimer_cpu.virt_timer, cntpct_el0);
285 /* If FEAT_ECV_POFF is in use then we need to sync the physical timer */
286 if ((hypctx_read_sys_reg(hypctx, HOST_CNTHCTL_EL2) & CNTHCTL_ECV_EN) != 0) {
287 vtime_sync_timer(hypctx, &hypctx->vtimer_cpu.phys_timer,
288 cntpct_el0);
289 }
290 }
291
292 static void
vtimer_inject_irq_callout_phys(void * context)293 vtimer_inject_irq_callout_phys(void *context)
294 {
295 struct hypctx *hypctx;
296
297 hypctx = context;
298 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
299 hypctx->vtimer_cpu.phys_timer.irqid, true);
300 }
301
302 static void
vtimer_inject_irq_callout_virt(void * context)303 vtimer_inject_irq_callout_virt(void *context)
304 {
305 struct hypctx *hypctx;
306
307 hypctx = context;
308 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
309 hypctx->vtimer_cpu.virt_timer.irqid, true);
310 }
311
312 static void
vtimer_schedule_irq(struct hypctx * hypctx,bool phys)313 vtimer_schedule_irq(struct hypctx *hypctx, bool phys)
314 {
315 sbintime_t time;
316 struct vtimer_timer *timer;
317 uint64_t cntpct_el0;
318 uint64_t diff;
319
320 if (phys)
321 timer = &hypctx->vtimer_cpu.phys_timer;
322 else
323 timer = &hypctx->vtimer_cpu.virt_timer;
324 cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
325 hypctx_read_sys_reg(hypctx, HOST_CNTVOFF_EL2);
326 if (timer->cntx_cval_el0 < cntpct_el0) {
327 /* Timer set in the past, trigger interrupt */
328 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
329 timer->irqid, true);
330 } else {
331 diff = timer->cntx_cval_el0 - cntpct_el0;
332 time = diff * SBT_1S / tmr_frq;
333 if (phys)
334 callout_reset_sbt(&timer->callout, time, 0,
335 vtimer_inject_irq_callout_phys, hypctx, 0);
336 else
337 callout_reset_sbt(&timer->callout, time, 0,
338 vtimer_inject_irq_callout_virt, hypctx, 0);
339 }
340 }
341
342 static void
vtimer_remove_irq(struct hypctx * hypctx,struct vcpu * vcpu)343 vtimer_remove_irq(struct hypctx *hypctx, struct vcpu *vcpu)
344 {
345 struct vtimer_cpu *vtimer_cpu;
346 struct vtimer_timer *timer;
347
348 vtimer_cpu = &hypctx->vtimer_cpu;
349 timer = &vtimer_cpu->phys_timer;
350
351 callout_drain(&timer->callout);
352 /*
353 * The interrupt needs to be deactivated here regardless of the callout
354 * function having been executed. The timer interrupt can be masked with
355 * the CNTP_CTL_EL0.IMASK bit instead of reading the IAR register.
356 * Masking the interrupt doesn't remove it from the list registers.
357 */
358 vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(vcpu), timer->irqid, false);
359 }
360
361 /*
362 * Timer emulation functions.
363 *
364 * The guest should use the virtual timer, however some software, e.g. u-boot,
365 * used the physical timer. Emulate this in software for the guest to use.
366 *
367 * Adjust for cntvoff_el2 so the physical and virtual timers are at similar
368 * times. This simplifies interrupt handling in the virtual timer as the
369 * adjustment will have already happened.
370 */
371
372 int
vtimer_phys_ctl_read(struct vcpu * vcpu,uint64_t * rval,void * arg)373 vtimer_phys_ctl_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
374 {
375 struct hypctx *hypctx;
376 struct vtimer_cpu *vtimer_cpu;
377 uint64_t cntpct_el0;
378
379 hypctx = vcpu_get_cookie(vcpu);
380 vtimer_cpu = &hypctx->vtimer_cpu;
381
382 cntpct_el0 = READ_SPECIALREG(cntpct_el0) - hypctx_read_sys_reg(hypctx, HOST_CNTVOFF_EL2);
383 if (vtimer_cpu->phys_timer.cntx_cval_el0 < cntpct_el0)
384 /* Timer condition met */
385 *rval = vtimer_cpu->phys_timer.cntx_ctl_el0 | CNTP_CTL_ISTATUS;
386 else
387 *rval = vtimer_cpu->phys_timer.cntx_ctl_el0 & ~CNTP_CTL_ISTATUS;
388
389 return (0);
390 }
391
392 int
vtimer_phys_ctl_write(struct vcpu * vcpu,uint64_t wval,void * arg)393 vtimer_phys_ctl_write(struct vcpu *vcpu, uint64_t wval, void *arg)
394 {
395 struct hypctx *hypctx;
396 struct vtimer_cpu *vtimer_cpu;
397 uint64_t ctl_el0;
398 bool timer_toggled_on;
399
400 hypctx = vcpu_get_cookie(vcpu);
401 vtimer_cpu = &hypctx->vtimer_cpu;
402
403 timer_toggled_on = false;
404 ctl_el0 = vtimer_cpu->phys_timer.cntx_ctl_el0;
405
406 if (!timer_enabled(ctl_el0) && timer_enabled(wval))
407 timer_toggled_on = true;
408 else if (timer_enabled(ctl_el0) && !timer_enabled(wval))
409 vtimer_remove_irq(hypctx, vcpu);
410
411 vtimer_cpu->phys_timer.cntx_ctl_el0 = wval;
412
413 if (timer_toggled_on)
414 vtimer_schedule_irq(hypctx, true);
415
416 return (0);
417 }
418
419 int
vtimer_phys_cnt_read(struct vcpu * vcpu,uint64_t * rval,void * arg)420 vtimer_phys_cnt_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
421 {
422 struct hypctx *hypctx;
423
424 hypctx = vcpu_get_cookie(vcpu);
425 *rval = READ_SPECIALREG(cntpct_el0) - hypctx_read_sys_reg(hypctx, HOST_CNTVOFF_EL2);
426 return (0);
427 }
428
429 int
vtimer_phys_cnt_write(struct vcpu * vcpu,uint64_t wval,void * arg)430 vtimer_phys_cnt_write(struct vcpu *vcpu, uint64_t wval, void *arg)
431 {
432 return (0);
433 }
434
435 int
vtimer_phys_cval_read(struct vcpu * vcpu,uint64_t * rval,void * arg)436 vtimer_phys_cval_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
437 {
438 struct hypctx *hypctx;
439 struct vtimer_cpu *vtimer_cpu;
440
441 hypctx = vcpu_get_cookie(vcpu);
442 vtimer_cpu = &hypctx->vtimer_cpu;
443
444 *rval = vtimer_cpu->phys_timer.cntx_cval_el0;
445
446 return (0);
447 }
448
449 int
vtimer_phys_cval_write(struct vcpu * vcpu,uint64_t wval,void * arg)450 vtimer_phys_cval_write(struct vcpu *vcpu, uint64_t wval, void *arg)
451 {
452 struct hypctx *hypctx;
453 struct vtimer_cpu *vtimer_cpu;
454
455 hypctx = vcpu_get_cookie(vcpu);
456 vtimer_cpu = &hypctx->vtimer_cpu;
457
458 vtimer_cpu->phys_timer.cntx_cval_el0 = wval;
459
460 vtimer_remove_irq(hypctx, vcpu);
461 if (timer_enabled(vtimer_cpu->phys_timer.cntx_ctl_el0)) {
462 vtimer_schedule_irq(hypctx, true);
463 }
464
465 return (0);
466 }
467
468 int
vtimer_phys_tval_read(struct vcpu * vcpu,uint64_t * rval,void * arg)469 vtimer_phys_tval_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
470 {
471 struct hypctx *hypctx;
472 struct vtimer_cpu *vtimer_cpu;
473 uint32_t cntpct_el0;
474
475 hypctx = vcpu_get_cookie(vcpu);
476 vtimer_cpu = &hypctx->vtimer_cpu;
477
478 if (!(vtimer_cpu->phys_timer.cntx_ctl_el0 & CNTP_CTL_ENABLE)) {
479 /*
480 * ARMv8 Architecture Manual, p. D7-2702: the result of reading
481 * TVAL when the timer is disabled is UNKNOWN. I have chosen to
482 * return the maximum value possible on 32 bits which means the
483 * timer will fire very far into the future.
484 */
485 *rval = (uint32_t)RES1;
486 } else {
487 cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
488 hypctx_read_sys_reg(hypctx, HOST_CNTVOFF_EL2);
489 *rval = vtimer_cpu->phys_timer.cntx_cval_el0 - cntpct_el0;
490 }
491
492 return (0);
493 }
494
495 int
vtimer_phys_tval_write(struct vcpu * vcpu,uint64_t wval,void * arg)496 vtimer_phys_tval_write(struct vcpu *vcpu, uint64_t wval, void *arg)
497 {
498 struct hypctx *hypctx;
499 struct vtimer_cpu *vtimer_cpu;
500 uint64_t cntpct_el0;
501
502 hypctx = vcpu_get_cookie(vcpu);
503 vtimer_cpu = &hypctx->vtimer_cpu;
504
505 cntpct_el0 = READ_SPECIALREG(cntpct_el0) - hypctx_read_sys_reg(hypctx, HOST_CNTVOFF_EL2);
506 vtimer_cpu->phys_timer.cntx_cval_el0 = (int32_t)wval + cntpct_el0;
507
508 vtimer_remove_irq(hypctx, vcpu);
509 if (timer_enabled(vtimer_cpu->phys_timer.cntx_ctl_el0)) {
510 vtimer_schedule_irq(hypctx, true);
511 }
512
513 return (0);
514 }
515
516 struct vtimer_softc {
517 struct resource *res;
518 void *ihl;
519 int rid;
520 };
521
522 static int
vtimer_probe(device_t dev)523 vtimer_probe(device_t dev)
524 {
525 device_set_desc(dev, "Virtual timer");
526 return (BUS_PROBE_DEFAULT);
527 }
528
529 static int
vtimer_attach(device_t dev)530 vtimer_attach(device_t dev)
531 {
532 struct vtimer_softc *sc;
533
534 sc = device_get_softc(dev);
535
536 sc->rid = 0;
537 sc->res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->rid, RF_ACTIVE);
538 if (sc->res == NULL)
539 return (ENXIO);
540
541 bus_setup_intr(dev, sc->res, INTR_TYPE_CLK, vtimer_virtual_timer_intr,
542 NULL, NULL, &sc->ihl);
543
544 return (0);
545 }
546
547 static device_method_t vtimer_methods[] = {
548 /* Device interface */
549 DEVMETHOD(device_probe, vtimer_probe),
550 DEVMETHOD(device_attach, vtimer_attach),
551
552 /* End */
553 DEVMETHOD_END
554 };
555
556 DEFINE_CLASS_0(vtimer, vtimer_driver, vtimer_methods,
557 sizeof(struct vtimer_softc));
558
559 DRIVER_MODULE(vtimer, generic_timer, vtimer_driver, 0, 0);
560