configfs USB gadget write() call hangs - c

I'm trying to emulate a HID device (a gamepad for PS3) from a Raspberry Pi Zero using configfs with kernel v4.19.40 (pretty much the same process as described here). Following the instructions in gadget_hid.txt, I was able to get a working prototype that is able to send button presses from the Pi Zero to a USB host (a PS3). However, when some state changes on the USB host (i.e. a certain game is started), the program freezes. strace says that it is stuck on a call to write() to the configfs HID file (/dev/hidgX, in my case /dev/hidg0). How could the USB host block the device from sending data? If the USB host is not ready to receive, won't it just ignore the data sent?
In debugging I found this patch which seemed to be related but I saw no change after applying it.
Here is a small code sample (the full version can be found in gadget_hid.txt) that produces the same problem:
int main(int argc, const char *argv[]) {
int fd = 0;
char report[8];
int to_send = 8;
fd_set rfds;
if ((fd = open("/dev/hidg0", O_RDWR, 0666)) == -1) {
perror("/dev/hidg0");
return 3;
}
while (1) {
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
// Generate some data to send
fill_report(&report);
// Gets stuck here
write(fd, report, to_send);
}
close(fd);
return 0;
}

Related

Linux on RPi debian, hidraw write() to USB device outputs a few junk characters to /dev/hidraw0 which if not cleared jam the device

We have a set of USB devices which we monitor using a RPi. The monitoring code polls the devices using hidraw direct interface about once a second. The protocol uses 64 byte packets to send commands and receive data and all responses are 64 bytes long at most.
The same scheme works fine under Windows using the Windows HID driver. On Linux however we use hidraw and find that the device interface gets jammed after a short time resulting in unsuccessful write{}s to the device.
After a lot of investigation I came across a recommendation to try to follow the communication between a host and an hidraw device using this in a terminal:
sudo cat /dev/hidraw0
As it turns out, running this command outputs 4-8 bytes of unreadable characters to the terminal every write() and unexpectedly it also clears the jam for hidraw0. All subsequent write()'s and read()'s to that device work flawlessly.
If that device is disconnected and then reconnected the jam condition returns shortly thereafter. I have single stepped the code and verified that the "junk" is output during the execution of the write().
I tried to add fsync() calls before and after the write() in hope to clear the buffers and avoid this issue but that did not help. The code for the write() and subsequent read() is standard as follows:
#define USB_PACKET 64
#define USB_WRDELAY 10 //ms
FILE* fd;
int errno, res;
char packet[USB_PACKET];
fd = 0;
/* Open the Device with non-blocking reads. */
fd = open("/dev/hidraw0", O_RDWR|O_NONBLOCK);
if (fd < 0) {
perror("Unable to open device");
return 0; // failure
}
memset(packet, 0x0, sizeof(packet));
packet[0] = 0x34; // command code - request for USB device status bytes
fsync();
res = write(fd, &packet, sizeof(packet));
fsync();
if (res < 0) {
printf("Error: %d in USB write()\n", errno);
close(fd);
return 0; // failure
} else {
usleep(1000*USB_WRDELAY ); // delay gives OS and device time to respond
res = read(fd, &packet, sizeof(packet));
if (res < 0) {
printf("Error: %d in USB read()\n", errno);
close(fd);
return 0; // failure
} else {
// good read, packet holds the response data
// process the device data
close(fd);
return 1; // OK
}
}
return 0; // failure
This is a sample of the gibberish we read on the terminal running the cat command for each executed write():
4n��#/5 �
I am not understanding where this junk comes from and how to get rid of it. I tried several things that did not work out such as adding a read() with a timeout before the write - hoping it is some data left from a previous incomplete read().
Also tried to write a smaller buffer as I need only send only a 2 byte command as well as adding a delay between the open() and write().
Unfortunately using the cat in the terminal interferes with the hot plug/unplug detection of the USB devices so it is not a solution we can use in deployment.
I'll appreciate any words of wisdom on this.

Handling USB cable not connected on linux driver

