/*
 *  prom.c - Routines for talking to the Open Firmware PROM
 *
 *  Copyright (C) 2001, 2002 Ethan Benson
 *
 *  Copyright (C) 1999 Benjamin Herrenschmidt
 *
 *  Copyright (C) 1999 Marius Vollmer
 *
 *  Copyright (C) 1996 Paul Mackerras.
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "prom.h"
#include "stdarg.h"
#include "stddef.h"
#include "stdlib.h"
#include "types.h"
#include "ctype.h"
#include "asm/processor.h"
#include "errors.h"
#include "debug.h"
#include "string.h"

#define READ_BLOCKS_USE_READ	1

static int yaboot_debug;

prom_entry prom;

ihandle prom_stdin, prom_stdout;

static ihandle prom_mem, prom_mmu;
static ihandle prom_chosen, prom_options;

struct prom_args {
     const char *service;
     int nargs;
     int nret;
     void *args[10];
};

void *
call_prom (const char *service, int nargs, int nret, ...)
{
     va_list list;
     int i;
     struct prom_args prom_args;

     prom_args.service = service;
     prom_args.nargs = nargs;
     prom_args.nret = nret;
     va_start (list, nret);
     for (i = 0; i < nargs; ++i)
	  prom_args.args[i] = va_arg(list, void *);
     va_end(list);
     for (i = 0; i < nret; ++i)
	  prom_args.args[i + nargs] = 0;
     prom (&prom_args);
     if (nret > 0)
	  return prom_args.args[nargs];
     else
	  return 0;
}

void *
call_prom_return (const char *service, int nargs, int nret, ...)
{
     va_list list;
     int i;
     void* result;
     struct prom_args prom_args;

     prom_args.service = service;
     prom_args.nargs = nargs;
     prom_args.nret = nret;
     va_start (list, nret);
     for (i = 0; i < nargs; ++i)
	  prom_args.args[i] = va_arg(list, void *);
     for (i = 0; i < nret; ++i)
	  prom_args.args[i + nargs] = 0;
     if (prom (&prom_args) != 0)
	  return PROM_INVALID_HANDLE;
     if (nret > 0) {
	  result = prom_args.args[nargs];
	  for (i=1; i<nret; i++) {
	       void** rp = va_arg(list, void**);
	       *rp = prom_args.args[i+nargs];
	  }
     } else
	  result = 0;
     va_end(list);
     return result;
}

static void *
call_method_1 (char *method, prom_handle h, int nargs, ...)
{
     va_list list;
     int i;
     struct prom_args prom_args;

     prom_args.service = "call-method";
     prom_args.nargs = nargs+2;
     prom_args.nret = 2;
     prom_args.args[0] = method;
     prom_args.args[1] = h;
     va_start (list, nargs);
     for (i = 0; i < nargs; ++i)
	  prom_args.args[2+i] = va_arg(list, void *);
     va_end(list);
     prom_args.args[2+nargs] = 0;
     prom_args.args[2+nargs+1] = 0;

     prom (&prom_args);

     if (prom_args.args[2+nargs] != 0)
     {
	  prom_printf ("method '%s' failed %p\n", method, prom_args.args[2+nargs]);
	  return 0;
     }
     return prom_args.args[2+nargs+1];
}


prom_handle
prom_finddevice (char *name)
{
     return call_prom ("finddevice", 1, 1, name);
}

prom_handle
prom_findpackage(char *path)
{
     return call_prom ("find-package", 1, 1, path);
}

int
prom_getprop (prom_handle pack, char *name, void *mem, int len)
{
     return (int)call_prom ("getprop", 4, 1, pack, name, mem, len);
}

int
prom_getproplen(prom_handle pack, const char *name)
{
     return (int)call_prom("getproplen", 2, 1, pack, name);
}

int
prom_setprop (prom_handle pack, char *name, void *mem, int len)
{
     return (int)call_prom ("setprop", 4, 1, pack, name, mem, len);
}

int
prom_get_chosen (char *name, void *mem, int len)
{
     return prom_getprop (prom_chosen, name, mem, len);
}

int
prom_get_options (char *name, void *mem, int len)
{
     if (prom_options == (void *)-1)
	  return -1;
     return prom_getprop (prom_options, name, mem, len);
}

int
prom_set_options (char *name, void *mem, int len)
{
     if (prom_options == (void *)-1)
	  return -1;
     return prom_setprop (prom_options, name, mem, len);
}

int
prom_get_devtype (char *device)
{
     phandle    dev;
     int        result;
     char       tmp[64];

     if (strstr(device, TOK_ISCSI))
	  return FILE_DEVICE_ISCSI;

     /* Find OF device phandle */
     dev = prom_finddevice(device);
     if (dev == PROM_INVALID_HANDLE) {
	  return FILE_ERR_BADDEV;
     }

     /* Check the kind of device */
     result = prom_getprop(dev, "device_type", tmp, 63);
     if (result == -1) {
	  prom_printf("can't get <device_type> for device: %s\n", device);
	  return FILE_ERR_BADDEV;
     }
     tmp[result] = 0;
     if (!strcmp(tmp, "block"))
	  return FILE_DEVICE_BLOCK;
     else if (!strcmp(tmp, "network"))
	  return FILE_DEVICE_NET;
     else {
	  prom_printf("Unkown device type <%s>\n", tmp);
	  return FILE_ERR_BADDEV;
     }
}

