Commit 002841be authored by Oleksij Rempel's avatar Oleksij Rempel Committed by David S. Miller

net: dsa: microchip: Add partial ACL support for ksz9477 switches

This patch adds partial Access Control List (ACL) support for the
ksz9477 family of switches. ACLs enable filtering of incoming layer 2
MAC, layer 3 IP, and layer 4 TCP/UDP packets on each port. They provide
additional capabilities for filtering routed network protocols and can
take precedence over other forwarding functions.

ACLs can filter ingress traffic based on header fields such as
source/destination MAC address, EtherType, IPv4 address, IPv4 protocol,
UDP/TCP ports, and TCP flags. The ACL is an ordered list of up to 16
access control rules programmed into the ACL Table. Each entry specifies
a set of matching conditions and action rules for controlling packet
forwarding and priority.

The ACL also implements a count function, generating an interrupt
instead of a forwarding action. It can be used as a watchdog timer or an
event counter. The ACL consists of three parts: matching rules, action
rules, and processing entries. Multiple match conditions can be either
AND'ed or OR'ed together.

This patch introduces support for a subset of the available ACL
functionality, specifically layer 2 matching and prioritization of
matched packets. For example:

tc qdisc add dev lan2 clsact
tc filter add dev lan2 ingress protocol 0x88f7 flower action skbedit prio 7

tc qdisc add dev lan1 clsact
tc filter add dev lan1 ingress protocol 0x88f7 flower action skbedit prio 7

The hardware offloading implementation was benchmarked against a
configuration without hardware offloading. This latter setup relied on a
software-based Linux bridge. No noticeable differences were observed
between the two configurations. Here is an example of software-based
test:

ip l s dev enu1u1 up
ip l s dev enu1u2 up
ip l s dev enu1u4 up
ethtool -A enu1u1 autoneg off rx off tx off
ethtool -A enu1u2 autoneg off rx off tx off
ethtool -A enu1u4 autoneg off rx off tx off
ip l a name br0 type bridge
ip l s dev br0 up
ip l s enu1u1 master br0
ip l s enu1u2 master br0
ip l s enu1u4 master br0

tc qdisc add dev enu1u1 root handle 1:  ets strict 4 priomap 3 3 2 2 1 1 0 0
tc qdisc add dev enu1u4 root handle 1:  ets strict 4 priomap 3 3 2 2 1 1 0 0
tc qdisc add dev enu1u2 root handle 1:  ets strict 4 priomap 3 3 2 2 1 1 0 0

tc qdisc add dev enu1u1 clsact
tc filter add dev enu1u1 ingress protocol ipv4  flower action skbedit prio 7

tc qdisc add dev enu1u4 clsact
tc filter add dev enu1u4 ingress protocol ipv4  flower action skbedit prio 0

On a system attached to the port enu1u2 I run two iperf3 server
instances:
iperf3 -s -p 5210 &
iperf3 -s -p 5211 &

On systems attached to enu1u4 and enu1u1 I run:
iperf3 -u -c  172.17.0.1 -p 5210 -b100M  -l1472 -t100
and
iperf3 -u -c  172.17.0.1 -p 5211 -b100M  -l1472 -t100

