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 2008 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#include <strings.h>
28#include <string.h>
29#include <libnvpair.h>
30#include <sys/fm/ldom.h>
31#include <fm/libtopo.h>
32#include <fm/topo_mod.h>
33#include <fm/fmd_fmri.h>
34#include <fm/fmd_agent.h>
35#include <sys/fm/ldom.h>
36
37struct cpu_walk_data {
38	tnode_t		*parent;	/* walk start node */
39	ldom_hdl_t	*lhp;		/* ldom handle */
40	int		(*func)(ldom_hdl_t *, nvlist_t *); /* callback func */
41	int		err;		/* walk errors count */
42	int		online;		/* online cpus count */
43	int		offline;	/* offline cpus count */
44	int		fail;		/* callback fails */
45};
46
47static topo_method_f
48	cpu_retire, cpu_unretire, cpu_service_state,
49	cpu_unusable, mem_asru_compute, dimm_page_unusable,
50	dimm_page_service_state, dimm_page_retire, dimm_page_unretire;
51
52const topo_method_t pi_cpu_methods[] = {
53	{ TOPO_METH_RETIRE, TOPO_METH_RETIRE_DESC,
54	    TOPO_METH_RETIRE_VERSION, TOPO_STABILITY_INTERNAL,
55	    cpu_retire },
56	{ TOPO_METH_UNRETIRE, TOPO_METH_UNRETIRE_DESC,
57	    TOPO_METH_UNRETIRE_VERSION, TOPO_STABILITY_INTERNAL,
58	    cpu_unretire },
59	{ TOPO_METH_SERVICE_STATE, TOPO_METH_SERVICE_STATE_DESC,
60	    TOPO_METH_SERVICE_STATE_VERSION, TOPO_STABILITY_INTERNAL,
61	    cpu_service_state },
62	{ TOPO_METH_UNUSABLE, TOPO_METH_UNUSABLE_DESC,
63	    TOPO_METH_UNUSABLE_VERSION, TOPO_STABILITY_INTERNAL,
64	    cpu_unusable },
65	{ NULL }
66};
67
68const topo_method_t pi_mem_methods[] = {
69	{ TOPO_METH_ASRU_COMPUTE, TOPO_METH_ASRU_COMPUTE_DESC,
70	    TOPO_METH_ASRU_COMPUTE_VERSION, TOPO_STABILITY_INTERNAL,
71	    mem_asru_compute },
72	{ TOPO_METH_SERVICE_STATE, TOPO_METH_SERVICE_STATE_DESC,
73	    TOPO_METH_SERVICE_STATE_VERSION, TOPO_STABILITY_INTERNAL,
74	    dimm_page_service_state },
75	{ TOPO_METH_UNUSABLE, TOPO_METH_UNUSABLE_DESC,
76	    TOPO_METH_UNUSABLE_VERSION, TOPO_STABILITY_INTERNAL,
77	    dimm_page_unusable },
78	{ TOPO_METH_RETIRE, TOPO_METH_RETIRE_DESC,
79	    TOPO_METH_RETIRE_VERSION, TOPO_STABILITY_INTERNAL,
80	    dimm_page_retire },
81	{ TOPO_METH_UNRETIRE, TOPO_METH_UNRETIRE_DESC,
82	    TOPO_METH_UNRETIRE_VERSION, TOPO_STABILITY_INTERNAL,
83	    dimm_page_unretire },
84	{ NULL }
85};
86
87static ldom_hdl_t *pi_lhp = NULL;
88
89#pragma init(pi_ldom_init)
90static void
91pi_ldom_init(void)
92{
93	pi_lhp = ldom_init(NULL, NULL);
94}
95
96#pragma fini(pi_ldom_fini)
97static void
98pi_ldom_fini(void)
99{
100	if (pi_lhp != NULL)
101		ldom_fini(pi_lhp);
102}
103
104static int
105set_retnvl(topo_mod_t *mod, nvlist_t **out, const char *retname, uint32_t ret)
106{
107	nvlist_t *nvl;
108
109	topo_mod_dprintf(mod, "topo method set \"%s\" = %u\n", retname, ret);
110
111	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) < 0)
112		return (topo_mod_seterrno(mod, EMOD_NOMEM));
113
114	if (nvlist_add_uint32(nvl, retname, ret) != 0) {
115		nvlist_free(nvl);
116		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
117	}
118
119	*out = nvl;
120	return (0);
121}
122
123/*
124 * For each visited cpu node, call the callback function with its ASRU.
125 */
126static int
127cpu_walker(topo_mod_t *mod, tnode_t *node, void *pdata)
128{
129	struct cpu_walk_data *swdp = pdata;
130	nvlist_t *asru;
131	int err, rc;
132
133	/*
134	 * Terminate the walk if we reach start-node's sibling
135	 */
136	if (node != swdp->parent &&
137	    topo_node_parent(node) == topo_node_parent(swdp->parent))
138		return (TOPO_WALK_TERMINATE);
139
140	if (strcmp(topo_node_name(node), CPU) != 0 &&
141	    strcmp(topo_node_name(node), STRAND) != 0)
142		return (TOPO_WALK_NEXT);
143
144	if (topo_node_asru(node, &asru, NULL, &err) != 0) {
145		swdp->fail++;
146		return (TOPO_WALK_NEXT);
147	}
148
149	rc = swdp->func(swdp->lhp, asru);
150
151	/*
152	 * The "offline" and "online" counter are only useful for the "status"
153	 * callback.
154	 */
155	if (rc == P_OFFLINE || rc == P_FAULTED) {
156		swdp->offline++;
157		err = 0;
158	} else if (rc == P_ONLINE) {
159		swdp->online++;
160		err = 0;
161	} else {
162		swdp->fail++;
163		err = errno;
164	}
165
166	/* dump out status info if debug is turned on. */
167	if (getenv("TOPOCHIPDBG") != NULL ||
168	    getenv("TOPOSUN4VPIDBG") != NULL) {
169		const char *op;
170		char *fmristr = NULL;
171
172		if (swdp->func == ldom_fmri_retire)
173			op = "retire";
174		else if (swdp->func == ldom_fmri_unretire)
175			op = "unretire";
176		else if (swdp->func == ldom_fmri_status)
177			op = "check status";
178		else
179			op = "unknown op";
180
181		(void) topo_mod_nvl2str(mod, asru, &fmristr);
182		topo_mod_dprintf(mod, "%s cpu (%s): rc = %d, err = %s\n",
183		    op, fmristr == NULL ? "unknown fmri" : fmristr,
184		    rc, strerror(err));
185		if (fmristr != NULL)
186			topo_mod_strfree(mod, fmristr);
187	}
188
189	nvlist_free(asru);
190	return (TOPO_WALK_NEXT);
191}
192
193static int
194walk_cpus(topo_mod_t *mod, struct cpu_walk_data *swdp, tnode_t *parent,
195    int (*func)(ldom_hdl_t *, nvlist_t *))
196{
197	topo_walk_t *twp;
198	int err;
199
200	swdp->lhp = pi_lhp;
201	swdp->parent = parent;
202	swdp->func = func;
203	swdp->err = swdp->offline = swdp->online = swdp->fail = 0;
204
205	/*
206	 * Return failure if ldom service is not initialized.
207	 */
208	if (pi_lhp == NULL) {
209		swdp->fail++;
210		return (0);
211	}
212
213	twp = topo_mod_walk_init(mod, parent, cpu_walker, swdp, &err);
214	if (twp == NULL)
215		return (-1);
216
217	err = topo_walk_step(twp, TOPO_WALK_CHILD);
218	topo_walk_fini(twp);
219
220	if (err == TOPO_WALK_ERR || swdp->err > 0)
221		return (-1);
222
223	return (0);
224}
225
226/* ARGSUSED */
227int
228cpu_retire(topo_mod_t *mod, tnode_t *node, topo_version_t version,
229    nvlist_t *in, nvlist_t **out)
230{
231	struct cpu_walk_data swd;
232	uint32_t rc;
233
234	if (version > TOPO_METH_RETIRE_VERSION)
235		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
236
237	if (walk_cpus(mod, &swd, node, ldom_fmri_retire) == -1)
238		return (-1);
239
240	rc = swd.fail > 0 ? FMD_AGENT_RETIRE_FAIL : FMD_AGENT_RETIRE_DONE;
241
242	return (set_retnvl(mod, out, TOPO_METH_RETIRE_RET, rc));
243}
244
245/* ARGSUSED */
246int
247cpu_unretire(topo_mod_t *mod, tnode_t *node, topo_version_t version,
248    nvlist_t *in, nvlist_t **out)
249{
250	struct cpu_walk_data swd;
251	uint32_t rc;
252
253	if (version > TOPO_METH_UNRETIRE_VERSION)
254		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
255
256	if (walk_cpus(mod, &swd, node, ldom_fmri_unretire) == -1)
257		return (-1);
258
259	rc = swd.fail > 0 ? FMD_AGENT_RETIRE_FAIL : FMD_AGENT_RETIRE_DONE;
260
261	return (set_retnvl(mod, out, TOPO_METH_UNRETIRE_RET, rc));
262}
263
264/* ARGSUSED */
265int
266cpu_service_state(topo_mod_t *mod, tnode_t *node, topo_version_t version,
267    nvlist_t *in, nvlist_t **out)
268{
269	struct cpu_walk_data swd;
270	uint32_t rc;
271
272	if (version > TOPO_METH_SERVICE_STATE_VERSION)
273		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
274
275	if (walk_cpus(mod, &swd, node, ldom_fmri_status) == -1)
276		return (-1);
277
278	if (swd.fail > 0)
279		rc = FMD_SERVICE_STATE_UNKNOWN;
280	else if (swd.offline > 0)
281		rc = swd.online > 0 ? FMD_SERVICE_STATE_DEGRADED :
282		    FMD_SERVICE_STATE_UNUSABLE;
283	else
284		rc = FMD_SERVICE_STATE_OK;
285
286	return (set_retnvl(mod, out, TOPO_METH_SERVICE_STATE_RET, rc));
287}
288
289/* ARGSUSED */
290int
291cpu_unusable(topo_mod_t *mod, tnode_t *node, topo_version_t version,
292    nvlist_t *in, nvlist_t **out)
293{
294	struct cpu_walk_data swd;
295	uint32_t rc;
296
297	if (version > TOPO_METH_UNUSABLE_VERSION)
298		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
299
300	if (walk_cpus(mod, &swd, node, ldom_fmri_status) == -1)
301		return (-1);
302
303	rc = (swd.offline > 0 && swd.fail + swd.online == 0) ? 1 : 0;
304
305	return (set_retnvl(mod, out, TOPO_METH_UNUSABLE_RET, rc));
306}
307
308static nvlist_t *
309mem_fmri_create(topo_mod_t *mod, char *serial, char *label)
310{
311	int err;
312	nvlist_t *fmri;
313
314	if (topo_mod_nvalloc(mod, &fmri, NV_UNIQUE_NAME) != 0)
315		return (NULL);
316	err = nvlist_add_uint8(fmri, FM_VERSION, FM_MEM_SCHEME_VERSION);
317	err |= nvlist_add_string(fmri, FM_FMRI_SCHEME, FM_FMRI_SCHEME_MEM);
318	if (serial != NULL)
319		err |= nvlist_add_string_array(fmri, FM_FMRI_MEM_SERIAL_ID,
320		    &serial, 1);
321	if (label != NULL)
322		err |= nvlist_add_string(fmri, FM_FMRI_MEM_UNUM, label);
323	if (err != 0) {
324		nvlist_free(fmri);
325		(void) topo_mod_seterrno(mod, EMOD_FMRI_NVL);
326		return (NULL);
327	}
328
329	return (fmri);
330}
331
332/* Topo Methods */
333static int
334mem_asru_compute(topo_mod_t *mod, tnode_t *node, topo_version_t version,
335    nvlist_t *in, nvlist_t **out)
336{
337	nvlist_t *asru, *pargs, *args, *hcsp;
338	int err;
339	char *serial = NULL, *label = NULL;
340	uint64_t pa, offset;
341
342	if (version > TOPO_METH_ASRU_COMPUTE_VERSION)
343		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
344
345	if (strcmp(topo_node_name(node), DIMM) != 0)
346		return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
347
348	pargs = NULL;
349
350	if (nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0)
351		(void) nvlist_lookup_string(pargs, FM_FMRI_HC_SERIAL_ID,
352		    &serial);
353	if (serial == NULL &&
354	    nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) == 0)
355		(void) nvlist_lookup_string(args, FM_FMRI_HC_SERIAL_ID,
356		    &serial);
357
358	(void) topo_node_label(node, &label, &err);
359
360	asru = mem_fmri_create(mod, serial, label);
361
362	if (label != NULL)
363		topo_mod_strfree(mod, label);
364
365	if (asru == NULL)
366		return (topo_mod_seterrno(mod, EMOD_NOMEM));
367
368	err = 0;
369
370	/*
371	 * For a memory page, 'in' includes an hc-specific member which
372	 * specifies physaddr and/or offset. Set them in asru as well.
373	 */
374	if (pargs && nvlist_lookup_nvlist(pargs,
375	    FM_FMRI_HC_SPECIFIC, &hcsp) == 0) {
376		if (nvlist_lookup_uint64(hcsp,
377		    FM_FMRI_HC_SPECIFIC_PHYSADDR, &pa) == 0)
378			err += nvlist_add_uint64(asru, FM_FMRI_MEM_PHYSADDR,
379			    pa);
380		if (nvlist_lookup_uint64(hcsp,
381		    FM_FMRI_HC_SPECIFIC_OFFSET, &offset) == 0)
382			err += nvlist_add_uint64(asru, FM_FMRI_MEM_OFFSET,
383			    offset);
384	}
385
386
387	if (err != 0 || topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) < 0) {
388		nvlist_free(asru);
389		return (topo_mod_seterrno(mod, EMOD_NOMEM));
390	}
391
392	err = nvlist_add_string(*out, TOPO_PROP_VAL_NAME, TOPO_PROP_ASRU);
393	err |= nvlist_add_uint32(*out, TOPO_PROP_VAL_TYPE, TOPO_TYPE_FMRI);
394	err |= nvlist_add_nvlist(*out, TOPO_PROP_VAL_VAL, asru);
395	nvlist_free(asru);
396
397	if (err != 0) {
398		nvlist_free(*out);
399		*out = NULL;
400		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
401	}
402
403	return (0);
404}
405
406static boolean_t
407is_page_fmri(nvlist_t *nvl)
408{
409	nvlist_t *hcsp;
410	uint64_t val;
411
412	if (nvlist_lookup_nvlist(nvl, FM_FMRI_HC_SPECIFIC, &hcsp) == 0 &&
413	    (nvlist_lookup_uint64(hcsp, FM_FMRI_HC_SPECIFIC_OFFSET,
414	    &val) == 0 ||
415	    nvlist_lookup_uint64(hcsp, "asru-" FM_FMRI_HC_SPECIFIC_OFFSET,
416	    &val) == 0 ||
417	    nvlist_lookup_uint64(hcsp, FM_FMRI_HC_SPECIFIC_PHYSADDR,
418	    &val) == 0 ||
419	    nvlist_lookup_uint64(hcsp, "asru-" FM_FMRI_HC_SPECIFIC_PHYSADDR,
420	    &val) == 0))
421		return (B_TRUE);
422
423	return (B_FALSE);
424}
425
426static int
427dimm_page_service_state(topo_mod_t *mod, tnode_t *node, topo_version_t version,
428    nvlist_t *in, nvlist_t **out)
429{
430	uint32_t rc = FMD_SERVICE_STATE_OK;
431	nvlist_t *asru;
432	int err;
433
434	if (version > TOPO_METH_SERVICE_STATE_VERSION)
435		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
436
437	if (pi_lhp != NULL && is_page_fmri(in) &&
438	    topo_node_asru(node, &asru, in, &err) == 0) {
439		err = ldom_fmri_status(pi_lhp, asru);
440
441		if (err == 0 || err == EINVAL)
442			rc = FMD_SERVICE_STATE_UNUSABLE;
443		else if (err == EAGAIN)
444			rc = FMD_SERVICE_STATE_ISOLATE_PENDING;
445		nvlist_free(asru);
446	}
447
448	return (set_retnvl(mod, out, TOPO_METH_SERVICE_STATE_RET, rc));
449}
450
451static int
452dimm_page_unusable(topo_mod_t *mod, tnode_t *node, topo_version_t version,
453    nvlist_t *in, nvlist_t **out)
454{
455	uint32_t rc = 0;
456	nvlist_t *asru;
457	int err;
458
459	if (version > TOPO_METH_UNUSABLE_VERSION)
460		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
461
462	if (pi_lhp != NULL && is_page_fmri(in) &&
463	    topo_node_asru(node, &asru, in, &err) == 0) {
464		err = ldom_fmri_status(pi_lhp, asru);
465
466		if (err == 0 || err == EINVAL)
467			rc = 1;
468		nvlist_free(asru);
469	}
470
471	return (set_retnvl(mod, out, TOPO_METH_UNUSABLE_RET, rc));
472}
473
474static int
475dimm_page_retire(topo_mod_t *mod, tnode_t *node, topo_version_t version,
476    nvlist_t *in, nvlist_t **out)
477{
478	uint32_t rc = FMD_AGENT_RETIRE_FAIL;
479	nvlist_t *asru;
480	int err;
481
482	if (version > TOPO_METH_RETIRE_VERSION)
483		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
484
485	if (pi_lhp != NULL && is_page_fmri(in) &&
486	    topo_node_asru(node, &asru, in, &err) == 0) {
487		err = ldom_fmri_retire(pi_lhp, asru);
488
489		if (err == 0 || err == EIO || err == EINVAL)
490			rc = FMD_AGENT_RETIRE_DONE;
491		else if (err == EAGAIN)
492			rc = FMD_AGENT_RETIRE_ASYNC;
493		nvlist_free(asru);
494	}
495
496	return (set_retnvl(mod, out, TOPO_METH_RETIRE_RET, rc));
497}
498
499static int
500dimm_page_unretire(topo_mod_t *mod, tnode_t *node, topo_version_t version,
501    nvlist_t *in, nvlist_t **out)
502{
503	uint32_t rc = FMD_AGENT_RETIRE_FAIL;
504	nvlist_t *asru;
505	int err;
506
507	if (version > TOPO_METH_UNRETIRE_VERSION)
508		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
509
510	if (pi_lhp != NULL && is_page_fmri(in) &&
511	    topo_node_asru(node, &asru, in, &err) == 0) {
512		err = ldom_fmri_unretire(pi_lhp, asru);
513
514		if (err == 0 || err == EIO)
515			rc = FMD_AGENT_RETIRE_DONE;
516		nvlist_free(asru);
517	}
518
519	return (set_retnvl(mod, out, TOPO_METH_UNRETIRE_RET, rc));
520}
521