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