3
votes

I am creating a test application for a new platform that we are working on to test libudev and make sure our C application will be able to be properly notified when a particular usb device is inserted or removed.

On our system every time this device is removed and added it's mounted in a different location such as /dev/hidraw0 or /dev/hidraw1 etc. So my test application uses libudev to both monitor events and enumerate through the hidraw devices so it can find where it's been mounted. Every time the device status changes, insertion or removal, the code finds where it's mounted and restarts libudev event monitoring.

My questions:

1- I see the "add" events. But why do I never see the remove events?
2- Am I properly closing and restarting the libudev monitoring such that resources aren't being leaked?

Sample output:

Found UGA. SysPath=[/sys/class/hidraw/hidraw2] DevPath=[/dev/hidraw2]
Starting monitor returned 2

UGA added....
Found UGA. SysPath=[/sys/class/hidraw/hidraw2] DevPath=[/dev/hidraw2]
Starting monitor returned 2

UGA added....
Found UGA. SysPath=[/sys/class/hidraw/hidraw1] Dev Path=[/dev/hidraw1]
Starting monitor returned 2

EDIT:
What I've seen is that this test application is receiving the remove events when run on our much older x86 platform with a 2.6 kernel while the same code running on our new ARM platform with a 3.10.66 kernel doesn't receive the remove event.

Could this be a udev/libudev system configuration or kernel issue?

Also when running udevadm monitor on the ARM board the required remove events are being sent as shown below. So why isn't libudev not forwarding them to my application?

box:/home/eng$ udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

KERNEL[80296.266707] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001D (hid)
KERNEL[80296.266864] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
UDEV  [80296.267553] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001D (hid)
KERNEL[80296.267695] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
UDEV  [80296.268031] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
UDEV  [80296.268890] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
KERNEL[80304.423516] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
KERNEL[80304.424514] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
KERNEL[80304.427211] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E (hid)
KERNEL[80304.458031] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E/hidraw/hidraw1 (hidraw)
UDEV  [80304.460096] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
UDEV  [80304.467105] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
UDEV  [80304.472885] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E (hid)
UDEV  [80304.474903] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E/hidraw/hidraw1 (hidraw)

Test Code:

/* Linux */
#include <linux/input.h>
#include <linux/hidraw.h>
#include <linux/fd.h>

/* Unix */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <libudev.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>

/* C */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <locale.h>
#include <stdbool.h>
#include <stdint.h>

#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif


struct udev *udev;
struct udev_monitor *udevMonitor;
int mon_fd = -1;
int fd = -1;
char sysPath[50];
char devPath[50];


enum { DEVICE_NO_CHANGE = 0, DEVICE_ADD, DEVICE_REMOVE, DEVICE_MOVED };
enum { OFFLINE = 0, ONLINE };
enum { ENABLED = 1, DISABLED };
enum { FAILED = 0, PENDING , ACTIVE };


void msSleep(int milliseconds);
void GetUGAMountPoints(void);
int StartUDEVMonitor(void);
int usb_check_device_status(void);

void GetUGAMountPoints(void)
{
    struct udev_device *dev;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *dev_list_entry;

    strcpy( devPath, "/dev/hidraw" );

    /* Create a list of the devices in the 'hidraw' subsystem. */
    enumerate = udev_enumerate_new(udev);
    udev_enumerate_add_match_subsystem(enumerate, "hidraw");
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);

    udev_list_entry_foreach(dev_list_entry, devices)
    {
        const char *path;

        path = udev_list_entry_get_name(dev_list_entry);
        dev = udev_device_new_from_syspath(udev, path);
        dev = udev_device_get_parent_with_subsystem_devtype( dev, "usb", "usb_device");
        if (!dev)
        {
            printf("Unable to find parent usb device.\n");
        }
        else
        {
            if( !strcmp(udev_device_get_sysattr_value(dev, "idVendor"), "0a70") &&
                !strcmp(udev_device_get_sysattr_value(dev, "idProduct"), "0441") )
            {
                char num = path[ strlen(path) - 1 ];
                devPath[ strlen(devPath) ] = num;

                /* Yes I know this sprintf() is insecure and lazy
                   This code is just for testing and not going into
                   production in this form
                */
                sprintf(sysPath, "/sys/class/hidraw/hidraw%c", num);

                printf("Found UGA. SysPath=[%s] Dev Path=[%s]\n", sysPath, devPath);
                udev_device_unref(dev);
                break;
            }
            else
            {
                udev_device_unref(dev);
            }
        }
    }

    udev_enumerate_unref(enumerate);
    return;
}