As a result, IP traffic on port enu1u1 will be prioritized and take
precedence over IP traffic on port enu1u4
Signed-off-by: default avatarOleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 15299227
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_COMMON) += ksz_switch.o
ksz_switch-objs := ksz_common.o
ksz_switch-objs += ksz9477.o
ksz_switch-objs += ksz9477.o ksz9477_acl.o ksz9477_tc_flower.o
ksz_switch-objs += ksz8795.o
ksz_switch-objs += lan937x_main.o
......
......@@ -1004,6 +1004,8 @@ void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port)
/* clear pending interrupts */
if (dev->info->internal_phy[port])
ksz_pread16(dev, port, REG_PORT_PHY_INT_ENABLE, &data16);
ksz9477_port_acl_init(dev, port);
}
void ksz9477_config_cpu_port(struct dsa_switch *ds)
......
......@@ -57,4 +57,40 @@ int ksz9477_switch_init(struct ksz_device *dev);
void ksz9477_switch_exit(struct ksz_device *dev);
void ksz9477_port_queue_split(struct ksz_device *dev, int port);
int ksz9477_port_acl_init(struct ksz_device *dev, int port);
void ksz9477_port_acl_free(struct ksz_device *dev, int port);
int ksz9477_cls_flower_add(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
int ksz9477_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
#define KSZ9477_ACL_ENTRY_SIZE 18
#define KSZ9477_ACL_MAX_ENTRIES 16
struct ksz9477_acl_entry {
u8 entry[KSZ9477_ACL_ENTRY_SIZE];
unsigned long cookie;
u32 prio;
};
struct ksz9477_acl_entries {
struct ksz9477_acl_entry entries[KSZ9477_ACL_MAX_ENTRIES];
int entries_count;
};
struct ksz9477_acl_priv {
struct ksz9477_acl_entries acles;
};
void ksz9477_acl_remove_entries(struct ksz_device *dev, int port,
struct ksz9477_acl_entries *acles,
unsigned long cookie);
int ksz9477_acl_write_list(struct ksz_device *dev, int port);
int ksz9477_sort_acl_entries(struct ksz_device *dev, int port);
void ksz9477_acl_action_rule_cfg(u8 *entry, bool force_prio, u8 prio_val);
void ksz9477_acl_processing_rule_set_action(u8 *entry, u8 action_idx);
void ksz9477_acl_match_process_l2(struct ksz_device *dev, int port,
u16 ethtype, u8 *src_mac, u8 *dst_mac,
unsigned long cookie, u32 prio);
#endif
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
#include "ksz9477.h"
#include "ksz9477_reg.h"
#include "ksz_common.h"
#define ETHER_TYPE_FULL_MASK cpu_to_be16(~0)
#define KSZ9477_MAX_TC 7
/**
* ksz9477_flower_parse_key_l2 - Parse Layer 2 key from flow rule and configure
* ACL entries accordingly.
* @dev: Pointer to the ksz_device.
* @port: Port number.
* @extack: Pointer to the netlink_ext_ack.
* @rule: Pointer to the flow_rule.
* @cookie: The cookie to associate with the entry.
* @prio: The priority of the entry.
*
* This function parses the Layer 2 key from the flow rule and configures
* the corresponding ACL entries. It checks for unsupported offloads and
* available entries before proceeding with the configuration.
*
* Returns: 0 on success or a negative error code on failure.
*/
static int ksz9477_flower_parse_key_l2(struct ksz_device *dev, int port,
struct netlink_ext_ack *extack,
struct flow_rule *rule,
unsigned long cookie, u32 prio)
{
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
struct flow_match_eth_addrs ematch;
struct ksz9477_acl_entries *acles;
int required_entries;
u8 *src_mac = NULL;
u8 *dst_mac = NULL;
u16 ethtype = 0;
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_match_basic match;
flow_rule_match_basic(rule, &match);
if (match.key->n_proto) {
if (match.mask->n_proto != ETHER_TYPE_FULL_MASK) {
NL_SET_ERR_MSG_MOD(extack,
"ethernet type mask must be a full mask");
return -EINVAL;
}
ethtype = be16_to_cpu(match.key->n_proto);
}
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
flow_rule_match_eth_addrs(rule, &ematch);
if (!is_zero_ether_addr(ematch.key->src)) {
if (!is_broadcast_ether_addr(ematch.mask->src))
goto not_full_mask_err;
src_mac = ematch.key->src;
}
if (!is_zero_ether_addr(ematch.key->dst)) {
if (!is_broadcast_ether_addr(ematch.mask->dst))
goto not_full_mask_err;
dst_mac = ematch.key->dst;
}
}
acles = &acl->acles;
/* ACL supports only one MAC per entry */
required_entries = src_mac && dst_mac ? 2 : 1;
/* Check if there are enough available entries */
if (acles->entries_count + required_entries > KSZ9477_ACL_MAX_ENTRIES) {
NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached");
return -EOPNOTSUPP;
}
ksz9477_acl_match_process_l2(dev, port, ethtype, src_mac, dst_mac,
cookie, prio);
return 0;
not_full_mask_err:
NL_SET_ERR_MSG_MOD(extack, "MAC address mask must be a full mask");
return -EOPNOTSUPP;
}
/**
* ksz9477_flower_parse_key - Parse flow rule keys for a specified port on a
* ksz_device.
* @dev: The ksz_device instance.
* @port: The port number to parse the flow rule keys for.
* @extack: The netlink extended ACK for reporting errors.
* @rule: The flow_rule to parse.
* @cookie: The cookie to associate with the entry.
* @prio: The priority of the entry.
*
* This function checks if the used keys in the flow rule are supported by
* the device and parses the L2 keys if they match. If unsupported keys are
* used, an error message is set in the extended ACK.
*
* Returns: 0 on success or a negative error code on failure.
*/
static int ksz9477_flower_parse_key(struct ksz_device *dev, int port,
struct netlink_ext_ack *extack,
struct flow_rule *rule,
unsigned long cookie, u32 prio)
{
struct flow_dissector *dissector = rule->match.dissector;
int ret;
if (dissector->used_keys &
~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) {
NL_SET_ERR_MSG_MOD(extack,
"Unsupported keys used");
return -EOPNOTSUPP;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC) ||
flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
ret = ksz9477_flower_parse_key_l2(dev, port, extack, rule,
cookie, prio);
if (ret)
return ret;
}
return 0;
}
/**
* ksz9477_flower_parse_action - Parse flow rule actions for a specified port
* on a ksz_device.
* @dev: The ksz_device instance.
* @port: The port number to parse the flow rule actions for.
* @extack: The netlink extended ACK for reporting errors.
* @cls: The flow_cls_offload instance containing the flow rule.
* @entry_idx: The index of the ACL entry to store the action.
*
* This function checks if the actions in the flow rule are supported by
* the device. Currently, only actions that change priorities are supported.
* If unsupported actions are encountered, an error message is set in the
* extended ACK.
*
* Returns: 0 on success or a negative error code on failure.
*/
static int ksz9477_flower_parse_action(struct ksz_device *dev, int port,
struct netlink_ext_ack *extack,
struct flow_cls_offload *cls,
int entry_idx)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
const struct flow_action_entry *act;
struct ksz9477_acl_entry *entry;
bool prio_force = false;
u8 prio_val = 0;
int i;
if (TC_H_MIN(cls->classid)) {
NL_SET_ERR_MSG_MOD(extack, "hw_tc is not supported. Use: action skbedit prio");
return -EOPNOTSUPP;
}
flow_action_for_each(i, act, &rule->action) {
switch (act->id) {
case FLOW_ACTION_PRIORITY:
if (act->priority > KSZ9477_MAX_TC) {
NL_SET_ERR_MSG_MOD(extack, "Priority value is too high");
return -EOPNOTSUPP;
}
prio_force = true;
prio_val = act->priority;
break;
default:
NL_SET_ERR_MSG_MOD(extack, "action not supported");
return -EOPNOTSUPP;
}
}
/* pick entry to store action */
entry = &acl->acles.entries[entry_idx];
ksz9477_acl_action_rule_cfg(entry->entry, prio_force, prio_val);
ksz9477_acl_processing_rule_set_action(entry->entry, entry_idx);
return 0;
}
/**
* ksz9477_cls_flower_add - Add a flow classification rule for a specified port
* on a ksz_device.
* @ds: The DSA switch instance.
* @port: The port number to add the flow classification rule to.
* @cls: The flow_cls_offload instance containing the flow rule.
* @ingress: A flag indicating if the rule is applied on the ingress path.
*
* This function adds a flow classification rule for a specified port on a
* ksz_device. It checks if the ACL offloading is supported and parses the flow
* keys and actions. If the ACL is not supported, it returns an error. If there
* are unprocessed entries, it parses the action for the rule.
*
* Returns: 0 on success or a negative error code on failure.
*/
int ksz9477_cls_flower_add(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct netlink_ext_ack *extack = cls->common.extack;
struct ksz_device *dev = ds->priv;
struct ksz9477_acl_priv *acl;
int action_entry_idx;
int ret;
acl = dev->ports[port].acl_priv;
if (!acl) {
NL_SET_ERR_MSG_MOD(extack, "ACL offloading is not supported");
return -EOPNOTSUPP;
}
/* A complex rule set can take multiple entries. Use first entry
* to store the action.
*/
action_entry_idx = acl->acles.entries_count;
ret = ksz9477_flower_parse_key(dev, port, extack, rule, cls->cookie,
cls->common.prio);
if (ret)
return ret;
ret = ksz9477_flower_parse_action(dev, port, extack, cls,
action_entry_idx);
if (ret)
return ret;
ret = ksz9477_sort_acl_entries(dev, port);
if (ret)
return ret;
return ksz9477_acl_write_list(dev, port);
}
/**
* ksz9477_cls_flower_del - Remove a flow classification rule for a specified
* port on a ksz_device.
* @ds: The DSA switch instance.
* @port: The port number to remove the flow classification rule from.
* @cls: The flow_cls_offload instance containing the flow rule.
* @ingress: A flag indicating if the rule is applied on the ingress path.
*
* This function removes a flow classification rule for a specified port on a
* ksz_device. It checks if the ACL is initialized, and if not, returns an
* error. If the ACL is initialized, it removes entries with the specified
* cookie and rewrites the ACL list.
*
* Returns: 0 on success or a negative error code on failure.
*/
int ksz9477_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
unsigned long cookie = cls->cookie;
struct ksz_device *dev = ds->priv;
struct ksz9477_acl_priv *acl;
acl = dev->ports[port].acl_priv;
if (!acl)
return -EOPNOTSUPP;
ksz9477_acl_remove_entries(dev, port, &acl->acles, cookie);
return ksz9477_acl_write_list(dev, port);
}
......@@ -2627,6 +2627,23 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
ksz_update_port_member(dev, port);
}
static void ksz_port_teardown(struct dsa_switch *ds, int port)
{
struct ksz_device *dev = ds->priv;
switch (dev->chip_id) {
case KSZ8563_CHIP_ID:
case KSZ9477_CHIP_ID:
case KSZ9563_CHIP_ID:
case KSZ9567_CHIP_ID:
case KSZ9893_CHIP_ID:
case KSZ9896_CHIP_ID:
case KSZ9897_CHIP_ID:
if (dsa_is_user_port(ds, port))
ksz9477_port_acl_free(dev, port);
}
}
static int ksz_port_pre_bridge_flags(struct dsa_switch *ds, int port,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack)
......@@ -3172,6 +3189,44 @@ static int ksz_switch_detect(struct ksz_device *dev)
return 0;
}
static int ksz_cls_flower_add(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
struct ksz_device *dev = ds->priv;
switch (dev->chip_id) {
case KSZ8563_CHIP_ID:
case KSZ9477_CHIP_ID:
case KSZ9563_CHIP_ID:
case KSZ9567_CHIP_ID:
case KSZ9893_CHIP_ID:
case KSZ9896_CHIP_ID:
case KSZ9897_CHIP_ID:
return ksz9477_cls_flower_add(ds, port, cls, ingress);
}
return -EOPNOTSUPP;
}
static int ksz_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
struct ksz_device *dev = ds->priv;
switch (dev->chip_id) {
case KSZ8563_CHIP_ID:
case KSZ9477_CHIP_ID:
case KSZ9563_CHIP_ID:
case KSZ9567_CHIP_ID:
case KSZ9893_CHIP_ID:
case KSZ9896_CHIP_ID:
case KSZ9897_CHIP_ID:
return ksz9477_cls_flower_del(ds, port, cls, ingress);
}
return -EOPNOTSUPP;
}
/* Bandwidth is calculated by idle slope/transmission speed. Then the Bandwidth
* is converted to Hex-decimal using the successive multiplication method. On
* every step, integer part is taken and decimal part is carry forwarded.
......@@ -3504,6 +3559,7 @@ static const struct dsa_switch_ops ksz_switch_ops = {
.port_bridge_join = ksz_port_bridge_join,
.port_bridge_leave = ksz_port_bridge_leave,
.port_stp_state_set = ksz_port_stp_state_set,
.port_teardown = ksz_port_teardown,
.port_pre_bridge_flags = ksz_port_pre_bridge_flags,
.port_bridge_flags = ksz_port_bridge_flags,
.port_fast_age = ksz_port_fast_age,
......@@ -3526,6 +3582,8 @@ static const struct dsa_switch_ops ksz_switch_ops = {
.port_hwtstamp_set = ksz_hwtstamp_set,
.port_txtstamp = ksz_port_txtstamp,
.port_rxtstamp = ksz_port_rxtstamp,
.cls_flower_add = ksz_cls_flower_add,
.cls_flower_del = ksz_cls_flower_del,
.port_setup_tc = ksz_setup_tc,
.get_mac_eee = ksz_get_mac_eee,
.set_mac_eee = ksz_set_mac_eee,
......
......@@ -117,6 +117,7 @@ struct ksz_port {
u32 rgmii_tx_val;
u32 rgmii_rx_val;
struct ksz_device *ksz_dev;
void *acl_priv;
struct ksz_irq pirq;
u8 num;
#if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment