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