Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (c) 2018 Microsoft Corp.
3 : : * Copyright (c) 2010-2012 Citrix Inc.
4 : : * Copyright (c) 2012 NetApp Inc.
5 : : * All rights reserved.
6 : : */
7 : :
8 : : /*
9 : : * Network Virtualization Service.
10 : : */
11 : :
12 : :
13 : : #include <stdint.h>
14 : : #include <string.h>
15 : : #include <stdio.h>
16 : : #include <errno.h>
17 : : #include <unistd.h>
18 : :
19 : : #include <rte_ethdev.h>
20 : : #include <rte_string_fns.h>
21 : : #include <rte_memzone.h>
22 : : #include <rte_malloc.h>
23 : : #include <rte_atomic.h>
24 : : #include <rte_branch_prediction.h>
25 : : #include <rte_ether.h>
26 : : #include <rte_common.h>
27 : : #include <rte_errno.h>
28 : : #include <rte_cycles.h>
29 : : #include <rte_memory.h>
30 : : #include <rte_eal.h>
31 : : #include <dev_driver.h>
32 : : #include <bus_vmbus_driver.h>
33 : :
34 : : #include "hn_logs.h"
35 : : #include "hn_var.h"
36 : : #include "hn_nvs.h"
37 : :
38 : : static const uint32_t hn_nvs_version[] = {
39 : : NVS_VERSION_61,
40 : : NVS_VERSION_6,
41 : : NVS_VERSION_5,
42 : : NVS_VERSION_4,
43 : : NVS_VERSION_2,
44 : : NVS_VERSION_1
45 : : };
46 : :
47 : 0 : struct rte_vmbus_device *get_vmbus_device(struct hn_data *hv)
48 : : {
49 : 0 : struct rte_vmbus_device *vmbus = hv->vmbus;
50 : :
51 : : /* For secondary process, vmbus is in the eth_dev private */
52 [ # # ]: 0 : if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
53 : 0 : struct rte_eth_dev *dev = &rte_eth_devices[hv->port_id];
54 : 0 : struct hn_nvs_process_priv *process_priv = dev->process_private;
55 : :
56 : 0 : vmbus = process_priv->vmbus_dev;
57 : : }
58 : :
59 : 0 : return vmbus;
60 : : }
61 : :
62 : : static int hn_nvs_req_send(struct hn_data *hv,
63 : : void *req, uint32_t reqlen)
64 : : {
65 : 0 : return rte_vmbus_chan_send(hn_primary_chan(hv),
66 : : VMBUS_CHANPKT_TYPE_INBAND,
67 : : req, reqlen, 0,
68 : : VMBUS_CHANPKT_FLAG_NONE, NULL);
69 : : }
70 : :
71 : : static int
72 : 0 : __hn_nvs_execute(struct hn_data *hv,
73 : : void *req, uint32_t reqlen,
74 : : void *resp, uint32_t resplen,
75 : : uint32_t type)
76 : : {
77 : : struct vmbus_channel *chan = hn_primary_chan(hv);
78 : : char buffer[NVS_RESPSIZE_MAX];
79 : : const struct hn_nvs_hdr *hdr;
80 : : uint64_t xactid;
81 : : uint32_t len;
82 : : int ret;
83 : :
84 : : /* Send request to ring buffer */
85 : 0 : ret = rte_vmbus_chan_send(chan, VMBUS_CHANPKT_TYPE_INBAND,
86 : : req, reqlen, 0,
87 : : VMBUS_CHANPKT_FLAG_RC, NULL);
88 : :
89 [ # # ]: 0 : if (ret) {
90 : 0 : PMD_DRV_LOG(ERR, "send request failed: %d", ret);
91 : 0 : return ret;
92 : : }
93 : :
94 : 0 : retry:
95 : 0 : len = sizeof(buffer);
96 : 0 : ret = rte_vmbus_chan_recv(chan, buffer, &len, &xactid);
97 [ # # ]: 0 : if (ret == -EAGAIN) {
98 : 0 : rte_delay_us(HN_CHAN_INTERVAL_US);
99 : 0 : goto retry;
100 : : }
101 : :
102 [ # # ]: 0 : if (ret < 0) {
103 : 0 : PMD_DRV_LOG(ERR, "recv response failed: %d", ret);
104 : 0 : return ret;
105 : : }
106 : :
107 [ # # ]: 0 : if (len < sizeof(*hdr)) {
108 : 0 : PMD_DRV_LOG(ERR, "response missing NVS header");
109 : 0 : return -EINVAL;
110 : : }
111 : :
112 : : hdr = (struct hn_nvs_hdr *)buffer;
113 : :
114 : : /* Silently drop received packets while waiting for response */
115 [ # # # ]: 0 : switch (hdr->type) {
116 : 0 : case NVS_TYPE_RNDIS:
117 : 0 : hn_nvs_ack_rxbuf(chan, xactid);
118 : : /* fallthrough */
119 : :
120 : 0 : case NVS_TYPE_TXTBL_NOTE:
121 : 0 : PMD_DRV_LOG(DEBUG, "discard packet type 0x%x", hdr->type);
122 : 0 : goto retry;
123 : : }
124 : :
125 [ # # ]: 0 : if (hdr->type != type) {
126 : 0 : PMD_DRV_LOG(ERR, "unexpected NVS resp %#x, expect %#x",
127 : : hdr->type, type);
128 : 0 : return -EINVAL;
129 : : }
130 : :
131 [ # # ]: 0 : if (len < resplen) {
132 : 0 : PMD_DRV_LOG(ERR,
133 : : "invalid NVS resp len %u (expect %u)",
134 : : len, resplen);
135 : 0 : return -EINVAL;
136 : : }
137 : :
138 : 0 : memcpy(resp, buffer, resplen);
139 : :
140 : : /* All pass! */
141 : 0 : return 0;
142 : : }
143 : :
144 : :
145 : : /*
146 : : * Execute one control command and get the response.
147 : : * Only one command can be active on a channel at once
148 : : * Unlike BSD, DPDK does not have an interrupt context
149 : : * so the polling is required to wait for response.
150 : : */
151 : : static int
152 : 0 : hn_nvs_execute(struct hn_data *hv,
153 : : void *req, uint32_t reqlen,
154 : : void *resp, uint32_t resplen,
155 : : uint32_t type)
156 : : {
157 : 0 : struct hn_rx_queue *rxq = hv->primary;
158 : : int ret;
159 : :
160 : 0 : rte_spinlock_lock(&rxq->ring_lock);
161 : 0 : ret = __hn_nvs_execute(hv, req, reqlen, resp, resplen, type);
162 : : rte_spinlock_unlock(&rxq->ring_lock);
163 : :
164 : 0 : return ret;
165 : : }
166 : :
167 : : static int
168 : 0 : hn_nvs_doinit(struct hn_data *hv, uint32_t nvs_ver)
169 : : {
170 : : struct hn_nvs_init init;
171 : : struct hn_nvs_init_resp resp;
172 : : uint32_t status;
173 : : int error;
174 : :
175 : : memset(&init, 0, sizeof(init));
176 : 0 : init.type = NVS_TYPE_INIT;
177 : 0 : init.ver_min = nvs_ver;
178 : 0 : init.ver_max = nvs_ver;
179 : :
180 : 0 : error = hn_nvs_execute(hv, &init, sizeof(init),
181 : : &resp, sizeof(resp),
182 : : NVS_TYPE_INIT_RESP);
183 [ # # ]: 0 : if (error)
184 : : return error;
185 : :
186 : 0 : status = resp.status;
187 [ # # ]: 0 : if (status != NVS_STATUS_OK) {
188 : : /* Not fatal, try other versions */
189 : 0 : PMD_INIT_LOG(DEBUG, "nvs init failed for ver 0x%x",
190 : : nvs_ver);
191 : 0 : return -EINVAL;
192 : : }
193 : :
194 : : return 0;
195 : : }
196 : :
197 : : static int
198 : 0 : hn_nvs_conn_rxbuf(struct hn_data *hv)
199 : : {
200 : : struct hn_nvs_rxbuf_conn conn;
201 : : struct hn_nvs_rxbuf_connresp resp;
202 : : uint32_t status;
203 : : int error;
204 : :
205 : : /* Kernel has already setup RXBUF on primary channel. */
206 : :
207 : : /*
208 : : * Connect RXBUF to NVS.
209 : : */
210 : 0 : conn.type = NVS_TYPE_RXBUF_CONN;
211 : 0 : conn.gpadl = hv->rxbuf_res.phys_addr;
212 : 0 : conn.sig = NVS_RXBUF_SIG;
213 : 0 : PMD_DRV_LOG(DEBUG, "connect rxbuff va=%p gpad=%#" PRIx64,
214 : : hv->rxbuf_res.addr,
215 : : hv->rxbuf_res.phys_addr);
216 : :
217 : 0 : error = hn_nvs_execute(hv, &conn, sizeof(conn),
218 : : &resp, sizeof(resp),
219 : : NVS_TYPE_RXBUF_CONNRESP);
220 [ # # ]: 0 : if (error) {
221 : 0 : PMD_DRV_LOG(ERR,
222 : : "exec nvs rxbuf conn failed: %d",
223 : : error);
224 : 0 : return error;
225 : : }
226 : :
227 : 0 : status = resp.status;
228 [ # # ]: 0 : if (status != NVS_STATUS_OK) {
229 : 0 : PMD_DRV_LOG(ERR,
230 : : "nvs rxbuf conn failed: %x", status);
231 : 0 : return -EIO;
232 : : }
233 [ # # ]: 0 : if (resp.nsect != 1) {
234 : 0 : PMD_DRV_LOG(ERR,
235 : : "nvs rxbuf response num sections %u != 1",
236 : : resp.nsect);
237 : 0 : return -EIO;
238 : : }
239 : :
240 : 0 : PMD_DRV_LOG(INFO,
241 : : "receive buffer size %u count %u",
242 : : resp.nvs_sect[0].slotsz,
243 : : resp.nvs_sect[0].slotcnt);
244 : 0 : hv->rxbuf_section_cnt = resp.nvs_sect[0].slotcnt;
245 : :
246 : : /*
247 : : * Primary queue's rxbuf_info is not allocated at creation time.
248 : : * Now we can allocate it after we figure out the slotcnt.
249 : : */
250 : 0 : hv->primary->rxbuf_info = rte_calloc("HN_RXBUF_INFO",
251 : : hv->rxbuf_section_cnt,
252 : : sizeof(*hv->primary->rxbuf_info),
253 : : RTE_CACHE_LINE_SIZE);
254 [ # # ]: 0 : if (!hv->primary->rxbuf_info) {
255 : 0 : PMD_DRV_LOG(ERR,
256 : : "could not allocate rxbuf info");
257 : 0 : return -ENOMEM;
258 : : }
259 : :
260 : : return 0;
261 : : }
262 : :
263 : : static void
264 : 0 : hn_nvs_disconn_rxbuf(struct hn_data *hv)
265 : : {
266 : : struct hn_nvs_rxbuf_disconn disconn;
267 : : int error;
268 : :
269 : : /*
270 : : * Disconnect RXBUF from NVS.
271 : : */
272 : : memset(&disconn, 0, sizeof(disconn));
273 : 0 : disconn.type = NVS_TYPE_RXBUF_DISCONN;
274 : 0 : disconn.sig = NVS_RXBUF_SIG;
275 : :
276 : : /* NOTE: No response. */
277 : : error = hn_nvs_req_send(hv, &disconn, sizeof(disconn));
278 [ # # ]: 0 : if (error) {
279 : 0 : PMD_DRV_LOG(ERR,
280 : : "send nvs rxbuf disconn failed: %d",
281 : : error);
282 : : }
283 : :
284 : : /*
285 : : * Linger long enough for NVS to disconnect RXBUF.
286 : : */
287 : : rte_delay_ms(200);
288 : 0 : }
289 : :
290 : : static void
291 : 0 : hn_nvs_disconn_chim(struct hn_data *hv)
292 : : {
293 : : int error;
294 : :
295 [ # # ]: 0 : if (hv->chim_cnt != 0) {
296 : : struct hn_nvs_chim_disconn disconn;
297 : :
298 : : /* Disconnect chimney sending buffer from NVS. */
299 : : memset(&disconn, 0, sizeof(disconn));
300 : 0 : disconn.type = NVS_TYPE_CHIM_DISCONN;
301 : 0 : disconn.sig = NVS_CHIM_SIG;
302 : :
303 : : /* NOTE: No response. */
304 : : error = hn_nvs_req_send(hv, &disconn, sizeof(disconn));
305 : :
306 [ # # ]: 0 : if (error) {
307 : 0 : PMD_DRV_LOG(ERR,
308 : : "send nvs chim disconn failed: %d", error);
309 : : }
310 : :
311 : 0 : hv->chim_cnt = 0;
312 : : /*
313 : : * Linger long enough for NVS to disconnect chimney
314 : : * sending buffer.
315 : : */
316 : : rte_delay_ms(200);
317 : : }
318 : 0 : }
319 : :
320 : : static int
321 : 0 : hn_nvs_conn_chim(struct hn_data *hv)
322 : : {
323 : : struct hn_nvs_chim_conn chim;
324 : : struct hn_nvs_chim_connresp resp;
325 : : uint32_t sectsz;
326 : 0 : unsigned long len = hv->chim_res.len;
327 : : int error;
328 : :
329 : : /* Connect chimney sending buffer to NVS */
330 : : memset(&chim, 0, sizeof(chim));
331 : 0 : chim.type = NVS_TYPE_CHIM_CONN;
332 : 0 : chim.gpadl = hv->chim_res.phys_addr;
333 : 0 : chim.sig = NVS_CHIM_SIG;
334 : 0 : PMD_DRV_LOG(DEBUG, "connect send buf va=%p gpad=%#" PRIx64,
335 : : hv->chim_res.addr,
336 : : hv->chim_res.phys_addr);
337 : :
338 : 0 : error = hn_nvs_execute(hv, &chim, sizeof(chim),
339 : : &resp, sizeof(resp),
340 : : NVS_TYPE_CHIM_CONNRESP);
341 [ # # ]: 0 : if (error) {
342 : 0 : PMD_DRV_LOG(ERR, "exec nvs chim conn failed");
343 : 0 : return error;
344 : : }
345 : :
346 [ # # ]: 0 : if (resp.status != NVS_STATUS_OK) {
347 : 0 : PMD_DRV_LOG(ERR, "nvs chim conn failed: %x",
348 : : resp.status);
349 : 0 : return -EIO;
350 : : }
351 : :
352 : 0 : sectsz = resp.sectsz;
353 [ # # # # ]: 0 : if (sectsz == 0 || sectsz & (sizeof(uint32_t) - 1)) {
354 : : /* Can't use chimney sending buffer; done! */
355 : 0 : PMD_DRV_LOG(NOTICE,
356 : : "invalid chimney sending buffer section size: %u",
357 : : sectsz);
358 : : error = -EINVAL;
359 : 0 : goto cleanup;
360 : : }
361 : :
362 : 0 : hv->chim_szmax = sectsz;
363 : 0 : hv->chim_cnt = len / sectsz;
364 : :
365 : 0 : PMD_DRV_LOG(INFO, "send buffer %lu section size:%u, count:%u",
366 : : len, hv->chim_szmax, hv->chim_cnt);
367 : :
368 : : /* Done! */
369 : 0 : return 0;
370 : :
371 : : cleanup:
372 : 0 : hn_nvs_disconn_chim(hv);
373 : 0 : return error;
374 : : }
375 : :
376 : : /*
377 : : * Configure MTU and enable VLAN.
378 : : */
379 : : static int
380 [ # # ]: 0 : hn_nvs_conf_ndis(struct hn_data *hv, unsigned int mtu)
381 : : {
382 : : struct hn_nvs_ndis_conf conf;
383 : : int error;
384 : :
385 : : memset(&conf, 0, sizeof(conf));
386 : 0 : conf.type = NVS_TYPE_NDIS_CONF;
387 : 0 : conf.mtu = mtu + RTE_ETHER_HDR_LEN;
388 : 0 : conf.caps = NVS_NDIS_CONF_VLAN;
389 : :
390 : : /* enable SRIOV */
391 [ # # ]: 0 : if (hv->nvs_ver >= NVS_VERSION_5)
392 : 0 : conf.caps |= NVS_NDIS_CONF_SRIOV;
393 : :
394 : : /* NOTE: No response. */
395 : : error = hn_nvs_req_send(hv, &conf, sizeof(conf));
396 [ # # ]: 0 : if (error) {
397 : 0 : PMD_DRV_LOG(ERR,
398 : : "send nvs ndis conf failed: %d", error);
399 : 0 : return error;
400 : : }
401 : :
402 : : return 0;
403 : : }
404 : :
405 : : static int
406 : 0 : hn_nvs_init_ndis(struct hn_data *hv)
407 : : {
408 : : struct hn_nvs_ndis_init ndis;
409 : : int error;
410 : :
411 : : memset(&ndis, 0, sizeof(ndis));
412 : 0 : ndis.type = NVS_TYPE_NDIS_INIT;
413 : 0 : ndis.ndis_major = NDIS_VERSION_MAJOR(hv->ndis_ver);
414 : 0 : ndis.ndis_minor = NDIS_VERSION_MINOR(hv->ndis_ver);
415 : :
416 : : /* NOTE: No response. */
417 : : error = hn_nvs_req_send(hv, &ndis, sizeof(ndis));
418 [ # # ]: 0 : if (error)
419 : 0 : PMD_DRV_LOG(ERR,
420 : : "send nvs ndis init failed: %d", error);
421 : :
422 : 0 : return error;
423 : : }
424 : :
425 : : static int
426 : 0 : hn_nvs_init(struct hn_data *hv)
427 : : {
428 : : unsigned int i;
429 : : int error;
430 : :
431 : : /*
432 : : * Find the supported NVS version and set NDIS version accordingly.
433 : : */
434 [ # # ]: 0 : for (i = 0; i < RTE_DIM(hn_nvs_version); ++i) {
435 : 0 : error = hn_nvs_doinit(hv, hn_nvs_version[i]);
436 [ # # ]: 0 : if (error) {
437 : 0 : PMD_INIT_LOG(DEBUG, "version %#x error %d",
438 : : hn_nvs_version[i], error);
439 : : continue;
440 : : }
441 : :
442 : 0 : hv->nvs_ver = hn_nvs_version[i];
443 : :
444 : : /* Set NDIS version according to NVS version. */
445 : 0 : hv->ndis_ver = NDIS_VERSION_6_30;
446 [ # # ]: 0 : if (hv->nvs_ver <= NVS_VERSION_4)
447 : 0 : hv->ndis_ver = NDIS_VERSION_6_1;
448 : :
449 : 0 : PMD_INIT_LOG(DEBUG,
450 : : "NVS version %#x, NDIS version %u.%u",
451 : : hv->nvs_ver, NDIS_VERSION_MAJOR(hv->ndis_ver),
452 : : NDIS_VERSION_MINOR(hv->ndis_ver));
453 : 0 : return 0;
454 : : }
455 : :
456 : 0 : PMD_DRV_LOG(ERR,
457 : : "no NVS compatible version available");
458 : 0 : return -ENXIO;
459 : : }
460 : :
461 : : int
462 : 0 : hn_nvs_attach(struct hn_data *hv, unsigned int mtu)
463 : : {
464 : : int error;
465 : :
466 : : /*
467 : : * Initialize NVS.
468 : : */
469 : 0 : error = hn_nvs_init(hv);
470 [ # # ]: 0 : if (error)
471 : : return error;
472 : :
473 : : /** Configure NDIS before initializing it. */
474 [ # # ]: 0 : if (hv->nvs_ver >= NVS_VERSION_2) {
475 : 0 : error = hn_nvs_conf_ndis(hv, mtu);
476 [ # # ]: 0 : if (error)
477 : : return error;
478 : : }
479 : :
480 : : /*
481 : : * Initialize NDIS.
482 : : */
483 : 0 : error = hn_nvs_init_ndis(hv);
484 [ # # ]: 0 : if (error)
485 : : return error;
486 : :
487 : : /*
488 : : * Connect RXBUF.
489 : : */
490 : 0 : error = hn_nvs_conn_rxbuf(hv);
491 [ # # ]: 0 : if (error)
492 : : return error;
493 : :
494 : : /*
495 : : * Connect chimney sending buffer.
496 : : */
497 : 0 : error = hn_nvs_conn_chim(hv);
498 [ # # ]: 0 : if (error) {
499 : 0 : hn_nvs_disconn_rxbuf(hv);
500 : 0 : return error;
501 : : }
502 : :
503 : : return 0;
504 : : }
505 : :
506 : : void
507 : 0 : hn_nvs_detach(struct hn_data *hv __rte_unused)
508 : : {
509 : 0 : PMD_INIT_FUNC_TRACE();
510 : :
511 : : /* NOTE: there are no requests to stop the NVS. */
512 : 0 : hn_nvs_disconn_rxbuf(hv);
513 : 0 : hn_nvs_disconn_chim(hv);
514 : 0 : }
515 : :
516 : : /*
517 : : * Ack the consumed RXBUF associated w/ this channel packet,
518 : : * so that this RXBUF can be recycled by the hypervisor.
519 : : */
520 : : void
521 : 0 : hn_nvs_ack_rxbuf(struct vmbus_channel *chan, uint64_t tid)
522 : : {
523 : : unsigned int retries = 0;
524 : 0 : struct hn_nvs_rndis_ack ack = {
525 : : .type = NVS_TYPE_RNDIS_ACK,
526 : : .status = NVS_STATUS_OK,
527 : : };
528 : : int error;
529 : :
530 : : PMD_RX_LOG(DEBUG, "ack RX id %" PRIu64, tid);
531 : :
532 : 0 : again:
533 : 0 : error = rte_vmbus_chan_send(chan, VMBUS_CHANPKT_TYPE_COMP,
534 : : &ack, sizeof(ack), tid,
535 : : VMBUS_CHANPKT_FLAG_NONE, NULL);
536 : :
537 [ # # ]: 0 : if (error == 0)
538 : 0 : return;
539 : :
540 [ # # ]: 0 : if (error == -EAGAIN) {
541 : : /*
542 : : * NOTE:
543 : : * This should _not_ happen in real world, since the
544 : : * consumption of the TX bufring from the TX path is
545 : : * controlled.
546 : : */
547 : : PMD_RX_LOG(NOTICE, "RXBUF ack retry");
548 [ # # ]: 0 : if (++retries < 10) {
549 : : rte_delay_ms(1);
550 : 0 : goto again;
551 : : }
552 : : }
553 : : /* RXBUF leaks! */
554 : 0 : PMD_DRV_LOG(ERR, "RXBUF ack failed");
555 : : }
556 : :
557 : : int
558 : 0 : hn_nvs_alloc_subchans(struct hn_data *hv, uint32_t *nsubch)
559 : : {
560 : : struct hn_nvs_subch_req req;
561 : : struct hn_nvs_subch_resp resp;
562 : : int error;
563 : :
564 : : memset(&req, 0, sizeof(req));
565 : 0 : req.type = NVS_TYPE_SUBCH_REQ;
566 : 0 : req.op = NVS_SUBCH_OP_ALLOC;
567 : 0 : req.nsubch = *nsubch;
568 : :
569 : 0 : error = hn_nvs_execute(hv, &req, sizeof(req),
570 : : &resp, sizeof(resp),
571 : : NVS_TYPE_SUBCH_RESP);
572 [ # # ]: 0 : if (error)
573 : : return error;
574 : :
575 [ # # ]: 0 : if (resp.status != NVS_STATUS_OK) {
576 : 0 : PMD_INIT_LOG(ERR,
577 : : "nvs subch alloc failed: %#x",
578 : : resp.status);
579 : 0 : return -EIO;
580 : : }
581 : :
582 [ # # ]: 0 : if (resp.nsubch > *nsubch) {
583 : 0 : PMD_INIT_LOG(NOTICE,
584 : : "%u subchans are allocated, requested %u",
585 : : resp.nsubch, *nsubch);
586 : : }
587 : 0 : *nsubch = resp.nsubch;
588 : :
589 : 0 : return 0;
590 : : }
591 : :
592 : : int
593 : 0 : hn_nvs_set_datapath(struct hn_data *hv, uint32_t path)
594 : : {
595 : : struct hn_nvs_datapath dp;
596 : : int error;
597 : :
598 [ # # ]: 0 : PMD_DRV_LOG(DEBUG, "set datapath %s",
599 : : path ? "VF" : "Synthetic");
600 : :
601 : : memset(&dp, 0, sizeof(dp));
602 : 0 : dp.type = NVS_TYPE_SET_DATAPATH;
603 : 0 : dp.active_path = path;
604 : :
605 : : error = hn_nvs_req_send(hv, &dp, sizeof(dp));
606 [ # # ]: 0 : if (error) {
607 : 0 : PMD_DRV_LOG(ERR,
608 : : "send set datapath failed: %d",
609 : : error);
610 : : }
611 : :
612 : 0 : return error;
613 : : }
|