![]() |
Linux
Application Development |
Michael K. Johnson Erik W. Troan |
/* robin.c -- implements simple serial port interaction program */
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h> /* for strerror() */
#include <termios.h>
#include <unistd.h>
void usage(int exitcode, char *error, char *addl) {
fprintf(stderr, "Usage: robin [options] <port>\n"
" [options] include:\n"
" -H for this help\n"
" -r for raw mode\n"
" -c to add CR with NL on output\n"
" -h for hardware flow control\n"
" -s for software flow control\n"
" -n for no flow control\n"
" -b <bps> for signalling rate\n");
if (error) fprintf(stderr, "%s: %s\n", error, addl);
exit(exitcode);
}
speed_t symbolic_speed(int speednum) {
if (speednum >= 460800) return B460800;
if (speednum >= 230400) return B230400;
if (speednum >= 115200) return B115200;
if (speednum >= 57600) return B57600;
if (speednum >= 38400) return B38400;
if (speednum >= 19200) return B19200;
if (speednum >= 9600) return B9600;
if (speednum >= 4800) return B4800;
if (speednum >= 2400) return B2400;
if (speednum >= 1800) return B1800;
if (speednum >= 1200) return B1200;
if (speednum >= 600) return B600;
if (speednum >= 300) return B300;
if (speednum >= 200) return B200;
if (speednum >= 150) return B150;
if (speednum >= 134) return B134;
if (speednum >= 110) return B110;
if (speednum >= 75) return B75;
return B50;
}
/* These need to have file scope so that we can use them in
* signal handlers */
/* old port termios settings to restore */
static struct termios pots;
/* old stdout/in termios settings to restore */
static struct termios sots;
/* port file descriptor */
int pf;
/* restore original terminal settings on exit */
void cleanup_termios(int signal) {
tcsetattr(pf, TCSANOW, &pots);
tcsetattr(STDIN_FILENO, TCSANOW, &sots);
exit(0);
}
/* handle a single escape character */
void send_escape(int fd, char c) {
switch (c) {
case 'q':
/* restore termios settings and exit */
cleanup_termios(0);
break;
case 'b':
/* send a break */
tcsendbreak(fd, 0);
break;
default:
/* pass the character through */
/* "C-\ C-\" sends "C-\" */
write(fd, &c, 1);
break;
}
return;
}
/* handle escape characters, writing to output */
void cook_buf(int fd, char *buf, int num) {
int current = 0;
static int in_escape = 0;
if (in_escape) {
/* cook_buf last called with an incomplete escape sequence */
send_escape(fd, buf[0]);
num--;
buf++;
in_escape = 0;
}
while (current < num) {
# define CTRLCHAR(c) ((c)-0x40)
while ((current < num) && (buf[current] != CTRLCHAR('\\'))) current++;
if (current) write (fd, buf, current);
if (current < num) {
/* found an escape character */
current++;
if (current >= num) {
/* interpret first character of next sequence */
in_escape = 1;
return;
}
send_escape(fd, buf[current]);
}
num -= current;
buf += current;
current = 0;
}
return;
}
int main(int argc, char *argv[]) {
char c; /* used for argument parsing */
struct termios pts; /* termios settings on port */
struct termios sts; /* termios settings on stdout/in */
char *portname;
int speed = 0; /* used in argument parsing to set speed */
struct sigaction sact;/* used to initialize the signal handler */
fd_set ready; /* used for select */
int raw = 0; /* raw mode? */
int i = 0; /* used in the multiplex loop */
int done = 0;
# define BUFSIZE 1024
char buf[BUFSIZE];
poptContext optCon; /* context for parsing command-line options */
struct poptOption optionsTable[] = {
{ "bps", 'b', POPT_ARG_INT, &speed, 0 },
{ "crnl", 'c', 0, 0, 'c' },
{ "help", 'H', 0, 0, 'H' },
{ "hwflow", 'h', 0, 0, 'h' },
{ "noflow", 'n', 0, 0, 'n' },
{ "raw", 'r', 0, &raw, 0 },
{ "swflow", 's', 0, 0, 's' },
{ NULL, 0, 0, NULL, 0 }
};
#ifdef DSLEEP
/* wait 10 minutes so we can attach a debugger */
sleep(600);
#endif
if (argc < 2) usage(1, "Not enough arguments", "");
/* Normally, we'd let popt figure out which argument is the
port name. Here we're forcing it to be the last one as it
eases the rest of the option parsing. */
portname = argv[argc - 1];
pf = open(portname, O_RDWR);
if (pf < 0)
usage(1, strerror(errno), portname);
/* modify the port configuration */
tcgetattr(pf, &pts);
pots = pts;
/* some things we want to set arbitrarily */
pts.c_lflag &= ~ICANON;
pts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
pts.c_cflag |= HUPCL;
pts.c_cc[VMIN] = 1;
pts.c_cc[VTIME] = 0;
/* Standard CR/LF handling: this is a dumb terminal.
* Do no translation:
* no NL -> CR/NL mapping on output, and
* no CR -> NL mapping on input.
*/
pts.c_oflag &= ~ONLCR;
pts.c_iflag &= ~ICRNL;
/* Now deal with the local terminal side */
tcgetattr(STDIN_FILENO, &sts);
sots = sts;
/* again, some arbitrary things */
sts.c_iflag &= ~(BRKINT | ICRNL);
sts.c_iflag |= IGNBRK;
sts.c_lflag &= ~ISIG;
sts.c_cc[VMIN] = 1;
sts.c_cc[VTIME] = 0;
sts.c_lflag &= ~ICANON;
/* no local echo: allow the other end to do the echoing */
sts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL);
optCon = poptGetContext("robin", argc, argv, optionsTable, 0);
/* option processing will now modify pts and sts */
while ((c = poptGetNextOpt(optCon)) >= 0) {
switch (c) {
case 'H':
usage(0, NULL, NULL);
break;
case 'c':
/* send CR with NL */
pts.c_oflag |= ONLCR;
break;
case 'h':
/* hardware flow control */
pts.c_cflag |= CRTSCTS;
pts.c_iflag &= ~(IXON | IXOFF | IXANY);
break;
case 's':
/* software flow control */
pts.c_cflag &= ~CRTSCTS;
pts.c_iflag |= IXON | IXOFF | IXANY;
break;
case 'n':
/* no flow control */
pts.c_cflag &= ~CRTSCTS;
pts.c_iflag &= ~(IXON | IXOFF | IXANY);
break;
}
}
if (c < -1) {
/* an error occurred during option processing */
fprintf(stderr, "%s: %s\n",
poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
poptStrerror(c));
return 1;
}
poptFreeContext(optCon);
/* speed is not modified unless -b is specified */
if (speed) {
cfsetospeed(&pts, symbolic_speed(speed));
cfsetispeed(&pts, symbolic_speed(speed));
}
/* set the signal handler to restore the old
* termios handler */
sact.sa_handler = cleanup_termios;
sigaction(SIGHUP, &sact, NULL);
sigaction(SIGINT, &sact, NULL);
sigaction(SIGPIPE, &sact, NULL);
sigaction(SIGTERM, &sact, NULL);
/* Now set the modified termios settings */
tcsetattr(pf, TCSANOW, &pts);
tcsetattr(STDIN_FILENO, TCSANOW, &sts);
do {
FD_ZERO(&ready);
FD_SET(STDIN_FILENO, &ready);
FD_SET(pf, &ready);
select(pf+1, &ready, NULL, NULL, NULL);
if (FD_ISSET(pf, &ready)) {
/* pf has characters for us */
i = read(pf, buf, BUFSIZE);
if (i >= 1) {
write(STDOUT_FILENO, buf, i);
} else {
done = 1;
}
}
if (FD_ISSET(STDIN_FILENO, &ready)) {
/* standard input has characters for us */
i = read(STDIN_FILENO, buf, BUFSIZE);
if (i >= 1) {
if (raw) {
write(pf, buf, i);
} else {
cook_buf(pf, buf, i);
}
} else {
done = 1;
}
}
} while (!done);
/* restore original terminal settings and exit */
tcsetattr(pf, TCSANOW, &pots);
tcsetattr(STDIN_FILENO, TCSANOW, &sots);
exit(0);
}