The DMAC entries are written through set_egress_mac() and keyed on the
gateway's MAC alone. That is what an RTL930x L3_EGR_INTF_MAC entry
holds, but an RTL839x ROUTING row also names the switch MAC entry a
routed frame leaves with (SW_MAC_IDX), and routing with the MAC of the
device a route goes out of needs a row per gateway MAC and switch MAC
entry.
Give the entries a callback of their own, set_dmac(), which takes what
an entry holds, struct otto_l3_dmac: the gateway's MAC and the switch
MAC entry, which RTL930x does not take. RTL930x writes the entry
through otto_l3_930x_set_egress_mac() as before. RTL839x writes the
ROUTING row with SW_MAC_IDX from it, so set_egress_mac() is RTL930x's
alone now.
No functional change: every entry still holds a switch MAC entry of 0,
so the rows and the DMAC entries written, and which routes share them,
are as they were. Compile-tested on rtl839x and rtl930x; route-tested
on an HPE 1920-48G (RTL8393) together with the following patch, on
f29dfe25ba.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25822
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
A following patch calls otto_l3_gw_seen() from the handling of a
device's MAC change, which sits above it in l3.c. Move it there,
together with otto_l3_route_inside(), which it calls, so that no
forward declaration is needed. No functional change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25822
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
An AQR113C can report its copper link over MDIO before it writes the
new rate into the USXGMII code word, which carries 10G while the copper
link is down. phylink reads the speed only when the link comes up, so
the MAC stays at 10G. On an RT-AX89X with a 1G partner on the AQR113C
port this happened in 3 of 20 link-ups, and the port passed no traffic.
Report a rate change in the code word as a link drop, so that phylink
resolves the link again at the new rate. With this change all 20
link-ups ended at 1G; 9 came up at 10G first and were re-resolved
within a second.
Fixes: 05c180511d ("qualcommax: add UNIPHY PCS driver")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25829
Signed-off-by: Robert Marko <robimarko@gmail.com>
The burst of the port egress rate limiter is clamped to 64000 bytes.
The bucket of the limiter is 16 bits wide and has to hold the burst and
the frame that is sent. When the two no longer fit, the port is not
limited at all, so that clamp only works for frames of up to 1535
bytes.
On an RTL9303 with a limit of 100 Mbit/s, 400 Mbit/s offered and frames
of 9018 bytes, all 400 Mbit/s pass.
Clamp the burst to what leaves room for the largest frame the switch
forwards.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Quinn O'Shea <qoshea@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25831
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Build rt-loader with -O2 and tune the LZMA decoder's inlining decisions
to balance code size and decompression performance.
The existing -O3 setting applies to all loader C sources. In the
standalone decoder comparison used to investigate this change, -O3
substantially increased the generated code size. Simply switching to
-O2 reduced that footprint, but left the frequently used rc_get_bit()
helper out of line, adding function-call and stack-access overhead
throughout range decoding.
Adjust the two helpers independently:
- Declare rc_get_bit() inline to encourage integration into its callers.
This lets the compiler eliminate call overhead and optimise accesses
to the range-coder state, probability values and decoded symbols.
- Mark rc_do_normalize() noinline to retain a shared implementation of
the conditional normalisation path. This limits code duplication as
the surrounding bit-decoding helpers are inlined.
Use the inline keyword rather than always_inline for rc_get_bit().
With the tested compiler, forcing this helper to inline provided no
additional benefit over the simpler declaration.
A standalone comparison with GCC 13.2.0 targeting MIPS32r2/34Kc used
the same synthetic 1 MiB LZMA stream, producing 4 MiB of output. A
generic single-issue in-order model with a 32 KiB instruction cache
and a 16 KiB data cache produced the following results:
- Original -O2: 4,960 bytes of decoder .text; 548,160,936 model cycles.
- Modified -O2: 4,960 bytes of decoder .text; 389,069,748 model cycles.
- Original -O3: 10,496 bytes of decoder .text; 336,140,240 model cycles.
In that setup, the modified -O2 decoder retains approximately 75% of
the cycle reduction achieved by -O3 while using 52.7% less code.
It requires 15.7% more model cycles than -O3. Compared with unmodified
-O2, it reduces model cycles by 29.0% without increasing code size.
All compared variants produced byte-identical output.
These figures describe the standalone decoder experiment, not measured
Realtek hardware performance. The experiment used -fno-pic and
-mno-abicalls; rt-loader retains -fpic and -mabicalls. Actual loader
size and boot-time effects therefore require validation with the
target build and hardware.
The patch changes the optimisation level for the whole loader and
the inlining policy of these two decoder helpers. It leaves the
decompression algorithm, bitstream format and error-handling logic
unchanged.
Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25813
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The squashfs images of the TP-Link RE200 v1 and RE210 v1 differ between
builds; the uncompressed kernel of the RE200 one was 271 bytes larger,
from its device tree on:
-00001040: 6d00 0080 00cb 1a9e 0000 0000 0000 006f
+00001040: 6d00 0080 00da 1b9e 0000 0000 0000 006f
The RE210 device tree is exactly 271 bytes larger than the RE200 one, so
the RE200 image carried the kernel of the RE210.
tplink-v1-okli-image puts kernel and rootfs together in
$(IMAGE_ROOTFS).factory or .sysupgrade, a name shared by all devices with
the same rootfs. Both devices are built at the same time, so one can
overwrite the file of the other between cp, cat and mktplinkfw, and an
image can end up with the kernel and device tree of the other device.
Link: https://github.com/openwrt/openwrt/pull/25816
Signed-off-by: Paul Spooren <mail@aparcar.org>
On RTL839x a route through a gateway the switch holds no L2 entry for
gets one invented by otto_l2_nexthop_add(): static, with the don't-care
port. The gateway is then cut off from the switch, and what the route
should forward goes to the CPU instead. Measured on an HPE 1920-48G
(RTL8393), main f29dfe25ba, gateway G on a bridge VLAN behind lan48,
with a permanent neighbour and a MAC the switch had not seen, 8 echo
requests to a destination behind it, forwarding off:
- G's L2 entry was written static, port 63
- G got 0 of 3 replies from the switch; the 8 echo requests reached
the CPU, none reached G
- with the neighbour made dynamic the entry stayed as it was
- with the route removed G got 3 of 3 replies, and the switch learnt
it on port 47
Setting lan48's static move action to forward did not restore it, so
that alone does not explain it; the mechanism is not known.
RTL930x stopped inventing that entry in 138fabb79f ("realtek: l3: do
not invent an L2 entry for a next hop") wherever a route can trap, and
since #25805 an RTL839x route can. Require an entry the switch already
holds on every family: the route traps until the switch learns the
gateway from the traffic the CPU routes, and a later neighbour update
programs it. Nothing asks otto_l2_nexthop_add() to invent an entry any
more, so the flag and the invented entry go. A route trapped for want
of one is now logged on RTL839x as on RTL930x. On RTL839x an L2 entry
that is a next hop already is now pointed at its gateway's ROUTING row
as on RTL930x, one row per gateway MAC since c282dd1f4d.
The same case with this change: no entry written, the route trapped,
G got 3 of 3 replies and was learnt on port 47; the 8 echo requests
reached the CPU and, with forwarding on, G through the kernel, TTL 63.
With the neighbour made dynamic the route forwarded on the learnt
entry, 8 of 8 at G, none at the CPU. The cases from #25805 rerun (two
prefixes in both orders, neighbour loss): same results. Since that run
only the log line and comments changed.
As 138fabb79f says, a route through a permanent neighbour the switch
had not learnt stays with the CPU, since such a neighbour sees no
update: measured here too, it stayed trapped after G was learnt, until
the neighbour was made dynamic. Also as on RTL930x, a route that claims
an entry the switch learnt again after a flush counts itself on top of
the count the flushed entry left, so that entry can stay a next hop
once the route is gone; not tested.
RTL930x: no change, it already required such an entry. RTL838x and
RTL931x have no setup, so their FIB and neighbour events are ignored and
none of this runs there. Compile-tested on rtl930x.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25824
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The NBG7815 cools itself with a GPIO driven fan on TLMM 21. A second
tmp103 sits on the second I2C bus (BLSP QUP4), which the stock firmware
reads as the board temperature, and the stock firmware keeps GPIO 22
high to enable the second fan.
The switching points come from the stock firmware, which controls the
fan from a shell daemon /usr/sbin/fanctld, configured in
/etc/config/fanctld:
config fanctld config
option Enabled '1'
option Delay '20'
option Hits '3'
option TempTrig '108'
option TempClr '85'
fanctld polls every 20 s and starts the fan once any of thermal_zone0
(nss-top), wifi0 or wifi1 has been at or above TempTrig for three
consecutive polls. It stops the fan again only after all three have been
at or below TempClr for three polls:
if [ $temp1 -ge $temp_trig -o $temp2 -ge $temp_trig -o $temp3 -ge $temp_trig ]; then
echo 2
elif [ $temp1 -le $temp_clr -a $temp2 -le $temp_clr -a $temp3 -le $temp_clr ]; then
echo 1
The trip is placed at 85 degC with 5 degC hysteresis, so the fan runs
from 85 degC and stops again at 80 degC, rather than at the stock 108/85:
the SoC hardly reaches 108, so the case gets uncomfortably hot under load
before the fan would ever start, as reported for this device in
https://github.com/openwrt/openwrt/pull/14210
It sits on the CPU cluster rather than on the sensors the stock daemon
polls: the wifi sensors are only reachable through the vendor thermaltool
there, and one zone is enough to drive the fan.
Add the gpio-fan as a cooling device with that trip, enable the second
I2C bus with its tmp103, add the gpio-hog for the second fan enable and
describe the USB VBUS switch on GPIO 32 as a regulator rather than
hogging it.
That regulator carries enable-active-high. For regulator-fixed, gpiolib
ignores the flag in the GPIO specifier and takes the line as active low
without that property, so the regulator would drive GPIO 32 low at probe
- logged as "enforce active low on GPIO handle" - while the stock
firmware holds the line high.
The AQR PHY is deliberately left out of the cooling maps: its driver
rejects the low trip the thermal core installs by default, so a thermal
zone bound to it fails to set its trips.
Co-developed-by: Semih Baskan <strst.gs@gmail.com>
Signed-off-by: Semih Baskan <strst.gs@gmail.com>
Signed-off-by: André Valentin <avalentin@marcant.net>
The front panel of the NBG7815 carries nine RGB LEDs, driven by three TI
LP5569 controllers on the first I2C bus. The stock firmware keeps GPIO 60
high to enable the LED board.
The third controller sits at 0x40, where the stock firmware pokes two
registers directly instead of binding a driver, and the board schematics
call it an LP5009. It answers to the lp5569 register layout though: a
write to the pwm registers at 0x16 lights the LEDs, while the lp50xx
driver only ever got -EIO. Verified on the device by reading back the
registers and by the LEDs actually turning on.
Placement and colours were measured with a camera pointed at the front bar,
one frame per lit LED against a dark frame. LED1 to LED6 walk through the bar
monotonically, so the two controllers form one continuous chain: 0x32
ascending, 0x35 descending, which is also the channel order the stock
zled_ctrl script uses. Red, green and blue each come out as the dominant
channel on all three controllers.
The three groups of the controller at 0x40 light the same spot near the
middle of the bar instead, each with about twice the output of a bar segment,
so they feed one brighter element together rather than three segments of
their own.
Channel numbers are per controller. Getting that wrong is quiet but
destructive: lp55xx_multicolor_brightness() adds the number to the pwm
register base without checking it against the channel count, so the
writes land in the current registers of other channels instead.
Add the controller nodes with the channel mapping and the LED currents
of the stock firmware, the gpio-hog for the LED board enable, and set
LED7 to white through board.d. That is the brighter element in the middle
rather than one end of the bar, and it is the one the stock firmware keeps
driving in every state through its two register writes to 0x40.
The same LED is wired to the led-running and led-upgrade aliases, so
diag.sh turns it on once the system is up and blinks it while a sysupgrade
runs. Measured with a camera at 30 fps: 36 cycles of 200 ms on and 200 ms
off for the duration of the flash.
led-boot and led-failsafe are deliberately absent. Both states are set
from preinit, before the i2c driver for the controller is loaded, so the
LED does not exist yet and led_set_attr() silently skips it - confirmed by
recording a boot, where the LED stays dark and then comes on in one step.
The aliases need a "label" on the multi-led node: get_dt_led() looks for
label, then chan-name, and otherwise composes a name from color and
function, which would be "rgb:" here. The lp55xx driver only reads "label"
from the controller node, so it does not affect the class device name.
Signed-off-by: André Valentin <avalentin@marcant.net>
Rename struct rtldsa_vlan_info to struct otto_vlan_info and update
all references to match the otto_vlan_ namespace.
No functional changes intended.
Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25817
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The statistics functions already use the otto_stats_ prefix, while
the associated structure types and variables still use rtldsa_.
Rename the counter and MIB structure types, per-SoC MIB lists and
descriptors, and polling interval macros to match the module's
namespace. Update all declarations and references in the shared
header and the four SoC configurations.
No functional changes intended.
Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25817
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
On RTL839x a route forwards through its gateway's L2 entry. A gateway
that sends nothing lets that entry age out, and a packet routed
through it then hits the routing entry age-out exception, whose action
is forward after reset: the packet is routed and also reaches the CPU,
which routes it a second time when IPv4 forwarding is on.
Trap it instead (ROUTING_AGE, bits 15:14 of ROUTING_EXCPT_CTRL: 0 drop,
1 forward, 2 trap; Realtek GPL SDK, dal_cypress_trap.c, exception
ROUTE_EXCEPTION_TYPE_ENTRY_AGE_OUT), as the TTL exception already does.
The CPU routes such packets alone until the gateway sends again and
its entry is learnt anew.
Measured on an HPE 1920-48G (RTL8393), 10.99.0.0/16 via a gateway
that sends nothing, 8 echo requests per case, once the age of its L2
entry had run down to 0 (about 4.5 minutes on the bench):
- without this change: forwarding off, 8/8 routed and 8/8 at the CPU
as well; forwarding on, 14 at the gateway, 8 from the switch and 6
from the kernel
- with it: forwarding off, none routed, 8/8 at the CPU; forwarding
on, 8 at the gateway, none of them twice
With a fresh entry, 8/8 were routed in hardware and none reached the
CPU, without and with this change. Both images also carried the
pending switch MAC change for #25768 items 3 and 4 and bench debugfs
files.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25821
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The RTL839x route rules match the destination in the template's DIP
fields, and the trap rules the switch MAC too, but never the frame
type. An IPv6 frame fills those fields too, so an
IPv6 frame to the switch whose destination ends in the 32 bits of an
offloaded IPv4 prefix takes that prefix's rule and is routed in
hardware, as IPv6, to the IPv4 route's gateway. IPv4-embedded IPv6
addresses under a /96 prefix (RFC 6052), such as the NAT64 prefix
64:ff9b::/96, end that way.
Match FRAME_TYPE IPv4 (2, USER_FIELD_FRAME_TYPE in the Realtek GPL
SDK), which the IACL entry keeps at bits 337:336 with its mask at
121:120, where the PIE writer already puts frame_type.
Measured on an HPE 1920-48G (RTL8393), 10.99.0.0/16 offloaded via
gateway G, forwarding off, 8 IPv6 echo requests to the switch for
2001:db8:5::a63:601 to ...:608, whose low 32 bits are 10.99.6.1 to .8:
- without this change: 8/8 routed to G (hop limit 63), the rule's
packet counter +8
- with this change: none at G, 8/8 at the CPU, counter +0
Destinations with 10.99.6.x in their high 32 bits were not routed on
either. IPv4 routing through the rules was unchanged (prefix order,
neighbour loss and back, a gateway shared by two routes). Both images
were main c282dd1f4d with bench debugfs files and the switch MAC change
for items 3 and 4 of #25768, not posted yet (a later revision of it on
the second, differing in how routes follow a MAC change).
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25818
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The QCA8337 is behind PPE port 1, so with the plain QCA tag the EDMA
cannot offload the checksum or TSO of frames for lan3-lan8. Select the
qca-etype tag on its CPU port.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25754
Signed-off-by: Robert Marko <robimarko@gmail.com>
The QCA8K header sits in place of the ethertype, so the IPQ807x EDMA,
which finds the IP header by parsing past the Ethernet header and VLAN
tags, cannot offload the TX checksum or TSO of a frame for a QCA8337
behind a PPE port, and TCP and UDP leave those ports with bad
checksums. Add a "qca-etype" tagger that puts the header behind the
802.1ad ethertype, which the EDMA parses as a VLAN tag, and let qca8k
select it and program HEADER_CTRL to match.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25754
Signed-off-by: Robert Marko <robimarko@gmail.com>
phylink sets the PHY EEE advertisement from lpi_capabilities and does not
check lpi_interfaces, so the PHY of a USXGMII or 10GBASE-R port
advertises EEE although the MAC never enables LPI there. The XPCS behind
those modes reports no EEE ability (SR_XS_PCS_EEE_ABL reads 0 on IPQ8074),
and qca-ssdk does not enable MAC EEE on them for that reason. Offer EEE
only on a port whose phy-mode has MAC LPI, and add 2500BASE-X at
2.5 Gbit/s on ports 5 and 6, which qca-ssdk treats as EEE capable.
Fixes: dd052b4594 ("qualcommax: qca_ppe: add EEE support")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25754
Signed-off-by: Robert Marko <robimarko@gmail.com>
On RTL839x a gateway's L2 entry names the ROUTING row that holds the
gateway's MAC, and that row was the id of the route that claimed the
entry. The other routes through that gateway go on using the row after
the route is gone, and its id can go to a later route, which writes
its own gateway's MAC into it. Those routes then forward to the wrong
gateway.
Give RTL839x the DMAC entries RTL930x has: one ROUTING row per gateway
MAC, held by every route through it (otto_l3_dmac_get()) and written
when it is first taken. The bookkeeping takes 24 KiB, allocated at
probe as on RTL930x.
Measured on an HPE 1920-48G (RTL8393) with #25805 on main 17a3a87f6e,
8 echo requests per case, forwarding off: routes A and B via gateway
G, A removed, then route C via gateway H.
- without this change, C took A's row and B's traffic arrived 8/8 at
H, sent to H's MAC
- with it, C got a row of its own and B's traffic stayed 8/8 at G
Of the #25805 bench, prefix order in both orders, blackhole, neighbour
loss and back, and 22 routes in random order gave the same results as
there.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25808
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
On RTL839x every offloaded route is a PIE rule that matches the
destination prefix, and of the rules that match, the lowest index
acts. rtl839x_pie_rule_add() hands out the first free index, so a /16
added before a /24 inside it takes the /24's traffic. Nothing traps
either, so the switch forwards what the kernel would not: a blackhole
inside an offloaded prefix, a local prefix, routes under policy rules,
a gateway whose neighbour is gone (#25768, items 5 to 9).
Handle the rules the way the RTL930x prefix table is handled. The
rules pie.c keeps for L3 are the prefix rows: a route takes one when
it is added, longest prefix first, the local table ahead of main. The
ROUTING row with the gateway MAC gets a writer of its own. A move
writes the routes again at their new rows, since a rule holds nothing
its route does not. A route the switch must not forward redirects to
the CPU port, and only frames addressed to one of the switch's MACs
(SWITCHMAC), so a frame for another station is not taken.
The traps for routes left out, local prefixes, policy rules and a lost
neighbour now run on RTL839x too. Policy rules are checked for IPv4
only, as IPv6 is not offloaded there; the check replaces the "FIB4
default rule failed" message. A gateway whose MAC was not learnt on a
port still forwards, as before, and a lost event is still not
replayed. The debugfs route dump walks the L3 rules and shows the
trapping ones.
Measured on an HPE 1920-48G (RTL8393), RAM-booted from main
17a3a87f6e plus this series, 8 echo requests per case, the client and
both gateways behind one port in VLANs 7 and 3:
- /16 via A and /24 via B, added in both orders, forwarding off: /24
traffic 8/8 at B, the rest 8/8 at A, TTL 63, none at the CPU
- blackhole /24 inside the /16: none forwarded, 8 at the CPU
- a rule sending a /24 to table 100: offload suspended, the kernel
sends 8/8 to that table's gateway; back in hardware once it goes
- local 10.99.0.0/16 in table local: 8/8 answered by the switch
- gateway neighbour failed: 3/3 Destination Host Unreachable, back in
hardware once the gateway answers again
- 22 routes added in random order, half removed, then the rest: rows
dense and longest prefix first after each phase
The network (re)starts suspended the offload for about 10 ms each, as
the policy rules changed.
RTL930x: no change. Every changed check tests a flag or callback that
RTL930x sets, and the new row bound in otto_l3_route_place() lies
past the IPv6 catch-all row, which stops the IPv4 rows first. RTL838x
and RTL931x have no setup, so their FIB events are still ignored.
Compile-tested on rtl930x.
Not tested: a gateway MAC change, routes past rule 127 (the order
across blocks was checked by hand, #25768).
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25805
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
L3 is to keep its RTL839x routes longest prefix first, the way the
RTL930x prefix table holds them, so it needs rules that nothing else
hands out and that it writes in place. Mark blocks 0-3 as used, so
rtl839x_pie_rule_add() never returns one of them, and add a writer
for them that takes the template of the block holding the IPv4
destination.
Until L3 uses the writer, its rules come from rtl839x_pie_rule_add()
from block 4 on, where template 2 holds the destination too. Nothing
else adds RTL839x rules: tc offload is wired for RTL93xx only.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25805
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
otto_l2_nexthop_add() fails when the gateway finds no free L2 slot, or,
where the next hop has to exist already (RTL930x), when the gateway's
address has not been learnt. The route then still forwards, through
the L2 index it held before, or index 0 on its first update.
Let it trap instead. RTL930x behaves as before: the call fails before
it writes nh->port, which stays port_ignore, so no_port already traps
the route. On RTL839x the PIE rule forwards whatever the route says
until the rules can trap, so nothing changes there either.
Not tested on hardware: no failed next hop write was provoked.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25805
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Move hardware statistics helpers and packet counter allocation, read
and clear functions into stats.c.
Standardize function names on the otto_stats_ prefix, followed by the
chip identifier where applicable, and remove redundant stats suffixes.
Update declarations, includes, callers and operation tables accordingly.
No functional changes intended.
Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25802
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Move the hash index, hash/CAM lookup, unicast reference lookup and
ageing-time helpers into l2.c. This removes the dependency of L2 code
on lookup helpers implemented in dsa.c.
Rename the moved functions with the otto_l2_ prefix, place their
public declarations in l2.h and update includes and callers.
Leave DSA callbacks and port/bridge handling in place.
No functional changes intended.
Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25802
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
LAG handling is split between dsa.c and lag.c. Consolidate the remaining
LAG callbacks and helpers in lag.c to keep related functionality together.
Rename the moved functions with the otto_lag_ prefix and declare the
cross-file interfaces in lag.h. Update callers, DSA operation tables and
RTL93xx configuration references, and add the required includes.
Leave the port management functions in place for a later refactoring.
No functional changes intended.
Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25802
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Add the Time Aware Priority (IEEE 802.1Qbv) queuing discipline, used
for Time-Sensitive Networking.
The Hellcreek switch driver depends on taprio, so disable it to avoid
a new config prompt.
Link: https://github.com/openwrt/openwrt/pull/25777
Signed-off-by: Nick Hainke <vincent@systemli.org>
The Felix switch of the LS1028A is built in and depends on taprio
being built in or disabled. If taprio is built as a module, e.g. by
the upcoming kmod-sched-taprio, Felix can only be built as a module as
well and the switch is missing in the image.
Build taprio into the kernel, so that the switch stays built in and
Felix and ENETC can offload taprio, cbs and the gate action.
Link: https://github.com/openwrt/openwrt/pull/25777
Signed-off-by: Nick Hainke <vincent@systemli.org>
The vendor BL2 of the Wavlink WL-WNT100X3 initializes DDR3 at
1866 MT/s:
NOTICE: BL2: v2.7(release):292bca1-dirty
NOTICE: DDR3 1866MHZ
NOTICE: EMI: Using DDR3 settings
while the plain spim-nand-ddr3 BL2 used by the ubootmod variant runs
DDR3 at the default 2133 MT/s, which is above what the vendor
qualified on this board.
Switch the ubootmod BL2 to the existing DDR3 1866 MT/s build, in line
with the vendor firmware.
Tested on WL-WNT100X3: BL2 reports the ddr3-1866 build, the memory
test passes, U-Boot and OpenWrt boot from UBI.
Fixes: d2fabb974c ("mediatek: add support for Wavlink WL-WNT100X3 ubootmod")
Signed-off-by: Fil Dunsky <filipp.dunsky@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25763
Signed-off-by: Robert Marko <robimarko@gmail.com>
qca8k was built into the kernel so the MX65's QCA8337 switches work in
failsafe. However, the QCA8337 PHY driver was not, so the internal PHYs
bound to genphy and missed the chip-specific setup in
qca83xx_config_init(), such as disabling EEE.
Switch to kmod-dsa-qca8k for the MX65 instead, which now pulls in
kmod-phy-qca83xx. Both are loaded in preinit, and the PHY driver loads
first, so failsafe keeps working and the PHYs get the right driver.
This also removes qca8k from the kernel of every other bcm53xx device.
Attach an extra patch sent upstream to use the internal MDIO, which no
longer supports generic PHY.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Rosen Penev <rosenp@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25767
Signed-off-by: Robert Marko <robimarko@gmail.com>
The internal PHYs of the QCA8327/8334/8337 switches are handled by the
qca83xx PHY driver. Without it they silently fall back to genphy and
lose the chip-specific setup and fixups. Every device using
kmod-dsa-qca8k already pulls it in manually except the MikroTik
RB960PGS, which was missing it.
Add the dependency, like other DSA packages do with their PHY drivers,
and drop the now-redundant device package entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Rosen Penev <rosenp@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25767
Signed-off-by: Robert Marko <robimarko@gmail.com>
All of these AR8327s are claimed through the PHY ID by
ar8xxx_phy_probe(), which needs a plain ethernet-phy@0 node carrying
qca,ar8327-initvals, while a CPU port has to hang off a phandle to
that PHY. The switch nodes already in the tree take the other path:
ar8xxx_mdiodev_probe() claims them by compatible and the CPU port
runs off a fixed-link. Both end up with the same single swconfig
device and the same initvals.
Switch the remaining boards over to that second form, i.e. an
ethernet-switch@10 with compatible = "qca,ar8327" and reg = <0x10>.
0x10 is the first register of the AR8327 window, 0x18 being the page
select register, so that is the address to probe at. Where the phy-mode
only lived on the PHY node, move it to the MAC that took over the
link.
For zyxel,nbg6x16, wd,mynet-n750, and comfast,cf-wr650ac, fix the address
from 1f to 10. The former is for built-in 100Mbps switches. The mdio1
stuff is also irrelevant. Both ethernet interfaces are part of the switch.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25467
Signed-off-by: Robert Marko <robimarko@gmail.com>
These external AR8236s were copied from the builtin-switch pattern
(switch0@1f). QCA9558 has no builtin switch; AR8236 register access
starts at MDIO 0x10.
Use ethernet-switch@10 with compatible = "qca,ar8236" and reg = <0x10>,
matching the other external QCA switches.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25467
Signed-off-by: Robert Marko <robimarko@gmail.com>
DSA numbers LAGs from 1 to num_lag_ids. The driver uses the id as it is
to index its LAG arrays and as the hardware trunk group, both of which
start at 0. Group 0 is therefore never used and the last id is refused,
so one LAG fewer than the switch supports can be offloaded.
Accept ids from 1 to num_lag_ids and subtract one where the id becomes
a group.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Quinn O'Shea <qoshea@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25792
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
port_lag_leave returns 0 whatever happened, and it clears the port from
the driver's LAG masks before it asks the hardware to release it. When
the release fails, DSA is told that it worked and the driver no longer
counts the port as a LAG member while the hardware still does.
Release the port in hardware first, update the masks only when that
succeeded, and return the error, which DSA then logs.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Quinn O'Shea <qoshea@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25792
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The first member of a LAG becomes its primary port when lag_primary[]
holds -1, but the array comes zero-filled from devm_kzalloc() and is
never set to -1. Every member is therefore marked non-primary, and the
FDB and MDB code of the RTL83xx skips non-primary ports.
The leave path is wrong as well: it elects a new primary whenever any
member leaves, uses fls64(), which is one-based, as a port number, and
keeps the old primary when the last member leaves, so the next LAG with
that id starts without one.
Set every slot to -1 at probe, record the primary only after the
hardware join succeeded, and on leave elect a new primary only when the
primary itself leaves: the lowest remaining member, or -1 when the LAG
is empty.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Quinn O'Shea <qoshea@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25792
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
port_lag_leave looks the LAG up with dsa_lag_id(). DSA drops its
reference on the LAG before it notifies the switch, so when the last
port leaves, the LAG is already unmapped and the lookup returns -ENODEV.
Without this change, the leave is rejected as "LAG -19 invalid" and the
port stays a member of the hardware trunk. Addresses learned on it
afterwards are stored as trunk entries and frames for them are no
longer delivered, and the port cannot join a LAG again.
DSA passes the id in the notifier for exactly this reason. Use it in
port_lag_leave, and in port_lag_join for symmetry, and drop the check
for a return value dsa_lag_id() never had.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Quinn O'Shea <qoshea@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25792
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
TC functions currently use a mix of rtl83xx_ and rtldsa_ prefixes.
Use otto_tc_ for generic functions and otto_tc_930x_ or otto_tc_931x_
for device-specific functions to make TC module ownership explicit.
Remove redundant tc components from the remaining function names.
Update all declarations, call sites, callback assignments and
function references in comments accordingly.
Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25790
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
There is a similar race condition in rtl8214fc_media_set() as in
rtl8214fc_power_set(). It changes the base PHY's page registers in
separate MDIO bus lock sections, so another port can see the internal
page in between and restore it.
This fix holds the MDIO bus lock for the whole page-select sequence in
rtl8214fc_media_set().
Signed-off-by: Harm Kroon <hhhkroon@hotmail.com>
Link: https://github.com/openwrt/openwrt/pull/25766
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
There is a race condition in rtl8214fc_power_set(). It reads the
extended page register (reg 30) with __phy_read() without the MDIO bus
lock. Port 0 of the RTL8214FC is the base PHY. The other ports
poll __rtl8214fc_media_is_fibre(), which sets the base PHY's reg 30 to 8
(INTERNAL) for a short time. If port 0 reads in that window, it
"restores" page 8 and stays there. Its standard registers then point to
the internal page, so no link is seen. Every later power cycle reads 8
and writes 8 again.
This fix holds the MDIO bus lock for the whole page-select sequence in
rtl8214fc_power_set().
Signed-off-by: Harm Kroon <hhhkroon@hotmail.com>
Link: https://github.com/openwrt/openwrt/pull/25766
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
A PIE rule on RTL839x counts the packets it matches into the counter
named by its LOG_IDX field, bits 80:70 of the entry (Realtek GPL SDK,
rtk_cypress_tableField_list.c), which are bits 16:6 of r[14]. The
writer puts the index at bits 14:4, so the hardware reads it divided
by four, and bit 0 of the index lands in MIR_IDX.
The reader shifts the same way, so the route dump in debugfs reads the
counter the driver allocated rather than the one the hardware fills.
Write and read the index at bit 6.
Tested on an HPE 1920-48G (RTL8393) with two offloaded routes, which
got packet counters 0 and 1, and 8 echo requests through one route at
a time. Before, the requests through the second route added 8 to the
first route's counter and nothing to its own. After, each route's
counter grew by 8 for its own requests and stayed put for the other's.
Counters above 1 were not exercised.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25795
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
What an RTL839x does with a routed IPv4 packet whose TTL runs out is
set by the IP4_TTL_EXECEED field of ROUTING_EXCPT_CTRL, and nothing in
the driver writes that register. On main, #25768 found that an echo
request with TTL 1 through an offloaded route got no Time Exceeded
from the switch, and nothing reached the gateway either.
Set the field to trap, so that the CPU gets the packet and the kernel
answers it as it would on a route it forwards itself. The encoding,
0 drop, 1 forward, 2 trap, is the one of the Realtek GPL SDK
(dal_cypress_trap.c). The other fields of the register keep their
value.
Tested on an HPE 1920-48G (RTL8393), with 10.99.1.0/24 offloaded
through a gateway behind the client's own port and 8 echo requests
with TTL 1 per case, in one boot. With the field at 2, the client got
7 Time Exceeded replies, consistent with the kernel's ICMP rate limit,
and none of the requests reached the gateway. With the field forced
to 0 through devmem, the client got no reply and nothing reached the
gateway, the symptom #25768 reported on main. Echo requests with the
default TTL still reached the gateway 8/8, TTL 63, with IPv4
forwarding off.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25794
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
rtldsa_port_fdb_dump() calls cond_resched() every 64 rows, but at the
end of the loop, after the "continue" that skips each row that is not
a valid unicast entry. So it only reschedules when such a 64th row
happens to hold a valid unicast entry.
DSA calls the function once per port. On the 52-port HPE 1920-48G
(RTL8393) a full "bridge fdb show" walks the 16384 rows 52 times:
96 ms per port, 5.0 s in all. With PREEMPT_NONE no other task runs
on that CPU meanwhile. When the kernel's watchdog worker, which
toggles the board's external PT7A7514, waits on that CPU, it misses
its pings and the board resets during the dump, without any console
output.
Move cond_resched() to the top of the loop, as the L3 debugfs dumps
already do, so it runs every 64 rows whatever the row holds.
Tested on the HPE 1920-48G under ping load, with full dumps. Without
this change and the dump pinned to CPU0, 20 of 20 dumps completed
with the watchdog worker on CPU1; moved to CPU0 in the same boot, the
board reset on the first dump. With this change, 20 of 20 completed
with both on CPU0, 20 of 20 with CPU1 kept busy and 20 of 20 with
nothing pinned. Across those 60 dumps no two toggles of the watchdog
GPIO were more than 0.51 s apart. Not tested on other families.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25787
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The port counters hold the multicast and broadcast frame counts, and
the frame totals are already built from them, but the fields that
report them on their own are never assigned. ethtool leaves the
multicast and broadcast members of the standard MAC statistics out, and
the multicast member of the link statistics reads zero.
Assign them from the counters that are already collected.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Quinn O'Shea <qoshea@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25756
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
On RTL839x the ethernet driver turns on the L2 notification engine and
passes its learn and age events to the bridge. It reports them against
the conduit, which is not a bridge port, so br_switchdev_event() drops
every one of them. Nothing else reads them. RTL838x, RTL930x and
RTL931x run without this code.
Drop the notification setup, its interrupt handling and ring, and the
NIC reset code that preserved them. The vendor SDK defines no
interrupt in the mask bits this code also set on the other families:
bits 20-22 of DMA_IF_INTR_MSK on RTL838x, bits 4-6 of
DMA_IF_INTR_TX_DONE_MSK on RTL930x and RTL931x.
Tested on an HPE 1920-48G (RTL8393):
- after boot, L2_NOTIFICATION_CTRL reads 0x00001002 (engine off) and
DMA_IF_INTR_MSK has none of bits 20-22 set;
- the switch still learns the hosts behind its ports;
- a route offloaded with IPv4 forwarding off still delivers 8 of 8
echoes.
This does not stop the resets on a full bridge fdb show on that switch:
they happen with or without this code.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25776
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
An RTL839x has no L3 tables to dump. It routes through PIE rules that
match the destination prefix and forward to the L2 entry of the
gateway, and that entry names the ROUTING table row holding the gateway
MAC. Add realtek_otto_l3/routes for it: it walks the PIE rules and
shows each routing one with its L2 entry, its ROUTING row and its
packet counter.
The setup of the debugfs directory moves into a helper that both
families share.
Tested on an HPE 1920-48G (RTL8393) with 10.99.0.0/16 via one host
and 10.99.5.0/24 via another:
- both rows show the gateway's MAC in the L2 entry and in the ROUTING
row, on the port that host is behind;
- 8 echoes into each prefix raise the counter of the rule that matched
by 8;
- the counters show the /16 rule, installed first, matching 10.99.5.x
as well, and captures saw all 8 of those echoes at the /16 gateway;
- after deleting the /24 route, only the /16 row is left.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25775
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
rtl839x_pie_rule_read() does not read back what rtl839x_pie_rule_write()
writes:
- it takes the forward action, and the drop, from r[14], while the
writer puts them in the low bits of r[13];
- it never reads the forward and log data;
- it reads the masks of the templated fields word aligned, while the
writer puts them 8 bits up;
- it puts the two halves of the destination address in the wrong
order and builds the mask from the address.
Read all of these back from where the writer puts them. The hardware
follows the writer: the routes it programs on an HPE 1920-48G (RTL8393)
forward to their next hop.
Nothing reads a PIE rule back on RTL839x yet, so this changes no
behaviour. The RTL839x L3 debugfs added next uses it.
Checked on that switch through the debugfs dump of the next commit:
- the rules of 10.99.0.0/16 and 10.99.5.0/24 read back as these
prefixes, with the L2 index of their gateway;
- their raw words hold forward action 6 in the low bits of r[13];
- their masks sit 8 bits up, as written.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25775
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The ports list only full duplex in their MAC capabilities, so the PHY
advertises no half-duplex mode. A link partner with autonegotiation off
is found by parallel detection, which resolves to half duplex, and the
port does not link.
Add MAC_10HD and MAC_100HD to ports 1-6. The GMAC programs half duplex;
USXGMII runs on the XGMAC, which has no duplex setting, so
mac_get_caps() removes both modes for that interface.
On a Xiaomi AX3600 (QCA8075) with the partner forced to 10 or 100 Mbps,
the port stays down before the change and links at half duplex after
it, 3/3 runs each.
Closes: https://github.com/openwrt/openwrt/issues/25657
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25750
Signed-off-by: Robert Marko <robimarko@gmail.com>
On the PSGMII instance (UNIPHY0 on IPQ8074 and IPQ6018), SGMII and
1000BASE-X run with every mode select bit of UNIPHY_MODE_CTRL clear, as
qca-ssdk's __adpt_hppe_uniphy_sgmii_mode_set() programs it. The driver
sets UNIPHY_SGMII_MODE there instead, and the Zyxel NWA110AX, whose
AR8033 sits on UNIPHY0 channel 0, transmits nothing.
Leave the mode select clear on the instance with a clock pair per
channel, and report 125 MHz for that setting in the clock recalc.
Writing 0x20 or 0x21 to the register on the NWA110AX restores TX.
Closes: https://github.com/openwrt/openwrt/issues/25657
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25750
Signed-off-by: Robert Marko <robimarko@gmail.com>
The RTL839x applies a port's isolation mask to every frame that came in
on it, routed ones included. The driver leaves the port out of its own
mask, so a route offloaded through a gateway behind the port a packet
came in on, such as routing between two VLANs of one trunk, drops
everything: the kernel shows the route as offloaded, and the drop
counter PORT_ISOLATION counts each packet.
Keep the port in its own mask, as the GPL SDK does at init
(_dal_cypress_port_init_config() sets every port's mask to all ports).
A broadcast is still not sent back out of the port it came in on. Only
RTL839x changes; the other families keep their masks as they are.
Tested on an HPE 1920-48G (RTL8393), with 10.99.1.0/24 routed from VLAN
7 to a gateway on VLAN 3, both behind lan48. On main, 0/8 echo requests
reached the gateway, even with IPv4 forwarding on in the kernel. With
this change and forwarding off, so that only the hardware path delivers,
8/8 arrived, TTL 63, from the switch MAC. Routing to a gateway behind
lan47 still delivers 8/8. Two bursts of 50 broadcasts entering lan48
added nothing to its tx_BroadcastPkts, while the same entering lan47
added 50 and 53.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25764
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
otto_l3_839x_setup_port_macs() fills the RTL839x routing source MAC
table with the switch MAC plus one, plus two and so on, a leftover
marked "BUG: VRRP for testing". A route only ever names entry 0, since
switch_mac_id is never set, so every frame the switch routes in
hardware leaves with the switch MAC plus one, a MAC that no interface
of the switch has.
Write the switch MAC as it is. That is what the GPL SDK puts in entry 0
when it sets the switch MAC (dal_cypress_switch_mgmtMacAddr_set(),
"Patch: Set router mac entry 0").
Tested on an HPE 1920-48G (RTL8393), with 10.99.1.0/24 routed between
VLAN 7 on lan48 and a gateway on VLAN 3 on lan47, and IPv4 forwarding
off in the kernel so that only the hardware path delivers. On main, 8/8
echo requests reached the gateway with the switch MAC plus one in the
last byte as their source. With this change, 8/8 arrived with the switch
MAC.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Gennaro Cimmino <gcimmino@rayonra.net>
Link: https://github.com/openwrt/openwrt/pull/25759
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
The Lianguo LG-SWTGW3C8F is an eight-port SFP+ managed switch based on
the Realtek RTL9303. It is the same vendor board as the Horaco
ZX-SWTGW2C8F and the GoodTop GT-SWTXG8FM (boardmodel RTL9303_8XGE in
the U-Boot environment) and uses the Horaco device tree unchanged.
Device specification:
- SoC: Realtek RTL9303
- RAM: 128 MiB DDR3, Winbond W631GG6KB-12
- Flash: 16 MiB SPI NOR, Winbond W25Q128
- Ethernet: 8x SFP+ cages
- Button: reset, GPIO 5 active-low
- System LED: GPIO 0
- Console: RJ45 RS-232 port, 115200 8N1
- Power input: 12 V DC
- Bootloader: Realtek U-Boot 2011.12 (1.0.0.2)
- Vendor firmware: KT-NOS 1.1.1.30
- Vendor board model: RTL9303_8XGE
The flash layout is the Horaco's. The MAC address is read from ethaddr
in BDINFO through NVMEM. The vendor firmware and bootloader use the
uImage magic 0x83800000, like the Horaco.
Installation from the vendor web interface:
1. Open Management -> Firmware -> Manual Upgrade.
2. Select the action Upgrade and the method HTTP.
3. Upload the squashfs-factory.bix image.
4. Wait for "Upgrade Image Done", then reboot the switch.
Installation over the serial console:
1. Put the initramfs-kernel.bin image on a TFTP server that is reached
through one of the SFP+ cages. The cages are switch ports 0, 8, 16,
20, 24, 25, 26 and 27; the commands below use the eighth one.
2. Stop the bootloader with ESC at the autoboot prompt and enter:
rtk network on
rtk ext-devInit 0
rtk 10g 27 fiber10g
setenv ipaddr <address of the switch>
setenv serverip <address of the TFTP server>
tftpboot 0x84f00000 <name of the initramfs image>
bootm 0x84f00000
3. Write the squashfs-sysupgrade.bin image with sysupgrade from the
running system.
Return to the vendor firmware: with a backup of the 12 MiB firmware
partition taken before OpenWrt was installed, copy it to
/tmp/RUNTIME.bin under OpenWrt and write it back:
mtd -f write /tmp/RUNTIME.bin firmware
mtd verify /tmp/RUNTIME.bin firmware
Tested on hardware: both installation routes, sysupgrade, the return
to the vendor firmware, the MAC address, a 10G link over a DAC in each
of the eight cages and a 1G link over fibre, each with its port LED
(blue at 10G, green at 1G), the system LED and the reset button. With
an SR module and no fibre the port reports loss of signal, and the
laser goes off when the port is set down. A 2.5G link over fibre came
up as well, with a local SFP quirk for the module that is not part of
this change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Quinn O'Shea <qoshea@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25715
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>