/* * CXL Utility library for devices * * Copyright(C) 2020 Intel Corporation. * * This work is licensed under the terms of the GNU GPL, version 2. See the * COPYING file in the top-level directory. */ #include "qemu/osdep.h" #include "qemu/log.h" #include "hw/cxl/cxl.h" /* * Device registers have no restrictions per the spec, and so fall back to the * default memory mapped register rules in 8.2: * Software shall use CXL.io Memory Read and Write to access memory mapped * register defined in this section. Unless otherwise specified, software * shall restrict the accesses width based on the following: * • A 32 bit register shall be accessed as a 1 Byte, 2 Bytes or 4 Bytes * quantity. * • A 64 bit register shall be accessed as a 1 Byte, 2 Bytes, 4 Bytes or 8 * Bytes * • The address shall be a multiple of the access width, e.g. when * accessing a register as a 4 Byte quantity, the address shall be * multiple of 4. * • The accesses shall map to contiguous bytes.If these rules are not * followed, the behavior is undefined */ static uint64_t caps_reg_read(void *opaque, hwaddr offset, unsigned size) { CXLDeviceState *cxl_dstate = opaque; switch (size) { case 4: return cxl_dstate->caps_reg_state32[offset / size]; case 8: return cxl_dstate->caps_reg_state64[offset / size]; default: g_assert_not_reached(); } } static uint64_t dev_reg_read(void *opaque, hwaddr offset, unsigned size) { CXLDeviceState *cxl_dstate = opaque; switch (size) { case 1: return cxl_dstate->dev_reg_state[offset]; case 2: return cxl_dstate->dev_reg_state16[offset / size]; case 4: return cxl_dstate->dev_reg_state32[offset / size]; case 8: return cxl_dstate->dev_reg_state64[offset / size]; default: g_assert_not_reached(); } } static uint64_t mailbox_reg_read(void *opaque, hwaddr offset, unsigned size) { CXLDeviceState *cxl_dstate; CXLCCI *cci = opaque; if (object_dynamic_cast(OBJECT(cci->intf), TYPE_CXL_TYPE3)) { cxl_dstate = &CXL_TYPE3(cci->intf)->cxl_dstate; } else if (object_dynamic_cast(OBJECT(cci->intf), TYPE_CXL_SWITCH_MAILBOX_CCI)) { cxl_dstate = &CXL_SWITCH_MAILBOX_CCI(cci->intf)->cxl_dstate; } else { return 0; } switch (size) { case 1: return cxl_dstate->mbox_reg_state[offset]; case 2: return cxl_dstate->mbox_reg_state16[offset / size]; case 4: return cxl_dstate->mbox_reg_state32[offset / size]; case 8: if (offset == A_CXL_DEV_BG_CMD_STS) { uint64_t bg_status_reg; bg_status_reg = FIELD_DP64(0, CXL_DEV_BG_CMD_STS, OP, cci->bg.opcode); bg_status_reg = FIELD_DP64(bg_status_reg, CXL_DEV_BG_CMD_STS, PERCENTAGE_COMP, cci->bg.complete_pct); bg_status_reg = FIELD_DP64(bg_status_reg, CXL_DEV_BG_CMD_STS, RET_CODE, cci->bg.ret_code); /* endian? */ cxl_dstate->mbox_reg_state64[offset / size] = bg_status_reg; } if (offset == A_CXL_DEV_MAILBOX_STS) { uint64_t status_reg = cxl_dstate->mbox_reg_state64[offset / size]; if (cci->bg.complete_pct) { status_reg = FIELD_DP64(status_reg, CXL_DEV_MAILBOX_STS, BG_OP, 0); cxl_dstate->mbox_reg_state64[offset / size] = status_reg; } } return cxl_dstate->mbox_reg_state64[offset / size]; default: g_assert_not_reached(); } } static void mailbox_mem_writel(uint32_t *reg_state, hwaddr offset, uint64_t value) { switch (offset) { case A_CXL_DEV_MAILBOX_CTRL: /* fallthrough */ case A_CXL_DEV_MAILBOX_CAP: /* RO register */ break; default: qemu_log_mask(LOG_UNIMP, "%s Unexpected 32-bit access to 0x%" PRIx64 " (WI)\n", __func__, offset); return; } reg_state[offset / sizeof(*reg_state)] = value; } static void mailbox_mem_writeq(uint64_t *reg_state, hwaddr offset, uint64_t value) { switch (offset) { case A_CXL_DEV_MAILBOX_CMD: break; case A_CXL_DEV_BG_CMD_STS: break; case A_CXL_DEV_MAILBOX_STS: /* Read only register, will get updated by the state machine */ return; default: qemu_log_mask(LOG_UNIMP, "%s Unexpected 64-bit access to 0x%" PRIx64 " (WI)\n", __func__, offset); return; } reg_state[offset / sizeof(*reg_state)] = value; } static void mailbox_reg_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { CXLDeviceState *cxl_dstate; CXLCCI *cci = opaque; if (object_dynamic_cast(OBJECT(cci->intf), TYPE_CXL_TYPE3)) { cxl_dstate = &CXL_TYPE3(cci->intf)->cxl_dstate; } else if (object_dynamic_cast(OBJECT(cci->intf), TYPE_CXL_SWITCH_MAILBOX_CCI)) { cxl_dstate = &CXL_SWITCH_MAILBOX_CCI(cci->intf)->cxl_dstate; } else { return; } if (offset >= A_CXL_DEV_CMD_PAYLOAD) { memcpy(cxl_dstate->mbox_reg_state + offset, &value, size); return; } switch (size) { case 4: mailbox_mem_writel(cxl_dstate->mbox_reg_state32, offset, value); break; case 8: mailbox_mem_writeq(cxl_dstate->mbox_reg_state64, offset, value); break; default: g_assert_not_reached(); } if (ARRAY_FIELD_EX32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CTRL, DOORBELL)) { uint64_t command_reg = cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD]; uint8_t cmd_set = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND_SET); uint8_t cmd = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND); size_t len_in = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, LENGTH); uint8_t *pl = cxl_dstate->mbox_reg_state + A_CXL_DEV_CMD_PAYLOAD; /* * Copy taken to avoid need for individual command handlers to care * about aliasing. */ g_autofree uint8_t *pl_in_copy = NULL; size_t len_out = 0; uint64_t status_reg; bool bg_started = false; int rc; pl_in_copy = g_memdup2(pl, len_in); if (len_in == 0 || pl_in_copy) { /* Avoid stale data - including from earlier cmds */ memset(pl, 0, CXL_MAILBOX_MAX_PAYLOAD_SIZE); rc = cxl_process_cci_message(cci, cmd_set, cmd, len_in, pl_in_copy, &len_out, pl, &bg_started); } else { rc = CXL_MBOX_INTERNAL_ERROR; } /* Set bg and the return code */ status_reg = FIELD_DP64(0, CXL_DEV_MAILBOX_STS, BG_OP, bg_started ? 1 : 0); status_reg = FIELD_DP64(status_reg, CXL_DEV_MAILBOX_STS, ERRNO, rc); /* Set the return length */ command_reg = FIELD_DP64(0, CXL_DEV_MAILBOX_CMD, COMMAND_SET, cmd_set); command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND, cmd); command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, LENGTH, len_out); cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD] = command_reg; cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_STS] = status_reg; /* Tell the host we're done */ ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CTRL, DOORBELL, 0); } } static uint64_t mdev_reg_read(void *opaque, hwaddr offset, unsigned size) { uint64_t retval = 0; retval = FIELD_DP64(retval, CXL_MEM_DEV_STS, MEDIA_STATUS, 1); retval = FIELD_DP64(retval, CXL_MEM_DEV_STS, MBOX_READY, 1); return retval; } static void ro_reg_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { /* Many register sets are read only */ } static const MemoryRegionOps mdev_ops = { .read = mdev_reg_read, .write = ro_reg_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 8, .unaligned = false, }, .impl = { .min_access_size = 8, .max_access_size = 8, }, }; static const MemoryRegionOps mailbox_ops = { .read = mailbox_reg_read, .write = mailbox_reg_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 8, .unaligned = false, }, .impl = { .min_access_size = 1, .max_access_size = 8, }, }; static const MemoryRegionOps dev_ops = { .read = dev_reg_read, .write = ro_reg_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 8, .unaligned = false, }, .impl = { .min_access_size = 1, .max_access_size = 8, }, }; static const MemoryRegionOps caps_ops = { .read = caps_reg_read, .write = ro_reg_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 1, .max_access_size = 8, .unaligned = false, }, .impl = { .min_access_size = 4, .max_access_size = 8, }, }; void cxl_device_register_block_init(Object *obj, CXLDeviceState *cxl_dstate, CXLCCI *cci) { /* This will be a BAR, so needs to be rounded up to pow2 for PCI spec */ memory_region_init(&cxl_dstate->device_registers, obj, "device-registers", pow2ceil(CXL_MMIO_SIZE)); memory_region_init_io(&cxl_dstate->caps, obj, &caps_ops, cxl_dstate, "cap-array", CXL_CAPS_SIZE); memory_region_init_io(&cxl_dstate->device, obj, &dev_ops, cxl_dstate, "device-status", CXL_DEVICE_STATUS_REGISTERS_LENGTH); memory_region_init_io(&cxl_dstate->mailbox, obj, &mailbox_ops, cci, "mailbox", CXL_MAILBOX_REGISTERS_LENGTH); memory_region_init_io(&cxl_dstate->memory_device, obj, &mdev_ops, cxl_dstate, "memory device caps", CXL_MEMORY_DEVICE_REGISTERS_LENGTH); memory_region_add_subregion(&cxl_dstate->device_registers, 0, &cxl_dstate->caps); memory_region_add_subregion(&cxl_dstate->device_registers, CXL_DEVICE_STATUS_REGISTERS_OFFSET, &cxl_dstate->device); memory_region_add_subregion(&cxl_dstate->device_registers, CXL_MAILBOX_REGISTERS_OFFSET, &cxl_dstate->mailbox); memory_region_add_subregion(&cxl_dstate->device_registers, CXL_MEMORY_DEVICE_REGISTERS_OFFSET, &cxl_dstate->memory_device); } void cxl_event_set_status(CXLDeviceState *cxl_dstate, CXLEventLogType log_type, bool available) { if (available) { cxl_dstate->event_status |= (1 << log_type); } else { cxl_dstate->event_status &= ~(1 << log_type); } ARRAY_FIELD_DP64(cxl_dstate->dev_reg_state64, CXL_DEV_EVENT_STATUS, EVENT_STATUS, cxl_dstate->event_status); } static void device_reg_init_common(CXLDeviceState *cxl_dstate) { CXLEventLogType log; for (log = 0; log < CXL_EVENT_TYPE_MAX; log++) { cxl_event_set_status(cxl_dstate, log, false); } } static void mailbox_reg_init_common(CXLDeviceState *cxl_dstate) { const uint8_t msi_n = 9; /* 2048 payload size */ ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CAP, PAYLOAD_SIZE, CXL_MAILBOX_PAYLOAD_SHIFT); cxl_dstate->payload_size = CXL_MAILBOX_MAX_PAYLOAD_SIZE; /* irq support */ ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CAP, BG_INT_CAP, 1); ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CAP, MSI_N, msi_n); cxl_dstate->mbox_msi_n = msi_n; } static void memdev_reg_init_common(CXLDeviceState *cxl_dstate) { } void cxl_device_register_init_t3(CXLType3Dev *ct3d) { CXLDeviceState *cxl_dstate = &ct3d->cxl_dstate; uint64_t *cap_h = cxl_dstate->caps_reg_state64; const int cap_count = 3; /* CXL Device Capabilities Array Register */ ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_ID, 0); ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_VERSION, 1); ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_COUNT, cap_count); cxl_device_cap_init(cxl_dstate, DEVICE_STATUS, 1, 2); device_reg_init_common(cxl_dstate); cxl_device_cap_init(cxl_dstate, MAILBOX, 2, 1); mailbox_reg_init_common(cxl_dstate); cxl_device_cap_init(cxl_dstate, MEMORY_DEVICE, 0x4000, 1); memdev_reg_init_common(cxl_dstate); cxl_initialize_mailbox_t3(&ct3d->cci, DEVICE(ct3d), CXL_MAILBOX_MAX_PAYLOAD_SIZE); } void cxl_device_register_init_swcci(CSWMBCCIDev *sw) { CXLDeviceState *cxl_dstate = &sw->cxl_dstate; uint64_t *cap_h = cxl_dstate->caps_reg_state64; const int cap_count = 3; /* CXL Device Capabilities Array Register */ ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_ID, 0); ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_VERSION, 1); ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_COUNT, cap_count); cxl_device_cap_init(cxl_dstate, DEVICE_STATUS, 1, 2); device_reg_init_common(cxl_dstate); cxl_device_cap_init(cxl_dstate, MAILBOX, 2, 1); mailbox_reg_init_common(cxl_dstate); cxl_device_cap_init(cxl_dstate, MEMORY_DEVICE, 0x4000, 1); memdev_reg_init_common(cxl_dstate); } uint64_t cxl_device_get_timestamp(CXLDeviceState *cxl_dstate) { uint64_t time, delta; uint64_t final_time = 0; if (cxl_dstate->timestamp.set) { /* Find the delta from the last time the host set the time. */ time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); delta = time - cxl_dstate->timestamp.last_set; final_time = cxl_dstate->timestamp.host_set + delta; } return final_time; }