/*
   debug.c: a handy utility to view registers & memory for NetWinder
   Copyright 1998  woody@corelcomputer.com

   Changes for Linux 2.2 Pat Beirne
      mostly involve change the access from mem_read/write to mem_mmap
   
   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; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

#define	DEB_BYTE	1
#define	DEB_WORD        2
#define	DEB_DWORD       4

#define	CMD_DUMP	'd'
#define	CMD_EDIT	'e'
#define	CMD_INPUT	'i'
#define	CMD_OUTPUT	'o'
#define	CMD_SUPER_IN	"si"
#define	CMD_SUPER_OUT	"so"
#define	CMD_CPLD_READ	"cr"
#define	CMD_CPLD_WRITE	"cw"
#define	CMD_LED_READ	"lr"
#define	CMD_LED_WRITE	"lw"
#define	CMD_GPIO_READ	"gr"
#define	CMD_GPIO_WRITE	"gw"

#define VERSION		"1.3"

/* these globals handle the differences between Linux 2.0.x
 and 2.2.x */
static int kernel_version = 20;	/* or 22 */
#define IO_BASE_20 0xE0000000
#define IO_BASE_22 0x7c000000
static long io_base;
#define PAGE_SIZE 0x1000


static char *_pIO;

#define MAP_IO(h) _pIO = mmap(0,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_FILE|MAP_SHARED,h,io_base)
#define UNMAP_IO munmap(_pIO,PAGE_SIZE)
#define IS_OK_IO _pIO
#define outb(p, v)    *(_pIO+(p)) = (char)v
#define inb(p) *(_pIO+(p))

#define UNLOCK    outb(0x370, 0x87); outb(0x370, 0x87);
#define LOCK      outb(0x370, 0xAA);

extern int errno;

int tmpSuperIO;

#define TOASCII(a) (((a)>=' ' && (a)<0x7F) ? a : '.')
/*
 display as bytes, shorts or longs

 bdata   pointer to the data to read
 bcount  number of bytes
 buff    a text buffer to fill for display
 */
void
db (int f_mem, long bdata, int bcount, char *buff, int bytes_at_a_time)
{
  int bposition;
  int i;
  char asciibuf[17];
  char *pmmap, *p;
  int count;
  int offset;

  if (!bcount)
    return;

  /* shorts and longs must be aligned */
  bdata &= ~(bytes_at_a_time - 1);

  /* option to skip the first few reads in a line */
  bposition = bdata & 0x0F;

  /* map in the zone, rounding down to nearest page */
  offset = bdata & (PAGE_SIZE - 1);
  pmmap = mmap (0, bcount + offset, PROT_READ, MAP_FILE | MAP_SHARED,
		f_mem, bdata - offset);
  /* and create a pointer to the exact location within the mmap */
  p = pmmap + offset;

  for (count = bcount; count > 0;)
  {
    strcpy (asciibuf, "                ");

    buff += sprintf (buff, "%08X  ", bdata & 0xfffffff0);
    /* if the first few bytes need to be skipped .... */
    while (bposition)
    {
      buff += sprintf (buff, "   ");
      bposition--;
    }
    do
    {
      unsigned int a;
      switch (bytes_at_a_time)
      {
      case 1:
	a = *(unsigned char *) p;
	buff += sprintf (buff, "%02X%c", a,
			 (bdata & 15) == 7 ? '-' :
			 ((bdata & 7) == 3 ? '.' : ' '));
	asciibuf[bdata & 0xf] = TOASCII (a);
	bdata++;
	break;
      case 2:
	a = *(unsigned short *) p;
	buff += sprintf (buff, "%04X%c ", a,
			 (bdata & 15) == 6 ? '-' :
			 ((bdata & 7) == 2 ? '.' : ' '));
	asciibuf[bdata & 0xf] = TOASCII (a & 255);
	bdata++;
	asciibuf[bdata & 0xf] = TOASCII (a >> 8);
	bdata++;
	break;
      case 4:
	a = *(unsigned long *) p;
	buff += sprintf (buff, "%08X %c  ", a,
			 (bdata & 15) == 4 ? '-' :
			 ((bdata & 7) == 2 ? '.' : ' '));
	asciibuf[bdata & 0xf] = TOASCII (a & 255);
	bdata++;
	asciibuf[bdata & 0xf] = TOASCII ((a >> 8) & 255);
	bdata++;
	asciibuf[bdata & 0xf] = TOASCII ((a >> 16) & 255);
	bdata++;
	asciibuf[bdata & 0xf] = TOASCII (a >> 24);
	bdata++;
	break;
      default:
	return;
      }
      p += bytes_at_a_time;
      count -= bytes_at_a_time;
    }
    while (bdata & 0xF);
    buff += sprintf (buff, "  [%16s]\n", asciibuf);
  }
  munmap (pmmap, bcount + offset);
}

