/*
* Copyright (c) 2015 American Megatrends, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
*/
#define _BSD_SOURCE
#include <ipmitool/helper.h>
#include <ipmitool/log.h>
#include <ipmitool/bswap.h>
#include <ipmitool/ipmi.h>
#include <ipmitool/ipmi_intf.h>
#include <ipmitool/ipmi_oem.h>
#include <ipmitool/ipmi_strings.h>
#include <ipmitool/ipmi_constants.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>
#include <scsi/scsi_ioctl.h>
#include <scsi/scsi.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#define PACKED __attribute__ ((packed))
#define BEGIN_SIG "$G2-CONFIG-HOST$"
#define BEGIN_SIG_LEN 16
#define MAX_REQUEST_SIZE 64 * 1024
#define CMD_RESERVED 0x0000
#define SCSI_AMICMD_CURI_WRITE 0xE2
#define SCSI_AMICMD_CURI_READ 0xE3
#define SCSI_AMIDEF_CMD_SECTOR 0x01
#define SCSI_AMIDEF_DATA_SECTOR 0x02
#define ERR_SUCCESS 0 /* Success */
#define ERR_BIG_DATA 1 /* Too Much Data */
#define ERR_NO_DATA 2 /* No/Less Data Available */
#define ERR_UNSUPPORTED 3 /* Unsupported Command */
#define IN_PROCESS 0x8000 /* Bit 15 of Status */
#define SCSI_AMICMD_ID 0xEE
/* SCSI Command Packets */
typedef struct {
unsigned char OpCode;
unsigned char Lun;
unsigned int Lba;
union {
struct {
unsigned char Reserved6;
unsigned short Length;
unsigned char Reserved9[3];
} PACKED Cmd10;
struct Len32 {
unsigned int Length32;
unsigned char Reserved10[2];
} PACKED Cmd12;
} PACKED CmdLen;
} PACKED SCSI_COMMAND_PACKET;
typedef struct {
uint8_t byNetFnLUN;
uint8_t byCmd;
uint8_t byData[MAX_REQUEST_SIZE];
} PACKED IPMIUSBRequest_T;
typedef struct {
uint8_t BeginSig[BEGIN_SIG_LEN];
uint16_t Command;
uint16_t Status;
uint32_t DataInLen;
uint32_t DataOutLen;
uint32_t InternalUseDataIn;
uint32_t InternalUseDataOut;
} CONFIG_CMD;
static int ipmi_usb_setup(struct ipmi_intf *intf);
static struct ipmi_rs *ipmi_usb_send_cmd(struct ipmi_intf *intf,
struct ipmi_rq *req);
struct ipmi_intf ipmi_usb_intf = {
.name = "usb",
.desc = "IPMI USB Interface(OEM Interface for AMI Devices)",
.setup = ipmi_usb_setup,
.sendrecv = ipmi_usb_send_cmd,
};
int
scsiProbeNew(int *num_ami_devices, int *sg_nos)
{
int inplen = *num_ami_devices;
int numdevfound = 0;
char linebuf[81];
char vendor[81];
int lineno = 0;
FILE *fp;
fp = fopen("/proc/scsi/sg/device_strs", "r");
if (fp == NULL) {
/* Return 1 on error */
return 1;
}
while (1) {
/* Read line by line and search for "AMI" */
if (fgets(linebuf, 80, fp) == NULL) {
break;
}
if (sscanf(linebuf, "%s", vendor) == 1) {
if (strncmp(vendor, "AMI", strlen("AMI")) == 0) {
numdevfound++;
sg_nos[numdevfound - 1] = lineno;
if (numdevfound == inplen) {
break;
}
}
lineno++;
}
}
*num_ami_devices = numdevfound;
if (fp != NULL) {
fclose(fp);
fp = NULL;
}
return 0;
}
int
OpenCD(struct ipmi_intf *intf, char *CDName)
{
intf->fd = open(CDName, O_RDWR);
if (intf->fd == (-1)) {
lprintf(LOG_ERR, "OpenCD:Unable to open device, %s",
strerror(errno));
return 1;
}
return 0;
}
int
sendscsicmd_SGIO(int cd_desc, unsigned char *cdb_buf, unsigned char cdb_len,
void *data_buf, unsigned int *data_len, int direction,
void *sense_buf, unsigned char slen, unsigned int timeout)
{
sg_io_hdr_t io_hdr;
/* Prepare command */
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = cdb_len;
/* Transfer direction and length */
io_hdr.dxfer_direction = direction;
io_hdr.dxfer_len = *data_len;
io_hdr.dxferp = data_buf;
io_hdr.cmdp = cdb_buf;
io_hdr.sbp = (unsigned char *)sense_buf;
io_hdr.mx_sb_len = slen;
io_hdr.timeout = timeout;
if (!timeout) {
io_hdr.timeout = 20000;
}
if (ioctl(cd_desc, SG_IO, &io_hdr) < 0) {
lprintf(LOG_ERR, "sendscsicmd_SGIO: SG_IO ioctl error");
return 1;
} else {
if (io_hdr.status != 0) {
return 1;
}
}
if (!timeout) {
return 0;
}
if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
lprintf(LOG_DEBUG, "sendscsicmd_SGIO: SG_INFO_OK - Not OK");
} else {
lprintf(LOG_DEBUG, "sendscsicmd_SGIO: SG_INFO_OK - OK");
return 0;
}
return 1;
}
int
AMI_SPT_CMD_Identify(int cd_desc, char *szSignature)
{
SCSI_COMMAND_PACKET IdPkt = {0};
int ret;
unsigned int siglen = 10;
IdPkt.OpCode = SCSI_AMICMD_ID;
ret = sendscsicmd_SGIO(cd_desc, (unsigned char *)&IdPkt,
10, szSignature, &siglen, SG_DXFER_FROM_DEV,
NULL, 0, 5000);
return ret;
}
int
IsG2Drive(int cd_desc)
{
char szSignature[15];
int ret;
memset(szSignature, 0, 15);
flock(cd_desc, LOCK_EX);
ret = AMI_SPT_CMD_Identify(cd_desc, szSignature);
flock(cd_desc, LOCK_UN);
if (ret != 0) {
lprintf(LOG_DEBUG,
"IsG2Drive:Unable to send ID command to the device");
return 1;
}
if (strncmp(szSignature, "$$$AMI$$$", strlen("$$$AMI$$$")) != 0) {
lprintf(LOG_ERR,
"IsG2Drive:Signature mismatch when ID command sent");
return 1;
}
return 0;
}
int
FindG2CDROM(struct ipmi_intf *intf)
{
int err = 0;
char device[256];
int devarray[16];
int numdev = 16;
int iter;
err = scsiProbeNew(&numdev, devarray);
if (err == 0 && numdev > 0) {
for (iter = 0; iter < numdev; iter++) {
sprintf(device, "/dev/sg%d", devarray[iter]);
if (!OpenCD(intf, device)) {
if (!IsG2Drive(intf->fd)) {
lprintf(LOG_DEBUG, "USB Device found");
return 1;
}
close(intf->fd);
}
}
} else {
lprintf(LOG_DEBUG, "Unable to find Virtual CDROM Device");
}
return 0;
}
static int
ipmi_usb_setup(struct ipmi_intf *intf)
{
if (FindG2CDROM(intf) == 0) {
lprintf(LOG_ERR, "Error in USB session setup \n");
return (-1);
}
intf->opened = 1;
return 0;
}
void
InitCmdHeader(CONFIG_CMD *pG2CDCmdHeader)
{
memset(pG2CDCmdHeader, 0, sizeof(CONFIG_CMD));
memcpy((char *)pG2CDCmdHeader->BeginSig, BEGIN_SIG, BEGIN_SIG_LEN);
}
int
AMI_SPT_CMD_SendCmd(int cd_desc, char *Buffer, char type, uint16_t buflen,
unsigned int timeout)
{
SCSI_COMMAND_PACKET Cmdpkt;
char sensebuff[32];
int ret;
unsigned int pktLen;
int count = 3;
memset(&Cmdpkt, 0, sizeof(SCSI_COMMAND_PACKET));
Cmdpkt.OpCode = SCSI_AMICMD_CURI_WRITE;
Cmdpkt.Lba = htonl(type);
Cmdpkt.CmdLen.Cmd10.Length = htons(1);
pktLen = buflen;
while (count > 0) {
ret = sendscsicmd_SGIO(cd_desc, (unsigned char *)&Cmdpkt,
10, Buffer, &pktLen, SG_DXFER_TO_DEV,
sensebuff, 32, timeout);
count--;
if (ret == 0) {
break;
} else {
ret = (-1);
}
}
return ret;
}
int
AMI_SPT_CMD_RecvCmd(int cd_desc, char *Buffer, char type, uint16_t buflen)
{
SCSI_COMMAND_PACKET Cmdpkt;
char sensebuff[32];
int ret;
unsigned int pktLen;
int count = 3;
memset(&Cmdpkt, 0, sizeof(SCSI_COMMAND_PACKET));
Cmdpkt.OpCode = SCSI_AMICMD_CURI_READ;
Cmdpkt.Lba = htonl(type);
Cmdpkt.CmdLen.Cmd10.Length = htons(1);
pktLen = buflen;
while (count > 0) {
ret = sendscsicmd_SGIO(cd_desc, (unsigned char *)&Cmdpkt,
10, Buffer, &pktLen, SG_DXFER_FROM_DEV,
sensebuff, 32, 5000);
count--;
if (0 == ret) {
break;
} else {
ret = (-1);
}
}
return ret;
}
int
ReadCD(int cd_desc, char CmdData, char *Buffer, uint32_t DataLen)
{
int ret;
ret = AMI_SPT_CMD_RecvCmd(cd_desc, Buffer, CmdData, DataLen);
if (ret != 0) {
lprintf(LOG_ERR, "Error while reading CD-Drive");
return (-1);
}
return 0;
}
int
WriteCD(int cd_desc, char CmdData, char *Buffer, unsigned int timeout,
uint32_t DataLen)
{
int ret;
ret = AMI_SPT_CMD_SendCmd(cd_desc, Buffer, CmdData, DataLen, timeout);
if (ret != 0) {
lprintf(LOG_ERR, "Error while writing to CD-Drive");
return (-1);
}
return 0;
}
int
WriteSplitData(struct ipmi_intf *intf, char *Buffer, char Sector,
uint32_t NumBytes, uint32_t timeout)
{
uint32_t BytesWritten = 0;
int retVal;
if (NumBytes == 0) {
return 0;
}
while (BytesWritten < NumBytes) {
if ((retVal = WriteCD(intf->fd, Sector,
(Buffer + BytesWritten),
timeout, NumBytes)) != 0) {
return retVal;
}
BytesWritten += NumBytes;
}
return 0;
}
int
ReadSplitData(struct ipmi_intf *intf, char *Buffer, char Sector,
uint32_t NumBytes)
{
uint32_t BytesRead = 0;
if (NumBytes == 0) {
return 0;
}
while (BytesRead < NumBytes) {
if (ReadCD(intf->fd, Sector, (Buffer + BytesRead),
NumBytes) == (-1)) {
return 1;
}
BytesRead += NumBytes;
}
return 0;
}
int
WaitForCommandCompletion(struct ipmi_intf *intf, CONFIG_CMD *pG2CDCmdHeader,
uint32_t timeout, uint32_t DataLen)
{
uint32_t TimeCounter = 0;
do {
if (ReadCD(intf->fd, SCSI_AMIDEF_CMD_SECTOR,
(char *)(pG2CDCmdHeader), DataLen) == (-1)) {
lprintf(LOG_ERR, "ReadCD returned ERROR");
return 1;
}
if (pG2CDCmdHeader->Status & IN_PROCESS) {
usleep(1000);
if (timeout > 0) {
TimeCounter++;
if (TimeCounter == (timeout + 1)) {
return 2;
}
}
} else {
lprintf(LOG_DEBUG, "Command completed");
break;
}
} while (1);
return 0;
}
int
SendDataToUSBDriver(struct ipmi_intf *intf, char *ReqBuffer,
unsigned int ReqBuffLen, unsigned char *ResBuffer,
int *ResBuffLen, unsigned int timeout)
{
char CmdHeaderBuffer[sizeof(CONFIG_CMD)];
int retVal;
int waitretval = 0;
unsigned int to = 0;
uint32_t DataLen = 0;
CONFIG_CMD *pG2CDCmdHeader = (CONFIG_CMD *)CmdHeaderBuffer;
/* FillHeader */
InitCmdHeader(pG2CDCmdHeader);
/* Set command number */
pG2CDCmdHeader->Command = CMD_RESERVED;
/* Fill Lengths */
pG2CDCmdHeader->DataOutLen = *ResBuffLen;
pG2CDCmdHeader->DataInLen = ReqBuffLen;
if (!timeout) {
to = 3000;
}
DataLen = sizeof(CONFIG_CMD);
if (WriteCD(intf->fd, SCSI_AMIDEF_CMD_SECTOR,
(char *)(pG2CDCmdHeader), to, DataLen) == (-1)) {
lprintf(LOG_ERR,
"Error in Write CD of SCSI_AMIDEF_CMD_SECTOR");
return (-1);
}
/* Write the data to hard disk */
if ((retVal = WriteSplitData(intf, ReqBuffer,
SCSI_AMIDEF_DATA_SECTOR,
ReqBuffLen, timeout)) != 0) {
lprintf(LOG_ERR,
"Error in WriteSplitData of SCSI_AMIDEF_DATA_SECTOR");
return (-1);
}
if (!timeout) {
return 0;
}
/* Read Status now */
waitretval = WaitForCommandCompletion(intf, pG2CDCmdHeader, timeout,
DataLen);
if (waitretval != 0) {
lprintf(LOG_ERR, "WaitForCommandComplete failed");
return (0 - waitretval);
} else {
lprintf(LOG_DEBUG, "WaitForCommandCompletion SUCCESS");
}
switch (pG2CDCmdHeader->Status) {
case ERR_SUCCESS:
*ResBuffLen = pG2CDCmdHeader->DataOutLen;
lprintf(LOG_DEBUG, "Before ReadSplitData %x", *ResBuffLen);
if (ReadSplitData(intf, (char *)ResBuffer,
SCSI_AMIDEF_DATA_SECTOR,
pG2CDCmdHeader->DataOutLen) != 0) {
lprintf(LOG_ERR,
"Err ReadSplitData SCSI_AMIDEF_DATA_SCTR");
return (-1);
}
/* Additional read to see verify there was not problem
* with the previous read
*/
DataLen = sizeof(CONFIG_CMD);
ReadCD(intf->fd, SCSI_AMIDEF_CMD_SECTOR,
(char *)(pG2CDCmdHeader), DataLen);
break;
case ERR_BIG_DATA:
lprintf(LOG_ERR, "Too much data");
break;
case ERR_NO_DATA:
lprintf(LOG_ERR, "Too little data");
break;
case ERR_UNSUPPORTED:
lprintf(LOG_ERR, "Unsupported command");
break;
default:
lprintf(LOG_ERR, "Unknown status");
}
return pG2CDCmdHeader->Status;
}
static struct ipmi_rs *
ipmi_usb_send_cmd(struct ipmi_intf *intf, struct ipmi_rq *req)
{
static struct ipmi_rs rsp;
long timeout = 20000;
uint8_t byRet = 0;
char ReqBuff[MAX_REQUEST_SIZE] = {0};
IPMIUSBRequest_T *pReqPkt = (IPMIUSBRequest_T *)ReqBuff;
int retries = 0;
/********** FORM IPMI PACKET *****************/
pReqPkt->byNetFnLUN = req->msg.netfn << 2;
pReqPkt->byNetFnLUN += req->msg.lun;
pReqPkt->byCmd = req->msg.cmd;
if (req->msg.data_len) {
memcpy(pReqPkt->byData, req->msg.data, req->msg.data_len);
}
/********** SEND DATA TO USB ******************/
while (retries < 3) {
retries++;
byRet = SendDataToUSBDriver(intf, ReqBuff,
2 + req->msg.data_len, rsp.data,
&rsp.data_len,timeout);
if (byRet == 0) {
break;
}
}
if (retries == 3) {
lprintf(LOG_ERR,
"Error while sending command using",
"SendDataToUSBDriver");
rsp.ccode = byRet;
return &rsp;
}
rsp.ccode = rsp.data[0];
/* Save response data for caller */
if ((rsp.ccode == 0) && (rsp.data_len > 0)) {
memmove(rsp.data, rsp.data + 1, rsp.data_len - 1);
rsp.data[rsp.data_len] = 0;
rsp.data_len -= 1;
}
return &rsp;
}