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


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:


#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;
}
view raw tcdrain.c hosted with ❤ by GitHub

Output:


You can see as the number of bytes increased, it took a longer time to complete tcdrain system call.

Comments

Popular posts from this blog

bb.utils.contains yocto

Difference between RDEPENDS and DEPENDS in Yocto

PR, PN and PV Variable in Yocto