[PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device

Corvin Köhne posted 14 patches 3 months ago
There is a newer version of this series
[PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device
Posted by Corvin Köhne 3 months ago
From: YannickV <Y.Vossen@beckhoff.com>

This adds the Beckhoff Communication Controller (CCAT). The information
block, EEPROM interface and DMA controller are currently  implemented.

The EEPROM provides production information for Beckhoff Devices.
An EEPORM binary must therefor be handed over. It should be aligned to
a power of two. If no EEPROM binary is handed over an empty EEPROM of
size 4096 is initialized.

This device is needed for the Beckhoff CX7200 board emulation.

Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
 hw/misc/Kconfig         |   3 +
 hw/misc/beckhoff_ccat.c | 365 ++++++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build     |   1 +
 3 files changed, 369 insertions(+)
 create mode 100644 hw/misc/beckhoff_ccat.c

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 99548e146f..f3a2efa350 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -223,4 +223,7 @@ config XLNX_VERSAL_TRNG
 config XLNX_ZYNQ_DDRC
     bool
 
+config BECKHOFF_CCAT
+    bool
+
 source macio/Kconfig
diff --git a/hw/misc/beckhoff_ccat.c b/hw/misc/beckhoff_ccat.c
new file mode 100644
index 0000000000..0e21962a98
--- /dev/null
+++ b/hw/misc/beckhoff_ccat.c
@@ -0,0 +1,365 @@
+/*
+ * Beckhoff Communication Controller Emulation
+ *
+ * Copyright (c) Beckhoff Automation GmbH. & Co. KG
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "system/block-backend.h"
+#include "exec/address-spaces.h"
+#include "exec/memory.h"
+#include "system/dma.h"
+#include "qemu/error-report.h"
+#include "block/block.h"
+#include "block/block_int.h"
+#include "block/qdict.h"
+#include "hw/block/block.h"
+
+#ifndef CCAT_ERR_DEBUG
+#define CCAT_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(level, ...) do { \
+    if (CCAT_ERR_DEBUG > (level)) { \
+        fprintf(stderr,  ": %s: ", __func__); \
+        fprintf(stderr, ## __VA_ARGS__); \
+    } \
+} while (0)
+
+#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__)
+
+#define TYPE_BECKHOFF_CCAT "beckhoff-ccat"
+#define BECKHOFF_CCAT(obj) \
+    OBJECT_CHECK(BeckhoffCcat, (obj), TYPE_BECKHOFF_CCAT)
+
+#define MAX_NUM_SLOTS 32
+
+#define CCAT_EEPROM_OFFSET 0x100
+#define CCAT_DMA_OFFSET 0x8000
+
+#define CCAT_MEM_SIZE 0xFFFF
+#define CCAT_DMA_SIZE 0x800
+#define CCAT_EEPROM_SIZE 0x20
+
+#define EEPROM_MEMORY_SIZE 0x1000
+
+#define EEPROM_CMD_OFFSET (CCAT_EEPROM_OFFSET + 0x00)
+    #define EEPROM_CMD_WRITE_MASK 0x2
+    #define EEPROM_CMD_READ_MASK 0x1
+#define EEPROM_ADR_OFFSET (CCAT_EEPROM_OFFSET + 0x04)
+#define EEPROM_DATA_OFFSET (CCAT_EEPROM_OFFSET + 0x08)
+
+#define DMA_BUFFER_OFFSET (CCAT_DMA_OFFSET + 0x00)
+#define DMA_DIRECTION_OFFSET (CCAT_DMA_OFFSET + 0x7c0)
+    #define DMA_DIRECTION_MASK 1
+#define DMA_TRANSFER_OFFSET (CCAT_DMA_OFFSET + 0x7c4)
+#define DMA_HOST_ADR_OFFSET (CCAT_DMA_OFFSET + 0x7c8)
+#define DMA_TRANSFER_LENGTH_OFFSET (CCAT_DMA_OFFSET + 0x7cc)
+
+/*
+ * The informationblock  is always located at address 0x0.
+ * Address and size are therefor replaced by two identifiers.
+ * The Parameter give information about the maximal number of
+ * function slots and the creation date (in this case 01.01.2001)
+ */
+#define CCAT_ID_1 0x88a4
+#define CCAT_ID_2 0x54414343
+#define CCAT_INFO_BLOCK_PARAMS (MAX_NUM_SLOTS << 0) | (0x1 << 8) | \
+                              (0x1 << 16) | (0x1 << 24)
+
+#define CCAT_FUN_TYPE_ENTRY 0x0001
+#define CCAT_FUN_TYPE_EEPROM 0x0012
+#define CCAT_FUN_TYPE_DMA 0x0013
+
+typedef struct BeckhoffCcat {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+
+    uint8_t mem[CCAT_MEM_SIZE];
+
+    BlockBackend *eeprom_blk;
+    uint8_t *eeprom_storage;
+    int64_t eeprom_size;
+} BeckhoffCcat;
+
+typedef struct __attribute__((packed)) CcatFunctionBlock {
+    uint16_t type;
+    uint16_t revision;
+    uint32_t parameter;
+    uint32_t address_offset;
+    uint32_t size;
+} CcatFunctionBlock;
+
+static void sync_eeprom(BeckhoffCcat *s)
+{
+    if (!s->eeprom_blk) {
+        return;
+    }
+    blk_pwrite(s->eeprom_blk, 0, s->eeprom_size, s->eeprom_storage, 0);
+}
+
+static uint64_t beckhoff_ccat_eeprom_read(void *opaque, hwaddr addr,
+                                          unsigned size)
+{
+    BeckhoffCcat *s = opaque;
+    uint64_t val = 0;
+    memcpy(&val, &s->mem[addr], size);
+    return val;
+}
+
+static void beckhoff_ccat_eeprom_write(void *opaque, hwaddr addr, uint64_t val,
+                              unsigned size)
+{
+    BeckhoffCcat *s = opaque;
+    uint64_t eeprom_adr;
+    switch (addr) {
+    case EEPROM_CMD_OFFSET:
+        eeprom_adr = *(uint32_t *)&s->mem[EEPROM_ADR_OFFSET];
+        eeprom_adr = (eeprom_adr * 2) % s->eeprom_size;
+        if (val & EEPROM_CMD_READ_MASK) {
+            uint64_t buf = 0;
+            uint32_t bytes_to_read = 8;
+            if (eeprom_adr > s->eeprom_size - 8) {
+                bytes_to_read = s->eeprom_size - eeprom_adr;
+            }
+            memcpy(&buf, s->eeprom_storage + eeprom_adr, bytes_to_read);
+            *(uint64_t *)&s->mem[EEPROM_DATA_OFFSET] = buf;
+
+        } else if (val & EEPROM_CMD_WRITE_MASK) {
+            uint32_t buf = *(uint32_t *)&s->mem[EEPROM_DATA_OFFSET];
+            memcpy(s->eeprom_storage + eeprom_adr, &buf, 2);
+            sync_eeprom(s);
+        }
+        break;
+    default:
+            memcpy(&s->mem[addr], &val, size);
+    }
+}
+
+static uint64_t beckhoff_ccat_dma_read(void *opaque, hwaddr addr, unsigned size)
+{
+    BeckhoffCcat *s = opaque;
+    uint64_t val = 0;
+
+    switch (addr) {
+    case DMA_TRANSFER_OFFSET:
+        if (s->mem[DMA_TRANSFER_OFFSET] & 0x1) {
+            DB_PRINT("DMA transfer finished\n");
+            s->mem[DMA_TRANSFER_OFFSET] = 0;
+        }
+        break;
+    }
+    memcpy(&val, &s->mem[addr], size);
+    return val;
+}
+
+static void beckhoff_ccat_dma_write(void *opaque, hwaddr addr, uint64_t val,
+                           unsigned size)
+{
+    BeckhoffCcat *s = opaque;
+    switch (addr) {
+    case DMA_TRANSFER_OFFSET:
+        uint8_t len = s->mem[DMA_TRANSFER_LENGTH_OFFSET];
+        uint8_t *mem_buf = &s->mem[DMA_BUFFER_OFFSET];
+
+        if (s->mem[DMA_DIRECTION_OFFSET] & DMA_DIRECTION_MASK) {
+            dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];
+            dma_memory_read(&address_space_memory, dmaAddr,
+                            mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);
+        } else {
+            dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];
+            dma_memory_write(&address_space_memory, dmaAddr + 8,
+                                mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);
+        }
+        break;
+    }
+    memcpy(&s->mem[addr], &val, size);
+}
+static uint64_t beckhoff_ccat_read(void *opaque, hwaddr addr, unsigned size)
+{
+    DB_PRINT("CCAT_READ addr=0x%lx size=%u\n", addr, size);
+
+    BeckhoffCcat *s = opaque;
+    uint64_t val = 0;
+
+    if (addr > CCAT_MEM_SIZE - size) {
+        error_report("Overflow. Address or size is too large.\n");
+        exit(1);
+    }
+
+    if (addr >= CCAT_EEPROM_OFFSET &&
+                        addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
+        return beckhoff_ccat_eeprom_read(opaque, addr, size);
+    } else if (addr >= CCAT_DMA_OFFSET &&
+                        addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
+        return beckhoff_ccat_dma_read(opaque, addr, size);
+    } else {
+        memcpy(&val, &s->mem[addr], size);
+    }
+
+    return val;
+}
+
+static void beckhoff_ccat_write(void *opaque, hwaddr addr, uint64_t val,
+                                unsigned size)
+{
+    DB_PRINT("CCAT_WRITE addr=0x%lx size=%u val=0x%lx\n", addr, size, val);
+
+    BeckhoffCcat *s = opaque;
+
+    if (addr > CCAT_MEM_SIZE - size) {
+        error_report("Overflow. Address or size is too large.\n");
+        exit(1);
+    }
+
+    if (addr >= CCAT_EEPROM_OFFSET &&
+                        addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
+        beckhoff_ccat_eeprom_write(opaque, addr, val, size);
+    } else if (addr >= CCAT_DMA_OFFSET &&
+                        addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
+        beckhoff_ccat_dma_write(opaque, addr, val, size);
+    } else {
+        memcpy(&s->mem[addr], &val, size);
+    }
+}
+
+static const MemoryRegionOps beckhoff_ccat_ops = {
+    .read = beckhoff_ccat_read,
+    .write = beckhoff_ccat_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+};
+
+
+static void beckhoff_ccat_reset(DeviceState *dev)
+{
+    BeckhoffCcat *s = BECKHOFF_CCAT(dev);
+
+    CcatFunctionBlock function_blocks[MAX_NUM_SLOTS] = {0};
+
+    CcatFunctionBlock info_block = {
+        .type = CCAT_FUN_TYPE_ENTRY,
+        .revision = 0x0001,
+        .parameter = CCAT_INFO_BLOCK_PARAMS,
+        .address_offset = CCAT_ID_1,
+        .size = CCAT_ID_2
+    };
+    CcatFunctionBlock eeprom_block = {
+        .type = CCAT_FUN_TYPE_EEPROM,
+        .revision = 0x0001,
+        .parameter = 0,
+        .address_offset = CCAT_EEPROM_OFFSET,
+        .size = CCAT_EEPROM_SIZE
+    };
+    CcatFunctionBlock dma_block = {
+        .type = CCAT_FUN_TYPE_DMA,
+        .revision = 0x0000,
+        .parameter = 0,
+        .address_offset = CCAT_DMA_OFFSET,
+        .size = CCAT_DMA_SIZE
+    };
+
+    /*
+     * The EEPROM interface is usually at function slot 11.
+     * The DMA controller is usually at function slot 15.
+     */
+    function_blocks[0] = info_block;
+    function_blocks[11] = eeprom_block;
+    function_blocks[15] = dma_block;
+
+    memcpy(&s->mem[0], function_blocks, sizeof(function_blocks));
+}
+
+static void beckhoff_ccat_realize(DeviceState *dev, Error **errp)
+{
+    BeckhoffCcat *s = BECKHOFF_CCAT(dev);
+    BlockBackend *blk;
+
+    blk = blk_by_name("ccat-eeprom");
+
+    if (blk) {
+        uint64_t blk_size = blk_getlength(blk);
+        if (!is_power_of_2(blk_size)) {
+            error_report("Blockend size is not a power of two.");
+        }
+
+        if (blk_size < 512) {
+            error_report("Blockend size is too small. Using backup.");
+            s->eeprom_size = EEPROM_MEMORY_SIZE;
+            s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
+            memset(s->eeprom_storage, 0x00, s->eeprom_size);
+        } else {
+            DB_PRINT("EEPROM block backend found\n");
+            blk_set_perm(blk, BLK_PERM_WRITE, BLK_PERM_ALL, errp);
+
+            s->eeprom_size = blk_size;
+            s->eeprom_blk = blk;
+            s->eeprom_storage = blk_blockalign(s->eeprom_blk, s->eeprom_size);
+
+            if (!blk_check_size_and_read_all(s->eeprom_blk, DEVICE(s),
+                                             s->eeprom_storage, s->eeprom_size,
+                                             errp)) {
+                exit(1);
+            }
+        }
+    } else {
+        s->eeprom_size = EEPROM_MEMORY_SIZE;
+        s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
+        memset(s->eeprom_storage, 0x00, s->eeprom_size);
+    }
+
+    beckhoff_ccat_reset(dev);
+}
+
+static void beckhoff_ccat_init(Object *obj)
+{
+    BeckhoffCcat *s = BECKHOFF_CCAT(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    memory_region_init_io(&s->iomem, obj, &beckhoff_ccat_ops, s,
+                          TYPE_BECKHOFF_CCAT, CCAT_MEM_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void beckhoff_ccat_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    dc->realize = beckhoff_ccat_realize;
+}
+
+static const TypeInfo beckhoff_ccat_info = {
+ .name = TYPE_BECKHOFF_CCAT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BeckhoffCcat),
+ .class_init = beckhoff_ccat_class_init,
+ .instance_init = beckhoff_ccat_init,
+};
+
+static void beckhoff_ccat_register_types(void)
+{
+    type_register_static(&beckhoff_ccat_info);
+}
+
+type_init(beckhoff_ccat_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 6ee7b6c71d..1fc1468464 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -14,6 +14,7 @@ system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c'))
 system_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_debug.c'))
 system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
 system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
+system_ss.add(when: 'CONFIG_BECKHOFF_CCAT', if_true: files('beckhoff_ccat.c'))
 
 system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
 
-- 
2.50.1


Re: [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device
Posted by Peter Maydell 2 months, 3 weeks ago
On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
>
> From: YannickV <Y.Vossen@beckhoff.com>
>
> This adds the Beckhoff Communication Controller (CCAT). The information
> block, EEPROM interface and DMA controller are currently  implemented.
>
> The EEPROM provides production information for Beckhoff Devices.
> An EEPORM binary must therefor be handed over. It should be aligned to
> a power of two. If no EEPROM binary is handed over an empty EEPROM of
> size 4096 is initialized.
>
> This device is needed for the Beckhoff CX7200 board emulation.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
> ---
>  hw/misc/Kconfig         |   3 +
>  hw/misc/beckhoff_ccat.c | 365 ++++++++++++++++++++++++++++++++++++++++
>  hw/misc/meson.build     |   1 +
>  3 files changed, 369 insertions(+)
>  create mode 100644 hw/misc/beckhoff_ccat.c
>
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 99548e146f..f3a2efa350 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -223,4 +223,7 @@ config XLNX_VERSAL_TRNG
>  config XLNX_ZYNQ_DDRC
>      bool
>
> +config BECKHOFF_CCAT
> +    bool
> +
>  source macio/Kconfig
> diff --git a/hw/misc/beckhoff_ccat.c b/hw/misc/beckhoff_ccat.c
> new file mode 100644
> index 0000000000..0e21962a98
> --- /dev/null
> +++ b/hw/misc/beckhoff_ccat.c
> @@ -0,0 +1,365 @@
> +/*
> + * Beckhoff Communication Controller Emulation
> + *
> + * Copyright (c) Beckhoff Automation GmbH. & Co. KG
> + *
> + * 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, see <http://www.gnu.org/licenses/>.

This needs an SPDX license identifier line. If you have that,
then you don't need the human-readable license blurb.

Is there a datasheet/manual for this device? The comment
at the top of the file is a good place to put the URL to it.

> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/sysbus.h"
> +#include "hw/register.h"
> +#include "qemu/bitops.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "system/block-backend.h"
> +#include "exec/address-spaces.h"
> +#include "exec/memory.h"
> +#include "system/dma.h"
> +#include "qemu/error-report.h"
> +#include "block/block.h"
> +#include "block/block_int.h"
> +#include "block/qdict.h"
> +#include "hw/block/block.h"
> +
> +#ifndef CCAT_ERR_DEBUG
> +#define CCAT_ERR_DEBUG 0
> +#endif
> +
> +#define DB_PRINT_L(level, ...) do { \
> +    if (CCAT_ERR_DEBUG > (level)) { \
> +        fprintf(stderr,  ": %s: ", __func__); \
> +        fprintf(stderr, ## __VA_ARGS__); \
> +    } \
> +} while (0)

No debug printf macros in new code, please. Use either:
 * tracepoints, for "this is information that might be
   interesting for somebody debugging the device or code
   that runs on the device"
 * qemu_log_mask(LOG_UNIMP, ...), for "the guest tried
   to use a feature that we don't implement"
 * qemu_log_mask(LOG_GUEST_ERROR, ...), for "the guest
   tried to do something that the datasheet says is
   undefined behaviour/reserved/unpredictable"

> +
> +#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__)
> +
> +#define TYPE_BECKHOFF_CCAT "beckhoff-ccat"
> +#define BECKHOFF_CCAT(obj) \
> +    OBJECT_CHECK(BeckhoffCcat, (obj), TYPE_BECKHOFF_CCAT)

Prefer the new-style OBJECT_DECLARE_SIMPLE_TYPE etc
macros, rather than defining the cast macro yourself.

> +
> +#define MAX_NUM_SLOTS 32
> +
> +#define CCAT_EEPROM_OFFSET 0x100
> +#define CCAT_DMA_OFFSET 0x8000
> +
> +#define CCAT_MEM_SIZE 0xFFFF
> +#define CCAT_DMA_SIZE 0x800
> +#define CCAT_EEPROM_SIZE 0x20
> +
> +#define EEPROM_MEMORY_SIZE 0x1000
> +
> +#define EEPROM_CMD_OFFSET (CCAT_EEPROM_OFFSET + 0x00)
> +    #define EEPROM_CMD_WRITE_MASK 0x2
> +    #define EEPROM_CMD_READ_MASK 0x1
> +#define EEPROM_ADR_OFFSET (CCAT_EEPROM_OFFSET + 0x04)
> +#define EEPROM_DATA_OFFSET (CCAT_EEPROM_OFFSET + 0x08)
> +
> +#define DMA_BUFFER_OFFSET (CCAT_DMA_OFFSET + 0x00)
> +#define DMA_DIRECTION_OFFSET (CCAT_DMA_OFFSET + 0x7c0)
> +    #define DMA_DIRECTION_MASK 1
> +#define DMA_TRANSFER_OFFSET (CCAT_DMA_OFFSET + 0x7c4)
> +#define DMA_HOST_ADR_OFFSET (CCAT_DMA_OFFSET + 0x7c8)
> +#define DMA_TRANSFER_LENGTH_OFFSET (CCAT_DMA_OFFSET + 0x7cc)
> +
> +/*
> + * The informationblock  is always located at address 0x0.
> + * Address and size are therefor replaced by two identifiers.
> + * The Parameter give information about the maximal number of
> + * function slots and the creation date (in this case 01.01.2001)
> + */
> +#define CCAT_ID_1 0x88a4
> +#define CCAT_ID_2 0x54414343
> +#define CCAT_INFO_BLOCK_PARAMS (MAX_NUM_SLOTS << 0) | (0x1 << 8) | \
> +                              (0x1 << 16) | (0x1 << 24)
> +
> +#define CCAT_FUN_TYPE_ENTRY 0x0001
> +#define CCAT_FUN_TYPE_EEPROM 0x0012
> +#define CCAT_FUN_TYPE_DMA 0x0013
> +
> +typedef struct BeckhoffCcat {
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion iomem;
> +
> +    uint8_t mem[CCAT_MEM_SIZE];
> +
> +    BlockBackend *eeprom_blk;
> +    uint8_t *eeprom_storage;
> +    int64_t eeprom_size;
> +} BeckhoffCcat;
> +
> +typedef struct __attribute__((packed)) CcatFunctionBlock {
> +    uint16_t type;
> +    uint16_t revision;
> +    uint32_t parameter;
> +    uint32_t address_offset;
> +    uint32_t size;
> +} CcatFunctionBlock;

"attribute((packed))" is a bit of a red flag of doing something
wrong. In this case, you use this in beckhoff_ccat_reset() to
set up a host C structure that you memcpy() into s->mem[].
That is not portable to big-endian systems.

> +
> +static void sync_eeprom(BeckhoffCcat *s)
> +{
> +    if (!s->eeprom_blk) {
> +        return;
> +    }
> +    blk_pwrite(s->eeprom_blk, 0, s->eeprom_size, s->eeprom_storage, 0);
> +}
> +
> +static uint64_t beckhoff_ccat_eeprom_read(void *opaque, hwaddr addr,
> +                                          unsigned size)
> +{
> +    BeckhoffCcat *s = opaque;
> +    uint64_t val = 0;
> +    memcpy(&val, &s->mem[addr], size);

What is this trying to do ? If you want to read
N bytes of data in little endian order from a host
address, use ldn_le_p(). (Also available in "big endian"
and "host endianness" flavours.)

> +    return val;
> +}
> +
> +static void beckhoff_ccat_eeprom_write(void *opaque, hwaddr addr, uint64_t val,
> +                              unsigned size)
> +{
> +    BeckhoffCcat *s = opaque;
> +    uint64_t eeprom_adr;
> +    switch (addr) {
> +    case EEPROM_CMD_OFFSET:
> +        eeprom_adr = *(uint32_t *)&s->mem[EEPROM_ADR_OFFSET];
> +        eeprom_adr = (eeprom_adr * 2) % s->eeprom_size;
> +        if (val & EEPROM_CMD_READ_MASK) {
> +            uint64_t buf = 0;
> +            uint32_t bytes_to_read = 8;
> +            if (eeprom_adr > s->eeprom_size - 8) {
> +                bytes_to_read = s->eeprom_size - eeprom_adr;
> +            }
> +            memcpy(&buf, s->eeprom_storage + eeprom_adr, bytes_to_read);
> +            *(uint64_t *)&s->mem[EEPROM_DATA_OFFSET] = buf;

All this code with casting to different pointer types and
using memcpy() looks very suspicious. Use the relevant
ld*_*_p() functions to load the right sized values at the
right endianness. (Casting from one pointer type to another
is usually an indication that you're going down a wrong path.)

> +
> +        } else if (val & EEPROM_CMD_WRITE_MASK) {
> +            uint32_t buf = *(uint32_t *)&s->mem[EEPROM_DATA_OFFSET];
> +            memcpy(s->eeprom_storage + eeprom_adr, &buf, 2);
> +            sync_eeprom(s);
> +        }
> +        break;
> +    default:
> +            memcpy(&s->mem[addr], &val, size);
> +    }
> +}
> +
> +static uint64_t beckhoff_ccat_dma_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    BeckhoffCcat *s = opaque;
> +    uint64_t val = 0;
> +
> +    switch (addr) {
> +    case DMA_TRANSFER_OFFSET:
> +        if (s->mem[DMA_TRANSFER_OFFSET] & 0x1) {
> +            DB_PRINT("DMA transfer finished\n");
> +            s->mem[DMA_TRANSFER_OFFSET] = 0;
> +        }
> +        break;
> +    }
> +    memcpy(&val, &s->mem[addr], size);
> +    return val;
> +}
> +
> +static void beckhoff_ccat_dma_write(void *opaque, hwaddr addr, uint64_t val,
> +                           unsigned size)
> +{
> +    BeckhoffCcat *s = opaque;
> +    switch (addr) {
> +    case DMA_TRANSFER_OFFSET:
> +        uint8_t len = s->mem[DMA_TRANSFER_LENGTH_OFFSET];
> +        uint8_t *mem_buf = &s->mem[DMA_BUFFER_OFFSET];
> +
> +        if (s->mem[DMA_DIRECTION_OFFSET] & DMA_DIRECTION_MASK) {
> +            dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];

Should probably be ldl_le_p().

> +            dma_memory_read(&address_space_memory, dmaAddr,
> +                            mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);
> +        } else {
> +            dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];
> +            dma_memory_write(&address_space_memory, dmaAddr + 8,
> +                                mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);

Does this really use addr for reads and addr + 8 for writes ?

> +        }
> +        break;
> +    }
> +    memcpy(&s->mem[addr], &val, size);
> +}
> +static uint64_t beckhoff_ccat_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    DB_PRINT("CCAT_READ addr=0x%lx size=%u\n", addr, size);
> +
> +    BeckhoffCcat *s = opaque;
> +    uint64_t val = 0;
> +
> +    if (addr > CCAT_MEM_SIZE - size) {
> +        error_report("Overflow. Address or size is too large.\n");
> +        exit(1);

Don't error_report() and exit for things like this.
Either:
 (a) this is not possible unless there's a bug in QEMU:
     in that case, use assert()
 (b) this is possible if the guest does something silly:
     in that case use qemu_log_mask(LOG_GUEST_ERROR, ...)

In this case it looks like an assert() is appropriate, because
the memory_region_init_io() size argument should ensure that
you can't get here with an addr + size that is beyond the
end of the block.

> +    }
> +
> +    if (addr >= CCAT_EEPROM_OFFSET &&
> +                        addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
> +        return beckhoff_ccat_eeprom_read(opaque, addr, size);
> +    } else if (addr >= CCAT_DMA_OFFSET &&
> +                        addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
> +        return beckhoff_ccat_dma_read(opaque, addr, size);
> +    } else {
> +        memcpy(&val, &s->mem[addr], size);
> +    }
> +
> +    return val;
> +}
> +
> +static void beckhoff_ccat_write(void *opaque, hwaddr addr, uint64_t val,
> +                                unsigned size)
> +{
> +    DB_PRINT("CCAT_WRITE addr=0x%lx size=%u val=0x%lx\n", addr, size, val);
> +
> +    BeckhoffCcat *s = opaque;
> +
> +    if (addr > CCAT_MEM_SIZE - size) {
> +        error_report("Overflow. Address or size is too large.\n");
> +        exit(1);
> +    }
> +
> +    if (addr >= CCAT_EEPROM_OFFSET &&
> +                        addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
> +        beckhoff_ccat_eeprom_write(opaque, addr, val, size);
> +    } else if (addr >= CCAT_DMA_OFFSET &&
> +                        addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
> +        beckhoff_ccat_dma_write(opaque, addr, val, size);
> +    } else {
> +        memcpy(&s->mem[addr], &val, size);
> +    }
> +}
> +
> +static const MemoryRegionOps beckhoff_ccat_ops = {
> +    .read = beckhoff_ccat_read,
> +    .write = beckhoff_ccat_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 8,
> +    },
> +};
> +
> +
> +static void beckhoff_ccat_reset(DeviceState *dev)
> +{
> +    BeckhoffCcat *s = BECKHOFF_CCAT(dev);
> +
> +    CcatFunctionBlock function_blocks[MAX_NUM_SLOTS] = {0};
> +
> +    CcatFunctionBlock info_block = {
> +        .type = CCAT_FUN_TYPE_ENTRY,
> +        .revision = 0x0001,
> +        .parameter = CCAT_INFO_BLOCK_PARAMS,
> +        .address_offset = CCAT_ID_1,
> +        .size = CCAT_ID_2
> +    };
> +    CcatFunctionBlock eeprom_block = {
> +        .type = CCAT_FUN_TYPE_EEPROM,
> +        .revision = 0x0001,
> +        .parameter = 0,
> +        .address_offset = CCAT_EEPROM_OFFSET,
> +        .size = CCAT_EEPROM_SIZE
> +    };
> +    CcatFunctionBlock dma_block = {
> +        .type = CCAT_FUN_TYPE_DMA,
> +        .revision = 0x0000,
> +        .parameter = 0,
> +        .address_offset = CCAT_DMA_OFFSET,
> +        .size = CCAT_DMA_SIZE
> +    };
> +
> +    /*
> +     * The EEPROM interface is usually at function slot 11.
> +     * The DMA controller is usually at function slot 15.
> +     */
> +    function_blocks[0] = info_block;
> +    function_blocks[11] = eeprom_block;
> +    function_blocks[15] = dma_block;
> +
> +    memcpy(&s->mem[0], function_blocks, sizeof(function_blocks));

This isn't going to work on big-endian hosts, as noted above.

> +}
> +
> +static void beckhoff_ccat_realize(DeviceState *dev, Error **errp)
> +{
> +    BeckhoffCcat *s = BECKHOFF_CCAT(dev);
> +    BlockBackend *blk;
> +
> +    blk = blk_by_name("ccat-eeprom");

No other device calls blk_by_name() to get its block backend:
you should not do so either. Use a qdev property and have
the SoC/board model set it.

> +
> +    if (blk) {
> +        uint64_t blk_size = blk_getlength(blk);
> +        if (!is_power_of_2(blk_size)) {
> +            error_report("Blockend size is not a power of two.");

In a realize function you have an Error** argument, so you
should report errors via error_setg() + return.

> +        }
> +
> +        if (blk_size < 512) {
> +            error_report("Blockend size is too small. Using backup.");
> +            s->eeprom_size = EEPROM_MEMORY_SIZE;
> +            s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
> +            memset(s->eeprom_storage, 0x00, s->eeprom_size);

We don't try to fix things up like this in other block-backed
devices, I think, so we shouldn't do it here either.

> +        } else {
> +            DB_PRINT("EEPROM block backend found\n");
> +            blk_set_perm(blk, BLK_PERM_WRITE, BLK_PERM_ALL, errp);
> +
> +            s->eeprom_size = blk_size;
> +            s->eeprom_blk = blk;
> +            s->eeprom_storage = blk_blockalign(s->eeprom_blk, s->eeprom_size);
> +
> +            if (!blk_check_size_and_read_all(s->eeprom_blk, DEVICE(s),
> +                                             s->eeprom_storage, s->eeprom_size,
> +                                             errp)) {
> +                exit(1);

Don't silently exit(): use 'return', to report the error
to your caller.

> +            }
> +        }
> +    } else {
> +        s->eeprom_size = EEPROM_MEMORY_SIZE;
> +        s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
> +        memset(s->eeprom_storage, 0x00, s->eeprom_size);
> +    }
> +
> +    beckhoff_ccat_reset(dev);
> +}
> +
> +static void beckhoff_ccat_init(Object *obj)
> +{
> +    BeckhoffCcat *s = BECKHOFF_CCAT(obj);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> +
> +    memory_region_init_io(&s->iomem, obj, &beckhoff_ccat_ops, s,
> +                          TYPE_BECKHOFF_CCAT, CCAT_MEM_SIZE);
> +    sysbus_init_mmio(sbd, &s->iomem);
> +}
> +
> +static void beckhoff_ccat_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    dc->realize = beckhoff_ccat_realize;

The guest can read and write mem[], so I think you need
a reset handler, and also a VMState for migration.

> +}

thanks
-- PMM
Re: [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device
Posted by Corvin Köhne 1 month ago
On Tue, 2025-08-19 at 17:03 +0100, Peter Maydell wrote:
> Is there a datasheet/manual for this device? The comment
> at the top of the file is a good place to put the URL to it.

Unfortunately, there's no publicly available manual so far. We're currently
trying to convince people to publish one. We're going to add the URL to the
comment when on is available.


Best regards,
Corvin