Search code examples
linuxsymlinkioctludev

Unexpected ioctl behavior on symlinks


I'm currently working with a HID device. It has two setup modes : HID, and RS232. So I wrote a little script to switch him to RS232, whenever it's plugged as a HID device, using this udev rule :

ENV{ID_VENDOR}=="Vendor", ENV{ID_VENDOR_ID}=="001d", ENV{ID_USB_DRIVER}=="usbhid",\
SYMLINK+="hid_device", RUN+="/path/to/HID_to_serial"

The script is as follows :

// HID_to_serial.c
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

//#define DEFAULT_DEVICE_PATH   "/dev/hidraw0"
#define DEFAULT_DEVICE_PATH     "/dev/hid_device"

int main(int argc, char **argv)
{
  int fd = open(DEFAULT_DEVICE_PATH, O_RDWR);

  if (fd < 0)
  {
      perror("Unable to open device");
      return 1;
  }

  // Very specific report descriptor
  const char buf[64] = {  0x02, 0x0b, 0x02, 0x04, 0x42, 0x40, 0x10, 0x42,
                          0x62, 0x10, 0x42, 0x42, 0x03, 0x00, 0x00, 0x00,
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

  int res = ioctl(fd, _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, 64), buf);
  if (res < 0)
      perror("ioctl");
  else
      printf("Device was succesfully switched back to serial mode!\n");

  return 0;
}

Now, usually, when I plug the device, Linux gives it the /dev/hidraw0 file. And when I use my script on /dev/hidraw0, it works perfectly. The report descriptor used in the script is correct, and everything works as intended : the HID device switches back to RS232 mode.

However, when I try to use my script on the /dev/hid_device symlink created by the udev rule, it doesn't work 99% of the time, telling me ioctl: Invalid argument. The even stranger thing is that it works, but 1% of the time (maybe even less often).

Does anyone have any idea where this might come from, and how to fix it, or work around it? Thanks in advance.


Solution

  • I found the problem.

    It lies in my udev rule : it's not precise enough.

    When I plug the device, linux creates two character special device files in /dev/ : hidraw0, and input/event15 (in my case). They share a lot of environment values, like for example their ID_VENDOR_ID, or their ID_USB_DRIVER. However, they do not share the same MAJOR.

    So what I did was add ENV{MAJOR}=="correct_major" in my udev rule, and now my symlink is linked to the correct device file.

    It also explains why it worked sometimes : I guess that because of the lack of details in the udev rule, sometimes the symlink was linked to the correct device file, sometimes not.