|  | /* | 
|  | * 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; | 
|  | } |