Skip to content

Commit

Permalink
ipmi: Add support for IPMB direct messages
Browse files Browse the repository at this point in the history
An application has come up that has a device sitting right on the IPMB
that would like to communicate with the BMC on the IPMB using normal
IPMI commands.

Sending these commands and handling the responses is easy enough, no
modifications are needed to the IPMI infrastructure.  But if this is an
application that also needs to receive IPMB commands and respond, some
way is needed to handle these incoming commands and send the responses.

Currently, the IPMI message handler only sends commands to the interface
and only receives responses from interface.  This change extends the
interface to receive commands/responses and send commands/responses.
These are formatted differently in support of receiving/sending IPMB
messages directly.

Signed-off-by: Corey Minyard <minyard@acm.org>
Tested-by: Andrew Manley <andrew.manley@sealingtech.com>
Reviewed-by: Andrew Manley <andrew.manley@sealingtech.com>
  • Loading branch information
cminyard committed Oct 5, 2021
1 parent 1e4071f commit 059747c
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 33 deletions.
288 changes: 255 additions & 33 deletions drivers/char/ipmi/ipmi_msghandler.c
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,11 @@ static int is_ipmb_bcast_addr(struct ipmi_addr *addr)
return addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE;
}

static int is_ipmb_direct_addr(struct ipmi_addr *addr)
{
return addr->addr_type == IPMI_IPMB_DIRECT_ADDR_TYPE;
}

static void free_recv_msg_list(struct list_head *q)
{
struct ipmi_recv_msg *msg, *msg2;
Expand Down Expand Up @@ -805,6 +810,17 @@ ipmi_addr_equal(struct ipmi_addr *addr1, struct ipmi_addr *addr2)
&& (ipmb_addr1->lun == ipmb_addr2->lun));
}

if (is_ipmb_direct_addr(addr1)) {
struct ipmi_ipmb_direct_addr *daddr1
= (struct ipmi_ipmb_direct_addr *) addr1;
struct ipmi_ipmb_direct_addr *daddr2
= (struct ipmi_ipmb_direct_addr *) addr2;

return daddr1->slave_addr == daddr2->slave_addr &&
daddr1->rq_lun == daddr2->rq_lun &&
daddr1->rs_lun == daddr2->rs_lun;
}

if (is_lan_addr(addr1)) {
struct ipmi_lan_addr *lan_addr1
= (struct ipmi_lan_addr *) addr1;
Expand Down Expand Up @@ -843,6 +859,23 @@ int ipmi_validate_addr(struct ipmi_addr *addr, int len)
return 0;
}

if (is_ipmb_direct_addr(addr)) {
struct ipmi_ipmb_direct_addr *daddr = (void *) addr;

if (addr->channel != 0)
return -EINVAL;
if (len < sizeof(struct ipmi_ipmb_direct_addr))
return -EINVAL;

if (daddr->slave_addr & 0x01)
return -EINVAL;
if (daddr->rq_lun >= 4)
return -EINVAL;
if (daddr->rs_lun >= 4)
return -EINVAL;
return 0;
}

