Home | History | Annotate | Line # | Download | only in src
      1  1.1  christos /*
      2  1.1  christos  * SPDX-License-Identifier: MIT
      3  1.1  christos  *
      4  1.1  christos  * Copyright (C) 2011-2012 Mathieu Desnoyers <mathieu.desnoyers (at) efficios.com>
      5  1.1  christos  * Copyright (C) 2019 Michael Jeanson <mjeanson (at) efficios.com>
      6  1.1  christos  */
      7  1.1  christos 
      8  1.1  christos #include <assert.h>
      9  1.1  christos #include <ctype.h>
     10  1.1  christos #include <dirent.h>
     11  1.1  christos #include <errno.h>
     12  1.1  christos #include <fcntl.h>
     13  1.1  christos #include <limits.h>
     14  1.1  christos #include <pthread.h>
     15  1.1  christos #include <stdio.h>
     16  1.1  christos #include <stdlib.h>
     17  1.1  christos #include <string.h>
     18  1.1  christos #include <unistd.h>
     19  1.1  christos 
     20  1.1  christos #include <urcu/compiler.h>
     21  1.1  christos 
     22  1.1  christos #define URCU_CPUMASK_SIZE 4096
     23  1.1  christos 
     24  1.1  christos #if defined(HAVE_SYSCONF)
     25  1.1  christos static inline int get_num_possible_cpus_sysconf(void)
     26  1.1  christos {
     27  1.1  christos 	return sysconf(_SC_NPROCESSORS_CONF);
     28  1.1  christos }
     29  1.1  christos #else
     30  1.1  christos /*
     31  1.1  christos  * On platforms without sysconf(), always return -1.
     32  1.1  christos  */
     33  1.1  christos static inline int get_num_possible_cpus_sysconf(void)
     34  1.1  christos {
     35  1.1  christos 	return -1;
     36  1.1  christos }
     37  1.1  christos #endif
     38  1.1  christos 
     39  1.1  christos #ifdef __linux__
     40  1.1  christos /*
     41  1.1  christos  * Get the highest CPU id from sysfs.
     42  1.1  christos  *
     43  1.1  christos  * Iterate on all the folders in "/sys/devices/system/cpu" that start with
     44  1.1  christos  * "cpu" followed by an integer, keep the highest CPU id encountered during
     45  1.1  christos  * this iteration and add 1 to get a number of CPUs.
     46  1.1  christos  *
     47  1.1  christos  * Returns the highest CPU id, or -1 on error.
     48  1.1  christos  */
     49  1.1  christos static inline int _get_max_cpuid_from_sysfs(const char *path)
     50  1.1  christos {
     51  1.1  christos 	long max_cpuid = -1;
     52  1.1  christos 
     53  1.1  christos 	DIR *cpudir;
     54  1.1  christos 	struct dirent *entry;
     55  1.1  christos 
     56  1.1  christos 	assert(path);
     57  1.1  christos 
     58  1.1  christos 	cpudir = opendir(path);
     59  1.1  christos 	if (cpudir == NULL)
     60  1.1  christos 		goto end;
     61  1.1  christos 
     62  1.1  christos 	/*
     63  1.1  christos 	 * Iterate on all directories named "cpu" followed by an integer.
     64  1.1  christos 	 */
     65  1.1  christos 	while ((entry = readdir(cpudir))) {
     66  1.1  christos 		if (entry->d_type == DT_DIR &&
     67  1.1  christos 			strncmp(entry->d_name, "cpu", 3) == 0) {
     68  1.1  christos 
     69  1.1  christos 			char *endptr;
     70  1.1  christos 			long cpu_id;
     71  1.1  christos 
     72  1.1  christos 			cpu_id = strtol(entry->d_name + 3, &endptr, 10);
     73  1.1  christos 			if ((cpu_id < LONG_MAX) && (endptr != entry->d_name + 3)
     74  1.1  christos 					&& (*endptr == '\0')) {
     75  1.1  christos 				if (cpu_id > max_cpuid)
     76  1.1  christos 					max_cpuid = cpu_id;
     77  1.1  christos 			}
     78  1.1  christos 		}
     79  1.1  christos 	}
     80  1.1  christos 
     81  1.1  christos 	if (closedir(cpudir))
     82  1.1  christos 		perror("closedir");
     83  1.1  christos 
     84  1.1  christos 	/*
     85  1.1  christos 	 * If the max CPU id is out of bound, set it to -1 so it results in a
     86  1.1  christos 	 * CPU num of 0.
     87  1.1  christos 	 */
     88  1.1  christos 	if (max_cpuid < 0 || max_cpuid > INT_MAX)
     89  1.1  christos 		max_cpuid = -1;
     90  1.1  christos 
     91  1.1  christos end:
     92  1.1  christos 	return max_cpuid;
     93  1.1  christos }
     94  1.1  christos 
     95  1.1  christos static inline int get_max_cpuid_from_sysfs(void)
     96  1.1  christos {
     97  1.1  christos 	return _get_max_cpuid_from_sysfs("/sys/devices/system/cpu");
     98  1.1  christos }
     99  1.1  christos 
    100  1.1  christos 
    101  1.1  christos /*
    102  1.1  christos  * As a fallback to parsing the CPU mask in "/sys/devices/system/cpu/possible",
    103  1.1  christos  * iterate on all the folders in "/sys/devices/system/cpu" that start with
    104  1.1  christos  * "cpu" followed by an integer, keep the highest CPU id encountered during
    105  1.1  christos  * this iteration and add 1 to get a number of CPUs.
    106  1.1  christos  *
    107  1.1  christos  * Then get the value from sysconf(_SC_NPROCESSORS_CONF) as a fallback and
    108  1.1  christos  * return the highest one.
    109  1.1  christos  *
    110  1.1  christos  * On Linux, using the value from sysconf can be unreliable since the way it
    111  1.1  christos  * counts CPUs varies between C libraries and even between versions of the same
    112  1.1  christos  * library. If we used it directly, getcpu() could return a value greater than
    113  1.1  christos  * this sysconf, in which case the arrays indexed by processor would overflow.
    114  1.1  christos  *
    115  1.1  christos  * As another example, the MUSL libc implementation of the _SC_NPROCESSORS_CONF
    116  1.1  christos  * sysconf does not return the number of configured CPUs in the system but
    117  1.1  christos  * relies on the cpu affinity mask of the current task.
    118  1.1  christos  *
    119  1.1  christos  * Returns 0 or less on error.
    120  1.1  christos  */
    121  1.1  christos static inline int get_num_possible_cpus_fallback(void)
    122  1.1  christos {
    123  1.1  christos 	/*
    124  1.1  christos 	 * Get the sysconf value as a last resort. Keep the highest number.
    125  1.1  christos 	 */
    126  1.1  christos 	return caa_max(get_num_possible_cpus_sysconf(), get_max_cpuid_from_sysfs() + 1);
    127  1.1  christos }
    128  1.1  christos 
    129  1.1  christos /*
    130  1.1  christos  * Get a CPU mask string from sysfs.
    131  1.1  christos  *
    132  1.1  christos  * buf: the buffer where the mask will be read.
    133  1.1  christos  * max_bytes: the maximum number of bytes to write in the buffer.
    134  1.1  christos  * path: file path to read the mask from.
    135  1.1  christos  *
    136  1.1  christos  * Returns the number of bytes read or -1 on error.
    137  1.1  christos  */
    138  1.1  christos static inline int get_cpu_mask_from_sysfs(char *buf, size_t max_bytes, const char *path)
    139  1.1  christos {
    140  1.1  christos 	ssize_t bytes_read = 0;
    141  1.1  christos 	size_t total_bytes_read = 0;
    142  1.1  christos 	int fd = -1, ret = -1;
    143  1.1  christos 
    144  1.1  christos 	assert(path);
    145  1.1  christos 
    146  1.1  christos 	if (buf == NULL)
    147  1.1  christos 		goto end;
    148  1.1  christos 
    149  1.1  christos 	fd = open(path, O_RDONLY);
    150  1.1  christos 	if (fd < 0)
    151  1.1  christos 		goto end;
    152  1.1  christos 
    153  1.1  christos 	do {
    154  1.1  christos 		bytes_read = read(fd, buf + total_bytes_read,
    155  1.1  christos 				max_bytes - total_bytes_read);
    156  1.1  christos 
    157  1.1  christos 		if (bytes_read < 0) {
    158  1.1  christos 			if (errno == EINTR) {
    159  1.1  christos 				continue;	/* retry operation */
    160  1.1  christos 			} else {
    161  1.1  christos 				goto end;
    162  1.1  christos 			}
    163  1.1  christos 		}
    164  1.1  christos 
    165  1.1  christos 		total_bytes_read += bytes_read;
    166  1.1  christos 		assert(total_bytes_read <= max_bytes);
    167  1.1  christos 	} while (max_bytes > total_bytes_read && bytes_read != 0);
    168  1.1  christos 
    169  1.1  christos 	/*
    170  1.1  christos 	 * Make sure the mask read is a null terminated string.
    171  1.1  christos 	 */
    172  1.1  christos 	if (total_bytes_read < max_bytes)
    173  1.1  christos 		buf[total_bytes_read] = '\0';
    174  1.1  christos 	else
    175  1.1  christos 		buf[max_bytes - 1] = '\0';
    176  1.1  christos 
    177  1.1  christos 	if (total_bytes_read > INT_MAX)
    178  1.1  christos 		goto end;
    179  1.1  christos 
    180  1.1  christos 	ret = (int) total_bytes_read;
    181  1.1  christos 
    182  1.1  christos end:
    183  1.1  christos 	if (fd >= 0 && close(fd) < 0)
    184  1.1  christos 		perror("close");
    185  1.1  christos 
    186  1.1  christos 	return ret;
    187  1.1  christos }
    188  1.1  christos 
    189  1.1  christos /*
    190  1.1  christos  * Get the CPU possible mask string from sysfs.
    191  1.1  christos  *
    192  1.1  christos  * buf: the buffer where the mask will be read.
    193  1.1  christos  * max_bytes: the maximum number of bytes to write in the buffer.
    194  1.1  christos  *
    195  1.1  christos  * Returns the number of bytes read or -1 on error.
    196  1.1  christos  */
    197  1.1  christos static inline int get_possible_cpu_mask_from_sysfs(char *buf, size_t max_bytes)
    198  1.1  christos {
    199  1.1  christos 	return get_cpu_mask_from_sysfs(buf, max_bytes,
    200  1.1  christos 			"/sys/devices/system/cpu/possible");
    201  1.1  christos }
    202  1.1  christos 
    203  1.1  christos /*
    204  1.1  christos  * Get the highest CPU id from the possible CPU mask.
    205  1.1  christos  *
    206  1.1  christos  * pmask: the mask to parse.
    207  1.1  christos  * len: the len of the mask excluding '\0'.
    208  1.1  christos  *
    209  1.1  christos  * Returns the highest CPU id from the mask or -1 on error.
    210  1.1  christos  */
    211  1.1  christos static inline int get_max_cpuid_from_mask(const char *pmask, size_t len)
    212  1.1  christos {
    213  1.1  christos 	ssize_t i;
    214  1.1  christos 	unsigned long cpu_index;
    215  1.1  christos 	char *endptr;
    216  1.1  christos 
    217  1.1  christos 	/* We need at least one char to read */
    218  1.1  christos 	if (len < 1)
    219  1.1  christos 		goto error;
    220  1.1  christos 
    221  1.1  christos 	/* Start from the end to read the last CPU index. */
    222  1.1  christos 	for (i = len - 1; i > 0; i--) {
    223  1.1  christos 		/* Break when we hit the first separator. */
    224  1.1  christos 		if ((pmask[i] == ',') || (pmask[i] == '-')) {
    225  1.1  christos 			i++;
    226  1.1  christos 			break;
    227  1.1  christos 		}
    228  1.1  christos 	}
    229  1.1  christos 
    230  1.1  christos 	cpu_index = strtoul(&pmask[i], &endptr, 10);
    231  1.1  christos 
    232  1.1  christos 	if ((&pmask[i] != endptr) && (cpu_index < INT_MAX))
    233  1.1  christos 		return (int) cpu_index;
    234  1.1  christos 
    235  1.1  christos error:
    236  1.1  christos 	return -1;
    237  1.1  christos }
    238  1.1  christos 
    239  1.1  christos /*
    240  1.1  christos  * On Linux try sysfs first and fallback to sysconf.
    241  1.1  christos  */
    242  1.1  christos static inline int get_possible_cpus_array_len(void)
    243  1.1  christos {
    244  1.1  christos 	int ret;
    245  1.1  christos 	char buf[URCU_CPUMASK_SIZE];
    246  1.1  christos 
    247  1.1  christos 	/* Get the possible cpu mask from sysfs, fallback to sysconf. */
    248  1.1  christos 	ret = get_possible_cpu_mask_from_sysfs((char *) &buf, URCU_CPUMASK_SIZE);
    249  1.1  christos 	if (ret <= 0)
    250  1.1  christos 		goto fallback;
    251  1.1  christos 
    252  1.1  christos 	/* Parse the possible cpu mask, on failure fallback to sysconf. */
    253  1.1  christos 	ret = get_max_cpuid_from_mask((char *) &buf, ret);
    254  1.1  christos 	if (ret >= 0) {
    255  1.1  christos 		/* Add 1 to convert from max cpuid to an array len. */
    256  1.1  christos 		ret++;
    257  1.1  christos 		goto end;
    258  1.1  christos 	}
    259  1.1  christos 
    260  1.1  christos fallback:
    261  1.1  christos 	/* Fallback to sysconf. */
    262  1.1  christos 	ret = get_num_possible_cpus_fallback();
    263  1.1  christos 
    264  1.1  christos end:
    265  1.1  christos 	return ret;
    266  1.1  christos }
    267  1.1  christos #else
    268  1.1  christos /*
    269  1.1  christos  * On other platforms, only use sysconf.
    270  1.1  christos  */
    271  1.1  christos static inline int get_possible_cpus_array_len(void)
    272  1.1  christos {
    273  1.1  christos 	return get_num_possible_cpus_sysconf();
    274  1.1  christos }
    275  1.1  christos #endif
    276