1 |
/* |
2 |
* Cisco router simulation platform. |
3 |
* Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr) |
4 |
* |
5 |
* Virtual console TTY. |
6 |
* |
7 |
* "Interactive" part idea by Mtve. |
8 |
* TCP console added by Mtve. |
9 |
* Serial console by Peter Ross (suxen_drol@hotmail.com) |
10 |
*/ |
11 |
|
12 |
/* By default, Cygwin supports only 64 FDs with select()! */ |
13 |
#ifdef __CYGWIN__ |
14 |
#define FD_SETSIZE 1024 |
15 |
#endif |
16 |
|
17 |
#include <stdio.h> |
18 |
#include <stdlib.h> |
19 |
#include <string.h> |
20 |
#include <unistd.h> |
21 |
#include <sys/types.h> |
22 |
#include <sys/socket.h> |
23 |
#include <termios.h> |
24 |
#include <fcntl.h> |
25 |
#include <errno.h> |
26 |
#include <assert.h> |
27 |
|
28 |
#include <arpa/telnet.h> |
29 |
|
30 |
#include "utils.h" |
31 |
#include "cpu.h" |
32 |
#include "vm.h" |
33 |
#include "dynamips.h" |
34 |
#include "mips64_exec.h" |
35 |
#include "ppc32_exec.h" |
36 |
#include "device.h" |
37 |
#include "memory.h" |
38 |
#include "dev_c7200.h" |
39 |
#include "dev_c3600.h" |
40 |
#include "dev_c2691.h" |
41 |
#include "dev_c3725.h" |
42 |
#include "dev_c3745.h" |
43 |
#include "dev_c2600.h" |
44 |
#include "dev_vtty.h" |
45 |
|
46 |
/* VTTY list */ |
47 |
static pthread_mutex_t vtty_list_mutex = PTHREAD_MUTEX_INITIALIZER; |
48 |
static vtty_t *vtty_list = NULL; |
49 |
static pthread_t vtty_thread; |
50 |
|
51 |
#define VTTY_LIST_LOCK() pthread_mutex_lock(&vtty_list_mutex); |
52 |
#define VTTY_LIST_UNLOCK() pthread_mutex_unlock(&vtty_list_mutex); |
53 |
|
54 |
static struct termios tios,tios_orig; |
55 |
|
56 |
/* Send Telnet command: WILL TELOPT_ECHO */ |
57 |
static void vtty_telnet_will_echo(vtty_t *vtty) |
58 |
{ |
59 |
u_char cmd[] = { IAC, WILL, TELOPT_ECHO }; |
60 |
write(vtty->fd,cmd,sizeof(cmd)); |
61 |
} |
62 |
|
63 |
/* Send Telnet command: Suppress Go-Ahead */ |
64 |
static void vtty_telnet_will_suppress_go_ahead(vtty_t *vtty) |
65 |
{ |
66 |
u_char cmd[] = { IAC, WILL, TELOPT_SGA }; |
67 |
write(vtty->fd,cmd,sizeof(cmd)); |
68 |
} |
69 |
|
70 |
/* Send Telnet command: Don't use linemode */ |
71 |
static void vtty_telnet_dont_linemode(vtty_t *vtty) |
72 |
{ |
73 |
u_char cmd[] = { IAC, DONT, TELOPT_LINEMODE }; |
74 |
write(vtty->fd,cmd,sizeof(cmd)); |
75 |
} |
76 |
|
77 |
/* Send Telnet command: does the client support terminal type message? */ |
78 |
static void vtty_telnet_do_ttype(vtty_t *vtty) |
79 |
{ |
80 |
u_char cmd[] = { IAC, DO, TELOPT_TTYPE }; |
81 |
write(vtty->fd,cmd,sizeof(cmd)); |
82 |
} |
83 |
|
84 |
/* Restore TTY original settings */ |
85 |
static void vtty_term_reset(void) |
86 |
{ |
87 |
tcsetattr(STDIN_FILENO,TCSANOW,&tios_orig); |
88 |
} |
89 |
|
90 |
/* Initialize real TTY */ |
91 |
static void vtty_term_init(void) |
92 |
{ |
93 |
tcgetattr(STDIN_FILENO,&tios); |
94 |
|
95 |
memcpy(&tios_orig,&tios,sizeof(struct termios)); |
96 |
atexit(vtty_term_reset); |
97 |
|
98 |
tios.c_cc[VTIME] = 0; |
99 |
tios.c_cc[VMIN] = 1; |
100 |
|
101 |
/* Disable Ctrl-C, Ctrl-S, Ctrl-Q and Ctrl-Z */ |
102 |
tios.c_cc[VINTR] = 0; |
103 |
tios.c_cc[VSTART] = 0; |
104 |
tios.c_cc[VSTOP] = 0; |
105 |
tios.c_cc[VSUSP] = 0; |
106 |
|
107 |
tios.c_lflag &= ~(ICANON|ECHO); |
108 |
tios.c_iflag &= ~ICRNL; |
109 |
tcsetattr(STDIN_FILENO, TCSANOW, &tios); |
110 |
tcflush(STDIN_FILENO,TCIFLUSH); |
111 |
} |
112 |
|
113 |
/* Wait for a TCP connection */ |
114 |
static int vtty_tcp_conn_wait(vtty_t *vtty) |
115 |
{ |
116 |
struct sockaddr_in serv; |
117 |
int one = 1; |
118 |
|
119 |
vtty->state = VTTY_STATE_TCP_INVALID; |
120 |
|
121 |
if ((vtty->accept_fd = socket(PF_INET,SOCK_STREAM,0)) < 0) { |
122 |
perror("vtty_tcp_waitcon: socket"); |
123 |
return(-1); |
124 |
} |
125 |
|
126 |
if (setsockopt(vtty->accept_fd,SOL_SOCKET,SO_REUSEADDR, |
127 |
&one,sizeof(one)) < 0) |
128 |
{ |
129 |
perror("vtty_tcp_waitcon: setsockopt(SO_REUSEADDR)"); |
130 |
goto error; |
131 |
} |
132 |
|
133 |
memset(&serv,0,sizeof(serv)); |
134 |
serv.sin_family = AF_INET; |
135 |
serv.sin_addr.s_addr = htonl(INADDR_ANY); |
136 |
serv.sin_port = htons(vtty->tcp_port); |
137 |
|
138 |
if (bind(vtty->accept_fd,(struct sockaddr *)&serv,sizeof(serv)) < 0) { |
139 |
perror("vtty_tcp_waitcon: bind"); |
140 |
goto error; |
141 |
} |
142 |
|
143 |
if (listen(vtty->accept_fd,1) < 0) { |
144 |
perror("vtty_tcp_waitcon: listen"); |
145 |
goto error; |
146 |
} |
147 |
|
148 |
vm_log(vtty->vm,"VTTY","%s: waiting connection on tcp port %d (FD %d)\n", |
149 |
vtty->name,vtty->tcp_port,vtty->accept_fd); |
150 |
|
151 |
vtty->select_fd = &vtty->accept_fd; |
152 |
vtty->state = VTTY_STATE_TCP_WAITING; |
153 |
return(0); |
154 |
|
155 |
error: |
156 |
close(vtty->accept_fd); |
157 |
vtty->accept_fd = -1; |
158 |
vtty->select_fd = NULL; |
159 |
return(-1); |
160 |
} |
161 |
|
162 |
/* Accept a TCP connection */ |
163 |
static int vtty_tcp_conn_accept(vtty_t *vtty) |
164 |
{ |
165 |
if ((vtty->fd = accept(vtty->accept_fd,NULL,NULL)) < 0) { |
166 |
fprintf(stderr,"vtty_tcp_conn_accept: accept on port %d failed %s\n", |
167 |
vtty->tcp_port,strerror(errno)); |
168 |
return(-1); |
169 |
} |
170 |
|
171 |
vm_log(vtty->vm,"VTTY","%s is now connected (accept_fd=%d,conn_fd=%d)\n", |
172 |
vtty->name,vtty->accept_fd,vtty->fd); |
173 |
|
174 |
/* Adapt Telnet settings */ |
175 |
if (vtty->terminal_support) { |
176 |
vtty_telnet_do_ttype(vtty); |
177 |
vtty_telnet_will_echo(vtty); |
178 |
vtty_telnet_will_suppress_go_ahead(vtty); |
179 |
vtty_telnet_dont_linemode(vtty); |
180 |
vtty->input_state = VTTY_INPUT_TELNET; |
181 |
} |
182 |
|
183 |
if (!(vtty->fstream = fdopen(vtty->fd, "wb"))) { |
184 |
close(vtty->fd); |
185 |
vtty->fd = -1; |
186 |
return(-1); |
187 |
} |
188 |
|
189 |
fprintf(vtty->fstream, |
190 |
"Connected to Dynamips VM \"%s\" (ID %u, type %s) - %s\r\n\r\n", |
191 |
vtty->vm->name, vtty->vm->instance_id, vm_get_type(vtty->vm), |
192 |
vtty->name); |
193 |
|
194 |
vtty->select_fd = &vtty->fd; |
195 |
vtty->state = VTTY_STATE_TCP_RUNNING; |
196 |
return(0); |
197 |
} |
198 |
|
199 |
/* |
200 |
* Parse serial interface descriptor string, return 0 if success |
201 |
* string takes the form "device:baudrate:databits:parity:stopbits:hwflow" |
202 |
* device is mandatory, other options are optional (default=9600,8,N,1,0). |
203 |
*/ |
204 |
int vtty_parse_serial_option(vtty_serial_option_t *option, char *optarg) |
205 |
{ |
206 |
char *array[6]; |
207 |
int count; |
208 |
|
209 |
if ((count = m_strtok(optarg, ':', array, 6)) < 1) { |
210 |
fprintf(stderr,"vtty_parse_serial_option: invalid string\n"); |
211 |
return(-1); |
212 |
} |
213 |
|
214 |
if (!(option->device = strdup(array[0]))) { |
215 |
fprintf(stderr,"vtty_parse_serial_option: unable to copy string\n"); |
216 |
return(-1); |
217 |
} |
218 |
|
219 |
option->baudrate = (count>1) ? atoi(array[1]) : 9600; |
220 |
option->databits = (count>2) ? atoi(array[2]) : 8; |
221 |
|
222 |
if (count > 3) { |
223 |
switch(*array[3]) { |
224 |
case 'o': |
225 |
case 'O': |
226 |
option->parity = 1; /* odd */ |
227 |
case 'e': |
228 |
case 'E': |
229 |
option->parity = 2; /* even */ |
230 |
default: |
231 |
option->parity = 0; /* none */ |
232 |
} |
233 |
} else { |
234 |
option->parity = 0; |
235 |
} |
236 |
|
237 |
option->stopbits = (count>4) ? atoi(array[4]) : 1; |
238 |
option->hwflow = (count>5) ? atoi(array[5]) : 0; |
239 |
return(0); |
240 |
} |
241 |
|
242 |
#if defined(__CYGWIN__) || defined(SUNOS) |
243 |
void cfmakeraw(struct termios *termios_p) { |
244 |
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP| |
245 |
INLCR|IGNCR|ICRNL|IXON); |
246 |
termios_p->c_oflag &= ~OPOST; |
247 |
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); |
248 |
termios_p->c_cflag &= ~(CSIZE|PARENB); |
249 |
termios_p->c_cflag |= CS8; |
250 |
} |
251 |
#endif |
252 |
|
253 |
/* |
254 |
* Setup serial port, return 0 if success. |
255 |
*/ |
256 |
static int vtty_serial_setup(vtty_t *vtty, const vtty_serial_option_t *option) |
257 |
{ |
258 |
struct termios tio; |
259 |
int tio_baudrate; |
260 |
|
261 |
if (tcgetattr(vtty->fd, &tio) != 0) { |
262 |
fprintf(stderr, "error: tcgetattr failed\n"); |
263 |
return(-1); |
264 |
} |
265 |
|
266 |
cfmakeraw(&tio); |
267 |
|
268 |
tio.c_cflag = 0 |
269 |
|CLOCAL // ignore modem control lines |
270 |
; |
271 |
|
272 |
tio.c_cflag &= ~CREAD; |
273 |
tio.c_cflag |= CREAD; |
274 |
|
275 |
switch(option->baudrate) { |
276 |
case 50 : tio_baudrate = B50; break; |
277 |
case 75 : tio_baudrate = B75; break; |
278 |
case 110 : tio_baudrate = B110; break; |
279 |
case 134 : tio_baudrate = B134; break; |
280 |
case 150 : tio_baudrate = B150; break; |
281 |
case 200 : tio_baudrate = B200; break; |
282 |
case 300 : tio_baudrate = B300; break; |
283 |
case 600 : tio_baudrate = B600; break; |
284 |
case 1200 : tio_baudrate = B1200; break; |
285 |
case 1800 : tio_baudrate = B1800; break; |
286 |
case 2400 : tio_baudrate = B2400; break; |
287 |
case 4800 : tio_baudrate = B4800; break; |
288 |
case 9600 : tio_baudrate = B9600; break; |
289 |
case 19200 : tio_baudrate = B19200; break; |
290 |
case 38400 : tio_baudrate = B38400; break; |
291 |
case 57600 : tio_baudrate = B57600; break; |
292 |
#if defined(B76800) |
293 |
case 76800 : tio_baudrate = B76800; break; |
294 |
#endif |
295 |
case 115200 : tio_baudrate = B115200; break; |
296 |
#if defined(B230400) |
297 |
case 230400 : tio_baudrate = B230400; break; |
298 |
#endif |
299 |
default: |
300 |
fprintf(stderr, "error: unsupported baudrate\n"); |
301 |
return(-1); |
302 |
} |
303 |
|
304 |
cfsetospeed(&tio, tio_baudrate); |
305 |
cfsetispeed(&tio, tio_baudrate); |
306 |
|
307 |
tio.c_cflag &= ~CSIZE; /* clear size flag */ |
308 |
switch(option->databits) { |
309 |
case 5 : tio.c_cflag |= CS5; break; |
310 |
case 6 : tio.c_cflag |= CS6; break; |
311 |
case 7 : tio.c_cflag |= CS7; break; |
312 |
case 8 : tio.c_cflag |= CS8; break; |
313 |
default : |
314 |
fprintf(stderr, "error: unsupported databits\n"); |
315 |
return(-1); |
316 |
} |
317 |
|
318 |
tio.c_iflag &= ~INPCK; /* clear parity flag */ |
319 |
tio.c_cflag &= ~(PARENB|PARODD); |
320 |
switch(option->parity) { |
321 |
case 0 : break; |
322 |
case 2 : tio.c_iflag|=INPCK; tio.c_cflag|=PARENB; break; /* even */ |
323 |
case 1 : tio.c_iflag|=INPCK; tio.c_cflag|=PARENB|PARODD; break; /* odd */ |
324 |
default: |
325 |
fprintf(stderr, "error: unsupported parity\n"); |
326 |
return(-1); |
327 |
} |
328 |
|
329 |
tio.c_cflag &= ~CSTOPB; /* clear stop flag */ |
330 |
switch(option->stopbits) { |
331 |
case 1 : break; |
332 |
case 2 : tio.c_cflag |= CSTOPB; break; |
333 |
default : |
334 |
fprintf(stderr, "error: unsupported stopbits\n"); |
335 |
return(-1); |
336 |
} |
337 |
|
338 |
#if defined(CRTSCTS) |
339 |
tio.c_cflag &= ~CRTSCTS; |
340 |
#endif |
341 |
#if defined(CNEW_RTSCTS) |
342 |
tio.c_cflag &= ~CNEW_RTSCTS; |
343 |
#endif |
344 |
if (option->hwflow) { |
345 |
#if defined(CRTSCTS) |
346 |
tio.c_cflag |= CRTSCTS; |
347 |
#else |
348 |
tio.c_cflag |= CNEW_RTSCTS; |
349 |
#endif |
350 |
} |
351 |
|
352 |
tio.c_cc[VTIME] = 0; |
353 |
tio.c_cc[VMIN] = 1; /* block read() until one character is available */ |
354 |
|
355 |
#if 0 |
356 |
/* not neccessary unless O_NONBLOCK used */ |
357 |
if (fcntl(vtty->fd, F_SETFL, 0) != 0) { /* enable blocking mode */ |
358 |
fprintf(stderr, "error: fnctl F_SETFL failed\n"); |
359 |
return(-1); |
360 |
} |
361 |
#endif |
362 |
|
363 |
if (tcflush(vtty->fd, TCIOFLUSH) != 0) { |
364 |
fprintf(stderr, "error: tcflush failed\n"); |
365 |
return(-1); |
366 |
} |
367 |
|
368 |
if (tcsetattr(vtty->fd, TCSANOW, &tio) != 0 ) { |
369 |
fprintf(stderr, "error: tcsetattr failed\n"); |
370 |
return(-1); |
371 |
} |
372 |
|
373 |
return(0); |
374 |
} |
375 |
|
376 |
/* Create a virtual tty */ |
377 |
vtty_t *vtty_create(vm_instance_t *vm,char *name,int type,int tcp_port, |
378 |
const vtty_serial_option_t *option) |
379 |
{ |
380 |
vtty_t *vtty; |
381 |
|
382 |
if (!(vtty = malloc(sizeof(*vtty)))) { |
383 |
fprintf(stderr,"VTTY: unable to create new virtual tty.\n"); |
384 |
return NULL; |
385 |
} |
386 |
|
387 |
memset(vtty,0,sizeof(*vtty)); |
388 |
vtty->name = name; |
389 |
vtty->type = type; |
390 |
vtty->vm = vm; |
391 |
vtty->fd = -1; |
392 |
vtty->fstream = NULL; |
393 |
vtty->accept_fd = -1; |
394 |
pthread_mutex_init(&vtty->lock,NULL); |
395 |
vtty->terminal_support = 1; |
396 |
vtty->input_state = VTTY_INPUT_TEXT; |
397 |
|
398 |
switch (vtty->type) { |
399 |
case VTTY_TYPE_NONE: |
400 |
vtty->select_fd = NULL; |
401 |
break; |
402 |
|
403 |
case VTTY_TYPE_TERM: |
404 |
vtty_term_init(); |
405 |
vtty->fd = STDIN_FILENO; |
406 |
vtty->select_fd = &vtty->fd; |
407 |
vtty->fstream = stdout; |
408 |
break; |
409 |
|
410 |
case VTTY_TYPE_TCP: |
411 |
vtty->tcp_port = tcp_port; |
412 |
vtty_tcp_conn_wait(vtty); |
413 |
break; |
414 |
|
415 |
case VTTY_TYPE_SERIAL: |
416 |
vtty->fd = open(option->device, O_RDWR); |
417 |
if (vtty->fd < 0) { |
418 |
fprintf(stderr,"VTTY: open failed\n"); |
419 |
free(vtty); |
420 |
return NULL; |
421 |
} |
422 |
if (vtty_serial_setup(vtty,option)) { |
423 |
fprintf(stderr,"VTTY: setup failed\n"); |
424 |
close(vtty->fd); |
425 |
free(vtty); |
426 |
return NULL; |
427 |
} |
428 |
vtty->select_fd = &vtty->fd; |
429 |
vtty->terminal_support = 0; |
430 |
break; |
431 |
|
432 |
default: |
433 |
fprintf(stderr,"tty_create: bad vtty type %d\n",vtty->type); |
434 |
return NULL; |
435 |
} |
436 |
|
437 |
/* Add this new VTTY to the list */ |
438 |
VTTY_LIST_LOCK(); |
439 |
vtty->next = vtty_list; |
440 |
vtty->pprev = &vtty_list; |
441 |
|
442 |
if (vtty_list != NULL) |
443 |
vtty_list->pprev = &vtty->next; |
444 |
|
445 |
vtty_list = vtty; |
446 |
VTTY_LIST_UNLOCK(); |
447 |
return vtty; |
448 |
} |
449 |
|
450 |
/* Delete a virtual tty */ |
451 |
void vtty_delete(vtty_t *vtty) |
452 |
{ |
453 |
if (vtty != NULL) { |
454 |
if (vtty->pprev != NULL) { |
455 |
VTTY_LIST_LOCK(); |
456 |
if (vtty->next) |
457 |
vtty->next->pprev = vtty->pprev; |
458 |
*(vtty->pprev) = vtty->next; |
459 |
VTTY_LIST_UNLOCK(); |
460 |
} |
461 |
|
462 |
if ((vtty->fstream) && (vtty->fstream != stdout)) |
463 |
fclose(vtty->fstream); |
464 |
|
465 |
/* We don't close FD 0 since it is stdin */ |
466 |
if (vtty->fd > 0) { |
467 |
vm_log(vtty->vm,"VTTY","%s: closing FD %d\n",vtty->name,vtty->fd); |
468 |
close(vtty->fd); |
469 |
} |
470 |
|
471 |
if (vtty->accept_fd != -1) { |
472 |
vm_log(vtty->vm,"VTTY","%s: closing accept FD %d\n", |
473 |
vtty->name,vtty->accept_fd); |
474 |
close(vtty->accept_fd); |
475 |
} |
476 |
|
477 |
free(vtty); |
478 |
} |
479 |
} |
480 |
|
481 |
/* Store a character in the FIFO buffer */ |
482 |
static int vtty_store(vtty_t *vtty,u_char c) |
483 |
{ |
484 |
u_int nwptr; |
485 |
|
486 |
VTTY_LOCK(vtty); |
487 |
nwptr = vtty->write_ptr + 1; |
488 |
if (nwptr == VTTY_BUFFER_SIZE) |
489 |
nwptr = 0; |
490 |
|
491 |
if (nwptr == vtty->read_ptr) { |
492 |
VTTY_UNLOCK(vtty); |
493 |
return(-1); |
494 |
} |
495 |
|
496 |
vtty->buffer[vtty->write_ptr] = c; |
497 |
vtty->write_ptr = nwptr; |
498 |
VTTY_UNLOCK(vtty); |
499 |
return(0); |
500 |
} |
501 |
|
502 |
/* Store a string in the FIFO buffer */ |
503 |
int vtty_store_str(vtty_t *vtty,char *str) |
504 |
{ |
505 |
if (!vtty) |
506 |
return(0); |
507 |
|
508 |
while(*str != 0) { |
509 |
if (vtty_store(vtty,*str) == -1) |
510 |
return(-1); |
511 |
|
512 |
str++; |
513 |
} |
514 |
|
515 |
vtty->input_pending = TRUE; |
516 |
return(0); |
517 |
} |
518 |
|
519 |
/* Store CTRL+C in buffer */ |
520 |
int vtty_store_ctrlc(vtty_t *vtty) |
521 |
{ |
522 |
if (vtty) |
523 |
vtty_store(vtty,0x03); |
524 |
return(0); |
525 |
} |
526 |
|
527 |
/* |
528 |
* Read a character from the terminal. |
529 |
*/ |
530 |
static int vtty_term_read(vtty_t *vtty) |
531 |
{ |
532 |
u_char c; |
533 |
|
534 |
if (read(vtty->fd,&c,1) == 1) |
535 |
return(c); |
536 |
|
537 |
perror("read from vtty failed"); |
538 |
return(-1); |
539 |
} |
540 |
|
541 |
/* |
542 |
* Read a character from the TCP connection. |
543 |
*/ |
544 |
static int vtty_tcp_read(vtty_t *vtty) |
545 |
{ |
546 |
u_char c; |
547 |
|
548 |
switch(vtty->state) { |
549 |
case VTTY_STATE_TCP_RUNNING: |
550 |
if (read(vtty->fd,&c,1) == 1) |
551 |
return(c); |
552 |
|
553 |
/* Problem with the connection: Re-enter wait mode */ |
554 |
shutdown(vtty->fd,2); |
555 |
fclose(vtty->fstream); |
556 |
close(vtty->fd); |
557 |
vtty->fstream = NULL; |
558 |
vtty->fd = -1; |
559 |
vtty->select_fd = &vtty->accept_fd; |
560 |
vtty->state = VTTY_STATE_TCP_WAITING; |
561 |
return(-1); |
562 |
|
563 |
case VTTY_STATE_TCP_WAITING: |
564 |
/* A new connection has arrived */ |
565 |
vtty_tcp_conn_accept(vtty); |
566 |
return(-1); |
567 |
} |
568 |
|
569 |
/* Shouldn't happen... */ |
570 |
return(-1); |
571 |
} |
572 |
|
573 |
/* |
574 |
* Read a character from the virtual TTY. |
575 |
* |
576 |
* If the VTTY is a TCP connection, restart it in case of error. |
577 |
*/ |
578 |
static int vtty_read(vtty_t *vtty) |
579 |
{ |
580 |
switch(vtty->type) { |
581 |
case VTTY_TYPE_TERM: |
582 |
case VTTY_TYPE_SERIAL: |
583 |
return(vtty_term_read(vtty)); |
584 |
case VTTY_TYPE_TCP: |
585 |
return(vtty_tcp_read(vtty)); |
586 |
default: |
587 |
fprintf(stderr,"vtty_read: bad vtty type %d\n",vtty->type); |
588 |
return(-1); |
589 |
} |
590 |
|
591 |
/* NOTREACHED */ |
592 |
return(-1); |
593 |
} |
594 |
|
595 |
/* Remote control for MIPS64 processors */ |
596 |
static int remote_control_mips64(vtty_t *vtty,char c,cpu_mips_t *cpu) |
597 |
{ |
598 |
switch(c) { |
599 |
/* Show information about JIT compiled pages */ |
600 |
case 'b': |
601 |
printf("\nCPU0: %u JIT compiled pages [Exec Area Pages: %lu/%lu]\n", |
602 |
cpu->compiled_pages, |
603 |
(u_long)cpu->exec_page_alloc, |
604 |
(u_long)cpu->exec_page_count); |
605 |
break; |
606 |
|
607 |
/* Non-JIT mode statistics */ |
608 |
case 'j': |
609 |
mips64_dump_stats(cpu); |
610 |
break; |
611 |
|
612 |
default: |
613 |
return(FALSE); |
614 |
} |
615 |
|
616 |
return(TRUE); |
617 |
} |
618 |
|
619 |
/* Remote control for PPC32 processors */ |
620 |
static int remote_control_ppc32(vtty_t *vtty,char c,cpu_ppc_t *cpu) |
621 |
{ |
622 |
switch(c) { |
623 |
/* Show information about JIT compiled pages */ |
624 |
case 'b': |
625 |
printf("\nCPU0: %u JIT compiled pages [Exec Area Pages: %lu/%lu]\n", |
626 |
cpu->compiled_pages, |
627 |
(u_long)cpu->exec_page_alloc, |
628 |
(u_long)cpu->exec_page_count); |
629 |
break; |
630 |
|
631 |
/* Non-JIT mode statistics */ |
632 |
case 'j': |
633 |
ppc32_dump_stats(cpu); |
634 |
break; |
635 |
|
636 |
default: |
637 |
return(FALSE); |
638 |
} |
639 |
|
640 |
return(TRUE); |
641 |
} |
642 |
|
643 |
/* Process remote control char */ |
644 |
static void remote_control(vtty_t *vtty,u_char c) |
645 |
{ |
646 |
vm_instance_t *vm = vtty->vm; |
647 |
cpu_gen_t *cpu0; |
648 |
|
649 |
cpu0 = vm->boot_cpu; |
650 |
|
651 |
/* Specific commands for the different CPU models */ |
652 |
if (cpu0) { |
653 |
switch(cpu0->type) { |
654 |
case CPU_TYPE_MIPS64: |
655 |
if (remote_control_mips64(vtty,c,CPU_MIPS64(cpu0))) |
656 |
return; |
657 |
break; |
658 |
case CPU_TYPE_PPC32: |
659 |
if (remote_control_ppc32(vtty,c,CPU_PPC32(cpu0))) |
660 |
return; |
661 |
break; |
662 |
} |
663 |
} |
664 |
|
665 |
switch(c) { |
666 |
/* Show the object list */ |
667 |
case 'o': |
668 |
vm_object_dump(vm); |
669 |
break; |
670 |
|
671 |
/* Stop the MIPS VM */ |
672 |
case 'q': |
673 |
vm->status = VM_STATUS_SHUTDOWN; |
674 |
break; |
675 |
|
676 |
/* Reboot the C7200 */ |
677 |
case 'k': |
678 |
#if 0 |
679 |
if (vm->type == VM_TYPE_C7200) |
680 |
c7200_boot_ios(VM_C7200(vm)); |
681 |
#endif |
682 |
break; |
683 |
|
684 |
/* Show the device list */ |
685 |
case 'd': |
686 |
dev_show_list(vm); |
687 |
pci_dev_show_list(vm->pci_bus[0]); |
688 |
pci_dev_show_list(vm->pci_bus[1]); |
689 |
break; |
690 |
|
691 |
/* Show info about Port Adapters or Network Modules */ |
692 |
case 'p': |
693 |
switch(vm->type) { |
694 |
case VM_TYPE_C3600: |
695 |
c3600_nm_show_all_info(VM_C3600(vm)); |
696 |
break; |
697 |
case VM_TYPE_C7200: |
698 |
c7200_pa_show_all_info(VM_C7200(vm)); |
699 |
break; |
700 |
case VM_TYPE_C2691: |
701 |
c2691_nm_show_all_info(VM_C2691(vm)); |
702 |
break; |
703 |
case VM_TYPE_C3725: |
704 |
c3725_nm_show_all_info(VM_C3725(vm)); |
705 |
break; |
706 |
case VM_TYPE_C3745: |
707 |
c3745_nm_show_all_info(VM_C3745(vm)); |
708 |
break; |
709 |
case VM_TYPE_C2600: |
710 |
c2600_nm_show_all_info(VM_C2600(vm)); |
711 |
break; |
712 |
} |
713 |
break; |
714 |
|
715 |
/* Dump the MIPS registers */ |
716 |
case 'r': |
717 |
if (cpu0) cpu0->reg_dump(cpu0); |
718 |
break; |
719 |
|
720 |
/* Dump the latest memory accesses */ |
721 |
case 'm': |
722 |
if (cpu0) memlog_dump(cpu0); |
723 |
break; |
724 |
|
725 |
/* Suspend CPU emulation */ |
726 |
case 's': |
727 |
vm_suspend(vm); |
728 |
break; |
729 |
|
730 |
/* Resume CPU emulation */ |
731 |
case 'u': |
732 |
vm_resume(vm); |
733 |
break; |
734 |
|
735 |
/* Dump the MMU information */ |
736 |
case 't': |
737 |
if (cpu0) cpu0->mmu_dump(cpu0); |
738 |
break; |
739 |
|
740 |
/* Dump the MMU information (raw mode) */ |
741 |
case 'z': |
742 |
if (cpu0) cpu0->mmu_raw_dump(cpu0); |
743 |
break; |
744 |
|
745 |
/* Memory translation cache statistics */ |
746 |
case 'l': |
747 |
if (cpu0) cpu0->mts_show_stats(cpu0); |
748 |
break; |
749 |
|
750 |
/* Extract the configuration from the NVRAM */ |
751 |
case 'c': |
752 |
vm_ios_save_config(vm); |
753 |
break; |
754 |
|
755 |
/* Determine an idle pointer counter */ |
756 |
case 'i': |
757 |
if (cpu0) |
758 |
cpu0->get_idling_pc(cpu0); |
759 |
break; |
760 |
|
761 |
/* Experimentations / Tests */ |
762 |
case 'x': |
763 |
if (cpu0) { |
764 |
/* IRQ triggering */ |
765 |
vm_set_irq(vm,2); |
766 |
} |
767 |
break; |
768 |
|
769 |
case 'y': |
770 |
if (cpu0) { |
771 |
/* IRQ clearing */ |
772 |
vm_clear_irq(vm,2); |
773 |
} |
774 |
break; |
775 |
|
776 |
/* Twice Ctrl + ']' (0x1d, 29), or Alt-Gr + '*' (0xb3, 179) */ |
777 |
case 0x1d: |
778 |
case 0xb3: |
779 |
vtty_store(vtty,c); |
780 |
break; |
781 |
|
782 |
default: |
783 |
printf("\n\nInstance %s (ID %d)\n\n",vm->name,vm->instance_id); |
784 |
|
785 |
printf("o - Show the VM object list\n" |
786 |
"d - Show the device list\n" |
787 |
"r - Dump CPU registers\n" |
788 |
"t - Dump MMU information\n" |
789 |
"z - Dump MMU information (raw mode)\n" |
790 |
"m - Dump the latest memory accesses\n" |
791 |
"s - Suspend CPU emulation\n" |
792 |
"u - Resume CPU emulation\n" |
793 |
"q - Quit the emulator\n" |
794 |
"k - Reboot the virtual machine\n" |
795 |
"b - Show info about JIT compiled pages\n" |
796 |
"l - MTS cache statistics\n" |
797 |
"c - Write IOS configuration to disk\n" |
798 |
"j - Non-JIT mode statistics\n" |
799 |
"i - Determine an idling pointer counter\n" |
800 |
"x - Experimentations (can crash the box!)\n" |
801 |
"^] - Send ^]\n" |
802 |
"Other - This help\n"); |
803 |
} |
804 |
} |
805 |
|
806 |
|
807 |
/* Read a character (until one is available) and store it in buffer */ |
808 |
static void vtty_read_and_store(vtty_t *vtty) |
809 |
{ |
810 |
int c; |
811 |
|
812 |
/* wait until we get a character input */ |
813 |
c = vtty_read(vtty); |
814 |
|
815 |
/* if read error, do nothing */ |
816 |
if (c < 0) return; |
817 |
|
818 |
if (!vtty->terminal_support) { |
819 |
vtty_store(vtty,c); |
820 |
return; |
821 |
} |
822 |
|
823 |
switch(vtty->input_state) { |
824 |
case VTTY_INPUT_TEXT : |
825 |
switch(c) { |
826 |
case 0x1b: |
827 |
vtty->input_state = VTTY_INPUT_VT1; |
828 |
return; |
829 |
|
830 |
/* Ctrl + ']' (0x1d, 29), or Alt-Gr + '*' (0xb3, 179) */ |
831 |
case 0x1d: |
832 |
case 0xb3: |
833 |
vtty->input_state = VTTY_INPUT_REMOTE; |
834 |
return; |
835 |
case IAC : |
836 |
vtty->input_state = VTTY_INPUT_TELNET; |
837 |
return; |
838 |
case 0: /* NULL - Must be ignored - generated by Linux telnet */ |
839 |
case 10: /* LF (Line Feed) - Must be ignored on Windows platform */ |
840 |
return; |
841 |
default: |
842 |
/* Store a standard character */ |
843 |
vtty_store(vtty,c); |
844 |
return; |
845 |
} |
846 |
|
847 |
case VTTY_INPUT_VT1 : |
848 |
switch(c) { |
849 |
case 0x5b: |
850 |
vtty->input_state = VTTY_INPUT_VT2; |
851 |
return; |
852 |
default: |
853 |
vtty_store(vtty,0x1b); |
854 |
vtty_store(vtty,c); |
855 |
} |
856 |
vtty->input_state = VTTY_INPUT_TEXT; |
857 |
return; |
858 |
|
859 |
case VTTY_INPUT_VT2 : |
860 |
switch(c) { |
861 |
case 0x41: /* Up Arrow */ |
862 |
vtty_store(vtty,16); |
863 |
break; |
864 |
case 0x42: /* Down Arrow */ |
865 |
vtty_store(vtty,14); |
866 |
break; |
867 |
case 0x43: /* Right Arrow */ |
868 |
vtty_store(vtty,6); |
869 |
break; |
870 |
case 0x44: /* Left Arrow */ |
871 |
vtty_store(vtty,2); |
872 |
break; |
873 |
default: |
874 |
vtty_store(vtty,0x5b); |
875 |
vtty_store(vtty,0x1b); |
876 |
vtty_store(vtty,c); |
877 |
break; |
878 |
} |
879 |
vtty->input_state = VTTY_INPUT_TEXT; |
880 |
return; |
881 |
|
882 |
case VTTY_INPUT_REMOTE : |
883 |
remote_control(vtty, c); |
884 |
vtty->input_state = VTTY_INPUT_TEXT; |
885 |
return; |
886 |
|
887 |
case VTTY_INPUT_TELNET : |
888 |
vtty->telnet_cmd = c; |
889 |
switch(c) { |
890 |
case WILL: |
891 |
case WONT: |
892 |
case DO: |
893 |
case DONT: |
894 |
vtty->input_state = VTTY_INPUT_TELNET_IYOU; |
895 |
return; |
896 |
case SB : |
897 |
vtty->telnet_cmd = c; |
898 |
vtty->input_state = VTTY_INPUT_TELNET_SB1; |
899 |
return; |
900 |
case SE: |
901 |
break; |
902 |
case IAC : |
903 |
vtty_store(vtty, IAC); |
904 |
break; |
905 |
} |
906 |
vtty->input_state = VTTY_INPUT_TEXT; |
907 |
return; |
908 |
|
909 |
case VTTY_INPUT_TELNET_IYOU : |
910 |
vtty->telnet_opt = c; |
911 |
/* if telnet client can support ttype, ask it to send ttype string */ |
912 |
if ((vtty->telnet_cmd == WILL) && |
913 |
(vtty->telnet_opt == TELOPT_TTYPE)) |
914 |
{ |
915 |
vtty_put_char(vtty, IAC); |
916 |
vtty_put_char(vtty, SB); |
917 |
vtty_put_char(vtty, TELOPT_TTYPE); |
918 |
vtty_put_char(vtty, TELQUAL_SEND); |
919 |
vtty_put_char(vtty, IAC); |
920 |
vtty_put_char(vtty, SE); |
921 |
} |
922 |
vtty->input_state = VTTY_INPUT_TEXT; |
923 |
return; |
924 |
|
925 |
case VTTY_INPUT_TELNET_SB1 : |
926 |
vtty->telnet_opt = c; |
927 |
vtty->input_state = VTTY_INPUT_TELNET_SB2; |
928 |
return; |
929 |
|
930 |
case VTTY_INPUT_TELNET_SB2 : |
931 |
vtty->telnet_qual = c; |
932 |
if ((vtty->telnet_opt == TELOPT_TTYPE) && |
933 |
(vtty->telnet_qual == TELQUAL_IS)) |
934 |
vtty->input_state = VTTY_INPUT_TELNET_SB_TTYPE; |
935 |
else |
936 |
vtty->input_state = VTTY_INPUT_TELNET_NEXT; |
937 |
return; |
938 |
|
939 |
case VTTY_INPUT_TELNET_SB_TTYPE : |
940 |
/* parse ttype string: first char is sufficient */ |
941 |
/* if client is xterm or vt, set the title bar */ |
942 |
if ((c == 'x') || (c == 'X') || (c == 'v') || (c == 'V')) { |
943 |
fprintf(vtty->fstream, "\033]0;Dynamips(%i): %s, %s\07", |
944 |
vtty->vm->instance_id, vtty->vm->name, vtty->name); |
945 |
} |
946 |
vtty->input_state = VTTY_INPUT_TELNET_NEXT; |
947 |
return; |
948 |
|
949 |
case VTTY_INPUT_TELNET_NEXT : |
950 |
/* ignore all chars until next IAC */ |
951 |
if (c == IAC) |
952 |
vtty->input_state = VTTY_INPUT_TELNET; |
953 |
return; |
954 |
} |
955 |
} |
956 |
|
957 |
/* Read a character from the buffer (-1 if the buffer is empty) */ |
958 |
int vtty_get_char(vtty_t *vtty) |
959 |
{ |
960 |
u_char c; |
961 |
|
962 |
VTTY_LOCK(vtty); |
963 |
|
964 |
if (vtty->read_ptr == vtty->write_ptr) { |
965 |
VTTY_UNLOCK(vtty); |
966 |
return(-1); |
967 |
} |
968 |
|
969 |
c = vtty->buffer[vtty->read_ptr++]; |
970 |
|
971 |
if (vtty->read_ptr == VTTY_BUFFER_SIZE) |
972 |
vtty->read_ptr = 0; |
973 |
|
974 |
VTTY_UNLOCK(vtty); |
975 |
return(c); |
976 |
} |
977 |
|
978 |
/* Returns TRUE if a character is available in buffer */ |
979 |
int vtty_is_char_avail(vtty_t *vtty) |
980 |
{ |
981 |
int res; |
982 |
|
983 |
VTTY_LOCK(vtty); |
984 |
res = (vtty->read_ptr != vtty->write_ptr); |
985 |
VTTY_UNLOCK(vtty); |
986 |
return(res); |
987 |
} |
988 |
|
989 |
/* Put char to vtty */ |
990 |
void vtty_put_char(vtty_t *vtty, char ch) |
991 |
{ |
992 |
switch(vtty->type) { |
993 |
case VTTY_TYPE_NONE: |
994 |
break; |
995 |
|
996 |
case VTTY_TYPE_TERM: |
997 |
fwrite(&ch, 1, 1, vtty->fstream); |
998 |
break; |
999 |
|
1000 |
case VTTY_TYPE_TCP: |
1001 |
if ((vtty->state == VTTY_STATE_TCP_RUNNING) && |
1002 |
(fwrite(&ch, 1, 1, vtty->fstream) != 1)) |
1003 |
{ |
1004 |
vm_log(vtty->vm,"VTTY","%s: put char %d failed (%s)\n", |
1005 |
vtty->name,(int)ch,strerror(errno)); |
1006 |
} |
1007 |
break; |
1008 |
|
1009 |
case VTTY_TYPE_SERIAL: |
1010 |
if (write(vtty->fd,&ch,1) != 1) { |
1011 |
vm_log(vtty->vm,"VTTY","%s: put char 0x%x failed (%s)\n", |
1012 |
vtty->name,(int)ch,strerror(errno)); |
1013 |
} |
1014 |
break; |
1015 |
|
1016 |
default: |
1017 |
fprintf(stderr,"vtty_put_char: bad vtty type %d\n",vtty->type); |
1018 |
exit(1); |
1019 |
} |
1020 |
} |
1021 |
|
1022 |
/* Put a buffer to vtty */ |
1023 |
void vtty_put_buffer(vtty_t *vtty,char *buf,size_t len) |
1024 |
{ |
1025 |
size_t i; |
1026 |
|
1027 |
for(i=0;i<len;i++) |
1028 |
vtty_put_char(vtty,buf[i]); |
1029 |
} |
1030 |
|
1031 |
/* Flush VTTY output */ |
1032 |
void vtty_flush(vtty_t *vtty) |
1033 |
{ |
1034 |
switch(vtty->type) { |
1035 |
case VTTY_TYPE_TERM: |
1036 |
case VTTY_TYPE_TCP: |
1037 |
if (vtty->fstream) |
1038 |
fflush(vtty->fstream); |
1039 |
break; |
1040 |
|
1041 |
case VTTY_TYPE_SERIAL: |
1042 |
fsync(vtty->fd); |
1043 |
break; |
1044 |
} |
1045 |
} |
1046 |
|
1047 |
/* VTTY thread */ |
1048 |
static void *vtty_thread_main(void *arg) |
1049 |
{ |
1050 |
vtty_t *vtty; |
1051 |
struct timeval tv; |
1052 |
int fd,fd_max,res; |
1053 |
fd_set rfds; |
1054 |
|
1055 |
for(;;) { |
1056 |
VTTY_LIST_LOCK(); |
1057 |
|
1058 |
/* Build the FD set */ |
1059 |
FD_ZERO(&rfds); |
1060 |
fd_max = -1; |
1061 |
for(vtty=vtty_list;vtty;vtty=vtty->next) { |
1062 |
if (!vtty->select_fd) |
1063 |
continue; |
1064 |
|
1065 |
if ((fd = *vtty->select_fd) < 0) |
1066 |
continue; |
1067 |
|
1068 |
if (fd > fd_max) fd_max = fd; |
1069 |
FD_SET(fd,&rfds); |
1070 |
} |
1071 |
VTTY_LIST_UNLOCK(); |
1072 |
|
1073 |
/* Wait for incoming data */ |
1074 |
tv.tv_sec = 0; |
1075 |
tv.tv_usec = 50 * 1000; /* 50 ms */ |
1076 |
res = select(fd_max+1,&rfds,NULL,NULL,&tv); |
1077 |
|
1078 |
if (res == -1) { |
1079 |
if (errno != EINTR) { |
1080 |
perror("vtty_thread: select"); |
1081 |
|
1082 |
for(vtty=vtty_list;vtty;vtty=vtty->next) { |
1083 |
fprintf(stderr," %-15s: %s, FD %d\n", |
1084 |
vtty->vm->name,vtty->name,vtty->fd); |
1085 |
} |
1086 |
} |
1087 |
continue; |
1088 |
} |
1089 |
|
1090 |
/* Examine active FDs and call user handlers */ |
1091 |
VTTY_LIST_LOCK(); |
1092 |
for(vtty=vtty_list;vtty;vtty=vtty->next) { |
1093 |
if (!vtty->select_fd) |
1094 |
continue; |
1095 |
|
1096 |
if ((fd = *vtty->select_fd) < 0) |
1097 |
continue; |
1098 |
|
1099 |
if (FD_ISSET(fd,&rfds)) { |
1100 |
vtty_read_and_store(vtty); |
1101 |
vtty->input_pending = TRUE; |
1102 |
} |
1103 |
|
1104 |
if (vtty->input_pending) { |
1105 |
if (vtty->read_notifier != NULL) |
1106 |
vtty->read_notifier(vtty); |
1107 |
|
1108 |
vtty->input_pending = FALSE; |
1109 |
} |
1110 |
|
1111 |
/* Flush any pending output */ |
1112 |
if (!vtty->managed_flush) |
1113 |
vtty_flush(vtty); |
1114 |
} |
1115 |
VTTY_LIST_UNLOCK(); |
1116 |
} |
1117 |
|
1118 |
return NULL; |
1119 |
} |
1120 |
|
1121 |
/* Initialize the VTTY thread */ |
1122 |
int vtty_init(void) |
1123 |
{ |
1124 |
if (pthread_create(&vtty_thread,NULL,vtty_thread_main,NULL)) { |
1125 |
perror("vtty: pthread_create"); |
1126 |
return(-1); |
1127 |
} |
1128 |
|
1129 |
return(0); |
1130 |
} |