|  | /* | 
|  | * CXL Event processing | 
|  | * | 
|  | * Copyright(C) 2023 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/bswap.h" | 
|  | #include "qemu/error-report.h" | 
|  | #include "hw/pci/msi.h" | 
|  | #include "hw/pci/msix.h" | 
|  | #include "hw/cxl/cxl.h" | 
|  | #include "hw/cxl/cxl_events.h" | 
|  |  | 
|  | /* Artificial limit on the number of events a log can hold */ | 
|  | #define CXL_TEST_EVENT_OVERFLOW 8 | 
|  |  | 
|  | static void reset_overflow(CXLEventLog *log) | 
|  | { | 
|  | log->overflow_err_count = 0; | 
|  | log->first_overflow_timestamp = 0; | 
|  | log->last_overflow_timestamp = 0; | 
|  | } | 
|  |  | 
|  | void cxl_event_init(CXLDeviceState *cxlds, int start_msg_num) | 
|  | { | 
|  | CXLEventLog *log; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < CXL_EVENT_TYPE_MAX; i++) { | 
|  | log = &cxlds->event_logs[i]; | 
|  | log->next_handle = 1; | 
|  | log->overflow_err_count = 0; | 
|  | log->first_overflow_timestamp = 0; | 
|  | log->last_overflow_timestamp = 0; | 
|  | log->irq_enabled = false; | 
|  | log->irq_vec = start_msg_num++; | 
|  | qemu_mutex_init(&log->lock); | 
|  | QSIMPLEQ_INIT(&log->events); | 
|  | } | 
|  |  | 
|  | /* Override -- Dynamic Capacity uses the same vector as info */ | 
|  | cxlds->event_logs[CXL_EVENT_TYPE_DYNAMIC_CAP].irq_vec = | 
|  | cxlds->event_logs[CXL_EVENT_TYPE_INFO].irq_vec; | 
|  |  | 
|  | } | 
|  |  | 
|  | static CXLEvent *cxl_event_get_head(CXLEventLog *log) | 
|  | { | 
|  | return QSIMPLEQ_FIRST(&log->events); | 
|  | } | 
|  |  | 
|  | static CXLEvent *cxl_event_get_next(CXLEvent *entry) | 
|  | { | 
|  | return QSIMPLEQ_NEXT(entry, node); | 
|  | } | 
|  |  | 
|  | static int cxl_event_count(CXLEventLog *log) | 
|  | { | 
|  | CXLEvent *event; | 
|  | int rc = 0; | 
|  |  | 
|  | QSIMPLEQ_FOREACH(event, &log->events, node) { | 
|  | rc++; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static bool cxl_event_empty(CXLEventLog *log) | 
|  | { | 
|  | return QSIMPLEQ_EMPTY(&log->events); | 
|  | } | 
|  |  | 
|  | static void cxl_event_delete_head(CXLDeviceState *cxlds, | 
|  | CXLEventLogType log_type, | 
|  | CXLEventLog *log) | 
|  | { | 
|  | CXLEvent *entry = cxl_event_get_head(log); | 
|  |  | 
|  | reset_overflow(log); | 
|  | QSIMPLEQ_REMOVE_HEAD(&log->events, node); | 
|  | if (cxl_event_empty(log)) { | 
|  | cxl_event_set_status(cxlds, log_type, false); | 
|  | } | 
|  | g_free(entry); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * return true if an interrupt should be generated as a result | 
|  | * of inserting this event. | 
|  | */ | 
|  | bool cxl_event_insert(CXLDeviceState *cxlds, CXLEventLogType log_type, | 
|  | CXLEventRecordRaw *event) | 
|  | { | 
|  | uint64_t time; | 
|  | CXLEventLog *log; | 
|  | CXLEvent *entry; | 
|  |  | 
|  | if (log_type >= CXL_EVENT_TYPE_MAX) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | time = cxl_device_get_timestamp(cxlds); | 
|  |  | 
|  | log = &cxlds->event_logs[log_type]; | 
|  |  | 
|  | QEMU_LOCK_GUARD(&log->lock); | 
|  |  | 
|  | if (cxl_event_count(log) >= CXL_TEST_EVENT_OVERFLOW) { | 
|  | if (log->overflow_err_count == 0) { | 
|  | log->first_overflow_timestamp = time; | 
|  | } | 
|  | log->overflow_err_count++; | 
|  | log->last_overflow_timestamp = time; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | entry = g_new0(CXLEvent, 1); | 
|  |  | 
|  | memcpy(&entry->data, event, sizeof(*event)); | 
|  |  | 
|  | entry->data.hdr.handle = cpu_to_le16(log->next_handle); | 
|  | log->next_handle++; | 
|  | /* 0 handle is never valid */ | 
|  | if (log->next_handle == 0) { | 
|  | log->next_handle++; | 
|  | } | 
|  | entry->data.hdr.timestamp = cpu_to_le64(time); | 
|  |  | 
|  | QSIMPLEQ_INSERT_TAIL(&log->events, entry, node); | 
|  | cxl_event_set_status(cxlds, log_type, true); | 
|  |  | 
|  | /* Count went from 0 to 1 */ | 
|  | return cxl_event_count(log) == 1; | 
|  | } | 
|  |  | 
|  | void cxl_discard_all_event_records(CXLDeviceState *cxlds) | 
|  | { | 
|  | CXLEventLogType log_type; | 
|  | CXLEventLog *log; | 
|  |  | 
|  | for (log_type = 0; log_type < CXL_EVENT_TYPE_MAX; log_type++) { | 
|  | log = &cxlds->event_logs[log_type]; | 
|  | while (!cxl_event_empty(log)) { | 
|  | cxl_event_delete_head(cxlds, log_type, log); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | CXLRetCode cxl_event_get_records(CXLDeviceState *cxlds, CXLGetEventPayload *pl, | 
|  | uint8_t log_type, int max_recs, | 
|  | size_t *len) | 
|  | { | 
|  | CXLEventLog *log; | 
|  | CXLEvent *entry; | 
|  | uint16_t nr; | 
|  |  | 
|  | if (log_type >= CXL_EVENT_TYPE_MAX) { | 
|  | return CXL_MBOX_INVALID_INPUT; | 
|  | } | 
|  |  | 
|  | log = &cxlds->event_logs[log_type]; | 
|  |  | 
|  | QEMU_LOCK_GUARD(&log->lock); | 
|  |  | 
|  | entry = cxl_event_get_head(log); | 
|  | for (nr = 0; entry && nr < max_recs; nr++) { | 
|  | memcpy(&pl->records[nr], &entry->data, CXL_EVENT_RECORD_SIZE); | 
|  | entry = cxl_event_get_next(entry); | 
|  | } | 
|  |  | 
|  | if (!cxl_event_empty(log)) { | 
|  | pl->flags |= CXL_GET_EVENT_FLAG_MORE_RECORDS; | 
|  | } | 
|  |  | 
|  | if (log->overflow_err_count) { | 
|  | pl->flags |= CXL_GET_EVENT_FLAG_OVERFLOW; | 
|  | pl->overflow_err_count = cpu_to_le16(log->overflow_err_count); | 
|  | pl->first_overflow_timestamp = | 
|  | cpu_to_le64(log->first_overflow_timestamp); | 
|  | pl->last_overflow_timestamp = | 
|  | cpu_to_le64(log->last_overflow_timestamp); | 
|  | } | 
|  |  | 
|  | pl->record_count = cpu_to_le16(nr); | 
|  | *len = CXL_EVENT_PAYLOAD_HDR_SIZE + (CXL_EVENT_RECORD_SIZE * nr); | 
|  |  | 
|  | return CXL_MBOX_SUCCESS; | 
|  | } | 
|  |  | 
|  | CXLRetCode cxl_event_clear_records(CXLDeviceState *cxlds, | 
|  | CXLClearEventPayload *pl) | 
|  | { | 
|  | CXLEventLog *log; | 
|  | uint8_t log_type; | 
|  | CXLEvent *entry; | 
|  | int nr; | 
|  |  | 
|  | log_type = pl->event_log; | 
|  |  | 
|  | if (log_type >= CXL_EVENT_TYPE_MAX) { | 
|  | return CXL_MBOX_INVALID_INPUT; | 
|  | } | 
|  |  | 
|  | log = &cxlds->event_logs[log_type]; | 
|  |  | 
|  | QEMU_LOCK_GUARD(&log->lock); | 
|  | /* | 
|  | * Must iterate the queue twice. | 
|  | * "The device shall verify the event record handles specified in the input | 
|  | * payload are in temporal order. If the device detects an older event | 
|  | * record that will not be cleared when Clear Event Records is executed, | 
|  | * the device shall return the Invalid Handle return code and shall not | 
|  | * clear any of the specified event records." | 
|  | *   -- CXL r3.1 Section 8.2.9.2.3: Clear Event Records (0101h) | 
|  | */ | 
|  | entry = cxl_event_get_head(log); | 
|  | for (nr = 0; entry && nr < pl->nr_recs; nr++) { | 
|  | uint16_t handle = pl->handle[nr]; | 
|  |  | 
|  | /* NOTE: Both handles are little endian. */ | 
|  | if (handle == 0 || entry->data.hdr.handle != handle) { | 
|  | return CXL_MBOX_INVALID_INPUT; | 
|  | } | 
|  | entry = cxl_event_get_next(entry); | 
|  | } | 
|  |  | 
|  | entry = cxl_event_get_head(log); | 
|  | for (nr = 0; entry && nr < pl->nr_recs; nr++) { | 
|  | cxl_event_delete_head(cxlds, log_type, log); | 
|  | entry = cxl_event_get_head(log); | 
|  | } | 
|  |  | 
|  | return CXL_MBOX_SUCCESS; | 
|  | } | 
|  |  | 
|  | void cxl_event_irq_assert(CXLType3Dev *ct3d) | 
|  | { | 
|  | CXLDeviceState *cxlds = &ct3d->cxl_dstate; | 
|  | PCIDevice *pdev = &ct3d->parent_obj; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < CXL_EVENT_TYPE_MAX; i++) { | 
|  | CXLEventLog *log = &cxlds->event_logs[i]; | 
|  |  | 
|  | if (!log->irq_enabled || cxl_event_empty(log)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /*  Notifies interrupt, legacy IRQ is not supported */ | 
|  | if (msix_enabled(pdev)) { | 
|  | msix_notify(pdev, log->irq_vec); | 
|  | } else if (msi_enabled(pdev)) { | 
|  | msi_notify(pdev, log->irq_vec); | 
|  | } | 
|  | } | 
|  | } |