ipmi_smic.c revision 248705
1130803Smarcel/*-
2130803Smarcel * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
3130803Smarcel * All rights reserved.
4130803Smarcel *
5130803Smarcel * Redistribution and use in source and binary forms, with or without
6130803Smarcel * modification, are permitted provided that the following conditions
7130803Smarcel * are met:
8130803Smarcel * 1. Redistributions of source code must retain the above copyright
9130803Smarcel *    notice, this list of conditions and the following disclaimer.
10130803Smarcel * 2. Redistributions in binary form must reproduce the above copyright
11130803Smarcel *    notice, this list of conditions and the following disclaimer in the
12130803Smarcel *    documentation and/or other materials provided with the distribution.
13130803Smarcel *
14130803Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15130803Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16130803Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17130803Smarcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18130803Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19130803Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20130803Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21130803Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22130803Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23130803Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24130803Smarcel * SUCH DAMAGE.
25130803Smarcel */
26130803Smarcel
27130803Smarcel#include <sys/cdefs.h>
28130803Smarcel__FBSDID("$FreeBSD: head/sys/dev/ipmi/ipmi_smic.c 248705 2013-03-25 14:30:34Z melifaro $");
29130803Smarcel
30130803Smarcel#include <sys/param.h>
31130803Smarcel#include <sys/systm.h>
32130803Smarcel#include <sys/bus.h>
33130803Smarcel#include <sys/condvar.h>
34130803Smarcel#include <sys/eventhandler.h>
35130803Smarcel#include <sys/kernel.h>
36130803Smarcel#include <sys/kthread.h>
37130803Smarcel#include <sys/module.h>
38130803Smarcel#include <sys/rman.h>
39130803Smarcel#include <sys/selinfo.h>
40130803Smarcel#include <machine/bus.h>
41130803Smarcel
42130803Smarcel#ifdef LOCAL_MODULE
43130803Smarcel#include <ipmi.h>
44130803Smarcel#include <ipmivars.h>
45130803Smarcel#else
46130803Smarcel#include <sys/ipmi.h>
47130803Smarcel#include <dev/ipmi/ipmivars.h>
48130803Smarcel#endif
49130803Smarcel
50130803Smarcelstatic void	smic_wait_for_tx_okay(struct ipmi_softc *);
51130803Smarcelstatic void	smic_wait_for_rx_okay(struct ipmi_softc *);
52130803Smarcelstatic void	smic_wait_for_not_busy(struct ipmi_softc *);
53130803Smarcelstatic void	smic_set_busy(struct ipmi_softc *);
54130803Smarcel
55130803Smarcelstatic void
56130803Smarcelsmic_wait_for_tx_okay(struct ipmi_softc *sc)
57130803Smarcel{
58130803Smarcel	int flags;
59130803Smarcel
60130803Smarcel	do {
61130803Smarcel		flags = INB(sc, SMIC_FLAGS);
62130803Smarcel	} while (!(flags & SMIC_STATUS_TX_RDY));
63130803Smarcel}
64130803Smarcel
65130803Smarcelstatic void
66130803Smarcelsmic_wait_for_rx_okay(struct ipmi_softc *sc)
67130803Smarcel{
68130803Smarcel	int flags;
69130803Smarcel
70130803Smarcel	do {
71130803Smarcel		flags = INB(sc, SMIC_FLAGS);
72130803Smarcel	} while (!(flags & SMIC_STATUS_RX_RDY));
73130803Smarcel}
74130803Smarcel
75130803Smarcelstatic void
76130803Smarcelsmic_wait_for_not_busy(struct ipmi_softc *sc)
77130803Smarcel{
78130803Smarcel	int flags;
79130803Smarcel
80130803Smarcel	do {
81130803Smarcel		flags = INB(sc, SMIC_FLAGS);
82130803Smarcel	} while (flags & SMIC_STATUS_BUSY);
83130803Smarcel}
84130803Smarcel
85130803Smarcelstatic void
86130803Smarcelsmic_set_busy(struct ipmi_softc *sc)
87130803Smarcel{
88130803Smarcel	int flags;
89130803Smarcel
90130803Smarcel	flags = INB(sc, SMIC_FLAGS);
91130803Smarcel	flags |= SMIC_STATUS_BUSY;
92130803Smarcel	flags &= ~SMIC_STATUS_RESERVED;
93130803Smarcel	OUTB(sc, SMIC_FLAGS, flags);
94130803Smarcel}
95130803Smarcel
96130803Smarcel/*
97130803Smarcel * Start a transfer with a WR_START transaction that sends the NetFn/LUN
98130803Smarcel * address.
99130803Smarcel */
100130803Smarcelstatic int
101130803Smarcelsmic_start_write(struct ipmi_softc *sc, u_char data)
102130803Smarcel{
103130803Smarcel	u_char error, status;
104130803Smarcel
105130803Smarcel	smic_wait_for_not_busy(sc);
106130803Smarcel
107130803Smarcel	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_START);
108130803Smarcel	OUTB(sc, SMIC_DATA, data);
109130803Smarcel	smic_set_busy(sc);
110130803Smarcel	smic_wait_for_not_busy(sc);
111130803Smarcel	status = INB(sc, SMIC_CTL_STS);
112130803Smarcel	if (status != SMIC_SC_SMS_WR_START) {
113130803Smarcel		error = INB(sc, SMIC_DATA);
114130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Write did not start %02x\n",
115130803Smarcel		    error);
116130803Smarcel		return (0);
117130803Smarcel	}
118130803Smarcel	return (1);
119130803Smarcel}
120130803Smarcel
121130803Smarcel/*
122130803Smarcel * Write a byte in the middle of the message (either the command or one of
123130803Smarcel * the data bytes) using a WR_NEXT transaction.
124130803Smarcel */
125130803Smarcelstatic int
126130803Smarcelsmic_write_next(struct ipmi_softc *sc, u_char data)
127130803Smarcel{
128130803Smarcel	u_char error, status;
129130803Smarcel
130130803Smarcel	smic_wait_for_tx_okay(sc);
131130803Smarcel	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT);
132130803Smarcel	OUTB(sc, SMIC_DATA, data);
133130803Smarcel	smic_set_busy(sc);
134130803Smarcel	smic_wait_for_not_busy(sc);
135130803Smarcel	status = INB(sc, SMIC_CTL_STS);
136130803Smarcel	if (status != SMIC_SC_SMS_WR_NEXT) {
137130803Smarcel		error = INB(sc, SMIC_DATA);
138130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Write did not next %02x\n",
139130803Smarcel		    error);
140130803Smarcel		return (0);
141130803Smarcel	}
142130803Smarcel	return (1);
143130803Smarcel}
144130803Smarcel
145130803Smarcel/*
146130803Smarcel * Write the last byte of a transfer to end the write phase via a WR_END
147130803Smarcel * transaction.
148130803Smarcel */
149130803Smarcelstatic int
150130803Smarcelsmic_write_last(struct ipmi_softc *sc, u_char data)
151130803Smarcel{
152130803Smarcel	u_char error, status;
153130803Smarcel
154130803Smarcel	smic_wait_for_tx_okay(sc);
155130803Smarcel	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END);
156130803Smarcel	OUTB(sc, SMIC_DATA, data);
157130803Smarcel	smic_set_busy(sc);
158130803Smarcel	smic_wait_for_not_busy(sc);
159130803Smarcel	status = INB(sc, SMIC_CTL_STS);
160130803Smarcel	if (status != SMIC_SC_SMS_WR_END) {
161130803Smarcel		error = INB(sc, SMIC_DATA);
162130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Write did not end %02x\n",
163130803Smarcel		    error);
164130803Smarcel		return (0);
165130803Smarcel	}
166130803Smarcel	return (1);
167130803Smarcel}
168130803Smarcel
169130803Smarcel/*
170130803Smarcel * Start the read phase of a transfer with a RD_START transaction.
171130803Smarcel */
172130803Smarcelstatic int
173130803Smarcelsmic_start_read(struct ipmi_softc *sc, u_char *data)
174130803Smarcel{
175130803Smarcel	u_char error, status;
176130803Smarcel
177130803Smarcel	smic_wait_for_not_busy(sc);
178130803Smarcel
179130803Smarcel	smic_wait_for_rx_okay(sc);
180130803Smarcel	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START);
181130803Smarcel	smic_set_busy(sc);
182130803Smarcel	smic_wait_for_not_busy(sc);
183130803Smarcel	status = INB(sc, SMIC_CTL_STS);
184130803Smarcel	if (status != SMIC_SC_SMS_RD_START) {
185130803Smarcel		error = INB(sc, SMIC_DATA);
186130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Read did not start %02x\n",
187130803Smarcel		    error);
188130803Smarcel		return (0);
189130803Smarcel	}
190130803Smarcel	*data = INB(sc, SMIC_DATA);
191130803Smarcel	return (1);
192130803Smarcel}
193130803Smarcel
194130803Smarcel/*
195130803Smarcel * Read a byte via a RD_NEXT transaction.  If this was the last byte, return
196130803Smarcel * 2 rather than 1.
197130803Smarcel */
198130803Smarcelstatic int
199130803Smarcelsmic_read_byte(struct ipmi_softc *sc, u_char *data)
200130803Smarcel{
201130803Smarcel	u_char error, status;
202130803Smarcel
203130803Smarcel	smic_wait_for_rx_okay(sc);
204130803Smarcel	OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT);
205130803Smarcel	smic_set_busy(sc);
206130803Smarcel	smic_wait_for_not_busy(sc);
207130803Smarcel	status = INB(sc, SMIC_CTL_STS);
208130803Smarcel	if (status != SMIC_SC_SMS_RD_NEXT &&
209130803Smarcel	    status != SMIC_SC_SMS_RD_END) {
210130803Smarcel		error = INB(sc, SMIC_DATA);
211130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Read did not next %02x\n",
212130803Smarcel		    error);
213130803Smarcel		return (0);
214130803Smarcel	}
215130803Smarcel	*data = INB(sc, SMIC_DATA);
216130803Smarcel	if (status == SMIC_SC_SMS_RD_NEXT)
217130803Smarcel		return (1);
218130803Smarcel	else
219130803Smarcel		return (2);
220130803Smarcel}
221130803Smarcel
222130803Smarcel/* Complete a transfer via a RD_END transaction after reading the last byte. */
223130803Smarcelstatic int
224130803Smarcelsmic_read_end(struct ipmi_softc *sc)
225130803Smarcel{
226130803Smarcel	u_char error, status;
227130803Smarcel
228130803Smarcel	OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_END);
229130803Smarcel	smic_set_busy(sc);
230130803Smarcel	smic_wait_for_not_busy(sc);
231130803Smarcel	status = INB(sc, SMIC_CTL_STS);
232130803Smarcel	if (status != SMIC_SC_SMS_RDY) {
233130803Smarcel		error = INB(sc, SMIC_DATA);
234130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Read did not end %02x\n",
235130803Smarcel		    error);
236130803Smarcel		return (0);
237130803Smarcel	}
238130803Smarcel	return (1);
239130803Smarcel}
240130803Smarcel
241130803Smarcelstatic int
242130803Smarcelsmic_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
243130803Smarcel{
244130803Smarcel	u_char *cp, data;
245130803Smarcel	int i, state;
246130803Smarcel
247130803Smarcel	/* First, start the message with the address. */
248130803Smarcel	if (!smic_start_write(sc, req->ir_addr))
249130803Smarcel		return (0);
250130803Smarcel#ifdef SMIC_DEBUG
251130803Smarcel	device_printf(sc->ipmi_dev, "SMIC: WRITE_START address: %02x\n",
252130803Smarcel	    req->ir_addr);
253130803Smarcel#endif
254130803Smarcel
255130803Smarcel	if (req->ir_requestlen == 0) {
256130803Smarcel		/* Send the command as the last byte. */
257130803Smarcel		if (!smic_write_last(sc, req->ir_command))
258130803Smarcel			return (0);
259130803Smarcel#ifdef SMIC_DEBUG
260130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
261130803Smarcel		    req->ir_command);
262130803Smarcel#endif
263130803Smarcel	} else {
264130803Smarcel		/* Send the command. */
265130803Smarcel		if (!smic_write_next(sc, req->ir_command))
266130803Smarcel			return (0);
267130803Smarcel#ifdef SMIC_DEBUG
268130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
269130803Smarcel		    req->ir_command);
270130803Smarcel#endif
271130803Smarcel
272130803Smarcel		/* Send the payload. */
273130803Smarcel		cp = req->ir_request;
274130803Smarcel		for (i = 0; i < req->ir_requestlen - 1; i++) {
275130803Smarcel			if (!smic_write_next(sc, *cp++))
276130803Smarcel				return (0);
277130803Smarcel#ifdef SMIC_DEBUG
278130803Smarcel			device_printf(sc->ipmi_dev, "SMIC: Wrote data: %02x\n",
279130803Smarcel			    cp[-1]);
280130803Smarcel#endif
281130803Smarcel		}
282130803Smarcel		if (!smic_write_last(sc, *cp))
283130803Smarcel			return (0);
284130803Smarcel#ifdef SMIC_DEBUG
285130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Write last data: %02x\n",
286130803Smarcel		    *cp);
287130803Smarcel#endif
288130803Smarcel	}
289130803Smarcel
290130803Smarcel	/* Start the read phase by reading the NetFn/LUN. */
291130803Smarcel	if (smic_start_read(sc, &data) != 1)
292130803Smarcel		return (0);
293130803Smarcel#ifdef SMIC_DEBUG
294130803Smarcel	device_printf(sc->ipmi_dev, "SMIC: Read address: %02x\n", data);
295130803Smarcel#endif
296130803Smarcel	if (data != IPMI_REPLY_ADDR(req->ir_addr)) {
297130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Reply address mismatch\n");
298130803Smarcel		return (0);
299130803Smarcel	}
300130803Smarcel
301130803Smarcel	/* Read the command. */
302130803Smarcel	if (smic_read_byte(sc, &data) != 1)
303130803Smarcel		return (0);
304130803Smarcel#ifdef SMIC_DEBUG
305130803Smarcel	device_printf(sc->ipmi_dev, "SMIC: Read command: %02x\n", data);
306130803Smarcel#endif
307130803Smarcel	if (data != req->ir_command) {
308130803Smarcel		device_printf(sc->ipmi_dev, "SMIC: Command mismatch\n");
309130803Smarcel		return (0);
310130803Smarcel	}
311130803Smarcel
312130803Smarcel	/* Read the completion code. */
313130803Smarcel	state = smic_read_byte(sc, &req->ir_compcode);
314130803Smarcel	if (state == 0)
315130803Smarcel		return (0);
316130803Smarcel#ifdef SMIC_DEBUG
317130803Smarcel	device_printf(sc->ipmi_dev, "SMIC: Read completion code: %02x\n",
318130803Smarcel	    req->ir_compcode);
319130803Smarcel#endif
320130803Smarcel
321130803Smarcel	/* Finally, read the reply from the BMC. */
322130803Smarcel	i = 0;
323130803Smarcel	while (state == 1) {
324130803Smarcel		state = smic_read_byte(sc, &data);
325130803Smarcel		if (state == 0)
326130803Smarcel			return (0);
327130803Smarcel		if (i < req->ir_replybuflen) {
328130803Smarcel			req->ir_reply[i] = data;
329130803Smarcel#ifdef SMIC_DEBUG
330130803Smarcel			device_printf(sc->ipmi_dev, "SMIC: Read data: %02x\n",
331130803Smarcel			    data);
332130803Smarcel		} else {
333130803Smarcel			device_printf(sc->ipmi_dev,
334130803Smarcel			    "SMIC: Read short %02x byte %d\n", data, i + 1);
335130803Smarcel#endif
336130803Smarcel		}
337130803Smarcel		i++;
338130803Smarcel	}
339130803Smarcel
340130803Smarcel	/* Terminate the transfer. */
341130803Smarcel	if (!smic_read_end(sc))
342130803Smarcel		return (0);
343130803Smarcel	req->ir_replylen = i;
344130803Smarcel#ifdef SMIC_DEBUG
345130803Smarcel	device_printf(sc->ipmi_dev, "SMIC: READ finished (%d bytes)\n", i);
346130803Smarcel	if (req->ir_replybuflen < i)
347130803Smarcel#else
348130803Smarcel	if (req->ir_replybuflen < i && req->ir_replybuflen != 0)
349130803Smarcel#endif
350130803Smarcel		device_printf(sc->ipmi_dev,
351130803Smarcel		    "SMIC: Read short: %zd buffer, %d actual\n",
352130803Smarcel		    req->ir_replybuflen, i);
353130803Smarcel	return (1);
354130803Smarcel}
355130803Smarcel
356130803Smarcelstatic void
357130803Smarcelsmic_loop(void *arg)
358130803Smarcel{
359130803Smarcel	struct ipmi_softc *sc = arg;
360130803Smarcel	struct ipmi_request *req;
361130803Smarcel	int i, ok;
362130803Smarcel
363130803Smarcel	IPMI_LOCK(sc);
364130803Smarcel	while ((req = ipmi_dequeue_request(sc)) != NULL) {
365130803Smarcel		IPMI_UNLOCK(sc);
366130803Smarcel		ok = 0;
367130803Smarcel		for (i = 0; i < 3 && !ok; i++)
368130803Smarcel			ok = smic_polled_request(sc, req);
369130803Smarcel		if (ok)
370130803Smarcel			req->ir_error = 0;
371130803Smarcel		else
372130803Smarcel			req->ir_error = EIO;
373130803Smarcel		IPMI_LOCK(sc);
374130803Smarcel		ipmi_complete_request(sc, req);
375130803Smarcel	}
376130803Smarcel	IPMI_UNLOCK(sc);
377130803Smarcel	kproc_exit(0);
378130803Smarcel}
379130803Smarcel
380130803Smarcelstatic int
381130803Smarcelsmic_startup(struct ipmi_softc *sc)
382130803Smarcel{
383130803Smarcel
384130803Smarcel	return (kproc_create(smic_loop, sc, &sc->ipmi_kthread, 0, 0,
385130803Smarcel	    "%s: smic", device_get_nameunit(sc->ipmi_dev)));
386130803Smarcel}
387130803Smarcel
388130803Smarcelint
389130803Smarcelipmi_smic_attach(struct ipmi_softc *sc)
390130803Smarcel{
391130803Smarcel	int flags;
392130803Smarcel
393130803Smarcel	/* Setup function pointers. */
394130803Smarcel	sc->ipmi_startup = smic_startup;
395130803Smarcel	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
396130803Smarcel
397130803Smarcel	/* See if we can talk to the controller. */
398130803Smarcel	flags = INB(sc, SMIC_FLAGS);
399130803Smarcel	if (flags == 0xff) {
400130803Smarcel		device_printf(sc->ipmi_dev, "couldn't find it\n");
401130803Smarcel		return (ENXIO);
402130803Smarcel	}
403130803Smarcel
404130803Smarcel#ifdef SMIC_DEBUG
405130803Smarcel	device_printf(sc->ipmi_dev, "SMIC: initial state: %02x\n", flags);
406130803Smarcel#endif
407130803Smarcel
408130803Smarcel	return (0);
409130803Smarcel}
410130803Smarcel