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