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(__arm__) || defined(__mips__) || \
46	defined(__powerpc__) || defined(__sparc64__)
47#include <machine/sc_machdep.h>
48#else
49#include <machine/pc/display.h>
50#endif
51
52#include <dev/syscons/syscons.h>
53
54/*
55 * XXX Placeholder.
56 * This calculations should be dynamically scaled by number of separate sc
57 * devices.  A base value of 'extra_history_size' should be defined for
58 * each syscons unit, and added and subtracted from the dynamic
59 * 'extra_history_size' as units are added and removed.  This way, each time
60 * a new syscons unit goes online, extra_history_size is automatically bumped.
61 */
62#define	MAXSC	1
63
64#if !defined(SC_MAX_HISTORY_SIZE)
65#define SC_MAX_HISTORY_SIZE	(1000 * MAXCONS * MAXSC)
66#endif
67
68#if !defined(SC_HISTORY_SIZE)
69#define SC_HISTORY_SIZE		(ROW * 4)
70#endif
71
72#if (SC_HISTORY_SIZE * MAXCONS * MAXSC) > SC_MAX_HISTORY_SIZE
73#undef SC_MAX_HISTORY_SIZE
74#define SC_MAX_HISTORY_SIZE	(SC_HISTORY_SIZE * MAXCONS * MAXSC)
75#endif
76
77/* local variables */
78static int		extra_history_size
79				= SC_MAX_HISTORY_SIZE - SC_HISTORY_SIZE*MAXCONS;
80
81/* local functions */
82static void copy_history(sc_vtb_t *from, sc_vtb_t *to);
83static void history_to_screen(scr_stat *scp);
84
85/* allocate a history buffer */
86int
87sc_alloc_history_buffer(scr_stat *scp, int lines, int prev_ysize, int wait)
88{
89	/*
90	 * syscons unconditionally allocates buffers up to
91	 * SC_HISTORY_SIZE lines or scp->ysize lines, whichever
92	 * is larger. A value greater than that is allowed,
93	 * subject to extra_history_size.
94	 */
95	sc_vtb_t *history;
96	sc_vtb_t *prev_history;
97	int cur_lines;				/* current buffer size */
98	int min_lines;				/* guaranteed buffer size */
99	int delta;				/* lines to put back */
100
101	if (lines <= 0)
102		lines = SC_HISTORY_SIZE;	/* use the default value */
103
104	/* make it at least as large as the screen size */
105	lines = imax(lines, scp->ysize);
106
107	/* remove the history buffer while we update it */
108	history = prev_history = scp->history;
109	scp->history = NULL;
110
111	/* calculate the amount of lines to put back to extra_history_size */
112	delta = 0;
113	if (prev_history) {
114		cur_lines = sc_vtb_rows(history);
115		min_lines = imax(SC_HISTORY_SIZE, prev_ysize);
116		if (cur_lines > min_lines)
117			delta = cur_lines - min_lines;
118	}
119
120	/* lines up to min_lines are always allowed. */
121	min_lines = imax(SC_HISTORY_SIZE, scp->ysize);
122	if (lines > min_lines) {
123		if (lines - min_lines > extra_history_size + delta) {
124			/* too many lines are requested */
125			scp->history = prev_history;
126			return EINVAL;
127		}
128	}
129
130	/* allocate a new buffer */
131	history = (sc_vtb_t *)malloc(sizeof(*history),
132				     M_DEVBUF,
133				     (wait) ? M_WAITOK : M_NOWAIT);
134	if (history != NULL) {
135		if (lines > min_lines)
136			extra_history_size -= lines - min_lines;
137		/* XXX error check? */
138		sc_vtb_init(history, VTB_RINGBUFFER, scp->xsize, lines,
139			    NULL, wait);
140		/* FIXME: XXX no good? */
141		sc_vtb_clear(history, scp->sc->scr_map[0x20],
142			     SC_NORM_ATTR << 8);
143		if (prev_history != NULL)
144			copy_history(prev_history, history);
145		scp->history_pos = sc_vtb_tail(history);
146	} else {
147		scp->history_pos = 0;
148	}
149
150	/* destroy the previous buffer */
151	if (prev_history != NULL) {
152		extra_history_size += delta;
153		sc_vtb_destroy(prev_history);
154		free(prev_history, M_DEVBUF);
155	}
156
157	scp->history = history;
158
159	return 0;
160}
161
162static void
163copy_history(sc_vtb_t *from, sc_vtb_t *to)
164{
165	int lines;
166	int cols;
167	int cols1;
168	int cols2;
169	int pos;
170	int i;
171
172	lines = sc_vtb_rows(from);
173	cols1 = sc_vtb_cols(from);
174	cols2 = sc_vtb_cols(to);
175	cols = imin(cols1, cols2);
176	pos = sc_vtb_tail(from);
177	for (i = 0; i < lines; ++i) {
178		sc_vtb_append(from, pos, to, cols);
179		if (cols < cols2)
180			sc_vtb_seek(to, sc_vtb_pos(to,
181						   sc_vtb_tail(to),
182						   cols2 - cols));
183		pos = sc_vtb_pos(from, pos, cols1);
184	}
185}
186
187void
188sc_free_history_buffer(scr_stat *scp, int prev_ysize)
189{
190	sc_vtb_t *history;
191	int cur_lines;				/* current buffer size */
192	int min_lines;				/* guaranteed buffer size */
193
194	history = scp->history;
195	scp->history = NULL;
196	if (history == NULL)
197		return;
198
199	cur_lines = sc_vtb_rows(history);
200	min_lines = imax(SC_HISTORY_SIZE, prev_ysize);
201	extra_history_size += (cur_lines > min_lines) ?
202				  cur_lines - min_lines : 0;
203
204	sc_vtb_destroy(history);
205	free(history, M_DEVBUF);
206}
207
208/* copy entire screen into the top of the history buffer */
209void
210sc_hist_save(scr_stat *scp)
211{
212	sc_vtb_append(&scp->vtb, 0, scp->history, scp->xsize*scp->ysize);
213	scp->history_pos = sc_vtb_tail(scp->history);
214}
215
216/* restore the screen by copying from the history buffer */
217int
218sc_hist_restore(scr_stat *scp)
219{
220	int ret;
221
222	if (scp->history_pos != sc_vtb_tail(scp->history)) {
223		scp->history_pos = sc_vtb_tail(scp->history);
224		history_to_screen(scp);
225		ret =  0;
226	} else {
227		ret = 1;
228	}
229	sc_vtb_seek(scp->history, sc_vtb_pos(scp->history,
230					     sc_vtb_tail(scp->history),
231					     -scp->xsize*scp->ysize));
232	return ret;
233}
234
235/* copy screen-full of saved lines */
236static void
237history_to_screen(scr_stat *scp)
238{
239	int pos;
240	int i;
241
242	pos = scp->history_pos;
243	for (i = 1; i <= scp->ysize; ++i) {
244		pos = sc_vtb_pos(scp->history, pos, -scp->xsize);
245		sc_vtb_copy(scp->history, pos,
246			    &scp->vtb, scp->xsize*(scp->ysize - i),
247			    scp->xsize);
248	}
249	mark_all(scp);
250}
251
252/* go to the tail of the history buffer */
253void
254sc_hist_home(scr_stat *scp)
255{
256	scp->history_pos = sc_vtb_tail(scp->history);
257	history_to_screen(scp);
258}
259
260/* go to the top of the history buffer */
261void
262sc_hist_end(scr_stat *scp)
263{
264	scp->history_pos = sc_vtb_pos(scp->history, sc_vtb_tail(scp->history),
265				      scp->xsize*scp->ysize);
266	history_to_screen(scp);
267}
268
269/* move one line up */
270int
271sc_hist_up_line(scr_stat *scp)
272{
273	if (sc_vtb_pos(scp->history, scp->history_pos, -(scp->xsize*scp->ysize))
274	    == sc_vtb_tail(scp->history))
275		return -1;
276	scp->history_pos = sc_vtb_pos(scp->history, scp->history_pos,
277				      -scp->xsize);
278	history_to_screen(scp);
279	return 0;
280}
281
282/* move one line down */
283int
284sc_hist_down_line(scr_stat *scp)
285{
286	if (scp->history_pos == sc_vtb_tail(scp->history))
287		return -1;
288	scp->history_pos = sc_vtb_pos(scp->history, scp->history_pos,
289				      scp->xsize);
290	history_to_screen(scp);
291	return 0;
292}
293
294int
295sc_hist_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
296{
297	scr_stat *scp;
298	int error;
299
300	switch (cmd) {
301
302	case CONS_HISTORY:  	/* set history size */
303		scp = SC_STAT(tp);
304		if (*(int *)data <= 0)
305			return EINVAL;
306		if (scp->status & BUFFER_SAVED)
307			return EBUSY;
308		DPRINTF(5, ("lines:%d, ysize:%d, pool:%d\n",
309			    *(int *)data, scp->ysize, extra_history_size));
310		error = sc_alloc_history_buffer(scp,
311					       imax(*(int *)data, scp->ysize),
312					       scp->ysize, TRUE);
313		DPRINTF(5, ("error:%d, rows:%d, pool:%d\n", error,
314			    sc_vtb_rows(scp->history), extra_history_size));
315		return error;
316
317	case CONS_CLRHIST:
318		scp = SC_STAT(tp);
319		sc_vtb_clear(scp->history, scp->sc->scr_map[0x20],
320		    SC_NORM_ATTR << 8);
321		return 0;
322	}
323
324	return ENOIOCTL;
325}
326
327#endif /* SC_NO_HISTORY */
328