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