/*  psiox.c
 *    v0.02
 *    by Frank "Shaggy" Barrus          feb6399@ultb.isc.rit.edu
 *    started:      July 29, 1994
 *    last update:  August 1, 1994
 *
 *  This program communicates with Atari SIO peripherals 
 *  (currently just the disk drives)
 *  that are connected through the PC parallel port
 *  it supports single, enhanced, and double density
 *
 *  It is designed to be run under Linux, but could
 *  probably be modified for other operating systems.
 *
 *  The following is the pinout for the cable:
 *  (thanks to Mike Munoz for the idea and the cable pinout,
 *   which has been copied directly from his source code...
 *   the rest of the code is all mine)
 *
 *   Cable specifications:
 *                       
 *   Pinouts:           
 *            IBM Parallel Port         Atari SIO     name
 *                                                            
 *                 1  ----------------->  7          command/  
 *                 14 <----------------   5          dataout 
 *                 16 ----------------->  10         vcc/rdy
 *                 17 ----------------->  3          datain
 *                 24 -----------------   4          gnd  
 *                 25 -----------------   6          gnd 
 *                                                      
 *                                                             
 *  Connector Specs:                                              
 *        IBM Parallel Port           DB25M    25 pin male d-shell 
 *   (view as if you were plugging the connector into your face)  
 *             1   2  3  4  5  6  7  8  9  10 11 12 13           
 *               14 15 16 17 18 19 20 21 22 23 24 25            
 *                                                             
 *                                                            
 *                                                           
 *        Atari SIO                   Molex    13 pin      
 *   (view as if you were plugging the connector into your face)  
 *                                                         
 *               1  3  5  7  9  11  13                       
 *                 2  4  6  8  10  12                       
 *                                                         
 *                                                        
 *    Notes:                                             
 *      (1) Keep the wire length less than 3 feet.    
 *
 *  
 */


#include <stdio.h>


typedef unsigned char byte;
typedef unsigned short word;

#define True	1
#define False	0


#define TIMER_HZ 1193182

#define PAR1	0x37a


char errmsg[80];



#define SIO2PC_MAGIC	0x0296

struct sio2pc_image_hdr {
	byte	code_lo, code_hi;	/* ID code = 0x0296 */
	byte	size_b1, size_b2;	/* low 16-bits of size */
	byte	secsize_lo, secsize_hi;	/* sectors size: 128/256 */
	byte	size_b3, size_b4;	/* high 16-bits of size (paragraphs) */
	byte	resv[6];		/* reserved */
	byte	errnum_hi, errnum_lo;	/* number of sectors with errors */
};

struct errormap {
	byte	sec_lo, sec_hi;		/* sector with error */
	byte	cstat;			/* communication status */
	byte	hstat; 			/* hardware status */
	byte	resv[4];		/* reserved */
};


struct {
	int	nsec;			/* number of sectors */
	int	secsize;		/* size of sector */
	int	timeout;		/* timeout in milliseconds */
	byte	cstat;			/* communication status */
	byte	hstat;			/* hardware status */
} disk[8];


#define HI(x)			((x)>>8)
#define LO(x)			((x)&0x00ff)
#define WORD(l,h)		((l)|((h)<<8))
#define LONG(b1,b2,b3,b4)	(WORD(b1,b2)|(WORD(b3,b4)<<16))
#define B1(n)			((n)&0x00ff)
#define B2(n)			(((n)>>8)&0x00ff)
#define B3(n)			(((n)>>16)&0x00ff)
#define B4(n)			(((n)>>24)&0x00ff)

#define GETWORD(v)		WORD(v##_lo, v##_hi)
#define GETLONG(v)		LONG(v##_b1, v##_b2, v##_b3, v##_b4)
#define PUTWORD(v,w)		{ v##_lo = LO(w); v##_hi = HI(w); }
#define PUTLONG(v,l)		{ v##_b1 = B1(l); v##_b2 = B2(l); \
				v##_b3 = B3(l); v##_b4 = B4(l); }



static inline byte in_portb(word p) {
volatile byte v;
	asm("inb %%dx,%%al":"a="(v):"d"(p));
	return (byte)v;
}

