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$");
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/rman.h>
39162562Sjhb#include <sys/selinfo.h>
40162562Sjhb#include <machine/bus.h>
41162562Sjhb
42162562Sjhb#ifdef LOCAL_MODULE
43162562Sjhb#include <ipmi.h>
44162562Sjhb#include <ipmivars.h>
45162562Sjhb#else
46162562Sjhb#include <sys/ipmi.h>
47162562Sjhb#include <dev/ipmi/ipmivars.h>
48162562Sjhb#endif
49162562Sjhb
50162562Sjhbstatic void	smic_wait_for_tx_okay(struct ipmi_softc *);
51162562Sjhbstatic void	smic_wait_for_rx_okay(struct ipmi_softc *);
52162562Sjhbstatic void	smic_wait_for_not_busy(struct ipmi_softc *);
53162562Sjhbstatic void	smic_set_busy(struct ipmi_softc *);
54162562Sjhb
55162562Sjhbstatic void
56162562Sjhbsmic_wait_for_tx_okay(struct ipmi_softc *sc)
57162562Sjhb{
58162562Sjhb	int flags;
59162562Sjhb
60162562Sjhb	do {
61162562Sjhb		flags = INB(sc, SMIC_FLAGS);
62162562Sjhb	} while (!(flags & SMIC_STATUS_TX_RDY));
63162562Sjhb}
64162562Sjhb
65162562Sjhbstatic void
66162562Sjhbsmic_wait_for_rx_okay(struct ipmi_softc *sc)
67162562Sjhb{
68162562Sjhb	int flags;
69162562Sjhb
70162562Sjhb	do {
71162562Sjhb		flags = INB(sc, SMIC_FLAGS);
72162562Sjhb	} while (!(flags & SMIC_STATUS_RX_RDY));
73162562Sjhb}
74162562Sjhb
75162562Sjhbstatic void
76162562Sjhbsmic_wait_for_not_busy(struct ipmi_softc *sc)
77162562Sjhb{
78162562Sjhb	int flags;
79162562Sjhb
80162562Sjhb	do {
81162562Sjhb		flags = INB(sc, SMIC_FLAGS);
82162562Sjhb	} while (flags & SMIC_STATUS_BUSY);
83162562Sjhb}
84162562Sjhb
85162562Sjhbstatic void
86162562Sjhbsmic_set_busy(struct ipmi_softc *sc)
87162562Sjhb{
88162562Sjhb	int flags;
89162562Sjhb
90162562Sjhb	flags = INB(sc, SMIC_FLAGS);
91162562Sjhb	flags |= SMIC_STATUS_BUSY;
92162562Sjhb	flags &= ~SMIC_STATUS_RESERVED;
93162562Sjhb	OUTB(sc, SMIC_FLAGS, flags);
94162562Sjhb}
95162562Sjhb
96162562Sjhb/*
97162562Sjhb * Start a transfer with a WR_START transaction that sends the NetFn/LUN
98162562Sjhb * address.
99162562Sjhb */
100162562Sjhbstatic int
101162562Sjhbsmic_start_write(struct ipmi_softc *sc, u_char data)
102162562Sjhb{
103162562Sjhb	u_char error, status;
104162562Sjhb
105162562Sjhb	smic_wait_for_not_busy(sc);
106162562Sjhb
107162562Sjhb	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_START);
108162562Sjhb	OUTB(sc, SMIC_DATA, data);
109162562Sjhb	smic_set_busy(sc);
110162562Sjhb	smic_wait_for_not_busy(sc);
111162562Sjhb	status = INB(sc, SMIC_CTL_STS);
112162562Sjhb	if (status != SMIC_SC_SMS_WR_START) {
113162562Sjhb		error = INB(sc, SMIC_DATA);
114162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Write did not start %02x\n",
115162562Sjhb		    error);
116162562Sjhb		return (0);
117162562Sjhb	}
118162562Sjhb	return (1);
119162562Sjhb}
120162562Sjhb
121162562Sjhb/*
122162562Sjhb * Write a byte in the middle of the message (either the command or one of
123162562Sjhb * the data bytes) using a WR_NEXT transaction.
124162562Sjhb */
125162562Sjhbstatic int
126162562Sjhbsmic_write_next(struct ipmi_softc *sc, u_char data)
127162562Sjhb{
128162562Sjhb	u_char error, status;
129162562Sjhb
130163035Sjhb	smic_wait_for_tx_okay(sc);
131162562Sjhb	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT);
132162562Sjhb	OUTB(sc, SMIC_DATA, data);
133162562Sjhb	smic_set_busy(sc);
134162562Sjhb	smic_wait_for_not_busy(sc);
135162562Sjhb	status = INB(sc, SMIC_CTL_STS);
136162562Sjhb	if (status != SMIC_SC_SMS_WR_NEXT) {
137162562Sjhb		error = INB(sc, SMIC_DATA);
138162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Write did not next %02x\n",
139162562Sjhb		    error);
140162562Sjhb		return (0);
141162562Sjhb	}
142162562Sjhb	return (1);
143162562Sjhb}
144162562Sjhb
145162562Sjhb/*
146162562Sjhb * Write the last byte of a transfer to end the write phase via a WR_END
147162562Sjhb * transaction.
148162562Sjhb */
149162562Sjhbstatic int
150162562Sjhbsmic_write_last(struct ipmi_softc *sc, u_char data)
151162562Sjhb{
152162562Sjhb	u_char error, status;
153162562Sjhb
154163035Sjhb	smic_wait_for_tx_okay(sc);
155162562Sjhb	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END);
156162562Sjhb	OUTB(sc, SMIC_DATA, data);
157162562Sjhb	smic_set_busy(sc);
158162562Sjhb	smic_wait_for_not_busy(sc);
159162562Sjhb	status = INB(sc, SMIC_CTL_STS);
160162562Sjhb	if (status != SMIC_SC_SMS_WR_END) {
161162562Sjhb		error = INB(sc, SMIC_DATA);
162162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Write did not end %02x\n",
163162562Sjhb		    error);
164162562Sjhb		return (0);
165162562Sjhb	}
166162562Sjhb	return (1);
167162562Sjhb}
168162562Sjhb
169162562Sjhb/*
170162562Sjhb * Start the read phase of a transfer with a RD_START transaction.
171162562Sjhb */
172162562Sjhbstatic int
173162562Sjhbsmic_start_read(struct ipmi_softc *sc, u_char *data)
174162562Sjhb{
175162562Sjhb	u_char error, status;
176162562Sjhb
177162562Sjhb	smic_wait_for_not_busy(sc);
178162562Sjhb
179163035Sjhb	smic_wait_for_rx_okay(sc);
180162562Sjhb	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START);
181162562Sjhb	smic_set_busy(sc);
182162562Sjhb	smic_wait_for_not_busy(sc);
183162562Sjhb	status = INB(sc, SMIC_CTL_STS);
184162562Sjhb	if (status != SMIC_SC_SMS_RD_START) {
185162562Sjhb		error = INB(sc, SMIC_DATA);
186162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Read did not start %02x\n",
187162562Sjhb		    error);
188162562Sjhb		return (0);
189162562Sjhb	}
190162562Sjhb	*data = INB(sc, SMIC_DATA);
191162562Sjhb	return (1);
192162562Sjhb}
193162562Sjhb
194162562Sjhb/*
195162562Sjhb * Read a byte via a RD_NEXT transaction.  If this was the last byte, return
196162562Sjhb * 2 rather than 1.
197162562Sjhb */
198162562Sjhbstatic int
199162562Sjhbsmic_read_byte(struct ipmi_softc *sc, u_char *data)
200162562Sjhb{
201162562Sjhb	u_char error, status;
202162562Sjhb
203163035Sjhb	smic_wait_for_rx_okay(sc);
204162562Sjhb	OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT);
205162562Sjhb	smic_set_busy(sc);
206162562Sjhb	smic_wait_for_not_busy(sc);
207162562Sjhb	status = INB(sc, SMIC_CTL_STS);
208162562Sjhb	if (status != SMIC_SC_SMS_RD_NEXT &&
209162562Sjhb	    status != SMIC_SC_SMS_RD_END) {
210162562Sjhb		error = INB(sc, SMIC_DATA);
211162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Read did not next %02x\n",
212162562Sjhb		    error);
213162562Sjhb		return (0);
214162562Sjhb	}
215162562Sjhb	*data = INB(sc, SMIC_DATA);
216162562Sjhb	if (status == SMIC_SC_SMS_RD_NEXT)
217162562Sjhb		return (1);
218162562Sjhb	else
219162562Sjhb		return (2);
220162562Sjhb}
221162562Sjhb
222162562Sjhb/* Complete a transfer via a RD_END transaction after reading the last byte. */
223162562Sjhbstatic int
224162562Sjhbsmic_read_end(struct ipmi_softc *sc)
225162562Sjhb{
226162562Sjhb	u_char error, status;
227162562Sjhb
228162562Sjhb	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_END);
229162562Sjhb	smic_set_busy(sc);
230162562Sjhb	smic_wait_for_not_busy(sc);
231162562Sjhb	status = INB(sc, SMIC_CTL_STS);
232162562Sjhb	if (status != SMIC_SC_SMS_RDY) {
233162562Sjhb		error = INB(sc, SMIC_DATA);
234162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Read did not end %02x\n",
235162562Sjhb		    error);
236162562Sjhb		return (0);
237162562Sjhb	}
238162562Sjhb	return (1);
239162562Sjhb}
240162562Sjhb
241162562Sjhbstatic int
242162562Sjhbsmic_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
243162562Sjhb{
244162562Sjhb	u_char *cp, data;
245162562Sjhb	int i, state;
246162562Sjhb
247162562Sjhb	/* First, start the message with the address. */
248162562Sjhb	if (!smic_start_write(sc, req->ir_addr))
249162562Sjhb		return (0);
250163034Sjhb#ifdef SMIC_DEBUG
251163034Sjhb	device_printf(sc->ipmi_dev, "SMIC: WRITE_START address: %02x\n",
252163034Sjhb	    req->ir_addr);
253163034Sjhb#endif
254162562Sjhb
255162562Sjhb	if (req->ir_requestlen == 0) {
256162562Sjhb		/* Send the command as the last byte. */
257162562Sjhb		if (!smic_write_last(sc, req->ir_command))
258162562Sjhb			return (0);
259163034Sjhb#ifdef SMIC_DEBUG
260163034Sjhb		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
261163034Sjhb		    req->ir_command);
262163034Sjhb#endif
263162562Sjhb	} else {
264162562Sjhb		/* Send the command. */
265162562Sjhb		if (!smic_write_next(sc, req->ir_command))
266162562Sjhb			return (0);
267163034Sjhb#ifdef SMIC_DEBUG
268163034Sjhb		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
269163034Sjhb		    req->ir_command);
270163034Sjhb#endif
271162562Sjhb
272162562Sjhb		/* Send the payload. */
273162562Sjhb		cp = req->ir_request;
274163034Sjhb		for (i = 0; i < req->ir_requestlen - 1; i++) {
275162562Sjhb			if (!smic_write_next(sc, *cp++))
276162562Sjhb				return (0);
277163034Sjhb#ifdef SMIC_DEBUG
278163034Sjhb			device_printf(sc->ipmi_dev, "SMIC: Wrote data: %02x\n",
279163034Sjhb			    cp[-1]);
280163034Sjhb#endif
281163034Sjhb		}
282162562Sjhb		if (!smic_write_last(sc, *cp))
283162562Sjhb			return (0);
284163034Sjhb#ifdef SMIC_DEBUG
285163034Sjhb		device_printf(sc->ipmi_dev, "SMIC: Write last data: %02x\n",
286163034Sjhb		    *cp);
287163034Sjhb#endif
288162562Sjhb	}
289162562Sjhb
290162562Sjhb	/* Start the read phase by reading the NetFn/LUN. */
291162562Sjhb	if (smic_start_read(sc, &data) != 1)
292162562Sjhb		return (0);
293163034Sjhb#ifdef SMIC_DEBUG
294163034Sjhb	device_printf(sc->ipmi_dev, "SMIC: Read address: %02x\n", data);
295163034Sjhb#endif
296162562Sjhb	if (data != IPMI_REPLY_ADDR(req->ir_addr)) {
297162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Reply address mismatch\n");
298162562Sjhb		return (0);
299162562Sjhb	}
300162562Sjhb
301162562Sjhb	/* Read the command. */
302162562Sjhb	if (smic_read_byte(sc, &data) != 1)
303162562Sjhb		return (0);
304163034Sjhb#ifdef SMIC_DEBUG
305163034Sjhb	device_printf(sc->ipmi_dev, "SMIC: Read command: %02x\n", data);
306163034Sjhb#endif
307162562Sjhb	if (data != req->ir_command) {
308162562Sjhb		device_printf(sc->ipmi_dev, "SMIC: Command mismatch\n");
309162562Sjhb		return (0);
310162562Sjhb	}
311162562Sjhb
312162562Sjhb	/* Read the completion code. */
313162562Sjhb	state = smic_read_byte(sc, &req->ir_compcode);
314162562Sjhb	if (state == 0)
315162562Sjhb		return (0);
316163034Sjhb#ifdef SMIC_DEBUG
317163034Sjhb	device_printf(sc->ipmi_dev, "SMIC: Read completion code: %02x\n",
318163034Sjhb	    req->ir_compcode);
319163034Sjhb#endif
320162562Sjhb
321162562Sjhb	/* Finally, read the reply from the BMC. */
322162562Sjhb	i = 0;
323162562Sjhb	while (state == 1) {
324162562Sjhb		state = smic_read_byte(sc, &data);
325162562Sjhb		if (state == 0)
326162562Sjhb			return (0);
327163034Sjhb		if (i < req->ir_replybuflen) {
328162562Sjhb			req->ir_reply[i] = data;
329163034Sjhb#ifdef SMIC_DEBUG
330163034Sjhb			device_printf(sc->ipmi_dev, "SMIC: Read data: %02x\n",
331163034Sjhb			    data);
332163034Sjhb		} else {
333163034Sjhb			device_printf(sc->ipmi_dev,
334163034Sjhb			    "SMIC: Read short %02x byte %d\n", data, i + 1);
335163034Sjhb#endif
336163034Sjhb		}
337162562Sjhb		i++;
338162562Sjhb	}
339162562Sjhb
340162562Sjhb	/* Terminate the transfer. */
341162562Sjhb	if (!smic_read_end(sc))
342162562Sjhb		return (0);
343162562Sjhb	req->ir_replylen = i;
344163034Sjhb#ifdef SMIC_DEBUG
345163034Sjhb	device_printf(sc->ipmi_dev, "SMIC: READ finished (%d bytes)\n", i);
346163034Sjhb	if (req->ir_replybuflen < i)
347163034Sjhb#else
348162562Sjhb	if (req->ir_replybuflen < i && req->ir_replybuflen != 0)
349163034Sjhb#endif
350162562Sjhb		device_printf(sc->ipmi_dev,
351162562Sjhb		    "SMIC: Read short: %zd buffer, %d actual\n",
352162562Sjhb		    req->ir_replybuflen, i);
353162562Sjhb	return (1);
354162562Sjhb}
355162562Sjhb
356162562Sjhbstatic void
357162562Sjhbsmic_loop(void *arg)
358162562Sjhb{
359162562Sjhb	struct ipmi_softc *sc = arg;
360162562Sjhb	struct ipmi_request *req;
361162562Sjhb	int i, ok;
362162562Sjhb
363162562Sjhb	IPMI_LOCK(sc);
364162562Sjhb	while ((req = ipmi_dequeue_request(sc)) != NULL) {
365248705Smelifaro		IPMI_UNLOCK(sc);
366162562Sjhb		ok = 0;
367278872Sscottl		for (i = 0; i < 3 && !ok; i++) {
368278872Sscottl			IPMI_IO_LOCK(sc);
369162562Sjhb			ok = smic_polled_request(sc, req);
370278872Sscottl			IPMI_IO_UNLOCK(sc);
371278872Sscottl		}
372162562Sjhb		if (ok)
373162562Sjhb			req->ir_error = 0;
374162562Sjhb		else
375162562Sjhb			req->ir_error = EIO;
376248705Smelifaro		IPMI_LOCK(sc);
377162562Sjhb		ipmi_complete_request(sc, req);
378162562Sjhb	}
379162562Sjhb	IPMI_UNLOCK(sc);
380172836Sjulian	kproc_exit(0);
381162562Sjhb}
382162562Sjhb
383162562Sjhbstatic int
384162562Sjhbsmic_startup(struct ipmi_softc *sc)
385162562Sjhb{
386162562Sjhb
387172836Sjulian	return (kproc_create(smic_loop, sc, &sc->ipmi_kthread, 0, 0,
388162562Sjhb	    "%s: smic", device_get_nameunit(sc->ipmi_dev)));
389162562Sjhb}
390162562Sjhb
391278872Sscottlstatic int
392278872Sscottlsmic_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, int timo)
393278872Sscottl{
394278872Sscottl	int i, ok;
395278872Sscottl
396278872Sscottl	ok = 0;
397278872Sscottl	for (i = 0; i < 3 && !ok; i++) {
398278872Sscottl		IPMI_IO_LOCK(sc);
399278872Sscottl		ok = smic_polled_request(sc, req);
400278872Sscottl		IPMI_IO_UNLOCK(sc);
401278872Sscottl	}
402278872Sscottl	if (ok)
403278872Sscottl		req->ir_error = 0;
404278872Sscottl	else
405278872Sscottl		req->ir_error = EIO;
406278872Sscottl	return (req->ir_error);
407278872Sscottl}
408278872Sscottl
409162562Sjhbint
410162562Sjhbipmi_smic_attach(struct ipmi_softc *sc)
411162562Sjhb{
412162562Sjhb	int flags;
413162562Sjhb
414162562Sjhb	/* Setup function pointers. */
415162562Sjhb	sc->ipmi_startup = smic_startup;
416162562Sjhb	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
417278872Sscottl	sc->ipmi_driver_request = smic_driver_request;
418287435Sjhb	sc->ipmi_driver_requests_polled = 1;
419162562Sjhb
420162562Sjhb	/* See if we can talk to the controller. */
421162562Sjhb	flags = INB(sc, SMIC_FLAGS);
422162562Sjhb	if (flags == 0xff) {
423162562Sjhb		device_printf(sc->ipmi_dev, "couldn't find it\n");
424162562Sjhb		return (ENXIO);
425162562Sjhb	}
426162562Sjhb
427163034Sjhb#ifdef SMIC_DEBUG
428163034Sjhb	device_printf(sc->ipmi_dev, "SMIC: initial state: %02x\n", flags);
429163034Sjhb#endif
430163034Sjhb
431162562Sjhb	return (0);
432162562Sjhb}
433