| // 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" | |
| static const int padding_bytes = 26; | |
| 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) + | |
| ALIGN(nca->payload, 4)); | |
| *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; | |
| int payload; | |
| /* NC-SI spec DSP_0222_1.2.0, section 8.2.2.2 | |
| * requires payload to be padded with 0 to | |
| * 32-bit boundary before the checksum field. | |
| * Ensure the padding bytes are accounted for in | |
| * skb allocation | |
| */ | |
| payload = ALIGN(nca->payload, 4); | |
| len = sizeof(struct ncsi_cmd_pkt_hdr) + 4; | |
| len += max(payload, padding_bytes); | |
| cmd = skb_put_zero(skb, len); | |
| unsafe_memcpy(&cmd->mfr_id, nca->data, nca->payload, | |
| /* skb allocated with enough to load the 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 }, | |
| { NCSI_PKT_CMD_GMCMA, 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 payload; | |
| 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. | |
| * Payload needs padding so that the checksum field following payload is | |
| * aligned to 32-bit boundary. | |
| * 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; | |
| payload = ALIGN(nca->payload, 4); | |
| len += max(payload, padding_bytes); | |
| /* 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_cmd_handler *nch = NULL; | |
| struct ncsi_request *nr; | |
| unsigned char type; | |
| struct ethhdr *eh; | |
| int i, ret; | |
| /* Use OEM generic handler for Netlink request */ | |
| if (nca->req_flags == NCSI_REQ_FLAG_NETLINK_DRIVEN) | |
| type = NCSI_PKT_CMD_OEM; | |
| else | |
| type = nca->type; | |
| /* Search for the handler */ | |
| for (i = 0; i < ARRAY_SIZE(ncsi_cmd_handlers); i++) { | |
| if (ncsi_cmd_handlers[i].type == 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); | |
| /* If mac address received from device then use it for | |
| * source address as unicast address else use broadcast | |
| * address as source address | |
| */ | |
| if (nca->ndp->gma_flag == 1) | |
| memcpy(eh->h_source, nca->ndp->ndev.dev->dev_addr, ETH_ALEN); | |
| else | |
| 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; | |
| } |