1437 lines
46 KiB
C
1437 lines
46 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
|
|
|
|
/* Access Control List (ACL) structure:
|
|
*
|
|
* There are multiple groups of registers involved in ACL configuration:
|
|
*
|
|
* - Matching Rules: These registers define the criteria for matching incoming
|
|
* packets based on their header information (Layer 2 MAC, Layer 3 IP, or
|
|
* Layer 4 TCP/UDP). Different register settings are used depending on the
|
|
* matching rule mode (MD) and the Enable (ENB) settings.
|
|
*
|
|
* - Action Rules: These registers define how the ACL should modify the packet's
|
|
* priority, VLAN tag priority, and forwarding map once a matching rule has
|
|
* been triggered. The settings vary depending on whether the matching rule is
|
|
* in Count Mode (MD = 01 and ENB = 00) or not.
|
|
*
|
|
* - Processing Rules: These registers control the overall behavior of the ACL,
|
|
* such as selecting which matching rule to apply first, enabling/disabling
|
|
* specific rules, or specifying actions for matched packets.
|
|
*
|
|
* ACL Structure:
|
|
* +----------------------+
|
|
* +----------------------+ | (optional) |
|
|
* | Matching Rules | | Matching Rules |
|
|
* | (Layer 2, 3, 4) | | (Layer 2, 3, 4) |
|
|
* +----------------------+ +----------------------+
|
|
* | |
|
|
* \___________________________/
|
|
* v
|
|
* +----------------------+
|
|
* | Processing Rules |
|
|
* | (action idx, |
|
|
* | matching rule set) |
|
|
* +----------------------+
|
|
* |
|
|
* v
|
|
* +----------------------+
|
|
* | Action Rules |
|
|
* | (Modify Priority, |
|
|
* | Forwarding Map, |
|
|
* | VLAN tag, etc) |
|
|
* +----------------------+
|
|
*/
|
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include "ksz9477.h"
|
|
#include "ksz9477_reg.h"
|
|
#include "ksz_common.h"
|
|
|
|
#define KSZ9477_PORT_ACL_0 0x600
|
|
|
|
enum ksz9477_acl_port_access {
|
|
KSZ9477_ACL_PORT_ACCESS_0 = 0x00,
|
|
KSZ9477_ACL_PORT_ACCESS_1 = 0x01,
|
|
KSZ9477_ACL_PORT_ACCESS_2 = 0x02,
|
|
KSZ9477_ACL_PORT_ACCESS_3 = 0x03,
|
|
KSZ9477_ACL_PORT_ACCESS_4 = 0x04,
|
|
KSZ9477_ACL_PORT_ACCESS_5 = 0x05,
|
|
KSZ9477_ACL_PORT_ACCESS_6 = 0x06,
|
|
KSZ9477_ACL_PORT_ACCESS_7 = 0x07,
|
|
KSZ9477_ACL_PORT_ACCESS_8 = 0x08,
|
|
KSZ9477_ACL_PORT_ACCESS_9 = 0x09,
|
|
KSZ9477_ACL_PORT_ACCESS_A = 0x0A,
|
|
KSZ9477_ACL_PORT_ACCESS_B = 0x0B,
|
|
KSZ9477_ACL_PORT_ACCESS_C = 0x0C,
|
|
KSZ9477_ACL_PORT_ACCESS_D = 0x0D,
|
|
KSZ9477_ACL_PORT_ACCESS_E = 0x0E,
|
|
KSZ9477_ACL_PORT_ACCESS_F = 0x0F,
|
|
KSZ9477_ACL_PORT_ACCESS_10 = 0x10,
|
|
KSZ9477_ACL_PORT_ACCESS_11 = 0x11
|
|
};
|
|
|
|
#define KSZ9477_ACL_MD_MASK GENMASK(5, 4)
|
|
#define KSZ9477_ACL_MD_DISABLE 0
|
|
#define KSZ9477_ACL_MD_L2_MAC 1
|
|
#define KSZ9477_ACL_MD_L3_IP 2
|
|
#define KSZ9477_ACL_MD_L4_TCP_UDP 3
|
|
|
|
#define KSZ9477_ACL_ENB_MASK GENMASK(3, 2)
|
|
#define KSZ9477_ACL_ENB_L2_COUNTER 0
|
|
#define KSZ9477_ACL_ENB_L2_TYPE 1
|
|
#define KSZ9477_ACL_ENB_L2_MAC 2
|
|
#define KSZ9477_ACL_ENB_L2_MAC_TYPE 3
|
|
|
|
/* only IPv4 src or dst can be used with mask */
|
|
#define KSZ9477_ACL_ENB_L3_IPV4_ADDR_MASK 1
|
|
/* only IPv4 src and dst can be used without mask */
|
|
#define KSZ9477_ACL_ENB_L3_IPV4_ADDR_SRC_DST 2
|
|
|
|
#define KSZ9477_ACL_ENB_L4_IP_PROTO 0
|
|
#define KSZ9477_ACL_ENB_L4_TCP_SRC_DST_PORT 1
|
|
#define KSZ9477_ACL_ENB_L4_UDP_SRC_DST_PORT 2
|
|
#define KSZ9477_ACL_ENB_L4_TCP_SEQ_NUMBER 3
|
|
|
|
#define KSZ9477_ACL_SD_SRC BIT(1)
|
|
#define KSZ9477_ACL_SD_DST 0
|
|
#define KSZ9477_ACL_EQ_EQUAL BIT(0)
|
|
#define KSZ9477_ACL_EQ_NOT_EQUAL 0
|
|
|
|
#define KSZ9477_ACL_PM_M GENMASK(7, 6)
|
|
#define KSZ9477_ACL_PM_DISABLE 0
|
|
#define KSZ9477_ACL_PM_HIGHER 1
|
|
#define KSZ9477_ACL_PM_LOWER 2
|
|
#define KSZ9477_ACL_PM_REPLACE 3
|
|
#define KSZ9477_ACL_P_M GENMASK(5, 3)
|
|
|
|
#define KSZ9477_PORT_ACL_CTRL_0 0x0612
|
|
|
|
#define KSZ9477_ACL_WRITE_DONE BIT(6)
|
|
#define KSZ9477_ACL_READ_DONE BIT(5)
|
|
#define KSZ9477_ACL_WRITE BIT(4)
|
|
#define KSZ9477_ACL_INDEX_M GENMASK(3, 0)
|
|
|
|
/**
|
|
* ksz9477_dump_acl_index - Print the ACL entry at the specified index
|
|
*
|
|
* @dev: Pointer to the ksz9477 device structure.
|
|
* @acle: Pointer to the ACL entry array.
|
|
* @index: The index of the ACL entry to print.
|
|
*
|
|
* This function prints the details of an ACL entry, located at a particular
|
|
* index within the ksz9477 device's ACL table. It omits printing entries that
|
|
* are empty.
|
|
*
|
|
* Return: 1 if the entry is non-empty and printed, 0 otherwise.
|
|
*/
|
|
static int ksz9477_dump_acl_index(struct ksz_device *dev,
|
|
struct ksz9477_acl_entry *acle, int index)
|
|
{
|
|
bool empty = true;
|
|
char buf[64];
|
|
u8 *entry;
|
|
int i;
|
|
|
|
entry = &acle[index].entry[0];
|
|
for (i = 0; i <= KSZ9477_ACL_PORT_ACCESS_11; i++) {
|
|
if (entry[i])
|
|
empty = false;
|
|
|
|
sprintf(buf + (i * 3), "%02x ", entry[i]);
|
|
}
|
|
|
|
/* no need to print empty entries */
|
|
if (empty)
|
|
return 0;
|
|
|
|
dev_err(dev->dev, " Entry %02d, prio: %02d : %s", index,
|
|
acle[index].prio, buf);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_dump_acl - Print ACL entries
|
|
*
|
|
* @dev: Pointer to the device structure.
|
|
* @acle: Pointer to the ACL entry array.
|
|
*/
|
|
static void ksz9477_dump_acl(struct ksz_device *dev,
|
|
struct ksz9477_acl_entry *acle)
|
|
{
|
|
int count = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < KSZ9477_ACL_MAX_ENTRIES; i++)
|
|
count += ksz9477_dump_acl_index(dev, acle, i);
|
|
|
|
if (count != KSZ9477_ACL_MAX_ENTRIES - 1)
|
|
dev_err(dev->dev, " Empty ACL entries were skipped\n");
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_is_valid_matching_rule - Check if an ACL entry contains a valid
|
|
* matching rule.
|
|
*
|
|
* @entry: Pointer to ACL entry buffer
|
|
*
|
|
* This function checks if the given ACL entry buffer contains a valid
|
|
* matching rule by inspecting the Mode (MD) and Enable (ENB) fields.
|
|
*
|
|
* Returns: True if it's a valid matching rule, false otherwise.
|
|
*/
|
|
static bool ksz9477_acl_is_valid_matching_rule(u8 *entry)
|
|
{
|
|
u8 val1, md, enb;
|
|
|
|
val1 = entry[KSZ9477_ACL_PORT_ACCESS_1];
|
|
|
|
md = FIELD_GET(KSZ9477_ACL_MD_MASK, val1);
|
|
if (md == KSZ9477_ACL_MD_DISABLE)
|
|
return false;
|
|
|
|
if (md == KSZ9477_ACL_MD_L2_MAC) {
|
|
/* L2 counter is not support, so it is not valid rule for now */
|
|
enb = FIELD_GET(KSZ9477_ACL_ENB_MASK, val1);
|
|
if (enb == KSZ9477_ACL_ENB_L2_COUNTER)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_get_cont_entr - Get count of contiguous ACL entries and validate
|
|
* the matching rules.
|
|
* @dev: Pointer to the KSZ9477 device structure.
|
|
* @port: Port number.
|
|
* @index: Index of the starting ACL entry.
|
|
*
|
|
* Based on the KSZ9477 switch's Access Control List (ACL) system, the RuleSet
|
|
* in an ACL entry indicates which entries contain Matching rules linked to it.
|
|
* This RuleSet is represented by two registers: KSZ9477_ACL_PORT_ACCESS_E and
|
|
* KSZ9477_ACL_PORT_ACCESS_F. Each bit set in these registers corresponds to
|
|
* an entry containing a Matching rule for this RuleSet.
|
|
*
|
|
* For a single Matching rule linked, only one bit is set. However, when an
|
|
* entry links multiple Matching rules, forming what's termed a 'complex rule',
|
|
* multiple bits are set in these registers.
|
|
*
|
|
* This function checks that, for complex rules, the entries containing the
|
|
* linked Matching rules are contiguous in terms of their indices. It calculates
|
|
* and returns the number of these contiguous entries.
|
|
*
|
|
* Returns:
|
|
* - 0 if the entry is empty and can be safely overwritten
|
|
* - 1 if the entry represents a simple rule
|
|
* - The number of contiguous entries if it is the root entry of a complex
|
|
* rule
|
|
* - -ENOTEMPTY if the entry is part of a complex rule but not the root
|
|
* entry
|
|
* - -EINVAL if the validation fails
|
|
*/
|
|
static int ksz9477_acl_get_cont_entr(struct ksz_device *dev, int port,
|
|
int index)
|
|
{
|
|
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
|
|
struct ksz9477_acl_entries *acles = &acl->acles;
|
|
int start_idx, end_idx, contiguous_count;
|
|
unsigned long val;
|
|
u8 vale, valf;
|
|
u8 *entry;
|
|
int i;
|
|
|
|
entry = &acles->entries[index].entry[0];
|
|
vale = entry[KSZ9477_ACL_PORT_ACCESS_E];
|
|
valf = entry[KSZ9477_ACL_PORT_ACCESS_F];
|
|
|
|
val = (vale << 8) | valf;
|
|
|
|
/* If no bits are set, return an appropriate value or error */
|
|
if (!val) {
|
|
if (ksz9477_acl_is_valid_matching_rule(entry)) {
|
|
/* Looks like we are about to corrupt some complex rule.
|
|
* Do not print an error here, as this is a normal case
|
|
* when we are trying to find a free or starting entry.
|
|
*/
|
|
dev_dbg(dev->dev, "ACL: entry %d starting with a valid matching rule, but no bits set in RuleSet\n",
|
|
index);
|
|
return -ENOTEMPTY;
|
|
}
|
|
|
|
/* This entry does not contain a valid matching rule */
|
|
return 0;
|
|
}
|
|
|
|
start_idx = find_first_bit((unsigned long *)&val, 16);
|
|
end_idx = find_last_bit((unsigned long *)&val, 16);
|
|
|
|
/* Calculate the contiguous count */
|
|
contiguous_count = end_idx - start_idx + 1;
|
|
|
|
/* Check if the number of bits set in val matches our calculated count */
|
|
if (contiguous_count != hweight16(val)) {
|
|
/* Probably we have a fragmented complex rule, which is not
|
|
* supported by this driver.
|
|
*/
|
|
dev_err(dev->dev, "ACL: number of bits set in RuleSet does not match calculated count\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* loop over the contiguous entries and check for valid matching rules */
|
|
for (i = start_idx; i <= end_idx; i++) {
|
|
u8 *current_entry = &acles->entries[i].entry[0];
|
|
|
|
if (!ksz9477_acl_is_valid_matching_rule(current_entry)) {
|
|
/* we have something linked without a valid matching
|
|
* rule. ACL table?
|
|
*/
|
|
dev_err(dev->dev, "ACL: entry %d does not contain a valid matching rule\n",
|
|
i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (i > start_idx) {
|
|
vale = current_entry[KSZ9477_ACL_PORT_ACCESS_E];
|
|
valf = current_entry[KSZ9477_ACL_PORT_ACCESS_F];
|
|
/* Following entry should have empty linkage list */
|
|
if (vale || valf) {
|
|
dev_err(dev->dev, "ACL: entry %d has non-empty RuleSet linkage\n",
|
|
i);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return contiguous_count;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_update_linkage - Update the RuleSet linkage for an ACL entry
|
|
* after a move operation.
|
|
*
|
|
* @dev: Pointer to the ksz_device.
|
|
* @entry: Pointer to the ACL entry array.
|
|
* @old_idx: The original index of the ACL entry before moving.
|
|
* @new_idx: The new index of the ACL entry after moving.
|
|
*
|
|
* This function updates the RuleSet linkage bits for an ACL entry when
|
|
* it's moved from one position to another in the ACL table. The RuleSet
|
|
* linkage is represented by two 8-bit registers, which are combined
|
|
* into a 16-bit value for easier manipulation. The linkage bits are shifted
|
|
* based on the difference between the old and new index. If any bits are lost
|
|
* during the shift operation, an error is returned.
|
|
*
|
|
* Note: Fragmentation within a RuleSet is not supported. Hence, entries must
|
|
* be moved as complete blocks, maintaining the integrity of the RuleSet.
|
|
*
|
|
* Returns: 0 on success, or -EINVAL if any RuleSet linkage bits are lost
|
|
* during the move.
|
|
*/
|
|
static int ksz9477_acl_update_linkage(struct ksz_device *dev, u8 *entry,
|
|
u16 old_idx, u16 new_idx)
|
|
{
|
|
unsigned int original_bit_count;
|
|
unsigned long rule_linkage;
|
|
u8 vale, valf, val0;
|
|
int shift;
|
|
|
|
val0 = entry[KSZ9477_ACL_PORT_ACCESS_0];
|
|
vale = entry[KSZ9477_ACL_PORT_ACCESS_E];
|
|
valf = entry[KSZ9477_ACL_PORT_ACCESS_F];
|
|
|
|
/* Combine the two u8 values into one u16 for easier manipulation */
|
|
rule_linkage = (vale << 8) | valf;
|
|
original_bit_count = hweight16(rule_linkage);
|
|
|
|
/* Even if HW is able to handle fragmented RuleSet, we don't support it.
|
|
* RuleSet is filled only for the first entry of the set.
|
|
*/
|
|
if (!rule_linkage)
|
|
return 0;
|
|
|
|
if (val0 != old_idx) {
|
|
dev_err(dev->dev, "ACL: entry %d has unexpected ActionRule linkage: %d\n",
|
|
old_idx, val0);
|
|
return -EINVAL;
|
|
}
|
|
|
|
val0 = new_idx;
|
|
|
|
/* Calculate the number of positions to shift */
|
|
shift = new_idx - old_idx;
|
|
|
|
/* Shift the RuleSet */
|
|
if (shift > 0)
|
|
rule_linkage <<= shift;
|
|
else
|
|
rule_linkage >>= -shift;
|
|
|
|
/* Check that no bits were lost in the process */
|
|
if (original_bit_count != hweight16(rule_linkage)) {
|
|
dev_err(dev->dev, "ACL RuleSet linkage bits lost during move\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
entry[KSZ9477_ACL_PORT_ACCESS_0] = val0;
|
|
|
|
/* Update the RuleSet bitfields in the entry */
|
|
entry[KSZ9477_ACL_PORT_ACCESS_E] = (rule_linkage >> 8) & 0xFF;
|
|
entry[KSZ9477_ACL_PORT_ACCESS_F] = rule_linkage & 0xFF;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_validate_and_get_src_count - Validate source and destination indices
|
|
* and determine the source entry count.
|
|
* @dev: Pointer to the KSZ device structure.
|
|
* @port: Port number on the KSZ device where the ACL entries reside.
|
|
* @src_idx: Index of the starting ACL entry that needs to be validated.
|
|
* @dst_idx: Index of the destination where the source entries are intended to
|
|
* be moved.
|
|
* @src_count: Pointer to the variable that will hold the number of contiguous
|
|
* source entries if the validation passes.
|
|
* @dst_count: Pointer to the variable that will hold the number of contiguous
|
|
* destination entries if the validation passes.
|
|
*
|
|
* This function performs validation on the source and destination indices
|
|
* provided for ACL entries. It checks if the indices are within the valid
|
|
* range, and if the source entries are contiguous. Additionally, the function
|
|
* ensures that there's adequate space at the destination for the source entries
|
|
* and that the destination index isn't in the middle of a RuleSet. If all
|
|
* validations pass, the function returns the number of contiguous source and
|
|
* destination entries.
|
|
*
|
|
* Return: 0 on success, otherwise returns a negative error code if any
|
|
* validation check fails.
|
|
*/
|
|
static int ksz9477_validate_and_get_src_count(struct ksz_device *dev, int port,
|
|
int src_idx, int dst_idx,
|
|
int *src_count, int *dst_count)
|
|
{
|
|
int ret;
|
|
|
|
if (src_idx >= KSZ9477_ACL_MAX_ENTRIES ||
|
|
dst_idx >= KSZ9477_ACL_MAX_ENTRIES) {
|
|
dev_err(dev->dev, "ACL: invalid entry index\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Validate if the source entries are contiguous */
|
|
ret = ksz9477_acl_get_cont_entr(dev, port, src_idx);
|
|
if (ret < 0)
|
|
return ret;
|
|
*src_count = ret;
|
|
|
|
if (!*src_count) {
|
|
dev_err(dev->dev, "ACL: source entry is empty\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dst_idx + *src_count >= KSZ9477_ACL_MAX_ENTRIES) {
|
|
dev_err(dev->dev, "ACL: Not enough space at the destination. Move operation will fail.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Validate if the destination entry is empty or not in the middle of
|
|
* a RuleSet.
|
|
*/
|
|
ret = ksz9477_acl_get_cont_entr(dev, port, dst_idx);
|
|
if (ret < 0)
|
|
return ret;
|
|
*dst_count = ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_move_entries_downwards - Move a range of ACL entries downwards in
|
|
* the list.
|
|
* @dev: Pointer to the KSZ device structure.
|
|
* @acles: Pointer to the structure encapsulating all the ACL entries.
|
|
* @start_idx: Starting index of the entries to be relocated.
|
|
* @num_entries_to_move: Number of consecutive entries to be relocated.
|
|
* @end_idx: Destination index where the first entry should be situated post
|
|
* relocation.
|
|
*
|
|
* This function is responsible for rearranging a specific block of ACL entries
|
|
* by shifting them downwards in the list based on the supplied source and
|
|
* destination indices. It ensures that the linkage between the ACL entries is
|
|
* maintained accurately after the relocation.
|
|
*
|
|
* Return: 0 on successful relocation of entries, otherwise returns a negative
|
|
* error code.
|
|
*/
|
|
static int ksz9477_move_entries_downwards(struct ksz_device *dev,
|
|
struct ksz9477_acl_entries *acles,
|
|
u16 start_idx,
|
|
u16 num_entries_to_move,
|
|
u16 end_idx)
|
|
{
|
|
struct ksz9477_acl_entry *e;
|
|
int ret, i;
|
|
|
|
for (i = start_idx; i < end_idx; i++) {
|
|
e = &acles->entries[i];
|
|
*e = acles->entries[i + num_entries_to_move];
|
|
|
|
ret = ksz9477_acl_update_linkage(dev, &e->entry[0],
|
|
i + num_entries_to_move, i);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_move_entries_upwards - Move a range of ACL entries upwards in the
|
|
* list.
|
|
* @dev: Pointer to the KSZ device structure.
|
|
* @acles: Pointer to the structure holding all the ACL entries.
|
|
* @start_idx: The starting index of the entries to be moved.
|
|
* @num_entries_to_move: Number of contiguous entries to be moved.
|
|
* @target_idx: The destination index where the first entry should be placed
|
|
* after moving.
|
|
*
|
|
* This function rearranges a chunk of ACL entries by moving them upwards
|
|
* in the list based on the given source and destination indices. The reordering
|
|
* process preserves the linkage between entries by updating it accordingly.
|
|
*
|
|
* Return: 0 if the entries were successfully moved, otherwise a negative error
|
|
* code.
|
|
*/
|
|
static int ksz9477_move_entries_upwards(struct ksz_device *dev,
|
|
struct ksz9477_acl_entries *acles,
|
|
u16 start_idx, u16 num_entries_to_move,
|
|
u16 target_idx)
|
|
{
|
|
struct ksz9477_acl_entry *e;
|
|
int ret, i, b;
|
|
|
|
for (i = start_idx; i > target_idx; i--) {
|
|
b = i + num_entries_to_move - 1;
|
|
|
|
e = &acles->entries[b];
|
|
*e = acles->entries[i - 1];
|
|
|
|
ret = ksz9477_acl_update_linkage(dev, &e->entry[0], i - 1, b);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_move_entries - Move a block of contiguous ACL entries from a
|
|
* source to a destination index.
|
|
* @dev: Pointer to the KSZ9477 device structure.
|
|
* @port: Port number.
|
|
* @src_idx: Index of the starting source ACL entry.
|
|
* @dst_idx: Index of the starting destination ACL entry.
|
|
*
|
|
* This function aims to move a block of contiguous ACL entries from the source
|
|
* index to the destination index while ensuring the integrity and validity of
|
|
* the ACL table.
|
|
*
|
|
* In case of any errors during the adjustments or copying, the function will
|
|
* restore the ACL entries to their original state from the backup.
|
|
*
|
|
* Return: 0 if the move operation is successful. Returns -EINVAL for validation
|
|
* errors or other error codes based on specific failure conditions.
|
|
*/
|
|
static int ksz9477_acl_move_entries(struct ksz_device *dev, int port,
|
|
u16 src_idx, u16 dst_idx)
|
|
{
|
|
struct ksz9477_acl_entry buffer[KSZ9477_ACL_MAX_ENTRIES];
|
|
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
|
|
struct ksz9477_acl_entries *acles = &acl->acles;
|
|
int src_count, ret, dst_count;
|
|
|
|
/* Nothing to do */
|
|
if (src_idx == dst_idx)
|
|
return 0;
|
|
|
|
ret = ksz9477_validate_and_get_src_count(dev, port, src_idx, dst_idx,
|
|
&src_count, &dst_count);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* In case dst_index is greater than src_index, we need to adjust the
|
|
* destination index to account for the entries that will be moved
|
|
* downwards and the size of the entry located at dst_idx.
|
|
*/
|
|
if (dst_idx > src_idx)
|
|
dst_idx = dst_idx + dst_count - src_count;
|
|
|
|
/* Copy source block to buffer and update its linkage */
|
|
for (int i = 0; i < src_count; i++) {
|
|
buffer[i] = acles->entries[src_idx + i];
|
|
ret = ksz9477_acl_update_linkage(dev, &buffer[i].entry[0],
|
|
src_idx + i, dst_idx + i);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/* Adjust other entries and their linkage based on destination */
|
|
if (dst_idx > src_idx) {
|
|
ret = ksz9477_move_entries_downwards(dev, acles, src_idx,
|
|
src_count, dst_idx);
|
|
} else {
|
|
ret = ksz9477_move_entries_upwards(dev, acles, src_idx,
|
|
src_count, dst_idx);
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Copy buffer to destination block */
|
|
for (int i = 0; i < src_count; i++)
|
|
acles->entries[dst_idx + i] = buffer[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_get_next_block_start - Identify the starting index of the next ACL
|
|
* block.
|
|
* @dev: Pointer to the device structure.
|
|
* @port: The port number on which the ACL entries are being checked.
|
|
* @start: The starting index from which the search begins.
|
|
*
|
|
* This function looks for the next valid ACL block starting from the provided
|
|
* 'start' index and returns the beginning index of that block. If the block is
|
|
* invalid or if it reaches the end of the ACL entries without finding another
|
|
* block, it returns the maximum ACL entries count.
|
|
*
|
|
* Returns:
|
|
* - The starting index of the next valid ACL block.
|
|
* - KSZ9477_ACL_MAX_ENTRIES if no other valid blocks are found after 'start'.
|
|
* - A negative error code if an error occurs while checking.
|
|
*/
|
|
static int ksz9477_get_next_block_start(struct ksz_device *dev, int port,
|
|
int start)
|
|
{
|
|
int block_size;
|
|
|
|
for (int i = start; i < KSZ9477_ACL_MAX_ENTRIES;) {
|
|
block_size = ksz9477_acl_get_cont_entr(dev, port, i);
|
|
if (block_size < 0 && block_size != -ENOTEMPTY)
|
|
return block_size;
|
|
|
|
if (block_size > 0)
|
|
return i;
|
|
|
|
i++;
|
|
}
|
|
return KSZ9477_ACL_MAX_ENTRIES;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_swap_acl_blocks - Swap two ACL blocks
|
|
* @dev: Pointer to the device structure.
|
|
* @port: The port number on which the ACL blocks are to be swapped.
|
|
* @i: The starting index of the first ACL block.
|
|
* @j: The starting index of the second ACL block.
|
|
*
|
|
* This function is used to swap two ACL blocks present at given indices. The
|
|
* main purpose is to aid in the sorting and reordering of ACL blocks based on
|
|
* certain criteria, e.g., priority. It checks the validity of the block at
|
|
* index 'i', ensuring it's not an empty block, and then proceeds to swap it
|
|
* with the block at index 'j'.
|
|
*
|
|
* Returns:
|
|
* - 0 on successful swapping of blocks.
|
|
* - -EINVAL if the block at index 'i' is empty.
|
|
* - A negative error code if any other error occurs during the swap.
|
|
*/
|
|
static int ksz9477_swap_acl_blocks(struct ksz_device *dev, int port, int i,
|
|
int j)
|
|
{
|
|
int ret, current_block_size;
|
|
|
|
current_block_size = ksz9477_acl_get_cont_entr(dev, port, i);
|
|
if (current_block_size < 0)
|
|
return current_block_size;
|
|
|
|
if (!current_block_size) {
|
|
dev_err(dev->dev, "ACL: swapping empty entry %d\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = ksz9477_acl_move_entries(dev, port, i, j);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ksz9477_acl_move_entries(dev, port, j - current_block_size, i);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_sort_acl_entr_no_back - Sort ACL entries for a given port based on
|
|
* priority without backing up entries.
|
|
* @dev: Pointer to the device structure.
|
|
* @port: The port number whose ACL entries need to be sorted.
|
|
*
|
|
* This function sorts ACL entries of the specified port using a variant of the
|
|
* bubble sort algorithm. It operates on blocks of ACL entries rather than
|
|
* individual entries. Each block's starting point is identified and then
|
|
* compared with subsequent blocks based on their priority. If the current
|
|
* block has a lower priority than the subsequent block, the two blocks are
|
|
* swapped.
|
|
*
|
|
* This is done in order to maintain an organized order of ACL entries based on
|
|
* priority, ensuring efficient and predictable ACL rule application.
|
|
*
|
|
* Returns:
|
|
* - 0 on successful sorting of entries.
|
|
* - A negative error code if any issue arises during sorting, e.g.,
|
|
* if the function is unable to get the next block start.
|
|
*/
|
|
static int ksz9477_sort_acl_entr_no_back(struct ksz_device *dev, int port)
|
|
{
|
|
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
|
|
struct ksz9477_acl_entries *acles = &acl->acles;
|
|
struct ksz9477_acl_entry *curr, *next;
|
|
int i, j, ret;
|
|
|
|
/* Bubble sort */
|
|
for (i = 0; i < KSZ9477_ACL_MAX_ENTRIES;) {
|
|
curr = &acles->entries[i];
|
|
|
|
j = ksz9477_get_next_block_start(dev, port, i + 1);
|
|
if (j < 0)
|
|
return j;
|
|
|
|
while (j < KSZ9477_ACL_MAX_ENTRIES) {
|
|
next = &acles->entries[j];
|
|
|
|
if (curr->prio > next->prio) {
|
|
ret = ksz9477_swap_acl_blocks(dev, port, i, j);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
j = ksz9477_get_next_block_start(dev, port, j + 1);
|
|
if (j < 0)
|
|
return j;
|
|
}
|
|
|
|
i = ksz9477_get_next_block_start(dev, port, i + 1);
|
|
if (i < 0)
|
|
return i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_sort_acl_entries - Sort the ACL entries for a given port.
|
|
* @dev: Pointer to the KSZ device.
|
|
* @port: Port number.
|
|
*
|
|
* This function sorts the Access Control List (ACL) entries for a specified
|
|
* port. Before sorting, a backup of the original entries is created. If the
|
|
* sorting process fails, the function will log error messages displaying both
|
|
* the original and attempted sorted entries, and then restore the original
|
|
* entries from the backup.
|
|
*
|
|
* Return: 0 if the sorting succeeds, otherwise a negative error code.
|
|
*/
|
|
int ksz9477_sort_acl_entries(struct ksz_device *dev, int port)
|
|
{
|
|
struct ksz9477_acl_entry backup[KSZ9477_ACL_MAX_ENTRIES];
|
|
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
|
|
struct ksz9477_acl_entries *acles = &acl->acles;
|
|
int ret;
|
|
|
|
/* create a backup of the ACL entries, if something goes wrong
|
|
* we can restore the ACL entries.
|
|
*/
|
|
memcpy(backup, acles->entries, sizeof(backup));
|
|
|
|
ret = ksz9477_sort_acl_entr_no_back(dev, port);
|
|
if (ret) {
|
|
dev_err(dev->dev, "ACL: failed to sort entries for port %d\n",
|
|
port);
|
|
dev_err(dev->dev, "ACL dump before sorting:\n");
|
|
ksz9477_dump_acl(dev, backup);
|
|
dev_err(dev->dev, "ACL dump after sorting:\n");
|
|
ksz9477_dump_acl(dev, acles->entries);
|
|
/* Restore the original entries */
|
|
memcpy(acles->entries, backup, sizeof(backup));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_wait_ready - Waits for the ACL operation to complete on a given
|
|
* port.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number to wait for.
|
|
*
|
|
* This function checks if the ACL write or read operation is completed by
|
|
* polling the specified register.
|
|
*
|
|
* Returns: 0 if the operation is successful, or a negative error code if an
|
|
* error occurs.
|
|
*/
|
|
static int ksz9477_acl_wait_ready(struct ksz_device *dev, int port)
|
|
{
|
|
unsigned int wr_mask = KSZ9477_ACL_WRITE_DONE | KSZ9477_ACL_READ_DONE;
|
|
unsigned int val, reg;
|
|
int ret;
|
|
|
|
reg = dev->dev_ops->get_port_addr(port, KSZ9477_PORT_ACL_CTRL_0);
|
|
|
|
ret = regmap_read_poll_timeout(dev->regmap[0], reg, val,
|
|
(val & wr_mask) == wr_mask, 1000, 10000);
|
|
if (ret)
|
|
dev_err(dev->dev, "Failed to read/write ACL table\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_entry_write - Writes an ACL entry to a given port at the
|
|
* specified index.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number to write the ACL entry to.
|
|
* @entry: A pointer to the ACL entry data.
|
|
* @idx: The index at which to write the ACL entry.
|
|
*
|
|
* This function writes the provided ACL entry to the specified port at the
|
|
* given index.
|
|
*
|
|
* Returns: 0 if the operation is successful, or a negative error code if an
|
|
* error occurs.
|
|
*/
|
|
static int ksz9477_acl_entry_write(struct ksz_device *dev, int port, u8 *entry,
|
|
int idx)
|
|
{
|
|
int ret, i;
|
|
u8 val;
|
|
|
|
for (i = 0; i < KSZ9477_ACL_ENTRY_SIZE; i++) {
|
|
ret = ksz_pwrite8(dev, port, KSZ9477_PORT_ACL_0 + i, entry[i]);
|
|
if (ret) {
|
|
dev_err(dev->dev, "Failed to write ACL entry %d\n", i);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* write everything down */
|
|
val = FIELD_PREP(KSZ9477_ACL_INDEX_M, idx) | KSZ9477_ACL_WRITE;
|
|
ret = ksz_pwrite8(dev, port, KSZ9477_PORT_ACL_CTRL_0, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* wait until everything is written */
|
|
return ksz9477_acl_wait_ready(dev, port);
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_port_enable - Enables ACL functionality on a given port.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number on which to enable ACL functionality.
|
|
*
|
|
* This function enables ACL functionality on the specified port by configuring
|
|
* the appropriate control registers. It returns 0 if the operation is
|
|
* successful, or a negative error code if an error occurs.
|
|
*
|
|
* 0xn801 - KSZ9477S 5.2.8.2 Port Priority Control Register
|
|
* Bit 7 - Highest Priority
|
|
* Bit 6 - OR'ed Priority
|
|
* Bit 4 - MAC Address Priority Classification
|
|
* Bit 3 - VLAN Priority Classification
|
|
* Bit 2 - 802.1p Priority Classification
|
|
* Bit 1 - Diffserv Priority Classification
|
|
* Bit 0 - ACL Priority Classification
|
|
*
|
|
* Current driver implementation sets 802.1p priority classification by default.
|
|
* In this function we add ACL priority classification with OR'ed priority.
|
|
* According to testing, priority set by ACL will supersede the 802.1p priority.
|
|
*
|
|
* 0xn803 - KSZ9477S 5.2.8.4 Port Authentication Control Register
|
|
* Bit 2 - Access Control List (ACL) Enable
|
|
* Bits 1:0 - Authentication Mode
|
|
* 00 = Reserved
|
|
* 01 = Block Mode. Authentication is enabled. When ACL is
|
|
* enabled, all traffic that misses the ACL rules is
|
|
* blocked; otherwise ACL actions apply.
|
|
* 10 = Pass Mode. Authentication is disabled. When ACL is
|
|
* enabled, all traffic that misses the ACL rules is
|
|
* forwarded; otherwise ACL actions apply.
|
|
* 11 = Trap Mode. Authentication is enabled. All traffic is
|
|
* forwarded to the host port. When ACL is enabled, all
|
|
* traffic that misses the ACL rules is blocked; otherwise
|
|
* ACL actions apply.
|
|
*
|
|
* We are using Pass Mode int this function.
|
|
*
|
|
* Returns: 0 if the operation is successful, or a negative error code if an
|
|
* error occurs.
|
|
*/
|
|
static int ksz9477_acl_port_enable(struct ksz_device *dev, int port)
|
|
{
|
|
int ret;
|
|
|
|
ret = ksz_prmw8(dev, port, P_PRIO_CTRL, 0, PORT_ACL_PRIO_ENABLE |
|
|
PORT_OR_PRIO);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return ksz_pwrite8(dev, port, REG_PORT_MRI_AUTHEN_CTRL,
|
|
PORT_ACL_ENABLE |
|
|
FIELD_PREP(PORT_AUTHEN_MODE, PORT_AUTHEN_PASS));
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_port_disable - Disables ACL functionality on a given port.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number on which to disable ACL functionality.
|
|
*
|
|
* This function disables ACL functionality on the specified port by writing a
|
|
* value of 0 to the REG_PORT_MRI_AUTHEN_CTRL control register and remove
|
|
* PORT_ACL_PRIO_ENABLE bit from P_PRIO_CTRL register.
|
|
*
|
|
* Returns: 0 if the operation is successful, or a negative error code if an
|
|
* error occurs.
|
|
*/
|
|
static int ksz9477_acl_port_disable(struct ksz_device *dev, int port)
|
|
{
|
|
int ret;
|
|
|
|
ret = ksz_prmw8(dev, port, P_PRIO_CTRL, PORT_ACL_PRIO_ENABLE, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return ksz_pwrite8(dev, port, REG_PORT_MRI_AUTHEN_CTRL, 0);
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_write_list - Write a list of ACL entries to a given port.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number on which to write ACL entries.
|
|
*
|
|
* This function enables ACL functionality on the specified port, writes a list
|
|
* of ACL entries to the port, and disables ACL functionality if there are no
|
|
* entries.
|
|
*
|
|
* Returns: 0 if the operation is successful, or a negative error code if an
|
|
* error occurs.
|
|
*/
|
|
int ksz9477_acl_write_list(struct ksz_device *dev, int port)
|
|
{
|
|
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
|
|
struct ksz9477_acl_entries *acles = &acl->acles;
|
|
int ret, i;
|
|
|
|
/* ACL should be enabled before writing entries */
|
|
ret = ksz9477_acl_port_enable(dev, port);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* write all entries */
|
|
for (i = 0; i < ARRAY_SIZE(acles->entries); i++) {
|
|
u8 *entry = acles->entries[i].entry;
|
|
|
|
/* Check if entry was removed and should be zeroed.
|
|
* If last fields of the entry are not zero, it means
|
|
* it is removed locally but currently not synced with the HW.
|
|
* So, we will write it down to the HW to remove it.
|
|
*/
|
|
if (i >= acles->entries_count &&
|
|
entry[KSZ9477_ACL_PORT_ACCESS_10] == 0 &&
|
|
entry[KSZ9477_ACL_PORT_ACCESS_11] == 0)
|
|
continue;
|
|
|
|
ret = ksz9477_acl_entry_write(dev, port, entry, i);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* now removed entry is clean on HW side, so it can
|
|
* in the cache too
|
|
*/
|
|
if (i >= acles->entries_count &&
|
|
entry[KSZ9477_ACL_PORT_ACCESS_10] != 0 &&
|
|
entry[KSZ9477_ACL_PORT_ACCESS_11] != 0) {
|
|
entry[KSZ9477_ACL_PORT_ACCESS_10] = 0;
|
|
entry[KSZ9477_ACL_PORT_ACCESS_11] = 0;
|
|
}
|
|
}
|
|
|
|
if (!acles->entries_count)
|
|
return ksz9477_acl_port_disable(dev, port);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_remove_entries - Remove ACL entries with a given cookie from a
|
|
* specified ksz9477_acl_entries structure.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number on which to remove ACL entries.
|
|
* @acles: The ksz9477_acl_entries instance.
|
|
* @cookie: The cookie value to match for entry removal.
|
|
*
|
|
* This function iterates through the entries array, removing any entries with
|
|
* a matching cookie value. The remaining entries are then shifted down to fill
|
|
* the gap.
|
|
*/
|
|
void ksz9477_acl_remove_entries(struct ksz_device *dev, int port,
|
|
struct ksz9477_acl_entries *acles,
|
|
unsigned long cookie)
|
|
{
|
|
int entries_count = acles->entries_count;
|
|
int ret, i, src_count;
|
|
int src_idx = -1;
|
|
|
|
if (!entries_count)
|
|
return;
|
|
|
|
/* Search for the first position with the cookie */
|
|
for (i = 0; i < entries_count; i++) {
|
|
if (acles->entries[i].cookie == cookie) {
|
|
src_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* No entries with the matching cookie found */
|
|
if (src_idx == -1)
|
|
return;
|
|
|
|
/* Get the size of the cookie entry. We may have complex entries. */
|
|
src_count = ksz9477_acl_get_cont_entr(dev, port, src_idx);
|
|
if (src_count <= 0)
|
|
return;
|
|
|
|
/* Move all entries down to overwrite removed entry with the cookie */
|
|
ret = ksz9477_move_entries_downwards(dev, acles, src_idx,
|
|
src_count,
|
|
entries_count - src_count);
|
|
if (ret) {
|
|
dev_err(dev->dev, "Failed to move ACL entries down\n");
|
|
return;
|
|
}
|
|
|
|
/* Overwrite new empty places at the end of the list with zeros to make
|
|
* sure not unexpected things will happen or no unexplored quirks will
|
|
* come out.
|
|
*/
|
|
for (i = entries_count - src_count; i < entries_count; i++) {
|
|
struct ksz9477_acl_entry *entry = &acles->entries[i];
|
|
|
|
memset(entry, 0, sizeof(*entry));
|
|
|
|
/* Set all access bits to be able to write zeroed entry to HW */
|
|
entry->entry[KSZ9477_ACL_PORT_ACCESS_10] = 0xff;
|
|
entry->entry[KSZ9477_ACL_PORT_ACCESS_11] = 0xff;
|
|
}
|
|
|
|
/* Adjust the total entries count */
|
|
acles->entries_count -= src_count;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_port_acl_init - Initialize the ACL for a specified port on a ksz
|
|
* device.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number to initialize the ACL for.
|
|
*
|
|
* This function allocates memory for an acl structure, associates it with the
|
|
* specified port, and initializes the ACL entries to a default state. The
|
|
* entries are then written using the ksz9477_acl_write_list function, ensuring
|
|
* the ACL has a predictable initial hardware state.
|
|
*
|
|
* Returns: 0 on success, or an error code on failure.
|
|
*/
|
|
int ksz9477_port_acl_init(struct ksz_device *dev, int port)
|
|
{
|
|
struct ksz9477_acl_entries *acles;
|
|
struct ksz9477_acl_priv *acl;
|
|
int ret, i;
|
|
|
|
acl = kzalloc(sizeof(*acl), GFP_KERNEL);
|
|
if (!acl)
|
|
return -ENOMEM;
|
|
|
|
dev->ports[port].acl_priv = acl;
|
|
|
|
acles = &acl->acles;
|
|
/* write all entries */
|
|
for (i = 0; i < ARRAY_SIZE(acles->entries); i++) {
|
|
u8 *entry = acles->entries[i].entry;
|
|
|
|
/* Set all access bits to be able to write zeroed
|
|
* entry
|
|
*/
|
|
entry[KSZ9477_ACL_PORT_ACCESS_10] = 0xff;
|
|
entry[KSZ9477_ACL_PORT_ACCESS_11] = 0xff;
|
|
}
|
|
|
|
ret = ksz9477_acl_write_list(dev, port);
|
|
if (ret)
|
|
goto free_acl;
|
|
|
|
return 0;
|
|
|
|
free_acl:
|
|
kfree(dev->ports[port].acl_priv);
|
|
dev->ports[port].acl_priv = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_port_acl_free - Free the ACL resources for a specified port on a ksz
|
|
* device.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number to initialize the ACL for.
|
|
*
|
|
* This disables the ACL for the specified port and frees the associated memory,
|
|
*/
|
|
void ksz9477_port_acl_free(struct ksz_device *dev, int port)
|
|
{
|
|
if (!dev->ports[port].acl_priv)
|
|
return;
|
|
|
|
ksz9477_acl_port_disable(dev, port);
|
|
|
|
kfree(dev->ports[port].acl_priv);
|
|
dev->ports[port].acl_priv = NULL;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_set_reg - Set entry[16] and entry[17] depending on the updated
|
|
* entry[]
|
|
* @entry: An array containing the entries
|
|
* @reg: The register of the entry that needs to be updated
|
|
* @value: The value to be assigned to the updated entry
|
|
*
|
|
* This function updates the entry[] array based on the provided register and
|
|
* value. It also sets entry[0x10] and entry[0x11] according to the ACL byte
|
|
* enable rules.
|
|
*
|
|
* 0x10 - Byte Enable [15:8]
|
|
*
|
|
* Each bit enables accessing one of the ACL bytes when a read or write is
|
|
* initiated by writing to the Port ACL Byte Enable LSB Register.
|
|
* Bit 0 applies to the Port ACL Access 7 Register
|
|
* Bit 1 applies to the Port ACL Access 6 Register, etc.
|
|
* Bit 7 applies to the Port ACL Access 0 Register
|
|
* 1 = Byte is selected for read/write
|
|
* 0 = Byte is not selected
|
|
*
|
|
* 0x11 - Byte Enable [7:0]
|
|
*
|
|
* Each bit enables accessing one of the ACL bytes when a read or write is
|
|
* initiated by writing to the Port ACL Byte Enable LSB Register.
|
|
* Bit 0 applies to the Port ACL Access F Register
|
|
* Bit 1 applies to the Port ACL Access E Register, etc.
|
|
* Bit 7 applies to the Port ACL Access 8 Register
|
|
* 1 = Byte is selected for read/write
|
|
* 0 = Byte is not selected
|
|
*/
|
|
static void ksz9477_acl_set_reg(u8 *entry, enum ksz9477_acl_port_access reg,
|
|
u8 value)
|
|
{
|
|
if (reg >= KSZ9477_ACL_PORT_ACCESS_0 &&
|
|
reg <= KSZ9477_ACL_PORT_ACCESS_7) {
|
|
entry[KSZ9477_ACL_PORT_ACCESS_10] |=
|
|
BIT(KSZ9477_ACL_PORT_ACCESS_7 - reg);
|
|
} else if (reg >= KSZ9477_ACL_PORT_ACCESS_8 &&
|
|
reg <= KSZ9477_ACL_PORT_ACCESS_F) {
|
|
entry[KSZ9477_ACL_PORT_ACCESS_11] |=
|
|
BIT(KSZ9477_ACL_PORT_ACCESS_F - reg);
|
|
} else {
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
|
|
entry[reg] = value;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_matching_rule_cfg_l2 - Configure an ACL filtering entry to match
|
|
* L2 types of Ethernet frames
|
|
* @entry: Pointer to ACL entry buffer
|
|
* @ethertype: Ethertype value
|
|
* @eth_addr: Pointer to Ethernet address
|
|
* @is_src: If true, match the source MAC address; if false, match the
|
|
* destination MAC address
|
|
*
|
|
* This function configures an Access Control List (ACL) filtering
|
|
* entry to match Layer 2 types of Ethernet frames based on the provided
|
|
* ethertype and Ethernet address. Additionally, it can match either the source
|
|
* or destination MAC address depending on the value of the is_src parameter.
|
|
*
|
|
* Register Descriptions for MD = 01 and ENB != 00 (Layer 2 MAC header
|
|
* filtering)
|
|
*
|
|
* 0x01 - Mode and Enable
|
|
* Bits 5:4 - MD (Mode)
|
|
* 01 = Layer 2 MAC header or counter filtering
|
|
* Bits 3:2 - ENB (Enable)
|
|
* 01 = Comparison is performed only on the TYPE value
|
|
* 10 = Comparison is performed only on the MAC Address value
|
|
* 11 = Both the MAC Address and TYPE are tested
|
|
* Bit 1 - S/D (Source / Destination)
|
|
* 0 = Destination address
|
|
* 1 = Source address
|
|
* Bit 0 - EQ (Equal / Not Equal)
|
|
* 0 = Not Equal produces true result
|
|
* 1 = Equal produces true result
|
|
*
|
|
* 0x02-0x07 - MAC Address
|
|
* 0x02 - MAC Address [47:40]
|
|
* 0x03 - MAC Address [39:32]
|
|
* 0x04 - MAC Address [31:24]
|
|
* 0x05 - MAC Address [23:16]
|
|
* 0x06 - MAC Address [15:8]
|
|
* 0x07 - MAC Address [7:0]
|
|
*
|
|
* 0x08-0x09 - EtherType
|
|
* 0x08 - EtherType [15:8]
|
|
* 0x09 - EtherType [7:0]
|
|
*/
|
|
static void ksz9477_acl_matching_rule_cfg_l2(u8 *entry, u16 ethertype,
|
|
u8 *eth_addr, bool is_src)
|
|
{
|
|
u8 enb = 0;
|
|
u8 val;
|
|
|
|
if (ethertype)
|
|
enb |= KSZ9477_ACL_ENB_L2_TYPE;
|
|
if (eth_addr)
|
|
enb |= KSZ9477_ACL_ENB_L2_MAC;
|
|
|
|
val = FIELD_PREP(KSZ9477_ACL_MD_MASK, KSZ9477_ACL_MD_L2_MAC) |
|
|
FIELD_PREP(KSZ9477_ACL_ENB_MASK, enb) |
|
|
FIELD_PREP(KSZ9477_ACL_SD_SRC, is_src) | KSZ9477_ACL_EQ_EQUAL;
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_1, val);
|
|
|
|
if (eth_addr) {
|
|
int i;
|
|
|
|
for (i = 0; i < ETH_ALEN; i++) {
|
|
ksz9477_acl_set_reg(entry,
|
|
KSZ9477_ACL_PORT_ACCESS_2 + i,
|
|
eth_addr[i]);
|
|
}
|
|
}
|
|
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_8, ethertype >> 8);
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_9, ethertype & 0xff);
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_action_rule_cfg - Set action for an ACL entry
|
|
* @entry: Pointer to the ACL entry
|
|
* @force_prio: If true, force the priority value
|
|
* @prio_val: Priority value
|
|
*
|
|
* This function sets the action for the specified ACL entry. It prepares
|
|
* the priority mode and traffic class values and updates the entry's
|
|
* action registers accordingly. Currently, there is no port or VLAN PCP
|
|
* remapping.
|
|
*
|
|
* ACL Action Rule Parameters for Non-Count Modes (MD ≠ 01 or ENB ≠ 00)
|
|
*
|
|
* 0x0A - PM, P, RPE, RP[2:1]
|
|
* Bits 7:6 - PM[1:0] - Priority Mode
|
|
* 00 = ACL does not specify the packet priority. Priority is
|
|
* determined by standard QoS functions.
|
|
* 01 = Change packet priority to P[2:0] if it is greater than QoS
|
|
* result.
|
|
* 10 = Change packet priority to P[2:0] if it is smaller than the
|
|
* QoS result.
|
|
* 11 = Always change packet priority to P[2:0].
|
|
* Bits 5:3 - P[2:0] - Priority value
|
|
* Bit 2 - RPE - Remark Priority Enable
|
|
* Bits 1:0 - RP[2:1] - Remarked Priority value (bits 2:1)
|
|
* 0 = Disable priority remarking
|
|
* 1 = Enable priority remarking. VLAN tag priority (PCP) bits are
|
|
* replaced by RP[2:0].
|
|
*
|
|
* 0x0B - RP[0], MM
|
|
* Bit 7 - RP[0] - Remarked Priority value (bit 0)
|
|
* Bits 6:5 - MM[1:0] - Map Mode
|
|
* 00 = No forwarding remapping
|
|
* 01 = The forwarding map in FORWARD is OR'ed with the forwarding
|
|
* map from the Address Lookup Table.
|
|
* 10 = The forwarding map in FORWARD is AND'ed with the forwarding
|
|
* map from the Address Lookup Table.
|
|
* 11 = The forwarding map in FORWARD replaces the forwarding map
|
|
* from the Address Lookup Table.
|
|
* 0x0D - FORWARD[n:0]
|
|
* Bits 7:0 - FORWARD[n:0] - Forwarding map. Bit 0 = port 1,
|
|
* bit 1 = port 2, etc.
|
|
* 1 = enable forwarding to this port
|
|
* 0 = do not forward to this port
|
|
*/
|
|
void ksz9477_acl_action_rule_cfg(u8 *entry, bool force_prio, u8 prio_val)
|
|
{
|
|
u8 prio_mode, val;
|
|
|
|
if (force_prio)
|
|
prio_mode = KSZ9477_ACL_PM_REPLACE;
|
|
else
|
|
prio_mode = KSZ9477_ACL_PM_DISABLE;
|
|
|
|
val = FIELD_PREP(KSZ9477_ACL_PM_M, prio_mode) |
|
|
FIELD_PREP(KSZ9477_ACL_P_M, prio_val);
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_A, val);
|
|
|
|
/* no port or VLAN PCP remapping for now */
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_B, 0);
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_D, 0);
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_processing_rule_set_action - Set the action for the processing
|
|
* rule set.
|
|
* @entry: Pointer to the ACL entry
|
|
* @action_idx: Index of the action to be applied
|
|
*
|
|
* This function sets the action for the processing rule set by updating the
|
|
* appropriate register in the entry. There can be only one action per
|
|
* processing rule.
|
|
*
|
|
* Access Control List (ACL) Processing Rule Registers:
|
|
*
|
|
* 0x00 - First Rule Number (FRN)
|
|
* Bits 3:0 - First Rule Number. Pointer to an Action rule entry.
|
|
*/
|
|
void ksz9477_acl_processing_rule_set_action(u8 *entry, u8 action_idx)
|
|
{
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_0, action_idx);
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_processing_rule_add_match - Add a matching rule to the rule set
|
|
* @entry: Pointer to the ACL entry
|
|
* @match_idx: Index of the matching rule to be added
|
|
*
|
|
* This function adds a matching rule to the rule set by updating the
|
|
* appropriate bits in the entry's rule set registers.
|
|
*
|
|
* Access Control List (ACL) Processing Rule Registers:
|
|
*
|
|
* 0x0E - RuleSet [15:8]
|
|
* Bits 7:0 - RuleSet [15:8] Specifies a set of one or more Matching rule
|
|
* entries. RuleSet has one bit for each of the 16 Matching rule entries.
|
|
* If multiple Matching rules are selected, then all conditions will be
|
|
* AND'ed to produce a final match result.
|
|
* 0 = Matching rule not selected
|
|
* 1 = Matching rule selected
|
|
*
|
|
* 0x0F - RuleSet [7:0]
|
|
* Bits 7:0 - RuleSet [7:0]
|
|
*/
|
|
static void ksz9477_acl_processing_rule_add_match(u8 *entry, u8 match_idx)
|
|
{
|
|
u8 vale = entry[KSZ9477_ACL_PORT_ACCESS_E];
|
|
u8 valf = entry[KSZ9477_ACL_PORT_ACCESS_F];
|
|
|
|
if (match_idx < 8)
|
|
valf |= BIT(match_idx);
|
|
else
|
|
vale |= BIT(match_idx - 8);
|
|
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_E, vale);
|
|
ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_F, valf);
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_get_init_entry - Get a new uninitialized entry for a specified
|
|
* port on a ksz_device.
|
|
* @dev: The ksz_device instance.
|
|
* @port: The port number to get the uninitialized entry for.
|
|
* @cookie: The cookie to associate with the entry.
|
|
* @prio: The priority to associate with the entry.
|
|
*
|
|
* This function retrieves the next available ACL entry for the specified port,
|
|
* clears all access flags, and associates it with the current cookie.
|
|
*
|
|
* Returns: A pointer to the new uninitialized ACL entry.
|
|
*/
|
|
static struct ksz9477_acl_entry *
|
|
ksz9477_acl_get_init_entry(struct ksz_device *dev, int port,
|
|
unsigned long cookie, u32 prio)
|
|
{
|
|
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
|
|
struct ksz9477_acl_entries *acles = &acl->acles;
|
|
struct ksz9477_acl_entry *entry;
|
|
|
|
entry = &acles->entries[acles->entries_count];
|
|
entry->cookie = cookie;
|
|
entry->prio = prio;
|
|
|
|
/* clear all access flags */
|
|
entry->entry[KSZ9477_ACL_PORT_ACCESS_10] = 0;
|
|
entry->entry[KSZ9477_ACL_PORT_ACCESS_11] = 0;
|
|
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* ksz9477_acl_match_process_l2 - Configure Layer 2 ACL matching rules and
|
|
* processing rules.
|
|
* @dev: Pointer to the ksz_device.
|
|
* @port: Port number.
|
|
* @ethtype: Ethernet type.
|
|
* @src_mac: Source MAC address.
|
|
* @dst_mac: Destination MAC address.
|
|
* @cookie: The cookie to associate with the entry.
|
|
* @prio: The priority of the entry.
|
|
*
|
|
* This function sets up matching and processing rules for Layer 2 ACLs.
|
|
* It takes into account that only one MAC per entry is supported.
|
|
*/
|
|
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)
|
|
{
|
|
struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
|
|
struct ksz9477_acl_entries *acles = &acl->acles;
|
|
struct ksz9477_acl_entry *entry;
|
|
|
|
entry = ksz9477_acl_get_init_entry(dev, port, cookie, prio);
|
|
|
|
/* ACL supports only one MAC per entry */
|
|
if (src_mac && dst_mac) {
|
|
ksz9477_acl_matching_rule_cfg_l2(entry->entry, ethtype, src_mac,
|
|
true);
|
|
|
|
/* Add both match entries to first processing rule */
|
|
ksz9477_acl_processing_rule_add_match(entry->entry,
|
|
acles->entries_count);
|
|
acles->entries_count++;
|
|
ksz9477_acl_processing_rule_add_match(entry->entry,
|
|
acles->entries_count);
|
|
|
|
entry = ksz9477_acl_get_init_entry(dev, port, cookie, prio);
|
|
ksz9477_acl_matching_rule_cfg_l2(entry->entry, 0, dst_mac,
|
|
false);
|
|
acles->entries_count++;
|
|
} else {
|
|
u8 *mac = src_mac ? src_mac : dst_mac;
|
|
bool is_src = src_mac ? true : false;
|
|
|
|
ksz9477_acl_matching_rule_cfg_l2(entry->entry, ethtype, mac,
|
|
is_src);
|
|
ksz9477_acl_processing_rule_add_match(entry->entry,
|
|
acles->entries_count);
|
|
acles->entries_count++;
|
|
}
|
|
}
|