xref: /netbsd-src/sys/dev/pci/vio9p.c (revision 53b02e147d4ed531c0d2a5ca9b3e8026ba3e99b5)
1 /*	$NetBSD: vio9p.c,v 1.4 2021/09/26 01:16:09 thorpej Exp $	*/
2 
3 /*
4  * Copyright (c) 2019 Internet Initiative Japan, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __KERNEL_RCSID(0, "$NetBSD: vio9p.c,v 1.4 2021/09/26 01:16:09 thorpej Exp $");
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/kernel.h>
34 #include <sys/bus.h>
35 #include <sys/conf.h>
36 #include <sys/condvar.h>
37 #include <sys/device.h>
38 #include <sys/mutex.h>
39 #include <sys/sysctl.h>
40 #include <sys/module.h>
41 #include <sys/syslog.h>
42 #include <sys/select.h>
43 #include <sys/kmem.h>
44 
45 #include <sys/file.h>
46 #include <sys/filedesc.h>
47 #include <sys/uio.h>
48 
49 #include <dev/pci/virtioreg.h>
50 #include <dev/pci/virtiovar.h>
51 
52 #include "ioconf.h"
53 
54 //#define VIO9P_DEBUG	1
55 //#define VIO9P_DUMP	1
56 #ifdef VIO9P_DEBUG
57 #define DLOG(fmt, args...) \
58 	do { log(LOG_DEBUG, "%s: " fmt "\n", __func__, ##args); } while (0)
59 #else
60 #define DLOG(fmt, args...) __nothing
61 #endif
62 
63 /* Configuration registers */
64 #define VIO9P_CONFIG_TAG_LEN	0 /* 16bit */
65 #define VIO9P_CONFIG_TAG	2
66 
67 #define VIO9P_FLAG_BITS		VIRTIO_COMMON_FLAG_BITS
68 
69 // Must be the same as P9P_DEFREQLEN of usr.sbin/puffs/mount_9p/ninepuffs.h
70 #define VIO9P_MAX_REQLEN	(16 * 1024)
71 #define VIO9P_SEGSIZE		PAGE_SIZE
72 #define VIO9P_N_SEGMENTS	(VIO9P_MAX_REQLEN / VIO9P_SEGSIZE)
73 
74 #define P9_MAX_TAG_LEN		16
75 
76 CTASSERT((PAGE_SIZE) == (VIRTIO_PAGE_SIZE)); /* XXX */
77 
78 struct vio9p_softc {
79 	device_t		sc_dev;
80 
81 	struct virtio_softc	*sc_virtio;
82 	struct virtqueue	sc_vq[1];
83 
84 	uint16_t		sc_taglen;
85 	uint8_t			sc_tag[P9_MAX_TAG_LEN + 1];
86 
87 	int			sc_flags;
88 #define VIO9P_INUSE		__BIT(0)
89 
90 	int			sc_state;
91 #define VIO9P_S_INIT		0
92 #define VIO9P_S_REQUESTING	1
93 #define VIO9P_S_REPLIED		2
94 #define VIO9P_S_CONSUMING	3
95 	kcondvar_t		sc_wait;
96 	struct selinfo		sc_sel;
97 	kmutex_t		sc_lock;
98 
99 	bus_dmamap_t		sc_dmamap_tx;
100 	bus_dmamap_t		sc_dmamap_rx;
101 	char			*sc_buf_tx;
102 	char			*sc_buf_rx;
103 	size_t			sc_buf_rx_len;
104 	off_t			sc_buf_rx_offset;
105 };
106 
107 /*
108  * Locking notes:
109  * - sc_state, sc_wait and sc_sel are protected by sc_lock
110  *
111  * The state machine (sc_state):
112  * - INIT       =(write from client)=> REQUESTING
113  * - REQUESTING =(reply from host)=>   REPLIED
114  * - REPLIED    =(read from client)=>  CONSUMING
115  * - CONSUMING  =(read completed(*))=> INIT
116  *
117  * (*) read may not finish by one read(2) request, then
118  *     the state remains CONSUMING.
119  */
120 
121 static int	vio9p_match(device_t, cfdata_t, void *);
122 static void	vio9p_attach(device_t, device_t, void *);
123 static void	vio9p_read_config(struct vio9p_softc *);
124 static int	vio9p_request_done(struct virtqueue *);
125 
126 static int	vio9p_read(struct file *, off_t *, struct uio *, kauth_cred_t,
127 		    int);
128 static int	vio9p_write(struct file *, off_t *, struct uio *,
129 		    kauth_cred_t, int);
130 static int	vio9p_ioctl(struct file *, u_long, void *);
131 static int	vio9p_close(struct file *);
132 static int	vio9p_kqfilter(struct file *, struct knote *);
133 
134 static const struct fileops vio9p_fileops = {
135 	.fo_name = "vio9p",
136 	.fo_read = vio9p_read,
137 	.fo_write = vio9p_write,
138 	.fo_ioctl = vio9p_ioctl,
139 	.fo_fcntl = fnullop_fcntl,
140 	.fo_poll = fnullop_poll,
141 	.fo_stat = fbadop_stat,
142 	.fo_close = vio9p_close,
143 	.fo_kqfilter = vio9p_kqfilter,
144 	.fo_restart = fnullop_restart,
145 };
146 
147 static dev_type_open(vio9p_dev_open);
148 
149 const struct cdevsw vio9p_cdevsw = {
150 	.d_open = vio9p_dev_open,
151 	.d_read = noread,
152 	.d_write = nowrite,
153 	.d_ioctl = noioctl,
154 	.d_stop = nostop,
155 	.d_tty = notty,
156 	.d_poll = nopoll,
157 	.d_mmap = nommap,
158 	.d_kqfilter = nokqfilter,
159 	.d_discard = nodiscard,
160 	.d_flag = D_OTHER | D_MPSAFE,
161 };
162 
163 static int
164 vio9p_dev_open(dev_t dev, int flag, int mode, struct lwp *l)
165 {
166 	struct vio9p_softc *sc;
167 	struct file *fp;
168 	int error, fd;
169 
170 	sc = device_lookup_private(&vio9p_cd, minor(dev));
171 	if (sc == NULL)
172 		return ENXIO;
173 
174 	/* FIXME TOCTOU */
175 	if (ISSET(sc->sc_flags, VIO9P_INUSE))
176 		return EBUSY;
177 
178 	/* falloc() will fill in the descriptor for us. */
179 	error = fd_allocfile(&fp, &fd);
180 	if (error != 0)
181 		return error;
182 
183 	sc->sc_flags |= VIO9P_INUSE;
184 
185 	return fd_clone(fp, fd, flag, &vio9p_fileops, sc);
186 }
187 
188 static int
189 vio9p_ioctl(struct file *fp, u_long cmd, void *addr)
190 {
191 	int error = 0;
192 
193 	switch (cmd) {
194 	case FIONBIO:
195 		break;
196 	default:
197 		error = EINVAL;
198 		break;
199 	}
200 
201 	return error;
202 }
203 
204 static int
205 vio9p_read(struct file *fp, off_t *offp, struct uio *uio,
206     kauth_cred_t cred, int flags)
207 {
208 	struct vio9p_softc *sc = fp->f_data;
209 	struct virtio_softc *vsc = sc->sc_virtio;
210 	struct virtqueue *vq = &sc->sc_vq[0];
211 	int error, slot, len;
212 
213 	DLOG("enter");
214 
215 	mutex_enter(&sc->sc_lock);
216 
217 	if (sc->sc_state == VIO9P_S_INIT) {
218 		DLOG("%s: not requested", device_xname(sc->sc_dev));
219 		error = EAGAIN;
220 		goto out;
221 	}
222 
223 	if (sc->sc_state == VIO9P_S_CONSUMING) {
224 		KASSERT(sc->sc_buf_rx_len > 0);
225 		/* We already have some remaining, consume it. */
226 		len = sc->sc_buf_rx_len - sc->sc_buf_rx_offset;
227 		goto consume;
228 	}
229 
230 #if 0
231 	if (uio->uio_resid != VIO9P_MAX_REQLEN)
232 		return EINVAL;
233 #else
234 	if (uio->uio_resid > VIO9P_MAX_REQLEN) {
235 		error = EINVAL;
236 		goto out;
237 	}
238 #endif
239 
240 	error = 0;
241 	while (sc->sc_state == VIO9P_S_REQUESTING) {
242 		error = cv_timedwait_sig(&sc->sc_wait, &sc->sc_lock, hz);
243 		if (error != 0)
244 			break;
245 	}
246 	if (sc->sc_state == VIO9P_S_REPLIED)
247 		sc->sc_state = VIO9P_S_CONSUMING;
248 
249 	if (error != 0)
250 		goto out;
251 
252 	error = virtio_dequeue(vsc, vq, &slot, &len);
253 	if (error != 0) {
254 		log(LOG_ERR, "%s: virtio_dequeue failed: %d\n",
255 		       device_xname(sc->sc_dev), error);
256 		goto out;
257 	}
258 	DLOG("len=%d", len);
259 	sc->sc_buf_rx_len = len;
260 	sc->sc_buf_rx_offset = 0;
261 	bus_dmamap_sync(virtio_dmat(vsc), sc->sc_dmamap_tx, 0, VIO9P_MAX_REQLEN,
262 	    BUS_DMASYNC_POSTWRITE);
263 	bus_dmamap_sync(virtio_dmat(vsc), sc->sc_dmamap_rx, 0, VIO9P_MAX_REQLEN,
264 	    BUS_DMASYNC_POSTREAD);
265 	virtio_dequeue_commit(vsc, vq, slot);
266 #ifdef VIO9P_DUMP
267 	int i;
268 	log(LOG_DEBUG, "%s: buf: ", __func__);
269 	for (i = 0; i < len; i++) {
270 		log(LOG_DEBUG, "%c", (char)sc->sc_buf_rx[i]);
271 	}
272 	log(LOG_DEBUG, "\n");
273 #endif
274 
275 consume:
276 	DLOG("uio_resid=%lu", uio->uio_resid);
277 	if (len < uio->uio_resid) {
278 		error = EINVAL;
279 		goto out;
280 	}
281 	len = uio->uio_resid;
282 	error = uiomove(sc->sc_buf_rx + sc->sc_buf_rx_offset, len, uio);
283 	if (error != 0)
284 		goto out;
285 
286 	sc->sc_buf_rx_offset += len;
287 	if (sc->sc_buf_rx_offset == sc->sc_buf_rx_len) {
288 		sc->sc_buf_rx_len = 0;
289 		sc->sc_buf_rx_offset = 0;
290 
291 		sc->sc_state = VIO9P_S_INIT;
292 		selnotify(&sc->sc_sel, 0, 1);
293 	}
294 
295 out:
296 	mutex_exit(&sc->sc_lock);
297 	return error;
298 }
299 
300 static int
301 vio9p_write(struct file *fp, off_t *offp, struct uio *uio,
302     kauth_cred_t cred, int flags)
303 {
304 	struct vio9p_softc *sc = fp->f_data;
305 	struct virtio_softc *vsc = sc->sc_virtio;
306 	struct virtqueue *vq = &sc->sc_vq[0];
307 	int error, slot;
308 	size_t len;
309 
310 	DLOG("enter");
311 
312 	mutex_enter(&sc->sc_lock);
313 
314 	if (sc->sc_state != VIO9P_S_INIT) {
315 		DLOG("already requesting");
316 		error = EAGAIN;
317 		goto out;
318 	}
319 
320 	if (uio->uio_resid == 0) {
321 		error = 0;
322 		goto out;
323 	}
324 
325 	if (uio->uio_resid > VIO9P_MAX_REQLEN) {
326 		error = EINVAL;
327 		goto out;
328 	}
329 
330 	len = uio->uio_resid;
331 	error = uiomove(sc->sc_buf_tx, len, uio);
332 	if (error != 0)
333 		goto out;
334 
335 	DLOG("len=%lu", len);
336 #ifdef VIO9P_DUMP
337 	int i;
338 	log(LOG_DEBUG, "%s: buf: ", __func__);
339 	for (i = 0; i < len; i++) {
340 		log(LOG_DEBUG, "%c", (char)sc->sc_buf_tx[i]);
341 	}
342 	log(LOG_DEBUG, "\n");
343 #endif
344 
345 	error = virtio_enqueue_prep(vsc, vq, &slot);
346 	if (error != 0) {
347 		log(LOG_ERR, "%s: virtio_enqueue_prep failed\n",
348 		       device_xname(sc->sc_dev));
349 		goto out;
350 	}
351 	DLOG("slot=%d", slot);
352 	error = virtio_enqueue_reserve(vsc, vq, slot,
353 	    sc->sc_dmamap_tx->dm_nsegs + sc->sc_dmamap_rx->dm_nsegs);
354 	if (error != 0) {
355 		log(LOG_ERR, "%s: virtio_enqueue_reserve failed\n",
356 		       device_xname(sc->sc_dev));
357 		goto out;
358 	}
359 
360 	/* Tx */
361 	bus_dmamap_sync(virtio_dmat(vsc), sc->sc_dmamap_tx, 0,
362 	    len, BUS_DMASYNC_PREWRITE);
363 	virtio_enqueue(vsc, vq, slot, sc->sc_dmamap_tx, true);
364 	/* Rx */
365 	bus_dmamap_sync(virtio_dmat(vsc), sc->sc_dmamap_rx, 0,
366 	    VIO9P_MAX_REQLEN, BUS_DMASYNC_PREREAD);
367 	virtio_enqueue(vsc, vq, slot, sc->sc_dmamap_rx, false);
368 	virtio_enqueue_commit(vsc, vq, slot, true);
369 
370 	sc->sc_state = VIO9P_S_REQUESTING;
371 out:
372 	mutex_exit(&sc->sc_lock);
373 	return error;
374 }
375 
376 static int
377 vio9p_close(struct file *fp)
378 {
379 	struct vio9p_softc *sc = fp->f_data;
380 
381 	KASSERT(ISSET(sc->sc_flags, VIO9P_INUSE));
382 	sc->sc_flags &= ~VIO9P_INUSE;
383 
384 	return 0;
385 }
386 
387 static void
388 filt_vio9p_detach(struct knote *kn)
389 {
390 	struct vio9p_softc *sc = kn->kn_hook;
391 
392 	mutex_enter(&sc->sc_lock);
393 	selremove_knote(&sc->sc_sel, kn);
394 	mutex_exit(&sc->sc_lock);
395 }
396 
397 static int
398 filt_vio9p_read(struct knote *kn, long hint)
399 {
400 	struct vio9p_softc *sc = kn->kn_hook;
401 	int rv;
402 
403 	kn->kn_data = sc->sc_buf_rx_len;
404 	/* XXX need sc_lock? */
405 	rv = (kn->kn_data > 0) || sc->sc_state != VIO9P_S_INIT;
406 
407 	return rv;
408 }
409 
410 static const struct filterops vio9p_read_filtops = {
411 	.f_flags = FILTEROP_ISFD,
412 	.f_attach = NULL,
413 	.f_detach = filt_vio9p_detach,
414 	.f_event = filt_vio9p_read,
415 };
416 
417 static int
418 filt_vio9p_write(struct knote *kn, long hint)
419 {
420 	struct vio9p_softc *sc = kn->kn_hook;
421 
422 	/* XXX need sc_lock? */
423 	return sc->sc_state == VIO9P_S_INIT;
424 }
425 
426 static const struct filterops vio9p_write_filtops = {
427 	.f_flags = FILTEROP_ISFD,
428 	.f_attach = NULL,
429 	.f_detach = filt_vio9p_detach,
430 	.f_event = filt_vio9p_write,
431 };
432 
433 static int
434 vio9p_kqfilter(struct file *fp, struct knote *kn)
435 {
436 	struct vio9p_softc *sc = fp->f_data;
437 
438 	switch (kn->kn_filter) {
439 	case EVFILT_READ:
440 		kn->kn_fop = &vio9p_read_filtops;
441 		break;
442 
443 	case EVFILT_WRITE:
444 		kn->kn_fop = &vio9p_write_filtops;
445 		break;
446 
447 	default:
448 		log(LOG_ERR, "%s: kn_filter=%u\n", __func__, kn->kn_filter);
449 		return EINVAL;
450 	}
451 
452 	kn->kn_hook = sc;
453 
454 	mutex_enter(&sc->sc_lock);
455 	selrecord_knote(&sc->sc_sel, kn);
456 	mutex_exit(&sc->sc_lock);
457 
458 	return 0;
459 }
460 
461 CFATTACH_DECL_NEW(vio9p, sizeof(struct vio9p_softc),
462     vio9p_match, vio9p_attach, NULL, NULL);
463 
464 static int
465 vio9p_match(device_t parent, cfdata_t match, void *aux)
466 {
467 	struct virtio_attach_args *va = aux;
468 
469 	if (va->sc_childdevid == VIRTIO_DEVICE_ID_9P)
470 		return 1;
471 
472 	return 0;
473 }
474 
475 static void
476 vio9p_attach(device_t parent, device_t self, void *aux)
477 {
478 	struct vio9p_softc *sc = device_private(self);
479 	struct virtio_softc *vsc = device_private(parent);
480 	uint64_t features;
481 	int error;
482 
483 	if (virtio_child(vsc) != NULL) {
484 		aprint_normal(": child already attached for %s; "
485 			      "something wrong...\n", device_xname(parent));
486 		return;
487 	}
488 
489 	sc->sc_dev = self;
490 	sc->sc_virtio = vsc;
491 
492 	virtio_child_attach_start(vsc, self, IPL_VM, NULL,
493 	    NULL, virtio_vq_intr,
494 	    VIRTIO_F_INTR_MPSAFE | VIRTIO_F_INTR_SOFTINT, 0,
495 	    VIO9P_FLAG_BITS);
496 
497 	features = virtio_features(vsc);
498 	if (features == 0)
499 		goto err_none;
500 
501 	error = virtio_alloc_vq(vsc, &sc->sc_vq[0], 0, VIO9P_MAX_REQLEN,
502 	    VIO9P_N_SEGMENTS * 2, "vio9p");
503 	if (error != 0)
504 		goto err_none;
505 
506 	sc->sc_vq[0].vq_done = vio9p_request_done;
507 
508 	virtio_child_attach_set_vqs(vsc, sc->sc_vq, 1);
509 
510 	sc->sc_buf_tx = kmem_alloc(VIO9P_MAX_REQLEN, KM_SLEEP);
511 	sc->sc_buf_rx = kmem_alloc(VIO9P_MAX_REQLEN, KM_SLEEP);
512 
513 	error = bus_dmamap_create(virtio_dmat(vsc), VIO9P_MAX_REQLEN,
514 	    VIO9P_N_SEGMENTS, VIO9P_SEGSIZE, 0, BUS_DMA_WAITOK, &sc->sc_dmamap_tx);
515 	if (error != 0) {
516 		aprint_error_dev(sc->sc_dev, "bus_dmamap_create failed: %d\n",
517 		    error);
518 		goto err_vq;
519 	}
520 	error = bus_dmamap_create(virtio_dmat(vsc), VIO9P_MAX_REQLEN,
521 	    VIO9P_N_SEGMENTS, VIO9P_SEGSIZE, 0, BUS_DMA_WAITOK, &sc->sc_dmamap_rx);
522 	if (error != 0) {
523 		aprint_error_dev(sc->sc_dev, "bus_dmamap_create failed: %d\n",
524 		    error);
525 		goto err_vq;
526 	}
527 
528 	error = bus_dmamap_load(virtio_dmat(vsc), sc->sc_dmamap_tx,
529 	    sc->sc_buf_tx, VIO9P_MAX_REQLEN, NULL, BUS_DMA_WAITOK | BUS_DMA_WRITE);
530 	if (error != 0) {
531 		aprint_error_dev(sc->sc_dev, "bus_dmamap_load failed: %d\n",
532 		    error);
533 		goto err_dmamap;
534 	}
535 	error = bus_dmamap_load(virtio_dmat(vsc), sc->sc_dmamap_rx,
536 	    sc->sc_buf_rx, VIO9P_MAX_REQLEN, NULL, BUS_DMA_WAITOK | BUS_DMA_READ);
537 	if (error != 0) {
538 		aprint_error_dev(sc->sc_dev, "bus_dmamap_load failed: %d\n",
539 		    error);
540 		goto err_dmamap;
541 	}
542 
543 	sc->sc_state = VIO9P_S_INIT;
544 	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
545 	cv_init(&sc->sc_wait, "vio9p");
546 
547 	vio9p_read_config(sc);
548 	aprint_normal_dev(self, "tagged as %s\n", sc->sc_tag);
549 
550 	error = virtio_child_attach_finish(vsc);
551 	if (error != 0)
552 		goto err_mutex;
553 
554 	return;
555 
556 err_mutex:
557 	cv_destroy(&sc->sc_wait);
558 	mutex_destroy(&sc->sc_lock);
559 err_dmamap:
560 	bus_dmamap_destroy(virtio_dmat(vsc), sc->sc_dmamap_tx);
561 	bus_dmamap_destroy(virtio_dmat(vsc), sc->sc_dmamap_rx);
562 err_vq:
563 	virtio_free_vq(vsc, &sc->sc_vq[0]);
564 err_none:
565 	virtio_child_attach_failed(vsc);
566 	return;
567 }
568 
569 static void
570 vio9p_read_config(struct vio9p_softc *sc)
571 {
572 	device_t dev = sc->sc_dev;
573 	uint8_t reg;
574 	int i;
575 
576 	/* these values are explicitly specified as little-endian */
577 	sc->sc_taglen = virtio_read_device_config_le_2(sc->sc_virtio,
578 		VIO9P_CONFIG_TAG_LEN);
579 
580 	if (sc->sc_taglen > P9_MAX_TAG_LEN) {
581 		aprint_error_dev(dev, "warning: tag is trimmed from %u to %u\n",
582 		    sc->sc_taglen, P9_MAX_TAG_LEN);
583 		sc->sc_taglen = P9_MAX_TAG_LEN;
584 	}
585 
586 	for (i = 0; i < sc->sc_taglen; i++) {
587 		reg = virtio_read_device_config_1(sc->sc_virtio,
588 		    VIO9P_CONFIG_TAG + i);
589 		sc->sc_tag[i] = reg;
590 	}
591 	sc->sc_tag[i] = '\0';
592 }
593 
594 static int
595 vio9p_request_done(struct virtqueue *vq)
596 {
597 	struct virtio_softc *vsc = vq->vq_owner;
598 	struct vio9p_softc *sc = device_private(virtio_child(vsc));
599 
600 	DLOG("enter");
601 
602 	mutex_enter(&sc->sc_lock);
603 	sc->sc_state = VIO9P_S_REPLIED;
604 	cv_broadcast(&sc->sc_wait);
605 	selnotify(&sc->sc_sel, 0, 1);
606 	mutex_exit(&sc->sc_lock);
607 
608 	return 1;
609 }
610 
611 MODULE(MODULE_CLASS_DRIVER, vio9p, "virtio");
612 
613 #ifdef _MODULE
614 #include "ioconf.c"
615 #endif
616 
617 static int
618 vio9p_modcmd(modcmd_t cmd, void *opaque)
619 {
620 #ifdef _MODULE
621 	devmajor_t bmajor = NODEVMAJOR, cmajor = NODEVMAJOR;
622 #endif
623 	int error = 0;
624 
625 #ifdef _MODULE
626 	switch (cmd) {
627 	case MODULE_CMD_INIT:
628 		error = config_init_component(cfdriver_ioconf_vio9p,
629 		    cfattach_ioconf_vio9p, cfdata_ioconf_vio9p);
630 		devsw_attach(vio9p_cd.cd_name, NULL, &bmajor,
631 		    &vio9p_cdevsw, &cmajor);
632 		break;
633 	case MODULE_CMD_FINI:
634 		error = config_fini_component(cfdriver_ioconf_vio9p,
635 		    cfattach_ioconf_vio9p, cfdata_ioconf_vio9p);
636 		break;
637 	default:
638 		error = ENOTTY;
639 		break;
640 	}
641 #endif
642 
643 	return error;
644 }
645