1  /* Generic test case for CPU affinity functions.
       2     Copyright (C) 2015-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  /* This file is included by the tst-affinity*.c files to test the two
      20     variants of the functions, under different conditions.  The
      21     following functions have to be defined:
      22  
      23     static int getaffinity (size_t, cpu_set_t *);
      24     static int setaffinity (size_t, const cpu_set_t *);
      25     static bool early_test (struct conf *);
      26  
      27     The first two functions shall affect the affinity mask for the
      28     current thread and return 0 for success, -1 for error (with an
      29     error code in errno).
      30  
      31     early_test is invoked before the tests in this file affect the
      32     affinity masks.  If it returns true, testing continues, otherwise
      33     no more tests run and the overall test exits with status 1.
      34  */
      35  
      36  #include <errno.h>
      37  #include <limits.h>
      38  #include <sched.h>
      39  #include <stdbool.h>
      40  #include <stdio.h>
      41  
      42  /* CPU set configuration determined.  Can be used from early_test.  */
      43  struct conf
      44  {
      45    int set_size;			/* in bits */
      46    int last_cpu;
      47  };
      48  
      49  static int
      50  find_set_size (void)
      51  {
      52    /* There is considerable controversy about how to determine the size
      53       of the kernel CPU mask.  The probing loop below is only intended
      54       for testing purposes.  */
      55    for (int num_cpus = 64; num_cpus <= INT_MAX / 2; ++num_cpus)
      56      {
      57        cpu_set_t *set = CPU_ALLOC (num_cpus);
      58        size_t size = CPU_ALLOC_SIZE (num_cpus);
      59  
      60        if (set == NULL)
      61  	{
      62  	  printf ("error: CPU_ALLOC (%d) failed\n", num_cpus);
      63  	  return -1;
      64  	}
      65        if (getaffinity (size, set) == 0)
      66  	{
      67  	  CPU_FREE (set);
      68  	  return num_cpus;
      69  	}
      70        if (errno != EINVAL)
      71  	{
      72  	  printf ("error: getaffinity for %d CPUs: %m\n", num_cpus);
      73  	  CPU_FREE (set);
      74  	  return -1;
      75  	}
      76        CPU_FREE (set);
      77      }
      78    puts ("error: Cannot find maximum CPU number");
      79    return -1;
      80  }
      81  
      82  static int
      83  find_last_cpu (const cpu_set_t *set, size_t size)
      84  {
      85    /* We need to determine the set size with CPU_COUNT_S and the
      86       cpus_found counter because there is no direct way to obtain the
      87       actual CPU set size, in bits, from the value of
      88       CPU_ALLOC_SIZE.  */
      89    size_t cpus_found = 0;
      90    size_t total_cpus = CPU_COUNT_S (size, set);
      91    int last_cpu = -1;
      92  
      93    for (int cpu = 0; cpus_found < total_cpus; ++cpu)
      94      {
      95        if (CPU_ISSET_S (cpu, size, set))
      96  	{
      97  	  last_cpu = cpu;
      98  	  ++cpus_found;
      99  	}
     100      }
     101    return last_cpu;
     102  }
     103  
     104  static void
     105  setup_conf (struct conf *conf)
     106  {
     107    *conf = (struct conf) {-1, -1};
     108    conf->set_size = find_set_size ();
     109    if (conf->set_size > 0)
     110      {
     111        cpu_set_t *set = CPU_ALLOC (conf->set_size);
     112  
     113        if (set == NULL)
     114  	{
     115  	  printf ("error: CPU_ALLOC (%d) failed\n", conf->set_size);
     116  	  CPU_FREE (set);
     117  	  return;
     118  	}
     119        if (getaffinity (CPU_ALLOC_SIZE (conf->set_size), set) < 0)
     120  	{
     121  	  printf ("error: getaffinity failed: %m\n");
     122  	  CPU_FREE (set);
     123  	  return;
     124  	}
     125        conf->last_cpu = find_last_cpu (set, CPU_ALLOC_SIZE (conf->set_size));
     126        if (conf->last_cpu < 0)
     127  	puts ("info: No test CPU found");
     128        CPU_FREE (set);
     129      }
     130  }
     131  
     132  static bool
     133  test_size (const struct conf *conf, size_t size)
     134  {
     135    if (size < conf->set_size)
     136      {
     137        printf ("info: Test not run for CPU set size %zu\n", size);
     138        return true;
     139      }
     140  
     141    cpu_set_t *initial_set = CPU_ALLOC (size);
     142    cpu_set_t *set2 = CPU_ALLOC (size);
     143    cpu_set_t *active_cpu_set = CPU_ALLOC (size);
     144  
     145    if (initial_set == NULL || set2 == NULL || active_cpu_set == NULL)
     146      {
     147        printf ("error: size %zu: CPU_ALLOC failed\n", size);
     148        return false;
     149      }
     150    size_t kernel_size = CPU_ALLOC_SIZE (size);
     151  
     152    if (getaffinity (kernel_size, initial_set) < 0)
     153      {
     154        printf ("error: size %zu: getaffinity: %m\n", size);
     155        return false;
     156      }
     157    if (setaffinity (kernel_size, initial_set) < 0)
     158      {
     159        printf ("error: size %zu: setaffinity: %m\n", size);
     160        return true;
     161      }
     162  
     163    /* Use one-CPU set to test switching between CPUs.  */
     164    int last_active_cpu = -1;
     165    for (int cpu = 0; cpu <= conf->last_cpu; ++cpu)
     166      {
     167        int active_cpu = sched_getcpu ();
     168        if (last_active_cpu >= 0 && last_active_cpu != active_cpu)
     169  	{
     170  	  printf ("error: Unexpected CPU %d, expected %d\n",
     171  		  active_cpu, last_active_cpu);
     172  	  return false;
     173  	}
     174  
     175        if (!CPU_ISSET_S (cpu, kernel_size, initial_set))
     176  	continue;
     177        last_active_cpu = cpu;
     178  
     179        CPU_ZERO_S (kernel_size, active_cpu_set);
     180        CPU_SET_S (cpu, kernel_size, active_cpu_set);
     181        if (setaffinity (kernel_size, active_cpu_set) < 0)
     182  	{
     183  	  printf ("error: size %zu: setaffinity (%d): %m\n", size, cpu);
     184  	  return false;
     185  	}
     186        active_cpu = sched_getcpu ();
     187        if (active_cpu != cpu)
     188  	{
     189  	  printf ("error: Unexpected CPU %d, expected %d\n", active_cpu, cpu);
     190  	  return false;
     191  	}
     192        unsigned int numa_cpu, numa_node;
     193        if (getcpu (&numa_cpu, &numa_node) != 0)
     194  	{
     195  	  printf ("error: getcpu: %m\n");
     196  	  return false;
     197  	}
     198        if ((unsigned int) active_cpu != numa_cpu)
     199  	{
     200  	  printf ("error: Unexpected CPU %d, expected %d\n",
     201  		  active_cpu, numa_cpu);
     202  	  return false;
     203  	}
     204        if (getaffinity (kernel_size, set2) < 0)
     205  	{
     206  	  printf ("error: size %zu: getaffinity (2): %m\n", size);
     207  	  return false;
     208  	}
     209        if (!CPU_EQUAL_S (kernel_size, active_cpu_set, set2))
     210  	{
     211  	  printf ("error: size %zu: CPU sets do not match\n", size);
     212  	  return false;
     213  	}
     214      }
     215  
     216    /* Test setting the all-ones set.  */
     217    for (int cpu = 0; cpu < size; ++cpu)
     218      CPU_SET_S (cpu, kernel_size, set2);
     219    if (setaffinity (kernel_size, set2) < 0)
     220      {
     221        printf ("error: size %zu: setaffinity (3): %m\n", size);
     222        return false;
     223      }
     224  
     225    if (setaffinity (kernel_size, initial_set) < 0)
     226      {
     227        printf ("error: size %zu: setaffinity (4): %m\n", size);
     228        return false;
     229      }
     230    if (getaffinity (kernel_size, set2) < 0)
     231      {
     232        printf ("error: size %zu: getaffinity (3): %m\n", size);
     233        return false;
     234      }
     235    if (!CPU_EQUAL_S (kernel_size, initial_set, set2))
     236      {
     237        printf ("error: size %zu: CPU sets do not match (2)\n", size);
     238        return false;
     239      }
     240  
     241    CPU_FREE (initial_set);
     242    CPU_FREE (set2);
     243    CPU_FREE (active_cpu_set);
     244  
     245    return true;
     246  }
     247  
     248  static int
     249  do_test (void)
     250  {
     251    {
     252      cpu_set_t set;
     253      if (getaffinity (sizeof (set), &set) < 0 && errno == ENOSYS)
     254        {
     255  	puts ("warning: getaffinity not supported, test cannot run");
     256  	return 0;
     257        }
     258      if (sched_getcpu () < 0 && errno == ENOSYS)
     259        {
     260  	puts ("warning: sched_getcpu not supported, test cannot run");
     261  	return 0;
     262        }
     263    }
     264  
     265    struct conf conf;
     266    setup_conf (&conf);
     267    /* Note: The CPU set size in bits can be less than the CPU count
     268       (and the maximum test CPU) because the userspace interface rounds
     269       up the bit count, and the rounded-up buffer size is passed into
     270       the kernel.  The kernel does not know that some of the buffer are
     271       actually padding, and writes data there.  */
     272    printf ("info: Detected CPU set size (in bits): %d\n", conf.set_size);
     273    printf ("info: Maximum test CPU: %d\n", conf.last_cpu);
     274    if (conf.set_size < 0 || conf.last_cpu < 0)
     275      return 1;
     276  
     277    if (!early_test (&conf))
     278      return 1;
     279  
     280    bool error = false;
     281    error |= !test_size (&conf, 1024);
     282    error |= !test_size (&conf, conf.set_size);
     283    error |= !test_size (&conf, 2);
     284    error |= !test_size (&conf, 32);
     285    error |= !test_size (&conf, 40);
     286    error |= !test_size (&conf, 64);
     287    error |= !test_size (&conf, 96);
     288    error |= !test_size (&conf, 128);
     289    error |= !test_size (&conf, 256);
     290    error |= !test_size (&conf, 8192);
     291    return error;
     292  }
     293  
     294  #define TEST_FUNCTION do_test ()
     295  #include "../test-skeleton.c"