Handling USB cable not connected on linux driver - c

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.

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.

How to make mass storage device read only to Windows from firmware

I'm working with a firmware for a hardware that has an USB interface. When the device is connected to USB port, it enumerates as 2 classes: CDC & MSC.
For the MSC part, the device is user configurable in such a way that the device could be write-protected. So the user can choose whether the device is read-only or having read-write capability.
I'm using a pre written USB library that can enumerate the device with 2 interfaces. The part I'm struggling with is how can I let my firmware act as if the device is read-only.
The microcontroller is in the STM32L4 series. Most of the code is generated from STM32CubeMX software.
After some investigation, I've figured out that the SCSI Write10 along with SCSI Request Sense commands can check whether the MSC is write protected. Current implementation is as follows:
static int8_t SCSI_Write10 (USBD_HandleTypeDef *pdev, uint8_t lun , uint8_t *params)
{
USBD_CDC_MSC_HandleTypeDef *hmsc = pdev->pClassData;
uint32_t len;
if (hmsc->bot_state == USBD_BOT_IDLE) /* Idle */
{
/* case 8 : Hi <> Do */
if ((hmsc->cbw.bmFlags & 0x80U) == 0x80U)
{
SCSI_SenseCode(pdev, hmsc->cbw.bLUN, ILLEGAL_REQUEST, INVALID_CDB);
return -1;
}
/* Check whether Media is ready */
if(((USBD_CDC_StorageItfTypeDef *)pdev->pUserData)->IsReady(lun) !=0 )
{
SCSI_SenseCode(pdev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
return -1;
}
/* Check If media is write-protected */
if(((USBD_CDC_StorageItfTypeDef *)pdev->pUserData)->IsWriteProtected(lun) !=0 )
{
SCSI_SenseCode(pdev, lun, NOT_READY, WRITE_PROTECTED);
return -1;
}
// Other code
}
I've debugged and checked that IsWriteProtected() is correctly returning non zero value when the device is configured as write protected. The SCSI_SenseCode() is pushing the error code in a circular list. Windows is supposed to send Request Sense SCSI command to retrieve the error code, but I've checked with breakpoint that Windows never sent this command!
int8_t SCSI_ProcessCmd(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *cmd)
{
switch (cmd[0])
{
case SCSI_TEST_UNIT_READY:
SCSI_TestUnitReady(pdev, lun, cmd);
break;
case SCSI_REQUEST_SENSE:
SCSI_RequestSense (pdev, lun, cmd); // <<-- This is never reached
break;
case SCSI_INQUIRY:
SCSI_Inquiry(pdev, lun, cmd);
break;
....
If I configure the device in write-protect manner, the disk drive enumeration in PC takes a very long time and File Explorer is kept busy for a very long time. After the enumeration, I can open the disk drive and if I try to write to the disk, Windows reports that a disk I/O has occurred and write fails. I was expecting Windows to tell me that the disk is read-only.
If I configure the device as write-enabled, then the disk drive enumeration takes regular time as if I've plugged a regular pen drive.
Why Windows never sent Request Sense SCSI command?
Why disk enumeration is taking a very long time when -1 is returned due to write protection from Write10 command? I need the disk to enumerate quickly with or without write-protection.

How to connect my device to wifi with libnl?

I'm creating a C library that manage a lot of pheripherical of my embedded device. The S.O. used, is a linux dristro compiled with yocto. I'm trying to make some functions to connect my device to a wifi (well-know) router, with netlink (using the libnl commands). With the help of this community i've developed a function able to scan the routers in the area link here . Some of you know how to use the libnl command for connect my device to a router wifi?
I've developed the following code, that try to connect to an AP called "Validator_Test" (that have no password for authentication). The software return no error, but my device still remain disconneted from the ap.
static int iw_conn() {
struct nl_msg *msg = nlmsg_alloc();
int if_index = if_nametoindex("wlan0"); // Use this wireless interface for scanning.
// Open socket to kernel.
struct nl_sock *socket = nl_socket_alloc(); // Allocate new netlink socket in memory.
genl_connect(socket); // Create file descriptor and bind socket.
int driver_id = genl_ctrl_resolve(socket, "nl80211"); // Find the nl80211 driver ID.
genlmsg_put(msg, 0, 0, driver_id, 0, (NLM_F_REQUEST | NLM_F_ACK), NL80211_CMD_CONNECT, 0);
nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); // Add message attribute, which interface to use.
nla_put(msg, NL80211_ATTR_SSID, strlen("Validator_Test"), "Validator_Test");
nla_put(msg, NL80211_ATTR_MAC, strlen("00:1e:42:21:e4:e9"), "00:1e:42:21:e4:e9");
int ret = nl_send_auto_complete(socket, msg); // Send the message.
printf("NL80211_CMD_CONNECT sent %d bytes to the kernel.\n", ret);
ret = nl_recvmsgs_default(socket); // Retrieve the kernel's answer. callback_dump() prints SSIDs to stdout.
nlmsg_free(msg);
if (ret < 0) {
printf("ERROR: nl_recvmsgs_default() returned %d (%s).\n", ret, nl_geterror(-ret));
return ret;
}
nla_put_failure:
return -ENOSPC;
}
It seems similar to this one:
How to use libnl and netlink socket for connect devices to AP programatically?
--
Thanks for the code.
Based on your code, I modified and did the test here; it works. The source code is at: https://github.com/neojou/nl80211/blob/master/test_connect/src/test_connect_nl80211.c
Some suggestions for this:
Make sure the test environment is correct
Before test the code, maybe you can try to use iw to do the test. iw is the open source tool, which uses netlink also. you can type "sudo iw wlan0 connect Validator_Test" and then use iwconfig to see if it is connected or not first. ( Suppose there is no security setting at the AP as you said )
there are two differences between your source code and mine
(1) don't need to set NL80211_ATTR_MAC
(2) ret = nl_recvmsgs_default(socket);
not sure if there is any judgement of the return value of your ap_conn(), but it seems better to return 0 in ap_conn(), when nl_recvmsgs_default() returns 0.

configfs USB gadget write() call hangs

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;
}

Virtual comport communication with windows 10 in C

im trying to communicate with a device via a virtual comport (comport to usb adapter,PL2303) on Win10. The device is an Eltek RC250 datalogger.
I have already installed an older PL2303 driver. The devicemanager recognized the device without any errors. Sending and receiving data between the device and the official software is working properly.
My problem is that after ReadFile is executed the program is doing nothing. I think ReadFile is waiting for more input from the device and therefore stucked in this function.
Trying it on a win7 System gets to the same issue.
The message which i write to the device is a valid message.
The following code shows the communication.
hComm = CreateFile("COM3", //port name
GENERIC_READ | GENERIC_WRITE, //Read/Write
0, // No Sharing
NULL, // No Security
OPEN_EXISTING,// Open existing port only
0, // Non Overlapped I/O
NULL); // Null for Comm Devices /* establish connection to serial port */
if (hComm == INVALID_HANDLE_VALUE)
printf("Error in opening serial port");
else
printf("opening serial port successfully");
nNumberOfBytesToWrite = sizeof(message);
resW = WriteFile(
hComm,
message,
nNumberOfBytesToWrite,
&lpNumberOfBytesWritten,
NULL);
do
{
printf("\nread");
resR = ReadFile(
hComm,
&answer,
sizeof(lpNumberOfBytesRead),
&lpNumberOfBytesRead,
NULL);
SerialBuffer[i] = answer;
i++;
}
while (lpNumberOfBytesRead > 0);
return 0;
Please help me, i have no clue what the problem might be.
Thomas
In the ReadFile() call, third parameter should be sizeof(answer) (or possibly just 1 since it appears to be a single byte), but certainly not sizeof(lpNumberOfBytesRead). It is blocking waiting for 4 bytes (size of a DWORD) when presumably answer is a single byte?
Also if you have not explicitly set a Comm timeout, you have no idea how long ReadFile() will wait before returning 0 to exit the loop. If the timeout is indefinite then it will never exist the loop.
There are other potential issues in this call, but without seeing how the parameters are declared, it is not possible to say.

Resources