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