xref: /illumos-gate/usr/src/uts/intel/io/ipmi/ipmi.c (revision 5dbfd19a)
1 /*
2  * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 /* $FreeBSD: src/sys/dev/ipmi/ipmi.c,v 1.16 2011/11/07 15:43:11 ed Exp $ */
28 
29 /*
30  * Copyright 2012, Joyent, Inc.  All rights reserved.
31  */
32 
33 #include <sys/devops.h>
34 #include <sys/conf.h>
35 #include <sys/modctl.h>
36 #include <sys/types.h>
37 #include <sys/file.h>
38 #include <sys/errno.h>
39 #include <sys/open.h>
40 #include <sys/cred.h>
41 #include <sys/uio.h>
42 #include <sys/stat.h>
43 #include <sys/cmn_err.h>
44 #include <sys/ddi.h>
45 #include <sys/sunddi.h>
46 #include <sys/smbios.h>
47 #include <sys/smbios_impl.h>
48 #include <sys/ipmi.h>
49 #include "ipmivars.h"
50 
51 static kmutex_t		slpmutex;
52 static kcondvar_t	slplock;
53 
54 /*
55  * Request management.
56  */
57 
58 /* Allocate a new request with request and reply buffers. */
59 struct ipmi_request *
60 ipmi_alloc_request(struct ipmi_device *dev, long msgid, uint8_t addr,
61     uint8_t command, size_t requestlen, size_t replylen)
62 {
63 	struct ipmi_request *req;
64 
65 	req = kmem_zalloc(sizeof (struct ipmi_request) + requestlen + replylen,
66 	    KM_SLEEP);
67 	req->ir_sz = sizeof (struct ipmi_request) + requestlen + replylen;
68 	req->ir_owner = dev;
69 	req->ir_msgid = msgid;
70 	req->ir_addr = addr;
71 	req->ir_command = command;
72 	if (requestlen) {
73 		req->ir_request = (uchar_t *)&req[1];
74 		req->ir_requestlen = requestlen;
75 	}
76 	if (replylen) {
77 		req->ir_reply = (uchar_t *)&req[1] + requestlen;
78 		req->ir_replybuflen = replylen;
79 	}
80 	return (req);
81 }
82 
83 /* Free a request no longer in use. */
84 void
85 ipmi_free_request(struct ipmi_request *req)
86 {
87 	kmem_free(req, req->ir_sz);
88 }
89 
90 /* Store a processed request on the appropriate completion queue. */
91 /*ARGSUSED*/
92 void
93 ipmi_complete_request(struct ipmi_softc *sc, struct ipmi_request *req)
94 {
95 	struct ipmi_device *dev;
96 
97 	IPMI_LOCK_ASSERT(sc);
98 
99 	/*
100 	 * Anonymous requests (from inside the driver) always have a
101 	 * waiter that we awaken.
102 	 */
103 	if (req->ir_owner == NULL) {
104 		mutex_enter(&slpmutex);
105 		cv_signal(&slplock);
106 		mutex_exit(&slpmutex);
107 	} else {
108 		dev = req->ir_owner;
109 		TAILQ_INSERT_TAIL(&dev->ipmi_completed_requests, req, ir_link);
110 		pollwakeup(dev->ipmi_pollhead, POLLIN | POLLRDNORM);
111 	}
112 }
113 
114 /*
115  * Enqueue an internal driver request and wait until it is completed.
116  */
117 static int
118 ipmi_submit_driver_request(struct ipmi_softc *sc, struct ipmi_request *req,
119     int timo)
120 {
121 	int error;
122 
123 	IPMI_LOCK(sc);
124 	error = sc->ipmi_enqueue_request(sc, req);
125 	if (error == 0) {
126 		/* Wait for result - see ipmi_complete_request */
127 		IPMI_UNLOCK(sc);
128 		mutex_enter(&slpmutex);
129 		if (timo == 0)
130 			cv_wait(&slplock, &slpmutex);
131 		else
132 			error = cv_timedwait(&slplock, &slpmutex,
133 			    ddi_get_lbolt() + timo);
134 		mutex_exit(&slpmutex);
135 		IPMI_LOCK(sc);
136 		if (error == -1)
137 			error = EWOULDBLOCK;
138 		else
139 			error = req->ir_error;
140 	}
141 	IPMI_UNLOCK(sc);
142 
143 	return (error);
144 }
145 
146 /*
147  * Helper routine for polled system interfaces that use
148  * ipmi_polled_enqueue_request() to queue requests.  This request
149  * waits until there is a pending request and then returns the first
150  * request.  If the driver is shutting down, it returns NULL.
151  */
152 struct ipmi_request *
153 ipmi_dequeue_request(struct ipmi_softc *sc)
154 {
155 	struct ipmi_request *req;
156 
157 	IPMI_LOCK_ASSERT(sc);
158 
159 	while (!sc->ipmi_detaching && TAILQ_EMPTY(&sc->ipmi_pending_requests))
160 		cv_wait(&sc->ipmi_request_added, &sc->ipmi_lock);
161 	if (sc->ipmi_detaching)
162 		return (NULL);
163 
164 	req = TAILQ_FIRST(&sc->ipmi_pending_requests);
165 	TAILQ_REMOVE(&sc->ipmi_pending_requests, req, ir_link);
166 	return (req);
167 }
168 
169 int
170 ipmi_polled_enqueue_request(struct ipmi_softc *sc, struct ipmi_request *req)
171 {
172 
173 	IPMI_LOCK_ASSERT(sc);
174 
175 	TAILQ_INSERT_TAIL(&sc->ipmi_pending_requests, req, ir_link);
176 	cv_signal(&sc->ipmi_request_added);
177 	return (0);
178 }
179 
180 void
181 ipmi_startup(struct ipmi_softc *sc)
182 {
183 	struct ipmi_request *req;
184 	int error, i;
185 
186 	mutex_init(&slpmutex, NULL, MUTEX_DEFAULT, NULL);
187 	cv_init(&slplock, NULL, CV_DEFAULT, NULL);
188 
189 	/* Initialize interface-independent state. */
190 	mutex_init(&sc->ipmi_lock, NULL, MUTEX_DEFAULT, NULL);
191 	cv_init(&sc->ipmi_request_added, NULL, CV_DEFAULT, NULL);
192 	TAILQ_INIT(&sc->ipmi_pending_requests);
193 
194 	/* Initialize interface-dependent state. */
195 	error = sc->ipmi_startup(sc);
196 	if (error) {
197 		cmn_err(CE_WARN, "Failed to initialize interface: %d", error);
198 		return;
199 	}
200 
201 	/* Send a GET_DEVICE_ID request. */
202 	req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
203 	    IPMI_GET_DEVICE_ID, 0, 15);
204 
205 	error = ipmi_submit_driver_request(sc, req, MAX_TIMEOUT);
206 	if (error == EWOULDBLOCK) {
207 		cmn_err(CE_WARN, "Timed out waiting for GET_DEVICE_ID");
208 		ipmi_free_request(req);
209 		return;
210 	} else if (error) {
211 		cmn_err(CE_WARN, "Failed GET_DEVICE_ID: %d", error);
212 		ipmi_free_request(req);
213 		return;
214 	} else if (req->ir_compcode != 0) {
215 		cmn_err(CE_WARN,
216 		    "Bad completion code for GET_DEVICE_ID: %d",
217 		    req->ir_compcode);
218 		ipmi_free_request(req);
219 		return;
220 	} else if (req->ir_replylen < 5) {
221 		cmn_err(CE_WARN, "Short reply for GET_DEVICE_ID: %d",
222 		    req->ir_replylen);
223 		ipmi_free_request(req);
224 		return;
225 	}
226 
227 	cmn_err(CE_CONT, "!device rev. %d, firmware rev. %d.%d%d, "
228 	    "version %d.%d",
229 	    req->ir_reply[1] & 0x0f, req->ir_reply[2] & 0x7f,
230 	    req->ir_reply[3] >> 4, req->ir_reply[3] & 0x0f,
231 	    req->ir_reply[4] & 0x0f, req->ir_reply[4] >> 4);
232 
233 	ipmi_free_request(req);
234 
235 	req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
236 	    IPMI_CLEAR_FLAGS, 1, 0);
237 
238 	if ((error = ipmi_submit_driver_request(sc, req, 0)) != 0)
239 		cmn_err(CE_WARN, "Failed to clear IPMI flags: %d\n", error);
240 
241 	/* Magic numbers */
242 	if (req->ir_compcode == 0xc0) {
243 		cmn_err(CE_NOTE, "!Clear flags is busy");
244 	}
245 	if (req->ir_compcode == 0xc1) {
246 		cmn_err(CE_NOTE, "!Clear flags illegal");
247 	}
248 	ipmi_free_request(req);
249 
250 	for (i = 0; i < 8; i++) {
251 		req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
252 		    IPMI_GET_CHANNEL_INFO, 1, 0);
253 		req->ir_request[0] = (uchar_t)i;
254 
255 		if (ipmi_submit_driver_request(sc, req, 0) != 0) {
256 			ipmi_free_request(req);
257 			break;
258 		}
259 
260 		if (req->ir_compcode != 0) {
261 			ipmi_free_request(req);
262 			break;
263 		}
264 		ipmi_free_request(req);
265 	}
266 	cmn_err(CE_CONT, "!number of channels %d", i);
267 
268 	/* probe for watchdog */
269 	req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
270 	    IPMI_GET_WDOG, 0, 0);
271 
272 	if ((error = ipmi_submit_driver_request(sc, req, 0)) != 0) {
273 		cmn_err(CE_WARN, "Failed to check IPMI watchdog: %d\n", error);
274 		ipmi_free_request(req);
275 		return;
276 	}
277 
278 	if (req->ir_compcode == 0x00) {
279 		cmn_err(CE_CONT, "!watchdog supported");
280 
281 		/*
282 		 * Here is where we could register a watchdog event handler.
283 		 * See ipmi_wd_event() in the FreeBSD code.
284 		 */
285 	}
286 	ipmi_free_request(req);
287 }
288