Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get notifications for SD card events?

Tags:

c

linux

udev

I want to check the presence of an SD card, and receive notifications for SD card add/remove.

So far I have used libudev, and I've made a small application which listens for SD card events.

The code is listed below:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <sys/time.h> //debug -> remove me

#include <libudev.h>

#define ADD_FILTER "add"
#define REMOVE_FILTER "remove"
#define SUBSYSTEM_FILTER "block"
#define ATTR_FILTER "ID_MODEL"
#define SD_ATTR_VALUE "SD_MMC"
#define ATTR_ACTIVE_SD "ID_PART_TABLE_TYPE"

static bool isDeviceSD(struct udev_device *device);
static bool isDevPresent(struct udev *device);
static void print_device(struct udev_device *device, const char *source); //for debugging -> remove me
static bool s_bSD_present;

int main()
{
    struct udev *udev;
    struct udev_monitor *udev_monitor = NULL;
    fd_set readfds;
    s_bSD_present = false;

    udev = udev_new();
    if (udev == NULL)
    {
        printf("udev_new FAILED \n");
        return 1;
    }

    s_bSD_present = isDevPresent(udev);
    if(s_bSD_present)
    {
        printf("+++SD is plugged in \n");
    }
    else
    {
        printf("---SD is not plugged in \n");
    }

    udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
    if (udev_monitor == NULL) {
        printf("udev_monitor_new_from_netlink FAILED \n");
        return 1;
    }

    //add some filters
    if( udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, SUBSYSTEM_FILTER, NULL) < 0 )
    {
        printf("udev_monitor_filter_add_match_subsystem_devtype FAILED \n");
        return 1;
    }

    if (udev_monitor_enable_receiving(udev_monitor) < 0)
    {
        printf("udev_monitor_enable_receiving FAILED \n");
        return 1;
    }

    while (1) {
        printf("Polling for new data... \n");

        int fdcount = 0;

        FD_ZERO(&readfds);

        if (udev_monitor != NULL)
        {
            FD_SET(udev_monitor_get_fd(udev_monitor), &readfds);
        }

        fdcount = select(udev_monitor_get_fd(udev_monitor)+1, &readfds, NULL, NULL, NULL);
        if (fdcount < 0)
        {
            if (errno != EINTR)
                printf("Error receiving uevent message\n");
            continue;
        }

        if ((udev_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(udev_monitor), &readfds))
        {
            struct udev_device *device;

            device = udev_monitor_receive_device(udev_monitor);
            if (device == NULL)
                continue;

            //check the action
            const char* szAction = udev_device_get_action(device);
            if( strcmp(szAction, ADD_FILTER) == 0)
            {
                if( !s_bSD_present && isDeviceSD(device) )
                {
                    s_bSD_present = true;
                    printf("+++SD has been plugged in \n");
                }
            }
            else if( strcmp(szAction, REMOVE_FILTER) == 0 )
            {
                if( s_bSD_present && isDeviceSD(device) )
                {
                    s_bSD_present = false;
                    printf("---SD has been removed \n");
                }
            }

            udev_device_unref(device);
        }
    }

    return 0;
}

static bool isDeviceSD(struct udev_device *device)
{
    bool retVal = false;
    struct udev_list_entry *list_entry = 0;
    struct udev_list_entry* model_entry = 0;
    struct udev_list_entry* active_sd_entry = 0;

    list_entry = udev_device_get_properties_list_entry(device);
    model_entry = udev_list_entry_get_by_name(list_entry, ATTR_FILTER);
    if( 0 != model_entry )
    {
        const char* szModelValue = udev_list_entry_get_value(model_entry);
        active_sd_entry = udev_list_entry_get_by_name(list_entry, ATTR_ACTIVE_SD);
        if(strcmp(szModelValue, SD_ATTR_VALUE) == 0 && active_sd_entry != 0)
        {
            printf("Device is SD \n");
            retVal = true;

            //print_device(device, "UDEV");
        }
    }
    return retVal;
}


