Merge branch 'vendor/WPA_SUPPLICANT'
[dragonfly.git] / sys / dev / misc / ipmi / ipmi_ssif.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_ssif.c 172836 2007-10-20 23:23:23Z julian $
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/conf.h>
38
39 #include <bus/smbus/smbconf.h>
40 #include <dev/smbus/smb/smb.h>
41
42 #include "smbus_if.h"
43
44 #ifdef LOCAL_MODULE
45 #include <ipmivars.h>
46 #else
47 #include <dev/misc/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
58 static void
59 dump_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                 kprintf(" %02x", bytes[i]);
66         kprintf("\n");
67 }
68 #endif
69
70 static int
71 ssif_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         tsleep(ssif_polled_request, 0, "ssifwt", hz / 10);
178
179         /* Try to read the first packet. */
180 read_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                 tsleep(ssif_polled_request, 0, "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                         kprintf("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
302 done:
303         /* Save the total length and return success. */
304         req->ir_replylen = len;
305         smbus_release_bus(smbus, dev);
306         return (1);
307
308 fail:
309         smbus_release_bus(smbus, dev);
310         return (0);
311 }
312
313 static void
314 ssif_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                         tsleep(ssif_loop, 0, "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                 tsleep(ssif_loop, 0, "delay", hz / 100);
346
347                 IPMI_LOCK(sc);
348         }
349         IPMI_UNLOCK(sc);
350         kthread_exit();
351 }
352
353 static int
354 ssif_startup(struct ipmi_softc *sc)
355 {
356
357         return (kthread_create(ssif_loop, sc, &sc->ipmi_kthread,
358             "%s: ssif", device_get_nameunit(sc->ipmi_dev)));
359 }
360
361 int
362 ipmi_ssif_attach(struct ipmi_softc *sc, device_t smbus, int smbus_address)
363 {
364
365         /* Setup smbus address. */
366         sc->ipmi_ssif_smbus = smbus;
367         sc->ipmi_ssif_smbus_address = smbus_address;
368
369         /* Setup function pointers. */
370         sc->ipmi_startup = ssif_startup;
371         sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
372
373         return (0);
374 }