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/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/selinfo.h>
38
39#include <dev/smbus/smbconf.h>
40#include <dev/smbus/smb.h>
41
42#include "smbus_if.h"
43
44#ifdef LOCAL_MODULE
45#include <ipmivars.h>
46#else
47#include <dev/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
58static void
59dump_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		printf(" %02x", bytes[i]);
66	printf("\n");
67}
68#endif
69
70static int
71ssif_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	pause("ssifwt", hz / 10);
178
179	/* Try to read the first packet. */
180read_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		pause("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			printf("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
302done:
303	/* Save the total length and return success. */
304	req->ir_replylen = len;
305	smbus_release_bus(smbus, dev);
306	return (1);
307
308fail:
309	smbus_release_bus(smbus, dev);
310	return (0);
311}
312
313static void
314ssif_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			pause("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		pause("delay", hz / 100);
346
347		IPMI_LOCK(sc);
348	}
349	IPMI_UNLOCK(sc);
350	kproc_exit(0);
351}
352
353static int
354ssif_startup(struct ipmi_softc *sc)
355{
356
357	return (kproc_create(ssif_loop, sc, &sc->ipmi_kthread, 0, 0,
358	    "%s: ssif", device_get_nameunit(sc->ipmi_dev)));
359}
360
361static int
362ssif_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, int timo)
363{
364	int error;
365
366	IPMI_LOCK(sc);
367	error = ipmi_polled_enqueue_request(sc, req);
368	if (error == 0)
369		error = msleep(req, &sc->ipmi_requests_lock, 0, "ipmireq",
370		    timo);
371	if (error == 0)
372		error = req->ir_error;
373	IPMI_UNLOCK(sc);
374	return (error);
375}
376
377int
378ipmi_ssif_attach(struct ipmi_softc *sc, device_t smbus, int smbus_address)
379{
380
381	/* Setup smbus address. */
382	sc->ipmi_ssif_smbus = smbus;
383	sc->ipmi_ssif_smbus_address = smbus_address;
384
385	/* Setup function pointers. */
386	sc->ipmi_startup = ssif_startup;
387	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
388	sc->ipmi_driver_request = ssif_driver_request;
389
390	return (0);
391}
392