dvnixload/src/serial.c

409 lines
9.0 KiB
C

/*
* serial.c -- Serial port routines.
*
* Copyright (C) 2008 Hugo Villeneuve <hugo@hugovil.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 of the License, 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 more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "config.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <ctype.h> /* For isdigit() and friends... */
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include "common.h"
#include "serial.h"
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define CHAR_BUF_MAX 256
#define MAX_LINE_CHAR 768
const char *baud_rate_ascii[] = {
"2400",
"4800",
"9600",
"19200",
"38400",
"57600",
"115200"
};
#define BAUD_RATE_VALUES_COUNT \
(sizeof(baud_rate_ascii)/sizeof(baud_rate_ascii[0]))
const speed_t baud_rate_termios[] = {
B2400,
B4800,
B9600,
B19200,
B38400,
B57600,
B115200
};
struct serial_port_t {
int fd; /* Serial port file descriptor.*/
struct termios oldtio; /* To save serial port original settings. */
struct termios newtio;
};
static struct serial_port_t serial_port;
/*
* Validates baud rate entered on the command line as an ASCII string, and
* returns an index corresponding to this speed inside an array (see above).
*/
int
validate_baudrate(char *baud_rate_string)
{
int i;
for (i = 0; i < BAUD_RATE_VALUES_COUNT; i++) {
if (STREQ(baud_rate_ascii[i], baud_rate_string))
return i; /* Found it! */
}
return -1;
}
static int
serial_port_configure(int baud_rate_index)
{
int status;
speed_t speed = baud_rate_termios[baud_rate_index];
/* Save current port settings. */
tcgetattr(serial_port.fd, &serial_port.oldtio);
/* Clear struct for new port settings. */
bzero(&serial_port.newtio, sizeof(serial_port.newtio));
/*
* speed : Set bps rate.
* CRTSCTS : output hardware flow control (only used if the cable has
* all necessary lines. See sect. 7 of Serial-HOWTO)
* CS8 : 8n1 (8bit,no parity,1 stopbit)
* CLOCAL : local connection, no modem contol
* CREAD : enable receiving characters
*/
serial_port.newtio.c_cflag = speed | CS8 | CLOCAL | CREAD;
/*
* IGNPAR : ignore bytes with parity errors
* ICRNL : map CR to NL (otherwise a CR input on the other computer
* will not terminate input)
* otherwise make device raw (no other input processing)
*/
serial_port.newtio.c_iflag = IGNPAR;
/* Raw output. */
serial_port.newtio.c_oflag = 0;
/* Set input mode (non-canonical, no echo,...) */
serial_port.newtio.c_lflag = 0;
/* Inter-character timer unused */
serial_port.newtio.c_cc[VTIME] = 0;
/* Blocking read until 1 chars received */
serial_port.newtio.c_cc[VMIN] = 1;
/* Now clean the modem line and activate the settings for the port. */
tcflush(serial_port.fd, TCIFLUSH);
status = tcsetattr(serial_port.fd, TCSANOW, &serial_port.newtio);
if (status < 0) {
log_fail("tcsetattr() failed");
return status;
}
return 0;
}
int
serial_port_open(const char *port, const int baud_rate_index)
{
int status;
log_debug2("baud_rate_index = %d", baud_rate_index);
/*
* The O_NOCTTY flag tells UNIX that this program doesn't want to be the
* "controlling terminal" for that port. If you don't specify this then
* any input (such as keyboard abort signals and so forth) will affect
* your process.
*
* The O_NDELAY flag tells UNIX that this program doesn't care what
* state the DCD signal line is in - whether the other end of the port
* is up and running. If you do not specify this flag, your process will
* be put to sleep until the DCD signal line is the space voltage.
*
* Timeouts are ignored in canonical input mode or when the O_NDELAY
* option is set on the file via open or fcntl. */
serial_port.fd = open(port, O_RDWR | O_NOCTTY);
if (serial_port.fd < 0) {
/* Could not open the port. */
log_fail("Error opening serial port %s", port);
perror(PACKAGE_NAME);
status = EXIT_FAILURE;
} else
status = serial_port_configure(baud_rate_index);
/* Flush unread data. */
status = tcflush(serial_port.fd, TCIFLUSH);
if (status != 0)
log_fail("Failure calling ioctl with TCFLSH argument");
return status;
}
void
serial_port_close(void)
{
if (serial_port.fd != 0) {
log_debug2("Closing Serial Port");
/* Restoring original port settings. */
tcsetattr(serial_port.fd, TCSANOW, &serial_port.oldtio);
close(serial_port.fd);
serial_port.fd = 0;
}
}
int
set_dtr(int dtr)
{
int status;
ioctl(serial_port.fd, TIOCMGET, &status);
if (dtr) status &= ~TIOCM_DTR;
else status |= TIOCM_DTR;
ioctl(serial_port.fd, TIOCMSET, &status);
return EXIT_SUCCESS;
}
int
set_rts(int rts)
{
int status;
ioctl(serial_port.fd, TIOCMGET, &status);
if (rts) status &= ~TIOCM_RTS;
else status |= TIOCM_RTS;
ioctl(serial_port.fd, TIOCMSET, &status);
return EXIT_SUCCESS;
}
int
send_message(const char *msg)
{
int status;
ssize_t len;
int size = strlen(msg);
int k;
char buf[CHAR_BUF_MAX];
log_debug2("Sending size = %d", size);
strncpy(buf, msg, sizeof(buf));
for (k = 0; k < size; k++) {
/* Replace CR/LF by \0 */
if (buf[k] == '\n')
buf[k] = 0x00;
log_debug3(" [%02d] 0x%02X", k, (u_int8_t) buf[k]);
}
len = write(serial_port.fd, buf, size);
if (len < 0) {
perror(PACKAGE_NAME);
log_fail(" Error writing message to serial port.");
status = EXIT_FAILURE;
} else
status = EXIT_SUCCESS;
log_debug2(" Written %d characters", len);
return status;
}
int
send_number(u_int32_t number, int digits)
{
char buf[CHAR_BUF_MAX];
int index = 0;
sprintf(buf, "%08X", number);
index = 8 - digits;
return send_message(&buf[index]);
}
int
send_buffer(u_int8_t *data, ssize_t size_in_bytes)
{
int k;
u_int32_t *data32 = (u_int32_t *) data;
if ((size_in_bytes % 4) != 0) {
log_fail("Data must be 32-bits aligned");
return 1;
}
for (k = 0; k < (size_in_bytes / 4); k++)
send_number(data32[k], 8);
return 0;
}
int
send_buffer_bin(u_int8_t *data, ssize_t size_in_bytes)
{
for (; size_in_bytes > 0; size_in_bytes--, data++)
write(serial_port.fd, data, 1);
return 0;
}
int
send_ascii_data(u_int8_t *data, ssize_t size)
{
int status;
ssize_t len;
log_debug2("Sending size = %d", size);
len = write(serial_port.fd, data, size);
if (len < 0) {
perror(PACKAGE_NAME);
log_fail(" Error writing ADCII data to serial port.");
status = EXIT_FAILURE;
} else
status = EXIT_SUCCESS;
log_debug2(" Written %d characters", len);
return status;
}
/*
* Compare received characters with str1, str2 and str3.
* The function returns when either str1, str2 or str3 is found,
* or after a timeout.
*
* Return value:
* -1 = Error
* 0 = no strings found
* 1 = str1 found
* 2 = str2 found
* 3 = str3 found
*/
int
wait_for_message(const char *str1, const char *str2, const char *str3)
{
int str_found = 0;
char input[CHAR_BUF_MAX];
char *message;
const char *received_string = NULL;
int i;
ssize_t len;
i = 0;
input[0] = '\0';
if (!str1) {
log_fail("str1 search string not specified");
return -1;
}
len = sprintf(input, "[%s]", str1);
if (str2)
len += sprintf(&input[len], " [%s]", str2);
if (str3)
len += sprintf(&input[len], " [%s]", str3);
log_info("Expecting messages: %s", input);
while (!str_found) {
do {
/* read() returns after 1 char have been input */
len = read(serial_port.fd, &input[i], 1);
if (len != 0) {
log_debug3("[%d] 0x%02X", i,
(u_int8_t) input[i]);
i++;
} else
log_debug2("LEN == 0");
} while ((input[i - 1] != 0) &&
(i < (CHAR_BUF_MAX - 1)) &&
(input[i - 1] != 0x0A) &&
(input[i - 1] != 0x0D));
/* Remove trailing CR or LF */
if ((input[i-1] == 0x0A) || (input[i-1] == 0x0D))
input[i-1] = '\0';
i = 0;
/* Remove space before string */
while (input[i] == ' ')
i++;
message = &input[i];
if (strlen(message) == 0)
continue;
log_debug1(" Reading: [%s]", message);
if (strstr(message, str1)) {
str_found = 1;
received_string = str1;
}
if (str2 != NULL) {
if (strstr(message, str2)) {
str_found = 2;
received_string = str2;
}
}
if (str3 != NULL) {
if (strstr(message, str3)) {
str_found = 3;
received_string = str3;
}
}
i = 0;
}
if (received_string)
log_info(" [%s] received", received_string);
return str_found;
}