/* ----------------------------------------------------------------------- *
 *   
 *   Copyright 2003 Murali Krishnan Ganapathy
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
 *   Bostom MA 02111-1307, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

/*
 * COM32 image
 *
 * Reads a "stack" from track 0 of the hard disk, pops one off
 * and executes the specified kernel
 *
 * This code is not exactly the best example of writing good
 * code, with loots of global variables. But this gets the job
 * done.
 */

#include <com32.h>
#include <autoboot.h>
#define NULL ((void *) 0)
#undef DEBUGMODE
/*
 * STACK: consists of a 16 bit offset, followed by a GAP, followed by a BLOCK,
 *        followed by a GAP, followed by a BLOCK,....
 *
 *        A GAP is  GAPSIZE zero bytes (to terminate previous string)
 *        A BLOCK is a 16 bit offset followed by a string.
 *        The 16 bit offset is the offset at which the next BLOCK can be 
 *        found or zero, if this is the last block.
 *
 * Initially: the first 16 bit offset, stores the offset of the first block.
 *
 *       Every time, we need to pop an element off the "stack", we read the 
 *       string located at the BLOCK pointed to by the first 16 bit offset, 
 *       and replace the first 16 bit offset with the one we found in the 
 *       BLOCK.
 *
 */

unsigned char *kernel; /* Pointer to the next kernel */
unsigned char *msg; /* Pointer to message */
unsigned char buffer[BUFSIZE]; 

#ifdef DEBUGMODE
// Debugging info
unsigned char debug[] = "Debugging Info: Stack Offset=";
// Press a key to continue
unsigned char press[] = "Press any key to continue...\r\n";
#endif

// Header to print
unsigned char header[] = "Auto selecting ..."; 
// String to print before name of kernel
unsigned char def_msg[] = "default kernel ("; 
//Error string.
unsigned char error[] = "You should never see this.\r\n"; 
// Bad disk read
unsigned char badread[] = "Disk read unsuccessful. Error code =";
// Bad disk write
unsigned char badwrite[] = "Disk write unsuccessful. Error code =";
// Bad disk reset
unsigned char badreset[] = "Disk reset unsuccessful. Error code =";
// End of line
unsigned char eoln[] = "\r\n";

// Register sets for Interrupt calls
com32sys_t inreg, outreg;
// Temporary vars
unsigned int ctr,len;

static inline void memset(void *buf, int ch, unsigned int len)
{
  asm volatile("cld; rep; stosb"
	       : "+D" (buf), "+c" (len) : "a" (ch) : "memory");
}

static inline void memcpy(void *dst, const void *src, unsigned int len)
{
  asm volatile("cld; rep; movsb"
	       : "+D" (dst), "+S" (src), "+c" (len) : : "memory");
}

int quit(void)
{
  // Quit back to prompt
  __com32.cs_syscall(0x20,&inreg,NULL);
}

unsigned char getchar()
{
  memset(&inreg,0,sizeof inreg);
  inreg.eax.b[1]=0x08; /* AH=0x08, Get char no echo */
  __com32.cs_syscall(0x21,&inreg,&outreg);
  return outreg.eax.b[0];
}

void printchar(unsigned char c)
{
  memset(&inreg,0,sizeof inreg);
  inreg.eax.b[1]=0x02; /* AH=0x02 implies Write Char */
  inreg.edx.b[0]=c;
  __com32.cs_syscall(0x21,&inreg,NULL);
}

void printnum(unsigned int num)
{
  // Print the number, equivalent of printf("%5d")
  unsigned int tmp;
  unsigned char a,b,c,d,e;
  tmp = num;
  // Extract digits of num
  e = tmp % 10;
  tmp = tmp / 10;
  d = tmp % 10;
  tmp = tmp / 10;
  c = tmp % 10;
  tmp = tmp / 10;
  b = tmp % 10;
  a = tmp / 10;
  // Now the digits are a,b,c,d,e in that order
  printchar(a+'0');
  printchar(b+'0');
  printchar(c+'0');
  printchar(d+'0');
  printchar(e+'0');
}

void print(unsigned char* str,unsigned int len)
     //Print Null-terminated string of length <= len
{
  memset(&inreg, 0, sizeof inreg);
  memcpy(__com32.cs_bounce,str,len);
  *((unsigned char *)(__com32.cs_bounce+len+1))=0; //Just in case
  inreg.eax.w[0] = 0x0002;	/* Write string */
  inreg.ebx.w[0] = OFFS(__com32.cs_bounce);
  inreg.es       = SEG(__com32.cs_bounce);
  __com32.cs_syscall(0x22, &inreg, NULL);
}

#ifdef DEBUGMODE
void printbuf()
{
  // prints the first 50 bytes of buffer 
  unsigned char ans;
  ans = 0;
  for (ctr=0; ctr < 50; ctr++)
    {
      printnum(buffer[ctr]);
      if (ctr % 8 == 7) 
	print(eoln,sizeof eoln);
      else
	printchar(',');
    }
}
#endif

unsigned int checkstatus(unsigned char *msg, unsigned int len)
{
  // Check return status of just completed operation
  if (outreg.eax.b[1] > 0) {
    print(msg,len);
    printnum(outreg.eax.b[1]);
    print(eoln,sizeof eoln);
    return 1;
  }
  else return 0;
}

