Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: mscc: ocelot: fix oversize frame dropping for preemptible TCs

This switch implements Hold/Release in a strange way, with no control
from the user as required by IEEE 802.1Q-2018 through Set-And-Hold-MAC
and Set-And-Release-MAC, but rather, it emits HOLD requests implicitly
based on the schedule.

Namely, when the gate of a preemptible TC is about to close (actually
QSYS::PREEMPTION_CFG.HOLD_ADVANCE octet times in advance of this event),
the QSYS seems to emit a HOLD request pulse towards the MAC which
preempts the currently transmitted packet, and further packets are held
back in the queue system.

This allows large frames to be squeezed through small time slots,
because HOLD requests initiated by the gate events result in the frame
being segmented in multiple fragments, the bit time of which is equal to
the size of the time slot.

It has been reported that the vsc9959_tas_guard_bands_update() logic
breaks this, because it doesn't take preemptible TCs into account, and
enables oversized frame dropping when the time slot doesn't allow a full
MTU to be sent, but it does allow 2*minFragSize to be sent (128B).
Packets larger than 128B are dropped instead of being sent in multiple
fragments.

Confusingly, the manual says:

| For guard band, SDU calculation of a traffic class of a port, if
| preemption is enabled (through 'QSYS::PREEMPTION_CFG.P_QUEUES') then
| QSYS::PREEMPTION_CFG.HOLD_ADVANCE is used, otherwise
| QSYS::QMAXSDU_CFG_*.QMAXSDU_* is used.

but this only refers to the static guard band durations, and the
QMAXSDU_CFG_* registers have dual purpose - the other being oversized
frame dropping, which takes place irrespective of whether frames are
preemptible or express.

So, to fix the problem, we need to call vsc9959_tas_guard_bands_update()
from ocelot_port_update_active_preemptible_tcs(), and modify the guard
band logic to consider a different (lower) oversize limit for
preemptible traffic classes.

Fixes: 403ffc2c34de ("net: mscc: ocelot: add support for preemptible traffic classes")
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Message-ID: <20230705104422.49025-4-vladimir.oltean@nxp.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Vladimir Oltean and committed by
Jakub Kicinski
c6efb4ae c6081914

+22 -6
+17 -4
drivers/net/dsa/ocelot/felix_vsc9959.c
··· 1209 1209 static void vsc9959_tas_guard_bands_update(struct ocelot *ocelot, int port) 1210 1210 { 1211 1211 struct ocelot_port *ocelot_port = ocelot->ports[port]; 1212 + struct ocelot_mm_state *mm = &ocelot->mm[port]; 1212 1213 struct tc_taprio_qopt_offload *taprio; 1213 1214 u64 min_gate_len[OCELOT_NUM_TC]; 1215 + u32 val, maxlen, add_frag_size; 1216 + u64 needed_min_frag_time_ps; 1214 1217 int speed, picos_per_byte; 1215 1218 u64 needed_bit_time_ps; 1216 - u32 val, maxlen; 1217 1219 u8 tas_speed; 1218 1220 int tc; 1219 1221 ··· 1255 1253 */ 1256 1254 needed_bit_time_ps = (u64)(maxlen + 24) * picos_per_byte; 1257 1255 1256 + /* Preemptible TCs don't need to pass a full MTU, the port will 1257 + * automatically emit a HOLD request when a preemptible TC gate closes 1258 + */ 1259 + val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port); 1260 + add_frag_size = QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_X(val); 1261 + needed_min_frag_time_ps = picos_per_byte * 1262 + (u64)(24 + 2 * ethtool_mm_frag_size_add_to_min(add_frag_size)); 1263 + 1258 1264 dev_dbg(ocelot->dev, 1259 - "port %d: max frame size %d needs %llu ps at speed %d\n", 1260 - port, maxlen, needed_bit_time_ps, speed); 1265 + "port %d: max frame size %d needs %llu ps, %llu ps for mPackets at speed %d\n", 1266 + port, maxlen, needed_bit_time_ps, needed_min_frag_time_ps, 1267 + speed); 1261 1268 1262 1269 vsc9959_tas_min_gate_lengths(taprio, min_gate_len); 1263 1270 ··· 1278 1267 remaining_gate_len_ps = 1279 1268 vsc9959_tas_remaining_gate_len_ps(min_gate_len[tc]); 1280 1269 1281 - if (remaining_gate_len_ps > needed_bit_time_ps) { 1270 + if ((mm->active_preemptible_tcs & BIT(tc)) ? 1271 + remaining_gate_len_ps > needed_min_frag_time_ps : 1272 + remaining_gate_len_ps > needed_bit_time_ps) { 1282 1273 /* Setting QMAXSDU_CFG to 0 disables oversized frame 1283 1274 * dropping. 1284 1275 */
+5 -2
drivers/net/ethernet/mscc/ocelot_mm.c
··· 67 67 val = mm->preemptible_tcs; 68 68 69 69 /* Cut through switching doesn't work for preemptible priorities, 70 - * so first make sure it is disabled. 70 + * so first make sure it is disabled. Also, changing the preemptible 71 + * TCs affects the oversized frame dropping logic, so that needs to be 72 + * re-triggered. And since tas_guard_bands_update() also implicitly 73 + * calls cut_through_fwd(), we don't need to explicitly call it. 71 74 */ 72 75 mm->active_preemptible_tcs = val; 73 - ocelot->ops->cut_through_fwd(ocelot); 76 + ocelot->ops->tas_guard_bands_update(ocelot, port); 74 77 75 78 dev_dbg(ocelot->dev, 76 79 "port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n",