Reading keyboard input from /dev/tty0? - linux

I am writing a remote control driver for an embedded system with Linux 3.X. It's using an GUI which reads keyboard input from /dev/tty0. Related source codes of this GUI about opening a keyboard device are as follows.
static int TTY_Open (const char *unused)
{
if (geteuid() == 0) /* is a super user, try to open the active console */
tty_dev = "/dev/tty0";
else /* not a super user, so try to open the control terminal */
tty_dev = "/dev/tty";
kbd_fd = open(tty_dev, O_RDONLY | O_NOCTTY);
if (kbd_fd < 0)
return -1;
if (tcgetattr(kbd_fd, &startup_termios) < 0)
goto err;
work_termios = startup_termios;
work_termios.c_lflag &= ~(ICANON | ECHO | ISIG);
work_termios.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON);
work_termios.c_iflag |= IGNBRK;
work_termios.c_cc[VMIN] = 0;
work_termios.c_cc[VTIME] = 0;
if(tcsetattr(kbd_fd, TCSAFLUSH, &work_termios) < 0)
goto err;
/* Put the keyboard into MEDIUMRAW mode. Despite the name, this
* is really "mostly raw", with the kernel just folding long
* scancode sequences (e.g. E0 XX) onto single keycodes.
*/
ioctl (kbd_fd, KDGKBMODE, &startup_kbdmode);
if (ioctl(kbd_fd, KDSKBMODE, K_MEDIUMRAW) < 0)
goto err;
return kbd_fd;
err:
close(kbd_fd);
kbd_fd = 0;
return -1;
}
My driver was implemented with Linux input subsystem. In its interrupt handler, input_report_key is used to report key event.
Then I find it surprising that the GUI can read both inputs from an USB keyboard and the remote driver! But the devices corresponding to the keyboard and remote control are /dev/input/eventX. How could it read those inputs from /dev/tty0???
I find it quite confusing and can not get answers from Google. Can someone explain it?

Related

Linux usb serial line input may lose the trailing new line

