xref: /illumos-gate/usr/src/uts/common/io/tem.c (revision 3db86aab)
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  * ANSI terminal emulator module; parse ANSI X3.64 escape sequences and
31  * the like.
32  */
33 
34 #include <sys/types.h>
35 #include <sys/file.h>
36 #include <sys/conf.h>
37 #include <sys/errno.h>
38 #include <sys/open.h>
39 #include <sys/cred.h>
40 #include <sys/kmem.h>
41 #include <sys/ascii.h>
42 #include <sys/consdev.h>
43 #include <sys/font.h>
44 #include <sys/fbio.h>
45 #include <sys/conf.h>
46 #include <sys/modctl.h>
47 #include <sys/strsubr.h>
48 #include <sys/stat.h>
49 #include <sys/visual_io.h>
50 #include <sys/mutex.h>
51 #include <sys/param.h>
52 #include <sys/debug.h>
53 #include <sys/cmn_err.h>
54 #include <sys/console.h>
55 #include <sys/ddi.h>
56 #include <sys/sunddi.h>
57 #include <sys/sunldi.h>
58 #include <sys/tem_impl.h>
59 #include <sys/tem.h>
60 #ifdef _HAVE_TEM_FIRMWARE
61 #include <sys/promif.h>
62 #endif /* _HAVE_TEM_FIRMWARE */
63 #include <sys/consconfig_dacf.h>
64 
65 /* Terminal emulator functions */
66 static int	tem_setup_terminal(struct vis_devinit *, tem_t *,
67 			size_t, size_t);
68 static void	tem_modechange_callback(tem_t *, struct vis_devinit *);
69 static void	tem_free(tem_t *);
70 
71 static int	tem_adjust_row(tem_t *, int, cred_t *);
72 
73 /*
74  * Globals
75  */
76 ldi_ident_t	term_li = NULL;
77 
78 
79 extern struct mod_ops mod_miscops;
80 
81 static struct modlmisc	modlmisc = {
82 	&mod_miscops,	/* modops */
83 	"ANSI Terminal Emulator", /* name */
84 };
85 
86 static struct modlinkage modlinkage = {
87 	MODREV_1, (void *)&modlmisc, NULL
88 };
89 
90 int
91 _init(void)
92 {
93 	int ret;
94 	ret = mod_install(&modlinkage);
95 	if (ret != 0)
96 		return (ret);
97 	ret = ldi_ident_from_mod(&modlinkage, &term_li);
98 	if (ret != 0) {
99 		(void) mod_remove(&modlinkage);
100 		return (ret);
101 	}
102 	return (0);
103 }
104 
105 int
106 _fini()
107 {
108 	int ret;
109 
110 	ret = mod_remove(&modlinkage);
111 	if (ret == 0) {
112 		ldi_ident_release(term_li);
113 		term_li = NULL;
114 	}
115 	return (ret);
116 }
117 
118 int
119 _info(struct modinfo *modinfop)
120 {
121 	return (mod_info(&modlinkage, modinfop));
122 }
123 
124 int
125 tem_fini(tem_t *tem)
126 {
127 	int lyr_rval;
128 
129 	mutex_enter(&tem->lock);
130 
131 	ASSERT(tem->hdl != NULL);
132 
133 	/*
134 	 * Allow layered on driver to clean up console private
135 	 * data.
136 	 */
137 	(void) ldi_ioctl(tem->hdl, VIS_DEVFINI,
138 	    0, FKIOCTL, kcred, &lyr_rval);
139 
140 	/*
141 	 * Close layered on driver
142 	 */
143 	(void) ldi_close(tem->hdl, NULL, kcred);
144 	tem->hdl = NULL;
145 
146 	mutex_exit(&tem->lock);
147 
148 	tem_free(tem);
149 
150 	return (0);
151 }
152 
153 static int
154 tem_init_failed(tem_t *tem, cred_t *credp, boolean_t finish_ioctl)
155 {
156 	int	lyr_rval;
157 
158 	if (finish_ioctl)
159 		(void) ldi_ioctl(tem->hdl, VIS_DEVFINI, 0, FWRITE|FKIOCTL,
160 		    credp, &lyr_rval);
161 
162 	(void) ldi_close(tem->hdl, NULL, credp);
163 	tem_free(tem);
164 	return (ENXIO);
165 }
166 
167 static void
168 tem_free_state(struct tem_state *tems)
169 {
170 	ASSERT(tems != NULL);
171 
172 	if (tems->a_outbuf != NULL)
173 		kmem_free(tems->a_outbuf,
174 		    tems->a_c_dimension.width);
175 	if (tems->a_blank_line != NULL)
176 		kmem_free(tems->a_blank_line,
177 		    tems->a_c_dimension.width);
178 	if (tems->a_pix_data != NULL)
179 		kmem_free(tems->a_pix_data,
180 		    tems->a_pix_data_size);
181 	kmem_free(tems, sizeof (struct tem_state));
182 }
183 
184 static void
185 tem_free(tem_t *tem)
186 {
187 	ASSERT(tem != NULL);
188 
189 	if (tem->state != NULL)
190 		tem_free_state(tem->state);
191 
192 	kmem_free(tem, sizeof (struct tem));
193 }
194 
195 /*
196  * This is the main entry point to the module.  It handles output requests
197  * during normal system operation, when (e.g.) mutexes are available.
198  */
199 void
200 tem_write(tem_t *tem, uchar_t *buf, ssize_t len, cred_t *credp)
201 {
202 	mutex_enter(&tem->lock);
203 
204 	ASSERT(tem->hdl != NULL);
205 
206 	tem_check_first_time(tem, credp, CALLED_FROM_NORMAL);
207 	tem_terminal_emulate(tem, buf, len, credp, CALLED_FROM_NORMAL);
208 
209 	mutex_exit(&tem->lock);
210 }
211 
212 int
213 tem_init(tem_t **ptem, char *pathname, cred_t *credp)
214 {
215 	struct vis_devinit devinit;
216 	tem_t *tem;
217 	size_t height = 0;
218 	size_t width = 0;
219 	uint32_t row = 0;
220 	uint32_t col = 0;
221 	char	*pathbuf;
222 	int	err = 0;
223 	int	lyr_rval;
224 
225 	tem = kmem_zalloc(sizeof (struct tem), KM_SLEEP);
226 
227 	mutex_init(&tem->lock, (char *)NULL, MUTEX_DRIVER, NULL);
228 
229 #ifdef	_HAVE_TEM_FIRMWARE
230 	tem->cons_wrtvec = tem_write;
231 #endif /* _HAVE_TEM_FIRMWARE */
232 
233 	/*
234 	 * Open the layered device using the devfs physical device name
235 	 * after adding the /devices prefix.
236 	 */
237 	pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
238 	(void) strcpy(pathbuf, "/devices");
239 	if (i_ddi_prompath_to_devfspath(pathname,
240 	    pathbuf + strlen("/devices")) != DDI_SUCCESS) {
241 		cmn_err(CE_WARN, "terminal emulator: Path conversion error");
242 		kmem_free(pathbuf, MAXPATHLEN);
243 		tem_free(tem);
244 		return (ENXIO);
245 	}
246 	if (ldi_open_by_name(pathbuf, FWRITE, credp, &tem->hdl, term_li) != 0) {
247 		cmn_err(CE_WARN, "terminal emulator: Device path open error");
248 		kmem_free(pathbuf, MAXPATHLEN);
249 		tem_free(tem);
250 		return (ENXIO);
251 	}
252 	kmem_free(pathbuf, MAXPATHLEN);
253 
254 	devinit.modechg_cb  = (vis_modechg_cb_t)tem_modechange_callback;
255 	devinit.modechg_arg = (struct vis_modechg_arg *)tem;
256 
257 	/*
258 	 * Initialize the console and get the device parameters
259 	 */
260 	if ((err = ldi_ioctl(tem->hdl, VIS_DEVINIT,
261 	    (intptr_t)&devinit, FWRITE|FKIOCTL, credp, &lyr_rval)) != 0) {
262 		cmn_err(CE_WARN, "terminal emulator: Compatible fb not found");
263 		return (tem_init_failed(tem, credp, B_FALSE));
264 	}
265 
266 	/* Make sure the fb driver and terminal emulator versions match */
267 	if (devinit.version != VIS_CONS_REV) {
268 		cmn_err(CE_WARN,
269 		    "terminal emulator: VIS_CONS_REV %d (see sys/visual_io.h) "
270 		    "of console fb driver not supported", devinit.version);
271 		return (tem_init_failed(tem, credp, B_TRUE));
272 	}
273 
274 	if ((tem->fb_polledio = devinit.polledio) == NULL) {
275 		cmn_err(CE_WARN, "terminal emulator: fb doesn't support polled "
276 		    "I/O");
277 		return (tem_init_failed(tem, credp, B_TRUE));
278 	}
279 
280 	/* other sanity checks */
281 	if (!((devinit.depth == 4) || (devinit.depth == 8) ||
282 		(devinit.depth == 24) || (devinit.depth == 32))) {
283 		cmn_err(CE_WARN, "terminal emulator: unsupported depth");
284 		return (tem_init_failed(tem, credp, B_TRUE));
285 	}
286 
287 	if ((devinit.mode != VIS_TEXT) && (devinit.mode != VIS_PIXEL)) {
288 		cmn_err(CE_WARN, "terminal emulator: unsupported mode");
289 		return (tem_init_failed(tem, credp, B_TRUE));
290 	}
291 
292 	if ((devinit.mode == VIS_PIXEL) && plat_stdout_is_framebuffer()) {
293 		plat_tem_get_prom_size(&height, &width);
294 	}
295 
296 	/*
297 	 * Initialize the terminal emulator
298 	 */
299 	mutex_enter(&tem->lock);
300 	if ((err = tem_setup_terminal(&devinit, tem, height, width)) != 0) {
301 		cmn_err(CE_WARN, "terminal emulator: Init failed");
302 		(void) ldi_ioctl(tem->hdl, VIS_DEVFINI, 0, FWRITE|FKIOCTL,
303 		    credp, &lyr_rval);
304 		(void) ldi_close(tem->hdl, NULL, credp);
305 		mutex_exit(&tem->lock);
306 		tem_free(tem);
307 		return (err);
308 	}
309 
310 	/*
311 	 * On SPARC don't clear the screen if the console is the framebuffer.
312 	 * Otherwise it needs to be cleared to get rid of junk that may be
313 	 * in frameuffer memory, since the screen isn't cleared when
314 	 * boot messages are directed elsewhere.
315 	 */
316 	if (devinit.mode == VIS_TEXT) {
317 		/*
318 		 * The old getting current cursor position code, which
319 		 * is not needed here, has been in tem_write/tem_polled_write.
320 		 */
321 		tem_reset_display(tem, credp, CALLED_FROM_NORMAL, 0);
322 	} else if (plat_stdout_is_framebuffer()) {
323 		ASSERT(devinit.mode == VIS_PIXEL);
324 		plat_tem_hide_prom_cursor();
325 		tem_reset_display(tem, credp, CALLED_FROM_NORMAL, 0);
326 
327 		/*
328 		 * We are getting the current cursor position in pixel
329 		 * mode so that we don't over-write the console output
330 		 * during boot.
331 		 */
332 		plat_tem_get_prom_pos(&row, &col);
333 
334 		/*
335 		 * Adjust the row if necessary when the font of our
336 		 * kernel console tem is different with that of prom
337 		 * tem.
338 		 */
339 		row = tem_adjust_row(tem, row, credp);
340 
341 		/* first line of our kernel console output */
342 		tem->state->first_line = row + 1;
343 
344 		/* re-set and align cusror position */
345 		tem->state->a_c_cursor.row = row;
346 		tem->state->a_c_cursor.col = 0;
347 		tem_align_cursor(tem);
348 	} else {
349 		tem_reset_display(tem, credp, CALLED_FROM_NORMAL, 1);
350 	}
351 
352 #ifdef _HAVE_TEM_FIRMWARE
353 	if (plat_stdout_is_framebuffer()) {
354 		/*
355 		 * Drivers in the console stream may emit additional
356 		 * messages before we are ready. This causes text
357 		 * overwrite on the screen. So we set the redirection
358 		 * here. It is safe because the ioctl in consconfig_dacf
359 		 * will succeed and consmode will be set to CONS_KFB.
360 		 */
361 		prom_set_stdout_redirect(console_prom_write_cb,
362 		    (promif_redir_arg_t)tem);
363 
364 	}
365 #endif /* _HAVE_TEM_FIRMWARE */
366 
367 	mutex_exit(&tem->lock);
368 	*ptem = tem; /* Return tem to caller only upon success */
369 	return (0);
370 }
371 
372 /*
373  * This is a callback function that we register with the frame
374  * buffer driver layered underneath.  It gets invoked from
375  * the underlying frame buffer driver to reconfigure the terminal
376  * emulator to a new screen size and depth in conjunction with
377  * framebuffer videomode changes.
378  */
379 void
380 tem_modechange_callback(tem_t *tem, struct vis_devinit *devinit)
381 {
382 	mutex_enter(&tem->lock);
383 
384 	ASSERT(tem->hdl != NULL);
385 
386 	(void) tem_setup_terminal(devinit, tem,
387 	    tem->state->a_c_dimension.height,
388 	    tem->state->a_c_dimension.width);
389 
390 	tem_reset_display(tem, kcred, CALLED_FROM_NORMAL, 1);
391 
392 	mutex_exit(&tem->lock);
393 
394 	if (tem->modechg_cb != NULL)
395 		tem->modechg_cb(tem->modechg_arg);
396 }
397 
398 static int
399 tem_setup_terminal(
400 	struct vis_devinit *devinit,
401 	tem_t *tem,
402 	size_t height, size_t width)
403 {
404 	int i;
405 	struct tem_state *new_state, *prev_state;
406 
407 	ASSERT(MUTEX_HELD(&tem->lock));
408 
409 	prev_state = tem->state;
410 
411 	new_state = kmem_zalloc(sizeof (struct tem_state), KM_SLEEP);
412 
413 	new_state->a_pdepth = devinit->depth;
414 	new_state->display_mode = devinit->mode;
415 	new_state->linebytes = devinit->linebytes;
416 
417 	switch (devinit->mode) {
418 	case VIS_TEXT:
419 		new_state->a_p_dimension.width  = 0;
420 		new_state->a_p_dimension.height = 0;
421 		new_state->a_c_dimension.width	= devinit->width;
422 		new_state->a_c_dimension.height = devinit->height;
423 
424 		new_state->in_fp.f_display = tem_text_display;
425 		new_state->in_fp.f_copy = tem_text_copy;
426 		new_state->in_fp.f_cursor = tem_text_cursor;
427 		new_state->in_fp.f_cls = tem_text_cls;
428 		new_state->in_fp.f_bit2pix = NULL;
429 
430 		new_state->a_blank_line =
431 			kmem_alloc(new_state->a_c_dimension.width, KM_SLEEP);
432 
433 		for (i = 0; i < new_state->a_c_dimension.width; i++)
434 			new_state->a_blank_line[i] = ' ';
435 
436 		break;
437 	case VIS_PIXEL:
438 
439 		/*
440 		 * First check to see if the user has specified a screen size.
441 		 * If so, use those values.  Else use 34x80 as the default.
442 		 */
443 		if (width == 0) {
444 			width = TEM_DEFAULT_COLS;
445 			height = TEM_DEFAULT_ROWS;
446 		}
447 		new_state->a_c_dimension.height = height;
448 		new_state->a_c_dimension.width = width;
449 
450 		new_state->a_p_dimension.height = devinit->height;
451 		new_state->a_p_dimension.width = devinit->width;
452 
453 		new_state->in_fp.f_display = tem_pix_display;
454 		new_state->in_fp.f_copy = tem_pix_copy;
455 		new_state->in_fp.f_cursor = tem_pix_cursor;
456 		new_state->in_fp.f_cls = tem_pix_cls;
457 
458 		new_state->a_blank_line = NULL;
459 
460 		/*
461 		 * set_font() will select a appropriate sized font for
462 		 * the number of rows and columns selected.  If we don't
463 		 * have a font that will fit, then it will use the
464 		 * default builtin font and adjust the rows and columns
465 		 * to fit on the screen.
466 		 */
467 		set_font(&new_state->a_font,
468 		    &new_state->a_c_dimension.height,
469 		    &new_state->a_c_dimension.width,
470 		    new_state->a_p_dimension.height,
471 		    new_state->a_p_dimension.width);
472 
473 		new_state->a_p_offset.y =
474 			(new_state->a_p_dimension.height -
475 			(new_state->a_c_dimension.height *
476 			new_state->a_font.height)) / 2;
477 
478 		new_state->a_p_offset.x =
479 			(new_state->a_p_dimension.width -
480 			(new_state->a_c_dimension.width *
481 			new_state->a_font.width)) / 2;
482 
483 		switch (devinit->depth) {
484 		case 4:
485 			new_state->in_fp.f_bit2pix = bit_to_pix4;
486 			new_state->a_pix_data_size =
487 				(((new_state->a_font.width * 4) +
488 				NBBY - 1) / NBBY) * new_state->a_font.height;
489 			break;
490 		case 8:
491 			new_state->in_fp.f_bit2pix = bit_to_pix8;
492 			new_state->a_pix_data_size =
493 				new_state->a_font.width *
494 				new_state->a_font.height;
495 			break;
496 		case 24:
497 		case 32:
498 			new_state->in_fp.f_bit2pix = bit_to_pix24;
499 			new_state->a_pix_data_size =
500 				new_state->a_font.width *
501 				new_state->a_font.height;
502 			new_state->a_pix_data_size *= 4;
503 			break;
504 		}
505 
506 		new_state->a_pix_data =
507 			kmem_alloc(new_state->a_pix_data_size, KM_SLEEP);
508 
509 		break;
510 
511 	default:
512 		/*
513 		 * The layered fb driver conveyed an unrecognized rendering
514 		 * mode.  We cannot proceed with tem initialization.
515 		 */
516 		kmem_free(new_state, sizeof (struct tem_state));
517 		return (ENXIO);
518 	}
519 
520 	new_state->a_outbuf =
521 		kmem_alloc(new_state->a_c_dimension.width, KM_SLEEP);
522 
523 	/*
524 	 * Change state atomically so that polled I/O requests
525 	 * can be safely and reliably serviced anytime after the terminal
526 	 * emulator is originally initialized and the console mode has been
527 	 * switched over from the PROM, even while a videomode change
528 	 * callback is being processed.
529 	 */
530 	tem->state = new_state;
531 
532 	if (prev_state != NULL)
533 		tem_free_state(prev_state);
534 
535 	return (0);
536 }
537 
538 /*
539  * This function is used to display a rectangular blit of data
540  * of a given size and location via the underlying framebuffer driver.
541  * The blit can be as small as a pixel or as large as the screen.
542  */
543 void
544 tem_display_layered(
545 	tem_t *tem,
546 	struct vis_consdisplay *pda,
547 	cred_t *credp)
548 {
549 	int rval;
550 
551 	(void) ldi_ioctl(tem->hdl, VIS_CONSDISPLAY,
552 	    (intptr_t)pda, FKIOCTL, credp, &rval);
553 }
554 
555 /*
556  * This function is used to invoke a block copy operation in the
557  * underlying framebuffer driver.  Rectangle copies are how scrolling
558  * is implemented, as well as horizontal text shifting escape seqs.
559  * such as from vi when deleting characters and words.
560  */
561 void
562 tem_copy_layered(
563 	tem_t *tem,
564 	struct vis_conscopy *pma,
565 	cred_t *credp)
566 {
567 	int rval;
568 
569 	(void) ldi_ioctl(tem->hdl, VIS_CONSCOPY,
570 	    (intptr_t)pma, FKIOCTL, credp, &rval);
571 }
572 
573 /*
574  * This function is used to show or hide a rectangluar monochrom
575  * pixel inverting, text block cursor via the underlying framebuffer.
576  */
577 void
578 tem_cursor_layered(
579 	tem_t *tem,
580 	struct vis_conscursor *pca,
581 	cred_t *credp)
582 {
583 	int rval;
584 
585 	(void) ldi_ioctl(tem->hdl, VIS_CONSCURSOR,
586 	    (intptr_t)pca, FKIOCTL, credp, &rval);
587 }
588 
589 void
590 tem_reset_colormap(
591 	tem_t *tem,
592 	cred_t *credp,
593 	enum called_from called_from)
594 {
595 	struct vis_cmap cm;
596 	int rval;
597 
598 	if (called_from == CALLED_FROM_STANDALONE)
599 		return;
600 
601 	switch (tem->state->a_pdepth) {
602 	case 8:
603 		cm.index = 0;
604 		cm.count = 16;
605 		cm.red   = cmap4_to_24.red;   /* 8-bits (1/3 of TrueColor 24) */
606 		cm.blue  = cmap4_to_24.blue;  /* 8-bits (1/3 of TrueColor 24) */
607 		cm.green = cmap4_to_24.green; /* 8-bits (1/3 of TrueColor 24) */
608 		(void) ldi_ioctl(tem->hdl, VIS_PUTCMAP, (intptr_t)&cm,
609 		    FKIOCTL, credp, &rval);
610 		break;
611 	}
612 }
613 
614 void
615 tem_get_size(tem_t *tem, ushort_t *r, ushort_t *c,
616 	ushort_t *x, ushort_t *y)
617 {
618 	*r = (ushort_t)tem->state->a_c_dimension.height;
619 	*c = (ushort_t)tem->state->a_c_dimension.width;
620 	*x = (ushort_t)tem->state->a_p_dimension.width;
621 	*y = (ushort_t)tem->state->a_p_dimension.height;
622 }
623 
624 void
625 tem_register_modechg_cb(tem_t *tem, tem_modechg_cb_t func,
626 	tem_modechg_cb_arg_t arg)
627 {
628 	tem->modechg_cb = func;
629 	tem->modechg_arg = arg;
630 }
631 
632 /*
633  * This function is to scroll up the OBP output, which has
634  * different screen height and width with our kernel console.
635  */
636 static void
637 tem_prom_scroll_up(struct tem *tem, int nrows, cred_t *credp)
638 {
639 	struct tem_state	*tems = tem->state;
640 	struct vis_conscopy	ma;
641 	int	ncols, width;
642 
643 	/* copy */
644 	ma.s_row = nrows * tems->a_font.height;
645 	ma.e_row = tems->a_p_dimension.height - 1;
646 	ma.t_row = 0;
647 
648 	ma.s_col = 0;
649 	ma.e_col = tems->a_p_dimension.width - 1;
650 	ma.t_col = 0;
651 
652 	tem_copy(tem, &ma, credp, CALLED_FROM_NORMAL);
653 
654 	/* clear */
655 	width = tems->a_font.width;
656 	ncols = (tems->a_p_dimension.width +
657 	    (width - 1))/ width;
658 
659 	tem_pix_cls_range(tem,
660 	    0, nrows, tems->a_p_offset.y,
661 	    0, ncols, 0,
662 	    B_TRUE, credp, CALLED_FROM_NORMAL);
663 }
664 
665 #define	PROM_DEFAULT_FONT_HEIGHT	22
666 #define	PROM_DEFAULT_WINDOW_TOP	0x8a
667 
668 /*
669  * This function is to compute the starting row of the console, according to
670  * PROM cursor's position. Here we have to take different fonts into account.
671  */
672 static int
673 tem_adjust_row(tem_t *tem, int prom_row, cred_t *credp)
674 {
675 	int	tem_row;
676 	int	tem_y;
677 	int	prom_charheight = 0;
678 	int	prom_window_top = 0;
679 	int	scroll_up_lines;
680 
681 	plat_tem_get_prom_font_size(&prom_charheight, &prom_window_top);
682 	if (prom_charheight == 0)
683 		prom_charheight = PROM_DEFAULT_FONT_HEIGHT;
684 	if (prom_window_top == 0)
685 		prom_window_top = PROM_DEFAULT_WINDOW_TOP;
686 
687 	tem_y = (prom_row + 1) * prom_charheight + prom_window_top -
688 	    tem->state->a_p_offset.y;
689 	tem_row = (tem_y + tem->state->a_font.height - 1) /
690 	    tem->state->a_font.height - 1;
691 
692 	if (tem_row < 0) {
693 		tem_row = 0;
694 	} else if (tem_row >= (tem->state->a_c_dimension.height - 1)) {
695 		/*
696 		 * Scroll up the prom outputs if the PROM cursor's position is
697 		 * below our tem's lower boundary.
698 		 */
699 		scroll_up_lines = tem_row -
700 		    (tem->state->a_c_dimension.height - 1);
701 		tem_prom_scroll_up(tem, scroll_up_lines, credp);
702 		tem_row = tem->state->a_c_dimension.height - 1;
703 	}
704 
705 	return (tem_row);
706 }
707