#include "ufi.h"
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
//#include <stdlib.h> // div_t
#include <string.h> // memset, memcpy
#define M0E (PORTF&=~0x10)
#define M0A (PORTF|= 0x10)
#define M1E (PORTF&=~0x80)
#define M1A (PORTF|= 0x80)
#define M2E (PORTB&=~0x02)
#define M2A (PORTB|= 0x02)
#define M3E (PORTD&=~0x02)
#define M3A (PORTD|= 0x02)
#define DS0E (PORTF&=~0x40)
#define DS0A (PORTF|= 0x40)
#define DS1E (PORTF&=~0x20)
#define DS1A (PORTF|= 0x20)
#define DS2E (PORTC&=~0x40)
#define DS2A (PORTC|= 0x40)
#define DS3E (PORTD&=~0x04)
#define DS3A (PORTD|= 0x04)
#define TK0 (PINB&0x04)
#define STPT (PINB|=0x08) // Step-Toggle
#define WP (PINB&0x10)
#define DC (PINB&0x20) // angeblich: High bei Diskwechsel, Low wenn Disk vorhanden nach seek(-1)
#define SS0 (PORTB|= 0x40) // Kopf 0
#define SS1 (PORTB&=~0x40) // Kopf 1
#define WD (PIND&0x01)
#define IDX (PIND&0x08)
#define DIRL (PORTD&=~0x80) // zur Mitte, höhere Spur
#define DIRH (PORTD|= 0x80) // nach Außen, niedrigere Spur
// Die Variante "hi<<8|lo" generiert in avr-gcc sinnlosen Kode.
// Der Umweg über eine union wird hingegen hervorragend optimiert.
union W{
byte b[2];
unsigned w;
};
// USB: "Command Block Wrapper"
// Dieser wird (nur) für den Bulk-Only-Transport gebraucht,
// während bei CBI (Control-Bulk-Interrupt) nur der scsi-Teil übertragen wird.
// (dort mit 10 oder 12 Bytes)
static struct _CmdBlock{
long signature; // 0..3 dCBWSignature 0x43425355 == 'USBC'
long tag; // 4..7 dCBWTag wird zu CmdStatus.tag kopiert
union{
long datalen; // 8..11 dCBWDataTransferLength Nettodatentransferlänge
int datalenL;
};
byte flags; // 12 bmCBWFlags Bit 7: 0=out, 1=in
byte lun; // 13 bCBWLUN (untere 4 Bits) Gerätenummer
byte scsilen; // 14 bCBWCBLength (untere 5 Bits) Länge 1..16
byte data[16]; // 15..30 CBWCB
inline byte opcode() const {return data[0]&0x3F;}
// Die LUN in den Bits 7:5 von data[1] ist ungültig!
inline unsigned lba() const {W w={{data[5],data[4]}}; return w.w;}
// liefert LBA in richtiger Bytesortierung (nur 16 Bit)
inline unsigned len() const {W w={{data[8],data[7]}}; if (data[0]&0x80) w.b[1]=w.b[0],w.b[0]=data[9]; return w.w;}
// liefert Länge in richtiger Bytesorterung je nach Opcode (nur 16 Bit)
}CmdBlock;
// erst mal nur ein Standard-Sektor! (Also NUR EINER und NUR 512 Byte = MSDOS)
static struct _Sector{
byte header;
byte data[512];
unsigned crc;
}sector;
// USB: "Command Status Wrapper"
// Dieser wird (nur) für den Bulk-Only-Transport gebraucht.
// Fehler werden bei CBI per EP0Stall gemeldet.
static struct _CmdStatus{
long signature; // 0..3 dCSWSignature 0x53425355 == 'USBS'
long tag; // 4..7 dCSWTag Kopie von CmdBlock.tag
union{
long datamiss; // 8..11 dCSWDataResidue 0 .. CmdBlock.datalen
int datamissL;
};
byte status; // 12 bCSWStatus 0=OK, 1=Fehler, 2=Phasenfehler
}CmdStatus;
Disk disks[5]={
// {{ 3,1,2,80 },0, 512,500, 17,2,80,1}, // 3½" HD
{{ 5,1,2,80,0},1, 512,500, 15,2,80,1}, // 5¼" HD
// {{ 5,1,2,80 },2,1024,250, 7,2,80,2}, // 5¼" DD KC85
// {{ 8,3,2,40 },3, 128,150, 31,2,40,3}, // 8" CP/M-Sektorlänge
// {{80,1,1,28 },4,1024,500,254,1,28,1}, // QIC-80 Bandlaufwerk (wieviel Sektoren eigentlich? 5714?)
};
/*
byte Disk::index() const {
return this-disks;
}
*/
void Disk::poweron() {
for (dp=disks; dp<disks+LUNS; dp++) {
dp->d.track=-1; // Kopfposition unbekannt
dp->error(0x629); // Power-on-Reset
}
}
// Das erste, was usbstor.sys abfragt
static int GetDriveInfo() {
if (CmdBlock.data[1]&0x03 || CmdBlock.data[2]) {
return 0x8524; // Ungültiger Kommandoblock
}
switch (dp->d.type) {
case 0: sector.data[0]=0x1F; break; // kein Laufwerk angeschlossen
case 80: sector.data[0]=0x01; break; // Sequenzieller Zugriff bei QIC-80, sonst Blockzugriff
}
sector.data[1]=0x80; // Bit 7: wechselbar
sector.data[3]=0x01; // UFI
sector.data[4]=0x1F; // 0x1F für UFI
strcpy_P((char*)sector.data+8,PSTR("ChaosCh")); // wird während Installation angezeigt
PGM_P s=0;
switch (dp->d.type&0xFE) {
case 2: s=PSTR("3\xBD\"-Laufwerk"); break; // wird während Installation angezeigt, Windows erwartet ANSI-Kodierung
case 4: s=PSTR("5\xBC\"-Laufwerk"); break;
case 8: s=PSTR("8\"-Laufwerk"); break;
case 80: s=PSTR("Streamer QIC-80"); break; // 16 Bytes Platz, üblicherweise max. 15 Zeichen benutzt
}
if (s) strcpy_P((char*)sector.data+16,s);
sector.data[32]='0'; // Revision: nicht zu sehen, in allen Beispielen "0.00"
sector.data[33]='8';
sector.data[34]='1';
sector.data[35]='5';
return 36;
}
static byte* addPage01(byte*p) {
*p++=0x01; // Seitennummer
*p++=0x0A; // Folgebytes
*p++=0; // 0x00: kein Fehler, 0x04: Fehler
// Lesewiederholungen, max. 255
// Schreibwiederholungen
return p+8;
}
static byte* addPage05(byte*p) {
*p++=0x05; // Seitennummer
*p++=0x1E; // Folgebytes
int i=dp->transferrate;
*p++=i>>8;*p++=i&0xFF; // Transferrate (pro Laufwerk und Diskette)
*p++=dp->heads; // Anzahl Köpfe
*p++=dp->sectors; // Sektoren pro Spur
i=dp->sectorlength;
*p++=i>>8;*p++=i&0xFF; // Sektorlänge in Bytes
*p++=0;
*p++=dp->tracks; // 80 Zylinder (Spuren)
p+=9;
*p++=5; // Motoreinschaltverzögerung: fest 0,5 Sekunden
*p++=30; // Motorausschaltverzögerung: fest 3 Sekunden
p+=7;
i=dp->d.rotationrate();
*p++=i>>8;*p++=i&0xFF; // Drehzahl
return p+2;
}
static byte* addPage1B(byte*p) {
*p++=0x1B; // Seitennummer
*p++=0x0A; // Folgebytes
*p++=0; // 0x80 für „Systemdiskettengerät“ — hm, was das sein soll??
*p++=1; // Gesamtanzahl logischer Einheiten (noch einmal??)
return p+8;
}
static byte* addPage1C(byte*p) {
*p++=0x1C; // Seitennummer
*p++=0x06; // Folgebytes
p++;
*p++=5; // Inaktivitätstimer: fest 2 s
return p+4;
}
static int ModeSense() { // Page Control (Byte 2: [7:6]) wird ignoriert
byte page=CmdBlock.data[2]&0x3F;
sector.data[2]=0x94; // 0 = default, 0x1E = 3½"DD 0x94 = 3½"HD
sector.data[3]=0x80; // 0 = schreibbar, 0x80 = schreibgeschützt
byte*p=sector.data+8;
switch (page) {
case 0x01: p=addPage01(p); break; // Read-Write Error Recovery
case 0x05: p=addPage05(p); break; // Flexible Disk Page
case 0x1B: p=addPage1B(p); break; // Removable Block Access Capacities Page
case 0x1C: p=addPage1C(p); break; // Timer and Protect Page
case 0x3F: p=addPage01(p);
p=addPage05(p);
p=addPage1B(p);
p=addPage1C(p); break; // Return all pages
}
return sector.data[1]=p-sector.data; // Länge eintragen (LSB genügt)
}
static int sync() {
DIRH; // DIR = H = nach außen
byte i=dp->d.tracks+5; // Maximale Schrittzahl
while (TK0) { // Anzeige der Spur 0
if (!--i) return 0x8206; // Keine Referenz gefunden
STPT;
_delay_ms(5);
STPT;
_delay_ms(5);
}
dp->d.track=0;
dp->d.motto=20;
return 0;
}
// Köpfe relativ bewegen
static void seek(char by) {
if (!by) return;
if (by>=0) DIRL; // DIR = L = nach innen
else{
DIRH; // DIR = H = nach außen
by=-by;
}
by<<=1; // Flanken doppelt
do{
STPT; // STEP toggeln
_delay_ms(4);
}while(--by);
}
// Köpfe auf Spur bewegen
static int seekto(char track) {
int e;
if (dp->d.track<0) {
e=sync();
if (e) return e;
}
seek(track-dp->d.track);
if (track<0) track=0; // Bei seekto(-1) zum Quittieren von Disk-Change
if (track && TK0) {dp->sense[2]=1; dp->d.track=-1; return 0x8302;} // No seek complete
// if (!track && !TK0) {dp->sense[2]=2; dp->d.track=-1; return 0x8302;} // No seek complete
dp->d.track=track;
dp->d.motto=20;
return 0;
}
// Laufwerk auswählen, andere Laufwerke abwählen, Motor aktivieren
static void drvon(byte index) {
switch (index) {
case 0: DS1A; DS2A; DS3A; DS0E; break;
case 1: DS0A; DS2A; DS3A; DS1E; break;
case 2: DS0A; DS1A; DS3A; DS2E; break;
case 3: DS0A; DS1A; DS2A; DS3E; break;
}
}
static void moton(byte index) {
switch (index) {
case 0: M0E; break;
case 1: M1E; break;
case 2: M2E; break;
case 3: M3E; break;
}
PRR1 = 0x11; // Timer3 aktivieren
TCCR3B= 0x0B; // Vorteiler 64, CTC-Modus
OCR3A = 25000-1; // teilt durch 25k, das ergibt 10 Hz
TIMSK3= 0x02; // mit Interrupt
}
// Laufwerk auswählen, andere Laufwerke abwählen, Motor aktivieren, warten
static int drvsel() {
drvon(dp->index);
// Motor hochlaufen lassen
moton(dp->index);
_delay_ms(0.5);
if (!dp->d.motto) {
/*if (DC)*/ dp->d.mediumpresent=false;
EICRA=0x80; // fallende Flanke an INT3
EIFR =0x08;
EIMSK=0x08; // <mediumpresent> vom Indexloch-Interrupt setzen lassen
_delay_ms(200);
EIMSK=0; // keine Indexloch-Interrupts mehr
}
// Timeout neu starten
dp->d.motto=20;
// Diskette gewechselt?
if (!dp->d.mediumpresent) {
seekto(-1);
if (DC) return 0x823A; // Keine Diskette
dp->d.mediumpresent=true;
// if (dp->index==1) PORTD&=~0x20;
return 0x8628; // Diskette gewechselt
}
return 0;
}
// Köpfe auf Spur 0 bewegen (Laufwerk ist bereits ausgewählt und Motor dreht)
static int Rezero() {
int e=drvsel();
if (e) return e;
return sync();
}
static void drvoff(byte index) {
switch (index) {
case 0: DS0A; break; // Auswahl abschalten
case 1: DS1A; break;
case 2: DS2A; break;
case 3: DS3A; break;
}
}
static void motoff(byte index) {
switch (index) {
case 0: M0A; break; // Motor abschalten
case 1: M1A; break;
case 2: M2A; break;
case 3: M3A; break;
}
}
// Motor und LED abschalten lassen
// Wie beim PC leuchtet auch die LED nach (müsste sie nicht)
// Sie geht nur dann vorfristig aus, wenn auf ein anderes Laufwerk zugegriffen wird.
static void ontimertick() {
byte count=0;
for (Disk*dp=disks;dp<disks+4;dp++) {
if (!dp->d.motto) continue;
++count;
if (--dp->d.motto) continue;
motoff(dp->index);
drvoff(dp->index);
--count;
}
if (!count) {
PRR1=0x19; // Timer3 deaktivieren
// PORTD|=0x20; // LED aus
}
}
// 10 Hz
ISR(TIMER3_COMPA_vect) {
// PIND|=0x20; // Kippen => 5 Hz
ontimertick();
}
// Indexloch
ISR(INT3_vect) {
dp->d.mediumpresent=true; // nur wenn Interrupt freigegeben
}
static int Seek() {
int e=drvsel();
if (e) return e;
#if 1
unsigned lba=CmdBlock.lba();
asm(
"0: lsl %A0 \n"
" rol %B0 \n" // Kein CY weil LBA < 7FFF
" cp %B0,%2 \n"
" brcs 2f \n"
" sub %B0,%2 \n"
" inc %A0 \n"
"2: dec %3 \n"
" brne 0b \n"
:"=r" (lba)
:"0" (lba), "r" (dp->sectors*dp->heads), "r" ((byte)8) );
byte track=lba&0xFF;
#else
byte track=CmdBlock.lba()%byte(dp->sectors*dp->heads);
#endif
// Spur ansteuern
return seekto(track);
}
static void fillcap(byte*dst,int blocks,int seclen,byte type=0) {
--blocks; // letzter LBA
dst[2]=blocks>>8;
dst[3]=blocks&0xFF;
dst[4]=type;
dst[6]=seclen>>8;
dst[7]=seclen&0xFF;
}
static int GetCapacity() {
if (dp->sense[0]) return -1; // Erst mal Fehler lesen lassen!
if (!dp->d.mediumpresent) {
int e=drvsel();
if (e) return e;
}
fillcap(sector.data,dp->numberblocks(),dp->sectorlength);
return 8;
}
static int GetFormatCapacity() {
if (dp->sense[0]) return -1; // Erst mal Fehler lesen lassen!
sector.data[3]=8;
fillcap(sector.data+4,dp->numberblocks(),dp->sectorlength,dp->d.mediumpresent?2:0);
return 12;
}
static int GetLastError() {
sector.data[0]=0x70;
sector.data[2]=dp->sense[0];
sector.data[7]=10;
sector.data[12]=dp->sense[1];
sector.data[13]=dp->sense[2];
return 18;
}
static int DriveReady() {
if (!dp->d.mediumpresent) return 0x823A;
return 0;
}
static int StartStop() {
if (CmdBlock.data[4]&2) { // Auswurf nicht möglich
return 0x8524; // Ungültiges Feld im Kommandopaket
}
if (CmdBlock.data[4]&1) return drvsel(); // auch: Medium erkennen??
drvoff(dp->index);
return 0;
}
static int ResetDrive() { // Selbsttest ausführen (wie umfangreich?)
return 0;
}
static int LockDisk() {
if (CmdBlock.data[4]) { // Verriegeln nicht möglich
return 0x8524; // Ungültiges Feld im Kommandopaket
}
return 0;
}
static int ModeSelect() {
//TODO: Zusatzdaten von Bulk-Out-Pipe lesen
return 0;
}
// Minimal 1 Byte, maximal 64 Byte
static void epwrite(const byte*p,byte l) {
while (!(UEINTX&1<<TXINI));
UEINTX&=~(1<<TXINI);
do UEDATX=*p++; while (--l);
UEINTX&=~(1<<FIFOCON);
}
// Mehr als 64 Byte
static void epwrite(const byte*p,int ll) {
while (ll) {
byte l=ll<=64?(byte)ll:64;
epwrite(p,l);
p+=l;
ll-=l;
}
}
// 1..64 Bytes
static void epread(byte*p,byte l) {
while (!(UEINTX&1<<RXOUTI)); // Warten bis Daten da sind
UEINTX&=~(1<<RXOUTI);
do *p++=UEDATX; while (--l); // einsammeln und linear adressierbar machen
UEINTX&=~(1<<FIFOCON);
}
// 0.. KBytes
static void epread(byte*p,int ll) {
while (ll) {
byte l=ll<=64?(byte)ll:64;
epread(p,l);
p+=l;
ll-=l;
}
}
// Sammelroutine für Lesen, Schreiben, Schreiben+Verify und Verify (ohne Daten)
static int ReadWrite(char opcode) {
unsigned len=CmdBlock.len();
if (!len) return 0;
int e=drvsel(); // Laufwerk aktivieren, andere abwählen, Motor starten
if (e) return e;
if (opcode==0x0A && !WP) return 0x8727; // Diskette schreibgeschützt
unsigned lba=CmdBlock.lba();
do{
asm(
"0: lsl %A0 \n"
" rol %B0 \n" // niemals CY weil LBA < 7FFF
" cp %B0,%2 \n"
" brcs 2f \n"
" sub %B0,%2 \n"
" inc %A0 \n"
"2: dec %3 \n"
" brne 0b \n"
:"=r" (lba)
:"0" (lba), "r" (dp->sectors), "r" ((byte)8) );
byte sec=lba>>8; // Divisionsrest
byte track=lba&0xFF; // Divisionsergebnis
if (dp->heads&2) { // weitere Division vermeiden
// Kopf auswählen
if (track&1) SS1; else SS0;
track>>=1;
}
// Spur ansteuern
e=seekto(track);
if (e) return e;
if (opcode==0x0A) epread(sector.data,512); // beim Schreiben
#if 1
PORTD&=~0x20; // EIN
_delay_ms(20);
e=readwrite(sec+1);
PORTD|= 0x20; // AUS
#else
e=readwrite(sec+1);
if (e) return e;
#endif
if (opcode==0x08) epwrite(sector.data,512); // beim Lesen
lba++;
}while(--len);
dp->d.motto=20;
CmdBlock.datalen=0;
return 0; // ReadWrite() sendet oder holt Daten selbständig
}
static byte DecodeScsiCommand() {
dp=disks+CmdBlock.lun;
int l=0x8520; // Anzahl zu liefernder Bytes, außer bei ReadWrite, oder Fehlerkode
switch (CmdBlock.opcode()) { // (Datenphase)
// 6-Byte-Befehle
case 0x00: l=DriveReady(); break; // Unit bereit (-)
case 0x01: l=Rezero(); break; // Spur 0 anfahren (-)
case 0x03: l=GetLastError(); break; // = Request Sense (18)
case 0x04: break; // Formatieren
case 0x12: l=GetDriveInfo(); break; // PnP-Abfrage (input)
case 0x15: l=ModeSelect(); break;
case 0x1A: l=ModeSense(); break; // Modus melden
case 0x1B: l=StartStop(); break;
case 0x1D: l=ResetDrive(); break;
case 0x1E: l=LockDisk(); break;
// 10-Byte-Befehle
case 0x23: l=GetFormatCapacity();break;// Formatierbare Kapazitäten lesen (12+)
case 0x25: l=GetCapacity(); break; // Aktuelle Kapazität lesen (8)
case 0x28: // Lesen
case 0x2A: // Schreiben
case 0x2E: // Schreiben mit Verify
case 0x2F: l=ReadWrite(CmdBlock.opcode()&0x1B); break; // Lesbarkeit und Prüfsumme testen
case 0x2B: l=Seek(); break; // LBA anfahren (-)
}
if (l>=0 && CmdBlock.datalen>512) l=0x8280; // Fehler: Speicherüberlauf (sollte nie passieren)
if (l<0) {
if (l!=-1) dp->error(l&0xFFF); // 12 Bit davon setzen
UENUM=1;
UECONX=0x21; // STALL setzen
return 1;
}
int datalen=CmdBlock.datalenL; // ReadWrite() muss datalen auf Null abbauen, in allen anderen Fällen muss datalen ≤ 512 sein
l-=datalen;
if (l>=0) CmdStatus.datamissL=l; // Wenn die SCSI-Funktion mehr hätte liefern können
epwrite(sector.data,datalen); // Paket abschicken
dp->error(0);
dp->sense[2]=0; // Fehler löschen
return 0; // okay oder nicht
}
void usbPollEP2() {
UENUM=2; // der Bulk-Out-Endpoint
byte ueintx=UEINTX;
if (ueintx&1<<RXOUTI) { // Daten angekommen?
PINB|=1<<0; // toggeln
UEINTX=ueintx&=~(1<<RXOUTI);
byte*p=(byte*)&CmdBlock;
do *p++=UEDATX; while (p<(byte*)(&CmdBlock+1)); // einsammeln und linear adressierbar machen
UEINTX=ueintx&=~(1<<FIFOCON);
if (CmdBlock.signature!=0x43425355
|| CmdBlock.lun>=LUNS
|| CmdBlock.flags&0x1F
|| (byte)(CmdBlock.scsilen-1)>=16) {
UECONX=0x21; // beide Endpoints abriegeln
UENUM=1;
UECONX=0x21;
}else{
if ((unsigned long)CmdBlock.datalen<=512) { // Aber nicht für mehr als 1 Sektor
if (CmdBlock.flags&0x80) { // Rückgabepuffer vorbereiten
UENUM=1;
memset(sector.data,0,CmdBlock.datalenL);
}else epread(sector.data,CmdBlock.datalenL);
}
// Die IN-FIFO sollte frei sein
CmdStatus.signature=0x53425355;
CmdStatus.tag=CmdBlock.tag;
CmdStatus.datamiss=0;
CmdStatus.status=DecodeScsiCommand();
do{
usbPollEP0();
UENUM=1;
}while (UECONX&1<<STALLRQ); // Warte bis der Host den EP1Stall entfernt hat
epwrite((byte*)&CmdStatus,(byte)13); // Statuspaket abschicken
}
PINB|=1<<0; // toggeln
}
}
Vorgefundene Kodierung: UTF-8 | 0
|