/*
 * pthread_setaffinity.c
 *
 * Description:
 * This translation unit implements thread cpu affinity setting.
 *
 * --------------------------------------------------------------------------
 *
 *      Pthreads4w - POSIX Threads Library for Win32
 *      Copyright(C) 1998 John E. Bossom
 *      Copyright(C) 1999-2018, Pthreads4w contributors
 *
 *      Homepage: https://sourceforge.net/projects/pthreads4w/
 *
 *      The current list of contributors is contained
 *      in the file CONTRIBUTORS included with the source
 *      code distribution. The list can also be seen at the
 *      following World Wide Web location:
 *      https://sourceforge.net/p/pthreads4w/wiki/Contributors/
 *
 * This file is part of Pthreads4w.
 *
 *    Pthreads4w 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 3 of the License, or
 *    (at your option) any later version.
 *
 *    Pthreads4w 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 Pthreads4w.  If not, see <http://www.gnu.org/licenses/>. *
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "pthread.h"
#include "implement.h"

int
pthread_setaffinity_np (pthread_t thread, size_t cpusetsize,
                                  const cpu_set_t *cpuset)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *   The pthread_setaffinity_np() function sets the CPU affinity mask
      *   of the thread thread to the CPU set pointed to by cpuset.  If the
      *   call is successful, and the thread is not currently running on one
      *   of the CPUs in cpuset, then it is migrated to one of those CPUs.
      *
      * PARAMETERS
      *		thread
      *					The target thread
      *
      *		cpusetsize
      *					Ignored in pthreads4w.
      *					Usually set to sizeof(cpu_set_t)
      *
      *		cpuset
      *					The new cpu set mask.
      *
      *   				The set of CPUs on which the thread will actually run
      *   				is the intersection of the set specified in the cpuset
      *   				argument and the set of CPUs actually present for
      *   				the process.
      *
      * DESCRIPTION
      *   The pthread_setaffinity_np() function sets the CPU affinity mask
      *   of the thread thread to the CPU set pointed to by cpuset.  If the
      *   call is successful, and the thread is not currently running on one
      *   of the CPUs in cpuset, then it is migrated to one of those CPUs.
      *
      * RESULTS
      * 				0		Success
      * 				ESRCH	Thread does not exist
      * 				EFAULT	pcuset is NULL
      * 				EAGAIN	The thread affinity could not be set
      * 				ENOSYS  The platform does not support this function
      *
      * ------------------------------------------------------
      */
{
#if ! defined(HAVE_CPU_AFFINITY)

  return ENOSYS;

#else

  int result = 0;
  ptw32_thread_t * tp;
  ptw32_mcs_local_node_t node;
  cpu_set_t processCpuset;

  ptw32_mcs_lock_acquire (&ptw32_thread_reuse_lock, &node);

  tp = (ptw32_thread_t *) thread.p;

  if (NULL == tp || thread.x != tp->ptHandle.x || NULL == tp->threadH)
    {
	  result = ESRCH;
    }
  else
	{
	  if (cpuset)
		{
		  if (sched_getaffinity(0, sizeof(cpu_set_t), &processCpuset))
		    {
			  result = PTW32_GET_ERRNO();
		    }
		  else
			{
			  /*
			   * Result is the intersection of available CPUs and the mask.
			   */
			  cpu_set_t newMask;

			  CPU_AND(&newMask, &processCpuset, cpuset);

			  if (((_sched_cpu_set_vector_*)&newMask)->_cpuset)
				{
				  if (SetThreadAffinityMask (tp->threadH, ((_sched_cpu_set_vector_*)&newMask)->_cpuset))
					{
					  /*
					   * We record the intersection of the process affinity
					   * and the thread affinity cpusets so that
					   * pthread_getaffinity_np() returns the actual thread
					   * CPU set.
					   */
					  tp->cpuset = ((_sched_cpu_set_vector_*)&newMask)->_cpuset;
					}
				  else
					{
					  result = EAGAIN;
					}
				}
			  else
				{
				  result = EINVAL;
				}
			}
		}
	  else
		{
		  result = EFAULT;
		}
	}

  ptw32_mcs_lock_release (&node);

  return result;

#endif
}

int
pthread_getaffinity_np (pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *   The pthread_getaffinity_np() function returns the CPU affinity mask
      *   of the thread thread in the CPU set pointed to by cpuset.
      *
      * PARAMETERS
      *		thread
      *					The target thread
      *
      *		cpusetsize
      *					Ignored in pthreads4w.
      *					Usually set to sizeof(cpu_set_t)
      *
      *		cpuset
      *					The location where the current cpu set
      *					will be returned.
      *
      *
      * DESCRIPTION
      *   The pthread_getaffinity_np() function returns the CPU affinity mask
      *   of the thread thread in the CPU set pointed to by cpuset.
      *
      * RESULTS
      * 				0		Success
      * 				ESRCH	thread does not exist
      * 				EFAULT	cpuset is NULL
      *                                 ENOSYS  The platform does not support this function
      *
      * ------------------------------------------------------
      */
{
#if ! defined(HAVE_CPU_AFFINITY)

  return ENOSYS;

#else

  int result = 0;
  ptw32_thread_t * tp;
  ptw32_mcs_local_node_t node;

  ptw32_mcs_lock_acquire(&ptw32_thread_reuse_lock, &node);

  tp = (ptw32_thread_t *) thread.p;

  if (NULL == tp || thread.x != tp->ptHandle.x || NULL == tp->threadH)
    {
	  result = ESRCH;
    }
  else
    {
	  if (cpuset)
	    {
		  if (tp->cpuset)
		    {
			  /*
			   * The application may have set thread affinity independently
			   * via SetThreadAffinityMask(). If so, we adjust our record of the threads
			   * affinity and try to do so in a reasonable way.
			   */
			  DWORD_PTR vThreadMask = SetThreadAffinityMask(tp->threadH, tp->cpuset);
			  if (vThreadMask && vThreadMask != tp->cpuset)
			    {
				  (void) SetThreadAffinityMask(tp->threadH, vThreadMask);
				  tp->cpuset = vThreadMask;
			    }
		    }
		  ((_sched_cpu_set_vector_*)cpuset)->_cpuset = tp->cpuset;
		}
	  else
	    {
		  result = EFAULT;
	    }
    }

  ptw32_mcs_lock_release(&node);

  return result;

#endif
}