xref: /freebsd-13-stable/sys/dev/beri/virtio/network/if_vtbe.c (revision 3bc80996974a61a4223eae4c1ccd47b6ee32a48a)
1 /*-
2  * Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
3  * All rights reserved.
4  *
5  * This software was developed by SRI International and the University of
6  * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
7  * ("CTSRD"), as part of the DARPA CRASH research programme.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
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 /*
32  * BERI Virtio Networking Frontend
33  */
34 
35 #include <sys/cdefs.h>
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/bus.h>
39 #include <sys/kernel.h>
40 #include <sys/module.h>
41 #include <sys/malloc.h>
42 #include <sys/rman.h>
43 #include <sys/timeet.h>
44 #include <sys/timetc.h>
45 #include <sys/endian.h>
46 #include <sys/lock.h>
47 #include <sys/mbuf.h>
48 #include <sys/mutex.h>
49 #include <sys/socket.h>
50 #include <sys/sockio.h>
51 #include <sys/sysctl.h>
52 #include <sys/mdioctl.h>
53 #include <sys/conf.h>
54 #include <sys/stat.h>
55 #include <sys/uio.h>
56 
57 #include <dev/fdt/fdt_common.h>
58 #include <dev/ofw/openfirm.h>
59 #include <dev/ofw/ofw_bus.h>
60 #include <dev/ofw/ofw_bus_subr.h>
61 
62 #include <net/bpf.h>
63 #include <net/if.h>
64 #include <net/ethernet.h>
65 #include <net/if_dl.h>
66 #include <net/if_media.h>
67 #include <net/if_types.h>
68 #include <net/if_var.h>
69 #include <net/if_vlan_var.h>
70 
71 #include <netinet/in.h>
72 #include <netinet/udp.h>
73 #include <netinet/tcp.h>
74 
75 #include <machine/bus.h>
76 #include <machine/fdt.h>
77 #include <machine/cpu.h>
78 #include <machine/intr.h>
79 
80 #include <dev/beri/virtio/virtio.h>
81 #include <dev/beri/virtio/virtio_mmio_platform.h>
82 
83 #include <dev/altera/pio/pio.h>
84 
85 #include <dev/virtio/mmio/virtio_mmio.h>
86 #include <dev/virtio/network/virtio_net.h>
87 #include <dev/virtio/virtio_ids.h>
88 #include <dev/virtio/virtio_config.h>
89 #include <dev/virtio/virtio_ring.h>
90 
91 #include "pio_if.h"
92 
93 #define	DPRINTF(fmt, args...)	printf(fmt, ##args)
94 
95 #define	READ4(_sc, _reg) \
96 	bus_read_4((_sc)->res[0], _reg)
97 #define	WRITE4(_sc, _reg, _val) \
98 	bus_write_4((_sc)->res[0], _reg, _val)
99 
100 #define	VTBE_LOCK(sc)			mtx_lock(&(sc)->mtx)
101 #define	VTBE_UNLOCK(sc)			mtx_unlock(&(sc)->mtx)
102 #define	VTBE_ASSERT_LOCKED(sc)		mtx_assert(&(sc)->mtx, MA_OWNED);
103 #define	VTBE_ASSERT_UNLOCKED(sc)	mtx_assert(&(sc)->mtx, MA_NOTOWNED);
104 
105 /*
106  * Driver data and defines.
107  */
108 #define	DESC_COUNT	256
109 
110 struct vtbe_softc {
111 	struct resource		*res[2];
112 	bus_space_tag_t		bst;
113 	bus_space_handle_t	bsh;
114 	device_t		dev;
115 	struct ifnet		*ifp;
116 	int			if_flags;
117 	struct mtx		mtx;
118 	boolean_t		is_attached;
119 
120 	int			beri_mem_offset;
121 	device_t		pio_send;
122 	device_t		pio_recv;
123 	int			opened;
124 
125 	struct vqueue_info	vs_queues[2];
126 	int			vs_curq;
127 	int			hdrsize;
128 };
129 
130 static struct resource_spec vtbe_spec[] = {
131 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
132 	{ -1, 0 }
133 };
134 
135 static void vtbe_txfinish_locked(struct vtbe_softc *sc);
136 static void vtbe_rxfinish_locked(struct vtbe_softc *sc);
137 static void vtbe_stop_locked(struct vtbe_softc *sc);
138 static int pio_enable_irq(struct vtbe_softc *sc, int enable);
139 
140 static void
vtbe_txstart_locked(struct vtbe_softc * sc)141 vtbe_txstart_locked(struct vtbe_softc *sc)
142 {
143 	struct iovec iov[DESC_COUNT];
144 	struct virtio_net_hdr *vnh;
145 	struct vqueue_info *vq;
146 	struct iovec *tiov;
147 	struct ifnet *ifp;
148 	struct mbuf *m;
149 	struct uio uio;
150 	int enqueued;
151 	int iolen;
152 	int error;
153 	int reg;
154 	int len;
155 	int n;
156 
157 	VTBE_ASSERT_LOCKED(sc);
158 
159 	/* RX queue */
160 	vq = &sc->vs_queues[0];
161 	if (!vq_has_descs(vq)) {
162 		return;
163 	}
164 
165 	ifp = sc->ifp;
166 	if (ifp->if_drv_flags & IFF_DRV_OACTIVE) {
167 		return;
168 	}
169 
170 	enqueued = 0;
171 
172 	if (!vq_ring_ready(vq))
173 		return;
174 
175 	vq->vq_save_used = be16toh(vq->vq_used->idx);
176 
177 	for (;;) {
178 		if (!vq_has_descs(vq)) {
179 			ifp->if_drv_flags |= IFF_DRV_OACTIVE;
180 			break;
181 		}
182 
183 		IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
184 		if (m == NULL) {
185 			break;
186 		}
187 
188 		n = vq_getchain(sc->beri_mem_offset, vq, iov,
189 			DESC_COUNT, NULL);
190 		KASSERT(n == 2,
191 			("Unexpected amount of descriptors (%d)", n));
192 
193 		tiov = getcopy(iov, n);
194 		vnh = iov[0].iov_base;
195 		memset(vnh, 0, sc->hdrsize);
196 
197 		len = iov[1].iov_len;
198 		uio.uio_resid = len;
199 		uio.uio_iov = &tiov[1];
200 		uio.uio_segflg = UIO_SYSSPACE;
201 		uio.uio_iovcnt = 1;
202 		uio.uio_offset = 0;
203 		uio.uio_rw = UIO_READ;
204 
205 		error = m_mbuftouio(&uio, m, 0);
206 		if (error)
207 			panic("m_mbuftouio failed\n");
208 
209 		iolen = (len - uio.uio_resid + sc->hdrsize);
210 
211 		free(tiov, M_DEVBUF);
212 		vq_relchain(vq, iov, n, iolen);
213 
214 		if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
215 
216 		BPF_MTAP(ifp, m);
217 		m_freem(m);
218 
219 		++enqueued;
220 	}
221 
222 	if (enqueued != 0) {
223 		reg = htobe32(VIRTIO_MMIO_INT_VRING);
224 		WRITE4(sc, VIRTIO_MMIO_INTERRUPT_STATUS, reg);
225 
226 		PIO_SET(sc->pio_send, Q_INTR, 1);
227 	}
228 }
229 
230 static void
vtbe_txstart(struct ifnet * ifp)231 vtbe_txstart(struct ifnet *ifp)
232 {
233 	struct vtbe_softc *sc = ifp->if_softc;
234 
235 	VTBE_LOCK(sc);
236 	vtbe_txstart_locked(sc);
237 	VTBE_UNLOCK(sc);
238 }
239 
240 static void
vtbe_stop_locked(struct vtbe_softc * sc)241 vtbe_stop_locked(struct vtbe_softc *sc)
242 {
243 	struct ifnet *ifp;
244 
245 	VTBE_ASSERT_LOCKED(sc);
246 
247 	ifp = sc->ifp;
248 	ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
249 }
250 
251 static void
vtbe_init_locked(struct vtbe_softc * sc)252 vtbe_init_locked(struct vtbe_softc *sc)
253 {
254 	struct ifnet *ifp = sc->ifp;
255 
256 	VTBE_ASSERT_LOCKED(sc);
257 
258 	if (ifp->if_drv_flags & IFF_DRV_RUNNING)
259 		return;
260 
261 	ifp->if_drv_flags |= IFF_DRV_RUNNING;
262 }
263 
264 static void
vtbe_init(void * if_softc)265 vtbe_init(void *if_softc)
266 {
267 	struct vtbe_softc *sc = if_softc;
268 
269 	VTBE_LOCK(sc);
270 	vtbe_init_locked(sc);
271 	VTBE_UNLOCK(sc);
272 }
273 
274 static int
vtbe_ioctl(struct ifnet * ifp,u_long cmd,caddr_t data)275 vtbe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
276 {
277 	struct ifmediareq *ifmr;
278 	struct vtbe_softc *sc;
279 	struct ifreq *ifr;
280 	int mask, error;
281 
282 	sc = ifp->if_softc;
283 	ifr = (struct ifreq *)data;
284 
285 	error = 0;
286 	switch (cmd) {
287 	case SIOCSIFFLAGS:
288 		VTBE_LOCK(sc);
289 		if (ifp->if_flags & IFF_UP) {
290 			pio_enable_irq(sc, 1);
291 
292 			if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
293 				vtbe_init_locked(sc);
294 			}
295 		} else {
296 			pio_enable_irq(sc, 0);
297 
298 			if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
299 				vtbe_stop_locked(sc);
300 			}
301 		}
302 		sc->if_flags = ifp->if_flags;
303 		VTBE_UNLOCK(sc);
304 		break;
305 	case SIOCADDMULTI:
306 	case SIOCDELMULTI:
307 		break;
308 	case SIOCSIFMEDIA:
309 	case SIOCGIFMEDIA:
310 		ifmr = (struct ifmediareq *)data;
311 		ifmr->ifm_count = 1;
312 		ifmr->ifm_status = (IFM_AVALID | IFM_ACTIVE);
313 		ifmr->ifm_active = (IFM_ETHER | IFM_10G_T | IFM_FDX);
314 		ifmr->ifm_current = ifmr->ifm_active;
315 		break;
316 	case SIOCSIFCAP:
317 		mask = ifp->if_capenable ^ ifr->ifr_reqcap;
318 		if (mask & IFCAP_VLAN_MTU) {
319 			ifp->if_capenable ^= IFCAP_VLAN_MTU;
320 		}
321 		break;
322 
323 	case SIOCSIFADDR:
324 		pio_enable_irq(sc, 1);
325 	default:
326 		error = ether_ioctl(ifp, cmd, data);
327 		break;
328 	}
329 
330 	return (error);
331 }
332 
333 static void
vtbe_txfinish_locked(struct vtbe_softc * sc)334 vtbe_txfinish_locked(struct vtbe_softc *sc)
335 {
336 	struct ifnet *ifp;
337 
338 	VTBE_ASSERT_LOCKED(sc);
339 
340 	ifp = sc->ifp;
341 }
342 
343 static int
vq_init(struct vtbe_softc * sc)344 vq_init(struct vtbe_softc *sc)
345 {
346 	struct vqueue_info *vq;
347 	uint8_t *base;
348 	int size;
349 	int reg;
350 	int pfn;
351 
352 	vq = &sc->vs_queues[sc->vs_curq];
353 	vq->vq_qsize = DESC_COUNT;
354 
355 	reg = READ4(sc, VIRTIO_MMIO_QUEUE_PFN);
356 	pfn = be32toh(reg);
357 	vq->vq_pfn = pfn;
358 
359 	size = vring_size(vq->vq_qsize, VRING_ALIGN);
360 	base = paddr_map(sc->beri_mem_offset,
361 		(pfn << PAGE_SHIFT), size);
362 
363 	/* First pages are descriptors */
364 	vq->vq_desc = (struct vring_desc *)base;
365 	base += vq->vq_qsize * sizeof(struct vring_desc);
366 
367 	/* Then avail ring */
368 	vq->vq_avail = (struct vring_avail *)base;
369 	base += (2 + vq->vq_qsize + 1) * sizeof(uint16_t);
370 
371 	/* Then it's rounded up to the next page */
372 	base = (uint8_t *)roundup2((uintptr_t)base, VRING_ALIGN);
373 
374 	/* And the last pages are the used ring */
375 	vq->vq_used = (struct vring_used *)base;
376 
377 	/* Mark queue as allocated, and start at 0 when we use it. */
378 	vq->vq_flags = VQ_ALLOC;
379 	vq->vq_last_avail = 0;
380 
381 	return (0);
382 }
383 
384 static void
vtbe_proc_rx(struct vtbe_softc * sc,struct vqueue_info * vq)385 vtbe_proc_rx(struct vtbe_softc *sc, struct vqueue_info *vq)
386 {
387 	struct iovec iov[DESC_COUNT];
388 	struct iovec *tiov;
389 	struct ifnet *ifp;
390 	struct uio uio;
391 	struct mbuf *m;
392 	int iolen;
393 	int i;
394 	int n;
395 
396 	ifp = sc->ifp;
397 
398 	n = vq_getchain(sc->beri_mem_offset, vq, iov,
399 		DESC_COUNT, NULL);
400 
401 	KASSERT(n >= 1 && n <= DESC_COUNT,
402 		("wrong n %d", n));
403 
404 	tiov = getcopy(iov, n);
405 
406 	iolen = 0;
407 	for (i = 1; i < n; i++) {
408 		iolen += iov[i].iov_len;
409 	}
410 
411 	uio.uio_resid = iolen;
412 	uio.uio_iov = &tiov[1];
413 	uio.uio_segflg = UIO_SYSSPACE;
414 	uio.uio_iovcnt = (n - 1);
415 	uio.uio_rw = UIO_WRITE;
416 
417 	if ((m = m_uiotombuf(&uio, M_NOWAIT, 0, ETHER_ALIGN,
418 	    M_PKTHDR)) == NULL) {
419 		if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
420 		goto done;
421 	}
422 
423 	m->m_pkthdr.rcvif = ifp;
424 
425 	if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
426 
427 	CURVNET_SET(ifp->if_vnet);
428 	VTBE_UNLOCK(sc);
429 	(*ifp->if_input)(ifp, m);
430 	VTBE_LOCK(sc);
431 	CURVNET_RESTORE();
432 
433 done:
434 	free(tiov, M_DEVBUF);
435 	vq_relchain(vq, iov, n, iolen + sc->hdrsize);
436 }
437 
438 static void
vtbe_rxfinish_locked(struct vtbe_softc * sc)439 vtbe_rxfinish_locked(struct vtbe_softc *sc)
440 {
441 	struct vqueue_info *vq;
442 	int reg;
443 
444 	/* TX queue */
445 	vq = &sc->vs_queues[1];
446 	if (!vq_ring_ready(vq))
447 		return;
448 
449 	/* Process new descriptors */
450 	vq->vq_save_used = be16toh(vq->vq_used->idx);
451 
452 	while (vq_has_descs(vq)) {
453 		vtbe_proc_rx(sc, vq);
454 	}
455 
456 	/* Interrupt the other side */
457 	reg = htobe32(VIRTIO_MMIO_INT_VRING);
458 	WRITE4(sc, VIRTIO_MMIO_INTERRUPT_STATUS, reg);
459 
460 	PIO_SET(sc->pio_send, Q_INTR, 1);
461 }
462 
463 static void
vtbe_intr(void * arg)464 vtbe_intr(void *arg)
465 {
466 	struct vtbe_softc *sc;
467 	int pending;
468 	uint32_t reg;
469 
470 	sc = arg;
471 
472 	VTBE_LOCK(sc);
473 
474 	reg = PIO_READ(sc->pio_recv);
475 
476 	/* Ack */
477 	PIO_SET(sc->pio_recv, reg, 0);
478 
479 	pending = htobe32(reg);
480 	if (pending & Q_SEL) {
481 		reg = READ4(sc, VIRTIO_MMIO_QUEUE_SEL);
482 		sc->vs_curq = be32toh(reg);
483 	}
484 
485 	if (pending & Q_PFN) {
486 		vq_init(sc);
487 	}
488 
489 	if (pending & Q_NOTIFY) {
490 		/* beri rx / arm tx notify */
491 		vtbe_txfinish_locked(sc);
492 	}
493 
494 	if (pending & Q_NOTIFY1) {
495 		vtbe_rxfinish_locked(sc);
496 	}
497 
498 	VTBE_UNLOCK(sc);
499 }
500 
501 static int
vtbe_get_hwaddr(struct vtbe_softc * sc,uint8_t * hwaddr)502 vtbe_get_hwaddr(struct vtbe_softc *sc, uint8_t *hwaddr)
503 {
504 	int rnd;
505 
506 	/*
507 	 * Generate MAC address, use 'bsd' + random 24 low-order bits.
508 	 */
509 
510 	rnd = arc4random() & 0x00ffffff;
511 
512 	hwaddr[0] = 'b';
513 	hwaddr[1] = 's';
514 	hwaddr[2] = 'd';
515 	hwaddr[3] = rnd >> 16;
516 	hwaddr[4] = rnd >>  8;
517 	hwaddr[5] = rnd >>  0;
518 
519 	return (0);
520 }
521 
522 static int
pio_enable_irq(struct vtbe_softc * sc,int enable)523 pio_enable_irq(struct vtbe_softc *sc, int enable)
524 {
525 
526 	/*
527 	 * IRQ lines should be disabled while reprogram FPGA core.
528 	 */
529 
530 	if (enable) {
531 		if (sc->opened == 0) {
532 			sc->opened = 1;
533 			PIO_SETUP_IRQ(sc->pio_recv, vtbe_intr, sc);
534 		}
535 	} else {
536 		if (sc->opened == 1) {
537 			PIO_TEARDOWN_IRQ(sc->pio_recv);
538 			sc->opened = 0;
539 		}
540 	}
541 
542 	return (0);
543 }
544 
545 static int
vtbe_probe(device_t dev)546 vtbe_probe(device_t dev)
547 {
548 
549 	if (!ofw_bus_status_okay(dev))
550 		return (ENXIO);
551 
552 	if (!ofw_bus_is_compatible(dev, "sri-cambridge,beri-vtnet"))
553 		return (ENXIO);
554 
555 	device_set_desc(dev, "Virtio BERI Ethernet Controller");
556 	return (BUS_PROBE_DEFAULT);
557 }
558 
559 static int
vtbe_attach(device_t dev)560 vtbe_attach(device_t dev)
561 {
562 	uint8_t macaddr[ETHER_ADDR_LEN];
563 	struct vtbe_softc *sc;
564 	struct ifnet *ifp;
565 	int reg;
566 
567 	sc = device_get_softc(dev);
568 	sc->dev = dev;
569 
570 	sc->hdrsize = sizeof(struct virtio_net_hdr);
571 
572 	if (bus_alloc_resources(dev, vtbe_spec, sc->res)) {
573 		device_printf(dev, "could not allocate resources\n");
574 		return (ENXIO);
575 	}
576 
577 	/* Memory interface */
578 	sc->bst = rman_get_bustag(sc->res[0]);
579 	sc->bsh = rman_get_bushandle(sc->res[0]);
580 
581 	mtx_init(&sc->mtx, device_get_nameunit(sc->dev),
582 	    MTX_NETWORK_LOCK, MTX_DEF);
583 
584 	if (setup_offset(dev, &sc->beri_mem_offset) != 0)
585 		return (ENXIO);
586 	if (setup_pio(dev, "pio-send", &sc->pio_send) != 0)
587 		return (ENXIO);
588 	if (setup_pio(dev, "pio-recv", &sc->pio_recv) != 0)
589 		return (ENXIO);
590 
591 	/* Setup MMIO */
592 
593 	/* Specify that we provide network device */
594 	reg = htobe32(VIRTIO_ID_NETWORK);
595 	WRITE4(sc, VIRTIO_MMIO_DEVICE_ID, reg);
596 
597 	/* The number of desc we support */
598 	reg = htobe32(DESC_COUNT);
599 	WRITE4(sc, VIRTIO_MMIO_QUEUE_NUM_MAX, reg);
600 
601 	/* Our features */
602 	reg = htobe32(VIRTIO_NET_F_MAC |
603     			VIRTIO_F_NOTIFY_ON_EMPTY);
604 	WRITE4(sc, VIRTIO_MMIO_HOST_FEATURES, reg);
605 
606 	/* Get MAC */
607 	if (vtbe_get_hwaddr(sc, macaddr)) {
608 		device_printf(sc->dev, "can't get mac\n");
609 		return (ENXIO);
610 	}
611 
612 	/* Set up the ethernet interface. */
613 	sc->ifp = ifp = if_alloc(IFT_ETHER);
614 	ifp->if_baudrate = IF_Gbps(10);
615 	ifp->if_softc = sc;
616 	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
617 	ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX |
618 			 IFF_MULTICAST | IFF_PROMISC);
619 	ifp->if_capabilities = IFCAP_VLAN_MTU;
620 	ifp->if_capenable = ifp->if_capabilities;
621 	ifp->if_start = vtbe_txstart;
622 	ifp->if_ioctl = vtbe_ioctl;
623 	ifp->if_init = vtbe_init;
624 	IFQ_SET_MAXLEN(&ifp->if_snd, DESC_COUNT - 1);
625 	ifp->if_snd.ifq_drv_maxlen = DESC_COUNT - 1;
626 	IFQ_SET_READY(&ifp->if_snd);
627 	ifp->if_hdrlen = sizeof(struct ether_vlan_header);
628 
629 	/* All ready to run, attach the ethernet interface. */
630 	ether_ifattach(ifp, macaddr);
631 
632 	sc->is_attached = true;
633 
634 	return (0);
635 }
636 
637 static device_method_t vtbe_methods[] = {
638 	DEVMETHOD(device_probe,		vtbe_probe),
639 	DEVMETHOD(device_attach,	vtbe_attach),
640 	{ 0, 0 }
641 };
642 
643 static driver_t vtbe_driver = {
644 	"vtbe",
645 	vtbe_methods,
646 	sizeof(struct vtbe_softc),
647 };
648 
649 static devclass_t vtbe_devclass;
650 
651 DRIVER_MODULE(vtbe, simplebus, vtbe_driver, vtbe_devclass, 0, 0);
652 MODULE_DEPEND(vtbe, ether, 1, 1, 1);
653