FreeBSD/Linux Kernel Cross Reference
sys/kern/tty_cons.c
1 /*-
2 * Copyright (c) 1988 University of Utah.
3 * Copyright (c) 1991 The Regents of the University of California.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to Berkeley by
7 * the Systems Programming Group of the University of Utah Computer
8 * Science Department.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 4. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
34 * from: @(#)cons.c 7.2 (Berkeley) 5/9/91
35 */
36
37 #include <sys/cdefs.h>
38 __FBSDID("$FreeBSD$");
39
40 #include "opt_ddb.h"
41
42 #include <sys/param.h>
43 #include <sys/systm.h>
44 #include <sys/lock.h>
45 #include <sys/mutex.h>
46 #include <sys/conf.h>
47 #include <sys/cons.h>
48 #include <sys/fcntl.h>
49 #include <sys/kdb.h>
50 #include <sys/kernel.h>
51 #include <sys/malloc.h>
52 #include <sys/msgbuf.h>
53 #include <sys/namei.h>
54 #include <sys/priv.h>
55 #include <sys/proc.h>
56 #include <sys/queue.h>
57 #include <sys/reboot.h>
58 #include <sys/sysctl.h>
59 #include <sys/tty.h>
60 #include <sys/uio.h>
61 #include <sys/vnode.h>
62
63 #include <ddb/ddb.h>
64
65 #include <machine/cpu.h>
66
67 static d_open_t cnopen;
68 static d_close_t cnclose;
69 static d_read_t cnread;
70 static d_write_t cnwrite;
71 static d_ioctl_t cnioctl;
72 static d_poll_t cnpoll;
73 static d_kqfilter_t cnkqfilter;
74
75 static struct cdevsw cn_cdevsw = {
76 .d_version = D_VERSION,
77 .d_open = cnopen,
78 .d_close = cnclose,
79 .d_read = cnread,
80 .d_write = cnwrite,
81 .d_ioctl = cnioctl,
82 .d_poll = cnpoll,
83 .d_name = "console",
84 .d_flags = D_TTY | D_NEEDGIANT,
85 .d_kqfilter = cnkqfilter,
86 };
87
88 struct cn_device {
89 STAILQ_ENTRY(cn_device) cnd_next;
90 struct vnode *cnd_vp;
91 struct consdev *cnd_cn;
92 };
93
94 #define CNDEVPATHMAX 32
95 #define CNDEVTAB_SIZE 4
96 static struct cn_device cn_devtab[CNDEVTAB_SIZE];
97 static STAILQ_HEAD(, cn_device) cn_devlist =
98 STAILQ_HEAD_INITIALIZER(cn_devlist);
99
100 #define CND_INVALID(cnd, td) \
101 (cnd == NULL || cnd->cnd_vp == NULL || \
102 (cnd->cnd_vp->v_type == VBAD && !cn_devopen(cnd, td, 1)))
103
104 static dev_t cn_udev_t;
105 SYSCTL_OPAQUE(_machdep, OID_AUTO, consdev, CTLFLAG_RD,
106 &cn_udev_t, sizeof cn_udev_t, "T,struct cdev *", "");
107
108 int cons_avail_mask = 0; /* Bit mask. Each registered low level console
109 * which is currently unavailable for inpit
110 * (i.e., if it is in graphics mode) will have
111 * this bit cleared.
112 */
113 static int cn_mute;
114 static int openflag; /* how /dev/console was opened */
115 static int cn_is_open;
116 static char *consbuf; /* buffer used by `consmsgbuf' */
117 static struct callout conscallout; /* callout for outputting to constty */
118 struct msgbuf consmsgbuf; /* message buffer for console tty */
119 static u_char console_pausing; /* pause after each line during probe */
120 static char *console_pausestr=
121 "<pause; press any key to proceed to next line or '.' to end pause mode>";
122 struct tty *constty; /* pointer to console "window" tty */
123 static struct mtx cnputs_mtx; /* Mutex for cnputs(). */
124 static int use_cnputs_mtx = 0; /* != 0 if cnputs_mtx locking reqd. */
125
126 static void constty_timeout(void *arg);
127
128 static struct consdev cons_consdev;
129 DATA_SET(cons_set, cons_consdev);
130 SET_DECLARE(cons_set, struct consdev);
131
132 void
133 cninit(void)
134 {
135 struct consdev *best_cn, *cn, **list;
136
137 /*
138 * Check if we should mute the console (for security reasons perhaps)
139 * It can be changes dynamically using sysctl kern.consmute
140 * once we are up and going.
141 *
142 */
143 cn_mute = ((boothowto & (RB_MUTE
144 |RB_SINGLE
145 |RB_VERBOSE
146 |RB_ASKNAME)) == RB_MUTE);
147
148 /*
149 * Find the first console with the highest priority.
150 */
151 best_cn = NULL;
152 SET_FOREACH(list, cons_set) {
153 cn = *list;
154 cnremove(cn);
155 if (cn->cn_probe == NULL)
156 continue;
157 cn->cn_probe(cn);
158 if (cn->cn_pri == CN_DEAD)
159 continue;
160 if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri)
161 best_cn = cn;
162 if (boothowto & RB_MULTIPLE) {
163 /*
164 * Initialize console, and attach to it.
165 */
166 cn->cn_init(cn);
167 cnadd(cn);
168 }
169 }
170 if (best_cn == NULL)
171 return;
172 if ((boothowto & RB_MULTIPLE) == 0) {
173 best_cn->cn_init(best_cn);
174 cnadd(best_cn);
175 }
176 if (boothowto & RB_PAUSE)
177 console_pausing = 1;
178 /*
179 * Make the best console the preferred console.
180 */
181 cnselect(best_cn);
182 }
183
184 void
185 cninit_finish()
186 {
187 console_pausing = 0;
188 }
189
190 /* add a new physical console to back the virtual console */
191 int
192 cnadd(struct consdev *cn)
193 {
194 struct cn_device *cnd;
195 int i;
196
197 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
198 if (cnd->cnd_cn == cn)
199 return (0);
200 for (i = 0; i < CNDEVTAB_SIZE; i++) {
201 cnd = &cn_devtab[i];
202 if (cnd->cnd_cn == NULL)
203 break;
204 }
205 if (cnd->cnd_cn != NULL)
206 return (ENOMEM);
207 cnd->cnd_cn = cn;
208 if (cn->cn_name[0] == '\0') {
209 /* XXX: it is unclear if/where this print might output */
210 printf("WARNING: console at %p has no name\n", cn);
211 }
212 STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next);
213
214 /* Add device to the active mask. */
215 cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0);
216
217 return (0);
218 }
219
220 void
221 cnremove(struct consdev *cn)
222 {
223 struct cn_device *cnd;
224 int i;
225
226 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
227 if (cnd->cnd_cn != cn)
228 continue;
229 STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next);
230 if (cnd->cnd_vp != NULL)
231 vn_close(cnd->cnd_vp, openflag, NOCRED, NULL);
232 cnd->cnd_vp = NULL;
233 cnd->cnd_cn = NULL;
234
235 /* Remove this device from available mask. */
236 for (i = 0; i < CNDEVTAB_SIZE; i++)
237 if (cnd == &cn_devtab[i]) {
238 cons_avail_mask &= ~(1 << i);
239 break;
240 }
241 #if 0
242 /*
243 * XXX
244 * syscons gets really confused if console resources are
245 * freed after the system has initialized.
246 */
247 if (cn->cn_term != NULL)
248 cn->cn_term(cn);
249 #endif
250 return;
251 }
252 }
253
254 void
255 cnselect(struct consdev *cn)
256 {
257 struct cn_device *cnd;
258
259 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
260 if (cnd->cnd_cn != cn)
261 continue;
262 if (cnd == STAILQ_FIRST(&cn_devlist))
263 return;
264 STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next);
265 STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next);
266 return;
267 }
268 }
269
270 void
271 cnavailable(struct consdev *cn, int available)
272 {
273 int i;
274
275 for (i = 0; i < CNDEVTAB_SIZE; i++) {
276 if (cn_devtab[i].cnd_cn == cn)
277 break;
278 }
279 if (available) {
280 if (i < CNDEVTAB_SIZE)
281 cons_avail_mask |= (1 << i);
282 cn->cn_flags &= ~CN_FLAG_NOAVAIL;
283 } else {
284 if (i < CNDEVTAB_SIZE)
285 cons_avail_mask &= ~(1 << i);
286 cn->cn_flags |= CN_FLAG_NOAVAIL;
287 }
288 }
289
290 int
291 cnunavailable(void)
292 {
293
294 return (cons_avail_mask == 0);
295 }
296
297 /*
298 * XXX: rewrite to use sbufs instead
299 */
300
301 static int
302 sysctl_kern_console(SYSCTL_HANDLER_ARGS)
303 {
304 struct cn_device *cnd;
305 struct consdev *cp, **list;
306 char *name, *p;
307 int delete, len, error;
308
309 len = 2;
310 SET_FOREACH(list, cons_set) {
311 cp = *list;
312 if (cp->cn_name[0] != '\0')
313 len += strlen(cp->cn_name) + 1;
314 }
315 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
316 len += strlen(cnd->cnd_cn->cn_name) + 1;
317 len = len > CNDEVPATHMAX ? len : CNDEVPATHMAX;
318 MALLOC(name, char *, len, M_TEMP, M_WAITOK | M_ZERO);
319 p = name;
320 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
321 p += sprintf(p, "%s,", cnd->cnd_cn->cn_name);
322 *p++ = '/';
323 SET_FOREACH(list, cons_set) {
324 cp = *list;
325 if (cp->cn_name[0] != '\0')
326 p += sprintf(p, "%s,", cp->cn_name);
327 }
328 error = sysctl_handle_string(oidp, name, len, req);
329 if (error == 0 && req->newptr != NULL) {
330 p = name;
331 error = ENXIO;
332 delete = 0;
333 if (*p == '-') {
334 delete = 1;
335 p++;
336 }
337 SET_FOREACH(list, cons_set) {
338 cp = *list;
339 if (strcmp(p, cp->cn_name) != 0)
340 continue;
341 if (delete) {
342 cnremove(cp);
343 error = 0;
344 } else {
345 error = cnadd(cp);
346 if (error == 0)
347 cnselect(cp);
348 }
349 break;
350 }
351 }
352 FREE(name, M_TEMP);
353 return (error);
354 }
355
356 SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING|CTLFLAG_RW,
357 0, 0, sysctl_kern_console, "A", "Console device control");
358
359 /*
360 * User has changed the state of the console muting.
361 * This may require us to open or close the device in question.
362 */
363 static int
364 sysctl_kern_consmute(SYSCTL_HANDLER_ARGS)
365 {
366 int error;
367 int ocn_mute;
368
369 ocn_mute = cn_mute;
370 error = sysctl_handle_int(oidp, &cn_mute, 0, req);
371 if (error != 0 || req->newptr == NULL)
372 return (error);
373 if (ocn_mute && !cn_mute && cn_is_open)
374 error = cnopen(NULL, openflag, 0, curthread);
375 else if (!ocn_mute && cn_mute && cn_is_open) {
376 error = cnclose(NULL, openflag, 0, curthread);
377 cn_is_open = 1; /* XXX hack */
378 }
379 return (error);
380 }
381
382 SYSCTL_PROC(_kern, OID_AUTO, consmute, CTLTYPE_INT|CTLFLAG_RW,
383 0, sizeof(cn_mute), sysctl_kern_consmute, "I", "");
384
385 static int
386 cn_devopen(struct cn_device *cnd, struct thread *td, int forceopen)
387 {
388 char path[CNDEVPATHMAX];
389 struct nameidata nd;
390 struct vnode *vp;
391 struct cdev *dev;
392 struct cdevsw *csw;
393 int error;
394
395 if ((vp = cnd->cnd_vp) != NULL) {
396 if (!forceopen && vp->v_type != VBAD) {
397 dev = vp->v_rdev;
398 csw = dev_refthread(dev);
399 if (csw == NULL)
400 return (ENXIO);
401 error = (*csw->d_open)(dev, openflag, 0, td);
402 dev_relthread(dev);
403 return (error);
404 }
405 cnd->cnd_vp = NULL;
406 vn_close(vp, openflag, td->td_ucred, td);
407 }
408 snprintf(path, sizeof(path), "/dev/%s", cnd->cnd_cn->cn_name);
409 NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, path, td);
410 error = vn_open(&nd, &openflag, 0, NULL);
411 if (error == 0) {
412 NDFREE(&nd, NDF_ONLY_PNBUF);
413 VOP_UNLOCK(nd.ni_vp, 0, td);
414 if (nd.ni_vp->v_type == VCHR)
415 cnd->cnd_vp = nd.ni_vp;
416 else
417 vn_close(nd.ni_vp, openflag, td->td_ucred, td);
418 }
419 return (cnd->cnd_vp != NULL);
420 }
421
422 static int
423 cnopen(struct cdev *dev, int flag, int mode, struct thread *td)
424 {
425 struct cn_device *cnd;
426
427 openflag = flag | FWRITE; /* XXX */
428 cn_is_open = 1; /* console is logically open */
429 if (cn_mute)
430 return (0);
431 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
432 cn_devopen(cnd, td, 0);
433 return (0);
434 }
435
436 static int
437 cnclose(struct cdev *dev, int flag, int mode, struct thread *td)
438 {
439 struct cn_device *cnd;
440 struct vnode *vp;
441
442 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
443 if ((vp = cnd->cnd_vp) == NULL)
444 continue;
445 cnd->cnd_vp = NULL;
446 vn_close(vp, openflag, td->td_ucred, td);
447 }
448 cn_is_open = 0;
449 return (0);
450 }
451
452 static int
453 cnread(struct cdev *dev, struct uio *uio, int flag)
454 {
455 struct cn_device *cnd;
456 struct cdevsw *csw;
457 int error;
458
459 cnd = STAILQ_FIRST(&cn_devlist);
460 if (cn_mute || CND_INVALID(cnd, curthread))
461 return (0);
462 dev = cnd->cnd_vp->v_rdev;
463 csw = dev_refthread(dev);
464 if (csw == NULL)
465 return (ENXIO);
466 error = (csw->d_read)(dev, uio, flag);
467 dev_relthread(dev);
468 return (error);
469 }
470
471 static int
472 cnwrite(struct cdev *dev, struct uio *uio, int flag)
473 {
474 struct cn_device *cnd;
475 struct cdevsw *csw;
476 int error;
477
478 cnd = STAILQ_FIRST(&cn_devlist);
479 if (cn_mute || CND_INVALID(cnd, curthread))
480 goto done;
481 if (constty)
482 dev = constty->t_dev;
483 else
484 dev = cnd->cnd_vp->v_rdev;
485 if (dev != NULL) {
486 log_console(uio);
487 csw = dev_refthread(dev);
488 if (csw == NULL)
489 return (ENXIO);
490 error = (csw->d_write)(dev, uio, flag);
491 dev_relthread(dev);
492 return (error);
493 }
494 done:
495 uio->uio_resid = 0; /* dump the data */
496 return (0);
497 }
498
499 static int
500 cnioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
501 {
502 struct cn_device *cnd;
503 struct cdevsw *csw;
504 int error;
505
506 cnd = STAILQ_FIRST(&cn_devlist);
507 if (cn_mute || CND_INVALID(cnd, td))
508 return (0);
509 /*
510 * Superuser can always use this to wrest control of console
511 * output from the "virtual" console.
512 */
513 if (cmd == TIOCCONS && constty) {
514 error = priv_check(td, PRIV_TTY_CONSOLE);
515 if (error)
516 return (error);
517 constty = NULL;
518 return (0);
519 }
520 dev = cnd->cnd_vp->v_rdev;
521 if (dev == NULL)
522 return (0); /* XXX : ENOTTY ? */
523 csw = dev_refthread(dev);
524 if (csw == NULL)
525 return (ENXIO);
526 error = (csw->d_ioctl)(dev, cmd, data, flag, td);
527 dev_relthread(dev);
528 return (error);
529 }
530
531 /*
532 * XXX
533 * poll/kqfilter do not appear to be correct
534 */
535 static int
536 cnpoll(struct cdev *dev, int events, struct thread *td)
537 {
538 struct cn_device *cnd;
539 struct cdevsw *csw;
540 int error;
541
542 cnd = STAILQ_FIRST(&cn_devlist);
543 if (cn_mute || CND_INVALID(cnd, td))
544 return (0);
545 dev = cnd->cnd_vp->v_rdev;
546 if (dev == NULL)
547 return (0);
548 csw = dev_refthread(dev);
549 if (csw == NULL)
550 return (ENXIO);
551 error = (csw->d_poll)(dev, events, td);
552 dev_relthread(dev);
553 return (error);
554 }
555
556 static int
557 cnkqfilter(struct cdev *dev, struct knote *kn)
558 {
559 struct cn_device *cnd;
560 struct cdevsw *csw;
561 int error;
562
563 cnd = STAILQ_FIRST(&cn_devlist);
564 if (cn_mute || CND_INVALID(cnd, curthread))
565 return (EINVAL);
566 dev = cnd->cnd_vp->v_rdev;
567 if (dev == NULL)
568 return (ENXIO);
569 csw = dev_refthread(dev);
570 if (csw == NULL)
571 return (ENXIO);
572 error = (csw->d_kqfilter)(dev, kn);
573 dev_relthread(dev);
574 return (error);
575 }
576
577 /*
578 * Low level console routines.
579 */
580 int
581 cngetc(void)
582 {
583 int c;
584
585 if (cn_mute)
586 return (-1);
587 while ((c = cncheckc()) == -1)
588 ;
589 if (c == '\r')
590 c = '\n'; /* console input is always ICRNL */
591 return (c);
592 }
593
594 int
595 cncheckc(void)
596 {
597 struct cn_device *cnd;
598 struct consdev *cn;
599 int c;
600
601 if (cn_mute)
602 return (-1);
603 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
604 cn = cnd->cnd_cn;
605 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) {
606 if (cn->cn_checkc != NULL)
607 c = cn->cn_checkc(cn);
608 else
609 c = cn->cn_getc(cn);
610 if (c != -1) {
611 return (c);
612 }
613 }
614 }
615 return (-1);
616 }
617
618 void
619 cnputc(int c)
620 {
621 struct cn_device *cnd;
622 struct consdev *cn;
623 char *cp;
624
625 if (cn_mute || c == '\0')
626 return;
627 STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
628 cn = cnd->cnd_cn;
629 if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) {
630 if (c == '\n')
631 cn->cn_putc(cn, '\r');
632 cn->cn_putc(cn, c);
633 }
634 }
635 if (console_pausing && c == '\n' && !kdb_active) {
636 for (cp = console_pausestr; *cp != '\0'; cp++)
637 cnputc(*cp);
638 if (cngetc() == '.')
639 console_pausing = 0;
640 cnputc('\r');
641 for (cp = console_pausestr; *cp != '\0'; cp++)
642 cnputc(' ');
643 cnputc('\r');
644 }
645 }
646
647 void
648 cnputs(char *p)
649 {
650 int c;
651 int unlock_reqd = 0;
652
653 if (use_cnputs_mtx) {
654 mtx_lock_spin(&cnputs_mtx);
655 unlock_reqd = 1;
656 }
657
658 while ((c = *p++) != '\0')
659 cnputc(c);
660
661 if (unlock_reqd)
662 mtx_unlock_spin(&cnputs_mtx);
663 }
664
665 static int consmsgbuf_size = 8192;
666 SYSCTL_INT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RW, &consmsgbuf_size, 0,
667 "");
668
669 /*
670 * Redirect console output to a tty.
671 */
672 void
673 constty_set(struct tty *tp)
674 {
675 int size;
676
677 KASSERT(tp != NULL, ("constty_set: NULL tp"));
678 if (consbuf == NULL) {
679 size = consmsgbuf_size;
680 consbuf = malloc(size, M_TTYS, M_WAITOK);
681 msgbuf_init(&consmsgbuf, consbuf, size);
682 callout_init(&conscallout, 0);
683 }
684 constty = tp;
685 constty_timeout(NULL);
686 }
687
688 /*
689 * Disable console redirection to a tty.
690 */
691 void
692 constty_clear(void)
693 {
694 int c;
695
696 constty = NULL;
697 if (consbuf == NULL)
698 return;
699 callout_stop(&conscallout);
700 while ((c = msgbuf_getchar(&consmsgbuf)) != -1)
701 cnputc(c);
702 free(consbuf, M_TTYS);
703 consbuf = NULL;
704 }
705
706 /* Times per second to check for pending console tty messages. */
707 static int constty_wakeups_per_second = 5;
708 SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW,
709 &constty_wakeups_per_second, 0, "");
710
711 static void
712 constty_timeout(void *arg)
713 {
714 int c;
715
716 while (constty != NULL && (c = msgbuf_getchar(&consmsgbuf)) != -1) {
717 if (tputchar(c, constty) < 0)
718 constty = NULL;
719 }
720 if (constty != NULL) {
721 callout_reset(&conscallout, hz / constty_wakeups_per_second,
722 constty_timeout, NULL);
723 } else {
724 /* Deallocate the constty buffer memory. */
725 constty_clear();
726 }
727 }
728
729 static void
730 cn_drvinit(void *unused)
731 {
732
733 make_dev(&cn_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "console");
734
735 mtx_init(&cnputs_mtx, "cnputs_mtx", NULL, MTX_SPIN | MTX_NOWITNESS);
736 use_cnputs_mtx = 1;
737 }
738
739 SYSINIT(cndev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, cn_drvinit, NULL);
Cache object: 6e8e26aea90267a545daaed9b12dab09
|