L4Re Operating System Framework
Interface and Usage Documentation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
transfer.h
1/*
2 * Copyright (C) 2016-2017, 2020, 2022-2024 Kernkonzept GmbH.
3 * Author(s): Jean Wolter <jean.wolter@kernkonzept.com>
4 * Alexander Warg <warg@os.inf.tu-dresden.de>
5 *
6 * License: see LICENSE.spdx (in this directory or the directories above)
7 */
8#pragma once
9
10#include "virtio_net.h"
11#include "request.h"
12#include "vlan.h"
13
14#include <vector>
15
16#include <l4/cxx/pair>
17
35{
36public:
37 enum class Result
38 {
39 Delivered, Exception, Dropped,
40 };
41
52 static
53 Result transfer(Virtio_net_request const &request,
54 Virtio_net *dst_dev,
55 L4virtio::Svr::Virtqueue *dst_queue,
56 Virtio_vlan_mangle &mangle)
57 {
58 Dbg trace(Dbg::Request, Dbg::Trace, "REQ");
59 trace.printf("Transfer request: %p\n", request.header());
60
61 /*
62 * src description
63 *
64 * We already looked at the very first buffer to find the target of
65 * the packet. The request processor of the "parent request"
66 * contains the current state of the transaction up to this
67 * point. Since there might be more then one target for the request
68 * we have to keep track of our own state and need our own request
69 * processor instance, which will be initialized using the current
70 * state of the "parent request".
71 */
72 L4virtio::Svr::Request_processor src_req_proc = request.get_request_processor();
73
74 /* the buffer descriptors used for this transaction and the amount
75 * of bytes copied to the current target descriptor */
76 Buffer src = request.first_buffer();
77 Buffer dst;
78 int total = 0;
79 l4_uint16_t num_merged = 0;
81 std::vector<Consumed_entry> consumed;
82
85 Virtio_net::Hdr *dst_header = nullptr;
86
87 for (;;)
88 {
89 try
90 {
91 if (src.done() && !src_req_proc.next(request.dev()->mem_info(), &src))
92 // Request completely copied to destination.
93 break;
94 }
96 {
97 trace.printf("\tTransfer failed, bad descriptor exception, dropping.\n");
98
99 // Handle partial transfers to destination port.
100 if (!consumed.empty())
101 // Partial transfer, rewind to before first descriptor of transfer.
102 dst_queue->rewind_avail(consumed.at(0).first);
103 else if (dst_head)
104 // Partial transfer, still at first _dst_head.
105 dst_queue->rewind_avail(dst_head);
106 throw;
107 }
108
109 /* The source data structures are already initialized, the header
110 is consumed and src stands at the very first real buffer.
111 Initialize the target data structures if necessary and fill the
112 header. */
113 if (!dst_head)
114 {
115 if (!dst_queue->ready())
116 return Result::Dropped;
117
118 auto r = dst_queue->next_avail();
119
120 if (L4_UNLIKELY(!r))
121 {
122 trace.printf("\tTransfer %p failed, destination queue depleted, dropping.\n",
123 request.header());
124 // Abort incomplete transfer.
125 if (!consumed.empty())
126 dst_queue->rewind_avail(consumed.front().first);
127 return Result::Dropped;
128 }
129
130 try
131 {
132 dst_head = dst_req_proc.start(dst_dev->mem_info(), r, &dst);
133 }
135 {
136 Dbg(Dbg::Request, Dbg::Warn, "REQ")
137 .printf("%s: bad descriptor exception: %s - %i"
138 " -- signal device error in destination device %p.\n",
139 __PRETTY_FUNCTION__, e.message(), e.error, dst_dev);
140
141 dst_dev->device_error();
142 return Result::Exception; // Must not touch the dst queues anymore.
143 }
144
145 if (!dst_header)
146 {
147 if (dst.left < sizeof(Virtio_net::Hdr))
149 "Target buffer too small for header");
150 dst_header = reinterpret_cast<Virtio_net::Hdr *>(dst.pos);
151 trace.printf("\t: Copying header to %p (size: %u)\n",
152 dst.pos, dst.left);
153 /*
154 * Header and csum offloading/general segmentation offloading
155 *
156 * We just copy the original header from source to
157 * destination and have to consider three different
158 * cases:
159 * - no flags are set
160 * - we got a packet that is completely checksummed
161 * and correctly fragmented, there is nothing to
162 * do other then copying.
163 * - virtio_net_hdr_f_needs_csum set
164 * - the packet is partially checksummed; if we would
165 * send the packet out on the wire we would have
166 * to calculate checksums now. But here we rely on
167 * the ability of our guest to handle partially
168 * checksummed packets and simply delegate the
169 * checksum calculation to them.
170 * - gso_type != gso_none
171 * - the packet needs to be segmented; if we would
172 * send it out on the wire we would have to
173 * segment it now. But again we rely on the
174 * ability of our guest to handle gso
175 *
176 * We currently assume that our guests negotiated
177 * virtio_net_f_guest_*, this needs to be checked in
178 * the future.
179 *
180 * We also discussed the usage of
181 * virtio_net_hdr_f_data_valid to remove the need to
182 * checksum packets at all. But since our clients send
183 * partially checksummed packets anyway the only
184 * interesting case would be a packet without
185 * net_hdr_f_needs_checksum set. In that case we would
186 * signal that we checked the checksum and the
187 * checksum is actually correct. Since we do not know
188 * the origin of the packet (it could have been send
189 * by an external node and could have been routed to
190 * u) we can not signal this without actually
191 * verifying the checksum. Otherwise a packet with an
192 * invalid checksum could be successfully delivered.
193 */
194 total = sizeof(Virtio_net::Hdr);
195 memcpy(dst_header, request.header(), total);
196 mangle.rewrite_hdr(dst_header);
197 dst.skip(total);
198 }
199 ++num_merged;
200 }
201
202 bool has_dst_buffer = !dst.done();
203 if (!has_dst_buffer)
204 try
205 {
206 // The current dst buffer is full, try to get next chained buffer.
207 has_dst_buffer = dst_req_proc.next(dst_dev->mem_info(), &dst);
208 }
210 {
211 Dbg(Dbg::Request, Dbg::Warn, "REQ")
212 .printf("%s: bad descriptor exception: %s - %i"
213 " -- signal device error in destination device %p.\n",
214 __PRETTY_FUNCTION__, e.message(), e.error, dst_dev);
215 dst_dev->device_error();
216 return Result::Exception; // Must not touch the dst queues anymore.
217 }
218
219 if (has_dst_buffer)
220 {
221 trace.printf("\t: Copying %p#%p:%u (%x) -> %p#%p:%u (%x)\n",
222 request.dev(),
223 src.pos, src.left, src.left,
224 dst_dev, dst.pos, dst.left, dst.left);
225
226 total += mangle.copy_pkt(dst, src);
227 }
228 else
229 {
230 // save descriptor information for later
231 trace.printf("\t: Saving descriptor for later\n");
232 consumed.push_back(Consumed_entry(dst_head, total));
233 total = 0;
235 }
236 }
237
238 /*
239 * Finalize the Request delivery. Call `finish()` on the destination
240 * port's receive queue, which will result in triggering the destination
241 * client IRQ.
242 */
243
244 if (!dst_header)
245 {
246 if (!total)
247 trace.printf("\tTransfer %p - not started yet, dropping\n", request.header());
248 return Result::Dropped;
249 }
250
251 if (consumed.empty())
252 {
253 assert(dst_head);
254 assert(num_merged == 1);
255 trace.printf("\tTransfer - Invoke dst_queue->finish()\n");
256 dst_header->num_buffers = 1;
257 dst_queue->finish(dst_head, dst_dev, total);
258 }
259 else
260 {
261 assert(dst_head);
262 dst_header->num_buffers = num_merged;
263 consumed.push_back(Consumed_entry(dst_head, total));
264 trace.printf("\tTransfer - Invoke dst_queue->finish(iter)\n");
265 dst_queue->finish(consumed.begin(), consumed.end(), dst_dev);
266 }
267
268 return Result::Delivered;
269 }
270};
Exception for an abstract runtime error.
Definition exceptions:129
void device_error()
Transition device into DEVICE_NEEDS_RESET state.
Definition l4virtio:1018
Mem_list const * mem_info() const
Get the memory region list used for this device.
Definition l4virtio:892
Encapsulate the state for processing a VIRTIO request.
Definition virtio:473
bool next(DESC_MAN *dm, ARGS... args)
Switch to the next descriptor in a descriptor chain.
Definition virtio:570
void start(DESC_MAN *dm, Virtqueue *ring, Virtqueue::Head_desc const &request, ARGS... args)
Start processing a new request.
Definition virtio:501
VIRTIO request, essentially a descriptor from the available ring.
Definition virtio:94
Virtqueue implementation for the device.
Definition virtio:88
Request next_avail()
Get the next available descriptor from the available ring.
Definition virtio:136
void finish(Head_desc &d, QUEUE_OBSERVER *o, l4_uint32_t len=0)
Add a descriptor to the used ring, and notify an observer.
Definition virtio:240
void rewind_avail(Head_desc const &d)
Return unfinished descriptors to the available ring, i.e.
Definition virtio:160
bool ready() const
Test if this queue is in working state.
Definition virtqueue:399
Abstraction for a network request.
Definition request.h:36
A network request to only a single destination.
Definition transfer.h:35
static Result transfer(Virtio_net_request const &request, Virtio_net *dst_dev, L4virtio::Svr::Virtqueue *dst_queue, Virtio_vlan_mangle &mangle)
Deliver the request to the destination port.
Definition transfer.h:53
The Base class of a Port.
Definition virtio_net.h:74
Class for VLAN packet rewriting.
Definition vlan.h:37
void rewrite_hdr(Virtio_net::Hdr *hdr)
Rewrite the virtio network header.
Definition vlan.h:142
l4_uint32_t copy_pkt(Buffer &dst, Buffer &src)
Copy packet from src to dst.
Definition vlan.h:93
unsigned short int l4_uint16_t
Unsigned 16bit value.
Definition l4int.h:27
@ L4_EINVAL
Invalid argument.
Definition err.h:46
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:275
Pair implementation.
Data buffer used to transfer packets.
Exception used by Queue to indicate descriptor errors.
Definition virtio:398
char const * message() const
Get a human readable description of the error code.
Definition virtio:430
l4_uint32_t left
Bytes left in buffer.
Definition virtio:309
char * pos
Current buffer position.
Definition virtio:308
l4_uint32_t skip(l4_uint32_t bytes)
Skip given number of bytes in this buffer.
Definition virtio:375
bool done() const
Check if there are no more bytes left in the buffer.
Definition virtio:388
Pair of two values.
Definition pair:28