/* * 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_thread.c -- terminal program using threads. * * $Id: term_thread.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 threads to multiplex * 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 #include #include #include /* prototype for nanosleep() */ #include #include /* defines N_TTY */ #include /************************* CONFIGURATION **************************/ #define SERIALDEVICE "/dev/modem" /* serial port device special file */ #define LOCALECHO 0 /* 1 enable local echo */ #define EXITSEQUENCE 3 /* ^C causes exit */ #define BAUD B57600 /* B9600, B38400, B57600, B11500... */ #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(void *); void serial_input(void *); void sig_handler(int); int serial_open(char *); int serial_cnfg(int); int stdin_cnfg(int); int device_init(int); struct termios otty; jmp_buf env; int fdt; /* * 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 iret; int fd; struct sigaction sa; pthread_t th; /* 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); } /* Create a thread for serial input */ iret = pthread_create(&th, NULL, (void *(*)(void*)) serial_input, (void *) &fd); /* set up sig handler and a graceful exit route */ sa.sa_handler = sig_handler; sa.sa_flags = 0; { int n; for (n = 0; n <= SIGUNUSED; ++n) { sigaction(n, &sa, NULL); } } if (0 == setjmp(env)) { serial_output(&fd); } tcsetattr(STDIN_FILENO, TCSANOW, &otty); close(fd); return EXIT_SUCCESS; } /* * Serial port output process. * * Data from stdin is output to the serial port. */ void serial_output(void *fdo) { unsigned char c; int ch; int fd; int stop = 0; fd = *(int *)fdo; /* 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) { switch (ch = getchar()) { case EXITSEQUENCE: fprintf(stderr, "Exit terminal program? Y/n ?\b"); if (('N' == (ch = getchar())) || 'n' == ch) { fprintf(stderr, "\rExit aborted... continuing. \r\n"); break; } /* fall through */ case EOF: stop = 1; fprintf(stderr, "\n\r\n"); break; default: c = (unsigned char) ch; write(fd, &c, 1); #if LOCALECHO write(STDOUT_FILENO, &c, 1); #endif } } } /* * Serial port input process. * * Input from the serial port is output to stdout. */ void serial_input(void *fdi) { unsigned char s[MAX_INPUT]; int cnt = 0; int fd; fd = *(int *)fdi; /* get input from serial port and write it to stdout */ while (1) { switch (cnt = read(fd, &s, MAX_INPUT)) { default: /* write char to stdout */ write(STDOUT_FILENO, &s, cnt); break; case -1: if (errno != EAGAIN) { perror("serial port read()"); return; } case 0: break; } } } /* * Provide the parent process a graceful exit from * its read/write loop. * * Note that arg2 to longjmp() causes setjmp() to return * sig. There is no significance to using sig other than * to prevent gcc from complaining that sig is unused. */ void sig_handler(int sig) { longjmp(env, sig); } /* * 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; } } return fd; } /* * configure the serial port * * hardware flow control, 8n1, full duplex, and * single character raw i/o with blocking enabled. */ #define TERMSIZE (offsetof (struct termios, c_cc[NCCS])) int serial_cnfg(int fd) { struct termios tty, stty; if (tcgetattr(fd, &tty)) { return -2; } #define CFLAGS (FLOWCTL | BITS | CTLLOCAL | CREAD) #define IFLAGS (IGNBRK | PARITY) /* raw io, hardware flow control, 8n1 */ tty.c_iflag = IFLAGS; /* input flags */ tty.c_cflag = CFLAGS; /* control flags */ tty.c_lflag = 0; /* local flags */ tty.c_oflag = 0; /* output flags */ tty.c_cc[VMIN] = 100; /* wait for up to 100 characters */ tty.c_cc[VTIME] = 1; /* 1/10th second interchar timeout */ #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; if (tcgetattr(fd, &tty)) { return -2; } otty = tty; /* save old settings */ /* * change only what we must... */ tty.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL); tty.c_iflag &= ~(INLCR | IGNCR | ICRNL); tty.c_cc[VMIN] = 1; /* wait for 1 character */ tty.c_cc[VTIME] = 0; /* turn off timer */ 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 0; }