summaryrefslogtreecommitdiffstats
path: root/tools/usb_gadget
diff options
context:
space:
mode:
authorreillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-25 08:35:56 +0000
committerreillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-25 08:35:56 +0000
commitac37a8cc221f9b68682e0c872fd35a8ce2177837 (patch)
treefb14b611df80a1b8d72eb085dcac2c476657afe6 /tools/usb_gadget
parenta9987cae11bccd09827d0b3cb91bd4f70091d59d (diff)
downloadchromium_src-ac37a8cc221f9b68682e0c872fd35a8ce2177837.zip
chromium_src-ac37a8cc221f9b68682e0c872fd35a8ce2177837.tar.gz
chromium_src-ac37a8cc221f9b68682e0c872fd35a8ce2177837.tar.bz2
[usb_gadget p09] Software updates over HTTP.
Add a /update resource which accepts a new software package (assuming the hash is correct) and performs an in-place upgrade. To avoid leaving the device in an unusable state the old version remains running until the new version starts to accept HTTP connections. At this point the Tornado I/O loop is running correctly and even if USB functionality is broken the device should accept another software update to fix it. BUG=396682 R=rockot@chromium.org,rpaquay@chromium.org,kalman@chromium.org Review URL: https://codereview.chromium.org/412243002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285531 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/usb_gadget')
-rw-r--r--tools/usb_gadget/__main__.py4
-rwxr-xr-xtools/usb_gadget/package.py29
-rw-r--r--tools/usb_gadget/server.py72
3 files changed, 104 insertions, 1 deletions
diff --git a/tools/usb_gadget/__main__.py b/tools/usb_gadget/__main__.py
index 20998c5..4ca6ad9 100644
--- a/tools/usb_gadget/__main__.py
+++ b/tools/usb_gadget/__main__.py
@@ -25,6 +25,9 @@ def ParseArgs():
parser.add_argument(
'--hardware', default='beaglebone-black',
help='Hardware configuration.')
+ parser.add_argument(
+ '--start-claimed',
+ help='Start with the device claimed by this client.')
return parser.parse_args()
@@ -34,6 +37,7 @@ def main():
server.interface = args.interface
server.port = args.port
server.hardware = args.hardware
+ server.claimed_by = args.start_claimed
addrs = netifaces.ifaddresses(server.interface)
ip_address = addrs[netifaces.AF_INET][0]['addr']
diff --git a/tools/usb_gadget/package.py b/tools/usb_gadget/package.py
index d1a2f2d..069bb14 100755
--- a/tools/usb_gadget/package.py
+++ b/tools/usb_gadget/package.py
@@ -3,13 +3,14 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""Utility to package the USB gadget framework.
+"""Utility to package and upload the USB gadget framework.
"""
import argparse
import hashlib
import os
import StringIO
+import urllib2
import zipfile
@@ -37,6 +38,27 @@ def MakeZip(directory=None, files=None):
return content, md5
+def EncodeBody(filename, buf):
+ return '\r\n'.join([
+ '--foo',
+ 'Content-Disposition: form-data; name="file"; filename="{}"'
+ .format(filename),
+ 'Content-Type: application/octet-stream',
+ '',
+ buf,
+ '--foo--',
+ ''
+ ])
+
+
+def UploadZip(content, md5, host):
+ filename = 'usb_gadget-{}.zip'.format(md5)
+ req = urllib2.Request(url='http://{}/update'.format(host),
+ data=EncodeBody(filename, content))
+ req.add_header('Content-Type', 'multipart/form-data; boundary=foo')
+ urllib2.urlopen(req)
+
+
def main():
parser = argparse.ArgumentParser(
description='Package (and upload) the USB gadget framework.')
@@ -50,6 +72,9 @@ def main():
'--hash-file', type=str, metavar='FILE',
help='save package hash as FILE')
parser.add_argument(
+ '--upload', type=str, metavar='HOST[:PORT]',
+ help='upload package to target system')
+ parser.add_argument(
'files', metavar='FILE', type=str, nargs='*',
help='source files')
@@ -62,6 +87,8 @@ def main():
if args.hash_file:
with open(args.hash_file, 'w') as hash_file:
hash_file.write(md5)
+ if args.upload:
+ UploadZip(content, md5, args.upload)
if __name__ == '__main__':
diff --git a/tools/usb_gadget/server.py b/tools/usb_gadget/server.py
index 9f23726..e5aa8b8 100644
--- a/tools/usb_gadget/server.py
+++ b/tools/usb_gadget/server.py
@@ -5,10 +5,16 @@
"""WSGI application to manage a USB gadget.
"""
+import datetime
+import hashlib
import re
+import subprocess
import sys
+import time
+import urllib2
from tornado import httpserver
+from tornado import ioloop
from tornado import web
import default_gadget
@@ -20,6 +26,9 @@ chip = None
claimed_by = None
default = default_gadget.DefaultGadget()
gadget = None
+hardware = None
+interface = None
+port = None
def SwitchGadget(new_gadget):
@@ -45,6 +54,68 @@ class VersionHandler(web.RequestHandler):
self.write(version)
+class UpdateHandler(web.RequestHandler):
+
+ def post(self):
+ fileinfo = self.request.files['file'][0]
+
+ match = VERSION_PATTERN.match(fileinfo['filename'])
+ if match is None:
+ self.write('Filename must contain MD5 hash.')
+ self.set_status(400)
+ return
+
+ content = fileinfo['body']
+ md5sum = hashlib.md5(content).hexdigest()
+ if md5sum != match.group(1):
+ self.write('File hash does not match.')
+ self.set_status(400)
+ return
+
+ filename = 'usb_gadget-{}.zip'.format(md5sum)
+ with open(filename, 'wb') as f:
+ f.write(content)
+
+ args = ['/usr/bin/python', filename,
+ '--interface', interface,
+ '--port', str(port),
+ '--hardware', hardware]
+ if claimed_by is not None:
+ args.extend(['--start-claimed', claimed_by])
+
+ print 'Reloading with version {}...'.format(md5sum)
+
+ global http_server
+ if chip.IsConfigured():
+ chip.Destroy()
+ http_server.stop()
+
+ child = subprocess.Popen(args, close_fds=True)
+
+ while True:
+ child.poll()
+ if child.returncode is not None:
+ self.write('New package exited with error {}.'
+ .format(child.returncode))
+ self.set_status(500)
+
+ http_server = httpserver.HTTPServer(app)
+ http_server.listen(port)
+ SwitchGadget(gadget)
+ return
+
+ try:
+ f = urllib2.urlopen('http://{}/version'.format(address))
+ if f.getcode() == 200:
+ # Update complete, wait 1 second to make sure buffers are flushed.
+ io_loop = ioloop.IOLoop.instance()
+ io_loop.add_timeout(datetime.timedelta(seconds=1), io_loop.stop)
+ return
+ except urllib2.URLError:
+ pass
+ time.sleep(0.1)
+
+
class ClaimHandler(web.RequestHandler):
def post(self):
@@ -88,6 +159,7 @@ class ReconnectHandler(web.RequestHandler):
app = web.Application([
(r'/version', VersionHandler),
+ (r'/update', UpdateHandler),
(r'/claim', ClaimHandler),
(r'/unclaim', UnclaimHandler),
(r'/unconfigure', UnconfigureHandler),