1162562Sjhb/*-
2162562Sjhb * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
3162562Sjhb * All rights reserved.
4162562Sjhb *
5162562Sjhb * Redistribution and use in source and binary forms, with or without
6162562Sjhb * modification, are permitted provided that the following conditions
7162562Sjhb * are met:
8162562Sjhb * 1. Redistributions of source code must retain the above copyright
9162562Sjhb *    notice, this list of conditions and the following disclaimer.
10162562Sjhb * 2. Redistributions in binary form must reproduce the above copyright
11162562Sjhb *    notice, this list of conditions and the following disclaimer in the
12162562Sjhb *    documentation and/or other materials provided with the distribution.
13162562Sjhb *
14162562Sjhb * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15162562Sjhb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16162562Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17162562Sjhb * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18162562Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19162562Sjhb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20162562Sjhb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21162562Sjhb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22162562Sjhb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23162562Sjhb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24162562Sjhb * SUCH DAMAGE.
25162562Sjhb */
26162562Sjhb
27162562Sjhb#include <sys/cdefs.h>
28162562Sjhb__FBSDID("$FreeBSD: releng/10.2/sys/dev/ipmi/ipmi_ssif.c 278872 2015-02-16 22:33:44Z scottl $");
29162562Sjhb
30162562Sjhb#include <sys/param.h>
31162562Sjhb#include <sys/systm.h>
32162562Sjhb#include <sys/bus.h>
33162562Sjhb#include <sys/condvar.h>
34162562Sjhb#include <sys/eventhandler.h>
35162562Sjhb#include <sys/kernel.h>
36162562Sjhb#include <sys/kthread.h>
37162562Sjhb#include <sys/module.h>
38162562Sjhb#include <sys/selinfo.h>
39162562Sjhb
40162562Sjhb#include <dev/smbus/smbconf.h>
41162562Sjhb#include <dev/smbus/smb.h>
42162562Sjhb
43162562Sjhb#include "smbus_if.h"
44162562Sjhb
45162562Sjhb#ifdef LOCAL_MODULE
46162562Sjhb#include <ipmivars.h>
47162562Sjhb#else
48162562Sjhb#include <dev/ipmi/ipmivars.h>
49162562Sjhb#endif
50162562Sjhb
51162562Sjhb#define SMBUS_WRITE_SINGLE	0x02
52162562Sjhb#define SMBUS_WRITE_START	0x06
53162562Sjhb#define SMBUS_WRITE_CONT	0x07
54162562Sjhb#define SMBUS_READ_START	0x03
55162562Sjhb#define SMBUS_READ_CONT		0x09
56162562Sjhb#define SMBUS_DATA_SIZE		32
57162562Sjhb
58162562Sjhb#ifdef SSIF_DEBUG
59162562Sjhbstatic void
60162562Sjhbdump_buffer(device_t dev, const char *msg, u_char *bytes, int len)
61162562Sjhb{
62162562Sjhb	int i;
63162562Sjhb
64162562Sjhb	device_printf(dev, "%s:", msg);
65162562Sjhb	for (i = 0; i < len; i++)
66162562Sjhb		printf(" %02x", bytes[i]);
67162562Sjhb	printf("\n");
68162562Sjhb}
69162562Sjhb#endif
70162562Sjhb
71162562Sjhbstatic int
72162562Sjhbssif_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
73162562Sjhb{
74162562Sjhb	u_char ssif_buf[SMBUS_DATA_SIZE];
75162562Sjhb	device_t dev = sc->ipmi_dev;
76162562Sjhb	device_t smbus = sc->ipmi_ssif_smbus;
77162562Sjhb	u_char *cp, block, count, offset;
78162562Sjhb	size_t len;
79162562Sjhb	int error;
80162562Sjhb
81162562Sjhb	/* Acquire the bus while we send the request. */
82162562Sjhb	if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
83162562Sjhb		return (0);
84162562Sjhb
85162562Sjhb	/*
86162562Sjhb	 * First, send out the request.  Begin by filling out the first
87162562Sjhb	 * packet which includes the NetFn/LUN and command.
88162562Sjhb	 */
89162562Sjhb	ssif_buf[0] = req->ir_addr;
90162562Sjhb	ssif_buf[1] = req->ir_command;
91162562Sjhb	if (req->ir_requestlen > 0)
92162562Sjhb		bcopy(req->ir_request, &ssif_buf[2],
93162562Sjhb		    min(req->ir_requestlen, SMBUS_DATA_SIZE - 2));
94162562Sjhb
95162562Sjhb	/* Small requests are sent with a single command. */
96162562Sjhb	if (req->ir_requestlen <= 30) {
97162562Sjhb#ifdef SSIF_DEBUG
98162562Sjhb		dump_buffer(dev, "WRITE_SINGLE", ssif_buf,
99162562Sjhb		    req->ir_requestlen + 2);
100162562Sjhb#endif
101162562Sjhb		error = smbus_error(smbus_bwrite(smbus,
102162562Sjhb			sc->ipmi_ssif_smbus_address, SMBUS_WRITE_SINGLE,
103162562Sjhb			req->ir_requestlen + 2, ssif_buf));
104162562Sjhb		if (error) {
105162562Sjhb#ifdef SSIF_ERROR_DEBUG
106162562Sjhb			device_printf(dev, "SSIF: WRITE_SINGLE error %d\n",
107162562Sjhb			    error);
108162562Sjhb#endif
109162562Sjhb			goto fail;
110162562Sjhb		}
111162562Sjhb	} else {
112162562Sjhb		/* Longer requests are sent out in 32-byte messages. */
113162562Sjhb#ifdef SSIF_DEBUG
114162562Sjhb		dump_buffer(dev, "WRITE_START", ssif_buf, SMBUS_DATA_SIZE);
115162562Sjhb#endif
116162562Sjhb		error = smbus_error(smbus_bwrite(smbus,
117162562Sjhb			sc->ipmi_ssif_smbus_address, SMBUS_WRITE_START,
118162562Sjhb			SMBUS_DATA_SIZE, ssif_buf));
119162562Sjhb		if (error) {
120162562Sjhb#ifdef SSIF_ERROR_DEBUG
121162562Sjhb			device_printf(dev, "SSIF: WRITE_START error %d\n",
122162562Sjhb			    error);
123162562Sjhb#endif
124162562Sjhb			goto fail;
125162562Sjhb		}
126162562Sjhb
127162562Sjhb		len = req->ir_requestlen - (SMBUS_DATA_SIZE - 2);
128162562Sjhb		cp = req->ir_request + (SMBUS_DATA_SIZE - 2);
129162562Sjhb		while (len > 0) {
130162562Sjhb#ifdef SSIF_DEBUG
131162562Sjhb			dump_buffer(dev, "WRITE_CONT", cp,
132162562Sjhb			    min(len, SMBUS_DATA_SIZE));
133162562Sjhb#endif
134162562Sjhb			error = smbus_error(smbus_bwrite(smbus,
135162562Sjhb			    sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
136162562Sjhb			    min(len, SMBUS_DATA_SIZE), cp));
137162562Sjhb			if (error) {
138162562Sjhb#ifdef SSIF_ERROR_DEBUG
139162562Sjhb				device_printf(dev, "SSIF: WRITE_CONT error %d\n",
140162562Sjhb				    error);
141162562Sjhb#endif
142162562Sjhb				goto fail;
143162562Sjhb			}
144162562Sjhb			cp += SMBUS_DATA_SIZE;
145162562Sjhb			len -= SMBUS_DATA_SIZE;
146162562Sjhb		}
147162562Sjhb
148162562Sjhb		/*
149162562Sjhb		 * The final WRITE_CONT transaction has to have a non-zero
150162562Sjhb		 * length that is also not SMBUS_DATA_SIZE.  If our last
151162562Sjhb		 * WRITE_CONT transaction in the loop sent SMBUS_DATA_SIZE
152162562Sjhb		 * bytes, then len will be 0, and we send an extra 0x00 byte
153162562Sjhb		 * to terminate the transaction.
154162562Sjhb		 */
155162562Sjhb		if (len == 0) {
156162562Sjhb			char c = 0;
157162562Sjhb
158162562Sjhb#ifdef SSIF_DEBUG
159162562Sjhb			dump_buffer(dev, "WRITE_CONT", &c, 1);
160162562Sjhb#endif
161162562Sjhb			error = smbus_error(smbus_bwrite(smbus,
162162562Sjhb				sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
163162562Sjhb				1, &c));
164162562Sjhb			if (error) {
165162562Sjhb#ifdef SSIF_ERROR_DEBUG
166162562Sjhb				device_printf(dev, "SSIF: WRITE_CONT error %d\n",
167162562Sjhb				    error);
168162562Sjhb#endif
169162562Sjhb				goto fail;
170162562Sjhb			}
171162562Sjhb		}
172162562Sjhb	}
173162562Sjhb
174162562Sjhb	/* Release the bus. */
175162562Sjhb	smbus_release_bus(smbus, dev);
176162562Sjhb
177162562Sjhb	/* Give the BMC 100ms to chew on the request. */
178167086Sjhb	pause("ssifwt", hz / 10);
179162562Sjhb
180162562Sjhb	/* Try to read the first packet. */
181162562Sjhbread_start:
182162562Sjhb	if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
183162562Sjhb		return (0);
184162562Sjhb	count = SMBUS_DATA_SIZE;
185162562Sjhb	error = smbus_error(smbus_bread(smbus,
186162562Sjhb	    sc->ipmi_ssif_smbus_address, SMBUS_READ_START, &count, ssif_buf));
187162562Sjhb	if (error == ENXIO || error == EBUSY) {
188162562Sjhb		smbus_release_bus(smbus, dev);
189162562Sjhb#ifdef SSIF_DEBUG
190162562Sjhb		device_printf(dev, "SSIF: READ_START retry\n");
191162562Sjhb#endif
192162562Sjhb		/* Give the BMC another 10ms. */
193167086Sjhb		pause("ssifwt", hz / 100);
194162562Sjhb		goto read_start;
195162562Sjhb	}
196162562Sjhb	if (error) {
197162562Sjhb#ifdef SSIF_ERROR_DEBUG
198162562Sjhb		device_printf(dev, "SSIF: READ_START failed: %d\n", error);
199162562Sjhb#endif
200162562Sjhb		goto fail;
201162562Sjhb	}
202162562Sjhb#ifdef SSIF_DEBUG
203162562Sjhb	device_printf("SSIF: READ_START: ok\n");
204162562Sjhb#endif
205162562Sjhb
206162562Sjhb	/*
207162562Sjhb	 * If this is the first part of a multi-part read, then we need to
208162562Sjhb	 * skip the first two bytes.
209162562Sjhb	 */
210162562Sjhb	if (count == SMBUS_DATA_SIZE && ssif_buf[0] == 0 && ssif_buf[1] == 1)
211162562Sjhb		offset = 2;
212162562Sjhb	else
213162562Sjhb		offset = 0;
214162562Sjhb
215162562Sjhb	/* We had better get the reply header. */
216162562Sjhb	if (count < 3) {
217162562Sjhb		device_printf(dev, "SSIF: Short reply packet\n");
218162562Sjhb		goto fail;
219162562Sjhb	}
220162562Sjhb
221162562Sjhb	/* Verify the NetFn/LUN. */
222162562Sjhb	if (ssif_buf[offset] != IPMI_REPLY_ADDR(req->ir_addr)) {
223162562Sjhb		device_printf(dev, "SSIF: Reply address mismatch\n");
224162562Sjhb		goto fail;
225162562Sjhb	}
226162562Sjhb
227162562Sjhb	/* Verify the command. */
228162562Sjhb	if (ssif_buf[offset + 1] != req->ir_command) {
229162562Sjhb		device_printf(dev, "SMIC: Command mismatch\n");
230162562Sjhb		goto fail;
231162562Sjhb	}
232162562Sjhb
233162562Sjhb	/* Read the completion code. */
234162562Sjhb	req->ir_compcode = ssif_buf[offset + 2];
235162562Sjhb
236162562Sjhb	/* If this is a single read, just copy the data and return. */
237162562Sjhb	if (offset == 0) {
238162562Sjhb#ifdef SSIF_DEBUG
239162562Sjhb		dump_buffer(dev, "READ_SINGLE", ssif_buf, count);
240162562Sjhb#endif
241162562Sjhb		len = count - 3;
242162562Sjhb		bcopy(&ssif_buf[3], req->ir_reply,
243162562Sjhb		    min(req->ir_replybuflen, len));
244162562Sjhb		goto done;
245162562Sjhb	}
246162562Sjhb
247162562Sjhb	/*
248162562Sjhb	 * This is the first part of a multi-read transaction, so copy
249162562Sjhb	 * out the payload and start looping.
250162562Sjhb	 */
251162562Sjhb#ifdef SSIF_DEBUG
252162562Sjhb	dump_buffer(dev, "READ_START", ssif_buf + 2, count - 2);
253162562Sjhb#endif
254162562Sjhb	bcopy(&ssif_buf[5], req->ir_reply, min(req->ir_replybuflen, count - 5));
255162562Sjhb	len = count - 5;
256162562Sjhb	block = 1;
257162562Sjhb
258162562Sjhb	for (;;) {
259162562Sjhb		/* Read another packet via READ_CONT. */
260162562Sjhb		count = SMBUS_DATA_SIZE;
261162562Sjhb		error = smbus_error(smbus_bread(smbus,
262162562Sjhb		    sc->ipmi_ssif_smbus_address, SMBUS_READ_CONT, &count,
263162562Sjhb		    ssif_buf));
264162562Sjhb		if (error) {
265162562Sjhb#ifdef SSIF_ERROR_DEBUG
266162562Sjhb			printf("SSIF: READ_CONT failed: %d\n", error);
267162562Sjhb#endif
268162562Sjhb			goto fail;
269162562Sjhb		}
270162562Sjhb#ifdef SSIF_DEBUG
271162562Sjhb		device_printf(dev, "SSIF: READ_CONT... ok\n");
272162562Sjhb#endif
273162562Sjhb
274162562Sjhb		/* Verify the block number.  0xff marks the last block. */
275162562Sjhb		if (ssif_buf[0] != 0xff && ssif_buf[0] != block) {
276162562Sjhb			device_printf(dev, "SSIF: Read wrong block %d %d\n",
277162562Sjhb			    ssif_buf[0], block);
278162562Sjhb			goto fail;
279162562Sjhb		}
280162562Sjhb		if (ssif_buf[0] != 0xff && count < SMBUS_DATA_SIZE) {
281162562Sjhb			device_printf(dev,
282162562Sjhb			    "SSIF: Read short middle block, length %d\n",
283162562Sjhb			    count);
284162562Sjhb			goto fail;
285162562Sjhb		}
286162562Sjhb#ifdef SSIF_DEBUG
287162562Sjhb		if (ssif_buf[0] == 0xff)
288162562Sjhb			dump_buffer(dev, "READ_END", ssif_buf + 1, count - 1);
289162562Sjhb		else
290162562Sjhb			dump_buffer(dev, "READ_CONT", ssif_buf + 1, count - 1);
291162562Sjhb#endif
292162562Sjhb		if (len < req->ir_replybuflen)
293162562Sjhb			bcopy(&ssif_buf[1], &req->ir_reply[len],
294162562Sjhb			    min(req->ir_replybuflen - len, count - 1));
295162562Sjhb		len += count - 1;
296162562Sjhb
297162562Sjhb		/* If this was the last block we are done. */
298162562Sjhb		if (ssif_buf[0] != 0xff)
299162562Sjhb			break;
300162562Sjhb		block++;
301162562Sjhb	}
302162562Sjhb
303162562Sjhbdone:
304162562Sjhb	/* Save the total length and return success. */
305162562Sjhb	req->ir_replylen = len;
306162562Sjhb	smbus_release_bus(smbus, dev);
307162562Sjhb	return (1);
308162562Sjhb
309162562Sjhbfail:
310162562Sjhb	smbus_release_bus(smbus, dev);
311162562Sjhb	return (0);
312162562Sjhb}
313162562Sjhb
314162562Sjhbstatic void
315162562Sjhbssif_loop(void *arg)
316162562Sjhb{
317162562Sjhb	struct ipmi_softc *sc = arg;
318162562Sjhb	struct ipmi_request *req;
319162562Sjhb	int i, ok;
320162562Sjhb
321162562Sjhb	IPMI_LOCK(sc);
322162562Sjhb	while ((req = ipmi_dequeue_request(sc)) != NULL) {
323162562Sjhb		IPMI_UNLOCK(sc);
324162562Sjhb		ok = 0;
325162562Sjhb		for (i = 0; i < 5; i++) {
326162562Sjhb			ok = ssif_polled_request(sc, req);
327162562Sjhb			if (ok)
328162562Sjhb				break;
329162562Sjhb
330162562Sjhb			/* Wait 60 ms between retries. */
331167086Sjhb			pause("retry", 60 * hz / 1000);
332162562Sjhb#ifdef SSIF_RETRY_DEBUG
333162562Sjhb			device_printf(sc->ipmi_dev,
334162562Sjhb			    "SSIF: Retrying request (%d)\n", i + 1);
335162562Sjhb#endif
336162562Sjhb		}
337162562Sjhb		if (ok)
338162562Sjhb			req->ir_error = 0;
339162562Sjhb		else
340162562Sjhb			req->ir_error = EIO;
341162562Sjhb		IPMI_LOCK(sc);
342162562Sjhb		ipmi_complete_request(sc, req);
343162562Sjhb		IPMI_UNLOCK(sc);
344162562Sjhb
345162562Sjhb		/* Enforce 10ms between requests. */
346167086Sjhb		pause("delay", hz / 100);
347162562Sjhb
348162562Sjhb		IPMI_LOCK(sc);
349162562Sjhb	}
350162562Sjhb	IPMI_UNLOCK(sc);
351172836Sjulian	kproc_exit(0);
352162562Sjhb}
353162562Sjhb
354162562Sjhbstatic int
355162562Sjhbssif_startup(struct ipmi_softc *sc)
356162562Sjhb{
357162562Sjhb
358172836Sjulian	return (kproc_create(ssif_loop, sc, &sc->ipmi_kthread, 0, 0,
359162562Sjhb	    "%s: ssif", device_get_nameunit(sc->ipmi_dev)));
360162562Sjhb}
361162562Sjhb
362278872Sscottlstatic int
363278872Sscottlssif_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, int timo)
364278872Sscottl{
365278872Sscottl	int error;
366278872Sscottl
367278872Sscottl	IPMI_LOCK(sc);
368278872Sscottl	error = ipmi_polled_enqueue_request(sc, req);
369278872Sscottl	if (error == 0)
370278872Sscottl		error = msleep(req, &sc->ipmi_requests_lock, 0, "ipmireq",
371278872Sscottl		    timo);
372278872Sscottl	if (error == 0)
373278872Sscottl		error = req->ir_error;
374278872Sscottl	IPMI_UNLOCK(sc);
375278872Sscottl	return (error);
376278872Sscottl}
377278872Sscottl
378162562Sjhbint
379162562Sjhbipmi_ssif_attach(struct ipmi_softc *sc, device_t smbus, int smbus_address)
380162562Sjhb{
381162562Sjhb
382162562Sjhb	/* Setup smbus address. */
383162562Sjhb	sc->ipmi_ssif_smbus = smbus;
384162562Sjhb	sc->ipmi_ssif_smbus_address = smbus_address;
385162562Sjhb
386162562Sjhb	/* Setup function pointers. */
387162562Sjhb	sc->ipmi_startup = ssif_startup;
388162562Sjhb	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
389278872Sscottl	sc->ipmi_driver_request = ssif_driver_request;
390162562Sjhb
391162562Sjhb	return (0);
392162562Sjhb}
393