1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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/rman.h>
41#include <sys/selinfo.h>
42#include <machine/bus.h>
43
44#ifdef LOCAL_MODULE
45#include <ipmi.h>
46#include <ipmivars.h>
47#else
48#include <sys/ipmi.h>
49#include <dev/ipmi/ipmivars.h>
50#endif
51
52static void	smic_wait_for_tx_okay(struct ipmi_softc *);
53static void	smic_wait_for_rx_okay(struct ipmi_softc *);
54static void	smic_wait_for_not_busy(struct ipmi_softc *);
55static void	smic_set_busy(struct ipmi_softc *);
56
57static void
58smic_wait_for_tx_okay(struct ipmi_softc *sc)
59{
60	int flags;
61
62	do {
63		flags = INB(sc, SMIC_FLAGS);
64	} while (!(flags & SMIC_STATUS_TX_RDY));
65}
66
67static void
68smic_wait_for_rx_okay(struct ipmi_softc *sc)
69{
70	int flags;
71
72	do {
73		flags = INB(sc, SMIC_FLAGS);
74	} while (!(flags & SMIC_STATUS_RX_RDY));
75}
76
77static void
78smic_wait_for_not_busy(struct ipmi_softc *sc)
79{
80	int flags;
81
82	do {
83		flags = INB(sc, SMIC_FLAGS);
84	} while (flags & SMIC_STATUS_BUSY);
85}
86
87static void
88smic_set_busy(struct ipmi_softc *sc)
89{
90	int flags;
91
92	flags = INB(sc, SMIC_FLAGS);
93	flags |= SMIC_STATUS_BUSY;
94	flags &= ~SMIC_STATUS_RESERVED;
95	OUTB(sc, SMIC_FLAGS, flags);
96}
97
98/*
99 * Start a transfer with a WR_START transaction that sends the NetFn/LUN
100 * address.
101 */
102static int
103smic_start_write(struct ipmi_softc *sc, u_char data)
104{
105	u_char error, status;
106
107	smic_wait_for_not_busy(sc);
108
109	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_START);
110	OUTB(sc, SMIC_DATA, data);
111	smic_set_busy(sc);
112	smic_wait_for_not_busy(sc);
113	status = INB(sc, SMIC_CTL_STS);
114	if (status != SMIC_SC_SMS_WR_START) {
115		error = INB(sc, SMIC_DATA);
116		device_printf(sc->ipmi_dev, "SMIC: Write did not start %02x\n",
117		    error);
118		return (0);
119	}
120	return (1);
121}
122
123/*
124 * Write a byte in the middle of the message (either the command or one of
125 * the data bytes) using a WR_NEXT transaction.
126 */
127static int
128smic_write_next(struct ipmi_softc *sc, u_char data)
129{
130	u_char error, status;
131
132	smic_wait_for_tx_okay(sc);
133	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT);
134	OUTB(sc, SMIC_DATA, data);
135	smic_set_busy(sc);
136	smic_wait_for_not_busy(sc);
137	status = INB(sc, SMIC_CTL_STS);
138	if (status != SMIC_SC_SMS_WR_NEXT) {
139		error = INB(sc, SMIC_DATA);
140		device_printf(sc->ipmi_dev, "SMIC: Write did not next %02x\n",
141		    error);
142		return (0);
143	}
144	return (1);
145}
146
147/*
148 * Write the last byte of a transfer to end the write phase via a WR_END
149 * transaction.
150 */
151static int
152smic_write_last(struct ipmi_softc *sc, u_char data)
153{
154	u_char error, status;
155
156	smic_wait_for_tx_okay(sc);
157	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END);
158	OUTB(sc, SMIC_DATA, data);
159	smic_set_busy(sc);
160	smic_wait_for_not_busy(sc);
161	status = INB(sc, SMIC_CTL_STS);
162	if (status != SMIC_SC_SMS_WR_END) {
163		error = INB(sc, SMIC_DATA);
164		device_printf(sc->ipmi_dev, "SMIC: Write did not end %02x\n",
165		    error);
166		return (0);
167	}
168	return (1);
169}
170
171/*
172 * Start the read phase of a transfer with a RD_START transaction.
173 */
174static int
175smic_start_read(struct ipmi_softc *sc, u_char *data)
176{
177	u_char error, status;
178
179	smic_wait_for_not_busy(sc);
180
181	smic_wait_for_rx_okay(sc);
182	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START);
183	smic_set_busy(sc);
184	smic_wait_for_not_busy(sc);
185	status = INB(sc, SMIC_CTL_STS);
186	if (status != SMIC_SC_SMS_RD_START) {
187		error = INB(sc, SMIC_DATA);
188		device_printf(sc->ipmi_dev, "SMIC: Read did not start %02x\n",
189		    error);
190		return (0);
191	}
192	*data = INB(sc, SMIC_DATA);
193	return (1);
194}
195
196/*
197 * Read a byte via a RD_NEXT transaction.  If this was the last byte, return
198 * 2 rather than 1.
199 */
200static int
201smic_read_byte(struct ipmi_softc *sc, u_char *data)
202{
203	u_char error, status;
204
205	smic_wait_for_rx_okay(sc);
206	OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT);
207	smic_set_busy(sc);
208	smic_wait_for_not_busy(sc);
209	status = INB(sc, SMIC_CTL_STS);
210	if (status != SMIC_SC_SMS_RD_NEXT &&
211	    status != SMIC_SC_SMS_RD_END) {
212		error = INB(sc, SMIC_DATA);
213		device_printf(sc->ipmi_dev, "SMIC: Read did not next %02x\n",
214		    error);
215		return (0);
216	}
217	*data = INB(sc, SMIC_DATA);
218	if (status == SMIC_SC_SMS_RD_NEXT)
219		return (1);
220	else
221		return (2);
222}
223
224/* Complete a transfer via a RD_END transaction after reading the last byte. */
225static int
226smic_read_end(struct ipmi_softc *sc)
227{
228	u_char error, status;
229
230	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_END);
231	smic_set_busy(sc);
232	smic_wait_for_not_busy(sc);
233	status = INB(sc, SMIC_CTL_STS);
234	if (status != SMIC_SC_SMS_RDY) {
235		error = INB(sc, SMIC_DATA);
236		device_printf(sc->ipmi_dev, "SMIC: Read did not end %02x\n",
237		    error);
238		return (0);
239	}
240	return (1);
241}
242
243static int
244smic_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
245{
246	u_char *cp, data;
247	int i, state;
248
249	/* First, start the message with the address. */
250	if (!smic_start_write(sc, req->ir_addr))
251		return (0);
252#ifdef SMIC_DEBUG
253	device_printf(sc->ipmi_dev, "SMIC: WRITE_START address: %02x\n",
254	    req->ir_addr);
255#endif
256
257	if (req->ir_requestlen == 0) {
258		/* Send the command as the last byte. */
259		if (!smic_write_last(sc, req->ir_command))
260			return (0);
261#ifdef SMIC_DEBUG
262		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
263		    req->ir_command);
264#endif
265	} else {
266		/* Send the command. */
267		if (!smic_write_next(sc, req->ir_command))
268			return (0);
269#ifdef SMIC_DEBUG
270		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
271		    req->ir_command);
272#endif
273
274		/* Send the payload. */
275		cp = req->ir_request;
276		for (i = 0; i < req->ir_requestlen - 1; i++) {
277			if (!smic_write_next(sc, *cp++))
278				return (0);
279#ifdef SMIC_DEBUG
280			device_printf(sc->ipmi_dev, "SMIC: Wrote data: %02x\n",
281			    cp[-1]);
282#endif
283		}
284		if (!smic_write_last(sc, *cp))
285			return (0);
286#ifdef SMIC_DEBUG
287		device_printf(sc->ipmi_dev, "SMIC: Write last data: %02x\n",
288		    *cp);
289#endif
290	}
291
292	/* Start the read phase by reading the NetFn/LUN. */
293	if (smic_start_read(sc, &data) != 1)
294		return (0);
295#ifdef SMIC_DEBUG
296	device_printf(sc->ipmi_dev, "SMIC: Read address: %02x\n", data);
297#endif
298	if (data != IPMI_REPLY_ADDR(req->ir_addr)) {
299		device_printf(sc->ipmi_dev, "SMIC: Reply address mismatch\n");
300		return (0);
301	}
302
303	/* Read the command. */
304	if (smic_read_byte(sc, &data) != 1)
305		return (0);
306#ifdef SMIC_DEBUG
307	device_printf(sc->ipmi_dev, "SMIC: Read command: %02x\n", data);
308#endif
309	if (data != req->ir_command) {
310		device_printf(sc->ipmi_dev, "SMIC: Command mismatch\n");
311		return (0);
312	}
313
314	/* Read the completion code. */
315	state = smic_read_byte(sc, &req->ir_compcode);
316	if (state == 0)
317		return (0);
318#ifdef SMIC_DEBUG
319	device_printf(sc->ipmi_dev, "SMIC: Read completion code: %02x\n",
320	    req->ir_compcode);
321#endif
322
323	/* Finally, read the reply from the BMC. */
324	i = 0;
325	while (state == 1) {
326		state = smic_read_byte(sc, &data);
327		if (state == 0)
328			return (0);
329		if (i < req->ir_replybuflen) {
330			req->ir_reply[i] = data;
331#ifdef SMIC_DEBUG
332			device_printf(sc->ipmi_dev, "SMIC: Read data: %02x\n",
333			    data);
334		} else {
335			device_printf(sc->ipmi_dev,
336			    "SMIC: Read short %02x byte %d\n", data, i + 1);
337#endif
338		}
339		i++;
340	}
341
342	/* Terminate the transfer. */
343	if (!smic_read_end(sc))
344		return (0);
345	req->ir_replylen = i;
346#ifdef SMIC_DEBUG
347	device_printf(sc->ipmi_dev, "SMIC: READ finished (%d bytes)\n", i);
348	if (req->ir_replybuflen < i)
349#else
350	if (req->ir_replybuflen < i && req->ir_replybuflen != 0)
351#endif
352		device_printf(sc->ipmi_dev,
353		    "SMIC: Read short: %zd buffer, %d actual\n",
354		    req->ir_replybuflen, i);
355	return (1);
356}
357
358static void
359smic_loop(void *arg)
360{
361	struct ipmi_softc *sc = arg;
362	struct ipmi_request *req;
363	int i, ok;
364
365	IPMI_LOCK(sc);
366	while ((req = ipmi_dequeue_request(sc)) != NULL) {
367		IPMI_UNLOCK(sc);
368		ok = 0;
369		for (i = 0; i < 3 && !ok; i++) {
370			IPMI_IO_LOCK(sc);
371			ok = smic_polled_request(sc, req);
372			IPMI_IO_UNLOCK(sc);
373		}
374		if (ok)
375			req->ir_error = 0;
376		else
377			req->ir_error = EIO;
378		IPMI_LOCK(sc);
379		ipmi_complete_request(sc, req);
380	}
381	IPMI_UNLOCK(sc);
382	kproc_exit(0);
383}
384
385static int
386smic_startup(struct ipmi_softc *sc)
387{
388
389	return (kproc_create(smic_loop, sc, &sc->ipmi_kthread, 0, 0,
390	    "%s: smic", device_get_nameunit(sc->ipmi_dev)));
391}
392
393static int
394smic_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, int timo)
395{
396	int i, ok;
397
398	ok = 0;
399	for (i = 0; i < 3 && !ok; i++) {
400		IPMI_IO_LOCK(sc);
401		ok = smic_polled_request(sc, req);
402		IPMI_IO_UNLOCK(sc);
403	}
404	if (ok)
405		req->ir_error = 0;
406	else
407		req->ir_error = EIO;
408	return (req->ir_error);
409}
410
411int
412ipmi_smic_attach(struct ipmi_softc *sc)
413{
414	int flags;
415
416	/* Setup function pointers. */
417	sc->ipmi_startup = smic_startup;
418	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
419	sc->ipmi_driver_request = smic_driver_request;
420	sc->ipmi_driver_requests_polled = 1;
421
422	/* See if we can talk to the controller. */
423	flags = INB(sc, SMIC_FLAGS);
424	if (flags == 0xff) {
425		device_printf(sc->ipmi_dev, "couldn't find it\n");
426		return (ENXIO);
427	}
428
429#ifdef SMIC_DEBUG
430	device_printf(sc->ipmi_dev, "SMIC: initial state: %02x\n", flags);
431#endif
432
433	return (0);
434}
435