xref: /freebsd/sys/dev/ipmi/ipmi_ssif.c (revision 43faedc1339a9624c7acedb7f3e5624e64da5b99)
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/selinfo.h>
39 
40 #include <dev/smbus/smbconf.h>
41 #include <dev/smbus/smb.h>
42 
43 #include "smbus_if.h"
44 
45 #ifdef LOCAL_MODULE
46 #include <ipmivars.h>
47 #else
48 #include <dev/ipmi/ipmivars.h>
49 #endif
50 
51 #define SMBUS_WRITE_SINGLE	0x02
52 #define SMBUS_WRITE_START	0x06
53 #define SMBUS_WRITE_CONT	0x07
54 #define SMBUS_READ_START	0x03
55 #define SMBUS_READ_CONT		0x09
56 #define SMBUS_DATA_SIZE		32
57 
58 #ifdef SSIF_DEBUG
59 static void
60 dump_buffer(device_t dev, const char *msg, u_char *bytes, int len)
61 {
62 	int i;
63 
64 	device_printf(dev, "%s:", msg);
65 	for (i = 0; i < len; i++)
66 		printf(" %02x", bytes[i]);
67 	printf("\n");
68 }
69 #endif
70 
71 static int
72 ssif_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
73 {
74 	u_char ssif_buf[SMBUS_DATA_SIZE];
75 	device_t dev = sc->ipmi_dev;
76 	device_t smbus = sc->ipmi_ssif_smbus;
77 	u_char *cp, block, count, offset;
78 	size_t len;
79 	int error;
80 
81 	/* Acquire the bus while we send the request. */
82 	if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
83 		return (0);
84 
85 	/*
86 	 * First, send out the request.  Begin by filling out the first
87 	 * packet which includes the NetFn/LUN and command.
88 	 */
89 	ssif_buf[0] = req->ir_addr;
90 	ssif_buf[1] = req->ir_command;
91 	if (req->ir_requestlen > 0)
92 		bcopy(req->ir_request, &ssif_buf[2],
93 		    min(req->ir_requestlen, SMBUS_DATA_SIZE - 2));
94 
95 	/* Small requests are sent with a single command. */
96 	if (req->ir_requestlen <= 30) {
97 #ifdef SSIF_DEBUG
98 		dump_buffer(dev, "WRITE_SINGLE", ssif_buf,
99 		    req->ir_requestlen + 2);
100 #endif
101 		error = smbus_error(smbus_bwrite(smbus,
102 			sc->ipmi_ssif_smbus_address, SMBUS_WRITE_SINGLE,
103 			req->ir_requestlen + 2, ssif_buf));
104 		if (error) {
105 #ifdef SSIF_ERROR_DEBUG
106 			device_printf(dev, "SSIF: WRITE_SINGLE error %d\n",
107 			    error);
108 #endif
109 			goto fail;
110 		}
111 	} else {
112 		/* Longer requests are sent out in 32-byte messages. */
113 #ifdef SSIF_DEBUG
114 		dump_buffer(dev, "WRITE_START", ssif_buf, SMBUS_DATA_SIZE);
115 #endif
116 		error = smbus_error(smbus_bwrite(smbus,
117 			sc->ipmi_ssif_smbus_address, SMBUS_WRITE_START,
118 			SMBUS_DATA_SIZE, ssif_buf));
119 		if (error) {
120 #ifdef SSIF_ERROR_DEBUG
121 			device_printf(dev, "SSIF: WRITE_START error %d\n",
122 			    error);
123 #endif
124 			goto fail;
125 		}
126 
127 		len = req->ir_requestlen - (SMBUS_DATA_SIZE - 2);
128 		cp = req->ir_request + (SMBUS_DATA_SIZE - 2);
129 		while (len > 0) {
130 #ifdef SSIF_DEBUG
131 			dump_buffer(dev, "WRITE_CONT", cp,
132 			    min(len, SMBUS_DATA_SIZE));
133 #endif
134 			error = smbus_error(smbus_bwrite(smbus,
135 			    sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
136 			    min(len, SMBUS_DATA_SIZE), cp));
137 			if (error) {
138 #ifdef SSIF_ERROR_DEBUG
139 				device_printf(dev, "SSIF: WRITE_CONT error %d\n",
140 				    error);
141 #endif
142 				goto fail;
143 			}
144 			cp += SMBUS_DATA_SIZE;
145 			len -= SMBUS_DATA_SIZE;
146 		}
147 
148 		/*
149 		 * The final WRITE_CONT transaction has to have a non-zero
150 		 * length that is also not SMBUS_DATA_SIZE.  If our last
151 		 * WRITE_CONT transaction in the loop sent SMBUS_DATA_SIZE
152 		 * bytes, then len will be 0, and we send an extra 0x00 byte
153 		 * to terminate the transaction.
154 		 */
155 		if (len == 0) {
156 			char c = 0;
157 
158 #ifdef SSIF_DEBUG
159 			dump_buffer(dev, "WRITE_CONT", &c, 1);
160 #endif
161 			error = smbus_error(smbus_bwrite(smbus,
162 				sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
163 				1, &c));
164 			if (error) {
165 #ifdef SSIF_ERROR_DEBUG
166 				device_printf(dev, "SSIF: WRITE_CONT error %d\n",
167 				    error);
168 #endif
169 				goto fail;
170 			}
171 		}
172 	}
173 
174 	/* Release the bus. */
175 	smbus_release_bus(smbus, dev);
176 
177 	/* Give the BMC 100ms to chew on the request. */
178 	pause("ssifwt", hz / 10);
179 
180 	/* Try to read the first packet. */
181 read_start:
182 	if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
183 		return (0);
184 	count = SMBUS_DATA_SIZE;
185 	error = smbus_error(smbus_bread(smbus,
186 	    sc->ipmi_ssif_smbus_address, SMBUS_READ_START, &count, ssif_buf));
187 	if (error == ENXIO || error == EBUSY) {
188 		smbus_release_bus(smbus, dev);
189 #ifdef SSIF_DEBUG
190 		device_printf(dev, "SSIF: READ_START retry\n");
191 #endif
192 		/* Give the BMC another 10ms. */
193 		pause("ssifwt", hz / 100);
194 		goto read_start;
195 	}
196 	if (error) {
197 #ifdef SSIF_ERROR_DEBUG
198 		device_printf(dev, "SSIF: READ_START failed: %d\n", error);
199 #endif
200 		goto fail;
201 	}
202 #ifdef SSIF_DEBUG
203 	device_printf("SSIF: READ_START: ok\n");
204 #endif
205 
206 	/*
207 	 * If this is the first part of a multi-part read, then we need to
208 	 * skip the first two bytes.
209 	 */
210 	if (count == SMBUS_DATA_SIZE && ssif_buf[0] == 0 && ssif_buf[1] == 1)
211 		offset = 2;
212 	else
213 		offset = 0;
214 
215 	/* We had better get the reply header. */
216 	if (count < 3) {
217 		device_printf(dev, "SSIF: Short reply packet\n");
218 		goto fail;
219 	}
220 
221 	/* Verify the NetFn/LUN. */
222 	if (ssif_buf[offset] != IPMI_REPLY_ADDR(req->ir_addr)) {
223 		device_printf(dev, "SSIF: Reply address mismatch\n");
224 		goto fail;
225 	}
226 
227 	/* Verify the command. */
228 	if (ssif_buf[offset + 1] != req->ir_command) {
229 		device_printf(dev, "SMIC: Command mismatch\n");
230 		goto fail;
231 	}
232 
233 	/* Read the completion code. */
234 	req->ir_compcode = ssif_buf[offset + 2];
235 
236 	/* If this is a single read, just copy the data and return. */
237 	if (offset == 0) {
238 #ifdef SSIF_DEBUG
239 		dump_buffer(dev, "READ_SINGLE", ssif_buf, count);
240 #endif
241 		len = count - 3;
242 		bcopy(&ssif_buf[3], req->ir_reply,
243 		    min(req->ir_replybuflen, len));
244 		goto done;
245 	}
246 
247 	/*
248 	 * This is the first part of a multi-read transaction, so copy
249 	 * out the payload and start looping.
250 	 */
251 #ifdef SSIF_DEBUG
252 	dump_buffer(dev, "READ_START", ssif_buf + 2, count - 2);
253 #endif
254 	bcopy(&ssif_buf[5], req->ir_reply, min(req->ir_replybuflen, count - 5));
255 	len = count - 5;
256 	block = 1;
257 
258 	for (;;) {
259 		/* Read another packet via READ_CONT. */
260 		count = SMBUS_DATA_SIZE;
261 		error = smbus_error(smbus_bread(smbus,
262 		    sc->ipmi_ssif_smbus_address, SMBUS_READ_CONT, &count,
263 		    ssif_buf));
264 		if (error) {
265 #ifdef SSIF_ERROR_DEBUG
266 			printf("SSIF: READ_CONT failed: %d\n", error);
267 #endif
268 			goto fail;
269 		}
270 #ifdef SSIF_DEBUG
271 		device_printf(dev, "SSIF: READ_CONT... ok\n");
272 #endif
273 
274 		/* Verify the block number.  0xff marks the last block. */
275 		if (ssif_buf[0] != 0xff && ssif_buf[0] != block) {
276 			device_printf(dev, "SSIF: Read wrong block %d %d\n",
277 			    ssif_buf[0], block);
278 			goto fail;
279 		}
280 		if (ssif_buf[0] != 0xff && count < SMBUS_DATA_SIZE) {
281 			device_printf(dev,
282 			    "SSIF: Read short middle block, length %d\n",
283 			    count);
284 			goto fail;
285 		}
286 #ifdef SSIF_DEBUG
287 		if (ssif_buf[0] == 0xff)
288 			dump_buffer(dev, "READ_END", ssif_buf + 1, count - 1);
289 		else
290 			dump_buffer(dev, "READ_CONT", ssif_buf + 1, count - 1);
291 #endif
292 		if (len < req->ir_replybuflen)
293 			bcopy(&ssif_buf[1], &req->ir_reply[len],
294 			    min(req->ir_replybuflen - len, count - 1));
295 		len += count - 1;
296 
297 		/* If this was the last block we are done. */
298 		if (ssif_buf[0] != 0xff)
299 			break;
300 		block++;
301 	}
302 
303 done:
304 	/* Save the total length and return success. */
305 	req->ir_replylen = len;
306 	smbus_release_bus(smbus, dev);
307 	return (1);
308 
309 fail:
310 	smbus_release_bus(smbus, dev);
311 	return (0);
312 }
313 
314 static void
315 ssif_loop(void *arg)
316 {
317 	struct ipmi_softc *sc = arg;
318 	struct ipmi_request *req;
319 	int i, ok;
320 
321 	IPMI_LOCK(sc);
322 	while ((req = ipmi_dequeue_request(sc)) != NULL) {
323 		IPMI_UNLOCK(sc);
324 		ok = 0;
325 		for (i = 0; i < 5; i++) {
326 			ok = ssif_polled_request(sc, req);
327 			if (ok)
328 				break;
329 
330 			/* Wait 60 ms between retries. */
331 			pause("retry", 60 * hz / 1000);
332 #ifdef SSIF_RETRY_DEBUG
333 			device_printf(sc->ipmi_dev,
334 			    "SSIF: Retrying request (%d)\n", i + 1);
335 #endif
336 		}
337 		if (ok)
338 			req->ir_error = 0;
339 		else
340 			req->ir_error = EIO;
341 		IPMI_LOCK(sc);
342 		ipmi_complete_request(sc, req);
343 		IPMI_UNLOCK(sc);
344 
345 		/* Enforce 10ms between requests. */
346 		pause("delay", hz / 100);
347 
348 		IPMI_LOCK(sc);
349 	}
350 	IPMI_UNLOCK(sc);
351 	kproc_exit(0);
352 }
353 
354 static int
355 ssif_startup(struct ipmi_softc *sc)
356 {
357 
358 	return (kproc_create(ssif_loop, sc, &sc->ipmi_kthread, 0, 0,
359 	    "%s: ssif", device_get_nameunit(sc->ipmi_dev)));
360 }
361 
362 static int
363 ssif_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, int timo)
364 {
365 	int error;
366 
367 	IPMI_LOCK(sc);
368 	error = ipmi_polled_enqueue_request(sc, req);
369 	if (error == 0)
370 		error = msleep(req, &sc->ipmi_requests_lock, 0, "ipmireq",
371 		    timo);
372 	if (error == 0)
373 		error = req->ir_error;
374 	IPMI_UNLOCK(sc);
375 	return (error);
376 }
377 
378 int
379 ipmi_ssif_attach(struct ipmi_softc *sc, device_t smbus, int smbus_address)
380 {
381 
382 	/* Setup smbus address. */
383 	sc->ipmi_ssif_smbus = smbus;
384 	sc->ipmi_ssif_smbus_address = smbus_address;
385 
386 	/* Setup function pointers. */
387 	sc->ipmi_startup = ssif_startup;
388 	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
389 	sc->ipmi_driver_request = ssif_driver_request;
390 
391 	return (0);
392 }
393