I am testing a serial communication protocol based on printable characters only.
The setup has a pc connected to an arduino board by USB. The PC USB serial is operated in canonical mode, with no echo, no flow control, 9600 baud.
Since a read timeout is requested, pselect is called before the serial read. The arduino board simply echoes back every received character without any processing. The PC OS is Linux Neon with kernel 5.13.0-40-generic.
When lines of a specific length are transmitted from the PC and echoed back by the arduino, they are received correctly except for the final new line that is missing.
A further read, returns an empty line (the previously missing NL).
Lines with different length are transmitted and received correctly, including the trailing NL.
This behavior is fully repeatable and stable. The following code reproduce the problem for a line transmitted with a length of 65 characters (including NL) and received with a length of 64 (NL missing). Other line lengths work fine.
Thanks for any hints.
/* remote serial loop test 20220626 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/select.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#define TX_MINLEN 63
#define TX_MAXLEN 66
#define DATA_MAXLEN 128
#define LINK_DEVICE "/dev/ttyUSB0"
#define LINK_SPEED B9600
#define RECEIVE_TIMEOUT 2000
int main()
{
int wlen;
int retval;
int msglen;
uint8_t tx_data[257] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
for (int i=16; i < 256; i++) tx_data[i] = tx_data[i & 0xf];
uint8_t rx_data[257];
/* serial interface */
char * device;
int speed;
int fd;
fd_set fdset;
struct timespec receive_timeout;
struct timespec *p_receive_timeout = &receive_timeout;
struct termios tty;
/* open serial device in blocking mode */
fd = open(LINK_DEVICE, O_RDWR | O_NOCTTY);
if (fd < 0) {
printf("Error opening %s: %s\n",LINK_DEVICE,strerror(errno));
return -1;
}
/* prepare serial read by select to have read timeout */
FD_ZERO(&(fdset));
FD_SET(fd,&(fdset));
if (RECEIVE_TIMEOUT >= 0) {
p_receive_timeout->tv_sec = RECEIVE_TIMEOUT / 1000;
p_receive_timeout->tv_nsec = RECEIVE_TIMEOUT % 1000 * 1000000;
}
else
p_receive_timeout = NULL;
/* get termios structure */
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
/* set tx and rx baudrate */
cfsetospeed(&tty, (speed_t)LINK_SPEED);
cfsetispeed(&tty, (speed_t)LINK_SPEED);
/* set no modem ctrl, 8 bit, no parity, 1 stop */
tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
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 */
/* canonical mode: one line at a time (\n is line terminator) */
tty.c_lflag |= ICANON | ISIG;
tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
/* input control */
tty.c_iflag &= ~IGNCR; /* preserve carriage return */
tty.c_iflag &= ~INPCK; /* no parity checking */
tty.c_iflag &= ~INLCR; /* no NL to CR traslation */
tty.c_iflag &= ~ICRNL; /* no CR to NL traslation */
tty.c_iflag &= ~IUCLC; /* no upper to lower case mapping */
tty.c_iflag &= ~IMAXBEL;/* no ring bell at rx buffer full */
tty.c_iflag &= ~(IXON | IXOFF | IXANY);/* no SW flowcontrol */
/* no output remapping, no char dependent delays */
tty.c_oflag = 0;
/* no additional EOL chars, confirm EOF to be 0x04 */
tty.c_cc[VEOL] = 0x00;
tty.c_cc[VEOL2] = 0x00;
tty.c_cc[VEOF] = 0x04;
/* set changed attributes really */
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
/* wait for serial link hardware to settle, required by arduino reset
* triggered by serial control lines */
sleep(2);
/* empty serial buffers, both tx and rx */
tcflush(fd,TCIOFLUSH);
/* repeat transmit and receive, each time reducing data length by 1 char */
for (int l=TX_MAXLEN; l > TX_MINLEN - 1; l--) {
/* prepare data: set EOL and null terminator for current length */
tx_data[l] = '\n';
tx_data[l+1] = 0;
/* send data */
int sent = write(fd,tx_data,l+1);
/* receive data */
/* wait for received data or for timeout */
retval = pselect(fd+1,&(fdset),NULL,NULL,p_receive_timeout,NULL);
/* check for error or timeout */
if (retval < 0)
printf("pselect error: %d - %s\n",retval,strerror(errno));
else if (retval == 0)
printf("serial read timeout\n");
/* there is enough data for a non block read: do read */
msglen = read(fd,&rx_data,DATA_MAXLEN);
/* check rx data length */
if (msglen != l+1)
printf("******** RX ERROR: sent %d, received %d\n",l+1,msglen);
else
continue;
/* check received data, including new line if present */
for (int i=0; i < msglen; i++) {
if (tx_data[i] == rx_data[i])
continue;
else {
printf("different rx data:|%s|\n",rx_data);
break;
}
}
/* clear RX buffer */
for (int i=0; i < msglen + 1; i++) rx_data[i] = 0;
}
}
When performing stream-based communication, be it via pipes, serial ports, or TCP sockets, you can never rely on reads to always return a full "unit of transmission" (in this case a line, but could also be a fixed-size block). The reason is that stream-based communication can always be split by any part of the transmission stack (even potentially the sender) into multiple blocks, and there is never a guarantee that a single read will always read a full block.
For example, you could always run into the race condition that your microcontroller is still sending parts of the message when you call read(), so not all characters are read exactly. In your case, that's not what you're seeing, because that would be more of a stochastic phenomenon (that would be worse with an increased interrupt load on the computer) and not so easily reproducible. Instead, because you're talking about the number 64 here, you're running into the static buffer size used in the kernel's tty driver that will only ever return at most 64 bytes at once, regardless of what the specified read size actually is. However, in other cases it could still be that you'll see additional failures by the kernel returning only the first couple of characters of a line in the first read(), and the rest in the second, depending on precise timing details -- you've probably not seen that yet, but it's bound to happen at some point.
The only reliable way to properly implement communication protocols in streaming situations (serial port, pipes, TCP sockets, etc.) is to consider the following:
For fixed-size data (e.g. communication units that are always N bytes in size) to loop around a read() call until you've read exactly the right amount of bytes (reads that follow an incomplete read would obviously ask for less bytes than the original read, just to make up the difference)
For variable-size data (for example communication units that are separated by a line end character) you have two options: either you read only one character at a time until you reach the end-of-line character (inefficient, uses lots of syscalls), or you keep track of the communication state via a large enough buffer that you constantly fill with read() operations until the buffer contains a line-end character, at which point you remove that line from the buffer (but keep the rest) and process that.
As a complete aside, if you're doing anything with serial communication, I can very much recommend the excellent libserialport library (LGPLv3 license) that makes working with serial ports a lot easier -- and has the benefit of being cross-platform. (Doesn't help with your issue, just thought that I'd mention it.)
Upgrading from linux kernel version 5.13.0-40-generic to 5.13.0-51-generic solved the problem.