static inline byte out_portb(word p, byte v) {
	asm("outb %%al,%%dx"::"a"(v),"d"(p));
}


static inline disable_interrupts(void)
{
	if(iopl(3) < 0) {
		perror("You must be root to run this program\niopl");
		exit(1);
	}	
	asm("cli");
}

static inline enable_interrupts(void)
{
	asm("sti");
	iopl(0);
}


static inline void init_timer(void)
{
	out_portb(0x61, 1);
	out_portb(0x43, 0xb4);
}

static inline void latch_timer(void)
{
	out_portb(0x43, 0x80);
}


static inline void set_timer(word v)
{
	init_timer();
	out_portb(0x42, v&0xff);
	out_portb(0x42, v>>8);
}

static inline word read_timer(void)
{
word v;
	latch_timer();
	v = in_portb(0x42);
	v |= (in_portb(0x42) << 8);
	return v;
}


static inline void timer_sync(void)
{
word t,ot;
	t = read_timer();
	do {
		ot = t;
		t = read_timer();
	} while(t <= ot);
}


static inline int check_timeout(word *t, int *cycles)
{
word ot;
	ot = *t;
	*t = read_timer();
	if(*t > ot)
		if(--(*cycles) <= 0)
			return True;
	return False;
}


static inline void delay(unsigned long t)
{
	set_timer(0);
	for(; t > 0xffff; t -= 0x10000)
		timer_sync();
	set_timer(t);
	timer_sync();
}

static inline void delay_msec(int msec)
{
	delay(TIMER_HZ*msec/1000);
}

static inline void delay_usec(int usec)
{
	delay(TIMER_HZ*usec/1000000);
}


static inline void set_timer_freq(int freq)
{
	set_timer(TIMER_HZ/freq);
}




int bps = 19320;	/* 18700 - 19800 */
byte pbyte;


char *atari_to_vt(byte b)
{
static char buf[20];
char ch;
	ch = b&0x7f;
	if(ch < 32 || ch > 126)
		ch = '~';
	if(b > 128)
		sprintf(buf, "\033[7m%c\033[0m", ch);
	else
		sprintf(buf, "%c", ch);
	return buf; 
}


void dump_sector(byte *buf, int secsize)
{
int i, n;
	for(i=0; i<secsize; i++) {
		if(!(i&15))
			printf("%02X: ", i);
		printf("%02X ", buf[i]);
		if((i&15) == 15) {
			printf("   ");
			for(n=-15; n<1; n++)
				printf("%s", atari_to_vt(buf[i+n]));
			printf("\n");
		}
	}
}


void sio_send(byte *buf, int len)
{
int i, b;
	set_timer_freq(bps);
	while(len-- > 0) {
		b = (((*buf++)^0xff)<<2)|0x02;
		for(i=0; i<10; i++) {
			timer_sync();
			out_portb(PAR1, (b&0x02)|pbyte);
			b >>= 1;
		}
	}	
}


int sio_recv(byte *buf, int len, int timeout)
{
byte b;
int i, n;
word t;
byte data[1000];
	n = 0;
	while(len-- > 0) {
		t = 65535;
		set_timer_freq(1000);
		while(!(in_portb(PAR1)&8)) 
			if(check_timeout(&t, &timeout)) 
				return n;
		set_timer_freq(bps*4/3);   /* get to center of next bit */
		timer_sync();
		set_timer_freq(bps);
		b = 0;
		for(i=0; i<8; i++) {
			if(!(in_portb(PAR1)&8))
				b |= (1<<i);
			timer_sync();
		}
		timeout = 20*1000/bps;	/* timeout after 20 bit times */
		if(timeout < 1)
			timeout = 1;
		*buf++ = b;
		n++;
	}
	return n; 
}


void sio_set_command(int onoff)
{
	pbyte = 0x04|(onoff != 0);
	out_portb(PAR1, pbyte);
}


byte compute_checksum(byte *data, int len)
{
int sum;
	sum = 0;
	while(len-- > 0) 
		sum += *data++;
	while(sum > 0xff)
		sum = (sum&0xff)+(sum>>8);
	return sum;
}


