#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);
val |= BIT(0);
val |= BIT(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;
writeb(1, &fdb_tbl->locks->host_lock);
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) {
writeb(port, &e->port);
}
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;
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 (i = left; i <= right; i++) {
memcpy_fromio(mac, FDB_MAC_TBL_ENTRY(i)->mac, ETH_ALEN);
hash = icssm_prueth_sw_fdb_hash(mac);
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)) {
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) {
empty_slot_idx = icssm_prueth_sw_fdb_empty_slot_right(mt, mti);
left = mti;
right = empty_slot_idx;
icssm_prueth_sw_fdb_move_range_right(prueth, left, right);
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);
icssm_prueth_sw_fdb_update_index_tbl(prueth, left, right);
return FDB_MAC_TBL_ENTRY(mti);
}
if (empty_slot_idx == mti - 1) {
writew(empty_slot_idx, &bucket_info->bucket_idx);
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);
}
left = empty_slot_idx;
right = mti - 1;
icssm_prueth_sw_fdb_move_range_left(prueth, left, right);
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);
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);
return -EINVAL;
}
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);
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) {
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;
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;
}
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);
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);
mac_info = FDB_MAC_TBL_ENTRY(right);
flags = readb(&mac_info->flags);
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)) {
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;
}
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);
}
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();
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;
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]));
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);
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);
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);
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;
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;
dram_mac = prueth->mem[PRUETH_MEM_DRAM1].va;
break;
default:
netdev_err(emac->ndev, "invalid port\n");
return -EINVAL;
}
memcpy_toio(dram_mac + PORT_MAC_ADDR, emac->mac_addr, 6);
dram_base = prueth->mem[PRUETH_MEM_DRAM1].va;
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]));
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);
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);
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);
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;
sharedramaddr = ICSS_LOCAL_SHARED_RAM;
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) {
pru_rproc_set_ctable(prueth->pru0, PRU_C28, sharedramaddr);
pru_rproc_set_ctable(prueth->pru1, PRU_C28, sharedramaddr);
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;
}