1/*-
2 * Copyright (c) 1999 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp>
3 * Copyright (c) 1992-1998 S�ren Schmidt
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer,
11 *    without modification, immediately at the beginning of the file.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 *    derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include "opt_syscons.h"
34
35#ifndef SC_NO_HISTORY
36
37#include <sys/param.h>
38#include <sys/systm.h>
39#include <sys/conf.h>
40#include <sys/consio.h>
41#include <sys/tty.h>
42#include <sys/kernel.h>
43#include <sys/malloc.h>
44
45#if defined(__sparc64__) || defined(__powerpc__)
46#include <machine/sc_machdep.h>
47#else
48#include <machine/pc/display.h>
49#endif
50
51#include <dev/syscons/syscons.h>
52
53/*
54 * XXX Placeholder.
55 * This calculations should be dynamically scaled by number of separate sc
56 * devices.  A base value of 'extra_history_size' should be defined for
57 * each syscons unit, and added and subtracted from the dynamic
58 * 'extra_history_size' as units are added and removed.  This way, each time
59 * a new syscons unit goes online, extra_history_size is automatically bumped.
60 */
61#define	MAXSC	1
62
63#if !defined(SC_MAX_HISTORY_SIZE)
64#define SC_MAX_HISTORY_SIZE	(1000 * MAXCONS * MAXSC)
65#endif
66
67#if !defined(SC_HISTORY_SIZE)
68#define SC_HISTORY_SIZE		(ROW * 4)
69#endif
70
71#if (SC_HISTORY_SIZE * MAXCONS * MAXSC) > SC_MAX_HISTORY_SIZE
72#undef SC_MAX_HISTORY_SIZE
73#define SC_MAX_HISTORY_SIZE	(SC_HISTORY_SIZE * MAXCONS * MAXSC)
74#endif
75
76/* local variables */
77static int		extra_history_size
78				= SC_MAX_HISTORY_SIZE - SC_HISTORY_SIZE*MAXCONS;
79
80/* local functions */
81static void copy_history(sc_vtb_t *from, sc_vtb_t *to);
82static void history_to_screen(scr_stat *scp);
83
84/* allocate a history buffer */
85int
86sc_alloc_history_buffer(scr_stat *scp, int lines, int prev_ysize, int wait)
87{
88	/*
89	 * syscons unconditionally allocates buffers up to
90	 * SC_HISTORY_SIZE lines or scp->ysize lines, whichever
91	 * is larger. A value greater than that is allowed,
92	 * subject to extra_history_size.
93	 */
94	sc_vtb_t *history;
95	sc_vtb_t *prev_history;
96	int cur_lines;				/* current buffer size */
97	int min_lines;				/* guaranteed buffer size */
98	int delta;				/* lines to put back */
99
100	if (lines <= 0)
101		lines = SC_HISTORY_SIZE;	/* use the default value */
102
103	/* make it at least as large as the screen size */
104	lines = imax(lines, scp->ysize);
105
106	/* remove the history buffer while we update it */
107	history = prev_history = scp->history;
108	scp->history = NULL;
109
110	/* calculate the amount of lines to put back to extra_history_size */
111	delta = 0;
112	if (prev_history) {
113		cur_lines = sc_vtb_rows(history);
114		min_lines = imax(SC_HISTORY_SIZE, prev_ysize);
115		if (cur_lines > min_lines)
116			delta = cur_lines - min_lines;
117	}
118
119	/* lines up to min_lines are always allowed. */
120	min_lines = imax(SC_HISTORY_SIZE, scp->ysize);
121	if (lines > min_lines) {
122		if (lines - min_lines > extra_history_size + delta) {
123			/* too many lines are requested */
124			scp->history = prev_history;
125			return EINVAL;
126		}
127	}
128
129	/* allocate a new buffer */
130	history = (sc_vtb_t *)malloc(sizeof(*history),
131				     M_DEVBUF,
132				     (wait) ? M_WAITOK : M_NOWAIT);
133	if (history != NULL) {
134		if (lines > min_lines)
135			extra_history_size -= lines - min_lines;
136		/* XXX error check? */
137		sc_vtb_init(history, VTB_RINGBUFFER, scp->xsize, lines,
138			    NULL, wait);
139		/* FIXME: XXX no good? */
140		sc_vtb_clear(history, scp->sc->scr_map[0x20],
141			     SC_NORM_ATTR << 8);
142		if (prev_history != NULL)
143			copy_history(prev_history, history);
144		scp->history_pos = sc_vtb_tail(history);
145	} else {
146		scp->history_pos = 0;
147	}
148
149	/* destroy the previous buffer */
150	if (prev_history != NULL) {
151		extra_history_size += delta;
152		sc_vtb_destroy(prev_history);
153		free(prev_history, M_DEVBUF);
154	}
155
156	scp->history = history;
157
158	return 0;
159}
160
161static void
162copy_history(sc_vtb_t *from, sc_vtb_t *to)
163{
164	int lines;
165	int cols;
166	int cols1;
167	int cols2;
168	int pos;
169	int i;
170
171	lines = sc_vtb_rows(from);
172	cols1 = sc_vtb_cols(from);
173	cols2 = sc_vtb_cols(to);
174	cols = imin(cols1, cols2);
175	pos = sc_vtb_tail(from);
176	for (i = 0; i < lines; ++i) {
177		sc_vtb_append(from, pos, to, cols);
178		if (cols < cols2)
179			sc_vtb_seek(to, sc_vtb_pos(to,
180						   sc_vtb_tail(to),
181						   cols2 - cols));
182		pos = sc_vtb_pos(from, pos, cols1);
183	}
184}
185
186void
187sc_free_history_buffer(scr_stat *scp, int prev_ysize)
188{
189	sc_vtb_t *history;
190	int cur_lines;				/* current buffer size */
191	int min_lines;				/* guaranteed buffer size */
192
193	history = scp->history;
194	scp->history = NULL;
195	if (history == NULL)
196		return;
197
198	cur_lines = sc_vtb_rows(history);
199	min_lines = imax(SC_HISTORY_SIZE, prev_ysize);
200	extra_history_size += (cur_lines > min_lines) ?
201				  cur_lines - min_lines : 0;
202
203	sc_vtb_destroy(history);
204	free(history, M_DEVBUF);
205}
206
207/* copy entire screen into the top of the history buffer */
208void
209sc_hist_save(scr_stat *scp)
210{
211	sc_vtb_append(&scp->vtb, 0, scp->history, scp->xsize*scp->ysize);
212	scp->history_pos = sc_vtb_tail(scp->history);
213}
214
215/* restore the screen by copying from the history buffer */
216int
217sc_hist_restore(scr_stat *scp)
218{
219	int ret;
220
221	if (scp->history_pos != sc_vtb_tail(scp->history)) {
222		scp->history_pos = sc_vtb_tail(scp->history);
223		history_to_screen(scp);
224		ret =  0;
225	} else {
226		ret = 1;
227	}
228	sc_vtb_seek(scp->history, sc_vtb_pos(scp->history,
229					     sc_vtb_tail(scp->history),
230					     -scp->xsize*scp->ysize));
231	return ret;
232}
233
234/* copy screen-full of saved lines */
235static void
236history_to_screen(scr_stat *scp)
237{
238	int pos;
239	int i;
240
241	pos = scp->history_pos;
242	for (i = 1; i <= scp->ysize; ++i) {
243		pos = sc_vtb_pos(scp->history, pos, -scp->xsize);
244		sc_vtb_copy(scp->history, pos,
245			    &scp->vtb, scp->xsize*(scp->ysize - i),
246			    scp->xsize);
247	}
248	mark_all(scp);
249}
250
251/* go to the tail of the history buffer */
252void
253sc_hist_home(scr_stat *scp)
254{
255	scp->history_pos = sc_vtb_tail(scp->history);
256	history_to_screen(scp);
257}
258
259/* go to the top of the history buffer */
260void
261sc_hist_end(scr_stat *scp)
262{
263	scp->history_pos = sc_vtb_pos(scp->history, sc_vtb_tail(scp->history),
264				      scp->xsize*scp->ysize);
265	history_to_screen(scp);
266}
267
268/* move one line up */
269int
270sc_hist_up_line(scr_stat *scp)
271{
272	if (sc_vtb_pos(scp->history, scp->history_pos, -(scp->xsize*scp->ysize))
273	    == sc_vtb_tail(scp->history))
274		return -1;
275	scp->history_pos = sc_vtb_pos(scp->history, scp->history_pos,
276				      -scp->xsize);
277	history_to_screen(scp);
278	return 0;
279}
280
281/* move one line down */
282int
283sc_hist_down_line(scr_stat *scp)
284{
285	if (scp->history_pos == sc_vtb_tail(scp->history))
286		return -1;
287	scp->history_pos = sc_vtb_pos(scp->history, scp->history_pos,
288				      scp->xsize);
289	history_to_screen(scp);
290	return 0;
291}
292
293int
294sc_hist_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
295{
296	scr_stat *scp;
297	int error;
298
299	switch (cmd) {
300
301	case CONS_HISTORY:  	/* set history size */
302		scp = SC_STAT(tp);
303		if (*(int *)data <= 0)
304			return EINVAL;
305		if (scp->status & BUFFER_SAVED)
306			return EBUSY;
307		DPRINTF(5, ("lines:%d, ysize:%d, pool:%d\n",
308			    *(int *)data, scp->ysize, extra_history_size));
309		error = sc_alloc_history_buffer(scp,
310					       imax(*(int *)data, scp->ysize),
311					       scp->ysize, TRUE);
312		DPRINTF(5, ("error:%d, rows:%d, pool:%d\n", error,
313			    sc_vtb_rows(scp->history), extra_history_size));
314		return error;
315
316	case CONS_CLRHIST:
317		scp = SC_STAT(tp);
318		sc_vtb_clear(scp->history, scp->sc->scr_map[0x20],
319		    SC_NORM_ATTR << 8);
320		return 0;
321	}
322
323	return ENOIOCTL;
324}
325
326#endif /* SC_NO_HISTORY */
327