1 /*
2 * linux-cmd-handler.c
3 *
4 * A test/example program that receives a command sent to LUN 2,
5 * prints it out, and sends a response.
6 *
7 * Author: MontaVista Software, Inc.
8 * Corey Minyard <minyard@mvista.com>
9 * source@mvista.com
10 *
11 * Copyright 2017 MontaVista Software Inc.
12 *
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU General Public License as published by the
15 * Free Software Foundation; either version 2 of the License, or (at your
16 * option) any later version.
17 *
18 *
19 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
20 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
25 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
28 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 * You should have received a copy of the GNU General Public License along
31 * with this program; if not, write to the Free Software Foundation, Inc.,
32 * 675 Mass Ave, Cambridge, MA 02139, USA.
33 */
34
35 /*
36 * This program provides an example of how to receive a command from
37 * the BMC and send a response. This can be used to extend IPMI in the
38 * host system, if you want to do that.
39 *
40 * This works by sending a sending a command to the BMC on LUN 2. The
41 * BMC should route this to the receive queue, the driver will pick it
42 * up and if something is registered to that particular netfn/cmd, it
43 * will route it to that.
44 *
45 * Generally you are sending these commands over a lan interface. Here is
46 * an example ipmitool command to do this:
47 *
48 * ipmitool -I lan -A MD5 -U <user> -P <pw) -l 2 -H t-langley-1 raw 2 3 1 2 3 4
49 * You should get the response:
50 * 01 02 03 04
51 * Note that older versions on ipmitool may have a broken -l option.
52 *
53 * In openipmicmd, you would do the following:
54 * => f 2 2 3 1 2 3 4
55 * => Got message:
56 * type = response
57 * addr_type = SI
58 * channel = 0xf
59 * lun = 0x2
60 * netfn = 0x3
61 * cmd = 0x3
62 * data =00 01 02 03 04
63 *
64 */
65
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <sys/types.h>
69 #include <sys/stat.h>
70 #include <fcntl.h>
71 #include <errno.h>
72 #include <string.h>
73 #include <stdbool.h>
74 #include <sys/select.h>
75 #include <sys/ioctl.h>
76 #include <linux/ipmi.h>
77
78 static char *progname;
79 static char *devname = "/dev/ipmi0";
80 static int netfn = 2;
81 static int cmd = 3;
82
83 static void
usage(int exitcode)84 usage(int exitcode)
85 {
86 printf("Wait for incoming commands on an IPMI interface, print then,\n");
87 printf("and send a response back\n\n");
88 printf("%s [-d|--device <device file>] [-n|--netfn <netfn>]\n", progname);
89 printf(" [-c|--command] <command>\n\n");
90 printf(" -d|--device - Set the IPMI device to use."
91 " Default is /dev/ipmi0\n");
92 printf(" -n|--netfn - Set the netfn to listen for. Default is 2\n");
93 printf(" -c|--command - Set the command to listen for. Default is 3\n");
94
95 exit(exitcode);
96 }
97
98 static int
parse_num(const char * str,const char * optname)99 parse_num(const char *str, const char *optname)
100 {
101 char *end;
102 int num;
103
104 if (*str == '\0') {
105 fprintf(stderr, "Empty value given for %s, must be an integer\n",
106 optname);
107 exit(1);
108 }
109 num = strtoul(str, &end, 0);
110 if (*end != '\0') {
111 fprintf(stderr, "Invalid value given for %s, must be an integer\n",
112 optname);
113 exit(1);
114 }
115
116 return num;
117 }
118
119 static void
parse_args(int argc,char * argv[])120 parse_args(int argc, char *argv[])
121 {
122 int argn;
123
124 progname = argv[0];
125
126 for (argn = 1; argn < argc; argn++) {
127 int p = argn;
128
129 if (argv[p][0] != '-')
130 break;
131
132 if (strcmp(argv[p], "-h") == 0 || strcmp(argv[p], "--help") == 0)
133 usage(0);
134
135 /* All options here down take a value. */
136 argn++;
137 if (strcmp(argv[p], "-d") == 0 || strcmp(argv[p], "--device") == 0) {
138 if (argn >= argc)
139 goto no_parm;
140 devname = argv[argn];
141 continue;
142 }
143 if (strcmp(argv[p], "-n") == 0 || strcmp(argv[p], "--netfn") == 0) {
144 if (argn >= argc)
145 goto no_parm;
146 netfn = parse_num(argv[argn], argv[p]);
147 continue;
148 }
149 if (strcmp(argv[p], "-c") == 0 || strcmp(argv[p], "--command") == 0) {
150 if (argn >= argc)
151 goto no_parm;
152 cmd = parse_num(argv[argn], argv[p]);
153 continue;
154 }
155
156 fprintf(stderr, "Unknown option given: %s\n", argv[p]);
157 usage(1);
158 no_parm:
159 fprintf(stderr, "Option %s must have a value\n", argv[p]);
160 exit(1);
161 }
162
163 if (argn < argc) {
164 fprintf(stderr, "This program takes only options, no parameters\n");
165 exit(1);
166 }
167
168 if (netfn & 1) {
169 fprintf(stderr, "The netfn must be an even number\n");
170 exit(1);
171 }
172 }
173
174 int
main(int argc,char * argv[])175 main(int argc, char *argv[])
176 {
177 int fd, rv;
178 struct ipmi_cmdspec cmdspec;
179
180 parse_args(argc, argv);
181
182 fd = open(devname, O_RDWR);
183 if (fd == -1) {
184 fprintf(stderr, "Error opening %s: %s\n", devname, strerror(errno));
185 exit(1);
186 }
187
188 cmdspec.netfn = netfn;
189 cmdspec.cmd = cmd;
190 rv = ioctl(fd, IPMICTL_REGISTER_FOR_CMD, &cmdspec);
191 if (rv == -1) {
192 fprintf(stderr, "Error registering for command %2.2x:%2.2x: %s\n",
193 netfn, cmd, strerror(errno));
194 exit(1);
195 }
196
197 while (true) {
198 fd_set readfds;
199 struct ipmi_recv recv;
200 struct ipmi_addr addr;
201 struct ipmi_req resp;
202 unsigned char data[IPMI_MAX_MSG_LENGTH];
203 unsigned char rspdata[IPMI_MAX_MSG_LENGTH];
204 unsigned int i;
205
206 /* Wait for something. */
207 FD_ZERO(&readfds);
208 FD_SET(fd, &readfds);
209 rv = select(fd + 1, &readfds, NULL, NULL, NULL);
210 if (rv == -1) {
211 fprintf(stderr, "Error from select: %s\n", strerror(errno));
212 exit(1);
213 }
214
215 /* Receive the message. */
216 recv.addr = (unsigned char *) &addr;
217 recv.addr_len = sizeof(addr);
218 recv.msg.data = data;
219 recv.msg.data_len = sizeof(data);
220 rv = ioctl(fd, IPMICTL_RECEIVE_MSG, &recv);
221 if (rv == -1) {
222 fprintf(stderr, "Error receiving message: %s\n", strerror(errno));
223 continue;
224 }
225
226 if (recv.recv_type == IPMI_RESPONSE_RESPONSE_TYPE) {
227 /*
228 * This is a response to the response we sent. Kind of
229 * weird sounding, but this lets the driver report errors
230 * in sending the response.
231 */
232 if (recv.msg.data_len < 1)
233 fprintf(stderr,
234 "Response response didn't contain a return code\n");
235 else if (recv.msg.data[0] != 0)
236 fprintf(stderr,
237 "Response response had an error: %2.2x\n",
238 recv.msg.data[0]);
239 continue;
240 }
241
242 if (recv.recv_type != IPMI_CMD_RECV_TYPE) {
243 /*
244 * This should never happen, we haven't registered for events or
245 * sent any commands to get responses for.
246 */
247 fprintf(stderr, "Got invalid message type: %d\n", recv.recv_type);
248 continue;
249 }
250
251 /* Got a valid message. Print it. */
252 printf("Got command %2.2x:%2.2x, data:", recv.msg.netfn, recv.msg.cmd);
253 for (i = 0; i < recv.msg.data_len; i++) {
254 if ((i % 16) == 0)
255 printf("\n ");
256 printf(" %2.2x", recv.msg.data[i]);
257 }
258 printf("\n");
259
260 /* Send echo response back to the address we got it from. */
261 resp.addr = recv.addr;
262 resp.addr_len = recv.addr_len;
263 resp.msgid = recv.msgid;
264 resp.msg.netfn = recv.msg.netfn | 1; /* Set to a response. */
265 resp.msg.cmd = recv.msg.cmd;
266 /*
267 * All the strange finagling is is adding the error byte at
268 * the beginning of the response.
269 */
270 if (recv.msg.data_len > sizeof(rspdata) - 1)
271 recv.msg.data_len = sizeof(rspdata) - 1;
272 memcpy(rspdata + 1, recv.msg.data, recv.msg.data_len);
273 rspdata[0] = 0;
274 resp.msg.data = rspdata;
275 resp.msg.data_len = recv.msg.data_len + 1;
276 rv = ioctl(fd, IPMICTL_SEND_COMMAND, &resp);
277 if (rv == -1)
278 fprintf(stderr, "Error sending response: %s\n", strerror(errno));
279 }
280
281 exit(0);
282 }
283