Serial Port Programming Part 4 - tcdrain - example, internal implementation
The tcdrain will block the process until all the data which is present in the Linux TTY buffer is written to the hardware. tcdrain is a C Library function which translates it to ioctl with command argument set to 'TCSBRK'
Let's see how tcdrain is internally implemented in kernel:
'tty_ioctl' function present in drivers/tty/tty_io.c handles all the ioctl calls from Serial Port
You can see from the above screenshot, it calls tty_wait_until_sent function, it is defined in drivers/tty/tty_ioctl.c file
Let's see how tcdrain is internally implemented in kernel:
'tty_ioctl' function present in drivers/tty/tty_io.c handles all the ioctl calls from Serial Port
You can see from the above screenshot, it calls tty_wait_until_sent function, it is defined in drivers/tty/tty_ioctl.c file
tty_wait_until_sent uses wait_event_interruptible_timeout until the tty_chars_in_buffer function returns 0. tty_chars_in_buffer returns the number of bytes present in the device private output queue. This is implemented by the particular serial port device driver. If we use 'pl2303' device, then it will be in the pl2303 usb serial port driver.
Let's write a C Code to understand better. Connect two serial port devices TX and RX.
Below code accepts number of bytes to transmit from user, fills 'A' in it and transmit it. It then calls tcdrain, captures the timestamp before and after tcdrain, and displays the difference in time
Code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <sys/types.h> | |
#include <termios.h> | |
#include <fcntl.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
#define SERIAL_DEVICE "/dev/ttyUSB1" | |
struct timespec diff(struct timespec start, struct timespec end) | |
{ | |
struct timespec temp; | |
if ((end.tv_nsec-start.tv_nsec)<0) { | |
temp.tv_sec = end.tv_sec-start.tv_sec-1; | |
temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec; | |
} else { | |
temp.tv_sec = end.tv_sec-start.tv_sec; | |
temp.tv_nsec = end.tv_nsec-start.tv_nsec; | |
} | |
return temp; | |
} | |
int main() | |
{ | |
struct termios serial_port_settings; | |
struct timespec start_time; | |
struct timespec end_time; | |
struct timespec diff_time; | |
int fd; | |
int retval; | |
char *buf = NULL; | |
int bufsize = 0; | |
fd = open(SERIAL_DEVICE, O_RDWR); | |
if (fd < 0) { | |
perror("Failed to open SERIAL_DEVICE"); | |
exit(1); | |
} | |
retval = tcgetattr(fd, &serial_port_settings); | |
if (retval < 0) { | |
perror("Failed to get termios structure"); | |
exit(2); | |
} | |
//setting baud rate to B38400 | |
retval = cfsetospeed(&serial_port_settings, B38400); | |
if (retval < 0) { | |
perror("Failed to set 38400 output baud rate"); | |
exit(3); | |
} | |
retval = cfsetispeed(&serial_port_settings, B38400); | |
if (retval < 0) { | |
perror("Failed to set 38400 input baud rate"); | |
exit(4); | |
} | |
serial_port_settings.c_lflag |= ICANON; | |
serial_port_settings.c_oflag |= OCRNL; | |
serial_port_settings.c_oflag |= OLCUC; | |
retval = tcsetattr(fd, TCSANOW, &serial_port_settings); | |
if (retval < 0) { | |
perror("Failed to set serial attributes"); | |
exit(5); | |
} | |
printf("Successfully set the baud rate\n"); | |
loop: | |
printf("Enter size of the buffer:"); | |
scanf("%d", &bufsize); | |
if (bufsize <= 0) { | |
printf("Buffer size should be greater > 0\n"); | |
goto loop; | |
} | |
buf = malloc(bufsize); | |
if (!buf) { | |
perror("Failed to allocate buffer\n"); | |
exit(6); | |
} | |
memset(buf, 'A', bufsize); | |
buf[bufsize-2] = '\n'; | |
buf[bufsize-1] = '\0'; | |
clock_gettime(CLOCK_MONOTONIC, &start_time); | |
retval = write(fd, buf, bufsize); | |
if (retval < 0) { | |
perror("write on SERIAL_DEVICE failed"); | |
exit(7); | |
} | |
tcdrain(fd); | |
clock_gettime(CLOCK_MONOTONIC, &end_time); | |
diff_time = diff(start_time, end_time); | |
printf("tcdrain took %ld seconds, %lu nanoseconds\n", | |
diff_time.tv_sec, diff_time.tv_nsec); | |
free(buf); | |
goto loop; | |
close(fd); | |
return 0; | |
} |
Output:
You can see as the number of bytes increased, it took a longer time to complete tcdrain system call.
Comments
Post a Comment