Source file: /~heha/basteln/PC/usbfloppy/usbfloppy.zip/ufi.cpp

#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
 }
}
Detected encoding: UTF-80