void
prom_init (prom_entry pp)
{
     prom = pp;

     prom_chosen = prom_finddevice ("/chosen");
     if (prom_chosen == (void *)-1)
	  prom_exit ();
     prom_options = prom_finddevice ("/options");
     if (prom_get_chosen ("stdout", &prom_stdout, sizeof(prom_stdout)) <= 0)
	  prom_exit();
     if (prom_get_chosen ("stdin", &prom_stdin, sizeof(prom_stdin)) <= 0)
	  prom_abort ("\nCan't open stdin");
     if (prom_get_chosen ("memory", &prom_mem, sizeof(prom_mem)) <= 0)
	  prom_abort ("\nCan't get mem handle");
     if (prom_get_chosen ("mmu", &prom_mmu, sizeof(prom_mmu)) <= 0)
	  prom_abort ("\nCan't get mmu handle");

     yaboot_debug = 0;
     prom_get_options("linux,yaboot-debug", &yaboot_debug, sizeof(yaboot_debug));

  // move cursor to fresh line
     prom_printf ("\n");

     /* Add a few OF methods (thanks Darwin) */
#if DEBUG
     prom_printf ("Adding OF methods...\n");
#endif

     prom_interpret (
	  /* All values in this forth code are in hex */
	  "hex "
	  /* Those are a few utilities ripped from Apple */
	  ": D2NIP decode-int nip nip ;\r"	// A useful function to save space
	  ": GPP$ get-package-property 0= ;\r"	// Another useful function to save space
	  ": ^on0 0= if -1 throw then ;\r"	// Bail if result zero
	  ": $CM $call-method ;\r"
	  );

     /* Some forth words used by the release method */
     prom_interpret (
	  " \" /chosen\" find-package if "
		 "dup \" memory\" rot GPP$ if "
			 "D2NIP swap "				 // ( MEMORY-ihandle "/chosen"-phandle )
			 "\" mmu\" rot GPP$ if "
				 "D2NIP "				 // ( MEMORY-ihandle MMU-ihandle )
			 "else "
				 "0 "					 // ( MEMORY-ihandle 0 )
			 "then "
		 "else "
			 "0 0 "						 // ( 0 0 )
		 "then "
	  "else "
		 "0 0 "							 // ( 0 0 )
	  "then\r"
	  "value mmu# "
	  "value mem# "
	  );

     prom_interpret (
	  ": ^mem mem# $CM ; "
	  ": ^mmu mmu# $CM ; "
	  );

     DEBUG_F("OF interface initialized.\n");
}

prom_handle
prom_open (char *spec)
{
     return call_prom ("open", 1, 1, spec, strlen(spec));
}

void
prom_close (prom_handle file)
{
     call_prom ("close", 1, 0, file);
}

int
prom_read (prom_handle file, void *buf, int n)
{
     int result = 0;
     int retries = 10;

     if (n == 0)
	  return 0;
     while(--retries) {
	  result = (int)call_prom ("read", 3, 1, file, buf, n);
	  if (result != 0)
	       break;
	  call_prom("interpret", 1, 1, " 10 ms");
     }

     return result;
}

int
prom_write (prom_handle file, void *buf, int n)
{
     return (int)call_prom ("write", 3, 1, file, buf, n);
}

int
prom_seek (prom_handle file, int pos)
{
     int status = (int)call_prom ("seek", 3, 1, file, 0, pos);
     return status == 0 || status == 1;
}

int
prom_lseek (prom_handle file, unsigned long long pos)
{
     int status = (int)call_prom ("seek", 3, 1, file,
				  (unsigned int)(pos >> 32), (unsigned int)(pos & 0xffffffffUL));
     return status == 0 || status == 1;
}

int
prom_loadmethod (prom_handle device, void* addr)
{
     return (int)call_method_1 ("load", device, 1, addr);
}