static bool isDevPresent(struct udev *device)
{
    bool retVal = false;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *dev_list_entry;

    enumerate = udev_enumerate_new(device);
    udev_enumerate_add_match_subsystem(enumerate, SUBSYSTEM_FILTER);
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);

    udev_list_entry_foreach(dev_list_entry, devices)
    {
        struct udev_device *dev;
        const char* dev_path = udev_list_entry_get_name(dev_list_entry);
        dev = udev_device_new_from_syspath(device, dev_path);

        if( true == isDeviceSD(dev) )
        {
            retVal = true;
            udev_device_unref(dev);
            break;
        }

        udev_device_unref(dev);
    }
    udev_enumerate_unref(enumerate);

    return retVal;
}


static void print_device(struct udev_device *device, const char *source)
{
      struct timeval tv;
      struct timezone tz;

      gettimeofday(&tv, &tz);
      printf("%-6s[%llu.%06u] %-8s %s (%s)\n",
             source,
             (unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec,
             udev_device_get_action(device),
             udev_device_get_devpath(device),
             udev_device_get_subsystem(device));

            struct udev_list_entry *list_entry;

            udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
                  printf("%s=%s\n",
                         udev_list_entry_get_name(list_entry),
                         udev_list_entry_get_value(list_entry));
            printf("\n");

}

This code will get notifications for SD card add/remove (and the initial state of the SD - plugged/unplugged). However, it is more of a hack, and it doesn't work in all cases.

I currently use the ID_MODEL attribute of the device and check if it is SD_MMC - for SD cards. I need only this type of card for now, so it's enough.

When an SD card is inserted, the following events are sent for the subsystem block: 2 change events, and 1 add event for each partition. The event properties are listed below:

<----- change event - subsystem block - disk type disk ----->

UDEV  [1339412734.522055] change   /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd (block)
UDEV_LOG=3
ACTION=change
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd
SUBSYSTEM=block
DEVNAME=/dev/sdd
DEVTYPE=disk
SEQNUM=3168
ID_VENDOR=Generic-
ID_VENDOR_ENC=Generic-
ID_VENDOR_ID=0bda
ID_MODEL=SD_MMC
ID_MODEL_ENC=SD\x2fMMC\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=0151
ID_REVISION=1.00
ID_SERIAL=Generic-_SD_MMC_20060413092100000-0:2
ID_SERIAL_SHORT=20060413092100000
ID_TYPE=disk
ID_INSTANCE=0:2
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2
ID_PART_TABLE_TYPE=dos
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION_TABLE=1
UDISKS_PARTITION_TABLE_SCHEME=mbr
UDISKS_PARTITION_TABLE_COUNT=2
MAJOR=8
MINOR=48
DEVLINKS=/dev/block/8:48 /dev/disk/by-id/usb-Generic-_SD_MMC_20060413092100000-0:2 /dev/disk/by-path/pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2

<----- add event partition 1 - subsystem block - disk type partition ----->

UDEV  [1339412734.719107] add      /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd1 (block)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd1
SUBSYSTEM=block
DEVNAME=/dev/sdd1
DEVTYPE=partition
SEQNUM=3169
ID_VENDOR=Generic-
ID_VENDOR_ENC=Generic-
ID_VENDOR_ID=0bda
ID_MODEL=SD_MMC
ID_MODEL_ENC=SD\x2fMMC\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=0151
ID_REVISION=1.00
ID_SERIAL=Generic-_SD_MMC_20060413092100000-0:2
ID_SERIAL_SHORT=20060413092100000
ID_TYPE=disk
ID_INSTANCE=0:2
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2
ID_PART_TABLE_TYPE=dos
ID_FS_UUID=6343c7b9-92a9-4d8f-bdd8-893f1190f294
ID_FS_UUID_ENC=6343c7b9-92a9-4d8f-bdd8-893f1190f294
ID_FS_VERSION=1.0
ID_FS_TYPE=ext2
ID_FS_USAGE=filesystem
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION=1
UDISKS_PARTITION_SCHEME=mbr
UDISKS_PARTITION_NUMBER=1
UDISKS_PARTITION_TYPE=0x83
UDISKS_PARTITION_SIZE=1006919680
UDISKS_PARTITION_SLAVE=/sys/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd
UDISKS_PARTITION_OFFSET=11618304
UDISKS_PARTITION_ALIGNMENT_OFFSET=0
MAJOR=8
MINOR=49
DEVLINKS=/dev/block/8:49 /dev/disk/by-id/usb-Generic-_SD_MMC_20060413092100000-0:2-part1 /dev/disk/by-path/pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2-part1 /dev/disk/by-uuid/6343c7b9-92a9-4d8f-bdd8-893f1190f294

<----- add event partition 2 - subsystem block - disk type partition ----->

UDEV  [1339412734.731338] add      /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd2 (block)
UDEV_LOG=3
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd/sdd2
SUBSYSTEM=block
DEVNAME=/dev/sdd2
DEVTYPE=partition
SEQNUM=3170
ID_VENDOR=Generic-
ID_VENDOR_ENC=Generic-
ID_VENDOR_ID=0bda
ID_MODEL=SD_MMC
ID_MODEL_ENC=SD\x2fMMC\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
ID_MODEL_ID=0151
ID_REVISION=1.00
ID_SERIAL=Generic-_SD_MMC_20060413092100000-0:2
ID_SERIAL_SHORT=20060413092100000
ID_TYPE=disk
ID_INSTANCE=0:2
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=usb-storage
ID_PATH=pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2
ID_PART_TABLE_TYPE=dos
UDISKS_PRESENTATION_NOPOLICY=0
UDISKS_PARTITION=1
UDISKS_PARTITION_SCHEME=mbr
UDISKS_PARTITION_NUMBER=2
UDISKS_PARTITION_TYPE=0xda
UDISKS_PARTITION_SIZE=11618304
UDISKS_PARTITION_FLAGS=boot
UDISKS_PARTITION_SLAVE=/sys/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:2/block/sdd
UDISKS_PARTITION_OFFSET=1022410752
UDISKS_PARTITION_ALIGNMENT_OFFSET=0
MAJOR=8
MINOR=50
DEVLINKS=/dev/block/8:50 /dev/disk/by-id/usb-Generic-_SD_MMC_20060413092100000-0:2-part2 /dev/disk/by-path/pci-0000:02:03.0-usb-0:1:1.0-scsi-0:0:0:2-part2

From the change event I cannot extract any information regarding whether the device was added or removed. I have tried to open the device name \dev\sdd but I don't have permissions, so that option falls...

For now I'm just checking the action attribute for the partitions ( add / remove ).

This version of the program works pretty well for SD cards that have partitions. When there are no partitions, only the change events are received.

So my question is: is there any way I can check whether the media was added/removed from the change event? Or is there some other way to check if a device is available (keeping in mind the partition issue)?

Any suggestion on improving the device attribute iteration or the method of getting the notifications would be welcomed.

P.S. And I can't use libusb :).

like image 868
Alexandru C. Avatar asked Jun 11 '12 11:06

Alexandru C.


1 Answers

Ok. So I got it working on the PC for SD cards without partitions.

The updated code is this:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <sys/time.h> //debug -> remove me
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <libudev.h>

#define ADD_FILTER "add"
#define REMOVE_FILTER "remove"
#define SUBSYSTEM_FILTER "block"
#define DEVTYPE_FILTER "disk"
#define ATTR_FILTER "ID_MODEL"
#define SD_ATTR_VALUE "SD_MMC"
#define ATTR_ADDED_DISK "UDISKS_PARTITION_TABLE" // attribute is available for "change" event when SD card is added (n/a when removed)

static bool isDeviceSD(struct udev_device *device); //checks if device is SD card (MMC)
static bool isDevPresent(struct udev *device); //checks if device is present (SD + added)
static bool isDeviceAdded(struct udev_device *device); //checks if device is added (presence of attribute ATTR_ADDED_DISK)
static void print_device(struct udev_device *device, const char *source); //for debugging -> remove me
static bool s_bSD_present;

int main()
{
    struct udev *udev;
    struct udev_monitor *udev_monitor = NULL;
    fd_set readfds;
    s_bSD_present = false;

    udev = udev_new();
    if (udev == NULL)
    {
        printf("udev_new FAILED \n");
        return 1;
    }

    if( isDevPresent(udev) )
    {
        s_bSD_present = true;
        printf("+++SD is plugged in \n");
    }
    else
    {
        printf("---SD is not plugged in \n");
    }

    udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
    if (udev_monitor == NULL) {
        printf("udev_monitor_new_from_netlink FAILED \n");
        return 1;
    }

    //add some filters
    if( udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, SUBSYSTEM_FILTER, DEVTYPE_FILTER) < 0 )
    {
        printf("udev_monitor_filter_add_match_subsystem_devtype FAILED \n");
        return 1;
    }

    if (udev_monitor_enable_receiving(udev_monitor) < 0)
    {
        printf("udev_monitor_enable_receiving FAILED \n");
        return 1;
    }

    while (1) {
        printf("Polling for new data... \n");

        int fdcount = 0;

        FD_ZERO(&readfds);

        if (udev_monitor != NULL)
        {
            FD_SET(udev_monitor_get_fd(udev_monitor), &readfds);
        }

        fdcount = select(udev_monitor_get_fd(udev_monitor)+1, &readfds, NULL, NULL, NULL);
        if (fdcount < 0)
        {
            if (errno != EINTR)
                printf("Error receiving uevent message\n");
            continue;
        }

        if ((udev_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(udev_monitor), &readfds))
        {
            struct udev_device *device;

            device = udev_monitor_receive_device(udev_monitor);
            if (device == NULL)
                continue;

            //check presence
            if( isDeviceSD(device) && isDeviceAdded(device) )
            {
                if(!s_bSD_present) //guard for double "change" events
                {
                    s_bSD_present = true;
                    printf("+++SD has been plugged in \n");
                }
            }
            else
            {
                if(s_bSD_present) //not needed -> just keeping consistency
                {
                    s_bSD_present = false;
                    printf("---SD has been removed \n");
                }
            }

            udev_device_unref(device);
        }
    }

    return 0;
}

