1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27
28#include <errno.h>
29#include <limits.h>
30#include <strings.h>
31#include <unistd.h>
32#include <topo_error.h>
33#include <fm/topo_mod.h>
34#include <sys/fm/protocol.h>
35
36#include <topo_method.h>
37#include <cpu.h>
38
39/*
40 * platform specific cpu module
41 */
42#define	PLATFORM_CPU_VERSION	CPU_VERSION
43#define	PLATFORM_CPU_NAME	"platform-cpu"
44
45static int cpu_enum(topo_mod_t *, tnode_t *, const char *, topo_instance_t,
46    topo_instance_t, void *, void *);
47static void cpu_release(topo_mod_t *, tnode_t *);
48static int cpu_nvl2str(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
49    nvlist_t **);
50static int cpu_str2nvl(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
51    nvlist_t **);
52static int cpu_fmri_asru(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
53    nvlist_t **);
54static int cpu_fmri_create_meth(topo_mod_t *, tnode_t *, topo_version_t,
55    nvlist_t *, nvlist_t **);
56static nvlist_t *fmri_create(topo_mod_t *, uint32_t, uint8_t, char *);
57
58static const topo_method_t cpu_methods[] = {
59	{ TOPO_METH_NVL2STR, TOPO_METH_NVL2STR_DESC, TOPO_METH_NVL2STR_VERSION,
60	    TOPO_STABILITY_INTERNAL, cpu_nvl2str },
61	{ TOPO_METH_STR2NVL, TOPO_METH_STR2NVL_DESC, TOPO_METH_STR2NVL_VERSION,
62	    TOPO_STABILITY_INTERNAL, cpu_str2nvl },
63	{ TOPO_METH_ASRU_COMPUTE, TOPO_METH_ASRU_COMPUTE_DESC,
64	    TOPO_METH_ASRU_COMPUTE_VERSION, TOPO_STABILITY_INTERNAL,
65	    cpu_fmri_asru },
66	{ TOPO_METH_FMRI, TOPO_METH_FMRI_DESC, TOPO_METH_FMRI_VERSION,
67	    TOPO_STABILITY_INTERNAL, cpu_fmri_create_meth },
68	{ NULL }
69};
70
71static const topo_modops_t cpu_ops =
72	{ cpu_enum, cpu_release };
73
74static const topo_modinfo_t cpu_info =
75	{ "cpu", FM_FMRI_SCHEME_CPU, CPU_VERSION, &cpu_ops };
76
77int
78cpu_init(topo_mod_t *mod, topo_version_t version)
79{
80	cpu_node_t *cpuip;
81
82	if (getenv("TOPOCPUDEBUG"))
83		topo_mod_setdebug(mod);
84	topo_mod_dprintf(mod, "initializing cpu builtin\n");
85
86	if (version != CPU_VERSION)
87		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
88
89	if ((cpuip = topo_mod_zalloc(mod, sizeof (cpu_node_t))) == NULL)
90		return (topo_mod_seterrno(mod, EMOD_NOMEM));
91
92	if ((cpuip->cn_kc = kstat_open()) == NULL) {
93		topo_mod_dprintf(mod, "kstat_open failed: %s\n",
94		    strerror(errno));
95		topo_mod_free(mod, cpuip, sizeof (cpu_node_t));
96		return (-1);
97	}
98
99	cpuip->cn_ncpustats = sysconf(_SC_CPUID_MAX);
100	if ((cpuip->cn_cpustats = topo_mod_zalloc(mod, (
101	    cpuip->cn_ncpustats + 1) * sizeof (kstat_t *))) == NULL) {
102		(void) kstat_close(cpuip->cn_kc);
103		topo_mod_free(mod, cpuip, sizeof (cpu_node_t));
104		return (-1);
105	}
106
107	if (topo_mod_register(mod, &cpu_info, TOPO_VERSION) != 0) {
108		topo_mod_dprintf(mod, "failed to register cpu_info: "
109		    "%s\n", topo_mod_errmsg(mod));
110		topo_mod_free(mod, cpuip->cn_cpustats,
111		    (cpuip->cn_ncpustats + 1) * sizeof (kstat_t *));
112		(void) kstat_close(cpuip->cn_kc);
113		topo_mod_free(mod, cpuip, sizeof (cpu_node_t));
114		return (-1);
115	}
116
117	topo_mod_setspecific(mod, (void *)cpuip);
118
119	return (0);
120}
121
122void
123cpu_fini(topo_mod_t *mod)
124{
125	cpu_node_t *cpuip;
126
127	cpuip = topo_mod_getspecific(mod);
128
129	if (cpuip->cn_cpustats != NULL)
130		topo_mod_free(mod, cpuip->cn_cpustats,
131		    (cpuip->cn_ncpustats + 1) * sizeof (kstat_t *));
132
133	(void) kstat_close(cpuip->cn_kc);
134	topo_mod_free(mod, cpuip, sizeof (cpu_node_t));
135
136	topo_mod_unregister(mod);
137}
138
139static int
140cpu_kstat_init(cpu_node_t *cpuip, int i)
141{
142	kstat_t *ksp;
143
144	if (cpuip->cn_cpustats[i] == NULL) {
145		if ((ksp = kstat_lookup(cpuip->cn_kc, "cpu_info", i, NULL)) ==
146		    NULL || kstat_read(cpuip->cn_kc, ksp, NULL) < 0)
147			return (-1);
148
149		cpuip->cn_cpustats[i] = ksp;
150	} else {
151		ksp = cpuip->cn_cpustats[i];
152	}
153
154	return (ksp->ks_instance);
155}
156
157/*ARGSUSED*/
158static int
159cpu_create(topo_mod_t *mod, tnode_t *rnode, const char *name,
160    topo_instance_t min, topo_instance_t max, cpu_node_t *cpuip)
161{
162	int i;
163	processorid_t cpu_id;
164	char *s, sbuf[21];
165	kstat_named_t *ks;
166	nvlist_t *fmri;
167
168	for (i = 0; i <= cpuip->cn_ncpustats; i++) {
169
170		if ((cpu_id = cpu_kstat_init(cpuip, i)) < 0)
171			continue;
172
173		if ((ks = kstat_data_lookup(cpuip->cn_cpustats[i],
174		    "device_ID")) != NULL) {
175			(void) snprintf(sbuf, 21, "%llX", ks->value.ui64);
176			s = sbuf;
177		} else {
178			s = NULL;
179		}
180
181		if ((fmri = fmri_create(mod, cpu_id, 0, s)) == NULL)
182			continue;
183		(void) topo_node_bind(mod, rnode, name, cpu_id, fmri);
184		nvlist_free(fmri);
185	}
186
187	return (0);
188}
189
190
191/*ARGSUSED*/
192static int
193cpu_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
194    topo_instance_t min, topo_instance_t max, void *arg, void *notused2)
195{
196	topo_mod_t *nmp;
197	cpu_node_t *cpuip = (cpu_node_t *)arg;
198
199	if ((nmp = topo_mod_load(mod, PLATFORM_CPU_NAME,
200	    PLATFORM_CPU_VERSION)) == NULL) {
201		if (topo_mod_errno(mod) == ETOPO_MOD_NOENT) {
202			/*
203			 * There is no platform specific cpu module, so use
204			 * the default enumeration with kstats of this builtin
205			 * cpu module.
206			 */
207			if (topo_node_range_create(mod, pnode, name, 0,
208			    cpuip->cn_ncpustats + 1) < 0) {
209				topo_mod_dprintf(mod,
210				    "cpu enumeration failed to create "
211				    "cpu range [0-%d]: %s\n",
212				    cpuip->cn_ncpustats + 1,
213				    topo_mod_errmsg(mod));
214				return (-1); /* mod_errno set */
215			}
216			(void) topo_method_register(mod, pnode, cpu_methods);
217			return (cpu_create(mod, pnode, name, min, max, cpuip));
218
219		} else {
220			/* Fail to load the module */
221			topo_mod_dprintf(mod,
222			    "Failed to load module %s: %s",
223			    PLATFORM_CPU_NAME,
224			    topo_mod_errmsg(mod));
225			return (-1);
226		}
227	}
228
229	if (topo_mod_enumerate(nmp, pnode, PLATFORM_CPU_NAME, name,
230	    min, max, NULL) < 0) {
231		topo_mod_dprintf(mod,
232		    "%s failed to enumerate: %s",
233		    PLATFORM_CPU_NAME,
234		    topo_mod_errmsg(mod));
235		return (-1);
236	}
237	(void) topo_method_register(mod, pnode, cpu_methods);
238
239	return (0);
240}
241
242static void
243cpu_release(topo_mod_t *mod, tnode_t *node)
244{
245	topo_method_unregister_all(mod, node);
246}
247
248ssize_t
249fmri_nvl2str(nvlist_t *nvl, uint8_t version, char *buf, size_t buflen)
250{
251	int rc;
252	uint8_t	type;
253	uint32_t cpuid, way;
254	uint32_t	index;
255	uint16_t	bit;
256	uint64_t serint;
257	char *serstr = NULL;
258
259	if (version == CPU_SCHEME_VERSION0) {
260		if (nvlist_lookup_uint32(nvl, FM_FMRI_CPU_ID, &cpuid) != 0 ||
261		    nvlist_lookup_uint64(nvl, FM_FMRI_CPU_SERIAL_ID, &serint)
262		    != 0)
263			return (0);
264
265		return (snprintf(buf, buflen, "cpu:///%s=%u/%s=%llX",
266		    FM_FMRI_CPU_ID, cpuid, FM_FMRI_CPU_SERIAL_ID,
267		    (u_longlong_t)serint));
268
269	} else if (version == CPU_SCHEME_VERSION1) {
270		if (nvlist_lookup_uint32(nvl, FM_FMRI_CPU_ID, &cpuid) != 0)
271			return (0);
272
273		/*
274		 * Serial number is an optional element
275		 */
276		if ((rc = nvlist_lookup_string(nvl, FM_FMRI_CPU_SERIAL_ID,
277		    &serstr)) != 0)
278
279			if (rc != ENOENT)
280				return (0);
281
282		/*
283		 * Cache index, way and type are optional elements
284		 * But if we have one of them, we must have them all.
285		 */
286		rc = nvlist_lookup_uint32(nvl, FM_FMRI_CPU_CACHE_INDEX,
287		    &index);
288		rc |= nvlist_lookup_uint32(nvl, FM_FMRI_CPU_CACHE_WAY, &way);
289		rc |= nvlist_lookup_uint16(nvl, FM_FMRI_CPU_CACHE_BIT, &bit);
290		rc |= nvlist_lookup_uint8(nvl, FM_FMRI_CPU_CACHE_TYPE, &type);
291
292		/* Insure there were no errors accessing the nvl */
293		if (rc != 0 && rc != ENOENT)
294			return (0);
295
296		if (serstr == NULL) {
297			/* If we have a serial string and no cache info */
298			if (rc == ENOENT)
299				return (snprintf(buf, buflen, "cpu:///%s=%u",
300				    FM_FMRI_CPU_ID, cpuid));
301			else {
302				return (snprintf(buf, buflen,
303				    "cpu:///%s=%u/%s=%u/%s=%u/%s=%d/%s=%d",
304				    FM_FMRI_CPU_ID, cpuid,
305				    FM_FMRI_CPU_CACHE_INDEX, index,
306				    FM_FMRI_CPU_CACHE_WAY, way,
307				    FM_FMRI_CPU_CACHE_BIT, bit,
308				    FM_FMRI_CPU_CACHE_TYPE, type));
309			}
310		} else {
311			if (rc == ENOENT) {
312				return (snprintf(buf, buflen,
313				    "cpu:///%s=%u/%s=%s",
314				    FM_FMRI_CPU_ID, cpuid,
315				    FM_FMRI_CPU_SERIAL_ID, serstr));
316			} else {
317				return (snprintf(buf, buflen,
318				"cpu:///%s=%u/%s=%s/%s=%u/%s=%u/%s=%d/%s=%d",
319				    FM_FMRI_CPU_ID, cpuid,
320				    FM_FMRI_CPU_SERIAL_ID, serstr,
321				    FM_FMRI_CPU_CACHE_INDEX, index,
322				    FM_FMRI_CPU_CACHE_WAY, way,
323				    FM_FMRI_CPU_CACHE_BIT, bit,
324				    FM_FMRI_CPU_CACHE_TYPE, type));
325			}
326		}
327	} else
328		return (0);
329}
330
331/*ARGSUSED*/
332static int
333cpu_nvl2str(topo_mod_t *mod, tnode_t *node, topo_version_t version,
334    nvlist_t *in, nvlist_t **out)
335{
336	uint8_t fver;
337	ssize_t len;
338	char *name;
339
340	if (version > TOPO_METH_NVL2STR_VERSION)
341		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
342
343	if (nvlist_lookup_uint8(in, FM_VERSION, &fver) != 0)
344		return (topo_mod_seterrno(mod, EMOD_FMRI_VERSION));
345
346	if ((len = fmri_nvl2str(in, fver, NULL, 0)) == 0 ||
347	    (name = topo_mod_alloc(mod, len + 1)) == NULL ||
348	    fmri_nvl2str(in, fver, name, len + 1) == 0)
349		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
350
351	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) < 0) {
352		topo_mod_free(mod, name, len + 1);
353		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
354	}
355
356	if (nvlist_add_string(*out, "fmri-string", name) != 0) {
357		topo_mod_free(mod, name, len + 1);
358		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
359	}
360	topo_mod_free(mod, name, len + 1);
361
362	return (0);
363}
364
365/*ARGSUSED*/
366static int
367cpu_str2nvl(topo_mod_t *mod, tnode_t *node, topo_version_t version,
368    nvlist_t *in, nvlist_t **out)
369{
370	int err;
371	ulong_t cpuid;
372	uint8_t	type = 0;
373	uint32_t way = 0;
374	uint32_t index = 0;
375	int	index_present = 0;
376	uint16_t	bit = 0;
377	char *str, *s, *end, *serial_end;
378	char *serial = NULL;
379	nvlist_t *fmri;
380
381	if (version > TOPO_METH_STR2NVL_VERSION)
382		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
383
384	if (nvlist_lookup_string(in, "fmri-string", &str) != 0)
385		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
386
387	/* We're expecting a string version of a cpu scheme FMRI */
388	if (strncmp(str, "cpu:///", 7) != 0)
389		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
390
391	s = strchr(str + 7, '=');
392	if (s == NULL)
393		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
394
395	++s;
396	cpuid = strtoul(s, &end, 0);
397
398	if (cpuid == ULONG_MAX && errno == ERANGE)
399		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
400
401	/* If there is a serial #, then there might also be cache data */
402	if (*(s = end) == '/') {
403		s = strchr(s, '=');
404		++s;
405		serial = s;
406		serial_end = strchr(s, '/');
407		/* If there is cache data, all must be present */
408		if (serial_end != NULL) {
409			/* Now terminate the serial string */
410			*serial_end = '\0';
411			index_present = 1;
412			s = serial_end + 1;
413			s = strchr(s, '=');
414			++s;
415			index = strtoul(s, &end, 0);
416			if (*(s = end) != '/') {
417				return (topo_mod_seterrno(mod,
418				    EMOD_FMRI_MALFORM));
419			}
420			s = strchr(s, '=');
421			if (s == NULL) {
422				return (topo_mod_seterrno(mod,
423				    EMOD_FMRI_MALFORM));
424			}
425			++s;
426			way = strtoul(s, &end, 0);
427			if (*(s = end) != '/') {
428				return (topo_mod_seterrno(mod,
429				    EMOD_FMRI_MALFORM));
430			}
431			s = strchr(s, '=');
432			if (s == NULL) {
433				return (topo_mod_seterrno(mod,
434				    EMOD_FMRI_MALFORM));
435			}
436			++s;
437			bit = strtoul(s, &end, 0);
438			if (*(s = end) != '/') {
439				return (topo_mod_seterrno(mod,
440				    EMOD_FMRI_MALFORM));
441			}
442			s = strchr(s, '=');
443			if (s == NULL) {
444				return (topo_mod_seterrno(mod,
445				    EMOD_FMRI_MALFORM));
446			}
447			++s;
448			type = strtoul(s, &end, 0);
449		}
450
451	}
452	if (topo_mod_nvalloc(mod, &fmri, NV_UNIQUE_NAME) != 0)
453		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
454
455	err = nvlist_add_uint8(fmri, FM_VERSION, CPU_SCHEME_VERSION1);
456	err |= nvlist_add_string(fmri, FM_FMRI_SCHEME, FM_FMRI_SCHEME_CPU);
457	err |= nvlist_add_uint32(fmri, FM_FMRI_CPU_ID, (uint32_t)cpuid);
458	err |= nvlist_add_uint8(fmri, FM_FMRI_CPU_MASK, 0);
459	if (serial != NULL)
460		err |= nvlist_add_string(fmri, FM_FMRI_CPU_SERIAL_ID,
461		    serial);
462
463	if (index_present) {
464		err |= nvlist_add_uint32(fmri, FM_FMRI_CPU_CACHE_INDEX,
465		    index);
466		err |= nvlist_add_uint32(fmri, FM_FMRI_CPU_CACHE_WAY,
467		    way);
468		err |= nvlist_add_uint16(fmri, FM_FMRI_CPU_CACHE_BIT,
469		    bit);
470		err |= nvlist_add_uint8(fmri, FM_FMRI_CPU_CACHE_TYPE,
471		    type);
472	}
473	if (err != 0) {
474		nvlist_free(fmri);
475		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
476	}
477	*out = fmri;
478
479	return (0);
480}
481
482static nvlist_t *
483fmri_create(topo_mod_t *mod, uint32_t cpu_id, uint8_t cpumask, char *s)
484{
485	int err;
486	nvlist_t *fmri;
487
488	if (topo_mod_nvalloc(mod, &fmri, NV_UNIQUE_NAME) != 0) {
489		(void) topo_mod_seterrno(mod, EMOD_FMRI_NVL);
490		return (NULL);
491	}
492
493	err = nvlist_add_uint8(fmri, FM_VERSION, FM_CPU_SCHEME_VERSION);
494	err |= nvlist_add_string(fmri, FM_FMRI_SCHEME, FM_FMRI_SCHEME_CPU);
495	err |= nvlist_add_uint32(fmri, FM_FMRI_CPU_ID, cpu_id);
496	err |= nvlist_add_uint8(fmri, FM_FMRI_CPU_MASK, cpumask);
497	if (s != NULL)
498		err |= nvlist_add_string(fmri, FM_FMRI_CPU_SERIAL_ID, s);
499	if (err != 0) {
500		nvlist_free(fmri);
501		(void) topo_mod_seterrno(mod, EMOD_FMRI_NVL);
502		return (NULL);
503	}
504
505	return (fmri);
506}
507
508/*ARGSUSED*/
509static int
510cpu_fmri_asru(topo_mod_t *mod, tnode_t *node, topo_version_t version,
511    nvlist_t *in, nvlist_t **out)
512{
513	int rc;
514	uint32_t cpu_id;
515	uint8_t cpumask = 0;
516	char *serial = NULL;
517
518	if ((rc = nvlist_lookup_uint32(in, FM_FMRI_CPU_ID, &cpu_id)) != 0) {
519		if (rc == ENOENT)
520			return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
521		else
522			return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
523	}
524
525	(void) nvlist_lookup_string(in, FM_FMRI_CPU_SERIAL_ID, &serial);
526	(void) nvlist_lookup_uint8(in, FM_FMRI_CPU_MASK, &cpumask);
527
528	*out = fmri_create(mod, cpu_id, cpumask, serial);
529
530	return (0);
531}
532
533/*ARGSUSED*/
534static int
535cpu_fmri_create_meth(topo_mod_t *mod, tnode_t *node, topo_version_t version,
536    nvlist_t *in, nvlist_t **out)
537{
538	int		rc;
539	nvlist_t	*args;
540	uint32_t	cpu_id;
541	uint8_t		cpumask = 0;
542	char		*serial = NULL;
543
544	if (version > TOPO_METH_FMRI_VERSION) {
545		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
546	}
547
548	rc = nvlist_lookup_nvlist(in, TOPO_METH_FMRI_ARG_NVL, &args);
549	if (rc != 0) {
550		/*
551		 * This routine requires arguments to be packed in the
552		 * format used in topo_fmri_create()
553		 */
554		return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
555	}
556
557	if (nvlist_lookup_string(args, FM_FMRI_CPU_SERIAL_ID, &serial) != 0 ||
558	    nvlist_lookup_uint32(args, FM_FMRI_CPU_ID, &cpu_id) != 0 ||
559	    nvlist_lookup_uint8(args, FM_FMRI_CPU_MASK, &cpumask) != 0) {
560		return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
561	}
562
563	*out = fmri_create(mod, cpu_id, cpumask, serial);
564	if (*out == NULL) {
565		return (-1);
566	}
567
568	return (0);
569}
570