xref: /titanic_41/usr/src/uts/common/io/wscons.c (revision 63360950109af2ce85a962ca61f40b8782f11100)
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 2006 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 = (struct iocblk *)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 = (struct linkblk *)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 = (struct linkblk *)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 *)mp->b_rptr)->ioc_flag;
582 
583 	STRUCT_DECL(cons_getterm, wcterm);
584 	STRUCT_INIT(wcterm, flag);
585 
586 	arg = *((intptr_t *)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 = (struct iocblk *)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 **)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 ||
711 		    mp->b_cont->b_cont != NULL)
712 			goto open_fail;
713 
714 		/*
715 		 * If there's no null terminator in the string, fail.
716 		 */
717 		len = mp->b_cont->b_wptr - mp->b_cont->b_rptr;
718 		if (memchr(mp->b_cont->b_rptr, 0, len) == NULL)
719 			goto open_fail;
720 
721 		/*
722 		 * NOTE:  should eventually get default
723 		 * dimensions from a property, e.g. screen-#rows.
724 		 */
725 		iocp->ioc_error = tem_init(&wscons.wc_tem,
726 			(char *)mp->b_cont->b_rptr, iocp->ioc_cr);
727 		/*
728 		 * Of course, if the terminal emulator initialization
729 		 * failed, fail.
730 		 */
731 		if (iocp->ioc_error != 0)
732 			goto open_fail;
733 
734 		tem_register_modechg_cb(wscons.wc_tem, wc_modechg_cb,
735 			(tem_modechg_cb_arg_t)&wscons);
736 
737 		/*
738 		 * Refresh terminal size with info from terminal emulator.
739 		 */
740 		wc_get_size(&wscons);
741 
742 		/*
743 		 * ... and succeed.
744 		 */
745 		mp->b_datap->db_type = M_IOCACK;
746 
747 open_fail:
748 		qreply(q, mp);
749 		break;
750 
751 	case WC_CLOSE_FB:
752 		/*
753 		 * There's nothing that can call this, so it's not
754 		 * really implemented.
755 		 */
756 		mp->b_datap->db_type = M_IOCNAK;
757 		/*
758 		 * However, if it were implemented, it would clearly
759 		 * be root-only.
760 		 */
761 		if ((iocp->ioc_error = secpolicy_console(iocp->ioc_cr)) != 0)
762 			goto close_fail;
763 
764 		iocp->ioc_error = EINVAL;
765 
766 close_fail:
767 		qreply(q, mp);
768 		break;
769 
770 	default:
771 
772 		/*
773 		 * The only way in which "ttycommon_ioctl" can fail is
774 		 * if the "ioctl" requires a response containing data
775 		 * to be returned to the user, and no mblk could be
776 		 * allocated for the data.  No such "ioctl" alters our
777 		 * state.  Thus, we always go ahead and do any
778 		 * state-changes the "ioctl" calls for.  If we couldn't
779 		 * allocate the data, "ttycommon_ioctl" has stashed the
780 		 * "ioctl" away safely, so we just call "qbufcall" to
781 		 * request that we be called back when we stand a
782 		 * better chance of allocating the data.
783 		 */
784 		datasize = ttycommon_ioctl(&wscons.wc_ttycommon, q, mp, &error);
785 		if (datasize != 0) {
786 			if (wscons.wc_bufcallid != 0)
787 				qunbufcall(q, wscons.wc_bufcallid);
788 			wscons.wc_bufcallid = qbufcall(q, datasize, BPRI_HI,
789 			    wcreioctl, NULL);
790 			return;
791 		}
792 
793 		if (error < 0) {
794 			if (iocp->ioc_cmd == TCSBRK)
795 				error = 0;
796 			else
797 				error = EINVAL;
798 		}
799 		if (error != 0) {
800 			iocp->ioc_error = error;
801 			mp->b_datap->db_type = M_IOCNAK;
802 		}
803 		qreply(q, mp);
804 		break;
805 	}
806 }
807 
808 /*
809  * This function gets the polled I/O structures from the lower
810  * keyboard driver.  If any initialization or resource allocation
811  * needs to be done by the lower driver, it will be done when
812  * the lower driver services this message.
813  */
814 static void
815 wc_open_kb_polledio(struct wscons *wscons, queue_t *q, mblk_t *mp)
816 {
817 	mblk_t *mp2;
818 	struct iocblk *iocp;
819 
820 	DPRINTF(PRINT_L1, PRINT_MASK_ALL,
821 		("wc_open_kb_polledio: sending CONSOPENPOLLEDIO\n"));
822 
823 	mp2 = mkiocb(CONSOPENPOLLEDIO);
824 
825 	if (mp2 == NULL) {
826 		/*
827 		 * If we can't get an mblk, then wait for it.
828 		 */
829 		goto nomem;
830 	}
831 
832 	mp2->b_cont = allocb(sizeof (struct cons_polledio *), BPRI_HI);
833 
834 	if (mp2->b_cont == NULL) {
835 		/*
836 		 * If we can't get an mblk, then wait for it, and release
837 		 * the mblk that we have already allocated.
838 		 */
839 		freemsg(mp2);
840 		goto nomem;
841 	}
842 
843 	iocp = (struct iocblk *)mp2->b_rptr;
844 
845 	iocp->ioc_count = sizeof (struct cons_polledio *);
846 	mp2->b_cont->b_wptr = mp2->b_cont->b_rptr +
847 		sizeof (struct cons_polledio *);
848 
849 	wscons->wc_pending_link = mp;
850 	wscons->wc_kb_getpolledio_id = iocp->ioc_id;
851 
852 	putnext(wscons->wc_kbdqueue, mp2);
853 
854 	return;
855 
856 nomem:
857 	iocp = (struct iocblk *)mp->b_rptr;
858 	iocp->ioc_error = ENOMEM;
859 	mp->b_datap->db_type = M_IOCNAK;
860 	qreply(q, mp);
861 }
862 
863 /*
864  * This function releases the polled I/O structures from the lower
865  * keyboard driver.  If any de-initialization needs to be done, or
866  * any resources need to be released, it will be done when the lower
867  * driver services this message.
868  */
869 static void
870 wc_close_kb_polledio(struct wscons *wscons, queue_t *q, mblk_t *mp)
871 {
872 	mblk_t *mp2;
873 	struct iocblk *iocp;
874 
875 	DPRINTF(PRINT_L1, PRINT_MASK_ALL,
876 		("wc_close_kb_polledio: sending CONSCLOSEPOLLEDIO\n"));
877 
878 	mp2 = mkiocb(CONSCLOSEPOLLEDIO);
879 
880 	if (mp2 == NULL) {
881 		/*
882 		 * If we can't get an mblk, then wait for it.
883 		 */
884 		goto nomem;
885 	}
886 
887 	mp2->b_cont = allocb(sizeof (struct cons_polledio *), BPRI_HI);
888 
889 	if (mp2->b_cont == NULL) {
890 		/*
891 		 * If we can't get an mblk, then wait for it, and release
892 		 * the mblk that we have already allocated.
893 		 */
894 		freemsg(mp2);
895 
896 		goto nomem;
897 	}
898 
899 	iocp = (struct iocblk *)mp2->b_rptr;
900 
901 	iocp->ioc_count = 0;
902 
903 	wscons->wc_pending_link = mp;
904 	wscons->wc_kb_getpolledio_id = iocp->ioc_id;
905 
906 	putnext(wscons->wc_kbdqueue, mp2);
907 
908 	return;
909 
910 nomem:
911 	iocp = (struct iocblk *)mp->b_rptr;
912 	iocp->ioc_error = ENOMEM;
913 	mp->b_datap->db_type = M_IOCNAK;
914 	qreply(q, mp);
915 }
916 
917 #ifdef _HAVE_TEM_FIRMWARE
918 /* ARGSUSED */
919 static void
920 wcopoll(void *arg)
921 {
922 	queue_t *q;
923 
924 	q = wscons.wc_ttycommon.t_writeq;
925 	wscons.wc_timeoutid = 0;
926 	/* See if we can continue output */
927 	if ((wscons.wc_flags & WCS_BUSY) && wscons.wc_pendc != -1) {
928 		if (prom_mayput((char)wscons.wc_pendc) == 0) {
929 			wscons.wc_pendc = -1;
930 			wscons.wc_flags &= ~WCS_BUSY;
931 			if (!(wscons.wc_flags&(WCS_DELAY|WCS_STOPPED)))
932 				wcstart();
933 		} else
934 			wscons.wc_timeoutid = qtimeout(q, wcopoll, NULL, 1);
935 	}
936 }
937 #endif	/* _HAVE_TEM_FIRMWARE */
938 
939 /*
940  * Restart output on the console after a timeout.
941  */
942 /* ARGSUSED */
943 static void
944 wcrstrt(void *arg)
945 {
946 	ASSERT(wscons.wc_ttycommon.t_writeq != NULL);
947 	wscons.wc_flags &= ~WCS_DELAY;
948 	wcstart();
949 }
950 
951 /*
952  * Start console output
953  */
954 static void
955 wcstart(void)
956 {
957 #ifdef _HAVE_TEM_FIRMWARE
958 	int c;
959 	ssize_t cc;
960 #endif /* _HAVE_TEM_FIRMWARE */
961 	queue_t *q;
962 	mblk_t *bp;
963 	mblk_t *nbp;
964 
965 	/*
966 	 * If we're waiting for something to happen (delay timeout to
967 	 * expire, current transmission to finish, output to be
968 	 * restarted, output to finish draining), don't grab anything
969 	 * new.
970 	 */
971 	if (wscons.wc_flags & (WCS_DELAY|WCS_BUSY|WCS_STOPPED))
972 		return;
973 
974 	q = wscons.wc_ttycommon.t_writeq;
975 	/*
976 	 * assumes that we have been called by whoever holds the
977 	 * exclusionary lock on the write-side queue (protects
978 	 * wc_flags and wc_pendc).
979 	 */
980 	for (;;) {
981 		if ((bp = getq(q)) == NULL)
982 			return;	/* nothing to transmit */
983 
984 		/*
985 		 * We have a new message to work on.
986 		 * Check whether it's a delay or an ioctl (the latter
987 		 * occurs if the ioctl in question was waiting for the output
988 		 * to drain).  If it's one of those, process it immediately.
989 		 */
990 		switch (bp->b_datap->db_type) {
991 
992 		case M_DELAY:
993 			/*
994 			 * Arrange for "wcrstrt" to be called when the
995 			 * delay expires; it will turn WCS_DELAY off,
996 			 * and call "wcstart" to grab the next message.
997 			 */
998 			if (wscons.wc_timeoutid != 0)
999 				(void) quntimeout(q, wscons.wc_timeoutid);
1000 			wscons.wc_timeoutid = qtimeout(q, wcrstrt, NULL,
1001 			    (clock_t)(*(unsigned char *)bp->b_rptr + 6));
1002 			wscons.wc_flags |= WCS_DELAY;
1003 			freemsg(bp);
1004 			return;	/* wait for this to finish */
1005 
1006 		case M_IOCTL:
1007 			/*
1008 			 * This ioctl was waiting for the output ahead of
1009 			 * it to drain; obviously, it has.  Do it, and
1010 			 * then grab the next message after it.
1011 			 */
1012 			wcioctl(q, bp);
1013 			continue;
1014 		}
1015 
1016 #ifdef _HAVE_TEM_FIRMWARE
1017 		if (consmode == CONS_KFB) {
1018 #endif /* _HAVE_TEM_FIRMWARE */
1019 			if (wscons.wc_tem != NULL) {
1020 				for (nbp = bp; nbp != NULL; nbp = nbp->b_cont) {
1021 					if (nbp->b_wptr > nbp->b_rptr) {
1022 						(void) tem_write(wscons.wc_tem,
1023 						    nbp->b_rptr,
1024 						    nbp->b_wptr - nbp->b_rptr,
1025 						    kcred);
1026 					}
1027 				}
1028 				freemsg(bp);
1029 			}
1030 #ifdef _HAVE_TEM_FIRMWARE
1031 			continue;
1032 		}
1033 
1034 		/* consmode = CONS_FW */
1035 		if ((cc = bp->b_wptr - bp->b_rptr) == 0) {
1036 			freemsg(bp);
1037 			continue;
1038 		}
1039 		/*
1040 		 * Direct output to the frame buffer if this device
1041 		 * is not the "hardware" console.
1042 		 */
1043 		if (wscons.wc_defer_output) {
1044 			/*
1045 			 * Never do output here;
1046 			 * it takes forever.
1047 			 */
1048 			wscons.wc_flags |= WCS_BUSY;
1049 			wscons.wc_pendc = -1;
1050 			(void) putbq(q, bp);
1051 			if (q->q_count > 128) { /* do it soon */
1052 				softcall(wconsout, NULL);
1053 			} else {	/* wait a bit */
1054 				if (wscons.wc_timeoutid != 0)
1055 					(void) quntimeout(q,
1056 					    wscons.wc_timeoutid);
1057 				wscons.wc_timeoutid = qtimeout(q, wconsout,
1058 				    NULL, hz / 30);
1059 			}
1060 			return;
1061 		}
1062 		for (;;) {
1063 			c = *bp->b_rptr++;
1064 			cc--;
1065 			if (prom_mayput((char)c) != 0) {
1066 				wscons.wc_flags |= WCS_BUSY;
1067 				wscons.wc_pendc = c;
1068 				if (wscons.wc_timeoutid != 0)
1069 					(void) quntimeout(q,
1070 					    wscons.wc_timeoutid);
1071 				wscons.wc_timeoutid = qtimeout(q, wcopoll,
1072 				    NULL, 1);
1073 				if (bp != NULL)
1074 					/* not done with this message yet */
1075 					(void) putbq(q, bp);
1076 				return;
1077 			}
1078 			while (cc <= 0) {
1079 				nbp = bp;
1080 				bp = bp->b_cont;
1081 				freeb(nbp);
1082 				if (bp == NULL)
1083 					return;
1084 				cc = bp->b_wptr - bp->b_rptr;
1085 			}
1086 		}
1087 #endif /* _HAVE_TEM_FIRMWARE */
1088 	}
1089 }
1090 
1091 #ifdef _HAVE_TEM_FIRMWARE
1092 /*
1093  * Output to frame buffer console.
1094  * It takes a long time to scroll.
1095  */
1096 /* ARGSUSED */
1097 static void
1098 wconsout(void *dummy)
1099 {
1100 	uchar_t *cp;
1101 	ssize_t cc;
1102 	queue_t *q;
1103 	mblk_t *bp;
1104 	mblk_t *nbp;
1105 	char *current_position;
1106 	ssize_t bytes_left;
1107 
1108 	if ((q = wscons.wc_ttycommon.t_writeq) == NULL) {
1109 		return;	/* not attached to a stream */
1110 	}
1111 
1112 	/*
1113 	 * Set up to copy up to MAXHIWAT bytes.
1114 	 */
1115 	current_position = &obuf[0];
1116 	bytes_left = MAXHIWAT;
1117 	while ((bp = getq(q)) != NULL) {
1118 		if (bp->b_datap->db_type == M_IOCTL) {
1119 			/*
1120 			 * This ioctl was waiting for the output ahead of
1121 			 * it to drain; obviously, it has.  Put it back
1122 			 * so that "wcstart" can handle it, and transmit
1123 			 * what we've got.
1124 			 */
1125 			(void) putbq(q, bp);
1126 			goto transmit;
1127 		}
1128 
1129 		do {
1130 			cp = bp->b_rptr;
1131 			cc = bp->b_wptr - cp;
1132 			while (cc != 0) {
1133 				if (bytes_left == 0) {
1134 					/*
1135 					 * Out of buffer space; put this
1136 					 * buffer back on the queue, and
1137 					 * transmit what we have.
1138 					 */
1139 					bp->b_rptr = cp;
1140 					(void) putbq(q, bp);
1141 					goto transmit;
1142 				}
1143 				*current_position++ = *cp++;
1144 				cc--;
1145 				bytes_left--;
1146 			}
1147 			nbp = bp;
1148 			bp = bp->b_cont;
1149 			freeb(nbp);
1150 		} while (bp != NULL);
1151 	}
1152 
1153 transmit:
1154 	if ((cc = MAXHIWAT - bytes_left) != 0)
1155 		console_puts(obuf, cc);
1156 
1157 	wscons.wc_flags &= ~WCS_BUSY;
1158 	wcstart();
1159 }
1160 #endif /* _HAVE_TEM_FIRMWARE */
1161 
1162 /*
1163  * Put procedure for lower read queue.
1164  * Pass everything up to queue above "upper half".
1165  */
1166 static int
1167 wclrput(queue_t *q, mblk_t *mp)
1168 {
1169 	queue_t *upq;
1170 	struct iocblk *iocp;
1171 
1172 	DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1173 		("wclrput: wclrput type = 0x%x\n", mp->b_datap->db_type));
1174 
1175 	switch (mp->b_datap->db_type) {
1176 
1177 	case M_FLUSH:
1178 		if (*mp->b_rptr == FLUSHW || *mp->b_rptr == FLUSHRW) {
1179 			/*
1180 			 * Flush our write queue.
1181 			 */
1182 			/* XXX doesn't flush M_DELAY */
1183 			flushq(WR(q), FLUSHDATA);
1184 			*mp->b_rptr = FLUSHR;	/* it has been flushed */
1185 		}
1186 		if (*mp->b_rptr == FLUSHR || *mp->b_rptr == FLUSHRW) {
1187 			flushq(q, FLUSHDATA);
1188 			*mp->b_rptr = FLUSHW;	/* it has been flushed */
1189 			qreply(q, mp);	/* give the read queues a crack at it */
1190 		} else
1191 			freemsg(mp);
1192 		break;
1193 
1194 	case M_DATA:
1195 		if ((upq = wscons.wc_ttycommon.t_readq) != NULL) {
1196 			if (!canput(upq->q_next)) {
1197 				ttycommon_qfull(&wscons.wc_ttycommon, upq);
1198 				wcstart();
1199 				freemsg(mp);
1200 			} else
1201 				putnext(upq, mp);
1202 		} else
1203 			freemsg(mp);
1204 		break;
1205 
1206 	case M_IOCACK:
1207 	case M_IOCNAK:
1208 		iocp = (struct iocblk *)mp->b_rptr;
1209 		if (wscons.wc_pending_link != NULL &&
1210 		    iocp->ioc_id == wscons.wc_kb_getpolledio_id) {
1211 			switch (mp->b_datap->db_type) {
1212 
1213 			case M_IOCACK:
1214 				switch (iocp->ioc_cmd) {
1215 
1216 
1217 				case CONSOPENPOLLEDIO:
1218 					DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1219 						("wclrput: "
1220 						"ACK CONSOPENPOLLEDIO\n"));
1221 					wscons.wc_kb_polledio =
1222 					    *(struct cons_polledio **)
1223 					    mp->b_cont->b_rptr;
1224 					wscons.wc_polledio.
1225 					    cons_polledio_getchar =
1226 						wc_polled_getchar;
1227 					wscons.wc_polledio.
1228 					    cons_polledio_ischar =
1229 						wc_polled_ischar;
1230 					break;
1231 
1232 				case CONSCLOSEPOLLEDIO:
1233 					DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1234 						("wclrput: "
1235 						"ACK CONSCLOSEPOLLEDIO\n"));
1236 					wscons.wc_kb_polledio = NULL;
1237 					wscons.wc_kbdqueue = NULL;
1238 					wscons.wc_polledio.
1239 					    cons_polledio_getchar = NULL;
1240 					wscons.wc_polledio.
1241 					    cons_polledio_ischar = NULL;
1242 					break;
1243 				default:
1244 					DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1245 						("wclrput: "
1246 						"ACK UNKNOWN\n"));
1247 				}
1248 
1249 				break;
1250 			case M_IOCNAK:
1251 				/*
1252 				 * Keyboard may or may not support polled I/O.
1253 				 * This ioctl may have been rejected because
1254 				 * we only have the wc->conskbd chain built,
1255 				 * and the keyboard driver has not been linked
1256 				 * underneath conskbd yet.
1257 				 */
1258 				DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1259 					("wclrput: NAK\n"));
1260 
1261 				switch (iocp->ioc_cmd) {
1262 
1263 				case CONSCLOSEPOLLEDIO:
1264 					wscons.wc_kb_polledio = NULL;
1265 					wscons.wc_kbdqueue = NULL;
1266 					wscons.wc_polledio.
1267 					    cons_polledio_getchar = NULL;
1268 					wscons.wc_polledio.
1269 					    cons_polledio_ischar = NULL;
1270 					break;
1271 				}
1272 				break;
1273 			}
1274 
1275 			/*
1276 			 * Discard the response, replace it with the
1277 			 * pending response to the I_PLINK, then let it
1278 			 * flow upward.
1279 			 */
1280 			freemsg(mp);
1281 			mp = wscons.wc_pending_link;
1282 			wscons.wc_pending_link = NULL;
1283 			wscons.wc_kb_getpolledio_id = 0;
1284 		}
1285 		/* FALLTHROUGH */
1286 
1287 	default:	/* inc M_ERROR, M_HANGUP, M_IOCACK, M_IOCNAK, ... */
1288 		DPRINTF(PRINT_L1, PRINT_MASK_ALL,
1289 			("wclrput: Message DISCARDED\n"));
1290 		if ((upq = wscons.wc_ttycommon.t_readq) != NULL) {
1291 			putnext(upq, mp);
1292 		} else {
1293 			freemsg(mp);
1294 		}
1295 		break;
1296 	}
1297 
1298 	return (0);
1299 }
1300 
1301 /*
1302  * These are for systems without OBP, and for devices that cannot be
1303  * shared between Solaris and the OBP.
1304  */
1305 static void
1306 wc_polled_putchar(cons_polledio_arg_t arg, unsigned char c)
1307 {
1308 	if (c == '\n')
1309 		wc_polled_putchar(arg, '\r');
1310 
1311 	if (wscons.wc_tem == NULL) {
1312 		/*
1313 		 * We have no terminal emulator configured.  We have no
1314 		 * recourse but to drop the output on the floor.
1315 		 */
1316 		return;
1317 	}
1318 
1319 	tem_polled_write(wscons.wc_tem, &c, 1);
1320 }
1321 
1322 /*
1323  * These are for systems without OBP, and for devices that cannot be
1324  * shared between Solaris and the OBP.
1325  */
1326 static int
1327 wc_polled_getchar(cons_polledio_arg_t arg)
1328 {
1329 	struct wscons *wscons = (struct wscons *)arg;
1330 
1331 	if (wscons->wc_kb_polledio == NULL) {
1332 		prom_printf("wscons:  getchar with no keyboard support");
1333 		prom_printf("Halted...");
1334 		for (;;)
1335 			/* HANG FOREVER */;
1336 	}
1337 
1338 	return (wscons->wc_kb_polledio->cons_polledio_getchar(
1339 	    wscons->wc_kb_polledio->cons_polledio_argument));
1340 }
1341 
1342 static boolean_t
1343 wc_polled_ischar(cons_polledio_arg_t arg)
1344 {
1345 	struct wscons *wscons = (struct wscons *)arg;
1346 
1347 	if (wscons->wc_kb_polledio == NULL)
1348 		return (B_FALSE);
1349 
1350 	return (wscons->wc_kb_polledio->cons_polledio_ischar(
1351 	    wscons->wc_kb_polledio->cons_polledio_argument));
1352 }
1353 
1354 static void
1355 wc_polled_enter(cons_polledio_arg_t arg)
1356 {
1357 	struct wscons *wscons = (struct wscons *)arg;
1358 
1359 	if (wscons->wc_kb_polledio == NULL)
1360 		return;
1361 
1362 	if (wscons->wc_kb_polledio->cons_polledio_enter != NULL) {
1363 		wscons->wc_kb_polledio->cons_polledio_enter(
1364 		    wscons->wc_kb_polledio->cons_polledio_argument);
1365 	}
1366 }
1367 
1368 static void
1369 wc_polled_exit(cons_polledio_arg_t arg)
1370 {
1371 	struct wscons *wscons = (struct wscons *)arg;
1372 
1373 	if (wscons->wc_kb_polledio == NULL)
1374 		return;
1375 
1376 	if (wscons->wc_kb_polledio->cons_polledio_exit != NULL) {
1377 		wscons->wc_kb_polledio->cons_polledio_exit(
1378 		    wscons->wc_kb_polledio->cons_polledio_argument);
1379 	}
1380 }
1381 
1382 
1383 #ifdef DEBUG
1384 static void
1385 wc_dprintf(const char *fmt, ...)
1386 {
1387 	char buf[256];
1388 	va_list ap;
1389 
1390 	va_start(ap, fmt);
1391 	(void) vsprintf(buf, fmt, ap);
1392 	va_end(ap);
1393 
1394 	cmn_err(CE_WARN, "wc: %s", buf);
1395 }
1396 #endif
1397 
1398 static void
1399 update_property(struct wscons *wscons, char *name, ushort_t value)
1400 {
1401 	char data[8];
1402 
1403 	(void) snprintf(data, sizeof (data), "%u", value);
1404 	(void) ddi_prop_update_string(wscons->wc_dev, wc_dip, name, data);
1405 }
1406 
1407 /*
1408  * Gets the number of text rows and columns and the
1409  * width and height (in pixels) of the console.
1410  */
1411 static void
1412 wc_get_size(struct wscons *wscons)
1413 {
1414 	struct winsize *t = &wscons->wc_ttycommon.t_size;
1415 	ushort_t r = LOSCREENLINES, c = LOSCREENCOLS, x = 0, y = 0;
1416 
1417 	if (wscons->wc_tem != NULL)
1418 		tem_get_size(wscons->wc_tem, &r, &c, &x, &y);
1419 #ifdef _HAVE_TEM_FIRMWARE
1420 	else {
1421 		console_get_size(&r, &c, &x, &y);
1422 	}
1423 #endif /* _HAVE_TEM_FIRMWARE */
1424 
1425 	update_property(wscons, "screen-#cols",  t->ws_col = c);
1426 	update_property(wscons, "screen-#rows",  t->ws_row = r);
1427 	update_property(wscons, "screen-width",  t->ws_xpixel = x);
1428 	update_property(wscons, "screen-height", t->ws_ypixel = y);
1429 }
1430 
1431 static void
1432 wc_modechg_cb(tem_modechg_cb_arg_t arg)
1433 {
1434 	wc_get_size((struct wscons *)arg);
1435 }
1436