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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26/*
27 * FMA capability messenger
28 *
29 * fdd-msg module is called once when fmd starts up. It does the following
30 * based on different scenarios
31 *
32 * 1. If it's on a x86 platform, fdd-msg module sends fdd running on service
33 * processor a message (ILOM) which indicates the Solaris host FMA capability.
34 * The message is sent via the BMC driver (KCS interface) to the IPMI stack
35 * of ILOM using the IPMI Sun OEM core tunnel command. The sub-command is
36 * CORE_TUNNEL_SUBCMD_HOSTCAP. The IPMI stack posts an host FMA capability
37 * event to the event manager upon receiving this message. fdd subscribes to
38 * the event manager for this event. Upon receving this event, fdd will adjust
39 * its configuration.
40 *
41 * 2. If it's on a Sparc platform, fdd-msg module just exit for now.
42 */
43
44#include <errno.h>
45#include <stdio.h>
46#include <strings.h>
47#include <sys/systeminfo.h>
48#include <libipmi.h>
49#include <sys/devfm.h>
50#include <fm/fmd_api.h>
51#if defined(__x86)
52#include <sys/x86_archext.h>
53#include <fm/fmd_agent.h>
54#include <libnvpair.h>
55#endif
56
57#define	CMD_SUNOEM_CORE_TUNNEL 0x44
58#define	CORE_TUNNEL_SUBCMD_HOSTFMACAP 2
59#define	OEM_DATA_LENGTH 3
60#define	VERSION 0x10
61
62#if defined(__x86)
63typedef struct cpu_tbl {
64	char vendor[X86_VENDOR_STRLEN];
65	int32_t family;
66	int32_t model;
67	char *propname;
68} cpu_tbl_t;
69
70static cpu_tbl_t fma_cap_list[] = {
71	{"GenuineIntel", 6, 26, "NHMEP_fma_cap"},
72	{"GenuineIntel", 6, 46, "NHMEX_fma_cap"},
73	{"GenuineIntel", 6, 44, "WSMEP_fma_cap"},
74	{"GenuineIntel", 6, 47, "INTLN_fma_cap"},
75	{0, 0, 0, 0}
76};
77#endif
78
79static int
80check_sunoem(ipmi_handle_t *ipmi_hdl)
81{
82	ipmi_deviceid_t *devid;
83
84	if ((devid = ipmi_get_deviceid(ipmi_hdl)) == NULL)
85		return (-1);
86
87	if (!ipmi_is_sun_ilom(devid))
88		return (-2);
89
90	return (0);
91}
92
93#if defined(__x86)
94static int32_t
95fma_cap_cpu_info(cpu_tbl_t *ci)
96{
97	nvlist_t **cpus, *nvl;
98	uint_t ncpu, i;
99	fmd_agent_hdl_t *hdl;
100	char *ven;
101	int32_t family, model;
102
103	if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) == NULL)
104		return (-1);
105	if (fmd_agent_physcpu_info(hdl, &cpus, &ncpu) != 0) {
106		fmd_agent_close(hdl);
107		return (-1);
108	}
109	fmd_agent_close(hdl);
110
111	if (cpus == NULL)
112		return (-1);
113
114	/*
115	 * There is no mixed CPU type on x86 systems, it's ok to
116	 * just pick the first one
117	 */
118	nvl = cpus[0];
119	if (nvlist_lookup_string(nvl, FM_PHYSCPU_INFO_VENDOR_ID, &ven) != 0 ||
120	    nvlist_lookup_int32(nvl, FM_PHYSCPU_INFO_FAMILY, &family) != 0 ||
121	    nvlist_lookup_int32(nvl, FM_PHYSCPU_INFO_MODEL, &model) != 0) {
122		for (i = 0; i < ncpu; i++)
123			nvlist_free(cpus[i]);
124		umem_free(cpus, sizeof (nvlist_t *) * ncpu);
125		return (-1);
126	}
127
128	(void) snprintf(ci->vendor, X86_VENDOR_STRLEN, "%s", ven);
129	ci->family = family;
130	ci->model = model;
131
132	for (i = 0; i < ncpu; i++)
133		nvlist_free(cpus[i]);
134	umem_free(cpus, sizeof (nvlist_t *) * ncpu);
135	return (0);
136}
137#endif
138
139static uint32_t
140get_cap_conf(fmd_hdl_t *hdl)
141{
142	uint32_t fma_cap;
143#if defined(__x86)
144	int found = 0;
145	cpu_tbl_t *cl, ci;
146
147	if (fma_cap_cpu_info(&ci) == 0) {
148		fmd_hdl_debug(hdl, "Got CPU info: vendor=%s, family=%d, "
149		    "model=%d\n", ci.vendor, ci.family, ci.model);
150		for (cl = fma_cap_list; cl->propname != NULL; cl++) {
151			if (strncmp(ci.vendor, cl->vendor,
152			    X86_VENDOR_STRLEN) == 0 &&
153			    ci.family == cl->family &&
154			    ci.model == cl->model) {
155				found++;
156				break;
157			}
158		}
159	} else {
160		fmd_hdl_debug(hdl, "Failed to get CPU info");
161	}
162
163	if (found) {
164		fma_cap = fmd_prop_get_int32(hdl, cl->propname);
165		fmd_hdl_debug(hdl, "Found property, FMA capability=0x%x",
166		    fma_cap);
167	} else {
168#endif
169		fma_cap = fmd_prop_get_int32(hdl, "default_fma_cap");
170		fmd_hdl_debug(hdl, "Didn't find FMA capability property, "
171		    "use default=0x%x", fma_cap);
172#if defined(__x86)
173	}
174#endif
175
176	return (fma_cap);
177}
178
179static void
180send_fma_cap_to_ilom(fmd_hdl_t *hdl, uint32_t fma_cap)
181{
182	int error;
183	char *msg;
184	ipmi_handle_t *ipmi_hdl;
185	ipmi_cmd_t cmd;
186	uint8_t oem_data[OEM_DATA_LENGTH];
187
188	if ((ipmi_hdl = ipmi_open(&error, &msg, IPMI_TRANSPORT_BMC, NULL))
189	    == NULL) {
190		/*
191		 * If /dev/ipmi0 doesn't exist on the system, then return
192		 * without doing anything.
193		 */
194		if (error != EIPMI_BMC_OPEN_FAILED)
195			fmd_hdl_abort(hdl, "Failed to initialize IPMI "
196			    "connection: %s\n", msg);
197		fmd_hdl_debug(hdl, "Failed: no IPMI connection present");
198		return;
199	}
200
201	/*
202	 * Check if it's Sun ILOM
203	 */
204	if (check_sunoem(ipmi_hdl) != 0) {
205		fmd_hdl_debug(hdl, "Service Processor does not run "
206		    "Sun ILOM");
207		ipmi_close(ipmi_hdl);
208		return;
209	}
210
211	oem_data[0] = CORE_TUNNEL_SUBCMD_HOSTFMACAP;
212	oem_data[1] = VERSION;
213	oem_data[2] = fma_cap;
214
215	cmd.ic_netfn = IPMI_NETFN_OEM;
216	cmd.ic_lun = 0;
217	cmd.ic_cmd = CMD_SUNOEM_CORE_TUNNEL;
218	cmd.ic_dlen = OEM_DATA_LENGTH;
219	cmd.ic_data = oem_data;
220
221	if (ipmi_send(ipmi_hdl, &cmd) == NULL) {
222		fmd_hdl_debug(hdl, "Failed to send Solaris FMA "
223		    "capability to ilom: %s", ipmi_errmsg(ipmi_hdl));
224	}
225
226	ipmi_close(ipmi_hdl);
227}
228
229/*ARGSUSED*/
230static void
231fma_cap_init(fmd_hdl_t *hdl, id_t id, void *data)
232{
233	uint32_t	fma_cap;
234
235	fma_cap = get_cap_conf(hdl);
236	send_fma_cap_to_ilom(hdl, fma_cap);
237
238	fmd_hdl_unregister(hdl);
239}
240
241static const fmd_hdl_ops_t fmd_ops = {
242	NULL,		/* fmdo_recv */
243	fma_cap_init,	/* fmdo_timeout */
244	NULL,		/* fmdo_close */
245	NULL,		/* fmdo_stats */
246	NULL,		/* fmdo_gc */
247	NULL,		/* fmdo_send */
248	NULL,		/* fmdo_topo */
249};
250
251static const fmd_prop_t fmd_props[] = {
252	{ "interval", FMD_TYPE_TIME, "1s" },
253	{ "default_fma_cap", FMD_TYPE_UINT32, "0x3" },
254	{ "NHMEP_fma_cap", FMD_TYPE_UINT32, "0x3" },
255	{ "NHMEX_fma_cap", FMD_TYPE_UINT32, "0x2" },
256	{ "WSMEP_fma_cap", FMD_TYPE_UINT32, "0x3" },
257	{ "INTLN_fma_cap", FMD_TYPE_UINT32, "0x2" },
258	{ NULL, 0, NULL }
259};
260
261static const fmd_hdl_info_t fmd_info = {
262	"FMA Capability Messenger", "1.1", &fmd_ops, fmd_props
263};
264
265void
266_fmd_init(fmd_hdl_t *hdl)
267{
268	char isa[8];
269
270	/*
271	 * For now the module only sends message to ILOM on i386 platforms
272	 * till CR 6933053 is fixed. Module unregister may cause etm module
273	 * core dump due to 6933053.
274	 */
275	if ((sysinfo(SI_ARCHITECTURE, isa, sizeof (isa)) == -1) ||
276	    (strncmp(isa, "i386", 4) != 0))
277		return;
278
279	if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0)
280		return;
281
282	/*
283	 * Setup the timer.
284	 */
285	(void) fmd_timer_install(hdl, NULL, NULL, 2000000000ULL);
286}
287
288/*ARGSUSED*/
289void
290_fmd_fini(fmd_hdl_t *hdl)
291{
292}
293