/*
 * Copyright © 2012 Aleksej Saushev, The NetBSD Foundation
 * Copyright © 2009-2019 Inria.  All rights reserved.
 * Copyright © 2009-2010, 2020 Université Bordeaux
 * Copyright © 2011 Cisco Systems, Inc.  All rights reserved.
 * See COPYING in top-level directory.
 */

#define _NETBSD_SOURCE /* request "_np" functions */

#include "private/autogen/config.h"

#include <sys/types.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/param.h>
#include <pthread.h>
#include <sched.h>
#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif

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

static void
hwloc_netbsd_bsd2hwloc(hwloc_bitmap_t hwloc_cpuset, const cpuset_t *cpuset)
{
  unsigned cpu, cpulimit;
  int found = 0;
  hwloc_bitmap_zero(hwloc_cpuset);
  cpulimit = cpuset_size(cpuset) * CHAR_BIT;
  for (cpu = 0; cpu < cpulimit; cpu++)
    if (cpuset_isset(cpu, cpuset)) {
      hwloc_bitmap_set(hwloc_cpuset, cpu);
      found++;
    }
  /* when never bound, it returns an empty set, fill it instead */
  if (!found)
    hwloc_bitmap_fill(hwloc_cpuset);
}

static void
hwloc_netbsd_hwloc2bsd(hwloc_const_bitmap_t hwloc_cpuset, cpuset_t *cpuset)
{
  unsigned cpu, cpulimit;
  cpuset_zero(cpuset);
  cpulimit = cpuset_size(cpuset) * CHAR_BIT;
  for (cpu = 0; cpu < cpulimit; cpu++)
    if (hwloc_bitmap_isset(hwloc_cpuset, cpu))
      cpuset_set(cpu, cpuset);
}

static int
hwloc_netbsd_set_proc_cpubind(hwloc_topology_t topology __hwloc_attribute_unused, hwloc_pid_t pid, hwloc_const_bitmap_t hwloc_cpuset, int flags __hwloc_attribute_unused)
{
  int status;
  cpuset_t *cpuset = cpuset_create();
  hwloc_netbsd_hwloc2bsd(hwloc_cpuset, cpuset);
  status = sched_setaffinity_np(pid, cpuset_size(cpuset), cpuset);
  cpuset_destroy(cpuset);
  return status;
}

static int
hwloc_netbsd_get_proc_cpubind(hwloc_topology_t topology __hwloc_attribute_unused, hwloc_pid_t pid, hwloc_bitmap_t hwloc_cpuset, int flags __hwloc_attribute_unused)
{
  int status;
  cpuset_t *cpuset = cpuset_create();
  status = sched_getaffinity_np(pid, cpuset_size(cpuset), cpuset);
  hwloc_netbsd_bsd2hwloc(hwloc_cpuset, cpuset);
  cpuset_destroy(cpuset);
  return status;
}


static int
hwloc_netbsd_set_thisproc_cpubind(hwloc_topology_t topology, hwloc_const_bitmap_t hwloc_cpuset, int flags)
{
  return hwloc_netbsd_set_proc_cpubind(topology, 0, hwloc_cpuset, flags);
}

static int
hwloc_netbsd_get_thisproc_cpubind(hwloc_topology_t topology, hwloc_bitmap_t hwloc_cpuset, int flags)
{
  return hwloc_netbsd_get_proc_cpubind(topology, 0, hwloc_cpuset, flags);
}


static int
hwloc_netbsd_set_thread_cpubind(hwloc_topology_t topology __hwloc_attribute_unused, hwloc_thread_t tid, hwloc_const_bitmap_t hwloc_cpuset, int flags __hwloc_attribute_unused)
{
  int status;
  cpuset_t *cpuset = cpuset_create();
  hwloc_netbsd_hwloc2bsd(hwloc_cpuset, cpuset);
  status = pthread_setaffinity_np(tid, cpuset_size(cpuset), cpuset);
  cpuset_destroy(cpuset);

  if (status) {
    errno = status;
    return -1;
  }
  return 0;
}

static int
hwloc_netbsd_get_thread_cpubind(hwloc_topology_t topology __hwloc_attribute_unused, hwloc_thread_t tid, hwloc_bitmap_t hwloc_cpuset, int flags __hwloc_attribute_unused)
{
  int status;
  cpuset_t *cpuset = cpuset_create();
  status = pthread_getaffinity_np(tid, cpuset_size(cpuset), cpuset);
  hwloc_netbsd_bsd2hwloc(hwloc_cpuset, cpuset);
  cpuset_destroy(cpuset);

  if (status) {
    errno = status;
    return -1;
  }
  return 0;
}


static int
hwloc_netbsd_set_thisthread_cpubind(hwloc_topology_t topology, hwloc_const_bitmap_t hwloc_cpuset, int flags)
{
  return hwloc_netbsd_set_thread_cpubind(topology, pthread_self(), hwloc_cpuset, flags);
}

static int
hwloc_netbsd_get_thisthread_cpubind(hwloc_topology_t topology, hwloc_bitmap_t hwloc_cpuset, int flags)
{
  return hwloc_netbsd_get_thread_cpubind(topology, pthread_self(), hwloc_cpuset, flags);
}

static int
hwloc_look_netbsd(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;
  int64_t memsize;

  assert(dstatus->phase == HWLOC_DISC_PHASE_CPU);

  if (!topology->levels[0][0]->cpuset) {
    /* Nobody (even the x86 backend) created objects yet, setup basic objects */
    int nbprocs = hwloc_fallback_nbprocessors(0);
    if (nbprocs >= 1)
      topology->support.discovery->pu = 1;
    else
      nbprocs = 1;
    hwloc_alloc_root_sets(topology->levels[0][0]);
    hwloc_setup_pu_level(topology, nbprocs);
  }

  memsize = hwloc_fallback_memsize();
  if (memsize > 0)
    topology->machine_memory.local_memory = memsize;;

  /* Add NetBSD specific information */
  hwloc_obj_add_info(topology->levels[0][0], "Backend", "NetBSD");
  hwloc_add_uname_info(topology, NULL);
  return 0;
}

void
hwloc_set_netbsd_hooks(struct hwloc_binding_hooks *hooks,
		       struct hwloc_topology_support *support __hwloc_attribute_unused)
{
  hooks->set_proc_cpubind = hwloc_netbsd_set_proc_cpubind;
  hooks->get_proc_cpubind = hwloc_netbsd_get_proc_cpubind;
  hooks->set_thisproc_cpubind = hwloc_netbsd_set_thisproc_cpubind;
  hooks->get_thisproc_cpubind = hwloc_netbsd_get_thisproc_cpubind;

  hooks->set_thread_cpubind = hwloc_netbsd_set_thread_cpubind;
  hooks->get_thread_cpubind = hwloc_netbsd_get_thread_cpubind;
  hooks->set_thisthread_cpubind = hwloc_netbsd_set_thisthread_cpubind;
  hooks->get_thisthread_cpubind = hwloc_netbsd_get_thisthread_cpubind;
}

static struct hwloc_backend *
hwloc_netbsd_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_netbsd;
  return backend;
}

static struct hwloc_disc_component hwloc_netbsd_disc_component = {
  "netbsd",
  HWLOC_DISC_PHASE_CPU,
  HWLOC_DISC_PHASE_GLOBAL,
  hwloc_netbsd_component_instantiate,
  50,
  1,
  NULL
};

const struct hwloc_component hwloc_netbsd_component = {
  HWLOC_COMPONENT_ABI,
  NULL, NULL,
  HWLOC_COMPONENT_TYPE_DISC,
  0,
  &hwloc_netbsd_disc_component
};