Linux Serial Port Blocked using termios.h configuration

I'm writing an embedded Linux application that (1) opens a serial connection to another device, (2) sends a known command, (3) checks port for incoming characters (response) until expected response phrase or character is detected, (4) repeats step 2 and 3 until a series of commands are sent and responses received, (5) then closes the port.
My app would go through some cycles of the above sequence and every now and then it would be waiting for a response (reading) when all of a sudden the communication stops and my software faults out because of my built-in timeout logic.
Is there anything in my port configuration that would cause the port to be blocked due to specific byte sent (possibly due to electrical noise)?
Here is how I'm opening my ports (showing configurations via termios.h):
struct termios options;
fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == -1) {
debug() << "Port open failed!"
return FAIL;
}
debug() << "Port Opened Successful"
fcntl(fd, F_SETFL, 0); // This setting interacts with VMIN and VTIME below
// Get options
tcgetattr(fd, &options);
// Adjust Com port options
options.c_cflag |= (CLOCAL | CREAD); // Program will not "own" port, enable reading on port
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Sets RAW input mode (does not treat input as a line of text with CR/LF ending)
options.c_oflag &= ~ OPOST; // Sets RAW ouput mode (avoids newline mapping to CR+LF characters)
options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turns off SW flow c
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 10;
// Set options
tcsetattr(fd, TCSANOW, &options);
//return fd;
return SUCCEED;
I can't figure out why the communication all of a sudden just freezes up and then goes away when I cycle power to my device. Thanks all!
More info - here are my read and write functions:
int Comm::Receive(unsigned char* rBuf)
{
int bytes;
ioctl(fd, FIONREAD, &bytes);
if (bytes >= 1)
{
bytes = read(fd, rBuf, 1);
if (bytes < 0)
return READ_ERR;
return SUCCEED;
}
else
return NO_DATA_AVAILABLE;
}
int Comm::Send(int xCt, unsigned char* xBuf)
{
int bytes;
if (fd == -1)
return FAIL;
bytes = write(fd, xBuf, xCt);
if (bytes != xCt)
return FAIL;
else
return SUCCEED;
}
Welcome to the joys of serial ports...
Thought 1: wrap your read calls with a select ()
Thought 2: Unset the ICANON flag in tcsetattr, and set a VTIME attribute for a deliberate timeout (and then, obviously, handle it)
Thought 3: Nothing about serial comms ever works perfectly.
I also had a similar problem with sending command to devices and reading responses from the device. Please refer to below SOF post and the answer this was working for my problem.
In these cases, We have to care about the protocol which we are going to use for device communication (send and receive). If we can send commands successfully and we didn't receive a response with a noise from device implies there is something wrong in the data packet which was sent to the device. First of all check the protocol specification first and create a byte array for a simple command (like make a beep sound) and send it.
Send data to a barcode scanner over RS232 serial port
I can do something for you If you can post your complete source code with the output.
Enjoy the code. Thanks.

Retrieval of the error counters via TIOCGICOUNT returns always error (-1)

I have come across a show stopping problem when developing an interface application for a USB to RS422 converter module.
I need to retrieve the UART error counters for framing, overrun, parity and break errors. But the call to ioctl always returns -1 and the counter values from the retrieved struct are jumping to very big numbers.
The code i am using to retrieve the counters is the following:
struct serial_icounter_struct counters;
int ret = ioctl(portDescriptor, TIOCGICOUNT, &counters);
To set the portDescriptor i am using a similar code to:
int portDescriptor = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
struct termios new_port_settings;
//clear the new struct
memset(&new_port_settings, 0, sizeof(new_port_settings));
//set port settings
new_port_settings.c_cflag = B57600 | CS8 | CLOCAL | CREAD;
new_port_settings.c_oflag = 0;
new_port_settings.c_lflag = 0;
new_port_settings.c_cc[VMIN] = 0;
new_port_settings.c_cc[VTIME] = 0;
int error = tcsetattr(portDescriptor, TCSANOW, &new_port_settings)
Sometimes we also need to enable flow control or parity e.g.
new_port_settings.c_cflag = new_port_settings.c_cflag | CRTSCTS;
I have tried the code on a Ubuntu 11.10 32bit and on a SLES11 SP1 64bit, both with the FTDI_SIO kernel module.
Is anybody aware of any kind of issue regarding the usage of TIOCGICOUNT or am I doing something wrong?