int
prom_getblksize (prom_handle file)
{
     return (int)call_method_1 ("block-size", file, 0);
}

int
prom_readblocks (prom_handle dev, int blockNum, int blockCount, void *buffer)
{
#if READ_BLOCKS_USE_READ
     int status;
     unsigned int blksize;

     blksize = prom_getblksize(dev);
     if (blksize <= 1)
	  blksize = 512;
     status = prom_seek(dev, blockNum * blksize);
     if (status != 1) {
	  return 0;
	  prom_printf("Can't seek to 0x%x\n", blockNum * blksize);
     }

     status = prom_read(dev, buffer, blockCount * blksize);
//  prom_printf("prom_readblocks, bl: %d, cnt: %d, status: %d\n",
//  	blockNum, blockCount, status);

     return status == (blockCount * blksize);
#else
     int result;
     int retries = 10;

     if (blockCount == 0)
	  return blockCount;
     while(--retries) {
	  result = call_method_1 ("read-blocks", dev, 3, buffer, blockNum, blockCount);
	  if (result != 0)
	       break;
	  call_prom("interpret", 1, 1, " 10 ms");
     }

     return result;
#endif
}

int
prom_getchar ()
{
     char c;
     int a;

     while ((a = (int)call_prom ("read", 3, 1, prom_stdin, &c, 1)) == 0)
	  ;
     if (a == -1)
	  prom_abort ("EOF on console\n");
     return c;
}

int
prom_nbgetchar()
{
     char ch;

     return (int) call_prom("read", 3, 1, prom_stdin, &ch, 1) > 0? ch: -1;
}

void
prom_putchar (char c)
{
     if (c == '\n')
	  call_prom ("write", 3, 1, prom_stdout, "\r\n", 2);
     else
	  call_prom ("write", 3, 1, prom_stdout, &c, 1);
}

void
prom_puts (prom_handle file, char *s)
{
     const char *p, *q;

     for (p = s; *p != 0; p = q)
     {
	  for (q = p; *q != 0 && *q != '\n'; ++q)
	       ;
	  if (q > p)
	       call_prom ("write", 3, 1, file, p, q - p);
	  if (*q != 0)
	  {
	       ++q;
	       call_prom ("write", 3, 1, file, "\r\n", 2);
	  }
     }
}

void
prom_vfprintf (prom_handle file, const char *fmt, va_list ap)
{
     static char printf_buf[2048];
     vsprintf (printf_buf, fmt, ap);
     prom_puts (file, printf_buf);
}

void
prom_vprintf (const char *fmt, va_list ap)
{
     static char printf_buf[2048];
     vsprintf (printf_buf, fmt, ap);
     prom_puts (prom_stdout, printf_buf);
}

void
prom_fprintf (prom_handle file, const char *fmt, ...)
{
     va_list ap;
     va_start (ap, fmt);
     prom_vfprintf (file, fmt, ap);
     va_end (ap);
}

void
prom_printf (const char *fmt, ...)
{
     va_list ap;
     va_start (ap, fmt);
     prom_vfprintf (prom_stdout, fmt, ap);
     va_end (ap);
}

void
prom_debug (const char *fmt, ...)
{
     va_list ap;

     if (!yaboot_debug)
          return;

     va_start (ap, fmt);
     prom_vfprintf (prom_stdout, fmt, ap);
     va_end (ap);
}

void
prom_perror (int error, char *filename)
{
     if (error == FILE_ERR_EOF)
	  prom_printf("%s: Unexpected End Of File\n", filename);
     else if (error == FILE_ERR_NOTFOUND)
	  prom_printf("%s: No such file or directory\n", filename);
     else if (error == FILE_CANT_SEEK)
	  prom_printf("%s: Seek error\n", filename);
     else if (error == FILE_IOERR)
	  prom_printf("%s: Input/output error\n", filename);
     else if (error == FILE_BAD_PATH)
	  prom_printf("%s: Path too long\n", filename);
     else if (error == FILE_ERR_BAD_TYPE)
	  prom_printf("%s: Not a regular file\n", filename);
     else if (error == FILE_ERR_NOTDIR)
	  prom_printf("%s: Not a directory\n", filename);
     else if (error == FILE_ERR_BAD_FSYS)
	  prom_printf("%s: Unknown or corrupt filesystem\n", filename);
     else if (error == FILE_ERR_SYMLINK_LOOP)
	  prom_printf("%s: Too many levels of symbolic links\n", filename);
     else if (error == FILE_ERR_LENGTH)
	  prom_printf("%s: File too large\n", filename);
     else if (error == FILE_ERR_FSBUSY)
	  prom_printf("%s: Filesystem busy\n", filename);
     else if (error == FILE_ERR_BADDEV)
	  prom_printf("%s: Unable to open file, Invalid device\n", filename);
     else
	  prom_printf("%s: Unknown error\n", filename);
}