What i need:
A usb based communiction device from an embedded target (running linux) to a host (running windows)
What i have:
A tty-device based on community drivers (u_serial.c, f_acm.c) and a userspace program utilizing the open/close/write/read commands.
The problem:
Userspace daemon successfully opens the tty device and reads from it even if the usb cable is not connected. if usb cable is connected while this happens - all usb enumeration is on hold until i cancel the program (cancel the read(), that is).
the same behavior happens when tty is only open()ed, without read().
What i saw:
The open() function of my device is defined like the u_serial.c open().
it looks somthing like this:
static int gs_open(struct tty_struct *tty, struct file *file)
{
/*
*definitions
*/
/*
*basic error checks
*/
//check if the tty device is already open/not opened/currently being opened and deal with each
//if not open:
//<real code starts:>
/* Do the "real open" */
spin_lock_irq(&port->port_lock);
/* allocate circular buffer on first open */
if (port->port_write_buf.buf_buf == NULL) {
spin_unlock_irq(&port->port_lock);
status = gs_buf_alloc(&port->port_write_buf, WRITE_BUF_SIZE);
spin_lock_irq(&port->port_lock);
if (status) {
pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n",
port->port_num, tty, file);
port->openclose = false;
goto exit_unlock_port;
}
}
tty->driver_data = port;
port->port_tty = tty;
port->open_count = 1;
port->openclose = false;
/* if connected, start the I/O stream */
if (port->port_usb) {
struct gserial *gser = port->port_usb;
pr_debug("gs_open: start ttyGS%d\n", port->port_num);
gs_start_io(port);
if (gser->connect)
gser->connect(gser);
}
pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file);
status = 0;
exit_unlock_port:
spin_unlock_irq(&port->port_lock);
return status;
}
Some not related code was removed. see source here.
Note that even if usb is not connected (port->port_usb is 0) - we continue and return 0, which means everything is ok.
also note that if usb is not connected, we do not execute gs_start_io(), which is used to do the actual read from device, and works only if usb is connected.
So one question here is - if the kernel is built with no errors on open() command while usb cable is not connected - why is it holding when i have an open descriptor to the device?
i would assume that either it would fail to open the device in the first place, or ignore the situation and continue the usb connection normally.
also, if this is the correct behavior - how can i deal with/know if the cable is disconnected?
A similar discussion was held here some years ago, with no real conlclusions..
And a final thought - the issue can be dealt with if the userspace program will be notified when usb is connected, but this doesn't mean the current behavior is right.
thanks.

Opening a serial port on OS X hangs forever without O_NONBLOCK flag

I have a serial to USB converter (FTDI, drivers installed from http://www.ftdichip.com/Drivers/VCP.htm) connecting a serial device to a MacBook Air. It shows up on the MacBook as both /dev/cu.usbserial-A4017CQY and /dev/tty.usbserial-A4017CQY. All behaviour I describe is identical regardless of which of these two I use.
Edit: Using /dev/cu.* did solve the problem. I'm not sure why it seemed not to work when I first posted this question. Thanks to duskwuff for pointing me in the right direction, though he has his TTY names backwards: /dev/tty.* will wait for flow control, while /dev/cu.* will not.
The first problem I encountered was that the syscall to open() would block forever if I did not use the O_NONBLOCK flag. Using the flag, I get a good file descriptor, but write() does not seem to actually write (though it returns just fine claiming to have written the bytes), and read() fails with the error "Resource temporarily unavailable".
stty -af /dev/cu.usbserial-A4017CQY shows the settings just fine, but if I try to change them with a command like stty -f /dev/cu.usbserial-A4017CQY -clocal, they do appear to be changed when displayed with a successive call to stty.
If I use select() to wait for the device to become ready before reading/writing, it reports after a short time that it is ready to write, but never to read. This gels with how write() completes without complaint, while read() fails. Note that the data written never does actually make it to the device.
The entire test program I wrote to debug this is below:
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
#define SYSCALL(A) do { ret = A; if (ret == -1) { perror(#A); return -1; } else printf("%s returned %d\n", #A, ret); } while (0)
int ret; /* necessary for SYSCALL */
int main()
{
struct termios tio;
char buf[256];
int fd = open("/dev/cu.usbserial-A4017CQY",
O_RDWR | O_NOCTTY | O_NONBLOCK);
fd_set rfds, wfds, xfds;
struct timeval to;
to.tv_sec = 5;
to.tv_usec = 0;
SYSCALL(tcgetattr(fd, &tio));
cfmakeraw(&tio);
tio.c_cflag = CS8|CREAD|CLOCAL;
tio.c_cc[VMIN] = 1;
tio.c_cc[VTIME] = 1;
cfsetispeed(&tio, B115200);
cfsetospeed(&tio, B115200);
SYSCALL(tcsetattr(fd, TCSANOW, &tio));
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&xfds);
FD_SET(fd, &rfds);
FD_SET(fd, &wfds);
FD_SET(fd, &xfds);
int ret = select(fd+1, &rfds, NULL, &xfds, &to);
if (ret == -1) perror("select");
else if (ret > 0)
{
if(FD_ISSET(fd, &rfds))
puts("Ready to read");
if(FD_ISSET(fd, &wfds))
puts("Ready to write");
if(FD_ISSET(fd, &xfds))
puts("Exception!");
}
else puts("Timed out!");
SYSCALL(write(fd, "/home\n", 5));
SYSCALL(read(fd, buf, 256));
return 0;
}
You have a flow control issue. Either loop back join RTS/CTS and DTR/DSR/CD on your cable, have the other end provide control signals, or, as #duskwuff suggests, use the device that ignores flow control.
I see that you are setting CLOCAL -- that should work, but some USB devices do not do the right thing. Your description is consistent with the device waiting for modem control signals.