Serial port not reading

I've got this strange issue with reading from a serial port on Linux. About 50% of the time, I won't get any data back from the serial port when I'm reading from it, yet if I use GTKTerm I get data back 100% of the time. I've double-checked my settings, and the terminal settings that I'm using are the exact same that GTKTerm uses, so I'm mystified as to why this would occur. The only thing that I could think of is that the port is set in canonical mode, not raw mode, but I set it properly - the device that I'm reading from does not send back newlines with all of the commands. However, everything is working properly when the device sends back commands with a newline character.
Here's the code I'm using:
int fd = open(serial_port, O_RDWR );
if( fd < 0 ){
perror("open");
}
//Set the serial port settings
struct termios newio;
if( tcgetattr( fd, &newio ) < 0 )
perror("tcgetattr");
if( cfsetospeed( &newio, B9600 ) < 0 )
perror("cfsetospeed");
newio.c_cflag &= ~CSTOPB;
newio.c_cflag &= ~CSIZE;
newio.c_cflag |= CS8;
newio.c_cflag |= CREAD;
newio.c_iflag |= IGNPAR;
newio.c_iflag |= IGNBRK;
newio.c_iflag &= ~BRKINT;
newio.c_iflag &= ~ICRNL;
newio.c_iflag &= ~IXON;
newio.c_cflag |= CLOCAL;
newio.c_oflag = 0;
newio.c_lflag = 0;
newio.c_cc[VTIME] = 0;
newio.c_cc[VMIN] = 1;
if( tcsetattr( fd, TCSANOW, &newio ) < 0 )
perror("tcsetattr");
tcflush( fd, TCOFLUSH );
tcflush( fd, TCIFLUSH );
Reading code (in separate thread)
void* thr(void* ign){
char buffer[10];
while( 1 ){
int got = read(fd, buffer, 10);
buffer[got] = 0;
printf("got %s\n", buffer);
}
}
Well, I found out the problem.
For some bizzare reason, the device that I'm sending to doesn't seem to have a big buffer or something; it turns out that when I send commands as a string all at once, I won't always get a response back. Sleeping between sending characters fixes the problem(though I admit, it's not a very good solution). What was probably happening was that I was getting interrupted at points, which would allow enough time for the device to process some of its input. GTKTerm will send out each character as it is received, and it's impossible to type fast enough for this error to occur.

Linux serial port buffer not empty when opening device

I have a system where I am seeing strange behavior with the serial ports that I don't expect. I've previously seen this on occasion with usb-to-serial adapters, but now I'm seeing it on native serial ports as well, with much greater frequency.
The system is set up to run automated tests and will first perform some tasks that cause a large amount of data to be outputted from the serial device while I do not have the ports open. The device will also reset itself. Only the tx/rx lines are connected. There is no flow control.
After these tasks complete, the testware opens the serial ports and immediately fails because it gets unexpected responses. When I reproduce this, I found that if I open the serial port in a terminal program, I see several kilobytes of old data (that appears to have been sent when the port was closed) immediately flushed out. Once I close this program, I can then run the tests as expected.
What could cause this to happen? How does Linux handle buffering the serial port when the device is closed? If I opened a device, made it send output, and then closed it without reading from it, would this cause the same problem?
The Linux terminal driver buffers input even if it is not opened. This can be a useful feature, especially if the speed/parity/etc. are set appropriately.
To replicate the behavior of lesser operating systems, read all pending input from the port as soon as it is open:
...
int fd = open ("/dev/ttyS0", O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0)
exit (1);
set_blocking (fd, 0); // disable reads blocked when no input ready
char buf [10000];
int n;
do {
n = read (fd, buf, sizeof buf);
} while (n > 0);
set_blocking (fd, 1); // enable read blocking (if desired)
... // now there is no pending input
void set_blocking (int fd, int should_block)
{
struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0)
{
error ("error %d getting term settings set_blocking", errno);
return;
}
tty.c_cc[VMIN] = should_block ? 1 : 0;
tty.c_cc[VTIME] = should_block ? 5 : 0; // 0.5 seconds read timeout
if (tcsetattr (fd, TCSANOW, &tty) != 0)
error ("error setting term %sblocking", should_block ? "" : "no");
}

Resources