54d4e0636f37093b089fc4a7748ca9e7db8533d4
[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
8 #include <sys/ioctl.h>
9 #include <sys/time.h>
10 #include <sys/types.h>
11
12 #include <linux/ipmi.h>
13
14 #include <log/log.h>
15 #include <talloc/talloc.h>
16
17 #include "ipmi.h"
18
19 struct ipmi {
20         int     fd;
21         long    seq;
22 };
23
24 static const char *ipmi_devnode = "/dev/ipmi0";
25
26 bool ipmi_bootdev_is_valid(int x)
27 {
28         switch (x) {
29         case IPMI_BOOTDEV_NONE:
30         case IPMI_BOOTDEV_NETWORK:
31         case IPMI_BOOTDEV_DISK:
32         case IPMI_BOOTDEV_SAFE:
33         case IPMI_BOOTDEV_CDROM:
34         case IPMI_BOOTDEV_SETUP:
35                 return true;
36         }
37
38         return false;
39 }
40
41 static int ipmi_send(struct ipmi *ipmi, uint8_t netfn, uint8_t cmd,
42                 uint8_t *buf, uint16_t len)
43 {
44         struct ipmi_system_interface_addr addr;
45         struct ipmi_req req;
46         int rc;
47
48         memset(&addr, 0, sizeof(addr));
49         addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
50         addr.channel = IPMI_BMC_CHANNEL;
51
52         memset(&req, 0, sizeof(req));
53         req.addr = (unsigned char *)&addr;
54         req.addr_len = sizeof(addr);
55
56         req.msgid = ipmi->seq++;
57
58         req.msg.data = buf;
59         req.msg.data_len = len;
60         req.msg.netfn = netfn;
61         req.msg.cmd = cmd;
62
63         rc = ioctl(ipmi->fd, IPMICTL_SEND_COMMAND, &req);
64         if (rc < 0) {
65                 pb_log("IPMI: send (netfn %d, cmd %d, %d bytes) failed: %m\n",
66                                 netfn, cmd, len);
67                 return -1;
68         }
69
70         return 0;
71 }
72
73 static int ipmi_recv(struct ipmi *ipmi, uint8_t *netfn, uint8_t *cmd,
74                 long *seq, uint8_t *buf, uint16_t *len)
75 {
76         struct ipmi_recv recv;
77         struct ipmi_addr addr;
78         int rc;
79
80         recv.addr = (unsigned char *)&addr;
81         recv.addr_len = sizeof(addr);
82         recv.msg.data = buf;
83         recv.msg.data_len = *len;
84
85         rc = ioctl(ipmi->fd, IPMICTL_RECEIVE_MSG_TRUNC, &recv);
86         if (rc < 0 && errno != EMSGSIZE) {
87                 pb_log("IPMI: recv (%d bytes) failed: %m\n", *len);
88                 return -1;
89         } else if (rc < 0 && errno == EMSGSIZE) {
90                 pb_debug("IPMI: truncated message (netfn %d, cmd %d, "
91                                 "size %d), continuing anyway\n",
92                                 recv.msg.netfn, recv.msg.cmd, *len);
93         }
94
95         *netfn = recv.msg.netfn;
96         *cmd = recv.msg.cmd;
97         *seq = recv.msgid;
98         *len = recv.msg.data_len;
99
100         return 0;
101 }
102
103 int ipmi_transaction(struct ipmi *ipmi, uint8_t netfn, uint8_t cmd,
104                 uint8_t *req_buf, uint16_t req_len,
105                 uint8_t *resp_buf, uint16_t *resp_len,
106                 int timeout_ms)
107 {
108         struct timeval start, now, delta;
109         struct pollfd pollfds[1];
110         struct flock lock;
111         int expired_ms, rc;
112
113         memset(&lock, 0, sizeof(lock));
114         lock.l_type = F_WRLCK;
115         lock.l_whence = SEEK_SET;
116         rc = fcntl(ipmi->fd, F_SETLKW, &lock);
117         if (rc == -1) {
118                 pb_log("IPMI: error locking IPMI device: %m\n");
119                 return rc;
120         }
121
122         rc = ipmi_send(ipmi, netfn, cmd, req_buf, req_len);
123         if (rc)
124                 goto out;
125
126         pollfds[0].fd = ipmi->fd;
127         pollfds[0].events = POLLIN;
128
129         gettimeofday(&start, NULL);
130         expired_ms = 0;
131
132         for (;;) {
133                 uint8_t resp_netfn, resp_cmd;
134                 long seq;
135
136                 rc = poll(pollfds, 1, timeout_ms - expired_ms);
137
138                 if (rc < 0) {
139                         pb_log("IPMI: poll() error %m");
140                         break;
141                 }
142                 if (rc == 0) {
143                         pb_log("IPMI: timeout waiting for response "
144                                         "(netfn %d, cmd %d)\n", netfn, cmd);
145                         rc = -1;
146                         break;
147                 }
148
149                 if (!(pollfds[0].revents & POLLIN)) {
150                         pb_log("IPMI: unexpected fd status from poll?\n");
151                         rc = -1;
152                         break;
153                 }
154
155                 rc = ipmi_recv(ipmi, &resp_netfn, &resp_cmd, &seq,
156                                 resp_buf, resp_len);
157                 if (rc)
158                         break;
159
160                 if (seq != ipmi->seq - 1) {
161                         pb_log("IPMI: out-of-sequence reply: "
162                                         "exp %ld, got %ld\n",
163                                         ipmi->seq, seq);
164
165                         if (timeout_ms) {
166                                 gettimeofday(&now, NULL);
167                                 timersub(&now, &start, &delta);
168                                 expired_ms = (delta.tv_sec * 1000) +
169                                                 (delta.tv_usec / 1000);
170
171                                 if (expired_ms >= timeout_ms) {
172                                         rc = -1;
173                                         break;
174                                 }
175                         }
176                 } else {
177                         pb_debug("IPMI: netfn(%x->%x), cmd(%x->%x)\n",
178                                         netfn, resp_netfn, cmd, resp_cmd);
179                         rc = 0;
180                         goto out;
181                 }
182         }
183
184 out:
185         lock.l_type = F_UNLCK;
186         fcntl(ipmi->fd, F_SETLKW, &lock);
187         return rc ? -1 : 0;
188 }
189
190 static int ipmi_destroy(void *p)
191 {
192         struct ipmi *ipmi = p;
193         close(ipmi->fd);
194         return 1;
195 }
196
197 struct ipmi *ipmi_open(void *ctx)
198 {
199         struct ipmi *ipmi;
200         int fd;
201
202         fd = open(ipmi_devnode, O_RDWR);
203         if (fd < 0) {
204                 pb_log("IPMI: can't open IPMI device %s: %m\n", ipmi_devnode);
205                 return NULL;
206         }
207
208         ipmi = talloc(ctx, struct ipmi);
209         ipmi->fd = fd;
210         ipmi->seq = 0;
211
212         talloc_set_destructor(ipmi, ipmi_destroy);
213
214         return ipmi;
215 }
216
217 bool ipmi_present(void)
218 {
219         return !access(ipmi_devnode, R_OK | W_OK);
220 }
221