Serial communication buffer data out of order

I'm trying to implement a simple SLAM project with Arduino and C on Linux Mint 15.
The Arduino project is sending data to notebook via bluetooth (serial). The data is read by a C program. In Arduino serial, the data is shown correctly, but in the notebook, the received data is wrong. (In image, white is Arduino data. The terminal shows the 'received' data.)
I'm sending d080x096y099z035 (for example) and receiving 99z0356y0999z035 (out of order?).
So, I have some questions:
What can I do to make the read()command in C, read the data in the correct order and length? (order: d000x000y000z000, length = 16)
In the Arduino sending function, are there length differences using Serial.print(char buffer[]) and Serial.println(char buffer[])? (Like adding a '\n' or something else at the end of buffer?)
Should I use the delay() function in the Arduino code or in the C code?
In Arduino:
...
int buffer_size = 17;
char buffer[17];
//void setup()
void loop(){
//create the string resp = "d000x111y222z333"
...
resp.toCharArray(buffer, buffersize);
bluetooth.print(buffer);
delay(200);
}
In C program:
...
int fd = open("/dev/rfcomm4", O_RDONLY | O_NOCTTY | O_NDELAY);
printf("fd code %d\n", fd);
if (fd == -1)
{
gchar *msg = "open_port: Unable to open /dev/rfcomm4";
gtk_label_set_text(GTK_LABEL(label), msg);
perror("error: ");
}
char buffer[17];
int n;
printf("entering in loop...\n");
while (1)
{
n = read(fd, buffer, sizeof(buffer));
printf("%s\n", buffer);
}
Sorry I'm not an expert but just a few ideas you might check concering your questions:
to 1) I guess it might be a problem with encoding, as Python AFAIK expects files to be unicode. So try open (.... ,encoding='ascii') or whatever encoding you use
Please also pay attention that you might block the GTK mainthread, that causes heavy delays in your UI. So I recommend creating a own thread for reading the serial port and filling an internal buffer, that get's rendered by the GTK mainthread, if you send an update request:
http://www.pardon-sleeuwaegen.be/antoon/python/page0.html

init, read and write for linux serial device with C

