| #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(); |
| } |
| /*---------------------------------------------------------------------------*/ |