258 Commits
Author SHA1 Message Date
logicog c263a8ab64 Add assmebler injection script 2026-10-15 06:53:23 +02:00
logicog c02f660c08 doc: Add HOWTO for using firmware tracing to figure LED configuration
This adds a howto for figuring out LED configuration via tracing
of functions in the originam firmware image. The method also provides
information on PHY and SDS configuration, including PHY patches.
2026-10-11 22:31:33 +02:00
René van Dorst 4e00f5a592 Merge pull request #398 from mintsoft/lldpd
LLDP implementation
2026-10-10 09:17:00 +00:00
Rob Emery eff3f42ae1 Rewriting sim implementation to be data driven
The value for enabled should be an int, not a string
2026-10-10 09:51:50 +01:00
Rob Emery dda873a633 Moving lldp cmd parsing to be above the is_sfp check to take precedence
Needed to increase buffer size for this to send app.js

The logical port status is what is returned in the array, for
non-sequential systems we need to map through physToLog rather
than just subtracting 1

physToLog is 1 based! not 0; also fixing up typecasts

Reimplementing using an array of objects to represent ports.
The previous approach of a bitmask of logical ports, was just too
simple on devices such as the SWGT024_V2_0_MANAGED which has
gaps in the port assignments.
2026-10-10 09:51:33 +01:00
Rob Emery 9b2bd7f1b8 The tick rate has been changed now so we'll have to approximate
timing with ticks

Fixing up the log_to_phys_port assignment

2000 is about every 10 seconds, so scaling up to once every 30ish

Removing the attempt to block lldp frames,
This does not work however the hard work has been done for us in
the #514 so we can borrow that work later on
2026-10-10 09:50:38 +01:00
Rob Emery 2dfe531fe0 Fixing missing parts to make the tests pass
boot_to_html outputs 1 and 0 not true and false, making the sim match
the real implementation.
lldp.json has a hanging , in the array; so overwriting it
2026-10-10 09:50:27 +01:00
Rob Emery 72c2625121 Icon from https://github.com/logicog/RTLPlayground/pull/429#issuecomment-5687458129 2026-10-10 09:50:27 +01:00
Rob Emery 58fd1a3ccc Adding lldp.json to the simulator
Moving the global on/off onto the LLDP page

Building a table of LLDP status from the S ports. Later on, if
we wish to implement an lldp receiver we can display the remote
details here

Adding support to the cmd parser for the lldp commands
2026-10-10 09:50:01 +01:00
Rob Emery 4f1a2f04d8 Allowing commands like port 1 lldp block and port 2 lldp permit
so we are able to enable/disable lldp transmission on a specific
port.
2026-10-10 09:50:01 +01:00
Rob Emery cedc795b82 Adding UI options to enable/disable LLDP 2026-10-10 09:50:01 +01:00
Rob Emery ccf541a06b Refactor and golfing from https://github.com/logicog/RTLPlayground/pull/398#issuecomment-5654087271
I've tweaked it a little to keep the port_position in as I think
that is clearer than a random "12" in the loop later on
2026-10-10 09:50:01 +01:00
Rob Emery d5124df89e avoid SRAM 2026-10-10 09:50:01 +01:00
Rob Emery 3eacc9c8a5 LLPD_MAC_ADDR_LEN -> MAC_ADDR_LEN and memcopy instead of manually setting bytes 2026-10-10 09:50:01 +01:00
Rob Emery 735672ccf1 Moving to Bank3 2026-10-10 09:49:52 +01:00
Rob Emery 1ebee4d6e9 Implementing basic LLDP server, should send port_id message
to each port once every 30 seconds.
* Additionally fixes the on-ward passing of LLDP frames through
the switch

The ports on the case are marked 1-8 so this should reflect that
rather than being 0-based

We can send the hostname as well, this can be dynamically allocated

Let's send the model as the system description as well
2026-10-10 09:49:24 +01:00
René van Dorst e67c928eb7 Merge pull request #465 from andreas5232/add-device-ONT-S207CW-62TS-SE
add support for ONT-S207CW-62TS-SE and identical Binardat-2G06-04210GSM
2026-10-08 06:45:31 +00:00
Andreas Erhard 46f8ea8712 re-added struct comments again 2026-10-08 00:32:02 +02:00
Andreas Erhard b0eab52a2e applied feedback from review 2026-10-07 01:18:08 +02:00
Andreas Erhard 8ce062c349 resized images 2026-10-07 01:13:20 +02:00
Andreas Erhard f768220a73 updated docs and removed duplicate picturs 2026-10-05 23:51:02 +02:00
René van Dorst eab2e26510 Merge pull request #553 from DrDoof/cleanup/leds-dump-debug
leds: move the LED register dump to BANK3 and behind a command
2026-10-05 17:43:11 +00:00
DrDoof b0ed56d423 leds: move the LED register dump to BANK3 and behind a command
leds_dump() printed the pin mux, the LED enables and every LED set on each
boot, from BANK2. It now lives in its own file in BANK3, where there is
room, and runs only on the new "leds" command, next to gpio and sds, so it
stays at hand for bringing up a new board without costing BANK2 or boot
output.
2026-10-05 08:22:19 +02:00
René van Dorst 36a064d03d Merge pull request #554 from DrDoof/cleanup/vlanlist-dead-test
httpd: drop a test in send_vlanlist() that can never be true
2026-10-05 06:11:32 +00:00
DrDoof 2d01b8b7d7 httpd: drop a test in send_vlanlist() that can never be true
vlan_get() only returns an error for VLAN 4095 and up, and the loop stops
at 4094.
2026-10-05 04:35:07 +02:00
Andreas Erhard fa632cdc6e updated docs and corrected led mapping 2026-10-05 00:43:25 +02:00
Andreas Erhard 177fb76e1b Merge branch 'main' into add-device-ONT-S207CW-62TS-SE 2026-10-04 23:45:20 +02:00
René van Dorst ff57084799 Merge pull request #405 from DrDoof/feat/isolation-gui
isolate: configure port isolation from the web interface
2026-10-04 20:47:04 +00:00
René van Dorst 2dc5520928 Merge pull request #545 from brunoz78/kp9000-v3_2
machine: add keepLink KP-9000-9XHML-X V3.2 (2M-PCB23-V3.2)
2026-10-04 20:19:38 +00:00
d00f 7c11142a04 isolate: configure port isolation from the web interface
A matrix of the ports, one row per ingress port, showing which ports a
frame entering there may leave through. The port number of a row turns
red when the port is not fully open, and the cell for the port itself is
marked since it is always allowed. Apply sends one "isolate" command per
changed row, and Allow all ticks the whole matrix.

The switch side is a new /isolation.json, which reports each port's
allowance mask read back from the hardware. The page is an Isolation
section of the single-page UI.
2026-10-04 22:11:19 +02:00
feelfree69 957a5b0e23 Merge pull request #549 from DrDoof/fix/strcpy-definition
Bring back the definition of strcpy
2026-10-04 21:50:09 +02:00
DrDoof c773230a9b Bring back the definition of strcpy
494320b removed strcpy() from rtl837x_flash.c but left its declaration
in rtl837x_common.h. Since then the linker has taken strcpy from the
SDCC library instead, which expects the destination as a generic pointer
in DPL, DPH and B. The callers pass an __xdata pointer and leave B as it
is, so the copy lands wherever B happens to point. boot.c only gets away
with it because B is zero there; a call with B set, as from the ChaCha20
test in #207, writes into internal RAM and stops the 8051.
2026-10-04 19:12:24 +02:00
logicog 0e9c997d06 Merge pull request #543 from brunoz78/i18n-de
web: add German (de) translation
2026-10-04 07:20:15 +02:00
logicog 5ab247e996 Merge pull request #535 from DrDoof/fix/igmp-v2
igmp: handle IGMPv1/v2 reports and v2 leaves
2026-10-04 07:16:52 +02:00
logicog d80717f3f0 Merge pull request #537 from dobodu/main
Update French translations and port configurations
2026-10-04 06:48:33 +02:00
Andreas Erhard 24069439b0 reversed rj45 port order and optimized led config 2026-10-04 01:11:10 +02:00
Andreas Erhard 4117f549cc Merge branch 'main' into add-device-ONT-S207CW-62TS-SE 2026-10-03 19:27:49 +02:00
Brunoz78andClaude Opus 5.5 2e24b24426 doc: add a photo of the 2M-PCB23-V3.2 board
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:35:00 +02:00
Brunoz78andClaude Opus 5.5 b71239f394 machine: add keepLink KP-9000-9XHML-X V3.2 (2M-PCB23-V3.2)
The V3.2 board has the same component placement as V3.1 and works with
its machine definition: RJ45 links at 2.5G, LEDs, SFP detection with DOM
readings at 10G, factory MAC at 0x1FC000 and the reset button. The new
target only reports "V3.2" as the machine name.

Installed from the original firmware V100.9.25 through its web
interface with the OEM installer image. Reading the flash in-circuit with
a SOIC-8 clip gave the chip ID EF FF FF, as on a V3.1 board.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 17:10:01 +02:00
René van Dorst 6e69f5bba3 Merge pull request #541 from odensc/patch-1
feat: add GoodTop GT-ST018M as supported device
2026-10-03 09:07:30 +00:00
logicog 1224335b66 Merge pull request #544 from HiroGitea/fix/storm-const-sdcc46
Fix SDCC 4.6.0 const warnings in storm control
2026-10-03 10:55:16 +02:00
Hiro e5b19bc594 storm: add the const qualifiers SDCC 4.6 asks for
Follow-up to #494. Storm control (#455) was merged after it and brings
three objects in code space without const: the extern declaration of
machine in rtl837x_storm.c and the name tables storm_types and
storm_names. SDCC 4.6.0 reports them as warnings 356 and 357, which
fail the build with CI=1 (--Werror). SDCC 4.5, which the CI uses, does
not report them.

The declarations now take the form used in the rest of the tree, as in
mime_strings in httpd.c. The images for KP_9000_6XHML_X2 and
KP_9000_9XHML_X_V3_1 are byte-identical before and after.
2026-10-03 12:02:08 +09:00
Brunoz78andClaude Opus 5.5 89fcc6836d web: add German (de) translation
Adds a German dictionary to LANG in app.js (all 317 keys, placeholders
unchanged), the language selector entry in index.html and the login
strings in login.html. Uses Swiss spelling (ss instead of ß), which
reads correctly for all German speakers.

Checked against a mock of the httpd endpoints: every section renders
with the German strings; no layout changes compared to English.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:28:57 +02:00
Oden f9d4ab0074 feat: add GoodTop device to SWTG018AS_A_V2_0 PCB doc 2026-10-01 18:37:10 -07:00
Oden 6f68ccef28 feat: add GoodTop GT-ST018M as supported device 2026-10-01 18:35:38 -07:00
dobodu 8e687ef28e Update firmware messages and regex patterns
Correction to missing 100m and small french syntax errors
2026-10-01 20:52:58 +02:00
DrDoof 87db2dd4b8 igmp: handle IGMPv1/v2 reports and v2 leaves
Only frames to 01:00:5e:00:00:16 reached the IGMP handler, so v1/v2
reports, which go to the group address, were never seen. The handler
also took the group from the first v3 group record for every type.

Pass all IPv4 multicast to the handler, which takes the IGMP header
from the IP header length, the group from the IGMP header for v1/v2
reports and v2 leaves and from the first group record for v3, and
returns 0 for anything that is not IGMP so it goes on to the IP stack.
The message and record types have names now.
2026-10-01 20:42:22 +02:00
dobodu aac1aed4c8 Update LINKS array to include '100M' 2026-10-01 12:08:53 +02:00
logicog f0aea3dcac Merge pull request #455 from DrDoof/feat/storm-control
storm: limit broadcast, multicast and unknown unicast and multicast per port
2026-10-01 08:05:37 +02:00
dobodu f10ab30f6d Update French translations and port configurations
Various updates, improvements and corrections to the french translation
2026-09-30 23:06:30 +02:00
DrDoof de431c6d84 storm: meters from 24 up, plainer parser, documentation from the review
Storm control used meters 0 to 39, the same meters the ACL hands out,
so a storm limit and an acl meter could overwrite each other. It now
uses 24 + port * 4 + type, meters 24 to 63.

parse_storm() is no longer reentrant and uses plain locals, looks the
type up in a while loop and returns straight from the one-line cases.
storm_show() uses plain locals too.

The documentation says how IGMP decides between mcast and umcast, how
to get from a meter index to its limit, where the IFG bit of a meter
is, and what an ingress bandwidth limit on the same port does to the
storm limit.
2026-09-30 22:48:31 +02:00
DrDoof 26547f3e01 storm: limit broadcast, multicast and unknown unicast and multicast per port 2026-09-29 21:46:56 +02:00
d00f 13b75a73c9 Merge pull request #474 from DrDoof/feat/chip-temp
temp: read the on-chip thermal sensor
2026-09-29 09:51:57 +02:00
d00f c6fceae724 Merge pull request #420 from DrDoof/fix/static-build-determinism
Make the static-file build repeatable
2026-09-29 07:09:46 +02:00
DrDoof 068ec4c510 fileadder: print the generated files only when there is no image to write
The image rule no longer passes -p, so fileadder fell through to printing
html_data.h and html_data.c on stdout on every image build. The listing is
only useful when fileadder is run without an image, to see what it would
generate.
2026-09-29 05:27:16 +02:00
d00f 3a92459190 build: don't regenerate html_data from the image rule
The .bin rule passes -p html_data to fileadder, which rewrites
html_data.c and html_data.h after the objects that include them have
already been compiled and linked. The next make therefore rebuilds
html_data.c, httpd.c and page_impl.c even when nothing changed, and
so does the one after that. The rule only needs to embed the files
into the image; the header comes from the html_data.c rule above.

Spotted by plaes in #358, which cannot be merged as it stands.

With this and the previous commit, a second make after a full build
compiles nothing, and the image is byte for byte the same as before.
2026-09-29 05:27:07 +02:00
d00f 5ffeb3d16a build: make the static-file build repeatable
Two things kept the same source from producing the same image.

fileadder walked the input directory with readdir(), which returns entries
in whatever order the filesystem hands them over. The same tree therefore
embedded the files in a different order on a different filesystem, and the
image differed byte for byte. scandir() with alphasort() fixes the order.

version.h was listed in .PHONY, which makes it permanently out of date and
propagates that to everything including it, so a plain build recompiled the
four largest translation units even when nothing had changed. Dropping it
from .PHONY alone would go too far the other way: with no prerequisites the
recipe would never run again and the version string would freeze at its
first value. A FORCE prerequisite keeps the recipe running every time, and
the generated header is only moved into place when its content actually
differs, so the timestamp survives an unchanged rebuild.

Measured on this tree: with FORCE the version follows GIT_VERSION and an
unchanged rebuild leaves version.h untouched; without it the header keeps
the value from the first build.
2026-09-29 05:27:07 +02:00
logicog de705380c9 Merge pull request #507 from bloqaudio/fix/build-uip-modules-per-config
uip: build the optional modules only when configured
2026-09-27 17:47:29 +02:00
logicog e4f3315af3 Merge pull request #525 from UAb5eSMn/web-network-config
web: Change ip, netmask and gw network settings at the same time
2026-09-27 17:42:21 +02:00
UAb5eSMn 07ab04e668 web: Change ip, netmask and gw network settings at the same time 2026-09-26 15:33:16 +02:00
bloq c5ce24ff78 Merge pull request #518 from vDorst/refactor_dedup_machine_init
machine-init: dedupe the same machine_custom_init()
2026-09-26 08:21:57 -05:00
René van Dorst bcb237158f machine-init: dedupe the same machine_custom_init() 2026-09-26 08:39:17 +02:00
logicog f103daaafa Merge pull request #519 from dobodu/main
Added french translation
2026-09-26 06:26:17 +02:00
bloq 168add47a3 Merge pull request #452 from gnacho/feat/session-timeout-config
httpd: make the session timeout configurable
2026-09-25 16:57:51 -05:00
gnacho 423886dc54 web: keep service labels unindented and add the es session strings
Restore the sy_igmp and sy_services_note lines to their original
indentation so they do not show up as changed in the diff; the new
session timeout strings move to their own lines. Also add the es
strings, which were missing after the merge with main.
2026-09-25 23:22:35 +02:00
gnacho 4a9137387c test: define session_timeout in the host harness
test_port_tables and test_page_json link httpd/page_impl.c unmodified,
which now references session_timeout. Define it in support.c next to
the other firmware globals so the tests link again on current main.
2026-09-25 23:22:35 +02:00
gnacho 2fc08f40d2 httpd: drop Max-Age from the session cookie
The server timeout is an inactivity timeout: scan_header() refreshes
last_session_use on every authenticated request. Max-Age counts from
login instead, and the cookie is only set there, so the browser deletes
it session_timeout seconds after logging in however busy the session
is. Leave expiry to the server, which already handles it.

The five-digit itoa16_html() stays: information.json must print
timeouts from 10000 to 65535 correctly.
2026-09-25 23:22:35 +02:00
gnacho 4a7fd7a098 Merge remote-tracking branch 'origin/main' into feat/session-timeout-config 2026-09-25 23:16:16 +02:00
dobodu 19cd1200b4 Update French translations in app.js 2026-09-25 21:59:13 +02:00
bloq 333adbaf19 Merge pull request #523 from DrDoof/cleanup/drop-call-traces
port, dhcp: drop the called/done traces from the console and syslog
2026-09-25 14:21:09 -05:00
bloq 8051061fd7 Merge pull request #522 from DrDoof/cleanup/dhcp-dead-memcpy
dhcp: drop the MAC copy after the requested IP and server id
2026-09-25 14:20:59 -05:00
bloq 8ae3f5e70f Merge pull request #521 from DrDoof/fix/dhcp-option-length
dhcp: skip IP and time options that are shorter than four bytes
2026-09-25 14:20:50 -05:00
dobodu 9ce328d155 Update French translations in app.js 2026-09-25 21:07:50 +02:00
dobodu 2e463a2f30 Add French translations to login page 2026-09-25 20:53:25 +02:00
DrDoof 422dc3f24d port, dhcp: drop the called/done traces from the console and syslog
rtl837x_port.c prints "called" and "done" around mirroring, PVID, VLAN
and L2 setup, and dhcp.c around discover, request, start and stop. They
land on the console on every boot and config replay, and in the log
once syslog is running. The VLAN line keeps the VLAN, members and
tagged values, only without the function name in front.

On the KP_9000_6XHML_X2 this takes 359 bytes out of BANK1, 88 out of
BANK2 and 24 out of BANK3. BANK1 goes from 431 to 790 bytes free.
2026-09-25 07:45:03 +02:00
DrDoof 4b01ff9f8a dhcp: drop the MAC copy after the requested IP and server id
dhcp_addopt_request_ip() and dhcp_addopt_server_id() both end with a
copy of four bytes of our MAC to the current position, without moving
the position on. The next option always starts there, so those bytes
are written over before the packet goes out and never reach the wire.
The packet stays the same without the copy, and BANK3 is 102 bytes
smaller on the KP_9000_6XHML_X2.
2026-09-25 07:45:03 +02:00
DrDoof 456a4d9d3f dhcp: skip IP and time options that are shorter than four bytes
ip_opt() copies four bytes whatever the option length says, so a
subnet, router, DNS, server id or broadcast option shorter than four
bytes stores the start of the next option as the address. long_opt()
does not look at the length at all and always moves on by six bytes, so
a lease, renewal or rebind option of another length puts the parser out
of step for the rest of the packet.

Both now read the length. An address or time shorter than four bytes is
skipped, a longer one is taken from its first four bytes and stepped
over whole, and a skipped time leaves the previous value alone.

Found in the code, not on the wire; no server here sends such options.
BANK3 is 47 bytes smaller on the KP_9000_6XHML_X2.
2026-09-25 07:45:03 +02:00
dobodu 02e9189f5f Add French to language selection
Added French language options to the selection.
2026-09-25 01:00:14 +02:00
dobodu aff7d9c098 Add French translations for various UI elements 2026-09-25 00:56:34 +02:00
René van Dorst 770926abd1 Merge pull request #517 from DrDoof/fix/rx-drain
rx: keep draining while the NIC buffer holds data
2026-09-24 19:27:08 +00:00
René van Dorst 13f04d8acb Merge pull request #515 from polyfaces/hasivo_s1100wp_8gt_1sx_se
machine: add the Hasivo S1100WP-8GT-1SX-SE target
2026-09-24 16:55:35 +00:00
polyfaces 49284fee15 Update docuementation and PCB photos 2026-09-24 22:01:53 +08:00
DrDoof db9ce7543e rx: keep draining while the NIC buffer holds data
The main loop drains the NIC only after an RX interrupt, at most
RX_BUDGET frames, then clears the status and waits for the next one.
When frames are left and the buffer reaches the RX stop threshold, the
NIC takes no more frames and raises no further interrupt, so reception
stops for good.

Check the buffer after clearing the status and keep draining while it
holds data; the interrupt is re-enabled only once it is empty.
2026-09-24 15:44:10 +02:00
polyfaces 7a14c2d4a5 Add Hasivo S1100WP_8GT_1SX_SE to supported devices
Update support_devices.md
2026-09-24 18:58:07 +08:00
polyfaces 92948079a9 machine: add the Hasivo S1100WP-8GT-1SX-SE target
RTL8373-based 8×2.5G PoE + 1×SFP switch.

- 8 copper ports at 10/100/1000/2500 Mbps, SFP at 1G/2.5G/10G (SDS 1)
- board doc with label specs and photos
2026-09-24 12:28:54 +08:00
René van Dorst e406a3e82b Merge pull request #499 from DrDoof/up/nic-rx-irq
rtlplayground: receive on the NIC interrupt instead of polling the buffer
2026-09-23 18:32:47 +00:00
René van Dorst c43b955db0 Merge pull request #498 from DrDoof/up/tick-block
rtlplayground: run the timed work once per tick
2026-09-23 18:31:47 +00:00
logicog 0321763e91 Merge pull request #511 from DrDoof/perf/rtl8224-ready
rtl8224: wait for the PHY to answer instead of a fixed 2.5 s
2026-09-23 20:29:10 +02:00
René van Dorst 4bbbcd7d04 Merge pull request #513 from DrDoof/fix/port-name-no-arg
cmd: "port <n> name" without a name clears it instead of reading stale words
2026-09-23 18:21:14 +00:00
DrDoof 682ed861fa rtl8224: wait for the PHY to answer instead of a fixed 2.5 s
rtl8224_enable() waits 2.5 s after releasing the RTL8224 reset before
anything talks to the PHY. On an SWTGW218AS the chip id already reads
back 0x8224 at the first poll, about 265 ms after the release.

Remember when the reset was released and, once SMI is up, poll the chip
id until it answers, giving up after the same 2.5 s as before. The PHY
writes that follow read back the same as before.

The reset pulse before it goes from 0.5 s to 20 ms, twice the 10 ms the
related RTL8372 and RTL8221B need. The pulse does reset the chip: the
MDI reverse register (MMD 30, 0x0a90) still reads its power-on value
0x00f3 just before the init writes 0x00fc, as it does with 0.5 s.
Together the console banner comes 3 s sooner.

The SerDes write loop in phy_config_8224() waited on the busy bit with
no limit, so a PHY that never answers stopped the boot before the
network came up. Bound it and report a timeout.
2026-09-23 20:21:11 +02:00
d00f 3a9045b97e cmd: "port <n> name" without a name clears it instead of reading stale words
The name branch read the fourth word without checking that the line had
one, and the tokenizer does not reset unused word slots, so "port 5 name"
took the name from wherever the previous command's fourth word had been:
"port 5 name hello" followed by "port 5 name" gave "hello" again, and
after a longer line it gave the tail of that line. The web UI never sends
the form, the console can. With no argument the name is now empty, which
also gives the console a way to remove a name.
2026-09-23 17:46:05 +02:00
d00f 4b9f6c319f Merge pull request #508 from DrDoof/fix/rebuild-on-flag-change
build: rebuild objects when the compiler flags change
2026-09-23 16:38:08 +02:00
DrDoof 6e2701bc8c rtlplayground: receive on the NIC interrupt instead of polling the buffer
The receive interrupt was wired up but never used: EX1 was enabled, the
handler re-enabled itself and did nothing, and the mask bit for it in the
NIC stayed clear, so idle() read the receive buffer register on every
pass to find out whether a packet had arrived, and slept until the next
tick when none had. A packet that arrived during that sleep waited for
the tick: 2.2 ms on average, 5 ms at worst, for every frame the CPU
handles.

Now the handler latches rx_irq and masks itself, and idle() sleeps
unless a receive, a link change, a console command or the tick is
pending. On rx_irq the loop drains the buffer, clears the status bit and
re-enables EX1, in that order: the NIC holds the line as long as the
status bit is set, and the bit does not clear while the buffer still
holds a frame, so a frame that lands between the last read and EX1 = 1
raises the interrupt again rather than getting lost. The other order,
clearing before draining, left the line asserted and produced an
interrupt on every pass. The mask is written after nic_setup(), which
resets it.

Measured on an RTL8373 board with the same counters in both images,
clocked by the tick counter, traffic from a host behind an LACP trunk:

```
                          polling     interrupt
idle, register ops/s          788            79
25 pps, RTT avg/max     2.19/4.96     0.44/0.75 ms
500 pps, RTT avg/max    2.78/5.06     0.39/5.40 ms
500 pps, loss               0.08%         0.08%
```

The handler compiles to clr EX1 / setb rx_irq / reti.

The timer interrupt now raises a tick flag too, so idle() no longer works
out TICKS_DUE twice per pass, and cmd_available moves from xdata to a
bit. The four wake-up flags sit in the low bits of one byte at 0x20, so
the sleep test is a single read of that byte. tx_buf_empty deliberately
stays out: it is 1 when there is nothing to do, so including it would
keep the sleep test from ever firing.
2026-09-23 16:12:32 +02:00
DrDoof ac3b4b17ae rtlplayground: run the timed work once per tick
idle() ran everything on every pass: the seconds block, the SFP pin
test, the TCP periodic timer behind a gate of its own, the STP timers
behind theirs. The timed work moves into handle_tick(), which idle()
calls once per system tick from a single gate; handle_tx() loses its
private one.

handle_sfp() ran on every pass as well, two GPIO reads per pass to learn
that nothing changed; it now runs every ten ticks. A module inserted is
still seen within 50 ms, and the read itself has been off the loop since
the wake-up wait moved to the tick.

No change in behaviour otherwise: the link, receive and console paths
stay where they were. SWTGW218AS: code +45 B, xdata +1 B, stack
unchanged.

The health checkpoints follow the work they measure: the seconds block,
SFP, TX and STP phases are taken inside handle_tick(), the link, RX and
command phases in idle().
2026-09-23 16:09:18 +02:00
DrDoof f87eb70c6a rtlplayground: read the link registers when the switch reports a change
The link-change interrupt was set up at boot (mode 0x5f84, mask 0x5f34)
but never enabled: the handler did nothing and did not clear the event in
the switch, so EX0 stayed commented out and idle() read the two link
registers on every pass instead, 400 reads a second on a quiet switch.

The handler now only latches a flag and masks itself; the register access
macros build a write in the shared SFR data bytes, so an interrupt that
touched the switch in the middle of a write from the main loop would
corrupt it. idle() clears the event register (write-one-to-clear, 0x3ff
for the ten ports), re-enables EX0 and then reads the link state, in that
order, so a change that lands during the read raises a new edge. The flag
is also set once a second from the seconds block, so a board whose INT0
line turns out not to be wired keeps detecting link changes, a second
late at worst. Boot drops the events pending from bringing the links up
before the mask is set.

Measured on an RTL8373 switch before this change: 0x5f88 held 0x3fd, nine
ports had reported a change nobody read. With the change a counter in the
loop went 2 -> 3 -> 4 on "port 5 off" / "port 5 on" and stayed at 4 over
three reads without events, so the event is delivered, cleared and
re-armed, and there is no flood.

SWTGW218AS: code +57 B, xdata 0, stack 151 -> 150 B; the flag is a bit
variable. The handler compiles to clr EX0 / setb link_irq / reti.

idle() also keeps in a bit what it saw in NIC_RX_BUFF_DATA when deciding
whether to sleep, and the first iteration of the receive loop uses it
instead of reading the register again a moment later. Between the two
only the link check and handle_sfp() run, and neither can make a queued
packet disappear. Every later iteration reads the register as before, so
the budget loop behaves as it did. This part was not measured on
hardware: one register read per pass needs a throughput run to show up.
SWTGW218AS: BANK1 and BANK2 +6 bytes each, BANK3 unchanged.
2026-09-23 16:04:39 +02:00
DrDoof 214cc6b737 rtlplayground: keep the STP and TCP timers on the system tick
stp_timers() ran every fourth pass of the main loop and stp_clock counted
passes, which only measures time while a pass is one tick long. It never
quite was: a pass is as long as the packets it handles, and #482 now takes
up to four of them, so under an IPv4 stream a pass is about 16 ms and STP
time runs at a third of its rate, with hellos 6 s apart and a root aged out
after a minute. Since #483 the loop also stops sleeping while packets wait,
so under a flood of cheap frames passes come faster than ticks and STP time
runs fast instead, shortening the forward delay in exactly the situation a
loop produces. uip_periodic() had the same problem from the other side: it
ran once per pass, so the retransmit timer sped up with the loop.

Both now go by the tick counter the timer interrupt keeps. The STP block
catches up on ticks a long pass missed, at most eight steps at a time, and
handle_tx() runs its periodics once per tick at most. One byte of xdata
more, 48 bytes of code.

A host test drives the gate with one pass every 5 ms, every 16 ms and
every 0.6 ms for ten seconds each; the STP clock lands on 500 steps in
all three, where counting passes gave 156 and 4166. A pass of 1.3 s loses
time rather than firing 65 steps at once, and the arithmetic survives the
tick counter wrapping.
2026-09-23 16:00:59 +02:00
feelfree69 40177f6318 Merge pull request #352 from DrDoof/feat/health-cmd
health: a CLI dump of where the main loop's time goes
2026-09-23 15:05:55 +02:00
feelfree69 d3dda09079 Merge pull request #489 from DrDoof/feat/port-media-pause
port: report link role and negotiated pause per port
2026-09-23 14:59:35 +02:00
feelfree69 277e729f2c Merge pull request #509 from DrDoof/phy/advertise-pause-above-1g
phy: advertise pause on the forced speeds from 1G up
2026-09-23 14:32:32 +02:00
feelfree69 f778b9159b Merge pull request #462 from DrDoof/fix/gui-save-merge
web: keep stored config lines and per-port ingress when saving
2026-09-23 08:53:05 +02:00
feelfree69 b6d6d2391d Merge pull request #473 from DrDoof/fix/config-replay-skip
config, httpd: skip an over-long line and refuse to store one
2026-09-23 08:52:19 +02:00
d00f 2c231fdf69 phy: advertise pause on the forced speeds from 1G up
The advertisement written for a forced speed of 1G and above is 0x1001,
while auto and the forced 10M and 100M modes all carry bit 10, the PAUSE
capability. Forcing a port to 1g or faster therefore takes pause off the
link: the advertisement narrows and the far end has nothing left to
agree to.

Measured on a SWTGW218AS through phyget and physet, on a port with no
link so nothing was disturbed. Auto reads 0x15e1, port 1 1g reads
0x1001, port 1 auto reads 0x15e1 again, and writing 0x1401 by hand
sticks. That bit 10 is the pause capability follows from a second
reading: two ports advertise the same 0x15e1 from this side, and pause
is active only against the partner whose own advertisement carries the
bit.

Reported by feelfree69 in #489.
2026-09-22 23:12:50 +02:00
d00f 689751ffe6 build: rebuild objects when the compiler flags change
The .d files track headers, so adding or removing a flag on a tree that
was already built leaves every object that did not change for some other
reason compiled with the old flags, and the image ends up a mix of the
two.

How that shows depends on the flag. DIAG=1 fails the link on undefined
symbols, which at least stops the build. An opt-in whose hooks expand to
empty macros loses the calls instead, so the link succeeds and the
feature quietly measures nothing.

Objects now depend on a stamp holding CC_FLAGS, rewritten only when the
flags differ, so an unchanged flag set still does not force a rebuild.
2026-09-22 22:25:53 +02:00
feelfree69 fa93340c24 Merge pull request #443 from DrDoof/build/xram-size
Makefile: let the linker check external RAM against 0xBFFF
2026-09-22 20:50:59 +02:00
feelfree69 e9523a2f67 Merge pull request #460 from DrDoof/fix/multipart-request-path
httpd: keep a continued upload away from the cmd and login handlers
2026-09-22 20:46:52 +02:00
feelfree69 088be074f5 Merge pull request #411 from DrDoof/fix/httpd-upload-bound
httpd: stop a firmware upload at the end of the image area
2026-09-22 20:45:55 +02:00
d00f 340fdd97e5 port: report link role and negotiated pause per port
Two per-port MAC status registers that nothing in the firmware reads:
0x6408 says which end is master on a BASE-T link, 0x63FC and 0x6400 say
whether pause was negotiated in each direction. port <n> show prints
both at the end.

The read sits in rtl837x_port.c rather than phy_show(), because these
are MAC registers and phy_show() is skipped for SFP ports. Master/slave
keys off machine.is_sfp[] so it stays off the fibre ports, where the
notion does not apply.

Bit N of each register is chip port N, which is not how
RTL837X_REG_LINKS_STS indexes its bytes a few lines above in the same
header.
2026-09-22 20:28:54 +02:00
DrDoof 09ce4b391b httpd: stop a firmware upload at the end of the image area
edc469f made max_upload count, which bounds how many bytes a request may
carry. Nothing bounds where those bytes land. The two are not the same
check: the first watches the request, the second watches the flash address.

uptr walks forward one page at a time from FIRMWARE_UPLOAD_START and has no
ceiling. A body whose closing boundary never arrives keeps writing page after
page past the staging region, and on a flash exactly twice FIRMWARE_UPLOAD_START
the address wraps onto the running image at zero, so a bad upload takes the
working firmware with it. That commit's own message notes the related hazard:
flash_write_bytes() carries the address in three bytes and drops what does not
fit.

Bound it where uptr actually moves: refuse the next page once it would leave
the area check_and_flash_update_image() reads back, and answer 400. A partial
image left behind is harmless - boot rejects it on CRC and erases it.

This rebase drops the part of the original patch that removed max_upload;
the length limit and the address limit now sit side by side.
2026-09-22 20:04:44 +02:00
feelfree69 7e6577a811 Merge pull request #415 from DrDoof/fix/syslog-ring-overrun
syslog: stop the log ring from lapping its reader
2026-09-22 18:54:51 +02:00
bloqaudio 675faf0471 uip: build the IPv6 neighbour cache only for IPv6 builds
uip-neighbor.c is the IPv6 neighbour cache. Its only caller is the neighbour
solicitation handler in uip_process(), which sits inside #if UIP_CONF_IPV6,
so on an IPv4 build the module is linked without being reachable.

Guard it on the same macro as its caller.

Worth recording separately: the module does not currently compile with
UIP_CONF_IPV6 set. uip_ipaddr_cmpx() is defined only in the non-IPv6 half of
uip.h, and uip_neighbor_add() calls it, so an IPv6 build stops with an
implicit declaration. That is true before this change as well, and is left
alone here.

On SWTGW218AS, on top of the forwarding change, the stack gains a further 12
bytes to 172 available and BANK1 drops another 714 bytes to 46080.
2026-09-22 09:23:13 -05:00
bloqaudio 175ca9464a uip: build the forwarding module only when configured
uip-fw.c forwards packets between several network interfaces. Nothing in
this firmware calls it: the only references to uip_fw_output() outside the
module are commented out in uip-split.c. It is compiled and linked all the
same, and its register pressure lands in direct RAM, which on SWTGW218AS is
already reported as having no spare space.

Add UIP_CONF_FORWARDING alongside the existing UIP_CONF_BROADCAST and build
the module only when it is set. A device that forwards turns it on and gets
the module unchanged; a single-interface device stops paying for it.

On SWTGW218AS the stack gains 9 bytes, from 151 available to 160, and BANK1
drops 1200 bytes, from 47994 to 46794.
2026-09-22 09:23:13 -05:00
René van Dorst 38241f0a08 Merge pull request #500 from 13974684927/add-f7008-2-5
machine: add the F7008-2.5 (GPOE-V1.2) 8+1 target
2026-09-22 13:28:01 +02:00
笙笙宝宝 0fe323b12f machine: add the F7008-2.5 (GPOE-V1.2) 8+1 target
Add support for the F7008-2.5 board (PCB F7008-2.5GPOE-V1.2), an
RTL8373N + RTL8224N design with eight 2.5GBASE-T ports and one SFP+ cage.

The LED pads are not wired in port order, so the generic DEFAULT_8C_1SFP
target lights the wrong lamp on this board. The pad to lamp map was
measured on the hardware and is documented in doc/devices/F7008-2.5.md.

The green lamp marks a 2.5G link, the yellow lamp blinks while data flows.
2026-09-22 19:12:12 +08:00
d00f 94187461c6 ci: build once with HEALTH=1 as well
Code behind a build flag nobody sets in CI rots quietly. One extra build of
the same machine with the instrumentation on keeps it compiling.
2026-09-21 22:25:32 +02:00
d00f fa0eb91586 health: build the instrumentation only when asked for
The counters, the phase hooks in idle() and the "health" command are now
behind HEALTH, set from the command line with HEALTH=1. Without it the
hooks are empty macros and the command does not exist, so a default build
carries none of it: on SWTGW218AS that is 1065 bytes of bank 2, 101 of the
common segment and 28 of xdata, plus seven banked calls per main-loop pass.

It is a developer's tool. Reading the dump takes knowing the main loop, and
a user who needs it for a report can build with the flag on, which is how
the kernel treats its lock and latency instrumentation as well.
2026-09-21 22:25:32 +02:00
d00f 2f872ea08e health: drop the constant-false branch the compiler warned about
The tcp slot dump printed the slot index behind `if (UIP_CONNS > 1)`, and
with one connection configured that is a constant-false condition around
live code: sdcc reported two "unreachable code" warnings for the body and
one "conditional flow changed by optimizer" for the loop that could only
run once.

Print the single slot directly and state the assumption as a build-time
check, so a build that raises UIP_CONNS fails loudly here instead of
quietly labelling every line as slot zero. Builds warning-free again.
2026-09-21 22:25:32 +02:00
d00f ab2d30a05b doc: describe the health command
What each line of the dump means, how two dumps give rates, and the
two things worth knowing before trusting the numbers: the command
inflates its own cmd phase counters by the cost of printing, and the
untouched figure is a high-water mark, not a live level.
2026-09-21 22:25:32 +02:00
d00f d37ce07119 health: a CLI dump of where the main loop's time goes
Every probe of a misbehaving switch so far measured it from the
outside. This puts the witnesses inside.

Each idle() pass increments a loop counter and stamps a checkpoint
after every phase: link polling, SFP, RX, TX, STP and the command
dispatch. A phase spanning two or more system ticks counts as slow and
its worst time is kept, in 5 ms units. handle_rx counts frames, so the
dump separates a flooded CPU from a stalled loop.

At boot the free stack area is painted, preserving the interrupt state
around the painting, and the dump reports how much of the pattern
survives next to the live stack pointer. The TCP connection table is
printed whole: state, timers, retransmissions, ports and peer, next to
the httpd transfer state, so a permanently occupied slot names its
occupant. With more than one slot configured every slot is listed.

Everything is raw hex; two dumps a known time apart give the rates.
The implementation sits in cmd_parser next to the other command
handlers and needs no storage beyond its counters.
2026-09-21 22:25:32 +02:00
René van Dorst 4bd6ffd083 Merge pull request #492 from vDorst/feat_build
CI: Turn on error on warning also for sdcc-compiler.
2026-09-21 18:11:15 +00:00
René van Dorst 94cfec8693 CI: Build a 4x2/8372 and 8x1/8373 version 2026-09-21 19:50:15 +02:00
René van Dorst a4bb3f1725 CI: Enable CI option to stop at warnings 2026-09-21 19:50:15 +02:00
René van Dorst 931e6da0ab Makefile: Add option CI to turn all warnings into errors 2026-09-21 19:50:11 +02:00
René van Dorst 88454ec7d7 Merge pull request #439 from vDorst/feat_i2c_precompute
I2CBUS: Calculate I2C-settings as compiletime instead at runtime.
2026-09-21 15:30:56 +02:00
René van Dorst f0efb84abb Merge pull request #494 from tofurky/sdcc_4.6.0_warnings
Fix warnings seen on SDCC 4.6.0 due to missing const qualifiers
2026-09-21 10:51:54 +02:00
René van Dorst 62d3424a92 Merge pull request #496 from DrDoof/fix/sfp-nonblocking-wake
sfp: do not block the main loop while a module wakes up
2026-09-21 10:32:22 +02:00
DrDoof a3860c29cb sfp: do not block the main loop while a module wakes up
handle_sfp() called sfp_module_read() as soon as it saw a module appear, and
that path opened with delay(100). delay() spins on PCON until the tick counter
runs down, so the main loop stood still for 100 ticks - half a second at
SYS_TICK_HZ 200. Nothing else ran in that window: no STP, no LACP, no packet
handling. A diagnostic build measured the sfp phase at up to 107 ticks, the
longest stall of any phase in the loop.

Remember when the module showed up and let the loop continue. The I2C read
happens on a later pass, once the wake-up period has elapsed. Pulling a module
cancels a pending read, and a failed read still restores the detect bit, so the
existing retry-on-the-next-poll behaviour is unchanged.

The comparison is written out rather than using a helper so that this does not
depend on anything outside the current tree. The pending flag is an array
indexed by slot, like the other per-slot state in this file.

Cost on SWTGW218AS, measured against the branch point: BANK3 11173 -> 11264,
CSEG, BANK1 and BANK2 unchanged, plus 4 bytes of xdata.
2026-09-21 09:45:18 +02:00
René van Dorst b1961c6955 Merge pull request #495 from tofurky/sfp_normalize_nul
Properly handle NULs when normalizing SFP fields
2026-09-21 08:51:57 +02:00
Matt Merhar bb84bdc55a Properly handle NULs when normalizing SFP fields
Some generic 1.25gbps DAC cables were seen to have a NUL *after* the
space padding in the model field. This caused the normalization to miss
the space padding in the string. The vendor field did not have the
issue.

Note that this seems to be against spec per SFF-8472, which mentions
only space padding or *all* NUL, not a combination of both. In the case
of all NUL, we'll waste a few cycles with no-op writes.
2026-09-20 19:55:07 -04:00
Andreas Erhard 9733ff61a0 another almost working state. port order is wrong and leds are not correct, but ports are all working 2026-09-21 01:16:16 +02:00
Matt Merhar 1b5243714e Fix warnings seen on SDCC 4.6.0 due to missing const qualifiers
Building with SDCC 4.6.0 emits tons of warnings e.g.:

	cmd_parser.c:31: warning 356: object in read-only code space should be const
	cmd_parser.c:36: warning 357: pointer to object in read-only code space should be pointer to const

These of course become fatal when building with --Werror.

Comparing resultant binaries, it seems that a single string is shifted
(deduplication?) but the numbers emitted at the end of the build are
otherwise identical.
2026-09-20 18:25:17 -04:00
Andreas Erhard 36459fdd76 Merge branch 'main' into add-device-ONT-S207CW-62TS-SE 2026-09-20 22:18:41 +02:00
René van Dorst ce11790a0d I2C: Silence compiler warning, de-magic values. 2026-09-20 20:31:43 +02:00
René van Dorst 55431b39f6 Merge pull request #483 from DrDoof/perf/rx-nosleep
rtlplayground: do not sleep in idle() while a packet is waiting
2026-09-20 13:02:59 +00:00
René van Dorst 46e1c955ba Merge pull request #482 from DrDoof/perf/rx-budget
rtlplayground: take several packets per loop in handle_rx()
2026-09-20 13:02:07 +00:00
d00f 7b728cec6f rtlplayground: do not sleep in idle() while a packet is waiting
idle() went to sleep before it looked at the receive buffer, so a packet that
arrived just after the loop dozed off waited for the next tick before anything
touched it. Every TCP round-trip paid that toll, which is why the web UI felt
sluggish even though no phase of the loop was anywhere near busy.

Check the receive buffer first and only sleep when it is empty. The read takes
its result straight from SFR_DATA_U16 rather than going through reg_read_m()
and the sfr_data[] copy, which is what the rest of the tree already does.

Measured on SWTGW218AS with this change alone:

  web UI, median of 15 requests for the main page   88 ms -> 76 ms
  CPU port under a one-way UDP stream               159 -> 230 packets/s

The loop still sleeps normally when there is nothing to do, so this does not
turn the core into a busy-wait: loops/ticks stays at 0.99 on an idle switch,
and rises above 1.0 only while packets are actually queued.

Costs 12 bytes of code and no extra XDATA.
2026-09-20 14:56:44 +02:00
d00f 0d17dd563f rtlplayground: take several packets per loop in handle_rx()
handle_rx() picked up exactly one packet per iteration of the main loop. Since
the loop sleeps until the next tick, that capped the CPU port at roughly one
packet per tick no matter how little work it had to do. A burst of broadcast,
ARP or IGMP traffic aimed at the CPU queued up behind that and pushed out the
STP and LACP timers.

Drain up to RX_BUDGET packets in one pass instead. The budget matters: without
one a sustained flood would spin here and starve everything else in the loop.

The two register reads in the loop take their result straight from
SFR_DATA_U16 rather than going through reg_read_m() and the sfr_data[] copy,
which is what the rest of the tree already does.

Measured on SWTGW218AS with a one-way UDP stream, counting the packets the CPU
actually processed, with this change alone:

  before          159 packets/s
  RX_BUDGET 4     245 packets/s, loop iteration ~16 ms
  RX_BUDGET 8     298 packets/s, loop iteration ~26 ms

Throughput is already most of the way there at 4, so the larger budget mostly
buys a longer worst-case delay for the other phases. STP stayed responsive in
both cases.

Costs 71 bytes of code and no extra XDATA.
2026-09-20 14:42:50 +02:00
René van Dorst d556d634ca Merge pull request #487 from jagheterfredrik/patch-1
add zlib1g-dev to Dockerfile
2026-09-20 12:34:33 +00:00
Fredrik Gustafsson 2844a1ed6d add zlib1g-dev to Dockerfile
Current Docker-based build fails without it
2026-09-20 14:31:03 +02:00
René van Dorst 7b5e365d5e Merge pull request #409 from gnacho/i18n-es
feat(translate): add Spanish (es) translations
2026-09-20 11:40:56 +00:00
gnacho 45fb382b07 feat(translate): add Spanish option to the language selector 2026-09-20 13:29:12 +02:00
René van Dorst 8973774594 Merge pull request #476 from bloqaudio/fix/eee-port-argument
eee: accept a port again, and stop the web UI discarding other ports
2026-09-20 09:50:25 +00:00
René van Dorst 48c5b0c9cf Merge pull request #486 from bloqaudio/fix/syslog-null-conn
syslog: do not read the connection before there is one
2026-09-20 09:39:04 +00:00
René van Dorst 1557a7cf3c MACHINE: rebase, update the new added devices 2026-09-20 11:30:17 +02:00
René van Dorst 131bc106af boot/i2c-setup(): Remove the switch-statement defaults.
All the possibilities are handled, or otherwise a compile-time macro
I2CBUS_VALID ensures invalid options are rejected with a compiler error.

Also fix a compiler warning.
2026-09-20 11:25:35 +02:00
René van Dorst 19ccc4003d i2c: Add compiletime check macro for validating the i2c pins.
Use 0x7 as error value for an invalid I2C SDA/SCL-pin value.
The macro `I2CBUS_VALID` checks for this value.
When a invalid pin is ues, the compiler errors out with:
  "machine.c:<line>: error 176: sizeof applied to an incomplete type",

Error message is not ideal but at-least points to the right location in
the code.
2026-09-20 11:25:35 +02:00
René van Dorst b5fe9f217f i2c/sfp: Demagic shift-values 2026-09-20 11:25:35 +02:00
René van Dorst f65d14f452 I2CBUS: Calculate I2C-settings as compiletime instead at runtime.
We can reduce the i2c-settings to just one byte, and this new const value
can directly be used by the I2C-peripheral without additional computation.
This saves 1-xdata load and a lot of bit-shifting.

I see a codesize savings of +200 bytes.
2026-09-20 11:25:34 +02:00
René van Dorst b92fba870c Merge pull request #484 from bloqaudio/fix/upload-auth-gate
httpd: gate /upload on authentication before it changes state, and make the length limit count
2026-09-20 09:21:25 +00:00
René van Dorst 4deee377a7 Merge pull request #485 from bloqaudio/fix/led-set-index
leds: index the second LED register from the set it belongs to
2026-09-20 08:40:52 +00:00
René van Dorst fe42b07e2b Merge pull request #481 from DrDoof/fix/rx-checksum
uip: verify TCP and UDP checksums using the RX descriptor flag
2026-09-20 07:59:46 +00:00
d00f 9984649177 uip: verify TCP and UDP checksums using the RX descriptor flag
The TODO next to the disabled TCP checksum code asks which bit of the RX tag
carries the result. It is bit 2 of byte 1: the hardware sets it when the L4
checksum is bad.

That answers the question for UDP as well, which was not being checked either
because UIP_CONF_UDP_CHECKSUMS is 0. Both paths now read the flag instead of
running a checksum over the payload on the 8051.

A zero UDP checksum, which is optional in IPv4, is not flagged by the hardware,
so DHCP keeps working and no extra condition is needed.

rx_headers lives in rtlplayground.c, which the host tests do not link, so
test_httpd_tx carries the storage itself the way it already carries uip_buf.
A zeroed descriptor reads as a good checksum, so the existing scenarios are
unaffected.

A new scenario sets the flag on a request and expects it counted as a checksum
error and left unanswered, then sends the same request with the flag clear and
expects it served. Built against uip.c without this change, those first two
checks fail and the two control checks still pass, so the scenario tells the
two versions apart rather than merely exercising the code.

Costs 156 bytes of code on SWTGW218AS and no extra XDATA.
2026-09-20 00:35:10 +02:00
René van Dorst 7d47edb649 Merge pull request #477 from bloqaudio/fix/port-speed-select
web: show the speed a port is set to, not always auto
2026-09-19 11:26:03 +00:00
René van Dorst fdb19fe0ee Merge pull request #451 from SkullyDev/main
HASIVO_S600W_4GT_2XGT_SE machine
2026-09-19 11:25:30 +00:00
SkullyDev b9b50f9a70 Merge branch 'main' into main 2026-09-19 13:07:28 +03:00
SkullyDev c31b67572b Remove led behavior & LED comments
Should work without gpio_input_setup
2026-09-19 13:03:42 +03:00
Andreas Erhard a4e3b4368b extracted ONT-S207CW-62TS-SE into separate device 2026-09-18 22:16:06 +02:00
bloqaudio 93003c8cb8 web: show the speed a port is set to, not always auto
The speed selector on the Ports tab was filled with its options and never
given a value, so it read "auto" on every load whatever the port was set
to. Forcing a port to 1G and reloading showed "auto" in the selector
beside a link badge reading 1G.

The advertisement bits ports.json already reports say what the port is
set to: phy_set_speed() writes the same three registers the JSON reads
back, and every forced speed leaves exactly one of the six bits standing
while auto leaves the lot. Map those to the selector, and leave it alone
while it has focus, as the MTU field does.

Values with no entry in the table, such as the 100M setting that
advertises both duplex modes, leave the selector untouched rather than
claim a setting the list cannot express.
2026-09-18 15:13:41 -05:00
bloqaudio fd058ea74d port: name the port an eee command acted on the way the user wrote it
port_eee_enable() and port_eee_disable() report the port with
print_byte(), which prints the internal index in hex, so turning EEE off
on port 5 answers "EEE off for 04". port_eee_status() in the same file
already uses print_phys_port(), and the status listing it produces counts
from one, so the two halves of the same command disagreed.

Use print_phys_port() in all three messages.
2026-09-18 14:59:47 -05:00
bloqaudio edc469fe29 httpd: make the firmware upload length limit count
max_upload was armed once when the upload began and never reduced, so
`max_upload - uip_len > 0` was an unsigned expression that only went
false when a segment arrived whose length matched the limit exactly. The
write pointer climbed from the staging address with nothing to stop it,
while flash_write_bytes() carries the address in three bytes and drops
whatever does not fit.

Subtract each segment as it is taken and compare before subtracting, which
is what the comment there described. The limit was only ever armed for
firmware uploads; configuration uploads keep the bound their own buffer
already imposes.
2026-09-18 14:01:03 -05:00
bloqaudio a71c5359a5 httpd: refuse an unauthenticated firmware upload before it changes state
The multipart branch for /config tests authenticated before it touches
anything; the branch for /upload does not, and the 401 for it is only
raised further down at the dispatch. By then the request has already set
config_upload, uptr, verify_crc, max_upload and pre_acc, and announced a
firmware upload on the console.

Those five are globals while the transfer state they steer is per
connection, and the stack is built for ten connections, so a request that
is about to be rejected can still retarget an upload another client is in
the middle of: a configuration upload continues as a firmware one and its
remaining parts are written to the staging area instead of the
configuration sector.

Apply the same test the neighbouring branch uses, before the assignments.
2026-09-18 14:00:51 -05:00
bloqaudio 9e3863cc4e syslog: do not read the connection before there is one
udp_callbacks() offers every UDP connection event to syslog_callback(),
which decides whether the event is its own by comparing the local port
against state.syslog_conn->lport. The pointer is null until syslog is
started: syslog_init() clears it at boot and syslog_stop() clears it
again, so with syslog off, which is the default, every poll dereferences
null and reads whatever lives at the bottom of xdata.

Should that word ever match the port being offered, the send path runs on
another application's connection, overwrites uip_appdata and drops the
log line it was holding.

Test the pointer first.
2026-09-18 13:59:59 -05:00
bloqaudio d22ec04b5b leds: index the second LED register from the set it belongs to
leds_setup() walks all four LED sets and writes the high-speed nibble of
each into one of two registers, LED3_0_SET1 for sets 0 and 1 and
LED3_0_SET3 for sets 2 and 3. The second branch reuses the index
expression of the first without rebasing it, so sets 2 and 3 address
sfr_data[-1], [-2], [-3] and [-4].

Two things follow. Sets 2 and 3 never receive their nibble, because the
read-modify-write pair writes back exactly what it read. And four bytes
below sfr_data are overwritten on every boot of every machine: the loop
runs over all four sets whatever the board declares. On this build the
linker places sfr_data directly after the serial receive ring, so the
writes land in the tail of that ring.

leds_dump() reads the same two registers back and already rebases with
(set - 2); use the same form here.
2026-09-18 13:59:50 -05:00
bloqaudio 0a65536a7f web: send an eee command in the documented argument order
The EEE toggle posted `eee <port> <on|off>`, the reverse of the
`eee [on|off|status] [port] [100m|1g|2g5]` the parser and the
documentation describe, so the switch answered with its usage line and
the checkbox sprang back. The multi-page UI this replaced sent `eee on`
with no port at all, which toggled every port instead of one.

Moving the port to the end breaks the stored-configuration merge, which
builds its drop key as a line prefix: with the port trailing, a per-port
line matches the bare eee entry and replaces every eee line in the
configuration rather than the one for that port. Give the command its own
case beside the bandwidth one, so a line with a port replaces only that
port and a line without one clears them all, and accept the optional
speed word the command has always taken.

Reported by tofurky in issue #459.
2026-09-18 13:57:07 -05:00
bloqaudio 0631c813e2 cmd: parse the port of an eee command from the start of the word
parse_eee() sniffs word 2 for a trailing 'g' or 'm' to tell a speed from
a port number, and advances idx past the digits to do so. The port branch
then hands that advanced idx to cmd_parse_port_separator(), which expects
the first character of the number and finds the separator instead, so it
returns 0 and the command is rejected. Every other caller passes
cmd_words_b[].

The result is that no eee command carrying a port number works, in any
argument order, and the stored per-port lines a saved configuration
replays are silently dropped.

Reported by tofurky in issue #459, along with this fix.
2026-09-18 13:56:53 -05:00
René van Dorst eaebeba528 Merge pull request #464 from DrDoof/fix/json-escape
httpd: don't let a quote in a port name break the web page
2026-09-18 18:40:16 +00:00
René van Dorst b36fd95356 Merge pull request #450 from bloqaudio/feat/idle-poll-pause
web: stop polling when the page has had no input for ten minutes
2026-09-18 18:36:34 +00:00
d00f 52b1d1a4ea web: show the chip temperature in the System page
The row sits between Hardware and Flash. The firmware sends the formatted string
rather than the raw register, since the System table prints its values as they
arrive.

Costs 186 bytes in BANK1.
2026-09-18 18:28:13 +02:00
d00f 48f28ae7ff temp: read the on-chip thermal sensor
The chip has a pair of thermal meters and nothing was reading them. This adds a
`temp` command that prints the current reading next to the one taken at power-on.

The result register at 0x340 splits a 19 bit value across two fields: bits 18:3
live in the low 16 bits, bits 2:0 in bits 18:16, with the sign at bit 18, degrees
in 17:10 and a fraction below. The nice part of that layout is that the degrees
fall out of a shift of the low half, so the conversion stays 16 bit.

The meter comes out of reset latched: the register keeps whatever was measured
when the board powered up and then never moves. Four samples 40 s apart came back
identical down to the raw ADC, and 0x340 matched the power-on register at 0x350.
Clearing bit 2 of the control register at 0x338 during init is what makes it live.

Measured on an SWTGW218AS: 58.7 to 59.5 C while running, with the power-on
register still showing the 49.2 C it started from. For what it is worth, the
scale lines up three ways: the threshold register reads -40 and +125, the slope
from warm-up works out at 4450 LSB/C, and the slope between the two meters at
4388 LSB/C.

We only read the meter that is up after boot. The other sits in reset with no
coefficients, so it would just repeat its power-on value.

Costs 231 bytes in BANK2.
2026-09-18 18:28:13 +02:00
d00f a7fc8303e9 cmd: move name_char() above the parse_mac_cmd comment
The helper landed between that comment and the function it describes, so the
comment now reads as if it belonged to name_char(). Moving the helper up puts
the two back together. The generated code is unchanged.
2026-09-18 17:33:24 +02:00
René van Dorst a62866a93a Merge pull request #472 from DrDoof/fix/httpd-sent-len
httpd: account for the bytes actually sent
2026-09-18 16:19:46 +02:00
d00f 42bbed9661 httpd: brace the empty else body so the host tests build clean
gcc flags the else whose only statement is dbg_string(), because that macro
expands to nothing unless DEBUG is on, leaving the branch with an empty body.
The line is older than this branch; the bench is simply the first thing that
compiles httpd.c with a host compiler and -Wall -Wextra.

Turn warnings into errors in the test Makefile too, so the next one is caught
where it appears instead of scrolling past. It is the only warning the four
test binaries produce, so nothing else has to change.
2026-09-18 16:09:56 +02:00
d00f 3c8de6da87 httpd: refuse a configuration with a line the replay cannot take
The replay takes a line of at most 126 bytes, so a longer one is skipped
on boot. Neither the upload path nor the editor looked at line length,
so the file the user saved was not the configuration the switch would
come up with.

Reject such a body in config_take(), before the sector is erased, which
the upload path already maps to 400. The editor points at the offending
line and its length, and measures it in bytes rather than characters, so
a name written in a non-ASCII script is counted the way the switch
counts it.
2026-09-18 16:03:16 +02:00
d00f 3a6f62b1d5 config: skip a line the parser cannot buffer instead of stopping
execute_config() replays the saved configuration line by line into
cmd_buffer. A line that does not fit, or one cmd_tokenize() rejects,
ended the replay right there: everything below it was silently left
unapplied, while the file itself and the editor showing it stayed
complete. On a switch here, a 130-byte line placed after the last real
setting was enough to lose every line that followed it on the next boot.

Report the offending line and carry on with the next one. The skip flag
lives across the flash page refill, so a long line that crosses a page
boundary is not resumed halfway and run as a truncated command.
2026-09-18 16:03:16 +02:00
René van Dorst 530e2683f0 Merge pull request #471 from ScApi/main
Add support for Goalake IG108 / Netcore GS9 / PB-2132 PCB
2026-09-18 11:14:53 +02:00
Sylwester Petela 9fedc6dd3d Remove unnecessary comments and fix removed empty line 2026-09-18 10:19:06 +02:00
Sylwester Petela 5e23d13771 Sort Devices in alphabetical order 2026-09-18 10:17:35 +02:00
Sylwester Petela 09d5857fdd Trim case and PCB images 2026-09-18 10:16:54 +02:00
d00f be309853c9 test: bench for the httpd transmit path
Drives the unmodified httpd.c and uip.c through a handshake and a GET
from a client that moves its receive window, and compares the bytes that
reach the wire with the file they are meant to carry. The served file
holds a position-dependent pattern, so a repeat or a gap is reported at
its offset rather than as content that differs somewhere.

The first scenario holds the window still and must pass: it is the check
that the bench itself is sound, before the two moving-window scenarios
are believed.
2026-09-18 03:17:11 +02:00
d00f 072edb36a8 httpd: account for the bytes actually sent
uip_mss() is the limit on the next segment, not a record of the last
one: uIP recomputes it from the window the peer advertises in every
incoming segment, before the application is called. The ACK path
subtracted it from slen and added it to o_idx, and the retransmit path
handed it to uip_send(), so whenever the window moved between a segment
going out and its ACK coming back, the file being served lost or
repeated the difference. A window shrinking from 1460 to 600 between the
two repeats 860 bytes under fresh sequence numbers, which a browser
writes into the file; a window opening the same way skips 860.

Remember the length in tx_send() and use it in both places. The
retransmit path then repeats exactly the segment uIP still counts as
outstanding, which is what uip_connr->len makes it put on the wire.
2026-09-18 02:39:52 +02:00
d00f 10dd330e3c httpd: subtract through a pointer of known type
uip_appdata is void *, so p - uip_appdata subtracts pointers of
different types. SDCC accepts it, C does not, and no host compiler will
build the file for a unit test. Cast to the type the byte count is meant
in; the generated firmware code is unchanged.
2026-09-18 02:39:52 +02:00
Andreas Erhard c514eb8bf3 Merge branch 'logicog:main' into add-device-ONT-S207CW-62TS-SE 2026-09-17 23:33:30 +02:00
d00f e2c4ed6be3 sfp: clean the module strings where they are read
The vendor, model and serial strings are filtered while the EEPROM read
fills the buffers, so the page copies them out as they are. NUL still
ends a string, so a field shorter than its slot reads the same as before.

The three loops in send_status() become one call each. strcpy() cannot
return the length: rtl837x_common.h hides that prototype under
RTLP_HOST_TEST so host builds take the C library version, and a call
that adds its result to slen does not compile there. A small static
helper does the copy instead, and bank 1 drops by 96 bytes.
2026-09-17 22:32:46 +02:00
d00f 56ee56af2e cmd: keep quotes and control bytes out of names when they are set
The console, POST /cmd and the saved config all go through the command
parser, so one filter there covers every way a name can be set. The
hostname already did this inline; port and VLAN names now do it too.

The SFP strings still need escaping on the way out: they come from the
module's EEPROM, not from a command.
2026-09-17 22:29:57 +02:00
d00f 8626b4c1d4 httpd: don't let a quote in a port name break the web page
A port named NAS"1 made status.json unparsable, so the page stopped
loading, and the name survives a reboot in the saved config. VLAN names
and the SFP strings have the same problem.

They now go through a helper that replaces ", \ and non-printable bytes
with a dot, the way the hostname is already sanitized on ingest. The
length does not change, so the size checks still hold.

The helper also covers the 0xa0 byte that send_sfp_info() skipped as a
special case, so that check is gone.
2026-09-17 22:29:57 +02:00
René van Dorst 0cc09e4015 Merge pull request #467 from DrDoof/test/table-engine-mock
test: run the table code and the JSON pages on the host
2026-09-17 18:56:40 +00:00
d00f 674dc1f35d port, httpd: parenthesise the mixed operators and drop a dead local
Compiling these two files for the host turns up warnings that SDCC does
not print: four places mix & with | and two mix && with ||, and l2_delete
keeps a counter it never reads. The parentheses only say what the
compiler already does, so the generated code is unchanged.

counter_to_html() reads the SFRs directly for the zero test, which saves
14 bytes of bank 1.
2026-09-17 20:49:01 +02:00
Sylwester Petela 7e6b443e07 Merge branch 'main' of https://github.com/ScApi/RTLPlayground 2026-09-17 20:01:38 +02:00
Sylwester Petela 73ec184c23 Added the port-type and led colours 2026-09-17 20:01:29 +02:00
René van Dorst bf41fbd202 Merge pull request #463 from DrDoof/fix/cmd-error-status
cmd: report a rejected command as an error and keep it out of the history
2026-09-17 17:33:56 +00:00
Sylwester Petela 04ab442c6d Merge branch 'logicog:main' into main 2026-09-17 19:28:02 +02:00
René van Dorst c02623a610 Merge pull request #468 from mintsoft/readme-fix
Updating images for new UI
2026-09-17 17:16:19 +00:00
Sylwester Petela c69bf4c5ec Add support for Goalake IG108 / Netcore GS9 / PB-2132 PCB
Support For Goalake IG108 and Netcore GS9 based on same PCB = PB-2132

SFP port untested
2026-09-17 19:13:27 +02:00
Andreas Erhard 575274786a incorporated feedback from PR 2026-09-17 12:08:37 +02:00
Rob Emery 7f4f6dae03 Fixing typo's and image sizes 2026-09-17 09:25:35 +00:00
Rob Emery 4eeb12c020 Merge branch 'logicog:main' into readme-fix 2026-09-17 12:17:09 +03:00
Rob Emery 7d81b15b6e Updating images to match new UI 2026-09-17 10:13:14 +01:00
d00f d6253ddc25 test: the JSON generators against the tables the CLI wrote
page_impl.c and rtl837x_port.c are linked together over the simulated
engine, so /vlan.json, /vlanlist and /l2.json are checked against what
vlan_create, pvid and the L2 table hold, through the same bytes the switch
would. The L2 walk is driven the way the web page drives it, page after
page until an index repeats, which is also where the repeat of the first
entry is pinned down as the wrap marker the page relies on. The counter
test fails on the code before the previous commit.
2026-09-17 04:22:22 +02:00
d00f 8cac67cdf7 httpd: print 64-bit packet counters in full on the status page
send_status wrote the high and the low word of a packet counter one after
the other, each without its leading zeros. That reads fine until a port
passes four billion packets; from then on a low word of 5 prints as "0x15"
next to a high word of 1, and the rates the page derives from the
difference between two readings jump around. send_counters already padded
the low word, so both now share one helper that pads it only when the high
word is set, which also drops the leading "0" the counters page used to
print in front of every small value.
2026-09-17 04:22:22 +02:00
d00f 5620e950ce test: simulate the register file and the table engine, and test the tables
The switch is reached through four register functions and the SFR_DATA
registers, so a flat register file behind them is enough to run the table
code on the host. A few addresses are live: writing TBL_CTRL with EXECUTE
performs the VLAN or L2 operation at once and clears the bit, STAT_GET
fetches a counter, L2_TBL_FLUSH_CTRL drops entries. Nothing ever reports
busy, so a polling loop runs once and the tests say nothing about what
happens when hardware stalls; that question was settled elsewhere.

The header states the VLAN and L2 entry layouts on its own, so a test can
hold what rtl837x_port.c writes against a second reading of the same bits
rather than against itself. The first suite does that for the VLAN entry,
the PVID register two ports share, the static multicast and management
entries, the per-port flush and the trunk registers. Two stub headers stand
in for the ones a firmware build generates, so the harness needs no SDCC
build first.
2026-09-17 04:22:22 +02:00
d00f 83491bc9e3 test: let the shim compile sources that touch the SFRs and libc
Two things kept every register-driven module out of the host harness. The
SFR keywords were unknown, so rtl837x_sfr.h did not compile at all, and once
it did, each translation unit would have defined its own copy of the
registers. They map to extern declarations now; a mock emits the storage
once by re-including the header with the keywords redefined.

The other was memset. rtl837x_common.h hides the firmware's own prototypes
under RTLP_HOST_TEST so host builds use the C library, but nothing declared
the library versions, and the modules do call them. The shim pulls in
string.h ahead of everything, where nothing can clash with it.
2026-09-17 03:55:30 +02:00
Andreas Erhard eb83b8be09 added support for ONT-S207CW-62TS-SE and identical Binardat-2G06-04210GSM 2026-09-17 00:18:46 +02:00
d00f bece4a20a1 cmd: let the helper print the Error prefix
The messages said Error: themselves, or usage:, or nothing at all.
cmd_error() prints the prefix now, so every rejected command answers the
same way and the strings only carry what is specific to them.

Two messages that started with Error or refusing are reworded so the
prefix does not read twice. The syslog usage text becomes one string
instead of three calls.
2026-09-16 23:55:11 +02:00
gnacho cdc84c75e4 httpd: give the session cookie a Max-Age matching the timeout
The session cookie carried neither Expires nor Max-Age, so browsers that
clean up session cookies aggressively (LibreWolf, for one) may drop it
while the session is still valid. Send Max-Age=session_timeout so the
declared lifetime matches the timeout already enforced by the firmware.

itoa16_html() only converted four digits, so extend it to five to print
timeouts up to 65535. That also fixes information.json, which used the
same helper and reported wrong values from 10000 s up.
2026-09-16 19:05:48 +02:00
gnacho 6be1871bb4 Merge remote-tracking branch 'origin/main' into feat/session-timeout-config
# Conflicts:
#	httpd/httpd.c
2026-09-16 18:08:21 +02:00
gnacho 4686515de3 web: add a session timeout field to the Services card
Adds a numeric input next to the syslog settings to read and set the
session timeout without knowing the command. The current value is
reported in information.json, so the field also shows the default the
firmware was built with.

Costs 86 bytes of code (BANK1) and nothing in RAM.
2026-09-16 18:07:49 +02:00
d00f c39cc421a7 print: take the message as a pointer to const
The strings are literals in code space, so the parameter can say so.
Drops six warning 357 from the build.
2026-09-16 16:17:46 +02:00
d00f c1df7c23d1 cmd: set the error status and print the message in one helper
Every rejecting path repeated the same two lines. cmd_error() does both,
which also makes it harder to add a message and forget the status.
2026-09-16 16:16:10 +02:00
René van Dorst 9bae4052a3 Merge pull request #454 from bennydiamond/Fix_MACHINE_LIANGUO_HYWS_SGT0108S
Support Lianguo HYWS-SGT0108S - Missing closing bracket
2026-09-16 09:12:33 +02:00
bennydiamond 0d115e0e0b Include HYWS-SGT0108S in makefile recipe 'machine_check' 2026-09-15 15:40:56 -04:00
bennydiamond 8e78d1e629 Missing closing bracket 2026-09-15 15:40:56 -04:00
d00f 13cc9db1ce cmd: report a rejected command as an error and keep it out of the history
run_cmd_body() answers POST /cmd with 400 when err_status is set, but
only the tokenizer set it. Every "Error:", "Invalid ..." and "Unknown
command" from the parsers went back as 200, so the web page reported the
command as applied, and save_cmd put the rejected line into the command
history, from where Save to flash wrote it into the startup config.

Set ERR_INVALID_ARGUMENT on every path that rejects a command: the err:
labels of the parsers, the parsers that print their own error, the
stp parser and the unknown command fallback. Warnings that still apply
the command, such as a full VLAN name table, are left alone.

Record a command in the history only when it was accepted, and let
execute_commands() report the first failure of a multi-line body, since
cmd_tokenize() clears err_status again for every following line.
2026-09-15 21:17:47 +02:00
d00f 3dcab09047 web: keep stored config lines and per-port ingress when saving
Save to flash ran the stored config through the same filter as the
command log, so stored lines the page does not know were dropped. The
stored lines are now carried over as they are and only the log is
filtered.

ingress only changes the ports it lists, but the merge replaced every
stored ingress line. It now merges per port; "ingress t|u|a" still
replaces everything, and per-port settings an all-ports line already
overrode are not revived.

Write from the config editor no longer clears the command log or the
unsaved marker, since the edited text does not hold the changes made on
other pages; only Save, which merges them, clears the log.

The command log grows from 1 kB to 2 kB, which still fits the 2500 byte
output buffer, and send_cmd_log() stops at the end of that buffer.
2026-09-15 20:18:17 +02:00
René van Dorst 94992562fd Merge pull request #458 from DrDoof/fix/httpd-session-init
httpd: do not accept a session cookie before the first login
2026-09-15 18:14:27 +00:00
René van Dorst 6150ada5f8 Merge pull request #457 from DrDoof/fix/flash-zero-length
flash: bail out of reads of zero length
2026-09-15 18:10:50 +00:00
René van Dorst 8116ef0a5b Merge pull request #461 from DrDoof/ci/pull-request
ci: build pull requests too
2026-09-15 18:06:42 +00:00
d00f 0cd56b1196 ci: build pull requests too
The workflow only ran on push, so a pull request opened from a fork
never got a build, and neither did the host-side unit tests that run in
the same job.
2026-09-15 19:27:50 +02:00
d00f 3b3f36256c flash: drop the unused status variable from flash_read_bulk()
Since #427 the busy wait calls flash_read_status() directly, so the
local it used to go through is no longer read anywhere.
2026-09-15 15:28:36 +02:00
d00f 18b28edcbb httpd: keep a continued upload away from the cmd and login handlers
handle_post() takes the request path from byte 6 of the segment it is
given. For the first segment of a POST that is the path, but for a later
segment of a multipart upload it is whatever part of the file happens to
sit there. The cmd and login handlers were matched before the multipart
state was checked, so a segment of a config or firmware upload with
"cmd " at that offset ran the rest of the segment as CLI commands, and
one with "login " there aborted the upload with 400.

Check for an upload in progress, and for new upload and config requests,
before looking at cmd and login. A new POST cannot match both sets of
words, so nothing changes for requests that are not uploads.
2026-09-15 09:57:49 +02:00
d00f 6ea9503ab2 httpd: do not accept a session cookie before the first login
session_id is cleared at boot and only a successful login fills it in,
while the uptime counter and last_session_use both start at 0. For the
first SESSION_TIMEOUT seconds after a start, a request carrying an empty
"Cookie: session=" therefore passes is_word_x(session, session_id) and
is treated as logged in, which is enough for /cmd, /config and /upload.

Only look at the cookie once a session has been handed out.
2026-09-15 09:37:11 +02:00
DrDoof e0df37f359 flash: bail out of a security-register read of zero length
flash_read_security() ends its loop the same way flash_read_bulk() did
before the previous commit: only through comparisons with 1, 2, 3 and 4. A length of
zero matches none of them, the following subtraction wraps on uint16_t
and the loop keeps printing until the counter comes back down to 4.

No caller passes zero today, since the only one is the CLI with a
constant length of 40, but the bound belongs in the function rather
than in whoever calls it.
2026-09-15 09:14:40 +02:00
DrDoof c04a5fc3a4 flash: bail out of a bulk read of zero length
flash_read_bulk() ends its copy loop only through the comparisons
len == 1|2|3|4, so a length of zero matches none of them. The
following len -= 4 wraps on uint16_t to 65532 and reaches 4 only
after 16384 iterations, writing 65536 bytes to the destination and
overwriting the whole XDATA space.

send_config() gets there with valid_len 0 whenever the stored
configuration begins with a NUL byte, which is the state left by
uploading an empty configuration file. Every later request for the
configuration page then clears the management VLAN and the STP
flag and replaces the host address, while the switch keeps running
and keeps forwarding traffic.
2026-09-15 09:14:40 +02:00
gnacho f0b311fc43 httpd: make the session timeout configurable
The session timeout was a compile-time constant (200 s), so changing it
meant rebuilding the firmware. Make it a variable that keeps the same
default and add a "session <seconds>" command, so it can be set from
the console or from the startup configuration like the other settings.

Measured on KP_9000_9XHML_X_V3_1: ROM +180 bytes (BANK1 +17, BANK2
+161) and xdata +2 bytes; the internal RAM map is unchanged.
2026-09-15 00:24:45 +02:00
René van Dorst 2f005d2993 Merge pull request #324 from bloqaudio/pr/host-test-harness
test: add host-side unit-test harness, covering cmd_editor
2026-09-14 21:59:07 +00:00
SkullyDev 974052877d Added config for HASIVO_S600W_4GT_2XGT_SE (4x2.5G, 2x10G)
SysLed behavior logic added so it can mimic any original style
2026-09-15 00:37:27 +03:00
gnacho ae77ffe495 feat(translate): add Spanish (es) translations for single-page UI 2026-09-14 22:44:25 +02:00
bloqaudio f65416f0d1 ci: run the host-side unit tests on every push
The harness builds with the gcc the workflow already installs and runs
in under a second, so it goes before the SDCC steps and fails the build
first when a host-visible regression lands.
2026-09-14 15:04:19 -05:00
bloqaudio ffde4eaf60 web: stop polling when the page has had no input for ten minutes
The firmware counts every authenticated request as session activity, and
the page polls the status every 2.5 s, so an open tab never let the
session expire. Track the last mouse, key, touch or wheel event and treat
the page as hidden once ten minutes have passed without one: the pollers
keep ticking but send nothing, the session times out in the firmware as
it did before, and the next input resumes polling, which meets the 401
and returns to the login page.
2026-09-14 14:06:41 -05:00
bloqaudio c05d83132b test: drop the unused typedef keyword and make the gcc-only warning switch conditional
Building the firmware headers on the host showed two warnings from
machine.h: `typedef struct machine { ... };` and the same for
machine_runtime declare no alias, so the keyword only draws "useless
storage class specifier in empty declaration". Every user says
`struct machine`, so drop the keyword.

-Wno-builtin-declaration-mismatch is a gcc switch and clang reports it
as an unknown option on every build; add it only when the compiler is
not clang.
2026-09-14 14:05:33 -05:00
bloqaudio dcd3cd5a98 test: add host-side unit-test harness, covering cmd_editor
Compile selected firmware translation units unmodified under host gcc with
AddressSanitizer and UBSan, force-including a shim header that erases the
SDCC 8051 keywords, and link them against a mocked serial ring and console.
A first suite covers the CLI line editor: line entry, backspace editing,
and both long-input overflow cases (length cap and ring-pointer stall).

The only firmware-source change is one #ifndef RTLP_HOST_TEST block in
rtl837x_common.h grouping the libc-named prototypes (sleep/memcpy/memset/
strlen/strcpy/strcmp) so host builds take them from the C library instead;
on-target builds are unaffected.

Run with: make -C test
2026-09-14 14:02:35 -05:00
René van Dorst 9f107b6e60 Merge pull request #410 from DrDoof/fix/httpd-rexmit-cont
httpd: retransmit a continued chunk from where it was sent
2026-09-14 18:12:43 +00:00
René van Dorst 15b10d9ed0 Merge pull request #427 from bennydiamond/Flash_SingleIO_ReadStatus
Temporary revert to SingleIO for Flash operations (except data read)
2026-09-14 17:19:29 +00:00
René van Dorst abf1cd1344 Merge pull request #429 from bloqaudio/feat/webui-v2
web: replace the multi-page UI with a themed single-page app
2026-09-14 17:17:39 +00:00
René van Dorst b5b52776bc Merge pull request #449 from bloqaudio/fix/bank2-dhcp-bank3
Move DHCP out of BANK2 into BANK3
2026-09-14 16:55:24 +00:00
René van Dorst 0486c34bfd Merge pull request #448 from bloqaudio/fix/stp-port-keyword
stp: accept the documented port keyword again
2026-09-14 16:55:08 +00:00
bloqaudio 690092d564 Move DHCP out of BANK2 into BANK3
BANK2 outgrew its 48 KB window on PCB_SWTG018AS_V2_1_0 (49182 bytes, 30
over) once the spanning tree code learnt about link aggregation, and the
image builder refuses to link. Only KP_9000_6XHML_X2 is built in CI, so
the overflow was not caught there.

dhcp.c is self-contained: its three exports are __banked, the rest of
the module is only reached from inside it, it holds no __code pointers,
and its uip calls already crossed into BANK1. Moving the pragma is the
whole change. BANK2 49182 -> 43691 on PCB_SWTG018AS_V2_1_0, BANK3 5709
-> 11206, BANK1 +6 for the trampolines; the generated assembly shows no
plain lcall leaving the module's bank.
2026-09-14 08:29:39 -05:00
bloqaudio 115ea09ebb web: address a link aggregation group as one spanning tree entity
The firmware lists a LAG as its own entity in /stp.json (`lag` and `mbr`,
`p` 0) and drops the member ports from the list; its settings take
`stp lag <n>`. Give the group its own row in both spanning tree tables,
named with its members, key the cells by entity rather than port number,
and rebuild the tables whenever the entity set or a membership changes so
a group created on the LAG tab appears without a reload. The `stp lag`
commands are recognised for the unsaved-changes badge and the startup
config merge alongside `stp port`.
2026-09-14 08:14:53 -05:00
bloqaudio f3e1a393b5 stp: accept the documented port keyword again
The rename of the entity index turned `stp port <n>` into `stp ent <n>`
in the parser only. doc/stp.md, the web UI and every saved startup
config still spell it `stp port`, so per-port settings were rejected on
the console and silently dropped when the startup config was replayed at
boot. Restore the keyword; `stp lag <n>` is unchanged.
2026-09-14 08:05:57 -05:00
bloqaudio 206c72b5ef web: floor the optical power at -40 dBm
A dark receiver reports 0 mW, which printed as -Infinity dBm. The DOM
value has a resolution of 0.1 uW, so -40 dBm is the lowest reading the
module can express; clamp there.
2026-09-14 08:03:36 -05:00
bloqaudio 472811cc9a doc: add the single-page UI to the changelog 2026-09-14 08:03:36 -05:00
bloqaudio 80552816b9 web: move the language and theme selectors to the System section
The header carried the save button, the language selector and the theme
selector on every section, although the latter two are set once. They
now live in a Display card in the System section; the header keeps the
title, the unsaved-changes badge and Save to flash.
2026-09-14 08:03:36 -05:00
bloqaudio c1caf6f569 web: stop the MAC table walk when its section is left
The forwarding database is read a page at a time; leaving the MAC table
or ports section let the walk carry on through the request queue. Give
the walk a generation number that the leave hooks bump, and stop the
walk at the next page when it is stale.
2026-09-14 08:03:36 -05:00
bloqaudio ddad65fe90 web: keep a VLAN's row when its details cannot be read
The VLAN table is one /vlanlist plus one /vlan.json per VLAN. A failed
per-VLAN request rejected the chain, so every VLAN after it was missing
from the table, indistinguishable from VLANs that do not exist. Keep the
row with the id and name from the list and a ? in the port columns.
2026-09-14 08:03:36 -05:00
bloqaudio ff7b517491 web: let the merged config be edited before it is written
Save to flash showed the merged startup config read-only, so a stray line
had to be fixed afterwards in the System section. The preview is now a
textarea: the size and unknown-line notes follow the edits, the write
button is disabled above 2048 bytes, and what is written is the edited
text.
2026-09-14 08:03:36 -05:00
bloqaudio 65417433eb web: fit the hostname in the sidebar, widen the number fields and the page
The default hostname (RTLPlayground- plus six hex digits) overflows the
200 px sidebar; clip it with an ellipsis and keep the full name in the
title. The MTU and Mbit/s fields were 70 px, which leaves no room for five
digits next to a number spinner. The main column stops at 1400 px instead
of 1100 px so the dashboard cards sit side by side on a wide screen.
2026-09-14 08:03:36 -05:00
bloqaudio b7621fad31 web: keep the port detail view live and summarise each port in a tooltip
The old dashboard showed a hover tooltip per port with the link state and,
for SFP ports, the module and its DOM readings, and rewrote it on every
poll. The single-page app replaced it with a click-to-open dialog that was
a snapshot: the values did not change while it was open.

Build the rows once per port and use them for both: the dialog is
re-rendered on every status poll while it is open, and each port in the
strip carries the same rows as its title.
2026-09-14 08:03:36 -05:00
bloqaudio 82f78cb8f2 web: render the per-port tables in physical port order
The firmware emits every per-port array in logical port order, which on a
board with a remapped port table (SWGT024: phys_to_log = {4,5,6,7,8,3,...})
is 6, 1, 2, 3, 4, 5. The old pages iterated over physical port numbers and
looked each entry up; the single-page app rendered the arrays as received,
so the ports, spanning tree, EEE and bandwidth tables and the dashboard
strip came out in logical order, and the strip put the SFP marker and the
detail view on the wrong port.

Sort every array by portNum on receipt so index i is port i+1 throughout.
2026-09-14 08:03:36 -05:00
bloqaudio 75f2ade1a1 doc: describe the web UI files and translation table after the rewrite 2026-09-14 08:03:36 -05:00
bloqaudio 3e91b05a42 web: replace the multi-page UI with a themed single-page app
The pages become one index.html holding every section, one app.js and a
self-contained login.html. The layout is a sidebar with light, dark and
Selenized themes (or follow the OS), a live dashboard with per-port
packet rates, and confirmation dialogs in front of the actions that can
lock the operator out.

Behaviour changes against the previous pages:

- every request goes through one queue that waits for the previous
  response BODY rather than its headers: the firmware shares a single
  output buffer across connections and delimits responses by closing,
  so a second request in flight corrupts the first
- "Save to flash" merges the command log into the startup configuration
  with the grammar the console accepts, shows the result before writing,
  verifies the sector by reading it back and clears the command log only
  after that; the header shows an "unsaved changes" badge while the
  running configuration differs from flash
- the console section shows the output the firmware returns for a
  command, including the parser's explanation on a rejected one
- the firmware update section checks size, bank header and CRC16 of the
  image in the browser before uploading, shows upload progress and waits
  for the switch to come back after the reset
- the ports table shows the learned MAC of a port when there is exactly
  one and the count otherwise; the MAC table sorts per column, filters,
  and names the LAG a learned address sits behind
- the spanning tree section sends only the settings that changed on
  Apply instead of one command per control event
- link aggregation groups are numbered 1-4 like the command line, and
  applying an empty group clears it

The translation table is inline in app.js with English, Japanese and
Chinese. The language selector sits in the page header and reloads the
page so that dynamically built tables are rebuilt in the new language.

Only login.html and the favicon are served before authentication; the
stylesheet, the port images and the old script bundle no longer exist.
The simulator's allowlist follows the firmware's.
2026-09-14 08:03:36 -05:00
bloqaudio ec1cb4106d httpd: count every authenticated request as session activity
The session timer was refreshed only when a page from the file table was
served, so a client that loads its pages once and then talks to the JSON
endpoints alone is logged out SESSION_TIMEOUT seconds after the load,
however busy it is. A firmware upload started after that point is answered
with 401 instead of a verdict. Refresh the timer wherever the session
cookie validates, which covers every authenticated request, and drop the
file-serving refresh that this makes redundant.
2026-09-14 08:03:35 -05:00
René van Dorst 62c6c65f1c Merge pull request #435 from bennydiamond/Support_HYWS-SGT0108S
Support for Lianguo HYWS-SGT0108S 8 + 1 unmanaged
2026-09-14 09:46:20 +02:00
René van Dorst c691591295 Merge branch 'main' into Support_HYWS-SGT0108S 2026-09-14 09:40:16 +02:00
d00f eb4bae278b Makefile: let the linker check external RAM against 0xBFFF
The 8051 in the RTL837x has 48 kB of XDATA, 0x0000 to 0xBFFF. Writing
a byte and reading it back from the same address works all the way up
to 0xBFFF on a SWTGW218AS, while at 0xC000 the byte comes back as 0x00.
Without --xram-size the linker assumes 16 MB, so running past the end
would still build cleanly and only show up at run time.

The value is 49151 rather than 49152. The linker check adds up the
sizes of the XDATA areas, and XDATA allocation starts at 0x0001, so a
limit of 49152 would still let the last byte land on 0xC000.
2026-09-14 02:07:57 +02:00
bennydiamond 7908044a44 Rework flash_write_enable() 2026-09-13 17:32:46 -04:00
bennydiamond 0ec1a5e63f Proper mapping for phys_to_log_port array 2026-09-13 17:16:34 -04:00
bennydiamond 114af559c7 Add support for Lianguo HYWS-SGT0108S unmanaged 8+1 2026-09-13 12:08:20 -04:00
bennydiamond 3ec9319d77 Fix flash driver for proper SIO ops
Set SPI peripheral to SIO mode for all operations except READ.
Set back SPI Peripheral to DIO after said operation.
2026-09-13 01:40:26 -04:00
d00f 8d7faf5312 httpd: retransmit a continued chunk from where it was sent
A file larger than the output buffer is sent in two phases: the first
buffer goes out in MSS-sized pieces, with o_idx walking through it, and
what is left is then streamed from flash a chunk at a time into outbuf
from offset 0.

The continuation branch never reset o_idx, so it kept the offset the first
phase had left it at, while uip_rexmit() retransmits from outbuf + o_idx.
A lost continuation segment was therefore retransmitted as the bytes 1460
further into the buffer, which the client accepts as the missing data, and
with o_idx + slen past the 2500-byte buffer it also read whatever xdata
follows outbuf.

Reproduced on a SWTGW218AS by dropping 15% of the switch's TCP segments:
seven of eight fetches of a 21 kB static came back the right length and
the wrong content, each first differing at a fixed phase of the 1460-byte
chunk. With o_idx reset the retransmission repeats the chunk it sent.

The o_idx += slen right after slen = 0 went too: it added nothing.

(cherry picked from commit 789d29423288f89411a41673e26ce1854b296c3d)
2026-09-08 16:22:07 +02:00
d00f 552bd1cb18 syslog: stop the log ring from lapping its reader
write_char() masked writeptr and never looked at readptr, so once more
than LOGBUF_SIZE bytes were written between two drains the ring wrapped
over content that had not been sent yet.

That is not a corner case. The ring is emptied once per main loop pass,
while a single command runs to completion inside one pass, and a full L2
listing prints well over twice the 512 byte buffer. On hardware the whole
1227 byte dump came out as one 174 byte datagram whose first line began in
the middle of a MAC address.

Drop the character when the ring is full instead of overwriting, so what
has been logged stays intact and it is the tail that is lost. A full ring
also flags a line, otherwise a burst without a newline would leave the
reader waiting on line_available forever and syslog would stop for good.
2026-09-07 22:51:57 +02:00
113 changed files with 8707 additions and 4302 deletions

No files matched your search

+9 -2
View File
@@ -3,6 +3,7 @@ name: Build firmware
on:
push:
branches: ['**']
pull_request:
jobs:
build:
@@ -17,7 +18,13 @@ jobs:
run: |
apt update
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y
- name: Run the host-side unit tests
run: make -C test
- name: Check if machine.c can be compiled for all machines
run: make machine_check
- name: Make project
run: make MACHINE="KP_9000_6XHML_X2"
- name: Make project (4x2/RTL8372)
run: make CI=1 MACHINE="KP_9000_6XHML_X2"
- name: Make project (8x1/RTL8373)
run: make CI=1 MACHINE="KP_9000_9XHML_X_V3_1"
- name: Make project with the health instrumentation
run: make clean && make CI=1 MACHINE="KP_9000_6XHML_X2" HEALTH=1
+4
View File
@@ -7,3 +7,7 @@ version.h
tools/httpd_sim
tools/injector
tools/fileadder
# host-test stand-ins for the two generated headers
!test/stub/html_data.h
!test/stub/version.h
+9
View File
@@ -6,11 +6,20 @@
- Web UI
- Compress the embedded assets (minify + gzip) and switch to a single-page layout. #315
- Replace the multi-page UI with a themed single-page app: light, dark and Selenized themes following the browser by default,
English, Japanese and Chinese, save to flash merges the command log into the startup config and verifies the write,
firmware images are checked in the browser before upload. #429
## Changed
## Fixed
- Config
- A startup configuration line longer than the command buffer no longer stops the replay:
the offending line is skipped and the following lines are still applied.
- The web UI and the configuration upload now refuse a line the replay cannot take,
instead of writing it to flash.
## Breaking changes
- Config
+1
View File
@@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y \
python3 \
libjson-c-dev \
git \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
# git safe.directory for mounted repos (Makefile uses git describe)
+32 -8
View File
@@ -19,6 +19,14 @@ ifeq ($(MACHINE),)
else
CC_FLAGS += -DMACHINE_$(MACHINE)
endif
# Health instrumentation and the "health" console command: HEALTH=1 gmake ...
ifneq ($(HEALTH),)
CC_FLAGS += -DHEALTH
endif
ifeq ($(CI),1)
CC_FLAGS += --Werror
endif
BUILDDIR = output/$(MACHINE)
VERSION_HEADER := version.h
@@ -71,10 +79,13 @@ SRCS += \
rtl837x_igmp.c \
rtl837x_init.c \
rtl837x_leds.c \
rtl837x_leds_dump.c \
rtl837x_phy.c \
rtl837x_pins.c\
rtl837x_port.c \
rtl837x_stp.c
rtl837x_stp.c \
rtl837x_lldp.c \
rtl837x_storm.c
SRCS += \
httpd/httpd.c \
httpd/page_impl.c
@@ -104,11 +115,14 @@ html_min: $(HTML)
html_data.c html_data.h &: $(HTML) | tools html_min
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -z -d $(HTML_MIN) -p html_data
$(VERSION_HEADER):
$(VERSION_HEADER): FORCE
@printf '%s\n' "#ifndef VERSION_H" "#define VERSION_H" \
"#define VERSION_SW \"$(VERSION_EXTENSION)\"" \
"#define BUILD_DATE \"$(BUILD_DATE)\"" \
"#endif" > $(VERSION_HEADER)
"#endif" > $(VERSION_HEADER).tmp
@cmp -s $(VERSION_HEADER).tmp $(VERSION_HEADER) \
&& rm -f $(VERSION_HEADER).tmp \
|| mv $(VERSION_HEADER).tmp $(VERSION_HEADER)
httpd: html_data.h
@@ -126,15 +140,25 @@ distclean: $(SUBDIRSCLEAN)
$(SUBDIRSCLEAN):
$(MAKE) -C $(@:clean=) clean
$(BUILDDIR)/%.rel: %.c | create_build_dir html_data.h
# Objects depend on the flags they were built with, through a stamp file that
# is rewritten only when CC_FLAGS actually changes.
CCFLAGS_STAMP := $(BUILDDIR)/.ccflags
.PHONY: FORCE
FORCE:
$(CCFLAGS_STAMP): FORCE | create_build_dir
@echo '$(CC_FLAGS)' | cmp -s - $@ 2>/dev/null || echo '$(CC_FLAGS)' > $@
$(BUILDDIR)/%.rel: %.c $(CCFLAGS_STAMP) | create_build_dir html_data.h
$(CC) -MMD $(CC_FLAGS) -o $@ -c $<
$(BUILDDIR)/%.rel: %.asm | create_build_dir
$(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir
${ASM} ${AFLAGS} -o $@ $<
# mv -f $(addprefix $(basename $^), .lst .rel .sym) .
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel
$(CC) $(CC_FLAGS) -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^
$(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^
$(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx
objcopy --input-target=ihex -O binary $< $@
@@ -144,11 +168,11 @@ $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.i
tools/output/imagebuilder -i $^ $@
tools/output/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/output/fileadder -a $(CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -z -d $(HTML_MIN) -p html_data -b BANK1 $@
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -z -d $(HTML_MIN) -b BANK1 $@
tools/output/crc_calculator -u $@
ln -sf $(MACHINE)/rtlplayground-$(FILENAME_EXTENSION).bin output/rtlplayground.bin
.PHONY: clean distclean all $(SUBDIRS) $(SUBDIRSCLEAN) $(VERSION_HEADER) create_build_dir
.PHONY: clean distclean all $(SUBDIRS) $(SUBDIRSCLEAN) create_build_dir
.PHONY:
machine_check:
+18 -16
View File
@@ -2,15 +2,15 @@
A Playground for Firmware development for advanced user of RTL8372/RTL8373 based 2.5GBit Switches.
For each hardware configuration of these devices, there is usually a managed and an
umanaged version sold, with mostly identical hardware. The aim is to provide management
unmanaged version sold, with mostly identical hardware. The aim is to provide management
features also for unmanaged devices with additional features such as Management VLAN,
dhcp servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however
DHCP servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however
only the following features are provided:
- A modern web-interface with mouse-over to display further information
- A serial console interface to configure all features
- IGMP to configure Multicast streaming
- Port configuration showing detailed informtion about own and Link-partner advertised
Speed settins and configuration of these settings on the local side
- Port configuration showing detailed information about own and Link-partner advertised
Speed settings and configuration of these settings on the local side
- Per-port configuration of frame sizes (MTUs) for Jumbo-Frame support or limiting MTUs
for particular devices
- EEE (Energy Efficient Ethernet) can be configured per-port. Detailed information is
@@ -25,7 +25,7 @@ only the following features are provided:
- Firmware updates via the web
- Installation as a firmware upgrade from the original web-interface
<img width="1420" height="623" alt="GUI" src="doc/images/gui.png" />
<img width="1673" height="977" alt="GUI" src="doc/images/gui.png" />
While the firmware provides already considerable improvements over the original managed firmware,
the firmware still lacks support for the proprietary loop prevention
@@ -37,7 +37,7 @@ a backup of the original flash content via a SOIC clamp such as also used for BI
backups and can re-install that firmware in case something is wrong. For this no soldering
skills are necessary.
The firmware supports all hardware featues of devices with
The firmware supports all hardware features of devices with
- 4 2.5GBit ports + 2 SFP+ ports
- 5 2.5GBIT + 1 SFP+ port
- 8 2.5GBit + 1 SFP+ port
@@ -131,7 +131,7 @@ rtlplayground-v0.1.0-12c98ba-dirty-LIANGUO_ZX_SWTGW215AS.bin
> [!CAUTION]
> This image can be flashed directly to the chip OR through the firmware update/upgrade
> interface of RTLPlaygound interface
> interface of RTLPlayground interface
## (2) Compiling for OEM running device with management options (web upgrade)
@@ -179,7 +179,7 @@ ghidra see this information about [Ghidra images](doc/ghidra.md).
## (4) Installation through the Web interface (software way)
Managed switches (OEM firmware of RTLplaygroud firmware) can be upgraded via the web interface.
Managed switches (OEM firmware of RTLplayground firmware) can be upgraded via the web interface.
Unmanaged switch cannot be flashed this way (see 5).
Go to "Firmware update" tab, select the correct file.
@@ -190,7 +190,7 @@ Go to "Firmware update" tab, select the correct file.
> [!CAUTION]
> Check one more time that your device matches the machine type before flashing.
> Be shure you have a backup of the original firmware before diving in RTLPlaygroung.
> Be sure you have a backup of the original firmware before diving in RTLPlayground.
Finally, push the Upload File Button and you're done !
@@ -198,7 +198,7 @@ Finally, push the Upload File Button and you're done !
## (5) Flashing the ROM directly (hardware way, but also only way to rescue)
This procedure is the only way to flash unmanaged switches, if the ROM chip is large enough.
This is also the only way to unbrick your device if something went wroong.
This is also the only way to unbrick your device if something went wrong.
> [!IMPORTANT]
> You need a SOIC-8 clip to flash the ROM chip directly onboard.
@@ -218,7 +218,7 @@ This is also the only way to unbrick your device if something went wroong.
- ERASE THE ROM (BLANK) !
- Load the firmware into IMSProg.
- Flash is to the ROM chip.
- Disconect the clip from the ROM chip.
- Disconnect the clip from the ROM chip.
- You're done, ready for the first boot.
## (6) Connecting a serial interface (optional)
@@ -233,7 +233,7 @@ When you power up the switch, the device will perform some examples and provide
## (8) The web-interface
The web-interface can be reached under the [default 192.168.10.247](http://192.168.10.247) unless you
specified an IP adress in the config.txt before compilation.
specified an IP address in the config.txt before compilation.
> [!TIP]
> The default password is `1234`.
@@ -333,17 +333,18 @@ While in compilation part, you might write directly to config.txt file before ma
nano config.txt
```
If you want to modify settings after the flash is done, go to the Advanced Settings tab in System Settings
If you want to modify settings after the flash is done, go to the System tab and find the Startup Configuration.
<img width="1085" height="646" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" />
<img width="1673" height="978" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" />
```
ip xxx.xxx.xxx.xxx = IP adress of the switch
gw yyy.yyy.yyy.yyy = IP adress of the gateway
ip xxx.xxx.xxx.xxx = IP address of the switch
gw yyy.yyy.yyy.yyy = IP address of the gateway
netmask zzz.zzz.zzz.zzz = Network mask of the switch
port x name xxx = Name xxx the port number x
port z 1g = Set 1g speed for port z
igmp on/off = Turn IGMP on or off
session xxxx = Web session timeout in seconds (default 200)
```
[To be continue]
@@ -359,4 +360,5 @@ The following documents give further documentation on specific features of the R
- [SFP+ ports](doc/sfp.md)
- [Trunking aka. port aggregation](doc/trunking.md)
- [VLAN](doc/vlan.md)
- [Storm control](doc/storm_control.md)
- [Modifications and Flash replacement](doc/mods.md)
+9 -10
View File
@@ -21,8 +21,9 @@ extern __code const struct machine machine;
extern __xdata struct machine_runtime machine_detected;
extern __xdata uint8_t sfr_data[4];
extern volatile __xdata uint32_t ticks;
__xdata uint32_t rtl8224_release_tick;
extern __xdata uint8_t tx_seq;
extern __code uint8_t * __code hex;
extern __code const uint8_t * __code const hex;
@@ -240,10 +241,10 @@ void rtl8224_enable(void) __banked
reg_bit_clear(RTL837X_REG_GPIO_32_63_OUTPUT, 4);
// Configure Pin as output
reg_bit_set(RTL837X_REG_GPIO_32_63_DIRECTION, 4);
delay(100);
delay(4);
// Set pin 4 high
reg_bit_set(RTL837X_REG_GPIO_32_63_OUTPUT, 4);
delay(500);
rtl8224_release_tick = ticks;
}
@@ -397,10 +398,12 @@ void setup_i2c(void) __banked
// HW Control register, enable I2C depending on PIN configuration
reg_read_m(RTL837X_PIN_MUX_1);
for (uint8_t sfp = 0; sfp < machine.n_sfp; sfp++) {
const uint8_t scl_bus = i2c_bus_from_scl_pin(machine.sfp_port[sfp].i2c.scl);
const uint8_t sda_bus = i2c_bus_from_sda_pin(machine.sfp_port[sfp].i2c.sda);
uint8_t i2c = machine.sfp_port[sfp].i2c;
uint8_t scl_bus = (i2c >> RTL837X_REG_I2C_SCL_SHIFT) & RTL837X_REG_I2C_SCL_MASK;
uint8_t sda_bus = (i2c >> RTL837X_REG_I2C_SDA_SHIFT) & RTL837X_REG_I2C_SDA_MASK;
print_string("Configuring I2C for SFP idx="); print_byte(sfp); print_string(" SCL="); print_byte(scl_bus); print_string(", SDA="); print_byte(sda_bus); write_char('\n');
switch (scl_bus) {
// `& RTL837X_REG_I2C_SCL_MASK` is needed to silens a compiler warning 110.
switch (scl_bus & RTL837X_REG_I2C_SCL_MASK) {
case 3:
// Bit 5-6 0b10 -> SCL (implies enabled SDA on bus 3)
sfr_mask_data(0, 0x60, 0x40);
@@ -419,8 +422,6 @@ void setup_i2c(void) __banked
sfr_mask_data(0, 0x80, 0x80);
sfr_mask_data(1, 0x01, 0x00);
break;
default:
print_string("Invalid SCL bus number: "); print_byte(scl_bus); write_char('\n');
}
switch (sda_bus) {
@@ -444,8 +445,6 @@ void setup_i2c(void) __banked
// Bit 9-10 0b01 -> SDA
sfr_mask_data(1, 0x06, 0x02);
break;
default:
print_string("Invalid SDA bus number: "); print_byte(sda_bus); write_char('\n');
}
}
reg_write_m(RTL837X_PIN_MUX_1);
+391 -71
View File
@@ -5,6 +5,8 @@
// #define DEBUG
// #define REGDBG 1
#include <8051.h>
#include "cmd_parser.h"
#include "rtl837x_common.h"
#include "rtl837x_port.h"
#include "rtl837x_flash.h"
@@ -14,6 +16,8 @@
#include "rtl837x_stp.h"
#include "rtl837x_igmp.h"
#include "rtl837x_bandwidth.h"
#include "rtl837x_lldp.h"
#include "rtl837x_storm.h"
#include "sfp.h"
#include "dhcp.h"
#include "syslog.h"
@@ -21,26 +25,30 @@
#include "version.h"
#include "machine.h"
#include "rtl837x_leds.h"
#include "phy.h"
#pragma codeseg BANK2
#pragma constseg BANK2
extern __code struct machine machine;
extern __code const struct machine machine;
extern __xdata bool stp_enabled;
extern __code uint8_t log_to_phys_port[9];
extern __code const uint8_t log_to_phys_port[9];
extern __xdata bool lldp_enabled;
extern volatile __xdata uint32_t ticks;
extern volatile __xdata uint8_t sfr_data[4];
extern __code uint8_t * __code greeting;
extern __code uint8_t * __code hex;
extern __code const uint8_t * __code const greeting;
extern __code const uint8_t * __code const hex;
extern __xdata uint8_t flash_buf[FLASH_BUF_SIZE];
extern __xdata struct flash_region_t flash_region;
extern __xdata char passwd[21];
extern __xdata uint16_t session_timeout;
extern __xdata struct dhcp_state dhcp_state;
__xdata uint8_t vlan_names[VLAN_NAMES_SIZE];
@@ -59,7 +67,6 @@ __xdata uint8_t hexvalue[4] = { 0 };
// Buffer for writing to flash 0x1fd000, copy to 0x1fe000
__xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
__xdata uint8_t cmd_available;
__xdata char save_cmd;
@@ -105,7 +112,7 @@ inline uint8_t isnumber(uint8_t l)
return (l <= ('9'-'0'));
}
uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd)
uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd)
{
if (cmd_words_len == 0 || start > (cmd_words_len - 1)) {
return 0;
@@ -426,6 +433,22 @@ err:
return 0;
}
static void cmd_error(__code const char *msg)
{
err_status = ERR_INVALID_ARGUMENT;
print_string("Error: ");
print_string(msg);
}
static uint8_t name_char(uint8_t c)
{
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\')
return '.';
return c;
}
// Sets the management MAC from a "mac <aa:bb:cc:dd:ee:ff>" command (or prints
// the current one with a bare "mac"). Refuses blank/multicast/locally
// administered/all-zero-OUI addresses so the running MAC stays reachable, and
@@ -441,11 +464,11 @@ void parse_mac_cmd(void)
return;
}
if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1])) {
print_string("Error: mac [<aa:bb:cc:dd:ee:ff>]\n");
cmd_error("mac [<aa:bb:cc:dd:ee:ff>]\n");
return;
}
if ((mac_parse_result[0] & 0x03) || ((mac_parse_result[0] | mac_parse_result[1] | mac_parse_result[2]) == 0)) {
print_string("refusing: must be unicast, globally administered\n");
cmd_error("the MAC must be unicast and globally administered\n");
return;
}
if (memcmp(mac_parse_result, uip_ethaddr.addr, 6) == 0) {
@@ -523,7 +546,7 @@ void parse_lag(void)
port_lag_members_set(group, members);
return;
err:
print_string("Error: lag (show | <1-4> (d | <port>...))\n");
cmd_error("lag (show | <1-4> (d | <port>...))\n");
}
@@ -565,7 +588,7 @@ void parse_lag_hash(void)
port_lag_hash_set(group, hash);
return;
err:
print_string("Error: laghash <1-4> [smac|dmac|sip|dip|sport|dport]\n");
cmd_error("laghash <1-4> [smac|dmac|sip|dip|sport|dport]\n");
}
@@ -619,7 +642,7 @@ void parse_vlan(void)
*vlan_str++ = hex[(vlan_settings.vlan >> 4) & 0xf] ;
*vlan_str++ = hex[vlan_settings.vlan & 0xf];
while(!cmd_is_space_or_nul(pos)) {
uint8_t c = cmd_buffer[pos++];
uint8_t c = name_char(cmd_buffer[pos++]);
write_char(c);
*vlan_str++ = c;
}
@@ -663,7 +686,7 @@ void parse_vlan(void)
}
return;
err:
print_string("Error: vlan (<vlan-id>|show) [port][t]...\n");
cmd_error("vlan (<vlan-id>|show) [port][t]...\n");
}
@@ -714,7 +737,7 @@ void parse_isolate(void)
return;
err:
print_string("Error: isolate <port> [show|off] [port]...\n");
cmd_error("isolate <port> [show|off] [port]...\n");
}
@@ -751,7 +774,7 @@ void parse_ingress(void)
// Setting mode for all ports at once
for (log_port = machine.min_port; log_port <= machine.max_port; log_port++) {
if (!port_ingress_filter(log_port, mode)) {
print_string("Error setting ingress filter for port "); print_phys_port(log_port); write_char('\n');
cmd_error("cannot set the ingress filter for port "); print_phys_port(log_port); write_char('\n');
return;
}
print_string("All ports ingress filter set to: ");
@@ -762,18 +785,18 @@ void parse_ingress(void)
idx = cmd_words_b[w];
uint8_t ret = cmd_parse_port(idx);
if (ret != 1) {
print_string("Invalid physical port number\n");
cmd_error("Invalid physical port number\n");
continue;
}
log_port = atoi_results_u8;
idx += ret;
if (!vlan_ingress_mode_parse(cmd_buffer[idx++], &mode) || !cmd_is_space_or_nul(idx)) {
print_string("Invalid ingress mode for port "); print_phys_port(log_port); print_string(" in ingress command\n");
cmd_error("Invalid ingress mode for port "); print_phys_port(log_port); print_string(" in ingress command\n");
goto err;
}
if (!port_ingress_filter(log_port, mode)) {
print_string("Error setting ingress filter for port "); print_phys_port(log_port); write_char('\n');
cmd_error("cannot set the ingress filter for port "); print_phys_port(log_port); write_char('\n');
return;
}
print_string("Port "); print_phys_port(log_port);
@@ -783,7 +806,7 @@ void parse_ingress(void)
}
return;
err:
print_string("Error: ingress [p]<u/t/a>...\n");
cmd_error("ingress [p]<u/t/a>...\n");
}
void parse_mirror(void)
@@ -848,7 +871,7 @@ void parse_mirror(void)
return;
err:
print_string("Port/command missing: mirror [status/off/<mirroring port> [port][t/r]]...\n");
cmd_error("Port/command missing: mirror [status/off/<mirroring port> [port][t/r]]...\n");
return;
}
@@ -856,15 +879,16 @@ err:
void parse_port(void)
{
if (cmd_words_len < 3) {
print_string("\nUsage:" \
cmd_error("\nUsage:" \
"\nport <port> [show|on|off]" \
"\nport <port> [10m|100m|1g|2g5|duplex] [half|full]" \
"\nport <port> lldp [block|permit]" \
"\nport <port> name [custom port name]\n");
return;
}
if (cmd_parse_port_separator(cmd_words_b[1]) == 0) {
print_string("Invalid port number\n");
cmd_error("Invalid port number\n");
return;
}
phy_settings.port = atoi_results_u8;
@@ -877,17 +901,28 @@ void parse_port(void)
print_string_x(port_names[phy_settings.port]);
if (!machine.is_sfp[phy_settings.port]) {
phy_show(phy_settings.port);
} else {
write_char('\n');
}
port_media_show(phy_settings.port);
} else if (cmd_compare(2, "name")) {
uint8_t i = 0;
while ( (i < PORT_NAME_SIZE-1) && (cmd_buffer[cmd_words_b[3] + i] != NUL) ) {
port_names[phy_settings.port][i] = cmd_buffer[cmd_words_b[3] + i];
while ( cmd_words_len > 3 && (i < PORT_NAME_SIZE-1) && (cmd_buffer[cmd_words_b[3] + i] != NUL) ) {
port_names[phy_settings.port][i] = name_char(cmd_buffer[cmd_words_b[3] + i]);
i++;
}
port_names[phy_settings.port][i] = NUL;
print_string("\nName set to: \"");
print_string_x(port_names[phy_settings.port]);
print_string("\"\n");
} else if (cmd_compare(2, "lldp")) {
if (cmd_compare(3, "block")){
lldp_logical_port_status &= ~((uint16_t)1 << phy_settings.port);
} else if (cmd_compare(3, "permit")) {
lldp_logical_port_status |= (uint16_t)1 << phy_settings.port;
} else {
print_string ("Unknown port <port> lldp [block|permit] command\n");
}
} else if (machine.is_sfp[phy_settings.port]) {
print_string(" is SFP no PHY information available.\n");
} else if (cmd_compare(2, "10m")) {
@@ -942,7 +977,7 @@ void parse_port(void)
phy_settings.duplex = PHY_DUPLEX_HALF;
phy_set_duplex();
} else {
print_string("Unknown port command\n");
cmd_error("Unknown port command\n");
}
}
@@ -970,7 +1005,7 @@ void parse_mtu(void)
print_byte(p);
if (atoi_short(cmd_words_b[2]) == 0 || atoi_results_short < 64 || atoi_results_short > 0x3fff) {
print_string("MTU must be 64..16383\n");
cmd_error("MTU must be 64..16383\n");
return;
}
REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (atoi_results_short >> 10) & 0xf, (atoi_results_short >> 2) & 0xff,
@@ -979,7 +1014,7 @@ void parse_mtu(void)
return;
err:
print_string("mtu [port] [size]\n");
cmd_error("mtu [port] [size]\n");
return;
}
@@ -1036,11 +1071,11 @@ void parse_sfp(void)
idx += ret;
slot = atoi_results_u8 - 1;
if (ret == 0 || !cmd_is_space(idx) || slot > 1) {
print_string("Illegal SFP slot number\n");
cmd_error("Illegal SFP slot number\n");
return;
}
if (slot >= machine.n_sfp) {
print_string("SFP slot not present\n");
cmd_error("SFP slot not present\n");
return;
}
@@ -1066,7 +1101,7 @@ void parse_sfp(void)
handle_sfp();
return;
err:
print_string("\nUsage:\n\tsfp\n\tsfp [1|2] [1g|2g5|10g]\n");
cmd_error("\nUsage:\n\tsfp\n\tsfp [1|2] [1g|2g5|10g]\n");
}
@@ -1098,7 +1133,7 @@ void parse_regget(void)
return;
err:
print_string("usage: regget <hexvalue>\n\tlike: regget 0BB0 or regget 0c");
cmd_error("regget <hexvalue>\n\tlike: regget 0BB0 or regget 0c");
return;
}
@@ -1145,7 +1180,33 @@ void parse_regset(void)
return;
err:
print_string("usage: regset <hexvalue> <hexvalue>\n\tlike regset 0b abcd1234.");
cmd_error("regset <hexvalue> <hexvalue>\n\tlike regset 0b abcd1234.");
}
static void print_temperature(uint16_t reg)
{
int16_t v;
reg_read(reg);
v = SFR_DATA_U16;
if (v < 0) {
write_char('-');
v = -v;
}
itoa(v >> 7);
write_char('.');
itoa(((v & 0x7f) * 10) >> 7);
print_string(" C\n");
}
void parse_temp(void)
{
print_string("\nChip temperature: ");
print_temperature(RTL837X_TM_RESULT);
print_string("At power-on: ");
print_temperature(RTL837X_TM_RESULT_POWERON);
}
@@ -1188,7 +1249,7 @@ void parse_sdsget(void)
return;
err:
print_string("usage: sdsget <sds-id> <hex:page> <hex:reg>\n");
cmd_error("sdsget <sds-id> <hex:page> <hex:reg>\n");
return;
}
@@ -1244,7 +1305,7 @@ void parse_sdsset(void)
return;
err:
print_string("usage: sdsset <sds-id> <hex:page> <hex:reg> <hex:val>\n");
cmd_error("sdsset <sds-id> <hex:page> <hex:reg> <hex:val>\n");
return;
}
@@ -1293,7 +1354,7 @@ void parse_phyget(void)
return;
err:
print_string("usage: phyget <phy-id> <dev-id> <hex:reg>\n");
cmd_error("phyget <phy-id> <dev-id> <hex:reg>\n");
return;
}
@@ -1353,7 +1414,7 @@ void parse_physet(void)
return;
err:
print_string("usage: physet <phy-id> <dev-id> <hex:reg> <hex:val>\n");
cmd_error("physet <phy-id> <dev-id> <hex:reg> <hex:val>\n");
return;
}
@@ -1388,7 +1449,7 @@ void parse_passwd(void)
passwd[j] = NUL;
return;
}
print_string("Missing password\n");
cmd_error("Missing password\n");
}
@@ -1411,8 +1472,8 @@ void parse_eee(void)
speed_word = 2;
} else if (cmd_is_space_or_nul(idx)) {
// Word 2 is a port number
if (cmd_parse_port_separator(idx) == 0) {
print_string("Speed word invalid, use: [100m|1g|2g5]\n");
if (cmd_parse_port_separator(cmd_words_b[2]) == 0) {
cmd_error("Speed word invalid, use: [100m|1g|2g5]\n");
return;
}
port = atoi_results_u8;
@@ -1431,7 +1492,7 @@ void parse_eee(void)
speed = EEE_2G5;
else
{
print_string("Speed word invalid, use: [100m|1g|2g5]\n");
cmd_error("Speed word invalid, use: [100m|1g|2g5]\n");
return;
}
}
@@ -1451,7 +1512,7 @@ void parse_eee(void)
else
port_eee_status_all();
} else {
print_string("eee [on|off|status] [port] [100m|1g|2g5]\n");
cmd_error("eee [on|off|status] [port] [100m|1g|2g5]\n");
}
}
@@ -1522,7 +1583,58 @@ void parse_bw(void)
return;
err:
print_string("usage: bw [in|out|status] <port> [<hexvalue>|off|drop|fc]\n");
cmd_error("bw [in|out|status] <port> [<hexvalue>|off|drop|fc]\n");
}
static __code const char * __code const storm_types[STORM_TYPES] = { "bcast", "mcast", "ucast", "umcast" };
void parse_storm(void)
{
uint8_t port, type, d;
__xdata uint32_t rate;
__xdata uint8_t *p;
if (cmd_words_len == 1)
return storm_show();
if (cmd_words_len < 4 || !cmd_parse_port_separator(cmd_words_b[1]))
goto err;
port = atoi_results_u8;
type = 0;
while (!cmd_compare(2, storm_types[type])) {
if (++type == STORM_TYPES)
goto err;
}
if (cmd_words_len == 4 && cmd_compare(3, "off"))
return storm_off(port, type);
if (cmd_words_len != 5)
goto err;
rate = 0;
p = &cmd_buffer[cmd_words_b[3]];
do {
d = *p++ - '0';
if (d > 9 || rate > 99999999)
goto err;
rate = (rate << 3) + (rate << 1) + d;
} while (*p != ' ' && *p != NUL);
if (cmd_compare(4, "pps")) {
if (!rate || rate > 0xfffff)
goto err;
storm_set(port, type, rate, 1);
} else if (cmd_compare(4, "kbps")) {
if (!rate || rate > 10000000)
goto err;
storm_set(port, type, rate, 0);
} else {
goto err;
}
return;
err:
print_string("usage: storm [<port> bcast|mcast|ucast|umcast <rate> pps|kbps|off]\n");
}
void parse_syslog(void)
@@ -1560,7 +1672,7 @@ void parse_syslog(void)
if (was_enabled)
syslog_start();
} else {
print_string("Invalid IP address\n");
cmd_error("Invalid IP address\n");
}
} else if (cmd_compare(1, "port")) {
if (cmd_words_len < 3) { // no additional argument -> print current port
@@ -1571,7 +1683,7 @@ void parse_syslog(void)
/* atoi_short() returns zero for no digits and for a value past
* 65535; port zero is not a port either, so both tests are needed. */
if (!atoi_short(cmd_words_b[2]) || !atoi_results_short) {
print_string("Invalid port\n");
cmd_error("Invalid port\n");
return;
}
/* The port is frozen into the connection by uip_udp_new(), so
@@ -1587,12 +1699,31 @@ void parse_syslog(void)
}
else
{
print_string("Error: syslog [on|off|ip [ip-address]|port [number]]\n");
print_string(" on/off enables or disables syslog, ip sets the syslog server IP address,\n");
print_string(" port sets the destination UDP port (default 514)\n");
cmd_error("syslog [on|off|ip [ip-address]|port [number]]\n"
" on/off enables or disables syslog, ip sets the syslog server IP address,\n"
" port sets the destination UDP port (default 514)\n");
}
}
void parse_session(void)
{
if (cmd_words_len >= 2) {
/* atoi_short() returns zero for no digits and for a value past
* 65535; a zero-second timeout is not useful either, so both
* tests are needed. */
if (!atoi_short(cmd_words_b[1]) || !atoi_results_short) {
print_string("Must be 1-65535 s\n");
return;
}
session_timeout = atoi_results_short;
}
/* Also the confirmation after setting: one message for both. */
print_string("Session timeout: ");
itoa_short(session_timeout);
print_string(" s\n");
}
// Parse command into words
// cmd_words_len contains the number of words found.
// cmd_words_b[] contains only start of a word offset.
@@ -1757,7 +1888,7 @@ void cmd_parser(void) __banked
print_string("Setting ip: ");
print_ip(ip); write_char('\n');
} else {
print_string("Invalid IP address\n" \
cmd_error("Invalid IP address\n" \
"Error: ip [<ip-address>|dhcp]\n" \
" The dhcp option enables the dhcp client, calling ip without options prints the current IP\n" \
" Calling with a valid IP address will stop any ongoing dhcp client and set the IP address\n");
@@ -1773,7 +1904,7 @@ void cmd_parser(void) __banked
print_string("Setting gw: ");
print_ip(ip); write_char('\n');
} else {
print_string("Invalid IP address\n" \
cmd_error("Invalid IP address\n" \
"Error: gw <ip-address>\n");
}
}
@@ -1787,7 +1918,7 @@ void cmd_parser(void) __banked
print_string("Setting netmask: ");
print_ip(ip); write_char('\n');
} else {
print_string("Invalid IP address\n");
cmd_error("Invalid IP address\n");
}
}
write_char('\n');
@@ -1803,8 +1934,9 @@ void cmd_parser(void) __banked
igmp_setup();
else if (cmd_compare(1, "show"))
igmp_show();
else
print_string("Error: igmp on|off|show\n");
else {
cmd_error("igmp on|off|show\n");
}
} else if (cmd_compare(0, "mac")) {
parse_mac_cmd();
} else if (cmd_compare(0, "hostname")) {
@@ -1822,22 +1954,25 @@ void cmd_parser(void) __banked
uint8_t c = *hp++;
if (c == NUL || c == '\r' || c == '\n')
break;
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\')
c = '.';
*dst++ = c;
*dst++ = name_char(c);
}
*dst = NUL;
} else {
print_string("Error: hostname [name] - the name must not contain spaces\n");
cmd_error("hostname [name] - the name must not contain spaces\n");
}
} else if (cmd_compare(0, "stp")) {
stp_parse();
#ifdef HEALTH
} else if (cmd_compare(0, "health")) {
health_show();
#endif
} else if (cmd_compare(0, "pvid")) {
if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
port_pvid_set(atoi_results_u8, atoi_results_short);
else
print_string("Error: pvid <port> <1-4094>\n");
else {
cmd_error("pvid <port> <1-4094>\n");
}
} else if (cmd_compare(0, "vlan")) {
parse_vlan();
} else if (cmd_compare(0, "isolate")) {
@@ -1853,6 +1988,8 @@ void cmd_parser(void) __banked
write_char('\n');
} else if (cmd_compare(0, "gpio")) {
print_gpio_status();
} else if (cmd_compare(0, "leds")) {
leds_dump();
} else if (cmd_compare(0, "regget")) {
parse_regget();
} else if (cmd_compare(0, "regset")) {
@@ -1873,6 +2010,10 @@ void cmd_parser(void) __banked
parse_eee();
} else if (cmd_compare(0, "bw")) {
parse_bw();
} else if (cmd_compare(0, "temp")) {
parse_temp();
} else if (cmd_compare(0, "storm")) {
parse_storm();
} else if (cmd_compare(0, "version")) {
print_sw_version();
} else if (cmd_compare(0, "time")) {
@@ -1895,13 +2036,23 @@ void cmd_parser(void) __banked
}
} else if (cmd_compare(0, "ingress")) {
parse_ingress();
} else if (cmd_compare(0, "session")) {
parse_session();
} else if (cmd_compare(0, "lldp")) {
if (cmd_compare(1, "on")) {
print_string("LLDP enabled\n");
lldp_enabled = 1;
} else {
print_string("LLDP disabled\n");
lldp_enabled = 0;
}
}
else {
print_string("Unknown command\n");
cmd_error("Unknown command\n");
}
if (save_cmd && cmd_words_len) {
if (save_cmd && cmd_words_len && err_status == ERR_OK) {
// Find end of the cmd-buffer, looking for the NUL-byte.
uint8_t i = cmd_words_b[cmd_words_len - 1];
do {
@@ -1946,6 +2097,7 @@ void execute_config(void) __banked
save_cmd = 0;
uint8_t cmd_idx = 0;
uint8_t skipping = 0;
do {
flash_region.addr = pos;
flash_region.len = FLASH_READ_BURST_SIZE;
@@ -1956,20 +2108,23 @@ void execute_config(void) __banked
do {
if (cmd_idx >= (CMD_BUF_SIZE - 1)) {
cmd_buffer[cmd_idx] = NUL;
print_string("ERROR: Command too long: ");
print_string("ERROR: Command too long, skipped: ");
print_string_x(cmd_buffer);
write_char('\n');
err_status = ERR_CMD_TOO_LONG;
goto config_done;
cmd_idx = 0;
skipping = 1;
}
c = flash_buf[cfg_idx++];
if (c == 0 || c == '\n') {
cmd_buffer[cmd_idx] = NUL;
if (cmd_idx) {
cmd_tokenize();
if (err_status != ERR_OK)
goto config_done;
cmd_parser();
if (skipping) {
skipping = 0;
} else {
cmd_buffer[cmd_idx] = NUL;
if (cmd_idx) {
cmd_tokenize();
if (err_status == ERR_OK)
cmd_parser();
}
}
if (c == 0)
goto config_done;
@@ -1977,8 +2132,10 @@ void execute_config(void) __banked
continue;
}
cmd_buffer[cmd_idx] = c;
cmd_idx++;
if (!skipping) {
cmd_buffer[cmd_idx] = c;
cmd_idx++;
}
} while (cfg_idx);
pages_left--;
@@ -1995,6 +2152,7 @@ config_done:
// If a command is too long or can't be tokenized, remaining commands are not executed
// Returns the status via `err_status`-variable.
void execute_commands(__xdata uint8_t *p) __banked {
__xdata uint8_t failed = ERR_OK;
err_status = ERR_OK;
uint8_t cmd_idx = 0;
while (1) {
@@ -2005,9 +2163,13 @@ void execute_commands(__xdata uint8_t *p) __banked {
if (err_status != ERR_OK)
return;
cmd_parser();
if (failed == ERR_OK)
failed = err_status;
}
if (*p == 0)
if (*p == 0) {
err_status = failed;
return;
}
cmd_idx = 0;
} else {
if (cmd_idx < (CMD_BUF_SIZE - 1)) {
@@ -2024,3 +2186,161 @@ void execute_commands(__xdata uint8_t *p) __banked {
p++;
};
}
#ifdef HEALTH
/*
* Health instrumentation. The counters live in xdata and are fed from the
* main loop through the banked entry points below; the "health" command
* prints everything in one go. All figures are raw hex, diffing two dumps
* gives the rates. Phase times are in system ticks (SYS_TICK_HZ = 200,
* 5 ms each); a phase counts as slow when it spans 2 or more ticks.
*/
extern __xdata uint16_t len_left; /* httpd: bytes still to send of the current file */
extern __xdata uint8_t entry; /* httpd: index of the file being sent */
static __xdata uint32_t health_loops;
__xdata uint16_t health_rx_frames;
static __xdata uint16_t ph_last_t;
static __xdata uint8_t ph_max[HEALTH_PHASES];
static __xdata uint16_t ph_slow[HEALTH_PHASES];
/* The ISR increments ticks between the two byte reads of a 16-bit load, so
* read until two loads agree. */
static uint16_t ticks_lo(void)
{
uint16_t a, b;
a = (uint16_t)ticks;
do {
b = a;
a = (uint16_t)ticks;
} while (a != b);
return a;
}
void health_loop_start(void) __banked
{
health_loops++;
ph_last_t = ticks_lo();
}
void health_phase(uint8_t id) __banked
{
__xdata uint16_t now = ticks_lo();
__xdata uint16_t d = now - ph_last_t;
ph_last_t = now;
if (d > 0xff)
d = 0xff;
if ((uint8_t)d > ph_max[id])
ph_max[id] = d;
if (d >= 2)
ph_slow[id]++;
}
/* Fill the stack area above the current frame with a pattern; the dump scans
* for how much of it survived. The 8051 stack grows upward, so everything
* above SP is free at this point. Interrupts are held off so an ISR does not
* push into the area mid-paint. */
void health_stack_paint(void) __banked
{
__xdata uint8_t from = SP + 4;
__xdata uint8_t ea = EA;
EA = 0;
for (uint8_t i = 0xff; i >= from; i--)
*(__idata uint8_t *)i = 0xa5;
EA = ea;
}
static void ph_line(__code const char *name, uint8_t id)
{
print_string(name);
print_string(" max ");
print_byte(ph_max[id]);
print_string(" slow ");
print_short(ph_slow[id]);
write_char('\n');
}
void health_show(void) __banked
{
__xdata uint8_t i;
print_string("up ");
reg_read_m(RTL837X_REG_SEC_COUNTER);
print_byte(sfr_data[0]); print_byte(sfr_data[1]);
print_byte(sfr_data[2]); print_byte(sfr_data[3]);
print_string(" ticks ");
print_long(ticks);
print_string(" loops ");
print_long(health_loops);
print_string(" rx ");
print_short(health_rx_frames);
write_char('\n');
/* Phase timing: max duration seen (ticks of 5 ms) and slow-pass count */
ph_line("link", HEALTH_PH_LINK);
ph_line("sfp ", HEALTH_PH_SFP);
ph_line("rx ", HEALTH_PH_RX);
ph_line("tx ", HEALTH_PH_TX);
ph_line("stp ", HEALTH_PH_STP);
ph_line("cmd ", HEALTH_PH_CMD);
/* Stack: current depth plus how much of the painted area is untouched */
print_string("sp ");
print_byte(SP);
print_string(" untouched ");
i = 0;
while (*(__idata uint8_t *)(0xff - i) == 0xa5 && i < 0x86)
i++;
print_byte(i);
write_char('\n');
/* The TCP connection slot. One on this configuration; a build with more
* has to say which slot each line is, so make that a visible decision. */
#if UIP_CONNS != 1
#error "health: the tcp dump prints a single slot, extend it for UIP_CONNS > 1"
#endif
i = 0;
{
print_string("tcp ");
print_string("st ");
print_byte(uip_conns[i].tcpstateflags);
print_string(" tmr ");
print_byte(uip_conns[i].timer);
print_string(" rtx ");
print_byte(uip_conns[i].nrtx);
print_string(" rto ");
print_byte(uip_conns[i].rto);
print_string(" len ");
print_short(uip_conns[i].len);
print_string(" mss ");
print_short(uip_conns[i].mss);
write_char('\n');
print_string(" lport ");
print_short(HTONS(uip_conns[i].lport));
print_string(" rport ");
print_short(HTONS(uip_conns[i].rport));
print_string(" rip ");
print_short(HTONS(uip_conns[i].ripaddr[0]));
write_char(' ');
print_short(HTONS(uip_conns[i].ripaddr[1]));
write_char('\n');
}
/* httpd transfer state and the protocol flags */
print_string("httpd left ");
print_short(len_left);
print_string(" entry ");
print_byte(entry);
print_string(" stp ");
print_byte(stp_enabled);
print_string(" mvlan ");
print_short(management_vlan);
write_char('\n');
}
#endif
+28 -1
View File
@@ -6,9 +6,36 @@
#include "rtl837x_common.h"
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
extern __xdata uint8_t cmd_available;
extern __bit __at(0x03) cmd_available;
extern __xdata uint8_t err_status;
/* Health instrumentation, built with HEALTH=1. Without it the hooks below
* compile to nothing and the "health" command does not exist. */
#ifdef HEALTH
/* Phase ids, in main-loop order */
#define HEALTH_PH_LINK 0
#define HEALTH_PH_SFP 1
#define HEALTH_PH_RX 2
#define HEALTH_PH_TX 3
#define HEALTH_PH_STP 4
#define HEALTH_PH_CMD 5
#define HEALTH_PHASES 6
#define HEALTH_LLDP 7
extern __xdata uint16_t health_rx_frames;
#define health_rx_frame() health_rx_frames++
void health_loop_start(void) __banked; /* once per idle() pass, resets the phase baseline */
void health_phase(uint8_t id) __banked; /* after each phase; accounts ticks since last checkpoint */
void health_stack_paint(void) __banked; /* paint the unused stack area once at boot */
void health_show(void) __banked; /* "health" CLI dump */
#else
#define health_rx_frame()
#define health_loop_start()
#define health_phase(id)
#define health_stack_paint()
#endif
void cmd_tokenize(void) __banked;
void cmd_parser(void) __banked;
void execute_config(void) __banked;
+18 -20
View File
@@ -50,7 +50,7 @@ __xdata uip_ipaddr_t server;
#define DHCP_PARAM_DNS 6
#define DHCP_END 255
#pragma codeseg BANK2
#pragma codeseg BANK3
#pragma constseg BANK2
struct dhcp_pkt {
@@ -139,7 +139,6 @@ void dhcp_addopt_request_ip(void)
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[1];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[2];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[3];
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4);
}
@@ -151,13 +150,11 @@ void dhcp_addopt_server_id(void)
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[1];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[2];
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[3];
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4);
}
void dhcp_send_discover(void)
{
print_string("dhcp_send_discover called\n");
dhcp_prepare_request();
dhcp_state.opt_ptr = 0;
@@ -193,7 +190,6 @@ void dhcp_send_discover(void)
void dhcp_send_request(void)
{
print_string("dhcp_send_request called\n");
dhcp_prepare_request();
dhcp_state.opt_ptr = 0;
@@ -232,19 +228,21 @@ void ip_opt(__xdata uint8_t * ip)
{
dhcp_state.opt_ptr++;
uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
if (len >= 4)
memcpy(ip, &DHCP_OPT[dhcp_state.opt_ptr], 4);
// There may be more than one IP option, such as 2 DNS servers advertised
dhcp_state.opt_ptr += len - 4;
dhcp_state.opt_ptr += len;
}
void long_opt(void)
uint8_t long_opt(void)
{
dhcp_state.opt_ptr++;
dhcp_state.opt_ptr++;
uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++];
if (len < 4) {
dhcp_state.opt_ptr += len;
return 0;
}
long_value = DHCP_OPT[dhcp_state.opt_ptr++];
long_value <<= 8;
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
@@ -252,6 +250,8 @@ void long_opt(void)
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
long_value <<= 8;
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
dhcp_state.opt_ptr += len - 4;
return 1;
}
@@ -275,16 +275,16 @@ void parse_opts(void)
ip_opt(&dhcp_state.broadcast[0]);
break;
case DHCP_LEASE:
long_opt();
dhcp_state.lease = long_value;
if (long_opt())
dhcp_state.lease = long_value;
break;
case DHCP_REBIND:
long_opt();
dhcp_state.rebind = long_value;
if (long_opt())
dhcp_state.rebind = long_value;
break;
case DHCP_RENEWAL:
long_opt();
dhcp_state.renewal = long_value;
if (long_opt())
dhcp_state.renewal = long_value;
break;
case DHCP_END:
break;
@@ -376,13 +376,11 @@ void dhcp_start(void) __banked
*tid++ = SFR_DATA_8;
*tid = SFR_DATA_0;
dhcp_state.state = DHCP_START;
print_string("dhcp_start done\n");
}
void dhcp_stop(void) __banked
{
print_string("dhcp_stop called\n");
uip_udp_remove(dhcp_state.conn);
dhcp_state.state = DHCP_OFF;
}
+40
View File
@@ -0,0 +1,40 @@
### 2M-PCB23-V3.2
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| keepLINK | KP-9000-9XHML-X | Yes| 2M-PCB23-V3.2 | 2M (Winbond 25Q) | |
The original web interface reports this board as hardware version V3.2
(original firmware V100.9.25, January 2026).
## PCB
<img src="photos/2M-PCB23-V3.2-managed/2M-PCB23-V3.2-top.jpeg" width="300" />
## Compared to V3.1
The component placement matches [2M-PCB23-V3.1](2M-PCB23-V3_1.md): same
positions for the flash (U8), the T7/T8/T9/T10 headers, the SFP cage and its
LED, and the unpopulated U7/U9 footprints. Build with
`MACHINE=KP_9000_9XHML_X_V3_2`, which uses the V3.1 machine definition and
only differs in the reported name.
## Tested
Installed from the original firmware through its web interface with
`installer/output/rtlplayground_oem_upgrade.bin`, then:
- RJ45 ports link at 2.5G; LEDs: orange at 2.5G, green below
- SFP port: module detected with vendor/model/serial and DOM readings
(temperature, Vcc, TX bias, TX/RX power), link up at 10G
- factory MAC read from flash (0x1FC000)
- reset button (GPIO48): held for more than 10 s it restores the default config
## Flash access
Reading the flash in-circuit with a SOIC-8 clip did not work: the chip ID
comes back as `EF FF FF` (only the manufacturer byte is right) and reads
return 0xff, the same as on a V3.1 board. The switch chip is powered through
the clip and keeps driving the SPI lines. Make sure a way back is acceptable
before installing, or hold the switch chip in reset while reading.
+70
View File
@@ -0,0 +1,70 @@
# F7008-2.5
Documentation for the unmanaged 8 + 1 switch sold as `7018-2.5G` by Guangnianwei
(光年微), board marking `F7008-2.5`, PCB `F7008-2.5GPOE-V1.2`.
### Brands
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|-------|------|---------|-----|-------|----------|
| Guangnianwei (光年微) | 7018-2.5G | No | F7008-2.5GPOE-V1.2 | W25Q64JVSSIQ | 8373N + 8224N |
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP+ port with 1G/2.5G/10G modules
- LEDs of the eight RJ45 ports
### Not verified
- The LED pads for the SFP+ cage are not mapped yet. The SFP+ lamps are left
undriven, so an inserted module lights nothing.
- The UART header footprint is present but unpopulated, and no boot log could be
captured from it, so the baud rate is unknown. There are most likely
unpopulated 0-Ohm resistors next to the header; bridging those should bring
the console to life.
### Build target
```
make MACHINE=F7008_2_5
```
### LED wiring
Measured pad to lamp map (`led_mux` index -> lamp):
| Port | green lamp | yellow lamp |
|------|-----------|-------------|
| 1 | pad 0 | pad 1 |
| 2 | pad 2 | pad 3 |
| 3 | pad 4 | pad 5 |
| 4 | pad 6 | pad 8 |
| 5 | pad 9 | pad 10 |
| 6 | pad 12 | pad 15 |
| 7 | pad 18 | pad 21 |
| 8 | pad 24 | pad 25 |
Pads 7, 11, 13, 14, 16, 17, 19, 20, 22, 23, 26 and 27 do not drive an RJ45 lamp.
The front panel is silkscreened `2.5G: Green / 10/100M: Yellow`, and the target
follows it: the green lamp is on while the port is linked at 2.5G, the yellow
lamp blinks while data is flowing.
### PCB overview
The board is marked `F7008-2.5GPOE-V1.2`.
Top side:
<img src="photos/F7008-2.5/PCB-top.jpg" width="600" />
Bottom side:
<img src="photos/F7008-2.5/PCB-bottom.jpg" width="600" />
### Hardware notes
- 4 x `RT4802SNE` magnetics
- Unpopulated UART header footprint, next to the magnetics
- Power input 12V 1A, 180 x 84 x 29 mm metal case
+75
View File
@@ -0,0 +1,75 @@
# Hasivo S1100WP-8GT-1SX-SE
RTL8373-based 8×2.5G PoE + 1×SFP switch.
### Label specifications
- **Manufacturer**: Hasivo
- **Model**: S1100WP-8GT-1SX-SE
- **Ports**:
- 8 × RJ45: 10/100/1000/2500 Mbps with PoE
- 1 × SFP: 1G / 2.5G / 10G
- **Power input**: DC 52V 2.5A
- **Stock firmware defaults**: IP 192.168.0.1, user/password `admin`/`admin`
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps (PoE is not configurable via RTLPlayground)
- SFP port supporting 1G, 2.5G and 10G modules
- LEDs: green (2.5G) and amber (1G/100M/10M) per copper port; combined link/act on the SFP port
### Photos
Front panel:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/front.jpg" width="600" />
Label:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/label.jpg" width="600" />
### PCB overview
Top side:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_top.jpg" width="600" />
Bottom side:
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_bottom.jpg" width="600" />
### Port layout
| Logical port | Physical port | Type |
|--------------|---------------|--------|
| 1-8 | 1-8 | Copper |
| 9 | 9 | SFP |
### LED configuration
Copper ports use LED SET0, the SFP port uses LED SET1.
| SET | LED0 | LED2 |
|------|-----------------------------------------------|------------------------------------------------------|
| SET0 | Green — lights on 2.5G link with activity | Amber — lights on 1G / 100M / 10M link with activity |
| SET1 | All speeds — lights on any link with activity | — |
### SFP GPIO assignments
| SFP (logical 9) | pin_detect (ModAbs) | pin_los | pin_tx_disable | SerDes | I2C SDA | I2C SCL |
|-----------------|---------------------|---------|----------------|--------|-----------------|---------------------|
| SFP | GPIO30_ACL_BIT3_EN | GPIO37 | GPIO_NA | SDS1 | GPIO39_I2C_SDA4 | GPIO40_I2C_SCL3_MDC1 |
### Machine configuration
- `reset_pin`: GPIO54_ACL_BIT2_EN
- High LEDs (pads 27-29): mux = `LED_27 | LED_28_SYS | LED_29`, enable = `LED_28_SYS | LED_29`
### PoE
The 8 copper ports support PoE powered by a DC 52V 2.5A input. Power management is handled by two HS104PTI chipsets over I2C (GPIO47_I2C_SDA0, GPIO46_I2C_SCL0):
* Chip 1 (I2C address 0x1A): 2.5G Ports 1–4
* Chip 2 (I2C address 0x2A): 2.5G Ports 5–8
Note: The current implementation does not yet support PoE management. Consequently, PoE currently operates in standalone mode without software control.
+64
View File
@@ -0,0 +1,64 @@
# Lianguo HYWS-SGT0108S
Following is documentation for unmanaged switch marked as `HYWS-SGT0108S`.
Original software is running UART on 9600 baud rate. Output is very minimal.
Using SPI clamp in-board is the only method for initial installation and update.
Stock flash chip is 512KB in size. Consider replacing for update through Web UI.
- Header for uart is clearly identified.
- The red LED act as a powered-on LED.
- There is no SYS LED.
- There is a footprint for a Reset button. Unpopulated. Unclear if routed back to RTL8373.
### Label specifications
- **Name**: 2.5G Ethernet Switch 8+1
- **Model**: HYWS-SGT0108S
- **Ports**:
- 8 × RJ45: 10/100/1000/2500 Mbps
- 1 × SFP: 1000 / 2500 / 10000 Mbps
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP port supporting 1G, 2.5G and 10G modules
- RJ45 LEDs (Blue+White)
- SFP Single LED (Blue)
### Assembled unit
Top side
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-top.jpg" width="300" />
Front
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-front.jpg" width="300" />
Bottom
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-bottom.jpg" width="300" />
### PCB overview
**Board markings**
- Top silkscreen: 2G5F_20G_V1.01 / 2023-09-28
Top side
<img src="photos/HYWS-SGT0108S-unmanaged\PCB-top.jpg" width="300" />
Bottom
<img src="photos/HYWS-SGT0108S-unmanaged\PCB-bot.jpg" width="300" />
Original SPI Flash Chip
<img src="photos/HYWS-SGT0108S-unmanaged\og_spi_flash.jpg" width="300" />
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+122
View File
@@ -0,0 +1,122 @@
# ONT-S207CW-62TS-SE / Binardat 2G06-04210GSM / Binardat 2G06-04210GS
## Overview
ONT-S207CW-62TS-SE, Binardat 2G06-04210GSM (managed), and Binardat 2G06-04210GS (unmanaged) are **switches with identical hardware**:
- **CPU:** RTL8372N (confirmed via serial: "Detecting CPU: RTL8372N")
- **Flash:** GD25Q128E (16MB)
- **Ports:** 4x 2.5G RJ45 + 2x 10G SFP+
- **Console:** 115200 baud (RTLPlayground firmware) / 9600 baud (stock firmware)
**Note:** These devices are **RTL8372N-based** with **LAN ports mounted upside-down on the PCB**, requiring a custom configuration. The `MACHINE_ONT_S207CW_62TS_SE` definition uses a **custom configuration** with:
- Corrected port mapping (physical ports 1-4 = RJ45, 5-6 = SFP)
- Optimized LED sets for different port wiring (RJ45 Ports 1-2 have reversed LED wiring)
- Custom LED mux configuration
## Device Photos
### Binardat 2G06-04210GSM
**Front View:**
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-front.jpg" width="400" />
**Back View:**
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-back.jpg" width="400" />
**PCB View:**
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-pcb.jpg" width="400" />
*Photos courtesy of [andreas5232](https://github.com/andreas5232) from [emesix/ONT-S207CW-62TS-SE#1](https://github.com/emesix/ONT-S207CW-62TS-SE/issues/1)*
## Hardware Specification
| Feature | Value |
|---|---|
| **CPU** | RTL8372N |
| **Flash** | GD25Q128E (16MB) |
| **RAM** | Integrated in RTL8372N |
| **RJ45 Ports** | 4x 2.5GBase-T (Ports 1-4) |
| **SFP+ Ports** | 2x 10G (Ports 5-6) |
| **Console** | UART (115200 baud for RTLPlayground, 9600 baud for stock) |
| **Stock Web UI IP** | 192.168.2.1 |
| **Loader Mode IP** | 192.168.10.247 |
## Port Layout
```
┌─────────────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │RJ45 │ │RJ45 │ │RJ45 │ │RJ45 │ │SFP+ │ │SFP+ │ │
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ │ 6 │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ [System LEDs] │
└─────────────────────────────────────────────┘
```
## LED Behavior
### RJ45 Ports (Physical 1-4)
| LED Color | Speed |
|---|---|
| **Green** | 2.5G |
| **Orange** | 1G / 100M / 10M |
| **Off** | No link |
### SFP+ Ports (Physical 5-6)
| LED Color | Speed |
|---|---|
| **Green** | 10G |
| **Orange** | 2.5G / 1G |
| **Off** | No link / No SFP module |
## Port to Logical Mapping
Custom mapping for ONT-S207CW-62TS-SE:
| Physical Port | Logical Port | Type | LED Set |
|---|---|---|---|
| 1 | 7 | RJ45 | 2 |
| 2 | 6 | RJ45 | 2 |
| 3 | 5 | RJ45 | 0 |
| 4 | 4 | RJ45 | 0 |
| 5 | 3 | SFP+ (SDS0) | 3 |
| 6 | 8 | SFP+ (SDS1) | 3 |
**Note:** Ports 1-2 (RJ45) have reversed LED wiring and use LED-Set 2. SFP Ports 5-6 use LED-Set 3 to account for wiring differences.
## Flashing Instructions
See the [README.md](../../README.md) for general flashing instructions.
## Stock Firmware Information
| Property | Value |
|---|---|
| **Default IP** | 192.168.2.1 (ONT) / 192.168.10.247 (Loader Mode) |
| **Default Login** | admin/admin (may vary by OEM) |
| **Console Baud** | 9600 (stock) / 115200 (RTLPlayground) |
| **CPU String** | "RTL8372N" |
| **Loader Mode IP** | 192.168.10.247 |
## Configuration Details
- **CPU:** RTL8372N
- **SFP:** SDS0 on logical port 3 (GPIO37), SDS1 on logical port 8 (GPIO38)
- **Port Mapping:** Physical 1-4 → Logical 7-4, Physical 5-6 → Logical 3,8
- **LED Wiring:** RJ45 Ports 1-2 and SFP Port 6 have reversed LED pins (orange/green swapped)
- **LED Sets:** Set 0 for RJ45 Ports 3-4, Set 1 unused, Set 2 for RJ45 Ports 1-2, Set 3 for SFP Ports 5-6
## Notes
- ONT-S207CW-62TS-SE, Binardat 2G06-04210GSM (managed), and Binardat 2G06-04210GS (unmanaged) are **RTL8372N-based** with **LAN ports mounted upside-down on the PCB**
- Web UI shows correct physical port order (1-2-3-4 for RJ45, 5-6 for SFP)
- Loader Mode always remains accessible at `192.168.10.247` for recovery
- Serial console: 9600 baud (stock) → 115200 baud (RTLPlayground)
## References
- [GitHub Issue: emesix/ONT-S207CW-62TS-SE#1](https://github.com/emesix/ONT-S207CW-62TS-SE/issues/1) - Original photos and discussion
- [PR: logicog/RTLPlayground#465](https://github.com/logicog/RTLPlayground/pull/465) - Feedback and improvements
+53
View File
@@ -0,0 +1,53 @@
# IG108
Following is documentation for unmanaged switch marked as `IG108`.
Original software is running UART on 9600 baud rate.
Using SPI clamp in-board is the only method for initial installation.
### Brands
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|---------|------------|---------|------------|-------------|---------------|
| Goalake | IG108 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
| Netcore | GS9 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP port with 1G/2.5G/10G modules
- LEDs
### PCB overview
**Board markings**
- Top silkscreen: PB-2132
Front Goalake:
<img src="photos/PB-2132/case.jpg" width="600" />
Label Goalake:
<img src="photos/PB-2132/label.jpg" width="600" />
Top side:
<img src="photos/PB-2132/PCB-top.jpg" width="600" />
Bottom:
<img src="photos/PB-2132/PCB-bottom.jpg" width="600" />
### Serial console
The PCB has unpopulated through-holes near the SFP Port, with description of each pin, works out of the box.
- **Settings**: 115200 baud / 8N1 / 3.3V TTL
### Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
+5 -4
View File
@@ -1,9 +1,10 @@
# SWTG018AS-A V2.0
## Brands
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|--------|------------------|-------|------------------|-----------------|---------------|
| Ampcom | SWTG018AS-A V2.0 | No | SWTG018AS-A V2.0 | 2MB | 8273N + 8224N |
| Horaco | HC-SWTGW218AS-A | Yes | SWTG018AS-A V2.0 | 2MB(25Q16JVSIQ) | 8273N + 8224N |
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|---------|------------------|-------|------------------|-----------------|---------------|
| Ampcom | SWTG018AS-A V2.0 | No | SWTG018AS-A V2.0 | 2MB | 8273N + 8224N |
| Horaco | HC-SWTGW218AS-A | Yes | SWTG018AS-A V2.0 | 2MB(25Q16JVSIQ) | 8273N + 8224N |
| GoodTop | GT-ST018M | Yes | SWTG018AS-A V2.0 | ? | 8273N + 8224N |
The following is a documentation for the unmanaged switch marked as `SWTG018AS-A V2.0`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

+79
View File
@@ -0,0 +1,79 @@
# The health command
`health` prints a one-page snapshot of the firmware's vital signs on the
serial console. It exists for the situations where the switch misbehaves and
every probe from the network measures the network instead of the device: the
dump is taken from inside, costs one command, and works while HTTP does not.
```
> health
up 0000001f ticks 0x00001f4c loops 0x000016da rx 0x024e
link max 01 slow 0x0000
sfp max 74 slow 0x0001
rx max 00 slow 0x0000
tx max 00 slow 0x0000
stp max 04 slow 0x0001
cmd max 02 slow 0x0001
sp 80 untouched 62
tcp st 00 tmr 00 rtx 00 rto 00 len 0x0000 mss 0x05b4
lport 0x0000 rport 0x0000 rip 0x0000 0x0000
httpd left 0x0000 entry 00 stp 01 mvlan 0x0002
```
The instrumentation is not part of a default build. It costs about 1.1 kB
of bank 2, a few bytes of xdata and seven banked calls per main-loop pass,
which is nothing a user needs unless they are chasing a problem, so it is
switched on at build time:
```
make MACHINE=SWTGW218AS HEALTH=1
```
Without it the hooks compile to nothing and `health` is not a command.
All figures are raw hex. The counters are cumulative: take two dumps a known
time apart and the differences give the rates.
## The first line
`up` is the seconds counter, `ticks` the system tick (200 per second),
`loops` the number of main-loop passes and `rx` the frames handle_rx has
taken off the NIC. A healthy loop completes one pass per tick, so the
difference in `loops` tracks the difference in `ticks`; a loop that falls
behind is being held up by one of the phases below. The `rx` rate separates
a flooded CPU port from a stalled loop, which look identical from outside.
## Phases
Each line is one section of the main loop: the link poll, SFP handling,
frame reception, TCP transmission, the STP timers and the command dispatch.
`max` is the worst time that section has taken, in ticks of 5 ms; `slow`
counts the passes that took two ticks or more. A misbehaving subsystem
names itself here, which is the difference between knowing that the loop is
slow and knowing why.
A dump of its own output takes the console some milliseconds, so running
`health` repeatedly increments the `cmd` counters by itself. That is the
instrument observing itself, not a fault.
## Stack
At boot the free stack area is painted with a pattern. `sp` is the live
stack pointer; `untouched` counts the painted bytes still intact at the top,
which is the closest thing to a high-water mark this hardware offers. A
shrinking `untouched` across dumps means something is reaching deeper than
anything before it.
## The TCP slot
The connection table is printed whole, one slot per pair of lines: TCP
state (3 is ESTABLISHED, 0 closed), retransmission timer and counter, bytes
in flight, negotiated MSS, then the ports and the peer address. With a
single slot serving the entire web interface, one glance answers the
question every management outage starts with: who is holding it. A slot
showing state 3 with nothing in flight and a peer that no longer answers is
a connection whose owner went away.
`httpd left` and `entry` describe the file transfer the web server believes
it is in the middle of, and the trailing flags show whether STP is running
and which VLAN carries management.
+572
View File
@@ -0,0 +1,572 @@
# LEDs HOWTO using firmware function tracing
This is a tutorial to show how to figure out the LED configuration for
unmanaged switches (on managed ones one can just print out the registers
from the command line) without using an I2C port on the device together
an I2C-capable microcontroller. You will only need to have a SOIC clamp
and will have to run ghidra to do a bit of reversing.
The additional advantage of this method is that it not only gives the LED
configuration but also the full PHY configuration and SDS configuration e.g.
in case of additional PHYs being used on the device.
We will use the ZoeRax HG0402XG V1.1 as an example device.
The first step is to read out the flash with a SOIC clamp.
Save the image as
`zoerax_hg0402xg.bin`
The next step is to use a decompiler such as ghidra to identify the
functions used to write or read registers, SDS or to the PHY.
<img src="images/ghidra_open.png" width="300" />
First, load the code in BANK0 into ghidra. The type of code is
8051 default big-endian. The code starts at offset 0x0002 in the file
and has length 0x3ffd
<img src="images/ghidra_add_block.png" width="300" />
Add BANK1 into ghidra by using File->Add to program...
and add the code starting at 0x4000 in `zoerax_hg0402xg.bin` with length
0xc000. Repeat for BANK2 (starts at 0x10000) and BANK3 (starts at 0x1c0000).
They both have length 0xc000.
<img src="images/ghidra_search_read_reg.png" width="300" />
Now, find the read_reg function. An example of the read-register function
is given below. Note the critical bit, namely the execution of the
read-function of an ASIC register via an SFR:
```
CODE1::4dc9 75 a0 01 MOV SFR_EXEC_GO,#0x1
```
This sequence of bytes is very specific. Replace 0x01 with 0x03 and you have
the regster write SFR, 0x05 reads the SerDes, 0x07 writes it. 0x09 reads
from the PHY and 0x0b writes to it.
```
**************************************************************
* FUNCTION *
**************************************************************
undefined reg_read()
undefined <UNASSIGNED> <RETURN>
reg_read
CODE1::4da9 ab 07 MOV R3,BANK0_R7 = ??
CODE1::4dab aa 06 MOV R2,BANK0_R6 = ??
CODE1::4dad e4 CLR A
CODE1::4dae ff MOV R7,A
CODE1::4daf fe MOV R6,A
CODE1::4db0 fd MOV R5,A
CODE1::4db1 fc MOV R4,A
CODE1::4db2 90 00 c9 MOV DPTR,#0xc9
CODE1::4db5 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4db8 e4 CLR A
CODE1::4db9 ff MOV R7,A
CODE1::4dba fe MOV R6,A
CODE1::4dbb fd MOV R5,A
CODE1::4dbc fc MOV R4,A
CODE1::4dbd 90 00 c5 MOV DPTR,#0xc5
CODE1::4dc0 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4dc3 ea MOV A,R2
CODE1::4dc4 f5 a2 MOV SFR_A_ADDRH,A = ??
CODE1::4dc6 eb MOV A,R3
CODE1::4dc7 f5 a3 MOV SFR_A_ADDRL,A = ??
CODE1::4dc9 75 a0 01 MOV SFR_EXEC_GO,#0x1 <<<<<<<< Executes READ REG
LAB_CODE1__4dcc XREF[1]: CODE1::4dcf(j)
CODE1::4dcc af a1 MOV R7,SFR_EXEC_STATUS = ??
CODE1::4dce ef MOV A,R7
CODE1::4dcf 70 fb JNZ LAB_CODE1__4dcc
CODE1::4dd1 af a4 MOV R7,DAT_SFR_a4 = ??
CODE1::4dd3 fc MOV R4,A
CODE1::4dd4 fd MOV R5,A
CODE1::4dd5 fe MOV R6,A
CODE1::4dd6 90 00 c9 MOV DPTR,#0xc9
CODE1::4dd9 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4ddc 90 00 c5 MOV DPTR,#0xc5
CODE1::4ddf 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4de2 c0 00 PUSH BANK0_R0
CODE1::4de4 90 00 c9 MOV DPTR,#0xc9
CODE1::4de7 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4dea 78 18 MOV R0,#0x18
CODE1::4dec 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
CODE1::4def d0 00 POP BANK0_R0
CODE1::4df1 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4df4 90 00 c5 MOV DPTR,#0xc5
CODE1::4df7 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4dfa af a5 MOV R7,DAT_SFR_a5 = ??
CODE1::4dfc e4 CLR A
CODE1::4dfd fc MOV R4,A
CODE1::4dfe fd MOV R5,A
CODE1::4dff fe MOV R6,A
CODE1::4e00 90 00 c9 MOV DPTR,#0xc9
CODE1::4e03 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e06 90 00 c5 MOV DPTR,#0xc5
CODE1::4e09 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4e0c c0 00 PUSH BANK0_R0
CODE1::4e0e 90 00 c9 MOV DPTR,#0xc9
CODE1::4e11 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4e14 78 10 MOV R0,#0x10
CODE1::4e16 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
CODE1::4e19 d0 00 POP BANK0_R0
CODE1::4e1b 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4e1e 90 00 c5 MOV DPTR,#0xc5
CODE1::4e21 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e24 af a6 MOV R7,DAT_SFR_a6 = ??
CODE1::4e26 e4 CLR A
CODE1::4e27 fc MOV R4,A
CODE1::4e28 fd MOV R5,A
CODE1::4e29 fe MOV R6,A
CODE1::4e2a 90 00 c9 MOV DPTR,#0xc9
CODE1::4e2d 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e30 90 00 c5 MOV DPTR,#0xc5
CODE1::4e33 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4e36 c0 00 PUSH BANK0_R0
CODE1::4e38 90 00 c9 MOV DPTR,#0xc9
CODE1::4e3b 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4e3e 78 08 MOV R0,#0x8
CODE1::4e40 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
CODE1::4e43 d0 00 POP BANK0_R0
CODE1::4e45 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4e48 90 00 c5 MOV DPTR,#0xc5
CODE1::4e4b 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e4e af a7 MOV R7,DAT_SFR_a7 = ??
CODE1::4e50 e4 CLR A
CODE1::4e51 fc MOV R4,A
CODE1::4e52 fd MOV R5,A
CODE1::4e53 fe MOV R6,A
CODE1::4e54 90 00 c9 MOV DPTR,#0xc9
CODE1::4e57 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e5a 90 00 c5 MOV DPTR,#0xc5
CODE1::4e5d 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
CODE1::4e60 90 00 c9 MOV DPTR,#0xc9
CODE1::4e63 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
CODE1::4e66 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
CODE1::4e69 90 00 c5 MOV DPTR,#0xc5
CODE1::4e6c 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
CODE1::4e6f 90 00 c5 MOV DPTR,#0xc5
CODE1::4e72 02 05 dd LJMP read_xmem_32_b undefined read_xmem_32_b()
-- Flow Override: CALL_RETURN (CALL_TERMINATOR)
```
INSERT ghidra_search_read_reg.png
Identify all the read and write function to the HW of the SoC using the find
in memory function.
The next task is to adapt the intercept routines in a special assembler
file. Don't worry, you do not need to understand assembler to do this.
```
; Assembler file to intercept register writes/reads and PHY writes/reads
; assemble with as31
; Compile with
; as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
; ./injector zoerax_hg0402xg.bin RTLPlayground/rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
; Start at 0x3b00, where there is always some space in BANK 0
.equ valptr, 0x199
.equ SFR_DATA_0, 0xa7
.equ SFR_DATA_8, 0xa6
.equ SFR_DATA_16, 0xa5
.equ SFR_DATA_24, 0xa4
; Intercept Register Write
.org 0x4d62
lcall reg_write
nop
; Intercept Register Read
.org 0x4dad
lcall reg_read_head
.org 0x4e6f
lcall reg_read_tail
; Intercept PHY write
.org 0x522c
lcall phy_write
; Intercept PHY read
.org 0x526f
lcall phy_read_head
.org 0x5295
ljmp phy_read_tail
; Intercept SDS write
.org 0x503c
lcall sds_write
nop
; Intercept SDS read
.org 0x5061
lcall sds_read_head
nop
.org 0x507b
ljmp sds_read_tail
;
; BEGIN actual code at unused end of BANK0
;
.org 0x3b00
;
; print the address of a register write. The address is in R6/R7. A and r4 can be scratched
;
reg_write:
mov a, #'R'
acall putc
mov a, r6
acall wbyte
mov a, r7
acall wbyte
mov a, #'-'
acall putc
mov dptr, #valptr
movx a, @dptr
acall wbyte
mov dptr, #valptr + 1
movx a, @dptr
acall wbyte
mov dptr, #valptr + 2
movx a, @dptr
acall wbyte
mov dptr, #valptr + 3
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov a, r7
mov r5, a
mov a, r6
mov r4, a
ret
[...]
```
The full assembler file can be found here: XXXXXXXXXX
Once you have assembled the code, inject the bytes into the existing image,
creating a patched version:
```
$ as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
Begin Pass #1
Begin Pass #2
./injector zoerax_hg0402xg.bin rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
Input file size: 1048576
Bytes read:: 1048576
Patched 417 bytes.
```
Now flash `zoerax_hg0402xg_patched.bin` onto device.
Connect a serial adapter and plug in power. You will see:
```
$ picocom -b 9600 /dev/ttyUSB0
picocom v3.1
port is : /dev/ttyUSB0
flowcontrol : none
baudrate is : 9600
parity is : none
databits are : 8
stopbits are : 1
escape is : C-a
local echo is : no
noinit is : no
noreset is : no
hangup is : no
nolock is : no
send_cmd is : sz -vv
receive_cmd is : rz -vv -E
imap is :
omap is :
emap is : crcrlf,delbs,
logfile is : none
initstring : none
exit_after is : not set
exit is : no
Type [C-a] [C-h] to see available commands
Terminal ready
[The printout only starts once the serial peripherial has been set up. Initially its timing is off:]
�.Ӛ�҂���������30-00005555 r6330:00005555 R6330-00005515 [RTL837X_REG_SMI_MAC_TYPE]
[RTL837X_REG_SMI_PORT_POLLING: ]
r6334:00000000 R6334-000000f0
r6454:00000000 R6454-00007000
[Read SoC-Revision]
r000c:00200000 R000c-002a0000 r000c:202a6818 r000c:202a6818 R000c-20206818
p001e.000d:ffff R02f8-0000ffff R02f4-0000fffa P000001.1e00000d:fffa p001e.000d:ffff p001e.000d:ffff
R02f8-0000ffff R02f4-0000fff0 P000001.1e00000d:fff0
[RTL837X_REG_LED_MODE being configured:]
r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
[RLDP Leds configured via RTL837X_REG_LED_RLDP_X:]
r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
[RTL837X_REG_LED_GLB_IO_EN: ]
r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
[RTL837X_PIN_MUX_0, makes serial go out of sync:]
r7f8c:30000000 R7f8c-30000000 r7f8c:300��.Ӛ�҂���������30-00005555
r6330:00005555 R6330-00005515 r6334:00000000 R6334-000000f0
r6454:00000000 R6454-00007000
[Read SoC-Revision (again)]
r000c:00200000 R000c-002a0000 r000c:202a6818 r000c:202a6818 R000c-20206818
p001e.000d:ffff R02f8-0000ffff R02f4-0000fffa P000001.1e00000d:fffa p001e.000d:ffff p001e.000d:ffff
R02f8-0000ffff R02f4-0000fff0
P000001.1e00000d:fff0
#### LED configuration ########
[RTL837X_REG_LED_MODE being configured (final):]
r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
[More RTL837X_REG_LED_RLDP]
r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
[RTL837X_REG_LED_GLB_IO_EN]
r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
[RTL837X_PIN_MUX_0
r7f8c:30000000 R7f8c-30000000 r7f8c:30000000 R7f8c-38000000
[RTL837X_LED_PORT_SET_SEL]
r654c:00000000 R654c-00010000
[RTL837X_REG_LED1_0_SET0]
R6548-00000141
[RTL837X_REG_LED3_2_SET0]
r6544:01411000 R6544-01410174
[RTL837X_REG_LED1_0_SET1]
r6540:00440070 R6540-0044017f
[RTL837X_REG_LED3_0_SET1]
r6528:00000000 R6528-000f0000
[SDS 0 being configure:]
r7b20:00000bff R7b20-000003ff r7b20:000003ff R7b20-000003fa r7b20:000003fa R7b20-000003fa
Q002110:4480 Q002113:0400 Q002118:6d02 Q00211b:424e Q00211d:0002 Q00361c:1390
Q003614:003f Q003610:0200 Q002e04:0080 Q002e06:0408 Q002e07:020d Q002e09:0601
Q002e0b:222c Q002e0c:a217 Q002e0d:fe40 Q002e15:f5c1 Q002e16:0443 Q002e1d:abb0
Q000612:5078 Q000706:9401 Q000708:9401 Q00070a:9401 Q00070c:9401 Q001f0b:0003
Q000603:c45c Q00061f:2100 q001f0b:0003 Q001f0b:000f q002000:0000 Q002000:0030
q002000:0030 Q002000:0010 q002000:0010 Q002000:0050 q002000:0050 Q002000:00d0
q002000:00d0 Q002000:0cd0 q002000:0cd0 Q002000:04d0 q002000:04d0 Q002000:04d0
q002000:04d0 Q002000:0cd0 q002000:0cd0 Q002000:00d0 q002000:00d0 Q002000:00d0
q002000:00d0 Q002000:0050 q002000:0050 Q002000:0010 q002000:0010 Q002000:0010
q002000:0010 Q002000:0030 q002000:0030 Q002000:0000 q001f00:0000 Q001f00:000b
q001f00:000b Q001f00:0000
[SDS 1 being configured]
r7b20:000003fa R7b20-000003fa r7b20:000003fa R7b20-0000035a r7b20:0000035a R7b20-0000035a
Q012110:4480 Q012113:0400 Q012118:6d02 Q01211b:424e Q01211d:0002 Q01361c:1390
Q013614:003f Q013610:0200 Q012e04:0080 Q012e06:0408 Q012e07:020d Q012e09:0601
Q012e0b:222c Q012e0c:a217 Q012e0d:fe40 Q012e15:f5c1 Q012e16:0443 Q012e1d:abb0
Q010612:5078 Q010706:9401 Q010708:9401 Q01070a:9401 Q01070c:9401 Q011f0b:0003
Q010603:c45c Q01061f:2100 q011f0b:0003 Q011f0b:000f q012000:0000 Q012000:0030
q012000:0030 Q012000:0010 q012000:0010 Q012000:0050 q012000:0050 Q012000:00d0
q012000:00d0 Q012000:0cd0 q012000:0cd0 Q012000:04d0 q012000:04d0 Q012000:04d0
q012000:04d0 Q012000:0cd0 q012000:0cd0 Q012000:00d0 q012000:00d0 Q012000:00d0
q012000:00d0 Q012000:0050 q012000:0050 Q012000:0010 q012000:0010 Q012000:0010
q012000:0010 Q012000:0030 q012000:0030 Q012000:0000 q011f00:0000 Q011f00:000b
q011f00:000b Q011f00:0000 q012100:4902 Q012100:4906 q013605:0000 Q013605:4000
Q011f02:001f q011f15:0096 q002100:4902 Q002100:4906 q003605:0000 Q003605:4000
Q001f02:001f q001f15:00a6
r0a90:000000f3 R0a90-000000fc P0000f0.1f00a610:2858 r5fd4:0002914a
R5fd4-001a914a r1538:00000e33 R1538-00000e37 r1538:00000e37 R1538-00000e37
r1538:00000e37 R1538-00000f37 r1638:00000e33 R1638-00000e37 r1638:00000e37
R1638-00000e37 r1638:00000e37 R1638-00000f37 r1738:00000e33 R1738-00000e37
r1738:00000e37 R1738-00000e37 r1738:00000e37 R1738-00000f37 r1838:00000e33
R1838-00000e37 r1838:00000e37 R1838-00000e37 r1838:00000e37 R1838-00000f37
r1938:00000e33 R1938-00000e37 r1938:00000e37 R1938-00000e37 r1938:00000e37
R1938-00000f37 r1a38:00000e33 R1a38-00000e37 r1a38:00000e37 R1a38-00000e37
r1a38:00000e37 R1a38-00000f37 r0b7c:000000d8 R0b7c-000000f8 R7124-00001050
R7128-00001050 R712c-00001050 R7130-00001050 R7134-00001050 R7138-00001050
R713c-00001050 R7140-00001050 R7144-00001050 R7148-00001050 r6040:00000030
R6040-00000031 p041f.a436:8195 P000010.1f00a436:0005 p041f.a438:02d4
P000010.1f00a436:801e p041f.a438:0000 p041f.b820:0000 P000010.1f00b820:0010
p041f.b800:00c0 p041f.a436:801e P000010.1f00a436:8023 p041f.a438:0000
P000010.1f00a438:1802 p041f.a436:8025
[PHY with ID 5 being patched] P000010.1f00a436:b82e p041f.a438:0000
P000010.1f00a438:0001 p041f.b820:0010 P000010.1f00b820:0090
P000010.1f00a436:a016 P000010.1f00a438:0000 P000010.1f00a436:a012
P000010.1f00a438:0000 P000010.1f00a436:a014 P000010.1f00a438:1800
P000010.1f00a438:8010 P000010.1f00a438:1800 P000010.1f00a438:8013
P000010.1f00a438:1800 P000010.1f00a438:8026 P000010.1f00a438:1800
P000010.1f00a438:802a P000010.1f00a438:1800 P000010.1f00a438:802e
P000010.1f00a438:1800 P000010.1f00a438:802e P000010.1f00a438:1800
P000010.1f00a438:802e P000010.1f00a438:1800 P000010.1f00a438:802e
...
```
Where:
```
r: read-reg
R: write-reg
q: read SDS
Q: write SDS
p: read PHY
P: write SDS
```
The LED configuration has been marked above. We now write exactly those registers
into the registers of `rtlplayground.c` on startup:
```
[...]
set_sys_led_state(SYS_LED_ON);
// [RTL837X_REG_LED_MODE being configured (final):]
// r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
REG_SET(RTL837X_REG_LED_MODE, 0x0021e6b0);
// [More RTL837X_REG_LED_RLDP]
// r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
REG_SET(0x65f8, 0x0000001b);
REG_SET(0x65fc, 0xfffff000);
REG_SET(0x6600, 0x0000000f);
// [RTL837X_REG_LED_GLB_IO_EN]
// r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
REG_SET(0x65dc, 0x77ffff00);
// [RTL837X_PIN_MUX_0
// r7f8c:30000000 R7f8c-30000000 r7f8c:30000000 R7f8c-38000000
REG_SET(0x7f8c, 0x38000000);
// [RTL837X_LED_PORT_SET_SEL]
// r654c:00000000 R654c-00010000
REG_SET(0x654c, 0x00010040);
// [RTL837X_REG_LED1_0_SET0]
// R6548-00000141
REG_SET(0x6548, 0x00000141);
// [RTL837X_REG_LED3_2_SET0]
// r6544:01411000 R6544-01410174
REG_SET(0x6544, 0x01410174);
// [RTL837X_REG_LED1_0_SET1]
// r6540:00440070 R6540-0044017f
REG_SET(0x6540, 0x0044017f);
// [RTL837X_REG_LED3_0_SET1]
// r6528:00000000 R6528-000f0000
REG_SET(0x6528, 0x000f0000);
cmd_editor_init();
[...]
```
Create an initial configuraiton for your device by copying over an existing one
for the same device type, i.e. same port-configuraiton and same SoC,
give it a new name. Make sure that the led_mux_custom and led_mux fields are not
used, because our device does not customize the LED muxes.
Compile and flash onto your device.
Now, boot the switch and issue the `leds`command, which will print out:
```
RTL837X_PIN_MUX_0: 0x38000000
RTL837X_REG_LED_GLB_IO_EN: 0x77ffff00
RTL837X_REG_LED1_0_SET0: 0x00000141
RTL837X_REG_LED3_2_SET0: 0x01410174
RTL837X_REG_LED1_0_SET1: 0x0044017f
RTL837X_REG_LED3_2_SET1: 0x18000040
RTL837X_REG_LED1_0_SET2: 0x00000000
RTL837X_REG_LED3_2_SET2: 0x00000000
RTL837X_REG_LED1_0_SET3: 0x00000000
RTL837X_REG_LED3_2_SET3: 0x00000000
RTL837X_REG_LED3_0_SET1: 0x000f0000
RTL837X_REG_LED3_0_SET3: 0x00000000
RTL837X_LED_PORT_SET_SEL: 0x00010040
RTL837X_REG_LED_GLB_MUX_1: 0x3fffffff
RTL837X_REG_LED_GLB_MUX_2: 0x0c3fffff
RTL837X_REG_LED_GLB_MUX_3: 0x1245038d
RTL837X_REG_LED_GLB_MUX_4: 0x19616554
RTL837X_REG_LED_GLB_MUX_5: 0x2079d71a
RTL837X_REG_LED_GLB_MUX_6: 0x000238a1
RTL837X_REG_LED_GLB_ACTIVE: 0x3ffbedff
RTL837X_REG_LED_MODE: 0x0021e6b0
LED pad Configuration:
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b
3f 3f 3f 3f 3f 3f 3f 3f 0f 0c 0d 0e 10 11 12 14 15 16 18 19 1a 1c 1d 1e 20 21 22 23
LED-set Configuration:
LED-ID 0 1 2 3
SET 0: 000141 000000 000174 000141
SET 1: 0f017f 000044 000040 001800
SET 2: 000000 000000 000000 000000
SET 3: 000000 000000 000000 000000
Port 3: SET 1: ( 2G5 TWO_1G 1G 500M 100M 10M LINK ACT 10G TWO_5G 5G TWO_2G5), ( 1G LINK), ( LINK), ( COL DUPLEX),
Port 4: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 5: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 6: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 7: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
Port 8: SET 1: ( 2G5 TWO_1G 1G 500M 100M 10M LINK ACT 10G TWO_5G 5G TWO_2G5), ( 1G LINK), ( LINK), ( COL DUPLEX),
```
Together with the GPIO information, this will give you the following machine configuration:
```
_code const struct machine machine = {
.machine_name = "ZOERAX HG0402XG",
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
.n_sfp = 2,
.log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6},
.phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0},
.is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2},
// Left SFP port (5)
.sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
// Right SFP port (6)
.sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1},
/* On Ethernet: Left led is orange, right green
* 100M and 1G give right led green with activity
* 2.5GBit adds solid left orange LED
* LEDs for the SFP-ports are the same as for the Ethernet ports
*/
.led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT,
0,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
LEDS_2G5 | LEDS_LINK | LEDS_ACT
},
{ LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M |
LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5,
LEDS_1G | LEDS_LINK,
LEDS_LINK,
LEDS_COL | LEDS_DUPLEX
}
}
};
```
+6 -2
View File
@@ -69,7 +69,7 @@ The code currently provides the following functions:
void igmp_setup(void) __banked;
void igmp_enable(void) __banked;
void igmp_router_port_set(uint16_t pmask) __banked;
void igmp_packet_handler(void) __banked;
uint8_t igmp_packet_handler(void) __banked;
void igmp_show(void) __banked;
```c
`igmp_setup()` is called at boot-time and configures flooding of all IP-MC packets by
@@ -80,7 +80,11 @@ of IP-MC packets to be limited to only subscribed ports.
`igmp_router_port_set()`configures forwarding ports for IGMP messages.
`igmp_packet_handler()` implements handling of trapped IGMP packets by the CPU.
`igmp_packet_handler()` implements handling of trapped IGMP packets by the CPU. It is called
for frames to 01:00:5e:xx:xx:xx and returns 0 when the frame is not a trapped IGMP packet,
so that it is passed on to the IP stack. IGMPv1/v2 reports (0x12, 0x16) join the group given
in the IGMP header, IGMPv2 leaves (0x17) leave it, IGMPv3 reports use the first group record
(type 4 joins, type 3 leaves). Trapped IGMP frames arrive without a VLAN tag.
`igmp_show()` prints out the IGMP configuration on the CLI.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 120 KiB

+54
View File
@@ -0,0 +1,54 @@
# Storm Control
Storm control limits the rate at which a port admits broadcast, multicast,
unknown unicast and unknown multicast frames. Frames of a type above its limit
are dropped at ingress, so a loop or a misbehaving host cannot flood the rest
of the network through that port. Known unicast traffic is not affected.
## Commands
```
storm <port> bcast|mcast|ucast|umcast <rate> pps
storm <port> bcast|mcast|ucast|umcast <rate> kbps
storm <port> bcast|mcast|ucast|umcast off
storm
```
`ucast` is unicast to a MAC address the switch has not learned yet, and
`umcast` is multicast without an entry in the address table. Multicast groups
get their entries from IGMP snooping, so without `igmp on` all multicast is
unknown and only the `umcast` limit applies to it; `mcast` covers the groups
IGMP has learned. A limit in
packets per second goes from 1 to 1048575, a limit in kbit/s from 1 to
10000000. `storm` without arguments prints the limits of every port. The
limits are kept in the configuration like any other setting, and the Bandwidth
page of the web interface has a Storm control card to set them.
## Registers
Each port and type has an enable bit and a meter index, and each limit uses
one of the 64 shared meters of the switch. RTLPlayground gives every port and
type its own meter, index `24 + port * 4 + type`, so one port's storm does not
eat into the limit of another. Storm control uses meters 24 to 63 and leaves
0 to 23 to the ACL. The limit of a meter is in `RTL837X_METER_RATE + 4 *
meter`, and bit `meter % 32` of `RTL837X_METER_MODE + 4 * (meter / 32)`
selects pps instead of kbit/s.
```
#define RTL837X_STORM_CTRL 0x54e4 /* + 4 * type, bit = port */
#define RTL837X_STORM_MIDX 0x54f4 /* + 8 * type + 4 * (port / 5), 6 bits per port */
#define RTL837X_METER_RATE 0x5cf0 /* + 4 * meter, 24 bits, kbit/s or pps */
#define RTL837X_METER_MODE 0x5ef0 /* + 4 * (meter / 32), bit set = pps */
```
In kbit/s mode one step of the rate is 1 kbit/s. Measured with 100 and 1000 byte
frames, the meter counts a frame with its FCS, but without preamble and
interframe gap. Every meter has a bit that adds them, in `0x5f08 + 4 * (meter
/ 32)`, which storm control leaves cleared. `IGBW_INC_IFG` and `EGBW_INC_IFG`
are the matching bits of the bandwidth control.
An ingress bandwidth limit on the same port works on the same arriving frames
as the storm limit rather than after it, and the two together pass far less
than either one: a 1 Mbit/s `umcast` limit with a 10 Mbit/s ingress limit let
0.12 to 0.16 Mbit/s of a 1.4 Gbit/s stream through, where each alone passed
1.17 and 10.2 Mbit/s.
The type is 0 for broadcast, 1 for multicast, 2 for unknown unicast and 3 for
unknown multicast. The burst size of a meter (0x5df0 + 4 * meter) counts packets
in pps mode; its default of 0x2000 lets 8192 frames through before a pps limit
bites, so a pps limit sets the burst to one second of traffic. A kbit/s limit
puts back the default.
+66 -94
View File
@@ -1,135 +1,107 @@
# Supporting Multiple Languages in the Web UI
The firmware uses a client-side i18n approach
all translations are stored in a single JavaScript dictionary embedded in the firmware.
No server-side changes are needed.
The firmware uses a client-side i18n approach: all translations are stored
in one JavaScript dictionary embedded in the firmware. No server-side
changes are needed.
## Architecture
All translation logic lives in `html/i18n.js`. The file contains:
All translation logic lives at the top of `html/app.js`:
- A `LANG` object with one sub-object per language (`en`, `ja`, ...)
- Language auto-detection (browser language → `localStorage` override)
- `t(key)` — look up a translated string
- `setLang(lang)` — switch language and update the page
- `applyTranslation(el)` — apply translation to one DOM element
- A `LANG` object with one sub-object per language (`en`, `ja`, `zh`)
- Language auto-detection (browser language, overridden by the
`rtl_lang` key in `localStorage`, which the language selector in the
System section writes)
- `t(key, vars)` looks up a translated string; `{name}` placeholders in
the string are replaced from `vars`, e.g. `t("v_del_q", {n: vid})`
- `i18nApply()` applies the dictionary to the static markup once at load
Translation keys are **flat strings** (no nesting). The English keys in `LANG.en` also serve as the fallback when a key is missing in another language.
Translation keys are flat strings. The English entry is the fallback when
a key is missing in another language, and the key itself is the fallback
when it is missing everywhere.
Changing the language stores the choice and reloads the page, so every
dynamically built table is rebuilt in the new language.
`login.html` is served before authentication and does not load `app.js`;
it carries its own five strings per language in an inline table.
## How to Add a New Language
### 1. Add a dictionary entry in `html/i18n.js`
### 1. Add a dictionary entry in `html/app.js`
Append a new sub-object to the `LANG` object. Every key from `LANG.en` must be present:
Append a sub-object to `LANG`. Every key from `LANG.en` should be present:
```js
var LANG = {
en: {
nav_overview: 'Overview',
nav_port_config: 'Port Configuration',
// ... all keys for English
},
ja: {
nav_overview: '概要',
nav_port_config: 'ポート設定',
// ... all keys for Japanese
},
LANGCODE: { // ← add your language here
nav_overview: '...',
nav_port_config: '...',
// ... translate every key
},
var LANG={
en:{
nav_dash:"Dashboard",
...
},
LANGCODE:{ // add your language here
nav_dash:"...",
...
}
};
```
### 2. Add the language to the navigation sidebar
### 2. Add the language to the selector in `html/index.html`
In `html/navigation.js`, add an `<option>` to the language selector:
```js
+ "<option value='en'>English</option><option value='ja'>日本語</option>"
```html
<select class="ctl" id="langSel" data-i18n-t="sy_lang">
<option value="en">English</option>
<option value="ja">日本語</option>
<option value="zh">中文</option>
<option value="LANGCODE">Native name</option>
</select>
```
Replace with:
### 3. Add the login strings in `html/login.html`
```js
+ "<option value='en'>English</option><option value='ja'>日本語</option><option value='LANGCODE'>Native Name</option>"
```
The inline table there holds, per language: page title, subtitle,
password label, button label, wrong-password message.
### 3. Verify auto-detection
### 4. Verify auto-detection
The language detection code in `i18n.js` reads `navigator.language` and normalises it to the first two characters:
```js
var browser = (navigator.language || navigator.userLanguage || 'en').substring(0, 2);
return LANG[browser] ? browser : 'en';
```
If the two-letter code matches a key in `LANG`, it will be auto-selected. No changes needed here.
The detection normalises `navigator.language` to its first two
characters and selects that language if `LANG` has it, otherwise English.
## Two Translation Mechanisms
### (A) `data-i18n` attribute (declarative — for HTML)
Add `data-i18n="key_name"` to any HTML element. The English text goes in the element content as a fallback:
### (A) `data-i18n` attributes (declarative, for HTML)
```html
<h1 data-i18n="port_heading">Port Configuration</h1>
<input type="button" data-i18n="port_apply" value="Apply">
<option data-i18n="port_auto">Auto</option>
<title data-i18n="port_title">Port Configuration</title>
<h2 data-i18n="pt_title">Port configuration</h2>
<button class="ctl" data-i18n="c_apply" data-i18n-t="sy_dhcp_t">Apply</button>
<input class="in" data-i18n-p="l2_filter">
```
On page load, `applyTranslation()` sets:
- `data-i18n` sets the element's text content
- `data-i18n-t` sets the `title` attribute (tooltip)
- `data-i18n-p` sets the `placeholder` attribute
- `el.value` for `<input type="submit|button">`
- `el.textContent` for `<option>`, `<title>`
- `el.innerHTML` for everything else
The English text stays in the markup as the fallback and as the source
of truth for what the key means.
### (B) `t('key')` call (imperative — for JavaScript strings)
When generating HTML or text in JavaScript, wrap translatable strings with `t()`:
### (B) `t("key")` calls (imperative, for JavaScript)
```js
td.appendChild(document.createTextNode(t('common_port') + i));
td.innerHTML = t('port_auto');
iHTML += "<tr><td>" + t('port_vendor') + "</td></tr>";
tr.insertCell().textContent=t("c_port")+" "+p;
toast(t("v_loaded",{n:vid}),"ok");
```
### Special Case: Link Speed Display
The `linkS` array in `html/main.js` maps numeric link states to display strings. The first two entries (`speed_disabled`, `speed_down`) use `t()` for translation; the remaining entries are static literals (they are the same in all languages):
```js
const linkS = [
function(){return t('speed_disabled')},
function(){return t('speed_down')},
"10M", "100M", "1000M", "500M", "10G", "2.5G", "5G"
];
function linkText(idx) { var v = linkS[idx]; return typeof v === 'function' ? v() : v; }
```
Always use `linkText(idx)` (not `linkS[idx]`) to read these values.
Strings that are identical in every language (`10M`, `2.5G`, `MAC`,
`VLAN`, `CPU`, `RSTP`) are literals, not dictionary entries.
## Size Considerations
- `html/i18n.js` is embedded in the firmware filesystem (~14 KB for two languages)
- Each new language adds roughly the same number of bytes as the English dictionary (~3–4 KB)
- The firmware binary is padded to 512 KiB, so a few extra KB do not change the flash footprint
- Values that are identical in all languages should be inlined as literals rather than added to the dictionary (e.g., `"10M"`, `"2.5G"`, `"MAC"`, `"VLAN"`, `"CPU"`)
- Each language adds roughly 3 KB to `app.js` before compression; the
build gzips the file, so the flash cost of a language is closer to 1 KB
- The largest embedded file must stay below 64 KB after compression
(`uint16_t` length in the file table); `app.js` with three languages
is about 29 KB compressed
## Build
No special flags are needed. The `html/` directory is embedded by `fileadder` during the build.
## Script Load Order
`i18n.js` must be loaded after `main.js` (which defines `t()`'s dependencies like `LANG`) but before any page-specific JS that calls `t()`:
```html
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<script src="/eee.js"></script> <!-- uses t() -->
```
The `navigation.js` script is loaded last (bottom of `<body>`).
No special flags are needed. `html/` is minified and embedded by the
normal build (see `webui-compression.md`).
+9
View File
@@ -6,6 +6,9 @@ The following devices have been tested and are fully working:
| Ampcom | WAM902-SWTG018AS| No | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 |
| Davuaz | Da-K6501W | No | [PCB-K0501W-V2.0](devices/K0501W_V2_0.md) | | 5 + 1 |
| FOXNEO | FNS-1200P | No | [PCB-K0402W-U13-V2.0](devices/FNS-1200P.md) | 2M | 4 + 2 |
| Goalake | IG108 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 |
| GoodTop | GT-ST018M | Yes | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | ? | 8 + 1 |
| Hasivo | S1100WP_8GT_1SX_SE | Yes | [S1100WP_8GT_1SX_SE-V1.05](devices/HASIVO_S1100WP_8GT_1SX_SE.md) | 2M | 8 + 1 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 |
| Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 |
@@ -19,11 +22,16 @@ The following devices have been tested and are fully working:
| Keeplink | KP-9000-6XH-X2 / KP-9000-6XHML-X2 | No/Yes | [2M-PCB43-V1.2 / V2.1](devices/KP-9000-6XH-X2.md) | | 4 + 2 |
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V2.2](devices/2M-PCB23-V2_2.md) | 2M | 8 + 1 |
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.1](devices/2M-PCB23-V3_1.md) | 2M | 8 + 1 |
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.2](devices/2M-PCB23-V3_2.md) | 2M | 8 + 1 |
| LIANGUO | SWTG024AS | No | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 0.5M | 4 + 2 |
| Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 |
| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 |
| Mokerlink| 2G040210GSM | Yes | [2M-PCB43-V1.1](devices/2M-PCB43-V1.1.md) | | 4 + 2 |
| Mokerlink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| MokerLink| POE-2G080110GS | No | [2M-PCB51-ML-V1.1](devices/2M-PCB51-ML-V1_1.md) | 4M | 8 + 1 |
| Binardat | 2G06-04210GSM | No | [PCB-SWTG024AS-A-2.0.1](devices/ONT-S207CW-62TS-SE.md) | 0.5M | 4 + 2 |
| ONT | ONT-S207CW-62TS-SE | No | [PCB-SWTG024AS-A-2.0.1](devices/ONT-S207CW-62TS-SE.md) | 0.5M | 4 + 2 |
| Netcore | GS9 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 |
| Ruiying | RY-4GT-2SX | No | [FG-4GT-2SX_V2.0](devices/FG-4GT-2SX_V2.0.md) | 4M | 4 + 2 |
| Ruiying | RY-8GT-1SX | No | [FG-8GT-1SX](devices/FG-8GT-1SX.md) | 1M | 8 + 1 |
| SiscoLink | SL-G0402F | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
@@ -33,6 +41,7 @@ The following devices have been tested and are fully working:
| Xikestore| SKS3200M-4GPY2XF| Yes | [SWTG024AS-v1.0](devices/SWTG024AS.md) | | 4 + 2 |
| XikeStor | SKS3200-8E1X | Yes | [SWTG118AS-V2.1-17462](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| Ztyuav | Z-QWYT0402 | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Guangnianwei | 7018-2.5G | No | [F7008-2.5GPOE-V1.2](devices/F7008-2.5.md) | 8M | 8 + 1 |
For KP-9000-6XH-X2 / KP-9000-6XHML-X2 / Mokerlink 2G040210GSM devices, select
the machine target by PCB revision. The ML/non-ML or managed/unmanaged label
+12 -10
View File
@@ -19,16 +19,17 @@ html/ --(minify.py)--> output/html_min/ --(fileadder -z)--> flash image
served bytes, so minification alone already reduces the transfer size
from 109,814 to 97,730 bytes, independent of gzip.
- The Web UI is a single-page application: `index.html` holds all pages
as sections (`#/ports`, `#/vlan`, ...) that the sidebar switches
as sections (`#ports`, `#vlan`, ...) that the sidebar switches
between via the URL hash. `login.html` stays separate because it is
the authentication gate. Only these two pages plus `main.js`,
`style.css`, the three SVG port images and the favicon are embedded.
- All JavaScript (i18n, shared helpers, navigation and the per-section
scripts) lives in one `html/main.js` bundle that the pages load as
their only script, so the browser fetches it once and gzip can
compress across all of it. A section's initialisation runs when the
section is shown, its polling intervals run only while it is visible,
and re-entering a section refetches the data.
the authentication gate and is self-contained (its own styles and
strings), so nothing else is served before login. Only these two
pages plus `app.js` and the favicon are embedded; the stylesheet and
the theme bootstrap are inline in `index.html`.
- All JavaScript (translation table, shared helpers, navigation and the
per-section code) lives in one `html/app.js` that the page loads as
its only script, so the browser fetches it once and gzip can compress
across all of it. A section starts its pollers when it is shown and
stops them when it is left; re-entering a section refetches the data.
- `fileadder -z` gzip-compresses every file (zlib, gzip format,
`Z_BEST_COMPRESSION`) when it generates the file table
(`html_data.c`/`html_data.h`) and when it embeds the files into the
@@ -71,7 +72,8 @@ JSON polling of the visible section).
## Notes
- The files stay well below the `uint16_t` size limit of the file table
(largest gzip output: 17.2 KB for the merged `main.js`).
(largest gzip output: about 29 KB for `app.js`, three languages
included).
- `fileadder` terminates the embedded files with a NUL directly after the
content (previously at `data_read + 1`), so the `strlen()`-based size
computation no longer depends on uninitialised buffer content.
+2393
View File
File diff suppressed because it is too large. Load diff
+456 -282
View File
@@ -1,310 +1,484 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<link rel="stylesheet" href="style.css">
<title data-i18n="index_title">FreeSwitchOS Main Page</title>
<style>
.popup {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
.popup-content {
background: #fff;
padding: 20px;
border-radius: 5px;
text-align: center;
height: 80%
}
.popup .popup-content {
overflow-y: scroll;
}
.tab-bar { display: flex; border-bottom: 2px solid #226; margin-bottom: 0; padding: 1px 16px; padding-bottom: 0; }
.tab-btn { padding: 10px 20px; background-color: #ddf; border: none; cursor: pointer; font-size: 1em; border-radius: 8px 8px 0 0; margin-right: 4px; }
.tab-btn:hover { background-color: #aaf; }
.tab-btn.active { background-color: #aaf; font-weight: bold; border-bottom: 2px solid #aaf; margin-bottom: -2px; }
.tab-content { display: none; }
.tab-content.active { display: block; }
</style>
</head>
<body>
<nav id="sidebar"></nav>
<div id="ports" style="margin-left:16%;padding:1px 16px"></div>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Switch</title>
<style>
:root{
--bg:#f4f6f8;--panel:#ffffff;--panel2:#eaeef2;--bd:#d7dde4;--fg:#1a222c;--fg2:#5c6773;
--ac:#0969da;--acfg:#ffffff;--ok:#158744;--bad:#cf222e;--warn:#9a6700;--cy:#1b7c83;
--sel:rgba(9,105,218,.10);--shadow:0 2px 10px rgba(20,30,40,.07);
--s10:#b58900;--s100:#b58900;--s1000:#159744;--s2g5:#0d9488;--s5g:#0d9488;--s10g:#2563eb;
}
:root[data-theme="dark"]{
--bg:#0e1116;--panel:#161b22;--panel2:#1f262e;--bd:#2c333c;--fg:#e6edf3;--fg2:#8d96a0;
--ac:#4c8ef7;--acfg:#ffffff;--ok:#3fb950;--bad:#f85149;--warn:#d29922;--cy:#39c5cf;
--sel:rgba(76,142,247,.16);--shadow:0 2px 10px rgba(0,0,0,.35);
--s10:#d29922;--s100:#d29922;--s1000:#3fb950;--s2g5:#2dd4bf;--s5g:#2dd4bf;--s10g:#58a6ff;
}
:root[data-theme="sel-dark"]{
--bg:#103c48;--panel:#184956;--panel2:#2d5b69;--bd:#2d5b69;--fg:#adbcbc;--fg2:#72898f;
--ac:#4695f7;--acfg:#ffffff;--ok:#75b938;--bad:#fa5750;--warn:#dbb32d;--cy:#41c7b9;
--sel:rgba(70,149,247,.18);--shadow:0 2px 10px rgba(0,20,26,.35);
--s10:#dbb32d;--s100:#dbb32d;--s1000:#75b938;--s2g5:#41c7b9;--s5g:#41c7b9;--s10g:#4695f7;
}
:root[data-theme="sel-light"]{
--bg:#fbf3db;--panel:#ece3cc;--panel2:#d5cdb6;--bd:#d5cdb6;--fg:#53676d;--fg2:#909995;
--ac:#0072d4;--acfg:#ffffff;--ok:#489100;--bad:#d2212d;--warn:#ad8900;--cy:#009c8f;
--sel:rgba(0,114,212,.12);--shadow:0 2px 10px rgba(80,70,40,.12);
--s10:#ad8900;--s100:#ad8900;--s1000:#489100;--s2g5:#009c8f;--s5g:#009c8f;--s10g:#0072d4;
}
*{box-sizing:border-box;margin:0}
html,body{height:100%}
body{
font:14px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
background:var(--bg);color:var(--fg);display:flex;
}
button,input,select,textarea{font:inherit;color:var(--fg)}
a{color:var(--ac);text-decoration:none}
#nav{
width:200px;flex:none;background:var(--panel);border-right:1px solid var(--bd);
display:flex;flex-direction:column;position:sticky;top:0;height:100vh;
}
#brand{display:flex;align-items:center;gap:9px;padding:16px 16px 12px;font-weight:650;font-size:15px}
#brand svg{flex:none;stroke:var(--ac)}
#brand span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#navlist{list-style:none;padding:4px 8px;overflow-y:auto;flex:1}
#navlist li{
display:flex;align-items:center;gap:9px;padding:7px 10px;margin:1px 0;
border-radius:7px;cursor:pointer;color:var(--fg2);font-weight:500;
}
#navlist li svg{flex:none;stroke:currentColor}
#navlist li:hover{background:var(--panel2);color:var(--fg)}
#navlist li.act{background:var(--sel);color:var(--ac)}
#navfoot{padding:10px 16px;border-top:1px solid var(--bd);font-size:12px;color:var(--fg2)}
#wrap{flex:1;min-width:0;display:flex;flex-direction:column}
#top{
display:flex;align-items:center;gap:10px;padding:10px 20px;
background:var(--panel);border-bottom:1px solid var(--bd);position:sticky;top:0;z-index:5;
}
#burger{display:none;background:none;border:0;cursor:pointer;padding:4px}
#ttitle{font-size:16px;font-weight:650;flex:1}
#dirty{
display:none;font-size:12px;font-weight:600;color:var(--warn);
border:1px solid var(--warn);border-radius:20px;padding:2px 10px;
}
#dirty.show{display:inline-block}
select.ctl,button.ctl{
background:var(--panel2);border:1px solid var(--bd);border-radius:7px;
padding:5px 9px;cursor:pointer;font-size:13px;white-space:nowrap;
}
button.pri{background:var(--ac);color:var(--acfg);border-color:transparent;font-weight:600}
button.pri:hover,button.ctl:hover{filter:brightness(1.07)}
button:disabled{opacity:.45;cursor:default;filter:none}
main{padding:18px 20px 40px;max-width:1400px;width:100%}
section.tab{display:none}
section.tab.act{display:block}
.card{
background:var(--panel);border:1px solid var(--bd);border-radius:10px;
box-shadow:var(--shadow);padding:16px 18px;margin-bottom:16px;
}
.card h2{font-size:14px;font-weight:650;margin-bottom:12px;color:var(--fg)}
.card h2 .hint{font-weight:400;font-size:12px;color:var(--fg2);margin-left:8px}
.grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:16px;align-items:start}
.grid2 .card{margin-bottom:0}
table.t{width:100%;border-collapse:collapse;font-size:13px}
table.t th{
text-align:left;font-weight:600;color:var(--fg2);font-size:12px;
padding:6px 8px;border-bottom:1px solid var(--bd);white-space:nowrap;
}
table.t th[data-sort]{cursor:pointer;user-select:none}
table.t td{padding:6px 8px;border-bottom:1px solid var(--bd);vertical-align:middle}
table.t tr:last-child td{border-bottom:0}
table.t tr:hover td{background:var(--sel)}
.num{font-variant-numeric:tabular-nums;text-align:right}
.scrollx{overflow-x:auto}
input.in,select.in,textarea.in{
background:var(--bg);border:1px solid var(--bd);border-radius:7px;padding:5px 8px;outline:none;
}
input.in:focus,select.in:focus,textarea.in:focus{border-color:var(--ac)}
input.in.sm{width:7em}
label.row{display:flex;align-items:center;gap:10px;margin:8px 0}
label.row span.lb{width:110px;flex:none;color:var(--fg2);font-size:13px}
.switch{position:relative;display:inline-block;width:34px;height:19px;flex:none}
.switch input{opacity:0;width:0;height:0}
.switch i{
position:absolute;inset:0;background:var(--bd);border-radius:19px;transition:.15s;cursor:pointer;
}
.switch i:before{
content:"";position:absolute;width:15px;height:15px;border-radius:50%;
left:2px;top:2px;background:var(--panel);transition:.15s;
}
.switch input:checked+i{background:var(--ok)}
.switch input:checked+i:before{transform:translateX(15px)}
#strip{display:flex;gap:8px;flex-wrap:wrap;padding:4px 0 2px}
.port{
width:46px;border:2px solid var(--bd);border-radius:7px;padding:5px 0 3px;
text-align:center;cursor:pointer;background:var(--panel2);position:relative;
}
.port.sfp{width:56px}
.port .pn{font-weight:650;font-size:13px}
.port .ps{font-size:10px;color:var(--fg2);display:block;line-height:1.2}
.port .jack{
display:block;width:18px;height:12px;margin:3px auto 2px;border-radius:2px;
border:2px solid var(--fg2);border-top-width:5px;opacity:.55;
}
.port.sfp .jack{width:28px;height:10px;border-top-width:3px}
.port.up{border-color:var(--pc);background:color-mix(in srgb,var(--pc) 10%,var(--panel2))}
.port.up .jack{border-color:var(--pc);opacity:1}
.port.up .ps{color:var(--pc);font-weight:600}
.port.dis{opacity:.45;border-style:dashed}
.badge{font-size:11px;font-weight:600;border-radius:10px;padding:1px 8px;background:var(--panel2);color:var(--fg2)}
.badge.ok{color:var(--ok)}.badge.bad{color:var(--bad)}
.seg{display:inline-flex;border:1px solid var(--bd);border-radius:7px;overflow:hidden}
.seg button{
background:var(--panel);border:0;padding:3px 8px;cursor:pointer;font-size:12px;color:var(--fg2);
border-right:1px solid var(--bd);
}
.seg button:last-child{border-right:0}
.seg button.on{background:var(--ac);color:var(--acfg)}
#toasts{position:fixed;right:16px;bottom:16px;z-index:50;display:flex;flex-direction:column;gap:8px}
.toast{
background:var(--panel);border:1px solid var(--bd);border-left:4px solid var(--ac);
border-radius:8px;padding:9px 14px;box-shadow:var(--shadow);font-size:13px;max-width:340px;
animation:tin .18s ease-out;
}
.toast.ok{border-left-color:var(--ok)}.toast.err{border-left-color:var(--bad)}
@keyframes tin{from{transform:translateY(8px);opacity:0}to{transform:none;opacity:1}}
#mback{
position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:40;display:none;
align-items:flex-start;justify-content:center;padding:6vh 16px;
}
#mback.show{display:flex}
#modal{
background:var(--panel);border:1px solid var(--bd);border-radius:12px;box-shadow:var(--shadow);
width:min(760px,96vw);max-height:86vh;display:flex;flex-direction:column;
}
#mhead{display:flex;align-items:center;padding:13px 18px;border-bottom:1px solid var(--bd)}
#mtitle{font-weight:650;font-size:15px;flex:1}
#mx{background:none;border:0;font-size:19px;cursor:pointer;color:var(--fg2);padding:0 2px}
#mbody{padding:14px 18px;overflow:auto}
#mfoot{padding:12px 18px;border-top:1px solid var(--bd);display:flex;gap:10px;justify-content:flex-end}
#mfoot:empty{display:none}
pre.cfg{
background:var(--bg);border:1px solid var(--bd);border-radius:8px;padding:10px 12px;
font:12px/1.5 ui-monospace,Consolas,monospace;white-space:pre-wrap;word-break:break-all;
}
textarea.cfg{
width:100%;min-height:200px;background:var(--bg);border:1px solid var(--bd);border-radius:8px;
padding:10px 12px;font:12px/1.5 ui-monospace,Consolas,monospace;resize:vertical;outline:none;
}
.mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
.mut{color:var(--fg2)}
.small{font-size:12px}
.dangerzone{border-color:color-mix(in srgb,var(--bad) 40%,var(--bd))}
button.danger{background:var(--bad);color:#fff;border-color:transparent}
@media (max-width:820px){
#nav{position:fixed;left:-210px;transition:left .18s;z-index:30;box-shadow:var(--shadow)}
#nav.open{left:0}
#burger{display:block}
main{padding:14px 12px 40px}
#top{padding:8px 10px;gap:7px}
#ttitle{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#dirty{padding:2px 7px}
}
</style>
<script>
(function(){
var pref;
try{pref=localStorage.getItem("theme")||"auto";}catch(e){pref="auto";}
var dark=window.matchMedia("(prefers-color-scheme: dark)").matches;
var t=pref;
if(pref==="auto")t=dark?"dark":"light";
if(pref==="auto-sel")t=dark?"sel-dark":"sel-light";
document.documentElement.dataset.theme=t;
})();
</script>
</head>
<body>
<div class="page" id="page-overview" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="index_heading">Switch Configuration</h1>
<table id="infoTable">
<tr>
<th colspan="2" data-i18n="index_settings">Settings</th>
</tr>
<tbody>
</tbody>
</table>
</div>
<nav id="nav">
<div id="brand">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke-width="2">
<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M6 11v2M10 11v2M14 11v2M18 11v2"/>
</svg>
<span id="brandname">Switch</span>
</div>
<ul id="navlist"></ul>
<div id="navfoot"><span id="fver"></span></div>
</nav>
<div class="page" id="page-ports" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="port_heading">Port Configuration</h1>
<table id="speedtable">
<tr> <th data-i18n="port_col_port">Port</th> <th data-i18n="port_col_name">Name</th> <th data-i18n="port_col_speed">Current Link Speed</th><th data-i18n="port_col_devices">Connected devices</th><th data-i18n="port_col_set_speed">Set Speed</th><th data-i18n="port_col_disabled">Disabled</th><th data-i18n="port_col_apply">Apply</th></tr>
</table>
<h2 style="margin-top:3em" data-i18n="port_mtu_heading">Configure Maximum Frame Size (MTU) forwarded at Port</h2>
<table id="mtutable" style="margin-top:1em">
</table>
</div>
<div id="wrap">
<header id="top">
<button id="burger" title="Menu">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--fg)" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
</button>
<span id="ttitle"></span>
<span id="dirty" data-i18n="hdr_dirty" data-i18n-t="hdr_dirty_t">unsaved changes</span>
<button class="ctl pri" id="saveBtn" data-i18n="hdr_save" data-i18n-t="hdr_save_t">Save to flash</button>
</header>
<div class="page" id="page-stat" style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="popup" class="popup">
<div class="popup-content">
<h2 data-i18n="stat_detailed">Detailed Port Statistics</h2>
<div id="popup_text"></div>
<button id="closePopup" class="action" data-i18n="stat_close">Close</button>
<main>
<section class="tab act" id="tab-dash">
<div class="card"><h2><span data-i18n="d_ports">Ports</span> <span class="hint" data-i18n="d_ports_h">click a port for details</span></h2>
<div id="strip"></div>
</div>
<div class="grid2">
<div class="card"><h2 data-i18n="d_system">System</h2><table class="t" id="sysinfo"></table></div>
<div class="card"><h2><span data-i18n="d_traffic">Traffic</span> <span class="hint" data-i18n="d_traffic_h">packets/s, live</span></h2>
<div class="scrollx"><table class="t" id="traffic">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_link">Link</th><th class="num" data-i18n="d_txpps">TX pps</th><th class="num" data-i18n="d_rxpps">RX pps</th><th class="num" data-i18n="d_txbad">TX bad</th><th class="num" data-i18n="d_rxbad">RX bad</th></tr></thead>
<tbody></tbody></table></div>
</div>
</div>
<h1 data-i18n="stat_heading">Port Statistics</h1>
<table id="statstable">
<tr> <th data-i18n="stat_col_port">Port</th> <th data-i18n="stat_col_name">Name</th> <th data-i18n="stat_col_link">link</th> <th data-i18n="stat_col_tx_good">TX Good</th> <th data-i18n="stat_col_tx_bad">TX Bad</th> <th data-i18n="stat_col_rx_good">RX Good</th> <th data-i18n="stat_col_rx_bad">RX Bad</th> <th data-i18n="stat_col_all"> All Counters </th></tr>
</table>
</div>
</section>
<div class="page" id="page-vlan" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="vlan_heading">VLAN Configuration</h1>
<form id="vform">
<div>
<label for="vlanSelect" data-i18n="vlan_select">VLAN Select:</label>
<select id="vlanSelect" style="margin: 0 0 0 8px">
<option value="" disabled selected data-i18n="vlan_choose">— VLAN Choose —</option>
</select>
</div>
<br/>
<div>
<label for="vid" data-i18n="vlan_id">VLAN ID:</label>
<input type="number" min="1" max="4094" id="vid" name="vid">
<button type="button" style="margin: 0 0 0 24px" onclick="fetchVLAN();" data-i18n="vlan_get_config">Get Configuration</button>
</div>
<br/><br/>
<label for="vname" data-i18n="vlan_name">VLAN Name:</label>
<input type="text" id="vname" name="vname"><br><br>
<br/>
<h2 data-i18n="vlan_tagged">Tagged Ports</h2>
<div id="tPorts"><button type="button" style="transform: translateY(-100%);margin: 0 50px 0 0" onclick="utClicked(true);" data-i18n="vlan_select_all">Select all</button></div>
<h2 data-i18n="vlan_untagged">Untagged Ports</h2>
<div id="uPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="utClicked(false);" data-i18n="vlan_select_all">Select all</button> </div>
<h2 data-i18n="vlan_pvid">Use as default VLAN for incoming traffic (PVID)</h2>
<div id="pPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="pvClicked(true);" data-i18n="vlan_select_all">Select all</button> </div>
<br/> <input style="width:40%;" class="action" id="vlan_sub" onclick="vlanSub();" type="button" data-i18n="vlan_update" value="Update / Create">
</form>
<h2 data-i18n="vlan_configured">Configured VLANs</h2>
<table id="vlanTable" style="width:90%">
<thead>
<tr>
<th>VLAN</th>
<th data-i18n="vlan_col_name">Name</th>
<th data-i18n="vlan_col_member">Member Ports</th>
<th data-i18n="vlan_col_tagged">Tagged Ports</th>
<th data-i18n="vlan_col_untagged">Untagged Ports</th>
<th data-i18n="vlan_col_pvid">PVID Ports</th>
<th data-i18n="vlan_col_delete">Delete</th>
</tr>
</thead>
<tbody id="vlanTableBody">
</tbody>
</table>
</div>
<div class="page" id="page-l2" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="l2_heading">L2 Configuration</h1>
<p><span data-i18n="l2_shown">Shown:</span> <span id="l2count">-</span></p>
<table id="l2table">
<tr>
<th><span class="l2sort" onclick="l2SortBy('port')"><span data-i18n="l2_col_port">Port</span><span id="l2a_port" class="l2arrow"></span></span><br>
<input id="l2f_port" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
<th><span class="l2sort" onclick="l2SortBy('mac')">MAC<span id="l2a_mac" class="l2arrow"></span></span><br>
<input id="l2f_mac" class="l2filter" oninput="l2FilterChanged()" size="14"></th>
<th><span class="l2sort" onclick="l2SortBy('vlan')">VLAN<span id="l2a_vlan" class="l2arrow"></span></span><br>
<input id="l2f_vlan" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
<th><span class="l2sort" onclick="l2SortBy('type')"><span data-i18n="l2_col_type">Type</span><span id="l2a_type" class="l2arrow"></span></span><br>
<input id="l2f_type" class="l2filter" oninput="l2FilterChanged()" size="8"></th>
<th data-i18n="l2_col_remove">Remove Entry</th></tr>
</table>
</div>
<div class="page" id="page-stp" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1>Spanning Tree (RSTP)</h1>
<h2>Mode: <select id="stpMode"><option value="off">Disabled</option><option value="on">Enabled</option></select>
<input style="width:15%;margin-left: 3em;" class="action" id="stp_sub" onclick="stpSub();" type="button" value="Apply"></h2>
<div id="stpStat" style="font-family:monospace;"></div>
<h2>Bridge settings</h2>
<table id="stpBridge">
<tr>
<th>Priority</th><th>Version</th><th>Hello [s]</th><th>Max age [s]</th><th>Fwd delay [s]</th><th>Tx hold</th>
</tr>
<tr>
<td><select id="bPrio"></select></td>
<td><select id="bVer"><option value="rstp">RSTP</option><option value="stp">STP compat</option></select></td>
<td><input id="bHello" type="number" min="1" max="10" style="width:4em"></td>
<td><input id="bMaxage" type="number" min="6" max="40" style="width:4em"></td>
<td><input id="bFwd" type="number" min="4" max="30" style="width:4em"></td>
<td><input id="bTxhold" type="number" min="1" max="10" style="width:4em"></td>
</tr>
</table>
<p style="font-size:small">Changes apply immediately. Edge ports skip the listen period; guard/filter act on received BPDUs.</p>
<h2>Port configuration</h2>
<table id="stpPortsTbl">
<tr>
<th>Port</th><th>State</th><th>Path Cost<br><span style="font-weight:normal;font-size:small">(0 = Auto)</span></th><th>Priority</th><th>Edge Port</th><th>BPDU Filter</th><th>Guard</th><th>Point-to-Point</th>
</tr>
</table>
<h2>Port status</h2>
<table id="stpStatTbl">
<tr>
<th>Port</th><th>Port State</th><th>Role</th><th>Designated Bridge</th><th>Designated Port ID</th><th>Designated Cost</th><th>Oper. Edge</th><th>Oper. P2P</th>
</tr>
</table>
</div>
<div class="page" id="page-mirror" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="mirror_heading">Mirror Configuration</h1>
<label class="tswitch"><span data-i18n="mirror_enabled">Enabled:</span> <input id="me" type="checkbox"></label><br/>
<label for="mp"><span data-i18n="mirror_port">Mirroring Port:</span></label> <input type="number" id="mp" name="mp" min="1" max="9"/>
<h2 data-i18n="mirror_tx">Mirrored Ports (TX)</h2>
<div id="mPortsTX"></div>
<br />
<h2 data-i18n="mirror_rx">Mirrored Ports (RX)</h2>
<div id="mPortsRX"></div>
<br/> <input style="width:15%;" class="action" id="mirror_sub" onclick="mirrorSub();" type="button" data-i18n="mirror_update" value="Update / Create">
<input style="width:15%;" class="action" id="mirror_del" onclick="mirrorDel();" type="button" data-i18n="mirror_disable" value="Disable Mirroring">
</div>
<div class="page" id="page-lag" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="lag_heading">Link Aggregation Groups Configuration</h1>
<h2>LAG 1 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub0" onclick="lagSub(0);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
<div id="mLAG0"></div>
<br />
<h2>LAG 2 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub1" onclick="lagSub(1);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
<div id="mLAG1"></div>
<br />
<h2>LAG 3 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub2" onclick="lagSub(2);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
<div id="mLAG2"></div>
<br />
<h2>LAG 4 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub3" onclick="lagSub(3);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
<div id="mLAG3"></div>
</div>
<div class="page" id="page-eee" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="eee_heading">EEE Status</h1>
<table id="eeetable">
<tr> <th> </th> <th colspan="3" data-i18n="eee_advertising"> Advertising </th> <th colspan="3" data-i18n="eee_partner">Link-Partner advertises</th> <th></th></tr>
<tr> <th data-i18n="eee_port">Port</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th data-i18n="eee_active">Active?</th></tr>
</table>
<div>
<input style="width:20%;" class="action" id="eee_enable" onclick="eeeSub(0, 1);" type="button" data-i18n="eee_enable" value="Enable EEE">
<input style="width:20%;" class="action" id="eee_disable" onclick="eeeSub(0, 0);" type="button" data-i18n="eee_disable" value="Disable EEE">
<section class="tab" id="tab-ports">
<div class="card"><h2 data-i18n="pt_title">Port configuration</h2>
<div class="scrollx"><table class="t" id="ptable">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_name">Name</th><th data-i18n="c_link">Link</th><th data-i18n="pt_conn">Connected</th><th data-i18n="pt_speed">Speed</th><th data-i18n="c_enabled">Enabled</th><th data-i18n="pt_mtu">MTU</th><th></th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" style="margin-top:8px" data-i18n="pt_sfp_note">SFP speed is set per slot and is not saved in the startup config.</p>
</div>
</div>
</section>
<div class="page" id="page-bandwidth" style="margin-left:16%;padding:1px 16px;height:1000px;">
<h1 data-i18n="bw_heading">Ingress and Egress Bandwidth</h1>
<table id="bwtable">
<tr> <th> </th> <th colspan="3" data-i18n="bw_ingress"> Ingress </th> <th colspan="2" data-i18n="bw_egress">Egress</th> <th></th></tr>
<tr> <th data-i18n="bw_col_port">Port</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_flow">Flow Control</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_apply">Apply</th></tr>
</table>
</div>
<div class="page" id="page-system" style="margin-left:16%;padding:1px 16px;height:1000px;">
<div class="tab-bar">
<button class="tab-btn active" onclick="openTab(event, 'system-tab')" data-i18n="sys_tab_system">System</button>
<button class="tab-btn" onclick="openTab(event, 'advanced-tab')" data-i18n="sys_tab_advanced">Advanced</button>
<button class="tab-btn" onclick="openTab(event, 'console-tab')" data-i18n="sys_tab_console">Console</button>
<section class="tab" id="tab-stp">
<div class="card"><h2><span data-i18n="stp_title">Spanning tree</span> <span class="hint" data-i18n="stp_h">RSTP, 802.1w</span></h2>
<label class="row"><span class="lb" data-i18n="c_enabled">Enabled</span>
<span class="switch"><input type="checkbox" id="stpen"><i></i></span></label>
<p class="small mono" id="stpids">-</p>
<div id="stpbridge" style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;margin-top:10px">
<label><span class="small mut" data-i18n="stp_version">Version</span><br><select class="in" id="stpver"><option value="rstp">RSTP</option><option value="stp" data-i18n="stp_v_stp">STP compatible</option></select></label>
<label><span class="small mut" data-i18n="stp_prio">Bridge priority</span><br><select class="in" id="stpprio"></select></label>
<label><span class="small mut" data-i18n="stp_hello">Hello [s]</span><br><input class="in sm" id="stphello" type="number" min="1" max="10"></label>
<label><span class="small mut" data-i18n="stp_maxage">Max age [s]</span><br><input class="in sm" id="stpmaxage" type="number" min="6" max="40"></label>
<label><span class="small mut" data-i18n="stp_fwd">Forward delay [s]</span><br><input class="in sm" id="stpfwd" type="number" min="4" max="30"></label>
<label><span class="small mut" data-i18n="stp_txhold">TX hold</span><br><input class="in sm" id="stptxhold" type="number" min="1" max="10"></label>
</div>
<div style="margin-top:10px"><button class="ctl pri" id="stpbapply" data-i18n="stp_bridge_apply">Apply bridge settings</button></div>
<p class="small mut" style="margin-top:8px" data-i18n="stp_prio_note">Lower priority wins the root election (default 8 = 32768). Settings apply immediately; use Save to flash to keep them.</p>
</div>
<div id="system-tab" class="tab-content active">
<h1 data-i18n="sys_heading">System Settings</h1>
<div class="row">
<div class="lcol"> <label for="hostname" data-i18n="sys_hostname">Hostname:</label></div>
<div class="rcol"> <input id="hostname" type="text" maxlength="23" size="20"/>
<div class="card"><h2 data-i18n="stp_portcfg">Port configuration</h2>
<div class="scrollx"><table class="t" id="stpcfg">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_enabled">Enabled</th><th data-i18n="stp_edge">Edge</th><th data-i18n="stp_pcost">Path cost</th><th data-i18n="stp_pprio">Priority</th><th data-i18n="stp_guard">Guard</th><th data-i18n="stp_filter">BPDU filter</th><th data-i18n="stp_p2p">Point-to-point</th><th></th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" style="margin-top:8px" data-i18n="stp_port_note">Edge ports forward immediately and cause no topology change; Auto treats a port as edge after 3 s without BPDUs, and any received BPDU revokes it. Path cost 0 = automatic.</p>
</div>
<div class="card"><h2 data-i18n="stp_status">Port status</h2>
<div class="scrollx"><table class="t" id="stpstat">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th></tr></thead>
<tbody></tbody></table></div>
</div>
</section>
<section class="tab" id="tab-stats">
<div class="card"><h2><span data-i18n="st_title">Port statistics</span> <span class="hint" data-i18n="st_h">totals since boot</span></h2>
<div class="scrollx"><table class="t" id="stable">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_name">Name</th><th data-i18n="c_link">Link</th><th class="num" data-i18n="st_txg">TX good</th><th class="num" data-i18n="st_txb">TX bad</th><th class="num" data-i18n="st_rxg">RX good</th><th class="num" data-i18n="st_rxb">RX bad</th><th></th></tr></thead>
<tbody></tbody></table></div>
</div>
</section>
<section class="tab" id="tab-vlan">
<div class="card"><h2><span data-i18n="v_title">VLANs</span> <span class="hint"><span data-i18n="v_mgmt">Management VLAN</span>: <span id="vmgmtcur">-</span></span></h2>
<div class="scrollx"><table class="t" id="vtable">
<thead><tr><th data-i18n="v_vid">VID</th><th data-i18n="c_name">Name</th><th data-i18n="v_members">Members</th><th data-i18n="v_tagged">Tagged</th><th data-i18n="v_untagged">Untagged</th><th data-i18n="v_pvid_on">PVID on</th><th></th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" id="vempty" style="display:none;margin-top:6px" data-i18n="v_empty">No VLANs configured.</p>
</div>
<div class="card"><h2 data-i18n="v_editor">VLAN editor</h2>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
<label><span data-i18n="v_vid">VID</span> <input class="in sm" id="vvid" type="number" min="1" max="4094"></label>
<label><span data-i18n="c_name">Name</span> <input class="in" id="vname" size="12" maxlength="15" data-i18n-p="v_optional"></label>
<button class="ctl" id="vload" data-i18n="c_load">Load</button>
<button class="ctl pri" id="vapply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="vmgmt" data-i18n="v_setmgmt" data-i18n-t="v_setmgmt_t">Set as mgmt VLAN</button>
</div>
<div class="scrollx"><table class="t" id="vedit"><tbody></tbody></table></div>
<p class="small mut" style="margin-top:6px" data-i18n="v_legend">U = untagged member, T = tagged member, - = not a member. PVID assigns this VLAN to untagged ingress traffic.</p>
</div>
<div class="card"><h2><span data-i18n="v_ingress">Ingress filtering</span> <span class="hint" data-i18n="v_ingress_h">write-only: the state is not readable from the switch</span></h2>
<div class="scrollx"><table class="t" id="ingress"><tbody></tbody></table></div>
<div style="margin-top:10px"><button class="ctl pri" id="ingapply" data-i18n="v_ing_apply">Apply ingress modes</button></div>
</div>
</section>
<section class="tab" id="tab-l2">
<div class="card"><h2><span data-i18n="l2_title">MAC address table</span>
<span class="hint"><input class="in" id="l2filter" data-i18n-p="l2_filter" size="14"></span></h2>
<div style="display:flex;gap:10px;margin-bottom:10px">
<button class="ctl" id="l2refresh" data-i18n="c_refresh">Refresh</button>
<button class="ctl" id="l2flush" data-i18n="l2_flush">Flush learned entries</button>
<span class="small mut" id="l2count" style="align-self:center"></span>
</div>
<div class="scrollx"><table class="t" id="l2table">
<thead><tr><th data-sort="pport"><span data-i18n="c_port">Port</span><span class="arrow"></span></th><th data-sort="mac">MAC<span class="arrow"></span></th><th data-sort="vlan">VLAN<span class="arrow"></span></th><th data-sort="stat"><span data-i18n="c_type">Type</span><span class="arrow"></span></th><th></th></tr></thead>
<tbody></tbody></table></div>
</div>
</section>
<section class="tab" id="tab-mirror">
<div class="card"><h2 data-i18n="m_title">Port mirroring</h2>
<div style="display:flex;gap:12px;align-items:center;margin-bottom:10px;flex-wrap:wrap">
<span class="badge" id="mstate">off</span>
<label><span data-i18n="m_monitor">Monitor port</span> <select class="in" id="mport"></select></label>
<button class="ctl pri" id="mapply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="moff" data-i18n="c_disable">Disable</button>
</div>
<div class="scrollx"><table class="t" id="mtable"><tbody></tbody></table></div>
<p class="small mut" style="margin-top:6px" data-i18n="m_note">Both = mirror RX and TX of the port to the monitor port.</p>
</div>
</section>
<section class="tab" id="tab-lldp">
<div class="card"><h2 data-i18n="lldp_title">LLDP</h2>
<label class="row"><span class="lb" data-i18n="lldp_enabled">Enabled</span>
<span class="switch"><input type="checkbox" id="lldp_en"><i></i></span>
</label>
<div class="scrollx"><table class="t" id="lldp_table">
<thead><tr><th data-i18n="c_port">Port Number</th><th data-i18n="lldp_portname">Name</th><th data-i18n="lldp_enable">Send Frames</th></tr></thead>
<tbody></tbody></table></div>
</div>
</section>
<section class="tab" id="tab-isolate">
<div class="card"><h2 data-i18n="iso_title">Port isolation</h2>
<p class="small mut" style="margin-bottom:10px" data-i18n="iso_intro">A frame entering a port may only leave through the ports ticked in its row.</p>
<div class="scrollx"><table class="t" id="isotbl" style="width:auto"><thead></thead><tbody></tbody></table></div>
<p class="small mut" style="margin-top:6px" data-i18n="iso_legend">The cell of the port itself is always allowed.</p>
<div style="display:flex;gap:12px;margin-top:10px">
<button class="ctl pri" id="isoapply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="isoall" data-i18n="iso_all">Allow all</button>
</div>
</div>
</section>
<section class="tab" id="tab-lag">
<div id="lagwrap" class="grid2"></div>
<p class="small mut" style="margin-top:10px" data-i18n="lag_note">A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.</p>
</section>
<section class="tab" id="tab-eee">
<div class="card"><h2 data-i18n="e_title">Energy Efficient Ethernet</h2>
<div class="scrollx"><table class="t" id="etable">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="e_adv">Advertised</th><th data-i18n="e_lp">Link partner</th><th data-i18n="e_active">Active</th><th data-i18n="e_enable">Enable</th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" style="margin-top:8px" data-i18n="e_note">Advertised and link-partner flags per speed: 100M, 1G, 2.5G. SFP ports do not support EEE.</p>
</div>
</section>
<section class="tab" id="tab-bw">
<div class="card"><h2><span data-i18n="bw_title">Bandwidth limits</span> <span class="hint" data-i18n="bw_h">Mbit/s, 0.016-10000</span></h2>
<div class="scrollx"><table class="t" id="btable">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="bw_in">Ingress limit</th><th>Mbit/s</th><th data-i18n="bw_exceed">When exceeded</th><th data-i18n="bw_out">Egress limit</th><th>Mbit/s</th><th></th></tr></thead>
<tbody></tbody></table></div>
</div>
<div class="card"><h2><span data-i18n="sc_title">Storm control</span> <span class="hint" data-i18n="sc_h">per port and traffic type, empty = off</span></h2>
<div class="scrollx"><table class="t" id="sctable">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="sc_bcast">Broadcast</th><th data-i18n="sc_mcast">Multicast</th><th data-i18n="sc_ucast">Unknown unicast</th><th data-i18n="sc_umcast">Unknown multicast</th><th></th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" style="margin-top:8px" data-i18n="sc_note">Frames of a type above its limit are dropped where they enter the switch.</p>
</div>
</section>
<section class="tab" id="tab-system">
<div class="grid2">
<div class="card"><h2 data-i18n="sy_network">Network</h2>
<label class="row"><span class="lb" data-i18n="i_host">Hostname</span><input class="in" id="sy-host" maxlength="23"></label>
<label class="row"><span class="lb" data-i18n="i_ip">IP address</span><input class="in" id="sy-ip" size="15"></label>
<label class="row"><span class="lb" data-i18n="i_mask">Netmask</span><input class="in" id="sy-mask" size="15"></label>
<label class="row"><span class="lb" data-i18n="i_gw">Gateway</span><input class="in" id="sy-gw" size="15"></label>
<div style="display:flex;gap:10px;margin-top:12px">
<button class="ctl pri" id="sy-apply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="sy-dhcp" data-i18n="sy_dhcp" data-i18n-t="sy_dhcp_t">Use DHCP</button>
</div>
</div>
<div class="row">
<div class="lcol"> <label for="ip" data-i18n="sys_ip">IP address:</label></div>
<div class="rcol"> <input id="ip" type="text" size="15"/>
</div>
<div class="card"><h2 data-i18n="sy_services">Services</h2>
<label class="row" style="justify-content:space-between"><span data-i18n="sy_igmp">IGMP snooping</span>
<span class="switch"><input type="checkbox" id="sy-igmp"><i></i></span></label>
<label class="row" style="justify-content:space-between"><span data-i18n="i_syslog">Syslog</span>
<span class="switch"><input type="checkbox" id="sy-syslog"><i></i></span></label>
<div class="row" style="display:flex;gap:8px;align-items:center;margin:-2px 0 10px">
<span class="small mut" data-i18n="sy_server">Server</span>
<input class="in" id="sy-sysip" style="flex:1;min-width:0;width:0" data-i18n-p="sy_sysip">
<span class="small mut" data-i18n="sy_port">Port</span>
<input class="in" id="sy-sysport" style="width:64px" type="number" min="1" max="65535"></div>
<label class="row" style="justify-content:space-between"><span data-i18n="sy_sesstmo">Session timeout (s)</span>
<input class="in" id="sy-sesstmo" style="width:80px" type="number" min="1" max="65535"></label>
<p class="small mut" data-i18n="sy_services_note">Service state reflects the startup config; runtime state is not readable.</p>
</div>
<div class="row">
<div class="lcol"> <label for="netmask" data-i18n="sys_netmask">Netmask:</label></div>
<div class="rcol"> <input id="netmask" type="text" size="15"/>
</div>
<div class="card"><h2 data-i18n="sy_password">Admin password</h2>
<label class="row"><span class="lb" data-i18n="sy_newpw">New password</span><input class="in" id="sy-pw1" type="password" maxlength="20"></label>
<label class="row"><span class="lb" data-i18n="sy_repeat">Repeat</span><input class="in" id="sy-pw2" type="password" maxlength="20"></label>
<button class="ctl pri" id="sy-pwapply" style="margin-top:8px" data-i18n="sy_pwapply">Change password</button>
<p class="small mut" style="margin-top:8px" data-i18n="sy_pw_note">Takes effect immediately; save to flash to keep it after reboot.</p>
</div>
<div class="row">
<div class="lcol"> <label for="gw" data-i18n="sys_gateway">Gateway:</label></div>
<div class="rcol"> <input id="gw" type="text" size="15"/>
<div class="card"><h2 data-i18n="sy_console">Console</h2>
<div style="display:flex;gap:8px">
<input class="in" id="sy-cmd" style="flex:1" data-i18n-p="sy_cmd" spellcheck="false">
<button class="ctl pri" id="sy-send" data-i18n="sy_send">Send</button>
</div>
<pre class="cfg" id="sy-cout" style="margin-top:10px;min-height:70px;max-height:180px;overflow:auto"></pre>
</div>
<div class="row">
<div class="lcol"> <label for="model" data-i18n="sys_model">Model:</label></div>
<div class="rcol"> <span id="model">-</span>
</div>
</div>
<div class="row">
<div class="lcol"> <label for="lang-select" data-i18n="sys_language">Language:</label></div>
<div class="rcol">
<select id="lang-select" onchange="changeLang()">
<div class="card"><h2 data-i18n="sy_display">Display</h2>
<label class="row"><span class="lb" data-i18n="sy_lang">Language</span>
<select class="in" id="langSel">
<option value="de">Deutsch</option>
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="ja">日本語</option>
<option value="zh">中文</option>
</select>
</div>
</select></label>
<label class="row"><span class="lb" data-i18n="sy_theme">Theme</span>
<select class="in" id="themeSel">
<option value="auto" data-i18n="th_auto">System</option>
<option value="auto-sel" data-i18n="th_auto_sel">System (Selenized)</option>
<option value="light" data-i18n="th_light">Light</option>
<option value="dark" data-i18n="th_dark">Dark</option>
<option value="sel-light" data-i18n="th_sel_light">Selenized Light</option>
<option value="sel-dark" data-i18n="th_sel_dark">Selenized Dark</option>
</select></label>
<p class="small mut" data-i18n="sy_display_note">Stored in this browser only.</p>
</div>
<div class="row">
<div class="lcol"> <label for="mgmtvlan" data-i18n="sys_mgmt_vlan">Management VLAN:</label></div>
<div class="rcol">
<select id="mgmtvlan" onchange="mgmtVlanChanged()">
</select>
</div>
</div>
<div class="card" style="margin-top:16px"><h2><span data-i18n="sy_startup">Startup configuration</span>
<span class="hint"><span id="cfgbytes"></span>, <span data-i18n="sy_replayed">replayed on every boot</span></span></h2>
<textarea class="cfg" id="cfgedit" spellcheck="false"></textarea>
<div style="display:flex;gap:10px;margin-top:10px;flex-wrap:wrap">
<button class="ctl" id="cfgreload" data-i18n="sy_reload">Reload from flash</button>
<button class="ctl pri" id="cfgwrite" data-i18n="sy_write">Write to flash</button>
<span class="small mut" style="align-self:center" data-i18n="sy_unknown_note">Unknown lines are highlighted before writing.</span>
</div>
<br/>
<span data-i18n="sys_ip_note">When updating the above settings, remember to point your browser to the new IP afterwards:</span><br/>
<input style="width:40%;" class="action" id="ip_sub" onclick="ipSub();" type="button" data-i18n="sys_update" value="Update Settings"><br/>
<br/>
<span data-i18n="sys_save_label">Save all current settings to Flash:</span><br/>
<input style="width:40%;" class="action" id="flash_sub" onclick="flashSave();" type="button" data-i18n="sys_save" value="Save Settings to Flash">
</div>
<div id="advanced-tab" class="tab-content">
<h1 data-i18n="sys_advanced">Advanced Settings</h1>
<div class="lcol"> <label for="config_display" data-i18n="sys_startup_config">Startup configuration:</label></div>
<textarea id="config_display" rows="8" cols="60"></textarea>
<br/><br/>
<span data-i18n="sys_startup_warn">Be careful when saving the directly edited startup configuration, you can lock yourself out:</span><br/>
<input style="width:40%;" class="action" id="clear_config" onclick="clearConfig();" type="button" data-i18n="sys_clear_config" value="Clear Startup Config">
<br/>
<input style="width:40%;" class="action" id="flash_startup_sub" onclick="flashStartupSave();" type="button" data-i18n="sys_save_startup" value="Save Startup Settings to Flash">
<br/>
<input style="width:40%;" class="action" id="switch_reset" onclick="resetSwitch();" type="button" data-i18n="sys_reset" value="Reset Switch">
<div class="card dangerzone" style="margin-top:16px"><h2 data-i18n="sy_maint">Maintenance</h2>
<button class="ctl danger" id="sy-reboot" data-i18n="sy_reboot">Reboot switch</button>
</div>
</section>
<div id="console-tab" class="tab-content">
<h1 data-i18n="sys_console">Console Command</h1>
<label for="console_command" data-i18n="sys_enter_cmd">Enter command:</label>
<input type="text" id="console_cmd" name="console_cmd" style="width:40%;">
<input style="width:20%;" class="action" id="cmd_sub" onclick="cmdSub();" type="button" data-i18n="sys_send_cmd" value="Send Command"><br/>
<br/><br/>
<span data-i18n="sys_console_warn">Be careful when entering console commands, you can lock yourself out!</span><br/>
<br/>
<pre id="console_out" style="height:22em;overflow:auto;white-space:pre-wrap;border:1px solid;padding:0.5em;"></pre>
<section class="tab" id="tab-fw">
<div class="card"><h2 data-i18n="fw_title">Firmware update</h2>
<p class="small mut" style="margin-bottom:12px" data-i18n="fw_intro">Upload an RTLPlayground image (512 KiB .bin). The image is staged to flash and CRC-checked; on success the switch reboots itself, re-verifies the image and applies it. The startup configuration is preserved.</p>
<input type="file" id="fwfile" accept=".bin">
<div id="fwinfo" class="small" style="margin:10px 0"></div>
<div style="display:flex;gap:10px;align-items:center">
<button class="ctl pri" id="fwup" disabled data-i18n="fw_upload">Upload</button>
<progress id="fwprog" max="100" value="0" style="display:none;width:220px"></progress>
<span class="small" id="fwstat"></span>
</div>
</div>
</div>
</section>
</main>
</div>
<div class="page" id="page-update" style="margin-left:16%;padding:1px 16px;height:1000px;width:40%;">
<h1 data-i18n="update_heading">Firmware Update</h1>
<form enctype="multipart/form-data" action="/upload" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
<span data-i18n="update_instruction">Choose a firmware update file to upload:</span> <br/> <br/>
<input name="uploadedfile" type="file" accept=".bin" /><br />
<input style="margin-top:3em" type="submit" data-i18n="update_upload" value="Upload File" />
</form>
</div>
</body>
<div id="toasts"></div>
<div id="mback"><div id="modal">
<div id="mhead"><span id="mtitle"></span><button id="mx">&#x2715;</button></div>
<div id="mbody"></div><div id="mfoot"></div>
</div></div>
<script src="/app.js"></script>
</body>
</html>
+97 -23
View File
@@ -1,30 +1,104 @@
<!DOCTYPE html>
<html>
<title data-i18n="login_title">RTL Switch Login</title>
<link rel="stylesheet" href="style.css">
<script src="/main.js"></script>
<script>
function removeNote() {
document.getElementById("incorrect").innerHTML = "";
}
window.addEventListener("load", function() {
if (document.referrer.endsWith("login.html"))
document.getElementById("incorrect").innerHTML = t('login_wrong');
});
</script>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Switch Login</title>
<style>
:root{
--bg:#f4f6f8;--panel:#ffffff;--bd:#d7dde4;--fg:#1a222c;--fg2:#5c6773;
--ac:#0969da;--acfg:#ffffff;--bad:#cf222e;
}
:root[data-theme="dark"]{
--bg:#0e1116;--panel:#161b22;--bd:#2c333c;--fg:#e6edf3;--fg2:#8d96a0;
--ac:#4c8ef7;--acfg:#ffffff;--bad:#f85149;
}
:root[data-theme="sel-dark"]{
--bg:#103c48;--panel:#184956;--bd:#2d5b69;--fg:#adbcbc;--fg2:#72898f;
--ac:#4695f7;--acfg:#ffffff;--bad:#fa5750;
}
:root[data-theme="sel-light"]{
--bg:#fbf3db;--panel:#ece3cc;--bd:#d5cdb6;--fg:#53676d;--fg2:#909995;
--ac:#0072d4;--acfg:#ffffff;--bad:#d2212d;
}
*{box-sizing:border-box;margin:0}
body{
font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
background:var(--bg);color:var(--fg);
min-height:100vh;display:flex;align-items:center;justify-content:center;
}
.card{
background:var(--panel);border:1px solid var(--bd);border-radius:12px;
padding:36px 32px;width:min(360px,92vw);
box-shadow:0 8px 30px rgba(0,0,0,.12);
}
.logo{display:flex;align-items:center;gap:10px;margin-bottom:6px}
h1{font-size:19px;font-weight:650}
p.sub{color:var(--fg2);font-size:13px;margin-bottom:24px}
label{display:block;font-size:13px;color:var(--fg2);margin-bottom:6px}
input[type=password]{
width:100%;padding:10px 12px;font-size:15px;color:var(--fg);
background:var(--bg);border:1px solid var(--bd);border-radius:8px;outline:none;
}
input[type=password]:focus{border-color:var(--ac)}
button{
width:100%;margin-top:18px;padding:10px;font-size:15px;font-weight:600;
color:var(--acfg);background:var(--ac);border:0;border-radius:8px;cursor:pointer;
}
button:hover{filter:brightness(1.1)}
#err{color:var(--bad);font-size:13px;min-height:20px;margin-top:14px;text-align:center}
</style>
<script>
(function(){
var pref;
try{pref=localStorage.getItem("theme")||"auto";}catch(e){pref="auto";}
var dark=window.matchMedia("(prefers-color-scheme: dark)").matches;
var t=pref;
if(pref==="auto")t=dark?"dark":"light";
if(pref==="auto-sel")t=dark?"sel-dark":"sel-light";
document.documentElement.dataset.theme=t;
})();
</script>
</head>
<body class="login_page">
<div class = "center">
<h1 data-i18n="login_heading"> RTL Switch Login</h1>
<form method="post" action="login">
<div class="txt_field">
<input name="pwd" type="password" autocomplete="current-password" onclick="removeNote()" required />
<span></span>
<label data-i18n="login_password">Password</label>
<body>
<div class="card">
<div class="logo">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="var(--ac)" stroke-width="2">
<rect x="2" y="7" width="20" height="10" rx="2"/>
<path d="M6 11v2M10 11v2M14 11v2M18 11v2"/>
</svg>
<h1 id="hn">Switch Login</h1>
</div>
<input type="submit" data-i18n="login_login" value="Login"/>
<h3 id="incorrect" style="margin-top: 5em;"></h3>
<p class="sub" id="sub">RTLPlayground management interface</p>
<form method="post" action="login">
<label for="pwd" id="lp">Password</label>
<input id="pwd" name="pwd" type="password" autocomplete="current-password" autofocus required
oninput="document.getElementById('err').textContent=''">
<button type="submit" id="lb">Sign in</button>
<div id="err"></div>
</form>
</div>
<script>
(function(){
var L={
de:["Switch-Anmeldung","RTLPlayground-Verwaltungsoberfläche","Passwort","Anmelden","Falsches Passwort, bitte erneut versuchen."],
en:["Switch Login","RTLPlayground management interface","Password","Sign in","Wrong password, try again."],
ja:["スイッチ ログイン","RTLPlayground 管理インターフェース","パスワード","ログイン","パスワードが違います。もう一度お試しください。"],
zh:["交换机登录","RTLPlayground 管理界面","密码","登录","密码错误,请重试。"],
es:["Inicio de sesión del switch","Interfaz de gestión de RTLPlayground","Contraseña","Iniciar sesión","Contraseña incorrecta, inténtalo de nuevo."],
fr:["Utilisateur du commutateur","Interface de management RTLPlayground","Mot de passe","Se connecter","Mot de passe erronné, essayez à nouveau."]
};
var l=null;
try{l=localStorage.getItem("rtl_lang");}catch(e){}
if(!L[l])l=(navigator.language||"en").slice(0,2);
var s=L[l]||L.en;
document.title=s[0];
document.getElementById("hn").textContent=s[0];
document.getElementById("sub").textContent=s[1];
document.getElementById("lp").textContent=s[2];
document.getElementById("lb").textContent=s[3];
if(document.referrer.endsWith("login.html"))document.getElementById("err").textContent=s[4];
})();
</script>
</body>
</html>
-2862
View File
File diff suppressed because it is too large. Load diff
-26
View File
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
<g id="layer1" transform="translate(-314.07 -485.06)">
<g id="g1" transform="translate(256.16 417.12)">
<rect id="r1" style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:4;fill:#ffffff" rx="4.514" ry="4.514" height="90.625" width="117.88" y="69.93" x="59.896"/>
<g id="g2"> <g id="g3">
<path class="bg" id="rect3787"
d="m73.958 75.66h89.41c2.3083 0 4.1667 1.8583 4.1667 4.1667l0.00028 52.653h-21.742v9.414h-8.694l0.00004 12.586h-36.871l-0.00004-12.586h-8.694v-9.414h-21.742l-0.000336-52.653c-0.000011-2.3083 1.8583-4.1667 4.1667-4.1667z"
style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:2.5;fill:#dcdcdc"/>
<g id="g4" transform="translate(-0.368)" style="stroke:#191919;stroke-linecap:square;fill:#666666">
<rect id="r2" height="22.786" width="3.9062" y="75.747" x="80.556"/>
<rect id="r3" height="22.786" width="3.9062" y="75.747" x="153.6" />
<rect id="r4" height="22.786" width="3.9062" y="75.747" x="132.73"/>
<rect id="r5" height="22.786" width="3.9062" y="75.747" x="122.3"/>
<rect id="r6" height="22.786" width="3.9062" y="75.747" x="111.86"/>
<rect id="r7" height="22.786" width="3.9062" y="75.747" x="101.43"/>
<rect id="r8" height="22.786" width="3.9062" y="75.747" x="90.99"/>
<rect id="r9" height="22.786" width="3.9062" y="75.747" x="143.17"/>
</g>
</g>
<rect class="led" id="r10" style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:2;fill:#1dfe0a" height="13.89" width="17.1" y="137.03" x="69.542"/>
<rect class="led" id="r11" style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:2;fill:#ffd10a" height="13.89" width="17.1" y="137.03" x="150.68"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

-24
View File
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<g id="layer1" transform="translate(-41.68,-55.95)">
<g id="g1" transform="translate(-177.45,-12.07)">
<g id="g2" transform="matrix(1.46,0,0,1.44,51.31,-317.4)">
<path id="p1"
d="m 117.94442,272.67478 h 7.64646 c 0.21062,0 0.38018,0.16649 0.38018,0.3733 v 4.69239 c 0,0.2068 -0.16956,0.3733 -0.38018,0.3733 h -2.1027 v 0.31956 h -3.44117 v -0.31956 h -2.1027 c -0.21062,0 -0.38018,-0.16649 -0.38018,-0.3733 v -4.69239 c 0,-0.20681 0.16956,-0.3733 0.38018,-0.3733 z"
style="fill:#ffffff;stroke:#000000;stroke-width:0.4"/>
<path class="bg" id="p2"
d="m 118.36775,273.15632 h 6.79794 c 0.18725,0 0.338,0.14695 0.338,0.32946 v 4.14153 c 0,0.18252 -0.15074,0.32946 -0.338,0.32946 h -6.79794 c -0.18724,0 -0.338,-0.14695 -0.338,-0.32946 v -4.14153 c 0,-0.18252 0.15074,-0.32946 0.338,-0.32946 z"
style="fill:#000000;stroke-width:0.26" />
<path d="m 118.65,273.53 h 6.24 v 4.05 h -6.24 z" style="fill:#1a1a1a;stroke-width:0.26" id="p3" />
<path d="m 118.86,276.61 h 5.82" style="fill:#1a1a1a;stroke:#000000;stroke-width:0.26" id="p4" />
<path d="m 124.05,274.25 h 1.39 v 0.72 h -1.39 z" style="fill:#1a1a1a;stroke-width:0.26" id="p5" />
<path d="m 124.05,276 h 1.39 v 0.72 h -1.39 z" style="fill:#1a1a1a;stroke-width:0.26" id="p6" />
<path transform="matrix(0.26,0,0,0.26,116.9,272.11)" d="M 7.3648613,7.0152205 H 29.369861" style="opacity:0.61257;fill:#4d4d4d;stroke:#4d4d4d;filter:url(#filter17794-0)" id="p7" />
</g>
<g id="g3" style="opacity:1;fill:#37d733;" transform="matrix(0.93,0,0,0.92,62.18,-43.11)">
<circle class="led" id="c1" cx="176.86" cy="125.7" r="0.93"/>
<circle class="led" id="c2" r="0.93" cy="125.7" cx="180.84"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

-172
View File
@@ -1,172 +0,0 @@
h1, h2, h3 {
color: #226;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
width: 12%;
height: calc(100% - 50px);
position: fixed;
top: 50px;
overflow: auto;
}
li a {
background-color: #aaf;
display: block;
color: #000;
padding: 8px 16px;
text-decoration: none;
}
/* Change the link color on hover */
li a:hover {
background-color: #226;
color: white;
}
table, th, td {
border: 1px solid #226;
padding: 8px;
}
table {
border-collapse: collapse;
width: 60%;
}
td {
text-align: right;
}
input[type=submit] { padding: 8px 16px;background-color:#aaf;color:#000; margin-bottom: 2em;border-radius: 15px;width: 100%;}
input[type=submit]:hover { background-color: #226; color: white;}
input[type=file] { padding: 8px 16px;background-color:#aaf;color:#000; margin-bottom: 2em;border-radius: 15px;width: 60%;}
input[type=file]:hover { background-color: #226; color: white;}
b
button {padding: 8px; background-color:#99f; color:#000;border-radius: 15px;}
button:hover { background-color: #226; color: white;}
.action{padding: 8px 16px;margin-top: 2em;margin-right: 3em; background-color: #aaf;color: #000;border-radius: 15px; width: 100%;}
.action:hover { background-color: #226; color: white;}
/* Port selection inputs */
.psel {
position: absolute;
opacity: 0;
width: 0;
height: 0;
margin-top: 1em;
}
.psel + img {
cursor: pointer;
opacity: 0.4;
padding: 8px;
}
.psel:checked + img {
/* outline: 2px solid #f00;*/
opacity: 1.0;
}
object, img {
margin: 0.5em 0.5em;
}
.cbgroup {
display: inline-block;
text-align: center;
}
.cbgroup label {
display: block;
}
.disabled{ opacity: .4; background-color: #f88; color: #000}
.isSFP{ opacity: .4; background-color: #660;}
.isNOK{ color: #900;}
.isOK{ color: #090;}
.ip{padding:8px 16px;margin-bottom: 1em;margin-left: 1em}
.rotext{display:inline-block;padding:8px 16px;margin-bottom: 1em;margin-left: 1em}
.row {display: flex;}
.rcol {flex: 90%;}
.lcol {flex: 10%;}
/* Login page */
.login_page {margin: 0; padding: 0; background: #aaf; height: 100vh; overflow: hidden;}
.center{
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -40% );
width: 500px; height: 400px;
background: white;
border-radius: 2px;
}
.center h1 {
text-align: center;
border-bottom: 1px solid silver;
}
.center form {
padding: 0 60px;
box-sizing: border-box;
}
form .txt_field{
position: relative;
border-bottom: 2px solid #adadad;
margin: 30px 0;
}
.txt_field input { width: 100%; padding: 0 5px; height: 40px; font-size: 16px; border: none;
background: none;
outline: none;}
.txt_field label { position: absolute; top: 50%; left: 5px; color:#adadad; transform: translateY(-50%);
font-size: 16px; pointer-events: none;transition: .5s; }
.txt_field span::before{ content:''; position: absolute; top: 40px; left:0; width: 100%; height: 2px; background: #aaf;}
.txt_field input:focus ~ label,
.txt_field input:valid ~ label { top: -5px; color: #aaf; }
.tooltip {
position: relative; display: inline-block; cursor: pointer;
}
.tooltiptext {
visibility: hidden;
width: 260px;
background-color: #226;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
top: 80%;
left: 50%;
margin-left: -130px;
}
.tooltiptext::after {
content: "";
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent black transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.tt_table {
width: 100%; font-size:90%;
}
.tt_table td {
text-align: left;
}
select { text-align-last: right; font-family: monospace}
option { direction: rtl; font-family: sans-serif}
#vlanTable td { text-align: left; }
.l2sort{cursor:pointer;user-select:none}
.l2sort:hover{text-decoration:underline}
.l2arrow{opacity:0.55;font-size:0.85em}
.l2filter{width:100%;box-sizing:border-box;font-weight:normal;font-size:0.9em}
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" version="1.1" viewBox="0 0 577 434.38">
<defs>
<radialGradient id="rG39" gradientUnits="userSpaceOnUse" cy="676.79" cx="508.65" gradientTransform="matrix(1,0,0,0.5704,-2,274.75)" r="86.402">
<stop stop-color="#000000" offset="0"/>
<stop stop-color="#000000" stop-opacity="0" offset="1"/>
</radialGradient>
<filter id="f39" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="2.8"/>
</filter>
</defs>
<g transform="translate(-101.235,-272.06)">
<path d="m420.75,672.71v-18.688l82.055-42.01,89.748,35.306v18.414l-93.012,43.848-78.792-36.871z" transform="matrix(3.115,0,0,3.115,-1188.5,-1524.8)" filter="url(#f39)" fill="url(#rG39)"/>
<path style="color:#000000;" fill="#3a68b0" d="M143.37,387.98,384.43,501.62,630.32,385.7,389.26,272.06z"/>
<path style="color:#000000;" fill="#5d9ce1" d="M384.43,501.62v141.21l245.89-115.92v-141.21z"/>
<path style="color:#000000;" fill="#1b3a88" d="M143.37,387.98,384.43,501.62v141.21l-241.06-113.64z"/>
<path fill-opacity="0.7" fill="#f2fdff" d="m422.47,315.78-38.514,5.6218-38.514,5.6218,16.367,5.3307-46.555,22.107,30.52,9.9405,46.555-22.107,16.367,5.3308,6.8691-15.929,6.9054-15.917z"/>
<path fill-opacity="0.7" fill="#f2fdff" d="m269.31,421.78,6.8691-15.929,6.8691-15.929,16.367,5.3308,46.555-22.107,30.52,9.9405-46.555,22.107,16.367,5.3307-38.514,5.6218-38.477,5.6336z"/>
<path fill-opacity="0.7" fill="#f2fdff" d="m507.05,352.03-38.514,5.6218-38.514,5.6218,16.367,5.3307-46.555,22.107,30.52,9.9405,46.555-22.107,16.367,5.3308,6.8691-15.929,6.9054-15.917z"/>
<path fill-opacity="0.7" fill="#f2fdff" d="m353.9,458.03,6.8691-15.929,6.8691-15.929,16.367,5.3308,46.555-22.107,30.52,9.9405-46.555,22.107,16.367,5.3307-38.514,5.6218-38.477,5.6336z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

+93 -61
View File
@@ -12,7 +12,11 @@
#include "debug.h"
#define SESSION_ID_LENGTH 12
#define SESSION_TIMEOUT 200
#define SESSION_TIMEOUT_DEFAULT 200
/* Seconds of inactivity after which a session cookie stops validating.
* Configurable with the "session" command, see cmd_parser.c. */
__xdata uint16_t session_timeout = SESSION_TIMEOUT_DEFAULT;
#define CMARK_S 6
@@ -22,9 +26,9 @@
extern volatile __xdata uint8_t sfr_data[4];
extern volatile __xdata uint32_t ticks;
extern __xdata uint8_t cmd_capture; /* owned by rtlplayground.c, see write_char_no_syslog() */
extern __code uint8_t * __code hex;
extern __code struct f_data f_data[];
extern __code char * __code mime_strings[];
extern __code const uint8_t * __code const hex;
extern __code const struct f_data f_data[];
extern __code const char * __code const mime_strings[];
extern __xdata struct flash_region_t flash_region;
extern __xdata uint32_t flash_size;
@@ -37,6 +41,7 @@ __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
__xdata uint8_t entry;
__xdata uint16_t slen;
__xdata uint16_t o_idx;
__xdata uint16_t sent_len;
__xdata uint16_t len_left;
__xdata uint16_t cont_len;
__xdata uint32_t cont_addr;
@@ -76,7 +81,6 @@ __xdata uint8_t fw_reset_pending;
__xdata char session_id[SESSION_ID_LENGTH + 1];
__xdata uint8_t authenticated;
__xdata uint32_t now;
__xdata uint8_t * __xdata timeptr;
__xdata uint32_t last_session_use;
#define TSTATE_NONE 0
@@ -127,7 +131,7 @@ uint8_t find_entry(__xdata uint8_t *e)
}
bool is_word(__xdata uint8_t *xdata_str_p, __code uint8_t * __xdata code_str_p)
bool is_word(__xdata uint8_t *xdata_str_p, __code const uint8_t * __xdata code_str_p)
{
uint8_t u, c;
@@ -149,7 +153,7 @@ bool is_word(__xdata uint8_t *xdata_str_p, __code uint8_t * __xdata code_str_p)
/* name must be lower-case, starting with the '\n' of the previous line's end */
__xdata uint8_t *header_value(__xdata uint8_t *p, __code uint8_t *name)
__xdata uint8_t *header_value(__xdata uint8_t *p, __code const uint8_t *name)
{
uint8_t u, c;
@@ -341,14 +345,16 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
read_reg_timer(&now);
if (session) {
if (now - last_session_use > SESSION_TIMEOUT) {
if (session && session_id[0]) {
if (now - last_session_use > session_timeout) {
dbg_string("Session expired\n");
} else {
if (is_word_x(session, session_id))
if (is_word_x(session, session_id)) {
authenticated = 1;
else
last_session_use = now;
} else {
dbg_string("Invalid session cookie!\n");
}
}
}
return p;
@@ -375,8 +381,8 @@ void gen_random_hex_chars(__xdata uint8_t * b, __xdata uint8_t bytes)
static uint8_t config_take(void)
{
// #386: needs static, otherwise it still lands in SRAM/DSEG
static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last;
__xdata uint8_t cfg_bl;
static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last, cfg_i;
__xdata uint8_t cfg_bl, cfg_run;
cfg_bl = strlen_x(boundary);
@@ -418,6 +424,13 @@ static uint8_t config_take(void)
// the payload plus its terminator must fit the sector
if (cfg_end - cfg_body + 1 > CONFIG_LEN)
return 2;
cfg_run = 0;
for (cfg_i = cfg_body; cfg_i < cfg_end; cfg_i++) {
if (config_buf[cfg_i] == '\n')
cfg_run = 0;
else if (++cfg_run >= CMD_BUF_SIZE - 1)
return 2;
}
config_buf[cfg_end] = 0;
flash_region.addr = CONFIG_START;
flash_sector_erase();
@@ -530,6 +543,19 @@ uint8_t stream_upload(void)
crc16_bank1(upload_settings.p + upload_settings.bptr);
flash_buf[write_len++] = upload_settings.p[upload_settings.bptr++];
if (write_len >= FLASH_PAGE_SIZE) {
/* The staged image spans FIRMWARE_UPLOAD_START to twice that,
* the span check_and_flash_update_image() reads back. Nothing
* else bounds uptr: a body whose closing boundary never arrives
* keeps writing, and on a flash exactly this size the address
* wraps onto the running image at zero. */
if (uptr >= (uint32_t)FIRMWARE_UPLOAD_START * 2) {
print_string("Upload runs past the image area! Aborting.\n");
slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nContent-Length: 30\r\n"
"Content-Type: text/plain\r\n\r\n"
"NO: upload exceeds image area\n");
s->tstate = TSTATE_NONE;
return 0;
}
dbg_string("len: "); dbg_short(write_len); dbg_char(' ');
dbg_string("CRC16: "); dbg_short(crc_value); dbg_char('\n');
if (uptr % FLASH_SECTOR_SIZE == 0) {
@@ -559,7 +585,7 @@ static void handle_config_fragment(__xdata uint8_t *p)
__xdata uint16_t frag_len;
uint8_t taken;
frag_len = uip_len - (p - uip_appdata);
frag_len = uip_len - (p - (__xdata uint8_t *)uip_appdata);
if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) {
print_string("Configuration too large, aborting.\n");
config_upload = 0;
@@ -589,7 +615,7 @@ static void handle_firmware_fragment(__xdata uint8_t *p)
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
__xdata uint16_t frag_len, payload_start;
frag_len = uip_len - (p - uip_appdata);
frag_len = uip_len - (p - (__xdata uint8_t *)uip_appdata);
if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) {
print_string("Firmware upload header too large, aborting.\n");
config_upload = 0;
@@ -684,7 +710,7 @@ static uint8_t post_body_take(__xdata uint8_t *p)
send_bad_request();
return 0;
}
have = uip_len - (p - uip_appdata);
have = uip_len - (p - (__xdata uint8_t *)uip_appdata);
if (have >= content_length) {
p[content_length] = NUL;
return 1;
@@ -750,6 +776,10 @@ void handle_post(void)
return;
}
if (is_word(request_path, "upload")) {
if (!authenticated) {
send_unauthorized();
return;
}
if (flash_size < FIRMWARE_UPLOAD_START*2)
{
print_string("Flash too small for firmware upload!\n");
@@ -777,7 +807,23 @@ void handle_post(void)
dbg_string("Multipart request\n");
}
if (is_word(request_path, "cmd")) {
if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) {
dbg_string("POST upload/config request\n");
if (!authenticated) {
send_unauthorized();
return;
}
if (!boundary[0]) {
dbg_string("Bad request, no boundary!\n");
send_bad_request();
return;
}
if (config_upload)
handle_config_fragment(p);
else
handle_firmware_fragment(p);
return;
} else if (is_word(request_path, "cmd")) {
p += 4;
if (!authenticated) {
send_unauthorized();
@@ -803,22 +849,6 @@ void handle_post(void)
return;
run_login_body(p);
return;
} else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) {
dbg_string("POST upload/config request\n");
if (!authenticated) {
send_unauthorized();
return;
}
if (!boundary[0]) {
dbg_string("Bad request, no boundary!\n");
send_bad_request();
return;
}
if (config_upload)
handle_config_fragment(p);
else
handle_firmware_fragment(p);
return;
} else {
send_not_found();
return;
@@ -826,6 +856,13 @@ void handle_post(void)
}
static void tx_send(uint16_t len)
{
sent_len = len;
uip_send(outbuf + o_idx, len);
}
void httpd_appcall(void)
{
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
@@ -861,23 +898,22 @@ void httpd_appcall(void)
}
} else if (uip_acked() && s->tstate == TSTATE_TX) {
dbg_string("ACK\n");
if (slen > uip_mss()) {
slen -= uip_mss();
o_idx += uip_mss();
if (slen > sent_len) {
slen -= sent_len;
o_idx += sent_len;
} else {
slen = 0;
o_idx += slen;
}
s->tstate = TSTATE_ACKED;
if (slen > uip_mss()) {
dbg_string("Sending A: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, uip_mss());
tx_send(uip_mss());
s->tstate = TSTATE_TX;
} else if (slen > 0) {
dbg_string("Sending B: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, slen);
tx_send(slen);
s->tstate = TSTATE_TX;
} else if (cont_len) {
dbg_string("CONT cont_len: "); dbg_short(cont_len);
@@ -887,7 +923,8 @@ void httpd_appcall(void)
flash_region.addr = cont_addr;
flash_region.len = slen;
flash_read_bulk(outbuf);
uip_send(outbuf, slen);
o_idx = 0;
tx_send(slen);
cont_len -= slen;
cont_addr += slen;
s->tstate = TSTATE_TX;
@@ -898,8 +935,9 @@ void httpd_appcall(void)
reset_chip();
}
} else if (uip_newdata() && s->tstate == TSTATE_POST) {
// Check here maxupload by subtracting uip_len and close socekt if fails!
if (max_upload - uip_len > 0) {
if (config_upload || uip_len <= max_upload) {
if (!config_upload)
max_upload -= uip_len;
upload_settings.p = uip_appdata;
upload_settings.bptr = 0;
upload_settings.plen = uip_len;
@@ -978,6 +1016,8 @@ void httpd_appcall(void)
send_eee();
} else if (is_word(q, "/bandwidth.json")) {
send_bandwidth();
} else if (is_word(q, "/storm.json")) {
send_storm();
} else if (is_word(q, "/l2.json")) {
parse_short(q + 13); // e.g.: /l2.json?idx=10
send_l2(short_parsed);
@@ -986,12 +1026,16 @@ void httpd_appcall(void)
l2_delete(short_parsed);
} else if (is_word(q, "/mirror.json")) {
send_mirror();
} else if (is_word(q, "/isolation.json")) {
send_isolation();
} else if (is_word(q, "/mtu.json")) {
send_mtu();
} else if (is_word(q, "/lag.json")) {
send_lag();
} else if (is_word(q, "/stp.json")) {
send_stp();
} else if (is_word(q, "/lldp.json")) {
send_lldp();
} else if (is_word(q, "/vlanlist")) {
send_vlanlist();
} else if (is_word(q, "/config")) {
@@ -1010,19 +1054,11 @@ void httpd_appcall(void)
}
} else {
dbg_string("Have entry, authenticated: "); dbg_byte(authenticated); dbg_char('\n');
if (!authenticated && !(f_data[entry].start == FDATA_START_login_html
|| f_data[entry].start == FDATA_START_port_svg
|| f_data[entry].start == FDATA_START_sfp_svg
|| f_data[entry].start == FDATA_START_main_js
|| f_data[entry].start == FDATA_START_style_css)) {
if (!authenticated && !(f_data[entry].start == FDATA_START_login_html
|| f_data[entry].start == FDATA_START_favicon_ico)) {
send_to_login();
goto do_send;
}
// A web-page is actively accessed, we can reset session time-out
reg_read_m(RTL837X_REG_SEC_COUNTER);
timeptr = (uint8_t*)&last_session_use; // last_session_use is Little endian
timeptr[0] = sfr_data[3]; timeptr[1] = sfr_data[2]; timeptr[2] = sfr_data[1]; timeptr[3] = sfr_data[0];
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: ");
slen += strtox(outbuf + slen, mime_strings[f_data[entry].mime]);
/* 'unsafe-inline' is needed for the inline onclick handlers
@@ -1053,24 +1089,20 @@ do_send:
o_idx = 0;
if (slen > uip_mss()) {
dbg_string("Sending a: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, uip_mss());
tx_send(uip_mss());
dbg_string("Sending a done\n");
} else {
dbg_string("Sending b: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, slen);
tx_send(slen);
dbg_string("Sending b done\n");
}
s->tstate = TSTATE_TX;
} else if (uip_rexmit()) { // Connection established, need to rexmit?
dbg_string("RETRANSMIT requested\n");
if (slen > uip_mss()) {
dbg_string("Sending C: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, uip_mss());
if (sent_len) {
dbg_string("Sending C: "); dbg_short(sent_len); dbg_char('\n');
tx_send(sent_len);
dbg_string("Sending C done\n");
} else if (slen > 0) {
dbg_string("Sending D: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, slen);
dbg_string("Sending D done\n");
}
s->tstate = TSTATE_TX;
uip_len = 0;
+163 -30
View File
@@ -3,6 +3,7 @@
#include "rtl837x_sfr.h"
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "rtl837x_storm.h"
#include "rtl837x_port.h"
#include "rtl837x_flash.h"
#include "rtl837x_pins.h"
@@ -28,9 +29,10 @@ extern __code const struct machine machine;
extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
extern __xdata uint16_t slen;
extern __xdata uint16_t management_vlan;
extern __xdata uint16_t session_timeout;
extern __xdata uint16_t cont_len;
extern __xdata uint32_t cont_addr;
extern __code uint8_t * __code hex;
extern __code const uint8_t * __code const hex;
extern __xdata uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask;
extern __xdata uint8_t sfr_data[4];
@@ -47,8 +49,11 @@ extern __xdata char sfp_module_model[2][17];
extern __xdata char sfp_module_serial[2][17];
extern __xdata uint8_t sfp_options[2];
__code uint8_t * __code HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n";
__code uint8_t * __code HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n";
extern __xdata uint16_t lldp_logical_port_status;
extern __xdata bool lldp_enabled;
__code const uint8_t * __code const HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n";
__code const uint8_t * __code const HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n";
// Convert uint8_t to ascii HEX char push on html-buffer.
void charhex_to_html(char c)
@@ -70,6 +75,21 @@ void char_to_html(char c)
}
static void str_x_to_html(__xdata const char *s)
{
while (*s)
outbuf[slen++] = *s++;
}
void json_char_to_html(uint8_t c)
{
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\')
c = '.';
outbuf[slen++] = c;
}
// Convert uint8_t to ascii HEX char.
void byte_to_html(uint8_t val)
{
@@ -99,12 +119,14 @@ void itoa_html(uint8_t v)
char_to_html('0' + (v % 10));
}
void itoa16_html(uint16_t v) /* sufficient for VLAN IDs (max 4094) */
void itoa16_html(uint16_t v) /* up to five digits (65535) */
{
uint8_t print_zeros = 0;
uint8_t d;
d = v / 1000;
d = v / 10000;
if (d) { char_to_html('0' + d); print_zeros = 1; }
d = (v / 1000) % 10;
if (d || print_zeros) { char_to_html('0' + d); print_zeros = 1; }
d = (v / 100) % 10;
if (d || print_zeros) { char_to_html('0' + d); print_zeros = 1; }
d = (v / 10) % 10;
@@ -112,11 +134,28 @@ void itoa16_html(uint16_t v) /* sufficient for VLAN IDs (max 4094) */
char_to_html('0' + (v % 10));
}
void string_to_html(__code char *s)
void string_to_html(__code const char *s)
{
while (*s) char_to_html(*s++);
}
static void temp_to_html(void)
{
int16_t v;
reg_read(RTL837X_TM_RESULT);
v = SFR_DATA_U16;
if (v < 0) {
char_to_html('-');
v = -v;
}
itoa_html(v >> 7);
char_to_html('.');
itoa_html(((v & 0x7f) * 10) >> 7);
char_to_html(' ');
char_to_html('C');
}
uint16_t stat_content(void)
{
dbg_string("stat_content called\n");
@@ -175,6 +214,20 @@ void reg_to_html_long(uint16_t reg)
}
/* The 64-bit counter of the last STAT_GET as one number: the low word keeps
* its leading zeros whenever the high word is set. */
void counter_to_html(void)
{
reg_read_m(RTL837X_STAT_V_HIGH);
if (SFR_DATA_24 | SFR_DATA_16 | SFR_DATA_8 | SFR_DATA_0) {
sfr_data_to_html();
reg_to_html_long(RTL837X_STAT_V_LOW);
} else {
reg_to_html(RTL837X_STAT_V_LOW);
}
}
void send_sfp_info(uint8_t sfp)
{
// This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields
@@ -184,8 +237,8 @@ void send_sfp_info(uint8_t sfp)
if (i < 20 || i >= 60 || (i >= 36 && i < 40)) // Skip Non-ASCII codes
continue;
uint8_t c = sfp_buf[i & 0xf];
if (c && c != 0xa0) // a0 is the byte read from a non-existant I2C EEPROM
char_to_html(c);
if (c)
json_char_to_html(c);
}
}
@@ -229,7 +282,9 @@ void send_basic_info(void)
itoa_html(syslog_state.server_ip[2]); char_to_html('.');
itoa_html(syslog_state.server_ip[3]); char_to_html(':');
itoa16_html(syslog_state.server_port);
slen += strtox(outbuf + slen, "\",\"mac_address\":\"");
slen += strtox(outbuf + slen, "\",\"session_timeout\":");
itoa16_html(session_timeout);
slen += strtox(outbuf + slen, ",\"mac_address\":\"");
byte_to_html(uip_ethaddr.addr[0]); char_to_html(':');
byte_to_html(uip_ethaddr.addr[1]); char_to_html(':');
byte_to_html(uip_ethaddr.addr[2]); char_to_html(':');
@@ -248,6 +303,8 @@ void send_basic_info(void)
slen += strtox(outbuf + slen, BUILD_DATE);
slen += strtox(outbuf + slen, "\",\"hw_ver\":\"");
slen += strtox(outbuf + slen, machine.machine_name);
slen += strtox(outbuf + slen, "\",\"chip_temp\":\"");
temp_to_html();
slen += strtox(outbuf + slen, "\",\"flash_size\":\"");
string_to_html(get_flash_size_str());
@@ -279,7 +336,7 @@ void send_vlan(uint16_t vlan)
dbg_string("VLAN has no name\n");
} else {
while(vlan_names[n] && vlan_names[n] != ' ')
char_to_html(vlan_names[n++]);
json_char_to_html(vlan_names[n++]);
}
slen += strtox(outbuf + slen, "\",\"pvid\":\"0x");
uint16_t pvid_mask = 0;
@@ -311,8 +368,7 @@ bool send_counters(uint8_t phys_port)
for (uint8_t counter = 0; counter < 0x37; counter++) {
STAT_GET(counter, log_port);
slen += strtox(outbuf + slen, "\"0x");
reg_to_html(RTL837X_STAT_V_HIGH);
reg_to_html_long(RTL837X_STAT_V_LOW);
counter_to_html();
char_to_html('\"');
if (counter != 0x36)
char_to_html(',');
@@ -432,8 +488,6 @@ void l2_delete(uint16_t idx)
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
dbg_string("L2 DELETE\n");
dbg_short(idx);
__xdata uint8_t entries_left = L2_MAX_TRANSFER;
do {
reg_read(RTL837X_TBL_CTRL);
} while (SFR_DATA_0 & TBL_EXECUTE);
@@ -568,7 +622,6 @@ static void bridge_to_html(void)
byte_to_html(pi_mac[i]);
}
void send_stp(void)
{
uint8_t i, j, st, dsg;
@@ -683,8 +736,37 @@ void send_stp(void)
slen += strtox(outbuf + slen, "]}");
}
void send_lldp(void)
{
dbg_string("send_lldp called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
slen += strtox(outbuf + slen, "{\"on\":");
bool_to_html(lldp_enabled);
slen += strtox(outbuf + slen, ",\"portStatus\":[");
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"logPort\":");
itoa_html(i);
slen += strtox(outbuf + slen, ",\"name\":\"");
for (uint8_t j = 0; j < PORT_NAME_SIZE && port_names[i][j]; j++) {
json_char_to_html(port_names[i][j]);
}
slen += strtox(outbuf + slen, "\",\"permitted\":");
bool_to_html(1 & (lldp_logical_port_status >> i));
slen += strtox(outbuf + slen, "}");
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
char_to_html('}');
}
void send_eee(void)
{
@@ -778,6 +860,63 @@ void send_bandwidth(void)
}
void send_storm(void)
{
__xdata uint8_t i, t, idx;
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('[');
for (i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"en\":\"");
for (t = 0; t < STORM_TYPES; t++)
char_to_html(reg_bit_test(RTL837X_STORM_CTRL + (t << 2), i) ? '1' : '0');
slen += strtox(outbuf + slen, "\",\"pps\":\"");
for (t = 0; t < STORM_TYPES; t++) {
idx = STORM_METER(i, t);
char_to_html(reg_bit_test(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f) ? '1' : '0');
}
slen += strtox(outbuf + slen, "\",\"rate\":\"");
for (t = 0; t < STORM_TYPES; t++) {
reg_read_m(RTL837X_METER_RATE + (STORM_METER(i, t) << 2));
byte_to_html(sfr_data[1]);
byte_to_html(sfr_data[2]);
byte_to_html(sfr_data[3]);
}
char_to_html('"');
char_to_html('}');
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
}
void send_isolation(void)
{
__xdata uint16_t allow;
dbg_string("send_isolation called\n");
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('[');
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"allow\":\"");
allow = port_isolation_get(i);
byte_to_html(allow >> 8);
byte_to_html(allow & 0xff);
char_to_html('"');
char_to_html('}');
if (i < machine.max_port)
char_to_html(',');
}
char_to_html(']');
}
void send_mtu(void)
{
dbg_string("send_mtu called\n");
@@ -814,7 +953,7 @@ void send_status(void)
itoa_html(i);
slen += strtox(outbuf + slen, ",\"name\":\"");
for (uint8_t j = 0; j < PORT_NAME_SIZE && port_names[i][j]; j++) {
char_to_html(port_names[i][j]);
json_char_to_html(port_names[i][j]);
}
slen += strtox(outbuf + slen, "\"");
@@ -853,14 +992,11 @@ void send_status(void)
sfp_send_data(sfp, 238, 1);
}
slen += strtox(outbuf + slen,"\",\"sfp_vendor\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_vendor[sfp][s]; s++)
outbuf[slen++] = sfp_module_vendor[sfp][s];
str_x_to_html(sfp_module_vendor[sfp]);
slen += strtox(outbuf + slen,"\",\"sfp_model\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_model[sfp][s]; s++)
outbuf[slen++] = sfp_module_model[sfp][s];
str_x_to_html(sfp_module_model[sfp]);
slen += strtox(outbuf + slen,"\",\"sfp_serial\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_serial[sfp][s]; s++)
outbuf[slen++] = sfp_module_serial[sfp][s];
str_x_to_html(sfp_module_serial[sfp]);
slen += strtox(outbuf + slen,"\",\"sfp_los\":");
if (machine.sfp_port[sfp].pin_los == GPIO_NA) {
slen += strtox(outbuf + slen,"null");
@@ -914,8 +1050,7 @@ void send_status(void)
STAT_GET(STAT_COUNTER_TX_PKTS, i);
slen += strtox(outbuf + slen, ",\"txG\":\"0x");
reg_to_html(RTL837X_STAT_V_HIGH);
reg_to_html(RTL837X_STAT_V_LOW);
counter_to_html();
slen += strtox(outbuf + slen, "\",\"txB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i);
@@ -923,8 +1058,7 @@ void send_status(void)
slen += strtox(outbuf + slen, "\",\"rxG\":\"0x");
STAT_GET(STAT_COUNTER_RX_PKTS, i);
reg_to_html(RTL837X_STAT_V_HIGH);
reg_to_html(RTL837X_STAT_V_LOW);
counter_to_html();
slen += strtox(outbuf + slen, "\",\"rxB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i);
@@ -990,7 +1124,7 @@ void send_cmd_log(void)
__xdata uint8_t found_begin = 0;
dbg_string("History ptr: ");
dbg_short(cmd_history_ptr); dbg_char('\n');
while (p != cmd_history_ptr) {
while (p != cmd_history_ptr && slen < TCP_OUTBUF_SIZE) {
if (!cmd_history[p] || cmd_history[p] == '\n')
found_begin = 1;
if (found_begin && cmd_history[p])
@@ -1015,8 +1149,7 @@ void send_vlanlist(void)
slen += strtox(outbuf + slen, ",\"vlan\":[");
for (i = 1; i < 4095; i++) {
if (vlan_get(i) < 0)
continue;
vlan_get(i);
if (!(sfr_data[0] & 0x02)) /* bit 1: VLAN table entry valid */
continue;
@@ -1034,7 +1167,7 @@ void send_vlanlist(void)
n = vlan_name(i);
if (n != 0xffff) {
while (vlan_names[n] && vlan_names[n] != ' ')
char_to_html(vlan_names[n++]);
json_char_to_html(vlan_names[n++]);
}
slen += strtox(outbuf + slen, "\"}");
+6 -1
View File
@@ -3,22 +3,27 @@
#include <stdbool.h>
extern __code uint8_t * __code HTTP_RESPONCE_TXT;
extern __code const uint8_t * __code const HTTP_RESPONCE_TXT;
void itoa16_html(uint16_t v);
bool send_counters(uint8_t phys_port);
void send_status(void);
void send_vlan(uint16_t vlan);
void send_basic_info(void);
void send_bandwidth(void);
void send_storm(void);
void send_eee(void);
void send_l2(uint16_t idx);
void l2_delete(uint16_t idx);
void send_mirror(void);
void send_mtu(void);
void send_isolation(void);
void send_config(void);
void send_cmd_log(void);
void send_lag(void);
void send_stp(void);
void send_lldp(void);
void send_vlanlist(void);
/* Convert only the lower nibble to ascii HEX char.
+2 -2
View File
@@ -30,7 +30,7 @@
__xdata uint8_t buffer[0x1000];
__xdata uint8_t dio_enabled;
__code uint8_t * __code hex = "0123456789abcdef";
__code const uint8_t * __code const hex = "0123456789abcdef";
void isr_timer0(void) __interrupt(1)
{
@@ -67,7 +67,7 @@ void write_char(char c)
}
void print_string(__code char *p)
void print_string(__code const char *p)
{
while (*p)
write_char(*p++);
+318 -43
View File
@@ -5,6 +5,29 @@
#include "rtl837x_regs.h"
#include "rtl837x_common.h"
#define I2CBUS_INVALID (7)
// This is a MACRO to check if I2C_BUS_FROM_SDA_PIN or I2C_BUS_FROM_SCL_PIN don't return I2CBUS_INVALID.
// When I2CBUS_INVALID is returned, compiler stops with "machine.c:<line>: error 176: sizeof applied to an incomplete type"
#define I2CBUS_VALID(bus) (0 * sizeof(char[(bus) != I2CBUS_INVALID ? 1 : 0]))
#define I2CBUS(sda, scl) \
((uint8_t)((I2C_BUS_FROM_SCL_PIN(scl) << RTL837X_REG_I2C_SCL_SHIFT) | \
(I2C_BUS_FROM_SDA_PIN(sda) << RTL837X_REG_I2C_SDA_SHIFT) | \
I2CBUS_VALID(I2C_BUS_FROM_SCL_PIN(scl)) | \
I2CBUS_VALID(I2C_BUS_FROM_SDA_PIN(sda))))
#define I2C_BUS_FROM_SDA_PIN(sda_pin) \
((sda_pin) == GPIO47_I2C_SDA0 ? 0 : \
(sda_pin) == GPIO49_I2C_SDA1 ? 1 : \
(sda_pin) == GPIO51_I2C_SDA2_UART1_RX ? 2 : \
(sda_pin) == GPIO41_I2C_SDA3_MDIO1 ? 3 : \
(sda_pin) == GPIO39_I2C_SDA4 ? 4 : I2CBUS_INVALID)
#define I2C_BUS_FROM_SCL_PIN(scl_pin) \
((scl_pin) == GPIO46_I2C_SCL0 ? 0 : \
(scl_pin) == GPIO48_I2C_SCL1 ? 1 : \
(scl_pin) == GPIO50_I2C_SCL2_UART1_TX ? 2 : \
(scl_pin) == GPIO40_I2C_SCL3_MDC1 ? 3 : I2CBUS_INVALID)
#if defined(MACHINE_KP_9000_6XHML_X2) || \
defined(MACHINE_KP_9000_6XH_X2_V1_1) || \
defined(MACHINE_KP_9000_6XHML_X2_V1_1) || \
@@ -34,13 +57,13 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO10_LED10,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 0,
.sfp_port[0].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
// Right SFP port (6)
.sfp_port[1].pin_detect = GPIO30_ACL_BIT3_EN,
.sfp_port[1].pin_los = GPIO37,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 1,
.sfp_port[1].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0},
@@ -70,7 +93,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
/* Conditions for LED on:
* dual led orange: ledset_0 & ledset_2
@@ -105,13 +128,13 @@ __code const struct machine machine = {
.sfp_port[0].pin_detect = GPIO38,
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
// Right SFP port
.sfp_port[1].pin_detect = GPIO37,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO48_I2C_SCL1, // Button-Switch is unpopulated on PCB, but can be added manually (hole in case is already there)
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 },
@@ -152,7 +175,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0},
@@ -177,7 +200,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 },
@@ -214,9 +237,14 @@ __code const struct machine machine = {
},
};
#elif defined MACHINE_KP_9000_9XHML_X_V3_1
#elif defined(MACHINE_KP_9000_9XHML_X_V3_1) || defined(MACHINE_KP_9000_9XHML_X_V3_2)
// 2M-PCB23-V3.2 is wired like V3.1 (SFP detection, LEDs, factory MAC location)
__code const struct machine machine = {
#if defined(MACHINE_KP_9000_9XHML_X_V3_2)
.machine_name = "keepLink KP-9000-9XHML-X V3.2",
#else
.machine_name = "keepLink KP-9000-9XHML-X V3.1",
#endif
.isRTL8373 = 1,
.min_port = 0,
.max_port = 8,
@@ -227,7 +255,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_detect = GPIO38,
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO48_I2C_SCL1,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -265,13 +293,13 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 }, /* GPIO 39 */
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 39 */
// Right SFP port (J2)
.sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 }, /* GPIO 40 */
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 40 */
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1},
@@ -306,13 +334,13 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 }, /* GPIO 39 */
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 39 */
// Right SFP port (J2)
.sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX,
.sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 }, /* GPIO 40 */
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ), /* GPIO 40 */
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1},
@@ -346,7 +374,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO48_I2C_SCL1,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -385,12 +413,12 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = 10,
.sfp_port[0].pin_tx_disable = 0xFF,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
.sfp_port[1].pin_detect = 30,
.sfp_port[1].pin_los = 51,
.sfp_port[1].pin_tx_disable = 0xFF,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 , .enable = LED_27 | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1},
@@ -425,7 +453,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -458,7 +486,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO_NA, // no LOS pin wired
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -499,7 +527,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -531,7 +559,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0},
@@ -556,12 +584,12 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO50_I2C_SCL2_UART1_TX,
.sfp_port[0].pin_tx_disable = GPIO54_ACL_BIT2_EN,
.sfp_port[0].sds = 0,
.sfp_port[0].i2c = { .sda = GPIO47_I2C_SDA0, .scl = GPIO46_I2C_SCL0 },
.sfp_port[0].i2c = I2CBUS( GPIO47_I2C_SDA0, GPIO46_I2C_SCL0 ),
.sfp_port[1].pin_detect = GPIO36_PWM_OUT,
.sfp_port[1].pin_los = GPIO37,
.sfp_port[1].pin_tx_disable = GPIO51_I2C_SDA2_UART1_RX,
.sfp_port[1].sds = 1,
.sfp_port[1].i2c = { .sda = GPIO49_I2C_SDA1, .scl = GPIO48_I2C_SCL1 },
.sfp_port[1].i2c = I2CBUS( GPIO49_I2C_SDA1, GPIO48_I2C_SCL1 ),
.reset_pin = GPIO_NA,
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1},
.led_sets = {
@@ -596,13 +624,13 @@ __code const struct machine machine = {
.sfp_port[0].pin_detect = GPIO38,
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
// Right SFP port
.sfp_port[1].pin_detect = GPIO37,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 },
@@ -641,7 +669,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -676,7 +704,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO48_I2C_SCL1,
.high_leds = { .mux = LED_28_SYS, .enable = LED_27 | LED_28_SYS | LED_29 },
.led_mux_custom = 1,
@@ -720,14 +748,14 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
// Right SFP port (6)
// LED pin 24
.sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX,
.sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1},
@@ -769,11 +797,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = {
.sda = GPIO39_I2C_SDA4,
.scl = GPIO40_I2C_SCL3_MDC1
},
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = {
@@ -823,14 +847,14 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
/* Right SFP (logical 3, SDS0): GPIO50=ModAbs, GPIO51=RX_LOS */
.sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX,
.sfp_port[1].pin_los = GPIO51_I2C_SDA2_UART1_RX,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
@@ -879,14 +903,14 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 0,
.sfp_port[0].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
// SFP port on SDS1 / logical port 8
.sfp_port[1].pin_detect = GPIO38,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 1,
.sfp_port[1].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 },
@@ -911,6 +935,68 @@ __code const struct machine machine = {
},
};
#elif defined MACHINE_ONT_S207CW_62TS_SE
__code const struct machine machine = {
.machine_name = "ONT-S207CW-62TS-SE / Binardat 2G06-04210GSM / Binardat 2G06-04210GS",
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
.n_sfp = 2,
.log_to_phys_port = {0, 0, 0, 5, 4, 3, 2, 1, 6},
.phys_to_log_port = {7, 6, 5, 4, 3, 8, 0, 0, 0},
.is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2},
// SFP port on SDS0 / logical port 3
.sfp_port[0].pin_detect = GPIO37,
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 0,
.sfp_port[0].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
// SFP port on SDS1 / logical port 8
.sfp_port[1].pin_detect = GPIO38,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].pin_tx_disable = GPIO_NA,
.sfp_port[1].sds = 1,
.sfp_port[1].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 3, 0, 0, 2, 2, 3},
.mac_flash_offset = 0x1FC000,
.led_sets = {
{ // RJ45 Ports 3-4 (normal wiring)
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
0,
0
},
{ // Unused
0,
0,
0,
0
},
{ // RJ45 Ports 1-2 (reversed wiring)
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
0,
0
},
{ // SFP Ports 5-6
LEDS_10G | LEDS_LINK | LEDS_ACT,
LEDS_2G5 | LEDS_1G | LEDS_LINK | LEDS_ACT,
0,
0
},
},
.led_mux_custom = 1,
.led_mux = {
0x00,0x01,0x04,0x05,0x08,0x09,0x0c,0x3f,0x0d,0x10,0x11,0x0e,0x14,0x11,0x12,0x15,0x15,0x16,0x18,0x19,0x1a,0x19,0x1d,0x1e,0x1c,0x1d,0x20,0x21
},
};
#elif defined MACHINE_SWTG024AS_A_2_0_1_5C_1SFP
__code const struct machine machine = {
.machine_name = "SWTG024AS-A-V2.0.1-5C-1SFP",
@@ -925,7 +1011,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 },
@@ -968,7 +1054,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_27 | LED_28_SYS | LED_29 },
@@ -1049,13 +1135,13 @@ __code const struct machine machine = {
.sfp_port[0].pin_detect = GPIO38,
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
// Right SFP port
.sfp_port[1].pin_detect = GPIO37,
.sfp_port[1].pin_los = GPIO_NA,
.sfp_port[1].sds = 0,
.sfp_port[1].i2c = { .sda = GPIO41_I2C_SDA3_MDIO1, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
@@ -1114,7 +1200,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO_NA,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -1145,6 +1231,41 @@ __code const struct machine machine = {
},
};
#elif defined MACHINE_LIANGUO_HYWS_SGT0108S
__code const struct machine machine = {
.machine_name = "Lianguo HYWS-SGT0108S",
.isRTL8373 = 1,
.min_port = 0,
.max_port = 8,
.n_sfp = 1,
.log_to_phys_port = {2, 1, 4, 3, 5, 6, 7, 8, 9},
.phys_to_log_port = {1, 0, 3, 2, 4, 5, 6, 7, 8},
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1},
.sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = 0, .enable = 0 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 },
.led_sets = {
{
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // RJ45 Blue LEDs
LEDS_2G5 | LEDS_LINK, // RJ45 White LEDs
0,
0
},
{
LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // SFP blue LED?
0,
0,
0
},
},
};
#elif defined MACHINE_POE_2G080110GS
__code const struct machine machine = {
.machine_name = "MokerLink POE-2G080110GS",
@@ -1159,7 +1280,7 @@ __code const struct machine machine = {
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -1188,6 +1309,160 @@ __code const struct machine machine = {
0x1a, 0x19, 0x1d, 0x1e, 0x1c,
0x1d, 0x20, 0x21 },
};
#elif defined MACHINE_PB_2132
__code const struct machine machine = {
.machine_name = "PB-2132",
.isRTL8373 = 1,
.min_port = 0,
.max_port = 8,
.n_sfp = 1,
.log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9},
.phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8},
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1},
.sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
.led_sets = {
{
/* LED0 = RJ45: Amber LED: 2.5G */
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
/* LED1 = RJ45: Green LED: 10M / 100M / 1000M */
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
0,
0
},
{
/* SFP+ single Green LED. */
LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT,
0,
0,
0
},
},
};
#elif defined MACHINE_HASIVO_S600W_4GT_2XGT_SE
__code const struct machine machine = {
.machine_name = "Hasivo S600W-4GT-2XGT-SE",
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
.n_sfp = 0,
.n_10g = 2,
.log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6},
.phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0},
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 0},
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1 },
/* Ports 1-4: Green: 2.5GBit, Orange: 10/100/1000MBit
* Ports 5-6: Green: 10GBit, Orange: 5/2.5GBit, None: 10/100/1000MBit
*/
.led_sets = {
{
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow
LEDS_2G5 | LEDS_LINK, // Green
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK, // Orange
0, // Unused
},
{
LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow
LEDS_10G | LEDS_LINK, // Green
LEDS_5G | LEDS_2G5 | LEDS_LINK, // Orange
0, // Unused
},
},
.led_mux_custom = 1,
.led_mux = {
0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x3f, 0x0f, 0x20,
0x21, 0x22, 0x10, 0x11, 0x12,
0x14, 0x15, 0x16, 0x18, 0x19,
0x1a, 0x1c, 0x1d, 0x1e, 0x0c,
0x0d, 0x0e, 0x23
},
};
#elif defined MACHINE_HASIVO_S1100WP_8GT_1SX_SE
__code const struct machine machine = {
.machine_name = "Hasivo S1100WP-8GT-1SX-SE",
.isRTL8373 = 1,
.mac_flash_offset = 0x1FC000,
.min_port = 0,
.max_port = 8,
.n_sfp = 1,
.log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9},
.phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8},
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1},
.sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
.led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT, // Green LED (right)
0,
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Amber LED (left)
0
},
{ LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT, // SFP LED
0,
0,
0
},
},
};
#elif defined MACHINE_F7008_2_5
__code const struct machine machine = {
.machine_name = "F7008-2.5 8+1 SFP Port Switch",
.isRTL8373 = 1,
.min_port = 0,
.max_port = 8,
.n_sfp = 1,
.log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9},
.phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8},
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1},
.sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
.sfp_port[0].pin_los = GPIO37,
.sfp_port[0].pin_tx_disable = GPIO_NA,
.sfp_port[0].sds = 1,
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1},
.led_sets = {
{ /* SET0 */
LEDS_2G5 | LEDS_LINK, /* LEDID0 - green: 2.5G link */
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, /* LEDID1 - yellow: traffic */
0,
0,
},
{ /* SET1 - SFP+ */
LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_LINK,
LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_LINK | LEDS_ACT,
0,
0,
},
},
.led_mux_custom = 1,
.led_mux = {
0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x02,
0x0d, 0x10, 0x11, 0x02, 0x14, 0x02, 0x02, 0x15,
0x02, 0x02, 0x18, 0x02, 0x02, 0x19, 0x02, 0x02,
0x1c, 0x1d, 0x02, 0x02,
},
};
#else
#error "Please select a machine type in machine.h"
+10 -10
View File
@@ -22,6 +22,7 @@
// #define MACHINE_KP_9000_9XH_X_EU
// #define MACHINE_KP_9000_9XHML_X_V2_2
// #define MACHINE_KP_9000_9XHML_X_V3_1
// #define MACHINE_KP_9000_9XHML_X_V3_2
// #define MACHINE_SWGT024_V2_0_MANAGED
// #define MACHINE_SWGT024_V2_0_UNMANAGED
// #define MACHINE_TRENDNET_TEG_S562
@@ -40,18 +41,17 @@
// #define MACHINE_HI_K0801WS
// #define MACHINE_FNS1200P
// #define MACHINE_PCB_SWTG024AS_A_2_0_1
// #define MACHINE_ONT_S207CW_62TS_SE
// #define MACHINE_SWTG024AS_A_2_0_1_5C_1SFP
// #define MACHINE_SWTG024AS_V2_0
// #define MACHINE_FG_4GT_2SX_V2_0
// #define MACHINE_FG_8GT_1SX
// #define MACHINE_LIANGUO_HYWS_SGT0108S
// #define MACHINE_POE_2G080110GS
typedef struct {
// GPIO pins for SDA/SCL
uint8_t sda;
uint8_t scl;
} i2c_bus_t;
// #define MACHINE_PB_2132
// #define MACHINE_HASIVO_S600W_4GT_2XGT_SE
// #define MACHINE_HASIVO_S1100WP_8GT_1SX_SE
// #define MACHINE_F7008_2_5
#define LED_27 1
// SYSTEM LED
@@ -71,10 +71,10 @@ struct sfp_port
uint8_t pin_los; // gpio number 0-63, 0xFF = don't have it?
uint8_t pin_tx_disable; // gpio number 0-63, 0xFF = not present
uint8_t sds;
i2c_bus_t i2c;
uint8_t i2c;
};
typedef struct machine {
struct machine {
char machine_name[30];
uint8_t isRTL8373;
// Lowest logical port number
@@ -102,7 +102,7 @@ typedef struct machine {
uint32_t mac_flash_offset;
};
typedef struct machine_runtime
struct machine_runtime
{
uint8_t isRTL8373 : 1;
uint8_t isN : 1;
+7 -34
View File
@@ -15,7 +15,11 @@
#if defined(MACHINE_KP_9000_6XH_X2) || \
defined(MACHINE_KP_9000_6XH_X2_V2_1) || \
defined(MACHINE_KP_9000_6XHML_X2_V2_1)
defined(MACHINE_KP_9000_6XHML_X2_V2_1) || \
defined(MACHINE_HI_K0402WS) || \
defined(MACHINE_PCB_K0402WS_V2) || \
defined(MACHINE_PCB_K0402WS_V3) || \
defined(MACHINE_FNS1200P)
void machine_custom_init(void) __banked
{
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6);
@@ -42,18 +46,6 @@ void machine_custom_init(void) __banked
}
}
#elif defined(MACHINE_PCB_K0402WS_V3) || defined(MACHINE_HI_K0402WS)
void machine_custom_init(void) __banked
{
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6);
}
#elif defined(MACHINE_PCB_K0402WS_V2) || defined(MACHINE_FNS1200P)
void machine_custom_init(void) __banked
{
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6);
}
#elif defined MACHINE_PCB_SWTG024AS_A_2_0_1
void machine_custom_init(void) __banked
{
@@ -63,27 +55,8 @@ void machine_custom_init(void) __banked
reg_bit_clear(RTL837X_REG_LED_MODE, 7);
}
#elif defined MACHINE_SWTG024AS_A_2_0_1_5C_1SFP
void machine_custom_init(void) __banked
{
uint16_t pval;
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6);
reg_bit_set(RTL837X_REG_LED_MODE, 17);
reg_bit_clear(RTL837X_REG_LED_MODE, 9);
reg_bit_clear(RTL837X_REG_LED_MODE, 7);
// OEM firmware sets these companion SDS0 polarity bits for the RTL8221B.
sds_read(0, 0, 0);
pval = SFR_DATA_U16;
sds_write_v(0, 0, 0, pval | 0x100);
sds_read(0, 6, 2);
pval = SFR_DATA_U16;
sds_write_v(0, 6, 2, pval | 0x4000);
}
#elif defined MACHINE_SWTG024AS_V2_0
#elif defined(MACHINE_SWTG024AS_A_2_0_1_5C_1SFP) || \
defined(MACHINE_SWTG024AS_V2_0)
void machine_custom_init(void) __banked
{
uint16_t pval;
+25 -9
View File
@@ -7,6 +7,14 @@
#define SYS_TICK_HZ 200
/* Timers of the main loop count system ticks, not loop passes: a pass is as
* long as the packets it handles, and since #483 the loop no longer sleeps
* while packets wait, so passes can outrun the tick as well as lag it. `last`
* is the low byte of ticks when the timer last ran; the byte arithmetic wraps
* every 256 ticks, so a pass longer than 1.28 s loses time rather than
* running the timer 64 times to catch up. */
#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period))
#define CPU_PORT 9
#define NUL '\0'
@@ -54,6 +62,7 @@ extern __xdata uint8_t sbuf[SBUF_SIZE];
#define ERR_OK 0
#define ERR_TOO_MANY_ARGUMENTS 1
#define ERR_CMD_TOO_LONG 2
#define ERR_INVALID_ARGUMENT 3
// For RX data, a propriatary RTL FRAME is inserted. Instead of 0x0800 for IPv4,
// the RTL_FRAME_TAG_ID is used as part of an 8-byte tag. When VLAN is activated,
@@ -97,7 +106,7 @@ struct vlan_tag {
#define FIRMWARE_UPLOAD_START 0x80000
// Constants for the circular command buffer, the size must be 2^n
#define CMD_HISTORY_SIZE 0x400
#define CMD_HISTORY_SIZE 0x800
#define CMD_HISTORY_MASK (CMD_HISTORY_SIZE - 1)
enum sfp_speeds {
@@ -122,6 +131,7 @@ struct flash_region_t {
};
extern __xdata char port_names[9][PORT_NAME_SIZE];
extern __xdata uint16_t lldp_logical_port_status;
extern __xdata bool stp_enabled;
@@ -134,9 +144,9 @@ extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2];
extern __xdata struct uip_eth_addr uip_ethaddr;
// Headers for calls in the common code area (HOME/BANK0)
void print_string_no_syslog(__code char *p);
void print_string_newline_no_syslog(__code char *p);
void print_string(__code char *p);
void print_string_no_syslog(__code const char *p);
void print_string_newline_no_syslog(__code const char *p);
void print_string(__code const char *p);
void print_string_x(__xdata char *p);
void print_long(uint32_t a);
void print_short(uint16_t a);
@@ -158,7 +168,6 @@ void reg_write_m(uint16_t reg_addr);
void sds_read(uint8_t sds_id, uint8_t page, uint8_t reg);
void sds_write_v(uint8_t sds_id, uint8_t page, uint8_t reg, uint16_t v);
void delay(uint16_t t);
void sleep(uint16_t t);
void write_char_no_syslog(char c);
void write_char(char c);
void print_reg(uint16_t reg);
@@ -170,19 +179,26 @@ uint8_t reg_bit_test(uint16_t reg_addr, char bit);
void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set);
void sfr_set_zero(void);
void reset_chip(void);
/* Firmware implementations that shadow libc names. Host unit-test builds
* (RTLP_HOST_TEST) hide these prototypes so they don't clash with glibc;
* argument order matches libc, so on-host callers transparently use the
* C library. See test/. */
#ifndef RTLP_HOST_TEST
void sleep(uint16_t t);
void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16_t len);
void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len);
void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len);
int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len);
uint16_t strlen(__code const char *s);
uint16_t strlen_x(__xdata const char *s);
uint16_t strtox(__xdata uint8_t *dst, __code const char *s);
uint16_t strcpy(__xdata uint8_t *dst, const char *s);
char strcmp(__xdata const uint8_t *a, __code const uint8_t *b);
#endif
void memcpyc(__xdata uint8_t *dst, __code const uint8_t *src, uint16_t len);
uint16_t strlen_x(__xdata const char *s);
uint16_t strtox(__xdata uint8_t *dst, __code const char *s);
bool strstart(__xdata const uint8_t *a, __code const uint8_t *b);
bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b);
void tcpip_output(void);
uint8_t read_flash(uint8_t bank, __code uint8_t *addr);
uint8_t read_flash(uint8_t bank, __code const uint8_t *addr);
void get_random_32(void);
void read_reg_timer(__xdata uint32_t * tmr);
bool gpio_pin_test(uint8_t pin);
+103 -93
View File
@@ -28,11 +28,16 @@ __xdata uint8_t flash_capacity_code;
#define CMD_READ_JEDEC_ID 0x9f
#define CMD_FREAD_DIO 0xbb
#define STATUS_REG_BUSY_MASK 0x01
#define STATUS_REG_WEL_MASK 0x02
/*
* Configure Memory Managed IO
*/
void flash_configure_mmio(void)
static void flash_configure_mmio(void)
{
while(SFR_FLASH_EXEC_BUSY);
// Set configuration for MMIO access by controller
if (dio_enabled) {
SFR_FLASH_MODEB = 0x18;
@@ -46,6 +51,54 @@ void flash_configure_mmio(void)
SFR_FLASH_DUMMYCYCLES = 8;
}
static void flash_configure_sio(void)
{
while(SFR_FLASH_EXEC_BUSY);
// Set configuration for SIO access by controller
SFR_FLASH_MODEB = 0x0;
SFR_FLASH_DUMMYCYCLES = 0;
}
static uint8_t flash_read_status(void)
{
uint8_t status;
uint8_t old_cmd_r;
uint8_t old_tconf;
while(SFR_FLASH_EXEC_BUSY);
// SAVE SPI peripheral state at entry
old_cmd_r = SFR_FLASH_CMD_R;
old_tconf = SFR_FLASH_TCONF;
SFR_FLASH_TCONF = 0x11;
SFR_FLASH_CMD_R = CMD_READ_STATUS;
SFR_FLASH_EXEC_GO = 1;
while(SFR_FLASH_EXEC_BUSY);
status = SFR_FLASH_DATA0;
// RESTORE SPI peripheral state
SFR_FLASH_CMD_R = old_cmd_r;
SFR_FLASH_TCONF = old_tconf;
return status;
}
static void flash_write_enable(void)
{
while (flash_read_status() & STATUS_REG_BUSY_MASK);
while(SFR_FLASH_EXEC_BUSY);
SFR_FLASH_TCONF = 0x18;
SFR_FLASH_CMD = CMD_WRITE_ENABLE;
SFR_FLASH_EXEC_GO = 1;
while (SFR_FLASH_EXEC_BUSY);
while (!(flash_read_status() & STATUS_REG_WEL_MASK));
}
/*
* Initializes the flash controller for programmed control
@@ -82,30 +135,12 @@ void flash_init(uint8_t enable_dio)
flash_configure_mmio();
}
uint8_t flash_read_status(void)
{
// Test Controller Busy (we might call this directly after executing a command)
while(SFR_FLASH_EXEC_BUSY);
// setup status read command
SFR_FLASH_TCONF = 0x11;
SFR_FLASH_CMD_R = CMD_READ_STATUS;
// execute and wait for controller done
SFR_FLASH_EXEC_GO = 1;
while(SFR_FLASH_EXEC_BUSY);
return SFR_FLASH_DATA0;
}
void flash_read_uid(void)
{
while (flash_read_status() & 0x1);
flash_configure_sio();
while (flash_read_status() & STATUS_REG_BUSY_MASK);
// Set slow read mode for UID
SFR_FLASH_MODEB = 0x0;
SFR_FLASH_CMD_R = CMD_READ_UNIQUE_ID;
SFR_FLASH_DUMMYCYCLES = 8;
@@ -136,7 +171,7 @@ void flash_read_uid(void)
flash_configure_mmio();
}
__code char* get_flash_size_str(void)
__code const char* get_flash_size_str(void)
{
switch (flash_capacity_code) {
case 0x12: return "256 KB";
@@ -152,12 +187,12 @@ __code char* get_flash_size_str(void)
void flash_read_jedecid(void)
{
while (flash_read_status() & 0x1);
flash_configure_sio();
while (flash_read_status() & STATUS_REG_BUSY_MASK);
// Set read mode for JEDEC ID
SFR_FLASH_MODEB = 0x0;
SFR_FLASH_CMD_R = CMD_READ_JEDEC_ID;
SFR_FLASH_DUMMYCYCLES = 0;
// Transfer 3 bytes back
SFR_FLASH_TCONF = 0x13;
@@ -180,53 +215,18 @@ void flash_read_jedecid(void)
}
void flash_write_enable(void)
{
short status;
// Wait until busy bit clear
do {
status = flash_read_status();
} while (status & 0x1);
SFR_FLASH_TCONF = 0x18;
SFR_FLASH_CMD = CMD_WRITE_ENABLE;
/* The following makes sure that the PAGE_PROGRAM command,
* where the data to be written follows the command word directly
* works properly
*/
SFR_FLASH_DUMMYCYCLES = 0;
SFR_FLASH_MODEB = 0;
SFR_FLASH_EXEC_GO = 1;
// Wait for write status enabled
do {
status = flash_read_status();
} while (!(status & 0x2));
}
/*
* Reads bulk data of length len from the flash memory starging at address src
* and writes the data into a buffer pointed to by dst in XMEM
*/
void flash_read_bulk(__xdata uint8_t *dst)
{
short status;
do {
status = flash_read_status();
} while (status & 0x1);
if (!flash_region.len)
return;
// Set fast read mode
if (dio_enabled) {
SFR_FLASH_MODEB = 0x18;
SFR_FLASH_CMD_R = CMD_FREAD_DIO;
SFR_FLASH_DUMMYCYCLES = 4;
} else {
SFR_FLASH_MODEB = 0x0;
SFR_FLASH_CMD_R = CMD_FREAD; // Fast read
SFR_FLASH_DUMMYCYCLES = 8; // Add 8 dummy clocks
}
flash_configure_sio();
while (flash_read_status() & STATUS_REG_BUSY_MASK);
flash_configure_mmio();
// Read 4 bytes
@@ -260,12 +260,15 @@ void flash_read_bulk(__xdata uint8_t *dst)
void flash_read_security(void)
{
while (flash_read_status() & 0x1);
if (!flash_region.len)
return;
flash_configure_sio();
while (flash_read_status() & STATUS_REG_BUSY_MASK);
// Set slow read mode
SFR_FLASH_MODEB = 0x0;
SFR_FLASH_CMD_R = CMD_READ_SECURITY_REGS; // read security register
SFR_FLASH_DUMMYCYCLES = 8; // Add 8 dummy clocks as for fast read
// Transfer 4 bytes (command + 3byte address)
SFR_FLASH_TCONF = 4;
@@ -298,6 +301,7 @@ void flash_read_security(void)
void flash_sector_erase(void)
{
flash_configure_sio();
flash_write_enable();
SFR_FLASH_TCONF = 8;
SFR_FLASH_CMD = CMD_SECTOR_ERASE;
@@ -307,7 +311,7 @@ void flash_sector_erase(void)
SFR_FLASH_ADDR0 = flash_region.addr;
SFR_FLASH_EXEC_GO = 1;
while (flash_read_status() & 0x1);
while (flash_read_status() & STATUS_REG_BUSY_MASK);
flash_configure_mmio();
}
@@ -315,33 +319,39 @@ void flash_sector_erase(void)
void flash_write_bytes(__xdata uint8_t *ptr)
{
// write_char('\n'); write_char('>'); print_long(flash_region.addr); write_char(':'); print_short(flash_region.len); write_char('-'); print_byte(*ptr); // write_char('\n');
while(1) {
flash_write_enable();
SFR_FLASH_CMD = CMD_PAGE_PROGRAM;
SFR_FLASH_TCONF = 0x40 | 8 | 4; // Bytes written is 4, 8 enables write, 0x40 is unknown
flash_configure_sio();
while(1) {
flash_write_enable();
SFR_FLASH_CMD = CMD_PAGE_PROGRAM;
// Last transfer?
if (flash_region.len < 5) {
SFR_FLASH_TCONF = 8 | flash_region.len;
}
if (flash_region.len < 5) {
SFR_FLASH_TCONF = 8 | flash_region.len;
} else {
SFR_FLASH_TCONF = 0x40 | 8 | 4; // Bytes written is 4, 8 enables write, 0x40 is unknown
}
SFR_FLASH_ADDR16 = flash_region.addr >> 16;
SFR_FLASH_ADDR8 = flash_region.addr >> 8;
SFR_FLASH_ADDR0 = flash_region.addr;
SFR_FLASH_DATA0 = *ptr++;
SFR_FLASH_DATA8 = *ptr++;
SFR_FLASH_DATA16 = *ptr++;
SFR_FLASH_DATA24 = *ptr++;
SFR_FLASH_ADDR16 = flash_region.addr >> 16;
SFR_FLASH_ADDR8 = flash_region.addr >> 8;
SFR_FLASH_ADDR0 = flash_region.addr;
// Safely load only the valid bytes remaining in the buffer
SFR_FLASH_DATA0 = *ptr++;
if (flash_region.len > 1) SFR_FLASH_DATA8 = *ptr++;
if (flash_region.len > 2) SFR_FLASH_DATA16 = *ptr++;
if (flash_region.len > 3) SFR_FLASH_DATA24 = *ptr++;
// Execute transfer, we wait for completion at top of loop
SFR_FLASH_EXEC_GO = 1;
// Execute transfer, wait for completion at top of loop
SFR_FLASH_EXEC_GO = 1;
if (flash_region.len < 5)
break;
if (flash_region.len < 5)
break;
flash_region.len -= 4;
flash_region.addr += 4;
};
while (flash_read_status() & 0x1);
flash_configure_mmio();
flash_region.len -= 4;
flash_region.addr += 4;
};
while (flash_read_status() & STATUS_REG_BUSY_MASK);
flash_configure_mmio();
}
+1 -2
View File
@@ -13,14 +13,13 @@
void flash_init(uint8_t enable_dio);
void flash_read_uid(void);
void flash_write_enable(void);
void flash_dump(uint8_t len);
void flash_read_jedecid(void);
void flash_read_security(void);
void flash_sector_erase(void);
void flash_read_bulk(__xdata uint8_t *dst);
void flash_write_bytes(__xdata uint8_t *ptr);
__code char* get_flash_size_str(void);
__code const char* get_flash_size_str(void);
#endif
+45 -19
View File
@@ -9,16 +9,18 @@
#define IPMC_USES_L3MC
#include <stdint.h>
#include <stddef.h>
#include "rtl837x_common.h"
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_igmp.h"
#include "machine.h"
extern __code struct machine machine;
extern __code const struct machine machine;
extern __xdata uint8_t igmpEnabled;
#include "uip.h"
#include "uip_arp.h"
#pragma codeseg BANK1
#pragma constseg BANK1
@@ -29,6 +31,13 @@ extern __xdata struct machine_runtime machine_detected;
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
__xdata uint16_t idx;
#define IGMP_IP_HDR offsetof(struct igmp_pkt, hlen)
#define IGMP_IP_IHL 0x0f
#define IGMP_GROUP_OFS 4
#define IGMP_V3_RECORD_OFS 8
static __xdata uint8_t igmp_grp;
static __xdata uint8_t igmp_join;
#ifdef IPMC_USES_L3MC
struct ipmc_table_entry {
@@ -198,8 +207,10 @@ void entry_to_l2mc(void)
#endif
void igmp_packet_handler(void) __banked
uint8_t igmp_packet_handler(void) __banked
{
if (IGMP_I->ipv4_tag != HTONS(UIP_ETHTYPE_IP) || IGMP_I->protocol != IGMP_PROTO)
return 0;
// By default we do not send anything out
uip_len = 0;
@@ -211,24 +222,40 @@ void igmp_packet_handler(void) __banked
}
write_char('\n');
#endif
if (IGMP_I->protocol != 2)
return;
igmp_grp = IGMP_IP_HDR + ((IGMP_I->hlen & IGMP_IP_IHL) << 2);
#ifdef DEBUG
print_string("Found IGMP, type: "); print_byte(IGMP_I->igmp_type); write_char('\n');
print_string("Found IGMP, type: "); print_byte(uip_buf[igmp_grp]); write_char('\n');
#endif
// We react to IGMPv1/v2 and v3 membership reports
if (!(IGMP_I->igmp_type == 0x12 || IGMP_I->igmp_type == 0x16 || IGMP_I->igmp_type == 0x22))
return;
switch (uip_buf[igmp_grp]) {
case IGMP_V1_REPORT:
case IGMP_V2_REPORT:
igmp_join = 1;
break;
case IGMP_V2_LEAVE:
igmp_join = 0;
break;
case IGMP_V3_REPORT:
igmp_grp += IGMP_V3_RECORD_OFS;
if (uip_buf[igmp_grp] == IGMP_V3_TO_EXCLUDE)
igmp_join = 1;
else if (uip_buf[igmp_grp] == IGMP_V3_TO_INCLUDE)
igmp_join = 0;
else
return 1;
break;
default:
return 1;
}
igmp_grp += IGMP_GROUP_OFS;
#ifdef DEBUG
print_string("IGMP membership report, type "); print_byte(IGMP_I->igmp_rtype); write_char('\n');
print_string("IGMP join "); print_byte(igmp_join); write_char('\n');
#endif
#ifdef IPMC_USES_L3MC
memset((__xdata uint8_t *)&entry, 0, sizeof(struct ipmc_table_entry));
// For IPv4 MC, the Source-IP is 0.0.0.0
entry.sip[0] = 0x00; entry.sip[1] = 0x00; entry.sip[2] = 0x00; entry.sip[3] = 0x00;
// For IPv4 MC, the Destination-IP is the IPv4 MC address
entry.dip[0] = IGMP_I->mc_ip[0]; entry.dip[1] = IGMP_I->mc_ip[1]; entry.dip[2] = IGMP_I->mc_ip[2]; entry.dip[3] = IGMP_I->mc_ip[3];
memcpy(entry.dip, &uip_buf[igmp_grp], sizeof(entry.dip));
entry_to_l3mc();
#else
/* The L2 Multicast MAC for IP-Multicast is 01:00:5e:xx:yy:zz, where
@@ -238,7 +265,7 @@ void igmp_packet_handler(void) __banked
*/
memset((__xdata uint8_t *)&entry, 0, sizeof(struct l2mc_table_entry));
entry.mac[0] = 0x01; entry.mac[1] = 0x00; entry.mac[2] = 0x5e;
entry.mac[3] = IGMP_I->mc_ip[1] & 0x7f; entry.mac[4] = IGMP_I->mc_ip[2]; entry.mac[5] = IGMP_I->mc_ip[3];
entry.mac[3] = uip_buf[igmp_grp + 1] & 0x7f; entry.mac[4] = uip_buf[igmp_grp + 2]; entry.mac[5] = uip_buf[igmp_grp + 3];
entry.vlan = 1; //TODO: Get this out of the packet and compare with VLAN table!
entry_to_ipmc();
#endif
@@ -286,7 +313,7 @@ void igmp_packet_handler(void) __banked
print_sfr_data();
#endif
idx = ((sfr_data[2] & 0xf) << 8) | sfr_data[3];
if (IGMP_I->igmp_rtype == 0x4) {// Join group
if (igmp_join) {// Join group
if (sfr_data[2] & 0x10) {
print_string("\nIGMP-Entry FOUND\n");
reg_read_m(RTL837x_L2_DATA_OUT_B);
@@ -297,7 +324,7 @@ void igmp_packet_handler(void) __banked
// Update (found) entry with portmask from trapped Packet
entry.pmask |= ((uint16_t)1) << ((IGMP_I->rtl_tag.pmask >> 8) & 0x0f); // Swap bytes from network order, only 4 LSB count
// print_string("\nPort-Mask: "); print_short(entry.pmask); write_char('\n');
} else if (IGMP_I->igmp_rtype == 0x3){ // Leave group
} else { // Leave group
if (sfr_data[2] & 0x10) {
print_string("\nIGMP_Entry FOUND\n");
reg_read_m(RTL837x_L2_DATA_OUT_B);
@@ -316,7 +343,7 @@ void igmp_packet_handler(void) __banked
// print_string("\nPort-Mask: "); print_short(entry.pmask); write_char('\n');
} else {
print_string("IGMP Entry already deleted\n");
return;
return 1;
}
if (!entry.pmask && idx) { // No more ports in that group and an actual entry?
// Delete Entry
@@ -328,14 +355,12 @@ void igmp_packet_handler(void) __banked
reg_read_m(RTL837X_TBL_CTRL);
} while (sfr_data[3] & 0x1);
print_string("IGMP Entry deleted\n");
return;
return 1;
}
} else { // Unknown message: ignore.
return;
}
if (!entry.pmask)
return;
return 1;
print_string("Updating IGMP entry\n");
// Write the updated entry
#ifdef IPMC_USES_L3MC
@@ -372,4 +397,5 @@ void igmp_packet_handler(void) __banked
reg_read_m(RTL837x_TBL_DATA_0);
print_sfr_data();
#endif
return 1;
}
+9 -1
View File
@@ -3,10 +3,18 @@
#include <stdint.h>
#define IGMP_PROTO 2
#define IGMP_V1_REPORT 0x12
#define IGMP_V2_REPORT 0x16
#define IGMP_V2_LEAVE 0x17
#define IGMP_V3_REPORT 0x22
#define IGMP_V3_TO_INCLUDE 3
#define IGMP_V3_TO_EXCLUDE 4
void igmp_setup(void) __banked;
void igmp_enable(void) __banked;
void igmp_router_port_set(uint16_t pmask) __banked;
void igmp_packet_handler(void) __banked;
uint8_t igmp_packet_handler(void) __banked;
void igmp_show(void) __banked;
#endif
+13 -1
View File
@@ -9,7 +9,7 @@
#include "boot.h"
extern __xdata uint8_t sfr_data[4];
extern __code struct machine machine;
extern __code const struct machine machine;
extern __xdata struct machine_runtime machine_detected;
#pragma codeseg BANK2
@@ -92,8 +92,18 @@ void static sds_init(void)
}
void static tm_init(void)
{
reg_read_m(RTL837X_TM_CTRL2);
sfr_mask_data(0, TM_CTRL2_EN_LATCH, 0);
reg_write_m(RTL837X_TM_CTRL2);
}
void rtl8373_init(void) __banked
{
tm_init();
print_string("\nrtl8373_init called\n");
// r65d8:3ffbedff R65d8-3ffbedff
@@ -191,6 +201,8 @@ void rtl8373_init(void) __banked
void rtl8372_init(void) __banked
{
tm_init();
print_string("\nrtl8372_init called\n");
sds_init();
+4 -147
View File
@@ -15,7 +15,7 @@
#include "rtl837x_leds.h"
#include "machine.h"
extern __code struct machine machine;
extern __code const struct machine machine;
#include "uip.h"
@@ -24,149 +24,6 @@ extern __code struct machine machine;
extern __xdata uint8_t sfr_data[4];
void leds_dump(void) __banked
{
print_string("RTL837X_PIN_MUX_0: "); print_reg(RTL837X_PIN_MUX_0); write_char('\n');
print_string("RTL837X_REG_LED_GLB_IO_EN: "); print_reg(RTL837X_REG_LED_GLB_IO_EN); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET0: "); print_reg(RTL837X_REG_LED1_0_SET0); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET0: "); print_reg(RTL837X_REG_LED3_2_SET0); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET1: "); print_reg(RTL837X_REG_LED1_0_SET1); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET1: "); print_reg(RTL837X_REG_LED3_2_SET1); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET2: "); print_reg(RTL837X_REG_LED1_0_SET2); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET2: "); print_reg(RTL837X_REG_LED3_2_SET2); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET3: "); print_reg(RTL837X_REG_LED1_0_SET3); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET3: "); print_reg(RTL837X_REG_LED3_2_SET3); write_char('\n');
print_string("RTL837X_REG_LED3_0_SET1: "); print_reg(RTL837X_REG_LED3_0_SET1); write_char('\n');
print_string("RTL837X_REG_LED3_0_SET3: "); print_reg(RTL837X_REG_LED3_0_SET3); write_char('\n');
print_string("RTL837X_LED_PORT_SET_SEL: "); print_reg(RTL837X_LED_PORT_SET_SEL); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_1: "); print_reg(RTL837X_REG_LED_GLB_MUX_1); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_2: "); print_reg(RTL837X_REG_LED_GLB_MUX_2); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_3: "); print_reg(RTL837X_REG_LED_GLB_MUX_3); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_4: "); print_reg(RTL837X_REG_LED_GLB_MUX_4); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_5: "); print_reg(RTL837X_REG_LED_GLB_MUX_5); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_6: "); print_reg(RTL837X_REG_LED_GLB_MUX_6); write_char('\n');
print_string("RTL837X_REG_LED_GLB_ACTIVE: "); print_reg(RTL837X_REG_LED_GLB_ACTIVE); write_char('\n');
print_string("RTL837X_REG_LED_MODE: "); print_reg(RTL837X_REG_LED_MODE); write_char('\n');
print_string("LED pad Configuration:\n");
for (uint8_t i = 0; i < 28; i++) {
print_byte(i);
write_char(' ');
}
write_char('\n');
for (uint8_t i = 0; i < 28; i++) {
switch (i % 5) {
case 0: // 0
reg_read_m(RTL837X_REG_LED_GLB_MUX_1 + (i / 5) * 4);
print_byte(sfr_data[3] & 0x3f);
break;
case 1: // 6
print_byte(((sfr_data[3] >> 6) | (sfr_data[2] << 2)) & 0x3f);
break;
case 2: // 12
print_byte(((sfr_data[1] << 4) | (sfr_data[2] >> 4)) & 0x3f);
break;
case 3: // 18
print_byte((sfr_data[1] >> 2) & 0x3f);
break;
case 4: // 24
print_byte(sfr_data[0] & 0x3f);
break;
}
write_char(' ');
}
write_char('\n');
print_string("LED-set Configuration:\n");
print_string("LED-ID\t\t0\t\t1\t\t2\t\t3\n");
for (__xdata uint8_t set = 0; set < 4; set++) {
print_string("SET "); write_char('0' + set); write_char(':');
for (__xdata uint8_t ledid = 0; ledid < 4; ledid++) {
print_string("\t ");
uint8_t b;
if (set < 2) {
reg_read_m(RTL837X_REG_LED3_0_SET1);
b = sfr_data[3-((set << 1) + (ledid >> 1))];
print_byte(ledid & 1 ? b >> 4 : b & 0xf);
} else {
reg_read_m(RTL837X_REG_LED3_0_SET3);
b = sfr_data[3-(((set-2) << 1) + (ledid >> 1))];
print_byte(ledid & 1 ? b >> 4 : b & 0xf);
}
reg_read_m(RTL837X_REG_LED1_0_SET0 - set * 8 - ((ledid >> 1) * 4));
if (! (ledid & 1)) { // LEDID 0, 2
print_byte(sfr_data[2]); print_byte(sfr_data[3]);
} else {
print_byte(sfr_data[0]); print_byte(sfr_data[1]);
}
}
write_char('\n');
}
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
reg_read_m(RTL837X_LED_PORT_SET_SEL);
__xdata uint8_t set = sfr_data[3 - (i >> 2)];
set = (set >> ((i & 3) << 1));
print_string("Port "); write_char('0' + i); print_string(": SET ");
write_char('0' + set);
print_string(": ");
for (__xdata uint8_t ledid = 0; ledid < 4; ledid++) {
write_char('(');
reg_read_m(RTL837X_REG_LED1_0_SET0 - set * 8 - ((ledid >> 1) * 4));
if (ledid & 1) { // LEDID 1, 3
sfr_data[2] = sfr_data[0];
sfr_data[3] = sfr_data[1];
}
if (sfr_data[3] & 0x01)
print_string(" 2G5");
if (sfr_data[3] & 0x02)
print_string(" TWO_1G");
if (sfr_data[3] & 0x04)
print_string(" 1G");
if (sfr_data[3] & 0x08)
print_string(" 500M");
if (sfr_data[3] & 0x10)
print_string(" 100M");
if (sfr_data[3] & 0x20)
print_string(" 10M");
if (sfr_data[3] & 0x40)
print_string(" LINK");
if (sfr_data[3] & 0x80)
print_string(" LINK_FLASH");
if (sfr_data[2] & 0x01)
print_string(" ACT");
if (sfr_data[2] & 0x02)
print_string(" RX");
if (sfr_data[2] & 0x04)
print_string(" TX");
if (sfr_data[2] & 0x08)
print_string(" COL");
if (sfr_data[2] & 0x10)
print_string(" DUPLEX");
if (sfr_data[2] & 0x20)
print_string(" TRAINING");
if (sfr_data[2] & 0x40)
print_string(" MASTER");
__xdata uint8_t b;
if (set < 2) {
reg_read_m(RTL837X_REG_LED3_0_SET1);
b = sfr_data[3-((set << 1) + (ledid >> 1))];
} else {
reg_read_m(RTL837X_REG_LED3_0_SET3);
b = sfr_data[3-(((set-2) << 1) + (ledid >> 1))];
}
b = ledid & 1 ? b >> 4 : b & 0xf;
if (b & 0x1)
print_string(" 10G");
if (b & 0x2)
print_string(" TWO_5G");
if (b & 0x4)
print_string(" 5G");
if (b & 0x8)
print_string(" TWO_2G5");
print_string("), ");
}
write_char('\n');
}
}
void leds_setup(void) __banked
{
@@ -261,7 +118,7 @@ void leds_setup(void) __banked
sfr_data[3] = sfr_data[2] = sfr_data[1] = sfr_data[0] = 0;
reg_write_m(RTL837X_REG_LED3_0_SET1);
reg_write_m(RTL837X_REG_LED3_0_SET3);
__code uint8_t * __xdata lptr = &machine.led_sets[0][0];
__code const uint8_t * __xdata lptr = &machine.led_sets[0][0];
for (__xdata uint8_t set = 0; set < 4; set++) {
sfr_data[0] = *(lptr + 5);
sfr_data[1] = *(lptr + 4);
@@ -275,7 +132,7 @@ void leds_setup(void) __banked
reg_write_m(RTL837X_REG_LED3_0_SET1);
} else {
reg_read_m(RTL837X_REG_LED3_0_SET3);
sfr_data[3 - (set << 1)] = (*(lptr + 6) << 4) | (*(lptr + 2));
sfr_data[3 - ((set - 2) << 1)] = (*(lptr + 6) << 4) | (*(lptr + 2));
reg_write_m(RTL837X_REG_LED3_0_SET3);
}
lptr += 8;
@@ -291,7 +148,7 @@ void leds_setup(void) __banked
reg_write_m(RTL837X_REG_LED3_0_SET1);
} else {
reg_read_m(RTL837X_REG_LED3_0_SET3);
sfr_data[2 - (set << 1)] = (*(lptr + 6) << 4) | (*(lptr + 2));
sfr_data[2 - ((set - 2) << 1)] = (*(lptr + 6) << 4) | (*(lptr + 2));
reg_write_m(RTL837X_REG_LED3_0_SET3);
}
lptr += 8;
+155
View File
@@ -0,0 +1,155 @@
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_leds.h"
#include "machine.h"
#pragma codeseg BANK3
#pragma constseg BANK3
extern __code const struct machine machine;
extern __xdata uint8_t sfr_data[4];
void leds_dump(void) __banked
{
print_string("RTL837X_PIN_MUX_0: "); print_reg(RTL837X_PIN_MUX_0); write_char('\n');
print_string("RTL837X_REG_LED_GLB_IO_EN: "); print_reg(RTL837X_REG_LED_GLB_IO_EN); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET0: "); print_reg(RTL837X_REG_LED1_0_SET0); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET0: "); print_reg(RTL837X_REG_LED3_2_SET0); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET1: "); print_reg(RTL837X_REG_LED1_0_SET1); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET1: "); print_reg(RTL837X_REG_LED3_2_SET1); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET2: "); print_reg(RTL837X_REG_LED1_0_SET2); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET2: "); print_reg(RTL837X_REG_LED3_2_SET2); write_char('\n');
print_string("RTL837X_REG_LED1_0_SET3: "); print_reg(RTL837X_REG_LED1_0_SET3); write_char('\n');
print_string("RTL837X_REG_LED3_2_SET3: "); print_reg(RTL837X_REG_LED3_2_SET3); write_char('\n');
print_string("RTL837X_REG_LED3_0_SET1: "); print_reg(RTL837X_REG_LED3_0_SET1); write_char('\n');
print_string("RTL837X_REG_LED3_0_SET3: "); print_reg(RTL837X_REG_LED3_0_SET3); write_char('\n');
print_string("RTL837X_LED_PORT_SET_SEL: "); print_reg(RTL837X_LED_PORT_SET_SEL); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_1: "); print_reg(RTL837X_REG_LED_GLB_MUX_1); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_2: "); print_reg(RTL837X_REG_LED_GLB_MUX_2); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_3: "); print_reg(RTL837X_REG_LED_GLB_MUX_3); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_4: "); print_reg(RTL837X_REG_LED_GLB_MUX_4); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_5: "); print_reg(RTL837X_REG_LED_GLB_MUX_5); write_char('\n');
print_string("RTL837X_REG_LED_GLB_MUX_6: "); print_reg(RTL837X_REG_LED_GLB_MUX_6); write_char('\n');
print_string("RTL837X_REG_LED_GLB_ACTIVE: "); print_reg(RTL837X_REG_LED_GLB_ACTIVE); write_char('\n');
print_string("RTL837X_REG_LED_MODE: "); print_reg(RTL837X_REG_LED_MODE); write_char('\n');
print_string("LED pad Configuration:\n");
for (uint8_t i = 0; i < 28; i++) {
print_byte(i);
write_char(' ');
}
write_char('\n');
for (uint8_t i = 0; i < 28; i++) {
switch (i % 5) {
case 0: // 0
reg_read_m(RTL837X_REG_LED_GLB_MUX_1 + (i / 5) * 4);
print_byte(sfr_data[3] & 0x3f);
break;
case 1: // 6
print_byte(((sfr_data[3] >> 6) | (sfr_data[2] << 2)) & 0x3f);
break;
case 2: // 12
print_byte(((sfr_data[1] << 4) | (sfr_data[2] >> 4)) & 0x3f);
break;
case 3: // 18
print_byte((sfr_data[1] >> 2) & 0x3f);
break;
case 4: // 24
print_byte(sfr_data[0] & 0x3f);
break;
}
write_char(' ');
}
write_char('\n');
print_string("LED-set Configuration:\n");
print_string("LED-ID\t\t0\t\t1\t\t2\t\t3\n");
for (__xdata uint8_t set = 0; set < 4; set++) {
print_string("SET "); write_char('0' + set); write_char(':');
for (__xdata uint8_t ledid = 0; ledid < 4; ledid++) {
print_string("\t ");
uint8_t b;
if (set < 2) {
reg_read_m(RTL837X_REG_LED3_0_SET1);
b = sfr_data[3-((set << 1) + (ledid >> 1))];
print_byte(ledid & 1 ? b >> 4 : b & 0xf);
} else {
reg_read_m(RTL837X_REG_LED3_0_SET3);
b = sfr_data[3-(((set-2) << 1) + (ledid >> 1))];
print_byte(ledid & 1 ? b >> 4 : b & 0xf);
}
reg_read_m(RTL837X_REG_LED1_0_SET0 - set * 8 - ((ledid >> 1) * 4));
if (! (ledid & 1)) { // LEDID 0, 2
print_byte(sfr_data[2]); print_byte(sfr_data[3]);
} else {
print_byte(sfr_data[0]); print_byte(sfr_data[1]);
}
}
write_char('\n');
}
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
reg_read_m(RTL837X_LED_PORT_SET_SEL);
__xdata uint8_t set = sfr_data[3 - (i >> 2)];
set = (set >> ((i & 3) << 1));
print_string("Port "); write_char('0' + i); print_string(": SET ");
write_char('0' + set);
print_string(": ");
for (__xdata uint8_t ledid = 0; ledid < 4; ledid++) {
write_char('(');
reg_read_m(RTL837X_REG_LED1_0_SET0 - set * 8 - ((ledid >> 1) * 4));
if (ledid & 1) { // LEDID 1, 3
sfr_data[2] = sfr_data[0];
sfr_data[3] = sfr_data[1];
}
if (sfr_data[3] & 0x01)
print_string(" 2G5");
if (sfr_data[3] & 0x02)
print_string(" TWO_1G");
if (sfr_data[3] & 0x04)
print_string(" 1G");
if (sfr_data[3] & 0x08)
print_string(" 500M");
if (sfr_data[3] & 0x10)
print_string(" 100M");
if (sfr_data[3] & 0x20)
print_string(" 10M");
if (sfr_data[3] & 0x40)
print_string(" LINK");
if (sfr_data[3] & 0x80)
print_string(" LINK_FLASH");
if (sfr_data[2] & 0x01)
print_string(" ACT");
if (sfr_data[2] & 0x02)
print_string(" RX");
if (sfr_data[2] & 0x04)
print_string(" TX");
if (sfr_data[2] & 0x08)
print_string(" COL");
if (sfr_data[2] & 0x10)
print_string(" DUPLEX");
if (sfr_data[2] & 0x20)
print_string(" TRAINING");
if (sfr_data[2] & 0x40)
print_string(" MASTER");
__xdata uint8_t b;
if (set < 2) {
reg_read_m(RTL837X_REG_LED3_0_SET1);
b = sfr_data[3-((set << 1) + (ledid >> 1))];
} else {
reg_read_m(RTL837X_REG_LED3_0_SET3);
b = sfr_data[3-(((set-2) << 1) + (ledid >> 1))];
}
b = ledid & 1 ? b >> 4 : b & 0xf;
if (b & 0x1)
print_string(" 10G");
if (b & 0x2)
print_string(" TWO_5G");
if (b & 0x4)
print_string(" 5G");
if (b & 0x8)
print_string(" TWO_2G5");
print_string("), ");
}
write_char('\n');
}
}
+161
View File
@@ -0,0 +1,161 @@
#pragma codeseg BANK3
#pragma constseg BANK3
#include "rtl837x_common.h"
#include "rtl837x_lldp.h"
#include "rtl837x_sfr.h"
#include "rtl837x_common.h"
#include "dhcp.h"
#include "uip.h"
#include "uip/uip.h"
#include "machine.h"
extern __xdata bool lldp_enabled;
extern __code const struct machine machine;
__xdata uint16_t lldp_ticks;
__xdata uint16_t lldp_logical_port_status = 0xffff;
void lldp_init(void) __banked
{
lldp_ticks = 0;
}
void lldp_tick(void) __banked
{
lldp_ticks++;
if (lldp_ticks < LLDP_TX_INTERVAL_TICKS)
return;
lldp_ticks = 0;
if(lldp_enabled == 1)
lldp_send();
}
//Outgoing LLDP packet.
struct lldp_pkt {
struct uip_eth_addr dst;
struct uip_eth_addr src;
struct rtl_tag rtl_tag;
uint16_t ether_type;
uint8_t payload[64];
};
#define LLDP_O ((__xdata struct lldp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE])
// The type is 7 bits and the length is 9 bits
// we can represent this as the 7 most significant
// bits of the type bit and leave the least significant
// bit as 0 always as we're not going to need 2^9 for
// the length at any point
#define LLDP_TYPE(value) (value << 1)
void lldp_send(void) __banked __reentrant
{
__xdata uint8_t *p;
uint8_t port_position;
lldp_set_addresses();
lldp_set_rtl_wrapper();
p = LLDP_O->payload;
//Chassis ID
*p++ = LLDP_TYPE(LLDP_CHASSIS_ID_TLV_TYPE);
*p++ = LLDP_CHASSIS_ID_TLV_LENGTH;
*p++ = LLDP_CHASSIS_ID_TLV_SUBTYPE;
memcpy(p, uip_ethaddr.addr, MAC_ADDR_LEN);
p += MAC_ADDR_LEN;
//Port ID
*p++ = LLDP_TYPE(LLDP_PORT_ID_TLV_TYPE);
*p++ = LLDP_PORT_ID_TLV_LENGTH;
*p++ = LLDP_PORT_ID_TLV_SUBTYPE;
port_position = (p-LLDP_O->payload);
*p++ = '0'; // filled in per port below
//TTL
*p++ = LLDP_TYPE(LLDP_TTL_TLV_TYPE);
*p++ = LLDP_TTL_TLV_LENGTH;
*p++ = 0x00; //padding
*p++ = LLDP_TTL_TTL_SECONDS;
p += lldp_sysname(p);
p += lldp_sysdesc(p);
// End of LLDPDU TLV
*p++ = 0x00; //padding
*p++ = 0x00; //padding
while (p < (LLDP_O->payload + LLDP_MIN_ETHERNET_PAYLOAD_LENGTH))
*p++ = 0x00;
/*
* uip_len is the Ethernet frame length excluding FCS.
*
* The frame consists of:
*
* dst mac
* src mac
* rtl_tag sizeof(struct rtl_tag)
* EtherType (2 bytes)
* payload (how far we've moved p pointer)
*/
uip_len = MAC_ADDR_LEN + MAC_ADDR_LEN + sizeof(struct rtl_tag) + LLDP_ETHERTYPE_LENGTH + (p - LLDP_O->payload);
for (uint8_t port = machine.min_port; port <= machine.max_port; port++) {
if ((1 << port) & lldp_logical_port_status) {
LLDP_O->payload[port_position] = '0' + machine.log_to_phys_port[port];
LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port);
tcpip_output();
}
}
}
void lldp_set_addresses(void) __banked {
// LLDP destination multicast addr: 01:80:c2:00:00:0e
memcpyc(LLDP_O->dst.addr, "\x01\x80\xc2\x00\x00\x0e", MAC_ADDR_LEN);
memcpy(LLDP_O->src.addr, uip_ethaddr.addr, MAC_ADDR_LEN);
}
void lldp_set_rtl_wrapper(void) __banked {
/*
* This is the RTL CPU tag, not the Ethernet EtherType.
* port 0 -> 0x0001
* port 1 -> 0x0002
* port 2 -> 0x0004
*/
LLDP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
LLDP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
LLDP_O->rtl_tag.reason = 0x00;
LLDP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS);
LLDP_O->ether_type = HTONS(LLDP_ETHERTYPE);
}
uint8_t lldp_sysname(__xdata uint8_t *p) __banked
{
uint8_t hostname_length = strlen_x(hostname);
*p++ = LLDP_TYPE(LLDP_SYSNAME_TLV_TYPE);
*p++ = hostname_length;
memcpy(p, hostname, hostname_length);
return hostname_length + 2;
}
uint8_t lldp_sysdesc(__xdata uint8_t *p) __banked
{
uint16_t machine_name_length = strlen(machine.machine_name);
*p++ = LLDP_TYPE(LLDP_SYSDESC_TLV_TYPE);
*p++ = machine_name_length;
memcpyc(p, machine.machine_name, machine_name_length);
return machine_name_length + 2;
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef _LLDP_H_
#define _LLDP_H_
#include <stdint.h>
#define LLDP_MIN_ETHERNET_PAYLOAD_LENGTH 46
#define LLDP_ETHERTYPE 0x88cc
#define LLDP_ETHERTYPE_LENGTH 2
#define LLDP_MAX_FRAME 256
#define MAC_ADDR_LEN 6
#define LLDP_TX_INTERVAL_TICKS 6000
#define LLDP_PORT_ID_TLV_TYPE 2
#define LLDP_PORT_ID_TLV_LENGTH 0x02
#define LLDP_PORT_ID_TLV_SUBTYPE 0x07
#define LLDP_TTL_TLV_TYPE 3
#define LLDP_TTL_TLV_LENGTH 2
#define LLDP_TTL_TTL_SECONDS 120
#define LLDP_CHASSIS_ID_TLV_TYPE 1
#define LLDP_CHASSIS_ID_TLV_LENGTH 7
#define LLDP_CHASSIS_ID_TLV_SUBTYPE 4
#define LLDP_SYSNAME_TLV_TYPE 5
#define LLDP_SYSDESC_TLV_TYPE 6
void lldp_init(void) __banked;
void lldp_tick(void) __banked;
void lldp_send(void) __banked;
void lldp_set_addresses(void) __banked;
void lldp_set_rtl_wrapper(void) __banked;
uint8_t lldp_sysname(__xdata uint8_t *p) __banked;
uint8_t lldp_sysdesc(__xdata uint8_t *p) __banked;
#endif
+22 -5
View File
@@ -20,9 +20,11 @@
#pragma codeseg BANK2
#pragma constseg BANK2
extern __code uint16_t bit_mask[16];
extern __code const uint16_t bit_mask[16];
extern __code const struct machine machine;
extern __xdata struct machine_runtime machine_detected;
extern volatile __xdata uint32_t ticks;
extern __xdata uint32_t rtl8224_release_tick;
__xdata struct phy_settings phy_settings;
@@ -33,7 +35,7 @@ __xdata struct phy_settings phy_settings;
// Note: Adding `Swapping the RX for N-devices`-setting on the end of the array, didn't work.
// Setting will apply but still no packets flow.
// Settings are `0x2000, 0xc10c`,
__code uint16_t rtl8224_sds0_setttings[42] = {
__code const uint16_t rtl8224_sds0_setttings[42] = {
// SDS_DATA, SDS_CMD
0x4480, 0xc842,
0x0400, 0xc9c2,
@@ -214,9 +216,20 @@ void phy_config(uint8_t phy) __banked
}
void rtl8224_wait_ready(void) __banked
{
do {
rtl8224_read_reg_u16(RTL837X_REG_CHIP_ID + 1);
if (SFR_DATA_U16 == 0x8224)
return;
} while ((ticks - rtl8224_release_tick) < (SYS_TICK_HZ * 5 / 2));
print_string("\r\nRTL8224 not responding\r\n");
}
void phy_config_8224(void) __banked
{
uint16_t pval;
uint16_t tries;
print_string("\r\nphy_config_8224 called\r\nRTL8224 ID: ");
// Print RTL8224 chip id
@@ -244,9 +257,12 @@ void phy_config_8224(void) __banked
i++;
rtl8224_write_reg_u16(RTL837X_SDS_INDACS_CMD, rtl8224_sds0_setttings[i]);
i++;
tries = 0xffff;
do {
rtl8224_read_reg_u16(0x3f8);
} while (SFR_DATA_8 & 0x80);
} while ((SFR_DATA_8 & 0x80) && --tries);
if (SFR_DATA_8 & 0x80)
print_string("\r\nRTL8224 SerDes access timed out\r\n");
}
print_string("\r\nphy_config_8224 done\r\n");
@@ -354,8 +370,9 @@ void phy_set_speed(void) __banked
phy_modify(phy_settings.port, PHY_MMD31, PHY_MMD31_GBCR, 0x0200, 0x0000);
} else {
// AN Advertisement Register (MMD 7.0x0010)
// bits 0-4: 0x1 (802.3 supported), Extended Next Page format used
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1001);
// bits 0-4: 0x1 (802.3 supported), bit 10: PAUSE,
// Extended Next Page format used
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1401);
if (phy_settings.speed == PHY_SPEED_1G) {
// Multi-GBASE-TBASE-T AN Control 1 Register (MMD 7.0x0020)
// bit 14: SLAVE, bit 13: Multi-Port device, 1: LD Loop timin enableed
+1
View File
@@ -22,6 +22,7 @@ extern __xdata struct phy_settings phy_settings;
void rtl8224_phy_enable(void) __banked;
void phy_config(uint8_t phy) __banked;
void phy_config_8224(void) __banked;
void rtl8224_wait_ready(void) __banked;
void phy_set_speed(void) __banked;
void phy_set_duplex(void) __banked;
void phy_show(uint8_t port) __banked;
+1 -34
View File
@@ -10,38 +10,6 @@ extern __xdata uint8_t sfr_data[4];
#pragma codeseg BANK2
#pragma constseg BANK2
uint8_t i2c_bus_from_sda_pin(uint8_t sda_pin) __banked {
switch (sda_pin) {
case GPIO47_I2C_SDA0:
return 0;
case GPIO49_I2C_SDA1:
return 1;
case GPIO51_I2C_SDA2_UART1_RX:
return 2;
case GPIO41_I2C_SDA3_MDIO1:
return 3;
case GPIO39_I2C_SDA4:
return 4;
default:
return 0xFF;
}
}
uint8_t i2c_bus_from_scl_pin(uint8_t scl_pin) __banked{
switch (scl_pin) {
case GPIO46_I2C_SCL0:
return 0;
case GPIO48_I2C_SCL1:
return 1;
case GPIO50_I2C_SCL2_UART1_TX:
return 2;
case GPIO40_I2C_SCL3_MDC1:
return 3;
default:
return 0xFF;
}
}
/* Returns RTL837X_REG_GPIO_XX_OUTPUT register address */
static uint16_t gpio_output_reg(uint8_t pin) __banked{
return pin < 32 ? RTL837X_REG_GPIO_00_31_OUTPUT : RTL837X_REG_GPIO_32_63_OUTPUT;
@@ -147,8 +115,7 @@ bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) __banked __reentrant
REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00,
0x1 << (I2C_MEM_ADDR_WIDTH - 16) | len,
(dev >> 5) | i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5
| i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2,
(dev >> 5) | machine.sfp_port[slot].i2c,
((dev << 3) & 0xff) | 0x1);
do {
-6
View File
@@ -71,12 +71,6 @@
/* Not available GPIO */
#define GPIO_NA 0xFF
/* Convert SDA PIN GPIO to I2C bus number */
uint8_t i2c_bus_from_sda_pin(uint8_t sda_pin) __banked;
/* Convert SCL PIN GPIO to I2C bus number */
uint8_t i2c_bus_from_scl_pin(uint8_t scl_pin) __banked;
/*
* Setup a GPIO pin as input
* pin: GPIO pin number 0-63
+45 -24
View File
@@ -18,9 +18,9 @@
#pragma codeseg BANK1
#pragma constseg BANK1
extern __code uint8_t * __code hex;
extern __code uint16_t bit_mask[16];
extern __code struct machine machine;
extern __code const uint8_t * __code const hex;
extern __code const uint16_t bit_mask[16];
extern __code const struct machine machine;
extern __xdata uint8_t sfr_data[4];
extern __xdata uint16_t vlan_ptr;
extern __xdata uint8_t vlan_names[VLAN_NAMES_SIZE];
@@ -40,7 +40,6 @@ static void wait_table_ready(void)
void port_mirror_set(uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked
{
print_string("\nport_mirror_set called \n");
print_string("Mirroring port: "); print_byte(port); print_string(" with rx-mask: ");
print_short(rx_pmask); print_string(", tx mask: "); print_short(tx_pmask);
write_char('\n');
@@ -52,7 +51,6 @@ void port_mirror_set(uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t t
void port_mirror_del(void) __banked
{
print_string("\nport_mirror_del called \n");
REG_SET(RTL837x_MIRROR_CTRL, 0);
}
@@ -102,9 +100,9 @@ static void port_pvid_write(uint8_t port, __xdata uint16_t pvid)
reg_read_m(reg);
if (port & 0x1) {
REG_WRITE(reg, sfr_data[0], pvid >> 4, sfr_data[2] & 0x0f | (pvid << 4), sfr_data[3]);
REG_WRITE(reg, sfr_data[0], pvid >> 4, (sfr_data[2] & 0x0f) | (pvid << 4), sfr_data[3]);
} else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0 | (pvid >> 8), pvid);
REG_WRITE(reg, sfr_data[0], sfr_data[1], (sfr_data[2] & 0xf0) | (pvid >> 8), pvid);
}
}
@@ -112,7 +110,6 @@ void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked
{
uint8_t lag = port_lag_of(port);
print_string("\nport_pvid_set called \n");
if (lag == PORT_LAG_NONE) {
port_pvid_write(port, pvid);
return;
@@ -141,7 +138,6 @@ void vlan_delete(uint16_t vlan) __banked
if (!vlan || vlan >= 0xfff)
return;
print_string("\nvlan_delete called \n"); print_short(vlan);
vlan_name_remove(vlan);
REG_WRITE(RTL837x_TBL_DATA_IN_A, 0, 0, 0, 0);
REG_WRITE(RTL837X_TBL_CTRL, vlan >> 8, vlan, TBL_VLAN, TBL_WRITE | TBL_EXECUTE);
@@ -232,7 +228,7 @@ void vlan_create(void) __banked
vlan_settings.members |= 0x0200; // Set 10th bit
vlan_settings.tagged |= 0x0200;
print_string("\nvlan_create called\nvlan: "); print_short(vlan_settings.vlan);
print_string("vlan: "); print_short(vlan_settings.vlan);
print_string(", members: "); print_short(vlan_settings.members);
print_string(", tagged: "); print_short(vlan_settings.tagged); write_char('\n');
@@ -250,7 +246,6 @@ void vlan_create(void) __banked
wait_table_ready();
print_string("vlan_create done \n");
}
@@ -263,7 +258,6 @@ void vlan_create(void) __banked
*/
void vlan_setup(void) __banked
{
print_string("\nvlan_setup called \n");
// No VLAN names set up so far
vlan_ptr = 0;
@@ -285,7 +279,7 @@ void vlan_setup(void) __banked
#endif
reg_read_m(reg);
if (i & 0x1) {
REG_WRITE(reg, sfr_data[0], 0, sfr_data[2] & 0x0f | 0x10, sfr_data[3]);
REG_WRITE(reg, sfr_data[0], 0, (sfr_data[2] & 0x0f) | 0x10, sfr_data[3]);
} else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01);
}
@@ -331,7 +325,6 @@ void vlan_setup(void) __banked
print_string("\nvlan_setup, REG 0x4f4c: "); print_reg(0x4f4c);
#endif
print_string("vlan_setup done \n");
}
@@ -354,7 +347,6 @@ void port_l2_forget_port(uint8_t port) __banked
*/
uint8_t port_l2_forget(void) __banked
{
print_string("\nport_l2_forget called\n");
// Configure the entries to be flushed:
// port-based (bits 0-1 are 0 and dynamic entries, bit 2 specifies dynamic entries
REG_SET(RTL837x_L2_TBL_FLUSH_CNF, 0x0);
@@ -370,7 +362,6 @@ uint8_t port_l2_forget(void) __banked
reg_read_m(RTL837x_L2_TBL_FLUSH_CTRL);
} while (sfr_data[1]);
print_string("port_l2_forget done\n");
return 0;
}
@@ -465,7 +456,6 @@ void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmas
*/
void port_l2_setup(void) __banked
{
print_string("\nport_l2_setup called\n");
port_l2_forget();
@@ -483,7 +473,38 @@ void port_l2_setup(void) __banked
// When maximim entries learned, then simply flood the packet
reg_bit_set(RTL837X_L2_LRN_PORT_CONSTRT_ACT, 0);
print_string("\nport_l2_setup done\n");
}
void port_media_show(uint8_t port) __banked
{
uint8_t pause;
if (!machine.is_sfp[port]) {
reg_read_m(RTL837X_REG_LINKS_STS);
if (sfr_data[(port / 8) + 1] >> (port % 8) & 1) {
print_string("Link role: ");
reg_read_m(RTL837X_MAC_MSTR_SLV_STS);
if (sfr_data[3 - (port >> 3)] & (1 << (port & 7)))
print_string("master\n");
else
print_string("slave\n");
}
}
print_string("Pause negotiated:");
reg_read_m(RTL837X_MAC_TX_PAUSE_STS);
pause = sfr_data[3 - (port >> 3)] & (1 << (port & 7));
if (pause)
print_string(" TX");
reg_read_m(RTL837X_MAC_RX_PAUSE_STS);
if (sfr_data[3 - (port >> 3)] & (1 << (port & 7))) {
print_string(" RX");
pause = 1;
}
if (!pause)
print_string(" none");
write_char('\n');
}
@@ -590,12 +611,12 @@ void port_eee_enable(__xdata uint8_t port,__xdata uint8_t speed) __banked
if (machine.is_sfp[port])
{
print_string("EEE can't be enabled for SFP port "); print_byte(port); print_string("\n");
print_string("EEE can't be enabled for SFP port "); print_phys_port(port); print_string("\n");
return;
}
REG_SET(RTL837X_EEE_CTRL_BASE + (port << 8), EEE_RX_ENABLE | EEE_TX_ENABLE);
print_string("EEE on for "); print_byte(port); print_string(" speed ");
print_string("EEE on for "); print_phys_port(port); print_string(" speed ");
// Enable all speeds up to the specified speed
if (speed & EEE_100) {
print_string("100m\n");
@@ -654,7 +675,7 @@ void port_eee_disable(uint8_t port) __banked
if (machine.is_sfp[port])
return;
print_string("EEE off for "); print_byte(port); write_char('\n');
print_string("EEE off for "); print_phys_port(port); write_char('\n');
REG_SET(RTL837X_EEE_CTRL_BASE + (port << 8), 0);
// Disable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, 0);
@@ -758,7 +779,7 @@ void port_eee_enable_all(__xdata uint8_t speed) __banked
port_eee_enable(i, speed);
} else {
if (speed & EEE_10G)
port_eee_enable(i, speed & EEE_NORESET | EEE_2G5);
port_eee_enable(i, (speed & EEE_NORESET) | EEE_2G5);
else
port_eee_enable(i, speed);
}
@@ -899,7 +920,7 @@ void vlan_dump(void) __banked
/** Set the ingress VLAN filtering */
bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked
{
if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) {
if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false;
}
if (enabled)
@@ -913,7 +934,7 @@ bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked
/** Get the ingress VLAN filtering status */
bool port_ingress_vlan_filter_get(uint8_t port) __banked
{
if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) {
if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false;
}
+1
View File
@@ -49,6 +49,7 @@ extern __xdata struct vlan_settings vlan_settings;
uint8_t port_l2_forget(void) __banked;
void port_l2_learned(void) __banked;
void port_stats_print(void) __banked;
void port_media_show(uint8_t port) __banked;
int8_t vlan_get(uint16_t vlan) __banked;
__xdata uint16_t vlan_name(uint16_t vlan) __banked;
void vlan_name_remove(uint16_t vlan) __banked;
+30
View File
@@ -9,6 +9,13 @@
#define RESET_SOC_BIT 0
#define RESET_NIC_BIT 2
#define RTL837X_TM_CTRL2 0x0338
#define TM_CTRL2_EN_LATCH 0x04
#define RTL837X_TM_RESULT 0x0340
#define RTL837X_TM_RESULT_POWERON 0x0350
// Temperature: 19 bit value, bits 18:3 in the low 16 bits of the register,
// bits 2:0 in bits 18:16. Bit 18 signed, bits 17:10 degrees C, bits 9:0 fraction.
#define RTL837X_REG_HW_CONF 0x6040
// Bits 4 & 5: CLOCK DIVIDER from 125MHz for Timer
@@ -83,6 +90,10 @@
#define RTL837X_REG_LINKS 0x63f0
#define RTL837X_REG_LINKS_89 0x63f4
#define RTL837X_REG_LINKS_STS 0x63E8
/* Per-port status bits: bit N is chip port N */
#define RTL837X_MAC_TX_PAUSE_STS 0x63FC
#define RTL837X_MAC_RX_PAUSE_STS 0x6400
#define RTL837X_MAC_MSTR_SLV_STS 0x6408
/* Each nibble encodes the link state of a port.
Port 0 appears to be the CPU port
@@ -126,6 +137,10 @@
#define RTL837X_REG_I2C_CTRL2 0x041c
#define RTL837X_REG_I2C_IN 0x0420
#define RTL837X_REG_I2C_OUT 0x0424
#define RTL837X_REG_I2C_SCL_SHIFT 5
#define RTL837X_REG_I2C_SDA_SHIFT 2
#define RTL837X_REG_I2C_SCL_MASK 0x3
#define RTL837X_REG_I2C_SDA_MASK 0x7
/*
* NIC Related registers
@@ -137,6 +152,10 @@
#define RTL837X_REG_RX_CTRL 0x785c
#define RTL837X_REG_TX_CTRL 0x7860
#define RTL837X_REG_NIC_RX_BUFF_DATA 0x7874
#define RTL837X_NIC_INT_STS 0x7854
#define RTL837X_NIC_INT_MSK 0x7858
#define NIC_INT_RXIS 0x02
#define NIC_INT_RXIE 0x02
#define RTL837X_REG_CPU_RX_CURR_PKT 0x787c
#define RTL837X_REG_NIC_TX_CURR_PKT 0x7884
#define RTL837X_REG_CPU_TX_CURR_PKT 0x7890
@@ -144,6 +163,9 @@
#define RTL837X_REG_CPU_TAG_AWARE_PMASK 0x603C
#define RTL837X_REG_MAC_FORCE_MODE 0x6344
/* Flags in byte 1 of the RX frame descriptor */
#define RX_TAG_L4_CSUM_BAD 0x04
/*
* Statistics related registers
*/
@@ -263,6 +285,9 @@
*/
#define RTL8373_RLDP_TIMER 0x1074
#define RTL837X_RMA0_CONF 0x4ecc
#define RTL837X_IMR_INT_PORT_LINK_STS_CHG 0x5f34
#define RTL837X_ISR_SW_INT_MODE 0x5f84
#define RTL837X_ISR_INT_PORT_LINK_CHG 0x5f88
#define RTL837X_RMA_CONF 0x4f1c
#define RTL837X_MSTP_STATES 0x5310
#define RTL837X_REG_LED_RLDP_1 0x65F8
@@ -302,6 +327,11 @@
#define RTL837X_IGBW_PORT_FC_CTRL 0x4C8C
#define RTL837X_EGBW_PORT_CTRL 0x1c34
#define RTL837X_EGBW_CTRL 0x447c
#define RTL837X_STORM_CTRL 0x54e4
#define RTL837X_STORM_MIDX 0x54f4
#define RTL837X_METER_RATE 0x5cf0
#define RTL837X_METER_BURST 0x5df0
#define RTL837X_METER_MODE 0x5ef0
#define EGBW_INC_IFG 0x02
#define EGBW_CPUMODE 0x01
+89
View File
@@ -0,0 +1,89 @@
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_storm.h"
#include "machine.h"
#pragma codeseg BANK3
#pragma constseg BANK3
extern __code const struct machine machine;
extern __xdata uint8_t sfr_data[4];
/* RTL837X_STORM_MIDX words, row type * 2 + half: half 0 holds ports 0-4, half 1 ports 5-9,
* 6 bits per port, each set to STORM_METER(port, type) */
static const __code uint8_t storm_midx[8][4] = {
{ 0x28, 0x92, 0x07, 0x18 }, { 0x3c, 0xe3, 0x4c, 0x2c },
{ 0x29, 0x96, 0x17, 0x59 }, { 0x3d, 0xe7, 0x5c, 0x6d },
{ 0x2a, 0x9a, 0x27, 0x9a }, { 0x3e, 0xeb, 0x6c, 0xae },
{ 0x2b, 0x9e, 0x37, 0xdb }, { 0x3f, 0xef, 0x7c, 0xef },
};
static __code const char * __code const storm_names[STORM_TYPES] = { " bcast ", " mcast ", " ucast ", " umcast " };
void storm_set(uint8_t port, __xdata uint8_t type, __xdata uint32_t rate, __xdata uint8_t pps) __banked
{
__xdata uint8_t idx = STORM_METER(port, type);
__xdata uint8_t row = (type << 1) | (port >= 5 ? 1 : 0);
__xdata uint8_t * __xdata r = (uint8_t *)&rate;
sfr_data[0] = 0;
sfr_data[1] = r[2];
sfr_data[2] = r[1];
sfr_data[3] = r[0];
reg_write_m(RTL837X_METER_RATE + (idx << 2));
if (!pps) {
sfr_data[1] = 0;
sfr_data[2] = 0x20;
sfr_data[3] = 0;
}
reg_write_m(RTL837X_METER_BURST + (idx << 2));
if (pps)
reg_bit_set(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f);
else
reg_bit_clear(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f);
sfr_data[0] = storm_midx[row][0];
sfr_data[1] = storm_midx[row][1];
sfr_data[2] = storm_midx[row][2];
sfr_data[3] = storm_midx[row][3];
reg_write_m(RTL837X_STORM_MIDX + (row << 2));
reg_bit_set(RTL837X_STORM_CTRL + (type << 2), port);
}
void storm_off(uint8_t port, __xdata uint8_t type) __banked
{
reg_bit_clear(RTL837X_STORM_CTRL + (type << 2), port);
}
void storm_show(void) __banked
{
uint8_t i, t, idx;
for (i = machine.min_port; i <= machine.max_port; i++) {
print_string("port ");
write_char('0' + machine.log_to_phys_port[i]);
for (t = 0; t < STORM_TYPES; t++) {
print_string(storm_names[t]);
if (!reg_bit_test(RTL837X_STORM_CTRL + (t << 2), i)) {
print_string("off");
continue;
}
idx = STORM_METER(i, t);
reg_read_m(RTL837X_METER_RATE + (idx << 2));
print_byte(sfr_data[1]); print_byte(sfr_data[2]); print_byte(sfr_data[3]);
if (reg_bit_test(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f))
print_string(" pps");
else
print_string(" kbps");
}
write_char('\n');
}
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef _RTL837X_STORM_H_
#define _RTL837X_STORM_H_
#include <stdint.h>
#define STORM_TYPES 4
#define STORM_METER_BASE 24
#define STORM_METER(port, type) (STORM_METER_BASE + (((port) << 2) | (type)))
void storm_set(uint8_t port, __xdata uint8_t type, __xdata uint32_t rate, __xdata uint8_t pps) __banked;
void storm_off(uint8_t port, __xdata uint8_t type) __banked;
void storm_show(void) __banked;
#endif
+7 -4
View File
@@ -15,12 +15,14 @@
#include "uip.h"
#include "machine.h"
extern __xdata uint8_t err_status;
// All entry points are __banked and nothing here runs from an interrupt,
// so the module does not need to stay in the resident bank
#pragma codeseg BANK2
#pragma constseg BANK2
extern __code struct machine machine;
extern __code const struct machine machine;
extern __xdata uint8_t sfr_data[4];
extern __xdata struct machine_runtime machine_detected;
@@ -33,7 +35,7 @@ extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
extern __xdata uint8_t cmd_words_len;
extern __xdata uint8_t cmd_words_b[15];
extern __xdata char save_cmd; /* 0 while execute_config() replays the saved config */
uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd);
uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd);
uint8_t atoi_byte(uint8_t idx);
uint8_t cmd_parse_port_separator(uint8_t idx);
extern __xdata uint8_t atoi_results_u8;
@@ -996,7 +998,7 @@ void stp_parse(void) __banked __reentrant
if (cmd_words_len < 3)
goto err;
if (cmd_compare(1, "ent") || cmd_compare(1, "lag")) {
if (cmd_compare(1, "port") || cmd_compare(1, "lag")) {
stp_lag_map();
if (cmd_words_len < 4)
goto err;
@@ -1134,5 +1136,6 @@ void stp_parse(void) __banked __reentrant
}
return;
err:
print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | ent <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n");
err_status = ERR_INVALID_ARGUMENT;
print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | port <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n");
}
+174 -97
View File
@@ -16,6 +16,7 @@
#include "rtl837x_leds.h"
#include "rtl837x_bandwidth.h"
#include "rtl837x_init.h"
#include "rtl837x_lldp.h"
#include "dhcp.h"
#include "cmd_parser.h"
#include "cmd_editor.h"
@@ -79,20 +80,23 @@ void crc16_bank1(__xdata uint8_t *v) __naked;
__xdata uint8_t idle_ready;
__code uint8_t ownIP[] = { 192, 168, 2, 2 };
__code uint8_t gatewayIP[] = { 192, 168, 2, 22};
__code uint8_t netmask[] = { 255, 255, 255, 0};
__code const uint8_t ownIP[] = { 192, 168, 2, 2 };
__code const uint8_t gatewayIP[] = { 192, 168, 2, 22};
__code const uint8_t netmask[] = { 255, 255, 255, 0};
__xdata struct uip_eth_addr uip_ethaddr;
volatile __xdata uint32_t ticks;
volatile __xdata uint8_t sec_counter;
volatile __xdata uint16_t sleep_ticks;
__xdata uint8_t stp_clock;
__xdata uint8_t stp_tick_last;
__xdata uint8_t arp_age_secs;
extern __xdata struct dhcp_state dhcp_state;
#define STP_TICK_DIVIDER 3
#define STP_TICK_STEP (SYS_TICK_HZ / STP_HZ)
#define STP_CATCH_UP 8
#define SFP_TICK_STEP 10
__xdata uint8_t sfp_tick_last;
/* Buffer for serial input, SBUF_SIZE must be power of 2 < 256
* Writing to this buffer is under the sole control of the serial ISR
@@ -108,8 +112,8 @@ extern __xdata uint8_t gpio_last_value[8];
extern __xdata struct flash_region_t flash_region;
__code uint8_t * __code greeting = "\nA minimal prompt to explore the RTL8372:\n";
__code uint8_t * __code hex = "0123456789abcdef";
__code const uint8_t * __code const greeting = "\nA minimal prompt to explore the RTL8372:\n";
__code const uint8_t * __code const hex = "0123456789abcdef";
__xdata uint8_t flash_buf[FLASH_BUF_SIZE];
@@ -124,9 +128,10 @@ __xdata uint8_t tx_seq;
__xdata bool stp_enabled;
__xdata uint8_t igmpEnabled;
__xdata bool lldp_enabled;
__xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */
__code uint16_t bit_mask[16] = {
__code const uint16_t bit_mask[16] = {
0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000
};
@@ -134,6 +139,11 @@ __code uint16_t bit_mask[16] = {
__xdata uint8_t linkbits_last[4];
__xdata uint8_t linkbits_last_p89;
__data __at(0x20) volatile uint8_t evflags;
volatile __bit __at(0x00) link_irq;
volatile __bit __at(0x01) rx_irq;
volatile __bit __at(0x02) tick_pending;
__bit __at(0x03) cmd_available;
// Last known state of the SFP detection/Loss of Signal pins
__xdata bool button_last;
__xdata uint8_t button_sec_counter_last;
@@ -195,6 +205,7 @@ void isr_timer0(void) __interrupt(1)
void isr_timer2(void) __interrupt(5)
{
ticks++;
tick_pending = 1;
if (sleep_ticks > 0)
sleep_ticks--;
sec_counter++;
@@ -254,8 +265,19 @@ void write_char(char c)
write_char_no_syslog(c);
if (syslog_state.enabled) {
logbuf[syslog_state.writeptr++] = c;
syslog_state.writeptr &= (LOGBUF_SIZE - 1);
__xdata uint16_t next = (syslog_state.writeptr + 1) & (LOGBUF_SIZE - 1);
/* The ring is drained once per main loop pass, and a single command
* can print several times its size, so writing on regardless laps
* the reader and the datagram starts mid record. Drop instead, and
* flag a line so the reader empties what is there even if no
* newline has been seen yet. */
if (next == syslog_state.readptr) {
syslog_state.line_available = 1;
return;
}
logbuf[syslog_state.writeptr] = c;
syslog_state.writeptr = next;
if (c == '\n')
syslog_state.line_available = 1;
}
@@ -303,19 +325,19 @@ void itoa_short(uint16_t v)
}
void print_string(__code char *p)
void print_string(__code const char *p)
{
while (*p)
write_char(*p++);
}
void print_string_no_syslog(__code char *p)
void print_string_no_syslog(__code const char *p)
{
while (*p)
write_char_no_syslog(*p++);
}
void print_string_newline_no_syslog(__code char *p)
void print_string_newline_no_syslog(__code const char *p)
{
write_char_no_syslog('\n');
print_string_no_syslog(p);
@@ -349,7 +371,7 @@ int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len)
return 0;
}
void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len)
void memcpyc(__xdata uint8_t *dst, __code const uint8_t *src, uint16_t len)
{
while (len--)
*dst++ = *src++;
@@ -390,6 +412,16 @@ uint16_t strlen_x(__xdata const char *s)
}
uint16_t strcpy(__xdata uint8_t *dst, const char *s)
{
__xdata uint8_t *b = dst;
while (*s)
*dst++ = *s++;
*dst = 0;
return dst - b;
}
char strcmp(__xdata const uint8_t *a, __code const uint8_t *b)
{
uint8_t i = 0;
@@ -478,14 +510,14 @@ void print_cmd_prompt(void)
}
/*
* External IRQ 0 Service Routine: Called on link change?
* Note that all registers are being put on the STACK because of calling a subroutine
* External IRQ 0 Service Routine: the switch reports a link change. The
* register access macros share the SFR data bytes with the main loop, so
* the handler only latches the event; idle() reads and clears it.
*/
void isr_ext0(void) __interrupt(0)
{
EX0 = 0; // Disable interrupt for the moment
IT0 = 1; // Trigger on falling edge of external interrupt
EX0 = 1; // Re-enable interrupt
EX0 = 0;
link_irq = 1;
}
@@ -494,9 +526,8 @@ void isr_ext0(void) __interrupt(0)
*/
void isr_ext1(void) __interrupt(2)
{
// This flag should only be reset after all packets have been read
EX1 = 0;
EX1 = 1;
rx_irq = 1;
}
/*
@@ -785,7 +816,7 @@ void nic_tx_packet(uint16_t ring_ptr)
* Note that the address in the flash memory is not simply 0xbbaddr, because
* the size of a bank is merely 0xc000.
*/
uint8_t read_flash(uint8_t bank, __code uint8_t *addr)
uint8_t read_flash(uint8_t bank, __code const uint8_t *addr)
{
uint8_t v;
uint8_t current_bank = PSBANK;
@@ -973,14 +1004,19 @@ void tcpip_output(void)
}
#define RX_BUDGET 4
void handle_rx(void)
{
// Check the amount of data available on the NIC/ASIC side
reg_read_m(RTL837X_REG_NIC_RX_BUFF_DATA);
if (sfr_data[2] != 0 || sfr_data[3] != 0) {
reg_read_m(RTL837X_REG_CPU_RX_CURR_PKT);
uint16_t ring_ptr = ((uint16_t)sfr_data[2]) << 8;
ring_ptr |= sfr_data[3];
__xdata uint8_t budget = RX_BUDGET;
while (budget--) {
// Check the amount of data available on the NIC/ASIC side
reg_read(RTL837X_REG_NIC_RX_BUFF_DATA);
if (!SFR_DATA_U16)
break;
reg_read(RTL837X_REG_CPU_RX_CURR_PKT);
uint16_t ring_ptr = SFR_DATA_U16;
ring_ptr <<= 3;
if (!nic_rx_header(ring_ptr)) {
REG_SET(RTL837X_REG_NIC_RXCMD, 1);
@@ -999,6 +1035,7 @@ void handle_rx(void)
REG_SET(RTL837X_REG_NIC_RXCMD, 1);
return;
}
health_rx_frame();
#ifdef RXTXDBG
print_string("\n<< ");
@@ -1027,8 +1064,7 @@ void handle_rx(void)
tcpip_output();
}
} else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet?
&& uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x16) {
igmp_packet_handler();
&& igmp_packet_handler()) {
if (uip_len) {
tcpip_output();
}
@@ -1152,54 +1188,8 @@ void handle_button(void)
}
}
//
// An idle function that sleeps for 1 tick and does all the house-keeping
//
void idle(void)
void check_links(void)
{
PCON |= 1;
if (sec_counter >= SYS_TICK_HZ) {
sec_counter -= SYS_TICK_HZ;
reg_read_m(RTL837X_REG_SEC_COUNTER);
uint8_t v = sfr_data[3];
#ifdef DEBUG
print_string(" Tick counter: "); print_long(ticks); write_char('\n');
#endif
v++;
sfr_data[3] = v;
if (!v) {
v = sfr_data[2];
v++;
sfr_data[2] = v;
if (!v) {
v = sfr_data[1];
v++;
sfr_data[1] = v;
if (!v) {
v = sfr_data[0];
v++;
sfr_data[0] = v;
}
}
}
reg_write_m(RTL837X_REG_SEC_COUNTER);
reg_read_m(RTL837X_REG_SEC_COUNTER);
// Check for button presses once a second
handle_button();
// Age the ARP cache: uip_arp_timer() expects a 10 s cadence
if (++arp_age_secs >= 10) {
arp_age_secs = 0;
uip_arp_timer();
}
#ifdef DEBUG
print_sfr_data();
write_char('\n');
#endif
}
// Check for Link changes
reg_read_m(RTL837X_REG_LINKS_89);
__xdata uint8_t linkbits_p89 = sfr_data[3];
@@ -1232,24 +1222,104 @@ void idle(void)
cpy_4(linkbits_last, sfr_data);
}
}
}
// Check for changes with SFP modules
handle_sfp();
// Check new Packets RX
handle_rx();
// Check UIP for packets to transmit
//
// An idle function that sleeps for 1 tick and does all the house-keeping
//
static void handle_tick(void)
{
if (sec_counter >= SYS_TICK_HZ) {
sec_counter -= SYS_TICK_HZ;
reg_read_m(RTL837X_REG_SEC_COUNTER);
uint8_t v = sfr_data[3];
#ifdef DEBUG
print_string(" Tick counter: "); print_long(ticks); write_char('\n');
#endif
v++;
sfr_data[3] = v;
if (!v) {
v = sfr_data[2];
v++;
sfr_data[2] = v;
if (!v) {
v = sfr_data[1];
v++;
sfr_data[1] = v;
if (!v) {
v = sfr_data[0];
v++;
sfr_data[0] = v;
}
}
}
reg_write_m(RTL837X_REG_SEC_COUNTER);
reg_read_m(RTL837X_REG_SEC_COUNTER);
// Check for button presses once a second
handle_button();
link_irq = 1;
// Age the ARP cache: uip_arp_timer() expects a 10 s cadence
if (++arp_age_secs >= 10) {
arp_age_secs = 0;
uip_arp_timer();
}
#ifdef DEBUG
print_sfr_data();
write_char('\n');
#endif
}
health_phase(HEALTH_PH_LINK);
if (TICKS_DUE(sfp_tick_last, SFP_TICK_STEP)) {
sfp_tick_last += SFP_TICK_STEP;
handle_sfp();
}
health_phase(HEALTH_PH_SFP);
handle_tx();
// If STP protocol enabled, decrease STP timers to trigger actions
health_phase(HEALTH_PH_TX);
if (stp_enabled) {
if (!stp_clock) {
stp_clock = STP_TICK_DIVIDER;
uint8_t n = STP_CATCH_UP;
while (n-- && TICKS_DUE(stp_tick_last, STP_TICK_STEP)) {
stp_tick_last += STP_TICK_STEP;
stp_timers();
} else {
stp_clock--;
}
}
// Check whether a command is waiting in the cmd_buffer and execute
health_phase(HEALTH_PH_STP);
lldp_tick();
health_phase(HEALTH_LLDP);
}
void idle(void)
{
if (!evflags)
PCON |= 1;
health_loop_start();
if (tick_pending) {
tick_pending = 0;
handle_tick();
}
if (link_irq) {
link_irq = 0;
REG_SET(RTL837X_ISR_INT_PORT_LINK_CHG, 0x3ff);
EX0 = 1;
check_links();
}
health_phase(HEALTH_PH_LINK);
if (rx_irq) {
rx_irq = 0;
handle_rx();
REG_SET(RTL837X_NIC_INT_STS, NIC_INT_RXIS);
reg_read(RTL837X_REG_NIC_RX_BUFF_DATA);
if (SFR_DATA_U16)
rx_irq = 1;
else
EX1 = 1;
}
health_phase(HEALTH_PH_RX);
if (cmd_available) {
cmd_available = 0;
cmd_tokenize();
@@ -1257,6 +1327,7 @@ void idle(void)
cmd_parser();
print_cmd_prompt();
}
health_phase(HEALTH_PH_CMD);
}
@@ -1282,14 +1353,16 @@ void reset_chip(void)
void setup_external_irqs(void)
{
REG_SET(0x5f84, 0x42);
REG_SET(0x5f34, 0x3ff);
REG_SET(RTL837X_ISR_SW_INT_MODE, 0x42);
REG_SET(RTL837X_ISR_INT_PORT_LINK_CHG, 0x3ff);
REG_SET(RTL837X_IMR_INT_PORT_LINK_STS_CHG, 0x3ff);
// EX0 = 1; // Enable external IRQ 0 (Link-change)
EX0 = 0;
IT0 = 1; // External IRQ on falling edge
link_irq = 1;
IT0 = 1; // External IRQ 0 on falling edge (link change)
EX0 = 1;
EX1 = 1; // External IRQ 1 enable
rx_irq = 1;
EX1 = 1; // External IRQ 1: the NIC has received a packet
EX2 = 1; // External IRQ 2 enable: bit EIE.0
EX3 = 1; // External IRQ 3 enable: bit EIE.1
PX3 = 1; // Set EIP.1 = 1: External IRQ 3 set to high priority
@@ -1570,7 +1643,8 @@ void check_and_flash_update_image(void)
void main(void)
{
ticks = 0;
stp_clock = STP_TICK_DIVIDER;
stp_tick_last = (uint8_t)ticks;
sfp_tick_last = (uint8_t)ticks;
dhcp_state.state = DHCP_OFF;
sbuf_ptr = 0;
@@ -1674,14 +1748,14 @@ void main(void)
REG_SET(RTL837X_PIN_MUX_2, 0x0); // Disable pins for ACL
init_smi();
if (machine_detected.isRTL8373)
rtl8224_wait_ready();
rtl8373_revision();
leds_setup();
machine_custom_init();
leds_dump();
set_sys_led_state(SYS_LED_SLOW);
if (machine_detected.isRTL8373)
@@ -1714,6 +1788,7 @@ void main(void)
stp_enabled = 0;
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
nic_setup();
REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE);
vlan_setup();
port_l2_setup();
igmp_setup();
@@ -1721,6 +1796,7 @@ void main(void)
uip_init();
uip_arp_init();
httpd_init();
lldp_init();
management_vlan = 1; // Default management VLAN is 1
@@ -1751,6 +1827,7 @@ void main(void)
cmd_editor_init();
health_stack_paint();
while (1) {
cmd_edit();
idle(); // Enter Idle mode until interrupt occurs
+410
View File
@@ -0,0 +1,410 @@
; Assembler file to intercept register writes/reads and PHY writes/reads
; assemble with as31
; Compile with
; as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
; ./injector zoerax_hg0402xg.bin RTLPlayground/rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
; Start at 0x3b00, where there is always some space in BANK 0
.equ valptr, 0xc7
.equ SFR_DATA_0, 0xa7
.equ SFR_DATA_8, 0xa6
.equ SFR_DATA_16, 0xa5
.equ SFR_DATA_24, 0xa4
; Intercept Register Write
.org 0x4d62
lcall reg_write
nop
; Intercept Register Read
.org 0x4dad
lcall reg_read_head
.org 0x4e6f
lcall reg_read_tail
; Intercept PHY write
.org 0x522c
lcall phy_write
; Intercept PHY read
.org 0x526f
lcall phy_read_head
.org 0x5295
ljmp phy_read_tail
; Intercept SDS write
.org 0x503c
lcall sds_write
nop
; Intercept SDS read
.org 0x5061
lcall sds_read_head
nop
.org 0x507b
ljmp sds_read_tail
;
; BEGIN actual code at unused end of BANK0
;
.org 0x3b00
;
; print the address of a register write. The address is in R6/R7. A and r4 can be scratched
;
reg_write:
mov a, #'R'
acall putc
mov a, r6
acall wbyte
mov a, r7
acall wbyte
mov a, #'-'
acall putc
mov dptr, #valptr
movx a, @dptr
acall wbyte
mov dptr, #valptr + 1
movx a, @dptr
acall wbyte
mov dptr, #valptr + 2
movx a, @dptr
acall wbyte
mov dptr, #valptr + 3
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov a, r7
mov r5, a
mov a, r6
mov r4, a
ret
;
; print the address of a register read The address is in R2/R3. Registers 4-7 are scratch
;
reg_read_head:
mov a, #'r'
acall putc
mov a, r2
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
clr a
mov r7, a
mov r6, a
mov r5, a
mov r4, a
ret
reg_read_tail:
mov dptr, #0xc5
movx a, @dptr
acall wbyte
mov dptr, #0xc5 + 1
movx a, @dptr
acall wbyte
mov dptr, #0xc5 + 2
movx a, @dptr
acall wbyte
mov dptr, #0xc5 + 3
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov dptr,#0xc5
ret
;
; print phy write options
;
phy_write:
mov a, #'P'
acall putc
mov a, r4
acall wbyte
; phymask
mov a, r6
acall wbyte
mov a, r7
acall wbyte
mov a, #'.'
acall putc
; dev_id?
mov a, r5
acall wbyte
; x_phywrite_opt
mov dptr, #0xf1
movx a, @dptr
acall wbyte
; The register going to SFR_C2 and SFR_C3
mov a, r2
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
mov dptr, #0xf2
movx a, @dptr
acall wbyte
mov dptr, #0xf3
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov dptr, #0xeb
mov r4, #0
ret
;
; print phy read options
;
phy_read_head:
mov r1, 4 ; actually r4
mov a, #'p'
acall putc
mov a, r7
acall wbyte
mov a, r5
acall wbyte
mov a, #'.'
acall putc
mov a, r2
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
mov r1, 7
mov a, r2
ret
phy_read_tail:
mov r6, 4
mov a, r6
acall wbyte
mov a, r7
acall wbyte
mov a, #' '
acall putc
mov r4, 6
ret
;
; print SDS write options
;
sds_write:
mov a, r4
mov r2, a ; R2 is scratch
mov a, #'Q'
acall putc
mov a, r7
acall wbyte
mov a, r5
acall wbyte
mov a, r3
acall wbyte
mov a, #':'
acall putc
mov dptr, #0xe1
movx a, @dptr
acall wbyte
mov dptr, #0xe2
movx a, @dptr
acall wbyte
mov a, #' '
acall putc
mov a, r7
mov r1, a
mov a, r5
mov r7, a
mov a, r2
mov r4, a
ret
;
; print SDS read options
;
sds_read_head:
mov a, r4
mov r1, a ; R1 is scratch
mov a, #'q'
acall putc
mov a, r7 ; sds_index bit
acall wbyte
mov a, r5 ; SFR_94 (2 x r5 | r7)
acall wbyte
mov a, r3 ; SFR_93
acall wbyte
mov a, #':'
acall putc
mov a, r1
mov r4, a ; R1 is scratch
mov a, r7
mov r1, a
mov a, r5
mov r7, a
ret
;
; print SDS read result
;
sds_read_tail:
mov a, r4
mov r6, a ; R6 is returned and preserved
mov a, r7 ; = r7 = SFR_DATA_8
acall wbyte
mov r7, SFR_DATA_0
mov a, r7 ; SFR_DATA_0
acall wbyte
mov a, #' '
acall putc
mov a, r6 ; R6 is returned and preserved
mov r4, a
ret
;
; Write a byte as hex to serial
;
nop
nop
wbyte:
mov r4, a
swap a
anl a, #0x0f
mov dptr,#hex
movc a, @a+dptr
jb ET0, wbyte1
ret
wbyte1:
jbc TI, wbyte2
sjmp wbyte1
wbyte2:
mov SBUF,a
mov a, r4
anl a, #0x0f
mov dptr,#hex
movc a, @a+dptr
wbyte3:
jbc TI, wbyte4
sjmp wbyte3
wbyte4:
mov SBUF,a
ret
;
; Intercept isr_ext0
;
isr_ext0:
mov a, #'i'
acall putc
mov r0,#0x51
inc @r0
ret
;
; Intercept isr_ext1
;
isr_ext1:
mov a, #'I'
acall putc
mov r0,#0x25
mov a, @r0
ret
;
; Intercept isr_ext2
;
isr_ext2:
mov a, #'J'
acall putc
mov r0, #0x1e
mov a, #0xa
ret
;
; Intercept isr_ext3
;
isr_ext3:
mov a, #'j'
acall putc
mov r0,#0x1e
mov a,#0x14
ret
;
; Write a character to serial
;
nop
nop
putc:
jb ET0, putc1
ret
putc1:
jbc TI,putc2
sjmp putc1
putc2:
mov SBUF,a
ret
hex:
.byte "0123456789abcdef"
.end
+20 -6
View File
@@ -20,6 +20,8 @@ extern __code const struct machine machine;
// SFP1 b0 = 1 => module missing, b1 = 1 => LOS;
// SFP2 b4 = 1 => module missing, b5 = 1 => LOS;
extern volatile __xdata uint32_t ticks;
__xdata uint8_t sfp_pins_last;
__xdata char sfp_module_vendor[2][17];
__xdata char sfp_module_model[2][17];
@@ -28,6 +30,8 @@ __xdata uint8_t sfp_options[2];
__xdata uint8_t sfp_buf[16]; /* scratch for one I2C transaction, the controller reads at most 16 bytes */
__xdata uint8_t sfp_speed[2];
__xdata uint8_t sfp_quirks[2];
__xdata uint8_t sfp_wake_at[2];
__xdata uint8_t sfp_wake_pending[2];
__code enum sfp_quirk {
@@ -35,12 +39,12 @@ __code enum sfp_quirk {
};
struct sfp_quirk_entry {
__code char *vendor; // Set vendor or model to 0 to act as wildcard
__code char *model;
__code const char *vendor; // Set vendor or model to 0 to act as wildcard
__code const char *model;
uint8_t quirks;
};
static __code struct sfp_quirk_entry sfp_quirk_table[] = {
static __code const struct sfp_quirk_entry sfp_quirk_table[] = {
{ "QSFPTEK", "QT-SFP+-T", SFP_QUIRK_DDM },
};
@@ -88,9 +92,14 @@ bool sfp_read_field(__xdata char *dst, uint8_t sfp, uint8_t start, uint8_t lengt
return false;
dst[length] = NUL;
memcpy(dst, sfp_buf, length);
for (uint8_t i = 0; i < length; i++) {
uint8_t c = sfp_buf[i];
if (c && (c < 0x20 || c > 0x7e || c == '"' || c == '\\'))
c = '.';
dst[i] = c;
}
while (length > 0 && dst[--length] == ' ')
while (length-- > 0 && (dst[length] == ' ' || dst[length] == NUL))
dst[length] = NUL;
return true;
@@ -152,7 +161,6 @@ static bool sfp_module_read(uint8_t sfp)
// Read Reg 11: Encoding, see SFF-8472 and SFF-8024
// Read Reg 12: Signalling rate (including overhead) in 100Mbit: 0xd: 1Gbit, 0x67:10Gbit
delay(100); // Delay, because some modules need time to wake up
if (!sfp_read_block(sfp, 11, 2))
return false;
@@ -194,6 +202,11 @@ void handle_sfp(void) __banked
if (sfp_pins_last & (0x1 << (sfp << 2))) {
sfp_pins_last &= ~(0x01 << (sfp << 2));
print_string("\n<MODULE INSERTED> Slot: "); write_char('1' + sfp);
sfp_wake_at[sfp] = ticks;
sfp_wake_pending[sfp] = 1;
} else if (sfp_wake_pending[sfp]
&& (uint8_t)((uint8_t)ticks - sfp_wake_at[sfp]) >= SFP_WAKE_TICKS) {
sfp_wake_pending[sfp] = 0;
if (!sfp_module_read(sfp)) {
print_string("SFP: an I2C read failed, retrying on the next poll\n");
sfp_pins_last |= 0x01 << (sfp << 2);
@@ -202,6 +215,7 @@ void handle_sfp(void) __banked
} else {
if (!(sfp_pins_last & (0x1 << (sfp << 2)))) {
sfp_pins_last |= 0x01 << (sfp << 2);
sfp_wake_pending[sfp] = 0;
print_string("\n<MODULE REMOVED> Slot: "); write_char('1' + sfp); write_char('\n');
}
}
+2
View File
@@ -1,6 +1,8 @@
#ifndef SFP_H
#define SFP_H
#define SFP_WAKE_TICKS 100
extern __xdata uint8_t sfp_pins_last;
extern __xdata char sfp_module_vendor[2][17];
extern __xdata char sfp_module_model[2][17];
+1 -1
View File
@@ -60,7 +60,7 @@ void syslog_stop(void) __banked
void syslog_callback(uint16_t lport) __banked
{
uint16_t syslog_hdr;
if (lport != state.syslog_conn->lport)
if (!state.syslog_conn || lport != state.syslog_conn->lport)
return;
if ((state.readptr != state.writeptr) && state.line_available)
+73
View File
@@ -0,0 +1,73 @@
# Host unit-test harness for RTLPlayground firmware logic.
#
# Compiles selected firmware translation units with host gcc + AddressSanitizer,
# force-including sdcc_shim.h to erase 8051 keywords, and links them against
# mocked hardware edges (support.c). No SDCC, no flashing, no hardware.
#
# make build + run all tests
# make run run tests
# make clean
CC = gcc
SRCROOT = ..
# -include shim first so __xdata/__code/__banked vanish before any firmware header.
CFLAGS = -std=gnu11 -g -O1 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-pragmas \
-Wno-pointer-sign \
-DRTLP_HOST_TEST \
-fsanitize=address,undefined -fno-omit-frame-pointer \
-include sdcc_shim.h -Istub -I. -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip
ifeq ($(findstring clang,$(shell $(CC) --version 2>/dev/null)),)
CFLAGS += -Wno-builtin-declaration-mismatch
endif
LDFLAGS = -fsanitize=address,undefined
BUILDDIR = build
# One binary per subsystem under test.
TESTS = $(BUILDDIR)/test_cmd_editor \
$(BUILDDIR)/test_port_tables \
$(BUILDDIR)/test_page_json \
$(BUILDDIR)/test_httpd_tx \
$(BUILDDIR)/test_tick_gate
# The table tests link the register mock and one environment file; stub/
# stands in for the two headers a firmware build generates.
TABLE_ENV = support.c hw_mock.c env_tables.c $(SRCROOT)/cmd_editor.c \
$(SRCROOT)/rtl837x_port.c $(SRCROOT)/httpd/page_impl.c
all: run
$(BUILDDIR):
mkdir -p $(BUILDDIR)
# cmd_editor.c is compiled UNMODIFIED from the firmware tree.
$(BUILDDIR)/test_cmd_editor: test_cmd_editor.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
# rtl837x_port.c and httpd/page_impl.c are compiled UNMODIFIED from the firmware tree.
$(BUILDDIR)/test_port_tables: test_port_tables.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR)
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
$(BUILDDIR)/test_tick_gate: test_tick_gate.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
$(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR)
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
# httpd/httpd.c and uip/uip.c are compiled UNMODIFIED from the firmware tree;
# this test carries the environment below them itself.
$(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR)
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
run: $(TESTS)
@fail=0; for t in $(TESTS); do \
echo "=== $$t ==="; \
$$t || fail=1; \
echo; \
done; \
if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi
clean:
rm -rf $(BUILDDIR)
.PHONY: all run clean
+62
View File
@@ -0,0 +1,62 @@
# Host unit-test harness
Compile and test individual firmware translation units on the build host with
**gcc + AddressSanitizer + UBSan** — no SDCC, no flashing, no hardware. This is
the fast inner loop for the logic-level bugs in `../../NOTES/08-findings.md`
(parser, line editor, HTTP header, DHCP): edit → `make` → see red/green in
seconds instead of a minutes-long flash-and-reboot cycle.
## Run
```
cd test
make # build + run all tests (ASan/UBSan on)
make clean
```
Exit status is non-zero if any check fails, a sanitizer trips, or the watchdog
fires — so it drops straight into CI.
## How it works
- **`sdcc_shim.h`** (force-included) erases SDCC 8051 keywords (`__xdata`,
`__code`, `__banked`, …) so firmware sources compile under host gcc. It changes
no logic — the code under test is byte-for-byte the firmware source.
- **`hw_mock.c` / `hw_mock.h`** simulate the ASIC edge: a flat register file
behind `reg_read`/`reg_write` and the `SFR_DATA_*` registers, with a table
engine that performs a VLAN or L2 operation the moment `TBL_CTRL` is written,
fetches a MIB counter on `STAT_GET` and drops entries on `L2_TBL_FLUSH_CTRL`.
Nothing ever reports busy, so a polling loop runs once. The header states the
VLAN and L2 entry layouts independently of the firmware, so a test can hold
what `rtl837x_port.c` writes against what `page_impl.c` reads.
- **`env_tables.c`** carries the globals and leaf calls those two modules link
against; **`stub/`** stands in for the two headers only a firmware build
generates, so the harness needs no SDCC build first.
- **`support.c` / `support.h`** mock the hardware edges: the 16-byte serial ring
(`sbuf`), the command/history buffers, and the character-output sink
(`write_char` etc.). Buffers are sized **exactly** as on target, so ASan
redzones catch the same off-by-one overflows the 8051 hits.
- Builds define **`RTLP_HOST_TEST`**, which hides the firmware's libc-named
prototypes (`memset`/`strlen`/…) in `rtl837x_common.h` so they don't clash with
glibc. Argument order matches libc, so on-host callers transparently use the C
library. This guard is compiled out of normal firmware builds — zero on-target
effect.
- A **SIGALRM watchdog** (`watchdog_arm`) turns an infinite-loop bug into a fast
test failure instead of a hung runner.
## Current coverage
| Test binary | TU under test | Findings exercised |
|-------------|---------------|--------------------|
| `test_cmd_editor` | `cmd_editor.c` | **C4** — full-line hang + `cmd_buffer` 1-byte overflow; basic entry & backspace regressions |
| `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed |
| `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` |
| `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission |
## Adding a test for another module
1. Write `test_<module>.c` with `main()` driving the module's entry points and
`CHECK(cond, msg)` assertions.
2. Add any missing mocked symbols to `support.c` (only what the linker asks for).
3. Add a target in the `Makefile` listing `test_<module>.c support.c ../<module>.c`.
4. If the module calls a firmware libc-name (`memcpy`/`strcpy`/…), it will use the
C library on host (compatible arg order); provide a `fw_*`-style mock only if
the semantics differ in a way the test cares about.
Good next targets (pure logic, high finding density): `cmd_parser.c` (C6/C8),
the HTTP header parser in `httpd/httpd.c` (S1), `dhcp.c` (S5).
+126
View File
@@ -0,0 +1,126 @@
/*
* env_tables.c - what rtl837x_port.c and httpd/page_impl.c link against
* besides the register mock: the console, the buffers page_impl writes into,
* the machine description and leaf calls into subsystems not under test.
* Buffers keep their firmware sizes so AddressSanitizer sees the same bounds
* the 8051 has.
*/
#include <stdint.h>
#include <stdio.h>
#include "rtl837x_common.h"
#include "rtl837x_port.h"
#include "machine.h"
#include "uip.h"
#include "rtl837x_flash.h"
#include "syslog.h"
#include "rtl837x_phy.h"
#include "support.h"
/* ---- console: everything lands in out_buf (support.c) ---- */
void print_byte(uint8_t v)
{
static const char h[] = "0123456789abcdef";
write_char(h[(v >> 4) & 0xf]);
write_char(h[v & 0xf]);
}
void print_short(uint16_t v) { print_byte(v >> 8); print_byte(v); }
void print_long(uint32_t v) { print_short(v >> 16); print_short(v); }
void print_reg(uint16_t v) { print_short(v); }
void itoa_short(uint16_t v)
{
char b[6]; int n = 0;
do { b[n++] = '0' + v % 10; v /= 10; } while (v);
while (n) write_char(b[--n]);
}
void dbg_string(char *p) { (void)p; }
void dbg_short(uint16_t v) { (void)v; }
void dbg_char(char c) { (void)c; }
void dbg_byte(uint8_t v) { (void)v; }
/* ---- copied verbatim from rtlplayground.c ---- */
uint16_t strtox(uint8_t *dst, const char *s)
{
uint8_t *b = dst;
while (*s)
*dst++ = *s++;
*dst = 0;
return dst - b;
}
const uint16_t bit_mask[16] = {
0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000
};
const uint8_t hex_chars[] = "0123456789abcdef";
uint8_t *hex = (uint8_t *)hex_chars;
/* ---- a 9-port RTL8373 whose logical ports map to themselves ---- */
const struct machine machine = {
.machine_name = "HOSTTEST",
.min_port = 0,
.max_port = 8,
.n_sfp = 0,
.log_to_phys_port = { 1, 2, 3, 4, 5, 6, 7, 8, 9 },
.phys_to_log_port = { 0, 1, 2, 3, 4, 5, 6, 7, 8 },
};
struct machine_runtime machine_detected = { .isRTL8373 = 1 };
/* ---- firmware state the modules read or write ---- */
uint8_t outbuf[TCP_OUTBUF_SIZE];
uint16_t slen;
uint16_t management_vlan = 1;
uint16_t cont_len;
uint32_t cont_addr;
uint8_t vlan_names[VLAN_NAMES_SIZE];
uint16_t vlan_ptr;
uint8_t sfp_pins_last = 0xff;
uint8_t sfp_options[2];
char sfp_module_vendor[2][17];
char sfp_module_model[2][17];
char sfp_module_serial[2][17];
char hostname[24] = "hosttest";
char port_names[9][PORT_NAME_SIZE];
struct flash_region_t flash_region;
struct syslog_state syslog_state;
bool stp_enabled;
bool lldp_enabled;
uint16_t lldp_logical_port_status;
uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask;
struct uip_eth_addr uip_ethaddr = { .addr = { 0x02, 0x11, 0x22, 0x33, 0x44, 0x55 } };
/* ---- leaf calls into subsystems not under test ---- */
void flash_read_bulk(uint8_t *dst) { (void)dst; }
const char *get_flash_size_str(void) { return "2M"; }
uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) { (void)slot; (void)reg; return 0; }
bool gpio_pin_test(uint8_t pin) { (void)pin; return false; }
void phy_read(uint8_t phy_id, uint8_t dev_id, uint16_t reg) { (void)phy_id; (void)dev_id; (void)reg; }
void phy_write(uint8_t phy_id, uint8_t dev_id, uint16_t reg, uint16_t val) { (void)phy_id; (void)dev_id; (void)reg; (void)val; }
void phy_reset(uint8_t port) { (void)port; }
uint8_t stp_port_role(uint8_t port) { (void)port; return 0; }
uint8_t stp_port_state(uint8_t port) { (void)port; return 3; }
/* ---- STP state send_stp() prints; the tree is not under test here ---- */
#include "rtl837x_stp.h"
struct bridge root_bridge;
struct bridge stp_dbridge[STP_ENTITIES];
uint16_t stp_bpdu_age[STP_ENTITIES];
uint16_t stp_dpid[STP_ENTITIES];
uint16_t stp_lag_mask[STP_LAG_COUNT];
uint16_t stp_tc_count;
uint32_t root_bridge_cost;
uint32_t stp_dcost[STP_ENTITIES];
uint32_t stp_pcost[STP_ENTITIES];
uint8_t stp_ent_of[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
uint8_t stp_fwddelay_s = 15, stp_hello_s = 2, stp_maxage_s = 20;
uint8_t stp_pflags[STP_ENTITIES];
uint8_t stp_pp2p[STP_ENTITIES];
uint8_t stp_pprio[STP_ENTITIES];
uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6;
/* ---- SFP: no module present, reads fail ---- */
uint8_t sfp_buf[16];
bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) { (void)slot; (void)reg; (void)len; return false; }
void print_phys_port(uint8_t port) { itoa_short(machine.log_to_phys_port[port]); }
+342
View File
@@ -0,0 +1,342 @@
/*
* hw_mock.c - the ASIC edge: SFR storage, register file, table engine.
*
* The shim declares every SFR extern so that including rtl837x_sfr.h from many
* translation units does not define them repeatedly. Undefining the four
* keywords here and re-including the header emits the definitions once, so
* the mock tracks the real register list without a hand-kept copy.
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#undef __sfr
#undef __sfr16
#undef __sfr32
#undef __sbit
#define __sfr volatile uint8_t
#define __sfr16 volatile uint16_t
#define __sfr32 volatile uint32_t
#define __sbit bool
#include "rtl837x_sfr.h"
#undef __sfr
#undef __sfr16
#undef __sfr32
#undef __sbit
#define __sfr extern volatile uint8_t
#define __sfr16 extern volatile uint16_t
#define __sfr32 extern volatile uint32_t
#define __sbit extern bool
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "hw_mock.h"
uint8_t sfr_data[4];
unsigned long hw_reads, hw_writes;
static uint32_t regfile[0x10000];
static uint32_t vlan_tbl[4096];
static struct hw_l2 l2[HW_L2_MAX];
static uint64_t counters[16][64];
void hw_reset(void)
{
memset(regfile, 0, sizeof(regfile));
memset(vlan_tbl, 0, sizeof(vlan_tbl));
memset(l2, 0, sizeof(l2));
memset(counters, 0, sizeof(counters));
memset(sfr_data, 0, sizeof(sfr_data));
hw_reads = hw_writes = 0;
}
void hw_reg_set(uint16_t addr, uint32_t v) { regfile[addr] = v; }
uint32_t hw_reg_get(uint16_t addr) { return regfile[addr]; }
uint32_t hw_vlan_word(uint16_t vid) { return vlan_tbl[vid & 0xfff]; }
void hw_counter_set(uint8_t port, uint8_t counter, uint64_t value) { counters[port & 0xf][counter & 0x3f] = value; }
/* ---- L2 table -------------------------------------------------------- */
static int l2_cmp(const void *x, const void *y)
{
const struct hw_l2 *a = x, *b = y;
if (a->present != b->present)
return a->present ? -1 : 1;
return (int)a->idx - (int)b->idx;
}
static void l2_sort(void) { qsort(l2, HW_L2_MAX, sizeof(l2[0]), l2_cmp); }
int hw_l2_count(void)
{
int n = 0;
for (int i = 0; i < HW_L2_MAX; i++)
n += l2[i].present;
return n;
}
const struct hw_l2 *hw_l2_at(int n)
{
l2_sort();
return (n >= 0 && n < hw_l2_count()) ? &l2[n] : 0;
}
static void l2_key(const struct hw_l2 *e, uint8_t mac[6], uint16_t *vid)
{
mac[0] = e->b >> 8; mac[1] = e->b;
mac[2] = e->a >> 24; mac[3] = e->a >> 16; mac[4] = e->a >> 8; mac[5] = e->a;
*vid = ((e->b >> 16) & 0xfff);
}
const struct hw_l2 *hw_l2_find(const uint8_t mac[6], uint16_t vid)
{
for (int i = 0; i < HW_L2_MAX; i++) {
uint8_t m[6]; uint16_t v;
if (!l2[i].present)
continue;
l2_key(&l2[i], m, &v);
if (v == vid && !memcmp(m, mac, 6))
return &l2[i];
}
return 0;
}
static struct hw_l2 *l2_free(void)
{
for (int i = 0; i < HW_L2_MAX; i++)
if (!l2[i].present)
return &l2[i];
return 0;
}
static uint16_t l2_hash(const uint8_t mac[6], uint16_t vid)
{
uint16_t h = vid;
for (int i = 0; i < 6; i++)
h = (uint16_t)(h * 31 + mac[i]);
return h & 0xfff;
}
void hw_l2_put(uint16_t idx, const uint8_t mac[6], uint16_t vid, uint16_t port_or_pmask, bool is_static)
{
struct hw_l2 *e = (struct hw_l2 *)hw_l2_find(mac, vid);
bool mc = mac[0] & 1;
if (!e)
e = l2_free();
e->present = true;
e->idx = idx & 0xfff;
e->a = ((uint32_t)mac[2] << 24) | ((uint32_t)mac[3] << 16) | ((uint32_t)mac[4] << 8) | mac[5];
e->b = (0x20u << 24) | ((uint32_t)(vid & 0xfff) << 16) | ((uint32_t)mac[0] << 8) | mac[1];
if (mc) {
e->b |= (uint32_t)(port_or_pmask & 0x3) << 30;
e->c = (port_or_pmask >> 2) & 0xff;
} else {
e->b |= (uint32_t)(port_or_pmask & 0x3) << 30;
e->c = (port_or_pmask >> 2) & 0x3;
}
if (is_static)
e->c |= 1u << 16;
}
void hw_l2_decode(const struct hw_l2 *e, uint8_t mac[6], uint16_t *vid, uint16_t *port_or_pmask,
bool *valid, bool *is_static, bool *mc)
{
l2_key(e, mac, vid);
*valid = (e->b >> 29) & 1;
*is_static = (e->c >> 16) & 1;
*mc = mac[0] & 1;
if (*mc)
*port_or_pmask = ((e->b >> 30) & 0x3) | ((e->c & 0xff) << 2);
else
*port_or_pmask = ((e->b >> 30) & 0x3) | ((e->c & 0x3) << 2);
}
/* ---- the engine ------------------------------------------------------ */
static void l2_out(const struct hw_l2 *e, uint16_t idx)
{
regfile[RTL837x_L2_DATA_OUT_A] = e ? e->a : 0;
regfile[RTL837x_L2_DATA_OUT_B] = e ? e->b : 0;
regfile[RTL837x_L2_DATA_OUT_C] = e ? e->c : 0;
regfile[RTL837x_TBL_DATA_0] = (regfile[RTL837x_TBL_DATA_0] & ~0x1fffu) | (e ? 0x1000 : 0) | (idx & 0xfff);
}
static void table_exec(uint32_t ctrl)
{
uint16_t idx = ((ctrl >> 16) & 0xfff);
uint8_t type = ctrl >> 8;
bool write = ctrl & TBL_WRITE;
if (type == TBL_VLAN) {
if (write)
vlan_tbl[idx] = regfile[RTL837x_TBL_DATA_IN_A];
else
regfile[RTL837x_L2_DATA_OUT_A] = vlan_tbl[idx];
return;
}
if (type != TBL_L2_UNICAST)
return;
uint32_t d0 = regfile[RTL837x_TBL_DATA_0];
uint8_t method = (d0 >> 14) & 0x3;
bool clear = (d0 >> 18) & 1;
if (method == TBL_LUTREAD_MAC) {
struct hw_l2 key = { .present = true, .a = regfile[RTL837x_TBL_DATA_IN_A],
.b = regfile[RTL837x_TBL_DATA_IN_B], .c = regfile[RTL837x_TBL_DATA_IN_C] };
uint8_t mac[6]; uint16_t vid;
l2_key(&key, mac, &vid);
struct hw_l2 *e = (struct hw_l2 *)hw_l2_find(mac, vid);
if (write) {
if (clear) {
if (e)
e->present = false;
l2_out(0, 0);
return;
}
if (!e) {
e = l2_free();
e->idx = l2_hash(mac, vid);
}
e->present = true;
e->a = key.a; e->b = key.b; e->c = key.c;
l2_out(e, e->idx);
return;
}
l2_out(e, e ? e->idx : 0);
return;
}
/* next-entry reads: the first present entry at or after idx, wrapping */
l2_sort();
int n = hw_l2_count();
struct hw_l2 *e = 0;
for (int i = 0; i < n; i++)
if (l2[i].idx >= idx) { e = &l2[i]; break; }
if (!e && n)
e = &l2[0];
if (write) {
/* a write by index replaces the entry that sits there */
for (int i = 0; i < n; i++)
if (l2[i].idx == idx) { e = &l2[i]; break; }
if (!e)
e = l2_free();
e->present = (regfile[RTL837x_TBL_DATA_IN_B] >> 29) & 1;
e->idx = idx;
e->a = regfile[RTL837x_TBL_DATA_IN_A];
e->b = regfile[RTL837x_TBL_DATA_IN_B];
e->c = regfile[RTL837x_TBL_DATA_IN_C];
return;
}
l2_out(e, e ? e->idx : idx);
}
static void flush_exec(uint32_t ctrl)
{
uint16_t mask = ctrl & 0x3ff;
bool dynamic_only = regfile[RTL837x_L2_TBL_FLUSH_CNF] == 0;
for (int i = 0; i < HW_L2_MAX; i++) {
uint8_t mac[6]; uint16_t vid, p; bool valid, st, mc;
if (!l2[i].present)
continue;
hw_l2_decode(&l2[i], mac, &vid, &p, &valid, &st, &mc);
if (mc || (st && dynamic_only))
continue;
if ((mask >> p) & 1)
l2[i].present = false;
}
}
static void stat_exec(uint32_t v)
{
uint8_t cnt = (uint8_t)(((v >> 8) & 0xff) << 3) | ((v >> 5) & 0x7);
uint8_t port = (v >> 1) & 0xf;
uint64_t c = counters[port][cnt & 0x3f];
regfile[RTL837X_STAT_V_HIGH] = (uint32_t)(c >> 32);
regfile[RTL837X_STAT_V_LOW] = (uint32_t)c;
}
static void reg_store(uint16_t addr, uint32_t v)
{
hw_writes++;
regfile[addr] = v;
switch (addr) {
case RTL837X_TBL_CTRL:
if (v & TBL_EXECUTE) {
table_exec(v);
regfile[addr] = v & ~(uint32_t)TBL_EXECUTE;
}
break;
case RTL837x_L2_TBL_FLUSH_CTRL:
if (v & L2_TBL_FLUSH_EXEC) {
flush_exec(v);
regfile[addr] = v & ~(uint32_t)L2_TBL_FLUSH_EXEC;
}
break;
case RTL837X_STAT_GET:
if (v & 1) {
stat_exec(v);
regfile[addr] = v & ~1u;
}
break;
}
}
static uint32_t sfr_word(void)
{
return ((uint32_t)SFR_DATA_24 << 24) | ((uint32_t)SFR_DATA_16 << 16) | ((uint32_t)SFR_DATA_8 << 8) | SFR_DATA_0;
}
static void sfr_load(uint32_t v)
{
SFR_DATA_24 = v >> 24; SFR_DATA_16 = v >> 16; SFR_DATA_8 = v >> 8; SFR_DATA_0 = v;
}
void reg_read(uint16_t addr)
{
hw_reads++;
sfr_load(regfile[addr]);
}
void reg_read_m(uint16_t addr)
{
reg_read(addr);
sfr_data[0] = SFR_DATA_24; sfr_data[1] = SFR_DATA_16; sfr_data[2] = SFR_DATA_8; sfr_data[3] = SFR_DATA_0;
}
void reg_write(uint16_t addr)
{
reg_store(addr, sfr_word());
}
void reg_write_m(uint16_t addr)
{
sfr_load(((uint32_t)sfr_data[0] << 24) | ((uint32_t)sfr_data[1] << 16) | ((uint32_t)sfr_data[2] << 8) | sfr_data[3]);
reg_write(addr);
}
void reg_bit_set(uint16_t addr, char bit)
{
reg_read_m(addr);
sfr_data[3 - (bit >> 3)] |= 1 << (bit & 7);
reg_write_m(addr);
}
void reg_bit_clear(uint16_t addr, char bit)
{
reg_read_m(addr);
sfr_data[3 - (bit >> 3)] &= ~(1 << (bit & 7));
reg_write_m(addr);
}
uint8_t reg_bit_test(uint16_t addr, char bit)
{
reg_read_m(addr);
return (sfr_data[3 - (bit >> 3)] >> (bit & 7)) & 1;
}
void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set)
{
sfr_data[3 - n] = (sfr_data[3 - n] & ~mask) | set;
}
+59
View File
@@ -0,0 +1,59 @@
/*
* hw_mock.h - a simulated RTL837x register file and table engine.
*
* The firmware reaches the ASIC only through reg_read/reg_read_m/reg_write/
* reg_write_m and the SFR_DATA_* registers, so mocking that edge is enough to
* run the table code on the host. Every register is a 32-bit word in a flat
* file; a few addresses are live: writing TBL_CTRL with EXECUTE performs the
* table operation at once and clears the bit, STAT_GET fetches a counter,
* L2_TBL_FLUSH_CTRL drops entries. Nothing here ever reports busy, so a
* polling loop in the firmware runs exactly once.
*
* The VLAN and L2 entry layouts mirror what rtl837x_port.c writes and what
* httpd/page_impl.c reads; the decoders below are an independent statement
* of that layout, so a test can compare the firmware's two ends against it.
*/
#ifndef TEST_HW_MOCK_H
#define TEST_HW_MOCK_H
#include <stdint.h>
#include <stdbool.h>
void hw_reset(void); /* every register 0, tables empty */
void hw_reg_set(uint16_t addr, uint32_t v);
uint32_t hw_reg_get(uint16_t addr);
extern uint8_t sfr_data[4]; /* the firmware's register scratch, owned here */
extern unsigned long hw_reads; /* reg_read + reg_read_m calls since hw_reset */
extern unsigned long hw_writes; /* reg_write + reg_write_m calls since hw_reset */
/* VLAN table: the 32-bit word the firmware wrote for that VID, 0 = never written */
uint32_t hw_vlan_word(uint16_t vid);
/* L2 table: entries as the three data words the firmware exchanges with the
* engine. Word B: bit 29 valid, bits 31:30 the low two bits of the port (or
* of the member mask), bits 27:16 VID, bits 15:0 the first two MAC octets.
* Word A: the last four MAC octets. Word C: bit 16 static, bits 1:0 the high
* two bits of the port; for a multicast MAC bits 7:0 are the member mask
* shifted right by two. */
#define HW_L2_MAX 64
struct hw_l2 {
bool present;
uint16_t idx;
uint32_t a, b, c; /* DATA_IN_A/B/C on write, L2_DATA_OUT_A/B/C on read */
};
/* Pointers returned below stay valid until the next table operation. */
int hw_l2_count(void);
const struct hw_l2 *hw_l2_at(int n); /* n-th present entry in idx order */
const struct hw_l2 *hw_l2_find(const uint8_t mac[6], uint16_t vid);
/* Put an entry the way the switch would hold it: a learned unicast entry on
* one port, or a static one. mc entries use pmask instead of a port. */
void hw_l2_put(uint16_t idx, const uint8_t mac[6], uint16_t vid, uint16_t port_or_pmask, bool is_static);
void hw_l2_decode(const struct hw_l2 *e, uint8_t mac[6], uint16_t *vid, uint16_t *port_or_pmask,
bool *valid, bool *is_static, bool *mc);
/* MIB counter the next STAT_GET for (port, counter) returns */
void hw_counter_set(uint8_t port, uint8_t counter, uint64_t value);
#endif /* TEST_HW_MOCK_H */
+56
View File
@@ -0,0 +1,56 @@
/*
* sdcc_shim.h — neutralize SDCC 8051 keywords so firmware translation units
* compile under host gcc/clang for off-target unit testing.
*
* Force-included (gcc -include) ahead of every firmware source under test.
* It only erases storage/qualifier keywords and the banked-call attribute;
* it does NOT change any logic, so the code under test is byte-for-byte the
* firmware source. Hardware/register access is provided by mocks (support.c),
* never by this file.
*/
#ifndef SDCC_SHIM_H
#define SDCC_SHIM_H
#include <stdbool.h>
#include <stdint.h>
/* Memory-space qualifiers → nothing (host has one flat address space). */
#define __xdata
#define __code
#define __pdata
#define __idata
#define __data
/* bool, not unsigned char: SDCC's bit types normalize any assigned value
* to 0/1, which _Bool reproduces exactly. */
#define __bit bool
/* Special-function registers are declared in rtl837x_sfr.h as
* `__sfr __at(0xNN) NAME;`. With __at erased those become plain declarations,
* so they must carry extern or every translation unit including the header
* defines its own copy. __sbit is the bit-in-an-SFR form and only appears
* there; the local bit variable keyword is __bit above. test/hw_mock.c
* undefines these four and re-includes the header once to emit the storage. */
#define __sfr extern volatile uint8_t
#define __sfr16 extern volatile uint16_t
#define __sfr32 extern volatile uint32_t
#define __sbit extern bool
/* The firmware ships its own memcpy/memset/strlen/strcpy/sleep, and
* rtl837x_common.h hides those prototypes under RTLP_HOST_TEST so host builds
* take the C library versions. Something has to declare them, and firmware
* sources do call memset and memcpy; this header is force-included ahead of
* everything, so pulling <string.h> in here puts the libc versions in scope
* before any firmware header and nothing clashes. sleep() is not in
* <string.h> and is declared on its own rather than through <unistd.h>. */
#include <string.h>
extern unsigned int sleep(unsigned int seconds);
/* Function attributes → nothing (no banking / no 8051 calling conventions). */
#define __banked
#define __reentrant
#define __naked
#define __using(n)
#define __interrupt(n)
#define __at(addr)
#endif /* SDCC_SHIM_H */
+11
View File
@@ -0,0 +1,11 @@
/* Stand-in for the generated html_data.h: the table generators under test
* never touch the embedded file list, and the real header exists only after
* a firmware build, which the host harness must not depend on. */
#ifndef FDATA_DEFS_H
#define FDATA_DEFS_H
#include <stdint.h>
typedef enum mime_type_e { mime_HTML = 0, mime_SVG, mime_ICO, mime_PNG, mime_JS, mime_CSS, mime_TXT } mime_type_t;
struct f_data { __code const char *file; uint32_t start; uint16_t len; mime_type_t mime; uint8_t gzip; };
#define FDATA_START_login_html 0x00040000UL
#define FDATA_START_favicon_ico 0x00041000UL
#endif
+5
View File
@@ -0,0 +1,5 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION_SW "host-test"
#define BUILD_DATE "1970-01-01 00:00:00"
#endif
+104
View File
@@ -0,0 +1,104 @@
/*
* support.c — mock hardware edges + test environment for the host harness.
*
* Everything the firmware expects the platform/other-TUs to provide, but which
* has no business touching real hardware in a unit test, lives here: the serial
* ring, the command/history buffers, the character-output sink, and stubs for
* the print helpers. The buffers are sized EXACTLY as on target so that
* AddressSanitizer redzones catch the same off-by-one overflows the 8051 hits.
*/
#include "sdcc_shim.h"
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <signal.h>
/* Declared by hand instead of <unistd.h>, whose sleep() prototype clashes with
* the firmware's own sleep() declaration in rtl837x_common.h. */
extern unsigned int alarm(unsigned int);
extern void _exit(int);
#include "rtl837x_common.h" /* SBUF_SIZE, CMD_BUF_SIZE, CMD_HISTORY_SIZE, decls */
#include "support.h"
/* --- symbols the editor/parser reference (extern in the firmware headers) --- */
__xdata volatile uint8_t sbuf_ptr;
__xdata uint8_t sbuf[SBUF_SIZE];
uint8_t cmd_buffer[CMD_BUF_SIZE]; /* ASan-instrumented: OOB caught */
uint8_t cmd_available;
uint8_t err_status;
uint8_t cmd_history[CMD_HISTORY_SIZE];
uint16_t cmd_history_ptr;
__xdata uint16_t session_timeout = 200; /* httpd global, referenced by page_impl.c */
/* editor-internal read pointer (defined in cmd_editor.c, external linkage) */
extern __xdata uint8_t l;
/* the editor entry points under test */
void cmd_editor_init(void);
void cmd_edit(void);
/* ------------------------------- output sink ------------------------------- */
char out_buf[8192];
size_t out_len;
void out_reset(void) { out_len = 0; out_buf[0] = 0; }
static void out_putc(char c) {
if (out_len < sizeof(out_buf) - 1) { out_buf[out_len++] = c; out_buf[out_len] = 0; }
}
/* --- mocked print helpers (signatures match rtl837x_common.h post-shim) ---- */
void write_char(char c) { out_putc(c); }
void print_string(const char *p) { while (*p) out_putc(*p++); }
void print_string_x(char *p) { while (*p) out_putc(*p++); }
void print_cmd_prompt(void) { print_string((char *)"> "); }
void itoa(uint8_t v) { /* firmware prints a decimal number */
char t[4]; int n = 0;
if (!v) { out_putc('0'); return; }
while (v) { t[n++] = '0' + v % 10; v /= 10; }
while (n) out_putc(t[--n]);
}
/* ------------------------------ environment -------------------------------- */
void env_reset(void) {
sbuf_ptr = 0;
t_memset(sbuf, 0, sizeof(sbuf));
t_memset(cmd_buffer, 0, sizeof(cmd_buffer));
t_memset(cmd_history, 0, sizeof(cmd_history));
cmd_history_ptr = 0;
cmd_available = 0;
err_status = 0;
out_reset();
cmd_editor_init(); /* sets l = sbuf_ptr, clears cursor/len */
}
/* Push one byte into the ring, draining first if it is full (leave 1 slot). */
static void ring_push(uint8_t b) {
while (((sbuf_ptr + 1) & SBUF_MASK) == l)
cmd_edit(); /* drain; under the C4 bug this spins */
sbuf[sbuf_ptr] = b;
sbuf_ptr = (sbuf_ptr + 1) & SBUF_MASK;
}
void feed_n(const char *s, size_t n) {
for (size_t i = 0; i < n; i++) ring_push((uint8_t)s[i]);
cmd_edit();
}
void feed(const char *s) { feed_n(s, t_strlen(s)); }
/* -------------------------------- watchdog --------------------------------- */
static const char *wd_what;
static void wd_fire(int sig) {
(void)sig;
/* async-signal-unsafe write is acceptable: we abort immediately after. */
fprintf(stderr, "\n HANG DETECTED (watchdog): %s\n", wd_what ? wd_what : "?");
_exit(2);
}
void watchdog_arm(unsigned seconds, const char *what) {
wd_what = what;
signal(SIGALRM, wd_fire);
alarm(seconds);
}
void watchdog_disarm(void) { alarm(0); }
/* -------------------------------- framework -------------------------------- */
int tests_run, tests_failed;
+53
View File
@@ -0,0 +1,53 @@
/*
* support.h — test harness API for off-target firmware unit tests.
*
* Provides the mock environment (serial ring, command buffers, output capture)
* plus a tiny assertion framework. The firmware sources under test are compiled
* unmodified against this; only the hardware edges are mocked here.
*/
#ifndef TEST_SUPPORT_H
#define TEST_SUPPORT_H
#include <stdint.h>
#include <stddef.h>
/* Distinct-named string helpers so host code needn't include <string.h>, which
* would clash with the firmware's own memset/strlen/strcpy prototypes in
* rtl837x_common.h (different 8051 signatures). */
static inline void t_memset(void *d, int c, size_t n) {
unsigned char *p = d; while (n--) *p++ = (unsigned char)c;
}
static inline size_t t_strlen(const char *s) { const char *p = s; while (*p) p++; return (size_t)(p - s); }
static inline int t_strcmp(const char *a, const char *b) {
while (*a && *a == *b) { a++; b++; } return (unsigned char)*a - (unsigned char)*b;
}
/* ---- captured terminal output (what the firmware would send over serial) ---- */
extern char out_buf[8192];
extern size_t out_len;
void out_reset(void);
/* ---- editor/parser environment reset ---- */
void env_reset(void); /* clears ring, buffers, editor state */
/* ---- drive the serial input ring exactly like the RX ISR would ----
* Pushes bytes into the 16-byte sbuf ring, draining via cmd_edit() whenever the
* ring would overflow, then a final drain. Mirrors real ISR→editor handoff.
*/
void feed(const char *s); /* NUL-terminated */
void feed_n(const char *s, size_t n); /* explicit length (for embedded NULs) */
/* ---- watchdog: arm before calling into code that could spin (C4 hang) ---- */
void watchdog_arm(unsigned seconds, const char *what);
void watchdog_disarm(void);
/* ---- minimal test framework ---- */
extern int tests_run, tests_failed;
#define CHECK(cond, msg) do { \
tests_run++; \
if (!(cond)) { tests_failed++; \
printf(" FAIL: %s (%s:%d)\n", (msg), __FILE__, __LINE__); \
} else { printf(" ok: %s\n", (msg)); } \
} while (0)
#endif /* TEST_SUPPORT_H */
Loaded 100 of 113 files, more files were not shown because too many files have changed in this diff. Show more