kernel: Import the IPMI driver from FreeBSD.
[dragonfly.git] / sys / dev / misc / ipmi / ipmi_smic.c
1 /*-
2  * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD: head/sys/dev/ipmi/ipmi_smic.c 248705 2013-03-25 14:30:34Z melifaro $
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/rman.h>
38 #include <sys/conf.h>
39
40 #ifdef LOCAL_MODULE
41 #include <ipmi.h>
42 #include <ipmivars.h>
43 #else
44 #include <sys/ipmi.h>
45 #include <dev/misc/ipmi/ipmivars.h>
46 #endif
47
48 static void     smic_wait_for_tx_okay(struct ipmi_softc *);
49 static void     smic_wait_for_rx_okay(struct ipmi_softc *);
50 static void     smic_wait_for_not_busy(struct ipmi_softc *);
51 static void     smic_set_busy(struct ipmi_softc *);
52
53 static void
54 smic_wait_for_tx_okay(struct ipmi_softc *sc)
55 {
56         int flags;
57
58         do {
59                 flags = INB(sc, SMIC_FLAGS);
60         } while (!(flags & SMIC_STATUS_TX_RDY));
61 }
62
63 static void
64 smic_wait_for_rx_okay(struct ipmi_softc *sc)
65 {
66         int flags;
67
68         do {
69                 flags = INB(sc, SMIC_FLAGS);
70         } while (!(flags & SMIC_STATUS_RX_RDY));
71 }
72
73 static void
74 smic_wait_for_not_busy(struct ipmi_softc *sc)
75 {
76         int flags;
77
78         do {
79                 flags = INB(sc, SMIC_FLAGS);
80         } while (flags & SMIC_STATUS_BUSY);
81 }
82
83 static void
84 smic_set_busy(struct ipmi_softc *sc)
85 {
86         int flags;
87
88         flags = INB(sc, SMIC_FLAGS);
89         flags |= SMIC_STATUS_BUSY;
90         flags &= ~SMIC_STATUS_RESERVED;
91         OUTB(sc, SMIC_FLAGS, flags);
92 }
93
94 /*
95  * Start a transfer with a WR_START transaction that sends the NetFn/LUN
96  * address.
97  */
98 static int
99 smic_start_write(struct ipmi_softc *sc, u_char data)
100 {
101         u_char error, status;
102
103         smic_wait_for_not_busy(sc);
104
105         OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_START);
106         OUTB(sc, SMIC_DATA, data);
107         smic_set_busy(sc);
108         smic_wait_for_not_busy(sc);
109         status = INB(sc, SMIC_CTL_STS);
110         if (status != SMIC_SC_SMS_WR_START) {
111                 error = INB(sc, SMIC_DATA);
112                 device_printf(sc->ipmi_dev, "SMIC: Write did not start %02x\n",
113                     error);
114                 return (0);
115         }
116         return (1);
117 }
118
119 /*
120  * Write a byte in the middle of the message (either the command or one of
121  * the data bytes) using a WR_NEXT transaction.
122  */
123 static int
124 smic_write_next(struct ipmi_softc *sc, u_char data)
125 {
126         u_char error, status;
127
128         smic_wait_for_tx_okay(sc);
129         OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT);
130         OUTB(sc, SMIC_DATA, data);
131         smic_set_busy(sc);
132         smic_wait_for_not_busy(sc);
133         status = INB(sc, SMIC_CTL_STS);
134         if (status != SMIC_SC_SMS_WR_NEXT) {
135                 error = INB(sc, SMIC_DATA);
136                 device_printf(sc->ipmi_dev, "SMIC: Write did not next %02x\n",
137                     error);
138                 return (0);
139         }
140         return (1);
141 }
142
143 /*
144  * Write the last byte of a transfer to end the write phase via a WR_END
145  * transaction.
146  */
147 static int
148 smic_write_last(struct ipmi_softc *sc, u_char data)
149 {
150         u_char error, status;
151
152         smic_wait_for_tx_okay(sc);
153         OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END);
154         OUTB(sc, SMIC_DATA, data);
155         smic_set_busy(sc);
156         smic_wait_for_not_busy(sc);
157         status = INB(sc, SMIC_CTL_STS);
158         if (status != SMIC_SC_SMS_WR_END) {
159                 error = INB(sc, SMIC_DATA);
160                 device_printf(sc->ipmi_dev, "SMIC: Write did not end %02x\n",
161                     error);
162                 return (0);
163         }
164         return (1);
165 }
166
167 /*
168  * Start the read phase of a transfer with a RD_START transaction.
169  */
170 static int
171 smic_start_read(struct ipmi_softc *sc, u_char *data)
172 {
173         u_char error, status;
174
175         smic_wait_for_not_busy(sc);
176
177         smic_wait_for_rx_okay(sc);
178         OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START);
179         smic_set_busy(sc);
180         smic_wait_for_not_busy(sc);
181         status = INB(sc, SMIC_CTL_STS);
182         if (status != SMIC_SC_SMS_RD_START) {
183                 error = INB(sc, SMIC_DATA);
184                 device_printf(sc->ipmi_dev, "SMIC: Read did not start %02x\n",
185                     error);
186                 return (0);
187         }
188         *data = INB(sc, SMIC_DATA);
189         return (1);
190 }
191
192 /*
193  * Read a byte via a RD_NEXT transaction.  If this was the last byte, return
194  * 2 rather than 1.
195  */
196 static int
197 smic_read_byte(struct ipmi_softc *sc, u_char *data)
198 {
199         u_char error, status;
200
201         smic_wait_for_rx_okay(sc);
202         OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT);
203         smic_set_busy(sc);
204         smic_wait_for_not_busy(sc);
205         status = INB(sc, SMIC_CTL_STS);
206         if (status != SMIC_SC_SMS_RD_NEXT &&
207             status != SMIC_SC_SMS_RD_END) {
208                 error = INB(sc, SMIC_DATA);
209                 device_printf(sc->ipmi_dev, "SMIC: Read did not next %02x\n",
210                     error);
211                 return (0);
212         }
213         *data = INB(sc, SMIC_DATA);
214         if (status == SMIC_SC_SMS_RD_NEXT)
215                 return (1);
216         else
217                 return (2);
218 }
219
220 /* Complete a transfer via a RD_END transaction after reading the last byte. */
221 static int
222 smic_read_end(struct ipmi_softc *sc)
223 {
224         u_char error, status;
225
226         OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_END);
227         smic_set_busy(sc);
228         smic_wait_for_not_busy(sc);
229         status = INB(sc, SMIC_CTL_STS);
230         if (status != SMIC_SC_SMS_RDY) {
231                 error = INB(sc, SMIC_DATA);
232                 device_printf(sc->ipmi_dev, "SMIC: Read did not end %02x\n",
233                     error);
234                 return (0);
235         }
236         return (1);
237 }
238
239 static int
240 smic_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
241 {
242         u_char *cp, data;
243         int i, state;
244
245         /* First, start the message with the address. */
246         if (!smic_start_write(sc, req->ir_addr))
247                 return (0);
248 #ifdef SMIC_DEBUG
249         device_printf(sc->ipmi_dev, "SMIC: WRITE_START address: %02x\n",
250             req->ir_addr);
251 #endif
252
253         if (req->ir_requestlen == 0) {
254                 /* Send the command as the last byte. */
255                 if (!smic_write_last(sc, req->ir_command))
256                         return (0);
257 #ifdef SMIC_DEBUG
258                 device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
259                     req->ir_command);
260 #endif
261         } else {
262                 /* Send the command. */
263                 if (!smic_write_next(sc, req->ir_command))
264                         return (0);
265 #ifdef SMIC_DEBUG
266                 device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n",
267                     req->ir_command);
268 #endif
269
270                 /* Send the payload. */
271                 cp = req->ir_request;
272                 for (i = 0; i < req->ir_requestlen - 1; i++) {
273                         if (!smic_write_next(sc, *cp++))
274                                 return (0);
275 #ifdef SMIC_DEBUG
276                         device_printf(sc->ipmi_dev, "SMIC: Wrote data: %02x\n",
277                             cp[-1]);
278 #endif
279                 }
280                 if (!smic_write_last(sc, *cp))
281                         return (0);
282 #ifdef SMIC_DEBUG
283                 device_printf(sc->ipmi_dev, "SMIC: Write last data: %02x\n",
284                     *cp);
285 #endif
286         }
287
288         /* Start the read phase by reading the NetFn/LUN. */
289         if (smic_start_read(sc, &data) != 1)
290                 return (0);
291 #ifdef SMIC_DEBUG
292         device_printf(sc->ipmi_dev, "SMIC: Read address: %02x\n", data);
293 #endif
294         if (data != IPMI_REPLY_ADDR(req->ir_addr)) {
295                 device_printf(sc->ipmi_dev, "SMIC: Reply address mismatch\n");
296                 return (0);
297         }
298
299         /* Read the command. */
300         if (smic_read_byte(sc, &data) != 1)
301                 return (0);
302 #ifdef SMIC_DEBUG
303         device_printf(sc->ipmi_dev, "SMIC: Read command: %02x\n", data);
304 #endif
305         if (data != req->ir_command) {
306                 device_printf(sc->ipmi_dev, "SMIC: Command mismatch\n");
307                 return (0);
308         }
309
310         /* Read the completion code. */
311         state = smic_read_byte(sc, &req->ir_compcode);
312         if (state == 0)
313                 return (0);
314 #ifdef SMIC_DEBUG
315         device_printf(sc->ipmi_dev, "SMIC: Read completion code: %02x\n",
316             req->ir_compcode);
317 #endif
318
319         /* Finally, read the reply from the BMC. */
320         i = 0;
321         while (state == 1) {
322                 state = smic_read_byte(sc, &data);
323                 if (state == 0)
324                         return (0);
325                 if (i < req->ir_replybuflen) {
326                         req->ir_reply[i] = data;
327 #ifdef SMIC_DEBUG
328                         device_printf(sc->ipmi_dev, "SMIC: Read data: %02x\n",
329                             data);
330                 } else {
331                         device_printf(sc->ipmi_dev,
332                             "SMIC: Read short %02x byte %d\n", data, i + 1);
333 #endif
334                 }
335                 i++;
336         }
337
338         /* Terminate the transfer. */
339         if (!smic_read_end(sc))
340                 return (0);
341         req->ir_replylen = i;
342 #ifdef SMIC_DEBUG
343         device_printf(sc->ipmi_dev, "SMIC: READ finished (%d bytes)\n", i);
344         if (req->ir_replybuflen < i)
345 #else
346         if (req->ir_replybuflen < i && req->ir_replybuflen != 0)
347 #endif
348                 device_printf(sc->ipmi_dev,
349                     "SMIC: Read short: %zd buffer, %d actual\n",
350                     req->ir_replybuflen, i);
351         return (1);
352 }
353
354 static void
355 smic_loop(void *arg)
356 {
357         struct ipmi_softc *sc = arg;
358         struct ipmi_request *req;
359         int i, ok;
360
361         IPMI_LOCK(sc);
362         while ((req = ipmi_dequeue_request(sc)) != NULL) {
363                 IPMI_UNLOCK(sc);
364                 ok = 0;
365                 for (i = 0; i < 3 && !ok; i++)
366                         ok = smic_polled_request(sc, req);
367                 if (ok)
368                         req->ir_error = 0;
369                 else
370                         req->ir_error = EIO;
371                 IPMI_LOCK(sc);
372                 ipmi_complete_request(sc, req);
373         }
374         IPMI_UNLOCK(sc);
375         kthread_exit();
376 }
377
378 static int
379 smic_startup(struct ipmi_softc *sc)
380 {
381
382         return (kthread_create(smic_loop, sc, &sc->ipmi_kthread,
383             "%s: smic", device_get_nameunit(sc->ipmi_dev)));
384 }
385
386 int
387 ipmi_smic_attach(struct ipmi_softc *sc)
388 {
389         int flags;
390
391         /* Setup function pointers. */
392         sc->ipmi_startup = smic_startup;
393         sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
394
395         /* See if we can talk to the controller. */
396         flags = INB(sc, SMIC_FLAGS);
397         if (flags == 0xff) {
398                 device_printf(sc->ipmi_dev, "couldn't find it\n");
399                 return (ENXIO);
400         }
401
402 #ifdef SMIC_DEBUG
403         device_printf(sc->ipmi_dev, "SMIC: initial state: %02x\n", flags);
404 #endif
405
406         return (0);
407 }