The Termios man pages ( for canonical mode state:

Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).

My question is: when a piece of hardware outputs data to be canonical compliant - is it putting the 0xD0xA (CRLF) bytes at the beginning of the transmission line to tell the read() function that data is ready to be read?

I've not thought about this a lot before and have defaulted (potentially erroneously) to thinking that the 0xD0xA is at the end of the transmission line.

Solution 1

is it putting the 0xD0xA (CRLF) bytes at the beginning of the transmission line to tell the read() function that data is ready to be read?

Your question has essentially already been answered by my last comment to you in your other post.
Apparently you don't believe the man page or me, and are also unclear what "line delimiter", line termination, and EOL mean.

The "serial port" or "hardware" has no concept of a "beginning" or an "end" of the "transmission line". It's all just payload data to the U[S]ART.
Line termination only has context when using termios in canonical mode to read the serial terminal buffer.
See Linux serial drivers to understand how removed your userspace code is from the hardware.

Linux uses the newline character, or linefeed which has ASCII code 0x0A, as the line terminator, as clearly stated in the man page (which you have quoted).
Termios allows the definition of additional end-of-line characters, i.e. VEOL and VEOL2 for serial terminals.
Each and every occurrence of a line delimiter character can and will cause a (pending) canonical read() to return.
The line delimiter character will be the last character returned in the buffer, unless the user buffer is too small to contain the entire line.

The character defined for EOF, i.e. VEOF which defaults to the ASCII code 0x04 for EOT, is handled slightly different by termios.
The receipt of the EOF character causes a (pending) canonical read() to return just like a line delimiter character, but the EOF character is not stored in the returned buffer.
Hence, when the EOF is preceded by a line delimiter, read() will have a return code of zero, an actual empty line!

If you're such a Doubting Thomas, then you should cross-connect a pair of USB-RS232 adapters together, and test what happens when reading from a serial terminal using termios.
Use a terminal emulator program such as minicom on the first serial terminal to enter data, and use the following C program to see the canonical reads on the other serial terminal.

#define SERIALTERMINAL      "/dev/ttyUSB1"
#include <errno.h>
#include <fcntl.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int set_interface_attribs(int fd, int speed)
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= CLOCAL | CREAD;
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    tty.c_lflag |= ICANON | ISIG;  /* canonical input */
    tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);

    tty.c_iflag &= ~IGNCR;  /* preserve carriage return */
    tty.c_iflag &= ~INPCK;
    tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);   /* no SW flowcontrol */

    tty.c_oflag &= ~OPOST;

    tty.c_cc[VEOL] = 0;
    tty.c_cc[VEOL2] = 0;
    tty.c_cc[VEOF] = 0x04;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    return 0;

int main()
    char *portname = SERIALTERMINAL;
    int fd;
    int wlen;

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);

    /* simple output */
    wlen = write(fd, "Hello!\n", 7);
    if (wlen != 7) {
        printf("Error from write: %d, %d\n", wlen, errno);
    tcdrain(fd);    /* delay for output */

    /* simple canonical input */
    do {
        unsigned char buf[83];
        unsigned char *p;
        int rdlen;

        rdlen = read(fd, buf, sizeof(buf) - 1);
        if (rdlen > 0) {
            buf[rdlen] = 0;
            printf("Read %d:", rdlen);
            /* first display as hex numbers then ASCII */
            for (p = buf; rdlen-- > 0; p++) {
                printf(" 0x%x", *p);
                if (*p < ' ')
                    *p = '.';   /* replace any control chars */
            printf("\n    \"%s\"\n\n", buf);
        } else if (rdlen < 0) {
            printf("Error from read: %d: %s\n", rdlen, strerror(errno));
        } else {  /* rdlen == 0 */
            printf("Nothing read. EOF?\n");
        /* repeat read */
    } while (1);

Note that the program does not strip out '\r' characters (i.e. attribute IGNCR is cleared), but carriage return is not defined as a line delimiter either.
Hence carriage returns in this termios configuration have no special meaning, and are passed through just like any printable character.

So typing (the equivalent of) ABCDEFG^M^J is read as:

Read 9: 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0xd 0xa

123^Mabc^J is read as:

Read 8: 0x31 0x32 0x33 0xd 0x61 0x62 0x63 0xa

An alternate termios configuration could strip out the carriage returns or treat the carriage returns as a line delimiter.