xref: /illumos-gate/usr/src/uts/common/io/wscons.c (revision 85bb5f1d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * "Workstation console" multiplexor driver for Sun.
31  *
32  * Sends output to the primary frame buffer using the PROM monitor;
33  * gets input from a stream linked below us that is the "keyboard
34  * driver", below which is linked the primary keyboard.
35  */
36 
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/signal.h>
40 #include <sys/cred.h>
41 #include <sys/vnode.h>
42 #include <sys/termios.h>
43 #include <sys/termio.h>
44 #include <sys/ttold.h>
45 #include <sys/stropts.h>
46 #include <sys/stream.h>
47 #include <sys/strsun.h>
48 #include <sys/tty.h>
49 #include <sys/buf.h>
50 #include <sys/uio.h>
51 #include <sys/stat.h>
52 #include <sys/kmem.h>
53 #include <sys/cpuvar.h>
54 #include <sys/kbio.h>
55 #include <sys/strredir.h>
56 #include <sys/fs/snode.h>
57 #include <sys/consdev.h>
58 #include <sys/conf.h>
59 #include <sys/ddi.h>
60 #include <sys/sunddi.h>
61 #include <sys/debug.h>
62 #include <sys/console.h>
63 #include <sys/ddi_impldefs.h>
64 #include <sys/promif.h>
65 #include <sys/policy.h>
66 #include <sys/tem.h>
67 #include <sys/wscons.h>
68 
69 #define	MINLINES	10
70 #define	MAXLINES	48
71 #define	LOSCREENLINES	34
72 #define	HISCREENLINES	48
73 
74 #define	MINCOLS		10
75 #define	MAXCOLS		120
76 #define	LOSCREENCOLS	80
77 #define	HISCREENCOLS	120
78 
79 struct wscons {
80 	struct tem *wc_tem;		/* Terminal emulator state */
81 	int	wc_flags;		/* random flags (protected by */
82 					/* write-side exclusion lock  */
83 	dev_t	wc_dev;			/* major/minor for this device */
84 	tty_common_t wc_ttycommon;	/* data common to all tty drivers */
85 #ifdef _HAVE_TEM_FIRMWARE
86 	int	wc_pendc;		/* pending output character */
87 	int	wc_defer_output;	/* set if output device is "slow" */
88 #endif /* _HAVE_TEM_FIRMWARE */
89 	queue_t	*wc_kbdqueue;		/* "console keyboard" device queue */
90 					/* below us */
91 	bufcall_id_t wc_bufcallid;	/* id returned by qbufcall */
92 	timeout_id_t wc_timeoutid;	/* id returned by qtimeout */
93 	cons_polledio_t		wc_polledio; /* polled I/O function pointers */
94 	cons_polledio_t		*wc_kb_polledio; /* keyboard's polledio */
95 	unsigned int	wc_kb_getpolledio_id; /* id for kb CONSOPENPOLLEDIO */
96 	mblk_t	*wc_pending_link;	/* I_PLINK pending for kb polledio */
97 } wscons;
98 
99 #define	WCS_ISOPEN	0x00000001	/* open is complete */
100 #define	WCS_STOPPED	0x00000002	/* output is stopped */
101 #define	WCS_DELAY	0x00000004	/* waiting for delay to finish */
102 #define	WCS_BUSY	0x00000008	/* waiting for transmission to finish */
103 
104 static int	wcopen(queue_t *, dev_t *, int, int, cred_t *);
105 static int	wcclose(queue_t *, int, cred_t *);
106 static int	wcuwput(queue_t *, mblk_t *);
107 static int	wclrput(queue_t *, mblk_t *);
108 
109 static struct module_info wcm_info = {
110 	0,
111 	"wc",
112 	0,
113 	INFPSZ,
114 	2048,
115 	128
116 };
117 
118 static struct qinit wcurinit = {
119 	putq,
120 	NULL,
121 	wcopen,
122 	wcclose,
123 	NULL,
124 	&wcm_info,
125 	NULL
126 };
127 
128 static struct qinit wcuwinit = {
129 	wcuwput,
130 	NULL,
131 	wcopen,
132 	wcclose,
133 	NULL,
134 	&wcm_info,
135 	NULL
136 };
137 
138 static struct qinit wclrinit = {
139 	wclrput,
140 	NULL,
141 	NULL,
142 	NULL,
143 	NULL,
144 	&wcm_info,
145 	NULL
146 };
147 
148 /*
149  * We always putnext directly to the underlying queue.
150  */
151 static struct qinit wclwinit = {
152 	NULL,
153 	NULL,
154 	NULL,
155 	NULL,
156 	NULL,
157 	&wcm_info,
158 	NULL
159 };
160 
161 static struct streamtab wcinfo = {
162 	&wcurinit,
163 	&wcuwinit,
164 	&wclrinit,
165 	&wclwinit,
166 };
167 
168 static int wc_info(dev_info_t *, ddi_info_cmd_t, void *, void **result);
169 static int wc_attach(dev_info_t *, ddi_attach_cmd_t);
170 static dev_info_t *wc_dip;
171 
172 DDI_DEFINE_STREAM_OPS(wc_ops, nulldev, nulldev, wc_attach, nodev, nodev,
173     wc_info, D_MTPERMOD | D_MP, &wcinfo);
174 
175 static void	wcreioctl(void *);
176 static void 	wcioctl(queue_t *, mblk_t *);
177 #ifdef _HAVE_TEM_FIRMWARE
178 static void	wcopoll(void *);
179 static void	wconsout(void *);
180 #endif /* _HAVE_TEM_FIRMWARE */
181 static void	wcrstrt(void *);
182 static void	wcstart(void);
183 static void	wc_open_kb_polledio(struct wscons *wc, queue_t *q, mblk_t *mp);
184 static void	wc_close_kb_polledio(struct wscons *wc, queue_t *q, mblk_t *mp);
185 static void	wc_polled_putchar(cons_polledio_arg_t arg, unsigned char c);
186 static boolean_t wc_polled_ischar(cons_polledio_arg_t arg);
187 static int	wc_polled_getchar(cons_polledio_arg_t arg);
188 static void	wc_polled_enter(cons_polledio_arg_t arg);
189 static void	wc_polled_exit(cons_polledio_arg_t arg);
190 static void	wc_get_size(struct wscons *wscons);
191 static void	wc_modechg_cb(tem_modechg_cb_arg_t arg);
192 
193 #include <sys/types.h>
194 #include <sys/conf.h>
195 #include <sys/param.h>
196 #include <sys/systm.h>
197 #include <sys/errno.h>
198 #include <sys/modctl.h>
199 
200 static struct dev_ops wc_ops;
201 
202 /*
203  * Debug printing
204  */
205 #ifndef DPRINTF
206 #ifdef DEBUG
207 /*PRINTFLIKE1*/
208 static void	wc_dprintf(const char *fmt, ...) __KPRINTFLIKE(1);
209 #define	DPRINTF(l, m, args) \
210 	(((l) >= wc_errlevel) && ((m) & wc_errmask) ?	\
211 		wc_dprintf args :			\
212 		(void) 0)
213 
214 /*
215  * Severity levels for printing
216  */
217 #define	PRINT_L0	0	/* print every message */
218 #define	PRINT_L1	1	/* debug */
219 #define	PRINT_L2	2	/* quiet */
220 
221 /*
222  * Masks
223  */
224 #define	PRINT_MASK_ALL		0xFFFFFFFFU
225 uint_t	wc_errmask = PRINT_MASK_ALL;
226 uint_t	wc_errlevel = PRINT_L2;
227 
228 #else
229 #define	DPRINTF(l, m, args)	/* NOTHING */
230 #endif
231 #endif
232 
233 /*
234  * Module linkage information for the kernel.
235  */
236 
237 static struct modldrv modldrv = {
238 	&mod_driverops, /* Type of module.  This one is a pseudo driver */
239 	"Workstation multiplexer Driver 'wc' %I%",
240 	&wc_ops,	/* driver ops */
241 };
242 
243 static struct modlinkage modlinkage = {
244 	MODREV_1,
245 	&modldrv,
246 	NULL
247 };
248 
249 int
250 _init(void)
251 {
252 	return (mod_install(&modlinkage));
253 }
254 
255 int
256 _fini(void)
257 {
258 	return (mod_remove(&modlinkage));
259 }
260 
261 int
262 _info(struct modinfo *modinfop)
263 {
264 	return (mod_info(&modlinkage, modinfop));
265 }
266 
267 /*ARGSUSED*/
268 static int
269 wc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
270 {
271 	if (ddi_create_minor_node(devi, "wscons", S_IFCHR,
272 	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
273 		ddi_remove_minor_node(devi, NULL);
274 		return (-1);
275 	}
276 	wc_dip = devi;
277 
278 	bzero(&(wscons.wc_polledio), sizeof (wscons.wc_polledio));
279 
280 	return (DDI_SUCCESS);
281 }
282 
283 /* ARGSUSED */
284 static int
285 wc_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
286 	void **result)
287 {
288 	int error;
289 
290 	switch (infocmd) {
291 	case DDI_INFO_DEVT2DEVINFO:
292 		if (wc_dip == NULL) {
293 			error = DDI_FAILURE;
294 		} else {
295 			*result = (void *) wc_dip;
296 			error = DDI_SUCCESS;
297 		}
298 		break;
299 	case DDI_INFO_DEVT2INSTANCE:
300 		*result = (void *)0;
301 		error = DDI_SUCCESS;
302 		break;
303 	default:
304 		error = DDI_FAILURE;
305 	}
306 	return (error);
307 }
308 
309 #ifdef _HAVE_TEM_FIRMWARE
310 /*
311  * Output buffer. Protected by the per-module inner perimeter.
312  */
313 #define	MAXHIWAT	2000
314 static char obuf[MAXHIWAT];
315 #endif /* _HAVE_TEM_FIRMWARE */
316 
317 /*ARGSUSED*/
318 static int
319 wcopen(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp)
320 {
321 	static boolean_t polledio_inited = B_FALSE;
322 	struct termios *termiosp;
323 	int len;
324 
325 	if (getminor(*devp) != 0)
326 		return (ENXIO);		/* sorry, only one per customer */
327 
328 	if (!(wscons.wc_flags & WCS_ISOPEN)) {
329 		mutex_init(&wscons.wc_ttycommon.t_excl, NULL, MUTEX_DEFAULT,
330 		    NULL);
331 		wscons.wc_ttycommon.t_iflag = 0;
332 		/*
333 		 * Get the default termios settings (cflag).
334 		 * These are stored as a property in the
335 		 * "options" node.
336 		 */
337 		if (ddi_getlongprop(DDI_DEV_T_ANY,
338 		    ddi_root_node(), 0, "ttymodes",
339 		    (caddr_t)&termiosp, &len) == DDI_PROP_SUCCESS &&
340 		    len == sizeof (struct termios)) {
341 
342 			wscons.wc_ttycommon.t_cflag = termiosp->c_cflag;
343 			kmem_free(termiosp, len);
344 		} else {
345 			/*
346 			 * Gack!  Whine about it.
347 			 */
348 			cmn_err(CE_WARN,
349 			    "wc: Couldn't get ttymodes property!\n");
350 		}
351 		wscons.wc_ttycommon.t_iocpending = NULL;
352 		wscons.wc_flags = WCS_ISOPEN;
353 
354 		wscons.wc_dev = *devp;
355 		wc_get_size(&wscons);
356 
357 		if (!polledio_inited) {
358 			polledio_inited = B_TRUE;
359 
360 			/*
361 			 * Initialize the parts of the polled I/O struct that
362 			 * are common to both input and output modes, but which
363 			 * don't flag to the upper layers, which if any of the
364 			 * two modes are available.  We don't know at this point
365 			 * if system is configured CONS_KFB, but we will when
366 			 * consconfig_dacf asks us with CONSOPENPOLLED I/O.
367 			 */
368 			wscons.wc_polledio.cons_polledio_version =
369 			    CONSPOLLEDIO_V0;
370 			wscons.wc_polledio.cons_polledio_argument =
371 			    (cons_polledio_arg_t)&wscons;
372 			wscons.wc_polledio.cons_polledio_enter =
373 			    wc_polled_enter;
374 			wscons.wc_polledio.cons_polledio_exit =
375 			    wc_polled_exit;
376 
377 #ifdef _HAVE_TEM_FIRMWARE
378 			/*
379 			 * If we're talking directly to a framebuffer, we assume
380 			 * that it's a "slow" device, so that rendering should
381 			 * be deferred to a timeout or softcall so that we write
382 			 * a bunch of characters at once.
383 			 */
384 			wscons.wc_defer_output = prom_stdout_is_framebuffer();
385 #endif /* _HAVE_TEM_FIRMWARE */
386 		}
387 	}
388 
389 	if (wscons.wc_ttycommon.t_flags & TS_XCLUDE) {
390 		if (secpolicy_excl_open(crp) != 0) {
391 			return (EBUSY);
392 		}
393 	}
394 	wscons.wc_ttycommon.t_readq = q;
395 	wscons.wc_ttycommon.t_writeq = WR(q);
396 	qprocson(q);
397 	return (0);
398 }
399 
400 /*ARGSUSED*/
401 static int
402 wcclose(queue_t *q, int flag, cred_t *crp)
403 {
404 	qprocsoff(q);
405 	if (wscons.wc_bufcallid != 0) {
406 		qunbufcall(q, wscons.wc_bufcallid);
407 		wscons.wc_bufcallid = 0;
408 	}
409 	if (wscons.wc_timeoutid != 0) {
410 		(void) quntimeout(q, wscons.wc_timeoutid);
411 		wscons.wc_timeoutid = 0;
412 	}
413 	ttycommon_close(&wscons.wc_ttycommon);
414 	wscons.wc_flags = 0;
415 	return (0);
416 }
417 
418 /*
419  * Put procedure for upper write queue.
420  * Respond to M_STOP, M_START, M_IOCTL, and M_FLUSH messages here;
421  * queue up M_BREAK, M_DELAY, and M_DATA messages for processing by
422  * the start routine, and then call the start routine; discard
423  * everything else.
424  */
425 static int
426 wcuwput(queue_t *q, mblk_t *mp)
427 {
428 	switch (mp->b_datap->db_type) {
429 
430 	case M_STOP:
431 		wscons.wc_flags |= WCS_STOPPED;
432 		freemsg(mp);
433 		break;
434 
435 	case M_START:
436 		wscons.wc_flags &= ~WCS_STOPPED;
437 		wcstart();
438 		freemsg(mp);
439 		break;
440 
441 	case M_IOCTL: {
442 		struct iocblk *iocp;
443 		struct linkblk *linkp;
444 
445 		iocp = (void *)mp->b_rptr;
446 		switch (iocp->ioc_cmd) {
447 
448 		case I_LINK:	/* stupid, but permitted */
449 		case I_PLINK:
450 			if (wscons.wc_kbdqueue != NULL) {
451 				/* somebody already linked */
452 				miocnak(q, mp, 0, EINVAL);
453 				return (0);
454 			}
455 			linkp = (void *)mp->b_cont->b_rptr;
456 			wscons.wc_kbdqueue = WR(linkp->l_qbot);
457 			mp->b_datap->db_type = M_IOCACK;
458 			iocp->ioc_count = 0;
459 			wc_open_kb_polledio(&wscons, q, mp);
460 			break;
461 
462 		case I_UNLINK:	/* stupid, but permitted */
463 		case I_PUNLINK:
464 			linkp = (void *)mp->b_cont->b_rptr;
465 			if (wscons.wc_kbdqueue != WR(linkp->l_qbot)) {
466 				/* not us */
467 				miocnak(q, mp, 0, EINVAL);
468 				return (0);
469 			}
470 
471 			mp->b_datap->db_type = M_IOCACK;
472 			iocp->ioc_count = 0;
473 			wc_close_kb_polledio(&wscons, q, mp);
474 			break;
475 
476 		case TCSETSW:
477 		case TCSETSF:
478 		case TCSETAW:
479 		case TCSETAF:
480 		case TCSBRK:
481 			/*
482 			 * The changes do not take effect until all
483 			 * output queued before them is drained.
484 			 * Put this message on the queue, so that
485 			 * "wcstart" will see it when it's done
486 			 * with the output before it.  Poke the
487 			 * start routine, just in case.
488 			 */
489 			(void) putq(q, mp);
490 			wcstart();
491 			break;
492 
493 		case CONSSETABORTENABLE:
494 		case CONSGETABORTENABLE:
495 		case KIOCSDIRECT:
496 			if (wscons.wc_kbdqueue != NULL) {
497 				(void) putnext(wscons.wc_kbdqueue, mp);
498 				break;
499 			}
500 			/* fall through */
501 
502 		default:
503 			/*
504 			 * Do it now.
505 			 */
506 			wcioctl(q, mp);
507 			break;
508 		}
509 		break;
510 	}
511 
512 	case M_FLUSH:
513 		if (*mp->b_rptr & FLUSHW) {
514 			/*
515 			 * Flush our write queue.
516 			 */
517 			flushq(q, FLUSHDATA);	/* XXX doesn't flush M_DELAY */
518 			*mp->b_rptr &= ~FLUSHW;	/* it has been flushed */
519 		}
520 		if (*mp->b_rptr & FLUSHR) {
521 			flushq(RD(q), FLUSHDATA);
522 			qreply(q, mp);	/* give the read queues a crack at it */
523 		} else
524 			freemsg(mp);
525 		break;
526 
527 	case M_BREAK:
528 		/*
529 		 * Ignore these, as they make no sense.
530 		 */
531 		freemsg(mp);
532 		break;
533 
534 	case M_DELAY:
535 	case M_DATA:
536 		/*
537 		 * Queue the message up to be transmitted,
538 		 * and poke the start routine.
539 		 */
540 		(void) putq(q, mp);
541 		wcstart();
542 		break;
543 
544 	default:
545 		/*
546 		 * "No, I don't want a subscription to Chain Store Age,
547 		 * thank you anyway."
548 		 */
549 		freemsg(mp);
550 		break;
551 	}
552 
553 	return (0);
554 }
555 
556 /*
557  * Retry an "ioctl", now that "qbufcall" claims we may be able to allocate
558  * the buffer we need.
559  */
560 /*ARGSUSED*/
561 static void
562 wcreioctl(void *arg)
563 {
564 	queue_t *q;
565 	mblk_t *mp;
566 
567 	wscons.wc_bufcallid = 0;
568 	q = wscons.wc_ttycommon.t_writeq;
569 	if ((mp = wscons.wc_ttycommon.t_iocpending) != NULL) {
570 		/* not pending any more */
571 		wscons.wc_ttycommon.t_iocpending = NULL;
572 		wcioctl(q, mp);
573 	}
574 }
575 
576 static int
577 wc_getterm(mblk_t *mp)
578 {
579 	char *term;
580 	intptr_t arg;
581 	int flag = ((struct iocblk *)(void *)mp->b_rptr)->ioc_flag;
582 
583 	STRUCT_DECL(cons_getterm, wcterm);
584 	STRUCT_INIT(wcterm, flag);
585 
586 	arg = *((intptr_t *)(void *)mp->b_cont->b_rptr);
587 
588 	if (ddi_copyin((void *)arg, STRUCT_BUF(wcterm),
589 	    STRUCT_SIZE(wcterm), flag) != 0) {
590 		return (EFAULT);
591 	}
592 
593 	if (consmode == CONS_FW) {
594 		/* PROM terminal emulator */
595 		term = "sun";
596 	} else {
597 		/* Kernel terminal emulator */
598 		ASSERT(consmode == CONS_KFB);
599 		term = "sun-color";
600 	}
601 
602 	if (STRUCT_FGET(wcterm, cn_term_len) <
603 	    strlen(term) + 1) {
604 		return (EOVERFLOW);
605 	}
606 
607 	if (ddi_copyout(term,
608 	    STRUCT_FGETP(wcterm, cn_term_type),
609 	    strlen(term) + 1, flag) != 0) {
610 		return (EFAULT);
611 	}
612 
613 	return (0);
614 }
615 
616 /*
617  * Process an "ioctl" message sent down to us.
618  */
619 static void
620 wcioctl(queue_t *q, mblk_t *mp)
621 {
622 	struct iocblk *iocp;
623 	size_t datasize;
624 	int error;
625 	long len;
626 
627 	iocp = (void *)mp->b_rptr;
628 
629 	switch (iocp->ioc_cmd) {
630 	case TIOCSWINSZ:
631 		/*
632 		 * Ignore all attempts to set the screen size; the
633 		 * value in the EEPROM is guaranteed (modulo PROM bugs)
634 		 * to be the value used by the PROM monitor code, so it
635 		 * is by definition correct.  Many programs (e.g.,
636 		 * "login" and "tset") will attempt to reset the size
637 		 * to (0, 0) or (34, 80), neither of which is
638 		 * necessarily correct.
639 		 * We just ACK the message, so as not to disturb
640 		 * programs that set the sizes.
641 		 */
642 		iocp->ioc_count = 0;	/* no data returned */
643 		mp->b_datap->db_type = M_IOCACK;
644 		qreply(q, mp);
645 		return;
646 
647 	case CONSOPENPOLLEDIO:
648 		DPRINTF(PRINT_L1, PRINT_MASK_ALL,
649 		    ("wcioctl: CONSOPENPOLLEDIO\n"));
650 
651 		error = miocpullup(mp, sizeof (struct cons_polledio *));
652 		if (error != 0) {
653 			miocnak(q, mp, 0, error);
654 			return;
655 		}
656 
657 		/*
658 		 * We are given an appropriate-sized data block,
659 		 * and return a pointer to our structure in it.
660 		 */
661 		if (consmode == CONS_KFB)
662 			wscons.wc_polledio.cons_polledio_putchar =
663 			    wc_polled_putchar;
664 		*(struct cons_polledio **)(void *)mp->b_cont->b_rptr =
665 		    &wscons.wc_polledio;
666 
667 		mp->b_datap->db_type = M_IOCACK;
668 
669 		qreply(q, mp);
670 		break;
671 
672 	case CONS_GETTERM:
673 		if ((error = wc_getterm(mp)) != 0)
674 			miocnak(q, mp, 0, error);
675 		else
676 			miocack(q, mp, 0, 0);
677 		return;
678 
679 	case WC_OPEN_FB:
680 		/*
681 		 * Start out pessimistic, so that we can just jump to
682 		 * the reply to bail out.
683 		 */
684 		mp->b_datap->db_type = M_IOCNAK;
685 
686 		/*
687 		 * First test:  really, this should be done only from
688 		 * inside the kernel.  Unfortunately, that information
689 		 * doesn't seem to be available in a streams ioctl,
690 		 * so restrict it to root only.  (Perhaps we could check
691 		 * for ioc_cr == kcred.)
692 		 */
693 		if ((iocp->ioc_error = secpolicy_console(iocp->ioc_cr)) != 0)
694 			goto open_fail;
695 
696 		/*
697 		 * Some miscellaneous checks...
698 		 */
699 		iocp->ioc_error = EINVAL;
700 
701 		/*
702 		 * If we're already open, fail.
703 		 */
704 		if (wscons.wc_tem != NULL)
705 			goto open_fail;
706 
707 		/*
708 		 * If we don't have exactly one continuation block, fail.
709 		 */
710 		if (mp->b_cont == NULL || mp->b_cont->b_cont != NULL)
711 			goto open_fail;
712 
713 		/*
714 		 * If there's no null terminator in the string, fail.
715 		 */
716 		len = MBLKL(mp->b_cont);
717 		if (memchr(mp->b_cont->b_rptr, 0, len) == NULL)
718 			goto open_fail;
719 
720 		/*
721 		 * NOTE:  should eventually get default
722 		 * dimensions from a property, e.g. screen-#rows.
723 		 */
724 		iocp->ioc_error = tem_init(&wscons.wc_tem,
725 		    (char *)mp->b_cont->b_rptr, iocp->ioc_cr);
726 		/*
727 		 * Of course, if the terminal emulator initialization
728 		 * failed, fail.
729 		 */
730 		if (iocp->ioc_error != 0)
731 			goto open_fail;
732 
733 		tem_register_modechg_cb(wscons.wc_tem, wc_modechg_cb,
734 		    (tem_modechg_cb_arg_t)&wscons);
735 
736 		/*
737 		 * Refresh terminal size with info from terminal emulator.
738 		 */
739 		wc_get_size(&wscons);
740 
741 		/*
742 		 * ... and succeed.
743 		 */
744 		mp->b_datap->db_type = M_IOCACK;
745 
746 open_fail:
747 		qreply(q, mp);
748 		break;
749 
750 	case WC_CLOSE_FB:
751 		/*
752 		 * There's nothing that can call this, so it's not
753 		 * really implemented.
754 		 */
755 		mp->b_datap->db_type = M_IOCNAK;
756 		/*
757 		 * However, if it were implemented, it would clearly
758 		 * be root-only.
759 		 */
760 		if ((iocp->ioc_error = secpolicy_console(iocp->ioc_cr)) != 0)
761 			goto close_fail;
762 
763 		iocp->ioc_error = EINVAL;
764 
765 close_fail:
766 		qreply(q, mp);
767 		break;
768 
769 	default:
770 
771 		/*
772 		 * The only way in which "ttycommon_ioctl" can fail is
773 		 * if the "ioctl" requires a response containing data
774 		 * to be returned to the user, and no mblk could be
775 		 * allocated for the data.  No such "ioctl" alters our
776 		 * state.  Thus, we always go ahead and do any
777 		 * state-changes the "ioctl" calls for.  If we couldn't
778 		 * allocate the data, "ttycommon_ioctl" has stashed the
779 		 * "ioctl" away safely, so we just call "qbufcall" to
780 		 * request that we be called back when we stand a
781 		 * better chance of allocating the data.
782 		 */
783 		datasize = ttycommon_ioctl(&wscons.wc_ttycommon, q, mp, &error);
784 		if (datasize != 0) {
785 			if (wscons.wc_bufcallid != 0)
786 				qunbufcall(q, wscons.wc_bufcallid);
787 			wscons.wc_bufcallid = qbufcall(q, datasize, BPRI_HI,
788 			    wcreioctl, NULL);
789 			return;
790 		}
791 
792 		if (error < 0) {
793 			if (iocp->ioc_cmd == TCSBRK)
794 				error = 0;
795 			else
796 				error = EINVAL;
797 		}
798 		if (error != 0) {
799 			iocp->ioc_error = error;
800 			mp->b_datap->db_type = M_IOCNAK;
801 		}
802 		qreply(q, mp);
803 		break;
804 	}
805 }
806 
807 /*
808  * This function gets the polled I/O structures from the lower
809  * keyboard driver.  If any initialization or resource allocation
810  * needs to be done by the lower driver, it will be done when
811  * the lower driver services this message.
812  */
813 static void
814 wc_open_kb_polledio(struct wscons *wscons, queue_t *q, mblk_t *mp)
815 {
816 	mblk_t *mp2;
817 	struct iocblk *iocp;
818 
819 	DPRINTF(PRINT_L1, PRINT_MASK_ALL,
820 	    ("wc_open_kb_polledio: sending CONSOPENPOLLEDIO\n"));
821 
822 	mp2 = mkiocb(CONSOPENPOLLEDIO);
823 
824 	if (mp2 == NULL) {
825 		/*
826 		 * If we can't get an mblk, then wait for it.
827 		 */
828 		goto nomem;
829 	}
830 
831 	mp2->b_cont = allocb(sizeof (struct cons_polledio *), BPRI_HI);
832 
833 	if (mp2->b_cont == NULL) {
834 		/*
835 		 * If we can't get an mblk, then wait for it, and release
836 		 * the mblk that we have already allocated.
837 		 */
838 		freemsg(mp2);
839 		goto nomem;
840 	}
841 
842 	iocp = (void *)mp2->b_rptr;
843 
844 	iocp->ioc_count = sizeof (struct cons_polledio *);
845 	mp2->b_cont->b_wptr = mp2->b_cont->b_rptr +
846 	    sizeof (struct cons_polledio *);
847 
848 	wscons->wc_pending_link = mp;
849 	wscons->wc_kb_getpolledio_id = iocp->ioc_id;
850 
851 	putnext(wscons->wc_kbdqueue, mp2);
852 
853 	return;
854 
855 nomem:
856 	iocp = (void *)mp->b_rptr;
857 	iocp->ioc_error = ENOMEM;
858 	mp->b_datap->db_type = M_IOCNAK;
859 	qreply(q, mp);
860 }
861 
862 /*
863  * This function releases the polled I/O structures from the lower
864  * keyboard driver.  If any de-initialization needs to be done, or
865  * any resources need to be released, it will be done when the lower
866  * driver services this message.
867  */
868 static void
869 wc_close_kb_polledio(struct wscons *wscons, queue_t *q, mblk_t *mp)
870 {
871 	mblk_t *mp2;
872 	struct iocblk *iocp;
873 
874 	DPRINTF(PRINT_L1, PRINT_MASK_ALL,
875 	    ("wc_close_kb_polledio: sending CONSCLOSEPOLLEDIO\n"));
876 
877 	mp2 = mkiocb(CONSCLOSEPOLLEDIO);
878 
879 	if (mp2 == NULL) {
880 		/*
881 		 * If we can't get an mblk, then wait for it.
882 		 */
883 		goto nomem;
884 	}
885 
886 	mp2->b_cont = allocb(sizeof (struct cons_polledio *), BPRI_HI);
887 
888 	if (mp2->b_cont == NULL) {
889 		/*
890 		 * If we can't get an mblk, then wait for it, and release
891 		 * the mblk that we have already allocated.
892 		 */
893 		freemsg(mp2);
894 
895 		goto nomem;
896 	}
897 
898 	iocp = (void *)mp2->b_rptr;
899 
900 	iocp->ioc_count = 0;
901 
902 	wscons->wc_pending_link = mp;
903 	wscons->wc_kb_getpolledio_id = iocp->ioc_id;
904 
905 	putnext(wscons->wc_kbdqueue, mp2);
906 
907 	return;
908 
909 nomem:
910 	iocp = (void *)mp->b_rptr;
911 	iocp->ioc_error = ENOMEM;
912 	mp->b_datap->db_type = M_IOCNAK;
913 	qreply(q, mp);
914 }
915 
916 #ifdef _HAVE_TEM_FIRMWARE
917 /* ARGSUSED */
918 static void
919 wcopoll(void *arg)
920 {
921 	queue_t *q;
922 
923 	q = wscons.wc_ttycommon.t_writeq;
924 	wscons.wc_timeoutid = 0;
925 	/* See if we can continue output */
926 	if ((wscons.wc_flags & WCS_BUSY) && wscons.wc_pendc != -1) {
927 		if (prom_mayput((char)wscons.wc_pendc) == 0) {
928 			wscons.wc_pendc = -1;
929 			wscons.wc_flags &= ~WCS_BUSY;
930 			if (!(wscons.wc_flags&(WCS_DELAY|WCS_STOPPED)))
931 				wcstart();
932 		} else
933 			wscons.wc_timeoutid = qtimeout(q, wcopoll, NULL, 1);
934 	}
935 }
936 #endif	/* _HAVE_TEM_FIRMWARE */
937 
938 /*
939  * Restart output on the console after a timeout.
940  */
941 /* ARGSUSED */
942 static void
943 wcrstrt(void *arg)
944 {
945 	ASSERT(wscons.wc_ttycommon.t_writeq != NULL);
946 	wscons.wc_flags &= ~WCS_DELAY;
947 	wcstart();
948 }
949 
950 /*
951  * Start console output
952  */
953 static void
954 wcstart(void)
955 {
956 #ifdef _HAVE_TEM_FIRMWARE
957 	int c;
958 	ssize_t cc;
959 #endif /* _HAVE_TEM_FIRMWARE */
960 	queue_t *q;
961 	mblk_t *bp;
962 	mblk_t *nbp;
963 
964 	/*
965 	 * If we're waiting for something to happen (delay timeout to
966 	 * expire, current transmission to finish, output to be
967 	 * restarted, output to finish draining), don't grab anything
968 	 * new.
969 	 */
970 	if (wscons.wc_flags & (WCS_DELAY|WCS_BUSY|WCS_STOPPED))
971 		return;
972 
973 	q = wscons.wc_ttycommon.t_writeq;
974 	/*
975 	 * assumes that we have been called by whoever holds the
976 	 * exclusionary lock on the write-side queue (protects
977 	 * wc_flags and wc_pendc).
978 	 */
979 	for (;;) {
980 		if ((bp = getq(q)) == NULL)
981 			return;	/* nothing to transmit */
982 
983 		/*
984 		 * We have a new message to work on.
985 		 * Check whether it's a delay or an ioctl (the latter
986 		 * occurs if the ioctl in question was waiting for the output
987 		 * to drain).  If it's one of those, process it immediately.
988 		 */
989 		switch (bp->b_datap->db_type) {
990 
991 		case M_DELAY:
992 			/*
993 			 * Arrange for "wcrstrt" to be called when the
994 			 * delay expires; it will turn WCS_DELAY off,
995 			 * and call "wcstart" to grab the next message.
996 			 */
997 			if (wscons.wc_timeoutid != 0)
998 				(void) quntimeout(q, wscons.wc_timeoutid);
999 			wscons.wc_timeoutid = qtimeout(q, wcrstrt, NULL,
1000 			    (clock_t)(*(unsigned char *)bp->b_rptr + 6));
1001 			wscons.wc_flags |= WCS_DELAY;
1002 			freemsg(bp);
1003 			return;	/* wait for this to finish */
1004 
1005 		case M_IOCTL:
1006 			/*
1007 			 * This ioctl was waiting for the output ahead of
1008 			 * it to drain; obviously, it has.  Do it, and
1009 			 * then grab the next message after it.
1010 			 */
1011 			wcioctl(q, bp);
1012 			continue;
1013 		}
1014 
1015 #ifdef _HAVE_TEM_FIRMWARE
1016 		if (consmode == CONS_KFB) {
1017 #endif /* _HAVE_TEM_FIRMWARE */
1018 			if (wscons.wc_tem != NULL) {
1019 				for (nbp = bp; nbp != NULL; nbp = nbp->b_cont) {
1020 					if (nbp->b_wptr > nbp->b_rptr) {
1021 						(void) tem_write(wscons.wc_tem,
1022 						    nbp->b_rptr,
1023 						    MBLKL(nbp),
1024 						    kcred);
1025 					}
1026 				}
1027 				freemsg(bp);
1028 			}
1029 #ifdef _HAVE_TEM_FIRMWARE
1030 			continue;
1031 		}
1032 
1033 		/* consmode = CONS_FW */
1034 		if ((cc = MBLKL(bp)) == 0) {
1035 			freemsg(bp);
1036 			continue;
1037 		}
1038 		/*
1039 		 * Direct output to the frame buffer if this device
1040 		 * is not the "hardware" console.
1041 		 */
1042 		if (wscons.wc_defer_output) {
1043 			/*
1044 			 * Never do output here;
1045 			 * it takes forever.
1046 			 */
1047 			wscons.wc_flags |= WCS_BUSY;
1048 			wscons.wc_pendc = -1;
1049 			(void) putbq(q, bp);
1050 			if (q->q_count > 128) { /* do it soon */
1051 				softcall(wconsout, NULL);
1052 			} else {	/* wait a bit */
1053 				if (wscons.wc_timeoutid != 0)
1054 					(void) quntimeout(q,
1055 					    wscons.wc_timeoutid);
1056 				wscons.wc_timeoutid = qtimeout(q, wconsout,
1057 				    NULL, hz / 30);
1058 			}
1059 			return;
1060 		}
1061 		for (;;) {
1062 			c = *bp->b_rptr++;
1063 			cc--;
1064 			if (prom_mayput((char)c) != 0) {
1065 				wscons.wc_flags |= WCS_BUSY;
1066 				wscons.wc_pendc = c;
1067 				if (wscons.wc_timeoutid != 0)
1068 					(void) quntimeout(q,
1069 					    wscons.wc_timeoutid);
1070 				wscons.wc_timeoutid = qtimeout(q, wcopoll,
1071 				    NULL, 1);
1072 				if (bp != NULL)
1073 					/* not done with this message yet */
1074 					(void) putbq(q, bp);
1075 				return;
1076 			}
1077 			while (cc <= 0) {
1078 				nbp = bp;
1079 				bp = bp->b_cont;
1080 				freeb(nbp);
1081 				if (bp == NULL)
1082 					return;
1083 				cc = MBLKL(bp);
1084 			}
1085 		}
1086 #endif /* _HAVE_TEM_FIRMWARE */
1087 	}
1088 }
1089 
1090 #ifdef _HAVE_TEM_FIRMWARE
1091 /*
1092  * Output to frame buffer console.
1093  * It takes a long time to scroll.
1094  */
1095 /* ARGSUSED */
1096 static void
1097 wconsout(void *dummy)
1098 {
1099 	uchar_t *cp;
1100 	ssize_t cc;
1101 	queue_t *q;
1102 	mblk_t *bp;
1103 	mblk_t *nbp;
1104 	char *current_position;
1105 	ssize_t bytes_left;
1106 
1107 	if ((q = wscons.wc_ttycommon.t_writeq) == NULL) {
1108 		return;	/* not attached to a stream */
1109 	}
1110 
1111 	/*
1112 	 * Set up to copy up to MAXHIWAT bytes.
1113 	 */
1114 	current_position = &obuf[0];
1115 	bytes_left = MAXHIWAT;
1116 	while ((bp = getq(q)) != NULL) {
1117 		if (bp->b_datap->db_type == M_IOCTL) {
1118 			/*
1119 			 * This ioctl was waiting for the output ahead of
1120 			 * it to drain; obviously, it has.  Put it back
1121 			 * so that "wcstart" can handle it, and transmit
1122 			 * what we've got.
1123 			 */
1124 			(void) putbq(q, bp);
1125 			goto transmit;
1126 		}
1127 
1128 		do {
1129 			cp = bp->b_rptr;
1130 			cc = MBLKL(bp);
1131 			while (cc != 0) {
1132 				if (bytes_left == 0) {
1133 					/*
1134 					 * Out of buffer space; put this
1135 					 * buffer back on the queue, and
1136 					 * transmit what we have.
1137 					 */
1138 					bp->b_rptr = cp;
1139 					(void) putbq(q, bp);
1140 					goto transmit;
1141 				}
1142 				*current_position++ = *cp++;
1143 				cc--;
1144 				bytes_left--;
1145 			}
1146 			nbp = bp;
1147 			bp = bp->b_cont;
1148 			freeb(nbp);
1149 		} while (bp != NULL);
1150 	}
1151 
1152 transmit:
1153 	if ((cc = MAXHIWAT - bytes_left) != 0)
1154 		console_puts(obuf, cc);
1155 
1156 	wscons.wc_flags &= ~WCS_BUSY;
1157 	wcstart();
1158 }
1159 #endif /* _HAVE_TEM_FIRMWARE */
1160 
1161 /*
1162  * Put procedure for lower read queue.
1163  * Pass everything up to queue above "upper half".
1164  */
1165 static int
1166 wclrput(queue_t *q, mblk_t *mp)
1167 {
1168 	queue_t *upq;
1169 	struct iocblk *iocp;
1170 
1171 	DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1172 	    ("wclrput: wclrput type = 0x%x\n", mp->b_datap->db_type));
1173 
1174 	switch (mp->b_datap->db_type) {
1175 
1176 	case M_FLUSH:
1177 		if (*mp->b_rptr == FLUSHW || *mp->b_rptr == FLUSHRW) {
1178 			/*
1179 			 * Flush our write queue.
1180 			 */
1181 			/* XXX doesn't flush M_DELAY */
1182 			flushq(WR(q), FLUSHDATA);
1183 			*mp->b_rptr = FLUSHR;	/* it has been flushed */
1184 		}
1185 		if (*mp->b_rptr == FLUSHR || *mp->b_rptr == FLUSHRW) {
1186 			flushq(q, FLUSHDATA);
1187 			*mp->b_rptr = FLUSHW;	/* it has been flushed */
1188 			qreply(q, mp);	/* give the read queues a crack at it */
1189 		} else
1190 			freemsg(mp);
1191 		break;
1192 
1193 	case M_DATA:
1194 		if ((upq = wscons.wc_ttycommon.t_readq) != NULL) {
1195 			if (!canput(upq->q_next)) {
1196 				ttycommon_qfull(&wscons.wc_ttycommon, upq);
1197 				wcstart();
1198 				freemsg(mp);
1199 			} else
1200 				putnext(upq, mp);
1201 		} else
1202 			freemsg(mp);
1203 		break;
1204 
1205 	case M_IOCACK:
1206 	case M_IOCNAK:
1207 		iocp = (void *)mp->b_rptr;
1208 		if (wscons.wc_pending_link != NULL &&
1209 		    iocp->ioc_id == wscons.wc_kb_getpolledio_id) {
1210 			switch (mp->b_datap->db_type) {
1211 
1212 			case M_IOCACK:
1213 				switch (iocp->ioc_cmd) {
1214 
1215 
1216 				case CONSOPENPOLLEDIO:
1217 					DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1218 					    ("wclrput: "
1219 					    "ACK CONSOPENPOLLEDIO\n"));
1220 					wscons.wc_kb_polledio =
1221 					    *(struct cons_polledio **)(void *)
1222 					    mp->b_cont->b_rptr;
1223 					wscons.wc_polledio.
1224 					    cons_polledio_getchar =
1225 					    wc_polled_getchar;
1226 					wscons.wc_polledio.
1227 					    cons_polledio_ischar =
1228 					    wc_polled_ischar;
1229 					break;
1230 
1231 				case CONSCLOSEPOLLEDIO:
1232 					DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1233 					    ("wclrput: "
1234 					    "ACK CONSCLOSEPOLLEDIO\n"));
1235 					wscons.wc_kb_polledio = NULL;
1236 					wscons.wc_kbdqueue = NULL;
1237 					wscons.wc_polledio.
1238 					    cons_polledio_getchar = NULL;
1239 					wscons.wc_polledio.
1240 					    cons_polledio_ischar = NULL;
1241 					break;
1242 				default:
1243 					DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1244 					    ("wclrput: ACK UNKNOWN\n"));
1245 				}
1246 
1247 				break;
1248 			case M_IOCNAK:
1249 				/*
1250 				 * Keyboard may or may not support polled I/O.
1251 				 * This ioctl may have been rejected because
1252 				 * we only have the wc->conskbd chain built,
1253 				 * and the keyboard driver has not been linked
1254 				 * underneath conskbd yet.
1255 				 */
1256 				DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1257 				    ("wclrput: NAK\n"));
1258 
1259 				switch (iocp->ioc_cmd) {
1260 
1261 				case CONSCLOSEPOLLEDIO:
1262 					wscons.wc_kb_polledio = NULL;
1263 					wscons.wc_kbdqueue = NULL;
1264 					wscons.wc_polledio.
1265 					    cons_polledio_getchar = NULL;
1266 					wscons.wc_polledio.
1267 					    cons_polledio_ischar = NULL;
1268 					break;
1269 				}
1270 				break;
1271 			}
1272 
1273 			/*
1274 			 * Discard the response, replace it with the
1275 			 * pending response to the I_PLINK, then let it
1276 			 * flow upward.
1277 			 */
1278 			freemsg(mp);
1279 			mp = wscons.wc_pending_link;
1280 			wscons.wc_pending_link = NULL;
1281 			wscons.wc_kb_getpolledio_id = 0;
1282 		}
1283 		/* FALLTHROUGH */
1284 
1285 	default:	/* inc M_ERROR, M_HANGUP, M_IOCACK, M_IOCNAK, ... */
1286 		DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1287 		    ("wclrput: Message DISCARDED\n"));
1288 		if ((upq = wscons.wc_ttycommon.t_readq) != NULL) {
1289 			putnext(upq, mp);
1290 		} else {
1291 			freemsg(mp);
1292 		}
1293 		break;
1294 	}
1295 
1296 	return (0);
1297 }
1298 
1299 /*
1300  * These are for systems without OBP, and for devices that cannot be
1301  * shared between Solaris and the OBP.
1302  */
1303 static void
1304 wc_polled_putchar(cons_polledio_arg_t arg, unsigned char c)
1305 {
1306 	if (c == '\n')
1307 		wc_polled_putchar(arg, '\r');
1308 
1309 	if (wscons.wc_tem == NULL) {
1310 		/*
1311 		 * We have no terminal emulator configured.  We have no
1312 		 * recourse but to drop the output on the floor.
1313 		 */
1314 		return;
1315 	}
1316 
1317 	tem_polled_write(wscons.wc_tem, &c, 1);
1318 }
1319 
1320 /*
1321  * These are for systems without OBP, and for devices that cannot be
1322  * shared between Solaris and the OBP.
1323  */
1324 static int
1325 wc_polled_getchar(cons_polledio_arg_t arg)
1326 {
1327 	struct wscons *wscons = (struct wscons *)arg;
1328 
1329 	if (wscons->wc_kb_polledio == NULL) {
1330 		prom_printf("wscons:  getchar with no keyboard support");
1331 		prom_printf("Halted...");
1332 		for (;;)
1333 			/* HANG FOREVER */;
1334 	}
1335 
1336 	return (wscons->wc_kb_polledio->cons_polledio_getchar(
1337 	    wscons->wc_kb_polledio->cons_polledio_argument));
1338 }
1339 
1340 static boolean_t
1341 wc_polled_ischar(cons_polledio_arg_t arg)
1342 {
1343 	struct wscons *wscons = (struct wscons *)arg;
1344 
1345 	if (wscons->wc_kb_polledio == NULL)
1346 		return (B_FALSE);
1347 
1348 	return (wscons->wc_kb_polledio->cons_polledio_ischar(
1349 	    wscons->wc_kb_polledio->cons_polledio_argument));
1350 }
1351 
1352 static void
1353 wc_polled_enter(cons_polledio_arg_t arg)
1354 {
1355 	struct wscons *wscons = (struct wscons *)arg;
1356 
1357 	if (wscons->wc_kb_polledio == NULL)
1358 		return;
1359 
1360 	if (wscons->wc_kb_polledio->cons_polledio_enter != NULL) {
1361 		wscons->wc_kb_polledio->cons_polledio_enter(
1362 		    wscons->wc_kb_polledio->cons_polledio_argument);
1363 	}
1364 }
1365 
1366 static void
1367 wc_polled_exit(cons_polledio_arg_t arg)
1368 {
1369 	struct wscons *wscons = (struct wscons *)arg;
1370 
1371 	if (wscons->wc_kb_polledio == NULL)
1372 		return;
1373 
1374 	if (wscons->wc_kb_polledio->cons_polledio_exit != NULL) {
1375 		wscons->wc_kb_polledio->cons_polledio_exit(
1376 		    wscons->wc_kb_polledio->cons_polledio_argument);
1377 	}
1378 }
1379 
1380 
1381 #ifdef DEBUG
1382 static void
1383 wc_dprintf(const char *fmt, ...)
1384 {
1385 	char buf[256];
1386 	va_list ap;
1387 
1388 	va_start(ap, fmt);
1389 	(void) vsprintf(buf, fmt, ap);
1390 	va_end(ap);
1391 
1392 	cmn_err(CE_WARN, "wc: %s", buf);
1393 }
1394 #endif
1395 
1396 static void
1397 update_property(struct wscons *wscons, char *name, ushort_t value)
1398 {
1399 	char data[8];
1400 
1401 	(void) snprintf(data, sizeof (data), "%u", value);
1402 	(void) ddi_prop_update_string(wscons->wc_dev, wc_dip, name, data);
1403 }
1404 
1405 /*
1406  * Gets the number of text rows and columns and the
1407  * width and height (in pixels) of the console.
1408  */
1409 static void
1410 wc_get_size(struct wscons *wscons)
1411 {
1412 	struct winsize *t = &wscons->wc_ttycommon.t_size;
1413 	ushort_t r = LOSCREENLINES, c = LOSCREENCOLS, x = 0, y = 0;
1414 
1415 	if (wscons->wc_tem != NULL)
1416 		tem_get_size(wscons->wc_tem, &r, &c, &x, &y);
1417 #ifdef _HAVE_TEM_FIRMWARE
1418 	else {
1419 		console_get_size(&r, &c, &x, &y);
1420 	}
1421 #endif /* _HAVE_TEM_FIRMWARE */
1422 
1423 	update_property(wscons, "screen-#cols",  t->ws_col = c);
1424 	update_property(wscons, "screen-#rows",  t->ws_row = r);
1425 	update_property(wscons, "screen-width",  t->ws_xpixel = x);
1426 	update_property(wscons, "screen-height", t->ws_ypixel = y);
1427 }
1428 
1429 static void
1430 wc_modechg_cb(tem_modechg_cb_arg_t arg)
1431 {
1432 	wc_get_size((struct wscons *)arg);
1433 }
1434