qemu/hw/ipmi/smbus_ipmi.c

392 lines
11 KiB
C
Raw Permalink Normal View History

/*
* QEMU IPMI SMBus (SSIF) emulation
*
* Copyright (c) 2015,2016 Corey Minyard, MontaVista Software, LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "migration/vmstate.h"
#include "hw/i2c/smbus_slave.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "hw/ipmi/ipmi.h"
#include "qom/object.h"
#include "hw/acpi/ipmi.h"
#define TYPE_SMBUS_IPMI "smbus-ipmi"
OBJECT_DECLARE_SIMPLE_TYPE(SMBusIPMIDevice, SMBUS_IPMI)
#define SSIF_IPMI_REQUEST 2
#define SSIF_IPMI_MULTI_PART_REQUEST_START 6
#define SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE 7
#define SSIF_IPMI_MULTI_PART_REQUEST_END 8
#define SSIF_IPMI_RESPONSE 3
#define SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE 9
#define SSIF_IPMI_MULTI_PART_RETRY 0xa
#define MAX_SSIF_IPMI_MSG_SIZE 255
#define MAX_SSIF_IPMI_MSG_CHUNK 32
#define IPMI_GET_SYS_INTF_CAP_CMD 0x57
struct SMBusIPMIDevice {
SMBusDevice parent;
IPMIBmc *bmc;
uint8_t outmsg[MAX_SSIF_IPMI_MSG_SIZE];
uint32_t outlen;
uint32_t currblk;
/* Holds the SMBUS message currently being sent to the host. */
uint8_t outbuf[MAX_SSIF_IPMI_MSG_CHUNK + 1]; /* len + message. */
uint32_t outpos;
uint8_t inmsg[MAX_SSIF_IPMI_MSG_SIZE];
uint32_t inlen;
/*
* This is a response number that we send with the command to make
* sure that the response matches the command.
*/
uint8_t waiting_rsp;
uint32_t uuid;
};
static void smbus_ipmi_handle_event(IPMIInterface *ii)
{
/* No interrupts, so nothing to do here. */
}
static void smbus_ipmi_handle_rsp(IPMIInterface *ii, uint8_t msg_id,
unsigned char *rsp, unsigned int rsp_len)
{
SMBusIPMIDevice *sid = SMBUS_IPMI(ii);
if (sid->waiting_rsp == msg_id) {
sid->waiting_rsp++;
if (rsp_len > MAX_SSIF_IPMI_MSG_SIZE) {
rsp[2] = IPMI_CC_REQUEST_DATA_TRUNCATED;
rsp_len = MAX_SSIF_IPMI_MSG_SIZE;
}
memcpy(sid->outmsg, rsp, rsp_len);
sid->outlen = rsp_len;
sid->outpos = 0;
sid->currblk = 0;
}
}
static void smbus_ipmi_set_atn(IPMIInterface *ii, int val, int irq)
{
/* This is where PEC would go. */
}
static void smbus_ipmi_set_irq_enable(IPMIInterface *ii, int val)
{
}
static void smbus_ipmi_send_msg(SMBusIPMIDevice *sid)
{
uint8_t *msg = sid->inmsg;
uint32_t len = sid->inlen;
IPMIBmcClass *bk = IPMI_BMC_GET_CLASS(sid->bmc);
sid->outlen = 0;
sid->outpos = 0;
sid->currblk = 0;
if (msg[0] == (IPMI_NETFN_APP << 2) && msg[1] == IPMI_GET_SYS_INTF_CAP_CMD)
{
/* We handle this ourself. */
sid->outmsg[0] = (IPMI_NETFN_APP + 1) << 2;
sid->outmsg[1] = msg[1];
if (len < 3) {
sid->outmsg[2] = IPMI_CC_REQUEST_DATA_LENGTH_INVALID;
sid->outlen = 3;
} else if ((msg[2] & 0x0f) != 0) {
sid->outmsg[2] = IPMI_CC_INVALID_DATA_FIELD;
sid->outlen = 3;
} else {
sid->outmsg[2] = 0;
sid->outmsg[3] = 0;
sid->outmsg[4] = (2 << 6); /* Multi-part supported. */
sid->outmsg[5] = MAX_SSIF_IPMI_MSG_SIZE;
sid->outmsg[6] = MAX_SSIF_IPMI_MSG_SIZE;
sid->outlen = 7;
}
return;
}
bk->handle_command(sid->bmc, sid->inmsg, sid->inlen, sizeof(sid->inmsg),
sid->waiting_rsp);
}
static uint8_t ipmi_receive_byte(SMBusDevice *dev)
{
SMBusIPMIDevice *sid = SMBUS_IPMI(dev);
if (sid->outpos >= sizeof(sid->outbuf)) {
return 0xff;
}
return sid->outbuf[sid->outpos++];
}
static int ipmi_load_readbuf(SMBusIPMIDevice *sid)
{
unsigned int block = sid->currblk, pos, len;
if (sid->outlen == 0) {
return -1;
}
if (sid->outlen <= 32) {
if (block != 0) {
return -1;
}
sid->outbuf[0] = sid->outlen;
memcpy(sid->outbuf + 1, sid->outmsg, sid->outlen);
sid->outpos = 0;
return 0;
}
if (block == 0) {
sid->outbuf[0] = 32;
sid->outbuf[1] = 0;
sid->outbuf[2] = 1;
memcpy(sid->outbuf + 3, sid->outmsg, 30);
sid->outpos = 0;
return 0;
}
/*
* Calculate the position in outmsg. 30 for the first block, 31
* for the rest of the blocks.
*/
pos = 30 + (block - 1) * 31;
if (pos >= sid->outlen) {
return -1;
}
len = sid->outlen - pos;
if (len > 31) {
/* More chunks after this. */
len = 31;
/* Blocks start at 0 for the first middle transaction. */
sid->outbuf[1] = block - 1;
} else {
sid->outbuf[1] = 0xff; /* End of message marker. */
}
sid->outbuf[0] = len + 1;
memcpy(sid->outbuf + 2, sid->outmsg + pos, len);
sid->outpos = 0;
return 0;
}
static int ipmi_write_data(SMBusDevice *dev, uint8_t *buf, uint8_t len)
{
SMBusIPMIDevice *sid = SMBUS_IPMI(dev);
bool send = false;
uint8_t cmd;
int ret = 0;
/* length is guaranteed to be >= 1. */
cmd = *buf++;
len--;
/* Handle read request, which don't have any data in the write part. */
switch (cmd) {
case SSIF_IPMI_RESPONSE:
sid->currblk = 0;
ret = ipmi_load_readbuf(sid);
break;
case SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE:
sid->currblk++;
ret = ipmi_load_readbuf(sid);
break;
case SSIF_IPMI_MULTI_PART_RETRY:
if (len >= 1) {
sid->currblk = buf[0];
ret = ipmi_load_readbuf(sid);
} else {
ret = -1;
}
break;
default:
break;
}
/* This should be a message write, make the length is there and correct. */
if (len >= 1) {
if (*buf != len - 1 || *buf > MAX_SSIF_IPMI_MSG_CHUNK) {
return -1; /* Bogus message */
}
buf++;
len--;
}
switch (cmd) {
case SSIF_IPMI_REQUEST:
send = true;
/* FALLTHRU */
case SSIF_IPMI_MULTI_PART_REQUEST_START:
if (len < 2) {
return -1; /* Bogus. */
}
memcpy(sid->inmsg, buf, len);
sid->inlen = len;
break;
case SSIF_IPMI_MULTI_PART_REQUEST_END:
send = true;
/* FALLTHRU */
case SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE:
if (!sid->inlen) {
return -1; /* Bogus. */
}
if (sid->inlen + len > MAX_SSIF_IPMI_MSG_SIZE) {
sid->inlen = 0; /* Discard the message. */
return -1; /* Bogus. */
}
if (len < 32) {
/*
* Special hack, a multi-part middle that is less than 32 bytes
* marks the end of a message. The specification is fairly
* confusing, so some systems to this, even sending a zero
* length end message to mark the end.
*/
send = true;
}
if (len > 0) {
memcpy(sid->inmsg + sid->inlen, buf, len);
}
sid->inlen += len;
break;
}
if (send && sid->inlen) {
smbus_ipmi_send_msg(sid);
}
return ret;
}
static const VMStateDescription vmstate_smbus_ipmi = {
.name = TYPE_SMBUS_IPMI,
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_SMBUS_DEVICE(parent, SMBusIPMIDevice),
VMSTATE_UINT8(waiting_rsp, SMBusIPMIDevice),
VMSTATE_UINT32(outlen, SMBusIPMIDevice),
VMSTATE_UINT32(currblk, SMBusIPMIDevice),
VMSTATE_UINT8_ARRAY(outmsg, SMBusIPMIDevice, MAX_SSIF_IPMI_MSG_SIZE),
VMSTATE_UINT32(outpos, SMBusIPMIDevice),
VMSTATE_UINT8_ARRAY(outbuf, SMBusIPMIDevice,
MAX_SSIF_IPMI_MSG_CHUNK + 1),
VMSTATE_UINT32(inlen, SMBusIPMIDevice),
VMSTATE_UINT8_ARRAY(inmsg, SMBusIPMIDevice, MAX_SSIF_IPMI_MSG_SIZE),
VMSTATE_END_OF_LIST()
}
};
static void smbus_ipmi_realize(DeviceState *dev, Error **errp)
{
SMBusIPMIDevice *sid = SMBUS_IPMI(dev);
IPMIInterface *ii = IPMI_INTERFACE(dev);
if (!sid->bmc) {
error_setg(errp, "IPMI device requires a bmc attribute to be set");
return;
}
sid->uuid = ipmi_next_uuid();
sid->bmc->intf = ii;
}
static void smbus_ipmi_init(Object *obj)
{
SMBusIPMIDevice *sid = SMBUS_IPMI(obj);
ipmi_bmc_find_and_link(obj, (Object **) &sid->bmc);
}
static void smbus_ipmi_get_fwinfo(struct IPMIInterface *ii, IPMIFwInfo *info)
{
SMBusIPMIDevice *sid = SMBUS_IPMI(ii);
info->interface_name = "smbus";
info->interface_type = IPMI_SMBIOS_SSIF;
info->ipmi_spec_major_revision = 2;
info->ipmi_spec_minor_revision = 0;
info->i2c_slave_address = sid->bmc->slave_addr;
info->base_address = sid->parent.i2c.address;
info->memspace = IPMI_MEMSPACE_SMBUS;
info->register_spacing = 1;
info->uuid = sid->uuid;
}
static void smbus_ipmi_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
IPMIInterfaceClass *iic = IPMI_INTERFACE_CLASS(oc);
SMBusDeviceClass *sc = SMBUS_DEVICE_CLASS(oc);
AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(oc);
sc->receive_byte = ipmi_receive_byte;
sc->write_data = ipmi_write_data;
dc->vmsd = &vmstate_smbus_ipmi;
dc->realize = smbus_ipmi_realize;
iic->set_atn = smbus_ipmi_set_atn;
iic->handle_rsp = smbus_ipmi_handle_rsp;
iic->handle_if_event = smbus_ipmi_handle_event;
iic->set_irq_enable = smbus_ipmi_set_irq_enable;
iic->get_fwinfo = smbus_ipmi_get_fwinfo;
adevc->build_dev_aml = build_ipmi_dev_aml;
}
static const TypeInfo smbus_ipmi_info = {
.name = TYPE_SMBUS_IPMI,
.parent = TYPE_SMBUS_DEVICE,
.instance_size = sizeof(SMBusIPMIDevice),
.instance_init = smbus_ipmi_init,
.class_init = smbus_ipmi_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_IPMI_INTERFACE },
{ TYPE_ACPI_DEV_AML_IF },
{ }
}
};
static void smbus_ipmi_register_types(void)
{
type_register_static(&smbus_ipmi_info);
}
type_init(smbus_ipmi_register_types)