linux/drivers/net/dsa/sja1105/sja1105_tas.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
   3 */
   4#include "sja1105.h"
   5
   6#define SJA1105_TAS_CLKSRC_DISABLED     0
   7#define SJA1105_TAS_CLKSRC_STANDALONE   1
   8#define SJA1105_TAS_CLKSRC_AS6802       2
   9#define SJA1105_TAS_CLKSRC_PTP          3
  10#define SJA1105_TAS_MAX_DELTA           BIT(19)
  11#define SJA1105_GATE_MASK               GENMASK_ULL(SJA1105_NUM_TC - 1, 0)
  12
  13/* This is not a preprocessor macro because the "ns" argument may or may not be
  14 * s64 at caller side. This ensures it is properly type-cast before div_s64.
  15 */
  16static s64 ns_to_sja1105_delta(s64 ns)
  17{
  18        return div_s64(ns, 200);
  19}
  20
  21/* Lo and behold: the egress scheduler from hell.
  22 *
  23 * At the hardware level, the Time-Aware Shaper holds a global linear arrray of
  24 * all schedule entries for all ports. These are the Gate Control List (GCL)
  25 * entries, let's call them "timeslots" for short. This linear array of
  26 * timeslots is held in BLK_IDX_SCHEDULE.
  27 *
  28 * Then there are a maximum of 8 "execution threads" inside the switch, which
  29 * iterate cyclically through the "schedule". Each "cycle" has an entry point
  30 * and an exit point, both being timeslot indices in the schedule table. The
  31 * hardware calls each cycle a "subschedule".
  32 *
  33 * Subschedule (cycle) i starts when
  34 *   ptpclkval >= ptpschtm + BLK_IDX_SCHEDULE_ENTRY_POINTS[i].delta.
  35 *
  36 * The hardware scheduler iterates BLK_IDX_SCHEDULE with a k ranging from
  37 *   k = BLK_IDX_SCHEDULE_ENTRY_POINTS[i].address to
  38 *   k = BLK_IDX_SCHEDULE_PARAMS.subscheind[i]
  39 *
  40 * For each schedule entry (timeslot) k, the engine executes the gate control
  41 * list entry for the duration of BLK_IDX_SCHEDULE[k].delta.
  42 *
  43 *         +---------+
  44 *         |         | BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS
  45 *         +---------+
  46 *              |
  47 *              +-----------------+
  48 *                                | .actsubsch
  49 *  BLK_IDX_SCHEDULE_ENTRY_POINTS v
  50 *                 +-------+-------+
  51 *                 |cycle 0|cycle 1|
  52 *                 +-------+-------+
  53 *                   |  |      |  |
  54 *  +----------------+  |      |  +-------------------------------------+
  55 *  |   .subschindx     |      |             .subschindx                |
  56 *  |                   |      +---------------+                        |
  57 *  |          .address |        .address      |                        |
  58 *  |                   |                      |                        |
  59 *  |                   |                      |                        |
  60 *  |  BLK_IDX_SCHEDULE v                      v                        |
  61 *  |              +-------+-------+-------+-------+-------+------+     |
  62 *  |              |entry 0|entry 1|entry 2|entry 3|entry 4|entry5|     |
  63 *  |              +-------+-------+-------+-------+-------+------+     |
  64 *  |                                  ^                    ^  ^  ^     |
  65 *  |                                  |                    |  |  |     |
  66 *  |        +-------------------------+                    |  |  |     |
  67 *  |        |              +-------------------------------+  |  |     |
  68 *  |        |              |              +-------------------+  |     |
  69 *  |        |              |              |                      |     |
  70 *  | +---------------------------------------------------------------+ |
  71 *  | |subscheind[0]<=subscheind[1]<=subscheind[2]<=...<=subscheind[7]| |
  72 *  | +---------------------------------------------------------------+ |
  73 *  |        ^              ^                BLK_IDX_SCHEDULE_PARAMS    |
  74 *  |        |              |                                           |
  75 *  +--------+              +-------------------------------------------+
  76 *
  77 *  In the above picture there are two subschedules (cycles):
  78 *
  79 *  - cycle 0: iterates the schedule table from 0 to 2 (and back)
  80 *  - cycle 1: iterates the schedule table from 3 to 5 (and back)
  81 *
  82 *  All other possible execution threads must be marked as unused by making
  83 *  their "subschedule end index" (subscheind) equal to the last valid
  84 *  subschedule's end index (in this case 5).
  85 */
  86static int sja1105_init_scheduling(struct sja1105_private *priv)
  87{
  88        struct sja1105_schedule_entry_points_entry *schedule_entry_points;
  89        struct sja1105_schedule_entry_points_params_entry
  90                                        *schedule_entry_points_params;
  91        struct sja1105_schedule_params_entry *schedule_params;
  92        struct sja1105_tas_data *tas_data = &priv->tas_data;
  93        struct sja1105_schedule_entry *schedule;
  94        struct sja1105_table *table;
  95        int schedule_start_idx;
  96        s64 entry_point_delta;
  97        int schedule_end_idx;
  98        int num_entries = 0;
  99        int num_cycles = 0;
 100        int cycle = 0;
 101        int i, k = 0;
 102        int port;
 103
 104        /* Discard previous Schedule Table */
 105        table = &priv->static_config.tables[BLK_IDX_SCHEDULE];
 106        if (table->entry_count) {
 107                kfree(table->entries);
 108                table->entry_count = 0;
 109        }
 110
 111        /* Discard previous Schedule Entry Points Parameters Table */
 112        table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS];
 113        if (table->entry_count) {
 114                kfree(table->entries);
 115                table->entry_count = 0;
 116        }
 117
 118        /* Discard previous Schedule Parameters Table */
 119        table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS];
 120        if (table->entry_count) {
 121                kfree(table->entries);
 122                table->entry_count = 0;
 123        }
 124
 125        /* Discard previous Schedule Entry Points Table */
 126        table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS];
 127        if (table->entry_count) {
 128                kfree(table->entries);
 129                table->entry_count = 0;
 130        }
 131
 132        /* Figure out the dimensioning of the problem */
 133        for (port = 0; port < SJA1105_NUM_PORTS; port++) {
 134                if (tas_data->offload[port]) {
 135                        num_entries += tas_data->offload[port]->num_entries;
 136                        num_cycles++;
 137                }
 138        }
 139
 140        /* Nothing to do */
 141        if (!num_cycles)
 142                return 0;
 143
 144        /* Pre-allocate space in the static config tables */
 145
 146        /* Schedule Table */
 147        table = &priv->static_config.tables[BLK_IDX_SCHEDULE];
 148        table->entries = kcalloc(num_entries, table->ops->unpacked_entry_size,
 149                                 GFP_KERNEL);
 150        if (!table->entries)
 151                return -ENOMEM;
 152        table->entry_count = num_entries;
 153        schedule = table->entries;
 154
 155        /* Schedule Points Parameters Table */
 156        table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS];
 157        table->entries = kcalloc(SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT,
 158                                 table->ops->unpacked_entry_size, GFP_KERNEL);
 159        if (!table->entries)
 160                /* Previously allocated memory will be freed automatically in
 161                 * sja1105_static_config_free. This is true for all early
 162                 * returns below.
 163                 */
 164                return -ENOMEM;
 165        table->entry_count = SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT;
 166        schedule_entry_points_params = table->entries;
 167
 168        /* Schedule Parameters Table */
 169        table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS];
 170        table->entries = kcalloc(SJA1105_MAX_SCHEDULE_PARAMS_COUNT,
 171                                 table->ops->unpacked_entry_size, GFP_KERNEL);
 172        if (!table->entries)
 173                return -ENOMEM;
 174        table->entry_count = SJA1105_MAX_SCHEDULE_PARAMS_COUNT;
 175        schedule_params = table->entries;
 176
 177        /* Schedule Entry Points Table */
 178        table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS];
 179        table->entries = kcalloc(num_cycles, table->ops->unpacked_entry_size,
 180                                 GFP_KERNEL);
 181        if (!table->entries)
 182                return -ENOMEM;
 183        table->entry_count = num_cycles;
 184        schedule_entry_points = table->entries;
 185
 186        /* Finally start populating the static config tables */
 187        schedule_entry_points_params->clksrc = SJA1105_TAS_CLKSRC_STANDALONE;
 188        schedule_entry_points_params->actsubsch = num_cycles - 1;
 189
 190        for (port = 0; port < SJA1105_NUM_PORTS; port++) {
 191                const struct tc_taprio_qopt_offload *offload;
 192
 193                offload = tas_data->offload[port];
 194                if (!offload)
 195                        continue;
 196
 197                schedule_start_idx = k;
 198                schedule_end_idx = k + offload->num_entries - 1;
 199                /* TODO this is the base time for the port's subschedule,
 200                 * relative to PTPSCHTM. But as we're using the standalone
 201                 * clock source and not PTP clock as time reference, there's
 202                 * little point in even trying to put more logic into this,
 203                 * like preserving the phases between the subschedules of
 204                 * different ports. We'll get all of that when switching to the
 205                 * PTP clock source.
 206                 */
 207                entry_point_delta = 1;
 208
 209                schedule_entry_points[cycle].subschindx = cycle;
 210                schedule_entry_points[cycle].delta = entry_point_delta;
 211                schedule_entry_points[cycle].address = schedule_start_idx;
 212
 213                /* The subschedule end indices need to be
 214                 * monotonically increasing.
 215                 */
 216                for (i = cycle; i < 8; i++)
 217                        schedule_params->subscheind[i] = schedule_end_idx;
 218
 219                for (i = 0; i < offload->num_entries; i++, k++) {
 220                        s64 delta_ns = offload->entries[i].interval;
 221
 222                        schedule[k].delta = ns_to_sja1105_delta(delta_ns);
 223                        schedule[k].destports = BIT(port);
 224                        schedule[k].resmedia_en = true;
 225                        schedule[k].resmedia = SJA1105_GATE_MASK &
 226                                        ~offload->entries[i].gate_mask;
 227                }
 228                cycle++;
 229        }
 230
 231        return 0;
 232}
 233
 234/* Be there 2 port subschedules, each executing an arbitrary number of gate
 235 * open/close events cyclically.
 236 * None of those gate events must ever occur at the exact same time, otherwise
 237 * the switch is known to act in exotically strange ways.
 238 * However the hardware doesn't bother performing these integrity checks.
 239 * So here we are with the task of validating whether the new @admin offload
 240 * has any conflict with the already established TAS configuration in
 241 * tas_data->offload.  We already know the other ports are in harmony with one
 242 * another, otherwise we wouldn't have saved them.
 243 * Each gate event executes periodically, with a period of @cycle_time and a
 244 * phase given by its cycle's @base_time plus its offset within the cycle
 245 * (which in turn is given by the length of the events prior to it).
 246 * There are two aspects to possible collisions:
 247 * - Collisions within one cycle's (actually the longest cycle's) time frame.
 248 *   For that, we need to compare the cartesian product of each possible
 249 *   occurrence of each event within one cycle time.
 250 * - Collisions in the future. Events may not collide within one cycle time,
 251 *   but if two port schedules don't have the same periodicity (aka the cycle
 252 *   times aren't multiples of one another), they surely will some time in the
 253 *   future (actually they will collide an infinite amount of times).
 254 */
 255static bool
 256sja1105_tas_check_conflicts(struct sja1105_private *priv, int port,
 257                            const struct tc_taprio_qopt_offload *admin)
 258{
 259        struct sja1105_tas_data *tas_data = &priv->tas_data;
 260        const struct tc_taprio_qopt_offload *offload;
 261        s64 max_cycle_time, min_cycle_time;
 262        s64 delta1, delta2;
 263        s64 rbt1, rbt2;
 264        s64 stop_time;
 265        s64 t1, t2;
 266        int i, j;
 267        s32 rem;
 268
 269        offload = tas_data->offload[port];
 270        if (!offload)
 271                return false;
 272
 273        /* Check if the two cycle times are multiples of one another.
 274         * If they aren't, then they will surely collide.
 275         */
 276        max_cycle_time = max(offload->cycle_time, admin->cycle_time);
 277        min_cycle_time = min(offload->cycle_time, admin->cycle_time);
 278        div_s64_rem(max_cycle_time, min_cycle_time, &rem);
 279        if (rem)
 280                return true;
 281
 282        /* Calculate the "reduced" base time of each of the two cycles
 283         * (transposed back as close to 0 as possible) by dividing to
 284         * the cycle time.
 285         */
 286        div_s64_rem(offload->base_time, offload->cycle_time, &rem);
 287        rbt1 = rem;
 288
 289        div_s64_rem(admin->base_time, admin->cycle_time, &rem);
 290        rbt2 = rem;
 291
 292        stop_time = max_cycle_time + max(rbt1, rbt2);
 293
 294        /* delta1 is the relative base time of each GCL entry within
 295         * the established ports' TAS config.
 296         */
 297        for (i = 0, delta1 = 0;
 298             i < offload->num_entries;
 299             delta1 += offload->entries[i].interval, i++) {
 300                /* delta2 is the relative base time of each GCL entry
 301                 * within the newly added TAS config.
 302                 */
 303                for (j = 0, delta2 = 0;
 304                     j < admin->num_entries;
 305                     delta2 += admin->entries[j].interval, j++) {
 306                        /* t1 follows all possible occurrences of the
 307                         * established ports' GCL entry i within the
 308                         * first cycle time.
 309                         */
 310                        for (t1 = rbt1 + delta1;
 311                             t1 <= stop_time;
 312                             t1 += offload->cycle_time) {
 313                                /* t2 follows all possible occurrences
 314                                 * of the newly added GCL entry j
 315                                 * within the first cycle time.
 316                                 */
 317                                for (t2 = rbt2 + delta2;
 318                                     t2 <= stop_time;
 319                                     t2 += admin->cycle_time) {
 320                                        if (t1 == t2) {
 321                                                dev_warn(priv->ds->dev,
 322                                                         "GCL entry %d collides with entry %d of port %d\n",
 323                                                         j, i, port);
 324                                                return true;
 325                                        }
 326                                }
 327                        }
 328                }
 329        }
 330
 331        return false;
 332}
 333
 334int sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
 335                            struct tc_taprio_qopt_offload *admin)
 336{
 337        struct sja1105_private *priv = ds->priv;
 338        struct sja1105_tas_data *tas_data = &priv->tas_data;
 339        int other_port, rc, i;
 340
 341        /* Can't change an already configured port (must delete qdisc first).
 342         * Can't delete the qdisc from an unconfigured port.
 343         */
 344        if (!!tas_data->offload[port] == admin->enable)
 345                return -EINVAL;
 346
 347        if (!admin->enable) {
 348                taprio_offload_free(tas_data->offload[port]);
 349                tas_data->offload[port] = NULL;
 350
 351                rc = sja1105_init_scheduling(priv);
 352                if (rc < 0)
 353                        return rc;
 354
 355                return sja1105_static_config_reload(priv);
 356        }
 357
 358        /* The cycle time extension is the amount of time the last cycle from
 359         * the old OPER needs to be extended in order to phase-align with the
 360         * base time of the ADMIN when that becomes the new OPER.
 361         * But of course our switch needs to be reset to switch-over between
 362         * the ADMIN and the OPER configs - so much for a seamless transition.
 363         * So don't add insult over injury and just say we don't support cycle
 364         * time extension.
 365         */
 366        if (admin->cycle_time_extension)
 367                return -ENOTSUPP;
 368
 369        if (!ns_to_sja1105_delta(admin->base_time)) {
 370                dev_err(ds->dev, "A base time of zero is not hardware-allowed\n");
 371                return -ERANGE;
 372        }
 373
 374        for (i = 0; i < admin->num_entries; i++) {
 375                s64 delta_ns = admin->entries[i].interval;
 376                s64 delta_cycles = ns_to_sja1105_delta(delta_ns);
 377                bool too_long, too_short;
 378
 379                too_long = (delta_cycles >= SJA1105_TAS_MAX_DELTA);
 380                too_short = (delta_cycles == 0);
 381                if (too_long || too_short) {
 382                        dev_err(priv->ds->dev,
 383                                "Interval %llu too %s for GCL entry %d\n",
 384                                delta_ns, too_long ? "long" : "short", i);
 385                        return -ERANGE;
 386                }
 387        }
 388
 389        for (other_port = 0; other_port < SJA1105_NUM_PORTS; other_port++) {
 390                if (other_port == port)
 391                        continue;
 392
 393                if (sja1105_tas_check_conflicts(priv, other_port, admin))
 394                        return -ERANGE;
 395        }
 396
 397        tas_data->offload[port] = taprio_offload_get(admin);
 398
 399        rc = sja1105_init_scheduling(priv);
 400        if (rc < 0)
 401                return rc;
 402
 403        return sja1105_static_config_reload(priv);
 404}
 405
 406void sja1105_tas_setup(struct dsa_switch *ds)
 407{
 408}
 409
 410void sja1105_tas_teardown(struct dsa_switch *ds)
 411{
 412        struct sja1105_private *priv = ds->priv;
 413        struct tc_taprio_qopt_offload *offload;
 414        int port;
 415
 416        for (port = 0; port < SJA1105_NUM_PORTS; port++) {
 417                offload = priv->tas_data.offload[port];
 418                if (!offload)
 419                        continue;
 420
 421                taprio_offload_free(offload);
 422        }
 423}
 424