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 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
32 */
33
34 #include <sys/devops.h>
35 #include <sys/conf.h>
36 #include <sys/modctl.h>
37 #include <sys/types.h>
38 #include <sys/file.h>
39 #include <sys/errno.h>
40 #include <sys/open.h>
41 #include <sys/cred.h>
42 #include <sys/uio.h>
43 #include <sys/stat.h>
44 #include <sys/cmn_err.h>
45 #include <sys/ddi.h>
46 #include <sys/sunddi.h>
47 #include <sys/smbios.h>
48 #include <sys/smbios_impl.h>
49 #include <sys/ipmi.h>
50 #include "ipmivars.h"
51
52 /*
53 * Request management.
54 */
55
56 /* Allocate a new request with request and reply buffers. */
57 struct ipmi_request *
ipmi_alloc_request(struct ipmi_device * dev,long msgid,uint8_t addr,uint8_t command,size_t requestlen,size_t replylen)58 ipmi_alloc_request(struct ipmi_device *dev, long msgid, uint8_t addr,
59 uint8_t command, size_t requestlen, size_t replylen)
60 {
61 struct ipmi_request *req;
62
63 req = kmem_zalloc(sizeof (struct ipmi_request) + requestlen + replylen,
64 KM_SLEEP);
65 req->ir_sz = sizeof (struct ipmi_request) + requestlen + replylen;
66 req->ir_owner = dev;
67 req->ir_msgid = msgid;
68 req->ir_addr = addr;
69 req->ir_command = command;
70 if (requestlen) {
71 req->ir_request = (uchar_t *)&req[1];
72 req->ir_requestlen = requestlen;
73 }
74 if (replylen) {
75 req->ir_reply = (uchar_t *)&req[1] + requestlen;
76 req->ir_replybuflen = replylen;
77 }
78
79 cv_init(&req->ir_cv, NULL, CV_DEFAULT, NULL);
80 req->ir_status = IRS_ALLOCATED;
81
82 return (req);
83 }
84
85 /* Free a request no longer in use. */
86 void
ipmi_free_request(struct ipmi_request * req)87 ipmi_free_request(struct ipmi_request *req)
88 {
89 if (req == NULL)
90 return;
91
92 cv_destroy(&req->ir_cv);
93
94 kmem_free(req, req->ir_sz);
95 }
96
97 /* Store a processed request on the appropriate completion queue. */
98 /*ARGSUSED*/
99 void
ipmi_complete_request(struct ipmi_softc * sc,struct ipmi_request * req)100 ipmi_complete_request(struct ipmi_softc *sc, struct ipmi_request *req)
101 {
102 struct ipmi_device *dev;
103
104 IPMI_LOCK_ASSERT(sc);
105
106 if (req->ir_status == IRS_CANCELED) {
107 ASSERT(req->ir_owner == NULL);
108 ipmi_free_request(req);
109 return;
110 }
111
112 req->ir_status = IRS_COMPLETED;
113
114 /*
115 * Anonymous requests (from inside the driver) always have a
116 * waiter that we awaken.
117 */
118 if (req->ir_owner == NULL) {
119 cv_signal(&req->ir_cv);
120 } else {
121 dev = req->ir_owner;
122 TAILQ_INSERT_TAIL(&dev->ipmi_completed_requests, req, ir_link);
123 pollwakeup(&dev->ipmi_pollhead, POLLIN | POLLRDNORM);
124
125 dev->ipmi_status &= ~IPMI_BUSY;
126 if (dev->ipmi_status & IPMI_CLOSING)
127 cv_signal(&dev->ipmi_cv);
128 }
129 }
130
131 /*
132 * Enqueue an internal driver request and wait until it is completed.
133 */
134 static int
ipmi_submit_driver_request(struct ipmi_softc * sc,struct ipmi_request ** preq,int timo)135 ipmi_submit_driver_request(struct ipmi_softc *sc, struct ipmi_request **preq,
136 int timo)
137 {
138 int error;
139 struct ipmi_request *req = *preq;
140
141 ASSERT(req->ir_owner == NULL);
142
143 IPMI_LOCK(sc);
144 error = sc->ipmi_enqueue_request(sc, req);
145
146 if (error != 0) {
147 IPMI_UNLOCK(sc);
148 return (error);
149 }
150
151 while (req->ir_status != IRS_COMPLETED && error >= 0)
152 if (timo == 0)
153 cv_wait(&req->ir_cv, &sc->ipmi_lock);
154 else
155 error = cv_timedwait(&req->ir_cv, &sc->ipmi_lock,
156 ddi_get_lbolt() + timo);
157
158 switch (req->ir_status) {
159 case IRS_QUEUED:
160 TAILQ_REMOVE(&sc->ipmi_pending_requests, req, ir_link);
161 req->ir_status = IRS_CANCELED;
162 error = EWOULDBLOCK;
163 break;
164 case IRS_PROCESSED:
165 req->ir_status = IRS_CANCELED;
166 error = EWOULDBLOCK;
167 *preq = NULL;
168 break;
169 case IRS_COMPLETED:
170 error = req->ir_error;
171 break;
172 default:
173 panic("IPMI: Invalid request status");
174 break;
175 }
176 IPMI_UNLOCK(sc);
177
178 return (error);
179 }
180
181 /*
182 * Helper routine for polled system interfaces that use
183 * ipmi_polled_enqueue_request() to queue requests. This request
184 * waits until there is a pending request and then returns the first
185 * request. If the driver is shutting down, it returns NULL.
186 */
187 struct ipmi_request *
ipmi_dequeue_request(struct ipmi_softc * sc)188 ipmi_dequeue_request(struct ipmi_softc *sc)
189 {
190 struct ipmi_request *req;
191
192 IPMI_LOCK_ASSERT(sc);
193
194 while (!sc->ipmi_detaching && TAILQ_EMPTY(&sc->ipmi_pending_requests))
195 cv_wait(&sc->ipmi_request_added, &sc->ipmi_lock);
196 if (sc->ipmi_detaching)
197 return (NULL);
198
199 req = TAILQ_FIRST(&sc->ipmi_pending_requests);
200 TAILQ_REMOVE(&sc->ipmi_pending_requests, req, ir_link);
201 req->ir_status = IRS_PROCESSED;
202
203 if (req->ir_owner != NULL)
204 req->ir_owner->ipmi_status |= IPMI_BUSY;
205
206 return (req);
207 }
208
209 int
ipmi_polled_enqueue_request(struct ipmi_softc * sc,struct ipmi_request * req)210 ipmi_polled_enqueue_request(struct ipmi_softc *sc, struct ipmi_request *req)
211 {
212
213 IPMI_LOCK_ASSERT(sc);
214
215 TAILQ_INSERT_TAIL(&sc->ipmi_pending_requests, req, ir_link);
216 req->ir_status = IRS_QUEUED;
217 cv_signal(&sc->ipmi_request_added);
218 return (0);
219 }
220
221 void
ipmi_shutdown(struct ipmi_softc * sc)222 ipmi_shutdown(struct ipmi_softc *sc)
223 {
224 taskq_destroy(sc->ipmi_kthread);
225
226 cv_destroy(&sc->ipmi_request_added);
227 mutex_destroy(&sc->ipmi_lock);
228 }
229
230 boolean_t
ipmi_startup(struct ipmi_softc * sc)231 ipmi_startup(struct ipmi_softc *sc)
232 {
233 struct ipmi_request *req;
234 int error, i;
235
236 /* Initialize interface-independent state. */
237 mutex_init(&sc->ipmi_lock, NULL, MUTEX_DEFAULT, NULL);
238 cv_init(&sc->ipmi_request_added, NULL, CV_DEFAULT, NULL);
239 TAILQ_INIT(&sc->ipmi_pending_requests);
240
241 /* Initialize interface-dependent state. */
242 error = sc->ipmi_startup(sc);
243 if (error) {
244 cmn_err(CE_WARN, "Failed to initialize interface: %d", error);
245 return (B_FALSE);
246 }
247
248 /* Send a GET_DEVICE_ID request. */
249 req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
250 IPMI_GET_DEVICE_ID, 0, 15);
251
252 error = ipmi_submit_driver_request(sc, &req, MAX_TIMEOUT);
253 if (error == EWOULDBLOCK) {
254 cmn_err(CE_WARN, "Timed out waiting for GET_DEVICE_ID");
255 ipmi_free_request(req);
256 return (B_FALSE);
257 } else if (error) {
258 cmn_err(CE_WARN, "Failed GET_DEVICE_ID: %d", error);
259 ipmi_free_request(req);
260 return (B_FALSE);
261 } else if (req->ir_compcode != 0) {
262 cmn_err(CE_WARN,
263 "Bad completion code for GET_DEVICE_ID: %d",
264 req->ir_compcode);
265 ipmi_free_request(req);
266 return (B_FALSE);
267 } else if (req->ir_replylen < 5) {
268 cmn_err(CE_WARN, "Short reply for GET_DEVICE_ID: %d",
269 req->ir_replylen);
270 ipmi_free_request(req);
271 return (B_FALSE);
272 }
273
274 cmn_err(CE_CONT, "!device rev. %d, firmware rev. %d.%d%d, "
275 "version %d.%d",
276 req->ir_reply[1] & 0x0f, req->ir_reply[2] & 0x7f,
277 req->ir_reply[3] >> 4, req->ir_reply[3] & 0x0f,
278 req->ir_reply[4] & 0x0f, req->ir_reply[4] >> 4);
279
280 ipmi_free_request(req);
281
282 req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
283 IPMI_CLEAR_FLAGS, 1, 0);
284
285 if ((error = ipmi_submit_driver_request(sc, &req, 0)) != 0) {
286 cmn_err(CE_WARN, "Failed to clear IPMI flags: %d\n", error);
287 ipmi_free_request(req);
288 return (B_FALSE);
289 }
290
291 /* Magic numbers */
292 if (req->ir_compcode == 0xc0) {
293 cmn_err(CE_NOTE, "!Clear flags is busy");
294 }
295 if (req->ir_compcode == 0xc1) {
296 cmn_err(CE_NOTE, "!Clear flags illegal");
297 }
298 ipmi_free_request(req);
299
300 for (i = 0; i < 8; i++) {
301 req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
302 IPMI_GET_CHANNEL_INFO, 1, 0);
303 req->ir_request[0] = (uchar_t)i;
304
305 if (ipmi_submit_driver_request(sc, &req, 0) != 0) {
306 ipmi_free_request(req);
307 break;
308 }
309
310 if (req->ir_compcode != 0) {
311 ipmi_free_request(req);
312 break;
313 }
314 ipmi_free_request(req);
315 }
316 cmn_err(CE_CONT, "!number of channels %d", i);
317
318 /* probe for watchdog */
319 req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0),
320 IPMI_GET_WDOG, 0, 0);
321
322 if ((error = ipmi_submit_driver_request(sc, &req, 0)) != 0) {
323 cmn_err(CE_WARN, "Failed to check IPMI watchdog: %d\n", error);
324 ipmi_free_request(req);
325 return (B_FALSE);
326 }
327
328 if (req->ir_compcode == 0x00) {
329 cmn_err(CE_CONT, "!watchdog supported");
330
331 /*
332 * Here is where we could register a watchdog event handler.
333 * See ipmi_wd_event() in the FreeBSD code.
334 */
335 }
336 ipmi_free_request(req);
337
338 return (B_TRUE);
339 }
340