/*
 * parisc.cc
 *
 * This module parses the PA-RISC device tree (published under /sys
 * by the kernel).
 *
 *
 *
 *
 */

#include "version.h"
#include "device-tree.h"
#include "osutils.h"
#include "heuristics.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>

__ID("@(#) $Id: parisc.cc 2037 2008-09-05 19:47:37Z lyonel $");

#define DEVICESPARISC "/sys/devices/parisc"

#define TP_NPROC  0x00
#define TP_MEMORY 0x01
#define TP_B_DMA  0x02
#define TP_OBSOLETE 0x03
#define TP_A_DMA  0x04
#define TP_A_DIRECT 0x05
#define TP_OTHER  0x06
#define TP_BCPORT 0x07
#define TP_CIO    0x08
#define TP_CONSOLE  0x09
#define TP_FIO    0x0a
#define TP_BA   0x0b
#define TP_IOA    0x0c
#define TP_BRIDGE 0x0d
#define TP_FABRIC 0x0e
#define TP_MC   0x0f
#define TP_FAULTY 0x1f

struct parisc_device
{
  long sversion;
  const char * id;
  hw::hwClass device_class;
  const char * description;
};

static struct parisc_device parisc_device_list[] =
{
  {0x00004, "cpu", hw::processor, "Processor"},
  {0x0000d, "mux", hw::communication, "MUX"},
  {0x0000e, "serial", hw::communication, "RS-232 Serial Interface"},
  {0x0000f, "display", hw::display, "Graphics Display"},
  {0x00014, "input", hw::input, "HIL"},
  {0x00015, "display", hw::display, "Graphics Display"},
  {0x0003a, "printer", hw::printer, "Centronics Parallel interface"},
  {0x000a8, "input", hw::input, "Keyboard"},
  {0x00039, "scsi", hw::storage, "Core SCSI controller"},
  {0x0003b, "scsi", hw::storage, "FW-SCSI controller"},
  {0x0005e, "network", hw::network, "Token Ring Interface"},
  {0x00089, "scsi", hw::storage, "FW-SCSI controller"},
  {0x00091, "fc", hw::storage, "Fibre Channel controller"},
  {0x0009a, "network", hw::network, "ATM Interface"},
  {0x000a7, "fc", hw::storage, "Fibre Channel controller"},
  {0x00070, "core", hw::bus, "Core Bus"},
  {0x00076, "eisa", hw::bus, "EISA Bus"},
  {0x00078, "vme", hw::bus, "VME Bus"},
  {0x00081, "core", hw::bus, "Core Bus"},
  {0x0008e, "wax", hw::bus, "Wax Bus"},
  {0x00090, "eisa", hw::bus, "Wax EISA Bus"},
  {0x00093, "bus", hw::bus, "TIMI Bus"},
  {0x0000c, "bridge", hw::bridge, "Bus Converter"},
  {0x0000a, "pci", hw::bridge, "PCI Bridge"},
  {0x000a5, "pci", hw::bridge, "PCI Bridge"},
  {0x00052, "lanconsole", hw::network, "LAN/Console"},
  {0x00060, "lanconsole", hw::network, "LAN/Console"},
  {0x00071, "scsi", hw::storage, "Core SCSI controller"},
  {0x00072, "network", hw::network, "Core Ethernet Interface"},
  {0x00072, "input", hw::input, "Core HIL"},
  {0x00074, "printer", hw::printer, "Core Centronics Parallel interface"},
  {0x00075, "serial", hw::communication, "Core RS-232 Serial Interface"},
  {0x00077, "display", hw::display, "Graphics Display"},
  {0x0007a, "audio", hw::multimedia, "Audio"},
  {0x0007b, "audio", hw::multimedia, "Audio"},
  {0x0007c, "scsi", hw::storage, "FW-SCSI controller"},
  {0x0007d, "network", hw::network, "FDDI Interface"},
  {0x0007e, "audio", hw::multimedia, "Audio"},
  {0x0007f, "audio", hw::multimedia, "Audio"},
  {0x00082, "scsi", hw::storage, "SCSI controller"},
  {0x00083, "floppy", hw::storage, "Floppy"},
  {0x00084, "input", hw::input, "PS/2 port"},
  {0x00085, "display", hw::display, "Graphics Display"},
  {0x00086, "network", hw::network, "Token Ring Interface"},
  {0x00087, "communication", hw::communication, "ISDN"},
  {0x00088, "network", hw::network, "VME Networking Interface"},
  {0x0008a, "network", hw::network, "Core Ethernet Interface"},
  {0x0008c, "serial", hw::communication, "RS-232 Serial Interface"},
  {0x0008d, "unknown", hw::communication, "RJ-16"},
  {0x0008f, "firmware", hw::memory, "Boot ROM"},
  {0x00096, "input", hw::input, "PS/2 port"},
  {0x00097, "network", hw::network, "100VG LAN Interface"},
  {0x000a2, "network", hw::network, "10/100BT LAN Interface"},
  {0x000a3, "scsi", hw::storage, "Ultra2 SCSI controller"},
  {0x00000, "generic", hw::generic, NULL},
};

enum cpu_type
{
  pcx     = 0,                                    /* pa7000          pa 1.0  */
  pcxs    = 1,                                    /* pa7000          pa 1.1a */
  pcxt    = 2,                                    /* pa7100          pa 1.1b */
  pcxt_   = 3,                                    /* pa7200  (t')    pa 1.1c */
  pcxl    = 4,                                    /* pa7100lc        pa 1.1d */
  pcxl2   = 5,                                    /* pa7300lc        pa 1.1e */
  pcxu    = 6,                                    /* pa8000          pa 2.0  */
  pcxu_   = 7,                                    /* pa8200  (u+)    pa 2.0  */
  pcxw    = 8,                                    /* pa8500          pa 2.0  */
  pcxw_   = 9,                                    /* pa8600  (w+)    pa 2.0  */
  pcxw2   = 10,                                   /* pa8700         pa 2.0  */
  mako    = 11                                    /* pa8800         pa 2.0  */
};

static struct hp_cpu_type_mask
{
  unsigned short model;
  unsigned short mask;
  enum cpu_type cpu;
}


hp_cpu_type_mask_list[] =
{

  {                                               /* 0x0000 - 0x000f */
    0x0000, 0x0ff0, pcx
  },
  {                                               /* 0x0040 - 0x004f */
    0x0048, 0x0ff0, pcxl
  },
  {                                               /* 0x0080 - 0x008f */
    0x0080, 0x0ff0, pcx
  },
  {                                               /* 0x0100 - 0x010f */
    0x0100, 0x0ff0, pcx
  },
  {                                               /* 0x0182 - 0x0183 */
    0x0182, 0x0ffe, pcx
  },
  {                                               /* 0x0182 - 0x0183 */
    0x0182, 0x0ffe, pcxt
  },
  {                                               /* 0x0184 - 0x0184 */
    0x0184, 0x0fff, pcxu
  },
  {                                               /* 0x0200 - 0x0201 */
    0x0200, 0x0ffe, pcxs
  },
  {                                               /* 0x0202 - 0x0202 */
    0x0202, 0x0fff, pcxs
  },
  {                                               /* 0x0203 - 0x0203 */
    0x0203, 0x0fff, pcxt
  },
  {                                               /* 0x0204 - 0x0207 */
    0x0204, 0x0ffc, pcxt
  },
  {                                               /* 0x0280 - 0x0283 */
    0x0280, 0x0ffc, pcxs
  },
  {                                               /* 0x0284 - 0x0287 */
    0x0284, 0x0ffc, pcxt
  },
  {                                               /* 0x0288 - 0x0288 */
    0x0288, 0x0fff, pcxt
  },
  {                                               /* 0x0300 - 0x0303 */
    0x0300, 0x0ffc, pcxs
  },
  {                                               /* 0x0310 - 0x031f */
    0x0310, 0x0ff0, pcxt
  },
  {                                               /* 0x0320 - 0x032f */
    0x0320, 0x0ff0, pcxt
  },
  {                                               /* 0x0400 - 0x040f */
    0x0400, 0x0ff0, pcxt
  },
  {                                               /* 0x0480 - 0x048f */
    0x0480, 0x0ff0, pcxl
  },
  {                                               /* 0x0500 - 0x050f */
    0x0500, 0x0ff0, pcxl2
  },
  {                                               /* 0x0510 - 0x051f */
    0x0510, 0x0ff0, pcxl2
  },
  {                                               /* 0x0580 - 0x0587 */
    0x0580, 0x0ff8, pcxt_
  },
  {                                               /* 0x0588 - 0x058b */
    0x0588, 0x0ffc, pcxt_
  },
  {                                               /* 0x058c - 0x058d */
    0x058c, 0x0ffe, pcxt_
  },
  {                                               /* 0x058e - 0x058e */
    0x058e, 0x0fff, pcxt_
  },
  {                                               /* 0x058f - 0x058f */
    0x058f, 0x0fff, pcxu
  },
  {                                               /* 0x0590 - 0x0591 */
    0x0590, 0x0ffe, pcxu
  },
  {                                               /* 0x0592 - 0x0592 */
    0x0592, 0x0fff, pcxt_
  },
  {                                               /* 0x0593 - 0x0593 */
    0x0593, 0x0fff, pcxu
  },
  {                                               /* 0x0594 - 0x0597 */
    0x0594, 0x0ffc, pcxu
  },
  {                                               /* 0x0598 - 0x0599 */
    0x0598, 0x0ffe, pcxu_
  },
  {                                               /* 0x059a - 0x059b */
    0x059a, 0x0ffe, pcxu
  },
  {                                               /* 0x059c - 0x059c */
    0x059c, 0x0fff, pcxu
  },
  {                                               /* 0x059d - 0x059d */
    0x059d, 0x0fff, pcxu_
  },
  {                                               /* 0x059e - 0x059e */
    0x059e, 0x0fff, pcxt_
  },
  {                                               /* 0x059f - 0x059f */
    0x059f, 0x0fff, pcxu
  },
  {                                               /* 0x05a0 - 0x05a1 */
    0x05a0, 0x0ffe, pcxt_
  },
  {                                               /* 0x05a2 - 0x05a3 */
    0x05a2, 0x0ffe, pcxu
  },
  {                                               /* 0x05a4 - 0x05a7 */
    0x05a4, 0x0ffc, pcxu
  },
  {                                               /* 0x05a8 - 0x05ab */
    0x05a8, 0x0ffc, pcxu
  },
  {                                               /* 0x05ad - 0x05ad */
    0x05ad, 0x0fff, pcxu_
  },
  {                                               /* 0x05ae - 0x05af */
    0x05ae, 0x0ffe, pcxu_
  },
  {                                               /* 0x05b0 - 0x05b1 */
    0x05b0, 0x0ffe, pcxu_
  },
  {                                               /* 0x05b2 - 0x05b2 */
    0x05b2, 0x0fff, pcxu_
  },
  {                                               /* 0x05b3 - 0x05b3 */
    0x05b3, 0x0fff, pcxu
  },
  {                                               /* 0x05b4 - 0x05b4 */
    0x05b4, 0x0fff, pcxw
  },
  {                                               /* 0x05b5 - 0x05b5 */
    0x05b5, 0x0fff, pcxu_
  },
  {                                               /* 0x05b6 - 0x05b7 */
    0x05b6, 0x0ffe, pcxu_
  },
  {                                               /* 0x05b8 - 0x05b9 */
    0x05b8, 0x0ffe, pcxu_
  },
  {                                               /* 0x05ba - 0x05ba */
    0x05ba, 0x0fff, pcxu_
  },
  {                                               /* 0x05bb - 0x05bb */
    0x05bb, 0x0fff, pcxw
  },
  {                                               /* 0x05bc - 0x05bf */
    0x05bc, 0x0ffc, pcxw
  },
  {                                               /* 0x05c0 - 0x05c3 */
    0x05c0, 0x0ffc, pcxw
  },
  {                                               /* 0x05c4 - 0x05c5 */
    0x05c4, 0x0ffe, pcxw
  },
  {                                               /* 0x05c6 - 0x05c6 */
    0x05c6, 0x0fff, pcxw
  },
  {                                               /* 0x05c7 - 0x05c7 */
    0x05c7, 0x0fff, pcxw_
  },
  {                                               /* 0x05c8 - 0x05cb */
    0x05c8, 0x0ffc, pcxw
  },
  {                                               /* 0x05cc - 0x05cd */
    0x05cc, 0x0ffe, pcxw
  },
  {                                               /* 0x05ce - 0x05cf */
    0x05ce, 0x0ffe, pcxw_
  },
  {                                               /* 0x05d0 - 0x05d3 */
    0x05d0, 0x0ffc, pcxw_
  },
  {                                               /* 0x05d4 - 0x05d5 */
    0x05d4, 0x0ffe, pcxw_
  },
  {                                               /* 0x05d6 - 0x05d6 */
    0x05d6, 0x0fff, pcxw
  },
  {                                               /* 0x05d7 - 0x05d7 */
    0x05d7, 0x0fff, pcxw_
  },
  {                                               /* 0x05d8 - 0x05db */
    0x05d8, 0x0ffc, pcxw_
  },
  {                                               /* 0x05dc - 0x05dd */
    0x05dc, 0x0ffe, pcxw2
  },
  {                                               /* 0x05de - 0x05de */
    0x05de, 0x0fff, pcxw_
  },
  {                                               /* 0x05df - 0x05df */
    0x05df, 0x0fff, pcxw2
  },
  {                                               /* 0x05e0 - 0x05e3 */
    0x05e0, 0x0ffc, pcxw2
  },
  {                                               /* 0x05e4 - 0x05e4 */
    0x05e4, 0x0fff, pcxw2
  },
  {                                               /* 0x05e5 - 0x05e5 */
    0x05e5, 0x0fff, pcxw_
  },
  {                                               /* 0x05e6 - 0x05e7 */
    0x05e6, 0x0ffe, pcxw2
  },
  {                                               /* 0x05e8 - 0x05ef */
    0x05e8, 0x0ff8, pcxw2
  },
  {                                               /* 0x05f0 - 0x05ff */
    0x05f0, 0x0ff0, pcxw2
  },
  {                                               /* 0x0600 - 0x061f */
    0x0600, 0x0fe0, pcxl
  },
  {                                               /* 0x0880 - 0x088f */
    0x0880, 0x0ff0, mako
  },
  {                                               /* terminate table */
    0x0000, 0x0000, pcx
  }
};

static const char *cpu_name_version[][2] =
{
  { "PA7000 (PCX)",     "1.0" },
  { "PA7000 (PCX-S)",   "1.1a" },
  { "PA7100 (PCX-T)",   "1.1b" },
  { "PA7200 (PCX-T')",  "1.1c" },
  { "PA7100LC (PCX-L)", "1.1d" },
  { "PA7300LC (PCX-L2)",        "1.1e" },
  { "PA8000 (PCX-U)",   "2.0" },
  { "PA8200 (PCX-U+)",  "2.0" },
  { "PA8500 (PCX-W)",   "2.0" },
  { "PA8600 (PCX-W+)",  "2.0" },
  { "PA8700 (PCX-W2)",  "2.0" },
  { "PA8800 (Mako)",    "2.0" }
};

static long get_long(const string & path)
{
  long result = -1;
  FILE * in = fopen(path.c_str(), "r");

  if (in)
  {
    if(fscanf(in, "%lx", &result) != 1)
      result = -1;
    fclose(in);
  }

  return result;
}


static string cpubusinfo(int cpu)
{
  char buffer[10];

  snprintf(buffer, sizeof(buffer), "cpu@%d", cpu);

  return string(buffer);
}


enum cpu_type parisc_get_cpu_type(long hversion)
{
  struct hp_cpu_type_mask *ptr;

