]> git.ozlabs.org Git - petitboot/blob - discover/ipmi.c
discover: Nicely format IPMI response buffers
[petitboot] / discover / ipmi.c
1
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <poll.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <arpa/inet.h>
8
9 #include <sys/ioctl.h>
10 #include <sys/time.h>
11 #include <sys/types.h>
12
13 #include <linux/ipmi.h>
14
15 #include <log/log.h>
16 #include <talloc/talloc.h>
17
18 #include "ipmi.h"
19
20 struct ipmi {
21         int     fd;
22         long    seq;
23 };
24
25 static const char *ipmi_devnode = "/dev/ipmi0";
26
27 bool ipmi_bootdev_is_valid(int x)
28 {
29         switch (x) {
30         case IPMI_BOOTDEV_NONE:
31         case IPMI_BOOTDEV_NETWORK:
32         case IPMI_BOOTDEV_DISK:
33         case IPMI_BOOTDEV_SAFE:
34         case IPMI_BOOTDEV_CDROM:
35         case IPMI_BOOTDEV_SETUP:
36                 return true;
37         }
38
39         return false;
40 }
41
42 static int ipmi_send(struct ipmi *ipmi, uint8_t netfn, uint8_t cmd,
43                 uint8_t *buf, uint16_t len)
44 {
45         struct ipmi_system_interface_addr addr;
46         struct ipmi_req req;
47         int rc;
48
49         memset(&addr, 0, sizeof(addr));
50         addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
51         addr.channel = IPMI_BMC_CHANNEL;
52
53         memset(&req, 0, sizeof(req));
54         req.addr = (unsigned char *)&addr;
55         req.addr_len = sizeof(addr);
56
57         req.msgid = ipmi->seq++;
58
59         req.msg.data = buf;
60         req.msg.data_len = len;
61         req.msg.netfn = netfn;
62         req.msg.cmd = cmd;
63
64         rc = ioctl(ipmi->fd, IPMICTL_SEND_COMMAND, &req);
65         if (rc < 0) {
66                 pb_log("IPMI: send (netfn %d, cmd %d, %d bytes) failed: %m\n",
67                                 netfn, cmd, len);
68                 return -1;
69         }
70
71         return 0;
72 }
73
74 static int ipmi_recv(struct ipmi *ipmi, uint8_t *netfn, uint8_t *cmd,
75                 long *seq, uint8_t *buf, uint16_t *len)
76 {
77         struct ipmi_recv recv;
78         struct ipmi_addr addr;
79         int rc;
80
81         recv.addr = (unsigned char *)&addr;
82         recv.addr_len = sizeof(addr);
83         recv.msg.data = buf;
84         recv.msg.data_len = *len;
85
86         rc = ioctl(ipmi->fd, IPMICTL_RECEIVE_MSG_TRUNC, &recv);
87         if (rc < 0 && errno != EMSGSIZE) {
88                 pb_log("IPMI: recv (%d bytes) failed: %m\n", *len);
89                 return -1;
90         } else if (rc < 0 && errno == EMSGSIZE) {
91                 pb_debug("IPMI: truncated message (netfn %d, cmd %d, "
92                                 "size %d), continuing anyway\n",
93                                 recv.msg.netfn, recv.msg.cmd, *len);
94         }
95
96         *netfn = recv.msg.netfn;
97         *cmd = recv.msg.cmd;
98         *seq = recv.msgid;
99         *len = recv.msg.data_len;
100
101         return 0;
102 }
103
104 int ipmi_transaction(struct ipmi *ipmi, uint8_t netfn, uint8_t cmd,
105                 uint8_t *req_buf, uint16_t req_len,
106                 uint8_t *resp_buf, uint16_t *resp_len,
107                 int timeout_ms)
108 {
109         struct timeval start, now, delta;
110         struct pollfd pollfds[1];
111         struct flock lock;
112         int expired_ms, rc;
113
114         memset(&lock, 0, sizeof(lock));
115         lock.l_type = F_WRLCK;
116         lock.l_whence = SEEK_SET;
117         rc = fcntl(ipmi->fd, F_SETLKW, &lock);
118         if (rc == -1) {
119                 pb_log("IPMI: error locking IPMI device: %m\n");
120                 return rc;
121         }
122
123         rc = ipmi_send(ipmi, netfn, cmd, req_buf, req_len);
124         if (rc)
125                 goto out;
126
127         pollfds[0].fd = ipmi->fd;
128         pollfds[0].events = POLLIN;
129
130         gettimeofday(&start, NULL);
131         expired_ms = 0;
132
133         for (;;) {
134                 uint8_t resp_netfn, resp_cmd;
135                 long seq;
136
137                 rc = poll(pollfds, 1, timeout_ms - expired_ms);
138
139                 if (rc < 0) {
140                         pb_log("IPMI: poll() error %m");
141                         break;
142                 }
143                 if (rc == 0) {
144                         pb_log("IPMI: timeout waiting for response "
145                                         "(netfn %d, cmd %d)\n", netfn, cmd);
146                         rc = -1;
147                         break;
148                 }
149
150                 if (!(pollfds[0].revents & POLLIN)) {
151                         pb_log("IPMI: unexpected fd status from poll?\n");
152                         rc = -1;
153                         break;
154                 }
155
156                 rc = ipmi_recv(ipmi, &resp_netfn, &resp_cmd, &seq,
157                                 resp_buf, resp_len);
158                 if (rc)
159                         break;
160
161                 if (seq != ipmi->seq - 1) {
162                         pb_log("IPMI: out-of-sequence reply: "
163                                         "exp %ld, got %ld\n",
164                                         ipmi->seq, seq);
165
166                         if (timeout_ms) {
167                                 gettimeofday(&now, NULL);
168                                 timersub(&now, &start, &delta);
169                                 expired_ms = (delta.tv_sec * 1000) +
170                                                 (delta.tv_usec / 1000);
171
172                                 if (expired_ms >= timeout_ms) {
173                                         rc = -1;
174                                         break;
175                                 }
176                         }
177                 } else {
178                         pb_debug("IPMI: netfn(%x->%x), cmd(%x->%x)\n",
179                                         netfn, resp_netfn, cmd, resp_cmd);
180                         rc = 0;
181                         goto out;
182                 }
183         }
184
185 out:
186         lock.l_type = F_UNLCK;
187         if (fcntl(ipmi->fd, F_SETLKW, &lock) == -1)
188                 pb_log("IPMI: error unlocking IPMI device: %m\n");
189         return rc ? -1 : 0;
190 }
191
192 static int ipmi_destroy(void *p)
193 {
194         struct ipmi *ipmi = p;
195         close(ipmi->fd);
196         return 1;
197 }
198
199 struct ipmi *ipmi_open(void *ctx)
200 {
201         struct ipmi *ipmi;
202         int fd;
203
204         fd = open(ipmi_devnode, O_RDWR | O_CLOEXEC);
205         if (fd < 0) {
206                 pb_log("IPMI: can't open IPMI device %s: %m\n", ipmi_devnode);
207                 return NULL;
208         }
209
210         ipmi = talloc(ctx, struct ipmi);
211         ipmi->fd = fd;
212         ipmi->seq = 0;
213
214         talloc_set_destructor(ipmi, ipmi_destroy);
215
216         return ipmi;
217 }
218
219 bool ipmi_present(void)
220 {
221         return !access(ipmi_devnode, R_OK | W_OK);
222 }
223
224 /* Reads and applies an IPMI interface config override, which closely follows
225  * the format of an interface config struct as described in lib/types */
226 int parse_ipmi_interface_override(struct config *config, uint8_t *buf,
227                                 uint16_t len)
228 {
229         struct interface_config *ifconf;
230         char *ipstr, *gatewaystr;
231         uint8_t hwsize, ipsize;
232         int addr_type, i = 0;
233         socklen_t addr_len;
234
235         /* Get 1-byte hardware address size and ip address size */
236         memcpy(&hwsize, &buf[i], sizeof(hwsize));
237         i += sizeof(hwsize);
238         memcpy(&ipsize, &buf[i], sizeof(ipsize));
239         i += sizeof(ipsize);
240
241         if (!hwsize || !ipsize) {
242                 pb_log_fn("Empty response\n");
243                 return -1;
244         }
245
246         /* At the moment only support 6-byte MAC addresses */
247         if (hwsize != sizeof(ifconf->hwaddr)) {
248                 pb_log("Unsupported HW address size in network override: %u\n",
249                        hwsize);
250                 return -1;
251         }
252
253         /* Sanity check the IP address size */
254         if (ipsize == 4) {
255                 addr_type = AF_INET;
256                 addr_len = INET_ADDRSTRLEN;
257         } else if (ipsize == 16) {
258                 addr_type = AF_INET6;
259                 addr_len = INET6_ADDRSTRLEN;
260         } else {
261                 pb_log("Unsupported IP address size: %u\n", ipsize);
262                 return -1;
263         }
264
265         /* Everything past here is the interface config */
266         ifconf = talloc_zero(config, struct interface_config);
267         if (!ifconf) {
268                 pb_log("Failed to allocate network override\n");
269                 return -1;
270         }
271
272         /* Hardware Address */
273         memcpy(ifconf->hwaddr, &buf[i], hwsize);
274         i += hwsize;
275
276         /* Check 1-byte ignore and method flags */
277         ifconf->ignore = !!buf[i++];
278         ifconf->method = !!buf[i++];
279
280         if (ifconf->method == CONFIG_METHOD_STATIC) {
281                 if (ipsize + ipsize  + 1 > len - i) {
282                         pb_log("Expected data greater than buffer size\n");
283                         talloc_free(ifconf);
284                         return -1;
285                 }
286
287                 /* IP address */
288                 ipstr = talloc_array(ifconf, char, addr_len);
289                 if (!inet_ntop(addr_type, &buf[i], ipstr, addr_len)) {
290                         pb_log("Failed to convert ipaddr: %m\n");
291                         talloc_free(ifconf);
292                         return -1;
293                 }
294                 i += ipsize;
295
296                 /* IP address subnet */
297                 ifconf->static_config.address = talloc_asprintf(ifconf,
298                                                 "%s/%u", ipstr, buf[i]);
299                 i++;
300
301                 /* Gateway address */
302                 gatewaystr = talloc_array(ifconf, char, addr_len);
303                 if (!inet_ntop(addr_type, &buf[i], gatewaystr, addr_len)) {
304                         pb_log("Failed to convert gateway: %m\n");
305                         talloc_free(ifconf);
306                         return -1;
307                 }
308                 ifconf->static_config.gateway = gatewaystr;
309                 i += ipsize;
310         }
311
312         ifconf->override = true;
313         pb_log("Applying IPMI network interface override\n");
314
315         /* Replace any existing interface config */
316         talloc_free(config->network.interfaces);
317         config->network.n_interfaces = 1;
318         config->network.interfaces = talloc(config, struct interface_config *);
319         config->network.interfaces[0] = ifconf;
320
321         return 0;
322 }
323
324 void ipmi_get_bmc_mac(struct ipmi *ipmi, uint8_t *buf)
325 {
326         uint16_t resp_len = 8;
327         uint8_t resp[8];
328         uint8_t req[] = { 0x1, 0x5, 0x0, 0x0 };
329         char *debug_buf;
330         int i, rc;
331
332         rc = ipmi_transaction(ipmi, IPMI_NETFN_TRANSPORT,
333                         IPMI_CMD_TRANSPORT_GET_LAN_PARAMS,
334                         req, sizeof(req),
335                         resp, &resp_len,
336                         ipmi_timeout);
337
338         debug_buf = format_buffer(ipmi, resp, resp_len);
339         pb_debug_fn("BMC MAC resp [%d][%d]:\n%s\n",
340                         rc, resp_len, debug_buf);
341         talloc_free(debug_buf);
342
343         if (rc == 0 && resp_len > 0) {
344                 for (i = 2; i < resp_len; i++) {
345                         buf[i - 2] = resp[i];
346                 }
347         }
348
349 }
350
351 /*
352  * Retrieve info from the "Get Device ID" IPMI commands.
353  * See Chapter 20.1 in the IPMIv2 specification.
354  */
355 void ipmi_get_bmc_versions(struct ipmi *ipmi, struct system_info *info)
356 {
357         uint16_t resp_len = 16;
358         uint8_t resp[16], bcd;
359         char *debug_buf;
360         int rc;
361
362         /* Retrieve info from current side */
363         rc = ipmi_transaction(ipmi, IPMI_NETFN_APP,
364                         IPMI_CMD_APP_GET_DEVICE_ID,
365                         NULL, 0,
366                         resp, &resp_len,
367                         ipmi_timeout);
368
369         debug_buf = format_buffer(ipmi, resp, resp_len);
370         pb_debug_fn("BMC version resp [%d][%d]:\n%s\n",
371                         rc, resp_len, debug_buf);
372         talloc_free(debug_buf);
373
374         if (rc == 0 && (resp_len == 12 || resp_len == 16)) {
375                 info->bmc_current = talloc_array(info, char *, 4);
376                 info->n_bmc_current = 4;
377
378                 info->bmc_current[0] = talloc_asprintf(info, "Device ID: 0x%x",
379                                                 resp[1]);
380                 info->bmc_current[1] = talloc_asprintf(info, "Device Rev: 0x%x",
381                                                 resp[2]);
382                 bcd = resp[4] & 0x0f;
383                 bcd += 10 * (resp[4] >> 4);
384                 /* rev1.rev2.aux_revision */
385                 info->bmc_current[2] = talloc_asprintf(info,
386                                 "Firmware version: %u.%02u",
387                                 resp[3], bcd);
388                 if (resp_len == 16) {
389                         info->bmc_current[2] = talloc_asprintf_append(
390                                         info->bmc_current[2],
391                                         ".%02x%02x%02x%02x",
392                                         resp[12], resp[13], resp[14], resp[15]);
393                 }
394                 bcd = resp[5] & 0x0f;
395                 bcd += 10 * (resp[5] >> 4);
396                 info->bmc_current[3] = talloc_asprintf(info, "IPMI version: %u",
397                                                 bcd);
398         } else
399                 pb_debug_fn("Failed to retrieve Device ID from IPMI\n");
400
401         /* Retrieve info from golden side */
402         memset(resp, 0, sizeof(resp));
403         resp_len = 16;
404         rc = ipmi_transaction(ipmi, IPMI_NETFN_AMI,
405                         IPMI_CMD_APP_GET_DEVICE_ID_GOLDEN,
406                         NULL, 0,
407                         resp, &resp_len,
408                         ipmi_timeout);
409
410         debug_buf = format_buffer(ipmi, resp, resp_len);
411         pb_debug_fn("BMC golden resp [%d][%d]:\n%s\n",
412                         rc, resp_len, debug_buf);
413         talloc_free(debug_buf);
414
415         if (rc == 0 && (resp_len == 12 || resp_len == 16)) {
416                 info->bmc_golden = talloc_array(info, char *, 4);
417                 info->n_bmc_golden = 4;
418
419                 info->bmc_golden[0] = talloc_asprintf(info, "Device ID: 0x%x",
420                                                 resp[1]);
421                 info->bmc_golden[1] = talloc_asprintf(info, "Device Rev: 0x%x",
422                                                 resp[2]);
423                 bcd = resp[4] & 0x0f;
424                 bcd += 10 * (resp[4] >> 4);
425                 /* rev1.rev2.aux_revision */
426                 info->bmc_golden[2] = talloc_asprintf(info,
427                                 "Firmware version: %u.%02u",
428                                 resp[3], bcd);
429                 if (resp_len == 16) {
430                         info->bmc_golden[2] = talloc_asprintf_append(
431                                         info->bmc_golden[2],
432                                         ".%02x%02x%02x%02x",
433                                         resp[12], resp[13], resp[14], resp[15]);
434                 }
435                 bcd = resp[5] & 0x0f;
436                 bcd += 10 * (resp[5] >> 4);
437                 info->bmc_golden[3] = talloc_asprintf(info, "IPMI version: %u",
438                                                 bcd);
439         } else
440                 pb_debug_fn("Failed to retrieve Golden Device ID from IPMI\n");
441 }
442