// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2020 Marvell International Ltd. All rights reserved */ #include #include "prestera.h" #include "prestera_hw.h" #include "prestera_acl.h" #include "prestera_span.h" struct prestera_acl { struct prestera_switch *sw; struct list_head rules; }; struct prestera_acl_ruleset { struct rhashtable rule_ht; struct prestera_switch *sw; u16 id; }; struct prestera_acl_rule { struct rhash_head ht_node; struct list_head list; struct list_head match_list; struct list_head action_list; struct prestera_flow_block *block; unsigned long cookie; u32 priority; u8 n_actions; u8 n_matches; u32 id; }; static const struct rhashtable_params prestera_acl_rule_ht_params = { .key_len = sizeof(unsigned long), .key_offset = offsetof(struct prestera_acl_rule, cookie), .head_offset = offsetof(struct prestera_acl_rule, ht_node), .automatic_shrinking = true, }; static struct prestera_acl_ruleset * prestera_acl_ruleset_create(struct prestera_switch *sw) { struct prestera_acl_ruleset *ruleset; int err; ruleset = kzalloc(sizeof(*ruleset), GFP_KERNEL); if (!ruleset) return ERR_PTR(-ENOMEM); err = rhashtable_init(&ruleset->rule_ht, &prestera_acl_rule_ht_params); if (err) goto err_rhashtable_init; err = prestera_hw_acl_ruleset_create(sw, &ruleset->id); if (err) goto err_ruleset_create; ruleset->sw = sw; return ruleset; err_ruleset_create: rhashtable_destroy(&ruleset->rule_ht); err_rhashtable_init: kfree(ruleset); return ERR_PTR(err); } static void prestera_acl_ruleset_destroy(struct prestera_acl_ruleset *ruleset) { prestera_hw_acl_ruleset_del(ruleset->sw, ruleset->id); rhashtable_destroy(&ruleset->rule_ht); kfree(ruleset); } struct prestera_flow_block * prestera_acl_block_create(struct prestera_switch *sw, struct net *net) { struct prestera_flow_block *block; block = kzalloc(sizeof(*block), GFP_KERNEL); if (!block) return NULL; INIT_LIST_HEAD(&block->binding_list); block->net = net; block->sw = sw; block->ruleset = prestera_acl_ruleset_create(sw); if (IS_ERR(block->ruleset)) { kfree(block); return NULL; } return block; } void prestera_acl_block_destroy(struct prestera_flow_block *block) { prestera_acl_ruleset_destroy(block->ruleset); WARN_ON(!list_empty(&block->binding_list)); kfree(block); } static struct prestera_flow_block_binding * prestera_acl_block_lookup(struct prestera_flow_block *block, struct prestera_port *port) { struct prestera_flow_block_binding *binding; list_for_each_entry(binding, &block->binding_list, list) if (binding->port == port) return binding; return NULL; } int prestera_acl_block_bind(struct prestera_flow_block *block, struct prestera_port *port) { struct prestera_flow_block_binding *binding; int err; if (WARN_ON(prestera_acl_block_lookup(block, port))) return -EEXIST; binding = kzalloc(sizeof(*binding), GFP_KERNEL); if (!binding) return -ENOMEM; binding->span_id = PRESTERA_SPAN_INVALID_ID; binding->port = port; err = prestera_hw_acl_port_bind(port, block->ruleset->id); if (err) goto err_rules_bind; list_add(&binding->list, &block->binding_list); return 0; err_rules_bind: kfree(binding); return err; } int prestera_acl_block_unbind(struct prestera_flow_block *block, struct prestera_port *port) { struct prestera_flow_block_binding *binding; binding = prestera_acl_block_lookup(block, port); if (!binding) return -ENOENT; list_del(&binding->list); prestera_hw_acl_port_unbind(port, block->ruleset->id); kfree(binding); return 0; } struct prestera_acl_ruleset * prestera_acl_block_ruleset_get(struct prestera_flow_block *block) { return block->ruleset; } u16 prestera_acl_rule_ruleset_id_get(const struct prestera_acl_rule *rule) { return rule->block->ruleset->id; } struct net *prestera_acl_block_net(struct prestera_flow_block *block) { return block->net; } struct prestera_switch *prestera_acl_block_sw(struct prestera_flow_block *block) { return block->sw; } struct prestera_acl_rule * prestera_acl_rule_lookup(struct prestera_acl_ruleset *ruleset, unsigned long cookie) { return rhashtable_lookup_fast(&ruleset->rule_ht, &cookie, prestera_acl_rule_ht_params); } struct prestera_acl_rule * prestera_acl_rule_create(struct prestera_flow_block *block, unsigned long cookie) { struct prestera_acl_rule *rule; rule = kzalloc(sizeof(*rule), GFP_KERNEL); if (!rule) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&rule->match_list); INIT_LIST_HEAD(&rule->action_list); rule->cookie = cookie; rule->block = block; return rule; } struct list_head * prestera_acl_rule_match_list_get(struct prestera_acl_rule *rule) { return &rule->match_list; } struct list_head * prestera_acl_rule_action_list_get(struct prestera_acl_rule *rule) { return &rule->action_list; } int prestera_acl_rule_action_add(struct prestera_acl_rule *rule, struct prestera_acl_rule_action_entry *entry) { struct prestera_acl_rule_action_entry *a_entry; a_entry = kmalloc(sizeof(*a_entry), GFP_KERNEL); if (!a_entry) return -ENOMEM; memcpy(a_entry, entry, sizeof(*entry)); list_add(&a_entry->list, &rule->action_list); rule->n_actions++; return 0; } u8 prestera_acl_rule_action_len(struct prestera_acl_rule *rule) { return rule->n_actions; } u32 prestera_acl_rule_priority_get(struct prestera_acl_rule *rule) { return rule->priority; } void prestera_acl_rule_priority_set(struct prestera_acl_rule *rule, u32 priority) { rule->priority = priority; } int prestera_acl_rule_match_add(struct prestera_acl_rule *rule, struct prestera_acl_rule_match_entry *entry) { struct prestera_acl_rule_match_entry *m_entry; m_entry = kmalloc(sizeof(*m_entry), GFP_KERNEL); if (!m_entry) return -ENOMEM; memcpy(m_entry, entry, sizeof(*entry)); list_add(&m_entry->list, &rule->match_list); rule->n_matches++; return 0; } u8 prestera_acl_rule_match_len(struct prestera_acl_rule *rule) { return rule->n_matches; } void prestera_acl_rule_destroy(struct prestera_acl_rule *rule) { struct prestera_acl_rule_action_entry *a_entry; struct prestera_acl_rule_match_entry *m_entry; struct list_head *pos, *n; list_for_each_safe(pos, n, &rule->match_list) { m_entry = list_entry(pos, typeof(*m_entry), list); list_del(pos); kfree(m_entry); } list_for_each_safe(pos, n, &rule->action_list) { a_entry = list_entry(pos, typeof(*a_entry), list); list_del(pos); kfree(a_entry); } kfree(rule); } int prestera_acl_rule_add(struct prestera_switch *sw, struct prestera_acl_rule *rule) { u32 rule_id; int err; /* try to add rule to hash table first */ err = rhashtable_insert_fast(&rule->block->ruleset->rule_ht, &rule->ht_node, prestera_acl_rule_ht_params); if (err) return err; /* add rule to hw */ err = prestera_hw_acl_rule_add(sw, rule, &rule_id); if (err) goto err_rule_add; rule->id = rule_id; list_add_tail(&rule->list, &sw->acl->rules); return 0; err_rule_add: rhashtable_remove_fast(&rule->block->ruleset->rule_ht, &rule->ht_node, prestera_acl_rule_ht_params); return err; } void prestera_acl_rule_del(struct prestera_switch *sw, struct prestera_acl_rule *rule) { rhashtable_remove_fast(&rule->block->ruleset->rule_ht, &rule->ht_node, prestera_acl_rule_ht_params); list_del(&rule->list); prestera_hw_acl_rule_del(sw, rule->id); } int prestera_acl_rule_get_stats(struct prestera_switch *sw, struct prestera_acl_rule *rule, u64 *packets, u64 *bytes, u64 *last_use) { u64 current_packets; u64 current_bytes; int err; err = prestera_hw_acl_rule_stats_get(sw, rule->id, ¤t_packets, ¤t_bytes); if (err) return err; *packets = current_packets; *bytes = current_bytes; *last_use = jiffies; return 0; } int prestera_acl_init(struct prestera_switch *sw) { struct prestera_acl *acl; acl = kzalloc(sizeof(*acl), GFP_KERNEL); if (!acl) return -ENOMEM; INIT_LIST_HEAD(&acl->rules); sw->acl = acl; acl->sw = sw; return 0; } void prestera_acl_fini(struct prestera_switch *sw) { struct prestera_acl *acl = sw->acl; WARN_ON(!list_empty(&acl->rules)); kfree(acl); }