19113a79cSeschrock /*
29113a79cSeschrock  * CDDL HEADER START
39113a79cSeschrock  *
49113a79cSeschrock  * The contents of this file are subject to the terms of the
59113a79cSeschrock  * Common Development and Distribution License (the "License").
69113a79cSeschrock  * You may not use this file except in compliance with the License.
79113a79cSeschrock  *
89113a79cSeschrock  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
99113a79cSeschrock  * or http://www.opensolaris.org/os/licensing.
109113a79cSeschrock  * See the License for the specific language governing permissions
119113a79cSeschrock  * and limitations under the License.
129113a79cSeschrock  *
139113a79cSeschrock  * When distributing Covered Code, include this CDDL HEADER in each
149113a79cSeschrock  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
159113a79cSeschrock  * If applicable, add the following below this CDDL HEADER, with the
169113a79cSeschrock  * fields enclosed by brackets "[]" replaced with your own identifying
179113a79cSeschrock  * information: Portions Copyright [yyyy] [name of copyright owner]
189113a79cSeschrock  *
199113a79cSeschrock  * CDDL HEADER END
209113a79cSeschrock  */
219113a79cSeschrock 
229113a79cSeschrock /*
2381d9f076SRobert Johnston  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
249113a79cSeschrock  * Use is subject to license terms.
25*989f2807SJerry Jelinek  * Copyright 2012 Joyent, Inc.  All rights reserved.
269113a79cSeschrock  */
279113a79cSeschrock 
289113a79cSeschrock /*
29*989f2807SJerry Jelinek  * /dev/ipmi IPMI monitor
309113a79cSeschrock  *
319113a79cSeschrock  * The purpose of this module is to monitor the connection between the system
32*989f2807SJerry Jelinek  * and the service processor attached via /dev/ipmi0.  The module assumes the SP
339113a79cSeschrock  * supports the Sun OEM uptime IPMI command.  If the BMC connection does not
349113a79cSeschrock  * exist, or the uptime function is not implemented, then the module unloads
359113a79cSeschrock  * without doing anything.
369113a79cSeschrock  *
379113a79cSeschrock  * When the module is first loaded, or a reset is detected, the module will
389113a79cSeschrock  * generate the ESC_PLATFORM_SP_RESET sysevent as a system-wide notification to
399113a79cSeschrock  * indicate that this event has occurred.
409113a79cSeschrock  *
419113a79cSeschrock  * Note that this event generation is not guaranteed to have a one-to-one
429113a79cSeschrock  * correspondence with an SP reset.  There is no persistence, so if fmd is
439113a79cSeschrock  * restarted we will generate this event again.  Thus the event only indicates
449113a79cSeschrock  * the possibility that the SP has been reset.  This could be enhanced using fmd
459113a79cSeschrock  * checkpoints to have some persistent state to avoid this scenario.  However,
469113a79cSeschrock  * it currently serves the useful dual purpose of notifying consumers of system
479113a79cSeschrock  * startup as well as SP reset through a single channel.
489113a79cSeschrock  */
499113a79cSeschrock 
509113a79cSeschrock #include <errno.h>
519113a79cSeschrock #include <libipmi.h>
529113a79cSeschrock #include <libsysevent.h>
539113a79cSeschrock #include <string.h>
549113a79cSeschrock #include <fm/fmd_api.h>
559113a79cSeschrock #include <sys/sysevent/eventdefs.h>
569113a79cSeschrock 
579113a79cSeschrock typedef struct sp_monitor {
589113a79cSeschrock 	ipmi_handle_t	*sm_hdl;
599113a79cSeschrock 	uint32_t	sm_seconds;
609113a79cSeschrock 	uint32_t	sm_generation;
619113a79cSeschrock 	hrtime_t	sm_interval;
629113a79cSeschrock } sp_monitor_t;
639113a79cSeschrock 
649113a79cSeschrock static void
sp_post_sysevent(fmd_hdl_t * hdl)659113a79cSeschrock sp_post_sysevent(fmd_hdl_t *hdl)
669113a79cSeschrock {
679113a79cSeschrock 	sp_monitor_t *smp = fmd_hdl_getspecific(hdl);
689113a79cSeschrock 	sysevent_id_t eid;
699113a79cSeschrock 
709113a79cSeschrock 	fmd_hdl_debug(hdl, "SP reset detected, posting sysevent");
719113a79cSeschrock 
729113a79cSeschrock 	if (sysevent_post_event(EC_PLATFORM, ESC_PLATFORM_SP_RESET,
739113a79cSeschrock 	    SUNW_VENDOR, "fmd", NULL, &eid) != 0) {
749113a79cSeschrock 		fmd_hdl_debug(hdl, "failed to send sysevent: %s",
759113a79cSeschrock 		    strerror(errno));
769113a79cSeschrock 		/*
779113a79cSeschrock 		 * We reset the seconds and generation so that the next time
789113a79cSeschrock 		 * through we will try to post the sysevent again.
799113a79cSeschrock 		 */
809113a79cSeschrock 		smp->sm_seconds = -1U;
819113a79cSeschrock 		smp->sm_generation = -1U;
829113a79cSeschrock 	}
839113a79cSeschrock }
849113a79cSeschrock 
859113a79cSeschrock /*ARGSUSED*/
869113a79cSeschrock static void
sp_timeout(fmd_hdl_t * hdl,id_t id,void * data)879113a79cSeschrock sp_timeout(fmd_hdl_t *hdl, id_t id, void *data)
889113a79cSeschrock {
899113a79cSeschrock 	sp_monitor_t *smp = fmd_hdl_getspecific(hdl);
909113a79cSeschrock 	uint32_t seconds, generation;
919113a79cSeschrock 
929113a79cSeschrock 	if (ipmi_sunoem_uptime(smp->sm_hdl, &seconds, &generation) != 0) {
939113a79cSeschrock 		/*
949113a79cSeschrock 		 * Ignore uptime failures.  We will generate the appropriate
959113a79cSeschrock 		 * event when it comes back online.
969113a79cSeschrock 		 */
979113a79cSeschrock 		fmd_hdl_debug(hdl, "failed to get uptime: %s",
989113a79cSeschrock 		    ipmi_errmsg(smp->sm_hdl));
999113a79cSeschrock 	} else {
1009113a79cSeschrock 		/*
1019113a79cSeschrock 		 * We want to catch cases where the generation number is
1029113a79cSeschrock 		 * explicitly reset, or when the SP configuration is reset after
1039113a79cSeschrock 		 * a reboot (and the generation number is 0).  We also post a
1049113a79cSeschrock 		 * sysevent when the module initially loads, since we can't be
1059113a79cSeschrock 		 * sure if we missed a SP reset or not.
1069113a79cSeschrock 		 */
1079113a79cSeschrock 		if (seconds < smp->sm_seconds ||
1089113a79cSeschrock 		    generation != smp->sm_generation ||
1099113a79cSeschrock 		    smp->sm_seconds == 0)
1109113a79cSeschrock 			sp_post_sysevent(hdl);
1119113a79cSeschrock 
1129113a79cSeschrock 		smp->sm_seconds = seconds;
1139113a79cSeschrock 		smp->sm_generation = generation;
1149113a79cSeschrock 	}
1159113a79cSeschrock 
1169113a79cSeschrock 	(void) fmd_timer_install(hdl, NULL, NULL, smp->sm_interval);
1179113a79cSeschrock }
1189113a79cSeschrock 
1199113a79cSeschrock static const fmd_hdl_ops_t fmd_ops = {
1209113a79cSeschrock 	NULL,		/* fmdo_recv */
1219113a79cSeschrock 	sp_timeout,	/* fmdo_timeout */
1229113a79cSeschrock 	NULL,		/* fmdo_close */
1239113a79cSeschrock 	NULL,		/* fmdo_stats */
1249113a79cSeschrock 	NULL,		/* fmdo_gc */
1259113a79cSeschrock };
1269113a79cSeschrock 
1279113a79cSeschrock static const fmd_prop_t fmd_props[] = {
1289113a79cSeschrock 	{ "interval", FMD_TYPE_TIME, "60sec" },
1299113a79cSeschrock 	{ NULL, 0, NULL }
1309113a79cSeschrock };
1319113a79cSeschrock 
1329113a79cSeschrock static const fmd_hdl_info_t fmd_info = {
1339113a79cSeschrock 	"Service Processor Monitor", "1.0", &fmd_ops, fmd_props
1349113a79cSeschrock };
1359113a79cSeschrock 
1369113a79cSeschrock void
_fmd_init(fmd_hdl_t * hdl)1379113a79cSeschrock _fmd_init(fmd_hdl_t *hdl)
1389113a79cSeschrock {
1399113a79cSeschrock 	sp_monitor_t *smp;
1409113a79cSeschrock 	int error;
1419113a79cSeschrock 	char *msg;
1429113a79cSeschrock 
1439113a79cSeschrock 	if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0)
1449113a79cSeschrock 		return;
1459113a79cSeschrock 
1469113a79cSeschrock 	smp = fmd_hdl_zalloc(hdl, sizeof (sp_monitor_t), FMD_SLEEP);
1479113a79cSeschrock 	fmd_hdl_setspecific(hdl, smp);
1489113a79cSeschrock 
14981d9f076SRobert Johnston 	if ((smp->sm_hdl = ipmi_open(&error, &msg, IPMI_TRANSPORT_BMC, NULL))
15081d9f076SRobert Johnston 	    == NULL) {
1519113a79cSeschrock 		/*
152*989f2807SJerry Jelinek 		 * If /dev/ipmi0 doesn't exist on the system, then unload the
1539113a79cSeschrock 		 * module without doing anything.
1549113a79cSeschrock 		 */
1559113a79cSeschrock 		if (error != EIPMI_BMC_OPEN_FAILED)
1569113a79cSeschrock 			fmd_hdl_abort(hdl, "failed to initialize IPMI "
1579113a79cSeschrock 			    "connection: %s\n", msg);
1589113a79cSeschrock 		fmd_hdl_debug(hdl, "failed to load: no IPMI connection "
1599113a79cSeschrock 		    "present");
1609113a79cSeschrock 		fmd_hdl_free(hdl, smp, sizeof (sp_monitor_t));
1619113a79cSeschrock 		fmd_hdl_unregister(hdl);
1629113a79cSeschrock 		return;
1639113a79cSeschrock 	}
1649113a79cSeschrock 
1659113a79cSeschrock 	/*
1669113a79cSeschrock 	 * Attempt an initial uptime() call.  If the IPMI command is
1679113a79cSeschrock 	 * unrecognized, then this is an unsupported platform and the module
1689113a79cSeschrock 	 * should be unloaded.  Any other error is treated is transient failure.
1699113a79cSeschrock 	 */
1709113a79cSeschrock 	if ((error = ipmi_sunoem_uptime(smp->sm_hdl, &smp->sm_seconds,
1719113a79cSeschrock 	    &smp->sm_generation)) != 0 &&
1729113a79cSeschrock 	    ipmi_errno(smp->sm_hdl) == EIPMI_INVALID_COMMAND) {
1739113a79cSeschrock 		fmd_hdl_debug(hdl, "failed to load: uptime command "
1749113a79cSeschrock 		    "not supported");
1759113a79cSeschrock 		ipmi_close(smp->sm_hdl);
1769113a79cSeschrock 		fmd_hdl_free(hdl, smp, sizeof (sp_monitor_t));
1779113a79cSeschrock 		fmd_hdl_unregister(hdl);
1789113a79cSeschrock 		return;
1799113a79cSeschrock 	}
1809113a79cSeschrock 
1819113a79cSeschrock 	smp->sm_interval = fmd_prop_get_int64(hdl, "interval");
1829113a79cSeschrock 
1839113a79cSeschrock 	if (error == 0)
1849113a79cSeschrock 		fmd_hdl_debug(hdl, "successfully loaded, uptime = %u seconds "
1859113a79cSeschrock 		    "(generation %u)", smp->sm_seconds, smp->sm_generation);
1869113a79cSeschrock 	else
1879113a79cSeschrock 		fmd_hdl_debug(hdl, "successfully loaded, but uptime call "
1889113a79cSeschrock 		    "failed: %s", ipmi_errmsg(smp->sm_hdl));
1899113a79cSeschrock 
1909113a79cSeschrock 	/*
1919113a79cSeschrock 	 * Setup the recurring timer.
1929113a79cSeschrock 	 */
1939113a79cSeschrock 	(void) fmd_timer_install(hdl, NULL, NULL, 0);
1949113a79cSeschrock }
1959113a79cSeschrock 
1969113a79cSeschrock void
_fmd_fini(fmd_hdl_t * hdl)1979113a79cSeschrock _fmd_fini(fmd_hdl_t *hdl)
1989113a79cSeschrock {
1999113a79cSeschrock 	sp_monitor_t *smp = fmd_hdl_getspecific(hdl);
2009113a79cSeschrock 
2019113a79cSeschrock 	if (smp) {
2029113a79cSeschrock 		ipmi_close(smp->sm_hdl);
2039113a79cSeschrock 		fmd_hdl_free(hdl, smp, sizeof (sp_monitor_t));
2049113a79cSeschrock 	}
2059113a79cSeschrock }
206