forked from Minki/linux
2874c5fd28
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 3029 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
384 lines
9.3 KiB
C
384 lines
9.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright Gavin Shan, IBM Corporation 2016.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <net/ncsi.h>
|
|
#include <net/net_namespace.h>
|
|
#include <net/sock.h>
|
|
#include <net/genetlink.h>
|
|
|
|
#include "internal.h"
|
|
#include "ncsi-pkt.h"
|
|
|
|
u32 ncsi_calculate_checksum(unsigned char *data, int len)
|
|
{
|
|
u32 checksum = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < len; i += 2)
|
|
checksum += (((u32)data[i] << 8) | data[i + 1]);
|
|
|
|
checksum = (~checksum + 1);
|
|
return checksum;
|
|
}
|
|
|
|
/* This function should be called after the data area has been
|
|
* populated completely.
|
|
*/
|
|
static void ncsi_cmd_build_header(struct ncsi_pkt_hdr *h,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
u32 checksum;
|
|
__be32 *pchecksum;
|
|
|
|
h->mc_id = 0;
|
|
h->revision = NCSI_PKT_REVISION;
|
|
h->reserved = 0;
|
|
h->id = nca->id;
|
|
h->type = nca->type;
|
|
h->channel = NCSI_TO_CHANNEL(nca->package,
|
|
nca->channel);
|
|
h->length = htons(nca->payload);
|
|
h->reserved1[0] = 0;
|
|
h->reserved1[1] = 0;
|
|
|
|
/* Fill with calculated checksum */
|
|
checksum = ncsi_calculate_checksum((unsigned char *)h,
|
|
sizeof(*h) + nca->payload);
|
|
pchecksum = (__be32 *)((void *)h + sizeof(struct ncsi_pkt_hdr) +
|
|
nca->payload);
|
|
*pchecksum = htonl(checksum);
|
|
}
|
|
|
|
static int ncsi_cmd_handler_default(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_sp(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_sp_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->hw_arbitration = nca->bytes[0];
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_dc(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_dc_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->ald = nca->bytes[0];
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_rc(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_rc_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_ae(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_ae_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->mc_id = nca->bytes[0];
|
|
cmd->mode = htonl(nca->dwords[1]);
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_sl(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_sl_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->mode = htonl(nca->dwords[0]);
|
|
cmd->oem_mode = htonl(nca->dwords[1]);
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_svf(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_svf_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->vlan = htons(nca->words[1]);
|
|
cmd->index = nca->bytes[6];
|
|
cmd->enable = nca->bytes[7];
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_ev(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_ev_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->mode = nca->bytes[3];
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_sma(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_sma_pkt *cmd;
|
|
int i;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
for (i = 0; i < 6; i++)
|
|
cmd->mac[i] = nca->bytes[i];
|
|
cmd->index = nca->bytes[6];
|
|
cmd->at_e = nca->bytes[7];
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_ebf(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_ebf_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->mode = htonl(nca->dwords[0]);
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_egmf(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_egmf_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->mode = htonl(nca->dwords[0]);
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_snfc(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_snfc_pkt *cmd;
|
|
|
|
cmd = skb_put_zero(skb, sizeof(*cmd));
|
|
cmd->mode = nca->bytes[0];
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_cmd_handler_oem(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_cmd_oem_pkt *cmd;
|
|
unsigned int len;
|
|
|
|
len = sizeof(struct ncsi_cmd_pkt_hdr) + 4;
|
|
if (nca->payload < 26)
|
|
len += 26;
|
|
else
|
|
len += nca->payload;
|
|
|
|
cmd = skb_put_zero(skb, len);
|
|
memcpy(&cmd->mfr_id, nca->data, nca->payload);
|
|
ncsi_cmd_build_header(&cmd->cmd.common, nca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct ncsi_cmd_handler {
|
|
unsigned char type;
|
|
int payload;
|
|
int (*handler)(struct sk_buff *skb,
|
|
struct ncsi_cmd_arg *nca);
|
|
} ncsi_cmd_handlers[] = {
|
|
{ NCSI_PKT_CMD_CIS, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_SP, 4, ncsi_cmd_handler_sp },
|
|
{ NCSI_PKT_CMD_DP, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_EC, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_DC, 4, ncsi_cmd_handler_dc },
|
|
{ NCSI_PKT_CMD_RC, 4, ncsi_cmd_handler_rc },
|
|
{ NCSI_PKT_CMD_ECNT, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_DCNT, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_AE, 8, ncsi_cmd_handler_ae },
|
|
{ NCSI_PKT_CMD_SL, 8, ncsi_cmd_handler_sl },
|
|
{ NCSI_PKT_CMD_GLS, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_SVF, 8, ncsi_cmd_handler_svf },
|
|
{ NCSI_PKT_CMD_EV, 4, ncsi_cmd_handler_ev },
|
|
{ NCSI_PKT_CMD_DV, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_SMA, 8, ncsi_cmd_handler_sma },
|
|
{ NCSI_PKT_CMD_EBF, 4, ncsi_cmd_handler_ebf },
|
|
{ NCSI_PKT_CMD_DBF, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_EGMF, 4, ncsi_cmd_handler_egmf },
|
|
{ NCSI_PKT_CMD_DGMF, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_SNFC, 4, ncsi_cmd_handler_snfc },
|
|
{ NCSI_PKT_CMD_GVI, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_GC, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_GP, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_GCPS, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_GNS, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_GNPTS, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_GPS, 0, ncsi_cmd_handler_default },
|
|
{ NCSI_PKT_CMD_OEM, -1, ncsi_cmd_handler_oem },
|
|
{ NCSI_PKT_CMD_PLDM, 0, NULL },
|
|
{ NCSI_PKT_CMD_GPUUID, 0, ncsi_cmd_handler_default }
|
|
};
|
|
|
|
static struct ncsi_request *ncsi_alloc_command(struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_dev_priv *ndp = nca->ndp;
|
|
struct ncsi_dev *nd = &ndp->ndev;
|
|
struct net_device *dev = nd->dev;
|
|
int hlen = LL_RESERVED_SPACE(dev);
|
|
int tlen = dev->needed_tailroom;
|
|
int len = hlen + tlen;
|
|
struct sk_buff *skb;
|
|
struct ncsi_request *nr;
|
|
|
|
nr = ncsi_alloc_request(ndp, nca->req_flags);
|
|
if (!nr)
|
|
return NULL;
|
|
|
|
/* NCSI command packet has 16-bytes header, payload, 4 bytes checksum.
|
|
* The packet needs padding if its payload is less than 26 bytes to
|
|
* meet 64 bytes minimal ethernet frame length.
|
|
*/
|
|
len += sizeof(struct ncsi_cmd_pkt_hdr) + 4;
|
|
if (nca->payload < 26)
|
|
len += 26;
|
|
else
|
|
len += nca->payload;
|
|
|
|
/* Allocate skb */
|
|
skb = alloc_skb(len, GFP_ATOMIC);
|
|
if (!skb) {
|
|
ncsi_free_request(nr);
|
|
return NULL;
|
|
}
|
|
|
|
nr->cmd = skb;
|
|
skb_reserve(skb, hlen);
|
|
skb_reset_network_header(skb);
|
|
|
|
skb->dev = dev;
|
|
skb->protocol = htons(ETH_P_NCSI);
|
|
|
|
return nr;
|
|
}
|
|
|
|
int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca)
|
|
{
|
|
struct ncsi_request *nr;
|
|
struct ethhdr *eh;
|
|
struct ncsi_cmd_handler *nch = NULL;
|
|
int i, ret;
|
|
|
|
/* Search for the handler */
|
|
for (i = 0; i < ARRAY_SIZE(ncsi_cmd_handlers); i++) {
|
|
if (ncsi_cmd_handlers[i].type == nca->type) {
|
|
if (ncsi_cmd_handlers[i].handler)
|
|
nch = &ncsi_cmd_handlers[i];
|
|
else
|
|
nch = NULL;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!nch) {
|
|
netdev_err(nca->ndp->ndev.dev,
|
|
"Cannot send packet with type 0x%02x\n", nca->type);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Get packet payload length and allocate the request
|
|
* It is expected that if length set as negative in
|
|
* handler structure means caller is initializing it
|
|
* and setting length in nca before calling xmit function
|
|
*/
|
|
if (nch->payload >= 0)
|
|
nca->payload = nch->payload;
|
|
nr = ncsi_alloc_command(nca);
|
|
if (!nr)
|
|
return -ENOMEM;
|
|
|
|
/* track netlink information */
|
|
if (nca->req_flags == NCSI_REQ_FLAG_NETLINK_DRIVEN) {
|
|
nr->snd_seq = nca->info->snd_seq;
|
|
nr->snd_portid = nca->info->snd_portid;
|
|
nr->nlhdr = *nca->info->nlhdr;
|
|
}
|
|
|
|
/* Prepare the packet */
|
|
nca->id = nr->id;
|
|
ret = nch->handler(nr->cmd, nca);
|
|
if (ret) {
|
|
ncsi_free_request(nr);
|
|
return ret;
|
|
}
|
|
|
|
/* Fill the ethernet header */
|
|
eh = skb_push(nr->cmd, sizeof(*eh));
|
|
eh->h_proto = htons(ETH_P_NCSI);
|
|
eth_broadcast_addr(eh->h_dest);
|
|
eth_broadcast_addr(eh->h_source);
|
|
|
|
/* Start the timer for the request that might not have
|
|
* corresponding response. Given NCSI is an internal
|
|
* connection a 1 second delay should be sufficient.
|
|
*/
|
|
nr->enabled = true;
|
|
mod_timer(&nr->timer, jiffies + 1 * HZ);
|
|
|
|
/* Send NCSI packet */
|
|
skb_get(nr->cmd);
|
|
ret = dev_queue_xmit(nr->cmd);
|
|
if (ret < 0) {
|
|
ncsi_free_request(nr);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|