[Branch,~linaro-validation/lava-dispatcher/trunk] Rev 667: Add sdmux and general lmp functionality

Message ID 20130905095228.25045.22026.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

Dave Pigott Sept. 5, 2013, 9:52 a.m.
Merge authors:
  Dave Pigott (dpigott)
Related merge proposals:
  https://code.launchpad.net/~dpigott/lava-dispatcher/add-sdmux-and-lmp-functionality/+merge/183131
  proposed by: Dave Pigott (dpigott)
  review: Approve - Tyler Baker (tyler-baker)
  review: Approve - Antonio Terceiro (terceiro)
  review: Resubmit - Dave Pigott (dpigott)
------------------------------------------------------------
revno: 667 [merge]
committer: Dave Pigott <dave.pigott@linaro.org>
branch nick: trunk
timestamp: Thu 2013-09-05 10:51:15 +0100
message:
  Add sdmux and general lmp functionality
removed:
  lava_dispatcher/device/sdmux.sh
added:
  lava_dispatcher/actions/lmp/
  lava_dispatcher/actions/lmp/__init__.py
  lava_dispatcher/actions/lmp/board.py
  lava_dispatcher/actions/lmp/ethsata.py
  lava_dispatcher/actions/lmp/hdmi.py
  lava_dispatcher/actions/lmp/lsgpio.py
  lava_dispatcher/actions/lmp/sdmux.py
  lava_dispatcher/actions/lmp/usb.py
modified:
  lava_dispatcher/config.py
  lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf
  lava_dispatcher/device/sdmux.py
  setup.py


--
lp:lava-dispatcher
https://code.launchpad.net/~linaro-validation/lava-dispatcher/trunk

You are subscribed to branch lp:lava-dispatcher.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-dispatcher/trunk/+edit-subscription

Patch

=== added directory 'lava_dispatcher/actions/lmp'
=== added file 'lava_dispatcher/actions/lmp/__init__.py'
--- lava_dispatcher/actions/lmp/__init__.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/lmp/__init__.py	2013-09-04 10:19:58 +0000
@@ -0,0 +1,1 @@ 
+__author__ = 'dpigott'

=== added file 'lava_dispatcher/actions/lmp/board.py'
--- lava_dispatcher/actions/lmp/board.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/lmp/board.py	2013-09-05 08:59:44 +0000
@@ -0,0 +1,132 @@ 
+# Copyright (C) 2013 Linaro Limited
+#
+# Author: Dave Pigott <dave.pigott@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along
+# with this program; if not, see <http://www.gnu.org/licenses>.
+
+import serial
+import json
+import logging
+from serial import (
+    serialutil
+)
+from lava_dispatcher.errors import (
+    CriticalError,
+)
+
+class LAVALmpDeviceSerial(object):
+    def __init__(self, serialno, board_type):
+        device_map = {
+            "sdmux": "0a",
+            "sata": "19",
+            "lsgpio": "09",
+            "hdmi": "0c",
+            "usb": "04"
+        }
+        self.serialno = "LL" + device_map[board_type] + serialno.zfill(12)
+        logging.debug("LMP Serial #: %s" % self.serialno)
+        self.lmpType = "org.linaro.lmp." + board_type
+        self.board_type = board_type
+        try:
+            self.port = serial.Serial("/dev/serial/by-id/usb-Linaro_Ltd_LavaLMP_" + self.serialno + "-if00", timeout=1)
+        except serial.serialutil.SerialException as e:
+            logging.error("LMP: Error opening {0:s}: {1:s}".format(self.serialno, e))
+            raise
+        self.START_FRAME = '\x02'
+        self.END_FRAME = '\x04'
+        self.send_frame('{"schema":"org.linaro.lmp.info"}')
+        message = self.get_response("board")
+        if message['serial'] != self.serialno:
+            raise CriticalError("Lmp %s not connected" % serial)
+        # With the sdmux, we must wait until the device has switched to the requested state. Not all Lmp boards provide
+        # the required state information in the report
+        # TODO: Fix firmware so that they all do
+        if board_type == "sdmux":
+            self.wait_for_confirmation = True
+        else:
+            self.wait_for_confirmation = False
+
+    def send_command(self, mode, selection):
+        message = '{"schema":"' + self.lmpType + '",' + \
+            '"serial":"' + self.serialno + '",' + \
+            '"modes":[{"name":"' + mode + '",' + \
+            '"option":"' + selection + '"}]}'
+
+        self.send_frame(message)
+
+        if self.wait_for_confirmation:
+            device_in_mode = False
+
+            while not device_in_mode:
+                try:
+                    response = self.get_frame()
+                except ValueError as e:
+                    logging.warning("LMP Frame read error: %s" % e)
+                    continue
+                else:
+                    for i in response["report"]:
+                        if i["name"] == "modes":
+                            modes = dict(i)
+                            for j in modes["modes"]:
+                                state = dict(j)
+                                if state["name"] == mode and state["mode"] == selection:
+                                    logging.debug("LMP %s: %s now in mode %s" % (self.board_type, mode, selection))
+                                    device_in_mode = True
+
+    def send_frame(self, command):
+        logging.debug("LMP: Sending %s" % command)
+        payload = self.START_FRAME + command + self.END_FRAME
+        self.port.write(payload)
+
+    def get_response(self, schema):
+        got_schema = False
+
+        result = self.get_frame()
+
+        while not got_schema:
+            if result['schema'] == "org.linaro.lmp." + schema:
+                got_schema = True
+            else:
+                result = self.get_frame()
+
+        return result
+
+    def get_frame(self):
+        char = self.port.read()
+
+        while char != self.START_FRAME:
+            char = self.port.read()
+
+        response = ""
+
+        while char != self.END_FRAME:
+            char = self.port.read()
+            if char != self.END_FRAME:
+                response += char
+
+        logging.debug("LMP: Got %s" % response)
+
+        return json.loads(response)
+
+    def close(self):
+        self.port.close()
+
+
+def lmp_send_command(serial, lmp_type, mode, state):
+    lmp = LAVALmpDeviceSerial(serial, lmp_type)
+    lmp.send_command(mode, state)
+    lmp.close()

