NSLU2-Linux
view · edit · print · history

How to generate events when a media card is inserted into an USB reader

When a media card (e.g. a cf card) is inserted into a USB media card reader (that is already plugged in) then no events are generated by this hardware change. (If an unplugged reader is plugged in then Hotplug can be used to respond to the circumstance.)

The problem is that linux and the USB media card reader do not have a mechanism to announce that the media card scsi devices (which is how the device driver models the media cards to the system) have been hotplugged. A solution is to poll the presence of the media card.

sdparm

sdparm http://sg.torque.net/sg/sdparm.html is a tool that is meant to do for scsi discs what hdparm does for ide ones. (You may like to try most likely in vain to set the idle time of your usb enclosure using it). We can use sdparm to test for the presence of a valid scsi device on USB reader's installed devices. You will need to build or install sdparm (in Debian, apt-get install sdparm).

Polling

The following C program will poll the success of a specified command and if there is a change in the success of the command from fail to success (i.e. sdparm was not recognising a valid scsi device and now it does) then the program will run another command (your event).

/* cf-monitor.c
 *
 * Code licensed with GPL
 *
 * cf-monitor TEST CMD [POLL millisecs]
 *
 * This command executes the command TEST every POLL millisecs (default 2000) and
 * if a transition from the command TEST failing to the command TEST succeeding occurs
 * then the command CMD is executed.
 *
 * Usage example:
 *
 * % sudo cf-monitor "sdparm /dev/sdc1 2> /dev/null > /dev/null" "beep" 1000 &
 *
 * Which polls device /dev/sdc1 every second and beeps if the device becomes available
 * to mount (while writing all output from sdparm to /dev/null).
 *
 * The sdparm command can be found at http://sg.torque.net/sg/sdparm.html
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define DEFAULT_POLL 2000

void wait(int milli_secs)
{
   struct timespec t;
   t.tv_sec  = milli_secs/1000;
   t.tv_nsec = 1000000 *(milli_secs%1000);
   nanosleep(&t,0);
}

int main(int argc, char **argv)
{
   char *cmd;
   int  flag = 0, fail, poll = DEFAULT_POLL;

   if (argc < 3) { printf("Usage: %s TEST CMD [POLL millisecs]\n", *argv); return 256; }
   if (argc > 3) { poll = atoi(*(argv+3)); }
   if (poll < 100) poll = 100; // sensible max poll rate of 10/s

   fail = system(*(argv+1));
   if (!fail) {flag = 1;}

   while (1)
     {
     fail = system(*(argv+1));
     if (!fail && flag == 0) { system(*(argv+2)); flag = 1; }
     if (fail) flag = 0;
     wait(poll);
     }

   return 0;
}

Starting on boot

Here's a boot script to be inserted as root in /etc/init.d/cfmonitor

The variables let you set up cf-monitor to call a script cfcardscript which polls the device dev using sdparm. You will need to specify your paths of course.

#! /bin/sh
#
cfmonitor=/usr/local/bin/cf-monitor
cfcardscript=/usr/local/bin/cf-card-script
sdparm=/usr/bin/sdparm
dev=/dev/sdc1
#

test -x "$cfmonitor"    || exit 0
test -x "$cfcardscript" || exit 0
test -x "$sdparm"       || exit 0

case "$1" in
  start)
    echo -n "Starting cf-monitor"
    start-stop-daemon --start --quiet -b --exec $cfmonitor -- \
      "$sdparm $dev 2> /dev/null > /dev/null" $cfcardscript 1000
    echo "."
    ;;
  stop)
    echo -n "Stopping cf-monitor"
    start-stop-daemon --stop --quiet -x $cfmonitor
    echo "."
    ;;
  reload|force-reload)
    start-stop-daemon --stop --quiet --signal 1 --exec $cf-monitor -- \
      "$sdparm $dev 2> /dev/null > /dev/null" $cfcardscript 1000
    ;;
  restart)
    echo -n "Stopping cf-monitor"
    start-stop-daemon --stop --quiet -x $cfmonitor
    echo "."
    sleep 1
    echo -n "Starting cf-monitor"
    start-stop-daemon --start --quiet -b --exec $cfmonitor -- \
      "$sdparm $dev 2> /dev/null > /dev/null" $cfcardscript 1000
    echo "."
    ;;
  *)
    echo "Usage: /etc/init.d/cf-monitor {start|stop|reload|restart|force-reload}"
    exit 1
esac

exit 0

You will then need to set up symbolic links to this script in /etc/rc*.d using


% update-rc.d cfmonitor defaults

In use

I use the following script to copy from a Canon 20D's CF card to a network shared drive (as well as merging the irritating 100 photo directories into one -- after 10000 photos you will need to rename and restart with an empty directory, it will not overwrite existing files). The script mounts, copies then unmounts the card. It uses the 'beep' package to notify when the card is inserted, a file is transfered and the card can be removed.

#!/bin/sh
#
# To be run by cf-monitor using e.g.
#
# sudo cf-monitor "sdparm /dev/sdc1 2> /dev/null > /dev/null" cf-card-script 1000 &
#
# Depends on 'beep' package to control the system buzzer for audio feedback
#
########################################################

source_dev="/dev/sdc1"
source_dir="/media/cf"
target_dir="/media/hdd/public/CF-CARD/"

########################################################

mount $source_dev $source_dir

beep -f 392.0 -n -f 493.9

if [ -e "$source_dir/dcim" ]          # Check if file exists.
then
  for file in `ls $source_dir/dcim/*canon/*`
  do
  target="$target_dir"`expr "$file" : '.*\([i_]mg.*\)'`
  if [ ! -e "$target" ]
  then
    cp "$file" "$target"
    beep -f 450 -l 10
  fi
  done
fi
chown -R guest "$target_dir"

umount /dev/sdc1

beep -f 493.9 -n -f 392.0

Syslogd

I found that a kernel entry of "Device not ready. Make sure there is a disc in the drive." was made each time sdparm failed in polling the device. This may annoy. I uninstalled syslogd (put an 'exit 0' at the top of the file in /etc/init.d/syslog) installed the syslog-ng package and in the configfile /etc/syslog-ng/syslog-ng.conf changed the source line to include kernel messages (this should be the default install really)

source src { file("/proc/kmsg" log_prefix("kernel: ")); unix-stream("/dev/log"); internal(); };

and added the line to create a filter for the message

filter f_sdparm {not match("Device not ready. Make sure there is a disc in the drive."); };

and changed the output line to

log { source(src); filter(f_sdparm); destination(messages); };

You may well want to comment out all the output lines at the bottom of the file except messages, console and console_all and add the change the option line at the top of the file to

options { long_hostnames(off); sync(0); perm(0640); group(log_readers); dir_perm(0755); dir_group(log_readers); };

view · edit · print · history · Last edited by emm_is.
Based on work by sponer and emm_is.
Originally by emm_is.
Page last modified on March 07, 2006, at 11:29 AM