void
prom_readline (char *prompt, char *buf, int len)
{
     int i = 0;
     int c;

     if (prompt)
	  prom_puts (prom_stdout, prompt);

     while (i < len-1 && (c = prom_getchar ()) != '\r')
     {
	  if (c == 8)
	  {
	       if (i > 0)
	       {
		    prom_puts (prom_stdout, "\b \b");
		    i--;
	       }
	       else
		    prom_putchar ('\a');
	  }
	  else if (isprint (c))
	  {
	       prom_putchar (c);
	       buf[i++] = c;
	  }
	  else
	       prom_putchar ('\a');
     }
     prom_putchar ('\n');
     buf[i] = 0;
}

#ifdef CONFIG_SET_COLORMAP
int prom_set_color(prom_handle device, int color, int r, int g, int b)
{
     return (int)call_prom( "call-method", 6, 1, "color!", device, color, b, g, r );
}
#endif /* CONFIG_SET_COLORMAP */

void
prom_exit ()
{
     call_prom ("exit", 0, 0);
}

void
prom_abort (char *fmt, ...)
{
     va_list ap;
     va_start (ap, fmt);
     prom_vfprintf (prom_stdout, fmt, ap);
     va_end (ap);
     prom_exit ();
}

void
prom_sleep (int seconds)
{
     int end;
     end = (prom_getms() + (seconds * 1000));
     while (prom_getms() <= end);
}

/* if address given is claimed look for other addresses to get the needed
 * space before giving up
 */
void *
prom_claim_chunk(void *virt, unsigned int size, unsigned int align)
{
     void *found, *addr;
     for(addr=virt; addr <= (void*)PROM_CLAIM_MAX_ADDR;
         addr+=(0x100000/sizeof(addr))) {
          found = call_prom("claim", 3, 1, addr, size, 0);
          if (found != (void *)-1) {
               prom_debug("claim of 0x%x at 0x%x returned 0x%x\n", size, (int)addr, (int)found);
               return(found);
          }
     }
     prom_printf("ERROR: claim of 0x%x in range 0x%x-0x%x failed\n", size, (int)virt, PROM_CLAIM_MAX_ADDR);
     return((void*)-1);
}

/* Start from top of memory and work down to get the needed space */
void *
prom_claim_chunk_top(unsigned int size, unsigned int align)
{
     void *found, *addr;
     for(addr=(void*)PROM_CLAIM_MAX_ADDR; addr >= (void *)size;
         addr-=(0x100000/sizeof(addr))) {
          found = call_prom("claim", 3, 1, addr, size, 0);
          if (found != (void *)-1) {
               prom_debug("claim of 0x%x at 0x%x returned 0x%x\n", size, (int)addr, (int)found);
               return(found);
          }
     }
     prom_printf("ERROR: claim of 0x%x in range 0x0-0x%x failed\n", size, PROM_CLAIM_MAX_ADDR);
     return((void*)-1);
}

void *
prom_claim (void *virt, unsigned int size, unsigned int align)
{
     void *ret;

     ret = call_prom ("claim", 3, 1, virt, size, align);
     if (ret == (void *)-1)
          prom_printf("ERROR: claim of 0x%x at 0x%x failed\n", size, (int)virt);
     else
          prom_debug("claim of 0x%x at 0x%x returned 0x%x\n", size, (int)virt, (int)ret);

     return ret;
}

void
prom_release(void *virt, unsigned int size)
{
     void *ret;

     ret = call_prom ("release", 2, 0, virt, size);
     prom_debug("release of 0x%x at 0x%x returned 0x%x\n", size, (int)virt, (int)ret);
}

void
prom_map (void *phys, void *virt, int size)
{
     unsigned long msr = mfmsr();

     /* Only create a mapping if we're running with relocation enabled. */
     if ( (msr & MSR_IR) && (msr & MSR_DR) )
	  call_method_1 ("map", prom_mmu, 4, -1, size, virt, phys);
}

void
prom_unmap (void *phys, void *virt, int size)
{
     unsigned long msr = mfmsr();

     /* Only unmap if we're running with relocation enabled. */
     if ( (msr & MSR_IR) && (msr & MSR_DR) )
	  call_method_1 ("map", prom_mmu, 4, -1, size, virt, phys);
}

char *
prom_getargs ()
{
     static char args[256];
     int l;

     l = prom_get_chosen ("bootargs", args, 255);
     args[l] = '\0';
     return args;
}

