#ifdef __linux__
#include <signal.h>
#include <unistd.h>
#include <asm/io.h>
#endif /* __linux__ */
#include "prtrans.h"

unsigned baseaddr, stataddr;

#ifdef __linux__
static void cleanup (int i);

static void cleanup (int i) {
  outb(0, baseaddr); /* this ensures that the server doesn't remain blocked */
  exit(-1);
}
#endif /* __linux__ */

int prinit (void) {
#ifdef __linux__
  if (ioperm (baseaddr, 2, 1))
    return -1;

  signal(SIGTERM, cleanup);
#endif /* __linux__ */
  outb(0, baseaddr);
  stataddr = baseaddr + 1;

  return 0;
}

void prclose (void) {
  outb(0, baseaddr);
}

void output (unsigned char byte) {
  send (&byte, 1);
}

unsigned input (void) {
  unsigned char byte;

  receive (&byte, 1);
  return byte;
}

unsigned wait_input (void) {
  register unsigned char data;

  outb(0xf0, baseaddr);
  while(0x20 & inb(stataddr))
    usleep (SLEEP_TIME);

  outb(0, baseaddr);

  while(!(0x20 & inb(stataddr)));
  data = (0xc0 & inb(stataddr)) >> 6;
  outb(8, baseaddr);

  while(0x20 & inb(stataddr));
  data |= (0xc0 & inb(stataddr)) >> 4;
  outb(0, baseaddr);

  while(!(0x20 & inb(stataddr)));
  data |= (0xc0 & inb(stataddr)) >> 2;
  outb(8, baseaddr);

  while(0x20 & inb(stataddr));
  data |= 0xc0 & inb(stataddr);
  outb(0, baseaddr);

  while(!(0x20 & inb(stataddr)));

  return data ^ 0xaa;
}

#if !defined(__i386__) && !defined(__GNU_C__)
void send (unsigned char *buffer, unsigned length) {
  while (length--) {
    outb(*buffer | 8, baseaddr);
    while(0x20 & inb(stataddr));
    outb(*buffer++ << 4, baseaddr);
    while(!(0x20 & inb(stataddr)));
  }
}
#else /* __i386__ && __GNU_C__ */
void send (unsigned char *buffer, unsigned length) {
  asm ("push %%ebp
	movw _baseaddr,%%dx
	movl %1,%%ecx
	movl %0,%%ebp

sloop:  movb (%%ebp),%%al
	orb $8,%%al
        outb %%al,%%dx

        incw %%dx
wait1:
        inb %%dx,%%al
        testb $32,%%al
        jne wait1

        movb %0,%%al
        salb $4,%%al
        decw %%dx
        outb %%al,%%dx

        incw %%dx
wait2:
        inb %%dx,%%al
        testb $32,%%al
        je wait2
	decw %%dx

	inc %%ebp
	loop sloop
	pop %%ebp"
       : /* no outputs */
       : "g" (buffer), "g" (length)
       : "al", "ecx", "dx");
}
#endif /* __i386__ && __GNU_C__ */

#if !defined(__i386__) && !defined(__GNU_C__)
void receive (unsigned char *buffer, unsigned length) {
  register unsigned char data;

  while (length--) {
    outb(0xf0, baseaddr);
    while(0x20 & inb(stataddr));
    outb(0, baseaddr);

    while(!(0x20 & inb(stataddr)));
    data = (0xc0 & inb(stataddr)) >> 6;
    outb(8, baseaddr);

    while(0x20 & inb(stataddr));
    data |= (0xc0 & inb(stataddr)) >> 4;
    outb(0, baseaddr);

    while(!(0x20 & inb(stataddr)));
    data |= (0xc0 & inb(stataddr)) >> 2;
    outb(8, baseaddr);

    while(0x20 & inb(stataddr));
    data |= 0xc0 & inb(stataddr);
    outb(0, baseaddr);

    *buffer++ = data ^ 0xaa;

    while(!(0x20 & inb(stataddr)));
  }
}
#else /* __i386__ && __GNU_C__ */
void receive (unsigned char *buffer, unsigned length) {
  asm ("movw _baseaddr,%%dx
	push %%ebp
	movl %1,%%ecx
	movl %0,%%ebp

rloop:	movb $0xf0,%%al
	outb %%al,%%dx
	incw %%dx
input0:
	inb %%dx,%%al
	testb $32,%%al
	jne input0
	decw %%dx

	xorb %%al,%%al
	outb %%al,%%dx
	incw %%dx

input1:
	inb %%dx,%%al
	testb $32,%%al
	je input1
	decw %%dx
	andb $0xc0,%%al
	shrb $6,%%al
	mov %%al,(%%ebp)

	movb $8,%%al
	outb %%al,%%dx
	incw %%dx

input2:
	inb %%dx,%%al
	testb $32,%%al
	jne input2
	decw %%dx
	andb $0xc0,%%al
	shrb $4,%%al
	orb %%al,(%%ebp)

	xorb %%al,%%al
	outb %%al,%%dx
	incw %%dx

input3:
	inb %%dx,%%al
	testb $32,%%al
	je input3
	decw %%dx
	andb $0xc0,%%al
	shrb $2,%%al
	orb %%al,(%%ebp)

	movb $8,%%al
	outb %%al,%%dx
	incw %%dx

input4:
	inb %%dx,%%al
	testb $32,%%al
	jne input4
	decw %%dx
	andb $0xc0,%%al
	orb %%al,(%%ebp)
	xorb $0xaa,(%%ebp)

	xorb %%al,%%al
	outb %%al,%%dx
	incw %%dx

input5:
	inb %%dx,%%al
	testb $32,%%al
	je input5

	decw %%dx

	inc %%ebp
	loop rloop
	pop %%ebp"
       : /* no outputs */
       : "g" (buffer), "g" (length)
       : "ax", "cx", "dx");
}
#endif /* __i386__ && __GNU_C__ */
