1 /* Definitions to support an abstract terminal controller.
2    Copyright 2002 Paul Twohey.
3 
4 This file is part of VMIPS.
5 
6 VMIPS is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
10 
11 VMIPS is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15 
16 You should have received a copy of the GNU General Public License along
17 with VMIPS; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
19 
20 #include "clock.h"
21 #include "error.h"
22 #include "terminalcontroller.h"
23 #include "vmips.h"
24 #include <cassert>
25 #include <cerrno>
26 #include <cstring>
27 #include <sys/time.h>
28 #include <unistd.h>
29 
TerminalController(Clock * clock,long keyboard_poll_ns,long keyboard_repoll_ns,long display_ready_delay_ns)30 TerminalController::TerminalController( Clock *clock, long keyboard_poll_ns,
31 					long keyboard_repoll_ns,
32 					long display_ready_delay_ns )
33 	: keyboard_poll_ns( keyboard_poll_ns ),
34 	  keyboard_repoll_ns( keyboard_repoll_ns ),
35 	  display_ready_delay_ns( display_ready_delay_ns ),
36 	  keyboard_poll(0), clock( clock ), max_fd( -1 )
37 {
38 	assert( clock );
39 	assert( keyboard_poll_ns > 0 );
40 	assert( keyboard_repoll_ns > 0 );
41 	assert( display_ready_delay_ns > 0 );
42 
43 	FD_ZERO( &unready_keyboards );
44 
45 	for( int i = 0; i < MAX_TERMINALS; i++ ) {
46 		lines[i].tty_fd = -1;
47 		lines[i].keyboard_char = 0;
48 		lines[i].keyboard_state = UNREADY;
49 		lines[i].display_state = READY;
50 		lines[i].keyboard_repoll = NULL;
51 		lines[i].display_delay = NULL;
52 	}
53 
54 	keyboard_poll = new KeyboardPoll( this );
55 	clock->add_deferred_task( keyboard_poll, keyboard_poll_ns );
56 }
57 
~TerminalController()58 TerminalController::~TerminalController()
59 {
60 	for( int i = 0; i < MAX_TERMINALS; i++ ) {
61 		remove_terminal( i );
62 	}
63 
64 	if( keyboard_poll )
65 		keyboard_poll->cancel();
66 }
67 
suspend()68 void TerminalController::suspend () {
69   for (int line = 0; line < MAX_TERMINALS; line++)
70     if (line_connected(line))
71       tcsetattr (lines[line].tty_fd, TCSAFLUSH, &lines[line].tty_state);
72 }
73 
connect_terminal(int tty_fd,int line)74 bool TerminalController::connect_terminal( int tty_fd, int line )
75 {
76 	// there is already a terminal connected to that line
77 	if( line_connected( line ) ) {
78 		while( close( tty_fd ) == -1 && errno == EINTR );
79 		return false;
80 	}
81 	assert( !lines[line].keyboard_repoll );
82 	assert( !lines[line].display_delay );
83 
84 	if (isatty(tty_fd)) {
85 	    // save the old terminal state
86 	    if (tcgetattr(tty_fd, &lines[line].tty_state) == -1) {
87 		error("cannot get terminal state on line %d: %s",
88 		      line, strerror(errno));
89 		close(tty_fd);
90 		return false;
91 	    }
92 	}
93 
94 	// prepare to call prepare_tty()
95 	lines[line].tty_fd = tty_fd;
96 
97 	if( !prepare_tty( line ) ) {
98 		lines[line].tty_fd = -1;
99 		while( close( tty_fd ) == -1 && errno == EINTR );
100 		return false;
101 	}
102 
103 	lines[line].keyboard_char = 0;
104 	lines[line].keyboard_state = UNREADY;
105 	lines[line].display_state = READY;
106 
107 	// the keyboard starts with no data
108 	FD_SET( tty_fd, &unready_keyboards );
109 
110 	if( tty_fd + 1 > max_fd )
111 		max_fd = tty_fd + 1;
112 
113 	assert( line_connected( line ) );
114 	return true;
115 }
116 
remove_terminal(int line)117 void TerminalController::remove_terminal( int line )
118 {
119 	assert( line >= 0 && line < MAX_TERMINALS );
120 
121 	int tty_fd = lines[line].tty_fd;
122 	if( tty_fd == -1 ) {
123 		assert( !lines[line].keyboard_repoll );
124 		assert( !lines[line].display_delay );
125 		return;
126 	}
127 
128 	// invalidate this line
129 	lines[line].tty_fd = -1;
130 	FD_CLR( tty_fd, &unready_keyboards );
131 
132 	if (isatty(tty_fd)) {
133 	    // reset the original terminal settings
134 	    if (tcsetattr(tty_fd, TCSAFLUSH, &lines[line].tty_state) == -1)
135 		    error("cannot restore state for terminal on line %d", line);
136 	}
137 
138 	// make sure to cancel tasks which refer to this line
139 	if( lines[line].keyboard_repoll ) {
140 		lines[line].keyboard_repoll->cancel();
141 		lines[line].keyboard_repoll = NULL;
142 	}
143 
144 	if( lines[line].display_delay ) {
145 		lines[line].display_delay->cancel();
146 		lines[line].display_delay = NULL;
147 	}
148 
149 	// recompute max_fd
150 	max_fd = -1;
151 	for( int i = 0; i < MAX_TERMINALS; i++ ) {
152 		if( lines[i].tty_fd > max_fd )
153 			max_fd = lines[i].tty_fd;
154 	}
155 	if( max_fd != -1 )
156 		max_fd++;
157 
158 	assert( !line_connected( line ) );
159 }
160 
reinitialize_terminals()161 void TerminalController::reinitialize_terminals()
162 {
163 	for( int i = 0; i < MAX_TERMINALS; i++ ) {
164 		if( !line_connected(i) )
165 			continue;
166 
167 		if( !prepare_tty( i ) ) {
168 			error("tty on line %d cannot be restored, removing",i);
169 			remove_terminal( i );
170 		}
171 	}
172 }
173 
ready_display(int line)174 void TerminalController::ready_display( int line )
175 {
176 	assert( line_connected( line ) );
177 	assert( lines[line].display_state == UNREADY );
178 	assert( lines[line].display_delay );
179 
180 	lines[line].display_delay = NULL;
181 	lines[line].display_state = READY;
182 }
183 
unready_display(int line,char data)184 void TerminalController::unready_display( int line, char data )
185 {
186 	assert( line_connected( line ) );
187 
188 	if( lines[line].display_state == UNREADY )
189 		return;
190 
191 	int result = write( lines[line].tty_fd, &data, sizeof(char) );
192 	assert( result == sizeof(char) );
193 
194 	assert( !lines[line].display_delay );
195 	lines[line].display_state = UNREADY;
196 	lines[line].display_delay = new DisplayDelay( this, line );
197 	clock->add_deferred_task( lines[line].display_delay,
198 				  display_ready_delay_ns );
199 }
200 
unready_keyboard(int line)201 void TerminalController::unready_keyboard( int line )
202 {
203 	assert( line_connected( line ) );
204 	assert( lines[line].keyboard_state == READY );
205 	assert( lines[line].keyboard_repoll );
206 
207 	FD_SET( lines[line].tty_fd, &unready_keyboards );
208 	lines[line].keyboard_state = UNREADY;
209 	lines[line].keyboard_repoll->cancel();
210 	lines[line].keyboard_repoll = NULL;
211 }
212 
ready_keyboard(int line)213 void TerminalController::ready_keyboard( int line )
214 {
215 	assert( line_connected( line ) );
216 
217 	lines[line].keyboard_state = READY;
218 	FD_CLR( lines[line].tty_fd, &unready_keyboards );
219 	read( lines[line].tty_fd, &lines[line].keyboard_char, sizeof(char) );
220     if (lines[line].keyboard_char == 0x1f) {
221       lines[line].keyboard_char = 0;
222       machine->attn_key ();
223     }
224 }
225 
repoll_keyboard(int line)226 void TerminalController::repoll_keyboard( int line )
227 {
228 	assert( line_connected( line ) );
229 	assert( lines[line].keyboard_state == READY );
230 
231 	ready_keyboard( line );
232 	lines[line].keyboard_repoll = new KeyboardRepoll( this, line );
233 	clock->add_deferred_task( lines[line].keyboard_repoll,
234 				  keyboard_repoll_ns );
235 }
236 
poll_keyboards()237 void TerminalController::poll_keyboards()
238 {
239 	// FIXME: setup new poll only when there is an UNREADY keyboard
240 	keyboard_poll = new KeyboardPoll( this );
241 	clock->add_deferred_task( keyboard_poll, keyboard_poll_ns );
242 
243 	if( max_fd == -1 )
244 		return;
245 
246 	fd_set to_read;
247 	copy_unready_keyboards( &to_read );
248 
249 	timeval zero = { 0, 0 };
250 	int read_count = select( max_fd, &to_read, NULL, NULL, &zero );
251 
252 	if( read_count == 0 )
253 		return;
254 
255 	for( int i = 0; i < MAX_TERMINALS; i++ ) {
256 		if(lines[i].tty_fd !=-1 && FD_ISSET(lines[i].tty_fd,&to_read)){
257 			assert( !lines[i].keyboard_repoll );
258 			assert( lines[i].keyboard_state == UNREADY );
259 
260 			// do not repoll keyboard until it is unready again
261 			lines[i].keyboard_repoll = new KeyboardRepoll( this,i);
262 			clock->add_deferred_task( lines[i].keyboard_repoll,
263 						   keyboard_repoll_ns );
264 			ready_keyboard( i );
265 		}
266 	}
267 }
268 
prepare_tty(int line)269 bool TerminalController::prepare_tty( int line )
270 {
271 	assert( line_connected( line ) );
272 	if (!isatty(lines[line].tty_fd)) {
273 		return true;
274 	}
275 
276 	int tty_fd = lines[line].tty_fd;
277 
278 	// use old state as a foundation
279 	termios new_tty_state = lines[line].tty_state;
280 
281 	// FIXME: add configurable escape code handling
282 	// change to the new state, disable echoing and line mode editing
283 	new_tty_state.c_lflag &= ~(ICANON | ECHO);
284 	new_tty_state.c_cc[VMIN] = 0;
285 	new_tty_state.c_cc[VTIME] = 0;
286 
287 	if( tcsetattr( tty_fd, TCSAFLUSH, &new_tty_state ) == -1 ) {
288 		error( "cannot set terminal state on line %d: %s",
289 		       line, strerror(errno) );
290 
291 		// attempt to restore the old state
292 		tcsetattr( tty_fd, TCSAFLUSH, &lines[line].tty_state );
293 		return false;
294 	}
295 
296 	return true;
297 }
298 
DisplayDelay(TerminalController * controller,int line)299 TerminalController::DisplayDelay::DisplayDelay( TerminalController *controller,
300 						int line )
301 	: controller( controller ), line( line )
302 {
303 	assert( controller );
304 	assert( controller->line_connected( line ) );
305 }
306 
~DisplayDelay()307 TerminalController::DisplayDelay::~DisplayDelay()
308 {
309 }
310 
real_task()311 void TerminalController::DisplayDelay::real_task()
312 {
313 	controller->ready_display( line );
314 }
315 
316 
KeyboardPoll(TerminalController * controller)317 TerminalController::KeyboardPoll::KeyboardPoll(TerminalController *controller)
318 	: controller( controller )
319 {
320 	assert( controller );
321 }
322 
~KeyboardPoll()323 TerminalController::KeyboardPoll::~KeyboardPoll()
324 {
325 }
326 
real_task()327 void TerminalController::KeyboardPoll::real_task()
328 {
329 	controller->poll_keyboards();
330 }
331 
332 
KeyboardRepoll(TerminalController * controller,int line)333 TerminalController::KeyboardRepoll::KeyboardRepoll(
334 	TerminalController *controller, int line )
335 	: controller( controller ), line( line )
336 {
337 	assert( controller );
338 	assert( controller->line_connected( line ) );
339 }
340 
~KeyboardRepoll()341 TerminalController::KeyboardRepoll::~KeyboardRepoll()
342 {
343 }
344 
real_task()345 void TerminalController::KeyboardRepoll::real_task()
346 {
347 	controller->repoll_keyboard( line );
348 }
349