linux-brain/net/tsn/genl_tsn.c

3697 lines
87 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/* Copyright 2017-2019 NXP */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/if_vlan.h>
#include <net/genetlink.h>
#include <net/netlink.h>
#include <linux/version.h>
#include <net/tsn.h>
#define NLA_PARSE_NESTED(a, b, c, d) \
nla_parse_nested_deprecated(a, b, c, d, NULL)
#define NLA_PUT_U64(a, b, c) nla_put_u64_64bit(a, b, c, NLA_U64)
static struct genl_family tsn_family;
LIST_HEAD(port_list);
static const struct nla_policy tsn_cmd_policy[TSN_CMD_ATTR_MAX + 1] = {
[TSN_CMD_ATTR_MESG] = { .type = NLA_STRING },
[TSN_CMD_ATTR_DATA] = { .type = NLA_S32 },
[TSN_ATTR_IFNAME] = { .type = NLA_STRING },
[TSN_ATTR_PORT_NUMBER] = { .type = NLA_U8 },
[TSN_ATTR_CAP] = { .type = NLA_NESTED },
[TSN_ATTR_QBV] = { .type = NLA_NESTED },
[TSN_ATTR_STREAM_IDENTIFY] = { .type = NLA_NESTED },
[TSN_ATTR_QCI_SP] = { .type = NLA_NESTED },
[TSN_ATTR_QCI_SFI] = { .type = NLA_NESTED },
[TSN_ATTR_QCI_SGI] = { .type = NLA_NESTED },
[TSN_ATTR_QCI_FMI] = { .type = NLA_NESTED },
[TSN_ATTR_CBS] = { .type = NLA_NESTED },
[TSN_ATTR_TSD] = { .type = NLA_NESTED },
[TSN_ATTR_QBU] = { .type = NLA_NESTED },
[TSN_ATTR_CT] = { .type = NLA_NESTED },
[TSN_ATTR_CBGEN] = { .type = NLA_NESTED },
[TSN_ATTR_CBREC] = { .type = NLA_NESTED },
[TSN_ATTR_CBSTAT] = { .type = NLA_NESTED },
[TSN_ATTR_DSCP] = { .type = NLA_NESTED },
};
static const struct nla_policy tsn_cap_policy[TSN_CAP_ATTR_MAX + 1] = {
[TSN_CAP_ATTR_QBV] = { .type = NLA_FLAG },
[TSN_CAP_ATTR_QCI] = { .type = NLA_FLAG },
[TSN_CAP_ATTR_QBU] = { .type = NLA_FLAG },
[TSN_CAP_ATTR_CBS] = { .type = NLA_FLAG },
[TSN_CAP_ATTR_CB] = { .type = NLA_FLAG },
[TSN_CAP_ATTR_TBS] = { .type = NLA_FLAG },
[TSN_CAP_ATTR_CTH] = { .type = NLA_FLAG },
};
static const struct nla_policy qci_cap_policy[TSN_QCI_STREAM_ATTR_MAX + 1] = {
[TSN_QCI_STREAM_ATTR_MAX_SFI] = { .type = NLA_U32 },
[TSN_QCI_STREAM_ATTR_MAX_SGI] = { .type = NLA_U32 },
[TSN_QCI_STREAM_ATTR_MAX_FMI] = { .type = NLA_U32 },
[TSN_QCI_STREAM_ATTR_SLM] = { .type = NLA_U32 },
};
static const struct nla_policy ct_policy[TSN_CT_ATTR_MAX + 1] = {
[TSN_CT_ATTR_QUEUE_STATE] = { .type = NLA_U8 }
};
static const struct nla_policy cbgen_policy[TSN_CBGEN_ATTR_MAX + 1] = {
[TSN_CBGEN_ATTR_INDEX] = { .type = NLA_U32 },
[TSN_CBGEN_ATTR_PORT_MASK] = { .type = NLA_U8 },
[TSN_CBGEN_ATTR_SPLIT_MASK] = { .type = NLA_U8 },
[TSN_CBGEN_ATTR_SEQ_LEN] = { .type = NLA_U8 },
[TSN_CBGEN_ATTR_SEQ_NUM] = { .type = NLA_U32 },
};
static const struct nla_policy cbrec_policy[TSN_CBREC_ATTR_MAX + 1] = {
[TSN_CBREC_ATTR_INDEX] = { .type = NLA_U32 },
[TSN_CBREC_ATTR_SEQ_LEN] = { .type = NLA_U8 },
[TSN_CBREC_ATTR_HIS_LEN] = { .type = NLA_U8 },
[TSN_CBREC_ATTR_TAG_POP_EN] = { .type = NLA_FLAG },
};
static const struct nla_policy cbstat_policy[TSN_CBSTAT_ATTR_MAX + 1] = {
[TSN_CBSTAT_ATTR_INDEX] = { .type = NLA_U32 },
[TSN_CBSTAT_ATTR_GEN_REC] = { .type = NLA_U8 },
[TSN_CBSTAT_ATTR_ERR] = { .type = NLA_U8 },
[TSN_CBSTAT_ATTR_SEQ_NUM] = { .type = NLA_U32 },
[TSN_CBSTAT_ATTR_SEQ_LEN] = { .type = NLA_U8 },
[TSN_CBSTAT_ATTR_SPLIT_MASK] = { .type = NLA_U8 },
[TSN_CBSTAT_ATTR_PORT_MASK] = { .type = NLA_U8 },
[TSN_CBSTAT_ATTR_HIS_LEN] = { .type = NLA_U8 },
[TSN_CBSTAT_ATTR_SEQ_HIS] = { .type = NLA_U32 },
};
static const struct nla_policy qbu_policy[TSN_QBU_ATTR_MAX + 1] = {
[TSN_QBU_ATTR_ADMIN_STATE] = { .type = NLA_U8 },
[TSN_QBU_ATTR_HOLD_ADVANCE] = { .type = NLA_U32},
[TSN_QBU_ATTR_RELEASE_ADVANCE] = { .type = NLA_U32},
[TSN_QBU_ATTR_ACTIVE] = { .type = NLA_FLAG},
[TSN_QBU_ATTR_HOLD_REQUEST] = { .type = NLA_U8},
};
static const struct nla_policy cbs_policy[TSN_CBS_ATTR_MAX + 1] = {
[TSN_CBS_ATTR_TC_INDEX] = { .type = NLA_U8},
[TSN_CBS_ATTR_BW] = { .type = NLA_U8},
};
static const struct nla_policy tsd_policy[TSN_TSD_ATTR_MAX + 1] = {
[TSN_TSD_ATTR_ENABLE] = { .type = NLA_FLAG},
[TSN_TSD_ATTR_DISABLE] = { .type = NLA_FLAG},
[TSN_TSD_ATTR_PERIOD] = { .type = NLA_U32},
[TSN_TSD_ATTR_MAX_FRM_NUM] = { .type = NLA_U32},
[TSN_TSD_ATTR_CYCLE_NUM] = { .type = NLA_U32},
[TSN_TSD_ATTR_LOSS_STEPS] = { .type = NLA_U32},
[TSN_TSD_ATTR_SYN_IMME] = { .type = NLA_FLAG},
};
static const struct nla_policy qbv_policy[TSN_QBV_ATTR_MAX + 1] = {
[TSN_QBV_ATTR_ADMINENTRY] = { .type = NLA_NESTED},
[TSN_QBV_ATTR_OPERENTRY] = { .type = NLA_NESTED},
[TSN_QBV_ATTR_ENABLE] = { .type = NLA_FLAG},
[TSN_QBV_ATTR_DISABLE] = { .type = NLA_FLAG},
[TSN_QBV_ATTR_CONFIGCHANGE] = { .type = NLA_FLAG},
[TSN_QBV_ATTR_CONFIGCHANGETIME] = { .type = NLA_U64},
[TSN_QBV_ATTR_MAXSDU] = { .type = NLA_U32},
[TSN_QBV_ATTR_GRANULARITY] = { .type = NLA_U32},
[TSN_QBV_ATTR_CURRENTTIME] = { .type = NLA_U64},
[TSN_QBV_ATTR_CONFIGPENDING] = {.type = NLA_FLAG},
[TSN_QBV_ATTR_CONFIGCHANGEERROR] = { .type = NLA_U64},
[TSN_QBV_ATTR_LISTMAX] = { .type = NLA_U32},
};
static const struct nla_policy qbv_ctrl_policy[TSN_QBV_ATTR_CTRL_MAX + 1] = {
[TSN_QBV_ATTR_CTRL_LISTCOUNT] = { .type = NLA_U32},
[TSN_QBV_ATTR_CTRL_GATESTATE] = { .type = NLA_U8},
[TSN_QBV_ATTR_CTRL_CYCLETIME] = { .type = NLA_U32},
[TSN_QBV_ATTR_CTRL_CYCLETIMEEXT] = { .type = NLA_U32},
[TSN_QBV_ATTR_CTRL_BASETIME] = { .type = NLA_U64},
[TSN_QBV_ATTR_CTRL_LISTENTRY] = { .type = NLA_NESTED},
};
static const struct nla_policy qbv_entry_policy[TSN_QBV_ATTR_ENTRY_MAX + 1] = {
[TSN_QBV_ATTR_ENTRY_ID] = { .type = NLA_U32},
[TSN_QBV_ATTR_ENTRY_GC] = { .type = NLA_U8},
[TSN_QBV_ATTR_ENTRY_TM] = { .type = NLA_U32},
};
static const struct nla_policy cb_streamid_policy[TSN_STREAMID_ATTR_MAX + 1] = {
[TSN_STREAMID_ATTR_INDEX] = { .type = NLA_U32},
[TSN_STREAMID_ATTR_ENABLE] = { .type = NLA_FLAG},
[TSN_STREAMID_ATTR_DISABLE] = { .type = NLA_FLAG},
[TSN_STREAMID_ATTR_STREAM_HANDLE] = { .type = NLA_S32},
[TSN_STREAMID_ATTR_IFOP] = { .type = NLA_U32},
[TSN_STREAMID_ATTR_OFOP] = { .type = NLA_U32},
[TSN_STREAMID_ATTR_IFIP] = { .type = NLA_U32},
[TSN_STREAMID_ATTR_OFIP] = { .type = NLA_U32},
[TSN_STREAMID_ATTR_TYPE] = { .type = NLA_U8},
[TSN_STREAMID_ATTR_NDMAC] = { .type = NLA_U64},
[TSN_STREAMID_ATTR_NTAGGED] = { .type = NLA_U8},
[TSN_STREAMID_ATTR_NVID] = { .type = NLA_U16},
[TSN_STREAMID_ATTR_SMAC] = { .type = NLA_U64},
[TSN_STREAMID_ATTR_STAGGED] = { .type = NLA_U8},
[TSN_STREAMID_ATTR_SVID] = { .type = NLA_U16},
[TSN_STREAMID_ATTR_COUNTERS_PSI] = { .type = NLA_U64},
[TSN_STREAMID_ATTR_COUNTERS_PSO] = { .type = NLA_U64},
[TSN_STREAMID_ATTR_COUNTERS_PSPPI] = { .type = NLA_U64},
[TSN_STREAMID_ATTR_COUNTERS_PSPPO] = { .type = NLA_U64},
};
static const struct nla_policy qci_sfi_policy[TSN_QCI_SFI_ATTR_MAX + 1] = {
[TSN_QCI_SFI_ATTR_INDEX] = { .type = NLA_U32},
[TSN_QCI_SFI_ATTR_ENABLE] = { .type = NLA_FLAG},
[TSN_QCI_SFI_ATTR_DISABLE] = { .type = NLA_FLAG},
[TSN_QCI_SFI_ATTR_STREAM_HANDLE] = { .type = NLA_S32},
[TSN_QCI_SFI_ATTR_PRIO_SPEC] = { .type = NLA_S8},
[TSN_QCI_SFI_ATTR_GATE_ID] = { .type = NLA_U32},
[TSN_QCI_SFI_ATTR_FILTER_TYPE] = { .type = NLA_U8},
[TSN_QCI_SFI_ATTR_FLOW_ID] = { .type = NLA_S32},
[TSN_QCI_SFI_ATTR_MAXSDU] = { .type = NLA_U16},
[TSN_QCI_SFI_ATTR_COUNTERS] = {
.len = sizeof(struct tsn_qci_psfp_sfi_counters)},
[TSN_QCI_SFI_ATTR_OVERSIZE_ENABLE] = { .type = NLA_FLAG},
[TSN_QCI_SFI_ATTR_OVERSIZE] = { .type = NLA_FLAG},
};
static const struct nla_policy qci_sgi_policy[] = {
[TSN_QCI_SGI_ATTR_INDEX] = { .type = NLA_U32},
[TSN_QCI_SGI_ATTR_ENABLE] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_DISABLE] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_CONFCHANGE] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_IRXEN] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_IRX] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_OEXEN] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_OEX] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_ADMINENTRY] = { .type = NLA_NESTED},
[TSN_QCI_SGI_ATTR_OPERENTRY] = { .type = NLA_NESTED},
[TSN_QCI_SGI_ATTR_CCTIME] = { .type = NLA_U64},
[TSN_QCI_SGI_ATTR_TICKG] = { .type = NLA_U32},
[TSN_QCI_SGI_ATTR_CUTIME] = { .type = NLA_U64},
[TSN_QCI_SGI_ATTR_CPENDING] = { .type = NLA_FLAG},
[TSN_QCI_SGI_ATTR_CCERROR] = { .type = NLA_U64},
};
static const struct nla_policy qci_sgi_ctrl_policy[] = {
[TSN_SGI_ATTR_CTRL_INITSTATE] = { .type = NLA_FLAG},
[TSN_SGI_ATTR_CTRL_LEN] = { .type = NLA_U8},
[TSN_SGI_ATTR_CTRL_CYTIME] = { .type = NLA_U32},
[TSN_SGI_ATTR_CTRL_CYTIMEEX] = { .type = NLA_U32},
[TSN_SGI_ATTR_CTRL_BTIME] = { .type = NLA_U64},
[TSN_SGI_ATTR_CTRL_INITIPV] = { .type = NLA_S8},
[TSN_SGI_ATTR_CTRL_GCLENTRY] = { .type = NLA_NESTED},
};
static const struct nla_policy qci_sgi_gcl_policy[] = {
[TSN_SGI_ATTR_GCL_GATESTATE] = { .type = NLA_FLAG},
[TSN_SGI_ATTR_GCL_IPV] = { .type = NLA_S8},
[TSN_SGI_ATTR_GCL_INTERVAL] = { .type = NLA_U32},
[TSN_SGI_ATTR_GCL_OCTMAX] = { .type = NLA_U32},
};
static const struct nla_policy qci_fmi_policy[] = {
[TSN_QCI_FMI_ATTR_INDEX] = { .type = NLA_U32},
[TSN_QCI_FMI_ATTR_ENABLE] = { .type = NLA_FLAG},
[TSN_QCI_FMI_ATTR_DISABLE] = { .type = NLA_FLAG},
[TSN_QCI_FMI_ATTR_CIR] = { .type = NLA_U32},
[TSN_QCI_FMI_ATTR_CBS] = { .type = NLA_U32},
[TSN_QCI_FMI_ATTR_EIR] = { .type = NLA_U32},
[TSN_QCI_FMI_ATTR_EBS] = { .type = NLA_U32},
[TSN_QCI_FMI_ATTR_CF] = { .type = NLA_FLAG},
[TSN_QCI_FMI_ATTR_CM] = { .type = NLA_FLAG},
[TSN_QCI_FMI_ATTR_DROPYL] = { .type = NLA_FLAG},
[TSN_QCI_FMI_ATTR_MAREDEN] = { .type = NLA_FLAG},
[TSN_QCI_FMI_ATTR_MARED] = { .type = NLA_FLAG},
[TSN_QCI_FMI_ATTR_COUNTERS] = {
.len = sizeof(struct tsn_qci_psfp_fmi_counters)},
};
static const struct nla_policy dscp_policy[] = {
[TSN_DSCP_ATTR_INDEX] = { .type = NLA_U32},
[TSN_DSCP_ATTR_DISABLE] = { .type = NLA_FLAG},
[TSN_DSCP_ATTR_COS] = { .type = NLA_U8},
[TSN_DSCP_ATTR_DPL] = { .type = NLA_U8},
};
static ATOMIC_NOTIFIER_HEAD(tsn_notif_chain);
/**
* register_tsn_notifier - Register notifier
* @nb: notifier_block
*
* Register switch device notifier.
*/
int register_tsn_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&tsn_notif_chain, nb);
}
EXPORT_SYMBOL_GPL(register_tsn_notifier);
/**
* unregister_tsn_notifier - Unregister notifier
* @nb: notifier_block
*
* Unregister switch device notifier.
*/
int unregister_tsn_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_unregister(&tsn_notif_chain, nb);
}
EXPORT_SYMBOL_GPL(unregister_tsn_notifier);
/**
* call_tsn_notifiers - Call notifiers
* @val: value passed unmodified to notifier function
* @dev: port device
* @info: notifier information data
*
* Call all network notifier blocks.
*/
int call_tsn_notifiers(unsigned long val, struct net_device *dev,
struct tsn_notifier_info *info)
{
info->dev = dev;
return atomic_notifier_call_chain(&tsn_notif_chain, val, info);
}
EXPORT_SYMBOL_GPL(call_tsn_notifiers);
struct tsn_port *tsn_get_port(struct net_device *ndev)
{
struct tsn_port *port;
bool tsn_found = false;
list_for_each_entry(port, &port_list, list) {
if (port->netdev == ndev) {
tsn_found = true;
break;
}
}
if (!tsn_found)
return NULL;
return port;
}
EXPORT_SYMBOL_GPL(tsn_get_port);
static int tsn_prepare_reply(struct genl_info *info, u8 cmd,
struct sk_buff **skbp, size_t size)
{
struct sk_buff *skb;
void *reply;
/* If new attributes are added, please revisit this allocation
*/
skb = genlmsg_new(size, GFP_KERNEL);
if (!skb)
return -ENOMEM;
if (!info) {
nlmsg_free(skb);
return -EINVAL;
}
reply = genlmsg_put_reply(skb, info, &tsn_family, 0, cmd);
if (!reply) {
nlmsg_free(skb);
return -EINVAL;
}
*skbp = skb;
return 0;
}
static int tsn_mk_reply(struct sk_buff *skb, int aggr, void *data, int len)
{
/* add a netlink attribute to a socket buffer */
return nla_put(skb, aggr, len, data);
}
static int tsn_send_reply(struct sk_buff *skb, struct genl_info *info)
{
struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb));
void *reply = genlmsg_data(genlhdr);
genlmsg_end(skb, reply);
return genlmsg_reply(skb, info);
}
static int cmd_attr_echo_message(struct genl_info *info)
{
struct nlattr *na;
char *msg;
struct sk_buff *rep_skb;
size_t size;
int ret;
na = info->attrs[TSN_CMD_ATTR_MESG];
if (!na)
return -EINVAL;
msg = (char *)nla_data(na);
pr_info("tsn generic netlink receive echo mesg %s\n", msg);
size = nla_total_size(strlen(msg) + 1);
ret = tsn_prepare_reply(info, TSN_CMD_REPLY, &rep_skb,
size + NLMSG_ALIGN(MAX_USER_SIZE));
if (ret < 0)
return ret;
ret = tsn_mk_reply(rep_skb, TSN_CMD_ATTR_MESG, msg, size);
if (ret < 0)
goto err;
return tsn_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
return ret;
}
static int cmd_attr_echo_data(struct genl_info *info)
{
struct nlattr *na;
s32 data;
struct sk_buff *rep_skb;
size_t size;
int ret;
/*read data */
na = info->attrs[TSN_CMD_ATTR_DATA];
if (!na)
return -EINVAL;
data = nla_get_s32(info->attrs[TSN_CMD_ATTR_DATA]);
pr_info("tsn generic netlink receive echo data %d\n", data);
/* send back */
size = nla_total_size(sizeof(s32));
ret = tsn_prepare_reply(info, TSN_CMD_REPLY, &rep_skb,
size + NLMSG_ALIGN(MAX_USER_SIZE));
if (ret < 0)
return ret;
/* netlink lib func */
ret = nla_put_s32(rep_skb, TSN_CMD_ATTR_DATA, data);
if (ret < 0)
goto err;
return tsn_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
return ret;
}
static int tsn_echo_cmd(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_CMD_ATTR_MESG])
return cmd_attr_echo_message(info);
else if (info->attrs[TSN_CMD_ATTR_DATA])
return cmd_attr_echo_data(info);
return -EINVAL;
}
static int tsn_simple_reply(struct genl_info *info, u32 cmd,
char *portname, s32 retvalue)
{
struct sk_buff *rep_skb;
size_t size;
int ret;
/* send back */
size = nla_total_size(strlen(portname) + 1);
size += nla_total_size(sizeof(s32));
ret = tsn_prepare_reply(info, cmd,
&rep_skb, size + NLMSG_ALIGN(MAX_USER_SIZE));
if (ret < 0)
return ret;
/* netlink lib func */
ret = nla_put_string(rep_skb, TSN_ATTR_IFNAME, portname);
if (ret < 0)
return ret;
ret = nla_put_s32(rep_skb, TSN_CMD_ATTR_DATA, retvalue);
if (ret < 0)
return ret;
return tsn_send_reply(rep_skb, info);
}
struct tsn_port *tsn_init_check(struct genl_info *info,
struct net_device **ndev)
{
struct nlattr *na;
char *portname;
struct net_device *netdev;
struct tsn_port *port;
bool tsn_found = false;
if (!info->attrs[TSN_ATTR_IFNAME]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
"no portname", -EINVAL);
return NULL;
}
na = info->attrs[TSN_ATTR_IFNAME];
portname = (char *)nla_data(na);
netdev = __dev_get_by_name(genl_info_net(info), portname);
if (!netdev) {
tsn_simple_reply(info, TSN_CMD_REPLY,
"error device", -ENODEV);
return NULL;
}
list_for_each_entry(port, &port_list, list) {
if (port->netdev == netdev) {
tsn_found = true;
break;
}
}
if (!tsn_found) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -ENODEV);
return NULL;
}
*ndev = netdev;
return port;
}
static int tsn_cap_get(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *rep_skb;
struct nlattr *tsn_cap_attr;
int ret;
u32 cap = 0;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port) {
ret = -ENODEV;
goto out;
}
tsnops = port->tsnops;
genlhdr = info->genlhdr;
if (!tsnops->get_capability) {
ret = -EOPNOTSUPP;
goto out;
}
cap = tsnops->get_capability(netdev);
if (cap < 0) {
ret = cap;
goto out;
}
/* Pad netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
goto out;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name)) {
ret = -EMSGSIZE;
goto err;
}
tsn_cap_attr = nla_nest_start_noflag(rep_skb, TSN_ATTR_CAP);
if (!tsn_cap_attr) {
ret = -EMSGSIZE;
goto err;
}
if (cap & TSN_CAP_QBV) {
if (nla_put_flag(rep_skb, TSN_CAP_ATTR_QBV))
goto err;
}
if (cap & TSN_CAP_QCI) {
if (nla_put_flag(rep_skb, TSN_CAP_ATTR_QCI))
goto err;
}
if (cap & TSN_CAP_QBU) {
if (nla_put_flag(rep_skb, TSN_CAP_ATTR_QBU))
goto err;
}
if (cap & TSN_CAP_CBS) {
if (nla_put_flag(rep_skb, TSN_CAP_ATTR_CBS))
goto err;
}
if (cap & TSN_CAP_CB) {
if (nla_put_flag(rep_skb, TSN_CAP_ATTR_CB))
goto err;
}
if (cap & TSN_CAP_TBS) {
if (nla_put_flag(rep_skb, TSN_CAP_ATTR_TBS))
goto err;
}
if (cap & TSN_CAP_CTH) {
if (nla_put_flag(rep_skb, TSN_CAP_ATTR_CTH))
goto err;
}
nla_nest_end(rep_skb, tsn_cap_attr);
tsn_send_reply(rep_skb, info);
return 0;
err:
nlmsg_free(rep_skb);
out:
if (ret < 0)
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
static int cmd_cb_streamid_set(struct genl_info *info)
{
struct nlattr *na, *sid[TSN_STREAMID_ATTR_MAX + 1];
u32 sid_index;
u8 iden_type = 1;
bool enable;
int ret;
struct net_device *netdev;
struct tsn_cb_streamid sidconf;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
memset(&sidconf, 0, sizeof(struct tsn_cb_streamid));
if (!info->attrs[TSN_ATTR_STREAM_IDENTIFY])
return -EINVAL;
na = info->attrs[TSN_ATTR_STREAM_IDENTIFY];
ret = NLA_PARSE_NESTED(sid, TSN_STREAMID_ATTR_MAX,
na, cb_streamid_policy);
if (ret)
return -EINVAL;
if (!sid[TSN_STREAMID_ATTR_INDEX])
return -EINVAL;
sid_index = nla_get_u32(sid[TSN_STREAMID_ATTR_INDEX]);
if (sid[TSN_STREAMID_ATTR_ENABLE])
enable = true;
else if (sid[TSN_STREAMID_ATTR_DISABLE])
enable = false;
else
return -EINVAL;
if (!enable)
goto loaddev;
if (sid[TSN_STREAMID_ATTR_TYPE])
iden_type = nla_get_u8(sid[TSN_STREAMID_ATTR_TYPE]);
else
return -EINVAL;
sidconf.type = iden_type;
switch (iden_type) {
case STREAMID_NULL:
if (!sid[TSN_STREAMID_ATTR_NDMAC] ||
!sid[TSN_STREAMID_ATTR_NTAGGED] ||
!sid[TSN_STREAMID_ATTR_NVID]) {
return -EINVAL;
}
sidconf.para.nid.dmac =
nla_get_u64(sid[TSN_STREAMID_ATTR_NDMAC]);
sidconf.para.nid.tagged =
nla_get_u8(sid[TSN_STREAMID_ATTR_NTAGGED]);
sidconf.para.nid.vid =
nla_get_u16(sid[TSN_STREAMID_ATTR_NVID]);
break;
case STREAMID_SMAC_VLAN:
/* TODO: not supportted yet */
if (!sid[TSN_STREAMID_ATTR_SMAC] ||
!sid[TSN_STREAMID_ATTR_STAGGED] ||
!sid[TSN_STREAMID_ATTR_SVID]) {
return -EINVAL;
}
sidconf.para.sid.smac =
nla_get_u64(sid[TSN_STREAMID_ATTR_SMAC]);
sidconf.para.sid.tagged =
nla_get_u8(sid[TSN_STREAMID_ATTR_STAGGED]);
sidconf.para.sid.vid =
nla_get_u16(sid[TSN_STREAMID_ATTR_SVID]);
break;
case STREAMID_DMAC_VLAN:
case STREAMID_IP:
default:
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (sid[TSN_STREAMID_ATTR_STREAM_HANDLE])
sidconf.handle =
nla_get_s32(sid[TSN_STREAMID_ATTR_STREAM_HANDLE]);
if (sid[TSN_STREAMID_ATTR_IFOP])
sidconf.ifac_oport = nla_get_u32(sid[TSN_STREAMID_ATTR_IFOP]);
if (sid[TSN_STREAMID_ATTR_OFOP])
sidconf.ofac_oport = nla_get_u32(sid[TSN_STREAMID_ATTR_OFOP]);
if (sid[TSN_STREAMID_ATTR_IFIP])
sidconf.ifac_iport = nla_get_u32(sid[TSN_STREAMID_ATTR_IFIP]);
if (sid[TSN_STREAMID_ATTR_OFIP])
sidconf.ofac_iport = nla_get_u32(sid[TSN_STREAMID_ATTR_OFIP]);
loaddev:
if (!tsnops->cb_streamid_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -EOPNOTSUPP;
}
ret = tsnops->cb_streamid_set(netdev, sid_index, enable, &sidconf);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
/* simple reply here. To be continue */
if (tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, 0))
return -1;
return 0;
}
static int tsn_cb_streamid_set(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_cb_streamid_set(info);
return 0;
}
return -1;
}
static int cmd_cb_streamid_get(struct genl_info *info)
{
struct nlattr *na, *sidattr, *sid[TSN_STREAMID_ATTR_MAX + 1];
u32 sid_index;
struct genlmsghdr *genlhdr;
struct sk_buff *rep_skb;
int ret, i;
int valid;
struct net_device *netdev;
struct tsn_cb_streamid sidconf;
struct tsn_cb_streamid_counters sidcounts;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
memset(&sidconf, 0, sizeof(struct tsn_cb_streamid));
memset(&sidcounts, 0, sizeof(struct tsn_cb_streamid_counters));
if (!info->attrs[TSN_ATTR_STREAM_IDENTIFY])
return -EINVAL;
na = info->attrs[TSN_ATTR_STREAM_IDENTIFY];
ret = NLA_PARSE_NESTED(sid, TSN_STREAMID_ATTR_MAX,
na, cb_streamid_policy);
if (ret)
return -EINVAL;
if (!sid[TSN_STREAMID_ATTR_INDEX])
return -EINVAL;
sid_index = nla_get_u32(sid[TSN_STREAMID_ATTR_INDEX]);
if (!tsnops->cb_streamid_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
ret = -EINVAL;
goto exit;
} else {
valid = tsnops->cb_streamid_get(netdev, sid_index, &sidconf);
if (valid < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, valid);
return valid;
}
}
/* send back */
genlhdr = info->genlhdr;
ret = tsn_prepare_reply(info, genlhdr->cmd, &rep_skb,
NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
/* input netlink the parameters */
sidattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_STREAM_IDENTIFY);
if (!sidattr) {
ret = -EINVAL;
goto err;
}
if (nla_put_u32(rep_skb, TSN_STREAMID_ATTR_INDEX, sid_index))
return -EMSGSIZE;
if (valid == 1) {
nla_put_flag(rep_skb, TSN_STREAMID_ATTR_ENABLE);
} else if (valid == 0) {
nla_put_flag(rep_skb, TSN_STREAMID_ATTR_DISABLE);
} else {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
goto err;
}
if (nla_put_s32(rep_skb,
TSN_STREAMID_ATTR_STREAM_HANDLE, sidconf.handle) ||
nla_put_u32(rep_skb, TSN_STREAMID_ATTR_IFOP, sidconf.ifac_oport) ||
nla_put_u32(rep_skb, TSN_STREAMID_ATTR_OFOP, sidconf.ofac_oport) ||
nla_put_u32(rep_skb, TSN_STREAMID_ATTR_IFIP, sidconf.ifac_iport) ||
nla_put_u32(rep_skb, TSN_STREAMID_ATTR_OFIP, sidconf.ofac_iport) ||
nla_put_u8(rep_skb, TSN_STREAMID_ATTR_TYPE, sidconf.type))
return -EMSGSIZE;
switch (sidconf.type) {
case STREAMID_NULL:
if (NLA_PUT_U64(rep_skb, TSN_STREAMID_ATTR_NDMAC,
sidconf.para.nid.dmac) ||
nla_put_u16(rep_skb, TSN_STREAMID_ATTR_NVID,
sidconf.para.nid.vid) ||
nla_put_u8(rep_skb, TSN_STREAMID_ATTR_NTAGGED,
sidconf.para.nid.tagged))
return -EMSGSIZE;
break;
case STREAMID_SMAC_VLAN:
if (NLA_PUT_U64(rep_skb, TSN_STREAMID_ATTR_SMAC,
sidconf.para.sid.smac) ||
nla_put_u16(rep_skb, TSN_STREAMID_ATTR_SVID,
sidconf.para.sid.vid) ||
nla_put_u8(rep_skb, TSN_STREAMID_ATTR_STAGGED,
sidconf.para.sid.tagged))
return -EMSGSIZE;
break;
case STREAMID_DMAC_VLAN:
case STREAMID_IP:
default:
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
goto err;
}
if (!tsnops->cb_streamid_counters_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
goto err;
} else {
ret = tsnops->cb_streamid_counters_get(netdev,
sid_index,
&sidcounts);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
goto err;
}
}
if (NLA_PUT_U64(rep_skb, TSN_STREAMID_ATTR_COUNTERS_PSI,
sidcounts.per_stream.input) ||
NLA_PUT_U64(rep_skb, TSN_STREAMID_ATTR_COUNTERS_PSO,
sidcounts.per_stream.output))
return -EMSGSIZE;
for (i = 0; i < 32; i++) {
if (NLA_PUT_U64(rep_skb, TSN_STREAMID_ATTR_COUNTERS_PSPPI,
sidcounts.per_streamport[i].input) ||
NLA_PUT_U64(rep_skb, TSN_STREAMID_ATTR_COUNTERS_PSPPO,
sidcounts.per_streamport[i].output))
return -EMSGSIZE;
}
nla_nest_end(rep_skb, sidattr);
/* end netlink input the parameters */
/* netlink lib func */
ret = nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name);
if (ret < 0)
goto err;
ret = nla_put_s32(rep_skb, TSN_CMD_ATTR_DATA, 0);
if (ret < 0)
goto err;
return tsn_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
exit:
return ret;
}
static int tsn_cb_streamid_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_cb_streamid_get(info);
return 0;
}
return -1;
}
static int cmb_cb_streamid_counters_get(struct genl_info *info)
{
return 0;
}
static int tsn_cb_streamid_counters_get(struct sk_buff *skb,
struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmb_cb_streamid_counters_get(info);
return 0;
}
return -1;
}
static int tsn_qci_cap_get(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *qci_cap;
struct sk_buff *rep_skb;
int ret;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
struct tsn_qci_psfp_stream_param qci_cap_status;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port) {
ret = -EINVAL;
goto out;
}
tsnops = port->tsnops;
genlhdr = info->genlhdr;
memset(&qci_cap_status, 0, sizeof(qci_cap_status));
if (!tsnops->qci_get_maxcap) {
ret = -EOPNOTSUPP;
goto out;
}
ret = tsnops->qci_get_maxcap(netdev, &qci_cap_status);
if (ret < 0)
goto out;
/* Pad netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
goto out;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name)) {
ret = -EMSGSIZE;
goto err;
}
qci_cap = nla_nest_start_noflag(rep_skb, TSN_ATTR_QCI_SP);
if (!qci_cap) {
ret = -EMSGSIZE;
goto err;
}
if (nla_put_u32(rep_skb, TSN_QCI_STREAM_ATTR_MAX_SFI,
qci_cap_status.max_sf_instance) ||
nla_put_u32(rep_skb, TSN_QCI_STREAM_ATTR_MAX_SGI,
qci_cap_status.max_sg_instance) ||
nla_put_u32(rep_skb, TSN_QCI_STREAM_ATTR_MAX_FMI,
qci_cap_status.max_fm_instance) ||
nla_put_u32(rep_skb, TSN_QCI_STREAM_ATTR_SLM,
qci_cap_status.supported_list_max)) {
ret = -EMSGSIZE;
goto err;
}
nla_nest_end(rep_skb, qci_cap);
tsn_send_reply(rep_skb, info);
return 0;
err:
nlmsg_free(rep_skb);
out:
if (ret < 0)
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
static int cmd_qci_sfi_set(struct genl_info *info)
{
struct nlattr *na, *sfi[TSN_QCI_SFI_ATTR_MAX + 1];
u32 sfi_handle;
bool enable;
int ret;
struct net_device *netdev;
struct tsn_qci_psfp_sfi_conf sficonf;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
memset(&sficonf, 0, sizeof(struct tsn_qci_psfp_sfi_conf));
if (!info->attrs[TSN_ATTR_QCI_SFI])
return -EINVAL;
na = info->attrs[TSN_ATTR_QCI_SFI];
ret = NLA_PARSE_NESTED(sfi, TSN_QCI_SFI_ATTR_MAX, na, qci_sfi_policy);
if (ret) {
pr_info("tsn: parse value TSN_QCI_SFI_ATTR_MAX error.");
return -EINVAL;
}
if (!sfi[TSN_QCI_SFI_ATTR_INDEX])
return -EINVAL;
sfi_handle = nla_get_u32(sfi[TSN_QCI_SFI_ATTR_INDEX]);
if (sfi[TSN_QCI_SFI_ATTR_ENABLE]) {
enable = true;
} else if (sfi[TSN_QCI_SFI_ATTR_DISABLE]) {
enable = false;
goto loaddrive;
} else {
pr_err("tsn: must provde ENABLE or DISABLE attribute.\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (!sfi[TSN_QCI_SFI_ATTR_GATE_ID]) {
pr_err("tsn: must provide stream gate index\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (!sfi[TSN_QCI_SFI_ATTR_STREAM_HANDLE])
sficonf.stream_handle_spec = -1;
else
sficonf.stream_handle_spec =
nla_get_s32(sfi[TSN_QCI_SFI_ATTR_STREAM_HANDLE]);
if (!sfi[TSN_QCI_SFI_ATTR_PRIO_SPEC])
sficonf.priority_spec = -1;
else
sficonf.priority_spec =
nla_get_s8(sfi[TSN_QCI_SFI_ATTR_PRIO_SPEC]);
sficonf.stream_gate_instance_id =
nla_get_u32(sfi[TSN_QCI_SFI_ATTR_GATE_ID]);
if (sfi[TSN_QCI_SFI_ATTR_MAXSDU])
sficonf.stream_filter.maximum_sdu_size =
nla_get_u16(sfi[TSN_QCI_SFI_ATTR_MAXSDU]);
else
sficonf.stream_filter.maximum_sdu_size = 0;
if (sfi[TSN_QCI_SFI_ATTR_FLOW_ID])
sficonf.stream_filter.flow_meter_instance_id =
nla_get_s32(sfi[TSN_QCI_SFI_ATTR_FLOW_ID]);
else
sficonf.stream_filter.flow_meter_instance_id = -1;
if (sfi[TSN_QCI_SFI_ATTR_OVERSIZE_ENABLE])
sficonf.block_oversize_enable = true;
if (sfi[TSN_QCI_SFI_ATTR_OVERSIZE])
sficonf.block_oversize = true;
loaddrive:
if (!tsnops->qci_sfi_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -EINVAL;
}
ret = tsnops->qci_sfi_set(netdev, sfi_handle, enable, &sficonf);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
ret = tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, 0);
if (ret)
return ret;
return 0;
}
static int tsn_qci_sfi_set(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_sfi_set(info);
return 0;
}
return -1;
}
static int cmd_qci_sfi_get(struct genl_info *info)
{
struct nlattr *na, *sfiattr;
struct nlattr *sfi[TSN_QCI_SFI_ATTR_MAX + 1];
u32 sfi_handle;
struct sk_buff *rep_skb;
int ret, valid = 0;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
struct tsn_qci_psfp_sfi_conf sficonf;
struct tsn_qci_psfp_sfi_counters sficount;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
genlhdr = info->genlhdr;
if (!info->attrs[TSN_ATTR_QCI_SFI])
return -EINVAL;
na = info->attrs[TSN_ATTR_QCI_SFI];
ret = NLA_PARSE_NESTED(sfi, TSN_QCI_SFI_ATTR_MAX,
na, qci_sfi_policy);
if (ret)
return -EINVAL;
if (!sfi[TSN_QCI_SFI_ATTR_INDEX])
return -EINVAL;
sfi_handle = nla_get_u32(sfi[TSN_QCI_SFI_ATTR_INDEX]);
memset(&sficonf, 0, sizeof(struct tsn_qci_psfp_sfi_conf));
memset(&sficount, 0, sizeof(struct tsn_qci_psfp_sfi_counters));
if (!tsnops->qci_sfi_get || !tsnops->qci_sfi_counters_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
ret = -EINVAL;
goto exit;
} else {
valid = tsnops->qci_sfi_get(netdev, sfi_handle, &sficonf);
if (valid < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, valid);
return valid;
}
valid = tsnops->qci_sfi_counters_get(netdev, sfi_handle,
&sficount);
if (valid < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, valid);
return valid;
}
}
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
goto err;
sfiattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_QCI_SFI);
if (!sfiattr) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
ret = -EINVAL;
goto err;
}
if (nla_put_u32(rep_skb, TSN_QCI_SFI_ATTR_INDEX, sfi_handle))
return -EMSGSIZE;
if (valid) {
if (nla_put_flag(rep_skb, TSN_QCI_SFI_ATTR_ENABLE))
return -EMSGSIZE;
} else {
if (nla_put_flag(rep_skb, TSN_QCI_SFI_ATTR_DISABLE))
return -EMSGSIZE;
}
if (nla_put_s32(rep_skb, TSN_QCI_SFI_ATTR_STREAM_HANDLE,
sficonf.stream_handle_spec) ||
nla_put_s8(rep_skb, TSN_QCI_SFI_ATTR_PRIO_SPEC,
sficonf.priority_spec) ||
nla_put_u32(rep_skb, TSN_QCI_SFI_ATTR_GATE_ID,
sficonf.stream_gate_instance_id))
return -EMSGSIZE;
if (sficonf.stream_filter.maximum_sdu_size)
if (nla_put_u16(rep_skb, TSN_QCI_SFI_ATTR_MAXSDU,
sficonf.stream_filter.maximum_sdu_size))
return -EMSGSIZE;
if (sficonf.stream_filter.flow_meter_instance_id >= 0)
if (nla_put_s32(rep_skb, TSN_QCI_SFI_ATTR_FLOW_ID,
sficonf.stream_filter.flow_meter_instance_id))
return -EMSGSIZE;
if (sficonf.block_oversize_enable)
if (nla_put_flag(rep_skb, TSN_QCI_SFI_ATTR_OVERSIZE_ENABLE))
return -EMSGSIZE;
if (sficonf.block_oversize)
if (nla_put_flag(rep_skb, TSN_QCI_SFI_ATTR_OVERSIZE))
return -EMSGSIZE;
if (nla_put(rep_skb, TSN_QCI_SFI_ATTR_COUNTERS,
sizeof(struct tsn_qci_psfp_sfi_counters), &sficount))
return -EMSGSIZE;
nla_nest_end(rep_skb, sfiattr);
return tsn_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
exit:
return ret;
}
static int tsn_qci_sfi_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_sfi_get(info);
return 0;
}
return -1;
}
static int cmd_qci_sfi_counters_get(struct genl_info *info)
{
struct nlattr *na, *sfiattr;
struct nlattr *sfi[TSN_QCI_SFI_ATTR_MAX + 1];
u32 sfi_handle;
struct sk_buff *rep_skb;
int ret;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
struct tsn_qci_psfp_sfi_counters sficount;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
genlhdr = info->genlhdr;
if (!info->attrs[TSN_ATTR_QCI_SFI])
return -EINVAL;
na = info->attrs[TSN_ATTR_QCI_SFI];
ret = NLA_PARSE_NESTED(sfi, TSN_QCI_SFI_ATTR_MAX,
na, qci_sfi_policy);
if (ret)
return -EINVAL;
if (!sfi[TSN_QCI_SFI_ATTR_INDEX])
return -EINVAL;
sfi_handle = nla_get_u32(sfi[TSN_QCI_SFI_ATTR_INDEX]);
memset(&sficount, 0, sizeof(struct tsn_qci_psfp_sfi_counters));
if (!tsnops->qci_sfi_counters_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = tsnops->qci_sfi_counters_get(netdev, sfi_handle, &sficount);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
ret = tsn_prepare_reply(info, genlhdr->cmd, &rep_skb,
NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
goto err;
sfiattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_QCI_SFI);
if (!sfiattr) {
ret = -EINVAL;
goto err;
}
if (nla_put_u32(rep_skb, TSN_QCI_SFI_ATTR_INDEX, sfi_handle))
return -EMSGSIZE;
ret = tsnops->qci_sfi_counters_get(netdev, sfi_handle, &sficount);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
if (nla_put(rep_skb, TSN_QCI_SFI_ATTR_COUNTERS,
sizeof(struct tsn_qci_psfp_sfi_counters), &sficount))
return -EMSGSIZE;
nla_nest_end(rep_skb, sfiattr);
return tsn_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, -EINVAL);
return ret;
}
static int tsn_qci_sfi_counters_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_sfi_counters_get(info);
return 0;
}
return -1;
}
static int cmd_qci_sgi_set(struct genl_info *info)
{
struct nlattr *na;
struct nlattr *sgia[TSN_QCI_SGI_ATTR_MAX + 1];
struct nlattr *admin[TSN_SGI_ATTR_CTRL_MAX + 1];
int ret = 0;
struct net_device *netdev;
const struct tsn_ops *tsnops;
struct tsn_qci_psfp_sgi_conf sgi;
struct tsn_qci_psfp_gcl *gcl = NULL;
u16 sgi_handle = 0;
u16 listcount = 0;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
memset(&sgi, 0, sizeof(struct tsn_qci_psfp_sgi_conf));
if (!info->attrs[TSN_ATTR_QCI_SGI]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_QCI_SGI];
ret = NLA_PARSE_NESTED(sgia, TSN_QCI_SGI_ATTR_MAX,
na, qci_sgi_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (sgia[TSN_QCI_SGI_ATTR_ENABLE] && sgia[TSN_QCI_SGI_ATTR_DISABLE]) {
pr_err("tsn: enable or disable?\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -1;
}
if (sgia[TSN_QCI_SGI_ATTR_INDEX])
sgi_handle = nla_get_u32(sgia[TSN_QCI_SGI_ATTR_INDEX]);
if (sgia[TSN_QCI_SGI_ATTR_DISABLE]) {
sgi.gate_enabled = 0;
goto loaddev;
} else {
/* set default to be enable*/
sgi.gate_enabled = 1;
}
if (sgia[TSN_QCI_SGI_ATTR_CONFCHANGE])
sgi.config_change = 1;
if (sgia[TSN_QCI_SGI_ATTR_IRXEN])
sgi.block_invalid_rx_enable = 1;
if (sgia[TSN_QCI_SGI_ATTR_IRX])
sgi.block_invalid_rx = 1;
if (sgia[TSN_QCI_SGI_ATTR_OEXEN])
sgi.block_octets_exceeded_enable = 1;
if (sgia[TSN_QCI_SGI_ATTR_OEX])
sgi.block_octets_exceeded = 1;
if (sgia[TSN_QCI_SGI_ATTR_ADMINENTRY]) {
struct nlattr *entry;
int rem;
int count = 0;
na = sgia[TSN_QCI_SGI_ATTR_ADMINENTRY];
ret = NLA_PARSE_NESTED(admin, TSN_SGI_ATTR_CTRL_MAX,
na, qci_sgi_ctrl_policy);
/* Other parameters in admin control */
if (admin[TSN_SGI_ATTR_CTRL_INITSTATE])
sgi.admin.gate_states = 1;
if (admin[TSN_SGI_ATTR_CTRL_CYTIME])
sgi.admin.cycle_time =
nla_get_u32(admin[TSN_SGI_ATTR_CTRL_CYTIME]);
if (admin[TSN_SGI_ATTR_CTRL_CYTIMEEX])
sgi.admin.cycle_time_extension =
nla_get_u32(admin[TSN_SGI_ATTR_CTRL_CYTIMEEX]);
if (admin[TSN_SGI_ATTR_CTRL_BTIME])
sgi.admin.base_time =
nla_get_u64(admin[TSN_SGI_ATTR_CTRL_BTIME]);
if (admin[TSN_SGI_ATTR_CTRL_INITIPV])
sgi.admin.init_ipv =
nla_get_s8(admin[TSN_SGI_ATTR_CTRL_INITIPV]);
else
sgi.admin.init_ipv = -1;
if (admin[TSN_SGI_ATTR_CTRL_LEN]) {
sgi.admin.control_list_length =
nla_get_u8(admin[TSN_SGI_ATTR_CTRL_LEN]);
listcount = sgi.admin.control_list_length;
}
if (!listcount)
goto loaddev;
gcl = kmalloc_array(listcount, sizeof(*gcl), GFP_KERNEL);
memset(gcl, 0, listcount * sizeof(struct tsn_qci_psfp_gcl));
/* Check the whole admin attrs,
* checkout the TSN_SGI_ATTR_CTRL_GCLENTRY attributes
*/
nla_for_each_nested(entry, na, rem) {
struct nlattr *gcl_entry[TSN_SGI_ATTR_GCL_MAX + 1];
struct nlattr *ti, *om;
if (nla_type(entry) != TSN_SGI_ATTR_CTRL_GCLENTRY)
continue;
/* parse each TSN_SGI_ATTR_CTRL_GCLENTRY */
ret = NLA_PARSE_NESTED(gcl_entry, TSN_SGI_ATTR_GCL_MAX,
entry, qci_sgi_gcl_policy);
/* Parse gate control list */
if (gcl_entry[TSN_SGI_ATTR_GCL_GATESTATE])
(gcl + count)->gate_state = 1;
if (gcl_entry[TSN_SGI_ATTR_GCL_IPV])
(gcl + count)->ipv =
nla_get_s8(gcl_entry[TSN_SGI_ATTR_GCL_IPV]);
if (gcl_entry[TSN_SGI_ATTR_GCL_INTERVAL]) {
ti = gcl_entry[TSN_SGI_ATTR_GCL_INTERVAL];
(gcl + count)->time_interval = nla_get_u32(ti);
}
if (gcl_entry[TSN_SGI_ATTR_GCL_OCTMAX]) {
om = gcl_entry[TSN_SGI_ATTR_GCL_OCTMAX];
(gcl + count)->octet_max = nla_get_u32(om);
}
count++;
if (count >= listcount)
break;
}
if (count < listcount) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
pr_err("tsn: count less than TSN_SGI_ATTR_CTRL_LEN\n");
kfree(gcl);
return -EINVAL;
}
} else {
pr_info("tsn: no admin list parameters setting\n");
}
loaddev:
if (!tsnops->qci_sgi_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
kfree(gcl);
return -EINVAL;
}
sgi.admin.gcl = gcl;
ret = tsnops->qci_sgi_set(netdev, sgi_handle, &sgi);
kfree(gcl);
if (!ret)
return tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
static int tsn_qci_sgi_set(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_sgi_set(info);
return 0;
}
return -1;
}
static int cmd_qci_sgi_get(struct genl_info *info)
{
struct nlattr *na, *sgiattr, *adminattr, *sglattr;
struct nlattr *sgi[TSN_QCI_SGI_ATTR_MAX + 1];
struct sk_buff *rep_skb;
int ret;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
struct tsn_qci_psfp_sgi_conf sgiadmin;
struct tsn_qci_psfp_gcl *gcl = NULL;
const struct tsn_ops *tsnops;
u16 sgi_handle;
u8 listcount, i;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_QCI_SGI]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
pr_err("tsn: no sgi handle input\n");
return -EINVAL;
}
na = info->attrs[TSN_ATTR_QCI_SGI];
ret = NLA_PARSE_NESTED(sgi, TSN_QCI_SGI_ATTR_MAX,
na, qci_sgi_policy);
if (ret)
return -EINVAL;
if (!sgi[TSN_QCI_SGI_ATTR_INDEX]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
pr_err("tsn: no sgi handle input\n");
return -EINVAL;
}
sgi_handle = nla_get_u32(sgi[TSN_QCI_SGI_ATTR_INDEX]);
/* Get config data from device */
genlhdr = info->genlhdr;
memset(&sgiadmin, 0, sizeof(struct tsn_qci_psfp_sgi_conf));
if (!tsnops->qci_sgi_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = tsnops->qci_sgi_get(netdev, sgi_handle, &sgiadmin);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
/* Form netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
sgiattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_QCI_SGI);
if (!sgiattr)
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_QCI_SGI_ATTR_INDEX, sgi_handle))
return -EMSGSIZE;
/* Gate enable? sgiadmin.gate_enabled */
if (sgiadmin.gate_enabled) {
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_ENABLE))
return -EMSGSIZE;
} else {
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_DISABLE))
return -EMSGSIZE;
}
if (sgiadmin.config_change)
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_CONFCHANGE))
return -EMSGSIZE;
if (sgiadmin.block_invalid_rx_enable)
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_IRXEN))
return -EMSGSIZE;
if (sgiadmin.block_invalid_rx)
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_IRX))
return -EMSGSIZE;
if (sgiadmin.block_octets_exceeded_enable)
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_OEXEN))
return -EMSGSIZE;
if (sgiadmin.block_octets_exceeded)
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_OEX))
return -EMSGSIZE;
/* Administration */
adminattr = nla_nest_start_noflag(rep_skb, TSN_QCI_SGI_ATTR_ADMINENTRY);
if (!adminattr)
return -EMSGSIZE;
if (sgiadmin.admin.gate_states)
if (nla_put_flag(rep_skb, TSN_SGI_ATTR_CTRL_INITSTATE))
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_SGI_ATTR_CTRL_CYTIME,
sgiadmin.admin.cycle_time) ||
nla_put_u32(rep_skb, TSN_SGI_ATTR_CTRL_CYTIMEEX,
sgiadmin.admin.cycle_time_extension) ||
NLA_PUT_U64(rep_skb, TSN_SGI_ATTR_CTRL_BTIME,
sgiadmin.admin.base_time) ||
nla_put_u8(rep_skb, TSN_SGI_ATTR_CTRL_INITIPV,
sgiadmin.admin.init_ipv))
return -EMSGSIZE;
listcount = sgiadmin.admin.control_list_length;
if (!listcount)
goto out1;
if (!sgiadmin.admin.gcl) {
pr_err("error: no gate control list\n");
ret = -EINVAL;
goto err;
}
gcl = sgiadmin.admin.gcl;
/* loop list */
for (i = 0; i < listcount; i++) {
s8 ipv;
u32 ti, omax;
if (!(gcl + i)) {
pr_err("error: list count too big\n");
ret = -EINVAL;
kfree(sgiadmin.admin.gcl);
goto err;
}
/* Adminastration entry */
sglattr = nla_nest_start_noflag(rep_skb,
TSN_SGI_ATTR_CTRL_GCLENTRY);
if (!sglattr)
return -EMSGSIZE;
ipv = (gcl + i)->ipv;
ti = (gcl + i)->time_interval;
omax = (gcl + i)->octet_max;
if ((gcl + i)->gate_state)
if (nla_put_flag(rep_skb, TSN_SGI_ATTR_GCL_GATESTATE))
return -EMSGSIZE;
if (nla_put_s8(rep_skb, TSN_SGI_ATTR_GCL_IPV, ipv) ||
nla_put_u32(rep_skb, TSN_SGI_ATTR_GCL_INTERVAL, ti) ||
nla_put_u32(rep_skb, TSN_SGI_ATTR_GCL_OCTMAX, omax))
return -EMSGSIZE;
/* End administration entry */
nla_nest_end(rep_skb, sglattr);
}
kfree(sgiadmin.admin.gcl);
if (nla_put_u8(rep_skb, TSN_SGI_ATTR_CTRL_LEN, listcount))
return -EMSGSIZE;
out1:
/* End adminastration */
nla_nest_end(rep_skb, adminattr);
nla_nest_end(rep_skb, sgiattr);
return tsn_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
static int tsn_qci_sgi_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_sgi_get(info);
return 0;
}
return -1;
}
static int cmd_qci_sgi_status_get(struct genl_info *info)
{
struct nlattr *na, *sgiattr, *operattr, *sglattr;
struct nlattr *sgi[TSN_QCI_SGI_ATTR_MAX + 1];
struct sk_buff *rep_skb;
int ret;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
struct tsn_psfp_sgi_status sgistat;
struct tsn_qci_psfp_gcl *gcl = NULL;
const struct tsn_ops *tsnops;
u16 sgi_handle;
u8 listcount;
int valid, i;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_QCI_SGI]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
pr_err("tsn: no sgi handle input\n");
return -EINVAL;
}
na = info->attrs[TSN_ATTR_QCI_SGI];
ret = NLA_PARSE_NESTED(sgi, TSN_QCI_SGI_ATTR_MAX,
na, qci_sgi_policy);
if (ret)
return -EINVAL;
if (!sgi[TSN_QCI_SGI_ATTR_INDEX]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
pr_err("tsn: no sgi handle input\n");
return -EINVAL;
}
sgi_handle = nla_get_u32(sgi[TSN_QCI_SGI_ATTR_INDEX]);
/* Get status data from device */
genlhdr = info->genlhdr;
memset(&sgistat, 0, sizeof(struct tsn_psfp_sgi_status));
if (!tsnops->qci_sgi_status_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
valid = tsnops->qci_sgi_status_get(netdev, sgi_handle, &sgistat);
if (valid < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, valid);
return valid;
}
/* Form netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
/* Down one netlink attribute level */
sgiattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_QCI_SGI);
if (!sgiattr)
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_QCI_SGI_ATTR_INDEX, sgi_handle))
return -EMSGSIZE;
/* Gate enable */
if (valid == 1) {
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_ENABLE))
return -EMSGSIZE;
} else {
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_DISABLE))
return -EMSGSIZE;
}
if (nla_put_u32(rep_skb, TSN_QCI_SGI_ATTR_TICKG,
sgistat.tick_granularity) ||
NLA_PUT_U64(rep_skb, TSN_QCI_SGI_ATTR_CCTIME,
sgistat.config_change_time) ||
NLA_PUT_U64(rep_skb, TSN_QCI_SGI_ATTR_CUTIME,
sgistat.current_time) ||
NLA_PUT_U64(rep_skb, TSN_QCI_SGI_ATTR_CCERROR,
sgistat.config_change_error))
return -EMSGSIZE;
if (sgistat.config_pending)
if (nla_put_flag(rep_skb, TSN_QCI_SGI_ATTR_CPENDING))
return -EMSGSIZE;
/* operation data */
operattr = nla_nest_start_noflag(rep_skb, TSN_QCI_SGI_ATTR_OPERENTRY);
if (!operattr)
return -EMSGSIZE;
if (sgistat.oper.gate_states)
if (nla_put_flag(rep_skb, TSN_SGI_ATTR_CTRL_INITSTATE))
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_SGI_ATTR_CTRL_CYTIME,
sgistat.oper.cycle_time) ||
nla_put_u32(rep_skb, TSN_SGI_ATTR_CTRL_CYTIMEEX,
sgistat.oper.cycle_time_extension) ||
NLA_PUT_U64(rep_skb, TSN_SGI_ATTR_CTRL_BTIME,
sgistat.oper.base_time) ||
nla_put_u8(rep_skb, TSN_SGI_ATTR_CTRL_INITIPV,
sgistat.oper.init_ipv))
return -EMSGSIZE;
/* Loop list */
listcount = sgistat.oper.control_list_length;
if (!listcount)
goto out1;
if (!sgistat.oper.gcl) {
pr_err("error: list lenghth is not zero!\n");
ret = -EINVAL;
goto err;
}
gcl = sgistat.oper.gcl;
/* loop list */
for (i = 0; i < listcount; i++) {
s8 ipv;
u32 ti, omax;
if (!(gcl + i)) {
pr_err("error: list count too big\n");
ret = -EINVAL;
kfree(sgistat.oper.gcl);
goto err;
}
/* Operation entry */
sglattr = nla_nest_start_noflag(rep_skb,
TSN_SGI_ATTR_CTRL_GCLENTRY);
if (!sglattr)
return -EMSGSIZE;
ipv = (gcl + i)->ipv;
ti = (gcl + i)->time_interval;
omax = (gcl + i)->octet_max;
if ((gcl + i)->gate_state)
if (nla_put_flag(rep_skb, TSN_SGI_ATTR_GCL_GATESTATE))
return -EMSGSIZE;
if (nla_put_s8(rep_skb, TSN_SGI_ATTR_GCL_IPV, ipv) ||
nla_put_u32(rep_skb, TSN_SGI_ATTR_GCL_INTERVAL, ti) ||
nla_put_u32(rep_skb, TSN_SGI_ATTR_GCL_OCTMAX, omax))
return -EMSGSIZE;
/* End operation entry */
nla_nest_end(rep_skb, sglattr);
}
kfree(sgistat.oper.gcl);
if (nla_put_u8(rep_skb, TSN_SGI_ATTR_CTRL_LEN, listcount))
return -EMSGSIZE;
out1:
/* End operation */
nla_nest_end(rep_skb, operattr);
nla_nest_end(rep_skb, sgiattr);
return tsn_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
static int tsn_qci_sgi_status_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_sgi_status_get(info);
return 0;
}
return -1;
}
static int cmd_qci_fmi_set(struct genl_info *info)
{
struct nlattr *na, *fmi[TSN_QCI_FMI_ATTR_MAX + 1];
u32 index;
int ret;
struct net_device *netdev;
struct tsn_qci_psfp_fmi fmiconf;
const struct tsn_ops *tsnops;
bool enable = 0;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
memset(&fmiconf, 0, sizeof(struct tsn_qci_psfp_fmi));
if (!info->attrs[TSN_ATTR_QCI_FMI])
return -EINVAL;
na = info->attrs[TSN_ATTR_QCI_FMI];
ret = NLA_PARSE_NESTED(fmi, TSN_QCI_FMI_ATTR_MAX, na, qci_fmi_policy);
if (ret) {
pr_info("tsn: parse value TSN_QCI_FMI_ATTR_MAX error.");
return -EINVAL;
}
if (!fmi[TSN_QCI_FMI_ATTR_INDEX])
return -EINVAL;
index = nla_get_u32(fmi[TSN_QCI_FMI_ATTR_INDEX]);
if (fmi[TSN_QCI_FMI_ATTR_DISABLE])
goto loaddev;
enable = 1;
if (fmi[TSN_QCI_FMI_ATTR_CIR])
fmiconf.cir = nla_get_u32(fmi[TSN_QCI_FMI_ATTR_CIR]);
if (fmi[TSN_QCI_FMI_ATTR_CBS])
fmiconf.cbs = nla_get_u32(fmi[TSN_QCI_FMI_ATTR_CBS]);
if (fmi[TSN_QCI_FMI_ATTR_EIR])
fmiconf.eir = nla_get_u32(fmi[TSN_QCI_FMI_ATTR_EIR]);
if (fmi[TSN_QCI_FMI_ATTR_EBS])
fmiconf.ebs = nla_get_u32(fmi[TSN_QCI_FMI_ATTR_EBS]);
if (fmi[TSN_QCI_FMI_ATTR_CF])
fmiconf.cf = 1;
if (fmi[TSN_QCI_FMI_ATTR_CM])
fmiconf.cm = 1;
if (fmi[TSN_QCI_FMI_ATTR_DROPYL])
fmiconf.drop_on_yellow = 1;
if (fmi[TSN_QCI_FMI_ATTR_MAREDEN])
fmiconf.mark_red_enable = 1;
if (fmi[TSN_QCI_FMI_ATTR_MARED])
fmiconf.mark_red = 1;
loaddev:
if (!tsnops->qci_fmi_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -EINVAL;
}
ret = tsnops->qci_fmi_set(netdev, index, enable, &fmiconf);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
ret = tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, 0);
if (ret)
return ret;
return 0;
}
static int tsn_qci_fmi_set(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_fmi_set(info);
return 0;
}
return -1;
}
static int cmd_qci_fmi_get(struct genl_info *info)
{
struct nlattr *na, *fmi[TSN_QCI_FMI_ATTR_MAX + 1], *fmiattr;
u32 index;
struct sk_buff *rep_skb;
int ret;
struct net_device *netdev;
struct tsn_qci_psfp_fmi fmiconf;
struct tsn_qci_psfp_fmi_counters counters;
const struct tsn_ops *tsnops;
struct genlmsghdr *genlhdr;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_QCI_FMI])
return -EINVAL;
na = info->attrs[TSN_ATTR_QCI_FMI];
ret = NLA_PARSE_NESTED(fmi, TSN_QCI_FMI_ATTR_MAX,
na, qci_fmi_policy);
if (ret) {
pr_info("tsn: parse value TSN_QCI_FMI_ATTR_MAX error.");
return -EINVAL;
}
if (!fmi[TSN_QCI_FMI_ATTR_INDEX])
return -EINVAL;
index = nla_get_u32(fmi[TSN_QCI_FMI_ATTR_INDEX]);
/* Get data from device */
memset(&fmiconf, 0, sizeof(struct tsn_qci_psfp_fmi));
memset(&counters, 0, sizeof(struct tsn_qci_psfp_fmi_counters));
if (!tsnops->qci_fmi_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -EINVAL;
}
ret = tsnops->qci_fmi_get(netdev, index, &fmiconf, &counters);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY, netdev->name, ret);
return ret;
}
genlhdr = info->genlhdr;
/* Form netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
fmiattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_QCI_FMI);
if (!fmiattr)
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_QCI_FMI_ATTR_INDEX, index) ||
nla_put_u32(rep_skb, TSN_QCI_FMI_ATTR_CIR, fmiconf.cir) ||
nla_put_u32(rep_skb, TSN_QCI_FMI_ATTR_CBS, fmiconf.cbs) ||
nla_put_u32(rep_skb, TSN_QCI_FMI_ATTR_EIR, fmiconf.eir) ||
nla_put_u32(rep_skb, TSN_QCI_FMI_ATTR_EBS, fmiconf.ebs))
return -EMSGSIZE;
if (fmiconf.cf)
if (nla_put_flag(rep_skb, TSN_QCI_FMI_ATTR_CF))
return -EMSGSIZE;
if (fmiconf.cm)
if (nla_put_flag(rep_skb, TSN_QCI_FMI_ATTR_CM))
return -EMSGSIZE;
if (fmiconf.drop_on_yellow)
if (nla_put_flag(rep_skb, TSN_QCI_FMI_ATTR_DROPYL))
return -EMSGSIZE;
if (fmiconf.mark_red_enable)
if (nla_put_flag(rep_skb, TSN_QCI_FMI_ATTR_MAREDEN))
return -EMSGSIZE;
if (fmiconf.mark_red)
if (nla_put_flag(rep_skb, TSN_QCI_FMI_ATTR_MAREDEN))
return -EMSGSIZE;
if (nla_put(rep_skb, TSN_QCI_FMI_ATTR_COUNTERS,
sizeof(struct tsn_qci_psfp_fmi_counters), &counters))
return -EMSGSIZE;
nla_nest_end(rep_skb, fmiattr);
tsn_send_reply(rep_skb, info);
return 0;
}
static int tsn_qci_fmi_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qci_fmi_get(info);
return 0;
}
return -1;
}
static int cmd_qbv_set(struct genl_info *info)
{
struct nlattr *na, *na1;
struct nlattr *qbv_table;
struct nlattr *qbv[TSN_QBV_ATTR_MAX + 1];
struct nlattr *qbvctrl[TSN_QBV_ATTR_CTRL_MAX + 1];
int rem;
int ret = 0;
struct net_device *netdev;
struct tsn_qbv_conf qbvconfig;
const struct tsn_ops *tsnops;
struct tsn_qbv_entry *gatelist = NULL;
int count = 0;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
memset(&qbvconfig, 0, sizeof(struct tsn_qbv_conf));
if (!info->attrs[TSN_ATTR_QBV])
return -EINVAL;
na = info->attrs[TSN_ATTR_QBV];
ret = NLA_PARSE_NESTED(qbv, TSN_QBV_ATTR_MAX, na, qbv_policy);
if (ret)
return -EINVAL;
if (qbv[TSN_QBV_ATTR_ENABLE])
qbvconfig.gate_enabled = 1;
else
goto setdrive;
if (qbv[TSN_QBV_ATTR_CONFIGCHANGE])
qbvconfig.config_change = 1;
if (!qbv[TSN_QBV_ATTR_ADMINENTRY]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -1;
}
na1 = qbv[TSN_QBV_ATTR_ADMINENTRY];
NLA_PARSE_NESTED(qbvctrl, TSN_QBV_ATTR_CTRL_MAX,
na1, qbv_ctrl_policy);
if (qbvctrl[TSN_QBV_ATTR_CTRL_CYCLETIME]) {
qbvconfig.admin.cycle_time =
nla_get_u32(qbvctrl[TSN_QBV_ATTR_CTRL_CYCLETIME]);
}
if (qbvctrl[TSN_QBV_ATTR_CTRL_CYCLETIMEEXT]) {
qbvconfig.admin.cycle_time_extension =
nla_get_u32(qbvctrl[TSN_QBV_ATTR_CTRL_CYCLETIMEEXT]);
}
if (qbvctrl[TSN_QBV_ATTR_CTRL_BASETIME]) {
qbvconfig.admin.base_time =
nla_get_u64(qbvctrl[TSN_QBV_ATTR_CTRL_BASETIME]);
}
if (qbvctrl[TSN_QBV_ATTR_CTRL_GATESTATE]) {
qbvconfig.admin.gate_states =
nla_get_u8(qbvctrl[TSN_QBV_ATTR_CTRL_GATESTATE]);
}
if (qbvctrl[TSN_QBV_ATTR_CTRL_LISTCOUNT]) {
int listcount;
listcount = nla_get_u32(qbvctrl[TSN_QBV_ATTR_CTRL_LISTCOUNT]);
qbvconfig.admin.control_list_length = listcount;
gatelist = kmalloc_array(listcount,
sizeof(*gatelist),
GFP_KERNEL);
nla_for_each_nested(qbv_table, na1, rem) {
struct nlattr *qbv_entry[TSN_QBV_ATTR_ENTRY_MAX + 1];
if (nla_type(qbv_table) != TSN_QBV_ATTR_CTRL_LISTENTRY)
continue;
ret = NLA_PARSE_NESTED(qbv_entry,
TSN_QBV_ATTR_ENTRY_MAX,
qbv_table, qbv_entry_policy);
if (ret)
return -EINVAL;
(gatelist + count)->gate_state =
nla_get_u8(qbv_entry[TSN_QBV_ATTR_ENTRY_GC]);
(gatelist + count)->time_interval =
nla_get_u32(qbv_entry[TSN_QBV_ATTR_ENTRY_TM]);
count++;
if (count > listcount)
break;
}
}
if (gatelist)
qbvconfig.admin.control_list = gatelist;
setdrive:
if (!tsnops->qbv_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
goto err;
}
ret = tsnops->qbv_set(netdev, &qbvconfig);
/* send back */
if (ret < 0)
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
else
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
err:
kfree(gatelist);
return ret;
}
static int tsn_qbv_set(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME]) {
cmd_qbv_set(info);
return 0;
}
return -1;
}
static int cmd_qbv_get(struct genl_info *info)
{
struct nlattr *qbv, *qbvadminattr;
struct sk_buff *rep_skb;
int ret;
int len = 0, i = 0;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
struct tsn_qbv_conf qbvconf;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
genlhdr = info->genlhdr;
memset(&qbvconf, 0, sizeof(struct tsn_qbv_conf));
if (!tsnops->qbv_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = tsnops->qbv_get(netdev, &qbvconf);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
qbv = nla_nest_start_noflag(rep_skb, TSN_ATTR_QBV);
if (!qbv)
return -EMSGSIZE;
qbvadminattr = nla_nest_start_noflag(rep_skb, TSN_QBV_ATTR_ADMINENTRY);
if (!qbvadminattr)
return -EMSGSIZE;
if (qbvconf.admin.control_list) {
len = qbvconf.admin.control_list_length;
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_CTRL_LISTCOUNT, len))
return -EMSGSIZE;
for (i = 0; i < len; i++) {
struct nlattr *qbv_table;
u8 gs;
u32 tp;
int glisttype = TSN_QBV_ATTR_CTRL_LISTENTRY;
gs = (qbvconf.admin.control_list + i)->gate_state;
tp = (qbvconf.admin.control_list + i)->time_interval;
qbv_table =
nla_nest_start_noflag(rep_skb, glisttype);
if (!qbv_table)
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_ENTRY_ID, i) ||
nla_put_u8(rep_skb, TSN_QBV_ATTR_ENTRY_GC, gs) ||
nla_put_u32(rep_skb, TSN_QBV_ATTR_ENTRY_TM, tp))
return -EMSGSIZE;
nla_nest_end(rep_skb, qbv_table);
}
if (qbvconf.admin.gate_states)
if (nla_put_u8(rep_skb, TSN_QBV_ATTR_CTRL_GATESTATE,
qbvconf.admin.gate_states))
return -EMSGSIZE;
if (qbvconf.admin.cycle_time)
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_CTRL_CYCLETIME,
qbvconf.admin.cycle_time))
return -EMSGSIZE;
if (qbvconf.admin.cycle_time_extension)
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_CTRL_CYCLETIMEEXT,
qbvconf.admin.cycle_time_extension))
return -EMSGSIZE;
if (qbvconf.admin.base_time)
if (NLA_PUT_U64(rep_skb, TSN_QBV_ATTR_CTRL_BASETIME,
qbvconf.admin.base_time))
return -EMSGSIZE;
kfree(qbvconf.admin.control_list);
} else {
pr_info("tsn: error get administrator data.");
}
nla_nest_end(rep_skb, qbvadminattr);
if (qbvconf.gate_enabled) {
if (nla_put_flag(rep_skb, TSN_QBV_ATTR_ENABLE))
return -EMSGSIZE;
} else {
if (nla_put_flag(rep_skb, TSN_QBV_ATTR_DISABLE))
return -EMSGSIZE;
}
if (qbvconf.maxsdu)
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_MAXSDU, qbvconf.maxsdu))
return -EMSGSIZE;
if (qbvconf.config_change)
if (nla_put_flag(rep_skb, TSN_QBV_ATTR_CONFIGCHANGE))
return -EMSGSIZE;
nla_nest_end(rep_skb, qbv);
tsn_send_reply(rep_skb, info);
return ret;
}
static int cmd_qbv_status_get(struct genl_info *info)
{
struct nlattr *qbv, *qbvoperattr;
struct sk_buff *rep_skb;
int ret;
int len = 0, i = 0;
struct net_device *netdev;
struct genlmsghdr *genlhdr;
struct tsn_qbv_status qbvstatus;
const struct tsn_ops *tsnops;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
genlhdr = info->genlhdr;
memset(&qbvstatus, 0, sizeof(struct tsn_qbv_status));
if (!tsnops->qbv_get_status) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = tsnops->qbv_get_status(netdev, &qbvstatus);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
qbv = nla_nest_start_noflag(rep_skb, TSN_ATTR_QBV);
if (!qbv)
return -EMSGSIZE;
qbvoperattr = nla_nest_start_noflag(rep_skb, TSN_QBV_ATTR_OPERENTRY);
if (!qbvoperattr)
return -EMSGSIZE;
if (qbvstatus.oper.control_list) {
len = qbvstatus.oper.control_list_length;
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_CTRL_LISTCOUNT, len)) {
nla_nest_cancel(rep_skb, qbvoperattr);
return -EMSGSIZE;
}
for (i = 0; i < len; i++) {
struct nlattr *qbv_table;
u8 gs;
u32 tp;
int glisttype = TSN_QBV_ATTR_CTRL_LISTENTRY;
gs = (qbvstatus.oper.control_list + i)->gate_state;
tp = (qbvstatus.oper.control_list + i)->time_interval;
qbv_table = nla_nest_start_noflag(rep_skb, glisttype);
if (!qbv_table)
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_ENTRY_ID, i) ||
nla_put_u8(rep_skb, TSN_QBV_ATTR_ENTRY_GC, gs) ||
nla_put_u32(rep_skb, TSN_QBV_ATTR_ENTRY_TM, tp)) {
nla_nest_cancel(rep_skb, qbv_table);
return -EMSGSIZE;
}
nla_nest_end(rep_skb, qbv_table);
}
if (qbvstatus.oper.gate_states) {
if (nla_put_u8(rep_skb, TSN_QBV_ATTR_CTRL_GATESTATE,
qbvstatus.oper.gate_states))
return -EMSGSIZE;
}
if (qbvstatus.oper.cycle_time) {
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_CTRL_CYCLETIME,
qbvstatus.oper.cycle_time))
return -EMSGSIZE;
}
if (qbvstatus.oper.cycle_time_extension) {
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_CTRL_CYCLETIMEEXT,
qbvstatus.oper.cycle_time_extension))
return -EMSGSIZE;
}
if (qbvstatus.oper.base_time) {
if (NLA_PUT_U64(rep_skb, TSN_QBV_ATTR_CTRL_BASETIME,
qbvstatus.oper.base_time))
return -EMSGSIZE;
}
kfree(qbvstatus.oper.control_list);
} else {
pr_info("tsn: error get operation list data.");
}
nla_nest_end(rep_skb, qbvoperattr);
if (qbvstatus.config_change_time) {
if (NLA_PUT_U64(rep_skb, TSN_QBV_ATTR_CONFIGCHANGETIME,
qbvstatus.config_change_time))
return -EMSGSIZE;
}
if (qbvstatus.tick_granularity) {
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_GRANULARITY,
qbvstatus.tick_granularity))
return -EMSGSIZE;
}
if (qbvstatus.current_time) {
if (NLA_PUT_U64(rep_skb, TSN_QBV_ATTR_CURRENTTIME,
qbvstatus.current_time))
return -EMSGSIZE;
}
if (qbvstatus.config_pending) {
if (nla_put_flag(rep_skb, TSN_QBV_ATTR_CONFIGPENDING))
return -EMSGSIZE;
}
if (qbvstatus.config_change_error) {
if (NLA_PUT_U64(rep_skb, TSN_QBV_ATTR_CONFIGCHANGEERROR,
qbvstatus.config_change_error))
return -EMSGSIZE;
}
if (qbvstatus.supported_list_max) {
if (nla_put_u32(rep_skb, TSN_QBV_ATTR_LISTMAX,
qbvstatus.supported_list_max))
return -EMSGSIZE;
}
nla_nest_end(rep_skb, qbv);
tsn_send_reply(rep_skb, info);
return ret;
}
static int tsn_qbv_status_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME])
cmd_qbv_status_get(info);
return 0;
}
static int tsn_qbv_get(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME])
cmd_qbv_get(info);
return 0;
}
static int tsn_cbs_set(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na;
struct nlattr *cbsa[TSN_CBS_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
int ret;
u8 tc, bw;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_CBS]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_CBS];
if (!tsnops->cbs_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = NLA_PARSE_NESTED(cbsa, TSN_CBS_ATTR_MAX, na, cbs_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (!cbsa[TSN_CBS_ATTR_TC_INDEX]) {
pr_err("tsn: no TSN_CBS_ATTR_TC_INDEX input\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
tc = nla_get_u8(cbsa[TSN_CBS_ATTR_TC_INDEX]);
if (!cbsa[TSN_CBS_ATTR_BW]) {
pr_err("tsn: no TSN_CBS_ATTR_BW input\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
bw = nla_get_u8(cbsa[TSN_CBS_ATTR_BW]);
if (bw > 100) {
pr_err("tsn: TSN_CBS_ATTR_BW isn't in the range of 0~100\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
ret = tsnops->cbs_set(netdev, tc, bw);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
return 0;
}
static int tsn_cbs_get(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na, *cbsattr;
struct nlattr *cbsa[TSN_CBS_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
struct sk_buff *rep_skb;
int ret;
struct genlmsghdr *genlhdr;
u8 tc;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_CBS]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (!tsnops->cbs_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
na = info->attrs[TSN_ATTR_CBS];
ret = NLA_PARSE_NESTED(cbsa, TSN_CBS_ATTR_MAX, na, cbs_policy);
if (ret) {
pr_err("tsn: parse value TSN_CBS_ATTR_MAX error.");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
/* Get status data from device */
genlhdr = info->genlhdr;
/* Form netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd, &rep_skb,
NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
cbsattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_CBS);
if (!cbsattr)
return -EMSGSIZE;
if (!cbsa[TSN_CBS_ATTR_TC_INDEX]) {
pr_err("tsn: must to specify the TSN_CBS_ATTR_TC_INDEX\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
tc = nla_get_u8(cbsa[TSN_CBS_ATTR_TC_INDEX]);
ret = tsnops->cbs_get(netdev, tc);
if (ret < 0) {
pr_err("tsn: cbs_get return error\n");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
if (nla_put_u8(rep_skb, TSN_CBS_ATTR_BW, ret & 0XF))
return -EMSGSIZE;
nla_nest_end(rep_skb, cbsattr);
return tsn_send_reply(rep_skb, info);
}
static int cmd_qbu_set(struct genl_info *info)
{
struct nlattr *na;
struct nlattr *qbua[TSN_QBU_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
int ret;
u8 preemptible = 0;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_QBU]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_QBU];
ret = NLA_PARSE_NESTED(qbua, TSN_QBU_ATTR_MAX, na, qbu_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (qbua[TSN_QBU_ATTR_ADMIN_STATE])
preemptible = nla_get_u8(qbua[TSN_QBU_ATTR_ADMIN_STATE]);
else
pr_info("No preemptible TSN_QBU_ATTR_ADMIN_STATE config!\n");
if (!tsnops->qbu_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -EINVAL;
}
ret = tsnops->qbu_set(netdev, preemptible);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
return 0;
}
static int tsn_qbu_set(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME])
return cmd_qbu_set(info);
return -1;
}
static int cmd_qbu_get_status(struct genl_info *info)
{
struct nlattr *qbuattr;
struct net_device *netdev;
const struct tsn_ops *tsnops;
struct sk_buff *rep_skb;
int ret;
struct genlmsghdr *genlhdr;
struct tsn_preempt_status pps;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
/* Get status data from device */
genlhdr = info->genlhdr;
memset(&pps, 0, sizeof(struct tsn_preempt_status));
if (!tsnops->qbu_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = tsnops->qbu_get(netdev, &pps);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
/* Form netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
qbuattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_QBU);
if (!qbuattr)
return -EMSGSIZE;
if (nla_put_u8(rep_skb, TSN_QBU_ATTR_ADMIN_STATE, pps.admin_state) ||
nla_put_u32(rep_skb,
TSN_QBU_ATTR_HOLD_ADVANCE, pps.hold_advance) ||
nla_put_u32(rep_skb,
TSN_QBU_ATTR_RELEASE_ADVANCE, pps.release_advance))
return -EMSGSIZE;
if (pps.preemption_active) {
if (nla_put_flag(rep_skb, TSN_QBU_ATTR_ACTIVE))
return -EMSGSIZE;
}
if (nla_put_u8(rep_skb, TSN_QBU_ATTR_HOLD_REQUEST, pps.hold_request))
return -EMSGSIZE;
nla_nest_end(rep_skb, qbuattr);
return tsn_send_reply(rep_skb, info);
}
static int tsn_qbu_get_status(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[TSN_ATTR_IFNAME])
return cmd_qbu_get_status(info);
return -1;
}
static int tsn_tsd_set(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na;
struct nlattr *ntsd[TSN_TSD_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
struct tsn_tsd tsd;
int ret;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
memset(&tsd, 0, sizeof(struct tsn_tsd));
if (!info->attrs[TSN_ATTR_TSD]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_TSD];
ret = NLA_PARSE_NESTED(ntsd, TSN_TSD_ATTR_MAX, na, tsd_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (!tsnops->tsd_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -EINVAL;
}
if (nla_get_flag(ntsd[TSN_TSD_ATTR_DISABLE])) {
tsd.enable = false;
} else {
if (ntsd[TSN_TSD_ATTR_PERIOD])
tsd.period = nla_get_u32(ntsd[TSN_TSD_ATTR_PERIOD]);
if (!tsd.period) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (ntsd[TSN_TSD_ATTR_MAX_FRM_NUM])
tsd.maxFrameNum =
nla_get_u32(ntsd[TSN_TSD_ATTR_MAX_FRM_NUM]);
if (ntsd[TSN_TSD_ATTR_SYN_IMME])
tsd.syn_flag = 2;
else
tsd.syn_flag = 1;
tsd.enable = true;
}
ret = tsnops->tsd_set(netdev, &tsd);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
return 0;
}
static int tsn_tsd_get(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na, *tsdattr;
struct nlattr *tsda[TSN_TSD_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
struct sk_buff *rep_skb;
int ret;
struct genlmsghdr *genlhdr;
struct tsn_tsd_status tts;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_TSD]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
if (!tsnops->tsd_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = tsnops->tsd_get(netdev, &tts);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
na = info->attrs[TSN_ATTR_TSD];
ret = NLA_PARSE_NESTED(tsda, TSN_TSD_ATTR_MAX,
na, tsd_policy);
if (ret) {
pr_err("tsn: parse value TSN_TSD_ATTR_MAX error.");
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
/* Get status data from device */
genlhdr = info->genlhdr;
/* Form netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd, &rep_skb,
NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
tsdattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_TSD);
if (!tsdattr)
return -EMSGSIZE;
if (nla_put_u32(rep_skb, TSN_TSD_ATTR_PERIOD, tts.period) ||
nla_put_u32(rep_skb, TSN_TSD_ATTR_MAX_FRM_NUM, tts.maxFrameNum) ||
nla_put_u32(rep_skb, TSN_TSD_ATTR_CYCLE_NUM, tts.cycleNum) ||
nla_put_u32(rep_skb, TSN_TSD_ATTR_LOSS_STEPS, tts.loss_steps) ||
nla_put_u32(rep_skb, TSN_TSD_ATTR_MAX_FRM_NUM, tts.maxFrameNum))
return -EMSGSIZE;
if (!tts.enable) {
if (nla_put_flag(rep_skb, TSN_TSD_ATTR_DISABLE))
return -EMSGSIZE;
} else {
if (nla_put_flag(rep_skb, TSN_TSD_ATTR_ENABLE))
return -EMSGSIZE;
}
if (tts.flag == 2)
if (nla_put_flag(rep_skb, TSN_TSD_ATTR_SYN_IMME))
return -EMSGSIZE;
nla_nest_end(rep_skb, tsdattr);
return tsn_send_reply(rep_skb, info);
}
static int tsn_ct_set(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na;
struct nlattr *cta[TSN_CT_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
int ret;
u8 queue_stat;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_CT]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_CT];
if (!tsnops->ct_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = NLA_PARSE_NESTED(cta, TSN_CT_ATTR_MAX,
na, ct_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
queue_stat = nla_get_u8(cta[TSN_CT_ATTR_QUEUE_STATE]);
ret = tsnops->ct_set(netdev, queue_stat);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
return 0;
}
static int tsn_cbgen_set(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na;
struct nlattr *cbgena[TSN_CBGEN_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
int ret;
u32 index;
struct tsn_seq_gen_conf sg_conf;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_CBGEN]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_CBGEN];
if (!tsnops->cbgen_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = NLA_PARSE_NESTED(cbgena, TSN_CBGEN_ATTR_MAX,
na, cbgen_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
index = nla_get_u32(cbgena[TSN_CBGEN_ATTR_INDEX]);
memset(&sg_conf, 0, sizeof(struct tsn_seq_gen_conf));
sg_conf.iport_mask = nla_get_u8(cbgena[TSN_CBGEN_ATTR_PORT_MASK]);
sg_conf.split_mask = nla_get_u8(cbgena[TSN_CBGEN_ATTR_SPLIT_MASK]);
sg_conf.seq_len = nla_get_u8(cbgena[TSN_CBGEN_ATTR_SEQ_LEN]);
sg_conf.seq_num = nla_get_u32(cbgena[TSN_CBGEN_ATTR_SEQ_NUM]);
ret = tsnops->cbgen_set(netdev, index, &sg_conf);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
return 0;
}
static int tsn_cbrec_set(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na;
struct nlattr *cbreca[TSN_CBREC_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
int ret;
u32 index;
struct tsn_seq_rec_conf sr_conf;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_CBREC]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_CBREC];
if (!tsnops->cbrec_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = NLA_PARSE_NESTED(cbreca, TSN_CBREC_ATTR_MAX,
na, cbrec_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
index = nla_get_u32(cbreca[TSN_CBREC_ATTR_INDEX]);
memset(&sr_conf, 0, sizeof(struct tsn_seq_rec_conf));
sr_conf.seq_len = nla_get_u8(cbreca[TSN_CBREC_ATTR_SEQ_LEN]);
sr_conf.his_len = nla_get_u8(cbreca[TSN_CBREC_ATTR_HIS_LEN]);
sr_conf.rtag_pop_en = nla_get_flag(cbreca[TSN_CBREC_ATTR_TAG_POP_EN]);
ret = tsnops->cbrec_set(netdev, index, &sr_conf);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
return 0;
}
static int tsn_cbstatus_get(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na;
struct nlattr *cba[TSN_CBSTAT_ATTR_MAX + 1];
struct nlattr *cbattr;
struct net_device *netdev;
const struct tsn_ops *tsnops;
struct sk_buff *rep_skb;
int ret;
unsigned int index;
struct genlmsghdr *genlhdr;
struct tsn_cb_status cbstat;
struct tsn_port *port;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
/* Get status data from device */
genlhdr = info->genlhdr;
memset(&cbstat, 0, sizeof(struct tsn_cb_status));
if (!tsnops->cb_get) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
na = info->attrs[TSN_ATTR_CBSTAT];
ret = NLA_PARSE_NESTED(cba, TSN_CBSTAT_ATTR_MAX,
na, cbstat_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
index = nla_get_u32(cba[TSN_CBSTAT_ATTR_INDEX]);
ret = tsnops->cb_get(netdev, index, &cbstat);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
/* Form netlink reply data */
ret = tsn_prepare_reply(info, genlhdr->cmd,
&rep_skb, NLMSG_ALIGN(MAX_ATTR_SIZE));
if (ret < 0)
return ret;
if (nla_put_string(rep_skb, TSN_ATTR_IFNAME, netdev->name))
return -EMSGSIZE;
cbattr = nla_nest_start_noflag(rep_skb, TSN_ATTR_CBSTAT);
if (!cbattr)
return -EMSGSIZE;
if (nla_put_u8(rep_skb, TSN_CBSTAT_ATTR_GEN_REC, cbstat.gen_rec) ||
nla_put_u8(rep_skb, TSN_CBSTAT_ATTR_ERR, cbstat.err) ||
nla_put_u32(rep_skb, TSN_CBSTAT_ATTR_SEQ_NUM,
cbstat.seq_num) ||
nla_put_u8(rep_skb, TSN_CBSTAT_ATTR_SEQ_LEN, cbstat.seq_len) ||
nla_put_u8(rep_skb, TSN_CBSTAT_ATTR_SPLIT_MASK,
cbstat.split_mask) ||
nla_put_u8(rep_skb, TSN_CBSTAT_ATTR_PORT_MASK,
cbstat.iport_mask) ||
nla_put_u8(rep_skb, TSN_CBSTAT_ATTR_HIS_LEN, cbstat.his_len) ||
nla_put_u32(rep_skb, TSN_CBSTAT_ATTR_SEQ_HIS,
cbstat.seq_his))
return -EMSGSIZE;
nla_nest_end(rep_skb, cbattr);
return tsn_send_reply(rep_skb, info);
}
static int tsn_dscp_set(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *na;
struct nlattr *dscpa[TSN_DSCP_ATTR_MAX + 1];
struct net_device *netdev;
const struct tsn_ops *tsnops;
int ret;
bool enable = 0;
struct tsn_port *port;
int dscp_ix;
struct tsn_qos_switch_dscp_conf dscp_conf;
port = tsn_init_check(info, &netdev);
if (!port)
return -ENODEV;
tsnops = port->tsnops;
if (!info->attrs[TSN_ATTR_DSCP]) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
na = info->attrs[TSN_ATTR_DSCP];
if (!tsnops->dscp_set) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EPERM);
return -1;
}
ret = NLA_PARSE_NESTED(dscpa, TSN_DSCP_ATTR_MAX,
na, dscp_policy);
if (ret) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, -EINVAL);
return -EINVAL;
}
enable = 1;
if (dscpa[TSN_DSCP_ATTR_DISABLE])
enable = 0;
dscp_ix = nla_get_u32(dscpa[TSN_DSCP_ATTR_INDEX]);
dscp_conf.cos = nla_get_u32(dscpa[TSN_DSCP_ATTR_COS]);
dscp_conf.dpl = nla_get_u32(dscpa[TSN_DSCP_ATTR_DPL]);
ret = tsnops->dscp_set(netdev, enable, dscp_ix, &dscp_conf);
if (ret < 0) {
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, ret);
return ret;
}
tsn_simple_reply(info, TSN_CMD_REPLY,
netdev->name, 0);
return 0;
}
static const struct genl_ops tsnnl_ops[] = {
{
.cmd = TSN_CMD_ECHO,
.doit = tsn_echo_cmd,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CAP_GET,
.doit = tsn_cap_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QBV_SET,
.doit = tsn_qbv_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QBV_GET,
.doit = tsn_qbv_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QBV_GET_STATUS,
.doit = tsn_qbv_status_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CB_STREAMID_SET,
.doit = tsn_cb_streamid_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CB_STREAMID_GET,
.doit = tsn_cb_streamid_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CB_STREAMID_GET_COUNTS,
.doit = tsn_cb_streamid_counters_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_CAP_GET,
.doit = tsn_qci_cap_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_SFI_SET,
.doit = tsn_qci_sfi_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_SFI_GET,
.doit = tsn_qci_sfi_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_SFI_GET_COUNTS,
.doit = tsn_qci_sfi_counters_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_SGI_SET,
.doit = tsn_qci_sgi_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_SGI_GET,
.doit = tsn_qci_sgi_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_SGI_GET_STATUS,
.doit = tsn_qci_sgi_status_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_FMI_SET,
.doit = tsn_qci_fmi_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QCI_FMI_GET,
.doit = tsn_qci_fmi_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CBS_SET,
.doit = tsn_cbs_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CBS_GET,
.doit = tsn_cbs_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QBU_SET,
.doit = tsn_qbu_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_QBU_GET_STATUS,
.doit = tsn_qbu_get_status,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_TSD_SET,
.doit = tsn_tsd_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_TSD_GET,
.doit = tsn_tsd_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CT_SET,
.doit = tsn_ct_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CBGEN_SET,
.doit = tsn_cbgen_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CBREC_SET,
.doit = tsn_cbrec_set,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_CBSTAT_GET,
.doit = tsn_cbstatus_get,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = TSN_CMD_DSCP_SET,
.doit = tsn_dscp_set,
.flags = GENL_ADMIN_PERM,
},
};
static const struct genl_multicast_group tsn_mcgrps[] = {
[TSN_MCGRP_QBV] = { .name = TSN_MULTICAST_GROUP_QBV},
[TSN_MCGRP_QCI] = { .name = TSN_MULTICAST_GROUP_QCI},
};
static struct genl_family tsn_family = {
.name = TSN_GENL_NAME,
.version = TSN_GENL_VERSION,
.maxattr = TSN_CMD_ATTR_MAX,
.module = THIS_MODULE,
.netnsok = true,
.ops = tsnnl_ops,
.n_ops = ARRAY_SIZE(tsnnl_ops),
.mcgrps = tsn_mcgrps,
.n_mcgrps = ARRAY_SIZE(tsn_mcgrps),
};
int tsn_port_register(struct net_device *netdev,
struct tsn_ops *tsnops, u16 groupid)
{
struct tsn_port *port;
if (list_empty(&port_list)) {
INIT_LIST_HEAD(&port_list);
} else {
list_for_each_entry(port, &port_list, list) {
if (port->netdev == netdev) {
pr_info("TSN device already registered!\n");
return -1;
}
}
}
port = kzalloc(sizeof(*port), GFP_KERNEL);
if (!port)
return -1;
port->netdev = netdev;
port->groupid = groupid;
port->tsnops = tsnops;
port->nd.dev = netdev;
if (groupid < GROUP_OFFSET_SWITCH)
port->type = TSN_ENDPOINT;
else
port->type = TSN_SWITCH;
list_add_tail(&port->list, &port_list);
if (tsnops && tsnops->device_init)
port->tsnops->device_init(netdev);
return 0;
}
EXPORT_SYMBOL(tsn_port_register);
void tsn_port_unregister(struct net_device *netdev)
{
struct tsn_port *p;
list_for_each_entry(p, &port_list, list) {
if (!p || !p->netdev)
continue;
if (p->netdev == netdev) {
if (p->tsnops->device_deinit)
p->tsnops->device_deinit(netdev);
list_del(&p->list);
kfree(p);
break;
}
}
}
EXPORT_SYMBOL(tsn_port_unregister);
static int tsn_multicast_to_user(unsigned long event,
struct tsn_notifier_info *tsn_info)
{
struct sk_buff *skb;
struct genlmsghdr *nlh;
int res;
struct tsn_qbv_conf *qbvdata;
/* If new attributes are added, please revisit this allocation */
skb = genlmsg_new(sizeof(*tsn_info), GFP_KERNEL);
if (!skb) {
pr_err("Allocation failure.\n");
return -ENOMEM;
}
switch (event) {
case TSN_QBV_CONFIGCHANGETIME_ARRIVE:
nlh = genlmsg_put(skb, 0, 1, &tsn_family,
GFP_KERNEL, TSN_CMD_QBV_SET);
qbvdata = &tsn_info->ntdata.qbv_notify;
res = NLA_PUT_U64(skb, TSN_QBV_ATTR_CTRL_BASETIME,
qbvdata->admin.base_time);
if (res) {
pr_err("put data failure!\n");
goto done;
}
res = nla_put_u32(skb, TSN_QBV_ATTR_CTRL_CYCLETIME,
qbvdata->admin.cycle_time);
if (res) {
pr_err("put data failure!\n");
goto done;
}
if (qbvdata->gate_enabled)
res = nla_put_flag(skb, TSN_QBV_ATTR_ENABLE +
TSN_QBV_ATTR_CTRL_MAX);
else
res = nla_put_flag(skb, TSN_QBV_ATTR_DISABLE +
TSN_QBV_ATTR_CTRL_MAX);
if (res) {
pr_err("put data failure!\n");
goto done;
}
res = nla_put_u32(skb, TSN_QBV_ATTR_CTRL_UNSPEC,
tsn_info->dev->ifindex);
if (res) {
pr_err("put data failure!\n");
goto done;
}
break;
default:
pr_info("event not supportted!\n");
break;
}
(void)genlmsg_end(skb, nlh);
res = genlmsg_multicast_allns(&tsn_family, skb, 0,
TSN_MCGRP_QBV, GFP_KERNEL);
skb = NULL;
if (res && res != -ESRCH) {
pr_err("genlmsg_multicast_allns error: %d\n", res);
goto done;
}
if (res == -ESRCH)
res = 0;
done:
if (skb) {
nlmsg_free(skb);
skb = NULL;
}
return res;
}
/* called with RTNL or RCU */
static int tsn_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct tsn_notifier_info *tsn_info;
int err = NOTIFY_DONE;
switch (event) {
case TSN_QBV_CONFIGCHANGETIME_ARRIVE:
tsn_info = ptr;
err = tsn_multicast_to_user(event, tsn_info);
if (err) {
err = notifier_from_errno(err);
break;
}
break;
default:
pr_info("event not supportted!\n");
break;
}
return err;
}
static struct notifier_block tsn_notifier = {
.notifier_call = tsn_event,
};
static int __init tsn_genetlink_init(void)
{
int ret;
pr_info("tsn generic netlink module v%d init...\n", TSN_GENL_VERSION);
ret = genl_register_family(&tsn_family);
if (ret != 0) {
pr_info("failed to init tsn generic netlink example module\n");
return ret;
}
register_tsn_notifier(&tsn_notifier);
return 0;
}
static void __exit tsn_genetlink_exit(void)
{
int ret;
ret = genl_unregister_family(&tsn_family);
if (ret != 0)
pr_info("failed to unregister family:%i\n", ret);
unregister_tsn_notifier(&tsn_notifier);
}
module_init(tsn_genetlink_init);
module_exit(tsn_genetlink_exit);
MODULE_LICENSE("GPL");