1 /*
2 * Copyright (c) 2015 Julien Grall <julien.grall@citrix.com>
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/param.h>
28 #include <sys/module.h>
29 #include <sys/systm.h>
30 #include <sys/eventhandler.h>
31 #include <sys/consio.h>
32 #include <sys/priv.h>
33 #include <sys/proc.h>
34 #include <sys/uio.h>
35 #include <sys/tty.h>
36 #include <sys/systm.h>
37 #include <sys/taskqueue.h>
38 #include <sys/conf.h>
39 #include <sys/kernel.h>
40 #include <sys/bus.h>
41 #include <sys/cons.h>
42 #include <sys/kdb.h>
43 #include <sys/proc.h>
44 #include <sys/reboot.h>
45
46 #include <machine/stdarg.h>
47
48 #include <vm/vm.h>
49 #include <vm/pmap.h>
50
51 #include <xen/xen-os.h>
52 #include <xen/hypervisor.h>
53 #include <xen/xen_intr.h>
54 #include <contrib/xen/io/console.h>
55
56 #include "opt_ddb.h"
57 #include "opt_printf.h"
58
59 #ifdef DDB
60 #include <ddb/ddb.h>
61 #endif
62
63 static char driver_name[] = "xc";
64
65 struct xencons_priv;
66
67 typedef void xencons_early_init_t(struct xencons_priv *cons);
68 typedef int xencons_init_t(device_t dev, struct tty *tp,
69 driver_intr_t intr_handler);
70 typedef int xencons_read_t(struct xencons_priv *cons, char *buffer,
71 unsigned int size);
72 typedef int xencons_write_t(struct xencons_priv *cons, const char *buffer,
73 unsigned int size);
74
75 struct xencons_ops {
76 /*
77 * Called by the low-level driver during early boot.
78 * Only the minimal set up to get a console should be done here.
79 */
80 xencons_early_init_t *early_init;
81 /* Prepare the console to be fully use */
82 xencons_init_t *init;
83 /* Read/write helpers */
84 xencons_read_t *read;
85 xencons_write_t *write;
86 };
87
88 struct xencons_priv {
89 /* Mutex to protect the shared ring and the internal buffers */
90 struct mtx mtx;
91 /* Interrupt handler used for notify the backend */
92 xen_intr_handle_t intr_handle;
93 /* KDB internal state */
94 #ifdef KDB
95 int altbrk;
96 #endif
97 /* Status of the tty */
98 bool opened;
99 /* Callout used when the write buffer is full */
100 struct callout callout;
101
102 /* Internal buffers must be used with mtx locked */
103 #define WBUF_SIZE 4096
104 #define WBUF_MASK(_i) ((_i)&(WBUF_SIZE-1))
105 char wbuf[WBUF_SIZE];
106 unsigned int wc, wp; /* Consumer/producer wbuf */
107
108 #define RBUF_SIZE 1024
109 #define RBUF_MASK(_i) ((_i)&(RBUF_SIZE-1))
110 char rbuf[RBUF_SIZE];
111 unsigned int rc, rp; /* Consumer/producer rbuf */
112
113 /* Pointer to the console operations */
114 const struct xencons_ops *ops;
115
116 /*
117 * Ring specific fields
118 * XXX: make an union?
119 */
120 /* Event channel number for early notification (PV only) */
121 uint32_t evtchn;
122 /* Console shared page */
123 struct xencons_interface *intf;
124 };
125
126 /*
127 * Data for the main console
128 * Necessary to support low-level console driver
129 */
130 static struct xencons_priv main_cons;
131
132 #define XC_POLLTIME (hz/10)
133
134 /*----------------------------- Debug function ------------------------------*/
135 struct putchar_arg {
136 char *buf;
137 size_t size;
138 size_t n_next;
139 };
140
141 void __weak_symbol
xen_emergency_print(const char * str,size_t size)142 xen_emergency_print(const char *str, size_t size)
143 {
144 KASSERT((xen_domain()), ("call to xc_printf from non Xen guest"));
145 HYPERVISOR_console_write(str, size);
146 }
147
148 static void
putchar(int c,void * arg)149 putchar(int c, void *arg)
150 {
151 struct putchar_arg *pca;
152
153 pca = (struct putchar_arg *)arg;
154
155 if (pca->buf == NULL) {
156 /*
157 * We have no buffer, output directly to the
158 * console char by char.
159 */
160 xen_emergency_print((char *)&c, 1);
161 } else {
162 pca->buf[pca->n_next++] = c;
163 if ((pca->size == pca->n_next) || (c = '\0')) {
164 /* Flush the buffer */
165 xen_emergency_print(pca->buf, pca->n_next);
166 pca->n_next = 0;
167 }
168 }
169 }
170
171 void
xc_printf(const char * fmt,...)172 xc_printf(const char *fmt, ...)
173 {
174 va_list ap;
175 struct putchar_arg pca;
176 #ifdef PRINTF_BUFR_SIZE
177 char buf[PRINTF_BUFR_SIZE];
178
179 pca.buf = buf;
180 pca.size = sizeof(buf);
181 pca.n_next = 0;
182 #else
183 pca.buf = NULL;
184 pca.size = 0;
185 #endif
186
187 va_start(ap, fmt);
188 kvprintf(fmt, putchar, &pca, 10, ap);
189 va_end(ap);
190
191 #ifdef PRINTF_BUFR_SIZE
192 if (pca.n_next != 0)
193 xen_emergency_print(buf, pca.n_next);
194 #endif
195 }
196
197 /*---------------------- Helpers for the console lock -----------------------*/
198 /*
199 * The lock is not used when the kernel is panicing as it will never recover
200 * and we want to output no matter what it costs.
201 */
xencons_lock(struct xencons_priv * cons)202 static inline void xencons_lock(struct xencons_priv *cons)
203 {
204
205 if (!KERNEL_PANICKED())
206 mtx_lock_spin(&cons->mtx);
207
208 }
209
xencons_unlock(struct xencons_priv * cons)210 static inline void xencons_unlock(struct xencons_priv *cons)
211 {
212
213 if (!KERNEL_PANICKED())
214 mtx_unlock_spin(&cons->mtx);
215 }
216
217 #define xencons_lock_assert(cons) mtx_assert(&(cons)->mtx, MA_OWNED)
218
219 /*------------------ Helpers for the hypervisor console ---------------------*/
220 static void
xencons_early_init_hypervisor(struct xencons_priv * cons)221 xencons_early_init_hypervisor(struct xencons_priv *cons)
222 {
223 /*
224 * Nothing to setup for the low-level console when using
225 * the hypervisor console.
226 */
227 }
228
229 static int
xencons_init_hypervisor(device_t dev,struct tty * tp,driver_intr_t intr_handler)230 xencons_init_hypervisor(device_t dev, struct tty *tp,
231 driver_intr_t intr_handler)
232 {
233 struct xencons_priv *cons;
234 int err;
235
236 cons = tty_softc(tp);
237
238 err = xen_intr_bind_virq(dev, VIRQ_CONSOLE, 0, NULL,
239 intr_handler, tp, INTR_TYPE_TTY | INTR_MPSAFE, &cons->intr_handle);
240 if (err != 0)
241 device_printf(dev, "Can't register console interrupt\n");
242
243 return (err);
244 }
245
246 static int
xencons_write_hypervisor(struct xencons_priv * cons,const char * buffer,unsigned int size)247 xencons_write_hypervisor(struct xencons_priv *cons, const char *buffer,
248 unsigned int size)
249 {
250
251 HYPERVISOR_console_io(CONSOLEIO_write, size, buffer);
252
253 return (size);
254 }
255
256 static int
xencons_read_hypervisor(struct xencons_priv * cons,char * buffer,unsigned int size)257 xencons_read_hypervisor(struct xencons_priv *cons, char *buffer,
258 unsigned int size)
259 {
260
261 xencons_lock_assert(cons);
262
263 return (HYPERVISOR_console_io(CONSOLEIO_read, size, buffer));
264 }
265
266 static const struct xencons_ops xencons_hypervisor_ops = {
267 .early_init = xencons_early_init_hypervisor,
268 .init = xencons_init_hypervisor,
269 .read = xencons_read_hypervisor,
270 .write = xencons_write_hypervisor,
271 };
272
273 /*------------------ Helpers for the ring console ---------------------------*/
274 static void
xencons_early_init_ring(struct xencons_priv * cons)275 xencons_early_init_ring(struct xencons_priv *cons)
276 {
277 cons->intf = pmap_mapdev_attr(ptoa(xen_get_console_mfn()), PAGE_SIZE,
278 VM_MEMATTR_XEN);
279 cons->evtchn = xen_get_console_evtchn();
280 }
281
282 static int
xencons_init_ring(device_t dev,struct tty * tp,driver_intr_t intr_handler)283 xencons_init_ring(device_t dev, struct tty *tp, driver_intr_t intr_handler)
284 {
285 struct xencons_priv *cons;
286 int err;
287
288 cons = tty_softc(tp);
289
290 if (cons->evtchn == 0)
291 return (ENODEV);
292
293 err = xen_intr_bind_local_port(dev, cons->evtchn, NULL,
294 intr_handler, tp, INTR_TYPE_TTY | INTR_MPSAFE, &cons->intr_handle);
295 if (err != 0)
296 return (err);
297
298 return (0);
299 }
300
301 static void
xencons_notify_ring(struct xencons_priv * cons)302 xencons_notify_ring(struct xencons_priv *cons)
303 {
304 /*
305 * The console may be used before the ring interrupt is properly
306 * initialized.
307 * If so, fallback to directly use the event channel hypercall.
308 */
309 if (__predict_true(cons->intr_handle != NULL))
310 xen_intr_signal(cons->intr_handle);
311 else {
312 struct evtchn_send send = {
313 .port = cons->evtchn
314 };
315
316 HYPERVISOR_event_channel_op(EVTCHNOP_send, &send);
317 }
318 }
319
320 static int
xencons_write_ring(struct xencons_priv * cons,const char * buffer,unsigned int size)321 xencons_write_ring(struct xencons_priv *cons, const char *buffer,
322 unsigned int size)
323 {
324 struct xencons_interface *intf;
325 XENCONS_RING_IDX wcons, wprod;
326 int sent;
327
328 intf = cons->intf;
329
330 xencons_lock_assert(cons);
331
332 wcons = intf->out_cons;
333 wprod = intf->out_prod;
334
335 mb();
336 KASSERT((wprod - wcons) <= sizeof(intf->out),
337 ("console send ring inconsistent"));
338
339 for (sent = 0; sent < size; sent++, wprod++) {
340 if ((wprod - wcons) >= sizeof(intf->out))
341 break;
342 intf->out[MASK_XENCONS_IDX(wprod, intf->out)] = buffer[sent];
343 }
344
345 wmb();
346 intf->out_prod = wprod;
347
348 xencons_notify_ring(cons);
349
350 return (sent);
351 }
352
353 static int
xencons_read_ring(struct xencons_priv * cons,char * buffer,unsigned int size)354 xencons_read_ring(struct xencons_priv *cons, char *buffer, unsigned int size)
355 {
356 struct xencons_interface *intf;
357 XENCONS_RING_IDX rcons, rprod;
358 unsigned int rsz;
359
360 intf = cons->intf;
361
362 xencons_lock_assert(cons);
363
364 rcons = intf->in_cons;
365 rprod = intf->in_prod;
366 rmb();
367
368 for (rsz = 0; rsz < size; rsz++, rcons++) {
369 if (rprod == rcons)
370 break;
371 buffer[rsz] = intf->in[MASK_XENCONS_IDX(rcons, intf->in)];
372 }
373
374 wmb();
375 intf->in_cons = rcons;
376
377 /* No need to notify the backend if nothing has been read */
378 if (rsz != 0)
379 xencons_notify_ring(cons);
380
381 return (rsz);
382 }
383
384 static const struct xencons_ops xencons_ring_ops = {
385 .early_init = xencons_early_init_ring,
386 .init = xencons_init_ring,
387 .read = xencons_read_ring,
388 .write = xencons_write_ring,
389 };
390
391 /*------------------ Common implementation of the console -------------------*/
392
393 /*
394 * Called by the low-level driver during early boot to initialize the
395 * main console driver.
396 * Only the minimal set up to get a console should be done here.
397 */
398 static void
xencons_early_init(void)399 xencons_early_init(void)
400 {
401
402 mtx_init(&main_cons.mtx, "XCONS LOCK", NULL, MTX_SPIN);
403
404 if (xen_get_console_evtchn() == 0)
405 main_cons.ops = &xencons_hypervisor_ops;
406 else
407 main_cons.ops = &xencons_ring_ops;
408
409 main_cons.ops->early_init(&main_cons);
410 }
411
412 /*
413 * Receive character from the console and put them in the internal buffer
414 * XXX: Handle overflow of the internal buffer
415 */
416 static void
xencons_rx(struct xencons_priv * cons)417 xencons_rx(struct xencons_priv *cons)
418 {
419 char buf[16];
420 int sz;
421
422 xencons_lock(cons);
423 while ((sz = cons->ops->read(cons, buf, sizeof(buf))) > 0) {
424 int i;
425
426 for (i = 0; i < sz; i++)
427 cons->rbuf[RBUF_MASK(cons->rp++)] = buf[i];
428 }
429 xencons_unlock(cons);
430 }
431
432 /* Return true if the write buffer is full */
433 static bool
xencons_tx_full(struct xencons_priv * cons)434 xencons_tx_full(struct xencons_priv *cons)
435 {
436 unsigned int used;
437
438 xencons_lock(cons);
439 used = cons->wp - cons->wc;
440 xencons_unlock(cons);
441
442 return (used >= WBUF_SIZE);
443 }
444
445 static void
xencons_tx_flush(struct xencons_priv * cons,int force)446 xencons_tx_flush(struct xencons_priv *cons, int force)
447 {
448 int sz;
449
450 xencons_lock(cons);
451 while (cons->wc != cons->wp) {
452 int sent;
453 sz = cons->wp - cons->wc;
454 if (sz > (WBUF_SIZE - WBUF_MASK(cons->wc)))
455 sz = WBUF_SIZE - WBUF_MASK(cons->wc);
456 sent = cons->ops->write(cons, &cons->wbuf[WBUF_MASK(cons->wc)],
457 sz);
458
459 /*
460 * The other end may not have been initialized. Ignore
461 * the force.
462 */
463 if (__predict_false(sent < 0))
464 break;
465
466 /*
467 * If force is set, spin until the console data is
468 * flushed through the domain controller.
469 */
470 if (sent == 0 && __predict_true(!force))
471 break;
472
473 cons->wc += sent;
474 }
475 xencons_unlock(cons);
476 }
477
478 static bool
xencons_putc(struct xencons_priv * cons,int c,bool force_flush)479 xencons_putc(struct xencons_priv *cons, int c, bool force_flush)
480 {
481
482 xencons_lock(cons);
483 if ((cons->wp - cons->wc) < WBUF_SIZE)
484 cons->wbuf[WBUF_MASK(cons->wp++)] = c;
485 xencons_unlock(cons);
486
487 xencons_tx_flush(cons, force_flush);
488
489 return (xencons_tx_full(cons));
490 }
491
492 static int
xencons_getc(struct xencons_priv * cons)493 xencons_getc(struct xencons_priv *cons)
494 {
495 int ret;
496
497 xencons_lock(cons);
498 if (cons->rp != cons->rc) {
499 /* We need to return only one char */
500 ret = (int)cons->rbuf[RBUF_MASK(cons->rc)];
501 cons->rc++;
502 } else {
503 ret = -1;
504 }
505
506 xencons_unlock(cons);
507
508 return (ret);
509 }
510
511 static bool
xencons_tx(struct tty * tp)512 xencons_tx(struct tty *tp)
513 {
514 bool cons_full;
515 char c;
516 struct xencons_priv *cons;
517
518 cons = tty_softc(tp);
519
520 tty_assert_locked(tp);
521
522 /*
523 * Don't transmit any character if the buffer is full. Otherwise,
524 * characters may be lost
525 */
526 if (xencons_tx_full(cons))
527 return (false);
528
529 cons_full = false;
530 while (!cons_full && ttydisc_getc(tp, &c, 1) == 1)
531 cons_full = xencons_putc(cons, c, false);
532
533 return (!cons_full);
534 }
535
536 static void
xencons_intr(void * arg)537 xencons_intr(void *arg)
538 {
539 struct tty *tp;
540 struct xencons_priv *cons;
541 int ret;
542
543 tp = arg;
544 cons = tty_softc(tp);
545
546 /*
547 * The input will be used by the low-level console when KDB is active
548 */
549 if (kdb_active)
550 return;
551
552 /*
553 * It's not necessary to retrieve input when the tty is not opened
554 */
555 if (!cons->opened)
556 return;
557
558 xencons_rx(cons);
559
560 tty_lock(tp);
561 while ((ret = xencons_getc(cons)) != -1) {
562 #ifdef KDB
563 kdb_alt_break(ret, &cons->altbrk);
564 #endif
565 ttydisc_rint(tp, ret, 0);
566 }
567 ttydisc_rint_done(tp);
568 tty_unlock(tp);
569
570 /* Try to flush remaining characters if necessary */
571 xencons_tx_flush(cons, 0);
572 }
573
574 /*
575 * Helpers to call while shutting down:
576 * - Force flush all output
577 */
578 static void
xencons_shutdown(void * arg,int howto)579 xencons_shutdown(void *arg, int howto)
580 {
581 struct tty *tp;
582
583 tp = arg;
584
585 xencons_tx_flush(tty_softc(tp), 1);
586 }
587
588 /*---------------------- Low-level console driver ---------------------------*/
589 static void
xencons_cnprobe(struct consdev * cp)590 xencons_cnprobe(struct consdev *cp)
591 {
592
593 if (!xen_domain())
594 return;
595
596 cp->cn_pri = (boothowto & RB_SERIAL) ? CN_REMOTE : CN_NORMAL;
597 sprintf(cp->cn_name, "%s0", driver_name);
598 }
599
600 static void
xencons_cninit(struct consdev * cp)601 xencons_cninit(struct consdev *cp)
602 {
603
604 xencons_early_init();
605 }
606
607 static void
xencons_cnterm(struct consdev * cp)608 xencons_cnterm(struct consdev *cp)
609 {
610 }
611
612 static void
xencons_cngrab(struct consdev * cp)613 xencons_cngrab(struct consdev *cp)
614 {
615 }
616
617 static void
xencons_cnungrab(struct consdev * cp)618 xencons_cnungrab(struct consdev *cp)
619 {
620 }
621
622 static int
xencons_cngetc(struct consdev * dev)623 xencons_cngetc(struct consdev *dev)
624 {
625
626 xencons_rx(&main_cons);
627
628 return (xencons_getc(&main_cons));
629 }
630
631 static void
xencons_cnputc(struct consdev * dev,int c)632 xencons_cnputc(struct consdev *dev, int c)
633 {
634 /*
635 * The low-level console is used by KDB and panic. We have to ensure
636 * that any character sent will be seen by the backend.
637 */
638 xencons_putc(&main_cons, c, true);
639 }
640
641 CONSOLE_DRIVER(xencons);
642
643 /*----------------------------- TTY driver ---------------------------------*/
644
645 static int
xencons_tty_open(struct tty * tp)646 xencons_tty_open(struct tty *tp)
647 {
648 struct xencons_priv *cons;
649
650 cons = tty_softc(tp);
651
652 cons->opened = true;
653
654 return (0);
655 }
656
657 static void
xencons_tty_close(struct tty * tp)658 xencons_tty_close(struct tty *tp)
659 {
660 struct xencons_priv *cons;
661
662 cons = tty_softc(tp);
663
664 cons->opened = false;
665 }
666
667 static void
xencons_timeout(void * v)668 xencons_timeout(void *v)
669 {
670 struct tty *tp;
671 struct xencons_priv *cons;
672
673 tp = v;
674 cons = tty_softc(tp);
675
676 if (!xencons_tx(tp))
677 callout_reset(&cons->callout, XC_POLLTIME,
678 xencons_timeout, tp);
679 }
680
681 static void
xencons_tty_outwakeup(struct tty * tp)682 xencons_tty_outwakeup(struct tty *tp)
683 {
684 struct xencons_priv *cons;
685
686 cons = tty_softc(tp);
687
688 callout_stop(&cons->callout);
689
690 if (!xencons_tx(tp))
691 callout_reset(&cons->callout, XC_POLLTIME,
692 xencons_timeout, tp);
693 }
694
695 static struct ttydevsw xencons_ttydevsw = {
696 .tsw_flags = TF_NOPREFIX,
697 .tsw_open = xencons_tty_open,
698 .tsw_close = xencons_tty_close,
699 .tsw_outwakeup = xencons_tty_outwakeup,
700 };
701
702 /*------------------------ Main console driver ------------------------------*/
703 static void
xencons_identify(driver_t * driver,device_t parent)704 xencons_identify(driver_t *driver, device_t parent)
705 {
706 device_t child __unused;
707
708 if (main_cons.ops == NULL)
709 return;
710
711 child = BUS_ADD_CHILD(parent, 0, driver_name, 0);
712 }
713
714 static int
xencons_probe(device_t dev)715 xencons_probe(device_t dev)
716 {
717
718 device_set_desc(dev, "Xen Console");
719 return (BUS_PROBE_NOWILDCARD);
720 }
721
722 static int
xencons_attach(device_t dev)723 xencons_attach(device_t dev)
724 {
725 struct tty *tp;
726 /*
727 * The main console is already allocated statically in order to
728 * support low-level console
729 */
730 struct xencons_priv *cons;
731 int err;
732
733 cons = &main_cons;
734
735 tp = tty_alloc(&xencons_ttydevsw, cons);
736 tty_makedev(tp, NULL, "%s%r", driver_name, 0);
737 device_set_softc(dev, tp);
738
739 callout_init_mtx(&cons->callout, tty_getlock(tp), 0);
740
741 err = cons->ops->init(dev, tp, xencons_intr);
742 if (err != 0) {
743 device_printf(dev, "Unable to initialize the console (%d)\n",
744 err);
745 return (err);
746 }
747
748 /* register handler to flush console on shutdown */
749 if ((EVENTHANDLER_REGISTER(shutdown_post_sync, xencons_shutdown,
750 tp, SHUTDOWN_PRI_DEFAULT)) == NULL)
751 device_printf(dev, "shutdown event registration failed!\n");
752
753 return (0);
754 }
755
756 static int
xencons_resume(device_t dev)757 xencons_resume(device_t dev)
758 {
759 struct xencons_priv *cons;
760 struct tty *tp;
761 int err;
762
763 tp = device_get_softc(dev);
764 cons = tty_softc(tp);
765 xen_intr_unbind(&cons->intr_handle);
766
767 err = cons->ops->init(dev, tp, xencons_intr);
768 if (err != 0) {
769 device_printf(dev, "Unable to resume the console (%d)\n", err);
770 return (err);
771 }
772
773 return (0);
774 }
775
776 static device_method_t xencons_methods[] = {
777 DEVMETHOD(device_identify, xencons_identify),
778 DEVMETHOD(device_probe, xencons_probe),
779 DEVMETHOD(device_attach, xencons_attach),
780 DEVMETHOD(device_resume, xencons_resume),
781
782 DEVMETHOD_END
783 };
784
785 static driver_t xencons_driver = {
786 driver_name,
787 xencons_methods,
788 0,
789 };
790
791 DRIVER_MODULE(xc, xenpv, xencons_driver, 0, 0);
792