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