On Mon, 18 May 2026 at 08:34, Corvin Köhne <[email protected]> wrote:
>
> From: YannickV <[email protected]>
>
> 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: YannickV <[email protected]>
> ---
>  hw/misc/Kconfig         |   3 +
>  hw/misc/beckhoff_ccat.c | 339 ++++++++++++++++++++++++++++++++++++++++
>  hw/misc/meson.build     |   1 +
>  3 files changed, 343 insertions(+)
>  create mode 100644 hw/misc/beckhoff_ccat.c
>
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 1543ee6653..2217b2005b 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -257,4 +257,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..a29e9f6166
> --- /dev/null
> +++ b/hw/misc/beckhoff_ccat.c
> @@ -0,0 +1,339 @@
> +/*
> + * Beckhoff Communication Controller Emulation
> + *
> + * Copyright (c) Beckhoff Automation GmbH. & Co. KG
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/core/sysbus.h"
> +#include "qemu/units.h"
> +#include "hw/core/register.h"
> +#include "qemu/bitops.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "system/block-backend.h"
> +#include "system/address-spaces.h"
> +#include "system/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"
> +#include "migration/vmstate.h"
> +#include "qemu/bswap.h"
> +
> +#ifndef CCAT_ERR_DEBUG
> +#define CCAT_ERR_DEBUG 0
> +#endif
> +
> +#define TYPE_BECKHOFF_CCAT "beckhoff-ccat"
> +OBJECT_DECLARE_SIMPLE_TYPE(BeckhoffCcat, BECKHOFF_CCAT)
> +
> +#define MAX_NUM_SLOTS 32
> +#define CCAT_FUNCTION_BLOCK_SIZE 16
> +
> +#define CCAT_EEPROM_OFFSET 0x100
> +#define CCAT_DMA_OFFSET 0x8000
> +
> +#define CCAT_MEM_SIZE (64 * KiB)
> +#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.

"information block is"

> + * Address and size are therefor replaced by two identifiers.

"therefore"

> + * The Parameter give information about the maximal number of

"parameters give"

> + * 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;
> +    uint32_t eeprom_size;
> +} BeckhoffCcat;
> +
> +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;
> +    return ldn_le_p(&s->mem[addr], size);
> +}
> +
> +static void beckhoff_ccat_eeprom_write(void *opaque, hwaddr addr, uint64_t 
> val,
> +                              unsigned size)
> +{
> +    BeckhoffCcat *s = opaque;
> +    uint64_t eeprom_adr;
> +    uint64_t buf;
> +    uint32_t bytes_to_read;
> +
> +    switch (addr) {
> +    case EEPROM_CMD_OFFSET:
> +        eeprom_adr = ldl_le_p(&s->mem[EEPROM_ADR_OFFSET]);
> +        eeprom_adr = (eeprom_adr * 2) % s->eeprom_size;
> +        if (val & EEPROM_CMD_READ_MASK) {
> +            buf = 0;
> +            bytes_to_read = 8;
> +            if (eeprom_adr > s->eeprom_size - 8) {
> +                bytes_to_read = s->eeprom_size - eeprom_adr;
> +            }
> +            buf = ldn_le_p(s->eeprom_storage + eeprom_adr, bytes_to_read);
> +            stq_le_p(&s->mem[EEPROM_DATA_OFFSET], buf);
> +        } else if (val & EEPROM_CMD_WRITE_MASK) {
> +            buf = ldl_le_p(&s->mem[EEPROM_DATA_OFFSET]);
> +            stw_le_p((uint16_t *)(s->eeprom_storage + eeprom_adr), buf);

stw_le_p() takes a void* for the address, so you don't need the cast here.

> +            sync_eeprom(s);
> +        }
> +        break;
> +    default:
> +        stn_le_p(&s->mem[addr], size, val);
> +    }
> +}
> +
> +static uint64_t beckhoff_ccat_dma_read(void *opaque, hwaddr addr, unsigned 
> size)
> +{
> +    BeckhoffCcat *s = opaque;
> +
> +    switch (addr) {
> +    case DMA_TRANSFER_OFFSET:
> +        if (s->mem[DMA_TRANSFER_OFFSET] & 0x1) {
> +            s->mem[DMA_TRANSFER_OFFSET] = 0;

I'm guessing this is some kind of "read-to-clear" behaviour,
but it would be helpful to have a comment, especially as there
is no datasheet that readers can refer to.

> +        }
> +        break;
> +    }
> +    return ldn_le_p(&s->mem[addr], size);
> +}
> +
> +static void beckhoff_ccat_dma_write(void *opaque, hwaddr addr, uint64_t val,
> +                           unsigned size)
> +{
> +    BeckhoffCcat *s = opaque;
> +    dma_addr_t dmaAddr;

QEMU convention for variable names is lowercase, not camelCase.

> +    uint8_t len;
> +    uint8_t *mem_buf;

> +static void beckhoff_ccat_class_init(ObjectClass *klass, const void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    dc->realize = beckhoff_ccat_realize;
> +    device_class_set_legacy_reset(dc, beckhoff_ccat_reset);
> +    dc->vmsd = &vmstate_beckhoff_ccat;
> +    device_class_set_props(dc, beckhoff_ccat_properties);
> +}

It would be preferable I think to use the newer reset API
rather than device_class_set_legacy_reset(), since this is
new code. Setting rc->phases.hold is the equivalent in the
new API to the old legacy reset.

thanks
-- PMM

Reply via email to