ipmi_ssif.c revision 331722
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: stable/11/sys/dev/ipmi/ipmi_ssif.c 331722 2018-03-29 02:50:57Z eadler $");
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
59static void
60dump_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
71static int
72ssif_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. */
181read_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
303done:
304	/* Save the total length and return success. */
305	req->ir_replylen = len;
306	smbus_release_bus(smbus, dev);
307	return (1);
308
309fail:
310	smbus_release_bus(smbus, dev);
311	return (0);
312}
313
314static void
315ssif_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
354static int
355ssif_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
362static int
363ssif_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
378int
379ipmi_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