diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h index 8bac6412291..e459662e03a 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe.h @@ -239,6 +239,18 @@ */ #define PPE_FLOW_SPREAD_QUEUES 4 +/* --- RSS hash (in the IPO block): unprogrammed, the 5-tuple hash is + * degenerate and every flow lands in one bucket. + */ +#define PPE_RSS_HASH_MASK (0x0b0000 + 0x4318) +#define PPE_RSS_HASH_SEED (0x0b0000 + 0x431c) +#define PPE_RSS_HASH_MIX(i) (0x0b0000 + 0x4320 + (i) * 0x4) +#define PPE_RSS_HASH_FIN(i) (0x0b0000 + 0x4350 + (i) * 0x4) +#define PPE_RSS_HASH_MASK_IPV4 (0x0b0000 + 0x4380) +#define PPE_RSS_HASH_SEED_IPV4 (0x0b0000 + 0x4384) +#define PPE_RSS_HASH_MIX_IPV4(i) (0x0b0000 + 0x4390 + (i) * 0x4) +#define PPE_RSS_HASH_FIN_IPV4(i) (0x0b0000 + 0x43b0 + (i) * 0x4) + /* --- L2 (base 0x060000) --- */ #define PPE_L2_BASE 0x060000 @@ -502,16 +514,6 @@ #define PPE_QM_UCAST_HASH_MAP(i) (PPE_QM_BASE + 0x30000 + (i) * 0x10) #define PPE_QM_HASH_CLASS GENMASK(3, 0) -/* The 5-tuple RSS hash in the IPO block. */ -#define PPE_RSS_HASH_MASK (0x0b0000 + 0x4318) -#define PPE_RSS_HASH_SEED (0x0b0000 + 0x431c) -#define PPE_RSS_HASH_MIX(i) (0x0b0000 + 0x4320 + (i) * 0x4) -#define PPE_RSS_HASH_FIN(i) (0x0b0000 + 0x4350 + (i) * 0x4) -#define PPE_RSS_HASH_MASK_IPV4 (0x0b0000 + 0x4380) -#define PPE_RSS_HASH_SEED_IPV4 (0x0b0000 + 0x4384) -#define PPE_RSS_HASH_MIX_IPV4(i) (0x0b0000 + 0x4390 + (i) * 0x4) -#define PPE_RSS_HASH_FIN_IPV4(i) (0x0b0000 + 0x43b0 + (i) * 0x4) - #define PPE_QM_UCAST_PRI_MAP(i) (PPE_QM_BASE + 0x42000 + (i) * 0x10) #define PPE_QM_PRI_CLASS GENMASK(3, 0) @@ -745,6 +747,8 @@ static inline struct qca_ppe_priv *ds_to_priv(struct dsa_switch *ds) u64 ppe_mib_read(struct qca_ppe_priv *priv, int port, unsigned int off); struct tc_tbf_qopt_offload; +struct tc_ets_qopt_offload; +struct tc_prio_qopt_offload; struct tc_query_caps_base; struct tc_mqprio_qopt_offload; @@ -762,6 +766,10 @@ int qca_ppe_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel, int nsel); int qca_ppe_setup_tc_tbf(struct qca_ppe_priv *priv, int port, struct tc_tbf_qopt_offload *qopt); +int qca_ppe_setup_tc_ets(struct qca_ppe_priv *priv, int port, + struct tc_ets_qopt_offload *qopt); +int qca_ppe_setup_tc_prio(struct qca_ppe_priv *priv, int port, + struct tc_prio_qopt_offload *qopt); int qca_ppe_tc_query_caps(struct tc_query_caps_base *base); int qca_ppe_setup_tc_mqprio(struct qca_ppe_priv *priv, int port, struct tc_mqprio_qopt_offload *qopt); diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c index 73c15609a2f..eb5faa0c089 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_ppe_scheduler.c @@ -1647,6 +1647,135 @@ int qca_ppe_tc_query_caps(struct tc_query_caps_base *base) return 0; } +/* The port's unicast scheduler is three strict bands built at probe and not + * reconfigurable: priorities eight and up at the top, the classified + * priorities under them, best-effort below both, and the queues inside a band + * a hash spread served round robin rather than a ladder of their own. ETS and + * prio are how that shape is expressed to tc, which numbers bands from the + * top, so the top band is band 0. A shape this hardware cannot give is refused + * rather than quietly flattened into one it can; nothing is programmed either + * way, an accepted qdisc only gains the port's counters. + */ +#define PPE_QOS_BANDS 3 + +static int ppe_qos_bands_set(struct qca_ppe_priv *priv, int port, u32 handle, + unsigned int bands, const u8 *priomap) +{ + struct ppe_port_shaper *sh = &priv->shaper[port]; + unsigned int i; + + if (bands != PPE_QOS_BANDS) { + dev_err(priv->ds.dev, + "port %d: %u bands, this scheduler is fixed at %d\n", + port, bands, PPE_QOS_BANDS); + return -EINVAL; + } + + /* The priomap the qdisc fills in when the user gave none puts every + * priority on the last band, which is an absence of an opinion rather + * than a different one. + */ + for (i = 0; i < TC_PRIO_MAX + 1; i++) + if (priomap[i] != bands - 1) + break; + + if (i < TC_PRIO_MAX + 1) { + for (i = 0; i < TC_PRIO_MAX + 1; i++) { + /* The band split ppe_qm_init gives a user port. */ + u8 band = PPE_QOS_BANDS - 1 - + min_t(unsigned int, + i / PPE_FLOW_SPREAD_QUEUES, + PPE_QOS_BANDS - 1); + + if (priomap[i] == band) + continue; + + dev_err(priv->ds.dev, + "port %d: priority %u is mapped to band %u; this scheduler is fixed and gives it band %u\n", + port, i, priomap[i], band); + return -EOPNOTSUPP; + } + } + + sh->bands_handle = handle; + ppe_port_tx_counters(priv, port, &sh->base_bytes, &sh->base_pkts, + &sh->base_drops); + + return 0; +} + +int qca_ppe_setup_tc_ets(struct qca_ppe_priv *priv, int port, + struct tc_ets_qopt_offload *qopt) +{ + struct ppe_port_shaper *sh = &priv->shaper[port]; + unsigned int i; + + if (qopt->parent != TC_H_ROOT) + return -EOPNOTSUPP; + + switch (qopt->command) { + case TC_ETS_REPLACE: + /* sch_ets gives every band a quantum whether the user asked + * for one or not, so a non-zero quantum is not a request. Bands + * that differ are: refuse those, because the bands are strict + * and the only round robin under them is between a band's hash + * buckets, which no priority selects. + */ + for (i = 1; i < qopt->replace_params.bands; i++) { + if (qopt->replace_params.quanta[i] == + qopt->replace_params.quanta[0]) + continue; + + dev_err(priv->ds.dev, + "port %d: band %u asks for a weight of its own; these bands are strict\n", + port, i); + return -EOPNOTSUPP; + } + + return ppe_qos_bands_set(priv, port, qopt->handle, + qopt->replace_params.bands, + qopt->replace_params.priomap); + case TC_ETS_DESTROY: + if (qopt->handle == sh->bands_handle) + sh->bands_handle = 0; + return 0; + case TC_ETS_STATS: + if (!sh->bands_handle || qopt->handle != sh->bands_handle) + return -EOPNOTSUPP; + ppe_port_shaper_stats(priv, port, &qopt->stats); + return 0; + default: + return -EOPNOTSUPP; + } +} + +int qca_ppe_setup_tc_prio(struct qca_ppe_priv *priv, int port, + struct tc_prio_qopt_offload *qopt) +{ + struct ppe_port_shaper *sh = &priv->shaper[port]; + + if (qopt->parent != TC_H_ROOT) + return -EOPNOTSUPP; + + switch (qopt->command) { + case TC_PRIO_REPLACE: + return ppe_qos_bands_set(priv, port, qopt->handle, + qopt->replace_params.bands, + qopt->replace_params.priomap); + case TC_PRIO_DESTROY: + if (qopt->handle == sh->bands_handle) + sh->bands_handle = 0; + return 0; + case TC_PRIO_STATS: + if (!sh->bands_handle || qopt->handle != sh->bands_handle) + return -EOPNOTSUPP; + ppe_port_shaper_stats(priv, port, &qopt->stats); + return 0; + default: + return -EOPNOTSUPP; + } +} + /* The hardware has one shaper per port and nowhere to hang a class off it, so * only a root tbf is a rate this switch can keep. The qdisc's overhead, mpu and * linklayer are not carried into the hardware, which meters the frame it puts @@ -1704,6 +1833,10 @@ int qca_ppe_setup_tc(struct dsa_switch *ds, int port, enum tc_setup_type type, switch (type) { case TC_SETUP_QDISC_TBF: return qca_ppe_setup_tc_tbf(priv, port, type_data); + case TC_SETUP_QDISC_ETS: + return qca_ppe_setup_tc_ets(priv, port, type_data); + case TC_SETUP_QDISC_PRIO: + return qca_ppe_setup_tc_prio(priv, port, type_data); case TC_SETUP_QDISC_MQPRIO: return qca_ppe_setup_tc_mqprio(priv, port, type_data); case TC_QUERY_CAPS: