/*
 * SPDX-License-Identifier: BSD-3-Clause
 * Copyright © 2009 CNRS
 * Copyright © 2009-2020 Inria.  All rights reserved.
 * Copyright © 2009-2010, 2013 Université Bordeaux
 * Copyright © 2011 Cisco Systems, Inc.  All rights reserved.
 * See COPYING in top-level directory.
 */

/* TODO: psets? (Only for root)
 * since 11i 1.6:
   _SC_PSET_SUPPORT
   pset_create/destroy/assign/setattr
   pset_ctl/getattr
   pset_bind()
   pthread_pset_bind_np()
 */

#include "private/autogen/config.h"

#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <errno.h>
#include <stdio.h>

#include "hwloc.h"
#include "private/private.h"
#include "private/debug.h"

#include <sys/mpctl.h>
#include <sys/mman.h>
#include <pthread.h>

static ldom_t
hwloc_hpux_find_ldom(hwloc_topology_t topology, hwloc_const_bitmap_t hwloc_set)
{
  int has_numa = sysconf(_SC_CCNUMA_SUPPORT) == 1;
  hwloc_obj_t obj;

  if (!has_numa)
    return -1;

  obj = hwloc_get_first_largest_obj_inside_cpuset(topology, hwloc_set);
  if (!hwloc_bitmap_isequal(obj->cpuset, hwloc_set))
    /* Does not correspond to exactly one node */
    return -1;
  /* obj is the highest possibly matching object, but some (single) child (with same cpuset) could match too */
  while (obj->type != HWLOC_OBJ_NUMANODE) {
    /* try the first child, in case it has the same cpuset */
    if (!obj->first_child
	|| !obj->first_child->cpuset
	|| !hwloc_bitmap_isequal(obj->cpuset, obj->first_child->cpuset))
      return -1;
    obj = obj->first_child;
  }

  return obj->os_index;
}

static spu_t
hwloc_hpux_find_spu(hwloc_topology_t topology __hwloc_attribute_unused, hwloc_const_bitmap_t hwloc_set)
{
  spu_t cpu;

  cpu = hwloc_bitmap_first(hwloc_set);
  if (cpu != -1 && hwloc_bitmap_weight(hwloc_set) == 1)
    return cpu;
  return -1;
}

/* Note: get_cpubind not available on HP-UX */
static int
hwloc_hpux_set_proc_cpubind(hwloc_topology_t topology, hwloc_pid_t pid, hwloc_const_bitmap_t hwloc_set, int flags)
{
  ldom_t ldom;
  spu_t cpu;

  /* Drop previous binding */
  mpctl(MPC_SETLDOM, MPC_LDOMFLOAT, pid);
  mpctl(MPC_SETPROCESS, MPC_SPUFLOAT, pid);

  if (hwloc_bitmap_isequal(hwloc_set, hwloc_topology_get_complete_cpuset(topology)))
    return 0;

  ldom = hwloc_hpux_find_ldom(topology, hwloc_set);
  if (ldom != -1)
    return mpctl(MPC_SETLDOM, ldom, pid);

  cpu = hwloc_hpux_find_spu(topology, hwloc_set);
  if (cpu != -1)
    return mpctl((flags & HWLOC_CPUBIND_STRICT) ? MPC_SETPROCESS_FORCE : MPC_SETPROCESS, cpu, pid);

  errno = EXDEV;
  return -1;
}

static int
hwloc_hpux_set_thisproc_cpubind(hwloc_topology_t topology, hwloc_const_bitmap_t hwloc_set, int flags)
{
  return hwloc_hpux_set_proc_cpubind(topology, MPC_SELFPID, hwloc_set, flags);
}

#ifdef hwloc_thread_t
static int
hwloc_hpux_set_thread_cpubind(hwloc_topology_t topology, hwloc_thread_t pthread, hwloc_const_bitmap_t hwloc_set, int flags)
{
  ldom_t ldom, ldom2;
  spu_t cpu, cpu2;

  /* Drop previous binding */
  pthread_ldom_bind_np(&ldom2, PTHREAD_LDOMFLOAT_NP, pthread);
  pthread_processor_bind_np(PTHREAD_BIND_ADVISORY_NP, &cpu2, PTHREAD_SPUFLOAT_NP, pthread);

  if (hwloc_bitmap_isequal(hwloc_set, hwloc_topology_get_complete_cpuset(topology)))
    return 0;

  ldom = hwloc_hpux_find_ldom(topology, hwloc_set);
  if (ldom != -1)
    return pthread_ldom_bind_np(&ldom2, ldom, pthread);

  cpu = hwloc_hpux_find_spu(topology, hwloc_set);
  if (cpu != -1)
    return pthread_processor_bind_np((flags & HWLOC_CPUBIND_STRICT) ? PTHREAD_BIND_FORCED_NP : PTHREAD_BIND_ADVISORY_NP, &cpu2, cpu, pthread);

  errno = EXDEV;
  return -1;
}

