/* -*-C-*-

$Id: hppacach.c,v 1.15 2003/02/14 18:28:19 cph Exp $

Copyright (c) 1990-1999 Massachusetts Institute of Technology

This file is part of MIT/GNU Scheme.

MIT/GNU Scheme 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.

MIT/GNU Scheme 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 MIT/GNU Scheme; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.

*/

#include <stdio.h>
#include <math.h>
#include <sys/stat.h>
#include <memory.h>
#include <nlist.h>
#include <unistd.h>
#include <errno.h>

#include "hppacach.h"

#define true			1
#define false			0
#define boolean			int

#ifdef DEBUG

#define DEBUGGING(stmt) stmt

#else

#define DEBUGGING(stmt) do						\
{									\
} while (0)

#endif

/* File that contains the symbol table for the kernel */

char * kernel_filenames [] =
{
  "/hp-ux",
  "/stand/vmunix",
  0
};

/* File where the kernel image lives */

#define KERNEL_MEMORY_FILE	"/dev/kmem"

#define UTSNAME_SYMBOL		"utsname"
#define PDC_CACHE_SYMBOL	"cache_tlb_parms"

static struct utsname sysinfo;
static char **the_argv;

void
io_error (pname, format, fname)
     char * pname;
     char * format;
     char * fname;
{
  char errmsg[MAXPATHLEN + 257];
  char errstring[MAXPATHLEN + 257];

  sprintf (&errmsg[0], format, fname);
  sprintf (&errstring[0], "%s: %s: %s", (the_argv[0]), pname, (&errmsg[0]));
  perror (&errstring[0]);
}

void
io_lose (pname, format, fname)
     char * pname;
     char * format;
     char * fname;
{
  io_error (pname, format, fname);
  exit (1);
}

struct kernel_locations
{
  long utsname_location;
  long pdc_cache_location;
};

struct nlist nl[] =
{
  { UTSNAME_SYMBOL },
  { PDC_CACHE_SYMBOL },
  { 0 },
};

static char *
choose_kernel_file ()
{
  char ** p = kernel_filenames;
  while (1)
    {
      struct stat s;
      if ((stat ((*p), (&s))) == 0)
	return (*p);
      p += 1;
    }
  fprintf (stderr, "Unable to find kernel.\n");
  fflush (stderr);
  exit (1);
}

void
read_nlist (kloc)
     struct kernel_locations *kloc;
{
  char * kernel_filename = (choose_kernel_file ());
  DEBUGGING (printf ("reading nlist...\n"));

  if ((nlist (kernel_filename, nl)) != 0)
    io_lose ("read_nlist", "failed on both %s and %s", kernel_filename);

  DEBUGGING (printf ("reading nlist done.\n"));

  kloc->utsname_location = nl[0].n_value;
  kloc->pdc_cache_location = nl[1].n_value;

  DEBUGGING (printf ("utsname location = 0x%x\n", kloc->utsname_location));
  DEBUGGING (printf ("pdc_cache location = 0x%x\n", kloc->pdc_cache_location));
}

void
read_parameters (pdc_cache)
     struct pdc_cache_dump *pdc_cache;
{
  struct kernel_locations kloc;
  struct utsname kerninfo;
  int kmem = (open (KERNEL_MEMORY_FILE, O_RDONLY));
  if (kmem < 0)
    io_lose ("read_parameters", "open (%s) failed", KERNEL_MEMORY_FILE);
  read_nlist (&kloc);
  if ((lseek (kmem, kloc.utsname_location, SEEK_SET)) < 0)
    io_lose ("read_parameters", "lseek (%s) failed", KERNEL_MEMORY_FILE);
  if ((read (kmem, (&kerninfo), (sizeof (kerninfo)))) !=
      (sizeof (kerninfo)))
    io_lose ("read_parameters", "read (%s) failed", KERNEL_MEMORY_FILE);
  if ((memcmp ((&kerninfo), (&sysinfo), (sizeof (sysinfo)))) != 0)
    fprintf (stderr, "read_parameters: uname and %s in %s differ.\n",
	     kloc.utsname_location, KERNEL_MEMORY_FILE);
  strncpy (pdc_cache->hardware, (kerninfo.machine),
	   (sizeof (kerninfo.machine)));
  if ((lseek (kmem, (kloc.pdc_cache_location), SEEK_SET)) < 0)
    io_lose ("read_parameters", "lseek (%s) failed", KERNEL_MEMORY_FILE);
  if ((read (kmem, &pdc_cache->cache_format,
	     (sizeof (pdc_cache->cache_format)))) !=
      (sizeof (pdc_cache->cache_format)))
    io_lose ("read_parameters", "read (%s) failed", KERNEL_MEMORY_FILE);
  if ((close (kmem)) < 0)
    io_lose ("read_parameters", "close (%s) failed", KERNEL_MEMORY_FILE);
  return;
}

