Merge branch 'add-kernel-tc-mqprio-and-tc-taprio-support-for-preemptible-traffic-classes'
Vladimir Oltean says: ==================== Add kernel tc-mqprio and tc-taprio support for preemptible traffic classes The last RFC in August 2022 contained a proposal for the UAPI of both TSN standards which together form Frame Preemption (802.1Q and 802.3): https://lore.kernel.org/netdev/20220816222920.1952936-1-vladimir.oltean@nxp.com/ It wasn't clear at the time whether the 802.1Q portion of Frame Preemption should be exposed via the tc qdisc (mqprio, taprio) or via some other layer (perhaps also ethtool like the 802.3 portion, or dcbnl), even though the options were discussed extensively, with pros and cons: https://lore.kernel.org/netdev/20220816222920.1952936-3-vladimir.oltean@nxp.com/ So the 802.3 portion got submitted separately and finally was accepted: https://lore.kernel.org/netdev/20230119122705.73054-1-vladimir.oltean@nxp.com/ leaving the only remaining question: how do we expose the 802.1Q bits? This series proposes that we use the Qdisc layer, through separate (albeit very similar) UAPI in mqprio and taprio, and that both these Qdiscs pass the information down to the offloading device driver through the common mqprio offload structure (which taprio also passes). An implementation is provided for the NXP LS1028A on-board Ethernet endpoint (enetc). Previous versions also contained support for its embedded switch (felix), but this needs more work and will be submitted separately. v4: https://lore.kernel.org/netdev/20230403103440.2895683-1-vladimir.oltean@nxp.com/ v2: https://lore.kernel.org/netdev/20230219135309.594188-1-vladimir.oltean@nxp.com/ v1: https://lore.kernel.org/netdev/20230216232126.3402975-1-vladimir.oltean@nxp.com/ ==================== Link: https://lore.kernel.org/r/20230411180157.1850527-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
f7d29571ab
|
@ -25,6 +25,24 @@ void enetc_port_mac_wr(struct enetc_si *si, u32 reg, u32 val)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(enetc_port_mac_wr);
|
||||
|
||||
void enetc_set_ptcfpr(struct enetc_hw *hw, unsigned long preemptible_tcs)
|
||||
{
|
||||
u32 val;
|
||||
int tc;
|
||||
|
||||
for (tc = 0; tc < 8; tc++) {
|
||||
val = enetc_port_rd(hw, ENETC_PTCFPR(tc));
|
||||
|
||||
if (preemptible_tcs & BIT(tc))
|
||||
val |= ENETC_PTCFPR_FPE;
|
||||
else
|
||||
val &= ~ENETC_PTCFPR_FPE;
|
||||
|
||||
enetc_port_wr(hw, ENETC_PTCFPR(tc), val);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(enetc_set_ptcfpr);
|
||||
|
||||
static int enetc_num_stack_tx_queues(struct enetc_ndev_priv *priv)
|
||||
{
|
||||
int num_tx_rings = priv->num_tx_rings;
|
||||
|
@ -2640,16 +2658,19 @@ static void enetc_reset_tc_mqprio(struct net_device *ndev)
|
|||
}
|
||||
|
||||
enetc_debug_tx_ring_prios(priv);
|
||||
|
||||
enetc_set_ptcfpr(hw, 0);
|
||||
}
|
||||
|
||||
int enetc_setup_tc_mqprio(struct net_device *ndev, void *type_data)
|
||||
{
|
||||
struct tc_mqprio_qopt_offload *mqprio = type_data;
|
||||
struct enetc_ndev_priv *priv = netdev_priv(ndev);
|
||||
struct tc_mqprio_qopt *mqprio = type_data;
|
||||
struct tc_mqprio_qopt *qopt = &mqprio->qopt;
|
||||
struct enetc_hw *hw = &priv->si->hw;
|
||||
int num_stack_tx_queues = 0;
|
||||
u8 num_tc = mqprio->num_tc;
|
||||
struct enetc_bdr *tx_ring;
|
||||
u8 num_tc = qopt->num_tc;
|
||||
int offset, count;
|
||||
int err, tc, q;
|
||||
|
||||
|
@ -2663,8 +2684,8 @@ int enetc_setup_tc_mqprio(struct net_device *ndev, void *type_data)
|
|||
return err;
|
||||
|
||||
for (tc = 0; tc < num_tc; tc++) {
|
||||
offset = mqprio->offset[tc];
|
||||
count = mqprio->count[tc];
|
||||
offset = qopt->offset[tc];
|
||||
count = qopt->count[tc];
|
||||
num_stack_tx_queues += count;
|
||||
|
||||
err = netdev_set_tc_queue(ndev, tc, count, offset);
|
||||
|
@ -2693,6 +2714,8 @@ int enetc_setup_tc_mqprio(struct net_device *ndev, void *type_data)
|
|||
|
||||
enetc_debug_tx_ring_prios(priv);
|
||||
|
||||
enetc_set_ptcfpr(hw, mqprio->preemptible_tcs);
|
||||
|
||||
return 0;
|
||||
|
||||
err_reset_tc:
|
||||
|
|
|
@ -486,6 +486,7 @@ static inline void enetc_cbd_free_data_mem(struct enetc_si *si, int size,
|
|||
|
||||
void enetc_reset_ptcmsdur(struct enetc_hw *hw);
|
||||
void enetc_set_ptcmsdur(struct enetc_hw *hw, u32 *queue_max_sdu);
|
||||
void enetc_set_ptcfpr(struct enetc_hw *hw, unsigned long preemptible_tcs);
|
||||
|
||||
#ifdef CONFIG_FSL_ENETC_QOS
|
||||
int enetc_qos_query_caps(struct net_device *ndev, void *type_data);
|
||||
|
|
|
@ -965,6 +965,10 @@ static inline u32 enetc_usecs_to_cycles(u32 usecs)
|
|||
return (u32)div_u64(usecs * ENETC_CLK, 1000000ULL);
|
||||
}
|
||||
|
||||
/* Port traffic class frame preemption register */
|
||||
#define ENETC_PTCFPR(n) (0x1910 + (n) * 4) /* n = [0 ..7] */
|
||||
#define ENETC_PTCFPR_FPE BIT(31)
|
||||
|
||||
/* port time gating control register */
|
||||
#define ENETC_PTGCR 0x11a00
|
||||
#define ENETC_PTGCR_TGE BIT(31)
|
||||
|
|
|
@ -39,6 +39,7 @@ void ethtool_aggregate_pause_stats(struct net_device *dev,
|
|||
struct ethtool_pause_stats *pause_stats);
|
||||
void ethtool_aggregate_rmon_stats(struct net_device *dev,
|
||||
struct ethtool_rmon_stats *rmon_stats);
|
||||
bool ethtool_dev_mm_supported(struct net_device *dev);
|
||||
|
||||
#else
|
||||
static inline int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
|
||||
|
@ -112,5 +113,10 @@ ethtool_aggregate_rmon_stats(struct net_device *dev,
|
|||
{
|
||||
}
|
||||
|
||||
static inline bool ethtool_dev_mm_supported(struct net_device *dev)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */
|
||||
#endif /* _LINUX_ETHTOOL_NETLINK_H_ */
|
||||
|
|
|
@ -166,11 +166,13 @@ struct tc_mqprio_caps {
|
|||
struct tc_mqprio_qopt_offload {
|
||||
/* struct tc_mqprio_qopt must always be the first element */
|
||||
struct tc_mqprio_qopt qopt;
|
||||
struct netlink_ext_ack *extack;
|
||||
u16 mode;
|
||||
u16 shaper;
|
||||
u32 flags;
|
||||
u64 min_rate[TC_QOPT_MAX_QUEUE];
|
||||
u64 max_rate[TC_QOPT_MAX_QUEUE];
|
||||
unsigned long preemptible_tcs;
|
||||
};
|
||||
|
||||
struct tc_taprio_caps {
|
||||
|
@ -193,6 +195,7 @@ struct tc_taprio_sched_entry {
|
|||
|
||||
struct tc_taprio_qopt_offload {
|
||||
struct tc_mqprio_qopt_offload mqprio;
|
||||
struct netlink_ext_ack *extack;
|
||||
u8 enable;
|
||||
ktime_t base_time;
|
||||
u64 cycle_time;
|
||||
|
|
|
@ -719,6 +719,11 @@ enum {
|
|||
|
||||
#define __TC_MQPRIO_SHAPER_MAX (__TC_MQPRIO_SHAPER_MAX - 1)
|
||||
|
||||
enum {
|
||||
TC_FP_EXPRESS = 1,
|
||||
TC_FP_PREEMPTIBLE = 2,
|
||||
};
|
||||
|
||||
struct tc_mqprio_qopt {
|
||||
__u8 num_tc;
|
||||
__u8 prio_tc_map[TC_QOPT_BITMASK + 1];
|
||||
|
@ -732,12 +737,23 @@ struct tc_mqprio_qopt {
|
|||
#define TC_MQPRIO_F_MIN_RATE 0x4
|
||||
#define TC_MQPRIO_F_MAX_RATE 0x8
|
||||
|
||||
enum {
|
||||
TCA_MQPRIO_TC_ENTRY_UNSPEC,
|
||||
TCA_MQPRIO_TC_ENTRY_INDEX, /* u32 */
|
||||
TCA_MQPRIO_TC_ENTRY_FP, /* u32 */
|
||||
|
||||
/* add new constants above here */
|
||||
__TCA_MQPRIO_TC_ENTRY_CNT,
|
||||
TCA_MQPRIO_TC_ENTRY_MAX = (__TCA_MQPRIO_TC_ENTRY_CNT - 1)
|
||||
};
|
||||
|
||||
enum {
|
||||
TCA_MQPRIO_UNSPEC,
|
||||
TCA_MQPRIO_MODE,
|
||||
TCA_MQPRIO_SHAPER,
|
||||
TCA_MQPRIO_MIN_RATE64,
|
||||
TCA_MQPRIO_MAX_RATE64,
|
||||
TCA_MQPRIO_TC_ENTRY,
|
||||
__TCA_MQPRIO_MAX,
|
||||
};
|
||||
|
||||
|
@ -1236,6 +1252,7 @@ enum {
|
|||
TCA_TAPRIO_TC_ENTRY_UNSPEC,
|
||||
TCA_TAPRIO_TC_ENTRY_INDEX, /* u32 */
|
||||
TCA_TAPRIO_TC_ENTRY_MAX_SDU, /* u32 */
|
||||
TCA_TAPRIO_TC_ENTRY_FP, /* u32 */
|
||||
|
||||
/* add new constants above here */
|
||||
__TCA_TAPRIO_TC_ENTRY_CNT,
|
||||
|
|
|
@ -249,3 +249,26 @@ bool __ethtool_dev_mm_supported(struct net_device *dev)
|
|||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
bool ethtool_dev_mm_supported(struct net_device *dev)
|
||||
{
|
||||
const struct ethtool_ops *ops = dev->ethtool_ops;
|
||||
bool supported;
|
||||
int ret;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
if (!ops)
|
||||
return false;
|
||||
|
||||
ret = ethnl_ops_begin(dev);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
supported = __ethtool_dev_mm_supported(dev);
|
||||
|
||||
ethnl_ops_complete(dev);
|
||||
|
||||
return supported;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ethtool_dev_mm_supported);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* Copyright (c) 2010 John Fastabend <john.r.fastabend@intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/ethtool_netlink.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
|
@ -27,15 +28,19 @@ struct mqprio_sched {
|
|||
u32 flags;
|
||||
u64 min_rate[TC_QOPT_MAX_QUEUE];
|
||||
u64 max_rate[TC_QOPT_MAX_QUEUE];
|
||||
u32 fp[TC_QOPT_MAX_QUEUE];
|
||||
};
|
||||
|
||||
static int mqprio_enable_offload(struct Qdisc *sch,
|
||||
const struct tc_mqprio_qopt *qopt,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct tc_mqprio_qopt_offload mqprio = {.qopt = *qopt};
|
||||
struct mqprio_sched *priv = qdisc_priv(sch);
|
||||
struct net_device *dev = qdisc_dev(sch);
|
||||
struct tc_mqprio_qopt_offload mqprio = {
|
||||
.qopt = *qopt,
|
||||
.extack = extack,
|
||||
};
|
||||
int err, i;
|
||||
|
||||
switch (priv->mode) {
|
||||
|
@ -60,6 +65,8 @@ static int mqprio_enable_offload(struct Qdisc *sch,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
mqprio_fp_to_offload(priv->fp, &mqprio);
|
||||
|
||||
err = dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_MQPRIO,
|
||||
&mqprio);
|
||||
if (err)
|
||||
|
@ -133,48 +140,131 @@ static int mqprio_parse_opt(struct net_device *dev, struct tc_mqprio_qopt *qopt,
|
|||
/* If ndo_setup_tc is not present then hardware doesn't support offload
|
||||
* and we should return an error.
|
||||
*/
|
||||
if (qopt->hw && !dev->netdev_ops->ndo_setup_tc)
|
||||
if (qopt->hw && !dev->netdev_ops->ndo_setup_tc) {
|
||||
NL_SET_ERR_MSG(extack,
|
||||
"Device does not support hardware offload");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct
|
||||
nla_policy mqprio_tc_entry_policy[TCA_MQPRIO_TC_ENTRY_MAX + 1] = {
|
||||
[TCA_MQPRIO_TC_ENTRY_INDEX] = NLA_POLICY_MAX(NLA_U32,
|
||||
TC_QOPT_MAX_QUEUE),
|
||||
[TCA_MQPRIO_TC_ENTRY_FP] = NLA_POLICY_RANGE(NLA_U32,
|
||||
TC_FP_EXPRESS,
|
||||
TC_FP_PREEMPTIBLE),
|
||||
};
|
||||
|
||||
static const struct nla_policy mqprio_policy[TCA_MQPRIO_MAX + 1] = {
|
||||
[TCA_MQPRIO_MODE] = { .len = sizeof(u16) },
|
||||
[TCA_MQPRIO_SHAPER] = { .len = sizeof(u16) },
|
||||
[TCA_MQPRIO_MIN_RATE64] = { .type = NLA_NESTED },
|
||||
[TCA_MQPRIO_MAX_RATE64] = { .type = NLA_NESTED },
|
||||
[TCA_MQPRIO_TC_ENTRY] = { .type = NLA_NESTED },
|
||||
};
|
||||
|
||||
static int parse_attr(struct nlattr *tb[], int maxtype, struct nlattr *nla,
|
||||
const struct nla_policy *policy, int len)
|
||||
static int mqprio_parse_tc_entry(u32 fp[TC_QOPT_MAX_QUEUE],
|
||||
struct nlattr *opt,
|
||||
unsigned long *seen_tcs,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
int nested_len = nla_len(nla) - NLA_ALIGN(len);
|
||||
struct nlattr *tb[TCA_MQPRIO_TC_ENTRY_MAX + 1];
|
||||
int err, tc;
|
||||
|
||||
if (nested_len >= nla_attr_size(0))
|
||||
return nla_parse_deprecated(tb, maxtype,
|
||||
nla_data(nla) + NLA_ALIGN(len),
|
||||
nested_len, policy, NULL);
|
||||
|
||||
memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mqprio_parse_nlattr(struct Qdisc *sch, struct tc_mqprio_qopt *qopt,
|
||||
struct nlattr *opt)
|
||||
{
|
||||
struct mqprio_sched *priv = qdisc_priv(sch);
|
||||
struct nlattr *tb[TCA_MQPRIO_MAX + 1];
|
||||
struct nlattr *attr;
|
||||
int i, rem, err;
|
||||
|
||||
err = parse_attr(tb, TCA_MQPRIO_MAX, opt, mqprio_policy,
|
||||
sizeof(*qopt));
|
||||
err = nla_parse_nested(tb, TCA_MQPRIO_TC_ENTRY_MAX, opt,
|
||||
mqprio_tc_entry_policy, extack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (!qopt->hw)
|
||||
if (NL_REQ_ATTR_CHECK(extack, opt, tb, TCA_MQPRIO_TC_ENTRY_INDEX)) {
|
||||
NL_SET_ERR_MSG(extack, "TC entry index missing");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tc = nla_get_u32(tb[TCA_MQPRIO_TC_ENTRY_INDEX]);
|
||||
if (*seen_tcs & BIT(tc)) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, tb[TCA_MQPRIO_TC_ENTRY_INDEX],
|
||||
"Duplicate tc entry");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*seen_tcs |= BIT(tc);
|
||||
|
||||
if (tb[TCA_MQPRIO_TC_ENTRY_FP])
|
||||
fp[tc] = nla_get_u32(tb[TCA_MQPRIO_TC_ENTRY_FP]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mqprio_parse_tc_entries(struct Qdisc *sch, struct nlattr *nlattr_opt,
|
||||
int nlattr_opt_len,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct mqprio_sched *priv = qdisc_priv(sch);
|
||||
struct net_device *dev = qdisc_dev(sch);
|
||||
bool have_preemption = false;
|
||||
unsigned long seen_tcs = 0;
|
||||
u32 fp[TC_QOPT_MAX_QUEUE];
|
||||
struct nlattr *n;
|
||||
int tc, rem;
|
||||
int err = 0;
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
|
||||
fp[tc] = priv->fp[tc];
|
||||
|
||||
nla_for_each_attr(n, nlattr_opt, nlattr_opt_len, rem) {
|
||||
if (nla_type(n) != TCA_MQPRIO_TC_ENTRY)
|
||||
continue;
|
||||
|
||||
err = mqprio_parse_tc_entry(fp, n, &seen_tcs, extack);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) {
|
||||
priv->fp[tc] = fp[tc];
|
||||
if (fp[tc] == TC_FP_PREEMPTIBLE)
|
||||
have_preemption = true;
|
||||
}
|
||||
|
||||
if (have_preemption && !ethtool_dev_mm_supported(dev)) {
|
||||
NL_SET_ERR_MSG(extack, "Device does not support preemption");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Parse the other netlink attributes that represent the payload of
|
||||
* TCA_OPTIONS, which are appended right after struct tc_mqprio_qopt.
|
||||
*/
|
||||
static int mqprio_parse_nlattr(struct Qdisc *sch, struct tc_mqprio_qopt *qopt,
|
||||
struct nlattr *opt,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct nlattr *nlattr_opt = nla_data(opt) + NLA_ALIGN(sizeof(*qopt));
|
||||
int nlattr_opt_len = nla_len(opt) - NLA_ALIGN(sizeof(*qopt));
|
||||
struct mqprio_sched *priv = qdisc_priv(sch);
|
||||
struct nlattr *tb[TCA_MQPRIO_MAX + 1] = {};
|
||||
struct nlattr *attr;
|
||||
int i, rem, err;
|
||||
|
||||
if (nlattr_opt_len >= nla_attr_size(0)) {
|
||||
err = nla_parse_deprecated(tb, TCA_MQPRIO_MAX, nlattr_opt,
|
||||
nlattr_opt_len, mqprio_policy,
|
||||
NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!qopt->hw) {
|
||||
NL_SET_ERR_MSG(extack,
|
||||
"mqprio TCA_OPTIONS can only contain netlink attributes in hardware mode");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (tb[TCA_MQPRIO_MODE]) {
|
||||
priv->flags |= TC_MQPRIO_F_MODE;
|
||||
|
@ -187,13 +277,19 @@ static int mqprio_parse_nlattr(struct Qdisc *sch, struct tc_mqprio_qopt *qopt,
|
|||
}
|
||||
|
||||
if (tb[TCA_MQPRIO_MIN_RATE64]) {
|
||||
if (priv->shaper != TC_MQPRIO_SHAPER_BW_RATE)
|
||||
if (priv->shaper != TC_MQPRIO_SHAPER_BW_RATE) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, tb[TCA_MQPRIO_MIN_RATE64],
|
||||
"min_rate accepted only when shaper is in bw_rlimit mode");
|
||||
return -EINVAL;
|
||||
}
|
||||
i = 0;
|
||||
nla_for_each_nested(attr, tb[TCA_MQPRIO_MIN_RATE64],
|
||||
rem) {
|
||||
if (nla_type(attr) != TCA_MQPRIO_MIN_RATE64)
|
||||
if (nla_type(attr) != TCA_MQPRIO_MIN_RATE64) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, attr,
|
||||
"Attribute type expected to be TCA_MQPRIO_MIN_RATE64");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (i >= qopt->num_tc)
|
||||
break;
|
||||
priv->min_rate[i] = nla_get_u64(attr);
|
||||
|
@ -203,13 +299,19 @@ static int mqprio_parse_nlattr(struct Qdisc *sch, struct tc_mqprio_qopt *qopt,
|
|||
}
|
||||
|
||||
if (tb[TCA_MQPRIO_MAX_RATE64]) {
|
||||
if (priv->shaper != TC_MQPRIO_SHAPER_BW_RATE)
|
||||
if (priv->shaper != TC_MQPRIO_SHAPER_BW_RATE) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, tb[TCA_MQPRIO_MAX_RATE64],
|
||||
"max_rate accepted only when shaper is in bw_rlimit mode");
|
||||
return -EINVAL;
|
||||
}
|
||||
i = 0;
|
||||
nla_for_each_nested(attr, tb[TCA_MQPRIO_MAX_RATE64],
|
||||
rem) {
|
||||
if (nla_type(attr) != TCA_MQPRIO_MAX_RATE64)
|
||||
if (nla_type(attr) != TCA_MQPRIO_MAX_RATE64) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, attr,
|
||||
"Attribute type expected to be TCA_MQPRIO_MAX_RATE64");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (i >= qopt->num_tc)
|
||||
break;
|
||||
priv->max_rate[i] = nla_get_u64(attr);
|
||||
|
@ -218,6 +320,13 @@ static int mqprio_parse_nlattr(struct Qdisc *sch, struct tc_mqprio_qopt *qopt,
|
|||
priv->flags |= TC_MQPRIO_F_MAX_RATE;
|
||||
}
|
||||
|
||||
if (tb[TCA_MQPRIO_TC_ENTRY]) {
|
||||
err = mqprio_parse_tc_entries(sch, nlattr_opt, nlattr_opt_len,
|
||||
extack);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -231,7 +340,7 @@ static int mqprio_init(struct Qdisc *sch, struct nlattr *opt,
|
|||
int i, err = -EOPNOTSUPP;
|
||||
struct tc_mqprio_qopt *qopt = NULL;
|
||||
struct tc_mqprio_caps caps;
|
||||
int len;
|
||||
int len, tc;
|
||||
|
||||
BUILD_BUG_ON(TC_MAX_QUEUE != TC_QOPT_MAX_QUEUE);
|
||||
BUILD_BUG_ON(TC_BITMASK != TC_QOPT_BITMASK);
|
||||
|
@ -249,6 +358,9 @@ static int mqprio_init(struct Qdisc *sch, struct nlattr *opt,
|
|||
if (!opt || nla_len(opt) < sizeof(*qopt))
|
||||
return -EINVAL;
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
|
||||
priv->fp[tc] = TC_FP_EXPRESS;
|
||||
|
||||
qdisc_offload_query_caps(dev, TC_SETUP_QDISC_MQPRIO,
|
||||
&caps, sizeof(caps));
|
||||
|
||||
|
@ -258,7 +370,7 @@ static int mqprio_init(struct Qdisc *sch, struct nlattr *opt,
|
|||
|
||||
len = nla_len(opt) - NLA_ALIGN(sizeof(*qopt));
|
||||
if (len > 0) {
|
||||
err = mqprio_parse_nlattr(sch, qopt, opt);
|
||||
err = mqprio_parse_nlattr(sch, qopt, opt, extack);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
@ -399,6 +511,33 @@ nla_put_failure:
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int mqprio_dump_tc_entries(struct mqprio_sched *priv,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct nlattr *n;
|
||||
int tc;
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) {
|
||||
n = nla_nest_start(skb, TCA_MQPRIO_TC_ENTRY);
|
||||
if (!n)
|
||||
return -EMSGSIZE;
|
||||
|
||||
if (nla_put_u32(skb, TCA_MQPRIO_TC_ENTRY_INDEX, tc))
|
||||
goto nla_put_failure;
|
||||
|
||||
if (nla_put_u32(skb, TCA_MQPRIO_TC_ENTRY_FP, priv->fp[tc]))
|
||||
goto nla_put_failure;
|
||||
|
||||
nla_nest_end(skb, n);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
nla_put_failure:
|
||||
nla_nest_cancel(skb, n);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb)
|
||||
{
|
||||
struct net_device *dev = qdisc_dev(sch);
|
||||
|
@ -449,6 +588,9 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb)
|
|||
(dump_rates(priv, &opt, skb) != 0))
|
||||
goto nla_put_failure;
|
||||
|
||||
if (mqprio_dump_tc_entries(priv, skb))
|
||||
goto nla_put_failure;
|
||||
|
||||
return nla_nest_end(skb, nla);
|
||||
nla_put_failure:
|
||||
nlmsg_trim(skb, nla);
|
||||
|
|
|
@ -114,4 +114,18 @@ void mqprio_qopt_reconstruct(struct net_device *dev, struct tc_mqprio_qopt *qopt
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(mqprio_qopt_reconstruct);
|
||||
|
||||
void mqprio_fp_to_offload(u32 fp[TC_QOPT_MAX_QUEUE],
|
||||
struct tc_mqprio_qopt_offload *mqprio)
|
||||
{
|
||||
unsigned long preemptible_tcs = 0;
|
||||
int tc;
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
|
||||
if (fp[tc] == TC_FP_PREEMPTIBLE)
|
||||
preemptible_tcs |= BIT(tc);
|
||||
|
||||
mqprio->preemptible_tcs = preemptible_tcs;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mqprio_fp_to_offload);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -14,5 +14,7 @@ int mqprio_validate_qopt(struct net_device *dev, struct tc_mqprio_qopt *qopt,
|
|||
struct netlink_ext_ack *extack);
|
||||
void mqprio_qopt_reconstruct(struct net_device *dev,
|
||||
struct tc_mqprio_qopt *qopt);
|
||||
void mqprio_fp_to_offload(u32 fp[TC_QOPT_MAX_QUEUE],
|
||||
struct tc_mqprio_qopt_offload *mqprio);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/ethtool_netlink.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
|
@ -96,6 +97,7 @@ struct taprio_sched {
|
|||
struct list_head taprio_list;
|
||||
int cur_txq[TC_MAX_QUEUE];
|
||||
u32 max_sdu[TC_MAX_QUEUE]; /* save info from the user */
|
||||
u32 fp[TC_QOPT_MAX_QUEUE]; /* only for dump and offloading */
|
||||
u32 txtime_delay;
|
||||
};
|
||||
|
||||
|
@ -1002,6 +1004,9 @@ static const struct nla_policy entry_policy[TCA_TAPRIO_SCHED_ENTRY_MAX + 1] = {
|
|||
static const struct nla_policy taprio_tc_policy[TCA_TAPRIO_TC_ENTRY_MAX + 1] = {
|
||||
[TCA_TAPRIO_TC_ENTRY_INDEX] = { .type = NLA_U32 },
|
||||
[TCA_TAPRIO_TC_ENTRY_MAX_SDU] = { .type = NLA_U32 },
|
||||
[TCA_TAPRIO_TC_ENTRY_FP] = NLA_POLICY_RANGE(NLA_U32,
|
||||
TC_FP_EXPRESS,
|
||||
TC_FP_PREEMPTIBLE),
|
||||
};
|
||||
|
||||
static const struct nla_policy taprio_policy[TCA_TAPRIO_ATTR_MAX + 1] = {
|
||||
|
@ -1520,22 +1525,31 @@ static int taprio_enable_offload(struct net_device *dev,
|
|||
return -ENOMEM;
|
||||
}
|
||||
offload->enable = 1;
|
||||
offload->extack = extack;
|
||||
mqprio_qopt_reconstruct(dev, &offload->mqprio.qopt);
|
||||
offload->mqprio.extack = extack;
|
||||
taprio_sched_to_offload(dev, sched, offload, &caps);
|
||||
mqprio_fp_to_offload(q->fp, &offload->mqprio);
|
||||
|
||||
for (tc = 0; tc < TC_MAX_QUEUE; tc++)
|
||||
offload->max_sdu[tc] = q->max_sdu[tc];
|
||||
|
||||
err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload);
|
||||
if (err < 0) {
|
||||
NL_SET_ERR_MSG(extack,
|
||||
"Device failed to setup taprio offload");
|
||||
NL_SET_ERR_MSG_WEAK(extack,
|
||||
"Device failed to setup taprio offload");
|
||||
goto done;
|
||||
}
|
||||
|
||||
q->offloaded = true;
|
||||
|
||||
done:
|
||||
/* The offload structure may linger around via a reference taken by the
|
||||
* device driver, so clear up the netlink extack pointer so that the
|
||||
* driver isn't tempted to dereference data which stopped being valid
|
||||
*/
|
||||
offload->extack = NULL;
|
||||
offload->mqprio.extack = NULL;
|
||||
taprio_offload_free(offload);
|
||||
|
||||
return err;
|
||||
|
@ -1663,13 +1677,14 @@ out:
|
|||
static int taprio_parse_tc_entry(struct Qdisc *sch,
|
||||
struct nlattr *opt,
|
||||
u32 max_sdu[TC_QOPT_MAX_QUEUE],
|
||||
u32 fp[TC_QOPT_MAX_QUEUE],
|
||||
unsigned long *seen_tcs,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct nlattr *tb[TCA_TAPRIO_TC_ENTRY_MAX + 1] = { };
|
||||
struct net_device *dev = qdisc_dev(sch);
|
||||
u32 val = 0;
|
||||
int err, tc;
|
||||
u32 val;
|
||||
|
||||
err = nla_parse_nested(tb, TCA_TAPRIO_TC_ENTRY_MAX, opt,
|
||||
taprio_tc_policy, extack);
|
||||
|
@ -1694,15 +1709,18 @@ static int taprio_parse_tc_entry(struct Qdisc *sch,
|
|||
|
||||
*seen_tcs |= BIT(tc);
|
||||
|
||||
if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU])
|
||||
if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]) {
|
||||
val = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]);
|
||||
if (val > dev->max_mtu) {
|
||||
NL_SET_ERR_MSG_MOD(extack, "TC max SDU exceeds device max MTU");
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
if (val > dev->max_mtu) {
|
||||
NL_SET_ERR_MSG_MOD(extack, "TC max SDU exceeds device max MTU");
|
||||
return -ERANGE;
|
||||
max_sdu[tc] = val;
|
||||
}
|
||||
|
||||
max_sdu[tc] = val;
|
||||
if (tb[TCA_TAPRIO_TC_ENTRY_FP])
|
||||
fp[tc] = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_FP]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1712,29 +1730,51 @@ static int taprio_parse_tc_entries(struct Qdisc *sch,
|
|||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct taprio_sched *q = qdisc_priv(sch);
|
||||
struct net_device *dev = qdisc_dev(sch);
|
||||
u32 max_sdu[TC_QOPT_MAX_QUEUE];
|
||||
bool have_preemption = false;
|
||||
unsigned long seen_tcs = 0;
|
||||
u32 fp[TC_QOPT_MAX_QUEUE];
|
||||
struct nlattr *n;
|
||||
int tc, rem;
|
||||
int err = 0;
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) {
|
||||
max_sdu[tc] = q->max_sdu[tc];
|
||||
fp[tc] = q->fp[tc];
|
||||
}
|
||||
|
||||
nla_for_each_nested(n, opt, rem) {
|
||||
if (nla_type(n) != TCA_TAPRIO_ATTR_TC_ENTRY)
|
||||
continue;
|
||||
|
||||
err = taprio_parse_tc_entry(sch, n, max_sdu, &seen_tcs,
|
||||
err = taprio_parse_tc_entry(sch, n, max_sdu, fp, &seen_tcs,
|
||||
extack);
|
||||
if (err)
|
||||
goto out;
|
||||
return err;
|
||||
}
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) {
|
||||
q->max_sdu[tc] = max_sdu[tc];
|
||||
q->fp[tc] = fp[tc];
|
||||
if (fp[tc] != TC_FP_EXPRESS)
|
||||
have_preemption = true;
|
||||
}
|
||||
|
||||
if (have_preemption) {
|
||||
if (!FULL_OFFLOAD_IS_ENABLED(q->flags)) {
|
||||
NL_SET_ERR_MSG(extack,
|
||||
"Preemption only supported with full offload");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (!ethtool_dev_mm_supported(dev)) {
|
||||
NL_SET_ERR_MSG(extack,
|
||||
"Device does not support preemption");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -2015,7 +2055,7 @@ static int taprio_init(struct Qdisc *sch, struct nlattr *opt,
|
|||
{
|
||||
struct taprio_sched *q = qdisc_priv(sch);
|
||||
struct net_device *dev = qdisc_dev(sch);
|
||||
int i;
|
||||
int i, tc;
|
||||
|
||||
spin_lock_init(&q->current_entry_lock);
|
||||
|
||||
|
@ -2072,6 +2112,9 @@ static int taprio_init(struct Qdisc *sch, struct nlattr *opt,
|
|||
q->qdiscs[i] = qdisc;
|
||||
}
|
||||
|
||||
for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
|
||||
q->fp[tc] = TC_FP_EXPRESS;
|
||||
|
||||
taprio_detect_broken_mqprio(q);
|
||||
|
||||
return taprio_change(sch, opt, extack);
|
||||
|
@ -2215,6 +2258,7 @@ error_nest:
|
|||
}
|
||||
|
||||
static int taprio_dump_tc_entries(struct sk_buff *skb,
|
||||
struct taprio_sched *q,
|
||||
struct sched_gate_list *sched)
|
||||
{
|
||||
struct nlattr *n;
|
||||
|
@ -2232,6 +2276,9 @@ static int taprio_dump_tc_entries(struct sk_buff *skb,
|
|||
sched->max_sdu[tc]))
|
||||
goto nla_put_failure;
|
||||
|
||||
if (nla_put_u32(skb, TCA_TAPRIO_TC_ENTRY_FP, q->fp[tc]))
|
||||
goto nla_put_failure;
|
||||
|
||||
nla_nest_end(skb, n);
|
||||
}
|
||||
|
||||
|
@ -2273,7 +2320,7 @@ static int taprio_dump(struct Qdisc *sch, struct sk_buff *skb)
|
|||
nla_put_u32(skb, TCA_TAPRIO_ATTR_TXTIME_DELAY, q->txtime_delay))
|
||||
goto options_error;
|
||||
|
||||
if (oper && taprio_dump_tc_entries(skb, oper))
|
||||
if (oper && taprio_dump_tc_entries(skb, q, oper))
|
||||
goto options_error;
|
||||
|
||||
if (oper && dump_schedule(skb, oper))
|
||||
|
|
Loading…
Reference in New Issue