LCOV - code coverage report
Current view: top level - root/contrail/vrouter/dp-core - vr_offloads.c (source / functions) Hit Total Coverage
Test: OpenSDN C/C++ coverage (all TARGET_SET jobs) Lines: 15 283 5.3 %
Date: 2026-08-03 02:19:58 Functions: 4 16 25.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * vr_offloads.c -- datapath flow offloads management
       3             :  *
       4             :  * Copyright 2018 Mellanox Technologies, Ltd
       5             :  */
       6             : #include <vrouter.h>
       7             : #include <vr_offloads_dp.h>
       8             : #include <vr_btable.h>
       9             : #include <vr_packet.h>
      10             : 
      11             : extern unsigned int vr_interfaces;
      12             : extern unsigned int vr_flow_entries;
      13             : extern unsigned int vr_oflow_entries;
      14             : 
      15             : struct vr_interface *pvif;
      16             : unsigned int host_ip;
      17             : struct vr_btable *offload_flows;
      18             : struct vr_btable *offload_tags;
      19             : unsigned int datapath_offloads;
      20             : 
      21             : static int
      22           0 : vr_offloads_interface_add(struct vr_interface *vif)
      23             : {
      24           0 :     if (!vif)
      25           0 :         return -EINVAL;
      26           0 :     if (vif->vif_type == VIF_TYPE_PHYSICAL) {
      27           0 :         if (pvif && pvif != vif)
      28           0 :             vr_printf("offload: More than one physical interface\n");
      29             :         else
      30           0 :             pvif = vif;
      31           0 :     } else if (vif->vif_type == VIF_TYPE_HOST) {
      32           0 :         if (host_ip && vif->vif_ip != host_ip)
      33           0 :             vr_printf("offload: More than one host interface\n");
      34             :         else
      35           0 :             host_ip = vif->vif_ip;
      36             :     }
      37           0 :     return 0;
      38             : }
      39             : 
      40             : static int
      41           0 : vr_offloads_interface_del(struct vr_interface *vif)
      42             : {
      43           0 :     if (!vif)
      44           0 :         return -EINVAL;
      45           0 :     if (vif->vif_type == VIF_TYPE_PHYSICAL) {
      46           0 :         if(vif != pvif)
      47           0 :             vr_printf("offload: More than one physical interface\n");
      48             :         else
      49           0 :             pvif = NULL;
      50           0 :     } else if (vif->vif_type == VIF_TYPE_HOST) {
      51           0 :         if (vif->vif_ip != host_ip)
      52           0 :             vr_printf("offload: More than one host interface\n");
      53             :         else
      54           0 :             host_ip = 0;
      55             :     }
      56             : 
      57           0 :     return 0;
      58             : }
      59             : 
      60             : static bool
      61           0 : vr_offloads_is_offloaded_nexthop(struct vr_nexthop *nh)
      62             : {
      63           0 :     return ((nh->nh_flags & NH_FLAG_VALID) && !(nh->nh_flags & NH_FLAG_MCAST) &&
      64           0 :         (nh->nh_flags & NH_FLAG_POLICY_ENABLED) && (nh->nh_type == NH_ENCAP) &&
      65           0 :         nh->nh_dev);
      66             : }
      67             : 
      68             : static int
      69           0 : vr_offloads_mpls_add(struct vr_nexthop *nh, int label)
      70             : {
      71             :     struct vr_offload_tag *otag;
      72             : 
      73           0 :     if (!nh)
      74           0 :         return -EINVAL;
      75           0 :     if (!vr_offloads_is_offloaded_nexthop(nh))
      76           0 :         return 0;
      77             : 
      78           0 :     otag = (struct vr_offload_tag *)vr_btable_get(offload_tags, nh->nh_dev->vif_idx);
      79           0 :     if (!otag) {
      80           0 :         vr_printf("offload: Invalid tag for nexthop ID %u\n",nh->nh_id);
      81           0 :         return 0;
      82             :     }
      83             : 
      84           0 :     if (nh->nh_family == AF_BRIDGE && (nh->nh_flags & NH_FLAG_L2_CONTROL_DATA))
      85           0 :         otag += VR_OFFLOADS_TAG_TYPE_MPLS_L2;
      86           0 :     else if (nh->nh_family != AF_BRIDGE)
      87           0 :         otag += VR_OFFLOADS_TAG_TYPE_MPLS_L3;
      88             :     else
      89           0 :         return 0;
      90             : 
      91           0 :     if (!otag->nh) {
      92           0 :         otag->tag = label;
      93           0 :         otag->nh = nh;
      94           0 :     } else if (otag->tag != label) {
      95           0 :         vr_printf("offload: 2 different MPLS labels (%u,%u) point to the same"
      96             :                   " tag type of vif %u nexthop %u\n", otag->tag, label,
      97           0 :                   nh->nh_dev->vif_idx, nh->nh_id);
      98             :     }
      99             : 
     100           0 :     return 0;
     101             : }
     102             : 
     103             : static int
     104           0 : vr_offloads_vxlan_add(struct vr_nexthop * nh, int vnid)
     105             : {
     106             :     struct vr_offload_tag *otag;
     107             : 
     108           0 :     if (!nh)
     109           0 :         return -EINVAL;
     110           0 :     if (!vr_offloads_is_offloaded_nexthop(nh))
     111           0 :         return 0;
     112             : 
     113           0 :     otag = (struct vr_offload_tag *)vr_btable_get(offload_tags, nh->nh_dev->vif_idx);
     114           0 :     if (!otag) {
     115           0 :         vr_printf("offload: Invalid tag for nexthop ID %u\n",nh->nh_id);
     116           0 :         return 0;
     117             :     }
     118             : 
     119           0 :     otag += VR_OFFLOADS_TAG_TYPE_VXLAN;
     120             : 
     121           0 :     if (!otag->nh) {
     122           0 :         otag->tag = vnid;
     123           0 :         otag->nh = nh;
     124           0 :     } else if (otag->tag != vnid) {
     125           0 :         vr_printf("offload: 2 different VXLAN VNIs (%u,%u) point to the same"
     126             :                   " tag type of vif %u nexthop %u\n", otag->tag, vnid,
     127           0 :                   nh->nh_dev->vif_idx, nh->nh_id);
     128             :     }
     129             : 
     130           0 :     return 0;
     131             : }
     132             : 
     133             : static int
     134           0 : vr_offloads_mpls_del(int label)
     135             : {
     136             :     struct vr_offload_tag *otag;
     137           0 :     struct vrouter *router = vrouter_get(0);
     138           0 :     struct vr_nexthop *nh = __vrouter_get_label(router, label);
     139             : 
     140           0 :     if (!nh)
     141           0 :         return -EINVAL;
     142             : 
     143           0 :     if (!vr_offloads_is_offloaded_nexthop(nh))
     144           0 :         return 0;
     145             : 
     146           0 :     otag = (struct vr_offload_tag *)vr_btable_get(offload_tags, nh->nh_dev->vif_idx);
     147           0 :     if (!otag) {
     148           0 :         vr_printf("offload: Invalid tag for nexthop ID %u\n",nh->nh_id);
     149           0 :         return 0;
     150             :     }
     151             : 
     152           0 :     if (nh->nh_family == AF_BRIDGE && (nh->nh_flags & NH_FLAG_L2_CONTROL_DATA))
     153           0 :         otag += VR_OFFLOADS_TAG_TYPE_MPLS_L2;
     154           0 :     else if (nh->nh_family != AF_BRIDGE)
     155           0 :         otag += VR_OFFLOADS_TAG_TYPE_MPLS_L3;
     156             :     else
     157           0 :         return 0;
     158             : 
     159           0 :     if (otag->nh && otag->tag == label)
     160           0 :         memset(otag, 0, sizeof(*otag));
     161             :     else
     162           0 :         vr_printf("offload: Unexpected tag %u for MPLS label %u, nexthop %u\n",
     163             :                   otag->tag, label, nh->nh_id);
     164             : 
     165           0 :     return 0;
     166             : }
     167             : 
     168             : static int
     169           0 : vr_offloads_vxlan_del(int vnid)
     170             : {
     171             :     struct vr_offload_tag *otag;
     172           0 :     struct vrouter *router = vrouter_get(0);
     173           0 :     struct vr_nexthop *nh = (struct vr_nexthop *)vr_itable_get(router->vr_vxlan_table, vnid);
     174             : 
     175           0 :     if (!nh)
     176           0 :         return -EINVAL;
     177             : 
     178           0 :     if (!vr_offloads_is_offloaded_nexthop(nh))
     179           0 :         return 0;
     180             : 
     181           0 :     otag = (struct vr_offload_tag *)vr_btable_get(offload_tags, nh->nh_dev->vif_idx);
     182           0 :     if (!otag) {
     183           0 :         vr_printf("offload: Invalid tag for nexthop ID %u\n",nh->nh_id);
     184           0 :         return 0;
     185             :     }
     186             : 
     187           0 :     otag += VR_OFFLOADS_TAG_TYPE_VXLAN;
     188             : 
     189           0 :     if (otag->nh && otag->tag == vnid)
     190           0 :         memset(otag, 0, sizeof(*otag));
     191             :     else
     192           0 :         vr_printf("offload: Unexpected tag %u for VXLAN vni %u, nexthop %u\n",
     193             :                   otag->tag, vnid, nh->nh_id);
     194             : 
     195           0 :     return 0;
     196             : }
     197             : 
     198             : static int
     199           0 : vr_offloads_flow_del(struct vr_flow_entry * fe)
     200             : {
     201             :     struct vr_offload_flow *oflow;
     202           0 :     int ret = 0;
     203             : 
     204           0 :     if (!fe)
     205           0 :         return -EINVAL;
     206             : 
     207           0 :     if (!offload_flows)
     208           0 :         return 0;
     209             : 
     210           0 :     oflow = (struct vr_offload_flow *)vr_btable_get(offload_flows,
     211             :                                                     fe->fe_hentry.hentry_index);
     212           0 :     if (!oflow) {
     213           0 :         vr_printf("offload: Invalid tag for flow ID %u\n",
     214             :                   fe->fe_hentry.hentry_index);
     215           0 :         return 0;
     216             :     }
     217             : 
     218           0 :     if(oflow->flow_handle) {
     219           0 :         ret = vr_offload_flow_destroy(oflow);
     220           0 :         if (!ret)
     221           0 :             memset(oflow, 0, sizeof(*oflow));
     222             :         else
     223           0 :             vr_printf("offload: Failed to destroy flow ID %u\n", oflow->fe_index);
     224             :     }
     225             : 
     226           0 :     return 0;
     227             : }
     228             : 
     229             : static int
     230           0 : vr_offloads_flow_set(struct vr_flow_entry * fe, unsigned int fe_index,
     231             :                         struct vr_flow_entry * rfe)
     232             : {
     233             :     struct vr_nexthop *nh, *snh;
     234             :     struct vr_offload_flow *oflow;
     235             :     struct vr_offload_tag *otag;
     236           0 :     struct vrouter *router = vrouter_get(0);
     237           0 :     int ret = 0;
     238             : 
     239           0 :     if (!fe)
     240           0 :         return -EINVAL;
     241             : 
     242           0 :     if (!pvif || !host_ip) {
     243           0 :         vr_printf("offload: Missing physical/host interface\n");
     244           0 :         return 0;
     245             :     }
     246             : 
     247           0 :     oflow = (struct vr_offload_flow *)vr_btable_get(offload_flows, fe_index);
     248           0 :     if (!oflow) {
     249           0 :         vr_printf("offload: Invalid tag for flow ID %u\n", fe_index);
     250           0 :         return 0;
     251             :     }
     252             : 
     253             :     /* For flow change do destroy and create */
     254           0 :     if (oflow->flow_handle) {
     255           0 :         ret = vr_offload_flow_destroy(oflow);
     256           0 :         if (!ret) {
     257           0 :             memset(oflow, 0, sizeof(*oflow));
     258             :         } else {
     259           0 :             vr_printf("offload: Failed to change flow ID %u\n", oflow->fe_index);
     260           0 :             return 0;
     261             :         }
     262             :     }
     263             : 
     264           0 :     nh = __vrouter_get_nexthop(router, fe->fe_key.flow_nh_id);
     265           0 :     if (!nh)
     266           0 :         return -EINVAL;
     267             : 
     268           0 :     if (!(fe->fe_flags & VR_FLOW_FLAG_ACTIVE) || fe->fe_action != VR_FLOW_ACTION_FORWARD ||
     269           0 :         !vr_offloads_is_offloaded_nexthop(nh))
     270             :         /* Not a valid flow to be offloaded */
     271           0 :         return 0;
     272             : 
     273           0 :     snh = __vrouter_get_nexthop(router, fe->fe_src_nh_index);
     274           0 :     if (!snh)
     275           0 :         return -EINVAL;
     276             : 
     277           0 :     if (snh->nh_type != NH_TUNNEL || !(snh->nh_flags & NH_FLAG_VALID))
     278             :         /* Not a valid flow to be offloaded */
     279           0 :         return 0;
     280             : 
     281           0 :     otag = (struct vr_offload_tag *)vr_btable_get(offload_tags, nh->nh_dev->vif_idx);
     282           0 :     if (!otag) {
     283           0 :         vr_printf("offload: Invalid tag for nexthop ID %u\n", nh->nh_id);
     284           0 :         return 0;
     285             :     }
     286             : 
     287           0 :     if (snh->nh_flags & NH_FLAG_TUNNEL_GRE) {
     288           0 :         if (otag[VR_OFFLOADS_TAG_TYPE_MPLS_L2].nh && (nh->nh_dev->vif_flags &
     289             :                                                       VIF_FLAG_L2_ENABLED)) {
     290           0 :             oflow->tunnel_type = NH_FLAG_TUNNEL_GRE;
     291           0 :             oflow->tunnel_tag =otag[VR_OFFLOADS_TAG_TYPE_MPLS_L2].tag;
     292           0 :             oflow->nh = otag[VR_OFFLOADS_TAG_TYPE_MPLS_L2].nh;
     293           0 :             oflow->is_mpls_l2 = true;
     294           0 :         } else if (otag[VR_OFFLOADS_TAG_TYPE_MPLS_L3].nh && (nh->nh_dev->vif_flags &
     295             :                                                              VIF_FLAG_L3_ENABLED)) {
     296           0 :             oflow->tunnel_type = NH_FLAG_TUNNEL_GRE;
     297           0 :             oflow->tunnel_tag = otag[VR_OFFLOADS_TAG_TYPE_MPLS_L3].tag;
     298           0 :             oflow->nh = otag[VR_OFFLOADS_TAG_TYPE_MPLS_L3].nh;
     299           0 :             oflow->is_mpls_l2 = false;
     300             :         }
     301           0 :     } else if (snh->nh_flags & NH_FLAG_TUNNEL_UDP_MPLS) {
     302           0 :         if (otag[VR_OFFLOADS_TAG_TYPE_MPLS_L2].nh && (nh->nh_dev->vif_flags &
     303             :                                                       VIF_FLAG_L2_ENABLED)) {
     304           0 :             oflow->tunnel_type = NH_FLAG_TUNNEL_UDP_MPLS;
     305           0 :             oflow->tunnel_tag =otag[VR_OFFLOADS_TAG_TYPE_MPLS_L2].tag;
     306           0 :             oflow->nh = otag[VR_OFFLOADS_TAG_TYPE_MPLS_L2].nh;
     307           0 :             oflow->is_mpls_l2 = true;
     308           0 :         } else if (otag[VR_OFFLOADS_TAG_TYPE_MPLS_L3].nh && (nh->nh_dev->vif_flags &
     309             :                                                              VIF_FLAG_L3_ENABLED)) {
     310           0 :             oflow->tunnel_type = NH_FLAG_TUNNEL_UDP_MPLS;
     311           0 :             oflow->tunnel_tag = otag[VR_OFFLOADS_TAG_TYPE_MPLS_L3].tag;
     312           0 :             oflow->nh = otag[VR_OFFLOADS_TAG_TYPE_MPLS_L3].nh;
     313           0 :             oflow->is_mpls_l2 = false;
     314             :         }
     315           0 :     } else if (snh->nh_flags & NH_FLAG_TUNNEL_VXLAN) {
     316           0 :         if (otag[VR_OFFLOADS_TAG_TYPE_VXLAN].nh) {
     317           0 :             oflow->tunnel_type = NH_FLAG_TUNNEL_VXLAN;
     318           0 :             oflow->tunnel_tag = otag[VR_OFFLOADS_TAG_TYPE_VXLAN].tag;
     319           0 :             oflow->nh = otag[VR_OFFLOADS_TAG_TYPE_VXLAN].nh;
     320             :         }
     321             :     } else {
     322             :         /* Not a valid flow to be offloaded */
     323           0 :         return 0;
     324             :     }
     325             : 
     326           0 :     if (!oflow->nh) {
     327           0 :         vr_printf("offload: Invalid tag type for flow create\n");
     328           0 :         return 0;
     329             :     }
     330             : 
     331           0 :     oflow->pvif = pvif;
     332           0 :     oflow->fe = fe;
     333           0 :     oflow->ip = host_ip;
     334           0 :     oflow->fe_index = fe_index;
     335             : 
     336           0 :     ret = vr_offload_flow_create(oflow);
     337           0 :     if (ret) {
     338           0 :         vr_printf("offload: Failed to create flow ID %u\n", fe_index);
     339           0 :         memset(oflow, 0, sizeof(*oflow));
     340             :     }
     341             : 
     342           0 :     return 0;
     343             : }
     344             : 
     345             : struct vr_offload_ops vr_offload_ops = {
     346             :     .voo_flow_set = vr_offloads_flow_set,
     347             :     .voo_flow_del = vr_offloads_flow_del,
     348             :     .voo_interface_add = vr_offloads_interface_add,
     349             :     .voo_interface_del = vr_offloads_interface_del,
     350             :     .voo_mpls_add = vr_offloads_mpls_add,
     351             :     .voo_mpls_del = vr_offloads_mpls_del,
     352             :     .voo_vxlan_add = vr_offloads_vxlan_add,
     353             :     .voo_vxlan_del = vr_offloads_vxlan_del,
     354             : };
     355             : 
     356             : int
     357          53 : vr_offloads_init(struct vrouter *router)
     358             : {
     359             :     unsigned int entry_size;
     360             :     struct vr_offload_ops *offload;
     361             : 
     362          53 :     if (!datapath_offloads)
     363          53 :         return 0;
     364             : 
     365             :     /* Do not initialize twice. E.g. a soft reset would not have unregistered
     366             :      * the offloads. */
     367           0 :     offload = vr_rcu_dereference(offload_ops);
     368           0 :     if (offload)
     369           0 :         return 0;
     370             : 
     371           0 :     if (!vr_offload_flow_destroy || !vr_offload_flow_create ||
     372           0 :         !vr_offload_prepare) {
     373             :         /* Not an error necessarily. Offloads are not implemented for this host
     374             :          * type, so don't register anything. External implementation can still
     375             :          * be registered after initialization. */
     376           0 :         vr_printf("offload: no built-in offload implementation for current context\n");
     377           0 :         return 0;
     378             :     }
     379             : 
     380           0 :     if (!offload_tags) {
     381             :         /* Round up to the next divisor */
     382           0 :         for (entry_size = sizeof(struct vr_offload_tag) * VR_OFFLOADS_TAG_TYPE_MAX;
     383           0 :              VR_SINGLE_ALLOC_LIMIT % entry_size; entry_size++);
     384           0 :         offload_tags = vr_btable_alloc(vr_interfaces, entry_size);
     385           0 :         if (!offload_tags) {
     386           0 :             return -ENOMEM;
     387             :         }
     388             :     }
     389             : 
     390           0 :     if (!offload_flows) {
     391             :         /* Round up to the next divisor */
     392           0 :         for (entry_size = sizeof(struct vr_offload_flow);
     393           0 :              VR_SINGLE_ALLOC_LIMIT % entry_size; entry_size++);
     394           0 :         offload_flows = vr_btable_alloc(vr_flow_entries + vr_oflow_entries, entry_size);
     395           0 :         if (!offload_flows) {
     396           0 :             vr_btable_free(offload_tags);
     397           0 :             offload_tags = NULL;
     398           0 :             return -ENOMEM;
     399             :         }
     400             :     }
     401             : 
     402           0 :     vr_offload_register(&vr_offload_ops);
     403             : 
     404           0 :     return 0;
     405             : 
     406             : }
     407             : 
     408             : static void
     409          53 : _vr_offloads_exit(struct vrouter *router, bool soft_reset)
     410             : {
     411             :     struct vr_offload_flow *oflow;
     412             :     struct vr_offload_tag *otag;
     413             :     unsigned int i;
     414             :     unsigned int entry_num, entry_size;
     415             : 
     416          53 :     if (!datapath_offloads)
     417          53 :         return;
     418             : 
     419           0 :     if (!vr_offload_flow_destroy || !vr_offload_flow_create ||
     420           0 :         !vr_offload_prepare)
     421           0 :         return;
     422             : 
     423           0 :     if (offload_flows) {
     424           0 :         entry_num = vr_btable_entries(offload_flows);
     425           0 :         entry_size = vr_btable_size(offload_flows) / entry_num;
     426           0 :         for (i = 0; i < entry_num; i++) {
     427           0 :             oflow = (struct vr_offload_flow *)vr_btable_get(offload_flows, i);
     428           0 :             if (!oflow)
     429           0 :                 continue;
     430           0 :             if(oflow->flow_handle)
     431           0 :                 vr_offload_flow_destroy(oflow);
     432           0 :             memset(oflow, 0, entry_size);
     433             :         }
     434             : 
     435           0 :         if (!soft_reset) {
     436           0 :             vr_btable_free(offload_flows);
     437           0 :             offload_flows = NULL;
     438             :         }
     439             :     }
     440             : 
     441           0 :     if (offload_tags) {
     442           0 :         entry_num = vr_btable_entries(offload_tags);
     443           0 :         entry_size = vr_btable_size(offload_tags) / entry_num;
     444           0 :         for (i = 0; i < entry_num; i++) {
     445           0 :             otag = (struct vr_offload_tag *)vr_btable_get(offload_tags, i);
     446           0 :             if (otag)
     447           0 :                     memset(otag, 0, entry_size);
     448             :         }
     449             : 
     450           0 :         if (!soft_reset) {
     451           0 :             vr_btable_free(offload_tags);
     452           0 :             offload_tags = NULL;
     453             :         }
     454             :     }
     455             : 
     456           0 :     pvif = NULL;
     457           0 :     host_ip = 0;
     458             : }
     459             : 
     460             : inline struct vr_offload_flow *
     461           0 : vr_offloads_flow_get(unsigned int index)
     462             : {
     463             :     struct vr_offload_flow *oflow = (struct vr_offload_flow *)
     464           0 :                                     vr_btable_get(offload_flows, index);
     465             : 
     466           0 :     if (!oflow || !oflow->flow_handle)
     467             :         /* An invalid packet flow */
     468           0 :         return NULL;
     469             : 
     470           0 :     return  oflow;
     471             : }
     472             : 
     473             : /*
     474             :  * Called by an external offload module to register itself with vrouter.
     475             :  */
     476             : int
     477           0 : vr_offload_register(const struct vr_offload_ops *new_handler)
     478             : {
     479             :     struct vr_offload_ops *offload;
     480             : 
     481           0 :     if (!datapath_offloads || !new_handler)
     482           0 :         return -EINVAL;
     483             : 
     484           0 :     offload = vr_rcu_dereference(offload_ops);
     485           0 :     if (offload)
     486           0 :         return -EBUSY;
     487             : 
     488           0 :     offload = vr_malloc(sizeof(*offload), VR_MALLOC_OBJECT);
     489           0 :     if (!offload)
     490           0 :         return -ENOMEM;
     491           0 :     *offload = *new_handler;
     492           0 :     vr_rcu_assign_pointer(offload_ops, offload);
     493           0 :     vr_synchronize_rcu();
     494             : 
     495           0 :     return 0;
     496             : }
     497             : #if defined(__linux__) && defined(__KERNEL__)
     498             : EXPORT_SYMBOL(vr_offload_register);
     499             : #endif
     500             : 
     501             : /*
     502             :  * Called by an external offload module to unregister itself with vrouter.
     503             :  */
     504             : static void
     505          53 : _vr_offload_unregister(void)
     506             : {
     507          53 :     struct vr_offload_ops *offload = vr_rcu_dereference(offload_ops);
     508             : 
     509          53 :     if (offload) {
     510           0 :         vr_rcu_assign_pointer(offload_ops, NULL);
     511           0 :         vr_synchronize_rcu();
     512           0 :         vr_free(offload, VR_MALLOC_OBJECT);
     513             :     }
     514          53 : }
     515             : 
     516             : int
     517           0 : vr_offload_unregister(void)
     518             : {
     519           0 :     struct vrouter *router = vrouter_get(0);
     520             : 
     521           0 :     _vr_offloads_exit(router, false);
     522           0 :     _vr_offload_unregister();
     523             : 
     524           0 :     return 0;
     525             : }
     526             : #if defined(__linux__) && defined(__KERNEL__)
     527             : EXPORT_SYMBOL(vr_offload_unregister);
     528             : #endif
     529             : 
     530             : void
     531          53 : vr_offloads_exit(struct vrouter *router, bool soft_reset)
     532             : {
     533          53 :     _vr_offloads_exit(router, soft_reset);
     534          53 :     if (!soft_reset)
     535          53 :         _vr_offload_unregister();
     536          53 : }
     537             : 
     538             : /* Statistics update functions used by offload module */
     539             : #if defined(__linux__) && defined(__KERNEL__)
     540             : EXPORT_SYMBOL(vr_flow_incr_stats);
     541             : EXPORT_SYMBOL(vr_nexthop_update_offload_vrfstats);
     542             : #endif

Generated by: LCOV version 1.14