qemu/hw/cxl/cxl-cdat.c
Ira Weiny 64fdad5e67 cxl/cdat: Fix header sum value in CDAT checksum
The addition of the DCD support for CXL type-3 devices extended the CDAT
table large enough that the checksum being returned was incorrect.[1]

This was because the checksum value was using the header length field
rather than each of the 4 bytes of the length field.  This was
previously not seen because the length of the CDAT data was less than
256 thus resulting in an equivalent checksum value.

Properly calculate the checksum for the CDAT header.

[1] https://lore.kernel.org/all/20231116-fix-cdat-devm-free-v1-1-b148b40707d7@intel.com/

Fixes: aba578bdac ("hw/cxl/cdat: CXL CDAT Data Object Exchange implementation")
Cc: Huai-Cheng Kuo <hchkuo@avery-design.com.tw>
Signed-off-by: Ira Weiny <ira.weiny@intel.com>
Reviewed-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Fan Ni <fan.ni@samsung.com>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

Message-Id: <20240126120132.24248-5-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

221 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;
uint8_t *hdr_buf;
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);
hdr_buf = (uint8_t *)cdat_header;
for (i = 0; i < sizeof(*cdat_header); i++) {
sum += hdr_buf[i];
}
/* 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);
}