/* write to memory

returns true to continue
modifies the first parameter on return
*/
int
eb (long *paddr, int f_mem, int deb_mode, char *scan_buffer)
{
  char *pmmap, *p;
  long addr = *paddr;
  unsigned char btemp;
  unsigned long dtemp;
  int i;
  int ret = 1;

  /* mmap a page just below deb_add */
  pmmap = mmap (0, PAGE_SIZE, PROT_READ | PROT_WRITE,
		MAP_FILE | MAP_SHARED, f_mem, (long) addr & ~(PAGE_SIZE - 1));
  if (pmmap == 0)
  {
    printf ("Memory access error!\n");
    return 0;
  }

  switch (deb_mode)
  {
  case DEB_BYTE:
    btemp = *(char *) (pmmap + ((long) addr & (PAGE_SIZE - 1)));

    printf ("Byte %p: %02X -> ", addr, btemp & 0xFF);

    fgets (scan_buffer, 127, stdin);
    i = sscanf (scan_buffer, "%02X", &dtemp);
    if (i == -1)
    {
      ret = 0;
      break;
    }
    *(char *) (pmmap + ((long) addr & (PAGE_SIZE - 1))) = dtemp;
    addr++;
    break;
  case DEB_WORD:
    dtemp = *(unsigned short *) (pmmap + ((long) addr & (PAGE_SIZE - 1)));

    printf ("Dword %p: %04X -> ", addr, dtemp);
    fgets (scan_buffer, 127, stdin);
    i = sscanf (scan_buffer, "%04X", &dtemp);
    if (i == -1)
    {
      ret = 0;
      break;
    }
    *(unsigned short *) (pmmap + ((long) addr & (PAGE_SIZE - 1))) = dtemp;
    addr += 2;
    break;
  case DEB_DWORD:
    dtemp = *(unsigned long *) (pmmap + ((long) addr & (PAGE_SIZE - 1)));

    printf ("Dword %p: %08X -> ", addr, dtemp);
    fgets (scan_buffer, 127, stdin);
    i = sscanf (scan_buffer, "%08X", &dtemp);
    if (i == -1)
    {
      ret = 0;
      break;
    }

    *(unsigned long *) (pmmap + ((long) addr & (PAGE_SIZE - 1))) = dtemp;
    addr += 4;
    break;
  }				// switch
  munmap (pmmap, PAGE_SIZE);
  /* pass the new address back to the caller */
  *paddr = addr;
  return ret;
}

