linux/net/ncsi/ncsi-rsp.c
Samuel Mendoza-Jonas 062b3e1b6d net/ncsi: Refactor MAC, VLAN filters
The NCSI driver defines a generic ncsi_channel_filter struct that can be
used to store arbitrarily formatted filters, and several generic methods
of accessing data stored in such a filter.
However in both the driver and as defined in the NCSI specification
there are only two actual filters: VLAN ID filters and MAC address
filters. The splitting of the MAC filter into unicast, multicast, and
mixed is also technically not necessary as these are stored in the same
location in hardware.

To save complexity, particularly in the set up and accessing of these
generic filters, remove them in favour of two specific structs. These
can be acted on directly and do not need several generic helper
functions to use.

This also fixes a memory error found by KASAN on ARM32 (which is not
upstream yet), where response handlers accessing a filter's data field
could write past allocated memory.

[  114.926512] ==================================================================
[  114.933861] BUG: KASAN: slab-out-of-bounds in ncsi_configure_channel+0x4b8/0xc58
[  114.941304] Read of size 2 at addr 94888558 by task kworker/0:2/546
[  114.947593]
[  114.949146] CPU: 0 PID: 546 Comm: kworker/0:2 Not tainted 4.16.0-rc6-00119-ge156398bfcad #13
...
[  115.170233] The buggy address belongs to the object at 94888540
[  115.170233]  which belongs to the cache kmalloc-32 of size 32
[  115.181917] The buggy address is located 24 bytes inside of
[  115.181917]  32-byte region [94888540, 94888560)
[  115.192115] The buggy address belongs to the page:
[  115.196943] page:9eeac100 count:1 mapcount:0 mapping:94888000 index:0x94888fc1
[  115.204200] flags: 0x100(slab)
[  115.207330] raw: 00000100 94888000 94888fc1 0000003f 00000001 9eea2014 9eecaa74 96c003e0
[  115.215444] page dumped because: kasan: bad access detected
[  115.221036]
[  115.222544] Memory state around the buggy address:
[  115.227384]  94888400: fb fb fb fb fc fc fc fc 04 fc fc fc fc fc fc fc
[  115.233959]  94888480: 00 00 00 fc fc fc fc fc 00 04 fc fc fc fc fc fc
[  115.240529] >94888500: 00 00 04 fc fc fc fc fc 00 00 04 fc fc fc fc fc
[  115.247077]                                             ^
[  115.252523]  94888580: 00 04 fc fc fc fc fc fc 06 fc fc fc fc fc fc fc
[  115.259093]  94888600: 00 00 06 fc fc fc fc fc 00 00 04 fc fc fc fc fc
[  115.265639] ==================================================================

Reported-by: Joel Stanley <joel@jms.id.au>
Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-04-17 13:50:58 -04:00

1017 lines
27 KiB
C

/*
* Copyright Gavin Shan, IBM Corporation 2016.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <net/ncsi.h>
#include <net/net_namespace.h>
#include <net/sock.h>
#include "internal.h"
#include "ncsi-pkt.h"
static int ncsi_validate_rsp_pkt(struct ncsi_request *nr,
unsigned short payload)
{
struct ncsi_rsp_pkt_hdr *h;
u32 checksum;
__be32 *pchecksum;
/* Check NCSI packet header. We don't need validate
* the packet type, which should have been checked
* before calling this function.
*/
h = (struct ncsi_rsp_pkt_hdr *)skb_network_header(nr->rsp);
if (h->common.revision != NCSI_PKT_REVISION)
return -EINVAL;
if (ntohs(h->common.length) != payload)
return -EINVAL;
/* Check on code and reason */
if (ntohs(h->code) != NCSI_PKT_RSP_C_COMPLETED ||
ntohs(h->reason) != NCSI_PKT_RSP_R_NO_ERROR)
return -EINVAL;
/* Validate checksum, which might be zeroes if the
* sender doesn't support checksum according to NCSI
* specification.
*/
pchecksum = (__be32 *)((void *)(h + 1) + payload - 4);
if (ntohl(*pchecksum) == 0)
return 0;
checksum = ncsi_calculate_checksum((unsigned char *)h,
sizeof(*h) + payload - 4);
if (*pchecksum != htonl(checksum))
return -EINVAL;
return 0;
}
static int ncsi_rsp_handler_cis(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_package *np;
struct ncsi_channel *nc;
unsigned char id;
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, &np, &nc);
if (!nc) {
if (ndp->flags & NCSI_DEV_PROBED)
return -ENXIO;
id = NCSI_CHANNEL_INDEX(rsp->rsp.common.channel);
nc = ncsi_add_channel(np, id);
}
return nc ? 0 : -ENODEV;
}
static int ncsi_rsp_handler_sp(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_package *np;
unsigned char id;
/* Add the package if it's not existing. Otherwise,
* to change the state of its child channels.
*/
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
&np, NULL);
if (!np) {
if (ndp->flags & NCSI_DEV_PROBED)
return -ENXIO;
id = NCSI_PACKAGE_INDEX(rsp->rsp.common.channel);
np = ncsi_add_package(ndp, id);
if (!np)
return -ENODEV;
}
return 0;
}
static int ncsi_rsp_handler_dp(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_package *np;
struct ncsi_channel *nc;
unsigned long flags;
/* Find the package */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
&np, NULL);
if (!np)
return -ENODEV;
/* Change state of all channels attached to the package */
NCSI_FOR_EACH_CHANNEL(np, nc) {
spin_lock_irqsave(&nc->lock, flags);
nc->state = NCSI_CHANNEL_INACTIVE;
spin_unlock_irqrestore(&nc->lock, flags);
}
return 0;
}
static int ncsi_rsp_handler_ec(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
ncm = &nc->modes[NCSI_MODE_ENABLE];
if (ncm->enable)
return 0;
ncm->enable = 1;
return 0;
}
static int ncsi_rsp_handler_dc(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
int ret;
ret = ncsi_validate_rsp_pkt(nr, 4);
if (ret)
return ret;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
ncm = &nc->modes[NCSI_MODE_ENABLE];
if (!ncm->enable)
return 0;
ncm->enable = 0;
return 0;
}
static int ncsi_rsp_handler_rc(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
unsigned long flags;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Update state for the specified channel */
spin_lock_irqsave(&nc->lock, flags);
nc->state = NCSI_CHANNEL_INACTIVE;
spin_unlock_irqrestore(&nc->lock, flags);
return 0;
}
static int ncsi_rsp_handler_ecnt(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
ncm = &nc->modes[NCSI_MODE_TX_ENABLE];
if (ncm->enable)
return 0;
ncm->enable = 1;
return 0;
}
static int ncsi_rsp_handler_dcnt(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
ncm = &nc->modes[NCSI_MODE_TX_ENABLE];
if (!ncm->enable)
return 0;
ncm->enable = 1;
return 0;
}
static int ncsi_rsp_handler_ae(struct ncsi_request *nr)
{
struct ncsi_cmd_ae_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if the AEN has been enabled */
ncm = &nc->modes[NCSI_MODE_AEN];
if (ncm->enable)
return 0;
/* Update to AEN configuration */
cmd = (struct ncsi_cmd_ae_pkt *)skb_network_header(nr->cmd);
ncm->enable = 1;
ncm->data[0] = cmd->mc_id;
ncm->data[1] = ntohl(cmd->mode);
return 0;
}
static int ncsi_rsp_handler_sl(struct ncsi_request *nr)
{
struct ncsi_cmd_sl_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
cmd = (struct ncsi_cmd_sl_pkt *)skb_network_header(nr->cmd);
ncm = &nc->modes[NCSI_MODE_LINK];
ncm->data[0] = ntohl(cmd->mode);
ncm->data[1] = ntohl(cmd->oem_mode);
return 0;
}
static int ncsi_rsp_handler_gls(struct ncsi_request *nr)
{
struct ncsi_rsp_gls_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
unsigned long flags;
/* Find the package and channel */
rsp = (struct ncsi_rsp_gls_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
ncm = &nc->modes[NCSI_MODE_LINK];
ncm->data[2] = ntohl(rsp->status);
ncm->data[3] = ntohl(rsp->other);
ncm->data[4] = ntohl(rsp->oem_status);
if (nr->flags & NCSI_REQ_FLAG_EVENT_DRIVEN)
return 0;
/* Reset the channel monitor if it has been enabled */
spin_lock_irqsave(&nc->lock, flags);
nc->monitor.state = NCSI_CHANNEL_MONITOR_START;
spin_unlock_irqrestore(&nc->lock, flags);
return 0;
}
static int ncsi_rsp_handler_svf(struct ncsi_request *nr)
{
struct ncsi_cmd_svf_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_vlan_filter *ncf;
unsigned long flags;
void *bitmap;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
cmd = (struct ncsi_cmd_svf_pkt *)skb_network_header(nr->cmd);
ncf = &nc->vlan_filter;
if (cmd->index > ncf->n_vids)
return -ERANGE;
/* Add or remove the VLAN filter. Remember HW indexes from 1 */
spin_lock_irqsave(&nc->lock, flags);
bitmap = &ncf->bitmap;
if (!(cmd->enable & 0x1)) {
if (test_and_clear_bit(cmd->index - 1, bitmap))
ncf->vids[cmd->index - 1] = 0;
} else {
set_bit(cmd->index - 1, bitmap);
ncf->vids[cmd->index - 1] = ntohs(cmd->vlan);
}
spin_unlock_irqrestore(&nc->lock, flags);
return 0;
}
static int ncsi_rsp_handler_ev(struct ncsi_request *nr)
{
struct ncsi_cmd_ev_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if VLAN mode has been enabled */
ncm = &nc->modes[NCSI_MODE_VLAN];
if (ncm->enable)
return 0;
/* Update to VLAN mode */
cmd = (struct ncsi_cmd_ev_pkt *)skb_network_header(nr->cmd);
ncm->enable = 1;
ncm->data[0] = ntohl(cmd->mode);
return 0;
}
static int ncsi_rsp_handler_dv(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if VLAN mode has been enabled */
ncm = &nc->modes[NCSI_MODE_VLAN];
if (!ncm->enable)
return 0;
/* Update to VLAN mode */
ncm->enable = 0;
return 0;
}
static int ncsi_rsp_handler_sma(struct ncsi_request *nr)
{
struct ncsi_cmd_sma_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mac_filter *ncf;
unsigned long flags;
void *bitmap;
bool enabled;
int index;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* According to NCSI spec 1.01, the mixed filter table
* isn't supported yet.
*/
cmd = (struct ncsi_cmd_sma_pkt *)skb_network_header(nr->cmd);
enabled = cmd->at_e & 0x1;
ncf = &nc->mac_filter;
bitmap = &ncf->bitmap;
if (cmd->index > ncf->n_uc + ncf->n_mc + ncf->n_mixed)
return -ERANGE;
index = (cmd->index - 1) * ETH_ALEN;
spin_lock_irqsave(&nc->lock, flags);
if (enabled) {
set_bit(cmd->index - 1, bitmap);
memcpy(&ncf->addrs[index], cmd->mac, ETH_ALEN);
} else {
clear_bit(cmd->index - 1, bitmap);
memset(&ncf->addrs[index], 0, ETH_ALEN);
}
spin_unlock_irqrestore(&nc->lock, flags);
return 0;
}
static int ncsi_rsp_handler_ebf(struct ncsi_request *nr)
{
struct ncsi_cmd_ebf_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the package and channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if broadcast filter has been enabled */
ncm = &nc->modes[NCSI_MODE_BC];
if (ncm->enable)
return 0;
/* Update to broadcast filter mode */
cmd = (struct ncsi_cmd_ebf_pkt *)skb_network_header(nr->cmd);
ncm->enable = 1;
ncm->data[0] = ntohl(cmd->mode);
return 0;
}
static int ncsi_rsp_handler_dbf(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if broadcast filter isn't enabled */
ncm = &nc->modes[NCSI_MODE_BC];
if (!ncm->enable)
return 0;
/* Update to broadcast filter mode */
ncm->enable = 0;
ncm->data[0] = 0;
return 0;
}
static int ncsi_rsp_handler_egmf(struct ncsi_request *nr)
{
struct ncsi_cmd_egmf_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if multicast filter has been enabled */
ncm = &nc->modes[NCSI_MODE_MC];
if (ncm->enable)
return 0;
/* Update to multicast filter mode */
cmd = (struct ncsi_cmd_egmf_pkt *)skb_network_header(nr->cmd);
ncm->enable = 1;
ncm->data[0] = ntohl(cmd->mode);
return 0;
}
static int ncsi_rsp_handler_dgmf(struct ncsi_request *nr)
{
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if multicast filter has been enabled */
ncm = &nc->modes[NCSI_MODE_MC];
if (!ncm->enable)
return 0;
/* Update to multicast filter mode */
ncm->enable = 0;
ncm->data[0] = 0;
return 0;
}
static int ncsi_rsp_handler_snfc(struct ncsi_request *nr)
{
struct ncsi_cmd_snfc_pkt *cmd;
struct ncsi_rsp_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
/* Find the channel */
rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Check if flow control has been enabled */
ncm = &nc->modes[NCSI_MODE_FC];
if (ncm->enable)
return 0;
/* Update to flow control mode */
cmd = (struct ncsi_cmd_snfc_pkt *)skb_network_header(nr->cmd);
ncm->enable = 1;
ncm->data[0] = cmd->mode;
return 0;
}
static int ncsi_rsp_handler_gvi(struct ncsi_request *nr)
{
struct ncsi_rsp_gvi_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_version *ncv;
int i;
/* Find the channel */
rsp = (struct ncsi_rsp_gvi_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Update to channel's version info */
ncv = &nc->version;
ncv->version = ntohl(rsp->ncsi_version);
ncv->alpha2 = rsp->alpha2;
memcpy(ncv->fw_name, rsp->fw_name, 12);
ncv->fw_version = ntohl(rsp->fw_version);
for (i = 0; i < ARRAY_SIZE(ncv->pci_ids); i++)
ncv->pci_ids[i] = ntohs(rsp->pci_ids[i]);
ncv->mf_id = ntohl(rsp->mf_id);
return 0;
}
static int ncsi_rsp_handler_gc(struct ncsi_request *nr)
{
struct ncsi_rsp_gc_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
size_t size;
/* Find the channel */
rsp = (struct ncsi_rsp_gc_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Update channel's capabilities */
nc->caps[NCSI_CAP_GENERIC].cap = ntohl(rsp->cap) &
NCSI_CAP_GENERIC_MASK;
nc->caps[NCSI_CAP_BC].cap = ntohl(rsp->bc_cap) &
NCSI_CAP_BC_MASK;
nc->caps[NCSI_CAP_MC].cap = ntohl(rsp->mc_cap) &
NCSI_CAP_MC_MASK;
nc->caps[NCSI_CAP_BUFFER].cap = ntohl(rsp->buf_cap);
nc->caps[NCSI_CAP_AEN].cap = ntohl(rsp->aen_cap) &
NCSI_CAP_AEN_MASK;
nc->caps[NCSI_CAP_VLAN].cap = rsp->vlan_mode &
NCSI_CAP_VLAN_MASK;
size = (rsp->uc_cnt + rsp->mc_cnt + rsp->mixed_cnt) * ETH_ALEN;
nc->mac_filter.addrs = kzalloc(size, GFP_KERNEL);
if (!nc->mac_filter.addrs)
return -ENOMEM;
nc->mac_filter.n_uc = rsp->uc_cnt;
nc->mac_filter.n_mc = rsp->mc_cnt;
nc->mac_filter.n_mixed = rsp->mixed_cnt;
nc->vlan_filter.vids = kcalloc(rsp->vlan_cnt,
sizeof(*nc->vlan_filter.vids),
GFP_KERNEL);
if (!nc->vlan_filter.vids)
return -ENOMEM;
/* Set VLAN filters active so they are cleared in the first
* configuration state
*/
nc->vlan_filter.bitmap = U64_MAX;
nc->vlan_filter.n_vids = rsp->vlan_cnt;
return 0;
}
static int ncsi_rsp_handler_gp(struct ncsi_request *nr)
{
struct ncsi_channel_vlan_filter *ncvf;
struct ncsi_channel_mac_filter *ncmf;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_rsp_gp_pkt *rsp;
struct ncsi_channel *nc;
unsigned short enable;
unsigned char *pdata;
unsigned long flags;
void *bitmap;
int i;
/* Find the channel */
rsp = (struct ncsi_rsp_gp_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Modes with explicit enabled indications */
if (ntohl(rsp->valid_modes) & 0x1) { /* BC filter mode */
nc->modes[NCSI_MODE_BC].enable = 1;
nc->modes[NCSI_MODE_BC].data[0] = ntohl(rsp->bc_mode);
}
if (ntohl(rsp->valid_modes) & 0x2) /* Channel enabled */
nc->modes[NCSI_MODE_ENABLE].enable = 1;
if (ntohl(rsp->valid_modes) & 0x4) /* Channel Tx enabled */
nc->modes[NCSI_MODE_TX_ENABLE].enable = 1;
if (ntohl(rsp->valid_modes) & 0x8) /* MC filter mode */
nc->modes[NCSI_MODE_MC].enable = 1;
/* Modes without explicit enabled indications */
nc->modes[NCSI_MODE_LINK].enable = 1;
nc->modes[NCSI_MODE_LINK].data[0] = ntohl(rsp->link_mode);
nc->modes[NCSI_MODE_VLAN].enable = 1;
nc->modes[NCSI_MODE_VLAN].data[0] = rsp->vlan_mode;
nc->modes[NCSI_MODE_FC].enable = 1;
nc->modes[NCSI_MODE_FC].data[0] = rsp->fc_mode;
nc->modes[NCSI_MODE_AEN].enable = 1;
nc->modes[NCSI_MODE_AEN].data[0] = ntohl(rsp->aen_mode);
/* MAC addresses filter table */
pdata = (unsigned char *)rsp + 48;
enable = rsp->mac_enable;
ncmf = &nc->mac_filter;
spin_lock_irqsave(&nc->lock, flags);
bitmap = &ncmf->bitmap;
for (i = 0; i < rsp->mac_cnt; i++, pdata += 6) {
if (!(enable & (0x1 << i)))
clear_bit(i, bitmap);
else
set_bit(i, bitmap);
memcpy(&ncmf->addrs[i * ETH_ALEN], pdata, ETH_ALEN);
}
spin_unlock_irqrestore(&nc->lock, flags);
/* VLAN filter table */
enable = ntohs(rsp->vlan_enable);
ncvf = &nc->vlan_filter;
bitmap = &ncvf->bitmap;
spin_lock_irqsave(&nc->lock, flags);
for (i = 0; i < rsp->vlan_cnt; i++, pdata += 2) {
if (!(enable & (0x1 << i)))
clear_bit(i, bitmap);
else
set_bit(i, bitmap);
ncvf->vids[i] = ntohs(*(__be16 *)pdata);
}
spin_unlock_irqrestore(&nc->lock, flags);
return 0;
}
static int ncsi_rsp_handler_gcps(struct ncsi_request *nr)
{
struct ncsi_rsp_gcps_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_stats *ncs;
/* Find the channel */
rsp = (struct ncsi_rsp_gcps_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Update HNC's statistics */
ncs = &nc->stats;
ncs->hnc_cnt_hi = ntohl(rsp->cnt_hi);
ncs->hnc_cnt_lo = ntohl(rsp->cnt_lo);
ncs->hnc_rx_bytes = ntohl(rsp->rx_bytes);
ncs->hnc_tx_bytes = ntohl(rsp->tx_bytes);
ncs->hnc_rx_uc_pkts = ntohl(rsp->rx_uc_pkts);
ncs->hnc_rx_mc_pkts = ntohl(rsp->rx_mc_pkts);
ncs->hnc_rx_bc_pkts = ntohl(rsp->rx_bc_pkts);
ncs->hnc_tx_uc_pkts = ntohl(rsp->tx_uc_pkts);
ncs->hnc_tx_mc_pkts = ntohl(rsp->tx_mc_pkts);
ncs->hnc_tx_bc_pkts = ntohl(rsp->tx_bc_pkts);
ncs->hnc_fcs_err = ntohl(rsp->fcs_err);
ncs->hnc_align_err = ntohl(rsp->align_err);
ncs->hnc_false_carrier = ntohl(rsp->false_carrier);
ncs->hnc_runt_pkts = ntohl(rsp->runt_pkts);
ncs->hnc_jabber_pkts = ntohl(rsp->jabber_pkts);
ncs->hnc_rx_pause_xon = ntohl(rsp->rx_pause_xon);
ncs->hnc_rx_pause_xoff = ntohl(rsp->rx_pause_xoff);
ncs->hnc_tx_pause_xon = ntohl(rsp->tx_pause_xon);
ncs->hnc_tx_pause_xoff = ntohl(rsp->tx_pause_xoff);
ncs->hnc_tx_s_collision = ntohl(rsp->tx_s_collision);
ncs->hnc_tx_m_collision = ntohl(rsp->tx_m_collision);
ncs->hnc_l_collision = ntohl(rsp->l_collision);
ncs->hnc_e_collision = ntohl(rsp->e_collision);
ncs->hnc_rx_ctl_frames = ntohl(rsp->rx_ctl_frames);
ncs->hnc_rx_64_frames = ntohl(rsp->rx_64_frames);
ncs->hnc_rx_127_frames = ntohl(rsp->rx_127_frames);
ncs->hnc_rx_255_frames = ntohl(rsp->rx_255_frames);
ncs->hnc_rx_511_frames = ntohl(rsp->rx_511_frames);
ncs->hnc_rx_1023_frames = ntohl(rsp->rx_1023_frames);
ncs->hnc_rx_1522_frames = ntohl(rsp->rx_1522_frames);
ncs->hnc_rx_9022_frames = ntohl(rsp->rx_9022_frames);
ncs->hnc_tx_64_frames = ntohl(rsp->tx_64_frames);
ncs->hnc_tx_127_frames = ntohl(rsp->tx_127_frames);
ncs->hnc_tx_255_frames = ntohl(rsp->tx_255_frames);
ncs->hnc_tx_511_frames = ntohl(rsp->tx_511_frames);
ncs->hnc_tx_1023_frames = ntohl(rsp->tx_1023_frames);
ncs->hnc_tx_1522_frames = ntohl(rsp->tx_1522_frames);
ncs->hnc_tx_9022_frames = ntohl(rsp->tx_9022_frames);
ncs->hnc_rx_valid_bytes = ntohl(rsp->rx_valid_bytes);
ncs->hnc_rx_runt_pkts = ntohl(rsp->rx_runt_pkts);
ncs->hnc_rx_jabber_pkts = ntohl(rsp->rx_jabber_pkts);
return 0;
}
static int ncsi_rsp_handler_gns(struct ncsi_request *nr)
{
struct ncsi_rsp_gns_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_stats *ncs;
/* Find the channel */
rsp = (struct ncsi_rsp_gns_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Update HNC's statistics */
ncs = &nc->stats;
ncs->ncsi_rx_cmds = ntohl(rsp->rx_cmds);
ncs->ncsi_dropped_cmds = ntohl(rsp->dropped_cmds);
ncs->ncsi_cmd_type_errs = ntohl(rsp->cmd_type_errs);
ncs->ncsi_cmd_csum_errs = ntohl(rsp->cmd_csum_errs);
ncs->ncsi_rx_pkts = ntohl(rsp->rx_pkts);
ncs->ncsi_tx_pkts = ntohl(rsp->tx_pkts);
ncs->ncsi_tx_aen_pkts = ntohl(rsp->tx_aen_pkts);
return 0;
}
static int ncsi_rsp_handler_gnpts(struct ncsi_request *nr)
{
struct ncsi_rsp_gnpts_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_channel *nc;
struct ncsi_channel_stats *ncs;
/* Find the channel */
rsp = (struct ncsi_rsp_gnpts_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
NULL, &nc);
if (!nc)
return -ENODEV;
/* Update HNC's statistics */
ncs = &nc->stats;
ncs->pt_tx_pkts = ntohl(rsp->tx_pkts);
ncs->pt_tx_dropped = ntohl(rsp->tx_dropped);
ncs->pt_tx_channel_err = ntohl(rsp->tx_channel_err);
ncs->pt_tx_us_err = ntohl(rsp->tx_us_err);
ncs->pt_rx_pkts = ntohl(rsp->rx_pkts);
ncs->pt_rx_dropped = ntohl(rsp->rx_dropped);
ncs->pt_rx_channel_err = ntohl(rsp->rx_channel_err);
ncs->pt_rx_us_err = ntohl(rsp->rx_us_err);
ncs->pt_rx_os_err = ntohl(rsp->rx_os_err);
return 0;
}
static int ncsi_rsp_handler_gps(struct ncsi_request *nr)
{
struct ncsi_rsp_gps_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_package *np;
/* Find the package */
rsp = (struct ncsi_rsp_gps_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
&np, NULL);
if (!np)
return -ENODEV;
return 0;
}
static int ncsi_rsp_handler_gpuuid(struct ncsi_request *nr)
{
struct ncsi_rsp_gpuuid_pkt *rsp;
struct ncsi_dev_priv *ndp = nr->ndp;
struct ncsi_package *np;
/* Find the package */
rsp = (struct ncsi_rsp_gpuuid_pkt *)skb_network_header(nr->rsp);
ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
&np, NULL);
if (!np)
return -ENODEV;
memcpy(np->uuid, rsp->uuid, sizeof(rsp->uuid));
return 0;
}
static struct ncsi_rsp_handler {
unsigned char type;
int payload;
int (*handler)(struct ncsi_request *nr);
} ncsi_rsp_handlers[] = {
{ NCSI_PKT_RSP_CIS, 4, ncsi_rsp_handler_cis },
{ NCSI_PKT_RSP_SP, 4, ncsi_rsp_handler_sp },
{ NCSI_PKT_RSP_DP, 4, ncsi_rsp_handler_dp },
{ NCSI_PKT_RSP_EC, 4, ncsi_rsp_handler_ec },
{ NCSI_PKT_RSP_DC, 4, ncsi_rsp_handler_dc },
{ NCSI_PKT_RSP_RC, 4, ncsi_rsp_handler_rc },
{ NCSI_PKT_RSP_ECNT, 4, ncsi_rsp_handler_ecnt },
{ NCSI_PKT_RSP_DCNT, 4, ncsi_rsp_handler_dcnt },
{ NCSI_PKT_RSP_AE, 4, ncsi_rsp_handler_ae },
{ NCSI_PKT_RSP_SL, 4, ncsi_rsp_handler_sl },
{ NCSI_PKT_RSP_GLS, 16, ncsi_rsp_handler_gls },
{ NCSI_PKT_RSP_SVF, 4, ncsi_rsp_handler_svf },
{ NCSI_PKT_RSP_EV, 4, ncsi_rsp_handler_ev },
{ NCSI_PKT_RSP_DV, 4, ncsi_rsp_handler_dv },
{ NCSI_PKT_RSP_SMA, 4, ncsi_rsp_handler_sma },
{ NCSI_PKT_RSP_EBF, 4, ncsi_rsp_handler_ebf },
{ NCSI_PKT_RSP_DBF, 4, ncsi_rsp_handler_dbf },
{ NCSI_PKT_RSP_EGMF, 4, ncsi_rsp_handler_egmf },
{ NCSI_PKT_RSP_DGMF, 4, ncsi_rsp_handler_dgmf },
{ NCSI_PKT_RSP_SNFC, 4, ncsi_rsp_handler_snfc },
{ NCSI_PKT_RSP_GVI, 40, ncsi_rsp_handler_gvi },
{ NCSI_PKT_RSP_GC, 32, ncsi_rsp_handler_gc },
{ NCSI_PKT_RSP_GP, -1, ncsi_rsp_handler_gp },
{ NCSI_PKT_RSP_GCPS, 172, ncsi_rsp_handler_gcps },
{ NCSI_PKT_RSP_GNS, 172, ncsi_rsp_handler_gns },
{ NCSI_PKT_RSP_GNPTS, 172, ncsi_rsp_handler_gnpts },
{ NCSI_PKT_RSP_GPS, 8, ncsi_rsp_handler_gps },
{ NCSI_PKT_RSP_OEM, 0, NULL },
{ NCSI_PKT_RSP_PLDM, 0, NULL },
{ NCSI_PKT_RSP_GPUUID, 20, ncsi_rsp_handler_gpuuid }
};
int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
struct ncsi_rsp_handler *nrh = NULL;
struct ncsi_dev *nd;
struct ncsi_dev_priv *ndp;
struct ncsi_request *nr;
struct ncsi_pkt_hdr *hdr;
unsigned long flags;
int payload, i, ret;
/* Find the NCSI device */
nd = ncsi_find_dev(dev);
ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL;
if (!ndp)
return -ENODEV;
/* Check if it is AEN packet */
hdr = (struct ncsi_pkt_hdr *)skb_network_header(skb);
if (hdr->type == NCSI_PKT_AEN)
return ncsi_aen_handler(ndp, skb);
/* Find the handler */
for (i = 0; i < ARRAY_SIZE(ncsi_rsp_handlers); i++) {
if (ncsi_rsp_handlers[i].type == hdr->type) {
if (ncsi_rsp_handlers[i].handler)
nrh = &ncsi_rsp_handlers[i];
else
nrh = NULL;
break;
}
}
if (!nrh) {
netdev_err(nd->dev, "Received unrecognized packet (0x%x)\n",
hdr->type);
return -ENOENT;
}
/* Associate with the request */
spin_lock_irqsave(&ndp->lock, flags);
nr = &ndp->requests[hdr->id];
if (!nr->used) {
spin_unlock_irqrestore(&ndp->lock, flags);
return -ENODEV;
}
nr->rsp = skb;
if (!nr->enabled) {
spin_unlock_irqrestore(&ndp->lock, flags);
ret = -ENOENT;
goto out;
}
/* Validate the packet */
spin_unlock_irqrestore(&ndp->lock, flags);
payload = nrh->payload;
if (payload < 0)
payload = ntohs(hdr->length);
ret = ncsi_validate_rsp_pkt(nr, payload);
if (ret) {
netdev_warn(ndp->ndev.dev,
"NCSI: 'bad' packet ignored for type 0x%x\n",
hdr->type);
goto out;
}
/* Process the packet */
ret = nrh->handler(nr);
if (ret)
netdev_err(ndp->ndev.dev,
"NCSI: Handler for packet type 0x%x returned %d\n",
hdr->type, ret);
out:
ncsi_free_request(nr);
return ret;
}