1066 lines
27 KiB
C
1066 lines
27 KiB
C
// 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;
|
|
}
|