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).
HANDLE hAccess; for (int n=9; n; n--) { // von hinten probieren TCHAR 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:Da sich auch normale Parallelschnittstellen mit diesem
CreateFile()
-Aufruf öffnen lassen, sollte ein USB2LPT-Test
folgen.
// 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-Datumsstempel
DWORD 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 Datum
GetDateFormat(LOCALE_USER_DEFAULT,0,&st,NULL,s,20);
// ...
}else{
// dies ist ein Standard-Parallelport oder irgendetwas anderes
}
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 InpOut32.DLL
erspart.
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), //0x222010 IoData,sizeof IoData,NULL,0,&BytesRet,NULL); }Dabei ist a ein Adressbyte, von dem die Basisadresse eines echten Parallelports abgezogen wurde, und es ergibt sich:
BYTE inb(BYTE a) { BYTE IoData[1]; DWORD BytesRet; IoData[0]=a|0x10; // Lese-Bit DeviceIoControl(hAccess, CTL_CODE(FILE_DEVICE_UNKNOWN,0x804,METHOD_BUFFERED,FILE_ANY_ACCESS), IoData,sizeof(IoData),IoData,sizeof(IoData),&BytesRet,NULL); return IoData[0]; }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:
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.