qemu/hw/cxl/cxl-cdat.c
Ira Weiny c62926f730 cxl/cdat: Handle cdat table build errors
The callback for building CDAT tables may return negative error codes.
This was previously unhandled and will result in potentially huge
allocations later on in ct3_build_cdat()

Detect the negative error code and defer cdat building.

Fixes: f5ee7413d5 ("hw/mem/cxl-type3: Add CXL CDAT Data Object Exchange")
Cc: Huai-Cheng Kuo <hchkuo@avery-design.com.tw>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Fan Ni <fan.ni@samsung.com>
Signed-off-by: Ira Weiny <ira.weiny@intel.com>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Message-Id: <20240126120132.24248-2-Jonathan.Cameron@huawei.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2024-02-14 06:09:32 -05:00

216 lines
5.8 KiB
C

/*
* CXL CDAT Structure
*
* Copyright (C) 2021 Avery Design Systems, Inc.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "hw/pci/pci.h"
#include "hw/cxl/cxl.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
static void cdat_len_check(CDATSubHeader *hdr, Error **errp)
{
assert(hdr->length);
assert(hdr->reserved == 0);
switch (hdr->type) {
case CDAT_TYPE_DSMAS:
assert(hdr->length == sizeof(CDATDsmas));
break;
case CDAT_TYPE_DSLBIS:
assert(hdr->length == sizeof(CDATDslbis));
break;
case CDAT_TYPE_DSMSCIS:
assert(hdr->length == sizeof(CDATDsmscis));
break;
case CDAT_TYPE_DSIS:
assert(hdr->length == sizeof(CDATDsis));
break;
case CDAT_TYPE_DSEMTS:
assert(hdr->length == sizeof(CDATDsemts));
break;
case CDAT_TYPE_SSLBIS:
assert(hdr->length >= sizeof(CDATSslbisHeader));
assert((hdr->length - sizeof(CDATSslbisHeader)) %
sizeof(CDATSslbe) == 0);
break;
default:
error_setg(errp, "Type %d is reserved", hdr->type);
}
}
static void ct3_build_cdat(CDATObject *cdat, Error **errp)
{
g_autofree CDATTableHeader *cdat_header = NULL;
g_autofree CDATEntry *cdat_st = NULL;
uint8_t sum = 0;
int ent, i;
/* Use default table if fopen == NULL */
assert(cdat->build_cdat_table);
cdat_header = g_malloc0(sizeof(*cdat_header));
if (!cdat_header) {
error_setg(errp, "Failed to allocate CDAT header");
return;
}
cdat->built_buf_len = cdat->build_cdat_table(&cdat->built_buf,
cdat->private);
if (cdat->built_buf_len <= 0) {
/* Build later as not all data available yet */
cdat->to_update = true;
return;
}
cdat->to_update = false;
cdat_st = g_malloc0(sizeof(*cdat_st) * (cdat->built_buf_len + 1));
if (!cdat_st) {
error_setg(errp, "Failed to allocate CDAT entry array");
return;
}
/* Entry 0 for CDAT header, starts with Entry 1 */
for (ent = 1; ent < cdat->built_buf_len + 1; ent++) {
CDATSubHeader *hdr = cdat->built_buf[ent - 1];
uint8_t *buf = (uint8_t *)cdat->built_buf[ent - 1];
cdat_st[ent].base = hdr;
cdat_st[ent].length = hdr->length;
cdat_header->length += hdr->length;
for (i = 0; i < hdr->length; i++) {
sum += buf[i];
}
}
/* CDAT header */
cdat_header->revision = CXL_CDAT_REV;
/* For now, no runtime updates */
cdat_header->sequence = 0;
cdat_header->length += sizeof(CDATTableHeader);
sum += cdat_header->revision + cdat_header->sequence +
cdat_header->length;
/* Sum of all bytes including checksum must be 0 */
cdat_header->checksum = ~sum + 1;
cdat_st[0].base = g_steal_pointer(&cdat_header);
cdat_st[0].length = sizeof(*cdat_header);
cdat->entry_len = 1 + cdat->built_buf_len;
cdat->entry = g_steal_pointer(&cdat_st);
}
static void ct3_load_cdat(CDATObject *cdat, Error **errp)
{
g_autofree CDATEntry *cdat_st = NULL;
g_autofree char *buf = NULL;
uint8_t sum = 0;
int num_ent;
int i = 0, ent = 1;
gsize file_size = 0;
CDATSubHeader *hdr;
GError *error = NULL;
/* Read CDAT file and create its cache */
if (!g_file_get_contents(cdat->filename, (gchar **)&buf,
&file_size, &error)) {
error_setg(errp, "CDAT: File read failed: %s", error->message);
g_error_free(error);
return;
}
if (file_size < sizeof(CDATTableHeader)) {
error_setg(errp, "CDAT: File too short");
return;
}
i = sizeof(CDATTableHeader);
num_ent = 1;
while (i < file_size) {
hdr = (CDATSubHeader *)(buf + i);
if (i + sizeof(CDATSubHeader) > file_size) {
error_setg(errp, "CDAT: Truncated table");
return;
}
cdat_len_check(hdr, errp);
i += hdr->length;
if (i > file_size) {
error_setg(errp, "CDAT: Truncated table");
return;
}
num_ent++;
}
if (i != file_size) {
error_setg(errp, "CDAT: File length mismatch");
return;
}
cdat_st = g_new0(CDATEntry, num_ent);
/* Set CDAT header, Entry = 0 */
cdat_st[0].base = buf;
cdat_st[0].length = sizeof(CDATTableHeader);
i = 0;
while (i < cdat_st[0].length) {
sum += buf[i++];
}
/* Read CDAT structures */
while (i < file_size) {
hdr = (CDATSubHeader *)(buf + i);
cdat_st[ent].base = hdr;
cdat_st[ent].length = hdr->length;
while (buf + i < (char *)cdat_st[ent].base + cdat_st[ent].length) {
assert(i < file_size);
sum += buf[i++];
}
ent++;
}
if (sum != 0) {
warn_report("CDAT: Found checksum mismatch in %s", cdat->filename);
}
cdat->entry_len = num_ent;
cdat->entry = g_steal_pointer(&cdat_st);
cdat->buf = g_steal_pointer(&buf);
}
void cxl_doe_cdat_init(CXLComponentState *cxl_cstate, Error **errp)
{
CDATObject *cdat = &cxl_cstate->cdat;
if (cdat->filename) {
ct3_load_cdat(cdat, errp);
} else {
ct3_build_cdat(cdat, errp);
}
}
void cxl_doe_cdat_update(CXLComponentState *cxl_cstate, Error **errp)
{
CDATObject *cdat = &cxl_cstate->cdat;
if (cdat->to_update) {
ct3_build_cdat(cdat, errp);
}
}
void cxl_doe_cdat_release(CXLComponentState *cxl_cstate)
{
CDATObject *cdat = &cxl_cstate->cdat;
free(cdat->entry);
if (cdat->built_buf) {
cdat->free_cdat_table(cdat->built_buf, cdat->built_buf_len,
cdat->private);
}
g_free(cdat->buf);
}