Auch wenn ein USB-Spielzeug – etwa ein Styropor-Raketenwerfer – nur mit einer Windows-CD daherkommt, lässt es sich dennoch mit etwas Reverse Engineering unter Linux betreiben. Mit »libusb« sogar ohne Treiber, vom Userspace aus und mit Perl gesteuert.
Ärger im Büro? Der muss nicht gleich in einer Schlacht eskalieren wie in dem Video “The Great Office War” [2] des Spielzeugherstellers Hasbro. Auch ohne Kriegserklärung und Aufmarschplan lohnt sich die Anschaffung des USB-gesteuerten Raketenwerfers Rocket Baby (Abbildung 1) der chinesischen Firma Cheeky Dream für etwa 20 Euro. Dient er doch nicht nur der Erheiterung der Kollegen, sondern bietet auch Gelegenheit, das recht komplexe USB-Subsystem des Linux-Kernels zu studieren [5].
In der Verpackung des Spielzeugs findet sich allerdings nur eine CD für Windows XP, keine Spur von einem Linux-Treiber. Dies spornte einige spielverliebte Entwickler offensichtlich dazu an, das verwendete USB-Protokoll unter Windows mit USB-Schnüffelwerkzeugen wie USBsniff zu ergründen und mittels Reverse Engineering Schnittstellen für Sprachen wie Python oder für andere Betriebssysteme zu basteln [3].
Ubuntu erkennt das Spielzeug automatisch nach dem Anstöpseln. Die Nachrichten des Kernels sind in der Logdatei »/var/log/messages« nachzulesen (Abbildung 2). Sie zeigen an, dass die Spielzeug-Stalinorgel nun am UHCI-Controller des Intel-PC hängt.

