/* * Terminal Program Examples * Copyright 2003 by Floyd L. Davidson, floyd@barrow.com * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for details. * * term_canon.c -- terminal program using canonical input. * * $Id: term_canon.c,v 1.1.0.1 2003/11/28 16:08:38 floyd Exp floyd $ */ /* * A simple terminal program useful for testing * serial ports, devices connected to serial ports, * and programs for serial ports. * * While this is a useful program, its intended function * is to demonstrate how to use fork() to multiplex * canonical input and output when programming a serial port. */ /* * One of _BSD_SOURCE, _SVID_SOURCE, or _GNU_SOURCE * must be defined to allow invoking gcc with the * -ansi switch. Otherwise, __USE_MISC is not defined * in /usr/include/features.h, and CRTSCTS is then * undefined in /usr/include/bits/termios.h. */ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include /* defines MAX_INPUT */ #include #include /* prototype for strlen() */ #include #include /* prototype for nanosleep() */ #include #include /* defines N_TTY */ /************************* CONFIGURATION **************************/ #define SERIALDEVICE "/dev/modem" /* serial port device special file */ #define EXITSEQUENCE 3 /* ^C causes exit */ #define BAUD B57600 /* B9600, B38400, B57600, B115000...*/ #define BITS CS8 /* CS5, CS6, CS7, CS8 */ #define PARITY IGNPAR /* IGNPAR, PARENB, PARENB | PARODD */ #define FLOWCTL CRTSCTS /* CRTSCTS, IXON | IXOFF | IXANY */ #define CTLLOCAL 0 /* CLOCAL or 0 */ /**********************************************************************/ void serial_output(int); void serial_input(int); void sig_handler(int); int serial_open(char *); int serial_cnfg(int); int stdin_cnfg(int); int device_init(int); int getch(void); int kbhit(void); struct termios otty; /* * device (e.g., modem) init strings. * * each 'istring' will be sent in order, * with a 'idelay' seconds of sleep time following */ struct devinit { char *istring; int idelay; } devinit[] = { {"ATZ\r\n", 2}, {"ATL3V1E1Q0&D2\r\n", 0} }; #define NUMSTRINGS (sizeof devinit / sizeof *devinit) int main(void) { int fd; pid_t pid; /* open the serial port */ if (0 > (fd = serial_open(SERIALDEVICE))) { perror("open: " SERIALDEVICE); exit(EXIT_FAILURE); } /* configure the serial port */ if (0 != serial_cnfg(fd)) { perror("config: " SERIALDEVICE); exit(EXIT_FAILURE); } /* re-configure stdin */ if (0 != stdin_cnfg(STDIN_FILENO)) { perror("config: stdin"); exit(EXIT_FAILURE); } /* terminal settings done, now handle in/ouput */ switch (pid = fork()) { case -1: /* failed to fork */ perror("fork"); tcsetattr(STDIN_FILENO, TCSANOW, &otty); close(fd); exit(EXIT_FAILURE); case 0: /* child process */ serial_input(fd); close(fd); exit(EXIT_SUCCESS); /* sends SIGCHLD to parent */ default: /* parent */ serial_output(fd); tcsetattr(STDIN_FILENO, TCSANOW, &otty); close(fd); kill(pid, SIGKILL); /* kill the child if it still exists */ break; } return EXIT_SUCCESS; } /* * Serial port output process. * * Data from stdin is output to the serial port. */ void serial_output(int fd) { unsigned char c; int ch; int stop = 0; /* init the device on the serial port */ if (0 > device_init(fd)) { perror("initialize device"); return; } /* get input from keyboard and write to serial port */ while (!stop) { if (0 < kbhit()) { ch = getch(); } else { continue; } switch (ch) { case EXITSEQUENCE: fprintf(stderr, "\b \r\nExit terminal program? Y/n ?\b"); if (('N' == (ch = getch())) || 'n' == ch) { fprintf(stderr, "\rExit aborted... continuing. \r\n"); break; } /* fall through */ case EOF: stop = 1; fprintf(stderr, "\r\n"); break; default: c = (unsigned char) ch; write(fd, &c, 1); } } } /* * Serial port input process. * * Input from the serial port is output to stdout. */ void serial_input(int fd) { char s[MAX_INPUT]; FILE *fp; close(STDIN_FILENO); if (NULL == (fp = fdopen(fd, "r"))) { return; } /* get input from serial port and write it to stdout */ while (fgets(s, sizeof s, fp)) { fprintf(stderr, "%s", s); } } /* * Open serial port for reading and writing: * * Flag it not to be a controlling tty to prevent * killing the process with garble from the port. * * Flag it as non-blocking to allow open() to * return even if serial port DCD line indicates * no carrier detect. * * Remove non-blocking flag if successful, so that * read() and write() can block. */ int serial_open(char *device) { int fd, oldflags; /* O_NONBLOCK allows open even with no carrier detect */ if (-1 != (fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK))) { /* clear O_NONBLOCK to allow read() and write() to block */ if ((-1 != (oldflags = fcntl(fd, F_GETFL, 0))) && (-1 != fcntl(fd, F_SETFL, oldflags & ~O_NONBLOCK))) { /* flush input and output */ if (-1 == tcflush(fd, TCIOFLUSH)) { close(fd); return -1; } } else { close(fd); return -1; } } else { int x = errno; fprintf(stderr,"open failed, errno = %d\n", x); } return fd; } /* * configure the serial port * * flow control, bitrate, parity, and the number * of stop bits are set by #defines at the beginning * of this file. * * single character raw i/o with blocking enabled, * plus output processing to change NL to CRNL. */ #define TERMSIZE (offsetof (struct termios, c_cc[NCCS])) int serial_cnfg(int fd) { struct termios tty, stty; tcgetattr(fd, &tty); #define IFLAGS (IGNBRK | PARITY) #define CFLAGS (FLOWCTL | BITS | CREAD | CTLLOCAL) #define LFLAGS (ICANON) #define OFLAGS (ONLCR | OPOST) /* raw io, hardware flow control, 8n1 */ tty.c_iflag = IFLAGS; /* input flags */ tty.c_cflag = CFLAGS; /* control flags */ tty.c_lflag = LFLAGS; /* local flags */ tty.c_oflag = OFLAGS; /* output flags */ tty.c_cc[VINTR] = 0; /* */ tty.c_cc[VQUIT] = 0; /* */ tty.c_cc[VERASE] = 0; /* */ tty.c_cc[VKILL] = 0; /* */ tty.c_cc[VEOF] = 0; /* */ tty.c_cc[VEOL] = 0; /* */ tty.c_cc[VEOL2] = 0; /* */ #ifdef __linux__ /* for linux only */ tty.c_line = N_TTY; /* set line discipline */ #endif cfsetospeed(&tty, BAUD); /* set bit rate */ cfsetispeed(&tty, BAUD); if (tcsetattr(fd, TCSADRAIN, &tty) || tcgetattr(fd, &stty)) { return -1; } /* verify the changes were actually made */ return memcmp(&tty, &stty, TERMSIZE) ? -1 : 0; } /* * re-configure stdin * * hardware flow control, 8n1, full duplex, and * single character raw i/o with blocking enabled. */ int stdin_cnfg(int fd) { struct termios tty, stty; tcgetattr(fd, &tty); otty = tty; /* save old settings */ /* * change only what we must... */ tty.c_lflag = (ICANON | ECHO); tty.c_iflag = ICRNL; tty.c_cc[VERASE] = 8; tty.c_cc[VINTR] = 127; if (tcsetattr(fd, TCSADRAIN, &tty) || tcgetattr(fd, &stty)) { return -1; } /* verify the changes were actually made */ return memcmp(&tty, &stty, TERMSIZE) ? -1 : 0; } /* * send init strings to the device connected to the serial port */ int device_init(int fd) { unsigned int n; struct timespec tv; for (n = 0; n < NUMSTRINGS; ++n) { write(fd, devinit[n].istring, strlen(devinit[n].istring)); tv.tv_sec = devinit[n].idelay; tv.tv_nsec = 0; nanosleep(&tv, NULL); } return 1; } /* * kbhit() -- a keyboard lookahead monitor * * returns the number of characters available to read. * * Note that we do not verify the tcsetattr() calls in * this function, and assume that since it worked when * called in the configuration function that it will * also work here. We hope... */ int kbhit(void) { int count = 0; int error; struct timespec tv; struct termios tty, ntty; tcgetattr(STDIN_FILENO, &tty); ntty = tty; ntty.c_lflag &= ~ICANON; if (0 == (error = tcsetattr(STDIN_FILENO, TCSANOW, &ntty))) { error += ioctl(STDIN_FILENO, FIONREAD, &count); error += tcsetattr(STDIN_FILENO, TCSANOW, &tty); tv.tv_sec = 0; tv.tv_nsec = 10; nanosleep(&tv, NULL); } return error == 0 ? count : -1; } /* * getch() -- a blocking single character input from stdin * * Returns a character, or -1 if an input error occurs. * * Note that we do not verify the tcsetattr() calls in * this function, and assume that since it worked when * called in the configuration function that it will * also work here. We hope... */ int getch(void) { unsigned char ch; int error; struct termios tty, ntty; tcgetattr(STDIN_FILENO, &tty); ntty = tty; ntty.c_lflag &= ~ICANON; ntty.c_lflag &= ~ECHO; ntty.c_cc[VMIN] = 1; ntty.c_cc[VTIME] = 0; if (0 == (error = tcsetattr(STDIN_FILENO, TCSANOW, &ntty))) { error = read(STDIN_FILENO, &ch, 1 ); error += tcsetattr(STDIN_FILENO, TCSANOW, &tty); } return (error == 1 ? (int) ch : -1 ); }