void
print_sel (sel, name, pattern, op)
     unsigned int sel;
     char *name;
     char *pattern;
     char *op;
{
  switch (sel)
  {
    case 0:
      printf ("\n      Both ");
      printf (pattern, "D");
      printf (" and ");
      printf (pattern, "I");
      printf (" must be used to %s.", op);
      break;

    case 1:
      printf ("\n      Only ");
      printf (pattern, "D");
      printf (" needs to be used to %s.", op);
      break;

    case 2:
      printf ("\n      Only ");
      printf (pattern, "I");
      printf (" needs to be used to %s.", op);
      break;

    case 3:
      printf ("\n      Either ");
      printf (pattern, "D");
      printf (" or ");
      printf (pattern, "I");
      printf (" can be used to %s.", op);
      break;

    default:
      fprintf (stderr, "\n      Bad %s value %d.", name, (sel));
      break;
  }

  return;
}

void
print_cst (cst)
     unsigned int cst;
{
  switch (cst)
  {
    case 0:
      printf ("\n      It does not issue coherent operations.");
      break;

    case 1:
      printf ("\n      It issues coherent operations.");
      break;

    default:
      printf ("\n      It has a reserved cst value %d.", (cst));
      break;
  }
  return;
}

void
print_cache (info, name, write_only_p)
     struct cache_info *info;
     char *name;
     boolean write_only_p;
{
  printf ("\n");

  /* First print the user-readable information as a comment. */

  printf ("    /*\n");
  printf ("      %s-cache information:\n", name);

  printf ("\tsize\t\t%ld bytes (%ld K).\n", info->size, (info->size / 1024));
  printf ("\tconf\t\t0x%08lx\n", info->conf.word);
  printf ("\tbase\t\t0x%lx\n", info->base);
  printf ("\tstride\t\t%ld bytes.\n", info->stride);
  printf ("\tcount\t\t%ld entries.\n", info->count);
  printf ("\tloop\t\t%ld association%s per entry.\n",
	  info->loop, ((info->loop == 1) ? "" : "s"));

  printf ("\tblock size\t%d line%s.\n",
	  info->conf.bits.block,
	  ((info->conf.bits.block == 1) ? "" : "s"));
  printf ("\tline size\t%d (16-byte units).\n", info->conf.bits.line);

  if (write_only_p)
  {
    printf ("      It is a read-only cache.");
  }
  else if (info->conf.bits.wt == 0)
  {
    printf ("      It is a write-to cache.");
  }
  else
  {
    printf ("      It is a write-through cache.");
  }

  print_cst ((info->conf.bits.cst));
  print_sel ((info->conf.bits.fsel), "f-sel", "F%sC", "flush");

  /* Now print the C-readable information. */

  printf ("\n    */\n");
  printf ("    { %ld, 0x%08lx, 0x%lx, %ld, %ld, %ld }",
	  info->size, info->conf.word, info->base,
	  info->stride, info->count, info->loop);

  return;
}

void
print_tlb (info, name)
     struct tlb_info *info;
     char *name;
{
  printf ("\n");

  /* First print the user-readable information as a comment. */

  printf ("    /*\n");
  printf ("      %s-TLB information:\n", name);

  printf ("\tsize\t\t%ld entries (%ld K).\n",
	  info->size, (info->size / 1024));
  printf ("\tconf\t\t0x%08lx\n", info->conf.word);
  printf ("\tsp_base\t\t0x%lx\n", info->sp_base);
  printf ("\tsp_stride\t%ld\n", info->sp_stride);
  printf ("\tsp_count\t%ld\n", info->sp_count);
  printf ("\toff_base\t0x%lx\n", info->off_base);
  printf ("\toff_stride\t%ld\n", info->off_stride);
  printf ("\toff_count\t%ld\n", info->off_count);
  printf ("\tloop\t\t%ld association%s per entry.",
	  info->loop, ((info->loop == 1) ? "" : "s"));
  
  print_cst ((info->conf.bits.cst));
  print_sel ((info->conf.bits.psel), "p-sel", "P%sTLB", "purge");

  /* Now print the C-readable information. */

  printf ("\n    */\n");
  printf ("    { %ld, 0x%08lx, 0x%lx, %ld, %ld, 0x%lx, %ld, %ld, %ld }",
	  info->size, info->conf.word,
	  info->sp_base, info->sp_stride, info->sp_count,
	  info->off_base, info->off_stride, info->off_count,
	  info->loop);

  return;  
}