static int
hwloc_hpux_set_thisthread_cpubind(hwloc_topology_t topology, hwloc_const_bitmap_t hwloc_set, int flags)
{
  return hwloc_hpux_set_thread_cpubind(topology, PTHREAD_SELFTID_NP, hwloc_set, flags);
}
#endif

/* According to HP docs, HP-UX up to 11iv2 don't support migration */

#ifdef MAP_MEM_FIRST_TOUCH
static void*
hwloc_hpux_alloc_membind(hwloc_topology_t topology, size_t len, hwloc_const_nodeset_t _nodeset, hwloc_membind_policy_t policy, int flags)
{
  hwloc_const_nodeset_t nodeset;
  int mmap_flags;
  void *p;

  /* Can not give a set of nodes.  */
  nodeset = hwloc_topology_get_complete_nodeset(topology);
  if (policy != HWLOC_MEMBIND_DEFAULT
      && !hwloc_bitmap_isequal(nodeset, _nodeset)) {
    errno = EXDEV;
    return hwloc_alloc_or_fail(topology, len, flags);
  }

  switch (policy) {
    case HWLOC_MEMBIND_DEFAULT:
    case HWLOC_MEMBIND_BIND:
      mmap_flags = 0;
      break;
    case HWLOC_MEMBIND_FIRSTTOUCH:
      mmap_flags = MAP_MEM_FIRST_TOUCH;
      break;
    case HWLOC_MEMBIND_INTERLEAVE:
      mmap_flags = MAP_MEM_INTERLEAVED;
      break;
    default:
      errno = ENOSYS;
      return NULL;
  }

  p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | mmap_flags, -1, 0);
  return p == MAP_FAILED ? NULL : p;
}
#endif /* MAP_MEM_FIRST_TOUCH */

static int
hwloc_look_hpux(struct hwloc_backend *backend, struct hwloc_disc_status *dstatus)
{
  /*
   * This backend uses the underlying OS.
   * However we don't enforce topology->is_thissystem so that
   * we may still force use this backend when debugging with !thissystem.
   */

  struct hwloc_topology *topology = backend->topology;
  int has_numa = sysconf(_SC_CCNUMA_SUPPORT) == 1;
  spu_t currentcpu;
  ldom_t currentnode;
  hwloc_obj_t *nodes, obj;
  int i, nbnodes = 0;

  assert(dstatus->phase == HWLOC_DISC_PHASE_CPU);

  if (topology->levels[0][0]->cpuset)
    /* somebody discovered things */
    return -1;

  hwloc_alloc_root_sets(topology->levels[0][0]);

  if (has_numa) {
    nbnodes = mpctl((topology->flags & HWLOC_TOPOLOGY_FLAG_INCLUDE_DISALLOWED) ?
      MPC_GETNUMLDOMS_SYS : MPC_GETNUMLDOMS, 0, 0);
  }
  hwloc_debug("%d nodes\n", nbnodes);

  nodes = malloc(nbnodes * sizeof(*nodes));
  if (!nodes)
    has_numa = 0;

  if (has_numa) {
    i = 0;
    currentnode = mpctl((topology->flags & HWLOC_TOPOLOGY_FLAG_INCLUDE_DISALLOWED) ?
      MPC_GETFIRSTLDOM_SYS : MPC_GETFIRSTLDOM, 0, 0);
    while (currentnode != -1 && i < nbnodes) {
      hwloc_debug("node %d is %d\n", i, currentnode);
      nodes[i] = obj = hwloc_alloc_setup_object(topology, HWLOC_OBJ_NUMANODE, (unsigned) currentnode);
      obj->cpuset = hwloc_bitmap_alloc();
      obj->nodeset = hwloc_bitmap_alloc();
      hwloc_bitmap_set(obj->nodeset, currentnode);
      /* TODO: obj->attr->node.memory_kB */
      /* TODO: obj->attr->node.huge_page_free */

      currentnode = mpctl((topology->flags & HWLOC_TOPOLOGY_FLAG_INCLUDE_DISALLOWED) ?
        MPC_GETNEXTLDOM_SYS : MPC_GETNEXTLDOM, currentnode, 0);
      i++;
    }
  }

  i = 0;
  currentcpu = mpctl((topology->flags & HWLOC_TOPOLOGY_FLAG_INCLUDE_DISALLOWED) ?
      MPC_GETFIRSTSPU_SYS : MPC_GETFIRSTSPU, 0,0);
  while (currentcpu != -1) {
    obj = hwloc_alloc_setup_object(topology, HWLOC_OBJ_PU, (unsigned) currentcpu);
    obj->cpuset = hwloc_bitmap_alloc();
    hwloc_bitmap_set(obj->cpuset, currentcpu);

    hwloc_debug("cpu %d\n", currentcpu);

    if (has_numa) {
      /* Add this cpu to its node */
      currentnode = mpctl(MPC_SPUTOLDOM, currentcpu, 0);
      /* Hopefully it's just the same as previous cpu */
      if (i >= nbnodes || (ldom_t) nodes[i]->os_index != currentnode)
        for (i = 0; i < nbnodes; i++)
          if ((ldom_t) nodes[i]->os_index == currentnode)
            break;
      if (i < nbnodes) {
        hwloc_bitmap_set(nodes[i]->cpuset, currentcpu);
        hwloc_debug("is in node %d\n", i);
      } else {
        hwloc_debug("%s", "is in no node?!\n");
      }
    }

    /* Add cpu */
    hwloc__insert_object_by_cpuset(topology, NULL, obj, "hpux:pu");

    currentcpu = mpctl((topology->flags & HWLOC_TOPOLOGY_FLAG_INCLUDE_DISALLOWED) ?
      MPC_GETNEXTSPU_SYS : MPC_GETNEXTSPU, currentcpu, 0);
  }

  if (has_numa) {
    /* Add nodes */
    for (i = 0 ; i < nbnodes ; i++)
      hwloc__insert_object_by_cpuset(topology, NULL, nodes[i], "hpux:numanode");
  }

  topology->support.discovery->pu = 1;
  if (has_numa)
    topology->support.discovery->numa = 1;
  /* don't set disallowed_{pu,numa} since we don't set the allowed sets */

  hwloc_obj_add_info(topology->levels[0][0], "Backend", "HP-UX");
  hwloc_add_uname_info(topology, NULL);

  free(nodes);
  return 0;
}

void
hwloc_set_hpux_hooks(struct hwloc_binding_hooks *hooks,
		     struct hwloc_topology_support *support __hwloc_attribute_unused)
{
  hooks->set_proc_cpubind = hwloc_hpux_set_proc_cpubind;
  hooks->set_thisproc_cpubind = hwloc_hpux_set_thisproc_cpubind;
#ifdef hwloc_thread_t
  hooks->set_thread_cpubind = hwloc_hpux_set_thread_cpubind;
  hooks->set_thisthread_cpubind = hwloc_hpux_set_thisthread_cpubind;
#endif
#ifdef MAP_MEM_FIRST_TOUCH
  hooks->alloc_membind = hwloc_hpux_alloc_membind;
  hooks->alloc = hwloc_alloc_mmap;
  hooks->free_membind = hwloc_free_mmap;
  support->membind->firsttouch_membind = 1;
  support->membind->bind_membind = 1;
  support->membind->interleave_membind = 1;
#endif /* MAP_MEM_FIRST_TOUCH */
}

static struct hwloc_backend *
hwloc_hpux_component_instantiate(struct hwloc_topology *topology,
				 struct hwloc_disc_component *component,
				 unsigned excluded_phases __hwloc_attribute_unused,
				 const void *_data1 __hwloc_attribute_unused,
				 const void *_data2 __hwloc_attribute_unused,
				 const void *_data3 __hwloc_attribute_unused)
{
  struct hwloc_backend *backend;
  backend = hwloc_backend_alloc(topology, component);
  if (!backend)
    return NULL;
  backend->discover = hwloc_look_hpux;
  return backend;
}

static struct hwloc_disc_component hwloc_hpux_disc_component = {
  "hpux",
  HWLOC_DISC_PHASE_CPU,
  HWLOC_DISC_PHASE_GLOBAL,
  hwloc_hpux_component_instantiate,
  50,
  1,
  NULL
};

const struct hwloc_component hwloc_hpux_component = {
  HWLOC_COMPONENT_ABI,
  NULL, NULL,
  HWLOC_COMPONENT_TYPE_DISC,
  0,
  &hwloc_hpux_disc_component
};
