From patchwork Fri Jan 4 19:28:11 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andy Doan X-Patchwork-Id: 13811 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 59B0B23EDB for ; Fri, 4 Jan 2013 19:28:15 +0000 (UTC) Received: from mail-vc0-f173.google.com (mail-vc0-f173.google.com [209.85.220.173]) by fiordland.canonical.com (Postfix) with ESMTP id BE848A191AD for ; Fri, 4 Jan 2013 19:28:14 +0000 (UTC) Received: by mail-vc0-f173.google.com with SMTP id f13so16883840vcb.18 for ; Fri, 04 Jan 2013 11:28:14 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-received:x-forwarded-to:x-forwarded-for:delivered-to:x-received :received-spf:content-type:mime-version:x-launchpad-project :x-launchpad-branch:x-launchpad-message-rationale :x-launchpad-branch-revision-number:x-launchpad-notification-type:to :from:subject:message-id:date:reply-to:sender:errors-to:precedence :x-generated-by:x-launchpad-hash:x-gm-message-state; bh=bMnHrGa5jchp+grAkR5+4UgRNoWlKZVBga0B9u8bpOo=; b=OJZoukN2LrQIInZxI4szyg7drheMbLKcRoJaXMCumZ80rf+T2Zrhzt1OkyYVRupo0M q3/t4mBXA4ix7BGynKRdy3CYIl85CT0hPwo4/5Yjqflhuxt3VbPtJYr+NvkQ4LAbXU7S 0w7yiQ1lBOBo3PoPbLiFge+Ags5t1KJzWuh4XhKcrgvb4LfJQ6Q786LKjBFlWAPIdQU/ OHNZ18enEeURGCIg9I/LXFI2xyD3ixnezx4Uf84zaLka/QZzjAfV9TQH7D7oQMo3+XtP OAuUQves6VC404oVLq1YGqV8OaJFwOQnb4dvCU7W3hqIjqZkSw6UnMeb0s4kyDk3WHMS 1rmg== X-Received: by 10.52.18.207 with SMTP id y15mr66967138vdd.8.1357327694186; Fri, 04 Jan 2013 11:28:14 -0800 (PST) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.58.145.101 with SMTP id st5csp152318veb; Fri, 4 Jan 2013 11:28:13 -0800 (PST) X-Received: by 10.180.82.41 with SMTP id f9mr74959938wiy.25.1357327692671; Fri, 04 Jan 2013 11:28:12 -0800 (PST) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id d13si4941185wjn.52.2013.01.04.11.28.11 (version=TLSv1/SSLv3 cipher=OTHER); Fri, 04 Jan 2013 11:28:12 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) smtp.mail=bounces@canonical.com Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1TrCw7-00018S-Qj for ; Fri, 04 Jan 2013 19:28:11 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id B9042E03DC for ; Fri, 4 Jan 2013 19:28:11 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-dispatcher X-Launchpad-Branch: ~linaro-validation/lava-dispatcher/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 519 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-dispatcher/trunk] Rev 519: sdmux support Message-Id: <20130104192811.31646.17205.launchpad@ackee.canonical.com> Date: Fri, 04 Jan 2013 19:28:11 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="16393"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 430f8f371acdd71ae4316020b3d75f025170eb0a X-Gm-Message-State: ALoCoQnqlOOfla56VMuu63iUBFTUhFdk07gA0XBX+JfXP17cLI7wuIAesZ7kOQ6ieM62waytrHi8 Merge authors: Andy Doan (doanac) Related merge proposals: https://code.launchpad.net/~doanac/lava-dispatcher/sdmux-support/+merge/141494 proposed by: Andy Doan (doanac) review: Approve - Michael Hudson-Doyle (mwhudson) ------------------------------------------------------------ revno: 519 [merge] committer: Andy Doan branch nick: lava-dispatcher timestamp: Fri 2013-01-04 13:26:51 -0600 message: sdmux support added: doc/sdmux.png doc/sdmux.rst lava_dispatcher/device/sdmux.py lava_dispatcher/device/sdmux.sh modified: doc/index.rst lava_dispatcher/config.py lava_dispatcher/device/master.py lava_dispatcher/utils.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 === modified file 'doc/index.rst' --- doc/index.rst 2012-12-16 22:24:10 +0000 +++ doc/index.rst 2013-01-04 00:03:43 +0000 @@ -36,6 +36,7 @@ lava_test_shell.rst external_measurement.rst arm_energy_probe.rst + sdmux.rst proxy.rst * :ref:`search` === added file 'doc/sdmux.png' Binary files doc/sdmux.png 1970-01-01 00:00:00 +0000 and doc/sdmux.png 2013-01-04 00:03:43 +0000 differ === added file 'doc/sdmux.rst' --- doc/sdmux.rst 1970-01-01 00:00:00 +0000 +++ doc/sdmux.rst 2013-01-04 19:26:51 +0000 @@ -0,0 +1,89 @@ +Configuring and Using the SD-Mux +================================ + +An sd-mux is a piece of hardware that's been created that allows a single +SD card to be controlled by two different card readers. Access between the +card readers is mutually exclusive and requires them to work in conjunction +with each other. This provides an extremely useful way to deal with embedded +system use cases for devices that boot from an SD card. + +LAVA uses sd-mux devices to allow running unmodified test images including +bootloaders on test devices. This is a big improvement to the +`master image`_ approach. + +.. _`master image`: http://lava.readthedocs.org/en/latest/lava-image-creation.html#preparing-a-master-image + +.. image:: sdmux.png + +Manual Usage +------------ + +Before deploying to LAVA, its probably best to understand the mechanics of +the actual device and ensure its functioning. A setup like in the image above +is assumed where: + + * the target end of the mux is plugged into a dev board + * the host end is plugged into a USB SD card reader + * the SD card reader is plugged into a USB hub that's plugged into the host + +With that in place, the device can be identified. The easiest way to do this +is: + + * ensure the target device is off + * cause a usb plug event on the host (unplug and plug the usb hub) + +At this point, "dmesg" should show what device this SD card appeared under +like "/dev/sdb". Since these entries can change, the sd-mux code needs to know +the actual USB device/port information. This can be found with the sdmux.sh +script by running:: + + ./sdmux.sh -f /dev/sdb + Finding id for /dev/sdb + Device: /devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1.1 + Bus ID: 2-1.1 + +The key piece of information is "Bus ID: 2-1.1". This is required by the sdmux +script to turn on/off the USB port with access to the device. To turn the +device off which gives the target safe access run:: + + ID=2-1.1 + ./sdmux -d $ID off + +At this point the target can be powered on and use the device. After powering +off the target, the sd-card can be access on the host with:: + + ./sdmux -d $ID on + +This command will also print out the device entry like "/dev/sdb" to STDOUT + +Deploying in LAVA +----------------- + +In order for the dispatcher's sd-mux driver to work a few fields must be added +the device config:: + + # client_type required so that the sdmux driver will be used + client_type = sdmux + # this is the ID as discovered above using "sdmux.sh -f" + sdmux_id = 2-1.1 + # sdmux_version is optional, but can be used to help identify which hardware + # revision this target is using. + sdmux_version = 0.01-dave_anders + # power on/off commands are also required + power_on_cmd = /usr/local/bin/pdu_power.sh 1 1 + power_off_cmd = /usr/local/bin/pdu_power.sh 1 0 + +About Kernel Versions +--------------------- + +Testing of the sdmux code was done on Ubuntu Precise (12.04.1 LTS). As January +2013, some newer kernels are demonstrating bugs when toggling the sdmux on/off +from the host using the sdmux.sh script. Here's a list of what's currently +known: + + * 3.2.0-31 - Works + * 3.2.0-32 - Looks like it will work. Target boots, so its reading partition 1. + However, it can't mount the root partition. This likely implies that the host + is still supplying some amount of current to the USB port. + * 3.2.0-34 - Not working at all. sdmux.sh can turn off the port, but not turn + it back on. === modified file 'lava_dispatcher/config.py' --- lava_dispatcher/config.py 2012-12-16 22:20:53 +0000 +++ lava_dispatcher/config.py 2013-01-04 00:03:43 +0000 @@ -57,6 +57,8 @@ pre_connect_command = schema.StringOption() qemu_drive_interface = schema.StringOption() qemu_machine_type = schema.StringOption() + power_on_cmd = schema.StringOption() # for sdmux + power_off_cmd = schema.StringOption() # for sdmux reset_port_command = schema.StringOption() root_part = schema.IntOption() sdcard_part_android = schema.IntOption() @@ -69,6 +71,9 @@ possible_partitions_files = schema.ListOption(default=["init.partitions.rc", "fstab.partitions", "init.rc"]) + # see doc/sdmux.rst for details + sdmux_id = schema.StringOption() + sdmux_version = schema.StringOption(default="unknown") simulator_version_command = schema.StringOption() simulator_command = schema.StringOption() === modified file 'lava_dispatcher/device/master.py' --- lava_dispatcher/device/master.py 2012-12-18 19:50:48 +0000 +++ lava_dispatcher/device/master.py 2012-12-29 03:50:52 +0000 @@ -19,7 +19,6 @@ # along # with this program; if not, see . -import atexit import contextlib import logging import os @@ -47,7 +46,7 @@ OperationFailed, ) from lava_dispatcher.utils import ( - logging_spawn, + connect_to_serial, logging_system, mk_targz, string_to_list, @@ -86,8 +85,7 @@ if config.pre_connect_command: logging_system(config.pre_connect_command) - self.proc = self._connect_carefully(config.connection_command) - atexit.register(self._close_logging_spawn) + self.proc = connect_to_serial(config, self.sio) def get_device_version(self): return self.device_version @@ -316,53 +314,6 @@ finally: runner.run('umount /mnt') - def _connect_carefully(self, cmd): - retry_count = 0 - retry_limit = 3 - - port_stuck_message = 'Data Buffering Suspended\.' - conn_closed_message = 'Connection closed by foreign host\.' - - expectations = { - port_stuck_message: 'reset-port', - 'Connected\.\r': 'all-good', - conn_closed_message: 'retry', - pexpect.TIMEOUT: 'all-good', - } - patterns = [] - results = [] - for pattern, result in expectations.items(): - patterns.append(pattern) - results.append(result) - - while retry_count < retry_limit: - proc = logging_spawn(cmd, timeout=1200) - proc.logfile_read = self.sio - logging.info('Attempting to connect to device') - match = proc.expect(patterns, timeout=10) - result = results[match] - logging.info('Matched %r which means %s', patterns[match], result) - if result == 'retry': - proc.close(True) - retry_count += 1 - time.sleep(5) - continue - elif result == 'all-good': - return proc - elif result == 'reset-port': - reset_port = self.config.reset_port_command - if reset_port: - logging_system(reset_port) - else: - raise OperationFailed("no reset_port command configured") - proc.close(True) - retry_count += 1 - time.sleep(5) - raise OperationFailed("could execute connection_command successfully") - - def _close_logging_spawn(self): - self.proc.close(True) - def _wait_for_master_boot(self): self.proc.expect(self.config.image_boot_msg, timeout=300) self.proc.expect(self.config.master_str, timeout=300) === added file 'lava_dispatcher/device/sdmux.py' --- lava_dispatcher/device/sdmux.py 1970-01-01 00:00:00 +0000 +++ lava_dispatcher/device/sdmux.py 2013-01-04 19:26:51 +0000 @@ -0,0 +1,221 @@ +# Copyright (C) 2012 Linaro Limited +# +# Author: Andy Doan +# +# 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 . + +import contextlib +import logging +import os +import subprocess +import sys +import time + +from lava_dispatcher.errors import ( + CriticalError, +) +from lava_dispatcher.device.target import ( + Target +) +from lava_dispatcher.client.lmc_utils import ( + generate_android_image, + generate_image, + image_partition_mounted, +) +from lava_dispatcher.downloader import ( + download_image, +) +from lava_dispatcher.utils import ( + connect_to_serial, + ensure_directory, + extract_targz, + logging_system, +) + + +class SDMuxTarget(Target): + """ + 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 + """ + + def __init__(self, context, config): + super(SDMuxTarget, self).__init__(context, config) + + self.proc = None + + 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: + logging_system(config.pre_connect_command) + + def deploy_linaro(self, hwpack=None, rootfs=None): + img = generate_image(self, hwpack, rootfs, self.scratch_dir) + self._customize_linux(img) + self._write_image(img) + + def deploy_linaro_prebuilt(self, image): + img = download_image(image, self.context) + self._customize_linux(img) + self._write_image(img) + + def _customize_android(self, img): + sys_part = self.config.sys_part_android_org + with image_partition_mounted(img, sys_part) as d: + with open('%s/etc/mkshrc' % d, 'a') as f: + f.write('\n# LAVA CUSTOMIZATIONS\n') + f.write('PS1="%s"\n' % self.ANDROID_TESTER_PS1) + self.deployment_data = Target.android_deployment_data + + def deploy_android(self, boot, system, data): + scratch = self.scratch_dir + boot = download_image(boot, self.context, scratch, decompress=False) + data = download_image(data, self.context, scratch, decompress=False) + system = download_image(system, self.context, scratch, decompress=False) + + img = os.path.join(scratch, 'android.img') + device_type = self.config.lmc_dev_arg + generate_android_image(device_type, boot, data, system, img) + 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): + 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) + + @contextlib.contextmanager + def mux_device(self): + """ + This function gives us a safe context in which to deal with the + raw sdmux device. It will ensure that: + * the target is powered off + * the proper sdmux USB device is powered on + + It will then yield to the caller a dev entry like /dev/sdb + This entry can be used safely during this context. Upon exiting, + 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) + yield deventry + except subprocess.CalledProcessError: + raise CriticalError('Unable to access sdmux device') + finally: + logging.info('powering off sdmux') + subprocess.check_call([muxscript, '-d', muxid, 'off']) + + @contextlib.contextmanager + def file_system(self, partition, directory): + """ + This works in cojunction with the "mux_device" function to safely + access a partition/directory on the sdmux filesystem + """ + mntdir = os.path.join(self.scratch_dir, 'sdmux_mnt') + if not os.path.exists(mntdir): + os.mkdir(mntdir) + + with self.mux_device() as device: + device = '%s%s' % (device, partition) + try: + subprocess.check_call(['mount', device, mntdir]) + if directory[0] == '/': + directory = directory[1:] + path = os.path.join(mntdir, directory) + ensure_directory(path) + logging.info('sdmux(%s) mounted at: %s', device, path) + yield path + except CriticalError: + raise + except subprocess.CalledProcessError: + raise CriticalError('Unable to access sdmux device') + except: + logging.exception('Error accessing sdmux filesystem') + raise CriticalError('Error accessing sdmux filesystem') + finally: + logging.info('unmounting sdmux') + try: + subprocess.check_call(['umount', device]) + except subprocess.CalledProcessError: + logging.exception('umount failed, re-try in 5 seconds') + time.sleep(5) + if subprocess.call(['umount', device]) == 0: + logging.error( + 'Unable to unmount sdmux device %s', device) + + def extract_tarball(self, tarball_url, partition, directory='/'): + logging.info('extracting %s to target', tarball_url) + with self.file_system(partition, directory) as mntdir: + tb = download_image(tarball_url, self.context, decompress=False) + extract_targz(tb, '%s/%s' % (mntdir, directory)) + + def power_off(self, proc): + super(SDMuxTarget, self).power_off(proc) + logging_system(self.config.power_off_cmd) + + def power_on(self): + self.proc = connect_to_serial(self.config, self.sio) + + logging.info('powering on') + logging_system(self.config.power_on_cmd) + + return self.proc + + def get_device_version(self): + return self.config.sdmux_version + +target_class = SDMuxTarget === added file 'lava_dispatcher/device/sdmux.sh' --- lava_dispatcher/device/sdmux.sh 1970-01-01 00:00:00 +0000 +++ lava_dispatcher/device/sdmux.sh 2013-01-04 00:03:43 +0000 @@ -0,0 +1,77 @@ +#!/bin/bash +# based on https://github.com/liyan/suspend-usb-device + +#set -e + +usage() +{ + cat<&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 " already on" 1>&2 + else + echo -n "${ID}" > /sys/bus/usb/drivers/usb/bind + sleep 2 + fi + device_path=`ls $DIR 2>/dev/null` + if [ $? -ne 0 ] ; then + echo " No sdmux found at ${DIR}" 1>&2 + exit 1 + fi + echo /dev/${device_path} + + elif [ $ACTION = "off" ] ; then + echo " Powering off sdmux: $ID" + echo -n "${ID}" > /sys/bus/usb/drivers/usb/unbind + echo -n '0' > /sys/bus/usb/devices/$ID/power/autosuspend_delay_ms + 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 'lava_dispatcher/utils.py' --- lava_dispatcher/utils.py 2012-12-19 05:46:44 +0000 +++ lava_dispatcher/utils.py 2013-01-04 00:03:43 +0000 @@ -29,6 +29,7 @@ import time import urlparse import subprocess + from shlex import shlex import pexpect @@ -57,9 +58,11 @@ os.makedirs(dir) shutil.copy(src, dest) + def rmtree(directory): subprocess.call(['rm', '-rf', directory]) + def mkdtemp(basedir='/tmp'): """ returns a temporary directory that's deleted when the process exits """ @@ -211,6 +214,55 @@ timeout=1, lava_no_logging=1) +def connect_to_serial(device_config, sio): + """ + Attempts to connect to a serial console server like conmux or cyclades + """ + retry_count = 0 + retry_limit = 3 + + port_stuck_message = 'Data Buffering Suspended\.' + conn_closed_message = 'Connection closed by foreign host\.' + + expectations = { + port_stuck_message: 'reset-port', + 'Connected\.\r': 'all-good', + conn_closed_message: 'retry', + pexpect.TIMEOUT: 'all-good', + } + patterns = [] + results = [] + for pattern, result in expectations.items(): + patterns.append(pattern) + results.append(result) + + while retry_count < retry_limit: + proc = logging_spawn(device_config.connection_command, timeout=1200) + proc.logfile_read = sio + logging.info('Attempting to connect to device') + match = proc.expect(patterns, timeout=10) + result = results[match] + logging.info('Matched %r which means %s', patterns[match], result) + if result == 'retry': + proc.close(True) + retry_count += 1 + time.sleep(5) + continue + elif result == 'all-good': + atexit.register(proc.close, True) + return proc + elif result == 'reset-port': + reset_cmd = device_config.reset_port_command + if reset_cmd: + logging_system(reset_cmd) + else: + raise CriticalError('no reset_port command configured') + proc.close(True) + retry_count += 1 + time.sleep(5) + raise CriticalError('could execute connection_command successfully') + + # XXX Duplication: we should reuse lava-test TestArtifacts def generate_bundle_file_name(test_name): return ("{test_id}.{time.tm_year:04}-{time.tm_mon:02}-{time.tm_mday:02}T" === modified file 'setup.py' --- setup.py 2012-12-16 22:20:53 +0000 +++ setup.py 2012-12-29 22:35:35 +0000 @@ -31,6 +31,7 @@ 'default-config/lava-dispatcher/device-defaults.conf', 'default-config/lava-dispatcher/device-types/*.conf', 'default-config/lava-dispatcher/devices/*.conf', + 'device/sdmux.sh', ], }, data_files=[