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