static bool isDeviceSD(struct udev_device *device)
{
    bool retVal = false;
    struct udev_list_entry *list_entry = 0;
    struct udev_list_entry* model_entry = 0;

    list_entry = udev_device_get_properties_list_entry(device);
    model_entry = udev_list_entry_get_by_name(list_entry, ATTR_FILTER);
    if( 0 != model_entry )
    {
        const char* szModelValue = udev_list_entry_get_value(model_entry);
        if( strcmp( szModelValue, SD_ATTR_VALUE) == 0 )
        {
            //printf("Device is SD \n");
            retVal = true;

            //print_device(device, "UDEV");
        }
    }
    return retVal;
}

static bool isDeviceAdded(struct udev_device *device)
{
    bool retVal = false;
    struct udev_list_entry *list_entry = 0;
    struct udev_list_entry* added_disk_entry = 0;


    list_entry = udev_device_get_properties_list_entry(device);
    added_disk_entry = udev_list_entry_get_by_name(list_entry,/* "DEVNAME" */ ATTR_ADDED_DISK);
    if( 0 != added_disk_entry )
    {
        retVal = true;
    }
    return retVal;
}


static bool isDevPresent(struct udev *device)
{
    bool retVal = false;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *dev_list_entry;

    enumerate = udev_enumerate_new(device);
    udev_enumerate_add_match_subsystem(enumerate, SUBSYSTEM_FILTER);
    udev_enumerate_scan_devices(enumerate);
    devices = udev_enumerate_get_list_entry(enumerate);

    udev_list_entry_foreach(dev_list_entry, devices)
    {
        struct udev_device *dev;
        const char* dev_path = udev_list_entry_get_name(dev_list_entry);
        dev = udev_device_new_from_syspath(device, dev_path);

        if( isDeviceSD(dev) && isDeviceAdded(dev) )
        {
            retVal = true;
            udev_device_unref(dev);
            break;
        }

        udev_device_unref(dev);
    }
    udev_enumerate_unref(enumerate);

    return retVal;
}


static void print_device(struct udev_device *device, const char *source)
{
      struct timeval tv;
      struct timezone tz;

      gettimeofday(&tv, &tz);
      printf("%-6s[%llu.%06u] %-8s %s (%s)\n",
             source,
             (unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec,
             udev_device_get_action(device),
             udev_device_get_devpath(device),
             udev_device_get_subsystem(device));

            struct udev_list_entry *list_entry;

            udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device))
                  printf("%s=%s\n",
                         udev_list_entry_get_name(list_entry),
                         udev_list_entry_get_value(list_entry));
            printf("\n");

}

The solution (still not very bright) is checking for some attribute that is available only when SD card is added (like UDISKS_PARTITION_TABLE).

This works well on x86.

like image 112
Alexandru C. Avatar answered Oct 06 '22 18:10

Alexandru C.