#include <asm/unaligned.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/overflow.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#define NFP_SUBSYS "nfp_nsp"
#include "nfp.h"
#include "nfp_cpp.h"
#include "nfp_nsp.h"
#define NFP_NSP_TIMEOUT_DEFAULT 30
#define NFP_NSP_TIMEOUT_BOOT 30
#define NSP_STATUS 0x00
#define NSP_STATUS_MAGIC GENMASK_ULL(63, 48)
#define NSP_STATUS_MAJOR GENMASK_ULL(47, 44)
#define NSP_STATUS_MINOR GENMASK_ULL(43, 32)
#define NSP_STATUS_CODE GENMASK_ULL(31, 16)
#define NSP_STATUS_RESULT GENMASK_ULL(15, 8)
#define NSP_STATUS_BUSY BIT_ULL(0)
#define NSP_COMMAND 0x08
#define NSP_COMMAND_OPTION GENMASK_ULL(63, 32)
#define NSP_COMMAND_CODE GENMASK_ULL(31, 16)
#define NSP_COMMAND_DMA_BUF BIT_ULL(1)
#define NSP_COMMAND_START BIT_ULL(0)
#define NSP_BUFFER 0x10
#define NSP_BUFFER_CPP GENMASK_ULL(63, 40)
#define NSP_BUFFER_ADDRESS GENMASK_ULL(39, 0)
#define NSP_DFLT_BUFFER 0x18
#define NSP_DFLT_BUFFER_CPP GENMASK_ULL(63, 40)
#define NSP_DFLT_BUFFER_ADDRESS GENMASK_ULL(39, 0)
#define NSP_DFLT_BUFFER_CONFIG 0x20
#define NSP_DFLT_BUFFER_DMA_CHUNK_ORDER GENMASK_ULL(63, 58)
#define NSP_DFLT_BUFFER_SIZE_4KB GENMASK_ULL(15, 8)
#define NSP_DFLT_BUFFER_SIZE_MB GENMASK_ULL(7, 0)
#define NFP_CAP_CMD_DMA_SG 0x28
#define NSP_MAGIC 0xab10
#define NSP_MAJOR 0
#define NSP_MINOR 8
#define NSP_CODE_MAJOR GENMASK(15, 12)
#define NSP_CODE_MINOR GENMASK(11, 0)
#define NFP_FW_LOAD_RET_MAJOR GENMASK(15, 8)
#define NFP_FW_LOAD_RET_MINOR GENMASK(23, 16)
#define NFP_HWINFO_LOOKUP_SIZE GENMASK(11, 0)
#define NFP_VERSIONS_SIZE GENMASK(11, 0)
#define NFP_VERSIONS_CNT_OFF 0
#define NFP_VERSIONS_BSP_OFF 2
#define NFP_VERSIONS_CPLD_OFF 6
#define NFP_VERSIONS_APP_OFF 10
#define NFP_VERSIONS_BUNDLE_OFF 14
#define NFP_VERSIONS_UNDI_OFF 18
#define NFP_VERSIONS_NCSI_OFF 22
#define NFP_VERSIONS_CFGR_OFF 26
#define NSP_SFF_EEPROM_BLOCK_LEN 8
enum nfp_nsp_cmd {
SPCODE_NOOP = 0,
SPCODE_SOFT_RESET = 1,
SPCODE_FW_DEFAULT = 2,
SPCODE_PHY_INIT = 3,
SPCODE_MAC_INIT = 4,
SPCODE_PHY_RXADAPT = 5,
SPCODE_FW_LOAD = 6,
SPCODE_ETH_RESCAN = 7,
SPCODE_ETH_CONTROL = 8,
SPCODE_NSP_WRITE_FLASH = 11,
SPCODE_NSP_SENSORS = 12,
SPCODE_NSP_IDENTIFY = 13,
SPCODE_FW_STORED = 16,
SPCODE_HWINFO_LOOKUP = 17,
SPCODE_HWINFO_SET = 18,
SPCODE_FW_LOADED = 19,
SPCODE_VERSIONS = 21,
SPCODE_READ_SFF_EEPROM = 22,
SPCODE_READ_MEDIA = 23,
};
struct nfp_nsp_dma_buf {
__le32 chunk_cnt;
__le32 reserved[3];
struct {
__le32 size;
__le32 reserved;
__le64 addr;
} descs[];
};
static const struct {
int code;
const char *msg;
} nsp_errors[] = {
{ 6010, "could not map to phy for port" },
{ 6011, "not an allowed rate/lanes for port" },
{ 6012, "not an allowed rate/lanes for port" },
{ 6013, "high/low error, change other port first" },
{ 6014, "config not found in flash" },
};
struct nfp_nsp {
struct nfp_cpp *cpp;
struct nfp_resource *res;
struct {
u16 major;
u16 minor;
} ver;
bool modified;
unsigned int idx;
void *entries;
};
struct nfp_nsp_command_arg {
u16 code;
bool dma;
unsigned int timeout_sec;
u32 option;
u64 buf;
void (*error_cb)(struct nfp_nsp *state, u32 ret_val);
bool error_quiet;
};
struct nfp_nsp_command_buf_arg {
struct nfp_nsp_command_arg arg;
const void *in_buf;
unsigned int in_size;
void *out_buf;
unsigned int out_size;
};
struct nfp_cpp *nfp_nsp_cpp(struct nfp_nsp *state)
{
return state->cpp;
}
bool nfp_nsp_config_modified(struct nfp_nsp *state)
{
return state->modified;
}
void nfp_nsp_config_set_modified(struct nfp_nsp *state, bool modified)
{
state->modified = modified;
}
void *nfp_nsp_config_entries(struct nfp_nsp *state)
{
return state->entries;
}
unsigned int nfp_nsp_config_idx(struct nfp_nsp *state)
{
return state->idx;
}
void
nfp_nsp_config_set_state(struct nfp_nsp *state, void *entries, unsigned int idx)
{
state->entries = entries;
state->idx = idx;
}
void nfp_nsp_config_clear_state(struct nfp_nsp *state)
{
state->entries = NULL;
state->idx = 0;
}
static void nfp_nsp_print_extended_error(struct nfp_nsp *state, u32 ret_val)
{
int i;
if (!ret_val)
return;
for (i = 0; i < ARRAY_SIZE(nsp_errors); i++)
if (ret_val == nsp_errors[i].code)
nfp_err(state->cpp, "err msg: %s\n", nsp_errors[i].msg);
}
static int nfp_nsp_check(struct nfp_nsp *state)
{
struct nfp_cpp *cpp = state->cpp;
u64 nsp_status, reg;
u32 nsp_cpp;
int err;
nsp_cpp = nfp_resource_cpp_id(state->res);
nsp_status = nfp_resource_address(state->res) + NSP_STATUS;
err = nfp_cpp_readq(cpp, nsp_cpp, nsp_status, ®);
if (err < 0)
return err;
if (FIELD_GET(NSP_STATUS_MAGIC, reg) != NSP_MAGIC) {
nfp_err(cpp, "Cannot detect NFP Service Processor\n");
return -ENODEV;
}
state->ver.major = FIELD_GET(NSP_STATUS_MAJOR, reg);
state->ver.minor = FIELD_GET(NSP_STATUS_MINOR, reg);
if (state->ver.major != NSP_MAJOR) {
nfp_err(cpp, "Unsupported ABI %hu.%hu\n",
state->ver.major, state->ver.minor);
return -EINVAL;
}
if (state->ver.minor < NSP_MINOR) {
nfp_err(cpp, "ABI too old to support NIC operation (%u.%hu < %u.%u), please update the management FW on the flash\n",
NSP_MAJOR, state->ver.minor, NSP_MAJOR, NSP_MINOR);
return -EINVAL;
}
if (reg & NSP_STATUS_BUSY) {
nfp_err(cpp, "Service processor busy!\n");
return -EBUSY;
}
return 0;
}
struct nfp_nsp *nfp_nsp_open(struct nfp_cpp *cpp)
{
struct nfp_resource *res;
struct nfp_nsp *state;
int err;
res = nfp_resource_acquire(cpp, NFP_RESOURCE_NSP);
if (IS_ERR(res))
return (void *)res;
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state) {
nfp_resource_release(res);
return ERR_PTR(-ENOMEM);
}
state->cpp = cpp;
state->res = res;
err = nfp_nsp_check(state);
if (err) {
nfp_nsp_close(state);
return ERR_PTR(err);
}
return state;
}
void nfp_nsp_close(struct nfp_nsp *state)
{
nfp_resource_release(state->res);
kfree(state);
}
u16 nfp_nsp_get_abi_ver_major(struct nfp_nsp *state)
{
return state->ver.major;
}
u16 nfp_nsp_get_abi_ver_minor(struct nfp_nsp *state)
{
return state->ver.minor;
}
static int
nfp_nsp_wait_reg(struct nfp_cpp *cpp, u64 *reg, u32 nsp_cpp, u64 addr,
u64 mask, u64 val, u32 timeout_sec)
{
const unsigned long wait_until = jiffies + timeout_sec * HZ;
int err;
for (;;) {
const unsigned long start_time = jiffies;
err = nfp_cpp_readq(cpp, nsp_cpp, addr, reg);
if (err < 0)
return err;
if ((*reg & mask) == val)
return 0;
msleep(25);
if (time_after(start_time, wait_until))
return -ETIMEDOUT;
}
}
static int
__nfp_nsp_command(struct nfp_nsp *state, const struct nfp_nsp_command_arg *arg)
{
u64 reg, ret_val, nsp_base, nsp_buffer, nsp_status, nsp_command;
struct nfp_cpp *cpp = state->cpp;
u32 nsp_cpp;
int err;
nsp_cpp = nfp_resource_cpp_id(state->res);
nsp_base = nfp_resource_address(state->res);
nsp_status = nsp_base + NSP_STATUS;
nsp_command = nsp_base + NSP_COMMAND;
nsp_buffer = nsp_base + NSP_BUFFER;
err = nfp_nsp_check(state);
if (err)
return err;
err = nfp_cpp_writeq(cpp, nsp_cpp, nsp_buffer, arg->buf);
if (err < 0)
return err;
err = nfp_cpp_writeq(cpp, nsp_cpp, nsp_command,
FIELD_PREP(NSP_COMMAND_OPTION, arg->option) |
FIELD_PREP(NSP_COMMAND_CODE, arg->code) |
FIELD_PREP(NSP_COMMAND_DMA_BUF, arg->dma) |
FIELD_PREP(NSP_COMMAND_START, 1));
if (err < 0)
return err;
err = nfp_nsp_wait_reg(cpp, ®, nsp_cpp, nsp_command,
NSP_COMMAND_START, 0, NFP_NSP_TIMEOUT_DEFAULT);
if (err) {
nfp_err(cpp, "Error %d waiting for code 0x%04x to start\n",
err, arg->code);
return err;
}
err = nfp_nsp_wait_reg(cpp, ®, nsp_cpp, nsp_status, NSP_STATUS_BUSY,
0, arg->timeout_sec ?: NFP_NSP_TIMEOUT_DEFAULT);
if (err) {
nfp_err(cpp, "Error %d waiting for code 0x%04x to complete\n",
err, arg->code);
return err;
}
err = nfp_cpp_readq(cpp, nsp_cpp, nsp_command, &ret_val);
if (err < 0)
return err;
ret_val = FIELD_GET(NSP_COMMAND_OPTION, ret_val);
err = FIELD_GET(NSP_STATUS_RESULT, reg);
if (err) {
if (!arg->error_quiet)
nfp_warn(cpp, "Result (error) code set: %d (%d) command: %d\n",
-err, (int)ret_val, arg->code);
if (arg->error_cb)
arg->error_cb(state, ret_val);
else
nfp_nsp_print_extended_error(state, ret_val);
return -err;
}
return ret_val;
}
static int nfp_nsp_command(struct nfp_nsp *state, u16 code)
{
const struct nfp_nsp_command_arg arg = {
.code = code,
};
return __nfp_nsp_command(state, &arg);
}
static int
nfp_nsp_command_buf_def(struct nfp_nsp *nsp,
struct nfp_nsp_command_buf_arg *arg)
{
struct nfp_cpp *cpp = nsp->cpp;
u64 reg, cpp_buf;
int err, ret;
u32 cpp_id;
err = nfp_cpp_readq(cpp, nfp_resource_cpp_id(nsp->res),
nfp_resource_address(nsp->res) +
NSP_DFLT_BUFFER,
®);
if (err < 0)
return err;
cpp_id = FIELD_GET(NSP_DFLT_BUFFER_CPP, reg) << 8;
cpp_buf = FIELD_GET(NSP_DFLT_BUFFER_ADDRESS, reg);
if (arg->in_buf && arg->in_size) {
err = nfp_cpp_write(cpp, cpp_id, cpp_buf,
arg->in_buf, arg->in_size);
if (err < 0)
return err;
}
if (arg->out_buf && arg->out_size && arg->out_size > arg->in_size) {
err = nfp_cpp_write(cpp, cpp_id, cpp_buf + arg->in_size,
arg->out_buf, arg->out_size - arg->in_size);
if (err < 0)
return err;
}
if (!FIELD_FIT(NSP_BUFFER_CPP, cpp_id >> 8) ||
!FIELD_FIT(NSP_BUFFER_ADDRESS, cpp_buf)) {
nfp_err(cpp, "Buffer out of reach %08x %016llx\n",
cpp_id, cpp_buf);
return -EINVAL;
}
arg->arg.buf = FIELD_PREP(NSP_BUFFER_CPP, cpp_id >> 8) |
FIELD_PREP(NSP_BUFFER_ADDRESS, cpp_buf);
ret = __nfp_nsp_command(nsp, &arg->arg);
if (ret < 0)
return ret;
if (arg->out_buf && arg->out_size) {
err = nfp_cpp_read(cpp, cpp_id, cpp_buf,
arg->out_buf, arg->out_size);
if (err < 0)
return err;
}
return ret;
}
static int
nfp_nsp_command_buf_dma_sg(struct nfp_nsp *nsp,
struct nfp_nsp_command_buf_arg *arg,
unsigned int max_size, unsigned int chunk_order,
unsigned int dma_order)
{
struct nfp_cpp *cpp = nsp->cpp;
struct nfp_nsp_dma_buf *desc;
struct {
dma_addr_t dma_addr;
unsigned long len;
void *chunk;
} *chunks;
size_t chunk_size, dma_size;
dma_addr_t dma_desc;
struct device *dev;
unsigned long off;
int i, ret, nseg;
size_t desc_sz;
chunk_size = BIT_ULL(chunk_order);
dma_size = BIT_ULL(dma_order);
nseg = DIV_ROUND_UP(max_size, chunk_size);
chunks = kcalloc(nseg, sizeof(*chunks), GFP_KERNEL);
if (!chunks)
return -ENOMEM;
off = 0;
ret = -ENOMEM;
for (i = 0; i < nseg; i++) {
unsigned long coff;
chunks[i].chunk = kmalloc(chunk_size,
GFP_KERNEL | __GFP_NOWARN);
if (!chunks[i].chunk)
goto exit_free_prev;
chunks[i].len = min_t(u64, chunk_size, max_size - off);
coff = 0;
if (arg->in_size > off) {
coff = min_t(u64, arg->in_size - off, chunk_size);
memcpy(chunks[i].chunk, arg->in_buf + off, coff);
}
memset(chunks[i].chunk + coff, 0, chunk_size - coff);
off += chunks[i].len;
}
dev = nfp_cpp_device(cpp)->parent;
for (i = 0; i < nseg; i++) {
dma_addr_t addr;
addr = dma_map_single(dev, chunks[i].chunk, chunks[i].len,
DMA_BIDIRECTIONAL);
chunks[i].dma_addr = addr;
ret = dma_mapping_error(dev, addr);
if (ret)
goto exit_unmap_prev;
if (WARN_ONCE(round_down(addr, dma_size) !=
round_down(addr + chunks[i].len - 1, dma_size),
"unaligned DMA address: %pad %lu %zd\n",
&addr, chunks[i].len, dma_size)) {
ret = -EFAULT;
i++;
goto exit_unmap_prev;
}
}
desc_sz = struct_size(desc, descs, nseg);
desc = kmalloc(desc_sz, GFP_KERNEL);
if (!desc) {
ret = -ENOMEM;
goto exit_unmap_all;
}
desc->chunk_cnt = cpu_to_le32(nseg);
for (i = 0; i < nseg; i++) {
desc->descs[i].size = cpu_to_le32(chunks[i].len);
desc->descs[i].addr = cpu_to_le64(chunks[i].dma_addr);
}
dma_desc = dma_map_single(dev, desc, desc_sz, DMA_TO_DEVICE);
ret = dma_mapping_error(dev, dma_desc);
if (ret)
goto exit_free_desc;
arg->arg.dma = true;
arg->arg.buf = dma_desc;
ret = __nfp_nsp_command(nsp, &arg->arg);
if (ret < 0)
goto exit_unmap_desc;
i = 0;
off = 0;
while (off < arg->out_size) {
unsigned int len;
len = min_t(u64, chunks[i].len, arg->out_size - off);
memcpy(arg->out_buf + off, chunks[i].chunk, len);
off += len;
i++;
}
exit_unmap_desc:
dma_unmap_single(dev, dma_desc, desc_sz, DMA_TO_DEVICE);
exit_free_desc:
kfree(desc);
exit_unmap_all:
i = nseg;
exit_unmap_prev:
while (--i >= 0)
dma_unmap_single(dev, chunks[i].dma_addr, chunks[i].len,
DMA_BIDIRECTIONAL);
i = nseg;
exit_free_prev:
while (--i >= 0)
kfree(chunks[i].chunk);
kfree(chunks);
if (ret < 0)
nfp_err(cpp, "NSP: SG DMA failed for command 0x%04x: %d (sz:%d cord:%d)\n",
arg->arg.code, ret, max_size, chunk_order);
return ret;
}
static int
nfp_nsp_command_buf_dma(struct nfp_nsp *nsp,
struct nfp_nsp_command_buf_arg *arg,
unsigned int max_size, unsigned int dma_order)
{
unsigned int chunk_order, buf_order;
struct nfp_cpp *cpp = nsp->cpp;
bool sg_ok;
u64 reg;
int err;
buf_order = order_base_2(roundup_pow_of_two(max_size));
err = nfp_cpp_readq(cpp, nfp_resource_cpp_id(nsp->res),
nfp_resource_address(nsp->res) + NFP_CAP_CMD_DMA_SG,
®);
if (err < 0)
return err;
sg_ok = reg & BIT_ULL(arg->arg.code - 1);
if (!sg_ok) {
if (buf_order > dma_order) {
nfp_err(cpp, "NSP: can't service non-SG DMA for command 0x%04x\n",
arg->arg.code);
return -ENOMEM;
}
chunk_order = buf_order;
} else {
chunk_order = min_t(unsigned int, dma_order, PAGE_SHIFT);
}
return nfp_nsp_command_buf_dma_sg(nsp, arg, max_size, chunk_order,
dma_order);
}
static int
nfp_nsp_command_buf(struct nfp_nsp *nsp, struct nfp_nsp_command_buf_arg *arg)
{
unsigned int dma_order, def_size, max_size;
struct nfp_cpp *cpp = nsp->cpp;
u64 reg;
int err;
if (nsp->ver.minor < 13) {
nfp_err(cpp, "NSP: Code 0x%04x with buffer not supported (ABI %hu.%hu)\n",
arg->arg.code, nsp->ver.major, nsp->ver.minor);
return -EOPNOTSUPP;
}
err = nfp_cpp_readq(cpp, nfp_resource_cpp_id(nsp->res),
nfp_resource_address(nsp->res) +
NSP_DFLT_BUFFER_CONFIG,
®);
if (err < 0)
return err;
if (arg->out_buf && arg->out_size && arg->out_size > arg->in_size)
memset(arg->out_buf, 0, arg->out_size - arg->in_size);
max_size = max(arg->in_size, arg->out_size);
def_size = FIELD_GET(NSP_DFLT_BUFFER_SIZE_MB, reg) * SZ_1M +
FIELD_GET(NSP_DFLT_BUFFER_SIZE_4KB, reg) * SZ_4K;
dma_order = FIELD_GET(NSP_DFLT_BUFFER_DMA_CHUNK_ORDER, reg);
if (def_size >= max_size) {
return nfp_nsp_command_buf_def(nsp, arg);
} else if (!dma_order) {
nfp_err(cpp, "NSP: default buffer too small for command 0x%04x (%u < %u)\n",
arg->arg.code, def_size, max_size);
return -EINVAL;
}
return nfp_nsp_command_buf_dma(nsp, arg, max_size, dma_order);
}
int nfp_nsp_wait(struct nfp_nsp *state)
{
const unsigned long wait_until = jiffies + NFP_NSP_TIMEOUT_BOOT * HZ;
int err;
nfp_dbg(state->cpp, "Waiting for NSP to respond (%u sec max).\n",
NFP_NSP_TIMEOUT_BOOT);
for (;;) {
const unsigned long start_time = jiffies;
err = nfp_nsp_command(state, SPCODE_NOOP);
if (err != -EAGAIN)
break;
if (msleep_interruptible(25)) {
err = -ERESTARTSYS;
break;
}
if (time_after(start_time, wait_until)) {
err = -ETIMEDOUT;
break;
}
}
if (err)
nfp_err(state->cpp, "NSP failed to respond %d\n", err);
return err;
}
int nfp_nsp_device_soft_reset(struct nfp_nsp *state)
{
return nfp_nsp_command(state, SPCODE_SOFT_RESET);
}
int nfp_nsp_mac_reinit(struct nfp_nsp *state)
{
return nfp_nsp_command(state, SPCODE_MAC_INIT);
}
static void nfp_nsp_load_fw_extended_msg(struct nfp_nsp *state, u32 ret_val)
{
static const char * const major_msg[] = {
"Firmware from driver loaded",
"Firmware from flash loaded",
"Firmware loading failure",
};
static const char * const minor_msg[] = {
"",
"no named partition on flash",
"error reading from flash",
"can not deflate",
"not a trusted file",
"can not parse FW file",
"MIP not found in FW file",
"null firmware name in MIP",
"FW version none",
"FW build number none",
"no FW selection policy HWInfo key found",
"static FW selection policy",
"FW version has precedence",
"different FW application load requested",
"development build",
};
unsigned int major, minor;
const char *level;
major = FIELD_GET(NFP_FW_LOAD_RET_MAJOR, ret_val);
minor = FIELD_GET(NFP_FW_LOAD_RET_MINOR, ret_val);
if (!nfp_nsp_has_stored_fw_load(state))
return;
if (major == 0 && (minor == 0 || minor == 10))
level = KERN_DEBUG;
else if (major == 2)
level = KERN_ERR;
else
level = KERN_INFO;
if (major >= ARRAY_SIZE(major_msg))
nfp_printk(level, state->cpp, "FW loading status: %x\n",
ret_val);
else if (minor >= ARRAY_SIZE(minor_msg))
nfp_printk(level, state->cpp, "%s, reason code: %d\n",
major_msg[major], minor);
else
nfp_printk(level, state->cpp, "%s%c %s\n",
major_msg[major], minor ? ',' : '.',
minor_msg[minor]);
}
int nfp_nsp_load_fw(struct nfp_nsp *state, const struct firmware *fw)
{
struct nfp_nsp_command_buf_arg load_fw = {
{
.code = SPCODE_FW_LOAD,
.option = fw->size,
.error_cb = nfp_nsp_load_fw_extended_msg,
},
.in_buf = fw->data,
.in_size = fw->size,
};
int ret;
ret = nfp_nsp_command_buf(state, &load_fw);
if (ret < 0)
return ret;
nfp_nsp_load_fw_extended_msg(state, ret);
return 0;
}
int nfp_nsp_write_flash(struct nfp_nsp *state, const struct firmware *fw)
{
struct nfp_nsp_command_buf_arg write_flash = {
{
.code = SPCODE_NSP_WRITE_FLASH,
.option = fw->size,
.timeout_sec = 900,
},
.in_buf = fw->data,
.in_size = fw->size,
};
return nfp_nsp_command_buf(state, &write_flash);
}
int nfp_nsp_read_eth_table(struct nfp_nsp *state, void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg eth_rescan = {
{
.code = SPCODE_ETH_RESCAN,
.option = size,
},
.out_buf = buf,
.out_size = size,
};
return nfp_nsp_command_buf(state, ð_rescan);
}
int nfp_nsp_write_eth_table(struct nfp_nsp *state,
const void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg eth_ctrl = {
{
.code = SPCODE_ETH_CONTROL,
.option = size,
},
.in_buf = buf,
.in_size = size,
};
return nfp_nsp_command_buf(state, ð_ctrl);
}
int nfp_nsp_read_identify(struct nfp_nsp *state, void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg identify = {
{
.code = SPCODE_NSP_IDENTIFY,
.option = size,
},
.out_buf = buf,
.out_size = size,
};
return nfp_nsp_command_buf(state, &identify);
}
int nfp_nsp_read_sensors(struct nfp_nsp *state, unsigned int sensor_mask,
void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg sensors = {
{
.code = SPCODE_NSP_SENSORS,
.option = sensor_mask,
},
.out_buf = buf,
.out_size = size,
};
return nfp_nsp_command_buf(state, &sensors);
}
int nfp_nsp_load_stored_fw(struct nfp_nsp *state)
{
const struct nfp_nsp_command_arg arg = {
.code = SPCODE_FW_STORED,
.error_cb = nfp_nsp_load_fw_extended_msg,
};
int ret;
ret = __nfp_nsp_command(state, &arg);
if (ret < 0)
return ret;
nfp_nsp_load_fw_extended_msg(state, ret);
return 0;
}
static int
__nfp_nsp_hwinfo_lookup(struct nfp_nsp *state, void *buf, unsigned int size,
bool optional)
{
struct nfp_nsp_command_buf_arg hwinfo_lookup = {
{
.code = SPCODE_HWINFO_LOOKUP,
.option = size,
.error_quiet = optional,
},
.in_buf = buf,
.in_size = size,
.out_buf = buf,
.out_size = size,
};
return nfp_nsp_command_buf(state, &hwinfo_lookup);
}
int nfp_nsp_hwinfo_lookup(struct nfp_nsp *state, void *buf, unsigned int size)
{
int err;
size = min_t(u32, size, NFP_HWINFO_LOOKUP_SIZE);
err = __nfp_nsp_hwinfo_lookup(state, buf, size, false);
if (err)
return err;
if (strnlen(buf, size) == size) {
nfp_err(state->cpp, "NSP HWinfo value not NULL-terminated\n");
return -EINVAL;
}
return 0;
}
int nfp_nsp_hwinfo_lookup_optional(struct nfp_nsp *state, void *buf,
unsigned int size, const char *default_val)
{
int err;
if (strnlen(default_val, size) == size)
return -EINVAL;
if (!nfp_nsp_has_hwinfo_lookup(state)) {
strcpy(buf, default_val);
return 0;
}
size = min_t(u32, size, NFP_HWINFO_LOOKUP_SIZE);
err = __nfp_nsp_hwinfo_lookup(state, buf, size, true);
if (err) {
if (err == -ENOENT) {
strcpy(buf, default_val);
return 0;
}
nfp_err(state->cpp, "NSP HWinfo lookup failed: %d\n", err);
return err;
}
if (strnlen(buf, size) == size) {
nfp_err(state->cpp, "NSP HWinfo value not NULL-terminated\n");
return -EINVAL;
}
return 0;
}
int nfp_nsp_hwinfo_set(struct nfp_nsp *state, void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg hwinfo_set = {
{
.code = SPCODE_HWINFO_SET,
.option = size,
},
.in_buf = buf,
.in_size = size,
};
return nfp_nsp_command_buf(state, &hwinfo_set);
}
int nfp_nsp_fw_loaded(struct nfp_nsp *state)
{
const struct nfp_nsp_command_arg arg = {
.code = SPCODE_FW_LOADED,
};
return __nfp_nsp_command(state, &arg);
}
int nfp_nsp_versions(struct nfp_nsp *state, void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg versions = {
{
.code = SPCODE_VERSIONS,
.option = min_t(u32, size, NFP_VERSIONS_SIZE),
},
.out_buf = buf,
.out_size = min_t(u32, size, NFP_VERSIONS_SIZE),
};
return nfp_nsp_command_buf(state, &versions);
}
const char *nfp_nsp_versions_get(enum nfp_nsp_versions id, bool flash,
const u8 *buf, unsigned int size)
{
static const u32 id2off[] = {
[NFP_VERSIONS_BSP] = NFP_VERSIONS_BSP_OFF,
[NFP_VERSIONS_CPLD] = NFP_VERSIONS_CPLD_OFF,
[NFP_VERSIONS_APP] = NFP_VERSIONS_APP_OFF,
[NFP_VERSIONS_BUNDLE] = NFP_VERSIONS_BUNDLE_OFF,
[NFP_VERSIONS_UNDI] = NFP_VERSIONS_UNDI_OFF,
[NFP_VERSIONS_NCSI] = NFP_VERSIONS_NCSI_OFF,
[NFP_VERSIONS_CFGR] = NFP_VERSIONS_CFGR_OFF,
};
unsigned int field, buf_field_cnt, buf_off;
if (id >= ARRAY_SIZE(id2off) || !id2off[id])
return ERR_PTR(-EINVAL);
field = id * 2 + flash;
buf_field_cnt = get_unaligned_le16(buf);
if (buf_field_cnt <= field)
return ERR_PTR(-ENOENT);
buf_off = get_unaligned_le16(buf + id2off[id] + flash * 2);
if (!buf_off)
return ERR_PTR(-ENOENT);
if (buf_off >= size)
return ERR_PTR(-EINVAL);
if (strnlen(&buf[buf_off], size - buf_off) == size - buf_off)
return ERR_PTR(-EINVAL);
return (const char *)&buf[buf_off];
}
static int
__nfp_nsp_module_eeprom(struct nfp_nsp *state, void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg module_eeprom = {
{
.code = SPCODE_READ_SFF_EEPROM,
.option = size,
},
.in_buf = buf,
.in_size = size,
.out_buf = buf,
.out_size = size,
};
return nfp_nsp_command_buf(state, &module_eeprom);
}
int nfp_nsp_read_module_eeprom(struct nfp_nsp *state, int eth_index,
unsigned int offset, void *data,
unsigned int len, unsigned int *read_len)
{
struct eeprom_buf {
u8 metalen;
__le16 length;
__le16 offset;
__le16 readlen;
u8 eth_index;
u8 data[];
} __packed *buf;
int bufsz, ret;
BUILD_BUG_ON(offsetof(struct eeprom_buf, data) % 8);
bufsz = struct_size(buf, data, round_up(len, NSP_SFF_EEPROM_BLOCK_LEN));
buf = kzalloc(bufsz, GFP_KERNEL);
if (!buf)
return -ENOMEM;
buf->metalen =
offsetof(struct eeprom_buf, data) / NSP_SFF_EEPROM_BLOCK_LEN;
buf->length = cpu_to_le16(len);
buf->offset = cpu_to_le16(offset);
buf->eth_index = eth_index;
ret = __nfp_nsp_module_eeprom(state, buf, bufsz);
*read_len = min_t(unsigned int, len, le16_to_cpu(buf->readlen));
if (*read_len)
memcpy(data, buf->data, *read_len);
if (!ret && *read_len < len)
ret = -EIO;
kfree(buf);
return ret;
};
int nfp_nsp_read_media(struct nfp_nsp *state, void *buf, unsigned int size)
{
struct nfp_nsp_command_buf_arg media = {
{
.code = SPCODE_READ_MEDIA,
.option = size,
},
.in_buf = buf,
.in_size = size,
.out_buf = buf,
.out_size = size,
};
return nfp_nsp_command_buf(state, &media);
}