1 /*- 2 * Copyright (c) 2013 Gleb Smirnoff <glebius@FreeBSD.org> 3 * Copyright (c) 2010 Juniper Networks, Inc. 4 * Copyright (c) 2009 Robert N. M. Watson 5 * Copyright (c) 2009 Bjoern A. Zeeb <bz@FreeBSD.org> 6 * Copyright (c) 2008 Yahoo!, Inc. 7 * All rights reserved. 8 * 9 * Written by: John Baldwin <jhb@FreeBSD.org> 10 * 11 * This software was developed by Robert N. M. Watson under contract 12 * to Juniper Networks, Inc. 13 * 14 * Redistribution and use in source and binary forms, with or without 15 * modification, are permitted provided that the following conditions 16 * are met: 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 2. Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * 3. Neither the name of the author nor the names of any co-contributors 23 * may be used to endorse or promote products derived from this software 24 * without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 */ 38 39 #include <sys/cdefs.h> 40 __FBSDID("$FreeBSD$"); 41 42 #include <sys/param.h> 43 #include <sys/pcpu.h> 44 #include <sys/sysctl.h> 45 #include <kvm.h> 46 #include <limits.h> 47 #include <stdlib.h> 48 49 #include "kvm_private.h" 50 51 static struct nlist kvm_pcpu_nl[] = { 52 { .n_name = "_cpuid_to_pcpu" }, 53 { .n_name = "_mp_maxcpus" }, 54 { .n_name = "_mp_ncpus" }, 55 { .n_name = NULL }, 56 }; 57 #define NL_CPUID_TO_PCPU 0 58 #define NL_MP_MAXCPUS 1 59 #define NL_MP_NCPUS 2 60 61 /* 62 * Kernel per-CPU data state. We cache this stuff on the first 63 * access. 64 * 65 * XXXRW: Possibly, this (and kvmpcpu_nl) should be per-kvm_t, in case the 66 * consumer has multiple handles in flight to differently configured 67 * kernels/crashdumps. 68 */ 69 static void **pcpu_data; 70 static int maxcpu; 71 static int mp_ncpus; 72 73 static int 74 _kvm_pcpu_init(kvm_t *kd) 75 { 76 size_t len; 77 int max; 78 void *data; 79 80 if (kvm_nlist(kd, kvm_pcpu_nl) < 0) 81 return (-1); 82 if (kvm_pcpu_nl[NL_CPUID_TO_PCPU].n_value == 0) { 83 _kvm_err(kd, kd->program, "unable to find cpuid_to_pcpu"); 84 return (-1); 85 } 86 if (kvm_pcpu_nl[NL_MP_MAXCPUS].n_value == 0) { 87 _kvm_err(kd, kd->program, "unable to find mp_maxcpus"); 88 return (-1); 89 } 90 if (kvm_read(kd, kvm_pcpu_nl[NL_MP_MAXCPUS].n_value, &max, 91 sizeof(max)) != sizeof(max)) { 92 _kvm_err(kd, kd->program, "cannot read mp_maxcpus"); 93 return (-1); 94 } 95 if (kvm_pcpu_nl[NL_MP_NCPUS].n_value == 0) { 96 _kvm_err(kd, kd->program, "unable to find mp_ncpus"); 97 return (-1); 98 } 99 if (kvm_read(kd, kvm_pcpu_nl[NL_MP_NCPUS].n_value, &mp_ncpus, 100 sizeof(mp_ncpus)) != sizeof(mp_ncpus)) { 101 _kvm_err(kd, kd->program, "cannot read mp_ncpus"); 102 return (-1); 103 } 104 len = max * sizeof(void *); 105 data = malloc(len); 106 if (data == NULL) { 107 _kvm_err(kd, kd->program, "out of memory"); 108 return (-1); 109 } 110 if (kvm_read(kd, kvm_pcpu_nl[NL_CPUID_TO_PCPU].n_value, data, len) != 111 (ssize_t)len) { 112 _kvm_err(kd, kd->program, "cannot read cpuid_to_pcpu array"); 113 free(data); 114 return (-1); 115 } 116 pcpu_data = data; 117 maxcpu = max; 118 return (0); 119 } 120 121 static void 122 _kvm_pcpu_clear(void) 123 { 124 125 maxcpu = 0; 126 free(pcpu_data); 127 pcpu_data = NULL; 128 } 129 130 void * 131 kvm_getpcpu(kvm_t *kd, int cpu) 132 { 133 char *buf; 134 135 if (kd == NULL) { 136 _kvm_pcpu_clear(); 137 return (NULL); 138 } 139 140 if (maxcpu == 0) 141 if (_kvm_pcpu_init(kd) < 0) 142 return ((void *)-1); 143 144 if (cpu >= maxcpu || pcpu_data[cpu] == NULL) 145 return (NULL); 146 147 buf = malloc(sizeof(struct pcpu)); 148 if (buf == NULL) { 149 _kvm_err(kd, kd->program, "out of memory"); 150 return ((void *)-1); 151 } 152 if (kvm_read(kd, (uintptr_t)pcpu_data[cpu], buf, 153 sizeof(struct pcpu)) != sizeof(struct pcpu)) { 154 _kvm_err(kd, kd->program, "unable to read per-CPU data"); 155 free(buf); 156 return ((void *)-1); 157 } 158 return (buf); 159 } 160 161 int 162 kvm_getmaxcpu(kvm_t *kd) 163 { 164 165 if (kd == NULL) { 166 _kvm_pcpu_clear(); 167 return (0); 168 } 169 170 if (maxcpu == 0) 171 if (_kvm_pcpu_init(kd) < 0) 172 return (-1); 173 return (maxcpu); 174 } 175 176 static int 177 _kvm_dpcpu_setcpu(kvm_t *kd, u_int cpu, int report_error) 178 { 179 180 if (!kd->dpcpu_initialized) { 181 if (report_error) 182 _kvm_err(kd, kd->program, "%s: not initialized", 183 __func__); 184 return (-1); 185 } 186 if (cpu >= kd->dpcpu_maxcpus) { 187 if (report_error) 188 _kvm_err(kd, kd->program, "%s: CPU %u too big", 189 __func__, cpu); 190 return (-1); 191 } 192 if (kd->dpcpu_off[cpu] == 0) { 193 if (report_error) 194 _kvm_err(kd, kd->program, "%s: CPU %u not found", 195 __func__, cpu); 196 return (-1); 197 } 198 kd->dpcpu_curcpu = cpu; 199 kd->dpcpu_curoff = kd->dpcpu_off[cpu]; 200 return (0); 201 } 202 203 /* 204 * Set up libkvm to handle dynamic per-CPU memory. 205 */ 206 static int 207 _kvm_dpcpu_init(kvm_t *kd) 208 { 209 struct nlist nl[] = { 210 #define NLIST_START_SET_PCPU 0 211 { .n_name = "___start_" DPCPU_SETNAME }, 212 #define NLIST_STOP_SET_PCPU 1 213 { .n_name = "___stop_" DPCPU_SETNAME }, 214 #define NLIST_DPCPU_OFF 2 215 { .n_name = "_dpcpu_off" }, 216 #define NLIST_MP_MAXCPUS 3 217 { .n_name = "_mp_maxcpus" }, 218 { .n_name = NULL }, 219 }; 220 uintptr_t *dpcpu_off_buf; 221 size_t len; 222 u_int dpcpu_maxcpus; 223 224 /* 225 * Locate and cache locations of important symbols using the internal 226 * version of _kvm_nlist, turning off initialization to avoid 227 * recursion in case of unresolveable symbols. 228 */ 229 if (_kvm_nlist(kd, nl, 0) != 0) 230 return (-1); 231 if (kvm_read(kd, nl[NLIST_MP_MAXCPUS].n_value, &dpcpu_maxcpus, 232 sizeof(dpcpu_maxcpus)) != sizeof(dpcpu_maxcpus)) 233 return (-1); 234 len = dpcpu_maxcpus * sizeof(*dpcpu_off_buf); 235 dpcpu_off_buf = malloc(len); 236 if (dpcpu_off_buf == NULL) 237 return (-1); 238 if (kvm_read(kd, nl[NLIST_DPCPU_OFF].n_value, dpcpu_off_buf, len) != 239 (ssize_t)len) { 240 free(dpcpu_off_buf); 241 return (-1); 242 } 243 kd->dpcpu_start = nl[NLIST_START_SET_PCPU].n_value; 244 kd->dpcpu_stop = nl[NLIST_STOP_SET_PCPU].n_value; 245 kd->dpcpu_maxcpus = dpcpu_maxcpus; 246 kd->dpcpu_off = dpcpu_off_buf; 247 kd->dpcpu_initialized = 1; 248 (void)_kvm_dpcpu_setcpu(kd, 0, 0); 249 return (0); 250 } 251 252 /* 253 * Check whether the dpcpu module has been initialized sucessfully or not, 254 * initialize it if permitted. 255 */ 256 int 257 _kvm_dpcpu_initialized(kvm_t *kd, int intialize) 258 { 259 260 if (kd->dpcpu_initialized || !intialize) 261 return (kd->dpcpu_initialized); 262 263 (void)_kvm_dpcpu_init(kd); 264 265 return (kd->dpcpu_initialized); 266 } 267 268 /* 269 * Check whether the value is within the dpcpu symbol range and only if so 270 * adjust the offset relative to the current offset. 271 */ 272 uintptr_t 273 _kvm_dpcpu_validaddr(kvm_t *kd, uintptr_t value) 274 { 275 276 if (value == 0) 277 return (value); 278 279 if (!kd->dpcpu_initialized) 280 return (value); 281 282 if (value < kd->dpcpu_start || value >= kd->dpcpu_stop) 283 return (value); 284 285 return (kd->dpcpu_curoff + value); 286 } 287 288 int 289 kvm_dpcpu_setcpu(kvm_t *kd, u_int cpu) 290 { 291 int ret; 292 293 if (!kd->dpcpu_initialized) { 294 ret = _kvm_dpcpu_init(kd); 295 if (ret != 0) { 296 _kvm_err(kd, kd->program, "%s: init failed", 297 __func__); 298 return (ret); 299 } 300 } 301 302 return (_kvm_dpcpu_setcpu(kd, cpu, 1)); 303 } 304 305 /* 306 * Obtain a per-CPU copy for given cpu from UMA_ZONE_PCPU allocation. 307 */ 308 ssize_t 309 kvm_read_zpcpu(kvm_t *kd, void *buf, u_long base, size_t size, int cpu) 310 { 311 312 return (kvm_read(kd, (uintptr_t)(base + sizeof(struct pcpu) * cpu), 313 buf, size)); 314 } 315 316 /* 317 * Fetch value of a counter(9). 318 */ 319 uint64_t 320 kvm_counter_u64_fetch(kvm_t *kd, u_long base) 321 { 322 uint64_t r, c; 323 324 if (mp_ncpus == 0) 325 if (_kvm_pcpu_init(kd) < 0) 326 return (0); 327 328 r = 0; 329 for (int i = 0; i < mp_ncpus; i++) { 330 if (kvm_read_zpcpu(kd, &c, base, sizeof(c), i) != sizeof(c)) 331 return (0); 332 r += c; 333 } 334 335 return (r); 336 } 337