=== added file 'lava_dispatcher/actions/lmp/ethsata.py'
--- lava_dispatcher/actions/lmp/ethsata.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/lmp/ethsata.py	2013-09-04 15:09:05 +0000
@@ -0,0 +1,29 @@ 
+# Copyright (C) 2013 Linaro Limited
+#
+# Author: Dave Pigott <dave.pigott@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along
+# with this program; if not, see <http://www.gnu.org/licenses>.
+
+from lava_dispatcher.actions.lmp.board import lmp_send_command
+
+
+def disconnect(serial):
+    lmp_send_command(serial, "sata", "sata", "disconnect")
+
+
+def passthru(serial):
+    lmp_send_command(serial, "sata", "sata", "passthru")

=== added file 'lava_dispatcher/actions/lmp/hdmi.py'
--- lava_dispatcher/actions/lmp/hdmi.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/lmp/hdmi.py	2013-09-04 15:09:05 +0000
@@ -0,0 +1,33 @@ 
+# Copyright (C) 2013 Linaro Limited
+#
+# Author: Dave Pigott <dave.pigott@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along
+# with this program; if not, see <http://www.gnu.org/licenses>.
+
+from lava_dispatcher.actions.lmp.board import lmp_send_command
+
+
+def disconnect(serial):
+    lmp_send_command(serial, "hdmi", "hdmi", "disconnect")
+
+
+def passthru(serial):
+    lmp_send_command(serial, "hdmi", "hdmi", "passthru")
+
+
+def fake(serial):
+    lmp_send_command(serial, "hdmi", "hdmi", "fake")

=== added file 'lava_dispatcher/actions/lmp/lsgpio.py'
--- lava_dispatcher/actions/lmp/lsgpio.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/lmp/lsgpio.py	2013-09-04 15:09:05 +0000
@@ -0,0 +1,45 @@ 
+# Copyright (C) 2013 Linaro Limited
+#
+# Author: Dave Pigott <dave.pigott@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along
+# with this program; if not, see <http://www.gnu.org/licenses>.
+
+from lava_dispatcher.actions.lmp.board import lmp_send_command
+
+
+def audio_disconnect(serial):
+    lmp_send_command(serial, "lsgpio", "audio", "disconnect")
+
+
+def audio_passthru(serial):
+    lmp_send_command(serial, "lsgpio", "audio", "passthru")
+
+
+def a_dir_in(serial):
+    lmp_send_command(serial, "lsgpio", "a-dir", "in")
+
+
+def a_dir_out(serial):
+    lmp_send_command(serial, "lsgpio", "a-dir", "out")
+
+
+def b_dir_in(serial):
+    lmp_send_command(serial, "lsgpio", "b-dir", "in")
+
+
+def b_dir_out(serial):
+    lmp_send_command(serial, "lsgpio", "b-dir", "out")