int StartUDEVMonitor(void)
{
    int retCode = ACTIVE;

    /* Create the udev object */
    udev = udev_new();
    if (!udev)
    {
        retCode = FAILED;
    }
    else
    {
        GetUGAMountPoints();

        /* Set up a monitor to monitor hidraw devices */
        udevMonitor = udev_monitor_new_from_netlink(udev, "kernel");
        udev_monitor_filter_add_match_subsystem_devtype(udevMonitor, "hidraw", NULL);
        udev_monitor_enable_receiving(udevMonitor);

        mon_fd = udev_monitor_get_fd(udevMonitor);
        fd = open(devPath, O_RDWR | O_NONBLOCK);

        if (fd < 0)
        {
            retCode = PENDING;
        }
    }
    return( retCode );
}

int usb_check_device_status(void)
{
    struct udev_device *dev;
    char action[25];  
    int retCode = DEVICE_NO_CHANGE;

    fd_set fds;
    struct timeval tv;
    int ret;

    FD_ZERO(&fds);
    FD_SET(mon_fd, &fds);
    tv.tv_sec = 0;
    tv.tv_usec = 75000;

    do
    {
        ret = select(mon_fd+1, &fds, NULL, NULL, &tv);

        /* Check if our file descriptor has received data. */
        if (ret > 0 && FD_ISSET(mon_fd, &fds))
        {
            FD_CLR(mon_fd, &fds);
            /* Make the call to receive the device.
               select() ensured that this will not block. */
            dev = udev_monitor_receive_device(udevMonitor);
            if (dev)
            {
                strcpy( action, udev_device_get_action(dev));
                printf("usb_check_device_status()::[%s]\n", action);

                if( !strcmp("remove",action))
                {
                    printf("UGA removed....\n");
                    retCode = DEVICE_REMOVE;
                }
                else if( !strcmp("add",action))
                {
                    printf("UGA added....\n");
                    retCode = DEVICE_ADD;
                }
                else if( !strcmp("move",action))
                {
                    printf("UGA moved....\n");
                    retCode = DEVICE_MOVED;
                }
                udev_device_unref(dev);
            }
        }
    }while( ret != 0 );
    return( retCode );
}


void msSleep(int milliseconds)
{
      usleep(milliseconds * 1000);
}


int main( int argc, char *argv[] )
{
    int status = StartUDEVMonitor();
    printf("Starting monitor returned %d\n",status);

    if(status == ACTIVE )
    {
        while(1)
        {
            if( usb_check_device_status() != DEVICE_NO_CHANGE )
            {
                close(fd);
                close(mon_fd);
                udev_monitor_unref(udevMonitor);
                udev_unref(udev);
                StartUDEVMonitor();
                printf("Starting monitor returned %d\n\n",status);
            }
            msSleep(75);
        }
    }
    return( 1 );
}
1

1 Answers

3
votes

So by looking at the output of udevadm monitor I was able to see that on the newer kernel on the ARM board the remove events aren't tagged with hidraw as they are with the add event as shown below:

box:/home/eng$ udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

KERNEL[80296.266707] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001D (hid)
KERNEL[80296.266864] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
UDEV  [80296.267553] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001D (hid)
KERNEL[80296.267695] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
UDEV  [80296.268031] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
UDEV  [80296.268890] remove   /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
KERNEL[80304.423516] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
KERNEL[80304.424514] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
KERNEL[80304.427211] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E (hid)
KERNEL[80304.458031] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E/hidraw/hidraw1 (hidraw)
UDEV  [80304.460096] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1 (usb)
UDEV  [80304.467105] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0 (usb)
UDEV  [80304.472885] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E (hid)
UDEV  [80304.474903] add      /devices/soc0/soc.1/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.1/2-1.1:1.0/0003:0A70:0441.001E/hidraw/hidraw1 (hidraw)

So by changing the filter setup in StartUDEVMonitor() from:

udev_monitor_filter_add_match_subsystem_devtype(udevMonitor, "hidraw", NULL);

to:

udev_monitor_filter_add_match_subsystem_devtype(udevMonitor, "hid", NULL);

the application now reliably receives both remove and add events on the newer platform with the newer kernel.