I'm working on a new project where I want to make a connection with an FTDI which is connected to my debian machine. I am intending to write the code with C, not C++. Here lies my problem. All the examples I find are incomplete or are made for a c++ compiler in stead of the GCC compiler.
The goal is to talk to my microcontroller which is connected to the FTDI. For debugging i want to start building a linux application which is able to:
initialize a serial connection on startup with ttyUSB1
send a character string
display character strings when they are received by the pc
save the communication to a .txt file
Is there any example code or tutorial to make this?
If I succeed I will defenetly place the code here so that new viewers can use it to!
Edit:
Like I said I would post the code if I had it, and this is what worked for me:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#define MODEM "/dev/ttyUSB0"
#define BAUDRATE B115200
int main(int argc,char** argv)
{
struct termios tio;
struct termios stdio;
struct termios old_stdio;
int tty_fd, flags;
unsigned char c='D';
tcgetattr(STDOUT_FILENO,&old_stdio);
printf("Please start with %s /dev/ttyS1 (for example)\n",argv[0]);
memset(&stdio,0,sizeof(stdio));
stdio.c_iflag=0;
stdio.c_oflag=0;
stdio.c_cflag=0;
stdio.c_lflag=0;
stdio.c_cc[VMIN]=1;
stdio.c_cc[VTIME]=0;
tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // make the reads non-blocking
memset(&tio,0,sizeof(tio));
tio.c_iflag=0;
tio.c_oflag=0;
tio.c_cflag=CS8|CREAD|CLOCAL; // 8n1, see termios.h for more information
tio.c_lflag=0;
tio.c_cc[VMIN]=1;
tio.c_cc[VTIME]=5;
if((tty_fd = open(MODEM , O_RDWR | O_NONBLOCK)) == -1){
printf("Error while opening\n"); // Just if you want user interface error control
return -1;
}
cfsetospeed(&tio,BAUDRATE);
cfsetispeed(&tio,BAUDRATE); // baudrate is declarated above
tcsetattr(tty_fd,TCSANOW,&tio);
while (c!='q'){
if (read(tty_fd,&c,1)>0){
write(STDOUT_FILENO,&c,1); // if new data is available on the serial port, print it out
printf("\n");
}
if (read(STDIN_FILENO,&c,1)>0){
write(tty_fd,&c,1);//if new data is available on the console, send it to serial port
printf("\n");
}
}
close(tty_fd);
tcsetattr(STDOUT_FILENO,TCSANOW,&old_stdio);
return EXIT_SUCCESS;
}
Most of the code came from http://en.wikibooks.org/wiki/Serial_Programming/Serial_Linux but i also used a bit from the code posted below.
Handling with serial ports ( for linux OS ) :
- To open communication, you will need a descriptor which will be the handle for your serial port.
- Set the flags to control how the comunication will be.
- Write the command to this Handle ( make sure you're formatting the input correctly ).
- Get the answer. (make sure you're to read the amount of information you want )
- Close the handle.
It will seem like this:
int fd; // file descriptor
int flags; // communication flags
int rsl_len; // result size
char message[128]; // message to send, you can even dinamically alocate.
char result[128]; // result to read, same from above, thanks to #Lu
flags = O_RDWR | O_NOCTTY; // Read and write, and make the job control for portability
if ((fd = open("/dev/ttyUSB1", flags)) == -1 ) {
printf("Error while opening\n"); // Just if you want user interface error control
return -1;
}
// In this point your communication is already estabilished, lets send out something
strcpy(message, "Hello");
if (rsl_len = write(fd, message, strlen(message)) < 0 ) {
printf("Error while sending message\n"); // Again just in case
return -2;
}
if (rsl_len = read(fd, &result, sizeof(result)) < 0 ) {
printf("Error while reading return\n");
return -3;
}
close(fd);
Note that you have to care about what to write and what to read.
Some more flags can be used in case of parity control, stop bits, baud rate and more.
Since gcc is a C/C++ compiler, you don't need to limit yourself to pure C.
Sticking to pure C is OK if you enjoy writing lots of boilerplate code, and if you really know what you're doing. Many people use Unix APIs incorrectly, and a lot of example code out there is much too simplistic. Writing correct Unix C code is somewhat annoying, to say the least.
Personally, I'd suggest using not only C++, but also a higher-level application development framework like Qt. Qt 5 comes bundled with a QtSerialPort module that makes it easy to enumerate the serial ports, configure them, and get data into and out of them. Qt does not force you to use the gui modules, it can be a command line application, or a non-interactive server/daemon.
QtSerialPort is also usable from Qt 4, but it doesn't come bundled with Qt 4, you have to add it to your project. I suggest starting out with Qt 5, it's nicer to use with its C++11 support.
The code using Qt can be pretty simple, not much longer than your plain-English description. The below is a Qt console application using Qt 5 and C++11. It uses the core and serialport modules. It also handles the SIGINT signal so that the output file gets flushed before the process would terminate due to a ^C. I'm using QLocalSocket in place of raw Unix API to communicate from the Unix signal handler, the functionality is the same.
Only the code within main is strictly required, the rest is just to make it properly wrap things up when you hit ^C.
#include <QCoreApplication>
#include <QSerialPort>
#include <QFile>
#include <QTextStream>
#include <QLocalServer>
#include <QLocalSocket>
#include <cstdio>
#include <csignal>
QLocalSocket * xmit;
static void signalHandler(int)
{
xmit->write(" ");
xmit->flush();
}
static bool setupSignalHandler()
{
QLocalServer srv;
srv.listen("foobarbaz");
xmit = new QLocalSocket(qApp);
xmit->connectToServer(srv.serverName(), QIODevice::WriteOnly);
srv.waitForNewConnection();
QLocalSocket * receive = srv.nextPendingConnection();
receive->setParent(qApp);
qApp->connect(receive, &QLocalSocket::readyRead, &QCoreApplication::quit);
struct sigaction sig;
sig.sa_handler = signalHandler;
sigemptyset(&sig.sa_mask);
sig.sa_flags = SA_RESTART;
return ! sigaction(SIGINT, &sig, NULL);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
setupSignalHandler();
QSerialPort port("ttyUSB1");
QFile file("file.txt");
QTextStream err(stderr, QIODevice::WriteOnly);
QTextStream out(stdout, QIODevice::WriteOnly);
if (!file.open(QIODevice::WriteOnly)) {
err << "Couldn't open the output file" << endl;
return 1;
}
if (!port.open(QIODevice::ReadWrite)) {
err << "Couldn't open the port" << endl;
return 2;
}
port.setBaudRate(9600);
QObject::connect(&port, &QSerialPort::readyRead, [&](){
QByteArray data = port.readAll();
out << data;
file.write(data);
});
out << "Use ^C to quit" << endl;
return a.exec();
}

Resources