=== added file 'lava_dispatcher/actions/lmp/sdmux.py'
--- lava_dispatcher/actions/lmp/sdmux.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/lmp/sdmux.py	2013-09-04 15:09:05 +0000
@@ -0,0 +1,53 @@ 
+# Copyright (C) 2013 Linaro Limited
+#
+# Author: Dave Pigott <dave.pigott@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along
+# with this program; if not, see <http://www.gnu.org/licenses>.
+
+from lava_dispatcher.actions.lmp.board import lmp_send_command
+
+
+def dut_disconnect(serial):
+    lmp_send_command(serial, "sdmux", "dut", "disconnect")
+
+
+def dut_usda(serial):
+    lmp_send_command(serial, "sdmux", "dut", "uSDA")
+
+
+def dut_usdb(serial):
+    lmp_send_command(serial, "sdmux", "dut", "uSDB")
+
+
+def host_disconnect(serial):
+    lmp_send_command(serial, "sdmux", "host", "disconnect")
+
+
+def host_usda(serial):
+    lmp_send_command(serial, "sdmux", "host", "uSDA")
+
+
+def host_usdb(serial):
+    lmp_send_command(serial, "sdmux", "host", "uSDB")
+
+
+def dut_power_off(serial):
+    lmp_send_command(serial, "sdmux", "dut-power", "short-for-off")
+
+
+def dut_power_on(serial):
+    lmp_send_command(serial, "sdmux", "dut-power", "short-for-on")

=== added file 'lava_dispatcher/actions/lmp/usb.py'
--- lava_dispatcher/actions/lmp/usb.py	1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/lmp/usb.py	2013-09-04 15:09:05 +0000
@@ -0,0 +1,33 @@ 
+# Copyright (C) 2013 Linaro Limited
+#
+# Author: Dave Pigott <dave.pigott@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along
+# with this program; if not, see <http://www.gnu.org/licenses>.
+
+from lava_dispatcher.actions.lmp.board import lmp_send_command
+
+
+def device(serial):
+   lmp_send_command(serial, "usb", "usb", "device")
+
+
+def host(serial):
+    lmp_send_command(serial, "usb", "usb", "host")
+
+
+def disconnect(serial):
+    lmp_send_command(serial, "usb", "usb", "disconnect")

=== modified file 'lava_dispatcher/config.py'
--- lava_dispatcher/config.py	2013-08-28 14:55:50 +0000
+++ lava_dispatcher/config.py	2013-09-04 11:07:28 +0000
@@ -81,6 +81,9 @@ 
     testboot_offset = schema.IntOption(fatal=True)
     # see doc/sdmux.rst for details
     sdmux_id = schema.StringOption()
+    sdmux_usb_id = schema.StringOption()
+    sdmux_mount_retry_seconds = schema.IntOption(default=20)
+    sdmux_mount_wait_seconds = schema.IntOption(default=10)
     sdmux_version = schema.StringOption(default="unknown")
 
     simulator_version_command = schema.StringOption()

=== modified file 'lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf'
--- lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf	2013-08-28 14:55:50 +0000
+++ lava_dispatcher/default-config/lava-dispatcher/device-defaults.conf	2013-09-04 11:07:28 +0000
@@ -133,3 +133,9 @@ 
 
 # How many times the dispatcher should try to reboot master and test images before failing
 boot_retries = 3
+
+# For an sdmux enabled device, maximum amount of time to wait for the /dev/sdX to appear after device has been switched
+# to host mode
+sdmux_mount_retry_seconds = 20
+# How long to wait after the /dev/sdX entry has turned up before trying to unmount anything attached to it
+sdmux_mount_wait_seconds = 10

=== modified file 'lava_dispatcher/device/sdmux.py'
--- lava_dispatcher/device/sdmux.py	2013-08-30 22:15:05 +0000
+++ lava_dispatcher/device/sdmux.py	2013-09-05 09:51:15 +0000
@@ -1,6 +1,7 @@ 
-# Copyright (C) 2012 Linaro Limited
+# Copyright (C) 2012-2013 Linaro Limited
 #
 # Author: Andy Doan <andy.doan@linaro.org>
+#         Dave Pigott <dave.pigott@linaro.org>
 #
 # This file is part of LAVA Dispatcher.
 #
@@ -21,8 +22,10 @@ 
 import contextlib
 import logging
 import os
