diff --git a/include/net/tsn.h b/include/net/tsn.h new file mode 100644 index 000000000000..f1c53bae76be --- /dev/null +++ b/include/net/tsn.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* Copyright 2017-2019 NXP */ + +#ifndef __TSN_H__ +#define __TSN_H__ + +#include +#include + +enum tsn_notifier_type { + TSN_QBV_CONFIGCHANGETIME_ARRIVE = 1, +}; + +struct tsn_notifier_info { + struct net_device *dev; + union { + struct tsn_qbv_conf qbv_notify; + struct tsn_qci_psfp_sgi_conf qci_notify; + } ntdata; +}; + +static inline struct net_device * +tsn_notifier_info_to_dev(const struct tsn_notifier_info *info) +{ + return info->dev; +} + +struct tsn_ops { + void (*device_init)(struct net_device *ndev); + void (*device_deinit)(struct net_device *ndev); + u32 (*get_capability)(struct net_device *ndev); + /* Qbv standard */ + int (*qbv_set)(struct net_device *ndev, struct tsn_qbv_conf *qbvconf); + int (*qbv_get)(struct net_device *ndev, struct tsn_qbv_conf *qbvconf); + int (*qbv_get_status)(struct net_device *ndev, + struct tsn_qbv_status *qbvstat); + int (*cb_streamid_set)(struct net_device *ndev, u32 index, + bool enable, struct tsn_cb_streamid *sid); + int (*cb_streamid_get)(struct net_device *ndev, u32 index, + struct tsn_cb_streamid *sid); + int (*cb_streamid_counters_get)(struct net_device *ndev, u32 index, + struct tsn_cb_streamid_counters *sidcounter); + int (*qci_get_maxcap)(struct net_device *ndev, + struct tsn_qci_psfp_stream_param *qcicapa); + int (*qci_sfi_set)(struct net_device *ndev, u32 index, bool enable, + struct tsn_qci_psfp_sfi_conf *sficonf); + /* return: 0 stream filter instance not valid + * 1 stream filter instance valid + * -1 error happened + */ + int (*qci_sfi_get)(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sfi_conf *sficonf); + int (*qci_sfi_counters_get)(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sfi_counters *sficounter); + int (*qci_sgi_set)(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sgi_conf *sgiconf); + int (*qci_sgi_get)(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sgi_conf *sgiconf); + int (*qci_sgi_status_get)(struct net_device *ndev, u16 index, + struct tsn_psfp_sgi_status *sgistat); + int (*qci_fmi_set)(struct net_device *ndev, u32 index, bool enable, + struct tsn_qci_psfp_fmi *fmi); + int (*qci_fmi_get)(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_fmi *fmi, + struct tsn_qci_psfp_fmi_counters *counters); + int (*cbs_set)(struct net_device *ndev, u8 tc, u8 bw); + int (*cbs_get)(struct net_device *ndev, u8 tc); + /* To set a 8 bits vector shows 8 traffic classes + * preemtable(1) or express(0) + */ + int (*qbu_set)(struct net_device *ndev, u8 ptvector); + /* To get port preemtion status */ + int (*qbu_get)(struct net_device *ndev, + struct tsn_preempt_status *preemptstat); + int (*tsd_set)(struct net_device *ndev, struct tsn_tsd *tsd); + int (*tsd_get)(struct net_device *ndev, struct tsn_tsd_status *stats); + int (*ct_set)(struct net_device *ndev, u8 cut_thru); + int (*cbgen_set)(struct net_device *ndev, u32 index, + struct tsn_seq_gen_conf *seqgen); + int (*cbrec_set)(struct net_device *ndev, u32 index, + struct tsn_seq_rec_conf *seqrec); + int (*cb_get)(struct net_device *ndev, u32 index, + struct tsn_cb_status *c); + int (*dscp_set)(struct net_device *ndev, bool enable, + const u8 dscp_ix, + struct tsn_qos_switch_dscp_conf *c); +}; + +enum ethdev_type { + TSN_SWITCH, + TSN_ENDPOINT, +}; + +#define GROUP_OFFSET_SWITCH 256 + +struct tsn_port { + u16 groupid; + struct tsn_ops *tsnops; + struct net_device *netdev; + struct list_head list; + enum ethdev_type type; + u8 tc_nums; + struct tsn_notifier_info nd; +}; + +struct tsn_port *tsn_get_port(struct net_device *ndev); +int register_tsn_notifier(struct notifier_block *nb); +int unregister_tsn_notifier(struct notifier_block *nb); +int call_tsn_notifiers(unsigned long val, struct net_device *dev, + struct tsn_notifier_info *info); +int tsn_port_register(struct net_device *netdev, + struct tsn_ops *tsnops, u16 groupid); +void tsn_port_unregister(struct net_device *netdev); +#endif diff --git a/include/uapi/linux/tsn.h b/include/uapi/linux/tsn.h new file mode 100644 index 000000000000..2c65398e8c18 --- /dev/null +++ b/include/uapi/linux/tsn.h @@ -0,0 +1,1207 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ +/* Copyright 2017-2019 NXP */ + +#ifndef __UAPI_GENL_TSN_H +#define __UAPI_GENL_TSN_H + +#define TSN_GENL_NAME "TSN_GEN_CTRL" +#define TSN_GENL_VERSION 0x1 + +#define MAX_USER_SIZE 0 +#define MAX_ATTR_SIZE 3072 +#define MAX_TOTAL_MSG_SIZE (MAX_USER_SIZE + MAX_ATTR_SIZE) +#define MAX_ENTRY_SIZE 2048 +#define MAX_ENTRY_NUMBER 128 +#define MAX_IFNAME_COUNT 64 + +#define TSN_MULTICAST_GROUP_QBV "qbv" +#define TSN_MULTICAST_GROUP_QCI "qci" + +/* multicast groups */ +enum tsn_multicast_groups { + TSN_MCGRP_QBV, + TSN_MCGRP_QCI, + TSN_MCGRP_MAX +}; + +enum tsn_capability { + TSN_CAP_QBV = 0x1, + TSN_CAP_QCI = 0x2, + TSN_CAP_QBU = 0x4, + TSN_CAP_CBS = 0x8, /* Credit-based Shapter Qav */ + TSN_CAP_CB = 0x10, /* 8021CB redundancy and replication */ + TSN_CAP_TBS = 0x20, /* Time Based schedule */ + TSN_CAP_CTH = 0x40, /* cut through */ +}; + +/* + * Commands sent from userspace + * Not versioned. New commands should only be inserted at the enum's end + * prior to __TSN_CMD_MAX + */ + +enum { + TSN_CMD_UNSPEC = 0, /* Reserved */ + TSN_CMD_QBV_SET, + TSN_CMD_QBV_GET, + TSN_CMD_QBV_GET_STATUS, + TSN_CMD_CB_STREAMID_SET, + TSN_CMD_CB_STREAMID_GET, + TSN_CMD_CB_STREAMID_GET_COUNTS, + TSN_CMD_QCI_CAP_GET, /* Qci capability get length capability get */ + TSN_CMD_QCI_SFI_SET, + TSN_CMD_QCI_SFI_GET, + TSN_CMD_QCI_SFI_GET_COUNTS, + TSN_CMD_QCI_SGI_SET, + TSN_CMD_QCI_SGI_GET, + TSN_CMD_QCI_SGI_GET_STATUS, + TSN_CMD_QCI_FMI_SET, + TSN_CMD_QCI_FMI_GET, + TSN_CMD_CBS_SET, + TSN_CMD_CBS_GET, + TSN_CMD_QBU_SET, + TSN_CMD_QBU_GET_STATUS, + TSN_CMD_QAV_SET_CBS, + TSN_CMD_QAV_GET_CBS, + TSN_CMD_TSD_SET, + TSN_CMD_TSD_GET, + TSN_CMD_CT_SET, + TSN_CMD_CBGEN_SET, + TSN_CMD_CBREC_SET, + TSN_CMD_CBSTAT_GET, + TSN_CMD_PCPMAP_SET_UNUSE, + TSN_CMD_DSCP_SET, + TSN_CMD_ECHO, /* user->kernel request/get-response */ + TSN_CMD_REPLY, /* kernel->user event */ + TSN_CMD_CAP_GET, + __TSN_CMD_MAX, +}; +#define TSN_CMD_MAX (__TSN_CMD_MAX - 1) + + +enum { + TSN_CMD_ATTR_UNSPEC = 0, + TSN_CMD_ATTR_MESG, /* demo message */ + TSN_CMD_ATTR_DATA, /* demo data */ + TSN_ATTR_IFNAME, + TSN_ATTR_PORT_NUMBER, + TSN_ATTR_QBV, + TSN_ATTR_STREAM_IDENTIFY, /* stream identify */ + TSN_ATTR_QCI_SP, /* psfp port capbility parameters */ + TSN_ATTR_QCI_SFI, /* psfp stream filter instance */ + TSN_ATTR_QCI_SGI, /* psfp stream gate instance */ + TSN_ATTR_QCI_FMI, /* psfp flow meter instance */ + TSN_ATTR_CBS, /* credit-based shaper */ + TSN_ATTR_TSD, /* Time Specific Departure */ + TSN_ATTR_QBU, /* preemption */ + TSN_ATTR_CT, /* cut through */ + TSN_ATTR_CBGEN, /* 802.1CB sequence generate */ + TSN_ATTR_CBREC, /* 802.1CB sequence recover */ + TSN_ATTR_CBSTAT, /* 802.1CB status */ + TSN_ATTR_PCPMAP_UNUSE, + TSN_ATTR_DSCP, + TSN_ATTR_CAP, /* TSN capbility */ + __TSN_CMD_ATTR_MAX, +}; +#define TSN_CMD_ATTR_MAX (__TSN_CMD_ATTR_MAX - 1) + +enum { + TSN_CAP_ATTR_UNSPEC, + TSN_CAP_ATTR_QBV, + TSN_CAP_ATTR_QCI, + TSN_CAP_ATTR_QBU, + TSN_CAP_ATTR_CBS, + TSN_CAP_ATTR_CB, + TSN_CAP_ATTR_TBS, + TSN_CAP_ATTR_CTH, + __TSN_CAP_ATTR_MAX, + TSN_CAP_ATTR_MAX = __TSN_CAP_ATTR_MAX - 1, +}; + +enum { + TSN_QBU_ATTR_UNSPEC, + TSN_QBU_ATTR_ADMIN_STATE, + TSN_QBU_ATTR_HOLD_ADVANCE, + TSN_QBU_ATTR_RELEASE_ADVANCE, + TSN_QBU_ATTR_ACTIVE, + TSN_QBU_ATTR_HOLD_REQUEST, + __TSN_QBU_ATTR_MAX, + TSN_QBU_ATTR_MAX = __TSN_QBU_ATTR_MAX - 1, +}; + +enum { + TSN_CBS_ATTR_UNSPEC, + TSN_CBS_ATTR_TC_INDEX, + TSN_CBS_ATTR_BW, + __TSN_CBS_ATTR_MAX, + TSN_CBS_ATTR_MAX = __TSN_CBS_ATTR_MAX - 1, +}; + +enum { + TSN_TSD_ATTR_UNSPEC, + TSN_TSD_ATTR_DISABLE, + TSN_TSD_ATTR_ENABLE, + TSN_TSD_ATTR_PERIOD, + TSN_TSD_ATTR_MAX_FRM_NUM, + TSN_TSD_ATTR_CYCLE_NUM, + TSN_TSD_ATTR_LOSS_STEPS, + TSN_TSD_ATTR_SYN_IMME, + __TSN_TSD_ATTR_MAX, + TSN_TSD_ATTR_MAX = __TSN_TSD_ATTR_MAX - 1, +}; + +enum { + TSN_STREAMID_ATTR_UNSPEC, + TSN_STREAMID_ATTR_INDEX, + TSN_STREAMID_ATTR_ENABLE, + TSN_STREAMID_ATTR_DISABLE, + TSN_STREAMID_ATTR_STREAM_HANDLE, + TSN_STREAMID_ATTR_IFOP, + TSN_STREAMID_ATTR_OFOP, + TSN_STREAMID_ATTR_IFIP, + TSN_STREAMID_ATTR_OFIP, + TSN_STREAMID_ATTR_TYPE, + TSN_STREAMID_ATTR_NDMAC, + TSN_STREAMID_ATTR_NTAGGED, + TSN_STREAMID_ATTR_NVID, + TSN_STREAMID_ATTR_SMAC, + TSN_STREAMID_ATTR_STAGGED, + TSN_STREAMID_ATTR_SVID, + TSN_STREAMID_ATTR_COUNTERS_PSI, + TSN_STREAMID_ATTR_COUNTERS_PSO, + TSN_STREAMID_ATTR_COUNTERS_PSPPI, + TSN_STREAMID_ATTR_COUNTERS_PSPPO, + __TSN_STREAMID_ATTR_MAX, + TSN_STREAMID_ATTR_MAX = __TSN_STREAMID_ATTR_MAX - 1, +}; + +enum { + TSN_QCI_STREAM_ATTR_UNSPEC = 0, + TSN_QCI_STREAM_ATTR_MAX_SFI, + TSN_QCI_STREAM_ATTR_MAX_SGI, + TSN_QCI_STREAM_ATTR_MAX_FMI, + TSN_QCI_STREAM_ATTR_SLM, + __TSN_QCI_STREAM_ATTR_MAX, + TSN_QCI_STREAM_ATTR_MAX = __TSN_QCI_STREAM_ATTR_MAX - 1, +}; + +enum { + TSN_QCI_SFI_ATTR_UNSPEC = 0, + TSN_QCI_SFI_ATTR_INDEX, + TSN_QCI_SFI_ATTR_ENABLE, + TSN_QCI_SFI_ATTR_DISABLE, + TSN_QCI_SFI_ATTR_STREAM_HANDLE, + TSN_QCI_SFI_ATTR_PRIO_SPEC, + TSN_QCI_SFI_ATTR_GATE_ID, + TSN_QCI_SFI_ATTR_FILTER_TYPE, + TSN_QCI_SFI_ATTR_FLOW_ID, + TSN_QCI_SFI_ATTR_MAXSDU, + TSN_QCI_SFI_ATTR_COUNTERS, + TSN_QCI_SFI_ATTR_OVERSIZE_ENABLE, + TSN_QCI_SFI_ATTR_OVERSIZE, + __TSN_QCI_SFI_ATTR_MAX, + TSN_QCI_SFI_ATTR_MAX = __TSN_QCI_SFI_ATTR_MAX - 1, +}; + +enum { + TSN_QCI_SFI_ATTR_COUNTERS_UNSPEC = 0, + TSN_QCI_SFI_ATTR_MATCH, + TSN_QCI_SFI_ATTR_PASS, + TSN_QCI_SFI_ATTR_DROP, + TSN_QCI_SFI_ATTR_SDU_DROP, + TSN_QCI_SFI_ATTR_SDU_PASS, + TSN_QCI_SFI_ATTR_RED, + __TSN_QCI_SFI_ATTR_COUNT_MAX, + TSN_QCI_SFI_ATTR_COUNT_MAX = __TSN_QCI_SFI_ATTR_COUNT_MAX - 1, +}; + +enum { + TSN_QCI_SGI_ATTR_UNSPEC = 0, + TSN_QCI_SGI_ATTR_INDEX, + TSN_QCI_SGI_ATTR_ENABLE, + TSN_QCI_SGI_ATTR_DISABLE, + TSN_QCI_SGI_ATTR_CONFCHANGE, + TSN_QCI_SGI_ATTR_IRXEN, /* Invalid rx enable*/ + TSN_QCI_SGI_ATTR_IRX, + TSN_QCI_SGI_ATTR_OEXEN, /* Octet exceed enable */ + TSN_QCI_SGI_ATTR_OEX, + TSN_QCI_SGI_ATTR_ADMINENTRY, + TSN_QCI_SGI_ATTR_OPERENTRY, + TSN_QCI_SGI_ATTR_CCTIME, /* config change time */ + TSN_QCI_SGI_ATTR_TICKG, + TSN_QCI_SGI_ATTR_CUTIME, + TSN_QCI_SGI_ATTR_CPENDING, + TSN_QCI_SGI_ATTR_CCERROR, + __TSN_QCI_SGI_ATTR_MAX, + TSN_QCI_SGI_ATTR_MAX = __TSN_QCI_SGI_ATTR_MAX - 1, +}; + +enum { + TSN_SGI_ATTR_CTRL_UNSPEC = 0, + TSN_SGI_ATTR_CTRL_INITSTATE, + TSN_SGI_ATTR_CTRL_LEN, + TSN_SGI_ATTR_CTRL_CYTIME, + TSN_SGI_ATTR_CTRL_CYTIMEEX, + TSN_SGI_ATTR_CTRL_BTIME, + TSN_SGI_ATTR_CTRL_INITIPV, + TSN_SGI_ATTR_CTRL_GCLENTRY, + __TSN_SGI_ATTR_CTRL_MAX, + TSN_SGI_ATTR_CTRL_MAX = __TSN_SGI_ATTR_CTRL_MAX - 1, +}; + +enum { + TSN_SGI_ATTR_GCL_UNSPEC = 0, + TSN_SGI_ATTR_GCL_GATESTATE, + TSN_SGI_ATTR_GCL_IPV, + TSN_SGI_ATTR_GCL_INTERVAL, + TSN_SGI_ATTR_GCL_OCTMAX, + __TSN_SGI_ATTR_GCL_MAX, + TSN_SGI_ATTR_GCL_MAX = __TSN_SGI_ATTR_GCL_MAX - 1, +}; + +enum { + TSN_QCI_FMI_ATTR_UNSPEC = 0, + TSN_QCI_FMI_ATTR_INDEX, + TSN_QCI_FMI_ATTR_ENABLE, + TSN_QCI_FMI_ATTR_DISABLE, + TSN_QCI_FMI_ATTR_CIR, + TSN_QCI_FMI_ATTR_CBS, + TSN_QCI_FMI_ATTR_EIR, + TSN_QCI_FMI_ATTR_EBS, + TSN_QCI_FMI_ATTR_CF, + TSN_QCI_FMI_ATTR_CM, + TSN_QCI_FMI_ATTR_DROPYL, + TSN_QCI_FMI_ATTR_MAREDEN, + TSN_QCI_FMI_ATTR_MARED, + TSN_QCI_FMI_ATTR_COUNTERS, + __TSN_QCI_FMI_ATTR_MAX, + TSN_QCI_FMI_ATTR_MAX = __TSN_QCI_FMI_ATTR_MAX - 1, +}; + +enum { + TSN_QBV_ATTR_UNSPEC, + TSN_QBV_ATTR_ENABLE, + TSN_QBV_ATTR_DISABLE, + TSN_QBV_ATTR_CONFIGCHANGE, + TSN_QBV_ATTR_CONFIGCHANGETIME, + TSN_QBV_ATTR_MAXSDU, + TSN_QBV_ATTR_GRANULARITY, + TSN_QBV_ATTR_CURRENTTIME, + TSN_QBV_ATTR_CONFIGPENDING, + TSN_QBV_ATTR_CONFIGCHANGEERROR, + TSN_QBV_ATTR_ADMINENTRY, + TSN_QBV_ATTR_OPERENTRY, + TSN_QBV_ATTR_LISTMAX, + __TSN_QBV_ATTR_MAX, + TSN_QBV_ATTR_MAX = __TSN_QBV_ATTR_MAX - 1, +}; + +enum { + TSN_QBV_ATTR_CTRL_UNSPEC, + TSN_QBV_ATTR_CTRL_LISTCOUNT, + TSN_QBV_ATTR_CTRL_GATESTATE, + TSN_QBV_ATTR_CTRL_CYCLETIME, + TSN_QBV_ATTR_CTRL_CYCLETIMEEXT, + TSN_QBV_ATTR_CTRL_BASETIME, + TSN_QBV_ATTR_CTRL_LISTENTRY, + __TSN_QBV_ATTR_CTRL_MAX, + TSN_QBV_ATTR_CTRL_MAX = __TSN_QBV_ATTR_CTRL_MAX - 1, +}; + +enum { + TSN_QBV_ATTR_ENTRY_UNSPEC, + TSN_QBV_ATTR_ENTRY_ID, + TSN_QBV_ATTR_ENTRY_GC, + TSN_QBV_ATTR_ENTRY_TM, + __TSN_QBV_ATTR_ENTRY_MAX, + TSN_QBV_ATTR_ENTRY_MAX = __TSN_QBV_ATTR_ENTRY_MAX - 1, +}; + +enum { + TSN_CT_ATTR_UNSPEC, + TSN_CT_ATTR_QUEUE_STATE, + __TSN_CT_ATTR_MAX, + TSN_CT_ATTR_MAX = __TSN_CT_ATTR_MAX - 1, +}; + +enum { + TSN_CBGEN_ATTR_UNSPEC, + TSN_CBGEN_ATTR_INDEX, + TSN_CBGEN_ATTR_PORT_MASK, + TSN_CBGEN_ATTR_SPLIT_MASK, + TSN_CBGEN_ATTR_SEQ_LEN, + TSN_CBGEN_ATTR_SEQ_NUM, + __TSN_CBGEN_ATTR_MAX, + TSN_CBGEN_ATTR_MAX = __TSN_CBGEN_ATTR_MAX - 1, +}; + +enum { + TSN_CBREC_ATTR_UNSPEC, + TSN_CBREC_ATTR_INDEX, + TSN_CBREC_ATTR_SEQ_LEN, + TSN_CBREC_ATTR_HIS_LEN, + TSN_CBREC_ATTR_TAG_POP_EN, + __TSN_CBREC_ATTR_MAX, + TSN_CBREC_ATTR_MAX = __TSN_CBREC_ATTR_MAX - 1, +}; + +enum { + TSN_CBSTAT_ATTR_UNSPEC, + TSN_CBSTAT_ATTR_INDEX, + TSN_CBSTAT_ATTR_GEN_REC, + TSN_CBSTAT_ATTR_ERR, + TSN_CBSTAT_ATTR_SEQ_NUM, + TSN_CBSTAT_ATTR_SEQ_LEN, + TSN_CBSTAT_ATTR_SPLIT_MASK, + TSN_CBSTAT_ATTR_PORT_MASK, + TSN_CBSTAT_ATTR_HIS_LEN, + TSN_CBSTAT_ATTR_SEQ_HIS, + __TSN_CBSTAT_ATTR_MAX, + TSN_CBSTAT_ATTR_MAX = __TSN_CBSTAT_ATTR_MAX - 1, +}; + +enum { + TSN_DSCP_ATTR_UNSPEC, + TSN_DSCP_ATTR_DISABLE, + TSN_DSCP_ATTR_INDEX, + TSN_DSCP_ATTR_COS, + TSN_DSCP_ATTR_DPL, + __TSN_DSCP_ATTR_MAX, + TSN_DSCP_ATTR_MAX = __TSN_DSCP_ATTR_MAX - 1, +}; + +#define ptptime_t __u64 + +#define MAX_QUEUE_CNT 8 + +struct tsn_preempt_status { + /* The value of admin_state shows a 8-bits vector value for showing + * the framePreemptionAdminStatus parameter and PreemptionPriority + * for the traffic class. Bit-7 is the highest priority traffic class + * and the bit-0 is the lowest priority traffic class. + * The bit is express (0) and is preemptible (1). + */ + __u8 admin_state; + /* The value of the holdAdvance parameter for the port in nanoseconds. + * There is no default value; the holdAdvance is a property of the + * underlying MAC." This parameter corresponds to the holdAdvance + * parameter in 802.1Qbu. + */ + __u32 hold_advance; + + /* The value of the releaseAdvance parameter for the port in + * nanoseconds. There is no default value; the releaseAdvance is a + * property of the underlying MAC." This parameter corresponds to the + * releaseAdvance parameter in 802.1Qbu. + */ + __u32 release_advance; + + /* The value is active (TRUE) when preemption is operationally active + * for the port, and idle (FALSE) otherwise. This parameter corresponds + * to the preemptionActive parameter in 802.1Qbu. + */ + __u8 preemption_active; + + /* The value is hold (1) when the sequence of gate operations for + * the port has executed a Set-And-Hold-MAC operation, and release + * (2) when the sequence of gate operations has executed a + * Set-And-Release-MAC operation. The value of this object is release + * (FALSE) on system initialization. This parameter corresponds to the + * holdRequest parameter in 802.1Qbu. + */ + __u8 hold_request; +}; + +enum tsn_tx_mode { + TX_MODE_STRICT, + TX_MODE_CBS, + TX_MODE_ETS, + TX_MODE_VENDOR_DEFINE = 255, +}; + +#define QUEUE_TX_MASK ((1 << TX_MODE_STRICT) | (1 << TX_MODE_CBS) \ + | (1 << TX_MODE_ETS) | (1 << TX_MODE_VENDOR_DEFINE)) + +struct cbs_status { + __u8 delta_bw; /* percentage, 0~100 */ + __u32 idleslope; + __s32 sendslope; + __u32 maxframesize; + __u32 hicredit; + __s32 locredit; + __u32 maxninference; +}; + +struct tx_queue { + /* tx_queue_capbility shows the queue's capability mask. + * refer the enum tsn_tx_mode + */ + __u8 capability; + + /* tx_queue_mode is current queue working mode */ + __u8 mode; + + /* prio is showing the queue priority */ + __u8 prio; + + /* mstat shows the status data of cbs or priority */ + union { + struct cbs_status cbs; + }; +}; + +struct port_status { + /* txqueue_cnt shows how many queues in this port */ + __u8 queue_cnt; + + /* max_rate(Mbit/s) is the port transmit rate current port is setting */ + __u32 max_rate; + + /* tsn_capability mask the tsn capability */ + __u32 tsn_capability; +}; + +enum tsn_cb_streamid_type { + STREAMID_RESERVED = 0, + /* Null Stream identification */ + STREAMID_NULL, + /* Source MAC and VLAN Stream identification */ + STREAMID_SMAC_VLAN, + /* Active Destination MAC and VLAN stream identification */ + STREAMID_DMAC_VLAN, + /* IP stream identification */ + STREAMID_IP, +}; + +/* When instantiating an instance of the Null Stream identification function + * 8021CB(6.4) for a particular input Stream, the managed objects in the + * following subsections serve as the tsnStreamIdParameters managed object + * 8021CB claus(9.1.1.7). + */ +struct tsn_cb_null_streamid { + /* tsnCpeNullDownDestMac. Specifies the destination_address that + * identifies a packet in an Enhanced Internal Sublayer Service (EISS) + * indication primitive, to the Null Stream identification function. + */ + __u64 dmac; + + /* tsnCpeNullDownTagged. It can take the following values: + * 1 tagged: A frame must have a VLAN tag to be recognized as belonging + * to the Stream. + * 2 priority: A frame must be untagged, or have a VLAN tag with a VLAN + * ID = 0 to be recognized as belonging to the Stream. + * 3 all: A frame is recognized as belonging to the Stream whether + * tagged or not. + */ + __u8 tagged; + + /* tsnCpeNullDownVlan. Specifies the vlan_identifier parameter that + * identifies a packet in an EISS indication primitive to the Null + * Stream identification function. A value of 0 indicates that the vlan + * _identifier parameter is ignored on EISS indication primitives. + */ + __u16 vid; +}; + +struct tsn_cb_source_streamid { + __u64 smac; + __u8 tagged; + __u16 vid; +}; + +struct tsn_cb_dest_streamid { + __u64 down_dmac; + __u8 down_tagged; + __u16 down_vid; + __u8 down_prio; + __u64 up_dmac; + __u8 up_tagged; + __u16 up_vid; + __u8 up_prio; +}; + +struct tsn_cb_ip_streamid { + __u64 dmac; + __u8 tagged; + __u16 vid; + __u64 siph; + __u64 sipl; + __u64 diph; + __u64 dipl; + __u8 dscp; + __u8 npt; + __u16 sport; + __u16 dport; +}; + +/* 802.1CB stream identify table clause 9.1 */ +struct tsn_cb_streamid { + /* The objects in a given entry of the Stream identity table are used + * to control packets whose stream_handle subparameter is equal to the + * entry tsnStreamIdHandle object. + */ + __s32 handle; + + /* The list of ports on which an in-facing Stream identification + * function in the output (towards the system forwarding function) + * direction Only Active Destination MAC and VLAN Stream identification + * (or nothing) can be configured. + */ + __u32 ifac_oport; + + /* The list of ports on which an out-facing Stream identification + * function in the output (towards the physical interface) direction. + * Only Active Destination MAC and VLAN Stream identification + * (or nothing) can be configured. + */ + __u32 ofac_oport; + + /* The list of ports on which an in-facing Stream identification + * function in the input (coming from the system forwarding function) + * direction + */ + __u32 ifac_iport; + + /* The list of ports on which an out-facing Stream identification + * function in the input (coming from the physical interface) direction + * . + */ + __u32 ofac_iport; + + /* An enumerated value indicating the method used to identify packets + * belonging to the Stream. + * The Organizationally Unique Identifier (OUI) or Company Identifier + * (CID) to identify the organization defining the enumerated type + * should be: 00-80-C2 + * 1: null stream identification + * 2: source mac and vlan stream identification + * 3: activ destination mac and vlan stream identification + * 4: ip stream identifaciton + */ + __u8 type; + + /* tsnStreamIdParameters The number of controlling parameters for a + * Stream identification method, their types and values, are specific + * to the tsnStreamIdIdentificationType + */ + union { + struct tsn_cb_null_streamid nid; + struct tsn_cb_source_streamid sid; + struct tsn_cb_dest_streamid did; + struct tsn_cb_ip_streamid iid; + } para; +}; + +/* Following counters are instantiated for each port on which the Stream + * identification function (6.2) is configured. The counters are indexed by + * port number, facing (in-facing or out-facing), and stream_handle value + * (tsnStreamIdHandle, 9.1.1.1). + */ +struct tsn_cb_streamid_counters { + struct { + __u64 input; + __u64 output; + } per_stream; + + struct { + __u64 input; + __u64 output; + } per_streamport[32]; +}; + +/* 802.1Qci Stream Parameter Table, read from port */ +struct tsn_qci_psfp_stream_param { + /* MaxStreamFilterInstances. + * The maximum number of Stream Filter instances supported by this + * Bridge component. + */ + __s32 max_sf_instance; + + /* MaxStreamGateInstances + * The maximum number of Stream Gate instances supported by this Bridge + * component. + */ + __s32 max_sg_instance; + + /* MaxFlowMeterInstances + * The maximum number of Flow Meter instances supported by this Bridge + * component. + */ + __s32 max_fm_instance; + + /* SupportedListMax + * The maximum value supported by this Bridge component of the + * AdminControlListLength and OperControlListLength parameters. + */ + __s32 supported_list_max; +}; + +/* 802.1Qci Stream Filter Instance Table, counters part only. */ +struct tsn_qci_psfp_sfi_counters { + /* The MatchingFramesCount counter counts received frames that match + * this stream filter. + */ + __u64 matching_frames_count; + + /* The PassingFramesCount counter counts received frames that pass the + * gate associated with this stream filter. + */ + __u64 passing_frames_count; + + /* The NotPassingFramesCount counter counts received frames that do not + * pass the gate associated with this stream filter. + */ + __u64 not_passing_frames_count; + + /* The PassingSDUCount counter counts received frames that pass the SDU + * size filter specification associated with this stream filter. + */ + __u64 passing_sdu_count; + + /* The NotPassingSDUCount counter counts received frames that do not + * pass the SDU size filter specification associated with this stream + * filter. + */ + __u64 not_passing_sdu_count; + + /* The REDFramesCount counter counts received random early detection + * (RED) frames associated with this stream filter. + */ + __u64 red_frames_count; +}; + +/* 802.1Qci Stream Filter Instance Table, configuration part only. */ +struct tsn_qci_psfp_sfi_conf { + + /* The StreamHandleSpec parameter contains a stream identifier + * specification value. A value of -1 denotes the wild card value; zero + * or positive values denote stream identifier values. + */ + __s32 stream_handle_spec; + + /* The PrioritySpec parameter contains a priority specification value. + * A value of -1 denotes the wild card value; zero or positive values + * denote priority values. + */ + __s8 priority_spec; + + /* The StreamGateInstanceID parameter contains the index of an entry in + * the Stream Gate Table. + */ + __u32 stream_gate_instance_id; + + /* The filter specifications. The actions specified in a filter + * specification can result in a frame passing or failing the specified + * filter. Frames that fail a filter are discarded. + */ + struct { + /* The MaximumSDUSize parameter specifies the maximum allowed + * frame size for the stream. Any frame exceeding this value + * will be dropped. A value of 0 denote that the MaximumSDUSize + * filter is disabled for this stream. + */ + __u16 maximum_sdu_size; + + /* The FlowMeterInstanceID parameter contains the index of an + * entry in the Flow Meter Table. A value of -1 denotes that + * no flow meter is assigned; zero or positive values denote + * flow meter IDs. + */ + __s32 flow_meter_instance_id; + } stream_filter; + + /* The StreamBlockedDueToOversizeFrameEnable object contains a Boolean + * value that indicates whether the StreamBlockedDueToOversizeFrame + * function is enabled (TRUE) or disabled (FALSE). + */ + __u8 block_oversize_enable; + + /* The StreamBlockedDueToOversizeFrame object contains a Boolean value + * that indicates whether, if the StreamBlockedDueToOversizeFrame + * function is enabled, all frames are to be discarded (TRUE) or not + * (FALSE). + */ + __u8 block_oversize; +}; + +/* 802.1Qci Stream Gate Control List Entry. */ +struct tsn_qci_psfp_gcl { + /* The GateState parameter specifies a desired state, open (true) or + * closed (false), for the stream gate. + */ + __u8 gate_state; + + /* An IPV is encoded as a signed integer. A negative denotes the null + * value; zero or positive values denote internal priority values. + */ + __s8 ipv; + + /* A TimeInterval is encoded in 4 octets as a 32-bit unsigned integer, + * representing a number of nanoseconds. + */ + __u32 time_interval; + + /* The maximum number of octets that are permitted to pass the gate + * during the specified TimeInterval. If zero, there is no maximum. + */ + __u32 octet_max; + +}; + +/* 802.1Qci Stream Gate Admin/Operation common list control parameters */ +struct tsn_qci_sg_control { + /* The administrative/operation value of the GateStates parameter + * for the stream gate. A value of false indicates closed; + * a value of true indicates open. + */ + __u8 gate_states; + + /* The administrative/operation value of the ListMax parameter for the + * gate. The integer value indicates the number of entries (TLVs) in + * the AdminControlList/OperControlList. + */ + __u8 control_list_length; + + /* The administrative/operation value of the CycleTime parameter for + * the gate. The value is an unsigned integer number of nanoseconds. + */ + __u32 cycle_time; + + /* The administrative/operation value of the CycleTimeExtension + * parameter for the gate. The value is an unsigned integer number + * of nanoseconds. + */ + __u32 cycle_time_extension; + + /* The administrative/operation value of the BaseTime parameter for the + * gate. The value is a representation of a PTPtime value, consisting + * of a 48-bit integer number of seconds and a 32-bit integer number of + * nanoseconds. + */ + ptptime_t base_time; + + /* The administrative/operation value of the IPV parameter for the gate. + * A value of -1 denotes the null value; zero or positive values denote + * internal priority values. + */ + __s8 init_ipv; + + /* control_list contend the gate control list of + * administrative/operation + */ + struct tsn_qci_psfp_gcl *gcl; +}; + +/* 802.1Qci Stream Gate Instance Table, configuration part only. */ +struct tsn_qci_psfp_sgi_conf { + /* The GateEnabled parameter determines whether the stream gate is + * active (true) or inactive (false). + */ + __u8 gate_enabled; + + /* The ConfigChange parameter signals the start of a configuration + * change when it is set to TRUE. This should only be done when the + * various administrative parameters are all set to appropriate values. + */ + __u8 config_change; + + /* admin control parameters with admin control list */ + struct tsn_qci_sg_control admin; + + /* The GateClosedDueToInvalidRxEnable object contains a Boolean value + * that indicates whether the GateClosedDueToInvalidRx function is + * enabled (TRUE) or disabled (FALSE). + */ + __u8 block_invalid_rx_enable; + + /* The GateClosedDueToInvalidRx object contains a Boolean value that + * indicates whether, if the GateClosedDueToInvalidRx function is + * enabled, all frames are to be discarded (TRUE) or not (FALSE). + */ + __u8 block_invalid_rx; + + /* The GateClosedDueToOctetsExceededEnable object contains a Boolean + * value that indicates whether the GateClosedDueToOctetsExceeded + * function is enabled (TRUE) or disabled (FALSE). + */ + __u8 block_octets_exceeded_enable; + + /* The GateClosedDueToOctetsExceeded object contains a Boolean value + * that indicates whether, if the GateClosedDueToOctetsExceeded + * function is enabled, all frames are to be discarded (TRUE) or not + * (FALSE). + */ + __u8 block_octets_exceeded; +}; + +/* 802.1Qci Stream Gate Instance Table, status part only. */ +struct tsn_psfp_sgi_status { + + /* admin control parameters with admin control list */ + struct tsn_qci_sg_control oper; + + /* The PTPtime at which the next config change is scheduled to occur. + * The value is a representation of a PTPtime value, consisting of a + * 48-bit integer number of seconds and a 32-bit integer number of + * nanoseconds. + */ + ptptime_t config_change_time; + + /* The granularity of the cycle time clock, represented as an unsigned + * number of tenths of nanoseconds. + */ + __u32 tick_granularity; + + /* The current time, in PTPtime, as maintained by the local system. + * The value is a representation of a PTPtime value, consisting of a + * 48-bit integer number of seconds and a 32-bit integer number of + * nanoseconds. + */ + ptptime_t current_time; + + /* The value of the ConfigPending state machine variable. The value is + * TRUE if a configuration change is in progress but has not yet + * completed. + */ + __u8 config_pending; + + /* A counter of the number of times that a re-configuration of the + * traffic schedule has been requested with the old schedule still + * running and the requested base time was in the past. + */ + __u64 config_change_error; + +}; + +/* 802.1Qci Flow Meter Instance Table. */ +struct tsn_qci_psfp_fmi { + /* The FlowMeterCIR parameter contains an integer value that represents + * the CIR value for the flow meter, in kbit/s. + */ + __u32 cir; + + /* The FlowMeterCBS parameter contains an integer value that represents + * the CBS value for the flow meter, in octets. + */ + __u32 cbs; + + /* The FlowMeterEIR parameter contains an integer value that represents + * the EIR value for the flow meter, in kbit/s. + */ + __u32 eir; + + /* The FlowMeterEBS parameter contains an integer value that represents + * the EBS value for the flow meter, in octets. + */ + __u32 ebs; + + /* The FlowMeterCF parameter contains a Boolean value that represents + * the CF value for the flow meter, as a Boolean value indicating no + * coupling (FALSE) or coupling (TRUE). + */ + __u8 cf; + + /* The FlowMeterCM parameter contains a Boolean value that represents + * the CM value for the flow meter, as a Boolean value indicating + * colorBlind (FALSE) or colorAware (TRUE). + */ + __u8 cm; + + /* The FlowMeterDropOnYellow parameter contains a Boolean value that + * indicates whether yellow frames are dropped (TRUE) or have + * drop_eligible set to TRUE (FALSE). + */ + __u8 drop_on_yellow; + + /* The FlowMeterMarkAllFramesRedEnable parameter contains a Boolean + * value that indicates whether the MarkAllFramesRed function + * is enabled (TRUE) or disabled (FALSE). + */ + __u8 mark_red_enable; + + /* The FlowMeterMarkAllFramesRed parameter contains a Boolean value + * that indicates whether, if the MarkAllFramesRed function is enabled, + * all frames are to be discarded (TRUE) or not (FALSE). + */ + __u8 mark_red; +}; + +struct tsn_qci_psfp_fmi_counters { + __u64 bytecount; + __u64 drop; + __u64 dr0_green; + __u64 dr1_green; + __u64 dr2_yellow; + __u64 remark_yellow; + __u64 dr3_red; + __u64 remark_red; +}; + +/* 802.1cb */ +struct tsn_seq_gen_conf { + + /* The InputPortMask parameter contains a port mask. + * If the packet is from input port belonging to this + * port mask then it's on known stream and sequence + * generation parameters can be applied. + */ + __u8 iport_mask; + + /* The SplitMask parameter contains a output port mask + * used to add redundant paths. + */ + __u8 split_mask; + + /* The SequenceSpaceLenLog parameter is a value to specifies + * number of bits to be used for sequence number. + */ + __u8 seq_len; + + /* The SequenceNumber parameter is a value to used for + * outgoing packet's sequence number generation. + */ + __u32 seq_num; +}; + +struct tsn_seq_rec_conf { + + /* The SequenceSpaceLenLog parameter is a value to specifies + * number of bits to be used for sequence number. + */ + __u8 seq_len; + + /* The HistorySpaceLenLog parameter is a value to specifies + * number of bits to be used for history register. + */ + __u8 his_len; + + /* The RTagPopEnable parameter contains a __u8 to enable removal + * of redundancy tag from the packet. + */ + __u8 rtag_pop_en; +}; + +struct tsn_cb_status { + + /* The GenRecover parameter contains a value specifies type + * of stream sequence parameters: + * 0: Stream sequence parameters are for generation. + * 1: Stream sequence parameters are for recovery. + */ + __u8 gen_rec; + + /* The ErrStatus parameter indicates stream's error status + * 1: This switch is expected to sequence the stream, + * but the incoming packet has sequence number. + * 2: This switch is expected to recover the stream, + * but the incoming packet is NONSEQ. + */ + __u8 err; + + /* The SequenceNumber parameter is a value to used for + * outgoing packet's sequence number generation. + */ + __u32 seq_num; + + /* The SequenceSpaceLenLog parameter is a value to specifies + * number of bits to be used for sequence number. + */ + __u8 seq_len; + + /* The SplitMask parameter contains a output port mask + * used to add redundant paths. + */ + __u8 split_mask; + + /* The InputPortMask parameter contains a port mask. + * If the packet is from input port belonging to this + * port mask then it's on known stream and sequence + * generation parameters can be applied. + */ + __u8 iport_mask; + + /* The HistorySpaceLenLog parameter is a value to specifies + * number of bits to be used for history register. + */ + __u8 his_len; + + /* The SequenceHistory parameter Maintains history of sequence + * numbers of received packets. + */ + __u32 seq_his; +}; + +/* An entry for gate control list */ +struct tsn_qbv_entry { + /* Octet represent the gate states for the corresponding traffic + * classes. + * The MS bit corresponds to traffic class 7. + * The LS bit to traffic class 0. + * A bit value of 0 indicates closed; + * A bit value of 1 indicates open. + */ + __u8 gate_state; + + /* A TimeInterval is encoded in 4 octets as a 32-bit unsigned integer, + * representing a number of nanoseconds. + */ + __u32 time_interval; +}; + +/* The administrative/operation time and gate list */ +struct tsn_qbv_basic { + /* The administrative/operation value of the GateStates parameter for + * the Port. + * The bits of the octet represent the gate states for the + * corresponding traffic classes; the MS bit corresponds to traffic + * class 7, the LS bit to traffic class 0. A bit value of 0 indicates + * closed; a bit value of 1 indicates open. + * The value of this object MUST be retained + * across reinitializations of the management system. + */ + __u8 gate_states; + + /* The administrative/operation value of the ListMax parameter for the + * port. The integer value indicates the number of entries (TLVs) in + * the AdminControlList. The value of this object MUST be retained + * across reinitializations of the management system. + */ + __u32 control_list_length; + + /* The administrative/operation value of the AdminCycleTime + * parameter for the Port. The numerator and denominator together + * represent the cycle time as a rational number of seconds. The value + * of this object MUST be retained across reinitializations of the + * management system. + */ + __u32 cycle_time; + + /* The administrative/operation value of the CycleTimeExtension + * parameter for the Port. The value is an unsigned integer number of + * nanoseconds. + * The value of this object MUST be retained across reinitializations + * of the management system. + */ + + __u32 cycle_time_extension; + + /* The administrative/operation value of the BaseTime parameter for the + * Port. The value is a representation of a PTPtime value, consisting + * of a 48-bit integer number of seconds and a 32-bit integer number of + * nanoseconds. + * The value of this object MUST be retained across reinitializations of + * the management system. + */ + ptptime_t base_time; + + /* admin_control_list represent the AdminControlList/OperControlList. + * The administrative version of the gate control list for the Port. + */ + struct tsn_qbv_entry *control_list; +}; + +struct tsn_qbv_conf { + /* The GateEnabled parameter determines whether traffic scheduling is + * active (true) or inactive (false). The value of this object MUST be + * retained across reinitializations of the management system. + */ + __u8 gate_enabled; + + /* The maxsdu parameter denoting the maximum SDU size supported by the + * queue. + */ + __u32 maxsdu; + + /* The ConfigChange parameter signals the start of a configuration + * change when it is set to TRUE. This should only be done when the + * various administrative parameters are all set to appropriate values. + */ + __u8 config_change; + + /* The admin parameter signals the admin relate cycletime, basictime, + * gatelist paraters. + */ + struct tsn_qbv_basic admin; +}; + +/* 802.1Qbv (Time Aware Shaper) port status */ +struct tsn_qbv_status { + /* The PTPtime at which the next config change is scheduled to occur. + * The value is a representation of a PTPtime value, consisting of a + * 48-bit integer number of seconds and a 32-bit integer number of + * nanoseconds. The value of this object MUST be retained across + * reinitializations of the management system. + */ + ptptime_t config_change_time; + + /* The granularity of the cycle time clock, represented as an unsigned + * number of tenths of nanoseconds. The value of this object MUST be + * retained across reinitializations of the management system. + */ + __u32 tick_granularity; + + /* The current time, in PTPtime, as maintained by the local system. + * The value is a representation of a PTPtime value, consisting of a + * 48-bit integer number of seconds and a 32-bit integer number of + * nanoseconds. + */ + ptptime_t current_time; + + /* The value of the ConfigPending state machine variable. The value is + * TRUE if a configuration change is in progress but has not yet + * completed. + */ + __u8 config_pending; + + /* A counter of the number of times that a re-configuration of the + * traffic schedule has been requested with the old schedule still + * running and the requested base time was in the past. + */ + __u64 config_change_error; + + /* The maximum value supported by this Port of the + * AdminControlListLength and OperControlListLength parameters. + */ + __u32 supported_list_max; + + /* Operation settings parameters and Oper gate list */ + struct tsn_qbv_basic oper; +}; + +/* Time Specific Departure parameters */ +struct tsn_tsd { + __u8 enable; + + /* The cycle time, in units of microsecond(us)*/ + __u32 period; + + /* The maximum number of frames which could be transmitted on one cycle + * The exceeding frames will be transmitted on next cycle. + */ + __u32 maxFrameNum; + + /* Specify the time of the first cycle begins. + * 1: begin when the queue get the first frame to transmit. + * 2: begin immediately at the end of setting function. + */ + __u32 syn_flag; +}; + +struct tsn_tsd_status { + __u8 enable; + __u32 period; + __u32 maxFrameNum; + __u32 flag; + __u32 cycleNum; + __u32 loss_steps; +}; + +struct tsn_qos_switch_dscp_conf { + __u8 trust; + __u8 cos; + __u8 dpl; + __u8 remark; + __u8 dscp; /* New ingress translated DSCP value */ +}; + +#endif /* _UAPI_GENL_TSN_H */ diff --git a/net/Kconfig b/net/Kconfig index 3101bfcbdd7a..ca360be927cf 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -228,6 +228,7 @@ source "net/ieee802154/Kconfig" source "net/mac802154/Kconfig" source "net/sched/Kconfig" source "net/dcb/Kconfig" +source "net/tsn/Kconfig" source "net/dns_resolver/Kconfig" source "net/batman-adv/Kconfig" source "net/openvswitch/Kconfig" diff --git a/net/Makefile b/net/Makefile index 449fc0b221f8..40e08db63e81 100644 --- a/net/Makefile +++ b/net/Makefile @@ -59,6 +59,9 @@ obj-$(CONFIG_CAIF) += caif/ ifneq ($(CONFIG_DCB),) obj-y += dcb/ endif +ifneq ($(CONFIG_TSN),) +obj-y += tsn/ +endif obj-$(CONFIG_6LOWPAN) += 6lowpan/ obj-$(CONFIG_IEEE802154) += ieee802154/ obj-$(CONFIG_MAC802154) += mac802154/ diff --git a/net/tsn/Kconfig b/net/tsn/Kconfig new file mode 100644 index 000000000000..9f22807a74c9 --- /dev/null +++ b/net/tsn/Kconfig @@ -0,0 +1,15 @@ +config TSN + bool "802.1 Time-Sensitive Networking support" + default n + depends on VLAN_8021Q && PTP_1588_CLOCK + help + This enables support for TSN(time sensitive networking) + TSN features include: + 802.1Qav: + 802.1Qbv: + 802.1Qci: + 802.1Qbu: + 802.1AS: + 802.1CB: + + If unsure, say N. diff --git a/net/tsn/Makefile b/net/tsn/Makefile new file mode 100644 index 000000000000..ed46381bf24f --- /dev/null +++ b/net/tsn/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_TSN) += genl_tsn.o diff --git a/net/tsn/genl_tsn.c b/net/tsn/genl_tsn.c new file mode 100644 index 000000000000..1fec19542308 --- /dev/null +++ b/net/tsn/genl_tsn.c @@ -0,0 +1,3696 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* Copyright 2017-2019 NXP */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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");