blob: c5c10bbeab21d0fd2244772a8484ea270e87e335 [file] [log] [blame]
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>
#include <assert.h>
#include "aboot-tiny.h"
#include "jacana_serialport.h"
#include <time.h>
#include "jacana_usleep.h"
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
#ifndef SERIAL_RX_BUF_SIZE
#define SERIAL_RX_BUF_SIZE (1024)
#endif
typedef enum {
SERIALPORT_PARITY_NONE = 1,
SERIALPORT_PARITY_MARK = 2,
SERIALPORT_PARITY_EVEN = 3,
SERIALPORT_PARITY_ODD = 4,
SERIALPORT_PARITY_SPACE = 5
} serialport_parity_t;
typedef enum {
SERIALPORT_STOPBITS_ONE = 1,
SERIALPORT_STOPBITS_ONE_FIVE = 2,
SERIALPORT_STOPBITS_TWO = 3
} serialport_stop_bits_t;
typedef struct {
int baud;
int data_bits;
bool rtscts;
bool xon;
bool xoff;
bool xany;
bool dsrdtr;
bool hupcl;
serialport_parity_t parity;
serialport_stop_bits_t stop_bits;
} serial_port_connect_opt_t;
/*---------------------------------------------------------------------------*/
typedef struct serial_tx_memb {
struct serial_tx_memb *next;
uint8_t *data;
int len;
int remain;
} serial_tx_memb_t;
/*---------------------------------------------------------------------------*/
static uint8_t rx_buf[SERIAL_RX_BUF_SIZE];
static aboot_tiny_uart_rx_callback_t rx_cb;
static int serial_fd = -1;
static int fd_error;
/*---------------------------------------------------------------------------*/
static serial_tx_memb_t *tx_queue_list;
static serial_tx_memb_t **tx_queue = &tx_queue_list;
/*---------------------------------------------------------------------------*/
static inline void
queue_init(serial_tx_memb_t **queue)
{
*queue = NULL;
}
/*---------------------------------------------------------------------------*/
static inline bool
queue_is_empty(serial_tx_memb_t **queue)
{
return *queue == NULL ? true : false;
}
/*---------------------------------------------------------------------------*/
static inline serial_tx_memb_t *
queue_peek(serial_tx_memb_t **queue)
{
return *queue;
}
/*---------------------------------------------------------------------------*/
static inline serial_tx_memb_t *
queue_dequeue(serial_tx_memb_t **queue)
{
serial_tx_memb_t *l;
l = *queue;
if(*queue != NULL) {
*queue = ((serial_tx_memb_t *)*queue)->next;
}
return l;
}
/*---------------------------------------------------------------------------*/
static inline void
queue_enqueue(serial_tx_memb_t **queue, serial_tx_memb_t *element)
{
element->next = NULL;
if(*queue == NULL) {
*queue = element;
} else {
serial_tx_memb_t *l;
for(l = *queue; l->next != NULL; l = l->next) {
}
l->next = element;
}
}
/*---------------------------------------------------------------------------*/
static int
set_fd(fd_set *rset, fd_set *wset)
{
if(serial_fd > 0 && !fd_error) {
FD_SET(serial_fd, rset);
if(!queue_is_empty(tx_queue)) {
FD_SET(serial_fd, wset);
}
return 1;
} else {
return 0;
}
}
/*---------------------------------------------------------------------------*/
static void
serial_do_tx(void)
{
serial_tx_memb_t *p;
int cnt, offset;
p = queue_peek(tx_queue);
if(p) {
offset = p->len - p->remain;
cnt = write(serial_fd, p->data + offset, p->remain);
#if 0
unsigned int ii,j;
ii = p->remain;
if(ii > 128)
{
ii = 128;
}
char buff[512];
memset(buff, 0, sizeof(buff));
for(j=0;j<ii;j++)
{
sprintf(&buff[j], "%c", *(p->data + offset+j));
}
aboot_tiny_log_printf("Tlen=%d,data=%s", p->remain, buff);
#endif
if(cnt > 0) {
p->remain -= cnt;
if(p->remain == 0) {
queue_dequeue(tx_queue);
aboot_tiny_mem_free(p->data);
aboot_tiny_mem_free(p);
}
} else if(cnt < 0) {
aboot_tiny_log_printf("write error: error code %d\n", errno);
fd_error = errno;
}
}
}
/*---------------------------------------------------------------------------*/
static void
handle_fd(fd_set *rset, fd_set *wset)
{
int cnt;
if(serial_fd >= 0 && !fd_error) {
if(FD_ISSET(serial_fd, wset)) {
serial_do_tx();
if(queue_is_empty(tx_queue)) {
FD_CLR(serial_fd, wset);
}
}
if(FD_ISSET(serial_fd, rset)) {
cnt = read(serial_fd, rx_buf, sizeof(rx_buf));
#if 0
unsigned int ii,j;
ii = cnt;
if(ii > 64)
{
ii = 64;
}
char buf[256];
memset(buf, 0, sizeof(buf));
for(j=0;j<ii;j++)
{
sprintf(&buf[j], "%c,", rx_buf[j]);
}
aboot_tiny_log_printf("Rlen=%d,data=%s", cnt, buf);
#endif
if((cnt > 0) && rx_cb) {
rx_cb(rx_buf, cnt);
} else if(cnt < 0) {
aboot_tiny_log_printf("read error: error code %d\n", errno);
fd_error = errno;
}
}
}
}
/*---------------------------------------------------------------------------*/
static const struct select_callback serial_select_callback = {
set_fd,
handle_fd,
};
/*---------------------------------------------------------------------------*/
static int
to_baud_const(int baud)
{
switch(baud) {
case 0: return B0;
case 50: return B50;
case 75: return B75;
case 110: return B110;
case 134: return B134;
case 150: return B150;
case 200: return B200;
case 300: return B300;
case 600: return B600;
case 1200: return B1200;
case 1800: return B1800;
case 2400: return B2400;
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
#if defined(__linux__)
case 460800: return B460800;
case 500000: return B500000;
case 576000: return B576000;
case 921600: return B921600;
case 1000000: return B1000000;
case 1152000: return B1152000;
case 1500000: return B1500000;
case 2000000: return B2000000;
case 2500000: return B2500000;
case 3000000: return B3000000;
case 3500000: return B3500000;
case 4000000: return B4000000;
#endif
}
return -1;
}
/*---------------------------------------------------------------------------*/
static int
to_data_bits_const(int data_bits)
{
switch(data_bits) {
case 8: default: return CS8;
case 7: return CS7;
case 6: return CS6;
case 5: return CS5;
}
return -1;
}
/*---------------------------------------------------------------------------*/
static int
set_baudrate(int fd, serial_port_connect_opt_t *opt)
{
/* lookup the standard baudrates from the table */
int baud_rate = to_baud_const(opt->baud);
/* get port options */
struct termios options;
if(-1 == tcgetattr(fd, &options)) {
aboot_tiny_log_printf("Error: %s setting custom baud rate of %d", strerror(errno), opt->baud);
return -1;
}
if(-1 == baud_rate) {
aboot_tiny_log_printf("Error baud rate of %d is not supported on your platform", opt->baud);
return -1;
}
/* If we have a good baud rate set it and lets go */
cfsetospeed(&options, baud_rate);
cfsetispeed(&options, baud_rate);
/* throw away all the buffered data */
tcflush(fd, TCIOFLUSH);
/* make the changes now */
tcsetattr(fd, TCSANOW, &options);
return 1;
}
/*---------------------------------------------------------------------------*/
static int
set_default_baudrate(int fd)
{
/* Default BaudRate => 115200. Just for AP UART. */
int baud_rate = to_baud_const(115200);
/* get port options */
struct termios options;
if(-1 == tcgetattr(fd, &options))
return -1;
if(-1 == baud_rate)
return -1;
/* If we have a good baud rate set it and lets go */
cfsetospeed(&options, baud_rate);
cfsetispeed(&options, baud_rate);
/* throw away all the buffered data */
tcflush(fd, TCIOFLUSH);
/* make the changes now */
tcsetattr(fd, TCSANOW, &options);
return 1;
}
/*---------------------------------------------------------------------------*/
static int
setup(int fd, serial_port_connect_opt_t *opt)
{
int data_bits = to_data_bits_const(opt->data_bits);
if(data_bits == -1) {
aboot_tiny_log_printf("Invalid data bits %d\n", opt->data_bits);
return -1;
}
if(fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
aboot_tiny_log_printf("Set FD_CLOEXEC failed\n");
return -1;
}
/* Get port configuration for modification */
struct termios options;
tcgetattr(fd, &options);
/* IGNPAR: ignore bytes with parity errors */
options.c_iflag = IGNPAR;
/* ICRNL: map CR to NL (otherwise a CR input on the other computer will not terminate input) */
/* Future potential option */
/* options.c_iflag = ICRNL; */
/* otherwise make device raw (no other input processing) */
/* Specify data bits */
options.c_cflag &= ~CSIZE;
options.c_cflag |= data_bits;
options.c_cflag &= ~(CRTSCTS);
if(opt->rtscts) {
options.c_cflag |= CRTSCTS;
}
options.c_iflag &= ~(IXON | IXOFF | IXANY);
if(opt->xon) {
options.c_iflag |= IXON;
}
if(opt->xoff) {
options.c_iflag |= IXOFF;
}
if(opt->xany) {
options.c_iflag |= IXANY;
}
switch(opt->parity) {
case SERIALPORT_PARITY_NONE:
options.c_cflag &= ~PARENB;
break;
case SERIALPORT_PARITY_ODD:
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
break;
case SERIALPORT_PARITY_EVEN:
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
break;
default:
aboot_tiny_log_printf("Invalid parity setting %d", opt->parity);
return -1;
}
switch(opt->stop_bits) {
case SERIALPORT_STOPBITS_ONE:
options.c_cflag &= ~CSTOPB;
break;
case SERIALPORT_STOPBITS_TWO:
options.c_cflag |= CSTOPB;
break;
default:
aboot_tiny_log_printf("Invalid stop bits setting %d", opt->stop_bits);
return -1;
}
options.c_cflag |= CLOCAL; /* ignore status lines */
options.c_cflag |= CREAD; /* enable receiver */
options.c_oflag = 0;
/* ICANON makes partial lines not readable. It should be optional. */
/* It works with ICRNL. */
options.c_lflag = 0; /* ICANON; */
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
tcsetattr(fd, TCSANOW, &options);
if(set_baudrate(fd, opt) == -1) {
return -1;
}
return 1;
}
/*---------------------------------------------------------------------------*/
static int
serial_port_open(const char *dev, serial_port_connect_opt_t *opt)
{
assert(serial_fd == -1); /* previously port has not been closed */
serial_fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC | O_SYNC);
if(serial_fd < 0) {
return -1;
}
if(setup(serial_fd, opt) < 0) {
return -1;
}
rx_cb = NULL;
fd_error = 0;
aboot_tiny_log_printf("serial fd=%d rate=%d", serial_fd, opt->baud);
aboot_tiny_select_set_callback(serial_fd, &serial_select_callback);
queue_init(tx_queue);
return 0;
}
static void
serial_port_close(void)
{
serial_tx_memb_t *p;
if(serial_fd >= 0) {
aboot_tiny_select_set_callback(serial_fd, NULL);
rx_cb = NULL;
/* BaudRate = 115200bps. */
set_default_baudrate(serial_fd);
close(serial_fd);
serial_fd = -1;
while(1) {
p = queue_dequeue(tx_queue);
if(!p) {
break;
}
aboot_tiny_mem_free(p->data);
aboot_tiny_mem_free(p);
}
}
}
/*---------------------------------------------------------------------------*/
int
jacana_serialport_write(const uint8_t *buf, size_t len)
{
serial_tx_memb_t *p;
if(serial_fd < 0 || fd_error) {
return len;
}
p = aboot_tiny_mem_alloc(sizeof(serial_tx_memb_t));
if(!p) {
aboot_tiny_log_printf("out of memory\n");
return -1;
}
p->data = aboot_tiny_mem_alloc(len);
if(!p->data) {
aboot_tiny_mem_free((void *)p);
aboot_tiny_log_printf("out of memory\n");
return -1;
}
memcpy(p->data, (void *)buf, len);
p->len = p->remain = len;
queue_enqueue(tx_queue, p);
return len;
}
/*---------------------------------------------------------------------------*/
int
jacana_serialport_init(const char *dev, int baud,
aboot_tiny_uart_rx_callback_t cb)
{
serial_port_connect_opt_t connect_op;
connect_op.baud = baud;
connect_op.data_bits = 8;
connect_op.rtscts = false;
connect_op.xon = false;
connect_op.xoff = false;
connect_op.xany = false;
connect_op.dsrdtr = false;
connect_op.hupcl = false;
connect_op.parity = SERIALPORT_PARITY_NONE;
connect_op.stop_bits = SERIALPORT_STOPBITS_ONE;
if(!serial_port_open(dev, &connect_op)) {
rx_cb = cb;
return 0;
} else {
aboot_tiny_log_printf("open device \"%s\" failed\n", dev);
return -1;
}
}
/*---------------------------------------------------------------------------*/
void
jacana_serialport_exit(void)
{
serial_port_close();
}
/*---------------------------------------------------------------------------*/