void diskreset(void)
{
  /* AH = 00h
     DL = drive (if bit 7 is set both hard disks and floppy disks reset)
  */
  memset(&inreg,0,sizeof inreg);
  inreg.eax.b[1] = 0x00; // RESET  DISK
  inreg.edx.b[0] = 0x80; // First HARD DISK
  __com32.cs_syscall(0x13,&inreg,&outreg);
  checkstatus(badreset,sizeof badreset);
}

void readbuf(void)
     // Read the "stack" into the buffer
{
  /* AH = 02h
     AL = number of sectors to read (must be nonzero)
     CH = low eight bits of cylinder number
     CL = sector number 1-63 (bits 0-5)
     high two bits of cylinder (bits 6-7, hard disk only)
     DH = head number
     DL = drive number (bit 7 set for hard disk)
     ES:BX -> data buffer
  */
  diskreset(); //Probably not needed
  memset(&inreg, 0, sizeof inreg);
  inreg.eax.b[0] = NUM_SECT;
  inreg.eax.b[1] = 0x02;
  inreg.ecx.b[0] = SECT_START;
  inreg.ecx.b[1] = 0x00;
  inreg.edx.b[0] = 0x80;
  inreg.edx.b[1] = 0x00;
  inreg.ebx.w[0] = OFFS(__com32.cs_bounce);
  inreg.es       = SEG(__com32.cs_bounce);
  /* ES:BX = cs_bounce */
  __com32.cs_syscall(0x13, &inreg, &outreg);
  
  /* If error reading then quit */
  if (checkstatus(badread,sizeof badread)) quit();

  /* Read data out from the low memory buffer*/
  memcpy(buffer,__com32.cs_bounce,BUFSIZE);
}

void writebuf(void)
     // Write the contents of buffer to the hard disk
{
  /* Write sectors to hard disk
     AH = 03h
     AL = number of sectors to write (must be nonzero)
     CH = low eight bits of cylinder number
     CL = sector number 1-63 (bits 0-5)
     high two bits of cylinder (bits 6-7, hard disk only)
     DH = head number
     DL = drive number (bit 7 set for hard disk)
     ES:BX -> data buffer
  */
  diskreset(); //Probably not needed
  memset(&inreg, 0, sizeof inreg);
  inreg.eax.b[0] = NUM_SECT;	
  inreg.eax.b[1] = 0x03;	
  inreg.ecx.b[0] = SECT_START;
  inreg.ecx.b[1] = 0x00;
  inreg.edx.b[0] = 0x80;
  inreg.edx.b[1] = 0x00;
  memcpy(__com32.cs_bounce,buffer,BUFSIZE); 
  /* Copy data into low memory buffer */
  inreg.ebx.w[0] = OFFS(__com32.cs_bounce);
  inreg.es       = SEG(__com32.cs_bounce);
  __com32.cs_syscall(0x13, &inreg, &outreg);

  /* Check for errors */
  checkstatus(badwrite,sizeof badwrite);
}

void nextkernel(void)
{
  unsigned int ofs;
  kernel = NULL; 
  ofs = (buffer[0]<<8)+(buffer[1]); // Offset to read from
#ifdef DEBUGMODE
  print(debug,sizeof debug);
  printnum(ofs);
  print(eoln,sizeof eoln);
#endif
  if ((0 < ofs) && (ofs < BUFSIZE)) {
    kernel = buffer+ofs+2; // kernel points to the right place
    // Set the next pointer - Can be done using memcpy also...
    buffer[0]=buffer[ofs];
    buffer[1]=buffer[ofs+1];
  }
}

void execkernel(void)
     //Execute specified kernel or default_kernel if kernel=NULL
{
  if ((kernel == NULL) || (kernel[0] == 0x00)) {
    // My command line is the default kernel
    kernel = (unsigned char *) __com32.cs_cmdline;
    print(def_msg,sizeof def_msg);
    print(kernel,BUFSIZE);
    printchar(')');
    print(eoln,sizeof eoln);
  }
  else {
    print(kernel,BUFSIZE);
    print(eoln,sizeof eoln);
  }
#ifdef DEBUGMODE
  print(press,sizeof press);
  getchar();
#endif
  // Ask ISOLINUX to execute the kernel
  memset(&inreg, 0, sizeof inreg);
  memcpy(__com32.cs_bounce,kernel,BUFSIZE);
  inreg.eax.w[0] = 0x0003;	/* Run Command*/
  inreg.ebx.w[0] = OFFS(__com32.cs_bounce);
  inreg.es       = SEG(__com32.cs_bounce);
  __com32.cs_syscall(0x22, &inreg, NULL);
  // What follows should never get executed.
  print(error,sizeof error);
  quit();
}

int __start(void)
{
  print(header,sizeof header);
  readbuf(); // Read data into buffer
#ifdef DEBUGMODE
  printbuf(); // Print first few bytes of buffer
#endif
  nextkernel(); // Compute next kernel
  writebuf(); // Write data back
#ifdef DEBUGMODE
  printbuf(); // Write changed data
#endif
  execkernel(); // Execute the kernel
  return 0;
}
