On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koe...@gmail.com> wrote: > > From: YannickV <y.vos...@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.vos...@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