// SPDX-License-Identifier: GPL-2.0 /****************************************************************************** * * Copyright (c) 2020 Intel Corporation * *****************************************************************************/ #include #include #include #include #include #include #include "pon_qos_tc_flower.h" #include "pon_qos_tc_ext_vlan.h" #include "pon_qos_tc_vlan_filter.h" #include "pon_qos_tc_qmap.h" #include "pon_qos_tc_mirred.h" #include "pon_qos_tc_police.h" #include "pon_qos_tc_trap.h" struct pon_qos_storage_node { /** Unique key for TC flower - flower cookie */ unsigned long key; /** Type of TC flower */ enum pon_qos_tc_flower_type type; /** Pointer to structure of specific type */ void *arg1; /** Pointer to structure of specific type */ void *arg2; /** Hash table identifier */ struct hlist_node node; }; static DEFINE_HASHTABLE(pon_qos_storage, 16); static char *flower_type_str_get(enum pon_qos_tc_flower_type type) { switch (type) { case TC_TYPE_EXT_VLAN: return "TC_TYPE_EXT_VLAN"; case TC_TYPE_VLAN_FILTER: return "TC_TYPE_VLAN_FILTER"; case TC_TYPE_QUEUE: return "TC_TYPE_QUEUE"; case TC_TYPE_MIRRED: return "TC_TYPE_MIRRED"; case TC_TYPE_POLICE: return "TC_TYPE_POLICE"; case TC_TYPE_COLMARK: return "TC_TYPE_COLMARK"; case TC_TYPE_TRAP: return "TC_TYPE_TRAP"; default: return "TC_TYPE_UNKNOWN"; } } int pon_qos_tc_flower_storage_add(struct net_device *dev, unsigned long cookie, enum pon_qos_tc_flower_type type, void *arg1, void *arg2) { struct pon_qos_storage_node *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM; entry->key = cookie; entry->type = type; entry->arg1 = arg1; entry->arg2 = arg2; hash_add(pon_qos_storage, &entry->node, entry->key); netdev_dbg(dev, "PON flower storage add, cookie: %lx, type: %s, arg1: %p, arg2: %p\n", entry->key, flower_type_str_get(entry->type), entry->arg1, entry->arg2); return 0; } static int flower_remove(struct net_device *dev, u32 handle, struct tc_cls_flower_offload *f, enum pon_qos_tc_flower_type type, void *arg1, void *arg2) { switch (type) { case TC_TYPE_EXT_VLAN: return pon_qos_tc_ext_vlan_del(dev, arg1, arg2); case TC_TYPE_VLAN_FILTER: return pon_qos_tc_vlan_filter_del(dev, arg1, arg2); case TC_TYPE_QUEUE: return pon_qos_tc_unmap(dev, arg1); case TC_TYPE_MIRRED: return pon_qos_mirred_unoffload(dev, f, handle); case TC_TYPE_POLICE: case TC_TYPE_COLMARK: return pon_qos_police_unoffload(dev, arg1, arg2); case TC_TYPE_TRAP: return pon_qos_trap_unoffload(dev, f, handle); default: return -EOPNOTSUPP; } return -EOPNOTSUPP; } static int pon_qos_tc_flower_storage_del(struct net_device *dev, u32 handle, struct tc_cls_flower_offload *f) { struct pon_qos_storage_node *entry; struct hlist_node *tmp; hash_for_each_possible_safe (pon_qos_storage, entry, tmp, node, f->cookie) { if (f->cookie != entry->key) continue; flower_remove(dev, handle, f, entry->type, entry->arg1, entry->arg2); netdev_dbg(dev, "PON flower storage del, cookie: %lx, type: %s\n", entry->key, flower_type_str_get(entry->type)); hash_del(&entry->node); kfree(entry); } return 0; } static bool is_type_mirred(struct net_device *dev, struct tc_cls_flower_offload *f) { const struct tc_action *a; LIST_HEAD(actions); if (tc_no_actions(f->exts)) return false; tcf_exts_to_list(f->exts, &actions); list_for_each_entry (a, &actions, list) if (is_tcf_mirred_redirect(a)) return true; return false; } static bool is_type_colmark(struct net_device *dev, struct tc_cls_flower_offload *f) { /* This driver supports only signle actions for now */ if (tc_no_actions(f->exts)) return false; return is_tcf_colmark(f->exts->actions[0]); } static bool is_type_police(struct net_device *dev, struct tc_cls_flower_offload *f) { /* This driver supports only signle actions for now */ if (tc_no_actions(f->exts)) return false; return is_tcf_police(f->exts->actions[0]); } static bool is_type_trap(struct net_device *dev, struct tc_cls_flower_offload *f) { const struct tc_action *a; LIST_HEAD(actions); if (tc_no_actions(f->exts)) return false; tcf_exts_to_list(f->exts, &actions); list_for_each_entry (a, &actions, list) if (((struct tcf_gact *)a)->tcf_action == TC_ACT_TRAP) return true; return false; } static bool is_type_queue(struct net_device *dev, struct tc_cls_flower_offload *f) { return !!f->classid; } static bool is_type_vlan_filter(struct net_device *dev, struct tc_cls_flower_offload *f) { struct net_device *indev = NULL; int ifi = (int)*(int *)f->key; if (ifi) { indev = dev_get_by_index(dev_net(dev), ifi); if (!indev) return false; if (!strncmp(dev->name, indev->name, strlen(dev->name))) { dev_put(indev); return true; } dev_put(indev); } return false; } static bool is_type_ext_vlan(struct net_device *dev, struct tc_cls_flower_offload *f) { if (dissector_uses_key(f->dissector, FLOW_DISSECTOR_KEY_BASIC) || dissector_uses_key(f->dissector, FLOW_DISSECTOR_KEY_VLAN) || dissector_uses_key(f->dissector, FLOW_DISSECTOR_KEY_CVLAN)) return true; return false; } static enum pon_qos_tc_flower_type flower_type_get(struct net_device *dev, struct tc_cls_flower_offload *f) { if (is_type_trap(dev, f)) return TC_TYPE_TRAP; if (is_type_mirred(dev, f)) return TC_TYPE_MIRRED; if (is_type_police(dev, f)) return TC_TYPE_POLICE; if (is_type_colmark(dev, f)) return TC_TYPE_COLMARK; if (is_type_queue(dev, f)) return TC_TYPE_QUEUE; if (is_type_vlan_filter(dev, f)) return TC_TYPE_VLAN_FILTER; if (is_type_ext_vlan(dev, f)) return TC_TYPE_EXT_VLAN; return TC_TYPE_UNKNOWN; } static int pon_qos_tc_flower_replace(struct net_device *dev, u32 handle, struct tc_cls_flower_offload *f) { enum pon_qos_tc_flower_type type; type = flower_type_get(dev, f); netdev_dbg(dev, "TC TYPE: %s", flower_type_str_get(type)); switch (type) { case TC_TYPE_EXT_VLAN: return pon_qos_tc_ext_vlan_add(dev, f); case TC_TYPE_VLAN_FILTER: return pon_qos_tc_vlan_filter_add(dev, f); case TC_TYPE_QUEUE: return pon_qos_tc_map(dev, f); case TC_TYPE_MIRRED: return pon_qos_mirred_offload(dev, f, handle); case TC_TYPE_POLICE: case TC_TYPE_COLMARK: return pon_qos_police_offload(dev, f); case TC_TYPE_TRAP: return pon_qos_trap_offload(dev, f, handle); default: return -EOPNOTSUPP; } return -EOPNOTSUPP; } static int pon_qos_tc_flower_destroy(struct net_device *dev, u32 handle, struct tc_cls_flower_offload *f) { return pon_qos_tc_flower_storage_del(dev, handle, f); } int pon_qos_tc_flower_offload(struct net_device *dev, u32 handle, struct tc_cls_flower_offload *f) { int ret = 0; ASSERT_RTNL(); netdev_dbg(dev, "%s:start %d\n", __func__, ret); if (TC_H_MAJ(f->common.classid) != TC_H_MAJ(TC_H_CLSACT)) return -EOPNOTSUPP; switch (f->command) { case TC_CLSFLOWER_REPLACE: { netdev_dbg(dev, "%s:replace:\n", __func__); ret = pon_qos_tc_flower_replace(dev, handle, f); break; } case TC_CLSFLOWER_DESTROY: { netdev_dbg(dev, "%s:destroy:\n", __func__); ret = pon_qos_tc_flower_destroy(dev, handle, f); break; } case TC_CLSFLOWER_STATS: { netdev_dbg(dev, "%s:stats:\n", __func__); return -EOPNOTSUPP; } default: return -EOPNOTSUPP; } netdev_dbg(dev, "%s:exit %d\n", __func__, ret); return ret; } EXPORT_SYMBOL(pon_qos_tc_flower_offload);