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