Abbildung 2: Nach dem Einstöpseln des Spielzeug-Raketenwerfers erkennt der Kernel das Device und weist ihm einen USB-Eintrag zu.
Das USB-Subsystem des Kernels hat den Raketenwerfer laut Logfile unter »usb 5-1« aufgenommen. Details zeigt der Sys-FS-Baum unter »/sys/bus/usb/devices/5-1«. Das USB-Filesystem »usbfs« projiziert hier Kernel-interne USB-Daten in den Userspace. Abbildung 3 zeigt, dass die Hersteller-ID (»idVendor«) des vom Kernel erkannten Kriegsspielzeugs 0x0a81 ist und die Produkt-ID (»idProduct«) 0x0701. Der Kernel nimmt eingesteckte USB-Geräte unter zufälligen USB-Nummern auf, statt »usb 5-1« könnte es nächstes Mal »usb 3-1« sein.
Doch hängt nur ein Gerät mit den gerade ermittelten Werten für »idVendor« und »idProduct« am PC, kann ein Programm dessen Adresse zuverlässig und relativ zügig ermitteln, indem es den ganzen USB-Baum durchstöbert, bis es die gesuchte Kombination findet.
Nicht nur Chefsache
Um nun Kontakt mit dem USB-Gerät aufzunehmen, verwendet Linux normalerweise Devicetreiber im Kernel. Diese sind allerdings schwer zu schreiben, da der kleinste Pointerfehler das gesamte Linux-System von der Klippe schubst und einen Reboot erforderlich macht.
Außerdem muss der Anwender Devicetreiber für jeden Kernel neu kompilieren. Gerade dort ändern sich Datenstrukturen zudem unheimlich schnell, sodass es durchaus passieren kann, dass der Sourcecode eines mühevoll für den Kernel 2.6.22 geschriebenen Treibers mit Version 2.6.24 nicht mehr funktioniert.
Benötigt man allerdings keinen hohen Datendurchsatz oder Antworten in Echtzeit, muss die Steuerungslogik nicht im Kernel laufen. Bietet der Kernel zudem eine Schnittstelle wie »usbfs« an, um mit USB-Geräten auf Hardware-Ebene zu kommunizieren, ist es möglich, den gesamten Treiber im Userspace zu implementieren. Das Open-Source-Projekt »libusb« [6] stellt eine bequeme Library für C-Programme zur Verfügung, das Perl-Modul Device::USB vom CPAN wickelt Perl-Funktionen drumherum.
Steuerung mit einem Byte
Listing 2 zeigt, wie sich der Geschützturm des Raketenwerfers mit ein paar Zeilen Perl-Code um etwa einen Zentimeter nach oben schwenken lässt. Zunächst sucht die Methode »find_device()« des Moduls Device::USB nach einem Gerät mit den vorher ermittelten Werten für »idVendor« und »idProduct« im USB-Baum. Die Methode »open()« nimmt im Erfolgsfall dann Verbindung mit dem gefundenen Device auf.
Das USB-Subsystem des Kernels unterstützt vier verschiedene Kommunikationsmodi für USB-Controller: Control-Transfers für Kurznachrichten, Bulk-Transfers für größere Datenmengen, Interrupt-Transfers für zeitkritische Daten und Isosynchronous-Transfers für Echtzeitdaten.
Durch Reverse Engineering fanden Entwickler heraus, dass der Raketenwerfer zur Bewegung des Kanzelturms und zum Abfeuern der Styroporraketen Control-Messages mit 1 Byte Länge erwartet. Die Tabelle 1 zeigt die Codes für die verschiedenen Aktionen. Dabei setzt ein Code den Werfer so lange in Bewegung, bis ein weiterer entweder die Richtung ändert oder ein Stopp-Befehl die Bewegung abbricht.
|
Tabelle 1: |
|
|---|---|
|
Aktion |
Code |
|
down |
0x01 |
|
up |
0x02 |
|
left |
0x04 |
|
right |
0x08 |
|
fire |
0x10 |
|
stop |
0x20 |
|
start |
0x40 |
Die an die Methode »control_msg()« in den Zeilen 12 und 19 übergebenen Hex-Werte legen fest, wie die USB-Schnittstelle das Kontrollbyte (Listing 1) an den Controller weiterreicht: »0x21« steht für den Request-Typ, »0x09« für »USB_REQ_SET_CONFIGURATION«, »0x02« für »USB_RECIP_ENDPOINT« und der Wert »0« für einen nicht benutzten Index. Es folgt der mit der Perl-Funktion »chr()« ermittelte Byte-Wert des angegebenen Integer-Werts »0x02« zur Steuerung des Werfers nach oben.
Die letzten beiden Parameter geben mit »1« die Länge des Strings an (im vorliegenden Fall genau 1 Byte) und die Wartezeit auf eine Antwort in Millisekunden (»1000«), bevor das Programm einen Fehler auslöst.
Danach genehmigt sich das Testprogramm mit Hilfe des Moduls Time::HiRes vom CPAN und dessen Funktion »usleep()« ein kurzes Schläfchen von einer Zehntelsekunde (100000 Mikrosekunden) und setzt anschließend das Kontrollbyte »0x20« ab, was der Empfänger als Stopp-Kommando interpretiert und den Motor des Geschützturms wieder zur Ruhe bringt.
Die zwei Aufrufe von »control_msg()« im Listing 2 haben den Geschützturm also insgesamt eine Zehntelsekunde lang nach oben gefahren. Falls dieser nicht eh schon am oberen Ende angekommen war, hat der Motor kurz aufgeheult und die Styroporraketen haben sich um etwa 20 Grad nach oben gedreht.
Beim Feuern einer Rakete ist zu beachten, dass der Geschützmotor ungefähr zwei Sekunden lang pumpen muss, um intern die nötige Spannung zum Abfeuern der Styroporgranate aufzubauen. Damit das Programm weiß, wann es den Motor nicht mehr benötigt, weil die Rakete in der Luft ist, muss es lesend auf die USB-Schnittstelle zugreifen und Daten vom Controller des Geschützes einholen.
Am Anschlag
Dieser meldet, welche Aktionen gerade verfügbar sind und welche nicht. Ist der Geschützturm zum Beispiel am rechten Anschlag, liefert er einen Status-String mit dem Wert »0x08« (binär »0000_1000«) zurück, um anzuzeigen, dass alle Aktionen außer »0x08« nun verfügbar sind (»0x08« symbolisiert die Richtung »right«). Steht der Geschützturm hingegen am linken unteren Anschlag, liefert die Statusmeldung »0x05« (binär »0000_0101«) zurück, denn sowohl »0x01« (»down«) als auch »0x04« (»left«) sind jetzt blockiert.
Analog setzt das USB-Device kurz nach dem Abfeuern einer Rakete das Flag »0x10« (binär »0001_0000«), dann weiß die Steuerung, dass sie jetzt den Motor mit »0x20« abstellen kann.
|
Listing 1: Parameter für |
|---|
01 $requesttype => 0x21 02 $request => 0x09 03 $value => 0x02 04 $index => 0 05 $bytes => chr(...) 06 $size => 1 07 $timeout => 1000 |
|
Listing 2: |
|---|
01 #!/usr/local/bin/perl -w 02 use strict; 03 04 use Time::HiRes qw(usleep); 05 use Device::USB; 06 my $usb = Device::USB->new; 07 my $dev = $usb->find_device(0xA81, 0x701); 08 $dev->open; 09 10 # Move Up 11 my $val = 0x02; 12 $dev->control_msg(0x21, 0x09, 0x02, 0, 13 chr($val), 1, 1000); 14 15 usleep(150_000); 16 17 # Stop 18 $val = 0x20; 19 $dev->control_msg(0x21, 0x09, 0x02, 0, 20 chr($val), 1, 1000); 21 22 # Read status 23 $val = 0x40; 24 my $buf; 25 $dev->control_msg(0x21, 0x09, 0x02, 0, 26 chr($val), 1, 1000); 27 $dev->bulk_read(1, $buf = "", 1, 1000); 28 printf "Status %08bn", ord($buf); |
Schütze A meldet
Um den Status des Geschützes abzufragen, schickt die Steuerung zunächst den Controlcode »0x40« mit »control_msg()« an das USB-Device, um gleich hinterher per Bulk-Transfer mit der Methode »bulk_read()« den bereitgestellten Datenstring abzuholen. Zeile 28 in Listing 2 schreibt als Ergebnis in den meisten Fällen »00000000« aus, es sei denn, der Turm steht am Anschlag oder eine Rakete wurde gerade abgefeuert.
Das Modul Device::USB::MissileLaun-cher::RocketBaby vom CPAN bietet eine schöne Abstraktion der Schnittstelle, ein neu konstruiertes Objekt verfügt über die Methoden »do()« und »cando()«, die Aktionen als Strings wie »left«, »up«, »fire« oder »stop« entgegennehmen. Die Methode »do()« führt die entsprechende Aktion aus, »cando()« hingegen prüft, ob die Aktion durchführbar ist.
Listing 3 illustriert den Gebrauch. Der Code dreht den Geschützturm zunächst bis ganz nach links unten, damit er dessen genaue Position kennt. Anschließend misst er die Zeit, die er benötigt, um den Turm sowohl nach ganz oben als auch bis zum rechten Anschlag zu drehen. Das Skript halbiert dann beide Zeiten, fährt den Turm mit den gewonnenen Werten zurück in die Mitte und feuert eine Rakete nach der anderen ab.
|
Listing 3: |
|---|
01 #!/usr/local/bin/perl -w
02 use strict;
03
04 use
05 Device::USB::MissileLauncher::RocketBaby;
06 use Time::HiRes qw(usleep gettimeofday
07 tv_interval);
08
09 my $rb =
10 Device::USB::MissileLauncher::RocketBaby
11 ->new();
12
13 do_until("left");
14 do_until("down");
15
16 my $right_start = [gettimeofday];
17 do_until("right");
18 my $right_elapsed = tv_interval(
19 $right_start, [gettimeofday] );
20
21 my $up_start = [gettimeofday];
22 do_until("up");
23 my $up_elapsed = tv_interval(
24 $up_start, [gettimeofday] );
25
26 do_until("left", $right_elapsed/2);
27 do_until("down", $up_elapsed/2);
28
29 for(1..3) {
30 do_until("fire");
31 usleep(100_000);
32 }
33
34 ###########################################
35 sub do_until {
36 ###########################################
37 my($what, $max_time) = @_;
38
39 my $start = [gettimeofday];
40
41 while($rb->cando( $what )) {
42 $rb->do( $what );
43 usleep(100_000);
44 last if defined $max_time and
45 tv_interval($start,
46 [gettimeofday]) > $max_time;
47 }
48 $rb->do("stop");
49 }
|
Installation
Linux benötigt das Paket »libusb-dev«, um aus dem Userspace auf USB-Geräte zugreifen zu können. Alle neueren Distributionen verfügen bereits darüber. Die Module Device::USB und Device::USB::MissileLauncher::RocketBaby installiert eine CPAN-Shell. Verschiedene Raketenwerfer [7] nutzen unterschiedliche Codes, die der Programmierer im Zweifelsfall aus dem Internet holt und in eine Abstraktion wie das Rocket-Baby-Modul vom CPAN verpackt.
Wie der Raketenwerfer vom Skript »center-fire« gesteuert herumorgelt, zeigt das Demo-Video auf [8]. Zum Schluss ein dringender Hinweis: Ein Export des Geräts oder des Programms in Schurkenstaaten ist verboten. (jcb)
|
Infos |
|---|
|
[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2009/05/Perl] [2] “The Great Office War”: [http://www.youtube.com/watch?v=pVKnF26qFFM] [3] Pedram Amini, “Python Interfacing a USB Missile Launcher”: [http://dvlabs.tippingpoint.com/blog/2009/02/12/python-interfacing-a-usb-missile-launcher] [4] Pyrocket: [http://code.google.com/p/pyrocket] [5] , Sreekrishnan Venkateswaran, “Essential Linux Device Drivers”: Prentice Hall, 2007 [6] Libusb-Projekt: [http://libusb.sourceforge.net] [7] Raketenwerfer-Typen: http://www.amazon.de/USBMis-USB-Raketenwerfer/dp/B000KC3YQK] [8] Demo-Video: [http://www.youtube.com/watch?v=-6qTRhDijJc] |
|
Der Autor |
|---|
|
|








