/*
* ipmi_serial_bmc_emu.c
*
* An emulator for various IPMI BMCs that sit on a serial port
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2006 MontaVista Software Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA. */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
#include <readline/readline.h>
#include <readline/history.h>
#define _GNU_SOURCE
#include <getopt.h>
#define IPMB_MAX_MSG_LENGTH 32
#define IPMI_MAX_MSG_LENGTH 36
struct msg {
unsigned char data[IPMB_MAX_MSG_LENGTH];
unsigned int data_len;
struct msg *next;
};
struct msg_info;
struct codec {
char *name;
void (*handle_char)(unsigned char ch, struct msg_info *mi);
void (*send)(unsigned char *msg, unsigned int msg_len,
struct msg_info *mi);
void *(*setup)(void);
void (*handle_event)(struct msg *emsg, struct msg_info *mi);
void (*handle_ipmb)(struct msg *emsg, struct msg_info *mi);
};
struct oem_handler {
char *name;
int (*handler)(const unsigned char *msg, unsigned int len,
struct msg_info *mi,
unsigned char *rsp, unsigned int *rsp_len);
void (*init)(struct msg_info *mi);
};
#define EVENT_BUFFER_GLOBAL_ENABLE (1 << 2)
#define EVENT_LOG_GLOBAL_ENABLE (1 << 3)
#define SUPPORTED_GLOBAL_ENABLES (EVENT_BUFFER_GLOBAL_ENABLE | \
EVENT_LOG_GLOBAL_ENABLE)
struct msg_info {
/* General info, set by main code, not formatters. */
void *info;
int sock;
struct codec *codec;
struct oem_handler *oem;
/* Queues for events and IPMB messages. */
struct msg *ipmb_q, *ipmb_q_tail;
struct msg *event_q, *event_q_tail;
void *oem_info;
/* Settings */
int echo;
unsigned char my_ipmb;
int debug;
int do_attn;
unsigned char attn_chars[8];
unsigned int attn_chars_len;
char global_enables;
/* Info from the recv message. */
unsigned char netfn;
unsigned char dest;
unsigned char seq;
unsigned char rsAddr;
unsigned char rqAddr;
unsigned char rqLUN;
unsigned char rsLUN;
unsigned char cmd;
};
static void socket_send(const unsigned char *data, unsigned int len,
struct msg_info *mi);
static void handle_msg(const unsigned char *msg, unsigned int len,
struct msg_info *mi);
static void handle_ipmb_msg(const unsigned char *msg, unsigned int len,
struct msg_info *mi, struct msg_info *top_mi);
static unsigned char hex2char[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
static int fromhex(unsigned char c)
{
if (isdigit(c))
return c - '0';
else if (isxdigit(c))
return tolower(c) - 'a' + 10;
else
return -EINVAL;
}
static unsigned char
ipmb_checksum(const unsigned char *data, int size)
{
unsigned char csum = 0;
for (; size > 0; size--, data++)
csum += *data;
return -csum;
}
static int
unformat_ipmb_msg(const unsigned char *msg, unsigned int *pos,
unsigned int *len, struct msg_info *mi)
{
msg += *pos;
if (*len < 7) {
fprintf(stderr, "Message too short\n");
return -EINVAL;
}
if (ipmb_checksum(msg, *len) != 0) {
fprintf(stderr, "Message checksum failure\n");
return -EINVAL;
}
(*len)--;
mi->rsAddr = msg[0];
mi->netfn = msg[1] >> 2;
mi->rsLUN = msg[1] & 3;
mi->rqAddr = msg[3];
mi->seq = msg[4] >> 2;
mi->rqLUN = msg[4] & 3;
mi->cmd = msg[5];
*pos += 6;
*len -= 6;
return 0;
}
static unsigned int
format_ipmb_rsp(unsigned char *msg, const unsigned char *omsg,
unsigned int omsg_len, struct msg_info *mi)
{
unsigned int msg_len;
msg[0] = mi->rqAddr;
msg[1] = (mi->netfn << 2) | mi->rqLUN;
msg[2] = ipmb_checksum(msg, 2);
msg[3] = mi->rsAddr;
msg[4] = (mi->seq << 2) | mi->rsLUN;
msg[5] = mi->cmd;
memcpy(msg+6, omsg, omsg_len);
msg_len = omsg_len + 6;
msg[msg_len] = ipmb_checksum(msg + 3, msg_len - 3);
msg_len++;
return msg_len;
}
static void
queue_ipmb(struct msg *imsg, struct msg_info *mi)
{
imsg->next = NULL;
if (mi->ipmb_q_tail)
mi->ipmb_q_tail->next = imsg;
else
mi->ipmb_q = imsg;
mi->ipmb_q_tail = imsg;
if (mi->do_attn)
socket_send(mi->attn_chars, mi->attn_chars_len, mi);
}
static void
queue_event(struct msg *emsg, struct msg_info *mi)
{
emsg->next = NULL;
if (mi->event_q_tail)
mi->event_q_tail->next = emsg;
else
mi->event_q = emsg;
mi->event_q_tail = emsg;
if (mi->do_attn)
socket_send(mi->attn_chars, mi->attn_chars_len, mi);
}
/***********************************************************************
*
* Radisys ASCII codec.
*
***********************************************************************/
#define RA_MAX_CHARS_SIZE (((IPMI_MAX_MSG_LENGTH + 1) * 3) + 4)
struct ra_data {
unsigned char recv_chars[RA_MAX_CHARS_SIZE];
unsigned int recv_chars_len;
int recv_chars_too_many;
};
static void ra_format_msg(const unsigned char *msg, unsigned int msg_len,
struct msg_info *mi)
{
unsigned int i;
unsigned int len;
unsigned char c[RA_MAX_CHARS_SIZE];
len = 0;
for (i = 0; i < msg_len; i++) {
c[len] = hex2char[msg[i] >> 4];
len++;
c[len] = hex2char[msg[i] & 0xf];
len++;
}
c[len] = 0x0d;
len++;
socket_send(c, len, mi);
}
static void
ra_ipmb_handler(struct msg *imsg, struct msg_info *mi)
{
ra_format_msg(imsg->data, imsg->data_len, mi);
free(imsg);
}
/*
* Called when the '0x0d' is seen.
*/
static int ra_unformat_msg(unsigned char *r, unsigned int len,
struct msg_info *mi)
{
unsigned char o[IPMI_MAX_MSG_LENGTH];
unsigned int p = 0;
unsigned int i = 0;
int rv;
while (p < len) {
rv = fromhex(r[p]);
if (rv < 0)
return rv;
o[i] = rv << 4;
p++;
if (p >= len)
return -EINVAL;
rv = fromhex(r[p]);
if (rv < 0)
return rv;
o[i] |= rv;
p++;
i++;
}
p = 0;
rv = unformat_ipmb_msg(o, &p, &i, mi);
if (rv)
return rv;
if ((mi->rsAddr == mi->my_ipmb) || (mi->rsAddr == 1))
handle_msg(o + p, i, mi);
else
handle_ipmb_msg(o + p, i, mi, mi);
return 0;
}
static void
ra_handle_char(unsigned char ch, struct msg_info *mi)
{
struct ra_data *info = mi->info;
unsigned int len = info->recv_chars_len;
unsigned char *r;
int rv;
if (ch == 0x0d) {
/* End of command, handle it. */
if (info->recv_chars_too_many) {
/* Input data overrun. */
fprintf(stderr, "Data overrun\n");
info->recv_chars_too_many = 0;
info->recv_chars_len = 0;
return;
}
rv = ra_unformat_msg(info->recv_chars, info->recv_chars_len, mi);
info->recv_chars_too_many = 0;
info->recv_chars_len = 0;
if (rv) {
/* Bad input data. */
fprintf(stderr, "Bad input data\n");
return;
}
return;
}
if (info->recv_chars_too_many)
return;
r = info->recv_chars;
if (len >= sizeof(info->recv_chars)) {
info->recv_chars_too_many = 1;
} else if ((len > 0) && isspace(r[len-1]) && isspace(ch)) {
/* Ignore multiple spaces together. */
} else {
r[len] = ch;
info->recv_chars_len++;
}
}
static void
ra_send(unsigned char *omsg, unsigned int omsg_len, struct msg_info *mi)
{
unsigned char msg[IPMI_MAX_MSG_LENGTH + 7];
unsigned int msg_len;
msg_len = format_ipmb_rsp(msg, omsg, omsg_len, mi);
ra_format_msg(msg, msg_len, mi);
}
static void *
ra_setup(void)
{
struct ra_data *info;
info = malloc(sizeof(*info));
if (!info)
return NULL;
info->recv_chars_len = 0;
info->recv_chars_too_many = 0;
return info;
}
/***********************************************************************
*
* Direct Mode codec.
*
***********************************************************************/
#define DM_START_CHAR 0xA0
#define DM_STOP_CHAR 0xA5
#define DM_PACKET_HANDSHAKE 0xA6
#define DM_DATA_ESCAPE_CHAR 0xAA
struct dm_data {
unsigned char recv_msg[IPMI_MAX_MSG_LENGTH + 4];
unsigned int recv_msg_len;
int recv_msg_too_many;
int in_recv_msg;
int in_escape;
};
static void
dm_handle_msg(unsigned char *msg, unsigned int len, struct msg_info *mi)
{
int rv;
unsigned int pos;
pos = 0;
rv = unformat_ipmb_msg(msg, &pos, &len, mi);
if (rv)
return;
handle_msg(msg + pos, len, mi);
}
static void
dm_handle_char(unsigned char ch, struct msg_info *mi)
{
struct dm_data *info = mi->info;
unsigned int len = info->recv_msg_len;
unsigned char c;
switch (ch) {
case DM_START_CHAR:
if (info->in_recv_msg)
fprintf(stderr, "Msg started in the middle of another\n");
info->in_recv_msg = 1;
info->recv_msg_len = 0;
info->recv_msg_too_many = 0;
info->in_escape = 0;
break;
case DM_STOP_CHAR:
if (!info->in_recv_msg)
fprintf(stderr, "Empty message\n");
else if (info->in_escape) {
info->in_recv_msg = 0;
fprintf(stderr, "Message ended in escape\n");
} else if (info->recv_msg_too_many) {
fprintf(stderr, "Message too long\n");
info->in_recv_msg = 0;
} else {
dm_handle_msg(info->recv_msg, info->recv_msg_len, mi);
info->in_recv_msg = 0;
}
info->in_escape = 0;
c = DM_PACKET_HANDSHAKE;
socket_send(&c, 1, mi);
break;
case DM_PACKET_HANDSHAKE:
info->in_escape = 0;
break;
case DM_DATA_ESCAPE_CHAR:
if (!info->recv_msg_too_many)
info->in_escape = 1;
break;
default:
if (!info->in_recv_msg)
/* Ignore characters outside of messages. */
break;
if (info->in_escape) {
info->in_escape = 0;
switch (ch) {
case 0xB0: ch = DM_START_CHAR; break;
case 0xB5: ch = DM_STOP_CHAR; break;
case 0xB6: ch = DM_PACKET_HANDSHAKE; break;
case 0xBA: ch = DM_DATA_ESCAPE_CHAR; break;
case 0x3B: ch = 0x1b; break;
default:
fprintf(stderr, "Invalid escape char: 0x%x\n", ch);
info->recv_msg_too_many = 1;
return;
}
}
if (!info->recv_msg_too_many) {
if (len >= sizeof(info->recv_msg)) {
info->recv_msg_too_many = 1;
break;
}
info->recv_msg[len] = ch;
info->recv_msg_len++;
}
break;
}
}
static void
dm_send(unsigned char *omsg, unsigned int omsg_len, struct msg_info *mi)
{
unsigned int i;
unsigned int len = 0;
unsigned char c[(IPMI_MAX_MSG_LENGTH + 7) * 2];
unsigned char msg[IPMI_MAX_MSG_LENGTH + 7];
unsigned int msg_len;
msg_len = format_ipmb_rsp(msg, omsg, omsg_len, mi);
c[len++] = 0xA0;
for (i = 0; i < msg_len; i++) {
switch (msg[i]) {
case 0xA0:
c[len++] = 0xAA;
c[len++] = 0xB0;
break;
case 0xA5:
c[len++] = 0xAA;
c[len++] = 0xB5;
break;
case 0xA6:
c[len++] = 0xAA;
c[len++] = 0xB6;
break;
case 0xAA:
c[len++] = 0xAA;
c[len++] = 0xBA;
break;
case 0x1B:
c[len++] = 0xAA;
c[len++] = 0x3B;
break;
default:
c[len++] = msg[i];
}
}
c[len++] = 0xA5;
socket_send(c, len, mi);
}
static void *
dm_setup(void)
{
struct dm_data *info;
info = malloc(sizeof(*info));
if (!info)
return NULL;
memset(info, 0, sizeof(*info));
return info;
}
/***********************************************************************
*
* Terminal Mode codec.
*
***********************************************************************/
#define TM_MAX_CHARS_SIZE (((IPMI_MAX_MSG_LENGTH + 1) * 3) + 4)
struct tm_data {
unsigned char recv_chars[TM_MAX_CHARS_SIZE];
unsigned int recv_chars_len;
int recv_chars_too_many;
};
static void tm_format_msg(const unsigned char *msg, unsigned int msg_len,
struct msg_info *mi)
{
unsigned int i;
unsigned int len;
unsigned char c[TM_MAX_CHARS_SIZE];
unsigned char t;
len = 0;
c[len] = '[';
len++;
t = mi->netfn << 2 | mi->rqLUN;
c[len] = hex2char[t >> 4];
len++;
c[len] = hex2char[t & 0xf];
len++;
/*
* Insert the sequence number and bridge bits. Bridge bits
* are always zero.
*/
t = mi->seq << 2;
c[len] = hex2char[t >> 4];
len++;
c[len] = hex2char[t & 0xf];
len++;
c[len] = hex2char[mi->cmd >> 4];
len++;
c[len] = hex2char[mi->cmd & 0xf];
len++;
/* Now the rest of the message. */
for (i = 0; ; ) {
c[len] = hex2char[msg[i] >> 4];
len++;
c[len] = hex2char[msg[i] & 0xf];
len++;
i++;
if (i == msg_len)
break;
c[len] = ' ';
len++;
}
c[len] = ']';
len++;
c[len] = 0x0a;
len++;
socket_send(c, len, mi);
}
/*
* Called when the ']' is seen, the leading '[' is removed, too. We
* get this with a leading space and no more than one space between
* items.
*/
static int tm_unformat_msg(unsigned char *r, unsigned int len,
struct msg_info *mi)
{
unsigned char o[IPMI_MAX_MSG_LENGTH];
unsigned int p = 0;
unsigned int i = 0;
int rv;
#define SKIP_SPACE if (isspace(r[p])) p++
#define ENSURE_MORE if (p >= len) return -EINVAL
SKIP_SPACE;
while (p < len) {
if (i >= sizeof(o))
return -EFBIG;
ENSURE_MORE;
rv = fromhex(r[p]);
if (rv < 0)
return rv;
o[i] = rv << 4;
p++;
ENSURE_MORE;
rv = fromhex(r[p]);
if (rv < 0)
return rv;
o[i] |= rv;
p++;
i++;
SKIP_SPACE;
}
if (i < 3)
return -EINVAL;
mi->netfn = o[0] >> 2;
mi->rqLUN = o[0] & 3;
mi->seq = o[1] >> 2;
mi->cmd = o[2];
handle_msg(o+3, i-3, mi);
return 0;
#undef SKIP_SPACE
#undef ENSURE_MORE
}
static void
tm_handle_char(unsigned char ch, struct msg_info *mi)
{
struct tm_data *info = mi->info;
unsigned int len = info->recv_chars_len;
unsigned char *r;
int rv;
if (ch == '[') {
/*
* Start of a command. Note that if a command is
* already in progress (len != 0) we abort it.
*/
if (len != 0)
fprintf(stderr, "Msg started in the middle of another\n");
/* Convert the leading '[' to a space, that's innocuous. */
info->recv_chars[0] = ' ';
info->recv_chars_len = 1;
info->recv_chars_too_many = 0;
return;
}
if (len == 0)
/* Ignore everything outside [ ]. */
return;
if (ch == ']') {
/* End of command, handle it. */
if (info->recv_chars_too_many) {
/* Input data overrun. */
fprintf(stderr, "Data overrun\n");
info->recv_chars_too_many = 0;
info->recv_chars_len = 0;
return;
}
rv = tm_unformat_msg(info->recv_chars, info->recv_chars_len, mi);
info->recv_chars_too_many = 0;
info->recv_chars_len = 0;
if (rv) {
/* Bad input data. */
fprintf(stderr, "Bad input data\n");
return;
}
return;
}
if (info->recv_chars_too_many)
return;
r = info->recv_chars;
if (len >= sizeof(info->recv_chars)) {
info->recv_chars_too_many = 1;
} else if ((len > 0) && isspace(r[len-1]) && isspace(ch)) {
/* Ignore multiple spaces together. */
} else {
r[len] = ch;
info->recv_chars_len++;
}
}
static void
tm_send(unsigned char *msg, unsigned int msg_len, struct msg_info *mi)
{
tm_format_msg(msg, msg_len, mi);
}
static void *
tm_setup(void)
{
struct tm_data *info;
info = malloc(sizeof(*info));
if (!info)
return NULL;
info->recv_chars_len = 0;
info->recv_chars_too_many = 0;
return info;
}
/***********************************************************************
*
* codec structure
*
***********************************************************************/
struct codec codecs[] = {
{ "TerminalMode",
tm_handle_char, tm_send, tm_setup, queue_event, queue_ipmb },
{ "Direct",
dm_handle_char, dm_send, dm_setup, queue_event, queue_ipmb },
{ "RadisysAscii",
ra_handle_char, ra_send, ra_setup, NULL, ra_ipmb_handler },
{ NULL }
};
static void
socket_send(const unsigned char *data, unsigned int len, struct msg_info *mi)
{
int rv;
unsigned int i;
if (mi->debug > 0) {
printf("Sock send:");
for (i=0; i<len; i++) {
if ((i % 16) == 0)
printf("\n ");
printf(" %2.2x(%c)", data[i], isprint(data[i]) ? data[i] : ' ');
}
printf("\n");
}
restart:
rv = write(mi->sock, data, len);
if (rv < 0) {
perror("write");
return;
} else if (((unsigned int) rv) < len) {
len -= rv;
data += rv;
goto restart;
}
}
#define IPMI_APP_NETFN 6
#define IPMI_GET_DEV_ID_CMD 0x01
#define IPMI_GET_DEVICE_GUID_CMD 0x08
#define IPMI_SET_BMC_GLOBAL_ENABLES_CMD 0x2e
#define IPMI_GET_BMC_GLOBAL_ENABLES_CMD 0x2f
#define IPMI_GET_MSG_FLAGS_CMD 0x31
#define IPMI_GET_MSG_CMD 0x33
#define IPMI_SEND_MSG_CMD 0x34
#define IPMI_READ_EVENT_MSG_CMD 0x35
#define IPMI_OEM_NETFN 0x2e
static unsigned char ipmb_devid_data[] = {
0x00, 0x01, 0x00, 0x48, 0x02, 0x9f, 0xaa, 0x01, 0x00, 0x23, 0x00,
0x00, 0x11, 0x00, 0x04
};
static void
handle_ipmb_msg(const unsigned char *msg, unsigned int len,
struct msg_info *mi, struct msg_info *top_mi)
{
struct msg *imsg;
unsigned char rsp[IPMI_MAX_MSG_LENGTH];
unsigned int rsp_len;
unsigned int i;
imsg = malloc(sizeof(*imsg));
if (!imsg)
return;
if (top_mi->debug > 0) {
printf("Recv IPMB Msg (%x:%x):", mi->netfn, mi->cmd);
for (i=0; i<len; i++) {
if ((i % 16) == 0)
printf("\n ");
printf(" %2.2x(%c)", msg[i], isprint(msg[i]) ? msg[i] : ' ');
}
printf("\n");
}
if (mi->netfn == IPMI_APP_NETFN) {
switch (mi->cmd) {
case IPMI_GET_DEV_ID_CMD:
rsp[0] = 0;
memcpy(rsp+1, ipmb_devid_data, sizeof(ipmb_devid_data));
rsp_len = sizeof(ipmb_devid_data) + 1;
break;
default:
goto invalid_msg;
}
} else
goto invalid_msg;
send_rsp:
/* Convert to response. */
mi->netfn |= 1;
imsg->data_len = format_ipmb_rsp(imsg->data, rsp, rsp_len, mi);
top_mi->codec->handle_ipmb(imsg, top_mi);
return;
invalid_msg:
rsp[0] = 0xc1;
rsp_len = 1;
goto send_rsp;
}
static unsigned char devid_data[] = {
0x20, 0x01, 0x00, 0x48, 0x02, 0x9f, 0x22, 0x03, 0x00, 0x11, 0x43,
0x00, 0x11, 0x00, 0x04
};
static unsigned char guid_data[] = {
0x00, 0x01, 0x00, 0x48, 0x02, 0x9f, 0xaa, 0x01,
0x00, 0x23, 0x00, 0x00, 0x11, 0x00, 0x04, 0x99
};
static void
handle_msg(const unsigned char *msg, unsigned int len, struct msg_info *mi)
{
unsigned int i;
unsigned char rsp[IPMI_MAX_MSG_LENGTH];
unsigned int rsp_len;
struct msg *m;
int rv;
struct msg_info nmi;
unsigned int p;
if (mi->debug > 0) {
printf("Recv Msg (%x:%x):", mi->netfn, mi->cmd);
for (i=0; i<len; i++) {
if ((i % 16) == 0)
printf("\n ");
printf(" %2.2x(%c)", msg[i], isprint(msg[i]) ? msg[i] : ' ');
}
printf("\n");
}
if (mi->oem) {
rv = mi->oem->handler(msg, len, mi, rsp, &rsp_len);
if (!rv)
goto send_rsp;
}
if (mi->netfn == IPMI_APP_NETFN) {
switch (mi->cmd) {
case IPMI_GET_DEV_ID_CMD:
rsp[0] = 0;
memcpy(rsp+1, devid_data, sizeof(devid_data));
rsp_len = sizeof(devid_data) + 1;
break;
case IPMI_GET_DEVICE_GUID_CMD:
rsp[0] = 0;
memcpy(rsp+1, guid_data, sizeof(guid_data));
rsp_len = sizeof(guid_data) + 1;
break;
case IPMI_GET_MSG_FLAGS_CMD:
rsp[0] = 0;
rsp[1] = 0;
if (mi->event_q)
rsp[1] |= 2;
if (mi->ipmb_q)
rsp[1] |= 1;
rsp_len = 2;
break;
case IPMI_GET_MSG_CMD:
if (!mi->ipmb_q) {
rsp[0] = 0x80;
rsp_len = 1;
break;
}
m = mi->ipmb_q;
mi->ipmb_q = m->next;
if (!mi->ipmb_q)
mi->ipmb_q_tail = NULL;
rsp[0] = 0;
rsp[1] = 0; /* Channel # */
/* Note we don't put our slave address in the response, as
that is what get smg expects. */
memcpy(rsp + 2, m->data + 1, m->data_len - 1);
rsp_len = 2 + m->data_len - 1;
free(m);
break;
case IPMI_SEND_MSG_CMD:
if (msg[0] != 0) {
rsp[0] = 0xcc;
rsp_len = 1;
break;
}
p = 1;
len -= 1;
rv = unformat_ipmb_msg(msg, &p, &len, &nmi);
if (rv) {
rsp[0] = 0xcc;
rsp_len = 1;
break;
}
if (nmi.netfn & 1) {
/* Ignore responses */
rsp[0] = 0;
rsp_len = 1;
break;
}
handle_ipmb_msg(msg+p, len, &nmi, mi);
rsp[0] = 0;
rsp_len = 1;
break;
case IPMI_SET_BMC_GLOBAL_ENABLES_CMD:
if (len < 1) {
rsp[0] = 0xcc;
rsp_len = 1;
break;
}
if ((msg[0] & ~SUPPORTED_GLOBAL_ENABLES) != 0) {
rsp[0] = 0xcc;
rsp_len = 1;
break;
}
mi->global_enables = msg[0];
rsp[0] = 0;
rsp_len = 1;
break;
case IPMI_GET_BMC_GLOBAL_ENABLES_CMD:
rsp[0] = 0;
rsp[1] = mi->global_enables;
rsp_len = 2;
break;
case IPMI_READ_EVENT_MSG_CMD:
if (!mi->event_q) {
rsp[0] = 0x80;
rsp_len = 1;
break;
}
m = mi->event_q;
mi->event_q = m->next;
if (!mi->event_q)
mi->event_q_tail = NULL;
rsp[0] = 0;
memcpy(rsp + 1, m->data, m->data_len);
rsp_len = 1 + m->data_len;
free(m);
break;
default:
goto invalid_msg;
}
} else
goto invalid_msg;
send_rsp:
/* Convert to response. */
mi->netfn |= 1;
mi->codec->send(rsp, rsp_len, mi);
return;
invalid_msg:
rsp[0] = 0xc1;
rsp_len = 1;
goto send_rsp;
}
#define PP_GET_SERIAL_INTF_CMD 0x01
#define PP_SET_SERIAL_INTF_CMD 0x02
static unsigned char pp_oem_chars[] = { 0x00, 0x40, 0x0a };
static int
pp_oem_handler(const unsigned char *msg, unsigned int len,
struct msg_info *mi,
unsigned char *rsp, unsigned int *rsp_len)
{
if ((len < 3) || (memcmp(msg, pp_oem_chars, 3) != 0))
return -ENOSYS;
msg += 3;
len -= 3;
if (mi->netfn == IPMI_OEM_NETFN) {
switch (mi->cmd) {
case PP_GET_SERIAL_INTF_CMD:
rsp[0] = 0;
memcpy(rsp+1, pp_oem_chars, 3);
rsp[4] = 0;
if (msg[0] == 1)
rsp[4] |= mi->echo;
*rsp_len = 5;
break;
case PP_SET_SERIAL_INTF_CMD:
if (len < 2)
rsp[0] = 0xcc;
else if (msg[0] == 1) {
mi->echo = msg[1] & 1;
rsp[0] = 0;
}
memcpy(rsp+1, pp_oem_chars, 3);
*rsp_len = 4;
break;
default:
return -ENOSYS;
}
} else
return -ENOSYS;
return 0;
}
static void
pp_oem_init(struct msg_info *mi)
{
mi->echo = 1;
}
#define RA_CONTROLLER_OEM_NETFN 0x3e
#define RA_GET_IPMB_ADDR_CMD 0x12
static int
ra_oem_handler(const unsigned char *msg, unsigned int len,
struct msg_info *mi,
unsigned char *rsp, unsigned int *rsp_len)
{
if (mi->netfn == RA_CONTROLLER_OEM_NETFN) {
switch (mi->cmd) {
case RA_GET_IPMB_ADDR_CMD:
rsp[0] = 0;
rsp[1] = mi->my_ipmb;
*rsp_len = 2;
break;
default:
return -ENOSYS;
}
} else if (mi->netfn == IPMI_APP_NETFN) {
switch (mi->cmd) {
case IPMI_GET_MSG_FLAGS_CMD:
/* No message flag support. */
rsp[0] = 0xc1;
*rsp_len = 1;
break;
default:
return -ENOSYS;
}
} else
return -ENOSYS;
return 0;
}
static void
ra_oem_init(struct msg_info *mi)
{
}
static struct oem_handler oem_handlers[] = {
{ "PigeonPoint", pp_oem_handler, pp_oem_init },
{ "Radisys", ra_oem_handler, ra_oem_init },
{ NULL }
};
static char *
next_tok(char **str)
{
char *rv;
char *s = *str;
while (isspace(*s))
s++;
rv = s;
while (*s && (!isspace(*s)))
s++;
if (*s) {
*s = '\0';
s++;
}
*str = s;
if (*rv)
return rv;
else
return NULL;
}
static void
exit_handler(char *line, struct msg_info *mi)
{
close(mi->sock);
exit(0);
}
static void
inc_debug_handler(char *line, struct msg_info *mi)
{
mi->debug++;
}
static void
dec_debug_handler(char *line, struct msg_info *mi)
{
if (mi->debug > 0)
mi->debug--;
}
static void
event_handler(char *line, struct msg_info *mi)
{
struct msg *emsg;
int i, p;
unsigned char *m;
if (!mi->codec->handle_event) {
printf("This codec does not support event messages\n");
return;
}
emsg = malloc(sizeof(*emsg));
if (!emsg) {
printf("Could not allocate event message\n");
return;
}
p = 0;
m = emsg->data;
for (i=0; i<16; i++) {
char *s, *e;
s = next_tok(&line);
if (!s) {
printf("Events need 16 bytes of data\n");
free(emsg);
return;
}
m[p++] = strtoul(s, &e, 16);
if (*e != '\0') {
printf("Byte %d was invalid\n", i+1);
free(emsg);
return;
}
}
emsg->data_len = 16;
mi->codec->handle_event(emsg, mi);
}
static void help_handler(char *line, struct msg_info *mi);
static const char help_help[] = "This command.";
static const char exit_help[] = "Quit the program.";
static const char quit_help[] = "Quit the program.";
static const char event_help[] =
"Put an event into the event queue. Takes 16 bytes of data like:\n"
" event 10 20 30 40 50 60 70 80 90 a0 b0 c0 d0 e0 f0 f1";
static const char inc_debug_help[] = "Increment the debugging flag.";
static const char dec_debug_help[] = "Decrement the debugging flag.";
static struct {
const char *cmd;
void (*handler)(char *line, struct msg_info *mi);
const char *help;
} cmds[] = {
{ "help", help_handler, help_help },
{ "exit", exit_handler, exit_help },
{ "quit", exit_handler, quit_help },
{ "event", event_handler, event_help },
{ "debug+", inc_debug_handler, inc_debug_help },
{ "debug-", dec_debug_handler, dec_debug_help },
{ NULL }
};
static void
help_handler(char *line, struct msg_info *mi)
{
int i;
printf("Valid commands:");
for (i=0; cmds[i].cmd; i++)
printf(" %s - %s\n", cmds[i].cmd, cmds[i].help);
}
static struct msg_info main_mi;
static void
command_string_handler(char *cmdline)
{
char *expansion = NULL;
int result;
int i;
char *s, *cmd;
if (cmdline == NULL) {
printf("\n");
exit_handler(NULL, &main_mi);
}
result = history_expand(cmdline, &expansion);
if (result < 0 || result == 2) {
fprintf(stderr, "%s\n", expansion);
} else if (expansion && strlen(expansion)){
add_history(expansion);
s = expansion;
cmd = next_tok(&s);
if (cmd) {
/* Extract the command. */
for (i=0; cmds[i].cmd != NULL; i++) {
if (strcmp(cmd, cmds[i].cmd) == 0)
break;
}
if (cmds[i].cmd) {
cmds[i].handler(s, &main_mi);
} else {
printf("Unknown command: '%s'\n", cmd);
}
}
}
if (expansion)
free(expansion);
}
struct option options[] = {
{ "codec", 1, NULL, 'c' },
{ "ipmb_addr", 1, NULL, 'a' },
{ "oem_setup", 1, NULL, 'o' },
{ "debug", 0, NULL, 'd' },
{ "attn", 2, NULL, 't' },
{ 0 }
};
static char *usage_str =
"%s [options] <server> <port>\n"
" Emulate various IPMI serial port BMCs, primarily for testing the\n"
" IPMI driver.\n"
" Options are:\n"
" -c <codec>, --codec <codec> - Set the codec to use. Valid codecs\n"
" are:\n"
" TerminalMode - Standard terminal mode\n"
" Direct - standard serial direct mode\n"
" RadisysAscii - Radisys defined ASCII\n"
" -a <addr>, --ipmb_addr <addr> - Set the IPMB address for the emulated\n"
" BMC.\n"
" -o <oem>, --oem_setup <oem> - Emulate certain OEM commands:\n"
" PigeonPoint - Emulate echo handling per the PigeonPoint IPMCs,\n"
" primarily for terminal mode.\n"
" Radisys - Emulate the Radisys method for fetching the IPMB address.\n"
" --attn[=<char>[,<char>[,...]]] - Set the attention characters to\n"
" the given value. This is sent whenever something is added to the\n"
" event or receive message queue. It defaults to one BELL character,\n"
" which is 0x07. The specified values are numbers, like 0x07.\n"
" For direct mode using the ASCII escape, this would be 0x1b. For\n"
" direct mode on the Sun CPxxxx, this would be 0xAA,0x47.\n"
" -d, --debug - Increment the debug setting\n"
" This program connects to a remote TCP port, so you need to have a\n"
" terminal server (in raw mode, not telnet mode) to use this program.\n"
" I use ser2net, get that if you need it.\n";
char *cmdname;
static void
usage(void)
{
printf(usage_str, cmdname);
exit(1);
}
int
main(int argc, char *argv[])
{
unsigned int i;
struct addrinfo hints, *res0;
struct sockaddr_storage saddr;
struct sockaddr *addr = (struct sockaddr *) &saddr;
size_t addrlen;
struct msg_info *mi = &main_mi;
int rv;
char *s, *e;
cmdname = argv[0];
memset(mi, 0, sizeof(*mi));
mi->my_ipmb = 0x20;
mi->codec = &(codecs[0]);
for (;;) {
int f;
f = getopt_long(argc, argv, "c:o:a:d", options, NULL);
if (f == -1)
break;
switch (f) {
case 'c':
for (i=0; codecs[i].name; i++) {
if (strcmp(codecs[i].name, optarg) == 0)
break;
}
if (codecs[i].name)
mi->codec = &(codecs[i]);
else {
fprintf(stderr, "Invalid codec: %s\n", optarg);
usage();
}
break;
case 'd':
mi->debug++;
break;
case 'a':
mi->my_ipmb = strtoul(optarg, NULL, 0);
break;
case 't':
mi->do_attn = 1;
if (optarg) {
s = optarg;
for (i=0; ; i++) {
if (i >= sizeof(mi->attn_chars)) {
fprintf(stderr, "Too many attention characters\n");
usage();
}
mi->attn_chars[i] = strtoul(s, &e, 0);
mi->attn_chars_len++;
if (*e == '\0')
break;
else if (*e == ',')
s = e + 1;
else {
fprintf(stderr, "Invalid attention characters\n");
usage();
}
}
} else {
mi->attn_chars[0] = 0x07;
mi->attn_chars_len = 1;
}
break;
case 'o':
for (i=0; oem_handlers[i].name != NULL; i++) {
if (strcmp(optarg, oem_handlers[i].name) == 0)
break;
}
if (oem_handlers[i].name) {
mi->oem = &(oem_handlers[i]);
mi->oem->init(mi);
} else {
fprintf(stderr, "Invalid OEM handler '%s'\n", optarg);
usage();
}
break;
default:
fprintf(stderr, "Invalid flag: '%c'\n", optopt);
usage();
}
}
i = optind;
if (i+2 < ((unsigned int) argc)) {
fprintf(stderr, "Host and/or port not supplied\n");
usage();
}
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
rv = getaddrinfo(argv[i], argv[i+1], &hints, &res0);
if (rv) {
perror("getaddrinfo");
usage();
}
/* Only get the first choices */
memcpy(addr, res0->ai_addr, res0->ai_addrlen);
addrlen = res0->ai_addrlen;
freeaddrinfo(res0);
mi->sock = socket(addr->sa_family, SOCK_STREAM, 0);
if (mi->sock < 0) {
perror("socket");
usage();
}
rv = connect(mi->sock, (struct sockaddr *) addr, addrlen);
if (rv < 0) {
perror("connect");
usage();
}
i = 1;
if (setsockopt(mi->sock, IPPROTO_TCP, TCP_NODELAY,
(char *) &i, sizeof(i)) == -1) {
perror("setsockopt TCP_NODELAY");
usage();
}
mi->info = mi->codec->setup();
if (!mi->info) {
fprintf(stderr, "Out of memory\n");
usage();
}
printf("Starting IPMI serial BMC emulator with:\n %s codec"
"\n %s OEM emulation\n",
mi->codec->name, mi->oem ? mi->oem->name : "no");
if (mi->do_attn) {
printf(" attention chars:");
for (i=0; i<mi->attn_chars_len; i++)
printf(" %2.2x", mi->attn_chars[i]);
printf("\n");
}
stifle_history(500);
rl_callback_handler_install("> ", command_string_handler);
for (;;) {
unsigned char buf[128];
int i;
fd_set readfds;
int rv2;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(mi->sock, &readfds);
rv = select(mi->sock+1, &readfds, NULL, NULL, NULL);
if (rv < 0) {
if (errno != EINTR) {
perror("select");
usage();
}
continue;
}
if (FD_ISSET(mi->sock, &readfds)) {
rv = read(mi->sock, buf, sizeof(buf));
if (rv < 0) {
perror("read");
usage();
}
if (mi->debug > 1) {
printf("recv:");
for (i=0; i<rv; i++) {
if ((i % 16) == 0)
printf("\n ");
printf(" %2.2x(%c)", buf[i],
isprint(buf[i]) ? buf[i] : ' ');
}
printf("\n");
}
for (i=0; i<rv; i++) {
/*
* Echo one at a time in case the echo gets turned off
* in the middle of this data.
*/
if (mi->echo) {
rv2 = write(mi->sock, buf+i, 1);
if (rv2 < 0) {
perror("write");
usage();
}
}
mi->codec->handle_char(buf[i], mi);
}
}
if (FD_ISSET(0, &readfds))
rl_callback_read_char();
}
}