int cmd_error(byte *frame, char *str)
{
char buf[80];	
	strcpy(buf, "%02X: cmd %02X %02X %02X %02X: ");
	strcat(buf, str);
	sprintf(errmsg, buf, frame[0], frame[1], frame[2], frame[3],
			frame[4], frame[5]);
	return False;
}

void purge_input(void)
{
byte temp[512];
int n;
	while((n=sio_recv(temp, sizeof(temp), 40*1000/bps))) {
		printf("purged %d bytes\n", n);
		disable_interrupts();
	}
}

int send_command(int dev, byte cmd, byte aux1, byte aux2)
{
byte frame[6];
	sio_set_command(0);
	purge_input();
	frame[0] = dev;
	frame[1] = cmd;
	frame[2] = aux1;
	frame[3] = aux2;
	frame[4] = compute_checksum(frame, 4);
	sio_set_command(1);
	delay_usec(1000);
	sio_send(frame, 5);
	delay_usec(800);
	sio_set_command(0);
	if(sio_recv(&frame[5], 1, 2000) < 1) 
		return cmd_error(frame, "no response");
	if(frame[5] == 'N') 
		return cmd_error(frame, "NAK");
	if(frame[5] != 'A') 
		return cmd_error(frame, "invalid response: %02X");
	return True;
}


int frame_error(char *str, int a, int b)
{
	sprintf(errmsg, str, a, b);
	return False;
}

int sio_frame_recv(byte *data, int size, int timeout)
{
int n, i;
byte buf[512];
byte ck;
	if((n = sio_recv(buf, sizeof(buf), timeout)) < 1)
		return frame_error("timeout", 0, 0);
	for(i=0; i<size; i++)
		data[i] = buf[i+1];
	if(n < size+2)
		return frame_error("short frame (%d)", n, 0);
	if(n > size+2)
		return frame_error("long frame (%d)", n, 0); 
	switch(buf[0]) {
	case 'C': break;
	case 'E': return frame_error("error", 0, 0);
	default: return frame_error("unknown response: %02X", buf[0], 0);
	}
	ck = compute_checksum(&buf[1], size);
	if(ck != buf[n-1]) 
		return frame_error("bad checksum (%02X != %02X)",
					ck, buf[n-1]);
	return True;
}


int read_sector(int drive, int sec, byte *buf)
{
byte ck;
int i, n;
int secsize;
	disable_interrupts();
	if((secsize = disk[drive].secsize) == 256 && sec < 4)
		secsize = 128;
	if(!send_command(drive+'0', 'R', sec&0xff, sec>>8)
	  || !sio_frame_recv(buf, secsize, disk[drive].timeout)) {
		enable_interrupts();
		return 0;
	}
	enable_interrupts();
	return secsize;
}


int get_drive_status(int drive)
{
byte buf[4];
	disable_interrupts();
	if(!send_command(drive+'0', 'S', 0, 0)
	   || !sio_frame_recv(buf, 4, disk[drive].timeout)) {
		enable_interrupts();
		return False;
	}
	disk[drive].cstat = buf[0];
	disk[drive].hstat = buf[1];
	disk[drive].timeout = buf[2]*1000;
	enable_interrupts();
	return True;
}


void init_drive(int drive)
{
byte buf[256];
int n;
	disk[drive].timeout = 160*1000;		/* 160 seconds */
	disk[drive].secsize = 256;
	disk[drive].nsec = 720;
	printf("D%d: ", drive);
	if(!get_drive_status(drive)) {
		printf("init failed: %s\n", errmsg);
		exit(0);
	}
	printf("status=%02X/%02X timeout=%d ", disk[drive].cstat,
			disk[drive].hstat, disk[drive].timeout/1000); 
	if(read_sector(drive, 4, buf)) 
		printf("double density\n", drive);
	else {
		disk[drive].secsize = 128;
		if(read_sector(drive, 721, buf)) {
			disk[drive].nsec = 1024;
			printf("enhanced density\n", drive);
		} else 
			printf("single density\n", drive);
	}
}


void interactive(int drive)
{
byte buf[256];
int sec;
int secsize;
	init_drive(drive);
	while(1) {
		printf("enter sector: ");
		scanf("%d", &sec);
		if(!(secsize = read_sector(drive, sec, buf)))
			printf("D%d: sector %5d: %s\n",
				drive, sec, errmsg);
		else 
			dump_sector(buf, secsize);
	}
}





