common/recipes-lib/ipmb/files/ipmb.c (171 lines of code) (raw):
/*
*
* Copyright 2015-present Facebook. All Rights Reserved.
*
* This file contains code to support IPMI2.0 Specificaton available @
* http://www.intel.com/content/www/us/en/servers/ipmi/ipmi-specifications.html
*
* 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 program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include <stdarg.h>
#include <unistd.h>
#include <limits.h>
#include <openbmc/ipc.h>
#include "ipmb.h"
#include <openbmc/ipmi.h>
static pthread_key_t rxkey, txkey;
static pthread_once_t key_once = PTHREAD_ONCE_INIT;
static void
destructor(void *buf)
{
if (buf)
free(buf);
}
static void
make_key()
{
(void) pthread_key_create(&rxkey, destructor);
(void) pthread_key_create(&txkey, destructor);
}
/*
* Return thread specific ipmb rx buffer
*/
ipmb_res_t*
ipmb_rxb()
{
ipmb_res_t *buf = NULL;
pthread_once(&key_once, make_key);
if ((buf = pthread_getspecific(rxkey)) == NULL) {
buf = malloc(MAX_IPMB_RES_LEN);
pthread_setspecific(rxkey, buf);
}
return (ipmb_res_t*)buf;
}
/*
* Return thread specific ipmb tx buffer
*/
ipmb_req_t*
ipmb_txb()
{
void *buf = NULL;
pthread_once(&key_once, make_key);
if ((buf = pthread_getspecific(txkey)) == NULL) {
buf = malloc(MAX_IPMB_RES_LEN);
pthread_setspecific(txkey, buf);
}
return (ipmb_req_t*)buf;
}
/*
* Function to handle IPMB messages
*/
int
lib_ipmb_handle(unsigned char bus_id,
unsigned char *request, unsigned int req_len,
unsigned char *response, unsigned char *res_len) {
size_t resp_len = MAX_IPMB_RES_LEN;
char sock_path[64];
sprintf(sock_path, "%s_%d", SOCK_PATH_IPMB, bus_id);
if (ipc_send_req(sock_path, request, (size_t)req_len, response,
&resp_len, TIMEOUT_IPMB) != 0) {
return -1;
}
*res_len = (unsigned char)resp_len;
if (resp_len > UCHAR_MAX) {
syslog(LOG_ERR, "ipmb response buffer truncated: %lu -> %u\n",
(unsigned long)resp_len, *res_len);
}
return 0;
}
int
ipmb_send_buf (unsigned char bus_id, unsigned char tlen)
{
unsigned char rlen = 0;
lib_ipmb_handle(bus_id,
(unsigned char *)ipmb_txb(), tlen,
(unsigned char *)ipmb_rxb(), &rlen);
if (rlen >= MIN_IPMB_RES_LEN)
return (int)rlen;
else
return -1;
}
int
ipmb_send_internal (unsigned char bus_id,
unsigned char addr,
unsigned char netfn,
unsigned char cmd, ...)
{
unsigned char *tbuf;
unsigned char *rbuf;
unsigned char tlen = 0;
unsigned char rlen = 0;
int data_len;
ipmb_req_t *req;
int data;
va_list dl; // data list
// Get thread specific buffers
tbuf = (unsigned char *)ipmb_txb();
rbuf = (unsigned char *)ipmb_rxb();
if (rbuf == NULL || tbuf == NULL)
return -1;
req = (ipmb_req_t*)tbuf;
req->res_slave_addr = addr;
req->netfn_lun = netfn;
req->cmd = cmd;
tlen = 0;
va_start(dl, cmd);
while ((data = va_arg(dl, int)) != OBMC_API_IPMB_CMD_END) {
if (tlen == (MAX_IPMB_RES_LEN - MIN_IPMB_REQ_LEN)) {
va_end(dl);
return -1;
}
req->data[tlen++] = (uint8_t)data;
}
va_end(dl);
tlen += MIN_IPMB_REQ_LEN;
rlen = 0;
lib_ipmb_handle(bus_id, tbuf, tlen, rbuf, &rlen);
if (rlen >= MIN_IPMB_RES_LEN) {
data_len = rlen - MIN_IPMB_RES_LEN;
} else {
data_len = -1;
}
return data_len;
}
int
lib_ipmb_send_request(uint8_t ipmi_cmd, uint8_t netfn,
uint8_t *txbuf, uint8_t txlen,
uint8_t *rxbuf, uint8_t *rxlen,
uint8_t bus_num, uint8_t dev_addr, uint8_t bmc_addr) {
uint8_t rdata[MAX_IPMB_RES_LEN] = {0x00};
uint8_t wdata[MAX_IPMB_RES_LEN] = {0x00};
uint8_t tlen = 0;
uint8_t rlen = 0;
ipmb_req_t *req;
ipmb_res_t *res;
req = (ipmb_req_t*) wdata;
req->res_slave_addr = dev_addr;
req->netfn_lun = netfn << LUN_OFFSET;
req->cmd = ipmi_cmd;
req->seq_lun = 0x00;
req->hdr_cksum = req->res_slave_addr + req->netfn_lun;
req->hdr_cksum = ZERO_CKSUM_CONST - req->hdr_cksum;
req->req_slave_addr = bmc_addr << 1;
if (txlen) {
memcpy(req->data, txbuf, txlen);
}
tlen = MIN_IPMB_REQ_LEN + txlen;
// Invoke IPMB library handler
lib_ipmb_handle(bus_num, wdata, tlen, rdata, &rlen);
res = (ipmb_res_t*) rdata;
if (rlen == 0) {
#ifdef DEBUG
syslog(LOG_DEBUG, "%s: Zero bytes received cc=0x%x\n", __func__, res->cc);
#endif
return CC_CAN_NOT_RESPOND;
}
// Handle IPMB response
if (res->cc) {
#ifdef DEBUG
syslog(LOG_ERR, "%s: Completion Code: 0x%X\n", __func__, res->cc);
#endif
return res->cc;
}
// copy the received data back to caller
*rxlen = rlen - IPMB_HDR_SIZE - IPMI_RESP_HDR_SIZE;
memcpy(rxbuf, res->data, *rxlen);
#ifdef DEBUG
{
for(int i=0; i< *rxlen; i++)
syslog(LOG_WARNING, "rbuf[%d]=%x\n", i, rxbuf[i]);
}
#endif
return CC_SUCCESS;
}