1922a46b8c
If a user unbinds and re-binds a NC-SI aware driver the kernel will
attempt to register the netlink interface at runtime. The structure is
marked __ro_after_init so registration fails spectacularly at this point.
# echo 1e660000.ethernet > /sys/bus/platform/drivers/ftgmac100/unbind
# echo 1e660000.ethernet > /sys/bus/platform/drivers/ftgmac100/bind
ftgmac100 1e660000.ethernet: Read MAC address 52:54:00:12:34:56 from chip
ftgmac100 1e660000.ethernet: Using NCSI interface
8<--- cut here ---
Unable to handle kernel paging request at virtual address 80a8f858
pgd = 8c768dd6
[80a8f858] *pgd=80a0841e(bad)
Internal error: Oops: 80d [#1] SMP ARM
CPU: 0 PID: 116 Comm: sh Not tainted 5.10.0-rc3-next-20201111-00003-gdd25b227ec1e #51
Hardware name: Generic DT based system
PC is at genl_register_family+0x1f8/0x6d4
LR is at 0xff26ffff
pc : [<8073f930>] lr : [<ff26ffff>] psr: 20000153
sp : 8553bc80 ip : 81406244 fp : 8553bd04
r10: 8085d12c r9 : 80a8f73c r8 : 85739000
r7 : 00000017 r6 : 80a8f860 r5 : 80c8ab98 r4 : 80a8f858
r3 : 00000000 r2 : 00000000 r1 : 81406130 r0 : 00000017
Flags: nzCv IRQs on FIQs off Mode SVC_32 ISA ARM Segment none
Control: 00c5387d Table: 85524008 DAC: 00000051
Process sh (pid: 116, stack limit = 0x1f1988d6)
...
Backtrace:
[<8073f738>] (genl_register_family) from [<80860ac0>] (ncsi_init_netlink+0x20/0x48)
r10:8085d12c r9:80c8fb0c r8:85739000 r7:00000000 r6:81218000 r5:85739000
r4:8121c000
[<80860aa0>] (ncsi_init_netlink) from [<8085d740>] (ncsi_register_dev+0x1b0/0x210)
r5:8121c400 r4:8121c000
[<8085d590>] (ncsi_register_dev) from [<805a8060>] (ftgmac100_probe+0x6e0/0x778)
r10:00000004 r9:80950228 r8:8115bc10 r7:8115ab00 r6:9eae2c24 r5:813b6f88
r4:85739000
[<805a7980>] (ftgmac100_probe) from [<805355ec>] (platform_drv_probe+0x58/0xa8)
r9:80c76bb0 r8:00000000 r7:80cd4974 r6:80c76bb0 r5:8115bc10 r4:00000000
[<80535594>] (platform_drv_probe) from [<80532d58>] (really_probe+0x204/0x514)
r7:80cd4974 r6:00000000 r5:80cd4868 r4:8115bc10
Jakub pointed out that ncsi_register_dev is obviously broken, because
there is only one family so it would never work if there was more than
one ncsi netdev.
Fix the crash by registering the netlink family once on boot, and drop
the code to unregister it.
Fixes: 955dc68cb9
("net/ncsi: Add generic netlink family")
Signed-off-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
Link: https://lore.kernel.org/r/20201112061210.914621-1-joel@jms.id.au
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
774 lines
18 KiB
C
774 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright Samuel Mendoza-Jonas, IBM Corporation 2018.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/etherdevice.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-pkt.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 },
|
|
[NCSI_ATTR_DATA] = { .type = NLA_BINARY, .len = 2048 },
|
|
[NCSI_ATTR_MULTI_FLAG] = { .type = NLA_FLAG },
|
|
[NCSI_ATTR_PACKAGE_MASK] = { .type = NLA_U32 },
|
|
[NCSI_ATTR_CHANNEL_MASK] = { .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 (nc == nc->package->preferred_channel)
|
|
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_noflag(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_noflag(skb, NCSI_PKG_ATTR);
|
|
if (!pnest)
|
|
return -ENOMEM;
|
|
nla_put_u32(skb, NCSI_PKG_ATTR_ID, np->id);
|
|
if ((0x1 << np->id) == ndp->package_whitelist)
|
|
nla_put_flag(skb, NCSI_PKG_ATTR_FORCED);
|
|
cnest = nla_nest_start_noflag(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_noflag(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_noflag(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_deprecated(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_noflag(skb, NCSI_ATTR_PACKAGE_LIST);
|
|
if (!attr) {
|
|
rc = -EMSGSIZE;
|
|
goto err;
|
|
}
|
|
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;
|
|
|
|
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 */
|
|
return -ERANGE;
|
|
}
|
|
|
|
channel = NULL;
|
|
if (info->attrs[NCSI_ATTR_CHANNEL_ID]) {
|
|
channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]);
|
|
NCSI_FOR_EACH_CHANNEL(package, nc)
|
|
if (nc->id == channel_id) {
|
|
channel = nc;
|
|
break;
|
|
}
|
|
if (!channel) {
|
|
netdev_info(ndp->ndev.dev,
|
|
"NCSI: Channel %u does not exist!\n",
|
|
channel_id);
|
|
return -ERANGE;
|
|
}
|
|
}
|
|
|
|
spin_lock_irqsave(&ndp->lock, flags);
|
|
ndp->package_whitelist = 0x1 << package->id;
|
|
ndp->multi_package = false;
|
|
spin_unlock_irqrestore(&ndp->lock, flags);
|
|
|
|
spin_lock_irqsave(&package->lock, flags);
|
|
package->multi_channel = false;
|
|
if (channel) {
|
|
package->channel_whitelist = 0x1 << channel->id;
|
|
package->preferred_channel = channel;
|
|
} else {
|
|
/* Allow any channel */
|
|
package->channel_whitelist = UINT_MAX;
|
|
package->preferred_channel = NULL;
|
|
}
|
|
spin_unlock_irqrestore(&package->lock, flags);
|
|
|
|
if (channel)
|
|
netdev_info(ndp->ndev.dev,
|
|
"Set package 0x%x, channel 0x%x as preferred\n",
|
|
package_id, channel_id);
|
|
else
|
|
netdev_info(ndp->ndev.dev, "Set package 0x%x as preferred\n",
|
|
package_id);
|
|
|
|
/* Update channel configuration */
|
|
if (!(ndp->flags & NCSI_DEV_RESET))
|
|
ncsi_reset_dev(&ndp->ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info)
|
|
{
|
|
struct ncsi_dev_priv *ndp;
|
|
struct ncsi_package *np;
|
|
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;
|
|
|
|
/* Reset any whitelists and disable multi mode */
|
|
spin_lock_irqsave(&ndp->lock, flags);
|
|
ndp->package_whitelist = UINT_MAX;
|
|
ndp->multi_package = false;
|
|
spin_unlock_irqrestore(&ndp->lock, flags);
|
|
|
|
NCSI_FOR_EACH_PACKAGE(ndp, np) {
|
|
spin_lock_irqsave(&np->lock, flags);
|
|
np->multi_channel = false;
|
|
np->channel_whitelist = UINT_MAX;
|
|
np->preferred_channel = NULL;
|
|
spin_unlock_irqrestore(&np->lock, flags);
|
|
}
|
|
netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n");
|
|
|
|
/* Update channel configuration */
|
|
if (!(ndp->flags & NCSI_DEV_RESET))
|
|
ncsi_reset_dev(&ndp->ndev);
|
|
|
|
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 int ncsi_set_package_mask_nl(struct sk_buff *msg,
|
|
struct genl_info *info)
|
|
{
|
|
struct ncsi_dev_priv *ndp;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
if (!info || !info->attrs)
|
|
return -EINVAL;
|
|
|
|
if (!info->attrs[NCSI_ATTR_IFINDEX])
|
|
return -EINVAL;
|
|
|
|
if (!info->attrs[NCSI_ATTR_PACKAGE_MASK])
|
|
return -EINVAL;
|
|
|
|
ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)),
|
|
nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX]));
|
|
if (!ndp)
|
|
return -ENODEV;
|
|
|
|
spin_lock_irqsave(&ndp->lock, flags);
|
|
if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) {
|
|
if (ndp->flags & NCSI_DEV_HWA) {
|
|
ndp->multi_package = true;
|
|
rc = 0;
|
|
} else {
|
|
netdev_err(ndp->ndev.dev,
|
|
"NCSI: Can't use multiple packages without HWA\n");
|
|
rc = -EPERM;
|
|
}
|
|
} else {
|
|
ndp->multi_package = false;
|
|
rc = 0;
|
|
}
|
|
|
|
if (!rc)
|
|
ndp->package_whitelist =
|
|
nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_MASK]);
|
|
spin_unlock_irqrestore(&ndp->lock, flags);
|
|
|
|
if (!rc) {
|
|
/* Update channel configuration */
|
|
if (!(ndp->flags & NCSI_DEV_RESET))
|
|
ncsi_reset_dev(&ndp->ndev);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int ncsi_set_channel_mask_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;
|
|
|
|
if (!info->attrs[NCSI_ATTR_CHANNEL_MASK])
|
|
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;
|
|
NCSI_FOR_EACH_PACKAGE(ndp, np)
|
|
if (np->id == package_id) {
|
|
package = np;
|
|
break;
|
|
}
|
|
if (!package)
|
|
return -ERANGE;
|
|
|
|
spin_lock_irqsave(&package->lock, flags);
|
|
|
|
channel = NULL;
|
|
if (info->attrs[NCSI_ATTR_CHANNEL_ID]) {
|
|
channel_id = nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_ID]);
|
|
NCSI_FOR_EACH_CHANNEL(np, nc)
|
|
if (nc->id == channel_id) {
|
|
channel = nc;
|
|
break;
|
|
}
|
|
if (!channel) {
|
|
spin_unlock_irqrestore(&package->lock, flags);
|
|
return -ERANGE;
|
|
}
|
|
netdev_dbg(ndp->ndev.dev,
|
|
"NCSI: Channel %u set as preferred channel\n",
|
|
channel->id);
|
|
}
|
|
|
|
package->channel_whitelist =
|
|
nla_get_u32(info->attrs[NCSI_ATTR_CHANNEL_MASK]);
|
|
if (package->channel_whitelist == 0)
|
|
netdev_dbg(ndp->ndev.dev,
|
|
"NCSI: Package %u set to all channels disabled\n",
|
|
package->id);
|
|
|
|
package->preferred_channel = channel;
|
|
|
|
if (nla_get_flag(info->attrs[NCSI_ATTR_MULTI_FLAG])) {
|
|
package->multi_channel = true;
|
|
netdev_info(ndp->ndev.dev,
|
|
"NCSI: Multi-channel enabled on package %u\n",
|
|
package_id);
|
|
} else {
|
|
package->multi_channel = false;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&package->lock, flags);
|
|
|
|
/* Update channel configuration */
|
|
if (!(ndp->flags & NCSI_DEV_RESET))
|
|
ncsi_reset_dev(&ndp->ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct genl_small_ops ncsi_ops[] = {
|
|
{
|
|
.cmd = NCSI_CMD_PKG_INFO,
|
|
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
|
|
.doit = ncsi_pkg_info_nl,
|
|
.dumpit = ncsi_pkg_info_all_nl,
|
|
.flags = 0,
|
|
},
|
|
{
|
|
.cmd = NCSI_CMD_SET_INTERFACE,
|
|
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
|
|
.doit = ncsi_set_interface_nl,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NCSI_CMD_CLEAR_INTERFACE,
|
|
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
|
|
.doit = ncsi_clear_interface_nl,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NCSI_CMD_SEND_CMD,
|
|
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
|
|
.doit = ncsi_send_cmd_nl,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NCSI_CMD_SET_PACKAGE_MASK,
|
|
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
|
|
.doit = ncsi_set_package_mask_nl,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
{
|
|
.cmd = NCSI_CMD_SET_CHANNEL_MASK,
|
|
.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
|
|
.doit = ncsi_set_channel_mask_nl,
|
|
.flags = GENL_ADMIN_PERM,
|
|
},
|
|
};
|
|
|
|
static struct genl_family ncsi_genl_family __ro_after_init = {
|
|
.name = "NCSI",
|
|
.version = 0,
|
|
.maxattr = NCSI_ATTR_MAX,
|
|
.policy = ncsi_genl_policy,
|
|
.module = THIS_MODULE,
|
|
.small_ops = ncsi_ops,
|
|
.n_small_ops = ARRAY_SIZE(ncsi_ops),
|
|
};
|
|
|
|
static int __init ncsi_init_netlink(void)
|
|
{
|
|
return genl_register_family(&ncsi_genl_family);
|
|
}
|
|
subsys_initcall(ncsi_init_netlink);
|