Weil aber das Abfangen und Umleiten von Portzugriffen wegen der Paketorientierung von USB ĂĽber eine bestimmte Geschwindigkeit nicht hinauskommt, erscheint eine API fĂĽr neue, spezialisierte Programme dennoch sinnvoll.
Die Strategie ist einfach und erfordert keine zusätzlichen DLLs:
Öffnen Sie mit CreateFile das Gerät "\\.\LPT1"
(oder "LPT2"
wenn es
das zweite Gerät ist usw.), und schicken/holen die zu transferierenden
Daten über möglichst wenige Aufrufe von DeviceIoControl.
InpOut32.DLL
(oder ähnliches), und,
zur Freude aller Administratoren,
es wird kein Sicherheitsloch geöffnet.
Denn InpOut32.DLL
ist wie eine Einladung, den Rechner mit wilden
Portzugriffen totzulegen (notfalls lässt sich damit sogar eine
Festplatte formatieren).
Da sich auch normale Parallelschnittstellen mit diesemHANDLE hAccess; for (int n= 9 ; n; n--) { // von hinten probierenTCHAR DevName[ 12 ];wsprintf(DevName, "\\\\.\\LPT%u",sn);hAccess=CreateFile(DevName, GENERIC_READ|GENERIC_WRITE, 0 ,NULL,OPEN_EXISTING,0 ,0 );if (hAccess!=INVALID_HANDLE_VALUE) goto found; } hAccess= 0 ;found:
CreateFile()
-Aufruf öffnen lassen, sollte ein USB2LPT-Test
folgen.
Der Hintergedanke, warum USB2LPT sich genauso wie ein echtes Parallelport öffnen lässt, ist, dass ein „Upper Filter Driver“ für normale Parallelports vorgesehen ist, der den gleichen Zugriffsmechanismus erlaubt und die Verwendung von// Den 8051 XRAM oder ATmega Flash-Speicher ab Adresse 6 auslesen, wo das Firmware-Datum steht. // Klappt das nicht, ist's kein USB2LPT. #include "usb2lpt.h"WORD addr = 6 ;WORD date = 0 ; // FAT-DatumsstempelDWORD BytesRet; if (DeviceIoControl(hAccess,IOCTL_VLPT_XramRead/*0x22228E*/,&adr,sizeof(adr),&date,sizeof(date),&BytesRet,NULL)) { // dies ist tatsächlich ein USB2LPT, und man das Datum wie folgt zur Anzeige bringen: FILETIME ft; DosDateTimeToFileTime(date, 0 ,&ft);SYSTEMTIME st; FileTimeToSystemTime(&ft,&st); TCHAR s[ 20 ]; // In diesen Zeichenpuffer kommt das landestypisch formatierte DatumGetDateFormat(LOCALE_USER_DEFAULT, 0 ,&st,NULL,s,20 );// ... }else{ // dies ist ein Standard-Parallelport oder irgendetwas anderes }
InpOut32.DLL
erspart.
Dabei ist a ein Adressbyte, von dem die Basisadresse eines echten Parallelports abgezogen wurde, und es ergibt sich:void outb(BYTE a, BYTE b) { BYTE IoData[ 2 ];DWORD BytesRet; IoData[ 0 ]=a;IoData[ 1 ]=b;DeviceIoControl(hAccess, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804 ,METHOD_BUFFERED,FILE_ANY_ACCESS), //0x222010IoData,sizeof IoData,NULL, 0 ,&BytesRet,NULL);}
Genauso wie oben ist a ein Adressbyte, von dem die Basisadresse eines echten Parallelports abgezogen wurde. Gelesen werden die Pegel an den Portpins (nicht notwendigerweise dasselbe wie das ausgegebene Byte). Es ergibt sich die folgende Liste:BYTE inb(BYTE a) { BYTE IoData[ 1 ];DWORD BytesRet; IoData[ 0 ]=a|0x10 ; // Lese-BitDeviceIoControl(hAccess, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804 ,METHOD_BUFFERED,FILE_ANY_ACCESS),IoData,sizeof(IoData),IoData,sizeof(IoData),&BytesRet,NULL); return IoData[ 0 ];}
Es gibt hierzu eine einfache DLL-Implementierung
als — gewissermaßen — vorläufige Referenzimplementierung.
Sieht man sich die Programmstücke genauer an, sieht man, dass alles über Ein- und Ausgabepuffer eines IOCTL-Kodes (0x222010) abgewickelt wird. Das ist kombinierfähig bis zu beliebigen Puffergrößen. Eine sinnvolle Obergrenze sind 64 Bytes, so ist es auch im Treiber realisiert.
Die Kombination von Zugriffen erfolgt durch das Hintereinanderschreiben von OUT-Adressen und OUT-Daten sowie IN-Adressen im Eingabepuffer. IN-Adressen sind das gleiche wie OUT-Adressen mit gesetztem Bit 4. Und: Für jede IN-Adresse benötigt man im Ausgabepuffer 1 Byte Platz. Das ist alles! Also eine Art Mikrocode für die USB2LPT-Firmware.
Beispielsweise:
// Lesen aller 17 Portpins in einem Rutsch void GetPinStates(BYTE states[ 3 ]) {static const BYTE SendBytes[ 3 ]={0x10 ,0x11 ,0x12 };DWORD BytesRet; DeviceIoControl(hAccess, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804 ,METHOD_BUFFERED,FILE_ANY_ACCESS),SendBytes, 3 ,states,3 ,&BytesRet,NULL);}
Die entsprechende HintertĂĽr sind vor allem weitere Adressen:
Die noch verbleibende Adresse 11 ist nicht belegt.
Die Feature-Register-Bits:
Nur High-Speed und Full-Speed: Durch Offene-Senke-Simulation wird der 5-V-Spannungspegel für HIGH-Ausgänge erreicht. Ansonsten werden nur maximal 3,3 V ausgegeben, was für manche Hardware nicht ausreichen könnte.
Nur Low-Speed:
Da hier nur schwache interne Pullup-Widerstände vorhanden sind (Richtwert 40 kΩ),
ist hierbei die Offene-Senke-Simulation mit Vorsicht zu verwenden
(in der Regel mit externem Pull-Up-Widerstand).
Der HIGH-Ausgangspegel liegt bei 5 V.
Falls 3,3 V gewünscht sind, muss dass USB2LPT-Gerät modifiziert werden,
durch Einsetzen einer Betriebsspannung reduzierenden Doppeldiode BAV199 bei D2
(Brücke SJ3) durchkratzen, und Ersatz von R1 durch 1,5 kΩ.
Siehe Schaltplan.
Die Sonderadresse 0x24 ist noch nicht implementiert.
Die gewĂĽnschte Waveform wird durch Beschreiben passender RAM-Bereiche geladen.
Durch Umschalten in den High-Speed-Transfer-Modus geht das normale Verhalten als Parallelport verloren! Während des High-Speed-Transfers blinkt die blaue LED.
Die genauere Spezifikation wird noch festgelegt.
Der Zugriff ist derselbe wie bei Cypress' ezusb.sys
und geladenem vend_ax.hex
.
Dabei macht man sich eine un(ter)dokumentierte Eigenart von Windows'
DeviceIoControl()
zu Nutze: Ist im Gerätesteuerkode METHOD_IN_DIRECT gesetzt,
fungiert lpOutBuffer
als weiterer Eingabepuffer.
Dieser enthält schließlich die Nutzdaten für die Control-Transfer-Datenphase.
lpInBuffer
zeigt nur auf den Wert von wValue
(wenn nInBufferSize==2
, wIndex
ist dann Null)
oder auf die Werte von wValue
und wIndex
(wenn nInBufferSize==4
).
Es sind belegt:
Symbolische Konstante | Wert der Konstante | Zugehöriges bRequest | Speicher beim 8051-Controller | Speicher beim ATmega-Controller |
---|---|---|---|---|
IOCTL_VLPT_AnchorDownload | 0x00222281 | 0xA0 | XRAM (limitiert) | nicht implementiert |
IOCTL_VLPT_RamRead | 0x00222286 | 0xA1 | iRAM, dRAM: Register, E/A dRAM ist ab 0x100 erreichbar mittels selbstmodifizierendem Kode | RAM: Register, E/A |
IOCTL_VLPT_RamWrite | 0x00222285 | |||
IOCTL_VLPT_EepromRead | 0x0022228A | 0xA2 wie vend_ax.iic | Boot-I²C-EEPROM 24C64 Beliebige I²C-Peripherie mit wIndex!=0 | interner EEPROM |
IOCTL_VLPT_EepromWrite | 0x00222289 | |||
IOCTL_VLPT_XramRead | 0x0022228E | 0xA3 wie vend_ax.iic | XRAM (komplett) == ROM Der 8051 ist von-Neumannisiert | Flash-Speicher |
IOCTL_VLPT_XramWrite | 0x0022228D | nicht implementiert (Lock-Bit gesetzt) |
USB2LPT-Version | 1.0, 1.1 | 1.2, 1.3, 1.4, 1.7 | 1.5 | 1.6 | 1.8 | Anmerkung |
---|---|---|---|---|---|---|
Controller | AN2131 | CY7C68013A | ATmega48 | ATmega8 | ATmega32U4 | |
Größe | 8 KByte (externer 24C64 via I²C) | 256 Byte | 512 Byte | 1 KByte | ||
Firmware | USB2LPT.A51 | USB2LPT2.A51 | usb2lpt5.c | usb2lpt6.c | usb2lpt8.cpp | |
Adresse | Inhalt | |||||
0 | 0xB2 | 0xC2 | 0x12 | 0xFF (frei) | 0x08 | Firmware-Start |
0xBE (beliebig) | 0xCE (beliebig) | USB-Deskriptoren (um Flash- zu sparen) | 0x42 | USB-Bootloader bleibt aktiv | ||
1.. | restliche Firmware (*.IIC) | frei (0xFF) | Kein Speicherabzug, sondern segmentiertes Bootloader-Image (zu erzeugen mittels hex2bix )
| |||
0xFFF0.. (-16) | Ab hier garantierter Datenbereich | zurzeit mit 0xFF gelöscht | ||||
0xFFF0..0xFFF5 (-16) | angedacht für TUserCfg | „roamende“ Konfigurationsdaten | ||||
0xFFF9 (-7) | ECR-Startwert | ab 2012-03-06 | ||||
0xFFFA (-6) | frei (0xFF) | OSCCAL-Startwert | frei | nur 12,8-MHz-Version (ohne Quarz) | ||
0xFFFB (-5) | Feature-Startwert | wird von der Firmware automatisch geschrieben | ||||
0xFFFC..0xFFFF (-4) | Seriennummer (DWORD) | 0 oder 0xFFFFFFFF = keine Seriennummer | ||||
0xFFFF (-1) | Seriennummer (BYTE) | - | Nur ganz frühe Geräte; praktisch ausgestorbene Variante |
Da die EEPROMs allesamt mit unvollständiger Adressdekodierung arbeiten, gelangt man mit den hohen Adressen automatisch ans EEPROM-Ende, unabhängig von der jeweiligen EEPROM-Größe.
Dieser Abschnitt ist für die Implementierung von Treibern relevant. Also insbesondere für Nicht-Windows-Systeme. Oder auch zum Nachbau von USB-Geräten mit anderen Mikrocontrollern. Grundlegend ist die Übermittlung von „Mikrocode“, die Übertragung von Adressen und Datenbytes, genauso wie oben beschrieben. Im einfachsten Fall zwei Bytes für einen OUT-Befehl sowie ein Byte für einen IN-Befehl, gefolgt vom Abfragen des IN-Datenbytes.
Für neuere Firmware-Implementierungen (ab 2009) gilt: Das OUT-Datenbyte zu einem OUT-Adressbyte darf sich in einem nachfolgenden Transfer befinden. Ansonsten müssen sich OUT-Adresse und zugehöriges OUT-Datenbyte stets in einem USB-Transfer-Block befinden.
FĂĽr Full-Speed- und High-Speed-USB2LPT gilt:
Es existiert Doppel-Pufferung fĂĽr USB-OUT und USB-IN-Bulk-Transfers.
Daher darf man bis zu 3 USB-OUT-Blöcke senden, bevor man die Ergebnisse
(in ebenso vielen Blöcken) abholen muss.
In allen anderen Fällen gilt:
Der USB-IN-Transfer-Block muss sich dem USB-OUT-Transfer-Block anschlieĂźen
(keine Schachtelung erlaubt).
USB2LPT bietet (bis zu) 4 Möglichkeiten zu deren Übertragung. Ein gemischter Zugriff (bspw. Senden des Mikrocodes nach Methode 1 und Lesen der IN-Datenbytes nach Methode 2) ist nicht vorgesehen und dessen Funktion implementationsabhängig.
Diese Schnittstelle ist auf minimalem Overhead ausgelegt. Sie ist die ursprĂĽngliche Schnittstelle.
Unter der USB-Adresse mit VID = 0x16C0, PID = 0x06B3 .. 0x06B6 und dem Interface 0 bietet USB2LPT zwei Bulk-Pipes zum direkten Mikrocode-Transfer an.
Bulk Out (Länge je nach Daten) |
---|
n Bytes Mikrocode |
Bulk In (Länge je nach Daten) |
---|
n IN-Datenbytes |
Bei Low-Speed (seit 2007) gibt es hierbei zwei Alternate Settings:
usb2lpt.sys
macht es mittels
quasi-paralleler Abarbeitung, er setzt zwei IRPs „nach unten“ ab
und wartet auf beider Komplettierung.
Die darunter liegenden Treiberschichten zerstĂĽckeln den Bulk-Transfer
selbständig in die von den Deskriptoren diktierten Häppchen.
usb2lpt.sys
DeviceIoControl() benutzt
(zwei Puffer gleichzeitig) und nicht ReadFile()/WriteFile() (jeweils nur 1 Puffer)!
Naja, man hätte auch ReadFile() verballhornen können und den Puffer bidirektional benutzen
(das geht!! Wird von HID auch so benutzt!), aber mich schreckt das eher zurĂĽck.
Zusätzlich unterstützt usb2lpt.sys die Mikrocodeübertragung per WriteFile()
und das Abholen der Ergebnisdaten per ReadFile()
, um Programmierumgebungen
nutzen zu können, die kein DeviceIoControl()
unterstützen, etwa Skriptsprachen.Windows stückelt zu lange Transferpuffer selbständig in FIFO-Häppchen! Die maximale Blockgröße unter Windows ist typisch 4 KByte.
Bei Umschaltung auf GPIF-Transfers (nur High-speed-fähige USB2LPT Rev. 2,3,4,7) erfolgt über diese beiden Bulk-Pipes der High-Speed-Datenstrom mit bis zu 48 MByte/s (Burst, praktisch maximal 20 MByte/s). Bei Full-Speed sind's maximal 0,8 MByte/s, ohne GPIF, aber mit schneller 8-Bit-FIFO, ähnlich FT245. Eine genauere Spezifikation werde ich erst bei Bedarf erarbeiten und implementieren; insbesondere wie dieser Modus aktiviert und wieder deaktiviert wird.
Bei Fehlsteuerung antwortet die BULK- bzw. INTERRUPT-Pipe mit STALL:
Das Konzept mit den beiden BULK-Pipes ist einer gewöhnlichen seriellen oder TCP-Verbindung entlehnt und lässt sich daher mühelos auf eine serielle Schnittstelle oder einer Ethernet-Verbindung umsetzen.
DeviceIoControl()
)
ist die Datenmenge unbegrenzt:
Windows und das USB-Protokoll kĂĽmmert sich um entsprechende Pufferungen.
Unter der USB-Adresse mit VID = 0x16C0, PID = 0x06B3 .. 0x06B6 und dem Interface 0 kann man USB2LPT folgende vendor-spezifische Befehle schicken:
bmRequestType | bRequest | wValue | wIndex | wLength | Data Out | |
---|---|---|---|---|---|---|
0x40 | 0x90 | 0x0000 | 0x0000 | n | n Bytes Mikrocode |
bRequest
-Werte zur VerfĂĽgung.
Der Puffer fĂĽr die IN-Datenbytes ist auf 64 Byte begrenzt!)
bmRequestType | bRequest | wValue | wIndex | wLength | Data In | |
---|---|---|---|---|---|---|
0xC0 | 0x90 | 0x0000 | 0x0000 | n | n IN-Datenbytes |
bmRequestType | bRequest | wValue | wIndex | wLength | Data In | ||
---|---|---|---|---|---|---|---|
0xC0 | 0x91 | µCode[0] | 0x00 | 0x0000 | 0x0001 | IN-Datenbyte |
bmRequestType | bRequest | wValue | wIndex | wLength | |
---|---|---|---|---|---|
0x40 | 0x92 | µCode[0] | µCode[1] | 0x0000 | 0x0000 |
bmRequestType | bRequest | wValue | wIndex | wLength | Data In | ||
---|---|---|---|---|---|---|---|
0xC0 | 0x92 | µCode[0] | µCode[1] | 0x0000 | 0x0002 | 2 IN-Datenbytes |
bmRequestType | bRequest | wValue | wIndex | wLength | Data In | |||
---|---|---|---|---|---|---|---|---|
0xC0* | 0x93 | µCode[0] | µCode[1] | µCode[2] | 0x00 | n | n IN-Datenbytes |
bmRequestType | bRequest | wValue | wIndex | wLength | Data In | |||
---|---|---|---|---|---|---|---|---|
0xC0* | 0x94 | µCode[0] | µCode[1] | µCode[2] | µCode[3] | n | n IN-Datenbytes |
wLength = 0
sein,
und bmRequestType
sollte 0x40 (Datenrichtung: OUT) sein.
Für minimale Latenz werden für kurze Mikrocodes die vier „freien“ Bytes des Setup-Transfers mit Nutzdaten gefüllt. Das ist der Trick.
Mikrocode darf auch im Block unvollständig übertragen werden, dann wird dieser mit dem nächsten Transfer fortgesetzt. Beispiel:
Bei Fehlsteuerung antwortet der SETUP-Transfer mit STALL:
Beide Reports sind gleichartig aufgebaut:
Interrupt Out (stets 64 Bytes lang) | |||
---|---|---|---|
0x08 | n | n Bytes Mikrocode | ungenutzt |
Interrupt In (stets 64 Bytes lang) | |||
---|---|---|---|
0x08 | n | n IN-Datenbytes | ungenutzt |
Der Report-Deskriptor des Low-Speed-USB2LPT bietet keine Input- und Output-Reports, danach lässt sich eine Fallunterscheidung machen.
Seit September 2011 ist der HID-Report-Deskriptor so gestaltet, dass sich dieser mit den HidP-Funktionen vernünftig enumerieren lässt. Im wesentlichen wurden verschiedene Dummy-Usages zugeordnet.
Der Feature-Report überträgt per Feature-Out den Mikrocode und per Feature-In die IN-Datenbytes. Er ist wie folgt aufgebaut:
bmRequestType | bRequest | wValue | wIndex | wLength | Data Out | ||||
---|---|---|---|---|---|---|---|---|---|
0x21 | 0x09 | n | 0x03 | Interface | 0x00 | n+1 | n | n Bytes Mikrocode |
bmRequestType | bRequest | wValue | wIndex | wLength | Data In | ||||
---|---|---|---|---|---|---|---|---|---|
0xA1 | 0x01 | n | 0x03 | Interface | 0x00 | n+1 | n | n IN-Datenbytes |
Diese Ăśbertragungsart soll auch bei Full-Speed und High-Speed verfĂĽgbar sein, ist jedoch dort ungetestet.
Seit September 2011 ist der HID-Report-Deskriptor so gestaltet, dass sich dieser mit den HidP-Funktionen vernünftig enumerieren lässt. Im wesentlichen wurden verschiedene Dummy-Usages zugeordnet. Dies kann man mit dem Programm hidparse auflisten.