+import glob
 import subprocess
 import time
+import lava_dispatcher.actions.lmp.sdmux as sdmux
 
 from lava_dispatcher.errors import (
     CriticalError,
@@ -64,26 +67,19 @@ 
     This adds support for the "sd mux" device. An SD-MUX device is a piece of
     hardware that allows the host and target to both connect to the same SD
     card. The control of the SD card can then be toggled between the target
-    and host via software. The schematics and pictures of this device can be
-    found at:
-      http://people.linaro.org/~doanac/sdmux/
-
-    Documentation for setting this up is located under doc/sdmux.rst.
-
-    NOTE: please read doc/sdmux.rst about kernel versions
-    """
+    and host via software.
+"""
 
     def __init__(self, context, config):
         super(SDMuxTarget, self).__init__(context, config)
 
         self.proc = None
 
+        if not config.sdmux_usb_id:
+            raise CriticalError('Device config requires "sdmux_usb_id"')
+
         if not config.sdmux_id:
             raise CriticalError('Device config requires "sdmux_id"')
-        if not config.power_on_cmd:
-            raise CriticalError('Device config requires "power_on_cmd"')
-        if not config.power_off_cmd:
-            raise CriticalError('Device config requires "power_off_cmd"')
 
         if config.pre_connect_command:
             self.context.run_command(config.pre_connect_command)
@@ -118,27 +114,19 @@ 
         self._customize_android(img)
         self._write_image(img)
 
-    def _as_chunks(self, fname, bsize):
-        with open(fname, 'r') as fd:
-            while True:
-                data = fd.read(bsize)
-                if not data:
-                    break
-                yield data
-
     def _write_image(self, image):
+        sdmux.dut_disconnect(self.config.sdmux_id)
+        sdmux.host_usda(self.config.sdmux_id)
+
         with self.mux_device() as device:
             logging.info("dd'ing image to device (%s)", device)
-            with open(device, 'w') as of:
-                written = 0
-                size = os.path.getsize(image)
-                # 4M chunks work well for SD cards
-                for chunk in self._as_chunks(image, 4 << 20):
-                    of.write(chunk)
-                    written += len(chunk)
-                    if written % (20 * (4 << 20)) == 0:  # only log every 80MB
-                        logging.info("wrote %d of %d bytes", written, size)
-                logging.info('closing %s, could take a while...', device)
+            dd_cmd = 'dd if=%s of=%s bs=4096 conv=fsync' % (image, device)
+            dd_proc = subprocess.Popen(dd_cmd, shell=True)
+            dd_proc.wait()
+            if dd_proc.returncode != 0:
+                raise CriticalError("Failed to dd image to device (Error code %d)" % dd_proc.returncode)
+
+        sdmux.host_disconnect(self.config.sdmux_id)
 
     @contextlib.contextmanager
     def mux_device(self):
@@ -153,23 +141,36 @@ 
         the USB device connect to the sdmux will be powered off so that the
         target will be able to safely access it.
         """
-        muxid = self.config.sdmux_id
-        source_dir = os.path.abspath(os.path.dirname(__file__))
-        muxscript = os.path.join(source_dir, 'sdmux.sh')
 
-        self.power_off(self.proc)
         self.proc = None
 
-        try:
-            deventry = subprocess.check_output([muxscript, '-d', muxid, 'on'])
-            deventry = deventry.strip()
-            logging.info('returning sdmux device as: %s', deventry)
+        syspath = "/sys/bus/usb/devices/" + self.config.sdmux_usb_id + \
+            "/" + self.config.sdmux_usb_id + \
+            "*/host*/target*/*:0:0:0/block/*"
+
+        retrycount = 0
+        deventry = ""
+
+        while retrycount < self.config.sdmux_mount_retry_seconds:
+            device_list = glob.glob(syspath)
+            for device in device_list:
+                deventry = os.path.join("/dev/", os.path.basename(device))
+                break
+            if deventry != "":
+                break
+            time.sleep(1)
+            retrycount += 1
+
+        if deventry != "":
+            logging.debug('found sdmux device %s: Waiting %ds for any mounts to complete'
+                          % (deventry, self.config.sdmux_mount_wait_seconds))
+            time.sleep(self.config.sdmux_mount_wait_seconds)
+            logging.debug("Unmounting %s*", deventry)
+            os.system("umount %s*" % deventry)
+            logging.debug('returning sdmux device as: %s', deventry)
             yield deventry
-        except subprocess.CalledProcessError:
+        else:
             raise CriticalError('Unable to access sdmux device')
-        finally:
-            logging.info('powering off sdmux')
-            self.context.run_command([muxscript, '-d', muxid, 'off'], failok=False)
 
     @contextlib.contextmanager
     def file_system(self, partition, directory):
@@ -219,10 +220,14 @@ 
     def power_off(self, proc):
         super(SDMuxTarget, self).power_off(proc)
         self.context.run_command(self.config.power_off_cmd)
+        sdmux.dut_disconnect(self.config.sdmux_id)
 
     def power_on(self):
         self.proc = connect_to_serial(self.context)
 
+        sdmux.host_disconnect(self.config.sdmux_id)
+        sdmux.dut_usda(self.config.sdmux_id)
+
         logging.info('powering on')
         self.context.run_command(self.config.power_on_cmd)
 

=== removed file 'lava_dispatcher/device/sdmux.sh'
--- lava_dispatcher/device/sdmux.sh	2013-01-28 23:59:47 +0000
+++ lava_dispatcher/device/sdmux.sh	1970-01-01 00:00:00 +0000
@@ -1,79 +0,0 @@ 
-#!/bin/bash
-# based on  https://github.com/liyan/suspend-usb-device
-
-#set -e
-
-usage()
-{
-    cat<<EOF
-This script will turn on/off power to a USB port. Its being
-used in conjunction with the SD Mux device.
-
-Power on/off a device or find its /dev/sdX with:
- $0 -d device_id on|off|deventry
-
-Find the device ID from a /dev/entry with
-$0 -f /dev/sdX
-
-EOF
-}
-
-while getopts "f:d:" opt; do
-	case $opt in
-		f)  DEV=$OPTARG ;;
-		d)  ID=$OPTARG ;;
-		\?) usage ; exit 1 ;;
-	esac
-done
-
-if [ -n "$DEV" ] ; then
-	echo "Finding id for $DEV"
-	DEVICE=$(udevadm info --query=path --name=${DEV} --attribute-walk | \
-	egrep "looking at parent device" | head -1 | \
-	sed -e "s/.*looking at parent device '\(\/devices\/.*\)\/.*\/host.*/\1/g")
-
-	if [ -z $DEVICE ]; then
-	    1>&2 echo "cannot find appropriate parent USB device, "
-	    1>&2 echo "perhaps ${DEV} is not an USB device?"
-	    exit 1
-	fi
-
-	# the trailing basename of ${DEVICE} is DEV_BUS_ID
-	DEV_BUS_ID=${DEVICE##*/}
-	echo Device: ${DEVICE}
-	echo Bus ID: ${DEV_BUS_ID}
-
-elif [ -n "$ID" ] ; then
-	ACTION=${!OPTIND:-}
-	DIR=/sys/bus/usb/devices/${ID}/${ID}*/host*/target*/*:0:0:0/block
-	if [ $ACTION == "on" ] ; then
-		if [ -d $DIR ] ; then
-			echo "<sdmux script> already on" 1>&2
-		else
-			echo -n "${ID}" > /sys/bus/usb/drivers/usb/bind
-			sleep 4
-		fi
-		device_path=`ls $DIR 2>/dev/null`
-		if [ $? -ne 0 ] ; then
-			echo "<sdmux script> No sdmux found at ${DIR}" 1>&2
-			exit 1
-		fi
-		echo /dev/${device_path}
-
-	elif [ $ACTION = "off" ] ; then
-		echo "<sdmux script> Powering off sdmux: $ID"
-		echo -n "${ID}" > /sys/bus/usb/drivers/usb/unbind
-		sleep 1
-		echo -n '0' > /sys/bus/usb/devices/$ID/power/autosuspend_delay_ms
-		sleep 1
-		echo -n 'auto' > /sys/bus/usb/devices/$ID/power/control
-		sleep 2
-	elif [ $ACTION = "deventry" ] ; then
-		echo /dev/`ls $DIR`
-	else
-		echo "ERROR: Action must be on/off"
-		usage; exit 1
-	fi
-else
-	usage
-fi

=== modified file 'setup.py'
--- setup.py	2013-07-18 14:01:21 +0000
+++ setup.py	2013-08-30 11:24:26 +0000
@@ -53,6 +53,7 @@ 
         "configglue",
         "PyYAML",
         'versiontools >= 1.8',
+        "pyserial",
     ],
     setup_requires=[
         'versiontools >= 1.8',