1 /*
2 * Copyright (c) 2023 Enphase Energy
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "eth_ivshmem_priv.h"
8
9 #include <zephyr/arch/cpu.h>
10 #include <zephyr/cache.h>
11 #include <zephyr/kernel.h>
12
13 #include <stdatomic.h>
14 #include <string.h>
15
16 /* These defines must match on the peer */
17 #define ETH_IVSHMEM_VRING_ALIGNMENT 64
18 #define ETH_IVSHMEM_FRAME_SIZE(len) ROUND_UP(18 + (len), L1_CACHE_BYTES)
19
20 #define VRING_FLUSH(x) sys_cache_data_flush_range(&(x), sizeof(x))
21 #define VRING_INVALIDATE(x) sys_cache_data_invd_range(&(x), sizeof(x))
22
23 static int calc_vring_size(
24 size_t section_size, uint16_t *vring_desc_len,
25 uint32_t *vring_header_size);
26 static uint32_t tx_buffer_advance(uint32_t max_len, uint32_t *position, uint32_t *len);
27 static int tx_clean_used(struct eth_ivshmem_queue *q);
28 static int get_rx_avail_desc_idx(struct eth_ivshmem_queue *q, uint16_t *avail_desc_idx);
29
eth_ivshmem_queue_init(struct eth_ivshmem_queue * q,uintptr_t shmem,size_t shmem_section_size,bool tx_buffer_first)30 int eth_ivshmem_queue_init(
31 struct eth_ivshmem_queue *q, uintptr_t shmem,
32 size_t shmem_section_size, bool tx_buffer_first)
33 {
34 memset(q, 0, sizeof(*q));
35
36 uint16_t vring_desc_len;
37 uint32_t vring_header_size;
38 int res = calc_vring_size(shmem_section_size, &vring_desc_len, &vring_header_size);
39
40 if (res != 0) {
41 return res;
42 }
43
44 q->desc_max_len = vring_desc_len;
45 q->vring_data_max_len = shmem_section_size - vring_header_size;
46 q->vring_header_size = vring_header_size;
47
48 if (tx_buffer_first) {
49 q->tx.shmem = (void *)shmem;
50 q->rx.shmem = (void *)(shmem + shmem_section_size);
51 } else {
52 q->rx.shmem = (void *)shmem;
53 q->tx.shmem = (void *)(shmem + shmem_section_size);
54 }
55
56 /* Init vrings */
57 vring_init(&q->tx.vring, vring_desc_len, q->tx.shmem, ETH_IVSHMEM_VRING_ALIGNMENT);
58 vring_init(&q->rx.vring, vring_desc_len, q->rx.shmem, ETH_IVSHMEM_VRING_ALIGNMENT);
59
60 /* Swap "used" pointers.
61 * This is done so that each peer only ever writes to its output section,
62 * while maintaining vring code consistency elsewhere in this file.
63 */
64 struct vring_used *tmp_used = q->tx.vring.used;
65
66 q->tx.vring.used = q->rx.vring.used;
67 q->rx.vring.used = tmp_used;
68
69 eth_ivshmem_queue_reset(q);
70
71 return 0;
72 }
73
eth_ivshmem_queue_reset(struct eth_ivshmem_queue * q)74 void eth_ivshmem_queue_reset(struct eth_ivshmem_queue *q)
75 {
76 q->tx.desc_head = 0;
77 q->tx.desc_len = 0;
78 q->tx.data_head = 0;
79 q->tx.data_tail = 0;
80 q->tx.data_len = 0;
81 q->tx.avail_idx = 0;
82 q->tx.used_idx = 0;
83 q->tx.pending_data_head = 0;
84 q->tx.pending_data_len = 0;
85 q->rx.avail_idx = 0;
86 q->rx.used_idx = 0;
87
88 memset(q->tx.shmem, 0, q->vring_header_size);
89
90 /* Init TX ring descriptors */
91 for (unsigned int i = 0; i < q->tx.vring.num - 1; i++)
92 q->tx.vring.desc[i].next = i + 1;
93 q->tx.vring.desc[q->tx.vring.num - 1].next = 0;
94 }
95
eth_ivshmem_queue_tx_get_buff(struct eth_ivshmem_queue * q,void ** data,size_t len)96 int eth_ivshmem_queue_tx_get_buff(struct eth_ivshmem_queue *q, void **data, size_t len)
97 {
98 /* Clean used TX buffers */
99 int res = tx_clean_used(q);
100
101 if (res != 0) {
102 return res;
103 }
104
105 if (q->tx.desc_len >= q->desc_max_len) {
106 return -ENOBUFS;
107 }
108
109 uint32_t head = q->tx.data_head;
110 uint32_t consumed_len = len;
111 uint32_t new_head = tx_buffer_advance(q->vring_data_max_len, &head, &consumed_len);
112
113 if (q->vring_data_max_len - q->tx.data_len < consumed_len) {
114 return -ENOBUFS;
115 }
116
117 struct vring_desc *tx_desc = &q->tx.vring.desc[q->tx.desc_head];
118
119 tx_desc->addr = q->vring_header_size + head;
120 tx_desc->len = len;
121 tx_desc->flags = 0;
122 VRING_FLUSH(*tx_desc);
123
124 *data = (uint8_t *)q->tx.shmem + q->vring_header_size + head;
125
126 q->tx.pending_data_head = new_head;
127 q->tx.pending_data_len = q->tx.data_len + consumed_len;
128
129 return 0;
130 }
131
eth_ivshmem_queue_tx_commit_buff(struct eth_ivshmem_queue * q)132 int eth_ivshmem_queue_tx_commit_buff(struct eth_ivshmem_queue *q)
133 {
134 /* Ensure that a TX buffer is pending */
135 if (q->tx.pending_data_len == 0) {
136 return -EINVAL;
137 }
138
139 uint16_t desc_head = q->tx.desc_head;
140
141 q->tx.desc_len++;
142 q->tx.desc_head = (q->tx.desc_head + 1) % q->desc_max_len;
143
144 q->tx.data_head = q->tx.pending_data_head;
145 q->tx.data_len = q->tx.pending_data_len;
146
147 q->tx.vring.avail->ring[q->tx.avail_idx % q->desc_max_len] = desc_head;
148
149 VRING_FLUSH(q->tx.vring.avail->ring[q->tx.avail_idx % q->desc_max_len]);
150 atomic_thread_fence(memory_order_seq_cst);
151
152 q->tx.avail_idx++;
153 q->tx.vring.avail->idx = q->tx.avail_idx;
154
155 VRING_FLUSH(q->tx.vring.avail->idx);
156
157 q->tx.pending_data_len = 0;
158
159 return 0;
160 }
161
eth_ivshmem_queue_rx(struct eth_ivshmem_queue * q,const void ** data,size_t * len)162 int eth_ivshmem_queue_rx(struct eth_ivshmem_queue *q, const void **data, size_t *len)
163 {
164 *data = NULL;
165 *len = 0;
166
167 uint16_t avail_desc_idx;
168 int res = get_rx_avail_desc_idx(q, &avail_desc_idx);
169
170 if (res != 0) {
171 return res;
172 }
173
174 struct vring_desc *desc = &q->rx.vring.desc[avail_desc_idx];
175
176 VRING_INVALIDATE(*desc);
177
178 uint64_t offset = desc->addr - q->vring_header_size;
179 uint32_t rx_len = desc->len;
180
181 if (offset > q->vring_data_max_len ||
182 rx_len > q->vring_data_max_len ||
183 offset > q->vring_data_max_len - rx_len) {
184 return -EINVAL;
185 }
186
187 *data = (uint8_t *)q->rx.shmem + q->vring_header_size + offset;
188 *len = desc->len;
189
190 return 0;
191 }
192
eth_ivshmem_queue_rx_complete(struct eth_ivshmem_queue * q)193 int eth_ivshmem_queue_rx_complete(struct eth_ivshmem_queue *q)
194 {
195 uint16_t avail_desc_idx;
196 int res = get_rx_avail_desc_idx(q, &avail_desc_idx);
197
198 if (res != 0) {
199 return res;
200 }
201
202 uint16_t used_idx = q->rx.used_idx % q->desc_max_len;
203
204 q->rx.used_idx++;
205 q->rx.vring.used->ring[used_idx].id = avail_desc_idx;
206 q->rx.vring.used->ring[used_idx].len = 1;
207 VRING_FLUSH(q->rx.vring.used->ring[used_idx]);
208 atomic_thread_fence(memory_order_seq_cst);
209
210 q->rx.vring.used->idx = q->rx.used_idx;
211 VRING_FLUSH(q->rx.vring.used->idx);
212 atomic_thread_fence(memory_order_seq_cst);
213
214 q->rx.avail_idx++;
215 vring_avail_event(&q->rx.vring) = q->rx.avail_idx;
216 VRING_FLUSH(vring_avail_event(&q->rx.vring));
217
218 return 0;
219 }
220
221 /**
222 * Calculates the vring descriptor length and header size.
223 * This must match what is calculated by the peer.
224 */
calc_vring_size(size_t section_size,uint16_t * vring_desc_len,uint32_t * vring_header_size)225 static int calc_vring_size(
226 size_t section_size, uint16_t *vring_desc_len,
227 uint32_t *vring_header_size)
228 {
229 static const int eth_min_mtu = 68;
230 uint32_t header_size;
231 int16_t desc_len;
232
233 for (desc_len = 4096; desc_len > 32; desc_len >>= 1) {
234 header_size = vring_size(desc_len, ETH_IVSHMEM_VRING_ALIGNMENT);
235 header_size = ROUND_UP(header_size, ETH_IVSHMEM_VRING_ALIGNMENT);
236 if (header_size < section_size / 8) {
237 break;
238 }
239 }
240
241 if (header_size > section_size) {
242 return -EINVAL;
243 }
244
245 uint32_t vring_data_size = section_size - header_size;
246
247 if (vring_data_size < 4 * eth_min_mtu) {
248 return -EINVAL;
249 }
250
251 *vring_desc_len = desc_len;
252 *vring_header_size = header_size;
253
254 return 0;
255 }
256
tx_buffer_advance(uint32_t max_len,uint32_t * position,uint32_t * len)257 static uint32_t tx_buffer_advance(uint32_t max_len, uint32_t *position, uint32_t *len)
258 {
259 uint32_t aligned_len = ETH_IVSHMEM_FRAME_SIZE(*len);
260 uint32_t contiguous_len = max_len - *position;
261
262 *len = aligned_len;
263 if (aligned_len > contiguous_len) {
264 /* Wrap back to zero */
265 *position = 0;
266 *len += contiguous_len;
267 }
268
269 return *position + aligned_len;
270 }
271
tx_clean_used(struct eth_ivshmem_queue * q)272 static int tx_clean_used(struct eth_ivshmem_queue *q)
273 {
274 while (true) {
275 VRING_INVALIDATE(q->tx.vring.used->idx);
276 if (q->tx.used_idx == q->tx.vring.used->idx) {
277 break;
278 }
279
280 struct vring_used_elem *used = &q->tx.vring.used->ring[
281 q->tx.used_idx % q->desc_max_len];
282
283 atomic_thread_fence(memory_order_seq_cst);
284 VRING_INVALIDATE(*used);
285
286 if (used->id >= q->desc_max_len || used->len != 1) {
287 return -EINVAL;
288 }
289
290 struct vring_desc *desc = &q->tx.vring.desc[used->id];
291
292 uint64_t offset = desc->addr - q->vring_header_size;
293 uint32_t len = desc->len;
294
295 uint32_t tail = q->tx.data_tail;
296 uint32_t consumed_len = len;
297 uint32_t new_tail = tx_buffer_advance(q->vring_data_max_len, &tail, &consumed_len);
298
299 if (consumed_len > q->tx.data_len ||
300 offset != tail) {
301 return -EINVAL;
302 }
303
304 q->tx.data_tail = new_tail;
305 q->tx.data_len -= consumed_len;
306 q->tx.desc_len--;
307 q->tx.used_idx++;
308 }
309 return 0;
310 }
311
get_rx_avail_desc_idx(struct eth_ivshmem_queue * q,uint16_t * avail_desc_idx)312 static int get_rx_avail_desc_idx(struct eth_ivshmem_queue *q, uint16_t *avail_desc_idx)
313 {
314 atomic_thread_fence(memory_order_seq_cst);
315 VRING_INVALIDATE(q->rx.vring.avail->idx);
316
317 uint16_t avail_idx = q->rx.vring.avail->idx;
318
319 if (avail_idx == q->rx.avail_idx) {
320 return -EWOULDBLOCK;
321 }
322
323 VRING_INVALIDATE(q->rx.vring.avail->ring[q->rx.avail_idx % q->desc_max_len]);
324 *avail_desc_idx = q->rx.vring.avail->ring[q->rx.avail_idx % q->desc_max_len];
325 if (*avail_desc_idx >= q->desc_max_len) {
326 return -EINVAL;
327 }
328
329 return 0;
330 }
331