int
main (int argc, char **argv)
{
  char textline[2560];
  char kbuff[128];
  char *ptext;
  void *deb_addr = &textline[0];
  int f_mem;
  int deb_mode = DEB_BYTE;
  int i;
  int last_cmd = 'd';
  int temp;
  int in_edit = 0;
  int dtemp;
  int ldev = 0;
  int j;
  int btemp;

  printf
    ("\nNetwinder Low Level Debug Tool V.%s, Woody, Corel Computer 1998.\n",
     VERSION);

  io_base = IO_BASE_20;
  i = open ("/proc/version", O_RDONLY);
  if (i == -1)
  {
    printf ("\n/proc directory is not mounted; assuming Linux 2.0.x");
  }
  else
  {
    read (i, textline, 256);
    close (i);
    if (strstr (textline, "2.2."))
    {
      kernel_version = 22;
      printf ("Using Linux 2.2 memory map\n");
      io_base = IO_BASE_22;
    }
    else if (strstr (textline, "2.3."))
    {
      kernel_version = 23;
      printf ("Using Linux 2.3 memory map\n");
      io_base = IO_BASE_22;
    }
  }

  if (getuid () != 0)
  {
    printf ("This program must be run as root!\n\n");
    return EXIT_FAILURE;
  }

  ldev = open ("/dev/nwdebug", O_RDONLY);
  if (ldev < 0)
  {
    printf ("Error %d opening /dev/nwdebug. Limited functionality.\n", ldev);
    ldev = 0;
  }

  f_mem = open ("/dev/mem", O_RDWR);
  ptext = &textline[0];

  while (1)
  {
    fflush (stdin);
    fprintf (stdout, "\n->");
    fgets (kbuff, 127, stdin);

    for (i = 0; i < 127; i++)
      if (kbuff[i] != 0x20)
	break;

    if (kbuff[i] == '\n')
    {
      *(int *) &kbuff[i] = last_cmd;
    }


    btemp = tolower (kbuff[i]);
    if (((btemp == 'l') || (btemp == 'g') || (btemp == 'c')) && (!ldev))
    {
      printf ("Install nwdebug.\n");
    }
    else
    {
      switch (btemp)
      {
      case 'e':
	i++;
	last_cmd = CMD_EDIT;

	if (tolower (kbuff[i]) == 'd')
	{
	  deb_mode = DEB_DWORD;
	  i++;
	}
	else if (tolower (kbuff[i]) == 'w')
	{
	  deb_mode = DEB_WORD;
	  i++;
	}
	else if (tolower (kbuff[i]) == 'b')
	{
	  deb_mode = DEB_BYTE;
	  i++;
	}

	i = sscanf (&kbuff[i], "%p", &deb_addr);

	while (eb ((long *) &deb_addr, f_mem, deb_mode, kbuff));

	break;			// letter E

      case 'd':
	i++;
	last_cmd = CMD_DUMP;

	if (tolower (kbuff[i]) == 'd')
	{
	  deb_mode = DEB_DWORD;
	  i++;
	}
	else if (tolower (kbuff[i]) == 'w')
	{
	  deb_mode = DEB_WORD;
	  i++;
	}
	else if (tolower (kbuff[i]) == 'b')
	{
	  deb_mode = DEB_BYTE;
	  i++;
	}

	i = sscanf (&kbuff[i], "%p", &deb_addr);

	if (i == -1)
	  deb_addr += 160;

	if (deb_addr)
	{
	  db (f_mem, (long) deb_addr, 160, ptext, deb_mode);
	  printf (ptext);
	}
	break;

      case 'i':
	i++;
	last_cmd = CMD_INPUT;

	i = sscanf (&kbuff[i], "%p", &deb_addr);

	//          if (i == -1)        //if just i - keep using this port
	//              deb_addr ++;
	{
	  MAP_IO (f_mem);

	  if (!IS_OK_IO)
	    printf ("Memory access error!\n");
	  btemp = inb ((long) deb_addr);

	  UNMAP_IO;

	  i =
	    sprintf (textline, "Port %04X", (unsigned int) deb_addr & 0xFFFF);
	  *(textline + i) = ' ';

	  sprintf (&textline[i + 1], " : %02X.\n", (unsigned char) btemp);
	  printf (textline);
	}

	break;

      case 'o':
	i++;
	last_cmd = CMD_OUTPUT;

	i = sscanf (&kbuff[i], "%04p %02X", &deb_addr, &btemp);

	if (i != -1)
	{
	  MAP_IO (f_mem);

	  if (!IS_OK_IO)
	    printf ("Port access error (%04X != %04X)!\n", i & 0xFFFF,
		    (unsigned int) deb_addr);
	  else
	    outb ((long) deb_addr, btemp);
	  UNMAP_IO;
	}
	break;

      case 's':
	i++;
	if (tolower(kbuff[i]) == 'i')
	{
	  i++;
	  last_cmd = (int) CMD_SUPER_IN;

	  i = sscanf (&kbuff[i], "%02X %04p", &ldev, &deb_addr);

	  MAP_IO (f_mem);
	  UNLOCK;
	  outb (0x370, 0x07);
	  outb (0x371, ldev);
	  outb (0x370, (unsigned int) deb_addr);
	  btemp = inb (0x371);
	  UNMAP_IO;

	  printf ("Logical dev. %02X, Reg %02X : %02X.\n",
		  ldev, (unsigned int) deb_addr & 0xFFFF, btemp);

	  break;
	}
	else if (tolower(kbuff[i]) == 'o')
	{
	  i++;
	  last_cmd = (int) CMD_SUPER_OUT;

	  i = sscanf (&kbuff[i], " %X %04p %02X", &ldev, &deb_addr, &btemp);

	  printf ("Logical dev. %02X, Reg %02X : %02X.\n",
		  ldev, (unsigned int) deb_addr, btemp);

	  MAP_IO (f_mem);
	  UNLOCK;
	  outb (0x370, 0x07);
	  outb (0x371, ldev);
	  outb (0x370, (int) deb_addr);
	  outb (0x371, (char) btemp);
	  UNMAP_IO;

	  break;
	}
	else
	  break;


      case 'c':
	i++;
	if (tolower(kbuff[i]) == 'r')
	{
	  i++;
	  ioctl (ldev, 0x5D, &btemp);	//CMD_READ_CPLD
	  if (!errno)
	    printf
	      ("CPLD: 0x%X (DS1620 rst:%s, 7111:%s, Mute:%s, Flash_wrt:N/A).\n",
	       btemp, btemp & 0x08 ? "OFF" : "ON",
	       btemp & 0x04 ? "disabled" : "enabled",
	       btemp & 0x02 ? "OFF" : "ON");
	  break;
	}
	else if (tolower(kbuff[i]) == 'w')
	{
	  i++;
	  last_cmd = (int) CMD_CPLD_WRITE;
	  i = sscanf (&kbuff[i], " %02X", &btemp);
	  if (i != -1)
	  {
	    btemp &= 0xEF;
	    ioctl (ldev, 0x5D, &j);	//CMD_READ_CPLD
	    ioctl (ldev, 0x5E, &btemp);	//CMD_WRITE_CPLD
	    if (!errno)
	    {
	      ioctl (ldev, 0x5D, &btemp);	//CMD_READ_CPLD
	      printf
		("CPLD: old 0x%X, new 0x%X (DS1620 rst:%s, 7111:%s, Mute:%s, Flash_wrt:N/A).\n",
		 j, btemp, btemp & 0x08 ? "OFF" : "ON",
		 btemp & 0x04 ? "disabled" : "enabled",
		 btemp & 0x02 ? "OFF" : "ON");
	    }
	  }
	  break;
	}
	else
	  break;


      case 'l':
	i++;
	if (tolower(kbuff[i]) == 'r')
	{
	  i++;
	  last_cmd = (int) CMD_LED_READ;
	  ioctl (ldev, 0x51, &btemp);	//CMD_GET_LED
	  if (!errno)
	    printf ("LED: 0x%X (RED:%s, GREEN:%s).\n",
		    btemp, btemp & 0x02 ? "ON" : "OFF",
		    btemp & 0x01 ? "ON" : "OFF");
	  break;
	}
	else if (tolower(kbuff[i]) == 'w')
	{
	  i++;
	  last_cmd = (int) CMD_LED_WRITE;
	  i = sscanf (&kbuff[i], " %02X", &btemp);
	  if (i != -1)
	  {
	    btemp &= 0x3;
	    ioctl (ldev, 0x51, &j);	//CMD_GET_LED
	    ioctl (ldev, 0x52, &btemp);	//CMD_SET_LED
	    if (!errno)
	    {
	      ioctl (ldev, 0x51, &btemp);	//CMD_GET_LED
	      printf ("LED: old 0x%X, new 0x%X.\n", j, btemp);
	    }
	  }
	  break;
	}
	else
	  break;


      case 'g':
	i++;
	if (tolower(kbuff[i]) == 'r')
	{
	  i++;
	  last_cmd = (int) CMD_GPIO_READ;
	  ioctl (ldev, 0x5B, &btemp);	//CMD_READ_GPIO
	  if (!errno)
	    printf ("GPIO: 0x%03X.\n", btemp);
	  break;
	}
	else if (tolower(kbuff[i]) == 'w')
	{
	  i++;
	  last_cmd = (int) CMD_GPIO_WRITE;
	  i = sscanf (&kbuff[i], " %04X", &btemp);
	  if (i != -1)
	  {
	    btemp &= 0xFFF;
	    ioctl (ldev, 0x5B, &j);	//CMD_READ_GPIO
	    ioctl (ldev, 0x5C, &btemp);	//CMD_WRITE_GPIO
	    if (!errno)
	    {
	      ioctl (ldev, 0x5B, &btemp);	//CMD_GET_GPIO
	      printf ("GPIO: old 0x%03X, new 0x%03X.\n", j, btemp);
	    }
	  }
	  break;
	}
	else
	  break;


      case 'q':
	close (f_mem);
	close (ldev);
	exit (0);
	break;

      case 'm':
	if (kernel_version == 20)
	{
//printf("               ...MEMORY MAP...\n");
	  printf ("                        Debuger\n");
	  printf ("Phys Start  End         Start(2)   PCI Start(1) Contents\n");
	  printf ("\n");
	  printf ("0000.0000   01FF.FFFF   0                       256 meg SDRAM\n");
	  printf ("4000.0000   4000.FFFF   FFF0.xxxx               DRAM mode registers\n");
	  printf ("4100.0000   413F.FFFF   E18x.xxxx               16 meg flash ROM\n");
	  printf ("4200.0000   420F.FFFF   E10x.xxxx               CSR\n");
	  printf ("5000.0000   5000.0FFF   E130.0xxx               StrongARM cache flush\n");
	  printf ("7800.0000   7800.0FFF   E120.0xxx               PCI write flush\n");
	  printf ("7900.0000   7900.0003   E110.0000               PCI IACK address\n");
	  printf ("7B00.0000   7BFF.FFFF   E0nn.xxxx               PCI config\n");
	  printf ("            VGA:E018..Ether100:E020,..SCSI:E030..IDE:E050..Ether10:E090..\n");
	  printf ("7C00.0000   7C00.0FFF   E000.0xxx               PCI I/O space\n");
	  printf ("7C00.xxxx               D080.xxxx               VGA config regs\n");
	  printf ("8000.0000   80FF.FFFF   D0xx.xxxx   08xx.xxxx   VGA\n");
//        printf ("9000.0000   9000.FFFF   D100.xxxx   1000.xxxx   modem codec (optional)\n");
	  printf ("A000.0000   A000.00FF   E140.00xx   2000.00xx   Ethernet 100\n");
	  printf ("A001.0000   A001.00FF   E141.00xx   2001.00xx   SCSI\n");
	  printf ("\n");
	  printf ("(1) PCI bus masters see these addresses.\n");
	  printf ("(2) These are the addresses used by Linux code, after the memory management\n");
	  printf ("unit has been enabled. Note that the SDRAM is owned by the Linux kernel and\n");
	  printf ("is managed at address C000.0000. When memory is dished out to user code, it\n");
	  printf ("appears duplicated at 0-BFFF.FFFF in the user space.\n");
	}
	else
	{
	  printf ("Debugger Addrs          Kernel\n");
	  printf ("Start       End         Addr(2)    PCI Start(1) Contents\n");
	  printf ("\n");
	  printf ("0000.0000   01FF.FFFF   C0xx.xxxx               256 meg SDRAM\n");
	  printf ("4000.0000   4000.FFFF                           DRAM mode registers\n");
	  printf ("4100.0000   413F.FFFF   D8xx.xxxx               16 meg flash ROM\n");
	  printf ("4200.0000   420F.FFFF   FE0x.xxxx               CSR\n");
	  printf ("5000.0000   5000.0FFF   DF00.0xxx               StrongARM cache flush\n");
	  printf ("7800.0000   7800.0FFF   FD00.0xxx               PCI write flush\n");
	  printf ("7900.0000   7900.0003   FC00.0000               PCI IACK address\n");
	  printf ("7B00.0000   7BFF.FFFF   F8nn.xxxx               PCI config\n");
	  printf ("            VGA:7B08..Ether100:7B10,..SCSI:7B20..IDE:7B40..Ether10:7B80..\n");
	  printf ("7C00.0000   7C00.0FFF   FF00.0xxx               PCI I/O space\n");
	  printf ("7C00.xxxx               D080.xxxx               VGA config regs\n");
	  printf ("8100.0000   81FF.FFFF   E1xx.xxxx   01xx.xxxx   VGA\n");
	  printf ("8080.0000   8080.00FF   E080.00xx   0080.00xx   Ethernet 100\n");
	  printf ("80A0.0000   80A0.1FFF   E0A0.xxxx   00A0.00xx   SCSI\n");
	  printf ("\n");
	  printf ("(1) PCI bus masters see these addresses.\n");
	  printf ("(2) These are the addresses used by Linux code, after the memory management\n");
	  printf ("unit has been enabled. Note that the SDRAM is owned by the Linux kernel and\n");
	  printf ("is managed at address C000.0000. When memory is dished out to user code, it\n");
	  printf ("appears duplicated at 0-BFFF.FFFF in the user space.\n");
	}


	break;


      case '?':
      case 'h':
	printf ("Debug commands (all values in hex):\n");
	printf ("dd addr          - dump dword\n");
	printf ("dw addr          - dump word\n");
	printf ("db addr          - dump byte\n");
	printf ("ed addr          - edit dword\n");
	printf ("ew addr          - edit word\n");
	printf ("eb addr          - edit byte\n");
	printf ("i  addr          - in port (byte)\n");
	printf ("o  addr val      - out port (byte)\n");
	printf ("si ldev reg      - superIO ldev reg in (byte)\n");
	printf ("so ldev reg val  - superIO ldev reg out (byte)\n");
	printf ("cr               - read CPLD (3 bits)\n");
	printf ("cw val           - write CPLD (3 bits)\n");
	printf ("lr               - read LED (0=black,1=green,2=red,3=amber)\n");
	printf ("lw val           - write LED\n");
	printf ("gr               - read GPIO\n");
	printf ("gw val           - write GPIO (all bits)\n");
	printf ("m                - display memory map\n");

	break;


      default:
	break;
      }
    }
  }
}

