+
+/* Flow offload API. */
+static uint32_t
+netdev_dummy_flow_hash(const ovs_u128 *ufid)
+{
+ return ufid->u32[0];
+}
+
+static struct offloaded_flow *
+find_offloaded_flow(const struct hmap *offloaded_flows, const ovs_u128 *ufid)
+{
+ uint32_t hash = netdev_dummy_flow_hash(ufid);
+ struct offloaded_flow *data;
+
+ HMAP_FOR_EACH_WITH_HASH (data, node, hash, offloaded_flows) {
+ if (ovs_u128_equals(*ufid, data->ufid)) {
+ return data;
+ }
+ }
+
+ return NULL;
+}
+
+static int
+netdev_dummy_flow_put(struct netdev *netdev, struct match *match,
+ struct nlattr *actions OVS_UNUSED,
+ size_t actions_len OVS_UNUSED,
+ const ovs_u128 *ufid, struct offload_info *info,
+ struct dpif_flow_stats *stats)
+{
+ struct netdev_dummy *dev = netdev_dummy_cast(netdev);
+ struct offloaded_flow *off_flow;
+ bool modify = true;
+
+ ovs_mutex_lock(&dev->mutex);
+
+ off_flow = find_offloaded_flow(&dev->offloaded_flows, ufid);
+ if (!off_flow) {
+ /* Create new offloaded flow. */
+ off_flow = xzalloc(sizeof *off_flow);
+ memcpy(&off_flow->ufid, ufid, sizeof *ufid);
+ hmap_insert(&dev->offloaded_flows, &off_flow->node,
+ netdev_dummy_flow_hash(ufid));
+ modify = false;
+ }
+
+ off_flow->mark = info->flow_mark;
+ memcpy(&off_flow->match, match, sizeof *match);
+
+ /* As we have per-netdev 'offloaded_flows', we don't need to match
+ * the 'in_port' for received packets. This will also allow offloading for
+ * packets passed to 'receive' command without specifying the 'in_port'. */
+ off_flow->match.wc.masks.in_port.odp_port = 0;
+
+ ovs_mutex_unlock(&dev->mutex);
+
+ if (VLOG_IS_DBG_ENABLED()) {
+ struct ds ds = DS_EMPTY_INITIALIZER;
+
+ ds_put_format(&ds, "%s: flow put[%s]: ", netdev_get_name(netdev),
+ modify ? "modify" : "create");
+ odp_format_ufid(ufid, &ds);
+ ds_put_cstr(&ds, " flow match: ");
+ match_format(match, NULL, &ds, OFP_DEFAULT_PRIORITY);
+ ds_put_format(&ds, ", mark: %"PRIu32, info->flow_mark);
+
+ VLOG_DBG("%s", ds_cstr(&ds));
+ ds_destroy(&ds);
+ }
+
+ if (stats) {
+ memset(stats, 0, sizeof *stats);
+ }
+ return 0;
+}
+
+static int
+netdev_dummy_flow_del(struct netdev *netdev, const ovs_u128 *ufid,
+ struct dpif_flow_stats *stats)
+{
+ struct netdev_dummy *dev = netdev_dummy_cast(netdev);
+ struct offloaded_flow *off_flow;
+ const char *error = NULL;
+ uint32_t mark;
+
+ ovs_mutex_lock(&dev->mutex);
+
+ off_flow = find_offloaded_flow(&dev->offloaded_flows, ufid);
+ if (!off_flow) {
+ error = "No such flow.";
+ goto exit;
+ }
+
+ mark = off_flow->mark;
+ hmap_remove(&dev->offloaded_flows, &off_flow->node);
+ free(off_flow);
+
+exit:
+ ovs_mutex_unlock(&dev->mutex);
+
+ if (error || VLOG_IS_DBG_ENABLED()) {
+ struct ds ds = DS_EMPTY_INITIALIZER;
+
+ ds_put_format(&ds, "%s: ", netdev_get_name(netdev));
+ if (error) {
+ ds_put_cstr(&ds, "failed to ");
+ }
+ ds_put_cstr(&ds, "flow del: ");
+ odp_format_ufid(ufid, &ds);
+ if (error) {
+ ds_put_format(&ds, " error: %s", error);
+ } else {
+ ds_put_format(&ds, " mark: %"PRIu32, mark);
+ }
+ VLOG(error ? VLL_WARN : VLL_DBG, "%s", ds_cstr(&ds));
+ ds_destroy(&ds);
+ }
+
+ if (stats) {
+ memset(stats, 0, sizeof *stats);
+ }
+ return error ? -1 : 0;
+}