xref: /freebsd/sys/dev/ipmi/ipmi_ssif.c (revision 4e8d558c)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/bus.h>
35 #include <sys/condvar.h>
36 #include <sys/eventhandler.h>
37 #include <sys/kernel.h>
38 #include <sys/kthread.h>
39 #include <sys/module.h>
40 #include <sys/selinfo.h>
41 
42 #include <dev/smbus/smbconf.h>
43 #include <dev/smbus/smb.h>
44 
45 #include "smbus_if.h"
46 
47 #ifdef LOCAL_MODULE
48 #include <ipmivars.h>
49 #else
50 #include <dev/ipmi/ipmivars.h>
51 #endif
52 
53 #define SMBUS_WRITE_SINGLE	0x02
54 #define SMBUS_WRITE_START	0x06
55 #define SMBUS_WRITE_CONT	0x07
56 #define SMBUS_READ_START	0x03
57 #define SMBUS_READ_CONT		0x09
58 #define SMBUS_DATA_SIZE		32
59 
60 #ifdef SSIF_DEBUG
61 static void
62 dump_buffer(device_t dev, const char *msg, u_char *bytes, int len)
63 {
64 	int i;
65 
66 	device_printf(dev, "%s:", msg);
67 	for (i = 0; i < len; i++)
68 		printf(" %02x", bytes[i]);
69 	printf("\n");
70 }
71 #endif
72 
73 static int
74 ssif_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
75 {
76 	u_char ssif_buf[SMBUS_DATA_SIZE];
77 	device_t dev = sc->ipmi_dev;
78 	device_t smbus = sc->ipmi_ssif_smbus;
79 	u_char *cp, block, count, offset;
80 	size_t len;
81 	int error;
82 
83 	/* Acquire the bus while we send the request. */
84 	if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
85 		return (0);
86 
87 	/*
88 	 * First, send out the request.  Begin by filling out the first
89 	 * packet which includes the NetFn/LUN and command.
90 	 */
91 	ssif_buf[0] = req->ir_addr;
92 	ssif_buf[1] = req->ir_command;
93 	if (req->ir_requestlen > 0)
94 		bcopy(req->ir_request, &ssif_buf[2],
95 		    min(req->ir_requestlen, SMBUS_DATA_SIZE - 2));
96 
97 	/* Small requests are sent with a single command. */
98 	if (req->ir_requestlen <= 30) {
99 #ifdef SSIF_DEBUG
100 		dump_buffer(dev, "WRITE_SINGLE", ssif_buf,
101 		    req->ir_requestlen + 2);
102 #endif
103 		error = smbus_error(smbus_bwrite(smbus,
104 			sc->ipmi_ssif_smbus_address, SMBUS_WRITE_SINGLE,
105 			req->ir_requestlen + 2, ssif_buf));
106 		if (error) {
107 #ifdef SSIF_ERROR_DEBUG
108 			device_printf(dev, "SSIF: WRITE_SINGLE error %d\n",
109 			    error);
110 #endif
111 			goto fail;
112 		}
113 	} else {
114 		/* Longer requests are sent out in 32-byte messages. */
115 #ifdef SSIF_DEBUG
116 		dump_buffer(dev, "WRITE_START", ssif_buf, SMBUS_DATA_SIZE);
117 #endif
118 		error = smbus_error(smbus_bwrite(smbus,
119 			sc->ipmi_ssif_smbus_address, SMBUS_WRITE_START,
120 			SMBUS_DATA_SIZE, ssif_buf));
121 		if (error) {
122 #ifdef SSIF_ERROR_DEBUG
123 			device_printf(dev, "SSIF: WRITE_START error %d\n",
124 			    error);
125 #endif
126 			goto fail;
127 		}
128 
129 		len = req->ir_requestlen - (SMBUS_DATA_SIZE - 2);
130 		cp = req->ir_request + (SMBUS_DATA_SIZE - 2);
131 		while (len > 0) {
132 #ifdef SSIF_DEBUG
133 			dump_buffer(dev, "WRITE_CONT", cp,
134 			    min(len, SMBUS_DATA_SIZE));
135 #endif
136 			error = smbus_error(smbus_bwrite(smbus,
137 			    sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
138 			    min(len, SMBUS_DATA_SIZE), cp));
139 			if (error) {
140 #ifdef SSIF_ERROR_DEBUG
141 				device_printf(dev, "SSIF: WRITE_CONT error %d\n",
142 				    error);
143 #endif
144 				goto fail;
145 			}
146 			cp += SMBUS_DATA_SIZE;
147 			len -= SMBUS_DATA_SIZE;
148 		}
149 
150 		/*
151 		 * The final WRITE_CONT transaction has to have a non-zero
152 		 * length that is also not SMBUS_DATA_SIZE.  If our last
153 		 * WRITE_CONT transaction in the loop sent SMBUS_DATA_SIZE
154 		 * bytes, then len will be 0, and we send an extra 0x00 byte
155 		 * to terminate the transaction.
156 		 */
157 		if (len == 0) {
158 			char c = 0;
159 
160 #ifdef SSIF_DEBUG
161 			dump_buffer(dev, "WRITE_CONT", &c, 1);
162 #endif
163 			error = smbus_error(smbus_bwrite(smbus,
164 				sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
165 				1, &c));
166 			if (error) {
167 #ifdef SSIF_ERROR_DEBUG
168 				device_printf(dev, "SSIF: WRITE_CONT error %d\n",
169 				    error);
170 #endif
171 				goto fail;
172 			}
173 		}
174 	}
175 
176 	/* Release the bus. */
177 	smbus_release_bus(smbus, dev);
178 
179 	/* Give the BMC 100ms to chew on the request. */
180 	pause("ssifwt", hz / 10);
181 
182 	/* Try to read the first packet. */
183 read_start:
184 	if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
185 		return (0);
186 	count = SMBUS_DATA_SIZE;
187 	error = smbus_error(smbus_bread(smbus,
188 	    sc->ipmi_ssif_smbus_address, SMBUS_READ_START, &count, ssif_buf));
189 	if (error == ENXIO || error == EBUSY) {
190 		smbus_release_bus(smbus, dev);
191 #ifdef SSIF_DEBUG
192 		device_printf(dev, "SSIF: READ_START retry\n");
193 #endif
194 		/* Give the BMC another 10ms. */
195 		pause("ssifwt", hz / 100);
196 		goto read_start;
197 	}
198 	if (error) {
199 #ifdef SSIF_ERROR_DEBUG
200 		device_printf(dev, "SSIF: READ_START failed: %d\n", error);
201 #endif
202 		goto fail;
203 	}
204 #ifdef SSIF_DEBUG
205 	device_printf("SSIF: READ_START: ok\n");
206 #endif
207 
208 	/*
209 	 * If this is the first part of a multi-part read, then we need to
210 	 * skip the first two bytes.
211 	 */
212 	if (count == SMBUS_DATA_SIZE && ssif_buf[0] == 0 && ssif_buf[1] == 1)
213 		offset = 2;
214 	else
215 		offset = 0;
216 
217 	/* We had better get the reply header. */
218 	if (count < 3) {
219 		device_printf(dev, "SSIF: Short reply packet\n");
220 		goto fail;
221 	}
222 
223 	/* Verify the NetFn/LUN. */
224 	if (ssif_buf[offset] != IPMI_REPLY_ADDR(req->ir_addr)) {
225 		device_printf(dev, "SSIF: Reply address mismatch\n");
226 		goto fail;
227 	}
228 
229 	/* Verify the command. */
230 	if (ssif_buf[offset + 1] != req->ir_command) {
231 		device_printf(dev, "SMIC: Command mismatch\n");
232 		goto fail;
233 	}
234 
235 	/* Read the completion code. */
236 	req->ir_compcode = ssif_buf[offset + 2];
237 
238 	/* If this is a single read, just copy the data and return. */
239 	if (offset == 0) {
240 #ifdef SSIF_DEBUG
241 		dump_buffer(dev, "READ_SINGLE", ssif_buf, count);
242 #endif
243 		len = count - 3;
244 		bcopy(&ssif_buf[3], req->ir_reply,
245 		    min(req->ir_replybuflen, len));
246 		goto done;
247 	}
248 
249 	/*
250 	 * This is the first part of a multi-read transaction, so copy
251 	 * out the payload and start looping.
252 	 */
253 #ifdef SSIF_DEBUG
254 	dump_buffer(dev, "READ_START", ssif_buf + 2, count - 2);
255 #endif
256 	bcopy(&ssif_buf[5], req->ir_reply, min(req->ir_replybuflen, count - 5));
257 	len = count - 5;
258 	block = 1;
259 
260 	for (;;) {
261 		/* Read another packet via READ_CONT. */
262 		count = SMBUS_DATA_SIZE;
263 		error = smbus_error(smbus_bread(smbus,
264 		    sc->ipmi_ssif_smbus_address, SMBUS_READ_CONT, &count,
265 		    ssif_buf));
266 		if (error) {
267 #ifdef SSIF_ERROR_DEBUG
268 			printf("SSIF: READ_CONT failed: %d\n", error);
269 #endif
270 			goto fail;
271 		}
272 #ifdef SSIF_DEBUG
273 		device_printf(dev, "SSIF: READ_CONT... ok\n");
274 #endif
275 
276 		/* Verify the block number.  0xff marks the last block. */
277 		if (ssif_buf[0] != 0xff && ssif_buf[0] != block) {
278 			device_printf(dev, "SSIF: Read wrong block %d %d\n",
279 			    ssif_buf[0], block);
280 			goto fail;
281 		}
282 		if (ssif_buf[0] != 0xff && count < SMBUS_DATA_SIZE) {
283 			device_printf(dev,
284 			    "SSIF: Read short middle block, length %d\n",
285 			    count);
286 			goto fail;
287 		}
288 #ifdef SSIF_DEBUG
289 		if (ssif_buf[0] == 0xff)
290 			dump_buffer(dev, "READ_END", ssif_buf + 1, count - 1);
291 		else
292 			dump_buffer(dev, "READ_CONT", ssif_buf + 1, count - 1);
293 #endif
294 		if (len < req->ir_replybuflen)
295 			bcopy(&ssif_buf[1], &req->ir_reply[len],
296 			    min(req->ir_replybuflen - len, count - 1));
297 		len += count - 1;
298 
299 		/* If this was the last block we are done. */
300 		if (ssif_buf[0] == 0xff)
301 			break;
302 		block++;
303 	}
304 
305 done:
306 	/* Save the total length and return success. */
307 	req->ir_replylen = len;
308 	smbus_release_bus(smbus, dev);
309 	return (1);
310 
311 fail:
312 	smbus_release_bus(smbus, dev);
313 	return (0);
314 }
315 
316 static void
317 ssif_loop(void *arg)
318 {
319 	struct ipmi_softc *sc = arg;
320 	struct ipmi_request *req;
321 	int i, ok;
322 
323 	IPMI_LOCK(sc);
324 	while ((req = ipmi_dequeue_request(sc)) != NULL) {
325 		IPMI_UNLOCK(sc);
326 		ok = 0;
327 		for (i = 0; i < 5; i++) {
328 			ok = ssif_polled_request(sc, req);
329 			if (ok)
330 				break;
331 
332 			/* Wait 60 ms between retries. */
333 			pause("retry", 60 * hz / 1000);
334 #ifdef SSIF_RETRY_DEBUG
335 			device_printf(sc->ipmi_dev,
336 			    "SSIF: Retrying request (%d)\n", i + 1);
337 #endif
338 		}
339 		if (ok)
340 			req->ir_error = 0;
341 		else
342 			req->ir_error = EIO;
343 		IPMI_LOCK(sc);
344 		ipmi_complete_request(sc, req);
345 		IPMI_UNLOCK(sc);
346 
347 		/* Enforce 10ms between requests. */
348 		pause("delay", hz / 100);
349 
350 		IPMI_LOCK(sc);
351 	}
352 	IPMI_UNLOCK(sc);
353 	kproc_exit(0);
354 }
355 
356 static int
357 ssif_startup(struct ipmi_softc *sc)
358 {
359 
360 	return (kproc_create(ssif_loop, sc, &sc->ipmi_kthread, 0, 0,
361 	    "%s: ssif", device_get_nameunit(sc->ipmi_dev)));
362 }
363 
364 static int
365 ssif_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, int timo)
366 {
367 	int error;
368 
369 	IPMI_LOCK(sc);
370 	error = ipmi_polled_enqueue_request(sc, req);
371 	if (error == 0)
372 		error = msleep(req, &sc->ipmi_requests_lock, 0, "ipmireq",
373 		    timo);
374 	if (error == 0)
375 		error = req->ir_error;
376 	IPMI_UNLOCK(sc);
377 	return (error);
378 }
379 
380 int
381 ipmi_ssif_attach(struct ipmi_softc *sc, device_t smbus, int smbus_address)
382 {
383 
384 	/* Setup smbus address. */
385 	sc->ipmi_ssif_smbus = smbus;
386 	sc->ipmi_ssif_smbus_address = smbus_address;
387 
388 	/* Setup function pointers. */
389 	sc->ipmi_startup = ssif_startup;
390 	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
391 	sc->ipmi_driver_request = ssif_driver_request;
392 
393 	return (0);
394 }
395