/* 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) | * +----------------------+
*/
/* 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
/** * 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.
*/ staticint 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;
/** * ksz9477_dump_acl - Print ACL entries * * @dev: Pointer to the device structure. * @acle: Pointer to the ACL entry array.
*/ staticvoid 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.
*/ staticbool 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) returnfalse;
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) returnfalse;
}
returntrue;
}
/** * 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
*/ staticint 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; unsignedlong val;
u8 vale, valf;
u8 *entry; int i;
/* 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;
}
/* 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.
*/ staticint ksz9477_acl_update_linkage(struct ksz_device *dev, u8 *entry,
u16 old_idx, u16 new_idx)
{ unsignedint original_bit_count; unsignedlong rule_linkage;
u8 vale, valf, val0; int shift;
/* 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.
*/ staticint 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;
/* 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.
*/ staticint 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.
*/ staticint 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.
*/ staticint 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.
*/ staticint 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.
*/ staticint ksz9477_swap_acl_blocks(struct ksz_device *dev, int port, int i, int j)
{ int ret, current_block_size;
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.
*/ staticint 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;
}
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.
*/ staticint ksz9477_acl_wait_ready(struct ksz_device *dev, int port)
{ unsignedint wr_mask = KSZ9477_ACL_WRITE_DONE | KSZ9477_ACL_READ_DONE; unsignedint val, reg; int ret;
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.
*/ staticint 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.
*/ staticint 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;
/** * 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.
*/ staticint 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;
/** * 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, unsignedlong 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;
/** * 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_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
*/ staticvoid 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);
} elseif (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]
*/ staticvoid 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;
/** * 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]
*/ staticvoid 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];
/** * 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.
*/ staticstruct ksz9477_acl_entry *
ksz9477_acl_get_init_entry(struct ksz_device *dev, int port, unsignedlong 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;
/** * 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, unsignedlong 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;
/* 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.