  for (ptr = hp_cpu_type_mask_list; 0 != ptr->mask; ptr++)
  {
    if (ptr->model == (hversion & ptr->mask))
      return ptr->cpu;
  }

  return pcx;                                     /* not reached: */
}


static int currentcpu = 0;

static hwNode get_device(long hw_type, long sversion, long hversion)
{
  hwNode newnode("generic");

  for(unsigned i=0; parisc_device_list[i].description!=NULL; i++)
  {
    if(sversion == parisc_device_list[i].sversion)
    {
      newnode = hwNode(parisc_device_list[i].id, parisc_device_list[i].device_class);
      newnode.setDescription(parisc_device_list[i].description);
      if(hw_type == TP_NPROC)
      {
        enum cpu_type cpu = parisc_get_cpu_type(hversion);
        newnode.setBusInfo(cpubusinfo(currentcpu++));
        newnode.setProduct(cpu_name_version[cpu][0]);
        newnode.setVersion(cpu_name_version[cpu][1]);
      }

      return newnode;
    }
  }

  switch(hw_type)
  {
    case TP_NPROC:
      newnode = hwNode("cpu", hw::processor);
      newnode.setDescription("Processor");
      newnode.setBusInfo(cpubusinfo(currentcpu++));
      break;
    case TP_MEMORY:
      newnode = hwNode("memory", hw::memory);
      newnode.setDescription("Memory");
      break;
    case TP_B_DMA:
      newnode.addCapability("b-dma", "Type B DMA I/O");
      break;
    case TP_A_DMA:
      newnode.addCapability("a-dma", "Type A DMA I/O");
      break;
    case TP_A_DIRECT:
      switch (sversion)
      {
        case 0x0D:
          newnode = hwNode("serial", hw::communication);
          newnode.setDescription("MUX port");
          break;
        case 0x0E:
          newnode = hwNode("serial", hw::communication);
          newnode.setDescription("RS-232 port");
          break;
      }

      newnode.addCapability("a-direct", "Type A Direct I/O");
      break;
    case TP_BCPORT:
      newnode = hwNode("busconverter", hw::bridge);
      newnode.setDescription("Bus converter port");
      break;
    case TP_CIO:
      newnode.setDescription("HP-CIO adapter");
      break;
    case TP_CONSOLE:
      newnode = hwNode("console", hw::input);
      newnode.setDescription("Console");
      break;
    case TP_FIO:
      newnode.setDescription("Foreign I/O module");
      break;
    case TP_BA:
      newnode = hwNode("bus", hw::bus);
      newnode.setDescription("Bus adapter");
      break;
    case TP_IOA:
      newnode.setDescription("I/O adapter");
      break;
    case TP_BRIDGE:
      newnode = hwNode("bridge", hw::bridge);
      newnode.setDescription("Bus bridge to foreign bus");
      break;
    case TP_FABRIC:
      newnode.setDescription("Fabric ASIC");
      break;
    case TP_FAULTY:
      newnode.disable();
      newnode.setDescription("Faulty module");
      break;
  }

  return newnode;
}


static bool scan_device(hwNode & node, string name = "")
{
  struct dirent **namelist;
  int n;
  hwNode * curnode = NULL;

  if(name != "")
  {
    size_t colon = name.rfind(":");
    long hw_type = get_long("hw_type");
    long sversion = get_long("sversion");
    long hversion = get_long("hversion");
    long rev = get_long("rev");
    hwNode newnode = get_device(hw_type, sversion, hversion);

    if((rev>0) && (newnode.getVersion() == ""))
    {
      char buffer[20];
      snprintf(buffer, sizeof(buffer), "%ld", rev);
      newnode.setVersion(buffer);
    }
    if(newnode.getBusInfo()=="")
      newnode.setBusInfo(guessBusInfo(name));
    if(exists("driver"))
    {
      string driver = readlink("driver");
      size_t slash = driver.rfind("/");
      newnode.setConfig("driver", driver.substr(slash==driver.npos?0:slash+1));
      newnode.claim();
    }
    if(colon!=name.npos)
      newnode.setPhysId(name.substr(colon+1));
    else
      newnode.setPhysId(name);
    curnode = node.addChild(newnode);
  }

  n = scandir(".", &namelist, selectdir, alphasort);
  if (n < 0)
    return false;
  else
  {
    for (int i = 0; i < n; i++)
      if(matches(namelist[i]->d_name, "^[0-9]+(:[0-9]+)*$"))
    {
      pushd(namelist[i]->d_name);
      scan_device(curnode?*curnode:node, namelist[i]->d_name);
      popd();
      free(namelist[i]);
    }
    free(namelist);
  }

  return true;
}


bool scan_parisc(hwNode & node)
{
  hwNode *core = node.getChild("core");

  currentcpu = 0;

  if (!core)
  {
    core = node.addChild(hwNode("core", hw::bus));
  }

  if (!core)
    return false;

  if(core->getDescription()=="")
    core->setDescription("Motherboard");
  pushd(DEVICESPARISC);
  scan_device(*core);
  popd();

  return true;
}
