linux/drivers/net/dsa/sja1105/sja1105_tas.c

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019, Vladimir Oltean <[email protected]>
 */
#include "sja1105.h"

#define SJA1105_TAS_CLKSRC_DISABLED
#define SJA1105_TAS_CLKSRC_STANDALONE
#define SJA1105_TAS_CLKSRC_AS6802
#define SJA1105_TAS_CLKSRC_PTP
#define SJA1105_GATE_MASK

#define work_to_sja1105_tas(d)
#define tas_to_sja1105(d)

static int sja1105_tas_set_runtime_params(struct sja1105_private *priv)
{}

/* 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).
 */
int sja1105_init_scheduling(struct sja1105_private *priv)
{}

/* 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.
 * So here we are with the task of validating whether the new @admin offload
 * has any conflict with the already established TAS configuration in
 * tas_data->offload.  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, int port,
			    const struct tc_taprio_qopt_offload *admin)
{}

/* Check the tc-taprio configuration on @port for conflicts with the tc-gate
 * global subschedule. If @port is -1, check it against all ports.
 * To reuse the sja1105_tas_check_conflicts logic without refactoring it,
 * convert the gating configuration to a dummy tc-taprio offload structure.
 */
bool sja1105_gating_check_conflicts(struct sja1105_private *priv, int port,
				    struct netlink_ext_ack *extack)
{}

int sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
			    struct tc_taprio_qopt_offload *admin)
{}

static int sja1105_tas_check_running(struct sja1105_private *priv)
{}

/* Write to PTPCLKCORP */
static int sja1105_tas_adjust_drift(struct sja1105_private *priv,
				    u64 correction)
{}

/* Write to PTPSCHTM */
static int sja1105_tas_set_base_time(struct sja1105_private *priv,
				     u64 base_time)
{}

static int sja1105_tas_start(struct sja1105_private *priv)
{}

static int sja1105_tas_stop(struct sja1105_private *priv)
{}

/* The schedule engine and the PTP clock are driven by the same oscillator, and
 * they run in parallel. But whilst the PTP clock can keep an absolute
 * time-of-day, the schedule engine is only running in 'ticks' (25 ticks make
 * up a delta, which is 200ns), and wrapping around at the end of each cycle.
 * The schedule engine is started when the PTP clock reaches the PTPSCHTM time
 * (in PTP domain).
 * Because the PTP clock can be rate-corrected (accelerated or slowed down) by
 * a software servo, and the schedule engine clock runs in parallel to the PTP
 * clock, there is logic internal to the switch that periodically keeps the
 * schedule engine from drifting away. The frequency with which this internal
 * syntonization happens is the PTP clock correction period (PTPCLKCORP). It is
 * a value also in the PTP clock domain, and is also rate-corrected.
 * To be precise, during a correction period, there is logic to determine by
 * how many scheduler clock ticks has the PTP clock drifted. At the end of each
 * correction period/beginning of new one, the length of a delta is shrunk or
 * expanded with an integer number of ticks, compared with the typical 25.
 * So a delta lasts for 200ns (or 25 ticks) only on average.
 * Sometimes it is longer, sometimes it is shorter. The internal syntonization
 * logic can adjust for at most 5 ticks each 20 ticks.
 *
 * The first implication is that you should choose your schedule correction
 * period to be an integer multiple of the schedule length. Preferably one.
 * In case there are schedules of multiple ports active, then the correction
 * period needs to be a multiple of them all. Given the restriction that the
 * cycle times have to be multiples of one another anyway, this means the
 * correction period can simply be the largest cycle time, hence the current
 * choice. This way, the updates are always synchronous to the transmission
 * cycle, and therefore predictable.
 *
 * The second implication is that at the beginning of a correction period, the
 * first few deltas will be modulated in time, until the schedule engine is
 * properly phase-aligned with the PTP clock. For this reason, you should place
 * your best-effort traffic at the beginning of a cycle, and your
 * time-triggered traffic afterwards.
 *
 * The third implication is that once the schedule engine is started, it can
 * only adjust for so much drift within a correction period. In the servo you
 * can only change the PTPCLKRATE, but not step the clock (PTPCLKADD). If you
 * want to do the latter, you need to stop and restart the schedule engine,
 * which is what the state machine handles.
 */
static void sja1105_tas_state_machine(struct work_struct *work)
{}

void sja1105_tas_clockstep(struct dsa_switch *ds)
{}

void sja1105_tas_adjfreq(struct dsa_switch *ds)
{}

void sja1105_tas_setup(struct dsa_switch *ds)
{}

void sja1105_tas_teardown(struct dsa_switch *ds)
{}