Hi,

Vladimir Oltean <olte...@gmail.com> 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 <olte...@gmail.com>
> ---
> 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 <olte...@gmail.com>
> + */
> +#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 <olte...@gmail.com>
> + */
> +#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

Reply via email to