Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Roger Quadros 4841 99.94% 6 85.71%
Kees Cook 3 0.06% 1 14.29%
Total 4844 7


// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments PRUETH Switch Driver
 *
 * Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com
 */
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/remoteproc.h>
#include <net/switchdev.h>
#include "icssm_prueth.h"
#include "icssm_prueth_switch.h"
#include "icssm_prueth_fdb_tbl.h"

#define FDB_IDX_TBL_ENTRY(n) (&prueth->fdb_tbl->index_a->index_tbl_entry[n])

#define FDB_MAC_TBL_ENTRY(n) (&prueth->fdb_tbl->mac_tbl_a->mac_tbl_entry[n])

#define FLAG_IS_STATIC	BIT(0)
#define FLAG_ACTIVE	BIT(1)

#define FDB_LEARN  1
#define FDB_PURGE  2

struct icssm_prueth_sw_fdb_work {
	netdevice_tracker ndev_tracker;
	struct work_struct work;
	struct prueth_emac *emac;
	u8 addr[ETH_ALEN];
	int event;
};

const struct prueth_queue_info sw_queue_infos[][NUM_QUEUES] = {
	[PRUETH_PORT_QUEUE_HOST] = {
		[PRUETH_QUEUE1] = {
			P0_Q1_BUFFER_OFFSET,
			P0_QUEUE_DESC_OFFSET,
			P0_Q1_BD_OFFSET,
			P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE2] = {
			P0_Q2_BUFFER_OFFSET,
			P0_QUEUE_DESC_OFFSET + 8,
			P0_Q2_BD_OFFSET,
			P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE3] = {
			P0_Q3_BUFFER_OFFSET,
			P0_QUEUE_DESC_OFFSET + 16,
			P0_Q3_BD_OFFSET,
			P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE4] = {
			P0_Q4_BUFFER_OFFSET,
			P0_QUEUE_DESC_OFFSET + 24,
			P0_Q4_BD_OFFSET,
			P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
		},
	},
	[PRUETH_PORT_QUEUE_MII0] = {
		[PRUETH_QUEUE1] = {
			P1_Q1_BUFFER_OFFSET,
			P1_Q1_BUFFER_OFFSET +
				((QUEUE_1_SIZE - 1) * ICSS_BLOCK_SIZE),
			P1_Q1_BD_OFFSET,
			P1_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE2] = {
			P1_Q2_BUFFER_OFFSET,
			P1_Q2_BUFFER_OFFSET +
				((QUEUE_2_SIZE - 1) * ICSS_BLOCK_SIZE),
			P1_Q2_BD_OFFSET,
			P1_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE3] = {
			P1_Q3_BUFFER_OFFSET,
			P1_Q3_BUFFER_OFFSET +
				((QUEUE_3_SIZE - 1) * ICSS_BLOCK_SIZE),
			P1_Q3_BD_OFFSET,
			P1_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE4] = {
			P1_Q4_BUFFER_OFFSET,
			P1_Q4_BUFFER_OFFSET +
				((QUEUE_4_SIZE - 1) * ICSS_BLOCK_SIZE),
			P1_Q4_BD_OFFSET,
			P1_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE),
		},
	},
	[PRUETH_PORT_QUEUE_MII1] = {
		[PRUETH_QUEUE1] = {
			P2_Q1_BUFFER_OFFSET,
			P2_Q1_BUFFER_OFFSET +
				((QUEUE_1_SIZE - 1) * ICSS_BLOCK_SIZE),
			P2_Q1_BD_OFFSET,
			P2_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE2] = {
			P2_Q2_BUFFER_OFFSET,
			P2_Q2_BUFFER_OFFSET +
				((QUEUE_2_SIZE - 1) * ICSS_BLOCK_SIZE),
			P2_Q2_BD_OFFSET,
			P2_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE3] = {
			P2_Q3_BUFFER_OFFSET,
			P2_Q3_BUFFER_OFFSET +
				((QUEUE_3_SIZE - 1) * ICSS_BLOCK_SIZE),
			P2_Q3_BD_OFFSET,
			P2_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE4] = {
			P2_Q4_BUFFER_OFFSET,
			P2_Q4_BUFFER_OFFSET +
				((QUEUE_4_SIZE - 1) * ICSS_BLOCK_SIZE),
			P2_Q4_BD_OFFSET,
			P2_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE),
		},
	},
};

