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