// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
/* Copyright (C) 2015-2017 Netronome Systems, Inc. */
|
|
/* Authors: David Brunecz <david.brunecz@netronome.com>
|
* Jakub Kicinski <jakub.kicinski@netronome.com>
|
* Jason Mcmullan <jason.mcmullan@netronome.com>
|
*/
|
|
#include <linux/bitfield.h>
|
#include <linux/ethtool.h>
|
#include <linux/if_ether.h>
|
#include <linux/kernel.h>
|
#include <linux/module.h>
|
|
#include "nfp.h"
|
#include "nfp_nsp.h"
|
#include "nfp6000/nfp6000.h"
|
|
#define NSP_ETH_NBI_PORT_COUNT 24
|
#define NSP_ETH_MAX_COUNT (2 * NSP_ETH_NBI_PORT_COUNT)
|
#define NSP_ETH_TABLE_SIZE (NSP_ETH_MAX_COUNT * \
|
sizeof(union eth_table_entry))
|
|
#define NSP_ETH_PORT_LANES GENMASK_ULL(3, 0)
|
#define NSP_ETH_PORT_INDEX GENMASK_ULL(15, 8)
|
#define NSP_ETH_PORT_LABEL GENMASK_ULL(53, 48)
|
#define NSP_ETH_PORT_PHYLABEL GENMASK_ULL(59, 54)
|
#define NSP_ETH_PORT_FEC_SUPP_BASER BIT_ULL(60)
|
#define NSP_ETH_PORT_FEC_SUPP_RS BIT_ULL(61)
|
|
#define NSP_ETH_PORT_LANES_MASK cpu_to_le64(NSP_ETH_PORT_LANES)
|
|
#define NSP_ETH_STATE_CONFIGURED BIT_ULL(0)
|
#define NSP_ETH_STATE_ENABLED BIT_ULL(1)
|
#define NSP_ETH_STATE_TX_ENABLED BIT_ULL(2)
|
#define NSP_ETH_STATE_RX_ENABLED BIT_ULL(3)
|
#define NSP_ETH_STATE_RATE GENMASK_ULL(11, 8)
|
#define NSP_ETH_STATE_INTERFACE GENMASK_ULL(19, 12)
|
#define NSP_ETH_STATE_MEDIA GENMASK_ULL(21, 20)
|
#define NSP_ETH_STATE_OVRD_CHNG BIT_ULL(22)
|
#define NSP_ETH_STATE_ANEG GENMASK_ULL(25, 23)
|
#define NSP_ETH_STATE_FEC GENMASK_ULL(27, 26)
|
|
#define NSP_ETH_CTRL_CONFIGURED BIT_ULL(0)
|
#define NSP_ETH_CTRL_ENABLED BIT_ULL(1)
|
#define NSP_ETH_CTRL_TX_ENABLED BIT_ULL(2)
|
#define NSP_ETH_CTRL_RX_ENABLED BIT_ULL(3)
|
#define NSP_ETH_CTRL_SET_RATE BIT_ULL(4)
|
#define NSP_ETH_CTRL_SET_LANES BIT_ULL(5)
|
#define NSP_ETH_CTRL_SET_ANEG BIT_ULL(6)
|
#define NSP_ETH_CTRL_SET_FEC BIT_ULL(7)
|
|
enum nfp_eth_raw {
|
NSP_ETH_RAW_PORT = 0,
|
NSP_ETH_RAW_STATE,
|
NSP_ETH_RAW_MAC,
|
NSP_ETH_RAW_CONTROL,
|
|
NSP_ETH_NUM_RAW
|
};
|
|
enum nfp_eth_rate {
|
RATE_INVALID = 0,
|
RATE_10M,
|
RATE_100M,
|
RATE_1G,
|
RATE_10G,
|
RATE_25G,
|
};
|
|
union eth_table_entry {
|
struct {
|
__le64 port;
|
__le64 state;
|
u8 mac_addr[6];
|
u8 resv[2];
|
__le64 control;
|
};
|
__le64 raw[NSP_ETH_NUM_RAW];
|
};
|
|
static const struct {
|
enum nfp_eth_rate rate;
|
unsigned int speed;
|
} nsp_eth_rate_tbl[] = {
|
{ RATE_INVALID, 0, },
|
{ RATE_10M, SPEED_10, },
|
{ RATE_100M, SPEED_100, },
|
{ RATE_1G, SPEED_1000, },
|
{ RATE_10G, SPEED_10000, },
|
{ RATE_25G, SPEED_25000, },
|
};
|
|
static unsigned int nfp_eth_rate2speed(enum nfp_eth_rate rate)
|
{
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(nsp_eth_rate_tbl); i++)
|
if (nsp_eth_rate_tbl[i].rate == rate)
|
return nsp_eth_rate_tbl[i].speed;
|
|
return 0;
|
}
|
|
static unsigned int nfp_eth_speed2rate(unsigned int speed)
|
{
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(nsp_eth_rate_tbl); i++)
|
if (nsp_eth_rate_tbl[i].speed == speed)
|
return nsp_eth_rate_tbl[i].rate;
|
|
return RATE_INVALID;
|
}
|
|
static void nfp_eth_copy_mac_reverse(u8 *dst, const u8 *src)
|
{
|
int i;
|
|
for (i = 0; i < ETH_ALEN; i++)
|
dst[ETH_ALEN - i - 1] = src[i];
|
}
|
|
static void
|
nfp_eth_port_translate(struct nfp_nsp *nsp, const union eth_table_entry *src,
|
unsigned int index, struct nfp_eth_table_port *dst)
|
{
|
unsigned int rate;
|
unsigned int fec;
|
u64 port, state;
|
|
port = le64_to_cpu(src->port);
|
state = le64_to_cpu(src->state);
|
|
dst->eth_index = FIELD_GET(NSP_ETH_PORT_INDEX, port);
|
dst->index = index;
|
dst->nbi = index / NSP_ETH_NBI_PORT_COUNT;
|
dst->base = index % NSP_ETH_NBI_PORT_COUNT;
|
dst->lanes = FIELD_GET(NSP_ETH_PORT_LANES, port);
|
|
dst->enabled = FIELD_GET(NSP_ETH_STATE_ENABLED, state);
|
dst->tx_enabled = FIELD_GET(NSP_ETH_STATE_TX_ENABLED, state);
|
dst->rx_enabled = FIELD_GET(NSP_ETH_STATE_RX_ENABLED, state);
|
|
rate = nfp_eth_rate2speed(FIELD_GET(NSP_ETH_STATE_RATE, state));
|
dst->speed = dst->lanes * rate;
|
|
dst->interface = FIELD_GET(NSP_ETH_STATE_INTERFACE, state);
|
dst->media = FIELD_GET(NSP_ETH_STATE_MEDIA, state);
|
|
nfp_eth_copy_mac_reverse(dst->mac_addr, src->mac_addr);
|
|
dst->label_port = FIELD_GET(NSP_ETH_PORT_PHYLABEL, port);
|
dst->label_subport = FIELD_GET(NSP_ETH_PORT_LABEL, port);
|
|
if (nfp_nsp_get_abi_ver_minor(nsp) < 17)
|
return;
|
|
dst->override_changed = FIELD_GET(NSP_ETH_STATE_OVRD_CHNG, state);
|
dst->aneg = FIELD_GET(NSP_ETH_STATE_ANEG, state);
|
|
if (nfp_nsp_get_abi_ver_minor(nsp) < 22)
|
return;
|
|
fec = FIELD_GET(NSP_ETH_PORT_FEC_SUPP_BASER, port);
|
dst->fec_modes_supported |= fec << NFP_FEC_BASER_BIT;
|
fec = FIELD_GET(NSP_ETH_PORT_FEC_SUPP_RS, port);
|
dst->fec_modes_supported |= fec << NFP_FEC_REED_SOLOMON_BIT;
|
if (dst->fec_modes_supported)
|
dst->fec_modes_supported |= NFP_FEC_AUTO | NFP_FEC_DISABLED;
|
|
dst->fec = 1 << FIELD_GET(NSP_ETH_STATE_FEC, state);
|
}
|
|
static void
|
nfp_eth_calc_port_geometry(struct nfp_cpp *cpp, struct nfp_eth_table *table)
|
{
|
unsigned int i, j;
|
|
for (i = 0; i < table->count; i++) {
|
table->max_index = max(table->max_index, table->ports[i].index);
|
|
for (j = 0; j < table->count; j++) {
|
if (table->ports[i].label_port !=
|
table->ports[j].label_port)
|
continue;
|
table->ports[i].port_lanes += table->ports[j].lanes;
|
|
if (i == j)
|
continue;
|
if (table->ports[i].label_subport ==
|
table->ports[j].label_subport)
|
nfp_warn(cpp,
|
"Port %d subport %d is a duplicate\n",
|
table->ports[i].label_port,
|
table->ports[i].label_subport);
|
|
table->ports[i].is_split = true;
|
}
|
}
|
}
|
|
static void
|
nfp_eth_calc_port_type(struct nfp_cpp *cpp, struct nfp_eth_table_port *entry)
|
{
|
if (entry->interface == NFP_INTERFACE_NONE) {
|
entry->port_type = PORT_NONE;
|
return;
|
} else if (entry->interface == NFP_INTERFACE_RJ45) {
|
entry->port_type = PORT_TP;
|
return;
|
}
|
|
if (entry->media == NFP_MEDIA_FIBRE)
|
entry->port_type = PORT_FIBRE;
|
else
|
entry->port_type = PORT_DA;
|
}
|
|
/**
|
* nfp_eth_read_ports() - retrieve port information
|
* @cpp: NFP CPP handle
|
*
|
* Read the port information from the device. Returned structure should
|
* be freed with kfree() once no longer needed.
|
*
|
* Return: populated ETH table or NULL on error.
|
*/
|
struct nfp_eth_table *nfp_eth_read_ports(struct nfp_cpp *cpp)
|
{
|
struct nfp_eth_table *ret;
|
struct nfp_nsp *nsp;
|
|
nsp = nfp_nsp_open(cpp);
|
if (IS_ERR(nsp))
|
return NULL;
|
|
ret = __nfp_eth_read_ports(cpp, nsp);
|
nfp_nsp_close(nsp);
|
|
return ret;
|
}
|
|
struct nfp_eth_table *
|
__nfp_eth_read_ports(struct nfp_cpp *cpp, struct nfp_nsp *nsp)
|
{
|
union eth_table_entry *entries;
|
struct nfp_eth_table *table;
|
int i, j, ret, cnt = 0;
|
|
entries = kzalloc(NSP_ETH_TABLE_SIZE, GFP_KERNEL);
|
if (!entries)
|
return NULL;
|
|
ret = nfp_nsp_read_eth_table(nsp, entries, NSP_ETH_TABLE_SIZE);
|
if (ret < 0) {
|
nfp_err(cpp, "reading port table failed %d\n", ret);
|
goto err;
|
}
|
|
for (i = 0; i < NSP_ETH_MAX_COUNT; i++)
|
if (entries[i].port & NSP_ETH_PORT_LANES_MASK)
|
cnt++;
|
|
/* Some versions of flash will give us 0 instead of port count.
|
* For those that give a port count, verify it against the value
|
* calculated above.
|
*/
|
if (ret && ret != cnt) {
|
nfp_err(cpp, "table entry count reported (%d) does not match entries present (%d)\n",
|
ret, cnt);
|
goto err;
|
}
|
|
table = kzalloc(struct_size(table, ports, cnt), GFP_KERNEL);
|
if (!table)
|
goto err;
|
|
table->count = cnt;
|
for (i = 0, j = 0; i < NSP_ETH_MAX_COUNT; i++)
|
if (entries[i].port & NSP_ETH_PORT_LANES_MASK)
|
nfp_eth_port_translate(nsp, &entries[i], i,
|
&table->ports[j++]);
|
|
nfp_eth_calc_port_geometry(cpp, table);
|
for (i = 0; i < table->count; i++)
|
nfp_eth_calc_port_type(cpp, &table->ports[i]);
|
|
kfree(entries);
|
|
return table;
|
|
err:
|
kfree(entries);
|
return NULL;
|
}
|
|
struct nfp_nsp *nfp_eth_config_start(struct nfp_cpp *cpp, unsigned int idx)
|
{
|
union eth_table_entry *entries;
|
struct nfp_nsp *nsp;
|
int ret;
|
|
entries = kzalloc(NSP_ETH_TABLE_SIZE, GFP_KERNEL);
|
if (!entries)
|
return ERR_PTR(-ENOMEM);
|
|
nsp = nfp_nsp_open(cpp);
|
if (IS_ERR(nsp)) {
|
kfree(entries);
|
return nsp;
|
}
|
|
ret = nfp_nsp_read_eth_table(nsp, entries, NSP_ETH_TABLE_SIZE);
|
if (ret < 0) {
|
nfp_err(cpp, "reading port table failed %d\n", ret);
|
goto err;
|
}
|
|
if (!(entries[idx].port & NSP_ETH_PORT_LANES_MASK)) {
|
nfp_warn(cpp, "trying to set port state on disabled port %d\n",
|
idx);
|
goto err;
|
}
|
|
nfp_nsp_config_set_state(nsp, entries, idx);
|
return nsp;
|
|
err:
|
nfp_nsp_close(nsp);
|
kfree(entries);
|
return ERR_PTR(-EIO);
|
}
|
|
void nfp_eth_config_cleanup_end(struct nfp_nsp *nsp)
|
{
|
union eth_table_entry *entries = nfp_nsp_config_entries(nsp);
|
|
nfp_nsp_config_set_modified(nsp, false);
|
nfp_nsp_config_clear_state(nsp);
|
nfp_nsp_close(nsp);
|
kfree(entries);
|
}
|
|
/**
|
* nfp_eth_config_commit_end() - perform recorded configuration changes
|
* @nsp: NFP NSP handle returned from nfp_eth_config_start()
|
*
|
* Perform the configuration which was requested with __nfp_eth_set_*()
|
* helpers and recorded in @nsp state. If device was already configured
|
* as requested or no __nfp_eth_set_*() operations were made no NSP command
|
* will be performed.
|
*
|
* Return:
|
* 0 - configuration successful;
|
* 1 - no changes were needed;
|
* -ERRNO - configuration failed.
|
*/
|
int nfp_eth_config_commit_end(struct nfp_nsp *nsp)
|
{
|
union eth_table_entry *entries = nfp_nsp_config_entries(nsp);
|
int ret = 1;
|
|
if (nfp_nsp_config_modified(nsp)) {
|
ret = nfp_nsp_write_eth_table(nsp, entries, NSP_ETH_TABLE_SIZE);
|
ret = ret < 0 ? ret : 0;
|
}
|
|
nfp_eth_config_cleanup_end(nsp);
|
|
return ret;
|
}
|
|
/**
|
* nfp_eth_set_mod_enable() - set PHY module enable control bit
|
* @cpp: NFP CPP handle
|
* @idx: NFP chip-wide port index
|
* @enable: Desired state
|
*
|
* Enable or disable PHY module (this usually means setting the TX lanes
|
* disable bits).
|
*
|
* Return:
|
* 0 - configuration successful;
|
* 1 - no changes were needed;
|
* -ERRNO - configuration failed.
|
*/
|
int nfp_eth_set_mod_enable(struct nfp_cpp *cpp, unsigned int idx, bool enable)
|
{
|
union eth_table_entry *entries;
|
struct nfp_nsp *nsp;
|
u64 reg;
|
|
nsp = nfp_eth_config_start(cpp, idx);
|
if (IS_ERR(nsp))
|
return PTR_ERR(nsp);
|
|
entries = nfp_nsp_config_entries(nsp);
|
|
/* Check if we are already in requested state */
|
reg = le64_to_cpu(entries[idx].state);
|
if (enable != FIELD_GET(NSP_ETH_CTRL_ENABLED, reg)) {
|
reg = le64_to_cpu(entries[idx].control);
|
reg &= ~NSP_ETH_CTRL_ENABLED;
|
reg |= FIELD_PREP(NSP_ETH_CTRL_ENABLED, enable);
|
entries[idx].control = cpu_to_le64(reg);
|
|
nfp_nsp_config_set_modified(nsp, true);
|
}
|
|
return nfp_eth_config_commit_end(nsp);
|
}
|
|
/**
|
* nfp_eth_set_configured() - set PHY module configured control bit
|
* @cpp: NFP CPP handle
|
* @idx: NFP chip-wide port index
|
* @configed: Desired state
|
*
|
* Set the ifup/ifdown state on the PHY.
|
*
|
* Return:
|
* 0 - configuration successful;
|
* 1 - no changes were needed;
|
* -ERRNO - configuration failed.
|
*/
|
int nfp_eth_set_configured(struct nfp_cpp *cpp, unsigned int idx, bool configed)
|
{
|
union eth_table_entry *entries;
|
struct nfp_nsp *nsp;
|
u64 reg;
|
|
nsp = nfp_eth_config_start(cpp, idx);
|
if (IS_ERR(nsp))
|
return PTR_ERR(nsp);
|
|
/* Older ABI versions did support this feature, however this has only
|
* been reliable since ABI 20.
|
*/
|
if (nfp_nsp_get_abi_ver_minor(nsp) < 20) {
|
nfp_eth_config_cleanup_end(nsp);
|
return -EOPNOTSUPP;
|
}
|
|
entries = nfp_nsp_config_entries(nsp);
|
|
/* Check if we are already in requested state */
|
reg = le64_to_cpu(entries[idx].state);
|
if (configed != FIELD_GET(NSP_ETH_STATE_CONFIGURED, reg)) {
|
reg = le64_to_cpu(entries[idx].control);
|
reg &= ~NSP_ETH_CTRL_CONFIGURED;
|
reg |= FIELD_PREP(NSP_ETH_CTRL_CONFIGURED, configed);
|
entries[idx].control = cpu_to_le64(reg);
|
|
nfp_nsp_config_set_modified(nsp, true);
|
}
|
|
return nfp_eth_config_commit_end(nsp);
|
}
|
|
static int
|
nfp_eth_set_bit_config(struct nfp_nsp *nsp, unsigned int raw_idx,
|
const u64 mask, const unsigned int shift,
|
unsigned int val, const u64 ctrl_bit)
|
{
|
union eth_table_entry *entries = nfp_nsp_config_entries(nsp);
|
unsigned int idx = nfp_nsp_config_idx(nsp);
|
u64 reg;
|
|
/* Note: set features were added in ABI 0.14 but the error
|
* codes were initially not populated correctly.
|
*/
|
if (nfp_nsp_get_abi_ver_minor(nsp) < 17) {
|
nfp_err(nfp_nsp_cpp(nsp),
|
"set operations not supported, please update flash\n");
|
return -EOPNOTSUPP;
|
}
|
|
/* Check if we are already in requested state */
|
reg = le64_to_cpu(entries[idx].raw[raw_idx]);
|
if (val == (reg & mask) >> shift)
|
return 0;
|
|
reg &= ~mask;
|
reg |= (val << shift) & mask;
|
entries[idx].raw[raw_idx] = cpu_to_le64(reg);
|
|
entries[idx].control |= cpu_to_le64(ctrl_bit);
|
|
nfp_nsp_config_set_modified(nsp, true);
|
|
return 0;
|
}
|
|
#define NFP_ETH_SET_BIT_CONFIG(nsp, raw_idx, mask, val, ctrl_bit) \
|
({ \
|
__BF_FIELD_CHECK(mask, 0ULL, val, "NFP_ETH_SET_BIT_CONFIG: "); \
|
nfp_eth_set_bit_config(nsp, raw_idx, mask, __bf_shf(mask), \
|
val, ctrl_bit); \
|
})
|
|
/**
|
* __nfp_eth_set_aneg() - set PHY autonegotiation control bit
|
* @nsp: NFP NSP handle returned from nfp_eth_config_start()
|
* @mode: Desired autonegotiation mode
|
*
|
* Allow/disallow PHY module to advertise/perform autonegotiation.
|
* Will write to hwinfo overrides in the flash (persistent config).
|
*
|
* Return: 0 or -ERRNO.
|
*/
|
int __nfp_eth_set_aneg(struct nfp_nsp *nsp, enum nfp_eth_aneg mode)
|
{
|
return NFP_ETH_SET_BIT_CONFIG(nsp, NSP_ETH_RAW_STATE,
|
NSP_ETH_STATE_ANEG, mode,
|
NSP_ETH_CTRL_SET_ANEG);
|
}
|
|
/**
|
* __nfp_eth_set_fec() - set PHY forward error correction control bit
|
* @nsp: NFP NSP handle returned from nfp_eth_config_start()
|
* @mode: Desired fec mode
|
*
|
* Set the PHY module forward error correction mode.
|
* Will write to hwinfo overrides in the flash (persistent config).
|
*
|
* Return: 0 or -ERRNO.
|
*/
|
static int __nfp_eth_set_fec(struct nfp_nsp *nsp, enum nfp_eth_fec mode)
|
{
|
return NFP_ETH_SET_BIT_CONFIG(nsp, NSP_ETH_RAW_STATE,
|
NSP_ETH_STATE_FEC, mode,
|
NSP_ETH_CTRL_SET_FEC);
|
}
|
|
/**
|
* nfp_eth_set_fec() - set PHY forward error correction control mode
|
* @cpp: NFP CPP handle
|
* @idx: NFP chip-wide port index
|
* @mode: Desired fec mode
|
*
|
* Return:
|
* 0 - configuration successful;
|
* 1 - no changes were needed;
|
* -ERRNO - configuration failed.
|
*/
|
int
|
nfp_eth_set_fec(struct nfp_cpp *cpp, unsigned int idx, enum nfp_eth_fec mode)
|
{
|
struct nfp_nsp *nsp;
|
int err;
|
|
nsp = nfp_eth_config_start(cpp, idx);
|
if (IS_ERR(nsp))
|
return PTR_ERR(nsp);
|
|
err = __nfp_eth_set_fec(nsp, mode);
|
if (err) {
|
nfp_eth_config_cleanup_end(nsp);
|
return err;
|
}
|
|
return nfp_eth_config_commit_end(nsp);
|
}
|
|
/**
|
* __nfp_eth_set_speed() - set interface speed/rate
|
* @nsp: NFP NSP handle returned from nfp_eth_config_start()
|
* @speed: Desired speed (per lane)
|
*
|
* Set lane speed. Provided @speed value should be subport speed divided
|
* by number of lanes this subport is spanning (i.e. 10000 for 40G, 25000 for
|
* 50G, etc.)
|
* Will write to hwinfo overrides in the flash (persistent config).
|
*
|
* Return: 0 or -ERRNO.
|
*/
|
int __nfp_eth_set_speed(struct nfp_nsp *nsp, unsigned int speed)
|
{
|
enum nfp_eth_rate rate;
|
|
rate = nfp_eth_speed2rate(speed);
|
if (rate == RATE_INVALID) {
|
nfp_warn(nfp_nsp_cpp(nsp),
|
"could not find matching lane rate for speed %u\n",
|
speed);
|
return -EINVAL;
|
}
|
|
return NFP_ETH_SET_BIT_CONFIG(nsp, NSP_ETH_RAW_STATE,
|
NSP_ETH_STATE_RATE, rate,
|
NSP_ETH_CTRL_SET_RATE);
|
}
|
|
/**
|
* __nfp_eth_set_split() - set interface lane split
|
* @nsp: NFP NSP handle returned from nfp_eth_config_start()
|
* @lanes: Desired lanes per port
|
*
|
* Set number of lanes in the port.
|
* Will write to hwinfo overrides in the flash (persistent config).
|
*
|
* Return: 0 or -ERRNO.
|
*/
|
int __nfp_eth_set_split(struct nfp_nsp *nsp, unsigned int lanes)
|
{
|
return NFP_ETH_SET_BIT_CONFIG(nsp, NSP_ETH_RAW_PORT, NSP_ETH_PORT_LANES,
|
lanes, NSP_ETH_CTRL_SET_LANES);
|
}
|