void
print_parameters (pdc_cache, node_p)
     struct pdc_cache_dump *pdc_cache;
     int node_p;
{
  struct pdc_cache_result *io_arch_format;

  if (node_p)
  {
    printf ("/* Emacs: Use -*- C -*- mode when editting this file. */\n\n");
    printf ("{\n  /* Cache description for %s, an HP PA %s processor. */\n\n",
	    sysinfo.nodename,
	    sysinfo.machine);
  }
  else
  {
    printf ("{\n");
  }

  io_arch_format = ((struct pdc_cache_result *) &(pdc_cache->cache_format));

  printf ("  \"%s\",\n\n  {", pdc_cache->hardware);

  print_cache (&(io_arch_format->I_info), "I", true);
  printf (",");
  print_cache (&(io_arch_format->D_info), "D", false);
  printf (",");

  print_tlb (&(io_arch_format->IT_info), "I");
  printf (",");
  print_tlb (&(io_arch_format->DT_info), "D");
  printf ("\n");

  printf ("  }};\n");
  return;
}

int
search_pdc_database (fd, pdc_cache, filename)
     int fd;
     struct pdc_cache_dump * pdc_cache;
     char * filename;
{
  while (1)
    {
      int scr =
	(read (fd, ((char *) pdc_cache), (sizeof (struct pdc_cache_dump))));
      if (scr < 0)
	io_lose ("search_pdc_database", "read (%s) failed", filename);
      if (scr != (sizeof (struct pdc_cache_dump)))
	{
	  if (scr == 0)
	    return (0);
	  fprintf (stderr, "%s: %s: incomplete read (%s)\n",
		   (the_argv[0]), "search_pdc_database", filename);
	  fflush (stderr);
	  exit (1);
	}
      if ((strcmp ((sysinfo . machine), (pdc_cache -> hardware))) == 0)
	return (1);
    }
}

#define MODE_ADD	0
#define MODE_REPLACE	1
#define MODE_PRINT	2

void
update_pdc_database (mode, pdc_cache, filename)
     int mode;
     struct pdc_cache_dump * pdc_cache;
     char * filename;
{
  int write_p = 1;
  int fd = (open (filename, (O_RDWR | O_CREAT), 0666));
  if (fd < 0)
  {
    if (errno != EACCES)
      io_lose ("update_pdc_database", "open (%s) failed", filename);
    fd = (open (filename, O_RDONLY));
    if (fd < 0)
      io_lose ("update_pdc_database", "open (%s) failed", filename);
    else
    {
      write_p = 0;
      if (mode != MODE_PRINT)
	fprintf (stderr, "Data base \"%s\" is write-protected.\n", filename);
    }
  }
  if (! (search_pdc_database (fd, pdc_cache, filename)))
    {
      read_parameters (pdc_cache);
      if (!write_p && (mode != MODE_PRINT))
	printf ("Could not write information to data base.\n");
      else
      {
	int scr =
	  (write (fd, ((char *) pdc_cache), (sizeof (struct pdc_cache_dump))));
	if (scr < 0)
	  io_lose ("update_pdc_database", "write (%s) failed", filename);
	if (scr != (sizeof (struct pdc_cache_dump)))
	  {
	    fprintf (stderr, "%s: %s: incomplete write (%s)\n",
		     (the_argv[0]), "update_pdc_database", filename);
	    fflush (stderr);
	    exit (1);
	  }
      }
    }
  else
  {
    struct pdc_cache_dump new_cache_s, * new_cache;

    new_cache = & new_cache_s;
    read_parameters (new_cache);
    if ((memcmp (new_cache, pdc_cache, (sizeof (struct pdc_cache_dump))))
	== 0)
    {
      if (mode != MODE_PRINT)
	printf ("Correct information for model %s is present in data base.\n",
		&new_cache->hardware[0]);
    }
    else
    {
      printf ("Data base contains different information for model %s.\n",
	      &new_cache->hardware[0]);
      switch (mode)
      {
	case MODE_REPLACE:
        {
	  if (write_p)
	  {
	    printf ("Keeping the new information.\n");
	    if ((lseek (fd, (- (sizeof (struct pdc_cache_dump))), SEEK_CUR))
		== -1)
	      io_lose ("update_pdc_database", "lseek (%s) failed", filename);
	    if ((write (fd, new_cache, (sizeof (struct pdc_cache_dump))))
		!= (sizeof (struct pdc_cache_dump)))
	      io_lose ("update_pdc_database", "write (%s) failed", filename);
	    break;
	  }
	}

	case MODE_ADD:
        {
	  printf ("Keeping the old information.\n");
	  break;
	}

	case MODE_PRINT:
        {
	  printf ("New information:\n");
	  print_parameters (new_cache, 1);
	  printf ("\n\nOld information:\n");
	}
	default:
	  fprintf (stderr, "%s error.  Unknown mode %d.\n", the_argv[0], mode);
      }
    }
  }
  if ((close (fd)) < 0)
    io_lose ("update_pdc_database", "close (%s) failed", filename);
  return;
}