if (is_lan_addr(addr)) {
if (len < sizeof(struct ipmi_lan_addr))
return -EINVAL;
Expand All @@ -862,6 +895,9 @@ unsigned int ipmi_addr_length(int addr_type)
|| (addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
return sizeof(struct ipmi_ipmb_addr);

if (addr_type == IPMI_IPMB_DIRECT_ADDR_TYPE)
return sizeof(struct ipmi_ipmb_direct_addr);

if (addr_type == IPMI_LAN_ADDR_TYPE)
return sizeof(struct ipmi_lan_addr);

Expand Down Expand Up @@ -2052,6 +2088,58 @@ static int i_ipmi_req_ipmb(struct ipmi_smi *intf,
return rv;
}

static int i_ipmi_req_ipmb_direct(struct ipmi_smi *intf,
struct ipmi_addr *addr,
long msgid,
struct kernel_ipmi_msg *msg,
struct ipmi_smi_msg *smi_msg,
struct ipmi_recv_msg *recv_msg,
unsigned char source_lun)
{
struct ipmi_ipmb_direct_addr *daddr;
bool is_cmd = !(recv_msg->msg.netfn & 0x1);

if (!(intf->handlers->flags & IPMI_SMI_CAN_HANDLE_IPMB_DIRECT))
return -EAFNOSUPPORT;

/* Responses must have a completion code. */
if (!is_cmd && msg->data_len < 1) {
ipmi_inc_stat(intf, sent_invalid_commands);
return -EINVAL;
}

if ((msg->data_len + 4) > IPMI_MAX_MSG_LENGTH) {
ipmi_inc_stat(intf, sent_invalid_commands);
return -EMSGSIZE;
}

daddr = (struct ipmi_ipmb_direct_addr *) addr;
if (daddr->rq_lun > 3 || daddr->rs_lun > 3) {
ipmi_inc_stat(intf, sent_invalid_commands);
return -EINVAL;
}

smi_msg->type = IPMI_SMI_MSG_TYPE_IPMB_DIRECT;
smi_msg->msgid = msgid;

if (is_cmd) {
smi_msg->data[0] = msg->netfn << 2 | daddr->rs_lun;
smi_msg->data[2] = recv_msg->msgid << 2 | daddr->rq_lun;
} else {
smi_msg->data[0] = msg->netfn << 2 | daddr->rq_lun;
smi_msg->data[2] = recv_msg->msgid << 2 | daddr->rs_lun;
}
smi_msg->data[1] = daddr->slave_addr;
smi_msg->data[3] = msg->cmd;

memcpy(smi_msg->data + 4, msg->data, msg->data_len);
smi_msg->data_size = msg->data_len + 4;

smi_msg->user_data = recv_msg;

return 0;
}

static int i_ipmi_req_lan(struct ipmi_smi *intf,
struct ipmi_addr *addr,
long msgid,
Expand Down Expand Up @@ -2241,6 +2329,9 @@ static int i_ipmi_request(struct ipmi_user *user,
rv = i_ipmi_req_ipmb(intf, addr, msgid, msg, smi_msg, recv_msg,
source_address, source_lun,
retries, retry_time_ms);
} else if (is_ipmb_direct_addr(addr)) {
rv = i_ipmi_req_ipmb_direct(intf, addr, msgid, msg, smi_msg,
recv_msg, source_lun);
} else if (is_lan_addr(addr)) {
rv = i_ipmi_req_lan(intf, addr, msgid, msg, smi_msg, recv_msg,
source_lun, retries, retry_time_ms);
Expand Down Expand Up @@ -3802,6 +3893,123 @@ static int handle_ipmb_get_msg_cmd(struct ipmi_smi *intf,
return rv;
}

static int handle_ipmb_direct_rcv_cmd(struct ipmi_smi *intf,
struct ipmi_smi_msg *msg)
{
struct cmd_rcvr *rcvr;
int rv = 0;
struct ipmi_user *user = NULL;
struct ipmi_ipmb_direct_addr *daddr;
struct ipmi_recv_msg *recv_msg;
unsigned char netfn = msg->rsp[0] >> 2;
unsigned char cmd = msg->rsp[3];

rcu_read_lock();
/* We always use channel 0 for direct messages. */
rcvr = find_cmd_rcvr(intf, netfn, cmd, 0);
if (rcvr) {
user = rcvr->user;
kref_get(&user->refcount);
} else
user = NULL;
rcu_read_unlock();

if (user == NULL) {
/* We didn't find a user, deliver an error response. */
ipmi_inc_stat(intf, unhandled_commands);

msg->data[0] = ((netfn + 1) << 2) | (msg->rsp[4] & 0x3);
msg->data[1] = msg->rsp[2];
msg->data[2] = msg->rsp[4] & ~0x3;
msg->data[3] = cmd;
msg->data[4] = IPMI_INVALID_CMD_COMPLETION_CODE;
msg->data_size = 5;

rcu_read_lock();
if (!intf->in_shutdown) {
smi_send(intf, intf->handlers, msg, 0);
/*
* We used the message, so return the value
* that causes it to not be freed or
* queued.
*/
rv = -1;
}
rcu_read_unlock();
} else {
recv_msg = ipmi_alloc_recv_msg();
if (!recv_msg) {
/*
* We couldn't allocate memory for the
* message, so requeue it for handling
* later.
*/
rv = 1;
kref_put(&user->refcount, free_user);
} else {
/* Extract the source address from the data. */
daddr = (struct ipmi_ipmb_direct_addr *)&recv_msg->addr;
daddr->addr_type = IPMI_IPMB_DIRECT_ADDR_TYPE;
daddr->channel = 0;
daddr->slave_addr = msg->rsp[1];
daddr->rs_lun = msg->rsp[0] & 3;
daddr->rq_lun = msg->rsp[2] & 3;

/*
* Extract the rest of the message information
* from the IPMB header.
*/
recv_msg->user = user;
recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
recv_msg->msgid = (msg->rsp[2] >> 2);
recv_msg->msg.netfn = msg->rsp[0] >> 2;
recv_msg->msg.cmd = msg->rsp[3];
recv_msg->msg.data = recv_msg->msg_data;

recv_msg->msg.data_len = msg->rsp_size - 4;
memcpy(recv_msg->msg_data, msg->rsp + 4,
msg->rsp_size - 4);
if (deliver_response(intf, recv_msg))
ipmi_inc_stat(intf, unhandled_commands);
else
ipmi_inc_stat(intf, handled_commands);
}
}

return rv;
}

static int handle_ipmb_direct_rcv_rsp(struct ipmi_smi *intf,
struct ipmi_smi_msg *msg)
{
struct ipmi_recv_msg *recv_msg;
struct ipmi_ipmb_direct_addr *daddr;

recv_msg = (struct ipmi_recv_msg *) msg->user_data;
if (recv_msg == NULL) {
dev_warn(intf->si_dev,
"IPMI message received with no owner. This could be because of a malformed message, or because of a hardware error. Contact your hardware vendor for assistance.\n");
return 0;
}

recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
recv_msg->msgid = msg->msgid;
daddr = (struct ipmi_ipmb_direct_addr *) &recv_msg->addr;
daddr->addr_type = IPMI_IPMB_DIRECT_ADDR_TYPE;
daddr->channel = 0;
daddr->slave_addr = msg->rsp[1];
daddr->rq_lun = msg->rsp[0] & 3;
daddr->rs_lun = msg->rsp[2] & 3;
recv_msg->msg.netfn = msg->rsp[0] >> 2;
recv_msg->msg.cmd = msg->rsp[3];
memcpy(recv_msg->msg_data, &msg->rsp[4], msg->rsp_size - 4);
recv_msg->msg.data = recv_msg->msg_data;
recv_msg->msg.data_len = msg->rsp_size - 4;
deliver_local_response(intf, recv_msg);

return 0;
}

static int handle_lan_get_msg_rsp(struct ipmi_smi *intf,
struct ipmi_smi_msg *msg)
{
Expand Down Expand Up @@ -4227,18 +4435,40 @@ static int handle_bmc_rsp(struct ipmi_smi *intf,
static int handle_one_recv_msg(struct ipmi_smi *intf,
struct ipmi_smi_msg *msg)
{
int requeue;
int requeue = 0;
int chan;
unsigned char cc;
bool is_cmd = !((msg->rsp[0] >> 2) & 1);

pr_debug("Recv: %*ph\n", msg->rsp_size, msg->rsp);

if ((msg->data_size >= 2)
if (msg->rsp_size < 2) {
/* Message is too small to be correct. */
dev_warn(intf->si_dev,
"BMC returned too small a message for netfn %x cmd %x, got %d bytes\n",
(msg->data[0] >> 2) | 1, msg->data[1], msg->rsp_size);

return_unspecified:
/* Generate an error response for the message. */
msg->rsp[0] = msg->data[0] | (1 << 2);
msg->rsp[1] = msg->data[1];
msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
msg->rsp_size = 3;
} else if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) {
/* commands must have at least 3 bytes, responses 4. */
if (is_cmd && (msg->rsp_size < 3)) {
ipmi_inc_stat(intf, invalid_commands);
goto out;
}
if (!is_cmd && (msg->rsp_size < 4))
goto return_unspecified;
} else if ((msg->data_size >= 2)
&& (msg->data[0] == (IPMI_NETFN_APP_REQUEST << 2))
&& (msg->data[1] == IPMI_SEND_MSG_CMD)
&& (msg->user_data == NULL)) {

if (intf->in_shutdown)
goto free_msg;
goto out;

/*
* This is the local response to a command send, start
Expand Down Expand Up @@ -4273,21 +4503,6 @@ static int handle_one_recv_msg(struct ipmi_smi *intf,
} else
/* The message was sent, start the timer. */
intf_start_seq_timer(intf, msg->msgid);
free_msg:
requeue = 0;
goto out;

} else if (msg->rsp_size < 2) {
/* Message is too small to be correct. */
dev_warn(intf->si_dev,
"BMC returned too small a message for netfn %x cmd %x, got %d bytes\n",
(msg->data[0] >> 2) | 1, msg->data[1], msg->rsp_size);

/* Generate an error response for the message. */
msg->rsp[0] = msg->data[0] | (1 << 2);
msg->rsp[1] = msg->data[1];
msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
msg->rsp_size = 3;
} else if (((msg->rsp[0] >> 2) != ((msg->data[0] >> 2) | 1))
|| (msg->rsp[1] != msg->data[1])) {
/*
Expand All @@ -4299,39 +4514,46 @@ static int handle_one_recv_msg(struct ipmi_smi *intf,
(msg->data[0] >> 2) | 1, msg->data[1],
msg->rsp[0] >> 2, msg->rsp[1]);

/* Generate an error response for the message. */
msg->rsp[0] = msg->data[0] | (1 << 2);
msg->rsp[1] = msg->data[1];
msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
msg->rsp_size = 3;
goto return_unspecified;
}

if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
&& (msg->rsp[1] == IPMI_SEND_MSG_CMD)
&& (msg->user_data != NULL)) {
if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) {
if ((msg->data[0] >> 2) & 1) {
/* It's a response to a sent response. */
chan = 0;
cc = msg->rsp[4];
goto process_response_response;
}
if (is_cmd)
requeue = handle_ipmb_direct_rcv_cmd(intf, msg);
else
requeue = handle_ipmb_direct_rcv_rsp(intf, msg);
} else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
&& (msg->rsp[1] == IPMI_SEND_MSG_CMD)
&& (msg->user_data != NULL)) {
/*
* It's a response to a response we sent. For this we
* deliver a send message response to the user.
*/
struct ipmi_recv_msg *recv_msg = msg->user_data;

requeue = 0;
if (msg->rsp_size < 2)
/* Message is too small to be correct. */
goto out;
struct ipmi_recv_msg *recv_msg;

chan = msg->data[2] & 0x0f;
if (chan >= IPMI_MAX_CHANNELS)
/* Invalid channel number */
goto out;
cc = msg->rsp[2];

process_response_response:
recv_msg = msg->user_data;

requeue = 0;
if (!recv_msg)
goto out;

recv_msg->recv_type = IPMI_RESPONSE_RESPONSE_TYPE;
recv_msg->msg.data = recv_msg->msg_data;
recv_msg->msg_data[0] = cc;
recv_msg->msg.data_len = 1;
recv_msg->msg_data[0] = msg->rsp[2];
deliver_local_response(intf, recv_msg);
} else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
&& (msg->rsp[1] == IPMI_GET_MSG_CMD)) {
Expand Down

0 comments on commit 059747c

Please sign in to comment.