void copy_disk_to_file(int drive, char *fname)
{
FILE *f;
byte buf[256];
int i, retry;
struct sio2pc_image_hdr hdr;
struct errormap *emap;
int nerr;
int nretry = 3;
	init_drive(drive);
	if((f = fopen(fname, "r"))) {
		fclose(f);
		printf("%s: disk image already exists\n", fname);
		exit(1);
	}
	f = fopen(fname, "w+");
	memset(&hdr, 0, sizeof(hdr));
	PUTWORD(hdr.code, SIO2PC_MAGIC);
	PUTLONG(hdr.size, disk[drive].nsec*(disk[drive].secsize/16));
	PUTWORD(hdr.secsize, disk[drive].secsize);
	fwrite(&hdr, sizeof(hdr), 1, f);
	emap = (struct errormap*)calloc(disk[drive].nsec,
				sizeof(struct errormap));
	nerr = 0;
	for(i=1; i<=disk[drive].nsec; i++) {
		printf("%04d\b\b\b\b", i);
		fflush(stdout);
		retry = nretry;
		while(retry-- && !read_sector(drive, i, buf)) {
			if(retry == nretry-1)
				printf("D%d: sector %5d: %s",
					drive, i, errmsg);
			else
				printf(", %s", errmsg);
			if(retry) {
				printf(" -- retry");
				fflush(stdout);
				if(i > disk[drive].nsec/2)
					read_sector(drive, 1, buf);
				else
					read_sector(drive,
						disk[drive].nsec, buf);
				sleep(1);
			} else {
				get_drive_status(drive);
				printf(" -- failed (%02X/%02X)",
					disk[drive].cstat, disk[drive].hstat);
				PUTWORD(emap[nerr].sec, i);
				emap[nerr].cstat = disk[drive].cstat;
				emap[nerr].hstat = disk[drive].hstat;
				nerr++;
			}
		}
		if(retry < nretry-1)
			printf("\n");
		fwrite(buf,disk[drive].secsize,1,f);
	}
	fwrite(emap, sizeof(*emap), nerr, f);
	free(emap);
	rewind(f);
	PUTWORD(hdr.errnum, nerr+1);
	fwrite(&hdr, sizeof(hdr), 1, f);
	fclose(f);
}


void show_stats(char *fname)
{
FILE *f;
struct errormap emap;
struct sio2pc_image_hdr hdr;
int i, secsize, nsec;
int nerr;
	f = fopen(fname, "r");
	fread(&hdr, sizeof(hdr), 1, f); 
	if(GETWORD(hdr.code) != SIO2PC_MAGIC) {
		printf("not an [E]SIO2PC file\n");
		return;
	}
	secsize = GETWORD(hdr.secsize);
	nsec = GETLONG(hdr.size)/(secsize/16);
	nerr = GETWORD(hdr.errnum);
	if(!nerr) 
		printf("%s: SIO2PC; %d sectors; %d bytes per sector\n",
			fname, nsec, secsize);
	else {
		printf("%s: ESIO2PC; %d sectors; %d bytes per sector; %d bad sectors\n", 
			fname, nsec, secsize, nerr-1);
		fseek(f, sizeof(hdr)+nsec*secsize, SEEK_SET);
		for(i=0; i<nerr-1; i++) {
			fread(&emap, sizeof(emap), 1, f);
			printf("%d:%02X/%02X  ", GETWORD(emap.sec),
				emap.cstat, emap.hstat);
		}
		printf("\n");
	}
	fclose(f);
}


