xref: /openbmc/qemu/hw/cxl/cxl-events.c (revision 6edfca9e)
1 /*
2  * CXL Event processing
3  *
4  * Copyright(C) 2023 Intel Corporation.
5  *
6  * This work is licensed under the terms of the GNU GPL, version 2. See the
7  * COPYING file in the top-level directory.
8  */
9 
10 #include <stdint.h>
11 
12 #include "qemu/osdep.h"
13 #include "qemu/bswap.h"
14 #include "qemu/typedefs.h"
15 #include "qemu/error-report.h"
16 #include "hw/pci/msi.h"
17 #include "hw/pci/msix.h"
18 #include "hw/cxl/cxl.h"
19 #include "hw/cxl/cxl_events.h"
20 
21 /* Artificial limit on the number of events a log can hold */
22 #define CXL_TEST_EVENT_OVERFLOW 8
23 
24 static void reset_overflow(CXLEventLog *log)
25 {
26     log->overflow_err_count = 0;
27     log->first_overflow_timestamp = 0;
28     log->last_overflow_timestamp = 0;
29 }
30 
31 void cxl_event_init(CXLDeviceState *cxlds, int start_msg_num)
32 {
33     CXLEventLog *log;
34     int i;
35 
36     for (i = 0; i < CXL_EVENT_TYPE_MAX; i++) {
37         log = &cxlds->event_logs[i];
38         log->next_handle = 1;
39         log->overflow_err_count = 0;
40         log->first_overflow_timestamp = 0;
41         log->last_overflow_timestamp = 0;
42         log->irq_enabled = false;
43         log->irq_vec = start_msg_num++;
44         qemu_mutex_init(&log->lock);
45         QSIMPLEQ_INIT(&log->events);
46     }
47 
48     /* Override -- Dynamic Capacity uses the same vector as info */
49     cxlds->event_logs[CXL_EVENT_TYPE_DYNAMIC_CAP].irq_vec =
50                       cxlds->event_logs[CXL_EVENT_TYPE_INFO].irq_vec;
51 
52 }
53 
54 static CXLEvent *cxl_event_get_head(CXLEventLog *log)
55 {
56     return QSIMPLEQ_FIRST(&log->events);
57 }
58 
59 static CXLEvent *cxl_event_get_next(CXLEvent *entry)
60 {
61     return QSIMPLEQ_NEXT(entry, node);
62 }
63 
64 static int cxl_event_count(CXLEventLog *log)
65 {
66     CXLEvent *event;
67     int rc = 0;
68 
69     QSIMPLEQ_FOREACH(event, &log->events, node) {
70         rc++;
71     }
72 
73     return rc;
74 }
75 
76 static bool cxl_event_empty(CXLEventLog *log)
77 {
78     return QSIMPLEQ_EMPTY(&log->events);
79 }
80 
81 static void cxl_event_delete_head(CXLDeviceState *cxlds,
82                                   CXLEventLogType log_type,
83                                   CXLEventLog *log)
84 {
85     CXLEvent *entry = cxl_event_get_head(log);
86 
87     reset_overflow(log);
88     QSIMPLEQ_REMOVE_HEAD(&log->events, node);
89     if (cxl_event_empty(log)) {
90         cxl_event_set_status(cxlds, log_type, false);
91     }
92     g_free(entry);
93 }
94 
95 /*
96  * return true if an interrupt should be generated as a result
97  * of inserting this event.
98  */
99 bool cxl_event_insert(CXLDeviceState *cxlds, CXLEventLogType log_type,
100                       CXLEventRecordRaw *event)
101 {
102     uint64_t time;
103     CXLEventLog *log;
104     CXLEvent *entry;
105 
106     if (log_type >= CXL_EVENT_TYPE_MAX) {
107         return false;
108     }
109 
110     time = cxl_device_get_timestamp(cxlds);
111 
112     log = &cxlds->event_logs[log_type];
113 
114     QEMU_LOCK_GUARD(&log->lock);
115 
116     if (cxl_event_count(log) >= CXL_TEST_EVENT_OVERFLOW) {
117         if (log->overflow_err_count == 0) {
118             log->first_overflow_timestamp = time;
119         }
120         log->overflow_err_count++;
121         log->last_overflow_timestamp = time;
122         return false;
123     }
124 
125     entry = g_new0(CXLEvent, 1);
126 
127     memcpy(&entry->data, event, sizeof(*event));
128 
129     entry->data.hdr.handle = cpu_to_le16(log->next_handle);
130     log->next_handle++;
131     /* 0 handle is never valid */
132     if (log->next_handle == 0) {
133         log->next_handle++;
134     }
135     entry->data.hdr.timestamp = cpu_to_le64(time);
136 
137     QSIMPLEQ_INSERT_TAIL(&log->events, entry, node);
138     cxl_event_set_status(cxlds, log_type, true);
139 
140     /* Count went from 0 to 1 */
141     return cxl_event_count(log) == 1;
142 }
143 
144 CXLRetCode cxl_event_get_records(CXLDeviceState *cxlds, CXLGetEventPayload *pl,
145                                  uint8_t log_type, int max_recs,
146                                  uint16_t *len)
147 {
148     CXLEventLog *log;
149     CXLEvent *entry;
150     uint16_t nr;
151 
152     if (log_type >= CXL_EVENT_TYPE_MAX) {
153         return CXL_MBOX_INVALID_INPUT;
154     }
155 
156     log = &cxlds->event_logs[log_type];
157 
158     QEMU_LOCK_GUARD(&log->lock);
159 
160     entry = cxl_event_get_head(log);
161     for (nr = 0; entry && nr < max_recs; nr++) {
162         memcpy(&pl->records[nr], &entry->data, CXL_EVENT_RECORD_SIZE);
163         entry = cxl_event_get_next(entry);
164     }
165 
166     if (!cxl_event_empty(log)) {
167         pl->flags |= CXL_GET_EVENT_FLAG_MORE_RECORDS;
168     }
169 
170     if (log->overflow_err_count) {
171         pl->flags |= CXL_GET_EVENT_FLAG_OVERFLOW;
172         pl->overflow_err_count = cpu_to_le16(log->overflow_err_count);
173         pl->first_overflow_timestamp = cpu_to_le64(log->first_overflow_timestamp);
174         pl->last_overflow_timestamp = cpu_to_le64(log->last_overflow_timestamp);
175     }
176 
177     pl->record_count = cpu_to_le16(nr);
178     *len = CXL_EVENT_PAYLOAD_HDR_SIZE + (CXL_EVENT_RECORD_SIZE * nr);
179 
180     return CXL_MBOX_SUCCESS;
181 }
182 
183 CXLRetCode cxl_event_clear_records(CXLDeviceState *cxlds, CXLClearEventPayload *pl)
184 {
185     CXLEventLog *log;
186     uint8_t log_type;
187     CXLEvent *entry;
188     int nr;
189 
190     log_type = pl->event_log;
191 
192     if (log_type >= CXL_EVENT_TYPE_MAX) {
193         return CXL_MBOX_INVALID_INPUT;
194     }
195 
196     log = &cxlds->event_logs[log_type];
197 
198     QEMU_LOCK_GUARD(&log->lock);
199     /*
200      * Must itterate the queue twice.
201      * "The device shall verify the event record handles specified in the input
202      * payload are in temporal order. If the device detects an older event
203      * record that will not be cleared when Clear Event Records is executed,
204      * the device shall return the Invalid Handle return code and shall not
205      * clear any of the specified event records."
206      *   -- CXL 3.0 8.2.9.2.3
207      */
208     entry = cxl_event_get_head(log);
209     for (nr = 0; entry && nr < pl->nr_recs; nr++) {
210         uint16_t handle = pl->handle[nr];
211 
212         /* NOTE: Both handles are little endian. */
213         if (handle == 0 || entry->data.hdr.handle != handle) {
214             return CXL_MBOX_INVALID_INPUT;
215         }
216         entry = cxl_event_get_next(entry);
217     }
218 
219     entry = cxl_event_get_head(log);
220     for (nr = 0; entry && nr < pl->nr_recs; nr++) {
221         cxl_event_delete_head(cxlds, log_type, log);
222         entry = cxl_event_get_head(log);
223     }
224 
225     return CXL_MBOX_SUCCESS;
226 }
227 
228 void cxl_event_irq_assert(CXLType3Dev *ct3d)
229 {
230     CXLDeviceState *cxlds = &ct3d->cxl_dstate;
231     PCIDevice *pdev = &ct3d->parent_obj;
232     int i;
233 
234     for (i = 0; i < CXL_EVENT_TYPE_MAX; i++) {
235         CXLEventLog *log = &cxlds->event_logs[i];
236 
237         if (!log->irq_enabled || cxl_event_empty(log)) {
238             continue;
239         }
240 
241         /*  Notifies interrupt, legacy IRQ is not supported */
242         if (msix_enabled(pdev)) {
243             msix_notify(pdev, log->irq_vec);
244         } else if (msi_enabled(pdev)) {
245             msi_notify(pdev, log->irq_vec);
246         }
247     }
248 }
249