static const struct prueth_queue_info rx_queue_infos[][NUM_QUEUES] = {
	[PRUETH_PORT_QUEUE_HOST] = {
		[PRUETH_QUEUE1] = {
			P0_Q1_BUFFER_OFFSET,
			HOST_QUEUE_DESC_OFFSET,
			P0_Q1_BD_OFFSET,
			P0_Q1_BD_OFFSET + ((HOST_QUEUE_1_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE2] = {
			P0_Q2_BUFFER_OFFSET,
			HOST_QUEUE_DESC_OFFSET + 8,
			P0_Q2_BD_OFFSET,
			P0_Q2_BD_OFFSET + ((HOST_QUEUE_2_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE3] = {
			P0_Q3_BUFFER_OFFSET,
			HOST_QUEUE_DESC_OFFSET + 16,
			P0_Q3_BD_OFFSET,
			P0_Q3_BD_OFFSET + ((HOST_QUEUE_3_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE4] = {
			P0_Q4_BUFFER_OFFSET,
			HOST_QUEUE_DESC_OFFSET + 24,
			P0_Q4_BD_OFFSET,
			P0_Q4_BD_OFFSET + ((HOST_QUEUE_4_SIZE - 1) * BD_SIZE),
		},
	},
	[PRUETH_PORT_QUEUE_MII0] = {
		[PRUETH_QUEUE1] = {
			P1_Q1_BUFFER_OFFSET,
			P1_QUEUE_DESC_OFFSET,
			P1_Q1_BD_OFFSET,
			P1_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE2] = {
			P1_Q2_BUFFER_OFFSET,
			P1_QUEUE_DESC_OFFSET + 8,
			P1_Q2_BD_OFFSET,
			P1_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE3] = {
			P1_Q3_BUFFER_OFFSET,
			P1_QUEUE_DESC_OFFSET + 16,
			P1_Q3_BD_OFFSET,
			P1_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE4] = {
			P1_Q4_BUFFER_OFFSET,
			P1_QUEUE_DESC_OFFSET + 24,
			P1_Q4_BD_OFFSET,
			P1_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE),
		},
	},
	[PRUETH_PORT_QUEUE_MII1] = {
		[PRUETH_QUEUE1] = {
			P2_Q1_BUFFER_OFFSET,
			P2_QUEUE_DESC_OFFSET,
			P2_Q1_BD_OFFSET,
			P2_Q1_BD_OFFSET + ((QUEUE_1_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE2] = {
			P2_Q2_BUFFER_OFFSET,
			P2_QUEUE_DESC_OFFSET + 8,
			P2_Q2_BD_OFFSET,
			P2_Q2_BD_OFFSET + ((QUEUE_2_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE3] = {
			P2_Q3_BUFFER_OFFSET,
			P2_QUEUE_DESC_OFFSET + 16,
			P2_Q3_BD_OFFSET,
			P2_Q3_BD_OFFSET + ((QUEUE_3_SIZE - 1) * BD_SIZE),
		},
		[PRUETH_QUEUE4] = {
			P2_Q4_BUFFER_OFFSET,
			P2_QUEUE_DESC_OFFSET + 24,
			P2_Q4_BD_OFFSET,
			P2_Q4_BD_OFFSET + ((QUEUE_4_SIZE - 1) * BD_SIZE),
		},
	},
};

void icssm_prueth_sw_free_fdb_table(struct prueth *prueth)
{
	if (prueth->emac_configured)
		return;

	kfree(prueth->fdb_tbl);
	prueth->fdb_tbl = NULL;
}

void icssm_prueth_sw_fdb_tbl_init(struct prueth *prueth)
{
	struct fdb_tbl *t = prueth->fdb_tbl;
	void __iomem *sram_base;
	u8 val;

	sram_base = prueth->mem[PRUETH_MEM_SHARED_RAM].va;

	t->index_a = sram_base + V2_1_FDB_TBL_OFFSET;
	t->mac_tbl_a = sram_base + FDB_MAC_TBL_OFFSET;
	t->port1_stp_cfg = sram_base + FDB_PORT1_STP_CFG_OFFSET;
	t->port2_stp_cfg = sram_base + FDB_PORT2_STP_CFG_OFFSET;
	t->flood_enable_flags = sram_base + FDB_FLOOD_ENABLE_FLAGS_OFFSET;
	t->locks = sram_base + FDB_LOCKS_OFFSET;

	val = readb(t->flood_enable_flags);
	/* host_flood_enable = 1 */
	val |= BIT(0);
	/* port1_flood_enable = 1 */
	val |= BIT(1);
	/* port2_flood_enable = 1 */
	val |= BIT(2);
	writeb(val, t->flood_enable_flags);

	writeb(0, &t->locks->host_lock);
	t->total_entries = 0;
}

static u8 icssm_pru_lock_done(struct fdb_tbl *fdb_tbl)
{
	return readb(&fdb_tbl->locks->pru_locks);
}

static int icssm_prueth_sw_fdb_spin_lock(struct fdb_tbl *fdb_tbl)
{
	u8 done;
	int ret;

	/* Take the host lock */
	writeb(1, &fdb_tbl->locks->host_lock);

	/* Wait for the PRUs to release their locks */
	ret = read_poll_timeout(icssm_pru_lock_done, done, done == 0,
				1, 10, false, fdb_tbl);
	if (ret == -ETIMEDOUT)
		writeb(0, &fdb_tbl->locks->host_lock);

	return ret;
}

static void icssm_prueth_sw_fdb_spin_unlock(struct fdb_tbl *fdb_tbl)
{
	writeb(0, &fdb_tbl->locks->host_lock);
}

static u8 icssm_prueth_sw_fdb_hash(const u8 *mac)
{
	return (mac[0] ^ mac[1] ^ mac[2] ^ mac[3] ^ mac[4] ^ mac[5]);
}

static int
icssm_prueth_sw_fdb_search(struct fdb_mac_tbl_array __iomem *mac_tbl,
			   struct fdb_index_tbl_entry __iomem *bucket_info,
			   const u8 *mac)
{
	unsigned int bucket_entries, mac_tbl_idx;
	u8 tmp_mac[ETH_ALEN];
	int i;

	mac_tbl_idx = readw(&bucket_info->bucket_idx);
	bucket_entries = readw(&bucket_info->bucket_entries);
	for (i = 0; i < bucket_entries; i++, mac_tbl_idx++) {
		memcpy_fromio(tmp_mac, mac_tbl->mac_tbl_entry[mac_tbl_idx].mac,
			      ETH_ALEN);
		if (ether_addr_equal(mac, tmp_mac))
			return mac_tbl_idx;
	}

	return -ENODATA;
}

static int icssm_prueth_sw_fdb_find_open_slot(struct fdb_tbl *fdb_tbl)
{
	unsigned int i;
	u8 flags;

	for (i = 0; i < FDB_MAC_TBL_MAX_ENTRIES; i++) {
		flags = readb(&fdb_tbl->mac_tbl_a->mac_tbl_entry[i].flags);
		if (!(flags & FLAG_ACTIVE))
			break;
	}

	return i;
}

static int
icssm_prueth_sw_find_fdb_insert(struct fdb_tbl *fdb, struct prueth *prueth,
				struct fdb_index_tbl_entry __iomem *bkt_info,
				const u8 *mac, const u8 port)
{
	struct fdb_mac_tbl_array __iomem *mac_tbl = fdb->mac_tbl_a;
	unsigned int bucket_entries, mac_tbl_idx;
	struct fdb_mac_tbl_entry __iomem *e;
	u8 mac_from_hw[ETH_ALEN];
	s8 cmp;
	int i;

	mac_tbl_idx = readw(&bkt_info->bucket_idx);
	bucket_entries = readw(&bkt_info->bucket_entries);

	for (i = 0; i < bucket_entries; i++, mac_tbl_idx++) {
		e = &mac_tbl->mac_tbl_entry[mac_tbl_idx];
		memcpy_fromio(mac_from_hw, e->mac, ETH_ALEN);
		cmp = memcmp(mac, mac_from_hw, ETH_ALEN);
		if (cmp < 0) {
			return mac_tbl_idx;
		} else if (cmp == 0) {
			if (readb(&e->port) != port) {
				/* MAC is already in FDB, only port is
				 * different. So just update the port.
				 * Note: total_entries and bucket_entries
				 * remain the same.
				 */
				writeb(port, &e->port);
			}

			/* MAC and port are the same, touch the fdb */
			writew(0, &e->age);
			return -EEXIST;
		}
	}

	return mac_tbl_idx;
}

static int
icssm_prueth_sw_fdb_empty_slot_left(struct fdb_mac_tbl_array __iomem *mac_tbl,
				    unsigned int mac_tbl_idx)
{
	u8 flags;
	int i;

	for (i = mac_tbl_idx - 1; i > -1; i--) {
		flags = readb(&mac_tbl->mac_tbl_entry[i].flags);
		if (!(flags & FLAG_ACTIVE))
			break;
	}

	return i;
}

static int
icssm_prueth_sw_fdb_empty_slot_right(struct fdb_mac_tbl_array __iomem *mac_tbl,
				     unsigned int mac_tbl_idx)
{
	u8 flags;
	int i;

	for (i = mac_tbl_idx; i < FDB_MAC_TBL_MAX_ENTRIES; i++) {
		flags = readb(&mac_tbl->mac_tbl_entry[i].flags);
		if (!(flags & FLAG_ACTIVE))
			return i;
	}

	return -1;
}

static void icssm_prueth_sw_fdb_move_range_left(struct prueth *prueth,
						u16 left, u16 right)
{
	struct fdb_mac_tbl_entry entry;
	u32 sz = 0;
	u16 i;

	sz = sizeof(struct fdb_mac_tbl_entry);
	for (i = left; i < right; i++) {
		memcpy_fromio(&entry, FDB_MAC_TBL_ENTRY(i + 1), sz);
		memcpy_toio(FDB_MAC_TBL_ENTRY(i), &entry, sz);
	}
}

static void icssm_prueth_sw_fdb_move_range_right(struct prueth *prueth,
						 u16 left, u16 right)
{
	struct fdb_mac_tbl_entry entry;
	u32 sz = 0;
	u16 i;

	sz = sizeof(struct fdb_mac_tbl_entry);
	for (i = right; i > left; i--) {
		memcpy_fromio(&entry, FDB_MAC_TBL_ENTRY(i - 1), sz);
		memcpy_toio(FDB_MAC_TBL_ENTRY(i), &entry, sz);
	}
}

static void icssm_prueth_sw_fdb_update_index_tbl(struct prueth *prueth,
						 u16 left, u16 right)
{
	unsigned int hash, hash_prev;
	u8 mac[ETH_ALEN];
	unsigned int i;

	/* To ensure we don't improperly update the
	 * bucket index, initialize with an invalid
	 * hash in case we are in leftmost slot
	 */
	hash_prev = 0xff;

	if (left > 0) {
		memcpy_fromio(mac, FDB_MAC_TBL_ENTRY(left - 1)->mac, ETH_ALEN);
		hash_prev = icssm_prueth_sw_fdb_hash(mac);
	}

	/* For each moved element, update the bucket index */
	for (i = left; i <= right; i++) {
		memcpy_fromio(mac, FDB_MAC_TBL_ENTRY(i)->mac, ETH_ALEN);
		hash = icssm_prueth_sw_fdb_hash(mac);

		/* Only need to update buckets once */
		if (hash != hash_prev)
			writew(i, &FDB_IDX_TBL_ENTRY(hash)->bucket_idx);

		hash_prev = hash;
	}
}

static struct fdb_mac_tbl_entry __iomem *
icssm_prueth_sw_find_free_mac(struct prueth *prueth, struct fdb_index_tbl_entry
			      __iomem *bucket_info, u8 suggested_mac_tbl_idx,
			      bool *update_indexes, const u8 *mac)
{
	s16 empty_slot_idx = 0, left = 0, right = 0;
	unsigned int mti = suggested_mac_tbl_idx;
	struct fdb_mac_tbl_array __iomem *mt;
	struct fdb_tbl *fdb;
	u8 flags;

	fdb = prueth->fdb_tbl;
	mt = fdb->mac_tbl_a;

	flags = readb(&FDB_MAC_TBL_ENTRY(mti)->flags);
	if (!(flags & FLAG_ACTIVE)) {
		/* Claim the entry */
		flags |= FLAG_ACTIVE;
		writeb(flags, &FDB_MAC_TBL_ENTRY(mti)->flags);

		return FDB_MAC_TBL_ENTRY(mti);
	}

	if (fdb->total_entries == FDB_MAC_TBL_MAX_ENTRIES)
		return NULL;

	empty_slot_idx = icssm_prueth_sw_fdb_empty_slot_left(mt, mti);
	if (empty_slot_idx == -1) {
		/* Nothing available on the left. But table isn't full
		 * so there must be space to the right,
		 */
		empty_slot_idx = icssm_prueth_sw_fdb_empty_slot_right(mt, mti);

		/* Shift right */
		left = mti;
		right = empty_slot_idx;
		icssm_prueth_sw_fdb_move_range_right(prueth, left, right);

		/* Claim the entry */
		flags = readb(&FDB_MAC_TBL_ENTRY(mti)->flags);
		flags |= FLAG_ACTIVE;
		writeb(flags, &FDB_MAC_TBL_ENTRY(mti)->flags);

		memcpy_toio(FDB_MAC_TBL_ENTRY(mti)->mac, mac, ETH_ALEN);

		/* There is a chance we moved something in a
		 * different bucket, update index table
		 */
		icssm_prueth_sw_fdb_update_index_tbl(prueth, left, right);

		return FDB_MAC_TBL_ENTRY(mti);
	}

	if (empty_slot_idx == mti - 1) {
		/* There is space immediately left of the open slot,
		 * which means the inserted MAC address
		 * must be the lowest-valued MAC address in bucket.
		 * Update bucket pointer accordingly.
		 */
		writew(empty_slot_idx, &bucket_info->bucket_idx);

		/* Claim the entry */
		flags = readb(&FDB_MAC_TBL_ENTRY(empty_slot_idx)->flags);
		flags |= FLAG_ACTIVE;
		writeb(flags, &FDB_MAC_TBL_ENTRY(empty_slot_idx)->flags);

		return FDB_MAC_TBL_ENTRY(empty_slot_idx);
	}

	/* There is empty space to the left, shift MAC table entries left */
	left = empty_slot_idx;
	right = mti - 1;
	icssm_prueth_sw_fdb_move_range_left(prueth, left, right);

	/* Claim the entry */
	flags = readb(&FDB_MAC_TBL_ENTRY(mti - 1)->flags);
	flags |= FLAG_ACTIVE;
	writeb(flags, &FDB_MAC_TBL_ENTRY(mti - 1)->flags);

	memcpy_toio(FDB_MAC_TBL_ENTRY(mti - 1)->mac, mac, ETH_ALEN);

	/* There is a chance we moved something in a
	 * different bucket, update index table
	 */
	icssm_prueth_sw_fdb_update_index_tbl(prueth, left, right);

	return FDB_MAC_TBL_ENTRY(mti - 1);
}

static int icssm_prueth_sw_insert_fdb_entry(struct prueth_emac *emac,
					    const u8 *mac, u8 is_static)
{
	struct fdb_index_tbl_entry __iomem *bucket_info;
	struct fdb_mac_tbl_entry __iomem *mac_info;
	struct prueth *prueth = emac->prueth;
	unsigned int hash_val, mac_tbl_idx;
	struct prueth_emac *other_emac;
	enum prueth_port other_port_id;
	int total_fdb_entries;
	struct fdb_tbl *fdb;
	u8 flags;
	s16 ret;
	int err;
	u16 val;

	fdb = prueth->fdb_tbl;
	other_port_id = (emac->port_id == PRUETH_PORT_MII0) ?
			 PRUETH_PORT_MII1 : PRUETH_PORT_MII0;

	other_emac = prueth->emac[other_port_id - 1];
	if (!other_emac)
		return -EINVAL;

	err = icssm_prueth_sw_fdb_spin_lock(fdb);
	if (err) {
		dev_err(prueth->dev, "PRU lock timeout %d\n", err);
		return err;
	}

	if (fdb->total_entries == FDB_MAC_TBL_MAX_ENTRIES) {
		icssm_prueth_sw_fdb_spin_unlock(fdb);
		return -ENOMEM;
	}

	if (ether_addr_equal(mac, emac->mac_addr) ||
	    (ether_addr_equal(mac, other_emac->mac_addr))) {
		icssm_prueth_sw_fdb_spin_unlock(fdb);
		/* Don't insert fdb of own mac addr */
		return -EINVAL;
	}

	/* Get the bucket that the mac belongs to */
	hash_val = icssm_prueth_sw_fdb_hash(mac);
	bucket_info = FDB_IDX_TBL_ENTRY(hash_val);

	if (!readw(&bucket_info->bucket_entries)) {
		mac_tbl_idx = icssm_prueth_sw_fdb_find_open_slot(fdb);
		writew(mac_tbl_idx, &bucket_info->bucket_idx);
	}

	ret = icssm_prueth_sw_find_fdb_insert(fdb, prueth, bucket_info, mac,
					      emac->port_id - 1);
	if (ret < 0) {
		icssm_prueth_sw_fdb_spin_unlock(fdb);
		/* mac is already in fdb table */
		return 0;
	}

	mac_tbl_idx = ret;

	mac_info = icssm_prueth_sw_find_free_mac(prueth, bucket_info,
						 mac_tbl_idx, NULL,
						 mac);
	if (!mac_info) {
		/* Should not happen */
		dev_warn(prueth->dev, "OUT of FDB MEM\n");
		icssm_prueth_sw_fdb_spin_unlock(fdb);
		return -ENOMEM;
	}

	memcpy_toio(mac_info->mac, mac, ETH_ALEN);
	writew(0, &mac_info->age);
	writeb(emac->port_id - 1, &mac_info->port);

	flags = readb(&mac_info->flags);
	if (is_static)
		flags |= FLAG_IS_STATIC;
	else
		flags &= ~FLAG_IS_STATIC;

	/* bit 1 - active */
	flags |= FLAG_ACTIVE;
	writeb(flags, &mac_info->flags);

	val = readw(&bucket_info->bucket_entries);
	val++;
	writew(val, &bucket_info->bucket_entries);

	fdb->total_entries++;

	total_fdb_entries = fdb->total_entries;

	icssm_prueth_sw_fdb_spin_unlock(fdb);

	dev_dbg(prueth->dev, "added fdb: %pM port=%d total_entries=%u\n",
		mac, emac->port_id, total_fdb_entries);

	return 0;
}

static int icssm_prueth_sw_delete_fdb_entry(struct prueth_emac *emac,
					    const u8 *mac, u8 is_static)
{
	struct fdb_index_tbl_entry __iomem *bucket_info;
	struct fdb_mac_tbl_entry __iomem *mac_info;
	struct fdb_mac_tbl_array __iomem *mt;
	unsigned int hash_val, mac_tbl_idx;
	unsigned int idx, entries;
	struct prueth *prueth;
	int total_fdb_entries;
	s16 ret, left, right;
	struct fdb_tbl *fdb;
	u8 flags;
	int err;
	u16 val;

	prueth = emac->prueth;
	fdb = prueth->fdb_tbl;
	mt = fdb->mac_tbl_a;

	err = icssm_prueth_sw_fdb_spin_lock(fdb);
	if (err) {
		dev_err(prueth->dev, "PRU lock timeout %d\n", err);
		return err;
	}

	if (fdb->total_entries == 0) {
		icssm_prueth_sw_fdb_spin_unlock(fdb);
		return 0;
	}

	/* Get the bucket that the mac belongs to */
	hash_val = icssm_prueth_sw_fdb_hash(mac);
	bucket_info = FDB_IDX_TBL_ENTRY(hash_val);

	ret = icssm_prueth_sw_fdb_search(mt, bucket_info, mac);
	if (ret < 0) {
		icssm_prueth_sw_fdb_spin_unlock(fdb);
		return ret;
	}

	mac_tbl_idx = ret;
	mac_info = FDB_MAC_TBL_ENTRY(mac_tbl_idx);

	/* Shift all elements in bucket to the left. No need to
	 * update index table since only shifting within bucket.
	 */
	left = mac_tbl_idx;
	idx = readw(&bucket_info->bucket_idx);
	entries = readw(&bucket_info->bucket_entries);
	right = idx + entries - 1;
	icssm_prueth_sw_fdb_move_range_left(prueth, left, right);

	/* Remove end of bucket from table */
	mac_info = FDB_MAC_TBL_ENTRY(right);
	flags = readb(&mac_info->flags);
	/* active = 0 */
	flags &= ~FLAG_ACTIVE;
	writeb(flags, &mac_info->flags);
	val = readw(&bucket_info->bucket_entries);
	val--;
	writew(val, &bucket_info->bucket_entries);
	fdb->total_entries--;

	total_fdb_entries = fdb->total_entries;

	icssm_prueth_sw_fdb_spin_unlock(fdb);

	dev_dbg(prueth->dev, "del fdb: %pM total_entries=%u\n",
		mac, total_fdb_entries);

	return 0;
}

int icssm_prueth_sw_do_purge_fdb(struct prueth_emac *emac)
{
	struct fdb_index_tbl_entry __iomem *bucket_info;
	struct prueth *prueth = emac->prueth;
	u8 flags, mac[ETH_ALEN];
	unsigned int hash_val;
	struct fdb_tbl *fdb;
	int ret, i;
	u16 val;

	fdb = prueth->fdb_tbl;

	ret = icssm_prueth_sw_fdb_spin_lock(fdb);
	if (ret) {
		dev_err(prueth->dev, "PRU lock timeout %d\n", ret);
		return ret;
	}

	if (fdb->total_entries == 0) {
		icssm_prueth_sw_fdb_spin_unlock(fdb);
		return 0;
	}

	for (i = 0; i < FDB_MAC_TBL_MAX_ENTRIES; i++) {
		flags = readb(&fdb->mac_tbl_a->mac_tbl_entry[i].flags);
		if ((flags & FLAG_ACTIVE) && !(flags & FLAG_IS_STATIC)) {
			/* Get the bucket that the mac belongs to */
			memcpy_fromio(mac, FDB_MAC_TBL_ENTRY(i)->mac,
				      ETH_ALEN);
			hash_val = icssm_prueth_sw_fdb_hash(mac);
			bucket_info = FDB_IDX_TBL_ENTRY(hash_val);
			flags &= ~FLAG_ACTIVE;
			writeb(flags,
			       &fdb->mac_tbl_a->mac_tbl_entry[i].flags);
			val = readw(&bucket_info->bucket_entries);
			val--;
			writew(val, &bucket_info->bucket_entries);
			fdb->total_entries--;
		}
	}

	icssm_prueth_sw_fdb_spin_unlock(fdb);
	return 0;
}

int icssm_prueth_sw_init_fdb_table(struct prueth *prueth)
{
	if (prueth->emac_configured)
		return 0;

	prueth->fdb_tbl = kmalloc_obj(*prueth->fdb_tbl);
	if (!prueth->fdb_tbl)
		return -ENOMEM;

	icssm_prueth_sw_fdb_tbl_init(prueth);

	return 0;
}

/**
 * icssm_prueth_sw_fdb_add - insert fdb entry
 *
 * @emac: EMAC data structure
 * @fdb: fdb info
 *
 */
void icssm_prueth_sw_fdb_add(struct prueth_emac *emac,
			     struct switchdev_notifier_fdb_info *fdb)
{
	icssm_prueth_sw_insert_fdb_entry(emac, fdb->addr, 1);
}

/**
 * icssm_prueth_sw_fdb_del - delete fdb entry
 *
 * @emac: EMAC data structure
 * @fdb: fdb info
 *
 */
void icssm_prueth_sw_fdb_del(struct prueth_emac *emac,
			     struct switchdev_notifier_fdb_info *fdb)
{
	icssm_prueth_sw_delete_fdb_entry(emac, fdb->addr, 1);
}

static void icssm_prueth_sw_fdb_work(struct work_struct *work)
{
	struct icssm_prueth_sw_fdb_work *fdb_work =
		container_of(work, struct icssm_prueth_sw_fdb_work, work);
	struct prueth_emac *emac = fdb_work->emac;

	rtnl_lock();

	/* Interface is not up */
	if (!emac->prueth->fdb_tbl)
		goto free;

	switch (fdb_work->event) {
	case FDB_LEARN:
		icssm_prueth_sw_insert_fdb_entry(emac, fdb_work->addr, 0);
		break;
	case FDB_PURGE:
		icssm_prueth_sw_do_purge_fdb(emac);
		break;
	default:
		break;
	}

free:
	rtnl_unlock();
	netdev_put(emac->ndev, &fdb_work->ndev_tracker);
	kfree(fdb_work);
}

int icssm_prueth_sw_learn_fdb(struct prueth_emac *emac, u8 *src_mac)
{
	struct icssm_prueth_sw_fdb_work *fdb_work;

	fdb_work = kzalloc_obj(*fdb_work, GFP_ATOMIC);
	if (WARN_ON(!fdb_work))
		return -ENOMEM;

	INIT_WORK(&fdb_work->work, icssm_prueth_sw_fdb_work);

	fdb_work->event = FDB_LEARN;
	fdb_work->emac  = emac;
	ether_addr_copy(fdb_work->addr, src_mac);

	netdev_hold(emac->ndev, &fdb_work->ndev_tracker, GFP_ATOMIC);
	queue_work(system_long_wq, &fdb_work->work);
	return 0;
}

int icssm_prueth_sw_purge_fdb(struct prueth_emac *emac)
{
	struct icssm_prueth_sw_fdb_work *fdb_work;

	fdb_work = kzalloc_obj(*fdb_work, GFP_ATOMIC);
	if (WARN_ON(!fdb_work))
		return -ENOMEM;

	INIT_WORK(&fdb_work->work, icssm_prueth_sw_fdb_work);

	fdb_work->event = FDB_PURGE;
	fdb_work->emac  = emac;

	netdev_hold(emac->ndev, &fdb_work->ndev_tracker, GFP_ATOMIC);
	queue_work(system_long_wq, &fdb_work->work);
	return 0;
}

void icssm_prueth_sw_hostconfig(struct prueth *prueth)
{
	void __iomem *dram1_base = prueth->mem[PRUETH_MEM_DRAM1].va;
	void __iomem *dram;

	/* queue information table */
	dram = dram1_base + P0_Q1_RX_CONTEXT_OFFSET;
	memcpy_toio(dram, sw_queue_infos[PRUETH_PORT_QUEUE_HOST],
		    sizeof(sw_queue_infos[PRUETH_PORT_QUEUE_HOST]));

	/* buffer descriptor offset table*/
	dram = dram1_base + QUEUE_DESCRIPTOR_OFFSET_ADDR;
	writew(P0_Q1_BD_OFFSET, dram);
	writew(P0_Q2_BD_OFFSET, dram + 2);
	writew(P0_Q3_BD_OFFSET, dram + 4);
	writew(P0_Q4_BD_OFFSET, dram + 6);

	/* buffer offset table */
	dram = dram1_base + QUEUE_OFFSET_ADDR;
	writew(P0_Q1_BUFFER_OFFSET, dram);
	writew(P0_Q2_BUFFER_OFFSET, dram + 2);
	writew(P0_Q3_BUFFER_OFFSET, dram + 4);
	writew(P0_Q4_BUFFER_OFFSET, dram + 6);

	/* queue size lookup table */
	dram = dram1_base + QUEUE_SIZE_ADDR;
	writew(HOST_QUEUE_1_SIZE, dram);
	writew(HOST_QUEUE_1_SIZE, dram + 2);
	writew(HOST_QUEUE_1_SIZE, dram + 4);
	writew(HOST_QUEUE_1_SIZE, dram + 6);

	/* queue table */
	dram = dram1_base + P0_QUEUE_DESC_OFFSET;
	memcpy_toio(dram, queue_descs[PRUETH_PORT_QUEUE_HOST],
		    sizeof(queue_descs[PRUETH_PORT_QUEUE_HOST]));
}

static int icssm_prueth_sw_port_config(struct prueth *prueth,
				       enum prueth_port port_id)
{
	unsigned int tx_context_ofs_addr, rx_context_ofs, queue_desc_ofs;
	void __iomem *dram, *dram_base, *dram_mac;
	struct prueth_emac *emac;
	void __iomem *dram1_base;

	dram1_base = prueth->mem[PRUETH_MEM_DRAM1].va;
	emac = prueth->emac[port_id - 1];
	switch (port_id) {
	case PRUETH_PORT_MII0:
		tx_context_ofs_addr     = TX_CONTEXT_P1_Q1_OFFSET_ADDR;
		rx_context_ofs          = P1_Q1_RX_CONTEXT_OFFSET;
		queue_desc_ofs          = P1_QUEUE_DESC_OFFSET;

		/* for switch PORT MII0 mac addr is in DRAM0. */
		dram_mac = prueth->mem[PRUETH_MEM_DRAM0].va;
		break;
	case PRUETH_PORT_MII1:
		tx_context_ofs_addr     = TX_CONTEXT_P2_Q1_OFFSET_ADDR;
		rx_context_ofs          = P2_Q1_RX_CONTEXT_OFFSET;
		queue_desc_ofs          = P2_QUEUE_DESC_OFFSET;

		/* for switch PORT MII1 mac addr is in DRAM1. */
		dram_mac = prueth->mem[PRUETH_MEM_DRAM1].va;
		break;
	default:
		netdev_err(emac->ndev, "invalid port\n");
		return -EINVAL;
	}

	/* setup mac address */
	memcpy_toio(dram_mac + PORT_MAC_ADDR, emac->mac_addr, 6);

	/* Remaining switch port configs are in DRAM1 */
	dram_base = prueth->mem[PRUETH_MEM_DRAM1].va;

	/* queue information table */
	memcpy_toio(dram_base + tx_context_ofs_addr,
		    sw_queue_infos[port_id],
		    sizeof(sw_queue_infos[port_id]));

	memcpy_toio(dram_base + rx_context_ofs,
		    rx_queue_infos[port_id],
		    sizeof(rx_queue_infos[port_id]));

	/* buffer descriptor offset table*/
	dram = dram_base + QUEUE_DESCRIPTOR_OFFSET_ADDR +
	       (port_id * NUM_QUEUES * sizeof(u16));
	writew(sw_queue_infos[port_id][PRUETH_QUEUE1].buffer_desc_offset, dram);
	writew(sw_queue_infos[port_id][PRUETH_QUEUE2].buffer_desc_offset,
	       dram + 2);
	writew(sw_queue_infos[port_id][PRUETH_QUEUE3].buffer_desc_offset,
	       dram + 4);
	writew(sw_queue_infos[port_id][PRUETH_QUEUE4].buffer_desc_offset,
	       dram + 6);

	/* buffer offset table */
	dram = dram_base + QUEUE_OFFSET_ADDR +
	       port_id * NUM_QUEUES * sizeof(u16);
	writew(sw_queue_infos[port_id][PRUETH_QUEUE1].buffer_offset, dram);
	writew(sw_queue_infos[port_id][PRUETH_QUEUE2].buffer_offset,
	       dram + 2);
	writew(sw_queue_infos[port_id][PRUETH_QUEUE3].buffer_offset,
	       dram + 4);
	writew(sw_queue_infos[port_id][PRUETH_QUEUE4].buffer_offset,
	       dram + 6);

	/* queue size lookup table */
	dram = dram_base + QUEUE_SIZE_ADDR +
	       port_id * NUM_QUEUES * sizeof(u16);
	writew(QUEUE_1_SIZE, dram);
	writew(QUEUE_2_SIZE, dram + 2);
	writew(QUEUE_3_SIZE, dram + 4);
	writew(QUEUE_4_SIZE, dram + 6);

	/* queue table */
	memcpy_toio(dram_base + queue_desc_ofs,
		    &queue_descs[port_id][0],
		    4 * sizeof(queue_descs[port_id][0]));

	emac->rx_queue_descs = dram1_base + P0_QUEUE_DESC_OFFSET;
	emac->tx_queue_descs = dram1_base +
		rx_queue_infos[port_id][PRUETH_QUEUE1].queue_desc_offset;

	return 0;
}

int icssm_prueth_sw_emac_config(struct prueth_emac *emac)
{
	struct prueth *prueth = emac->prueth;
	u32 sharedramaddr, ocmcaddr;
	int ret;

	/* PRU needs local shared RAM address for C28 */
	sharedramaddr = ICSS_LOCAL_SHARED_RAM;
	/* PRU needs real global OCMC address for C30*/
	ocmcaddr = (u32)prueth->mem[PRUETH_MEM_OCMC].pa;

	if (prueth->emac_configured & BIT(emac->port_id))
		return 0;

	ret = icssm_prueth_sw_port_config(prueth, emac->port_id);
	if (ret)
		return ret;

	if (!prueth->emac_configured) {
		/* Set in constant table C28 of PRUn to ICSS Shared memory */
		pru_rproc_set_ctable(prueth->pru0, PRU_C28, sharedramaddr);
		pru_rproc_set_ctable(prueth->pru1, PRU_C28, sharedramaddr);

		/* Set in constant table C30 of PRUn to OCMC memory */
		pru_rproc_set_ctable(prueth->pru0, PRU_C30, ocmcaddr);
		pru_rproc_set_ctable(prueth->pru1, PRU_C30, ocmcaddr);
	}
	return 0;
}

int icssm_prueth_sw_boot_prus(struct prueth *prueth, struct net_device *ndev)
{
	const struct prueth_firmware *pru_firmwares;
	const char *fw_name, *fw_name1;
	int ret;

	if (prueth->emac_configured)
		return 0;

	pru_firmwares = &prueth->fw_data->fw_pru[PRUSS_PRU0];
	fw_name = pru_firmwares->fw_name[prueth->eth_type];
	pru_firmwares = &prueth->fw_data->fw_pru[PRUSS_PRU1];
	fw_name1 = pru_firmwares->fw_name[prueth->eth_type];

	ret = rproc_set_firmware(prueth->pru0, fw_name);
	if (ret) {
		netdev_err(ndev, "failed to set PRU0 firmware %s: %d\n",
			   fw_name, ret);
		return ret;
	}
	ret = rproc_boot(prueth->pru0);
	if (ret) {
		netdev_err(ndev, "failed to boot PRU0: %d\n", ret);
		return ret;
	}

	ret = rproc_set_firmware(prueth->pru1, fw_name1);
	if (ret) {
		netdev_err(ndev, "failed to set PRU1 firmware %s: %d\n",
			   fw_name1, ret);
		goto rproc0_shutdown;
	}
	ret = rproc_boot(prueth->pru1);
	if (ret) {
		netdev_err(ndev, "failed to boot PRU1: %d\n", ret);
		goto rproc0_shutdown;
	}

	return 0;

rproc0_shutdown:
	rproc_shutdown(prueth->pru0);
	return ret;
}

int icssm_prueth_sw_shutdown_prus(struct prueth_emac *emac,
				  struct net_device *ndev)
{
	struct prueth *prueth = emac->prueth;

	if (prueth->emac_configured)
		return 0;

	rproc_shutdown(prueth->pru0);
	rproc_shutdown(prueth->pru1);

	return 0;
}