123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- /*
- * Copyright Samuel Mendoza-Jonas, IBM Corporation 2018.
- *
- * 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/if_arp.h>
- #include <linux/rtnetlink.h>
- #include <linux/etherdevice.h>
- #include <linux/module.h>
- #include <net/genetlink.h>
- #include <net/ncsi.h>
- #include <linux/skbuff.h>
- #include <net/sock.h>
- #include <uapi/linux/ncsi.h>
- #include "internal.h"
- #include "ncsi-netlink.h"
- static struct genl_family ncsi_genl_family;
- static const struct nla_policy ncsi_genl_policy[NCSI_ATTR_MAX + 1] = {
- [NCSI_ATTR_IFINDEX] = { .type = NLA_U32 },
- [NCSI_ATTR_PACKAGE_LIST] = { .type = NLA_NESTED },
- [NCSI_ATTR_PACKAGE_ID] = { .type = NLA_U32 },
- [NCSI_ATTR_CHANNEL_ID] = { .type = NLA_U32 },
- };
- static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex)
- {
- struct ncsi_dev_priv *ndp;
- struct net_device *dev;
- struct ncsi_dev *nd;
- struct ncsi_dev;
- if (!net)
- return NULL;
- dev = dev_get_by_index(net, ifindex);
- if (!dev) {
- pr_err("NCSI netlink: No device for ifindex %u\n", ifindex);
- return NULL;
- }
- nd = ncsi_find_dev(dev);
- ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL;
- dev_put(dev);
- return ndp;
- }
- static int ncsi_write_channel_info(struct sk_buff *skb,
- struct ncsi_dev_priv *ndp,
- struct ncsi_channel *nc)
- {
- struct ncsi_channel_vlan_filter *ncf;
- struct ncsi_channel_mode *m;
- struct nlattr *vid_nest;
- int i;
- nla_put_u32(skb, NCSI_CHANNEL_ATTR_ID, nc->id);
- m = &nc->modes[NCSI_MODE_LINK];
- nla_put_u32(skb, NCSI_CHANNEL_ATTR_LINK_STATE, m->data[2]);
- if (nc->state == NCSI_CHANNEL_ACTIVE)
- nla_put_flag(skb, NCSI_CHANNEL_ATTR_ACTIVE);
- if (ndp->force_channel == nc)
- nla_put_flag(skb, NCSI_CHANNEL_ATTR_FORCED);
- nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MAJOR, nc->version.version);
- nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MINOR, nc->version.alpha2);
- nla_put_string(skb, NCSI_CHANNEL_ATTR_VERSION_STR, nc->version.fw_name);
- vid_nest = nla_nest_start(skb, NCSI_CHANNEL_ATTR_VLAN_LIST);
- if (!vid_nest)
- return -ENOMEM;
- ncf = &nc->vlan_filter;
- i = -1;
- while ((i = find_next_bit((void *)&ncf->bitmap, ncf->n_vids,
- i + 1)) < ncf->n_vids) {
- if (ncf->vids[i])
- nla_put_u16(skb, NCSI_CHANNEL_ATTR_VLAN_ID,
- ncf->vids[i]);
- }
- nla_nest_end(skb, vid_nest);
- return 0;
- }
- static int ncsi_write_package_info(struct sk_buff *skb,
- struct ncsi_dev_priv *ndp, unsigned int id)
- {
- struct nlattr *pnest, *cnest, *nest;
- struct ncsi_package *np;
- struct ncsi_channel *nc;
- bool found;
- int rc;
- if (id > ndp->package_num - 1) {
- netdev_info(ndp->ndev.dev, "NCSI: No package with id %u\n", id);
- return -ENODEV;
- }
- found = false;
- NCSI_FOR_EACH_PACKAGE(ndp, np) {
- if (np->id != id)
- continue;
- pnest = nla_nest_start(skb, NCSI_PKG_ATTR);
- if (!pnest)
- return -ENOMEM;
- nla_put_u32(skb, NCSI_PKG_ATTR_ID, np->id);
- if (ndp->force_package == np)
- nla_put_flag(skb, NCSI_PKG_ATTR_FORCED);
- cnest = nla_nest_start(skb, NCSI_PKG_ATTR_CHANNEL_LIST);
- if (!cnest) {
- nla_nest_cancel(skb, pnest);
- return -ENOMEM;
- }
- NCSI_FOR_EACH_CHANNEL(np, nc) {
- nest = nla_nest_start(skb, NCSI_CHANNEL_ATTR);
- if (!nest) {
- nla_nest_cancel(skb, cnest);
- nla_nest_cancel(skb, pnest);
- return -ENOMEM;
- }
- rc = ncsi_write_channel_info(skb, ndp, nc);
- if (rc) {
- nla_nest_cancel(skb, nest);
- nla_nest_cancel(skb, cnest);
- nla_nest_cancel(skb, pnest);
- return rc;
- }
- nla_nest_end(skb, nest);
- }
- nla_nest_end(skb, cnest);
- nla_nest_end(skb, pnest);
- found = true;
- }
- if (!found)
- return -ENODEV;
- return 0;
- }
- static int ncsi_pkg_info_nl(struct sk_buff *msg, struct genl_info *info)
- {
- struct ncsi_dev_priv *ndp;
- unsigned int package_id;
- struct sk_buff *skb;
- struct nlattr *attr;
- void *hdr;
- int rc;
- if (!info || !info->attrs)
- return -EINVAL;
- if (!info->attrs[NCSI_ATTR_IFINDEX])
- return -EINVAL;
- if (!info->attrs[NCSI_ATTR_PACKAGE_ID])
- return -EINVAL;
- ndp = ndp_from_ifindex(genl_info_net(info),
- nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX]));
- if (!ndp)
- return -ENODEV;
- skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
- if (!skb)
- return -ENOMEM;
- hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq,
- &ncsi_genl_family, 0, NCSI_CMD_PKG_INFO);
- if (!hdr) {
- kfree_skb(skb);
- return -EMSGSIZE;
- }
- package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]);
- attr = nla_nest_start(skb, NCSI_ATTR_PACKAGE_LIST);
- if (!attr) {
- kfree_skb(skb);
- return -EMSGSIZE;
- }
- rc = ncsi_write_package_info(skb, ndp, package_id);
- if (rc) {
- nla_nest_cancel(skb, attr);
- goto err;
- }
- nla_nest_end(skb, attr);
- genlmsg_end(skb, hdr);
- return genlmsg_reply(skb, info);
- err:
- kfree_skb(skb);
- return rc;
- }
- static int ncsi_pkg_info_all_nl(struct sk_buff *skb,
- struct netlink_callback *cb)
- {
- struct nlattr *attrs[NCSI_ATTR_MAX + 1];
- struct ncsi_package *np, *package;
- struct ncsi_dev_priv *ndp;
- unsigned int package_id;
- struct nlattr *attr;
- void *hdr;
- int rc;
- rc = genlmsg_parse(cb->nlh, &ncsi_genl_family, attrs, NCSI_ATTR_MAX,
- ncsi_genl_policy, NULL);
- if (rc)
- return rc;
- if (!attrs[NCSI_ATTR_IFINDEX])
- return -EINVAL;
- ndp = ndp_from_ifindex(get_net(sock_net(skb->sk)),
- nla_get_u32(attrs[NCSI_ATTR_IFINDEX]));
- if (!ndp)
- return -ENODEV;
- package_id = cb->args[0];
- package = NULL;
- NCSI_FOR_EACH_PACKAGE(ndp, np)
- if (np->id == package_id)
- package = np;
- if (!package)
- return 0; /* done */
- hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
- &ncsi_genl_family, NLM_F_MULTI, NCSI_CMD_PKG_INFO);
- if (!hdr) {
- rc = -EMSGSIZE;
- goto err;
- }
- attr = nla_nest_start(skb, NCSI_ATTR_PACKAGE_LIST);
- rc = ncsi_write_package_info(skb, ndp, package->id);
- if (rc) {
- nla_nest_cancel(skb, attr);
- goto err;
- }
- nla_nest_end(skb, attr);
- genlmsg_end(skb, hdr);
- cb->args[0] = package_id + 1;
- return skb->len;
- err:
- genlmsg_cancel(skb, hdr);
- return rc;
- }
- static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info)
- {
- struct ncsi_package *np, *package;
- struct ncsi_channel *nc, *channel;
- u32 package_id, channel_id;
- struct ncsi_dev_priv *ndp;
- unsigned long flags;
- if (!info || !info->attrs)
- return -EINVAL;
- if (!info->attrs[NCSI_ATTR_IFINDEX])
- return -EINVAL;
- if (!info->attrs[NCSI_ATTR_PACKAGE_ID])
- return -EINVAL;
- ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)),
- nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX]));
- if (!ndp)
- return -ENODEV;
- package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]);
- package = NULL;
- spin_lock_irqsave(&ndp->lock, flags);
- NCSI_FOR_EACH_PACKAGE(ndp, np)
- if (np->id == package_id)
- package = np;
- if (!package) {
- /* The user has set a package that does not exist */
- spin_unlock_irqrestore(&ndp->lock, flags);
- return -ERANGE;
- }
- channel = NULL;
- if (!info->attrs[NCSI_ATTR_CHANNEL_ID]) {
- /* Allow any channel */
- channel_id = NCSI_RESERVED_CHANNEL;
- } else {
- channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]);
- NCSI_FOR_EACH_CHANNEL(package, nc)
- if (nc->id == channel_id)
- channel = nc;
- }
- if (channel_id != NCSI_RESERVED_CHANNEL && !channel) {
- /* The user has set a channel that does not exist on this
- * package
- */
- spin_unlock_irqrestore(&ndp->lock, flags);
- netdev_info(ndp->ndev.dev, "NCSI: Channel %u does not exist!\n",
- channel_id);
- return -ERANGE;
- }
- ndp->force_package = package;
- ndp->force_channel = channel;
- spin_unlock_irqrestore(&ndp->lock, flags);
- netdev_info(ndp->ndev.dev, "Set package 0x%x, channel 0x%x%s as preferred\n",
- package_id, channel_id,
- channel_id == NCSI_RESERVED_CHANNEL ? " (any)" : "");
- /* Bounce the NCSI channel to set changes */
- ncsi_stop_dev(&ndp->ndev);
- ncsi_start_dev(&ndp->ndev);
- return 0;
- }
- static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info)
- {
- struct ncsi_dev_priv *ndp;
- unsigned long flags;
- if (!info || !info->attrs)
- return -EINVAL;
- if (!info->attrs[NCSI_ATTR_IFINDEX])
- return -EINVAL;
- ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)),
- nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX]));
- if (!ndp)
- return -ENODEV;
- /* Clear any override */
- spin_lock_irqsave(&ndp->lock, flags);
- ndp->force_package = NULL;
- ndp->force_channel = NULL;
- spin_unlock_irqrestore(&ndp->lock, flags);
- netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n");
- /* Bounce the NCSI channel to set changes */
- ncsi_stop_dev(&ndp->ndev);
- ncsi_start_dev(&ndp->ndev);
- return 0;
- }
- static const struct genl_ops ncsi_ops[] = {
- {
- .cmd = NCSI_CMD_PKG_INFO,
- .policy = ncsi_genl_policy,
- .doit = ncsi_pkg_info_nl,
- .dumpit = ncsi_pkg_info_all_nl,
- .flags = 0,
- },
- {
- .cmd = NCSI_CMD_SET_INTERFACE,
- .policy = ncsi_genl_policy,
- .doit = ncsi_set_interface_nl,
- .flags = GENL_ADMIN_PERM,
- },
- {
- .cmd = NCSI_CMD_CLEAR_INTERFACE,
- .policy = ncsi_genl_policy,
- .doit = ncsi_clear_interface_nl,
- .flags = GENL_ADMIN_PERM,
- },
- };
- static struct genl_family ncsi_genl_family __ro_after_init = {
- .name = "NCSI",
- .version = 0,
- .maxattr = NCSI_ATTR_MAX,
- .module = THIS_MODULE,
- .ops = ncsi_ops,
- .n_ops = ARRAY_SIZE(ncsi_ops),
- };
- int ncsi_init_netlink(struct net_device *dev)
- {
- int rc;
- rc = genl_register_family(&ncsi_genl_family);
- if (rc)
- netdev_err(dev, "ncsi: failed to register netlink family\n");
- return rc;
- }
- int ncsi_unregister_netlink(struct net_device *dev)
- {
- int rc;
- rc = genl_unregister_family(&ncsi_genl_family);
- if (rc)
- netdev_err(dev, "ncsi: failed to unregister netlink family\n");
- return rc;
- }
|