/* * femul: The Atari Floppy Emulator * also known as: * SIO2Linux * * Copyrights are held by the respective authors listed below. * Licensed for distribution under the GNU Public License. * * You need to use sio2pc cable to run this * * * Compilation: * Requires gcc for inline assembly * Only runs on Intel(tm) Pentium(tm) or better processors * * Currently, this does not support the 'format' or 'verify' SIO * commands. * * * Version History: * * Version 1.4 22 Mar 1998 Preston Crow * * Added support for read-only images. Any image that can't * be opened for read/write will instead be opened read-only. * Also, if a '-r' option appears before the image, it will * be opened read-only. * * Cleaned up a few things. The system speed is now determined * dynamically, though it still uses the Pentium cycle counter. * A status request will now send write-protect information. * Added a short usage blurb for when no options are specified. * * It should be slightly more tollerant of other devices active * on the SIO bus, but it could still confuse it. * * Version 1.3 20 Mar 1998 Preston Crow * * The status command responds correctly for DD and ED images. * * This version is fully functional. Improvements beyond this * release will focus on adding a nice user interface, and * making it better at recognizing commands, so as to interact * safely with real SIO devices. A possible copy-protection * mode may be nice, where the program watches all the activity * on D1: while the program loads off of a real device, recording * all data, timing, and status information. Whether yet another * file format should be used, or some existing format, is an open * matter. * * Version 1.2 17 Mar 1998 Preston Crow * * I've added in support for checking the ring status after reading * a byte to determine if it is part of a command. However, as this * requires a separate system call, it may be too slow. If that proves * to be the case, it may be necessary to resort to direct assembly- * language access to the port (though this would eliminate compatibility * with non-Intel Linux systems). That seems to not work well; many * commands aren't recognized, at least when using the system call to * check the ring status, so I've implemented a rolling buffer that will * assume it has a command when the last five bytes have a valid checksum. * That may cause problems if a non-SIO2PC drive is used. * * It seems to work great for reading SD disk images right now. * I haven't tested writing, but I suspect it will also work. * It has problems when doing DD disk images. I suspect the * problem has to do with the status command returning hard-coded * information. * * The debugging output should be easier to read now, and should always * be printed in the same order as the data is transmitted or received. * * Version 1.1 Preston Crow * Lots of disk management added. * In theory, it should handle almost any ATR or XFD disk image * file now, both reading and writing. * Unfortunately, it is quite broken right now. I suspect timing * problems, though it may be problems with incorrect ACK/COMPLETE * signals or some sort of control signal separate from the data. * * Version 1.0 Pavel Machek * * This is Floppy EMULator - it will turn your linux machine into * atari 800's floppy drive. Copyright 1997 Pavel Machek * distribute under GPL. */ /* * Standard include files */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Data structures */ struct atr_head { unsigned char h0; /* 0x96 */ unsigned char h1; /* 0x02 */ unsigned char seccountlo; unsigned char seccounthi; unsigned char secsizelo; unsigned char secsizehi; unsigned char hiseccountlo; unsigned char hiseccounthi; unsigned char unused[8]; }; enum seekcodes { xfd, /* This is a xfd (raw sd) image */ atr, /* This is a regular ATR image */ atrdd3 /* This is a dd ATR image, including the first 3 sectors */ }; /* * Prototypes */ static void err(const char *s); static void raw(int fd); static void ack(unsigned char c); static void senddata(int disk,int sec); static void sendrawdata(unsigned char *buf,int size); static void recvdata(int disk,int sec); static int get_atari(void); void getcmd(unsigned char *buf); static void loaddisk(char *path,int disk); static void decode(unsigned char *buf); int iscmd(void); /* * Macros */ #define SEEK(n,i) (seekcode[i]==xfd)?SEEK0(n,i):((seekcode[i]=atr)?SEEK1(n,i):SEEK2(n,i)) #define SEEK0(n,i) ((n-1)*secsize[i]) #define SEEK1(n,i) (ATRHEAD + ((n<4)?((n-1)*128):(3*128+(n-4)*secsize[i]))) #define SEEK2(n,i) (ATRHEAD + ((n-1)*secsize[i])) #define ATRHEAD 16 #define MAXDISKS 8 /* * Default Timings from SIO2PC: * Time before first ACK: 85us * Time before second ACK: 1020us * Time before COMPLETE: 255us * Time after COMPLETE: 425us */ #define ACK1 85 #define ACK2 1020 #define COMPLETE1 255 #define COMPLETE2 425 /* * Global variables */ static int secsize[MAXDISKS]; static int seccount[MAXDISKS]; static enum seekcodes seekcode[MAXDISKS]; static int diskfd[MAXDISKS]; static int ro[MAXDISKS]; static int atari; /* fd of the serial port hooked up to the SIO2PC cable */ /* * usleep() * * This should block for the specified number of microseconds. * However, under Linux without RT extensions, this will likely * give up the timeslice, resulting in a 10ms delay. Hence, * I've rewritten the function to busy-wait for the required * time, based on the Pentium cycle-counter. * This will only work on true Intel(tm) Pentium or better x86 chips. * * For now, the processor speed is hard coded, but it could easily * be dynamically-determined by counting the cycles elapsed during * a sleep(1) call and dividing by a million. */ #define cyclecount(llptr) ({ \ __asm__ __volatile__ ( \ "\t.byte 0x0f; .byte 0x31 # RDTSC instruction\n" \ "\tmovl %%edx,%0 # High order 32 bits\n" \ "\tmovl %%eax,%1 # Low order 32 bits\n" \ : "=g" (*(((unsigned *)llptr)+1)), "=g" (*(llptr)) \ : /* No inputs */ \ : "eax", "edx");}) #define usleep(a) myusleep(a) static long long hz; void mysleepinit(void) { long long start; cyclecount(&start); sleep(1); cyclecount(&hz); hz -= start; } void myusleep(int us) { long long now; long long cycles; cyclecount(&now); cycles=now+hz*us/1000000; do { cyclecount(&now); } while (now>8); } write( atari, &sum, 1 ); if (c!=1) { if (errno) perror("write"); fprintf(stderr,"write failed\n"); exit(1); } printf("-%d bytes+sum-",size); } static void recvdata(int disk,int sec) { int i, sum = 0; unsigned char mybuf[ 2048 ]; int size; size=secsize[disk]; if (sec<=3) size=128; for( i=0; i> 8); } read(atari,&i,1); if ((i & 0xff) != (sum & 0xff)) printf( "[BAD SUM]" ); else { lseek(diskfd[disk],SEEK(sec,disk),SEEK_SET); i=write(diskfd[disk],mybuf,size); if (i!=size) printf("[write failed: %d]",i); } printf("-%d bytes+sum recvd-",size); } /* * get_atari() * * Open the serial device and return the file descriptor. * It assumes that it is /dev/ttyS0 unless there's a symlink * from /dev/mouse to that, in which case /dev/ttyS1 is used. */ static int get_atari(void) { int fd; char portname[64]="/dev/ttyS0"; struct stat stat_mouse,stat_tty; if (stat("/dev/mouse",&stat_mouse)==0) { stat(portname,&stat_tty); if (stat_mouse.st_rdev==stat_tty.st_rdev) { char *c; printf("/dev/ttyS0 is the mouse, using ttyS1\n"); c=index(portname,'0'); *c='1'; } } fd = open(portname,O_RDWR); if (fd<0) { fprintf(stderr,"Can't open %s\n",portname); exit(1); } raw(fd); /* Set up port parameters */ return(fd); } /* * getcmd() * * Read one 5-byte command * * The Atari will activate the command line while sending * the 5-byte command, which is detected as the ring indicator * by the iscmd() function. * What we do is read on byte, and if the command line is active * immediately after reading the byte, we assume that that byte * was the first of a 5-byte command. Otherwise, we assume that * that was a data byte going to or from another device. * * The second version of this function reads bytes until it gets * a block of 5 that have a correct checksum, and assume that that * represents a command regardless of the setting of the command * line. */ void getcmd1(unsigned char *buf) { int data=0; int i,r; int sum; again: do { if (data) { printf("%02x ",*buf); } ++data; r=read(atari,buf,1); if (r!=1) { printf(" -> read returned %d\n",r); } } while (!iscmd()); if (data>1) printf("-> %d data bytes\n",data-1); for(i=1;i<5;) { r=read(atari,buf+i,5-i); if (r>0) i+=r; else { printf("read returned %d\n",r); } } sum=0; for(i=0;i<4;++i) { sum+=buf[i]; sum = (sum&0xff) + (sum>>8); } if (buf[4]!=sum) { printf( "checksum mismatch [%02x %02x %02x %02x %02x (%02x)]\n",buf[0],buf[1],buf[2],buf[3],buf[4],sum); goto again; } } void getcmd(unsigned char *buf) { int data=0; int i,r; unsigned char bigbuf[1024]; i=0; while (1) { if (data) printf("-> %d data bytes\n",data),data=0; /* * Make sure we have at least 5 bytes */ while (i<5) { r=read(atari,bigbuf+i,sizeof(bigbuf)-i); if (r>0) i+=r; else { perror("read from serial port failed"); fprintf(stderr,"read returned %d\n",r); exit(1); } } /* * Copy them to the command buffer */ buf[0]=bigbuf[i-5]; buf[1]=bigbuf[i-4]; buf[2]=bigbuf[i-3]; buf[3]=bigbuf[i-2]; buf[4]=bigbuf[i-1]; for(r=0;r>8); } if (buf[4]==sum) { if (data) printf("-> %d data bytes\n",data); return; } } /* * Get ready to read some more */ printf("%02x ",buf[0]); ++data; bigbuf[0]=buf[1]; bigbuf[1]=buf[2]; bigbuf[2]=buf[3]; bigbuf[3]=buf[4]; i=4; } } /* * loaddisk() * * Ready a disk image. * The type of file (xfd/atr) is determined by the file size. */ static void loaddisk(char *path,int disk) { if (disk>=MAXDISKS) { fprintf(stderr,"Attempt to load invalid disk number %d\n",disk+1); exit(1); } diskfd[disk]=open(path,ro[disk]?O_RDONLY:O_RDWR); if (diskfd[disk]<0 && !ro[disk]) { ro[disk]=1; diskfd[disk]=open(path,ro[disk]?O_RDONLY:O_RDWR); } if (diskfd[disk]<0) { fprintf(stderr,"Unable to open disk image %s\n",path); exit(1); } /* * Determine the file type based on the size */ secsize[disk]=128; { struct stat buf; fstat(diskfd[disk],&buf); seekcode[disk]=atrdd3; if (((buf.st_size-ATRHEAD)%256)==128) seekcode[disk]=atr; if (((buf.st_size)%128)==0) seekcode[disk]=xfd; seccount[disk]=buf.st_size/secsize[disk]; } /* * Read disk geometry */ if (seekcode[disk]!=xfd) { struct atr_head atr; long paragraphs; read(diskfd[disk],&atr,sizeof(atr)); secsize[disk]=atr.secsizelo+256*atr.secsizehi; paragraphs=atr.seccountlo+atr.seccounthi*256+ atr.hiseccountlo*256*256+atr.hiseccounthi*256*256*256; if (secsize[disk]==128) { seccount[disk]=paragraphs/8; } else { paragraphs+=(3*128/16); seccount[disk]=paragraphs/16; } } printf( "Disk image %s opened%s (%d %d-byte sectors)\n",path,ro[disk]?" read-only":"",seccount[disk],secsize[disk]); } /* * decode() * * Given a command frame (5-bytes), decode it and * do whatever needs to be done. */ static void decode(unsigned char *buf) { int disk = -1, rs = -1, printer = -1; int sec; printf( "%02x %02x %02x %02x %02x ",buf[0],buf[1],buf[2],buf[3],buf[4]); switch( buf[0] ) { case 0x31: printf( "D1: " ); disk = 0; break; case 0x32: printf( "D2: " ); disk = 1; break; case 0x33: printf( "D3: " ); disk = 2; break; case 0x34: printf( "D4: " ); disk = 3; break; case 0x35: printf( "D5: " ); disk = 4; break; case 0x36: printf( "D6: " ); disk = 5; break; case 0x37: printf( "D7: " ); disk = 6; break; case 0x38: printf( "D8: " ); disk = 7; break; case 0x40: printf( "P: " ); printer = 0; break; case 0x41: printf( "P1: " ); printer = 0; break; case 0x42: printf( "P2: " ); printer = 1; break; case 0x43: printf( "P3: " ); printer = 2; break; case 0x44: printf( "P4: " ); printer = 3; break; case 0x45: printf( "P5: " ); printer = 4; break; case 0x46: printf( "P6: " ); printer = 5; break; case 0x47: printf( "P7: " ); printer = 6; break; case 0x48: printf( "P8: " ); printer = 7; break; case 0x50: printf( "R1: " ); rs = 0; break; case 0x51: printf( "R2: " ); rs = 1; break; case 0x52: printf( "R3: " ); rs = 2; break; case 0x53: printf( "R4: " ); rs = 3; break; default: printf( "???: ignored\n");return; } if (disk>=0&&diskfd[disk]<0) { printf( "[no image for this drive]\n" ); return; } if (printer>=0) {printf("[Printers not supported]\n"); return; } if (rs>=0) {printf("[Serial ports not supported]\n"); return; } sec = buf[2] + 256*buf[3]; switch( buf[1] ) { case 0x52: printf("read sector %d: ",sec); usleep(ACK1); ack('A'); usleep(COMPLETE1); ack('C'); usleep(COMPLETE2); senddata(disk,sec); break; case 0x57: printf("write sector %d: ",sec); usleep(ACK1); if (ro[disk]) { ack('N'); printf("[Read-only image]"); break; } ack('A'); recvdata(disk,sec); ack('A'); ack('C'); break; case 0x53: printf( "status:" ); usleep(ACK1); ack(0x41); { /* * Bob Woolley wrote on comp.sys.atari.8bit: * * at your end of the process, the bytes are * CMD status, H/W status, Timeout and unused. * CMD is the $2EA value previously * memtioned. Bit 7 indicates an ED disk. Bits * 6 and 5 ($6x) indicate DD. Bit 3 indicates * write protected. Bits 0-2 indicate different * error conditions. H/W is the FDD controller * chip status. Timeout is the device timeout * value for CIO to use if it wants. * * So, I expect you want to send a $60 as the * first byte if you want the OS to think you * are in DD. OK? */ static char status[] = { 0x10, 0x00, 1, 0 }; status[0]=(secsize[disk]==128?0x10:0x60); if (secsize[disk]==128 && seccount[disk]>720) status[0]=0x80; if (ro[disk]) { status[0] |= 8; } else { status[0] &= ~8; } usleep(COMPLETE1); ack(0x43); usleep(COMPLETE2); sendrawdata(status,sizeof(status)); } break; case 0x50: printf("put sector %d: ",sec); usleep(ACK1); if (ro[disk]) { ack('N'); printf("[Read-only image]"); break; } ack('A'); recvdata(disk, sec); ack('A'); ack('C'); break; case 0x21: printf( "format " ); break; case 0x20: printf( "download " ); break; case 0x54: printf( "readaddr " ); break; case 0x51: printf( "readspin " ); break; case 0x55: printf( "motoron " ); break; case 0x56: printf( "verify " ); break; default: printf( "??? " ); break; } printf( "\n" ); } /* * iscmd() * * returns true if the SIO command line is set (i.e., the modem ring indicator) */ int iscmd(void) { int r; ioctl(atari,TIOCMGET,&r); if (0) { printf("modem status: "); if (r&TIOCM_LE) printf(" LE"); if (r&TIOCM_DTR) printf(" DTR"); if (r&TIOCM_RTS) printf(" RTS"); if (r&TIOCM_ST) printf(" ST"); if (r&TIOCM_SR) printf(" SR"); if (r&TIOCM_CTS) printf(" CTS"); if (r&TIOCM_CAR) printf(" CAR"); if (r&TIOCM_RNG) printf(" RNG"); if (r&TIOCM_DSR) printf(" DSR"); printf("\n"); } return(r&TIOCM_RNG); }