void fix_file(int drive, char *fname)
{
FILE *f;
byte buf[256];
int i, sec, retry;
struct sio2pc_image_hdr hdr;
struct errormap *emap, *oldmap;
int nerr, oldnerr;
int nretry = 6;
	init_drive(drive);
	f = fopen(fname, "r+");
	fread(&hdr, sizeof(hdr), 1, f);
	if(GETWORD(hdr.code) != SIO2PC_MAGIC) {
		printf("not an SIO2PC file\n");
		return;
	}
	if(GETLONG(hdr.size) != disk[drive].nsec*(disk[drive].secsize/16)
	   || GETWORD(hdr.secsize) != disk[drive].secsize) {
		printf("%s: SIO2PC header does not match drive parameters\n");
		return;
	}
	oldnerr = GETWORD(hdr.errnum)-1;
	if(oldnerr < 1) {
		printf("%s: no errors to fix\n");
		return;
	}
	oldmap = (struct errormap*)calloc(oldnerr,
				sizeof(struct errormap));
	emap = (struct errormap*)calloc(oldnerr,
				sizeof(struct errormap));
	fseek(f, disk[drive].nsec*disk[drive].secsize, SEEK_CUR);
	fread(oldmap, sizeof(*oldmap), oldnerr, f);
	nerr = 0;
	for(i=0; i<oldnerr; i++) {
		sec = GETWORD(oldmap[i].sec);
		printf("D%d: sector %5d: ", drive, sec);
		fflush(stdout);
		retry = nretry;
		while(retry-- && !read_sector(drive, sec, buf)) {
			printf("%s; ", errmsg);
			fflush(stdout);
			if(retry) {
				read_sector(drive, retry*disk[drive].nsec/6, buf);
				sleep(2);
			} else {
				get_drive_status(drive);
				printf("failed (%02X/%02X)",
					disk[drive].cstat, disk[drive].hstat);
				PUTWORD(emap[nerr].sec, sec);
				emap[nerr].cstat = disk[drive].cstat;
				emap[nerr].hstat = disk[drive].hstat;
				nerr++;
			}
		}
		if(retry < 0)
			printf("\n");
		else
			printf("ok\n");
		fseek(f, sizeof(hdr)+(sec-1)*disk[drive].secsize, SEEK_SET);
		fwrite(buf,disk[drive].secsize,1,f);
	}
	rewind(f);
	PUTWORD(hdr.errnum, nerr+1);
	fwrite(&hdr, sizeof(hdr), 1, f);
	fseek(f, disk[drive].nsec*disk[drive].secsize, SEEK_CUR);
	fwrite(emap, sizeof(*emap), nerr, f);
	free(emap);
	free(oldmap);
	fclose(f);
}




main(int argc, char **argv)
{
int i;
int drive = 1;
char *fname = NULL;
int opt_interactive = 0, opt_fix = 0, opt_stats = 0, opt_read = 0;
	printf("PSIOX: Parallel SIO Xfer program   v0.02 (August 1, 1994)\n");
	printf("Written by Frank Barrus.  (c) 1994 ShagWare.\n");
	printf("Interface Concept by Mike Munoz\n\n");
	for(i=1; i<argc; i++) {
		if(argv[i][0] == '-') {
			if(!strcmp(argv[i], "-i"))
				opt_interactive++;
			else if(!strcmp(argv[i], "-d"))
				drive = atoi(argv[++i]);
			else if(!strcmp(argv[i], "-b"))
				bps = atoi(argv[++i]);
			else if(!strcmp(argv[i], "-r"))
				opt_read++;
			else if(!strcmp(argv[i], "-f"))
				opt_fix++;
			else if(!strcmp(argv[i], "-s"))
				opt_stats++;
			else {
				fprintf(stderr, "invalid switch: %s\n",
							argv[i]);
				exit(1);
			}
		} else
			fname = argv[i];
	}
	if(opt_interactive) 
		interactive(drive);
	else if(opt_stats && fname)
		show_stats(fname);
	else if(opt_fix && fname)
		fix_file(drive, fname);
	else if(opt_read && fname)
		copy_disk_to_file(drive, fname);
	else {
		fprintf(stderr,
			"usage: %s [-b bps] [-d drive] -i          %s\n",
			argv[0], "interactive mode");
		fprintf(stderr,
			"       %s [-b bps] [-d drive] -r <file>   %s\n",
			argv[0], "read disk into ESIO2PC file");
		fprintf(stderr,
			"       %s [-b bps] [-d drive] -f <file>   %s\n",
			argv[0], "attempt to fix ESIO2PC file");
		fprintf(stderr,
			"       %s [-b bps] [-d drive] -s <file>   %s\n",
			argv[0], "show stats of ESIO2PC file");
		exit(1);
	}
}