void
prom_setargs (char *args)
{
     int l = strlen (args)+1;
     if ((int)call_prom ("setprop", 4, 1, prom_chosen, "bootargs", args, l) != l)
	  prom_printf ("can't set args\n");
}

int prom_interpret (char *forth)
{
     return (int)call_prom("interpret", 1, 1, forth);
}

int
prom_getms(void)
{
     return (int) call_prom("milliseconds", 0, 1);
}

void
prom_pause(void)
{
     prom_print_available();
     call_prom("enter", 0, 0);
}

/*
 * prom_get_netinfo()
 * returns the packet with all needed info for netboot
 */
struct bootp_packet * prom_get_netinfo (void)
{
     void *bootp_response = NULL;
     char *propname;
     struct bootp_packet *packet;
     /* struct bootp_packet contains a VLA, so sizeof won't work.
        the VLA /must/ be the last field in the structure so use it's
        offset as a good estimate of the packet size */
     size_t packet_size = offsetof(struct bootp_packet, options);
     int i = 0, size, offset = 0;
     prom_handle chosen;
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

     chosen = prom_finddevice("/chosen");
     if (chosen < 0) {
          DEBUG_F("chosen=%lu\n", (unsigned long)chosen);
      return 0;
     }

     for (i = 0; i < ARRAY_SIZE(bootp_response_properties); i++) {
         propname = bootp_response_properties[i].name;
         size = prom_getproplen(chosen, propname);
         if (size <= 0)
             continue;

         DEBUG_F("using /chosen/%s\n", propname);
         offset = bootp_response_properties[i].offset;
         break;
     }

     if (size <= 0)
         return NULL;

     if (packet_size > size - offset) {
         prom_printf("Malformed %s property?\n", propname);
         return NULL;
     }

     bootp_response = malloc(size);
     if (!bootp_response)
         return NULL;

     if (prom_getprop(chosen, propname, bootp_response, size) < 0)
         return NULL;

     packet = bootp_response + offset;
     return packet;
}

/*
 * prom_get_mac()
 * returns the mac addr of an net card
 */
char * prom_get_mac (struct bootp_packet * packet)
{
     char * conf_path;
     int i;

     if (!packet)
        return NULL;

     /* 3 chars per byte in chaddr + \0 */
     conf_path = malloc(packet->hlen * 3 + 1);
     if (!conf_path)
         return NULL;
     sprintf(conf_path, "%02x", packet->chaddr[0]);

     for (i = 1; i < packet->hlen; i++) {
      char tmp[4];
      sprintf(tmp, "-%02x", packet->chaddr[i]);
      strcat(conf_path, tmp);
     }

     return conf_path;
}

/*
 * prom_get_ip()
 * returns the ip addr of an net card
 */
char * prom_get_ip (struct bootp_packet * packet)
{
     char * conf_path;

     if (!packet)
        return NULL;

     /* 8 chars in yiaddr + \0 */
     conf_path = malloc(9);
     if (!conf_path)
         return NULL;
     sprintf(conf_path, "%08x", packet->yiaddr);

     return conf_path;
}

/* We call this too early to use malloc, 128 cells should be large enough */
#define NR_AVAILABLE 128

void prom_print_available(void)
{
     prom_handle root;
     unsigned int addr_cells, size_cells;
     ihandle mem;
     unsigned int available[NR_AVAILABLE];
     unsigned int len;
     unsigned int *p;

     if (!yaboot_debug)
          return;

     root = prom_finddevice("/");
     if (!root)
          return;

     addr_cells = 2;
     prom_getprop(root, "#address-cells", &addr_cells, sizeof(addr_cells));

     size_cells = 1;
     prom_getprop(root, "#size-cells", &size_cells, sizeof(size_cells));

     mem = prom_finddevice("/memory@0");
     if (mem == PROM_INVALID_HANDLE)
          return;

     len = prom_getprop(mem, "available", available, sizeof(available));
     if (len == -1)
          return;
     len /= 4;

     prom_printf("\nAvailable memory ranges:\n");

     p = available;
     while (len > 0) {
          unsigned int addr, size;

          /*
           * Since we are in 32bit mode it should be safe to only print the
           * bottom 32bits of each range.
           */
          p += (addr_cells - 1);
          addr = *p;
          p++;

          p += (size_cells - 1);
          size = *p;
          p++;

          if (size)
               prom_printf("0x%08x-0x%08x (%3d MB)\n", addr, addr + size,
                           size/1024/1024);

          len -= (addr_cells + size_cells);
     }

     prom_printf("\n");
}

/*
 * Local variables:
 * c-file-style: "k&r"
 * c-basic-offset: 5
 * End:
 */
