Subject: Re: [PATCH v1 net-next 12/15] net: dsa: sja1105: Configure the Time-Aware Scheduler via tc-taprio offload

From: Vinicius Costa Gomes <[email-masked]>
Date: Wed, 11 Sep 2019 12:45:19 -0700

Hi,

Vladimir Oltean <[email-masked]> writes:

> This qdisc offload is the closest thing to what the SJA1105 supports in
> hardware for time-based egress shaping. The switch core really is built
> around SAE AS6802/TTEthernet (a TTTech standard) but can be made to
> operate similarly to IEEE 802.1Qbv with some constraints:
>
> - The gate control list is a global list for all ports. There are 8
>   execution threads that iterate through this global list in parallel.
>   I don't know why 8, there are only 4 front-panel ports.
>
> - Care must be taken by the user to make sure that two execution threads
>   never get to execute a GCL entry simultaneously. I created a O(n^4)
>   checker for this hardware limitation, prior to accepting a taprio
>   offload configuration as valid.
>
> - The spec says that if a GCL entry's interval is shorter than the frame
>   length, you shouldn't send it (and end up in head-of-line blocking).
>   Well, this switch does anyway.
>
> - The switch has no concept of ADMIN and OPER configurations. Because
>   it's so simple, the TAS settings are loaded through the static config
>   tables interface, so there isn't even place for any discussion about
>   'graceful switchover between ADMIN and OPER'. You just reset the
>   switch and upload a new OPER config.
>
> - The switch accepts multiple time sources for the gate events. Right
>   now I am using the standalone clock source as opposed to PTP. So the
>   base time parameter doesn't really do much. Support for the PTP clock
>   source will be added in the next patch.
>
> Signed-off-by: Vladimir Oltean <[email-masked]>
> ---
> Changes since RFC:
> - Removed the sja1105_tas_config_work workqueue.
> - Allocating memory with GFP_KERNEL.
> - Made the ASCII art drawing fit in < 80 characters.
> - Made most of the time-holding variables s64 instead of u64 (for fear
>   of them not holding the result of signed arithmetics properly).
>
>  drivers/net/dsa/sja1105/Kconfig        |   8 +
>  drivers/net/dsa/sja1105/Makefile       |   4 +
>  drivers/net/dsa/sja1105/sja1105.h      |   5 +
>  drivers/net/dsa/sja1105/sja1105_main.c |  19 +-
>  drivers/net/dsa/sja1105/sja1105_tas.c  | 420 +++++++++++++++++++++++++
>  drivers/net/dsa/sja1105/sja1105_tas.h  |  42 +++
>  6 files changed, 497 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/net/dsa/sja1105/sja1105_tas.c
>  create mode 100644 drivers/net/dsa/sja1105/sja1105_tas.h
>
> diff --git a/drivers/net/dsa/sja1105/Kconfig b/drivers/net/dsa/sja1105/Kconfig
> index 770134a66e48..55424f39cb0d 100644
> --- a/drivers/net/dsa/sja1105/Kconfig
> +++ b/drivers/net/dsa/sja1105/Kconfig
> @@ -23,3 +23,11 @@ config NET_DSA_SJA1105_PTP
>  	help
>  	  This enables support for timestamping and PTP clock manipulations in
>  	  the SJA1105 DSA driver.
> +
> +config NET_DSA_SJA1105_TAS
> +	bool "Support for the Time-Aware Scheduler on NXP SJA1105"
> +	depends on NET_DSA_SJA1105
> +	help
> +	  This enables support for the TTEthernet-based egress scheduling
> +	  engine in the SJA1105 DSA driver, which is controlled using a
> +	  hardware offload of the tc-tqprio qdisc.
> diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile
> index 4483113e6259..66161e874344 100644
> --- a/drivers/net/dsa/sja1105/Makefile
> +++ b/drivers/net/dsa/sja1105/Makefile
> @@ -12,3 +12,7 @@ sja1105-objs := \
>  ifdef CONFIG_NET_DSA_SJA1105_PTP
>  sja1105-objs += sja1105_ptp.o
>  endif
> +
> +ifdef CONFIG_NET_DSA_SJA1105_TAS
> +sja1105-objs += sja1105_tas.o
> +endif
> diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h
> index 3ca0b87aa3e4..d95f9ce3b4f9 100644
> --- a/drivers/net/dsa/sja1105/sja1105.h
> +++ b/drivers/net/dsa/sja1105/sja1105.h
> @@ -21,6 +21,7 @@
>  #define SJA1105_AGEING_TIME_MS(ms)	((ms) / 10)
>  
>  #include "sja1105_ptp.h"
> +#include "sja1105_tas.h"
>  
>  /* Keeps the different addresses between E/T and P/Q/R/S */
>  struct sja1105_regs {
> @@ -96,6 +97,7 @@ struct sja1105_private {
>  	struct mutex mgmt_lock;
>  	struct sja1105_tagger_data tagger_data;
>  	struct sja1105_ptp_data ptp_data;
> +	struct sja1105_tas_data tas_data;
>  };
>  
>  #include "sja1105_dynamic_config.h"
> @@ -111,6 +113,9 @@ typedef enum {
>  	SPI_WRITE = 1,
>  } sja1105_spi_rw_mode_t;
>  
> +/* From sja1105_main.c */
> +int sja1105_static_config_reload(struct sja1105_private *priv);
> +
>  /* From sja1105_spi.c */
>  int sja1105_spi_send_packed_buf(const struct sja1105_private *priv,
>  				sja1105_spi_rw_mode_t rw, u64 reg_addr,
> diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c
> index 8b930cc2dabc..4b393782cc84 100644
> --- a/drivers/net/dsa/sja1105/sja1105_main.c
> +++ b/drivers/net/dsa/sja1105/sja1105_main.c
> @@ -22,6 +22,7 @@
>  #include <linux/if_ether.h>
>  #include <linux/dsa/8021q.h>
>  #include "sja1105.h"
> +#include "sja1105_tas.h"
>  
>  static void sja1105_hw_reset(struct gpio_desc *gpio, unsigned int pulse_len,
>  			     unsigned int startup_delay)
> @@ -1382,7 +1383,7 @@ static void sja1105_bridge_leave(struct dsa_switch *ds, int port,
>   * modify at runtime (currently only MAC) and restore them after uploading,
>   * such that this operation is relatively seamless.
>   */
> -static int sja1105_static_config_reload(struct sja1105_private *priv)
> +int sja1105_static_config_reload(struct sja1105_private *priv)
>  {
>  	struct ptp_system_timestamp ptp_sts_before;
>  	struct ptp_system_timestamp ptp_sts_after;
> @@ -1761,6 +1762,7 @@ static void sja1105_teardown(struct dsa_switch *ds)
>  {
>  	struct sja1105_private *priv = ds->priv;
>  
> +	sja1105_tas_teardown(priv);
>  	cancel_work_sync(&priv->tagger_data.rxtstamp_work);
>  	skb_queue_purge(&priv->tagger_data.skb_rxtstamp_queue);
>  	sja1105_ptp_clock_unregister(priv);
> @@ -2088,6 +2090,18 @@ static bool sja1105_port_txtstamp(struct dsa_switch *ds, int port,
>  	return true;
>  }
>  
> +static int sja1105_port_setup_tc(struct dsa_switch *ds, int port,
> +				 enum tc_setup_type type,
> +				 void *type_data)
> +{
> +	switch (type) {
> +	case TC_SETUP_QDISC_TAPRIO:
> +		return sja1105_setup_tc_taprio(ds, port, type_data);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
>  static const struct dsa_switch_ops sja1105_switch_ops = {
>  	.get_tag_protocol	= sja1105_get_tag_protocol,
>  	.setup			= sja1105_setup,
> @@ -2120,6 +2134,7 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
>  	.port_hwtstamp_set	= sja1105_hwtstamp_set,
>  	.port_rxtstamp		= sja1105_port_rxtstamp,
>  	.port_txtstamp		= sja1105_port_txtstamp,
> +	.port_setup_tc		= sja1105_port_setup_tc,
>  };
>  
>  static int sja1105_check_device_id(struct sja1105_private *priv)
> @@ -2229,6 +2244,8 @@ static int sja1105_probe(struct spi_device *spi)
>  	}
>  	mutex_init(&priv->mgmt_lock);
>  
> +	sja1105_tas_setup(priv);
> +
>  	return dsa_register_switch(priv->ds);
>  }
>  
> diff --git a/drivers/net/dsa/sja1105/sja1105_tas.c b/drivers/net/dsa/sja1105/sja1105_tas.c
> new file mode 100644
> index 000000000000..769e1d8e5e8f
> --- /dev/null
> +++ b/drivers/net/dsa/sja1105/sja1105_tas.c
> @@ -0,0 +1,420 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2019, Vladimir Oltean <[email-masked]>
> + */
> +#include "sja1105.h"
> +
> +#define SJA1105_TAS_CLKSRC_DISABLED	0
> +#define SJA1105_TAS_CLKSRC_STANDALONE	1
> +#define SJA1105_TAS_CLKSRC_AS6802	2
> +#define SJA1105_TAS_CLKSRC_PTP		3
> +#define SJA1105_GATE_MASK		GENMASK_ULL(SJA1105_NUM_TC - 1, 0)
> +#define SJA1105_TAS_MAX_DELTA		BIT(19)
> +
> +/* This is not a preprocessor macro because the "ns" argument may or may not be
> + * s64 at caller side. This ensures it is properly type-cast before div_s64.
> + */
> +static s64 ns_to_sja1105_delta(s64 ns)
> +{
> +	return div_s64(ns, 200);
> +}
> +
> +/* Lo and behold: the egress scheduler from hell.
> + *
> + * At the hardware level, the Time-Aware Shaper holds a global linear arrray of
> + * all schedule entries for all ports. These are the Gate Control List (GCL)
> + * entries, let's call them "timeslots" for short. This linear array of
> + * timeslots is held in BLK_IDX_SCHEDULE.
> + *
> + * Then there are a maximum of 8 "execution threads" inside the switch, which
> + * iterate cyclically through the "schedule". Each "cycle" has an entry point
> + * and an exit point, both being timeslot indices in the schedule table. The
> + * hardware calls each cycle a "subschedule".
> + *
> + * Subschedule (cycle) i starts when
> + *   ptpclkval >= ptpschtm + BLK_IDX_SCHEDULE_ENTRY_POINTS[i].delta.
> + *
> + * The hardware scheduler iterates BLK_IDX_SCHEDULE with a k ranging from
> + *   k = BLK_IDX_SCHEDULE_ENTRY_POINTS[i].address to
> + *   k = BLK_IDX_SCHEDULE_PARAMS.subscheind[i]
> + *
> + * For each schedule entry (timeslot) k, the engine executes the gate control
> + * list entry for the duration of BLK_IDX_SCHEDULE[k].delta.
> + *
> + *         +---------+
> + *         |         | BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS
> + *         +---------+
> + *              |
> + *              +-----------------+
> + *                                | .actsubsch
> + *  BLK_IDX_SCHEDULE_ENTRY_POINTS v
> + *                 +-------+-------+
> + *                 |cycle 0|cycle 1|
> + *                 +-------+-------+
> + *                   |  |      |  |
> + *  +----------------+  |      |  +-------------------------------------+
> + *  |   .subschindx     |      |             .subschindx                |
> + *  |                   |      +---------------+                        |
> + *  |          .address |        .address      |                        |
> + *  |                   |                      |                        |
> + *  |                   |                      |                        |
> + *  |  BLK_IDX_SCHEDULE v                      v                        |
> + *  |              +-------+-------+-------+-------+-------+------+     |
> + *  |              |entry 0|entry 1|entry 2|entry 3|entry 4|entry5|     |
> + *  |              +-------+-------+-------+-------+-------+------+     |
> + *  |                                  ^                    ^  ^  ^     |
> + *  |                                  |                    |  |  |     |
> + *  |        +-------------------------+                    |  |  |     |
> + *  |        |              +-------------------------------+  |  |     |
> + *  |        |              |              +-------------------+  |     |
> + *  |        |              |              |                      |     |
> + *  | +---------------------------------------------------------------+ |
> + *  | |subscheind[0]<=subscheind[1]<=subscheind[2]<=...<=subscheind[7]| |
> + *  | +---------------------------------------------------------------+ |
> + *  |        ^              ^                BLK_IDX_SCHEDULE_PARAMS    |
> + *  |        |              |                                           |
> + *  +--------+              +-------------------------------------------+
> + *
> + *  In the above picture there are two subschedules (cycles):
> + *
> + *  - cycle 0: iterates the schedule table from 0 to 2 (and back)
> + *  - cycle 1: iterates the schedule table from 3 to 5 (and back)
> + *
> + *  All other possible execution threads must be marked as unused by making
> + *  their "subschedule end index" (subscheind) equal to the last valid
> + *  subschedule's end index (in this case 5).
> + */
> +static int sja1105_init_scheduling(struct sja1105_private *priv)
> +{
> +	struct sja1105_schedule_entry_points_entry *schedule_entry_points;
> +	struct sja1105_schedule_entry_points_params_entry
> +					*schedule_entry_points_params;
> +	struct sja1105_schedule_params_entry *schedule_params;
> +	struct sja1105_tas_data *tas_data = &priv->tas_data;
> +	struct sja1105_schedule_entry *schedule;
> +	struct sja1105_table *table;
> +	int subscheind[8] = {0};
> +	int schedule_start_idx;
> +	s64 entry_point_delta;
> +	int schedule_end_idx;
> +	int num_entries = 0;
> +	int num_cycles = 0;
> +	int cycle = 0;
> +	int i, k = 0;
> +	int port;
> +
> +	/* Discard previous Schedule Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE];
> +	if (table->entry_count) {
> +		kfree(table->entries);
> +		table->entry_count = 0;
> +	}
> +
> +	/* Discard previous Schedule Entry Points Parameters Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS];
> +	if (table->entry_count) {
> +		kfree(table->entries);
> +		table->entry_count = 0;
> +	}
> +
> +	/* Discard previous Schedule Parameters Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS];
> +	if (table->entry_count) {
> +		kfree(table->entries);
> +		table->entry_count = 0;
> +	}
> +
> +	/* Discard previous Schedule Entry Points Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS];
> +	if (table->entry_count) {
> +		kfree(table->entries);
> +		table->entry_count = 0;
> +	}
> +
> +	/* Figure out the dimensioning of the problem */
> +	for (port = 0; port < SJA1105_NUM_PORTS; port++) {
> +		if (tas_data->config[port]) {
> +			num_entries += tas_data->config[port]->num_entries;
> +			num_cycles++;
> +		}
> +	}
> +
> +	/* Nothing to do */
> +	if (!num_cycles)
> +		return 0;
> +
> +	/* Pre-allocate space in the static config tables */
> +
> +	/* Schedule Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE];
> +	table->entries = kcalloc(num_entries, table->ops->unpacked_entry_size,
> +				 GFP_KERNEL);
> +	if (!table->entries)
> +		return -ENOMEM;
> +	table->entry_count = num_entries;
> +	schedule = table->entries;
> +
> +	/* Schedule Points Parameters Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS];
> +	table->entries = kcalloc(SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT,
> +				 table->ops->unpacked_entry_size, GFP_KERNEL);
> +	if (!table->entries)
> +		return -ENOMEM;

Should this free the previous allocation, in case this one fails?
(also applies to the statements below)

> +	table->entry_count = SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT;
> +	schedule_entry_points_params = table->entries;
> +
> +	/* Schedule Parameters Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS];
> +	table->entries = kcalloc(SJA1105_MAX_SCHEDULE_PARAMS_COUNT,
> +				 table->ops->unpacked_entry_size, GFP_KERNEL);
> +	if (!table->entries)
> +		return -ENOMEM;
> +	table->entry_count = SJA1105_MAX_SCHEDULE_PARAMS_COUNT;
> +	schedule_params = table->entries;
> +
> +	/* Schedule Entry Points Table */
> +	table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS];
> +	table->entries = kcalloc(num_cycles, table->ops->unpacked_entry_size,
> +				 GFP_KERNEL);
> +	if (!table->entries)
> +		return -ENOMEM;
> +	table->entry_count = num_cycles;
> +	schedule_entry_points = table->entries;
> +
> +	/* Finally start populating the static config tables */
> +	schedule_entry_points_params->clksrc = SJA1105_TAS_CLKSRC_STANDALONE;
> +	schedule_entry_points_params->actsubsch = num_cycles - 1;
> +
> +	for (port = 0; port < SJA1105_NUM_PORTS; port++) {
> +		const struct tc_taprio_qopt_offload *tas_config;
> +
> +		tas_config = tas_data->config[port];
> +		if (!tas_config)
> +			continue;
> +
> +		schedule_start_idx = k;
> +		schedule_end_idx = k + tas_config->num_entries - 1;
> +		/* TODO this is only a relative base time for the subschedule
> +		 * (relative to PTPSCHTM). But as we're using standalone and
> +		 * not PTP clock as time reference, leave it like this for now.
> +		 * Later we'll have to enforce that all ports' base times are
> +		 * within SJA1105_TAS_MAX_DELTA 200ns cycles of one another.
> +		 */
> +		entry_point_delta = ns_to_sja1105_delta(tas_config->base_time);
> +
> +		schedule_entry_points[cycle].subschindx = cycle;
> +		schedule_entry_points[cycle].delta = entry_point_delta;
> +		schedule_entry_points[cycle].address = schedule_start_idx;
> +
> +		for (i = cycle; i < 8; i++)
> +			subscheind[i] = schedule_end_idx;
> +
> +		for (i = 0; i < tas_config->num_entries; i++, k++) {
> +			s64 delta_ns = tas_config->entries[i].interval;
> +
> +			schedule[k].delta = ns_to_sja1105_delta(delta_ns);
> +			schedule[k].destports = BIT(port);
> +			schedule[k].resmedia_en = true;
> +			schedule[k].resmedia = SJA1105_GATE_MASK &
> +					~tas_config->entries[i].gate_mask;
> +		}
> +		cycle++;
> +	}
> +
> +	for (i = 0; i < 8; i++)
> +		schedule_params->subscheind[i] = subscheind[i];
> +
> +	return 0;
> +}
> +
> +/* Be there 2 port subschedules, each executing an arbitrary number of gate
> + * open/close events cyclically.
> + * None of those gate events must ever occur at the exact same time, otherwise
> + * the switch is known to act in exotically strange ways.
> + * However the hardware doesn't bother performing these integrity checks - the
> + * designers probably said "nah, let's leave that to the experts" - oh well,
> + * now we're the experts.
> + * So here we are with the task of validating whether the new @qopt has any
> + * conflict with the already established TAS configuration in tas_data->config.
> + * We already know the other ports are in harmony with one another, otherwise
> + * we wouldn't have saved them.
> + * Each gate event executes periodically, with a period of @cycle_time and a
> + * phase given by its cycle's @base_time plus its offset within the cycle
> + * (which in turn is given by the length of the events prior to it).
> + * There are two aspects to possible collisions:
> + * - Collisions within one cycle's (actually the longest cycle's) time frame.
> + *   For that, we need to compare the cartesian product of each possible
> + *   occurrence of each event within one cycle time.
> + * - Collisions in the future. Events may not collide within one cycle time,
> + *   but if two port schedules don't have the same periodicity (aka the cycle
> + *   times aren't multiples of one another), they surely will some time in the
> + *   future (actually they will collide an infinite amount of times).
> + */
> +static bool
> +sja1105_tas_check_conflicts(struct sja1105_private *priv,
> +			    const struct tc_taprio_qopt_offload *qopt)
> +{
> +	struct sja1105_tas_data *tas_data = &priv->tas_data;
> +	int port;
> +
> +	for (port = 0; port < SJA1105_NUM_PORTS; port++) {
> +		const struct tc_taprio_qopt_offload *tas_config;
> +		s64 max_cycle_time, min_cycle_time;
> +		s64 delta1, delta2;
> +		s64 rbt1, rbt2;
> +		s64 stop_time;
> +		s64 t1, t2;
> +		int i, j;
> +		s32 rem;
> +
> +		tas_config = tas_data->config[port];
> +
> +		if (!tas_config)
> +			continue;
> +
> +		/* Check if the two cycle times are multiples of one another.
> +		 * If they aren't, then they will surely collide.
> +		 */
> +		max_cycle_time = max(tas_config->cycle_time, qopt->cycle_time);
> +		min_cycle_time = min(tas_config->cycle_time, qopt->cycle_time);
> +		div_s64_rem(max_cycle_time, min_cycle_time, &rem);
> +		if (rem)
> +			return true;
> +
> +		/* Calculate the "reduced" base time of each of the two cycles
> +		 * (transposed back as close to 0 as possible) by dividing to
> +		 * the cycle time.
> +		 */
> +		div_s64_rem(tas_config->base_time, tas_config->cycle_time,
> +			    &rem);
> +		rbt1 = rem;
> +
> +		div_s64_rem(qopt->base_time, qopt->cycle_time, &rem);
> +		rbt2 = rem;
> +
> +		stop_time = max_cycle_time + max(rbt1, rbt2);
> +
> +		/* delta1 is the relative base time of each GCL entry within
> +		 * the established ports' TAS config.
> +		 */
> +		for (i = 0, delta1 = 0;
> +		     i < tas_config->num_entries;
> +		     delta1 += tas_config->entries[i].interval, i++) {
> +
> +			/* delta2 is the relative base time of each GCL entry
> +			 * within the newly added TAS config.
> +			 */
> +			for (j = 0, delta2 = 0;
> +			     j < qopt->num_entries;
> +			     delta2 += qopt->entries[j].interval, j++) {
> +
> +				/* t1 follows all possible occurrences of the
> +				 * established ports' GCL entry i within the
> +				 * first cycle time.
> +				 */
> +				for (t1 = rbt1 + delta1;
> +				     t1 <= stop_time;
> +				     t1 += tas_config->cycle_time) {
> +
> +					/* t2 follows all possible occurrences
> +					 * of the newly added GCL entry j
> +					 * within the first cycle time.
> +					 */
> +					for (t2 = rbt2 + delta2;
> +					     t2 <= stop_time;
> +					     t2 += qopt->cycle_time) {
> +
> +						if (t1 == t2) {
> +							dev_warn(priv->ds->dev,
> +								 "GCL entry %d collides with entry %d of port %d\n",
> +								 j, i, port);
> +							return true;
> +						}
> +					}
> +				}
> +			}
> +		}
> +	}
> +
> +	return false;
> +}
> +
> +int sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
> +			    struct tc_taprio_qopt_offload *tas_config)
> +{
> +	struct sja1105_private *priv = ds->priv;
> +	struct sja1105_tas_data *tas_data = &priv->tas_data;
> +	int rc, i;
> +
> +	/* Can't change an already configured port (must delete qdisc first).
> +	 * Can't delete the qdisc from an unconfigured port.
> +	 */
> +	if (!!tas_data->config[port] == tas_config->enable)
> +		return -EINVAL;
> +
> +	if (!tas_config->enable) {
> +		taprio_free(tas_data->config[port]);
> +		tas_data->config[port] = NULL;
> +
> +		rc = sja1105_init_scheduling(priv);
> +		if (rc < 0)
> +			return rc;
> +
> +		return sja1105_static_config_reload(priv);
> +	}
> +
> +	/* The cycle time extension is the amount of time the last cycle from
> +	 * the old OPER needs to be extended in order to phase-align with the
> +	 * base time of the ADMIN when that becomes the new OPER.
> +	 * But of course our switch needs to be reset to switch-over between
> +	 * the ADMIN and the OPER configs - so much for a seamless transition.
> +	 * So don't add insult over injury and just say we don't support cycle
> +	 * time extension.
> +	 */
> +	if (tas_config->cycle_time_extension)
> +		return -ENOTSUPP;
> +
> +	if (!ns_to_sja1105_delta(tas_config->base_time)) {
> +		dev_err(ds->dev, "A base time of zero is not hardware-allowed\n");
> +		return -ERANGE;
> +	}
> +
> +	for (i = 0; i < tas_config->num_entries; i++) {
> +		s64 delta_ns = tas_config->entries[i].interval;
> +		s64 delta_cycles = ns_to_sja1105_delta(delta_ns);
> +		bool too_long, too_short;
> +
> +		too_long = (delta_cycles >= SJA1105_TAS_MAX_DELTA);
> +		too_short = (delta_cycles == 0);
> +		if (too_long || too_short) {
> +			dev_err(priv->ds->dev,
> +				"Interval %llu too %s for GCL entry %d\n",
> +				delta_ns, too_long ? "long" : "short", i);
> +			return -ERANGE;
> +		}
> +	}
> +
> +	if (sja1105_tas_check_conflicts(priv, tas_config))
> +		return -ERANGE;
> +
> +	tas_data->config[port] = taprio_get(tas_config);
> +
> +	rc = sja1105_init_scheduling(priv);
> +	if (rc < 0)
> +		return rc;
> +
> +	return sja1105_static_config_reload(priv);
> +}
> +
> +void sja1105_tas_setup(struct sja1105_private *priv)
> +{
> +}
> +
> +void sja1105_tas_teardown(struct sja1105_private *priv)
> +{
> +	struct sja1105_tas_data *tas_data = &priv->tas_data;
> +	int port;
> +
> +	for (port = 0; port < SJA1105_NUM_PORTS; port++)
> +		if (tas_data->config[port])
> +			taprio_free(tas_data->config[port]);
> +}
> diff --git a/drivers/net/dsa/sja1105/sja1105_tas.h b/drivers/net/dsa/sja1105/sja1105_tas.h
> new file mode 100644
> index 000000000000..0ef82810d9d7
> --- /dev/null
> +++ b/drivers/net/dsa/sja1105/sja1105_tas.h
> @@ -0,0 +1,42 @@
> +/* SPDX-License-Identifier: GPL-2.0
> + * Copyright (c) 2019, Vladimir Oltean <[email-masked]>
> + */
> +#ifndef _SJA1105_TAS_H
> +#define _SJA1105_TAS_H
> +
> +#include <net/pkt_sched.h>
> +
> +#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_TAS)
> +
> +struct sja1105_tas_data {
> +	struct tc_taprio_qopt_offload *config[SJA1105_NUM_PORTS];
> +};
> +
> +int sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
> +			    struct tc_taprio_qopt_offload *qopt);
> +
> +void sja1105_tas_setup(struct sja1105_private *priv);
> +
> +void sja1105_tas_teardown(struct sja1105_private *priv);
> +
> +#else
> +
> +/* C doesn't allow empty structures, bah! */
> +struct sja1105_tas_data {
> +	u8 dummy;
> +};
> +
> +static inline int
> +sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
> +			struct tc_taprio_qopt_offload *qopt)
> +{
> +	return -EOPNOTSUPP;
> +}
> +
> +static inline void sja1105_tas_setup(struct sja1105_private *priv) { }
> +
> +static inline void sja1105_tas_teardown(struct sja1105_private *priv) { }
> +
> +#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_TAS) */
> +
> +#endif /* _SJA1105_TAS_H */
> -- 
> 2.17.1