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