// 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) { … }