xref: /dragonfly/sys/dev/misc/ipmi/ipmi_smic.c (revision 73610d44)
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  * $FreeBSD: head/sys/dev/ipmi/ipmi_smic.c 248705 2013-03-25 14:30:34Z melifaro $
27  */
28 
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/bus.h>
32 #include <sys/condvar.h>
33 #include <sys/eventhandler.h>
34 #include <sys/kernel.h>
35 #include <sys/kthread.h>
36 #include <sys/module.h>
37 #include <sys/rman.h>
38 #include <sys/conf.h>
39 
40 #ifdef LOCAL_MODULE
41 #include <ipmi.h>
42 #include <ipmivars.h>
43 #else
44 #include <sys/ipmi.h>
45 #include <dev/misc/ipmi/ipmivars.h>
46 #endif
47 
48 static void	smic_wait_for_tx_okay(struct ipmi_softc *);
49 static void	smic_wait_for_rx_okay(struct ipmi_softc *);
50 static void	smic_wait_for_not_busy(struct ipmi_softc *);
51 static void	smic_set_busy(struct ipmi_softc *);
52 
53 static void
54 smic_wait_for_tx_okay(struct ipmi_softc *sc)
55 {
56 	int flags;
57 
58 	do {
59 		flags = INB(sc, SMIC_FLAGS);
60 	} while (!(flags & SMIC_STATUS_TX_RDY));
61 }
62 
63 static void
64 smic_wait_for_rx_okay(struct ipmi_softc *sc)
65 {
66 	int flags;
67 
68 	do {
69 		flags = INB(sc, SMIC_FLAGS);
70 	} while (!(flags & SMIC_STATUS_RX_RDY));
71 }
72 
73 static void
74 smic_wait_for_not_busy(struct ipmi_softc *sc)
75 {
76 	int flags;
77 
78 	do {
79 		flags = INB(sc, SMIC_FLAGS);
80 	} while (flags & SMIC_STATUS_BUSY);
81 }
82 
83 static void
84 smic_set_busy(struct ipmi_softc *sc)
85 {
86 	int flags;
87 
88 	flags = INB(sc, SMIC_FLAGS);
89 	flags |= SMIC_STATUS_BUSY;
90 	flags &= ~SMIC_STATUS_RESERVED;
91 	OUTB(sc, SMIC_FLAGS, flags);
92 }
93 
94 /*
95  * Start a transfer with a WR_START transaction that sends the NetFn/LUN
96  * address.
97  */
98 static int
99 smic_start_write(struct ipmi_softc *sc, u_char data)
100 {
101 	u_char error, status;
102 
103 	smic_wait_for_not_busy(sc);
104 
105 	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_START);
106 	OUTB(sc, SMIC_DATA, data);
107 	smic_set_busy(sc);
108 	smic_wait_for_not_busy(sc);
109 	status = INB(sc, SMIC_CTL_STS);
110 	if (status != SMIC_SC_SMS_WR_START) {
111 		error = INB(sc, SMIC_DATA);
112 		device_printf(sc->ipmi_dev, "SMIC: Write did not start %02x\n",
113 		    error);
114 		return (0);
115 	}
116 	return (1);
117 }
118 
119 /*
120  * Write a byte in the middle of the message (either the command or one of
121  * the data bytes) using a WR_NEXT transaction.
122  */
123 static int
124 smic_write_next(struct ipmi_softc *sc, u_char data)
125 {
126 	u_char error, status;
127 
128 	smic_wait_for_tx_okay(sc);
129 	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT);
130 	OUTB(sc, SMIC_DATA, data);
131 	smic_set_busy(sc);
132 	smic_wait_for_not_busy(sc);
133 	status = INB(sc, SMIC_CTL_STS);
134 	if (status != SMIC_SC_SMS_WR_NEXT) {
135 		error = INB(sc, SMIC_DATA);
136 		device_printf(sc->ipmi_dev, "SMIC: Write did not next %02x\n",
137 		    error);
138 		return (0);
139 	}
140 	return (1);
141 }
142 
143 /*
144  * Write the last byte of a transfer to end the write phase via a WR_END
145  * transaction.
146  */
147 static int
148 smic_write_last(struct ipmi_softc *sc, u_char data)
149 {
150 	u_char error, status;
151 
152 	smic_wait_for_tx_okay(sc);
153 	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END);
154 	OUTB(sc, SMIC_DATA, data);
155 	smic_set_busy(sc);
156 	smic_wait_for_not_busy(sc);
157 	status = INB(sc, SMIC_CTL_STS);
158 	if (status != SMIC_SC_SMS_WR_END) {
159 		error = INB(sc, SMIC_DATA);
160 		device_printf(sc->ipmi_dev, "SMIC: Write did not end %02x\n",
161 		    error);
162 		return (0);
163 	}
164 	return (1);
165 }
166 
167 /*
168  * Start the read phase of a transfer with a RD_START transaction.
169  */
170 static int
171 smic_start_read(struct ipmi_softc *sc, u_char *data)
172 {
173 	u_char error, status;
174 
175 	smic_wait_for_not_busy(sc);
176 
177 	smic_wait_for_rx_okay(sc);
178 	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START);
179 	smic_set_busy(sc);
180 	smic_wait_for_not_busy(sc);
181 	status = INB(sc, SMIC_CTL_STS);
182 	if (status != SMIC_SC_SMS_RD_START) {
183 		error = INB(sc, SMIC_DATA);
184 		device_printf(sc->ipmi_dev, "SMIC: Read did not start %02x\n",
185 		    error);
186 		return (0);
187 	}
188 	*data = INB(sc, SMIC_DATA);
189 	return (1);
190 }
191 
192 /*
193  * Read a byte via a RD_NEXT transaction.  If this was the last byte, return
194  * 2 rather than 1.
195  */
196 static int
197 smic_read_byte(struct ipmi_softc *sc, u_char *data)
198 {
199 	u_char error, status;
200 
201 	smic_wait_for_rx_okay(sc);
202 	OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT);
203 	smic_set_busy(sc);
204 	smic_wait_for_not_busy(sc);
205 	status = INB(sc, SMIC_CTL_STS);
206 	if (status != SMIC_SC_SMS_RD_NEXT &&
207 	    status != SMIC_SC_SMS_RD_END) {
208 		error = INB(sc, SMIC_DATA);
209 		device_printf(sc->ipmi_dev, "SMIC: Read did not next %02x\n",
210 		    error);
211 		return (0);
212 	}
213 	*data = INB(sc, SMIC_DATA);
214 	if (status == SMIC_SC_SMS_RD_NEXT)
215 		return (1);
216 	else
217 		return (2);
218 }
219 
220 /* Complete a transfer via a RD_END transaction after reading the last byte. */
221 static int
222 smic_read_end(struct ipmi_softc *sc)
223 {
224 	u_char error, status;
225 
226 	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_END);
227 	smic_set_busy(sc);
228 	smic_wait_for_not_busy(sc);
229 	status = INB(sc, SMIC_CTL_STS);
230 	if (status != SMIC_SC_SMS_RDY) {
231 		error = INB(sc, SMIC_DATA);
232 		device_printf(sc->ipmi_dev, "SMIC: Read did not end %02x\n",
233 		    error);
234 		return (0);
235 	}
236 	return (1);
237 }
238 
239 static int
240 smic_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
241 {
242 	u_char *cp, data;
243 	int i, state;
244 
245 	/* First, start the message with the address. */
246 	if (!smic_start_write(sc, req->ir_addr))
247 		return (0);
248 #ifdef SMIC_DEBUG
249 	device_printf(sc->ipmi_dev, "SMIC: WRITE_START address: %02x\n",
250 	    req->ir_addr);
251 #endif
252 
253 	if (req->ir_requestlen == 0) {
254 		/* Send the command as the last byte. */
255 		if (!smic_write_last(sc, req->ir_command))
256 			return (0);
257 #ifdef SMIC_DEBUG
258 		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
259 		    req->ir_command);
260 #endif
261 	} else {
262 		/* Send the command. */
263 		if (!smic_write_next(sc, req->ir_command))
264 			return (0);
265 #ifdef SMIC_DEBUG
266 		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
267 		    req->ir_command);
268 #endif
269 
270 		/* Send the payload. */
271 		cp = req->ir_request;
272 		for (i = 0; i < req->ir_requestlen - 1; i++) {
273 			if (!smic_write_next(sc, *cp++))
274 				return (0);
275 #ifdef SMIC_DEBUG
276 			device_printf(sc->ipmi_dev, "SMIC: Wrote data: %02x\n",
277 			    cp[-1]);
278 #endif
279 		}
280 		if (!smic_write_last(sc, *cp))
281 			return (0);
282 #ifdef SMIC_DEBUG
283 		device_printf(sc->ipmi_dev, "SMIC: Write last data: %02x\n",
284 		    *cp);
285 #endif
286 	}
287 
288 	/* Start the read phase by reading the NetFn/LUN. */
289 	if (smic_start_read(sc, &data) != 1)
290 		return (0);
291 #ifdef SMIC_DEBUG
292 	device_printf(sc->ipmi_dev, "SMIC: Read address: %02x\n", data);
293 #endif
294 	if (data != IPMI_REPLY_ADDR(req->ir_addr)) {
295 		device_printf(sc->ipmi_dev, "SMIC: Reply address mismatch\n");
296 		return (0);
297 	}
298 
299 	/* Read the command. */
300 	if (smic_read_byte(sc, &data) != 1)
301 		return (0);
302 #ifdef SMIC_DEBUG
303 	device_printf(sc->ipmi_dev, "SMIC: Read command: %02x\n", data);
304 #endif
305 	if (data != req->ir_command) {
306 		device_printf(sc->ipmi_dev, "SMIC: Command mismatch\n");
307 		return (0);
308 	}
309 
310 	/* Read the completion code. */
311 	state = smic_read_byte(sc, &req->ir_compcode);
312 	if (state == 0)
313 		return (0);
314 #ifdef SMIC_DEBUG
315 	device_printf(sc->ipmi_dev, "SMIC: Read completion code: %02x\n",
316 	    req->ir_compcode);
317 #endif
318 
319 	/* Finally, read the reply from the BMC. */
320 	i = 0;
321 	while (state == 1) {
322 		state = smic_read_byte(sc, &data);
323 		if (state == 0)
324 			return (0);
325 		if (i < req->ir_replybuflen) {
326 			req->ir_reply[i] = data;
327 #ifdef SMIC_DEBUG
328 			device_printf(sc->ipmi_dev, "SMIC: Read data: %02x\n",
329 			    data);
330 		} else {
331 			device_printf(sc->ipmi_dev,
332 			    "SMIC: Read short %02x byte %d\n", data, i + 1);
333 #endif
334 		}
335 		i++;
336 	}
337 
338 	/* Terminate the transfer. */
339 	if (!smic_read_end(sc))
340 		return (0);
341 	req->ir_replylen = i;
342 #ifdef SMIC_DEBUG
343 	device_printf(sc->ipmi_dev, "SMIC: READ finished (%d bytes)\n", i);
344 	if (req->ir_replybuflen < i)
345 #else
346 	if (req->ir_replybuflen < i && req->ir_replybuflen != 0)
347 #endif
348 		device_printf(sc->ipmi_dev,
349 		    "SMIC: Read short: %zd buffer, %d actual\n",
350 		    req->ir_replybuflen, i);
351 	return (1);
352 }
353 
354 static void
355 smic_loop(void *arg)
356 {
357 	struct ipmi_softc *sc = arg;
358 	struct ipmi_request *req;
359 	int i, ok;
360 
361 	IPMI_LOCK(sc);
362 	while ((req = ipmi_dequeue_request(sc)) != NULL) {
363 		IPMI_UNLOCK(sc);
364 		ok = 0;
365 		for (i = 0; i < 3 && !ok; i++)
366 			ok = smic_polled_request(sc, req);
367 		if (ok)
368 			req->ir_error = 0;
369 		else
370 			req->ir_error = EIO;
371 		IPMI_LOCK(sc);
372 		ipmi_complete_request(sc, req);
373 	}
374 	IPMI_UNLOCK(sc);
375 	kthread_exit();
376 }
377 
378 static int
379 smic_startup(struct ipmi_softc *sc)
380 {
381 
382 	return (kthread_create(smic_loop, sc, &sc->ipmi_kthread,
383 	    "%s: smic", device_get_nameunit(sc->ipmi_dev)));
384 }
385 
386 int
387 ipmi_smic_attach(struct ipmi_softc *sc)
388 {
389 	int flags;
390 
391 	/* Setup function pointers. */
392 	sc->ipmi_startup = smic_startup;
393 	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
394 
395 	/* See if we can talk to the controller. */
396 	flags = INB(sc, SMIC_FLAGS);
397 	if (flags == 0xff) {
398 		device_printf(sc->ipmi_dev, "couldn't find it\n");
399 		return (ENXIO);
400 	}
401 
402 #ifdef SMIC_DEBUG
403 	device_printf(sc->ipmi_dev, "SMIC: initial state: %02x\n", flags);
404 #endif
405 
406 	return (0);
407 }
408