void
print_pdc_database (filename)
     char * filename;
{
  struct pdc_cache_dump pdc_cache_s, *pdc_cache;
  int fd = (open (filename, (O_RDONLY), 0666));
  int first;

  if (fd < 0)
    io_lose ("print_pdc_database", "open (%s) failed", filename);

  pdc_cache = &pdc_cache_s;
  first = 1;
  while (1)
  {
    int scr =
      (read (fd, ((char *) pdc_cache), (sizeof (struct pdc_cache_dump))));
    if (scr < 0)
      io_lose ("print_pdc_database", "read (%s) failed", filename);
    if (scr != (sizeof (struct pdc_cache_dump)))
    {
      if (scr == 0)
	break;
      fprintf (stderr, "%s: %s: incomplete read (%s)\n",
	       (the_argv[0]), "print_pdc_database", filename);
      fflush (stderr);
      exit (1);
    }

    if (first == 0)
    {
      putchar ('\f');
      putchar ('\n');
    }
    else
    {
      first = 0;
    }
    print_parameters (pdc_cache, 0);
  }

  if ((close (fd)) < 0)
    io_lose ("print_pdc_database", "close (%s) failed", filename);
}

void
read_stored_parameters (pdc_cache, filename)
     struct pdc_cache_dump * pdc_cache;
     char * filename;
{
  int fd = (open (filename, (O_RDONLY), 0));
  if (fd < 0)
    io_lose ("read_stored_parameters", "open (%s) failed", filename);
  if (! (search_pdc_database (fd, pdc_cache, filename)))
    {
      fprintf (stderr, "%s: %s: unable to find entry in models database\n",
	       (the_argv[0]), "read_stored_parameters");
      fflush (stderr);
      exit (1);
    }
  if ((close (fd)) < 0)
    io_lose ("read_stored_parameters", "close (%s) failed", filename);
}

void
verify_parameters (new_pdc_cache, old_pdc_cache)
     struct pdc_cache_dump *new_pdc_cache, *old_pdc_cache;
{
  boolean lose;

  lose = false;
  if ((strcmp (new_pdc_cache->hardware, old_pdc_cache->hardware)) != 0)
  {
    lose = true;
    printf ("Model differs: old = %s; current = %s.\n",
	    new_pdc_cache->hardware, old_pdc_cache->hardware);
  }
  if ((memcmp (&new_pdc_cache->cache_format,
	       &old_pdc_cache->cache_format,
	       (sizeof (struct pdc_cache_result)))) != 0)
  {
    lose = true;
    printf ("The stored cache information is incorrect.\n");
  }
  if (!lose)
  {
    printf ("The stored cache information is correct.\n");
  }
  return;
}

void
usage ()
{
  fprintf (stderr, "usage: one of:\n");
  fprintf (stderr, " %s -add FILENAME\n", (the_argv[0]));
  fprintf (stderr, " %s -replace FILENAME\n", (the_argv[0]));
  fprintf (stderr, " %s -verify FILENAME\n", (the_argv[0]));
  fprintf (stderr, " %s -print FILENAME\n", (the_argv[0]));
  fprintf (stderr, " %s -printall FILENAME\n", (the_argv[0]));
  fflush (stderr);
  exit (1);
}

void
main (argc, argv)
     int argc;
     char **argv;
{
  the_argv = argv;
  if ((uname (&sysinfo)) < 0)
    io_lose ("main", "uname failed", 0);
  if (argc != 3)
    usage ();
  {
    char * keyword = (argv[1]);
    char * filename = (argv[2]);
    if ((strcmp (keyword, "-add")) == 0)
      {
	struct pdc_cache_dump pdc_cache;
	update_pdc_database (MODE_ADD, (&pdc_cache), filename);
      }
    else if ((strcmp (keyword, "-replace")) == 0)
      {
	struct pdc_cache_dump pdc_cache;
	update_pdc_database (MODE_REPLACE, (&pdc_cache), filename);
      }
    else if ((strcmp (keyword, "-print")) == 0)
      {
	struct pdc_cache_dump pdc_cache;
	update_pdc_database (MODE_PRINT, (&pdc_cache), filename);
	print_parameters (&pdc_cache, 1);
      }
    else if ((strcmp (keyword, "-printall")) == 0)
      {
	print_pdc_database (filename);
      }
    else if ((strcmp (keyword, "-verify")) == 0)
      {
	struct pdc_cache_dump old_pdc_cache;
	struct pdc_cache_dump new_pdc_cache;
	read_stored_parameters ((&old_pdc_cache), filename);
	read_parameters (&new_pdc_cache);
	verify_parameters ((&new_pdc_cache), (&old_pdc_cache));
      }
    else
      usage ();
  }
  exit (0);
}
