net/ncsi: Extend NC-SI Netlink interface to allow user space to send NC-SI command
authorJustin.Lee1@Dell.com <Justin.Lee1@Dell.com>
Thu, 11 Oct 2018 18:07:37 +0000 (18:07 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 16 Oct 2018 05:00:59 +0000 (22:00 -0700)
The new command (NCSI_CMD_SEND_CMD) is added to allow user space application
to send NC-SI command to the network card.
Also, add a new attribute (NCSI_ATTR_DATA) for transferring request and response.

The work flow is as below.

Request:
User space application
-> Netlink interface (msg)
-> new Netlink handler - ncsi_send_cmd_nl()
-> ncsi_xmit_cmd()

Response:
Response received - ncsi_rcv_rsp()
-> internal response handler - ncsi_rsp_handler_xxx()
-> ncsi_rsp_handler_netlink()
-> ncsi_send_netlink_rsp ()
-> Netlink interface (msg)
-> user space application

Command timeout - ncsi_request_timeout()
-> ncsi_send_netlink_timeout ()
-> Netlink interface (msg with zero data length)
-> user space application

Error:
Error detected
-> ncsi_send_netlink_err ()
-> Netlink interface (err msg)
-> user space application

Signed-off-by: Justin Lee <justin.lee1@dell.com>
Reviewed-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/ncsi.h
net/ncsi/internal.h
net/ncsi/ncsi-cmd.c
net/ncsi/ncsi-manage.c
net/ncsi/ncsi-netlink.c
net/ncsi/ncsi-netlink.h
net/ncsi/ncsi-rsp.c

index 4c292ecbb748fef563d739397af317b0f74a68a6..0a26a55766456017e552589aadbd3c5a8df86977 100644 (file)
@@ -23,6 +23,9 @@
  *     optionally the preferred NCSI_ATTR_CHANNEL_ID.
  * @NCSI_CMD_CLEAR_INTERFACE: clear any preferred package/channel combination.
  *     Requires NCSI_ATTR_IFINDEX.
+ * @NCSI_CMD_SEND_CMD: send NC-SI command to network card.
+ *     Requires NCSI_ATTR_IFINDEX, NCSI_ATTR_PACKAGE_ID
+ *     and NCSI_ATTR_CHANNEL_ID.
  * @NCSI_CMD_MAX: highest command number
  */
 enum ncsi_nl_commands {
@@ -30,6 +33,7 @@ enum ncsi_nl_commands {
        NCSI_CMD_PKG_INFO,
        NCSI_CMD_SET_INTERFACE,
        NCSI_CMD_CLEAR_INTERFACE,
+       NCSI_CMD_SEND_CMD,
 
        __NCSI_CMD_AFTER_LAST,
        NCSI_CMD_MAX = __NCSI_CMD_AFTER_LAST - 1
@@ -43,6 +47,7 @@ enum ncsi_nl_commands {
  * @NCSI_ATTR_PACKAGE_LIST: nested array of NCSI_PKG_ATTR attributes
  * @NCSI_ATTR_PACKAGE_ID: package ID
  * @NCSI_ATTR_CHANNEL_ID: channel ID
+ * @NCSI_ATTR_DATA: command payload
  * @NCSI_ATTR_MAX: highest attribute number
  */
 enum ncsi_nl_attrs {
@@ -51,6 +56,7 @@ enum ncsi_nl_attrs {
        NCSI_ATTR_PACKAGE_LIST,
        NCSI_ATTR_PACKAGE_ID,
        NCSI_ATTR_CHANNEL_ID,
+       NCSI_ATTR_DATA,
 
        __NCSI_ATTR_AFTER_LAST,
        NCSI_ATTR_MAX = __NCSI_ATTR_AFTER_LAST - 1
index 3d0a33b874f5abdae9e88807a80342f30b3c9eb8..13c9b5eeb3b719a726a38c2365fd372514f37a6f 100644 (file)
@@ -175,6 +175,8 @@ struct ncsi_package;
 #define NCSI_RESERVED_CHANNEL  0x1f
 #define NCSI_CHANNEL_INDEX(c)  ((c) & ((1 << NCSI_PACKAGE_SHIFT) - 1))
 #define NCSI_TO_CHANNEL(p, c)  (((p) << NCSI_PACKAGE_SHIFT) | (c))
+#define NCSI_MAX_PACKAGE       8
+#define NCSI_MAX_CHANNEL       32
 
 struct ncsi_channel {
        unsigned char               id;
@@ -220,11 +222,15 @@ struct ncsi_request {
        bool                 used;    /* Request that has been assigned  */
        unsigned int         flags;   /* NCSI request property           */
 #define NCSI_REQ_FLAG_EVENT_DRIVEN     1
+#define NCSI_REQ_FLAG_NETLINK_DRIVEN   2
        struct ncsi_dev_priv *ndp;    /* Associated NCSI device          */
        struct sk_buff       *cmd;    /* Associated NCSI command packet  */
        struct sk_buff       *rsp;    /* Associated NCSI response packet */
        struct timer_list    timer;   /* Timer on waiting for response   */
        bool                 enabled; /* Time has been enabled or not    */
+       u32                  snd_seq;     /* netlink sending sequence number */
+       u32                  snd_portid;  /* netlink portid of sender        */
+       struct nlmsghdr      nlhdr;       /* netlink message header          */
 };
 
 enum {
@@ -310,6 +316,7 @@ struct ncsi_cmd_arg {
                unsigned int   dwords[4];
        };
        unsigned char        *data;       /* NCSI OEM data                 */
+       struct genl_info     *info;       /* Netlink information           */
 };
 
 extern struct list_head ncsi_dev_list;
index 82b7d9201db8002c2c1c9600327e01a5f09a2c00..356af474e43c728f2f96c872000f57f0dff6c781 100644 (file)
@@ -17,6 +17,7 @@
 #include <net/ncsi.h>
 #include <net/net_namespace.h>
 #include <net/sock.h>
+#include <net/genetlink.h>
 
 #include "internal.h"
 #include "ncsi-pkt.h"
@@ -346,6 +347,13 @@ int ncsi_xmit_cmd(struct ncsi_cmd_arg *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);
index 091284760d21fa02dc0f9997a2c68ce7f1f618e6..6aa0614d2d28686eb0e861147ca8ba7956ed1df9 100644 (file)
@@ -19,6 +19,7 @@
 #include <net/addrconf.h>
 #include <net/ipv6.h>
 #include <net/if_inet6.h>
+#include <net/genetlink.h>
 
 #include "internal.h"
 #include "ncsi-pkt.h"
@@ -406,6 +407,9 @@ static void ncsi_request_timeout(struct timer_list *t)
 {
        struct ncsi_request *nr = from_timer(nr, t, timer);
        struct ncsi_dev_priv *ndp = nr->ndp;
+       struct ncsi_cmd_pkt *cmd;
+       struct ncsi_package *np;
+       struct ncsi_channel *nc;
        unsigned long flags;
 
        /* If the request already had associated response,
@@ -419,6 +423,18 @@ static void ncsi_request_timeout(struct timer_list *t)
        }
        spin_unlock_irqrestore(&ndp->lock, flags);
 
+       if (nr->flags == NCSI_REQ_FLAG_NETLINK_DRIVEN) {
+               if (nr->cmd) {
+                       /* Find the package */
+                       cmd = (struct ncsi_cmd_pkt *)
+                             skb_network_header(nr->cmd);
+                       ncsi_find_package_and_channel(ndp,
+                                                     cmd->cmd.common.channel,
+                                                     &np, &nc);
+                       ncsi_send_netlink_timeout(nr, np, nc);
+               }
+       }
+
        /* Release the request */
        ncsi_free_request(nr);
 }
index 32cb7751d2166403d9142886f8d2cbff1c245eea..33314381b4f5875757b73c80bc86765d6d84e184 100644 (file)
@@ -19,6 +19,7 @@
 #include <uapi/linux/ncsi.h>
 
 #include "internal.h"
+#include "ncsi-pkt.h"
 #include "ncsi-netlink.h"
 
 static struct genl_family ncsi_genl_family;
@@ -28,6 +29,7 @@ static const struct nla_policy ncsi_genl_policy[NCSI_ATTR_MAX + 1] = {
        [NCSI_ATTR_PACKAGE_LIST] =      { .type = NLA_NESTED },
        [NCSI_ATTR_PACKAGE_ID] =        { .type = NLA_U32 },
        [NCSI_ATTR_CHANNEL_ID] =        { .type = NLA_U32 },
+       [NCSI_ATTR_DATA] =              { .type = NLA_BINARY, .len = 2048 },
 };
 
 static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex)
@@ -365,6 +367,202 @@ static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info)
        return 0;
 }
 
+static int ncsi_send_cmd_nl(struct sk_buff *msg, struct genl_info *info)
+{
+       struct ncsi_dev_priv *ndp;
+       struct ncsi_pkt_hdr *hdr;
+       struct ncsi_cmd_arg nca;
+       unsigned char *data;
+       u32 package_id;
+       u32 channel_id;
+       int len, ret;
+
+       if (!info || !info->attrs) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (!info->attrs[NCSI_ATTR_IFINDEX]) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (!info->attrs[NCSI_ATTR_CHANNEL_ID]) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (!info->attrs[NCSI_ATTR_DATA]) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)),
+                              nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX]));
+       if (!ndp) {
+               ret = -ENODEV;
+               goto out;
+       }
+
+       package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]);
+       channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]);
+
+       if (package_id >= NCSI_MAX_PACKAGE || channel_id >= NCSI_MAX_CHANNEL) {
+               ret = -ERANGE;
+               goto out_netlink;
+       }
+
+       len = nla_len(info->attrs[NCSI_ATTR_DATA]);
+       if (len < sizeof(struct ncsi_pkt_hdr)) {
+               netdev_info(ndp->ndev.dev, "NCSI: no command to send %u\n",
+                           package_id);
+               ret = -EINVAL;
+               goto out_netlink;
+       } else {
+               data = (unsigned char *)nla_data(info->attrs[NCSI_ATTR_DATA]);
+       }
+
+       hdr = (struct ncsi_pkt_hdr *)data;
+
+       nca.ndp = ndp;
+       nca.package = (unsigned char)package_id;
+       nca.channel = (unsigned char)channel_id;
+       nca.type = hdr->type;
+       nca.req_flags = NCSI_REQ_FLAG_NETLINK_DRIVEN;
+       nca.info = info;
+       nca.payload = ntohs(hdr->length);
+       nca.data = data + sizeof(*hdr);
+
+       ret = ncsi_xmit_cmd(&nca);
+out_netlink:
+       if (ret != 0) {
+               netdev_err(ndp->ndev.dev,
+                          "NCSI: Error %d sending command\n",
+                          ret);
+               ncsi_send_netlink_err(ndp->ndev.dev,
+                                     info->snd_seq,
+                                     info->snd_portid,
+                                     info->nlhdr,
+                                     ret);
+       }
+out:
+       return ret;
+}
+
+int ncsi_send_netlink_rsp(struct ncsi_request *nr,
+                         struct ncsi_package *np,
+                         struct ncsi_channel *nc)
+{
+       struct sk_buff *skb;
+       struct net *net;
+       void *hdr;
+       int rc;
+
+       net = dev_net(nr->rsp->dev);
+
+       skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (!skb)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(skb, nr->snd_portid, nr->snd_seq,
+                         &ncsi_genl_family, 0, NCSI_CMD_SEND_CMD);
+       if (!hdr) {
+               kfree_skb(skb);
+               return -EMSGSIZE;
+       }
+
+       nla_put_u32(skb, NCSI_ATTR_IFINDEX, nr->rsp->dev->ifindex);
+       if (np)
+               nla_put_u32(skb, NCSI_ATTR_PACKAGE_ID, np->id);
+       if (nc)
+               nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, nc->id);
+       else
+               nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, NCSI_RESERVED_CHANNEL);
+
+       rc = nla_put(skb, NCSI_ATTR_DATA, nr->rsp->len, (void *)nr->rsp->data);
+       if (rc)
+               goto err;
+
+       genlmsg_end(skb, hdr);
+       return genlmsg_unicast(net, skb, nr->snd_portid);
+
+err:
+       kfree_skb(skb);
+       return rc;
+}
+
+int ncsi_send_netlink_timeout(struct ncsi_request *nr,
+                             struct ncsi_package *np,
+                             struct ncsi_channel *nc)
+{
+       struct sk_buff *skb;
+       struct net *net;
+       void *hdr;
+
+       skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (!skb)
+               return -ENOMEM;
+
+       hdr = genlmsg_put(skb, nr->snd_portid, nr->snd_seq,
+                         &ncsi_genl_family, 0, NCSI_CMD_SEND_CMD);
+       if (!hdr) {
+               kfree_skb(skb);
+               return -EMSGSIZE;
+       }
+
+       net = dev_net(nr->cmd->dev);
+
+       nla_put_u32(skb, NCSI_ATTR_IFINDEX, nr->cmd->dev->ifindex);
+
+       if (np)
+               nla_put_u32(skb, NCSI_ATTR_PACKAGE_ID, np->id);
+       else
+               nla_put_u32(skb, NCSI_ATTR_PACKAGE_ID,
+                           NCSI_PACKAGE_INDEX((((struct ncsi_pkt_hdr *)
+                                                nr->cmd->data)->channel)));
+
+       if (nc)
+               nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, nc->id);
+       else
+               nla_put_u32(skb, NCSI_ATTR_CHANNEL_ID, NCSI_RESERVED_CHANNEL);
+
+       genlmsg_end(skb, hdr);
+       return genlmsg_unicast(net, skb, nr->snd_portid);
+}
+
+int ncsi_send_netlink_err(struct net_device *dev,
+                         u32 snd_seq,
+                         u32 snd_portid,
+                         struct nlmsghdr *nlhdr,
+                         int err)
+{
+       struct nlmsghdr *nlh;
+       struct nlmsgerr *nle;
+       struct sk_buff *skb;
+       struct net *net;
+
+       skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (!skb)
+               return -ENOMEM;
+
+       net = dev_net(dev);
+
+       nlh = nlmsg_put(skb, snd_portid, snd_seq,
+                       NLMSG_ERROR, sizeof(*nle), 0);
+       nle = (struct nlmsgerr *)nlmsg_data(nlh);
+       nle->error = err;
+       memcpy(&nle->msg, nlhdr, sizeof(*nlh));
+
+       nlmsg_end(skb, nlh);
+
+       return nlmsg_unicast(net->genl_sock, skb, snd_portid);
+}
+
 static const struct genl_ops ncsi_ops[] = {
        {
                .cmd = NCSI_CMD_PKG_INFO,
@@ -385,6 +583,12 @@ static const struct genl_ops ncsi_ops[] = {
                .doit = ncsi_clear_interface_nl,
                .flags = GENL_ADMIN_PERM,
        },
+       {
+               .cmd = NCSI_CMD_SEND_CMD,
+               .policy = ncsi_genl_policy,
+               .doit = ncsi_send_cmd_nl,
+               .flags = GENL_ADMIN_PERM,
+       },
 };
 
 static struct genl_family ncsi_genl_family __ro_after_init = {
index 91a5c256f8c4fe1279bf449a5d1c83570fbd7ebb..c4a46887a932046d1c7b4dbcac8841372b40ab1d 100644 (file)
 
 #include "internal.h"
 
+int ncsi_send_netlink_rsp(struct ncsi_request *nr,
+                         struct ncsi_package *np,
+                         struct ncsi_channel *nc);
+int ncsi_send_netlink_timeout(struct ncsi_request *nr,
+                             struct ncsi_package *np,
+                             struct ncsi_channel *nc);
+int ncsi_send_netlink_err(struct net_device *dev,
+                         u32 snd_seq,
+                         u32 snd_portid,
+                         struct nlmsghdr *nlhdr,
+                         int err);
+
 int ncsi_init_netlink(struct net_device *dev);
 int ncsi_unregister_netlink(struct net_device *dev);
 
index d66b34749027f1349b56f49c2934a4c7717688bf..85fa59afae34a6fe54f60df466fb9b85e7732e0d 100644 (file)
 #include <net/ncsi.h>
 #include <net/net_namespace.h>
 #include <net/sock.h>
+#include <net/genetlink.h>
 
 #include "internal.h"
 #include "ncsi-pkt.h"
+#include "ncsi-netlink.h"
 
 static int ncsi_validate_rsp_pkt(struct ncsi_request *nr,
                                 unsigned short payload)
@@ -32,15 +34,25 @@ static int ncsi_validate_rsp_pkt(struct ncsi_request *nr,
         * before calling this function.
         */
        h = (struct ncsi_rsp_pkt_hdr *)skb_network_header(nr->rsp);
-       if (h->common.revision != NCSI_PKT_REVISION)
+
+       if (h->common.revision != NCSI_PKT_REVISION) {
+               netdev_dbg(nr->ndp->ndev.dev,
+                          "NCSI: unsupported header revision\n");
                return -EINVAL;
-       if (ntohs(h->common.length) != payload)
+       }
+       if (ntohs(h->common.length) != payload) {
+               netdev_dbg(nr->ndp->ndev.dev,
+                          "NCSI: payload length mismatched\n");
                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;
+           ntohs(h->reason) != NCSI_PKT_RSP_R_NO_ERROR) {
+               netdev_dbg(nr->ndp->ndev.dev,
+                          "NCSI: non zero response/reason code\n");
+               return -EPERM;
+       }
 
        /* Validate checksum, which might be zeroes if the
         * sender doesn't support checksum according to NCSI
@@ -52,8 +64,11 @@ static int ncsi_validate_rsp_pkt(struct ncsi_request *nr,
 
        checksum = ncsi_calculate_checksum((unsigned char *)h,
                                           sizeof(*h) + payload - 4);
-       if (*pchecksum != htonl(checksum))
+
+       if (*pchecksum != htonl(checksum)) {
+               netdev_dbg(nr->ndp->ndev.dev, "NCSI: checksum mismatched\n");
                return -EINVAL;
+       }
 
        return 0;
 }
@@ -941,6 +956,26 @@ static int ncsi_rsp_handler_gpuuid(struct ncsi_request *nr)
        return 0;
 }
 
+static int ncsi_rsp_handler_netlink(struct ncsi_request *nr)
+{
+       struct ncsi_dev_priv *ndp = nr->ndp;
+       struct ncsi_rsp_pkt *rsp;
+       struct ncsi_package *np;
+       struct ncsi_channel *nc;
+       int ret;
+
+       /* Find the package */
+       rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp);
+       ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+                                     &np, &nc);
+       if (!np)
+               return -ENODEV;
+
+       ret = ncsi_send_netlink_rsp(nr, np, nc);
+
+       return ret;
+}
+
 static struct ncsi_rsp_handler {
        unsigned char   type;
        int             payload;
@@ -1043,6 +1078,17 @@ int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev,
                netdev_warn(ndp->ndev.dev,
                            "NCSI: 'bad' packet ignored for type 0x%x\n",
                            hdr->type);
+
+               if (nr->flags == NCSI_REQ_FLAG_NETLINK_DRIVEN) {
+                       if (ret == -EPERM)
+                               goto out_netlink;
+                       else
+                               ncsi_send_netlink_err(ndp->ndev.dev,
+                                                     nr->snd_seq,
+                                                     nr->snd_portid,
+                                                     &nr->nlhdr,
+                                                     ret);
+               }
                goto out;
        }
 
@@ -1052,6 +1098,17 @@ int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev,
                netdev_err(ndp->ndev.dev,
                           "NCSI: Handler for packet type 0x%x returned %d\n",
                           hdr->type, ret);
+
+out_netlink:
+       if (nr->flags == NCSI_REQ_FLAG_NETLINK_DRIVEN) {
+               ret = ncsi_rsp_handler_netlink(nr);
+               if (ret) {
+                       netdev_err(ndp->ndev.dev,
+                                  "NCSI: Netlink handler for packet type 0x%x returned %d\n",
+                                  hdr->type, ret);
+               }
+       }
+
 out:
        ncsi_free_request(nr);
        return ret;