mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
net/ncsi: Extend NC-SI Netlink interface to allow user space to send NC-SI command
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>
This commit is contained in:
parent
6384e48323
commit
9771b8ccdf
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -14,6 +14,18 @@
|
||||
|
||||
#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);
|
||||
|
||||
|
@ -16,9 +16,11 @@
|
||||
#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;
|
||||
|
Loading…
Reference in New Issue
Block a user